diff options
author | b-r-o-c-k <brockmammen@gmail.com> | 2018-04-14 14:17:51 -0500 |
---|---|---|
committer | b-r-o-c-k <brockmammen@gmail.com> | 2018-04-14 14:17:51 -0500 |
commit | ad999eaa775d7d4b0cacedb30c6ea3a0ee699a6f (patch) | |
tree | 92de2079e80f5f289dd87a54af123cb7d90c3058 | |
parent | 78bc52ea5397c092d01cd08296fe1dc85d998329 (diff) | |
parent | ef4feab0e75be19c5f41d70a001db980b72090f5 (diff) | |
download | rneovim-ad999eaa775d7d4b0cacedb30c6ea3a0ee699a6f.tar.gz rneovim-ad999eaa775d7d4b0cacedb30c6ea3a0ee699a6f.tar.bz2 rneovim-ad999eaa775d7d4b0cacedb30c6ea3a0ee699a6f.zip |
Merge branch 'master' into s-dash-stdin
473 files changed, 45782 insertions, 11508 deletions
diff --git a/.gitignore b/.gitignore index ddbcc9c60d..85b371b926 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Tools +.ropeproject/ + # Build/deps dir /build/ /cmake-build-debug/ @@ -17,13 +20,13 @@ tags /src/nvim/po/vim.pot /src/nvim/po/*.ck -# generated by tests with $NVIM_LOG_FILE set. +# Generated by tests with $NVIM_LOG_FILE set. /.nvimlog -# Files generated by scripts/vim-patch.sh +# Generated by scripts/vim-patch.sh /.vim-src/ -# Files generated by the tests +# Generated by old (Vim) tests. /src/nvim/testdir/del /src/nvim/testdir/test*.out /src/nvim/testdir/test*.res @@ -36,10 +39,10 @@ tags /src/nvim/testdir/valgrind.* /src/nvim/testdir/.gdbinit -# Folder generated by the unit tests +# Generated by unit tests. /test/includes/post/ -# generated by luacheck during `make testlint' +# Generated by luacheck during `make testlint' /test/.luacheckcache # local make targets diff --git a/.travis.yml b/.travis.yml index 2bab1635ad..3cd21170b7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,13 +4,8 @@ language: c env: global: - # To force rebuilding of third-party dependencies, set this to 'true'. - - BUILD_NVIM_DEPS=false - # Travis has 1.5 virtual cores according to - # http://docs.travis-ci.com/user/speeding-up-the-build/#Paralellizing-your-build-on-one-VM - - MAKE_CMD="make -j2" - # Update PATH for pip. - - PATH="$(python2.7 -c 'import site; print(site.getuserbase())')/bin:/usr/lib/llvm-symbolizer-4.0/bin:$PATH" + # Set "false" to force rebuild of third-party dependencies. + - CACHE_ENABLE=true # Build directory for Neovim. - BUILD_DIR="$TRAVIS_BUILD_DIR/build" # Build directory for third-party dependencies. @@ -41,9 +36,7 @@ env: - UBSAN_OPTIONS="print_stacktrace=1 log_path=$LOG_DIR/ubsan" # Environment variables for Valgrind. - VALGRIND_LOG="$LOG_DIR/valgrind-%p.log" - # Cache marker for third-party dependencies cache. - # If this file exists, we know that the cache contains compiled - # dependencies and we can use it. + # If this file exists, the cache is valid (compile was successful). - CACHE_MARKER="$HOME/.cache/nvim-deps/.travis_cache_marker" # default target name for functional tests - FUNCTIONALTEST=functionaltest @@ -51,40 +44,41 @@ env: jobs: include: - - stage: sanitizers + - stage: normal builds os: linux - compiler: clang-4.0 + compiler: clang env: > CLANG_SANITIZER=ASAN_UBSAN CMAKE_FLAGS="$CMAKE_FLAGS -DPREFER_LUA=ON" - - stage: normal builds - os: linux - compiler: gcc-5 - env: FUNCTIONALTEST=functionaltest-lua + sudo: true + - os: linux + compiler: gcc + env: > + FUNCTIONALTEST=functionaltest-lua + CMAKE_FLAGS="$CMAKE_FLAGS -DPREFER_LUA=ON" + DEPS_CMAKE_FLAGS="$DEPS_CMAKE_FLAGS -DUSE_BUNDLED_LUAJIT=OFF" - os: linux - # Travis creates a cache per compiler. - # Set a different value here to store 32-bit - # dependencies in a separate cache. - compiler: gcc-5 -m32 + # Travis creates a cache per compiler. Set a different value here to + # store 32-bit dependencies in a separate cache. + compiler: gcc -m32 env: BUILD_32BIT=ON - os: osx compiler: clang osx_image: xcode7.3 # macOS 10.11 - os: osx - compiler: gcc-4.9 + compiler: gcc osx_image: xcode7.3 # macOS 10.11 - - stage: lint - os: linux + - os: linux env: CI_TARGET=lint - stage: Flaky builds os: linux - compiler: gcc-5 - env: GCOV=gcov-5 CMAKE_FLAGS="$CMAKE_FLAGS -DUSE_GCOV=ON" + compiler: gcc + env: GCOV=gcov CMAKE_FLAGS="$CMAKE_FLAGS -DUSE_GCOV=ON" - os: linux - compiler: clang-4.0 + compiler: clang env: CLANG_SANITIZER=TSAN allow_failures: - - env: GCOV=gcov-5 CMAKE_FLAGS="$CMAKE_FLAGS -DUSE_GCOV=ON" + - env: GCOV=gcov CMAKE_FLAGS="$CMAKE_FLAGS -DUSE_GCOV=ON" - env: CLANG_SANITIZER=TSAN fast_finish: true @@ -97,27 +91,22 @@ after_success: ci/after_success.sh addons: apt: - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-trusty-4.0 packages: - autoconf - automake - apport - build-essential - - clang-4.0 + - clang - cmake - cscope - - g++-5-multilib - g++-multilib - - gcc-5-multilib - gcc-multilib - gdb - language-pack-tr - libc6-dev-i386 - libtool - - llvm-4.0-dev - locales + - ninja-build - pkg-config - unzip - valgrind @@ -132,6 +121,7 @@ cache: directories: - "$HOME/.cache/pip" - "$HOME/.cache/nvim-deps" + - "$HOME/.cache/nvim-deps-downloads" notifications: webhooks: diff --git a/CMakeLists.txt b/CMakeLists.txt index cad3ea6786..47e873a845 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,11 @@ list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") include(PreventInTreeBuilds) # Prefer our bundled versions of dependencies. +if(DEFINED ENV{DEPS_BUILD_DIR}) +set(DEPS_PREFIX "$ENV{DEPS_BUILD_DIR}/usr" CACHE PATH "Path prefix for finding dependencies") +else() set(DEPS_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/.deps/usr" CACHE PATH "Path prefix for finding dependencies") +endif() if(CMAKE_CROSSCOMPILING AND NOT UNIX) list(INSERT CMAKE_FIND_ROOT_PATH 0 ${DEPS_PREFIX}) list(INSERT CMAKE_PREFIX_PATH 0 ${DEPS_PREFIX}/../host/bin) @@ -63,14 +67,14 @@ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY # If not in a git repo (e.g., a tarball) these tokens define the complete # version string, else they are combined with the result of `git describe`. set(NVIM_VERSION_MAJOR 0) -set(NVIM_VERSION_MINOR 2) -set(NVIM_VERSION_PATCH 3) +set(NVIM_VERSION_MINOR 3) +set(NVIM_VERSION_PATCH 0) set(NVIM_VERSION_PRERELEASE "-dev") # for package maintainers # API level -set(NVIM_API_LEVEL 3) # Bump this after any API change. +set(NVIM_API_LEVEL 4) # Bump this after any API change. set(NVIM_API_LEVEL_COMPAT 0) # Adjust this after a _breaking_ API change. -set(NVIM_API_PRERELEASE false) +set(NVIM_API_PRERELEASE true) file(TO_CMAKE_PATH ${CMAKE_CURRENT_LIST_DIR}/.git FORCED_GIT_DIR) include(GetGitRevisionDescription) @@ -204,6 +208,7 @@ int main(void) if(MSVC) # XXX: /W4 gives too many warnings. #3241 add_definitions(/W3 -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE) + add_definitions(-DWIN32) else() add_definitions(-Wall -Wextra -pedantic -Wno-unused-parameter -Wstrict-prototypes -std=gnu99) @@ -223,6 +228,9 @@ endif() if(MINGW) # Use POSIX compatible stdio in Mingw add_definitions(-D__USE_MINGW_ANSI_STDIO) +endif() +if(WIN32) + # Windows Vista is the minimum supported version add_definitions(-D_WIN32_WINNT=0x0600) endif() @@ -250,13 +258,14 @@ if(HAS_DIAG_COLOR_FLAG) endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.8.5") - # Array-bounds testing is broken in some GCC versions before 4.8.5. - # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56273 - check_c_compiler_flag(-Wno-array-bounds HAS_NO_ARRAY_BOUNDS_FLAG) - if(HAS_NO_ARRAY_BOUNDS_FLAG) - add_definitions(-Wno-array-bounds) - endif() + # 1. Array-bounds testing is broken in some GCC versions before 4.8.5. + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56273 + # 2. But _Pragma("...ignored") is broken (unresolved) in GCC 5+: + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66099 + # So we must disable -Warray-bounds globally for GCC (for kbtree.h, #7083). + check_c_compiler_flag(-Wno-array-bounds HAS_NO_ARRAY_BOUNDS_FLAG) + if(HAS_NO_ARRAY_BOUNDS_FLAG) + add_definitions(-Wno-array-bounds) endif() endif() @@ -277,6 +286,8 @@ else() set(DEBUG 0) endif() +option(LOG_LIST_ACTIONS "Add list actions logging" OFF) + add_definitions(-DINCLUDE_GENERATED_DECLARATIONS) if(CMAKE_COMPILER_IS_GNUCXX AND CMAKE_SYSTEM_NAME STREQUAL "Linux") @@ -505,17 +516,6 @@ if(BUSTED_PRG) get_property(TEST_INCLUDE_DIRS DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES) - # Set policy CMP0026 to OLD so we avoid CMake warnings on newer - # versions of cmake. - if(POLICY CMP0026) - cmake_policy(SET CMP0026 OLD) - endif() - if(CMAKE_GENERATOR MATCHES "Visual Studio") - set(TEST_LIBNVIM_PATH ${CMAKE_BINARY_DIR}/lib/nvim-test.dll) - else() - get_target_property(TEST_LIBNVIM_PATH nvim-test LOCATION) - endif() - # When running tests from 'ninja' we need to use the # console pool: to do so we need to use the USES_TERMINAL # option, but this is only available in CMake 3.2 @@ -524,10 +524,6 @@ if(BUSTED_PRG) list(APPEND TEST_TARGET_ARGS "USES_TERMINAL") endif() - configure_file( - test/config/paths.lua.in - ${CMAKE_BINARY_DIR}/test/config/paths.lua) - set(UNITTEST_PREREQS nvim-test unittest-headers) set(FUNCTIONALTEST_PREREQS nvim printargs-test shell-test) if(NOT WIN32) @@ -564,6 +560,32 @@ if(BUSTED_PRG) message(WARNING "disabling unit tests: no Luajit FFI in ${LUA_PRG}") endif() + if(${CMAKE_VERSION} VERSION_LESS 2.8.12) + if(CMAKE_GENERATOR MATCHES "Visual Studio") + set(TEST_LIBNVIM_PATH ${CMAKE_BINARY_DIR}/lib/nvim-test.dll) + else() + get_target_property(TEST_LIBNVIM_PATH nvim-test LOCATION) + endif() + configure_file( + ${CMAKE_SOURCE_DIR}/test/config/paths.lua.in + ${CMAKE_BINARY_DIR}/test/config/paths.lua) + else() + # To avoid duplicating paths.lua.in while we still support CMake < 2.8.12, + # use configure_file() to add the generator expression and then generate + # the final file + if(LUA_HAS_FFI) + set(TEST_LIBNVIM_PATH $<TARGET_FILE:nvim-test>) + else() + set(TEST_LIBNVIM_PATH "") + endif() + configure_file( + ${CMAKE_SOURCE_DIR}/test/config/paths.lua.in + ${CMAKE_BINARY_DIR}/test/config/paths.lua.gen) + file(GENERATE + OUTPUT ${CMAKE_BINARY_DIR}/test/config/paths.lua + INPUT ${CMAKE_BINARY_DIR}/test/config/paths.lua.gen) + endif() + add_custom_target(functionaltest COMMAND ${CMAKE_COMMAND} -DBUSTED_PRG=${BUSTED_PRG} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 20bce8ca62..05a97ebf18 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,7 @@ low-risk/isolated tasks: Developer guidelines -------------------- -- Nvim developers should read `:help dev-help`. +- Nvim developers should read `:help dev`. - External UI developers should read `:help dev-ui`. Reporting problems @@ -24,7 +24,7 @@ Reporting problems - Search [existing issues][github-issues] (including closed!) - Update Neovim to the latest version to see if your problem persists. - Disable plugins incrementally, to narrow down the cause of the issue. -- When reporting a crash, include a stacktrace. +- When reporting a crash, [include a stacktrace](https://github.com/neovim/neovim/wiki/Development-tips#backtrace-linux). - [Bisect][git-bisect] to the cause of a regression, if you are able. This is _extremely_ helpful. - Check `$NVIM_LOG_FILE`, if it exists. - Include `cmake --system-information` for **build** issues. @@ -92,7 +92,7 @@ and [AppVeyor]. - CI builds are compiled with [`-Werror`][gcc-warnings], so compiler warnings will fail the build. - If any tests fail, the build will fail. - See [Building Neovim#running-tests][wiki-run-tests] to run tests locally. + See [test/README.md#running-tests][run-tests] to run tests locally. Passing locally doesn't guarantee passing the CI build, because of the different compilers and platforms tested against. - CI runs [ASan] and other analyzers. @@ -168,7 +168,7 @@ as context, use the `-W` argument as well. [hygiene]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html [style-guide]: http://neovim.io/develop/style-guide.xml [ASan]: http://clang.llvm.org/docs/AddressSanitizer.html -[wiki-run-tests]: https://github.com/neovim/neovim/wiki/Building-Neovim#running-tests +[run-tests]: https://github.com/neovim/neovim/blob/master/test/README.md#running-tests [wiki-faq]: https://github.com/neovim/neovim/wiki/FAQ [review-checklist]: https://github.com/neovim/neovim/wiki/Code-review-checklist [3174]: https://github.com/neovim/neovim/issues/3174 @@ -1,3 +1,4 @@ +THIS_DIR = $(shell pwd) filter-false = $(strip $(filter-out 0 off OFF false FALSE,$1)) filter-true = $(strip $(filter-out 1 on ON true TRUE,$1)) @@ -8,11 +9,14 @@ CMAKE_PRG ?= $(shell (command -v cmake3 || echo cmake)) CMAKE_BUILD_TYPE ?= Debug CMAKE_FLAGS := -DCMAKE_BUILD_TYPE=$(CMAKE_BUILD_TYPE) -DOC_DOWNLOAD_URL_BASE := https://raw.githubusercontent.com/neovim/doc/gh-pages -CLINT_ERRORS_FILE_PATH := /reports/clint/errors.json BUILD_TYPE ?= $(shell (type ninja > /dev/null 2>&1 && echo "Ninja") || \ echo "Unix Makefiles") +DEPS_BUILD_DIR ?= .deps + +ifneq (1,$(words [$(DEPS_BUILD_DIR)])) + $(error DEPS_BUILD_DIR must not contain whitespace) +endif ifeq (,$(BUILD_TOOL)) ifeq (Ninja,$(BUILD_TYPE)) @@ -48,7 +52,7 @@ endif ifneq (,$(findstring functionaltest-lua,$(MAKECMDGOALS))) BUNDLED_LUA_CMAKE_FLAG := -DUSE_BUNDLED_LUA=ON - $(shell [ -x .deps/usr/bin/lua ] || rm build/.ran-*) + $(shell [ -x $(DEPS_BUILD_DIR)/usr/bin/lua ] || rm build/.ran-*) endif # For use where we want to make sure only a single job is run. This does issue @@ -68,20 +72,20 @@ cmake: $(MAKE) build/.ran-cmake build/.ran-cmake: | deps - cd build && $(CMAKE_PRG) -G '$(BUILD_TYPE)' $(CMAKE_FLAGS) $(CMAKE_EXTRA_FLAGS) .. + cd build && $(CMAKE_PRG) -G '$(BUILD_TYPE)' $(CMAKE_FLAGS) $(CMAKE_EXTRA_FLAGS) $(THIS_DIR) touch $@ deps: | build/.ran-third-party-cmake ifeq ($(call filter-true,$(USE_BUNDLED_DEPS)),) - +$(BUILD_CMD) -C .deps + +$(BUILD_CMD) -C $(DEPS_BUILD_DIR) endif build/.ran-third-party-cmake: ifeq ($(call filter-true,$(USE_BUNDLED_DEPS)),) - mkdir -p .deps - cd .deps && \ + mkdir -p $(DEPS_BUILD_DIR) + cd $(DEPS_BUILD_DIR) && \ $(CMAKE_PRG) -G '$(BUILD_TYPE)' $(BUNDLED_CMAKE_FLAG) $(BUNDLED_LUA_CMAKE_FLAG) \ - $(DEPS_CMAKE_FLAGS) ../third-party + $(DEPS_CMAKE_FLAGS) $(THIS_DIR)/third-party endif mkdir -p build touch $@ @@ -124,7 +128,7 @@ clean: $(MAKE) -C runtime/doc clean distclean: clean - rm -rf .deps build + rm -rf $(DEPS_BUILD_DIR) build install: | nvim +$(BUILD_CMD) -C build install @@ -141,6 +145,11 @@ check-single-includes: build/.ran-cmake appimage: bash scripts/genappimage.sh +# Build an appimage with embedded update information appimage-nightly for +# nightly builds or appimage-latest for a release +appimage-%: + bash scripts/genappimage.sh $* + lint: check-single-includes clint testlint lualint .PHONY: test testlint lualint functionaltest unittest lint clint clean distclean nvim libnvim cmake deps install appimage @@ -14,7 +14,7 @@ [](https://neovim.io/doc/reports/pvs) [](https://buildd.debian.org/neovim) -[](https://github.com/neovim/neovim/releases/) +[](https://github.com/neovim/neovim/releases/) Neovim is a project that seeks to aggressively refactor Vim in order to: @@ -34,11 +34,15 @@ Install from source make CMAKE_BUILD_TYPE=RelWithDebInfo sudo make install -To install to a non-default location, specify `CMAKE_INSTALL_PREFIX`: +To install to a non-default location, set `CMAKE_INSTALL_PREFIX`: make CMAKE_EXTRA_FLAGS="-DCMAKE_INSTALL_PREFIX=/full/path/" make install +To list all targets: + + cmake --build build --target help + See [the wiki](https://github.com/neovim/neovim/wiki/Building-Neovim) for details. Install from package diff --git a/appveyor.yml b/appveyor.yml index 2d6135c7a2..75ca74d0d5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,10 @@ version: '{build}' environment: APPVEYOR_CACHE_ENTRY_ZIP_ARGS: "-t7z -m0=lzma -mx=9" +image: Visual Studio 2017 configuration: +- MSVC_64 +- MSVC_32 - MINGW_64 - MINGW_32 - MINGW_64-gcov @@ -9,10 +12,12 @@ matrix: allow_failures: - configuration: MINGW_64-gcov install: [] +before_build: +- ps: Install-Product node 8 build_script: -- call ci\build.bat +- powershell ci\build.ps1 cache: -- C:\msys64\var\cache\pacman\pkg -> ci\build.bat +- C:\msys64\var\cache\pacman\pkg -> ci\build.ps1 - .deps -> third-party\** artifacts: - path: build/Neovim.zip diff --git a/ci/after_success.sh b/ci/after_success.sh deleted file mode 100755 index 388b6eb714..0000000000 --- a/ci/after_success.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -o pipefail - -if [[ -n "${GCOV}" ]]; then - coveralls --gcov "$(which "${GCOV}")" --encoding iso-8859-1 || echo 'coveralls upload failed.' - bash <(curl -s https://codecov.io/bash) || echo 'codecov upload failed.' -fi diff --git a/ci/before_cache.sh b/ci/before_cache.sh index 3d7cc0ec5a..d99b8e68c6 100755 --- a/ci/before_cache.sh +++ b/ci/before_cache.sh @@ -4,16 +4,24 @@ set -e set -o pipefail CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${CI_DIR}/common/build.sh" source "${CI_DIR}/common/suite.sh" # Don't cache pip's log and selfcheck. rm -rf "${HOME}/.cache/pip/log" rm -f "${HOME}/.cache/pip/selfcheck.json" +echo "before_cache.sh: cache size" +du -d 2 "${HOME}/.cache" | sort -n + # Update the third-party dependency cache only if the build was successful. if ended_successfully; then rm -rf "${HOME}/.cache/nvim-deps" mv "${DEPS_BUILD_DIR}" "${HOME}/.cache/nvim-deps" + + rm -rf "${HOME}/.cache/nvim-deps-downloads" + mv "${DEPS_DOWNLOAD_DIR}" "${HOME}/.cache/nvim-deps-downloads" + touch "${CACHE_MARKER}" - echo "Updated third-party dependencies (timestamp: $(stat -c '%y' "${CACHE_MARKER}"))." + echo "Updated third-party dependencies (timestamp: $(_stat "${CACHE_MARKER}"))." fi diff --git a/ci/before_install.sh b/ci/before_install.sh index f84ad935bc..9a3e192536 100755 --- a/ci/before_install.sh +++ b/ci/before_install.sh @@ -23,17 +23,26 @@ echo 'python info:' 2>&1 pyenv versions || true ) | sed 's/^/ /' -echo "Upgrade Python 2 pip." -pip2.7 -q install --user --upgrade pip - if [[ "${TRAVIS_OS_NAME}" == osx ]]; then - echo "Install Python 3." - brew install python3 + echo "Upgrade Python 3." + brew upgrade python echo "Upgrade Python 3 pip." pip3 -q install --user --upgrade pip else + echo "Upgrade Python 2 pip." + pip2.7 -q install --user --upgrade pip echo "Upgrade Python 3 pip." # Allow failure. pyenv pip3 on travis is broken: # https://github.com/travis-ci/travis-ci/issues/8363 pip3 -q install --user --upgrade pip || true fi + +echo "Install node (LTS)" + +if [[ "${TRAVIS_OS_NAME}" == osx ]] || [ ! -f ~/.nvm/nvm.sh ]; then + curl -o ~/.nvm/nvm.sh https://raw.githubusercontent.com/creationix/nvm/master/nvm.sh +fi + +source ~/.nvm/nvm.sh +nvm install --lts +nvm use --lts diff --git a/ci/build.bat b/ci/build.bat deleted file mode 100644 index 6eb22176a9..0000000000 --- a/ci/build.bat +++ /dev/null @@ -1,62 +0,0 @@ -:: These are native MinGW builds, but they use the toolchain inside -:: MSYS2, this allows using all the dependencies and tools available -:: in MSYS2, but we cannot build inside the MSYS2 shell. -echo on -if "%CONFIGURATION%" == "MINGW_32" ( - set ARCH=i686 - set BITS=32 -) else ( - set ARCH=x86_64 - set BITS=64 -) -if "%CONFIGURATION%" == "MINGW_64-gcov" ( - set USE_GCOV="-DUSE_GCOV=ON" -) - -:: We cannot have sh.exe in the PATH (MinGW) -set PATH=%PATH:C:\Program Files\Git\usr\bin;=% -set PATH=C:\msys64\mingw%BITS%\bin;C:\Windows\System32;C:\Windows;%PATH% -:: The default cpack in the PATH is not CMake -set PATH=C:\Program Files (x86)\CMake\bin\cpack.exe;%PATH% - -:: Build third-party dependencies -C:\msys64\usr\bin\bash -lc "pacman --verbose --noconfirm -Su" || goto :error -C:\msys64\usr\bin\bash -lc "pacman --verbose --noconfirm --needed -S mingw-w64-%ARCH%-cmake mingw-w64-%ARCH%-perl mingw-w64-%ARCH%-diffutils mingw-w64-%ARCH%-unibilium gperf" || goto :error - -:: Setup python (use AppVeyor system python) -C:\Python27\python.exe -m pip install neovim || goto :error -C:\Python35\python.exe -m pip install neovim || goto :error -:: Disambiguate python3 -move c:\Python35\python.exe c:\Python35\python3.exe -set PATH=C:\Python35;C:\Python27;%PATH% -:: Sanity check -python -c "import neovim; print(str(neovim))" || goto :error -python3 -c "import neovim; print(str(neovim))" || goto :error - -mkdir .deps -cd .deps -cmake -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=RelWithDebInfo ..\third-party\ || goto :error -mingw32-make VERBOSE=1 || goto :error -cd .. - -:: Build Neovim -mkdir build -cd build -cmake -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUSTED_OUTPUT_TYPE=nvim %USE_GCOV% -DGPERF_PRG="C:\msys64\usr\bin\gperf.exe" .. || goto :error -mingw32-make VERBOSE=1 || goto :error -bin\nvim --version || goto :error - -:: Functional tests -mingw32-make functionaltest VERBOSE=1 || goto :error - -if defined USE_GCOV ( - C:\msys64\usr\bin\bash -lc "cd /c/projects/neovim; bash <(curl -s https://codecov.io/bash) || echo 'codecov upload failed.'" -) - -:: Build artifacts -cpack -G ZIP -C RelWithDebInfo -if defined APPVEYOR_REPO_TAG_NAME cpack -G NSIS -C RelWithDebInfo - -goto :EOF -:error -exit /b %errorlevel% diff --git a/ci/build.ps1 b/ci/build.ps1 new file mode 100644 index 0000000000..71cc7c5b50 --- /dev/null +++ b/ci/build.ps1 @@ -0,0 +1,128 @@ +Set-PSDebug -Trace 1 + +$env:CONFIGURATION -match '^(?<compiler>\w+)_(?<bits>32|64)(?:-(?<option>\w+))?$' +$compiler = $Matches.compiler +$compileOption = $Matches.option +$bits = $Matches.bits +$cmakeBuildType = 'RelWithDebInfo' +$depsCmakeVars = @{ + CMAKE_BUILD_TYPE = $cmakeBuildType; +} +$nvimCmakeVars = @{ + CMAKE_BUILD_TYPE = $cmakeBuildType; + BUSTED_OUTPUT_TYPE = 'nvim'; +} + +# For pull requests, skip some build configurations to save time. +if ($env:APPVEYOR_PULL_REQUEST_HEAD_COMMIT -and $env:CONFIGURATION -match '^(MSVC_64|MINGW_32)$') { + exit 0 +} + +function exitIfFailed() { + if ($LastExitCode -ne 0) { + exit $LastExitCode + } +} + +if ($compiler -eq 'MINGW') { + if ($bits -eq 32) { + $arch = 'i686' + } + elseif ($bits -eq 64) { + $arch = 'x86_64' + } + if ($compileOption -eq 'gcov') { + $nvimCmakeVars['USE_GCOV'] = 'ON' + $uploadToCodecov = $true + } + # These are native MinGW builds, but they use the toolchain inside + # MSYS2, this allows using all the dependencies and tools available + # in MSYS2, but we cannot build inside the MSYS2 shell. + $cmakeGenerator = 'MinGW Makefiles' + $cmakeGeneratorArgs = 'VERBOSE=1' + + # Add MinGW to the PATH + $env:PATH = "C:\msys64\mingw$bits\bin;$env:PATH" + + # Build third-party dependencies + C:\msys64\usr\bin\bash -lc "pacman --verbose --noconfirm -Su" ; exitIfFailed + C:\msys64\usr\bin\bash -lc "pacman --verbose --noconfirm --needed -S mingw-w64-$arch-cmake mingw-w64-$arch-perl mingw-w64-$arch-diffutils mingw-w64-$arch-unibilium" ; exitIfFailed +} +elseif ($compiler -eq 'MSVC') { + $cmakeGeneratorArgs = '/verbosity:normal' + if ($bits -eq 32) { + $cmakeGenerator = 'Visual Studio 15 2017' + } + elseif ($bits -eq 64) { + $cmakeGenerator = 'Visual Studio 15 2017 Win64' + } +} + +# Remove Git Unix utilities from the PATH +$env:PATH = $env:PATH.Replace('C:\Program Files\Git\usr\bin', '') + +# Setup python (use AppVeyor system python) +C:\Python27\python.exe -m pip install neovim ; exitIfFailed +C:\Python35\python.exe -m pip install neovim ; exitIfFailed +# Disambiguate python3 +move c:\Python35\python.exe c:\Python35\python3.exe +$env:PATH = "C:\Python35;C:\Python27;$env:PATH" +# Sanity check +python -c "import neovim; print(str(neovim))" ; exitIfFailed +python3 -c "import neovim; print(str(neovim))" ; exitIfFailed + +$env:PATH = "C:\Ruby24\bin;$env:PATH" +cmd /c gem.cmd install neovim ; exitIfFailed +where.exe neovim-ruby-host.bat ; exitIfFailed + +cmd /c npm.cmd install -g neovim ; exitIfFailed +where.exe neovim-node-host.cmd ; exitIfFailed + +function convertToCmakeArgs($vars) { + return $vars.GetEnumerator() | foreach { "-D$($_.Key)=$($_.Value)" } +} + +mkdir .deps +cd .deps +cmake -G $cmakeGenerator $(convertToCmakeArgs($depsCmakeVars)) ..\third-party\ ; exitIfFailed +cmake --build . --config $cmakeBuildType -- $cmakeGeneratorArgs ; exitIfFailed +cd .. + +# Build Neovim +mkdir build +cd build +cmake -G $cmakeGenerator $(convertToCmakeArgs($nvimCmakeVars)) .. ; exitIfFailed +cmake --build . --config $cmakeBuildType -- $cmakeGeneratorArgs ; exitIfFailed +bin\nvim --version ; exitIfFailed + +# Functional tests +# The $LastExitCode from MSBuild can't be trusted +$failed = $false +# Temporarily turn off tracing to reduce log file output +Set-PSDebug -Off +cmake --build . --config $cmakeBuildType --target functionaltest -- $cmakeGeneratorArgs | + foreach { $failed = $failed -or + $_ -match 'Running functional tests failed with error'; $_ } +Set-PSDebug -Trace 1 +if ($failed) { + exit $LastExitCode +} + + +if ($uploadToCodecov) { + C:\msys64\usr\bin\bash -lc "cd /c/projects/neovim; bash <(curl -s https://codecov.io/bash) -c -F functionaltest || echo 'codecov upload failed.'" +} + +# Old tests +$env:PATH += ';C:\msys64\usr\bin' +& "C:\msys64\mingw$bits\bin\mingw32-make.exe" -C $(Convert-Path ..\src\nvim\testdir) VERBOSE=1 + +if ($uploadToCodecov) { + C:\msys64\usr\bin\bash -lc "cd /c/projects/neovim; bash <(curl -s https://codecov.io/bash) -c -F oldtest || echo 'codecov upload failed.'" +} + +# Build artifacts +cpack -G ZIP -C RelWithDebInfo +if ($env:APPVEYOR_REPO_TAG_NAME -ne $null) { + cpack -G NSIS -C RelWithDebInfo +} diff --git a/ci/common/build.sh b/ci/common/build.sh index f398a1a1cc..80973b64c9 100644 --- a/ci/common/build.sh +++ b/ci/common/build.sh @@ -1,5 +1,16 @@ +_stat() { + if test "${TRAVIS_OS_NAME}" = osx ; then + stat -f %Sm "${@}" + else + stat -c %y "${@}" + fi +} + top_make() { - ${MAKE_CMD} "$@" + echo '================================================================================' + # Travis has 1.5 virtual cores according to: + # http://docs.travis-ci.com/user/speeding-up-the-build/#Paralellizing-your-build-on-one-VM + ninja "$@" } build_make() { @@ -15,28 +26,21 @@ build_deps() { DEPS_CMAKE_FLAGS="${DEPS_CMAKE_FLAGS} -DUSE_BUNDLED_LUA=ON" fi - rm -rf "${DEPS_BUILD_DIR}" - - # If there is a valid cache and we're not forced to recompile, - # use cached third-party dependencies. - if test -f "${CACHE_MARKER}" && test "${BUILD_NVIM_DEPS}" != "true" ; then - local statcmd="stat -c '%y'" - if test "${TRAVIS_OS_NAME}" = osx ; then - statcmd="stat -f '%Sm'" - fi - echo "Using third-party dependencies from Travis's cache (last updated: $(${statcmd} "${CACHE_MARKER}"))." + mkdir -p "${DEPS_BUILD_DIR}" + mkdir -p "${DEPS_DOWNLOAD_DIR}" - mkdir -p "$(dirname "${DEPS_BUILD_DIR}")" - mv "${HOME}/.cache/nvim-deps" "${DEPS_BUILD_DIR}" - else - mkdir -p "${DEPS_BUILD_DIR}" + # Use cached dependencies if $CACHE_MARKER exists. + if test -f "${CACHE_MARKER}" && ! test "${CACHE_ENABLE}" = "false" ; then + echo "Using third-party dependencies from Travis cache (last update: $(_stat "${CACHE_MARKER}"))." + cp -r "${HOME}/.cache/nvim-deps" "${DEPS_BUILD_DIR}" + cp -r "${HOME}/.cache/nvim-deps-downloads" "${DEPS_DOWNLOAD_DIR}" fi # Even if we're using cached dependencies, run CMake and make to # update CMake configuration and update to newer deps versions. cd "${DEPS_BUILD_DIR}" echo "Configuring with '${DEPS_CMAKE_FLAGS}'." - CC= cmake ${DEPS_CMAKE_FLAGS} "${TRAVIS_BUILD_DIR}/third-party/" + CC= cmake -G Ninja ${DEPS_CMAKE_FLAGS} "${TRAVIS_BUILD_DIR}/third-party/" if ! top_make; then exit 1 @@ -56,7 +60,7 @@ prepare_build() { mkdir -p "${BUILD_DIR}" cd "${BUILD_DIR}" echo "Configuring with '${CMAKE_FLAGS} $@'." - cmake ${CMAKE_FLAGS} "$@" "${TRAVIS_BUILD_DIR}" + cmake -G Ninja ${CMAKE_FLAGS} "$@" "${TRAVIS_BUILD_DIR}" } build_nvim() { @@ -71,9 +75,11 @@ build_nvim() { exit 1 fi - echo "Building nvim-test." - if ! top_make nvim-test ; then - exit 1 + if test "${FUNCTIONALTEST}" != "functionaltest-lua"; then + echo "Building nvim-test." + if ! top_make nvim-test ; then + exit 1 + fi fi fi @@ -87,3 +93,12 @@ build_nvim() { cd "${TRAVIS_BUILD_DIR}" } + +macos_rvm_dance() { + # neovim-ruby gem requires a ruby newer than the macOS default. + source ~/.rvm/scripts/rvm + rvm get stable --auto-dotfiles + rvm reload + rvm use 2.2.5 + rvm use +} diff --git a/ci/common/test.sh b/ci/common/test.sh index 55f76ca798..bc80dfead7 100644 --- a/ci/common/test.sh +++ b/ci/common/test.sh @@ -1,6 +1,15 @@ . "${CI_DIR}/common/build.sh" . "${CI_DIR}/common/suite.sh" +submit_coverage() { + if [ -n "${GCOV}" ]; then + if curl --fail --output codecov.bash --silent https://codecov.io/bash; then + bash codecov.bash -c -F "$1" || echo "codecov upload failed." + rm -f codecov.bash + fi + fi +} + print_core() { local app="$1" local core="$2" @@ -71,7 +80,9 @@ valgrind_check() { } asan_check() { - check_logs "${1}" "*san.*" + if test "${CLANG_SANITIZER}" = "ASAN_UBSAN" ; then + check_logs "${1}" "*san.*" | asan_symbolize + fi } run_unittests() {( @@ -80,6 +91,7 @@ run_unittests() {( if ! build_make unittest ; then fail 'unittests' F 'Unit tests failed' fi + submit_coverage unittest check_core_dumps "$(which luajit)" exit_suite )} @@ -90,6 +102,7 @@ run_functionaltests() {( if ! build_make ${FUNCTIONALTEST}; then fail 'functionaltests' F 'Functional tests failed' fi + submit_coverage functionaltest asan_check "${LOG_DIR}" valgrind_check "${LOG_DIR}" check_core_dumps @@ -103,6 +116,7 @@ run_oldtests() {( reset fail 'oldtests' F 'Legacy tests failed' fi + submit_coverage oldtest asan_check "${LOG_DIR}" valgrind_check "${LOG_DIR}" check_core_dumps diff --git a/ci/install.sh b/ci/install.sh index c8a0c8825d..053549d6db 100755 --- a/ci/install.sh +++ b/ci/install.sh @@ -8,18 +8,26 @@ if [[ "${CI_TARGET}" == lint ]]; then fi if [[ "${TRAVIS_OS_NAME}" == osx ]]; then + brew install ninja brew install gettext brew reinstall -s libtool fi -# Use default CC to avoid compilation problems when installing Python modules. -echo "Install neovim module and coveralls for Python 2." -CC=cc pip2.7 -q install --user --upgrade neovim cpp-coveralls - echo "Install neovim module for Python 3." # Allow failure. pyenv pip3 on travis is broken: # https://github.com/travis-ci/travis-ci/issues/8363 CC=cc pip3 -q install --user --upgrade neovim || true -echo "Install neovim RubyGem." -gem install --no-document --version ">= 0.2.0" neovim +if ! [ "${TRAVIS_OS_NAME}" = osx ] ; then + # Update PATH for pip. + export PATH="$(python2.7 -c 'import site; print(site.getuserbase())')/bin:$PATH" + # Use default CC to avoid compilation problems when installing Python modules. + echo "Install neovim module for Python 2." + CC=cc pip2.7 -q install --user --upgrade neovim + + echo "Install neovim RubyGem." + gem install --no-document --version ">= 0.2.0" neovim +fi + +echo "Install neovim npm package" +npm install -g neovim diff --git a/ci/run_lint.sh b/ci/run_lint.sh index e7f6727448..ae9adb7c87 100755 --- a/ci/run_lint.sh +++ b/ci/run_lint.sh @@ -10,19 +10,19 @@ source "${CI_DIR}/common/suite.sh" enter_suite 'clint' -run_test 'top_make clint-full' clint +run_test 'make clint-full' clint exit_suite --continue enter_suite 'testlint' -run_test 'top_make testlint' testlint +run_test 'make testlint' testlint exit_suite --continue enter_suite 'lualint' -run_test 'top_make lualint' lualint +run_test 'make lualint' lualint exit_suite --continue @@ -31,7 +31,7 @@ enter_suite single-includes CLICOLOR_FORCE=1 run_test_wd \ --allow-hang \ 10s \ - 'top_make check-single-includes' \ + 'make check-single-includes' \ 'csi_clean' \ single-includes diff --git a/ci/run_tests.sh b/ci/run_tests.sh index a0bf6e010d..c175910da5 100755 --- a/ci/run_tests.sh +++ b/ci/run_tests.sh @@ -22,7 +22,9 @@ enter_suite tests if test "$CLANG_SANITIZER" != "TSAN" ; then # Additional threads are only created when the builtin UI starts, which # doesn't happen in the unit/functional tests - run_test run_unittests + if test "${FUNCTIONALTEST}" != "functionaltest-lua"; then + run_test run_unittests + fi run_test run_functionaltests fi run_test run_oldtests diff --git a/cmake/FindLibIntl.cmake b/cmake/FindLibIntl.cmake index ab4632cf45..f8442566a9 100644 --- a/cmake/FindLibIntl.cmake +++ b/cmake/FindLibIntl.cmake @@ -27,7 +27,7 @@ find_path(LibIntl_INCLUDE_DIR ) find_library(LibIntl_LIBRARY - NAMES intl libintl.a + NAMES intl libintl ) if (LibIntl_INCLUDE_DIR) diff --git a/cmake/FindMsgpack.cmake b/cmake/FindMsgpack.cmake index 6716289a98..cca0a00c20 100644 --- a/cmake/FindMsgpack.cmake +++ b/cmake/FindMsgpack.cmake @@ -44,7 +44,7 @@ endif() if(MSVC) # The import library for the msgpack DLL has a different name - list(APPEND MSGPACK_NAMES msgpack_import) + list(APPEND MSGPACK_NAMES msgpackc_import) else() list(APPEND MSGPACK_NAMES msgpackc msgpack) endif() diff --git a/cmake/GetCompileFlags.cmake b/cmake/GetCompileFlags.cmake index e0994b67bc..527bc88cdd 100644 --- a/cmake/GetCompileFlags.cmake +++ b/cmake/GetCompileFlags.cmake @@ -38,6 +38,7 @@ function(get_compile_flags _compile_flags) get_directory_property(include_directories_list DIRECTORY "src/nvim" INCLUDE_DIRECTORIES) + list(REMOVE_DUPLICATES include_directories_list) foreach(include_directory ${include_directories_list}) set(include_directories "${include_directories} -I${include_directory}") endforeach() diff --git a/cmake/LuaHelpers.cmake b/cmake/LuaHelpers.cmake index 32f7e46a57..3cba47412b 100644 --- a/cmake/LuaHelpers.cmake +++ b/cmake/LuaHelpers.cmake @@ -4,9 +4,8 @@ # Check if a module is available in Lua function(check_lua_module LUA_PRG_PATH MODULE RESULT_VAR) - execute_process(COMMAND ${LUA_PRG_PATH} -e "require('${MODULE}')" - RESULT_VARIABLE module_missing - ERROR_QUIET) + execute_process(COMMAND ${LUA_PRG_PATH} -l "${MODULE}" -e "" + RESULT_VARIABLE module_missing) if(module_missing) set(${RESULT_VAR} False PARENT_SCOPE) else() diff --git a/cmake/RunTests.cmake b/cmake/RunTests.cmake index 95c06aeb94..5b62fd72c9 100644 --- a/cmake/RunTests.cmake +++ b/cmake/RunTests.cmake @@ -38,7 +38,7 @@ set(ENV{SYSTEM_NAME} ${SYSTEM_NAME}) execute_process( COMMAND ${BUSTED_PRG} ${TEST_TAG} ${TEST_FILTER} -v -o ${BUSTED_OUTPUT_TYPE} --lua=${LUA_PRG} --lazy --helper=${TEST_DIR}/${TEST_TYPE}/preload.lua - --lpath=${BUILD_DIR}/?.lua ${TEST_PATH} + --lpath=${BUILD_DIR}/?.lua --lpath=?.lua ${TEST_PATH} WORKING_DIRECTORY ${WORKING_DIR} ERROR_VARIABLE err RESULT_VARIABLE res diff --git a/codecov.yml b/codecov.yml index 0b3de06b97..481eea89ee 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,6 +1,6 @@ codecov: notify: - require_ci_to_pass: yes + require_ci_to_pass: no ci: - appveyor - travis diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt index c3643db205..63cb3cc0d6 100644 --- a/config/CMakeLists.txt +++ b/config/CMakeLists.txt @@ -12,6 +12,11 @@ check_type_size("size_t" SIZEOF_SIZE_T) check_type_size("long long" SIZEOF_LONG_LONG) check_type_size("void *" SIZEOF_VOID_PTR) +if (CMAKE_HOST_SYSTEM_VERSION MATCHES ".*-Microsoft") + # Windows Subsystem for Linux + set(HAVE_WSL 1) +endif() + check_symbol_exists(_NSGetEnviron crt_externs.h HAVE__NSGETENVIRON) # Headers diff --git a/config/config.h.in b/config/config.h.in index 962eefd7a7..106013425d 100644 --- a/config/config.h.in +++ b/config/config.h.in @@ -48,6 +48,7 @@ #cmakedefine HAVE_UTIME_H #cmakedefine HAVE_UTIMES #cmakedefine HAVE_WORKING_LIBINTL +#cmakedefine HAVE_WSL #cmakedefine UNIX #cmakedefine USE_FNAME_CASE #cmakedefine HAVE_SYS_UIO_H @@ -62,6 +63,7 @@ #ifndef UNIT_TESTING #cmakedefine HAVE_JEMALLOC +#cmakedefine LOG_LIST_ACTIONS #endif #cmakedefine HAVE_BE64TOH diff --git a/man/nvim.1 b/man/nvim.1 index d2a3ea5c43..12f342247e 100644 --- a/man/nvim.1 +++ b/man/nvim.1 @@ -25,7 +25,7 @@ To enter commands in type a colon .Pq Sq \&: which is also used in this manual to denote commands. -For more information, consult the on-line help system with the +For more information, consult the online help system with the .Ic :help command. .Bl -tag -width Fl @@ -329,6 +329,8 @@ Implies .Fl -headless . .It Fl -headless Do not start a user interface. +.It Fl -listen Ar address +Start RPC server on this pipe or TCP socket. .It Fl h , -help Print usage information and exit. .It Fl v , -version @@ -337,12 +339,12 @@ Print version information and exit. .Sh ENVIRONMENT .Bl -tag -width Fl .It Ev VIM -Used to locate various user files, such as the user's init.vim. +Used to locate various user files, such as init.vim. .It Ev VIMRUNTIME -Used to locate run time files, such as on-line documentation and +Used to locate runtime files, such as online documentation and syntax highlighting definitions. .It Ev XDG_CONFIG_HOME -Path to use for the user-local configuration directory, see +Path to the user-local configuration directory, see .Sx FILES . Defaults to .Pa ~/.config @@ -356,7 +358,7 @@ Defaults to .Pa ~/.local/share if not set. .It Ev VIMINIT -A string of Ex commands to be executed at startup. +Ex commands to be executed at startup. For example, the command to quit is .Ic :q , so to have @@ -375,41 +377,32 @@ command. .Sh FILES .Bl -tag -width "~/.config/nvim/init.vim" .It Pa ~/.config/nvim/init.vim -The user-local +User-local .Nm configuration file. -See -.Ev XDG_CONFIG_HOME -above. .It Pa ~/.config/nvim -The user-local +User-local .Nm configuration directory. -See -.Ev XDG_CONFIG_HOME -above. +See also +.Ev XDG_CONFIG_HOME . .It Pa $VIM/sysinit.vim -The system-global +System-global .Nm configuration file. .It Pa /usr/local/share/nvim -The system-global +System-global .Nm runtime directory. .El .Sh AUTHORS -.Nm -was started by -.An Thiago de Arruda , -with a lot of help from others. -.Pp +Nvim was started by +.An Thiago de Arruda . Most of Vim was written by .An -nosplit -.An Bram Moolenaar , -with a lot of help from others. +.An Bram Moolenaar . See .Ic :help credits . -.Pp Vim is based on Stevie, worked on by .An Tim Thompson , .An Tony Andrews , diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 6dbe049232..0ae7c231af 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -42,6 +42,7 @@ foreach(PACKAGE ${PACKAGES}) nvim WORKING_DIRECTORY "${GENERATED_PACKAGE_DIR}/${PACKNAME}" ) + add_dependencies(${PACKNAME}-tags nvim_runtime_deps) add_custom_command(OUTPUT "${GENERATED_PACKAGE_DIR}/${PACKNAME}/doc/tags" DEPENDS @@ -71,7 +72,7 @@ foreach(DF ${DOCFILES}) endforeach() add_custom_target(helptags - COMMAND ${CMAKE_COMMAND} -E remove_directory ${GENERATED_RUNTIME_DIR}/doc + COMMAND ${CMAKE_COMMAND} -E remove ${GENERATED_RUNTIME_DIR}/doc/* COMMAND ${CMAKE_COMMAND} -E copy_directory ${PROJECT_SOURCE_DIR}/runtime/doc ${GENERATED_RUNTIME_DIR}/doc COMMAND "${PROJECT_BINARY_DIR}/bin/nvim" @@ -80,6 +81,7 @@ add_custom_target(helptags nvim WORKING_DIRECTORY "${GENERATED_RUNTIME_DIR}" ) +add_dependencies(helptags nvim_runtime_deps) add_custom_command(OUTPUT ${GENERATED_HELP_TAGS} DEPENDS @@ -138,7 +140,7 @@ endforeach() file(GLOB_RECURSE RUNTIME_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} rgb.txt - *.vim *.dict *.py *.rb *.ps *.tutor) + *.vim *.lua *.dict *.py *.rb *.ps *.spl *.tutor *.tutor.json) foreach(F ${RUNTIME_FILES}) get_filename_component(BASEDIR ${F} PATH) diff --git a/runtime/autoload/dist/ft.vim b/runtime/autoload/dist/ft.vim new file mode 100644 index 0000000000..2603c6822f --- /dev/null +++ b/runtime/autoload/dist/ft.vim @@ -0,0 +1,741 @@ +" Vim functions for file type detection +" +" Maintainer: Bram Moolenaar <Bram@vim.org> +" Last Change: 2017 Nov 11 + +" These functions are moved here from runtime/filetype.vim to make startup +" faster. + +" Line continuation is used here, remove 'C' from 'cpoptions' +let s:cpo_save = &cpo +set cpo&vim + +func dist#ft#Check_inp() + if getline(1) =~ '^\*' + setf abaqus + else + let n = 1 + if line("$") > 500 + let nmax = 500 + else + let nmax = line("$") + endif + while n <= nmax + if getline(n) =~? "^header surface data" + setf trasys + break + endif + let n = n + 1 + endwhile + endif +endfunc + +" This function checks for the kind of assembly that is wanted by the user, or +" can be detected from the first five lines of the file. +func dist#ft#FTasm() + " make sure b:asmsyntax exists + if !exists("b:asmsyntax") + let b:asmsyntax = "" + endif + + if b:asmsyntax == "" + call dist#ft#FTasmsyntax() + endif + + " if b:asmsyntax still isn't set, default to asmsyntax or GNU + if b:asmsyntax == "" + if exists("g:asmsyntax") + let b:asmsyntax = g:asmsyntax + else + let b:asmsyntax = "asm" + endif + endif + + exe "setf " . fnameescape(b:asmsyntax) +endfunc + +func dist#ft#FTasmsyntax() + " see if file contains any asmsyntax=foo overrides. If so, change + " b:asmsyntax appropriately + let head = " ".getline(1)." ".getline(2)." ".getline(3)." ".getline(4). + \" ".getline(5)." " + let match = matchstr(head, '\sasmsyntax=\zs[a-zA-Z0-9]\+\ze\s') + if match != '' + let b:asmsyntax = match + elseif ((head =~? '\.title') || (head =~? '\.ident') || (head =~? '\.macro') || (head =~? '\.subtitle') || (head =~? '\.library')) + let b:asmsyntax = "vmasm" + endif +endfunc + +" Check if one of the first five lines contains "VB_Name". In that case it is +" probably a Visual Basic file. Otherwise it's assumed to be "alt" filetype. +func dist#ft#FTVB(alt) + if getline(1).getline(2).getline(3).getline(4).getline(5) =~? 'VB_Name\|Begin VB\.\(Form\|MDIForm\|UserControl\)' + setf vb + else + exe "setf " . a:alt + endif +endfunc + +func dist#ft#FTbtm() + if exists("g:dosbatch_syntax_for_btm") && g:dosbatch_syntax_for_btm + setf dosbatch + else + setf btm + endif +endfunc + +func dist#ft#BindzoneCheck(default) + if getline(1).getline(2).getline(3).getline(4) =~ '^; <<>> DiG [0-9.]\+.* <<>>\|$ORIGIN\|$TTL\|IN\s\+SOA' + setf bindzone + elseif a:default != '' + exe 'setf ' . a:default + endif +endfunc + +func dist#ft#FTlpc() + if exists("g:lpc_syntax_for_c") + let lnum = 1 + while lnum <= 12 + if getline(lnum) =~# '^\(//\|inherit\|private\|protected\|nosave\|string\|object\|mapping\|mixed\)' + setf lpc + return + endif + let lnum = lnum + 1 + endwhile + endif + setf c +endfunc + +func dist#ft#FTheader() + if match(getline(1, min([line("$"), 200])), '^@\(interface\|end\|class\)') > -1 + if exists("g:c_syntax_for_h") + setf objc + else + setf objcpp + endif + elseif exists("g:c_syntax_for_h") + setf c + elseif exists("g:ch_syntax_for_h") + setf ch + else + setf cpp + endif +endfunc + +" This function checks if one of the first ten lines start with a '@'. In +" that case it is probably a change file. +" If the first line starts with # or ! it's probably a ch file. +" If a line has "main", "include", "//" ir "/*" it's probably ch. +" Otherwise CHILL is assumed. +func dist#ft#FTchange() + let lnum = 1 + while lnum <= 10 + if getline(lnum)[0] == '@' + setf change + return + endif + if lnum == 1 && (getline(1)[0] == '#' || getline(1)[0] == '!') + setf ch + return + endif + if getline(lnum) =~ "MODULE" + setf chill + return + endif + if getline(lnum) =~ 'main\s*(\|#\s*include\|//' + setf ch + return + endif + let lnum = lnum + 1 + endwhile + setf chill +endfunc + +func dist#ft#FTent() + " This function checks for valid cl syntax in the first five lines. + " Look for either an opening comment, '#', or a block start, '{". + " If not found, assume SGML. + let lnum = 1 + while lnum < 6 + let line = getline(lnum) + if line =~ '^\s*[#{]' + setf cl + return + elseif line !~ '^\s*$' + " Not a blank line, not a comment, and not a block start, + " so doesn't look like valid cl code. + break + endif + let lnum = lnum + 1 + endw + setf dtd +endfunc + +func dist#ft#EuphoriaCheck() + if exists('g:filetype_euphoria') + exe 'setf ' . g:filetype_euphoria + else + setf euphoria3 + endif +endfunc + +func dist#ft#DtraceCheck() + let lines = getline(1, min([line("$"), 100])) + if match(lines, '^module\>\|^import\>') > -1 + " D files often start with a module and/or import statement. + setf d + elseif match(lines, '^#!\S\+dtrace\|#pragma\s\+D\s\+option\|:\S\{-}:\S\{-}:') > -1 + setf dtrace + else + setf d + endif +endfunc + +func dist#ft#FTe() + if exists('g:filetype_euphoria') + exe 'setf ' . g:filetype_euphoria + else + let n = 1 + while n < 100 && n < line("$") + if getline(n) =~ "^\\s*\\(<'\\|'>\\)\\s*$" + setf specman + return + endif + let n = n + 1 + endwhile + setf eiffel + endif +endfunc + +" Distinguish between HTML, XHTML and Django +func dist#ft#FThtml() + let n = 1 + while n < 10 && n < line("$") + if getline(n) =~ '\<DTD\s\+XHTML\s' + setf xhtml + return + endif + if getline(n) =~ '{%\s*\(extends\|block\|load\)\>\|{#\s\+' + setf htmldjango + return + endif + let n = n + 1 + endwhile + setf html +endfunc + +" Distinguish between standard IDL and MS-IDL +func dist#ft#FTidl() + let n = 1 + while n < 50 && n < line("$") + if getline(n) =~ '^\s*import\s\+"\(unknwn\|objidl\)\.idl"' + setf msidl + return + endif + let n = n + 1 + endwhile + setf idl +endfunc + +" Distinguish between "default" and Cproto prototype file. */ +func dist#ft#ProtoCheck(default) + " Cproto files have a comment in the first line and a function prototype in + " the second line, it always ends in ";". Indent files may also have + " comments, thus we can't match comments to see the difference. + " IDL files can have a single ';' in the second line, require at least one + " chacter before the ';'. + if getline(2) =~ '.;$' + setf cpp + else + exe 'setf ' . a:default + endif +endfunc + +func dist#ft#FTm() + let n = 1 + let saw_comment = 0 " Whether we've seen a multiline comment leader. + while n < 100 + let line = getline(n) + if line =~ '^\s*/\*' + " /* ... */ is a comment in Objective C and Murphi, so we can't conclude + " it's either of them yet, but track this as a hint in case we don't see + " anything more definitive. + let saw_comment = 1 + endif + if line =~ '^\s*\(#\s*\(include\|import\)\>\|@import\>\|//\)' + setf objc + return + endif + if line =~ '^\s*%' + setf matlab + return + endif + if line =~ '^\s*(\*' + setf mma + return + endif + if line =~ '^\c\s*\(\(type\|var\)\>\|--\)' + setf murphi + return + endif + let n = n + 1 + endwhile + + if saw_comment + " We didn't see anything definitive, but this looks like either Objective C + " or Murphi based on the comment leader. Assume the former as it is more + " common. + setf objc + elseif exists("g:filetype_m") + " Use user specified default filetype for .m + exe "setf " . g:filetype_m + else + " Default is matlab + setf matlab + endif +endfunc + +func dist#ft#FTmms() + let n = 1 + while n < 10 + let line = getline(n) + if line =~ '^\s*\(%\|//\)' || line =~ '^\*' + setf mmix + return + endif + if line =~ '^\s*#' + setf make + return + endif + let n = n + 1 + endwhile + setf mmix +endfunc + +" This function checks if one of the first five lines start with a dot. In +" that case it is probably an nroff file: 'filetype' is set and 1 is returned. +func dist#ft#FTnroff() + if getline(1)[0] . getline(2)[0] . getline(3)[0] . getline(4)[0] . getline(5)[0] =~ '\.' + setf nroff + return 1 + endif + return 0 +endfunc + +func dist#ft#FTmm() + let n = 1 + while n < 10 + let line = getline(n) + if line =~ '^\s*\(#\s*\(include\|import\)\>\|@import\>\|/\*\)' + setf objcpp + return + endif + let n = n + 1 + endwhile + setf nroff +endfunc + +func dist#ft#FTpl() + if exists("g:filetype_pl") + exe "setf " . g:filetype_pl + else + " recognize Prolog by specific text in the first non-empty line + " require a blank after the '%' because Perl uses "%list" and "%translate" + let l = getline(nextnonblank(1)) + if l =~ '\<prolog\>' || l =~ '^\s*\(%\+\(\s\|$\)\|/\*\)' || l =~ ':-' + setf prolog + else + setf perl + endif + endif +endfunc + +func dist#ft#FTinc() + if exists("g:filetype_inc") + exe "setf " . g:filetype_inc + else + let lines = getline(1).getline(2).getline(3) + if lines =~? "perlscript" + setf aspperl + elseif lines =~ "<%" + setf aspvbs + elseif lines =~ "<?" + setf php + else + call dist#ft#FTasmsyntax() + if exists("b:asmsyntax") + exe "setf " . fnameescape(b:asmsyntax) + else + setf pov + endif + endif + endif +endfunc + +func dist#ft#FTprogress_cweb() + if exists("g:filetype_w") + exe "setf " . g:filetype_w + return + endif + if getline(1) =~ '&ANALYZE' || getline(3) =~ '&GLOBAL-DEFINE' + setf progress + else + setf cweb + endif +endfunc + +func dist#ft#FTprogress_asm() + if exists("g:filetype_i") + exe "setf " . g:filetype_i + return + endif + " This function checks for an assembly comment the first ten lines. + " If not found, assume Progress. + let lnum = 1 + while lnum <= 10 && lnum < line('$') + let line = getline(lnum) + if line =~ '^\s*;' || line =~ '^\*' + call dist#ft#FTasm() + return + elseif line !~ '^\s*$' || line =~ '^/\*' + " Not an empty line: Doesn't look like valid assembly code. + " Or it looks like a Progress /* comment + break + endif + let lnum = lnum + 1 + endw + setf progress +endfunc + +func dist#ft#FTprogress_pascal() + if exists("g:filetype_p") + exe "setf " . g:filetype_p + return + endif + " This function checks for valid Pascal syntax in the first ten lines. + " Look for either an opening comment or a program start. + " If not found, assume Progress. + let lnum = 1 + while lnum <= 10 && lnum < line('$') + let line = getline(lnum) + if line =~ '^\s*\(program\|unit\|procedure\|function\|const\|type\|var\)\>' + \ || line =~ '^\s*{' || line =~ '^\s*(\*' + setf pascal + return + elseif line !~ '^\s*$' || line =~ '^/\*' + " Not an empty line: Doesn't look like valid Pascal code. + " Or it looks like a Progress /* comment + break + endif + let lnum = lnum + 1 + endw + setf progress +endfunc + +func dist#ft#FTr() + let max = line("$") > 50 ? 50 : line("$") + + for n in range(1, max) + " Rebol is easy to recognize, check for that first + if getline(n) =~? '\<REBOL\>' + setf rebol + return + endif + endfor + + for n in range(1, max) + " R has # comments + if getline(n) =~ '^\s*#' + setf r + return + endif + " Rexx has /* comments */ + if getline(n) =~ '^\s*/\*' + setf rexx + return + endif + endfor + + " Nothing recognized, use user default or assume Rexx + if exists("g:filetype_r") + exe "setf " . g:filetype_r + else + " Rexx used to be the default, but R appears to be much more popular. + setf r + endif +endfunc + +func dist#ft#McSetf() + " Rely on the file to start with a comment. + " MS message text files use ';', Sendmail files use '#' or 'dnl' + for lnum in range(1, min([line("$"), 20])) + let line = getline(lnum) + if line =~ '^\s*\(#\|dnl\)' + setf m4 " Sendmail .mc file + return + elseif line =~ '^\s*;' + setf msmessages " MS Message text file + return + endif + endfor + setf m4 " Default: Sendmail .mc file +endfunc + +" Called from filetype.vim and scripts.vim. +func dist#ft#SetFileTypeSH(name) + if expand("<amatch>") =~ g:ft_ignore_pat + return + endif + if a:name =~ '\<csh\>' + " Some .sh scripts contain #!/bin/csh. + call dist#ft#SetFileTypeShell("csh") + return + elseif a:name =~ '\<tcsh\>' + " Some .sh scripts contain #!/bin/tcsh. + call dist#ft#SetFileTypeShell("tcsh") + return + elseif a:name =~ '\<zsh\>' + " Some .sh scripts contain #!/bin/zsh. + call dist#ft#SetFileTypeShell("zsh") + return + elseif a:name =~ '\<ksh\>' + let b:is_kornshell = 1 + if exists("b:is_bash") + unlet b:is_bash + endif + if exists("b:is_sh") + unlet b:is_sh + endif + elseif exists("g:bash_is_sh") || a:name =~ '\<bash\>' || a:name =~ '\<bash2\>' + let b:is_bash = 1 + if exists("b:is_kornshell") + unlet b:is_kornshell + endif + if exists("b:is_sh") + unlet b:is_sh + endif + elseif a:name =~ '\<sh\>' + let b:is_sh = 1 + if exists("b:is_kornshell") + unlet b:is_kornshell + endif + if exists("b:is_bash") + unlet b:is_bash + endif + endif + call dist#ft#SetFileTypeShell("sh") +endfunc + +" For shell-like file types, check for an "exec" command hidden in a comment, +" as used for Tcl. +" Also called from scripts.vim, thus can't be local to this script. +func dist#ft#SetFileTypeShell(name) + if expand("<amatch>") =~ g:ft_ignore_pat + return + endif + let l = 2 + while l < 20 && l < line("$") && getline(l) =~ '^\s*\(#\|$\)' + " Skip empty and comment lines. + let l = l + 1 + endwhile + if l < line("$") && getline(l) =~ '\s*exec\s' && getline(l - 1) =~ '^\s*#.*\\$' + " Found an "exec" line after a comment with continuation + let n = substitute(getline(l),'\s*exec\s\+\([^ ]*/\)\=', '', '') + if n =~ '\<tclsh\|\<wish' + setf tcl + return + endif + endif + exe "setf " . a:name +endfunc + +func dist#ft#CSH() + if exists("g:filetype_csh") + call dist#ft#SetFileTypeShell(g:filetype_csh) + elseif &shell =~ "tcsh" + call dist#ft#SetFileTypeShell("tcsh") + else + call dist#ft#SetFileTypeShell("csh") + endif +endfunc + +let s:ft_rules_udev_rules_pattern = '^\s*\cudev_rules\s*=\s*"\([^"]\{-1,}\)/*".*' +func dist#ft#FTRules() + let path = expand('<amatch>:p') + if path =~ '^/\(etc/udev/\%(rules\.d/\)\=.*\.rules\|lib/udev/\%(rules\.d/\)\=.*\.rules\)$' + setf udevrules + return + endif + if path =~ '^/etc/ufw/' + setf conf " Better than hog + return + endif + if path =~ '^/\(etc\|usr/share\)/polkit-1/rules\.d' + setf javascript + return + endif + try + let config_lines = readfile('/etc/udev/udev.conf') + catch /^Vim\%((\a\+)\)\=:E484/ + setf hog + return + endtry + let dir = expand('<amatch>:p:h') + for line in config_lines + if line =~ s:ft_rules_udev_rules_pattern + let udev_rules = substitute(line, s:ft_rules_udev_rules_pattern, '\1', "") + if dir == udev_rules + setf udevrules + endif + break + endif + endfor + setf hog +endfunc + +func dist#ft#SQL() + if exists("g:filetype_sql") + exe "setf " . g:filetype_sql + else + setf sql + endif +endfunc + +" If the file has an extension of 't' and is in a directory 't' or 'xt' then +" it is almost certainly a Perl test file. +" If the first line starts with '#' and contains 'perl' it's probably a Perl +" file. +" (Slow test) If a file contains a 'use' statement then it is almost certainly +" a Perl file. +func dist#ft#FTperl() + let dirname = expand("%:p:h:t") + if expand("%:e") == 't' && (dirname == 't' || dirname == 'xt') + setf perl + return 1 + endif + if getline(1)[0] == '#' && getline(1) =~ 'perl' + setf perl + return 1 + endif + if search('^use\s\s*\k', 'nc', 30) + setf perl + return 1 + endif + return 0 +endfunc + +" Choose context, plaintex, or tex (LaTeX) based on these rules: +" 1. Check the first line of the file for "%&<format>". +" 2. Check the first 1000 non-comment lines for LaTeX or ConTeXt keywords. +" 3. Default to "latex" or to g:tex_flavor, can be set in user's vimrc. +func dist#ft#FTtex() + let firstline = getline(1) + if firstline =~ '^%&\s*\a\+' + let format = tolower(matchstr(firstline, '\a\+')) + let format = substitute(format, 'pdf', '', '') + if format == 'tex' + let format = 'latex' + elseif format == 'plaintex' + let format = 'plain' + endif + elseif expand('%') =~ 'tex/context/.*/.*.tex' + let format = 'context' + else + " Default value, may be changed later: + let format = exists("g:tex_flavor") ? g:tex_flavor : 'plain' + " Save position, go to the top of the file, find first non-comment line. + let save_cursor = getpos('.') + call cursor(1,1) + let firstNC = search('^\s*[^[:space:]%]', 'c', 1000) + if firstNC " Check the next thousand lines for a LaTeX or ConTeXt keyword. + let lpat = 'documentclass\>\|usepackage\>\|begin{\|newcommand\>\|renewcommand\>' + let cpat = 'start\a\+\|setup\a\+\|usemodule\|enablemode\|enableregime\|setvariables\|useencoding\|usesymbols\|stelle\a\+\|verwende\a\+\|stel\a\+\|gebruik\a\+\|usa\a\+\|imposta\a\+\|regle\a\+\|utilisemodule\>' + let kwline = search('^\s*\\\%(' . lpat . '\)\|^\s*\\\(' . cpat . '\)', + \ 'cnp', firstNC + 1000) + if kwline == 1 " lpat matched + let format = 'latex' + elseif kwline == 2 " cpat matched + let format = 'context' + endif " If neither matched, keep default set above. + " let lline = search('^\s*\\\%(' . lpat . '\)', 'cn', firstNC + 1000) + " let cline = search('^\s*\\\%(' . cpat . '\)', 'cn', firstNC + 1000) + " if cline > 0 + " let format = 'context' + " endif + " if lline > 0 && (cline == 0 || cline > lline) + " let format = 'tex' + " endif + endif " firstNC + call setpos('.', save_cursor) + endif " firstline =~ '^%&\s*\a\+' + + " Translation from formats to file types. TODO: add AMSTeX, RevTex, others? + if format == 'plain' + setf plaintex + elseif format == 'context' + setf context + else " probably LaTeX + setf tex + endif + return +endfunc + +func dist#ft#FTxml() + let n = 1 + while n < 100 && n < line("$") + let line = getline(n) + " DocBook 4 or DocBook 5. + let is_docbook4 = line =~ '<!DOCTYPE.*DocBook' + let is_docbook5 = line =~ ' xmlns="http://docbook.org/ns/docbook"' + if is_docbook4 || is_docbook5 + let b:docbk_type = "xml" + if is_docbook5 + let b:docbk_ver = 5 + else + let b:docbk_ver = 4 + endif + setf docbk + return + endif + if line =~ 'xmlns:xbl="http://www.mozilla.org/xbl"' + setf xbl + return + endif + let n += 1 + endwhile + setf xml +endfunc + +func dist#ft#FTy() + let n = 1 + while n < 100 && n < line("$") + let line = getline(n) + if line =~ '^\s*%' + setf yacc + return + endif + if getline(n) =~ '^\s*\(#\|class\>\)' && getline(n) !~ '^\s*#\s*include' + setf racc + return + endif + let n = n + 1 + endwhile + setf yacc +endfunc + +func dist#ft#Redif() + let lnum = 1 + while lnum <= 5 && lnum < line('$') + if getline(lnum) =~ "^\ctemplate-type:" + setf redif + return + endif + let lnum = lnum + 1 + endwhile +endfunc + + +" Restore 'cpoptions' +let &cpo = s:cpo_save +unlet s:cpo_save diff --git a/runtime/autoload/health.vim b/runtime/autoload/health.vim index 53d45afc2e..56ae2071e9 100644 --- a/runtime/autoload/health.vim +++ b/runtime/autoload/health.vim @@ -93,26 +93,26 @@ function! s:help_to_link(s) abort return substitute(a:s, '\v:h%[elp] ([^|][^"\r\n ]+)', ':help |\1|', 'g') endfunction -" Format a message for a specific report item +" Format a message for a specific report item. +" a:1: Optional advice (string or list) function! s:format_report_message(status, msg, ...) abort " {{{ let output = ' - ' . a:status . ': ' . s:indent_after_line1(a:msg, 4) - let advice = [] " Optional parameters if a:0 > 0 - let advice = type(a:1) == type("") ? [a:1] : a:1 + let advice = type(a:1) == type('') ? [a:1] : a:1 if type(advice) != type([]) - throw "Expected String or List" + throw 'a:1: expected String or List' endif - endif - " Report each suggestion - if len(advice) > 0 - let output .= "\n - ADVICE:" + " Report each suggestion + if !empty(advice) + let output .= "\n - ADVICE:" + for suggestion in advice + let output .= "\n - " . s:indent_after_line1(suggestion, 10) + endfor + endif endif - for suggestion in advice - let output .= "\n - " . s:indent_after_line1(suggestion, 10) - endfor return s:help_to_link(output) endfunction " }}} @@ -128,6 +128,7 @@ function! health#report_ok(msg) abort " {{{ endfunction " }}} " Reports a health warning. +" a:1: Optional advice (string or list) function! health#report_warn(msg, ...) abort " {{{ if a:0 > 0 echo s:format_report_message('WARNING', a:msg, a:1) @@ -137,6 +138,7 @@ function! health#report_warn(msg, ...) abort " {{{ endfunction " }}} " Reports a failed healthcheck. +" a:1: Optional advice (string or list) function! health#report_error(msg, ...) abort " {{{ if a:0 > 0 echo s:format_report_message('ERROR', a:msg, a:1) diff --git a/runtime/autoload/health/nvim.vim b/runtime/autoload/health/nvim.vim index 58033f0405..d09a714719 100644 --- a/runtime/autoload/health/nvim.vim +++ b/runtime/autoload/health/nvim.vim @@ -7,12 +7,12 @@ function! s:check_config() abort " If $VIM is empty we don't care. Else make sure it is valid. if !empty($VIM) && !filereadable($VIM.'/runtime/doc/nvim.txt') let ok = v:false - call health#report_error("$VIM is invalid: ".$VIM) + call health#report_error('$VIM is invalid: '.$VIM) endif if exists('$NVIM_TUI_ENABLE_CURSOR_SHAPE') let ok = v:false - call health#report_warn("$NVIM_TUI_ENABLE_CURSOR_SHAPE is ignored in Nvim 0.2+", + call health#report_warn('$NVIM_TUI_ENABLE_CURSOR_SHAPE is ignored in Nvim 0.2+', \ [ "Use the 'guicursor' option to configure cursor shape. :help 'guicursor'", \ 'https://github.com/neovim/neovim/wiki/Following-HEAD#20170402' ]) endif @@ -100,8 +100,8 @@ function! s:check_performance() abort else call health#report_info(buildtype) call health#report_warn( - \ "Non-optimized build-type. Nvim will be slower.", - \ ["Install a different Nvim package, or rebuild with `CMAKE_BUILD_TYPE=RelWithDebInfo`.", + \ 'Non-optimized build-type. Nvim will be slower.', + \ ['Install a different Nvim package, or rebuild with `CMAKE_BUILD_TYPE=RelWithDebInfo`.', \ s:suggest_faq]) endif endfunction @@ -174,7 +174,7 @@ function! s:check_terminal() abort \ .(empty(kbs_entry) ? '? (not found)' : kdch1_entry)) endif for env_var in ['XTERM_VERSION', 'VTE_VERSION', 'TERM_PROGRAM', 'COLORTERM', 'SSH_TTY'] - if !exists('$'.env_var) + if exists('$'.env_var) call health#report_info(printf("$%s='%s'", env_var, eval('$'.env_var))) endif endfor diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index 0201ed8062..4adab1aa76 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -13,6 +13,12 @@ function! s:normalize_path(s) abort return substitute(substitute(a:s, '\', '/', 'g'), '/\./\|/\+', '/', 'g') endfunction +" Returns TRUE if `cmd` exits with success, else FALSE. +function! s:cmd_ok(cmd) abort + call system(a:cmd) + return v:shell_error == 0 +endfunction + " Simple version comparison. function! s:version_cmp(a, b) abort let a = split(a:a, '\.', 0) @@ -120,6 +126,13 @@ endfunction function! s:check_clipboard() abort call health#report_start('Clipboard (optional)') + if !empty($TMUX) && executable('tmux') && executable('pbpaste') && !s:cmd_ok('pbpaste') + let tmux_version = matchstr(system('tmux -V'), '\d\+\.\d\+') + call health#report_error('pbcopy does not work with tmux version: '.tmux_version, + \ ['Install tmux 2.6+. https://superuser.com/q/231130', + \ 'or use tmux with reattach-to-user-namespace. https://superuser.com/a/413233']) + endif + let clipboard_tool = provider#clipboard#Executable() if exists('g:clipboard') && empty(clipboard_tool) call health#report_error( @@ -247,20 +260,23 @@ function! s:check_python(version) abort let python_multiple = [] if exists(loaded_var) && !exists('*provider#'.pyname.'#Call') - call health#report_info('Disabled. '.loaded_var.'='.eval(loaded_var)) - return + call health#report_info('Disabled ('.loaded_var.'='.eval(loaded_var).'). This might be due to some previous error.') endif if !empty(pyenv) if empty(pyenv_root) - call health#report_warn( - \ 'pyenv was found, but $PYENV_ROOT is not set.', - \ ['Did you follow the final install instructions?', - \ 'If you use a shell "framework" like Prezto or Oh My Zsh, try without.', - \ 'Try a different shell (bash).'] + call health#report_info( + \ 'pyenv was found, but $PYENV_ROOT is not set. `pyenv root` will be used.' + \ .' If you run into problems, try setting $PYENV_ROOT explicitly.' \ ) + let pyenv_root = s:trim(s:system([pyenv, 'root'])) + endif + + if !isdirectory(pyenv_root) + call health#report_error('Invalid pyenv root: '.pyenv_root) else - call health#report_ok(printf('pyenv found: "%s"', pyenv)) + call health#report_info(printf('pyenv: %s', pyenv)) + call health#report_info(printf('pyenv root: %s', pyenv_root)) endif endif @@ -272,9 +288,6 @@ function! s:check_python(version) abort if empty(pyname) call health#report_warn('No Python interpreter was found with the neovim ' \ . 'module. Using the first available for diagnostics.') - if !empty(pythonx_errs) - call health#report_warn(pythonx_errs) - endif endif if !empty(pyname) @@ -332,8 +345,8 @@ function! s:check_python(version) abort endif endif - if !empty(python_bin) - if empty(venv) && !empty(pyenv) && !exists('g:'.host_prog_var) + if !empty(python_bin) && !exists('g:'.host_prog_var) + if empty(venv) && !empty(pyenv) \ && !empty(pyenv_root) && resolve(python_bin) !~# '^'.pyenv_root.'/' call health#report_warn('pyenv is not set up optimally.', [ \ printf('Create a virtualenv specifically ' @@ -341,7 +354,7 @@ function! s:check_python(version) abort \ . 'the need to install the Neovim Python module in each ' \ . 'version/virtualenv.', host_prog_var) \ ]) - elseif !empty(venv) && exists('g:'.host_prog_var) + elseif !empty(venv) if !empty(pyenv_root) let venv_root = pyenv_root else @@ -366,27 +379,16 @@ function! s:check_python(version) abort let python_bin = '' endif - " Check if $VIRTUAL_ENV is active - let virtualenv_inactive = 0 - + " Check if $VIRTUAL_ENV is valid. if exists('$VIRTUAL_ENV') - if !empty(pyenv) - let pyenv_prefix = resolve(s:trim(s:system([pyenv, 'prefix']))) - if $VIRTUAL_ENV != pyenv_prefix - let virtualenv_inactive = 1 - endif - elseif !empty(pyname) && exepath(pyname) !~# '^'.$VIRTUAL_ENV.'/' - let virtualenv_inactive = 1 + if !empty(pyname) && $VIRTUAL_ENV !=# matchstr(exepath(pyname), '^\V'.$VIRTUAL_ENV) + call health#report_warn( + \ '$VIRTUAL_ENV exists but appears to be inactive. ' + \ . 'This could lead to unexpected results.', + \ [ 'If you are using Zsh, see: http://vi.stackexchange.com/a/7654' ]) endif endif - if virtualenv_inactive - call health#report_warn( - \ '$VIRTUAL_ENV exists but appears to be inactive. ' - \ . 'This could lead to unexpected results.', - \ [ 'If you are using Zsh, see: http://vi.stackexchange.com/a/7654/5229' ]) - endif - " Diagnostic output call health#report_info('Executable: ' . (empty(python_bin) ? 'Not found' : python_bin)) if len(python_multiple) @@ -451,10 +453,11 @@ function! s:check_ruby() abort let host = provider#ruby#Detect() if empty(host) - call health#report_warn('Missing "neovim" gem.', - \ ['Run in shell: gem install neovim', - \ 'Is the gem bin directory in $PATH? Check `gem environment`.', - \ 'If you are using rvm/rbenv/chruby, try "rehashing".']) + call health#report_warn('`neovim-ruby-host` not found.', + \ ['Run `gem install neovim` to ensure the neovim RubyGem is installed.', + \ 'Run `gem environment` to ensure the gem bin directory is in $PATH.', + \ 'If you are using rvm/rbenv/chruby, try "rehashing".', + \ 'See :help g:ruby_host_prog for non-standard gem installations.']) return endif call health#report_info('Host: '. host) @@ -488,7 +491,7 @@ function! s:check_ruby() abort endfunction function! s:check_node() abort - call health#report_start('Node provider (optional)') + call health#report_start('Node.js provider (optional)') let loaded_var = 'g:loaded_node_provider' if exists(loaded_var) && !exists('*provider#node#Call') @@ -502,7 +505,16 @@ function! s:check_node() abort \ ['Install Node.js and verify that `node` and `npm` commands work.']) return endif - call health#report_info('Node: '. s:system('node -v')) + let node_v = get(split(s:system('node -v'), "\n"), 0, '') + call health#report_info('Node.js: '. node_v) + if !s:shell_error && s:version_cmp(node_v[1:], '6.0.0') < 0 + call health#report_warn('Neovim node.js host does not support '.node_v) + " Skip further checks, they are nonsense if nodejs is too old. + return + endif + if !provider#node#can_inspect() + 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() if empty(host) @@ -511,7 +523,7 @@ function! s:check_node() abort \ 'Is the npm bin directory in $PATH?']) return endif - call health#report_info('Host: '. host) + call health#report_info('Neovim node.js host: '. host) let latest_npm_cmd = has('win32') ? 'cmd /c npm info neovim --json' : 'npm info neovim --json' let latest_npm = s:system(split(latest_npm_cmd)) @@ -530,11 +542,11 @@ function! s:check_node() abort let latest_npm = get(get(pkg_data, 'dist-tags', {}), 'latest', 'unable to parse') endif - let current_npm_cmd = host .' --version' + let current_npm_cmd = ['node', host, '--version'] let current_npm = s:system(current_npm_cmd) if s:shell_error - call health#report_error('Failed to run: '. current_npm_cmd, - \ ['Report this issue with the output of: ', current_npm_cmd]) + call health#report_error('Failed to run: '. string(current_npm_cmd), + \ ['Report this issue with the output of: ', string(current_npm_cmd)]) return endif @@ -544,7 +556,7 @@ function! s:check_node() abort \ current_npm, latest_npm), \ ['Run in shell: npm update neovim']) else - call health#report_ok('Latest "neovim" npm is installed: '. current_npm) + call health#report_ok('Latest "neovim" npm package is installed: '. current_npm) endif endfunction diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim index dd71ede680..4d43a4582b 100644 --- a/runtime/autoload/man.vim +++ b/runtime/autoload/man.vim @@ -65,9 +65,9 @@ function! man#open_page(count, count1, mods, ...) abort try set eventignore+=BufReadCmd if a:mods !~# 'tab' && s:find_man() - execute 'silent edit' fnameescape(bufname) + execute 'silent keepalt edit' fnameescape(bufname) else - execute 'silent' a:mods 'split' fnameescape(bufname) + execute 'silent keepalt' a:mods 'split' fnameescape(bufname) endif finally set eventignore-=BufReadCmd @@ -148,7 +148,8 @@ function! s:get_page(path) abort let manwidth = empty($MANWIDTH) ? winwidth(0) : $MANWIDTH " Force MANPAGER=cat to ensure Vim is not recursively invoked (by man-db). " http://comments.gmane.org/gmane.editors.vim.devel/29085 - let cmd = ['env', 'MANPAGER=cat', 'MANWIDTH='.manwidth, 'man'] + " Set MAN_KEEP_FORMATTING so Debian man doesn't discard backspaces. + let cmd = ['env', 'MANPAGER=cat', 'MANWIDTH='.manwidth, 'MAN_KEEP_FORMATTING=1', 'man'] return s:system(cmd + (s:localfile_arg ? ['-l', a:path] : [a:path])) endfunction @@ -157,11 +158,10 @@ function! s:put_page(page) abort setlocal noreadonly silent keepjumps %delete _ silent put =a:page - " Remove all backspaced/escape characters. - execute 'silent keeppatterns keepjumps %substitute,.\b\|\e\[\d\+m,,e'.(&gdefault?'':'g') while getline(1) =~# '^\s*$' silent keepjumps 1delete _ endwhile + lua require("man").highlight_man_page() setlocal filetype=man endfunction @@ -299,6 +299,12 @@ endfunction " see man#extract_sect_and_name_ref on why tolower(sect) function! man#complete(arg_lead, cmd_line, cursor_pos) abort let args = split(a:cmd_line) + let cmd_offset = index(args, 'Man') + if cmd_offset > 0 + " Prune all arguments up to :Man itself. Otherwise modifier commands like + " :tab, :vertical, etc. would lead to a wrong length. + let args = args[cmd_offset:] + endif let l = len(args) if l > 3 return @@ -370,13 +376,12 @@ function! s:format_candidate(path, psect) abort endfunction function! man#init_pager() abort - " Remove all backspaced/escape characters. - execute 'silent keeppatterns keepjumps %substitute,.\b\|\e\[\d\+m,,e'.(&gdefault?'':'g') if getline(1) =~# '^\s*$' silent keepjumps 1delete _ else keepjumps 1 endif + lua require("man").highlight_man_page() " This is not perfect. See `man glDrawArraysInstanced`. Since the title is " all caps it is impossible to tell what the original capitilization was. let ref = substitute(matchstr(getline(1), '^[^)]\+)'), ' ', '_', 'g') diff --git a/runtime/autoload/provider.vim b/runtime/autoload/provider.vim index e6514f5ba8..dc24e801d0 100644 --- a/runtime/autoload/provider.vim +++ b/runtime/autoload/provider.vim @@ -1,20 +1,21 @@ -" Common functionality for providers +" Common functions for providers -let s:stderr = {} - -function! provider#stderr_collector(chan_id, data, event) - let stderr = get(s:stderr, a:chan_id, ['']) - let stderr[-1] .= a:data[0] - call extend(stderr, a:data[1:]) - let s:stderr[a:chan_id] = stderr -endfunction - -function! provider#clear_stderr(chan_id) - if has_key(s:stderr, a:chan_id) - call remove(s:stderr, a:chan_id) - endif -endfunction - -function! provider#get_stderr(chan_id) - return get(s:stderr, a:chan_id, []) +" Start the provider and perform a 'poll' request +" +" Returns a valid channel on success +function! provider#Poll(argv, orig_name, log_env) abort + let job = {'rpc': v:true, 'stderr_buffered': v:true} + try + let channel_id = jobstart(a:argv, job) + if channel_id > 0 && rpcrequest(channel_id, 'poll') ==# 'ok' + return channel_id + endif + catch + echomsg v:throwpoint + echomsg v:exception + for row in get(job, 'stderr', []) + echomsg row + endfor + endtry + throw remote#host#LoadErrorForHost(a:orig_name, a:log_env) endfunction diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim index 6454a01c2a..87a0315073 100644 --- a/runtime/autoload/provider/clipboard.vim +++ b/runtime/autoload/provider/clipboard.vim @@ -7,7 +7,7 @@ let s:clipboard = {} " When caching is enabled, store the jobid of the xclip/xsel process keeping " ownership of the selection, so we know how long the cache is valid. -let s:selection = { 'owner': 0, 'data': [], 'on_stderr': function('provider#stderr_collector') } +let s:selection = { 'owner': 0, 'data': [], 'stderr_buffered': v:true } function! s:selection.on_exit(jobid, data, event) abort " At this point this nvim instance might already have launched @@ -16,19 +16,17 @@ function! s:selection.on_exit(jobid, data, event) abort let self.owner = 0 endif if a:data != 0 - let stderr = provider#get_stderr(a:jobid) echohl WarningMsg - echomsg 'clipboard: error invoking '.get(self.argv, 0, '?').': '.join(stderr) + echomsg 'clipboard: error invoking '.get(self.argv, 0, '?').': '.join(self.stderr) echohl None endif - call provider#clear_stderr(a:jobid) endfunction let s:selections = { '*': s:selection, '+': copy(s:selection) } function! s:try_cmd(cmd, ...) abort let argv = split(a:cmd, " ") - let out = a:0 ? systemlist(argv, a:1, 1) : systemlist(argv, [''], 1) + let out = systemlist(argv, (a:0 ? a:1 : ['']), 1) if v:shell_error if !exists('s:did_error_try_cmd') echohl WarningMsg @@ -66,7 +64,7 @@ function! provider#clipboard#Executable() abort let s:paste = get(g:clipboard, 'paste', { '+': v:null, '*': v:null }) let s:cache_enabled = get(g:clipboard, 'cache_enabled', 0) return get(g:clipboard, 'name', 'g:clipboard') - elseif has('mac') && executable('pbcopy') + elseif has('mac') && executable('pbpaste') && s:cmd_ok('pbpaste') let s:copy['+'] = 'pbcopy' let s:paste['+'] = 'pbpaste' let s:copy['*'] = s:copy['+'] @@ -142,12 +140,13 @@ function! s:clipboard.set(lines, regtype, reg) abort return 0 end - let selection = s:selections[a:reg] - if selection.owner > 0 + if s:selections[a:reg].owner > 0 " The previous provider instance should exit when the new one takes " ownership, but kill it to be sure we don't fill up the job table. - call jobstop(selection.owner) + call jobstop(s:selections[a:reg].owner) end + 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 diff --git a/runtime/autoload/provider/node.vim b/runtime/autoload/provider/node.vim index b08ad4f316..39b5dc63b8 100644 --- a/runtime/autoload/provider/node.vim +++ b/runtime/autoload/provider/node.vim @@ -3,13 +3,59 @@ if exists('g:loaded_node_provider') endif let g:loaded_node_provider = 1 -let s:job_opts = {'rpc': v:true, 'on_stderr': function('provider#stderr_collector')} +function! s:is_minimum_version(version, min_major, min_minor) abort + if empty(a:version) + let nodejs_version = get(split(system(['node', '-v']), "\n"), 0, '') + if v:shell_error || nodejs_version[0] !=# 'v' + return 0 + endif + else + let nodejs_version = a:version + endif + " Remove surrounding junk. Example: 'v4.12.0' => '4.12.0' + let nodejs_version = matchstr(nodejs_version, '\(\d\.\?\)\+') + " [major, minor, patch] + let v_list = split(nodejs_version, '\.') + return len(v_list) == 3 + \ && ((str2nr(v_list[0]) > str2nr(a:min_major)) + \ || (str2nr(v_list[0]) == str2nr(a:min_major) + \ && str2nr(v_list[1]) >= str2nr(a:min_minor))) +endfunction + +" Support for --inspect-brk requires node 6.12+ or 7.6+ or 8+ +" Return 1 if it is supported +" Return 0 otherwise +function! provider#node#can_inspect() abort + if !executable('node') + return 0 + endif + let ver = get(split(system(['node', '-v']), "\n"), 0, '') + if v:shell_error || ver[0] !=# 'v' + return 0 + endif + return (ver[1] ==# '6' && s:is_minimum_version(ver, 6, 12)) + \ || s:is_minimum_version(ver, 7, 6) +endfunction function! provider#node#Detect() abort - return has('win32') ? exepath('neovim-node-host.cmd') : exepath('neovim-node-host') + if exists('g:node_host_prog') + return g:node_host_prog + endif + let global_modules = get(split(system('npm root -g'), "\n"), 0, '') + if v:shell_error || !isdirectory(global_modules) + return '' + endif + if !s:is_minimum_version(v:null, 6, 0) + return '' + endif + let entry_point = glob(global_modules . '/neovim/bin/cli.js') + if !filereadable(entry_point) + return '' + endif + return entry_point endfunction -function! provider#node#Prog() +function! provider#node#Prog() abort return s:prog endfunction @@ -19,37 +65,18 @@ function! provider#node#Require(host) abort return endif - if has('win32') - let args = provider#node#Prog() - else - let args = ['node'] - - if !empty($NVIM_NODE_HOST_DEBUG) - call add(args, '--inspect-brk') - endif + let args = ['node'] - call add(args , provider#node#Prog()) + if !empty($NVIM_NODE_HOST_DEBUG) && provider#node#can_inspect() + call add(args, '--inspect-brk') endif - try - let channel_id = jobstart(args, s:job_opts) - if rpcrequest(channel_id, 'poll') ==# 'ok' - return channel_id - endif - catch - echomsg v:throwpoint - echomsg v:exception - for row in provider#get_stderr(channel_id) - echomsg row - endfor - endtry - finally - call provider#clear_stderr(channel_id) - endtry - throw remote#host#LoadErrorForHost(a:host.orig_name, '$NVIM_NODE_LOG_FILE') + call add(args, provider#node#Prog()) + + return provider#Poll(args, a:host.orig_name, '$NVIM_NODE_LOG_FILE') endfunction -function! provider#node#Call(method, args) +function! provider#node#Call(method, args) abort if s:err != '' echoerr s:err return @@ -74,7 +101,7 @@ let s:err = '' let s:prog = provider#node#Detect() if empty(s:prog) - let s:err = 'Cannot find the "neovim" node package. Try :CheckHealth' + let s:err = 'Cannot find the "neovim" node package. Try :checkhealth' endif call remote#host#RegisterPlugin('node-provider', 'node', []) diff --git a/runtime/autoload/provider/python.vim b/runtime/autoload/provider/python.vim index 81fe194cb9..a06cbe4814 100644 --- a/runtime/autoload/provider/python.vim +++ b/runtime/autoload/provider/python.vim @@ -11,11 +11,11 @@ let g:loaded_python_provider = 1 let [s:prog, s:err] = provider#pythonx#Detect(2) -function! provider#python#Prog() +function! provider#python#Prog() abort return s:prog endfunction -function! provider#python#Error() +function! provider#python#Error() abort return s:err endfunction @@ -29,7 +29,7 @@ endif call remote#host#RegisterClone('legacy-python-provider', 'python') call remote#host#RegisterPlugin('legacy-python-provider', 'script_host.py', []) -function! provider#python#Call(method, args) +function! provider#python#Call(method, args) abort if s:err != '' return endif diff --git a/runtime/autoload/provider/python3.vim b/runtime/autoload/provider/python3.vim index 0c3b75b73d..242a224cb3 100644 --- a/runtime/autoload/provider/python3.vim +++ b/runtime/autoload/provider/python3.vim @@ -11,11 +11,11 @@ let g:loaded_python3_provider = 1 let [s:prog, s:err] = provider#pythonx#Detect(3) -function! provider#python3#Prog() +function! provider#python3#Prog() abort return s:prog endfunction -function! provider#python3#Error() +function! provider#python3#Error() abort return s:err endfunction @@ -29,7 +29,7 @@ endif call remote#host#RegisterClone('legacy-python3-provider', 'python3') call remote#host#RegisterPlugin('legacy-python3-provider', 'script_host.py', []) -function! provider#python3#Call(method, args) +function! provider#python3#Call(method, args) abort if s:err != '' return endif diff --git a/runtime/autoload/provider/pythonx.vim b/runtime/autoload/provider/pythonx.vim index 7285ed43ea..b51c398410 100644 --- a/runtime/autoload/provider/pythonx.vim +++ b/runtime/autoload/provider/pythonx.vim @@ -5,8 +5,6 @@ endif let s:loaded_pythonx_provider = 1 -let s:job_opts = {'rpc': v:true, 'on_stderr': function('provider#stderr_collector')} - function! provider#pythonx#Require(host) abort let ver = (a:host.orig_name ==# 'python') ? 2 : 3 @@ -20,22 +18,7 @@ function! provider#pythonx#Require(host) abort call add(args, plugin.path) endfor - try - let channel_id = jobstart(args, s:job_opts) - if rpcrequest(channel_id, 'poll') ==# 'ok' - return channel_id - endif - catch - echomsg v:throwpoint - echomsg v:exception - for row in provider#get_stderr(channel_id) - echomsg row - endfor - finally - call provider#clear_stderr(channel_id) - endtry - throw remote#host#LoadErrorForHost(a:host.orig_name, - \ '$NVIM_PYTHON_LOG_FILE') + return provider#Poll(args, a:host.orig_name, '$NVIM_PYTHON_LOG_FILE') endfunction function! provider#pythonx#Detect(major_ver) abort diff --git a/runtime/autoload/provider/ruby.vim b/runtime/autoload/provider/ruby.vim index da73a0dfc0..2fe3817512 100644 --- a/runtime/autoload/provider/ruby.vim +++ b/runtime/autoload/provider/ruby.vim @@ -4,17 +4,6 @@ if exists('g:loaded_ruby_provider') endif let g:loaded_ruby_provider = 1 -let s:stderr = {} -let s:job_opts = {'rpc': v:true} - -function! s:job_opts.on_stderr(chan_id, data, event) - let stderr = get(s:stderr, a:chan_id, ['']) - let last = remove(stderr, -1) - let a:data[0] = last.a:data[0] - call extend(stderr, a:data) - let s:stderr[a:chan_id] = stderr -endfunction - function! provider#ruby#Detect() abort if exists("g:ruby_host_prog") return g:ruby_host_prog @@ -23,7 +12,7 @@ function! provider#ruby#Detect() abort end endfunction -function! provider#ruby#Prog() +function! provider#ruby#Prog() abort return s:prog endfunction @@ -35,22 +24,10 @@ function! provider#ruby#Require(host) abort let prog .= " " . shellescape(plugin.path) endfor - try - let channel_id = jobstart(prog, s:job_opts) - if rpcrequest(channel_id, 'poll') ==# 'ok' - return channel_id - endif - catch - echomsg v:throwpoint - echomsg v:exception - for row in get(s:stderr, channel_id, []) - echomsg row - endfor - endtry - throw remote#host#LoadErrorForHost(a:host.orig_name, '$NVIM_RUBY_LOG_FILE') + return provider#Poll(prog, a:host.orig_name, '$NVIM_RUBY_LOG_FILE') endfunction -function! provider#ruby#Call(method, args) +function! provider#ruby#Call(method, args) abort if s:err != '' echoerr s:err return diff --git a/runtime/autoload/tutor.vim b/runtime/autoload/tutor.vim index 56e2283465..0f01190b9b 100644 --- a/runtime/autoload/tutor.vim +++ b/runtime/autoload/tutor.vim @@ -2,9 +2,6 @@ " Setup: {{{1 function! tutor#SetupVim() - if &columns < 90 - set columns=90 - endif if !exists('g:did_load_ftplugin') || g:did_load_ftplugin != 1 filetype plugin on endif diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index ef8b9c7d47..f828f2cdc1 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -49,6 +49,7 @@ version.api_prerelease Declares the current API level as unstable > (version.api_prerelease && fn.since == version.api_level) functions API function signatures ui_events UI event signatures |ui| +ui_options Supported |ui-options| {fn}.since API level where function {fn} was introduced {fn}.deprecated_since API level where function {fn} was deprecated types Custom handle types defined by Nvim @@ -60,8 +61,7 @@ External programs ("clients") can use the metadata to discover the |rpc-api|. API contract *api-contract* The API is made of functions and events. Clients call functions like those -described at |api-global|, and may "attach" in order to receive rich events, -described at |rpc-remote-ui|. +described at |api-global|, and may "attach" to receive rich |ui-events|. As Nvim develops, its API may change only according the following "contract": @@ -446,7 +446,11 @@ nvim_get_keymap({mode}) *nvim_get_keymap()* Array of maparg()-like dictionaries describing mappings nvim_get_api_info() *nvim_get_api_info()* - TODO: Documentation + Returns a 2-tuple (Array), where item 0 is the current channel + id and item 1 is the |api-metadata| map (Dictionary). + + Return:~ + 2-tuple [{channel-id}, {api-metadata}] Attributes:~ {async} @@ -477,6 +481,84 @@ nvim_call_atomic({calls}) *nvim_call_atomic()* error ocurred, the values from all preceding calls will still be returned. + *nvim_parse_expression()* +nvim_parse_expression({expr}, {flags}, {highlight}) + Parse a VimL expression + + Attributes:~ + {async} + + Parameters:~ + {expr} Expression to parse. Is always treated as a + single line. + {flags} Flags: - "m" if multiple expressions in a + row are allowed (only the first one will be + parsed), - "E" if EOC tokens are not allowed + (determines whether they will stop parsing + process or be recognized as an + operator/space, though also yielding an + error). - "l" when needing to start parsing + with lvalues for ":let" or ":for". Common + flag sets: - "m" to parse like for ":echo". - + "E" to parse like for "<C-r>=". - empty + string for ":call". - "lm" to parse for + ":let". + {highlight} If true, return value will also include + "highlight" key containing array of 4-tuples + (arrays) (Integer, Integer, Integer, String), + where first three numbers define the + highlighted region and represent line, + starting column and ending column (latter + exclusive: one should highlight region + [start_col, end_col)). + + Return:~ + AST: top-level dictionary with these keys: "error": + Dictionary with error, present only if parser saw some + error. Contains the following keys: "message": String, + error message in printf format, translated. Must contain + exactly one "%.*s". "arg": String, error message argument. + "len": Amount of bytes successfully parsed. With flags + equal to "" that should be equal to the length of expr + string. @note: “Sucessfully parsed†here means + “participated in AST creationâ€, not “till the first + errorâ€. "ast": AST, either nil or a dictionary with these + keys: "type": node type, one of the value names from + ExprASTNodeType stringified without "kExprNode" prefix. + "start": a pair [line, column] describing where node is + “started†where "line" is always 0 (will not be 0 if you + will be using nvim_parse_viml() on e.g. ":let", but that + is not present yet). Both elements are Integers. "len": + “length†of the node. This and "start" are there for + debugging purposes primary (debugging parser and providing + debug information). "children": a list of nodes described + in top/"ast". There always is zero, one or two children, + key will not be present if node has no children. Maximum + number of children may be found in node_maxchildren array. + Local values (present only for certain nodes): "scope": a + single Integer, specifies scope for "Option" and + "PlainIdentifier" nodes. For "Option" it is one of + ExprOptScope values, for "PlainIdentifier" it is one of + ExprVarScope values. "ident": identifier (without scope, + if any), present for "Option", "PlainIdentifier", + "PlainKey" and "Environment" nodes. "name": Integer, + register name (one character) or -1. Only present for + "Register" nodes. "cmp_type": String, comparison type, one + of the value names from ExprComparisonType, stringified + without "kExprCmp" prefix. Only present for "Comparison" + nodes. "ccs_strategy": String, case comparison strategy, + one of the value names from ExprCaseCompareStrategy, + stringified without "kCCStrategy" prefix. Only present for + "Comparison" nodes. "augmentation": String, augmentation + type for "Assignment" nodes. Is either an empty string, + "Add", "Subtract" or "Concat" for "=", "+=", "-=" or ".=" + respectively. "invert": Boolean, true if result of + comparison needs to be inverted. Only present for + "Comparison" nodes. "ivalue": Integer, integer value for + "Integer" nodes. "fvalue": Float, floating-point value for + "Float" nodes. "svalue": String, value for + "SingleQuotedString" and "DoubleQuotedString" nodes. + nvim__id({obj}) *nvim__id()* Returns object given as argument @@ -717,9 +799,10 @@ nvim_buf_add_highlight({buffer}, {src_id}, {hl_group}, {line}, or -1 for ungrouped highlight {hl_group} Name of the highlight group to use {line} Line to highlight (zero-indexed) - {col_start} Start of range of columns to highlight - {col_end} End of range of columns to highlight, or -1 - to highlight to end of line + {col_start} Start of (byte-indexed) column range to + highlight + {col_end} End of (byte-indexed) column range to + highlight, or -1 to highlight to end of line Return:~ The src_id that was used @@ -953,9 +1036,6 @@ nvim_tabpage_is_valid({tabpage}) *nvim_tabpage_is_valid()* ============================================================================== UI Functions *api-ui* -remote_ui_disconnect() *remote_ui_disconnect()* - TODO: Documentation - nvim_ui_attach({width}, {height}, {options}) *nvim_ui_attach()* TODO: Documentation @@ -968,4 +1048,4 @@ nvim_ui_try_resize({width}, {height}) *nvim_ui_try_resize()* nvim_ui_set_option({name}, {value}) *nvim_ui_set_option()* TODO: Documentation - vim:tw=78:ts=8:ft=help:norl:
\ No newline at end of file + vim:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 740f44414a..9a04bf2824 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -259,13 +259,12 @@ Name triggered by ~ |BufNew| just after creating a new buffer |SwapExists| detected an existing swap file -|TermOpen| when a terminal buffer is starting -|TermClose| when a terminal buffer ends +|TermOpen| when a terminal job starts +|TermClose| when a terminal job ends Options |FileType| when the 'filetype' option has been set |Syntax| when the 'syntax' option has been set -|TermChanged| after the value of 'term' has changed |OptionSet| after setting any option Startup and exit @@ -933,26 +932,20 @@ TabEnter Just after entering a tab page. |tab-page| TabLeave Just before leaving a tab page. |tab-page| A WinLeave event will have been triggered first. - {Nvim} *TabNew* + *TabNew* TabNew When creating a new tab page. |tab-page| After WinEnter and before TabEnter. - {Nvim} *TabNewEntered* + *TabNewEntered* TabNewEntered After entering a new tab page. |tab-page| After BufEnter. - {Nvim} *TabClosed* + *TabClosed* TabClosed After closing a tab page. <afile> can be used for the tab page number. - *TermChanged* -TermChanged After the value of 'term' has changed. Useful - for re-loading the syntax file to update the - colors, fonts and other terminal-dependent - settings. Executed for all loaded buffers. - {Nvim} *TermClose* -TermClose When a terminal buffer ends. - {Nvim} *TermOpen* -TermOpen When a terminal buffer is starting. This can - be used to configure the terminal emulator by - setting buffer variables. |terminal| + *TermClose* +TermClose When a |terminal| job ends. + *TermOpen* +TermOpen When a |terminal| job is starting. Can be + used to configure the terminal buffer. *TermResponse* TermResponse After the response to |t_RV| is received from the terminal. The value of |v:termresponse| diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt index c94c64eb84..eb2bac6fce 100644 --- a/runtime/doc/channel.txt +++ b/runtime/doc/channel.txt @@ -4,9 +4,9 @@ NVIM REFERENCE MANUAL by Thiago de Arruda -Nvim's facilities for async io *channel* +Nvim asynchronous IO *channel* - Type <M-]> to see the table of contents. + Type |gO| to see the table of contents. ============================================================================== 1. Introduction *channel-intro* @@ -43,7 +43,7 @@ bytes. Additionally, for a job channel using rpc, bytes can still be read over its stderr. Similarily, only bytes can be written to nvim's own stderr. *channel-callback* *buffered* - *on_stdout* *on_stderr* *on_stdin* *on_data* + *E5210* *on_stdout* *on_stderr* *on_stdin* *on_data* A callback function `on_{stream}` will be invoked with data read from the channel. By default, the callback will be invoked immediately when data is available, to facilitate interactive communication. The same callback will @@ -52,7 +52,11 @@ Alternatively the `{stream}_buffered` option can be set to invoke the callback only when the underlying stream reaches EOF, and will then be passed in complete output. This is helpful when only the complete output is useful, and not partial data. Futhermore if `{stream}_buffered` is set but not a callback, -the data is saved in the options dict, with the stream name as key. +the data is saved in the options dict, with the stream name as key. For this +to work a new options dict must be used for each opened channel. If a script +uses a global `s:job_opts` dict, it can be copied with |copy()| before supplying +it to |jobstart()|. If a dict is reused, so that the dict key already is +occupied, error `E5210` will be raised. - The arguments passed to the callback function are: diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt index 72dfe1230e..03699b3dfb 100644 --- a/runtime/doc/deprecated.txt +++ b/runtime/doc/deprecated.txt @@ -22,6 +22,10 @@ Commands ~ *:wv* *:wviminfo* Deprecated alias to |:wshada| command. +Environment Variables ~ +*$NVIM_LISTEN_ADDRESS* Deprecated in favor of |--listen|. If both are given, + $NVIM_LISTEN_ADDRESS is ignored. + Events ~ *EncodingChanged* Never fired; 'encoding' is always "utf-8". *FileEncoding* Never fired; equivalent to |EncodingChanged|. @@ -37,8 +41,8 @@ Functions ~ *file_readable()* Obsolete name for |filereadable()|. *highlight_exists()* Obsolete name for |hlexists()|. *highlightID()* Obsolete name for |hlID()|. -*jobclose()* Obsolete name for |chanclose()| -*jobsend()* Obsolete name for |chansend()| +*jobclose()* Obsolete name for |chanclose()| +*jobsend()* Obsolete name for |chansend()| *last_buffer_nr()* Obsolete name for bufnr("$"). Modifiers ~ @@ -48,8 +52,11 @@ Modifiers ~ *:map-special* <> notation is always enabled. |cpo-<| Options ~ +*'cscopeverbose'* Enabled by default. Use |:silent| instead. +'gd' +'gdefault' Enables the |:substitute| flag 'g' by default. *'fe'* 'fenc'+'enc' before Vim 6.0; no longer used. -*'highlight'* *'hl'* Names of builtin |highlight-groups| cannot be changed. +*'highlight'* *'hl'* Names of builtin |highlight-groups| cannot be changed. *'langnoremap'* Deprecated alias to 'nolangremap'. *'vi'* *'viminfo'* Deprecated alias to 'shada' option. diff --git a/runtime/doc/develop.txt b/runtime/doc/develop.txt index 36826e2479..4e77f40035 100644 --- a/runtime/doc/develop.txt +++ b/runtime/doc/develop.txt @@ -270,9 +270,9 @@ External UIs are expected to implement these common features: - Send the "super" key (Windows key, Apple key) as a |<D-| chord. Implementation ~ -- Options can be monitored for changes by the |OptionSet| autocmd. E.g. if the - user sets the 'guifont' option, this autocmd notifies channel 42: > - autocmd OptionSet guifont call rpcnotify(42, 'option-changed', 'guifont', &guifont) +- UI-related options ('guifont', 'ambiwidth', …) are published in the + "option_set" |ui-global| event. The event is triggered when the UI first + connects to Nvim and whenever an option is changed by the user or a plugin. vim:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt index 1b9a1b38fb..5939cb8a8b 100644 --- a/runtime/doc/editing.txt +++ b/runtime/doc/editing.txt @@ -562,16 +562,16 @@ list of the current window. buffer. Also see |++opt| and |+cmd|. -:[count]arge[dit][!] [++opt] [+cmd] {name} *:arge* *:argedit* - Add {name} to the argument list and edit it. +:[count]arge[dit][!] [++opt] [+cmd] {name} .. *:arge* *:argedit* + Add {name}s to the argument list and edit it. When {name} already exists in the argument list, this entry is edited. This is like using |:argadd| and then |:edit|. - Note that only one file name is allowed, and spaces - inside the file name are allowed, like with |:edit|. + Spaces in filenames have to be escaped with "\". [count] is used like with |:argadd|. - [!] is required if the current file cannot be - |abandon|ed. + If the current file cannot be |abandon|ed {name}s will + still be added to the argument list, but won't be + edited. No check for duplicates is done. Also see |++opt| and |+cmd|. :[count]arga[dd] {name} .. *:arga* *:argadd* *E479* diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index d2a3a962e6..11c4b62403 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1559,10 +1559,10 @@ v:exception The value of the exception most recently caught and not < Output: "caught oops". *v:false* *false-variable* -v:false Special value used to put "false" in JSON and msgpack. See - |json_encode()|. This value is converted to "v:false" when used - as a String (e.g. in |expr5| with string concatenation - operator) and to zero when used as a Number (e.g. in |expr5| +v:false Special value used to put "false" in JSON and msgpack. See + |json_encode()|. This value is converted to "v:false" when used + as a String (e.g. in |expr5| with string concatenation + operator) and to zero when used as a Number (e.g. in |expr5| or |expr7| when used with numeric operators). Read-only. *v:fcs_reason* *fcs_reason-variable* @@ -1703,16 +1703,16 @@ v:mouse_col Column number for a mouse click obtained with |getchar()|. value is zero when there was no mouse button click. *v:msgpack_types* *msgpack_types-variable* -v:msgpack_types Dictionary containing msgpack types used by |msgpackparse()| - and |msgpackdump()|. All types inside dictionary are fixed - (not editable) empty lists. To check whether some list is one +v:msgpack_types Dictionary containing msgpack types used by |msgpackparse()| + and |msgpackdump()|. All types inside dictionary are fixed + (not editable) empty lists. To check whether some list is one of msgpack types, use |is| operator. *v:null* *null-variable* -v:null Special value used to put "null" in JSON and NIL in msgpack. - See |json_encode()|. This value is converted to "v:null" when - used as a String (e.g. in |expr5| with string concatenation - operator) and to zero when used as a Number (e.g. in |expr5| +v:null Special value used to put "null" in JSON and NIL in msgpack. + See |json_encode()|. This value is converted to "v:null" when + used as a String (e.g. in |expr5| with string concatenation + operator) and to zero when used as a Number (e.g. in |expr5| or |expr7| when used with numeric operators). Read-only. *v:oldfiles* *oldfiles-variable* @@ -1788,9 +1788,9 @@ v:scrollstart String describing the script or function that caused the hit-enter prompt. *v:servername* *servername-variable* - *$NVIM_LISTEN_ADDRESS* -v:servername Default Nvim server address. Equivalent to - |$NVIM_LISTEN_ADDRESS| on startup. |serverstop()| +v:servername Primary listen-address of the current Nvim instance, the first + item returned by |serverlist()|. Can be set by |--listen| or + |$NVIM_LISTEN_ADDRESS| at startup. |serverstart()| |serverstop()| Read-only. @@ -1903,10 +1903,10 @@ v:throwpoint The point where the exception most recently caught and not < Output: "Exception from test.vim, line 2" *v:true* *true-variable* -v:true Special value used to put "true" in JSON and msgpack. See - |json_encode()|. This value is converted to "v:true" when used - as a String (e.g. in |expr5| with string concatenation - operator) and to one when used as a Number (e.g. in |expr5| or +v:true Special value used to put "true" in JSON and msgpack. See + |json_encode()|. This value is converted to "v:true" when used + as a String (e.g. in |expr5| with string concatenation + operator) and to one when used as a Number (e.g. in |expr5| or |expr7| when used with numeric operators). Read-only. *v:val* *val-variable* @@ -2492,7 +2492,7 @@ assert_fails({cmd} [, {error}]) *assert_fails()* assert_false({actual} [, {msg}]) *assert_false()* When {actual} is not false an error message is added to |v:errors|, like with |assert_equal()|. - A value is false when it is zero or |v:false|. When "{actual}" + A value is false when it is zero or |v:false|. When "{actual}" is not a number or |v:false| the assert fails. When {msg} is omitted an error in the form "Expected False but got {actual}" is produced. @@ -3182,7 +3182,7 @@ diff_hlID({lnum}, {col}) *diff_hlID()* empty({expr}) *empty()* Return the Number 1 if {expr} is empty, zero otherwise. A |List| or |Dictionary| is empty when it does not have any - items. A Number is empty when its value is zero. Special + items. A Number is empty when its value is zero. Special variable is empty when it is |v:false| or |v:null|. escape({string}, {chars}) *escape()* @@ -4327,17 +4327,25 @@ getqflist([{what}]) *getqflist()* If the optional {what} dictionary argument is supplied, then returns only the items listed in {what} as a dictionary. The following string items are supported in {what}: + context get the context stored with |setqflist()| + items quickfix list entries nr get information for this quickfix list; zero - means the current quickfix list + means the current quickfix list and '$' means + the last quickfix list title get the list title winid get the |window-ID| (if opened) all all of the above quickfix properties Non-string items in {what} are ignored. If "nr" is not present then the current quickfix list is used. + To get the number of lists in the quickfix stack, set 'nr' to + '$' in {what}. The 'nr' value in the returned dictionary + contains the quickfix stack size. In case of error processing {what}, an empty dictionary is returned. The returned dictionary contains the following entries: + context context information stored with |setqflist()| + items quickfix list entries nr quickfix list number title quickfix list title text winid quickfix |window-ID| (if opened) @@ -4749,7 +4757,7 @@ input({opts}) string, or a blank string (for no prompt). A '\n' can be used in the prompt to start a new line. - In the second form it accepts a single dictionary with the + In the second form it accepts a single dictionary with the following keys, any of which may be omitted: Key Default Description ~ @@ -4757,7 +4765,7 @@ input({opts}) default "" Same as {text} in the first form. completion nothing Same as {completion} in the first form. cancelreturn "" Same as {cancelreturn} from - |inputdialog()|. Also works with + |inputdialog()|. Also works with input(). highlight nothing Highlight handler: |Funcref|. @@ -4833,8 +4841,8 @@ input({opts}) modifier. If the function causes any errors, it will be skipped for the duration of the current input() call. - Currently coloring is disabled when command-line contains - arabic characters. + Highlighting is disabled if command-line contains arabic + characters. NOTE: This function must not be used in a startup file, for the versions that only run in GUI mode (e.g., the Win32 GUI). @@ -4948,19 +4956,19 @@ islocked({expr}) *islocked()* *E786* message. Use |exists()| to check for existence. id({expr}) *id()* - Returns a |String| which is a unique identifier of the - container type (|List|, |Dict| and |Partial|). It is - guaranteed that for the mentioned types `id(v1) ==# id(v2)` - returns true iff `type(v1) == type(v2) && v1 is v2` (note: - |v:_null_list| and |v:_null_dict| have the same `id()` with - different types because they are internally represented as - a NULL pointers). Currently `id()` returns a hexadecimal - representanion of the pointers to the containers (i.e. like - `0x994a40`), same as `printf("%p", {expr})`, but it is advised + Returns a |String| which is a unique identifier of the + container type (|List|, |Dict| and |Partial|). It is + guaranteed that for the mentioned types `id(v1) ==# id(v2)` + returns true iff `type(v1) == type(v2) && v1 is v2` (note: + |v:_null_list| and |v:_null_dict| have the same `id()` with + different types because they are internally represented as + a NULL pointers). Currently `id()` returns a hexadecimal + representanion of the pointers to the containers (i.e. like + `0x994a40`), same as `printf("%p", {expr})`, but it is advised against counting on exact format of return value. - It is not guaranteed that `id(no_longer_existing_container)` - will not be equal to some other `id()`: new containers may + It is not guaranteed that `id(no_longer_existing_container)` + will not be equal to some other `id()`: new containers may reuse identifiers of the garbage-collected ones. items({dict}) *items()* @@ -5071,14 +5079,14 @@ join({list} [, {sep}]) *join()* The opposite function is |split()|. json_decode({expr}) *json_decode()* - Convert {expr} from JSON object. Accepts |readfile()|-style - list as the input, as well as regular string. May output any + Convert {expr} from JSON object. Accepts |readfile()|-style + list as the input, as well as regular string. May output any Vim value. In the following cases it will output |msgpack-special-dict|: 1. Dictionary contains duplicate key. 2. Dictionary contains empty key. - 3. String contains NUL byte. Two special dictionaries: for - dictionary and for string will be emitted in case string + 3. String contains NUL byte. Two special dictionaries: for + dictionary and for string will be emitted in case string with NUL byte was a dictionary key. Note: function treats its input as UTF-8 always. The JSON @@ -5087,14 +5095,14 @@ json_decode({expr}) *json_decode()* Non-UTF-8 characters are an error. json_encode({expr}) *json_encode()* - Convert {expr} into a JSON string. Accepts - |msgpack-special-dict| as the input. Will not convert - |Funcref|s, mappings with non-string keys (can be created as - |msgpack-special-dict|), values with self-referencing - containers, strings which contain non-UTF-8 characters, - pseudo-UTF-8 strings which contain codepoints reserved for - surrogate pairs (such strings are not valid UTF-8 strings). - Non-printable characters are converted into "\u1234" escapes + Convert {expr} into a JSON string. Accepts + |msgpack-special-dict| as the input. Will not convert + |Funcref|s, mappings with non-string keys (can be created as + |msgpack-special-dict|), values with self-referencing + containers, strings which contain non-UTF-8 characters, + pseudo-UTF-8 strings which contain codepoints reserved for + surrogate pairs (such strings are not valid UTF-8 strings). + Non-printable characters are converted into "\u1234" escapes or special escapes like "\t", other are dumped as-is. keys({dict}) *keys()* @@ -5193,7 +5201,7 @@ line({expr}) The result is a Number, which is the line number of the file This autocommand jumps to the last known position in a file just after opening it, if the '" mark is set: > :au BufReadPost * - \ if line("'\"") > 1 && line("'\"") <= line("$") && &ft !~# 'commit' + \ if line("'\"") > 1 && line("'\"") <= line("$") && &ft !~# 'commit' \ | exe "normal! g`\"" \ | endif @@ -5244,7 +5252,7 @@ log10({expr}) *log10()* < -2.0 luaeval({expr}[, {expr}]) - Evaluate Lua expression {expr} and return its result converted + Evaluate Lua expression {expr} and return its result converted to Vim data structures. See |lua-luaeval| for more details. map({expr1}, {expr2}) *map()* @@ -5493,7 +5501,7 @@ matchaddpos({group}, {pos} [, {priority} [, {id} [, {dict}]]]) sets buffer line boundaries to redraw screen. It is supposed to be used when fast match additions and deletions are required, for example to highlight matching parentheses. - + *E5030* *E5031* The list {pos} can contain one of these items: - A number. This whole line will be highlighted. The first line has number 1. @@ -5507,6 +5515,10 @@ matchaddpos({group}, {pos} [, {priority} [, {id} [, {dict}]]]) - A list with three numbers, e.g., [23, 11, 3]. As above, but the third number gives the length of the highlight in bytes. + Entries with zero and negative line numbers are silently + ignored, as well as entries with negative column numbers and + lengths. + The maximum number of positions is 8. Example: > @@ -5675,7 +5687,7 @@ mkdir({name} [, {path} [, {prot}]]) :call mkdir($HOME . "/tmp/foo/bar", "p", 0700) < This function is not available in the |sandbox|. - If you try to create an existing directory with {path} set to + If you try to create an existing directory with {path} set to "p" mkdir() will silently exit. *mode()* @@ -5728,78 +5740,76 @@ msgpackdump({list}) {Nvim} *msgpackdump()* 5. Points 3. and 4. do not apply to |msgpack-special-dict|s. msgpackparse({list}) {Nvim} *msgpackparse()* - Convert a |readfile()|-style list to a list of VimL objects. + Convert a |readfile()|-style list to a list of VimL objects. Example: > let fname = expand('~/.config/nvim/shada/main.shada') let mpack = readfile(fname, 'b') let shada_objects = msgpackparse(mpack) -< This will read ~/.config/nvim/shada/main.shada file to +< This will read ~/.config/nvim/shada/main.shada file to `shada_objects` list. Limitations: - 1. Mapping ordering is not preserved unless messagepack - mapping is dumped using generic mapping + 1. Mapping ordering is not preserved unless messagepack + mapping is dumped using generic mapping (|msgpack-special-map|). - 2. Since the parser aims to preserve all data untouched - (except for 1.) some strings are parsed to - |msgpack-special-dict| format which is not convenient to + 2. Since the parser aims to preserve all data untouched + (except for 1.) some strings are parsed to + |msgpack-special-dict| format which is not convenient to use. *msgpack-special-dict* - Some messagepack strings may be parsed to special + Some messagepack strings may be parsed to special dictionaries. Special dictionaries are dictionaries which 1. Contain exactly two keys: `_TYPE` and `_VAL`. - 2. `_TYPE` key is one of the types found in |v:msgpack_types| + 2. `_TYPE` key is one of the types found in |v:msgpack_types| variable. - 3. Value for `_VAL` has the following format (Key column + 3. Value for `_VAL` has the following format (Key column contains name of the key from |v:msgpack_types|): Key Value ~ - nil Zero, ignored when dumping. This value cannot - possibly appear in |msgpackparse()| output in Neovim - versions which have |v:null|. - boolean One or zero. When dumping it is only checked that - value is a |Number|. This value cannot possibly - appear in |msgpackparse()| output in Neovim versions - which have |v:true| and |v:false|. - integer |List| with four numbers: sign (-1 or 1), highest two - bits, number with bits from 62nd to 31st, lowest 31 - bits. I.e. to get actual number one will need to use + nil Zero, ignored when dumping. Not returned by + |msgpackparse()| since |v:null| was introduced. + boolean One or zero. When dumping it is only checked that + value is a |Number|. Not returned by |msgpackparse()| + since |v:true| and |v:false| were introduced. + integer |List| with four numbers: sign (-1 or 1), highest two + bits, number with bits from 62nd to 31st, lowest 31 + bits. I.e. to get actual number one will need to use code like > _VAL[0] * ((_VAL[1] << 62) & (_VAL[2] << 31) & _VAL[3]) -< Special dictionary with this type will appear in - |msgpackparse()| output under one of the following +< Special dictionary with this type will appear in + |msgpackparse()| output under one of the following circumstances: - 1. |Number| is 32-bit and value is either above + 1. |Number| is 32-bit and value is either above INT32_MAX or below INT32_MIN. - 2. |Number| is 64-bit and value is above INT64_MAX. It - cannot possibly be below INT64_MIN because msgpack + 2. |Number| is 64-bit and value is above INT64_MAX. It + cannot possibly be below INT64_MIN because msgpack C parser does not support such values. - float |Float|. This value cannot possibly appear in + float |Float|. This value cannot possibly appear in |msgpackparse()| output. - string |readfile()|-style list of strings. This value will - appear in |msgpackparse()| output if string contains - zero byte or if string is a mapping key and mapping is - being represented as special dictionary for other + string |readfile()|-style list of strings. This value will + appear in |msgpackparse()| output if string contains + zero byte or if string is a mapping key and mapping is + being represented as special dictionary for other reasons. - binary |readfile()|-style list of strings. This value will - appear in |msgpackparse()| output if binary string + binary |readfile()|-style list of strings. This value will + appear in |msgpackparse()| output if binary string contains zero byte. - array |List|. This value cannot appear in |msgpackparse()| + array |List|. This value cannot appear in |msgpackparse()| output. *msgpack-special-map* - map |List| of |List|s with two items (key and value) each. - This value will appear in |msgpackparse()| output if + map |List| of |List|s with two items (key and value) each. + This value will appear in |msgpackparse()| output if parsed mapping contains one of the following keys: - 1. Any key that is not a string (including keys which + 1. Any key that is not a string (including keys which are binary strings). 2. String with NUL byte inside. 3. Duplicate key. 4. Empty key. - ext |List| with two values: first is a signed integer - representing extension type. Second is + ext |List| with two values: first is a signed integer + representing extension type. Second is |readfile()|-style list of strings. nextnonblank({lnum}) *nextnonblank()* @@ -6073,11 +6083,11 @@ pumvisible() *pumvisible()* py3eval({expr}) *py3eval()* Evaluate Python expression {expr} and return its result converted to Vim data structures. - Numbers and strings are returned as they are (strings are - copied though, Unicode strings are additionally converted to + Numbers and strings are returned as they are (strings are + copied though, Unicode strings are additionally converted to UTF-8). Lists are represented as Vim |List| type. - Dictionaries are represented as Vim |Dictionary| type with + Dictionaries are represented as Vim |Dictionary| type with keys converted to strings. {only available when compiled with the |+python3| feature} @@ -6145,7 +6155,7 @@ readfile({fname} [, {binary} [, {max}]]) reltime([{start} [, {end}]]) *reltime()* Return an item that represents a time value. The format of the item depends on the system. It can be passed to - |reltimestr()| to convert it to a string or |reltimefloat()| + |reltimestr()| to convert it to a string or |reltimefloat()| to convert to a float. Without an argument it returns the current "relative time", an @@ -6628,15 +6638,11 @@ server2client({clientid}, {string}) *server2client()* :echo server2client(expand("<client>"), "HELLO") < serverlist() *serverlist()* - Returns a list of available server names in a list. - When there are no servers an empty string is returned. + Returns a list of server addresses, or empty if all servers + were stopped. |serverstart()| |serverstop()| Example: > :echo serverlist() -< {Nvim} *--serverlist* - The Vim command-line option `--serverlist` was removed from - Nvim, but it can be imitated: > - nvim --cmd "echo serverlist()" --cmd "q" -< + serverstart([{address}]) *serverstart()* Opens a socket or named pipe at {address} and listens for |RPC| messages. Clients can send |API| commands to the address @@ -6664,13 +6670,9 @@ serverstart([{address}]) *serverstart()* < |$NVIM_LISTEN_ADDRESS| is set to {address} if not already set. - *--servername* - The Vim command-line option `--servername` can be imitated: > - nvim --cmd "let g:server_addr = serverstart('foo')" -< serverstop({address}) *serverstop()* - Closes the pipe or socket at {address}. Does nothing if - {address} is empty or invalid. + Closes the pipe or socket at {address}. + Returns TRUE if {address} is valid, else FALSE. If |$NVIM_LISTEN_ADDRESS| is stopped it is unset. If |v:servername| is stopped it is set to the next available address returned by |serverlist()|. @@ -6843,6 +6845,7 @@ setqflist({list} [, {action}[, {what}]]) *setqflist()* nr error number text description of the error type single-character error type, 'E', 'W', etc. + valid recognized error message The "col", "vcol", "nr", "type" and "text" entries are optional. Either "lnum" or "pattern" entry can be used to @@ -6852,21 +6855,26 @@ setqflist({list} [, {action}[, {what}]]) *setqflist()* item will not be handled as an error line. If both "pattern" and "lnum" are present then "pattern" will be used. + If the "valid" entry is not supplied, then the valid flag is + set when "bufnr" is a valid buffer or "filename" exists. If you supply an empty {list}, the quickfix list will be cleared. Note that the list is not exactly the same as what |getqflist()| returns. - *E927* - If {action} is set to 'a', then the items from {list} are - added to the existing quickfix list. If there is no existing - list, then a new list is created. + {action} values: *E927* + 'a' The items from {list} are added to the existing + quickfix list. If there is no existing list, then a + new list is created. - If {action} is set to 'r', then the items from the current - quickfix list are replaced with the items from {list}. This - can also be used to clear the list: > - :call setqflist([], 'r') + 'r' The items from the current quickfix list are replaced + with the items from {list}. This can also be used to + clear the list: > + :call setqflist([], 'r') < + 'f' All the quickfix lists in the quickfix stack are + freed. + If {action} is not present or is set to ' ', then a new list is created. @@ -6877,7 +6885,12 @@ setqflist({list} [, {action}[, {what}]]) *setqflist()* only the items listed in {what} are set. The first {list} argument is ignored. The following items can be specified in {what}: - nr list number in the quickfix stack + context any Vim type can be stored as a context + items list of quickfix entries. Same as the {list} + argument. + nr list number in the quickfix stack; zero + means the current quickfix list and '$' means + the last quickfix list title quickfix list title text Unsupported keys in {what} are ignored. If the "nr" item is not present, then the current quickfix list @@ -6929,9 +6942,9 @@ setreg({regname}, {value} [, {options}]) :call setreg('a', "1\n2\n3", 'b5') < This example shows using the functions to save and restore a - register (note: you may not reliably restore register value - without using the third argument to |getreg()| as without it - newlines are represented as newlines AND Nul bytes are + register (note: you may not reliably restore register value + without using the third argument to |getreg()| as without it + newlines are represented as newlines AND Nul bytes are represented as newlines as well, see |NL-used-for-Nul|). > :let var_a = getreg('a', 1, 1) :let var_amode = getregtype('a') @@ -6982,18 +6995,22 @@ shellescape({string} [, {special}]) *shellescape()* 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. - The <NL> character is also escaped. With a |non-zero-arg| - {special} and 'shell' containing "csh" in the tail it's + + 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. + Example of use with a |:!| command: > :exe '!dir ' . shellescape(expand('<cfile>'), 1) < This results in a directory listing for the file under the @@ -7369,20 +7386,20 @@ string({expr}) Return {expr} converted to a String. If {expr} is a Number, {expr} type result ~ String 'string' Number 123 - Float 123.123456 or 1.123456e8 or + Float 123.123456 or 1.123456e8 or `str2float('inf')` Funcref `function('name')` List [item, item] Dictionary {key: value, key: value} Note that in String values the ' character is doubled. Also see |strtrans()|. - Note 2: Output format is mostly compatible with YAML, except - for infinite and NaN floating-point values representations - which use |str2float()|. Strings are also dumped literally, - only single quote is escaped, which does not allow using YAML - for parsing back binary strings. |eval()| should always work for - strings and floats though and this is the only official - method, use |msgpackdump()| or |json_encode()| if you need to + Note 2: Output format is mostly compatible with YAML, except + for infinite and NaN floating-point values representations + which use |str2float()|. Strings are also dumped literally, + only single quote is escaped, which does not allow using YAML + for parsing back binary strings. |eval()| should always work for + strings and floats though and this is the only official + method, use |msgpackdump()| or |json_encode()| if you need to share data with other application. *strlen()* @@ -7577,17 +7594,29 @@ synIDtrans({synID}) *synIDtrans()* ":highlight link" are followed. synconcealed({lnum}, {col}) *synconcealed()* - The result is a List. The first item in the list is 0 if the - character at the position {lnum} and {col} is not part of a - concealable region, 1 if it is. The second item in the list is - a string. If the first item is 1, the second item contains the - text which will be displayed in place of the concealed text, - depending on the current setting of 'conceallevel'. The third - and final item in the list is a unique number representing the - specific syntax region matched. This allows detection of the - beginning of a new concealable region if there are two - consecutive regions with the same replacement character. - For an example use see $VIMRUNTIME/syntax/2html.vim . + The result is a List with currently three items: + 1. The first item in the list is 0 if the character at the + position {lnum} and {col} is not part of a concealable + region, 1 if it is. + 2. The second item in the list is a string. If the first item + is 1, the second item contains the text which will be + displayed in place of the concealed text, depending on the + current setting of 'conceallevel' and 'listchars'. + 3. The third and final item in the list is a number + representing the specific syntax region matched in the + line. When the character is not concealed the value is + zero. This allows detection of the beginning of a new + concealable region if there are two consecutive regions + with the same replacement character. For an example, if + the text is "123456" and both "23" and "45" are concealed + and replace by the character "X", then: + call returns ~ + synconcealed(lnum, 1) [0, '', 0] + synconcealed(lnum, 2) [1, 'X', 1] + synconcealed(lnum, 3) [1, 'X', 1] + synconcealed(lnum, 4) [1, 'X', 2] + synconcealed(lnum, 5) [1, 'X', 2] + synconcealed(lnum, 6) [0, '', 0] synstack({lnum}, {col}) *synstack()* @@ -7620,6 +7649,9 @@ system({cmd} [, {input}]) *system()* *E677* |writefile()| does with {binary} set to "b" (i.e. with a newline between each list item, and newlines inside list items converted to NULs). + When {input} is given and is a valid buffer id, the content of + the buffer is written to the file line by line, each line + terminated by a NL (and NUL where the text has NL). *E5677* Note: system() cannot write to or read from backgrounded ("&") shell commands, e.g.: > @@ -7630,10 +7662,10 @@ system({cmd} [, {input}]) *system()* *E677* redirection syntax) before input can reach it. Use |jobstart()| instead. - Note: Use |shellescape()| or |::S| with |expand()| or - |fnamemodify()| to escape special characters in a command - argument. Newlines in {cmd} may cause the command to fail. - The characters in 'shellquote' and 'shellxquote' may also + Note: Use |shellescape()| or |::S| with |expand()| or + |fnamemodify()| to escape special characters in a command + argument. Newlines in {cmd} may cause the command to fail. + The characters in 'shellquote' and 'shellxquote' may also cause trouble. The result is a String. Example: > @@ -7660,9 +7692,9 @@ system({cmd} [, {input}]) *system()* *E677* systemlist({cmd} [, {input} [, {keepempty}]]) *systemlist()* - Same as |system()|, but returns a |List| with lines (parts of - output separated by NL) with NULs transformed into NLs. Output - is the same as |readfile()| will output with {binary} argument + Same as |system()|, but returns a |List| with lines (parts of + output separated by NL) with NULs transformed into NLs. Output + is the same as |readfile()| will output with {binary} argument set to "b", except that a final newline is not preserved, unless {keepempty} is non-zero. Note that on MS-Windows you may get trailing CR characters. @@ -7926,7 +7958,7 @@ type({expr}) *type()* :if type(myvar) == type({}) :if type(myvar) == type(0.0) :if type(myvar) == type(v:true) -< In place of checking for |v:null| type it is better to check +< In place of checking for |v:null| type it is better to check for |v:null| directly as it is the only value of this type: > :if myvar is v:null < To check if the v:t_ variables exist use this: > @@ -8243,10 +8275,10 @@ writefile({list}, {fname} [, {flags}]) :call writefile(["foo"], "event.log", "a") :call writefile(["bar"], "event.log", "a") < - When {flags} contains "S" fsync() call is not used, with "s" - it is used, 'fsync' option applies by default. No fsync() - means that writefile() will finish faster, but writes may be - left in OS buffers and not yet written to disk. Such changes + When {flags} contains "S" fsync() call is not used, with "s" + it is used, 'fsync' option applies by default. No fsync() + means that writefile() will finish faster, but writes may be + left in OS buffers and not yet written to disk. Such changes will disappear if system crashes before OS does writing. All NL characters are replaced with a NUL character. @@ -8414,6 +8446,7 @@ win32 Windows version of Vim (32 or 64 bit). winaltkeys Compiled with 'winaltkeys' option. windows Compiled with support for more than one window. writebackup Compiled with 'writebackup' default on. +wsl WSL (Windows Subsystem for Linux) version of Vim. *string-match* Matching a pattern in a String @@ -8728,7 +8761,7 @@ like this: > When such a function is called, and it is not defined yet, Vim will search the "autoload" directories in 'runtimepath' for a script file called -"filename.vim". For example "~/.config/nvim/autoload/filename.vim". That +"filename.vim". For example "~/.config/nvim/autoload/filename.vim". That file should then define the function like this: > function filename#funcname() @@ -8889,11 +8922,6 @@ This does NOT work: > value and the global value are changed. Example: > :let &path = &path . ',/usr/local/include' -< This also works for terminal codes in the form t_xx. - But only for alphanumerical names. Example: > - :let &t_k1 = "\<Esc>[234;" -< When the code does not exist yet it will be created as - a terminal key code, there is no error. :let &{option-name} .= {expr1} For a string option: Append {expr1} to the value. @@ -9004,8 +9032,8 @@ This does NOT work: > < *E741* *E940* If you try to change a locked variable you get an error message: "E741: Value is locked: {name}". - If you try to lock or unlock a built-in variable you - will get an error message "E940: Cannot lock or unlock + If you try to lock or unlock a built-in variable you + will get an error message "E940: Cannot lock or unlock variable {name}". [depth] is relevant when locking a |List| or @@ -9264,17 +9292,17 @@ This does NOT work: > with the |:redraw| command. Example: > :new | redraw | echo "there is a new window" < *:echo-self-refer* - When printing nested containers echo prints second - occurrence of the self-referencing container using - "[...@level]" (self-referencing |List|) or + When printing nested containers echo prints second + occurrence of the self-referencing container using + "[...@level]" (self-referencing |List|) or "{...@level}" (self-referencing |Dict|): > :let l = [] :call add(l, l) :let l2 = [] :call add(l2, [l2]) :echo l l2 -< echoes "[[...@0]] [[[...@0]]]". Echoing "[l]" will - echo "[[[...@1]]]" because l first occurs at second +< echoes "[[...@0]] [[[...@0]]]". Echoing "[l]" will + echo "[[[...@1]]]" because l first occurs at second level. *:echon* @@ -10505,18 +10533,19 @@ missing: > To execute a command only when the |+eval| feature is disabled requires a trick, as this example shows: > - if 1 - nnoremap : :" - endif - normal :set history=111<CR> - if 1 - nunmap : - endif + + silent! while 0 + set history=111 + silent! endwhile + +When the |+eval| feature is available the command is skipped because of the +"while 0". Without the |+eval| feature the "while 0" is an error, which is +silently ignored, and the command is executed. The "<CR>" here is a real CR character, type CTRL-V Enter to get it. When the |+eval| feature is available the ":" is remapped to add a double -quote, which has the effect of commenting-out the command. without the +quote, which has the effect of commenting-out the command. Without the |+eval| feature the nnoremap command is skipped and the command is executed. ============================================================================== @@ -10563,7 +10592,7 @@ option will still be marked as it was set in the sandbox. In a few situations it is not allowed to change the text in the buffer, jump to another window and some other things that might confuse or break what Vim is currently doing. This mostly applies to things that happen when Vim is -actually doing something else. For example, evaluating the 'balloonexpr' may +actually doing something else. For example, evaluating the 'balloonexpr' may happen any moment the mouse cursor is resting at some position. This is not allowed when the textlock is active: @@ -10573,5 +10602,123 @@ This is not allowed when the textlock is active: - closing a window or quitting Vim - etc. +============================================================================== +13. Command-line expressions highlighting *expr-highlight* + +Expressions entered by the user in |i_CTRL-R_=|, |c_CTRL-\_e|, |quote=| are +highlighted by the built-in expressions parser. It uses highlight groups +described in the table below, which may be overriden by colorschemes. + *hl-NvimInvalid* +Besides the "Nvim"-prefixed highlight groups described below, there are +"NvimInvalid"-prefixed highlight groups which have the same meaning but +indicate that the token contains an error or that an error occurred just +before it. They have mostly the same hierarchy, except that (by default) in +place of any non-Nvim-prefixed group NvimInvalid linking to `Error` is used +and some other intermediate groups are present. + +Group Default link Colored expression ~ +*hl-NvimInternalError* None, red/red Parser bug + +*hl-NvimAssignment* Operator Generic assignment +*hl-NvimPlainAssignment* NvimAssignment `=` in |:let| +*hl-NvimAugmentedAssignment* NvimAssignment Generic, `+=`/`-=`/`.=` +*hl-NvimAssignmentWithAddition* NvimAugmentedAssignment `+=` in |:let+=| +*hl-NvimAssignmentWithSubtraction* NvimAugmentedAssignment `-=` in |:let-=| +*hl-NvimAssignmentWithConcatenation* NvimAugmentedAssignment `.=` in |:let.=| + +*hl-NvimOperator* Operator Generic operator + +*hl-NvimUnaryOperator* NvimOperator Generic unary op +*hl-NvimUnaryPlus* NvimUnaryOperator |expr-unary-+| +*hl-NvimUnaryMinus* NvimUnaryOperator |expr-unary--| +*hl-NvimNot* NvimUnaryOperator |expr-!| + +*hl-NvimBinaryOperator* NvimOperator Generic binary op +*hl-NvimComparison* NvimBinaryOperator Any |expr4| operator +*hl-NvimComparisonModifier* NvimComparison `#`/`?` near |expr4| op +*hl-NvimBinaryPlus* NvimBinaryOperator |expr-+| +*hl-NvimBinaryMinus* NvimBinaryOperator |expr--| +*hl-NvimConcat* NvimBinaryOperator |expr-.| +*hl-NvimConcatOrSubscript* NvimConcat |expr-.| or |expr-entry| +*hl-NvimOr* NvimBinaryOperator |expr-barbar| +*hl-NvimAnd* NvimBinaryOperator |expr-&&| +*hl-NvimMultiplication* NvimBinaryOperator |expr-star| +*hl-NvimDivision* NvimBinaryOperator |expr-/| +*hl-NvimMod* NvimBinaryOperator |expr-%| + +*hl-NvimTernary* NvimOperator `?` in |expr1| +*hl-NvimTernaryColon* NvimTernary `:` in |expr1| + +*hl-NvimParenthesis* Delimiter Generic bracket +*hl-NvimLambda* NvimParenthesis `{`/`}` in |lambda| +*hl-NvimNestingParenthesis* NvimParenthesis `(`/`)` in |expr-nesting| +*hl-NvimCallingParenthesis* NvimParenthesis `(`/`)` in |expr-function| + +*hl-NvimSubscript* NvimParenthesis Generic subscript +*hl-NvimSubscriptBracket* NvimSubscript `[`/`]` in |expr-[]| +*hl-NvimSubscriptColon* NvimSubscript `:` in |expr-[:]| +*hl-NvimCurly* NvimSubscript `{`/`}` in + |curly-braces-names| + +*hl-NvimContainer* NvimParenthesis Generic container +*hl-NvimDict* NvimContainer `{`/`}` in |dict| literal +*hl-NvimList* NvimContainer `[`/`]` in |list| literal + +*hl-NvimIdentifier* Identifier Generic identifier +*hl-NvimIdentifierScope* NvimIdentifier Namespace: letter + before `:` in + |internal-variables| +*hl-NvimIdentifierScopeDelimiter* NvimIdentifier `:` after namespace + letter +*hl-NvimIdentifierName* NvimIdentifier Rest of the ident +*hl-NvimIdentifierKey* NvimIdentifier Identifier after + |expr-entry| + +*hl-NvimColon* Delimiter `:` in |dict| literal +*hl-NvimComma* Delimiter `,` in |dict|/|list| + literal or + |expr-function| +*hl-NvimArrow* Delimiter `->` in |lambda| + +*hl-NvimRegister* SpecialChar |expr-register| +*hl-NvimNumber* Number Non-prefix digits + in integer + |expr-number| +*hl-NvimNumberPrefix* Type `0` for |octal-number| + `0x` for |hex-number| + `0b` for |binary-number| +*hl-NvimFloat* NvimNumber Floating-point + number + +*hl-NvimOptionSigil* Type `&` in |expr-option| +*hl-NvimOptionScope* NvimIdentifierScope Option scope if any +*hl-NvimOptionScopeDelimiter* NvimIdentifierScopeDelimiter + `:` after option scope +*hl-NvimOptionName* NvimIdentifier Option name + +*hl-NvimEnvironmentSigil* NvimOptionSigil `$` in |expr-env| +*hl-NvimEnvironmentName* NvimIdentifier Env variable name + +*hl-NvimString* String Generic string +*hl-NvimStringBody* NvimString Generic string + literal body +*hl-NvimStringQuote* NvimString Generic string quote +*hl-NvimStringSpecial* SpecialChar Generic string + non-literal body + +*hl-NvimSingleQuote* NvimStringQuote `'` in |expr-'| +*hl-NvimSingleQuotedBody* NvimStringBody Literal part of + |expr-'| string body +*hl-NvimSingleQuotedQuote* NvimStringSpecial `''` inside |expr-'| + string body + +*hl-NvimDoubleQuote* NvimStringQuote `"` in |expr-quote| +*hl-NvimDoubleQuotedBody* NvimStringBody Literal part of + |expr-quote| body +*hl-NvimDoubleQuotedEscape* NvimStringSpecial Valid |expr-quote| + escape sequence +*hl-NvimDoubleQuotedUnknownEscape* NvimInvalidValue Unrecognized + |expr-quote| escape + sequence vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/filetype.txt b/runtime/doc/filetype.txt index 78402b47fe..74c453f79a 100644 --- a/runtime/doc/filetype.txt +++ b/runtime/doc/filetype.txt @@ -24,10 +24,8 @@ Each time a new or existing file is edited, Vim will try to recognize the type of the file and set the 'filetype' option. This will trigger the FileType event, which can be used to set the syntax highlighting, set options, etc. -Detail: The ":filetype on" command will load one of these files: - Mac $VIMRUNTIME/filetype.vim - MS-DOS $VIMRUNTIME\filetype.vim - Unix $VIMRUNTIME/filetype.vim +Detail: The ":filetype on" command will load this file: + $VIMRUNTIME/filetype.vim This file is a Vim script that defines autocommands for the BufNewFile and BufRead events. If the file type is not found by the name, the file $VIMRUNTIME/scripts.vim is used to detect it from the diff --git a/runtime/doc/if_cscop.txt b/runtime/doc/if_cscop.txt index ac3d7a9ed8..451d525ea8 100644 --- a/runtime/doc/if_cscop.txt +++ b/runtime/doc/if_cscop.txt @@ -4,42 +4,19 @@ VIM REFERENCE MANUAL by Andy Kahn *cscope* *Cscope* -This document explains how to use Vim's cscope interface. +Cscope is a "code intelligence" tool that helps you navigate C programs. It +can also perform some refactoring tasks, such as renaming a global variable in +all source files. Think of it as "ctags on steroids". -Cscope is a tool like ctags, but think of it as ctags on steroids since it -does a lot more than what ctags provides. In Vim, jumping to a result from -a cscope query is just like jumping to any tag; it is saved on the tag stack -so that with the right keyboard mappings, you can jump back and forth between -functions as you normally would with |tags|. +See |cscope-usage| for a quickstart. Type |gO| to see the table of contents. ============================================================================== -1. Cscope introduction *cscope-intro* +Cscope introduction *cscope-intro* -The following text is taken from a version of the cscope man page: - ----- - - Cscope is an interactive screen-oriented tool that helps you: - - Learn how a C program works without endless flipping through a thick - listing. - - Locate the section of code to change to fix a bug without having to - learn the entire program. - - Examine the effect of a proposed change such as adding a value to an - enum variable. - - Verify that a change has been made in all source files such as adding - an argument to an existing function. - - Rename a global variable in all source files. - - Change a constant to a preprocessor symbol in selected lines of files. - - It is designed to answer questions like: +Cscope is designed to answer questions like: Where is this symbol used? Where is it defined? Where did this variable get its value? @@ -51,35 +28,17 @@ The following text is taken from a version of the cscope man page: Where is this source file in the directory structure? What files include this header file? - Cscope answers these questions from a symbol database that it builds the - first time it is used on the source files. On a subsequent call, cscope - rebuilds the database only if a source file has changed or the list of - source files is different. When the database is rebuilt the data for the - unchanged files is copied from the old database, which makes rebuilding - much faster than the initial build. - - ----- - -When cscope is normally invoked, you will get a full-screen selection -screen allowing you to make a query for one of the above questions. -However, once a match is found to your query and you have entered your -text editor to edit the source file containing match, you cannot simply -jump from tag to tag as you normally would with vi's Ctrl-] or :tag -command. - -Vim's cscope interface is done by invoking cscope with its line-oriented -interface, and then parsing the output returned from a query. The end -result is that cscope query results become just like regular tags, so -you can jump to them just like you do with normal tags (Ctrl-] or :tag) -and then go back by popping off the tagstack with Ctrl-T. (Please note -however, that you don't actually jump to a cscope tag simply by doing -Ctrl-] or :tag without remapping these commands or setting an option. -See the remaining sections on how the cscope interface works and for -suggested use.) +Cscope answers these questions from a symbol database that it builds the first +time it is used on the source files. On a subsequent call, cscope rebuilds +the database only if a source file has changed or the list of source files is +different. When the database is rebuilt the data for the unchanged files is +copied from the old database, which makes rebuilding much faster than the +initial build. +See |cscope-usage| to get started. ============================================================================== -2. Cscope related commands *cscope-commands* +Cscope commands *cscope-commands* *:cscope* *:cs* *:scs* *:scscope* *E259* *E262* *E561* *E560* All cscope commands are accessed through suboptions to the cscope commands. @@ -232,7 +191,7 @@ through your tags file(s). ============================================================================== -3. Cscope options *cscope-options* +Cscope options *cscope-options* Use the |:set| command to set all cscope options. Ideally, you would do this in one of your startup files (e.g., vimrc). Some cscope related @@ -245,7 +204,6 @@ started will have no effect! :set csprg=/usr/local/bin/cscope < *cscopequickfix* *csqf* *E469* -{not available when compiled without the |+quickfix| feature} 'cscopequickfix' specifies whether to use quickfix window to show cscope results. This is a list of comma-separated values. Each item consists of |cscope-find| command (s, g, d, c, t, e, f, i or a) and flag (+, - or 0). @@ -260,81 +218,56 @@ seems to be useful: > If 'cscopetag' is set, the commands ":tag" and CTRL-] as well as "vim -t" will always use |:cstag| instead of the default :tag behavior. Effectively, by setting 'cst', you will always search your cscope databases as well as -your tag files. The default is off. Examples: > - :set cst - :set nocst -< +your tag files. The default is off. + *cscoperelative* *csre* If 'cscoperelative' is set, then in absence of a prefix given to cscope (prefix is the argument of -P option of cscope), basename of cscope.out location (usually the project root directory) will be used as the prefix to construct an absolute path. The default is off. Note: This option is only effective when cscope (cscopeprg) is initialized without a prefix -path (-P). Examples: > - :set csre - :set nocsre -< +path (-P). + *cscopetagorder* *csto* The value of 'csto' determines the order in which |:cstag| performs a search. If 'csto' is set to zero, cscope database(s) are searched first, followed by tag file(s) if cscope did not return any matches. If 'csto' is set to one, tag file(s) are searched before cscope database(s). The default is zero. -Examples: > - :set csto=0 - :set csto=1 -< - *cscopeverbose* *csverb* -If 'cscopeverbose' is not set (the default), messages will not be printed -indicating success or failure when adding a cscope database. Ideally, you -should reset this option in your |init.vim| before adding any cscope -databases, and after adding them, set it. From then on, when you add more -databases within Vim, you will get a (hopefully) useful message should the -database fail to be added. Examples: > - :set csverb - :set nocsverb -< + *cscopepathcomp* *cspc* -The value of 'cspc' determines how many components of a file's path to -display. With the default value of zero the entire path will be displayed. +'cscopepathcomp' determines how many components of a file's path to display. +With the default value of zero the entire path will be displayed. The value one will display only the filename with no path. Other values display that many components. For example: > - :set cspc=3 + :set cscopepathcomp=3 will display the last 3 components of the file's path, including the file name itself. ============================================================================== -4. How to use cscope in Vim *cscope-howtouse* +Using cscope in Nvim *cscope-usage* *cscope-howtouse* -The first thing you need to do is to build a cscope database for your -source files. For the most basic case, simply do "cscope -b". Please -refer to the cscope man page for more details. +To get started, build the cscope database in your project root directory: > + cscope -bcqR -Assuming you have a cscope database, you need to "add" the database to Vim. -This establishes a cscope "connection" and makes it available for Vim to use. -You can do this in your vimrc file, or you can do it manually after starting -vim. For example, to add the cscope database "cscope.out", you would do: +See the cscope manpage for details: > + :Man cscope - :cs add cscope.out +By default the cscope database file is named "cscope.out". After building the +database, connect to it from Nvim: > + :cscope add cscope.out -You can double-check the result of this by executing ":cs show". This will -produce output which looks like this: +That establishes a cscope connection for Nvim to use. You can check the +result with ":cs show". It will show something like: # pid database name prepend path 0 28806 cscope.out <none> -Note: -Because of the Microsoft RTL limitations, Win32 version shows 0 instead -of the real pid. - Once a cscope connection is established, you can make queries to cscope and -the results will be printed to you. Queries are made using the command -":cs find". For example: - +the results will be printed. Queries are made using the command ":cs find". +For example: > :cs find g ALIGN_SIZE -This can get a little cumbersome since one ends up doing a significant -amount of typing. Fortunately, there are ways around this by mapping -shortcut keys. See |cscope-suggestions| for suggested usage. +To make this easier you can configure mappings, see |cscope-suggestions|. If the results return only one match, you will automatically be taken to it. If there is more than one match, you will be given a selection screen to pick @@ -343,39 +276,28 @@ simply hit Ctrl-T to get back to the previous one. ============================================================================== -5. Limitations *cscope-limitations* - -Cscope support for Vim is only available on systems that support these four -system calls: fork(), pipe(), execl(), waitpid(). This means it is mostly -limited to Unix systems. - -Additionally Cscope support works for Win32. For more information and a -cscope version for Win32 see: - - http://iamphet.nm.ru/cscope/index.html +Limitations *cscope-limitations* Hard-coded limitation: doing a |:tjump| when |:cstag| searches the tag files is not configurable (e.g., you can't do a tselect instead). + ============================================================================== -6. Suggested usage *cscope-suggestions* +Sample config *cscope-suggestions* -Put these entries in your vimrc (adjust the pathname accordingly to your -setup): > +Copy this into your init.vim (adjust paths for your system): > if has("cscope") set csprg=/usr/local/bin/cscope set csto=0 set cst - set nocsverb " add any database in current directory if filereadable("cscope.out") - cs add cscope.out + silent cs add cscope.out " else add database pointed to by environment elseif $CSCOPE_DB != "" - cs add $CSCOPE_DB + silent cs add $CSCOPE_DB endif - set csverb endif By setting 'cscopetag', we have effectively replaced all instances of the :tag @@ -447,47 +369,6 @@ Cscope Home Page (http://cscope.sourceforge.net/): > \:vert scs find d <C-R>=expand("<cword>")<CR><CR> nmap <C-Space><C-Space>a \:vert scs find a <C-R>=expand("<cword>")<CR><CR> - -============================================================================== -7. Cscope availability and information *cscope-info* - -If you do not already have cscope (it did not come with your compiler -license or OS distribution), then you can download it for free from: - http://cscope.sourceforge.net/ -This is released by SCO under the BSD license. - -If you want a newer version of cscope, you will probably have to buy it. -According to the (old) nvi documentation: - - You can buy version 13.3 source with an unrestricted license - for $400 from AT&T Software Solutions by calling +1-800-462-8146. - -Also you can download cscope 13.x and mlcscope 14.x (multi-lingual cscope -which supports C, C++, Java, lex, yacc, breakpoint listing, Ingres, and SDL) -from World-Wide Exptools Open Source packages page: - http://www.bell-labs.com/project/wwexptools/packages.html - -In Solaris 2.x, if you have the C compiler license, you will also have -cscope. Both are usually located under /opt/SUNWspro/bin - -SGI developers can also get it. Search for Cscope on this page: - http://freeware.sgi.com/index-by-alpha.html - https://toolbox.sgi.com/toolbox/utilities/cscope/ -The second one is for those who have a password for the SGI toolbox. - -There is source to an older version of a cscope clone (called "cs") available -on the net. Due to various reasons, this is not supported with Vim. - -The cscope interface/support for Vim was originally written by -Andy Kahn <ackahn@netapp.com>. The original structure (as well as a tiny -bit of code) was adapted from the cscope interface in nvi. Please report -any problems, suggestions, patches, et al., you have for the usage of -cscope within Vim to him. - *cscope-win32* -For a cscope version for Win32 see: - http://code.google.com/p/cscope-win32/ - -Win32 support was added by Sergey Khorev <sergey.khorev@gmail.com>. Contact -him if you have Win32-specific issues. +< vim:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/indent.txt b/runtime/doc/indent.txt index cf45ec4f38..7ba5a373dc 100644 --- a/runtime/doc/indent.txt +++ b/runtime/doc/indent.txt @@ -321,6 +321,21 @@ The examples below assume a 'shiftwidth' of 4. void function(); void function(); } } < + *cino-E* + EN Indent inside C++ linkage specifications (extern "C" or + extern "C++") N characters extra compared to a normal block. + (default 0). + + cino= cino=E-s > + extern "C" { extern "C" { + void function(); void function(); + } } + + extern "C" extern "C" + { { + void function(); void function(); + } } +< *cino-p* pN Parameter declarations for K&R-style function declarations will be indented N characters from the margin. (default @@ -550,7 +565,7 @@ The examples below assume a 'shiftwidth' of 4. The defaults, spelled out in full, are: - cinoptions=>s,e0,n0,f0,{0,}0,^0,L-1,:s,=s,l0,b0,gs,hs,N0,ps,ts,is,+s, + 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 Vim puts a line in column 1 if: diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt index b6cc18ab1b..1feff0723b 100644 --- a/runtime/doc/insert.txt +++ b/runtime/doc/insert.txt @@ -40,10 +40,11 @@ char action ~ *i_CTRL-[* *i_<Esc>* <Esc> or CTRL-[ End insert or Replace mode, go back to Normal mode. Finish abbreviation. - Note: If your <Esc> key is hard to hit on your keyboard, train - yourself to use CTRL-[. - If Esc doesn't work and you are using a Mac, try CTRL-Esc. - Or disable Listening under Accessibility preferences. + 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. + For example <A-x> acts like <Esc>x if <A-x> does not have an + 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 @@ -1077,6 +1078,8 @@ items: item with the same word is already present. empty when non-zero this match will be added even when it is an empty string + user_data custom data which is associated with the item and + available in |v:completed_item| All of these except "icase", "dup" and "empty" must be a string. If an item does not meet these requirements then an error message is given and further @@ -1170,6 +1173,8 @@ The menu is used when: The 'pumheight' option can be used to set a maximum height. The default is to use all space available. +The 'pumwidth' option can be used to set a minimum width. The default is 15 +characters. There are three states: 1. A complete match has been inserted, e.g., after using CTRL-N or CTRL-P. diff --git a/runtime/doc/job_control.txt b/runtime/doc/job_control.txt index e57015db22..ed5f16902a 100644 --- a/runtime/doc/job_control.txt +++ b/runtime/doc/job_control.txt @@ -58,7 +58,7 @@ Description of what happens: - The first shell is idle, waiting to read commands from its stdin. - The second shell is started with -c which executes the command (a for-loop printing 0 through 9) and then exits. - - `JobHandler()` callback is passed to |jobstart()| to handle various job + - `OnEvent()` callback is passed to |jobstart()| to handle various job events. It displays stdout/stderr data received from the shells. For |on_stdout| and |on_stderr| see |channel-callback|. diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt index 9b61fa6527..038e90fab0 100644 --- a/runtime/doc/map.txt +++ b/runtime/doc/map.txt @@ -232,8 +232,10 @@ For this reason the following is blocked: - Editing another buffer. - The |:normal| command. - Moving the cursor is allowed, but it is restored afterwards. +- If the cmdline is changed, the old text and cursor position are restored. If you want the mapping to do any of these let the returned characters do -that. +that. Alternatively use a |<Cmd>| mapping which doesn't have these +restrictions. You can use getchar(), it consumes typeahead if there is any. E.g., if you have these mappings: > @@ -272,6 +274,29 @@ again for using <expr>. This does work: > Using 0x80 as a single byte before other text does not work, it will be seen as a special key. + *<Cmd>* *:map-command* +A command mapping is a mapping that directly executes a command. Command +mappings are written by placing a command in between <Cmd> and <CR> in the +rhs of a mapping (in any mode): > + noremap <f3> <Cmd>echo mode(1)<cr> +< + *E5520* +The command must be complete and ended with a <CR>. If the command is +incomplete, an error is raised. |Command-line| mode is never entered. + +This is more flexible than using `:<c-u>` in visual and operator pending +mode, or `<c-o>:` in insert mode, as the commands are exectued directly in the +mode, and not normal mode. Also visual mode is not aborted. Commands can be +invoked directly in cmdline mode, which is not simple otherwise (a timer has +to be used). Unlike <expr> mappings, there are not any specific restrictions +what the command can do, except for what is normally possible to do in every +specific mode. The command should be executed the same way as if an +(unrestricted) |autocmd| was invoked or an async event event was processed. + +Note: In select mode, |:map| or |:vmap| command mappings will be executed in +visual mode. If a mapping is intended to work in select mode, it is +recomendend to map it using |:smap|, possibly in addition to the same mapping +with |:map| or |:xmap|. 1.3 MAPPING AND MODES *:map-modes* *mapmode-nvo* *mapmode-n* *mapmode-v* *mapmode-o* *mapmode-t* diff --git a/runtime/doc/msgpack_rpc.txt b/runtime/doc/msgpack_rpc.txt index 2d8f5af6d2..01d4e10cea 100644 --- a/runtime/doc/msgpack_rpc.txt +++ b/runtime/doc/msgpack_rpc.txt @@ -70,9 +70,8 @@ An rpc socket is automatically created with each instance. The socket location is stored in |v:servername|. By default this is a named pipe with an automatically generated address. See |XXX|. -To make Nvim listen on a TCP/IP socket instead, set the - |$NVIM_LISTEN_ADDRESS| environment variable before starting Nvim: > - NVIM_LISTEN_ADDRESS=127.0.0.1:6666 nvim +To make Nvim listen on a TCP/IP socket instead, specify |--listen|: > + nvim --listen 127.0.0.1:6666 <Also, more sockets and named pipes can be listened on using |serverstart()|. Note that localhost TCP sockets are generally less secure than named pipes, @@ -242,4 +241,4 @@ Even for statically compiled clients it is good practice to avoid hardcoding the type codes, because a client may be built against one Nvim version but connect to another with different type codes. - + vim:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 4fe2e07909..0b7c61ea18 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1807,12 +1807,6 @@ A jump table for the options with a short description can be found at |Q_op|. Determines the order in which ":cstag" performs a search. See |cscopetagorder|. - *'cscopeverbose'* *'csverb'* - *'nocscopeverbose'* *'nocsverb'* -'cscopeverbose' 'csverb' boolean (default off) - global - Give messages when adding a cscope database. See |cscopeverbose|. - *'cursorbind'* *'crb'* *'nocursorbind'* *'nocrb'* 'cursorbind' 'crb' boolean (default off) local to window @@ -2042,6 +2036,11 @@ A jump table for the options with a short description can be found at |Q_op|. column of the last screen line. Overrules "lastline". uhex Show unprintable characters hexadecimal as <xx> instead of using ^C and ~C. + msgsep When showing messages longer than 'cmdheight' lines, + only scroll the message lines and not the entire + screen. This also shows a separator line filled with + chars determined by 'fillchars' option, and + highlighted with the |MsgSeparator| group. When neither "lastline" nor "truncate" is included, a last line that doesn't fit is replaced with "@" lines. @@ -2383,7 +2382,7 @@ A jump table for the options with a short description can be found at |Q_op|. Only normal file name characters can be used, "/\*?[|<>" are illegal. *'fillchars'* *'fcs'* -'fillchars' 'fcs' string (default "vert:|,fold:-") +'fillchars' 'fcs' string (default "") global {not available when compiled without the |+windows| and |+folding| features} @@ -2393,16 +2392,20 @@ A jump table for the options with a short description can be found at |Q_op|. item default Used for ~ stl:c ' ' or '^' statusline of the current window stlnc:c ' ' or '=' statusline of the non-current windows - vert:c '|' vertical separators |:vsplit| - fold:c '-' filling 'foldtext' + vert:c '│' or '|' vertical separators |:vsplit| + fold:c '·' or '-' filling 'foldtext' diff:c '-' deleted lines of the 'diff' option + msgsep:c ' ' message separator 'display' Any one that is omitted will fall back to the default. For "stl" and "stlnc" the space will be used when there is highlighting, '^' or '=' otherwise. + If 'ambiwidth' is "double" then "vert" and "fold" default to + single-byte alternatives. + Example: > - :set fillchars=stl:^,stlnc:=,vert:\|,fold:-,diff:- + :set fillchars=stl:^,stlnc:=,vert:│,fold:·,diff:- < This is similar to the default, except that these characters will also be used when there is highlighting. @@ -2712,6 +2715,10 @@ A jump table for the options with a short description can be found at |Q_op|. :s///g subst. one subst. all :s///gg subst. all subst. one + DEPRECATED: Setting this option may break plugins that are not aware + of this option. Also, many users get confused that adding the /g flag + has the opposite effect of that it normally does. + *'grepformat'* *'gfm'* 'grepformat' 'gfm' string (default "%f:%l:%m,%f:%l%m,%f %l%m") global @@ -3301,7 +3308,17 @@ A jump table for the options with a short description can be found at |Q_op|. pattern and/or a lot of text the match may not be found. This is to avoid that Vim hangs while you are typing the pattern. The |hl-IncSearch| highlight group determines the highlighting. - See also: 'hlsearch'. + When 'hlsearch' is on, all matched strings are highlighted too while typing + a search command. See also: 'hlsearch'. + If you don't want turn 'hlsearch' on, but want to highlight all matches + while searching, you can turn on and off 'hlsearch' with autocmd. + Example: > + augroup vimrc-incsearch-highlight + autocmd! + autocmd CmdlineEnter /,\? :set hlsearch + autocmd CmdlineLeave /,\? :set nohlsearch + augroup END +< CTRL-L can be used to add one character from after the current match to the command line. If 'ignorecase' and 'smartcase' are set and the command line has no uppercase characters, the added character is @@ -3672,7 +3689,7 @@ A jump table for the options with a short description can be found at |Q_op|. < Minimum value is 2, maximum value is 1000. *'linespace'* *'lsp'* -'linespace' 'lsp' number (default 0, 1 for Win32 GUI) +'linespace' 'lsp' number (default 0) global {only in the GUI} Number of pixel lines inserted between characters. Useful if the font @@ -3809,6 +3826,23 @@ 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. + *'makeencoding'* *'menc'* +'makeencoding' 'menc' string (default "") + global or local to buffer |global-local| + {only available when compiled with the |+multi_byte| + feature} + {not in Vi} + Encoding used for reading the output of external commands. When empty, + encoding is not converted. + This is used for `:make`, `:lmake`, `:grep`, `:lgrep`, `:grepadd`, + `:lgrepadd`, `:cfile`, `:cgetfile`, `:caddfile`, `:lfile`, `:lgetfile`, + and `:laddfile`. + + This would be mostly useful when you use MS-Windows. If |+iconv| is + enabled and GNU libiconv is used, setting 'makeencoding' to "char" has + the same effect as setting to the system locale encoding. Example: > + :set makeencoding=char " system locale is used +< *'makeprg'* *'mp'* 'makeprg' 'mp' string (default "make") global or local to buffer |global-local| @@ -5132,10 +5166,10 @@ A jump table for the options with a short description can be found at |Q_op|. security reasons. *'shellcmdflag'* *'shcf'* -'shellcmdflag' 'shcf' string (default: "-c"; Windows: "/c") +'shellcmdflag' 'shcf' string (default: "-c"; Windows: "/s /c") global Flag passed to the shell to execute "!" and ":!" commands; e.g., - "bash.exe -c ls" or "cmd.exe /c dir". For Windows + `bash.exe -c ls` or `cmd.exe /s /c "dir"`. For Windows systems, the default is set according to the value of 'shell', to reduce the need to set this option by the user. On Unix it can have more than one flag. Each white space separated @@ -5256,7 +5290,7 @@ A jump table for the options with a short description can be found at |Q_op|. to execute most external commands with cmd.exe. *'shellxquote'* *'sxq'* -'shellxquote' 'sxq' string (default: "") +'shellxquote' 'sxq' string (default: "", Windows: "\"") global Quoting character(s), put around the command passed to the shell, for the "!" and ":!" commands. Includes the redirection. See @@ -5420,14 +5454,13 @@ A jump table for the options with a short description can be found at |Q_op|. See |tab-page| for more information about tab pages. *'sidescroll'* *'ss'* -'sidescroll' 'ss' number (default 0) +'sidescroll' 'ss' number (default 1) global The minimal number of columns to scroll horizontally. Used only when the 'wrap' option is off and the cursor is moved off of the screen. When it is zero the cursor will be put in the middle of the screen. - When using a slow terminal set it to a large number or 0. When using - a fast terminal use a small number or 1. Not used for "zh" and "zl" - commands. + When using a slow terminal set it to a large number or 0. Not used + for "zh" and "zl" commands. *'sidescrolloff'* *'siso'* 'sidescrolloff' 'siso' number (default 0) @@ -6192,7 +6225,6 @@ A jump table for the options with a short description can be found at |Q_op|. When on, uses |highlight-guifg| and |highlight-guibg| attributes in the terminal (thus using 24-bit color). Requires a ISO-8613-3 compatible terminal. - Must be set at startup (in your |init.vim| or |--cmd|). *'terse'* *'noterse'* 'terse' boolean (default off) @@ -6258,15 +6290,14 @@ A jump table for the options with a short description can be found at |Q_op|. *'timeoutlen'* *'tm'* 'timeoutlen' 'tm' number (default 1000) global - The time in milliseconds that is waited for a mapped sequence to - complete. + Time in milliseconds to wait for a mapped sequence to complete. *'ttimeoutlen'* *'ttm'* 'ttimeoutlen' 'ttm' number (default 50) global - The time in milliseconds that is waited for a key code - sequence to complete. Also used for CTRL-\ CTRL-N and CTRL-\ CTRL-G - when part of a command has been typed. + Time in milliseconds to wait for a key code sequence to complete. Also + used for CTRL-\ CTRL-N and CTRL-\ CTRL-G when part of a command has + been typed. *'title'* *'notitle'* 'title' boolean (default off) @@ -6424,6 +6455,7 @@ A jump table for the options with a short description can be found at |Q_op|. Currently, these messages are given: >= 1 When the shada file is read or written. >= 2 When a file is ":source"'ed. + >= 3 UI info, terminal capabilities >= 5 Every searched tags file and include file. >= 8 Files for which a group of autocommands is executed. >= 9 Every executed autocommand. @@ -6895,7 +6927,8 @@ A jump table for the options with a short description can be found at |Q_op|. 'writedelay' 'wd' number (default 0) global The number of milliseconds to wait for each character sent to the - screen. When non-zero, characters are sent to the terminal one by - one. For debugging purposes. + screen. When positive, characters are sent to the UI one by one. + When negative, all redrawn characters cause a delay, even if the + character already was displayed by the UI. For debugging purposes. vim:tw=78:ts=8:ft=help:noet:norl: diff --git a/runtime/doc/provider.txt b/runtime/doc/provider.txt index b806b08f95..4a76f1399e 100644 --- a/runtime/doc/provider.txt +++ b/runtime/doc/provider.txt @@ -33,20 +33,20 @@ To use Vim Python 2/3 plugins with Nvim: - For Python 2 plugins, make sure an interpreter for Python 2.6 or 2.7 is available in your `$PATH`, then install the `neovim` Python package systemwide: > - $ sudo pip2 install --upgrade neovim + sudo pip2 install --upgrade neovim < or for the current user: > - $ pip2 install --user --upgrade neovim + pip2 install --user --upgrade neovim < - For Python 3 plugins, make sure an interpreter for Python 3.3 or above is available in your `$PATH`, then install the `neovim` Python package systemwide: > - $ sudo pip3 install --upgrade neovim + sudo pip3 install --upgrade neovim < or for the current user: > - $ pip3 install --user --upgrade neovim + pip3 install --user --upgrade neovim < Note: The `--upgrade` flag ensures you have the latest version even if - a previous version was already installed. +a previous version was already installed. PYTHON PROVIDER CONFIGURATION ~ *g:python_host_prog* @@ -93,7 +93,7 @@ Run |:checkhealth| to see if your system is up-to-date. RUBY QUICKSTART ~ To use Vim Ruby plugins with Nvim, just install the latest `neovim` RubyGem: > - $ gem install neovim + gem install neovim RUBY PROVIDER CONFIGURATION ~ *g:loaded_ruby_provider* @@ -112,7 +112,30 @@ To use an absolute path (e.g. to an rbenv installation): > To use the RVM "system" Ruby installation: > let g:ruby_host_prog = 'rvm system do neovim-ruby-host' < +============================================================================== +Node.js integration *provider-nodejs* + +Nvim supports Node.js |remote-plugin|s. +https://github.com/neovim/node-client/ +https://nodejs.org/ +NODEJS QUICKSTART~ + +To use javascript remote-plugins with Nvim, install the `neovim` npm package: > + npm install -g neovim +< +NODEJS PROVIDER CONFIGURATION~ + *g:loaded_node_provider* +To disable Node support: > + :let g:loaded_node_provider = 1 +< + *g:node_host_prog* +Command to start the Node host. Setting this makes startup faster. + +By default, Nvim searches for `neovim-node-host` using "npm root -g", which +can be slow. To avoid this, set g:node_host_prog to an absolute path: > + let g:node_host_prog = '/usr/local/bin/neovim-node-host' +< ============================================================================== Clipboard integration *provider-clipboard* *clipboard* diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt index da167c0f5b..f280286290 100644 --- a/runtime/doc/quickfix.txt +++ b/runtime/doc/quickfix.txt @@ -165,6 +165,9 @@ processing a quickfix or location list command, it will be aborted. keep Vim running while compiling. If you give the name of the errorfile, the 'errorfile' option will be set to [errorfile]. See |:cc| for [!]. + If the encoding of the error file differs from the + 'encoding' option, you can use the 'makeencoding' + option to specify the encoding. *:lf* *:lfile* :lf[ile][!] [errorfile] Same as ":cfile", except the location list for the @@ -176,6 +179,9 @@ processing a quickfix or location list command, it will be aborted. :cg[etfile] [errorfile] *:cg* *:cgetfile* Read the error file. Just like ":cfile" but don't jump to the first error. + If the encoding of the error file differs from the + 'encoding' option, you can use the 'makeencoding' + option to specify the encoding. :lg[etfile] [errorfile] *:lg* *:lgetfile* @@ -186,6 +192,9 @@ processing a quickfix or location list command, it will be aborted. :caddf[ile] [errorfile] Read the error file and add the errors from the errorfile to the current quickfix list. If a quickfix list is not present, then a new list is created. + If the encoding of the error file differs from the + 'encoding' option, you can use the 'makeencoding' + option to specify the encoding. *:laddf* *:laddfile* :laddf[ile] [errorfile] Same as ":caddfile", except the location list for the @@ -322,6 +331,7 @@ use this code: > endfunction au QuickfixCmdPost make call QfMakeConv() +Another option is using 'makeencoding'. EXECUTE A COMMAND IN ALL THE BUFFERS IN QUICKFIX OR LOCATION LIST: *:cdo* @@ -460,7 +470,11 @@ keep its height, ignoring 'winheight' and 'equalalways'. You can change the height manually (e.g., by dragging the status line above it with the mouse). In the quickfix window, each line is one error. The line number is equal to -the error number. You can use ":.cc" to jump to the error under the cursor. +the error number. The current entry is highlighted with the QuickFixLine +highlighting. You can change it to your liking, e.g.: > + :hi QuickFixLine ctermbg=Yellow guibg=Yellow + +You can use ":.cc" to jump to the error under the cursor. Hitting the <Enter> key or double-clicking the mouse on a line has the same effect. The file containing the error is opened in the window above the quickfix window. If there already is a window for that file, it is used @@ -586,6 +600,9 @@ lists, use ":cnewer 99" first. like |:cnext| and |:cprevious|, see above. This command does not accept a comment, any " characters are considered part of the arguments. + If the encoding of the program output differs from the + 'encoding' option, you can use the 'makeencoding' + option to specify the encoding. *:lmak* *:lmake* :lmak[e][!] [arguments] @@ -645,6 +662,7 @@ read the error messages: > au QuickfixCmdPost make call QfMakeConv() (Example by Faque Cheng) +Another option is using 'makeencoding'. ============================================================================== 5. Using :vimgrep and :grep *grep* *lid* @@ -759,6 +777,9 @@ id-utils) in a similar way to its compiler integration (see |:make| above). When 'grepprg' is "internal" this works like |:vimgrep|. Note that the pattern needs to be enclosed in separator characters then. + If the encoding of the program output differs from the + 'encoding' option, you can use the 'makeencoding' + option to specify the encoding. *:lgr* *:lgrep* :lgr[ep][!] [arguments] Same as ":grep", except the location list for the @@ -783,6 +804,10 @@ id-utils) in a similar way to its compiler integration (see |:make| above). \ | catch /E480:/ \ | endtry" < + If the encoding of the program output differs from the + 'encoding' option, you can use the 'makeencoding' + option to specify the encoding. + *:lgrepa* *:lgrepadd* :lgrepa[dd][!] [arguments] Same as ":grepadd", except the location list for the diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt index b22d2afa7e..a0ca17cc4a 100644 --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -655,7 +655,6 @@ Short explanation of each option: *option-list* 'cscoperelative' 'csre' Use cscope.out path basename as prefix 'cscopetag' 'cst' use cscope for tag commands 'cscopetagorder' 'csto' determines ":cstag" search order -'cscopeverbose' 'csverb' give messages when adding a cscope database 'cursorbind' 'crb' move cursor in window as it moves in other windows 'cursorcolumn' 'cuc' highlight the screen column of the cursor 'cursorline' 'cul' highlight the screen line of the cursor @@ -762,6 +761,7 @@ Short explanation of each option: *option-list* 'loadplugins' 'lpl' load plugin scripts when starting up 'magic' changes special characters in search patterns 'makeef' 'mef' name of the errorfile for ":make" +'makeencoding' 'menc' encoding of external make/grep commands 'makeprg' 'mp' program to use for the ":make" command 'matchpairs' 'mps' pairs of characters that "%" can match 'matchtime' 'mat' tenths of a second to show matching paren diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index 9b33926d04..21c47edc24 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -249,14 +249,14 @@ argument. for reading or writing a ShaDa file. Can be used to find out what is happening upon startup and exit. Example: > - vim -V8 foobar + nvim -V8 -V[N]{filename} - Like -V and set 'verbosefile' to {filename}. The result is - that messages are not displayed but written to the file - {filename}. {filename} must not start with a digit. + Like -V and set 'verbosefile' to {filename}. Messages are not + displayed; instead they are written to the file {filename}. + {filename} must not start with a digit. Example: > - vim -V20vimlog foobar + nvim -V20vimlog < *-D* -D Debugging. Go to debugging mode when executing the first @@ -355,6 +355,10 @@ argument. instead. See also |silent-mode|, which does start a (limited) UI. +--listen {addr} *--listen* + Start |RPC| server on pipe or TCP address {addr}. Sets the + primary listen address |v:servername| to {addr}. |serverstart()| + ============================================================================== 2. Initialization *initialization* *startup* @@ -439,15 +443,14 @@ accordingly. Vim proceeds in this order: :runtime! filetype.vim :runtime! ftplugin.vim :runtime! indent.vim -< This step is skipped if ":filetype ..." was called before now or if - the "-u NONE" command line argument was given. +< Skipped if ":filetype … off" was called or if the "-u NONE" command + line argument was given. 5. Enable syntax highlighting. This does the same as the command: > :runtime! syntax/syntax.vim -< Note: This enables filetype detection even if ":filetype off" was - called before now. - This step is skipped if the "-u NONE" command line argument was given. +< Skipped if ":syntax off" was called or if the "-u NONE" command + line argument was given. 6. Load the plugin scripts. *load-plugins* This does the same as the command: > @@ -1392,4 +1395,4 @@ RPC clients for debugging. $NVIM_LOG_FILE contains the log file path: > Usually the file is ~/.local/share/nvim/log unless that path is inaccessible or if $NVIM_LOG_FILE was set before |startup|. - vim:tw=78:ts=8:ft=help:norl: + vim:noet:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index 85330f3dec..fa66d9d071 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -4709,17 +4709,7 @@ ctermbg={color-nr} *highlight-ctermbg* "cterm". For example, on some systems "cterm=bold ctermfg=3" gives another color, on others you just get color 3. - For an xterm this depends on your resources, and is a bit - unpredictable. See your xterm documentation for the defaults. The - colors for a color-xterm can be changed from the .Xdefaults file. - Unfortunately this means that it's not possible to get the same colors - for each user. - - The MSDOS standard colors are fixed (in a console window), so these - have been used for the names. But the meaning of color names in X11 - are fixed, so these color settings have been used, to make the - highlighting settings portable (complicated, isn't it?). The - following names are recognized, with the color number used: + The following names are recognized, with the color number used: *cterm-colors* NR-16 NR-8 COLOR NAME ~ @@ -4919,6 +4909,8 @@ MatchParen The character under the cursor or just before it, if it *hl-ModeMsg* ModeMsg 'showmode' message (e.g., "-- INSERT --") + *hl-MsgSeparator* +MsgSeparator Separator for scrolled messages, `msgsep` flag of 'display' *hl-MoreMsg* MoreMsg |more-prompt| *hl-NonText* diff --git a/runtime/doc/term.txt b/runtime/doc/term.txt index a694185fc9..075ffdac52 100644 --- a/runtime/doc/term.txt +++ b/runtime/doc/term.txt @@ -38,13 +38,13 @@ Otherwise Nvim cannot know what sequences your terminal expects, and weird or sub-optimal behavior will result (scrolling quirks, wrong colors, etc.). $TERM is also important because it is mirrored by SSH to the remote session, -unlike other common client-end environment variables ($COLORTERM, -$XTERM_VERSION, $VTE_VERSION, $KONSOLE_PROFILE_NAME, $TERM_PROGRAM, ...). +unlike most other environment variables. For this terminal Set $TERM to |builtin-terms| ------------------------------------------------------------------------- iTerm (original) iterm, iTerm.app N iTerm2 (new capabilities) iterm2, iTerm2.app Y + Konsole konsole-256color N anything libvte-based vte, vte-256color Y (e.g. GNOME Terminal) (aliases: gnome, gnome-256color) tmux tmux, tmux-256color Y @@ -232,11 +232,6 @@ correct values. See |:mode|. Slow and fast terminals *slow-fast-terminal* *slow-terminal* -If you have a fast terminal you may like to set the 'ruler' option. The -cursor position is shown in the status line. If you are using horizontal -scrolling ('wrap' option off) consider setting 'sidescroll' to a small -number. - If you have a slow terminal you may want to reset the 'showcmd' and 'ruler' options. The command characters and cursor positions will not be shown in the status line (which involves a lot of cursor motions and attribute changes for diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index 685019aed7..6f6df2d7ae 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -9,7 +9,7 @@ Nvim UI protocol *ui* Type |gO| to see the table of contents. ============================================================================== -Introduction *ui-intro* +UI Events *ui-events* GUIs can be implemented as external processes communicating with Nvim over the RPC API. The UI model consists of a terminal-like grid with a single, @@ -30,10 +30,22 @@ a dictionary with these (optional) keys: `ext_cmdline` Externalize the cmdline. |ui-cmdline| `ext_wildmenu` Externalize the wildmenu. |ui-ext-wildmenu| -Nvim will then send msgpack-rpc notifications, with the method name "redraw" -and a single argument, an array of screen update events. -Update events are tuples whose first element is the event name and remaining -elements the event parameters. +Specifying a non-existent option is an error. To facilitate an ui that +supports different versions of Nvim, the |api-metadata| key `ui_options` +contains the list of supported options. Additionally Nvim currently requires +that all connected UIs use the same set of widgets. Therefore the active +widgets will be the intersection of the requested widget sets of all connected +UIs. The "option_set" event will be used to specify which widgets actually are +active. + +After attaching, Nvim will send msgpack-rpc notifications, with the method +name "redraw" and a single argument, an array of screen update events. Update +events are arrays whose first element is the event name and remaining elements +are each tuples of event parameters. This allows multiple events of the same +kind to be sent in a row without the event name being repeated. This batching +is mostly used for "put", as each "put" event just puts contents in one screen +cell, but clients must be prepared for multiple argument sets being batched +for all event kinds. Events must be handled in order. The user should only see the updated screen state after all events in the same "redraw" batch are processed (not any @@ -80,6 +92,29 @@ Global Events *ui-global* Some keys are missing in some modes. +["option_set", name, value] + The value of ui related option `name` changed. The sent options are + listed below: + + 'arabicshape' + 'ambiwith' + 'emoji' + 'guifont' + 'guifontset' + 'guifontwide' + 'linespace' + 'showtabline' + 'termguicolors' + `ext_*` (all |ui-ext-options|) + + Options are not added to the list if their effects are already taken + care of. For instance, instead of forwarding the raw 'mouse' option + value, `mouse_on` and `mouse_off` directly indicate if mouse support + is active right now. Some options like 'ambiwith' have already taken + effect on the grid, where appropriate empty cells are added, however + an ui might still use these options when rendering raw text sent from + Nvim, like the text of the cmdline when |ui-ext-cmdline| is set. + ["mode_change", mode, mode_idx] The mode changed. The first parameter `mode` is a string representing the current mode. `mode_idx` is an index into the array received in diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt index baac72c106..9232cd70c5 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -207,21 +207,18 @@ g8 Print the hex values of the bytes used in the :sh[ell] Removed. |vim-differences| {Nvim} *:terminal* *:te* -:te[rminal][!] [{cmd}] Execute {cmd} with 'shell' in a new |terminal| buffer. - Equivalent to: > - :enew - :call termopen('{cmd}') -< - See |termopen()|. +:te[rminal][!] [{cmd}] Execute {cmd} with 'shell' in a new |terminal-emulator| + buffer. Without {cmd}, start an interactive 'shell'. - Without {cmd}, start an interactive shell. + Type |i| to enter |Terminal-mode|, then keys are sent to + the job running in the terminal. Type <C-\><C-N> to + leave Terminal-mode. |CTRL-\_CTRL-N| - Creating the terminal buffer fails when changes have been - made to the current buffer, unless 'hidden' is set. + Fails if changes have been made to the current buffer, + unless 'hidden' is set. To enter |Terminal-mode| automatically: > - autocmd BufEnter term://* startinsert - autocmd BufLeave term://* stopinsert + autocmd TermOpen * startinsert < *:!cmd* *:!* *E34* :!{cmd} Execute {cmd} with 'shell'. See also |:terminal|. diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index c8155f7a68..3575a420b7 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -32,8 +32,10 @@ a complete and centralized reference of those differences. - 'backupdir' defaults to .,~/.local/share/nvim/backup (|xdg|) - 'belloff' defaults to "all" - 'complete' doesn't include "i" +- 'cscopeverbose' is enabled - 'directory' defaults to ~/.local/share/nvim/swap// (|xdg|), auto-created -- 'display' defaults to "lastline" +- 'display' defaults to "lastline,msgsep" +- 'fillchars' defaults (in effect) to "vert:│,fold:·" - 'formatoptions' defaults to "tcqj" - 'history' defaults to 10000 (the maximum) - 'hlsearch' is set by default @@ -47,6 +49,7 @@ a complete and centralized reference of those differences. - 'ruler' is set by default - 'sessionoptions' doesn't include "options" - 'showcmd' is set by default +- 'sidescroll' defaults to 1 - 'smarttab' is set by default - 'tabpagemax' defaults to 50 - 'tags' defaults to "./tags;,tags" @@ -70,6 +73,7 @@ Providers Ruby plugins |provider-ruby| Shared data |shada| Embedded terminal |terminal| +VimL parser |nvim_parse_expression()| XDG base directories |xdg| USER EXPERIENCE ~ @@ -126,7 +130,9 @@ Some `CTRL-SHIFT-...` key chords are distinguished from `CTRL-...` variants Options: 'cpoptions' flags: |cpo-_| + 'display' flag `msgsep` to minimize scrolling when showing messages 'guicursor' works in the terminal + 'fillchars' flag `msgsep` (see 'display' above) 'inccommand' shows interactive results for |:substitute|-like commands 'scrollback' 'statusline' supports unlimited alignment sections @@ -160,19 +166,23 @@ Events: Highlight groups: |hl-NormalNC| highlights non-current windows + |hl-MsgSeparator| highlights separator for scrolled messages |hl-QuickFixLine| |hl-Substitute| |hl-TermCursor| |hl-TermCursorNC| |hl-Whitespace| highlights 'listchars' whitespace - -UI: - *E5408* *E5409* *g:Nvim_color_expr* *g:Nvim_color_cmdline* - Command-line coloring is supported. Only |input()| and |inputdialog()| may - be colored. For testing purposes expressions (e.g. |i_CTRL-R_=|) and regular - command-line (|:|) are colored by callbacks defined in `g:Nvim_color_expr` - and `g:Nvim_color_cmdline` respectively (these callbacks are for testing - only, and will be removed in a future version). + |expr-highlight| highlight groups (prefixed with "Nvim") + +Command-line highlighting: + The expression prompt (|@=|, |c_CTRL-R_=|, |i_CTRL-R_=|) is highlighted + using a built-in VimL expression parser. |expr-highlight| + *E5408* *E5409* + |input()|, |inputdialog()| support custom highlighting. |input()-highlight| + *g:Nvim_color_cmdline* + (Experimental) Command-line (|:|) is colored by callback defined in + `g:Nvim_color_cmdline` (this callback is for testing only, and will be + removed in the future). ============================================================================== 4. Changed features *nvim-features-changed* @@ -299,7 +309,8 @@ Highlight groups: |hl-ColorColumn|, |hl-CursorColumn| are lower priority than most other groups -The variable name "count" is no fallback for |v:count| anymore. +VimL (Vim script) compatibility: + `count` does not alias to |v:count| ============================================================================== 5. Missing legacy features *nvim-features-missing* @@ -327,22 +338,26 @@ Ed-compatible mode: ":set noedcompatible" is ignored ":set edcompatible" is an error - *t_xx* *:set-termcap* *termcap-options* *t_AB* *t_Sb* *t_vb* *t_SI* + *t_xx* *termcap-options* *t_AB* *t_Sb* *t_vb* *t_SI* Nvim does not have special `t_XX` options nor <t_XX> keycodes to configure terminal capabilities. Instead Nvim treats the terminal as any other UI. For example, 'guicursor' sets the terminal cursor style if possible. - *'term'* *E529* *E530* *E531* + *:set-termcap* +Start Nvim with 'verbose' level 3 to see the terminal capabilities. > + nvim -V3 +< + *'term'* *E529* *E530* *E531* 'term' reflects the terminal type derived from |$TERM| and other environment checks. For debugging only; not reliable during startup. > :echo &term "builtin_x" means one of the |builtin-terms| was chosen, because the expected terminfo file was not found on the system. - *termcap* + *termcap* Nvim never uses the termcap database, only |terminfo| and |builtin-terms|. - *xterm-8bit* *xterm-8-bit* + *xterm-8bit* *xterm-8-bit* Xterm can be run in a mode where it uses true 8-bit CSI. Supporting this requires autodetection of whether the terminal is in UTF-8 mode or non-UTF-8 mode, as the 8-bit CSI character has to be written differently in each case. @@ -365,7 +380,6 @@ MS-DOS support: Test functions: test_alloc_fail() test_autochdir() - test_disable_char_avail() test_garbagecollect_now() test_null_channel() test_null_dict() @@ -373,6 +387,7 @@ Test functions: test_null_list() test_null_partial() test_null_string() + test_override() test_settime() Other options: diff --git a/runtime/filetype.vim b/runtime/filetype.vim index 76f76af345..f8a29fa2b3 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -1,7 +1,7 @@ " Vim support file to detect file types " " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2017 Nov 02 +" Last Change: 2018 Feb 14 " Listen very carefully, I will say this only once if exists("did_load_filetypes") @@ -48,28 +48,11 @@ func! s:StarSetf(ft) endif endfunc +" Vim help file +au BufNewFile,BufRead $VIMRUNTIME/doc/*.txt setf help + " Abaqus or Trasys -au BufNewFile,BufRead *.inp call s:Check_inp() - -func! s:Check_inp() - if getline(1) =~ '^\*' - setf abaqus - else - let n = 1 - if line("$") > 500 - let nmax = 500 - else - let nmax = line("$") - endif - while n <= nmax - if getline(n) =~? "^header surface data" - setf trasys - break - endif - let n = n + 1 - endwhile - endif -endfunc +au BufNewFile,BufRead *.inp call dist#ft#Check_inp() " A-A-P recipe au BufNewFile,BufRead *.aap setf aap @@ -167,44 +150,7 @@ au BufNewFile,BufRead */boot/grub/menu.lst,*/boot/grub/grub.conf,*/etc/grub.conf " Assembly (all kinds) " *.lst is not pure assembly, it has two extra columns (address, byte codes) -au BufNewFile,BufRead *.asm,*.[sS],*.[aA],*.mac,*.lst call s:FTasm() - -" This function checks for the kind of assembly that is wanted by the user, or -" can be detected from the first five lines of the file. -func! s:FTasm() - " make sure b:asmsyntax exists - if !exists("b:asmsyntax") - let b:asmsyntax = "" - endif - - if b:asmsyntax == "" - call s:FTasmsyntax() - endif - - " if b:asmsyntax still isn't set, default to asmsyntax or GNU - if b:asmsyntax == "" - if exists("g:asmsyntax") - let b:asmsyntax = g:asmsyntax - else - let b:asmsyntax = "asm" - endif - endif - - exe "setf " . fnameescape(b:asmsyntax) -endfunc - -func! s:FTasmsyntax() - " see if file contains any asmsyntax=foo overrides. If so, change - " b:asmsyntax appropriately - let head = " ".getline(1)." ".getline(2)." ".getline(3)." ".getline(4). - \" ".getline(5)." " - let match = matchstr(head, '\sasmsyntax=\zs[a-zA-Z0-9]\+\ze\s') - if match != '' - let b:asmsyntax = match - elseif ((head =~? '\.title') || (head =~? '\.ident') || (head =~? '\.macro') || (head =~? '\.subtitle') || (head =~? '\.library')) - let b:asmsyntax = "vmasm" - endif -endfunc +au BufNewFile,BufRead *.asm,*.[sS],*.[aA],*.mac,*.lst call dist#ft#FTasm() " Macro (VAX) au BufNewFile,BufRead *.mar setf vmasm @@ -234,17 +180,7 @@ au BufNewFile,BufRead *.awk setf awk au BufNewFile,BufRead *.mch,*.ref,*.imp setf b " BASIC or Visual Basic -au BufNewFile,BufRead *.bas call s:FTVB("basic") - -" Check if one of the first five lines contains "VB_Name". In that case it is -" probably a Visual Basic file. Otherwise it's assumed to be "alt" filetype. -func! s:FTVB(alt) - if getline(1).getline(2).getline(3).getline(4).getline(5) =~? 'VB_Name\|Begin VB\.\(Form\|MDIForm\|UserControl\)' - setf vb - else - exe "setf " . a:alt - endif -endfunc +au BufNewFile,BufRead *.bas call dist#ft#FTVB("basic") " Visual Basic Script (close to Visual Basic) or Visual Basic .NET au BufNewFile,BufRead *.vb,*.vbs,*.dsm,*.ctl setf vb @@ -262,14 +198,7 @@ au BufNewFile,BufRead *.cmd \ if getline(1) =~ '^/\*' | setf rexx | else | setf dosbatch | endif " Batch file for 4DOS -au BufNewFile,BufRead *.btm call s:FTbtm() -func! s:FTbtm() - if exists("g:dosbatch_syntax_for_btm") && g:dosbatch_syntax_for_btm - setf dosbatch - else - setf btm - endif -endfunc +au BufNewFile,BufRead *.btm call dist#ft#FTbtm() " BC calculator au BufNewFile,BufRead *.bc setf bc @@ -289,15 +218,7 @@ au BufNewFile,BufRead named*.conf,rndc*.conf,rndc*.key setf named " BIND zone au BufNewFile,BufRead named.root setf bindzone -au BufNewFile,BufRead *.db call s:BindzoneCheck('') - -func! s:BindzoneCheck(default) - if getline(1).getline(2).getline(3).getline(4) =~ '^; <<>> DiG [0-9.]\+.* <<>>\|$ORIGIN\|$TTL\|IN\s\+SOA' - setf bindzone - elseif a:default != '' - exe 'setf ' . a:default - endif -endfunc +au BufNewFile,BufRead *.db call dist#ft#BindzoneCheck('') " Blank au BufNewFile,BufRead *.bl setf blank @@ -306,27 +227,14 @@ au BufNewFile,BufRead *.bl setf blank au BufNewFile,BufRead */etc/blkid.tab,*/etc/blkid.tab.old setf xml " Bazel (http://bazel.io) -autocmd BufRead,BufNewFile *.bzl,WORKSPACE setfiletype bzl +autocmd BufRead,BufNewFile *.bzl,WORKSPACE setf bzl if has("fname_case") - autocmd BufRead,BufNewFile BUILD setfiletype bzl + " There is another check for BUILD further below. + autocmd BufRead,BufNewFile BUILD setf bzl endif " C or lpc -au BufNewFile,BufRead *.c call s:FTlpc() - -func! s:FTlpc() - if exists("g:lpc_syntax_for_c") - let lnum = 1 - while lnum <= 12 - if getline(lnum) =~# '^\(//\|inherit\|private\|protected\|nosave\|string\|object\|mapping\|mixed\)' - setf lpc - return - endif - let lnum = lnum + 1 - endwhile - endif - setf c -endfunc +au BufNewFile,BufRead *.c call dist#ft#FTlpc() " Calendar au BufNewFile,BufRead calendar setf calendar @@ -380,23 +288,7 @@ endif " .h files can be C, Ch C++, ObjC or ObjC++. " Set c_syntax_for_h if you want C, ch_syntax_for_h if you want Ch. ObjC is " detected automatically. -au BufNewFile,BufRead *.h call s:FTheader() - -func! s:FTheader() - if match(getline(1, min([line("$"), 200])), '^@\(interface\|end\|class\)') > -1 - if exists("g:c_syntax_for_h") - setf objc - else - setf objcpp - endif - elseif exists("g:c_syntax_for_h") - setf c - elseif exists("g:ch_syntax_for_h") - setf ch - else - setf cpp - endif -endfunc +au BufNewFile,BufRead *.h call dist#ft#FTheader() " Ch (CHscript) au BufNewFile,BufRead *.chf setf ch @@ -430,36 +322,7 @@ au BufNewFile,BufRead NEWS au BufNewFile,BufRead *..ch setf chill " Changes for WEB and CWEB or CHILL -au BufNewFile,BufRead *.ch call s:FTchange() - -" This function checks if one of the first ten lines start with a '@'. In -" that case it is probably a change file. -" If the first line starts with # or ! it's probably a ch file. -" If a line has "main", "include", "//" ir "/*" it's probably ch. -" Otherwise CHILL is assumed. -func! s:FTchange() - let lnum = 1 - while lnum <= 10 - if getline(lnum)[0] == '@' - setf change - return - endif - if lnum == 1 && (getline(1)[0] == '#' || getline(1)[0] == '!') - setf ch - return - endif - if getline(lnum) =~ "MODULE" - setf chill - return - endif - if getline(lnum) =~ 'main\s*(\|#\s*include\|//' - setf ch - return - endif - let lnum = lnum + 1 - endwhile - setf chill -endfunc +au BufNewFile,BufRead *.ch call dist#ft#FTchange() " ChordPro au BufNewFile,BufRead *.chopro,*.crd,*.cho,*.crdpro,*.chordpro setf chordpro @@ -471,27 +334,7 @@ au BufNewFile,BufRead *.dcl,*.icl setf clean au BufNewFile,BufRead *.eni setf cl " Clever or dtd -au BufNewFile,BufRead *.ent call s:FTent() - -func! s:FTent() - " This function checks for valid cl syntax in the first five lines. - " Look for either an opening comment, '#', or a block start, '{". - " If not found, assume SGML. - let lnum = 1 - while lnum < 6 - let line = getline(lnum) - if line =~ '^\s*[#{]' - setf cl - return - elseif line !~ '^\s*$' - " Not a blank line, not a comment, and not a block start, - " so doesn't look like valid cl code. - break - endif - let lnum = lnum + 1 - endw - setf dtd -endfunc +au BufNewFile,BufRead *.ent call dist#ft#FTent() " Clipper (or FoxPro; could also be eviews) au BufNewFile,BufRead *.prg @@ -546,19 +389,11 @@ au BufNewFile,BufRead *enlightenment/*.cfg setf c au BufNewFile,BufRead *Eterm/*.cfg setf eterm " Euphoria 3 or 4 -au BufNewFile,BufRead *.eu,*.ew,*.ex,*.exu,*.exw call s:EuphoriaCheck() +au BufNewFile,BufRead *.eu,*.ew,*.ex,*.exu,*.exw call dist#ft#EuphoriaCheck() if has("fname_case") - au BufNewFile,BufRead *.EU,*.EW,*.EX,*.EXU,*.EXW call s:EuphoriaCheck() + au BufNewFile,BufRead *.EU,*.EW,*.EX,*.EXU,*.EXW call dist#ft#EuphoriaCheck() endif -func! s:EuphoriaCheck() - if exists('g:filetype_euphoria') - exe 'setf ' . g:filetype_euphoria - else - setf euphoria3 - endif -endfunc - " Lynx config files au BufNewFile,BufRead lynx.cfg setf lynx @@ -603,19 +438,7 @@ au BufNewFile,BufRead */etc/dnsmasq.conf setf dnsmasq au BufNewFile,BufRead *.desc setf desc " the D language or dtrace -au BufNewFile,BufRead *.d call s:DtraceCheck() - -func! s:DtraceCheck() - let lines = getline(1, min([line("$"), 100])) - if match(lines, '^module\>\|^import\>') > -1 - " D files often start with a module and/or import statement. - setf d - elseif match(lines, '^#!\S\+dtrace\|#pragma\s\+D\s\+option\|:\S\{-}:\S\{-}:') > -1 - setf dtrace - else - setf d - endif -endfunc +au BufNewFile,BufRead *.d call dist#ft#DtraceCheck() " Desktop files au BufNewFile,BufRead *.desktop,.directory setf desktop @@ -647,7 +470,7 @@ au BufNewFile,BufRead *.rul \ endif " DCL (Digital Command Language - vms) or DNS zone file -au BufNewFile,BufRead *.com call s:BindzoneCheck('dcl') +au BufNewFile,BufRead *.com call dist#ft#BindzoneCheck('dcl') " DOT au BufNewFile,BufRead *.dot setf dot @@ -695,27 +518,11 @@ au BufNewFile,BufRead .editorconfig setf dosini au BufNewFile,BufRead *.ecd setf ecd " Eiffel or Specman or Euphoria -au BufNewFile,BufRead *.e,*.E call s:FTe() +au BufNewFile,BufRead *.e,*.E call dist#ft#FTe() " Elinks configuration au BufNewFile,BufRead */etc/elinks.conf,*/.elinks/elinks.conf setf elinks -func! s:FTe() - if exists('g:filetype_euphoria') - exe 'setf ' . g:filetype_euphoria - else - let n = 1 - while n < 100 && n < line("$") - if getline(n) =~ "^\\s*\\(<'\\|'>\\)\\s*$" - setf specman - return - endif - let n = n + 1 - endwhile - setf eiffel - endif -endfunc - " ERicsson LANGuage; Yaws is erlang too au BufNewFile,BufRead *.erl,*.hrl,*.yaws setf erlang @@ -884,24 +691,7 @@ au BufNewFile,BufRead *.hex,*.h32 setf hex au BufNewFile,BufRead *.t.html setf tilde " HTML (.shtml and .stm for server side) -au BufNewFile,BufRead *.html,*.htm,*.shtml,*.stm call s:FThtml() - -" Distinguish between HTML, XHTML and Django -func! s:FThtml() - let n = 1 - while n < 10 && n < line("$") - if getline(n) =~ '\<DTD\s\+XHTML\s' - setf xhtml - return - endif - if getline(n) =~ '{%\s*\(extends\|block\|load\)\>\|{#\s\+' - setf htmldjango - return - endif - let n = n + 1 - endwhile - setf html -endfunc +au BufNewFile,BufRead *.html,*.htm,*.shtml,*.stm call dist#ft#FThtml() " HTML with Ruby - eRuby au BufNewFile,BufRead *.erb,*.rhtml setf eruby @@ -928,20 +718,7 @@ au BufNewFile,BufRead *.htt,*.htb setf httest au BufNewFile,BufRead *.icn setf icon " IDL (Interface Description Language) -au BufNewFile,BufRead *.idl call s:FTidl() - -" Distinguish between standard IDL and MS-IDL -func! s:FTidl() - let n = 1 - while n < 50 && n < line("$") - if getline(n) =~ '^\s*import\s\+"\(unknwn\|objidl\)\.idl"' - setf msidl - return - endif - let n = n + 1 - endwhile - setf idl -endfunc +au BufNewFile,BufRead *.idl call dist#ft#FTidl() " Microsoft IDL (Interface Description Language) Also *.idl " MOF = WMI (Windows Management Instrumentation) Managed Object Format @@ -952,25 +729,10 @@ au BufNewFile,BufRead */.icewm/menu setf icemenu " Indent profile (must come before IDL *.pro!) au BufNewFile,BufRead .indent.pro setf indent -au BufNewFile,BufRead indent.pro call s:ProtoCheck('indent') +au BufNewFile,BufRead indent.pro call dist#ft#ProtoCheck('indent') " IDL (Interactive Data Language) -au BufNewFile,BufRead *.pro call s:ProtoCheck('idlang') - -" Distinguish between "default" and Cproto prototype file. */ -func! s:ProtoCheck(default) - " Cproto files have a comment in the first line and a function prototype in - " the second line, it always ends in ";". Indent files may also have - " comments, thus we can't match comments to see the difference. - " IDL files can have a single ';' in the second line, require at least one - " chacter before the ';'. - if getline(2) =~ '.;$' - setf cpp - else - exe 'setf ' . a:default - endif -endfunc - +au BufNewFile,BufRead *.pro call dist#ft#ProtoCheck('idlang') " Indent RC au BufNewFile,BufRead indentrc setf indent @@ -1019,7 +781,7 @@ au BufNewFile,BufRead *.java,*.jav setf java au BufNewFile,BufRead *.jj,*.jjt setf javacc " JavaScript, ECMAScript -au BufNewFile,BufRead *.js,*.javascript,*.es,*.jsx setf javascript +au BufNewFile,BufRead *.js,*.javascript,*.es,*.jsx,*.mjs setf javascript " Java Server Pages au BufNewFile,BufRead *.jsp setf jsp @@ -1183,33 +945,8 @@ au BufNewFile,BufRead *.markdown,*.mdown,*.mkd,*.mkdn,*.mdwn,*.md setf markdown " Mason au BufNewFile,BufRead *.mason,*.mhtml,*.comp setf mason -" Matlab or Objective C -au BufNewFile,BufRead *.m call s:FTm() - -func! s:FTm() - let n = 1 - while n < 10 - let line = getline(n) - if line =~ '^\s*\(#\s*\(include\|import\)\>\|@import\>\|/\*\|//\)' - setf objc - return - endif - if line =~ '^\s*%' - setf matlab - return - endif - if line =~ '^\s*(\*' - setf mma - return - endif - let n = n + 1 - endwhile - if exists("g:filetype_m") - exe "setf " . g:filetype_m - else - setf matlab - endif -endfunc +" Mathematica, Matlab, Murphi or Objective C +au BufNewFile,BufRead *.m call dist#ft#FTm() " Mathematica notebook au BufNewFile,BufRead *.nb setf mma @@ -1239,29 +976,11 @@ au BufNewFile,BufRead *.mgl setf mgl au BufNewFile,BufRead *.mix,*.mixal setf mix " MMIX or VMS makefile -au BufNewFile,BufRead *.mms call s:FTmms() +au BufNewFile,BufRead *.mms call dist#ft#FTmms() " Symbian meta-makefile definition (MMP) au BufNewFile,BufRead *.mmp setf mmp -func! s:FTmms() - let n = 1 - while n < 10 - let line = getline(n) - if line =~ '^\s*\(%\|//\)' || line =~ '^\*' - setf mmix - return - endif - if line =~ '^\s*#' - setf make - return - endif - let n = n + 1 - endwhile - setf mmix -endfunc - - " Modsim III (or LambdaProlog) au BufNewFile,BufRead *.mod \ if getline(1) =~ '\<module\>' | @@ -1315,6 +1034,9 @@ au BufNewFile,BufRead *.mush setf mush " Mutt setup file (also for Muttng) au BufNewFile,BufRead Mutt{ng,}rc setf muttrc +" N1QL +au BufRead,BufNewfile *.n1ql,*.nql setf n1ql + " Nano au BufNewFile,BufRead */etc/nanorc,*.nanorc setf nanorc @@ -1342,33 +1064,10 @@ au BufNewFile,BufRead *.me \ setf nroff | \ endif au BufNewFile,BufRead *.tr,*.nr,*.roff,*.tmac,*.mom setf nroff -au BufNewFile,BufRead *.[1-9] call s:FTnroff() - -" This function checks if one of the first five lines start with a dot. In -" that case it is probably an nroff file: 'filetype' is set and 1 is returned. -func! s:FTnroff() - if getline(1)[0] . getline(2)[0] . getline(3)[0] . getline(4)[0] . getline(5)[0] =~ '\.' - setf nroff - return 1 - endif - return 0 -endfunc +au BufNewFile,BufRead *.[1-9] call dist#ft#FTnroff() " Nroff or Objective C++ -au BufNewFile,BufRead *.mm call s:FTmm() - -func! s:FTmm() - let n = 1 - while n < 10 - let line = getline(n) - if line =~ '^\s*\(#\s*\(include\|import\)\>\|@import\>\|/\*\)' - setf objcpp - return - endif - let n = n + 1 - endwhile - setf nroff -endfunc +au BufNewFile,BufRead *.mm call dist#ft#FTmm() " Not Quite C au BufNewFile,BufRead *.nqc setf nqc @@ -1423,28 +1122,13 @@ au BufNewFile,BufRead *.pcmk setf pcmk " Perl if has("fname_case") - au BufNewFile,BufRead *.pl,*.PL call s:FTpl() + au BufNewFile,BufRead *.pl,*.PL call dist#ft#FTpl() else - au BufNewFile,BufRead *.pl call s:FTpl() + au BufNewFile,BufRead *.pl call dist#ft#FTpl() endif au BufNewFile,BufRead *.plx,*.al,*.psgi setf perl au BufNewFile,BufRead *.p6,*.pm6,*.pl6 setf perl6 -func! s:FTpl() - if exists("g:filetype_pl") - exe "setf " . g:filetype_pl - else - " recognize Prolog by specific text in the first non-empty line - " require a blank after the '%' because Perl uses "%list" and "%translate" - let l = getline(nextnonblank(1)) - if l =~ '\<prolog\>' || l =~ '^\s*\(%\+\(\s\|$\)\|/\*\)' || l =~ ':-' - setf prolog - else - setf perl - endif - endif -endfunc - " Perl, XPM or XPM2 au BufNewFile,BufRead *.pm \ if getline(1) =~ "XPM2" | @@ -1457,7 +1141,7 @@ au BufNewFile,BufRead *.pm " Perl POD au BufNewFile,BufRead *.pod setf pod -au BufNewFile,BufRead *.pod6 setf pod6 +au BufNewFile,BufRead *.pod6 setf pod6 " Php, php3, php4, etc. " Also Phtml (was used for PHP 2 in the past) @@ -1507,29 +1191,7 @@ au BufNewFile,BufRead *.pov setf pov au BufNewFile,BufRead .povrayrc setf povini " Povray, PHP or assembly -au BufNewFile,BufRead *.inc call s:FTinc() - -func! s:FTinc() - if exists("g:filetype_inc") - exe "setf " . g:filetype_inc - else - let lines = getline(1).getline(2).getline(3) - if lines =~? "perlscript" - setf aspperl - elseif lines =~ "<%" - setf aspvbs - elseif lines =~ "<?" - setf php - else - call s:FTasmsyntax() - if exists("b:asmsyntax") - exe "setf " . fnameescape(b:asmsyntax) - else - setf pov - endif - endif - endif -endfunc +au BufNewFile,BufRead *.inc call dist#ft#FTinc() " Printcap and Termcap au BufNewFile,BufRead *printcap @@ -1558,74 +1220,13 @@ au BufNewFile,BufRead *.action setf privoxy au BufNewFile,BufRead .procmail,.procmailrc setf procmail " Progress or CWEB -au BufNewFile,BufRead *.w call s:FTprogress_cweb() - -func! s:FTprogress_cweb() - if exists("g:filetype_w") - exe "setf " . g:filetype_w - return - endif - if getline(1) =~ '&ANALYZE' || getline(3) =~ '&GLOBAL-DEFINE' - setf progress - else - setf cweb - endif -endfunc +au BufNewFile,BufRead *.w call dist#ft#FTprogress_cweb() " Progress or assembly -au BufNewFile,BufRead *.i call s:FTprogress_asm() - -func! s:FTprogress_asm() - if exists("g:filetype_i") - exe "setf " . g:filetype_i - return - endif - " This function checks for an assembly comment the first ten lines. - " If not found, assume Progress. - let lnum = 1 - while lnum <= 10 && lnum < line('$') - let line = getline(lnum) - if line =~ '^\s*;' || line =~ '^\*' - call s:FTasm() - return - elseif line !~ '^\s*$' || line =~ '^/\*' - " Not an empty line: Doesn't look like valid assembly code. - " Or it looks like a Progress /* comment - break - endif - let lnum = lnum + 1 - endw - setf progress -endfunc +au BufNewFile,BufRead *.i call dist#ft#FTprogress_asm() " Progress or Pascal -au BufNewFile,BufRead *.p call s:FTprogress_pascal() - -func! s:FTprogress_pascal() - if exists("g:filetype_p") - exe "setf " . g:filetype_p - return - endif - " This function checks for valid Pascal syntax in the first ten lines. - " Look for either an opening comment or a program start. - " If not found, assume Progress. - let lnum = 1 - while lnum <= 10 && lnum < line('$') - let line = getline(lnum) - if line =~ '^\s*\(program\|unit\|procedure\|function\|const\|type\|var\)\>' - \ || line =~ '^\s*{' || line =~ '^\s*(\*' - setf pascal - return - elseif line !~ '^\s*$' || line =~ '^/\*' - " Not an empty line: Doesn't look like valid Pascal code. - " Or it looks like a Progress /* comment - break - endif - let lnum = lnum + 1 - endw - setf progress -endfunc - +au BufNewFile,BufRead *.p call dist#ft#FTprogress_pascal() " Software Distributor Product Specification File (POSIX 1387.2-1995) au BufNewFile,BufRead *.psf setf psf @@ -1711,40 +1312,7 @@ else endif " Rexx, Rebol or R -au BufNewFile,BufRead *.r,*.R call s:FTr() - -func! s:FTr() - let max = line("$") > 50 ? 50 : line("$") - - for n in range(1, max) - " Rebol is easy to recognize, check for that first - if getline(n) =~? '\<REBOL\>' - setf rebol - return - endif - endfor - - for n in range(1, max) - " R has # comments - if getline(n) =~ '^\s*#' - setf r - return - endif - " Rexx has /* comments */ - if getline(n) =~ '^\s*/\*' - setf rexx - return - endif - endfor - - " Nothing recognized, use user default or assume Rexx - if exists("g:filetype_r") - exe "setf " . g:filetype_r - else - " Rexx used to be the default, but R appears to be much more popular. - setf r - endif -endfunc +au BufNewFile,BufRead *.r,*.R call dist#ft#FTr() " Remind au BufNewFile,BufRead .reminders,*.remind,*.rem setf remind @@ -1840,23 +1408,7 @@ au BufNewFile,BufRead *.siv setf sieve au BufNewFile,BufRead sendmail.cf setf sm " Sendmail .mc files are actually m4. Could also be MS Message text file. -au BufNewFile,BufRead *.mc call s:McSetf() - -func! s:McSetf() - " Rely on the file to start with a comment. - " MS message text files use ';', Sendmail files use '#' or 'dnl' - for lnum in range(1, min([line("$"), 20])) - let line = getline(lnum) - if line =~ '^\s*\(#\|dnl\)' - setf m4 " Sendmail .mc file - return - elseif line =~ '^\s*;' - setf msmessages " MS Message text file - return - endif - endfor - setf m4 " Default: Sendmail .mc file -endfunc +au BufNewFile,BufRead *.mc call dist#ft#McSetf() " Services au BufNewFile,BufRead */etc/services setf services @@ -1897,101 +1449,23 @@ au BufNewFile,BufRead sgml.catalog* call s:StarSetf('catalog') " Shell scripts (sh, ksh, bash, bash2, csh); Allow .profile_foo etc. " Gentoo ebuilds and Arch Linux PKGBUILDs are actually bash scripts -au BufNewFile,BufRead .bashrc*,bashrc,bash.bashrc,.bash[_-]profile*,.bash[_-]logout*,.bash[_-]aliases*,*.bash,*/{,.}bash[_-]completion{,.d,.sh}{,/*},*.ebuild,*.eclass,PKGBUILD* call SetFileTypeSH("bash") -au BufNewFile,BufRead .kshrc*,*.ksh call SetFileTypeSH("ksh") -au BufNewFile,BufRead */etc/profile,.profile*,*.sh,*.env call SetFileTypeSH(getline(1)) +au BufNewFile,BufRead .bashrc*,bashrc,bash.bashrc,.bash[_-]profile*,.bash[_-]logout*,.bash[_-]aliases*,*.bash,*/{,.}bash[_-]completion{,.d,.sh}{,/*},*.ebuild,*.eclass,PKGBUILD* call dist#ft#SetFileTypeSH("bash") +au BufNewFile,BufRead .kshrc*,*.ksh call dist#ft#SetFileTypeSH("ksh") +au BufNewFile,BufRead */etc/profile,.profile*,*.sh,*.env call dist#ft#SetFileTypeSH(getline(1)) " Shell script (Arch Linux) or PHP file (Drupal) au BufNewFile,BufRead *.install \ if getline(1) =~ '<?php' | \ setf php | \ else | - \ call SetFileTypeSH("bash") | + \ call dist#ft#SetFileTypeSH("bash") | \ endif -" Also called from scripts.vim. -func! SetFileTypeSH(name) - if expand("<amatch>") =~ g:ft_ignore_pat - return - endif - if a:name =~ '\<csh\>' - " Some .sh scripts contain #!/bin/csh. - call SetFileTypeShell("csh") - return - elseif a:name =~ '\<tcsh\>' - " Some .sh scripts contain #!/bin/tcsh. - call SetFileTypeShell("tcsh") - return - elseif a:name =~ '\<zsh\>' - " Some .sh scripts contain #!/bin/zsh. - call SetFileTypeShell("zsh") - return - elseif a:name =~ '\<ksh\>' - let b:is_kornshell = 1 - if exists("b:is_bash") - unlet b:is_bash - endif - if exists("b:is_sh") - unlet b:is_sh - endif - elseif exists("g:bash_is_sh") || a:name =~ '\<bash\>' || a:name =~ '\<bash2\>' - let b:is_bash = 1 - if exists("b:is_kornshell") - unlet b:is_kornshell - endif - if exists("b:is_sh") - unlet b:is_sh - endif - elseif a:name =~ '\<sh\>' - let b:is_sh = 1 - if exists("b:is_kornshell") - unlet b:is_kornshell - endif - if exists("b:is_bash") - unlet b:is_bash - endif - endif - call SetFileTypeShell("sh") -endfunc - -" For shell-like file types, check for an "exec" command hidden in a comment, -" as used for Tcl. -" Also called from scripts.vim, thus can't be local to this script. -func! SetFileTypeShell(name) - if expand("<amatch>") =~ g:ft_ignore_pat - return - endif - let l = 2 - while l < 20 && l < line("$") && getline(l) =~ '^\s*\(#\|$\)' - " Skip empty and comment lines. - let l = l + 1 - endwhile - if l < line("$") && getline(l) =~ '\s*exec\s' && getline(l - 1) =~ '^\s*#.*\\$' - " Found an "exec" line after a comment with continuation - let n = substitute(getline(l),'\s*exec\s\+\([^ ]*/\)\=', '', '') - if n =~ '\<tclsh\|\<wish' - setf tcl - return - endif - endif - exe "setf " . a:name -endfunc - " tcsh scripts -au BufNewFile,BufRead .tcshrc*,*.tcsh,tcsh.tcshrc,tcsh.login call SetFileTypeShell("tcsh") +au BufNewFile,BufRead .tcshrc*,*.tcsh,tcsh.tcshrc,tcsh.login call dist#ft#SetFileTypeShell("tcsh") " csh scripts, but might also be tcsh scripts (on some systems csh is tcsh) -au BufNewFile,BufRead .login*,.cshrc*,csh.cshrc,csh.login,csh.logout,*.csh,.alias call s:CSH() - -func! s:CSH() - if exists("g:filetype_csh") - call SetFileTypeShell(g:filetype_csh) - elseif &shell =~ "tcsh" - call SetFileTypeShell("tcsh") - else - call SetFileTypeShell("csh") - endif -endfunc +au BufNewFile,BufRead .login*,.cshrc*,csh.cshrc,csh.login,csh.logout,*.csh,.alias call dist#ft#CSH() " Z-Shell script au BufNewFile,BufRead .zprofile,*/etc/zprofile,.zfbfmarks setf zsh @@ -2062,42 +1536,7 @@ au BufNewFile,BufRead *.mib,*.my setf mib " Snort Configuration au BufNewFile,BufRead *.hog,snort.conf,vision.conf setf hog -au BufNewFile,BufRead *.rules call s:FTRules() - -let s:ft_rules_udev_rules_pattern = '^\s*\cudev_rules\s*=\s*"\([^"]\{-1,}\)/*".*' -func! s:FTRules() - let path = expand('<amatch>:p') - if path =~ '^/\(etc/udev/\%(rules\.d/\)\=.*\.rules\|lib/udev/\%(rules\.d/\)\=.*\.rules\)$' - setf udevrules - return - endif - if path =~ '^/etc/ufw/' - setf conf " Better than hog - return - endif - if path =~ '^/\(etc\|usr/share\)/polkit-1/rules\.d' - setf javascript - return - endif - try - let config_lines = readfile('/etc/udev/udev.conf') - catch /^Vim\%((\a\+)\)\=:E484/ - setf hog - return - endtry - let dir = expand('<amatch>:p:h') - for line in config_lines - if line =~ s:ft_rules_udev_rules_pattern - let udev_rules = substitute(line, s:ft_rules_udev_rules_pattern, '\1', "") - if dir == udev_rules - setf udevrules - endif - break - endif - endfor - setf hog -endfunc - +au BufNewFile,BufRead *.rules call dist#ft#FTRules() " Spec (Linux RPM) au BufNewFile,BufRead *.spec setf spec @@ -2121,15 +1560,7 @@ au BufNewFile,BufRead squid.conf setf squid au BufNewFile,BufRead *.tyb,*.typ,*.tyc,*.pkb,*.pks setf sql " SQL -au BufNewFile,BufRead *.sql call s:SQL() - -func! s:SQL() - if exists("g:filetype_sql") - exe "setf " . g:filetype_sql - else - setf sql - endif -endfunc +au BufNewFile,BufRead *.sql call dist#ft#SQL() " SQLJ au BufNewFile,BufRead *.sqlj setf sqlj @@ -2176,32 +1607,9 @@ au BufNewFile,BufRead */etc/sudoers,sudoers.tmp setf sudoers " SVG (Scalable Vector Graphics) au BufNewFile,BufRead *.svg setf svg -" If the file has an extension of 't' and is in a directory 't' or 'xt' then -" it is almost certainly a Perl test file. -" If the first line starts with '#' and contains 'perl' it's probably a Perl -" file. -" (Slow test) If a file contains a 'use' statement then it is almost certainly -" a Perl file. -func! s:FTperl() - let dirname = expand("%:p:h:t") - if expand("%:e") == 't' && (dirname == 't' || dirname == 'xt') - setf perl - return 1 - endif - if getline(1)[0] == '#' && getline(1) =~ 'perl' - setf perl - return 1 - endif - if search('^use\s\s*\k', 'nc', 30) - setf perl - return 1 - endif - return 0 -endfunc - " Tads (or Nroff or Perl test file) au BufNewFile,BufRead *.t - \ if !s:FTnroff() && !s:FTperl() | setf tads | endif + \ if !dist#ft#FTnroff() && !dist#ft#FTperl() | setf tads | endif " Tags au BufNewFile,BufRead tags setf tags @@ -2230,64 +1638,10 @@ au BufNewFile,BufRead *.ti setf terminfo " TeX au BufNewFile,BufRead *.latex,*.sty,*.dtx,*.ltx,*.bbl setf tex -au BufNewFile,BufRead *.tex call s:FTtex() - -" Choose context, plaintex, or tex (LaTeX) based on these rules: -" 1. Check the first line of the file for "%&<format>". -" 2. Check the first 1000 non-comment lines for LaTeX or ConTeXt keywords. -" 3. Default to "latex" or to g:tex_flavor, can be set in user's vimrc. -func! s:FTtex() - let firstline = getline(1) - if firstline =~ '^%&\s*\a\+' - let format = tolower(matchstr(firstline, '\a\+')) - let format = substitute(format, 'pdf', '', '') - if format == 'tex' - let format = 'latex' - elseif format == 'plaintex' - let format = 'plain' - endif - else - " Default value, may be changed later: - let format = exists("g:tex_flavor") ? g:tex_flavor : 'plain' - " Save position, go to the top of the file, find first non-comment line. - let save_cursor = getpos('.') - call cursor(1,1) - let firstNC = search('^\s*[^[:space:]%]', 'c', 1000) - if firstNC " Check the next thousand lines for a LaTeX or ConTeXt keyword. - let lpat = 'documentclass\>\|usepackage\>\|begin{\|newcommand\>\|renewcommand\>' - let cpat = 'start\a\+\|setup\a\+\|usemodule\|enablemode\|enableregime\|setvariables\|useencoding\|usesymbols\|stelle\a\+\|verwende\a\+\|stel\a\+\|gebruik\a\+\|usa\a\+\|imposta\a\+\|regle\a\+\|utilisemodule\>' - let kwline = search('^\s*\\\%(' . lpat . '\)\|^\s*\\\(' . cpat . '\)', - \ 'cnp', firstNC + 1000) - if kwline == 1 " lpat matched - let format = 'latex' - elseif kwline == 2 " cpat matched - let format = 'context' - endif " If neither matched, keep default set above. - " let lline = search('^\s*\\\%(' . lpat . '\)', 'cn', firstNC + 1000) - " let cline = search('^\s*\\\%(' . cpat . '\)', 'cn', firstNC + 1000) - " if cline > 0 - " let format = 'context' - " endif - " if lline > 0 && (cline == 0 || cline > lline) - " let format = 'tex' - " endif - endif " firstNC - call setpos('.', save_cursor) - endif " firstline =~ '^%&\s*\a\+' - - " Translation from formats to file types. TODO: add AMSTeX, RevTex, others? - if format == 'plain' - setf plaintex - elseif format == 'context' - setf context - else " probably LaTeX - setf tex - endif - return -endfunc +au BufNewFile,BufRead *.tex call dist#ft#FTtex() " ConTeXt -au BufNewFile,BufRead tex/context/*/*.tex,*.mkii,*.mkiv,*.mkvi setf context +au BufNewFile,BufRead *.mkii,*.mkiv,*.mkvi setf context " Texinfo au BufNewFile,BufRead *.texinfo,*.texi,*.txi setf texinfo @@ -2385,7 +1739,7 @@ au BufRead,BufNewFile *.hw,*.module,*.pkg \ endif " Visual Basic (also uses *.bas) or FORM -au BufNewFile,BufRead *.frm call s:FTVB("form") +au BufNewFile,BufRead *.frm call dist#ft#FTVB("form") " SaxBasic is close to Visual Basic au BufNewFile,BufRead *.sba setf vb @@ -2475,36 +1829,10 @@ au BufNewFile,BufRead .Xdefaults,.Xpdefaults,.Xresources,xdm-config,*.ad setf xd " Xmath au BufNewFile,BufRead *.msc,*.msf setf xmath au BufNewFile,BufRead *.ms - \ if !s:FTnroff() | setf xmath | endif + \ if !dist#ft#FTnroff() | setf xmath | endif " XML specific variants: docbk and xbl -au BufNewFile,BufRead *.xml call s:FTxml() - -func! s:FTxml() - let n = 1 - while n < 100 && n < line("$") - let line = getline(n) - " DocBook 4 or DocBook 5. - let is_docbook4 = line =~ '<!DOCTYPE.*DocBook' - let is_docbook5 = line =~ ' xmlns="http://docbook.org/ns/docbook"' - if is_docbook4 || is_docbook5 - let b:docbk_type = "xml" - if is_docbook5 - let b:docbk_ver = 5 - else - let b:docbk_ver = 4 - endif - setf docbk - return - endif - if line =~ 'xmlns:xbl="http://www.mozilla.org/xbl"' - setf xbl - return - endif - let n += 1 - endwhile - setf xml -endfunc +au BufNewFile,BufRead *.xml call dist#ft#FTxml() " XMI (holding UML models) is also XML au BufNewFile,BufRead *.xmi setf xml @@ -2547,25 +1875,7 @@ au BufNewFile,BufRead *.xsl,*.xslt setf xslt au BufNewFile,BufRead *.yy,*.yxx,*.y++ setf yacc " Yacc or racc -au BufNewFile,BufRead *.y call s:FTy() - -func! s:FTy() - let n = 1 - while n < 100 && n < line("$") - let line = getline(n) - if line =~ '^\s*%' - setf yacc - return - endif - if getline(n) =~ '^\s*\(#\|class\>\)' && getline(n) !~ '^\s*#\s*include' - setf racc - return - endif - let n = n + 1 - endwhile - setf yacc -endfunc - +au BufNewFile,BufRead *.y call dist#ft#FTy() " Yaml au BufNewFile,BufRead *.yaml,*.yml setf yaml @@ -2581,9 +1891,9 @@ au BufNewFile,BufRead *.zut setf zimbutempl " Zope " dtml (zope dynamic template markup language), pt (zope page template), " cpt (zope form controller page template) -au BufNewFile,BufRead *.dtml,*.pt,*.cpt call s:FThtml() +au BufNewFile,BufRead *.dtml,*.pt,*.cpt call dist#ft#FThtml() " zsql (zope sql method) -au BufNewFile,BufRead *.zsql call s:SQL() +au BufNewFile,BufRead *.zsql call dist#ft#SQL() " Z80 assembler asz80 au BufNewFile,BufRead *.z8a setf z8a @@ -2625,6 +1935,11 @@ au BufNewFile,BufRead *asterisk*/*voicemail.conf* call s:StarSetf('asteriskvm') " Bazaar version control au BufNewFile,BufRead bzr_log.* setf bzr +" Bazel build file +if !has("fname_case") + au BufNewFile,BufRead BUILD setf bzl +endif + " BIND zone au BufNewFile,BufRead */named/db.*,*/bind/db.* call s:StarSetf('bindzone') @@ -2732,17 +2047,7 @@ au BufNewFile,BufRead *termcap* " ReDIF " Only used when the .rdf file was not detected to be XML. -au BufRead,BufNewFile *.rdf call s:Redif() -func! s:Redif() - let lnum = 1 - while lnum <= 5 && lnum < line('$') - if getline(lnum) =~ "^\ctemplate-type:" - setf redif - return - endif - let lnum = lnum + 1 - endwhile -endfunc +au BufRead,BufNewFile *.rdf call dist#ft#Redif() " Remind au BufNewFile,BufRead .reminders* call s:StarSetf('remind') @@ -2780,8 +2085,13 @@ au BufNewFile,BufRead zsh*,zlog* call s:StarSetf('zsh') " Plain text files, needs to be far down to not override others. This avoids " the "conf" type being used if there is a line starting with '#'. -au BufNewFile,BufRead *.txt,*.text,README setf text +au BufNewFile,BufRead *.text,README setf text +" Help files match *.txt but should have a last line that is a modeline. +au BufNewFile,BufRead *.txt + \ if getline('$') !~ 'vim:.*ft=help' + \| setf text + \| endif " Use the filetype detect plugins. They may overrule any of the previously " detected filetypes. @@ -2792,12 +2102,12 @@ runtime! ftdetect/*.vim " state. augroup END -" Generic configuration file (check this last, it's just guessing!) +" Generic configuration file. Use FALLBACK, it's just guessing! au filetypedetect BufNewFile,BufRead,StdinReadPost * \ if !did_filetype() && expand("<amatch>") !~ g:ft_ignore_pat \ && (getline(1) =~ '^#' || getline(2) =~ '^#' || getline(3) =~ '^#' \ || getline(4) =~ '^#' || getline(5) =~ '^#') | - \ setf conf | + \ setf FALLBACK conf | \ endif diff --git a/runtime/ftplugin/man.vim b/runtime/ftplugin/man.vim index e36dfc5a90..68ebb33e45 100644 --- a/runtime/ftplugin/man.vim +++ b/runtime/ftplugin/man.vim @@ -6,7 +6,7 @@ if exists('b:did_ftplugin') || &filetype !=# 'man' endif let b:did_ftplugin = 1 -let s:pager = !exists('b:man_sect') +let s:pager = get(s:, 'pager', 0) || !exists('b:man_sect') if s:pager call man#init_pager() @@ -35,7 +35,7 @@ if !exists('g:no_plugin_maps') && !exists('g:no_man_maps') nnoremap <silent> <buffer> <C-]> :Man<CR> nnoremap <silent> <buffer> K :Man<CR> nnoremap <silent> <buffer> <C-T> :call man#pop_tag()<CR> - if s:pager + if 1 == bufnr('%') || s:pager nnoremap <silent> <buffer> <nowait> q :lclose<CR>:q<CR> else nnoremap <silent> <buffer> <nowait> q :lclose<CR><C-W>c diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua new file mode 100644 index 0000000000..b0fbe9cc35 --- /dev/null +++ b/runtime/lua/man.lua @@ -0,0 +1,168 @@ +local buf_hls = {} + +local function highlight_line(line, linenr) + local chars = {} + local prev_char = '' + local overstrike, escape = false, false + local hls = {} -- Store highlight groups as { attr, start, final } + local NONE, BOLD, UNDERLINE, ITALIC = 0, 1, 2, 3 + local hl_groups = {[BOLD]="manBold", [UNDERLINE]="manUnderline", [ITALIC]="manItalic"} + local attr = NONE + local byte = 0 -- byte offset + + local function end_attr_hl(attr) + for i, hl in ipairs(hls) do + if hl.attr == attr and hl.final == -1 then + hl.final = byte + hls[i] = hl + end + end + end + + local function add_attr_hl(code) + local continue_hl = true + if code == 0 then + attr = NONE + continue_hl = false + elseif code == 1 then + attr = BOLD + elseif code == 22 then + attr = BOLD + continue_hl = false + elseif code == 3 then + attr = ITALIC + elseif code == 23 then + attr = ITALIC + continue_hl = false + elseif code == 4 then + attr = UNDERLINE + elseif code == 24 then + attr = UNDERLINE + continue_hl = false + else + attr = NONE + return + end + + if continue_hl then + hls[#hls + 1] = {attr=attr, start=byte, final=-1} + else + if attr == NONE then + for a, _ in pairs(hl_groups) do + end_attr_hl(a) + end + else + end_attr_hl(attr) + end + end + end + + -- Break input into UTF8 code points. ASCII code points (from 0x00 to 0x7f) + -- can be represented in one byte. Any code point above that is represented by + -- a leading byte (0xc0 and above) and continuation bytes (0x80 to 0xbf, or + -- decimal 128 to 191). + for char in line:gmatch("[^\128-\191][\128-\191]*") do + if overstrike then + local last_hl = hls[#hls] + if char == prev_char then + if char == '_' and attr == UNDERLINE and last_hl and last_hl.final == byte then + -- This underscore is in the middle of an underlined word + attr = UNDERLINE + else + attr = BOLD + end + elseif prev_char == '_' then + -- char is underlined + attr = UNDERLINE + elseif prev_char == '+' and char == 'o' then + -- bullet (overstrike text '+^Ho') + attr = BOLD + char = '·' + elseif prev_char == '·' and char == 'o' then + -- bullet (additional handling for '+^H+^Ho^Ho') + attr = BOLD + char = '·' + else + -- use plain char + attr = NONE + end + + -- Grow the previous highlight group if possible + if last_hl and last_hl.attr == attr and last_hl.final == byte then + last_hl.final = byte + #char + else + hls[#hls + 1] = {attr=attr, start=byte, final=byte + #char} + end + + overstrike = false + prev_char = '' + byte = byte + #char + chars[#chars + 1] = char + elseif escape then + -- Use prev_char to store the escape sequence + prev_char = prev_char .. char + -- We only want to match against SGR sequences, which consist of ESC + -- followed by '[', then a series of parameter and intermediate bytes in + -- the range 0x20 - 0x3f, then 'm'. (See ECMA-48, sections 5.4 & 8.3.117) + local sgr = prev_char:match("^%[([\032-\063]*)m$") + if sgr then + local match = '' + while sgr and #sgr > 0 do + -- Match against SGR parameters, which may be separated by ';' + match, sgr = sgr:match("^(%d*);?(.*)") + add_attr_hl(match + 0) -- coerce to number + end + escape = false + elseif not prev_char:match("^%[[\032-\063]*$") then + -- Stop looking if this isn't a partial CSI sequence + escape = false + end + elseif char == "\027" then + escape = true + prev_char = '' + elseif char == "\b" then + overstrike = true + prev_char = chars[#chars] + byte = byte - #prev_char + chars[#chars] = nil + else + byte = byte + #char + chars[#chars + 1] = char + end + end + + for _, hl in ipairs(hls) do + if hl.attr ~= NONE then + buf_hls[#buf_hls + 1] = { + 0, + -1, + hl_groups[hl.attr], + linenr - 1, + hl.start, + hl.final + } + end + end + + return table.concat(chars, '') +end + +local function highlight_man_page() + local mod = vim.api.nvim_buf_get_option(0, "modifiable") + vim.api.nvim_buf_set_option(0, "modifiable", true) + + local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false) + for i, line in ipairs(lines) do + lines[i] = highlight_line(line, i) + end + vim.api.nvim_buf_set_lines(0, 0, -1, false, lines) + + for _, args in ipairs(buf_hls) do + vim.api.nvim_buf_add_highlight(unpack(args)) + end + buf_hls = {} + + vim.api.nvim_buf_set_option(0, "modifiable", mod) +end + +return { highlight_man_page = highlight_man_page } diff --git a/runtime/optwin.vim b/runtime/optwin.vim index 8f04e495e5..13d0db0390 100644 --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -1087,6 +1087,9 @@ if has("quickfix") call <SID>OptionG("gp", &gp) call append("$", "grepformat\tlist of formats for output of 'grepprg'") call <SID>OptionG("gfm", &gfm) + call append("$", "makeencoding\tencoding of the \":make\" and \":grep\" output") + call append("$", "\t(global or local to buffer)") + call <SID>OptionG("menc", &menc) endif diff --git a/runtime/plugin/health.vim b/runtime/plugin/health.vim index e3482cb0fe..66ae8fb239 100644 --- a/runtime/plugin/health.vim +++ b/runtime/plugin/health.vim @@ -1,8 +1 @@ -function! s:complete(lead, _line, _pos) abort - return sort(filter(map(globpath(&runtimepath, 'autoload/health/*', 1, 1), - \ 'fnamemodify(v:val, ":t:r")'), - \ 'empty(a:lead) || v:val[:strlen(a:lead)-1] ==# a:lead')) -endfunction - -command! -nargs=* -complete=customlist,s:complete CheckHealth - \ call health#check([<f-args>]) +autocmd CmdUndefined CheckHealth checkhealth diff --git a/runtime/plugin/rplugin.vim b/runtime/plugin/rplugin.vim index ca15ec82d1..56163c894e 100644 --- a/runtime/plugin/rplugin.vim +++ b/runtime/plugin/rplugin.vim @@ -56,6 +56,6 @@ function! s:LoadRemotePlugins() abort endif endfunction -command! UpdateRemotePlugins call remote#host#UpdateRemotePlugins() +command! -bar UpdateRemotePlugins call remote#host#UpdateRemotePlugins() call s:LoadRemotePlugins() diff --git a/runtime/scripts.vim b/runtime/scripts.vim index 63bf9efcf9..18263e2842 100644 --- a/runtime/scripts.vim +++ b/runtime/scripts.vim @@ -1,11 +1,15 @@ " Vim support file to detect file types in scripts " " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last change: 2017 Aug 27 +" Last change: 2017 Nov 11 " This file is called by an autocommand for every file that has just been " loaded into a buffer. It checks if the type of file can be recognized by " the file contents. The autocommand is in $VIMRUNTIME/filetype.vim. +" +" Note that the pattern matches are done with =~# to avoid the value of the +" 'ignorecase' option making a difference. Where case is to be ignored use +" =~? instead. Do not use =~ anywhere. " Only do the rest when the FileType autocommand has not been triggered yet. @@ -28,12 +32,12 @@ set cpo&vim let s:line1 = getline(1) -if s:line1 =~ "^#!" +if s:line1 =~# "^#!" " A script that starts with "#!". " Check for a line like "#!/usr/bin/env VAR=val bash". Turn it into " "#!/usr/bin/bash" to make matching easier. - if s:line1 =~ '^#!\s*\S*\<env\s' + if s:line1 =~# '^#!\s*\S*\<env\s' let s:line1 = substitute(s:line1, '\S\+=\S\+', '', 'g') let s:line1 = substitute(s:line1, '\<env\s\+', '', '') endif @@ -44,11 +48,11 @@ if s:line1 =~ "^#!" " "#!/usr/bin/env perl [path/args]" " If there is no path use the first word: "#!perl [path/args]". " Otherwise get the last word after a slash: "#!/usr/bin/perl [path/args]". - if s:line1 =~ '^#!\s*\a:[/\\]' + if s:line1 =~# '^#!\s*\a:[/\\]' let s:name = substitute(s:line1, '^#!.*[/\\]\(\i\+\).*', '\1', '') - elseif s:line1 =~ '^#!.*\<env\>' + elseif s:line1 =~# '^#!.*\<env\>' let s:name = substitute(s:line1, '^#!.*\<env\>\s\+\(\i\+\).*', '\1', '') - elseif s:line1 =~ '^#!\s*[^/\\ ]*\>\([^/\\]\|$\)' + elseif s:line1 =~# '^#!\s*[^/\\ ]*\>\([^/\\]\|$\)' let s:name = substitute(s:line1, '^#!\s*\([^/\\ ]*\>\).*', '\1', '') else let s:name = substitute(s:line1, '^#!\s*\S*[/\\]\(\i\+\).*', '\1', '') @@ -56,116 +60,116 @@ if s:line1 =~ "^#!" " tcl scripts may have #!/bin/sh in the first line and "exec wish" in the " third line. Suggested by Steven Atkinson. - if getline(3) =~ '^exec wish' + if getline(3) =~# '^exec wish' let s:name = 'wish' endif " Bourne-like shell scripts: bash bash2 ksh ksh93 sh - if s:name =~ '^\(bash\d*\|\|ksh\d*\|sh\)\>' - call SetFileTypeSH(s:line1) " defined in filetype.vim + if s:name =~# '^\(bash\d*\|\|ksh\d*\|sh\)\>' + call dist#ft#SetFileTypeSH(s:line1) " defined in filetype.vim " csh scripts - elseif s:name =~ '^csh\>' + elseif s:name =~# '^csh\>' if exists("g:filetype_csh") - call SetFileTypeShell(g:filetype_csh) + call dist#ft#SetFileTypeShell(g:filetype_csh) else - call SetFileTypeShell("csh") + call dist#ft#SetFileTypeShell("csh") endif " tcsh scripts - elseif s:name =~ '^tcsh\>' - call SetFileTypeShell("tcsh") + elseif s:name =~# '^tcsh\>' + call dist#ft#SetFileTypeShell("tcsh") " Z shell scripts - elseif s:name =~ '^zsh\>' + elseif s:name =~# '^zsh\>' set ft=zsh " TCL scripts - elseif s:name =~ '^\(tclsh\|wish\|expectk\|itclsh\|itkwish\)\>' + elseif s:name =~# '^\(tclsh\|wish\|expectk\|itclsh\|itkwish\)\>' set ft=tcl " Expect scripts - elseif s:name =~ '^expect\>' + elseif s:name =~# '^expect\>' set ft=expect " Gnuplot scripts - elseif s:name =~ '^gnuplot\>' + elseif s:name =~# '^gnuplot\>' set ft=gnuplot " Makefiles - elseif s:name =~ 'make\>' + elseif s:name =~# 'make\>' set ft=make " Lua - elseif s:name =~ 'lua' + elseif s:name =~# 'lua' set ft=lua " Perl 6 - elseif s:name =~ 'perl6' + elseif s:name =~# 'perl6' set ft=perl6 " Perl - elseif s:name =~ 'perl' + elseif s:name =~# 'perl' set ft=perl " PHP - elseif s:name =~ 'php' + elseif s:name =~# 'php' set ft=php " Python - elseif s:name =~ 'python' + elseif s:name =~# 'python' set ft=python " Groovy - elseif s:name =~ '^groovy\>' + elseif s:name =~# '^groovy\>' set ft=groovy " Ruby - elseif s:name =~ 'ruby' + elseif s:name =~# 'ruby' set ft=ruby " JavaScript - elseif s:name =~ 'node\(js\)\=\>' || s:name =~ 'rhino\>' + elseif s:name =~# 'node\(js\)\=\>' || s:name =~# 'rhino\>' set ft=javascript " BC calculator - elseif s:name =~ '^bc\>' + elseif s:name =~# '^bc\>' set ft=bc " sed - elseif s:name =~ 'sed\>' + elseif s:name =~# 'sed\>' set ft=sed " OCaml-scripts - elseif s:name =~ 'ocaml' + elseif s:name =~# 'ocaml' set ft=ocaml " Awk scripts - elseif s:name =~ 'awk\>' + elseif s:name =~# 'awk\>' set ft=awk " Website MetaLanguage - elseif s:name =~ 'wml' + elseif s:name =~# 'wml' set ft=wml " Scheme scripts - elseif s:name =~ 'scheme' + elseif s:name =~# 'scheme' set ft=scheme " CFEngine scripts - elseif s:name =~ 'cfengine' + elseif s:name =~# 'cfengine' set ft=cfengine " Erlang scripts - elseif s:name =~ 'escript' + elseif s:name =~# 'escript' set ft=erlang " Haskell - elseif s:name =~ 'haskell' + elseif s:name =~# 'haskell' set ft=haskell " Scala - elseif s:name =~ 'scala\>' + elseif s:name =~# 'scala\>' set ft=scala endif @@ -180,28 +184,28 @@ else let s:line5 = getline(5) " Bourne-like shell scripts: sh ksh bash bash2 - if s:line1 =~ '^:$' - call SetFileTypeSH(s:line1) " defined in filetype.vim + if s:line1 =~# '^:$' + call dist#ft#SetFileTypeSH(s:line1) " defined in filetype.vim " Z shell scripts - elseif s:line1 =~ '^#compdef\>' || s:line1 =~ '^#autoload\>' || - \ "\n".s:line1."\n".s:line2."\n".s:line3."\n".s:line4."\n".s:line5 =~ '\n\s*emulate\s\+\%(-[LR]\s\+\)\=[ckz]\=sh\>' + elseif s:line1 =~# '^#compdef\>' || s:line1 =~# '^#autoload\>' || + \ "\n".s:line1."\n".s:line2."\n".s:line3."\n".s:line4."\n".s:line5 =~# '\n\s*emulate\s\+\%(-[LR]\s\+\)\=[ckz]\=sh\>' set ft=zsh " ELM Mail files - elseif s:line1 =~ '^From \([a-zA-Z][a-zA-Z_0-9\.=-]*\(@[^ ]*\)\=\|-\) .* \(19\|20\)\d\d$' + elseif s:line1 =~# '^From \([a-zA-Z][a-zA-Z_0-9\.=-]*\(@[^ ]*\)\=\|-\) .* \(19\|20\)\d\d$' set ft=mail " Mason - elseif s:line1 =~ '^<[%&].*>' + elseif s:line1 =~# '^<[%&].*>' set ft=mason " Vim scripts (must have '" vim' as the first line to trigger this) - elseif s:line1 =~ '^" *[vV]im$' + elseif s:line1 =~# '^" *[vV]im$' set ft=vim " MOO - elseif s:line1 =~ '^\*\* LambdaMOO Database, Format Version \%([1-3]\>\)\@!\d\+ \*\*$' + elseif s:line1 =~# '^\*\* LambdaMOO Database, Format Version \%([1-3]\>\)\@!\d\+ \*\*$' set ft=moo " Diff file: @@ -215,40 +219,45 @@ else " - "=== ", "--- ", "+++ " (bzr diff, common case) " - "=== (removed|added|renamed|modified)" (bzr diff, alternative) " - "# HG changeset patch" in first line (Mercurial export format) - elseif s:line1 =~ '^\(diff\>\|Only in \|\d\+\(,\d\+\)\=[cda]\d\+\>\|# It was generated by makepatch \|Index:\s\+\f\+\r\=$\|===== \f\+ \d\+\.\d\+ vs edited\|==== //\f\+#\d\+\|# HG changeset patch\)' - \ || (s:line1 =~ '^--- ' && s:line2 =~ '^+++ ') - \ || (s:line1 =~ '^\* looking for ' && s:line2 =~ '^\* comparing to ') - \ || (s:line1 =~ '^\*\*\* ' && s:line2 =~ '^--- ') - \ || (s:line1 =~ '^=== ' && ((s:line2 =~ '^=\{66\}' && s:line3 =~ '^--- ' && s:line4 =~ '^+++') || (s:line2 =~ '^--- ' && s:line3 =~ '^+++ '))) - \ || (s:line1 =~ '^=== \(removed\|added\|renamed\|modified\)') + elseif s:line1 =~# '^\(diff\>\|Only in \|\d\+\(,\d\+\)\=[cda]\d\+\>\|# It was generated by makepatch \|Index:\s\+\f\+\r\=$\|===== \f\+ \d\+\.\d\+ vs edited\|==== //\f\+#\d\+\|# HG changeset patch\)' + \ || (s:line1 =~# '^--- ' && s:line2 =~# '^+++ ') + \ || (s:line1 =~# '^\* looking for ' && s:line2 =~# '^\* comparing to ') + \ || (s:line1 =~# '^\*\*\* ' && s:line2 =~# '^--- ') + \ || (s:line1 =~# '^=== ' && ((s:line2 =~# '^=\{66\}' && s:line3 =~# '^--- ' && s:line4 =~# '^+++') || (s:line2 =~# '^--- ' && s:line3 =~# '^+++ '))) + \ || (s:line1 =~# '^=== \(removed\|added\|renamed\|modified\)') set ft=diff " PostScript Files (must have %!PS as the first line, like a2ps output) - elseif s:line1 =~ '^%![ \t]*PS' + elseif s:line1 =~# '^%![ \t]*PS' set ft=postscr " M4 scripts: Guess there is a line that starts with "dnl". - elseif s:line1 =~ '^\s*dnl\>' - \ || s:line2 =~ '^\s*dnl\>' - \ || s:line3 =~ '^\s*dnl\>' - \ || s:line4 =~ '^\s*dnl\>' - \ || s:line5 =~ '^\s*dnl\>' + elseif s:line1 =~# '^\s*dnl\>' + \ || s:line2 =~# '^\s*dnl\>' + \ || s:line3 =~# '^\s*dnl\>' + \ || s:line4 =~# '^\s*dnl\>' + \ || s:line5 =~# '^\s*dnl\>' set ft=m4 + " AmigaDos scripts + elseif $TERM == "amiga" + \ && (s:line1 =~# "^;" || s:line1 =~? '^\.bra') + set ft=amiga + " SiCAD scripts (must have procn or procd as the first line to trigger this) elseif s:line1 =~? '^ *proc[nd] *$' set ft=sicad " Purify log files start with "**** Purify" - elseif s:line1 =~ '^\*\*\*\* Purify' + elseif s:line1 =~# '^\*\*\*\* Purify' set ft=purifylog " XML - elseif s:line1 =~ '<?\s*xml.*?>' + elseif s:line1 =~# '<?\s*xml.*?>' set ft=xml " XHTML (e.g.: PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN") - elseif s:line1 =~ '\<DTD\s\+XHTML\s' + elseif s:line1 =~# '\<DTD\s\+XHTML\s' set ft=xhtml " HTML (e.g.: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN") @@ -257,43 +266,43 @@ else set ft=html " PDF - elseif s:line1 =~ '^%PDF-' + elseif s:line1 =~# '^%PDF-' set ft=pdf " XXD output - elseif s:line1 =~ '^\x\{7}: \x\{2} \=\x\{2} \=\x\{2} \=\x\{2} ' + elseif s:line1 =~# '^\x\{7}: \x\{2} \=\x\{2} \=\x\{2} \=\x\{2} ' set ft=xxd " RCS/CVS log output - elseif s:line1 =~ '^RCS file:' || s:line2 =~ '^RCS file:' + elseif s:line1 =~# '^RCS file:' || s:line2 =~# '^RCS file:' set ft=rcslog " CVS commit - elseif s:line2 =~ '^CVS:' || getline("$") =~ '^CVS: ' + elseif s:line2 =~# '^CVS:' || getline("$") =~# '^CVS: ' set ft=cvs " Prescribe - elseif s:line1 =~ '^!R!' + elseif s:line1 =~# '^!R!' set ft=prescribe " Send-pr - elseif s:line1 =~ '^SEND-PR:' + elseif s:line1 =~# '^SEND-PR:' set ft=sendpr " SNNS files - elseif s:line1 =~ '^SNNS network definition file' + elseif s:line1 =~# '^SNNS network definition file' set ft=snnsnet - elseif s:line1 =~ '^SNNS pattern definition file' + elseif s:line1 =~# '^SNNS pattern definition file' set ft=snnspat - elseif s:line1 =~ '^SNNS result file' + elseif s:line1 =~# '^SNNS result file' set ft=snnsres " Virata - elseif s:line1 =~ '^%.\{-}[Vv]irata' - \ || s:line2 =~ '^%.\{-}[Vv]irata' - \ || s:line3 =~ '^%.\{-}[Vv]irata' - \ || s:line4 =~ '^%.\{-}[Vv]irata' - \ || s:line5 =~ '^%.\{-}[Vv]irata' + elseif s:line1 =~# '^%.\{-}[Vv]irata' + \ || s:line2 =~# '^%.\{-}[Vv]irata' + \ || s:line3 =~# '^%.\{-}[Vv]irata' + \ || s:line4 =~# '^%.\{-}[Vv]irata' + \ || s:line5 =~# '^%.\{-}[Vv]irata' set ft=virata " Strace @@ -301,17 +310,17 @@ else set ft=strace " VSE JCL - elseif s:line1 =~ '^\* $$ JOB\>' || s:line1 =~ '^// *JOB\>' + elseif s:line1 =~# '^\* $$ JOB\>' || s:line1 =~# '^// *JOB\>' set ft=vsejcl " TAK and SINDA - elseif s:line4 =~ 'K & K Associates' || s:line2 =~ 'TAK 2000' + elseif s:line4 =~# 'K & K Associates' || s:line2 =~# 'TAK 2000' set ft=takout - elseif s:line3 =~ 'S Y S T E M S I M P R O V E D ' + elseif s:line3 =~# 'S Y S T E M S I M P R O V E D ' set ft=sindaout - elseif getline(6) =~ 'Run Date: ' + elseif getline(6) =~# 'Run Date: ' set ft=takcmp - elseif getline(9) =~ 'Node File 1' + elseif getline(9) =~# 'Node File 1' set ft=sindacmp " DNS zone files @@ -319,34 +328,34 @@ else set ft=bindzone " BAAN - elseif s:line1 =~ '|\*\{1,80}' && s:line2 =~ 'VRC ' - \ || s:line2 =~ '|\*\{1,80}' && s:line3 =~ 'VRC ' + elseif s:line1 =~# '|\*\{1,80}' && s:line2 =~# 'VRC ' + \ || s:line2 =~# '|\*\{1,80}' && s:line3 =~# 'VRC ' set ft=baan " Valgrind - elseif s:line1 =~ '^==\d\+== valgrind' || s:line3 =~ '^==\d\+== Using valgrind' + elseif s:line1 =~# '^==\d\+== valgrind' || s:line3 =~# '^==\d\+== Using valgrind' set ft=valgrind " Go docs - elseif s:line1 =~ '^PACKAGE DOCUMENTATION$' + elseif s:line1 =~# '^PACKAGE DOCUMENTATION$' set ft=godoc " Renderman Interface Bytestream - elseif s:line1 =~ '^##RenderMan' + elseif s:line1 =~# '^##RenderMan' set ft=rib " Scheme scripts - elseif s:line1 =~ 'exec\s\+\S*scheme' || s:line2 =~ 'exec\s\+\S*scheme' + elseif s:line1 =~# 'exec\s\+\S*scheme' || s:line2 =~# 'exec\s\+\S*scheme' set ft=scheme " Git output - elseif s:line1 =~ '^\(commit\|tree\|object\) \x\{40\}\>\|^tag \S\+$' + elseif s:line1 =~# '^\(commit\|tree\|object\) \x\{40\}\>\|^tag \S\+$' set ft=git " Gprof (gnu profiler) elseif s:line1 == 'Flat profile:' \ && s:line2 == '' - \ && s:line3 =~ '^Each sample counts as .* seconds.$' + \ && s:line3 =~# '^Each sample counts as .* seconds.$' set ft=gprof " Erlang terms @@ -357,18 +366,18 @@ else " CVS diff else let s:lnum = 1 - while getline(s:lnum) =~ "^? " && s:lnum < line("$") + while getline(s:lnum) =~# "^? " && s:lnum < line("$") let s:lnum += 1 endwhile - if getline(s:lnum) =~ '^Index:\s\+\f\+$' + if getline(s:lnum) =~# '^Index:\s\+\f\+$' set ft=diff " locale input files: Formal Definitions of Cultural Conventions " filename must be like en_US, fr_FR@euro or en_US.UTF-8 - elseif expand("%") =~ '\a\a_\a\a\($\|[.@]\)\|i18n$\|POSIX$\|translit_' + elseif expand("%") =~# '\a\a_\a\a\($\|[.@]\)\|i18n$\|POSIX$\|translit_' let s:lnum = 1 while s:lnum < 100 && s:lnum < line("$") - if getline(s:lnum) =~ '^LC_\(IDENTIFICATION\|CTYPE\|COLLATE\|MONETARY\|NUMERIC\|TIME\|MESSAGES\|PAPER\|TELEPHONE\|MEASUREMENT\|NAME\|ADDRESS\)$' + if getline(s:lnum) =~# '^LC_\(IDENTIFICATION\|CTYPE\|COLLATE\|MONETARY\|NUMERIC\|TIME\|MESSAGES\|PAPER\|TELEPHONE\|MEASUREMENT\|NAME\|ADDRESS\)$' setf fdcc break endif diff --git a/runtime/spell/en.utf-8.spl b/runtime/spell/en.utf-8.spl Binary files differnew file mode 100644 index 0000000000..83b9b8f7c2 --- /dev/null +++ b/runtime/spell/en.utf-8.spl diff --git a/runtime/syntax/dircolors.vim b/runtime/syntax/dircolors.vim index 1b598c39b5..3d7f63dc55 100644 --- a/runtime/syntax/dircolors.vim +++ b/runtime/syntax/dircolors.vim @@ -32,7 +32,7 @@ syntax match dircolorsEscape '\\[abefnrtv?_\\^#]' syntax match dircolorsEscape '\\[0-9]\{3}' syntax match dircolorsEscape '\\x[0-9a-f]\{3}' -if !has('gui_running') && &t_Co == '' +if !(has('gui_running') || &termguicolors) && &t_Co == '' syntax match dircolorsNumber '\<\d\+\>' highlight default link dircolorsNumber Number endif @@ -84,7 +84,7 @@ endfunction function! s:get_hi_str(color, place) abort if a:color >= 0 && a:color <= 255 - if has('gui_running') + if has('gui_running') || &termguicolors return ' gui' . a:place . '=' . s:termguicolors[a:color] elseif a:color <= 7 || &t_Co == 256 || &t_Co == 88 return ' cterm' . a:place . '=' . a:color @@ -169,7 +169,7 @@ function! s:preview_color(linenr) abort \ ' "\_s\zs' . colordef . '\ze\_s"' let hi_attrs_str = '' if !empty(hi_attrs) - if has('gui_running') + if has('gui_running') || &termguicolors let hi_attrs_str = ' gui=' . join(hi_attrs, ',') else let hi_attrs_str = ' cterm=' . join(hi_attrs, ',') @@ -199,11 +199,11 @@ endfunction let b:dc_next_index = 0 -if has('gui_running') +if has('gui_running') || &termguicolors call s:set_guicolors() endif -if has('gui_running') || &t_Co != '' +if has('gui_running') || &termguicolors || &t_Co != '' call s:reset_colors() autocmd CursorMoved,CursorMovedI <buffer> call s:preview_color('.') diff --git a/runtime/syntax/man.vim b/runtime/syntax/man.vim index 0975b160ae..b8e605cb9a 100644 --- a/runtime/syntax/man.vim +++ b/runtime/syntax/man.vim @@ -18,6 +18,10 @@ highlight default link manOptionDesc Constant highlight default link manReference PreProc highlight default link manSubHeading Function +highlight default manUnderline cterm=underline gui=underline +highlight default manBold cterm=bold gui=bold +highlight default manItalic cterm=italic gui=italic + if &filetype != 'man' " May have been included by some other filetype. finish diff --git a/runtime/tutor/en/vim-01-beginner.tutor b/runtime/tutor/en/vim-01-beginner.tutor index 3f243a18fa..dce98d53a4 100644 --- a/runtime/tutor/en/vim-01-beginner.tutor +++ b/runtime/tutor/en/vim-01-beginner.tutor @@ -1,26 +1,26 @@ # Welcome to the VIM Tutor -Vim is a very powerful editor that has many commands, too many to explain in a -tutor such as this. This tutor is designed to describe enough of the commands -that you will be able to easily use Vim as an all-purpose editor. It is -IMPORTANT to remember that this tutor is set up to teach by use. That means -that you need to do the exercises to learn them properly. If you only read -the text, you will soon forget what is most important! +Vim is a very powerful editor that has many commands, too many to explain in +a tutor such as this. This tutor is designed to describe enough of the +commands that you will be able to easily use Vim as an all-purpose editor. +It is IMPORTANT to remember that this tutor is set up to teach by use. That +means that you need to do the exercises to learn them properly. If you only +read the text, you will soon forget what is most important! -For now, make sure that your Shift-Lock key is NOT depressed and press the `j`{normal} -key enough times to move the cursor so that Lesson 0 completely fills the -screen. +For now, make sure that your Shift-Lock key is NOT depressed and press the +`j`{normal} key enough times to move the cursor so that Lesson 0 completely +fills the screen. # Lesson 0 -NOTE: The commands in the lessons will modify the text, but those changes won't -be saved. Don't worry about messing things up; just remember that pressing -[<Esc>](<Esc>) and then [u](u) will undo the latest change. +NOTE: The commands in the lessons will modify the text, but those changes +won't be saved. Don't worry about messing things up; just remember that +pressing [<Esc>](<Esc>) and then [u](u) will undo the latest change. This tutorial is interactive, and there are a few things you should know. -Pressing [<Enter>](<Enter>) over text highlighted [like this](holy-grail) will take you to some -relevant help (hopefully), and pressing K over any word will try to do so too. -Sometimes you will be required to modify text like +Pressing [<Enter>](<Enter>) over text highlighted [like this](holy-grail ) +will take you to some relevant help (hopefully), and pressing K over any +word will try to do so too. Sometimes you will be required to modify text like this here Once you have done the changes correctly, the ✗ sign at the left will change to ✓. I imagine you can already see how neat Vim can be. ;) @@ -33,10 +33,10 @@ or press a sequence of keys <Esc>0f<Space>d3wP$P ~~~ -Text within <'s and >'s (like `<Enter>`{normal}) describes a key to press instead of text -to type. +Text within <'s and >'s (like `<Enter>`{normal}) describes a key to press +instead of text to type. -Now, move to the next lesson (remember, use the `j`{normal} key to scroll down). +Now, move to the next lesson (use the `j`{normal} key to scroll down). ## Lesson 1.1: MOVING THE CURSOR @@ -63,87 +63,90 @@ NOTE: The cursor keys should also work. But using hjkl you will be able to # Lesson 1.2: EXITING VIM -!! NOTE: Before executing any of the steps below, read this entire lesson !! +!! NOTE: Before executing any of the steps below, +read this entire lesson !! - 1. Press the <Esc> key (to make sure you are in [Normal mode](Normal-mode). + 1. Press the <Esc> key (to make sure you are in Normal mode). - 2. Type: + 2. Type: `:q!`{vim} `<Enter>`{normal}. This exits the editor, DISCARDING any changes you have made. - 3. Open vim and get back here by executing the command that got you into this - tutor. That might be: + 3. Open vim and get back here by executing the command that got you into + this tutor. That might be: :Tutor <Enter> - 4. If you have these steps memorized and are confident, execute steps + 4. If you have these steps memorized and are confident, execute steps 1 through 3 to exit and re-enter the editor. NOTE: [:q!](:q) <Enter> discards any changes you made. In a few lessons you will learn how to save the changes to a file. - 5. Move the cursor down to Lesson 1.3. + 5. Move the cursor down to Lesson 1.3. ## Lesson 1.3: TEXT EDITING - DELETION ** Press `x`{normal} to delete the character under the cursor. ** - 1. Move the cursor to the line below marked --->. + 1. Move the cursor to the line below marked --->. - 2. To fix the errors, move the cursor until it is on top of the + 2. To fix the errors, move the cursor until it is on top of the character to be deleted. - 3. Press [the x key](x) to delete the unwanted character. + 3. Press [the x key](x) to delete the unwanted character. - 4. Repeat steps 2 through 4 until the sentence is correct. + 4. Repeat steps 2 through 4 until the sentence is correct. The ccow jumpedd ovverr thhe mooon. - 5. Now that the line is correct, go on to Lesson 1.4. + 5. Now that the line is correct, go on to Lesson 1.4. -NOTE: As you go through this tutor, do not try to memorize, learn by usage. +NOTE: As you go through this tutor, do not try to memorize, learn by + usage. # Lesson 1.4: TEXT EDITING: INSERTION ** Press `i`{normal} to insert text. ** - 1. Move the cursor to the first line below marked --->. + 1. Move the cursor to the first line below marked --->. - 2. To make the first line the same as the second, move the cursor on top + 2. To make the first line the same as the second, move the cursor on top of the first character AFTER where the text is to be inserted. - 3. Press `i`{normal} and type in the necessary additions. + 3. Press `i`{normal} and type in the necessary additions. - 4. As each error is fixed press `<Esc>`{normal} to return to Normal mode. + 4. As each error is fixed press `<Esc>`{normal} to return to Normal mode. Repeat steps 2 through 4 to correct the sentence. There is text misng this . There is some text missing from this line. - 5. When you are comfortable inserting text move to Lesson 1.5. + 5. When you are comfortable inserting text move to Lesson 1.5. # Lesson 1.5: TEXT EDITING: APPENDING ** Press `A`{normal} to append text. ** - 1. Move the cursor to the first line below marked --->. - It does not matter on what character the cursor is in that line. + 1. Move the cursor to the first line below marked --->. + It does not matter on what character the cursor is in that line. - 2. Press [A](A) and type in the necessary additions. + 2. Press [A](A) and type in the necessary additions. - 3. As the text has been appended press `<Esc>`{normal} to return to Normal mode. + 3. As the text has been appended press `<Esc>`{normal} to return to Normal + mode. - 4. Move the cursor to the second line marked ---> and repeat - steps 2 and 3 to correct this sentence. + 4. Move the cursor to the second line marked ---> and repeat + steps 2 and 3 to correct this sentence. There is some text missing from th There is some text missing from this line. There is also some text miss There is also some text missing here. - 5. When you are comfortable appending text move to Lesson 1.6. + 5. When you are comfortable appending text move to Lesson 1.6. # Lesson 1.6: EDITING A FILE @@ -151,49 +154,51 @@ There is also some text missing here. !! NOTE: Before executing any of the steps below, read this entire lesson !! - 1. Exit this tutor as you did in Lesson 1.2: `:q!`{vim} - Or, if you have access to another terminal, do the following there. + 1. Exit this tutor as you did in Lesson 1.2: `:q!`{vim} + Or, if you have access to another terminal, do the following there. - 2. At the shell prompt type this command: + 2. At the shell prompt type this command: ~~~ sh $ vim tutor ~~~ - 'vim' is the command to start the Vim editor, 'tutor' is the name of the - file you wish to edit. Use a file that may be changed. + 'vim' is the command to start the Vim editor, 'tutor' is the name of + the file you wish to edit. Use a file that may be changed. - 3. Insert and delete text as you learned in the previous lessons. + 3. Insert and delete text as you learned in the previous lessons. - 4. Save the file with changes and exit Vim with: + 4. Save the file with changes and exit Vim with: ~~~ cmd :wq ~~~ - Note you'll need to press `<Enter>` to execute the command. + Note you'll need to press `<Enter>` to execute the command. - 5. If you have quit vimtutor in step 1 restart the vimtutor and move down to - the following summary. + 5. If you have quit vimtutor in step 1 restart the vimtutor and move down + to the following summary. - 6. After reading the above steps and understanding them: do it. + 6. After reading the above steps and understanding them: do it. # Lesson 1 SUMMARY - 1. The cursor is moved using either the arrow keys or the hjkl keys. + 1. The cursor is moved using either the arrow keys or the hjkl keys. h (left) j (down) k (up) l (right) - 2. To start Vim from the shell prompt type: + 2. To start Vim from the shell prompt type: ~~~ sh $ vim FILENAME ~~~ - 3. To exit Vim type: `<Esc>`{normal} `:q!`{vim} `<Enter>`{normal} to trash all changes. - OR type: `<Esc>`{normal} `:wq`{vim} `<Enter>`{normal} to save the changes. + 3. To exit Vim type: `<Esc>`{normal} `:q!`{vim} `<Enter>`{normal} to trash + all changes. + OR type: `<Esc>`{normal} `:wq`{vim} `<Enter>`{normal} to save + the changes. - 4. To delete the character at the cursor type: `x`{normal} + 4. To delete the character at the cursor type: `x`{normal} - 5. To insert or append text type: - `i`{normal} insert text `<Esc>`{normal} insert before the cursor. - `A`{normal} append text `<Esc>`{normal} append after the line. + 5. To insert or append text type: + `i`{normal} insert text `<Esc>`{normal} insert before the cursor. + `A`{normal} append text `<Esc>`{normal} append after the line. NOTE: Pressing `<Esc>`{normal} will place you in Normal mode or will cancel an unwanted and partially completed command. @@ -204,38 +209,38 @@ Now continue with Lesson 2. ** Type `dw`{normal} to delete a word. ** - 1. Press `<Esc>`{normal} to make sure you are in Normal mode. + 1. Press `<Esc>`{normal} to make sure you are in Normal mode. - 2. Move the cursor to the line below marked --->. + 2. Move the cursor to the line below marked --->. - 3. Move the cursor to the beginning of a word that needs to be deleted. + 3. Move the cursor to the beginning of a word that needs to be deleted. - 4. Type [d](d)[w](w) to make the word disappear. + 4. Type [d](d)[w](w) to make the word disappear. There are a some words fun that don't belong paper in this sentence. - 5. Repeat steps 3 and 4 until the sentence is correct and go to Lesson 2.2. + 5. Repeat steps 3 and 4 until the sentence is correct and go to Lesson 2.2. # Lesson 2.2: MORE DELETION COMMANDS ** Type `d$`{normal} to delete to the end of the line. ** - 1. Press `<Esc>`{normal} to make sure you are in Normal mode. + 1. Press `<Esc>`{normal} to make sure you are in Normal mode. - 2. Move the cursor to the line below marked --->. + 2. Move the cursor to the line below marked --->. - 3. Move the cursor to the end of the correct line (AFTER the first . ). + 3. Move the cursor to the end of the correct line (AFTER the first . ). - 4. Type `d$`{normal} to delete to the end of the line. + 4. Type `d$`{normal} to delete to the end of the line. Somebody typed the end of this line twice. end of this line twice. - 5. Move on to Lesson 2.3 to understand what is happening. + 5. Move on to Lesson 2.3 to understand what is happening. # Lesson 2.3: ON OPERATORS AND MOTIONS - -Many commands that change text are made from an [operator](operator) and a [motion](navigation). +Many commands that change text are made from an [operator](operator) and +a [motion](navigation). The format for a delete command with the [d](d) delete operator is as follows: d motion @@ -251,26 +256,26 @@ The format for a delete command with the [d](d) delete operator is as follows: Thus typing `de`{normal} will delete from the cursor to the end of the word. -NOTE: Pressing just the motion while in Normal mode without an operator will - move the cursor as specified. +NOTE: Pressing just the motion while in Normal mode without an operator + will move the cursor as specified. # Lesson 2.4: USING A COUNT FOR A MOTION ** Typing a number before a motion repeats it that many times. ** - 1. Move the cursor to the start of the line marked ---> below. + 1. Move the cursor to the start of the line marked ---> below. - 2. Type `2w`{normal} to move the cursor two words forward. + 2. Type `2w`{normal} to move the cursor two words forward. - 3. Type `3e`{normal} to move the cursor to the end of the third word forward. + 3. Type `3e`{normal} to move the cursor to the end of the third word forward. - 4. Type `0`{normal} ([zero](0)) to move to the start of the line. + 4. Type `0`{normal} ([zero](0)) to move to the start of the line. - 5. Repeat steps 2 and 3 with different numbers. + 5. Repeat steps 2 and 3 with different numbers. This is just a line with words you can move around in. - 6. Move on to Lesson 2.5. + 6. Move on to Lesson 2.5. # Lesson 2.5: USING A COUNT TO DELETE MORE @@ -280,12 +285,12 @@ In the combination of the delete operator and a motion mentioned above you insert a count before the motion to delete more: d number motion - 1. Move the cursor to the first UPPER CASE word in the line marked --->. + 1. Move the cursor to the first UPPER CASE word in the line marked --->. - 2. Type `d2w`{normal} to delete the two UPPER CASE words + 2. Type `d2w`{normal} to delete the two UPPER CASE words - 3. Repeat steps 1 and 2 with a different count to delete the consecutive - UPPER CASE words with one command + 3. Repeat steps 1 and 2 with a different count to delete the consecutive + UPPER CASE words with one command This ABC DE line FGHI JK LMN OP of words is Q RS TUV cleaned up. @@ -293,13 +298,13 @@ This ABC DE line FGHI JK LMN OP of words is Q RS TUV cleaned up. ** Type `dd`{normal} to delete a whole line. ** - Due to the frequency of whole line deletion, the designers of Vi decided - it would be easier to simply type two d's to delete a line. +Due to the frequency of whole line deletion, the designers of Vi decided +it would be easier to simply type two d's to delete a line. - 1. Move the cursor to the second line in the phrase below. - 2. Type [dd](dd) to delete the line. - 3. Now move to the fourth line. - 4. Type `2dd`{normal} to delete two lines. + 1. Move the cursor to the second line in the phrase below. + 2. Type [dd](dd) to delete the line. + 3. Now move to the fourth line. + 4. Type `2dd`{normal} to delete two lines. 1) Roses are red, 2) Mud is fun, @@ -313,54 +318,56 @@ This ABC DE line FGHI JK LMN OP of words is Q RS TUV cleaned up. ** Press `u`{normal} to undo the last commands, `U`{normal} to fix a whole line. ** - 1. Move the cursor to the line below marked ---> and place it on the - first error. - 2. Type `x`{normal} to delete the first unwanted character. - 3. Now type `u`{normal} to undo the last command executed. - 4. This time fix all the errors on the line using the `x`{normal} command. - 5. Now type a capital `U`{normal} to return the line to its original state. - 6. Now type `u`{normal} a few times to undo the `U`{normal} and preceding commands. - 7. Now type `<Ctrl-r>`{normal} a few times to redo the commands (undo the undo's). + 1. Move the cursor to the line below marked ---> and place it on the + first error. + 2. Type `x`{normal} to delete the first unwanted character. + 3. Now type `u`{normal} to undo the last command executed. + 4. This time fix all the errors on the line using the `x`{normal} command. + 5. Now type a capital `U`{normal} to return the line to its original state. + 6. Now type `u`{normal} a few times to undo the `U`{normal} and preceding + commands. + 7. Now type `<C-r>`{normal} (Control + R) a few times to redo the commands + (undo the undos). Fiix the errors oon thhis line and reeplace them witth undo. - 8. These are very useful commands. Now move on to the Lesson 2 Summary. + 8. These are very useful commands. Now move on to the Lesson 2 Summary. # Lesson 2 SUMMARY - 1. To delete from the cursor up to the next word type: `dw`{normal} - 2. To delete from the cursor to the end of a line type: `d$`{normal} - 3. To delete a whole line type: `dd`{normal} - 4. To repeat a motion prepend it with a number: `2w`{normal} + 1. To delete from the cursor up to the next word type: `dw`{normal} + 2. To delete from the cursor to the end of a line type: `d$`{normal} + 3. To delete a whole line type: `dd`{normal} + 4. To repeat a motion prepend it with a number: `2w`{normal} - 5. The format for a change command is: + 5. The format for a change command is: operator [number] motion - where: + where: operator - is what to do, such as [d](d) for delete [number] - is an optional count to repeat the motion motion - moves over the text to operate on, such as: [w](w) (word), [$]($) (to the end of line), etc. - 6. To move to the start of the line use a zero: [0](0) + 6. To move to the start of the line use a zero: [0](0) - 7. To undo previous actions, type: `u`{normal} (lowercase u) - To undo all the changes on a line, type: `U`{normal} (capital U) - To undo the undo's, type: `<Ctrl-r>`{normal} + 7. To undo previous actions, type: `u`{normal} (lowercase u) + To undo all the changes on a line, type: `U`{normal} (capital U) + To undo the undo's, type: `<C-r>`{normal} # Lesson 3.1: THE PUT COMMAND ** Type `p`{normal} to put previously deleted text after the cursor. ** - 1. Move the cursor to the first ---> line below. + 1. Move the cursor to the first ---> line below. - 2. Type `dd`{normal} to delete the line and store it in a Vim register. + 2. Type `dd`{normal} to delete the line and store it in a Vim register. - 3. Move the cursor to the c) line, ABOVE where the deleted line should go. + 3. Move the cursor to the c) line, ABOVE where the deleted line should go. - 4. Type `p`{normal} to put the line below the cursor. + 4. Type `p`{normal} to put the line below the cursor. - 5. Repeat steps 2 through 4 to put all the lines in correct order. + 5. Repeat steps 2 through 4 to put all the lines in correct order. d) Can you learn too? b) Violets are blue, @@ -371,18 +378,18 @@ a) Roses are red, ** Type `rx`{normal} to replace the character at the cursor with x. ** - 1. Move the cursor to the first line below marked --->. + 1. Move the cursor to the first line below marked --->. - 2. Move the cursor so that it is on top of the first error. + 2. Move the cursor so that it is on top of the first error. - 3. Type `r`{normal} and then the character which should be there. + 3. Type `r`{normal} and then the character which should be there. - 4. Repeat steps 2 and 3 until the first line is equal to the second one. + 4. Repeat steps 2 and 3 until the first line is equal to the second one. Whan this lime was tuoed in, someone presswd some wrojg keys! When this line was typed in, someone pressed some wrong keys! - 5. Now move on to Lesson 3.3. + 5. Now move on to Lesson 3.3. NOTE: Remember that you should be learning by doing, not memorization. @@ -390,15 +397,16 @@ NOTE: Remember that you should be learning by doing, not memorization. ** To change until the end of a word, type `ce`{normal}. ** - 1. Move the cursor to the first line below marked --->. + 1. Move the cursor to the first line below marked --->. - 2. Place the cursor on the "u" in "lubw". + 2. Place the cursor on the "u" in "lubw". - 3. Type `ce`{normal} and the correct word (in this case, type "ine" ). + 3. Type `ce`{normal} and the correct word (in this case, type "ine" ). - 4. Press `<Esc>`{normal} and move to the next character that needs to be changed. + 4. Press `<Esc>`{normal} and move to the next character that needs to be + changed. - 5. Repeat steps 3 and 4 until the first sentence is the same as the second. + 5. Repeat steps 3 and 4 until the first sentence is the same as the second. This lubw has a few wptfd that mrrf changing usf the change operator. This line has a few words that need changing using the change operator. @@ -409,17 +417,17 @@ Notice that [c](c)e deletes the word and places you in Insert mode. ** The change operator is used with the same motions as delete. ** - 1. The change operator works in the same way as delete. The format is: + 1. The change operator works in the same way as delete. The format is: c [number] motion - 2. The motions are the same, such as `w`{normal} (word) and `$`{normal} (end of line). + 2. The motions are the same, such as `w`{normal} (word) and `$`{normal} (end of line). - 3. Move to the first line below marked --->. + 3. Move to the first line below marked --->. - 4. Move the cursor to the first error. + 4. Move the cursor to the first error. - 5. Type `c$`{normal} and type the rest of the line like the second and press `<Esc>`{normal}. + 5. Type `c$`{normal} and type the rest of the line like the second and press `<Esc>`{normal}. The end of this line needs some help to make it like the second. The end of this line needs to be corrected using the `c$`{normal} command. @@ -428,18 +436,18 @@ NOTE: You can use the Backspace key to correct mistakes while typing. # Lesson 3 SUMMARY - 1. To put back text that has just been deleted, type [p](p). This puts the - deleted text AFTER the cursor (if a line was deleted it will go on the - line below the cursor). + 1. To put back text that has just been deleted, type [p](p). This puts the + deleted text AFTER the cursor (if a line was deleted it will go on the + line below the cursor). - 2. To replace the character under the cursor, type [r](r) and then the - character you want to have there. + 2. To replace the character under the cursor, type [r](r) and then the + character you want to have there. - 3. The [change operator](c) allows you to change from the cursor to where the - motion takes you. Type `ce`{normal} to change from the cursor to the end of - the word, `c$`{normal} to change to the end of a line. + 3. The [change operator](c) allows you to change from the cursor to where + the motion takes you. Type `ce`{normal} to change from the cursor to the + end of the word, `c$`{normal} to change to the end of a line. - 4. The format for change is: + 4. The format for change is: c [number] motion @@ -447,42 +455,44 @@ Now go on to the next lesson. # Lesson 4.1: CURSOR LOCATION AND FILE STATUS -** Type `<Ctrl-g>`{normal} to show your location in the file and the file status. +** Type `<C-g>`{normal} to show your location in a file and the file status. Type `G`{normal} to move to a line in the file. ** NOTE: Read this entire lesson before executing any of the steps!! - 1. Hold down the `<Ctrl>`{normal} key and press `g`{normal}. We call this `<Ctrl-g>`{normal}. - A message will appear at the bottom of the page with the filename and the - position in the file. Remember the line number for Step 3. + 1. Hold down the `<Ctrl>`{normal} key and press `g`{normal}. We call this + `<C-g>`{normal}. A message will appear at the bottom of the page with the + filename and the position in the file. Remember the line number for + Step 3. -NOTE: You may see the cursor position in the lower right corner of the screen - This happens when the ['ruler']('ruler') option is set (see `:help 'ruler'`{vim} ). +NOTE: You may see the cursor position in the lower right corner of the + screen. This happens when the ['ruler']('ruler') option is set. + 2. Press [G](G) to move you to the bottom of the file. + Type [gg](gg) to move you to the start of the file. - 2. Press [G](G) to move you to the bottom of the file. - Type [gg](gg) to move you to the start of the file. + 3. Type the number of the line you were on and then `G`{normal}. This will + return you to the line you were on when you first pressed `<C-g>`{normal}. - 3. Type the number of the line you were on and then `G`{normal}. This will - return you to the line you were on when you first pressed `<Ctrl-g>`{normal}. - - 4. If you feel confident to do this, execute steps 1 through 3. + 4. If you feel confident to do this, execute steps 1 through 3. # Lesson 4.2: THE SEARCH COMMAND ** Type `/`{normal} followed by a phrase to search for the phrase. ** - 1. In Normal mode type the `/`{normal} character. Notice that it and the cursor - appear at the bottom of the screen as with the `:`{normal} command. + 1. In Normal mode type the `/`{normal} character. Notice that it and the + cursor appear at the bottom of the screen as with the `:`{normal} command. - 2. Now type 'errroor' `<Enter>`{normal}. This is the word you want to search for. + 2. Now type 'errroor' `<Enter>`{normal}. This is the word you want to search + for. - 3. To search for the same phrase again, simply type [n](n). - To search for the same phrase in the opposite direction, type [N](N). + 3. To search for the same phrase again, simply type [n](n). + To search for the same phrase in the opposite direction, type [N](N). - 4. To search for a phrase in the backward direction, use [?](?) instead of `/`{normal}. + 4. To search for a phrase in the backward direction, use [?](?) instead + of `/`{normal}. - 5. To go back to where you came from press `<Ctrl-o>`{normal} (keep `<Ctrl>`{normal} pressed down while - pressing the letter `o`{normal}). Repeat to go back further. `<Ctrl-i>`{normal} goes forward. + 5. To go back to where you came from press `<C-o>`{normal} (keep `<Ctrl>`{normal} pressed down while pressing the letter `o`{normal}). Repeat to go back + further. `<C-i>`{normal} goes forward. "errroor" is not the way to spell error; errroor is an error. @@ -493,15 +503,15 @@ NOTE: When the search reaches the end of the file it will continue at the ** Type `%`{normal} to find a matching ),], or }. ** - 1. Place the cursor on any (, [, or { in the line below marked --->. + 1. Place the cursor on any (, [, or { in the line below marked --->. - 2. Now type the [%](%) character. + 2. Now type the [%](%) character. - 3. The cursor will move to the matching parenthesis or bracket. + 3. The cursor will move to the matching parenthesis or bracket. - 4. Type `%`{normal} to move the cursor to the other matching bracket. + 4. Type `%`{normal} to move the cursor to the other matching bracket. - 5. Move the cursor to another (,),[,],{ or } and see what `%`{normal} does. + 5. Move the cursor to another (,),[,],{ or } and see what `%`{normal} does. This ( is a test line with ('s, ['s ] and {'s } in it. )) @@ -511,75 +521,79 @@ NOTE: This is very useful in debugging a program with unmatched parentheses! ** Type `:s/old/new/g` to substitute "new" for "old". ** - 1. Move the cursor to the line below marked --->. + 1. Move the cursor to the line below marked --->. - 2. Type + 2. Type ~~~ cmd :s/thee/the/ ~~~ NOTE that the [:s](:s) command only changed the first occurrence of "thee" in the line. - 3. Now type + 3. Now type ~~~ cmd :s/thee/the/g ~~~ - Adding the g [flag](:s_flags) means to substitute globally in the line, change - all occurrences of "thee" in the line. + Adding the g [flag](:s_flags) means to substitute globally in the line, + change all occurrences of "thee" in the line. Usually thee best time to see thee flowers is in thee spring. - 4. To change every occurrence of a character string between two lines, type + 4. To change every occurrence of a character string between two lines, type ~~~ cmd :#,#s/old/new/g ~~~ - where #,# are the line numbers of the range of lines where the substitution is to be done. + where #,# are the line numbers of the range of lines where the + substitution is to be done. - Type + Type ~~~ cmd - :%s/old/new/g + :%s/old/new/g ~~~ - to change every occurrence in the whole file. + to change every occurrence in the whole file. - Type + Type ~~~ cmd - :%s/old/new/gc + :%s/old/new/gc ~~~ - to find every occurrence in the whole file, with a prompt whether to substitute or not. + to find every occurrence in the whole file, with a prompt whether to + substitute or not. # Lesson 4 SUMMARY - 1. `<Ctrl-g>`{normal} displays your location in the file and the file status. - `G`{normal} moves to the end of the file. - number `G`{normal} moves to that line number. - `gg`{normal} moves to the first line. + 1. `<C-g>`{normal} displays your location and the file status. + `G`{normal} moves to the end of the file. + number `G`{normal} moves to that line number. + `gg`{normal} moves to the first line. - 2. Typing `/`{normal} followed by a phrase searches FORWARD for the phrase. - Typing `?`{normal} followed by a phrase searches BACKWARD for the phrase. - After a search type `n`{normal} to find the next occurrence in the same direction - or `N`{normal} to search in the opposite direction. - `<Ctrl-o>`{normal} takes you back to older positions, `<Ctrl-i>`{normal} to newer positions. + 2. Typing `/`{normal} followed by a phrase searches FORWARD for the phrase. + Typing `?`{normal} followed by a phrase searches BACKWARD for the phrase. + After a search type `n`{normal} to find the next occurrence in the same + direction or `N`{normal} to search in the opposite direction. + `<C-o>`{normal} takes you back to older positions, `<C-i>`{normal} to + newer positions. - 3. Typing `%`{normal} while the cursor is on a (,),[,],{, or } goes to its match. + 3. Typing `%`{normal} while the cursor is on a (,),[,],{, or } goes to its + match. - 4. To substitute new for the first old in a line type + 4. To substitute new for the first old in a line type ~~~ cmd :s/old/new ~~~ - To substitute new for all 'old's on a line type + To substitute new for all 'old's on a line type ~~~ cmd :s/old/new/g ~~~ - To substitute phrases between two line #'s type + To substitute phrases between two line #'s type ~~~ cmd :#,#s/old/new/g ~~~ - To substitute all occurrences in the file type + To substitute all occurrences in the file type ~~~ cmd :%s/old/new/g ~~~ - To ask for confirmation each time add 'c' + To ask for confirmation each time add 'c' ~~~ cmd :%s/old/new/gc ~~~ @@ -588,15 +602,15 @@ Usually thee best time to see thee flowers is in thee spring. ** Type `:!`{vim} followed by an external command to execute that command. ** - 1. Type the familiar command `:`{normal} to set the cursor at the bottom of the - screen. This allows you to enter a command-line command. + 1. Type the familiar command `:`{normal} to set the cursor at the bottom of + the screen. This allows you to enter a command-line command. - 2. Now type the [!](!cmd) (exclamation point) character. This allows you to - execute any external shell command. + 2. Now type the [!](!cmd) (exclamation point) character. This allows you to + execute any external shell command. - 3. As an example type "ls" following the "!" and then hit `<Enter>`{normal}. This - will show you a listing of your directory, just as if you were at the - shell prompt. + 3. As an example type "ls" following the "!" and then hit `<Enter>`{normal}. + This will show you a listing of your directory, just as if you were + at the shell prompt. NOTE: It is possible to execute any external command this way, also with arguments. @@ -608,24 +622,24 @@ NOTE: All `:`{vim} commands must be finished by hitting `<Enter>`{normal}. ** To save the changes made to the text, type `:w`{vim} FILENAME. ** - 1. Type `:!ls`{vim} to get a listing of your directory. - You already know you must hit `<Enter>`{normal} after this. + 1. Type `:!ls`{vim} to get a listing of your directory. + You already know you must hit `<Enter>`{normal} after this. - 2. Choose a filename that does not exist yet, such as TEST. + 2. Choose a filename that does not exist yet, such as TEST. - 3. Now type: + 3. Now type: ~~~ cmd :w TEST ~~~ - (where TEST is the filename you chose.) + (where TEST is the filename you chose.) - 4. This saves the whole file (the Vim Tutor) under the name TEST. - To verify this, type `:!ls`{vim} again to see your directory. + 4. This saves the whole file (the Vim Tutor) under the name TEST. + To verify this, type `:!ls`{vim} again to see your directory. NOTE: If you were to exit Vim and start it again with `nvim TEST`, the file would be an exact copy of the tutor when you saved it. - 5. Now remove the file by typing: + 5. Now remove the file by typing: ~~~ cmd :!rm TEST ~~~ @@ -634,53 +648,52 @@ NOTE: If you were to exit Vim and start it again with `nvim TEST`, the file ** To save part of the file, type `v`{normal} motion `:w FILENAME`{vim}. ** - 1. Move the cursor to this line. + 1. Move the cursor to this line. - 2. Press [v](v) and move the cursor to the fifth item below. Notice that the - text is highlighted. + 2. Press [v](v) and move the cursor to the fifth item below. Notice that the + text is highlighted. - 3. Press the `:`{normal} character. At the bottom of the screen + 3. Press the `:`{normal} character. At the bottom of the screen :'<,'> will appear. - 4. Type + 4. Type `:w TEST`{vim} - where TEST is a filename that does not exist yet. Verify that you see + where TEST is a filename that does not exist yet. Verify that you see `:'<,'>w TEST`{vim} - before you press `<Enter>`{normal}. + before you press `<Enter>`{normal}. - 5. Vim will write the selected lines to the file TEST. Use `:!ls`{vim} to see it. - Do not remove it yet! We will use it in the next lesson. + 5. Vim will write the selected lines to the file TEST. Use `:!ls`{vim} to see it. Do not remove it yet! We will use it in the next lesson. NOTE: Pressing [v](v) starts [Visual selection](visual-mode). You can move the cursor around to make the selection bigger or smaller. Then you can - use an operator to do something with the text. For example, `d`{normal} deletes - the text. + use an operator to do something with the text. For example, `d`{normal} + deletes the text. # Lesson 5.4: RETRIEVING AND MERGING FILES ** To insert the contents of a file, type `:r FILENAME`{vim}. ** - 1. Place the cursor just above this line. + 1. Place the cursor just above this line. NOTE: After executing Step 2 you will see text from Lesson 5.3. Then move DOWN to see this lesson again. - 2. Now retrieve your TEST file using the command + 2. Now retrieve your TEST file using the command `:r TEST`{vim} where TEST is the name of the file you used. The file you retrieve is placed below the cursor line. - 3. To verify that a file was retrieved, cursor back and notice that there - are now two copies of Lesson 5.3, the original and the file version. + 3. To verify that a file was retrieved, cursor back and notice that there + are now two copies of Lesson 5.3, the original and the file version. NOTE: You can also read the output of an external command. For example, @@ -690,39 +703,39 @@ NOTE: You can also read the output of an external command. For example, # Lesson 5 SUMMARY - 1. [:!command](:!cmd) executes an external command. + 1. [:!command](:!cmd) executes an external command. Some useful examples are: `:!ls`{vim} - shows a directory listing `:!rm FILENAME`{vim} - removes file FILENAME - 2. [:w](:w) FILENAME writes the current Vim file to disk with - name FILENAME. + 2. [:w](:w) FILENAME writes the current Vim file to disk with + name FILENAME. - 3. [v](v) motion :w FILENAME saves the Visually selected lines in file - FILENAME. + 3. [v](v) motion :w FILENAME saves the Visually selected lines in file + FILENAME. - 4. [:r](:r) FILENAME retrieves disk file FILENAME and puts it - below the cursor position. + 4. [:r](:r) FILENAME retrieves disk file FILENAME and puts it + below the cursor position. - 5. [:r !dir](:r!) reads the output of the dir command and - puts it below the cursor position. + 5. [:r !dir](:r!) reads the output of the dir command and + puts it below the cursor position. # Lesson 6.1: THE OPEN COMMAND ** Type `o`{normal} to open a line below the cursor and place you in Insert mode. ** - 1. Move the cursor to the line below marked --->. + 1. Move the cursor to the line below marked --->. - 2. Type the lowercase letter `o`{normal} to [open](o) up a line BELOW the cursor and place - you in Insert mode. + 2. Type the lowercase letter `o`{normal} to [open](o) up a line BELOW the + cursor and place you in Insert mode. - 3. Now type some text and press `<Esc>`{normal} to exit Insert mode. + 3. Now type some text and press `<Esc>`{normal} to exit Insert mode. After typing `o`{normal} the cursor is placed on the open line in Insert mode. - 4. To open up a line ABOVE the cursor, simply type a [capital O](O), rather - than a lowercase `o`{normal}. Try this on the line below. + 4. To open up a line ABOVE the cursor, simply type a [capital O](O), rather + than a lowercase `o`{normal}. Try this on the line below. Open up a line above this by typing O while the cursor is on this line. @@ -730,37 +743,39 @@ Open up a line above this by typing O while the cursor is on this line. ** Type `a`{normal} to insert text AFTER the cursor. ** - 1. Move the cursor to the start of the line below marked --->. + 1. Move the cursor to the start of the line below marked --->. - 2. Press `e`{normal} until the cursor is on the end of "li". + 2. Press `e`{normal} until the cursor is on the end of "li". - 3. Type the lowercase letter `a`{normal} to [append](a) text AFTER the cursor. + 3. Type the lowercase letter `a`{normal} to [append](a) text AFTER the + cursor. - 4. Complete the word like the line below it. Press `<Esc>`{normal} to exit Insert - mode. + 4. Complete the word like the line below it. Press `<Esc>`{normal} to exit + Insert mode. - 5. Use `e`{normal} to move to the next incomplete word and repeat steps 3 and 4. + 5. Use `e`{normal} to move to the next incomplete word and repeat steps 3 + and 4. This li will allow you to pract appendi text to a line. This line will allow you to practice appending text to a line. -NOTE: [a](a), [i](i) and [A](A) all go to the same Insert mode, the only difference is where - the characters are inserted. +NOTE: [a](a), [i](i) and [A](A) all go to the same Insert mode, the only + difference is where the characters are inserted. # Lesson 6.3: ANOTHER WAY TO REPLACE ** Type a capital `R`{normal} to replace more than one character. ** - 1. Move the cursor to the first line below marked --->. Move the cursor to + 1. Move the cursor to the first line below marked --->. Move the cursor to the beginning of the first "xxx". - 2. Now press `R`{normal} ([capital R](R)) and type the number below it in the second line, so that it - replaces the "xxx". + 2. Now press `R`{normal} ([capital R](R)) and type the number below it in the + second line, so that it replaces the "xxx". - 3. Press `<Esc>`{normal} to leave [Replace mode](mode-replace). Notice that the rest of the line - remains unmodified. + 3. Press `<Esc>`{normal} to leave [Replace mode](mode-replace). Notice that + the rest of the line remains unmodified. - 4. Repeat the steps to replace the remaining "xxx". + 4. Repeat the steps to replace the remaining "xxx". Adding 123 to xxx gives you xxx. Adding 123 to 456 gives you 579. @@ -772,51 +787,53 @@ NOTE: Replace mode is like Insert mode, but every typed character deletes an ** Use the `y`{normal} operator to copy text and `p`{normal} to paste it. ** - 1. Go to the line marked with ---> below and place the cursor after "a)". + 1. Go to the line marked with ---> below and place the cursor after "a)". - 2. Start Visual mode with `v`{normal} and move the cursor to just before "first". + 2. Start Visual mode with `v`{normal} and move the cursor to just before + "first". - 3. Type `y`{normal} to [yank](yank) (copy) the highlighted text. + 3. Type `y`{normal} to [yank](yank) (copy) the highlighted text. - 4. Move the cursor to the end of the next line: `j$`{normal} + 4. Move the cursor to the end of the next line: `j$`{normal} - 5. Type `p`{normal} to [put](put) (paste) the text. + 5. Type `p`{normal} to [put](put) (paste) the text. - 6. Press `a`{normal} and then type "second". Press `<Esc>`{normal} to leave Insert mode. + 6. Press `a`{normal} and then type "second". Press `<Esc>`{normal} to leave + Insert mode. - 7. Use Visual mode to select " item.", yank it with `y`{normal}, move to the end of - the next line with `j$`{normal} and put the text there with `p`{normal}. + 7. Use Visual mode to select "item.", yank it with `y`{normal}, move to the + end of the next line with `j$`{normal} and put the text there with `p`{normal} a) This is the first item. b) -NOTE: you can also use `y`{normal} as an operator; `yw`{normal} yanks one word. +NOTE: you can use `y`{normal} as an operator: `yw`{normal} yanks one word. # Lesson 6.5: SET OPTION ** Set an option so a search or substitute ignores case. ** - 1. Search for 'ignore' by entering: `/ignore` - Repeat several times by pressing `n`{normal}. + 1. Search for 'ignore' by entering: `/ignore` + Repeat several times by pressing `n`{normal}. - 2. Set the 'ic' (Ignore case) option by entering: + 2. Set the 'ic' (Ignore case) option by entering: ~~~ cmd :set ic ~~~ - 3. Now search for 'ignore' again by pressing `n`{normal}. - Notice that Ignore and IGNORE are now also found. + 3. Now search for 'ignore' again by pressing `n`{normal}. + Notice that Ignore and IGNORE are now also found. - 4. Set the 'hlsearch' and 'incsearch' options: + 4. Set the 'hlsearch' and 'incsearch' options: ~~~ cmd :set hls is ~~~ - 5. Now type the search command again and see what happens: /ignore <Enter> + 5. Now type the search command again and see what happens: /ignore <Enter> - 6. To disable ignoring case enter: + 6. To disable ignoring case enter: ~~~ cmd :set noic ~~~ - 7. To toggle the value of a setting, prepend it with "inv": + 7. To toggle the value of a setting, prepend it with "inv": ~~~ cmd :set invic ~~~ @@ -829,19 +846,20 @@ NOTE: If you want to ignore case for just one search command, use [\c](/\c) # Lesson 6 SUMMARY - 1. Type `o`{normal} to open a line BELOW the cursor and start Insert mode. - Type `O`{normal} to open a line ABOVE the cursor. + 1. Type `o`{normal} to open a line BELOW the cursor and start Insert mode. + Type `O`{normal} to open a line ABOVE the cursor. - 2. Type `a`{normal} to insert text AFTER the cursor. - Type `A`{normal} to insert text after the end of the line. + 2. Type `a`{normal} to insert text AFTER the cursor. + Type `A`{normal} to insert text after the end of the line. - 3. The `e`{normal} command moves to the end of a word. + 3. The `e`{normal} command moves to the end of a word. - 4. The `y`{normal} operator yanks (copies) text, `p`{normal} puts (pastes) it. + 4. The `y`{normal} operator copies text, `p`{normal} pastes it. - 5. Typing a capital `R`{normal} enters Replace mode until `<Esc>`{normal} is pressed. + 5. Typing a capital `R`{normal} enters Replace mode until `<Esc>`{normal} is + pressed. - 6. Typing "[:set](:set) xxx" sets the option "xxx". Some options are: + 6. Typing "[:set](:set) xxx" sets the option "xxx". Some options are: 'ic' 'ignorecase' ignore upper/lower case when searching 'is' 'incsearch' show partial matches for a search phrase @@ -849,11 +867,11 @@ NOTE: If you want to ignore case for just one search command, use [\c](/\c) You can either use the long or the short option name. - 7. Prepend "no" to switch an option off: + 7. Prepend "no" to switch an option off: ~~~ cmd :set noic ~~~ - 8. Prepend "inv" to toggle an option: + 8. Prepend "inv" to toggle an option: ~~~ cmd :set invic ~~~ @@ -870,7 +888,7 @@ these three: `:help`{vim} Read the text in the help window to find out how the help works. -Type `<Ctrl-w><Ctrl-w>`{normal} to jump from one window to another. +Type `<C-w><C-w>`{normal} to jump from one window to another. Type `:q`{vim} to close the help window. You can find help on just about any subject, by giving an argument to the @@ -888,13 +906,13 @@ You can find help on just about any subject, by giving an argument to the Vim has many more features than Vi, but most of them are disabled by default. To start using more features you have to create a "vimrc" file. - 1. Start editing the "vimrc" file. This depends on your system: + 1. Start editing the "vimrc" file. This depends on your system: `:e ~/.config/nvim/init.vim`{vim} for Unix-like systems - 2. Now read the example "vimrc" file contents: + 2. Now read the example "vimrc" file contents: `:r $VIMRUNTIME/vimrc_example.vim`{vim} - 3. Write the file with: + 3. Write the file with: `:w`{vim} The next time you start Vim it will use syntax highlighting. @@ -903,36 +921,38 @@ default. To start using more features you have to create a "vimrc" file. # Lesson 7.3: COMPLETION -** Command line completion with `<Ctrl-d>`{normal} and `<Tab>`{normal}. ** +** Command line completion with `<C-d>`{normal} and `<Tab>`{normal}. ** - 1. Look what files exist in the directory: `:!ls`{vim} + 1. Look what files exist in the directory: `:!ls`{vim} - 2. Type the start of a command: `:e`{vim} + 2. Type the start of a command: `:e`{vim} - 3. Press `<Ctrl-d>`{normal} and Vim will show a list of commands that start with "e". + 3. Press `<C-d>`{normal} and Vim will show a list of commands that start + with "e". - 4. Press `<Tab>`{normal} and Vim will complete the command name to ":edit". + 4. Press `<Tab>`{normal} and Vim will complete the command name to ":edit". - 5. Now add a space and the start of an existing file name: `:edit FIL`{vim} + 5. Now add a space and the start of an existing file name: `:edit FIL`{vim} - 6. Press `<Tab>`{normal}. Vim will complete the name (if it is unique). + 6. Press `<Tab>`{normal}. Vim will complete the name (if it is unique). -NOTE: Completion works for many commands. It is especially useful for `:help`{vim}. +NOTE: Completion works for many commands. It is especially useful for + `:help`{vim}. # Lesson 7 SUMMARY - 1. Type `:help`{vim} - or press `<F1>`{normal} or `<Help>`{normal} to open a help window. + 1. Type `:help`{vim} + or press `<F1>`{normal} or `<Help>`{normal} to open a help window. - 2. Type `:help TOPIC`{vim} to find help on TOPIC. + 2. Type `:help TOPIC`{vim} to find help on TOPIC. - 3. Type `<Ctrl-w><Ctrl-w>`{normal} to jump to another window + 3. Type `<C-w><C-w>`{normal} to jump to another window - 4. Type `:q`{vim} to close the help window + 4. Type `:q`{vim} to close the help window - 5. Create a vimrc startup script to keep your preferred settings. + 5. Create a vimrc startup script to keep your preferred settings. - 6. While in command mode, press `<Ctrl-d>`{normal} to see possible completions. + 6. While in command mode, press `<C-d>`{normal} to see possible completions. Press `<Tab>`{normal} to use one completion. # CONCLUSION @@ -941,7 +961,8 @@ This was intended to give a brief overview of the Vim editor, just enough to allow you to use the editor fairly easily. It is far from complete as Vim has many many more commands. Consult the help often. -There are many resources online to learn more about vim. Here's a bunch of them: +There are many resources online to learn more about vim. Here's a bunch of +them: - *Learn Vim Progressively*: http://yannesposito.com/Scratch/en/blog/Learn-Vim-Progressively/ - *Learning Vim in 2014*: http://benmccormick.org/learning-vim-in-2014/ @@ -951,8 +972,8 @@ There are many resources online to learn more about vim. Here's a bunch of them: - *7 Habits of Effective Text Editing*: http://www.moolenaar.net/habits.html - *vim-galore*: https://github.com/mhinz/vim-galore -If you prefer a book, *Practical Vim* by Drew Neil is recommended often (the sequel, *Modern -Vim*, includes material specific to nvim). +If you prefer a book, *Practical Vim* by Drew Neil is recommended often +(the sequel, *Modern Vim*, includes material specific to nvim). This tutorial was written by Michael C. Pierce and Robert K. Ware, Colorado School of Mines using ideas supplied by Charles Smith, Colorado State diff --git a/runtime/tutor/en/vim-01-beginner.tutor.json b/runtime/tutor/en/vim-01-beginner.tutor.json index 444bd7c4b7..2f87d7543f 100644 --- a/runtime/tutor/en/vim-01-beginner.tutor.json +++ b/runtime/tutor/en/vim-01-beginner.tutor.json @@ -1,45 +1,43 @@ { "expect": { "24": -1, - "102": "The cow jumped over the moon.", - "122": "There is some text missing from this line.", - "123": "There is some text missing from this line.", - "141": "There is some text missing from this line.", - "142": "There is some text missing from this line.", - "143": "There is also some text missing here.", - "144": "There is also some text missing here.", - "215": "There are some words that don't belong in this sentence.", - "231": "Somebody typed the end of this line twice.", - "271": -1, - "290": "This line of words is cleaned up.", - "304": -1, - "305": -1, - "306": -1, - "307": -1, - "308": -1, + "103": "The cow jumped over the moon.", + "124": "There is some text missing from this line.", + "125": "There is some text missing from this line.", + "144": "There is some text missing from this line.", + "145": "There is some text missing from this line.", + "146": "There is also some text missing here.", + "147": "There is also some text missing here.", + "220": "There are some words that don't belong in this sentence.", + "236": "Somebody typed the end of this line twice.", + "276": -1, + "295": "This line of words is cleaned up.", "309": -1, "310": -1, - "325": "Fix the errors on this line and replace them with undo.", - "365": -1, - "366": -1, - "367": -1, - "368": -1, - "382": "When this line was typed in, someone pressed some wrong keys!", - "383": "When this line was typed in, someone pressed some wrong keys!", - "403": "This line has a few words that need changing using the change operator.", - "404": "This line has a few words that need changing using the change operator.", - "424": "The end of this line needs to be corrected using the c$ command.", - "425": "The end of this line needs to be corrected using the c$ command.", - "487": -1, - "506": -1, - "531": "Usually the best time to see the flowers is in the spring.", - "722": -1, - "727": -1, - "744": "This line will allow you to practice appending text to a line.", - "745": "This line will allow you to practice appending text to a line.", - "765": "Adding 123 to 456 gives you 579.", - "766": "Adding 123 to 456 gives you 579.", - "790": "a) This is the first item.", - "791": " b) This is the second item." + "311": -1, + "312": -1, + "313": -1, + "314": -1, + "315": -1, + "332": "Fix the errors on this line and replace them with undo.", + "372": -1, + "373": -1, + "374": -1, + "375": -1, + "389": "When this line was typed in, someone pressed some wrong keys!", + "390": "When this line was typed in, someone pressed some wrong keys!", + "411": "This line has a few words that need changing using the change operator.", + "412": "This line has a few words that need changing using the change operator.", + "432": "The end of this line needs to be corrected using the c$ command.", + "433": "The end of this line needs to be corrected using the c$ command.", + "497": -1, + "516": -1, + "541": "Usually the best time to see the flowers is in the spring.", + "759": "This line will allow you to practice appending text to a line.", + "760": "This line will allow you to practice appending text to a line.", + "780": "Adding 123 to 456 gives you 579.", + "781": "Adding 123 to 456 gives you 579.", + "807": "a) This is the first item.", + "808": " b) This is the second item." } } diff --git a/scripts/gen_api_vimdoc.py b/scripts/gen_api_vimdoc.py index 51e585a007..69f70f6e2b 100644 --- a/scripts/gen_api_vimdoc.py +++ b/scripts/gen_api_vimdoc.py @@ -45,6 +45,8 @@ if sys.version_info[0] < 3: doc_filename = 'api.txt' # String used to find the start of the generated part of the doc. section_start_token = '*api-global*' +# Required prefix for API function names. +api_func_name_prefix = 'nvim_' # Section name overrides. section_name = { @@ -260,11 +262,11 @@ def parse_parblock(parent, width=62): def parse_source_xml(filename): """Collects API functions. - This returns two strings: - 1. The API functions - 2. The deprecated API functions + Returns two strings: + 1. API functions + 2. Deprecated API functions - The caller decides what to do with the deprecated documentation. + Caller decides what to do with the deprecated documentation. """ global xrefs xrefs = set() @@ -294,9 +296,8 @@ def parse_source_xml(filename): annotations = get_text(get_child(member, 'argsstring')) if annotations and ')' in annotations: annotations = annotations.rsplit(')', 1)[-1].strip() - # XXX: (doxygen 1.8.11) 'argsstring' only includes FUNC_ATTR_* - # attributes if the function signature is non-void. - # Force attributes here for such functions. + # XXX: (doxygen 1.8.11) 'argsstring' only includes attributes of + # non-void functions. Special-case void functions here. if name == 'nvim_get_mode' and len(annotations) == 0: annotations += 'FUNC_API_ASYNC' annotations = filter(None, map(lambda x: annotation_map.get(x), @@ -379,7 +380,7 @@ def parse_source_xml(filename): if 'Deprecated' in xrefs: deprecated_functions.append(func_doc) - else: + elif name.startswith(api_func_name_prefix): functions.append(func_doc) xrefs.clear() @@ -477,7 +478,7 @@ def gen_docs(config): docs += '\n\n\n' docs = docs.rstrip() + '\n\n' - docs += ' vim:tw=78:ts=8:ft=help:norl:' + docs += ' vim:tw=78:ts=8:ft=help:norl:\n' doc_file = os.path.join(base_dir, 'runtime/doc', doc_filename) delete_lines_below(doc_file, section_start_token) diff --git a/scripts/genappimage.sh b/scripts/genappimage.sh index a73ccd86c7..2c10ed7202 100755 --- a/scripts/genappimage.sh +++ b/scripts/genappimage.sh @@ -11,6 +11,8 @@ if [ -z "$ARCH" ]; then export ARCH="$(arch)" fi +TAG=$1 + # App name, used by generate_appimage. APP=nvim @@ -35,7 +37,7 @@ VERSION=$("$ROOT_DIR"/build/bin/nvim --version | head -n 1 | grep -o 'v.*') cd "$APP_BUILD_DIR" -curl -Lo "$APP_BUILD_DIR"/appimage_functions.sh https://github.com/probonopd/AppImages/raw/master/functions.sh +curl -Lo "$APP_BUILD_DIR"/appimage_functions.sh https://github.com/AppImage/AppImages/raw/master/functions.sh . ./appimage_functions.sh # Copy desktop and icon file to AppDir for AppRun to pick them up. @@ -53,7 +55,7 @@ move_lib # Delete stuff that should not go into the AppImage. # Delete dangerous libraries; see -# https://github.com/probonopd/AppImages/blob/master/excludelist +# https://github.com/AppImage/AppImages/blob/master/excludelist delete_blacklisted ######################################################################## @@ -69,14 +71,16 @@ cd "$APP_BUILD_DIR" # Get out of AppImage directory. # - Expects: $ARCH, $APP, $VERSION env vars # - Expects: ./$APP.AppDir/ directory # - Produces: ../out/$APP-$VERSION.glibc$GLIBC_NEEDED-$ARCH.AppImage -generate_appimage +if [ -n "$TAG" ]; then + generate_type2_appimage -u "gh-releases-zsync|neovim|neovim|$TAG|nvim.appimage.zsync" +else + generate_type2_appimage +fi -# NOTE: There is currently a bug in the `generate_appimage` function (see -# https://github.com/probonopd/AppImages/issues/228) that causes repeated builds -# that result in the same name to fail. -# Moving the final executable to a different folder gets around this issue. +# Moving the final executable to a different folder so it isn't in the +# way for a subsequent build. -mv "$ROOT_DIR"/out/*.AppImage "$ROOT_DIR"/build/bin +mv "$ROOT_DIR"/out/*.AppImage* "$ROOT_DIR"/build/bin # Remove the (now empty) folder the AppImage was built in rmdir "$ROOT_DIR"/out diff --git a/scripts/pvscheck.sh b/scripts/pvscheck.sh index 30c4d296d7..314966f6aa 100755 --- a/scripts/pvscheck.sh +++ b/scripts/pvscheck.sh @@ -343,13 +343,15 @@ run_analysis() {( cd "$tgt" + # pvs-studio-analyzer exits with a non-zero exit code when there are detected + # errors, so ignore its return pvs-studio-analyzer \ analyze \ --threads "$(get_jobs_num)" \ --output-file PVS-studio.log \ --verbose \ --file build/compile_commands.json \ - --sourcetree-root . + --sourcetree-root . || true plog-converter -t xml -o PVS-studio.xml PVS-studio.log plog-converter -t errorfile -o PVS-studio.err PVS-studio.log diff --git a/scripts/run-api-tests.exp b/scripts/run-api-tests.exp deleted file mode 100755 index 27c9c963e5..0000000000 --- a/scripts/run-api-tests.exp +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env expect - -if {$argc < 2} { - puts "Need commands for running the tests and for starting nvim" - exit 1 -} - -set timeout 60 -set run_tests [split [lindex $argv 0] " "] -set run_nvim [split [lindex $argv 1] " "] - -# don't echo to stdout -log_user 0 -# set NVIM_LISTEN_ADDRESS, so nvim will listen on a known socket -set env(NVIM_LISTEN_ADDRESS) "/tmp/nvim-[exec date +%s%N].sock" -# start nvim -spawn {*}$run_nvim -# save the job descriptor -set nvim_id $spawn_id -# Reset function that can be invoked by test runners to put nvim in a cleaner -# state -send { -:echo "read"."y" -} -# wait until nvim is ready -expect "ready" -# run tests -spawn {*}$run_tests -set tests_id $spawn_id -set status 1 -# listen for test output in the background -expect_background { - * { - # show test output to the user - send_user -- $expect_out(buffer) - } - eof { - # collect the exit status code - set spawn_id $tests_id - catch wait result - set status [lindex $result 3] - set spawn_id $nvim_id - # quit nvim - send ":qa!\r" - } -} -# switch back nvim and wait until it exits -set spawn_id $nvim_id -expect eof -exit $status diff --git a/scripts/shadacat.py b/scripts/shadacat.py index 2f2cf19f9d..522379aad4 100755 --- a/scripts/shadacat.py +++ b/scripts/shadacat.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3.4 +#!/usr/bin/env python3 import os import sys diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index 2875e7d95a..5182756d83 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -14,26 +14,34 @@ readonly BRANCH_PREFIX="vim-" CREATED_FILES=() usage() { - echo "Helper script for porting Vim patches. For more information, see" + echo "Port Vim patches to Neovim" echo "https://github.com/neovim/neovim/wiki/Merging-patches-from-upstream-vim" echo echo "Usage: ${BASENAME} [-h | -l | -p vim-revision | -r pr-number]" echo echo "Options:" echo " -h Show this message and exit." - echo " -l Show list of Vim patches missing from Neovim." - echo " -p {vim-revision} Download and generate the specified Vim patch." - echo " vim-revision can be a version number '8.0.xxx'" - echo " or a valid Git ref (hash, tag, etc.)." - echo " -P {vim-revision} Download, generate and apply the Vim patch." - echo " -g {vim-revision} Download the Vim patch vim-revision." - echo " vim-revision can be a version number of the " - echo " format '7.4.xxx' or a Git commit hash." - echo " -s Submit a vim-patch pull request to Neovim." - echo " -r {pr-number} Review a vim-patch pull request to Neovim." + echo " -l List missing Vim patches." + echo " -L List missing Vim patches (for scripts)." + echo " -M List all merged patch-numbers (at current v:version)." + echo " -p {vim-revision} Download and generate a Vim patch. vim-revision" + echo " can be a Vim version (8.0.xxx) or a Git hash." + echo " -P {vim-revision} Download, generate and apply a Vim patch." + echo " -g {vim-revision} Download a Vim patch." + echo " -s Create a vim-patch pull request." + echo " -r {pr-number} Review a vim-patch pull request." + echo ' -V Clone the Vim source code to $VIM_SOURCE_DIR.' echo - echo "Set VIM_SOURCE_DIR to change where Vim's sources are stored." - echo "Default is '${VIM_SOURCE_DIR_DEFAULT}'." + echo ' $VIM_SOURCE_DIR controls where Vim sources are found' + echo " (default: '${VIM_SOURCE_DIR_DEFAULT}')" +} + +msg_ok() { + printf "\e[32m✔\e[0m $@\n" +} + +msg_err() { + printf "\e[31m✘\e[0m $@\n" } # Checks if a program is in the user's PATH, and is executable. @@ -73,26 +81,29 @@ get_vim_sources() { require_executable git if [[ ! -d ${VIM_SOURCE_DIR} ]]; then - echo "Cloning Vim sources into '${VIM_SOURCE_DIR}'." + echo "Cloning Vim into: ${VIM_SOURCE_DIR}" git clone https://github.com/vim/vim.git "${VIM_SOURCE_DIR}" cd "${VIM_SOURCE_DIR}" else if [[ ! -d "${VIM_SOURCE_DIR}/.git" ]]; then - echo "✘ ${VIM_SOURCE_DIR} does not appear to be a git repository." + msg_err "${VIM_SOURCE_DIR} does not appear to be a git repository." echo " Please remove it and try again." exit 1 fi cd "${VIM_SOURCE_DIR}" - echo "Updating Vim sources in '${VIM_SOURCE_DIR}'." + echo "Updating Vim sources: ${VIM_SOURCE_DIR}" git pull && - echo "✔ Updated Vim sources." || - echo "✘ Could not update Vim sources; ignoring error." + msg_ok "Updated Vim sources." || + msg_err "Could not update Vim sources; ignoring error." fi } commit_message() { - printf 'vim-patch:%s\n\n%s\n\n%s' "${vim_version}" \ - "${vim_message}" "${vim_commit_url}" + if [[ -n "$vim_tag" ]]; then + printf '%s\n%s' "${vim_message}" "${vim_commit_url}" + else + printf 'vim-patch:%s\n\n%s\n%s' "$vim_version" "$vim_message" "$vim_commit_url" + fi } find_git_remote() { @@ -107,22 +118,23 @@ assign_commit_details() { vim_tag="v${1}" vim_commit=$(cd "${VIM_SOURCE_DIR}" \ && git log -1 --format="%H" "${vim_tag}") - local strip_commit_line=true + local munge_commit_line=true else # Interpret parameter as commit hash. vim_version="${1:0:12}" + vim_tag= vim_commit=$(cd "${VIM_SOURCE_DIR}" \ && git log -1 --format="%H" "${vim_version}") - local strip_commit_line=false + local munge_commit_line=false fi vim_commit_url="https://github.com/vim/vim/commit/${vim_commit}" vim_message="$(cd "${VIM_SOURCE_DIR}" \ && git log -1 --pretty='format:%B' "${vim_commit}" \ | sed -e 's/\(#[0-9]*\)/vim\/vim\1/g')" - if [[ ${strip_commit_line} == "true" ]]; then + if [[ ${munge_commit_line} == "true" ]]; then # Remove first line of commit message. - vim_message="$(echo "${vim_message}" | sed -e '1d')" + vim_message="$(echo "${vim_message}" | sed -e '1s/^patch /vim-patch:/')" fi patch_file="vim-${vim_version}.patch" } @@ -147,6 +159,10 @@ preprocess_patch() { local na_src_testdir='Make_amiga.mak\|Make_dos.mak\|Make_ming.mak\|Make_vms.mms' 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/testdir/\<\%('${na_src_testdir}'\)\>@norm! d/\v(^diff)|%$
' +w +q "$file" + # Remove version.c #7555 + local na_po='version.c' + 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/\<\%('${na_po}'\)\>@norm! d/\v(^diff)|%$
' +w +q "$file" + # Remove some *.po files. #5622 local na_po='sjiscorr.c\|ja.sjis.po\|ko.po\|pl.cp1250.po\|pl.po\|ru.cp1251.po\|uk.cp1251.po\|zh_CN.cp936.po\|zh_CN.po\|zh_TW.po' 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/po/\<\%('${na_po}'\)\>@norm! d/\v(^diff)|%$
' +w +q "$file" @@ -164,16 +180,16 @@ preprocess_patch() { "$file" > "$file".tmp && mv "$file".tmp "$file" } -get_vim_patch() { +get_vimpatch() { get_vim_sources assign_commit_details "${1}" git log -1 "${vim_commit}" -- >/dev/null 2>&1 || { - >&2 echo "✘ Couldn't find Vim revision '${vim_commit}'." + >&2 msg_err "Couldn't find Vim revision '${vim_commit}'." exit 3 } - echo "✔ Found Vim revision '${vim_commit}'." + msg_ok "Found Vim revision '${vim_commit}'." local patch_content patch_content="$(git --no-pager show --color=never -1 --pretty=medium "${vim_commit}")" @@ -186,11 +202,11 @@ get_vim_patch() { printf "Pre-processing patch...\n" preprocess_patch "${NVIM_SOURCE_DIR}/${patch_file}" - printf "✔ Saved patch to '${NVIM_SOURCE_DIR}/${patch_file}'.\n" + msg_ok "Saved patch to '${NVIM_SOURCE_DIR}/${patch_file}'.\n" } stage_patch() { - get_vim_patch "$1" + get_vimpatch "$1" local try_apply="${2:-}" local git_remote @@ -199,34 +215,36 @@ stage_patch() { checked_out_branch="$(git rev-parse --abbrev-ref HEAD)" if [[ "${checked_out_branch}" == ${BRANCH_PREFIX}* ]]; then - echo "✔ Current branch '${checked_out_branch}' seems to be a vim-patch" + msg_ok "Current branch '${checked_out_branch}' seems to be a vim-patch" echo " branch; not creating a new branch." else printf "\nFetching '${git_remote}/master'.\n" output="$(git fetch "${git_remote}" master 2>&1)" && - echo "✔ ${output}" || - (echo "✘ ${output}"; false) + msg_ok "${output}" || + (msg_err "${output}"; false) local nvim_branch="${BRANCH_PREFIX}${vim_version}" echo echo "Creating new branch '${nvim_branch}' based on '${git_remote}/master'." cd "${NVIM_SOURCE_DIR}" output="$(git checkout -b "${nvim_branch}" "${git_remote}/master" 2>&1)" && - echo "✔ ${output}" || - (echo "✘ ${output}"; false) + msg_ok "${output}" || + (msg_err "${output}"; false) fi printf "\nCreating empty commit with correct commit message.\n" output="$(commit_message | git commit --allow-empty --file 2>&1 -)" && - echo "✔ ${output}" || - (echo "✘ ${output}"; false) + msg_ok "${output}" || + (msg_err "${output}"; false) if test -n "$try_apply" ; then if ! check_executable patch; then - printf "\n✘ 'patch' command not found\n" + printf "\n" + msg_err "'patch' command not found\n" else printf "\nApplying patch...\n" - patch -p1 --posix < "${patch_file}" + patch -p1 < "${patch_file}" || true + find -name '*.orig' -type f -delete fi printf "\nInstructions:\n Proceed to port the patch.\n" else @@ -274,16 +292,17 @@ submit_pr() { local checked_out_branch checked_out_branch="$(git rev-parse --abbrev-ref HEAD)" if [[ "${checked_out_branch}" != ${BRANCH_PREFIX}* ]]; then - echo "✘ Current branch '${checked_out_branch}' doesn't seem to be a vim-patch branch." + msg_err "Current branch '${checked_out_branch}' doesn't seem to be a vim-patch branch." exit 1 fi local git_remote git_remote="$(find_git_remote)" local pr_body - pr_body="$(git log --reverse --format='#### %s%n%n%b%n' "${git_remote}"/master..HEAD)" + pr_body="$(git log --grep=vim-patch --reverse --format='#### %s%n%n%b%n' "${git_remote}"/master..HEAD)" local patches - patches=("$(git log --reverse --format='%s' "${git_remote}"/master..HEAD)") + # 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. local pr_title="${patches[*]}" # Create space-separated string from array. pr_title="${pr_title// /,}" # Replace spaces with commas. @@ -294,16 +313,16 @@ submit_pr() { if [[ $push_first -ne 0 ]]; then echo "Pushing to 'origin/${checked_out_branch}'." output="$(git push origin "${checked_out_branch}" 2>&1)" && - echo "✔ ${output}" || - (echo "✘ ${output}"; false) + msg_ok "${output}" || + (msg_err "${output}"; false) echo fi echo "Creating pull request." output="$(${submit_fn} "${pr_message}" 2>&1)" && - echo "✔ ${output}" || - (echo "✘ ${output}"; false) + msg_ok "${output}" || + (msg_err "${output}"; false) echo echo "Cleaning up files." @@ -314,43 +333,74 @@ submit_pr() { continue fi rm -- "${NVIM_SOURCE_DIR}/${patch_file}" - echo "✔ Removed '${NVIM_SOURCE_DIR}/${patch_file}'." + msg_ok "Removed '${NVIM_SOURCE_DIR}/${patch_file}'." done } -list_vim_patches() { - get_vim_sources +# Gets all Vim commits since the "start" commit. +list_vim_commits() { ( + cd "${VIM_SOURCE_DIR}" && git log --reverse --format='%H' v8.0.0000..HEAD +) } + +# Prints all (sorted) "vim-patch:xxx" tokens found in the Nvim git log. +list_vimpatch_tokens() { + local tokens + # Find all "vim-patch:xxx" tokens in the Nvim git log. + tokens="$(cd "${NVIM_SOURCE_DIR}" && git log -E --grep='vim-patch:[^ ]+' | grep 'vim-patch')" + echo "$tokens" | grep -E 'vim-patch:[^ ,{]{7,}' \ + | sed 's/.*\(vim-patch:[.0-9a-z]\+\).*/\1/' \ + | sort \ + | uniq +} - printf "\nVim patches missing from Neovim:\n" +# Prints all patch-numbers (for the current v:version) for which there is +# a "vim-patch:xxx" token in the Nvim git log. +list_vimpatch_numbers() { + # Transform "vim-patch:X.Y.ZZZZ" to "ZZZZ". + list_vimpatch_tokens | while read vimpatch_token; do + echo "$vimpatch_token" | grep '8\.0\.' | sed 's/.*vim-patch:8\.0\.\([0-9a-z]\+\).*/\1/' + done +} - # Get missing Vim commits - local vim_commits - vim_commits="$(cd "${VIM_SOURCE_DIR}" && git log --reverse --format='%H' v8.0.0000..HEAD)" +# Prints a newline-delimited list of Vim commits, for use by scripts. +list_missing_vimpatches() { + local tokens vim_commit vim_commits is_missing vim_tag patch_number + + # Find all "vim-patch:xxx" tokens in the Nvim git log. + tokens="$(list_vimpatch_tokens)" - local vim_commit + # Get missing Vim commits + vim_commits="$(list_vim_commits)" for vim_commit in ${vim_commits}; do - local is_missing - local vim_tag - # This fails for untagged commits (e.g., runtime file updates) so mask the return status - vim_tag="$(cd "${VIM_SOURCE_DIR}" && git describe --tags --exact-match "${vim_commit}" 2>/dev/null)" || true - if [[ -n "${vim_tag}" ]]; then - local patch_number="${vim_tag:5}" # Remove prefix like "v7.4." - patch_number="$(echo ${patch_number} | sed 's/^0*//g')" # Remove prefix "0" - # Tagged Vim patch, check version.c: - is_missing="$(sed -n '/static const int included_patches/,/}/p' "${NVIM_SOURCE_DIR}/src/nvim/version.c" | - grep -x -e "[[:space:]]*//[[:space:]]${patch_number} NA.*" -e "[[:space:]]*${patch_number}," >/dev/null && echo "false" || echo "true")" + # Check for vim-patch:<commit_hash> (usually runtime updates). + is_missing="$(echo "$tokens" | >/dev/null 2>&1 grep "vim\-patch:${vim_commit:0:7}" && echo false || echo true)" + + if ! [ "$is_missing" = "false" ] \ + && vim_tag="$(cd "${VIM_SOURCE_DIR}" && git describe --tags --exact-match "${vim_commit}" 2>/dev/null)" + then + # Vim version number (not commit hash). + # Check for vim-patch:<tag> (not commit hash). + patch_number="${vim_tag:1}" # "v7.4.0001" => "7.4.0001" + is_missing="$(echo "$tokens" | >/dev/null 2>&1 grep "vim\-patch:${patch_number}" && echo false || echo true)" vim_commit="${vim_tag#v}" - if (cd "${VIM_SOURCE_DIR}" && git --no-pager show --color=never --name-only "v${vim_commit}" 2>/dev/null) | grep -q ^runtime; then - vim_commit="${vim_commit} (+runtime)" - fi - else - # Untagged Vim patch (e.g. runtime updates), check the Neovim git log: - is_missing="$(cd "${NVIM_SOURCE_DIR}" && - git log -1 --no-merges --grep="vim\-patch:${vim_commit:0:7}" --pretty=format:false)" fi - if [[ ${is_missing} != "false" ]]; then - echo " • ${vim_commit}" + if ! [ "$is_missing" = "false" ]; then + echo "${vim_commit}" + fi + done +} + +# Prints a human-formatted list of Vim commits, with instructional messages. +show_vimpatches() { + get_vim_sources + printf "\nVim patches missing from Neovim:\n" + + list_missing_vimpatches | while read vim_commit; do + if (cd "${VIM_SOURCE_DIR}" && git --no-pager show --color=never --name-only "v${vim_commit}" 2>/dev/null) | grep -q ^runtime; then + printf " • ${vim_commit} (+runtime)\n" + else + printf " • ${vim_commit}\n" fi done @@ -380,9 +430,9 @@ review_commit() { echo if [[ -n "${vim_version}" ]]; then - echo "✔ Detected Vim patch '${vim_version}'." + msg_ok "Detected Vim patch '${vim_version}'." else - echo "✘ Could not detect the Vim patch number." + msg_err "Could not detect the Vim patch number." echo " This script assumes that the PR contains only commits" echo " with 'vim-patch:XXX' in their title." echo @@ -405,9 +455,9 @@ review_commit() { local commit_message commit_message="$(tail -n +4 <<< "${nvim_patch}" | head -n "${message_length}")" if [[ "${commit_message#${git_patch_prefix}}" == "${expected_commit_message}" ]]; then - echo "✔ Found expected commit message." + msg_ok "Found expected commit message." else - echo "✘ Wrong commit message." + msg_err "Wrong commit message." echo " Expected:" echo "${expected_commit_message}" echo " Actual:" @@ -417,10 +467,10 @@ review_commit() { echo echo "Creating files." echo "${nvim_patch}" > "${NVIM_SOURCE_DIR}/n${patch_file}" - echo "✔ Saved pull request diff to '${NVIM_SOURCE_DIR}/n${patch_file}'." + msg_ok "Saved pull request diff to '${NVIM_SOURCE_DIR}/n${patch_file}'." CREATED_FILES+=("${NVIM_SOURCE_DIR}/n${patch_file}") - get_vim_patch "${vim_version}" + get_vimpatch "${vim_version}" CREATED_FILES+=("${NVIM_SOURCE_DIR}/${patch_file}") echo @@ -461,14 +511,22 @@ review_pr() { clean_files } -while getopts "hlp:P:g:r:s" opt; do +while getopts "hlLMVp:P:g:r:s" opt; do case ${opt} in h) usage exit 0 ;; l) - list_vim_patches + show_vimpatches + exit 0 + ;; + L) + list_missing_vimpatches + exit 0 + ;; + M) + list_vimpatch_numbers exit 0 ;; p) @@ -480,7 +538,7 @@ while getopts "hlp:P:g:r:s" opt; do exit 0 ;; g) - get_vim_patch "${OPTARG}" + get_vimpatch "${OPTARG}" exit 0 ;; r) @@ -491,6 +549,10 @@ while getopts "hlp:P:g:r:s" opt; do submit_pr exit 0 ;; + V) + get_vim_sources + exit 0 + ;; *) exit 1 ;; diff --git a/scripts/vimpatch.lua b/scripts/vimpatch.lua new file mode 100755 index 0000000000..0924f3d718 --- /dev/null +++ b/scripts/vimpatch.lua @@ -0,0 +1,67 @@ +-- Updates version.c list of applied Vim patches. +-- +-- Usage: +-- VIM_SOURCE_DIR=~/neovim/.vim-src/ nvim -i NONE -u NONE --headless +'luafile ./scripts/vimpatch.lua' +q + +local nvim = vim.api + +local function pprint(o) + print(nvim.nvim_call_function('string', { o })) +end + +local function systemlist(...) + local rv = nvim.nvim_call_function('systemlist', ...) + local err = nvim.nvim_get_vvar('shell_error') + local args_str = nvim.nvim_call_function('string', ...) + if 0 ~= err then + error('command failed: '..args_str) + end + return rv +end + +local function vimpatch_sh_list_numbers() + return systemlist( { { 'bash', '-c', 'scripts/vim-patch.sh -M', } } ) +end + +-- Generates the lines to be inserted into the src/version.c +-- `included_patches[]` definition. +local function gen_version_c_lines() + -- Set of merged Vim 8.0.zzzz patch numbers. + local merged_patch_numbers = {} + local highest = 0 + for _, n in ipairs(vimpatch_sh_list_numbers()) do + if n then + merged_patch_numbers[tonumber(n)] = true + highest = math.max(highest, n) + end + end + + local lines = {} + for i = highest, 0, -1 do + local is_merged = (nil ~= merged_patch_numbers[i]) + if is_merged then + table.insert(lines, string.format(' %s,', i)) + else + table.insert(lines, string.format(' // %s,', i)) + end + end + + return lines +end + +local function patch_version_c() + local lines = gen_version_c_lines() + + nvim.nvim_command('silent noswapfile noautocmd edit src/nvim/version.c') + nvim.nvim_command('/static const int included_patches') + -- Delete the existing lines. + nvim.nvim_command('silent normal! j0d/};\rk') + -- Insert the lines. + nvim.nvim_call_function('append', { + nvim.nvim_eval('line(".")'), + lines, + }) + nvim.nvim_command('silent write') +end + +patch_version_c() diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml new file mode 100644 index 0000000000..81ffb9adf3 --- /dev/null +++ b/snap/snapcraft.yaml @@ -0,0 +1,41 @@ +name: neovim +version: git +summary: Vim-fork focused on extensibility and agility. +description: | + Neovim is a project that seeks to aggressively refactor Vim in order to: + + Simplify maintenance and encourage contributions + Split the work between multiple developers + Enable the implementation of new/modern user interfaces without any modifications to the core source + Improve extensibility with a new plugin architecture + For lots more details, see the wiki! +confinement: classic + +apps: + neovim: + command: usr/local/bin/nvim + plugs: [network, network-bind, x11] + environment: + HOME: /home/$USER + VIM: $SNAP/usr/local/share/nvim/runtime + +parts: + neovim: + source: . + plugin: make + make-parameters: + - CMAKE_BUILD_TYPE=Release + build-packages: + - ninja-build + - libtool + - libtool-bin + - autoconf + - automake + - cmake + - g++ + - pkg-config + - unzip + snap: + - usr/local/bin + - usr/local/share/nvim + - -usr/local/share/man diff --git a/src/.asan-blacklist b/src/.asan-blacklist index 928d81bd5a..9d7f721a88 100644 --- a/src/.asan-blacklist +++ b/src/.asan-blacklist @@ -1,3 +1,7 @@ # multiqueue.h pointer arithmetic is not accepted by asan fun:multiqueue_node_data fun:tv_dict_watcher_node_data + +# Allocation in loop_schedule_deferred() is freed by loop_deferred_event(), but +# this sometimes does not happen during teardown. +func:loop_schedule_deferred diff --git a/src/clint.py b/src/clint.py index e63175a69b..79ab91ebe1 100755 --- a/src/clint.py +++ b/src/clint.py @@ -201,6 +201,7 @@ _ERROR_CATEGORIES = [ 'runtime/printf', 'runtime/printf_format', 'runtime/threadsafe_fn', + 'runtime/deprecated', 'syntax/parenthesis', 'whitespace/alignment', 'whitespace/blank_line', @@ -2123,8 +2124,10 @@ def CheckExpressionAlignment(filename, clean_lines, linenum, error, startpos=0): + (level_starts[depth][2] == '{')): if depth not in ignore_error_levels: error(filename, linenum, 'whitespace/alignment', 2, - 'Inner expression should be aligned ' - 'as opening brace + 1 (+ 2 in case of {)') + ('Inner expression should be aligned ' + 'as opening brace + 1 (+ 2 in case of {{). ' + 'Relevant opening is on line {0!r}').format( + level_starts[depth][3])) prev_line_start = pos elif brace == 'e': pass @@ -2141,7 +2144,8 @@ def CheckExpressionAlignment(filename, clean_lines, linenum, error, startpos=0): ignore_error_levels.add(depth) line_ended_with_opening = ( pos == len(line) - 2 * (line.endswith(' \\')) - 1) - level_starts[depth] = (pos, line_ended_with_opening, brace) + level_starts[depth] = (pos, line_ended_with_opening, brace, + linenum) if line_ended_with_opening: depth_line_starts[depth] = (prev_line_start, brace) else: @@ -3200,6 +3204,14 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, if match: error(filename, linenum, 'runtime/printf', 4, 'Use xstrlcat or snprintf instead of %s' % match.group(1)) + if not Search(r'eval/typval\.[ch]$', filename): + match = Search(r'(?:\.|->)' + r'(?:lv_(?:first|last|refcount|len|watch|idx(?:_item)?' + r'|copylist|lock)' + r'|li_(?:next|prev|tv))\b', line) + if match: + error(filename, linenum, 'runtime/deprecated', 4, + 'Accessing list_T internals directly is prohibited') # Check for suspicious usage of "if" like # } if (a == b) { diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index a166ee6c02..2d803792c8 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -13,6 +13,9 @@ endif() if(WIN32) # tell MinGW compiler to enable wmain set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -municode") +elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework CoreFoundation") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -framework CoreFoundation") endif() set(TOUCHES_DIR ${PROJECT_BINARY_DIR}/touches) @@ -86,6 +89,8 @@ foreach(subdir event eval lua + viml + viml/parser ) if(${subdir} MATCHES "tui" AND NOT FEAT_TUI) continue() @@ -170,7 +175,7 @@ endif() get_directory_property(gen_cdefs COMPILE_DEFINITIONS) foreach(gen_cdef ${gen_cdefs} DO_NOT_DEFINE_EMPTY_ATTRIBUTES) - if(NOT "${gen_cdef}" MATCHES "INCLUDE_GENERATED_DECLARATIONS") + if(NOT ${gen_cdef} MATCHES "INCLUDE_GENERATED_DECLARATIONS") list(APPEND gen_cflags "-D${gen_cdef}") endif() endforeach() @@ -182,6 +187,10 @@ get_directory_property(gen_includes INCLUDE_DIRECTORIES) foreach(gen_include ${gen_includes} ${LUA_PREFERRED_INCLUDE_DIRS}) list(APPEND gen_cflags "-I${gen_include}") endforeach() +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_OSX_SYSROOT) + list(APPEND gen_cflags "-isysroot") + list(APPEND gen_cflags "${CMAKE_OSX_SYSROOT}") +endif() string(TOUPPER "${CMAKE_BUILD_TYPE}" build_type) separate_arguments(C_FLAGS_ARRAY UNIX_COMMAND ${CMAKE_C_FLAGS}) separate_arguments(C_FLAGS_${build_type}_ARRAY UNIX_COMMAND ${CMAKE_C_FLAGS_${build_type}}) @@ -350,6 +359,10 @@ endforeach() # Our dependencies come first. +if (CMAKE_SYSTEM_NAME MATCHES "OpenBSD") + list(APPEND NVIM_LINK_LIBRARIES pthread c++abi) +endif() + if (LibIntl_FOUND) list(APPEND NVIM_LINK_LIBRARIES ${LibIntl_LIBRARY}) endif() @@ -428,6 +441,7 @@ if(WIN32) COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/tidy.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/win32yank.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/winpty-agent.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/xxd.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/D3Dcompiler_47.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/libEGL.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ @@ -446,6 +460,8 @@ if(WIN32) COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/platforms/qwindows.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/platforms/ ) add_dependencies(nvim_runtime_deps external_blobs) +else() + add_custom_target(nvim_runtime_deps) # Stub target to avoid CMP0046. endif() add_library( diff --git a/src/nvim/README.md b/src/nvim/README.md index 0caf71e2c5..d668db0cdc 100644 --- a/src/nvim/README.md +++ b/src/nvim/README.md @@ -1,10 +1,22 @@ -Nvim core source -================ +Nvim core +========= Module-specific details are documented at the top of each module (`terminal.c`, -`screen.c`, ...). +`screen.c`, …). -See `:help development` for more guidelines. +See `:help dev` for guidelines. + +Filename conventions +-------------------- + +The source files use extensions to hint about their purpose. + +- `*.c`, `*.generated.c` - full C files, with all includes, etc. +- `*.c.h` - parametrized C files, contain all necessary includes, but require + defining macros before actually using. Example: `typval_encode.c.h` +- `*.h` - full headers, with all includes. Does *not* apply to `*.generated.h`. +- `*.h.generated.h` - exported functions’ declarations. +- `*.c.generated.h` - static functions’ declarations. Logs ---- @@ -20,17 +32,75 @@ UI events are logged at level 0 (`DEBUG_LOG_LEVEL`). rm -rf build/ make CMAKE_EXTRA_FLAGS="-DMIN_LOG_LEVEL=0" -Filename conventions --------------------- +Build with ASAN +--------------- -The source files use extensions to hint about their purpose. +Building Nvim with Clang sanitizers (Address Sanitizer: ASan, Undefined +Behavior Sanitizer: UBSan, Memory Sanitizer: MSan, Thread Sanitizer: TSan) is +a good way to catch undefined behavior, leaks and other errors as soon as they +happen. It's significantly faster than Valgrind. -- `*.c`, `*.generated.c` - full C files, with all includes, etc. -- `*.c.h` - parametrized C files, contain all necessary includes, but require - defining macros before actually using. Example: `typval_encode.c.h` -- `*.h` - full headers, with all includes. Does *not* apply to `*.generated.h`. -- `*.h.generated.h` - exported functions’ declarations. -- `*.c.generated.h` - static functions’ declarations. +Requires clang 3.4 or later: + + clang --version + +Build Nvim with sanitizer instrumentation: + + CC=clang make CMAKE_EXTRA_FLAGS="-DCLANG_ASAN_UBSAN=ON" + +Create a directory to store logs: + + mkdir -p "$HOME/logs" + +Enable the sanitizer(s) via these environment variables: + + # Change to detect_leaks=1 to detect memory leaks (slower). + export ASAN_OPTIONS="detect_leaks=0:log_path=$HOME/logs/asan" + export ASAN_SYMBOLIZER_PATH=/usr/lib/llvm-5.0/bin/llvm-symbolizer + + export MSAN_SYMBOLIZER_PATH=/usr/lib/llvm-5.0/bin/llvm-symbolizer + export TSAN_OPTIONS="external_symbolizer_path=/usr/lib/llvm-5.0/bin/llvm-symbolizer log_path=${HOME}/logs/tsan" + +Logs will be written to `${HOME}/logs/*san.PID`. + +TUI debugging +------------- + +### TUI troubleshoot + +Nvim logs its internal terminfo state at 'verbose' level 3. This makes it +possible to see exactly what terminfo values Nvim is using on any system. + + nvim -V3log + +### TUI trace + +The ancient `script` command is still the "state of the art" for tracing +terminal behavior. The libvterm `vterm-dump` utility formats the result for +human-readability. + +Record a Nvim terminal session and format it with `vterm-dump`: + + script foo + ./build/bin/nvim -u NONE + # Exit the script session with CTRL-d + + # Use `vterm-dump` utility to format the result. + ./.deps/usr/bin/vterm-dump foo > bar + +Then you can compare `bar` with another session, to debug TUI behavior. + +### TUI redraw + +Set the 'writedelay' option to see where and when the UI is painted. + + :set writedelay=1 + +### Terminal reference + +- `man terminfo` +- http://bazaar.launchpad.net/~libvterm/libvterm/trunk/view/head:/doc/seqs.txt +- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html Nvim lifecycle -------------- @@ -39,7 +109,7 @@ Following describes how Nvim processes input. Consider a typical Vim-like editing session: -01. Vim dispays the welcome screen +01. Vim displays the welcome screen 02. User types: `:` 03. Vim enters command-line mode 04. User types: `edit README.txt<CR>` diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 4b6a88e5fa..af723639c5 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -399,7 +399,11 @@ void nvim_buf_set_lines(uint64_t channel_id, // Only adjust marks if we managed to switch to a window that holds // the buffer, otherwise line numbers will be invalid. if (save_curbuf.br_buf == NULL) { - mark_adjust((linenr_T)start, (linenr_T)(end - 1), MAXLNUM, extra, false); + mark_adjust((linenr_T)start, + (linenr_T)(end - 1), + MAXLNUM, + (long)extra, + false); } changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra); @@ -763,8 +767,8 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) /// or -1 for ungrouped highlight /// @param hl_group Name of the highlight group to use /// @param line Line to highlight (zero-indexed) -/// @param col_start Start of range of columns to highlight -/// @param col_end End of range of columns to highlight, +/// @param col_start Start of (byte-indexed) column range to highlight +/// @param col_end End of (byte-indexed) column range to highlight, /// or -1 to highlight to end of line /// @param[out] err Error details, if any /// @return The src_id that was used @@ -806,7 +810,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, /// Clears highlights from a given source group and a range of lines /// -/// To clear a source group in the entire buffer, pass in 1 and -1 to +/// To clear a source group in the entire buffer, pass in 0 and -1 to /// line_start and line_end respectively. /// /// @param buffer Buffer handle diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index 2144c80d6a..390b7e8363 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -47,7 +47,7 @@ typedef enum { /// Internal call from lua code #define LUA_INTERNAL_CALL (VIML_INTERNAL_CALL + 1) -static inline bool is_internal_call(uint64_t channel_id) +static inline bool is_internal_call(const uint64_t channel_id) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_CONST; /// Check whether call is internal diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 629873998e..12a4279dd7 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -26,6 +26,7 @@ #include "nvim/version.h" #include "nvim/lib/kvec.h" #include "nvim/getchar.h" +#include "nvim/ui.h" /// Helper structure for vim_to_object typedef struct { @@ -46,13 +47,15 @@ typedef struct { /// @param[out] tstate Location where try state should be saved. void try_enter(TryState *const tstate) { + // TODO(ZyX-I): Check whether try_enter()/try_leave() may use + // enter_cleanup()/leave_cleanup(). Or + // save_dbg_stuff()/restore_dbg_stuff(). *tstate = (TryState) { .current_exception = current_exception, .msg_list = (const struct msglist *const *)msg_list, .private_msg_list = NULL, .trylevel = trylevel, .got_int = got_int, - .did_throw = did_throw, .need_rethrow = need_rethrow, .did_emsg = did_emsg, }; @@ -60,7 +63,6 @@ void try_enter(TryState *const tstate) current_exception = NULL; trylevel = 1; got_int = false; - did_throw = false; need_rethrow = false; did_emsg = false; } @@ -81,7 +83,6 @@ bool try_leave(const TryState *const tstate, Error *const err) assert(trylevel == 0); assert(!need_rethrow); assert(!got_int); - assert(!did_throw); assert(!did_emsg); assert(msg_list == &tstate->private_msg_list); assert(*msg_list == NULL); @@ -90,7 +91,6 @@ bool try_leave(const TryState *const tstate, Error *const err) current_exception = tstate->current_exception; trylevel = tstate->trylevel; got_int = tstate->got_int; - did_throw = tstate->did_throw; need_rethrow = tstate->need_rethrow; did_emsg = tstate->did_emsg; return ret; @@ -126,7 +126,7 @@ bool try_end(Error *err) did_emsg = false; if (got_int) { - if (did_throw) { + if (current_exception) { // If we got an interrupt, discard the current exception discard_current_exception(); } @@ -145,7 +145,7 @@ bool try_end(Error *err) if (should_free) { xfree(msg); } - } else if (did_throw) { + } else if (current_exception) { api_set_error(err, kErrorTypeException, "%s", current_exception->value); discard_current_exception(); } @@ -566,7 +566,7 @@ static inline void typval_encode_dict_end(EncodedData *const edata) typval_encode_dict_end(edata) #define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \ - TYPVAL_ENCODE_CONV_NIL() + TYPVAL_ENCODE_CONV_NIL(val) #define TYPVAL_ENCODE_SCOPE static #define TYPVAL_ENCODE_NAME object @@ -783,22 +783,20 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) break; case kObjectTypeArray: { - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc((ptrdiff_t)obj.data.array.size); for (uint32_t i = 0; i < obj.data.array.size; i++) { Object item = obj.data.array.items[i]; - listitem_T *li = tv_list_item_alloc(); + typval_T li_tv; - if (!object_to_vim(item, &li->li_tv, err)) { - // cleanup - tv_list_item_free(li); + if (!object_to_vim(item, &li_tv, err)) { tv_list_free(list); return false; } - tv_list_append(list, li); + tv_list_append_owned_tv(list, li_tv); } - list->lv_refcount++; + tv_list_ref(list); tv->v_type = VAR_LIST; tv->vval.v_list = list; @@ -957,6 +955,12 @@ static void init_ui_event_metadata(Dictionary *metadata) msgpack_rpc_to_object(&unpacked.data, &ui_events); msgpack_unpacked_destroy(&unpacked); PUT(*metadata, "ui_events", ui_events); + Array ui_options = ARRAY_DICT_INIT; + ADD(ui_options, STRING_OBJ(cstr_to_string("rgb"))); + for (UIExtension i = 0; i < kUIExtCount; i++) { + ADD(ui_options, STRING_OBJ(cstr_to_string(ui_ext_names[i]))); + } + PUT(*metadata, "ui_options", ARRAY_OBJ(ui_options)); } static void init_error_type_metadata(Dictionary *metadata) diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 87f334ac30..0634764f13 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -93,7 +93,6 @@ typedef struct { const struct msglist *const *msg_list; int trylevel; int got_int; - int did_throw; int need_rethrow; int did_emsg; } TryState; diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index a9eaccfac5..4cd2657561 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -43,10 +43,10 @@ void remote_ui_disconnect(uint64_t channel_id) return; } UIData *data = ui->data; - // destroy pending screen updates - api_free_array(data->buffer); + api_free_array(data->buffer); // Destroy pending screen updates. pmap_del(uint64_t)(connected_uis, channel_id); xfree(ui->data); + ui->data = NULL; // Flag UI as "stopped". ui_detach_impl(ui); xfree(ui); } @@ -56,7 +56,8 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (pmap_has(uint64_t)(connected_uis, channel_id)) { - api_set_error(err, kErrorTypeException, "UI already attached for channel"); + api_set_error(err, kErrorTypeException, + "UI already attached to channel: %" PRId64, channel_id); return; } @@ -86,6 +87,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, ui->put = remote_ui_put; ui->bell = remote_ui_bell; ui->visual_bell = remote_ui_visual_bell; + ui->default_colors_set = remote_ui_default_colors_set; ui->update_fg = remote_ui_update_fg; ui->update_bg = remote_ui_update_bg; ui->update_sp = remote_ui_update_sp; @@ -93,6 +95,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, ui->suspend = remote_ui_suspend; ui->set_title = remote_ui_set_title; ui->set_icon = remote_ui_set_icon; + ui->option_set = remote_ui_option_set; ui->event = remote_ui_event; memset(ui->ui_ext, 0, sizeof(ui->ui_ext)); @@ -128,7 +131,8 @@ void nvim_ui_detach(uint64_t channel_id, Error *err) FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (!pmap_has(uint64_t)(connected_uis, channel_id)) { - api_set_error(err, kErrorTypeException, "UI is not attached for channel"); + api_set_error(err, kErrorTypeException, + "UI not attached to channel: %" PRId64, channel_id); return; } remote_ui_disconnect(channel_id); @@ -140,7 +144,8 @@ void nvim_ui_try_resize(uint64_t channel_id, Integer width, FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (!pmap_has(uint64_t)(connected_uis, channel_id)) { - api_set_error(err, kErrorTypeException, "UI is not attached for channel"); + api_set_error(err, kErrorTypeException, + "UI not attached to channel: %" PRId64, channel_id); return; } @@ -161,7 +166,8 @@ void nvim_ui_set_option(uint64_t channel_id, String name, FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (!pmap_has(uint64_t)(connected_uis, channel_id)) { - api_set_error(error, kErrorTypeException, "UI is not attached for channel"); + api_set_error(error, kErrorTypeException, + "UI not attached to channel: %" PRId64, channel_id); return; } UI *ui = pmap_get(uint64_t)(connected_uis, channel_id); @@ -174,18 +180,6 @@ void nvim_ui_set_option(uint64_t channel_id, String name, static void ui_set_option(UI *ui, String name, Object value, Error *error) { -#define UI_EXT_OPTION(o, e) \ - do { \ - if (strequal(name.data, #o)) { \ - if (value.type != kObjectTypeBoolean) { \ - api_set_error(error, kErrorTypeValidation, #o " must be a Boolean"); \ - return; \ - } \ - ui->ui_ext[(e)] = value.data.boolean; \ - return; \ - } \ - } while (0) - if (strequal(name.data, "rgb")) { if (value.type != kObjectTypeBoolean) { api_set_error(error, kErrorTypeValidation, "rgb must be a Boolean"); @@ -195,13 +189,21 @@ static void ui_set_option(UI *ui, String name, Object value, Error *error) return; } - UI_EXT_OPTION(ext_cmdline, kUICmdline); - UI_EXT_OPTION(ext_popupmenu, kUIPopupmenu); - UI_EXT_OPTION(ext_tabline, kUITabline); - UI_EXT_OPTION(ext_wildmenu, kUIWildmenu); + for (UIExtension i = 0; i < kUIExtCount; i++) { + if (strequal(name.data, ui_ext_names[i])) { + if (value.type != kObjectTypeBoolean) { + snprintf((char *)IObuff, IOSIZE, "%s must be a Boolean", + ui_ext_names[i]); + api_set_error(error, kErrorTypeValidation, (char *)IObuff); + return; + } + ui->ui_ext[i] = value.data.boolean; + return; + } + } if (strequal(name.data, "popupmenu_external")) { - // LEGACY: Deprecated option, use `ui_ext` instead. + // LEGACY: Deprecated option, use `ext_cmdline` instead. if (value.type != kObjectTypeBoolean) { api_set_error(error, kErrorTypeValidation, "popupmenu_external must be a Boolean"); @@ -211,7 +213,8 @@ static void ui_set_option(UI *ui, String name, Object value, Error *error) return; } - api_set_error(error, kErrorTypeValidation, "No such ui option"); + api_set_error(error, kErrorTypeValidation, "No such UI option: %s", + name.data); #undef UI_EXT_OPTION } @@ -242,7 +245,7 @@ static void push_call(UI *ui, char *name, Array args) static void remote_ui_highlight_set(UI *ui, HlAttrs attrs) { Array args = ARRAY_DICT_INIT; - Dictionary hl = hlattrs2dict(attrs); + Dictionary hl = hlattrs2dict(&attrs, ui->rgb); ADD(args, DICTIONARY_OBJ(hl)); push_call(ui, "highlight_set", args); diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 847b21072a..c599b0ce72 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -47,17 +47,22 @@ void visual_bell(void) void flush(void) FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL; void update_fg(Integer fg) - FUNC_API_SINCE(3); + FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; void update_bg(Integer bg) - FUNC_API_SINCE(3); + FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; void update_sp(Integer sp) - FUNC_API_SINCE(3); + FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; +void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, + Integer cterm_fg, Integer cterm_bg) + FUNC_API_SINCE(4); void suspend(void) FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; void set_title(String title) FUNC_API_SINCE(3); void set_icon(String icon) FUNC_API_SINCE(3); +void option_set(String name, Object value) + FUNC_API_SINCE(4); void popupmenu_show(Array items, Integer selected, Integer row, Integer col) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index f4ccf07bec..07ec6e8c27 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -33,6 +33,10 @@ #include "nvim/syntax.h" #include "nvim/getchar.h" #include "nvim/os/input.h" +#include "nvim/os/process.h" +#include "nvim/viml/parser/expressions.h" +#include "nvim/viml/parser/parser.h" +#include "nvim/ui.h" #define LINE_BUFFER_SIZE 4096 @@ -41,14 +45,15 @@ #endif /// Executes an ex-command. -/// On VimL error: Returns the VimL error; v:errmsg is not updated. +/// +/// On parse error: forwards the Vim error; does not update v:errmsg. +/// On runtime error: forwards the Vim error; does not update v:errmsg. /// /// @param command Ex-command string -/// @param[out] err Error details (including actual VimL error), if any +/// @param[out] err Error details (Vim error), if any void nvim_command(String command, Error *err) FUNC_API_SINCE(1) { - // Run the command try_start(); do_cmdline_cmd(command.data); update_screen(VALID); @@ -205,18 +210,51 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, return cstr_as_string(ptr); } -String nvim_command_output(String str, Error *err) +/// Executes an ex-command and returns its (non-error) output. +/// Shell |:!| output is not captured. +/// +/// On parse error: forwards the Vim error; does not update v:errmsg. +/// On runtime error: forwards the Vim error; does not update v:errmsg. +/// +/// @param command Ex-command string +/// @param[out] err Error details (Vim error), if any +String nvim_command_output(String command, Error *err) FUNC_API_SINCE(1) { - do_cmdline_cmd("redir => v:command_output"); - nvim_command(str, err); - do_cmdline_cmd("redir END"); + const int save_msg_silent = msg_silent; + garray_T *const save_capture_ga = capture_ga; + garray_T capture_local; + ga_init(&capture_local, 1, 80); + + try_start(); + msg_silent++; + capture_ga = &capture_local; + do_cmdline_cmd(command.data); + capture_ga = save_capture_ga; + msg_silent = save_msg_silent; + try_end(err); if (ERROR_SET(err)) { - return (String)STRING_INIT; + goto theend; } - return cstr_to_string((char *)get_vim_var_str(VV_COMMAND_OUTPUT)); + if (capture_local.ga_len > 1) { + String s = (String){ + .data = capture_local.ga_data, + .size = (size_t)capture_local.ga_len, + }; + // redir usually (except :echon) prepends a newline. + if (s.data[0] == '\n') { + memmove(s.data, s.data + 1, s.size); + s.data[s.size - 1] = '\0'; + s.size = s.size - 1; + } + return s; // Caller will free the memory. + } + +theend: + ga_clear(&capture_local); + return (String)STRING_INIT; } /// Evaluates a VimL expression (:help expression). @@ -787,6 +825,10 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode) return keymap_array(mode, NULL); } +/// Returns a 2-tuple (Array), where item 0 is the current channel id and item +/// 1 is the |api-metadata| map (Dictionary). +/// +/// @returns 2-tuple [{channel-id}, {api-metadata}] Array nvim_get_api_info(uint64_t channel_id) FUNC_API_SINCE(1) FUNC_API_ASYNC FUNC_API_REMOTE_ONLY { @@ -889,6 +931,460 @@ theend: return rv; } +typedef struct { + ExprASTNode **node_p; + Object *ret_node_p; +} ExprASTConvStackItem; + +/// @cond DOXYGEN_NOT_A_FUNCTION +typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; +/// @endcond + +/// Parse a VimL expression +/// +/// @param[in] expr Expression to parse. Is always treated as a single line. +/// @param[in] flags Flags: +/// +/// - "m" if multiple expressions in a row are allowed (only +/// the first one will be parsed), +/// - "E" if EOC tokens are not allowed (determines whether +/// they will stop parsing process or be recognized as an +/// operator/space, though also yielding an error). +/// - "l" when needing to start parsing with lvalues for +/// ":let" or ":for". +/// +/// Common flag sets: +/// - "m" to parse like for ":echo". +/// - "E" to parse like for "<C-r>=". +/// - empty string for ":call". +/// - "lm" to parse for ":let". +/// @param[in] highlight If true, return value will also include "highlight" +/// key containing array of 4-tuples (arrays) (Integer, +/// Integer, Integer, String), where first three numbers +/// define the highlighted region and represent line, +/// starting column and ending column (latter exclusive: +/// one should highlight region [start_col, end_col)). +/// +/// @return AST: top-level dictionary with these keys: +/// +/// "error": Dictionary with error, present only if parser saw some +/// error. Contains the following keys: +/// +/// "message": String, error message in printf format, translated. +/// Must contain exactly one "%.*s". +/// "arg": String, error message argument. +/// +/// "len": Amount of bytes successfully parsed. With flags equal to "" +/// that should be equal to the length of expr string. +/// +/// @note: “Sucessfully parsed†here means “participated in AST +/// creationâ€, not “till the first errorâ€. +/// +/// "ast": AST, either nil or a dictionary with these keys: +/// +/// "type": node type, one of the value names from ExprASTNodeType +/// stringified without "kExprNode" prefix. +/// "start": a pair [line, column] describing where node is “started†+/// where "line" is always 0 (will not be 0 if you will be +/// using nvim_parse_viml() on e.g. ":let", but that is not +/// present yet). Both elements are Integers. +/// "len": “length†of the node. This and "start" are there for +/// debugging purposes primary (debugging parser and providing +/// debug information). +/// "children": a list of nodes described in top/"ast". There always +/// is zero, one or two children, key will not be present +/// if node has no children. Maximum number of children +/// may be found in node_maxchildren array. +/// +/// Local values (present only for certain nodes): +/// +/// "scope": a single Integer, specifies scope for "Option" and +/// "PlainIdentifier" nodes. For "Option" it is one of +/// ExprOptScope values, for "PlainIdentifier" it is one of +/// ExprVarScope values. +/// "ident": identifier (without scope, if any), present for "Option", +/// "PlainIdentifier", "PlainKey" and "Environment" nodes. +/// "name": Integer, register name (one character) or -1. Only present +/// for "Register" nodes. +/// "cmp_type": String, comparison type, one of the value names from +/// ExprComparisonType, stringified without "kExprCmp" +/// prefix. Only present for "Comparison" nodes. +/// "ccs_strategy": String, case comparison strategy, one of the +/// value names from ExprCaseCompareStrategy, +/// stringified without "kCCStrategy" prefix. Only +/// present for "Comparison" nodes. +/// "augmentation": String, augmentation type for "Assignment" nodes. +/// Is either an empty string, "Add", "Subtract" or +/// "Concat" for "=", "+=", "-=" or ".=" respectively. +/// "invert": Boolean, true if result of comparison needs to be +/// inverted. Only present for "Comparison" nodes. +/// "ivalue": Integer, integer value for "Integer" nodes. +/// "fvalue": Float, floating-point value for "Float" nodes. +/// "svalue": String, value for "SingleQuotedString" and +/// "DoubleQuotedString" nodes. +Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, + Error *err) + FUNC_API_SINCE(4) FUNC_API_ASYNC +{ + int pflags = 0; + for (size_t i = 0 ; i < flags.size ; i++) { + switch (flags.data[i]) { + case 'm': { pflags |= kExprFlagsMulti; break; } + case 'E': { pflags |= kExprFlagsDisallowEOC; break; } + case 'l': { pflags |= kExprFlagsParseLet; break; } + case NUL: { + api_set_error(err, kErrorTypeValidation, "Invalid flag: '\\0' (%u)", + (unsigned)flags.data[i]); + return (Dictionary)ARRAY_DICT_INIT; + } + default: { + api_set_error(err, kErrorTypeValidation, "Invalid flag: '%c' (%u)", + flags.data[i], (unsigned)flags.data[i]); + return (Dictionary)ARRAY_DICT_INIT; + } + } + } + ParserLine plines[] = { + { + .data = expr.data, + .size = expr.size, + .allocated = false, + }, + { NULL, 0, false }, + }; + ParserLine *plines_p = plines; + ParserHighlight colors; + kvi_init(colors); + ParserHighlight *const colors_p = (highlight ? &colors : NULL); + ParserState pstate; + viml_parser_init( + &pstate, parser_simple_get_line, &plines_p, colors_p); + ExprAST east = viml_pexpr_parse(&pstate, pflags); + + const size_t ret_size = ( + 2 // "ast", "len" + + (size_t)(east.err.msg != NULL) // "error" + + (size_t)highlight // "highlight" + + 0); + Dictionary ret = { + .items = xmalloc(ret_size * sizeof(ret.items[0])), + .size = 0, + .capacity = ret_size, + }; + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ast"), + .value = NIL, + }; + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("len"), + .value = INTEGER_OBJ((Integer)(pstate.pos.line == 1 + ? plines[0].size + : pstate.pos.col)), + }; + if (east.err.msg != NULL) { + Dictionary err_dict = { + .items = xmalloc(2 * sizeof(err_dict.items[0])), + .size = 2, + .capacity = 2, + }; + err_dict.items[0] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("message"), + .value = STRING_OBJ(cstr_to_string(east.err.msg)), + }; + if (east.err.arg == NULL) { + err_dict.items[1] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("arg"), + .value = STRING_OBJ(STRING_INIT), + }; + } else { + err_dict.items[1] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("arg"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(east.err.arg, (size_t)east.err.arg_len), + .size = (size_t)east.err.arg_len, + })), + }; + } + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("error"), + .value = DICTIONARY_OBJ(err_dict), + }; + } + if (highlight) { + Array hl = (Array) { + .items = xmalloc(kv_size(colors) * sizeof(hl.items[0])), + .capacity = kv_size(colors), + .size = kv_size(colors), + }; + for (size_t i = 0 ; i < kv_size(colors) ; i++) { + const ParserHighlightChunk chunk = kv_A(colors, i); + Array chunk_arr = (Array) { + .items = xmalloc(4 * sizeof(chunk_arr.items[0])), + .capacity = 4, + .size = 4, + }; + chunk_arr.items[0] = INTEGER_OBJ((Integer)chunk.start.line); + chunk_arr.items[1] = INTEGER_OBJ((Integer)chunk.start.col); + chunk_arr.items[2] = INTEGER_OBJ((Integer)chunk.end_col); + chunk_arr.items[3] = STRING_OBJ(cstr_to_string(chunk.group)); + hl.items[i] = ARRAY_OBJ(chunk_arr); + } + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("highlight"), + .value = ARRAY_OBJ(hl), + }; + } + kvi_destroy(colors); + + // Walk over the AST, freeing nodes in process. + ExprASTConvStack ast_conv_stack; + kvi_init(ast_conv_stack); + kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { + .node_p = &east.root, + .ret_node_p = &ret.items[0].value, + })); + while (kv_size(ast_conv_stack)) { + ExprASTConvStackItem cur_item = kv_last(ast_conv_stack); + ExprASTNode *const node = *cur_item.node_p; + if (node == NULL) { + assert(kv_size(ast_conv_stack) == 1); + kv_drop(ast_conv_stack, 1); + } else { + if (cur_item.ret_node_p->type == kObjectTypeNil) { + const size_t ret_node_items_size = (size_t)( + 3 // "type", "start" and "len" + + (node->children != NULL) // "children" + + (node->type == kExprNodeOption + || node->type == kExprNodePlainIdentifier) // "scope" + + (node->type == kExprNodeOption + || node->type == kExprNodePlainIdentifier + || node->type == kExprNodePlainKey + || node->type == kExprNodeEnvironment) // "ident" + + (node->type == kExprNodeRegister) // "name" + + (3 // "cmp_type", "ccs_strategy", "invert" + * (node->type == kExprNodeComparison)) + + (node->type == kExprNodeInteger) // "ivalue" + + (node->type == kExprNodeFloat) // "fvalue" + + (node->type == kExprNodeDoubleQuotedString + || node->type == kExprNodeSingleQuotedString) // "svalue" + + (node->type == kExprNodeAssignment) // "augmentation" + + 0); + Dictionary ret_node = { + .items = xmalloc(ret_node_items_size * sizeof(ret_node.items[0])), + .capacity = ret_node_items_size, + .size = 0, + }; + *cur_item.ret_node_p = DICTIONARY_OBJ(ret_node); + } + Dictionary *ret_node = &cur_item.ret_node_p->data.dictionary; + if (node->children != NULL) { + const size_t num_children = 1 + (node->children->next != NULL); + Array children_array = { + .items = xmalloc(num_children * sizeof(children_array.items[0])), + .capacity = num_children, + .size = num_children, + }; + for (size_t i = 0; i < num_children; i++) { + children_array.items[i] = NIL; + } + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("children"), + .value = ARRAY_OBJ(children_array), + }; + kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { + .node_p = &node->children, + .ret_node_p = &children_array.items[0], + })); + } else if (node->next != NULL) { + kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { + .node_p = &node->next, + .ret_node_p = cur_item.ret_node_p + 1, + })); + } else if (node != NULL) { + kv_drop(ast_conv_stack, 1); + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("type"), + .value = STRING_OBJ(cstr_to_string(east_node_type_tab[node->type])), + }; + Array start_array = { + .items = xmalloc(2 * sizeof(start_array.items[0])), + .capacity = 2, + .size = 2, + }; + start_array.items[0] = INTEGER_OBJ((Integer)node->start.line); + start_array.items[1] = INTEGER_OBJ((Integer)node->start.col); + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("start"), + .value = ARRAY_OBJ(start_array), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("len"), + .value = INTEGER_OBJ((Integer)node->len), + }; + switch (node->type) { + case kExprNodeDoubleQuotedString: + case kExprNodeSingleQuotedString: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("svalue"), + .value = STRING_OBJ(((String) { + .data = node->data.str.value, + .size = node->data.str.size, + })), + }; + break; + } + case kExprNodeOption: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("scope"), + .value = INTEGER_OBJ(node->data.opt.scope), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.opt.ident, + node->data.opt.ident_len), + .size = node->data.opt.ident_len, + })), + }; + break; + } + case kExprNodePlainIdentifier: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("scope"), + .value = INTEGER_OBJ(node->data.var.scope), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.var.ident, + node->data.var.ident_len), + .size = node->data.var.ident_len, + })), + }; + break; + } + case kExprNodePlainKey: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.var.ident, + node->data.var.ident_len), + .size = node->data.var.ident_len, + })), + }; + break; + } + case kExprNodeEnvironment: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.env.ident, + node->data.env.ident_len), + .size = node->data.env.ident_len, + })), + }; + break; + } + case kExprNodeRegister: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("name"), + .value = INTEGER_OBJ(node->data.reg.name), + }; + break; + } + case kExprNodeComparison: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("cmp_type"), + .value = STRING_OBJ(cstr_to_string( + eltkn_cmp_type_tab[node->data.cmp.type])), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ccs_strategy"), + .value = STRING_OBJ(cstr_to_string( + ccs_tab[node->data.cmp.ccs])), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("invert"), + .value = BOOLEAN_OBJ(node->data.cmp.inv), + }; + break; + } + case kExprNodeFloat: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("fvalue"), + .value = FLOAT_OBJ(node->data.flt.value), + }; + break; + } + case kExprNodeInteger: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ivalue"), + .value = INTEGER_OBJ((Integer)( + node->data.num.value > API_INTEGER_MAX + ? API_INTEGER_MAX + : (Integer)node->data.num.value)), + }; + break; + } + case kExprNodeAssignment: { + const ExprAssignmentType asgn_type = node->data.ass.type; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("augmentation"), + .value = STRING_OBJ( + asgn_type == kExprAsgnPlain + ? (String)STRING_INIT + : cstr_to_string(expr_asgn_type_tab[asgn_type])), + }; + break; + } + case kExprNodeMissing: + case kExprNodeOpMissing: + case kExprNodeTernary: + case kExprNodeTernaryValue: + case kExprNodeSubscript: + case kExprNodeListLiteral: + case kExprNodeUnaryPlus: + case kExprNodeBinaryPlus: + case kExprNodeNested: + case kExprNodeCall: + case kExprNodeComplexIdentifier: + case kExprNodeUnknownFigure: + case kExprNodeLambda: + case kExprNodeDictLiteral: + case kExprNodeCurlyBracesIdentifier: + case kExprNodeComma: + case kExprNodeColon: + case kExprNodeArrow: + case kExprNodeConcat: + case kExprNodeConcatOrSubscript: + case kExprNodeOr: + case kExprNodeAnd: + case kExprNodeUnaryMinus: + case kExprNodeBinaryMinus: + case kExprNodeNot: + case kExprNodeMultiplication: + case kExprNodeDivision: + case kExprNodeMod: { + break; + } + } + assert(cur_item.ret_node_p->data.dictionary.size + == cur_item.ret_node_p->data.dictionary.capacity); + xfree(*cur_item.node_p); + *cur_item.node_p = NULL; + } + } + } + kvi_destroy(ast_conv_stack); + + assert(ret.size == ret.capacity); + // Should be a no-op actually, leaving it in case non-nodes will need to be + // freed later. + viml_pexpr_free_ast(east); + viml_parser_destroy(&pstate); + return ret; +} + /// Writes a message to vim output or error buffer. The string is split /// and flushed after each newline. Incomplete lines are kept for writing @@ -976,3 +1472,95 @@ Float nvim__id_float(Float flt) { return flt; } + +/// Gets a list of dictionaries representing attached UIs. +/// +/// @return Array of UI dictionaries +Array nvim_list_uis(void) + FUNC_API_SINCE(4) +{ + return ui_array(); +} + +/// Gets the immediate children of process `pid`. +/// +/// @return Array of child process ids, empty if process not found. +Array nvim_get_proc_children(Integer pid, Error *err) + FUNC_API_SINCE(4) +{ + Array rvobj = ARRAY_DICT_INIT; + int *proc_list = NULL; + + if (pid <= 0 || pid > INT_MAX) { + api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid); + goto end; + } + + size_t proc_count; + int rv = os_proc_children((int)pid, &proc_list, &proc_count); + if (rv != 0) { + // syscall failed (possibly because of kernel options), try shelling out. + DLOG("fallback to vim._os_proc_children()"); + Array a = ARRAY_DICT_INIT; + ADD(a, INTEGER_OBJ(pid)); + String s = cstr_to_string("return vim._os_proc_children(select(1, ...))"); + Object o = nvim_execute_lua(s, a, err); + api_free_string(s); + api_free_array(a); + if (o.type == kObjectTypeArray) { + rvobj = o.data.array; + } else if (!ERROR_SET(err)) { + api_set_error(err, kErrorTypeException, + "Failed to get process children. pid=%" PRId64 " error=%d", + pid, rv); + } + goto end; + } + + for (size_t i = 0; i < proc_count; i++) { + ADD(rvobj, INTEGER_OBJ(proc_list[i])); + } + +end: + xfree(proc_list); + return rvobj; +} + +/// Gets info describing process `pid`. +/// +/// @return Map of process properties, or NIL if process not found. +Object nvim_get_proc(Integer pid, Error *err) + FUNC_API_SINCE(4) +{ + Object rvobj = OBJECT_INIT; + rvobj.data.dictionary = (Dictionary)ARRAY_DICT_INIT; + rvobj.type = kObjectTypeDictionary; + + if (pid <= 0 || pid > INT_MAX) { + api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid); + return NIL; + } +#ifdef WIN32 + rvobj.data.dictionary = os_proc_info((int)pid); + if (rvobj.data.dictionary.size == 0) { // Process not found. + return NIL; + } +#else + // Cross-platform process info APIs are miserable, so use `ps` instead. + 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_execute_lua(s, a, err); + api_free_string(s); + api_free_array(a); + if (o.type == kObjectTypeArray && o.data.array.size == 0) { + return NIL; // Process not found. + } else if (o.type == kObjectTypeDictionary) { + rvobj.data.dictionary = o.data.dictionary; + } else if (!ERROR_SET(err)) { + api_set_error(err, kErrorTypeException, + "Failed to get process info. pid=%" PRId64, pid); + } +#endif + return rvobj; +} diff --git a/src/nvim/arabic.c b/src/nvim/arabic.c index 1ef51d2a2a..e120e6d492 100644 --- a/src/nvim/arabic.c +++ b/src/nvim/arabic.c @@ -393,491 +393,141 @@ static bool A_is_f(int cur_c) // Change shape - from ISO-8859-6/Isolated to Form-B Isolated static int chg_c_a2s(int cur_c) { - int tempc; - switch (cur_c) { - case a_HAMZA: - tempc = a_s_HAMZA; - break; - - case a_ALEF_MADDA: - tempc = a_s_ALEF_MADDA; - break; - - case a_ALEF_HAMZA_ABOVE: - tempc = a_s_ALEF_HAMZA_ABOVE; - break; - - case a_WAW_HAMZA: - tempc = a_s_WAW_HAMZA; - break; - - case a_ALEF_HAMZA_BELOW: - tempc = a_s_ALEF_HAMZA_BELOW; - break; - - case a_YEH_HAMZA: - tempc = a_s_YEH_HAMZA; - break; - - case a_ALEF: - tempc = a_s_ALEF; - break; - - case a_TEH_MARBUTA: - tempc = a_s_TEH_MARBUTA; - break; - - case a_DAL: - tempc = a_s_DAL; - break; - - case a_THAL: - tempc = a_s_THAL; - break; - - case a_REH: - tempc = a_s_REH; - break; - - case a_ZAIN: - tempc = a_s_ZAIN; - break; - - case a_TATWEEL: // exceptions - tempc = cur_c; - break; - - case a_WAW: - tempc = a_s_WAW; - break; - - case a_ALEF_MAKSURA: - tempc = a_s_ALEF_MAKSURA; - break; - - case a_BEH: - tempc = a_s_BEH; - break; - - case a_TEH: - tempc = a_s_TEH; - break; - - case a_THEH: - tempc = a_s_THEH; - break; - - case a_JEEM: - tempc = a_s_JEEM; - break; - - case a_HAH: - tempc = a_s_HAH; - break; - - case a_KHAH: - tempc = a_s_KHAH; - break; - - case a_SEEN: - tempc = a_s_SEEN; - break; - - case a_SHEEN: - tempc = a_s_SHEEN; - break; - - case a_SAD: - tempc = a_s_SAD; - break; - - case a_DAD: - tempc = a_s_DAD; - break; - - case a_TAH: - tempc = a_s_TAH; - break; - - case a_ZAH: - tempc = a_s_ZAH; - break; - - case a_AIN: - tempc = a_s_AIN; - break; - - case a_GHAIN: - tempc = a_s_GHAIN; - break; - - case a_FEH: - tempc = a_s_FEH; - break; - - case a_QAF: - tempc = a_s_QAF; - break; - - case a_KAF: - tempc = a_s_KAF; - break; - - case a_LAM: - tempc = a_s_LAM; - break; - - case a_MEEM: - tempc = a_s_MEEM; - break; - - case a_NOON: - tempc = a_s_NOON; - break; - - case a_HEH: - tempc = a_s_HEH; - break; - - case a_YEH: - tempc = a_s_YEH; - break; - - default: - tempc = 0; + case a_HAMZA: return a_s_HAMZA; + case a_ALEF_MADDA: return a_s_ALEF_MADDA; + case a_ALEF_HAMZA_ABOVE: return a_s_ALEF_HAMZA_ABOVE; + case a_WAW_HAMZA: return a_s_WAW_HAMZA; + case a_ALEF_HAMZA_BELOW: return a_s_ALEF_HAMZA_BELOW; + case a_YEH_HAMZA: return a_s_YEH_HAMZA; + case a_ALEF: return a_s_ALEF; + case a_TEH_MARBUTA: return a_s_TEH_MARBUTA; + case a_DAL: return a_s_DAL; + case a_THAL: return a_s_THAL; + case a_REH: return a_s_REH; + case a_ZAIN: return a_s_ZAIN; + case a_TATWEEL: return cur_c; // exceptions + case a_WAW: return a_s_WAW; + case a_ALEF_MAKSURA: return a_s_ALEF_MAKSURA; + case a_BEH: return a_s_BEH; + case a_TEH: return a_s_TEH; + case a_THEH: return a_s_THEH; + case a_JEEM: return a_s_JEEM; + case a_HAH: return a_s_HAH; + case a_KHAH: return a_s_KHAH; + case a_SEEN: return a_s_SEEN; + case a_SHEEN: return a_s_SHEEN; + case a_SAD: return a_s_SAD; + case a_DAD: return a_s_DAD; + case a_TAH: return a_s_TAH; + case a_ZAH: return a_s_ZAH; + case a_AIN: return a_s_AIN; + case a_GHAIN: return a_s_GHAIN; + case a_FEH: return a_s_FEH; + case a_QAF: return a_s_QAF; + case a_KAF: return a_s_KAF; + case a_LAM: return a_s_LAM; + case a_MEEM: return a_s_MEEM; + case a_NOON: return a_s_NOON; + case a_HEH: return a_s_HEH; + case a_YEH: return a_s_YEH; } - - return tempc; + return 0; } // Change shape - from ISO-8859-6/Isolated to Initial static int chg_c_a2i(int cur_c) { - int tempc; - switch (cur_c) { - case a_YEH_HAMZA: - tempc = a_i_YEH_HAMZA; - break; - - case a_HAMZA: // exceptions - tempc = a_s_HAMZA; - break; - - case a_ALEF_MADDA: // exceptions - tempc = a_s_ALEF_MADDA; - break; - - case a_ALEF_HAMZA_ABOVE: // exceptions - tempc = a_s_ALEF_HAMZA_ABOVE; - break; - - case a_WAW_HAMZA: // exceptions - tempc = a_s_WAW_HAMZA; - break; - - case a_ALEF_HAMZA_BELOW: // exceptions - tempc = a_s_ALEF_HAMZA_BELOW; - break; - - case a_ALEF: // exceptions - tempc = a_s_ALEF; - break; - - case a_TEH_MARBUTA: // exceptions - tempc = a_s_TEH_MARBUTA; - break; - - case a_DAL: // exceptions - tempc = a_s_DAL; - break; - - case a_THAL: // exceptions - tempc = a_s_THAL; - break; - - case a_REH: // exceptions - tempc = a_s_REH; - break; - - case a_ZAIN: // exceptions - tempc = a_s_ZAIN; - break; - - case a_TATWEEL: // exceptions - tempc = cur_c; - break; - - case a_WAW: // exceptions - tempc = a_s_WAW; - break; - - case a_ALEF_MAKSURA: // exceptions - tempc = a_s_ALEF_MAKSURA; - break; - - case a_BEH: - tempc = a_i_BEH; - break; - - case a_TEH: - tempc = a_i_TEH; - break; - - case a_THEH: - tempc = a_i_THEH; - break; - - case a_JEEM: - tempc = a_i_JEEM; - break; - - case a_HAH: - tempc = a_i_HAH; - break; - - case a_KHAH: - tempc = a_i_KHAH; - break; - - case a_SEEN: - tempc = a_i_SEEN; - break; - - case a_SHEEN: - tempc = a_i_SHEEN; - break; - - case a_SAD: - tempc = a_i_SAD; - break; - - case a_DAD: - tempc = a_i_DAD; - break; - - case a_TAH: - tempc = a_i_TAH; - break; - - case a_ZAH: - tempc = a_i_ZAH; - break; - - case a_AIN: - tempc = a_i_AIN; - break; - - case a_GHAIN: - tempc = a_i_GHAIN; - break; - - case a_FEH: - tempc = a_i_FEH; - break; - - case a_QAF: - tempc = a_i_QAF; - break; - - case a_KAF: - tempc = a_i_KAF; - break; - - case a_LAM: - tempc = a_i_LAM; - break; - - case a_MEEM: - tempc = a_i_MEEM; - break; - - case a_NOON: - tempc = a_i_NOON; - break; - - case a_HEH: - tempc = a_i_HEH; - break; - - case a_YEH: - tempc = a_i_YEH; - break; - - default: - tempc = 0; + case a_YEH_HAMZA: return a_i_YEH_HAMZA; + case a_HAMZA: return a_s_HAMZA; // exceptions + case a_ALEF_MADDA: return a_s_ALEF_MADDA; // exceptions + case a_ALEF_HAMZA_ABOVE: return a_s_ALEF_HAMZA_ABOVE; // exceptions + case a_WAW_HAMZA: return a_s_WAW_HAMZA; // exceptions + case a_ALEF_HAMZA_BELOW: return a_s_ALEF_HAMZA_BELOW; // exceptions + case a_ALEF: return a_s_ALEF; // exceptions + case a_TEH_MARBUTA: return a_s_TEH_MARBUTA; // exceptions + case a_DAL: return a_s_DAL; // exceptions + case a_THAL: return a_s_THAL; // exceptions + case a_REH: return a_s_REH; // exceptions + case a_ZAIN: return a_s_ZAIN; // exceptions + case a_TATWEEL: return cur_c; // exceptions + case a_WAW: return a_s_WAW; // exceptions + case a_ALEF_MAKSURA: return a_s_ALEF_MAKSURA; // exceptions + case a_BEH: return a_i_BEH; + case a_TEH: return a_i_TEH; + case a_THEH: return a_i_THEH; + case a_JEEM: return a_i_JEEM; + case a_HAH: return a_i_HAH; + case a_KHAH: return a_i_KHAH; + case a_SEEN: return a_i_SEEN; + case a_SHEEN: return a_i_SHEEN; + case a_SAD: return a_i_SAD; + case a_DAD: return a_i_DAD; + case a_TAH: return a_i_TAH; + case a_ZAH: return a_i_ZAH; + case a_AIN: return a_i_AIN; + case a_GHAIN: return a_i_GHAIN; + case a_FEH: return a_i_FEH; + case a_QAF: return a_i_QAF; + case a_KAF: return a_i_KAF; + case a_LAM: return a_i_LAM; + case a_MEEM: return a_i_MEEM; + case a_NOON: return a_i_NOON; + case a_HEH: return a_i_HEH; + case a_YEH: return a_i_YEH; } - - return tempc; + return 0; } // Change shape - from ISO-8859-6/Isolated to Medial static int chg_c_a2m(int cur_c) { - int tempc; - switch (cur_c) { - case a_HAMZA: // exception - tempc = a_s_HAMZA; - break; - - case a_ALEF_MADDA: // exception - tempc = a_f_ALEF_MADDA; - break; - - case a_ALEF_HAMZA_ABOVE: // exception - tempc = a_f_ALEF_HAMZA_ABOVE; - break; - - case a_WAW_HAMZA: // exception - tempc = a_f_WAW_HAMZA; - break; - - case a_ALEF_HAMZA_BELOW: // exception - tempc = a_f_ALEF_HAMZA_BELOW; - break; - - case a_YEH_HAMZA: - tempc = a_m_YEH_HAMZA; - break; - - case a_ALEF: // exception - tempc = a_f_ALEF; - break; - - case a_BEH: - tempc = a_m_BEH; - break; - - case a_TEH_MARBUTA: // exception - tempc = a_f_TEH_MARBUTA; - break; - - case a_TEH: - tempc = a_m_TEH; - break; - - case a_THEH: - tempc = a_m_THEH; - break; - - case a_JEEM: - tempc = a_m_JEEM; - break; - - case a_HAH: - tempc = a_m_HAH; - break; - - case a_KHAH: - tempc = a_m_KHAH; - break; - - case a_DAL: // exception - tempc = a_f_DAL; - break; - - case a_THAL: // exception - tempc = a_f_THAL; - break; - - case a_REH: // exception - tempc = a_f_REH; - break; - - case a_ZAIN: // exception - tempc = a_f_ZAIN; - break; - - case a_SEEN: - tempc = a_m_SEEN; - break; - - case a_SHEEN: - tempc = a_m_SHEEN; - break; - - case a_SAD: - tempc = a_m_SAD; - break; - - case a_DAD: - tempc = a_m_DAD; - break; - - case a_TAH: - tempc = a_m_TAH; - break; - - case a_ZAH: - tempc = a_m_ZAH; - break; - - case a_AIN: - tempc = a_m_AIN; - break; - - case a_GHAIN: - tempc = a_m_GHAIN; - break; - - case a_TATWEEL: // exception - tempc = cur_c; - break; - - case a_FEH: - tempc = a_m_FEH; - break; - - case a_QAF: - tempc = a_m_QAF; - break; - - case a_KAF: - tempc = a_m_KAF; - break; - - case a_LAM: - tempc = a_m_LAM; - break; - - case a_MEEM: - tempc = a_m_MEEM; - break; - - case a_NOON: - tempc = a_m_NOON; - break; - - case a_HEH: - tempc = a_m_HEH; - break; - - case a_WAW: // exception - tempc = a_f_WAW; - break; - - case a_ALEF_MAKSURA: // exception - tempc = a_f_ALEF_MAKSURA; - break; - - case a_YEH: - tempc = a_m_YEH; - break; - - default: - tempc = 0; + case a_HAMZA: return a_s_HAMZA; // exception + case a_ALEF_MADDA: return a_f_ALEF_MADDA; // exception + case a_ALEF_HAMZA_ABOVE: return a_f_ALEF_HAMZA_ABOVE; // exception + case a_WAW_HAMZA: return a_f_WAW_HAMZA; // exception + case a_ALEF_HAMZA_BELOW: return a_f_ALEF_HAMZA_BELOW; // exception + case a_YEH_HAMZA: return a_m_YEH_HAMZA; + case a_ALEF: return a_f_ALEF; // exception + case a_BEH: return a_m_BEH; + case a_TEH_MARBUTA: return a_f_TEH_MARBUTA; // exception + case a_TEH: return a_m_TEH; + case a_THEH: return a_m_THEH; + case a_JEEM: return a_m_JEEM; + case a_HAH: return a_m_HAH; + case a_KHAH: return a_m_KHAH; + case a_DAL: return a_f_DAL; // exception + case a_THAL: return a_f_THAL; // exception + case a_REH: return a_f_REH; // exception + case a_ZAIN: return a_f_ZAIN; // exception + case a_SEEN: return a_m_SEEN; + case a_SHEEN: return a_m_SHEEN; + case a_SAD: return a_m_SAD; + case a_DAD: return a_m_DAD; + case a_TAH: return a_m_TAH; + case a_ZAH: return a_m_ZAH; + case a_AIN: return a_m_AIN; + case a_GHAIN: return a_m_GHAIN; + case a_TATWEEL: return cur_c; // exception + case a_FEH: return a_m_FEH; + case a_QAF: return a_m_QAF; + case a_KAF: return a_m_KAF; + case a_LAM: return a_m_LAM; + case a_MEEM: return a_m_MEEM; + case a_NOON: return a_m_NOON; + case a_HEH: return a_m_HEH; + case a_WAW: return a_f_WAW; // exception + case a_ALEF_MAKSURA: return a_f_ALEF_MAKSURA; // exception + case a_YEH: return a_m_YEH; } - - return tempc; + return 0; } // Change shape - from ISO-8859-6/Isolated to final static int chg_c_a2f(int cur_c) { - int tempc; - // NOTE: these encodings need to be accounted for // // a_f_ALEF_MADDA; @@ -888,280 +538,87 @@ static int chg_c_a2f(int cur_c) // a_f_LAM_ALEF_HAMZA_BELOW; switch (cur_c) { - case a_HAMZA: // exception - tempc = a_s_HAMZA; - break; - - case a_ALEF_MADDA: - tempc = a_f_ALEF_MADDA; - break; - - case a_ALEF_HAMZA_ABOVE: - tempc = a_f_ALEF_HAMZA_ABOVE; - break; - - case a_WAW_HAMZA: - tempc = a_f_WAW_HAMZA; - break; - - case a_ALEF_HAMZA_BELOW: - tempc = a_f_ALEF_HAMZA_BELOW; - break; - - case a_YEH_HAMZA: - tempc = a_f_YEH_HAMZA; - break; - - case a_ALEF: - tempc = a_f_ALEF; - break; - - case a_BEH: - tempc = a_f_BEH; - break; - - case a_TEH_MARBUTA: - tempc = a_f_TEH_MARBUTA; - break; - - case a_TEH: - tempc = a_f_TEH; - break; - - case a_THEH: - tempc = a_f_THEH; - break; - - case a_JEEM: - tempc = a_f_JEEM; - break; - - case a_HAH: - tempc = a_f_HAH; - break; - - case a_KHAH: - tempc = a_f_KHAH; - break; - - case a_DAL: - tempc = a_f_DAL; - break; - - case a_THAL: - tempc = a_f_THAL; - break; - - case a_REH: - tempc = a_f_REH; - break; - - case a_ZAIN: - tempc = a_f_ZAIN; - break; - - case a_SEEN: - tempc = a_f_SEEN; - break; - - case a_SHEEN: - tempc = a_f_SHEEN; - break; - - case a_SAD: - tempc = a_f_SAD; - break; - - case a_DAD: - tempc = a_f_DAD; - break; - - case a_TAH: - tempc = a_f_TAH; - break; - - case a_ZAH: - tempc = a_f_ZAH; - break; - - case a_AIN: - tempc = a_f_AIN; - break; - - case a_GHAIN: - tempc = a_f_GHAIN; - break; - - case a_TATWEEL: // exception - tempc = cur_c; - break; - - case a_FEH: - tempc = a_f_FEH; - break; - - case a_QAF: - tempc = a_f_QAF; - break; - - case a_KAF: - tempc = a_f_KAF; - break; - - case a_LAM: - tempc = a_f_LAM; - break; - - case a_MEEM: - tempc = a_f_MEEM; - break; - - case a_NOON: - tempc = a_f_NOON; - break; - - case a_HEH: - tempc = a_f_HEH; - break; - - case a_WAW: - tempc = a_f_WAW; - break; - - case a_ALEF_MAKSURA: - tempc = a_f_ALEF_MAKSURA; - break; - - case a_YEH: - tempc = a_f_YEH; - break; - - default: - tempc = 0; + case a_HAMZA: return a_s_HAMZA; // exception + case a_ALEF_MADDA: return a_f_ALEF_MADDA; + case a_ALEF_HAMZA_ABOVE: return a_f_ALEF_HAMZA_ABOVE; + case a_WAW_HAMZA: return a_f_WAW_HAMZA; + case a_ALEF_HAMZA_BELOW: return a_f_ALEF_HAMZA_BELOW; + case a_YEH_HAMZA: return a_f_YEH_HAMZA; + case a_ALEF: return a_f_ALEF; + case a_BEH: return a_f_BEH; + case a_TEH_MARBUTA: return a_f_TEH_MARBUTA; + case a_TEH: return a_f_TEH; + case a_THEH: return a_f_THEH; + case a_JEEM: return a_f_JEEM; + case a_HAH: return a_f_HAH; + case a_KHAH: return a_f_KHAH; + case a_DAL: return a_f_DAL; + case a_THAL: return a_f_THAL; + case a_REH: return a_f_REH; + case a_ZAIN: return a_f_ZAIN; + case a_SEEN: return a_f_SEEN; + case a_SHEEN: return a_f_SHEEN; + case a_SAD: return a_f_SAD; + case a_DAD: return a_f_DAD; + case a_TAH: return a_f_TAH; + case a_ZAH: return a_f_ZAH; + case a_AIN: return a_f_AIN; + case a_GHAIN: return a_f_GHAIN; + case a_TATWEEL: return cur_c; // exception + case a_FEH: return a_f_FEH; + case a_QAF: return a_f_QAF; + case a_KAF: return a_f_KAF; + case a_LAM: return a_f_LAM; + case a_MEEM: return a_f_MEEM; + case a_NOON: return a_f_NOON; + case a_HEH: return a_f_HEH; + case a_WAW: return a_f_WAW; + case a_ALEF_MAKSURA: return a_f_ALEF_MAKSURA; + case a_YEH: return a_f_YEH; } - - return tempc; + return 0; } // Change shape - from Initial to Medial static int chg_c_i2m(int cur_c) { - int tempc; - switch (cur_c) { - case a_i_YEH_HAMZA: - tempc = a_m_YEH_HAMZA; - break; - - case a_i_BEH: - tempc = a_m_BEH; - break; - - case a_i_TEH: - tempc = a_m_TEH; - break; - - case a_i_THEH: - tempc = a_m_THEH; - break; - - case a_i_JEEM: - tempc = a_m_JEEM; - break; - - case a_i_HAH: - tempc = a_m_HAH; - break; - - case a_i_KHAH: - tempc = a_m_KHAH; - break; - - case a_i_SEEN: - tempc = a_m_SEEN; - break; - - case a_i_SHEEN: - tempc = a_m_SHEEN; - break; - - case a_i_SAD: - tempc = a_m_SAD; - break; - - case a_i_DAD: - tempc = a_m_DAD; - break; - - case a_i_TAH: - tempc = a_m_TAH; - break; - - case a_i_ZAH: - tempc = a_m_ZAH; - break; - - case a_i_AIN: - tempc = a_m_AIN; - break; - - case a_i_GHAIN: - tempc = a_m_GHAIN; - break; - - case a_i_FEH: - tempc = a_m_FEH; - break; - - case a_i_QAF: - tempc = a_m_QAF; - break; - - case a_i_KAF: - tempc = a_m_KAF; - break; - - case a_i_LAM: - tempc = a_m_LAM; - break; - - case a_i_MEEM: - tempc = a_m_MEEM; - break; - - case a_i_NOON: - tempc = a_m_NOON; - break; - - case a_i_HEH: - tempc = a_m_HEH; - break; - - case a_i_YEH: - tempc = a_m_YEH; - break; - - default: - tempc = 0; + case a_i_YEH_HAMZA: return a_m_YEH_HAMZA; + case a_i_BEH: return a_m_BEH; + case a_i_TEH: return a_m_TEH; + case a_i_THEH: return a_m_THEH; + case a_i_JEEM: return a_m_JEEM; + case a_i_HAH: return a_m_HAH; + case a_i_KHAH: return a_m_KHAH; + case a_i_SEEN: return a_m_SEEN; + case a_i_SHEEN: return a_m_SHEEN; + case a_i_SAD: return a_m_SAD; + case a_i_DAD: return a_m_DAD; + case a_i_TAH: return a_m_TAH; + case a_i_ZAH: return a_m_ZAH; + case a_i_AIN: return a_m_AIN; + case a_i_GHAIN: return a_m_GHAIN; + case a_i_FEH: return a_m_FEH; + case a_i_QAF: return a_m_QAF; + case a_i_KAF: return a_m_KAF; + case a_i_LAM: return a_m_LAM; + case a_i_MEEM: return a_m_MEEM; + case a_i_NOON: return a_m_NOON; + case a_i_HEH: return a_m_HEH; + case a_i_YEH: return a_m_YEH; } - - return tempc; + return 0; } // Change shape - from Final to Medial static int chg_c_f2m(int cur_c) { - int tempc; - switch (cur_c) { // NOTE: these encodings are multi-positional, no ? // case a_f_ALEF_MADDA: // case a_f_ALEF_HAMZA_ABOVE: // case a_f_ALEF_HAMZA_BELOW: - case a_f_YEH_HAMZA: - tempc = a_m_YEH_HAMZA; - break; - + case a_f_YEH_HAMZA: return a_m_YEH_HAMZA; case a_f_WAW_HAMZA: // exceptions case a_f_ALEF: case a_f_TEH_MARBUTA: @@ -1171,165 +628,60 @@ static int chg_c_f2m(int cur_c) case a_f_ZAIN: case a_f_WAW: case a_f_ALEF_MAKSURA: - tempc = cur_c; - break; - - case a_f_BEH: - tempc = a_m_BEH; - break; - - case a_f_TEH: - tempc = a_m_TEH; - break; - - case a_f_THEH: - tempc = a_m_THEH; - break; - - case a_f_JEEM: - tempc = a_m_JEEM; - break; - - case a_f_HAH: - tempc = a_m_HAH; - break; - - case a_f_KHAH: - tempc = a_m_KHAH; - break; - - case a_f_SEEN: - tempc = a_m_SEEN; - break; - - case a_f_SHEEN: - tempc = a_m_SHEEN; - break; - - case a_f_SAD: - tempc = a_m_SAD; - break; - - case a_f_DAD: - tempc = a_m_DAD; - break; - - case a_f_TAH: - tempc = a_m_TAH; - break; - - case a_f_ZAH: - tempc = a_m_ZAH; - break; - - case a_f_AIN: - tempc = a_m_AIN; - break; - - case a_f_GHAIN: - tempc = a_m_GHAIN; - break; - - case a_f_FEH: - tempc = a_m_FEH; - break; - - case a_f_QAF: - tempc = a_m_QAF; - break; - - case a_f_KAF: - tempc = a_m_KAF; - break; - - case a_f_LAM: - tempc = a_m_LAM; - break; - - case a_f_MEEM: - tempc = a_m_MEEM; - break; - - case a_f_NOON: - tempc = a_m_NOON; - break; - - case a_f_HEH: - tempc = a_m_HEH; - break; - - case a_f_YEH: - tempc = a_m_YEH; - break; - + return cur_c; + case a_f_BEH: return a_m_BEH; + case a_f_TEH: return a_m_TEH; + case a_f_THEH: return a_m_THEH; + case a_f_JEEM: return a_m_JEEM; + case a_f_HAH: return a_m_HAH; + case a_f_KHAH: return a_m_KHAH; + case a_f_SEEN: return a_m_SEEN; + case a_f_SHEEN: return a_m_SHEEN; + case a_f_SAD: return a_m_SAD; + case a_f_DAD: return a_m_DAD; + case a_f_TAH: return a_m_TAH; + case a_f_ZAH: return a_m_ZAH; + case a_f_AIN: return a_m_AIN; + case a_f_GHAIN: return a_m_GHAIN; + case a_f_FEH: return a_m_FEH; + case a_f_QAF: return a_m_QAF; + case a_f_KAF: return a_m_KAF; + case a_f_LAM: return a_m_LAM; + case a_f_MEEM: return a_m_MEEM; + case a_f_NOON: return a_m_NOON; + case a_f_HEH: return a_m_HEH; + case a_f_YEH: return a_m_YEH; // NOTE: these encodings are multi-positional, no ? // case a_f_LAM_ALEF_MADDA_ABOVE: // case a_f_LAM_ALEF_HAMZA_ABOVE: // case a_f_LAM_ALEF_HAMZA_BELOW: // case a_f_LAM_ALEF: - default: - tempc = 0; } - - return tempc; + return 0; } // Change shape - from Combination (2 char) to an Isolated. static int chg_c_laa2i(int hid_c) { - int tempc; - switch (hid_c) { - case a_ALEF_MADDA: - tempc = a_s_LAM_ALEF_MADDA_ABOVE; - break; - - case a_ALEF_HAMZA_ABOVE: - tempc = a_s_LAM_ALEF_HAMZA_ABOVE; - break; - - case a_ALEF_HAMZA_BELOW: - tempc = a_s_LAM_ALEF_HAMZA_BELOW; - break; - - case a_ALEF: - tempc = a_s_LAM_ALEF; - break; - - default: - tempc = 0; + case a_ALEF_MADDA: return a_s_LAM_ALEF_MADDA_ABOVE; + case a_ALEF_HAMZA_ABOVE: return a_s_LAM_ALEF_HAMZA_ABOVE; + case a_ALEF_HAMZA_BELOW: return a_s_LAM_ALEF_HAMZA_BELOW; + case a_ALEF: return a_s_LAM_ALEF; } - - return tempc; + return 0; } // Change shape - from Combination-Isolated to Final. static int chg_c_laa2f(int hid_c) { - int tempc; - switch (hid_c) { - case a_ALEF_MADDA: - tempc = a_f_LAM_ALEF_MADDA_ABOVE; - break; - - case a_ALEF_HAMZA_ABOVE: - tempc = a_f_LAM_ALEF_HAMZA_ABOVE; - break; - - case a_ALEF_HAMZA_BELOW: - tempc = a_f_LAM_ALEF_HAMZA_BELOW; - break; - - case a_ALEF: - tempc = a_f_LAM_ALEF; - break; - - default: - tempc = 0; + case a_ALEF_MADDA: return a_f_LAM_ALEF_MADDA_ABOVE; + case a_ALEF_HAMZA_ABOVE: return a_f_LAM_ALEF_HAMZA_ABOVE; + case a_ALEF_HAMZA_BELOW: return a_f_LAM_ALEF_HAMZA_BELOW; + case a_ALEF: return a_f_LAM_ALEF; } - - return tempc; + return 0; } // Do "half-shaping" on character "c". Return zero if no shaping. diff --git a/src/nvim/ascii.h b/src/nvim/ascii.h index adde91f9ec..ff6840d690 100644 --- a/src/nvim/ascii.h +++ b/src/nvim/ascii.h @@ -3,6 +3,7 @@ #include <stdbool.h> +#include "nvim/macros.h" #include "nvim/func_attr.h" #include "nvim/os/os_defs.h" @@ -98,6 +99,10 @@ static inline bool ascii_isxdigit(int) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; +static inline bool ascii_isident(int) + REAL_FATTR_CONST + REAL_FATTR_ALWAYS_INLINE; + static inline bool ascii_isbdigit(int) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; @@ -138,6 +143,14 @@ static inline bool ascii_isxdigit(int c) || (c >= 'A' && c <= 'F'); } +/// Checks if `c` is an “identifier†character +/// +/// That is, whether it is alphanumeric character or underscore. +static inline bool ascii_isident(int c) +{ + return ASCII_ISALNUM(c) || c == '_'; +} + /// Checks if `c` is a binary digit, that is, 0-1. /// /// @see {ascii_isdigit} diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 766003a021..0cd6f628b5 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -129,7 +129,7 @@ read_buffer( if (read_stdin) { // Set or reset 'modified' before executing autocommands, so that // it can be changed there. - if (!readonlymode && !bufempty()) { + if (!readonlymode && !BUFEMPTY()) { changed(); } else if (retval != FAIL) { unchanged(curbuf, false); @@ -1387,31 +1387,40 @@ void set_curbuf(buf_T *buf, int action) /* Don't restart Select mode after switching to another buffer. */ VIsual_reselect = FALSE; - /* close_windows() or apply_autocmds() may change curbuf */ + // close_windows() or apply_autocmds() may change curbuf and wipe out "buf" prevbuf = curbuf; - bufref_T bufref; - set_bufref(&bufref, prevbuf); + bufref_T newbufref; + bufref_T prevbufref; + set_bufref(&prevbufref, prevbuf); + set_bufref(&newbufref, buf); + // Autocommands may delete the curren buffer and/or the buffer we wan to go + // to. In those cases don't close the buffer. if (!apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, false, curbuf) - || (bufref_valid(&bufref) && !aborting())) { + || (bufref_valid(&prevbufref) && bufref_valid(&newbufref) + && !aborting())) { if (prevbuf == curwin->w_buffer) { reset_synblock(curwin); } if (unload) { close_windows(prevbuf, false); } - if (bufref_valid(&bufref) && !aborting()) { + if (bufref_valid(&prevbufref) && !aborting()) { win_T *previouswin = curwin; - if (prevbuf == curbuf) - u_sync(FALSE); - close_buffer(prevbuf == curwin->w_buffer ? curwin : NULL, prevbuf, - unload ? action : (action == DOBUF_GOTO - && !P_HID(prevbuf) - && !bufIsChanged( - prevbuf)) ? DOBUF_UNLOAD : 0, FALSE); - if (curwin != previouswin && win_valid(previouswin)) - /* autocommands changed curwin, Grr! */ + if (prevbuf == curbuf) { + u_sync(false); + } + close_buffer(prevbuf == curwin->w_buffer ? curwin : NULL, + prevbuf, + unload + ? action + : (action == DOBUF_GOTO && !buf_hide(prevbuf) + && !bufIsChanged(prevbuf)) ? DOBUF_UNLOAD : 0, + false); + if (curwin != previouswin && win_valid(previouswin)) { + // autocommands changed curwin, Grr! curwin = previouswin; + } } } /* An autocommand may have deleted "buf", already entered it (e.g., when @@ -1616,7 +1625,7 @@ buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags) && curbuf != NULL && curbuf->b_ffname == NULL && curbuf->b_nwindows <= 1 - && (curbuf->b_ml.ml_mfp == NULL || bufempty())) { + && (curbuf->b_ml.ml_mfp == NULL || BUFEMPTY())) { buf = curbuf; /* It's like this buffer is deleted. Watch out for autocommands that * change curbuf! If that happens, allocate a new buffer anyway. */ @@ -1775,6 +1784,7 @@ void free_buf_options(buf_T *buf, int free_p_ff) clear_string_option(&buf->b_p_flp); clear_string_option(&buf->b_p_isk); clear_string_option(&buf->b_p_keymap); + keymap_ga_clear(&buf->b_kmap_ga); ga_clear(&buf->b_kmap_ga); clear_string_option(&buf->b_p_com); clear_string_option(&buf->b_p_cms); @@ -1808,6 +1818,7 @@ void free_buf_options(buf_T *buf, int free_p_ff) buf->b_p_ul = NO_LOCAL_UNDOLEVEL; clear_string_option(&buf->b_p_lw); clear_string_option(&buf->b_p_bkc); + clear_string_option(&buf->b_p_menc); } @@ -1870,7 +1881,7 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit) // If 'switchbuf' contains "split", "vsplit" or "newtab" and the // current buffer isn't empty: open new tab or window if (wp == NULL && (swb_flags & (SWB_VSPLIT | SWB_SPLIT | SWB_NEWTAB)) - && !bufempty()) { + && !BUFEMPTY()) { if (swb_flags & SWB_NEWTAB) { tabpage_new(); } else if (win_split(0, (swb_flags & SWB_VSPLIT) ? WSP_VERT : 0) @@ -2672,7 +2683,7 @@ void buflist_altfpos(win_T *win) } /// Check that "ffname" is not the same file as current file. -/// Fname must have a full path (expanded by path_get_absolute_path()). +/// Fname must have a full path (expanded by path_to_absolute()). /// /// @param ffname full path name to check bool otherfile(char_u *ffname) @@ -2682,7 +2693,7 @@ bool otherfile(char_u *ffname) } /// Check that "ffname" is not the same file as the file loaded in "buf". -/// Fname must have a full path (expanded by path_get_absolute_path()). +/// Fname must have a full path (expanded by path_to_absolute()). /// /// @param buf buffer to check /// @param ffname full path name to check @@ -4406,12 +4417,12 @@ do_arg_all ( } wp->w_arg_idx = i; - if (i == opened_len && !keep_tabs) { /* close this window */ - if (P_HID(buf) || forceit || buf->b_nwindows > 1 + if (i == opened_len && !keep_tabs) { // close this window + if (buf_hide(buf) || forceit || buf->b_nwindows > 1 || !bufIsChanged(buf)) { /* If the buffer was changed, and we would like to hide it, * try autowriting. */ - if (!P_HID(buf) && buf->b_nwindows <= 1 && bufIsChanged(buf)) { + if (!buf_hide(buf) && buf->b_nwindows <= 1 && bufIsChanged(buf)) { bufref_T bufref; set_bufref(&bufref, buf); (void)autowrite(buf, false); @@ -4426,7 +4437,7 @@ do_arg_all ( && (first_tabpage->tp_next == NULL || !had_tab)) { use_firstwin = true; } else { - win_close(wp, !P_HID(buf) && !bufIsChanged(buf)); + win_close(wp, !buf_hide(buf) && !bufIsChanged(buf)); // check if autocommands removed the next window if (!win_valid(wpnext)) { // start all over... @@ -4460,11 +4471,12 @@ do_arg_all ( last_curwin = curwin; last_curtab = curtab; win_enter(lastwin, false); - /* ":drop all" should re-use an empty window to avoid "--remote-tab" - * leaving an empty tab page when executed locally. */ - if (keep_tabs && bufempty() && curbuf->b_nwindows == 1 - && curbuf->b_ffname == NULL && !curbuf->b_changed) - use_firstwin = TRUE; + // ":drop all" should re-use an empty window to avoid "--remote-tab" + // leaving an empty tab page when executed locally. + if (keep_tabs && BUFEMPTY() && curbuf->b_nwindows == 1 + && curbuf->b_ffname == NULL && !curbuf->b_changed) { + use_firstwin = true; + } for (i = 0; i < count && i < opened_len && !got_int; ++i) { if (alist == &global_alist && i == global_alist.al_ga.ga_len - 1) @@ -4503,14 +4515,15 @@ do_arg_all ( new_curwin = curwin; new_curtab = curtab; } - (void)do_ecmd(0, alist_name(&AARGLIST(alist)[i]), NULL, NULL, - ECMD_ONE, - ((P_HID(curwin->w_buffer) - || bufIsChanged(curwin->w_buffer)) ? ECMD_HIDE : 0) - + ECMD_OLDBUF, curwin); - if (use_firstwin) - ++autocmd_no_leave; - use_firstwin = FALSE; + (void)do_ecmd(0, alist_name(&AARGLIST(alist)[i]), NULL, NULL, ECMD_ONE, + ((buf_hide(curwin->w_buffer) + || bufIsChanged(curwin->w_buffer)) + ? ECMD_HIDE : 0) + ECMD_OLDBUF, + curwin); + if (use_firstwin) { + autocmd_no_leave++; + } + use_firstwin = false; } os_breakcheck(); @@ -4697,14 +4710,14 @@ void ex_buffer_all(exarg_T *eap) * Close superfluous windows. */ for (wp = lastwin; open_wins > count; ) { - r = (P_HID(wp->w_buffer) || !bufIsChanged(wp->w_buffer) - || autowrite(wp->w_buffer, FALSE) == OK); + r = (buf_hide(wp->w_buffer) || !bufIsChanged(wp->w_buffer) + || autowrite(wp->w_buffer, false) == OK); if (!win_valid(wp)) { /* BufWrite Autocommands made the window invalid, start over */ wp = lastwin; } else if (r) { - win_close(wp, !P_HID(wp->w_buffer)); - --open_wins; + win_close(wp, !buf_hide(wp->w_buffer)); + open_wins--; wp = lastwin; } else { wp = wp->w_prev; diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index f1cbcb2627..8de4286216 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -637,6 +637,7 @@ struct file_buffer { uint32_t b_p_fex_flags; ///< flags for 'formatexpr' char_u *b_p_kp; ///< 'keywordprg' int b_p_lisp; ///< 'lisp' + char_u *b_p_menc; ///< 'makeencoding' char_u *b_p_mps; ///< 'matchpairs' int b_p_ml; ///< 'modeline' int b_p_ml_nobin; ///< b_p_ml saved for binary mode @@ -717,6 +718,7 @@ struct file_buffer { int b_ind_hash_comment; int b_ind_cpp_namespace; int b_ind_if_for_while; + int b_ind_cpp_extern_c; linenr_T b_no_eol_lnum; /* non-zero lnum when last line of next binary * write should not have an end-of-line */ diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 40af470bde..776e2bfa86 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -4,6 +4,7 @@ #include "nvim/api/ui.h" #include "nvim/channel.h" #include "nvim/eval.h" +#include "nvim/eval/encode.h" #include "nvim/event/socket.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/server.h" @@ -179,10 +180,12 @@ static Channel *channel_alloc(ChannelStreamType type) } /// Not implemented, only logging for now -void channel_create_event(Channel *chan, char *ext_source) +void channel_create_event(Channel *chan, const char *ext_source) { #if MIN_LOG_LEVEL <= INFO_LOG_LEVEL - char *stream_desc, *mode_desc, *source; + const char *stream_desc; + const char *mode_desc; + const char *source; switch (chan->streamtype) { case kChannelStreamProc: @@ -222,8 +225,8 @@ void channel_create_event(Channel *chan, char *ext_source) // external events should be included. source = ext_source; } else { - eval_format_source_name_line((char *)IObuff, sizeof(IObuff)); - source = (char *)IObuff; + eval_fmt_source_name_line((char *)IObuff, sizeof(IObuff)); + source = (const char *)IObuff; } ILOG("new channel %" PRIu64 " (%s%s): %s", chan->id, stream_desc, @@ -234,15 +237,16 @@ void channel_create_event(Channel *chan, char *ext_source) #endif } -void channel_incref(Channel *channel) +void channel_incref(Channel *chan) { - channel->refcount++; + chan->refcount++; } -void channel_decref(Channel *channel) +void channel_decref(Channel *chan) { - if (!(--channel->refcount)) { - multiqueue_put(main_loop.fast_events, free_channel_event, 1, channel); + if (!(--chan->refcount)) { + // delay free, so that libuv is done with the handles + multiqueue_put(main_loop.events, free_channel_event, 1, chan); } } @@ -264,18 +268,18 @@ void callback_reader_start(CallbackReader *reader) static void free_channel_event(void **argv) { - Channel *channel = argv[0]; - if (channel->is_rpc) { - rpc_free(channel); + Channel *chan = argv[0]; + if (chan->is_rpc) { + rpc_free(chan); } - callback_reader_free(&channel->on_stdout); - callback_reader_free(&channel->on_stderr); - callback_free(&channel->on_exit); + callback_reader_free(&chan->on_stdout); + callback_reader_free(&chan->on_stderr); + callback_free(&chan->on_exit); - pmap_del(uint64_t)(channels, channel->id); - multiqueue_free(channel->events); - xfree(channel); + pmap_del(uint64_t)(channels, chan->id); + multiqueue_free(chan->events); + xfree(chan); } static void channel_destroy_early(Channel *chan) @@ -283,12 +287,15 @@ static void channel_destroy_early(Channel *chan) if ((chan->id != --next_chan_id)) { abort(); } + pmap_del(uint64_t)(channels, chan->id); + chan->id = 0; if ((--chan->refcount != 0)) { abort(); } - free_channel_event((void **)&chan); + // uv will keep a reference to handles until next loop tick, so delay free + multiqueue_put(main_loop.events, free_channel_event, 1, chan); } @@ -391,17 +398,22 @@ uint64_t channel_connect(bool tcp, const char *address, bool rpc, CallbackReader on_output, int timeout, const char **error) { + Channel *channel; + if (!tcp && rpc) { char *path = fix_fname(address); - if (server_owns_pipe_address(path)) { - // avoid deadlock - xfree(path); - return channel_create_internal_rpc(); - } + bool loopback = server_owns_pipe_address(path); xfree(path); + if (loopback) { + // Create a loopback channel. This avoids deadlock if nvim connects to + // its own named pipe. + channel = channel_alloc(kChannelStreamInternal); + rpc_start(channel); + goto end; + } } - Channel *channel = channel_alloc(kChannelStreamSocket); + channel = channel_alloc(kChannelStreamSocket); if (!socket_connect(&main_loop, &channel->stream.socket, tcp, address, timeout, error)) { channel_destroy_early(channel); @@ -421,7 +433,8 @@ uint64_t channel_connect(bool tcp, const char *address, rstream_start(&channel->stream.socket, on_socket_output, channel); } - channel_create_event(channel, NULL); +end: + channel_create_event(channel, address); return channel->id; } @@ -440,15 +453,6 @@ void channel_from_connection(SocketWatcher *watcher) channel_create_event(channel, watcher->addr); } -/// Creates a loopback channel. This is used to avoid deadlock -/// when an instance connects to its own named pipe. -static uint64_t channel_create_internal_rpc(void) -{ - Channel *channel = channel_alloc(kChannelStreamInternal); - rpc_start(channel); - return channel->id; -} - /// Creates an API channel from stdin/stdout. This is used when embedding /// Neovim uint64_t channel_from_stdio(bool rpc, CallbackReader on_output, @@ -522,32 +526,21 @@ err: return 0; } -/// NB: mutates buf in place! -static list_T *buffer_to_tv_list(char *buf, size_t count) +/// Convert binary byte array to a readfile()-style list +/// +/// @param[in] buf Array to convert. +/// @param[in] len Array length. +/// +/// @return [allocated] Converted list. +static inline list_T *buffer_to_tv_list(const char *const buf, const size_t len) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE { - list_T *ret = tv_list_alloc(); - char *ptr = buf; - size_t remaining = count; - size_t off = 0; - - while (off < remaining) { - // append the line - if (ptr[off] == NL) { - tv_list_append_string(ret, ptr, (ssize_t)off); - size_t skip = off + 1; - ptr += skip; - remaining -= skip; - off = 0; - continue; - } - if (ptr[off] == NUL) { - // Translate NUL to NL - ptr[off] = NL; - } - off++; - } - tv_list_append_string(ret, ptr, (ssize_t)off); - return ret; + list_T *const l = tv_list_alloc(kListLenMayKnow); + // Empty buffer should be represented by [''], encode_list_write() thinks + // empty list is fine for the case. + tv_list_append_string(l, "", 0); + encode_list_write(l, buf, len); + return l; } // vimscript job callbacks must be executed on Nvim main loop @@ -599,6 +592,7 @@ static void on_stdio_input(Stream *stream, RBuffer *buf, size_t count, on_channel_output(stream, chan, buf, count, eof, &chan->on_stdout, "stdin"); } +/// @param type must have static lifetime static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, size_t count, bool eof, CallbackReader *reader, const char *type) @@ -613,14 +607,20 @@ static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, if (reader->cb.type != kCallbackNone) { process_channel_event(chan, &reader->cb, type, reader->buffer.ga_data, (size_t)reader->buffer.ga_len, 0); - ga_clear(&reader->buffer); } else if (reader->self) { - list_T *data = buffer_to_tv_list(reader->buffer.ga_data, - (size_t)reader->buffer.ga_len); - tv_dict_add_list(reader->self, type, strlen(type), data); + if (tv_dict_find(reader->self, type, -1) == NULL) { + list_T *data = buffer_to_tv_list(reader->buffer.ga_data, + (size_t)reader->buffer.ga_len); + tv_dict_add_list(reader->self, type, strlen(type), data); + } else { + // can't display error message now, defer it. + channel_incref(chan); + multiqueue_put(chan->events, on_buffered_error, 2, chan, type); + } } else { abort(); } + ga_clear(&reader->buffer); } else if (reader->cb.type != kCallbackNone) { process_channel_event(chan, &reader->cb, type, ptr, 0, 0); } @@ -641,6 +641,14 @@ static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, } } +static void on_buffered_error(void **args) +{ + Channel *chan = (Channel *)args[0]; + const char *stream = (const char *)args[1]; + EMSG3(_(e_streamkey), stream, chan->id); + channel_decref(chan); +} + static void channel_process_exit_cb(Process *proc, int status, void *data) { Channel *chan = data; @@ -673,7 +681,7 @@ static void on_channel_event(void **args) argv[1].v_type = VAR_LIST; argv[1].v_lock = VAR_UNLOCKED; argv[1].vval.v_list = ev->received; - argv[1].vval.v_list->lv_refcount++; + tv_list_ref(argv[1].vval.v_list); } else { argv[1].v_type = VAR_NUMBER; argv[1].v_lock = VAR_UNLOCKED; diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 577fc13a31..980b4ed426 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1611,141 +1611,145 @@ bool vim_isblankline(char_u *lbuf) /// If maxlen > 0, check at a maximum maxlen chars. /// /// @param start -/// @param prep Returns type of number 0 = decimal, 'x' or 'X' is hex, -/// '0' = octal, 'b' or 'B' is bin +/// @param prep Returns guessed type of number 0 = decimal, 'x' or 'X' is +/// hexadecimal, '0' = octal, 'b' or 'B' is binary. When using +/// STR2NR_FORCE is always zero. /// @param len Returns the detected length of number. -/// @param what Recognizes what number passed. +/// @param what Recognizes what number passed, @see ChStr2NrFlags. /// @param nptr Returns the signed result. /// @param unptr Returns the unsigned result. /// @param maxlen Max length of string to check. void vim_str2nr(const char_u *const start, int *const prep, int *const len, const int what, varnumber_T *const nptr, uvarnumber_T *const unptr, const int maxlen) + FUNC_ATTR_NONNULL_ARG(1) { - const char_u *ptr = start; + const char *ptr = (const char *)start; +#define STRING_ENDED(ptr) \ + (!(maxlen == 0 || (int)((ptr) - (const char *)start) < maxlen)) int pre = 0; // default is decimal - bool negative = false; + const bool negative = (ptr[0] == '-'); uvarnumber_T un = 0; - if (ptr[0] == '-') { - negative = true; + if (negative) { ptr++; } - // Recognize hex, octal and bin. - if ((ptr[0] == '0') && (ptr[1] != '8') && (ptr[1] != '9') - && (maxlen == 0 || maxlen > 1)) { + if (what & STR2NR_FORCE) { + // When forcing main consideration is skipping the prefix. Octal and decimal + // numbers have no prefixes to skip. pre is not set. + switch ((unsigned)what & (~(unsigned)STR2NR_FORCE)) { + case STR2NR_HEX: { + if (!STRING_ENDED(ptr + 2) + && ptr[0] == '0' + && (ptr[1] == 'x' || ptr[1] == 'X') + && ascii_isxdigit(ptr[2])) { + ptr += 2; + } + goto vim_str2nr_hex; + } + case STR2NR_BIN: { + if (!STRING_ENDED(ptr + 2) + && ptr[0] == '0' + && (ptr[1] == 'b' || ptr[1] == 'B') + && ascii_isbdigit(ptr[2])) { + ptr += 2; + } + goto vim_str2nr_bin; + } + case STR2NR_OCT: { + goto vim_str2nr_oct; + } + case 0: { + goto vim_str2nr_dec; + } + default: { + assert(false); + } + } + } else if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN)) + && !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 if ((what & STR2NR_HEX) - && ((pre == 'X') || (pre == 'x')) - && ascii_isxdigit(ptr[2]) - && (maxlen == 0 || maxlen > 2)) { - // hexadecimal + && !STRING_ENDED(ptr + 2) + && (pre == 'X' || pre == 'x') + && ascii_isxdigit(ptr[2])) { ptr += 2; - } else if ((what & STR2NR_BIN) - && ((pre == 'B') || (pre == 'b')) - && ascii_isbdigit(ptr[2]) - && (maxlen == 0 || maxlen > 2)) { - // binary + goto vim_str2nr_hex; + } + // Detect binary: 0b or 0B follwed by 0 or 1 + if ((what & STR2NR_BIN) + && !STRING_ENDED(ptr + 2) + && (pre == 'B' || pre == 'b') + && ascii_isbdigit(ptr[2])) { ptr += 2; - } else { - // decimal or octal, default is decimal - pre = 0; - - if (what & STR2NR_OCT) { - // Don't interpret "0", "08" or "0129" as octal. - for (int n = 1; ascii_isdigit(ptr[n]); ++n) { - if (ptr[n] > '7') { - // can't be octal - pre = 0; - break; - } - if (ptr[n] >= '0') { - // assume octal - pre = '0'; - } - if (n == maxlen) { - break; - } - } + goto vim_str2nr_bin; + } + // Detect octal number: zero followed by octal digits without '8' or '9' + pre = 0; + if (!(what & STR2NR_OCT) + || !('0' <= ptr[1] && ptr[1] <= '7')) { + goto vim_str2nr_dec; + } + for (int i = 2; !STRING_ENDED(ptr + i) && ascii_isdigit(ptr[i]); i++) { + if (ptr[i] > '7') { + goto vim_str2nr_dec; } } + pre = '0'; + goto vim_str2nr_oct; + } else { + goto vim_str2nr_dec; } // Do the string-to-numeric conversion "manually" to avoid sscanf quirks. - int n = 1; - if ((pre == 'B') || (pre == 'b') || what == STR2NR_BIN + STR2NR_FORCE) { - // bin - if (pre != 0) { - n += 2; // skip over "0b" + assert(false); // Should’ve used goto earlier. +#define PARSE_NUMBER(base, cond, conv) \ + do { \ + while (!STRING_ENDED(ptr) && (cond)) { \ + /* avoid ubsan error for overflow */ \ + if (un < UVARNUMBER_MAX / base) { \ + un = base * un + (uvarnumber_T)(conv); \ + } else { \ + un = UVARNUMBER_MAX; \ + } \ + ptr++; \ + } \ + } while (0) + switch (pre) { + case 'b': + case 'B': { +vim_str2nr_bin: + PARSE_NUMBER(2, (*ptr == '0' || *ptr == '1'), (*ptr - '0')); + break; } - while ('0' <= *ptr && *ptr <= '1') { - // avoid ubsan error for overflow - if (un < UVARNUMBER_MAX / 2) { - un = 2 * un + (uvarnumber_T)(*ptr - '0'); - } else { - un = UVARNUMBER_MAX; - } - ptr++; - if (n++ == maxlen) { - break; - } - } - } else if ((pre == '0') || what == STR2NR_OCT + STR2NR_FORCE) { - // octal - while ('0' <= *ptr && *ptr <= '7') { - // avoid ubsan error for overflow - if (un < UVARNUMBER_MAX / 8) { - un = 8 * un + (uvarnumber_T)(*ptr - '0'); - } else { - un = UVARNUMBER_MAX; - } - ptr++; - if (n++ == maxlen) { - break; - } + case '0': { +vim_str2nr_oct: + PARSE_NUMBER(8, ('0' <= *ptr && *ptr <= '7'), (*ptr - '0')); + break; } - } else if ((pre == 'X') || (pre == 'x') - || what == STR2NR_HEX + STR2NR_FORCE) { - // hex - if (pre != 0) { - n += 2; // skip over "0x" - } - while (ascii_isxdigit(*ptr)) { - // avoid ubsan error for overflow - if (un < UVARNUMBER_MAX / 16) { - un = 16 * un + (uvarnumber_T)hex2nr(*ptr); - } else { - un = UVARNUMBER_MAX; - } - ptr++; - if (n++ == maxlen) { - break; - } + case 0: { +vim_str2nr_dec: + PARSE_NUMBER(10, (ascii_isdigit(*ptr)), (*ptr - '0')); + break; } - } else { - // decimal - while (ascii_isdigit(*ptr)) { - // avoid ubsan error for overflow - if (un < UVARNUMBER_MAX / 10) { - un = 10 * un + (uvarnumber_T)(*ptr - '0'); - } else { - un = UVARNUMBER_MAX; - } - ptr++; - if (n++ == maxlen) { - break; - } + case 'x': + case 'X': { +vim_str2nr_hex: + PARSE_NUMBER(16, (ascii_isxdigit(*ptr)), (hex2nr(*ptr))); + break; } } +#undef PARSE_NUMBER if (prep != NULL) { *prep = pre; } if (len != NULL) { - *len = (int)(ptr - start); + *len = (int)(ptr - (const char *)start); } if (nptr != NULL) { @@ -1767,6 +1771,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, if (unptr != NULL) { *unptr = un; } +#undef STRING_ENDED } /// Return the value of a single hex character. diff --git a/src/nvim/charset.h b/src/nvim/charset.h index c69582c4c6..eb64b6128a 100644 --- a/src/nvim/charset.h +++ b/src/nvim/charset.h @@ -4,6 +4,7 @@ #include "nvim/types.h" #include "nvim/pos.h" #include "nvim/buffer_defs.h" +#include "nvim/eval/typval.h" /// Return the folded-case equivalent of the given character /// @@ -15,6 +16,21 @@ ?((int)(uint8_t)(c)) \ :((int)(c))) +/// Flags for vim_str2nr() +typedef enum { + STR2NR_DEC = 0, + STR2NR_BIN = (1 << 0), ///< Allow binary numbers. + STR2NR_OCT = (1 << 1), ///< Allow octal numbers. + STR2NR_HEX = (1 << 2), ///< Allow hexadecimal numbers. + /// Force one of the above variants. + /// + /// STR2NR_FORCE|STR2NR_DEC is actually not different from supplying zero + /// as flags, but still present for completeness. + STR2NR_FORCE = (1 << 3), + /// Recognize all formats vim_str2nr() can recognize. + STR2NR_ALL = STR2NR_BIN | STR2NR_OCT | STR2NR_HEX, +} ChStr2NrFlags; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "charset.h.generated.h" #endif diff --git a/src/nvim/cursor_shape.c b/src/nvim/cursor_shape.c index 97fc3a3ca3..b45e7002f7 100644 --- a/src/nvim/cursor_shape.c +++ b/src/nvim/cursor_shape.c @@ -102,11 +102,14 @@ char_u *parse_shape_opt(int what) } while (*modep != NUL) { colonp = vim_strchr(modep, ':'); - if (colonp == NULL) + commap = vim_strchr(modep, ','); + + if (colonp == NULL || (commap != NULL && commap < colonp)) { return (char_u *)N_("E545: Missing colon"); - if (colonp == modep) + } + if (colonp == modep) { return (char_u *)N_("E546: Illegal mode"); - commap = vim_strchr(modep, ','); + } // Repeat for all modes before the colon. // For the 'a' mode, we loop to handle all the modes. diff --git a/src/nvim/diff.c b/src/nvim/diff.c index cc0f3b2629..0ee1c3815d 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -855,12 +855,13 @@ void ex_diffpatch(exarg_T *eap) { char_u *buf = NULL; win_T *old_curwin = curwin; - char_u *newname = NULL; // name of patched file buffer + char_u *newname = NULL; // name of patched file buffer + char_u *esc_name = NULL; #ifdef UNIX char_u dirbuf[MAXPATHL]; char_u *fullname = NULL; -#endif // ifdef UNIX +#endif // We need two temp file names. // Name of original temp file. char_u *tmp_orig = vim_tempname(); @@ -880,21 +881,21 @@ void ex_diffpatch(exarg_T *eap) #ifdef UNIX // Get the absolute path of the patchfile, changing directory below. - fullname = (char_u *)FullName_save((char *)eap->arg, FALSE); -#endif // ifdef UNIX + fullname = (char_u *)FullName_save((char *)eap->arg, false); +#endif + esc_name = vim_strsave_shellescape( #ifdef UNIX - size_t buflen = STRLEN(tmp_orig) - + (fullname != NULL ? STRLEN(fullname) : STRLEN(eap->arg)) - + STRLEN(tmp_new) + 16; -#else - size_t buflen = STRLEN(tmp_orig) + (STRLEN(eap->arg)) + STRLEN(tmp_new) + 16; -#endif // ifdef UNIX - + fullname != NULL ? fullname : +#endif + eap->arg, true, true); + if (esc_name == NULL) { + goto theend; + } + size_t buflen = STRLEN(tmp_orig) + STRLEN(esc_name) + STRLEN(tmp_new) + 16; buf = xmalloc(buflen); #ifdef UNIX - // Temporarily chdir to /tmp, to avoid patching files in the current // directory when the patch file contains more than one patch. When we // have our own temp dir use that instead, it will be cleaned up when we @@ -911,26 +912,21 @@ void ex_diffpatch(exarg_T *eap) os_chdir(tempdir); shorten_fnames(TRUE); } -#endif // ifdef UNIX +#endif if (*p_pex != NUL) { // Use 'patchexpr' to generate the new file. #ifdef UNIX - eval_patch((char *) tmp_orig, - (char *) (fullname != NULL ? fullname : eap->arg), - (char *) tmp_new); + eval_patch((char *)tmp_orig, + (char *)(fullname != NULL ? fullname : eap->arg), + (char *)tmp_new); #else - eval_patch((char *) tmp_orig, (char *) eap->arg, (char *) tmp_new); -#endif // ifdef UNIX + eval_patch((char *)tmp_orig, (char *)eap->arg, (char *)tmp_new); +#endif } else { // Build the patch command and execute it. Ignore errors. -#ifdef UNIX - vim_snprintf((char *)buf, buflen, "patch -o %s %s < \"%s\"", - tmp_new, tmp_orig, fullname != NULL ? fullname : eap->arg); -#else - vim_snprintf((char *)buf, buflen, "patch -o %s %s < \"%s\"", - tmp_new, tmp_orig, eap->arg); -#endif // ifdef UNIX + vim_snprintf((char *)buf, buflen, "patch -o %s %s < %s", + tmp_new, tmp_orig, esc_name); block_autocmds(); // Avoid ShellCmdPost stuff (void)call_shell(buf, kShellOptFilter, NULL); unblock_autocmds(); @@ -943,10 +939,7 @@ void ex_diffpatch(exarg_T *eap) } shorten_fnames(TRUE); } -#endif // ifdef UNIX - - // patch probably has written over the screen - redraw_later(CLEAR); +#endif // Delete any .orig or .rej file created. STRCPY(buf, tmp_new); @@ -1012,7 +1005,8 @@ theend: xfree(buf); #ifdef UNIX xfree(fullname); -#endif // ifdef UNIX +#endif + xfree(esc_name); } /// Split the window and edit another file, setting options to show the diffs. @@ -1064,6 +1058,20 @@ void ex_diffthis(exarg_T *eap) diff_win_options(curwin, TRUE); } +static void set_diff_option(win_T *wp, int value) +{ + win_T *old_curwin = curwin; + + curwin = wp; + curbuf = curwin->w_buffer; + curbuf_lock++; + set_option_value("diff", (long)value, NULL, OPT_LOCAL); + curbuf_lock--; + curwin = old_curwin; + curbuf = curwin->w_buffer; +} + + /// Set options in window "wp" for diff mode. /// /// @param addbuf Add buffer to diff. @@ -1121,10 +1129,10 @@ void diff_win_options(win_T *wp, int addbuf) do_cmdline_cmd("set sbo+=hor"); } - // Saved the current values, to be restored in ex_diffoff(). - wp->w_p_diff_saved = TRUE; + // Save the current values, to be restored in ex_diffoff(). + wp->w_p_diff_saved = true; - wp->w_p_diff = true; + set_diff_option(wp, true); if (addbuf) { diff_buf_add(wp->w_buffer); @@ -1145,7 +1153,7 @@ void ex_diffoff(exarg_T *eap) // Set 'diff' off. If option values were saved in // diff_win_options(), restore the ones whose settings seem to have // been left over from diff mode. - wp->w_p_diff = false; + set_diff_option(wp, false); if (wp->w_p_diff_saved) { if (wp->w_p_scb) { @@ -1161,7 +1169,9 @@ void ex_diffoff(exarg_T *eap) } free_string_option(wp->w_p_fdm); - wp->w_p_fdm = vim_strsave(wp->w_p_fdm_save); + wp->w_p_fdm = vim_strsave(*wp->w_p_fdm_save + ? wp->w_p_fdm_save + : (char_u *)"manual"); if (wp->w_p_fdc == diff_foldcolumn) { wp->w_p_fdc = wp->w_p_fdc_save; } @@ -2271,7 +2281,7 @@ void ex_diffgetput(exarg_T *eap) } } - buf_empty = bufempty(); + buf_empty = BUFEMPTY(); added = 0; for (i = 0; i < count; ++i) { diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index dbcc8db109..bc4c12e0b7 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -1841,6 +1841,16 @@ void ex_loadkeymap(exarg_T *eap) status_redraw_curbuf(); } +/// Frees the buf_T.b_kmap_ga field of a buffer. +void keymap_ga_clear(garray_T *kmap_ga) +{ + kmap_T *kp = (kmap_T *)kmap_ga->ga_data; + for (int i = 0; i < kmap_ga->ga_len; i++) { + xfree(kp[i].from); + xfree(kp[i].to); + } +} + /// Stop using 'keymap'. static void keymap_unload(void) { @@ -1858,12 +1868,11 @@ static void keymap_unload(void) // clear the ":lmap"s kp = (kmap_T *)curbuf->b_kmap_ga.ga_data; - for (int i = 0; i < curbuf->b_kmap_ga.ga_len; ++i) { + for (int i = 0; i < curbuf->b_kmap_ga.ga_len; i++) { vim_snprintf((char *)buf, sizeof(buf), "<buffer> %s", kp[i].from); - (void)do_map(1, buf, LANGMAP, FALSE); - xfree(kp[i].from); - xfree(kp[i].to); + (void)do_map(1, buf, LANGMAP, false); } + keymap_ga_clear(&curbuf->b_kmap_ga); p_cpo = save_cpo; diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 2bafb77fef..a0f6ce152b 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -847,7 +847,7 @@ static int insert_handle_key(InsertState *s) case ' ': - if (mod_mask != 4) { + if (mod_mask != MOD_MASK_CTRL) { goto normalchar; } // FALLTHROUGH @@ -974,6 +974,10 @@ static int insert_handle_key(InsertState *s) multiqueue_process_events(main_loop.events); break; + case K_COMMAND: // some command + do_cmdline(NULL, getcmdkeycmd, NULL, 0); + break; + case K_HOME: // <Home> case K_KHOME: case K_S_HOME: @@ -1176,6 +1180,14 @@ static int insert_handle_key(InsertState *s) 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); + break; + } + if (!p_paste) { // Trigger InsertCharPre. char_u *str = do_insert_char_pre(s->c); @@ -1428,7 +1440,7 @@ static void ins_ctrl_v(void) * line and will not removed by the redraw */ edit_unputchar(); clear_showcmd(); - insert_special(c, FALSE, TRUE); + insert_special(c, true, true); revins_chars++; revins_legal++; } @@ -1927,7 +1939,7 @@ bool vim_is_ctrl_x_key(int c) case CTRL_X_EVAL: return (c == Ctrl_P || c == Ctrl_N); } - EMSG(_(e_internal)); + internal_error("vim_is_ctrl_x_key()"); return false; } @@ -3536,19 +3548,19 @@ theend: /* * Add completions from a list. */ -static void ins_compl_add_list(list_T *list) +static void ins_compl_add_list(list_T *const list) { - listitem_T *li; int dir = compl_direction; - /* Go through the List with matches and add each of them. */ - for (li = list->lv_first; li != NULL; li = li->li_next) { - if (ins_compl_add_tv(&li->li_tv, dir) == OK) - /* if dir was BACKWARD then honor it just once */ + // Go through the List with matches and add each of them. + TV_LIST_ITER(list, li, { + if (ins_compl_add_tv(TV_LIST_ITEM_TV(li), dir) == OK) { + // If dir was BACKWARD then honor it just once. dir = FORWARD; - else if (did_emsg) + } else if (did_emsg) { break; - } + } + }); } /* @@ -3600,6 +3612,8 @@ int ins_compl_add_tv(typval_T *const tv, const Direction dir) cptext[CPT_MENU] = tv_dict_get_string(tv->vval.v_dict, "menu", true); cptext[CPT_KIND] = tv_dict_get_string(tv->vval.v_dict, "kind", true); cptext[CPT_INFO] = tv_dict_get_string(tv->vval.v_dict, "info", true); + cptext[CPT_USER_DATA] = tv_dict_get_string(tv->vval.v_dict, + "user_data", true); icase = (bool)tv_dict_get_number(tv->vval.v_dict, "icase"); adup = (bool)tv_dict_get_number(tv->vval.v_dict, "dup"); @@ -3609,6 +3623,9 @@ int ins_compl_add_tv(typval_T *const tv, const Direction dir) memset(cptext, 0, sizeof(cptext)); } if (word == NULL || (!aempty && *word == NUL)) { + for (size_t i = 0; i < CPT_COUNT; i++) { + xfree(cptext[i]); + } return FAIL; } return ins_compl_add((char_u *)word, -1, icase, NULL, @@ -4043,8 +4060,9 @@ static void ins_compl_insert(int in_compl_func) // Set completed item. // { word, abbr, menu, kind, info } dict_T *dict = tv_dict_alloc(); - tv_dict_add_str(dict, S_LEN("word"), - (const char *)EMPTY_IF_NULL(compl_shown_match->cp_str)); + tv_dict_add_str( + dict, S_LEN("word"), + (const char *)EMPTY_IF_NULL(compl_shown_match->cp_str)); tv_dict_add_str( dict, S_LEN("abbr"), (const char *)EMPTY_IF_NULL(compl_shown_match->cp_text[CPT_ABBR])); @@ -4057,6 +4075,9 @@ static void ins_compl_insert(int in_compl_func) tv_dict_add_str( dict, S_LEN("info"), (const char *)EMPTY_IF_NULL(compl_shown_match->cp_text[CPT_INFO])); + tv_dict_add_str( + dict, S_LEN("user_data"), + (const char *)EMPTY_IF_NULL(compl_shown_match->cp_text[CPT_USER_DATA])); set_vim_var_dict(VV_COMPLETED_ITEM, dict); if (!in_compl_func) { compl_curr_match = compl_shown_match; @@ -4681,7 +4702,7 @@ static int ins_complete(int c, bool enable_pum) line = ml_get(curwin->w_cursor.lnum); compl_pattern = vim_strnsave(line + compl_col, compl_length); } else { - EMSG2(_(e_intern2), "ins_complete()"); + internal_error("ins_complete()"); return FAIL; } @@ -5044,13 +5065,11 @@ static void insert_special(int c, int allow_modmask, int ctrlv) char_u *p; int len; - /* - * Special function key, translate into "<Key>". Up to the last '>' is - * inserted with ins_str(), so as not to replace characters in replace - * mode. - * Only use mod_mask for special keys, to avoid things like <S-Space>, - * unless 'allow_modmask' is TRUE. - */ + // Special function key, translate into "<Key>". Up to the last '>' is + // inserted with ins_str(), so as not to replace characters in replace + // mode. + // Only use mod_mask for special keys, to avoid things like <S-Space>, + // unless 'allow_modmask' is TRUE. if (mod_mask & MOD_MASK_CMD) { // Command-key never produces a normal key. allow_modmask = true; } @@ -5235,28 +5254,27 @@ insertchar ( buf[0] = c; i = 1; - if (textwidth > 0) + if (textwidth > 0) { virtcol = get_nolist_virtcol(); - /* - * Stop the string when: - * - no more chars available - * - finding a special character (command key) - * - buffer is full - * - running into the 'textwidth' boundary - * - need to check for abbreviation: A non-word char after a word-char - */ - while ( (c = vpeekc()) != NUL - && !ISSPECIAL(c) - && (!has_mbyte || MB_BYTE2LEN_CHECK(c) == 1) - && i < INPUT_BUFLEN - && (textwidth == 0 - || (virtcol += byte2cells(buf[i - 1])) < (colnr_T)textwidth) - && !(!no_abbr && !vim_iswordc(c) && vim_iswordc(buf[i - 1]))) { + } + // Stop the string when: + // - no more chars available + // - finding a special character (command key) + // - buffer is full + // - running into the 'textwidth' boundary + // - need to check for abbreviation: A non-word char after a word-char + while ((c = vpeekc()) != NUL + && !ISSPECIAL(c) + && (!has_mbyte || MB_BYTE2LEN_CHECK(c) == 1) + && i < INPUT_BUFLEN + && !(p_fkmap && KeyTyped) // Farsi mode mapping moves cursor + && (textwidth == 0 + || (virtcol += byte2cells(buf[i - 1])) < (colnr_T)textwidth) + && !(!no_abbr && !vim_iswordc(c) && vim_iswordc(buf[i - 1]))) { c = vgetc(); - if (p_hkmap && KeyTyped) - c = hkmap(c); /* Hebrew mode mapping */ - if (p_fkmap && KeyTyped) - c = fkmap(c); /* Farsi mode mapping */ + if (p_hkmap && KeyTyped) { + c = hkmap(c); // Hebrew mode mapping + } buf[i++] = c; } @@ -6066,27 +6084,30 @@ void free_last_insert(void) #endif -/* - * Add character "c" to buffer "s". Escape the special meaning of K_SPECIAL - * and CSI. Handle multi-byte characters. - * Returns a pointer to after the added bytes. - */ +/// Add character "c" to buffer "s" +/// +/// Escapes the special meaning of K_SPECIAL and CSI, handles multi-byte +/// characters. +/// +/// @param[in] c Character to add. +/// @param[out] s Buffer to add to. Must have at least MB_MAXBYTES + 1 bytes. +/// +/// @return Pointer to after the added bytes. char_u *add_char2buf(int c, char_u *s) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { char_u temp[MB_MAXBYTES + 1]; - int i; - int len; - - len = (*mb_char2bytes)(c, temp); - for (i = 0; i < len; ++i) { + const int len = utf_char2bytes(c, temp); + for (int i = 0; i < len; i++) { c = temp[i]; - /* Need to escape K_SPECIAL and CSI like in the typeahead buffer. */ + // Need to escape K_SPECIAL and CSI like in the typeahead buffer. if (c == K_SPECIAL) { *s++ = K_SPECIAL; *s++ = KS_SPECIAL; *s++ = KE_FILLER; - } else + } else { *s++ = c; + } } return s; } @@ -6904,7 +6925,9 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) if (try_match && *look == keytyped) { return true; } - look++; + if (*look != NUL) { + look++; + } } /* @@ -7470,13 +7493,11 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) int oldState; int cpc[MAX_MCO]; /* composing characters */ - /* - * can't delete anything in an empty file - * can't backup past first character in buffer - * can't backup past starting point unless 'backspace' > 1 - * can backup to a previous line if 'backspace' == 0 - */ - if (bufempty() + // can't delete anything in an empty file + // can't backup past first character in buffer + // can't backup past starting point unless 'backspace' > 1 + // can backup to a previous line if 'backspace' == 0 + if (BUFEMPTY() || (!revins_on && ((curwin->w_cursor.lnum == 1 && curwin->w_cursor.col == 0) || (!can_bs(BS_START) diff --git a/src/nvim/edit.h b/src/nvim/edit.h index 0d61f26bcc..433a941295 100644 --- a/src/nvim/edit.h +++ b/src/nvim/edit.h @@ -6,11 +6,12 @@ /* * Array indexes used for cptext argument of ins_compl_add(). */ -#define CPT_ABBR 0 /* "abbr" */ -#define CPT_MENU 1 /* "menu" */ -#define CPT_KIND 2 /* "kind" */ -#define CPT_INFO 3 /* "info" */ -#define CPT_COUNT 4 /* Number of entries */ +#define CPT_ABBR 0 // "abbr" +#define CPT_MENU 1 // "menu" +#define CPT_KIND 2 // "kind" +#define CPT_INFO 3 // "info" +#define CPT_USER_DATA 4 // "user data" +#define CPT_COUNT 5 // Number of entries typedef int (*IndentGetter)(void); diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 577aa67c60..284185083e 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -110,9 +110,6 @@ #define DICT_MAXNEST 100 /* maximum nesting of lists and dicts */ -#define DO_NOT_FREE_CNT 99999 /* refcount for dict or list that should not - be freed. */ - #define AUTOLOAD_CHAR '#' /* Character used as separator in autoload function/variable names. */ @@ -218,6 +215,15 @@ static garray_T ga_scripts = {0, 0, sizeof(scriptvar_T *), 4, NULL}; static int echo_attr = 0; /* attributes used for ":echo" */ +/// Describe data to return from find_some_match() +typedef enum { + kSomeMatch, ///< Data for match(). + kSomeMatchEnd, ///< Data for matchend(). + kSomeMatchList, ///< Data for matchlist(). + kSomeMatchStr, ///< Data for matchstr(). + kSomeMatchStrPos, ///< Data for matchstrpos(). +} SomeMatchType; + /// trans_function_name() flags typedef enum { TFN_INT = 1, ///< May use internal function name @@ -396,7 +402,6 @@ static struct vimvar { VV(VV_OLDFILES, "oldfiles", VAR_LIST, 0), VV(VV_WINDOWID, "windowid", VAR_NUMBER, VV_RO_SBX), VV(VV_PROGPATH, "progpath", VAR_STRING, VV_RO), - VV(VV_COMMAND_OUTPUT, "command_output", VAR_STRING, 0), VV(VV_COMPLETED_ITEM, "completed_item", VAR_DICT, VV_RO), VV(VV_OPTION_NEW, "option_new", VAR_STRING, VV_RO), VV(VV_OPTION_OLD, "option_old", VAR_STRING, VV_RO), @@ -563,9 +568,9 @@ void eval_init(void) dict_T *const msgpack_types_dict = tv_dict_alloc(); for (size_t i = 0; i < ARRAY_SIZE(msgpack_type_names); i++) { - list_T *const type_list = tv_list_alloc(); - type_list->lv_lock = VAR_FIXED; - type_list->lv_refcount = 1; + list_T *const type_list = tv_list_alloc(0); + tv_list_set_lock(type_list, VAR_FIXED); + tv_list_ref(type_list); dictitem_T *const di = tv_dict_item_alloc(msgpack_type_names[i]); di->di_flags |= DI_FLAGS_RO|DI_FLAGS_FIX; di->di_tv = (typval_T) { @@ -586,7 +591,7 @@ void eval_init(void) dict_T *v_event = tv_dict_alloc(); v_event->dv_lock = VAR_FIXED; set_vim_var_dict(VV_EVENT, v_event); - set_vim_var_list(VV_ERRORS, tv_list_alloc()); + set_vim_var_list(VV_ERRORS, tv_list_alloc(kListLenUnknown)); set_vim_var_nr(VV_STDERR, CHAN_STDERR); set_vim_var_nr(VV_SEARCHFORWARD, 1L); set_vim_var_nr(VV_HLSEARCH, 1L); @@ -1014,7 +1019,7 @@ char_u *eval_to_string(char_u *arg, char_u **nextcmd, int convert) ga_init(&ga, (int)sizeof(char), 80); if (tv.vval.v_list != NULL) { tv_list_join(&ga, tv.vval.v_list, "\n"); - if (tv.vval.v_list->lv_len > 0) { + if (tv_list_len(tv.vval.v_list) > 0) { ga_append(&ga, NL); } } @@ -1102,10 +1107,11 @@ static void restore_vimvar(int idx, typval_T *save_tv) vimvars[idx].vv_tv = *save_tv; if (vimvars[idx].vv_type == VAR_UNKNOWN) { hi = hash_find(&vimvarht, vimvars[idx].vv_di.di_key); - if (HASHITEM_EMPTY(hi)) - EMSG2(_(e_intern2), "restore_vimvar()"); - else + if (HASHITEM_EMPTY(hi)) { + internal_error("restore_vimvar()"); + } else { hash_remove(&vimvarht, hi); + } } } @@ -1143,27 +1149,31 @@ list_T *eval_spell_expr(char_u *badword, char_u *expr) return list; } -/* - * "list" is supposed to contain two items: a word and a number. Return the - * word in "pp" and the number as the return value. - * Return -1 if anything isn't right. - * Used to get the good word and score from the eval_spell_expr() result. - */ -int get_spellword(list_T *list, const char **pp) +/// Get spell word from an entry from spellsuggest=expr: +/// +/// Entry in question is supposed to be a list (to be checked by the caller) +/// with two items: a word and a score represented as an unsigned number +/// (whether it actually is unsigned is not checked). +/// +/// Used to get the good word and score from the eval_spell_expr() result. +/// +/// @param[in] list List to get values from. +/// @param[out] ret_word Suggested word. Not initialized if return value is +/// -1. +/// +/// @return -1 in case of error, score otherwise. +int get_spellword(list_T *const list, const char **ret_word) { - listitem_T *li; - - li = list->lv_first; - if (li == NULL) { + if (tv_list_len(list) != 2) { + EMSG(_("E5700: Expression from 'spellsuggest' must yield lists with " + "exactly two values")); return -1; } - *pp = tv_get_string(&li->li_tv); - - li = li->li_next; - if (li == NULL) { + *ret_word = tv_list_find_str(list, 0); + if (*ret_word == NULL) { return -1; } - return tv_get_number(&li->li_tv); + return tv_list_find_nr(list, -1, NULL); } @@ -1269,7 +1279,7 @@ varnumber_T call_func_retnr(char_u *func, int argc, char *call_func_retstr(const char *const func, const int argc, const char_u *const *const argv, const bool safe) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC + FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC { typval_T rettv; // All arguments are passed as strings, no conversion to number. @@ -1504,9 +1514,6 @@ ex_let_vars ( ) { char_u *arg = arg_start; - list_T *l; - int i; - listitem_T *item; typval_T ltv; if (*arg != '[') { @@ -1518,46 +1525,52 @@ ex_let_vars ( return OK; } - /* - * ":let [v1, v2] = list" or ":for [v1, v2] in listlist" - */ - if (tv->v_type != VAR_LIST || (l = tv->vval.v_list) == NULL) { + // ":let [v1, v2] = list" or ":for [v1, v2] in listlist" + if (tv->v_type != VAR_LIST) { EMSG(_(e_listreq)); return FAIL; } + list_T *const l = tv->vval.v_list; - i = tv_list_len(l); - if (semicolon == 0 && var_count < i) { + const int len = tv_list_len(l); + if (semicolon == 0 && var_count < len) { EMSG(_("E687: Less targets than List items")); return FAIL; } - if (var_count - semicolon > i) { + if (var_count - semicolon > len) { EMSG(_("E688: More targets than List items")); return FAIL; } + // List l may actually be NULL, but it should fail with E688 or even earlier + // if you try to do ":let [] = v:_null_list". + assert(l != NULL); - item = l->lv_first; + listitem_T *item = tv_list_first(l); + size_t rest_len = tv_list_len(l); while (*arg != ']') { arg = skipwhite(arg + 1); - arg = ex_let_one(arg, &item->li_tv, TRUE, (char_u *)",;]", nextchars); - item = item->li_next; - if (arg == NULL) + arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, (const char_u *)",;]", + nextchars); + if (arg == NULL) { return FAIL; + } + rest_len--; + item = TV_LIST_ITEM_NEXT(l, item); arg = skipwhite(arg); if (*arg == ';') { /* Put the rest of the list (may be empty) in the var after ';'. * Create a new list for this. */ - l = tv_list_alloc(); + list_T *const rest_list = tv_list_alloc(rest_len); while (item != NULL) { - tv_list_append_tv(l, &item->li_tv); - item = item->li_next; + tv_list_append_tv(rest_list, TV_LIST_ITEM_TV(item)); + item = TV_LIST_ITEM_NEXT(l, item); } ltv.v_type = VAR_LIST; - ltv.v_lock = 0; - ltv.vval.v_list = l; - l->lv_refcount = 1; + ltv.v_lock = VAR_UNLOCKED; + ltv.vval.v_list = rest_list; + tv_list_ref(rest_list); arg = ex_let_one(skipwhite(arg + 1), <v, false, (char_u *)"]", nextchars); @@ -1567,7 +1580,7 @@ ex_let_vars ( } break; } else if (*arg != ',' && *arg != ']') { - EMSG2(_(e_intern2), "ex_let_vars()"); + internal_error("ex_let_vars()"); return FAIL; } } @@ -2072,6 +2085,8 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, * Loop until no more [idx] or .key is following. */ lp->ll_tv = &v->di_tv; + var1.v_type = VAR_UNKNOWN; + var2.v_type = VAR_UNKNOWN; while (*p == '[' || (*p == '.' && lp->ll_tv->v_type == VAR_DICT)) { if (!(lp->ll_tv->v_type == VAR_LIST && lp->ll_tv->vval.v_list != NULL) && !(lp->ll_tv->v_type == VAR_DICT @@ -2122,9 +2137,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, if (!quiet) { EMSG(_(e_dictrange)); } - if (!empty1) { - tv_clear(&var1); - } + tv_clear(&var1); return NULL; } if (rettv != NULL && (rettv->v_type != VAR_LIST @@ -2132,9 +2145,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, if (!quiet) { emsgf(_("E709: [:] requires a List value")); } - if (!empty1) { - tv_clear(&var1); - } + tv_clear(&var1); return NULL; } p = skipwhite(p + 1); @@ -2143,16 +2154,12 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, } else { lp->ll_empty2 = false; if (eval1(&p, &var2, true) == FAIL) { // Recursive! - if (!empty1) { - tv_clear(&var1); - } + tv_clear(&var1); return NULL; } if (!tv_check_str(&var2)) { // Not a number or string. - if (!empty1) { - tv_clear(&var1); - } + tv_clear(&var1); tv_clear(&var2); return NULL; } @@ -2165,12 +2172,8 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, if (!quiet) { emsgf(_(e_missbrac)); } - if (!empty1) { - tv_clear(&var1); - } - if (lp->ll_range && !lp->ll_empty2) { - tv_clear(&var2); - } + tv_clear(&var1); + tv_clear(&var2); return NULL; } @@ -2224,9 +2227,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, if (!quiet) { emsgf(_(e_dictkey), key); } - if (len == -1) { - tv_clear(&var1); - } + tv_clear(&var1); return NULL; } if (len == -1) { @@ -2234,32 +2235,28 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, } else { lp->ll_newkey = vim_strnsave(key, len); } - if (len == -1) { - tv_clear(&var1); - } + tv_clear(&var1); break; // existing variable, need to check if it can be changed } else if (!(flags & GLV_READ_ONLY) && var_check_ro(lp->ll_di->di_flags, (const char *)name, (size_t)(p - name))) { - if (len == -1) { - tv_clear(&var1); - } + tv_clear(&var1); return NULL; } - if (len == -1) { - tv_clear(&var1); - } + tv_clear(&var1); lp->ll_tv = &lp->ll_di->di_tv; } else { // Get the number and item for the only or first index of the List. if (empty1) { lp->ll_n1 = 0; } else { - lp->ll_n1 = (long)tv_get_number(&var1); // Is number or string. - tv_clear(&var1); + // Is number or string. + lp->ll_n1 = (long)tv_get_number(&var1); } + tv_clear(&var1); + lp->ll_dict = NULL; lp->ll_list = lp->ll_tv->vval.v_list; lp->ll_li = tv_list_find(lp->ll_list, lp->ll_n1); @@ -2270,9 +2267,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, } } if (lp->ll_li == NULL) { - if (lp->ll_range && !lp->ll_empty2) { - tv_clear(&var2); - } + tv_clear(&var2); if (!quiet) { EMSGN(_(e_listidx), lp->ll_n1); } @@ -2310,10 +2305,11 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, } } - lp->ll_tv = &lp->ll_li->li_tv; + lp->ll_tv = TV_LIST_ITEM_TV(lp->ll_li); } } + tv_clear(&var1); return p; } @@ -2375,47 +2371,55 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, int ll_n1 = lp->ll_n1; // Check whether any of the list items is locked - for (listitem_T *ri = rettv->vval.v_list->lv_first; + for (listitem_T *ri = tv_list_first(rettv->vval.v_list); ri != NULL && ll_li != NULL; ) { - if (tv_check_lock(ll_li->li_tv.v_lock, (const char *)lp->ll_name, + if (tv_check_lock(TV_LIST_ITEM_TV(ll_li)->v_lock, + (const char *)lp->ll_name, TV_CSTRING)) { return; } - ri = ri->li_next; + ri = TV_LIST_ITEM_NEXT(rettv->vval.v_list, ri); if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == ll_n1)) { break; } - ll_li = ll_li->li_next; + ll_li = TV_LIST_ITEM_NEXT(lp->ll_list, ll_li); ll_n1++; } /* * Assign the List values to the list items. */ - for (ri = rettv->vval.v_list->lv_first; ri != NULL; ) { + for (ri = tv_list_first(rettv->vval.v_list); ri != NULL; ) { if (op != NULL && *op != '=') { - eexe_mod_op(&lp->ll_li->li_tv, &ri->li_tv, (const char *)op); + eexe_mod_op(TV_LIST_ITEM_TV(lp->ll_li), TV_LIST_ITEM_TV(ri), + (const char *)op); } else { - tv_clear(&lp->ll_li->li_tv); - tv_copy(&ri->li_tv, &lp->ll_li->li_tv); + tv_clear(TV_LIST_ITEM_TV(lp->ll_li)); + tv_copy(TV_LIST_ITEM_TV(ri), TV_LIST_ITEM_TV(lp->ll_li)); } - ri = ri->li_next; - if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == lp->ll_n1)) + ri = TV_LIST_ITEM_NEXT(rettv->vval.v_list, ri); + if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == lp->ll_n1)) { break; - if (lp->ll_li->li_next == NULL) { + } + assert(lp->ll_li != NULL); + if (TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li) == NULL) { // Need to add an empty item. tv_list_append_number(lp->ll_list, 0); - assert(lp->ll_li->li_next); + // ll_li may have become invalid after append, don’t use it. + lp->ll_li = tv_list_last(lp->ll_list); // Valid again. + } else { + lp->ll_li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li); } - lp->ll_li = lp->ll_li->li_next; - ++lp->ll_n1; + lp->ll_n1++; } - if (ri != NULL) + if (ri != NULL) { EMSG(_("E710: List value has more items than target")); - else if (lp->ll_empty2 - ? (lp->ll_li != NULL && lp->ll_li->li_next != NULL) - : lp->ll_n1 != lp->ll_n2) + } else if (lp->ll_empty2 + ? (lp->ll_li != NULL + && TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li) != NULL) + : lp->ll_n1 != lp->ll_n2) { EMSG(_("E711: List value has not enough items")); + } } else { typval_T oldtv = TV_INITIAL_VALUE; dict_T *dict = lp->ll_dict; @@ -2460,9 +2464,11 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, notify: if (watched) { if (oldtv.v_type == VAR_UNKNOWN) { + assert(lp->ll_newkey != NULL); tv_dict_watcher_notify(dict, (char *)lp->ll_newkey, lp->ll_tv, NULL); } else { dictitem_T *di = lp->ll_di; + assert(di->di_key != NULL); tv_dict_watcher_notify(dict, (char *)di->di_key, lp->ll_tv, &oldtv); tv_clear(&oldtv); } @@ -2514,7 +2520,7 @@ void *eval_for_line(const char_u *arg, bool *errp, char_u **nextcmdp, int skip) * list being used in "tv". */ fi->fi_list = l; tv_list_watch_add(l, &fi->fi_lw); - fi->fi_lw.lw_item = l->lv_first; + fi->fi_lw.lw_item = tv_list_first(l); } } } @@ -2532,21 +2538,18 @@ void *eval_for_line(const char_u *arg, bool *errp, char_u **nextcmdp, int skip) * Return TRUE when a valid item was found, FALSE when at end of list or * something wrong. */ -int next_for_item(void *fi_void, char_u *arg) +bool next_for_item(void *fi_void, char_u *arg) { - forinfo_T *fi = (forinfo_T *)fi_void; - int result; - listitem_T *item; + forinfo_T *fi = (forinfo_T *)fi_void; - item = fi->fi_lw.lw_item; - if (item == NULL) - result = FALSE; - else { - fi->fi_lw.lw_item = item->li_next; - result = (ex_let_vars(arg, &item->li_tv, TRUE, - fi->fi_semicolon, fi->fi_varcount, NULL) == OK); + listitem_T *item = fi->fi_lw.lw_item; + if (item == NULL) { + return false; + } else { + fi->fi_lw.lw_item = TV_LIST_ITEM_NEXT(fi->fi_list, item); + return (ex_let_vars(arg, TV_LIST_ITEM_TV(item), true, + fi->fi_semicolon, fi->fi_varcount, NULL) == OK); } - return result; } // TODO(ZyX-I): move to eval/ex_cmds @@ -2870,7 +2873,10 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit) } *name_end = cc; } else if ((lp->ll_list != NULL - && tv_check_lock(lp->ll_list->lv_lock, (const char *)lp->ll_name, + // ll_list is not NULL when lvalue is not in a list, NULL lists + // yield E689. + && tv_check_lock(tv_list_locked(lp->ll_list), + (const char *)lp->ll_name, lp->ll_name_len)) || (lp->ll_dict != NULL && tv_check_lock(lp->ll_dict->dv_lock, @@ -2878,27 +2884,26 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit) lp->ll_name_len))) { return FAIL; } else if (lp->ll_range) { - listitem_T *li; - listitem_T *ll_li = lp->ll_li; - int ll_n1 = lp->ll_n1; - - while (ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= ll_n1)) { - li = ll_li->li_next; - if (tv_check_lock(ll_li->li_tv.v_lock, (const char *)lp->ll_name, + assert(lp->ll_list != NULL); + // Delete a range of List items. + listitem_T *const first_li = lp->ll_li; + listitem_T *last_li = first_li; + for (;;) { + listitem_T *const li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li); + if (tv_check_lock(TV_LIST_ITEM_TV(lp->ll_li)->v_lock, + (const char *)lp->ll_name, lp->ll_name_len)) { return false; } - ll_li = li; - ll_n1++; - } - - /* Delete a range of List items. */ - while (lp->ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) { - li = lp->ll_li->li_next; - tv_list_item_remove(lp->ll_list, lp->ll_li); lp->ll_li = li; - ++lp->ll_n1; + lp->ll_n1++; + if (lp->ll_li == NULL || (!lp->ll_empty2 && lp->ll_n2 < lp->ll_n1)) { + break; + } else { + last_li = lp->ll_li; + } } + tv_list_remove_items(lp->ll_list, first_li, last_li); } else { if (lp->ll_list != NULL) { // unlet a List item. @@ -2906,6 +2911,7 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit) } else { // unlet a Dictionary item. dict_T *d = lp->ll_dict; + assert(d != NULL); dictitem_T *di = lp->ll_di; bool watched = tv_dict_is_watched(d); char *key = NULL; @@ -2960,7 +2966,7 @@ int do_unlet(const char *const name, const size_t name_len, const int forceit) d = di->di_tv.vval.v_dict; } if (d == NULL) { - EMSG2(_(e_intern2), "do_unlet()"); + internal_error("do_unlet()"); return FAIL; } hashitem_T *hi = hash_find(ht, (const char_u *)varname); @@ -3043,13 +3049,13 @@ static int do_lock_var(lval_T *lp, char_u *const name_end, const int deep, /* (un)lock a range of List items. */ while (li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) { - tv_item_lock(&li->li_tv, deep, lock); - li = li->li_next; - ++lp->ll_n1; + tv_item_lock(TV_LIST_ITEM_TV(li), deep, lock); + li = TV_LIST_ITEM_NEXT(lp->ll_list, li); + lp->ll_n1++; } } else if (lp->ll_list != NULL) { // (un)lock a List item. - tv_item_lock(&lp->ll_li->li_tv, deep, lock); + tv_item_lock(TV_LIST_ITEM_TV(lp->ll_li), deep, lock); } else { // (un)lock a Dictionary item. tv_item_lock(&lp->ll_di->di_tv, deep, lock); @@ -4508,18 +4514,18 @@ eval_index ( if (!empty2 && (n2 < 0 || n2 + 1 < n1)) { n2 = -1; } - l = tv_list_alloc(); + l = tv_list_alloc(n2 - n1 + 1); item = tv_list_find(rettv->vval.v_list, n1); while (n1++ <= n2) { - tv_list_append_tv(l, &item->li_tv); - item = item->li_next; + tv_list_append_tv(l, TV_LIST_ITEM_TV(item)); + item = TV_LIST_ITEM_NEXT(rettv->vval.v_list, item); } tv_clear(rettv); rettv->v_type = VAR_LIST; rettv->vval.v_list = l; - l->lv_refcount++; + tv_list_ref(l); } else { - tv_copy(&tv_list_find(rettv->vval.v_list, n1)->li_tv, &var1); + tv_copy(TV_LIST_ITEM_TV(tv_list_find(rettv->vval.v_list, n1)), &var1); tv_clear(rettv); *rettv = var1; } @@ -4864,35 +4870,34 @@ void partial_unref(partial_T *pt) static int get_list_tv(char_u **arg, typval_T *rettv, int evaluate) { list_T *l = NULL; - typval_T tv; - listitem_T *item; if (evaluate) { - l = tv_list_alloc(); + l = tv_list_alloc(kListLenShouldKnow); } *arg = skipwhite(*arg + 1); while (**arg != ']' && **arg != NUL) { - if (eval1(arg, &tv, evaluate) == FAIL) /* recursive! */ + typval_T tv; + if (eval1(arg, &tv, evaluate) == FAIL) { // Recursive! goto failret; + } if (evaluate) { - item = tv_list_item_alloc(); - item->li_tv = tv; - item->li_tv.v_lock = 0; - tv_list_append(l, item); + tv.v_lock = VAR_UNLOCKED; + tv_list_append_owned_tv(l, tv); } - if (**arg == ']') + if (**arg == ']') { break; + } if (**arg != ',') { - EMSG2(_("E696: Missing comma in List: %s"), *arg); + emsgf(_("E696: Missing comma in List: %s"), *arg); goto failret; } *arg = skipwhite(*arg + 1); } if (**arg != ']') { - EMSG2(_("E697: Missing end of List ']': %s"), *arg); + emsgf(_("E697: Missing end of List ']': %s"), *arg); failret: if (evaluate) { tv_list_free(l); @@ -4904,7 +4909,7 @@ failret: if (evaluate) { rettv->v_type = VAR_LIST; rettv->vval.v_list = l; - ++l->lv_refcount; + tv_list_ref(l); } return OK; @@ -5172,6 +5177,8 @@ bool garbage_collect(bool testing) ABORTING(set_ref_list)(sub.additional_elements, copyID); } + ABORTING(set_ref_in_quickfix)(copyID); + bool did_free = false; if (!abort) { // 2. Free lists and dictionaries that are not referenced. @@ -5239,8 +5246,8 @@ static int free_unref_items(int copyID) // But don't free a list that has a watcher (used in a for loop), these // are not referenced anywhere. for (list_T *ll = gc_first_list; ll != NULL; ll = ll->lv_used_next) { - if ((ll->lv_copyID & COPYID_MASK) != (copyID & COPYID_MASK) - && ll->lv_watch == NULL) { + if ((tv_list_copyid(ll) & COPYID_MASK) != (copyID & COPYID_MASK) + && !tv_list_has_watchers(ll)) { // Free the List and ordinary items it contains, but don't recurse // into Lists and Dictionaries, they will be in the list of dicts // or list of lists. @@ -5260,7 +5267,7 @@ static int free_unref_items(int copyID) for (ll = gc_first_list; ll != NULL; ll = ll_next) { ll_next = ll->lv_used_next; if ((ll->lv_copyID & COPYID_MASK) != (copyID & COPYID_MASK) - && ll->lv_watch == NULL) { + && !tv_list_has_watchers(ll)) { // Free the List and ordinary items it contains, but don't recurse // into Lists and Dictionaries, they will be in the list of dicts // or list of lists. @@ -5325,15 +5332,16 @@ bool set_ref_in_list(list_T *l, int copyID, ht_stack_T **ht_stack) list_T *cur_l = l; for (;;) { - if (!abort) { - // Mark each item in the list. If the item contains a hashtab - // it is added to ht_stack, if it contains a list it is added to - // list_stack. - for (listitem_T *li = cur_l->lv_first; !abort && li != NULL; - li = li->li_next) { - abort = set_ref_in_item(&li->li_tv, copyID, ht_stack, &list_stack); + // Mark each item in the list. If the item contains a hashtab + // it is added to ht_stack, if it contains a list it is added to + // list_stack. + TV_LIST_ITER(cur_l, li, { + if (abort) { + break; } - } + abort = set_ref_in_item(TV_LIST_ITEM_TV(li), copyID, ht_stack, + &list_stack); + }); if (list_stack == NULL) { break; @@ -5928,6 +5936,14 @@ static int get_env_tv(char_u **arg, typval_T *rettv, int evaluate) } #ifdef INCLUDE_GENERATED_DECLARATIONS + +#ifdef _MSC_VER +// This prevents MSVC from replacing the functions with intrinsics, +// and causing errors when trying to get their addresses in funcs.generated.h +#pragma function (ceil) +#pragma function (floor) +#endif + # include "funcs.generated.h" #endif @@ -6530,12 +6546,10 @@ static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - list_T *l; - - rettv->vval.v_number = 1; /* Default: Failed */ + rettv->vval.v_number = 1; // Default: failed. if (argvars[0].v_type == VAR_LIST) { - if ((l = argvars[0].vval.v_list) != NULL - && !tv_check_lock(l->lv_lock, "add() argument", TV_TRANSLATE)) { + list_T *const l = argvars[0].vval.v_list; + if (!tv_check_lock(tv_list_locked(l), N_("add() argument"), TV_TRANSLATE)) { tv_list_append_tv(l, &argvars[1]); tv_copy(&argvars[0], rettv); } @@ -6586,9 +6600,10 @@ static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) && u_save(lnum, lnum + 1) == OK) { if (argvars[1].v_type == VAR_LIST) { l = argvars[1].vval.v_list; - if (l == NULL) + if (l == NULL) { return; - li = l->lv_first; + } + li = tv_list_first(l); } for (;; ) { if (l == NULL) { @@ -6596,7 +6611,7 @@ static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else if (li == NULL) { break; // End of list. } else { - tv = &li->li_tv; // Append item from list. + tv = TV_LIST_ITEM_TV(li); // Append item from list. } const char *const line = tv_get_string_chk(tv); if (line == NULL) { // Type error. @@ -6608,7 +6623,7 @@ static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (l == NULL) { break; } - li = li->li_next; + li = TV_LIST_ITEM_NEXT(l, li); } appended_lines_mark(lnum, added); @@ -6661,7 +6676,7 @@ static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr) } rettv->v_type = VAR_STRING; } else { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, ARGCOUNT); for (idx = 0; idx < ARGCOUNT; idx++) { tv_list_append_string(rettv->vval.v_list, (const char *)alist_name(&ARGLIST[idx]), -1); @@ -6771,7 +6786,7 @@ static void assert_error(garray_T *gap) if (vp->vv_type != VAR_LIST || vimvars[VV_ERRORS].vv_list == NULL) { // Make sure v:errors is a list. - set_vim_var_list(VV_ERRORS, tv_list_alloc()); + set_vim_var_list(VV_ERRORS, tv_list_alloc(1)); } tv_list_append_string(vimvars[VV_ERRORS].vv_list, (const char *)gap->ga_data, (ptrdiff_t)gap->ga_len); @@ -7247,30 +7262,26 @@ static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, FunPtr fptr) int func_call(char_u *name, typval_T *args, partial_T *partial, dict_T *selfdict, typval_T *rettv) { - listitem_T *item; typval_T argv[MAX_FUNC_ARGS + 1]; int argc = 0; int dummy; int r = 0; - for (item = args->vval.v_list->lv_first; item != NULL; - item = item->li_next) { + TV_LIST_ITER(args->vval.v_list, item, { if (argc == MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) { EMSG(_("E699: Too many arguments")); - break; + goto func_call_skip_call; } - /* Make a copy of each argument. This is needed to be able to set - * v_lock to VAR_FIXED in the copy without changing the original list. - */ - tv_copy(&item->li_tv, &argv[argc++]); - } + // Make a copy of each argument. This is needed to be able to set + // v_lock to VAR_FIXED in the copy without changing the original list. + tv_copy(TV_LIST_ITEM_TV(item), &argv[argc++]); + }); - if (item == NULL) { - r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &dummy, true, partial, selfdict); - } + r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL, + curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &dummy, true, partial, selfdict); +func_call_skip_call: // Free the arguments. while (argc > 0) { tv_clear(&argv[--argc]); @@ -7487,7 +7498,7 @@ static void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (!undo_allowed()) return; - if (argvars[1].v_type != VAR_LIST || argvars[1].vval.v_list == NULL) { + if (argvars[1].v_type != VAR_LIST) { EMSG(_(e_invarg)); return; } @@ -7595,7 +7606,7 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) long idx; if ((l = argvars[0].vval.v_list) != NULL) { - li = l->lv_first; + li = tv_list_first(l); if (argvars[2].v_type != VAR_UNKNOWN) { bool error = false; @@ -7613,9 +7624,11 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) li = NULL; } - for (; li != NULL; li = li->li_next) - if (tv_equal(&li->li_tv, &argvars[1], ic, FALSE)) - ++n; + for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { + if (tv_equal(TV_LIST_ITEM_TV(li), &argvars[1], ic, false)) { + n++; + } + } } } else if (argvars[0].v_type == VAR_DICT) { int todo; @@ -7933,34 +7946,51 @@ static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool n = true; switch (argvars[0].v_type) { - case VAR_STRING: - case VAR_FUNC: - n = argvars[0].vval.v_string == NULL - || *argvars[0].vval.v_string == NUL; - break; - case VAR_PARTIAL: - n = false; - break; - case VAR_NUMBER: - n = argvars[0].vval.v_number == 0; - break; - case VAR_FLOAT: - n = argvars[0].vval.v_float == 0.0; - break; - case VAR_LIST: - n = argvars[0].vval.v_list == NULL - || argvars[0].vval.v_list->lv_first == NULL; - break; - case VAR_DICT: - n = argvars[0].vval.v_dict == NULL - || argvars[0].vval.v_dict->dv_hashtab.ht_used == 0; - break; - case VAR_SPECIAL: - n = argvars[0].vval.v_special != kSpecialVarTrue; - break; - case VAR_UNKNOWN: - EMSG2(_(e_intern2), "f_empty(UNKNOWN)"); - break; + case VAR_STRING: + case VAR_FUNC: { + n = argvars[0].vval.v_string == NULL + || *argvars[0].vval.v_string == NUL; + break; + } + case VAR_PARTIAL: { + n = false; + break; + } + case VAR_NUMBER: { + n = argvars[0].vval.v_number == 0; + break; + } + case VAR_FLOAT: { + n = argvars[0].vval.v_float == 0.0; + break; + } + case VAR_LIST: { + n = (tv_list_len(argvars[0].vval.v_list) == 0); + break; + } + case VAR_DICT: { + n = (tv_dict_len(argvars[0].vval.v_dict) == 0); + break; + } + case VAR_SPECIAL: { + // Using switch to get warning if SpecialVarValue receives more values. + switch (argvars[0].vval.v_special) { + case kSpecialVarTrue: { + n = false; + break; + } + case kSpecialVarFalse: + case kSpecialVarNull: { + n = true; + break; + } + } + break; + } + case VAR_UNKNOWN: { + internal_error("f_empty(UNKNOWN)"); + break; + } } rettv->vval.v_number = n; @@ -8024,17 +8054,22 @@ static void f_executable(typval_T *argvars, typval_T *rettv, FunPtr fptr) && os_can_exe((const char_u *)name, NULL, false))); } +typedef struct { + const list_T *const l; + const listitem_T *li; +} GetListLineCookie; + static char_u *get_list_line(int c, void *cookie, int indent) { - const listitem_T **const p = (const listitem_T **)cookie; - const listitem_T *item = *p; + GetListLineCookie *const p = (GetListLineCookie *)cookie; + const listitem_T *const item = p->li; if (item == NULL) { return NULL; } char buf[NUMBUFLEN]; - const char *const s = tv_get_string_buf_chk(&item->li_tv, buf); - *p = item->li_next; + const char *const s = tv_get_string_buf_chk(TV_LIST_ITEM_TV(item), buf); + p->li = TV_LIST_ITEM_NEXT(p->l, item); return (char_u *)(s == NULL ? NULL : xstrdup(s)); } @@ -8076,11 +8111,14 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) do_cmdline_cmd(tv_get_string(&argvars[0])); } else if (argvars[0].vval.v_list != NULL) { list_T *const list = argvars[0].vval.v_list; - list->lv_refcount++; - listitem_T *const item = list->lv_first; - do_cmdline(NULL, get_list_line, (void *)&item, + tv_list_ref(list); + GetListLineCookie cookie = { + .l = list, + .li = tv_list_first(list), + }; + do_cmdline(NULL, get_list_line, (void *)&cookie, DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT|DOCMD_KEYTYPED); - list->lv_refcount--; + tv_list_unref(list); } msg_silent = save_msg_silent; emsg_silent = save_emsg_silent; @@ -8088,8 +8126,7 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) ga_append(capture_ga, NUL); rettv->v_type = VAR_STRING; - rettv->vval.v_string = vim_strsave(capture_ga->ga_data); - ga_clear(capture_ga); + rettv->vval.v_string = capture_ga->ga_data; capture_ga = save_capture_ga; } @@ -8198,7 +8235,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) result = eval_vars((char_u *)s, (char_u *)s, &len, NULL, &errormsg, NULL); emsg_off--; if (rettv->v_type == VAR_LIST) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, (result != NULL)); if (result != NULL) { tv_list_append_string(rettv->vval.v_list, (const char *)result, -1); } @@ -8221,8 +8258,8 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = ExpandOne(&xpc, (char_u *)s, NULL, options, WILD_ALL); } else { - tv_list_alloc_ret(rettv); ExpandOne(&xpc, (char_u *)s, NULL, options, WILD_ALL_KEEP); + tv_list_alloc_ret(rettv, xpc.xp_numfiles); for (int i = 0; i < xpc.xp_numfiles; i++) { tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], -1); @@ -8239,7 +8276,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "menu_get(path [, modes])" function static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); int modes = MENU_ALL_MODES; if (argvars[1].v_type == VAR_STRING) { const char_u *const strmodes = (char_u *)tv_get_string(&argvars[1]); @@ -8262,14 +8299,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) list_T *const l1 = argvars[0].vval.v_list; list_T *const l2 = argvars[1].vval.v_list; - if (l1 == NULL) { - const bool locked = tv_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE); - (void)locked; - assert(locked == true); - } else if (l2 == NULL) { - // Do nothing - tv_copy(&argvars[0], rettv); - } else if (!tv_check_lock(l1->lv_lock, arg_errmsg, TV_TRANSLATE)) { + if (!tv_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) { listitem_T *item; if (argvars[2].v_type != VAR_UNKNOWN) { before = (long)tv_get_number_chk(&argvars[2], &error); @@ -8277,7 +8307,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; // Type error; errmsg already given. } - if (before == l1->lv_len) { + if (before == tv_list_len(l1)) { item = NULL; } else { item = tv_list_find(l1, before); @@ -8286,8 +8316,9 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } } - } else + } else { item = NULL; + } tv_list_extend(l1, l2, item); tv_copy(&argvars[0], rettv); @@ -8406,7 +8437,7 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) } if (count < 0) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenUnknown); } if (*fname != NUL && !error) { @@ -8439,7 +8470,6 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) static void filter_map(typval_T *argvars, typval_T *rettv, int map) { typval_T *expr; - listitem_T *li, *nli; list_T *l = NULL; dictitem_T *di; hashtab_T *ht; @@ -8457,11 +8487,14 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) int idx = 0; if (argvars[0].v_type == VAR_LIST) { + tv_copy(&argvars[0], rettv); if ((l = argvars[0].vval.v_list) == NULL - || (!map && tv_check_lock(l->lv_lock, arg_errmsg, TV_TRANSLATE))) { + || (!map && tv_check_lock(tv_list_locked(l), arg_errmsg, + TV_TRANSLATE))) { return; } } else if (argvars[0].v_type == VAR_DICT) { + tv_copy(&argvars[0], rettv); if ((d = argvars[0].vval.v_dict) == NULL || (!map && tv_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) { return; @@ -8520,18 +8553,21 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) } else { vimvars[VV_KEY].vv_type = VAR_NUMBER; - for (li = l->lv_first; li != NULL; li = nli) { + for (listitem_T *li = tv_list_first(l); li != NULL;) { if (map - && tv_check_lock(li->li_tv.v_lock, arg_errmsg, TV_TRANSLATE)) { + && tv_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, + TV_TRANSLATE)) { break; } - nli = li->li_next; vimvars[VV_KEY].vv_nr = idx; - if (filter_map_one(&li->li_tv, expr, map, &rem) == FAIL - || did_emsg) + if (filter_map_one(TV_LIST_ITEM_TV(li), expr, map, &rem) == FAIL + || did_emsg) { break; + } if (!map && rem) { - tv_list_item_remove(l, li); + li = tv_list_item_remove(l, li); + } else { + li = TV_LIST_ITEM_NEXT(l, li); } idx++; } @@ -8542,8 +8578,6 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) did_emsg |= save_did_emsg; } - - tv_copy(&argvars[0], rettv); } static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp) @@ -8935,7 +8969,7 @@ static void common_function(typval_T *argvars, typval_T *rettv, goto theend; } list = argvars[arg_idx].vval.v_list; - if (list == NULL || list->lv_len == 0) { + if (tv_list_len(list) == 0) { arg_idx = 0; } } @@ -8946,7 +8980,7 @@ static void common_function(typval_T *argvars, typval_T *rettv, // result is a VAR_PARTIAL if (arg_idx > 0 || (arg_pt != NULL && arg_pt->pt_argc > 0)) { const int arg_len = (arg_pt == NULL ? 0 : arg_pt->pt_argc); - const int lv_len = (list == NULL ? 0 : list->lv_len); + const int lv_len = tv_list_len(list); pt->pt_argc = arg_len + lv_len; pt->pt_argv = xmalloc(sizeof(pt->pt_argv[0]) * pt->pt_argc); @@ -8955,11 +8989,9 @@ static void common_function(typval_T *argvars, typval_T *rettv, tv_copy(&arg_pt->pt_argv[i], &pt->pt_argv[i]); } if (lv_len > 0) { - for (listitem_T *li = list->lv_first; - li != NULL; - li = li->li_next) { - tv_copy(&li->li_tv, &pt->pt_argv[i++]); - } + TV_LIST_ITER(list, li, { + tv_copy(TV_LIST_ITEM_TV(li), &pt->pt_argv[i++]); + }); } } @@ -9045,7 +9077,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) li = tv_list_find(l, tv_get_number_chk(&argvars[1], &error)); if (!error && li != NULL) { - tv = &li->li_tv; + tv = TV_LIST_ITEM_TV(li); } } } else if (argvars[0].v_type == VAR_DICT) { @@ -9086,7 +9118,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } else if (strcmp(what, "args") == 0) { rettv->v_type = VAR_LIST; - if (tv_list_alloc_ret(rettv) != NULL) { + if (tv_list_alloc_ret(rettv, pt->pt_argc) != NULL) { for (int i = 0; i < pt->pt_argc; i++) { tv_list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]); } @@ -9110,8 +9142,10 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// Returns information about signs placed in a buffer as list of dicts. -static void get_buffer_signs(buf_T *buf, list_T *l) +static list_T *get_buffer_signs(buf_T *buf) + FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { + list_T *const l = tv_list_alloc(kListLenMayKnow); for (signlist_T *sign = buf->b_signlist; sign; sign = sign->next) { dict_T *const d = tv_dict_alloc(); @@ -9122,6 +9156,7 @@ static void get_buffer_signs(buf_T *buf, list_T *l) tv_list_append_dict(l, d); } + return l; } /// Returns buffer options, variables and other attributes in a dictionary. @@ -9145,7 +9180,7 @@ static dict_T *get_buffer_info(buf_T *buf) tv_dict_add_dict(dict, S_LEN("variables"), buf->b_vars); // List of windows displaying this buffer - list_T *const windows = tv_list_alloc(); + list_T *const windows = tv_list_alloc(kListLenMayKnow); FOR_ALL_TAB_WINDOWS(tp, wp) { if (wp->w_buffer == buf) { tv_list_append_number(windows, (varnumber_T)wp->handle); @@ -9155,9 +9190,7 @@ static dict_T *get_buffer_info(buf_T *buf) if (buf->b_signlist != NULL) { // List of signs placed in this buffer - list_T *const signs = tv_list_alloc(); - get_buffer_signs(buf, signs); - tv_dict_add_list(dict, S_LEN("signs"), signs); + tv_dict_add_list(dict, S_LEN("signs"), get_buffer_signs(buf)); } return dict; @@ -9171,7 +9204,7 @@ static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool sel_buflisted = false; bool sel_bufloaded = false; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); // List of all the buffers or selected buffers if (argvars[0].v_type == VAR_DICT) { @@ -9230,35 +9263,33 @@ static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retlist, typval_T *rettv) { - char_u *p; - - rettv->v_type = VAR_STRING; + rettv->v_type = (retlist ? VAR_LIST : VAR_STRING); rettv->vval.v_string = NULL; - if (retlist) { - tv_list_alloc_ret(rettv); - } - if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0) + if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0 || end < start) { + if (retlist) { + tv_list_alloc_ret(rettv, 0); + } return; + } - if (!retlist) { - if (start >= 1 && start <= buf->b_ml.ml_line_count) - p = ml_get_buf(buf, start, FALSE); - else - p = (char_u *)""; - rettv->vval.v_string = vim_strsave(p); - } else { - if (end < start) - return; - - if (start < 1) + if (retlist) { + if (start < 1) { start = 1; - if (end > buf->b_ml.ml_line_count) + } + if (end > buf->b_ml.ml_line_count) { end = buf->b_ml.ml_line_count; + } + tv_list_alloc_ret(rettv, end - start + 1); while (start <= end) { tv_list_append_string(rettv->vval.v_list, (const char *)ml_get_buf(buf, start++, false), -1); } + } else { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = ((start >= 1 && start <= buf->b_ml.ml_line_count) + ? vim_strsave(ml_get_buf(buf, start, false)) + : NULL); } } @@ -9583,8 +9614,8 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) theend: pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context); - tv_list_alloc_ret(rettv); ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP); + tv_list_alloc_ret(rettv, xpc.xp_numfiles); for (int i = 0; i < xpc.xp_numfiles; i++) { tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], @@ -9878,7 +9909,7 @@ static void f_getline(typval_T *argvars, typval_T *rettv, FunPtr fptr) const linenr_T lnum = tv_get_lnum(argvars); if (argvars[1].v_type == VAR_UNKNOWN) { - end = 0; + end = lnum; retlist = false; } else { end = tv_get_lnum(&argvars[1]); @@ -9892,7 +9923,7 @@ static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, typval_T *rettv) { if (what_arg->v_type == VAR_UNKNOWN) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); if (is_qf || wp != NULL) { (void)get_errorlist(wp, -1, rettv->vval.v_list); } @@ -9927,7 +9958,7 @@ static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) matchitem_T *cur = curwin->w_match_head; int i; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); while (cur != NULL) { dict_T *dict = tv_dict_alloc(); if (cur->match.regprog == NULL) { @@ -9940,7 +9971,7 @@ static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (llpos->lnum == 0) { break; } - list_T *l = tv_list_alloc(); + list_T *const l = tv_list_alloc(1 + (llpos->col > 0 ? 2 : 0)); tv_list_append_number(l, (varnumber_T)llpos->lnum); if (llpos->col > 0) { tv_list_append_number(l, (varnumber_T)llpos->col); @@ -9989,7 +10020,7 @@ static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos) fp = var2fpos(&argvars[0], true, &fnum); } - list_T *l = tv_list_alloc_ret(rettv); + list_T *const l = tv_list_alloc_ret(rettv, 4 + (!!getcurpos)); tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0); tv_list_append_number(l, ((fp != NULL) ? (varnumber_T)fp->lnum @@ -10062,12 +10093,12 @@ static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (return_list) { rettv->v_type = VAR_LIST; - rettv->vval.v_list = + rettv->vval.v_list = get_reg_contents(regname, (arg2 ? kGRegExprSrc : 0) | kGRegList); if (rettv->vval.v_list == NULL) { - rettv->vval.v_list = tv_list_alloc(); + rettv->vval.v_list = tv_list_alloc(0); } - rettv->vval.v_list->lv_refcount++; + tv_list_ref(rettv->vval.v_list); } else { rettv->v_type = VAR_STRING; rettv->vval.v_string = get_reg_contents(regname, arg2 ? kGRegExprSrc : 0); @@ -10115,7 +10146,7 @@ static dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx) tv_dict_add_nr(dict, S_LEN("tabnr"), tp_idx); - list_T *const l = tv_list_alloc(); + list_T *const l = tv_list_alloc(kListLenMayKnow); FOR_ALL_WINDOWS_IN_TAB(wp, tp) { tv_list_append_number(l, (varnumber_T)wp->handle); } @@ -10132,7 +10163,9 @@ static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tabpage_T *tparg = NULL; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, (argvars[0].v_type == VAR_UNKNOWN + ? 1 + : kListLenMayKnow)); if (argvars[0].v_type != VAR_UNKNOWN) { // Information about one tab page @@ -10231,7 +10264,7 @@ static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) { win_T *wparg = NULL; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); if (argvars[0].v_type != VAR_UNKNOWN) { wparg = win_id2wp(argvars); @@ -10456,9 +10489,9 @@ static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = ExpandOne( &xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options, WILD_ALL); } else { - tv_list_alloc_ret(rettv); ExpandOne(&xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options, WILD_ALL_KEEP); + tv_list_alloc_ret(rettv, xpc.xp_numfiles); for (int i = 0; i < xpc.xp_numfiles; i++) { tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], -1); @@ -10507,7 +10540,7 @@ static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (rettv->v_type == VAR_STRING) { rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n"); } else { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, ga.ga_len); for (int i = 0; i < ga.ga_len; i++) { tv_list_append_string(rettv->vval.v_list, ((const char **)(ga.ga_data))[i], -1); @@ -10646,6 +10679,9 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) "windows", "winaltkeys", "writebackup", +#if defined(HAVE_WSL) + "wsl", +#endif "nvim", }; @@ -10705,6 +10741,17 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) n = true; } + if (STRICMP(name, "ruby") == 0 && n == true) { + char *rubyhost = call_func_retstr("provider#ruby#Detect", 0, NULL, true); + if (rubyhost) { + if (*rubyhost == NUL) { + // Invalid rubyhost executable. Gem is probably not installed. + n = false; + } + xfree(rubyhost); + } + } + rettv->vval.v_number = n; } @@ -11024,39 +11071,42 @@ static void f_indent(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - list_T *l; - listitem_T *item; long idx = 0; - int ic = FALSE; + bool ic = false; rettv->vval.v_number = -1; if (argvars[0].v_type != VAR_LIST) { EMSG(_(e_listreq)); return; } - l = argvars[0].vval.v_list; + list_T *const l = argvars[0].vval.v_list; if (l != NULL) { - item = l->lv_first; + listitem_T *item = tv_list_first(l); if (argvars[2].v_type != VAR_UNKNOWN) { bool error = false; - // Start at specified item. Use the cached index that tv_list_find() - // sets, so that a negative number also works. - item = tv_list_find(l, tv_get_number_chk(&argvars[2], &error)); - idx = l->lv_idx; - if (argvars[3].v_type != VAR_UNKNOWN) { - ic = tv_get_number_chk(&argvars[3], &error); - } - if (error) { + // Start at specified item. + idx = tv_list_uidx(l, tv_get_number_chk(&argvars[2], &error)); + if (error || idx == -1) { item = NULL; + } else { + item = tv_list_find(l, idx); + assert(item != NULL); + } + if (argvars[3].v_type != VAR_UNKNOWN) { + ic = !!tv_get_number_chk(&argvars[3], &error); + if (error) { + item = NULL; + } } } - for (; item != NULL; item = item->li_next, ++idx) - if (tv_equal(&item->li_tv, &argvars[1], ic, FALSE)) { + for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) { + if (tv_equal(TV_LIST_ITEM_TV(item), &argvars[1], ic, false)) { rettv->vval.v_number = idx; break; } + } } } @@ -11085,6 +11135,7 @@ void get_user_input(const typval_T *const argvars, char defstr_buf[NUMBUFLEN]; char cancelreturn_buf[NUMBUFLEN]; char xp_name_buf[NUMBUFLEN]; + char def[1] = { 0 }; if (argvars[0].v_type == VAR_DICT) { if (argvars[1].v_type != VAR_UNKNOWN) { emsgf(_("E5050: {opts} must be the only argument")); @@ -11099,7 +11150,6 @@ void get_user_input(const typval_T *const argvars, if (defstr == NULL) { return; } - char def[1] = { 0 }; cancelreturn = tv_dict_get_string_buf_chk(dict, S_LEN("cancelreturn"), cancelreturn_buf, def); if (cancelreturn == NULL) { // error @@ -11220,11 +11270,10 @@ static void f_inputdialog(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - listitem_T *li; int selected; int mouse_used; - if (argvars[0].v_type != VAR_LIST || argvars[0].vval.v_list == NULL) { + if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listarg), "inputlist()"); return; } @@ -11235,15 +11284,16 @@ static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) msg_scroll = TRUE; msg_clr_eos(); - for (li = argvars[0].vval.v_list->lv_first; li != NULL; li = li->li_next) { - msg_puts(tv_get_string(&li->li_tv)); + TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { + msg_puts(tv_get_string(TV_LIST_ITEM_TV(li))); msg_putchar('\n'); - } + }); - /* Ask for choice. */ + // Ask for choice. selected = prompt_for_number(&mouse_used); - if (mouse_used) + if (mouse_used) { selected -= lines_left; + } rettv->vval.v_number = selected; } @@ -11299,9 +11349,8 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listarg), "insert()"); - } else if ((l = argvars[0].vval.v_list) != NULL - && !tv_check_lock(l->lv_lock, N_("insert() argument"), - TV_TRANSLATE)) { + } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + N_("insert() argument"), TV_TRANSLATE)) { long before = 0; if (argvars[2].v_type != VAR_UNKNOWN) { before = tv_get_number_chk(&argvars[2], &error); @@ -11312,7 +11361,7 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) } listitem_T *item = NULL; - if (before != l->lv_len) { + if (before != tv_list_len(l)) { item = tv_list_find(l, before); if (item == NULL) { EMSGN(_(e_listidx), before); @@ -11376,7 +11425,7 @@ static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG2(_(e_dictkey), lv.ll_newkey); } else if (lv.ll_list != NULL) { // List item. - rettv->vval.v_number = tv_islocked(&lv.ll_li->li_tv); + rettv->vval.v_number = tv_islocked(TV_LIST_ITEM_TV(lv.ll_li)); } else { // Dictionary item. rettv->vval.v_number = tv_islocked(&lv.ll_di->di_tv); @@ -11405,43 +11454,41 @@ static void dict_list(typval_T *const tv, typval_T *const rettv, return; } - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, tv_dict_len(tv->vval.v_dict)); TV_DICT_ITER(tv->vval.v_dict, di, { - listitem_T *const li = tv_list_item_alloc(); - tv_list_append(rettv->vval.v_list, li); + typval_T tv = { .v_lock = VAR_UNLOCKED }; switch (what) { case kDictListKeys: { - li->li_tv.v_type = VAR_STRING; - li->li_tv.v_lock = VAR_UNLOCKED; - li->li_tv.vval.v_string = vim_strsave(di->di_key); + tv.v_type = VAR_STRING; + tv.vval.v_string = vim_strsave(di->di_key); break; } case kDictListValues: { - tv_copy(&di->di_tv, &li->li_tv); + tv_copy(&di->di_tv, &tv); break; } case kDictListItems: { // items() - list_T *const sub_l = tv_list_alloc(); - li->li_tv.v_type = VAR_LIST; - li->li_tv.v_lock = VAR_UNLOCKED; - li->li_tv.vval.v_list = sub_l; - sub_l->lv_refcount++; - - listitem_T *sub_li = tv_list_item_alloc(); - tv_list_append(sub_l, sub_li); - sub_li->li_tv.v_type = VAR_STRING; - sub_li->li_tv.v_lock = VAR_UNLOCKED; - sub_li->li_tv.vval.v_string = vim_strsave(di->di_key); - - sub_li = tv_list_item_alloc(); - tv_list_append(sub_l, sub_li); - tv_copy(&di->di_tv, &sub_li->li_tv); + list_T *const sub_l = tv_list_alloc(2); + tv.v_type = VAR_LIST; + tv.vval.v_list = sub_l; + tv_list_ref(sub_l); + + tv_list_append_owned_tv(sub_l, (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = (char_u *)xstrdup((const char *)di->di_key), + }); + + tv_list_append_tv(sub_l, &di->di_tv); + break; } } + + tv_list_append_owned_tv(rettv->vval.v_list, tv); }); } @@ -11536,15 +11583,13 @@ static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable) } list_T *argl = cmd_tv->vval.v_list; - int argc = argl->lv_len; + int argc = tv_list_len(argl); if (!argc) { EMSG(_(e_invarg)); // List must have at least one item. return NULL; } - assert(argl->lv_first); - - const char *exe = tv_get_string_chk(&argl->lv_first->li_tv); + const char *exe = tv_get_string_chk(TV_LIST_ITEM_TV(tv_list_first(argl))); if (!exe || !os_can_exe((const char_u *)exe, NULL, true)) { if (exe && executable) { *executable = false; @@ -11559,15 +11604,15 @@ static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable) // Build the argument vector int i = 0; char **argv = xcalloc(argc + 1, sizeof(char *)); - for (listitem_T *arg = argl->lv_first; arg != NULL; arg = arg->li_next) { - const char *a = tv_get_string_chk(&arg->li_tv); + TV_LIST_ITER_CONST(argl, arg, { + const char *a = tv_get_string_chk(TV_LIST_ITEM_TV(arg)); if (!a) { // Did emsg in tv_get_string_chk; just deallocate argv. shell_free_argv(argv); return NULL; } argv[i++] = xstrdup(a); - } + }); return argv; } @@ -11695,7 +11740,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) list_T *args = argvars[0].vval.v_list; - Channel **jobs = xcalloc(args->lv_len, sizeof(*jobs)); + Channel **jobs = xcalloc(tv_list_len(args), sizeof(*jobs)); ui_busy_start(); MultiQueue *waiting_jobs = multiqueue_new_parent(loop_on_put, &main_loop); @@ -11704,10 +11749,10 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) // -1 for jobs that were skipped or timed out. int i = 0; - for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next, i++) { + TV_LIST_ITER_CONST(args, arg, { Channel *chan = NULL; - if (arg->li_tv.v_type != VAR_NUMBER - || !(chan = find_job(arg->li_tv.vval.v_number, false))) { + if (TV_LIST_ITEM_TV(arg)->v_type != VAR_NUMBER + || !(chan = find_job(TV_LIST_ITEM_TV(arg)->vval.v_number, false))) { jobs[i] = NULL; } else { jobs[i] = chan; @@ -11719,7 +11764,8 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) multiqueue_replace_parent(chan->events, waiting_jobs); } } - } + i++; + }); int remaining = -1; uint64_t before = 0; @@ -11728,7 +11774,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) before = os_hrtime(); } - for (i = 0; i < args->lv_len; i++) { + for (i = 0; i < tv_list_len(args); i++) { if (remaining == 0) { // timed out break; @@ -11755,10 +11801,10 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - list_T *rv = tv_list_alloc(); + list_T *const rv = tv_list_alloc(tv_list_len(args)); // restore the parent queue for any jobs still alive - for (i = 0; i < args->lv_len; i++) { + for (i = 0; i < tv_list_len(args); i++) { if (jobs[i] == NULL) { tv_list_append_number(rv, -3); continue; @@ -11774,7 +11820,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) multiqueue_free(waiting_jobs); xfree(jobs); ui_busy_stop(); - rv->lv_refcount++; + tv_list_ref(rv); rettv->v_type = VAR_LIST; rettv->vval.v_list = rv; } @@ -11788,9 +11834,6 @@ static void f_join(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG(_(e_listreq)); return; } - if (argvars[0].vval.v_list == NULL) { - return; - } const char *const sep = (argvars[1].v_type == VAR_UNKNOWN ? " " : tv_get_string_chk(&argvars[1])); @@ -12161,7 +12204,8 @@ static void f_mapcheck(typval_T *argvars, typval_T *rettv, FunPtr fptr) } -static void find_some_match(typval_T *argvars, typval_T *rettv, int type) +static void find_some_match(typval_T *const argvars, typval_T *const rettv, + const SomeMatchType type) { char_u *str = NULL; long len = 0; @@ -12182,25 +12226,38 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) p_cpo = (char_u *)""; rettv->vval.v_number = -1; - if (type == 3 || type == 4) { - // type 3: return empty list when there are no matches. - // type 4: return ["", -1, -1, -1] - tv_list_alloc_ret(rettv); - if (type == 4) { + switch (type) { + // matchlist(): return empty list when there are no matches. + case kSomeMatchList: { + tv_list_alloc_ret(rettv, kListLenMayKnow); + break; + } + // matchstrpos(): return ["", -1, -1, -1] + case kSomeMatchStrPos: { + tv_list_alloc_ret(rettv, 4); tv_list_append_string(rettv->vval.v_list, "", 0); tv_list_append_number(rettv->vval.v_list, -1); tv_list_append_number(rettv->vval.v_list, -1); tv_list_append_number(rettv->vval.v_list, -1); + break; + } + case kSomeMatchStr: { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + break; + } + case kSomeMatch: + case kSomeMatchEnd: { + // Do nothing: zero is default. + break; } - } else if (type == 2) { - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; } if (argvars[0].v_type == VAR_LIST) { - if ((l = argvars[0].vval.v_list) == NULL) + if ((l = argvars[0].vval.v_list) == NULL) { goto theend; - li = l->lv_first; + } + li = tv_list_first(l); } else { expr = str = (char_u *)tv_get_string(&argvars[0]); len = (long)STRLEN(str); @@ -12220,11 +12277,11 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) goto theend; } if (l != NULL) { - li = tv_list_find(l, start); - if (li == NULL) { + idx = tv_list_uidx(l, start); + if (idx == -1) { goto theend; } - idx = l->lv_idx; // Use the cached index. + li = tv_list_find(l, idx); } else { if (start < 0) start = 0; @@ -12260,7 +12317,8 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) break; } xfree(tofree); - tofree = expr = str = (char_u *)encode_tv2echo(&li->li_tv, NULL); + tofree = expr = str = (char_u *)encode_tv2echo(TV_LIST_ITEM_TV(li), + NULL); if (str == NULL) { break; } @@ -12275,8 +12333,8 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) /* Advance to just after the match. */ if (l != NULL) { - li = li->li_next; - ++idx; + li = TV_LIST_ITEM_NEXT(l, li); + idx++; } else { startcol = (colnr_T)(regmatch.startp[0] + (*mb_ptr2len)(regmatch.startp[0]) - str); @@ -12288,62 +12346,76 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) } if (match) { - if (type == 4) { - listitem_T *li1 = rettv->vval.v_list->lv_first; - listitem_T *li2 = li1->li_next; - listitem_T *li3 = li2->li_next; - listitem_T *li4 = li3->li_next; - xfree(li1->li_tv.vval.v_string); - - int rd = (int)(regmatch.endp[0] - regmatch.startp[0]); - li1->li_tv.vval.v_string = vim_strnsave(regmatch.startp[0], rd); - li3->li_tv.vval.v_number = (varnumber_T)(regmatch.startp[0] - expr); - li4->li_tv.vval.v_number = (varnumber_T)(regmatch.endp[0] - expr); - if (l != NULL) { - li2->li_tv.vval.v_number = (varnumber_T)idx; + switch (type) { + case kSomeMatchStrPos: { + list_T *const ret_l = rettv->vval.v_list; + listitem_T *li1 = tv_list_first(ret_l); + listitem_T *li2 = TV_LIST_ITEM_NEXT(ret_l, li1); + listitem_T *li3 = TV_LIST_ITEM_NEXT(ret_l, li2); + listitem_T *li4 = TV_LIST_ITEM_NEXT(ret_l, li3); + xfree(TV_LIST_ITEM_TV(li1)->vval.v_string); + + const size_t rd = (size_t)(regmatch.endp[0] - regmatch.startp[0]); + TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz( + (const char *)regmatch.startp[0], rd); + TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)( + regmatch.startp[0] - expr); + TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)( + regmatch.endp[0] - expr); + if (l != NULL) { + TV_LIST_ITEM_TV(li2)->vval.v_number = (varnumber_T)idx; + } + break; } - } else if (type == 3) { - int i; - - /* return list with matched string and submatches */ - for (i = 0; i < NSUBEXP; ++i) { - if (regmatch.endp[i] == NULL) { - tv_list_append_string(rettv->vval.v_list, NULL, 0); - } else { - tv_list_append_string(rettv->vval.v_list, - (const char *)regmatch.startp[i], - (regmatch.endp[i] - regmatch.startp[i])); + case kSomeMatchList: { + // Return list with matched string and submatches. + for (int i = 0; i < NSUBEXP; i++) { + if (regmatch.endp[i] == NULL) { + tv_list_append_string(rettv->vval.v_list, NULL, 0); + } else { + tv_list_append_string(rettv->vval.v_list, + (const char *)regmatch.startp[i], + (regmatch.endp[i] - regmatch.startp[i])); + } } + break; } - } else if (type == 2) { - // Return matched string. - if (l != NULL) { - tv_copy(&li->li_tv, rettv); - } else { - rettv->vval.v_string = (char_u *)xmemdupz( - (const char *)regmatch.startp[0], - (size_t)(regmatch.endp[0] - regmatch.startp[0])); + case kSomeMatchStr: { + // Return matched string. + if (l != NULL) { + tv_copy(TV_LIST_ITEM_TV(li), rettv); + } else { + rettv->vval.v_string = (char_u *)xmemdupz( + (const char *)regmatch.startp[0], + (size_t)(regmatch.endp[0] - regmatch.startp[0])); + } + break; } - } else if (l != NULL) { - rettv->vval.v_number = idx; - } else { - if (type != 0) { - rettv->vval.v_number = - (varnumber_T)(regmatch.startp[0] - str); - } else { - rettv->vval.v_number = - (varnumber_T)(regmatch.endp[0] - str); + case kSomeMatch: + case kSomeMatchEnd: { + if (l != NULL) { + rettv->vval.v_number = idx; + } else { + if (type == kSomeMatch) { + rettv->vval.v_number = + (varnumber_T)(regmatch.startp[0] - str); + } else { + rettv->vval.v_number = + (varnumber_T)(regmatch.endp[0] - str); + } + rettv->vval.v_number += (varnumber_T)(str - expr); + } + break; } - rettv->vval.v_number += (varnumber_T)(str - expr); } } vim_regfree(regmatch.regprog); } - if (type == 4 && l == NULL) { + if (type == kSomeMatchStrPos && l == NULL) { // matchstrpos() without a list: drop the second item - tv_list_item_remove(rettv->vval.v_list, - rettv->vval.v_list->lv_first->li_next); + list_T *const ret_l = rettv->vval.v_list; + tv_list_item_remove(ret_l, TV_LIST_ITEM_NEXT(ret_l, tv_list_first(ret_l))); } theend: @@ -12356,7 +12428,7 @@ theend: */ static void f_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - find_some_match(argvars, rettv, 1); + find_some_match(argvars, rettv, kSomeMatch); } /* @@ -12409,56 +12481,56 @@ static void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - rettv->vval.v_number = -1; + rettv->vval.v_number = -1; - char buf[NUMBUFLEN]; - const char *const group = tv_get_string_buf_chk(&argvars[0], buf); - if (group == NULL) { - return; - } + char buf[NUMBUFLEN]; + const char *const group = tv_get_string_buf_chk(&argvars[0], buf); + if (group == NULL) { + return; + } - if (argvars[1].v_type != VAR_LIST) { - EMSG2(_(e_listarg), "matchaddpos()"); - return; - } + if (argvars[1].v_type != VAR_LIST) { + EMSG2(_(e_listarg), "matchaddpos()"); + return; + } - list_T *l; - l = argvars[1].vval.v_list; - if (l == NULL) { - return; - } + list_T *l; + l = argvars[1].vval.v_list; + if (l == NULL) { + return; + } - bool error = false; - int prio = 10; - int id = -1; - const char *conceal_char = NULL; + bool error = false; + int prio = 10; + int id = -1; + const char *conceal_char = NULL; - if (argvars[2].v_type != VAR_UNKNOWN) { - prio = tv_get_number_chk(&argvars[2], &error); - if (argvars[3].v_type != VAR_UNKNOWN) { - id = tv_get_number_chk(&argvars[3], &error); - if (argvars[4].v_type != VAR_UNKNOWN) { - if (argvars[4].v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - return; - } - dictitem_T *di; - if ((di = tv_dict_find(argvars[4].vval.v_dict, S_LEN("conceal"))) - != NULL) { - conceal_char = tv_get_string(&di->di_tv); - } + if (argvars[2].v_type != VAR_UNKNOWN) { + prio = tv_get_number_chk(&argvars[2], &error); + if (argvars[3].v_type != VAR_UNKNOWN) { + id = tv_get_number_chk(&argvars[3], &error); + if (argvars[4].v_type != VAR_UNKNOWN) { + if (argvars[4].v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + return; + } + dictitem_T *di; + if ((di = tv_dict_find(argvars[4].vval.v_dict, S_LEN("conceal"))) + != NULL) { + conceal_char = tv_get_string(&di->di_tv); } } } - if (error == true) { - return; - } + } + if (error == true) { + return; + } - // id == 3 is ok because matchaddpos() is supposed to substitute :3match - if (id == 1 || id == 2) { - EMSGN(_("E798: ID is reserved for \"match\": %" PRId64), id); - return; - } + // id == 3 is ok because matchaddpos() is supposed to substitute :3match + if (id == 1 || id == 2) { + EMSGN(_("E798: ID is reserved for \"match\": %" PRId64), id); + return; + } rettv->vval.v_number = match_add(curwin, group, NULL, prio, id, l, conceal_char); @@ -12469,14 +12541,16 @@ static void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tv_list_alloc_ret(rettv); + const int id = tv_get_number(&argvars[0]); - int id = tv_get_number(&argvars[0]); + tv_list_alloc_ret(rettv, (id >= 1 && id <= 3 + ? 2 + : 0)); if (id >= 1 && id <= 3) { - matchitem_T *m; + matchitem_T *const m = (matchitem_T *)get_match(curwin, id); - if ((m = (matchitem_T *)get_match(curwin, id)) != NULL) { + if (m != NULL) { tv_list_append_string(rettv->vval.v_list, (const char *)syn_id2name(m->hlg_id), -1); tv_list_append_string(rettv->vval.v_list, (const char *)m->pattern, -1); @@ -12501,7 +12575,7 @@ static void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - find_some_match(argvars, rettv, 0); + find_some_match(argvars, rettv, kSomeMatchEnd); } /* @@ -12509,7 +12583,7 @@ static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - find_some_match(argvars, rettv, 3); + find_some_match(argvars, rettv, kSomeMatchList); } /* @@ -12517,13 +12591,13 @@ static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_matchstr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - find_some_match(argvars, rettv, 2); + find_some_match(argvars, rettv, kSomeMatchStr); } /// "matchstrpos()" function static void f_matchstrpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - find_some_match(argvars, rettv, 4); + find_some_match(argvars, rettv, kSomeMatchStrPos); } /// Get maximal/minimal number value in a list or dictionary @@ -12539,41 +12613,41 @@ static void max_min(const typval_T *const tv, typval_T *const rettv, const bool domax) FUNC_ATTR_NONNULL_ALL { - varnumber_T n = 0; bool error = false; + rettv->vval.v_number = 0; + varnumber_T n = (domax ? VARNUMBER_MIN : VARNUMBER_MAX); if (tv->v_type == VAR_LIST) { - const list_T *const l = tv->vval.v_list; - if (tv_list_len(l) != 0) { - n = tv_get_number_chk(&l->lv_first->li_tv, &error); - for (const listitem_T *li = l->lv_first->li_next; li != NULL && !error; - li = li->li_next) { - const varnumber_T i = tv_get_number_chk(&li->li_tv, &error); - if (domax ? i > n : i < n) { - n = i; - } - } + if (tv_list_len(tv->vval.v_list) == 0) { + return; } + TV_LIST_ITER_CONST(tv->vval.v_list, li, { + const varnumber_T i = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error); + if (error) { + return; + } + if (domax ? i > n : i < n) { + n = i; + } + }); } else if (tv->v_type == VAR_DICT) { - if (tv->vval.v_dict != NULL) { - bool first = true; - TV_DICT_ITER(tv->vval.v_dict, di, { - const varnumber_T i = tv_get_number_chk(&di->di_tv, &error); - if (error) { - break; - } - if (first) { - n = i; - first = true; - } else if (domax ? i > n : i < n) { - n = i; - } - }); + if (tv_dict_len(tv->vval.v_dict) == 0) { + return; } + TV_DICT_ITER(tv->vval.v_dict, di, { + const varnumber_T i = tv_get_number_chk(&di->di_tv, &error); + if (error) { + return; + } + if (domax ? i > n : i < n) { + n = i; + } + }); } else { EMSG2(_(e_listdictarg), domax ? "max()" : "min()"); + return; } - rettv->vval.v_number = error ? 0 : n; + rettv->vval.v_number = n; } /* @@ -12658,23 +12732,20 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG2(_(e_listarg), "msgpackdump()"); return; } - list_T *ret_list = tv_list_alloc_ret(rettv); - const list_T *list = argvars[0].vval.v_list; - if (list == NULL) { - return; - } + list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow); + list_T *const list = argvars[0].vval.v_list; msgpack_packer *lpacker = msgpack_packer_new(ret_list, &encode_list_write); const char *const msg = _("msgpackdump() argument, index %i"); // Assume that translation will not take more then 4 times more space char msgbuf[sizeof("msgpackdump() argument, index ") * 4 + NUMBUFLEN]; int idx = 0; - for (listitem_T *li = list->lv_first; li != NULL; li = li->li_next) { - vim_snprintf(msgbuf, sizeof(msgbuf), (char *) msg, idx); + TV_LIST_ITER(list, li, { + vim_snprintf(msgbuf, sizeof(msgbuf), (char *)msg, idx); idx++; - if (encode_vim_to_msgpack(lpacker, &li->li_tv, msgbuf) == FAIL) { + if (encode_vim_to_msgpack(lpacker, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) { break; } - } + }); msgpack_packer_free(lpacker); } @@ -12686,12 +12757,12 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG2(_(e_listarg), "msgpackparse()"); return; } - list_T *ret_list = tv_list_alloc_ret(rettv); - const list_T *list = argvars[0].vval.v_list; - if (list == NULL || list->lv_first == NULL) { + list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow); + const list_T *const list = argvars[0].vval.v_list; + if (tv_list_len(list) == 0) { return; } - if (list->lv_first->li_tv.v_type != VAR_STRING) { + if (TV_LIST_ITEM_TV(tv_list_first(list))->v_type != VAR_STRING) { EMSG2(_(e_invarg2), "List item is not a string"); return; } @@ -12731,13 +12802,12 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) goto f_msgpackparse_exit; } if (result == MSGPACK_UNPACK_SUCCESS) { - listitem_T *li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_UNKNOWN; - tv_list_append(ret_list, li); - if (msgpack_to_vim(unpacked.data, &li->li_tv) == FAIL) { + typval_T tv = { .v_type = VAR_UNKNOWN }; + if (msgpack_to_vim(unpacked.data, &tv) == FAIL) { EMSG2(_(e_invarg2), "Failed to convert msgpack string"); goto f_msgpackparse_exit; } + tv_list_append_owned_tv(ret_list, tv); } if (result == MSGPACK_UNPACK_CONTINUE) { if (rlret == OK) { @@ -12943,7 +13013,7 @@ static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else if (stride > 0 ? end + 1 < start : end - 1 > start) { emsgf(_("E727: Start past end")); } else { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, (end - start) / stride); for (i = start; stride > 0 ? i <= end : i >= end; i += stride) { tv_list_append_number(rettv->vval.v_list, (varnumber_T)i); } @@ -12964,9 +13034,6 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) long prevlen = 0; /* length of data in prev */ long prevsize = 0; /* size of prev buffer */ long maxline = MAXLNUM; - long cnt = 0; - char_u *p; /* position in buf */ - char_u *start; /* start of current line */ if (argvars[1].v_type != VAR_UNKNOWN) { if (strcmp(tv_get_string(&argvars[1]), "b") == 0) { @@ -12977,7 +13044,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - tv_list_alloc_ret(rettv); + list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown); // Always open the file in binary mode, library functions have a mind of // their own about CR-LF conversion. @@ -12987,19 +13054,20 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - while (cnt < maxline || maxline < 0) { + while (maxline < 0 || tv_list_len(l) < maxline) { readlen = (int)fread(buf, 1, io_size, fd); - /* This for loop processes what was read, but is also entered at end - * of file so that either: - * - an incomplete line gets written - * - a "binary" file gets an empty line at the end if it ends in a - * newline. */ + // This for loop processes what was read, but is also entered at end + // of file so that either: + // - an incomplete line gets written + // - a "binary" file gets an empty line at the end if it ends in a + // newline. + char_u *p; // Position in buf. + char_u *start; // Start of current line. for (p = buf, start = buf; p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary)); - ++p) { + p++) { if (*p == '\n' || readlen <= 0) { - listitem_T *li; char_u *s = NULL; size_t len = p - start; @@ -13026,22 +13094,32 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) prevlen = prevsize = 0; } - li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_STRING; - li->li_tv.v_lock = 0; - li->li_tv.vval.v_string = s; - tv_list_append(rettv->vval.v_list, li); - - start = p + 1; /* step over newline */ - if ((++cnt >= maxline && maxline >= 0) || readlen <= 0) + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = s, + }); + + start = p + 1; // Step over newline. + if (maxline < 0) { + if (tv_list_len(l) > -maxline) { + assert(tv_list_len(l) == 1 + (-maxline)); + tv_list_item_remove(l, tv_list_first(l)); + } + } else if (tv_list_len(l) >= maxline) { + assert(tv_list_len(l) == maxline); + break; + } + if (readlen <= 0) { break; - } else if (*p == NUL) + } + } else if (*p == NUL) { *p = '\n'; - /* Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this - * when finding the BF and check the previous two bytes. */ - else if (*p == 0xbf && enc_utf8 && !binary) { - /* Find the two bytes before the 0xbf. If p is at buf, or buf - * + 1, these may be in the "prev" string. */ + // Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this + // when finding the BF and check the previous two bytes. + } else if (*p == 0xbf && !binary) { + // Find the two bytes before the 0xbf. If p is at buf, or buf + 1, + // these may be in the "prev" string. char_u back1 = p >= buf + 1 ? p[-1] : prevlen >= 1 ? prev[prevlen - 1] : NUL; char_u back2 = p >= buf + 2 ? p[-2] @@ -13075,8 +13153,9 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } /* for */ - if ((cnt >= maxline && maxline >= 0) || readlen <= 0) + if ((maxline >= 0 && tv_list_len(l) >= maxline) || readlen <= 0) { break; + } if (start < p) { /* There's part of a line in buf, store it in "prev". */ if (p - start + prevlen >= prevsize) { @@ -13100,16 +13179,6 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } /* while */ - /* - * For a negative line count use only the lines at the end of the file, - * free the rest. - */ - if (maxline < 0) - while (cnt > -maxline) { - tv_list_item_remove(rettv->vval.v_list, rettv->vval.v_list->lv_first); - cnt--; - } - xfree(prev); fclose(fd); } @@ -13123,9 +13192,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// @return OK In case of success, FAIL in case of error static int list2proftime(typval_T *arg, proftime_T *tm) FUNC_ATTR_NONNULL_ALL { - if (arg->v_type != VAR_LIST - || arg->vval.v_list == NULL - || arg->vval.v_list->lv_len != 2) { + if (arg->v_type != VAR_LIST || tv_list_len(arg->vval.v_list) != 2) { return FAIL; } @@ -13190,7 +13257,7 @@ static void f_reltime(typval_T *argvars, typval_T *rettv, FunPtr fptr) STATIC_ASSERT(sizeof(u.prof) == sizeof(u) && sizeof(u.split) == sizeof(u), "type punning will produce incorrect results on this platform"); - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, 2); tv_list_append_number(rettv->vval.v_list, u.split.high); tv_list_append_number(rettv->vval.v_list, u.split.low); } @@ -13249,8 +13316,8 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } else if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listdictarg), "remove()"); - } else if ((l = argvars[0].vval.v_list) != NULL - && !tv_check_lock(l->lv_lock, arg_errmsg, TV_TRANSLATE)) { + } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + arg_errmsg, TV_TRANSLATE)) { bool error = false; idx = tv_get_number_chk(&argvars[1], &error); @@ -13261,8 +13328,8 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else { if (argvars[2].v_type == VAR_UNKNOWN) { // Remove one item, return its value. - tv_list_remove_items(l, item, item); - *rettv = item->li_tv; + tv_list_drop_items(l, item, item); + *rettv = *TV_LIST_ITEM_TV(item); xfree(item); } else { // Remove range of items, return list with values. @@ -13274,21 +13341,17 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else { int cnt = 0; - for (li = item; li != NULL; li = li->li_next) { - ++cnt; - if (li == item2) + for (li = item; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { + cnt++; + if (li == item2) { break; + } } if (li == NULL) { // Didn't find "item2" after "item". emsgf(_(e_invrange)); } else { - tv_list_remove_items(l, item, item2); - l = tv_list_alloc_ret(rettv); - l->lv_first = item; - l->lv_last = item2; - item->li_prev = NULL; - item2->li_next = NULL; - l->lv_len = cnt; + tv_list_move_items(l, item, item2, tv_list_alloc_ret(rettv, cnt), + cnt); } } } @@ -13318,7 +13381,7 @@ static void f_repeat(typval_T *argvars, typval_T *rettv, FunPtr fptr) { varnumber_T n = tv_get_number(&argvars[1]); if (argvars[0].v_type == VAR_LIST) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, (n > 0) * n * tv_list_len(argvars[0].vval.v_list)); while (n-- > 0) { tv_list_extend(rettv->vval.v_list, argvars[0].vval.v_list, NULL); } @@ -13433,7 +13496,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) q[-1] = NUL; q = (char *)path_tail((char_u *)p); } - if (q > p && !path_is_absolute_path((const char_u *)buf)) { + if (q > p && !path_is_absolute((const char_u *)buf)) { // Symlink is relative to directory of argument. Replace the // symlink with the resolved name in the same directory. const size_t p_len = strlen(p); @@ -13524,21 +13587,12 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) list_T *l; if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listarg), "reverse()"); - } else if ((l = argvars[0].vval.v_list) != NULL - && !tv_check_lock(l->lv_lock, N_("reverse() argument"), - TV_TRANSLATE)) { - listitem_T *li = l->lv_last; - l->lv_first = l->lv_last = NULL; - l->lv_len = 0; - while (li != NULL) { - listitem_T *const ni = li->li_prev; - tv_list_append(l, li); - li = ni; - } + } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + N_("reverse() argument"), TV_TRANSLATE)) { + tv_list_reverse(l); rettv->vval.v_list = l; rettv->v_type = VAR_LIST; - l->lv_refcount++; - l->lv_idx = l->lv_len - l->lv_idx - 1; + tv_list_ref(l); } } @@ -13760,7 +13814,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) scid_T save_current_SID; uint8_t *save_sourcing_name, *save_autocmd_fname, *save_autocmd_match; linenr_T save_sourcing_lnum; - int save_autocmd_fname_full, save_autocmd_bufnr; + int save_autocmd_bufnr; void *save_funccalp; if (l_provider_call_nesting) { @@ -13771,16 +13825,14 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) save_sourcing_lnum = sourcing_lnum; save_autocmd_fname = autocmd_fname; save_autocmd_match = autocmd_match; - save_autocmd_fname_full = autocmd_fname_full; save_autocmd_bufnr = autocmd_bufnr; save_funccalp = save_funccal(); - // + current_SID = provider_caller_scope.SID; sourcing_name = provider_caller_scope.sourcing_name; sourcing_lnum = provider_caller_scope.sourcing_lnum; autocmd_fname = provider_caller_scope.autocmd_fname; autocmd_match = provider_caller_scope.autocmd_match; - autocmd_fname_full = provider_caller_scope.autocmd_fname_full; autocmd_bufnr = provider_caller_scope.autocmd_bufnr; restore_funccal(provider_caller_scope.funccalp); } @@ -13796,7 +13848,6 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) sourcing_lnum = save_sourcing_lnum; autocmd_fname = save_autocmd_fname; autocmd_match = save_autocmd_match; - autocmd_fname_full = save_autocmd_fname_full; autocmd_bufnr = save_autocmd_bufnr; restore_funccal(save_funccalp); } @@ -13836,14 +13887,17 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) int argsl = 0; if (argvars[1].v_type == VAR_LIST) { args = argvars[1].vval.v_list; - argsl = args->lv_len; + argsl = tv_list_len(args); // Assert that all list items are strings - for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { - if (arg->li_tv.v_type != VAR_STRING) { - EMSG(_(e_invarg)); + int i = 0; + TV_LIST_ITER_CONST(args, arg, { + if (TV_LIST_ITEM_TV(arg)->v_type != VAR_STRING) { + emsgf(_("E5010: List item %d of the second argument is not a string"), + i); return; } - } + i++; + }); } if (argvars[0].vval.v_string == NULL || argvars[0].vval.v_string[0] == NUL) { @@ -13861,9 +13915,9 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) int i = 1; // Copy arguments to the vector if (argsl > 0) { - for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { - argv[i++] = xstrdup(tv_get_string(&arg->li_tv)); - } + TV_LIST_ITER_CONST(args, arg, { + argv[i++] = xstrdup(tv_get_string(TV_LIST_ITEM_TV(arg))); + }); } // The last item of argv must be NULL @@ -14094,7 +14148,7 @@ static void f_searchpairpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) int lnum = 0; int col = 0; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, 2); if (searchpair_cmn(argvars, &match_pos) > 0) { lnum = match_pos.lnum; @@ -14262,18 +14316,14 @@ do_searchpair ( static void f_searchpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { pos_T match_pos; - int lnum = 0; - int col = 0; - int n; int flags = 0; - tv_list_alloc_ret(rettv); + const int n = search_cmn(argvars, &match_pos, &flags); - n = search_cmn(argvars, &match_pos, &flags); - if (n > 0) { - lnum = match_pos.lnum; - col = match_pos.col; - } + tv_list_alloc_ret(rettv, 2 + (!!(flags & SP_SUBPAT))); + + const int lnum = (n > 0 ? match_pos.lnum : 0); + const int col = (n > 0 ? match_pos.col : 0); tv_list_append_number(rettv->vval.v_list, (varnumber_T)lnum); tv_list_append_number(rettv->vval.v_list, (varnumber_T)col); @@ -14289,13 +14339,9 @@ static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) char **addrs = server_address_list(&n); // Copy addrs into a linked list. - list_T *l = tv_list_alloc_ret(rettv); + list_T *const l = tv_list_alloc_ret(rettv, n); for (size_t i = 0; i < n; i++) { - listitem_T *li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_STRING; - li->li_tv.v_lock = 0; - li->li_tv.vval.v_string = (char_u *)addrs[i]; - tv_list_append(l, li); + tv_list_append_allocated_string(l, addrs[i]); } xfree(addrs); } @@ -14357,8 +14403,11 @@ static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; if (argvars[0].vval.v_string) { - server_stop((char *) argvars[0].vval.v_string); + bool rv = server_stop((char *)argvars[0].vval.v_string); + rettv->vval.v_number = (rv ? 1 : 0); } } @@ -14503,25 +14552,26 @@ static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *line = NULL; if (argvars[1].v_type == VAR_LIST) { l = argvars[1].vval.v_list; - li = l->lv_first; + li = tv_list_first(l); } else { line = tv_get_string_chk(&argvars[1]); } - /* default result is zero == OK */ + // Default result is zero == OK. for (;; ) { - if (l != NULL) { + if (argvars[1].v_type == VAR_LIST) { // List argument, get next string. if (li == NULL) { break; } - line = tv_get_string_chk(&li->li_tv); - li = li->li_next; + line = tv_get_string_chk(TV_LIST_ITEM_TV(li)); + li = TV_LIST_ITEM_NEXT(l, li); } - rettv->vval.v_number = 1; /* FAIL */ - if (line == NULL || lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) + rettv->vval.v_number = 1; // FAIL + if (line == NULL || lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) { break; + } /* When coming here from Insert mode, sync undo, so that this can be * undone separately from what was previously inserted. */ @@ -14593,7 +14643,8 @@ static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv) return; } const char *const act = tv_get_string_chk(action_arg); - if ((*act == 'a' || *act == 'r' || *act == ' ') && act[1] == NUL) { + if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f') + && act[1] == NUL) { action = *act; } else { EMSG2(_(e_invact), act); @@ -14622,8 +14673,8 @@ skip_args: title = (wp ? "setloclist()" : "setqflist()"); } - list_T *l = list_arg->vval.v_list; - if (l && set_errorlist(wp, l, action, (char_u *)title, d) == OK) { + list_T *const l = list_arg->vval.v_list; + if (set_errorlist(wp, l, action, (char_u *)title, d) == OK) { rettv->vval.v_number = 0; } } @@ -14648,8 +14699,6 @@ static void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - list_T *l; - listitem_T *li; dict_T *d; list_T *s = NULL; @@ -14658,92 +14707,89 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG(_(e_listreq)); return; } - if ((l = argvars[0].vval.v_list) != NULL) { - - /* To some extent make sure that we are dealing with a list from - * "getmatches()". */ - li = l->lv_first; - while (li != NULL) { - if (li->li_tv.v_type != VAR_DICT - || (d = li->li_tv.vval.v_dict) == NULL) { - EMSG(_(e_invarg)); - return; - } - if (!(tv_dict_find(d, S_LEN("group")) != NULL - && (tv_dict_find(d, S_LEN("pattern")) != NULL - || tv_dict_find(d, S_LEN("pos1")) != NULL) - && tv_dict_find(d, S_LEN("priority")) != NULL - && tv_dict_find(d, S_LEN("id")) != NULL)) { - EMSG(_(e_invarg)); - return; - } - li = li->li_next; + list_T *const l = argvars[0].vval.v_list; + // To some extent make sure that we are dealing with a list from + // "getmatches()". + int i = 0; + TV_LIST_ITER_CONST(l, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT + || (d = TV_LIST_ITEM_TV(li)->vval.v_dict) == NULL) { + emsgf(_("E474: List item %d is either not a dictionary " + "or an empty one"), i); + return; } + if (!(tv_dict_find(d, S_LEN("group")) != NULL + && (tv_dict_find(d, S_LEN("pattern")) != NULL + || tv_dict_find(d, S_LEN("pos1")) != NULL) + && tv_dict_find(d, S_LEN("priority")) != NULL + && tv_dict_find(d, S_LEN("id")) != NULL)) { + emsgf(_("E474: List item %d is missing one of the required keys"), i); + return; + } + i++; + }); - clear_matches(curwin); - li = l->lv_first; - bool match_add_failed = false; - while (li != NULL) { - int i = 0; - - d = li->li_tv.vval.v_dict; - dictitem_T *const di = tv_dict_find(d, S_LEN("pattern")); - if (di == NULL) { - if (s == NULL) { - s = tv_list_alloc(); - } + clear_matches(curwin); + bool match_add_failed = false; + TV_LIST_ITER_CONST(l, li, { + int i = 0; - // match from matchaddpos() - for (i = 1; i < 9; i++) { - char buf[5]; - snprintf(buf, sizeof(buf), "pos%d", i); - dictitem_T *const pos_di = tv_dict_find(d, buf, -1); - if (pos_di != NULL) { - if (pos_di->di_tv.v_type != VAR_LIST) { - return; - } + d = TV_LIST_ITEM_TV(li)->vval.v_dict; + dictitem_T *const di = tv_dict_find(d, S_LEN("pattern")); + if (di == NULL) { + if (s == NULL) { + s = tv_list_alloc(9); + } - tv_list_append_tv(s, &pos_di->di_tv); - s->lv_refcount++; - } else { - break; + // match from matchaddpos() + for (i = 1; i < 9; i++) { + char buf[5]; + snprintf(buf, sizeof(buf), "pos%d", i); + dictitem_T *const pos_di = tv_dict_find(d, buf, -1); + if (pos_di != NULL) { + if (pos_di->di_tv.v_type != VAR_LIST) { + return; } - } - } - // Note: there are three number buffers involved: - // - group_buf below. - // - numbuf in tv_dict_get_string(). - // - mybuf in tv_get_string(). - // - // If you change this code make sure that buffers will not get - // accidentally reused. - char group_buf[NUMBUFLEN]; - const char *const group = tv_dict_get_string_buf(d, "group", group_buf); - const int priority = (int)tv_dict_get_number(d, "priority"); - const int id = (int)tv_dict_get_number(d, "id"); - dictitem_T *const conceal_di = tv_dict_find(d, S_LEN("conceal")); - const char *const conceal = (conceal_di != NULL - ? tv_get_string(&conceal_di->di_tv) - : NULL); - if (i == 0) { - if (match_add(curwin, group, - tv_dict_get_string(d, "pattern", false), - priority, id, NULL, conceal) != id) { - match_add_failed = true; - } - } else { - if (match_add(curwin, group, NULL, priority, id, s, conceal) != id) { - match_add_failed = true; + tv_list_append_tv(s, &pos_di->di_tv); + tv_list_ref(s); + } else { + break; } - tv_list_unref(s); - s = NULL; } - li = li->li_next; } - if (!match_add_failed) { - rettv->vval.v_number = 0; + + // Note: there are three number buffers involved: + // - group_buf below. + // - numbuf in tv_dict_get_string(). + // - mybuf in tv_get_string(). + // + // If you change this code make sure that buffers will not get + // accidentally reused. + char group_buf[NUMBUFLEN]; + const char *const group = tv_dict_get_string_buf(d, "group", group_buf); + const int priority = (int)tv_dict_get_number(d, "priority"); + const int id = (int)tv_dict_get_number(d, "id"); + dictitem_T *const conceal_di = tv_dict_find(d, S_LEN("conceal")); + const char *const conceal = (conceal_di != NULL + ? tv_get_string(&conceal_di->di_tv) + : NULL); + if (i == 0) { + if (match_add(curwin, group, + tv_dict_get_string(d, "pattern", false), + priority, id, NULL, conceal) != id) { + match_add_failed = true; + } + } else { + if (match_add(curwin, group, NULL, priority, id, s, conceal) != id) { + match_add_failed = true; + } + tv_list_unref(s); + s = NULL; } + }); + if (!match_add_failed) { + rettv->vval.v_number = 0; } } @@ -14860,7 +14906,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[1].v_type == VAR_LIST) { list_T *ll = argvars[1].vval.v_list; // If the list is NULL handle like an empty list. - int len = ll == NULL ? 0 : ll->lv_len; + const int len = tv_list_len(ll); // First half: use for pointers to result lines; second half: use for // pointers to allocated copies. @@ -14869,11 +14915,9 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) char **allocval = lstval + len + 2; char **curallocval = allocval; - for (listitem_T *li = ll == NULL ? NULL : ll->lv_first; - li != NULL; - li = li->li_next) { + TV_LIST_ITER_CONST(ll, li, { char buf[NUMBUFLEN]; - *curval = tv_get_string_buf_chk(&li->li_tv, buf); + *curval = tv_get_string_buf_chk(TV_LIST_ITEM_TV(li), buf); if (*curval == NULL) { goto free_lstval; } @@ -14885,7 +14929,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) curallocval++; } curval++; - } + }); *curval++ = NULL; write_reg_contents_lst(regname, (char_u **)lstval, append, yank_type, @@ -15106,12 +15150,6 @@ static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; } -/// struct used in the array that's given to qsort() -typedef struct { - listitem_T *item; - int idx; -} sortItem_T; - /// struct storing information about current sort typedef struct { int item_compare_ic; @@ -15132,11 +15170,11 @@ static sortinfo_T *sortinfo = NULL; */ static int item_compare(const void *s1, const void *s2, bool keep_zero) { - sortItem_T *const si1 = (sortItem_T *)s1; - sortItem_T *const si2 = (sortItem_T *)s2; + ListSortItem *const si1 = (ListSortItem *)s1; + ListSortItem *const si2 = (ListSortItem *)s2; - typval_T *const tv1 = &si1->item->li_tv; - typval_T *const tv2 = &si2->item->li_tv; + typval_T *const tv1 = TV_LIST_ITEM_TV(si1->item); + typval_T *const tv2 = TV_LIST_ITEM_TV(si2->item); int res; @@ -15208,6 +15246,8 @@ item_compare_end: // When the result would be zero, compare the item indexes. Makes the // sort stable. if (res == 0 && !keep_zero) { + // WARNING: When using uniq si1 and si2 are actually listitem_T **, no + // indexes are there. res = si1->idx > si2->idx ? 1 : -1; } return res; @@ -15225,7 +15265,7 @@ static int item_compare_not_keeping_zero(const void *s1, const void *s2) static int item_compare2(const void *s1, const void *s2, bool keep_zero) { - sortItem_T *si1, *si2; + ListSortItem *si1, *si2; int res; typval_T rettv; typval_T argv[3]; @@ -15238,8 +15278,8 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) return 0; } - si1 = (sortItem_T *)s1; - si2 = (sortItem_T *)s2; + si1 = (ListSortItem *)s1; + si2 = (ListSortItem *)s2; if (partial == NULL) { func_name = sortinfo->item_compare_func; @@ -15249,8 +15289,8 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) // Copy the values. This is needed to be able to set v_lock to VAR_FIXED // in the copy without changing the original list items. - tv_copy(&si1->item->li_tv, &argv[0]); - tv_copy(&si2->item->li_tv, &argv[1]); + tv_copy(TV_LIST_ITEM_TV(si1->item), &argv[0]); + tv_copy(TV_LIST_ITEM_TV(si2->item), &argv[1]); rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this res = call_func((const char_u *)func_name, @@ -15273,6 +15313,8 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) // When the result would be zero, compare the pointers themselves. Makes // the sort stable. if (res == 0 && !keep_zero) { + // WARNING: When using uniq si1 and si2 are actually listitem_T **, no + // indexes are there. res = si1->idx > si2->idx ? 1 : -1; } @@ -15294,9 +15336,7 @@ static int item_compare2_not_keeping_zero(const void *s1, const void *s2) */ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) { - list_T *l; - listitem_T *li; - sortItem_T *ptrs; + ListSortItem *ptrs; long len; long i; @@ -15313,13 +15353,13 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listarg), sort ? "sort()" : "uniq()"); } else { - l = argvars[0].vval.v_list; - if (l == NULL || tv_check_lock(l->lv_lock, arg_errmsg, TV_TRANSLATE)) { - goto theend; + list_T *const l = argvars[0].vval.v_list; + if (tv_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) { + goto theend; } rettv->vval.v_list = l; rettv->v_type = VAR_LIST; - ++l->lv_refcount; + tv_list_ref(l); len = tv_list_len(l); if (len <= 1) { @@ -15385,80 +15425,45 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) } } - /* Make an array with each entry pointing to an item in the List. */ - ptrs = xmalloc((size_t)(len * sizeof (sortItem_T))); + // Make an array with each entry pointing to an item in the List. + ptrs = xmalloc((size_t)(len * sizeof(ListSortItem))); - i = 0; if (sort) { - // sort(): ptrs will be the list to sort. - for (li = l->lv_first; li != NULL; li = li->li_next) { - ptrs[i].item = li; - ptrs[i].idx = i; - i++; - } - info.item_compare_func_err = false; - // Test the compare function. - if ((info.item_compare_func != NULL - || info.item_compare_partial != NULL) - && item_compare2_not_keeping_zero(&ptrs[0], &ptrs[1]) - == ITEM_COMPARE_FAIL) { + tv_list_item_sort(l, ptrs, + ((info.item_compare_func == NULL + && info.item_compare_partial == NULL) + ? item_compare_not_keeping_zero + : item_compare2_not_keeping_zero), + &info.item_compare_func_err); + if (info.item_compare_func_err) { EMSG(_("E702: Sort compare function failed")); - } else { - // Sort the array with item pointers. - qsort(ptrs, (size_t)len, sizeof (sortItem_T), - (info.item_compare_func == NULL - && info.item_compare_partial == NULL ? - item_compare_not_keeping_zero : - item_compare2_not_keeping_zero)); - - if (!info.item_compare_func_err) { - // Clear the list and append the items in the sorted order. - l->lv_first = NULL; - l->lv_last = NULL; - l->lv_idx_item = NULL; - l->lv_len = 0; - - for (i = 0; i < len; i++) { - tv_list_append(l, ptrs[i].item); - } - } } } else { - int (*item_compare_func_ptr)(const void *, const void *); + ListSorter item_compare_func_ptr; // f_uniq(): ptrs will be a stack of items to remove. info.item_compare_func_err = false; if (info.item_compare_func != NULL || info.item_compare_partial != NULL) { - item_compare_func_ptr = item_compare2_keeping_zero; + item_compare_func_ptr = item_compare2_keeping_zero; } else { - item_compare_func_ptr = item_compare_keeping_zero; + item_compare_func_ptr = item_compare_keeping_zero; } - for (li = l->lv_first; li != NULL && li->li_next != NULL; li = li->li_next) { - if (item_compare_func_ptr(&li, &li->li_next) == 0) { - ptrs[i++].item = li; - } - if (info.item_compare_func_err) { - EMSG(_("E882: Uniq compare function failed")); - break; - } - } - - if (!info.item_compare_func_err) { - while (--i >= 0) { - assert(ptrs[i].item->li_next); - li = ptrs[i].item->li_next; - ptrs[i].item->li_next = li->li_next; - if (li->li_next != NULL) { - li->li_next->li_prev = ptrs[i].item; - } else { - l->lv_last = ptrs[i].item; + int idx = 0; + for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l)) + ; li != NULL;) { + listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li); + if (item_compare_func_ptr(&prev_li, &li) == 0) { + if (info.item_compare_func_err) { + EMSG(_("E882: Uniq compare function failed")); + break; } - tv_list_watch_fix(l, li); - tv_list_item_free(li); - l->lv_len--; + li = tv_list_item_remove(l, li); + } else { + idx++; + li = TV_LIST_ITEM_NEXT(l, li); } } } @@ -15549,13 +15554,12 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) hlf_T attr = HLF_COUNT; size_t len = 0; - tv_list_alloc_ret(rettv); - if (argvars[0].v_type == VAR_UNKNOWN) { // Find the start and length of the badly spelled word. len = spell_move_to(curwin, FORWARD, true, true, &attr); if (len != 0) { word = (char *)get_cursor_pos_ptr(); + curwin->w_set_curswant = true; } } else if (curwin->w_p_spell && *curbuf->b_s.b_p_spl != NUL) { const char *str = tv_get_string_chk(&argvars[0]); @@ -15575,6 +15579,7 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) } assert(len <= INT_MAX); + tv_list_alloc_ret(rettv, 2); tv_list_append_string(rettv->vval.v_list, word, len); tv_list_append_string(rettv->vval.v_list, (attr == HLF_SPB ? "bad" @@ -15591,41 +15596,36 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) { bool typeerr = false; int maxcount; - garray_T ga; - listitem_T *li; + garray_T ga = GA_EMPTY_INIT_VALUE; bool need_capital = false; - tv_list_alloc_ret(rettv); - if (curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) { const char *const str = tv_get_string(&argvars[0]); if (argvars[1].v_type != VAR_UNKNOWN) { maxcount = tv_get_number_chk(&argvars[1], &typeerr); if (maxcount <= 0) { - return; + goto f_spellsuggest_return; } if (argvars[2].v_type != VAR_UNKNOWN) { need_capital = tv_get_number_chk(&argvars[2], &typeerr); if (typeerr) { - return; + goto f_spellsuggest_return; } } - } else + } else { maxcount = 25; + } spell_suggest_list(&ga, (char_u *)str, maxcount, need_capital, false); + } - for (int i = 0; i < ga.ga_len; i++) { - char *p = ((char **)ga.ga_data)[i]; - - li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_STRING; - li->li_tv.v_lock = 0; - li->li_tv.vval.v_string = (char_u *)p; - tv_list_append(rettv->vval.v_list, li); - } - ga_clear(&ga); +f_spellsuggest_return: + tv_list_alloc_ret(rettv, (ptrdiff_t)ga.ga_len); + for (int i = 0; i < ga.ga_len; i++) { + char *const p = ((char **)ga.ga_data)[i]; + tv_list_append_allocated_string(rettv->vval.v_list, p); } + ga_clear(&ga); } static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) @@ -15657,10 +15657,11 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) pat = "[\\x01- ]\\+"; } - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); - if (typeerr) + if (typeerr) { return; + } regmatch.regprog = vim_regcomp((char_u *)pat, RE_MAGIC + RE_STRING); if (regmatch.regprog != NULL) { @@ -15677,7 +15678,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else { end = str + strlen(str); } - if (keepempty || end > str || (rettv->vval.v_list->lv_len > 0 + if (keepempty || end > str || (tv_list_len(rettv->vval.v_list) > 0 && *str != NUL && match && end < (const char *)regmatch.endp[0])) { @@ -16223,7 +16224,7 @@ static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) break; } case 'n': { // name - p = get_highlight_name(NULL, id - 1); + p = get_highlight_name_ext(NULL, id - 1, false); break; } case 'r': { // reverse @@ -16287,7 +16288,6 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) memset(str, NUL, sizeof(str)); - tv_list_alloc_ret(rettv); if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count && col >= 0 && (size_t)col <= STRLEN(ml_get(lnum)) && curwin->w_p_cole > 0) { (void)syn_get_id(curwin, lnum, col, false, NULL, false); @@ -16296,18 +16296,16 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) // get the conceal character if ((syntax_flags & HL_CONCEAL) && curwin->w_p_cole < 3) { cchar = syn_get_sub_char(); - if (cchar == NUL && curwin->w_p_cole == 1 && lcs_conceal != NUL) { - cchar = lcs_conceal; + if (cchar == NUL && curwin->w_p_cole == 1) { + cchar = (lcs_conceal == NUL) ? ' ' : lcs_conceal; } if (cchar != NUL) { - if (has_mbyte) - (*mb_char2bytes)(cchar, str); - else - str[0] = cchar; + utf_char2bytes(cchar, str); } } } + tv_list_alloc_ret(rettv, 3); tv_list_append_number(rettv->vval.v_list, (syntax_flags & HL_CONCEAL) != 0); // -1 to auto-determine strlen tv_list_append_string(rettv->vval.v_list, (const char *)str, -1); @@ -16330,7 +16328,7 @@ static void f_synstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) && lnum <= curbuf->b_ml.ml_line_count && col >= 0 && (size_t)col <= STRLEN(ml_get(lnum))) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); (void)syn_get_id(curwin, lnum, col, false, NULL, true); int id; @@ -16346,7 +16344,7 @@ static list_T *string_to_list(const char *str, size_t len, const bool keepempty) if (!keepempty && str[len - 1] == NL) { len--; } - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc(kListLenMayKnow); encode_list_write(list, str, len); return list; } @@ -16392,7 +16390,7 @@ static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, if (res == NULL) { if (retlist) { // return an empty list when there's no output - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, 0); } else { rettv->vval.v_string = (char_u *) xstrdup(""); } @@ -16405,7 +16403,7 @@ static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, keepempty = tv_get_number(&argvars[2]); } rettv->vval.v_list = string_to_list(res, nread, (bool)keepempty); - rettv->vval.v_list->lv_refcount++; + tv_list_ref(rettv->vval.v_list); rettv->v_type = VAR_LIST; xfree(res); @@ -16458,7 +16456,7 @@ static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } if (wp != NULL) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); while (wp != NULL) { tv_list_append_number(rettv->vval.v_list, wp->w_buffer->b_fnum); wp = wp->w_next; @@ -16556,7 +16554,7 @@ static void f_tagfiles(typval_T *argvars, typval_T *rettv, FunPtr fptr) char *fname; tagname_T tn; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenUnknown); fname = xmalloc(MAXPATHL); bool first = true; @@ -16585,8 +16583,8 @@ static void f_taglist(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[1].v_type != VAR_UNKNOWN) { fname = tv_get_string(&argvars[1]); } - (void)get_tags(tv_list_alloc_ret(rettv), (char_u *)tag_pattern, - (char_u *)fname); + (void)get_tags(tv_list_alloc_ret(rettv, kListLenUnknown), + (char_u *)tag_pattern, (char_u *)fname); } /* @@ -16692,6 +16690,18 @@ static void f_test_garbagecollect_now(typval_T *argvars, garbage_collect(true); } +// "test_write_list_log()" function +static void f_test_write_list_log(typval_T *const argvars, + typval_T *const rettv, + FunPtr fptr) +{ + const char *const fname = tv_get_string_chk(&argvars[0]); + if (fname == NULL) { + return; + } + list_write_log(fname); +} + bool callback_from_typval(Callback *const callback, typval_T *const arg) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { @@ -16827,7 +16837,9 @@ static void add_timer_info_all(typval_T *rettv) /// "timer_info([timer])" function static void f_timer_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, (argvars[0].v_type != VAR_UNKNOWN + ? 1 + : timers->table->n_occupied)); if (argvars[0].v_type != VAR_UNKNOWN) { if (argvars[0].v_type != VAR_NUMBER) { EMSG(_(e_number_exp)); @@ -16852,6 +16864,12 @@ static void f_timer_pause(typval_T *argvars, typval_T *unused, FunPtr fptr) int paused = (bool)tv_get_number(&argvars[1]); timer_T *timer = pmap_get(uint64_t)(timers, tv_get_number(&argvars[0])); if (timer != NULL) { + if (!timer->paused && paused) { + time_watcher_stop(&timer->tw); + } else if (timer->paused && !paused) { + time_watcher_start(&timer->tw, timer_due_cb, timer->timeout, + timer->timeout); + } timer->paused = paused; } } @@ -17151,7 +17169,7 @@ static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr) break; } case VAR_UNKNOWN: { - EMSG2(_(e_intern2), "f_type(UNKNOWN)"); + internal_error("f_type(UNKNOWN)"); break; } } @@ -17187,7 +17205,6 @@ static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_dict_alloc_ret(rettv); dict_T *dict = rettv->vval.v_dict; - list_T *list; tv_dict_add_nr(dict, S_LEN("synced"), (varnumber_T)curbuf->b_u_synced); tv_dict_add_nr(dict, S_LEN("seq_last"), (varnumber_T)curbuf->b_u_seq_last); @@ -17197,9 +17214,7 @@ static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_dict_add_nr(dict, S_LEN("time_cur"), (varnumber_T)curbuf->b_u_time_cur); tv_dict_add_nr(dict, S_LEN("save_cur"), (varnumber_T)curbuf->b_u_save_nr_cur); - list = tv_list_alloc(); - u_eval_tree(curbuf->b_u_oldhead, list); - tv_dict_add_list(dict, S_LEN("entries"), list); + tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(curbuf->b_u_oldhead)); } /* @@ -17258,7 +17273,7 @@ static void f_wildmenumode(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "win_findbuf()" function static void f_win_findbuf(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); win_findbuf(argvars, rettv->vval.v_list); } @@ -17277,8 +17292,7 @@ static void f_win_gotoid(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "win_id2tabwin()" function static void f_win_id2tabwin(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tv_list_alloc_ret(rettv); - win_id2tabwin(argvars, rettv->vval.v_list); + win_id2tabwin(argvars, rettv); } /// "win_id2win()" function @@ -17449,10 +17463,11 @@ static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// @return true in case of success, false otherwise. static bool write_list(FileDescriptor *const fp, const list_T *const list, const bool binary) + FUNC_ATTR_NONNULL_ARG(1) { int error = 0; - for (const listitem_T *li = list->lv_first; li != NULL; li = li->li_next) { - const char *const s = tv_get_string_chk(&li->li_tv); + TV_LIST_ITER_CONST(list, li, { + const char *const s = tv_get_string_chk(TV_LIST_ITEM_TV(li)); if (s == NULL) { return false; } @@ -17479,14 +17494,14 @@ static bool write_list(FileDescriptor *const fp, const list_T *const list, } } } - if (!binary || li->li_next != NULL) { + if (!binary || TV_LIST_ITEM_NEXT(list, li) != NULL) { const ptrdiff_t written = file_write(fp, "\n", 1); if (written < 0) { error = (int)written; goto write_list_error; } } - } + }); if ((error = file_flush(fp)) != 0) { goto write_list_error; } @@ -17496,56 +17511,29 @@ write_list_error: return false; } -/// Initializes a static list with 10 items. -void init_static_list(staticList10_T *sl) -{ - list_T *l = &sl->sl_list; - - memset(sl, 0, sizeof(staticList10_T)); - l->lv_first = &sl->sl_items[0]; - l->lv_last = &sl->sl_items[9]; - l->lv_refcount = DO_NOT_FREE_CNT; - l->lv_lock = VAR_FIXED; - sl->sl_list.lv_len = 10; - - for (int i = 0; i < 10; i++) { - listitem_T *li = &sl->sl_items[i]; - - if (i == 0) { - li->li_prev = NULL; - } else { - li->li_prev = li - 1; - } - if (i == 9) { - li->li_next = NULL; - } else { - li->li_next = li + 1; - } - } -} - /// Saves a typval_T as a string. /// -/// For lists, replaces NLs with NUL and separates items with NLs. +/// For lists or buffers, replaces NLs with NUL and separates items with NLs. /// -/// @param[in] tv A value to store as a string. -/// @param[out] len The length of the resulting string or -1 on error. +/// @param[in] tv Value to store as a string. +/// @param[out] len Length of the resulting string or -1 on error. /// @param[in] endnl If true, the output will end in a newline (if a list). /// @returns an allocated string if `tv` represents a VimL string, list, or /// number; NULL otherwise. static char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { + *len = 0; if (tv->v_type == VAR_UNKNOWN) { - *len = 0; return NULL; } - // For types other than list, let tv_get_string_buf_chk() get the value or + // For other types, let tv_get_string_buf_chk() get the value or // print an error. - if (tv->v_type != VAR_LIST) { + if (tv->v_type != VAR_LIST && tv->v_type != VAR_NUMBER) { const char *ret = tv_get_string_chk(tv); - if (ret && (*len = strlen(ret))) { + if (ret) { + *len = strlen(ret); return xmemdupz(ret, (size_t)(*len)); } else { *len = -1; @@ -17553,12 +17541,44 @@ static char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) } } + if (tv->v_type == VAR_NUMBER) { // Treat number as a buffer-id. + buf_T *buf = buflist_findnr(tv->vval.v_number); + if (buf) { + for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { + for (char_u *p = ml_get_buf(buf, lnum, false); *p != NUL; p++) { + *len += 1; + } + *len += 1; + } + } else { + EMSGN(_(e_nobufnr), tv->vval.v_number); + *len = -1; + return NULL; + } + + if (*len == 0) { + return NULL; + } + + char *ret = xmalloc(*len + 1); + char *end = ret; + for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { + for (char_u *p = ml_get_buf(buf, lnum, false); *p != NUL; p++) { + *end++ = (*p == '\n') ? NUL : *p; + } + *end++ = '\n'; + } + *end = NUL; + *len = end - ret; + return ret; + } + + assert(tv->v_type == VAR_LIST); // Pre-calculate the resulting length. - *len = 0; list_T *list = tv->vval.v_list; - for (listitem_T *li = list->lv_first; li != NULL; li = li->li_next) { - *len += strlen(tv_get_string(&li->li_tv)) + 1; - } + TV_LIST_ITER_CONST(list, li, { + *len += strlen(tv_get_string(TV_LIST_ITEM_TV(li))) + 1; + }); if (*len == 0) { return NULL; @@ -17566,14 +17586,14 @@ static char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) char *ret = xmalloc(*len + endnl); char *end = ret; - for (listitem_T *li = list->lv_first; li != NULL; li = li->li_next) { - for (const char *s = tv_get_string(&li->li_tv); *s != NUL; s++) { + TV_LIST_ITER_CONST(list, li, { + for (const char *s = tv_get_string(TV_LIST_ITEM_TV(li)); *s != NUL; s++) { *end++ = (*s == '\n') ? NUL : *s; } - if (endnl || li->li_next != NULL) { + if (endnl || TV_LIST_ITEM_NEXT(list, li) != NULL) { *end++ = '\n'; } - } + }); *end = NUL; *len = end - ret; return ret; @@ -17613,9 +17633,6 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG2(_(e_listarg), "writefile()"); return; } - if (argvars[0].vval.v_list == NULL) { - return; - } bool binary = false; bool append = false; @@ -17720,9 +17737,9 @@ pos_T *var2fpos(const typval_T *const tv, const int dollar_lnum, // We accept "$" for the column number: last column. li = tv_list_find(l, 1L); - if (li != NULL && li->li_tv.v_type == VAR_STRING - && li->li_tv.vval.v_string != NULL - && STRCMP(li->li_tv.vval.v_string, "$") == 0) { + if (li != NULL && TV_LIST_ITEM_TV(li)->v_type == VAR_STRING + && TV_LIST_ITEM_TV(li)->vval.v_string != NULL + && STRCMP(TV_LIST_ITEM_TV(li)->vval.v_string, "$") == 0) { pos.col = len + 1; } @@ -17799,17 +17816,18 @@ pos_T *var2fpos(const typval_T *const tv, const int dollar_lnum, */ static int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp) { - list_T *l = arg->vval.v_list; + list_T *l; long i = 0; long n; - /* List must be: [fnum, lnum, col, coladd, curswant], where "fnum" is only - * there when "fnump" isn't NULL; "coladd" and "curswant" are optional. */ + // List must be: [fnum, lnum, col, coladd, curswant], where "fnum" is only + // there when "fnump" isn't NULL; "coladd" and "curswant" are optional. if (arg->v_type != VAR_LIST - || l == NULL - || l->lv_len < (fnump == NULL ? 2 : 3) - || l->lv_len > (fnump == NULL ? 4 : 5)) + || (l = arg->vval.v_list) == NULL + || tv_list_len(l) < (fnump == NULL ? 2 : 3) + || tv_list_len(l) > (fnump == NULL ? 4 : 5)) { return FAIL; + } if (fnump != NULL) { n = tv_list_find_nr(l, i++, NULL); // fnum @@ -18234,7 +18252,7 @@ void set_vim_var_list(const VimVarIndex idx, list_T *const val) vimvars[idx].vv_type = VAR_LIST; vimvars[idx].vv_list = val; if (val != NULL) { - val->lv_refcount++; + tv_list_ref(val); } } @@ -19030,7 +19048,7 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv, } return; } else if (v->di_tv.v_type != tv->v_type) { - EMSG2(_(e_intern2), "set_var()"); + internal_error("set_var()"); } } @@ -19050,6 +19068,9 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv, return; } + // Make sure dict is valid + assert(dict != NULL); + v = xmalloc(sizeof(dictitem_T) + strlen(varname)); STRCPY(v->di_key, varname); if (tv_dict_add(dict, v) == FAIL) { @@ -19267,12 +19288,12 @@ int var_item_copy(const vimconv_T *const conv, case VAR_LIST: to->v_type = VAR_LIST; to->v_lock = 0; - if (from->vval.v_list == NULL) + if (from->vval.v_list == NULL) { to->vval.v_list = NULL; - else if (copyID != 0 && from->vval.v_list->lv_copyID == copyID) { - /* use the copy made earlier */ - to->vval.v_list = from->vval.v_list->lv_copylist; - ++to->vval.v_list->lv_refcount; + } else if (copyID != 0 && tv_list_copyid(from->vval.v_list) == copyID) { + // Use the copy made earlier. + to->vval.v_list = tv_list_latest_copy(from->vval.v_list); + tv_list_ref(to->vval.v_list); } else { to->vval.v_list = tv_list_copy(conv, from->vval.v_list, deep, copyID); } @@ -19297,7 +19318,7 @@ int var_item_copy(const vimconv_T *const conv, } break; case VAR_UNKNOWN: - EMSG2(_(e_intern2), "var_item_copy(UNKNOWN)"); + internal_error("var_item_copy(UNKNOWN)"); ret = FAIL; } --recurse; @@ -19365,14 +19386,10 @@ void ex_echo(exarg_T *eap) } msg_putchar_attr((uint8_t)(*p), echo_attr); } else { - if (has_mbyte) { - int i = (*mb_ptr2len)((const char_u *)p); + int i = (*mb_ptr2len)((const char_u *)p); - (void)msg_outtrans_len_attr((char_u *)p, i, echo_attr); - p += i - 1; - } else { - (void)msg_outtrans_len_attr((char_u *)p, 1, echo_attr); - } + (void)msg_outtrans_len_attr((char_u *)p, i, echo_attr); + p += i - 1; } } } @@ -20871,7 +20888,9 @@ void ex_delfunction(exarg_T *eap) if (!eap->skip) { if (fp == NULL) { - EMSG2(_(e_nofunc), eap->arg); + if (!eap->forceit) { + EMSG2(_(e_nofunc), eap->arg); + } return; } if (fp->uf_calls > 0) { @@ -20985,11 +21004,11 @@ void func_unref(char_u *name) if (fp == NULL && isdigit(*name)) { #ifdef EXITFREE if (!entered_free_all_mem) { - EMSG2(_(e_intern2), "func_unref()"); + internal_error("func_unref()"); abort(); } #else - EMSG2(_(e_intern2), "func_unref()"); + internal_error("func_unref()"); abort(); #endif } @@ -21028,7 +21047,7 @@ void func_ref(char_u *name) } else if (isdigit(*name)) { // Only give an error for a numbered function. // Fail silently, when named or lambda function isn't found. - EMSG2(_(e_intern2), "func_ref()"); + internal_error("func_ref()"); } } @@ -21040,6 +21059,22 @@ void func_ptr_ref(ufunc_T *fp) } } +/// Check whether funccall is still referenced outside +/// +/// It is supposed to be referenced if either it is referenced itself or if l:, +/// a: or a:000 are referenced as all these are statically allocated within +/// funccall structure. +static inline bool fc_referenced(const funccall_T *const fc) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_NONNULL_ALL +{ + return ((fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated) + != DO_NOT_FREE_CNT) + || fc->l_vars.dv_refcount != DO_NOT_FREE_CNT + || fc->l_avars.dv_refcount != DO_NOT_FREE_CNT + || fc->fc_refcount > 0); +} + /// Call a user function /// /// @param fp Function to call. @@ -21153,9 +21188,8 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, v->di_tv.v_type = VAR_LIST; v->di_tv.v_lock = VAR_FIXED; v->di_tv.vval.v_list = &fc->l_varlist; - memset(&fc->l_varlist, 0, sizeof(list_T)); - fc->l_varlist.lv_refcount = DO_NOT_FREE_CNT; - fc->l_varlist.lv_lock = VAR_FIXED; + 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. @@ -21204,8 +21238,8 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, if (ai >= 0 && ai < MAX_FUNC_ARGS) { tv_list_append(&fc->l_varlist, &fc->l_listitems[ai]); - fc->l_listitems[ai].li_tv = argvars[i]; - fc->l_listitems[ai].li_tv.v_lock = VAR_FIXED; + *TV_LIST_ITEM_TV(&fc->l_listitems[ai]) = argvars[i]; + TV_LIST_ITEM_TV(&fc->l_listitems[ai])->v_lock = VAR_FIXED; } } @@ -21275,15 +21309,17 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, } } + const bool do_profiling_yes = do_profiling == PROF_YES; + bool func_not_yet_profiling_but_should = - do_profiling == PROF_YES - && !fp->uf_profiling && has_profiling(FALSE, fp->uf_name, NULL); + do_profiling_yes + && !fp->uf_profiling && has_profiling(false, fp->uf_name, NULL); if (func_not_yet_profiling_but_should) func_do_profile(fp); bool func_or_func_caller_profiling = - do_profiling == PROF_YES + do_profiling_yes && (fp->uf_profiling || (fc->caller != NULL && fc->caller->func->uf_profiling)); @@ -21293,7 +21329,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, fp->uf_tm_children = profile_zero(); } - if (do_profiling == PROF_YES) { + if (do_profiling_yes) { script_prof_save(&wait_start); } @@ -21369,8 +21405,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, sourcing_name = save_sourcing_name; sourcing_lnum = save_sourcing_lnum; current_SID = save_current_SID; - if (do_profiling == PROF_YES) + if (do_profiling_yes) { script_prof_restore(&wait_start); + } if (p_verbose >= 12 && sourcing_name != NULL) { ++no_wait_return; @@ -21389,10 +21426,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, // If the a:000 list and the l: and a: dicts are not referenced and there // is no closure using it, we can free the funccall_T and what's in it. - if (fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT - && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT - && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT - && fc->fc_refcount <= 0) { + if (!fc_referenced(fc)) { free_funccal(fc, false); } else { // "fc" is still in use. This can happen when returning "a:000", @@ -21407,10 +21441,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, }); // Make a copy of the a:000 items, since we didn't do that above. - for (listitem_T *li = fc->l_varlist.lv_first; li != NULL; - li = li->li_next) { - tv_copy(&li->li_tv, &li->li_tv); - } + TV_LIST_ITER(&fc->l_varlist, li, { + tv_copy(TV_LIST_ITEM_TV(li), TV_LIST_ITEM_TV(li)); + }); } if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) { @@ -21437,10 +21470,8 @@ static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force) return; } - if (--fc->fc_refcount <= 0 && (force || ( - fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT - && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT - && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT))) { + fc->fc_refcount--; + if (force ? fc->fc_refcount <= 0 : !fc_referenced(fc)) { for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) { if (fc == *pfc) { *pfc = fc->caller; @@ -21475,8 +21506,6 @@ free_funccal ( int free_val /* a: vars were allocated */ ) { - listitem_T *li; - for (int i = 0; i < fc->fc_funcs.ga_len; i++) { ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i]; @@ -21494,14 +21523,14 @@ free_funccal ( // allocated variables. vars_clear_ext(&fc->l_avars.dv_hashtab, free_val); - /* free all l: variables */ + // Free all l: variables. vars_clear(&fc->l_vars.dv_hashtab); // Free the a:000 variables if they were allocated. if (free_val) { - for (li = fc->l_varlist.lv_first; li != NULL; li = li->li_next) { - tv_clear(&li->li_tv); - } + TV_LIST_ITER(&fc->l_varlist, li, { + tv_clear(TV_LIST_ITEM_TV(li)); + }); } func_ptr_unref(fc->func); @@ -22407,7 +22436,7 @@ static void script_host_eval(char *name, typval_T *argvars, typval_T *rettv) return; } - list_T *args = tv_list_alloc(); + 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); } @@ -22425,7 +22454,6 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments) .sourcing_lnum = sourcing_lnum, .autocmd_fname = autocmd_fname, .autocmd_match = autocmd_match, - .autocmd_fname_full = autocmd_fname_full, .autocmd_bufnr = autocmd_bufnr, .funccalp = save_funccal() }; @@ -22436,8 +22464,8 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments) {.v_type = VAR_LIST, .vval.v_list = arguments, .v_lock = 0}, {.v_type = VAR_UNKNOWN} }; - typval_T rettv = {.v_type = VAR_UNKNOWN, .v_lock = 0}; - arguments->lv_refcount++; + typval_T rettv = { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED }; + tv_list_ref(arguments); int dummy; (void)call_func((const char_u *)func, @@ -22458,7 +22486,8 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments) restore_funccal(provider_caller_scope.funccalp); provider_caller_scope = saved_provider_caller_scope; provider_call_nesting--; - + assert(provider_call_nesting >= 0); + return rettv; } @@ -22498,11 +22527,13 @@ bool eval_has_provider(const char *name) } /// Writes "<sourcing_name>:<sourcing_lnum>" to `buf[bufsize]`. -void eval_format_source_name_line(char *buf, size_t bufsize) +void eval_fmt_source_name_line(char *buf, size_t bufsize) { - snprintf(buf, bufsize, "%s:%" PRIdLINENR, - (sourcing_name ? sourcing_name : (char_u *)"?"), - (sourcing_name ? sourcing_lnum : 0)); + if (sourcing_name) { + snprintf(buf, bufsize, "%s:%" PRIdLINENR, sourcing_name, sourcing_lnum); + } else { + snprintf(buf, bufsize, "?"); + } } /// ":checkhealth [plugins]" diff --git a/src/nvim/eval.h b/src/nvim/eval.h index 0c0a6881f6..b798eae187 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -86,7 +86,6 @@ typedef enum { VV_OLDFILES, VV_WINDOWID, VV_PROGPATH, - VV_COMMAND_OUTPUT, VV_COMPLETED_ITEM, VV_OPTION_NEW, VV_OPTION_OLD, diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 54cbc54d78..daa3b637a3 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -313,6 +313,7 @@ return { tempname={}, termopen={args={1, 2}}, test_garbagecollect_now={}, + test_write_list_log={args=1}, timer_info={args={0,1}}, timer_pause={args=2}, timer_start={args={2,3}}, diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index 0933b1bf9c..17799b500c 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -60,8 +60,8 @@ static inline void create_special_dict(typval_T *const rettv, dictitem_T *const type_di = tv_dict_item_alloc_len(S_LEN("_TYPE")); type_di->di_tv.v_type = VAR_LIST; type_di->di_tv.v_lock = VAR_UNLOCKED; - type_di->di_tv.vval.v_list = (list_T *) eval_msgpack_type_lists[type]; - type_di->di_tv.vval.v_list->lv_refcount++; + type_di->di_tv.vval.v_list = (list_T *)eval_msgpack_type_lists[type]; + tv_list_ref(type_di->di_tv.vval.v_list); tv_dict_add(dict, type_di); dictitem_T *const val_di = tv_dict_item_alloc_len(S_LEN("_VAL")); val_di->di_tv = val; @@ -120,16 +120,14 @@ static inline int json_decoder_pop(ValuesStackItem obj, last_container = kv_last(*container_stack); } if (last_container.container.v_type == VAR_LIST) { - if (last_container.container.vval.v_list->lv_len != 0 + if (tv_list_len(last_container.container.vval.v_list) != 0 && !obj.didcomma) { EMSG2(_("E474: Expected comma before list item: %s"), val_location); tv_clear(&obj.val); return FAIL; } assert(last_container.special_val == NULL); - listitem_T *obj_li = tv_list_item_alloc(); - obj_li->li_tv = obj.val; - tv_list_append(last_container.container.vval.v_list, obj_li); + tv_list_append_owned_tv(last_container.container.vval.v_list, obj.val); } else if (last_container.stack_index == kv_size(*stack) - 2) { if (!obj.didcolon) { EMSG2(_("E474: Expected colon before dictionary value: %s"), @@ -152,14 +150,10 @@ static inline int json_decoder_pop(ValuesStackItem obj, } obj_di->di_tv = obj.val; } else { - list_T *const kv_pair = tv_list_alloc(); + list_T *const kv_pair = tv_list_alloc(2); tv_list_append_list(last_container.special_val, kv_pair); - listitem_T *const key_li = tv_list_item_alloc(); - key_li->li_tv = key.val; - tv_list_append(kv_pair, key_li); - listitem_T *const val_li = tv_list_item_alloc(); - val_li->li_tv = obj.val; - tv_list_append(kv_pair, val_li); + tv_list_append_owned_tv(kv_pair, key.val); + tv_list_append_owned_tv(kv_pair, obj.val); } } else { // Object with key only @@ -227,14 +221,19 @@ static inline int json_decoder_pop(ValuesStackItem obj, /// Create a new special dictionary that ought to represent a MAP /// /// @param[out] ret_tv Address where new special dictionary is saved. +/// @param[in] len Expected number of items to be populated before list +/// becomes accessible from VimL. It is still valid to +/// underpopulate a list, value only controls how many elements +/// will be allocated in advance. @see ListLenSpecials. /// /// @return [allocated] list which should contain key-value pairs. Return value /// may be safely ignored. -list_T *decode_create_map_special_dict(typval_T *const ret_tv) +list_T *decode_create_map_special_dict(typval_T *const ret_tv, + const ptrdiff_t len) FUNC_ATTR_NONNULL_ALL { - list_T *const list = tv_list_alloc(); - list->lv_refcount++; + list_T *const list = tv_list_alloc(len); + tv_list_ref(list); create_special_dict(ret_tv, kMPMap, ((typval_T) { .v_type = VAR_LIST, .v_lock = VAR_UNLOCKED, @@ -269,8 +268,8 @@ typval_T decode_string(const char *const s, const size_t len, ? ((s != NULL) && (memchr(s, NUL, len) != NULL)) : (bool)hasnul); if (really_hasnul) { - list_T *const list = tv_list_alloc(); - list->lv_refcount++; + list_T *const list = tv_list_alloc(kListLenMayKnow); + tv_list_ref(list); typval_T tv; create_special_dict(&tv, binary ? kMPBinary : kMPString, ((typval_T) { .v_type = VAR_LIST, @@ -291,7 +290,7 @@ typval_T decode_string(const char *const s, const size_t len, .v_type = VAR_STRING, .v_lock = VAR_UNLOCKED, .vval = { .v_string = (char_u *)( - s_allocated ? (char *)s : xmemdupz(s, len)) }, + (s == NULL || s_allocated) ? (char *)s : xmemdupz(s, len)) }, }; } } @@ -738,8 +737,9 @@ json_decode_string_cycle_start: } else if (last_container.special_val == NULL ? (last_container.container.v_type == VAR_DICT ? (DICT_LEN(last_container.container.vval.v_dict) == 0) - : (last_container.container.vval.v_list->lv_len == 0)) - : (last_container.special_val->lv_len == 0)) { + : (tv_list_len(last_container.container.vval.v_list) + == 0)) + : (tv_list_len(last_container.special_val) == 0)) { emsgf(_("E474: Leading comma: %.*s"), LENP(p, e)); goto json_decode_string_fail; } @@ -848,8 +848,8 @@ json_decode_string_cycle_start: break; } case '[': { - list_T *list = tv_list_alloc(); - list->lv_refcount++; + list_T *list = tv_list_alloc(kListLenMayKnow); + tv_list_ref(list); typval_T tv = { .v_type = VAR_LIST, .v_lock = VAR_UNLOCKED, @@ -869,7 +869,7 @@ json_decode_string_cycle_start: list_T *val_list = NULL; if (next_map_special) { next_map_special = false; - val_list = decode_create_map_special_dict(&tv); + val_list = decode_create_map_special_dict(&tv, kListLenMayKnow); } else { dict_T *dict = tv_dict_alloc(); dict->dv_refcount++; @@ -969,8 +969,8 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) .vval = { .v_number = (varnumber_T) mobj.via.u64 }, }; } else { - list_T *const list = tv_list_alloc(); - list->lv_refcount++; + list_T *const list = tv_list_alloc(4); + tv_list_ref(list); create_special_dict(rettv, kMPInteger, ((typval_T) { .v_type = VAR_LIST, .v_lock = VAR_UNLOCKED, @@ -992,8 +992,8 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) .vval = { .v_number = (varnumber_T) mobj.via.i64 }, }; } else { - list_T *const list = tv_list_alloc(); - list->lv_refcount++; + list_T *const list = tv_list_alloc(4); + tv_list_ref(list); create_special_dict(rettv, kMPInteger, ((typval_T) { .v_type = VAR_LIST, .v_lock = VAR_UNLOCKED, @@ -1038,18 +1038,19 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) break; } case MSGPACK_OBJECT_ARRAY: { - list_T *const list = tv_list_alloc(); - list->lv_refcount++; + list_T *const list = tv_list_alloc((ptrdiff_t)mobj.via.array.size); + tv_list_ref(list); *rettv = (typval_T) { .v_type = VAR_LIST, .v_lock = VAR_UNLOCKED, .vval = { .v_list = list }, }; for (size_t i = 0; i < mobj.via.array.size; i++) { - listitem_T *const li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_UNKNOWN; - tv_list_append(list, li); - if (msgpack_to_vim(mobj.via.array.ptr[i], &li->li_tv) == FAIL) { + // Not populated yet, need to create list item to push. + tv_list_append_owned_tv(list, (typval_T) { .v_type = VAR_UNKNOWN }); + if (msgpack_to_vim(mobj.via.array.ptr[i], + TV_LIST_ITEM_TV(tv_list_last(list))) + == FAIL) { return FAIL; } } @@ -1089,30 +1090,33 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) } break; msgpack_to_vim_generic_map: {} - list_T *const list = decode_create_map_special_dict(rettv); + list_T *const list = decode_create_map_special_dict( + rettv, (ptrdiff_t)mobj.via.map.size); for (size_t i = 0; i < mobj.via.map.size; i++) { - list_T *const kv_pair = tv_list_alloc(); + list_T *const kv_pair = tv_list_alloc(2); tv_list_append_list(list, kv_pair); - listitem_T *const key_li = tv_list_item_alloc(); - key_li->li_tv.v_type = VAR_UNKNOWN; - tv_list_append(kv_pair, key_li); - listitem_T *const val_li = tv_list_item_alloc(); - val_li->li_tv.v_type = VAR_UNKNOWN; - tv_list_append(kv_pair, val_li); - if (msgpack_to_vim(mobj.via.map.ptr[i].key, &key_li->li_tv) == FAIL) { + + typval_T key_tv = { .v_type = VAR_UNKNOWN }; + if (msgpack_to_vim(mobj.via.map.ptr[i].key, &key_tv) == FAIL) { + tv_clear(&key_tv); return FAIL; } - if (msgpack_to_vim(mobj.via.map.ptr[i].val, &val_li->li_tv) == FAIL) { + tv_list_append_owned_tv(kv_pair, key_tv); + + typval_T val_tv = { .v_type = VAR_UNKNOWN }; + if (msgpack_to_vim(mobj.via.map.ptr[i].val, &val_tv) == FAIL) { + tv_clear(&val_tv); return FAIL; } + tv_list_append_owned_tv(kv_pair, val_tv); } break; } case MSGPACK_OBJECT_EXT: { - list_T *const list = tv_list_alloc(); - list->lv_refcount++; + list_T *const list = tv_list_alloc(2); + tv_list_ref(list); tv_list_append_number(list, mobj.via.ext.type); - list_T *const ext_val_list = tv_list_alloc(); + list_T *const ext_val_list = tv_list_alloc(kListLenMayKnow); tv_list_append_list(list, ext_val_list); create_special_dict(rettv, kMPExt, ((typval_T) { .v_type = VAR_LIST, diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index ef647b3ee4..9bae436e3d 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -28,6 +28,11 @@ #include "nvim/lib/kvec.h" #include "nvim/eval/typval_encode.h" +#ifdef __MINGW32__ +# undef fpclassify +# define fpclassify __fpclassify +#endif + #define ga_concat(a, b) ga_concat(a, (char_u *)b) #define utf_ptr2char(b) utf_ptr2char((char_u *)b) #define utf_ptr2len(b) ((size_t)utf_ptr2len((char_u *)b)) @@ -53,17 +58,18 @@ int encode_list_write(void *const data, const char *const buf, const size_t len) list_T *const list = (list_T *) data; const char *const end = buf + len; const char *line_end = buf; - listitem_T *li = list->lv_last; + listitem_T *li = tv_list_last(list); // Continue the last list element if (li != NULL) { line_end = xmemscan(buf, NL, len); if (line_end != buf) { const size_t line_length = (size_t)(line_end - buf); - char *str = (char *)li->li_tv.vval.v_string; + char *str = (char *)TV_LIST_ITEM_TV(li)->vval.v_string; const size_t li_len = (str == NULL ? 0 : strlen(str)); - li->li_tv.vval.v_string = xrealloc(str, li_len + line_length + 1); - str = (char *)li->li_tv.vval.v_string + li_len; + TV_LIST_ITEM_TV(li)->vval.v_string = xrealloc( + str, li_len + line_length + 1); + str = (char *)TV_LIST_ITEM_TV(li)->vval.v_string + li_len; memcpy(str, buf, line_length); str[line_length] = 0; memchrsub(str, NUL, NL, line_length); @@ -135,21 +141,27 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, } case kMPConvPairs: case kMPConvList: { - int idx = 0; - const listitem_T *li; - for (li = v.data.l.list->lv_first; - li != NULL && li->li_next != v.data.l.li; - li = li->li_next) { - idx++; - } + const int idx = (v.data.l.li == tv_list_first(v.data.l.list) + ? 0 + : (v.data.l.li == NULL + ? tv_list_len(v.data.l.list) - 1 + : (int)tv_list_idx_of_item( + v.data.l.list, + TV_LIST_ITEM_PREV(v.data.l.list, + v.data.l.li)))); + const listitem_T *const li = (v.data.l.li == NULL + ? tv_list_last(v.data.l.list) + : TV_LIST_ITEM_PREV(v.data.l.list, + v.data.l.li)); if (v.type == kMPConvList || li == NULL - || (li->li_tv.v_type != VAR_LIST - && li->li_tv.vval.v_list->lv_len <= 0)) { - vim_snprintf((char *) IObuff, IOSIZE, idx_msg, idx); + || (TV_LIST_ITEM_TV(li)->v_type != VAR_LIST + && tv_list_len(TV_LIST_ITEM_TV(li)->vval.v_list) <= 0)) { + vim_snprintf((char *)IObuff, IOSIZE, idx_msg, idx); ga_concat(&msg_ga, IObuff); } else { - typval_T key_tv = li->li_tv.vval.v_list->lv_first->li_tv; + typval_T key_tv = *TV_LIST_ITEM_TV( + tv_list_first(TV_LIST_ITEM_TV(li)->vval.v_list)); char *const key = encode_tv2echo(&key_tv, NULL); vim_snprintf((char *) IObuff, IOSIZE, key_pair_msg, key, idx); xfree(key); @@ -202,21 +214,17 @@ bool encode_vim_list_to_buf(const list_T *const list, size_t *const ret_len, FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT { size_t len = 0; - if (list != NULL) { - for (const listitem_T *li = list->lv_first; - li != NULL; - li = li->li_next) { - if (li->li_tv.v_type != VAR_STRING) { - return false; - } - len++; - if (li->li_tv.vval.v_string != 0) { - len += STRLEN(li->li_tv.vval.v_string); - } + TV_LIST_ITER_CONST(list, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING) { + return false; } - if (len) { - len--; + len++; + if (TV_LIST_ITEM_TV(li)->vval.v_string != NULL) { + len += STRLEN(TV_LIST_ITEM_TV(li)->vval.v_string); } + }); + if (len) { + len--; } *ret_len = len; if (len == 0) { @@ -253,31 +261,34 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, char *const buf_end = buf + nbuf; char *p = buf; while (p < buf_end) { - assert(state->li_length == 0 || state->li->li_tv.vval.v_string != NULL); + assert(state->li_length == 0 + || TV_LIST_ITEM_TV(state->li)->vval.v_string != NULL); for (size_t i = state->offset; i < state->li_length && p < buf_end; i++) { - assert(state->li->li_tv.vval.v_string != NULL); - const char ch = (char)state->li->li_tv.vval.v_string[state->offset++]; + assert(TV_LIST_ITEM_TV(state->li)->vval.v_string != NULL); + const char ch = (char)( + TV_LIST_ITEM_TV(state->li)->vval.v_string[state->offset++]); *p++ = (char)((char)ch == (char)NL ? (char)NUL : (char)ch); } if (p < buf_end) { - state->li = state->li->li_next; + state->li = TV_LIST_ITEM_NEXT(state->list, state->li); if (state->li == NULL) { *read_bytes = (size_t) (p - buf); return OK; } *p++ = NL; - if (state->li->li_tv.v_type != VAR_STRING) { - *read_bytes = (size_t) (p - buf); + if (TV_LIST_ITEM_TV(state->li)->v_type != VAR_STRING) { + *read_bytes = (size_t)(p - buf); return FAIL; } state->offset = 0; - state->li_length = (state->li->li_tv.vval.v_string == NULL + state->li_length = (TV_LIST_ITEM_TV(state->li)->vval.v_string == NULL ? 0 - : STRLEN(state->li->li_tv.vval.v_string)); + : STRLEN(TV_LIST_ITEM_TV(state->li)->vval.v_string)); } } *read_bytes = nbuf; - return (state->offset < state->li_length || state->li->li_next != NULL + return ((state->offset < state->li_length + || TV_LIST_ITEM_NEXT(state->list, state->li) != NULL) ? NOTDONE : OK); } @@ -340,7 +351,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, do { \ const char *const fun_ = (const char *)(fun); \ if (fun_ == NULL) { \ - EMSG2(_(e_intern2), "string(): NULL function name"); \ + internal_error("string(): NULL function name"); \ ga_concat(gap, "function(NULL"); \ } else { \ ga_concat(gap, "function("); \ @@ -727,12 +738,11 @@ bool encode_check_json_key(const typval_T *const tv) if (val_di->di_tv.vval.v_list == NULL) { return true; } - for (const listitem_T *li = val_di->di_tv.vval.v_list->lv_first; - li != NULL; li = li->li_next) { - if (li->li_tv.v_type != VAR_STRING) { + TV_LIST_ITER_CONST(val_di->di_tv.vval.v_list, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING) { return false; } - } + }); return true; } diff --git a/src/nvim/eval/encode.h b/src/nvim/eval/encode.h index 9bc665253b..ccea245ab3 100644 --- a/src/nvim/eval/encode.h +++ b/src/nvim/eval/encode.h @@ -33,9 +33,10 @@ int encode_vim_to_echo(garray_T *const packer, /// Structure defining state for read_from_list() typedef struct { + const list_T *const list; ///< List being currently read. const listitem_T *li; ///< Item currently read. - size_t offset; ///< Byte offset inside the read item. - size_t li_length; ///< Length of the string inside the read item. + size_t offset; ///< Byte offset inside the read item. + size_t li_length; ///< Length of the string inside the read item. } ListReaderState; /// Initialize ListReaderState structure @@ -43,11 +44,13 @@ static inline ListReaderState encode_init_lrstate(const list_T *const list) FUNC_ATTR_NONNULL_ALL { return (ListReaderState) { - .li = list->lv_first, + .list = list, + .li = tv_list_first(list), .offset = 0, - .li_length = (list->lv_first->li_tv.vval.v_string == NULL + .li_length = (TV_LIST_ITEM_TV(tv_list_first(list))->vval.v_string == NULL ? 0 - : STRLEN(list->lv_first->li_tv.vval.v_string)), + : STRLEN(TV_LIST_ITEM_TV( + tv_list_first(list))->vval.v_string)), }; } diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 4bc3a85efb..c8b550f902 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -3,6 +3,7 @@ #include <stdio.h> #include <stddef.h> +#include <stdlib.h> #include <string.h> #include <assert.h> #include <stdbool.h> @@ -30,6 +31,7 @@ #include "nvim/message.h" // TODO(ZyX-I): Move line_breakcheck out of misc1 #include "nvim/misc1.h" // For line_breakcheck +#include "nvim/os/fileio.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/typval.c.generated.h" @@ -44,6 +46,70 @@ bool tv_in_free_unref_items = false; const char *const tv_empty_string = ""; //{{{1 Lists +//{{{2 List log +#ifdef LOG_LIST_ACTIONS +ListLog *list_log_first = NULL; +ListLog *list_log_last = NULL; + +/// Write list log to the given file +/// +/// @param[in] fname File to write log to. Will be appended to if already +/// present. +void list_write_log(const char *const fname) + FUNC_ATTR_NONNULL_ALL +{ + FileDescriptor fp; + const int fo_ret = file_open(&fp, fname, kFileCreate|kFileAppend, 0600); + if (fo_ret != 0) { + emsgf(_("E5142: Failed to open file %s: %s"), fname, os_strerror(fo_ret)); + return; + } + for (ListLog *chunk = list_log_first; chunk != NULL;) { + for (size_t i = 0; i < chunk->size; i++) { + char buf[10 + 1 + ((16 + 3) * 3) + (8 + 2) + 2]; + // act : hex " c:" len "[]" "\n\0" + const ListLogEntry entry = chunk->entries[i]; + const size_t snp_len = (size_t)snprintf( + buf, sizeof(buf), + "%-10.10s: l:%016" PRIxPTR "[%08d] 1:%016" PRIxPTR " 2:%016" PRIxPTR + "\n", + entry.action, entry.l, entry.len, entry.li1, entry.li2); + assert(snp_len + 1 == sizeof(buf)); + const ptrdiff_t fw_ret = file_write(&fp, buf, snp_len); + if (fw_ret != (ptrdiff_t)snp_len) { + assert(fw_ret < 0); + if (i) { + memmove(chunk->entries, chunk->entries + i, + sizeof(chunk->entries[0]) * (chunk->size - i)); + chunk->size -= i; + } + emsgf(_("E5143: Failed to write to file %s: %s"), + fname, os_strerror((int)fw_ret)); + return; + } + } + list_log_first = chunk->next; + xfree(chunk); + chunk = list_log_first; + } + const int fc_ret = file_close(&fp, true); + if (fc_ret != 0) { + emsgf(_("E5144: Failed to close file %s: %s"), fname, os_strerror(fc_ret)); + } +} + +#ifdef EXITFREE +/// Free list log +void list_free_log(void) +{ + for (ListLog *chunk = list_log_first; chunk != NULL;) { + list_log_first = chunk->next; + xfree(chunk); + chunk = list_log_first; + } +} +#endif +#endif //{{{2 List item /// Allocate a list item @@ -52,35 +118,29 @@ const char *const tv_empty_string = ""; /// and specifically set lv_lock. /// /// @return [allocated] new list item. -listitem_T *tv_list_item_alloc(void) +static listitem_T *tv_list_item_alloc(void) FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC { return xmalloc(sizeof(listitem_T)); } -/// Free a list item -/// -/// Also clears the value. Does not touch watchers. -/// -/// @param[out] item Item to free. -void tv_list_item_free(listitem_T *const item) - FUNC_ATTR_NONNULL_ALL -{ - tv_clear(&item->li_tv); - xfree(item); -} - /// Remove a list item from a List and free it /// /// Also clears the value. /// /// @param[out] l List to remove item from. /// @param[in,out] item Item to remove. -void tv_list_item_remove(list_T *const l, listitem_T *const item) +/// +/// @return Pointer to the list item just after removed one, NULL if removed +/// item was the last one. +listitem_T *tv_list_item_remove(list_T *const l, listitem_T *const item) FUNC_ATTR_NONNULL_ALL { - tv_list_remove_items(l, item, item); - tv_list_item_free(item); + listitem_T *const next_item = TV_LIST_ITEM_NEXT(l, item); + tv_list_drop_items(l, item, item); + tv_clear(TV_LIST_ITEM_TV(item)); + xfree(item); + return next_item; } //{{{2 List watchers @@ -137,8 +197,14 @@ void tv_list_watch_fix(list_T *const l, const listitem_T *const item) /// /// Caller should take care of the reference count. /// +/// @param[in] len Expected number of items to be populated before list +/// becomes accessible from VimL. It is still valid to +/// underpopulate a list, value only controls how many elements +/// will be allocated in advance. Currently does nothing. +/// @see ListLenSpecials. +/// /// @return [allocated] new list. -list_T *tv_list_alloc(void) +list_T *tv_list_alloc(const ptrdiff_t len) FUNC_ATTR_NONNULL_RET { list_T *const list = xcalloc(1, sizeof(list_T)); @@ -150,15 +216,59 @@ list_T *tv_list_alloc(void) list->lv_used_prev = NULL; list->lv_used_next = gc_first_list; gc_first_list = list; + list_log(list, NULL, (void *)(uintptr_t)len, "alloc"); return list; } +/// Initialize a static list with 10 items +/// +/// @param[out] sl Static list to initialize. +void tv_list_init_static10(staticList10_T *const sl) + FUNC_ATTR_NONNULL_ALL +{ +#define SL_SIZE ARRAY_SIZE(sl->sl_items) + list_T *const l = &sl->sl_list; + + memset(sl, 0, sizeof(staticList10_T)); + l->lv_first = &sl->sl_items[0]; + l->lv_last = &sl->sl_items[SL_SIZE - 1]; + l->lv_refcount = DO_NOT_FREE_CNT; + tv_list_set_lock(l, VAR_FIXED); + sl->sl_list.lv_len = 10; + + sl->sl_items[0].li_prev = NULL; + sl->sl_items[0].li_next = &sl->sl_items[1]; + sl->sl_items[SL_SIZE - 1].li_prev = &sl->sl_items[SL_SIZE - 2]; + sl->sl_items[SL_SIZE - 1].li_next = NULL; + + for (size_t i = 1; i < SL_SIZE - 1; i++) { + listitem_T *const li = &sl->sl_items[i]; + li->li_prev = li - 1; + li->li_next = li + 1; + } + list_log((const list_T *)sl, &sl->sl_items[0], &sl->sl_items[SL_SIZE - 1], + "s10init"); +#undef SL_SIZE +} + +/// Initialize static list with undefined number of elements +/// +/// @param[out] l List to initialize. +void tv_list_init_static(list_T *const l) + FUNC_ATTR_NONNULL_ALL +{ + memset(l, 0, sizeof(*l)); + l->lv_refcount = DO_NOT_FREE_CNT; + list_log(l, NULL, NULL, "sinit"); +} + /// Free items contained in a list /// /// @param[in,out] l List to clear. void tv_list_free_contents(list_T *const l) FUNC_ATTR_NONNULL_ALL { + list_log(l, NULL, NULL, "freecont"); for (listitem_T *item = l->lv_first; item != NULL; item = l->lv_first) { // Remove the item before deleting it. l->lv_first = item->li_next; @@ -188,6 +298,7 @@ void tv_list_free_list(list_T *const l) if (l->lv_used_next != NULL) { l->lv_used_next->lv_used_prev = l->lv_used_prev; } + list_log(l, NULL, NULL, "freelist"); xfree(l); } @@ -221,17 +332,18 @@ void tv_list_unref(list_T *const l) //{{{2 Add/remove -/// Remove items "item" to "item2" from list "l". +/// Remove items "item" to "item2" from list "l" /// /// @warning Does not free the listitem or the value! /// /// @param[out] l List to remove from. /// @param[in] item First item to remove. /// @param[in] item2 Last item to remove. -void tv_list_remove_items(list_T *const l, listitem_T *const item, - listitem_T *const item2) +void tv_list_drop_items(list_T *const l, listitem_T *const item, + listitem_T *const item2) FUNC_ATTR_NONNULL_ALL { + list_log(l, item, item2, "drop"); // Notify watchers. for (listitem_T *ip = item; ip != item2->li_next; ip = ip->li_next) { l->lv_len--; @@ -249,6 +361,51 @@ void tv_list_remove_items(list_T *const l, listitem_T *const item, item->li_prev->li_next = item2->li_next; } l->lv_idx_item = NULL; + list_log(l, l->lv_first, l->lv_last, "afterdrop"); +} + +/// Like tv_list_drop_items, but also frees all removed items +void tv_list_remove_items(list_T *const l, listitem_T *const item, + listitem_T *const item2) + FUNC_ATTR_NONNULL_ALL +{ + list_log(l, item, item2, "remove"); + tv_list_drop_items(l, item, item2); + for (listitem_T *li = item;;) { + tv_clear(TV_LIST_ITEM_TV(li)); + listitem_T *const nli = li->li_next; + xfree(li); + if (li == item2) { + break; + } + li = nli; + } +} + +/// Move items "item" to "item2" from list "l" to the end of the list "tgt_l" +/// +/// @param[out] l List to move from. +/// @param[in] item First item to move. +/// @param[in] item2 Last item to move. +/// @param[out] tgt_l List to move to. +/// @param[in] cnt Number of items moved. +void tv_list_move_items(list_T *const l, listitem_T *const item, + listitem_T *const item2, list_T *const tgt_l, + const int cnt) + FUNC_ATTR_NONNULL_ALL +{ + list_log(l, item, item2, "move"); + tv_list_drop_items(l, item, item2); + item->li_prev = tgt_l->lv_last; + item2->li_next = NULL; + if (tgt_l->lv_last == NULL) { + tgt_l->lv_first = item; + } else { + tgt_l->lv_last->li_next = item; + } + tgt_l->lv_last = item2; + tgt_l->lv_len += cnt; + list_log(tgt_l, tgt_l->lv_first, tgt_l->lv_last, "movetgt"); } /// Insert list item @@ -277,6 +434,7 @@ void tv_list_insert(list_T *const l, listitem_T *const ni, } item->li_prev = ni; l->lv_len++; + list_log(l, ni, item, "insert"); } } @@ -303,6 +461,7 @@ void tv_list_insert_tv(list_T *const l, typval_T *const tv, void tv_list_append(list_T *const l, listitem_T *const item) FUNC_ATTR_NONNULL_ALL { + list_log(l, item, NULL, "append"); if (l->lv_last == NULL) { // empty list l->lv_first = item; @@ -326,7 +485,19 @@ void tv_list_append_tv(list_T *const l, typval_T *const tv) FUNC_ATTR_NONNULL_ALL { listitem_T *const li = tv_list_item_alloc(); - tv_copy(tv, &li->li_tv); + tv_copy(tv, TV_LIST_ITEM_TV(li)); + tv_list_append(l, li); +} + +/// Like tv_list_append_tv(), but tv is moved to a list +/// +/// This means that it is no longer valid to use contents of the typval_T after +/// function exits. +void tv_list_append_owned_tv(list_T *const l, typval_T tv) + FUNC_ATTR_NONNULL_ALL +{ + listitem_T *const li = tv_list_item_alloc(); + *TV_LIST_ITEM_TV(li) = tv; tv_list_append(l, li); } @@ -334,33 +505,29 @@ void tv_list_append_tv(list_T *const l, typval_T *const tv) /// /// @param[out] l List to append to. /// @param[in,out] itemlist List to append. Reference count is increased. -void tv_list_append_list(list_T *const list, list_T *const itemlist) +void tv_list_append_list(list_T *const l, list_T *const itemlist) FUNC_ATTR_NONNULL_ARG(1) { - listitem_T *const li = tv_list_item_alloc(); - - li->li_tv.v_type = VAR_LIST; - li->li_tv.v_lock = VAR_UNLOCKED; - li->li_tv.vval.v_list = itemlist; - tv_list_append(list, li); - if (itemlist != NULL) { - itemlist->lv_refcount++; - } + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_LIST, + .v_lock = VAR_UNLOCKED, + .vval.v_list = itemlist, + }); + tv_list_ref(itemlist); } /// Append a dictionary to a list /// /// @param[out] l List to append to. /// @param[in,out] dict Dictionary to append. Reference count is increased. -void tv_list_append_dict(list_T *const list, dict_T *const dict) +void tv_list_append_dict(list_T *const l, dict_T *const dict) FUNC_ATTR_NONNULL_ARG(1) { - listitem_T *const li = tv_list_item_alloc(); - - li->li_tv.v_type = VAR_DICT; - li->li_tv.v_lock = VAR_UNLOCKED; - li->li_tv.vval.v_dict = dict; - tv_list_append(list, li); + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_DICT, + .v_lock = VAR_UNLOCKED, + .vval.v_dict = dict, + }); if (dict != NULL) { dict->dv_refcount++; } @@ -377,14 +544,15 @@ void tv_list_append_string(list_T *const l, const char *const str, const ssize_t len) FUNC_ATTR_NONNULL_ARG(1) { - if (str == NULL) { - assert(len == 0 || len == -1); - tv_list_append_allocated_string(l, NULL); - } else { - tv_list_append_allocated_string(l, (len >= 0 - ? xmemdupz(str, (size_t)len) - : xstrdup(str))); - } + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = (str == NULL + ? NULL + : (len >= 0 + ? xmemdupz(str, (size_t)len) + : xstrdup(str))), + }); } /// Append given string to the list @@ -396,12 +564,11 @@ void tv_list_append_string(list_T *const l, const char *const str, void tv_list_append_allocated_string(list_T *const l, char *const str) FUNC_ATTR_NONNULL_ARG(1) { - listitem_T *const li = tv_list_item_alloc(); - - tv_list_append(l, li); - li->li_tv.v_type = VAR_STRING; - li->li_tv.v_lock = VAR_UNLOCKED; - li->li_tv.vval.v_string = (char_u *)str; + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = (char_u *)str, + }); } /// Append number to the list @@ -411,11 +578,11 @@ void tv_list_append_allocated_string(list_T *const l, char *const str) /// listitem_T. void tv_list_append_number(list_T *const l, const varnumber_T n) { - listitem_T *const li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_NUMBER; - li->li_tv.v_lock = VAR_UNLOCKED; - li->li_tv.vval.v_number = n; - tv_list_append(l, li); + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_NUMBER, + .v_lock = VAR_UNLOCKED, + .vval.v_number = n, + }); } //{{{2 Operations on the whole list @@ -438,34 +605,36 @@ list_T *tv_list_copy(const vimconv_T *const conv, list_T *const orig, return NULL; } - list_T *copy = tv_list_alloc(); + list_T *copy = tv_list_alloc(tv_list_len(orig)); + tv_list_ref(copy); if (copyID != 0) { // Do this before adding the items, because one of the items may // refer back to this list. orig->lv_copyID = copyID; orig->lv_copylist = copy; } - listitem_T *item; - for (item = orig->lv_first; item != NULL && !got_int; - item = item->li_next) { + TV_LIST_ITER(orig, item, { + if (got_int) { + break; + } listitem_T *const ni = tv_list_item_alloc(); if (deep) { - if (var_item_copy(conv, &item->li_tv, &ni->li_tv, deep, copyID) == FAIL) { + if (var_item_copy(conv, TV_LIST_ITEM_TV(item), TV_LIST_ITEM_TV(ni), + deep, copyID) == FAIL) { xfree(ni); - break; + goto tv_list_copy_error; } } else { - tv_copy(&item->li_tv, &ni->li_tv); + tv_copy(TV_LIST_ITEM_TV(item), TV_LIST_ITEM_TV(ni)); } tv_list_append(copy, ni); - } - copy->lv_refcount++; - if (item != NULL) { - tv_list_unref(copy); - copy = NULL; - } + }); return copy; + +tv_list_copy_error: + tv_list_unref(copy); + return NULL; } /// Extend first list with the second @@ -475,17 +644,17 @@ list_T *tv_list_copy(const vimconv_T *const conv, list_T *const orig, /// @param[in] bef If not NULL, extends before this item. void tv_list_extend(list_T *const l1, list_T *const l2, listitem_T *const bef) - FUNC_ATTR_NONNULL_ARG(1, 2) + FUNC_ATTR_NONNULL_ARG(1) { - int todo = l2->lv_len; + int todo = tv_list_len(l2); listitem_T *const befbef = (bef == NULL ? NULL : bef->li_prev); listitem_T *const saved_next = (befbef == NULL ? NULL : befbef->li_next); // We also quit the loop when we have inserted the original item count of // the list, avoid a hang when we extend a list with itself. - for (listitem_T *item = l2->lv_first - ; item != NULL && --todo >= 0 + for (listitem_T *item = tv_list_first(l2) + ; item != NULL && todo-- ; item = (item == befbef ? saved_next : item->li_next)) { - tv_list_insert_tv(l1, &item->li_tv, bef); + tv_list_insert_tv(l1, TV_LIST_ITEM_TV(item), bef); } } @@ -540,13 +709,15 @@ static int list_join_inner(garray_T *const gap, list_T *const l, { size_t sumlen = 0; bool first = true; - listitem_T *item; // Stringify each item in the list. - for (item = l->lv_first; item != NULL && !got_int; item = item->li_next) { + TV_LIST_ITER(l, item, { + if (got_int) { + break; + } char *s; size_t len; - s = encode_tv2echo(&item->li_tv, &len); + s = encode_tv2echo(TV_LIST_ITEM_TV(item), &len); if (s == NULL) { return FAIL; } @@ -557,7 +728,7 @@ static int list_join_inner(garray_T *const gap, list_T *const l, p->tofree = p->s = (char_u *)s; line_breakcheck(); - } + }); // Allocate result buffer with its total size, avoid re-allocation and // multiple copy operations. Add 2 for a tailing ']' and NUL. @@ -591,16 +762,16 @@ static int list_join_inner(garray_T *const gap, list_T *const l, /// /// @return OK in case of success, FAIL otherwise. int tv_list_join(garray_T *const gap, list_T *const l, const char *const sep) - FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_NONNULL_ARG(1) { - if (l->lv_len < 1) { + if (!tv_list_len(l)) { return OK; } garray_T join_ga; int retval; - ga_init(&join_ga, (int)sizeof(Join), l->lv_len); + ga_init(&join_ga, (int)sizeof(Join), tv_list_len(l)); retval = list_join_inner(gap, l, sep, &join_ga); #define FREE_JOIN_TOFREE(join) xfree((join)->tofree) @@ -632,11 +803,13 @@ bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic, return false; } - listitem_T *item1 = l1->lv_first; - listitem_T *item2 = l2->lv_first; + listitem_T *item1 = tv_list_first(l1); + listitem_T *item2 = tv_list_first(l2); for (; item1 != NULL && item2 != NULL - ; item1 = item1->li_next, item2 = item2->li_next) { - if (!tv_equal(&item1->li_tv, &item2->li_tv, ic, recursive)) { + ; (item1 = TV_LIST_ITEM_NEXT(l1, item1), + item2 = TV_LIST_ITEM_NEXT(l2, item2))) { + if (!tv_equal(TV_LIST_ITEM_TV(item1), TV_LIST_ITEM_TV(item2), ic, + recursive)) { return false; } } @@ -644,6 +817,74 @@ bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic, return true; } +/// Reverse list in-place +/// +/// @param[in,out] l List to reverse. +void tv_list_reverse(list_T *const l) +{ + if (tv_list_len(l) <= 1) { + return; + } + list_log(l, NULL, NULL, "reverse"); +#define SWAP(a, b) \ + do { \ + tmp = a; \ + a = b; \ + b = tmp; \ + } while (0) + listitem_T *tmp; + + SWAP(l->lv_first, l->lv_last); + for (listitem_T *li = l->lv_first; li != NULL; li = li->li_next) { + SWAP(li->li_next, li->li_prev); + } +#undef SWAP + + l->lv_idx = l->lv_len - l->lv_idx - 1; +} + +// FIXME Add unit tests for tv_list_item_sort(). + +/// Sort list using libc qsort +/// +/// @param[in,out] l List to sort, will be sorted in-place. +/// @param ptrs Preallocated array of items to sort, must have at least +/// tv_list_len(l) entries. Should not be initialized. +/// @param[in] item_compare_func Function used to compare list items. +/// @param errp Location where information about whether error occurred is +/// saved by item_compare_func. If boolean there appears to be +/// true list will not be modified. Must be initialized to false +/// by the caller. +void tv_list_item_sort(list_T *const l, ListSortItem *const ptrs, + const ListSorter item_compare_func, + bool *errp) + FUNC_ATTR_NONNULL_ARG(3, 4) +{ + const int len = tv_list_len(l); + if (len <= 1) { + return; + } + list_log(l, NULL, NULL, "sort"); + int i = 0; + TV_LIST_ITER(l, li, { + ptrs[i].item = li; + ptrs[i].idx = i; + i++; + }); + // Sort the array with item pointers. + qsort(ptrs, (size_t)len, sizeof(ListSortItem), item_compare_func); + if (!(*errp)) { + // Clear the list and append the items in the sorted order. + l->lv_first = NULL; + l->lv_last = NULL; + l->lv_idx_item = NULL; + l->lv_len = 0; + for (i = 0; i < len; i++) { + tv_list_append(l, ptrs[i].item); + } + } +} + //{{{2 Indexing/searching /// Locate item with a given index in a list and return it @@ -662,13 +903,8 @@ listitem_T *tv_list_find(list_T *const l, int n) return NULL; } - // Negative index is relative to the end. - if (n < 0) { - n = l->lv_len + n; - } - - // Check for index out of range. - if (n < 0 || n >= l->lv_len) { + n = tv_list_uidx(l, n); + if (n == -1) { return NULL; } @@ -717,6 +953,7 @@ listitem_T *tv_list_find(list_T *const l, int n) // Cache the used index. l->lv_idx = idx; l->lv_idx_item = item; + list_log(l, l->lv_idx_item, (void *)(uintptr_t)l->lv_idx, "find"); return item; } @@ -740,7 +977,7 @@ varnumber_T tv_list_find_nr(list_T *const l, const int n, bool *const ret_error) } return -1; } - return tv_get_number_chk(&li->li_tv, ret_error); + return tv_get_number_chk(TV_LIST_ITEM_TV(li), ret_error); } /// Get list item l[n] as a string @@ -757,7 +994,7 @@ const char *tv_list_find_str(list_T *const l, const int n) emsgf(_(e_listidx), (int64_t)n); return NULL; } - return tv_get_string(&li->li_tv); + return tv_get_string(TV_LIST_ITEM_TV(li)); } /// Locate item in a list and return its index @@ -772,15 +1009,14 @@ long tv_list_idx_of_item(const list_T *const l, const listitem_T *const item) if (l == NULL) { return -1; } - long idx = 0; - const listitem_T *li; - for (li = l->lv_first; li != NULL && li != item; li = li->li_next) { + int idx = 0; + TV_LIST_ITER_CONST(l, li, { + if (li == item) { + return idx; + } idx++; - } - if (li == NULL) { - return -1; - } - return idx; + }); + return -1; } //{{{1 Dictionaries @@ -1339,7 +1575,7 @@ int tv_dict_add_list(dict_T *const d, const char *const key, item->di_tv.v_lock = VAR_UNLOCKED; item->di_tv.v_type = VAR_LIST; item->di_tv.vval.v_list = list; - list->lv_refcount++; + tv_list_ref(list); if (tv_dict_add(d, item) == FAIL) { tv_dict_item_free(item); return FAIL; @@ -1668,16 +1904,20 @@ void tv_dict_set_keys_readonly(dict_T *const dict) /// Also sets reference count. /// /// @param[out] ret_tv Structure where list is saved. +/// @param[in] len Expected number of items to be populated before list +/// becomes accessible from VimL. It is still valid to +/// underpopulate a list, value only controls how many elements +/// will be allocated in advance. @see ListLenSpecials. /// /// @return [allocated] pointer to the created list. -list_T *tv_list_alloc_ret(typval_T *const ret_tv) +list_T *tv_list_alloc_ret(typval_T *const ret_tv, const ptrdiff_t len) FUNC_ATTR_NONNULL_ALL { - list_T *const l = tv_list_alloc(); + list_T *const l = tv_list_alloc(len); ret_tv->vval.v_list = l; ret_tv->v_type = VAR_LIST; ret_tv->v_lock = VAR_UNLOCKED; - l->lv_refcount++; + tv_list_ref(l); return l; } @@ -1794,14 +2034,20 @@ static inline void _nothing_conv_func_end(typval_T *const tv, const int copyID) tv->v_lock = VAR_UNLOCKED; \ } while (0) +static inline void _nothing_conv_empty_dict(typval_T *const tv, + dict_T **const dictp) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ARG(2) +{ + tv_dict_unref(*dictp); + *dictp = NULL; + if (tv != NULL) { + tv->v_lock = VAR_UNLOCKED; + } +} #define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ do { \ assert((void *)&dict != (void *)&TYPVAL_ENCODE_NODICT_VAR); \ - tv_dict_unref((dict_T *)dict); \ - *((dict_T **)&dict) = NULL; \ - if (tv != NULL) { \ - ((typval_T *)tv)->v_lock = VAR_UNLOCKED; \ - } \ + _nothing_conv_empty_dict(tv, ((dict_T **)&dict)); \ } while (0) static inline int _nothing_conv_real_list_after_start( @@ -1998,7 +2244,7 @@ void tv_free(typval_T *tv) /// /// @param[in] from Location to copy from. /// @param[out] to Location to copy to. -void tv_copy(typval_T *const from, typval_T *const to) +void tv_copy(const typval_T *const from, typval_T *const to) { to->v_type = from->v_type; to->v_lock = VAR_UNLOCKED; @@ -2026,9 +2272,7 @@ void tv_copy(typval_T *const from, typval_T *const to) break; } case VAR_LIST: { - if (from->vval.v_list != NULL) { - to->vval.v_list->lv_refcount++; - } + tv_list_ref(to->vval.v_list); break; } case VAR_DICT: { @@ -2084,9 +2328,9 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock) CHANGE_LOCK(lock, l->lv_lock); if (deep < 0 || deep > 1) { // Recursive: lock/unlock the items the List contains. - for (listitem_T *li = l->lv_first; li != NULL; li = li->li_next) { - tv_item_lock(&li->li_tv, deep - 1, lock); - } + TV_LIST_ITER(l, li, { + tv_item_lock(TV_LIST_ITEM_TV(li), deep - 1, lock); + }); } } break; @@ -2122,6 +2366,8 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock) /// Check whether VimL value is locked itself or refers to a locked container /// +/// @warning Fixed container is not the same as locked. +/// /// @param[in] tv Value to check. /// /// @return True if value is locked, false otherwise. @@ -2130,8 +2376,7 @@ bool tv_islocked(const typval_T *const tv) { return ((tv->v_lock == VAR_LOCKED) || (tv->v_type == VAR_LIST - && tv->vval.v_list != NULL - && (tv->vval.v_list->lv_lock == VAR_LOCKED)) + && (tv_list_locked(tv->vval.v_list) == VAR_LOCKED)) || (tv->v_type == VAR_DICT && tv->vval.v_dict != NULL && (tv->vval.v_dict->dv_lock == VAR_LOCKED))); diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index c44b85644d..2272a580d6 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -6,6 +6,8 @@ #include <stdint.h> #include <string.h> #include <stdbool.h> +#include <assert.h> +#include <limits.h> #include "nvim/types.h" #include "nvim/hashtab.h" @@ -18,6 +20,9 @@ #include "nvim/gettext.h" #include "nvim/message.h" #include "nvim/macros.h" +#ifdef LOG_LIST_ACTIONS +# include "nvim/memory.h" +#endif /// Type used for VimL VAR_NUMBER values typedef int64_t varnumber_T; @@ -26,6 +31,28 @@ typedef uint64_t uvarnumber_T; /// Type used for VimL VAR_FLOAT values typedef double float_T; +/// Refcount for dict or list that should not be freed +enum { DO_NOT_FREE_CNT = (INT_MAX / 2) }; + +/// Additional values for tv_list_alloc() len argument +enum { + /// List length is not known in advance + /// + /// To be used when there is neither a way to know how many elements will be + /// needed nor are any educated guesses. + kListLenUnknown = -1, + /// List length *should* be known, but is actually not + /// + /// All occurrences of this value should be eventually removed. This is for + /// the case when the only reason why list length is not known is that it + /// would be hard to code without refactoring, but refactoring is needed. + kListLenShouldKnow = -2, + /// List length may be known in advance, but it requires too much effort + /// + /// To be used when it looks impractical to determine list length. + kListLenMayKnow = -3, +} ListLenSpecials; + /// Maximal possible value of varnumber_T variable #define VARNUMBER_MAX INT64_MAX #define UVARNUMBER_MAX UINT64_MAX @@ -150,12 +177,26 @@ struct listvar_S { list_T *lv_used_prev; ///< Previous list in used lists list. }; -// Static list with 10 items. Use init_static_list() to initialize. +// Static list with 10 items. Use tv_list_init_static10() to initialize. typedef struct { list_T sl_list; // must be first listitem_T sl_items[10]; } staticList10_T; +#define TV_LIST_STATIC10_INIT { \ + .sl_list = { \ + .lv_first = NULL, \ + .lv_last = NULL, \ + .lv_refcount = 0, \ + .lv_len = 0, \ + .lv_watch = NULL, \ + .lv_idx_item = NULL, \ + .lv_lock = VAR_FIXED, \ + .lv_used_next = NULL, \ + .lv_used_prev = NULL, \ + }, \ + } + // Structure to hold an item of a Dictionary. // Also used for a variable. // The key is copied into "di_key" to avoid an extra alloc/free for it. @@ -165,11 +206,11 @@ struct dictitem_S { char_u di_key[1]; ///< key (actually longer!) }; -#define TV_DICTITEM_STRUCT(KEY_LEN) \ +#define TV_DICTITEM_STRUCT(...) \ struct { \ typval_T di_tv; /* Structure that holds scope dictionary itself. */ \ uint8_t di_flags; /* Flags. */ \ - char_u di_key[KEY_LEN]; /* Key value. */ \ + char_u di_key[__VA_ARGS__]; /* Key value. */ \ } /// Structure to hold a scope dictionary @@ -277,6 +318,104 @@ typedef struct list_stack_S { struct list_stack_S *prev; } list_stack_T; +/// Structure representing one list item, used for sort array. +typedef struct { + listitem_T *item; ///< Sorted list item. + int idx; ///< Sorted list item index. +} ListSortItem; + +typedef int (*ListSorter)(const void *, const void *); + +#ifdef LOG_LIST_ACTIONS + +/// List actions log entry +typedef struct { + uintptr_t l; ///< List log entry belongs to. + uintptr_t li1; ///< First list item log entry belongs to, if applicable. + uintptr_t li2; ///< Second list item log entry belongs to, if applicable. + int len; ///< List length when log entry was created. + const char *action; ///< Logged action. +} ListLogEntry; + +typedef struct list_log ListLog; + +/// List actions log +struct list_log { + ListLog *next; ///< Next chunk or NULL. + size_t capacity; ///< Number of entries in current chunk. + size_t size; ///< Current chunk size. + ListLogEntry entries[]; ///< Actual log entries. +}; + +extern ListLog *list_log_first; ///< First list log chunk, NULL if missing +extern ListLog *list_log_last; ///< Last list log chunk + +static inline ListLog *list_log_alloc(const size_t size) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Allocate a new log chunk and update globals +/// +/// @param[in] size Number of entries in a new chunk. +/// +/// @return [allocated] Newly allocated chunk. +static inline ListLog *list_log_new(const size_t size) +{ + ListLog *ret = xmalloc(offsetof(ListLog, entries) + + size * sizeof(ret->entries[0])); + ret->size = 0; + ret->capacity = size; + ret->next = NULL; + if (list_log_first == NULL) { + list_log_first = ret; + } else { + list_log_last->next = ret; + } + list_log_last = ret; + return ret; +} + +static inline void list_log(const list_T *const l, + const listitem_T *const li1, + const listitem_T *const li2, + const char *const action) + REAL_FATTR_ALWAYS_INLINE; + +/// Add new entry to log +/// +/// If last chunk was filled it uses twice as much memory to allocate the next +/// chunk. +/// +/// @param[in] l List to which entry belongs. +/// @param[in] li1 List item 1. +/// @param[in] li2 List item 2, often used for integers and not list items. +/// @param[in] action Logged action. +static inline void list_log(const list_T *const l, + const listitem_T *const li1, + const listitem_T *const li2, + const char *const action) +{ + ListLog *tgt; + if (list_log_first == NULL) { + tgt = list_log_new(128); + } else if (list_log_last->size == list_log_last->capacity) { + tgt = list_log_new(list_log_last->capacity * 2); + } else { + tgt = list_log_last; + } + tgt->entries[tgt->size++] = (ListLogEntry) { + .l = (uintptr_t)l, + .li1 = (uintptr_t)li1, + .li2 = (uintptr_t)li2, + .len = (l == NULL ? 0 : l->lv_len), + .action = action, + }; +} +#else +# define list_log(...) +# define list_write_log(...) +# define list_free_log() +#endif + // In a hashtab item "hi_key" points to "di_key" in a dictitem. // This avoids adding a pointer to the hashtab item. @@ -284,20 +423,181 @@ typedef struct list_stack_S { #define TV_DICT_HI2DI(hi) \ ((dictitem_T *)((hi)->hi_key - offsetof(dictitem_T, di_key))) -static inline long tv_list_len(const list_T *const l) +/// Increase reference count for a given list +/// +/// Does nothing for NULL lists. +/// +/// @param[in] l List to modify. +static inline void tv_list_ref(list_T *const l) +{ + if (l == NULL) { + return; + } + l->lv_refcount++; +} + +static inline VarLockStatus tv_list_locked(const list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Get list lock status +/// +/// Returns VAR_FIXED for NULL lists. +/// +/// @param[in] l List to check. +static inline VarLockStatus tv_list_locked(const list_T *const l) +{ + if (l == NULL) { + return VAR_FIXED; + } + return l->lv_lock; +} + +/// Set list lock status +/// +/// May only “set†VAR_FIXED for NULL lists. +/// +/// @param[out] l List to modify. +/// @param[in] lock New lock status. +static inline void tv_list_set_lock(list_T *const l, + const VarLockStatus lock) +{ + if (l == NULL) { + assert(lock == VAR_FIXED); + return; + } + l->lv_lock = lock; +} + +/// Set list copyID +/// +/// Does not expect NULL list, be careful. +/// +/// @param[out] l List to modify. +/// @param[in] copyid New copyID. +static inline void tv_list_set_copyid(list_T *const l, + const int copyid) + FUNC_ATTR_NONNULL_ALL +{ + l->lv_copyID = copyid; +} + +static inline int tv_list_len(const list_T *const l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Get the number of items in a list /// /// @param[in] l List to check. -static inline long tv_list_len(const list_T *const l) +static inline int tv_list_len(const list_T *const l) { + list_log(l, NULL, NULL, "len"); if (l == NULL) { return 0; } return l->lv_len; } +static inline int tv_list_copyid(const list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; + +/// Get list copyID +/// +/// Does not expect NULL list, be careful. +/// +/// @param[in] l List to check. +static inline int tv_list_copyid(const list_T *const l) +{ + return l->lv_copyID; +} + +static inline list_T *tv_list_latest_copy(const list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; + +/// Get latest list copy +/// +/// Gets lv_copylist field assigned by tv_list_copy() earlier. +/// +/// Does not expect NULL list, be careful. +/// +/// @param[in] l List to check. +static inline list_T *tv_list_latest_copy(const list_T *const l) +{ + return l->lv_copylist; +} + +static inline int tv_list_uidx(const list_T *const l, int n) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Normalize index: that is, return either -1 or non-negative index +/// +/// @param[in] l List to index. Used to get length. +/// @param[in] n List index, possibly negative. +/// +/// @return -1 or list index in range [0, tv_list_len(l)). +static inline int tv_list_uidx(const list_T *const l, int n) +{ + // Negative index is relative to the end. + if (n < 0) { + n += tv_list_len(l); + } + + // Check for index out of range. + if (n < 0 || n >= tv_list_len(l)) { + return -1; + } + return n; +} + +static inline bool tv_list_has_watchers(const list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Check whether list has watchers +/// +/// E.g. is referenced by a :for loop. +/// +/// @param[in] l List to check. +/// +/// @return true if there are watchers, false otherwise. +static inline bool tv_list_has_watchers(const list_T *const l) +{ + return l && l->lv_watch; +} + +static inline listitem_T *tv_list_first(const list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Get first list item +/// +/// @param[in] l List to get item from. +/// +/// @return List item or NULL in case of an empty list. +static inline listitem_T *tv_list_first(const list_T *const l) +{ + if (l == NULL) { + list_log(l, NULL, NULL, "first"); + return NULL; + } + list_log(l, l->lv_first, NULL, "first"); + return l->lv_first; +} + +static inline listitem_T *tv_list_last(const list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Get last list item +/// +/// @param[in] l List to get item from. +/// +/// @return List item or NULL in case of an empty list. +static inline listitem_T *tv_list_last(const list_T *const l) +{ + if (l == NULL) { + list_log(l, NULL, NULL, "last"); + return NULL; + } + list_log(l, l->lv_last, NULL, "last"); + return l->lv_last; +} + static inline long tv_dict_len(const dict_T *const d) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; @@ -352,6 +652,76 @@ extern const char *const tv_empty_string; /// Specifies that free_unref_items() function has (not) been entered extern bool tv_in_free_unref_items; +/// Iterate over a list +/// +/// @param modifier Modifier: expected to be const or nothing, volatile should +/// also work if you have any uses for the volatile list. +/// @param[in] l List to iterate over. +/// @param li Name of the variable with current listitem_T entry. +/// @param code Cycle body. +#define _TV_LIST_ITER_MOD(modifier, l, li, code) \ + do { \ + modifier list_T *const l_ = (l); \ + list_log(l_, NULL, NULL, "iter" #modifier); \ + if (l_ != NULL) { \ + for (modifier listitem_T *li = l_->lv_first; \ + li != NULL; li = li->li_next) { \ + code \ + } \ + } \ + } while (0) + +/// Iterate over a list +/// +/// To be used when you need to modify list or values you iterate over, use +/// #TV_LIST_ITER_CONST if you don’t. +/// +/// @param[in] l List to iterate over. +/// @param li Name of the variable with current listitem_T entry. +/// @param code Cycle body. +#define TV_LIST_ITER(l, li, code) \ + _TV_LIST_ITER_MOD(, l, li, code) + +/// Iterate over a list +/// +/// To be used when you don’t need to modify list or values you iterate over, +/// use #TV_LIST_ITER if you do. +/// +/// @param[in] l List to iterate over. +/// @param li Name of the variable with current listitem_T entry. +/// @param code Cycle body. +#define TV_LIST_ITER_CONST(l, li, code) \ + _TV_LIST_ITER_MOD(const, l, li, code) + +// Below macros are macros to avoid duplicating code for functionally identical +// const and non-const function variants. + +/// Get typval_T out of list item +/// +/// @param[in] li List item to get typval_T from, must not be NULL. +/// +/// @return Pointer to typval_T. +#define TV_LIST_ITEM_TV(li) (&(li)->li_tv) + +/// Get next list item given the current one +/// +/// @param[in] l List to get item from. +/// @param[in] li List item to get typval_T from. +/// +/// @return Pointer to the next item or NULL. +#define TV_LIST_ITEM_NEXT(l, li) ((li)->li_next) + +/// Get previous list item given the current one +/// +/// @param[in] l List to get item from. +/// @param[in] li List item to get typval_T from. +/// +/// @return Pointer to the previous item or NULL. +#define TV_LIST_ITEM_PREV(l, li) ((li)->li_prev) +// List argument is not used currently, but it is a must for lists implemented +// as a pair (size(in list), array) without terminator - basically for lists on +// top of kvec. + /// Iterate over a dictionary /// /// @param[in] d Dictionary to iterate over. diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index a93ad2dbba..f2d0d7265f 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -355,14 +355,14 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( break; } case VAR_LIST: { - if (tv->vval.v_list == NULL || tv->vval.v_list->lv_len == 0) { + if (tv->vval.v_list == NULL || tv_list_len(tv->vval.v_list) == 0) { TYPVAL_ENCODE_CONV_EMPTY_LIST(tv); break; } - const int saved_copyID = tv->vval.v_list->lv_copyID; + const int saved_copyID = tv_list_copyid(tv->vval.v_list); _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, copyID, kMPConvList); - TYPVAL_ENCODE_CONV_LIST_START(tv, tv->vval.v_list->lv_len); + TYPVAL_ENCODE_CONV_LIST_START(tv, tv_list_len(tv->vval.v_list)); assert(saved_copyID != copyID && saved_copyID != copyID - 1); _mp_push(*mpstack, ((MPConvStackVal) { .type = kMPConvList, @@ -371,7 +371,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( .data = { .l = { .list = tv->vval.v_list, - .li = tv->vval.v_list->lv_first, + .li = tv_list_first(tv->vval.v_list), }, }, })); @@ -440,23 +440,43 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( // bits is not checked), other unsigned and have at most 31 // non-zero bits (number of bits is not checked). if (val_di->di_tv.v_type != VAR_LIST - || (val_list = val_di->di_tv.vval.v_list) == NULL - || val_list->lv_len != 4 - || val_list->lv_first->li_tv.v_type != VAR_NUMBER - || (sign = val_list->lv_first->li_tv.vval.v_number) == 0 - || val_list->lv_first->li_next->li_tv.v_type != VAR_NUMBER - || (highest_bits = - val_list->lv_first->li_next->li_tv.vval.v_number) < 0 - || val_list->lv_last->li_prev->li_tv.v_type != VAR_NUMBER - || (high_bits = - val_list->lv_last->li_prev->li_tv.vval.v_number) < 0 - || val_list->lv_last->li_tv.v_type != VAR_NUMBER - || (low_bits = val_list->lv_last->li_tv.vval.v_number) < 0) { + || tv_list_len(val_list = val_di->di_tv.vval.v_list) != 4) { goto _convert_one_value_regular_dict; } - uint64_t number = ((uint64_t)(((uint64_t)highest_bits) << 62) - | (uint64_t)(((uint64_t)high_bits) << 31) - | (uint64_t)low_bits); + + const listitem_T *const sign_li = tv_list_first(val_list); + if (TV_LIST_ITEM_TV(sign_li)->v_type != VAR_NUMBER + || (sign = TV_LIST_ITEM_TV(sign_li)->vval.v_number) == 0) { + goto _convert_one_value_regular_dict; + } + + const listitem_T *const highest_bits_li = ( + TV_LIST_ITEM_NEXT(val_list, sign_li)); + if (TV_LIST_ITEM_TV(highest_bits_li)->v_type != VAR_NUMBER + || ((highest_bits + = TV_LIST_ITEM_TV(highest_bits_li)->vval.v_number) + < 0)) { + goto _convert_one_value_regular_dict; + } + + const listitem_T *const high_bits_li = ( + TV_LIST_ITEM_NEXT(val_list, highest_bits_li)); + if (TV_LIST_ITEM_TV(high_bits_li)->v_type != VAR_NUMBER + || ((high_bits = TV_LIST_ITEM_TV(high_bits_li)->vval.v_number) + < 0)) { + goto _convert_one_value_regular_dict; + } + + const listitem_T *const low_bits_li = tv_list_last(val_list); + if (TV_LIST_ITEM_TV(low_bits_li)->v_type != VAR_NUMBER + || ((low_bits = TV_LIST_ITEM_TV(low_bits_li)->vval.v_number) + < 0)) { + goto _convert_one_value_regular_dict; + } + + const uint64_t number = ((uint64_t)(((uint64_t)highest_bits) << 62) + | (uint64_t)(((uint64_t)high_bits) << 31) + | (uint64_t)low_bits); if (sign > 0) { TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, number); } else { @@ -495,12 +515,12 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( if (val_di->di_tv.v_type != VAR_LIST) { goto _convert_one_value_regular_dict; } - const int saved_copyID = val_di->di_tv.vval.v_list->lv_copyID; + const int saved_copyID = tv_list_copyid(val_di->di_tv.vval.v_list); _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list, lv_copyID, copyID, kMPConvList); - TYPVAL_ENCODE_CONV_LIST_START(tv, - val_di->di_tv.vval.v_list->lv_len); + TYPVAL_ENCODE_CONV_LIST_START( + tv, tv_list_len(val_di->di_tv.vval.v_list)); assert(saved_copyID != copyID && saved_copyID != copyID - 1); _mp_push(*mpstack, ((MPConvStackVal) { .tv = tv, @@ -509,7 +529,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( .data = { .l = { .list = val_di->di_tv.vval.v_list, - .li = val_di->di_tv.vval.v_list->lv_first, + .li = tv_list_first(val_di->di_tv.vval.v_list), }, }, })); @@ -520,22 +540,21 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( goto _convert_one_value_regular_dict; } list_T *const val_list = val_di->di_tv.vval.v_list; - if (val_list == NULL || val_list->lv_len == 0) { + if (val_list == NULL || tv_list_len(val_list) == 0) { TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, TYPVAL_ENCODE_NODICT_VAR); break; } - for (const listitem_T *li = val_list->lv_first; li != NULL; - li = li->li_next) { - if (li->li_tv.v_type != VAR_LIST - || li->li_tv.vval.v_list->lv_len != 2) { + TV_LIST_ITER_CONST(val_list, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_LIST + || tv_list_len(TV_LIST_ITEM_TV(li)->vval.v_list) != 2) { goto _convert_one_value_regular_dict; } - } - const int saved_copyID = val_di->di_tv.vval.v_list->lv_copyID; + }); + const int saved_copyID = tv_list_copyid(val_di->di_tv.vval.v_list); _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_list, lv_copyID, copyID, kMPConvPairs); TYPVAL_ENCODE_CONV_DICT_START(tv, TYPVAL_ENCODE_NODICT_VAR, - val_list->lv_len); + tv_list_len(val_list)); assert(saved_copyID != copyID && saved_copyID != copyID - 1); _mp_push(*mpstack, ((MPConvStackVal) { .tv = tv, @@ -544,7 +563,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( .data = { .l = { .list = val_list, - .li = val_list->lv_first, + .li = tv_list_first(val_list), }, }, })); @@ -554,18 +573,23 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( const list_T *val_list; varnumber_T type; if (val_di->di_tv.v_type != VAR_LIST - || (val_list = val_di->di_tv.vval.v_list) == NULL - || val_list->lv_len != 2 - || (val_list->lv_first->li_tv.v_type != VAR_NUMBER) - || (type = val_list->lv_first->li_tv.vval.v_number) > INT8_MAX + || tv_list_len((val_list = val_di->di_tv.vval.v_list)) != 2 + || (TV_LIST_ITEM_TV(tv_list_first(val_list))->v_type + != VAR_NUMBER) + || ((type + = TV_LIST_ITEM_TV(tv_list_first(val_list))->vval.v_number) + > INT8_MAX) || type < INT8_MIN - || (val_list->lv_last->li_tv.v_type != VAR_LIST)) { + || (TV_LIST_ITEM_TV(tv_list_last(val_list))->v_type + != VAR_LIST)) { goto _convert_one_value_regular_dict; } size_t len; char *buf; - if (!encode_vim_list_to_buf(val_list->lv_last->li_tv.vval.v_list, - &len, &buf)) { + if (!( + encode_vim_list_to_buf( + TV_LIST_ITEM_TV(tv_list_last(val_list))->vval.v_list, &len, + &buf))) { goto _convert_one_value_regular_dict; } TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type); @@ -600,7 +624,7 @@ _convert_one_value_regular_dict: {} break; } case VAR_UNKNOWN: { - EMSG2(_(e_intern2), STR(_TYPVAL_ENCODE_CONVERT_ONE_VALUE) "()"); + internal_error(STR(_TYPVAL_ENCODE_CONVERT_ONE_VALUE) "()"); return FAIL; } } @@ -671,40 +695,45 @@ typval_encode_stop_converting_one_item: case kMPConvList: { if (cur_mpsv->data.l.li == NULL) { (void)_mp_pop(mpstack); - cur_mpsv->data.l.list->lv_copyID = cur_mpsv->saved_copyID; + tv_list_set_copyid(cur_mpsv->data.l.list, cur_mpsv->saved_copyID); TYPVAL_ENCODE_CONV_LIST_END(cur_mpsv->tv); continue; - } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { + } else if (cur_mpsv->data.l.li + != tv_list_first(cur_mpsv->data.l.list)) { TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(cur_mpsv->tv); } - tv = &cur_mpsv->data.l.li->li_tv; - cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; + tv = TV_LIST_ITEM_TV(cur_mpsv->data.l.li); + cur_mpsv->data.l.li = TV_LIST_ITEM_NEXT(cur_mpsv->data.l.list, + cur_mpsv->data.l.li); break; } case kMPConvPairs: { if (cur_mpsv->data.l.li == NULL) { (void)_mp_pop(mpstack); - cur_mpsv->data.l.list->lv_copyID = cur_mpsv->saved_copyID; + tv_list_set_copyid(cur_mpsv->data.l.list, cur_mpsv->saved_copyID); TYPVAL_ENCODE_CONV_DICT_END(cur_mpsv->tv, TYPVAL_ENCODE_NODICT_VAR); continue; - } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { + } else if (cur_mpsv->data.l.li + != tv_list_first(cur_mpsv->data.l.list)) { TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS( cur_mpsv->tv, TYPVAL_ENCODE_NODICT_VAR); } - const list_T *const kv_pair = cur_mpsv->data.l.li->li_tv.vval.v_list; + const list_T *const kv_pair = ( + TV_LIST_ITEM_TV(cur_mpsv->data.l.li)->vval.v_list); TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK( - encode_vim_to__error_ret, kv_pair->lv_first->li_tv); - if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, - &mpstack, cur_mpsv, - &kv_pair->lv_first->li_tv, - copyID, - objname) == FAIL) { + encode_vim_to__error_ret, *TV_LIST_ITEM_TV(tv_list_first(kv_pair))); + if ( + _TYPVAL_ENCODE_CONVERT_ONE_VALUE( + TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, cur_mpsv, + TV_LIST_ITEM_TV(tv_list_first(kv_pair)), copyID, objname) + == FAIL) { goto encode_vim_to__error_ret; } TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(cur_mpsv->tv, TYPVAL_ENCODE_NODICT_VAR); - tv = &kv_pair->lv_last->li_tv; - cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; + tv = TV_LIST_ITEM_TV(tv_list_last(kv_pair)); + cur_mpsv->data.l.li = TV_LIST_ITEM_NEXT(cur_mpsv->data.l.list, + cur_mpsv->data.l.li); break; } case kMPConvPartial: { diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c index c101cb1bb9..0c2bf397e5 100644 --- a/src/nvim/event/libuv_process.c +++ b/src/nvim/event/libuv_process.c @@ -26,15 +26,18 @@ int libuv_process_spawn(LibuvProcess *uvproc) uvproc->uvopts.file = proc->argv[0]; uvproc->uvopts.args = proc->argv; uvproc->uvopts.flags = UV_PROCESS_WINDOWS_HIDE; - if (proc->detach) { - uvproc->uvopts.flags |= UV_PROCESS_DETACHED; - } #ifdef WIN32 // libuv collapses the argv to a CommandLineToArgvW()-style string. cmd.exe // expects a different syntax (must be prepared by the caller before now). if (os_shell_is_cmdexe(proc->argv[0])) { uvproc->uvopts.flags |= UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS; } + if (proc->detach) { + uvproc->uvopts.flags |= UV_PROCESS_DETACHED; + } +#else + // Always setsid() on unix-likes. #8107 + uvproc->uvopts.flags |= UV_PROCESS_DETACHED; #endif uvproc->uvopts.exit_cb = exit_cb; uvproc->uvopts.cwd = proc->cwd; diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c index 5adf16c0f3..d92464f17b 100644 --- a/src/nvim/event/loop.c +++ b/src/nvim/event/loop.c @@ -30,19 +30,25 @@ void loop_init(Loop *loop, void *data) uv_signal_init(&loop->uv, &loop->children_watcher); uv_timer_init(&loop->uv, &loop->children_kill_timer); uv_timer_init(&loop->uv, &loop->poll_timer); + loop->poll_timer.data = xmalloc(sizeof(bool)); // "timeout expired" flag } -void loop_poll_events(Loop *loop, int ms) +/// Processes one `Loop.uv` event (at most). +/// Processes all `Loop.fast_events` events. +/// +/// @returns true if `ms` timeout was reached +bool loop_poll_events(Loop *loop, int ms) { if (loop->recursive++) { abort(); // Should not re-enter uv_run } uv_run_mode mode = UV_RUN_ONCE; + bool timeout_expired = false; if (ms > 0) { - // Use a repeating timeout of ms milliseconds to make sure - // we do not block indefinitely for I/O. + *((bool *)loop->poll_timer.data) = false; // reset "timeout expired" flag + // Dummy timer to ensure UV_RUN_ONCE does not block indefinitely for I/O. uv_timer_start(&loop->poll_timer, timer_cb, (uint64_t)ms, (uint64_t)ms); } else if (ms == 0) { // For ms == 0, do a non-blocking event poll. @@ -52,11 +58,13 @@ void loop_poll_events(Loop *loop, int ms) uv_run(&loop->uv, mode); if (ms > 0) { + timeout_expired = *((bool *)loop->poll_timer.data); uv_timer_stop(&loop->poll_timer); } loop->recursive--; // Can re-enter uv_run now multiqueue_process_events(loop->fast_events); + return timeout_expired; } /// Schedules an event from another thread. @@ -111,7 +119,7 @@ bool loop_close(Loop *loop, bool wait) uv_mutex_destroy(&loop->mutex); uv_close((uv_handle_t *)&loop->children_watcher, NULL); uv_close((uv_handle_t *)&loop->children_kill_timer, NULL); - uv_close((uv_handle_t *)&loop->poll_timer, NULL); + uv_close((uv_handle_t *)&loop->poll_timer, timer_close_cb); uv_close((uv_handle_t *)&loop->async, NULL); uint64_t start = wait ? os_hrtime() : 0; while (true) { @@ -163,5 +171,11 @@ static void async_cb(uv_async_t *handle) static void timer_cb(uv_timer_t *handle) { + bool *timeout_expired = handle->data; + *timeout_expired = true; } +static void timer_close_cb(uv_handle_t *handle) +{ + xfree(handle->data); +} diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index 4eb2dd0baf..60650344ce 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -12,6 +12,7 @@ #include "nvim/event/wstream.h" #include "nvim/event/process.h" #include "nvim/event/libuv_process.h" +#include "nvim/os/process.h" #include "nvim/os/pty_process.h" #include "nvim/globals.h" #include "nvim/macros.h" @@ -151,7 +152,6 @@ void process_close_streams(Process *proc) FUNC_ATTR_NONNULL_ALL int process_wait(Process *proc, int ms, MultiQueue *events) FUNC_ATTR_NONNULL_ARG(1) { - bool interrupted = false; if (!proc->refcount) { int status = proc->status; LOOP_PROCESS_EVENTS(proc->loop, proc->events, 0); @@ -173,7 +173,6 @@ int process_wait(Process *proc, int ms, MultiQueue *events) // we'll assume that a user frantically hitting interrupt doesn't like // the current job. Signal that it has to be killed. if (got_int) { - interrupted = true; got_int = false; process_stop(proc); if (ms == -1) { @@ -184,14 +183,13 @@ int process_wait(Process *proc, int ms, MultiQueue *events) } else { LOOP_PROCESS_EVENTS(proc->loop, events, 0); } + + proc->status = -2; } if (proc->refcount == 1) { // Job exited, collect status and manually invoke close_cb to free the job // resources - if (interrupted) { - proc->status = -2; - } decref(proc); if (events) { // the decref call created an exit event, process it now @@ -218,8 +216,7 @@ void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL // stdout/stderr, they will be closed when it exits(possibly due to being // terminated after a timeout) stream_may_close(&proc->in); - ILOG("Sending SIGTERM to pid %d", proc->pid); - uv_kill(proc->pid, SIGTERM); + os_proc_tree_kill(proc->pid, SIGTERM); break; case kProcessTypePty: // close all streams for pty processes to send SIGHUP to the process @@ -234,7 +231,7 @@ void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL if (!loop->children_stop_requests++) { // When there's at least one stop request pending, start a timer that // will periodically check if a signal should be send to the job. - ILOG("Starting job kill timer"); + ILOG("starting job kill timer"); uv_timer_start(&loop->children_kill_timer, children_kill_cb, KILL_TIMEOUT_MS, KILL_TIMEOUT_MS); } @@ -256,11 +253,9 @@ static void children_kill_cb(uv_timer_t *handle) if (elapsed >= KILL_TIMEOUT_MS) { int sig = proc->type == kProcessTypePty && elapsed < KILL_TIMEOUT_MS * 2 - ? SIGTERM - : SIGKILL; - ILOG("Sending %s to pid %d", sig == SIGTERM ? "SIGTERM" : "SIGKILL", - proc->pid); - uv_kill(proc->pid, sig); + ? SIGTERM + : SIGKILL; + os_proc_tree_kill(proc->pid, sig); } } } diff --git a/src/nvim/event/rstream.c b/src/nvim/event/rstream.c index e0500ba828..2fbe7f6773 100644 --- a/src/nvim/event/rstream.c +++ b/src/nvim/event/rstream.c @@ -95,7 +95,7 @@ static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf) // `uv_buf_t.len` happens to have different size on Windows. size_t write_count; buf->base = rbuffer_write_ptr(stream->buffer, &write_count); - buf->len = write_count; + buf->len = UV_BUF_LEN(write_count); } // Callback invoked by libuv after it copies the data into the buffer provided @@ -146,7 +146,7 @@ static void fread_idle_cb(uv_idle_t *handle) // `uv_buf_t.len` happens to have different size on Windows. size_t write_count; stream->uvbuf.base = rbuffer_write_ptr(stream->buffer, &write_count); - stream->uvbuf.len = write_count; + stream->uvbuf.len = UV_BUF_LEN(write_count); // the offset argument to uv_fs_read is int64_t, could someone really try // to read more than 9 quintillion (9e18) bytes? diff --git a/src/nvim/event/wstream.c b/src/nvim/event/wstream.c index 320006890d..d2fb52243c 100644 --- a/src/nvim/event/wstream.c +++ b/src/nvim/event/wstream.c @@ -90,7 +90,7 @@ bool wstream_write(Stream *stream, WBuffer *buffer) uv_buf_t uvbuf; uvbuf.base = buffer->data; - uvbuf.len = buffer->size; + uvbuf.len = UV_BUF_LEN(buffer->size); if (uv_write(&data->uv_req, stream->uvstream, &uvbuf, 1, write_cb)) { xfree(data); diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 8616508d88..c396b5891a 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -1175,7 +1175,7 @@ static void do_filter( // to read the error messages. Otherwise errors are ignored, so you can see // the error messages from the command that appear on stdout; use 'u' to fix // the text. - // Pass on the kShellDoOut flag when the output is being redirected. + // Pass on the kShellOptDoOut flag when the output is being redirected. if (call_shell( cmd_buf, kShellOptFilter | shell_flags, @@ -1417,7 +1417,7 @@ char_u *make_filter_cmd(char_u *cmd, char_u *itmp, char_u *otmp) #else // For shells that don't understand braces around commands, at least allow // the use of commands in a pipe. - xstrlcpy(buf, cmd, len); + xstrlcpy(buf, (char *)cmd, len); if (itmp != NULL) { // If there is a pipe, we have to put the '<' in front of it. // Don't do this when 'shellquote' is not empty, otherwise the @@ -2013,9 +2013,10 @@ int getfile(int fnum, char_u *ffname, char_u *sfname, int setpm, linenr_T lnum, } else other = (fnum != curbuf->b_fnum); - if (other) - ++no_wait_return; /* don't wait for autowrite message */ - if (other && !forceit && curbuf->b_nwindows == 1 && !P_HID(curbuf) + if (other) { + no_wait_return++; // don't wait for autowrite message + } + if (other && !forceit && curbuf->b_nwindows == 1 && !buf_hide(curbuf) && curbufIsChanged() && autowrite(curbuf, forceit) == FAIL) { if (p_confirm && p_write) dialog_changed(curbuf, FALSE); @@ -2032,17 +2033,19 @@ int getfile(int fnum, char_u *ffname, char_u *sfname, int setpm, linenr_T lnum, if (setpm) setpcmark(); if (!other) { - if (lnum != 0) + if (lnum != 0) { curwin->w_cursor.lnum = lnum; + } check_cursor_lnum(); beginline(BL_SOL | BL_FIX); - retval = 0; /* it's in the same file */ + retval = 0; // it's in the same file } else if (do_ecmd(fnum, ffname, sfname, NULL, lnum, - (P_HID(curbuf) ? ECMD_HIDE : 0) + (forceit ? ECMD_FORCEIT : 0), - curwin) == OK) - retval = -1; /* opened another file */ - else - retval = 1; /* error encountered */ + (buf_hide(curbuf) ? ECMD_HIDE : 0) + + (forceit ? ECMD_FORCEIT : 0), curwin) == OK) { + retval = -1; // opened another file + } else { + retval = 1; // error encountered + } theend: xfree(free_me); @@ -3541,6 +3544,9 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout) getvcol(curwin, &curwin->w_cursor, &sc, NULL, NULL); curwin->w_cursor.col = regmatch.endpos[0].col - 1; + if (curwin->w_cursor.col < 0) { + curwin->w_cursor.col = 0; + } getvcol(curwin, &curwin->w_cursor, NULL, NULL, &ec); if (subflags.do_number || curwin->w_p_nu) { int numw = number_width(curwin) + 1; @@ -4617,18 +4623,20 @@ int find_help_tags(char_u *arg, int *num_matches, char_u ***matches, int keep_la static char *(mtable[]) = {"*", "g*", "[*", "]*", "/*", "/\\*", "\"*", "**", "/\\(\\)", "/\\%(\\)", - "?", ":?", "?<CR>", "g?", "g?g?", "g??", "z?", + "?", ":?", "?<CR>", "g?", "g?g?", "g??", "/\\?", "/\\z(\\)", "\\=", ":s\\=", - "[count]", "[quotex]", "[range]", + "[count]", "[quotex]", + "[range]", ":[range]", "[pattern]", "\\|", "\\%$", "s/\\~", "s/\\U", "s/\\L", "s/\\1", "s/\\2", "s/\\3", "s/\\9"}; static char *(rtable[]) = {"star", "gstar", "[star", "]star", "/star", "/\\\\star", "quotestar", "starstar", "/\\\\(\\\\)", "/\\\\%(\\\\)", - "?", ":?", "?<CR>", "g?", "g?g?", "g??", "z?", + "?", ":?", "?<CR>", "g?", "g?g?", "g??", "/\\\\?", "/\\\\z(\\\\)", "\\\\=", ":s\\\\=", - "\\[count]", "\\[quotex]", "\\[range]", + "\\[count]", "\\[quotex]", + "\\[range]", ":\\[range]", "\\[pattern]", "\\\\bar", "/\\\\%\\$", "s/\\\\\\~", "s/\\\\U", "s/\\\\L", "s/\\\\1", "s/\\\\2", "s/\\\\3", "s/\\\\9"}; @@ -4856,7 +4864,9 @@ void fix_help_buffer(void) char_u *rt; // Set filetype to "help". - set_option_value("ft", 0L, "help", OPT_LOCAL); + if (STRCMP(curbuf->b_p_ft, "help") != 0) { + set_option_value("ft", 0L, "help", OPT_LOCAL); + } if (!syntax_present(curwin)) { for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; ++lnum) { @@ -5818,7 +5828,8 @@ static void sign_list_defined(sign_T *sp) } if (sp->sn_line_hl > 0) { msg_puts(" linehl="); - const char *const p = get_highlight_name(NULL, sp->sn_line_hl - 1); + const char *const p = get_highlight_name_ext(NULL, + sp->sn_line_hl - 1, false); if (p == NULL) { msg_puts("NONE"); } else { @@ -5827,7 +5838,8 @@ static void sign_list_defined(sign_T *sp) } if (sp->sn_text_hl > 0) { msg_puts(" texthl="); - const char *const p = get_highlight_name(NULL, sp->sn_text_hl - 1); + const char *const p = get_highlight_name_ext(NULL, + sp->sn_text_hl - 1, false); if (p == NULL) { msg_puts("NONE"); } else { @@ -6334,7 +6346,6 @@ char_u *skip_vimgrep_pat(char_u *p, char_u **s, int *flags) void ex_oldfiles(exarg_T *eap) { list_T *l = get_vim_var_list(VV_OLDFILES); - listitem_T *li; long nr = 0; if (l == NULL) { @@ -6342,19 +6353,22 @@ void ex_oldfiles(exarg_T *eap) } else { msg_start(); msg_scroll = true; - for (li = l->lv_first; li != NULL && !got_int; li = li->li_next) { + TV_LIST_ITER(l, li, { + if (got_int) { + break; + } nr++; - const char *fname = tv_get_string(&li->li_tv); + const char *fname = tv_get_string(TV_LIST_ITEM_TV(li)); if (!message_filtered((char_u *)fname)) { msg_outnum(nr); MSG_PUTS(": "); - msg_outtrans((char_u *)tv_get_string(&li->li_tv)); + msg_outtrans((char_u *)tv_get_string(TV_LIST_ITEM_TV(li))); msg_clr_eos(); msg_putchar('\n'); ui_flush(); // output one line at a time os_breakcheck(); } - } + }); // Assume "got_int" was set to truncate the listing. got_int = false; @@ -6364,7 +6378,7 @@ void ex_oldfiles(exarg_T *eap) quit_more = false; nr = prompt_for_number(false); msg_starthere(); - if (nr > 0 && nr <= l->lv_len) { + if (nr > 0 && nr <= tv_list_len(l)) { const char *const p = tv_list_find_str(l, nr - 1); if (p == NULL) { return; diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index e57e662039..ce02808ad3 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -108,7 +108,7 @@ return { }, { command='argedit', - flags=bit.bor(BANG, NEEDARG, RANGE, NOTADR, ZEROR, FILE1, EDITCMD, ARGOPT, TRLBAR), + flags=bit.bor(BANG, NEEDARG, RANGE, NOTADR, ZEROR, FILES, EDITCMD, ARGOPT, TRLBAR), addr_type=ADDR_ARGUMENTS, func='ex_argedit', }, @@ -692,7 +692,7 @@ return { }, { command='delcommand', - flags=bit.bor(NEEDARG, WORD1, TRLBAR, CMDWIN), + flags=bit.bor(BANG, NEEDARG, WORD1, TRLBAR, CMDWIN), addr_type=ADDR_LINES, func='ex_delcommand', }, diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index ec4ce63e17..821c050c50 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -1343,7 +1343,7 @@ void dialog_changed(buf_T *buf, int checkall) /// hidden, autowriting it or unloading it. bool can_abandon(buf_T *buf, int forceit) { - return P_HID(buf) + return buf_hide(buf) || !bufIsChanged(buf) || buf->b_nwindows > 1 || autowrite(buf, forceit) == OK @@ -1558,12 +1558,17 @@ static char_u *do_one_arg(char_u *str) /// Separate the arguments in "str" and return a list of pointers in the /// growarray "gap". -void get_arglist(garray_T *gap, char_u *str) +static void get_arglist(garray_T *gap, char_u *str, int escaped) { ga_init(gap, (int)sizeof(char_u *), 20); while (*str != NUL) { GA_APPEND(char_u *, gap, str); + // If str is escaped, don't handle backslashes or spaces + if (!escaped) { + return; + } + // Isolate one argument, change it in-place, put a NUL after it. str = do_one_arg(str); } @@ -1578,7 +1583,7 @@ int get_arglist_exp(char_u *str, int *fcountp, char_u ***fnamesp, bool wig) garray_T ga; int i; - get_arglist(&ga, str); + get_arglist(&ga, str, true); if (wig) { i = expand_wildcards(ga.ga_len, (char_u **)ga.ga_data, @@ -1609,6 +1614,7 @@ static int do_arglist(char_u *str, int what, int after) char_u **exp_files; char_u *p; int match; + int arg_escaped = true; // Set default argument for ":argadd" command. if (what == AL_ADD && *str == NUL) { @@ -1616,10 +1622,11 @@ static int do_arglist(char_u *str, int what, int after) return FAIL; } str = curbuf->b_fname; + arg_escaped = false; } // Collect all file name arguments in "new_ga". - get_arglist(&new_ga, str); + get_arglist(&new_ga, str, arg_escaped); if (what == AL_DEL) { regmatch_T regmatch; @@ -1853,12 +1860,12 @@ void do_argfile(exarg_T *eap, int argn) // if 'hidden' set, only check for changed file when re-editing // the same buffer other = true; - if (P_HID(curbuf)) { + if (buf_hide(curbuf)) { p = (char_u *)fix_fname((char *)alist_name(&ARGLIST[argn])); other = otherfile(p); xfree(p); } - if ((!P_HID(curbuf) || !other) + if ((!buf_hide(curbuf) || !other) && check_changed(curbuf, CCGD_AW | (other ? 0 : CCGD_MULTWIN) | (eap->forceit ? CCGD_FORCEIT : 0) @@ -1878,7 +1885,7 @@ void do_argfile(exarg_T *eap, int argn) // argument index. if (do_ecmd(0, alist_name(&ARGLIST[curwin->w_arg_idx]), NULL, eap, ECMD_LAST, - (P_HID(curwin->w_buffer) ? ECMD_HIDE : 0) + (buf_hide(curwin->w_buffer) ? ECMD_HIDE : 0) + (eap->forceit ? ECMD_FORCEIT : 0), curwin) == FAIL) { curwin->w_arg_idx = old_arg_idx; } else if (eap->cmdidx != CMD_argdo) { @@ -1895,7 +1902,7 @@ void ex_next(exarg_T *eap) // check for changed buffer now, if this fails the argument list is not // redefined. - if (P_HID(curbuf) + if (buf_hide(curbuf) || eap->cmdidx == CMD_snext || !check_changed(curbuf, CCGD_AW | (eap->forceit ? CCGD_FORCEIT : 0) @@ -1915,31 +1922,21 @@ void ex_next(exarg_T *eap) /// ":argedit" void ex_argedit(exarg_T *eap) { - int fnum; - int i; - char_u *s; - - // Add the argument to the buffer list and get the buffer number. - fnum = buflist_add(eap->arg, BLN_LISTED); + int i = eap->addr_count ? (int)eap->line2 : curwin->w_arg_idx + 1; - // Check if this argument is already in the argument list. - for (i = 0; i < ARGCOUNT; i++) { - if (ARGLIST[i].ae_fnum == fnum) { - break; - } - } - if (i == ARGCOUNT) { - // Can't find it, add it to the argument list. - s = vim_strsave(eap->arg); - int after = eap->addr_count > 0 ? (int)eap->line2 : curwin->w_arg_idx + 1; - i = alist_add_list(1, &s, after); - curwin->w_arg_idx = i; + if (do_arglist(eap->arg, AL_ADD, i) == FAIL) { + return; } + maketitle(); - alist_check_arg_idx(); - + if (curwin->w_arg_idx == 0 && (curbuf->b_ml.ml_flags & ML_EMPTY) + && curbuf->b_ffname == NULL) { + i = 0; + } // Edit the argument. - do_argfile(eap, i); + if (i < ARGCOUNT) { + do_argfile(eap, i); + } } /// ":argadd" @@ -1959,8 +1956,14 @@ void ex_argdelete(exarg_T *eap) eap->line2 = ARGCOUNT; } linenr_T n = eap->line2 - eap->line1 + 1; - if (*eap->arg != NUL || n <= 0) { + if (*eap->arg != NUL) { + // Can't have both a range and an argument. EMSG(_(e_invarg)); + } else if (n <= 0) { + // Don't give an error for ":%argdel" if the list is empty. + if (eap->line1 != 1 || eap->line2 != 0) { + EMSG(_(e_invrange)); + } } else { for (linenr_T i = eap->line1; i <= eap->line2; i++) { xfree(ARGLIST[i - 1].ae_fname); @@ -2005,7 +2008,7 @@ void ex_listdo(exarg_T *eap) if (eap->cmdidx == CMD_windo || eap->cmdidx == CMD_tabdo - || P_HID(curbuf) + || buf_hide(curbuf) || !check_changed(curbuf, CCGD_AW | (eap->forceit ? CCGD_FORCEIT : 0) | CCGD_EXCMD)) { @@ -3758,7 +3761,7 @@ static void script_host_execute(char *name, exarg_T *eap) char *const script = script_get(eap, &len); if (script != NULL) { - list_T *const args = tv_list_alloc(); + list_T *const args = tv_list_alloc(3); // script tv_list_append_allocated_string(args, script); // current range @@ -3773,7 +3776,7 @@ static void script_host_execute_file(char *name, exarg_T *eap) uint8_t buffer[MAXPATHL]; vim_FullName((char *)eap->arg, (char *)buffer, sizeof(buffer), false); - list_T *args = tv_list_alloc(); + list_T *args = tv_list_alloc(3); // filename tv_list_append_string(args, (const char *)buffer, -1); // current range @@ -3784,7 +3787,7 @@ static void script_host_execute_file(char *name, exarg_T *eap) static void script_host_do_range(char *name, exarg_T *eap) { - list_T *args = tv_list_alloc(); + list_T *args = tv_list_alloc(3); 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); @@ -3837,7 +3840,7 @@ void ex_drop(exarg_T *eap) // to split the current window or data could be lost. // Skip the check if the 'hidden' option is set, as in this case the // buffer won't be lost. - if (!P_HID(curbuf)) { + if (!buf_hide(curbuf)) { emsg_off++; split = check_changed(curbuf, CCGD_AW | CCGD_EXCMD); emsg_off--; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index e11788531b..93cb0e50fa 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -133,7 +133,6 @@ struct dbg_stuff { char_u *vv_throwpoint; int did_emsg; int got_int; - int did_throw; int need_rethrow; int check_cstack; except_T *current_exception; @@ -165,12 +164,11 @@ static void save_dbg_stuff(struct dbg_stuff *dsp) dsp->vv_exception = v_exception(NULL); dsp->vv_throwpoint = v_throwpoint(NULL); - /* Necessary for debugging an inactive ":catch", ":finally", ":endtry" */ - dsp->did_emsg = did_emsg; did_emsg = FALSE; - dsp->got_int = got_int; got_int = FALSE; - dsp->did_throw = did_throw; did_throw = FALSE; - dsp->need_rethrow = need_rethrow; need_rethrow = FALSE; - dsp->check_cstack = check_cstack; check_cstack = FALSE; + // Necessary for debugging an inactive ":catch", ":finally", ":endtry". + dsp->did_emsg = did_emsg; did_emsg = false; + dsp->got_int = got_int; got_int = false; + dsp->need_rethrow = need_rethrow; need_rethrow = false; + dsp->check_cstack = check_cstack; check_cstack = false; dsp->current_exception = current_exception; current_exception = NULL; } @@ -184,7 +182,6 @@ static void restore_dbg_stuff(struct dbg_stuff *dsp) (void)v_throwpoint(dsp->vv_throwpoint); did_emsg = dsp->did_emsg; got_int = dsp->got_int; - did_throw = dsp->did_throw; need_rethrow = dsp->need_rethrow; check_cstack = dsp->check_cstack; current_exception = dsp->current_exception; @@ -400,16 +397,11 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, initial_trylevel = trylevel; - /* - * "did_throw" will be set to TRUE when an exception is being thrown. - */ - did_throw = FALSE; - /* - * "did_emsg" will be set to TRUE when emsg() is used, in which case we - * cancel the whole command line, and any if/endif or loop. - * If force_abort is set, we cancel everything. - */ - did_emsg = FALSE; + current_exception = NULL; + // "did_emsg" will be set to TRUE when emsg() is used, in which case we + // cancel the whole command line, and any if/endif or loop. + // If force_abort is set, we cancel everything. + did_emsg = false; /* * KeyTyped is only set when calling vgetc(). Reset it here when not @@ -659,7 +651,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, * not to use a cs_line[] from an entry that isn't a ":while" * or ":for": It would make "current_line" invalid and can * cause a crash. */ - if (!did_emsg && !got_int && !did_throw + if (!did_emsg && !got_int && !current_exception && cstack.cs_idx >= 0 && (cstack.cs_flags[cstack.cs_idx] & (CSF_WHILE | CSF_FOR)) @@ -707,7 +699,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, } /* - * A ":finally" makes did_emsg, got_int, and did_throw pending for + * A ":finally" makes did_emsg, got_int and current_exception pending for * being restored at the ":endtry". Reset them here and set the * ACTIVE and FINALLY flags, so that the finally clause gets executed. * This includes the case where a missing ":endif", ":endwhile" or @@ -715,10 +707,11 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, */ if (cstack.cs_lflags & CSL_HAD_FINA) { cstack.cs_lflags &= ~CSL_HAD_FINA; - report_make_pending(cstack.cs_pending[cstack.cs_idx] - & (CSTP_ERROR | CSTP_INTERRUPT | CSTP_THROW), - did_throw ? (void *)current_exception : NULL); - did_emsg = got_int = did_throw = FALSE; + report_make_pending((cstack.cs_pending[cstack.cs_idx] + & (CSTP_ERROR | CSTP_INTERRUPT | CSTP_THROW)), + current_exception); + did_emsg = got_int = false; + current_exception = NULL; cstack.cs_flags[cstack.cs_idx] |= CSF_ACTIVE | CSF_FINALLY; } @@ -726,15 +719,14 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, * within this loop. */ trylevel = initial_trylevel + cstack.cs_trylevel; - /* - * If the outermost try conditional (across function calls and sourced - * files) is aborted because of an error, an interrupt, or an uncaught - * exception, cancel everything. If it is left normally, reset - * force_abort to get the non-EH compatible abortion behavior for - * the rest of the script. - */ - if (trylevel == 0 && !did_emsg && !got_int && !did_throw) - force_abort = FALSE; + // If the outermost try conditional (across function calls and sourced + // files) is aborted because of an error, an interrupt, or an uncaught + // exception, cancel everything. If it is left normally, reset + // force_abort to get the non-EH compatible abortion behavior for + // the rest of the script. + if (trylevel == 0 && !did_emsg && !got_int && !current_exception) { + force_abort = false; + } /* Convert an interrupt to an exception if appropriate. */ (void)do_intthrow(&cstack); @@ -749,11 +741,8 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, * - there is a command after '|', inside a :if, :while, :for or :try, or * looping for ":source" command or function call. */ - while (!((got_int - || (did_emsg && force_abort) || did_throw - ) - && cstack.cs_trylevel == 0 - ) + while (!((got_int || (did_emsg && force_abort) || current_exception) + && cstack.cs_trylevel == 0) && !(did_emsg /* Keep going when inside try/catch, so that the error can be * deal with, except when it is a syntax error, it may cause @@ -775,7 +764,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, * If a sourced file or executed function ran to its end, report the * unclosed conditional. */ - if (!got_int && !did_throw + if (!got_int && !current_exception && ((getline_equal(fgetline, cookie, getsourceline) && !source_finished(fgetline, cookie)) || (getline_equal(fgetline, cookie, get_func_line) @@ -815,17 +804,16 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, ? (char_u *)"endfunction" : (char_u *)NULL); if (trylevel == 0) { - /* - * When an exception is being thrown out of the outermost try - * conditional, discard the uncaught exception, disable the conversion - * of interrupts or errors to exceptions, and ensure that no more - * commands are executed. - */ - if (did_throw) { - void *p = NULL; - char_u *saved_sourcing_name; + // When an exception is being thrown out of the outermost try + // conditional, discard the uncaught exception, disable the conversion + // of interrupts or errors to exceptions, and ensure that no more + // commands are executed. + if (current_exception) { + void *p = NULL; + char_u *saved_sourcing_name; int saved_sourcing_lnum; - struct msglist *messages = NULL, *next; + struct msglist *messages = NULL; + struct msglist *next; /* * If the uncaught exception is a user exception, report it as an @@ -885,22 +873,22 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, suppress_errthrow = TRUE; } - /* - * The current cstack will be freed when do_cmdline() returns. An uncaught - * exception will have to be rethrown in the previous cstack. If a function - * has just returned or a script file was just finished and the previous - * cstack belongs to the same function or, respectively, script file, it - * will have to be checked for finally clauses to be executed due to the - * ":return" or ":finish". This is done in do_one_cmd(). - */ - if (did_throw) - need_rethrow = TRUE; + // The current cstack will be freed when do_cmdline() returns. An uncaught + // exception will have to be rethrown in the previous cstack. If a function + // has just returned or a script file was just finished and the previous + // cstack belongs to the same function or, respectively, script file, it + // will have to be checked for finally clauses to be executed due to the + // ":return" or ":finish". This is done in do_one_cmd(). + if (current_exception) { + need_rethrow = true; + } if ((getline_equal(fgetline, cookie, getsourceline) && ex_nesting_level > source_level(real_cookie)) || (getline_equal(fgetline, cookie, get_func_line) && ex_nesting_level > func_level(real_cookie) + 1)) { - if (!did_throw) - check_cstack = TRUE; + if (!current_exception) { + check_cstack = true; + } } else { /* When leaving a function, reduce nesting level. */ if (getline_equal(fgetline, cookie, get_func_line)) @@ -1480,10 +1468,11 @@ static char_u * do_one_cmd(char_u **cmdlinep, } char_u *after_modifier = ea.cmd; - ea.skip = did_emsg || got_int || did_throw || (cstack->cs_idx >= 0 - && !(cstack->cs_flags[cstack-> - cs_idx] - & CSF_ACTIVE)); + ea.skip = (did_emsg + || got_int + || current_exception + || (cstack->cs_idx >= 0 + && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE))); /* Count this line for profiling if ea.skip is FALSE. */ if (do_profiling == PROF_YES && !ea.skip) { @@ -1782,13 +1771,14 @@ static char_u * do_one_cmd(char_u **cmdlinep, )); - /* forced commands */ + // Forced commands. if (*p == '!' && ea.cmdidx != CMD_substitute && ea.cmdidx != CMD_smagic && ea.cmdidx != CMD_snomagic) { - ++p; - ea.forceit = TRUE; - } else - ea.forceit = FALSE; + p++; + ea.forceit = true; + } else { + ea.forceit = false; + } /* * 6. Parse arguments. @@ -3442,6 +3432,10 @@ const char * set_one_cmd_context( case CMD_profile: set_context_in_profile_cmd(xp, arg); break; + case CMD_checkhealth: + xp->xp_context = EXPAND_CHECKHEALTH; + xp->xp_pattern = (char_u *)arg; + break; case CMD_behave: xp->xp_context = EXPAND_BEHAVE; xp->xp_pattern = (char_u *)arg; @@ -4871,6 +4865,7 @@ static struct { { EXPAND_AUGROUP, "augroup" }, { EXPAND_BEHAVE, "behave" }, { EXPAND_BUFFERS, "buffer" }, + { EXPAND_CHECKHEALTH, "checkhealth" }, { EXPAND_COLORS, "color" }, { EXPAND_COMMANDS, "command" }, { EXPAND_COMPILER, "compiler" }, @@ -5925,9 +5920,10 @@ static void ex_colorscheme(exarg_T *eap) static void ex_highlight(exarg_T *eap) { - if (*eap->arg == NUL && eap->cmd[2] == '!') + if (*eap->arg == NUL && eap->cmd[2] == '!') { MSG(_("Greetings, Vim user!")); - do_highlight(eap->arg, eap->forceit, FALSE); + } + do_highlight((const char *)eap->arg, eap->forceit, false); } @@ -5968,22 +5964,24 @@ static void ex_quit(exarg_T *eap) wp = curwin; } - apply_autocmds(EVENT_QUITPRE, NULL, NULL, false, curbuf); + // Refuse to quit when locked. + if (curbuf_locked()) { + return; + } + apply_autocmds(EVENT_QUITPRE, NULL, NULL, false, wp->w_buffer); // Refuse to quit when locked or when the buffer in the last window is // being closed (can only happen in autocommands). - if (curbuf_locked() + if (!win_valid(wp) || (wp->w_buffer->b_nwindows == 1 && wp->w_buffer->b_locked > 0)) { return; } - - /* - * If there are more files or windows we won't exit. - */ - if (check_more(FALSE, eap->forceit) == OK && only_one_window()) - exiting = TRUE; - if ((!P_HID(curbuf) - && check_changed(curbuf, (p_awa ? CCGD_AW : 0) + // If there are more files or windows we won't exit. + if (check_more(false, eap->forceit) == OK && only_one_window()) { + exiting = true; + } + if ((!buf_hide(wp->w_buffer) + && check_changed(wp->w_buffer, (p_awa ? CCGD_AW : 0) | (eap->forceit ? CCGD_FORCEIT : 0) | CCGD_EXCMD)) || check_more(true, eap->forceit) == FAIL @@ -5999,8 +5997,8 @@ static void ex_quit(exarg_T *eap) if (only_one_window() && (ONE_WINDOW || eap->addr_count == 0)) { getout(0); } - /* close window; may free buffer */ - win_close(wp, !P_HID(wp->w_buffer) || eap->forceit); + // close window; may free buffer + win_close(wp, !buf_hide(wp->w_buffer) || eap->forceit); } } @@ -6099,7 +6097,7 @@ ex_win_close ( buf_T *buf = win->w_buffer; need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1); - if (need_hide && !P_HID(buf) && !forceit) { + if (need_hide && !buf_hide(buf) && !forceit) { if ((p_confirm || cmdmod.confirm) && p_write) { bufref_T bufref; set_bufref(&bufref, buf); @@ -6115,11 +6113,12 @@ ex_win_close ( } - /* free buffer when not hiding it or when it's a scratch buffer */ - if (tp == NULL) - win_close(win, !need_hide && !P_HID(buf)); - else - win_close_othertab(win, !need_hide && !P_HID(buf), tp); + // free buffer when not hiding it or when it's a scratch buffer + if (tp == NULL) { + win_close(win, !need_hide && !buf_hide(buf)); + } else { + win_close_othertab(win, !need_hide && !buf_hide(buf), tp); + } } /* @@ -6229,7 +6228,6 @@ void tabpage_close_other(tabpage_T *tp, int forceit) if (!valid_tabpage(tp) || tp->tp_firstwin == wp) break; } - apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, FALSE, curbuf); redraw_tabline = TRUE; if (h != tabline_height()) @@ -6351,7 +6349,7 @@ static void ex_exit(exarg_T *eap) getout(0); } // Quit current window, may free the buffer. - win_close(curwin, !P_HID(curwin->w_buffer)); + win_close(curwin, !buf_hide(curwin->w_buffer)); } } @@ -6921,24 +6919,23 @@ do_exedit ( empty buffer */ setpcmark(); if (do_ecmd(0, (eap->cmdidx == CMD_enew ? NULL : eap->arg), - NULL, eap, - eap->do_ecmd_lnum, - (P_HID(curbuf) ? ECMD_HIDE : 0) - + (eap->forceit ? ECMD_FORCEIT : 0) - // After a split we can use an existing buffer. - + (old_curwin != NULL ? ECMD_OLDBUF : 0) - + (eap->cmdidx == CMD_badd ? ECMD_ADDBUF : 0 ) - , old_curwin == NULL ? curwin : NULL) == FAIL) { - /* Editing the file failed. If the window was split, close it. */ + NULL, eap, eap->do_ecmd_lnum, + (buf_hide(curbuf) ? ECMD_HIDE : 0) + + (eap->forceit ? ECMD_FORCEIT : 0) + // After a split we can use an existing buffer. + + (old_curwin != NULL ? ECMD_OLDBUF : 0) + + (eap->cmdidx == CMD_badd ? ECMD_ADDBUF : 0) + , old_curwin == NULL ? curwin : NULL) == FAIL) { + // Editing the file failed. If the window was split, close it. if (old_curwin != NULL) { need_hide = (curbufIsChanged() && curbuf->b_nwindows <= 1); - if (!need_hide || P_HID(curbuf)) { + if (!need_hide || buf_hide(curbuf)) { cleanup_T cs; /* Reset the error/interrupt/exception state here so that * aborting() returns FALSE when closing a window. */ enter_cleanup(&cs); - win_close(curwin, !need_hide && !P_HID(curbuf)); + win_close(curwin, !need_hide && !buf_hide(curbuf)); /* Restore the error/interrupt/exception state if not * discarded by a new aborting error, interrupt, or @@ -6978,12 +6975,10 @@ do_exedit ( ex_no_reprint = TRUE; } -/* - * ":gui" and ":gvim" when there is no GUI. - */ +/// ":gui" and ":gvim" when there is no GUI. static void ex_nogui(exarg_T *eap) { - eap->errmsg = e_nogvim; + eap->errmsg = (char_u *)N_("E25: Nvim does not have a built-in GUI"); } @@ -8149,6 +8144,10 @@ static void ex_startinsert(exarg_T *eap) restart_edit = 'i'; curwin->w_curswant = 0; /* avoid MAXCOL */ } + + if (VIsual_active) { + showmode(); + } } /* @@ -8539,22 +8538,22 @@ eval_vars ( resultbuf = result; /* remember allocated string */ break; - case SPEC_AFILE: /* file name for autocommand */ - result = autocmd_fname; - if (result != NULL && !autocmd_fname_full) { - /* Still need to turn the fname into a full path. It is - * postponed to avoid a delay when <afile> is not used. */ - autocmd_fname_full = TRUE; - result = (char_u *)FullName_save((char *)autocmd_fname, FALSE); - xfree(autocmd_fname); - autocmd_fname = result; + case SPEC_AFILE: // file name for autocommand + if (autocmd_fname != NULL && !path_is_absolute(autocmd_fname)) { + // Still need to turn the fname into a full path. It was + // postponed to avoid a delay when <afile> is not used. + result = (char_u *)FullName_save((char *)autocmd_fname, false); + // Copy into `autocmd_fname`, don't reassign it. #8165 + xstrlcpy((char *)autocmd_fname, (char *)result, MAXPATHL); + xfree(result); } + result = autocmd_fname; if (result == NULL) { *errormsg = (char_u *)_( "E495: no autocommand file name to substitute for \"<afile>\""); return NULL; } - result = path_shorten_fname_if_possible(result); + result = path_try_shorten_fname(result); break; case SPEC_ABUF: /* buffer number for autocommand */ @@ -9718,29 +9717,37 @@ static void ex_filetype(exarg_T *eap) EMSG2(_(e_invarg2), arg); } -/// Do ":filetype plugin indent on" if user did not already do some -/// permutation thereof. +/// Set all :filetype options ON if user did not explicitly set any to OFF. void filetype_maybe_enable(void) { - if (filetype_detect == kNone - && filetype_plugin == kNone - && filetype_indent == kNone) { + if (filetype_detect == kNone) { source_runtime((char_u *)FILETYPE_FILE, true); filetype_detect = kTrue; + } + if (filetype_plugin == kNone) { source_runtime((char_u *)FTPLUGIN_FILE, true); filetype_plugin = kTrue; + } + if (filetype_indent == kNone) { source_runtime((char_u *)INDENT_FILE, true); filetype_indent = kTrue; } } -/* - * ":setfiletype {name}" - */ +/// ":setfiletype [FALLBACK] {name}" static void ex_setfiletype(exarg_T *eap) { if (!did_filetype) { - set_option_value("filetype", 0L, (char *)eap->arg, OPT_LOCAL); + char_u *arg = eap->arg; + + if (STRNCMP(arg, "FALLBACK ", 9) == 0) { + arg += 9; + } + + set_option_value("filetype", 0L, (char *)arg, OPT_LOCAL); + if (arg != eap->arg) { + did_filetype = false; + } } } diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index 9037b3c151..e23945c842 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -45,22 +45,21 @@ * there or even outside the try conditional. Try conditionals may be nested. */ -/* - * Configuration whether an exception is thrown on error or interrupt. When - * the preprocessor macros below evaluate to FALSE, an error (did_emsg) or - * interrupt (got_int) under an active try conditional terminates the script - * after the non-active finally clauses of all active try conditionals have been - * executed. Otherwise, errors and/or interrupts are converted into catchable - * exceptions (did_throw additionally set), which terminate the script only if - * not caught. For user exceptions, only did_throw is set. (Note: got_int can - * be set asynchronously afterwards by a SIGINT, so did_throw && got_int is not - * a reliant test that the exception currently being thrown is an interrupt - * exception. Similarly, did_emsg can be set afterwards on an error in an - * (unskipped) conditional command inside an inactive conditional, so did_throw - * && did_emsg is not a reliant test that the exception currently being thrown - * is an error exception.) - The macros can be defined as expressions checking - * for a variable that is allowed to be changed during execution of a script. - */ +// Configuration whether an exception is thrown on error or interrupt. When +// the preprocessor macros below evaluate to FALSE, an error (did_emsg) or +// interrupt (got_int) under an active try conditional terminates the script +// after the non-active finally clauses of all active try conditionals have been +// executed. Otherwise, errors and/or interrupts are converted into catchable +// exceptions, which terminate the script only if not caught. For user +// exceptions, only current_exception is set. (Note: got_int can be set +// asynchronously afterwards by a SIGINT, so current_exception && got_int is not +// a reliant test that the exception currently being thrown is an interrupt +// exception. Similarly, did_emsg can be set afterwards on an error in an +// (unskipped) conditional command inside an inactive conditional, so +// current_exception && did_emsg is not a reliant test that the exception +// currently being thrown is an error exception.) - The macros can be defined +// as expressions checking for a variable that is allowed to be changed during +// execution of a script. // Values used for the Vim release. #define THROW_ON_ERROR true @@ -68,6 +67,15 @@ #define THROW_ON_INTERRUPT true #define THROW_ON_INTERRUPT_TRUE +// Don't do something after an error, interrupt, or throw, or when +// there is a surrounding conditional and it was not active. +#define CHECK_SKIP \ + (did_emsg \ + || got_int \ + || current_exception \ + || (cstack->cs_idx > 0 \ + && !(cstack->cs_flags[cstack->cs_idx - 1] & CSF_ACTIVE))) + #define discard_pending_return(p) tv_free((typval_T *)(p)) /* @@ -94,7 +102,7 @@ static int cause_abort = FALSE; */ int aborting(void) { - return (did_emsg && force_abort) || got_int || did_throw; + return (did_emsg && force_abort) || got_int || current_exception; } /* @@ -178,8 +186,9 @@ int cause_errthrow(char_u *mesg, int severe, int *ignore) * currently throwing an exception, do nothing. The message text will * then be stored to v:errmsg by emsg() without displaying it. */ - if (((trylevel == 0 && !cause_abort) || emsg_silent) && !did_throw) - return FALSE; + if (((trylevel == 0 && !cause_abort) || emsg_silent) && !current_exception) { + return false; + } /* * Ignore an interrupt message when inside a try conditional or when an @@ -208,12 +217,13 @@ int cause_errthrow(char_u *mesg, int severe, int *ignore) * exception currently being thrown to prevent it from being caught. Just * execute finally clauses and terminate. */ - if (did_throw) { - /* When discarding an interrupt exception, reset got_int to prevent the - * same interrupt being converted to an exception again and discarding - * the error exception we are about to throw here. */ - if (current_exception->type == ET_INTERRUPT) - got_int = FALSE; + if (current_exception) { + // When discarding an interrupt exception, reset got_int to prevent the + // same interrupt being converted to an exception again and discarding + // the error exception we are about to throw here. + if (current_exception->type == ET_INTERRUPT) { + got_int = false; + } discard_current_exception(); } @@ -333,45 +343,44 @@ void do_errthrow(struct condstack *cstack, char_u *cmdname) */ int do_intthrow(struct condstack *cstack) { - /* - * If no interrupt occurred or no try conditional is active and no exception - * is being thrown, do nothing (for compatibility of non-EH scripts). - */ - if (!got_int || (trylevel == 0 && !did_throw)) - return FALSE; + // If no interrupt occurred or no try conditional is active and no exception + // is being thrown, do nothing (for compatibility of non-EH scripts). + if (!got_int || (trylevel == 0 && !current_exception)) { + return false; + } -#ifdef THROW_TEST /* avoid warning for condition always true */ +#ifdef THROW_TEST // avoid warning for condition always true if (!THROW_ON_INTERRUPT) { - /* - * The interrupt aborts everything except for executing finally clauses. - * Discard any user or error or interrupt exception currently being - * thrown. - */ - if (did_throw) + // The interrupt aborts everything except for executing finally clauses. + // Discard any user or error or interrupt exception currently being + // thrown. + if (current_exception) { discard_current_exception(); - } else + } + } else { #endif - { - /* - * Throw an interrupt exception, so that everything will be aborted - * (except for executing finally clauses), until the interrupt exception - * is caught; if still uncaught at the top level, the script processing - * will be terminated then. - If an interrupt exception is already - * being thrown, do nothing. - * - */ - if (did_throw) { - if (current_exception->type == ET_INTERRUPT) - return FALSE; + // Throw an interrupt exception, so that everything will be aborted + // (except for executing finally clauses), until the interrupt exception + // is caught; if still uncaught at the top level, the script processing + // will be terminated then. - If an interrupt exception is already + // being thrown, do nothing. + + if (current_exception) { + if (current_exception->type == ET_INTERRUPT) { + return false; + } - /* An interrupt exception replaces any user or error exception. */ + // An interrupt exception replaces any user or error exception. discard_current_exception(); } - if (throw_exception("Vim:Interrupt", ET_INTERRUPT, NULL) != FAIL) + if (throw_exception("Vim:Interrupt", ET_INTERRUPT, NULL) != FAIL) { do_throw(cstack); + } +#ifdef THROW_TEST } +#endif - return TRUE; + return true; } // Get an exception message that is to be stored in current_exception->value. @@ -517,7 +526,7 @@ static void discard_exception(except_T *excp, int was_finished) char_u *saved_IObuff; if (excp == NULL) { - EMSG(_(e_internal)); + internal_error("discard_exception()"); return; } @@ -565,8 +574,7 @@ void discard_current_exception(void) // Note: all globals manipulated here should be saved/restored in // try_enter/try_leave. current_exception = NULL; - did_throw = FALSE; - need_rethrow = FALSE; + need_rethrow = false; } /* @@ -619,8 +627,9 @@ static void catch_exception(except_T *excp) */ static void finish_exception(except_T *excp) { - if (excp != caught_stack) - EMSG(_(e_internal)); + if (excp != caught_stack) { + internal_error("finish_exception()"); + } caught_stack = caught_stack->caught; if (caught_stack != NULL) { set_vim_var_string(VV_EXCEPTION, (char *) caught_stack->value, -1); @@ -794,15 +803,7 @@ void ex_if(exarg_T *eap) ++cstack->cs_idx; cstack->cs_flags[cstack->cs_idx] = 0; - /* - * Don't do something after an error, interrupt, or throw, or when there - * is a surrounding conditional and it was not active. - */ - skip = did_emsg || got_int || did_throw || (cstack->cs_idx > 0 - && !(cstack->cs_flags[cstack-> - cs_idx - - 1] & - CSF_ACTIVE)); + skip = CHECK_SKIP; bool error; result = eval_to_bool(eap->arg, &error, &eap->nextcmd, skip); @@ -853,15 +854,7 @@ void ex_else(exarg_T *eap) int result; struct condstack *cstack = eap->cstack; - /* - * Don't do something after an error, interrupt, or throw, or when there is - * a surrounding conditional and it was not active. - */ - skip = did_emsg || got_int || did_throw || (cstack->cs_idx > 0 - && !(cstack->cs_flags[cstack-> - cs_idx - - 1] & - CSF_ACTIVE)); + skip = CHECK_SKIP; if (cstack->cs_idx < 0 || (cstack->cs_flags[cstack->cs_idx] @@ -951,15 +944,7 @@ void ex_while(exarg_T *eap) cstack->cs_flags[cstack->cs_idx] = eap->cmdidx == CMD_while ? CSF_WHILE : CSF_FOR; - /* - * Don't do something after an error, interrupt, or throw, or when - * there is a surrounding conditional and it was not active. - */ - skip = did_emsg || got_int || did_throw || (cstack->cs_idx > 0 - && !(cstack->cs_flags[cstack-> - cs_idx - - 1] & - CSF_ACTIVE)); + skip = CHECK_SKIP; if (eap->cmdidx == CMD_while) { /* * ":while bool-expr" @@ -1232,8 +1217,6 @@ void do_throw(struct condstack *cstack) cstack->cs_flags[idx] &= ~CSF_ACTIVE; cstack->cs_exception[idx] = current_exception; } - - did_throw = TRUE; } /* @@ -1252,15 +1235,7 @@ void ex_try(exarg_T *eap) cstack->cs_flags[cstack->cs_idx] = CSF_TRY; cstack->cs_pending[cstack->cs_idx] = CSTP_NONE; - /* - * Don't do something after an error, interrupt, or throw, or when there - * is a surrounding conditional and it was not active. - */ - skip = did_emsg || got_int || did_throw || (cstack->cs_idx > 0 - && !(cstack->cs_flags[cstack-> - cs_idx - - 1] & - CSF_ACTIVE)); + skip = CHECK_SKIP; if (!skip) { /* Set ACTIVE and TRUE. TRUE means that the corresponding ":catch" @@ -1352,8 +1327,9 @@ void ex_catch(exarg_T *eap) * corresponding try block never got active (because of an inactive * surrounding conditional or after an error or interrupt or throw). */ - if (!did_throw || !(cstack->cs_flags[idx] & CSF_TRUE)) - skip = TRUE; + if (!current_exception || !(cstack->cs_flags[idx] & CSF_TRUE)) { + skip = true; + } /* * Check for a match only if an exception is thrown but not caught by @@ -1412,18 +1388,23 @@ void ex_catch(exarg_T *eap) } if (caught) { - /* Make this ":catch" clause active and reset did_emsg, got_int, - * and did_throw. Put the exception on the caught stack. */ + /* Make this ":catch" clause active and reset did_emsg and got_int. + * Put the exception on the caught stack. */ cstack->cs_flags[idx] |= CSF_ACTIVE | CSF_CAUGHT; - did_emsg = got_int = did_throw = FALSE; + did_emsg = got_int = false; catch_exception((except_T *)cstack->cs_exception[idx]); /* It's mandatory that the current exception is stored in the cstack * so that it can be discarded at the next ":catch", ":finally", or * ":endtry" or when the catch clause is left by a ":continue", * ":break", ":return", ":finish", error, interrupt, or another * exception. */ - if (cstack->cs_exception[cstack->cs_idx] != current_exception) - EMSG(_(e_internal)); + if (cstack->cs_exception[cstack->cs_idx] != current_exception) { + internal_error("ex_catch()"); + } + // Discarding current_exceptions happens based on what is stored in + // cstack->cs_exception, *all* calls to discard_current_exception() are + // (and must be) guarded by current_exception check. + current_exception = NULL; } else { /* * If there is a preceding catch clause and it caught the exception, @@ -1482,7 +1463,7 @@ void ex_finally(exarg_T *eap) * interrupt or throw) or for a ":finally" without ":try" or a multiple * ":finally". After every other error (did_emsg or the conditional * errors detected above) or after an interrupt (got_int) or an - * exception (did_throw), the finally clause must be executed. + * exception (current_exception), the finally clause must be executed. */ skip = !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE); @@ -1509,30 +1490,31 @@ void ex_finally(exarg_T *eap) cleanup_conditionals(cstack, CSF_TRY, FALSE); /* - * Make did_emsg, got_int, did_throw pending. If set, they overrule - * a pending ":continue", ":break", ":return", or ":finish". Then - * we have particularly to discard a pending return value (as done + * Make did_emsg, got_int, current_exception pending. If set, they + * overrule a pending ":continue", ":break", ":return", or ":finish". + * Then we have particularly to discard a pending return value (as done * by the call to cleanup_conditionals() above when did_emsg or * got_int is set). The pending values are restored by the * ":endtry", except if there is a new error, interrupt, exception, * ":continue", ":break", ":return", or ":finish" in the following * finally clause. A missing ":endwhile", ":endfor" or ":endif" - * detected here is treated as if did_emsg and did_throw had + * detected here is treated as if did_emsg and current_exception had * already been set, respectively in case that the error is not - * converted to an exception, did_throw had already been unset. + * converted to an exception, current_exception had already been unset. * We must not set did_emsg here since that would suppress the * error message. */ - if (pending == CSTP_ERROR || did_emsg || got_int || did_throw) { + if (pending == CSTP_ERROR || did_emsg || got_int || current_exception) { if (cstack->cs_pending[cstack->cs_idx] == CSTP_RETURN) { report_discard_pending(CSTP_RETURN, cstack->cs_rettv[cstack->cs_idx]); discard_pending_return(cstack->cs_rettv[cstack->cs_idx]); } - if (pending == CSTP_ERROR && !did_emsg) - pending |= (THROW_ON_ERROR) ? CSTP_THROW : 0; - else - pending |= did_throw ? CSTP_THROW : 0; + if (pending == CSTP_ERROR && !did_emsg) { + pending |= (THROW_ON_ERROR ? CSTP_THROW : 0); + } else { + pending |= (current_exception ? CSTP_THROW : 0); + } pending |= did_emsg ? CSTP_ERROR : 0; pending |= got_int ? CSTP_INTERRUPT : 0; assert(pending >= CHAR_MIN && pending <= CHAR_MAX); @@ -1545,14 +1527,15 @@ void ex_finally(exarg_T *eap) * exception. When emsg() is called for a missing ":endif" or * a missing ":endwhile"/":endfor" detected here, the * exception will be discarded. */ - if (did_throw && cstack->cs_exception[cstack->cs_idx] - != current_exception) - EMSG(_(e_internal)); + if (current_exception + && cstack->cs_exception[cstack->cs_idx] != current_exception) { + internal_error("ex_finally()"); + } } /* * Set CSL_HAD_FINA, so do_cmdline() will reset did_emsg, - * got_int, and did_throw and make the finally clause active. + * got_int, and current_exception and make the finally clause active. * This will happen after emsg() has been called for a missing * ":endif" or a missing ":endwhile"/":endfor" detected here, so * that the following finally clause will be executed even then. @@ -1587,7 +1570,7 @@ void ex_endtry(exarg_T *eap) // made inactive by a ":continue", ":break", ":return", or ":finish" in // the finally clause. The latter case need not be tested since then // anything pending has already been discarded. - skip = (did_emsg || got_int || did_throw + skip = (did_emsg || got_int || current_exception || !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE)); if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { @@ -1604,12 +1587,13 @@ void ex_endtry(exarg_T *eap) /* * If an exception is being thrown, discard it to prevent it from * being rethrown at the end of this function. It would be - * discarded by the error message, anyway. Resets did_throw. + * discarded by the error message, anyway. Resets current_exception. * This does not affect the script termination due to the error * since "trylevel" is decremented after emsg() has been called. */ - if (did_throw) + if (current_exception) { discard_current_exception(); + } } else { idx = cstack->cs_idx; @@ -1619,9 +1603,11 @@ void ex_endtry(exarg_T *eap) * a finally clause, we need to rethrow it after closing the try * conditional. */ - if (did_throw && (cstack->cs_flags[idx] & CSF_TRUE) - && !(cstack->cs_flags[idx] & CSF_FINALLY)) - rethrow = TRUE; + if (current_exception + && (cstack->cs_flags[idx] & CSF_TRUE) + && !(cstack->cs_flags[idx] & CSF_FINALLY)) { + rethrow = true; + } } /* If there was no finally clause, show the user when debugging or @@ -1642,11 +1628,12 @@ void ex_endtry(exarg_T *eap) if (got_int) { skip = TRUE; (void)do_intthrow(cstack); - /* The do_intthrow() call may have reset did_throw or - * cstack->cs_pending[idx].*/ - rethrow = FALSE; - if (did_throw && !(cstack->cs_flags[idx] & CSF_FINALLY)) - rethrow = TRUE; + // The do_intthrow() call may have reset current_exception or + // cstack->cs_pending[idx]. + rethrow = false; + if (current_exception && !(cstack->cs_flags[idx] & CSF_FINALLY)) { + rethrow = true; + } } } @@ -1708,26 +1695,30 @@ void ex_endtry(exarg_T *eap) do_finish(eap, FALSE); break; - /* When the finally clause was entered due to an error, - * interrupt or throw (as opposed to a ":continue", ":break", - * ":return", or ":finish"), restore the pending values of - * did_emsg, got_int, and did_throw. This is skipped, if there - * was a new error, interrupt, throw, ":continue", ":break", - * ":return", or ":finish". in the finally clause. */ + // When the finally clause was entered due to an error, + // interrupt or throw (as opposed to a ":continue", ":break", + // ":return", or ":finish"), restore the pending values of + // did_emsg, got_int, and current_exception. This is skipped, if there + // was a new error, interrupt, throw, ":continue", ":break", + // ":return", or ":finish". in the finally clause. default: - if (pending & CSTP_ERROR) - did_emsg = TRUE; - if (pending & CSTP_INTERRUPT) - got_int = TRUE; - if (pending & CSTP_THROW) - rethrow = TRUE; + if (pending & CSTP_ERROR) { + did_emsg = true; + } + if (pending & CSTP_INTERRUPT) { + got_int = true; + } + if (pending & CSTP_THROW) { + rethrow = true; + } break; } } - if (rethrow) - /* Rethrow the current exception (within this cstack). */ + if (rethrow) { + // Rethrow the current exception (within this cstack). do_throw(cstack); + } } } @@ -1757,33 +1748,34 @@ void enter_cleanup(cleanup_T *csp) int pending = CSTP_NONE; /* - * Postpone did_emsg, got_int, did_throw. The pending values will be + * Postpone did_emsg, got_int, current_exception. The pending values will be * restored by leave_cleanup() except if there was an aborting error, * interrupt, or uncaught exception after this function ends. */ - if (did_emsg || got_int || did_throw || need_rethrow) { - csp->pending = (did_emsg ? CSTP_ERROR : 0) - | (got_int ? CSTP_INTERRUPT : 0) - | (did_throw ? CSTP_THROW : 0) - | (need_rethrow ? CSTP_THROW : 0); - - /* If we are currently throwing an exception (did_throw), save it as - * well. On an error not yet converted to an exception, update - * "force_abort" and reset "cause_abort" (as do_errthrow() would do). - * This is needed for the do_cmdline() call that is going to be made - * for autocommand execution. We need not save *msg_list because - * there is an extra instance for every call of do_cmdline(), anyway. + if (did_emsg || got_int || current_exception || need_rethrow) { + csp->pending = (did_emsg ? CSTP_ERROR : 0) + | (got_int ? CSTP_INTERRUPT : 0) + | (current_exception ? CSTP_THROW : 0) + | (need_rethrow ? CSTP_THROW : 0); + + /* If we are currently throwing an exception, save it as well. On an error + * not yet converted to an exception, update "force_abort" and reset + * "cause_abort" (as do_errthrow() would do). This is needed for the + * do_cmdline() call that is going to be made for autocommand execution. We + * need not save *msg_list because there is an extra instance for every call + * of do_cmdline(), anyway. */ - if (did_throw || need_rethrow) + if (current_exception || need_rethrow) { csp->exception = current_exception; - else { + } else { csp->exception = NULL; if (did_emsg) { force_abort |= cause_abort; cause_abort = FALSE; } } - did_emsg = got_int = did_throw = need_rethrow = FALSE; + did_emsg = got_int = need_rethrow = false; + current_exception = NULL; /* Report if required by the 'verbose' option or when debugging. */ report_make_pending(pending, csp->exception); @@ -1855,19 +1847,20 @@ void leave_cleanup(cleanup_T *csp) force_abort = FALSE; } - /* - * Restore the pending values of did_emsg, got_int, and did_throw. - */ - if (pending & CSTP_ERROR) - did_emsg = TRUE; - if (pending & CSTP_INTERRUPT) - got_int = TRUE; - if (pending & CSTP_THROW) - need_rethrow = TRUE; /* did_throw will be set by do_one_cmd() */ + // Restore the pending values of did_emsg, got_int, and current_exception. + if (pending & CSTP_ERROR) { + did_emsg = true; + } + if (pending & CSTP_INTERRUPT) { + got_int = true; + } + if (pending & CSTP_THROW) { + need_rethrow = true; // current_exception will be set by do_one_cmd() + } - /* Report if required by the 'verbose' option or when debugging. */ - report_resume_pending(pending, - (pending & CSTP_THROW) ? (void *)current_exception : NULL); + // Report if required by the 'verbose' option or when debugging. + report_resume_pending( + pending, ((pending & CSTP_THROW) ? (void *)current_exception : NULL)); } } diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index c1500e3121..698419405a 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -66,6 +66,8 @@ #include "nvim/lib/kvec.h" #include "nvim/api/private/helpers.h" #include "nvim/highlight_defs.h" +#include "nvim/viml/parser/parser.h" +#include "nvim/viml/parser/expressions.h" /// Command-line colors: one chunk /// @@ -192,7 +194,8 @@ static int cmd_showtail; /* Only show path tail in lists ? */ static int new_cmdpos; /* position set by set_cmdline_pos() */ -static Array cmdline_block; ///< currently displayed block of context +/// currently displayed block of context +static Array cmdline_block = ARRAY_DICT_INIT; /* * Type used by call_user_expand_func @@ -424,7 +427,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) curwin->w_botline = s->old_botline; highlight_match = false; validate_cursor(); // needed for TAB - redraw_later(SOME_VALID); + redraw_all_later(SOME_VALID); } if (ccline.cmdbuff != NULL) { @@ -441,14 +444,8 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) } } - if (s->gotesc) { // abandon command line - xfree(ccline.cmdbuff); - ccline.cmdbuff = NULL; - if (msg_scrolled == 0) { - compute_cmdrow(); - } - MSG(""); - redraw_cmdline = true; + if (s->gotesc) { + abandon_cmdline(); } } @@ -515,8 +512,12 @@ static int command_line_execute(VimState *state, int key) CommandLineState *s = (CommandLineState *)state; s->c = key; - if (s->c == K_EVENT) { - multiqueue_process_events(main_loop.events); + if (s->c == K_EVENT || s->c == K_COMMAND) { + if (s->c == K_EVENT) { + multiqueue_process_events(main_loop.events); + } else { + do_cmdline(NULL, getcmdkeycmd, NULL, DOCMD_NOWAIT); + } redrawcmdline(); return 1; } @@ -736,7 +737,7 @@ static int command_line_execute(VimState *state, int key) } if (vim_ispathsep(ccline.cmdbuff[s->j]) #ifdef BACKSLASH_IN_FILENAME - && vim_strchr(" *?[{`$%#", ccline.cmdbuff[s->j + 1]) + && vim_strchr((const char_u *)" *?[{`$%#", ccline.cmdbuff[s->j + 1]) == NULL #endif ) { @@ -1017,17 +1018,36 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match) ui_flush(); pos_T t; - int search_flags = SEARCH_KEEP + SEARCH_NOOF + SEARCH_PEEK; + char_u *pat; + int search_flags = SEARCH_NOOF; + + + if (s->firstc == ccline.cmdbuff[0]) { + pat = last_search_pattern(); + } else { + pat = ccline.cmdbuff; + } + + save_last_search_pattern(); + if (next_match) { t = s->match_end; + if (lt(s->match_start, s->match_end)) { + // start searching at the end of the match + // not at the beginning of the next column + (void)decl(&t); + } search_flags += SEARCH_COL; } else { t = s->match_start; } + if (!p_hls) { + search_flags += SEARCH_KEEP; + } emsg_off++; s->i = searchit(curwin, curbuf, &t, next_match ? FORWARD : BACKWARD, - ccline.cmdbuff, s->count, search_flags, + pat, s->count, search_flags, RE_SEARCH, 0, NULL); emsg_off--; ui_busy_stop(); @@ -1041,6 +1061,12 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match) // put back on the match s->search_start = t; (void)decl(&s->search_start); + } else if (next_match && s->firstc == '?') { + // move just after the current match, so that + // when nv_search finishes the cursor will be + // put back on the match + s->search_start = t; + (void)incl(&s->search_start); } if (lt(t, s->search_start) && next_match) { // wrap around @@ -1068,6 +1094,7 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match) } else { vim_beep(BO_ERROR); } + restore_last_search_pattern(); return; } @@ -1527,7 +1554,7 @@ static int command_line_handle_key(CommandLineState *s) } if (s->c != NUL) { if (s->c == s->firstc - || vim_strchr((char_u *)(p_magic ? "\\^$.*[" : "\\^$"), s->c) + || vim_strchr((char_u *)(p_magic ? "\\~^$.*[" : "\\^$"), s->c) != NULL) { // put a backslash before special characters stuffcharReadbuff(s->c); @@ -1651,10 +1678,9 @@ 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 (char_avail()) { - return 1; + if (ccline.cmdlen != 0) { + command_line_next_incsearch(s, s->c == Ctrl_G); } - command_line_next_incsearch(s, s->c == Ctrl_G); return command_line_not_changed(s); } break; @@ -1757,6 +1783,20 @@ static int command_line_not_changed(CommandLineState *s) return command_line_changed(s); } +/// Guess that the pattern matches everything. Only finds specific cases, such +/// as a trailing \|, which can happen while typing a pattern. +static int empty_pattern(char_u *p) +{ + int n = STRLEN(p); + + // remove trailing \v and the like + while (n >= 2 && p[n - 2] == '\\' + && vim_strchr((char_u *)"mMvVcCZ", p[n - 1]) != NULL) { + n -= 2; + } + return n == 0 || (n >= 2 && p[n - 2] == '\\' && p[n - 1] == '|'); +} + static int command_line_changed(CommandLineState *s) { // 'incsearch' highlighting. @@ -1771,20 +1811,27 @@ static int command_line_changed(CommandLineState *s) } s->incsearch_postponed = false; curwin->w_cursor = s->search_start; // start at old position + save_last_search_pattern(); // If there is no command line, don't do anything if (ccline.cmdlen == 0) { s->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; + } s->i = do_search(NULL, s->firstc, ccline.cmdbuff, s->count, - SEARCH_KEEP + SEARCH_OPT + SEARCH_NOOF + SEARCH_PEEK, - &tm); - --emsg_off; + search_flags, + &tm); + emsg_off--; // if interrupted while searching, behave like it failed if (got_int) { (void)vpeekc(); // remove <C-C> from input stream @@ -1825,6 +1872,12 @@ static int command_line_changed(CommandLineState *s) 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) { @@ -1834,6 +1887,7 @@ static int command_line_changed(CommandLineState *s) save_cmdline(&s->save_ccline); update_screen(SOME_VALID); restore_cmdline(&s->save_ccline); + restore_last_search_pattern(); // Leave it at the end to make CTRL-R CTRL-W work. if (s->i != 0) { @@ -1889,6 +1943,18 @@ static int command_line_changed(CommandLineState *s) return 1; } +/// Abandon the command line. +static void abandon_cmdline(void) +{ + xfree(ccline.cmdbuff); + ccline.cmdbuff = NULL; + if (msg_scrolled == 0) { + compute_cmdrow(); + } + MSG(""); + redraw_cmdline = true; +} + /* * getcmdline() - accept a command line starting with firstc. * @@ -2162,7 +2228,13 @@ getexmodeline ( /* Get one character at a time. Don't use inchar(), it can't handle * special characters. */ prev_char = c1; - c1 = vgetc(); + + // Check for a ":normal" command and no more characters left. + if (ex_normal_busy > 0 && typebuf.tb_len == 0) { + c1 = '\n'; + } else { + c1 = vgetc(); + } /* * Handle line editing. @@ -2428,6 +2500,63 @@ void free_cmdline_buf(void) enum { MAX_CB_ERRORS = 1 }; +/// Color expression cmdline using built-in expressions parser +/// +/// @param[in] colored_ccline Command-line to color. +/// @param[out] ret_ccline_colors What should be colored. +/// +/// Always colors the whole cmdline. +static void color_expr_cmdline(const CmdlineInfo *const colored_ccline, + ColoredCmdline *const ret_ccline_colors) + FUNC_ATTR_NONNULL_ALL +{ + ParserLine plines[] = { + { + .data = (const char *)colored_ccline->cmdbuff, + .size = STRLEN(colored_ccline->cmdbuff), + .allocated = false, + }, + { NULL, 0, false }, + }; + ParserLine *plines_p = plines; + ParserHighlight colors; + kvi_init(colors); + ParserState pstate; + viml_parser_init( + &pstate, parser_simple_get_line, &plines_p, &colors); + ExprAST east = viml_pexpr_parse(&pstate, kExprFlagsDisallowEOC); + viml_pexpr_free_ast(east); + viml_parser_destroy(&pstate); + kv_resize(ret_ccline_colors->colors, kv_size(colors)); + size_t prev_end = 0; + for (size_t i = 0 ; i < kv_size(colors) ; i++) { + const ParserHighlightChunk chunk = kv_A(colors, i); + if (chunk.start.col != prev_end) { + kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) { + .start = prev_end, + .end = chunk.start.col, + .attr = 0, + })); + } + const int id = syn_name2id((const char_u *)chunk.group); + const int attr = (id == 0 ? 0 : syn_id2attr(id)); + kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) { + .start = chunk.start.col, + .end = chunk.end_col, + .attr = attr, + })); + prev_end = chunk.end_col; + } + if (prev_end < (size_t)colored_ccline->cmdlen) { + kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) { + .start = prev_end, + .end = (size_t)colored_ccline->cmdlen, + .attr = 0, + })); + } + kvi_destroy(colors); +} + /// Color command-line /// /// Should use built-in command parser or user-specified one. Currently only the @@ -2510,13 +2639,7 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) tl_ret = try_leave(&tstate, &err); can_free_cb = true; } else if (colored_ccline->cmdfirstc == '=') { - try_enter(&tstate); - err_errmsg = N_( - "E5409: Unable to get g:Nvim_color_expr callback: %s"); - dgc_ret = tv_dict_get_callback(&globvardict, S_LEN("Nvim_color_expr"), - &color_cb); - tl_ret = try_leave(&tstate, &err); - can_free_cb = true; + color_expr_cmdline(colored_ccline, ccline_colors); } if (!tl_ret || !dgc_ret) { goto color_cmdline_error; @@ -2565,20 +2688,20 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) } varnumber_T prev_end = 0; int i = 0; - for (const listitem_T *li = tv.vval.v_list->lv_first; - li != NULL; li = li->li_next, i++) { - if (li->li_tv.v_type != VAR_LIST) { + TV_LIST_ITER_CONST(tv.vval.v_list, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_LIST) { PRINT_ERRMSG(_("E5401: List item %i is not a List"), i); goto color_cmdline_error; } - const list_T *const l = li->li_tv.vval.v_list; + const list_T *const l = TV_LIST_ITEM_TV(li)->vval.v_list; if (tv_list_len(l) != 3) { PRINT_ERRMSG(_("E5402: List item %i has incorrect length: %li /= 3"), i, tv_list_len(l)); goto color_cmdline_error; } bool error = false; - const varnumber_T start = tv_get_number_chk(&l->lv_first->li_tv, &error); + const varnumber_T start = ( + tv_get_number_chk(TV_LIST_ITEM_TV(tv_list_first(l)), &error)); if (error) { goto color_cmdline_error; } else if (!(prev_end <= start && start < colored_ccline->cmdlen)) { @@ -2598,8 +2721,8 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) .attr = 0, })); } - const varnumber_T end = tv_get_number_chk(&l->lv_first->li_next->li_tv, - &error); + const varnumber_T end = tv_get_number_chk( + TV_LIST_ITEM_TV(TV_LIST_ITEM_NEXT(l, tv_list_first(l))), &error); if (error) { goto color_cmdline_error; } else if (!(start < end && end <= colored_ccline->cmdlen)) { @@ -2615,7 +2738,8 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) goto color_cmdline_error; } prev_end = end; - const char *const group = tv_get_string_chk(&l->lv_last->li_tv); + const char *const group = tv_get_string_chk( + TV_LIST_ITEM_TV(tv_list_last(l))); if (group == NULL) { goto color_cmdline_error; } @@ -2626,7 +2750,8 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) .end = end, .attr = attr, })); - } + i++; + }); if (prev_end < colored_ccline->cmdlen) { kv_push(ccline_colors->colors, ((CmdlineColorChunk) { .start = prev_end, @@ -2818,11 +2943,11 @@ static void ui_ext_cmdline_show(CmdlineInfo *line) Array item = ARRAY_DICT_INIT; if (chunk.attr) { - attrentry_T *aep = syn_cterm_attr2entry(chunk.attr); + HlAttrs *aep = syn_cterm_attr2entry(chunk.attr); // TODO(bfredl): this desicion could be delayed by making attr_code a // recognized type - HlAttrs rgb_attrs = attrentry2hlattrs(aep, true); - ADD(item, DICTIONARY_OBJ(hlattrs2dict(rgb_attrs))); + Dictionary rgb_attrs = hlattrs2dict(aep, true); + ADD(item, DICTIONARY_OBJ(rgb_attrs)); } else { ADD(item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT)); } @@ -2870,6 +2995,7 @@ void ui_ext_cmdline_block_append(int indent, const char *line) void ui_ext_cmdline_block_leave(void) { api_free_array(cmdline_block); + cmdline_block = (Array)ARRAY_DICT_INIT; ui_call_cmdline_block_hide(); } @@ -3455,8 +3581,10 @@ nextwild ( return FAIL; } - MSG_PUTS("..."); /* show that we are busy */ - ui_flush(); + if (!ui_is_external(kUIWildmenu)) { + MSG_PUTS("..."); // show that we are busy + ui_flush(); + } i = (int)(xp->xp_pattern - ccline.cmdbuff); xp->xp_pattern_len = ccline.cmdpos - i; @@ -3995,12 +4123,12 @@ static int showmatches(expand_T *xp, int wildmenu) msg_start(); /* prepare for paging */ } - if (got_int) - got_int = FALSE; /* only int. the completion, not the cmd line */ - else if (wildmenu) - win_redr_status_matches(xp, num_files, files_found, 0, showtail); - else { - /* find the length of the longest file name */ + if (got_int) { + got_int = false; // only int. the completion, not the cmd line + } else if (wildmenu) { + win_redr_status_matches(xp, num_files, files_found, -1, showtail); + } else { + // find the length of the longest file name maxlen = 0; for (i = 0; i < num_files; ++i) { if (!showtail && (xp->xp_context == EXPAND_FILES @@ -4194,20 +4322,20 @@ addstar ( * use with vim_regcomp(). First work out how long it will be: */ - /* For help tags the translation is done in find_help_tags(). - * For a tag pattern starting with "/" no translation is needed. */ + // For help tags the translation is done in find_help_tags(). + // For a tag pattern starting with "/" no translation is needed. if (context == EXPAND_HELP + || context == EXPAND_CHECKHEALTH || context == EXPAND_COLORS || context == EXPAND_COMPILER || context == EXPAND_OWNSYNTAX || context == EXPAND_FILETYPE || context == EXPAND_PACKADD - || ((context == EXPAND_TAGS_LISTFILES - || context == EXPAND_TAGS) - && fname[0] == '/')) + || ((context == EXPAND_TAGS_LISTFILES || context == EXPAND_TAGS) + && fname[0] == '/')) { retval = vim_strnsave(fname, len); - else { - new_len = len + 2; /* +2 for '^' at start, NUL at end */ + } else { + new_len = len + 2; // +2 for '^' at start, NUL at end for (i = 0; i < len; i++) { if (fname[i] == '*' || fname[i] == '~') new_len++; /* '*' needs to be replaced by ".*" @@ -4604,6 +4732,10 @@ ExpandFromContext ( char *directories[] = { "syntax", "indent", "ftplugin", NULL }; return ExpandRTDir(pat, 0, num_file, file, directories); } + if (xp->xp_context == EXPAND_CHECKHEALTH) { + char *directories[] = { "autoload/health", NULL }; + return ExpandRTDir(pat, 0, num_file, file, directories); + } if (xp->xp_context == EXPAND_USER_LIST) { return ExpandUserList(xp, num_file, file); } @@ -4795,13 +4927,14 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file, flags |= EW_FILE | EW_EXEC | EW_SHELLCMD; bool mustfree = false; // Track memory allocation for *path. - /* For an absolute name we don't use $PATH. */ - if (path_is_absolute_path(pat)) + // For an absolute name we don't use $PATH. + if (path_is_absolute(pat)) { path = (char_u *)" "; - else if ((pat[0] == '.' && (vim_ispathsep(pat[1]) - || (pat[1] == '.' && vim_ispathsep(pat[2]))))) + } else if (pat[0] == '.' && (vim_ispathsep(pat[1]) + || (pat[1] == '.' + && vim_ispathsep(pat[2])))) { path = (char_u *)"."; - else { + } else { path = (char_u *)vim_getenv("PATH"); if (path == NULL) { path = (char_u *)""; @@ -4966,24 +5099,24 @@ static int ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, int *num_file, */ static int ExpandUserList(expand_T *xp, int *num_file, char_u ***file) { - list_T *retlist; - listitem_T *li; - garray_T ga; - - retlist = call_user_expand_func((user_expand_func_T)call_func_retlist, xp, - num_file, file); + list_T *const retlist = call_user_expand_func( + (user_expand_func_T)call_func_retlist, xp, num_file, file); if (retlist == NULL) { return FAIL; } + garray_T ga; ga_init(&ga, (int)sizeof(char *), 3); - /* Loop over the items in the list. */ - for (li = retlist->lv_first; li != NULL; li = li->li_next) { - if (li->li_tv.v_type != VAR_STRING || li->li_tv.vval.v_string == NULL) - continue; /* Skip non-string items and empty strings */ + // Loop over the items in the list. + TV_LIST_ITER_CONST(retlist, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING + || TV_LIST_ITEM_TV(li)->vval.v_string == NULL) { + continue; // Skip non-string items and empty strings. + } - GA_APPEND(char_u *, &ga, vim_strsave(li->li_tv.vval.v_string)); - } + GA_APPEND(char *, &ga, xstrdup( + (const char *)TV_LIST_ITEM_TV(li)->vval.v_string)); + }); tv_list_unref(retlist); *file = ga.ga_data; diff --git a/src/nvim/farsi.c b/src/nvim/farsi.c index 1053cb3ac2..5801a2d8fb 100644 --- a/src/nvim/farsi.c +++ b/src/nvim/farsi.c @@ -596,78 +596,83 @@ static void chg_r_to_Xor_X_(void) int fkmap(int c) { int tempc; - static int revins; + int insert_mode = (State & INSERT); + static int revins = 0; if (IS_SPECIAL(c)) { return c; } - if (ascii_isdigit(c) - || ((c == '.' - || c == '+' - || c == '-' - || c == '^' - || c == '%' - || c == '#' - || c == '=') - && revins)) { - if (!revins) { - if (curwin->w_cursor.col) { - if (!p_ri) { - dec_cursor(); - } + if (insert_mode) { + if (ascii_isdigit(c) + || ((c == '.' + || c == '+' + || c == '-' + || c == '^' + || c == '%' + || c == '#' + || c == '=') + && revins)) { + // Numbers are entered left-to-right. + if (!revins) { + if (curwin->w_cursor.col) { + if (!p_ri) { + dec_cursor(); + } - chg_c_toX_orX(); - chg_l_toXor_X(); - if (!p_ri) { - inc_cursor(); + chg_c_toX_orX(); + chg_l_toXor_X(); + if (!p_ri) { + inc_cursor(); + } } } - } - arrow_used = TRUE; - (void)stop_arrow(); + arrow_used = true; + (void)stop_arrow(); - if (!curwin->w_p_rl && revins) { - inc_cursor(); - } + if (!curwin->w_p_rl && revins) { + inc_cursor(); + } - revins++; - p_ri = 1; - } else { - if (revins) { - arrow_used = TRUE; - (void)stop_arrow(); + revins++; + p_ri = 1; + } else { + if (revins) { + // Stop entering number. + arrow_used = true; + (void)stop_arrow(); - revins = 0; - if (curwin->w_p_rl) { - while ((F_isdigit(gchar_cursor()) - || (gchar_cursor() == F_PERIOD - || gchar_cursor() == F_PLUS - || gchar_cursor() == F_MINUS - || gchar_cursor() == F_MUL - || gchar_cursor() == F_DIVIDE - || gchar_cursor() == F_PERCENT - || gchar_cursor() == F_EQUALS)) - && gchar_cursor() != NUL) { - curwin->w_cursor.col++; - } - } else { - if (curwin->w_cursor.col) { + revins = 0; + if (curwin->w_p_rl) { while ((F_isdigit(gchar_cursor()) - || (gchar_cursor() == F_PERIOD - || gchar_cursor() == F_PLUS - || gchar_cursor() == F_MINUS - || gchar_cursor() == F_MUL - || gchar_cursor() == F_DIVIDE - || gchar_cursor() == F_PERCENT - || gchar_cursor() == F_EQUALS)) - && --curwin->w_cursor.col) { + || (gchar_cursor() == F_PERIOD + || gchar_cursor() == F_PLUS + || gchar_cursor() == F_MINUS + || gchar_cursor() == F_MUL + || gchar_cursor() == F_DIVIDE + || gchar_cursor() == F_PERCENT + || gchar_cursor() == F_EQUALS)) + && gchar_cursor() != NUL) { + curwin->w_cursor.col++; + } + } else { + if (curwin->w_cursor.col) { + while ((F_isdigit(gchar_cursor()) + || (gchar_cursor() == F_PERIOD + || gchar_cursor() == F_PLUS + || gchar_cursor() == F_MINUS + || gchar_cursor() == F_MUL + || gchar_cursor() == F_DIVIDE + || gchar_cursor() == F_PERCENT + || gchar_cursor() == F_EQUALS)) + && --curwin->w_cursor.col) { + } } - } - if (!F_isdigit(gchar_cursor())) { - ++curwin->w_cursor.col; + if (!F_isdigit(gchar_cursor())) { + curwin->w_cursor.col++; + } } } } @@ -755,7 +760,7 @@ int fkmap(int c) case 'Y': case NL: case TAB: - if (p_ri && (c == NL) && curwin->w_cursor.col) { + if (p_ri && (c == NL) && curwin->w_cursor.col && insert_mode) { // If the char before the cursor is _X_ or X_ do not change // the one under the cursor with X type. @@ -826,135 +831,137 @@ int fkmap(int c) } } - if (!p_ri) { - dec_cursor(); - } + if (insert_mode) { + if (!p_ri) { + dec_cursor(); + } - switch ((tempc = gchar_cursor())) { - case _BE: - case _PE: - case _TE: - case _SE: - case _JIM: - case _CHE: - case _HE_J: - case _XE: - case _SIN: - case _SHIN: - case _SAD: - case _ZAD: - case _FE: - case _GHAF: - case _KAF: - case _KAF_H: - case _GAF: - case _LAM: - case _MIM: - case _NOON: - case _HE: - case _HE_: - case _TA: - case _ZA: - put_curr_and_l_to_X(toF_TyA((char_u)tempc)); - break; + switch ((tempc = gchar_cursor())) { + case _BE: + case _PE: + case _TE: + case _SE: + case _JIM: + case _CHE: + case _HE_J: + case _XE: + case _SIN: + case _SHIN: + case _SAD: + case _ZAD: + case _FE: + case _GHAF: + case _KAF: + case _KAF_H: + case _GAF: + case _LAM: + case _MIM: + case _NOON: + case _HE: + case _HE_: + case _TA: + case _ZA: + put_curr_and_l_to_X(toF_TyA((char_u)tempc)); + break; - case _AYN: - case _AYN_: - if (!p_ri) { - if (!curwin->w_cursor.col) { - put_curr_and_l_to_X(AYN); - break; + case _AYN: + case _AYN_: + if (!p_ri) { + if (!curwin->w_cursor.col) { + put_curr_and_l_to_X(AYN); + break; + } } - } - if (p_ri) { - inc_cursor(); - } else { - dec_cursor(); - } + if (p_ri) { + inc_cursor(); + } else { + dec_cursor(); + } - if (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR)) { - tempc = AYN_; - } else { - tempc = AYN; - } + if (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR)) { + tempc = AYN_; + } else { + tempc = AYN; + } - if (p_ri) { - dec_cursor(); - } else { - inc_cursor(); - } + if (p_ri) { + dec_cursor(); + } else { + inc_cursor(); + } - put_curr_and_l_to_X((char_u)tempc); - break; + put_curr_and_l_to_X((char_u)tempc); + break; - case _GHAYN: - case _GHAYN_: + case _GHAYN: + case _GHAYN_: - if (!p_ri) { - if (!curwin->w_cursor.col) { - put_curr_and_l_to_X(GHAYN); - break; + if (!p_ri) { + if (!curwin->w_cursor.col) { + put_curr_and_l_to_X(GHAYN); + break; + } } - } - if (p_ri) { - inc_cursor(); - } else { - dec_cursor(); - } + if (p_ri) { + inc_cursor(); + } else { + dec_cursor(); + } - if (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR)) { - tempc = GHAYN_; - } else { - tempc = GHAYN; - } + if (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR)) { + tempc = GHAYN_; + } else { + tempc = GHAYN; + } - if (p_ri) { - dec_cursor(); - } else { - inc_cursor(); - } + if (p_ri) { + dec_cursor(); + } else { + inc_cursor(); + } - put_curr_and_l_to_X((char_u)tempc); - break; + put_curr_and_l_to_X((char_u)tempc); + break; - case _YE: - case _IE: - case _YEE: + case _YE: + case _IE: + case _YEE: - if (!p_ri) { - if (!curwin->w_cursor.col) { - put_curr_and_l_to_X( - (tempc == _YE ? YE : tempc == _IE ? IE : YEE)); - break; + if (!p_ri) { + if (!curwin->w_cursor.col) { + put_curr_and_l_to_X( + (tempc == _YE ? YE : tempc == _IE ? IE : YEE)); + break; + } } - } - if (p_ri) { - inc_cursor(); - } else { - dec_cursor(); - } + if (p_ri) { + inc_cursor(); + } else { + dec_cursor(); + } - if (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR)) { - tempc = (tempc == _YE ? YE_ : tempc == _IE ? IE_ : YEE_); - } else { - tempc = (tempc == _YE ? YE : tempc == _IE ? IE : YEE); - } + if (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR)) { + tempc = (tempc == _YE ? YE_ : tempc == _IE ? IE_ : YEE_); + } else { + tempc = (tempc == _YE ? YE : tempc == _IE ? IE : YEE); + } - if (p_ri) { - dec_cursor(); - } else { - inc_cursor(); - } + if (p_ri) { + dec_cursor(); + } else { + inc_cursor(); + } - put_curr_and_l_to_X((char_u)tempc); - break; - } + put_curr_and_l_to_X((char_u)tempc); + break; + } - if (!p_ri) { - inc_cursor(); + if (!p_ri) { + inc_cursor(); + } } tempc = 0; diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index 8094a1b266..3272ce81bc 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -1560,7 +1560,7 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope) tv_dict_set_keys_readonly(dict); apply_autocmds(EVENT_DIRCHANGED, (char_u *)buf, (char_u *)new_dir, false, - NULL); + curbuf); tv_dict_clear(dict); @@ -1586,7 +1586,7 @@ int vim_chdirfile(char_u *fname) } #ifdef BACKSLASH_IN_FILENAME - slash_adjust(dir); + slash_adjust((char_u *)dir); #endif if (!strequal(dir, (char *)NameBuff)) { do_autocmd_dirchanged(dir, kCdScopeWindow); diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 1f4cd22754..25653deb3e 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -1,9 +1,7 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * fileio.c: read from and write to a file - */ +// fileio.c: read from and write to a file #include <assert.h> #include <errno.h> @@ -65,57 +63,62 @@ #define BUFSIZE 8192 /* size of normal write buffer */ #define SMBUFSIZE 256 /* size of emergency write buffer */ -/* - * The autocommands are stored in a list for each event. - * Autocommands for the same pattern, that are consecutive, are joined - * together, to avoid having to match the pattern too often. - * The result is an array of Autopat lists, which point to AutoCmd lists: - * - * first_autopat[0] --> Autopat.next --> Autopat.next --> NULL - * Autopat.cmds Autopat.cmds - * | | - * V V - * AutoCmd.next AutoCmd.next - * | | - * V V - * AutoCmd.next NULL - * | - * V - * NULL - * - * first_autopat[1] --> Autopat.next --> NULL - * Autopat.cmds - * | - * V - * AutoCmd.next - * | - * V - * NULL - * etc. - * - * The order of AutoCmds is important, this is the order in which they were - * defined and will have to be executed. - */ +// +// The autocommands are stored in a list for each event. +// Autocommands for the same pattern, that are consecutive, are joined +// together, to avoid having to match the pattern too often. +// The result is an array of Autopat lists, which point to AutoCmd lists: +// +// last_autopat[0] -----------------------------+ +// V +// first_autopat[0] --> Autopat.next --> Autopat.next --> NULL +// Autopat.cmds Autopat.cmds +// | | +// V V +// AutoCmd.next AutoCmd.next +// | | +// V V +// AutoCmd.next NULL +// | +// V +// NULL +// +// last_autopat[1] --------+ +// V +// first_autopat[1] --> Autopat.next --> NULL +// Autopat.cmds +// | +// V +// AutoCmd.next +// | +// V +// NULL +// etc. +// +// The order of AutoCmds is important, this is the order in which they were +// defined and will have to be executed. +// typedef struct AutoCmd { - char_u *cmd; /* The command to be executed (NULL - when command has been removed) */ - char nested; /* If autocommands nest here */ - char last; /* last command in list */ - scid_T scriptID; /* script ID where defined */ - struct AutoCmd *next; /* Next AutoCmd in list */ + char_u *cmd; // The command to be executed (NULL + // when command has been removed) + char nested; // If autocommands nest here + char last; // last command in list + scid_T scriptID; // script ID where defined + struct AutoCmd *next; // Next AutoCmd in list } AutoCmd; typedef struct AutoPat { - char_u *pat; /* pattern as typed (NULL when pattern - has been removed) */ - regprog_T *reg_prog; /* compiled regprog for pattern */ - AutoCmd *cmds; /* list of commands to do */ - struct AutoPat *next; /* next AutoPat in AutoPat list */ - int group; /* group ID */ - int patlen; /* strlen() of pat */ - int buflocal_nr; /* !=0 for buffer-local AutoPat */ - char allow_dirs; /* Pattern may match whole path */ - char last; /* last pattern for apply_autocmds() */ + struct AutoPat *next; // next AutoPat in AutoPat list; MUST + // be the first entry + char_u *pat; // pattern as typed (NULL when pattern + // has been removed) + regprog_T *reg_prog; // compiled regprog for pattern + AutoCmd *cmds; // list of commands to do + int group; // group ID + int patlen; // strlen() of pat + int buflocal_nr; // !=0 for buffer-local AutoPat + char allow_dirs; // Pattern may match whole path + char last; // last pattern for apply_autocmds() } AutoPat; /* @@ -226,6 +229,15 @@ void filemess(buf_T *buf, char_u *name, char_u *s, int attr) msg_scrolled_ign = FALSE; } +static AutoPat *last_autopat[NUM_EVENTS] = { + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL +}; + /* * Read lines from file "fname" into the buffer after line "from". * @@ -4318,7 +4330,7 @@ void shorten_fnames(int force) && !path_with_url((char *)buf->b_fname) && (force || buf->b_sfname == NULL - || path_is_absolute_path(buf->b_sfname))) { + || path_is_absolute(buf->b_sfname))) { xfree(buf->b_sfname); buf->b_sfname = NULL; p = path_shorten_fname(buf->b_ffname, dirname); @@ -4440,7 +4452,7 @@ char *modname(const char *fname, const char *ext, bool prepend_dot) /// @param size size of the buffer /// @param fp file to read from /// -/// @return true for end-of-file. +/// @return true for EOF or error bool vim_fgets(char_u *buf, int size, FILE *fp) FUNC_ATTR_NONNULL_ALL { char *retval; @@ -4451,7 +4463,7 @@ bool vim_fgets(char_u *buf, int size, FILE *fp) FUNC_ATTR_NONNULL_ALL do { errno = 0; retval = fgets((char *)buf, size, fp); - } while (retval == NULL && errno == EINTR); + } while (retval == NULL && errno == EINTR && ferror(fp)); if (buf[size - 2] != NUL && buf[size - 2] != '\n') { char tbuf[200]; @@ -4463,12 +4475,12 @@ bool vim_fgets(char_u *buf, int size, FILE *fp) FUNC_ATTR_NONNULL_ALL tbuf[sizeof(tbuf) - 2] = NUL; errno = 0; retval = fgets((char *)tbuf, sizeof(tbuf), fp); - if (retval == NULL && errno != EINTR) { + if (retval == NULL && (feof(fp) || errno != EINTR)) { break; } } while (tbuf[sizeof(tbuf) - 2] != NUL && tbuf[sizeof(tbuf) - 2] != '\n'); } - return retval ? false : feof(fp); + return retval == NULL; } /// Read 2 bytes from "fd" and turn them into an int, MSB first. @@ -5088,14 +5100,12 @@ void buf_reload(buf_T *buf, int orig_mode) flags |= READ_KEEP_UNDO; } - /* - * To behave like when a new file is edited (matters for - * BufReadPost autocommands) we first need to delete the current - * buffer contents. But if reading the file fails we should keep - * the old contents. Can't use memory only, the file might be - * too big. Use a hidden buffer to move the buffer contents to. - */ - if (bufempty() || saved == FAIL) { + // To behave like when a new file is edited (matters for + // BufReadPost autocommands) we first need to delete the current + // buffer contents. But if reading the file fails we should keep + // the old contents. Can't use memory only, the file might be + // too big. Use a hidden buffer to move the buffer contents to. + if (BUFEMPTY() || saved == FAIL) { savebuf = NULL; } else { // Allocate a buffer without putting it in the buffer list. @@ -5128,7 +5138,7 @@ void buf_reload(buf_T *buf, int orig_mode) if (savebuf != NULL && bufref_valid(&bufref) && buf == curbuf) { // Put the text back from the save buffer. First // delete any lines that readfile() added. - while (!bufempty()) { + while (!BUFEMPTY()) { if (ml_delete(buf->b_ml.ml_line_count, false) == FAIL) { break; } @@ -5530,6 +5540,15 @@ static void au_cleanup(void) /* remove the pattern if it has been marked for deletion */ if (ap->pat == NULL) { + if (ap->next == NULL) { + if (prev_ap == &(first_autopat[(int)event])) { + last_autopat[(int)event] = NULL; + } else { + // this depends on the "next" field being the first in + // the struct + last_autopat[(int)event] = (AutoPat *)prev_ap; + } + } *prev_ap = ap->next; vim_regfree(ap->reg_prog); xfree(ap); @@ -6122,10 +6141,13 @@ static int do_autocmd_event(event_T event, char_u *pat, int nested, char_u *cmd, patlen = (int)STRLEN(buflocal_pat); /* but not endpat */ } - /* - * Find AutoPat entries with this pattern. - */ - prev_ap = &first_autopat[(int)event]; + // Find AutoPat entries with this pattern. When adding a command it + // always goes at or after the last one, so start at the end. + if (!forceit && *cmd != NUL && last_autopat[(int)event] != NULL) { + prev_ap = &last_autopat[(int)event]; + } else { + prev_ap = &first_autopat[(int)event]; + } while ((ap = *prev_ap) != NULL) { if (ap->pat != NULL) { /* Accept a pattern when: @@ -6211,6 +6233,7 @@ static int do_autocmd_event(event_T event, char_u *pat, int nested, char_u *cmd, } ap->cmds = NULL; *prev_ap = ap; + last_autopat[(int)event] = ap; ap->next = NULL; if (group == AUGROUP_ALL) ap->group = current_augroup; @@ -6276,13 +6299,13 @@ do_doautocmd ( fname = skipwhite(fname); - /* - * Loop over the events. - */ - while (*arg && !ascii_iswhite(*arg)) - if (apply_autocmds_group(event_name2nr(arg, &arg), - fname, NULL, TRUE, group, curbuf, NULL)) - nothing_done = FALSE; + // Loop over the events. + while (*arg && !ends_excmd(*arg) && !ascii_iswhite(*arg)) { + if (apply_autocmds_group(event_name2nr(arg, &arg), fname, NULL, true, + group, curbuf, NULL)) { + nothing_done = false; + } + } if (nothing_done && do_msg) { MSG(_("No matching autocommands")); @@ -6657,7 +6680,6 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, char_u *save_sourcing_name; linenr_T save_sourcing_lnum; char_u *save_autocmd_fname; - int save_autocmd_fname_full; int save_autocmd_bufnr; char_u *save_autocmd_match; int save_autocmd_busy; @@ -6673,12 +6695,12 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, proftime_T wait_time; bool did_save_redobuff = false; - /* - * Quickly return if there are no autocommands for this event or - * autocommands are blocked. - */ - if (first_autopat[(int)event] == NULL || autocmd_blocked > 0) + // Quickly return if there are no autocommands for this event or + // autocommands are blocked. + if (event == NUM_EVENTS || first_autopat[(int)event] == NULL + || autocmd_blocked > 0) { goto BYPASS_AU; + } /* * When autocommands are busy, new autocommands are only executed when @@ -6730,7 +6752,6 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, * Save the autocmd_* variables and info about the current buffer. */ save_autocmd_fname = autocmd_fname; - save_autocmd_fname_full = autocmd_fname_full; save_autocmd_bufnr = autocmd_bufnr; save_autocmd_match = autocmd_match; save_autocmd_busy = autocmd_busy; @@ -6744,19 +6765,22 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, * invalid. */ if (fname_io == NULL) { - if (event == EVENT_COLORSCHEME || event == EVENT_OPTIONSET) + if (event == EVENT_COLORSCHEME || event == EVENT_OPTIONSET) { autocmd_fname = NULL; - else if (fname != NULL && *fname != NUL) + } else if (fname != NULL && !ends_excmd(*fname)) { autocmd_fname = fname; - else if (buf != NULL) + } else if (buf != NULL) { autocmd_fname = buf->b_ffname; - else + } else { autocmd_fname = NULL; - } else + } + } else { autocmd_fname = fname_io; - if (autocmd_fname != NULL) - autocmd_fname = vim_strsave(autocmd_fname); - autocmd_fname_full = FALSE; /* call FullName_save() later */ + } + if (autocmd_fname != NULL) { + // Allocate MAXPATHL for when eval_vars() resolves the fullpath. + autocmd_fname = vim_strnsave(autocmd_fname, MAXPATHL); + } /* * Set the buffer number to be used for <abuf>. @@ -6923,7 +6947,6 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, sourcing_lnum = save_sourcing_lnum; xfree(autocmd_fname); autocmd_fname = save_autocmd_fname; - autocmd_fname_full = save_autocmd_fname_full; autocmd_bufnr = save_autocmd_bufnr; autocmd_match = save_autocmd_match; current_SID = save_current_SID; diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua index d2b90db707..2666ca6e6f 100644 --- a/src/nvim/generators/gen_api_ui_events.lua +++ b/src/nvim/generators/gen_api_ui_events.lua @@ -37,7 +37,7 @@ function write_arglist(output, ev, need_copy) for j = 1, #ev.parameters do local param = ev.parameters[j] local kind = string.upper(param[1]) - local do_copy = need_copy and (kind == "ARRAY" or kind == "DICTIONARY" or kind == "STRING") + local do_copy = need_copy and (kind == "ARRAY" or kind == "DICTIONARY" or kind == "STRING" or kind == "OBJECT") output:write(' ADD(args, ') if do_copy then output:write('copy_object(') @@ -91,7 +91,7 @@ for i = 1, #events do recv_cleanup = recv_cleanup..' api_free_string('..param[2]..');\n' argc = argc+2 elseif param[1] == 'Array' then - send = send..' Array copy_'..param[2]..' = copy_array('..param[2]..');\n' + send = send..' Array '..copy..' = copy_array('..param[2]..');\n' argv = argv..', '..copy..'.items, INT2PTR('..copy..'.size)' recv = (recv..' Array '..param[2].. ' = (Array){.items = argv['..argc..'],'.. @@ -99,6 +99,15 @@ for i = 1, #events do recv_argv = recv_argv..', '..param[2] recv_cleanup = recv_cleanup..' api_free_array('..param[2]..');\n' argc = argc+2 + elseif param[1] == 'Object' then + send = send..' Object *'..copy..' = xmalloc(sizeof(Object));\n' + send = send..' *'..copy..' = copy_object('..param[2]..');\n' + argv = argv..', '..copy + recv = recv..' Object '..param[2]..' = *(Object *)argv['..argc..'];\n' + recv_argv = recv_argv..', '..param[2] + recv_cleanup = (recv_cleanup..' api_free_object('..param[2]..');\n'.. + ' xfree(argv['..argc..']);\n') + argc = argc+1 elseif param[1] == 'Integer' or param[1] == 'Boolean' then argv = argv..', INT2PTR('..param[2]..')' recv_argv = recv_argv..', PTR2INT(argv['..argc..'])' @@ -119,7 +128,7 @@ for i = 1, #events do write_signature(bridge_output, ev, 'UI *ui') bridge_output:write('\n{\n') bridge_output:write(send) - bridge_output:write(' UI_BRIDGE_CALL(ui, '..ev.name..', '..argc..', ui'..argv..');\n}\n') + bridge_output:write(' UI_BRIDGE_CALL(ui, '..ev.name..', '..argc..', ui'..argv..');\n}\n\n') end end diff --git a/src/nvim/generators/gen_declarations.lua b/src/nvim/generators/gen_declarations.lua index e999e53e4a..c40c37bb3e 100755 --- a/src/nvim/generators/gen_declarations.lua +++ b/src/nvim/generators/gen_declarations.lua @@ -164,9 +164,40 @@ local pattern = concat( ) if fname == '--help' then - print'Usage:' - print() - print' gendeclarations.lua definitions.c static.h non-static.h preprocessor.i' + print([[ +Usage: + + gendeclarations.lua definitions.c static.h non-static.h definitions.i + +Generates declarations for a C file definitions.c, putting declarations for +static functions into static.h and declarations for non-static functions into +non-static.h. File `definitions.i' should contain an already preprocessed +version of definitions.c and it is the only one which is actually parsed, +definitions.c is needed only to determine functions from which file out of all +functions found in definitions.i are needed. + +Additionally uses the following environment variables: + + NVIM_GEN_DECLARATIONS_LINE_NUMBERS: + If set to 1 then all generated declarations receive a comment with file + name and line number after the declaration. This may be useful for + debugging gen_declarations script, but not much beyond that with + configured development environment (i.e. with ctags/cscope/finding + definitions with clang/etc). + + WARNING: setting this to 1 will cause extensive rebuilds: declarations + generator script will not regenerate non-static.h file if its + contents did not change, but including line numbers will make + contents actually change. + + With contents changed timestamp of the file is regenerated even + when no real changes were made (e.g. a few lines were added to + a function which is not at the bottom of the file). + + With changed timestamp build system will assume that header + changed, triggering rebuilds of all C files which depend on the + "changed" header. +]]) os.exit() end @@ -193,23 +224,15 @@ local static = header local filepattern = '^#%a* (%d+) "([^"]-)/?([^"/]+)"' local curfile -local init = 0 +local init = 1 local curfile = nil local neededfile = fname:match('[^/]+$') local declline = 0 local declendpos = 0 local curdir = nil local is_needed_file = false +local init_is_nl = true while init ~= nil do - init = text:find('[\n;}]', init) - if init == nil then - break - end - local init_is_nl = text:sub(init, init) == '\n' - init = init + 1 - if init_is_nl and is_needed_file then - declline = declline + 1 - end if init_is_nl and text:sub(init, init) == '#' then local line, dir, file = text:match(filepattern, init) if file ~= nil then @@ -249,8 +272,10 @@ while init ~= nil do declaration = declaration:gsub(' $', '') declaration = declaration:gsub('^ ', '') declaration = declaration .. ';' - declaration = declaration .. (' // %s/%s:%u'):format( - curdir, curfile, declline) + if os.getenv('NVIM_GEN_DECLARATIONS_LINE_NUMBERS') == '1' then + declaration = declaration .. (' // %s/%s:%u'):format( + curdir, curfile, declline) + end declaration = declaration .. '\n' if declaration:sub(1, 6) == 'static' then static = static .. declaration @@ -260,6 +285,15 @@ while init ~= nil do declendpos = e end end + init = text:find('[\n;}]', init) + if init == nil then + break + end + init_is_nl = text:sub(init, init) == '\n' + init = init + 1 + if init_is_nl and is_needed_file then + declline = declline + 1 + end end non_static = non_static .. footer diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index 36562c0be9..fdc00d5dc0 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -36,6 +36,7 @@ local redraw_flags={ all_windows='P_RALL', everything='P_RCLR', curswant='P_CURSWANT', + ui_option='P_UI_OPTION', } local list_flags={ diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index ef152d638b..98164b2653 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -260,16 +260,17 @@ static void add_buff(buffheader_T *const buf, const char *const s, return; } - if (buf->bh_first.b_next == NULL) { /* first add to list */ + if (buf->bh_first.b_next == NULL) { // first add to list buf->bh_space = 0; buf->bh_curr = &(buf->bh_first); - } else if (buf->bh_curr == NULL) { /* buffer has already been read */ - EMSG(_("E222: Add to read buffer")); + } else if (buf->bh_curr == NULL) { // buffer has already been read + IEMSG(_("E222: Add to read buffer")); return; - } else if (buf->bh_index != 0) + } else if (buf->bh_index != 0) { memmove(buf->bh_first.b_next->b_str, - buf->bh_first.b_next->b_str + buf->bh_index, - STRLEN(buf->bh_first.b_next->b_str + buf->bh_index) + 1); + buf->bh_first.b_next->b_str + buf->bh_index, + STRLEN(buf->bh_first.b_next->b_str + buf->bh_index) + 1); + } buf->bh_index = 0; size_t len; @@ -1158,14 +1159,16 @@ void alloc_typebuf(void) */ void free_typebuf(void) { - if (typebuf.tb_buf == typebuf_init) - EMSG2(_(e_intern2), "Free typebuf 1"); - else + if (typebuf.tb_buf == typebuf_init) { + internal_error("Free typebuf 1"); + } else { xfree(typebuf.tb_buf); - if (typebuf.tb_noremap == noremapbuf_init) - EMSG2(_(e_intern2), "Free typebuf 2"); - else + } + if (typebuf.tb_noremap == noremapbuf_init) { + internal_error("Free typebuf 2"); + } else { xfree(typebuf.tb_noremap); + } } /* @@ -1583,7 +1586,7 @@ vungetc ( /* unget one character (can only be done once!) */ old_mouse_col = mouse_col; } -/// get a character: +/// Gets a character: /// 1. from the stuffbuffer /// This is used for abbreviated commands like "D" -> "d$". /// Also used to redo a command for ".". @@ -1601,7 +1604,7 @@ vungetc ( /* unget one character (can only be done once!) */ /// if "advance" is FALSE (vpeekc()): /// just look whether there is a character available. /// -/// When "no_mapping" is zero, checks for mappings in the current mode. +/// When `no_mapping` (global) is zero, checks for mappings in the current mode. /// Only returns one byte (of a multi-byte character). /// K_SPECIAL and CSI may be escaped, need to get two more bytes then. static int vgetorpeek(int advance) @@ -3361,6 +3364,10 @@ set_context_in_map_cmd ( arg = skipwhite(arg + 8); continue; } + if (STRNCMP(arg, "<special>", 9) == 0) { + arg = skipwhite(arg + 9); + continue; + } if (STRNCMP(arg, "<script>", 8) == 0) { arg = skipwhite(arg + 8); continue; @@ -3403,21 +3410,24 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file) for (round = 1; round <= 2; ++round) { count = 0; - for (i = 0; i < 6; ++i) { - if (i == 0) + for (i = 0; i < 7; i++) { + if (i == 0) { p = (char_u *)"<silent>"; - else if (i == 1) + } else if (i == 1) { p = (char_u *)"<unique>"; - else if (i == 2) + } else if (i == 2) { p = (char_u *)"<script>"; - else if (i == 3) + } else if (i == 3) { p = (char_u *)"<expr>"; - else if (i == 4 && !expand_buffer) + } else if (i == 4 && !expand_buffer) { p = (char_u *)"<buffer>"; - else if (i == 5) + } else if (i == 5) { p = (char_u *)"<nowait>"; - else + } else if (i == 6) { + p = (char_u *)"<special>"; + } else { continue; + } if (vim_regexec(regmatch, p, (colnr_T)0)) { if (round == 1) @@ -3903,7 +3913,7 @@ makemap ( c1 = 't'; break; default: - EMSG(_("E228: makemap: Illegal mode")); + IEMSG(_("E228: makemap: Illegal mode")); return FAIL; } do { /* do this twice if c2 is set, 3 times with c3 */ @@ -4235,3 +4245,70 @@ mapblock_T *get_maphash(int index, buf_T *buf) return (buf == NULL) ? maphash[index] : buf->b_maphash[index]; } + +/// Get command argument for <Cmd> key +char_u * getcmdkeycmd(int promptc, void *cookie, int indent) +{ + garray_T line_ga; + int c1 = -1, c2; + int cmod = 0; + bool aborted = false; + + ga_init(&line_ga, 1, 32); + + no_mapping++; + + got_int = false; + while (c1 != NUL && !aborted) { + ga_grow(&line_ga, 32); + + if (vgetorpeek(false) == NUL) { + // incomplete <Cmd> is an error, because there is not much the user + // could do in this state. + EMSG(e_cmdmap_err); + aborted = true; + break; + } + + // Get one character at a time. + c1 = vgetorpeek(true); + // Get two extra bytes for special keys + if (c1 == K_SPECIAL) { + c1 = vgetorpeek(true); // no mapping for these chars + c2 = vgetorpeek(true); + if (c1 == KS_MODIFIER) { + cmod = c2; + continue; + } + c1 = TO_SPECIAL(c1, c2); + } + + + if (got_int) { + aborted = true; + } else if (c1 == '\r' || c1 == '\n') { + c1 = NUL; // end the line + } else if (c1 == ESC) { + aborted = true; + } else if (c1 == K_COMMAND) { + // special case to give nicer error message + EMSG(e_cmdmap_repeated); + aborted = true; + } else if (IS_SPECIAL(c1)) { + EMSG2(e_cmdmap_key, get_special_key_name(c1, cmod)); + aborted = true; + } else { + ga_append(&line_ga, (char)c1); + } + + cmod = 0; + } + + no_mapping--; + + if (aborted) { + ga_clear(&line_ga); + } + + return (char_u *)line_ga.ga_data; +} diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 9c8cefc68e..56790bc89b 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -317,17 +317,11 @@ EXTERN int do_profiling INIT(= PROF_NONE); /* PROF_ values */ /* * The exception currently being thrown. Used to pass an exception to * a different cstack. Also used for discarding an exception before it is - * caught or made pending. Only valid when did_throw is TRUE. + * caught or made pending. */ EXTERN except_T *current_exception; /* - * did_throw: An exception is being thrown. Reset when the exception is caught - * or as long as it is pending in a finally clause. - */ -EXTERN int did_throw INIT(= FALSE); - -/* * need_rethrow: set to TRUE when a throw that cannot be handled in do_cmdline() * must be propagated to the cstack of the previously called do_cmdline(). */ @@ -416,7 +410,7 @@ EXTERN struct caller_scope { scid_T SID; uint8_t *sourcing_name, *autocmd_fname, *autocmd_match; linenr_T sourcing_lnum; - int autocmd_fname_full, autocmd_bufnr; + int autocmd_bufnr; void *funccalp; } provider_caller_scope; EXTERN int provider_call_nesting INIT(= 0); @@ -729,29 +723,6 @@ EXTERN int vr_lines_changed INIT(= 0); /* #Lines changed by "gR" so far */ /// Encoding used when 'fencs' is set to "default" EXTERN char_u *fenc_default INIT(= NULL); -// To speed up BYTELEN(); keep a lookup table to quickly get the length in -// bytes of a UTF-8 character from the first byte of a UTF-8 string. Bytes -// which are illegal when used as the first byte have a 1. The NUL byte has -// length 1. -EXTERN char utf8len_tab[256] INIT(= { - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1, -}); - # if defined(USE_ICONV) && defined(DYNAMIC_ICONV) /* Pointers to functions and variables to be loaded at runtime */ EXTERN size_t (*iconv)(iconv_t cd, const char **inbuf, size_t *inbytesleft, @@ -892,7 +863,6 @@ EXTERN char_u *last_cmdline INIT(= NULL); // last command line (for ":) EXTERN char_u *repeat_cmdline INIT(= NULL); // command line for "." EXTERN char_u *new_last_cmdline INIT(= NULL); // new value for last_cmdline EXTERN char_u *autocmd_fname INIT(= NULL); // fname for <afile> on cmdline -EXTERN int autocmd_fname_full; // autocmd_fname is full path EXTERN int autocmd_bufnr INIT(= 0); // fnum for <abuf> on cmdline EXTERN char_u *autocmd_match INIT(= NULL); // name for <amatch> on cmdline EXTERN int did_cursorhold INIT(= false); // set when CursorHold t'gerd @@ -957,7 +927,7 @@ extern char_u *compiled_sys; * directory is not a local directory, globaldir is NULL. */ EXTERN char_u *globaldir INIT(= NULL); -/* Characters from 'listchars' option */ +// 'listchars' characters. Defaults are overridden in set_chars_option(). EXTERN int lcs_eol INIT(= '$'); EXTERN int lcs_ext INIT(= NUL); EXTERN int lcs_prec INIT(= NUL); @@ -968,12 +938,13 @@ EXTERN int lcs_tab2 INIT(= NUL); EXTERN int lcs_trail INIT(= NUL); EXTERN int lcs_conceal INIT(= ' '); -/* Characters from 'fillchars' option */ +// 'fillchars' characters. Defaults are overridden in set_chars_option(). EXTERN int fill_stl INIT(= ' '); EXTERN int fill_stlnc INIT(= ' '); -EXTERN int fill_vert INIT(= ' '); -EXTERN int fill_fold INIT(= '-'); +EXTERN int fill_vert INIT(= 9474); // │ +EXTERN int fill_fold INIT(= 183); // · EXTERN int fill_diff INIT(= '-'); +EXTERN int fill_msgsep INIT(= ' '); /* Whether 'keymodel' contains "stopsel" and "startsel". */ EXTERN int km_stopsel INIT(= FALSE); @@ -1063,6 +1034,7 @@ EXTERN char_u e_for[] INIT(= N_("E588: :endfor without :for")); EXTERN char_u e_exists[] INIT(= N_("E13: File exists (add ! to override)")); EXTERN char_u e_failed[] INIT(= N_("E472: Command failed")); EXTERN char_u e_internal[] INIT(= N_("E473: Internal error")); +EXTERN char_u e_intern2[] INIT(= N_("E685: Internal error: %s")); EXTERN char_u e_interr[] INIT(= N_("Interrupted")); EXTERN char_u e_invaddr[] INIT(= N_("E14: Invalid address")); EXTERN char_u e_invarg[] INIT(= N_("E474: Invalid argument")); @@ -1082,6 +1054,9 @@ EXTERN char_u e_stdiochan2[] INIT(= N_( EXTERN char_u e_invstream[] INIT(= N_("E906: invalid stream for channel")); EXTERN char_u e_invstreamrpc[] INIT(= N_( "E906: invalid stream for rpc channel, use 'rpc'")); +EXTERN char_u e_streamkey[] INIT(= N_( + "E5210: dict key '%s' already set for buffered stream in channel %" + PRIu64)); EXTERN char_u e_libcall[] INIT(= N_("E364: Library call failed for \"%s()\"")); EXTERN char_u e_mkdir[] INIT(= N_("E739: Cannot create directory %s: %s")); EXTERN char_u e_markinval[] INIT(= N_("E19: Mark has invalid line number")); @@ -1092,7 +1067,6 @@ EXTERN char_u e_nesting[] INIT(= N_("E22: Scripts nested too deep")); EXTERN char_u e_noalt[] INIT(= N_("E23: No alternate file")); EXTERN char_u e_noabbr[] INIT(= N_("E24: No such abbreviation")); EXTERN char_u e_nobang[] INIT(= N_("E477: No ! allowed")); -EXTERN char_u e_nogvim[] INIT(= N_("E25: Nvim does not have a built-in GUI")); EXTERN char_u e_nogroup[] INIT(= N_("E28: No such highlight group name: %s")); EXTERN char_u e_noinstext[] INIT(= N_("E29: No inserted text yet")); EXTERN char_u e_nolastcmd[] INIT(= N_("E30: No previous command line")); @@ -1155,7 +1129,6 @@ EXTERN char_u e_write[] INIT(= N_("E80: Error while writing")); EXTERN char_u e_zerocount[] INIT(= N_("E939: Positive count required")); EXTERN char_u e_usingsid[] INIT(= N_( "E81: Using <SID> not in a script context")); -EXTERN char_u e_intern2[] INIT(= N_("E685: Internal error: %s")); EXTERN char_u e_maxmempat[] INIT(= N_( "E363: pattern uses more memory than 'maxmempattern'")); EXTERN char_u e_emptybuf[] INIT(= N_("E749: empty buffer")); @@ -1173,6 +1146,12 @@ EXTERN char_u e_fnametoolong[] INIT(= N_("E856: Filename too long")); EXTERN char_u e_float_as_string[] INIT(= N_("E806: using Float as a String")); EXTERN char_u e_autocmd_err[] INIT(=N_( "E5500: autocmd has thrown an exception: %s")); +EXTERN char_u e_cmdmap_err[] INIT(=N_( + "E5520: <Cmd> mapping must end with <CR>")); +EXTERN char_u e_cmdmap_repeated[] INIT(=N_( + "E5521: <Cmd> mapping must end with <CR> before second <Cmd>")); +EXTERN char_u e_cmdmap_key[] INIT(=N_( + "E5522: <Cmd> mapping must not include %s key")); EXTERN char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM")); diff --git a/src/nvim/hashtab.c b/src/nvim/hashtab.c index 3397788b00..526bc284a4 100644 --- a/src/nvim/hashtab.c +++ b/src/nvim/hashtab.c @@ -208,7 +208,7 @@ int hash_add(hashtab_T *ht, char_u *key) hash_T hash = hash_hash(key); hashitem_T *hi = hash_lookup(ht, (const char *)key, STRLEN(key), hash); if (!HASHITEM_EMPTY(hi)) { - EMSG2(_(e_intern2), "hash_add()"); + internal_error("hash_add()"); return FAIL; } hash_add_item(ht, hi, key, hash); diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index 927fc94bbe..3518c8bdcc 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -7,6 +7,34 @@ typedef int32_t RgbValue; +/// Highlighting attribute bits. +typedef enum { + HL_INVERSE = 0x01, + HL_BOLD = 0x02, + HL_ITALIC = 0x04, + HL_UNDERLINE = 0x08, + HL_UNDERCURL = 0x10, + HL_STANDOUT = 0x20, +} HlAttrFlags; + +/// Stores a complete highlighting entry, including colors and attributes +/// for both TUI and GUI. +typedef struct attr_entry { + int16_t rgb_ae_attr, cterm_ae_attr; // HL_BOLD, etc. + RgbValue rgb_fg_color, rgb_bg_color, rgb_sp_color; + int cterm_fg_color, cterm_bg_color; +} HlAttrs; + +#define HLATTRS_INIT (HlAttrs) { \ + .rgb_ae_attr = 0, \ + .cterm_ae_attr = 0, \ + .rgb_fg_color = -1, \ + .rgb_bg_color = -1, \ + .rgb_sp_color = -1, \ + .cterm_fg_color = 0, \ + .cterm_bg_color = 0, \ +} + /// Values for index in highlight_attr[]. /// When making changes, also update hlf_names below! typedef enum { @@ -59,6 +87,7 @@ typedef enum { , HLF_QFL // selected quickfix line , HLF_0 // Whitespace , HLF_INACTIVE // NormalNC: Normal text in non-current windows + , HLF_MSGSEP // message separator line , HLF_COUNT // MUST be the last one } hlf_T; @@ -109,7 +138,8 @@ EXTERN const char *hlf_names[] INIT(= { [HLF_MC] = "ColorColumn", [HLF_QFL] = "QuickFixLine", [HLF_0] = "Whitespace", - [HLF_INACTIVE] = "NormalNC" + [HLF_INACTIVE] = "NormalNC", + [HLF_MSGSEP] = "MsgSeparator", }); @@ -117,7 +147,6 @@ EXTERN int highlight_attr[HLF_COUNT]; // Highl. attr for each context. EXTERN int highlight_user[9]; // User[1-9] attributes EXTERN int highlight_stlnc[9]; // On top of user EXTERN int cterm_normal_fg_color INIT(= 0); -EXTERN int cterm_normal_fg_bold INIT(= 0); EXTERN int cterm_normal_bg_color INIT(= 0); EXTERN RgbValue normal_fg INIT(= -1); EXTERN RgbValue normal_bg INIT(= -1); diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index 3c02f5acbd..b78b56562c 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -549,7 +549,7 @@ static void cs_reading_emsg( static int cs_cnt_matches(size_t idx) { char *stok; - int nlines; + int nlines = 0; char *buf = xmalloc(CSREAD_BUFSIZE); for (;; ) { @@ -569,16 +569,20 @@ static int cs_cnt_matches(size_t idx) return CSCOPE_FAILURE; } - /* - * If the database is out of date, or there's some other problem, - * cscope will output error messages before the number-of-lines output. - * Display/discard any output that doesn't match what we want. - * Accept "\S*cscope: X lines", also matches "mlcscope". - */ - if ((stok = strtok(buf, (const char *)" ")) == NULL) + // If the database is out of date, or there's some other problem, + // cscope will output error messages before the number-of-lines output. + // Display/discard any output that doesn't match what we want. + // Accept "\S*cscope: X lines", also matches "mlcscope". + // Bail out for the "Unable to search" error. + if (strstr((const char *)buf, "Unable to search database") != NULL) { + break; + } + if ((stok = strtok(buf, (const char *)" ")) == NULL) { continue; - if (strstr((const char *)stok, "cscope:") == NULL) + } + if (strstr((const char *)stok, "cscope:") == NULL) { continue; + } if ((stok = strtok(NULL, (const char *)" ")) == NULL) continue; @@ -1012,9 +1016,9 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose, fclose(f); if (use_ll) /* Use location list */ wp = curwin; - /* '-' starts a new error list */ + // '-' starts a new error list if (qf_init(wp, tmp, (char_u *)"%f%*\\t%l%*\\t%m", - *qfpos == '-', cmdline) > 0) { + *qfpos == '-', cmdline, NULL) > 0) { if (postponed_split != 0) { (void)win_split(postponed_split > 0 ? postponed_split : 0, postponed_split_flags); @@ -1369,8 +1373,8 @@ static char *cs_manage_matches(char **matches, char **contexts, case Print: cs_print_tags_priv(mp, cp, cnt); break; - default: /* should not reach here */ - (void)EMSG(_("E570: fatal error in cs_manage_matches")); + default: // should not reach here + IEMSG(_("E570: fatal error in cs_manage_matches")); return NULL; } @@ -1685,8 +1689,15 @@ static int cs_read_prompt(size_t i) assert(IOSIZE >= cs_emsg_len); size_t maxlen = IOSIZE - cs_emsg_len; - for (;; ) { - while ((ch = getc(csinfo[i].fr_fp)) != EOF && ch != CSCOPE_PROMPT[0]) { + while (1) { + while (1) { + do { + errno = 0; + ch = fgetc(csinfo[i].fr_fp); + } while (ch == EOF && errno == EINTR && ferror(csinfo[i].fr_fp)); + if (ch == EOF || ch == CSCOPE_PROMPT[0]) { + break; + } // if there is room and char is printable if (bufpos < maxlen - 1 && vim_isprintc(ch)) { // lazy buffer allocation @@ -1715,9 +1726,13 @@ static int cs_read_prompt(size_t i) } } - for (size_t n = 0; n < strlen(CSCOPE_PROMPT); ++n) { - if (n > 0) - ch = (char)getc(csinfo[i].fr_fp); + for (size_t n = 0; n < strlen(CSCOPE_PROMPT); n++) { + if (n > 0) { + do { + errno = 0; + ch = fgetc(csinfo[i].fr_fp); + } while (ch == EOF && errno == EINTR && ferror(csinfo[i].fr_fp)); + } if (ch == EOF) { PERROR("cs_read_prompt EOF"); if (buf != NULL && buf[0] != NUL) diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index 279d45bb0a..2a215f854f 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -1314,6 +1314,43 @@ static int cin_starts_with(char_u *s, char *word) return STRNCMP(s, word, l) == 0 && !vim_isIDc(s[l]); } +/// Recognize a `extern "C"` or `extern "C++"` linkage specifications. +static int cin_is_cpp_extern_c(char_u *s) +{ + char_u *p; + int has_string_literal = false; + + s = cin_skipcomment(s); + if (STRNCMP(s, "extern", 6) == 0 && (s[6] == NUL || !vim_iswordc(s[6]))) { + p = cin_skipcomment(skipwhite(s + 6)); + while (*p != NUL) { + if (ascii_iswhite(*p)) { + p = cin_skipcomment(skipwhite(p)); + } else if (*p == '{') { + break; + } else if (p[0] == '"' && p[1] == 'C' && p[2] == '"') { + if (has_string_literal) { + return false; + } + has_string_literal = true; + p += 3; + } else if (p[0] == '"' && p[1] == 'C' && p[2] == '+' && p[3] == '+' + && p[4] == '"') { + if (has_string_literal) { + return false; + } + has_string_literal = true; + p += 5; + } else { + return false; + } + } + return has_string_literal ? true : false; + } + return false; +} + + /* * Skip strings, chars and comments until at or past "trypos". * Return the column found. @@ -1322,14 +1359,19 @@ static int cin_skip2pos(pos_T *trypos) { char_u *line; char_u *p; + char_u *new_p; p = line = ml_get(trypos->lnum); while (*p && (colnr_T)(p - line) < trypos->col) { - if (cin_iscomment(p)) + if (cin_iscomment(p)) { p = cin_skipcomment(p); - else { - p = skip_string(p); - ++p; + } else { + new_p = skip_string(p); + if (new_p == p) { + p++; + } else { + p = new_p; + } } } return (int)(p - line); @@ -1619,6 +1661,12 @@ void parse_cino(buf_T *buf) * while(). */ buf->b_ind_if_for_while = 0; + // indentation for # comments + buf->b_ind_hash_comment = 0; + + // Handle C++ extern "C" or "C++" + buf->b_ind_cpp_extern_c = 0; + for (p = buf->b_p_cino; *p; ) { l = p++; if (*p == '-') @@ -1687,6 +1735,7 @@ void parse_cino(buf_T *buf) case '#': buf->b_ind_hash_comment = n; break; 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; } if (*p == ',') ++p; @@ -2317,8 +2366,11 @@ int get_c_indent(void) amount += curbuf->b_ind_open_imag; l = skipwhite(get_cursor_line_ptr()); - if (cin_is_cpp_namespace(l)) + if (cin_is_cpp_namespace(l)) { amount += curbuf->b_ind_cpp_namespace; + } else if (cin_is_cpp_extern_c(l)) { + amount += curbuf->b_ind_cpp_extern_c; + } } else { /* Compensate for adding b_ind_open_extra later. */ amount -= curbuf->b_ind_open_extra; @@ -2517,6 +2569,9 @@ int get_c_indent(void) amount += curbuf->b_ind_cpp_namespace - added_to_amount; break; + } else if (cin_is_cpp_extern_c(l)) { + amount += curbuf->b_ind_cpp_extern_c - added_to_amount; + break; } if (cin_nocode(l)) diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index a75fe793ac..628bfef221 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -24,23 +24,23 @@ * Some useful tables. */ -static struct modmasktable { - short mod_mask; /* Bit-mask for particular key modifier */ - short mod_flag; /* Bit(s) for particular key modifier */ - char_u name; /* Single letter name of modifier */ -} mod_mask_table[] = -{ - {MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'M'}, - {MOD_MASK_META, MOD_MASK_META, (char_u)'T'}, - {MOD_MASK_CTRL, MOD_MASK_CTRL, (char_u)'C'}, - {MOD_MASK_SHIFT, MOD_MASK_SHIFT, (char_u)'S'}, - {MOD_MASK_MULTI_CLICK, MOD_MASK_2CLICK, (char_u)'2'}, - {MOD_MASK_MULTI_CLICK, MOD_MASK_3CLICK, (char_u)'3'}, - {MOD_MASK_MULTI_CLICK, MOD_MASK_4CLICK, (char_u)'4'}, - {MOD_MASK_CMD, MOD_MASK_CMD, (char_u)'D'}, +static const struct modmasktable { + uint16_t mod_mask; ///< Bit-mask for particular key modifier. + uint16_t mod_flag; ///< Bit(s) for particular key modifier. + char_u name; ///< Single letter name of modifier. +} mod_mask_table[] = { + { MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'M' }, + { MOD_MASK_META, MOD_MASK_META, (char_u)'T' }, + { MOD_MASK_CTRL, MOD_MASK_CTRL, (char_u)'C' }, + { MOD_MASK_SHIFT, MOD_MASK_SHIFT, (char_u)'S' }, + { MOD_MASK_MULTI_CLICK, MOD_MASK_2CLICK, (char_u)'2' }, + { MOD_MASK_MULTI_CLICK, MOD_MASK_3CLICK, (char_u)'3' }, + { MOD_MASK_MULTI_CLICK, MOD_MASK_4CLICK, (char_u)'4' }, + { MOD_MASK_CMD, MOD_MASK_CMD, (char_u)'D' }, // 'A' must be the last one - {MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'A'}, - {0, 0, NUL} + { MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'A' }, + { 0, 0, NUL } + // NOTE: when adding an entry, update MAX_KEY_NAME_LEN! }; /* @@ -139,11 +139,10 @@ static char_u modifier_keys_table[] = NUL }; -static struct key_name_entry { +static const struct key_name_entry { int key; // Special key code or ascii value - char *name; // Name of key -} key_names_table[] = -{ + const char *name; // Name of key +} key_names_table[] = { { ' ', "Space" }, { TAB, "Tab" }, { K_TAB, "Tab" }, @@ -286,7 +285,9 @@ static struct key_name_entry { { K_SNR, "SNR" }, { K_PLUG, "Plug" }, { K_PASTE, "Paste" }, + { K_COMMAND, "Cmd" }, { 0, NULL } + // NOTE: When adding a long name update MAX_KEY_NAME_LEN. }; static struct mousetable { @@ -318,73 +319,73 @@ static struct mousetable { {0, 0, 0, 0}, }; -/* - * Return the modifier mask bit (MOD_MASK_*) which corresponds to the given - * modifier name ('S' for Shift, 'C' for Ctrl etc). - */ +/// Return the modifier mask bit (#MOD_MASK_*) corresponding to mod name +/// +/// E.g. 'S' for shift, 'C' for ctrl. int name_to_mod_mask(int c) + FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT { - int i; - c = TOUPPER_ASC(c); - for (i = 0; mod_mask_table[i].mod_mask != 0; i++) - if (c == mod_mask_table[i].name) + for (size_t i = 0; mod_mask_table[i].mod_mask != 0; i++) { + if (c == mod_mask_table[i].name) { return mod_mask_table[i].mod_flag; + } + } return 0; } -/* - * Check if if there is a special key code for "key" that includes the - * modifiers specified. - */ -int simplify_key(int key, int *modifiers) +/// Check if there is a special key code for "key" with specified modifiers +/// +/// @param[in] key Initial key code. +/// @param[in,out] modifiers Initial modifiers, is adjusted to have simplified +/// modifiers. +/// +/// @return Simplified key code. +int simplify_key(const int key, int *modifiers) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - int i; - int key0; - int key1; - if (*modifiers & (MOD_MASK_SHIFT | MOD_MASK_CTRL | MOD_MASK_ALT)) { - /* TAB is a special case */ + // TAB is a special case. if (key == TAB && (*modifiers & MOD_MASK_SHIFT)) { *modifiers &= ~MOD_MASK_SHIFT; return K_S_TAB; } - key0 = KEY2TERMCAP0(key); - key1 = KEY2TERMCAP1(key); - for (i = 0; modifier_keys_table[i] != NUL; i += MOD_KEYS_ENTRY_SIZE) + const int key0 = KEY2TERMCAP0(key); + const int key1 = KEY2TERMCAP1(key); + for (int i = 0; modifier_keys_table[i] != NUL; i += MOD_KEYS_ENTRY_SIZE) { if (key0 == modifier_keys_table[i + 3] && key1 == modifier_keys_table[i + 4] && (*modifiers & modifier_keys_table[i])) { *modifiers &= ~modifier_keys_table[i]; return TERMCAP2KEY(modifier_keys_table[i + 1], - modifier_keys_table[i + 2]); + modifier_keys_table[i + 2]); } + } } return key; } -/* - * Change <xHome> to <Home>, <xUp> to <Up>, etc. - */ -int handle_x_keys(int key) +/// Change <xKey> to <Key> +int handle_x_keys(const int key) + FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT { switch (key) { - case K_XUP: return K_UP; - case K_XDOWN: return K_DOWN; - case K_XLEFT: return K_LEFT; - case K_XRIGHT: return K_RIGHT; - case K_XHOME: return K_HOME; - case K_ZHOME: return K_HOME; - case K_XEND: return K_END; - case K_ZEND: return K_END; - case K_XF1: return K_F1; - case K_XF2: return K_F2; - case K_XF3: return K_F3; - case K_XF4: return K_F4; - case K_S_XF1: return K_S_F1; - case K_S_XF2: return K_S_F2; - case K_S_XF3: return K_S_F3; - case K_S_XF4: return K_S_F4; + case K_XUP: return K_UP; + case K_XDOWN: return K_DOWN; + case K_XLEFT: return K_LEFT; + case K_XRIGHT: return K_RIGHT; + case K_XHOME: return K_HOME; + case K_ZHOME: return K_HOME; + case K_XEND: return K_END; + case K_ZEND: return K_END; + case K_XF1: return K_F1; + case K_XF2: return K_F2; + case K_XF3: return K_F3; + case K_XF4: return K_F4; + case K_S_XF1: return K_S_F1; + case K_S_XF2: return K_S_F2; + case K_S_XF3: return K_S_F3; + case K_S_XF4: return K_S_F4; } return key; } @@ -474,16 +475,20 @@ char_u *get_special_key_name(int c, int modifiers) string[idx++] = *s++; } } - } else { /* use name of special key */ - STRCPY(string + idx, key_names_table[table_idx].name); - idx = (int)STRLEN(string); + } else { // use name of special key + size_t len = STRLEN(key_names_table[table_idx].name); + + if ((int)len + idx + 2 <= MAX_KEY_NAME_LEN) { + STRCPY(string + idx, key_names_table[table_idx].name); + idx += (int)len; + } } string[idx++] = '>'; string[idx] = NUL; return string; } -/// Try translating a <> name +/// Try translating a <> name ("keycode"). /// /// @param[in,out] srcp Source from which <> are translated. Is advanced to /// after the <> name if there is a match. @@ -508,7 +513,7 @@ unsigned int trans_special(const char_u **srcp, const size_t src_len, return 0; } - /* Put the appropriate modifier in a string */ + // Put the appropriate modifier in a string. if (modifiers != 0) { dst[dlen++] = K_SPECIAL; dst[dlen++] = KS_MODIFIER; @@ -569,15 +574,11 @@ int find_special_key(const char_u **srcp, const size_t src_len, int *const modp, // Find end of modifier list last_dash = src; - for (bp = src + 1; bp <= end && (*bp == '-' || vim_isIDc(*bp)); bp++) { + for (bp = src + 1; bp <= end && (*bp == '-' || ascii_isident(*bp)); bp++) { if (*bp == '-') { last_dash = bp; if (bp + 1 <= end) { - if (has_mbyte) { - l = mb_ptr2len_len(bp + 1, (int) (end - bp) + 1); - } else { - l = 1; - } + l = utfc_ptr2len_len(bp + 1, (int)(end - bp) + 1); // Anything accepted, like <C-?>. // <C-"> or <M-"> are not special in strings as " is // the string delimiter. With a backslash it works: <M-\"> @@ -625,9 +626,11 @@ int find_special_key(const char_u **srcp, const size_t src_len, int *const modp, // Modifier with single letter, or special key name. if (in_string && last_dash[1] == '\\' && last_dash[2] == '"') { - off = 2; + // Special case for a double-quoted string + off = l = 2; + } else { + l = mb_ptr2len(last_dash + 1); } - l = mb_ptr2len(last_dash + 1); if (modifiers != 0 && last_dash[l + 1] == '>') { key = PTR2CHAR(last_dash + off); } else { @@ -702,33 +705,39 @@ int find_special_key_in_table(int c) { int i; - for (i = 0; key_names_table[i].name != NULL; i++) - if (c == key_names_table[i].key) + for (i = 0; key_names_table[i].name != NULL; i++) { + if (c == key_names_table[i].key) { break; - if (key_names_table[i].name == NULL) + } + } + if (key_names_table[i].name == NULL) { i = -1; + } return i; } -/* - * Find the special key with the given name (the given string does not have to - * end with NUL, the name is assumed to end before the first non-idchar). - * If the name starts with "t_" the next two characters are interpreted as a - * termcap name. - * Return the key code, or 0 if not found. - */ +/// Find the special key with the given name +/// +/// @param[in] name Name of the special. Does not have to end with NUL, it is +/// assumed to end before the first non-idchar. If name starts +/// with "t_" the next two characters are interpreted as +/// a termcap name. +/// +/// @return Key code or 0 if not found. int get_special_key_code(const char_u *name) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - char *table_name; - int i, j; - - for (i = 0; key_names_table[i].name != NULL; i++) { - table_name = key_names_table[i].name; - for (j = 0; vim_isIDc(name[j]) && table_name[j] != NUL; j++) - if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC(name[j])) + for (int i = 0; key_names_table[i].name != NULL; i++) { + const char *const table_name = key_names_table[i].name; + int j; + for (j = 0; ascii_isident(name[j]) && table_name[j] != NUL; j++) { + if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC(name[j])) { break; - if (!vim_isIDc(name[j]) && table_name[j] == NUL) + } + } + if (!ascii_isident(name[j]) && table_name[j] == NUL) { return key_names_table[i].key; + } } return 0; @@ -834,7 +843,7 @@ char_u *replace_termcodes(const char_u *from, const size_t from_len, } slen = trans_special(&src, (size_t)(end - src) + 1, result + dlen, true, - true); + false); if (slen) { dlen += slen; continue; diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h index ee64854c98..c64691e8ea 100644 --- a/src/nvim/keymap.h +++ b/src/nvim/keymap.h @@ -243,6 +243,7 @@ enum key_extra { , KE_EVENT // event , KE_PASTE // special key to toggle the 'paste' option. // sent only by UIs + , KE_COMMAND // special key to execute command in any mode }; /* @@ -431,6 +432,7 @@ enum key_extra { #define K_EVENT TERMCAP2KEY(KS_EXTRA, KE_EVENT) #define K_PASTE TERMCAP2KEY(KS_EXTRA, KE_PASTE) +#define K_COMMAND TERMCAP2KEY(KS_EXTRA, KE_COMMAND) /* Bits for modifier mask */ /* 0x01 cannot be used, because the modifier must be 0x02 or higher */ @@ -441,16 +443,17 @@ enum key_extra { #define MOD_MASK_2CLICK 0x20 // use MOD_MASK_MULTI_CLICK #define MOD_MASK_3CLICK 0x40 // use MOD_MASK_MULTI_CLICK #define MOD_MASK_4CLICK 0x60 // use MOD_MASK_MULTI_CLICK -#define MOD_MASK_CMD 0x80 // "super" key (OSX/Mac: command-key) +#define MOD_MASK_CMD 0x80 // "super" key (macOS: command-key) #define MOD_MASK_MULTI_CLICK (MOD_MASK_2CLICK|MOD_MASK_3CLICK| \ MOD_MASK_4CLICK) /* * The length of the longest special key name, including modifiers. - * Current longest is <M-C-S-T-4-MiddleRelease> (length includes '<' and '>'). + * Current longest is <M-C-S-T-D-A-4-ScrollWheelRight> (length includes '<' and + * '>'). */ -#define MAX_KEY_NAME_LEN 25 +#define MAX_KEY_NAME_LEN 32 // Maximum length of a special key event as tokens. This includes modifiers. // The longest event is something like <M-C-S-T-4-LeftDrag> which would be the diff --git a/src/nvim/lib/kbtree.h b/src/nvim/lib/kbtree.h index a3943054e6..e2688064a8 100644 --- a/src/nvim/lib/kbtree.h +++ b/src/nvim/lib/kbtree.h @@ -390,34 +390,14 @@ #define KBTREE_INIT(name, key_t, __cmp, T) \ KBTREE_INIT_IMPL(name, key_t, kbnode_##name##_t, __cmp, T, (sizeof(kbnode_##name##_t)+(2*T)*sizeof(void *))) -#if (!defined(__clang__) && !defined(__INTEL_COMPILER)) && (__GNUC__ > 4 ) - -// The index trickery shouldn't be UB anymore, -// still it is to much for gcc:s -Werror=array-bounds -# define __KB_PRAGMA_START \ - _Pragma("GCC diagnostic push") \ - _Pragma("GCC diagnostic ignored \"-Warray-bounds\"") - -# define __KB_PRAGMA_END \ - _Pragma("GCC diagnostic pop") \ - -#else - -# define __KB_PRAGMA_START -# define __KB_PRAGMA_END - -#endif - #define KBTREE_INIT_IMPL(name, key_t, kbnode_t, __cmp, T, ILEN) \ - __KB_PRAGMA_START \ __KB_TREE_T(name, key_t, T) \ __KB_GET_AUX1(name, key_t, kbnode_t, __cmp) \ __KB_GET(name, key_t, kbnode_t) \ __KB_INTERVAL(name, key_t, kbnode_t) \ __KB_PUT(name, key_t, kbnode_t, __cmp, T, ILEN) \ __KB_DEL(name, key_t, kbnode_t, T) \ - __KB_ITR(name, key_t, kbnode_t) \ - __KB_PRAGMA_END + __KB_ITR(name, key_t, kbnode_t) #define KB_DEFAULT_SIZE 512 diff --git a/src/nvim/lib/kvec.h b/src/nvim/lib/kvec.h index 584282d773..ad56c9237b 100644 --- a/src/nvim/lib/kvec.h +++ b/src/nvim/lib/kvec.h @@ -41,6 +41,7 @@ #include <string.h> #include "nvim/memory.h" +#include "nvim/os/os_defs.h" #define kv_roundup32(x) \ ((--(x)), \ @@ -62,7 +63,16 @@ #define kv_pop(v) ((v).items[--(v).size]) #define kv_size(v) ((v).size) #define kv_max(v) ((v).capacity) -#define kv_last(v) kv_A(v, kv_size(v) - 1) +#define kv_Z(v, i) kv_A(v, kv_size(v) - (i) - 1) +#define kv_last(v) kv_Z(v, 0) + +/// Drop last n items from kvec without resizing +/// +/// Previously spelled as `(void)kv_pop(v)`, repeated n times. +/// +/// @param[out] v Kvec to drop items from. +/// @param[in] n Number of elements to drop. +#define kv_drop(v, n) ((v).size -= (n)) #define kv_resize(v, s) \ ((v).capacity = (s), \ diff --git a/src/nvim/lib/ringbuf.h b/src/nvim/lib/ringbuf.h index 12b75ec65a..e63eae70b0 100644 --- a/src/nvim/lib/ringbuf.h +++ b/src/nvim/lib/ringbuf.h @@ -15,6 +15,7 @@ #ifndef NVIM_LIB_RINGBUF_H #define NVIM_LIB_RINGBUF_H +#include <stddef.h> #include <string.h> #include <assert.h> #include <stdint.h> @@ -73,6 +74,32 @@ typedef struct { \ RBType *buf_end; \ } TypeName##RingBuffer; +/// Dummy item free macros, for use in RINGBUF_INIT +/// +/// This macros actually does nothing. +/// +/// @param[in] item Item to be freed. +#define RINGBUF_DUMMY_FREE(item) + +/// Static ring buffer +/// +/// @warning Ring buffers created with this macros must neither be freed nor +/// deallocated. +/// +/// @param scope Ring buffer scope. +/// @param TypeName Ring buffer type name. +/// @param RBType Type of the single ring buffer element. +/// @param varname Variable name. +/// @param rbsize Ring buffer size. +#define RINGBUF_STATIC(scope, TypeName, RBType, varname, rbsize) \ +static RBType _##varname##_buf[rbsize]; \ +scope TypeName##RingBuffer varname = { \ + .buf = _##varname##_buf, \ + .next = _##varname##_buf, \ + .first = NULL, \ + .buf_end = _##varname##_buf + rbsize - 1, \ +}; + /// Initialize a new ring buffer /// /// @param TypeName Ring buffer type name. Actual type name will be diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index cacba3ce87..5da6d2c0a0 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -211,20 +211,28 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) size_t len; const char *s = lua_tolstring(lstate, -2, &len); if (cur.special) { - list_T *const kv_pair = tv_list_alloc(); - tv_list_append_list(cur.tv->vval.v_list, kv_pair); - listitem_T *const key = tv_list_item_alloc(); - key->li_tv = decode_string(s, len, kTrue, false, false); - tv_list_append(kv_pair, key); - if (key->li_tv.v_type == VAR_UNKNOWN) { + list_T *const kv_pair = tv_list_alloc(2); + + typval_T s_tv = decode_string(s, len, kTrue, false, false); + if (s_tv.v_type == VAR_UNKNOWN) { ret = false; tv_list_unref(kv_pair); continue; } - listitem_T *const val = tv_list_item_alloc(); - tv_list_append(kv_pair, val); + tv_list_append_owned_tv(kv_pair, s_tv); + + // Value: not populated yet, need to create list item to push. + tv_list_append_owned_tv(kv_pair, (typval_T) { + .v_type = VAR_UNKNOWN, + }); kv_push(stack, cur); - cur = (TVPopStackItem) { &val->li_tv, false, false, 0 }; + tv_list_append_list(cur.tv->vval.v_list, kv_pair); + cur = (TVPopStackItem) { + .tv = TV_LIST_ITEM_TV(tv_list_last(kv_pair)), + .container = false, + .special = false, + .idx = 0, + }; } else { dictitem_T *const di = tv_dict_item_alloc_len(s, len); if (tv_dict_add(cur.tv->vval.v_dict, di) == FAIL) { @@ -239,15 +247,23 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) } } else { assert(cur.tv->v_type == VAR_LIST); - lua_rawgeti(lstate, -1, cur.tv->vval.v_list->lv_len + 1); + lua_rawgeti(lstate, -1, tv_list_len(cur.tv->vval.v_list) + 1); if (lua_isnil(lstate, -1)) { lua_pop(lstate, 2); continue; } - listitem_T *const li = tv_list_item_alloc(); - tv_list_append(cur.tv->vval.v_list, li); + // Not populated yet, need to create list item to push. + tv_list_append_owned_tv(cur.tv->vval.v_list, (typval_T) { + .v_type = VAR_UNKNOWN, + }); kv_push(stack, cur); - cur = (TVPopStackItem) { &li->li_tv, false, false, 0 }; + // TODO(ZyX-I): Use indexes, here list item *will* be reallocated. + cur = (TVPopStackItem) { + .tv = TV_LIST_ITEM_TV(tv_list_last(cur.tv->vval.v_list)), + .container = false, + .special = false, + .idx = 0, + }; } } assert(!cur.container); @@ -305,8 +321,8 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) switch (table_props.type) { case kObjectTypeArray: { cur.tv->v_type = VAR_LIST; - cur.tv->vval.v_list = tv_list_alloc(); - cur.tv->vval.v_list->lv_refcount++; + cur.tv->vval.v_list = tv_list_alloc((ptrdiff_t)table_props.maxidx); + tv_list_ref(cur.tv->vval.v_list); if (table_props.maxidx != 0) { cur.container = true; cur.idx = lua_gettop(lstate); @@ -322,7 +338,8 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) } else { cur.special = table_props.has_string_with_nul; if (table_props.has_string_with_nul) { - decode_create_map_special_dict(cur.tv); + decode_create_map_special_dict( + cur.tv, (ptrdiff_t)table_props.string_keys_num); assert(cur.tv->v_type == VAR_DICT); dictitem_T *const val_di = tv_dict_find(cur.tv->vval.v_dict, S_LEN("_VAL")); @@ -406,7 +423,7 @@ nlua_pop_typval_table_processing_end: #define TYPVAL_ENCODE_CONV_STR_STRING TYPVAL_ENCODE_CONV_STRING #define TYPVAL_ENCODE_CONV_EXT_STRING(tv, str, len, type) \ - TYPVAL_ENCODE_CONV_NIL() + TYPVAL_ENCODE_CONV_NIL(tv) #define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ do { \ diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 8e7b8a1824..02eabb9c89 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -40,66 +40,6 @@ typedef struct { /// Name of the run code for use in messages #define NLUA_EVAL_NAME "<VimL compiled string>" -/// Call C function which does not expect any arguments -/// -/// @param function Called function -/// @param numret Number of returned arguments -#define NLUA_CALL_C_FUNCTION_0(lstate, function, numret) \ - do { \ - lua_pushcfunction(lstate, &function); \ - lua_call(lstate, 0, numret); \ - } while (0) -/// Call C function which expects one argument -/// -/// @param function Called function -/// @param numret Number of returned arguments -/// @param a… Supplied argument (should be a void* pointer) -#define NLUA_CALL_C_FUNCTION_1(lstate, function, numret, a1) \ - do { \ - lua_pushcfunction(lstate, &function); \ - lua_pushlightuserdata(lstate, a1); \ - lua_call(lstate, 1, numret); \ - } while (0) -/// Call C function which expects two arguments -/// -/// @param function Called function -/// @param numret Number of returned arguments -/// @param a… Supplied argument (should be a void* pointer) -#define NLUA_CALL_C_FUNCTION_2(lstate, function, numret, a1, a2) \ - do { \ - lua_pushcfunction(lstate, &function); \ - lua_pushlightuserdata(lstate, a1); \ - lua_pushlightuserdata(lstate, a2); \ - lua_call(lstate, 2, numret); \ - } while (0) -/// Call C function which expects three arguments -/// -/// @param function Called function -/// @param numret Number of returned arguments -/// @param a… Supplied argument (should be a void* pointer) -#define NLUA_CALL_C_FUNCTION_3(lstate, function, numret, a1, a2, a3) \ - do { \ - lua_pushcfunction(lstate, &function); \ - lua_pushlightuserdata(lstate, a1); \ - lua_pushlightuserdata(lstate, a2); \ - lua_pushlightuserdata(lstate, a3); \ - lua_call(lstate, 3, numret); \ - } while (0) -/// Call C function which expects five arguments -/// -/// @param function Called function -/// @param numret Number of returned arguments -/// @param a… Supplied argument (should be a void* pointer) -#define NLUA_CALL_C_FUNCTION_4(lstate, function, numret, a1, a2, a3, a4) \ - do { \ - lua_pushcfunction(lstate, &function); \ - lua_pushlightuserdata(lstate, a1); \ - lua_pushlightuserdata(lstate, a2); \ - lua_pushlightuserdata(lstate, a3); \ - lua_pushlightuserdata(lstate, a4); \ - lua_call(lstate, 4, numret); \ - } while (0) - /// Convert lua error into a Vim error message /// /// @param lstate Lua interpreter state. @@ -163,123 +103,6 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL return 1; } -/// Evaluate lua string -/// -/// Expects two values on the stack: string to evaluate, pointer to the -/// location where result is saved. Always returns nothing (from the lua point -/// of view). -static int nlua_exec_lua_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL -{ - const String *const str = (const String *)lua_touserdata(lstate, 1); - typval_T *const ret_tv = (typval_T *)lua_touserdata(lstate, 2); - lua_pop(lstate, 2); - - if (luaL_loadbuffer(lstate, str->data, str->size, NLUA_EVAL_NAME)) { - nlua_error(lstate, _("E5104: Error while creating lua chunk: %.*s")); - return 0; - } - if (lua_pcall(lstate, 0, 1, 0)) { - nlua_error(lstate, _("E5105: Error while calling lua chunk: %.*s")); - return 0; - } - if (!nlua_pop_typval(lstate, ret_tv)) { - return 0; - } - return 0; -} - -/// Evaluate lua string for each line in range -/// -/// Expects two values on the stack: string to evaluate and pointer to integer -/// array with line range. Always returns nothing (from the lua point of view). -static int nlua_exec_luado_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL -{ - const String *const str = (const String *)lua_touserdata(lstate, 1); - const linenr_T *const range = (const linenr_T *)lua_touserdata(lstate, 2); - lua_pop(lstate, 2); - -#define DOSTART "return function(line, linenr) " -#define DOEND " end" - const size_t lcmd_len = (str->size - + (sizeof(DOSTART) - 1) - + (sizeof(DOEND) - 1)); - char *lcmd; - if (lcmd_len < IOSIZE) { - lcmd = (char *)IObuff; - } else { - lcmd = xmalloc(lcmd_len + 1); - } - memcpy(lcmd, DOSTART, sizeof(DOSTART) - 1); - memcpy(lcmd + sizeof(DOSTART) - 1, str->data, str->size); - memcpy(lcmd + sizeof(DOSTART) - 1 + str->size, DOEND, sizeof(DOEND) - 1); -#undef DOSTART -#undef DOEND - - if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { - nlua_error(lstate, _("E5109: Error while creating lua chunk: %.*s")); - if (lcmd_len >= IOSIZE) { - xfree(lcmd); - } - return 0; - } - if (lcmd_len >= IOSIZE) { - xfree(lcmd); - } - if (lua_pcall(lstate, 0, 1, 0)) { - nlua_error(lstate, _("E5110: Error while creating lua function: %.*s")); - return 0; - } - for (linenr_T l = range[0]; l <= range[1]; l++) { - if (l > curbuf->b_ml.ml_line_count) { - break; - } - lua_pushvalue(lstate, -1); - lua_pushstring(lstate, (const char *)ml_get_buf(curbuf, l, false)); - lua_pushnumber(lstate, (lua_Number)l); - if (lua_pcall(lstate, 2, 1, 0)) { - nlua_error(lstate, _("E5111: Error while calling lua function: %.*s")); - break; - } - if (lua_isstring(lstate, -1)) { - size_t new_line_len; - const char *const new_line = lua_tolstring(lstate, -1, &new_line_len); - char *const new_line_transformed = xmemdupz(new_line, new_line_len); - for (size_t i = 0; i < new_line_len; i++) { - if (new_line_transformed[i] == NUL) { - new_line_transformed[i] = '\n'; - } - } - ml_replace(l, (char_u *)new_line_transformed, false); - changed_bytes(l, 0); - } - lua_pop(lstate, 1); - } - lua_pop(lstate, 1); - check_cursor(); - update_screen(NOT_VALID); - return 0; -} - -/// Evaluate lua file -/// -/// Expects one value on the stack: file to evaluate. Always returns nothing -/// (from the lua point of view). -static int nlua_exec_lua_file(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL -{ - const char *const filename = (const char *)lua_touserdata(lstate, 1); - lua_pop(lstate, 1); - - if (luaL_loadfile(lstate, filename)) { - nlua_error(lstate, _("E5112: Error while creating lua chunk: %.*s")); - return 0; - } - if (lua_pcall(lstate, 0, 0, 0)) { - nlua_error(lstate, _("E5113: Error while calling lua chunk: %.*s")); - return 0; - } - return 0; -} - /// Initialize lua interpreter state /// /// Called by lua interpreter itself to initialize state. @@ -327,7 +150,7 @@ static lua_State *nlua_init(void) preserve_exit(); } luaL_openlibs(lstate); - NLUA_CALL_C_FUNCTION_0(lstate, nlua_state_init, 0); + nlua_state_init(lstate); return lstate; } @@ -378,108 +201,18 @@ static lua_State *nlua_enter(void) void executor_exec_lua(const String str, typval_T *const ret_tv) FUNC_ATTR_NONNULL_ALL { - NLUA_CALL_C_FUNCTION_2(nlua_enter(), nlua_exec_lua_string, 0, - (void *)&str, ret_tv); -} + lua_State *const lstate = nlua_enter(); -/// Evaluate lua string -/// -/// Used for luaeval(). Expects three values on the stack: -/// -/// 1. String to evaluate. -/// 2. _A value. -/// 3. Pointer to location where result is saved. -/// -/// @param[in,out] lstate Lua interpreter state. -static int nlua_eval_lua_string(lua_State *const lstate) - FUNC_ATTR_NONNULL_ALL -{ - const String *const str = (const String *)lua_touserdata(lstate, 1); - typval_T *const arg = (typval_T *)lua_touserdata(lstate, 2); - typval_T *const ret_tv = (typval_T *)lua_touserdata(lstate, 3); - lua_pop(lstate, 3); - - garray_T str_ga; - ga_init(&str_ga, 1, 80); -#define EVALHEADER "local _A=select(1,...) return (" - const size_t lcmd_len = sizeof(EVALHEADER) - 1 + str->size + 1; - char *lcmd; - if (lcmd_len < IOSIZE) { - lcmd = (char *)IObuff; - } else { - lcmd = xmalloc(lcmd_len); - } - memcpy(lcmd, EVALHEADER, sizeof(EVALHEADER) - 1); - memcpy(lcmd + sizeof(EVALHEADER) - 1, str->data, str->size); - lcmd[lcmd_len - 1] = ')'; -#undef EVALHEADER - if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { - nlua_error(lstate, - _("E5107: Error while creating lua chunk for luaeval(): %.*s")); - if (lcmd != (char *)IObuff) { - xfree(lcmd); - } - return 0; - } - if (lcmd != (char *)IObuff) { - xfree(lcmd); - } - - if (arg == NULL || arg->v_type == VAR_UNKNOWN) { - lua_pushnil(lstate); - } else { - nlua_push_typval(lstate, arg); - } - if (lua_pcall(lstate, 1, 1, 0)) { - nlua_error(lstate, - _("E5108: Error while calling lua chunk for luaeval(): %.*s")); - return 0; - } - if (!nlua_pop_typval(lstate, ret_tv)) { - return 0; - } - - return 0; -} - -/// Evaluate lua string -/// -/// Expects four values on the stack: string to evaluate, pointer to args array, -/// and locations where result and error are saved, respectively. Always -/// returns nothing (from the lua point of view). -static int nlua_exec_lua_string_api(lua_State *const lstate) - FUNC_ATTR_NONNULL_ALL -{ - const String *str = (const String *)lua_touserdata(lstate, 1); - const Array *args = (const Array *)lua_touserdata(lstate, 2); - Object *retval = (Object *)lua_touserdata(lstate, 3); - Error *err = (Error *)lua_touserdata(lstate, 4); - - lua_pop(lstate, 4); - - if (luaL_loadbuffer(lstate, str->data, str->size, "<nvim>")) { - size_t len; - const char *str = lua_tolstring(lstate, -1, &len); - api_set_error(err, kErrorTypeValidation, - "Error loading lua: %.*s", (int)len, str); - return 0; - } - - for (size_t i = 0; i < args->size; i++) { - nlua_push_Object(lstate, args->items[i]); + if (luaL_loadbuffer(lstate, str.data, str.size, NLUA_EVAL_NAME)) { + nlua_error(lstate, _("E5104: Error while creating lua chunk: %.*s")); + return; } - - if (lua_pcall(lstate, (int)args->size, 1, 0)) { - size_t len; - const char *str = lua_tolstring(lstate, -1, &len); - api_set_error(err, kErrorTypeException, - "Error executing lua: %.*s", (int)len, str); - return 0; + if (lua_pcall(lstate, 0, 1, 0)) { + nlua_error(lstate, _("E5105: Error while calling lua chunk: %.*s")); + return; } - *retval = nlua_pop_Object(lstate, err); - - return 0; + nlua_pop_typval(lstate, ret_tv); } /// Print as a Vim message @@ -617,8 +350,46 @@ void executor_eval_lua(const String str, typval_T *const arg, typval_T *const ret_tv) FUNC_ATTR_NONNULL_ALL { - NLUA_CALL_C_FUNCTION_3(nlua_enter(), nlua_eval_lua_string, 0, - (void *)&str, arg, ret_tv); + lua_State *const lstate = nlua_enter(); + + garray_T str_ga; + ga_init(&str_ga, 1, 80); +#define EVALHEADER "local _A=select(1,...) return (" + const size_t lcmd_len = sizeof(EVALHEADER) - 1 + str.size + 1; + char *lcmd; + if (lcmd_len < IOSIZE) { + lcmd = (char *)IObuff; + } else { + lcmd = xmalloc(lcmd_len); + } + memcpy(lcmd, EVALHEADER, sizeof(EVALHEADER) - 1); + memcpy(lcmd + sizeof(EVALHEADER) - 1, str.data, str.size); + lcmd[lcmd_len - 1] = ')'; +#undef EVALHEADER + if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { + nlua_error(lstate, + _("E5107: Error while creating lua chunk for luaeval(): %.*s")); + if (lcmd != (char *)IObuff) { + xfree(lcmd); + } + return; + } + if (lcmd != (char *)IObuff) { + xfree(lcmd); + } + + if (arg->v_type == VAR_UNKNOWN) { + lua_pushnil(lstate); + } else { + nlua_push_typval(lstate, arg); + } + if (lua_pcall(lstate, 1, 1, 0)) { + nlua_error(lstate, + _("E5108: Error while calling lua chunk for luaeval(): %.*s")); + return; + } + + nlua_pop_typval(lstate, ret_tv); } /// Execute lua string @@ -632,10 +403,29 @@ void executor_eval_lua(const String str, typval_T *const arg, /// @return Return value of the execution. Object executor_exec_lua_api(const String str, const Array args, Error *err) { - Object retval = NIL; - NLUA_CALL_C_FUNCTION_4(nlua_enter(), nlua_exec_lua_string_api, 0, - (void *)&str, (void *)&args, &retval, err); - return retval; + lua_State *const lstate = nlua_enter(); + + if (luaL_loadbuffer(lstate, str.data, str.size, "<nvim>")) { + size_t len; + const char *errstr = lua_tolstring(lstate, -1, &len); + api_set_error(err, kErrorTypeValidation, + "Error loading lua: %.*s", (int)len, errstr); + return NIL; + } + + for (size_t i = 0; i < args.size; i++) { + nlua_push_Object(lstate, args.items[i]); + } + + if (lua_pcall(lstate, (int)args.size, 1, 0)) { + size_t len; + const char *errstr = lua_tolstring(lstate, -1, &len); + api_set_error(err, kErrorTypeException, + "Error executing lua: %.*s", (int)len, errstr); + return NIL; + } + + return nlua_pop_Object(lstate, err); } @@ -671,13 +461,70 @@ void ex_luado(exarg_T *const eap) EMSG(_("cannot save undo information")); return; } - const String cmd = { - .size = STRLEN(eap->arg), - .data = (char *)eap->arg, - }; - const linenr_T range[] = { eap->line1, eap->line2 }; - NLUA_CALL_C_FUNCTION_2(nlua_enter(), nlua_exec_luado_string, 0, - (void *)&cmd, (void *)range); + const char *const cmd = (const char *)eap->arg; + const size_t cmd_len = strlen(cmd); + + lua_State *const lstate = nlua_enter(); + +#define DOSTART "return function(line, linenr) " +#define DOEND " end" + const size_t lcmd_len = (cmd_len + + (sizeof(DOSTART) - 1) + + (sizeof(DOEND) - 1)); + char *lcmd; + if (lcmd_len < IOSIZE) { + lcmd = (char *)IObuff; + } else { + lcmd = xmalloc(lcmd_len + 1); + } + memcpy(lcmd, DOSTART, sizeof(DOSTART) - 1); + memcpy(lcmd + sizeof(DOSTART) - 1, cmd, cmd_len); + memcpy(lcmd + sizeof(DOSTART) - 1 + cmd_len, DOEND, sizeof(DOEND) - 1); +#undef DOSTART +#undef DOEND + + if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { + nlua_error(lstate, _("E5109: Error while creating lua chunk: %.*s")); + if (lcmd_len >= IOSIZE) { + xfree(lcmd); + } + return; + } + if (lcmd_len >= IOSIZE) { + xfree(lcmd); + } + if (lua_pcall(lstate, 0, 1, 0)) { + nlua_error(lstate, _("E5110: Error while creating lua function: %.*s")); + return; + } + for (linenr_T l = eap->line1; l <= eap->line2; l++) { + if (l > curbuf->b_ml.ml_line_count) { + break; + } + lua_pushvalue(lstate, -1); + lua_pushstring(lstate, (const char *)ml_get_buf(curbuf, l, false)); + lua_pushnumber(lstate, (lua_Number)l); + if (lua_pcall(lstate, 2, 1, 0)) { + nlua_error(lstate, _("E5111: Error while calling lua function: %.*s")); + break; + } + if (lua_isstring(lstate, -1)) { + size_t new_line_len; + const char *const new_line = lua_tolstring(lstate, -1, &new_line_len); + char *const new_line_transformed = xmemdupz(new_line, new_line_len); + for (size_t i = 0; i < new_line_len; i++) { + if (new_line_transformed[i] == NUL) { + new_line_transformed[i] = '\n'; + } + } + ml_replace(l, (char_u *)new_line_transformed, false); + changed_bytes(l, 0); + } + lua_pop(lstate, 1); + } + lua_pop(lstate, 1); + check_cursor(); + update_screen(NOT_VALID); } /// Run lua file @@ -688,6 +535,15 @@ void ex_luado(exarg_T *const eap) void ex_luafile(exarg_T *const eap) FUNC_ATTR_NONNULL_ALL { - NLUA_CALL_C_FUNCTION_1(nlua_enter(), nlua_exec_lua_file, 0, - (void *)eap->arg); + lua_State *const lstate = nlua_enter(); + + if (luaL_loadfile(lstate, (const char *)eap->arg)) { + nlua_error(lstate, _("E5112: Error while creating lua chunk: %.*s")); + return; + } + + if (lua_pcall(lstate, 0, 0, 0)) { + nlua_error(lstate, _("E5113: Error while calling lua chunk: %.*s")); + return; + } } diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index c7952520b0..e1bbb03d3f 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -1,3 +1,62 @@ +-- Internal-only until comments in #8107 are addressed. +-- Returns: +-- {errcode}, {output} +local function _system(cmd) + local out = vim.api.nvim_call_function('system', { cmd }) + local err = vim.api.nvim_get_vvar('shell_error') + return err, out +end + +-- Gets process info from the `ps` command. +-- Used by nvim_get_proc() as a fallback. +local function _os_proc_info(pid) + if pid == nil or pid <= 0 or type(pid) ~= 'number' then + error('invalid pid') + end + local cmd = { 'ps', '-p', pid, '-o', 'ucomm=', } + local err, name = _system(cmd) + if 1 == err and string.gsub(name, '%s*', '') == '' then + return {} -- Process not found. + elseif 0 ~= err then + local args_str = vim.api.nvim_call_function('string', { cmd }) + error('command failed: '..args_str) + end + local _, ppid = _system({ 'ps', '-p', pid, '-o', 'ppid=', }) + -- Remove trailing whitespace. + name = string.gsub(name, '%s+$', '') + ppid = string.gsub(ppid, '%s+$', '') + ppid = tonumber(ppid) == nil and -1 or tonumber(ppid) + return { + name = name, + pid = pid, + ppid = ppid, + } +end + +-- Gets process children from the `pgrep` command. +-- Used by nvim_get_proc_children() as a fallback. +local function _os_proc_children(ppid) + if ppid == nil or ppid <= 0 or type(ppid) ~= 'number' then + error('invalid ppid') + end + local cmd = { 'pgrep', '-P', ppid, } + local err, rv = _system(cmd) + if 1 == err and string.gsub(rv, '%s*', '') == '' then + return {} -- Process not found. + elseif 0 ~= err then + local args_str = vim.api.nvim_call_function('string', { cmd }) + error('command failed: '..args_str) + end + local children = {} + for s in string.gmatch(rv, '%S+') do + local i = tonumber(s) + if i ~= nil then + table.insert(children, i) + end + end + 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 @@ -58,7 +117,12 @@ local function _update_package_paths() end last_nvim_paths = cur_nvim_paths end ---{{{1 Module definition -return { + +local module = { _update_package_paths = _update_package_paths, + _os_proc_children = _os_proc_children, + _os_proc_info = _os_proc_info, + _system = _system, } + +return module diff --git a/src/nvim/macros.h b/src/nvim/macros.h index 26d4f74b6a..348df2d9b6 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -28,15 +28,11 @@ /// @return `s, sizeof(s) - 1` #define S_LEN(s) (s), (sizeof(s) - 1) -/* - * lineempty() - return TRUE if the line is empty - */ -#define lineempty(p) (*ml_get(p) == NUL) +/// LINEEMPTY() - return TRUE if the line is empty +#define LINEEMPTY(p) (*ml_get(p) == NUL) -/* - * bufempty() - return TRUE if the current buffer is empty - */ -#define bufempty() (curbuf->b_ml.ml_line_count == 1 && *ml_get((linenr_T)1) == \ +/// BUFEMPTY() - return TRUE if the current buffer is empty +#define BUFEMPTY() (curbuf->b_ml.ml_line_count == 1 && *ml_get((linenr_T)1) == \ NUL) /* @@ -75,7 +71,7 @@ do { \ if (*p_langmap \ && (condition) \ - && (p_lrm || KeyTyped) \ + && (p_lrm || (vgetc_busy ? typebuf_maplen() == 0 : KeyTyped)) \ && !KeyStuffed \ && (c) >= 0) \ { \ @@ -140,15 +136,24 @@ # define RESET_BINDING(wp) (wp)->w_p_scb = FALSE; (wp)->w_p_crb = FALSE -/// Calculate the length of a C array. +/// Calculate the length of a C array +/// +/// This should be called with a real array. Calling this with a pointer is an +/// error. A mechanism to detect many (though not all) of those errors at +/// compile time is implemented. It works by the second division producing +/// a division by zero in those cases (-Wdiv-by-zero in GCC). +#define ARRAY_SIZE(arr) \ + ((sizeof(arr)/sizeof((arr)[0])) \ + / ((size_t)(!(sizeof(arr) % sizeof((arr)[0]))))) + +/// Get last array entry /// /// This should be called with a real array. Calling this with a pointer is an -/// error. A mechanism to detect many (though not all) of those errors at compile -/// time is implemented. It works by the second division producing a division by -/// zero in those cases (-Wdiv-by-zero in GCC). -#define ARRAY_SIZE(arr) ((sizeof(arr)/sizeof((arr)[0])) / ((size_t)(!(sizeof(arr) % sizeof((arr)[0]))))) +/// error. +#define ARRAY_LAST_ENTRY(arr) (arr)[ARRAY_SIZE(arr) - 1] -#define RGB(r, g, b) ((r << 16) | (g << 8) | b) +// Duplicated in os/win_defs.h to avoid include-order sensitivity. +#define RGB_(r, g, b) ((r << 16) | (g << 8) | b) #define STR_(x) #x #define STR(x) STR_(x) @@ -183,4 +188,19 @@ /// @return ((Type *)obj). #define STRUCT_CAST(Type, obj) ((Type *)(obj)) +// Type of uv_buf_t.len is platform-dependent. +// Related: https://github.com/libuv/libuv/pull/1236 +#if defined(WIN32) +# define UV_BUF_LEN(x) (ULONG)(x) +#else +# define UV_BUF_LEN(x) (x) +#endif + +// Type of read()/write() `count` param is platform-dependent. +#if defined(WIN32) +# define IO_COUNT(x) (unsigned)(x) +#else +# define IO_COUNT(x) (x) +#endif + #endif // NVIM_MACROS_H diff --git a/src/nvim/main.c b/src/nvim/main.c index 6e2359dcab..067184c8c7 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -7,11 +7,6 @@ #include <string.h> #include <stdbool.h> -#ifdef WIN32 -# include <wchar.h> -# include <winnls.h> -#endif - #include <msgpack.h> #include "nvim/ascii.h" @@ -77,30 +72,30 @@ # include "nvim/os/pty_process_unix.h" #endif -/* Maximum number of commands from + or -c arguments. */ +// Maximum number of commands from + or -c arguments. #define MAX_ARG_CMDS 10 -/* values for "window_layout" */ -#define WIN_HOR 1 /* "-o" horizontally split windows */ -#define WIN_VER 2 /* "-O" vertically split windows */ -#define WIN_TABS 3 /* "-p" windows on tab pages */ +// values for "window_layout" +#define WIN_HOR 1 // "-o" horizontally split windows +#define WIN_VER 2 // "-O" vertically split windows +#define WIN_TABS 3 // "-p" windows on tab pages -/* Struct for various parameters passed between main() and other functions. */ +// Struct for various parameters passed between main() and other functions. typedef struct { int argc; char **argv; char *use_vimrc; // vimrc from -u argument - int n_commands; /* no. of commands from + or -c */ + int n_commands; // no. of commands from + or -c char *commands[MAX_ARG_CMDS]; // commands from + or -c arg - char_u cmds_tofree[MAX_ARG_CMDS]; /* commands that need free() */ - int n_pre_commands; /* no. of commands from --cmd */ + char_u cmds_tofree[MAX_ARG_CMDS]; // commands that need free() + int n_pre_commands; // no. of commands from --cmd char *pre_commands[MAX_ARG_CMDS]; // commands from --cmd argument - int edit_type; /* type of editing to do */ - char_u *tagname; /* tag from -t argument */ - char_u *use_ef; /* 'errorfile' from -q argument */ + int edit_type; // type of editing to do + char_u *tagname; // tag from -t argument + char_u *use_ef; // 'errorfile' from -q argument int want_full_screen; bool input_isatty; // stdin is a terminal @@ -108,13 +103,15 @@ typedef struct { bool err_isatty; // stderr is a terminal int no_swap_file; // "-n" argument used int use_debug_break_level; - int window_count; /* number of windows to use */ - int window_layout; /* 0, WIN_HOR, WIN_VER or WIN_TABS */ + int window_count; // number of windows to use + int window_layout; // 0, WIN_HOR, WIN_VER or WIN_TABS #if !defined(UNIX) - int literal; /* don't expand file names */ + int literal; // don't expand file names #endif - int diff_mode; /* start with 'diff' set */ + int diff_mode; // start with 'diff' set + + char *listen_addr; // --listen {address} } mparm_T; /* Values for edit_type. */ @@ -155,7 +152,6 @@ void event_init(void) signal_init(); // finish mspgack-rpc initialization channel_init(); - server_init(); terminal_init(); } @@ -229,7 +225,7 @@ int main(int argc, char **argv) #endif { #if defined(WIN32) && !defined(MAKE_LIB) - char *argv[argc]; + char **argv = xmalloc((size_t)argc * sizeof(char *)); for (int i = 0; i < argc; i++) { char *buf = NULL; utf16_to_utf8(argv_w[i], &buf); @@ -246,9 +242,8 @@ int main(int argc, char **argv) char_u *cwd = NULL; // current workding dir on startup time_init(); - /* Many variables are in "params" so that we can pass them to invoked - * functions without a lot of arguments. "argc" and "argv" are also - * copied, so that they can be changed. */ + // Many variables are in `params` so that we can pass them around easily. + // `argc` and `argv` are also copied, so that they can be changed. init_params(¶ms, argc, argv); init_startuptime(¶ms); @@ -259,11 +254,10 @@ int main(int argc, char **argv) check_and_set_isatty(¶ms); event_init(); - /* - * Process the command line arguments. File names are put in the global - * argument list "global_alist". - */ + // Process the command line arguments. File names are put in the global + // argument list "global_alist". command_line_scan(¶ms); + server_init(params.listen_addr); if (GARGCOUNT > 0) { fname = get_fname(¶ms, cwd); @@ -412,7 +406,7 @@ int main(int argc, char **argv) } // It's better to make v:oldfiles an empty list than NULL. if (get_vim_var_list(VV_OLDFILES) == NULL) { - set_vim_var_list(VV_OLDFILES, tv_list_alloc()); + set_vim_var_list(VV_OLDFILES, tv_list_alloc(0)); } /* @@ -571,11 +565,15 @@ int main(int argc, char **argv) */ normal_enter(false, false); +#if defined(WIN32) && !defined(MAKE_LIB) + xfree(argv); +#endif return 0; } -/* Exit properly */ +/// Exit properly void getout(int exitval) + FUNC_ATTR_NORETURN { tabpage_T *tp, *next_tp; @@ -791,7 +789,7 @@ static void command_line_scan(mparm_T *parmp) mch_exit(0); } else if (STRICMP(argv[0] + argv_idx, "api-info") == 0) { FileDescriptor fp; - const int fof_ret = file_open_fd(&fp, OS_STDOUT_FILENO, + const int fof_ret = file_open_fd(&fp, STDOUT_FILENO, kFileWriteOnly); msgpack_packer *p = msgpack_packer_new(&fp, msgpack_file_write); @@ -821,6 +819,9 @@ static void command_line_scan(mparm_T *parmp) if (!channel_from_stdio(true, CALLBACK_READER_INIT, &err)) { abort(); } + } else if (STRNICMP(argv[0] + argv_idx, "listen", 6) == 0) { + want_argument = true; + argv_idx += 6; } else if (STRNICMP(argv[0] + argv_idx, "literal", 7) == 0) { #if !defined(UNIX) parmp->literal = TRUE; @@ -866,10 +867,6 @@ static void command_line_scan(mparm_T *parmp) case 'f': /* "-f" GUI: run in foreground. */ break; - case 'g': /* "-g" start GUI */ - main_start_gui(); - break; - case 'F': { // "-F" start in Farsi mode: rl + fkmap set. p_fkmap = true; set_option_value("rl", 1L, NULL, 0); @@ -908,18 +905,8 @@ static void command_line_scan(mparm_T *parmp) parmp->no_swap_file = TRUE; break; - case 'p': /* "-p[N]" open N tab pages */ -#ifdef TARGET_API_MAC_OSX - /* For some reason on MacOS X, an argument like: - -psn_0_10223617 is passed in when invoke from Finder - or with the 'open' command */ - if (argv[0][argv_idx] == 's') { - argv_idx = -1; /* bypass full -psn */ - main_start_gui(); - break; - } -#endif - /* default is 0: open window for each file */ + case 'p': // "-p[N]" open N tab pages + // default is 0: open window for each file parmp->window_count = get_number_arg(argv[0], &argv_idx, 0); parmp->window_layout = WIN_TABS; break; @@ -1032,15 +1019,12 @@ static void command_line_scan(mparm_T *parmp) mainerr(err_opt_unknown, argv[0]); } - /* - * Handle option arguments with argument. - */ + // Handle option arguments with argument. if (want_argument) { - /* - * Check for garbage immediately after the option letter. - */ - if (argv[0][argv_idx] != NUL) + // Check for garbage immediately after the option letter. + if (argv[0][argv_idx] != NUL) { mainerr(err_opt_garbage, argv[0]); + } --argc; if (argc < 1 && c != 'S') /* -S has an optional argument */ @@ -1079,13 +1063,17 @@ static void command_line_scan(mparm_T *parmp) break; case '-': - if (argv[-1][2] == 'c') { - /* "--cmd {command}" execute command */ - if (parmp->n_pre_commands >= MAX_ARG_CMDS) + if (strequal(argv[-1], "--cmd")) { + // "--cmd {command}" execute command + if (parmp->n_pre_commands >= MAX_ARG_CMDS) { mainerr(err_extra_cmd, NULL); + } parmp->pre_commands[parmp->n_pre_commands++] = argv[0]; + } else if (strequal(argv[-1], "--listen")) { + // "--listen {address}" + parmp->listen_addr = argv[0]; } - /* "--startuptime <file>" already handled */ + // "--startuptime <file>" already handled break; case 'q': /* "-q {errorfile}" QuickFix mode */ @@ -1107,7 +1095,7 @@ scripterror: } int error; if (STRCMP(argv[0], "-") == 0) { - const int stdin_dup_fd = os_dup(OS_STDIN_FILENO); + const int stdin_dup_fd = os_dup(STDIN_FILENO); FileDescriptor *const stdin_dup = file_open_fd_new( &error, stdin_dup_fd, kFileReadOnly|kFileNonBlocking); assert(stdin_dup != NULL); @@ -1234,11 +1222,10 @@ static void init_params(mparm_T *paramp, int argc, char **argv) paramp->want_full_screen = true; paramp->use_debug_break_level = -1; paramp->window_count = -1; + paramp->listen_addr = NULL; } -/* - * Initialize global startuptime file if "--startuptime" passed as an argument. - */ +/// Initialize global startuptime file if "--startuptime" passed as an argument. static void init_startuptime(mparm_T *paramp) { for (int i = 1; i < paramp->argc; i++) { @@ -1259,12 +1246,12 @@ static void check_and_set_isatty(mparm_T *paramp) stdout_isatty = paramp->output_isatty = os_isatty(fileno(stdout)); paramp->err_isatty = os_isatty(fileno(stderr)); +#ifndef WIN32 int tty_fd = paramp->input_isatty - ? OS_STDIN_FILENO + ? STDIN_FILENO : (paramp->output_isatty - ? OS_STDOUT_FILENO - : (paramp->err_isatty ? OS_STDERR_FILENO : -1)); -#ifndef WIN32 + ? STDOUT_FILENO + : (paramp->err_isatty ? STDERR_FILENO : -1)); pty_process_save_termios(tty_fd); #endif TIME_MSG("window checked"); @@ -1378,7 +1365,7 @@ static void handle_quickfix(mparm_T *paramp) set_string_option_direct((char_u *)"ef", -1, paramp->use_ef, OPT_FREE, SID_CARG); vim_snprintf((char *)IObuff, IOSIZE, "cfile %s", p_ef); - if (qf_init(NULL, p_ef, p_efm, true, IObuff) < 0) { + if (qf_init(NULL, p_ef, p_efm, true, IObuff, p_menc) < 0) { ui_linefeed(); mch_exit(3); } @@ -1844,17 +1831,6 @@ static void source_startup_scripts(const mparm_T *const parmp) TIME_MSG("sourcing vimrc file(s)"); } -/* - * Setup to start using the GUI. Exit with an error when not available. - */ -static void main_start_gui(void) -{ - mch_errmsg(_(e_nogvim)); - mch_errmsg("\n"); - mch_exit(2); -} - - /// Get an environment variable, and execute it as Ex commands. /// /// @param env environment variable to execute @@ -1978,6 +1954,7 @@ static void usage(void) mch_msg(_(" --api-info Write msgpack-encoded API metadata to stdout\n")); mch_msg(_(" --embed Use stdin/stdout as a msgpack-rpc channel\n")); mch_msg(_(" --headless Don't start a user interface\n")); + mch_msg(_(" --listen <address> Start RPC server at this address\n")); #if !defined(UNIX) mch_msg(_(" --literal Don't expand wildcards\n")); #endif diff --git a/src/nvim/map.h b/src/nvim/map.h index 047aa163ce..ac1239a548 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -8,6 +8,11 @@ #include "nvim/api/private/dispatch.h" #include "nvim/bufhl_defs.h" +#if defined(__NetBSD__) +# undef uint64_t +# define uint64_t uint64_t +#endif + #define MAP_DECLS(T, U) \ KHASH_DECLARE(T##_##U##_map, T, U) \ \ diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 1ba400972c..3cd26a5bf7 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -171,6 +171,10 @@ void setpcmark(void) curwin->w_prev_pcmark = curwin->w_pcmark; curwin->w_pcmark = curwin->w_cursor; + if (curwin->w_pcmark.lnum == 0) { + curwin->w_pcmark.lnum = 1; + } + /* If jumplist is full: remove oldest entry */ if (++curwin->w_jumplistlen > JUMPLISTSIZE) { curwin->w_jumplistlen = JUMPLISTSIZE; diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index b24770a409..008bce6df6 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -72,19 +72,49 @@ struct interval { # include "unicode_tables.generated.h" #endif -/* - * Like utf8len_tab above, but using a zero for illegal lead bytes. - */ -const uint8_t utf8len_tab_zero[256] = -{ - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, - 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,0,0, +// To speed up BYTELEN(); keep a lookup table to quickly get the length in +// bytes of a UTF-8 character from the first byte of a UTF-8 string. Bytes +// which are illegal when used as the first byte have a 1. The NUL byte has +// length 1. +const uint8_t utf8len_tab[] = { + // ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?A ?B ?C ?D ?E ?F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B? + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C? + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // D? + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // E? + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1, // F? +}; + +// Like utf8len_tab above, but using a zero for illegal lead bytes. +const uint8_t utf8len_tab_zero[] = { + // ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?A ?B ?C ?D ?E ?F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B? + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C? + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // D? + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // E? + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 0, 0, // F? }; /* @@ -528,45 +558,52 @@ int utf_off2cells(unsigned off, unsigned max_off) return (off + 1 < max_off && ScreenLines[off + 1] == 0) ? 2 : 1; } -/* - * Convert a UTF-8 byte sequence to a wide character. - * If the sequence is illegal or truncated by a NUL the first byte is - * returned. - * Does not include composing characters, of course. - */ -int utf_ptr2char(const char_u *p) +/// Convert a UTF-8 byte sequence to a wide character +/// +/// If the sequence is illegal or truncated by a NUL then the first byte is +/// returned. Does not include composing characters for obvious reasons. +/// +/// @param[in] p String to convert. +/// +/// @return Unicode codepoint or byte value. +int utf_ptr2char(const char_u *const p) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - uint8_t len; - - if (p[0] < 0x80) /* be quick for ASCII */ + if (p[0] < 0x80) { // Be quick for ASCII. return p[0]; + } - len = utf8len_tab_zero[p[0]]; + const uint8_t len = utf8len_tab_zero[p[0]]; if (len > 1 && (p[1] & 0xc0) == 0x80) { - if (len == 2) + if (len == 2) { return ((p[0] & 0x1f) << 6) + (p[1] & 0x3f); + } if ((p[2] & 0xc0) == 0x80) { - if (len == 3) - return ((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6) - + (p[2] & 0x3f); + if (len == 3) { + return (((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6) + + (p[2] & 0x3f)); + } if ((p[3] & 0xc0) == 0x80) { - if (len == 4) - return ((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12) - + ((p[2] & 0x3f) << 6) + (p[3] & 0x3f); + if (len == 4) { + return (((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12) + + ((p[2] & 0x3f) << 6) + (p[3] & 0x3f)); + } if ((p[4] & 0xc0) == 0x80) { - if (len == 5) - return ((p[0] & 0x03) << 24) + ((p[1] & 0x3f) << 18) - + ((p[2] & 0x3f) << 12) + ((p[3] & 0x3f) << 6) - + (p[4] & 0x3f); - if ((p[5] & 0xc0) == 0x80 && len == 6) - return ((p[0] & 0x01) << 30) + ((p[1] & 0x3f) << 24) - + ((p[2] & 0x3f) << 18) + ((p[3] & 0x3f) << 12) - + ((p[4] & 0x3f) << 6) + (p[5] & 0x3f); + if (len == 5) { + return (((p[0] & 0x03) << 24) + ((p[1] & 0x3f) << 18) + + ((p[2] & 0x3f) << 12) + ((p[3] & 0x3f) << 6) + + (p[4] & 0x3f)); + } + if ((p[5] & 0xc0) == 0x80 && len == 6) { + return (((p[0] & 0x01) << 30) + ((p[1] & 0x3f) << 24) + + ((p[2] & 0x3f) << 18) + ((p[3] & 0x3f) << 12) + + ((p[4] & 0x3f) << 6) + (p[5] & 0x3f)); + } } } } } - /* Illegal value, just return the first byte */ + // Illegal value: just return the first byte. return p[0]; } @@ -767,23 +804,24 @@ int utfc_char2bytes(int off, char_u *buf) return len; } -/* - * Get the length of a UTF-8 byte sequence, not including any following - * composing characters. - * Returns 0 for "". - * Returns 1 for an illegal byte sequence. - */ -int utf_ptr2len(const char_u *p) +/// Get the length of a UTF-8 byte sequence representing a single codepoint +/// +/// @param[in] p UTF-8 string. +/// +/// @return Sequence length, 0 for empty string and 1 for non-UTF-8 byte +/// sequence. +int utf_ptr2len(const char_u *const p) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - int len; - int i; - - if (*p == NUL) + if (*p == NUL) { return 0; - len = utf8len_tab[*p]; - for (i = 1; i < len; ++i) - if ((p[i] & 0xc0) != 0x80) + } + const int len = utf8len_tab[*p]; + for (int i = 1; i < len; i++) { + if ((p[i] & 0xc0) != 0x80) { return 1; + } + } return len; } @@ -824,38 +862,38 @@ int utf_ptr2len_len(const char_u *p, int size) return len; } -/* - * Return the number of bytes the UTF-8 encoding of the character at "p" takes. - * This includes following composing characters. - */ -int utfc_ptr2len(const char_u *p) +/// Return the number of bytes occupied by a UTF-8 character in a string +/// +/// This includes following composing characters. +int utfc_ptr2len(const char_u *const p) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - int len; - int b0 = *p; - int prevlen; + uint8_t b0 = (uint8_t)(*p); - if (b0 == NUL) + if (b0 == NUL) { return 0; - if (b0 < 0x80 && p[1] < 0x80) /* be quick for ASCII */ + } + if (b0 < 0x80 && p[1] < 0x80) { // be quick for ASCII return 1; + } - /* Skip over first UTF-8 char, stopping at a NUL byte. */ - len = utf_ptr2len(p); + // Skip over first UTF-8 char, stopping at a NUL byte. + int len = utf_ptr2len(p); - /* Check for illegal byte. */ - if (len == 1 && b0 >= 0x80) + // Check for illegal byte. + if (len == 1 && b0 >= 0x80) { return 1; + } - /* - * Check for composing characters. We can handle only the first six, but - * skip all of them (otherwise the cursor would get stuck). - */ - prevlen = 0; - for (;; ) { - if (p[len] < 0x80 || !UTF_COMPOSINGLIKE(p + prevlen, p + len)) + // Check for composing characters. We can handle only the first six, but + // skip all of them (otherwise the cursor would get stuck). + int prevlen = 0; + for (;;) { + if (p[len] < 0x80 || !UTF_COMPOSINGLIKE(p + prevlen, p + len)) { return len; + } - /* Skip over composing char */ + // Skip over composing char. prevlen = len; len += utf_ptr2len(p + len); } @@ -913,23 +951,22 @@ int utfc_ptr2len_len(const char_u *p, int size) return len; } -/* - * Return the number of bytes the UTF-8 encoding of character "c" takes. - * This does not include composing characters. - */ -int utf_char2len(int c) +/// Determine how many bytes certain unicode codepoint will occupy +int utf_char2len(const int c) { - if (c < 0x80) + if (c < 0x80) { return 1; - if (c < 0x800) + } else if (c < 0x800) { return 2; - if (c < 0x10000) + } else if (c < 0x10000) { return 3; - if (c < 0x200000) + } else if (c < 0x200000) { return 4; - if (c < 0x4000000) + } else if (c < 0x4000000) { return 5; - return 6; + } else { + return 6; + } } /// Convert Unicode character to UTF-8 string @@ -937,46 +974,42 @@ int utf_char2len(int c) /// @param c character to convert to \p buf /// @param[out] buf UTF-8 string generated from \p c, does not add \0 /// @return Number of bytes (1-6). Does not include composing characters. -int utf_char2bytes(int c, char_u *const buf) +int utf_char2bytes(const int c, char_u *const buf) { - if (c < 0x80) { /* 7 bits */ + if (c < 0x80) { // 7 bits buf[0] = c; return 1; - } - if (c < 0x800) { /* 11 bits */ + } else if (c < 0x800) { // 11 bits buf[0] = 0xc0 + ((unsigned)c >> 6); buf[1] = 0x80 + (c & 0x3f); return 2; - } - if (c < 0x10000) { /* 16 bits */ + } else if (c < 0x10000) { // 16 bits buf[0] = 0xe0 + ((unsigned)c >> 12); buf[1] = 0x80 + (((unsigned)c >> 6) & 0x3f); buf[2] = 0x80 + (c & 0x3f); return 3; - } - if (c < 0x200000) { /* 21 bits */ + } else if (c < 0x200000) { // 21 bits buf[0] = 0xf0 + ((unsigned)c >> 18); buf[1] = 0x80 + (((unsigned)c >> 12) & 0x3f); buf[2] = 0x80 + (((unsigned)c >> 6) & 0x3f); buf[3] = 0x80 + (c & 0x3f); return 4; - } - if (c < 0x4000000) { /* 26 bits */ + } else if (c < 0x4000000) { // 26 bits buf[0] = 0xf8 + ((unsigned)c >> 24); buf[1] = 0x80 + (((unsigned)c >> 18) & 0x3f); buf[2] = 0x80 + (((unsigned)c >> 12) & 0x3f); buf[3] = 0x80 + (((unsigned)c >> 6) & 0x3f); buf[4] = 0x80 + (c & 0x3f); return 5; + } else { // 31 bits + buf[0] = 0xfc + ((unsigned)c >> 30); + buf[1] = 0x80 + (((unsigned)c >> 24) & 0x3f); + buf[2] = 0x80 + (((unsigned)c >> 18) & 0x3f); + buf[3] = 0x80 + (((unsigned)c >> 12) & 0x3f); + buf[4] = 0x80 + (((unsigned)c >> 6) & 0x3f); + buf[5] = 0x80 + (c & 0x3f); + return 6; } - /* 31 bits */ - buf[0] = 0xfc + ((unsigned)c >> 30); - buf[1] = 0x80 + (((unsigned)c >> 24) & 0x3f); - buf[2] = 0x80 + (((unsigned)c >> 18) & 0x3f); - buf[3] = 0x80 + (((unsigned)c >> 12) & 0x3f); - buf[4] = 0x80 + (((unsigned)c >> 6) & 0x3f); - buf[5] = 0x80 + (c & 0x3f); - return 6; } /* @@ -1513,14 +1546,15 @@ int utf_head_off(const char_u *base, const char_u *p) return (int)(p - q); } -/* - * Copy a character from "*fp" to "*tp" and advance the pointers. - */ -void mb_copy_char(const char_u **fp, char_u **tp) +/// Copy a character, advancing the pointers +/// +/// @param[in,out] fp Source of the character to copy. +/// @param[in,out] tp Destination to copy to. +void mb_copy_char(const char_u **const fp, char_u **const tp) { - int l = (*mb_ptr2len)(*fp); + const size_t l = (size_t)utfc_ptr2len(*fp); - memmove(*tp, *fp, (size_t)l); + memmove(*tp, *fp, l); *tp += l; *fp += l; } @@ -2262,9 +2296,7 @@ int convert_setup_ext(vimconv_T *vcp, char_u *from, bool from_unicode_is_utf8, if (vcp->vc_type == CONV_ICONV && vcp->vc_fd != (iconv_t)-1) iconv_close(vcp->vc_fd); # endif - vcp->vc_type = CONV_NONE; - vcp->vc_factor = 1; - vcp->vc_fail = false; + *vcp = (vimconv_T)MBYTE_NONE_CONV; /* No conversion when one of the names is empty or they are equal. */ if (from == NULL || *from == NUL || to == NULL || *to == NUL diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h index bf6ccb13a5..a5ce1b0a15 100644 --- a/src/nvim/mbyte.h +++ b/src/nvim/mbyte.h @@ -60,6 +60,12 @@ typedef enum { CONV_ICONV = 5, } ConvFlags; +#define MBYTE_NONE_CONV { \ + .vc_type = CONV_NONE, \ + .vc_factor = 1, \ + .vc_fail = false, \ +} + /// Structure used for string conversions typedef struct { int vc_type; ///< Zero or more ConvFlags. @@ -73,6 +79,8 @@ typedef struct { extern const uint8_t utf8len_tab_zero[256]; +extern const uint8_t utf8len_tab[256]; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "mbyte.h.generated.h" #endif diff --git a/src/nvim/memfile.c b/src/nvim/memfile.c index 4428dd42aa..4eeba12b87 100644 --- a/src/nvim/memfile.c +++ b/src/nvim/memfile.c @@ -376,8 +376,9 @@ void mf_put(memfile_T *mfp, bhdr_T *hp, bool dirty, bool infile) { unsigned flags = hp->bh_flags; - if ((flags & BH_LOCKED) == 0) - EMSG(_("E293: block was not locked")); + if ((flags & BH_LOCKED) == 0) { + IEMSG(_("E293: block was not locked")); + } flags &= ~BH_LOCKED; if (dirty) { flags |= BH_DIRTY; diff --git a/src/nvim/memline.c b/src/nvim/memline.c index f28a9e60f4..c11ca74f5c 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -293,7 +293,7 @@ int ml_open(buf_T *buf) */ hp = mf_new(mfp, false, 1); if (hp->bh_bnum != 0) { - EMSG(_("E298: Didn't get block nr 0?")); + IEMSG(_("E298: Didn't get block nr 0?")); goto error; } b0p = hp->bh_data; @@ -335,7 +335,7 @@ int ml_open(buf_T *buf) if ((hp = ml_new_ptr(mfp)) == NULL) goto error; if (hp->bh_bnum != 1) { - EMSG(_("E298: Didn't get block nr 1?")); + IEMSG(_("E298: Didn't get block nr 1?")); goto error; } pp = hp->bh_data; @@ -351,7 +351,7 @@ int ml_open(buf_T *buf) */ hp = ml_new_data(mfp, FALSE, 1); if (hp->bh_bnum != 2) { - EMSG(_("E298: Didn't get block nr 2?")); + IEMSG(_("E298: Didn't get block nr 2?")); goto error; } @@ -635,13 +635,14 @@ static void ml_upd_block0(buf_T *buf, upd_block0_T what) if (mfp == NULL || (hp = mf_get(mfp, 0, 1)) == NULL) return; b0p = hp->bh_data; - if (ml_check_b0_id(b0p) == FAIL) - EMSG(_("E304: ml_upd_block0(): Didn't get block 0??")); - else { - if (what == UB_FNAME) + if (ml_check_b0_id(b0p) == FAIL) { + IEMSG(_("E304: ml_upd_block0(): Didn't get block 0??")); + } else { + if (what == UB_FNAME) { set_b0_fname(b0p, buf); - else /* what == UB_SAME_DIR */ + } else { // what == UB_SAME_DIR set_b0_dir_flag(b0p, buf); + } } mf_put(mfp, hp, true, false); } @@ -1329,10 +1330,14 @@ recover_names ( names[2] = (char_u *)concat_fnames((char *)dir_name, ".sw?", TRUE); num_names = 3; } else { - p = dir_name + STRLEN(dir_name); - if (after_pathsep((char *)dir_name, (char *)p) && p[-1] == p[-2]) { - /* Ends with '//', Use Full path for swap name */ - tail = (char_u *)make_percent_swname((char *)dir_name, (char *)fname_res); + int len = (int)STRLEN(dir_name); + p = dir_name + len; + if (after_pathsep((char *)dir_name, (char *)p) + && len > 1 + && p[-1] == p[-2]) { + // Ends with '//', Use Full path for swap name + tail = (char_u *)make_percent_swname((char *)dir_name, + (char *)fname_res); } else { tail = path_tail(fname_res); tail = (char_u *)concat_fnames((char *)dir_name, (char *)tail, TRUE); @@ -1742,11 +1747,11 @@ ml_get_buf ( if (lnum > buf->b_ml.ml_line_count) { /* invalid line number */ if (recursive == 0) { - /* Avoid giving this message for a recursive call, may happen when - * the GUI redraws part of the text. */ - ++recursive; - EMSGN(_("E315: ml_get: invalid lnum: %" PRId64), lnum); - --recursive; + // Avoid giving this message for a recursive call, may happen when + // the GUI redraws part of the text. + recursive++; + IEMSGN(_("E315: ml_get: invalid lnum: %" PRId64), lnum); + recursive--; } errorret: STRCPY(IObuff, "???"); @@ -1774,11 +1779,11 @@ errorret: */ if ((hp = ml_find_line(buf, lnum, ML_FIND)) == NULL) { if (recursive == 0) { - /* Avoid giving this message for a recursive call, may happen - * when the GUI redraws part of the text. */ - ++recursive; - EMSGN(_("E316: ml_get: cannot find line %" PRId64), lnum); - --recursive; + // Avoid giving this message for a recursive call, may happen + // when the GUI redraws part of the text. + recursive++; + IEMSGN(_("E316: ml_get: cannot find line %" PRId64), lnum); + recursive--; } goto errorret; } @@ -2162,7 +2167,7 @@ ml_append_int ( return FAIL; pp = hp->bh_data; /* must be pointer block */ if (pp->pb_id != PTR_ID) { - EMSG(_("E317: pointer block id wrong 3")); + IEMSG(_("E317: pointer block id wrong 3")); mf_put(mfp, hp, false, false); return FAIL; } @@ -2295,8 +2300,8 @@ ml_append_int ( * Safety check: fallen out of for loop? */ if (stack_idx < 0) { - EMSG(_("E318: Updated too many blocks?")); - buf->b_ml.ml_stack_top = 0; /* invalidate stack */ + IEMSG(_("E318: Updated too many blocks?")); + buf->b_ml.ml_stack_top = 0; // invalidate stack } } @@ -2435,7 +2440,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, int message) return FAIL; pp = hp->bh_data; /* must be pointer block */ if (pp->pb_id != PTR_ID) { - EMSG(_("E317: pointer block id wrong 4")); + IEMSG(_("E317: pointer block id wrong 4")); mf_put(mfp, hp, false, false); return FAIL; } @@ -2630,9 +2635,9 @@ static void ml_flush_line(buf_T *buf) new_line = buf->b_ml.ml_line_ptr; hp = ml_find_line(buf, lnum, ML_FIND); - if (hp == NULL) - EMSGN(_("E320: Cannot find line %" PRId64), lnum); - else { + if (hp == NULL) { + IEMSGN(_("E320: Cannot find line %" PRId64), lnum); + } else { dp = hp->bh_data; idx = lnum - buf->b_ml.ml_locked_low; start = ((dp->db_index[idx]) & DB_INDEX_MASK); @@ -2841,7 +2846,7 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) pp = (PTR_BL *)(dp); /* must be pointer block */ if (pp->pb_id != PTR_ID) { - EMSG(_("E317: pointer block id wrong")); + IEMSG(_("E317: pointer block id wrong")); goto error_block; } @@ -2878,13 +2883,14 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) break; } } - if (idx >= (int)pp->pb_count) { /* past the end: something wrong! */ - if (lnum > buf->b_ml.ml_line_count) - EMSGN(_("E322: line number out of range: %" PRId64 " past the end"), - lnum - buf->b_ml.ml_line_count); + if (idx >= (int)pp->pb_count) { // past the end: something wrong! + if (lnum > buf->b_ml.ml_line_count) { + IEMSGN(_("E322: line number out of range: %" PRId64 " past the end"), + lnum - buf->b_ml.ml_line_count); - else - EMSGN(_("E323: line count wrong in block %" PRId64), bnum); + } else { + IEMSGN(_("E323: line count wrong in block %" PRId64), bnum); + } goto error_block; } if (action == ML_DELETE) { @@ -2960,7 +2966,7 @@ static void ml_lineadd(buf_T *buf, int count) pp = hp->bh_data; /* must be pointer block */ if (pp->pb_id != PTR_ID) { mf_put(mfp, hp, false, false); - EMSG(_("E317: pointer block id wrong 2")); + IEMSG(_("E317: pointer block id wrong 2")); break; } pp->pb_pointer[ip->ip_index].pe_line_count += count; @@ -3014,20 +3020,17 @@ int resolve_symlink(const char_u *fname, char_u *buf) } buf[ret] = NUL; - /* - * Check whether the symlink is relative or absolute. - * If it's relative, build a new path based on the directory - * portion of the filename (if any) and the path the symlink - * points to. - */ - if (path_is_absolute_path(buf)) + // Check whether the symlink is relative or absolute. + // If it's relative, build a new path based on the directory + // portion of the filename (if any) and the path the symlink + // points to. + if (path_is_absolute(buf)) { STRCPY(tmp, buf); - else { - char_u *tail; - - tail = path_tail(tmp); - if (STRLEN(tail) + STRLEN(buf) >= MAXPATHL) + } else { + char_u *tail = path_tail(tmp); + if (STRLEN(tail) + STRLEN(buf) >= MAXPATHL) { return FAIL; + } STRCPY(tail, buf); } } @@ -3052,9 +3055,12 @@ char_u *makeswapname(char_u *fname, char_u *ffname, buf_T *buf, char_u *dir_name #ifdef HAVE_READLINK char_u fname_buf[MAXPATHL]; #endif + int len = (int)STRLEN(dir_name); - s = dir_name + STRLEN(dir_name); - if (after_pathsep((char *)dir_name, (char *)s) && s[-1] == s[-2]) { /* Ends with '//', Use Full path */ + s = dir_name + len; + if (after_pathsep((char *)dir_name, (char *)s) + && 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); diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 328b96fd5c..a66ab6a3cc 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -559,6 +559,7 @@ void time_to_bytes(time_t time_, uint8_t buf[8]) #include "nvim/tag.h" #include "nvim/window.h" #include "nvim/os/os.h" +#include "nvim/eval/typval.h" /* * Free everything that we allocated. @@ -692,6 +693,7 @@ void free_all_mem(void) free_screenlines(); clear_hl_tables(); + list_free_log(); } #endif diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 01c8e94bac..42417f75d5 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -714,7 +714,7 @@ static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes) } } else { // visit recursively all children - list_T *children_list = tv_list_alloc(); + list_T *const children_list = tv_list_alloc(kListLenMayKnow); for (menu = menu->children; menu != NULL; menu = menu->next) { dict_T *dic = menu_get_recursive(menu, modes); if (tv_dict_len(dict) > 0) { diff --git a/src/nvim/message.c b/src/nvim/message.c index d9b3300758..3a3c9cb167 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -487,9 +487,6 @@ int emsg(const char_u *s_) } called_emsg = true; - if (emsg_silent == 0) { - ex_exitval = 1; - } // If "emsg_severe" is TRUE: When an error exception is to be thrown, // prefer this message over previous messages for the same command. @@ -540,6 +537,8 @@ int emsg(const char_u *s_) return true; } + ex_exitval = 1; + // Reset msg_silent, an error causes messages to be switched back on. msg_silent = 0; cmd_silent = FALSE; @@ -583,19 +582,60 @@ void emsg_invreg(int name) /// Print an error message with unknown number of arguments bool emsgf(const char *const fmt, ...) { + bool ret; + + va_list ap; + va_start(ap, fmt); + ret = emsgfv(fmt, ap); + va_end(ap); + + return ret; +} + +/// Print an error message with unknown number of arguments +static bool emsgfv(const char *fmt, va_list ap) +{ static char errbuf[IOSIZE]; if (emsg_not_now()) { return true; } - va_list ap; - va_start(ap, fmt); vim_vsnprintf(errbuf, sizeof(errbuf), fmt, ap, NULL); - va_end(ap); return emsg((const char_u *)errbuf); } +/// Same as emsg(...), but abort on error when ABORT_ON_INTERNAL_ERROR is +/// defined. It is used for internal errors only, so that they can be +/// detected when fuzzing vim. +void iemsg(const char *s) +{ + msg((char_u *)s); +#ifdef ABORT_ON_INTERNAL_ERROR + abort(); +#endif +} + +/// Same as emsgf(...) but abort on error when ABORT_ON_INTERNAL_ERROR is +/// defined. It is used for internal errors only, so that they can be +/// detected when fuzzing vim. +void iemsgf(const char *s, ...) +{ + va_list ap; + va_start(ap, s); + (void)emsgfv(s, ap); + va_end(ap); +#ifdef ABORT_ON_INTERNAL_ERROR + abort(); +#endif +} + +/// Give an "Internal error" message. +void internal_error(char *where) +{ + IEMSG2(_(e_intern2), where); +} + static void msg_emsgf_event(void **argv) { char *s = argv[0]; @@ -1237,31 +1277,30 @@ void msg_make(char_u *arg) } } -/* - * Output the string 'str' upto a NUL character. - * Return the number of characters it takes on the screen. - * - * If K_SPECIAL is encountered, then it is taken in conjunction with the - * following character and shown as <F1>, <S-Up> etc. Any other character - * which is not printable shown in <> form. - * If 'from' is TRUE (lhs of a mapping), a space is shown as <Space>. - * If a character is displayed in one of these special ways, is also - * highlighted (its highlight name is '8' in the p_hl variable). - * Otherwise characters are not highlighted. - * This function is used to show mappings, where we want to see how to type - * the character/string -- webb - */ -int -msg_outtrans_special ( - char_u *strstart, - int from /* TRUE for lhs of a mapping */ +/// Output the string 'str' upto a NUL character. +/// Return the number of characters it takes on the screen. +/// +/// If K_SPECIAL is encountered, then it is taken in conjunction with the +/// following character and shown as <F1>, <S-Up> etc. Any other character +/// which is not printable shown in <> form. +/// If 'from' is TRUE (lhs of a mapping), a space is shown as <Space>. +/// If a character is displayed in one of these special ways, is also +/// highlighted (its highlight name is '8' in the p_hl variable). +/// Otherwise characters are not highlighted. +/// This function is used to show mappings, where we want to see how to type +/// the character/string -- webb +int msg_outtrans_special( + const char_u *strstart, + int from ///< true for LHS of a mapping ) { - char_u *str = strstart; + if (strstart == NULL) { + return 0; // Do nothing. + } + const char_u *str = strstart; int retval = 0; - int attr; + int attr = hl_attr(HLF_8); - attr = hl_attr(HLF_8); while (*str != NUL) { const char *string; // Leading and trailing spaces need to be displayed in <> form. @@ -1307,7 +1346,7 @@ char *str2special_save(const char *const str, const bool replace_spaces, return (char *)ga.ga_data; } -/// Convert character, replacing key one key code with printable representation +/// Convert character, replacing key with printable representation. /// /// @param[in,out] sp String to convert. Is advanced to the next key code. /// @param[in] replace_spaces Convert spaces into <Space>, normally used for @@ -1392,7 +1431,7 @@ void str2specialbuf(const char *sp, char *buf, size_t len) while (*sp) { const char *s = str2special(&sp, false, false); const size_t s_len = strlen(s); - if (s_len <= len) { + if (len <= s_len) { break; } memcpy(buf, s, s_len); @@ -1785,17 +1824,13 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, } while (msg_col & 7); } else if (*s == BELL) { // beep (from ":sh") vim_beep(BO_SH); - } else { - if (has_mbyte) { - cw = (*mb_ptr2cells)(s); - if (enc_utf8 && maxlen >= 0) - /* avoid including composing chars after the end */ - l = utfc_ptr2len_len(s, (int)((str + maxlen) - s)); - else - l = (*mb_ptr2len)(s); + } else if (*s >= 0x20) { // printable char + cw = mb_ptr2cells(s); + if (maxlen >= 0) { + // avoid including composing chars after the end + l = utfc_ptr2len_len(s, (int)((str + maxlen) - s)); } else { - cw = 1; - l = 1; + l = utfc_ptr2len(s); } // When drawing from right to left or when a double-wide character // doesn't fit, draw a single character here. Otherwise collect @@ -1840,13 +1875,29 @@ bool message_filtered(char_u *msg) return cmdmod.filter_force ? match : !match; } +/// including horizontal separator +int msg_scrollsize(void) +{ + return msg_scrolled + p_ch + 1; +} + /* * Scroll the screen up one line for displaying the next message line. */ static void msg_scroll_up(void) { - /* scrolling up always works */ - screen_del_lines(0, 0, 1, (int)Rows, NULL); + if (dy_flags & DY_MSGSEP) { + if (msg_scrolled == 0) { + screen_fill(Rows-p_ch-1, Rows-p_ch, 0, (int)Columns, + fill_msgsep, fill_msgsep, hl_attr(HLF_MSGSEP)); + } + int nscroll = MIN(msg_scrollsize()+1, Rows); + ui_call_set_scroll_region(Rows-nscroll, Rows-1, 0, Columns-1); + screen_del_lines(Rows-nscroll, 0, 1, nscroll, NULL); + ui_reset_scroll_region(); + } else { + screen_del_lines(0, 0, 1, (int)Rows, NULL); + } } /* diff --git a/src/nvim/message.h b/src/nvim/message.h index 904a9d3ca1..82935a36a9 100644 --- a/src/nvim/message.h +++ b/src/nvim/message.h @@ -49,6 +49,15 @@ /// Like #EMSG, but for messages with one "%" PRIu64 inside #define EMSGU(s, n) emsgf((const char *) (s), (uint64_t)(n)) +/// Like #EMSG, but for internal messages +#define IEMSG(s) iemsg((const char *)(s)) + +/// Like #EMSG2, but for internal messages +#define IEMSG2(s, p) iemsgf((const char *)(s), (p)) + +/// Like #EMSGN, but for internal messages +#define IEMSGN(s, n) iemsgf((const char *)(s), (int64_t)(n)) + /// Display message at the recorded position #define MSG_PUTS(s) msg_puts((const char *)(s)) diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index f7ee2950ef..b0232b6516 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -749,8 +749,9 @@ open_line ( // Postpone calling changed_lines(), because it would mess up folding // with markers. // Skip mark_adjust when adding a line after the last one, there can't - // be marks there. - if (curwin->w_cursor.lnum + 1 < curbuf->b_ml.ml_line_count) { + // be marks there. But still needed in diff mode. + if (curwin->w_cursor.lnum + 1 < curbuf->b_ml.ml_line_count + || curwin->w_p_diff) { mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false); } did_append = true; @@ -1468,7 +1469,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) } } - char_u *newp = (char_u *) xmalloc((size_t)(linelen + newlen - oldlen)); + char_u *newp = xmalloc((size_t)(linelen + newlen - oldlen)); // Copy bytes before the cursor. if (col > 0) { @@ -1477,7 +1478,10 @@ void ins_char_bytes(char_u *buf, size_t charlen) // Copy bytes after the changed character(s). char_u *p = newp + col; - memmove(p + newlen, oldp + col + oldlen, (size_t)(linelen - col - oldlen)); + if (linelen > col + oldlen) { + memmove(p + newlen, oldp + col + oldlen, + (size_t)(linelen - col - oldlen)); + } // Insert or overwrite the new character. memmove(p, buf, charlen); @@ -1864,8 +1868,8 @@ void appended_lines(linenr_T lnum, long count) void appended_lines_mark(linenr_T lnum, long count) { // Skip mark_adjust when adding a line after the last one, there can't - // be marks there. - if (lnum + count < curbuf->b_ml.ml_line_count) { + // be marks there. But it's still needed in diff mode. + if (lnum + count < curbuf->b_ml.ml_line_count || curwin->w_p_diff) { mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, false); } changed_lines(lnum + 1, 0, lnum + 1, count); @@ -2609,13 +2613,12 @@ int match_user(char_u *name) return result; } -/* - * Preserve files and exit. - * When called IObuff must contain a message. - * NOTE: This may be called from deathtrap() in a signal handler, avoid unsafe - * functions, such as allocating memory. - */ +/// Preserve files and exit. +/// @note IObuff must contain a message. +/// @note This may be called from deadly_signal() in a signal handler, avoid +/// unsafe functions, such as allocating memory. void preserve_exit(void) + FUNC_ATTR_NORETURN { // 'true' when we are sure to exit, e.g., after a deadly signal static bool really_exiting = false; diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index d908a022f1..6f636f643a 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -608,8 +608,7 @@ bool mouse_scroll_horiz(int dir) return leftcol_changed(); } -// Adjust the clicked column position if there are concealed characters -// before the current column. But only when it's absolutely necessary. +/// Adjusts the clicked column position when 'conceallevel' > 0 static int mouse_adjust_click(win_T *wp, int row, int col) { if (!(wp->w_p_cole > 0 && curbuf->b_p_smc > 0 @@ -617,64 +616,102 @@ static int mouse_adjust_click(win_T *wp, int row, int col) return col; } - int end = (colnr_T)STRLEN(ml_get(wp->w_cursor.lnum)); - int vend = getviscol2(end, 0); + // `col` is the position within the current line that is highlighted by the + // cursor without consideration for concealed characters. The current line is + // scanned *up to* `col`, nudging it left or right when concealed characters + // are encountered. + // + // chartabsize() is used to keep track of the virtual column position relative + // to the line's bytes. For example: if col == 9 and the line starts with a + // tab that's 8 columns wide, we would want the cursor to be highlighting the + // second byte, not the ninth. + + linenr_T lnum = wp->w_cursor.lnum; + char_u *line = ml_get(lnum); + char_u *ptr = line; + char_u *ptr_end = line; + char_u *ptr_row_offset = line; // Where we begin adjusting `ptr_end` - if (col >= vend) { - return col; + // Find the offset where scanning should begin. + int offset = wp->w_leftcol; + if (row > 0) { + offset += row * (wp->w_width - win_col_off(wp) - win_col_off2(wp) - + wp->w_leftcol + wp->w_skipcol); } - int i = wp->w_leftcol; + int vcol; + + if (offset) { + // Skip everything up to an offset since nvim takes care of displaying the + // correct portion of the line when horizontally scrolling. + // When 'wrap' is enabled, only the row (of the wrapped line) needs to be + // checked for concealed characters. + vcol = 0; + while (vcol < offset && *ptr != NUL) { + vcol += chartabsize(ptr, vcol); + ptr += utfc_ptr2len(ptr); + } - if (row > 0) { - i += row * (wp->w_width - win_col_off(wp) - win_col_off2(wp) - - wp->w_leftcol) + wp->w_skipcol; + ptr_row_offset = ptr; } - int start_col = i; - int matchid; - int last_matchid; - int bcol = end - (vend - col); + // Align `ptr_end` with `col` + vcol = offset; + ptr_end = ptr_row_offset; + while (vcol < col && *ptr_end != NUL) { + vcol += chartabsize(ptr_end, vcol); + ptr_end += utfc_ptr2len(ptr_end); + } - while (i < bcol) { - matchid = syn_get_concealed_id(wp, wp->w_cursor.lnum, i); + int matchid; + int prev_matchid; + int nudge = 0; + int cwidth = 0; + + vcol = offset; + +#define incr() nudge++; ptr_end += utfc_ptr2len(ptr_end) +#define decr() nudge--; ptr_end -= utfc_ptr2len(ptr_end) + + while (ptr < ptr_end && *ptr != NUL) { + cwidth = chartabsize(ptr, vcol); + vcol += cwidth; + if (cwidth > 1 && *ptr == '\t' && nudge > 0) { + // A tab will "absorb" any previous adjustments. + cwidth = MIN(cwidth, nudge); + while (cwidth > 0) { + decr(); + cwidth--; + } + } + matchid = syn_get_concealed_id(wp, lnum, (colnr_T)(ptr - line)); if (matchid != 0) { if (wp->w_p_cole == 3) { - bcol++; + incr(); } else { - if (row > 0 && i == start_col) { - // Check if the current concealed character is actually part of - // the previous wrapped row's conceal group. - last_matchid = syn_get_concealed_id(wp, wp->w_cursor.lnum, - i - 1); - if (last_matchid == matchid) { - bcol++; - } - } else if (wp->w_p_cole == 1 - || (wp->w_p_cole == 2 - && (lcs_conceal != NUL - || syn_get_sub_char() != NUL))) { + if (!(row > 0 && ptr == ptr_row_offset) + && (wp->w_p_cole == 1 || (wp->w_p_cole == 2 + && (lcs_conceal != NUL + || syn_get_sub_char() != NUL)))) { // At least one placeholder character will be displayed. - bcol--; + decr(); } - last_matchid = matchid; + prev_matchid = matchid; - // Adjust for concealed text that spans more than one character. - do { - i++; - bcol++; - matchid = syn_get_concealed_id(wp, wp->w_cursor.lnum, i); - } while (last_matchid == matchid); + while (prev_matchid == matchid && *ptr != NUL) { + incr(); + ptr += utfc_ptr2len(ptr); + matchid = syn_get_concealed_id(wp, lnum, (colnr_T)(ptr - line)); + } continue; } } - i++; + ptr += utfc_ptr2len(ptr); } - return getviscol2(bcol, 0); + return col + nudge; } - diff --git a/src/nvim/move.c b/src/nvim/move.c index 134ffcd7dc..cb13a9e207 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -155,12 +155,11 @@ void update_topline(void) old_topline = curwin->w_topline; old_topfill = curwin->w_topfill; - /* - * If the buffer is empty, always set topline to 1. - */ - if (bufempty()) { /* special case - file is empty */ - if (curwin->w_topline != 1) + // 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); + } curwin->w_topline = 1; curwin->w_botline = 2; curwin->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP; @@ -673,7 +672,7 @@ int win_col_off(win_T *wp) return ((wp->w_p_nu || wp->w_p_rnu) ? number_width(wp) + 1 : 0) + (cmdwin_type == 0 || wp != curwin ? 0 : 1) + (int)wp->w_p_fdc - + (signcolumn_on(wp) ? 2 : 0); + + (signcolumn_on(wp) ? win_signcol_width(wp) : 0); } int curwin_col_off(void) diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c index 9bf122f4db..e5d80aea1d 100644 --- a/src/nvim/msgpack_rpc/server.c +++ b/src/nvim/msgpack_rpc/server.c @@ -32,26 +32,27 @@ static garray_T watchers = GA_EMPTY_INIT_VALUE; #endif /// Initializes the module -bool server_init(void) +bool server_init(const char *listen_addr) { ga_init(&watchers, sizeof(SocketWatcher *), 1); - bool must_free = false; - const char *listen_address = os_getenv(LISTEN_ADDRESS_ENV_VAR); - if (listen_address == NULL) { - must_free = true; - listen_address = server_address_new(); - } + // $NVIM_LISTEN_ADDRESS + const char *env_addr = os_getenv(LISTEN_ADDRESS_ENV_VAR); + int rv = listen_addr == NULL ? 1 : server_start(listen_addr); - if (!listen_address) { - return false; + if (0 != rv) { + rv = env_addr == NULL ? 1 : server_start(env_addr); + if (0 != rv) { + listen_addr = server_address_new(); + if (listen_addr == NULL) { + return false; + } + rv = server_start(listen_addr); + xfree((char *)listen_addr); + } } - bool ok = (server_start(listen_address) == 0); - if (must_free) { - xfree((char *) listen_address); - } - return ok; + return rv == 0; } /// Teardown a single server @@ -120,8 +121,8 @@ bool server_owns_pipe_address(const char *path) /// @param endpoint Address of the server. Either a 'ip:[port]' string or an /// arbitrary identifier (trimmed to 256 bytes) for the Unix /// socket or named pipe. -/// @returns 0 on success, 1 on a regular error, and negative errno -/// on failure to bind or listen. +/// @returns 0: success, 1: validation error, 2: already listening, +/// -errno: failed to bind or listen. int server_start(const char *endpoint) { if (endpoint == NULL || endpoint[0] == '\0') { @@ -145,7 +146,7 @@ int server_start(const char *endpoint) uv_freeaddrinfo(watcher->uv.tcp.addrinfo); } socket_watcher_close(watcher, free_server); - return 1; + return 2; } } @@ -177,7 +178,7 @@ int server_start(const char *endpoint) /// Stops listening on the address specified by `endpoint`. /// /// @param endpoint Address of the server. -void server_stop(char *endpoint) +bool server_stop(char *endpoint) { SocketWatcher *watcher; bool watcher_found = false; @@ -196,8 +197,8 @@ void server_stop(char *endpoint) } if (!watcher_found) { - ELOG("Not listening on %s", addr); - return; + WLOG("Not listening on %s", addr); + return false; } // Unset $NVIM_LISTEN_ADDRESS if it is the stopped address. @@ -219,6 +220,8 @@ void server_stop(char *endpoint) if (STRCMP(addr, get_vim_var_str(VV_SEND_SERVER)) == 0) { set_vservername(&watchers); } + + return true; } /// Returns an allocated array of server addresses. diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 1103fe15d2..e4310de5d8 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -345,6 +345,7 @@ static const struct nv_cmd { { K_F8, farsi_f8, 0, 0 }, { K_F9, farsi_f9, 0, 0 }, { K_EVENT, nv_event, NV_KEEPREG, 0 }, + { K_COMMAND, nv_colon, 0, 0 }, }; /* Number of commands in nv_cmds[]. */ @@ -1473,13 +1474,13 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) AppendToRedobuffLit(cap->searchbuf, -1); } AppendToRedobuff(NL_STR); - } else if (cap->cmdchar == ':') { - /* do_cmdline() has stored the first typed line in - * "repeat_cmdline". When several lines are typed repeating - * won't be possible. */ - if (repeat_cmdline == NULL) + } else if (cap->cmdchar == ':' || cap->cmdchar == K_COMMAND) { + // do_cmdline() has stored the first typed line in + // "repeat_cmdline". When several lines are typed repeating + // won't be possible. + if (repeat_cmdline == NULL) { ResetRedobuff(); - else { + } else { AppendToRedobuffLit(repeat_cmdline, -1); AppendToRedobuff(NL_STR); xfree(repeat_cmdline); @@ -1637,16 +1638,22 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) /* Prepare for redoing. Only use the nchar field for "r", * otherwise it might be the second char of the operator. */ if (cap->cmdchar == 'g' && (cap->nchar == 'n' - || cap->nchar == 'N')) + || cap->nchar == 'N')) { prep_redo(oap->regname, cap->count0, - get_op_char(oap->op_type), get_extra_op_char(oap->op_type), - oap->motion_force, cap->cmdchar, cap->nchar); - else if (cap->cmdchar != ':') - prep_redo(oap->regname, 0L, NUL, 'v', - get_op_char(oap->op_type), - get_extra_op_char(oap->op_type), - oap->op_type == OP_REPLACE - ? cap->nchar : NUL); + get_op_char(oap->op_type), get_extra_op_char(oap->op_type), + oap->motion_force, cap->cmdchar, cap->nchar); + } else if (cap->cmdchar != ':') { + int nchar = oap->op_type == OP_REPLACE ? cap->nchar : NUL; + + // reverse what nv_replace() did + if (nchar == REPLACE_CR_NCHAR) { + nchar = CAR; + } else if (nchar == REPLACE_NL_NCHAR) { + nchar = NL; + } + prep_redo(oap->regname, 0L, NUL, 'v', get_op_char(oap->op_type), + get_extra_op_char(oap->op_type), nchar); + } if (!redo_VIsual_busy) { redo_VIsual_mode = resel_VIsual_mode; redo_VIsual_vcol = resel_VIsual_vcol; @@ -4518,23 +4525,22 @@ static void nv_exmode(cmdarg_T *cap) } } -/* - * Handle a ":" command. - */ +/// Handle a ":" command and <Cmd>. static void nv_colon(cmdarg_T *cap) { int old_p_im; bool cmd_result; + bool is_cmdkey = cap->cmdchar == K_COMMAND; - if (VIsual_active) + if (VIsual_active && !is_cmdkey) { nv_operator(cap); - else { + } else { if (cap->oap->op_type != OP_NOP) { // Using ":" as a movement is characterwise exclusive. cap->oap->motion_type = kMTCharWise; cap->oap->inclusive = false; - } else if (cap->count0) { - /* translate "count:" into ":.,.+(count - 1)" */ + } else if (cap->count0 && !is_cmdkey) { + // translate "count:" into ":.,.+(count - 1)" stuffcharReadbuff('.'); if (cap->count0 > 1) { stuffReadbuff(",.+"); @@ -4548,9 +4554,9 @@ static void nv_colon(cmdarg_T *cap) old_p_im = p_im; - /* get a command line and execute it */ - cmd_result = do_cmdline(NULL, getexline, NULL, - cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0); + // get a command line and execute it + cmd_result = do_cmdline(NULL, is_cmdkey ? getcmdkeycmd : getexline, NULL, + cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0); /* If 'insertmode' changed, enter or exit Insert mode */ if (p_im != old_p_im) { @@ -5031,26 +5037,21 @@ static void nv_right(cmdarg_T *cap) if ((!PAST_LINE && oneright() == false) || (PAST_LINE && *get_cursor_pos_ptr() == NUL) ) { - /* - * <Space> wraps to next line if 'whichwrap' has 's'. - * 'l' wraps to next line if 'whichwrap' has 'l'. - * CURS_RIGHT wraps to next line if 'whichwrap' has '>'. - */ - if ( ((cap->cmdchar == ' ' - && vim_strchr(p_ww, 's') != NULL) - || (cap->cmdchar == 'l' - && vim_strchr(p_ww, 'l') != NULL) - || (cap->cmdchar == K_RIGHT - && vim_strchr(p_ww, '>') != NULL)) - && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { - /* When deleting we also count the NL as a character. - * Set cap->oap->inclusive when last char in the line is - * included, move to next line after that */ - if ( cap->oap->op_type != OP_NOP - && !cap->oap->inclusive - && !lineempty(curwin->w_cursor.lnum)) + // <Space> wraps to next line if 'whichwrap' has 's'. + // 'l' wraps to next line if 'whichwrap' has 'l'. + // CURS_RIGHT wraps to next line if 'whichwrap' has '>'. + if (((cap->cmdchar == ' ' && vim_strchr(p_ww, 's') != NULL) + || (cap->cmdchar == 'l' && vim_strchr(p_ww, 'l') != NULL) + || (cap->cmdchar == K_RIGHT && vim_strchr(p_ww, '>') != NULL)) + && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { + // When deleting we also count the NL as a character. + // Set cap->oap->inclusive when last char in the line is + // included, move to next line after that + if (cap->oap->op_type != OP_NOP + && !cap->oap->inclusive + && !LINEEMPTY(curwin->w_cursor.lnum)) { cap->oap->inclusive = true; - else { + } else { ++curwin->w_cursor.lnum; curwin->w_cursor.col = 0; curwin->w_cursor.coladd = 0; @@ -5060,12 +5061,14 @@ static void nv_right(cmdarg_T *cap) continue; } if (cap->oap->op_type == OP_NOP) { - /* Only beep and flush if not moved at all */ - if (n == cap->count1) + // Only beep and flush if not moved at all + if (n == cap->count1) { beep_flush(); + } } else { - if (!lineempty(curwin->w_cursor.lnum)) + if (!LINEEMPTY(curwin->w_cursor.lnum)) { cap->oap->inclusive = true; + } } break; } else if (PAST_LINE) { @@ -5123,13 +5126,12 @@ static void nv_left(cmdarg_T *cap) coladvance((colnr_T)MAXCOL); curwin->w_set_curswant = true; - /* When the NL before the first char has to be deleted we - * put the cursor on the NUL after the previous line. - * This is a very special case, be careful! - * Don't adjust op_end now, otherwise it won't work. */ - if ( (cap->oap->op_type == OP_DELETE - || cap->oap->op_type == OP_CHANGE) - && !lineempty(curwin->w_cursor.lnum)) { + // When the NL before the first char has to be deleted we + // put the cursor on the NUL after the previous line. + // This is a very special case, be careful! + // Don't adjust op_end now, otherwise it won't work. + if ((cap->oap->op_type == OP_DELETE || cap->oap->op_type == OP_CHANGE) + && !LINEEMPTY(curwin->w_cursor.lnum)) { char_u *cp = get_cursor_pos_ptr(); if (*cp != NUL) { @@ -5228,12 +5230,12 @@ static void nv_gotofile(cmdarg_T *cap) if (ptr != NULL) { // do autowrite if necessary - if (curbufIsChanged() && curbuf->b_nwindows <= 1 && !P_HID(curbuf)) { + if (curbufIsChanged() && curbuf->b_nwindows <= 1 && !buf_hide(curbuf)) { (void)autowrite(curbuf, false); } setpcmark(); (void)do_ecmd(0, ptr, NULL, NULL, ECMD_LAST, - P_HID(curbuf) ? ECMD_HIDE : 0, curwin); + buf_hide(curbuf) ? ECMD_HIDE : 0, curwin); if (cap->nchar == 'F' && lnum >= 0) { curwin->w_cursor.lnum = lnum; check_cursor_lnum(); @@ -5687,6 +5689,8 @@ static void nv_brackets(cmdarg_T *cap) cap->nchar == 's', false, NULL) == 0) { clearopbeep(cap->oap); break; + } else { + curwin->w_set_curswant = true; } if (cap->oap->op_type == OP_NOP && (fdo_flags & FDO_SEARCH) && KeyTyped) foldOpenCursor(); @@ -5856,10 +5860,13 @@ static void nv_replace(cmdarg_T *cap) if (got_int) reset_VIsual(); if (had_ctrl_v) { - if (cap->nchar == '\r') - cap->nchar = -1; - else if (cap->nchar == '\n') - cap->nchar = -2; + // Use a special (negative) number to make a difference between a + // literal CR or NL and a line break. + if (cap->nchar == CAR) { + cap->nchar = REPLACE_CR_NCHAR; + } else if (cap->nchar == NL) { + cap->nchar = REPLACE_NL_NCHAR; + } } nv_operator(cap); return; @@ -6096,10 +6103,11 @@ static void n_swapchar(cmdarg_T *cap) pos_T startpos; int did_change = 0; - if (checkclearopq(cap->oap)) + if (checkclearopq(cap->oap)) { return; + } - if (lineempty(curwin->w_cursor.lnum) && vim_strchr(p_ww, '~') == NULL) { + if (LINEEMPTY(curwin->w_cursor.lnum) && vim_strchr(p_ww, '~') == NULL) { clearopbeep(cap->oap); return; } diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 432c9a8b47..b421d81b7e 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -1629,13 +1629,18 @@ int op_replace(oparg_T *oap, int c) colnr_T oldlen; struct block_def bd; char_u *after_p = NULL; - int had_ctrl_v_cr = (c == -1 || c == -2); + int had_ctrl_v_cr = false; if ((curbuf->b_ml.ml_flags & ML_EMPTY ) || oap->empty) return OK; /* nothing to do */ - if (had_ctrl_v_cr) - c = (c == -1 ? '\r' : '\n'); + if (c == REPLACE_CR_NCHAR) { + had_ctrl_v_cr = true; + c = CAR; + } else if (c == REPLACE_NL_NCHAR) { + had_ctrl_v_cr = true; + c = NL; + } if (has_mbyte) mb_adjust_opend(oap); @@ -1713,7 +1718,7 @@ int op_replace(oparg_T *oap, int c) // insert pre-spaces memset(newp + bd.textcol, ' ', (size_t)bd.startspaces); // insert replacement chars CHECK FOR ALLOCATED SPACE - // -1/-2 is used for entering CR literally. + // REPLACE_CR_NCHAR/REPLACE_NL_NCHAR is used for entering CR literally. size_t after_p_len = 0; if (had_ctrl_v_cr || (c != '\r' && c != '\n')) { // strlen(newp) at this point @@ -2052,10 +2057,11 @@ void op_insert(oparg_T *oap, long count1) curwin->w_cursor = oap->end; check_cursor_col(); - /* Works just like an 'i'nsert on the next character. */ - if (!lineempty(curwin->w_cursor.lnum) - && oap->start_vcol != oap->end_vcol) + // Works just like an 'i'nsert on the next character. + if (!LINEEMPTY(curwin->w_cursor.lnum) + && oap->start_vcol != oap->end_vcol) { inc_cursor(); + } } } @@ -2180,9 +2186,10 @@ int op_change(oparg_T *oap) } else if (op_delete(oap) == FAIL) return FALSE; - if ((l > curwin->w_cursor.col) && !lineempty(curwin->w_cursor.lnum) - && !virtual_op) + if ((l > curwin->w_cursor.col) && !LINEEMPTY(curwin->w_cursor.lnum) + && !virtual_op) { inc_cursor(); + } // check for still on same line (<CR> in inserted text meaningless) // skip blank lines too @@ -2564,11 +2571,11 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) dict_T *dict = get_vim_var_dict(VV_EVENT); // the yanked text - list_T *list = tv_list_alloc(); + list_T *const list = tv_list_alloc((ptrdiff_t)reg->y_size); for (size_t i = 0; i < reg->y_size; i++) { tv_list_append_string(list, (const char *)reg->y_array[i], -1); } - list->lv_lock = VAR_FIXED; + tv_list_set_lock(list, VAR_FIXED); tv_dict_add_list(dict, S_LEN("regcontents"), list); // the register type @@ -2855,25 +2862,30 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) } } else if (y_type == kMTLineWise) { lnum = curwin->w_cursor.lnum; - /* Correct line number for closed fold. Don't move the cursor yet, - * u_save() uses it. */ - if (dir == BACKWARD) + // Correct line number for closed fold. Don't move the cursor yet, + // u_save() uses it. + if (dir == BACKWARD) { (void)hasFolding(lnum, &lnum, NULL); - else + } else { (void)hasFolding(lnum, NULL, &lnum); - if (dir == FORWARD) - ++lnum; - /* In an empty buffer the empty line is going to be replaced, include - * it in the saved lines. */ - if ((bufempty() ? u_save(0, 2) : u_save(lnum - 1, lnum)) == FAIL) + } + if (dir == FORWARD) { + lnum++; + } + // In an empty buffer the empty line is going to be replaced, include + // it in the saved lines. + if ((BUFEMPTY() ? u_save(0, 2) : u_save(lnum - 1, lnum)) == FAIL) { goto end; - if (dir == FORWARD) + } + if (dir == FORWARD) { curwin->w_cursor.lnum = lnum - 1; - else + } else { curwin->w_cursor.lnum = lnum; - curbuf->b_op_start = curwin->w_cursor; /* for mark_adjust() */ - } else if (u_save_cursor() == FAIL) + } + curbuf->b_op_start = curwin->w_cursor; // for mark_adjust() + } else if (u_save_cursor() == FAIL) { goto end; + } yanklen = (int)STRLEN(y_array[0]); @@ -3066,15 +3078,26 @@ 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 current line if (y_type == kMTCharWise && y_size == 1) { + linenr_T end_lnum = 0; // init for gcc + + 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; + } + } + do { totlen = (size_t)(count * yanklen); if (totlen > 0) { oldp = ml_get(lnum); - newp = (char_u *) xmalloc((size_t)(STRLEN(oldp) + totlen + 1)); + if (VIsual_active && col > (int)STRLEN(oldp)) { + lnum++; + continue; + } + newp = (char_u *)xmalloc((size_t)(STRLEN(oldp) + totlen + 1)); memmove(newp, oldp, (size_t)col); ptr = newp + col; for (i = 0; i < (size_t)count; i++) { @@ -3090,11 +3113,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) curwin->w_cursor.col += (colnr_T)(totlen - 1); } } - if (VIsual_active) + if (VIsual_active) { lnum++; - } while (VIsual_active - && (lnum <= curbuf->b_visual.vi_end.lnum - || lnum <= curbuf->b_visual.vi_start.lnum)); + } + } while (VIsual_active && lnum <= end_lnum); if (VIsual_active) { /* reset lnum to the last visual line */ lnum--; @@ -3177,9 +3199,9 @@ error: curbuf->b_op_start.lnum++; } // Skip mark_adjust when adding lines after the last one, there - // can't be marks there. + // can't be marks there. But still needed in diff mode. if (curbuf->b_op_start.lnum + (y_type == kMTCharWise) - 1 + nr_lines - < curbuf->b_ml.ml_line_count) { + < curbuf->b_ml.ml_line_count || curwin->w_p_diff) { mark_adjust(curbuf->b_op_start.lnum + (y_type == kMTCharWise), (linenr_T)MAXLNUM, nr_lines, 0L, false); } @@ -3997,7 +4019,7 @@ format_lines ( && (do_second_indent || do_number_indent) && prev_is_end_par && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { - if (do_second_indent && !lineempty(curwin->w_cursor.lnum + 1)) { + if (do_second_indent && !LINEEMPTY(curwin->w_cursor.lnum + 1)) { if (leader_len == 0 && next_leader_len == 0) { /* no comment found */ second_indent = @@ -4854,9 +4876,8 @@ static void *get_reg_wrap_one_line(char_u *s, int flags) if (!(flags & kGRegList)) { return s; } - list_T *list = tv_list_alloc(); - tv_list_append_string(list, NULL, 0); - list->lv_first->li_tv.vval.v_string = s; + list_T *const list = tv_list_alloc(1); + tv_list_append_allocated_string(list, (char *)s); return list; } @@ -4905,7 +4926,7 @@ void *get_reg_contents(int regname, int flags) return NULL; if (flags & kGRegList) { - list_T *list = tv_list_alloc(); + list_T *const list = tv_list_alloc((ptrdiff_t)reg->y_size); for (size_t i = 0; i < reg->y_size; i++) { tv_list_append_string(list, (const char *)reg->y_array[i], -1); } @@ -5594,7 +5615,7 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet) } free_register(reg); - list_T *const args = tv_list_alloc(); + list_T *const args = tv_list_alloc(1); const char regname = (char)name; tv_list_append_string(args, ®name, 1); @@ -5610,13 +5631,14 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet) list_T *res = result.vval.v_list; list_T *lines = NULL; - if (res->lv_len == 2 && res->lv_first->li_tv.v_type == VAR_LIST) { - lines = res->lv_first->li_tv.vval.v_list; - if (res->lv_last->li_tv.v_type != VAR_STRING) { + if (tv_list_len(res) == 2 + && TV_LIST_ITEM_TV(tv_list_first(res))->v_type == VAR_LIST) { + lines = TV_LIST_ITEM_TV(tv_list_first(res))->vval.v_list; + if (TV_LIST_ITEM_TV(tv_list_last(res))->v_type != VAR_STRING) { goto err; } - char_u *regtype = res->lv_last->li_tv.vval.v_string; - if (regtype == NULL || strlen((char*)regtype) > 1) { + char_u *regtype = TV_LIST_ITEM_TV(tv_list_last(res))->vval.v_string; + if (regtype == NULL || strlen((char *)regtype) > 1) { goto err; } switch (regtype[0]) { @@ -5641,20 +5663,21 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet) reg->y_type = kMTUnknown; } - reg->y_array = xcalloc((size_t)lines->lv_len, sizeof(uint8_t *)); - reg->y_size = (size_t)lines->lv_len; + reg->y_array = xcalloc((size_t)tv_list_len(lines), sizeof(char_u *)); + reg->y_size = (size_t)tv_list_len(lines); reg->additional_data = NULL; reg->timestamp = 0; // Timestamp is not saved for clipboard registers because clipboard registers // are not saved in the ShaDa file. int i = 0; - for (listitem_T *li = lines->lv_first; li != NULL; li = li->li_next) { - if (li->li_tv.v_type != VAR_STRING) { + TV_LIST_ITER_CONST(lines, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING) { goto err; } - reg->y_array[i++] = (char_u *)xstrdupnul((char *)li->li_tv.vval.v_string); - } + reg->y_array[i++] = (char_u *)xstrdupnul( + (const char *)TV_LIST_ITEM_TV(li)->vval.v_string); + }); if (reg->y_size > 0 && strlen((char*)reg->y_array[reg->y_size-1]) == 0) { // a known-to-be charwise yank might have a final linebreak @@ -5711,15 +5734,13 @@ static void set_clipboard(int name, yankreg_T *reg) return; } - list_T *lines = tv_list_alloc(); + list_T *const lines = tv_list_alloc( + (ptrdiff_t)reg->y_size + (reg->y_type != kMTCharWise)); for (size_t i = 0; i < reg->y_size; i++) { tv_list_append_string(lines, (const char *)reg->y_array[i], -1); } - list_T *args = tv_list_alloc(); - tv_list_append_list(args, lines); - char regtype; switch (reg->y_type) { case kMTLineWise: { @@ -5740,10 +5761,11 @@ static void set_clipboard(int name, yankreg_T *reg) assert(false); } } - tv_list_append_string(args, ®type, 1); - const char regname = (char)name; - tv_list_append_string(args, ®name, 1); + list_T *args = tv_list_alloc(3); + tv_list_append_list(args, lines); + tv_list_append_string(args, ®type, 1); + tv_list_append_string(args, ((char[]) { (char)name }), 1); (void)eval_call_provider("clipboard", "set", args); } diff --git a/src/nvim/option.c b/src/nvim/option.c index 913d27d508..c43ba2fc4f 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -13,7 +13,8 @@ // add some code to didset_window_options(). // - For a buffer option, add some code to buf_copy_options(). // - For a buffer string option, add code to check_buf_options(). -// - If it's a numeric option, add any necessary bounds checks to do_set(). +// - If it's a numeric option, add any necessary bounds checks to +// set_num_option(). // - If it's a list of flags, add some code in do_set(), search for WW_ALL. // - When adding an option with expansion (P_EXPAND), but with a different // default for Vi and Vim (no P_VI_DEF), add some code at VIMEXP. @@ -74,7 +75,9 @@ #include "nvim/undo.h" #include "nvim/window.h" #include "nvim/os/os.h" +#include "nvim/api/private/helpers.h" #include "nvim/os/input.h" +#include "nvim/os/lang.h" /* * The options that are local to a window or buffer have "indir" set to one of @@ -247,6 +250,7 @@ typedef struct vimoption { #define P_RWINONLY 0x10000000U ///< only redraw current window #define P_NDNAME 0x20000000U ///< only normal dir name chars allowed +#define P_UI_OPTION 0x40000000U ///< send option to remote ui #define HIGHLIGHT_INIT \ "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText," \ @@ -784,6 +788,8 @@ void set_init_1(void) didset_options2(); + lang_init(); + // enc_locale() will try to find the encoding of the current locale. // This will be used when 'default' is used as encoding specifier // in 'fileencodings' @@ -826,24 +832,26 @@ set_option_default ( if (flags & P_STRING) { /* Use set_string_option_direct() for local options to handle * freeing and allocating the value. */ - if (options[opt_idx].indir != PV_NONE) + if (options[opt_idx].indir != PV_NONE) { set_string_option_direct(NULL, opt_idx, - options[opt_idx].def_val[dvi], opt_flags, 0); - else { - if ((opt_flags & OPT_FREE) && (flags & P_ALLOCED)) + options[opt_idx].def_val[dvi], opt_flags, 0); + } else { + if ((opt_flags & OPT_FREE) && (flags & P_ALLOCED)) { free_string_option(*(char_u **)(varp)); + } *(char_u **)varp = options[opt_idx].def_val[dvi]; options[opt_idx].flags &= ~P_ALLOCED; } } else if (flags & P_NUM) { - if (options[opt_idx].indir == PV_SCROLL) + if (options[opt_idx].indir == PV_SCROLL) { win_comp_scroll(curwin); - else { - *(long *)varp = (long)options[opt_idx].def_val[dvi]; - /* May also set global value for local option. */ - if (both) + } else { + *(long *)varp = (long)(intptr_t)options[opt_idx].def_val[dvi]; + // May also set global value for local option. + if (both) { *(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL) = *(long *)varp; + } } } else { /* P_BOOL */ *(int *)varp = (int)(intptr_t)options[opt_idx].def_val[dvi]; @@ -921,7 +929,7 @@ void set_number_default(char *name, long val) opt_idx = findoption(name); if (opt_idx >= 0) { - options[opt_idx].def_val[VI_DEFAULT] = (char_u *)val; + options[opt_idx].def_val[VI_DEFAULT] = (char_u *)(intptr_t)val; } } @@ -1052,7 +1060,7 @@ void set_init_3(void) xfree(p); } - if (bufempty()) { + if (BUFEMPTY()) { int idx_ffs = findoption_len(S_LEN("ffs")); // Apply the first entry of 'fileformats' to the initial buffer. @@ -1185,6 +1193,7 @@ do_set ( set_options_default(OPT_FREE | opt_flags); didset_options(); didset_options2(); + ui_refresh_options(); redraw_all_later(CLEAR); } else { showoptions(1, opt_flags); @@ -1434,20 +1443,19 @@ do_set ( * [-]0-9 set number * other error */ - ++arg; - if (nextchar == '&') - value = (long)options[opt_idx].def_val[ - ((flags & P_VI_DEF) || cp_val) - ? VI_DEFAULT : VIM_DEFAULT]; - else if (nextchar == '<') { - /* For 'undolevels' NO_LOCAL_UNDOLEVEL means to - * use the global value. */ - if ((long *)varp == &curbuf->b_p_ul - && opt_flags == OPT_LOCAL) + arg++; + if (nextchar == '&') { + value = (long)(intptr_t)options[opt_idx].def_val[ + ((flags & P_VI_DEF) || cp_val) ? VI_DEFAULT : VIM_DEFAULT]; + } else if (nextchar == '<') { + // For 'undolevels' NO_LOCAL_UNDOLEVEL means to + // use the global value. + if ((long *)varp == &curbuf->b_p_ul && opt_flags == OPT_LOCAL) { value = NO_LOCAL_UNDOLEVEL; - else + } else { value = *(long *)get_varp_scope( &(options[opt_idx]), OPT_GLOBAL); + } } else if (((long *)varp == &p_wc || (long *)varp == &p_wcm) && (*arg == '<' @@ -1460,8 +1468,7 @@ do_set ( goto skip; } } else if (*arg == '-' || ascii_isdigit(*arg)) { - // Allow negative (for 'undolevels'), octal and - // hex numbers. + // Allow negative, octal and hex numbers. vim_str2nr(arg, NULL, &i, STR2NR_ALL, &value, NULL, 0); if (arg[i] != NUL && !ascii_iswhite(arg[i])) { errmsg = e_invarg; @@ -1491,6 +1498,7 @@ do_set ( char_u *newval; char_u *origval = NULL; char *saved_origval = NULL; + char *saved_newval = NULL; unsigned newlen; int comma; int bs; @@ -1507,7 +1515,17 @@ do_set ( /* The old value is kept until we are sure that the * new value is valid. */ oldval = *(char_u **)varp; - if (nextchar == '&') { /* set to default val */ + + // When setting the local value of a global + // option, the old value may be the global value. + if (((int)options[opt_idx].indir & PV_BOTH) && (opt_flags + & OPT_LOCAL)) { + origval = *(char_u **)get_varp(&options[opt_idx]); + } else { + origval = oldval; + } + + if (nextchar == '&') { // set to default val newval = options[opt_idx].def_val[ ((flags & P_VI_DEF) || cp_val) ? VI_DEFAULT : VIM_DEFAULT]; @@ -1566,6 +1584,9 @@ do_set ( break; } xfree(oldval); + if (origval == oldval) { + origval = *(char_u **)varp; + } oldval = *(char_u **)varp; } /* @@ -1602,15 +1623,6 @@ do_set ( ++arg; } - /* When setting the local value of a global - * option, the old value may be the global value. */ - if (((int)options[opt_idx].indir & PV_BOTH) - && (opt_flags & OPT_LOCAL)) - origval = *(char_u **)get_varp( - &options[opt_idx]); - else - origval = oldval; - /* * Copy the new string into allocated memory. * Can't use set_string_option_direct(), because @@ -1780,39 +1792,37 @@ do_set ( new_value_alloced = TRUE; } - /* Set the new value. */ + // Set the new value. *(char_u **)(varp) = newval; - if (!starting && origval != NULL) { + if (!starting && origval != NULL && newval != NULL) { // origval may be freed by // did_set_string_option(), make a copy. - saved_origval = xstrdup((char *) origval); + saved_origval = xstrdup((char *)origval); + // newval (and varp) may become invalid if the + // buffer is closed by autocommands. + saved_newval = xstrdup((char *)newval); } - /* Handle side effects, and set the global value for - * ":set" on local options. */ + // Handle side effects, and set the global value for + // ":set" on local options. Note: when setting 'syntax' + // or 'filetype' autocommands may be triggered that can + // cause havoc. errmsg = did_set_string_option(opt_idx, (char_u **)varp, new_value_alloced, oldval, errbuf, opt_flags); + if (errmsg == NULL) { + trigger_optionsset_string(opt_idx, opt_flags, saved_origval, + saved_newval); + } + xfree(saved_origval); + xfree(saved_newval); + // If error detected, print the error message. if (errmsg != NULL) { - xfree(saved_origval); goto skip; } - if (saved_origval != NULL) { - char buf_type[7]; - vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s", - (opt_flags & OPT_LOCAL) ? "local" : "global"); - set_vim_var_string(VV_OPTION_NEW, *(char **) varp, -1); - set_vim_var_string(VV_OPTION_OLD, saved_origval, -1); - set_vim_var_string(VV_OPTION_TYPE, buf_type, -1); - apply_autocmds(EVENT_OPTIONSET, - (char_u *)options[opt_idx].fullname, - NULL, false, NULL); - reset_v_option_vars(); - xfree(saved_origval); - } } else { // key code option(FIXME(tarruda): Show a warning or something // similar) @@ -2207,6 +2217,7 @@ void check_buf_options(buf_T *buf) check_string_option(&buf->b_p_tsr); check_string_option(&buf->b_p_lw); check_string_option(&buf->b_p_bkc); + check_string_option(&buf->b_p_menc); } /* @@ -2248,7 +2259,7 @@ int was_set_insecurely(char_u *opt, int opt_flags) uint32_t *flagp = insecure_flag(idx, opt_flags); return (*flagp & P_INSECURE) != 0; } - EMSG2(_(e_intern2), "was_set_insecurely()"); + internal_error("was_set_insecurely()"); return -1; } @@ -2307,8 +2318,8 @@ set_string_option_direct ( if (idx == -1) { // Use name. idx = findoption((const char *)name); if (idx < 0) { // Not found (should not happen). - EMSG2(_(e_intern2), "set_string_option_direct()"); - EMSG2(_("For option %s"), name); + internal_error("set_string_option_direct()"); + IEMSG2(_("For option %s"), name); return; } } @@ -2394,6 +2405,7 @@ static char *set_string_option(const int opt_idx, const char *const value, *varp = s; char *const saved_oldval = (starting ? NULL : xstrdup(oldval)); + char *const saved_newval = (starting ? NULL : xstrdup(s)); char *const r = (char *)did_set_string_option( opt_idx, (char_u **)varp, (int)true, (char_u *)oldval, NULL, opt_flags); @@ -2402,19 +2414,12 @@ static char *set_string_option(const int opt_idx, const char *const value, } // call autocommand after handling side effects - if (saved_oldval != NULL) { - char buf_type[7]; - vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s", - (opt_flags & OPT_LOCAL) ? "local" : "global"); - set_vim_var_string(VV_OPTION_NEW, (char *)(*varp), -1); - set_vim_var_string(VV_OPTION_OLD, saved_oldval, -1); - set_vim_var_string(VV_OPTION_TYPE, buf_type, -1); - apply_autocmds(EVENT_OPTIONSET, - (char_u *)options[opt_idx].fullname, - NULL, false, NULL); - reset_v_option_vars(); - xfree(saved_oldval); + if (r == NULL) { + trigger_optionsset_string(opt_idx, opt_flags, + saved_oldval, saved_newval); } + xfree(saved_oldval); + xfree(saved_newval); return r; } @@ -2431,6 +2436,11 @@ static bool valid_filetype(char_u *val) return true; } +#ifdef _MSC_VER +// MSVC optimizations are disabled for this function because it +// incorrectly generates an empty string for SHM_ALL. +#pragma optimize("", off) +#endif /* * Handle string options that need some action to perform when changed. * Returns NULL for success, or an error message for an error. @@ -2450,6 +2460,7 @@ did_set_string_option ( int did_chartab = FALSE; char_u **gvarp; bool free_oldval = (options[opt_idx].flags & P_ALLOCED); + int ft_changed = false; /* Get the global option to compare with, otherwise we would have to check * two values for all local options. */ @@ -2621,8 +2632,8 @@ did_set_string_option ( else if (varp == &p_ei) { if (check_ei() == FAIL) errmsg = e_invarg; - /* 'encoding' and 'fileencoding' */ - } else if (varp == &p_enc || gvarp == &p_fenc) { + // 'encoding', 'fileencoding' and 'makeencoding' + } else if (varp == &p_enc || gvarp == &p_fenc || gvarp == &p_menc) { if (gvarp == &p_fenc) { if (!MODIFIABLE(curbuf) && opt_flags != OPT_GLOBAL) { errmsg = e_modifiable; @@ -3169,6 +3180,8 @@ did_set_string_option ( } else if (gvarp == &p_ft) { if (!valid_filetype(*varp)) { errmsg = e_invarg; + } else { + ft_changed = STRCMP(oldval, *varp) != 0; } } else if (gvarp == &p_syn) { if (!valid_filetype(*varp)) { @@ -3251,10 +3264,12 @@ did_set_string_option ( apply_autocmds(EVENT_SYNTAX, curbuf->b_p_syn, curbuf->b_fname, TRUE, curbuf); } else if (varp == &(curbuf->b_p_ft)) { - /* 'filetype' is set, trigger the FileType autocommand */ - did_filetype = TRUE; - apply_autocmds(EVENT_FILETYPE, curbuf->b_p_ft, - curbuf->b_fname, TRUE, curbuf); + // 'filetype' is set, trigger the FileType autocommand + if (!(opt_flags & OPT_MODELINE) || ft_changed) { + did_filetype = true; + apply_autocmds(EVENT_FILETYPE, curbuf->b_p_ft, + curbuf->b_fname, true, curbuf); + } } if (varp == &(curwin->w_s->b_p_spl)) { char_u fname[200]; @@ -3295,6 +3310,9 @@ did_set_string_option ( return errmsg; } +#ifdef _MSC_VER +#pragma optimize("", on) +#endif /* * Simple int comparison function for use with qsort() @@ -3371,37 +3389,39 @@ skip: return NULL; /* no error */ } -/* - * Handle setting 'listchars' or 'fillchars'. - * Returns error message, NULL if it's OK. - */ + +/// Handle setting 'listchars' or 'fillchars'. +/// Assume monocell characters +/// +/// @param varp either &p_lcs ('listchars') or &p_fcs ('fillchar') +/// @return error message, NULL if it's OK. static char_u *set_chars_option(char_u **varp) { int round, i, len, entries; char_u *p, *s; int c1, c2 = 0; struct charstab { - int *cp; - char *name; + int *cp; ///< char value + char *name; ///< char id + int def; ///< default value }; - static struct charstab filltab[] = - { - {&fill_stl, "stl"}, - {&fill_stlnc, "stlnc"}, - {&fill_vert, "vert"}, - {&fill_fold, "fold"}, - {&fill_diff, "diff"}, + static struct charstab filltab[] = { + { &fill_stl, "stl" , ' ' }, + { &fill_stlnc, "stlnc", ' ' }, + { &fill_vert, "vert" , 9474 }, // │ + { &fill_fold, "fold" , 183 }, // · + { &fill_diff, "diff" , '-' }, + { &fill_msgsep, "msgsep", ' ' }, }; - static struct charstab lcstab[] = - { - {&lcs_eol, "eol"}, - {&lcs_ext, "extends"}, - {&lcs_nbsp, "nbsp"}, - {&lcs_prec, "precedes"}, - {&lcs_space, "space"}, - {&lcs_tab2, "tab"}, - {&lcs_trail, "trail"}, - {&lcs_conceal, "conceal"}, + static struct charstab lcstab[] = { + { &lcs_eol, "eol", NUL }, + { &lcs_ext, "extends", NUL }, + { &lcs_nbsp, "nbsp", NUL }, + { &lcs_prec, "precedes", NUL }, + { &lcs_space, "space", NUL }, + { &lcs_tab2, "tab", NUL }, + { &lcs_trail, "trail", NUL }, + { &lcs_conceal, "conceal", NUL }, }; struct charstab *tab; @@ -3411,20 +3431,29 @@ static char_u *set_chars_option(char_u **varp) } else { tab = filltab; entries = ARRAY_SIZE(filltab); + if (*p_ambw == 'd') { + // XXX: If ambiwidth=double then "|" and "·" take 2 columns, which is + // forbidden (TUI limitation?). Set old defaults. + filltab[2].def = '|'; + filltab[3].def = '-'; + } else { + filltab[2].def = 9474; // │ + filltab[3].def = 183; // · + } } - /* first round: check for valid value, second round: assign values */ - for (round = 0; round <= 1; ++round) { + // first round: check for valid value, second round: assign values + for (round = 0; round <= 1; round++) { if (round > 0) { - /* After checking that the value is valid: set defaults: space for - * 'fillchars', NUL for 'listchars' */ - for (i = 0; i < entries; ++i) - if (tab[i].cp != NULL) - *(tab[i].cp) = (varp == &p_lcs ? NUL : ' '); - if (varp == &p_lcs) + // After checking that the value is valid: set defaults + for (i = 0; i < entries; i++) { + if (tab[i].cp != NULL) { + *(tab[i].cp) = tab[i].def; + } + } + if (varp == &p_lcs) { lcs_tab1 = NUL; - else - fill_diff = '-'; + } } p = *varp; while (*p) { @@ -4021,6 +4050,10 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, (char_u *) options[opt_idx].fullname, NULL, false, NULL); reset_v_option_vars(); + if (options[opt_idx].flags & P_UI_OPTION) { + ui_call_option_set(cstr_as_string(options[opt_idx].fullname), + BOOLEAN_OBJ(value)); + } } comp_col(); /* in case 'ruler' or 'showcmd' changed */ @@ -4057,238 +4090,259 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, return (char *)e_secure; } - *pp = value; - /* Remember where the option was set. */ - set_option_scriptID_idx(opt_idx, opt_flags, current_SID); - - if (curbuf->b_p_sw < 0) { - errmsg = e_positive; - curbuf->b_p_sw = curbuf->b_p_ts; + // Many number options assume their value is in the signed int range. + if (value < INT_MIN || value > INT_MAX) { + return (char *)e_invarg; } - /* - * Number options that need some action when changed - */ - if (pp == &p_wh || pp == &p_hh) { - if (p_wh < 1) { + // Options that need some validation. + if (pp == &p_wh) { + if (value < 1) { errmsg = e_positive; - p_wh = 1; - } - if (p_wmh > p_wh) { + } else if (p_wmh > value) { errmsg = e_winheight; - p_wh = p_wmh; } - if (p_hh < 0) { + } else if (pp == &p_hh) { + if (value < 0) { errmsg = e_positive; - p_hh = 0; - } - - /* Change window height NOW */ - if (!ONE_WINDOW) { - if (pp == &p_wh && curwin->w_height < p_wh) - win_setheight((int)p_wh); - if (pp == &p_hh && curbuf->b_help && curwin->w_height < p_hh) - win_setheight((int)p_hh); } - } - /* 'winminheight' */ - else if (pp == &p_wmh) { - if (p_wmh < 0) { + } else if (pp == &p_wmh) { + if (value < 0) { errmsg = e_positive; - p_wmh = 0; - } - if (p_wmh > p_wh) { + } else if (value > p_wh) { errmsg = e_winheight; - p_wmh = p_wh; } - win_setminheight(); } else if (pp == &p_wiw) { - if (p_wiw < 1) { + if (value < 1) { errmsg = e_positive; - p_wiw = 1; + } else if (p_wmw > value) { + errmsg = e_winwidth; } - if (p_wmw > p_wiw) { + } else if (pp == &p_wmw) { + if (value < 0) { + errmsg = e_positive; + } else if (value > p_wiw) { errmsg = e_winwidth; - p_wiw = p_wmw; } - - /* Change window width NOW */ - if (!ONE_WINDOW && curwin->w_width < p_wiw) - win_setwidth((int)p_wiw); - } - /* 'winminwidth' */ - else if (pp == &p_wmw) { - if (p_wmw < 0) { + } else if (pp == &p_mco) { + if (value > MAX_MCO) { + errmsg = e_invarg; + } else if (value < 0) { errmsg = e_positive; - p_wmw = 0; } - if (p_wmw > p_wiw) { - errmsg = e_winwidth; - p_wmw = p_wiw; + } else if (pp == &p_titlelen) { + if (value < 0) { + errmsg = e_positive; } - win_setminheight(); - } else if (pp == &p_ls) { - /* (re)set last window status line */ - last_status(false); - } - /* (re)set tab page line */ - else if (pp == &p_stal) { - shell_new_rows(); /* recompute window positions and heights */ - } - /* 'foldlevel' */ - else if (pp == &curwin->w_p_fdl) { - if (curwin->w_p_fdl < 0) - curwin->w_p_fdl = 0; - newFoldLevel(); - } - /* 'foldminlines' */ - else if (pp == &curwin->w_p_fml) { - foldUpdateAll(curwin); - } - /* 'foldnestmax' */ - else if (pp == &curwin->w_p_fdn) { - if (foldmethodIsSyntax(curwin) || foldmethodIsIndent(curwin)) - foldUpdateAll(curwin); - } - /* 'foldcolumn' */ - else if (pp == &curwin->w_p_fdc) { - if (curwin->w_p_fdc < 0) { + } else if (pp == &p_uc) { + if (value < 0) { errmsg = e_positive; - curwin->w_p_fdc = 0; - } else if (curwin->w_p_fdc > 12) { - errmsg = e_invarg; - curwin->w_p_fdc = 12; } - // 'shiftwidth' or 'tabstop' - } else if (pp == &curbuf->b_p_sw || pp == (long *)&curbuf->b_p_ts) { - if (foldmethodIsIndent(curwin)) { - foldUpdateAll(curwin); + } else if (pp == &p_ch) { + if (value < 1) { + errmsg = e_positive; } - // When 'shiftwidth' changes, or it's zero and 'tabstop' changes: - // parse 'cinoptions'. - if (pp == &curbuf->b_p_sw || curbuf->b_p_sw == 0) { - parse_cino(curbuf); + } else if (pp == &p_tm) { + if (value < 0) { + errmsg = e_positive; } - } - /* 'maxcombine' */ - else if (pp == &p_mco) { - if (p_mco > MAX_MCO) - p_mco = MAX_MCO; - else if (p_mco < 0) - p_mco = 0; - screenclear(); /* will re-allocate the screen */ - } else if (pp == &curbuf->b_p_iminsert) { - if (curbuf->b_p_iminsert < 0 || curbuf->b_p_iminsert > B_IMODE_LAST) { + } else if (pp == &p_hi) { + if (value < 0) { + errmsg = e_positive; + } else if (value > 10000) { errmsg = e_invarg; - curbuf->b_p_iminsert = B_IMODE_NONE; } - p_iminsert = curbuf->b_p_iminsert; - showmode(); - /* Show/unshow value of 'keymap' in status lines. */ - status_redraw_curbuf(); - } else if (pp == &p_window) { - if (p_window < 1) - p_window = 1; - else if (p_window >= Rows) - p_window = Rows - 1; - } else if (pp == &curbuf->b_p_imsearch) { - if (curbuf->b_p_imsearch < -1 || curbuf->b_p_imsearch > B_IMODE_LAST) { + } else if (pp == &p_re) { + if (value < 0 || value > 2) { errmsg = e_invarg; - curbuf->b_p_imsearch = B_IMODE_NONE; } - p_imsearch = curbuf->b_p_imsearch; - } else if (pp == &p_channel || pp == &curbuf->b_p_channel) { - errmsg = e_invarg; - *pp = old_value; - } - /* if 'titlelen' has changed, redraw the title */ - else if (pp == &p_titlelen) { - if (p_titlelen < 0) { + } else if (pp == &p_report) { + if (value < 0) { errmsg = e_positive; - p_titlelen = 85; } - if (starting != NO_SCREEN && old_value != p_titlelen) - need_maketitle = TRUE; - } - /* if p_ch changed value, change the command line height */ - else if (pp == &p_ch) { - if (p_ch < 1) { + } else if (pp == &p_titlelen) { + if (value < 0) { errmsg = e_positive; - p_ch = 1; } - if (p_ch > Rows - min_rows() + 1) - p_ch = Rows - min_rows() + 1; - - /* Only compute the new window layout when startup has been - * completed. Otherwise the frame sizes may be wrong. */ - if (p_ch != old_value && full_screen - ) - command_height(); - } - /* when 'updatecount' changes from zero to non-zero, open swap files */ - else if (pp == &p_uc) { - if (p_uc < 0) { + } else if (pp == &p_so) { + if (value < 0 && full_screen) { + errmsg = e_scroll; + } + } else if (pp == &p_siso) { + if (value < 0 && full_screen) { errmsg = e_positive; - p_uc = 100; } - if (p_uc && !old_value) - ml_open_files(); - } else if (pp == &curwin->w_p_cole) { - if (curwin->w_p_cole < 0) { + } else if (pp == &p_cwh) { + if (value < 1) { + errmsg = e_positive; + } + } else if (pp == &p_ut) { + if (value < 0) { + errmsg = e_positive; + } + } else if (pp == &p_ss) { + if (value < 0) { + errmsg = e_positive; + } + } else if (pp == &curwin->w_p_fdl + || pp == (long *)GLOBAL_WO(&curwin->w_p_fdl)) { + if (value < 0) { + errmsg = e_positive; + } + } else if (pp == &curwin->w_p_fdc + || pp == (long *)GLOBAL_WO(&curwin->w_p_fdc)) { + if (value < 0) { errmsg = e_positive; - curwin->w_p_cole = 0; - } else if (curwin->w_p_cole > 3) { + } else if (value > 12) { errmsg = e_invarg; - curwin->w_p_cole = 3; - } - } - /* sync undo before 'undolevels' changes */ - else if (pp == &p_ul) { - /* use the old value, otherwise u_sync() may not work properly */ - p_ul = old_value; - u_sync(TRUE); - p_ul = value; - } else if (pp == &curbuf->b_p_ul) { - /* use the old value, otherwise u_sync() may not work properly */ - curbuf->b_p_ul = old_value; - u_sync(TRUE); - curbuf->b_p_ul = value; - } - /* 'numberwidth' must be positive */ - else if (pp == &curwin->w_p_nuw) { - if (curwin->w_p_nuw < 1) { + } + } else if (pp == &curwin->w_p_cole + || pp == (long *)GLOBAL_WO(&curwin->w_p_cole)) { + if (value < 0) { errmsg = e_positive; - curwin->w_p_nuw = 1; + } else if (value > 3) { + errmsg = e_invarg; } - if (curwin->w_p_nuw > 10) { + } else if (pp == &curwin->w_p_nuw + || pp == (long *)GLOBAL_WO(&curwin->w_p_nuw)) { + if (value < 1) { + errmsg = e_positive; + } else if (value > 10) { errmsg = e_invarg; - curwin->w_p_nuw = 10; } - curwin->w_nrwidth_line_count = 0; - } else if (pp == &curbuf->b_p_tw) { - if (curbuf->b_p_tw < 0) { + } else if (pp == &curbuf->b_p_iminsert || pp == &p_iminsert) { + if (value < 0 || value > B_IMODE_LAST) { + errmsg = e_invarg; + } + } else if (pp == &curbuf->b_p_imsearch || pp == &p_imsearch) { + if (value < -1 || value > B_IMODE_LAST) { + errmsg = e_invarg; + } + } else if (pp == &curbuf->b_p_channel || pp == &p_channel) { + errmsg = e_invarg; + } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { + if (value < -1 || value > SB_MAX + || (value != -1 && opt_flags == OPT_LOCAL && !curbuf->terminal)) { + errmsg = e_invarg; + } + } else if (pp == &curbuf->b_p_sw || pp == &p_sw) { + if (value < 0) { errmsg = e_positive; - curbuf->b_p_tw = 0; } + } else if (pp == &curbuf->b_p_ts || pp == &p_ts) { + if (value < 1) { + errmsg = e_positive; + } + } else if (pp == &curbuf->b_p_tw || pp == &p_tw) { + if (value < 0) { + errmsg = e_positive; + } + } + + // Don't change the value and return early if validation failed. + if (errmsg != NULL) { + return (char *)errmsg; + } + + *pp = value; + // Remember where the option was set. + set_option_scriptID_idx(opt_idx, opt_flags, current_SID); + + // For these options we want to fix some invalid values. + if (pp == &p_window) { + if (p_window < 1) { + p_window = Rows - 1; + } else if (p_window >= Rows) { + p_window = Rows - 1; + } + } else if (pp == &p_ch) { + if (p_ch > Rows - min_rows() + 1) { + p_ch = Rows - min_rows() + 1; + } + } + // Number options that need some action when changed + if (pp == &p_wh) { + if (!ONE_WINDOW && curwin->w_height < p_wh) { + win_setheight((int)p_wh); + } + } else if (pp == &p_hh) { + if (!ONE_WINDOW && curbuf->b_help && curwin->w_height < p_hh) { + win_setheight((int)p_hh); + } + } else if (pp == &p_wmh) { + win_setminheight(); + } else if (pp == &p_wiw) { + if (!ONE_WINDOW && curwin->w_width < p_wiw) { + win_setwidth((int)p_wiw); + } + } else if (pp == &p_ls) { + last_status(false); // (re)set last window status line. + } else if (pp == &p_stal) { + // (re)set tab page line + shell_new_rows(); // recompute window positions and heights + } else if (pp == &curwin->w_p_fdl) { + newFoldLevel(); + } else if (pp == &curwin->w_p_fml) { + foldUpdateAll(curwin); + } else if (pp == &curwin->w_p_fdn) { + if (foldmethodIsSyntax(curwin) || foldmethodIsIndent(curwin)) { + foldUpdateAll(curwin); + } + } else if (pp == &curbuf->b_p_sw || pp == &curbuf->b_p_ts) { + // 'shiftwidth' or 'tabstop' + if (foldmethodIsIndent(curwin)) { + foldUpdateAll(curwin); + } + // When 'shiftwidth' changes, or it's zero and 'tabstop' changes: + // parse 'cinoptions'. + if (pp == &curbuf->b_p_sw || curbuf->b_p_sw == 0) { + parse_cino(curbuf); + } + } else if (pp == &p_mco) { + screenclear(); // will re-allocate the screen + } else if (pp == &curbuf->b_p_iminsert) { + showmode(); + // Show/unshow value of 'keymap' in status lines. + status_redraw_curbuf(); + } else if (pp == &p_titlelen) { + // if 'titlelen' has changed, redraw the title + if (starting != NO_SCREEN && old_value != p_titlelen) { + need_maketitle = true; + } + } else if (pp == &p_ch) { + // if p_ch changed value, change the command line height + // Only compute the new window layout when startup has been + // completed. Otherwise the frame sizes may be wrong. + if (p_ch != old_value && full_screen) { + command_height(); + } + } else if (pp == &p_uc) { + // when 'updatecount' changes from zero to non-zero, open swap files + if (p_uc && !old_value) { + ml_open_files(); + } + } else if (pp == &p_ul || pp == &curbuf->b_p_ul) { + // sync undo before 'undolevels' changes + // use the old value, otherwise u_sync() may not work properly + *pp = old_value; + u_sync(true); + *pp = value; + } else if (pp == &curbuf->b_p_tw) { FOR_ALL_TAB_WINDOWS(tp, wp) { check_colorcolumn(wp); } } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { - // 'scrollback' - if (*pp < -1 || *pp > SB_MAX - || (*pp != -1 && opt_flags == OPT_LOCAL && !curbuf->terminal)) { - errmsg = e_invarg; - *pp = old_value; - } else if (curbuf->terminal) { + if (curbuf->terminal) { // Force the scrollback to take effect. terminal_resize(curbuf->terminal, UINT16_MAX, UINT16_MAX); } + } else if (pp == &curwin->w_p_nuw) { + curwin->w_nrwidth_line_count = 0; } - /* - * Check the bounds for numeric options here - */ + + // Check the (new) bounds for Rows and Columns here. if (Rows < min_rows() && full_screen) { if (errbuf != NULL) { vim_snprintf((char *)errbuf, errbuflen, @@ -4308,19 +4362,17 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, limit_screen_size(); - /* - * If the screen (shell) height has been changed, assume it is the - * physical screenheight. - */ + // If the screen (shell) height has been changed, assume it is the + // physical screenheight. if (old_Rows != Rows || old_Columns != Columns) { - /* Changing the screen size is not allowed while updating the screen. */ + // Changing the screen size is not allowed while updating the screen. if (updating_screen) { *pp = old_value; } else if (full_screen) { screen_resize((int)Columns, (int)Rows); } else { - /* Postpone the resizing; check the size and cmdline position for - * messages. */ + // Postpone the resizing; check the size and cmdline position for + // messages. check_shellsize(); if (cmdline_row > Rows - p_ch && Rows > p_ch) { assert(p_ch >= 0 && Rows - p_ch <= INT_MAX); @@ -4332,14 +4384,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } } - if (curbuf->b_p_ts <= 0) { - errmsg = e_positive; - curbuf->b_p_ts = 8; - } - if (p_tm < 0) { - errmsg = e_positive; - p_tm = 0; - } if ((curwin->w_p_scr <= 0 || (curwin->w_p_scr > curwin->w_height && curwin->w_height > 0)) @@ -4356,21 +4400,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, else /* curwin->w_p_scr > curwin->w_height */ curwin->w_p_scr = curwin->w_height; } - if (p_hi < 0) { - errmsg = e_positive; - p_hi = 0; - } else if (p_hi > 10000) { - errmsg = e_invarg; - p_hi = 10000; - } - if (p_re < 0 || p_re > 2) { - errmsg = e_invarg; - p_re = 0; - } - if (p_report < 0) { - errmsg = e_positive; - p_report = 1; - } if ((p_sj < -100 || p_sj >= Rows) && full_screen) { if (Rows != old_Rows) /* Rows changed, just adjust p_sj */ p_sj = Rows / 2; @@ -4379,30 +4408,11 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, p_sj = 1; } } - if (p_so < 0 && full_screen) { - errmsg = e_scroll; - p_so = 0; - } - if (p_siso < 0 && full_screen) { - errmsg = e_positive; - p_siso = 0; - } - if (p_cwh < 1) { - errmsg = e_positive; - p_cwh = 1; - } - if (p_ut < 0) { - errmsg = e_positive; - p_ut = 2000; - } - if (p_ss < 0) { - errmsg = e_positive; - p_ss = 0; - } - /* May set global value for local option. */ - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) + // May set global value for local option. + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { *(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL) = *pp; + } if (pp == &curbuf->b_p_scbk && !curbuf->terminal) { // Normal buffer: reset local 'scrollback' after updating the global value. @@ -4426,6 +4436,10 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, (char_u *) options[opt_idx].fullname, NULL, false, NULL); reset_v_option_vars(); + if (options[opt_idx].flags & P_UI_OPTION) { + ui_call_option_set(cstr_as_string(options[opt_idx].fullname), + INTEGER_OBJ(value)); + } } comp_col(); /* in case 'columns' or 'ls' changed */ @@ -4437,6 +4451,27 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, return (char *)errmsg; } +static void trigger_optionsset_string(int opt_idx, int opt_flags, + char *oldval, char *newval) +{ + if (oldval != NULL && newval != NULL) { + char buf_type[7]; + + vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s", + (opt_flags & OPT_LOCAL) ? "local" : "global"); + set_vim_var_string(VV_OPTION_OLD, oldval, -1); + set_vim_var_string(VV_OPTION_NEW, newval, -1); + set_vim_var_string(VV_OPTION_TYPE, buf_type, -1); + apply_autocmds(EVENT_OPTIONSET, + (char_u *)options[opt_idx].fullname, NULL, false, NULL); + reset_v_option_vars(); + if (options[opt_idx].flags & P_UI_OPTION) { + ui_call_option_set(cstr_as_string(options[opt_idx].fullname), + STRING_OBJ(cstr_as_string(newval))); + } + } +} + /* * Called after an option changed: check if something needs to be redrawn. */ @@ -4906,15 +4941,14 @@ showoptions ( vimoption_T **items = xmalloc(sizeof(vimoption_T *) * PARAM_COUNT); - /* Highlight title */ - if (all == 2) - MSG_PUTS_TITLE(_("\n--- Terminal codes ---")); - else if (opt_flags & OPT_GLOBAL) + // Highlight title + if (opt_flags & OPT_GLOBAL) { MSG_PUTS_TITLE(_("\n--- Global option values ---")); - else if (opt_flags & OPT_LOCAL) + } else if (opt_flags & OPT_LOCAL) { MSG_PUTS_TITLE(_("\n--- Local option values ---")); - else + } else { MSG_PUTS_TITLE(_("\n--- Options ---")); + } /* * do the loop two times: @@ -4989,14 +5023,39 @@ static int optval_default(vimoption_T *p, char_u *varp) if (varp == NULL) return TRUE; /* hidden option is always at default */ dvi = ((p->flags & P_VI_DEF) || p_cp) ? VI_DEFAULT : VIM_DEFAULT; - if (p->flags & P_NUM) - return *(long *)varp == (long)p->def_val[dvi]; - if (p->flags & P_BOOL) + if (p->flags & P_NUM) { + return *(long *)varp == (long)(intptr_t)p->def_val[dvi]; + } + if (p->flags & P_BOOL) { return *(int *)varp == (int)(intptr_t)p->def_val[dvi]; - /* P_STRING */ + } + // P_STRING return STRCMP(*(char_u **)varp, p->def_val[dvi]) == 0; } +/// Send update to UIs with values of UI relevant options +void ui_refresh_options(void) +{ + for (int opt_idx = 0; options[opt_idx].fullname; opt_idx++) { + uint32_t flags = options[opt_idx].flags; + if (!(flags & P_UI_OPTION)) { + continue; + } + String name = cstr_as_string(options[opt_idx].fullname); + void *varp = options[opt_idx].var; + Object value = OBJECT_INIT; + if (flags & P_BOOL) { + value = BOOLEAN_OBJ(*(int *)varp); + } else if (flags & P_NUM) { + value = INTEGER_OBJ(*(long *)varp); + } else if (flags & P_STRING) { + // cstr_as_string handles NULL string + value = STRING_OBJ(cstr_as_string(*(char **)varp)); + } + ui_call_option_set(name, value); + } +} + /* * showoneopt: show the value of one option * must not be called with a hidden option! @@ -5356,6 +5415,9 @@ void unset_global_local_option(char *name, void *from) case PV_LW: clear_string_option(&buf->b_p_lw); break; + case PV_MENC: + clear_string_option(&buf->b_p_menc); + break; } } @@ -5389,6 +5451,7 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags) case PV_UL: return (char_u *)&(curbuf->b_p_ul); case PV_LW: return (char_u *)&(curbuf->b_p_lw); case PV_BKC: return (char_u *)&(curbuf->b_p_bkc); + case PV_MENC: return (char_u *)&(curbuf->b_p_menc); } return NULL; /* "cannot happen" */ } @@ -5444,6 +5507,8 @@ static char_u *get_varp(vimoption_T *p) ? (char_u *)&(curbuf->b_p_ul) : p->var; case PV_LW: return *curbuf->b_p_lw != NUL ? (char_u *)&(curbuf->b_p_lw) : p->var; + case PV_MENC: return *curbuf->b_p_menc != NUL + ? (char_u *)&(curbuf->b_p_menc) : p->var; case PV_ARAB: return (char_u *)&(curwin->w_p_arab); case PV_LIST: return (char_u *)&(curwin->w_p_list); @@ -5540,7 +5605,7 @@ static char_u *get_varp(vimoption_T *p) case PV_KMAP: return (char_u *)&(curbuf->b_p_keymap); case PV_SCL: return (char_u *)&(curwin->w_p_scl); case PV_WINHL: return (char_u *)&(curwin->w_p_winhl); - default: EMSG(_("E356: get_varp ERROR")); + default: IEMSG(_("E356: get_varp ERROR")); } /* always return a valid pointer to avoid a crash! */ return (char_u *)&(curbuf->b_p_wm); @@ -5841,6 +5906,7 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_qe = vim_strsave(p_qe); buf->b_p_udf = p_udf; buf->b_p_lw = empty_option; + buf->b_p_menc = empty_option; /* * Don't copy the options set by ex_help(), use the saved values, diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index b16f222705..66a49fd6e0 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -394,11 +394,13 @@ EXTERN char_u *p_dir; /* 'directory' */ EXTERN char_u *p_dy; /* 'display' */ EXTERN unsigned dy_flags; #ifdef IN_OPTION_C -static char *(p_dy_values[]) = { "lastline", "truncate", "uhex", NULL }; +static char *(p_dy_values[]) = { "lastline", "truncate", "uhex", "msgsep", + NULL }; #endif #define DY_LASTLINE 0x001 #define DY_TRUNCATE 0x002 #define DY_UHEX 0x004 +#define DY_MSGSEP 0x008 EXTERN int p_ed; // 'edcompatible' EXTERN int p_emoji; // 'emoji' EXTERN char_u *p_ead; // 'eadirection' @@ -447,13 +449,13 @@ EXTERN char_u *p_popt; // 'printoptions' 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' EXTERN char_u *p_hlg; // 'helplang' EXTERN int p_hid; // 'hidden' -// Use P_HID to check if a buffer is to be hidden when it is no longer -// visible in a window. -# define P_HID(buf) (buf_hide(buf)) EXTERN char_u *p_hl; // 'highlight' EXTERN int p_hls; // 'hlsearch' EXTERN long p_hi; // 'history' @@ -478,6 +480,7 @@ EXTERN char_u *p_langmap; // 'langmap' EXTERN int p_lnr; // 'langnoremap' EXTERN int p_lrm; // 'langremap' EXTERN char_u *p_lm; // 'langmenu' +EXTERN long *p_linespace; // 'linespace' EXTERN char_u *p_lispwords; // 'lispwords' EXTERN long p_ls; // 'laststatus' EXTERN long p_stal; // 'showtabline' @@ -486,6 +489,7 @@ EXTERN char_u *p_lcs; // 'listchars' EXTERN int p_lz; // 'lazyredraw' EXTERN int p_lpl; // 'loadplugins' EXTERN int p_magic; // 'magic' +EXTERN char_u *p_menc; // 'makeencoding' EXTERN char_u *p_mef; // 'makeef' EXTERN char_u *p_mp; // 'makeprg' EXTERN char_u *p_cc; // 'colorcolumn' @@ -733,6 +737,7 @@ enum { , BV_KP , BV_LISP , BV_LW + , BV_MENC , BV_MA , BV_ML , BV_MOD diff --git a/src/nvim/options.lua b/src/nvim/options.lua index dd28a765fd..80484d0ad2 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -68,7 +68,8 @@ return { type='bool', scope={'global'}, vi_def=true, vim=true, - redraw={'everything'}, + redraw={'everything', 'ui_option'}, + varname='p_arshape', defaults={if_true={vi=true}} }, @@ -91,7 +92,7 @@ return { full_name='ambiwidth', abbreviation='ambw', type='string', scope={'global'}, vi_def=true, - redraw={'everything'}, + redraw={'everything', 'ui_option'}, varname='p_ambw', defaults={if_true={vi="single"}} }, @@ -532,7 +533,7 @@ return { vi_def=true, vim=true, varname='p_csverbose', - defaults={if_true={vi=0}} + defaults={if_true={vi=1}} }, { full_name='cursorbind', abbreviation='crb', @@ -641,7 +642,7 @@ return { vim=true, redraw={'all_windows'}, varname='p_dy', - defaults={if_true={vi="", vim="lastline"}} + defaults={if_true={vi="", vim="lastline,msgsep"}} }, { full_name='eadirection', abbreviation='ead', @@ -661,7 +662,7 @@ return { full_name='emoji', abbreviation='emo', type='bool', scope={'global'}, vi_def=true, - redraw={'everything'}, + redraw={'everything', 'ui_option'}, varname='p_emoji', defaults={if_true={vi=true}} }, @@ -811,7 +812,7 @@ return { vi_def=true, redraw={'all_windows'}, varname='p_fcs', - defaults={if_true={vi="vert:|,fold:-"}} + defaults={if_true={vi=''}} }, { full_name='fixendofline', abbreviation='fixeol', @@ -1021,23 +1022,26 @@ return { type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, - redraw={'everything'}, - enable_if=false, + varname='p_guifont', + redraw={'everything', 'ui_option'}, + defaults={if_true={vi=""}} }, { full_name='guifontset', abbreviation='gfs', type='string', list='onecomma', scope={'global'}, vi_def=true, - redraw={'everything'}, - enable_if=false, + varname='p_guifontset', + redraw={'everything', 'ui_option'}, + defaults={if_true={vi=""}} }, { full_name='guifontwide', abbreviation='gfw', type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, - redraw={'everything'}, - enable_if=false, + redraw={'everything', 'ui_option'}, + varname='p_guifontwide', + defaults={if_true={vi=""}} }, { full_name='guioptions', abbreviation='go', @@ -1395,8 +1399,9 @@ return { full_name='linespace', abbreviation='lsp', type='number', scope={'global'}, vi_def=true, - redraw={'everything'}, - enable_if=false, + redraw={'everything', 'ui_option'}, + varname='p_linespace', + defaults={if_true={vi=0}} }, { full_name='lisp', @@ -1453,6 +1458,13 @@ return { defaults={if_true={vi=""}} }, { + full_name='makeencoding', abbreviation='menc', + type='string', scope={'global', 'buffer'}, + vi_def=true, + varname='p_menc', + defaults={if_true={vi=""}} + }, + { full_name='makeprg', abbreviation='mp', type='string', scope={'global', 'buffer'}, secure=true, @@ -2036,7 +2048,7 @@ return { varname='p_shcf', defaults={ condition='WIN32', - if_true={vi="/c"}, + if_true={vi="/s /c"}, if_false={vi="-c"} } }, @@ -2092,7 +2104,11 @@ return { secure=true, vi_def=true, varname='p_sxq', - defaults={if_true={vi=""}} + defaults={ + condition='WIN32', + if_true={vi="\""}, + if_false={vi=""}, + } }, { full_name='shellxescape', abbreviation='sxe', @@ -2164,7 +2180,7 @@ return { full_name='showtabline', abbreviation='stal', type='number', scope={'global'}, vi_def=true, - redraw={'all_windows'}, + redraw={'all_windows', 'ui_option'}, varname='p_stal', defaults={if_true={vi=1}} }, @@ -2173,7 +2189,7 @@ return { type='number', scope={'global'}, vi_def=true, varname='p_ss', - defaults={if_true={vi=0}} + defaults={if_true={vi=1}} }, { full_name='sidescrolloff', abbreviation='siso', @@ -2435,7 +2451,7 @@ return { full_name='termguicolors', abbreviation='tgc', type='bool', scope={'global'}, vi_def=false, - redraw={'everything'}, + redraw={'ui_option'}, varname='p_tgc', defaults={if_true={vi=false}} }, @@ -2505,11 +2521,10 @@ return { full_name='titleold', type='string', scope={'global'}, secure=true, - gettext=true, no_mkrc=true, vi_def=true, varname='p_titleold', - defaults={if_true={vi=N_("")}} + defaults={if_true={vi=""}} }, { full_name='titlestring', diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index de0cd10d9c..3fcb9415c7 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -886,8 +886,8 @@ bool os_setenv_append_path(const char *fname) // No prescribed maximum on unix. # define MAX_ENVPATHLEN INT_MAX #endif - if (!path_is_absolute_path((char_u *)fname)) { - EMSG2(_(e_intern2), "os_setenv_append_path()"); + if (!path_is_absolute((char_u *)fname)) { + internal_error("os_setenv_append_path()"); return false; } const char *tail = (char *)path_tail_with_sep((char_u *)fname); diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c index b040904d0b..0ef9307a5a 100644 --- a/src/nvim/os/fileio.c +++ b/src/nvim/os/fileio.c @@ -4,7 +4,7 @@ /// @file fileio.c /// /// Buffered reading/writing to a file. Unlike fileio.c this is not dealing with -/// Neovim stuctures for buffer, with autocommands, etc: just fopen/fread/fwrite +/// Nvim stuctures for buffer, with autocommands, etc: just fopen/fread/fwrite /// replacement. #include <assert.h> @@ -39,11 +39,11 @@ /// @param[in] fname File name to open. /// @param[in] flags Flags, @see FileOpenFlags. Currently reading from and /// writing to the file at once is not supported, so either -/// FILE_WRITE_ONLY or FILE_READ_ONLY is required. +/// kFileWriteOnly or kFileReadOnly is required. /// @param[in] mode Permissions for the newly created file (ignored if flags -/// does not have FILE_CREATE\*). +/// does not have kFileCreate\*). /// -/// @return Error code (@see os_strerror()) or 0. +/// @return Error code, or 0 on success. @see os_strerror() int file_open(FileDescriptor *const ret_fp, const char *const fname, const int flags, const int mode) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT @@ -124,12 +124,11 @@ int file_open_fd(FileDescriptor *const ret_fp, const int fd, /// Like file_open(), but allocate and return ret_fp /// -/// @param[out] error Error code, @see os_strerror(). Is set to zero on -/// success. +/// @param[out] error Error code, or 0 on success. @see os_strerror() /// @param[in] fname File name to open. /// @param[in] flags Flags, @see FileOpenFlags. /// @param[in] mode Permissions for the newly created file (ignored if flags -/// does not have FILE_CREATE\*). +/// does not have kFileCreate\*). /// /// @return [allocated] Opened file or NULL in case of error. FileDescriptor *file_open_new(int *const error, const char *const fname, @@ -146,8 +145,7 @@ FileDescriptor *file_open_new(int *const error, const char *const fname, /// Like file_open_fd(), but allocate and return ret_fp /// -/// @param[out] error Error code, @see os_strerror(). Is set to zero on -/// success. +/// @param[out] error Error code, or 0 on success. @see os_strerror() /// @param[in] fd File descriptor to wrap. /// @param[in] flags Flags, @see FileOpenFlags. /// @param[in] mode Permissions for the newly created file (ignored if flags diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 87bab93420..0414794d01 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -222,7 +222,7 @@ int os_exepath(char *buffer, size_t *size) bool os_can_exe(const char_u *name, char_u **abspath, bool use_path) FUNC_ATTR_NONNULL_ARG(1) { - bool no_path = !use_path || path_is_absolute_path(name); + bool no_path = !use_path || path_is_absolute(name); #ifndef WIN32 // If the filename is "qualified" (relative or absolute) do not check $PATH. no_path |= (name[0] == '.' @@ -244,7 +244,7 @@ bool os_can_exe(const char_u *name, char_u **abspath, bool use_path) #endif if (ok) { if (abspath != NULL) { - *abspath = save_absolute_path(name); + *abspath = save_abs_path(name); } return true; } @@ -357,7 +357,7 @@ static bool is_executable_in_path(const char_u *name, char_u **abspath) #endif if (ok) { if (abspath != NULL) { // Caller asked for a copy of the path. - *abspath = save_absolute_path((char_u *)buf); + *abspath = save_abs_path((char_u *)buf); } rv = true; @@ -485,7 +485,7 @@ ptrdiff_t os_read(const int fd, bool *const ret_eof, char *const ret_buf, while (read_bytes != size) { assert(size >= read_bytes); const ptrdiff_t cur_read_bytes = read(fd, ret_buf + read_bytes, - size - read_bytes); + IO_COUNT(size - read_bytes)); if (cur_read_bytes > 0) { read_bytes += (size_t)cur_read_bytes; } @@ -598,7 +598,7 @@ ptrdiff_t os_write(const int fd, const char *const buf, const size_t size, while (written_bytes != size) { assert(size >= written_bytes); const ptrdiff_t cur_written_bytes = write(fd, buf + written_bytes, - size - written_bytes); + IO_COUNT(size - written_bytes)); if (cur_written_bytes > 0) { written_bytes += (size_t)cur_written_bytes; } @@ -1026,7 +1026,7 @@ bool os_fileid_equal_fileinfo(const FileID *file_id, /// to and return that name in allocated memory. /// Otherwise NULL is returned. char *os_resolve_shortcut(const char *fname) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC { HRESULT hr; IPersistFile *ppf = NULL; diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index 7d6f2abd7f..405500767d 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -188,7 +188,7 @@ size_t input_enqueue(String keys) uint8_t buf[6] = { 0 }; unsigned int new_size = trans_special((const uint8_t **)&ptr, (size_t)(end - ptr), buf, true, - true); + false); if (new_size) { new_size = handle_mouse_event(&ptr, buf, new_size); diff --git a/src/nvim/os/lang.c b/src/nvim/os/lang.c new file mode 100644 index 0000000000..47c278ee97 --- /dev/null +++ b/src/nvim/os/lang.c @@ -0,0 +1,43 @@ +// 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 + +#ifdef __APPLE__ +# define Boolean CFBoolean // Avoid conflict with API's Boolean +# include <CoreFoundation/CFLocale.h> +# include <CoreFoundation/CFString.h> +# undef Boolean +#endif + +#ifdef HAVE_LOCALE_H +# include <locale.h> +#endif +#include "nvim/os/os.h" + +void lang_init(void) +{ +#ifdef __APPLE__ + if (os_getenv("LANG") == NULL) { + CFLocaleRef cf_locale = CFLocaleCopyCurrent(); + CFTypeRef cf_lang_region = CFLocaleGetValue(cf_locale, + kCFLocaleIdentifier); + CFRetain(cf_lang_region); + CFRelease(cf_locale); + + const char *lang_region = CFStringGetCStringPtr(cf_lang_region, + kCFStringEncodingUTF8); + if (lang_region) { + os_setenv("LANG", lang_region, true); + } else { + char buf[20] = { 0 }; + if (CFStringGetCString(cf_lang_region, buf, 20, + kCFStringEncodingUTF8)) { + os_setenv("LANG", lang_region, true); + } + } + CFRelease(cf_lang_region); +# ifdef HAVE_LOCALE_H + setlocale(LC_ALL, ""); +# endif + } +#endif +} diff --git a/src/nvim/os/lang.h b/src/nvim/os/lang.h new file mode 100644 index 0000000000..f60e064f57 --- /dev/null +++ b/src/nvim/os/lang.h @@ -0,0 +1,7 @@ +#ifndef NVIM_OS_LANG_H +#define NVIM_OS_LANG_H + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/lang.h.generated.h" +#endif +#endif // NVIM_OS_LANG_H diff --git a/src/nvim/os/os_defs.h b/src/nvim/os/os_defs.h index 87f8d214bd..f81785675e 100644 --- a/src/nvim/os/os_defs.h +++ b/src/nvim/os/os_defs.h @@ -4,7 +4,6 @@ #include <ctype.h> #include <stdio.h> #include <stdlib.h> -#include <sys/param.h> #include <sys/stat.h> #include <sys/types.h> @@ -14,13 +13,6 @@ # include "nvim/os/unix_defs.h" #endif -/// File descriptor number used for standard IO streams -enum { - OS_STDIN_FILENO = STDIN_FILENO, - OS_STDOUT_FILENO = STDOUT_FILENO, - OS_STDERR_FILENO = STDERR_FILENO, -}; - #define BASENAMELEN (NAME_MAX - 5) // Use the system path length if it makes sense. diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c new file mode 100644 index 0000000000..a67e7487eb --- /dev/null +++ b/src/nvim/os/process.c @@ -0,0 +1,267 @@ +// 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 + +/// OS process functions +/// +/// psutil is a good reference for cross-platform syscall voodoo: +/// https://github.com/giampaolo/psutil/tree/master/psutil/arch + +#include <uv.h> // for HANDLE (win32) + +#ifdef WIN32 +# include <tlhelp32.h> // for CreateToolhelp32Snapshot +#endif + +#if defined(__FreeBSD__) // XXX: OpenBSD ? +# include <string.h> +# include <sys/types.h> +# include <sys/user.h> +#endif + +#if defined(__NetBSD__) || defined(__OpenBSD__) +# include <sys/param.h> +#endif + +#if defined(__APPLE__) || defined(BSD) +# include <sys/sysctl.h> +# include <pwd.h> +#endif + +#include "nvim/globals.h" +#include "nvim/log.h" +#include "nvim/os/process.h" +#include "nvim/os/os.h" +#include "nvim/os/os_defs.h" +#include "nvim/api/private/helpers.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/process.c.generated.h" +#endif + +#ifdef WIN32 +static bool os_proc_tree_kill_rec(HANDLE process, int sig) +{ + if (process == NULL) { + return false; + } + PROCESSENTRY32 pe; + DWORD pid = GetProcessId(process); + + if (pid != 0) { + HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (h != INVALID_HANDLE_VALUE) { + pe.dwSize = sizeof(PROCESSENTRY32); + if (!Process32First(h, &pe)) { + goto theend; + } + do { + if (pe.th32ParentProcessID == pid) { + HANDLE ph = OpenProcess(PROCESS_ALL_ACCESS, false, pe.th32ProcessID); + if (ph != NULL) { + os_proc_tree_kill_rec(ph, sig); + CloseHandle(ph); + } + } + } while (Process32Next(h, &pe)); + CloseHandle(h); + } + } + +theend: + return (bool)TerminateProcess(process, (unsigned int)sig); +} +/// Kills process `pid` and its descendants recursively. +bool os_proc_tree_kill(int pid, int sig) +{ + assert(sig >= 0); + assert(sig == SIGTERM || sig == SIGKILL); + if (pid > 0) { + ILOG("terminating process tree: %d", pid); + HANDLE h = OpenProcess(PROCESS_ALL_ACCESS, false, (DWORD)pid); + return os_proc_tree_kill_rec(h, sig); + } else { + ELOG("invalid pid: %d", pid); + } + return false; +} +#else +/// Kills process group where `pid` is the process group leader. +bool os_proc_tree_kill(int pid, int sig) +{ + assert(sig == SIGTERM || sig == SIGKILL); + int pgid = getpgid(pid); + if (pgid > 0) { // Ignore error. Never kill self (pid=0). + if (pgid == pid) { + ILOG("sending %s to process group: -%d", + sig == SIGTERM ? "SIGTERM" : "SIGKILL", pgid); + int rv = uv_kill(-pgid, sig); + return rv == 0; + } else { + // Should never happen, because process_spawn() did setsid() in the child. + ELOG("pgid %d != pid %d", pgid, pid); + } + } else { + ELOG("getpgid(%d) returned %d", pid, pgid); + } + return false; +} +#endif + +/// Gets the process ids of the immediate children of process `ppid`. +/// +/// @param ppid Process to inspect. +/// @param[out,allocated] proc_list Child process ids. +/// @param[out] proc_count Number of child processes. +/// @return 0 on success, 1 if process not found, 2 on other error. +int os_proc_children(int ppid, int **proc_list, size_t *proc_count) +{ + if (ppid < 0) { + return 2; + } + + int *temp = NULL; + *proc_list = NULL; + *proc_count = 0; + +#ifdef WIN32 + PROCESSENTRY32 pe; + + // Snapshot of all processes. + HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (h == INVALID_HANDLE_VALUE) { + return 2; + } + + pe.dwSize = sizeof(PROCESSENTRY32); + // Get root process. + if (!Process32First(h, &pe)) { + CloseHandle(h); + return 2; + } + // Collect processes whose parent matches `ppid`. + do { + if (pe.th32ParentProcessID == (DWORD)ppid) { + temp = xrealloc(temp, (*proc_count + 1) * sizeof(*temp)); + temp[*proc_count] = (int)pe.th32ProcessID; + (*proc_count)++; + } + } while (Process32Next(h, &pe)); + CloseHandle(h); + +#elif defined(__APPLE__) || defined(BSD) +# if defined(__APPLE__) +# define KP_PID(o) o.kp_proc.p_pid +# define KP_PPID(o) o.kp_eproc.e_ppid +# elif defined(__FreeBSD__) +# define KP_PID(o) o.ki_pid +# define KP_PPID(o) o.ki_ppid +# else +# define KP_PID(o) o.p_pid +# define KP_PPID(o) o.p_ppid +# endif +# ifdef __NetBSD__ + static int name[] = { + CTL_KERN, KERN_PROC2, KERN_PROC_ALL, 0, (int)(sizeof(struct kinfo_proc2)), 0 + }; +# else + static int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 }; +# endif + + // Get total process count. + size_t len = 0; + int rv = sysctl(name, ARRAY_SIZE(name) - 1, NULL, &len, NULL, 0); + if (rv) { + return 2; + } + + // Get ALL processes. +# ifdef __NetBSD__ + struct kinfo_proc2 *p_list = xmalloc(len); +# else + struct kinfo_proc *p_list = xmalloc(len); +# endif + rv = sysctl(name, ARRAY_SIZE(name) - 1, p_list, &len, NULL, 0); + if (rv) { + xfree(p_list); + return 2; + } + + // Collect processes whose parent matches `ppid`. + bool exists = false; + size_t p_count = len / sizeof(*p_list); + for (size_t i = 0; i < p_count; i++) { + exists = exists || KP_PID(p_list[i]) == ppid; + if (KP_PPID(p_list[i]) == ppid) { + temp = xrealloc(temp, (*proc_count + 1) * sizeof(*temp)); + temp[*proc_count] = KP_PID(p_list[i]); + (*proc_count)++; + } + } + xfree(p_list); + if (!exists) { + return 1; // Process not found. + } + +#elif defined(__linux__) + char proc_p[256] = { 0 }; + // Collect processes whose parent matches `ppid`. + // Rationale: children are defined in thread with same ID of process. + snprintf(proc_p, sizeof(proc_p), "/proc/%d/task/%d/children", ppid, ppid); + FILE *fp = fopen(proc_p, "r"); + if (fp == NULL) { + return 2; // Process not found, or /proc/…/children not supported. + } + int match_pid; + while (fscanf(fp, "%d", &match_pid) > 0) { + temp = xrealloc(temp, (*proc_count + 1) * sizeof(*temp)); + temp[*proc_count] = match_pid; + (*proc_count)++; + } + fclose(fp); +#endif + + *proc_list = temp; + return 0; +} + +#ifdef WIN32 +/// Gets various properties of the process identified by `pid`. +/// +/// @param pid Process to inspect. +/// @return Map of process properties, empty on error. +Dictionary os_proc_info(int pid) +{ + Dictionary pinfo = ARRAY_DICT_INIT; + PROCESSENTRY32 pe; + + // Snapshot of all processes. This is used instead of: + // OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, …) + // to avoid ERROR_PARTIAL_COPY. https://stackoverflow.com/a/29942376 + HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (h == INVALID_HANDLE_VALUE) { + return pinfo; // Return empty. + } + + pe.dwSize = sizeof(PROCESSENTRY32); + // Get root process. + if (!Process32First(h, &pe)) { + CloseHandle(h); + return pinfo; // Return empty. + } + // Find the process. + do { + if (pe.th32ProcessID == (DWORD)pid) { + break; + } + } while (Process32Next(h, &pe)); + CloseHandle(h); + + if (pe.th32ProcessID == (DWORD)pid) { + PUT(pinfo, "pid", INTEGER_OBJ(pid)); + PUT(pinfo, "ppid", INTEGER_OBJ((int)pe.th32ParentProcessID)); + PUT(pinfo, "name", STRING_OBJ(cstr_to_string(pe.szExeFile))); + } + + return pinfo; +} +#endif diff --git a/src/nvim/os/process.h b/src/nvim/os/process.h new file mode 100644 index 0000000000..1722d56bd3 --- /dev/null +++ b/src/nvim/os/process.h @@ -0,0 +1,11 @@ +#ifndef NVIM_OS_PROCESS_H +#define NVIM_OS_PROCESS_H + +#include <stddef.h> +#include "nvim/api/private/defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/process.h.generated.h" +#endif + +#endif // NVIM_OS_PROCESS_H diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index 53301e4b53..dfe2cfbb8d 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -72,8 +72,7 @@ int pty_process_spawn(PtyProcess *ptyproc) ELOG("forkpty failed: %s", strerror(errno)); return status; } else if (pid == 0) { - init_child(ptyproc); - abort(); + init_child(ptyproc); // never returns } // make sure the master file descriptor is non blocking @@ -146,8 +145,12 @@ void pty_process_teardown(Loop *loop) uv_signal_stop(&loop->children_watcher); } -static void init_child(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL +static void init_child(PtyProcess *ptyproc) + FUNC_ATTR_NONNULL_ALL { + // New session/process-group. #6530 + setsid(); + unsetenv("COLUMNS"); unsetenv("LINES"); unsetenv("TERMCAP"); @@ -163,14 +166,15 @@ static void init_child(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL Process *proc = (Process *)ptyproc; if (proc->cwd && os_chdir(proc->cwd) != 0) { - fprintf(stderr, "chdir failed: %s\n", strerror(errno)); + ELOG("chdir failed: %s", strerror(errno)); return; } char *prog = ptyproc->process.argv[0]; setenv("TERM", ptyproc->term_name ? ptyproc->term_name : "ansi", 1); execvp(prog, ptyproc->process.argv); - fprintf(stderr, "execvp failed: %s: %s\n", strerror(errno), prog); + ELOG("execvp failed: %s: %s", strerror(errno), prog); + _exit(122); // 122 is EXEC_FAILED in the Vim source. } static void init_termios(struct termios *termios) FUNC_ATTR_NONNULL_ALL diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index 3c4839a076..a6774a6275 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.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 + #include <assert.h> #include <stdbool.h> #include <stdlib.h> @@ -131,7 +134,7 @@ int pty_process_spawn(PtyProcess *ptyproc) } goto cleanup; } - proc->pid = GetProcessId(process_handle); + proc->pid = (int)GetProcessId(process_handle); if (!RegisterWaitForSingleObject( &ptyproc->finish_wait, @@ -339,20 +342,20 @@ static void quote_cmd_arg(char *dest, size_t dest_remaining, const char *src) } // Expected input/output: - // input : hello"world - // output: "hello\"world" - // input : hello""world - // output: "hello\"\"world" - // input : hello\world - // output: hello\world - // input : hello\\world - // output: hello\\world - // input : hello\"world - // output: "hello\\\"world" - // input : hello\\"world - // output: "hello\\\\\"world" - // input : hello world\ - // output: "hello world\\" + // input : 'hello"world' + // output: '"hello\"world"' + // input : 'hello""world' + // output: '"hello\"\"world"' + // input : 'hello\world' + // output: 'hello\world' + // input : 'hello\\world' + // output: 'hello\\world' + // input : 'hello\"world' + // output: '"hello\\\"world"' + // input : 'hello\\"world' + // output: '"hello\\\\\"world"' + // input : 'hello world\' + // output: '"hello world\\"' assert(dest_remaining--); *(dest++) = NUL; diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index e32c6e05d2..04f59d7522 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -123,6 +123,9 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args) if (opts & kShellOptRead) { output_ptr = &output; forward_output = false; + } else if (opts & kShellOptDoOut) { + // Caller has already redirected output + forward_output = false; } } @@ -133,7 +136,7 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args) xfree(input.data); if (output) { - (void)write_output(output, nread, true, true); + (void)write_output(output, nread, true); xfree(output); } @@ -189,6 +192,7 @@ static int do_os_system(char **argv, { out_data_decide_throttle(0); // Initialize throttle decider. out_data_ring(NULL, 0); // Initialize output ring-buffer. + bool has_input = (input != NULL && input[0] != '\0'); // the output buffer DynamicBuffer buf = DYNAMIC_BUFFER_INIT; @@ -212,7 +216,7 @@ static int do_os_system(char **argv, MultiQueue *events = multiqueue_new_child(main_loop.events); proc->events = events; proc->argv = argv; - int status = process_spawn(proc, input != NULL, true, true); + int status = process_spawn(proc, has_input, true, true); if (status) { loop_poll_events(&main_loop, 0); // Failed, probably 'shell' is not executable. @@ -231,7 +235,7 @@ static int do_os_system(char **argv, // deal with stream events as fast a possible. It prevents closing the // streams while there's still data in the OS buffer (due to the process // exiting before all data is read). - if (input != NULL) { + if (has_input) { wstream_init(&proc->in, 0); } rstream_init(&proc->out, 0); @@ -240,8 +244,8 @@ static int do_os_system(char **argv, rstream_start(&proc->err, data_cb, &buf); // write the input, if any - if (input) { - WBuffer *input_buffer = wstream_new_buffer((char *) input, len, 1, NULL); + if (has_input) { + WBuffer *input_buffer = wstream_new_buffer((char *)input, len, 1, NULL); if (!wstream_write(&proc->in, input_buffer)) { // couldn't write, stop the process and tell the user about it @@ -256,11 +260,25 @@ static int do_os_system(char **argv, // busy state. ui_busy_start(); ui_flush(); + if (forward_output) { + msg_sb_eol(); + msg_start(); + msg_no_more = true; + lines_left = -1; + } int exitcode = process_wait(proc, -1, NULL); if (!got_int && out_data_decide_throttle(0)) { // Last chunk of output was skipped; display it now. out_data_ring(NULL, SIZE_MAX); } + if (forward_output) { + // caller should decide if wait_return is invoked + no_wait_return++; + msg_end(); + no_wait_return--; + msg_no_more = false; + } + ui_busy_stop(); // prepare the out parameters if requested @@ -370,10 +388,10 @@ static bool out_data_decide_throttle(size_t size) pulse_msg[1] = (tick == 0 || 1 == tick) ? ' ' : '.'; pulse_msg[2] = (tick == 0 || 1 == tick || 2 == tick) ? ' ' : '.'; if (visit == 1) { - screen_del_lines(0, 0, 1, (int)Rows, NULL); + msg_putchar('\n'); } - int lastrow = (int)Rows - 1; - screen_puts_len((char_u *)pulse_msg, ARRAY_SIZE(pulse_msg), lastrow, 0, 0); + msg_putchar('\r'); // put cursor at start of line + msg_puts(pulse_msg); ui_flush(); return true; } @@ -404,7 +422,7 @@ static void out_data_ring(char *output, size_t size) } if (output == NULL && size == SIZE_MAX) { // Print mode - out_data_append_to_screen(last_skipped, last_skipped_len, true); + out_data_append_to_screen(last_skipped, &last_skipped_len, true); return; } @@ -432,60 +450,40 @@ static void out_data_ring(char *output, size_t size) /// @param output Data to append to screen lines. /// @param remaining Size of data. /// @param new_line If true, next data output will be on a new line. -static void out_data_append_to_screen(char *output, size_t remaining, - bool new_line) +static void out_data_append_to_screen(char *output, size_t *count, + bool eof) { - static colnr_T last_col = 0; // Column of last row to append to. - - size_t off = 0; - int last_row = (int)Rows - 1; - - while (output != NULL && off < remaining) { - // Found end of line? - if (output[off] == NL) { - // Can we start a new line or do we need to continue the last one? - if (last_col == 0) { - screen_del_lines(0, 0, 1, (int)Rows, NULL); + char *p = output, *end = output + *count; + while (p < end) { + if (*p == '\n' || *p == '\r' || *p == TAB || *p == BELL) { + msg_putchar_attr((uint8_t)(*p), 0); + p++; + } else { + // Note: this is not 100% precise: + // 1. we don't check if received continuation bytes are already invalid + // and we thus do some buffering that could be avoided + // 2. we don't compose chars over buffer boundaries, even if we see an + // incomplete UTF-8 sequence that could be composing with the last + // complete sequence. + // This will be corrected when we switch to vterm based implementation + int i = *p ? mb_ptr2len_len((char_u *)p, (int)(end-p)) : 1; + if (!eof && i == 1 && utf8len_tab_zero[*(uint8_t *)p] > (end-p)) { + *count = (size_t)(p - output); + goto end; } - screen_puts_len((char_u *)output, (int)off, last_row, last_col, 0); - last_col = 0; - size_t skip = off + 1; - output += skip; - remaining -= skip; - off = 0; - continue; - } - - // TODO(bfredl): using msg_puts would be better until - // terminal emulation is implemented. - if (output[off] < 0x20) { - output[off] = ' '; - } - - off++; - } - - if (output != NULL && remaining) { - if (last_col == 0) { - screen_del_lines(0, 0, 1, (int)Rows, NULL); + (void)msg_outtrans_len_attr((char_u *)p, i, 0); + p += i; } - screen_puts_len((char_u *)output, (int)remaining, last_row, last_col, 0); - last_col += (colnr_T)remaining; - } - - if (new_line) { - last_col = 0; } +end: ui_flush(); } static void out_data_cb(Stream *stream, RBuffer *buf, size_t count, void *data, bool eof) { - // We always output the whole buffer, so the buffer can never - // wrap around. size_t cnt; char *ptr = rbuffer_read_ptr(buf, &cnt); @@ -494,12 +492,16 @@ static void out_data_cb(Stream *stream, RBuffer *buf, size_t count, void *data, // Save the skipped output. If it is the final chunk, we display it later. out_data_ring(ptr, cnt); } else { - out_data_append_to_screen(ptr, cnt, eof); + out_data_append_to_screen(ptr, &cnt, eof); } if (cnt) { rbuffer_consumed(buf, cnt); } + + // Move remaining data to start of buffer, so the buffer can never + // wrap around. + rbuffer_reset(buf); } /// Parses a command string into a sequence of words, taking quotes into @@ -588,14 +590,10 @@ static void read_input(DynamicBuffer *buf) if (len == l) { // Finished a line, add a NL, unless this line should not have one. - // FIXME need to make this more readable if (lnum != curbuf->b_op_end.lnum - || (!curbuf->b_p_bin - && curbuf->b_p_fixeol) + || (!curbuf->b_p_bin && curbuf->b_p_fixeol) || (lnum != curbuf->b_no_eol_lnum - && (lnum != - curbuf->b_ml.ml_line_count - || curbuf->b_p_eol))) { + && (lnum != curbuf->b_ml.ml_line_count || curbuf->b_p_eol))) { dynamic_buffer_ensure(buf, buf->len + 1); buf->data[buf->len++] = NL; } @@ -611,28 +609,20 @@ static void read_input(DynamicBuffer *buf) } } -static size_t write_output(char *output, size_t remaining, bool to_buffer, - bool eof) +static size_t write_output(char *output, size_t remaining, bool eof) { if (!output) { return 0; } - char replacement_NUL = to_buffer ? NL : 1; char *start = output; size_t off = 0; - int lastrow = (int)Rows - 1; while (off < remaining) { if (output[off] == NL) { // Insert the line - if (to_buffer) { - output[off] = NUL; - ml_append(curwin->w_cursor.lnum++, (char_u *)output, (int)off + 1, - false); - } else { - screen_del_lines(0, 0, 1, (int)Rows, NULL); - screen_puts_len((char_u *)output, (int)off, lastrow, 0, 0); - } + output[off] = NUL; + ml_append(curwin->w_cursor.lnum++, (char_u *)output, (int)off + 1, + false); size_t skip = off + 1; output += skip; remaining -= skip; @@ -642,24 +632,19 @@ static size_t write_output(char *output, size_t remaining, bool to_buffer, if (output[off] == NUL) { // Translate NUL to NL - output[off] = replacement_NUL; + output[off] = NL; } off++; } if (eof) { if (remaining) { - if (to_buffer) { - // append unfinished line - ml_append(curwin->w_cursor.lnum++, (char_u *)output, 0, false); - // remember that the NL was missing - curbuf->b_no_eol_lnum = curwin->w_cursor.lnum; - } else { - screen_del_lines(0, 0, 1, (int)Rows, NULL); - screen_puts_len((char_u *)output, (int)remaining, lastrow, 0, 0); - } + // append unfinished line + ml_append(curwin->w_cursor.lnum++, (char_u *)output, 0, false); + // remember that the NL was missing + curbuf->b_no_eol_lnum = curwin->w_cursor.lnum; output += remaining; - } else if (to_buffer) { + } else { curbuf->b_no_eol_lnum = 0; } } diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index fd6d3b32e4..732be072e1 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -10,6 +10,7 @@ #endif #include "nvim/ascii.h" +#include "nvim/log.h" #include "nvim/vim.h" #include "nvim/globals.h" #include "nvim/memline.h" @@ -162,7 +163,7 @@ static void on_signal(SignalWatcher *handle, int signum, void *data) } break; default: - fprintf(stderr, "Invalid signal %d", signum); + ELOG("invalid signal: %d", signum); break; } } diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c index c471352c02..290d421acc 100644 --- a/src/nvim/os/time.c +++ b/src/nvim/os/time.c @@ -38,33 +38,33 @@ uint64_t os_hrtime(void) return uv_hrtime(); } -/// Sleeps for a certain amount of milliseconds. +/// Sleeps for `ms` milliseconds. /// -/// @param milliseconds Number of milliseconds to sleep +/// @param ms Number of milliseconds to sleep /// @param ignoreinput If true, only SIGINT (CTRL-C) can interrupt. -void os_delay(uint64_t milliseconds, bool ignoreinput) +void os_delay(uint64_t ms, bool ignoreinput) { if (ignoreinput) { - if (milliseconds > INT_MAX) { - milliseconds = INT_MAX; + if (ms > INT_MAX) { + ms = INT_MAX; } - LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, (int)milliseconds, got_int); + LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, (int)ms, got_int); } else { - os_microdelay(milliseconds * 1000u, ignoreinput); + os_microdelay(ms * 1000u, ignoreinput); } } -/// Sleeps for a certain amount of microseconds. +/// Sleeps for `us` microseconds. /// -/// @param ms Number of microseconds to sleep. +/// @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. -void os_microdelay(uint64_t ms, bool ignoreinput) +void os_microdelay(uint64_t us, bool ignoreinput) { uint64_t elapsed = 0u; uint64_t base = uv_hrtime(); // Convert microseconds to nanoseconds, or UINT64_MAX on overflow. - const uint64_t ns = (ms < UINT64_MAX / 1000u) ? ms * 1000u : UINT64_MAX; + const uint64_t ns = (us < UINT64_MAX / 1000u) ? us * 1000u : UINT64_MAX; uv_mutex_lock(&delay_mutex); diff --git a/src/nvim/os/unix_defs.h b/src/nvim/os/unix_defs.h index 5c9daca476..60a2dfa882 100644 --- a/src/nvim/os/unix_defs.h +++ b/src/nvim/os/unix_defs.h @@ -1,9 +1,8 @@ #ifndef NVIM_OS_UNIX_DEFS_H #define NVIM_OS_UNIX_DEFS_H -// Windows doesn't have unistd.h, so we include it here to avoid numerous -// instances of `#ifdef WIN32'. #include <unistd.h> +#include <sys/param.h> // POSIX.1-2008 says that NAME_MAX should be in here #include <limits.h> diff --git a/src/nvim/os/win_defs.h b/src/nvim/os/win_defs.h index 8fd2e51f8b..db93f016bf 100644 --- a/src/nvim/os/win_defs.h +++ b/src/nvim/os/win_defs.h @@ -32,11 +32,8 @@ // Windows defines a RGB macro that produces 0x00bbggrr color values for use // with GDI. Our macro is different, and we don't use GDI. -#if defined(RGB) -# undef RGB - // Duplicated from macros.h to avoid include-order sensitivity. -# define RGB(r, g, b) ((r << 16) | (g << 8) | b) -#endif +// Duplicated from macros.h to avoid include-order sensitivity. +#define RGB_(r, g, b) ((r << 16) | (g << 8) | b) #ifdef _MSC_VER # ifndef inline @@ -45,6 +42,9 @@ # ifndef restrict # define restrict __restrict # endif +# ifndef STDIN_FILENO +# define STDIN_FILENO _fileno(stdin) +# endif # ifndef STDOUT_FILENO # define STDOUT_FILENO _fileno(stdout) # endif @@ -60,6 +60,7 @@ #ifdef _MSC_VER typedef SSIZE_T ssize_t; +typedef int mode_t; #endif #ifndef SSIZE_MAX diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c index d7ba675b68..a27fee4e90 100644 --- a/src/nvim/os_unix.c +++ b/src/nvim/os_unix.c @@ -133,7 +133,8 @@ void mch_free_acl(vim_acl_T aclent) } #endif -void mch_exit(int r) FUNC_ATTR_NORETURN +void mch_exit(int r) + FUNC_ATTR_NORETURN { exiting = true; diff --git a/src/nvim/path.c b/src/nvim/path.c index 51adcfb135..168d835a66 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -332,7 +332,7 @@ int path_fnamencmp(const char *const fname1, const char *const fname2, && (p_fic ? (c1 != c2 && CH_FOLD(c1) != CH_FOLD(c2)) : c1 != c2)) { break; } - len -= MB_PTR2LEN((const char_u *)p1); + len -= (size_t)MB_PTR2LEN((const char_u *)p1); p1 += MB_PTR2LEN((const char_u *)p1); p2 += MB_PTR2LEN((const char_u *)p2); } @@ -452,10 +452,10 @@ char *FullName_save(const char *fname, bool force) /// Saves the absolute path. /// @param name An absolute or relative path. /// @return The absolute path of `name`. -char_u *save_absolute_path(const char_u *name) +char_u *save_abs_path(const char_u *name) FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { - if (!path_is_absolute_path(name)) { + if (!path_is_absolute(name)) { return (char_u *)FullName_save((char *)name, true); } return vim_strsave((char_u *) name); @@ -814,7 +814,7 @@ static void expand_path_option(char_u *curdir, garray_T *gap) STRCPY(buf, curdir); // relative to current directory } else if (path_with_url((char *)buf)) { continue; // URL can't be used here - } else if (!path_is_absolute_path(buf)) { + } else if (!path_is_absolute(buf)) { // Expand relative path to their full path equivalent size_t len = STRLEN(curdir); if (len + STRLEN(buf) + 3 > MAXPATHL) { @@ -949,19 +949,17 @@ static void uniquefy_paths(garray_T *gap, char_u *pattern) } } - if (path_is_absolute_path(path)) { - /* - * Last resort: shorten relative to curdir if possible. - * 'possible' means: - * 1. It is under the current directory. - * 2. The result is actually shorter than the original. - * - * Before curdir After - * /foo/bar/file.txt /foo/bar ./file.txt - * c:\foo\bar\file.txt c:\foo\bar .\file.txt - * /file.txt / /file.txt - * c:\file.txt c:\ .\file.txt - */ + if (path_is_absolute(path)) { + // Last resort: shorten relative to curdir if possible. + // 'possible' means: + // 1. It is under the current directory. + // 2. The result is actually shorter than the original. + // + // Before curdir After + // /foo/bar/file.txt /foo/bar ./file.txt + // c:\foo\bar\file.txt c:\foo\bar .\file.txt + // /file.txt / /file.txt + // c:\file.txt c:\ .\file.txt short_name = path_shorten_fname(path, curdir); if (short_name != NULL && short_name > path + 1 ) { @@ -1221,7 +1219,7 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file, */ if (path_has_exp_wildcard(p)) { if ((flags & EW_PATH) - && !path_is_absolute_path(p) + && !path_is_absolute(p) && !(p[0] == '.' && (vim_ispathsep(p[1]) || (p[1] == '.' && vim_ispathsep(p[2])))) @@ -1667,7 +1665,7 @@ int path_with_url(const char *fname) */ bool vim_isAbsName(char_u *name) { - return path_with_url((char *)name) != 0 || path_is_absolute_path(name); + return path_with_url((char *)name) != 0 || path_is_absolute(name); } /// Save absolute file name to "buf[len]". @@ -1691,7 +1689,7 @@ int vim_FullName(const char *fname, char *buf, size_t len, bool force) if (strlen(fname) > (len - 1)) { xstrlcpy(buf, fname, len); // truncate #ifdef WIN32 - slash_adjust(buf); + slash_adjust((char_u *)buf); #endif return FAIL; } @@ -1701,12 +1699,12 @@ int vim_FullName(const char *fname, char *buf, size_t len, bool force) return OK; } - int rv = path_get_absolute_path((char_u *)fname, (char_u *)buf, len, force); + int rv = path_to_absolute((char_u *)fname, (char_u *)buf, len, force); if (rv == FAIL) { xstrlcpy(buf, fname, len); // something failed; use the filename } #ifdef WIN32 - slash_adjust(buf); + slash_adjust((char_u *)buf); #endif return rv; } @@ -1741,7 +1739,7 @@ char *fix_fname(const char *fname) path_fix_case((char_u *)fname); // set correct case for file name # endif - return fname; + return (char *)fname; #endif } @@ -1910,7 +1908,7 @@ int pathcmp(const char *p, const char *q, int maxlen) /// - Pointer into `full_path` if shortened. /// - `full_path` unchanged if no shorter name is possible. /// - NULL if `full_path` is NULL. -char_u *path_shorten_fname_if_possible(char_u *full_path) +char_u *path_try_shorten_fname(char_u *full_path) { char_u *dirname = xmalloc(MAXPATHL); char_u *p = full_path; @@ -2191,8 +2189,8 @@ int append_path(char *path, const char *to_append, size_t max_len) /// @param force also expand when "fname" is already absolute. /// /// @return FAIL for failure, OK for success. -static int path_get_absolute_path(const char_u *fname, char_u *buf, - size_t len, int force) +static int path_to_absolute(const char_u *fname, char_u *buf, size_t len, + int force) { char_u *p; *buf = NUL; @@ -2201,8 +2199,14 @@ static int path_get_absolute_path(const char_u *fname, char_u *buf, char *end_of_path = (char *) fname; // expand it if forced or not an absolute path - if (force || !path_is_absolute_path(fname)) { - if ((p = vim_strrchr(fname, PATHSEP)) != NULL) { + if (force || !path_is_absolute(fname)) { + p = vim_strrchr(fname, '/'); +#ifdef WIN32 + if (p == NULL) { + p = vim_strrchr(fname, '\\'); + } +#endif + if (p != NULL) { // relative to root if (p == fname) { // only one path component @@ -2228,10 +2232,10 @@ static int path_get_absolute_path(const char_u *fname, char_u *buf, return append_path((char *)buf, end_of_path, len); } -/// Check if the given file is absolute. +/// Check if file `fname` is a full (absolute) path. /// /// @return `TRUE` if "fname" is absolute. -int path_is_absolute_path(const char_u *fname) +int path_is_absolute(const char_u *fname) { #ifdef WIN32 // A name like "d:/foo" and "//server/share" is absolute @@ -2256,7 +2260,7 @@ void path_guess_exepath(const char *argv0, char *buf, size_t bufsize) { char *path = getenv("PATH"); - if (path == NULL || path_is_absolute_path((char_u *)argv0)) { + if (path == NULL || path_is_absolute((char_u *)argv0)) { xstrlcpy(buf, argv0, bufsize); } else if (argv0[0] == '.' || strchr(argv0, PATHSEP)) { // Relative to CWD. diff --git a/src/nvim/po/CMakeLists.txt b/src/nvim/po/CMakeLists.txt index 121f22129a..94cc63baea 100644 --- a/src/nvim/po/CMakeLists.txt +++ b/src/nvim/po/CMakeLists.txt @@ -2,10 +2,8 @@ find_package(Gettext) find_program(XGETTEXT_PRG xgettext) find_program(ICONV_PRG iconv) -if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) - set(ENV{OLD_PO_FILE_INPUT} yes) - set(ENV{OLD_PO_FILE_OUTPUT} yes) - +option(LANGUAGES "Localizations to build") +if(NOT LANGUAGES) set(LANGUAGES af ca @@ -31,6 +29,12 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) vi zh_CN.UTF-8 zh_TW.UTF-8) +endif() + +if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) + set(ENV{OLD_PO_FILE_INPUT} yes) + set(ENV{OLD_PO_FILE_OUTPUT} yes) + set(NVIM_RELATIVE_SOURCES) foreach(SRC ${NVIM_SOURCES} ${NVIM_HEADERS}) @@ -135,22 +139,30 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) endmacro() # Create some translations from others. - BuildPoIconv(ja utf-8 euc-jp) - BuildMo(ja.euc-jp) + if(";${LANGUAGES};" MATCHES ";ja;") + BuildPoIconv(ja utf-8 euc-jp) + BuildMo(ja.euc-jp) + endif() - BuildPoIconv(cs ISO-8859-2 cp1250) - BuildMo(cs.cp1250) + if(";${LANGUAGES};" MATCHES ";cs;") + BuildPoIconv(cs ISO-8859-2 cp1250) + BuildMo(cs.cp1250) + endif() - BuildPoIconv(sk ISO-8859-2 cp1250) - BuildMo(sk.cp1250) + if(";${LANGUAGES};" MATCHES ";sk;") + BuildPoIconv(sk ISO-8859-2 cp1250) + BuildMo(sk.cp1250) + endif() add_custom_target(update-po-nb COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/no.po ${CMAKE_CURRENT_SOURCE_DIR}/nb.po DEPENDS no.po) list(APPEND UPDATE_PO_TARGETS update-po-nb) - CheckPo(nb) - BuildMo(nb) + if(";${LANGUAGES};" MATCHES ";no;") + CheckPo(nb) + BuildMo(nb) + endif() foreach(LANGUAGE ${LANGUAGES}) set(poFile "${CMAKE_CURRENT_SOURCE_DIR}/${LANGUAGE}.po") diff --git a/src/nvim/po/af.po b/src/nvim/po/af.po index 27bb2d3a80..f42ad04409 100644 --- a/src/nvim/po/af.po +++ b/src/nvim/po/af.po @@ -4654,7 +4654,7 @@ msgstr " GEFAAL" #~ msgstr "" #, fuzzy, c-format -#~ msgid "Did not rename %s because %s does not looks like a ShaDa file" +#~ msgid "Did not rename %s because %s does not look like a ShaDa file" #~ msgstr " [lyk nie soos 'n Vim ruillêer nie]" #, c-format diff --git a/src/nvim/po/fi.po b/src/nvim/po/fi.po index e491649e14..8fa0cc48d4 100644 --- a/src/nvim/po/fi.po +++ b/src/nvim/po/fi.po @@ -4719,7 +4719,7 @@ msgstr " EPÄONNISTUI" #~ msgstr "E886: Viminfo-tiedostoa ei voit uudelleennimetä nimelle %s" #, fuzzy, c-format -#~ msgid "Did not rename %s because %s does not looks like a ShaDa file" +#~ msgid "Did not rename %s because %s does not look like a ShaDa file" #~ msgstr " [ei näytä Vimin swap-tiedostolta]" #, c-format diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po index 68425073c7..1ac2d2247a 100644 --- a/src/nvim/po/uk.po +++ b/src/nvim/po/uk.po @@ -4473,7 +4473,7 @@ msgid "Can't rename ShaDa file from %s to %s!" msgstr "Ðе вдалоÑÑ Ð¿ÐµÑ€ÐµÐ¹Ð¼ÐµÐ½ÑƒÐ²Ð°Ñ‚Ð¸ файл ShaDa з %s у %s!" #, c-format -msgid "Did not rename %s because %s does not looks like a ShaDa file" +msgid "Did not rename %s because %s does not look like a ShaDa file" msgstr "Ðе перейменував %s, тому що %s не Ñхожий на файл ShaDa" #, c-format diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index 2462975c9b..348daf028a 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -567,6 +567,7 @@ static int pum_set_selected(int n, int repeat) && (repeat <= 1) && (vim_strchr(p_cot, 'p') != NULL)) { win_T *curwin_save = curwin; + tabpage_T *curtab_save = curtab; int res = OK; // Open a preview window. 3 lines by default. Prefer @@ -593,7 +594,7 @@ static int pum_set_selected(int n, int repeat) && (curbuf->b_p_bt[2] == 'f') && (curbuf->b_p_bh[0] == 'w')) { // Already a "wipeout" buffer, make it empty. - while (!bufempty()) { + while (!BUFEMPTY()) { ml_delete((linenr_T)1, FALSE); } } else { @@ -647,7 +648,12 @@ static int pum_set_selected(int n, int repeat) curwin->w_cursor.lnum = 1; curwin->w_cursor.col = 0; - if ((curwin != curwin_save) && win_valid(curwin_save)) { + if ((curwin != curwin_save && win_valid(curwin_save)) + || (curtab != curtab_save && valid_tabpage(curtab_save))) { + if (curtab != curtab_save && valid_tabpage(curtab_save)) { + goto_tabpage_tp(curtab_save, false, false); + } + // When the first completion is done and the preview // window is not resized, skip the preview window's // status line redrawing. diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 1fc585f0c9..46124e0672 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -76,17 +76,25 @@ struct qfline_S { */ #define LISTCOUNT 10 +/// Quickfix/Location list definition +/// +/// Usually the list contains one or more entries. But an empty list can be +/// created using setqflist()/setloclist() with a title and/or user context +/// information and entries can be added later using setqflist()/setloclist(). typedef struct qf_list_S { - qfline_T *qf_start; // pointer to the first error - qfline_T *qf_last; // pointer to the last error - qfline_T *qf_ptr; // pointer to the current error - int qf_count; // number of errors (0 means no error list) - int qf_index; // current index in the error list - int qf_nonevalid; // TRUE if not a single valid entry found - char_u *qf_title; // title derived from the command that created - // the error list + qfline_T *qf_start; ///< pointer to the first error + qfline_T *qf_last; ///< pointer to the last error + qfline_T *qf_ptr; ///< pointer to the current error + int qf_count; ///< number of errors (0 means empty list) + int qf_index; ///< current index in the error list + int qf_nonevalid; ///< TRUE if not a single valid entry found + char_u *qf_title; ///< title derived from the command that created + ///< the error list or set by setqflist + typval_T *qf_ctx; ///< context set by setqflist/setloclist } qf_list_T; +/// Quickfix/Location list stack definition +/// Contains a list of quickfix/location lists (qf_list_T) struct qf_info_S { /* * Count of references to this list. Used only for location lists. @@ -156,10 +164,12 @@ typedef struct { FILE *fd; typval_T *tv; char_u *p_str; + list_T *p_list; listitem_T *p_li; buf_T *buf; linenr_T buflnum; linenr_T lnumlast; + vimconv_T vc; } qfstate_T; typedef struct { @@ -193,19 +203,19 @@ typedef struct { static char_u *qf_last_bufname = NULL; static bufref_T qf_last_bufref = { NULL, 0, 0 }; -/* - * Read the errorfile "efile" into memory, line by line, building the error - * list. Set the error list's title to qf_title. - * Return -1 for error, number of errors for success. - */ -int -qf_init ( - win_T *wp, - char_u *efile, - char_u *errorformat, - int newlist, /* TRUE: start a new error list */ - char_u *qf_title -) +/// Read the errorfile "efile" into memory, line by line, building the error +/// list. Set the error list's title to qf_title. +/// +/// @params wp If non-NULL, make a location list +/// @params efile If non-NULL, errorfile to parse +/// @params errorformat 'errorformat' string used to parse the error lines +/// @params newlist If true, create a new error list +/// @params qf_title If non-NULL, title of the error list +/// @params enc If non-NULL, encoding used to parse errors +/// +/// @returns -1 for error, number of errors for success. +int qf_init(win_T *wp, char_u *efile, char_u *errorformat, int newlist, + char_u *qf_title, char_u *enc) { qf_info_T *qi = &ql_info; @@ -214,8 +224,8 @@ qf_init ( } return qf_init_ext(qi, efile, curbuf, NULL, errorformat, newlist, - (linenr_T)0, (linenr_T)0, - qf_title); + (linenr_T)0, (linenr_T)0, + qf_title, enc); } // Maximum number of bytes allowed per line while reading an errorfile. @@ -516,17 +526,17 @@ static int qf_get_next_list_line(qfstate_T *state) // Get the next line from the supplied list while (p_li != NULL - && (p_li->li_tv.v_type != VAR_STRING - || p_li->li_tv.vval.v_string == NULL)) { - p_li = p_li->li_next; // Skip non-string items + && (TV_LIST_ITEM_TV(p_li)->v_type != VAR_STRING + || TV_LIST_ITEM_TV(p_li)->vval.v_string == NULL)) { + p_li = TV_LIST_ITEM_NEXT(state->p_list, p_li); // Skip non-string items. } - if (p_li == NULL) { // End of the list + if (p_li == NULL) { // End of the list. state->p_li = NULL; return QF_END_OF_INPUT; } - len = STRLEN(p_li->li_tv.vval.v_string); + len = STRLEN(TV_LIST_ITEM_TV(p_li)->vval.v_string); if (len > IOSIZE - 2) { state->linebuf = qf_grow_linebuf(state, len); } else { @@ -534,9 +544,10 @@ static int qf_get_next_list_line(qfstate_T *state) state->linelen = len; } - STRLCPY(state->linebuf, p_li->li_tv.vval.v_string, state->linelen + 1); + STRLCPY(state->linebuf, TV_LIST_ITEM_TV(p_li)->vval.v_string, + state->linelen + 1); - state->p_li = p_li->li_next; // next item + state->p_li = TV_LIST_ITEM_NEXT(state->p_list, p_li); return QF_OK; } @@ -638,6 +649,22 @@ retry: } else { state->linebuf = IObuff; } + + // Convert a line if it contains a non-ASCII character + if (state->vc.vc_type != CONV_NONE && has_non_ascii(state->linebuf)) { + char_u *line = string_convert(&state->vc, state->linebuf, &state->linelen); + if (line != NULL) { + if (state->linelen < IOSIZE) { + STRLCPY(state->linebuf, line, state->linelen + 1); + xfree(line); + } else { + xfree(state->growbuf); + state->linebuf = state->growbuf = line; + state->growbufsiz = state->linelen < LINE_MAXLEN + ? state->linelen : LINE_MAXLEN; + } + } + } return QF_OK; } @@ -783,7 +810,7 @@ restofline: fields->type = *regmatch.startp[i]; } if (fmt_ptr->flags == '+' && !qi->qf_multiscan) { // %+ - if (linelen > fields->errmsglen) { + if (linelen >= fields->errmsglen) { // linelen + null terminator fields->errmsg = xrealloc(fields->errmsg, linelen + 1); fields->errmsglen = linelen + 1; @@ -794,7 +821,7 @@ restofline: continue; } len = (size_t)(regmatch.endp[i] - regmatch.startp[i]); - if (len > fields->errmsglen) { + if (len >= fields->errmsglen) { // len + null terminator fields->errmsg = xrealloc(fields->errmsg, len + 1); fields->errmsglen = len + 1; @@ -871,7 +898,7 @@ restofline: fields->namebuf[0] = NUL; // no match found, remove file name fields->lnum = 0; // don't jump to this line fields->valid = false; - if (linelen > fields->errmsglen) { + if (linelen >= fields->errmsglen) { // linelen + null terminator fields->errmsg = xrealloc(fields->errmsg, linelen + 1); fields->errmsglen = linelen + 1; @@ -976,15 +1003,15 @@ qf_init_ext( buf_T *buf, typval_T *tv, char_u *errorformat, - int newlist, /* TRUE: start a new error list */ - linenr_T lnumfirst, /* first line number to use */ - linenr_T lnumlast, /* last line number to use */ - char_u *qf_title + int newlist, // TRUE: start a new error list + linenr_T lnumfirst, // first line number to use + linenr_T lnumlast, // last line number to use + char_u *qf_title, + char_u *enc ) { - qfstate_T state = { NULL, 0, NULL, 0, NULL, NULL, NULL, NULL, - NULL, 0, 0 }; - qffields_T fields = { NULL, NULL, 0, 0L, 0, false, NULL, 0, 0, 0 }; + qfstate_T state; + qffields_T fields; qfline_T *old_last = NULL; bool adding = false; static efm_T *fmt_first = NULL; @@ -997,6 +1024,13 @@ qf_init_ext( xfree(qf_last_bufname); qf_last_bufname = NULL; + memset(&state, 0, sizeof(state)); + memset(&fields, 0, sizeof(fields)); + state.vc.vc_type = CONV_NONE; + if (enc != NULL && *enc != NUL) { + convert_setup(&state.vc, enc, p_enc); + } + fields.namebuf = xmalloc(CMDBUFFSIZE + 1); fields.errmsglen = CMDBUFFSIZE + 1; fields.errmsg = xmalloc(fields.errmsglen); @@ -1064,7 +1098,8 @@ qf_init_ext( if (tv->v_type == VAR_STRING) { state.p_str = tv->vval.v_string; } else if (tv->v_type == VAR_LIST) { - state.p_li = tv->vval.v_list->lv_first; + state.p_list = tv->vval.v_list; + state.p_li = tv_list_first(tv->vval.v_list); } state.tv = tv; } @@ -1093,6 +1128,7 @@ qf_init_ext( } if (qf_add_entry(qi, + qi->qf_curlist, qi->qf_directory, (*fields.namebuf || qi->qf_directory) ? fields.namebuf : ((qi->qf_currfile && fields.valid) @@ -1147,16 +1183,24 @@ qf_init_end: qf_update_buffer(qi, old_last); + if (state.vc.vc_type != CONV_NONE) { + convert_setup(&state.vc, NULL, NULL); + } + return retval; } -static void qf_store_title(qf_info_T *qi, char_u *title) +static void qf_store_title(qf_info_T *qi, int qf_idx, char_u *title) { + xfree(qi->qf_lists[qf_idx].qf_title); + qi->qf_lists[qf_idx].qf_title = NULL; + if (title != NULL) { - char_u *p = xmalloc(STRLEN(title) + 2); + size_t len = STRLEN(title) + 1; + char_u *p = xmallocz(len); - qi->qf_lists[qi->qf_curlist].qf_title = p; - sprintf((char *)p, ":%s", (char *)title); + qi->qf_lists[qf_idx].qf_title = p; + snprintf((char *)p, len + 1, ":%s", (char *)title); } } @@ -1185,7 +1229,7 @@ static void qf_new_list(qf_info_T *qi, char_u *qf_title) } else qi->qf_curlist = qi->qf_listcount++; memset(&qi->qf_lists[qi->qf_curlist], 0, (size_t)(sizeof(qf_list_T))); - qf_store_title(qi, qf_title); + qf_store_title(qi, qi->qf_curlist, qf_title); } /* @@ -1228,6 +1272,7 @@ void qf_free_all(win_T *wp) /// Add an entry to the end of the list of errors. /// /// @param qi quickfix list +/// @param qf_idx list index /// @param dir optional directory name /// @param fname file name or NULL /// @param bufnum buffer number or zero @@ -1241,9 +1286,10 @@ void qf_free_all(win_T *wp) /// @param valid valid entry /// /// @returns OK or FAIL. -static int qf_add_entry(qf_info_T *qi, char_u *dir, char_u *fname, int bufnum, - char_u *mesg, long lnum, int col, char_u vis_col, - char_u *pattern, int nr, char_u type, char_u valid) +static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, + int bufnum, char_u *mesg, long lnum, int col, + char_u vis_col, char_u *pattern, int nr, char_u type, + char_u valid) { qfline_T *qfp = xmalloc(sizeof(qfline_T)); qfline_T **lastp; // pointer to qf_last or NULL @@ -1274,12 +1320,12 @@ static int qf_add_entry(qf_info_T *qi, char_u *dir, char_u *fname, int bufnum, qfp->qf_type = (char_u)type; qfp->qf_valid = valid; - lastp = &qi->qf_lists[qi->qf_curlist].qf_last; - if (qi->qf_lists[qi->qf_curlist].qf_count == 0) { - /* first element in the list */ - qi->qf_lists[qi->qf_curlist].qf_start = qfp; - qi->qf_lists[qi->qf_curlist].qf_ptr = qfp; - qi->qf_lists[qi->qf_curlist].qf_index = 0; + lastp = &qi->qf_lists[qf_idx].qf_last; + if (qi->qf_lists[qf_idx].qf_count == 0) { + // first element in the list + qi->qf_lists[qf_idx].qf_start = qfp; + qi->qf_lists[qf_idx].qf_ptr = qfp; + qi->qf_lists[qf_idx].qf_index = 0; qfp->qf_prev = NULL; } else { assert(*lastp); @@ -1289,12 +1335,11 @@ static int qf_add_entry(qf_info_T *qi, char_u *dir, char_u *fname, int bufnum, qfp->qf_next = NULL; qfp->qf_cleared = false; *lastp = qfp; - qi->qf_lists[qi->qf_curlist].qf_count++; - if (qi->qf_lists[qi->qf_curlist].qf_index == 0 && qfp->qf_valid) { - /* first valid entry */ - qi->qf_lists[qi->qf_curlist].qf_index = - qi->qf_lists[qi->qf_curlist].qf_count; - qi->qf_lists[qi->qf_curlist].qf_ptr = qfp; + qi->qf_lists[qf_idx].qf_count++; + if (qi->qf_lists[qf_idx].qf_index == 0 && qfp->qf_valid) { + // first valid entry + qi->qf_lists[qf_idx].qf_index = qi->qf_lists[qf_idx].qf_count; + qi->qf_lists[qf_idx].qf_ptr = qfp; } return OK; @@ -1381,6 +1426,13 @@ void copy_loclist(win_T *from, win_T *to) else to_qfl->qf_title = NULL; + if (from_qfl->qf_ctx != NULL) { + to_qfl->qf_ctx = xcalloc(1, sizeof(typval_T)); + tv_copy(from_qfl->qf_ctx, to_qfl->qf_ctx); + } else { + to_qfl->qf_ctx = NULL; + } + if (from_qfl->qf_count) { qfline_T *from_qfp; qfline_T *prevp; @@ -1390,6 +1442,7 @@ void copy_loclist(win_T *from, win_T *to) i < from_qfl->qf_count && from_qfp != NULL; i++, from_qfp = from_qfp->qf_next) { if (qf_add_entry(to->w_llist, + to->w_llist->qf_curlist, NULL, NULL, 0, @@ -2353,8 +2406,9 @@ void qf_history(exarg_T *eap) } } -/// Free all the entries in the error list "idx". -static void qf_free(qf_info_T *qi, int idx) +/// Free all the entries in the error list "idx". Note that other information +/// associated with the list like context and title are not freed. +static void qf_free_items(qf_info_T *qi, int idx) { qfline_T *qfp; qfline_T *qfpnext; @@ -2378,16 +2432,34 @@ static void qf_free(qf_info_T *qi, int idx) qi->qf_lists[idx].qf_start = qfpnext; qi->qf_lists[idx].qf_count--; } - xfree(qi->qf_lists[idx].qf_title); + qi->qf_lists[idx].qf_start = NULL; qi->qf_lists[idx].qf_ptr = NULL; - qi->qf_lists[idx].qf_title = NULL; qi->qf_lists[idx].qf_index = 0; + qi->qf_lists[idx].qf_start = NULL; + qi->qf_lists[idx].qf_last = NULL; + qi->qf_lists[idx].qf_ptr = NULL; + qi->qf_lists[idx].qf_nonevalid = true; qf_clean_dir_stack(&qi->qf_dir_stack); qi->qf_directory = NULL; qf_clean_dir_stack(&qi->qf_file_stack); qi->qf_currfile = NULL; + qi->qf_multiline = false; + qi->qf_multiignore = false; + qi->qf_multiscan = false; +} + +/// Free error list "idx". Frees all the entries in the quickfix list, +/// associated context information and the title. +static void qf_free(qf_info_T *qi, int idx) +{ + qf_free_items(qi, idx); + + xfree(qi->qf_lists[idx].qf_title); + qi->qf_lists[idx].qf_title = NULL; + tv_free(qi->qf_lists[idx].qf_ctx); + qi->qf_lists[idx].qf_ctx = NULL; } /* @@ -2853,7 +2925,7 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) if (old_last == NULL) { if (buf != curbuf) { - EMSG2(_(e_intern2), "qf_fill_buffer()"); + internal_error("qf_fill_buffer()"); return; } @@ -2957,7 +3029,7 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) /* * Return TRUE if "buf" is the quickfix buffer. */ -int bt_quickfix(buf_T *buf) +int bt_quickfix(const buf_T *const buf) { return buf != NULL && buf->b_p_bt[0] == 'q'; } @@ -3024,6 +3096,7 @@ void ex_make(exarg_T *eap) qf_info_T *qi = &ql_info; int res; char_u *au_name = NULL; + char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; /* Redirect ":grep" to ":vimgrep" if 'grepprg' is "internal". */ if (grep_internal(eap->cmdidx)) { @@ -3075,19 +3148,18 @@ void ex_make(exarg_T *eap) } msg_start(); MSG_PUTS(":!"); - msg_outtrans((char_u *) cmd); // show what we are doing + msg_outtrans((char_u *)cmd); // show what we are doing - // let the shell know if we are redirecting output or not - do_shell((char_u *) cmd, *p_sp != NUL ? kShellOptDoOut : 0); + do_shell((char_u *)cmd, 0); res = qf_init(wp, fname, (eap->cmdidx != CMD_make && eap->cmdidx != CMD_lmake) ? p_gefm : p_efm, - (eap->cmdidx != CMD_grepadd - && eap->cmdidx != CMD_lgrepadd), - *eap->cmdlinep); - if (wp != NULL) + (eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd), + *eap->cmdlinep, enc); + if (wp != NULL) { qi = GET_LOC_LIST(wp); + } if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, TRUE, curbuf); @@ -3429,19 +3501,18 @@ void ex_cfile(exarg_T *eap) if (*eap->arg != NUL) set_string_option_direct((char_u *)"ef", -1, eap->arg, OPT_FREE, 0); - /* - * This function is used by the :cfile, :cgetfile and :caddfile - * commands. - * :cfile always creates a new quickfix list and jumps to the - * first error. - * :cgetfile creates a new quickfix list but doesn't jump to the - * first error. - * :caddfile adds to an existing quickfix list. If there is no - * quickfix list then a new list is created. - */ + char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; + // This function is used by the :cfile, :cgetfile and :caddfile + // commands. + // :cfile always creates a new quickfix list and jumps to the + // first error. + // :cgetfile creates a new quickfix list but doesn't jump to the + // first error. + // :caddfile adds to an existing quickfix list. If there is no + // quickfix list then a new list is created. if (qf_init(wp, p_ef, p_efm, (eap->cmdidx != CMD_caddfile && eap->cmdidx != CMD_laddfile), - *eap->cmdlinep) > 0 + *eap->cmdlinep, enc) > 0 && (eap->cmdidx == CMD_cfile || eap->cmdidx == CMD_lfile)) { if (au_name != NULL) @@ -3578,8 +3649,8 @@ void ex_vimgrep(exarg_T *eap) cur_qf_start = qi->qf_lists[qi->qf_curlist].qf_start; seconds = (time_t)0; - for (fi = 0; fi < fcount && !got_int && tomatch > 0; ++fi) { - fname = path_shorten_fname_if_possible(fnames[fi]); + for (fi = 0; fi < fcount && !got_int && tomatch > 0; fi++) { + fname = path_try_shorten_fname(fnames[fi]); if (time(NULL) > seconds) { /* Display the file name every second or so, show the user we are * working on it. */ @@ -3656,6 +3727,7 @@ void ex_vimgrep(exarg_T *eap) // dummy buffer, unless duplicate_name is set, then the // buffer will be wiped out below. if (qf_add_entry(qi, + qi->qf_curlist, NULL, // dir fname, duplicate_name ? 0 : buf->b_fnum, @@ -3837,6 +3909,7 @@ load_dummy_buffer ( bufref_T newbuf_to_wipe; int failed = true; aco_save_T aco; + int readfile_result; // Allocate a buffer without putting it in the buffer list. newbuf = buflist_new(NULL, NULL, (linenr_T)1, BLN_DUMMY); @@ -3850,7 +3923,9 @@ load_dummy_buffer ( /* need to open the memfile before putting the buffer in a window */ if (ml_open(newbuf) == OK) { - /* set curwin/curbuf to buf and save a few things */ + // Make sure this buffer isn't wiped out by auto commands. + newbuf->b_locked++; + // set curwin/curbuf to buf and save a few things aucmd_prepbuf(&aco, newbuf); /* Need to set the filename for autocommands. */ @@ -3864,9 +3939,11 @@ load_dummy_buffer ( curbuf->b_flags &= ~BF_DUMMY; newbuf_to_wipe.br_buf = NULL; - if (readfile(fname, NULL, - (linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM, - NULL, READ_NEW | READ_DUMMY) == OK + readfile_result = readfile(fname, NULL, (linenr_T)0, (linenr_T)0, + (linenr_T)MAXLNUM, NULL, + READ_NEW | READ_DUMMY); + newbuf->b_locked--; + if (readfile_result == OK && !got_int && !(curbuf->b_flags & BF_NEW)) { failed = FALSE; @@ -4025,6 +4102,7 @@ enum { QF_GETLIST_ITEMS = 0x2, QF_GETLIST_NR = 0x4, QF_GETLIST_WINID = 0x8, + QF_GETLIST_CONTEXT = 0x10, QF_GETLIST_ALL = 0xFF }; @@ -4034,16 +4112,22 @@ enum { int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) { qf_info_T *qi = &ql_info; + dictitem_T *di; if (wp != NULL) { qi = GET_LOC_LIST(wp); if (qi == NULL) { + // If querying for the size of the location list, return 0 + if (((di = tv_dict_find(what, S_LEN("nr"))) != NULL) + && (di->di_tv.v_type == VAR_STRING) + && strequal((const char *)di->di_tv.vval.v_string, "$")) { + return tv_dict_add_nr(retdict, S_LEN("nr"), 0); + } return FAIL; } } int status = OK; - dictitem_T *di; int flags = QF_GETLIST_NONE; int qf_idx = qi->qf_curlist; // default is the current list @@ -4056,6 +4140,17 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { return FAIL; } + } else if (qi->qf_listcount == 0) { // stack is empty + return FAIL; + } + flags |= QF_GETLIST_NR; + } else if (di->di_tv.v_type == VAR_STRING + && strequal((const char *)di->di_tv.vval.v_string, "$")) { + // Get the last quickfix list number + if (qi->qf_listcount > 0) { + qf_idx = qi->qf_listcount - 1; + } else { + qf_idx = -1; // Quickfix stack is empty } flags |= QF_GETLIST_NR; } else { @@ -4063,16 +4158,26 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) } } - if (tv_dict_find(what, S_LEN("all")) != NULL) { - flags |= QF_GETLIST_ALL; - } + if (qf_idx != -1) { + if (tv_dict_find(what, S_LEN("all")) != NULL) { + flags |= QF_GETLIST_ALL; + } - if (tv_dict_find(what, S_LEN("title")) != NULL) { - flags |= QF_GETLIST_TITLE; - } + if (tv_dict_find(what, S_LEN("title")) != NULL) { + flags |= QF_GETLIST_TITLE; + } + + if (tv_dict_find(what, S_LEN("winid")) != NULL) { + flags |= QF_GETLIST_WINID; + } - if (tv_dict_find(what, S_LEN("winid")) != NULL) { - flags |= QF_GETLIST_WINID; + if (tv_dict_find(what, S_LEN("context")) != NULL) { + flags |= QF_GETLIST_CONTEXT; + } + + if (tv_dict_find(what, S_LEN("items")) != NULL) { + flags |= QF_GETLIST_ITEMS; + } } if (flags & QF_GETLIST_TITLE) { @@ -4091,39 +4196,60 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) status = tv_dict_add_nr(retdict, S_LEN("winid"), win->handle); } } + if ((status == OK) && (flags & QF_GETLIST_ITEMS)) { + list_T *l = tv_list_alloc(kListLenMayKnow); + (void)get_errorlist(wp, qf_idx, l); + tv_dict_add_list(retdict, S_LEN("items"), l); + } + + if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) { + if (qi->qf_lists[qf_idx].qf_ctx != NULL) { + di = tv_dict_item_alloc_len(S_LEN("context")); + if (di != NULL) { + tv_copy(qi->qf_lists[qf_idx].qf_ctx, &di->di_tv); + if (tv_dict_add(retdict, di) == FAIL) { + tv_dict_item_free(di); + } + } + } else { + status = tv_dict_add_str(retdict, S_LEN("context"), ""); + } + } return status; } /// Add list of entries to quickfix/location list. Each list entry is /// a dictionary with item information. -static int qf_add_entries(qf_info_T *qi, list_T *list, char_u *title, - int action) +static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, + char_u *title, int action) { - listitem_T *li; dict_T *d; qfline_T *old_last = NULL; int retval = OK; bool did_bufnr_emsg = false; - if (action == ' ' || qi->qf_curlist == qi->qf_listcount) { + if (action == ' ' || qf_idx == qi->qf_listcount) { // make place for a new list qf_new_list(qi, title); - } else if (action == 'a' && qi->qf_lists[qi->qf_curlist].qf_count > 0) { + qf_idx = qi->qf_curlist; + } else if (action == 'a' && qi->qf_lists[qf_idx].qf_count > 0) { // Adding to existing list, use last entry. - old_last = qi->qf_lists[qi->qf_curlist].qf_last; + old_last = qi->qf_lists[qf_idx].qf_last; } else if (action == 'r') { - qf_free(qi, qi->qf_curlist); - qf_store_title(qi, title); + qf_free_items(qi, qf_idx); + qf_store_title(qi, qf_idx, title); } - for (li = list->lv_first; li != NULL; li = li->li_next) { - if (li->li_tv.v_type != VAR_DICT) - continue; /* Skip non-dict items */ + TV_LIST_ITER_CONST(list, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT) { + continue; // Skip non-dict items. + } - d = li->li_tv.vval.v_dict; - if (d == NULL) + d = TV_LIST_ITEM_TV(li)->vval.v_dict; + if (d == NULL) { continue; + } char *const filename = tv_dict_get_string(d, "filename", true); int bufnum = (int)tv_dict_get_number(d, "bufnr"); @@ -4154,7 +4280,13 @@ static int qf_add_entries(qf_info_T *qi, list_T *list, char_u *title, bufnum = 0; } + // If the 'valid' field is present it overrules the detected value. + if (tv_dict_find(d, "valid", -1) != NULL) { + valid = (int)tv_dict_get_number(d, "valid"); + } + int status = qf_add_entry(qi, + qf_idx, NULL, // dir (char_u *)filename, bufnum, @@ -4175,18 +4307,18 @@ static int qf_add_entries(qf_info_T *qi, list_T *list, char_u *title, retval = FAIL; break; } - } + }); - if (qi->qf_lists[qi->qf_curlist].qf_index == 0) { + if (qi->qf_lists[qf_idx].qf_index == 0) { // no valid entry - qi->qf_lists[qi->qf_curlist].qf_nonevalid = true; + qi->qf_lists[qf_idx].qf_nonevalid = true; } else { - qi->qf_lists[qi->qf_curlist].qf_nonevalid = false; + qi->qf_lists[qf_idx].qf_nonevalid = false; } if (action != 'a') { - qi->qf_lists[qi->qf_curlist].qf_ptr = qi->qf_lists[qi->qf_curlist].qf_start; - if (qi->qf_lists[qi->qf_curlist].qf_count > 0) { - qi->qf_lists[qi->qf_curlist].qf_index = 1; + qi->qf_lists[qf_idx].qf_ptr = qi->qf_lists[qf_idx].qf_start; + if (qi->qf_lists[qf_idx].qf_count > 0) { + qi->qf_lists[qf_idx].qf_index = 1; } } @@ -4209,14 +4341,28 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action) if ((di = tv_dict_find(what, S_LEN("nr"))) != NULL) { // Use the specified quickfix/location list if (di->di_tv.v_type == VAR_NUMBER) { - qf_idx = (int)di->di_tv.vval.v_number - 1; - if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { + // for zero use the current list + if (di->di_tv.vval.v_number != 0) { + qf_idx = (int)di->di_tv.vval.v_number - 1; + } + + if ((action == ' ' || action == 'a') && qf_idx == qi->qf_listcount) { + // When creating a new list, accept qf_idx pointing to the next + // non-available list + newlist = true; + } else if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { return FAIL; + } else { + newlist = false; // use the specified list } + } else if (di->di_tv.v_type == VAR_STRING + && strequal((const char *)di->di_tv.vval.v_string, "$") + && qi->qf_listcount > 0) { + qf_idx = qi->qf_listcount - 1; + newlist = false; } else { return FAIL; } - newlist = false; // use the specified list } if (newlist) { @@ -4235,10 +4381,85 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action) retval = OK; } } + if ((di = tv_dict_find(what, S_LEN("items"))) != NULL) { + if (di->di_tv.v_type == VAR_LIST) { + char_u *title_save = vim_strsave(qi->qf_lists[qf_idx].qf_title); + + retval = qf_add_entries(qi, qf_idx, di->di_tv.vval.v_list, + title_save, action == ' ' ? 'a' : action); + xfree(title_save); + } + } + + if ((di = tv_dict_find(what, S_LEN("context"))) != NULL) { + tv_free(qi->qf_lists[qf_idx].qf_ctx); + + typval_T *ctx = xcalloc(1, sizeof(typval_T)); + tv_copy(&di->di_tv, ctx); + qi->qf_lists[qf_idx].qf_ctx = ctx; + } return retval; } +// Find the non-location list window with the specified location list. +static win_T * find_win_with_ll(qf_info_T *qi) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if ((wp->w_llist == qi) && !bt_quickfix(wp->w_buffer)) { + return wp; + } + } + + return NULL; +} + +// Free the entire quickfix/location list stack. +// If the quickfix/location list window is open, then clear it. +static void qf_free_stack(win_T *wp, qf_info_T *qi) +{ + win_T *qfwin = qf_find_win(qi); + + if (qfwin != NULL) { + // If the quickfix/location list window is open, then clear it + if (qi->qf_curlist < qi->qf_listcount) { + qf_free(qi, qi->qf_curlist); + } + qf_update_buffer(qi, NULL); + } + + win_T *llwin = NULL; + win_T *orig_wp = wp; + if (wp != NULL && IS_LL_WINDOW(wp)) { + // If in the location list window, then use the non-location list + // window with this location list (if present) + llwin = find_win_with_ll(qi); + if (llwin != NULL) { + wp = llwin; + } + } + + qf_free_all(wp); + if (wp == NULL) { + // quickfix list + qi->qf_curlist = 0; + qi->qf_listcount = 0; + } else if (IS_LL_WINDOW(orig_wp)) { + // If the location list window is open, then create a new empty location + // list + qf_info_T *new_ll = ll_new_list(); + + // first free the list reference in the location list window + ll_free_all(&orig_wp->w_llist_ref); + + orig_wp->w_llist_ref = new_ll; + if (llwin != NULL) { + llwin->w_llist = new_ll; + new_ll->qf_refcount++; + } + } +} + // 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. @@ -4252,15 +4473,54 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title, qi = ll_get_or_alloc_list(wp); } - if (what != NULL) { + if (action == 'f') { + // Free the entire quickfix or location list stack + qf_free_stack(wp, qi); + } else if (what != NULL) { retval = qf_set_properties(qi, what, action); } else { - retval = qf_add_entries(qi, list, title, action); + retval = qf_add_entries(qi, qi->qf_curlist, list, title, action); } return retval; } +static bool mark_quickfix_ctx(qf_info_T *qi, int copyID) +{ + bool abort = false; + + for (int i = 0; i < LISTCOUNT && !abort; i++) { + typval_T *ctx = qi->qf_lists[i].qf_ctx; + if (ctx != NULL && ctx->v_type != VAR_NUMBER + && ctx->v_type != VAR_STRING && ctx->v_type != VAR_FLOAT) { + abort = set_ref_in_item(ctx, copyID, NULL, NULL); + } + } + + return abort; +} + +/// Mark the context of the quickfix list and the location lists (if present) as +/// "in use". So that garabage collection doesn't free the context. +bool set_ref_in_quickfix(int copyID) +{ + bool abort = mark_quickfix_ctx(&ql_info, copyID); + if (abort) { + return abort; + } + + FOR_ALL_TAB_WINDOWS(tp, win) { + if (win->w_llist != NULL) { + abort = mark_quickfix_ctx(win->w_llist, copyID); + if (abort) { + return abort; + } + } + } + + return abort; +} + /* * ":[range]cbuffer [bufnr]" command. * ":[range]caddbuffer [bufnr]" command. @@ -4338,7 +4598,7 @@ void ex_cbuffer(exarg_T *eap) if (qf_init_ext(qi, NULL, buf, NULL, p_efm, (eap->cmdidx != CMD_caddbuffer && eap->cmdidx != CMD_laddbuffer), - eap->line1, eap->line2, qf_title) > 0) { + eap->line1, eap->line2, qf_title, NULL) > 0) { if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name, curbuf->b_fname, true, curbuf); @@ -4399,11 +4659,11 @@ void ex_cexpr(exarg_T *eap) typval_T tv; if (eval0(eap->arg, &tv, NULL, true) != FAIL) { if ((tv.v_type == VAR_STRING && tv.vval.v_string != NULL) - || (tv.v_type == VAR_LIST && tv.vval.v_list != NULL)) { + || tv.v_type == VAR_LIST) { if (qf_init_ext(qi, NULL, NULL, &tv, p_efm, (eap->cmdidx != CMD_caddexpr && eap->cmdidx != CMD_laddexpr), - (linenr_T)0, (linenr_T)0, *eap->cmdlinep) > 0) { + (linenr_T)0, (linenr_T)0, *eap->cmdlinep, NULL) > 0) { if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name, curbuf->b_fname, true, curbuf); @@ -4474,6 +4734,9 @@ void ex_helpgrep(exarg_T *eap) } } + // Autocommands may change the list. Save it for later comparison + qf_info_T *save_qi = qi; + regmatch.regprog = vim_regcomp(eap->arg, RE_MAGIC + RE_STRING); regmatch.rm_ic = FALSE; if (regmatch.regprog != NULL) { @@ -4534,6 +4797,7 @@ void ex_helpgrep(exarg_T *eap) line[--l] = NUL; if (qf_add_entry(qi, + qi->qf_curlist, NULL, // dir fnames[fi], 0, @@ -4586,10 +4850,11 @@ void ex_helpgrep(exarg_T *eap) if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, - curbuf->b_fname, TRUE, curbuf); - if (!new_qi && qi != &ql_info && qf_find_buf(qi) == NULL) - /* autocommands made "qi" invalid */ + curbuf->b_fname, true, curbuf); + if (!new_qi && qi != save_qi && qf_find_buf(qi) == NULL) { + // autocommands made "qi" invalid return; + } } /* Jump to first match. */ diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index ae611a0005..e4de43b49e 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -232,17 +232,17 @@ #define LAST_NL NUPPER + ADD_NL #define WITH_NL(op) ((op) >= FIRST_NL && (op) <= LAST_NL) -#define MOPEN 80 /* -89 Mark this point in input as start of - * \( subexpr. MOPEN + 0 marks start of - * match. */ -#define MCLOSE 90 /* -99 Analogous to MOPEN. MCLOSE + 0 marks - * end of match. */ -#define BACKREF 100 /* -109 node Match same string again \1-\9 */ - -# define ZOPEN 110 /* -119 Mark this point in input as start of - * \z( subexpr. */ -# define ZCLOSE 120 /* -129 Analogous to ZOPEN. */ -# define ZREF 130 /* -139 node Match external submatch \z1-\z9 */ +#define MOPEN 80 // -89 Mark this point in input as start of + // \( … \) subexpr. MOPEN + 0 marks start of + // match. +#define MCLOSE 90 // -99 Analogous to MOPEN. MCLOSE + 0 marks + // end of match. +#define BACKREF 100 // -109 node Match same string again \1-\9. + +# define ZOPEN 110 // -119 Mark this point in input as start of + // \z( … \) subexpr. +# define ZCLOSE 120 // -129 Analogous to ZOPEN. +# define ZREF 130 // -139 node Match external submatch \z1-\z9 #define BRACE_COMPLEX 140 /* -149 node Match nodes between m & n times */ @@ -458,18 +458,15 @@ static int toggle_Magic(int x) /* Used for an error (down from) vim_regcomp(): give the error message, set * rc_did_emsg and return NULL */ -#define EMSG_RET_NULL(m) return (EMSG(m), rc_did_emsg = TRUE, (void *)NULL) -#define EMSG_RET_FAIL(m) return (EMSG(m), rc_did_emsg = TRUE, FAIL) -#define EMSG2_RET_NULL(m, \ - c) return (EMSG2((m), \ - (c) ? "" : "\\"), rc_did_emsg = TRUE, \ - (void *)NULL) -#define EMSG2_RET_FAIL(m, \ - c) return (EMSG2((m), \ - (c) ? "" : "\\"), rc_did_emsg = TRUE, \ - FAIL) +#define EMSG_RET_NULL(m) return (EMSG(m), rc_did_emsg = true, (void *)NULL) +#define IEMSG_RET_NULL(m) return (IEMSG(m), rc_did_emsg = true, (void *)NULL) +#define EMSG_RET_FAIL(m) return (EMSG(m), rc_did_emsg = true, FAIL) +#define EMSG2_RET_NULL(m, c) \ + return (EMSG2((m), (c) ? "" : "\\"), rc_did_emsg = true, (void *)NULL) +#define EMSG2_RET_FAIL(m, c) \ + return (EMSG2((m), (c) ? "" : "\\"), rc_did_emsg = true, FAIL) #define EMSG_ONE_RET_NULL EMSG2_RET_NULL(_( \ - "E369: invalid item in %s%%[]"), reg_magic == MAGIC_ALL) + "E369: invalid item in %s%%[]"), reg_magic == MAGIC_ALL) #define MAX_LIMIT (32767L << 16L) @@ -1891,8 +1888,8 @@ static char_u *regatom(int *flagp) case Magic(')'): if (one_exactly) EMSG_ONE_RET_NULL; - EMSG_RET_NULL(_(e_internal)); /* Supposed to be caught earlier. */ - /* NOTREACHED */ + IEMSG_RET_NULL(_(e_internal)); // Supposed to be caught earlier. + // NOTREACHED case Magic('='): case Magic('?'): @@ -4534,7 +4531,7 @@ regmatch ( brace_max[no] = OPERAND_MAX(scan); brace_count[no] = 0; } else { - EMSG(_(e_internal)); /* Shouldn't happen */ + internal_error("BRACE_LIMITS"); status = RA_FAIL; } } @@ -6477,41 +6474,35 @@ static regsubmatch_T rsm; // can only be used when can_f_submatch is true /// vim_regsub_both(). static int fill_submatch_list(int argc, typval_T *argv, int argcount) { - listitem_T *li; - int i; - char_u *s; - if (argcount == 0) { // called function doesn't take an argument return 0; } // Relies on sl_list to be the first item in staticList10_T. - init_static_list((staticList10_T *)(argv->vval.v_list)); + tv_list_init_static10((staticList10_T *)argv->vval.v_list); // There are always 10 list items in staticList10_T. - li = argv->vval.v_list->lv_first; - for (i = 0; i < 10; i++) { - s = rsm.sm_match->startp[i]; + listitem_T *li = tv_list_first(argv->vval.v_list); + for (int i = 0; i < 10; i++) { + char_u *s = rsm.sm_match->startp[i]; if (s == NULL || rsm.sm_match->endp[i] == NULL) { s = NULL; } else { s = vim_strnsave(s, (int)(rsm.sm_match->endp[i] - s)); } - li->li_tv.v_type = VAR_STRING; - li->li_tv.vval.v_string = s; - li = li->li_next; + TV_LIST_ITEM_TV(li)->v_type = VAR_STRING; + TV_LIST_ITEM_TV(li)->vval.v_string = s; + li = TV_LIST_ITEM_NEXT(argv->vval.v_list, li); } return 1; } static void clear_submatch_list(staticList10_T *sl) { - int i; - - for (i = 0; i < 10; i++) { - xfree(sl->sl_items[i].li_tv.vval.v_string); - } + TV_LIST_ITER(&sl->sl_list, li, { + xfree(TV_LIST_ITEM_TV(li)->vval.v_string); + }); } /// vim_regsub() - perform substitutions after a vim_regexec() or @@ -6645,13 +6636,12 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, typval_T argv[2]; int dummy; typval_T rettv; - staticList10_T matchList; + staticList10_T matchList = TV_LIST_STATIC10_INIT; rettv.v_type = VAR_STRING; rettv.vval.v_string = NULL; argv[0].v_type = VAR_LIST; argv[0].vval.v_list = &matchList.sl_list; - matchList.sl_list.lv_len = 0; if (expr->v_type == VAR_FUNC) { s = expr->vval.v_string; call_func(s, (int)STRLEN(s), &rettv, 1, argv, @@ -6665,7 +6655,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, fill_submatch_list, 0L, 0L, &dummy, true, partial, NULL); } - if (matchList.sl_list.lv_len > 0) { + if (tv_list_len(&matchList.sl_list) > 0) { // fill_submatch_list() was called. clear_submatch_list(&matchList); } @@ -7054,7 +7044,7 @@ list_T *reg_submatch_list(int no) colnr_T scol = rsm.sm_mmatch->startpos[no].col; colnr_T ecol = rsm.sm_mmatch->endpos[no].col; - list = tv_list_alloc(); + list = tv_list_alloc(elnum - slnum + 1); s = (const char *)reg_getline_submatch(slnum) + scol; if (slnum == elnum) { @@ -7073,7 +7063,7 @@ list_T *reg_submatch_list(int no) if (s == NULL || rsm.sm_match->endp[no] == NULL) { return NULL; } - list = tv_list_alloc(); + list = tv_list_alloc(1); tv_list_append_string(list, s, (const char *)rsm.sm_match->endp[no] - s); } diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 93ba9ce097..c520ef5fb9 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -1263,7 +1263,7 @@ static int nfa_regatom(void) rc_did_emsg = TRUE; return FAIL; } - EMSGN("INTERNAL: Unknown character class char: %" PRId64, c); + IEMSGN("INTERNAL: Unknown character class char: %" PRId64, c); return FAIL; } /* When '.' is followed by a composing char ignore the dot, so that @@ -4413,8 +4413,8 @@ static int check_char_class(int class, int c) break; default: - /* should not be here :P */ - EMSGN(_(e_ill_char_class), class); + // should not be here :P + IEMSGN(_(e_ill_char_class), class); return FAIL; } return FAIL; @@ -5992,8 +5992,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, int c = t->state->c; #ifdef REGEXP_DEBUG - if (c < 0) - EMSGN("INTERNAL: Negative state char: %" PRId64, c); + if (c < 0) { + IEMSGN("INTERNAL: Negative state char: %" PRId64, c); + } #endif result = (c == curc); @@ -6462,12 +6463,13 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags) * (and count its size). */ postfix = re2post(); if (postfix == NULL) { - /* TODO: only give this error for debugging? */ - if (post_ptr >= post_end) - EMSGN("Internal error: estimated max number " - "of states insufficient: %" PRId64, - post_end - post_start); - goto fail; /* Cascaded (syntax?) error */ + // TODO(vim): only give this error for debugging? + if (post_ptr >= post_end) { + IEMSGN("Internal error: estimated max number " + "of states insufficient: %" PRId64, + post_end - post_start); + } + goto fail; // Cascaded (syntax?) error } /* diff --git a/src/nvim/screen.c b/src/nvim/screen.c index ed96e98d32..731bb2658a 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -139,11 +139,6 @@ * doesn't fit. */ #define W_ENDCOL(wp) (wp->w_wincol + wp->w_width) -/* - * The attributes that are actually active for writing to the screen. - */ -static int screen_attr = 0; - static match_T search_hl; /* used for 'hlsearch' highlight matching */ static foldinfo_T win_foldinfo; /* info for 'foldcolumn' */ @@ -189,8 +184,6 @@ void redraw_win_later(win_T *wp, int type) void redraw_later_clear(void) { redraw_all_later(CLEAR); - /* Use attributes that is very unlikely to appear in text. */ - screen_attr = HL_BOLD | HL_UNDERLINE | HL_INVERSE; } /* @@ -307,13 +300,25 @@ void update_screen(int type) * if the screen was scrolled up when displaying a message, scroll it down */ if (msg_scrolled) { - clear_cmdline = TRUE; - if (msg_scrolled > Rows - 5) /* clearing is faster */ + clear_cmdline = true; + if (dy_flags & DY_MSGSEP) { + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + int valid = Rows - msg_scrollsize(); + if (wp->w_winrow + wp->w_height > valid) { + wp->w_redr_type = NOT_VALID; + wp->w_lines_valid = 0; + } + if (wp->w_winrow + wp->w_height + wp->w_status_height > valid) { + wp->w_redr_status = true; + } + } + } else if (msg_scrolled > Rows - 5) { // clearing is faster type = CLEAR; - else if (type != CLEAR) { - check_for_delay(FALSE); - if (screen_ins_lines(0, 0, msg_scrolled, (int)Rows, NULL) == FAIL) + } else if (type != CLEAR) { + check_for_delay(false); + if (screen_ins_lines(0, 0, msg_scrolled, (int)Rows, NULL) == FAIL) { type = CLEAR; + } FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_winrow < msg_scrolled) { if (wp->w_winrow + wp->w_height > msg_scrolled @@ -1589,6 +1594,20 @@ static void win_update(win_T *wp) got_int = save_got_int; } +/// Returns width of the signcolumn that should be used for the whole window +/// +/// @param wp window we want signcolumn width from +/// @return max width of signcolumn (cell unit) +/// +/// @note Returns a constant for now but hopefully we can improve neovim so that +/// the returned value width adapts to the maximum number of marks to draw +/// for the window +/// TODO(teto) +int win_signcol_width(win_T *wp) +{ + // 2 is vim default value + return 2; +} /* * Clear the rest of the window and mark the unused lines with "c1". use "c2" @@ -1616,7 +1635,7 @@ static void win_draw_end(win_T *wp, int c1, int c2, int row, int endrow, hlf_T h } if (signcolumn_on(wp)) { - int nn = n + 2; + int nn = n + win_signcol_width(wp); /* draw the sign column left of the fold column */ if (nn > wp->w_width) { @@ -1657,7 +1676,7 @@ static void win_draw_end(win_T *wp, int c1, int c2, int row, int endrow, hlf_T h } if (signcolumn_on(wp)) { - int nn = n + 2; + int nn = n + win_signcol_width(wp); /* draw the sign column after the fold column */ if (nn > wp->w_width) { @@ -1769,12 +1788,13 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T * text */ RL_MEMSET(col, win_hl_attr(wp, HLF_FL), wp->w_width - col); - // If signs are being displayed, add two spaces. + // If signs are being displayed, add spaces. if (signcolumn_on(wp)) { len = wp->w_width - col; if (len > 0) { - if (len > 2) { - len = 2; + int len_max = win_signcol_width(wp); + if (len > len_max) { + len = len_max; } copy_text_attr(off + col, (char_u *)" ", len, win_hl_attr(wp, HLF_FL)); @@ -2024,7 +2044,7 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T } screen_line(row + wp->w_winrow, wp->w_wincol, wp->w_width, - wp->w_width, false, wp); + wp->w_width, false, wp, 0); /* * Update w_cline_height and w_cline_folded if the cursor line was @@ -2445,10 +2465,6 @@ win_line ( line_attr = win_hl_attr(wp, HLF_QFL); } - if (wp->w_hl_attr_normal != 0) { - line_attr = hl_combine_attr(wp->w_hl_attr_normal, line_attr); - } - if (line_attr != 0) { area_highlighting = true; } @@ -2742,18 +2758,26 @@ win_line ( * buffer or when using Netbeans. */ if (signcolumn_on(wp)) { int text_sign; - /* Draw two cells with the sign value or blank. */ + // Draw cells with the sign value or blank. c_extra = ' '; char_attr = win_hl_attr(wp, HLF_SC); n_extra = 2; + n_extra = win_signcol_width(wp); if (row == startrow + filler_lines && filler_todo <= 0) { text_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_TEXT); if (text_sign != 0) { p_extra = sign_get_text(text_sign); + int symbol_blen = (int)STRLEN(p_extra); if (p_extra != NULL) { c_extra = NUL; - n_extra = (int)STRLEN(p_extra); + // symbol(s) bytes + (filling spaces) (one byte each) + n_extra = symbol_blen + + (win_signcol_width(wp) - mb_string2cells(p_extra)); + memset(extra, ' ', sizeof(extra)); + STRNCPY(extra, p_extra, STRLEN(p_extra)); + p_extra = extra; + p_extra[n_extra] = NUL; } char_attr = sign_get_attr(text_sign, FALSE); } @@ -2898,7 +2922,8 @@ win_line ( && lnum == wp->w_cursor.lnum && vcol >= (long)wp->w_virtcol && filler_todo <= 0 ) { - screen_line(screen_row, wp->w_wincol, col, -wp->w_width, wp->w_p_rl, wp); + screen_line(screen_row, wp->w_wincol, col, -wp->w_width, wp->w_p_rl, wp, + wp->w_hl_attr_normal); // Pretend we have finished updating the window. Except when // 'cursorcolumn' is set. if (wp->w_p_cuc) { @@ -3143,14 +3168,15 @@ win_line ( } --n_extra; } else { + int c0; + if (p_extra_free != NULL) { xfree(p_extra_free); p_extra_free = NULL; } - /* - * Get a character from the line itself. - */ - c = *ptr; + + // Get a character from the line itself. + c0 = c = *ptr; if (has_mbyte) { mb_c = c; if (enc_utf8) { @@ -3160,11 +3186,12 @@ win_line ( mb_utf8 = FALSE; if (mb_l > 1) { mb_c = utfc_ptr2char(ptr, u8cc); - /* Overlong encoded ASCII or ASCII with composing char - * is displayed normally, except a NUL. */ - if (mb_c < 0x80) - c = mb_c; - mb_utf8 = TRUE; + // Overlong encoded ASCII or ASCII with composing char + // is displayed normally, except a NUL. + if (mb_c < 0x80) { + c0 = c = mb_c; + } + mb_utf8 = true; /* At start of the line we can have a composing char. * Draw it as a space with a composing char. */ @@ -3358,10 +3385,11 @@ win_line ( /* Use nextline[] if possible, it has the start of the * next line concatenated. */ - if ((prev_ptr - line) - nextlinecol >= 0) - p = nextline + (prev_ptr - line) - nextlinecol; - else + if ((prev_ptr - line) - nextlinecol >= 0) { + p = nextline + ((prev_ptr - line) - nextlinecol); + } else { p = prev_ptr; + } cap_col -= (int)(prev_ptr - line); size_t tmplen = spell_check(wp, p, &spell_hlf, &cap_col, nochange); assert(tmplen <= INT_MAX); @@ -3428,10 +3456,8 @@ win_line ( char_attr = hl_combine_attr(char_attr, term_attrs[vcol]); } - /* - * Found last space before word: check for line break. - */ - if (wp->w_p_lbr && vim_isbreak(c) && !vim_isbreak(*ptr)) { + // Found last space before word: check for line break. + if (wp->w_p_lbr && c0 == c && vim_isbreak(c) && !vim_isbreak(*ptr)) { int mb_off = has_mbyte ? (*mb_head_off)(line, ptr - 1) : 0; char_u *p = ptr - (mb_off + 1); // TODO: is passing p for start of the line OK? @@ -3990,7 +4016,8 @@ win_line ( col++; } } - screen_line(screen_row, wp->w_wincol, col, wp->w_width, wp->w_p_rl, wp); + screen_line(screen_row, wp->w_wincol, col, wp->w_width, wp->w_p_rl, wp, + wp->w_hl_attr_normal); row++; /* @@ -4213,7 +4240,7 @@ win_line ( || (n_extra != 0 && (c_extra != NUL || *p_extra != NUL))) ) { screen_line(screen_row, wp->w_wincol, col - boguscols, - wp->w_width, wp->w_p_rl, wp); + wp->w_width, wp->w_p_rl, wp, wp->w_hl_attr_normal); boguscols = 0; ++row; ++screen_row; @@ -4367,7 +4394,8 @@ static int char_needs_redraw(int off_from, int off_to, int cols) && comp_char_differs(off_from, off_to)) || ((*mb_off2cells)(off_from, off_from + cols) > 1 && ScreenLines[off_from + 1] - != ScreenLines[off_to + 1]))))); + != ScreenLines[off_to + 1]))) + || p_wd < 0)); } /* @@ -4382,7 +4410,7 @@ static int char_needs_redraw(int off_from, int off_to, int cols) * When FALSE and "clear_width" > 0, clear columns "endcol" to "clear_width" */ static void screen_line(int row, int coloff, int endcol, - int clear_width, int rlflag, win_T *wp) + int clear_width, int rlflag, win_T *wp, int bg_attr) { unsigned off_from; unsigned off_to; @@ -4415,15 +4443,16 @@ static void screen_line(int row, int coloff, int endcol, /* Clear rest first, because it's left of the text. */ if (clear_width > 0) { while (col <= endcol && ScreenLines[off_to] == ' ' - && ScreenAttrs[off_to] == 0 + && ScreenAttrs[off_to] == bg_attr && (!enc_utf8 || ScreenLinesUC[off_to] == 0) ) { ++off_to; ++col; } - if (col <= endcol) - screen_fill(row, row + 1, col + coloff, - endcol + coloff + 1, ' ', ' ', 0); + if (col <= endcol) { + screen_fill(row, row + 1, col + coloff, endcol + coloff + 1, ' ', ' ', + bg_attr); + } } col = endcol + 1; off_to = LineOffset[row] + col + coloff; @@ -4431,6 +4460,13 @@ static void screen_line(int row, int coloff, int endcol, endcol = (clear_width > 0 ? clear_width : -clear_width); } + if (bg_attr) { + for (int c = col; c < endcol; c++) { + ScreenAttrs[off_from+c] = hl_combine_attr(bg_attr, + ScreenAttrs[off_from+c]); + } + } + redraw_next = char_needs_redraw(off_from, off_to, endcol - col); while (col < endcol) { @@ -4529,15 +4565,15 @@ static void screen_line(int row, int coloff, int endcol, /* blank out the rest of the line */ while (col < clear_width && ScreenLines[off_to] == ' ' - && ScreenAttrs[off_to] == 0 + && ScreenAttrs[off_to] == bg_attr && (!enc_utf8 || ScreenLinesUC[off_to] == 0) ) { ++off_to; ++col; } if (col < clear_width) { - screen_fill(row, row + 1, col + coloff, clear_width + coloff, - ' ', ' ', 0); + screen_fill(row, row + 1, col + coloff, clear_width + coloff, ' ', ' ', + bg_attr); off_to += clear_width - col; col = clear_width; } @@ -5847,30 +5883,16 @@ next_search_hl_pos( return 0; } -static void screen_start_highlight(int attr) -{ - screen_attr = attr; - ui_start_highlight(attr); -} - -static void screen_stop_highlight(void) -{ - ui_stop_highlight(); - screen_attr = 0; -} - /* * Put character ScreenLines["off"] on the screen at position "row" and "col", * using the attributes from ScreenAttrs["off"]. */ static void screen_char(unsigned off, int row, int col) { - int attr; - - /* Check for illegal values, just in case (could happen just after - * resizing). */ - if (row >= screen_Rows || col >= screen_Columns) + // Check for illegal values, just in case (could happen just after resizing). + if (row >= screen_Rows || col >= screen_Columns) { return; + } // Outputting the last character on the screen may scrollup the screen. // Don't to it! Mark the character invalid (update it when scrolled up) @@ -5882,17 +5904,8 @@ static void screen_char(unsigned off, int row, int col) return; } - /* - * Stop highlighting first, so it's easier to move the cursor. - */ - attr = ScreenAttrs[off]; - if (screen_attr != attr) - screen_stop_highlight(); - ui_cursor_goto(row, col); - - if (screen_attr != attr) - screen_start_highlight(attr); + ui_set_highlight(ScreenAttrs[off]); if (enc_utf8 && ScreenLinesUC[off] != 0) { char_u buf[MB_MAXBYTES + 1]; @@ -6001,7 +6014,7 @@ void screen_fill(int start_row, int end_row, int start_col, int end_col, int c1, ++off; if (off < end_off) { /* something to be cleared */ col = off - LineOffset[row]; - screen_stop_highlight(); + ui_clear_highlight(); ui_cursor_goto(row, col); // clear rest of this screen line ui_call_eol_clear(); col = end_col - col; @@ -6383,8 +6396,7 @@ static void screenclear2(void) return; } - screen_stop_highlight(); /* don't want highlighting here */ - + ui_clear_highlight(); // don't want highlighting here /* blank out ScreenLines */ for (i = 0; i < Rows; ++i) { @@ -6475,64 +6487,11 @@ void setcursor(void) /// Returns FAIL if the lines are not inserted, OK for success. int win_ins_lines(win_T *wp, int row, int line_count, int invalid, int mayclear) { - int did_delete; - int nextrow; - int lastrow; - int retval; - - if (invalid) - wp->w_lines_valid = 0; - - if (wp->w_height < 5) - return FAIL; - - if (line_count > wp->w_height - row) - line_count = wp->w_height - row; - - retval = win_do_lines(wp, row, line_count, mayclear, FALSE); - if (retval != MAYBE) - return retval; - - /* - * If there is a next window or a status line, we first try to delete the - * lines at the bottom to avoid messing what is after the window. - * If this fails and there are following windows, don't do anything to avoid - * messing up those windows, better just redraw. - */ - did_delete = FALSE; - if (wp->w_next != NULL || wp->w_status_height) { - if (screen_del_lines(0, wp->w_winrow + wp->w_height - line_count, - line_count, (int)Rows, NULL) == OK) - did_delete = TRUE; - else if (wp->w_next) - return FAIL; - } - /* - * if no lines deleted, blank the lines that will end up below the window - */ - if (!did_delete) { - wp->w_redr_status = TRUE; - redraw_cmdline = TRUE; - nextrow = wp->w_winrow + wp->w_height + wp->w_status_height; - lastrow = nextrow + line_count; - if (lastrow > Rows) - lastrow = Rows; - screen_fill(nextrow - line_count, lastrow - line_count, - wp->w_wincol, W_ENDCOL(wp), - ' ', ' ', 0); - } - - if (screen_ins_lines(0, wp->w_winrow + row, line_count, (int)Rows, NULL) - == FAIL) { - /* deletion will have messed up other windows */ - if (did_delete) { - wp->w_redr_status = TRUE; - win_rest_invalid(wp->w_next); - } + if (wp->w_height < 5) { return FAIL; } - return OK; + return win_do_lines(wp, row, line_count, invalid, mayclear, false); } /// Delete "line_count" window lines at "row" in window "wp". @@ -6542,47 +6501,18 @@ int win_ins_lines(win_T *wp, int row, int line_count, int invalid, int mayclear) /// Return OK for success, FAIL if the lines are not deleted. int win_del_lines(win_T *wp, int row, int line_count, int invalid, int mayclear) { - int retval; - - if (invalid) - wp->w_lines_valid = 0; - - if (line_count > wp->w_height - row) - line_count = wp->w_height - row; - - retval = win_do_lines(wp, row, line_count, mayclear, TRUE); - if (retval != MAYBE) - return retval; - - if (screen_del_lines(0, wp->w_winrow + row, line_count, - (int)Rows, NULL) == FAIL) { - return FAIL; - } - - /* - * If there are windows or status lines below, try to put them at the - * correct place. If we can't do that, they have to be redrawn. - */ - if (wp->w_next || wp->w_status_height || cmdline_row < Rows - 1) { - if (screen_ins_lines(0, wp->w_winrow + wp->w_height - line_count, - line_count, (int)Rows, NULL) == FAIL) { - wp->w_redr_status = TRUE; - win_rest_invalid(wp->w_next); - } - } - /* - * If this is the last window and there is no status line, redraw the - * command line later. - */ - else - redraw_cmdline = TRUE; - return OK; + return win_do_lines(wp, row, line_count, invalid, mayclear, true); } // Common code for win_ins_lines() and win_del_lines(). // Returns OK or FAIL when the work has been done. -static int win_do_lines(win_T *wp, int row, int line_count, int mayclear, int del) +static int win_do_lines(win_T *wp, int row, int line_count, + int invalid, int mayclear, int del) { + if (invalid) { + wp->w_lines_valid = 0; + } + if (!redrawing() || line_count <= 0) { return FAIL; } @@ -6836,16 +6766,20 @@ int showmode(void) if (p_ri) MSG_PUTS_ATTR(_(" REVERSE"), attr); MSG_PUTS_ATTR(_(" INSERT"), attr); - } else if (restart_edit == 'I') + } else if (restart_edit == 'I' || restart_edit == 'i' + || restart_edit == 'a') { MSG_PUTS_ATTR(_(" (insert)"), attr); - else if (restart_edit == 'R') + } else if (restart_edit == 'R') { MSG_PUTS_ATTR(_(" (replace)"), attr); - else if (restart_edit == 'V') + } else if (restart_edit == 'V') { MSG_PUTS_ATTR(_(" (vreplace)"), attr); - if (p_hkmap) + } + if (p_hkmap) { MSG_PUTS_ATTR(_(" Hebrew"), attr); - if (p_fkmap) + } + if (p_fkmap) { MSG_PUTS_ATTR(farsi_text_5, attr); + } if (State & LANGMAP) { if (curwin->w_p_arab) { MSG_PUTS_ATTR(_(" Arabic"), attr); @@ -7200,11 +7134,7 @@ static int fillchar_status(int *attr, win_T *wp) static int fillchar_vsep(win_T *wp, int *attr) { *attr = win_hl_attr(wp, HLF_C); - if (*attr == 0 && fill_vert == ' ') { - return '|'; - } else { - return fill_vert; - } + return fill_vert; } /* diff --git a/src/nvim/search.c b/src/nvim/search.c index 387614fd09..1943e2ca43 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -96,6 +96,9 @@ 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 int saved_no_hlsearch = 0; @@ -305,6 +308,36 @@ void free_search_patterns(void) #endif +/// Save and restore the search pattern for incremental highlight search +/// feature. +/// +/// It's similar but different from save_search_patterns() and +/// restore_search_patterns(), because the search pattern must be restored when +/// cancelling incremental searching even if it's called inside user functions. +void save_last_search_pattern(void) +{ + saved_last_search_spat = spats[RE_SEARCH]; + if (spats[RE_SEARCH].pat != NULL) { + saved_last_search_spat.pat = vim_strsave(spats[RE_SEARCH].pat); + } + saved_last_idx = last_idx; + saved_no_hlsearch = no_hlsearch; +} + +void restore_last_search_pattern(void) +{ + xfree(spats[RE_SEARCH].pat); + spats[RE_SEARCH] = saved_last_search_spat; + set_vv_searchforward(); + last_idx = saved_last_idx; + SET_NO_HLSEARCH(saved_no_hlsearch); +} + +char_u *last_search_pattern(void) +{ + return spats[RE_SEARCH].pat; +} + /* * Return TRUE when case should be ignored for search pattern "pat". * Uses the 'ignorecase' and 'smartcase' options. @@ -742,12 +775,17 @@ int searchit( } } if (ptr[matchcol] == NUL - || (nmatched = vim_regexec_multi(®match, - win, buf, lnum + matchpos.lnum, - matchcol, - tm - )) == 0) - break; + || (nmatched = vim_regexec_multi( + ®match, win, buf, lnum + matchpos.lnum, matchcol, + tm)) == 0) { + // If the search timed out, we did find a match + // but it might be the wrong one, so that's not + // OK. + if (tm != NULL && profile_passed_limit(*tm)) { + match_ok = false; + } + break; + } /* Need to get the line pointer again, a * multi-line search may have made it invalid. */ @@ -1342,13 +1380,15 @@ int searchc(cmdarg_T *cap, int t_cmd) lastc_bytelen += (*mb_char2bytes)(cap->ncharC2, lastc_bytes + lastc_bytelen); } } - } else { /* repeat previous search */ - if (*lastc == NUL) + } else { // repeat previous search + if (*lastc == NUL && lastc_bytelen == 1) { return FAIL; - if (dir) /* repeat in opposite direction */ + } + if (dir) { // repeat in opposite direction dir = -lastcdir; - else + } else { dir = lastcdir; + } t_cmd = last_t_cmd; c = *lastc; /* For multi-byte re-use last lastc_bytes[] and lastc_bytelen. */ @@ -1382,13 +1422,13 @@ int searchc(cmdarg_T *cap, int t_cmd) col -= (*mb_head_off)(p, p + col - 1) + 1; } if (lastc_bytelen == 1) { - if (p[col] == c && stop) + if (p[col] == c && stop) { break; - } else { - if (memcmp(p + col, lastc_bytes, lastc_bytelen) == 0 && stop) + } + } else if (STRNCMP(p + col, lastc_bytes, lastc_bytelen) == 0 && stop) { break; } - stop = TRUE; + stop = true; } } else { for (;; ) { @@ -2227,10 +2267,11 @@ int findsent(int dir, long count) break; found_dot = TRUE; } - if (decl(&pos) == -1) + if (decl(&pos) == -1) { break; - /* when going forward: Stop in front of empty line */ - if (lineempty(pos.lnum) && dir == FORWARD) { + } + // when going forward: Stop in front of empty line + if (LINEEMPTY(pos.lnum) && dir == FORWARD) { incl(&pos); goto found; } @@ -2534,10 +2575,12 @@ int bck_word(long count, int bigword, int stop) */ while (cls() == 0) { if (curwin->w_cursor.col == 0 - && lineempty(curwin->w_cursor.lnum)) + && LINEEMPTY(curwin->w_cursor.lnum)) { goto finished; - if (dec_cursor() == -1) /* hit start of file, stop here */ + } + if (dec_cursor() == -1) { // hit start of file, stop here return OK; + } } /* @@ -2601,10 +2644,12 @@ int end_word(long count, int bigword, int stop, int empty) */ while (cls() == 0) { if (empty && curwin->w_cursor.col == 0 - && lineempty(curwin->w_cursor.lnum)) + && LINEEMPTY(curwin->w_cursor.lnum)) { goto finished; - if (inc_cursor() == -1) /* hit end of file, stop here */ + } + if (inc_cursor() == -1) { // hit end of file, stop here return FAIL; + } } /* @@ -2657,10 +2702,12 @@ bckend_word ( * Move backward to end of the previous word */ while (cls() == 0) { - if (curwin->w_cursor.col == 0 && lineempty(curwin->w_cursor.lnum)) + if (curwin->w_cursor.col == 0 && LINEEMPTY(curwin->w_cursor.lnum)) { break; - if ((i = dec_cursor()) == -1 || (eol && i == 1)) + } + if ((i = dec_cursor()) == -1 || (eol && i == 1)) { return OK; + } } } return OK; @@ -3667,11 +3714,25 @@ current_quote ( int selected_quote = FALSE; /* Has quote inside selection */ int i; - /* Correct cursor when 'selection' is exclusive */ + // Correct cursor when 'selection' is "exclusive". if (VIsual_active) { + // this only works within one line + if (VIsual.lnum != curwin->w_cursor.lnum) { + return false; + } + vis_bef_curs = lt(VIsual, curwin->w_cursor); - if (*p_sel == 'e' && vis_bef_curs) + if (*p_sel == 'e') { + if (!vis_bef_curs) { + // VIsual needs to be start of Visual selection. + pos_T t = curwin->w_cursor; + + curwin->w_cursor = VIsual; + VIsual = t; + vis_bef_curs = true; + } dec_cursor(); + } vis_empty = equalpos(VIsual, curwin->w_cursor); } diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 736d6bf162..15ac28e1bf 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -16,12 +16,14 @@ #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/vim.h" +#include "nvim/pos.h" #include "nvim/ascii.h" #include "nvim/shada.h" #include "nvim/message.h" #include "nvim/globals.h" #include "nvim/memory.h" #include "nvim/mark.h" +#include "nvim/macros.h" #include "nvim/ops.h" #include "nvim/garray.h" #include "nvim/option.h" @@ -76,8 +78,8 @@ KHASH_SET_INIT_STR(strset) (vim_rename((char_u *)a, (char_u *)b)) #define mb_strnicmp(a, b, c) \ (mb_strnicmp((char_u *)a, (char_u *)b, c)) -#define path_shorten_fname_if_possible(b) \ - ((char *)path_shorten_fname_if_possible((char_u *)b)) +#define path_try_shorten_fname(b) \ + ((char *)path_try_shorten_fname((char_u *)b)) #define buflist_new(ffname, sfname, ...) \ (buflist_new((char_u *)ffname, (char_u *)sfname, __VA_ARGS__)) #define os_isdir(f) (os_isdir((char_u *) f)) @@ -375,7 +377,8 @@ KHASH_MAP_INIT_STR(file_marks, FileMarks) /// Before actually writing most of the data is read to this structure. typedef struct { HistoryMergerState hms[HIST_COUNT]; ///< Structures for history merging. - PossiblyFreedShadaEntry global_marks[NGLOBALMARKS]; ///< All global marks. + PossiblyFreedShadaEntry global_marks[NMARKS]; ///< Named global marks. + PossiblyFreedShadaEntry numbered_marks[EXTRA_MARKS]; ///< Numbered marks. PossiblyFreedShadaEntry registers[NUM_SAVED_REGISTERS]; ///< All registers. PossiblyFreedShadaEntry jumps[JUMPLISTSIZE]; ///< All dumped jumps. size_t jumps_size; ///< Number of jumps occupied. @@ -1180,8 +1183,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) list_T *oldfiles_list = get_vim_var_list(VV_OLDFILES); const bool force = flags & kShaDaForceit; const bool get_old_files = (flags & (kShaDaGetOldfiles | kShaDaForceit) - && (force || oldfiles_list == NULL - || oldfiles_list->lv_len == 0)); + && (force || tv_list_len(oldfiles_list) == 0)); const bool want_marks = flags & kShaDaWantMarks; const unsigned srni_flags = (unsigned) ( (flags & kShaDaWantInfo @@ -1218,7 +1220,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) khash_t(fnamebufs) fname_bufs = KHASH_EMPTY_TABLE(fnamebufs); khash_t(strset) oldfiles_set = KHASH_EMPTY_TABLE(strset); if (get_old_files && (oldfiles_list == NULL || force)) { - oldfiles_list = tv_list_alloc(); + oldfiles_list = tv_list_alloc(kListLenUnknown); set_vim_var_list(VV_OLDFILES, oldfiles_list); } ShaDaReadResult srni_ret; @@ -1398,7 +1400,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) } case kSDItemBufferList: { for (size_t i = 0; i < cur_entry.data.buffer_list.size; i++) { - char *const sfname = path_shorten_fname_if_possible( + char *const sfname = path_try_shorten_fname( cur_entry.data.buffer_list.buffers[i].fname); buf_T *const buf = buflist_new( cur_entry.data.buffer_list.buffers[i].fname, sfname, 0, @@ -1599,13 +1601,13 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, #define DUMP_ADDITIONAL_ELEMENTS(src, what) \ do { \ if ((src) != NULL) { \ - for (listitem_T *li = (src)->lv_first; li != NULL; li = li->li_next) { \ - if (encode_vim_to_msgpack(spacker, &li->li_tv, \ + TV_LIST_ITER((src), li, { \ + if (encode_vim_to_msgpack(spacker, TV_LIST_ITEM_TV(li), \ _("additional elements of ShaDa " what)) \ == FAIL) { \ goto shada_pack_entry_error; \ } \ - } \ + }); \ } \ } while (0) #define DUMP_ADDITIONAL_DATA(src, what) \ @@ -1647,25 +1649,21 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, case kSDItemHistoryEntry: { const bool is_hist_search = entry.data.history_item.histtype == HIST_SEARCH; - const size_t arr_size = 2 + (size_t) is_hist_search + (size_t) ( - entry.data.history_item.additional_elements == NULL - ? 0 - : entry.data.history_item.additional_elements->lv_len); + const size_t arr_size = 2 + (size_t)is_hist_search + (size_t)( + tv_list_len(entry.data.history_item.additional_elements)); msgpack_pack_array(spacker, arr_size); msgpack_pack_uint8(spacker, entry.data.history_item.histtype); PACK_BIN(cstr_as_string(entry.data.history_item.string)); if (is_hist_search) { - msgpack_pack_uint8(spacker, (uint8_t) entry.data.history_item.sep); + msgpack_pack_uint8(spacker, (uint8_t)entry.data.history_item.sep); } DUMP_ADDITIONAL_ELEMENTS(entry.data.history_item.additional_elements, "history entry item"); break; } case kSDItemVariable: { - const size_t arr_size = 2 + (size_t) ( - entry.data.global_var.additional_elements == NULL - ? 0 - : entry.data.global_var.additional_elements->lv_len); + const size_t arr_size = 2 + (size_t)( + tv_list_len(entry.data.global_var.additional_elements)); msgpack_pack_array(spacker, arr_size); const String varname = cstr_as_string(entry.data.global_var.name); PACK_BIN(varname); @@ -1684,10 +1682,8 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, break; } case kSDItemSubString: { - const size_t arr_size = 1 + (size_t) ( - entry.data.sub_string.additional_elements == NULL - ? 0 - : entry.data.sub_string.additional_elements->lv_len); + const size_t arr_size = 1 + (size_t)( + tv_list_len(entry.data.sub_string.additional_elements)); msgpack_pack_array(spacker, arr_size); PACK_BIN(cstr_as_string(entry.data.sub_string.sub)); DUMP_ADDITIONAL_ELEMENTS(entry.data.sub_string.additional_elements, @@ -2027,6 +2023,113 @@ shada_parse_msgpack_extra_bytes: return ret; } +/// Format shada entry for debugging purposes +/// +/// @param[in] entry ShaDa entry to format. +/// +/// @return string representing ShaDa entry in a static buffer. +static const char *shada_format_entry(const ShadaEntry entry) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_UNUSED FUNC_ATTR_NONNULL_RET +{ + static char ret[1024]; + ret[0] = 0; + vim_snprintf(S_LEN(ret), "[ ] ts=%" PRIu64 " "); + // ^ Space for `can_free_entry` + switch (entry.type) { + case kSDItemMissing: { + vim_snprintf_add(S_LEN(ret), "Missing"); + break; + } + case kSDItemHeader: { + vim_snprintf_add(S_LEN(ret), "Header { TODO }"); + break; + } + case kSDItemBufferList: { + vim_snprintf_add(S_LEN(ret), "BufferList { TODO }"); + break; + } + case kSDItemUnknown: { + vim_snprintf_add(S_LEN(ret), "Unknown { TODO }"); + break; + } + case kSDItemSearchPattern: { + vim_snprintf_add(S_LEN(ret), "SearchPattern { TODO }"); + break; + } + case kSDItemSubString: { + vim_snprintf_add(S_LEN(ret), "SubString { TODO }"); + break; + } + case kSDItemHistoryEntry: { + vim_snprintf_add(S_LEN(ret), "HistoryEntry { TODO }"); + break; + } + case kSDItemRegister: { + vim_snprintf_add(S_LEN(ret), "Register { TODO }"); + break; + } + case kSDItemVariable: { + vim_snprintf_add(S_LEN(ret), "Variable { TODO }"); + break; + } +#define FORMAT_MARK_ENTRY(entry_name, name_fmt, name_fmt_arg) \ + do { \ + typval_T ad_tv = { \ + .v_type = VAR_DICT, \ + .vval.v_dict = entry.data.filemark.additional_data \ + }; \ + size_t ad_len; \ + char *const ad = encode_tv2string(&ad_tv, &ad_len); \ + vim_snprintf_add( \ + S_LEN(ret), \ + entry_name " {" name_fmt " file=[%zu]\"%.512s\", " \ + "pos={l=%" PRIdLINENR ",c=%" PRIdCOLNR ",a=%" PRIdCOLNR "}, " \ + "ad={%p:[%zu]%.64s} }", \ + name_fmt_arg, \ + strlen(entry.data.filemark.fname), \ + entry.data.filemark.fname, \ + entry.data.filemark.mark.lnum, \ + entry.data.filemark.mark.col, \ + entry.data.filemark.mark.coladd, \ + entry.data.filemark.additional_data, \ + ad_len, \ + ad); \ + } while (0) + case kSDItemGlobalMark: { + FORMAT_MARK_ENTRY("GlobalMark", " name='%c',", entry.data.filemark.name); + break; + } + case kSDItemChange: { + FORMAT_MARK_ENTRY("Change", "%s", ""); + break; + } + case kSDItemLocalMark: { + FORMAT_MARK_ENTRY("LocalMark", " name='%c',", entry.data.filemark.name); + break; + } + case kSDItemJump: { + FORMAT_MARK_ENTRY("Jump", "%s", ""); + break; + } +#undef FORMAT_MARK_ENTRY + } + return ret; +} + +/// Format possibly freed shada entry for debugging purposes +/// +/// @param[in] entry ShaDa entry to format. +/// +/// @return string representing ShaDa entry in a static buffer. +static const char *shada_format_pfreed_entry( + const PossiblyFreedShadaEntry pfs_entry) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_UNUSED FUNC_ATTR_NONNULL_RET +{ + char *ret = (char *)shada_format_entry(pfs_entry.data); + ret[1] = (pfs_entry.can_free_entry ? 'T' : 'F'); + return ret; +} + /// Read and merge in ShaDa file, used when writing /// /// @param[in] sd_reader Structure containing file reader definition. @@ -2078,9 +2181,12 @@ static inline ShaDaWriteResult shada_read_when_writing( shada_free_shada_entry(&wms_entry->data); \ } \ } \ - wms_entry->can_free_entry = true; \ - wms_entry->data = (entry); \ + *wms_entry = pfs_entry; \ } while (0) + const PossiblyFreedShadaEntry pfs_entry = { + .can_free_entry = true, + .data = entry, + }; switch (entry.type) { case kSDItemMissing: { break; @@ -2132,13 +2238,49 @@ static inline ShaDaWriteResult shada_read_when_writing( break; } case kSDItemGlobalMark: { - const int idx = mark_global_index(entry.data.filemark.name); - if (idx < 0) { - ret = shada_pack_entry(packer, entry, 0); - shada_free_shada_entry(&entry); - break; + if (ascii_isdigit(entry.data.filemark.name)) { + bool processed_mark = false; + // Completely ignore numbered mark names, make a list sorted by + // timestamp. + for (size_t i = ARRAY_SIZE(wms->numbered_marks); i > 0; i--) { + ShadaEntry wms_entry = wms->numbered_marks[i - 1].data; + if (wms_entry.type != kSDItemGlobalMark) { + continue; + } + // Ignore duplicates. + if (wms_entry.timestamp == entry.timestamp + && (wms_entry.data.filemark.additional_data == NULL + && entry.data.filemark.additional_data == NULL) + && marks_equal(wms_entry.data.filemark.mark, + entry.data.filemark.mark) + && strcmp(wms_entry.data.filemark.fname, + entry.data.filemark.fname) == 0) { + shada_free_shada_entry(&entry); + processed_mark = true; + break; + } + if (wms_entry.timestamp >= entry.timestamp) { + processed_mark = true; + if (i < ARRAY_SIZE(wms->numbered_marks)) { + replace_numbered_mark(wms, i, pfs_entry); + } else { + shada_free_shada_entry(&entry); + } + break; + } + } + if (!processed_mark) { + replace_numbered_mark(wms, 0, pfs_entry); + } + } else { + const int idx = mark_global_index(entry.data.filemark.name); + if (idx < 0) { + ret = shada_pack_entry(packer, entry, 0); + shada_free_shada_entry(&entry); + break; + } + COMPARE_WITH_ENTRY(&wms->global_marks[idx], entry); } - COMPARE_WITH_ENTRY(&wms->global_marks[idx], entry); break; } case kSDItemChange: @@ -2182,8 +2324,7 @@ static inline ShaDaWriteResult shada_read_when_writing( shada_free_shada_entry(&wms_entry->data); } } - wms_entry->can_free_entry = true; - wms_entry->data = entry; + *wms_entry = pfs_entry; } } else { #define FREE_POSSIBLY_FREED_SHADA_ENTRY(entry) \ @@ -2223,6 +2364,20 @@ static inline ShaDaWriteResult shada_read_when_writing( return ret; } +/// Check whether buffer should be ignored +/// +/// @param[in] buf buf_T* to check. +/// @param[in] removable_bufs Cache of buffers ignored due to their location. +/// +/// @return true or false. +static inline bool ignore_buf(const buf_T *const buf, + khash_t(bufset) *const removable_bufs) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE +{ + return (buf->b_ffname == NULL || !buf->b_p_bl || bt_quickfix(buf) \ + || in_bufset(removable_bufs, buf)); +} + /// Get list of buffers to write to the shada file /// /// @param[in] removable_bufs Buffers which are ignored @@ -2234,11 +2389,9 @@ static inline ShadaEntry shada_get_buflist( { int max_bufs = get_shada_parameter('%'); size_t buf_count = 0; -#define IGNORE_BUF(buf)\ - (buf->b_ffname == NULL || !buf->b_p_bl || bt_quickfix(buf) \ - || in_bufset(removable_bufs, buf)) // NOLINT(whitespace/indent) FOR_ALL_BUFFERS(buf) { - if (!IGNORE_BUF(buf) && (max_bufs < 0 || buf_count < (size_t)max_bufs)) { + if (!ignore_buf(buf, removable_bufs) + && (max_bufs < 0 || buf_count < (size_t)max_bufs)) { buf_count++; } } @@ -2256,7 +2409,7 @@ static inline ShadaEntry shada_get_buflist( }; size_t i = 0; FOR_ALL_BUFFERS(buf) { - if (IGNORE_BUF(buf)) { + if (ignore_buf(buf, removable_bufs)) { continue; } if (i >= buf_count) { @@ -2270,7 +2423,6 @@ static inline ShadaEntry shada_get_buflist( i++; } -#undef IGNORE_BUF return buflist_entry; } @@ -2372,6 +2524,34 @@ static inline void shada_initialize_registers(WriteMergerState *const wms, } while (reg_iter != NULL); } +/// Replace numbered mark in WriteMergerState +/// +/// Frees the last mark, moves (including adjusting mark names) marks from idx +/// to the last-but-one one and saves the new mark at given index. +/// +/// @param[out] wms Merger state to adjust. +/// @param[in] idx Index at which new mark should be placed. +/// @param[in] entry New mark. +static inline void replace_numbered_mark(WriteMergerState *const wms, + const size_t idx, + const PossiblyFreedShadaEntry entry) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE +{ + if (ARRAY_LAST_ENTRY(wms->numbered_marks).can_free_entry) { + shada_free_shada_entry(&ARRAY_LAST_ENTRY(wms->numbered_marks).data); + } + for (size_t i = idx; i < ARRAY_SIZE(wms->numbered_marks) - 1; i++) { + if (wms->numbered_marks[i].data.type == kSDItemGlobalMark) { + wms->numbered_marks[i].data.data.filemark.name = (char)('0' + (int)i + 1); + } + } + memmove(wms->numbered_marks + idx + 1, wms->numbered_marks + idx, + sizeof(wms->numbered_marks[0]) + * (ARRAY_SIZE(wms->numbered_marks) - 1 - idx)); + wms->numbered_marks[idx] = entry; + wms->numbered_marks[idx].data.data.filemark.name = (char)('0' + (int)idx); +} + /// Write ShaDa file /// /// @param[in] sd_writer Structure containing file writer definition. @@ -2564,6 +2744,12 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, xfmark_T fm; jump_iter = mark_jumplist_iter(jump_iter, curwin, &fm); + if (fm.fmark.mark.lnum == 0) { + iemsgf("ShaDa: mark lnum zero (ji:%p, js:%p, len:%i)", + (void *)jump_iter, (void *)&curwin->w_jumplist[0], + curwin->w_jumplistlen); + continue; + } const buf_T *const buf = (fm.fmark.fnum == 0 ? NULL : buflist_findnr(fm.fmark.fnum)); @@ -2598,6 +2784,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, // Initialize global marks if (dump_global_marks) { const void *global_mark_iter = NULL; + size_t digit_mark_idx = 0; do { char name = NUL; xfmark_T fm; @@ -2620,7 +2807,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, } fname = (const char *) buf->b_ffname; } - wms->global_marks[mark_global_index(name)] = (PossiblyFreedShadaEntry) { + const PossiblyFreedShadaEntry pf_entry = { .can_free_entry = false, .data = { .type = kSDItemGlobalMark, @@ -2630,11 +2817,16 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, .mark = fm.fmark.mark, .name = name, .additional_data = fm.fmark.additional_data, - .fname = (char *) fname, + .fname = (char *)fname, } } }, }; + if (ascii_isdigit(name)) { + replace_numbered_mark(wms, digit_mark_idx++, pf_entry); + } else { + wms->global_marks[mark_global_index(name)] = pf_entry; + } } while (global_mark_iter != NULL); } @@ -2716,6 +2908,26 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, } } + // Update numbered marks: '0' should be replaced with the current position, + // '9' should be removed and all other marks shifted. + if (!ignore_buf(curbuf, &removable_bufs) && curwin->w_cursor.lnum != 0) { + replace_numbered_mark(wms, 0, (PossiblyFreedShadaEntry) { + .can_free_entry = false, + .data = { + .type = kSDItemGlobalMark, + .timestamp = os_time(), + .data = { + .filemark = { + .mark = curwin->w_cursor, + .name = '0', + .additional_data = NULL, + .fname = (char *)curbuf->b_ffname, + } + } + }, + }); + } + // Write the rest #define PACK_WMS_ARRAY(wms_array) \ do { \ @@ -2730,6 +2942,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, } \ } while (0) PACK_WMS_ARRAY(wms->global_marks); + PACK_WMS_ARRAY(wms->numbered_marks); PACK_WMS_ARRAY(wms->registers); for (size_t i = 0; i < wms->jumps_size; i++) { if (shada_pack_pfreed_entry(packer, wms->jumps[i], max_kbyte) @@ -2824,6 +3037,7 @@ shada_write_exit: return ret; } +#undef IGNORE_BUF #undef PACK_STATIC_STR /// Write ShaDa file to a given location @@ -2993,7 +3207,7 @@ shada_write_file_nomerge: {} } else { if (sw_ret == kSDWriteReadNotShada) { EMSG3(_(RNERR "Did not rename %s because %s " - "does not looks like a ShaDa file"), tempname, fname); + "does not look like a ShaDa file"), tempname, fname); } else { EMSG3(_(RNERR "Did not rename %s to %s because there were errors " "during writing it"), tempname, fname); diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 42c9bcc0ee..34eb2fdf1b 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -1484,21 +1484,23 @@ spell_move_to ( return found_len; } - if (curline) + if (curline) { break; // only check cursor line + } + + // If we are back at the starting line and searched it again there + // is no match, give up. + if (lnum == wp->w_cursor.lnum && wrapped) { + break; + } // Advance to next line. if (dir == BACKWARD) { - // If we are back at the starting line and searched it again there - // is no match, give up. - if (lnum == wp->w_cursor.lnum && wrapped) - break; - - if (lnum > 1) - --lnum; - else if (!p_ws) + if (lnum > 1) { + lnum--; + } else if (!p_ws) { break; // at first line and 'nowrapscan' - else { + } else { // Wrap around to the end of the buffer. May search the // starting line again and accept the last match. lnum = wp->w_buffer->b_ml.ml_line_count; @@ -1523,8 +1525,9 @@ spell_move_to ( // If we are back at the starting line and there is no match then // give up. - if (lnum == wp->w_cursor.lnum && (!found_one || wrapped)) + if (lnum == wp->w_cursor.lnum && !found_one) { break; + } // Skip the characters at the start of the next line that were // included in a match crossing line boundaries. @@ -2565,7 +2568,7 @@ static bool spell_iswordp(char_u *p, win_T *wp) int c; if (has_mbyte) { - l = MB_BYTE2LEN(*p); + l = MB_PTR2LEN(p); s = p; if (l == 1) { // be quick for ASCII @@ -3136,8 +3139,13 @@ spell_find_suggest ( if (su->su_badlen >= MAXWLEN) su->su_badlen = MAXWLEN - 1; // just in case STRLCPY(su->su_badword, su->su_badptr, su->su_badlen + 1); - (void)spell_casefold(su->su_badptr, su->su_badlen, - su->su_fbadword, MAXWLEN); + (void)spell_casefold(su->su_badptr, su->su_badlen, su->su_fbadword, MAXWLEN); + + // TODO(vim): make this work if the case-folded text is longer than the + // original text. Currently an illegal byte causes wrong pointer + // computations. + su->su_fbadword[su->su_badlen] = NUL; + // get caps flags for bad word su->su_badflags = badword_captype(su->su_badptr, su->su_badptr + su->su_badlen); @@ -3213,26 +3221,25 @@ spell_find_suggest ( // Find suggestions by evaluating expression "expr". static void spell_suggest_expr(suginfo_T *su, char_u *expr) { - list_T *list; - listitem_T *li; int score; const char *p; // The work is split up in a few parts to avoid having to export // suginfo_T. // First evaluate the expression and get the resulting list. - list = eval_spell_expr(su->su_badword, expr); + list_T *const list = eval_spell_expr(su->su_badword, expr); if (list != NULL) { // Loop over the items in the list. - for (li = list->lv_first; li != NULL; li = li->li_next) - if (li->li_tv.v_type == VAR_LIST) { + TV_LIST_ITER(list, li, { + if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) { // Get the word and the score from the items. - score = get_spellword(li->li_tv.vval.v_list, &p); + score = get_spellword(TV_LIST_ITEM_TV(li)->vval.v_list, &p); if (score >= 0 && score <= su->su_maxscore) { add_suggestion(su, &su->su_ga, (const char_u *)p, su->su_badlen, score, 0, true, su->su_sallang, false); } } + }); tv_list_unref(list); } @@ -4108,10 +4115,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so && goodword_ends) { int l; - if (has_mbyte) - l = MB_BYTE2LEN(fword[sp->ts_fidx]); - else - l = 1; + l = MB_PTR2LEN(fword + sp->ts_fidx); if (fword_ends) { // Copy the skipped character to preword. memmove(preword + sp->ts_prewordlen, @@ -4257,8 +4261,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Correct ts_fidx for the byte length of the // character (we didn't check that before). sp->ts_fidx = sp->ts_fcharstart - + MB_BYTE2LEN( - fword[sp->ts_fcharstart]); + + MB_PTR2LEN(fword + sp->ts_fcharstart); // For changing a composing character adjust // the score from SCORE_SUBST to @@ -4364,11 +4367,12 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // results. if (has_mbyte) { c = mb_ptr2char(fword + sp->ts_fidx); - stack[depth].ts_fidx += MB_BYTE2LEN(fword[sp->ts_fidx]); - if (enc_utf8 && utf_iscomposing(c)) + stack[depth].ts_fidx += MB_PTR2LEN(fword + sp->ts_fidx); + if (enc_utf8 && utf_iscomposing(c)) { stack[depth].ts_score -= SCORE_DEL - SCORE_DELCOMP; - else if (c == mb_ptr2char(fword + stack[depth].ts_fidx)) + } else if (c == mb_ptr2char(fword + stack[depth].ts_fidx)) { stack[depth].ts_score -= SCORE_DEL - SCORE_DELDUP; + } } else { ++stack[depth].ts_fidx; if (fword[sp->ts_fidx] == fword[sp->ts_fidx + 1]) @@ -4550,9 +4554,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Undo the STATE_SWAP swap: "21" -> "12". p = fword + sp->ts_fidx; if (has_mbyte) { - n = MB_BYTE2LEN(*p); + n = MB_PTR2LEN(p); c = mb_ptr2char(p + n); - memmove(p + MB_BYTE2LEN(p[n]), p, n); + memmove(p + MB_PTR2LEN(p + n), p, n); mb_char2bytes(c, p); } else { c = *p; @@ -4625,11 +4629,11 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Undo STATE_SWAP3: "321" -> "123" p = fword + sp->ts_fidx; if (has_mbyte) { - n = MB_BYTE2LEN(*p); + n = MB_PTR2LEN(p); c2 = mb_ptr2char(p + n); - fl = MB_BYTE2LEN(p[n]); + fl = MB_PTR2LEN(p + n); c = mb_ptr2char(p + n + fl); - tl = MB_BYTE2LEN(p[n + fl]); + tl = MB_PTR2LEN(p + n + fl); memmove(p + fl + tl, p, n); mb_char2bytes(c, p); mb_char2bytes(c2, p + tl); @@ -4688,10 +4692,10 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Undo ROT3L: "231" -> "123" p = fword + sp->ts_fidx; if (has_mbyte) { - n = MB_BYTE2LEN(*p); - n += MB_BYTE2LEN(p[n]); + n = MB_PTR2LEN(p); + n += MB_PTR2LEN(p + n); c = mb_ptr2char(p + n); - tl = MB_BYTE2LEN(p[n]); + tl = MB_PTR2LEN(p + n); memmove(p + tl, p, n); mb_char2bytes(c, p); } else { @@ -4741,9 +4745,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so p = fword + sp->ts_fidx; if (has_mbyte) { c = mb_ptr2char(p); - tl = MB_BYTE2LEN(*p); - n = MB_BYTE2LEN(p[tl]); - n += MB_BYTE2LEN(p[tl + n]); + tl = MB_PTR2LEN(p); + n = MB_PTR2LEN(p + tl); + n += MB_PTR2LEN(p + tl + n); memmove(p, p + tl, n); mb_char2bytes(c, p + n); } else { @@ -5355,7 +5359,7 @@ add_sound_suggest ( // Find the word nr in the soundfold tree. sfwordnr = soundfold_find(slang, goodword); if (sfwordnr < 0) { - EMSG2(_(e_intern2), "add_sound_suggest()"); + internal_error("add_sound_suggest()"); return; } @@ -7143,7 +7147,7 @@ void ex_spelldump(exarg_T *eap) set_option_value("spl", dummy, (char *)spl, OPT_LOCAL); xfree(spl); - if (!bufempty()) { + if (!BUFEMPTY()) { return; } diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 1f7f616782..f5d5d408a1 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -228,7 +228,6 @@ #include <stdio.h> #include <stdint.h> #include <wctype.h> -#include <strings.h> #include "nvim/vim.h" #include "nvim/spell_defs.h" @@ -3656,7 +3655,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) flags |= WF_REGION; l = *p - '0'; - if (l > spin->si_region_count) { + if (l == 0 || l > spin->si_region_count) { smsg(_("Invalid region nr in %s line %d: %s"), fname, lnum, p); break; diff --git a/src/nvim/strings.c b/src/nvim/strings.c index 687f734742..e3f6a8cbf6 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -50,6 +50,13 @@ #include "nvim/os/shell.h" #include "nvim/eval/encode.h" +#ifdef __MINGW32__ +# undef fpclassify +# define fpclassify __fpclassify +# undef isnan +# define isnan _isnan +#endif + /* * Copy "string" into newly allocated memory. */ diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index bc7362af72..2613c09c19 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -52,6 +52,7 @@ static bool did_syntax_onoff = false; struct hl_group { char_u *sg_name; ///< highlight group name char_u *sg_name_u; ///< uppercase of sg_name + int sg_cleared; ///< "hi clear" was used int sg_attr; ///< Screen attr @see ATTR_ENTRY int sg_link; ///< link to this highlight group ID int sg_set; ///< combination of flags in \ref SG_SET @@ -63,7 +64,7 @@ struct hl_group { int sg_cterm_bold; ///< bold attr was set for light color // for RGB UIs int sg_gui; ///< "gui=" highlighting attributes - ///< (combination of \ref HL_ATTRIBUTES) + ///< (combination of \ref HlAttrFlags) RgbValue sg_rgb_fg; ///< RGB foreground color RgbValue sg_rgb_bg; ///< RGB background color RgbValue sg_rgb_sp; ///< RGB special color @@ -880,7 +881,8 @@ static void syn_start_line(void) } next_match_idx = -1; - ++current_line_id; + current_line_id++; + next_seqnr = 1; } /* @@ -1598,6 +1600,7 @@ get_syntax_attr ( current_id = 0; current_trans_id = 0; current_flags = 0; + current_seqnr = 0; return 0; } @@ -2041,6 +2044,7 @@ syn_current_attr ( current_id = 0; current_trans_id = 0; current_flags = 0; + current_seqnr = 0; if (cur_si != NULL) { for (int idx = current_state.ga_len - 1; idx >= 0; --idx) { sip = &CUR_STATE(idx); @@ -2129,9 +2133,11 @@ syn_current_attr ( /* nextgroup ends at end of line, unless "skipnl" or "skipempty" present */ if (current_next_list != NULL - && syn_getcurline()[current_col + 1] == NUL - && !(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY))) + && (line = syn_getcurline())[current_col] != NUL + && line[current_col + 1] == NUL + && !(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY))) { current_next_list = NULL; + } if (!GA_EMPTY(&zero_width_next_ga)) ga_clear(&zero_width_next_ga); @@ -3019,12 +3025,19 @@ static void syn_cmd_conceal(exarg_T *eap, int syncing) return; next = skiptowhite(arg); - if (STRNICMP(arg, "on", 2) == 0 && next - arg == 2) - curwin->w_s->b_syn_conceal = TRUE; - else if (STRNICMP(arg, "off", 3) == 0 && next - arg == 3) - curwin->w_s->b_syn_conceal = FALSE; - else + if (*arg == NUL) { + if (curwin->w_s->b_syn_conceal) { + MSG(_("syn conceal on")); + } else { + MSG(_("syn conceal off")); + } + } else if (STRNICMP(arg, "on", 2) == 0 && next - arg == 2) { + curwin->w_s->b_syn_conceal = true; + } else if (STRNICMP(arg, "off", 3) == 0 && next - arg == 3) { + curwin->w_s->b_syn_conceal = false; + } else { EMSG2(_("E390: Illegal argument: %s"), arg); + } } /* @@ -3040,12 +3053,19 @@ static void syn_cmd_case(exarg_T *eap, int syncing) return; next = skiptowhite(arg); - if (STRNICMP(arg, "match", 5) == 0 && next - arg == 5) - curwin->w_s->b_syn_ic = FALSE; - else if (STRNICMP(arg, "ignore", 6) == 0 && next - arg == 6) - curwin->w_s->b_syn_ic = TRUE; - else + if (*arg == NUL) { + if (curwin->w_s->b_syn_ic) { + MSG(_("syntax case ignore")); + } else { + MSG(_("syntax case match")); + } + } else if (STRNICMP(arg, "match", 5) == 0 && next - arg == 5) { + curwin->w_s->b_syn_ic = false; + } else if (STRNICMP(arg, "ignore", 6) == 0 && next - arg == 6) { + curwin->w_s->b_syn_ic = true; + } else { EMSG2(_("E390: Illegal argument: %s"), arg); + } } /* @@ -3061,7 +3081,15 @@ static void syn_cmd_spell(exarg_T *eap, int syncing) return; next = skiptowhite(arg); - if (STRNICMP(arg, "toplevel", 8) == 0 && next - arg == 8) { + if (*arg == NUL) { + if (curwin->w_s->b_syn_spell == SYNSPL_TOP) { + MSG(_("syntax spell toplevel")); + } else if (curwin->w_s->b_syn_spell == SYNSPL_NOTOP) { + MSG(_("syntax spell notoplevel")); + } else { + MSG(_("syntax spell default")); + } + } else if (STRNICMP(arg, "toplevel", 8) == 0 && next - arg == 8) { curwin->w_s->b_syn_spell = SYNSPL_TOP; } else if (STRNICMP(arg, "notoplevel", 10) == 0 && next - arg == 10) { curwin->w_s->b_syn_spell = SYNSPL_NOTOP; @@ -3121,10 +3149,11 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing) */ void syntax_clear(synblock_T *block) { - block->b_syn_error = FALSE; /* clear previous error */ - block->b_syn_ic = FALSE; /* Use case, by default */ - block->b_syn_spell = SYNSPL_DEFAULT; /* default spell checking */ - block->b_syn_containedin = FALSE; + block->b_syn_error = false; // clear previous error + block->b_syn_ic = false; // Use case, by default + block->b_syn_spell = SYNSPL_DEFAULT; // default spell checking + block->b_syn_containedin = false; + block->b_syn_conceal = false; /* free the keywords */ clear_keywtab(&block->b_keywtab); @@ -4001,10 +4030,11 @@ get_group_name ( * Return NULL for any error; */ static char_u * -get_syn_options ( - char_u *arg, /* next argument to be checked */ - syn_opt_arg_T *opt, /* various things */ - int *conceal_char +get_syn_options( + char_u *arg, // next argument to be checked + syn_opt_arg_T *opt, // various things + int *conceal_char, + int skip // TRUE if skipping over command ) { char_u *gname_start, *gname; @@ -4080,14 +4110,17 @@ get_syn_options ( EMSG(_("E395: contains argument not accepted here")); return NULL; } - if (get_id_list(&arg, 8, &opt->cont_list) == FAIL) + if (get_id_list(&arg, 8, &opt->cont_list, skip) == FAIL) { return NULL; + } } else if (flagtab[fidx].argtype == 2) { - if (get_id_list(&arg, 11, &opt->cont_in_list) == FAIL) + if (get_id_list(&arg, 11, &opt->cont_in_list, skip) == FAIL) { return NULL; + } } else if (flagtab[fidx].argtype == 3) { - if (get_id_list(&arg, 9, &opt->next_list) == FAIL) + if (get_id_list(&arg, 9, &opt->next_list, skip) == FAIL) { return NULL; + } } else if (flagtab[fidx].argtype == 11 && arg[5] == '=') { /* cchar=? */ if (has_mbyte) { @@ -4206,11 +4239,11 @@ static void syn_cmd_include(exarg_T *eap, int syncing) */ eap->argt |= (XFILE | NOSPC); separate_nextcmd(eap); - if (*eap->arg == '<' || *eap->arg == '$' || path_is_absolute_path(eap->arg)) { - /* For an absolute path, "$VIM/..." or "<sfile>.." we ":source" the - * file. Need to expand the file name first. In other cases - * ":runtime!" is used. */ - source = TRUE; + if (*eap->arg == '<' || *eap->arg == '$' || path_is_absolute(eap->arg)) { + // For an absolute path, "$VIM/..." or "<sfile>.." we ":source" the + // file. Need to expand the file name first. In other cases + // ":runtime!" is used. + source = true; if (expand_filename(eap, syn_cmdlinep, &errormsg) == FAIL) { if (errormsg != NULL) EMSG(errormsg); @@ -4257,7 +4290,11 @@ static void syn_cmd_keyword(exarg_T *eap, int syncing) rest = get_group_name(arg, &group_name_end); if (rest != NULL) { - syn_id = syn_check_group(arg, (int)(group_name_end - arg)); + if (eap->skip) { + syn_id = -1; + } else { + syn_id = syn_check_group(arg, (int)(group_name_end - arg)); + } if (syn_id != 0) { // Allocate a buffer, for removing backslashes in the keyword. keyword_copy = xmalloc(STRLEN(rest) + 1); @@ -4276,7 +4313,7 @@ static void syn_cmd_keyword(exarg_T *eap, int syncing) cnt = 0; p = keyword_copy; for (; rest != NULL && !ends_excmd(*rest); rest = skipwhite(rest)) { - rest = get_syn_options(rest, &syn_opt_arg, &conceal_char); + rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip); if (rest == NULL || ends_excmd(*rest)) { break; } @@ -4375,17 +4412,18 @@ syn_cmd_match ( syn_opt_arg.cont_list = NULL; syn_opt_arg.cont_in_list = NULL; syn_opt_arg.next_list = NULL; - rest = get_syn_options(rest, &syn_opt_arg, &conceal_char); + rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip); /* get the pattern. */ init_syn_patterns(); memset(&item, 0, sizeof(item)); rest = get_syn_pattern(rest, &item); - if (vim_regcomp_had_eol() && !(syn_opt_arg.flags & HL_EXCLUDENL)) + if (vim_regcomp_had_eol() && !(syn_opt_arg.flags & HL_EXCLUDENL)) { syn_opt_arg.flags |= HL_HAS_EOL; + } - /* Get options after the pattern */ - rest = get_syn_options(rest, &syn_opt_arg, &conceal_char); + // Get options after the pattern + rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip); if (rest != NULL) { /* all arguments are valid */ /* @@ -4497,14 +4535,13 @@ syn_cmd_region ( syn_opt_arg.cont_in_list = NULL; syn_opt_arg.next_list = NULL; - /* - * get the options, patterns and matchgroup. - */ + // get the options, patterns and matchgroup. while (rest != NULL && !ends_excmd(*rest)) { - /* Check for option arguments */ - rest = get_syn_options(rest, &syn_opt_arg, &conceal_char); - if (rest == NULL || ends_excmd(*rest)) + // Check for option arguments + rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip); + if (rest == NULL || ends_excmd(*rest)) { break; + } /* must be a pattern or matchgroup then */ key_end = rest; @@ -4926,13 +4963,17 @@ static void syn_cmd_cluster(exarg_T *eap, int syncing) break; clstr_list = NULL; - if (get_id_list(&rest, opt_len, &clstr_list) == FAIL) { + if (get_id_list(&rest, opt_len, &clstr_list, eap->skip) == FAIL) { EMSG2(_(e_invarg2), rest); break; } - syn_combine_list(&SYN_CLSTR(curwin->w_s)[scl_id].scl_list, - &clstr_list, list_op); - got_clstr = TRUE; + if (scl_id >= 0) { + syn_combine_list(&SYN_CLSTR(curwin->w_s)[scl_id].scl_list, + &clstr_list, list_op); + } else { + xfree(clstr_list); + } + got_clstr = true; } if (got_clstr) { @@ -5180,9 +5221,10 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) static int get_id_list ( char_u **arg, - int keylen, /* length of keyword */ - short **list /* where to store the resulting list, if not - NULL, the list is silently skipped! */ + int keylen, // length of keyword + int16_t **list, // where to store the resulting list, if not + // NULL, the list is silently skipped! + int skip ) { char_u *p = NULL; @@ -5238,8 +5280,9 @@ get_id_list ( break; } if (count != 0) { - EMSG2(_("E408: %s must be first in contains list"), name + 1); - failed = TRUE; + EMSG2(_("E408: %s must be first in contains list"), + name + 1); + failed = true; xfree(name); break; } @@ -5251,7 +5294,11 @@ get_id_list ( id = SYNID_CONTAINED; id += current_syn_inc_tag; } else if (name[1] == '@') { - id = syn_check_cluster(name + 2, (int)(end - p - 1)); + if (skip) { + id = -1; + } else { + id = syn_check_cluster(name + 2, (int)(end - p - 1)); + } } else { /* * Handle full group name. @@ -5930,9 +5977,9 @@ static void syntime_report(void) // // When making changes here, also change runtime/colors/default.vim! -static char *highlight_init_both[] = -{ - "Conceal ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey", +static const char *highlight_init_both[] = { + "Conceal " + "ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey", "Cursor guibg=fg guifg=bg", "lCursor guibg=fg guifg=bg", "DiffText cterm=bold ctermbg=Red gui=bold guibg=Red", @@ -5952,11 +5999,11 @@ static char *highlight_init_both[] = "default link QuickFixLine Search", "default link Substitute Search", "default link Whitespace NonText", + "default link MsgSeparator StatusLine", NULL }; -static char *highlight_init_light[] = -{ +static const char *highlight_init_light[] = { "ColorColumn ctermbg=LightRed guibg=LightRed", "CursorColumn ctermbg=LightGrey guibg=Grey90", "CursorLine cterm=underline guibg=Grey90", @@ -5989,8 +6036,7 @@ static char *highlight_init_light[] = NULL }; -static char *highlight_init_dark[] = -{ +static const char *highlight_init_dark[] = { "ColorColumn ctermbg=DarkRed guibg=DarkRed", "CursorColumn ctermbg=DarkGrey guibg=Grey40", "CursorLine cterm=underline guibg=Grey40", @@ -6023,18 +6069,223 @@ static char *highlight_init_dark[] = NULL }; +const char *const highlight_init_cmdline[] = { + // XXX When modifying a list modify it in both valid and invalid halfs. + // TODO(ZyX-I): merge valid and invalid groups via a macros. + + // NvimInternalError should appear only when highlighter has a bug. + "NvimInternalError ctermfg=Red ctermbg=Red guifg=Red guibg=Red", + + // Highlight groups (links) used by parser: + + "default link NvimAssignment Operator", + "default link NvimPlainAssignment NvimAssignment", + "default link NvimAugmentedAssignment NvimAssignment", + "default link NvimAssignmentWithAddition NvimAugmentedAssignment", + "default link NvimAssignmentWithSubtraction NvimAugmentedAssignment", + "default link NvimAssignmentWithConcatenation NvimAugmentedAssignment", + + "default link NvimOperator Operator", + + "default link NvimUnaryOperator NvimOperator", + "default link NvimUnaryPlus NvimUnaryOperator", + "default link NvimUnaryMinus NvimUnaryOperator", + "default link NvimNot NvimUnaryOperator", + + "default link NvimBinaryOperator NvimOperator", + "default link NvimComparison NvimBinaryOperator", + "default link NvimComparisonModifier NvimComparison", + "default link NvimBinaryPlus NvimBinaryOperator", + "default link NvimBinaryMinus NvimBinaryOperator", + "default link NvimConcat NvimBinaryOperator", + "default link NvimConcatOrSubscript NvimConcat", + "default link NvimOr NvimBinaryOperator", + "default link NvimAnd NvimBinaryOperator", + "default link NvimMultiplication NvimBinaryOperator", + "default link NvimDivision NvimBinaryOperator", + "default link NvimMod NvimBinaryOperator", + + "default link NvimTernary NvimOperator", + "default link NvimTernaryColon NvimTernary", + + "default link NvimParenthesis Delimiter", + "default link NvimLambda NvimParenthesis", + "default link NvimNestingParenthesis NvimParenthesis", + "default link NvimCallingParenthesis NvimParenthesis", + + "default link NvimSubscript NvimParenthesis", + "default link NvimSubscriptBracket NvimSubscript", + "default link NvimSubscriptColon NvimSubscript", + "default link NvimCurly NvimSubscript", + + "default link NvimContainer NvimParenthesis", + "default link NvimDict NvimContainer", + "default link NvimList NvimContainer", + + "default link NvimIdentifier Identifier", + "default link NvimIdentifierScope NvimIdentifier", + "default link NvimIdentifierScopeDelimiter NvimIdentifier", + "default link NvimIdentifierName NvimIdentifier", + "default link NvimIdentifierKey NvimIdentifier", + + "default link NvimColon Delimiter", + "default link NvimComma Delimiter", + "default link NvimArrow Delimiter", + + "default link NvimRegister SpecialChar", + "default link NvimNumber Number", + "default link NvimFloat NvimNumber", + "default link NvimNumberPrefix Type", + + "default link NvimOptionSigil Type", + "default link NvimOptionName NvimIdentifier", + "default link NvimOptionScope NvimIdentifierScope", + "default link NvimOptionScopeDelimiter NvimIdentifierScopeDelimiter", + + "default link NvimEnvironmentSigil NvimOptionSigil", + "default link NvimEnvironmentName NvimIdentifier", + + "default link NvimString String", + "default link NvimStringBody NvimString", + "default link NvimStringQuote NvimString", + "default link NvimStringSpecial SpecialChar", + + "default link NvimSingleQuote NvimStringQuote", + "default link NvimSingleQuotedBody NvimStringBody", + "default link NvimSingleQuotedQuote NvimStringSpecial", + + "default link NvimDoubleQuote NvimStringQuote", + "default link NvimDoubleQuotedBody NvimStringBody", + "default link NvimDoubleQuotedEscape NvimStringSpecial", + + "default link NvimFigureBrace NvimInternalError", + "default link NvimSingleQuotedUnknownEscape NvimInternalError", + + "default link NvimSpacing Normal", + + // NvimInvalid groups: + + "default link NvimInvalidSingleQuotedUnknownEscape NvimInternalError", + + "default link NvimInvalid Error", + + "default link NvimInvalidAssignment NvimInvalid", + "default link NvimInvalidPlainAssignment NvimInvalidAssignment", + "default link NvimInvalidAugmentedAssignment NvimInvalidAssignment", + "default link NvimInvalidAssignmentWithAddition " + "NvimInvalidAugmentedAssignment", + "default link NvimInvalidAssignmentWithSubtraction " + "NvimInvalidAugmentedAssignment", + "default link NvimInvalidAssignmentWithConcatenation " + "NvimInvalidAugmentedAssignment", + + "default link NvimInvalidOperator NvimInvalid", + + "default link NvimInvalidUnaryOperator NvimInvalidOperator", + "default link NvimInvalidUnaryPlus NvimInvalidUnaryOperator", + "default link NvimInvalidUnaryMinus NvimInvalidUnaryOperator", + "default link NvimInvalidNot NvimInvalidUnaryOperator", + + "default link NvimInvalidBinaryOperator NvimInvalidOperator", + "default link NvimInvalidComparison NvimInvalidBinaryOperator", + "default link NvimInvalidComparisonModifier NvimInvalidComparison", + "default link NvimInvalidBinaryPlus NvimInvalidBinaryOperator", + "default link NvimInvalidBinaryMinus NvimInvalidBinaryOperator", + "default link NvimInvalidConcat NvimInvalidBinaryOperator", + "default link NvimInvalidConcatOrSubscript NvimInvalidConcat", + "default link NvimInvalidOr NvimInvalidBinaryOperator", + "default link NvimInvalidAnd NvimInvalidBinaryOperator", + "default link NvimInvalidMultiplication NvimInvalidBinaryOperator", + "default link NvimInvalidDivision NvimInvalidBinaryOperator", + "default link NvimInvalidMod NvimInvalidBinaryOperator", + + "default link NvimInvalidTernary NvimInvalidOperator", + "default link NvimInvalidTernaryColon NvimInvalidTernary", + + "default link NvimInvalidDelimiter NvimInvalid", + + "default link NvimInvalidParenthesis NvimInvalidDelimiter", + "default link NvimInvalidLambda NvimInvalidParenthesis", + "default link NvimInvalidNestingParenthesis NvimInvalidParenthesis", + "default link NvimInvalidCallingParenthesis NvimInvalidParenthesis", + + "default link NvimInvalidSubscript NvimInvalidParenthesis", + "default link NvimInvalidSubscriptBracket NvimInvalidSubscript", + "default link NvimInvalidSubscriptColon NvimInvalidSubscript", + "default link NvimInvalidCurly NvimInvalidSubscript", + + "default link NvimInvalidContainer NvimInvalidParenthesis", + "default link NvimInvalidDict NvimInvalidContainer", + "default link NvimInvalidList NvimInvalidContainer", + + "default link NvimInvalidValue NvimInvalid", + + "default link NvimInvalidIdentifier NvimInvalidValue", + "default link NvimInvalidIdentifierScope NvimInvalidIdentifier", + "default link NvimInvalidIdentifierScopeDelimiter NvimInvalidIdentifier", + "default link NvimInvalidIdentifierName NvimInvalidIdentifier", + "default link NvimInvalidIdentifierKey NvimInvalidIdentifier", + + "default link NvimInvalidColon NvimInvalidDelimiter", + "default link NvimInvalidComma NvimInvalidDelimiter", + "default link NvimInvalidArrow NvimInvalidDelimiter", + + "default link NvimInvalidRegister NvimInvalidValue", + "default link NvimInvalidNumber NvimInvalidValue", + "default link NvimInvalidFloat NvimInvalidNumber", + "default link NvimInvalidNumberPrefix NvimInvalidNumber", + + "default link NvimInvalidOptionSigil NvimInvalidIdentifier", + "default link NvimInvalidOptionName NvimInvalidIdentifier", + "default link NvimInvalidOptionScope NvimInvalidIdentifierScope", + "default link NvimInvalidOptionScopeDelimiter " + "NvimInvalidIdentifierScopeDelimiter", + + "default link NvimInvalidEnvironmentSigil NvimInvalidOptionSigil", + "default link NvimInvalidEnvironmentName NvimInvalidIdentifier", + + // Invalid string bodies and specials are still highlighted as valid ones to + // minimize the red area. + "default link NvimInvalidString NvimInvalidValue", + "default link NvimInvalidStringBody NvimStringBody", + "default link NvimInvalidStringQuote NvimInvalidString", + "default link NvimInvalidStringSpecial NvimStringSpecial", + + "default link NvimInvalidSingleQuote NvimInvalidStringQuote", + "default link NvimInvalidSingleQuotedBody NvimInvalidStringBody", + "default link NvimInvalidSingleQuotedQuote NvimInvalidStringSpecial", + + "default link NvimInvalidDoubleQuote NvimInvalidStringQuote", + "default link NvimInvalidDoubleQuotedBody NvimInvalidStringBody", + "default link NvimInvalidDoubleQuotedEscape NvimInvalidStringSpecial", + "default link NvimInvalidDoubleQuotedUnknownEscape NvimInvalidValue", + + "default link NvimInvalidFigureBrace NvimInvalidDelimiter", + + "default link NvimInvalidSpacing ErrorMsg", + + // Not actually invalid, but we highlight user that he is doing something + // wrong. + "default link NvimDoubleQuotedUnknownEscape NvimInvalidValue", + NULL, +}; + +/// Create default links for Nvim* highlight groups used for cmdline coloring +void syn_init_cmdline_highlight(bool reset, bool init) +{ + for (size_t i = 0 ; highlight_init_cmdline[i] != NULL ; i++) { + do_highlight(highlight_init_cmdline[i], reset, init); + } +} /// Load colors from a file if "g:colors_name" is set, otherwise load builtin /// colors /// /// @param both include groups where 'bg' doesn't matter /// @param reset clear groups first -void -init_highlight(int both, int reset) +void init_highlight(bool both, bool reset) { - int i; - char **pp; - static int had_both = FALSE; + static int had_both = false; // Try finding the color scheme file. Used when a color file was loaded // and 'background' or 't_Co' is changed. @@ -6054,10 +6305,10 @@ init_highlight(int both, int reset) * Didn't use a color file, use the compiled-in colors. */ if (both) { - had_both = TRUE; - pp = highlight_init_both; - for (i = 0; pp[i] != NULL; i++) { - do_highlight((char_u *)pp[i], reset, true); + had_both = true; + const char *const *const pp = highlight_init_both; + for (size_t i = 0; pp[i] != NULL; i++) { + do_highlight(pp[i], reset, true); } } else if (!had_both) { // Don't do anything before the call with both == TRUE from main(). @@ -6066,10 +6317,11 @@ init_highlight(int both, int reset) return; } - pp = (*p_bg == 'l') ? highlight_init_light : highlight_init_dark; - - for (i = 0; pp[i] != NULL; i++) { - do_highlight((char_u *)pp[i], reset, true); + const char *const *const pp = ((*p_bg == 'l') + ? highlight_init_light + : highlight_init_dark); + for (size_t i = 0; pp[i] != NULL; i++) { + do_highlight(pp[i], reset, true); } /* Reverse looks ugly, but grey may not work for 8 colors. Thus let it @@ -6079,15 +6331,14 @@ init_highlight(int both, int reset) * Clear the attributes, needed when changing the t_Co value. */ if (t_colors > 8) { do_highlight( - (char_u *)(*p_bg == 'l' - ? "Visual cterm=NONE ctermbg=LightGrey" - : "Visual cterm=NONE ctermbg=DarkGrey"), false, - true); + (*p_bg == 'l' + ? "Visual cterm=NONE ctermbg=LightGrey" + : "Visual cterm=NONE ctermbg=DarkGrey"), false, true); } else { - do_highlight((char_u *)"Visual cterm=reverse ctermbg=NONE", - FALSE, TRUE); - if (*p_bg == 'l') - do_highlight((char_u *)"Search ctermfg=black", FALSE, TRUE); + do_highlight("Visual cterm=reverse ctermbg=NONE", false, true); + if (*p_bg == 'l') { + do_highlight("Search ctermfg=black", false, true); + } } /* @@ -6104,6 +6355,7 @@ init_highlight(int both, int reset) recursive--; } } + syn_init_cmdline_highlight(false, false); } /* @@ -6138,17 +6390,22 @@ int load_colors(char_u *name) } -/// Handle the ":highlight .." command. -/// When using ":hi clear" this is called recursively for each group with -/// "forceit" and "init" both TRUE. -/// @param init TRUE when called for initializing -void -do_highlight(char_u *line, int forceit, int init) { - char_u *name_end; - char_u *linep; - char_u *key_start; - char_u *arg_start; - char_u *key = NULL, *arg = NULL; +/// Handle ":highlight" command +/// +/// When using ":highlight clear" this is called recursively for each group with +/// forceit and init being both true. +/// +/// @param[in] line Command arguments. +/// @param[in] forceit True when bang is given, allows to link group even if +/// it has its own settings. +/// @param[in] init True when initializing. +void do_highlight(const char *line, const bool forceit, const bool init) + FUNC_ATTR_NONNULL_ALL +{ + const char *name_end; + const char *linep; + const char *key_start; + const char *arg_start; long i; int off; int len; @@ -6162,140 +6419,129 @@ do_highlight(char_u *line, int forceit, int init) { int color; bool is_normal_group = false; // "Normal" group - /* - * If no argument, list current highlighting. - */ - if (ends_excmd(*line)) { + // If no argument, list current highlighting. + if (ends_excmd((uint8_t)(*line))) { for (int i = 1; i <= highlight_ga.ga_len && !got_int; i++) { - // todo(vim): only call when the group has attributes set + // TODO(brammool): only call when the group has attributes set highlight_list_one(i); } return; } - /* - * Isolate the name. - */ - name_end = skiptowhite(line); - linep = skipwhite(name_end); + // Isolate the name. + name_end = (const char *)skiptowhite((const char_u *)line); + linep = (const char *)skipwhite((const char_u *)name_end); - /* - * Check for "default" argument. - */ - if (STRNCMP(line, "default", name_end - line) == 0) { - dodefault = TRUE; + // Check for "default" argument. + if (strncmp(line, "default", name_end - line) == 0) { + dodefault = true; line = linep; - name_end = skiptowhite(line); - linep = skipwhite(name_end); + name_end = (const char *)skiptowhite((const char_u *)line); + linep = (const char *)skipwhite((const char_u *)name_end); } - /* - * Check for "clear" or "link" argument. - */ - if (STRNCMP(line, "clear", name_end - line) == 0) - doclear = TRUE; - if (STRNCMP(line, "link", name_end - line) == 0) - dolink = TRUE; + // Check for "clear" or "link" argument. + if (strncmp(line, "clear", name_end - line) == 0) { + doclear = true; + } else if (strncmp(line, "link", name_end - line) == 0) { + dolink = true; + } - /* - * ":highlight {group-name}": list highlighting for one group. - */ - if (!doclear && !dolink && ends_excmd(*linep)) { - id = syn_namen2id(line, (int)(name_end - line)); - if (id == 0) - EMSG2(_("E411: highlight group not found: %s"), line); - else + // ":highlight {group-name}": list highlighting for one group. + if (!doclear && !dolink && ends_excmd((uint8_t)(*linep))) { + id = syn_namen2id((const char_u *)line, (int)(name_end - line)); + if (id == 0) { + emsgf(_("E411: highlight group not found: %s"), line); + } else { highlight_list_one(id); + } return; } - /* - * Handle ":highlight link {from} {to}" command. - */ + // Handle ":highlight link {from} {to}" command. if (dolink) { - char_u *from_start = linep; - char_u *from_end; - char_u *to_start; - char_u *to_end; + const char *from_start = linep; + const char *from_end; + const char *to_start; + const char *to_end; int from_id; int to_id; - from_end = skiptowhite(from_start); - to_start = skipwhite(from_end); - to_end = skiptowhite(to_start); + from_end = (const char *)skiptowhite((const char_u *)from_start); + to_start = (const char *)skipwhite((const char_u *)from_end); + to_end = (const char *)skiptowhite((const char_u *)to_start); - if (ends_excmd(*from_start) || ends_excmd(*to_start)) { - EMSG2(_("E412: Not enough arguments: \":highlight link %s\""), - from_start); + if (ends_excmd((uint8_t)(*from_start)) + || ends_excmd((uint8_t)(*to_start))) { + emsgf(_("E412: Not enough arguments: \":highlight link %s\""), + from_start); return; } - if (!ends_excmd(*skipwhite(to_end))) { - EMSG2(_("E413: Too many arguments: \":highlight link %s\""), from_start); + if (!ends_excmd(*skipwhite((const char_u *)to_end))) { + emsgf(_("E413: Too many arguments: \":highlight link %s\""), from_start); return; } - from_id = syn_check_group(from_start, (int)(from_end - from_start)); - if (STRNCMP(to_start, "NONE", 4) == 0) + from_id = syn_check_group((const char_u *)from_start, + (int)(from_end - from_start)); + if (strncmp(to_start, "NONE", 4) == 0) { to_id = 0; - else - to_id = syn_check_group(to_start, (int)(to_end - to_start)); + } else { + to_id = syn_check_group((const char_u *)to_start, + (int)(to_end - to_start)); + } if (from_id > 0 && (!init || HL_TABLE()[from_id - 1].sg_set == 0)) { - /* - * Don't allow a link when there already is some highlighting - * for the group, unless '!' is used - */ + // Don't allow a link when there already is some highlighting + // for the group, unless '!' is used if (to_id > 0 && !forceit && !init && hl_has_settings(from_id - 1, dodefault)) { - if (sourcing_name == NULL && !dodefault) + if (sourcing_name == NULL && !dodefault) { EMSG(_("E414: group has settings, highlight link ignored")); + } } else { if (!init) HL_TABLE()[from_id - 1].sg_set |= SG_LINK; HL_TABLE()[from_id - 1].sg_link = to_id; HL_TABLE()[from_id - 1].sg_scriptID = current_SID; + HL_TABLE()[from_id - 1].sg_cleared = false; redraw_all_later(SOME_VALID); } } - /* Only call highlight_changed() once, after sourcing a syntax file */ - need_highlight_changed = TRUE; + // Only call highlight_changed() once, after sourcing a syntax file. + need_highlight_changed = true; return; } if (doclear) { - /* - * ":highlight clear [group]" command. - */ + // ":highlight clear [group]" command. line = linep; - if (ends_excmd(*line)) { + if (ends_excmd((uint8_t)(*line))) { do_unlet(S_LEN("colors_name"), true); restore_cterm_colors(); - /* - * Clear all default highlight groups and load the defaults. - */ - for (int idx = 0; idx < highlight_ga.ga_len; ++idx) { + // Clear all default highlight groups and load the defaults. + for (int idx = 0; idx < highlight_ga.ga_len; idx++) { highlight_clear(idx); } - init_highlight(TRUE, TRUE); + init_highlight(true, true); highlight_changed(); redraw_later_clear(); return; } - name_end = skiptowhite(line); - linep = skipwhite(name_end); + name_end = (const char *)skiptowhite((const char_u *)line); + linep = (const char *)skipwhite((const char_u *)name_end); } - /* - * Find the group name in the table. If it does not exist yet, add it. - */ - id = syn_check_group(line, (int)(name_end - line)); - if (id == 0) /* failed (out of memory) */ + // Find the group name in the table. If it does not exist yet, add it. + id = syn_check_group((const char_u *)line, (int)(name_end - line)); + if (id == 0) { // Failed (out of memory). return; - idx = id - 1; /* index is ID minus one */ + } + idx = id - 1; // Index is ID minus one. // Return if "default" was used and the group already has settings if (dodefault && hl_has_settings(idx, true)) { @@ -6304,19 +6550,21 @@ do_highlight(char_u *line, int forceit, int init) { is_normal_group = (STRCMP(HL_TABLE()[idx].sg_name_u, "NORMAL") == 0); - /* Clear the highlighting for ":hi clear {group}" and ":hi clear". */ + // Clear the highlighting for ":hi clear {group}" and ":hi clear". if (doclear || (forceit && init)) { highlight_clear(idx); if (!doclear) HL_TABLE()[idx].sg_set = 0; } + char *key = NULL; + char *arg = NULL; if (!doclear) { - while (!ends_excmd(*linep)) { + while (!ends_excmd((uint8_t)(*linep))) { key_start = linep; if (*linep == '=') { - EMSG2(_("E415: unexpected equal sign: %s"), key_start); - error = TRUE; + emsgf(_("E415: unexpected equal sign: %s"), key_start); + error = true; break; } @@ -6326,61 +6574,58 @@ do_highlight(char_u *line, int forceit, int init) { linep++; } xfree(key); - key = vim_strnsave_up(key_start, (int)(linep - key_start)); - linep = skipwhite(linep); + key = (char *)vim_strnsave_up((const char_u *)key_start, + (int)(linep - key_start)); + linep = (const char *)skipwhite((const char_u *)linep); - if (STRCMP(key, "NONE") == 0) { + if (strcmp(key, "NONE") == 0) { if (!init || HL_TABLE()[idx].sg_set == 0) { - if (!init) + if (!init) { HL_TABLE()[idx].sg_set |= SG_CTERM+SG_GUI; + } highlight_clear(idx); } continue; } - /* - * Check for the equal sign. - */ + // Check for the equal sign. if (*linep != '=') { - EMSG2(_("E416: missing equal sign: %s"), key_start); - error = TRUE; + emsgf(_("E416: missing equal sign: %s"), key_start); + error = true; break; } - ++linep; + linep++; - /* - * Isolate the argument. - */ - linep = skipwhite(linep); - if (*linep == '\'') { /* guifg='color name' */ + // Isolate the argument. + linep = (const char *)skipwhite((const char_u *)linep); + if (*linep == '\'') { // guifg='color name' arg_start = ++linep; - linep = vim_strchr(linep, '\''); + linep = strchr(linep, '\''); if (linep == NULL) { - EMSG2(_(e_invarg2), key_start); - error = TRUE; + emsgf(_(e_invarg2), key_start); + error = true; break; } } else { arg_start = linep; - linep = skiptowhite(linep); + linep = (const char *)skiptowhite((const char_u *)linep); } if (linep == arg_start) { - EMSG2(_("E417: missing argument: %s"), key_start); - error = TRUE; + emsgf(_("E417: missing argument: %s"), key_start); + error = true; break; } xfree(arg); - arg = vim_strnsave(arg_start, (int)(linep - arg_start)); + arg = xstrndup(arg_start, (size_t)(linep - arg_start)); - if (*linep == '\'') - ++linep; + if (*linep == '\'') { + linep++; + } - /* - * Store the argument. - */ - if ( STRCMP(key, "TERM") == 0 - || STRCMP(key, "CTERM") == 0 - || STRCMP(key, "GUI") == 0) { + // Store the argument. + if (strcmp(key, "TERM") == 0 + || strcmp(key, "CTERM") == 0 + || strcmp(key, "GUI") == 0) { attr = 0; off = 0; while (arg[off] != NUL) { @@ -6393,26 +6638,30 @@ do_highlight(char_u *line, int forceit, int init) { } } if (i < 0) { - EMSG2(_("E418: Illegal value: %s"), arg); - error = TRUE; + emsgf(_("E418: Illegal value: %s"), arg); + error = true; break; } - if (arg[off] == ',') /* another one follows */ - ++off; + if (arg[off] == ',') { // Another one follows. + off++; + } } - if (error) + if (error) { break; + } if (*key == 'C') { if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM)) { - if (!init) + if (!init) { HL_TABLE()[idx].sg_set |= SG_CTERM; + } HL_TABLE()[idx].sg_cterm = attr; HL_TABLE()[idx].sg_cterm_bold = FALSE; } } else if (*key == 'G') { if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { - if (!init) + if (!init) { HL_TABLE()[idx].sg_set |= SG_GUI; + } HL_TABLE()[idx].sg_gui = attr; } } @@ -6431,14 +6680,14 @@ do_highlight(char_u *line, int forceit, int init) { HL_TABLE()[idx].sg_cterm_bold = FALSE; } - if (ascii_isdigit(*arg)) + if (ascii_isdigit(*arg)) { color = atoi((char *)arg); - else if (STRICMP(arg, "fg") == 0) { - if (cterm_normal_fg_color) + } else if (STRICMP(arg, "fg") == 0) { + if (cterm_normal_fg_color) { color = cterm_normal_fg_color - 1; - else { + } else { EMSG(_("E419: FG color unknown")); - error = TRUE; + error = true; break; } } else if (STRICMP(arg, "bg") == 0) { @@ -6446,70 +6695,84 @@ do_highlight(char_u *line, int forceit, int init) { color = cterm_normal_bg_color - 1; else { EMSG(_("E420: BG color unknown")); - error = TRUE; + error = true; break; } } else { - static char *(color_names[28]) = { + static const char *color_names[] = { "Black", "DarkBlue", "DarkGreen", "DarkCyan", "DarkRed", "DarkMagenta", "Brown", "DarkYellow", "Gray", "Grey", "LightGray", "LightGrey", "DarkGray", "DarkGrey", "Blue", "LightBlue", "Green", "LightGreen", "Cyan", "LightCyan", "Red", "LightRed", "Magenta", - "LightMagenta", "Yellow", "LightYellow", "White", "NONE" + "LightMagenta", "Yellow", "LightYellow", "White", + "NONE" + }; + static const int color_numbers_16[] = { + 0, 1, 2, 3, + 4, 5, 6, 6, + 7, 7, + 7, 7, 8, 8, + 9, 9, 10, 10, + 11, 11, 12, 12, 13, + 13, 14, 14, 15, + -1 + }; + // For xterm with 88 colors: + static int color_numbers_88[] = { + 0, 4, 2, 6, + 1, 5, 32, 72, + 84, 84, + 7, 7, 82, 82, + 12, 43, 10, 61, + 14, 63, 9, 74, 13, + 75, 11, 78, 15, + -1 + }; + // For xterm with 256 colors: + static int color_numbers_256[] = { + 0, 4, 2, 6, + 1, 5, 130, 130, + 248, 248, + 7, 7, 242, 242, + 12, 81, 10, 121, + 14, 159, 9, 224, 13, + 225, 11, 229, 15, + -1 }; - static int color_numbers_16[28] = {0, 1, 2, 3, - 4, 5, 6, 6, - 7, 7, - 7, 7, 8, 8, - 9, 9, 10, 10, - 11, 11, 12, 12, 13, - 13, 14, 14, 15, -1}; - /* for xterm with 88 colors... */ - static int color_numbers_88[28] = {0, 4, 2, 6, - 1, 5, 32, 72, - 84, 84, - 7, 7, 82, 82, - 12, 43, 10, 61, - 14, 63, 9, 74, 13, - 75, 11, 78, 15, -1}; - /* for xterm with 256 colors... */ - static int color_numbers_256[28] = {0, 4, 2, 6, - 1, 5, 130, 130, - 248, 248, - 7, 7, 242, 242, - 12, 81, 10, 121, - 14, 159, 9, 224, 13, - 225, 11, 229, 15, -1}; - /* for terminals with less than 16 colors... */ - static int color_numbers_8[28] = {0, 4, 2, 6, - 1, 5, 3, 3, - 7, 7, - 7, 7, 0+8, 0+8, - 4+8, 4+8, 2+8, 2+8, - 6+8, 6+8, 1+8, 1+8, 5+8, - 5+8, 3+8, 3+8, 7+8, -1}; - - /* reduce calls to STRICMP a bit, it can be slow */ + // For terminals with less than 16 colors: + static int color_numbers_8[28] = { + 0, 4, 2, 6, + 1, 5, 3, 3, + 7, 7, + 7, 7, 0+8, 0+8, + 4+8, 4+8, 2+8, 2+8, + 6+8, 6+8, 1+8, 1+8, 5+8, + 5+8, 3+8, 3+8, 7+8, + -1 + }; + + // Reduce calls to STRICMP a bit, it can be slow. off = TOUPPER_ASC(*arg); - for (i = ARRAY_SIZE(color_names); --i >= 0; ) + for (i = ARRAY_SIZE(color_names); --i >= 0; ) { if (off == color_names[i][0] - && STRICMP(arg + 1, color_names[i] + 1) == 0) + && STRICMP(arg + 1, color_names[i] + 1) == 0) { break; + } + } if (i < 0) { - EMSG2(_( - "E421: Color name or number not recognized: %s"), - key_start); - error = TRUE; + emsgf(_("E421: Color name or number not recognized: %s"), + key_start); + error = true; break; } - /* Use the _16 table to check if its a valid color name. */ + // Use the _16 table to check if its a valid color name. color = color_numbers_16[i]; if (color >= 0) { if (t_colors == 8) { - /* t_Co is 8: use the 8 colors table */ + // t_Co is 8: use the 8 colors table. color = color_numbers_8[i]; if (key[5] == 'F') { /* set/reset bold attribute to get light foreground @@ -6533,16 +6796,13 @@ do_highlight(char_u *line, int forceit, int init) { } } } - /* Add one to the argument, to avoid zero. Zero is used for - * "NONE", then "color" is -1. */ + // Add one to the argument, to avoid zero. Zero is used for + // "NONE", then "color" is -1. if (key[5] == 'F') { HL_TABLE()[idx].sg_cterm_fg = color + 1; if (is_normal_group) { cterm_normal_fg_color = color + 1; - cterm_normal_fg_bold = (HL_TABLE()[idx].sg_cterm & HL_BOLD); - { - must_redraw = CLEAR; - } + must_redraw = CLEAR; } } else { HL_TABLE()[idx].sg_cterm_bg = color + 1; @@ -6566,15 +6826,15 @@ do_highlight(char_u *line, int forceit, int init) { } } } - } else if (STRCMP(key, "GUIFG") == 0) { + } else if (strcmp(key, "GUIFG") == 0) { if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { if (!init) HL_TABLE()[idx].sg_set |= SG_GUI; xfree(HL_TABLE()[idx].sg_rgb_fg_name); - if (STRCMP(arg, "NONE")) { - HL_TABLE()[idx].sg_rgb_fg_name = (uint8_t *)xstrdup((char *)arg); - HL_TABLE()[idx].sg_rgb_fg = name_to_color(arg); + if (strcmp(arg, "NONE")) { + HL_TABLE()[idx].sg_rgb_fg_name = (char_u *)xstrdup((char *)arg); + HL_TABLE()[idx].sg_rgb_fg = name_to_color((const char_u *)arg); } else { HL_TABLE()[idx].sg_rgb_fg_name = NULL; HL_TABLE()[idx].sg_rgb_fg = -1; @@ -6591,8 +6851,8 @@ do_highlight(char_u *line, int forceit, int init) { xfree(HL_TABLE()[idx].sg_rgb_bg_name); if (STRCMP(arg, "NONE") != 0) { - HL_TABLE()[idx].sg_rgb_bg_name = (uint8_t *)xstrdup((char *)arg); - HL_TABLE()[idx].sg_rgb_bg = name_to_color(arg); + HL_TABLE()[idx].sg_rgb_bg_name = (char_u *)xstrdup((char *)arg); + HL_TABLE()[idx].sg_rgb_bg = name_to_color((const char_u *)arg); } else { HL_TABLE()[idx].sg_rgb_bg_name = NULL; HL_TABLE()[idx].sg_rgb_bg = -1; @@ -6602,15 +6862,15 @@ do_highlight(char_u *line, int forceit, int init) { if (is_normal_group) { normal_bg = HL_TABLE()[idx].sg_rgb_bg; } - } else if (STRCMP(key, "GUISP") == 0) { + } else if (strcmp(key, "GUISP") == 0) { if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { if (!init) HL_TABLE()[idx].sg_set |= SG_GUI; xfree(HL_TABLE()[idx].sg_rgb_sp_name); - if (STRCMP(arg, "NONE") != 0) { - HL_TABLE()[idx].sg_rgb_sp_name = (uint8_t *)xstrdup((char *)arg); - HL_TABLE()[idx].sg_rgb_sp = name_to_color(arg); + if (strcmp(arg, "NONE") != 0) { + HL_TABLE()[idx].sg_rgb_sp_name = (char_u *)xstrdup((char *)arg); + HL_TABLE()[idx].sg_rgb_sp = name_to_color((const char_u *)arg); } else { HL_TABLE()[idx].sg_rgb_sp_name = NULL; HL_TABLE()[idx].sg_rgb_sp = -1; @@ -6620,31 +6880,26 @@ do_highlight(char_u *line, int forceit, int init) { if (is_normal_group) { normal_sp = HL_TABLE()[idx].sg_rgb_sp; } - } else if (STRCMP(key, "START") == 0 || STRCMP(key, "STOP") == 0) { + } else if (strcmp(key, "START") == 0 || strcmp(key, "STOP") == 0) { // Ignored for now } else { - EMSG2(_("E423: Illegal argument: %s"), key_start); - error = TRUE; + emsgf(_("E423: Illegal argument: %s"), key_start); + error = true; break; } + HL_TABLE()[idx].sg_cleared = false; - /* - * When highlighting has been given for a group, don't link it. - */ + // When highlighting has been given for a group, don't link it. if (!init || !(HL_TABLE()[idx].sg_set & SG_LINK)) { HL_TABLE()[idx].sg_link = 0; } - /* - * Continue with next argument. - */ - linep = skipwhite(linep); + // Continue with next argument. + linep = (const char *)skipwhite((const char_u *)linep); } } - /* - * If there is an error, and it's a new entry, remove it from the table. - */ + // If there is an error, and it's a new entry, remove it from the table. if (error && idx == highlight_ga.ga_len) { syn_unadd_group(); } else { @@ -6690,7 +6945,6 @@ void restore_cterm_colors(void) normal_bg = -1; normal_sp = -1; cterm_normal_fg_color = 0; - cterm_normal_fg_bold = 0; cterm_normal_bg_color = 0; } @@ -6714,6 +6968,8 @@ static int hl_has_settings(int idx, int check_link) */ static void highlight_clear(int idx) { + HL_TABLE()[idx].sg_cleared = true; + HL_TABLE()[idx].sg_attr = 0; HL_TABLE()[idx].sg_cterm = 0; HL_TABLE()[idx].sg_cterm_bold = FALSE; @@ -6742,9 +6998,9 @@ static void highlight_clear(int idx) /// GUI can redraw at any time for any buffer. static garray_T attr_table = GA_EMPTY_INIT_VALUE; -static inline attrentry_T * ATTR_ENTRY(int idx) +static inline HlAttrs * ATTR_ENTRY(int idx) { - return &((attrentry_T *)attr_table.ga_data)[idx]; + return &((HlAttrs *)attr_table.ga_data)[idx]; } @@ -6752,23 +7008,21 @@ static inline attrentry_T * ATTR_ENTRY(int idx) /// Add a new entry to the term_attr_table, attr_table or gui_attr_table /// if the combination is new. /// @return 0 for error. -int get_attr_entry(attrentry_T *aep) +int get_attr_entry(HlAttrs *aep) { garray_T *table = &attr_table; - attrentry_T *taep; - static int recursive = FALSE; + HlAttrs *taep; + static int recursive = false; /* * Init the table, in case it wasn't done yet. */ - table->ga_itemsize = sizeof(attrentry_T); + table->ga_itemsize = sizeof(HlAttrs); ga_set_growsize(table, 7); - /* - * Try to find an entry with the same specifications. - */ - for (int i = 0; i < table->ga_len; ++i) { - taep = &(((attrentry_T *)table->ga_data)[i]); + // Try to find an entry with the same specifications. + for (int i = 0; i < table->ga_len; i++) { + taep = &(((HlAttrs *)table->ga_data)[i]); if (aep->cterm_ae_attr == taep->cterm_ae_attr && aep->cterm_fg_color == taep->cterm_fg_color && aep->cterm_bg_color == taep->cterm_bg_color @@ -6805,7 +7059,7 @@ int get_attr_entry(attrentry_T *aep) // This is a new combination of colors and font, add an entry. - taep = GA_APPEND_VIA_PTR(attrentry_T, table); + taep = GA_APPEND_VIA_PTR(HlAttrs, table); memset(taep, 0, sizeof(*taep)); taep->cterm_ae_attr = aep->cterm_ae_attr; taep->cterm_fg_color = aep->cterm_fg_color; @@ -6833,9 +7087,9 @@ void clear_hl_tables(void) // Return the resulting attributes. int hl_combine_attr(int char_attr, int prim_attr) { - attrentry_T *char_aep = NULL; - attrentry_T *spell_aep; - attrentry_T new_en = ATTRENTRY_INIT; + HlAttrs *char_aep = NULL; + HlAttrs *spell_aep; + HlAttrs new_en = HLATTRS_INIT; if (char_attr == 0) { return prim_attr; @@ -6884,7 +7138,7 @@ int hl_combine_attr(int char_attr, int prim_attr) /// \note this function does not apply exclusively to cterm attr contrary /// to what its name implies /// \warn don't call it with attr 0 (i.e., the null attribute) -attrentry_T *syn_cterm_attr2entry(int attr) +HlAttrs *syn_cterm_attr2entry(int attr) { attr -= ATTR_OFF; if (attr >= attr_table.ga_len) { @@ -7133,7 +7387,7 @@ syn_list_header(int did_header, int outlen, int id) /// @param idx corrected highlight index static void set_hl_attr(int idx) { - attrentry_T at_en = ATTRENTRY_INIT; + HlAttrs at_en = HLATTRS_INIT; struct hl_group *sgp = HL_TABLE() + idx; @@ -7202,7 +7456,7 @@ char_u *syn_id2name(int id) /* * Like syn_name2id(), but take a pointer + length argument. */ -int syn_namen2id(char_u *linep, int len) +int syn_namen2id(const char_u *linep, int len) { char_u *name = vim_strnsave(linep, len); int id = syn_name2id(name); @@ -7218,7 +7472,7 @@ int syn_namen2id(char_u *linep, int len) /// @param len length of \p pp /// /// @return 0 for failure else the id of the group -int syn_check_group(char_u *pp, int len) +int syn_check_group(const char_u *pp, int len) { char_u *name = vim_strnsave(pp, len); int id = syn_name2id(name); @@ -7505,14 +7759,28 @@ static void highlight_list_two(int cnt, int attr) } -/* - * Function given to ExpandGeneric() to obtain the list of group names. - * Also used for synIDattr() function. - */ -const char *get_highlight_name(expand_T *const xp, const int idx) +/// Function given to ExpandGeneric() to obtain the list of group names. +const char *get_highlight_name(expand_T *const xp, int idx) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + return get_highlight_name_ext(xp, idx, true); +} + + +/// Obtain a highlight group name. +/// When "skip_cleared" is TRUE don't return a cleared entry. +const char *get_highlight_name_ext(expand_T *xp, int idx, int skip_cleared) FUNC_ATTR_WARN_UNUSED_RESULT { - // TODO(justinmk): 'xp' is unused + if (idx < 0) { + return NULL; + } + + // Items are never removed from the table, skip the ones that were cleared. + if (skip_cleared && idx < highlight_ga.ga_len && HL_TABLE()[idx].sg_cleared) { + return ""; + } + if (idx == highlight_ga.ga_len && include_none != 0) { return "none"; } else if (idx == highlight_ga.ga_len + include_none @@ -7524,7 +7792,7 @@ const char *get_highlight_name(expand_T *const xp, const int idx) } else if (idx == highlight_ga.ga_len + include_none + include_default + 1 && include_link != 0) { return "clear"; - } else if (idx < 0 || idx >= highlight_ga.ga_len) { + } else if (idx >= highlight_ga.ga_len) { return NULL; } return (const char *)HL_TABLE()[idx].sg_name; @@ -7532,685 +7800,685 @@ const char *get_highlight_name(expand_T *const xp, const int idx) color_name_table_T color_name_table[] = { // Colors from rgb.txt - { "AliceBlue", RGB(0xf0, 0xf8, 0xff) }, - { "AntiqueWhite", RGB(0xfa, 0xeb, 0xd7) }, - { "AntiqueWhite1", RGB(0xff, 0xef, 0xdb) }, - { "AntiqueWhite2", RGB(0xee, 0xdf, 0xcc) }, - { "AntiqueWhite3", RGB(0xcd, 0xc0, 0xb0) }, - { "AntiqueWhite4", RGB(0x8b, 0x83, 0x78) }, - { "Aqua", RGB(0x00, 0xff, 0xff) }, - { "Aquamarine", RGB(0x7f, 0xff, 0xd4) }, - { "Aquamarine1", RGB(0x7f, 0xff, 0xd4) }, - { "Aquamarine2", RGB(0x76, 0xee, 0xc6) }, - { "Aquamarine3", RGB(0x66, 0xcd, 0xaa) }, - { "Aquamarine4", RGB(0x45, 0x8b, 0x74) }, - { "Azure", RGB(0xf0, 0xff, 0xff) }, - { "Azure1", RGB(0xf0, 0xff, 0xff) }, - { "Azure2", RGB(0xe0, 0xee, 0xee) }, - { "Azure3", RGB(0xc1, 0xcd, 0xcd) }, - { "Azure4", RGB(0x83, 0x8b, 0x8b) }, - { "Beige", RGB(0xf5, 0xf5, 0xdc) }, - { "Bisque", RGB(0xff, 0xe4, 0xc4) }, - { "Bisque1", RGB(0xff, 0xe4, 0xc4) }, - { "Bisque2", RGB(0xee, 0xd5, 0xb7) }, - { "Bisque3", RGB(0xcd, 0xb7, 0x9e) }, - { "Bisque4", RGB(0x8b, 0x7d, 0x6b) }, - { "Black", RGB(0x00, 0x00, 0x00) }, - { "BlanchedAlmond", RGB(0xff, 0xeb, 0xcd) }, - { "Blue", RGB(0x00, 0x00, 0xff) }, - { "Blue1", RGB(0x0, 0x0, 0xff) }, - { "Blue2", RGB(0x0, 0x0, 0xee) }, - { "Blue3", RGB(0x0, 0x0, 0xcd) }, - { "Blue4", RGB(0x0, 0x0, 0x8b) }, - { "BlueViolet", RGB(0x8a, 0x2b, 0xe2) }, - { "Brown", RGB(0xa5, 0x2a, 0x2a) }, - { "Brown1", RGB(0xff, 0x40, 0x40) }, - { "Brown2", RGB(0xee, 0x3b, 0x3b) }, - { "Brown3", RGB(0xcd, 0x33, 0x33) }, - { "Brown4", RGB(0x8b, 0x23, 0x23) }, - { "BurlyWood", RGB(0xde, 0xb8, 0x87) }, - { "Burlywood1", RGB(0xff, 0xd3, 0x9b) }, - { "Burlywood2", RGB(0xee, 0xc5, 0x91) }, - { "Burlywood3", RGB(0xcd, 0xaa, 0x7d) }, - { "Burlywood4", RGB(0x8b, 0x73, 0x55) }, - { "CadetBlue", RGB(0x5f, 0x9e, 0xa0) }, - { "CadetBlue1", RGB(0x98, 0xf5, 0xff) }, - { "CadetBlue2", RGB(0x8e, 0xe5, 0xee) }, - { "CadetBlue3", RGB(0x7a, 0xc5, 0xcd) }, - { "CadetBlue4", RGB(0x53, 0x86, 0x8b) }, - { "ChartReuse", RGB(0x7f, 0xff, 0x00) }, - { "Chartreuse1", RGB(0x7f, 0xff, 0x0) }, - { "Chartreuse2", RGB(0x76, 0xee, 0x0) }, - { "Chartreuse3", RGB(0x66, 0xcd, 0x0) }, - { "Chartreuse4", RGB(0x45, 0x8b, 0x0) }, - { "Chocolate", RGB(0xd2, 0x69, 0x1e) }, - { "Chocolate1", RGB(0xff, 0x7f, 0x24) }, - { "Chocolate2", RGB(0xee, 0x76, 0x21) }, - { "Chocolate3", RGB(0xcd, 0x66, 0x1d) }, - { "Chocolate4", RGB(0x8b, 0x45, 0x13) }, - { "Coral", RGB(0xff, 0x7f, 0x50) }, - { "Coral1", RGB(0xff, 0x72, 0x56) }, - { "Coral2", RGB(0xee, 0x6a, 0x50) }, - { "Coral3", RGB(0xcd, 0x5b, 0x45) }, - { "Coral4", RGB(0x8b, 0x3e, 0x2f) }, - { "CornFlowerBlue", RGB(0x64, 0x95, 0xed) }, - { "Cornsilk", RGB(0xff, 0xf8, 0xdc) }, - { "Cornsilk1", RGB(0xff, 0xf8, 0xdc) }, - { "Cornsilk2", RGB(0xee, 0xe8, 0xcd) }, - { "Cornsilk3", RGB(0xcd, 0xc8, 0xb1) }, - { "Cornsilk4", RGB(0x8b, 0x88, 0x78) }, - { "Crimson", RGB(0xdc, 0x14, 0x3c) }, - { "Cyan", RGB(0x00, 0xff, 0xff) }, - { "Cyan1", RGB(0x0, 0xff, 0xff) }, - { "Cyan2", RGB(0x0, 0xee, 0xee) }, - { "Cyan3", RGB(0x0, 0xcd, 0xcd) }, - { "Cyan4", RGB(0x0, 0x8b, 0x8b) }, - { "DarkBlue", RGB(0x00, 0x00, 0x8b) }, - { "DarkCyan", RGB(0x00, 0x8b, 0x8b) }, - { "DarkGoldenRod", RGB(0xb8, 0x86, 0x0b) }, - { "DarkGoldenrod1", RGB(0xff, 0xb9, 0xf) }, - { "DarkGoldenrod2", RGB(0xee, 0xad, 0xe) }, - { "DarkGoldenrod3", RGB(0xcd, 0x95, 0xc) }, - { "DarkGoldenrod4", RGB(0x8b, 0x65, 0x8) }, - { "DarkGray", RGB(0xa9, 0xa9, 0xa9) }, - { "DarkGreen", RGB(0x00, 0x64, 0x00) }, - { "DarkGrey", RGB(0xa9, 0xa9, 0xa9) }, - { "DarkKhaki", RGB(0xbd, 0xb7, 0x6b) }, - { "DarkMagenta", RGB(0x8b, 0x00, 0x8b) }, - { "DarkOliveGreen", RGB(0x55, 0x6b, 0x2f) }, - { "DarkOliveGreen1", RGB(0xca, 0xff, 0x70) }, - { "DarkOliveGreen2", RGB(0xbc, 0xee, 0x68) }, - { "DarkOliveGreen3", RGB(0xa2, 0xcd, 0x5a) }, - { "DarkOliveGreen4", RGB(0x6e, 0x8b, 0x3d) }, - { "DarkOrange", RGB(0xff, 0x8c, 0x00) }, - { "DarkOrange1", RGB(0xff, 0x7f, 0x0) }, - { "DarkOrange2", RGB(0xee, 0x76, 0x0) }, - { "DarkOrange3", RGB(0xcd, 0x66, 0x0) }, - { "DarkOrange4", RGB(0x8b, 0x45, 0x0) }, - { "DarkOrchid", RGB(0x99, 0x32, 0xcc) }, - { "DarkOrchid1", RGB(0xbf, 0x3e, 0xff) }, - { "DarkOrchid2", RGB(0xb2, 0x3a, 0xee) }, - { "DarkOrchid3", RGB(0x9a, 0x32, 0xcd) }, - { "DarkOrchid4", RGB(0x68, 0x22, 0x8b) }, - { "DarkRed", RGB(0x8b, 0x00, 0x00) }, - { "DarkSalmon", RGB(0xe9, 0x96, 0x7a) }, - { "DarkSeaGreen", RGB(0x8f, 0xbc, 0x8f) }, - { "DarkSeaGreen1", RGB(0xc1, 0xff, 0xc1) }, - { "DarkSeaGreen2", RGB(0xb4, 0xee, 0xb4) }, - { "DarkSeaGreen3", RGB(0x9b, 0xcd, 0x9b) }, - { "DarkSeaGreen4", RGB(0x69, 0x8b, 0x69) }, - { "DarkSlateBlue", RGB(0x48, 0x3d, 0x8b) }, - { "DarkSlateGray", RGB(0x2f, 0x4f, 0x4f) }, - { "DarkSlateGray1", RGB(0x97, 0xff, 0xff) }, - { "DarkSlateGray2", RGB(0x8d, 0xee, 0xee) }, - { "DarkSlateGray3", RGB(0x79, 0xcd, 0xcd) }, - { "DarkSlateGray4", RGB(0x52, 0x8b, 0x8b) }, - { "DarkSlateGrey", RGB(0x2f, 0x4f, 0x4f) }, - { "DarkTurquoise", RGB(0x00, 0xce, 0xd1) }, - { "DarkViolet", RGB(0x94, 0x00, 0xd3) }, - { "DarkYellow", RGB(0xbb, 0xbb, 0x00) }, - { "DeepPink", RGB(0xff, 0x14, 0x93) }, - { "DeepPink1", RGB(0xff, 0x14, 0x93) }, - { "DeepPink2", RGB(0xee, 0x12, 0x89) }, - { "DeepPink3", RGB(0xcd, 0x10, 0x76) }, - { "DeepPink4", RGB(0x8b, 0xa, 0x50) }, - { "DeepSkyBlue", RGB(0x00, 0xbf, 0xff) }, - { "DeepSkyBlue1", RGB(0x0, 0xbf, 0xff) }, - { "DeepSkyBlue2", RGB(0x0, 0xb2, 0xee) }, - { "DeepSkyBlue3", RGB(0x0, 0x9a, 0xcd) }, - { "DeepSkyBlue4", RGB(0x0, 0x68, 0x8b) }, - { "DimGray", RGB(0x69, 0x69, 0x69) }, - { "DimGrey", RGB(0x69, 0x69, 0x69) }, - { "DodgerBlue", RGB(0x1e, 0x90, 0xff) }, - { "DodgerBlue1", RGB(0x1e, 0x90, 0xff) }, - { "DodgerBlue2", RGB(0x1c, 0x86, 0xee) }, - { "DodgerBlue3", RGB(0x18, 0x74, 0xcd) }, - { "DodgerBlue4", RGB(0x10, 0x4e, 0x8b) }, - { "Firebrick", RGB(0xb2, 0x22, 0x22) }, - { "Firebrick1", RGB(0xff, 0x30, 0x30) }, - { "Firebrick2", RGB(0xee, 0x2c, 0x2c) }, - { "Firebrick3", RGB(0xcd, 0x26, 0x26) }, - { "Firebrick4", RGB(0x8b, 0x1a, 0x1a) }, - { "FloralWhite", RGB(0xff, 0xfa, 0xf0) }, - { "ForestGreen", RGB(0x22, 0x8b, 0x22) }, - { "Fuchsia", RGB(0xff, 0x00, 0xff) }, - { "Gainsboro", RGB(0xdc, 0xdc, 0xdc) }, - { "GhostWhite", RGB(0xf8, 0xf8, 0xff) }, - { "Gold", RGB(0xff, 0xd7, 0x00) }, - { "Gold1", RGB(0xff, 0xd7, 0x0) }, - { "Gold2", RGB(0xee, 0xc9, 0x0) }, - { "Gold3", RGB(0xcd, 0xad, 0x0) }, - { "Gold4", RGB(0x8b, 0x75, 0x0) }, - { "GoldenRod", RGB(0xda, 0xa5, 0x20) }, - { "Goldenrod1", RGB(0xff, 0xc1, 0x25) }, - { "Goldenrod2", RGB(0xee, 0xb4, 0x22) }, - { "Goldenrod3", RGB(0xcd, 0x9b, 0x1d) }, - { "Goldenrod4", RGB(0x8b, 0x69, 0x14) }, - { "Gray", RGB(0x80, 0x80, 0x80) }, - { "Gray0", RGB(0x0, 0x0, 0x0) }, - { "Gray1", RGB(0x3, 0x3, 0x3) }, - { "Gray10", RGB(0x1a, 0x1a, 0x1a) }, - { "Gray100", RGB(0xff, 0xff, 0xff) }, - { "Gray11", RGB(0x1c, 0x1c, 0x1c) }, - { "Gray12", RGB(0x1f, 0x1f, 0x1f) }, - { "Gray13", RGB(0x21, 0x21, 0x21) }, - { "Gray14", RGB(0x24, 0x24, 0x24) }, - { "Gray15", RGB(0x26, 0x26, 0x26) }, - { "Gray16", RGB(0x29, 0x29, 0x29) }, - { "Gray17", RGB(0x2b, 0x2b, 0x2b) }, - { "Gray18", RGB(0x2e, 0x2e, 0x2e) }, - { "Gray19", RGB(0x30, 0x30, 0x30) }, - { "Gray2", RGB(0x5, 0x5, 0x5) }, - { "Gray20", RGB(0x33, 0x33, 0x33) }, - { "Gray21", RGB(0x36, 0x36, 0x36) }, - { "Gray22", RGB(0x38, 0x38, 0x38) }, - { "Gray23", RGB(0x3b, 0x3b, 0x3b) }, - { "Gray24", RGB(0x3d, 0x3d, 0x3d) }, - { "Gray25", RGB(0x40, 0x40, 0x40) }, - { "Gray26", RGB(0x42, 0x42, 0x42) }, - { "Gray27", RGB(0x45, 0x45, 0x45) }, - { "Gray28", RGB(0x47, 0x47, 0x47) }, - { "Gray29", RGB(0x4a, 0x4a, 0x4a) }, - { "Gray3", RGB(0x8, 0x8, 0x8) }, - { "Gray30", RGB(0x4d, 0x4d, 0x4d) }, - { "Gray31", RGB(0x4f, 0x4f, 0x4f) }, - { "Gray32", RGB(0x52, 0x52, 0x52) }, - { "Gray33", RGB(0x54, 0x54, 0x54) }, - { "Gray34", RGB(0x57, 0x57, 0x57) }, - { "Gray35", RGB(0x59, 0x59, 0x59) }, - { "Gray36", RGB(0x5c, 0x5c, 0x5c) }, - { "Gray37", RGB(0x5e, 0x5e, 0x5e) }, - { "Gray38", RGB(0x61, 0x61, 0x61) }, - { "Gray39", RGB(0x63, 0x63, 0x63) }, - { "Gray4", RGB(0xa, 0xa, 0xa) }, - { "Gray40", RGB(0x66, 0x66, 0x66) }, - { "Gray41", RGB(0x69, 0x69, 0x69) }, - { "Gray42", RGB(0x6b, 0x6b, 0x6b) }, - { "Gray43", RGB(0x6e, 0x6e, 0x6e) }, - { "Gray44", RGB(0x70, 0x70, 0x70) }, - { "Gray45", RGB(0x73, 0x73, 0x73) }, - { "Gray46", RGB(0x75, 0x75, 0x75) }, - { "Gray47", RGB(0x78, 0x78, 0x78) }, - { "Gray48", RGB(0x7a, 0x7a, 0x7a) }, - { "Gray49", RGB(0x7d, 0x7d, 0x7d) }, - { "Gray5", RGB(0xd, 0xd, 0xd) }, - { "Gray50", RGB(0x7f, 0x7f, 0x7f) }, - { "Gray51", RGB(0x82, 0x82, 0x82) }, - { "Gray52", RGB(0x85, 0x85, 0x85) }, - { "Gray53", RGB(0x87, 0x87, 0x87) }, - { "Gray54", RGB(0x8a, 0x8a, 0x8a) }, - { "Gray55", RGB(0x8c, 0x8c, 0x8c) }, - { "Gray56", RGB(0x8f, 0x8f, 0x8f) }, - { "Gray57", RGB(0x91, 0x91, 0x91) }, - { "Gray58", RGB(0x94, 0x94, 0x94) }, - { "Gray59", RGB(0x96, 0x96, 0x96) }, - { "Gray6", RGB(0xf, 0xf, 0xf) }, - { "Gray60", RGB(0x99, 0x99, 0x99) }, - { "Gray61", RGB(0x9c, 0x9c, 0x9c) }, - { "Gray62", RGB(0x9e, 0x9e, 0x9e) }, - { "Gray63", RGB(0xa1, 0xa1, 0xa1) }, - { "Gray64", RGB(0xa3, 0xa3, 0xa3) }, - { "Gray65", RGB(0xa6, 0xa6, 0xa6) }, - { "Gray66", RGB(0xa8, 0xa8, 0xa8) }, - { "Gray67", RGB(0xab, 0xab, 0xab) }, - { "Gray68", RGB(0xad, 0xad, 0xad) }, - { "Gray69", RGB(0xb0, 0xb0, 0xb0) }, - { "Gray7", RGB(0x12, 0x12, 0x12) }, - { "Gray70", RGB(0xb3, 0xb3, 0xb3) }, - { "Gray71", RGB(0xb5, 0xb5, 0xb5) }, - { "Gray72", RGB(0xb8, 0xb8, 0xb8) }, - { "Gray73", RGB(0xba, 0xba, 0xba) }, - { "Gray74", RGB(0xbd, 0xbd, 0xbd) }, - { "Gray75", RGB(0xbf, 0xbf, 0xbf) }, - { "Gray76", RGB(0xc2, 0xc2, 0xc2) }, - { "Gray77", RGB(0xc4, 0xc4, 0xc4) }, - { "Gray78", RGB(0xc7, 0xc7, 0xc7) }, - { "Gray79", RGB(0xc9, 0xc9, 0xc9) }, - { "Gray8", RGB(0x14, 0x14, 0x14) }, - { "Gray80", RGB(0xcc, 0xcc, 0xcc) }, - { "Gray81", RGB(0xcf, 0xcf, 0xcf) }, - { "Gray82", RGB(0xd1, 0xd1, 0xd1) }, - { "Gray83", RGB(0xd4, 0xd4, 0xd4) }, - { "Gray84", RGB(0xd6, 0xd6, 0xd6) }, - { "Gray85", RGB(0xd9, 0xd9, 0xd9) }, - { "Gray86", RGB(0xdb, 0xdb, 0xdb) }, - { "Gray87", RGB(0xde, 0xde, 0xde) }, - { "Gray88", RGB(0xe0, 0xe0, 0xe0) }, - { "Gray89", RGB(0xe3, 0xe3, 0xe3) }, - { "Gray9", RGB(0x17, 0x17, 0x17) }, - { "Gray90", RGB(0xe5, 0xe5, 0xe5) }, - { "Gray91", RGB(0xe8, 0xe8, 0xe8) }, - { "Gray92", RGB(0xeb, 0xeb, 0xeb) }, - { "Gray93", RGB(0xed, 0xed, 0xed) }, - { "Gray94", RGB(0xf0, 0xf0, 0xf0) }, - { "Gray95", RGB(0xf2, 0xf2, 0xf2) }, - { "Gray96", RGB(0xf5, 0xf5, 0xf5) }, - { "Gray97", RGB(0xf7, 0xf7, 0xf7) }, - { "Gray98", RGB(0xfa, 0xfa, 0xfa) }, - { "Gray99", RGB(0xfc, 0xfc, 0xfc) }, - { "Green", RGB(0x00, 0x80, 0x00) }, - { "Green1", RGB(0x0, 0xff, 0x0) }, - { "Green2", RGB(0x0, 0xee, 0x0) }, - { "Green3", RGB(0x0, 0xcd, 0x0) }, - { "Green4", RGB(0x0, 0x8b, 0x0) }, - { "GreenYellow", RGB(0xad, 0xff, 0x2f) }, - { "Grey", RGB(0x80, 0x80, 0x80) }, - { "Grey0", RGB(0x0, 0x0, 0x0) }, - { "Grey1", RGB(0x3, 0x3, 0x3) }, - { "Grey10", RGB(0x1a, 0x1a, 0x1a) }, - { "Grey100", RGB(0xff, 0xff, 0xff) }, - { "Grey11", RGB(0x1c, 0x1c, 0x1c) }, - { "Grey12", RGB(0x1f, 0x1f, 0x1f) }, - { "Grey13", RGB(0x21, 0x21, 0x21) }, - { "Grey14", RGB(0x24, 0x24, 0x24) }, - { "Grey15", RGB(0x26, 0x26, 0x26) }, - { "Grey16", RGB(0x29, 0x29, 0x29) }, - { "Grey17", RGB(0x2b, 0x2b, 0x2b) }, - { "Grey18", RGB(0x2e, 0x2e, 0x2e) }, - { "Grey19", RGB(0x30, 0x30, 0x30) }, - { "Grey2", RGB(0x5, 0x5, 0x5) }, - { "Grey20", RGB(0x33, 0x33, 0x33) }, - { "Grey21", RGB(0x36, 0x36, 0x36) }, - { "Grey22", RGB(0x38, 0x38, 0x38) }, - { "Grey23", RGB(0x3b, 0x3b, 0x3b) }, - { "Grey24", RGB(0x3d, 0x3d, 0x3d) }, - { "Grey25", RGB(0x40, 0x40, 0x40) }, - { "Grey26", RGB(0x42, 0x42, 0x42) }, - { "Grey27", RGB(0x45, 0x45, 0x45) }, - { "Grey28", RGB(0x47, 0x47, 0x47) }, - { "Grey29", RGB(0x4a, 0x4a, 0x4a) }, - { "Grey3", RGB(0x8, 0x8, 0x8) }, - { "Grey30", RGB(0x4d, 0x4d, 0x4d) }, - { "Grey31", RGB(0x4f, 0x4f, 0x4f) }, - { "Grey32", RGB(0x52, 0x52, 0x52) }, - { "Grey33", RGB(0x54, 0x54, 0x54) }, - { "Grey34", RGB(0x57, 0x57, 0x57) }, - { "Grey35", RGB(0x59, 0x59, 0x59) }, - { "Grey36", RGB(0x5c, 0x5c, 0x5c) }, - { "Grey37", RGB(0x5e, 0x5e, 0x5e) }, - { "Grey38", RGB(0x61, 0x61, 0x61) }, - { "Grey39", RGB(0x63, 0x63, 0x63) }, - { "Grey4", RGB(0xa, 0xa, 0xa) }, - { "Grey40", RGB(0x66, 0x66, 0x66) }, - { "Grey41", RGB(0x69, 0x69, 0x69) }, - { "Grey42", RGB(0x6b, 0x6b, 0x6b) }, - { "Grey43", RGB(0x6e, 0x6e, 0x6e) }, - { "Grey44", RGB(0x70, 0x70, 0x70) }, - { "Grey45", RGB(0x73, 0x73, 0x73) }, - { "Grey46", RGB(0x75, 0x75, 0x75) }, - { "Grey47", RGB(0x78, 0x78, 0x78) }, - { "Grey48", RGB(0x7a, 0x7a, 0x7a) }, - { "Grey49", RGB(0x7d, 0x7d, 0x7d) }, - { "Grey5", RGB(0xd, 0xd, 0xd) }, - { "Grey50", RGB(0x7f, 0x7f, 0x7f) }, - { "Grey51", RGB(0x82, 0x82, 0x82) }, - { "Grey52", RGB(0x85, 0x85, 0x85) }, - { "Grey53", RGB(0x87, 0x87, 0x87) }, - { "Grey54", RGB(0x8a, 0x8a, 0x8a) }, - { "Grey55", RGB(0x8c, 0x8c, 0x8c) }, - { "Grey56", RGB(0x8f, 0x8f, 0x8f) }, - { "Grey57", RGB(0x91, 0x91, 0x91) }, - { "Grey58", RGB(0x94, 0x94, 0x94) }, - { "Grey59", RGB(0x96, 0x96, 0x96) }, - { "Grey6", RGB(0xf, 0xf, 0xf) }, - { "Grey60", RGB(0x99, 0x99, 0x99) }, - { "Grey61", RGB(0x9c, 0x9c, 0x9c) }, - { "Grey62", RGB(0x9e, 0x9e, 0x9e) }, - { "Grey63", RGB(0xa1, 0xa1, 0xa1) }, - { "Grey64", RGB(0xa3, 0xa3, 0xa3) }, - { "Grey65", RGB(0xa6, 0xa6, 0xa6) }, - { "Grey66", RGB(0xa8, 0xa8, 0xa8) }, - { "Grey67", RGB(0xab, 0xab, 0xab) }, - { "Grey68", RGB(0xad, 0xad, 0xad) }, - { "Grey69", RGB(0xb0, 0xb0, 0xb0) }, - { "Grey7", RGB(0x12, 0x12, 0x12) }, - { "Grey70", RGB(0xb3, 0xb3, 0xb3) }, - { "Grey71", RGB(0xb5, 0xb5, 0xb5) }, - { "Grey72", RGB(0xb8, 0xb8, 0xb8) }, - { "Grey73", RGB(0xba, 0xba, 0xba) }, - { "Grey74", RGB(0xbd, 0xbd, 0xbd) }, - { "Grey75", RGB(0xbf, 0xbf, 0xbf) }, - { "Grey76", RGB(0xc2, 0xc2, 0xc2) }, - { "Grey77", RGB(0xc4, 0xc4, 0xc4) }, - { "Grey78", RGB(0xc7, 0xc7, 0xc7) }, - { "Grey79", RGB(0xc9, 0xc9, 0xc9) }, - { "Grey8", RGB(0x14, 0x14, 0x14) }, - { "Grey80", RGB(0xcc, 0xcc, 0xcc) }, - { "Grey81", RGB(0xcf, 0xcf, 0xcf) }, - { "Grey82", RGB(0xd1, 0xd1, 0xd1) }, - { "Grey83", RGB(0xd4, 0xd4, 0xd4) }, - { "Grey84", RGB(0xd6, 0xd6, 0xd6) }, - { "Grey85", RGB(0xd9, 0xd9, 0xd9) }, - { "Grey86", RGB(0xdb, 0xdb, 0xdb) }, - { "Grey87", RGB(0xde, 0xde, 0xde) }, - { "Grey88", RGB(0xe0, 0xe0, 0xe0) }, - { "Grey89", RGB(0xe3, 0xe3, 0xe3) }, - { "Grey9", RGB(0x17, 0x17, 0x17) }, - { "Grey90", RGB(0xe5, 0xe5, 0xe5) }, - { "Grey91", RGB(0xe8, 0xe8, 0xe8) }, - { "Grey92", RGB(0xeb, 0xeb, 0xeb) }, - { "Grey93", RGB(0xed, 0xed, 0xed) }, - { "Grey94", RGB(0xf0, 0xf0, 0xf0) }, - { "Grey95", RGB(0xf2, 0xf2, 0xf2) }, - { "Grey96", RGB(0xf5, 0xf5, 0xf5) }, - { "Grey97", RGB(0xf7, 0xf7, 0xf7) }, - { "Grey98", RGB(0xfa, 0xfa, 0xfa) }, - { "Grey99", RGB(0xfc, 0xfc, 0xfc) }, - { "Honeydew", RGB(0xf0, 0xff, 0xf0) }, - { "Honeydew1", RGB(0xf0, 0xff, 0xf0) }, - { "Honeydew2", RGB(0xe0, 0xee, 0xe0) }, - { "Honeydew3", RGB(0xc1, 0xcd, 0xc1) }, - { "Honeydew4", RGB(0x83, 0x8b, 0x83) }, - { "HotPink", RGB(0xff, 0x69, 0xb4) }, - { "HotPink1", RGB(0xff, 0x6e, 0xb4) }, - { "HotPink2", RGB(0xee, 0x6a, 0xa7) }, - { "HotPink3", RGB(0xcd, 0x60, 0x90) }, - { "HotPink4", RGB(0x8b, 0x3a, 0x62) }, - { "IndianRed", RGB(0xcd, 0x5c, 0x5c) }, - { "IndianRed1", RGB(0xff, 0x6a, 0x6a) }, - { "IndianRed2", RGB(0xee, 0x63, 0x63) }, - { "IndianRed3", RGB(0xcd, 0x55, 0x55) }, - { "IndianRed4", RGB(0x8b, 0x3a, 0x3a) }, - { "Indigo", RGB(0x4b, 0x00, 0x82) }, - { "Ivory", RGB(0xff, 0xff, 0xf0) }, - { "Ivory1", RGB(0xff, 0xff, 0xf0) }, - { "Ivory2", RGB(0xee, 0xee, 0xe0) }, - { "Ivory3", RGB(0xcd, 0xcd, 0xc1) }, - { "Ivory4", RGB(0x8b, 0x8b, 0x83) }, - { "Khaki", RGB(0xf0, 0xe6, 0x8c) }, - { "Khaki1", RGB(0xff, 0xf6, 0x8f) }, - { "Khaki2", RGB(0xee, 0xe6, 0x85) }, - { "Khaki3", RGB(0xcd, 0xc6, 0x73) }, - { "Khaki4", RGB(0x8b, 0x86, 0x4e) }, - { "Lavender", RGB(0xe6, 0xe6, 0xfa) }, - { "LavenderBlush", RGB(0xff, 0xf0, 0xf5) }, - { "LavenderBlush1", RGB(0xff, 0xf0, 0xf5) }, - { "LavenderBlush2", RGB(0xee, 0xe0, 0xe5) }, - { "LavenderBlush3", RGB(0xcd, 0xc1, 0xc5) }, - { "LavenderBlush4", RGB(0x8b, 0x83, 0x86) }, - { "LawnGreen", RGB(0x7c, 0xfc, 0x00) }, - { "LemonChiffon", RGB(0xff, 0xfa, 0xcd) }, - { "LemonChiffon1", RGB(0xff, 0xfa, 0xcd) }, - { "LemonChiffon2", RGB(0xee, 0xe9, 0xbf) }, - { "LemonChiffon3", RGB(0xcd, 0xc9, 0xa5) }, - { "LemonChiffon4", RGB(0x8b, 0x89, 0x70) }, - { "LightBlue", RGB(0xad, 0xd8, 0xe6) }, - { "LightBlue1", RGB(0xbf, 0xef, 0xff) }, - { "LightBlue2", RGB(0xb2, 0xdf, 0xee) }, - { "LightBlue3", RGB(0x9a, 0xc0, 0xcd) }, - { "LightBlue4", RGB(0x68, 0x83, 0x8b) }, - { "LightCoral", RGB(0xf0, 0x80, 0x80) }, - { "LightCyan", RGB(0xe0, 0xff, 0xff) }, - { "LightCyan1", RGB(0xe0, 0xff, 0xff) }, - { "LightCyan2", RGB(0xd1, 0xee, 0xee) }, - { "LightCyan3", RGB(0xb4, 0xcd, 0xcd) }, - { "LightCyan4", RGB(0x7a, 0x8b, 0x8b) }, - { "LightGoldenrod", RGB(0xee, 0xdd, 0x82) }, - { "LightGoldenrod1", RGB(0xff, 0xec, 0x8b) }, - { "LightGoldenrod2", RGB(0xee, 0xdc, 0x82) }, - { "LightGoldenrod3", RGB(0xcd, 0xbe, 0x70) }, - { "LightGoldenrod4", RGB(0x8b, 0x81, 0x4c) }, - { "LightGoldenRodYellow", RGB(0xfa, 0xfa, 0xd2) }, - { "LightGray", RGB(0xd3, 0xd3, 0xd3) }, - { "LightGreen", RGB(0x90, 0xee, 0x90) }, - { "LightGrey", RGB(0xd3, 0xd3, 0xd3) }, - { "LightMagenta", RGB(0xff, 0xbb, 0xff) }, - { "LightPink", RGB(0xff, 0xb6, 0xc1) }, - { "LightPink1", RGB(0xff, 0xae, 0xb9) }, - { "LightPink2", RGB(0xee, 0xa2, 0xad) }, - { "LightPink3", RGB(0xcd, 0x8c, 0x95) }, - { "LightPink4", RGB(0x8b, 0x5f, 0x65) }, - { "LightRed", RGB(0xff, 0xbb, 0xbb) }, - { "LightSalmon", RGB(0xff, 0xa0, 0x7a) }, - { "LightSalmon1", RGB(0xff, 0xa0, 0x7a) }, - { "LightSalmon2", RGB(0xee, 0x95, 0x72) }, - { "LightSalmon3", RGB(0xcd, 0x81, 0x62) }, - { "LightSalmon4", RGB(0x8b, 0x57, 0x42) }, - { "LightSeaGreen", RGB(0x20, 0xb2, 0xaa) }, - { "LightSkyBlue", RGB(0x87, 0xce, 0xfa) }, - { "LightSkyBlue1", RGB(0xb0, 0xe2, 0xff) }, - { "LightSkyBlue2", RGB(0xa4, 0xd3, 0xee) }, - { "LightSkyBlue3", RGB(0x8d, 0xb6, 0xcd) }, - { "LightSkyBlue4", RGB(0x60, 0x7b, 0x8b) }, - { "LightSlateBlue", RGB(0x84, 0x70, 0xff) }, - { "LightSlateGray", RGB(0x77, 0x88, 0x99) }, - { "LightSlateGrey", RGB(0x77, 0x88, 0x99) }, - { "LightSteelBlue", RGB(0xb0, 0xc4, 0xde) }, - { "LightSteelBlue1", RGB(0xca, 0xe1, 0xff) }, - { "LightSteelBlue2", RGB(0xbc, 0xd2, 0xee) }, - { "LightSteelBlue3", RGB(0xa2, 0xb5, 0xcd) }, - { "LightSteelBlue4", RGB(0x6e, 0x7b, 0x8b) }, - { "LightYellow", RGB(0xff, 0xff, 0xe0) }, - { "LightYellow1", RGB(0xff, 0xff, 0xe0) }, - { "LightYellow2", RGB(0xee, 0xee, 0xd1) }, - { "LightYellow3", RGB(0xcd, 0xcd, 0xb4) }, - { "LightYellow4", RGB(0x8b, 0x8b, 0x7a) }, - { "Lime", RGB(0x00, 0xff, 0x00) }, - { "LimeGreen", RGB(0x32, 0xcd, 0x32) }, - { "Linen", RGB(0xfa, 0xf0, 0xe6) }, - { "Magenta", RGB(0xff, 0x00, 0xff) }, - { "Magenta1", RGB(0xff, 0x0, 0xff) }, - { "Magenta2", RGB(0xee, 0x0, 0xee) }, - { "Magenta3", RGB(0xcd, 0x0, 0xcd) }, - { "Magenta4", RGB(0x8b, 0x0, 0x8b) }, - { "Maroon", RGB(0x80, 0x00, 0x00) }, - { "Maroon1", RGB(0xff, 0x34, 0xb3) }, - { "Maroon2", RGB(0xee, 0x30, 0xa7) }, - { "Maroon3", RGB(0xcd, 0x29, 0x90) }, - { "Maroon4", RGB(0x8b, 0x1c, 0x62) }, - { "MediumAquamarine", RGB(0x66, 0xcd, 0xaa) }, - { "MediumBlue", RGB(0x00, 0x00, 0xcd) }, - { "MediumOrchid", RGB(0xba, 0x55, 0xd3) }, - { "MediumOrchid1", RGB(0xe0, 0x66, 0xff) }, - { "MediumOrchid2", RGB(0xd1, 0x5f, 0xee) }, - { "MediumOrchid3", RGB(0xb4, 0x52, 0xcd) }, - { "MediumOrchid4", RGB(0x7a, 0x37, 0x8b) }, - { "MediumPurple", RGB(0x93, 0x70, 0xdb) }, - { "MediumPurple1", RGB(0xab, 0x82, 0xff) }, - { "MediumPurple2", RGB(0x9f, 0x79, 0xee) }, - { "MediumPurple3", RGB(0x89, 0x68, 0xcd) }, - { "MediumPurple4", RGB(0x5d, 0x47, 0x8b) }, - { "MediumSeaGreen", RGB(0x3c, 0xb3, 0x71) }, - { "MediumSlateBlue", RGB(0x7b, 0x68, 0xee) }, - { "MediumSpringGreen", RGB(0x00, 0xfa, 0x9a) }, - { "MediumTurquoise", RGB(0x48, 0xd1, 0xcc) }, - { "MediumVioletRed", RGB(0xc7, 0x15, 0x85) }, - { "MidnightBlue", RGB(0x19, 0x19, 0x70) }, - { "MintCream", RGB(0xf5, 0xff, 0xfa) }, - { "MistyRose", RGB(0xff, 0xe4, 0xe1) }, - { "MistyRose1", RGB(0xff, 0xe4, 0xe1) }, - { "MistyRose2", RGB(0xee, 0xd5, 0xd2) }, - { "MistyRose3", RGB(0xcd, 0xb7, 0xb5) }, - { "MistyRose4", RGB(0x8b, 0x7d, 0x7b) }, - { "Moccasin", RGB(0xff, 0xe4, 0xb5) }, - { "NavajoWhite", RGB(0xff, 0xde, 0xad) }, - { "NavajoWhite1", RGB(0xff, 0xde, 0xad) }, - { "NavajoWhite2", RGB(0xee, 0xcf, 0xa1) }, - { "NavajoWhite3", RGB(0xcd, 0xb3, 0x8b) }, - { "NavajoWhite4", RGB(0x8b, 0x79, 0x5e) }, - { "Navy", RGB(0x00, 0x00, 0x80) }, - { "NavyBlue", RGB(0x0, 0x0, 0x80) }, - { "OldLace", RGB(0xfd, 0xf5, 0xe6) }, - { "Olive", RGB(0x80, 0x80, 0x00) }, - { "OliveDrab", RGB(0x6b, 0x8e, 0x23) }, - { "OliveDrab1", RGB(0xc0, 0xff, 0x3e) }, - { "OliveDrab2", RGB(0xb3, 0xee, 0x3a) }, - { "OliveDrab3", RGB(0x9a, 0xcd, 0x32) }, - { "OliveDrab4", RGB(0x69, 0x8b, 0x22) }, - { "Orange", RGB(0xff, 0xa5, 0x00) }, - { "Orange1", RGB(0xff, 0xa5, 0x0) }, - { "Orange2", RGB(0xee, 0x9a, 0x0) }, - { "Orange3", RGB(0xcd, 0x85, 0x0) }, - { "Orange4", RGB(0x8b, 0x5a, 0x0) }, - { "OrangeRed", RGB(0xff, 0x45, 0x00) }, - { "OrangeRed1", RGB(0xff, 0x45, 0x0) }, - { "OrangeRed2", RGB(0xee, 0x40, 0x0) }, - { "OrangeRed3", RGB(0xcd, 0x37, 0x0) }, - { "OrangeRed4", RGB(0x8b, 0x25, 0x0) }, - { "Orchid", RGB(0xda, 0x70, 0xd6) }, - { "Orchid1", RGB(0xff, 0x83, 0xfa) }, - { "Orchid2", RGB(0xee, 0x7a, 0xe9) }, - { "Orchid3", RGB(0xcd, 0x69, 0xc9) }, - { "Orchid4", RGB(0x8b, 0x47, 0x89) }, - { "PaleGoldenRod", RGB(0xee, 0xe8, 0xaa) }, - { "PaleGreen", RGB(0x98, 0xfb, 0x98) }, - { "PaleGreen1", RGB(0x9a, 0xff, 0x9a) }, - { "PaleGreen2", RGB(0x90, 0xee, 0x90) }, - { "PaleGreen3", RGB(0x7c, 0xcd, 0x7c) }, - { "PaleGreen4", RGB(0x54, 0x8b, 0x54) }, - { "PaleTurquoise", RGB(0xaf, 0xee, 0xee) }, - { "PaleTurquoise1", RGB(0xbb, 0xff, 0xff) }, - { "PaleTurquoise2", RGB(0xae, 0xee, 0xee) }, - { "PaleTurquoise3", RGB(0x96, 0xcd, 0xcd) }, - { "PaleTurquoise4", RGB(0x66, 0x8b, 0x8b) }, - { "PaleVioletRed", RGB(0xdb, 0x70, 0x93) }, - { "PaleVioletRed1", RGB(0xff, 0x82, 0xab) }, - { "PaleVioletRed2", RGB(0xee, 0x79, 0x9f) }, - { "PaleVioletRed3", RGB(0xcd, 0x68, 0x89) }, - { "PaleVioletRed4", RGB(0x8b, 0x47, 0x5d) }, - { "PapayaWhip", RGB(0xff, 0xef, 0xd5) }, - { "PeachPuff", RGB(0xff, 0xda, 0xb9) }, - { "PeachPuff1", RGB(0xff, 0xda, 0xb9) }, - { "PeachPuff2", RGB(0xee, 0xcb, 0xad) }, - { "PeachPuff3", RGB(0xcd, 0xaf, 0x95) }, - { "PeachPuff4", RGB(0x8b, 0x77, 0x65) }, - { "Peru", RGB(0xcd, 0x85, 0x3f) }, - { "Pink", RGB(0xff, 0xc0, 0xcb) }, - { "Pink1", RGB(0xff, 0xb5, 0xc5) }, - { "Pink2", RGB(0xee, 0xa9, 0xb8) }, - { "Pink3", RGB(0xcd, 0x91, 0x9e) }, - { "Pink4", RGB(0x8b, 0x63, 0x6c) }, - { "Plum", RGB(0xdd, 0xa0, 0xdd) }, - { "Plum1", RGB(0xff, 0xbb, 0xff) }, - { "Plum2", RGB(0xee, 0xae, 0xee) }, - { "Plum3", RGB(0xcd, 0x96, 0xcd) }, - { "Plum4", RGB(0x8b, 0x66, 0x8b) }, - { "PowderBlue", RGB(0xb0, 0xe0, 0xe6) }, - { "Purple", RGB(0x80, 0x00, 0x80) }, - { "Purple1", RGB(0x9b, 0x30, 0xff) }, - { "Purple2", RGB(0x91, 0x2c, 0xee) }, - { "Purple3", RGB(0x7d, 0x26, 0xcd) }, - { "Purple4", RGB(0x55, 0x1a, 0x8b) }, - { "RebeccaPurple", RGB(0x66, 0x33, 0x99) }, - { "Red", RGB(0xff, 0x00, 0x00) }, - { "Red1", RGB(0xff, 0x0, 0x0) }, - { "Red2", RGB(0xee, 0x0, 0x0) }, - { "Red3", RGB(0xcd, 0x0, 0x0) }, - { "Red4", RGB(0x8b, 0x0, 0x0) }, - { "RosyBrown", RGB(0xbc, 0x8f, 0x8f) }, - { "RosyBrown1", RGB(0xff, 0xc1, 0xc1) }, - { "RosyBrown2", RGB(0xee, 0xb4, 0xb4) }, - { "RosyBrown3", RGB(0xcd, 0x9b, 0x9b) }, - { "RosyBrown4", RGB(0x8b, 0x69, 0x69) }, - { "RoyalBlue", RGB(0x41, 0x69, 0xe1) }, - { "RoyalBlue1", RGB(0x48, 0x76, 0xff) }, - { "RoyalBlue2", RGB(0x43, 0x6e, 0xee) }, - { "RoyalBlue3", RGB(0x3a, 0x5f, 0xcd) }, - { "RoyalBlue4", RGB(0x27, 0x40, 0x8b) }, - { "SaddleBrown", RGB(0x8b, 0x45, 0x13) }, - { "Salmon", RGB(0xfa, 0x80, 0x72) }, - { "Salmon1", RGB(0xff, 0x8c, 0x69) }, - { "Salmon2", RGB(0xee, 0x82, 0x62) }, - { "Salmon3", RGB(0xcd, 0x70, 0x54) }, - { "Salmon4", RGB(0x8b, 0x4c, 0x39) }, - { "SandyBrown", RGB(0xf4, 0xa4, 0x60) }, - { "SeaGreen", RGB(0x2e, 0x8b, 0x57) }, - { "SeaGreen1", RGB(0x54, 0xff, 0x9f) }, - { "SeaGreen2", RGB(0x4e, 0xee, 0x94) }, - { "SeaGreen3", RGB(0x43, 0xcd, 0x80) }, - { "SeaGreen4", RGB(0x2e, 0x8b, 0x57) }, - { "SeaShell", RGB(0xff, 0xf5, 0xee) }, - { "Seashell1", RGB(0xff, 0xf5, 0xee) }, - { "Seashell2", RGB(0xee, 0xe5, 0xde) }, - { "Seashell3", RGB(0xcd, 0xc5, 0xbf) }, - { "Seashell4", RGB(0x8b, 0x86, 0x82) }, - { "Sienna", RGB(0xa0, 0x52, 0x2d) }, - { "Sienna1", RGB(0xff, 0x82, 0x47) }, - { "Sienna2", RGB(0xee, 0x79, 0x42) }, - { "Sienna3", RGB(0xcd, 0x68, 0x39) }, - { "Sienna4", RGB(0x8b, 0x47, 0x26) }, - { "Silver", RGB(0xc0, 0xc0, 0xc0) }, - { "SkyBlue", RGB(0x87, 0xce, 0xeb) }, - { "SkyBlue1", RGB(0x87, 0xce, 0xff) }, - { "SkyBlue2", RGB(0x7e, 0xc0, 0xee) }, - { "SkyBlue3", RGB(0x6c, 0xa6, 0xcd) }, - { "SkyBlue4", RGB(0x4a, 0x70, 0x8b) }, - { "SlateBlue", RGB(0x6a, 0x5a, 0xcd) }, - { "SlateBlue1", RGB(0x83, 0x6f, 0xff) }, - { "SlateBlue2", RGB(0x7a, 0x67, 0xee) }, - { "SlateBlue3", RGB(0x69, 0x59, 0xcd) }, - { "SlateBlue4", RGB(0x47, 0x3c, 0x8b) }, - { "SlateGray", RGB(0x70, 0x80, 0x90) }, - { "SlateGray1", RGB(0xc6, 0xe2, 0xff) }, - { "SlateGray2", RGB(0xb9, 0xd3, 0xee) }, - { "SlateGray3", RGB(0x9f, 0xb6, 0xcd) }, - { "SlateGray4", RGB(0x6c, 0x7b, 0x8b) }, - { "SlateGrey", RGB(0x70, 0x80, 0x90) }, - { "Snow", RGB(0xff, 0xfa, 0xfa) }, - { "Snow1", RGB(0xff, 0xfa, 0xfa) }, - { "Snow2", RGB(0xee, 0xe9, 0xe9) }, - { "Snow3", RGB(0xcd, 0xc9, 0xc9) }, - { "Snow4", RGB(0x8b, 0x89, 0x89) }, - { "SpringGreen", RGB(0x00, 0xff, 0x7f) }, - { "SpringGreen1", RGB(0x0, 0xff, 0x7f) }, - { "SpringGreen2", RGB(0x0, 0xee, 0x76) }, - { "SpringGreen3", RGB(0x0, 0xcd, 0x66) }, - { "SpringGreen4", RGB(0x0, 0x8b, 0x45) }, - { "SteelBlue", RGB(0x46, 0x82, 0xb4) }, - { "SteelBlue1", RGB(0x63, 0xb8, 0xff) }, - { "SteelBlue2", RGB(0x5c, 0xac, 0xee) }, - { "SteelBlue3", RGB(0x4f, 0x94, 0xcd) }, - { "SteelBlue4", RGB(0x36, 0x64, 0x8b) }, - { "Tan", RGB(0xd2, 0xb4, 0x8c) }, - { "Tan1", RGB(0xff, 0xa5, 0x4f) }, - { "Tan2", RGB(0xee, 0x9a, 0x49) }, - { "Tan3", RGB(0xcd, 0x85, 0x3f) }, - { "Tan4", RGB(0x8b, 0x5a, 0x2b) }, - { "Teal", RGB(0x00, 0x80, 0x80) }, - { "Thistle", RGB(0xd8, 0xbf, 0xd8) }, - { "Thistle1", RGB(0xff, 0xe1, 0xff) }, - { "Thistle2", RGB(0xee, 0xd2, 0xee) }, - { "Thistle3", RGB(0xcd, 0xb5, 0xcd) }, - { "Thistle4", RGB(0x8b, 0x7b, 0x8b) }, - { "Tomato", RGB(0xff, 0x63, 0x47) }, - { "Tomato1", RGB(0xff, 0x63, 0x47) }, - { "Tomato2", RGB(0xee, 0x5c, 0x42) }, - { "Tomato3", RGB(0xcd, 0x4f, 0x39) }, - { "Tomato4", RGB(0x8b, 0x36, 0x26) }, - { "Turquoise", RGB(0x40, 0xe0, 0xd0) }, - { "Turquoise1", RGB(0x0, 0xf5, 0xff) }, - { "Turquoise2", RGB(0x0, 0xe5, 0xee) }, - { "Turquoise3", RGB(0x0, 0xc5, 0xcd) }, - { "Turquoise4", RGB(0x0, 0x86, 0x8b) }, - { "Violet", RGB(0xee, 0x82, 0xee) }, - { "VioletRed", RGB(0xd0, 0x20, 0x90) }, - { "VioletRed1", RGB(0xff, 0x3e, 0x96) }, - { "VioletRed2", RGB(0xee, 0x3a, 0x8c) }, - { "VioletRed3", RGB(0xcd, 0x32, 0x78) }, - { "VioletRed4", RGB(0x8b, 0x22, 0x52) }, - { "WebGray", RGB(0x80, 0x80, 0x80) }, - { "WebGreen", RGB(0x0, 0x80, 0x0) }, - { "WebGrey", RGB(0x80, 0x80, 0x80) }, - { "WebMaroon", RGB(0x80, 0x0, 0x0) }, - { "WebPurple", RGB(0x80, 0x0, 0x80) }, - { "Wheat", RGB(0xf5, 0xde, 0xb3) }, - { "Wheat1", RGB(0xff, 0xe7, 0xba) }, - { "Wheat2", RGB(0xee, 0xd8, 0xae) }, - { "Wheat3", RGB(0xcd, 0xba, 0x96) }, - { "Wheat4", RGB(0x8b, 0x7e, 0x66) }, - { "White", RGB(0xff, 0xff, 0xff) }, - { "WhiteSmoke", RGB(0xf5, 0xf5, 0xf5) }, - { "X11Gray", RGB(0xbe, 0xbe, 0xbe) }, - { "X11Green", RGB(0x0, 0xff, 0x0) }, - { "X11Grey", RGB(0xbe, 0xbe, 0xbe) }, - { "X11Maroon", RGB(0xb0, 0x30, 0x60) }, - { "X11Purple", RGB(0xa0, 0x20, 0xf0) }, - { "Yellow", RGB(0xff, 0xff, 0x00) }, - { "Yellow1", RGB(0xff, 0xff, 0x0) }, - { "Yellow2", RGB(0xee, 0xee, 0x0) }, - { "Yellow3", RGB(0xcd, 0xcd, 0x0) }, - { "Yellow4", RGB(0x8b, 0x8b, 0x0) }, - { "YellowGreen", RGB(0x9a, 0xcd, 0x32) }, + { "AliceBlue", RGB_(0xf0, 0xf8, 0xff) }, + { "AntiqueWhite", RGB_(0xfa, 0xeb, 0xd7) }, + { "AntiqueWhite1", RGB_(0xff, 0xef, 0xdb) }, + { "AntiqueWhite2", RGB_(0xee, 0xdf, 0xcc) }, + { "AntiqueWhite3", RGB_(0xcd, 0xc0, 0xb0) }, + { "AntiqueWhite4", RGB_(0x8b, 0x83, 0x78) }, + { "Aqua", RGB_(0x00, 0xff, 0xff) }, + { "Aquamarine", RGB_(0x7f, 0xff, 0xd4) }, + { "Aquamarine1", RGB_(0x7f, 0xff, 0xd4) }, + { "Aquamarine2", RGB_(0x76, 0xee, 0xc6) }, + { "Aquamarine3", RGB_(0x66, 0xcd, 0xaa) }, + { "Aquamarine4", RGB_(0x45, 0x8b, 0x74) }, + { "Azure", RGB_(0xf0, 0xff, 0xff) }, + { "Azure1", RGB_(0xf0, 0xff, 0xff) }, + { "Azure2", RGB_(0xe0, 0xee, 0xee) }, + { "Azure3", RGB_(0xc1, 0xcd, 0xcd) }, + { "Azure4", RGB_(0x83, 0x8b, 0x8b) }, + { "Beige", RGB_(0xf5, 0xf5, 0xdc) }, + { "Bisque", RGB_(0xff, 0xe4, 0xc4) }, + { "Bisque1", RGB_(0xff, 0xe4, 0xc4) }, + { "Bisque2", RGB_(0xee, 0xd5, 0xb7) }, + { "Bisque3", RGB_(0xcd, 0xb7, 0x9e) }, + { "Bisque4", RGB_(0x8b, 0x7d, 0x6b) }, + { "Black", RGB_(0x00, 0x00, 0x00) }, + { "BlanchedAlmond", RGB_(0xff, 0xeb, 0xcd) }, + { "Blue", RGB_(0x00, 0x00, 0xff) }, + { "Blue1", RGB_(0x0, 0x0, 0xff) }, + { "Blue2", RGB_(0x0, 0x0, 0xee) }, + { "Blue3", RGB_(0x0, 0x0, 0xcd) }, + { "Blue4", RGB_(0x0, 0x0, 0x8b) }, + { "BlueViolet", RGB_(0x8a, 0x2b, 0xe2) }, + { "Brown", RGB_(0xa5, 0x2a, 0x2a) }, + { "Brown1", RGB_(0xff, 0x40, 0x40) }, + { "Brown2", RGB_(0xee, 0x3b, 0x3b) }, + { "Brown3", RGB_(0xcd, 0x33, 0x33) }, + { "Brown4", RGB_(0x8b, 0x23, 0x23) }, + { "BurlyWood", RGB_(0xde, 0xb8, 0x87) }, + { "Burlywood1", RGB_(0xff, 0xd3, 0x9b) }, + { "Burlywood2", RGB_(0xee, 0xc5, 0x91) }, + { "Burlywood3", RGB_(0xcd, 0xaa, 0x7d) }, + { "Burlywood4", RGB_(0x8b, 0x73, 0x55) }, + { "CadetBlue", RGB_(0x5f, 0x9e, 0xa0) }, + { "CadetBlue1", RGB_(0x98, 0xf5, 0xff) }, + { "CadetBlue2", RGB_(0x8e, 0xe5, 0xee) }, + { "CadetBlue3", RGB_(0x7a, 0xc5, 0xcd) }, + { "CadetBlue4", RGB_(0x53, 0x86, 0x8b) }, + { "ChartReuse", RGB_(0x7f, 0xff, 0x00) }, + { "Chartreuse1", RGB_(0x7f, 0xff, 0x0) }, + { "Chartreuse2", RGB_(0x76, 0xee, 0x0) }, + { "Chartreuse3", RGB_(0x66, 0xcd, 0x0) }, + { "Chartreuse4", RGB_(0x45, 0x8b, 0x0) }, + { "Chocolate", RGB_(0xd2, 0x69, 0x1e) }, + { "Chocolate1", RGB_(0xff, 0x7f, 0x24) }, + { "Chocolate2", RGB_(0xee, 0x76, 0x21) }, + { "Chocolate3", RGB_(0xcd, 0x66, 0x1d) }, + { "Chocolate4", RGB_(0x8b, 0x45, 0x13) }, + { "Coral", RGB_(0xff, 0x7f, 0x50) }, + { "Coral1", RGB_(0xff, 0x72, 0x56) }, + { "Coral2", RGB_(0xee, 0x6a, 0x50) }, + { "Coral3", RGB_(0xcd, 0x5b, 0x45) }, + { "Coral4", RGB_(0x8b, 0x3e, 0x2f) }, + { "CornFlowerBlue", RGB_(0x64, 0x95, 0xed) }, + { "Cornsilk", RGB_(0xff, 0xf8, 0xdc) }, + { "Cornsilk1", RGB_(0xff, 0xf8, 0xdc) }, + { "Cornsilk2", RGB_(0xee, 0xe8, 0xcd) }, + { "Cornsilk3", RGB_(0xcd, 0xc8, 0xb1) }, + { "Cornsilk4", RGB_(0x8b, 0x88, 0x78) }, + { "Crimson", RGB_(0xdc, 0x14, 0x3c) }, + { "Cyan", RGB_(0x00, 0xff, 0xff) }, + { "Cyan1", RGB_(0x0, 0xff, 0xff) }, + { "Cyan2", RGB_(0x0, 0xee, 0xee) }, + { "Cyan3", RGB_(0x0, 0xcd, 0xcd) }, + { "Cyan4", RGB_(0x0, 0x8b, 0x8b) }, + { "DarkBlue", RGB_(0x00, 0x00, 0x8b) }, + { "DarkCyan", RGB_(0x00, 0x8b, 0x8b) }, + { "DarkGoldenRod", RGB_(0xb8, 0x86, 0x0b) }, + { "DarkGoldenrod1", RGB_(0xff, 0xb9, 0xf) }, + { "DarkGoldenrod2", RGB_(0xee, 0xad, 0xe) }, + { "DarkGoldenrod3", RGB_(0xcd, 0x95, 0xc) }, + { "DarkGoldenrod4", RGB_(0x8b, 0x65, 0x8) }, + { "DarkGray", RGB_(0xa9, 0xa9, 0xa9) }, + { "DarkGreen", RGB_(0x00, 0x64, 0x00) }, + { "DarkGrey", RGB_(0xa9, 0xa9, 0xa9) }, + { "DarkKhaki", RGB_(0xbd, 0xb7, 0x6b) }, + { "DarkMagenta", RGB_(0x8b, 0x00, 0x8b) }, + { "DarkOliveGreen", RGB_(0x55, 0x6b, 0x2f) }, + { "DarkOliveGreen1", RGB_(0xca, 0xff, 0x70) }, + { "DarkOliveGreen2", RGB_(0xbc, 0xee, 0x68) }, + { "DarkOliveGreen3", RGB_(0xa2, 0xcd, 0x5a) }, + { "DarkOliveGreen4", RGB_(0x6e, 0x8b, 0x3d) }, + { "DarkOrange", RGB_(0xff, 0x8c, 0x00) }, + { "DarkOrange1", RGB_(0xff, 0x7f, 0x0) }, + { "DarkOrange2", RGB_(0xee, 0x76, 0x0) }, + { "DarkOrange3", RGB_(0xcd, 0x66, 0x0) }, + { "DarkOrange4", RGB_(0x8b, 0x45, 0x0) }, + { "DarkOrchid", RGB_(0x99, 0x32, 0xcc) }, + { "DarkOrchid1", RGB_(0xbf, 0x3e, 0xff) }, + { "DarkOrchid2", RGB_(0xb2, 0x3a, 0xee) }, + { "DarkOrchid3", RGB_(0x9a, 0x32, 0xcd) }, + { "DarkOrchid4", RGB_(0x68, 0x22, 0x8b) }, + { "DarkRed", RGB_(0x8b, 0x00, 0x00) }, + { "DarkSalmon", RGB_(0xe9, 0x96, 0x7a) }, + { "DarkSeaGreen", RGB_(0x8f, 0xbc, 0x8f) }, + { "DarkSeaGreen1", RGB_(0xc1, 0xff, 0xc1) }, + { "DarkSeaGreen2", RGB_(0xb4, 0xee, 0xb4) }, + { "DarkSeaGreen3", RGB_(0x9b, 0xcd, 0x9b) }, + { "DarkSeaGreen4", RGB_(0x69, 0x8b, 0x69) }, + { "DarkSlateBlue", RGB_(0x48, 0x3d, 0x8b) }, + { "DarkSlateGray", RGB_(0x2f, 0x4f, 0x4f) }, + { "DarkSlateGray1", RGB_(0x97, 0xff, 0xff) }, + { "DarkSlateGray2", RGB_(0x8d, 0xee, 0xee) }, + { "DarkSlateGray3", RGB_(0x79, 0xcd, 0xcd) }, + { "DarkSlateGray4", RGB_(0x52, 0x8b, 0x8b) }, + { "DarkSlateGrey", RGB_(0x2f, 0x4f, 0x4f) }, + { "DarkTurquoise", RGB_(0x00, 0xce, 0xd1) }, + { "DarkViolet", RGB_(0x94, 0x00, 0xd3) }, + { "DarkYellow", RGB_(0xbb, 0xbb, 0x00) }, + { "DeepPink", RGB_(0xff, 0x14, 0x93) }, + { "DeepPink1", RGB_(0xff, 0x14, 0x93) }, + { "DeepPink2", RGB_(0xee, 0x12, 0x89) }, + { "DeepPink3", RGB_(0xcd, 0x10, 0x76) }, + { "DeepPink4", RGB_(0x8b, 0xa, 0x50) }, + { "DeepSkyBlue", RGB_(0x00, 0xbf, 0xff) }, + { "DeepSkyBlue1", RGB_(0x0, 0xbf, 0xff) }, + { "DeepSkyBlue2", RGB_(0x0, 0xb2, 0xee) }, + { "DeepSkyBlue3", RGB_(0x0, 0x9a, 0xcd) }, + { "DeepSkyBlue4", RGB_(0x0, 0x68, 0x8b) }, + { "DimGray", RGB_(0x69, 0x69, 0x69) }, + { "DimGrey", RGB_(0x69, 0x69, 0x69) }, + { "DodgerBlue", RGB_(0x1e, 0x90, 0xff) }, + { "DodgerBlue1", RGB_(0x1e, 0x90, 0xff) }, + { "DodgerBlue2", RGB_(0x1c, 0x86, 0xee) }, + { "DodgerBlue3", RGB_(0x18, 0x74, 0xcd) }, + { "DodgerBlue4", RGB_(0x10, 0x4e, 0x8b) }, + { "Firebrick", RGB_(0xb2, 0x22, 0x22) }, + { "Firebrick1", RGB_(0xff, 0x30, 0x30) }, + { "Firebrick2", RGB_(0xee, 0x2c, 0x2c) }, + { "Firebrick3", RGB_(0xcd, 0x26, 0x26) }, + { "Firebrick4", RGB_(0x8b, 0x1a, 0x1a) }, + { "FloralWhite", RGB_(0xff, 0xfa, 0xf0) }, + { "ForestGreen", RGB_(0x22, 0x8b, 0x22) }, + { "Fuchsia", RGB_(0xff, 0x00, 0xff) }, + { "Gainsboro", RGB_(0xdc, 0xdc, 0xdc) }, + { "GhostWhite", RGB_(0xf8, 0xf8, 0xff) }, + { "Gold", RGB_(0xff, 0xd7, 0x00) }, + { "Gold1", RGB_(0xff, 0xd7, 0x0) }, + { "Gold2", RGB_(0xee, 0xc9, 0x0) }, + { "Gold3", RGB_(0xcd, 0xad, 0x0) }, + { "Gold4", RGB_(0x8b, 0x75, 0x0) }, + { "GoldenRod", RGB_(0xda, 0xa5, 0x20) }, + { "Goldenrod1", RGB_(0xff, 0xc1, 0x25) }, + { "Goldenrod2", RGB_(0xee, 0xb4, 0x22) }, + { "Goldenrod3", RGB_(0xcd, 0x9b, 0x1d) }, + { "Goldenrod4", RGB_(0x8b, 0x69, 0x14) }, + { "Gray", RGB_(0x80, 0x80, 0x80) }, + { "Gray0", RGB_(0x0, 0x0, 0x0) }, + { "Gray1", RGB_(0x3, 0x3, 0x3) }, + { "Gray10", RGB_(0x1a, 0x1a, 0x1a) }, + { "Gray100", RGB_(0xff, 0xff, 0xff) }, + { "Gray11", RGB_(0x1c, 0x1c, 0x1c) }, + { "Gray12", RGB_(0x1f, 0x1f, 0x1f) }, + { "Gray13", RGB_(0x21, 0x21, 0x21) }, + { "Gray14", RGB_(0x24, 0x24, 0x24) }, + { "Gray15", RGB_(0x26, 0x26, 0x26) }, + { "Gray16", RGB_(0x29, 0x29, 0x29) }, + { "Gray17", RGB_(0x2b, 0x2b, 0x2b) }, + { "Gray18", RGB_(0x2e, 0x2e, 0x2e) }, + { "Gray19", RGB_(0x30, 0x30, 0x30) }, + { "Gray2", RGB_(0x5, 0x5, 0x5) }, + { "Gray20", RGB_(0x33, 0x33, 0x33) }, + { "Gray21", RGB_(0x36, 0x36, 0x36) }, + { "Gray22", RGB_(0x38, 0x38, 0x38) }, + { "Gray23", RGB_(0x3b, 0x3b, 0x3b) }, + { "Gray24", RGB_(0x3d, 0x3d, 0x3d) }, + { "Gray25", RGB_(0x40, 0x40, 0x40) }, + { "Gray26", RGB_(0x42, 0x42, 0x42) }, + { "Gray27", RGB_(0x45, 0x45, 0x45) }, + { "Gray28", RGB_(0x47, 0x47, 0x47) }, + { "Gray29", RGB_(0x4a, 0x4a, 0x4a) }, + { "Gray3", RGB_(0x8, 0x8, 0x8) }, + { "Gray30", RGB_(0x4d, 0x4d, 0x4d) }, + { "Gray31", RGB_(0x4f, 0x4f, 0x4f) }, + { "Gray32", RGB_(0x52, 0x52, 0x52) }, + { "Gray33", RGB_(0x54, 0x54, 0x54) }, + { "Gray34", RGB_(0x57, 0x57, 0x57) }, + { "Gray35", RGB_(0x59, 0x59, 0x59) }, + { "Gray36", RGB_(0x5c, 0x5c, 0x5c) }, + { "Gray37", RGB_(0x5e, 0x5e, 0x5e) }, + { "Gray38", RGB_(0x61, 0x61, 0x61) }, + { "Gray39", RGB_(0x63, 0x63, 0x63) }, + { "Gray4", RGB_(0xa, 0xa, 0xa) }, + { "Gray40", RGB_(0x66, 0x66, 0x66) }, + { "Gray41", RGB_(0x69, 0x69, 0x69) }, + { "Gray42", RGB_(0x6b, 0x6b, 0x6b) }, + { "Gray43", RGB_(0x6e, 0x6e, 0x6e) }, + { "Gray44", RGB_(0x70, 0x70, 0x70) }, + { "Gray45", RGB_(0x73, 0x73, 0x73) }, + { "Gray46", RGB_(0x75, 0x75, 0x75) }, + { "Gray47", RGB_(0x78, 0x78, 0x78) }, + { "Gray48", RGB_(0x7a, 0x7a, 0x7a) }, + { "Gray49", RGB_(0x7d, 0x7d, 0x7d) }, + { "Gray5", RGB_(0xd, 0xd, 0xd) }, + { "Gray50", RGB_(0x7f, 0x7f, 0x7f) }, + { "Gray51", RGB_(0x82, 0x82, 0x82) }, + { "Gray52", RGB_(0x85, 0x85, 0x85) }, + { "Gray53", RGB_(0x87, 0x87, 0x87) }, + { "Gray54", RGB_(0x8a, 0x8a, 0x8a) }, + { "Gray55", RGB_(0x8c, 0x8c, 0x8c) }, + { "Gray56", RGB_(0x8f, 0x8f, 0x8f) }, + { "Gray57", RGB_(0x91, 0x91, 0x91) }, + { "Gray58", RGB_(0x94, 0x94, 0x94) }, + { "Gray59", RGB_(0x96, 0x96, 0x96) }, + { "Gray6", RGB_(0xf, 0xf, 0xf) }, + { "Gray60", RGB_(0x99, 0x99, 0x99) }, + { "Gray61", RGB_(0x9c, 0x9c, 0x9c) }, + { "Gray62", RGB_(0x9e, 0x9e, 0x9e) }, + { "Gray63", RGB_(0xa1, 0xa1, 0xa1) }, + { "Gray64", RGB_(0xa3, 0xa3, 0xa3) }, + { "Gray65", RGB_(0xa6, 0xa6, 0xa6) }, + { "Gray66", RGB_(0xa8, 0xa8, 0xa8) }, + { "Gray67", RGB_(0xab, 0xab, 0xab) }, + { "Gray68", RGB_(0xad, 0xad, 0xad) }, + { "Gray69", RGB_(0xb0, 0xb0, 0xb0) }, + { "Gray7", RGB_(0x12, 0x12, 0x12) }, + { "Gray70", RGB_(0xb3, 0xb3, 0xb3) }, + { "Gray71", RGB_(0xb5, 0xb5, 0xb5) }, + { "Gray72", RGB_(0xb8, 0xb8, 0xb8) }, + { "Gray73", RGB_(0xba, 0xba, 0xba) }, + { "Gray74", RGB_(0xbd, 0xbd, 0xbd) }, + { "Gray75", RGB_(0xbf, 0xbf, 0xbf) }, + { "Gray76", RGB_(0xc2, 0xc2, 0xc2) }, + { "Gray77", RGB_(0xc4, 0xc4, 0xc4) }, + { "Gray78", RGB_(0xc7, 0xc7, 0xc7) }, + { "Gray79", RGB_(0xc9, 0xc9, 0xc9) }, + { "Gray8", RGB_(0x14, 0x14, 0x14) }, + { "Gray80", RGB_(0xcc, 0xcc, 0xcc) }, + { "Gray81", RGB_(0xcf, 0xcf, 0xcf) }, + { "Gray82", RGB_(0xd1, 0xd1, 0xd1) }, + { "Gray83", RGB_(0xd4, 0xd4, 0xd4) }, + { "Gray84", RGB_(0xd6, 0xd6, 0xd6) }, + { "Gray85", RGB_(0xd9, 0xd9, 0xd9) }, + { "Gray86", RGB_(0xdb, 0xdb, 0xdb) }, + { "Gray87", RGB_(0xde, 0xde, 0xde) }, + { "Gray88", RGB_(0xe0, 0xe0, 0xe0) }, + { "Gray89", RGB_(0xe3, 0xe3, 0xe3) }, + { "Gray9", RGB_(0x17, 0x17, 0x17) }, + { "Gray90", RGB_(0xe5, 0xe5, 0xe5) }, + { "Gray91", RGB_(0xe8, 0xe8, 0xe8) }, + { "Gray92", RGB_(0xeb, 0xeb, 0xeb) }, + { "Gray93", RGB_(0xed, 0xed, 0xed) }, + { "Gray94", RGB_(0xf0, 0xf0, 0xf0) }, + { "Gray95", RGB_(0xf2, 0xf2, 0xf2) }, + { "Gray96", RGB_(0xf5, 0xf5, 0xf5) }, + { "Gray97", RGB_(0xf7, 0xf7, 0xf7) }, + { "Gray98", RGB_(0xfa, 0xfa, 0xfa) }, + { "Gray99", RGB_(0xfc, 0xfc, 0xfc) }, + { "Green", RGB_(0x00, 0x80, 0x00) }, + { "Green1", RGB_(0x0, 0xff, 0x0) }, + { "Green2", RGB_(0x0, 0xee, 0x0) }, + { "Green3", RGB_(0x0, 0xcd, 0x0) }, + { "Green4", RGB_(0x0, 0x8b, 0x0) }, + { "GreenYellow", RGB_(0xad, 0xff, 0x2f) }, + { "Grey", RGB_(0x80, 0x80, 0x80) }, + { "Grey0", RGB_(0x0, 0x0, 0x0) }, + { "Grey1", RGB_(0x3, 0x3, 0x3) }, + { "Grey10", RGB_(0x1a, 0x1a, 0x1a) }, + { "Grey100", RGB_(0xff, 0xff, 0xff) }, + { "Grey11", RGB_(0x1c, 0x1c, 0x1c) }, + { "Grey12", RGB_(0x1f, 0x1f, 0x1f) }, + { "Grey13", RGB_(0x21, 0x21, 0x21) }, + { "Grey14", RGB_(0x24, 0x24, 0x24) }, + { "Grey15", RGB_(0x26, 0x26, 0x26) }, + { "Grey16", RGB_(0x29, 0x29, 0x29) }, + { "Grey17", RGB_(0x2b, 0x2b, 0x2b) }, + { "Grey18", RGB_(0x2e, 0x2e, 0x2e) }, + { "Grey19", RGB_(0x30, 0x30, 0x30) }, + { "Grey2", RGB_(0x5, 0x5, 0x5) }, + { "Grey20", RGB_(0x33, 0x33, 0x33) }, + { "Grey21", RGB_(0x36, 0x36, 0x36) }, + { "Grey22", RGB_(0x38, 0x38, 0x38) }, + { "Grey23", RGB_(0x3b, 0x3b, 0x3b) }, + { "Grey24", RGB_(0x3d, 0x3d, 0x3d) }, + { "Grey25", RGB_(0x40, 0x40, 0x40) }, + { "Grey26", RGB_(0x42, 0x42, 0x42) }, + { "Grey27", RGB_(0x45, 0x45, 0x45) }, + { "Grey28", RGB_(0x47, 0x47, 0x47) }, + { "Grey29", RGB_(0x4a, 0x4a, 0x4a) }, + { "Grey3", RGB_(0x8, 0x8, 0x8) }, + { "Grey30", RGB_(0x4d, 0x4d, 0x4d) }, + { "Grey31", RGB_(0x4f, 0x4f, 0x4f) }, + { "Grey32", RGB_(0x52, 0x52, 0x52) }, + { "Grey33", RGB_(0x54, 0x54, 0x54) }, + { "Grey34", RGB_(0x57, 0x57, 0x57) }, + { "Grey35", RGB_(0x59, 0x59, 0x59) }, + { "Grey36", RGB_(0x5c, 0x5c, 0x5c) }, + { "Grey37", RGB_(0x5e, 0x5e, 0x5e) }, + { "Grey38", RGB_(0x61, 0x61, 0x61) }, + { "Grey39", RGB_(0x63, 0x63, 0x63) }, + { "Grey4", RGB_(0xa, 0xa, 0xa) }, + { "Grey40", RGB_(0x66, 0x66, 0x66) }, + { "Grey41", RGB_(0x69, 0x69, 0x69) }, + { "Grey42", RGB_(0x6b, 0x6b, 0x6b) }, + { "Grey43", RGB_(0x6e, 0x6e, 0x6e) }, + { "Grey44", RGB_(0x70, 0x70, 0x70) }, + { "Grey45", RGB_(0x73, 0x73, 0x73) }, + { "Grey46", RGB_(0x75, 0x75, 0x75) }, + { "Grey47", RGB_(0x78, 0x78, 0x78) }, + { "Grey48", RGB_(0x7a, 0x7a, 0x7a) }, + { "Grey49", RGB_(0x7d, 0x7d, 0x7d) }, + { "Grey5", RGB_(0xd, 0xd, 0xd) }, + { "Grey50", RGB_(0x7f, 0x7f, 0x7f) }, + { "Grey51", RGB_(0x82, 0x82, 0x82) }, + { "Grey52", RGB_(0x85, 0x85, 0x85) }, + { "Grey53", RGB_(0x87, 0x87, 0x87) }, + { "Grey54", RGB_(0x8a, 0x8a, 0x8a) }, + { "Grey55", RGB_(0x8c, 0x8c, 0x8c) }, + { "Grey56", RGB_(0x8f, 0x8f, 0x8f) }, + { "Grey57", RGB_(0x91, 0x91, 0x91) }, + { "Grey58", RGB_(0x94, 0x94, 0x94) }, + { "Grey59", RGB_(0x96, 0x96, 0x96) }, + { "Grey6", RGB_(0xf, 0xf, 0xf) }, + { "Grey60", RGB_(0x99, 0x99, 0x99) }, + { "Grey61", RGB_(0x9c, 0x9c, 0x9c) }, + { "Grey62", RGB_(0x9e, 0x9e, 0x9e) }, + { "Grey63", RGB_(0xa1, 0xa1, 0xa1) }, + { "Grey64", RGB_(0xa3, 0xa3, 0xa3) }, + { "Grey65", RGB_(0xa6, 0xa6, 0xa6) }, + { "Grey66", RGB_(0xa8, 0xa8, 0xa8) }, + { "Grey67", RGB_(0xab, 0xab, 0xab) }, + { "Grey68", RGB_(0xad, 0xad, 0xad) }, + { "Grey69", RGB_(0xb0, 0xb0, 0xb0) }, + { "Grey7", RGB_(0x12, 0x12, 0x12) }, + { "Grey70", RGB_(0xb3, 0xb3, 0xb3) }, + { "Grey71", RGB_(0xb5, 0xb5, 0xb5) }, + { "Grey72", RGB_(0xb8, 0xb8, 0xb8) }, + { "Grey73", RGB_(0xba, 0xba, 0xba) }, + { "Grey74", RGB_(0xbd, 0xbd, 0xbd) }, + { "Grey75", RGB_(0xbf, 0xbf, 0xbf) }, + { "Grey76", RGB_(0xc2, 0xc2, 0xc2) }, + { "Grey77", RGB_(0xc4, 0xc4, 0xc4) }, + { "Grey78", RGB_(0xc7, 0xc7, 0xc7) }, + { "Grey79", RGB_(0xc9, 0xc9, 0xc9) }, + { "Grey8", RGB_(0x14, 0x14, 0x14) }, + { "Grey80", RGB_(0xcc, 0xcc, 0xcc) }, + { "Grey81", RGB_(0xcf, 0xcf, 0xcf) }, + { "Grey82", RGB_(0xd1, 0xd1, 0xd1) }, + { "Grey83", RGB_(0xd4, 0xd4, 0xd4) }, + { "Grey84", RGB_(0xd6, 0xd6, 0xd6) }, + { "Grey85", RGB_(0xd9, 0xd9, 0xd9) }, + { "Grey86", RGB_(0xdb, 0xdb, 0xdb) }, + { "Grey87", RGB_(0xde, 0xde, 0xde) }, + { "Grey88", RGB_(0xe0, 0xe0, 0xe0) }, + { "Grey89", RGB_(0xe3, 0xe3, 0xe3) }, + { "Grey9", RGB_(0x17, 0x17, 0x17) }, + { "Grey90", RGB_(0xe5, 0xe5, 0xe5) }, + { "Grey91", RGB_(0xe8, 0xe8, 0xe8) }, + { "Grey92", RGB_(0xeb, 0xeb, 0xeb) }, + { "Grey93", RGB_(0xed, 0xed, 0xed) }, + { "Grey94", RGB_(0xf0, 0xf0, 0xf0) }, + { "Grey95", RGB_(0xf2, 0xf2, 0xf2) }, + { "Grey96", RGB_(0xf5, 0xf5, 0xf5) }, + { "Grey97", RGB_(0xf7, 0xf7, 0xf7) }, + { "Grey98", RGB_(0xfa, 0xfa, 0xfa) }, + { "Grey99", RGB_(0xfc, 0xfc, 0xfc) }, + { "Honeydew", RGB_(0xf0, 0xff, 0xf0) }, + { "Honeydew1", RGB_(0xf0, 0xff, 0xf0) }, + { "Honeydew2", RGB_(0xe0, 0xee, 0xe0) }, + { "Honeydew3", RGB_(0xc1, 0xcd, 0xc1) }, + { "Honeydew4", RGB_(0x83, 0x8b, 0x83) }, + { "HotPink", RGB_(0xff, 0x69, 0xb4) }, + { "HotPink1", RGB_(0xff, 0x6e, 0xb4) }, + { "HotPink2", RGB_(0xee, 0x6a, 0xa7) }, + { "HotPink3", RGB_(0xcd, 0x60, 0x90) }, + { "HotPink4", RGB_(0x8b, 0x3a, 0x62) }, + { "IndianRed", RGB_(0xcd, 0x5c, 0x5c) }, + { "IndianRed1", RGB_(0xff, 0x6a, 0x6a) }, + { "IndianRed2", RGB_(0xee, 0x63, 0x63) }, + { "IndianRed3", RGB_(0xcd, 0x55, 0x55) }, + { "IndianRed4", RGB_(0x8b, 0x3a, 0x3a) }, + { "Indigo", RGB_(0x4b, 0x00, 0x82) }, + { "Ivory", RGB_(0xff, 0xff, 0xf0) }, + { "Ivory1", RGB_(0xff, 0xff, 0xf0) }, + { "Ivory2", RGB_(0xee, 0xee, 0xe0) }, + { "Ivory3", RGB_(0xcd, 0xcd, 0xc1) }, + { "Ivory4", RGB_(0x8b, 0x8b, 0x83) }, + { "Khaki", RGB_(0xf0, 0xe6, 0x8c) }, + { "Khaki1", RGB_(0xff, 0xf6, 0x8f) }, + { "Khaki2", RGB_(0xee, 0xe6, 0x85) }, + { "Khaki3", RGB_(0xcd, 0xc6, 0x73) }, + { "Khaki4", RGB_(0x8b, 0x86, 0x4e) }, + { "Lavender", RGB_(0xe6, 0xe6, 0xfa) }, + { "LavenderBlush", RGB_(0xff, 0xf0, 0xf5) }, + { "LavenderBlush1", RGB_(0xff, 0xf0, 0xf5) }, + { "LavenderBlush2", RGB_(0xee, 0xe0, 0xe5) }, + { "LavenderBlush3", RGB_(0xcd, 0xc1, 0xc5) }, + { "LavenderBlush4", RGB_(0x8b, 0x83, 0x86) }, + { "LawnGreen", RGB_(0x7c, 0xfc, 0x00) }, + { "LemonChiffon", RGB_(0xff, 0xfa, 0xcd) }, + { "LemonChiffon1", RGB_(0xff, 0xfa, 0xcd) }, + { "LemonChiffon2", RGB_(0xee, 0xe9, 0xbf) }, + { "LemonChiffon3", RGB_(0xcd, 0xc9, 0xa5) }, + { "LemonChiffon4", RGB_(0x8b, 0x89, 0x70) }, + { "LightBlue", RGB_(0xad, 0xd8, 0xe6) }, + { "LightBlue1", RGB_(0xbf, 0xef, 0xff) }, + { "LightBlue2", RGB_(0xb2, 0xdf, 0xee) }, + { "LightBlue3", RGB_(0x9a, 0xc0, 0xcd) }, + { "LightBlue4", RGB_(0x68, 0x83, 0x8b) }, + { "LightCoral", RGB_(0xf0, 0x80, 0x80) }, + { "LightCyan", RGB_(0xe0, 0xff, 0xff) }, + { "LightCyan1", RGB_(0xe0, 0xff, 0xff) }, + { "LightCyan2", RGB_(0xd1, 0xee, 0xee) }, + { "LightCyan3", RGB_(0xb4, 0xcd, 0xcd) }, + { "LightCyan4", RGB_(0x7a, 0x8b, 0x8b) }, + { "LightGoldenrod", RGB_(0xee, 0xdd, 0x82) }, + { "LightGoldenrod1", RGB_(0xff, 0xec, 0x8b) }, + { "LightGoldenrod2", RGB_(0xee, 0xdc, 0x82) }, + { "LightGoldenrod3", RGB_(0xcd, 0xbe, 0x70) }, + { "LightGoldenrod4", RGB_(0x8b, 0x81, 0x4c) }, + { "LightGoldenRodYellow", RGB_(0xfa, 0xfa, 0xd2) }, + { "LightGray", RGB_(0xd3, 0xd3, 0xd3) }, + { "LightGreen", RGB_(0x90, 0xee, 0x90) }, + { "LightGrey", RGB_(0xd3, 0xd3, 0xd3) }, + { "LightMagenta", RGB_(0xff, 0xbb, 0xff) }, + { "LightPink", RGB_(0xff, 0xb6, 0xc1) }, + { "LightPink1", RGB_(0xff, 0xae, 0xb9) }, + { "LightPink2", RGB_(0xee, 0xa2, 0xad) }, + { "LightPink3", RGB_(0xcd, 0x8c, 0x95) }, + { "LightPink4", RGB_(0x8b, 0x5f, 0x65) }, + { "LightRed", RGB_(0xff, 0xbb, 0xbb) }, + { "LightSalmon", RGB_(0xff, 0xa0, 0x7a) }, + { "LightSalmon1", RGB_(0xff, 0xa0, 0x7a) }, + { "LightSalmon2", RGB_(0xee, 0x95, 0x72) }, + { "LightSalmon3", RGB_(0xcd, 0x81, 0x62) }, + { "LightSalmon4", RGB_(0x8b, 0x57, 0x42) }, + { "LightSeaGreen", RGB_(0x20, 0xb2, 0xaa) }, + { "LightSkyBlue", RGB_(0x87, 0xce, 0xfa) }, + { "LightSkyBlue1", RGB_(0xb0, 0xe2, 0xff) }, + { "LightSkyBlue2", RGB_(0xa4, 0xd3, 0xee) }, + { "LightSkyBlue3", RGB_(0x8d, 0xb6, 0xcd) }, + { "LightSkyBlue4", RGB_(0x60, 0x7b, 0x8b) }, + { "LightSlateBlue", RGB_(0x84, 0x70, 0xff) }, + { "LightSlateGray", RGB_(0x77, 0x88, 0x99) }, + { "LightSlateGrey", RGB_(0x77, 0x88, 0x99) }, + { "LightSteelBlue", RGB_(0xb0, 0xc4, 0xde) }, + { "LightSteelBlue1", RGB_(0xca, 0xe1, 0xff) }, + { "LightSteelBlue2", RGB_(0xbc, 0xd2, 0xee) }, + { "LightSteelBlue3", RGB_(0xa2, 0xb5, 0xcd) }, + { "LightSteelBlue4", RGB_(0x6e, 0x7b, 0x8b) }, + { "LightYellow", RGB_(0xff, 0xff, 0xe0) }, + { "LightYellow1", RGB_(0xff, 0xff, 0xe0) }, + { "LightYellow2", RGB_(0xee, 0xee, 0xd1) }, + { "LightYellow3", RGB_(0xcd, 0xcd, 0xb4) }, + { "LightYellow4", RGB_(0x8b, 0x8b, 0x7a) }, + { "Lime", RGB_(0x00, 0xff, 0x00) }, + { "LimeGreen", RGB_(0x32, 0xcd, 0x32) }, + { "Linen", RGB_(0xfa, 0xf0, 0xe6) }, + { "Magenta", RGB_(0xff, 0x00, 0xff) }, + { "Magenta1", RGB_(0xff, 0x0, 0xff) }, + { "Magenta2", RGB_(0xee, 0x0, 0xee) }, + { "Magenta3", RGB_(0xcd, 0x0, 0xcd) }, + { "Magenta4", RGB_(0x8b, 0x0, 0x8b) }, + { "Maroon", RGB_(0x80, 0x00, 0x00) }, + { "Maroon1", RGB_(0xff, 0x34, 0xb3) }, + { "Maroon2", RGB_(0xee, 0x30, 0xa7) }, + { "Maroon3", RGB_(0xcd, 0x29, 0x90) }, + { "Maroon4", RGB_(0x8b, 0x1c, 0x62) }, + { "MediumAquamarine", RGB_(0x66, 0xcd, 0xaa) }, + { "MediumBlue", RGB_(0x00, 0x00, 0xcd) }, + { "MediumOrchid", RGB_(0xba, 0x55, 0xd3) }, + { "MediumOrchid1", RGB_(0xe0, 0x66, 0xff) }, + { "MediumOrchid2", RGB_(0xd1, 0x5f, 0xee) }, + { "MediumOrchid3", RGB_(0xb4, 0x52, 0xcd) }, + { "MediumOrchid4", RGB_(0x7a, 0x37, 0x8b) }, + { "MediumPurple", RGB_(0x93, 0x70, 0xdb) }, + { "MediumPurple1", RGB_(0xab, 0x82, 0xff) }, + { "MediumPurple2", RGB_(0x9f, 0x79, 0xee) }, + { "MediumPurple3", RGB_(0x89, 0x68, 0xcd) }, + { "MediumPurple4", RGB_(0x5d, 0x47, 0x8b) }, + { "MediumSeaGreen", RGB_(0x3c, 0xb3, 0x71) }, + { "MediumSlateBlue", RGB_(0x7b, 0x68, 0xee) }, + { "MediumSpringGreen", RGB_(0x00, 0xfa, 0x9a) }, + { "MediumTurquoise", RGB_(0x48, 0xd1, 0xcc) }, + { "MediumVioletRed", RGB_(0xc7, 0x15, 0x85) }, + { "MidnightBlue", RGB_(0x19, 0x19, 0x70) }, + { "MintCream", RGB_(0xf5, 0xff, 0xfa) }, + { "MistyRose", RGB_(0xff, 0xe4, 0xe1) }, + { "MistyRose1", RGB_(0xff, 0xe4, 0xe1) }, + { "MistyRose2", RGB_(0xee, 0xd5, 0xd2) }, + { "MistyRose3", RGB_(0xcd, 0xb7, 0xb5) }, + { "MistyRose4", RGB_(0x8b, 0x7d, 0x7b) }, + { "Moccasin", RGB_(0xff, 0xe4, 0xb5) }, + { "NavajoWhite", RGB_(0xff, 0xde, 0xad) }, + { "NavajoWhite1", RGB_(0xff, 0xde, 0xad) }, + { "NavajoWhite2", RGB_(0xee, 0xcf, 0xa1) }, + { "NavajoWhite3", RGB_(0xcd, 0xb3, 0x8b) }, + { "NavajoWhite4", RGB_(0x8b, 0x79, 0x5e) }, + { "Navy", RGB_(0x00, 0x00, 0x80) }, + { "NavyBlue", RGB_(0x0, 0x0, 0x80) }, + { "OldLace", RGB_(0xfd, 0xf5, 0xe6) }, + { "Olive", RGB_(0x80, 0x80, 0x00) }, + { "OliveDrab", RGB_(0x6b, 0x8e, 0x23) }, + { "OliveDrab1", RGB_(0xc0, 0xff, 0x3e) }, + { "OliveDrab2", RGB_(0xb3, 0xee, 0x3a) }, + { "OliveDrab3", RGB_(0x9a, 0xcd, 0x32) }, + { "OliveDrab4", RGB_(0x69, 0x8b, 0x22) }, + { "Orange", RGB_(0xff, 0xa5, 0x00) }, + { "Orange1", RGB_(0xff, 0xa5, 0x0) }, + { "Orange2", RGB_(0xee, 0x9a, 0x0) }, + { "Orange3", RGB_(0xcd, 0x85, 0x0) }, + { "Orange4", RGB_(0x8b, 0x5a, 0x0) }, + { "OrangeRed", RGB_(0xff, 0x45, 0x00) }, + { "OrangeRed1", RGB_(0xff, 0x45, 0x0) }, + { "OrangeRed2", RGB_(0xee, 0x40, 0x0) }, + { "OrangeRed3", RGB_(0xcd, 0x37, 0x0) }, + { "OrangeRed4", RGB_(0x8b, 0x25, 0x0) }, + { "Orchid", RGB_(0xda, 0x70, 0xd6) }, + { "Orchid1", RGB_(0xff, 0x83, 0xfa) }, + { "Orchid2", RGB_(0xee, 0x7a, 0xe9) }, + { "Orchid3", RGB_(0xcd, 0x69, 0xc9) }, + { "Orchid4", RGB_(0x8b, 0x47, 0x89) }, + { "PaleGoldenRod", RGB_(0xee, 0xe8, 0xaa) }, + { "PaleGreen", RGB_(0x98, 0xfb, 0x98) }, + { "PaleGreen1", RGB_(0x9a, 0xff, 0x9a) }, + { "PaleGreen2", RGB_(0x90, 0xee, 0x90) }, + { "PaleGreen3", RGB_(0x7c, 0xcd, 0x7c) }, + { "PaleGreen4", RGB_(0x54, 0x8b, 0x54) }, + { "PaleTurquoise", RGB_(0xaf, 0xee, 0xee) }, + { "PaleTurquoise1", RGB_(0xbb, 0xff, 0xff) }, + { "PaleTurquoise2", RGB_(0xae, 0xee, 0xee) }, + { "PaleTurquoise3", RGB_(0x96, 0xcd, 0xcd) }, + { "PaleTurquoise4", RGB_(0x66, 0x8b, 0x8b) }, + { "PaleVioletRed", RGB_(0xdb, 0x70, 0x93) }, + { "PaleVioletRed1", RGB_(0xff, 0x82, 0xab) }, + { "PaleVioletRed2", RGB_(0xee, 0x79, 0x9f) }, + { "PaleVioletRed3", RGB_(0xcd, 0x68, 0x89) }, + { "PaleVioletRed4", RGB_(0x8b, 0x47, 0x5d) }, + { "PapayaWhip", RGB_(0xff, 0xef, 0xd5) }, + { "PeachPuff", RGB_(0xff, 0xda, 0xb9) }, + { "PeachPuff1", RGB_(0xff, 0xda, 0xb9) }, + { "PeachPuff2", RGB_(0xee, 0xcb, 0xad) }, + { "PeachPuff3", RGB_(0xcd, 0xaf, 0x95) }, + { "PeachPuff4", RGB_(0x8b, 0x77, 0x65) }, + { "Peru", RGB_(0xcd, 0x85, 0x3f) }, + { "Pink", RGB_(0xff, 0xc0, 0xcb) }, + { "Pink1", RGB_(0xff, 0xb5, 0xc5) }, + { "Pink2", RGB_(0xee, 0xa9, 0xb8) }, + { "Pink3", RGB_(0xcd, 0x91, 0x9e) }, + { "Pink4", RGB_(0x8b, 0x63, 0x6c) }, + { "Plum", RGB_(0xdd, 0xa0, 0xdd) }, + { "Plum1", RGB_(0xff, 0xbb, 0xff) }, + { "Plum2", RGB_(0xee, 0xae, 0xee) }, + { "Plum3", RGB_(0xcd, 0x96, 0xcd) }, + { "Plum4", RGB_(0x8b, 0x66, 0x8b) }, + { "PowderBlue", RGB_(0xb0, 0xe0, 0xe6) }, + { "Purple", RGB_(0x80, 0x00, 0x80) }, + { "Purple1", RGB_(0x9b, 0x30, 0xff) }, + { "Purple2", RGB_(0x91, 0x2c, 0xee) }, + { "Purple3", RGB_(0x7d, 0x26, 0xcd) }, + { "Purple4", RGB_(0x55, 0x1a, 0x8b) }, + { "RebeccaPurple", RGB_(0x66, 0x33, 0x99) }, + { "Red", RGB_(0xff, 0x00, 0x00) }, + { "Red1", RGB_(0xff, 0x0, 0x0) }, + { "Red2", RGB_(0xee, 0x0, 0x0) }, + { "Red3", RGB_(0xcd, 0x0, 0x0) }, + { "Red4", RGB_(0x8b, 0x0, 0x0) }, + { "RosyBrown", RGB_(0xbc, 0x8f, 0x8f) }, + { "RosyBrown1", RGB_(0xff, 0xc1, 0xc1) }, + { "RosyBrown2", RGB_(0xee, 0xb4, 0xb4) }, + { "RosyBrown3", RGB_(0xcd, 0x9b, 0x9b) }, + { "RosyBrown4", RGB_(0x8b, 0x69, 0x69) }, + { "RoyalBlue", RGB_(0x41, 0x69, 0xe1) }, + { "RoyalBlue1", RGB_(0x48, 0x76, 0xff) }, + { "RoyalBlue2", RGB_(0x43, 0x6e, 0xee) }, + { "RoyalBlue3", RGB_(0x3a, 0x5f, 0xcd) }, + { "RoyalBlue4", RGB_(0x27, 0x40, 0x8b) }, + { "SaddleBrown", RGB_(0x8b, 0x45, 0x13) }, + { "Salmon", RGB_(0xfa, 0x80, 0x72) }, + { "Salmon1", RGB_(0xff, 0x8c, 0x69) }, + { "Salmon2", RGB_(0xee, 0x82, 0x62) }, + { "Salmon3", RGB_(0xcd, 0x70, 0x54) }, + { "Salmon4", RGB_(0x8b, 0x4c, 0x39) }, + { "SandyBrown", RGB_(0xf4, 0xa4, 0x60) }, + { "SeaGreen", RGB_(0x2e, 0x8b, 0x57) }, + { "SeaGreen1", RGB_(0x54, 0xff, 0x9f) }, + { "SeaGreen2", RGB_(0x4e, 0xee, 0x94) }, + { "SeaGreen3", RGB_(0x43, 0xcd, 0x80) }, + { "SeaGreen4", RGB_(0x2e, 0x8b, 0x57) }, + { "SeaShell", RGB_(0xff, 0xf5, 0xee) }, + { "Seashell1", RGB_(0xff, 0xf5, 0xee) }, + { "Seashell2", RGB_(0xee, 0xe5, 0xde) }, + { "Seashell3", RGB_(0xcd, 0xc5, 0xbf) }, + { "Seashell4", RGB_(0x8b, 0x86, 0x82) }, + { "Sienna", RGB_(0xa0, 0x52, 0x2d) }, + { "Sienna1", RGB_(0xff, 0x82, 0x47) }, + { "Sienna2", RGB_(0xee, 0x79, 0x42) }, + { "Sienna3", RGB_(0xcd, 0x68, 0x39) }, + { "Sienna4", RGB_(0x8b, 0x47, 0x26) }, + { "Silver", RGB_(0xc0, 0xc0, 0xc0) }, + { "SkyBlue", RGB_(0x87, 0xce, 0xeb) }, + { "SkyBlue1", RGB_(0x87, 0xce, 0xff) }, + { "SkyBlue2", RGB_(0x7e, 0xc0, 0xee) }, + { "SkyBlue3", RGB_(0x6c, 0xa6, 0xcd) }, + { "SkyBlue4", RGB_(0x4a, 0x70, 0x8b) }, + { "SlateBlue", RGB_(0x6a, 0x5a, 0xcd) }, + { "SlateBlue1", RGB_(0x83, 0x6f, 0xff) }, + { "SlateBlue2", RGB_(0x7a, 0x67, 0xee) }, + { "SlateBlue3", RGB_(0x69, 0x59, 0xcd) }, + { "SlateBlue4", RGB_(0x47, 0x3c, 0x8b) }, + { "SlateGray", RGB_(0x70, 0x80, 0x90) }, + { "SlateGray1", RGB_(0xc6, 0xe2, 0xff) }, + { "SlateGray2", RGB_(0xb9, 0xd3, 0xee) }, + { "SlateGray3", RGB_(0x9f, 0xb6, 0xcd) }, + { "SlateGray4", RGB_(0x6c, 0x7b, 0x8b) }, + { "SlateGrey", RGB_(0x70, 0x80, 0x90) }, + { "Snow", RGB_(0xff, 0xfa, 0xfa) }, + { "Snow1", RGB_(0xff, 0xfa, 0xfa) }, + { "Snow2", RGB_(0xee, 0xe9, 0xe9) }, + { "Snow3", RGB_(0xcd, 0xc9, 0xc9) }, + { "Snow4", RGB_(0x8b, 0x89, 0x89) }, + { "SpringGreen", RGB_(0x00, 0xff, 0x7f) }, + { "SpringGreen1", RGB_(0x0, 0xff, 0x7f) }, + { "SpringGreen2", RGB_(0x0, 0xee, 0x76) }, + { "SpringGreen3", RGB_(0x0, 0xcd, 0x66) }, + { "SpringGreen4", RGB_(0x0, 0x8b, 0x45) }, + { "SteelBlue", RGB_(0x46, 0x82, 0xb4) }, + { "SteelBlue1", RGB_(0x63, 0xb8, 0xff) }, + { "SteelBlue2", RGB_(0x5c, 0xac, 0xee) }, + { "SteelBlue3", RGB_(0x4f, 0x94, 0xcd) }, + { "SteelBlue4", RGB_(0x36, 0x64, 0x8b) }, + { "Tan", RGB_(0xd2, 0xb4, 0x8c) }, + { "Tan1", RGB_(0xff, 0xa5, 0x4f) }, + { "Tan2", RGB_(0xee, 0x9a, 0x49) }, + { "Tan3", RGB_(0xcd, 0x85, 0x3f) }, + { "Tan4", RGB_(0x8b, 0x5a, 0x2b) }, + { "Teal", RGB_(0x00, 0x80, 0x80) }, + { "Thistle", RGB_(0xd8, 0xbf, 0xd8) }, + { "Thistle1", RGB_(0xff, 0xe1, 0xff) }, + { "Thistle2", RGB_(0xee, 0xd2, 0xee) }, + { "Thistle3", RGB_(0xcd, 0xb5, 0xcd) }, + { "Thistle4", RGB_(0x8b, 0x7b, 0x8b) }, + { "Tomato", RGB_(0xff, 0x63, 0x47) }, + { "Tomato1", RGB_(0xff, 0x63, 0x47) }, + { "Tomato2", RGB_(0xee, 0x5c, 0x42) }, + { "Tomato3", RGB_(0xcd, 0x4f, 0x39) }, + { "Tomato4", RGB_(0x8b, 0x36, 0x26) }, + { "Turquoise", RGB_(0x40, 0xe0, 0xd0) }, + { "Turquoise1", RGB_(0x0, 0xf5, 0xff) }, + { "Turquoise2", RGB_(0x0, 0xe5, 0xee) }, + { "Turquoise3", RGB_(0x0, 0xc5, 0xcd) }, + { "Turquoise4", RGB_(0x0, 0x86, 0x8b) }, + { "Violet", RGB_(0xee, 0x82, 0xee) }, + { "VioletRed", RGB_(0xd0, 0x20, 0x90) }, + { "VioletRed1", RGB_(0xff, 0x3e, 0x96) }, + { "VioletRed2", RGB_(0xee, 0x3a, 0x8c) }, + { "VioletRed3", RGB_(0xcd, 0x32, 0x78) }, + { "VioletRed4", RGB_(0x8b, 0x22, 0x52) }, + { "WebGray", RGB_(0x80, 0x80, 0x80) }, + { "WebGreen", RGB_(0x0, 0x80, 0x0) }, + { "WebGrey", RGB_(0x80, 0x80, 0x80) }, + { "WebMaroon", RGB_(0x80, 0x0, 0x0) }, + { "WebPurple", RGB_(0x80, 0x0, 0x80) }, + { "Wheat", RGB_(0xf5, 0xde, 0xb3) }, + { "Wheat1", RGB_(0xff, 0xe7, 0xba) }, + { "Wheat2", RGB_(0xee, 0xd8, 0xae) }, + { "Wheat3", RGB_(0xcd, 0xba, 0x96) }, + { "Wheat4", RGB_(0x8b, 0x7e, 0x66) }, + { "White", RGB_(0xff, 0xff, 0xff) }, + { "WhiteSmoke", RGB_(0xf5, 0xf5, 0xf5) }, + { "X11Gray", RGB_(0xbe, 0xbe, 0xbe) }, + { "X11Green", RGB_(0x0, 0xff, 0x0) }, + { "X11Grey", RGB_(0xbe, 0xbe, 0xbe) }, + { "X11Maroon", RGB_(0xb0, 0x30, 0x60) }, + { "X11Purple", RGB_(0xa0, 0x20, 0xf0) }, + { "Yellow", RGB_(0xff, 0xff, 0x00) }, + { "Yellow1", RGB_(0xff, 0xff, 0x0) }, + { "Yellow2", RGB_(0xee, 0xee, 0x0) }, + { "Yellow3", RGB_(0xcd, 0xcd, 0x0) }, + { "Yellow4", RGB_(0x8b, 0x8b, 0x0) }, + { "YellowGreen", RGB_(0x9a, 0xcd, 0x32) }, { NULL, 0 }, }; @@ -8221,7 +8489,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 uint8_t *name) +RgbValue name_to_color(const char_u *name) { if (name[0] == '#' && isxdigit(name[1]) && isxdigit(name[2]) @@ -8247,24 +8515,21 @@ RgbValue name_to_color(const uint8_t *name) /// Gets highlight description for id `attr_id` as a map. Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Error *err) { - HlAttrs attrs = HLATTRS_INIT; + HlAttrs *aep = NULL; Dictionary dic = ARRAY_DICT_INIT; if (attr_id == 0) { - goto end; + return dic; } - attrentry_T *aep = syn_cterm_attr2entry((int)attr_id); + aep = syn_cterm_attr2entry((int)attr_id); if (!aep) { api_set_error(err, kErrorTypeException, "Invalid attribute id: %" PRId64, attr_id); return dic; } - attrs = attrentry2hlattrs(aep, rgb); - -end: - return hlattrs2dict(attrs); + return hlattrs2dict(aep, rgb); } diff --git a/src/nvim/syntax.h b/src/nvim/syntax.h index bb733ead30..f8282955a6 100644 --- a/src/nvim/syntax.h +++ b/src/nvim/syntax.h @@ -6,19 +6,6 @@ #include "nvim/buffer_defs.h" #include "nvim/ex_cmds_defs.h" -/// Terminal highlighting attribute bits. -/// Attributes above HL_ALL are used for syntax highlighting. -/// \addtogroup HL_ATTRIBUTES -/// @{ -#define HL_NORMAL 0x00 -#define HL_INVERSE 0x01 -#define HL_BOLD 0x02 -#define HL_ITALIC 0x04 -#define HL_UNDERLINE 0x08 -#define HL_UNDERCURL 0x10 -#define HL_STANDOUT 0x20 -/// @} - #define HL_CONTAINED 0x01 /* not used on toplevel */ #define HL_TRANSP 0x02 /* has no highlighting */ #define HL_ONELINE 0x04 /* match within one line only */ @@ -45,6 +32,9 @@ typedef struct { } color_name_table_T; extern color_name_table_T color_name_table[]; +/// Array of highlight definitions, used for unit testing +extern const char *const highlight_init_cmdline[]; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "syntax.h.generated.h" #endif diff --git a/src/nvim/syntax_defs.h b/src/nvim/syntax_defs.h index 7260853703..63089a62af 100644 --- a/src/nvim/syntax_defs.h +++ b/src/nvim/syntax_defs.h @@ -68,21 +68,4 @@ struct syn_state { * may have made the state invalid */ }; -// Structure shared between syntax.c, screen.c -typedef struct attr_entry { - int16_t rgb_ae_attr, cterm_ae_attr; // HL_BOLD, etc. - RgbValue rgb_fg_color, rgb_bg_color, rgb_sp_color; - int cterm_fg_color, cterm_bg_color; -} attrentry_T; - -#define ATTRENTRY_INIT { \ - .rgb_ae_attr = 0, \ - .cterm_ae_attr = 0, \ - .rgb_fg_color = -1, \ - .rgb_bg_color = -1, \ - .rgb_sp_color = -1, \ - .cterm_fg_color = 0, \ - .cterm_bg_color = 0, \ -} - #endif // NVIM_SYNTAX_DEFS_H diff --git a/src/nvim/tag.c b/src/nvim/tag.c index be9d621c7d..f23465e501 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -667,7 +667,7 @@ do_tag ( fname = xmalloc(MAXPATHL + 1); cmd = xmalloc(CMDBUFFSIZE + 1); - list = tv_list_alloc(); + list = tv_list_alloc(num_matches); for (i = 0; i < num_matches; ++i) { int len, cmd_len; diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index dfa758f41e..276b47536f 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -61,7 +61,6 @@ #include "nvim/edit.h" #include "nvim/mouse.h" #include "nvim/memline.h" -#include "nvim/mark.h" #include "nvim/map.h" #include "nvim/misc1.h" #include "nvim/move.h" @@ -181,13 +180,14 @@ void terminal_init(void) vterm_state_get_palette_color(state, color_index, &color); } map_put(int, int)(color_indexes, - RGB(color.red, color.green, color.blue), color_index + 1); + RGB_(color.red, color.green, color.blue), + color_index + 1); } VTermColor fg, bg; vterm_state_get_default_colors(state, &fg, &bg); - default_vt_fg = RGB(fg.red, fg.green, fg.blue); - default_vt_bg = RGB(bg.red, bg.green, bg.blue); + default_vt_fg = RGB_(fg.red, fg.green, fg.blue); + default_vt_bg = RGB_(bg.red, bg.green, bg.blue); default_vt_bg_rgb = bg; vterm_free(vt); } @@ -376,8 +376,6 @@ void terminal_enter(void) // Ensure the terminal is properly sized. terminal_resize(s->term, 0, 0); - checkpcmark(); - setpcmark(); int save_state = State; s->save_rd = RedrawingDisabled; State = TERM_FOCUS; @@ -463,6 +461,10 @@ static int terminal_execute(VimState *state, int key) } break; + case K_COMMAND: + do_cmdline(NULL, getcmdkeycmd, NULL, 0); + break; + case Ctrl_N: if (s->got_bsl) { return 0; @@ -568,8 +570,8 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, VTermScreenCell cell; fetch_cell(term, row, col, &cell); // Get the rgb value set by libvterm. - int vt_fg = RGB(cell.fg.red, cell.fg.green, cell.fg.blue); - int vt_bg = RGB(cell.bg.red, cell.bg.green, cell.bg.blue); + int vt_fg = RGB_(cell.fg.red, cell.fg.green, cell.fg.blue); + int vt_bg = RGB_(cell.bg.red, cell.bg.green, cell.bg.blue); vt_fg = vt_fg != default_vt_fg ? vt_fg : - 1; vt_bg = vt_bg != default_vt_bg ? vt_bg : - 1; // Since libvterm does not expose the color index used by the program, we @@ -588,7 +590,7 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, int attr_id = 0; if (hl_attrs || vt_fg != -1 || vt_bg != -1) { - attr_id = get_attr_entry(&(attrentry_T) { + attr_id = get_attr_entry(&(HlAttrs) { .cterm_ae_attr = (int16_t)hl_attrs, .cterm_fg_color = vt_fg_idx, .cterm_bg_color = vt_bg_idx, diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 111bd172ef..a31e1843fc 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -2,44 +2,61 @@ # Makefile to run all tests for Vim # -NVIM_PRG ?= ../../../build/bin/nvim -TMPDIR ?= Xtest-tmpdir -SCRIPTSOURCE := ../../../runtime +ifeq ($(OS),Windows_NT) + NVIM_PRG ?= ../../../build/bin/nvim.exe +else + NVIM_PRG ?= ../../../build/bin/nvim +endif +ROOT := ../../.. export SHELL := sh export NVIM_PRG := $(NVIM_PRG) -export TMPDIR +export TMPDIR := $(abspath ../../../Xtest-tmpdir) -SCRIPTS ?= \ - test13.out \ +SCRIPTS_DEFAULT = \ test14.out \ - test17.out \ test24.out \ - test32.out \ test37.out \ test40.out \ test42.out \ test48.out \ test49.out \ test52.out \ - test53.out \ test64.out \ - test73.out \ - test79.out \ + +ifneq ($(OS),Windows_NT) + SCRIPTS_DEFAULTS := $(SCRIPTS_DEFAULT) \ + test17.out \ + +endif + +SCRIPTS ?= $(SCRIPTS_DEFAULT) # Tests using runtest.vim. # Keep test_alot*.res as the last one, sort the others. NEW_TESTS ?= \ + test_arabic.res \ test_autocmd.res \ test_bufwintabinfo.res \ + test_changedtick.res \ test_charsearch.res \ + test_cindent.res \ + test_close_count.res \ test_cmdline.res \ test_command_count.res \ test_cscope.res \ + test_curswant.res \ test_digraph.res \ + test_edit.res \ + test_erasebackword.res \ + test_exists.res \ test_diffmode.res \ test_farsi.res \ + test_file_size.res \ test_filter_map.res \ + test_find_complete.res \ + test_fixeol.res \ + test_findfile.res \ test_fnameescape.res \ test_fold.res \ test_ga.res \ @@ -53,27 +70,38 @@ NEW_TESTS ?= \ test_hlsearch.res \ test_increment.res \ test_increment_dbcs.res \ + test_ins_complete.res \ test_lambda.res \ test_langmap.res \ + test_let.res \ + test_lineending.res \ + test_listdict.res \ + test_listchars.res \ + test_makeencoding.res \ test_marks.res \ test_match.res \ test_matchadd_conceal.res \ - test_matchadd_conceal_utf8.res \ test_mksession.res \ - test_mksession_utf8.res \ test_nested_function.res \ test_normal.res \ + test_number.res \ + test_options.res \ test_profile.res \ + test_put.res \ test_quickfix.res \ + test_recover.res \ test_retab.res \ + test_scrollbind.res \ test_search.res \ test_signs.res \ test_smartindent.res \ + test_spell.res \ test_stat.res \ test_startup.res \ - test_startup_utf8.res \ test_substitute.res \ test_syntax.res \ + test_system.res \ + test_tab.res \ test_tabpage.res \ test_textobjects.res \ test_timers.res \ @@ -81,8 +109,11 @@ NEW_TESTS ?= \ test_usercommands.res \ test_vimscript.res \ test_visual.res \ + test_winbuf_close.res \ test_window_id.res \ test_writefile.res \ + test_alot_latin.res \ + test_alot_utf8.res \ test_alot.res SCRIPTS_GUI := test16.out @@ -103,7 +134,7 @@ ifdef USE_VALGRIND $(VALGRIND_TOOL) \ --suppressions=../../.valgrind.supp \ --error-exitcode=123 \ - --log-file=valgrind.\%p.$* \ + --log-file=valgrind-\%p.$* \ $(VGDB) \ --trace-children=yes else @@ -121,7 +152,8 @@ nongui: nolog $(SCRIPTS) newtests report gui: nolog $(SCRIPTS) $(SCRIPTS_GUI) newtests report .gdbinit: - echo 'set $$_exitcode = -1\nrun\nif $$_exitcode != -1\n quit\nend' > .gdbinit + @echo "[OLDTEST-PREP] Setting up .gdbinit" + @echo 'set $$_exitcode = -1\nrun\nif $$_exitcode != -1\n quit\nend' > .gdbinit report: @echo @@ -140,7 +172,7 @@ $(SCRIPTS) $(SCRIPTS_GUI): $(NVIM_PRG) test1.out RM_ON_RUN := test.out X* viminfo RM_ON_START := test.ok -RUN_VIM := VIMRUNTIME=$(SCRIPTSOURCE); export VIMRUNTIME; $(TOOL) $(NVIM_PRG) -u unix.vim -U NONE -i viminfo --headless --noplugin -s dotest.in +RUN_VIM := $(TOOL) $(NVIM_PRG) -u unix.vim -U NONE -i viminfo --headless --noplugin -s dotest.in clean: -rm -rf *.out \ @@ -148,6 +180,7 @@ clean: *.res \ *.rej \ *.orig \ + *.tlog \ test.log \ messages \ $(RM_ON_RUN) \ @@ -160,59 +193,32 @@ clean: del test1.out: .gdbinit test1.in - -rm -rf $*.failed $(RM_ON_RUN) $(RM_ON_START) wrongtermsize - mkdir -p $(TMPDIR) - $(RUN_VIM) $*.in - @/bin/sh -c "if test -e wrongtermsize; then \ - echo; \ - echo test1 FAILED - terminal size must be 80x24 or larger; \ - echo; exit 1; \ - elif diff test.out $*.ok; then \ - mv -f test.out $*.out; \ - else \ - echo; \ - echo test1 FAILED - Something basic is wrong; \ - echo; \ - exit 1; \ - fi" - -rm -rf X* viminfo + @echo "[OLDTEST-PREP] Running test1" + @rm -rf $*.failed $(RM_ON_RUN) $(RM_ON_START) wrongtermsize + @mkdir -p $(TMPDIR) + @/bin/sh runnvim.sh $(ROOT) $(NVIM_PRG) $* $(RUN_VIM) $*.in + @rm -f wrongtermsize + @rm -rf X* viminfo %.out: %.in .gdbinit - -rm -rf $*.failed test.ok $(RM_ON_RUN) - mkdir -p $(TMPDIR) - cp $*.ok test.ok - # Sleep a moment to avoid that the xterm title is messed up. - # 200 msec is sufficient, but only modern sleep supports a fraction of - # a second, fall back to a second if it fails. - @-/bin/sh -c "sleep .2 > /dev/null 2>&1 || sleep 1" - $(RUN_VIM) $*.in - - # Check if the test.out file matches test.ok. - @/bin/sh -c "if test -f test.out; then \ - if diff -u test.out $*.ok; then \ - mv -f test.out $*.out; \ - else \ - echo $* FAILED >> test.log; \ - mv -f test.out $*.failed; \ - fi; \ - else \ - echo $* NO OUTPUT >>test.log; \ - fi" - @/bin/sh -c "if test -f valgrind; then \ - mv -f valgrind valgrind.$*; \ - fi" - -rm -rf X* test.ok viminfo + @echo "[OLDESTTEST] Running" $* + @rm -rf $*.failed test.ok $(RM_ON_RUN) + @mkdir -p $(TMPDIR) + @cp $*.ok test.ok + @/bin/sh runnvim.sh --oldesttest $(ROOT) $(NVIM_PRG) $* $(RUN_VIM) $*.in + @rm -rf X* test.ok viminfo test49.out: test49.vim nolog: - -rm -f test.log messages + @echo "[OLDTEST-PREP] Removing test.log and messages" + @rm -f test.log messages # New style of tests uses Vim script with assert calls. These are easier # to write and a lot easier to read and debug. # Limitation: Only works with the +eval feature. -RUN_VIMTEST = VIMRUNTIME=$(SCRIPTSOURCE); export VIMRUNTIME; $(TOOL) $(NVIM_PRG) -u unix.vim -U NONE --headless --noplugin +RUN_VIMTEST = $(TOOL) $(NVIM_PRG) -u unix.vim -U NONE --headless --noplugin newtests: newtestssilent @/bin/sh -c "if test -f messages && grep -q 'FAILED' messages; then \ @@ -222,5 +228,6 @@ newtests: newtestssilent newtestssilent: $(NEW_TESTS) %.res: %.vim .gdbinit - mkdir -p $(TMPDIR) - $(RUN_VIMTEST) -u NONE -S runtest.vim $*.vim + @echo "[OLDTEST] Running" $* + @mkdir -p $(TMPDIR) + @/bin/sh runnvim.sh $(ROOT) $(NVIM_PRG) $* $(RUN_VIMTEST) -u NONE -S runtest.vim $*.vim diff --git a/src/nvim/testdir/runnvim.sh b/src/nvim/testdir/runnvim.sh new file mode 100755 index 0000000000..43556f3ad3 --- /dev/null +++ b/src/nvim/testdir/runnvim.sh @@ -0,0 +1,82 @@ +#!/bin/sh + +main() {( + local separator="================================================================================" + local oldesttest= + if test "$1" = "--oldesttest" ; then + shift + oldesttest=1 + fi + local root="$1" ; shift + local nvim_prg="$1" ; shift + local test_name="$1" ; shift + + local tlog="$test_name.tlog" + + export NVIM_TEST_ARGC=$# + local arg + local i=0 + for arg ; do + eval "export NVIM_TEST_ARG$i=\"\$arg\"" + i=$(( i+1 )) + done + + export CI_DIR="$root/ci" + export BUILD_DIR="$(dirname "$nvim_prg")/.." + export FAILED=0 + + . "$CI_DIR/common/suite.sh" + . "$CI_DIR/common/test.sh" + + export VIMRUNTIME="$root/runtime" + if ! "$nvim_prg" \ + -u NONE -i NONE \ + --headless \ + --cmd 'set shortmess+=I noswapfile noundofile nomore' \ + -S runnvim.vim \ + "$tlog" > "out-$tlog" 2> "err-$tlog" + then + fail "$test_name" F "Nvim exited with non-zero code" + fi + echo "Stdout of :terminal runner" >> "$tlog" + echo "$separator" >> "$tlog" + cat "out-$tlog" >> "$tlog" + echo "$separator" >> "$tlog" + echo "Stderr of :terminal runner" >> "$tlog" + echo "$separator" >> "$tlog" + cat "err-$tlog" >> "$tlog" + echo "$separator" >> "$tlog" + if test "$oldesttest" = 1 ; then + if ! diff -q test.out "$test_name.ok" > /dev/null 2>&1 ; then + if test -f test.out ; then + fail "$test_name" F "Oldest test .out file differs from .ok file" + echo "Diff between test.out and $test_name.ok" >> "$tlog" + echo "$separator" >> "$tlog" + diff -a test.out "$test_name.ok" >> "$tlog" + echo "$separator" >> "$tlog" + else + echo "No output in test.out" >> "$tlog" + fi + fi + fi + if test "$FAILED" = 1 ; then + travis_fold start "$NVIM_TEST_CURRENT_SUITE/$test_name" + fi + valgrind_check . + if test -n "$LOG_DIR" ; then + asan_check "$LOG_DIR" + fi + check_core_dumps + if test "$FAILED" = 1 ; then + cat "$tlog" + fi + rm -f "$tlog" + if test "$FAILED" = 1 ; then + travis_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 + fi +)} + +main "$@" diff --git a/src/nvim/testdir/runnvim.vim b/src/nvim/testdir/runnvim.vim new file mode 100644 index 0000000000..396a3a6477 --- /dev/null +++ b/src/nvim/testdir/runnvim.vim @@ -0,0 +1,55 @@ +let s:logger = {'d_events': []} +function s:logger.on_stdout(id, data, event) + call add(self.d_events, [a:event, a:data]) +endfunction +let s:logger.on_stderr = s:logger.on_stdout +function s:logger.on_exit(id, data, event) + call add(self.d_events, [a:event, ['']]) +endfunction + +function Main() + let argc = +$NVIM_TEST_ARGC + let args = [] + for i in range(argc) + call add(args, eval("$NVIM_TEST_ARG" . i)) + endfor + set lines=25 + set columns=80 + enew + let job = termopen(args, s:logger) + let results = jobwait([job], 5 * 60 * 1000) + " TODO(ZyX-I): Get colors + let screen = getline(1, '$') + bwipeout! + let stringified_events = map(s:logger.d_events, + \'v:val[0] . ": " . ' . + \'join(map(v:val[1], '. + \ '''substitute(v:val, '. + \ '"\\v\\C(\\p@!.|\\<)", '. + \ '"\\=printf(\"<%x>\", '. + \ 'char2nr(submatch(0)))", '. + \ '"")''), '. + \ '''\n'')') + call setline(1, [ + \ 'Job exited with code ' . results[0], + \ printf('Screen (%u lines)', len(screen)), + \ repeat('=', 80), + \] + screen + [ + \ repeat('=', 80), + \ printf('Events (%u lines):', len(stringified_events)), + \ repeat('=', 80), + \] + stringified_events + [ + \ repeat('=', 80), + \]) + write + if results[0] != 0 + if results[0] != -1 + call jobstop(job) + endif + cquit + else + qall + endif +endfunction + +call Main() diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 4de1345679..7090be7726 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -4,7 +4,7 @@ " " To execute only specific test functions, add a second argument. It will be " matched against the names of the Test_ function. E.g.: -" ../vim -u NONE -S runtest.vim test_channel.vim open_delay +" ../vim -u NONE -S runtest.vim test_channel.vim open_delay " The output can be found in the "messages" file. " " The test script may contain anything, only functions that start with @@ -42,10 +42,11 @@ endif " Common with all tests on all systems. source setup.vim +" For consistency run all tests with 'nocompatible' set. " This also enables use of line continuation. -set viminfo+=nviminfo +set nocp viminfo+=nviminfo -" Use utf-8 or latin1 be default, instead of whatever the system default +" Use utf-8 or latin1 by default, instead of whatever the system default " happens to be. Individual tests can overrule this at the top of the file. if has('multi_byte') set encoding=utf-8 @@ -62,22 +63,42 @@ lang mess C " Always use forward slashes. set shellslash -" Make sure $HOME does not get read or written. -let $HOME = '/does/not/exist' - -" Prepare for calling garbagecollect_for_testing(). +" Prepare for calling test_garbagecollect_now(). let v:testing = 1 -" Align Nvim defaults to Vim. -set directory^=. -set backspace= -set nohidden smarttab noautoindent noautoread complete-=i noruler noshowcmd -set listchars=eol:$ -" Prevent Nvim log from writing to stderr. -let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log' +" Support function: get the alloc ID by name. +function GetAllocId(name) + exe 'split ' . s:srcdir . '/alloc.h' + let top = search('typedef enum') + if top == 0 + call add(v:errors, 'typedef not found in alloc.h') + endif + let lnum = search('aid_' . a:name . ',') + if lnum == 0 + call add(v:errors, 'Alloc ID ' . a:name . ' not defined') + endif + close + return lnum - top - 1 +endfunc func RunTheTest(test) echo 'Executing ' . a:test + + " Avoid stopping at the "hit enter" prompt + set nomore + + " Avoid a three second wait when a message is about to be overwritten by the + " mode message. + set noshowmode + + " Some tests wipe out buffers. To be consistent, always wipe out all + " buffers. + %bwipe! + + " The test may change the current directory. Save and restore the + " directory after executing the test. + let save_cwd = getcwd() + if exists("*SetUp") try call SetUp() @@ -88,14 +109,21 @@ func RunTheTest(test) call add(s:messages, 'Executing ' . a:test) let s:done += 1 - try + + if a:test =~ 'Test_nocatch_' + " Function handles errors itself. This avoids skipping commands after the + " error. exe 'call ' . a:test - catch /^\cskipped/ - call add(s:messages, ' Skipped') - call add(s:skipped, 'SKIPPED ' . a:test . ': ' . substitute(v:exception, '^\S*\s\+', '', '')) - catch - call add(v:errors, 'Caught exception in ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint) - endtry + else + try + exe 'call ' . a:test + catch /^\cskipped/ + call add(s:messages, ' Skipped') + call add(s:skipped, 'SKIPPED ' . a:test . ': ' . substitute(v:exception, '^\S*\s\+', '', '')) + catch + call add(v:errors, 'Caught exception in ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint) + endtry + endif if exists("*TearDown") try @@ -105,7 +133,14 @@ func RunTheTest(test) endtry endif - " Close any extra windows and make the current one not modified. + " Clear any autocommands + au! + + " Close any extra tab pages and windows and make the current one not modified. + while tabpagenr('$') > 1 + quit! + endwhile + while 1 let wincount = winnr('$') if wincount == 1 @@ -118,7 +153,8 @@ func RunTheTest(test) break endif endwhile - set nomodified + + exe 'cd ' . save_cwd endfunc func AfterTheTest() @@ -197,8 +233,16 @@ endif " Names of flaky tests. let s:flaky = [ - \ 'Test_with_partial_callback()', + \ 'Test_exit_callback_interval()', \ 'Test_oneshot()', + \ 'Test_out_cb()', + \ 'Test_paused()', + \ 'Test_quoteplus()', + \ 'Test_reltime()', + \ 'Test_terminal_composing_unicode()', + \ 'Test_terminal_redir_file()', + \ 'Test_terminal_tmap()', + \ 'Test_with_partial_callback()', \ 'Test_lambda_with_timer()', \ ] @@ -215,12 +259,30 @@ endif " Execute the tests in alphabetical order. for s:test in sort(s:tests) + " Silence, please! + set belloff=all + call RunTheTest(s:test) if len(v:errors) > 0 && index(s:flaky, s:test) >= 0 + call add(s:messages, 'Found errors in ' . s:test . ':') + call extend(s:messages, v:errors) call add(s:messages, 'Flaky test failed, running it again') + let first_run = v:errors + + " Flakiness is often caused by the system being very busy. Sleep a couple + " of seconds to have a higher chance of succeeding the second time. + sleep 2 + let v:errors = [] call RunTheTest(s:test) + if len(v:errors) > 0 + let second_run = v:errors + let v:errors = ['First run:'] + call extend(v:errors, first_run) + call add(v:errors, 'Second run:') + call extend(v:errors, second_run) + endif endif call AfterTheTest() diff --git a/src/nvim/testdir/sautest/autoload/footest.vim b/src/nvim/testdir/sautest/autoload/footest.vim index f467bc376d..1e78963a10 100644 --- a/src/nvim/testdir/sautest/autoload/footest.vim +++ b/src/nvim/testdir/sautest/autoload/footest.vim @@ -1,4 +1,4 @@ -" Autoload script used by test55 and test60 +" Autoload script used by test_listdict.vim, test_exists.vim and test_let.vim let footest#x = 1 func footest#F() return 0 diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim index 06f2199214..7d6dd0c7ce 100644 --- a/src/nvim/testdir/setup.vim +++ b/src/nvim/testdir/setup.vim @@ -1,8 +1,15 @@ " Common preparations for running tests. -set noruler -set noshowcmd -set belloff= +" Align Nvim defaults to Vim. +set sidescroll=0 +set directory^=. +set backspace= +set nohidden smarttab noautoindent noautoread complete-=i noruler noshowcmd +set listchars=eol:$ +set fillchars=vert:\|,fold:- +" Prevent Nvim log from writing to stderr. +let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log' + " Make sure 'runtimepath' and 'packpath' does not include $HOME. set rtp=$VIM/vimfiles,$VIMRUNTIME,$VIM/vimfiles/after diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim index 72cfea96c6..4925b04a82 100644 --- a/src/nvim/testdir/shared.vim +++ b/src/nvim/testdir/shared.vim @@ -1,5 +1,25 @@ " Functions shared by several tests. +" {Nvim} +" Filepath captured from output may be truncated, like this: +" /home/va...estdir/Xtest-tmpdir/nvimxbXN4i/10 +" Get last 2 segments, then combine with $TMPDIR. +func! Fix_truncated_tmpfile(fname) + " sanity check + if $TMPDIR ==# '' + throw '$TMPDIR is empty' + endif + let tmpdir_tail = fnamemodify(substitute($TMPDIR, '[\/]\+$', '', 'g'), ':t') + if tmpdir_tail ==# '' + throw 'empty tmpdir_tail' + endif + if a:fname !~# tmpdir_tail + throw printf('$TMPDIR (%s) not in fname: %s', tmpdir_tail, a:fname) + endif + let last2segments = matchstr(a:fname, '[\/][^\/]\+[\/][^\/]\+$') + return $TMPDIR.last2segments +endfunc + " Get the name of the Python executable. " Also keeps it in s:python. func PythonProg() diff --git a/src/nvim/testdir/test32.in b/src/nvim/testdir/test32.in deleted file mode 100644 index 76bd9be889..0000000000 --- a/src/nvim/testdir/test32.in +++ /dev/null @@ -1,60 +0,0 @@ -Test for insert expansion - -:se cpt=.,w -* add-expands (word from next line) from other window -* add-expands (current buffer first) -* Local expansion, ends in an empty line (unless it becomes a global expansion) -* starts Local and switches to global add-expansion -:se cpt=.,w,i -* i-add-expands and switches to local -* add-expands lines (it would end in an empty line if it didn't ignored it self) -:se cpt=kXtestfile -* checks k-expansion, and file expansion (use Xtest11 instead of test11, -* because TEST11.OUT may match first on DOS) -:se cpt=w -* checks make_cyclic in other window -:se cpt=u nohid -* checks unloaded buffer expansion -* checks adding mode abortion -:se cpt=t,d -* tag expansion, define add-expansion interrupted -* t-expansion - -STARTTEST -:se backspace="" -:se cpt=.,w ff=unix | $-2,$w!Xtestfile | set ff& -:se cot= -nO#include "Xtestfile" -ru -O - - -:se cpt=.,w,i -kOM - -:se cpt=kXtestfile -:w Xtest11.one -:w Xtest11.two -OIXA -:" use CTRL-X CTRL-F to complete Xtest11.one, remove it and then use -:" CTRL-X CTRL-F again to verify this doesn't cause trouble. -OXddk -:se cpt=w -OST -:se cpt=u nohid -oOEN -unl -:se cpt=t,d def=^\\k* tags=Xtestfile notagbsearch -O -a -:wq! test.out -ENDTEST - -start of testfile -run1 -run2 -end of testfile - -test11 36Gepeto /Tag/ -asd test11file 36G -Makefile to run diff --git a/src/nvim/testdir/test32.ok b/src/nvim/testdir/test32.ok deleted file mode 100644 index afc4463fac..0000000000 --- a/src/nvim/testdir/test32.ok +++ /dev/null @@ -1,15 +0,0 @@ -#include "Xtestfile" -run1 run3 -run3 run3 - -Makefile to run3 -Makefile to run3 -Makefile to run3 -Xtest11.two -STARTTEST -ENDTEST -unless -test11file 36Gepeto /Tag/ asd -asd -run1 run2 - diff --git a/src/nvim/testdir/test53.in b/src/nvim/testdir/test53.in deleted file mode 100644 index 20b5d019af..0000000000 --- a/src/nvim/testdir/test53.in +++ /dev/null @@ -1,74 +0,0 @@ -Tests for string and html text objects. vim: set ft=vim : - -Note that the end-of-line moves the cursor to the next test line. - -Also test match() and matchstr() - -STARTTEST -/^start:/ -da" -0va'a'rx -02f`da` -0fXdi" -03f'vi'ry -:set quoteescape=+*- -di` -$F"va"oha"i"rz -:" -/^<begin -jfXdit -0fXdit -fXdat -0fXdat -dit -:" -:put =matchstr(\"abcd\", \".\", 0, 2) " b -:put =matchstr(\"abcd\", \"..\", 0, 2) " bc -:put =matchstr(\"abcd\", \".\", 2, 0) " c (zero and negative -> first match) -:put =matchstr(\"abcd\", \".\", 0, -1) " a -:put =match(\"abcd\", \".\", 0, 5) " -1 -:put =match(\"abcd\", \".\", 0, -1) " 0 -:put =match('abc', '.', 0, 1) " 0 -:put =match('abc', '.', 0, 2) " 1 -:put =match('abc', '.', 0, 3) " 2 -:put =match('abc', '.', 0, 4) " -1 -:put =match('abc', '.', 1, 1) " 1 -:put =match('abc', '.', 2, 1) " 2 -:put =match('abc', '.', 3, 1) " -1 -:put =match('abc', '$', 0, 1) " 3 -:put =match('abc', '$', 0, 2) " -1 -:put =match('abc', '$', 1, 1) " 3 -:put =match('abc', '$', 2, 1) " 3 -:put =match('abc', '$', 3, 1) " 3 -:put =match('abc', '$', 4, 1) " -1 -:put =match('abc', '\zs', 0, 1) " 0 -:put =match('abc', '\zs', 0, 2) " 1 -:put =match('abc', '\zs', 0, 3) " 2 -:put =match('abc', '\zs', 0, 4) " 3 -:put =match('abc', '\zs', 0, 5) " -1 -:put =match('abc', '\zs', 1, 1) " 1 -:put =match('abc', '\zs', 2, 1) " 2 -:put =match('abc', '\zs', 3, 1) " 3 -:put =match('abc', '\zs', 4, 1) " -1 -:/^start:/,/^end:/wq! test.out -ENDTEST - -start: "wo\"rd\\" foo -'foo' 'bar' 'piep' -bla bla `quote` blah -out " in "noXno" -"'" 'blah' rep 'buh' -bla `s*`d-`+++`l**` b`la -voo "nah" sdf " asdf" sdf " sdf" sd - -<begin> --<b>asdf<i>Xasdf</i>asdf</b>- --<b>asdX<i>a<i />sdf</i>asdf</b>- --<b>asdf<i>Xasdf</i>asdf</b>- --<b>asdX<i>as<b />df</i>asdf</b>- --<b> -innertext object -</b> -</begin> -SEARCH: -end: diff --git a/src/nvim/testdir/test53.ok b/src/nvim/testdir/test53.ok deleted file mode 100644 index d57d86bbb0..0000000000 --- a/src/nvim/testdir/test53.ok +++ /dev/null @@ -1,45 +0,0 @@ -start: foo -xxxxxxxxxxxx'piep' -bla bla blah -out " in "" -"'" 'blah'yyyyy'buh' -bla `` b`la -voo "zzzzzzzzzzzzzzzzzzzzzzzzzzzzsd - -<begin> --<b>asdf<i></i>asdf</b>- --<b></b>- --<b>asdfasdf</b>- --- --<b></b> -</begin> -b -bc -c -a --1 -0 -0 -1 -2 --1 -1 -2 --1 -3 --1 -3 -3 -3 --1 -0 -1 -2 -3 --1 -1 -2 -3 --1 -SEARCH: -end: diff --git a/src/nvim/testdir/test73.in b/src/nvim/testdir/test73.in deleted file mode 100644 index 9d50f7a789..0000000000 --- a/src/nvim/testdir/test73.in +++ /dev/null @@ -1,168 +0,0 @@ -Tests for find completion. - -STARTTEST -:set wildmode=full -:" Do all test in a separate window to avoid E211 when we recursively -:" delete the Xfind directory during cleanup -:" -:" This will cause a few errors, do it silently. -:set visualbell -:" -:" On windows a stale "Xfind" directory may exist, remove it so that -:" we start from a clean state. -:call delete("Xfind", "rf") -:new -:let cwd=getcwd() -:let test_out = cwd . '/test.out' -:call mkdir('Xfind') -:cd Xfind -:set path= -:find -:exec "w! " . test_out -:close -:new -:set path=. -:find -:exec "w >>" . test_out -:close -:new -:set path=.,, -:find -:exec "w >>" . test_out -:close -:new -:set path=./** -:find -:exec "w >>" . test_out -:close -:new -:" We shouldn't find any file at this point, test.out must be empty. -:call mkdir('in') -:cd in -:call mkdir('path') -:exec "cd " . cwd -:e Xfind/file.txt -SHoly Grail:w -:e Xfind/in/file.txt -SJimmy Hoffa:w -:e Xfind/in/stuff.txt -SAnother Holy Grail:w -:e Xfind/in/path/file.txt -SE.T.:w -:set path=Xfind/** -:find file -:exec "w >>" . test_out -:find file -:exec "w >>" . test_out -:find file -:exec "w >>" . test_out -:" Rerun the previous three find completions, using fullpath in 'path' -:exec "set path=" . cwd . "/Xfind/**" -:find file -:exec "w >>" . test_out -:find file -:exec "w >>" . test_out -:find file -:exec "w >>" . test_out -:" Same steps again, using relative and fullpath items that point to the same -:" recursive location. -:" This is to test that there are no duplicates in the completion list. -:exec "set path+=Xfind/**" -:find file -:exec "w >>" . test_out -:find file -:exec "w >>" . test_out -:find file -:exec "w >>" . test_out -:find file -:" Test find completion for directory of current buffer, which at this point -:" is Xfind/in/file.txt. -:set path=. -:find st -:exec "w >>" . test_out -:" Test find completion for empty path item ",," which is the current directory -:cd Xfind -:set path=,, -:find f -:exec "w >>" . test_out -:" Test shortening of -:" -:" foo/x/bar/voyager.txt -:" foo/y/bar/voyager.txt -:" -:" When current directory is above foo/ they should be shortened to (in order -:" of appearance): -:" -:" x/bar/voyager.txt -:" y/bar/voyager.txt -:call mkdir('foo') -:cd foo -:call mkdir('x') -:call mkdir('y') -:cd x -:call mkdir('bar') -:cd .. -:cd y -:call mkdir('bar') -:cd .. -:cd .. -:" We should now be in the Xfind directory -:e foo/x/bar/voyager.txt -SVoyager 1:w -:e foo/y/bar/voyager.txt -SVoyager 2:w -:exec "set path=" . cwd . "/Xfind/**" -:find voyager -:exec "w >>" . test_out -:find voyager -:exec "w >>" . test_out -:" -:" When current directory is .../foo/y/bar they should be shortened to (in -:" order of appearance): -:" -:" ./voyager.txt -:" x/bar/voyager.txt -:cd foo -:cd y -:cd bar -:find voyager -:exec "w >> " . test_out -:find voyager -:exec "w >> " . test_out -:" Check the opposite too: -:cd .. -:cd .. -:cd x -:cd bar -:find voyager -:exec "w >> " . test_out -:find voyager -:exec "w >> " . test_out -:" Check for correct handling of shorten_fname()'s behavior on windows -:exec "cd " . cwd . "/Xfind/in" -:find file -:exec "w >>" . test_out -:" Test for relative to current buffer 'path' item -:exec "cd " . cwd . "/Xfind/" -:set path=./path -:" Open the file where Jimmy Hoffa is found -:e in/file.txt -:" Find the file containing 'E.T.' in the Xfind/in/path directory -:find file -:exec "w >>" . test_out -:" -:" Test that completion works when path=.,, -:" -:set path=.,, -:" Open Jimmy Hoffa file -:e in/file.txt -:exec "w >>" . test_out -:" Search for the file containing Holy Grail in same directory as in/path.txt -:find stu -:exec "w >>" . test_out -:q -:exec "cd " . cwd -:call delete("Xfind", "rf") -:qa! -ENDTEST - diff --git a/src/nvim/testdir/test73.ok b/src/nvim/testdir/test73.ok deleted file mode 100644 index 90efab756f..0000000000 --- a/src/nvim/testdir/test73.ok +++ /dev/null @@ -1,21 +0,0 @@ -Holy Grail -Jimmy Hoffa -E.T. -Holy Grail -Jimmy Hoffa -E.T. -Holy Grail -Jimmy Hoffa -E.T. -Another Holy Grail -Holy Grail -Voyager 1 -Voyager 2 -Voyager 2 -Voyager 1 -Voyager 1 -Voyager 2 -Jimmy Hoffa -E.T. -Jimmy Hoffa -Another Holy Grail diff --git a/src/nvim/testdir/test79.in b/src/nvim/testdir/test79.in Binary files differdeleted file mode 100644 index afbf2083d2..0000000000 --- a/src/nvim/testdir/test79.in +++ /dev/null diff --git a/src/nvim/testdir/test79.ok b/src/nvim/testdir/test79.ok Binary files differdeleted file mode 100644 index d4e0ae8819..0000000000 --- a/src/nvim/testdir/test79.ok +++ /dev/null diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index c1f6405579..b4baf7a8d7 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -2,29 +2,33 @@ " This makes testing go faster, since Vim doesn't need to restart. source test_assign.vim +source test_changedtick.vim source test_cursor_func.vim -source test_execute_func.vim source test_ex_undo.vim +source test_execute_func.vim source test_expr.vim -source test_expr_utf8.vim source test_feedkeys.vim source test_filter_cmd.vim source test_filter_map.vim +source test_findfile.vim source test_float_func.vim source test_functions.vim source test_ga.vim source test_goto.vim source test_jumps.vim source test_fileformat.vim +source test_filetype.vim source test_lambda.vim source test_menu.vim source test_mapping.vim source test_messages.vim -source test_options.vim source test_partial.vim source test_popup.vim +source test_put.vim +source test_recover.vim source test_regexp_utf8.vim source test_source_utf8.vim +source test_sha256.vim source test_statusline.vim source test_syn_attr.vim source test_tabline.vim diff --git a/src/nvim/testdir/test_alot_latin.vim b/src/nvim/testdir/test_alot_latin.vim new file mode 100644 index 0000000000..ebb3bde4ce --- /dev/null +++ b/src/nvim/testdir/test_alot_latin.vim @@ -0,0 +1,10 @@ +" A series of tests that can run in one Vim invocation. +" This makes testing go faster, since Vim doesn't need to restart. + +" These tests use latin1 'encoding'. Setting 'encoding' is in the individual +" files, so that they can be run by themselves. + +" Nvim does not allow setting 'encoding', so skip this test group. +finish + +source test_regexp_latin.vim diff --git a/src/nvim/testdir/test_alot_utf8.vim b/src/nvim/testdir/test_alot_utf8.vim new file mode 100644 index 0000000000..648d806a94 --- /dev/null +++ b/src/nvim/testdir/test_alot_utf8.vim @@ -0,0 +1,17 @@ +" A series of tests that can run in one Vim invocation. +" This makes testing go faster, since Vim doesn't need to restart. + +" These tests use utf8 'encoding'. Setting 'encoding' is already done in +" runtest.vim. Checking for the multi_byte feature is in the individual +" files, so that they can be run by themselves. + +source test_charsearch_utf8.vim +source test_expr_utf8.vim +source test_listlbr_utf8.vim +source test_matchadd_conceal_utf8.vim +source test_mksession_utf8.vim +source test_regexp_utf8.vim +source test_source_utf8.vim +source test_startup_utf8.vim +source test_utf8.vim +source test_utf8_comparisons.vim diff --git a/src/nvim/testdir/test_arabic.vim b/src/nvim/testdir/test_arabic.vim new file mode 100644 index 0000000000..9a16833a4c --- /dev/null +++ b/src/nvim/testdir/test_arabic.vim @@ -0,0 +1,472 @@ +" Simplistic testing of Arabic mode. + +if !has('arabic') || !has('multi_byte') + finish +endif + +source view_util.vim + +" Return list of Unicode characters at line lnum. +" Combining characters are treated as a single item. +func s:get_chars(lnum) + call cursor(a:lnum, 1) + let chars = [] + let numchars = strchars(getline('.'), 1) + for i in range(1, numchars) + exe 'norm ' i . '|' + let c=execute('ascii') + let c=substitute(c, '\n\?<.\{-}Hex\s*', 'U+', 'g') + let c=substitute(c, ',\s*Octal\s*\d*', '', 'g') + call add(chars, c) + endfor + return chars +endfunc + +func Test_arabic_toggle() + set arabic + call assert_equal(1, &rightleft) + call assert_equal(1, &arabicshape) + call assert_equal('arabic', &keymap) + call assert_equal(1, &delcombine) + + set iminsert=1 imsearch=1 + set arabic& + call assert_equal(0, &rightleft) + call assert_equal(1, &arabicshape) + call assert_equal('arabic', &keymap) + call assert_equal(1, &delcombine) + call assert_equal(0, &iminsert) + call assert_equal(-1, &imsearch) + + set arabicshape& keymap= delcombine& +endfunc + +func Test_arabic_input() + new + set arabic + " Typing sghl in Arabic insert mode should show the + " Arabic word 'Salaam' i.e. 'peace', spelled: + " SEEN, LAM, ALEF, MEEM. + " See: https://www.mediawiki.org/wiki/VisualEditor/Typing/Right-to-left + call feedkeys('isghl!', 'tx') + call assert_match("^ *!\uFEE1\uFEFC\uFEB3$", ScreenLines(1, &columns)[0]) + call assert_equal([ + \ 'U+0633', + \ 'U+0644 U+0627', + \ 'U+0645', + \ 'U+21'], s:get_chars(1)) + + " Without shaping, it should give individual Arabic letters. + set noarabicshape + call assert_match("^ *!\u0645\u0627\u0644\u0633$", ScreenLines(1, &columns)[0]) + call assert_equal([ + \ 'U+0633', + \ 'U+0644', + \ 'U+0627', + \ 'U+0645', + \ 'U+21'], s:get_chars(1)) + + set arabic& arabicshape& + bwipe! +endfunc + +func Test_arabic_toggle_keymap() + new + set arabic + call feedkeys("i12\<C-^>12\<C-^>12", 'tx') + call assert_match("^ *٢١21٢١$", ScreenLines(1, &columns)[0]) + call assert_equal('١٢12١٢', getline('.')) + set arabic& + bwipe! +endfunc + +func Test_delcombine() + new + set arabic + call feedkeys("isghl\<BS>\<BS>", 'tx') + call assert_match("^ *\uFEDE\uFEB3$", ScreenLines(1, &columns)[0]) + call assert_equal(['U+0633', 'U+0644'], s:get_chars(1)) + + " Now the same with 'nodelcombine' + set nodelcombine + %d + call feedkeys("isghl\<BS>\<BS>", 'tx') + call assert_match("^ *\uFEB1$", ScreenLines(1, &columns)[0]) + call assert_equal(['U+0633'], s:get_chars(1)) + set arabic& + bwipe! +endfunc + +" Values from src/arabic.h (not all used yet) +let s:a_COMMA = "\u060C" +let s:a_SEMICOLON = "\u061B" +let s:a_QUESTION = "\u061F" +let s:a_HAMZA = "\u0621" +let s:a_ALEF_MADDA = "\u0622" +let s:a_ALEF_HAMZA_ABOVE = "\u0623" +let s:a_WAW_HAMZA = "\u0624" +let s:a_ALEF_HAMZA_BELOW = "\u0625" +let s:a_YEH_HAMZA = "\u0626" +let s:a_ALEF = "\u0627" +let s:a_BEH = "\u0628" +let s:a_TEH_MARBUTA = "\u0629" +let s:a_TEH = "\u062a" +let s:a_THEH = "\u062b" +let s:a_JEEM = "\u062c" +let s:a_HAH = "\u062d" +let s:a_KHAH = "\u062e" +let s:a_DAL = "\u062f" +let s:a_THAL = "\u0630" +let s:a_REH = "\u0631" +let s:a_ZAIN = "\u0632" +let s:a_SEEN = "\u0633" +let s:a_SHEEN = "\u0634" +let s:a_SAD = "\u0635" +let s:a_DAD = "\u0636" +let s:a_TAH = "\u0637" +let s:a_ZAH = "\u0638" +let s:a_AIN = "\u0639" +let s:a_GHAIN = "\u063a" +let s:a_TATWEEL = "\u0640" +let s:a_FEH = "\u0641" +let s:a_QAF = "\u0642" +let s:a_KAF = "\u0643" +let s:a_LAM = "\u0644" +let s:a_MEEM = "\u0645" +let s:a_NOON = "\u0646" +let s:a_HEH = "\u0647" +let s:a_WAW = "\u0648" +let s:a_ALEF_MAKSURA = "\u0649" +let s:a_YEH = "\u064a" + +let s:a_FATHATAN = "\u064b" +let s:a_DAMMATAN = "\u064c" +let s:a_KASRATAN = "\u064d" +let s:a_FATHA = "\u064e" +let s:a_DAMMA = "\u064f" +let s:a_KASRA = "\u0650" +let s:a_SHADDA = "\u0651" +let s:a_SUKUN = "\u0652" + +let s:a_MADDA_ABOVE = "\u0653" +let s:a_HAMZA_ABOVE = "\u0654" +let s:a_HAMZA_BELOW = "\u0655" + +let s:a_ZERO = "\u0660" +let s:a_ONE = "\u0661" +let s:a_TWO = "\u0662" +let s:a_THREE = "\u0663" +let s:a_FOUR = "\u0664" +let s:a_FIVE = "\u0665" +let s:a_SIX = "\u0666" +let s:a_SEVEN = "\u0667" +let s:a_EIGHT = "\u0668" +let s:a_NINE = "\u0669" +let s:a_PERCENT = "\u066a" +let s:a_DECIMAL = "\u066b" +let s:a_THOUSANDS = "\u066c" +let s:a_STAR = "\u066d" +let s:a_MINI_ALEF = "\u0670" + +let s:a_s_FATHATAN = "\ufe70" +let s:a_m_TATWEEL_FATHATAN = "\ufe71" +let s:a_s_DAMMATAN = "\ufe72" + +let s:a_s_KASRATAN = "\ufe74" + +let s:a_s_FATHA = "\ufe76" +let s:a_m_FATHA = "\ufe77" +let s:a_s_DAMMA = "\ufe78" +let s:a_m_DAMMA = "\ufe79" +let s:a_s_KASRA = "\ufe7a" +let s:a_m_KASRA = "\ufe7b" +let s:a_s_SHADDA = "\ufe7c" +let s:a_m_SHADDA = "\ufe7d" +let s:a_s_SUKUN = "\ufe7e" +let s:a_m_SUKUN = "\ufe7f" + +let s:a_s_HAMZA = "\ufe80" +let s:a_s_ALEF_MADDA = "\ufe81" +let s:a_f_ALEF_MADDA = "\ufe82" +let s:a_s_ALEF_HAMZA_ABOVE = "\ufe83" +let s:a_f_ALEF_HAMZA_ABOVE = "\ufe84" +let s:a_s_WAW_HAMZA = "\ufe85" +let s:a_f_WAW_HAMZA = "\ufe86" +let s:a_s_ALEF_HAMZA_BELOW = "\ufe87" +let s:a_f_ALEF_HAMZA_BELOW = "\ufe88" +let s:a_s_YEH_HAMZA = "\ufe89" +let s:a_f_YEH_HAMZA = "\ufe8a" +let s:a_i_YEH_HAMZA = "\ufe8b" +let s:a_m_YEH_HAMZA = "\ufe8c" +let s:a_s_ALEF = "\ufe8d" +let s:a_f_ALEF = "\ufe8e" +let s:a_s_BEH = "\ufe8f" +let s:a_f_BEH = "\ufe90" +let s:a_i_BEH = "\ufe91" +let s:a_m_BEH = "\ufe92" +let s:a_s_TEH_MARBUTA = "\ufe93" +let s:a_f_TEH_MARBUTA = "\ufe94" +let s:a_s_TEH = "\ufe95" +let s:a_f_TEH = "\ufe96" +let s:a_i_TEH = "\ufe97" +let s:a_m_TEH = "\ufe98" +let s:a_s_THEH = "\ufe99" +let s:a_f_THEH = "\ufe9a" +let s:a_i_THEH = "\ufe9b" +let s:a_m_THEH = "\ufe9c" +let s:a_s_JEEM = "\ufe9d" +let s:a_f_JEEM = "\ufe9e" +let s:a_i_JEEM = "\ufe9f" +let s:a_m_JEEM = "\ufea0" +let s:a_s_HAH = "\ufea1" +let s:a_f_HAH = "\ufea2" +let s:a_i_HAH = "\ufea3" +let s:a_m_HAH = "\ufea4" +let s:a_s_KHAH = "\ufea5" +let s:a_f_KHAH = "\ufea6" +let s:a_i_KHAH = "\ufea7" +let s:a_m_KHAH = "\ufea8" +let s:a_s_DAL = "\ufea9" +let s:a_f_DAL = "\ufeaa" +let s:a_s_THAL = "\ufeab" +let s:a_f_THAL = "\ufeac" +let s:a_s_REH = "\ufead" +let s:a_f_REH = "\ufeae" +let s:a_s_ZAIN = "\ufeaf" +let s:a_f_ZAIN = "\ufeb0" +let s:a_s_SEEN = "\ufeb1" +let s:a_f_SEEN = "\ufeb2" +let s:a_i_SEEN = "\ufeb3" +let s:a_m_SEEN = "\ufeb4" +let s:a_s_SHEEN = "\ufeb5" +let s:a_f_SHEEN = "\ufeb6" +let s:a_i_SHEEN = "\ufeb7" +let s:a_m_SHEEN = "\ufeb8" +let s:a_s_SAD = "\ufeb9" +let s:a_f_SAD = "\ufeba" +let s:a_i_SAD = "\ufebb" +let s:a_m_SAD = "\ufebc" +let s:a_s_DAD = "\ufebd" +let s:a_f_DAD = "\ufebe" +let s:a_i_DAD = "\ufebf" +let s:a_m_DAD = "\ufec0" +let s:a_s_TAH = "\ufec1" +let s:a_f_TAH = "\ufec2" +let s:a_i_TAH = "\ufec3" +let s:a_m_TAH = "\ufec4" +let s:a_s_ZAH = "\ufec5" +let s:a_f_ZAH = "\ufec6" +let s:a_i_ZAH = "\ufec7" +let s:a_m_ZAH = "\ufec8" +let s:a_s_AIN = "\ufec9" +let s:a_f_AIN = "\ufeca" +let s:a_i_AIN = "\ufecb" +let s:a_m_AIN = "\ufecc" +let s:a_s_GHAIN = "\ufecd" +let s:a_f_GHAIN = "\ufece" +let s:a_i_GHAIN = "\ufecf" +let s:a_m_GHAIN = "\ufed0" +let s:a_s_FEH = "\ufed1" +let s:a_f_FEH = "\ufed2" +let s:a_i_FEH = "\ufed3" +let s:a_m_FEH = "\ufed4" +let s:a_s_QAF = "\ufed5" +let s:a_f_QAF = "\ufed6" +let s:a_i_QAF = "\ufed7" +let s:a_m_QAF = "\ufed8" +let s:a_s_KAF = "\ufed9" +let s:a_f_KAF = "\ufeda" +let s:a_i_KAF = "\ufedb" +let s:a_m_KAF = "\ufedc" +let s:a_s_LAM = "\ufedd" +let s:a_f_LAM = "\ufede" +let s:a_i_LAM = "\ufedf" +let s:a_m_LAM = "\ufee0" +let s:a_s_MEEM = "\ufee1" +let s:a_f_MEEM = "\ufee2" +let s:a_i_MEEM = "\ufee3" +let s:a_m_MEEM = "\ufee4" +let s:a_s_NOON = "\ufee5" +let s:a_f_NOON = "\ufee6" +let s:a_i_NOON = "\ufee7" +let s:a_m_NOON = "\ufee8" +let s:a_s_HEH = "\ufee9" +let s:a_f_HEH = "\ufeea" +let s:a_i_HEH = "\ufeeb" +let s:a_m_HEH = "\ufeec" +let s:a_s_WAW = "\ufeed" +let s:a_f_WAW = "\ufeee" +let s:a_s_ALEF_MAKSURA = "\ufeef" +let s:a_f_ALEF_MAKSURA = "\ufef0" +let s:a_s_YEH = "\ufef1" +let s:a_f_YEH = "\ufef2" +let s:a_i_YEH = "\ufef3" +let s:a_m_YEH = "\ufef4" +let s:a_s_LAM_ALEF_MADDA_ABOVE = "\ufef5" +let s:a_f_LAM_ALEF_MADDA_ABOVE = "\ufef6" +let s:a_s_LAM_ALEF_HAMZA_ABOVE = "\ufef7" +let s:a_f_LAM_ALEF_HAMZA_ABOVE = "\ufef8" +let s:a_s_LAM_ALEF_HAMZA_BELOW = "\ufef9" +let s:a_f_LAM_ALEF_HAMZA_BELOW = "\ufefa" +let s:a_s_LAM_ALEF = "\ufefb" +let s:a_f_LAM_ALEF = "\ufefc" + +let s:a_BYTE_ORDER_MARK = "\ufeff" + +func Test_shape_initial() + new + set arabicshape + + " Shaping arabic {testchar} non-arabic Tests chg_c_a2i(). + " pair[0] = testchar, pair[1] = next-result, pair[2] = current-result + for pair in [[s:a_YEH_HAMZA, s:a_f_GHAIN, s:a_i_YEH_HAMZA], + \ [s:a_HAMZA, s:a_s_GHAIN, s:a_s_HAMZA], + \ [s:a_ALEF_MADDA, s:a_s_GHAIN, s:a_s_ALEF_MADDA], + \ [s:a_ALEF_HAMZA_ABOVE, s:a_s_GHAIN, s:a_s_ALEF_HAMZA_ABOVE], + \ [s:a_WAW_HAMZA, s:a_s_GHAIN, s:a_s_WAW_HAMZA], + \ [s:a_ALEF_HAMZA_BELOW, s:a_s_GHAIN, s:a_s_ALEF_HAMZA_BELOW], + \ [s:a_ALEF, s:a_s_GHAIN, s:a_s_ALEF], + \ [s:a_TEH_MARBUTA, s:a_s_GHAIN, s:a_s_TEH_MARBUTA], + \ [s:a_DAL, s:a_s_GHAIN, s:a_s_DAL], + \ [s:a_THAL, s:a_s_GHAIN, s:a_s_THAL], + \ [s:a_REH, s:a_s_GHAIN, s:a_s_REH], + \ [s:a_ZAIN, s:a_s_GHAIN, s:a_s_ZAIN], + \ [s:a_TATWEEL, s:a_f_GHAIN, s:a_TATWEEL], + \ [s:a_WAW, s:a_s_GHAIN, s:a_s_WAW], + \ [s:a_ALEF_MAKSURA, s:a_s_GHAIN, s:a_s_ALEF_MAKSURA], + \ [s:a_BEH, s:a_f_GHAIN, s:a_i_BEH], + \ [s:a_TEH, s:a_f_GHAIN, s:a_i_TEH], + \ [s:a_THEH, s:a_f_GHAIN, s:a_i_THEH], + \ [s:a_JEEM, s:a_f_GHAIN, s:a_i_JEEM], + \ [s:a_HAH, s:a_f_GHAIN, s:a_i_HAH], + \ [s:a_KHAH, s:a_f_GHAIN, s:a_i_KHAH], + \ [s:a_SEEN, s:a_f_GHAIN, s:a_i_SEEN], + \ [s:a_SHEEN, s:a_f_GHAIN, s:a_i_SHEEN], + \ [s:a_SAD, s:a_f_GHAIN, s:a_i_SAD], + \ [s:a_DAD, s:a_f_GHAIN, s:a_i_DAD], + \ [s:a_TAH, s:a_f_GHAIN, s:a_i_TAH], + \ [s:a_ZAH, s:a_f_GHAIN, s:a_i_ZAH], + \ [s:a_AIN, s:a_f_GHAIN, s:a_i_AIN], + \ [s:a_GHAIN, s:a_f_GHAIN, s:a_i_GHAIN], + \ [s:a_FEH, s:a_f_GHAIN, s:a_i_FEH], + \ [s:a_QAF, s:a_f_GHAIN, s:a_i_QAF], + \ [s:a_KAF, s:a_f_GHAIN, s:a_i_KAF], + \ [s:a_LAM, s:a_f_GHAIN, s:a_i_LAM], + \ [s:a_MEEM, s:a_f_GHAIN, s:a_i_MEEM], + \ [s:a_NOON, s:a_f_GHAIN, s:a_i_NOON], + \ [s:a_HEH, s:a_f_GHAIN, s:a_i_HEH], + \ [s:a_YEH, s:a_f_GHAIN, s:a_i_YEH], + \ ] + call setline(1, s:a_GHAIN . pair[0] . ' ') + call assert_equal([pair[1] . pair[2] . ' '], ScreenLines(1, 3)) + endfor + + set arabicshape& + bwipe! +endfunc + +func Test_shape_isolated() + new + set arabicshape + + " Shaping non-arabic {testchar} non-arabic Tests chg_c_a2s(). + " pair[0] = testchar, pair[1] = current-result + for pair in [[s:a_HAMZA, s:a_s_HAMZA], + \ [s:a_ALEF_MADDA, s:a_s_ALEF_MADDA], + \ [s:a_ALEF_HAMZA_ABOVE, s:a_s_ALEF_HAMZA_ABOVE], + \ [s:a_WAW_HAMZA, s:a_s_WAW_HAMZA], + \ [s:a_ALEF_HAMZA_BELOW, s:a_s_ALEF_HAMZA_BELOW], + \ [s:a_YEH_HAMZA, s:a_s_YEH_HAMZA], + \ [s:a_ALEF, s:a_s_ALEF], + \ [s:a_TEH_MARBUTA, s:a_s_TEH_MARBUTA], + \ [s:a_DAL, s:a_s_DAL], + \ [s:a_THAL, s:a_s_THAL], + \ [s:a_REH, s:a_s_REH], + \ [s:a_ZAIN, s:a_s_ZAIN], + \ [s:a_TATWEEL, s:a_TATWEEL], + \ [s:a_WAW, s:a_s_WAW], + \ [s:a_ALEF_MAKSURA, s:a_s_ALEF_MAKSURA], + \ [s:a_BEH, s:a_s_BEH], + \ [s:a_TEH, s:a_s_TEH], + \ [s:a_THEH, s:a_s_THEH], + \ [s:a_JEEM, s:a_s_JEEM], + \ [s:a_HAH, s:a_s_HAH], + \ [s:a_KHAH, s:a_s_KHAH], + \ [s:a_SEEN, s:a_s_SEEN], + \ [s:a_SHEEN, s:a_s_SHEEN], + \ [s:a_SAD, s:a_s_SAD], + \ [s:a_DAD, s:a_s_DAD], + \ [s:a_TAH, s:a_s_TAH], + \ [s:a_ZAH, s:a_s_ZAH], + \ [s:a_AIN, s:a_s_AIN], + \ [s:a_GHAIN, s:a_s_GHAIN], + \ [s:a_FEH, s:a_s_FEH], + \ [s:a_QAF, s:a_s_QAF], + \ [s:a_KAF, s:a_s_KAF], + \ [s:a_LAM, s:a_s_LAM], + \ [s:a_MEEM, s:a_s_MEEM], + \ [s:a_NOON, s:a_s_NOON], + \ [s:a_HEH, s:a_s_HEH], + \ [s:a_YEH, s:a_s_YEH], + \ ] + call setline(1, ' ' . pair[0] . ' ') + call assert_equal([' ' . pair[1] . ' '], ScreenLines(1, 3)) + endfor + + set arabicshape& + bwipe! +endfunc + +func Test_shape_medial() + new + set arabicshape + + " Shaping arabic {testchar} arabic Tests chg_c_a2m(). + " pair[0] = testchar, pair[1] = next-result, pair[2] = current-result, + " pair[3] = previous-result + for pair in [[s:a_HAMZA, s:a_s_GHAIN, s:a_s_HAMZA, s:a_s_BEH], + \[s:a_ALEF_MADDA, s:a_s_GHAIN, s:a_f_ALEF_MADDA, s:a_i_BEH], + \[s:a_ALEF_HAMZA_ABOVE, s:a_s_GHAIN, s:a_f_ALEF_HAMZA_ABOVE, s:a_i_BEH], + \[s:a_WAW_HAMZA, s:a_s_GHAIN, s:a_f_WAW_HAMZA, s:a_i_BEH], + \[s:a_ALEF_HAMZA_BELOW, s:a_s_GHAIN, s:a_f_ALEF_HAMZA_BELOW, s:a_i_BEH], + \[s:a_YEH_HAMZA, s:a_f_GHAIN, s:a_m_YEH_HAMZA, s:a_i_BEH], + \[s:a_ALEF, s:a_s_GHAIN, s:a_f_ALEF, s:a_i_BEH], + \[s:a_BEH, s:a_f_GHAIN, s:a_m_BEH, s:a_i_BEH], + \[s:a_TEH_MARBUTA, s:a_s_GHAIN, s:a_f_TEH_MARBUTA, s:a_i_BEH], + \[s:a_TEH, s:a_f_GHAIN, s:a_m_TEH, s:a_i_BEH], + \[s:a_THEH, s:a_f_GHAIN, s:a_m_THEH, s:a_i_BEH], + \[s:a_JEEM, s:a_f_GHAIN, s:a_m_JEEM, s:a_i_BEH], + \[s:a_HAH, s:a_f_GHAIN, s:a_m_HAH, s:a_i_BEH], + \[s:a_KHAH, s:a_f_GHAIN, s:a_m_KHAH, s:a_i_BEH], + \[s:a_DAL, s:a_s_GHAIN, s:a_f_DAL, s:a_i_BEH], + \[s:a_THAL, s:a_s_GHAIN, s:a_f_THAL, s:a_i_BEH], + \[s:a_REH, s:a_s_GHAIN, s:a_f_REH, s:a_i_BEH], + \[s:a_ZAIN, s:a_s_GHAIN, s:a_f_ZAIN, s:a_i_BEH], + \[s:a_SEEN, s:a_f_GHAIN, s:a_m_SEEN, s:a_i_BEH], + \[s:a_SHEEN, s:a_f_GHAIN, s:a_m_SHEEN, s:a_i_BEH], + \[s:a_SAD, s:a_f_GHAIN, s:a_m_SAD, s:a_i_BEH], + \[s:a_DAD, s:a_f_GHAIN, s:a_m_DAD, s:a_i_BEH], + \[s:a_TAH, s:a_f_GHAIN, s:a_m_TAH, s:a_i_BEH], + \[s:a_ZAH, s:a_f_GHAIN, s:a_m_ZAH, s:a_i_BEH], + \[s:a_AIN, s:a_f_GHAIN, s:a_m_AIN, s:a_i_BEH], + \[s:a_GHAIN, s:a_f_GHAIN, s:a_m_GHAIN, s:a_i_BEH], + \[s:a_TATWEEL, s:a_f_GHAIN, s:a_TATWEEL, s:a_i_BEH], + \[s:a_FEH, s:a_f_GHAIN, s:a_m_FEH, s:a_i_BEH], + \[s:a_QAF, s:a_f_GHAIN, s:a_m_QAF, s:a_i_BEH], + \[s:a_KAF, s:a_f_GHAIN, s:a_m_KAF, s:a_i_BEH], + \[s:a_LAM, s:a_f_GHAIN, s:a_m_LAM, s:a_i_BEH], + \[s:a_MEEM, s:a_f_GHAIN, s:a_m_MEEM, s:a_i_BEH], + \[s:a_NOON, s:a_f_GHAIN, s:a_m_NOON, s:a_i_BEH], + \[s:a_HEH, s:a_f_GHAIN, s:a_m_HEH, s:a_i_BEH], + \[s:a_WAW, s:a_s_GHAIN, s:a_f_WAW, s:a_i_BEH], + \[s:a_ALEF_MAKSURA, s:a_s_GHAIN, s:a_f_ALEF_MAKSURA, s:a_i_BEH], + \[s:a_YEH, s:a_f_GHAIN, s:a_m_YEH, s:a_i_BEH], + \ ] + call setline(1, s:a_GHAIN . pair[0] . s:a_BEH) + call assert_equal([pair[1] . pair[2] . pair[3]], ScreenLines(1, 3)) + endfor + + set arabicshape& + bwipe! +endfunc + diff --git a/src/nvim/testdir/test_arglist.vim b/src/nvim/testdir/test_arglist.vim new file mode 100644 index 0000000000..19d0cee47a --- /dev/null +++ b/src/nvim/testdir/test_arglist.vim @@ -0,0 +1,355 @@ +" Test argument list commands + +func Test_argidx() + args a b c + last + call assert_equal(2, argidx()) + %argdelete + call assert_equal(0, argidx()) + " doing it again doesn't result in an error + %argdelete + call assert_equal(0, argidx()) + call assert_fails('2argdelete', 'E16:') + + args a b c + call assert_equal(0, argidx()) + next + call assert_equal(1, argidx()) + next + call assert_equal(2, argidx()) + 1argdelete + call assert_equal(1, argidx()) + 1argdelete + call assert_equal(0, argidx()) + 1argdelete + call assert_equal(0, argidx()) +endfunc + +func Test_argadd() + %argdelete + argadd a b c + call assert_equal(0, argidx()) + + %argdelete + argadd a + call assert_equal(0, argidx()) + argadd b c d + call assert_equal(0, argidx()) + + call Init_abc() + argadd x + call Assert_argc(['a', 'b', 'x', 'c']) + call assert_equal(1, argidx()) + + call Init_abc() + 0argadd x + call Assert_argc(['x', 'a', 'b', 'c']) + call assert_equal(2, argidx()) + + call Init_abc() + 1argadd x + call Assert_argc(['a', 'x', 'b', 'c']) + call assert_equal(2, argidx()) + + call Init_abc() + $argadd x + call Assert_argc(['a', 'b', 'c', 'x']) + call assert_equal(1, argidx()) + + call Init_abc() + $argadd x + +2argadd y + call Assert_argc(['a', 'b', 'c', 'x', 'y']) + call assert_equal(1, argidx()) + + %argd + edit d + arga + call assert_equal(1, len(argv())) + call assert_equal('d', get(argv(), 0, '')) + + %argd + edit some\ file + arga + call assert_equal(1, len(argv())) + call assert_equal('some file', get(argv(), 0, '')) + + %argd + new + arga + call assert_equal(0, len(argv())) +endfunc + +func Init_abc() + args a b c + next +endfunc + +func Assert_argc(l) + call assert_equal(len(a:l), argc()) + let i = 0 + while i < len(a:l) && i < argc() + call assert_equal(a:l[i], argv(i)) + let i += 1 + endwhile +endfunc + +" Test for [count]argument and [count]argdelete commands +" Ported from the test_argument_count.in test script +func Test_argument() + " Clean the argument list + arga a | %argd + + let save_hidden = &hidden + set hidden + + let g:buffers = [] + augroup TEST + au BufEnter * call add(buffers, expand('%:t')) + augroup END + + argadd a b c d + $argu + $-argu + -argu + 1argu + +2argu + + augroup TEST + au! + augroup END + + call assert_equal(['d', 'c', 'b', 'a', 'c'], g:buffers) + + redir => result + ar + redir END + call assert_true(result =~# 'a b \[c] d') + + .argd + call assert_equal(['a', 'b', 'd'], argv()) + + -argd + call assert_equal(['a', 'd'], argv()) + + $argd + call assert_equal(['a'], argv()) + + 1arga c + 1arga b + $argu + $arga x + call assert_equal(['a', 'b', 'c', 'x'], argv()) + + 0arga y + call assert_equal(['y', 'a', 'b', 'c', 'x'], argv()) + + %argd + call assert_equal([], argv()) + + arga a b c d e f + 2,$-argd + call assert_equal(['a', 'f'], argv()) + + let &hidden = save_hidden + + " Setting argument list should fail when the current buffer has unsaved + " changes + %argd + enew! + set modified + call assert_fails('args x y z', 'E37:') + args! x y z + call assert_equal(['x', 'y', 'z'], argv()) + call assert_equal('x', expand('%:t')) + + last | enew | argu + call assert_equal('z', expand('%:t')) + + %argdelete + call assert_fails('argument', 'E163:') +endfunc + +" Test for 0argadd and 0argedit +" Ported from the test_argument_0count.in test script +func Test_zero_argadd() + " Clean the argument list + arga a | %argd + + arga a b c d + 2argu + 0arga added + call assert_equal(['added', 'a', 'b', 'c', 'd'], argv()) + + 2argu + arga third + call assert_equal(['added', 'a', 'third', 'b', 'c', 'd'], argv()) + + %argd + arga a b c d + 2argu + 0arge edited + call assert_equal(['edited', 'a', 'b', 'c', 'd'], argv()) + + 2argu + arga third + call assert_equal(['edited', 'a', 'third', 'b', 'c', 'd'], argv()) + + 2argu + argedit file\ with\ spaces another file + call assert_equal(['edited', 'a', 'file with spaces', 'another', 'file', 'third', 'b', 'c', 'd'], argv()) + call assert_equal('file with spaces', expand('%')) +endfunc + +func Reset_arglist() + args a | %argd +endfunc + +" Test for argc() +func Test_argc() + call Reset_arglist() + call assert_equal(0, argc()) + argadd a b + call assert_equal(2, argc()) +endfunc + +" Test for arglistid() +func Test_arglistid() + call Reset_arglist() + arga a b + call assert_equal(0, arglistid()) + split + arglocal + call assert_equal(1, arglistid()) + tabnew | tabfirst + call assert_equal(0, arglistid(2)) + call assert_equal(1, arglistid(1, 1)) + call assert_equal(0, arglistid(2, 1)) + call assert_equal(1, arglistid(1, 2)) + tabonly | only | enew! + argglobal + call assert_equal(0, arglistid()) +endfunc + +" Test for argv() +func Test_argv() + call Reset_arglist() + call assert_equal([], argv()) + call assert_equal("", argv(2)) + argadd a b c d + call assert_equal('c', argv(2)) +endfunc + +" Test for the :argedit command +func Test_argedit() + call Reset_arglist() + argedit a + call assert_equal(['a'], argv()) + call assert_equal('a', expand('%:t')) + argedit b + call assert_equal(['a', 'b'], argv()) + call assert_equal('b', expand('%:t')) + argedit a + call assert_equal(['a', 'b', 'a'], argv()) + call assert_equal('a', expand('%:t')) + " When file name case is ignored, an existing buffer with only case + " difference is re-used. + argedit C D + call assert_equal('C', expand('%:t')) + call assert_equal(['a', 'b', 'a', 'C', 'D'], argv()) + argedit c + if has('fname_case') + call assert_equal(['a', 'b', 'a', 'C', 'c', 'D'], argv()) + else + call assert_equal(['a', 'b', 'a', 'C', 'C', 'D'], argv()) + endif + 0argedit x + if has('fname_case') + call assert_equal(['x', 'a', 'b', 'a', 'C', 'c', 'D'], argv()) + else + call assert_equal(['x', 'a', 'b', 'a', 'C', 'C', 'D'], argv()) + endif + enew! | set modified + call assert_fails('argedit y', 'E37:') + argedit! y + if has('fname_case') + call assert_equal(['x', 'y', 'y', 'a', 'b', 'a', 'C', 'c', 'D'], argv()) + else + call assert_equal(['x', 'y', 'y', 'a', 'b', 'a', 'C', 'C', 'D'], argv()) + endif + %argd + bwipe! C + bwipe! D +endfunc + +" Test for the :argdelete command +func Test_argdelete() + call Reset_arglist() + args aa a aaa b bb + argdelete a* + call assert_equal(['b', 'bb'], argv()) + call assert_equal('aa', expand('%:t')) + last + argdelete % + call assert_equal(['b'], argv()) + call assert_fails('argdelete', 'E471:') + call assert_fails('1,100argdelete', 'E16:') + %argd +endfunc + +" Tests for the :next, :prev, :first, :last, :rewind commands +func Test_argpos() + call Reset_arglist() + args a b c d + last + call assert_equal(3, argidx()) + call assert_fails('next', 'E165:') + prev + call assert_equal(2, argidx()) + Next + call assert_equal(1, argidx()) + first + call assert_equal(0, argidx()) + call assert_fails('prev', 'E164:') + 3next + call assert_equal(3, argidx()) + rewind + call assert_equal(0, argidx()) + %argd +endfunc + +" Test for autocommand that redefines the argument list, when doing ":all". +func Test_arglist_autocmd() + autocmd BufReadPost Xxx2 next Xxx2 Xxx1 + call writefile(['test file Xxx1'], 'Xxx1') + call writefile(['test file Xxx2'], 'Xxx2') + call writefile(['test file Xxx3'], 'Xxx3') + + new + " redefine arglist; go to Xxx1 + next! Xxx1 Xxx2 Xxx3 + " open window for all args + all + call assert_equal('test file Xxx1', getline(1)) + wincmd w + wincmd w + call assert_equal('test file Xxx1', getline(1)) + " should now be in Xxx2 + rewind + call assert_equal('test file Xxx2', getline(1)) + + autocmd! BufReadPost Xxx2 + enew! | only + call delete('Xxx1') + call delete('Xxx2') + call delete('Xxx3') + argdelete Xxx* + bwipe! Xxx1 Xxx2 Xxx3 +endfunc + +func Test_arg_all_expand() + call writefile(['test file Xxx1'], 'Xx x') + next notexist Xx\ x runtest.vim + call assert_equal('notexist Xx\ x runtest.vim', expand('##')) + call delete('Xx x') +endfunc diff --git a/src/nvim/testdir/test_autochdir.vim b/src/nvim/testdir/test_autochdir.vim new file mode 100644 index 0000000000..05d69631c4 --- /dev/null +++ b/src/nvim/testdir/test_autochdir.vim @@ -0,0 +1,19 @@ +" Test 'autochdir' behavior + +if !exists("+autochdir") + finish +endif + +func Test_set_filename() + let cwd = getcwd() + call test_autochdir() + set acd + new + w samples/Xtest + call assert_equal("Xtest", expand('%')) + call assert_equal("samples", substitute(getcwd(), '.*/\(\k*\)', '\1', '')) + bwipe! + set noacd + exe 'cd ' . cwd + call delete('samples/Xtest') +endfunc diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 82c04abf5b..24651b75e1 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -1,14 +1,13 @@ " Tests for autocommands -set belloff=all -function! s:cleanup_buffers() abort +func! s:cleanup_buffers() abort for bnr in range(1, bufnr('$')) if bufloaded(bnr) && bufnr('%') != bnr execute 'bd! ' . bnr endif endfor -endfunction +endfunc func Test_vim_did_enter() call assert_false(v:vim_did_enter) @@ -49,7 +48,7 @@ if has('timers') endfunc endif -function Test_bufunload() +func Test_bufunload() augroup test_bufunload_group autocmd! autocmd BufUnload * call add(s:li, "bufunload") @@ -80,7 +79,7 @@ function Test_bufunload() endfunc " SEGV occurs in older versions. (At least 7.4.2005 or older) -function Test_autocmd_bufunload_with_tabnext() +func Test_autocmd_bufunload_with_tabnext() tabedit tabfirst @@ -98,7 +97,7 @@ function Test_autocmd_bufunload_with_tabnext() quit endfunc -function Test_autocmd_bufwinleave_with_tabfirst() +func Test_autocmd_bufwinleave_with_tabfirst() tabedit augroup sample autocmd! @@ -110,7 +109,7 @@ function Test_autocmd_bufwinleave_with_tabfirst() endfunc " SEGV occurs in older versions. (At least 7.4.2321 or older) -function Test_autocmd_bufunload_avoiding_SEGV_01() +func Test_autocmd_bufunload_avoiding_SEGV_01() split aa.txt let lastbuf = bufnr('$') @@ -128,7 +127,7 @@ function Test_autocmd_bufunload_avoiding_SEGV_01() endfunc " SEGV occurs in older versions. (At least 7.4.2321 or older) -function Test_autocmd_bufunload_avoiding_SEGV_02() +func Test_autocmd_bufunload_avoiding_SEGV_02() setlocal buftype=nowrite let lastbuf = bufnr('$') @@ -353,8 +352,10 @@ endfunc " Closing a window might cause an endless loop " E814 for older Vims -function Test_autocmd_bufwipe_in_SessLoadPost() +func Test_autocmd_bufwipe_in_SessLoadPost() + edit Xtest tabnew + file Xsomething set noswapfile mksession! @@ -362,7 +363,7 @@ function Test_autocmd_bufwipe_in_SessLoadPost() \ 'let v:swapchoice="e"', \ 'augroup test_autocmd_sessionload', \ 'autocmd!', - \ 'autocmd SessionLoadPost * 4bw!', + \ 'autocmd SessionLoadPost * exe bufnr("Xsomething") . "bw!"', \ 'augroup END', \ '', \ 'func WriteErrors()', @@ -382,7 +383,7 @@ function Test_autocmd_bufwipe_in_SessLoadPost() endfunc " SEGV occurs in older versions. -function Test_autocmd_bufwipe_in_SessLoadPost2() +func Test_autocmd_bufwipe_in_SessLoadPost2() tabnew set noswapfile mksession! @@ -421,6 +422,382 @@ function Test_autocmd_bufwipe_in_SessLoadPost2() endfor endfunc +func Test_empty_doau() + doau \| +endfunc + +func s:AutoCommandOptionSet(match) + let item = remove(g:options, 0) + let expected = printf("Option: <%s>, Oldval: <%s>, NewVal: <%s>, Scope: <%s>\n", item[0], item[1], item[2], item[3]) + let actual = printf("Option: <%s>, Oldval: <%s>, NewVal: <%s>, Scope: <%s>\n", a:match, v:option_old, v:option_new, v:option_type) + let g:opt = [expected, actual] + "call assert_equal(expected, actual) +endfunc + +func Test_OptionSet() + throw 'skipped: Nvim does not support test_override()' + if !has("eval") || !has("autocmd") || !exists("+autochdir") + return + endif + + call test_override('starting', 1) + set nocp + au OptionSet * :call s:AutoCommandOptionSet(expand("<amatch>")) + + " 1: Setting number option" + let g:options=[['number', 0, 1, 'global']] + set nu + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 2: Setting local number option" + let g:options=[['number', 1, 0, 'local']] + setlocal nonu + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 3: Setting global number option" + let g:options=[['number', 1, 0, 'global']] + setglobal nonu + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 4: Setting local autoindent option" + let g:options=[['autoindent', 0, 1, 'local']] + setlocal ai + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 5: Setting global autoindent option" + let g:options=[['autoindent', 0, 1, 'global']] + setglobal ai + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 6: Setting global autoindent option" + let g:options=[['autoindent', 1, 0, 'global']] + set ai! + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " Should not print anything, use :noa + " 7: don't trigger OptionSet" + let g:options=[['invalid', 1, 1, 'invalid']] + noa set nonu + call assert_equal([['invalid', 1, 1, 'invalid']], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 8: Setting several global list and number option" + let g:options=[['list', 0, 1, 'global'], ['number', 0, 1, 'global']] + set list nu + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 9: don't trigger OptionSet" + let g:options=[['invalid', 1, 1, 'invalid'], ['invalid', 1, 1, 'invalid']] + noa set nolist nonu + call assert_equal([['invalid', 1, 1, 'invalid'], ['invalid', 1, 1, 'invalid']], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 10: Setting global acd" + let g:options=[['autochdir', 0, 1, 'local']] + setlocal acd + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 11: Setting global autoread (also sets local value)" + let g:options=[['autoread', 0, 1, 'global']] + set ar + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 12: Setting local autoread" + let g:options=[['autoread', 1, 1, 'local']] + setlocal ar + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 13: Setting global autoread" + let g:options=[['autoread', 1, 0, 'global']] + setglobal invar + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 14: Setting option backspace through :let" + let g:options=[['backspace', '', 'eol,indent,start', 'global']] + let &bs="eol,indent,start" + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 15: Setting option backspace through setbufvar()" + let g:options=[['backup', 0, 1, 'local']] + " try twice, first time, shouldn't trigger because option name is invalid, + " second time, it should trigger + call assert_fails("call setbufvar(1, '&l:bk', 1)", "E355") + " should trigger, use correct option name + call setbufvar(1, '&backup', 1) + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 16: Setting number option using setwinvar" + let g:options=[['number', 0, 1, 'local']] + call setwinvar(0, '&number', 1) + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 17: Setting key option, shouldn't trigger" + let g:options=[['key', 'invalid', 'invalid1', 'invalid']] + setlocal key=blah + setlocal key= + call assert_equal([['key', 'invalid', 'invalid1', 'invalid']], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 18: Setting string option" + let oldval = &tags + let g:options=[['tags', oldval, 'tagpath', 'global']] + set tags=tagpath + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 1l: Resetting string option" + let g:options=[['tags', 'tagpath', oldval, 'global']] + set tags& + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " Cleanup + au! OptionSet + for opt in ['nu', 'ai', 'acd', 'ar', 'bs', 'backup', 'cul', 'cp'] + exe printf(":set %s&vi", opt) + endfor + call test_override('starting', 0) + delfunc! AutoCommandOptionSet +endfunc + +func Test_OptionSet_diffmode() + throw 'skipped: Nvim does not support test_override()' + call test_override('starting', 1) + " 18: Changing an option when enetering diff mode + new + au OptionSet diff :let &l:cul=v:option_new + + call setline(1, ['buffer 1', 'line2', 'line3', 'line4']) + call assert_equal(0, &l:cul) + diffthis + call assert_equal(1, &l:cul) + + vnew + call setline(1, ['buffer 2', 'line 2', 'line 3', 'line4']) + call assert_equal(0, &l:cul) + diffthis + call assert_equal(1, &l:cul) + + diffoff + call assert_equal(0, &l:cul) + call assert_equal(1, getwinvar(2, '&l:cul')) + bw! + + call assert_equal(1, &l:cul) + diffoff! + call assert_equal(0, &l:cul) + call assert_equal(0, getwinvar(1, '&l:cul')) + bw! + + " Cleanup + au! OptionSet + call test_override('starting', 0) +endfunc + +func Test_OptionSet_diffmode_close() + throw 'skipped: Nvim does not support test_override()' + call test_override('starting', 1) + " 19: Try to close the current window when entering diff mode + " should not segfault + new + au OptionSet diff close + + call setline(1, ['buffer 1', 'line2', 'line3', 'line4']) + call assert_fails(':diffthis', 'E788') + call assert_equal(1, &diff) + vnew + call setline(1, ['buffer 2', 'line 2', 'line 3', 'line4']) + call assert_fails(':diffthis', 'E788') + call assert_equal(1, &diff) + bw! + call assert_fails(':diffoff!', 'E788') + bw! + + " Cleanup + au! OptionSet + call test_override('starting', 0) + "delfunc! AutoCommandOptionSet +endfunc + +" Test for Bufleave autocommand that deletes the buffer we are about to edit. +func Test_BufleaveWithDelete() + new | edit Xfile1 + + augroup test_bufleavewithdelete + autocmd! + autocmd BufLeave Xfile1 bwipe Xfile2 + augroup END + + call assert_fails('edit Xfile2', 'E143:') + call assert_equal('Xfile1', bufname('%')) + + autocmd! test_bufleavewithdelete BufLeave Xfile1 + augroup! test_bufleavewithdelete + + new + bwipe! Xfile1 +endfunc + +" Test for autocommand that changes the buffer list, when doing ":ball". +func Test_Acmd_BufAll() + enew! + %bwipe! + call writefile(['Test file Xxx1'], 'Xxx1') + call writefile(['Test file Xxx2'], 'Xxx2') + call writefile(['Test file Xxx3'], 'Xxx3') + + " Add three files to the buffer list + split Xxx1 + close + split Xxx2 + close + split Xxx3 + close + + " Wipe the buffer when the buffer is opened + au BufReadPost Xxx2 bwipe + + call append(0, 'Test file Xxx4') + ball + + call assert_equal(2, winnr('$')) + call assert_equal('Xxx1', bufname(winbufnr(winnr('$')))) + wincmd t + + au! BufReadPost + %bwipe! + call delete('Xxx1') + call delete('Xxx2') + call delete('Xxx3') + enew! | only +endfunc + +" Test for autocommand that changes current buffer on BufEnter event. +" Check if modelines are interpreted for the correct buffer. +func Test_Acmd_BufEnter() + %bwipe! + call writefile(['start of test file Xxx1', + \ "\<Tab>this is a test", + \ 'end of test file Xxx1'], 'Xxx1') + call writefile(['start of test file Xxx2', + \ 'vim: set noai :', + \ "\<Tab>this is a test", + \ 'end of test file Xxx2'], 'Xxx2') + + au BufEnter Xxx2 brew + set ai modeline modelines=3 + edit Xxx1 + " edit Xxx2, autocmd will do :brew + edit Xxx2 + exe "normal G?this is a\<CR>" + " Append text with autoindent to this file + normal othis should be auto-indented + call assert_equal("\<Tab>this should be auto-indented", getline('.')) + call assert_equal(3, line('.')) + " Remove autocmd and edit Xxx2 again + au! BufEnter Xxx2 + buf! Xxx2 + exe "normal G?this is a\<CR>" + " append text without autoindent to Xxx + normal othis should be in column 1 + call assert_equal("this should be in column 1", getline('.')) + call assert_equal(4, line('.')) + + %bwipe! + call delete('Xxx1') + call delete('Xxx2') + set ai&vim modeline&vim modelines&vim +endfunc + +" Test for issue #57 +" do not move cursor on <c-o> when autoindent is set +func Test_ai_CTRL_O() + enew! + set ai + let save_fo = &fo + set fo+=r + exe "normal o# abcdef\<Esc>2hi\<CR>\<C-O>d0\<Esc>" + exe "normal o# abcdef\<Esc>2hi\<C-O>d0\<Esc>" + call assert_equal(['# abc', 'def', 'def'], getline(2, 4)) + + set ai&vim + let &fo = save_fo + enew! +endfunc + +" Test for autocommand that deletes the current buffer on BufLeave event. +" Also test deleting the last buffer, should give a new, empty buffer. +func Test_BufLeave_Wipe() + throw 'skipped: TODO: ' + %bwipe! + let content = ['start of test file Xxx', + \ 'this is a test', + \ 'end of test file Xxx'] + call writefile(content, 'Xxx1') + call writefile(content, 'Xxx2') + + au BufLeave Xxx2 bwipe + edit Xxx1 + split Xxx2 + " delete buffer Xxx2, we should be back to Xxx1 + bwipe + call assert_equal('Xxx1', bufname('%')) + call assert_equal(1, winnr('$')) + + " Create an alternate buffer + %write! test.out + call assert_equal('test.out', bufname('#')) + " delete alternate buffer + bwipe test.out + call assert_equal('Xxx1', bufname('%')) + call assert_equal('', bufname('#')) + + au BufLeave Xxx1 bwipe + " delete current buffer, get an empty one + bwipe! + call assert_equal(1, line('$')) + call assert_equal('', bufname('%')) + let g:bufinfo = getbufinfo() + call assert_equal(1, len(g:bufinfo)) + + call delete('Xxx1') + call delete('Xxx2') + call delete('test.out') + %bwipe + au! BufLeave + + " check that bufinfo doesn't contain a pointer to freed memory + call test_garbagecollect_now() +endfunc + +func Test_QuitPre() + edit Xfoo + let winid = win_getid(winnr()) + split Xbar + au! QuitPre * let g:afile = expand('<afile>') + " Close the other window, <afile> should be correct. + exe win_id2win(winid) . 'q' + call assert_equal('Xfoo', g:afile) + + unlet g:afile + bwipe Xfoo + bwipe Xbar +endfunc + func Test_Cmdline() au! CmdlineEnter : let g:entered = expand('<afile>') au! CmdlineLeave : let g:left = expand('<afile>') @@ -436,9 +813,355 @@ func Test_Cmdline() au! CmdlineLeave / let g:left = expand('<afile>') let g:entered = 0 let g:left = 0 - call feedkeys("/hello<CR>", 'xt') + new + call setline(1, 'hello') + call feedkeys("/hello\<CR>", 'xt') call assert_equal('/', g:entered) call assert_equal('/', g:left) + bwipe! au! CmdlineEnter au! CmdlineLeave endfunc + +" Test for BufWritePre autocommand that deletes or unloads the buffer. +func Test_BufWritePre() + %bwipe + au BufWritePre Xxx1 bunload + au BufWritePre Xxx2 bwipe + + call writefile(['start of Xxx1', 'test', 'end of Xxx1'], 'Xxx1') + call writefile(['start of Xxx2', 'test', 'end of Xxx2'], 'Xxx2') + + edit Xtest + e! Xxx2 + bdel Xtest + e Xxx1 + " write it, will unload it and give an error msg + call assert_fails('w', 'E203') + call assert_equal('Xxx2', bufname('%')) + edit Xtest + e! Xxx2 + bwipe Xtest + " write it, will delete the buffer and give an error msg + call assert_fails('w', 'E203') + call assert_equal('Xxx1', bufname('%')) + au! BufWritePre + call delete('Xxx1') + call delete('Xxx2') +endfunc + +" Test for BufUnload autocommand that unloads all the other buffers +func Test_bufunload_all() + call writefile(['Test file Xxx1'], 'Xxx1')" + call writefile(['Test file Xxx2'], 'Xxx2')" + + let content = [ + \ "func UnloadAllBufs()", + \ " let i = 1", + \ " while i <= bufnr('$')", + \ " if i != bufnr('%') && bufloaded(i)", + \ " exe i . 'bunload'", + \ " endif", + \ " let i += 1", + \ " endwhile", + \ "endfunc", + \ "au BufUnload * call UnloadAllBufs()", + \ "au VimLeave * call writefile(['Test Finished'], 'Xout')", + \ "edit Xxx1", + \ "split Xxx2", + \ "q"] + call writefile(content, 'Xtest') + + call delete('Xout') + call system(v:progpath. ' -u NORC -i NONE -N -S Xtest') + call assert_true(filereadable('Xout')) + + call delete('Xxx1') + call delete('Xxx2') + call delete('Xtest') + call delete('Xout') +endfunc + +" Some tests for buffer-local autocommands +func Test_buflocal_autocmd() + let g:bname = '' + edit xx + au BufLeave <buffer> let g:bname = expand("%") + " here, autocommand for xx should trigger. + " but autocommand shall not apply to buffer named <buffer>. + edit somefile + call assert_equal('xx', g:bname) + let g:bname = '' + " here, autocommand shall be auto-deleted + bwipe xx + " autocmd should not trigger + edit xx + call assert_equal('', g:bname) + " autocmd should not trigger + edit somefile + call assert_equal('', g:bname) + enew + unlet g:bname +endfunc + +" Test for "*Cmd" autocommands +func Test_Cmd_Autocmds() + call writefile(['start of Xxx', "\tabc2", 'end of Xxx'], 'Xxx') + + enew! + au BufReadCmd XtestA 0r Xxx|$del + edit XtestA " will read text of Xxd instead + call assert_equal('start of Xxx', getline(1)) + + au BufWriteCmd XtestA call append(line("$"), "write") + write " will append a line to the file + call assert_equal('write', getline('$')) + call assert_fails('read XtestA', 'E484') " should not read anything + call assert_equal('write', getline(4)) + + " now we have: + " 1 start of Xxx + " 2 abc2 + " 3 end of Xxx + " 4 write + + au FileReadCmd XtestB '[r Xxx + 2r XtestB " will read Xxx below line 2 instead + call assert_equal('start of Xxx', getline(3)) + + " now we have: + " 1 start of Xxx + " 2 abc2 + " 3 start of Xxx + " 4 abc2 + " 5 end of Xxx + " 6 end of Xxx + " 7 write + + au FileWriteCmd XtestC '[,']copy $ + normal 4GA1 + 4,5w XtestC " will copy lines 4 and 5 to the end + call assert_equal("\tabc21", getline(8)) + call assert_fails('r XtestC', 'E484') " should not read anything + call assert_equal("end of Xxx", getline(9)) + + " now we have: + " 1 start of Xxx + " 2 abc2 + " 3 start of Xxx + " 4 abc21 + " 5 end of Xxx + " 6 end of Xxx + " 7 write + " 8 abc21 + " 9 end of Xxx + + let g:lines = [] + au FileAppendCmd XtestD call extend(g:lines, getline(line("'["), line("']"))) + w >>XtestD " will add lines to 'lines' + call assert_equal(9, len(g:lines)) + call assert_fails('$r XtestD', 'E484') " should not read anything + call assert_equal(9, line('$')) + call assert_equal('end of Xxx', getline('$')) + + au BufReadCmd XtestE 0r Xxx|$del + sp XtestE " split window with test.out + call assert_equal('end of Xxx', getline(3)) + + let g:lines = [] + exe "normal 2Goasdf\<Esc>\<C-W>\<C-W>" + au BufWriteCmd XtestE call extend(g:lines, getline(0, '$')) + wall " will write other window to 'lines' + call assert_equal(4, len(g:lines), g:lines) + call assert_equal("\tasdf", g:lines[2]) + + au! BufReadCmd + au! BufWriteCmd + au! FileReadCmd + au! FileWriteCmd + au! FileAppendCmd + %bwipe! + call delete('Xxx') + enew! +endfunc + +func SetChangeMarks(start, end) + exe a:start. 'mark [' + exe a:end. 'mark ]' +endfunc + +" Verify the effects of autocmds on '[ and '] +func Test_change_mark_in_autocmds() + edit! Xtest + call feedkeys("ia\<CR>b\<CR>c\<CR>d\<C-g>u", 'xtn') + + call SetChangeMarks(2, 3) + write + call assert_equal([1, 4], [line("'["), line("']")]) + + call SetChangeMarks(2, 3) + au BufWritePre * call assert_equal([1, 4], [line("'["), line("']")]) + write + au! BufWritePre + + if executable('cat') + write XtestFilter + write >> XtestFilter + + call SetChangeMarks(2, 3) + " Marks are set to the entire range of the write + au FilterWritePre * call assert_equal([1, 4], [line("'["), line("']")]) + " '[ is adjusted to just before the line that will receive the filtered + " data + au FilterReadPre * call assert_equal([4, 4], [line("'["), line("']")]) + " The filtered data is read into the buffer, and the source lines are + " still present, so the range is after the source lines + au FilterReadPost * call assert_equal([5, 12], [line("'["), line("']")]) + %!cat XtestFilter + " After the filtered data is read, the original lines are deleted + call assert_equal([1, 8], [line("'["), line("']")]) + au! FilterWritePre,FilterReadPre,FilterReadPost + undo + + call SetChangeMarks(1, 4) + au FilterWritePre * call assert_equal([2, 3], [line("'["), line("']")]) + au FilterReadPre * call assert_equal([3, 3], [line("'["), line("']")]) + au FilterReadPost * call assert_equal([4, 11], [line("'["), line("']")]) + 2,3!cat XtestFilter + call assert_equal([2, 9], [line("'["), line("']")]) + au! FilterWritePre,FilterReadPre,FilterReadPost + undo + + call delete('XtestFilter') + endif + + call SetChangeMarks(1, 4) + au FileWritePre * call assert_equal([2, 3], [line("'["), line("']")]) + 2,3write Xtest2 + au! FileWritePre + + call SetChangeMarks(2, 3) + au FileAppendPre * call assert_equal([1, 4], [line("'["), line("']")]) + write >> Xtest2 + au! FileAppendPre + + call SetChangeMarks(1, 4) + au FileAppendPre * call assert_equal([2, 3], [line("'["), line("']")]) + 2,3write >> Xtest2 + au! FileAppendPre + + call SetChangeMarks(1, 1) + au FileReadPre * call assert_equal([3, 1], [line("'["), line("']")]) + au FileReadPost * call assert_equal([4, 11], [line("'["), line("']")]) + 3read Xtest2 + au! FileReadPre,FileReadPost + undo + + call SetChangeMarks(4, 4) + " When the line is 0, it's adjusted to 1 + au FileReadPre * call assert_equal([1, 4], [line("'["), line("']")]) + au FileReadPost * call assert_equal([1, 8], [line("'["), line("']")]) + 0read Xtest2 + au! FileReadPre,FileReadPost + undo + + call SetChangeMarks(4, 4) + " When the line is 0, it's adjusted to 1 + au FileReadPre * call assert_equal([1, 4], [line("'["), line("']")]) + au FileReadPost * call assert_equal([2, 9], [line("'["), line("']")]) + 1read Xtest2 + au! FileReadPre,FileReadPost + undo + + bwipe! + call delete('Xtest') + call delete('Xtest2') +endfunc + +func Test_Filter_noshelltemp() + if !executable('cat') + return + endif + + enew! + call setline(1, ['a', 'b', 'c', 'd']) + + let shelltemp = &shelltemp + set shelltemp + + let g:filter_au = 0 + au FilterWritePre * let g:filter_au += 1 + au FilterReadPre * let g:filter_au += 1 + au FilterReadPost * let g:filter_au += 1 + %!cat + call assert_equal(3, g:filter_au) + + if has('filterpipe') + set noshelltemp + + let g:filter_au = 0 + au FilterWritePre * let g:filter_au += 1 + au FilterReadPre * let g:filter_au += 1 + au FilterReadPost * let g:filter_au += 1 + %!cat + call assert_equal(0, g:filter_au) + endif + + au! FilterWritePre,FilterReadPre,FilterReadPost + let &shelltemp = shelltemp + bwipe! +endfunc + +func Test_TextYankPost() + enew! + call setline(1, ['foo']) + + let g:event = [] + au TextYankPost * let g:event = copy(v:event) + + call assert_equal({}, v:event) + call assert_fails('let v:event = {}', 'E46:') + call assert_fails('let v:event.mykey = 0', 'E742:') + + norm "ayiw + call assert_equal( + \{'regcontents': ['foo'], 'regname': 'a', 'operator': 'y', 'regtype': 'v'}, + \g:event) + norm y_ + call assert_equal( + \{'regcontents': ['foo'], 'regname': '', 'operator': 'y', 'regtype': 'V'}, + \g:event) + call feedkeys("\<C-V>y", 'x') + call assert_equal( + \{'regcontents': ['f'], 'regname': '', 'operator': 'y', 'regtype': "\x161"}, + \g:event) + norm "xciwbar + call assert_equal( + \{'regcontents': ['foo'], 'regname': 'x', 'operator': 'c', 'regtype': 'v'}, + \g:event) + norm "bdiw + call assert_equal( + \{'regcontents': ['bar'], 'regname': 'b', 'operator': 'd', 'regtype': 'v'}, + \g:event) + + call assert_equal({}, v:event) + + au! TextYankPost + unlet g:event + bwipe! +endfunc + +func Test_nocatch_wipe_all_buffers() + " Real nasty autocommand: wipe all buffers on any event. + au * * bwipe * + call assert_fails('next x', 'E93') + bwipe + au! +endfunc + +func Test_nocatch_wipe_dummy_buffer() + " Nasty autocommand: wipe buffer on any event. + au * x bwipe + call assert_fails('lv½ /x', 'E480') + au! +endfunc diff --git a/src/nvim/testdir/test_bufwintabinfo.vim b/src/nvim/testdir/test_bufwintabinfo.vim index 1c9350c416..a592cd7b11 100644 --- a/src/nvim/testdir/test_bufwintabinfo.vim +++ b/src/nvim/testdir/test_bufwintabinfo.vim @@ -1,7 +1,6 @@ " Tests for the getbufinfo(), getwininfo() and gettabinfo() functions function Test_getbufwintabinfo() - 1,$bwipeout edit Xtestfile1 edit Xtestfile2 let buflist = getbufinfo() diff --git a/src/nvim/testdir/test_changedtick.vim b/src/nvim/testdir/test_changedtick.vim new file mode 100644 index 0000000000..3a91bb54aa --- /dev/null +++ b/src/nvim/testdir/test_changedtick.vim @@ -0,0 +1,57 @@ +" Tests for b:changedtick + +func Test_changedtick_increments() + new + " New buffer has an empty line, tick starts at 2. + let expected = 2 + call assert_equal(expected, b:changedtick) + call assert_equal(expected, b:['changedtick']) + call setline(1, 'hello') + let expected += 1 + call assert_equal(expected, b:changedtick) + call assert_equal(expected, b:['changedtick']) + undo + " Somehow undo counts as two changes. + let expected += 2 + call assert_equal(expected, b:changedtick) + call assert_equal(expected, b:['changedtick']) + bwipe! +endfunc + +func Test_changedtick_dict_entry() + let d = b: + call assert_equal(b:changedtick, d['changedtick']) +endfunc + +func Test_changedtick_bdel() + new + let bnr = bufnr('%') + let v = b:changedtick + bdel + " Delete counts as a change too. + call assert_equal(v + 1, getbufvar(bnr, 'changedtick')) +endfunc + +func Test_changedtick_islocked() + call assert_equal(0, islocked('b:changedtick')) + let d = b: + call assert_equal(0, islocked('d.changedtick')) +endfunc + +func Test_changedtick_fixed() + call assert_fails('let b:changedtick = 4', 'E46:') + call assert_fails('let b:["changedtick"] = 4', 'E46:') + + call assert_fails('lockvar b:changedtick', 'E940:') + call assert_fails('lockvar b:["changedtick"]', 'E46:') + call assert_fails('unlockvar b:changedtick', 'E940:') + call assert_fails('unlockvar b:["changedtick"]', 'E46:') + call assert_fails('unlet b:changedtick', 'E795:') + call assert_fails('unlet b:["changedtick"]', 'E46:') + + let d = b: + call assert_fails('lockvar d["changedtick"]', 'E46:') + call assert_fails('unlockvar d["changedtick"]', 'E46:') + call assert_fails('unlet d["changedtick"]', 'E46:') + +endfunc diff --git a/src/nvim/testdir/test_charsearch_utf8.vim b/src/nvim/testdir/test_charsearch_utf8.vim new file mode 100644 index 0000000000..ade7dd408c --- /dev/null +++ b/src/nvim/testdir/test_charsearch_utf8.vim @@ -0,0 +1,22 @@ +" Tests for related f{char} and t{char} using utf-8. +if !has('multi_byte') + finish +endif + +" Test for t,f,F,T movement commands +function! Test_search_cmds() + new! + call setline(1, "・最åˆã‹ã‚‰æœ€å¾Œã¾ã§æœ€å¼·ã®Vimã¯æœ€é«˜") + 1 + normal! f最 + call assert_equal([0, 1, 4, 0], getpos('.')) + normal! ; + call assert_equal([0, 1, 16, 0], getpos('.')) + normal! 2; + call assert_equal([0, 1, 43, 0], getpos('.')) + normal! , + call assert_equal([0, 1, 28, 0], getpos('.')) + bw! +endfunction + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim new file mode 100644 index 0000000000..444c4c4109 --- /dev/null +++ b/src/nvim/testdir/test_cindent.vim @@ -0,0 +1,76 @@ +" Test for cinoptions and cindent +" +" TODO: rewrite test3.in into this new style test + +func Test_cino_hash() + " Test that curbuf->b_ind_hash_comment is correctly reset + new + setlocal cindent cinoptions=#1 + setlocal cinoptions= + call setline(1, ["#include <iostream>"]) + call cursor(1, 1) + norm! o#include + "call feedkeys("o#include\<esc>", 't') + call assert_equal(["#include <iostream>", "#include"], getline(1,2)) + bwipe! +endfunc + +func Test_cino_extern_c() + " Test for cino-E + + let without_ind = [ + \ '#ifdef __cplusplus', + \ 'extern "C" {', + \ '#endif', + \ 'int func_a(void);', + \ '#ifdef __cplusplus', + \ '}', + \ '#endif' + \ ] + + let with_ind = [ + \ '#ifdef __cplusplus', + \ 'extern "C" {', + \ '#endif', + \ "\tint func_a(void);", + \ '#ifdef __cplusplus', + \ '}', + \ '#endif' + \ ] + new + setlocal cindent cinoptions=E0 + call setline(1, without_ind) + call feedkeys("gg=G", 'tx') + call assert_equal(with_ind, getline(1, '$')) + + setlocal cinoptions=E-s + call setline(1, with_ind) + call feedkeys("gg=G", 'tx') + call assert_equal(without_ind, getline(1, '$')) + + setlocal cinoptions=Es + let tests = [ + \ ['recognized', ['extern "C" {'], "\t\t;"], + \ ['recognized', ['extern "C++" {'], "\t\t;"], + \ ['recognized', ['extern /* com */ "C"{'], "\t\t;"], + \ ['recognized', ['extern"C"{'], "\t\t;"], + \ ['recognized', ['extern "C"', '{'], "\t\t;"], + \ ['not recognized', ['extern {'], "\t;"], + \ ['not recognized', ['extern /*"C"*/{'], "\t;"], + \ ['not recognized', ['extern "C" //{'], ";"], + \ ['not recognized', ['extern "C" /*{*/'], ";"], + \ ] + + for pair in tests + let lines = pair[1] + call setline(1, lines) + call feedkeys(len(lines) . "Go;", 'tx') + call assert_equal(pair[2], getline(len(lines) + 1), 'Failed for "' . string(lines) . '"') + endfor + + + + bwipe! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_close_count.vim b/src/nvim/testdir/test_close_count.vim new file mode 100644 index 0000000000..1f9adba32d --- /dev/null +++ b/src/nvim/testdir/test_close_count.vim @@ -0,0 +1,174 @@ + +" Tests for :[count]close! command +func Test_close_count() + enew! | only + + let wids = [win_getid()] + for i in range(5) + new + call add(wids, win_getid()) + endfor + + 4wincmd w + close! + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[5], wids[4], wids[3], wids[1], wids[0]], ids) + + 1close! + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[4], wids[3], wids[1], wids[0]], ids) + + $close! + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[4], wids[3], wids[1]], ids) + + 1wincmd w + 2close! + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[4], wids[1]], ids) + + 1wincmd w + new + call add(wids, win_getid()) + new + call add(wids, win_getid()) + 2wincmd w + -1close! + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[6], wids[4], wids[1]], ids) + + 2wincmd w + +1close! + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[6], wids[4]], ids) + + only! +endfunc + +" Tests for :[count]hide command +func Test_hide_count() + enew! | only + + let wids = [win_getid()] + for i in range(5) + new + call add(wids, win_getid()) + endfor + + 4wincmd w + .hide + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[5], wids[4], wids[3], wids[1], wids[0]], ids) + + 1hide + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[4], wids[3], wids[1], wids[0]], ids) + + $hide + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[4], wids[3], wids[1]], ids) + + 1wincmd w + 2hide + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[4], wids[1]], ids) + + 1wincmd w + new + call add(wids, win_getid()) + new + call add(wids, win_getid()) + 3wincmd w + -hide + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[7], wids[4], wids[1]], ids) + + 2wincmd w + +hide + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[7], wids[4]], ids) + + only! +endfunc + +" Tests for :[count]close! command with 'hidden' +func Test_hidden_close_count() + enew! | only + + let wids = [win_getid()] + for i in range(5) + new + call add(wids, win_getid()) + endfor + + set hidden + + $ hide + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[5], wids[4], wids[3], wids[2], wids[1]], ids) + + $-1 close! + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[5], wids[4], wids[3], wids[1]], ids) + + 1wincmd w + .+close! + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[5], wids[3], wids[1]], ids) + + set nohidden + only! +endfunc + +" Tests for 'CTRL-W c' command to close windows. +func Test_winclose_command() + enew! | only + + let wids = [win_getid()] + for i in range(5) + new + call add(wids, win_getid()) + endfor + + set hidden + + 4wincmd w + exe "normal \<C-W>c" + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[5], wids[4], wids[3], wids[1], wids[0]], ids) + + exe "normal 1\<C-W>c" + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[4], wids[3], wids[1], wids[0]], ids) + + exe "normal 9\<C-W>c" + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[4], wids[3], wids[1]], ids) + + 1wincmd w + exe "normal 2\<C-W>c" + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[4], wids[1]], ids) + + set nohidden + only! +endfunc diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index ac44e09a5a..673246e1fb 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -1,5 +1,6 @@ " Tests for editing the command line. + func Test_complete_tab() call writefile(['testfile'], 'Xtestfile') call feedkeys(":e Xtestf\t\r", "tx") @@ -25,8 +26,62 @@ func Test_complete_wildmenu() set nowildmenu endfunc +func Test_map_completion() + if !has('cmdline_compl') + return + endif + call feedkeys(":map <unique> <si\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <unique> <silent>', getreg(':')) + call feedkeys(":map <script> <un\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <script> <unique>', getreg(':')) + call feedkeys(":map <expr> <sc\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <expr> <script>', getreg(':')) + call feedkeys(":map <buffer> <e\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <buffer> <expr>', getreg(':')) + call feedkeys(":map <nowait> <b\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <nowait> <buffer>', getreg(':')) + call feedkeys(":map <special> <no\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <special> <nowait>', getreg(':')) + call feedkeys(":map <silent> <sp\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <silent> <special>', getreg(':')) +endfunc + +func Test_match_completion() + if !has('cmdline_compl') + return + endif + hi Aardig ctermfg=green + call feedkeys(":match \<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"match Aardig', getreg(':')) + call feedkeys(":match \<S-Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"match none', getreg(':')) +endfunc + +func Test_highlight_completion() + if !has('cmdline_compl') + return + endif + hi Aardig ctermfg=green + call feedkeys(":hi \<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"hi Aardig', getreg(':')) + call feedkeys(":hi li\<S-Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"hi link', getreg(':')) + call feedkeys(":hi d\<S-Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"hi default', getreg(':')) + call feedkeys(":hi c\<S-Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"hi clear', getreg(':')) + + " A cleared group does not show up in completions. + hi Anders ctermfg=green + call assert_equal(['Aardig', 'Anders'], getcompletion('A', 'highlight')) + hi clear Aardig + call assert_equal(['Anders'], getcompletion('A', 'highlight')) + hi clear Anders + call assert_equal([], getcompletion('A', 'highlight')) +endfunc + func Test_expr_completion() - if !(has('cmdline_compl') && has('eval')) + if !has('cmdline_compl') return endif for cmd in [ @@ -265,17 +320,17 @@ func Test_paste_in_cmdline() endfunc func Test_remove_char_in_cmdline() - call feedkeys(":abc def\<S-Left>\<Del>\<C-B>\"\<CR>", 'tx') - call assert_equal('"abc ef', @:) + call feedkeys(":abc def\<S-Left>\<Del>\<C-B>\"\<CR>", 'tx') + call assert_equal('"abc ef', @:) - call feedkeys(":abc def\<S-Left>\<BS>\<C-B>\"\<CR>", 'tx') - call assert_equal('"abcdef', @:) + call feedkeys(":abc def\<S-Left>\<BS>\<C-B>\"\<CR>", 'tx') + call assert_equal('"abcdef', @:) - call feedkeys(":abc def ghi\<S-Left>\<C-W>\<C-B>\"\<CR>", 'tx') - call assert_equal('"abc ghi', @:) + call feedkeys(":abc def ghi\<S-Left>\<C-W>\<C-B>\"\<CR>", 'tx') + call assert_equal('"abc ghi', @:) - call feedkeys(":abc def\<S-Left>\<C-U>\<C-B>\"\<CR>", 'tx') - call assert_equal('"def', @:) + call feedkeys(":abc def\<S-Left>\<C-U>\<C-B>\"\<CR>", 'tx') + call assert_equal('"def', @:) endfunc func Test_illegal_address1() @@ -330,4 +385,36 @@ func Test_cmdline_search_range() bwipe! endfunc +" Tests for getcmdline(), getcmdpos() and getcmdtype() +func Check_cmdline(cmdtype) + call assert_equal('MyCmd a', getcmdline()) + call assert_equal(8, getcmdpos()) + call assert_equal(a:cmdtype, getcmdtype()) + return '' +endfunc + +func Test_getcmdtype() + call feedkeys(":MyCmd a\<C-R>=Check_cmdline(':')\<CR>\<Esc>", "xt") + + let cmdtype = '' + debuggreedy + call feedkeys(":debug echo 'test'\<CR>", "t") + call feedkeys("let cmdtype = \<C-R>=string(getcmdtype())\<CR>\<CR>", "t") + call feedkeys("cont\<CR>", "xt") + 0debuggreedy + call assert_equal('>', cmdtype) + + call feedkeys("/MyCmd a\<C-R>=Check_cmdline('/')\<CR>\<Esc>", "xt") + call feedkeys("?MyCmd a\<C-R>=Check_cmdline('?')\<CR>\<Esc>", "xt") + + call feedkeys(":call input('Answer?')\<CR>", "t") + call feedkeys("MyCmd a\<C-R>=Check_cmdline('@')\<CR>\<C-C>", "xt") + + call feedkeys(":insert\<CR>MyCmd a\<C-R>=Check_cmdline('-')\<CR>\<Esc>", "xt") + + cnoremap <expr> <F6> Check_cmdline('=') + call feedkeys("a\<C-R>=MyCmd a\<F6>\<Esc>\<Esc>", "xt") + cunmap <F6> +endfunc + set cpo& diff --git a/src/nvim/testdir/test_command_count.vim b/src/nvim/testdir/test_command_count.vim index e438a8b077..2d793ed88f 100644 --- a/src/nvim/testdir/test_command_count.vim +++ b/src/nvim/testdir/test_command_count.vim @@ -1,6 +1,7 @@ " Test for user command counts. func Test_command_count_0() + let bufnr = bufnr('%') set hidden set noswapfile @@ -15,17 +16,17 @@ func Test_command_count_0() command! -range=% -addr=buffers RangeBuffersAll :let lines = [<line1>, <line2>] .,$RangeLoadedBuffers - call assert_equal([1, 1], lines) + call assert_equal([bufnr, bufnr], lines) %RangeLoadedBuffers - call assert_equal([1, 1], lines) + call assert_equal([bufnr, bufnr], lines) RangeLoadedBuffersAll - call assert_equal([1, 1], lines) + call assert_equal([bufnr, bufnr], lines) .,$RangeBuffers - call assert_equal([1, lastbuf], lines) + call assert_equal([bufnr, lastbuf], lines) %RangeBuffers - call assert_equal([1, lastbuf], lines) + call assert_equal([bufnr, lastbuf], lines) RangeBuffersAll - call assert_equal([1, lastbuf], lines) + call assert_equal([bufnr, lastbuf], lines) delcommand RangeLoadedBuffers delcommand RangeLoadedBuffersAll @@ -138,6 +139,7 @@ func Test_command_count_2() endfunc func Test_command_count_3() + let bufnr = bufnr('%') se nohidden e aaa let buf_aaa = bufnr('%') @@ -145,7 +147,7 @@ func Test_command_count_3() let buf_bbb = bufnr('%') e ccc let buf_ccc = bufnr('%') - buf 1 + exe bufnr . 'buf' call assert_equal([1, 1, 1], [buflisted(buf_aaa), buflisted(buf_bbb), buflisted(buf_ccc)]) exe buf_bbb . "," . buf_ccc . "bdelete" call assert_equal([1, 0, 0], [buflisted(buf_aaa), buflisted(buf_bbb), buflisted(buf_ccc)]) @@ -155,7 +157,7 @@ endfunc func Test_command_count_4() %argd - let bufnr = bufnr('$') + 1 + let bufnr = bufnr('$') arga aa bb cc dd ee ff 3argu let args = [] @@ -171,6 +173,8 @@ func Test_command_count_4() only! exe bufnr . 'buf' + bnext + let bufnr = bufnr('%') let buffers = [] .,$-bufdo call add(buffers, bufnr('%')) call assert_equal([bufnr, bufnr + 1, bufnr + 2, bufnr + 3, bufnr + 4], buffers) diff --git a/src/nvim/testdir/test_curswant.vim b/src/nvim/testdir/test_curswant.vim new file mode 100644 index 0000000000..e54cd4b280 --- /dev/null +++ b/src/nvim/testdir/test_curswant.vim @@ -0,0 +1,23 @@ +" Tests for curswant not changing when setting an option + +func Test_curswant() + new + call append(0, ['1234567890', '12345']) + + normal! ggf8j + call assert_equal(7, winsaveview().curswant) + let &tabstop=&tabstop + call assert_equal(4, winsaveview().curswant) + + normal! ggf8j + call assert_equal(7, winsaveview().curswant) + let &timeoutlen=&timeoutlen + call assert_equal(7, winsaveview().curswant) + + normal! ggf8j + call assert_equal(7, winsaveview().curswant) + let &ttimeoutlen=&ttimeoutlen + call assert_equal(7, winsaveview().curswant) + + enew! +endfunc diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 8ee82bd538..d95b29759e 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -198,14 +198,69 @@ func Test_diffget_diffput() call assert_fails('diffget', 'E101:') windo diffoff - bwipe! - bwipe! - enew! + %bwipe! +endfunc + +func Test_dp_do_buffer() + e! one + let bn1=bufnr('%') + let l = range(60) + call setline(1, l) + diffthis + + new two + let l[10] = 'one' + let l[20] = 'two' + let l[30] = 'three' + let l[40] = 'four' + let l[50] = 'five' + call setline(1, l) + diffthis + + " dp and do with invalid buffer number. + 11 + call assert_fails('norm 99999dp', 'E102:') + call assert_fails('norm 99999do', 'E102:') + call assert_fails('diffput non_existing_buffer', 'E94:') + call assert_fails('diffget non_existing_buffer', 'E94:') + + " dp and do with valid buffer number. + call assert_equal('one', getline('.')) + exe 'norm ' . bn1 . 'do' + call assert_equal('10', getline('.')) + 21 + call assert_equal('two', getline('.')) + diffget one + call assert_equal('20', getline('.')) + + 31 + exe 'norm ' . bn1 . 'dp' + 41 + diffput one + wincmd w + 31 + call assert_equal('three', getline('.')) + 41 + call assert_equal('four', getline('.')) + + " dp and do with buffer number which is not in diff mode. + new not_in_diff_mode + let bn3=bufnr('%') + wincmd w + 51 + call assert_fails('exe "norm" . bn3 . "dp"', 'E103:') + call assert_fails('exe "norm" . bn3 . "do"', 'E103:') + call assert_fails('diffput not_in_diff_mode', 'E94:') + call assert_fails('diffget not_in_diff_mode', 'E94:') + + windo diffoff + %bwipe! endfunc func Test_diffoff() enew! call setline(1, ['Two', 'Three']) + redraw let normattr = screenattr(1, 1) diffthis botright vert new @@ -220,10 +275,107 @@ func Test_diffoff() bwipe! endfunc +func Test_diffopt_icase() + set diffopt=icase,foldcolumn:0 + + e one + call setline(1, ['One', 'Two', 'Three', 'Four']) + redraw + let normattr = screenattr(1, 1) + diffthis + + botright vert new two + call setline(1, ['one', 'TWO', 'Three ', 'Four']) + diffthis + + redraw + call assert_equal(normattr, screenattr(1, 1)) + call assert_equal(normattr, screenattr(2, 1)) + call assert_notequal(normattr, screenattr(3, 1)) + call assert_equal(normattr, screenattr(4, 1)) + + diffoff! + %bwipe! + set diffopt& +endfunc + +func Test_diffopt_iwhite() + set diffopt=iwhite,foldcolumn:0 + + e one + " Difference in trailing spaces should be ignored, + " but not other space differences. + call setline(1, ["One \t", 'Two', 'Three', 'Four']) + redraw + let normattr = screenattr(1, 1) + diffthis + + botright vert new two + call setline(1, ["One\t ", "Two\t ", 'Three', ' Four']) + diffthis + + redraw + call assert_equal(normattr, screenattr(1, 1)) + call assert_equal(normattr, screenattr(2, 1)) + call assert_equal(normattr, screenattr(3, 1)) + call assert_notequal(normattr, screenattr(4, 1)) + + diffoff! + %bwipe! + set diffopt& +endfunc + +func Test_diffopt_context() + enew! + call setline(1, ['1', '2', '3', '4', '5', '6', '7']) + diffthis + new + call setline(1, ['1', '2', '3', '4', '5x', '6', '7']) + diffthis + + set diffopt=context:2 + call assert_equal('+-- 2 lines: 1', foldtextresult(1)) + set diffopt=context:1 + call assert_equal('+-- 3 lines: 1', foldtextresult(1)) + + diffoff! + %bwipe! + set diffopt& +endfunc + +func Test_diffopt_horizontal() + set diffopt=horizontal + diffsplit + + call assert_equal(&columns, winwidth(1)) + call assert_equal(&columns, winwidth(2)) + call assert_equal(&lines, winheight(1) + winheight(2) + 3) + call assert_inrange(0, 1, winheight(1) - winheight(2)) + + set diffopt& + diffoff! + %bwipe +endfunc + +func Test_diffopt_vertical() + set diffopt=vertical + diffsplit + + call assert_equal(&lines - 2, winheight(1)) + call assert_equal(&lines - 2, winheight(2)) + call assert_equal(&columns, winwidth(1) + winwidth(2) + 1) + call assert_inrange(0, 1, winwidth(1) - winwidth(2)) + + set diffopt& + diffoff! + %bwipe +endfunc + func Test_diffoff_hidden() set diffopt=filler,foldcolumn:0 e! one call setline(1, ['Two', 'Three']) + redraw let normattr = screenattr(1, 1) diffthis botright vert new two @@ -272,3 +424,140 @@ func Test_setting_cursor() call delete('Xtest1') call delete('Xtest2') endfunc + +func Test_diff_move_to() + new + call setline(1, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + diffthis + vnew + call setline(1, [1, '2x', 3, 4, 4, 5, '6x', 7, '8x', 9, '10x']) + diffthis + norm ]c + call assert_equal(2, line('.')) + norm 3]c + call assert_equal(9, line('.')) + norm 10]c + call assert_equal(11, line('.')) + norm [c + call assert_equal(9, line('.')) + norm 2[c + call assert_equal(5, line('.')) + norm 10[c + call assert_equal(2, line('.')) + %bwipe! +endfunc + +func Test_diffexpr() + if !executable('diff') + return + endif + + func DiffExpr() + silent exe '!diff ' . v:fname_in . ' ' . v:fname_new . '>' . v:fname_out + endfunc + set diffexpr=DiffExpr() + set diffopt=foldcolumn:0 + + enew! + call setline(1, ['one', 'two', 'three']) + redraw + let normattr = screenattr(1, 1) + diffthis + + botright vert new + call setline(1, ['one', 'two', 'three.']) + diffthis + + redraw + call assert_equal(normattr, screenattr(1, 1)) + call assert_equal(normattr, screenattr(2, 1)) + call assert_notequal(normattr, screenattr(3, 1)) + + diffoff! + %bwipe! + set diffexpr& diffopt& +endfunc + +func Test_diffpatch() + " The patch program on MS-Windows may fail or hang. + if !executable('patch') || !has('unix') + return + endif + new + insert +*************** +*** 1,3 **** + 1 +! 2 + 3 +--- 1,4 ---- + 1 +! 2x + 3 ++ 4 +. + saveas Xpatch + bwipe! + new + call assert_fails('diffpatch Xpatch', 'E816:') + + for name in ['Xpatch', 'Xpatch$HOME', 'Xpa''tch'] + call setline(1, ['1', '2', '3']) + if name != 'Xpatch' + call rename('Xpatch', name) + endif + exe 'diffpatch ' . escape(name, '$') + call assert_equal(['1', '2x', '3', '4'], getline(1, '$')) + if name != 'Xpatch' + call rename(name, 'Xpatch') + endif + bwipe! + endfor + + call delete('Xpatch') + bwipe! +endfunc + +func Test_diff_too_many_buffers() + for i in range(1, 8) + exe "new Xtest" . i + diffthis + endfor + new Xtest9 + call assert_fails('diffthis', 'E96:') + %bwipe! +endfunc + +func Test_diff_nomodifiable() + new + call setline(1, [1, 2, 3, 4]) + setl nomodifiable + diffthis + vnew + call setline(1, ['1x', 2, 3, 3, 4]) + diffthis + call assert_fails('norm dp', 'E793:') + setl nomodifiable + call assert_fails('norm do', 'E21:') + %bwipe! +endfunc + +func Test_diff_lastline() + enew! + only! + call setline(1, ['This is a ', 'line with five ', 'rows']) + diffthis + botright vert new + call setline(1, ['This is', 'a line with ', 'four rows']) + diffthis + 1 + call feedkeys("Je a\<CR>", 'tx') + call feedkeys("Je a\<CR>", 'tx') + let w1lines = winline() + wincmd w + $ + let w2lines = winline() + call assert_equal(w2lines, w1lines) + bwipe! + bwipe! +endfunc diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim new file mode 100644 index 0000000000..8f815478c2 --- /dev/null +++ b/src/nvim/testdir/test_edit.vim @@ -0,0 +1,1325 @@ +" Test for edit functions +" +if exists("+t_kD") + let &t_kD="[3;*~" +endif + +" Needed for testing basic rightleft: Test_edit_rightleft +source view_util.vim + +" Needs to come first until the bug in getchar() is +" fixed: https://groups.google.com/d/msg/vim_dev/fXL9yme4H4c/bOR-U6_bAQAJ +func! Test_edit_00b() + new + call setline(1, ['abc ']) + inoreabbr <buffer> h here some more + call cursor(1, 4) + " <c-l> expands the abbreviation and ends insertmode + call feedkeys(":set im\<cr> h\<c-l>:set noim\<cr>", 'tix') + call assert_equal(['abc here some more '], getline(1,'$')) + iunabbr <buffer> h + bw! +endfunc + +func! Test_edit_01() + " set for Travis CI? + " set nocp noesckeys + new + " 1) empty buffer + call assert_equal([''], getline(1,'$')) + " 2) delete in an empty line + call feedkeys("i\<del>\<esc>", 'tnix') + call assert_equal([''], getline(1,'$')) + %d + " 3) delete one character + call setline(1, 'a') + call feedkeys("i\<del>\<esc>", 'tnix') + call assert_equal([''], getline(1,'$')) + %d + " 4) delete a multibyte character + if has("multi_byte") + call setline(1, "\u0401") + call feedkeys("i\<del>\<esc>", 'tnix') + call assert_equal([''], getline(1,'$')) + %d + endif + " 5.1) delete linebreak with 'bs' option containing eol + let _bs=&bs + set bs=eol + call setline(1, ["abc def", "ghi jkl"]) + call cursor(1, 1) + call feedkeys("A\<del>\<esc>", 'tnix') + call assert_equal(['abc defghi jkl'], getline(1, 2)) + %d + " 5.2) delete linebreak with backspace option w/out eol + set bs= + call setline(1, ["abc def", "ghi jkl"]) + call cursor(1, 1) + call feedkeys("A\<del>\<esc>", 'tnix') + call assert_equal(["abc def", "ghi jkl"], getline(1, 2)) + let &bs=_bs + bw! +endfunc + +func! Test_edit_02() + " Change cursor position in InsertCharPre command + new + call setline(1, 'abc') + call cursor(1, 1) + fu! DoIt(...) + call cursor(1, 4) + if len(a:000) + let v:char=a:1 + endif + endfu + au InsertCharPre <buffer> :call DoIt('y') + call feedkeys("ix\<esc>", 'tnix') + call assert_equal(['abcy'], getline(1, '$')) + " Setting <Enter> in InsertCharPre + au! InsertCharPre <buffer> :call DoIt("\n") + call setline(1, 'abc') + call cursor(1, 1) + call feedkeys("ix\<esc>", 'tnix') + call assert_equal(['abc', ''], getline(1, '$')) + %d + au! InsertCharPre + " Change cursor position in InsertEnter command + " 1) when setting v:char, keeps changed cursor position + au! InsertEnter <buffer> :call DoIt('y') + call setline(1, 'abc') + call cursor(1, 1) + call feedkeys("ix\<esc>", 'tnix') + call assert_equal(['abxc'], getline(1, '$')) + " 2) when not setting v:char, restores changed cursor position + au! InsertEnter <buffer> :call DoIt() + call setline(1, 'abc') + call cursor(1, 1) + call feedkeys("ix\<esc>", 'tnix') + call assert_equal(['xabc'], getline(1, '$')) + au! InsertEnter + delfu DoIt + bw! +endfunc + +func! Test_edit_03() + " Change cursor after <c-o> command to end of line + new + call setline(1, 'abc') + call cursor(1, 1) + call feedkeys("i\<c-o>$y\<esc>", 'tnix') + call assert_equal(['abcy'], getline(1, '$')) + %d + call setline(1, 'abc') + call cursor(1, 1) + call feedkeys("i\<c-o>80|y\<esc>", 'tnix') + call assert_equal(['abcy'], getline(1, '$')) + %d + call setline(1, 'abc') + call feedkeys("Ad\<c-o>:s/$/efg/\<cr>hij", 'tnix') + call assert_equal(['hijabcdefg'], getline(1, '$')) + bw! +endfunc + +func! Test_edit_04() + " test for :stopinsert + new + call setline(1, 'abc') + call cursor(1, 1) + call feedkeys("i\<c-o>:stopinsert\<cr>$", 'tnix') + call feedkeys("aX\<esc>", 'tnix') + call assert_equal(['abcX'], getline(1, '$')) + %d + bw! +endfunc + +func! Test_edit_05() + " test for folds being opened + new + call setline(1, ['abcX', 'abcX', 'zzzZ']) + call cursor(1, 1) + set foldmethod=manual foldopen+=insert + " create fold for those two lines + norm! Vjzf + call feedkeys("$ay\<esc>", 'tnix') + call assert_equal(['abcXy', 'abcX', 'zzzZ'], getline(1, '$')) + %d + call setline(1, ['abcX', 'abcX', 'zzzZ']) + call cursor(1, 1) + set foldmethod=manual foldopen-=insert + " create fold for those two lines + norm! Vjzf + call feedkeys("$ay\<esc>", 'tnix') + call assert_equal(['abcXy', 'abcX', 'zzzZ'], getline(1, '$')) + %d + bw! +endfunc + +func! Test_edit_06() + " Test in diff mode + if !has("diff") || !executable("diff") + return + endif + new + call setline(1, ['abc', 'xxx', 'yyy']) + vnew + call setline(1, ['abc', 'zzz', 'xxx', 'yyy']) + wincmd p + diffthis + wincmd p + diffthis + wincmd p + call cursor(2, 1) + norm! zt + call feedkeys("Ozzz\<esc>", 'tnix') + call assert_equal(['abc', 'zzz', 'xxx', 'yyy'], getline(1,'$')) + bw! + bw! +endfunc + +func! Test_edit_07() + " 1) Test with completion <c-l> when popupmenu is visible + new + call setline(1, 'J') + + func! ListMonths() + call complete(col('.')-1, ['January', 'February', 'March', + \ 'April', 'May', 'June', 'July', 'August', 'September', + \ 'October', 'November', 'December']) + return '' + endfunc + inoremap <buffer> <F5> <C-R>=ListMonths()<CR> + + call feedkeys("A\<f5>\<c-p>". repeat("\<down>", 6)."\<c-l>\<down>\<c-l>\<cr>", 'tx') + call assert_equal(['July'], getline(1,'$')) + " 1) Test completion when InsertCharPre kicks in + %d + call setline(1, 'J') + fu! DoIt() + if v:char=='u' + let v:char='an' + endif + endfu + au InsertCharPre <buffer> :call DoIt() + call feedkeys("A\<f5>\<c-p>u\<cr>\<c-l>\<cr>", 'tx') + call assert_equal(["Jan\<c-l>",''], getline(1,'$')) + %d + call setline(1, 'J') + call feedkeys("A\<f5>\<c-p>u\<down>\<c-l>\<cr>", 'tx') + call assert_equal(["January"], getline(1,'$')) + + delfu ListMonths + delfu DoIt + iunmap <buffer> <f5> + bw! +endfunc + +func! Test_edit_08() + throw 'skipped: moved to test/functional/legacy/edit_spec.lua' + " reset insertmode from i_ctrl-r_= + let g:bufnr = bufnr('%') + new + call setline(1, ['abc']) + call cursor(1, 4) + call feedkeys(":set im\<cr>ZZZ\<c-r>=setbufvar(g:bufnr,'&im', 0)\<cr>",'tnix') + call assert_equal(['abZZZc'], getline(1,'$')) + call assert_equal([0, 1, 1, 0], getpos('.')) + call assert_false(0, '&im') + bw! + unlet g:bufnr +endfunc + +func! Test_edit_09() + " test i_CTRL-\ combinations + new + call setline(1, ['abc', 'def', 'ghi']) + call cursor(1, 1) + " 1) CTRL-\ CTLR-N + call feedkeys(":set im\<cr>\<c-\>\<c-n>ccABC\<c-l>", 'txin') + call assert_equal(['ABC', 'def', 'ghi'], getline(1,'$')) + call setline(1, ['ABC', 'def', 'ghi']) + " 2) CTRL-\ CTLR-G + call feedkeys("j0\<c-\>\<c-g>ZZZ\<cr>\<c-l>", 'txin') + call assert_equal(['ABC', 'ZZZ', 'def', 'ghi'], getline(1,'$')) + call feedkeys("I\<c-\>\<c-g>YYY\<c-l>", 'txin') + call assert_equal(['ABC', 'ZZZ', 'YYYdef', 'ghi'], getline(1,'$')) + set noinsertmode + " 3) CTRL-\ CTRL-O + call setline(1, ['ABC', 'ZZZ', 'def', 'ghi']) + call cursor(1, 1) + call feedkeys("A\<c-o>ix", 'txin') + call assert_equal(['ABxC', 'ZZZ', 'def', 'ghi'], getline(1,'$')) + call feedkeys("A\<c-\>\<c-o>ix", 'txin') + call assert_equal(['ABxCx', 'ZZZ', 'def', 'ghi'], getline(1,'$')) + " 4) CTRL-\ a (should be inserted literally, not special after <c-\> + call setline(1, ['ABC', 'ZZZ', 'def', 'ghi']) + call cursor(1, 1) + call feedkeys("A\<c-\>a", 'txin') + call assert_equal(["ABC\<c-\>a", 'ZZZ', 'def', 'ghi'], getline(1, '$')) + bw! +endfunc + +func! Test_edit_10() + " Test for starting selectmode + new + set selectmode=key keymodel=startsel + call setline(1, ['abc', 'def', 'ghi']) + call cursor(1, 4) + call feedkeys("A\<s-home>start\<esc>", 'txin') + call assert_equal(['startdef', 'ghi'], getline(1, '$')) + set selectmode= keymodel= + bw! +endfunc + +func! Test_edit_11() + " Test that indenting kicks in + new + set cindent + call setline(1, ['{', '', '']) + call cursor(2, 1) + call feedkeys("i\<c-f>int c;\<esc>", 'tnix') + call cursor(3, 1) + call feedkeys("i/* comment */", 'tnix') + call assert_equal(['{', "\<tab>int c;", "/* comment */"], getline(1, '$')) + " added changed cindentkeys slightly + set cindent cinkeys+=*/ + call setline(1, ['{', '', '']) + call cursor(2, 1) + call feedkeys("i\<c-f>int c;\<esc>", 'tnix') + call cursor(3, 1) + call feedkeys("i/* comment */", 'tnix') + call assert_equal(['{', "\<tab>int c;", "\<tab>/* comment */"], getline(1, '$')) + set cindent cinkeys+==end + call feedkeys("oend\<cr>\<esc>", 'tnix') + call assert_equal(['{', "\<tab>int c;", "\<tab>/* comment */", "\tend", ''], getline(1, '$')) + set cinkeys-==end + %d + " Use indentexpr instead of cindenting + func! Do_Indent() + if v:lnum == 3 + return 3*shiftwidth() + else + return 2*shiftwidth() + endif + endfunc + setl indentexpr=Do_Indent() indentkeys+=*/ + call setline(1, ['{', '', '']) + call cursor(2, 1) + call feedkeys("i\<c-f>int c;\<esc>", 'tnix') + call cursor(3, 1) + call feedkeys("i/* comment */", 'tnix') + call assert_equal(['{', "\<tab>\<tab>int c;", "\<tab>\<tab>\<tab>/* comment */"], getline(1, '$')) + set cinkeys&vim indentkeys&vim + set nocindent indentexpr= + delfu Do_Indent + bw! +endfunc + +func! Test_edit_12() + " Test changing indent in replace mode + new + call setline(1, ["\tabc", "\tdef"]) + call cursor(2, 4) + call feedkeys("R^\<c-d>", 'tnix') + call assert_equal(["\tabc", "def"], getline(1, '$')) + call assert_equal([0, 2, 2, 0], getpos('.')) + %d + call setline(1, ["\tabc", "\t\tdef"]) + call cursor(2, 2) + call feedkeys("R^\<c-d>", 'tnix') + call assert_equal(["\tabc", "def"], getline(1, '$')) + call assert_equal([0, 2, 1, 0], getpos('.')) + %d + call setline(1, ["\tabc", "\t\tdef"]) + call cursor(2, 2) + call feedkeys("R\<c-t>", 'tnix') + call assert_equal(["\tabc", "\t\t\tdef"], getline(1, '$')) + call assert_equal([0, 2, 2, 0], getpos('.')) + bw! + 10vnew + call setline(1, ["\tabc", "\t\tdef"]) + call cursor(2, 2) + call feedkeys("R\<c-t>", 'tnix') + call assert_equal(["\tabc", "\t\t\tdef"], getline(1, '$')) + call assert_equal([0, 2, 2, 0], getpos('.')) + %d + set sw=4 + call setline(1, ["\tabc", "\t\tdef"]) + call cursor(2, 2) + call feedkeys("R\<c-t>\<c-t>", 'tnix') + call assert_equal(["\tabc", "\t\t\tdef"], getline(1, '$')) + call assert_equal([0, 2, 2, 0], getpos('.')) + %d + call setline(1, ["\tabc", "\t\tdef"]) + call cursor(2, 2) + call feedkeys("R\<c-t>\<c-t>", 'tnix') + call assert_equal(["\tabc", "\t\t\tdef"], getline(1, '$')) + call assert_equal([0, 2, 2, 0], getpos('.')) + set et + set sw& et& + %d + call setline(1, ["\t/*"]) + set formatoptions=croql + call cursor(1, 3) + call feedkeys("A\<cr>\<cr>/", 'tnix') + call assert_equal(["\t/*", " *", " */"], getline(1, '$')) + set formatoptions& + bw! +endfunc + +func! Test_edit_13() + " Test smartindenting + if exists("+smartindent") + new + set smartindent autoindent + call setline(1, ["\tabc"]) + call feedkeys("A {\<cr>more\<cr>}\<esc>", 'tnix') + call assert_equal(["\tabc {", "\t\tmore", "\t}"], getline(1, '$')) + set smartindent& autoindent& + bw! + endif +endfunc + +func! Test_edit_CR() + " Test for <CR> in insert mode + " basically only in quickfix mode ist tested, the rest + " has been taken care of by other tests + if !has("quickfix") + return + endif + botright new + call writefile(range(1, 10), 'Xqflist.txt') + call setqflist([{'filename': 'Xqflist.txt', 'lnum': 2}]) + copen + set modifiable + call feedkeys("A\<cr>", 'tnix') + call assert_equal('Xqflist.txt', bufname('')) + call assert_equal(2, line('.')) + cclose + botright new + call setloclist(0, [{'filename': 'Xqflist.txt', 'lnum': 10}]) + lopen + set modifiable + call feedkeys("A\<cr>", 'tnix') + call assert_equal('Xqflist.txt', bufname('')) + call assert_equal(10, line('.')) + call feedkeys("A\<Enter>", 'tnix') + call feedkeys("A\<kEnter>", 'tnix') + call feedkeys("A\n", 'tnix') + call feedkeys("A\r", 'tnix') + call assert_equal(map(range(1, 10), 'string(v:val)') + ['', '', '', ''], getline(1, '$')) + bw! + lclose + call delete('Xqflist.txt') +endfunc + +func! Test_edit_CTRL_() + " disabled for Windows builds, why? + if !has("multi_byte") || !has("rightleft") || has("win32") + return + endif + let _encoding=&encoding + set encoding=utf-8 + " Test for CTRL-_ + new + call setline(1, ['abc']) + call cursor(1, 1) + call feedkeys("i\<c-_>xyz\<esc>", 'tnix') + call assert_equal(["\<C-_>xyzabc"], getline(1, '$')) + call assert_false(&revins) + set ari + call setline(1, ['abc']) + call cursor(1, 1) + call feedkeys("i\<c-_>xyz\<esc>", 'tnix') + call assert_equal(["æèñabc"], getline(1, '$')) + call assert_true(&revins) + call setline(1, ['abc']) + call cursor(1, 1) + call feedkeys("i\<c-_>xyz\<esc>", 'tnix') + call assert_equal(["xyzabc"], getline(1, '$')) + call assert_false(&revins) + set noari + let &encoding=_encoding + bw! +endfunc + +" needs to come first, to have the @. register empty +func! Test_edit_00a_CTRL_A() + " Test pressing CTRL-A + new + call setline(1, repeat([''], 5)) + call cursor(1, 1) + try + call feedkeys("A\<NUL>", 'tnix') + catch /^Vim\%((\a\+)\)\=:E29/ + call assert_true(1, 'E29 error caught') + endtry + call cursor(1, 1) + call feedkeys("Afoobar \<esc>", 'tnix') + call cursor(2, 1) + call feedkeys("A\<c-a>more\<esc>", 'tnix') + call cursor(3, 1) + call feedkeys("A\<NUL>and more\<esc>", 'tnix') + call assert_equal(['foobar ', 'foobar more', 'foobar morend more', '', ''], getline(1, '$')) + bw! +endfunc + +func! Test_edit_CTRL_EY() + " Ctrl-E/ Ctrl-Y in insert mode completion to scroll + 10new + call setline(1, range(1, 100)) + call cursor(30, 1) + norm! z. + call feedkeys("A\<c-x>\<c-e>\<c-e>\<c-e>\<c-e>\<c-e>", 'tnix') + call assert_equal(30, winsaveview()['topline']) + call assert_equal([0, 30, 2, 0], getpos('.')) + call feedkeys("A\<c-x>\<c-e>\<c-e>\<c-e>\<c-e>\<c-e>", 'tnix') + call feedkeys("A\<c-x>".repeat("\<c-y>", 10), 'tnix') + call assert_equal(21, winsaveview()['topline']) + call assert_equal([0, 30, 2, 0], getpos('.')) + bw! +endfunc + +func! Test_edit_CTRL_G() + new + call setline(1, ['foobar', 'foobar', 'foobar']) + call cursor(2, 4) + call feedkeys("ioooooooo\<c-g>k\<c-r>.\<esc>", 'tnix') + call assert_equal(['foooooooooobar', 'foooooooooobar', 'foobar'], getline(1, '$')) + call assert_equal([0, 1, 11, 0], getpos('.')) + call feedkeys("i\<c-g>k\<esc>", 'tnix') + call assert_equal([0, 1, 10, 0], getpos('.')) + call cursor(2, 4) + call feedkeys("i\<c-g>jzzzz\<esc>", 'tnix') + call assert_equal(['foooooooooobar', 'foooooooooobar', 'foozzzzbar'], getline(1, '$')) + call assert_equal([0, 3, 7, 0], getpos('.')) + call feedkeys("i\<c-g>j\<esc>", 'tnix') + call assert_equal([0, 3, 6, 0], getpos('.')) + bw! +endfunc + +func! Test_edit_CTRL_I() + " Tab in completion mode + let path=expand("%:p:h") + new + call setline(1, [path."/", '']) + call feedkeys("Arunt\<c-x>\<c-f>\<tab>\<cr>\<esc>", 'tnix') + call assert_match('runtest\.vim', getline(1)) + %d + call writefile(['one', 'two', 'three'], 'Xinclude.txt') + let include='#include Xinclude.txt' + call setline(1, [include, '']) + call cursor(2, 1) + call feedkeys("A\<c-x>\<tab>\<cr>\<esc>", 'tnix') + call assert_equal([include, 'one', ''], getline(1, '$')) + call feedkeys("2ggC\<c-x>\<tab>\<down>\<cr>\<esc>", 'tnix') + call assert_equal([include, 'two', ''], getline(1, '$')) + call feedkeys("2ggC\<c-x>\<tab>\<down>\<down>\<cr>\<esc>", 'tnix') + call assert_equal([include, 'three', ''], getline(1, '$')) + call feedkeys("2ggC\<c-x>\<tab>\<down>\<down>\<down>\<cr>\<esc>", 'tnix') + call assert_equal([include, '', ''], getline(1, '$')) + call delete("Xinclude.txt") + bw! +endfunc + +func! Test_edit_CTRL_K() + " Test pressing CTRL-K (basically only dictionary completion and digraphs + " the rest is already covered + call writefile(['A', 'AA', 'AAA', 'AAAA'], 'Xdictionary.txt') + set dictionary=Xdictionary.txt + new + call setline(1, 'A') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-k>\<cr>\<esc>", 'tnix') + call assert_equal(['AA', ''], getline(1, '$')) + %d + call setline(1, 'A') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-k>\<down>\<cr>\<esc>", 'tnix') + call assert_equal(['AAA'], getline(1, '$')) + %d + call setline(1, 'A') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-k>\<down>\<down>\<cr>\<esc>", 'tnix') + call assert_equal(['AAAA'], getline(1, '$')) + %d + call setline(1, 'A') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-k>\<down>\<down>\<down>\<cr>\<esc>", 'tnix') + call assert_equal(['A'], getline(1, '$')) + %d + call setline(1, 'A') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-k>\<down>\<down>\<down>\<down>\<cr>\<esc>", 'tnix') + call assert_equal(['AA'], getline(1, '$')) + + " press an unexecpted key after dictionary completion + %d + call setline(1, 'A') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-k>\<c-]>\<cr>\<esc>", 'tnix') + call assert_equal(['AA', ''], getline(1, '$')) + %d + call setline(1, 'A') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-k>\<c-s>\<cr>\<esc>", 'tnix') + call assert_equal(["AA\<c-s>", ''], getline(1, '$')) + %d + call setline(1, 'A') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-k>\<c-f>\<cr>\<esc>", 'tnix') + call assert_equal(["AA\<c-f>", ''], getline(1, '$')) + + set dictionary= + %d + call setline(1, 'A') + call cursor(1, 1) + let v:testing = 1 + try + call feedkeys("A\<c-x>\<c-k>\<esc>", 'tnix') + catch + " error sleeps 2 seconds, when v:testing is not set + let v:testing = 0 + endtry + call delete('Xdictionary.txt') + + if has("multi_byte") && !has("nvim") + call test_override("char_avail", 1) + set showcmd + %d + call feedkeys("A\<c-k>a:\<esc>", 'tnix') + call assert_equal(['ä'], getline(1, '$')) + call test_override("char_avail", 0) + set noshowcmd + endif + bw! +endfunc + +func! Test_edit_CTRL_L() + " Test Ctrl-X Ctrl-L (line completion) + new + set complete=. + call setline(1, ['one', 'two', 'three', '', '', '', '']) + call cursor(4, 1) + call feedkeys("A\<c-x>\<c-l>\<esc>", 'tnix') + call assert_equal(['one', 'two', 'three', 'three', '', '', ''], getline(1, '$')) + call feedkeys("cct\<c-x>\<c-l>\<c-n>\<esc>", 'tnix') + call assert_equal(['one', 'two', 'three', 't', '', '', ''], getline(1, '$')) + call feedkeys("cct\<c-x>\<c-l>\<c-n>\<c-n>\<esc>", 'tnix') + call assert_equal(['one', 'two', 'three', 't', '', '', ''], getline(1, '$')) + call feedkeys("cct\<c-x>\<c-l>\<c-n>\<c-n>\<c-n>\<esc>", 'tnix') + call assert_equal(['one', 'two', 'three', 'two', '', '', ''], getline(1, '$')) + call feedkeys("cct\<c-x>\<c-l>\<c-n>\<c-n>\<c-n>\<c-n>\<esc>", 'tnix') + call assert_equal(['one', 'two', 'three', 'three', '', '', ''], getline(1, '$')) + call feedkeys("cct\<c-x>\<c-l>\<c-p>\<esc>", 'tnix') + call assert_equal(['one', 'two', 'three', 'two', '', '', ''], getline(1, '$')) + call feedkeys("cct\<c-x>\<c-l>\<c-p>\<c-p>\<esc>", 'tnix') + call assert_equal(['one', 'two', 'three', 't', '', '', ''], getline(1, '$')) + call feedkeys("cct\<c-x>\<c-l>\<c-p>\<c-p>\<c-p>\<esc>", 'tnix') + call assert_equal(['one', 'two', 'three', 'three', '', '', ''], getline(1, '$')) + set complete= + call cursor(5, 1) + call feedkeys("A\<c-x>\<c-l>\<c-p>\<c-n>\<esc>", 'tnix') + call assert_equal(['one', 'two', 'three', 'three', "\<c-l>\<c-p>\<c-n>", '', ''], getline(1, '$')) + set complete& + %d + if has("conceal") && has("syntax") && !has("nvim") + call setline(1, ['foo', 'bar', 'foobar']) + call test_override("char_avail", 1) + set conceallevel=2 concealcursor=n + syn on + syn match ErrorMsg "^bar" + call matchadd("Conceal", 'oo', 10, -1, {'conceal': 'X'}) + func! DoIt() + let g:change=1 + endfunc + au! TextChangedI <buffer> :call DoIt() + + call cursor(2, 1) + call assert_false(exists("g:change")) + call feedkeys("A \<esc>", 'tnix') + call assert_equal(['foo', 'bar ', 'foobar'], getline(1, '$')) + call assert_equal(1, g:change) + + call test_override("char_avail", 0) + call clearmatches() + syn off + au! TextChangedI + delfu DoIt + unlet! g:change + endif + bw! +endfunc + +func! Test_edit_CTRL_N() + " Check keyword completion + new + set complete=. + call setline(1, ['INFER', 'loWER', '', '', ]) + call cursor(3, 1) + call feedkeys("Ai\<c-n>\<cr>\<esc>", "tnix") + call feedkeys("ILO\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['INFER', 'loWER', 'i', 'LO', '', ''], getline(1, '$')) + %d + call setline(1, ['INFER', 'loWER', '', '', ]) + call cursor(3, 1) + set ignorecase infercase + call feedkeys("Ii\<c-n>\<cr>\<esc>", "tnix") + call feedkeys("ILO\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['INFER', 'loWER', 'infer', 'LOWER', '', ''], getline(1, '$')) + + set noignorecase noinfercase complete& + bw! +endfunc + +func! Test_edit_CTRL_O() + " Check for CTRL-O in insert mode + new + inoreabbr <buffer> h here some more + call setline(1, ['abc', 'def']) + call cursor(1, 1) + " Ctrl-O after an abbreviation + exe "norm A h\<c-o>:set nu\<cr> text" + call assert_equal(['abc here some more text', 'def'], getline(1, '$')) + call assert_true(&nu) + set nonu + iunabbr <buffer> h + " Ctrl-O at end of line with 've'=onemore + call cursor(1, 1) + call feedkeys("A\<c-o>:let g:a=getpos('.')\<cr>\<esc>", 'tnix') + call assert_equal([0, 1, 23, 0], g:a) + call cursor(1, 1) + set ve=onemore + call feedkeys("A\<c-o>:let g:a=getpos('.')\<cr>\<esc>", 'tnix') + call assert_equal([0, 1, 24, 0], g:a) + set ve= + unlet! g:a + bw! +endfunc + +func! Test_edit_CTRL_R() + throw 'skipped: Nvim does not support test_override()' + " Insert Register + new + 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) + set noshowcmd + bw! +endfunc + +func! Test_edit_CTRL_S() + " Test pressing CTRL-S (basically only spellfile completion) + " the rest is already covered + new + if !has("spell") + call setline(1, 'vim') + call feedkeys("A\<c-x>ss\<cr>\<esc>", 'tnix') + call assert_equal(['vims', ''], getline(1, '$')) + bw! + return + endif + call setline(1, 'vim') + " spell option not yet set + try + call feedkeys("A\<c-x>\<c-s>\<cr>\<esc>", 'tnix') + catch /^Vim\%((\a\+)\)\=:E756/ + call assert_true(1, 'error caught') + endtry + call assert_equal(['vim', ''], getline(1, '$')) + %d + setl spell spelllang=en + call setline(1, 'vim') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-s>\<cr>\<esc>", 'tnix') + call assert_equal(['Vim', ''], getline(1, '$')) + %d + call setline(1, 'vim') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-s>\<down>\<cr>\<esc>", 'tnix') + call assert_equal(['Aim'], getline(1, '$')) + %d + call setline(1, 'vim') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-s>\<c-p>\<cr>\<esc>", 'tnix') + call assert_equal(['vim', ''], getline(1, '$')) + %d + " empty buffer + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-s>\<c-p>\<cr>\<esc>", 'tnix') + call assert_equal(['', ''], getline(1, '$')) + setl nospell + bw! +endfunc + +func! Test_edit_CTRL_T() + " Check for CTRL-T and CTRL-X CTRL-T in insert mode + " 1) increase indent + new + call setline(1, "abc") + call cursor(1, 1) + call feedkeys("A\<c-t>xyz", 'tnix') + call assert_equal(["\<tab>abcxyz"], getline(1, '$')) + " 2) also when paste option is set + set paste + call setline(1, "abc") + call cursor(1, 1) + call feedkeys("A\<c-t>xyz", 'tnix') + call assert_equal(["\<tab>abcxyz"], getline(1, '$')) + set nopaste + " CTRL-X CTRL-T (thesaurus complete) + call writefile(['angry furious mad enraged'], 'Xthesaurus') + set thesaurus=Xthesaurus + call setline(1, 'mad') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-t>\<cr>\<esc>", 'tnix') + call assert_equal(['mad', ''], getline(1, '$')) + %d + call setline(1, 'mad') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-t>\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['angry', ''], getline(1, '$')) + %d + call setline(1, 'mad') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-t>\<c-n>\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['furious', ''], getline(1, '$')) + %d + call setline(1, 'mad') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-t>\<c-n>\<c-n>\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['enraged', ''], getline(1, '$')) + %d + call setline(1, 'mad') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-t>\<c-n>\<c-n>\<c-n>\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['mad', ''], getline(1, '$')) + %d + call setline(1, 'mad') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-t>\<c-n>\<c-n>\<c-n>\<c-n>\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['mad', ''], getline(1, '$')) + " Using <c-p> <c-n> when 'complete' is empty + set complete= + %d + call setline(1, 'mad') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-t>\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['angry', ''], getline(1, '$')) + %d + call setline(1, 'mad') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-t>\<c-p>\<cr>\<esc>", 'tnix') + call assert_equal(['mad', ''], getline(1, '$')) + set complete& + + set thesaurus= + %d + call setline(1, 'mad') + call cursor(1, 1) + let v:testing = 1 + try + call feedkeys("A\<c-x>\<c-t>\<esc>", 'tnix') + catch + " error sleeps 2 seconds, when v:testing is not set + let v:testing = 0 + endtry + call assert_equal(['mad'], getline(1, '$')) + call delete('Xthesaurus') + bw! +endfunc + +func! Test_edit_CTRL_U() + " Test 'completefunc' + new + " -1, -2 and -3 are special return values + let g:special=0 + fun! CompleteMonths(findstart, base) + if a:findstart + " locate the start of the word + return g:special + else + " find months matching with "a:base" + let res = [] + for m in split("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec") + if m =~ '^\c'.a:base + call add(res, {'word': m, 'abbr': m.' Month', 'icase': 0}) + endif + endfor + return {'words': res, 'refresh': 'always'} + endif + endfun + set completefunc=CompleteMonths + call setline(1, ['', '']) + call cursor(1, 1) + call feedkeys("AX\<c-x>\<c-u>\<cr>\<esc>", 'tnix') + call assert_equal(['X', '', ''], getline(1, '$')) + %d + let g:special=-1 + call feedkeys("AX\<c-x>\<c-u>\<cr>\<esc>", 'tnix') + call assert_equal(['XJan', ''], getline(1, '$')) + %d + let g:special=-2 + call feedkeys("AX\<c-x>\<c-u>\<cr>\<esc>", 'tnix') + call assert_equal(['X', ''], getline(1, '$')) + %d + let g:special=-3 + call feedkeys("AX\<c-x>\<c-u>\<cr>\<esc>", 'tnix') + call assert_equal(['X', ''], getline(1, '$')) + %d + let g:special=0 + call feedkeys("AM\<c-x>\<c-u>\<cr>\<esc>", 'tnix') + call assert_equal(['Mar', ''], getline(1, '$')) + %d + call feedkeys("AM\<c-x>\<c-u>\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['May', ''], getline(1, '$')) + %d + call feedkeys("AM\<c-x>\<c-u>\<c-n>\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['M', ''], getline(1, '$')) + delfu CompleteMonths + %d + try + call feedkeys("A\<c-x>\<c-u>", 'tnix') + call assert_fails(1, 'unknown completion function') + catch /^Vim\%((\a\+)\)\=:E117/ + call assert_true(1, 'E117 error caught') + endtry + set completefunc= + bw! +endfunc + +func! Test_edit_CTRL_Z() + " Ctrl-Z when insertmode is not set inserts it literally + new + call setline(1, 'abc') + call feedkeys("A\<c-z>\<esc>", 'tnix') + call assert_equal(["abc\<c-z>"], getline(1,'$')) + bw! + " TODO: How to Test Ctrl-Z in insert mode, e.g. suspend? +endfunc + +func! Test_edit_DROP() + if !has("dnd") + return + endif + new + call setline(1, ['abc def ghi']) + call cursor(1, 1) + try + call feedkeys("i\<Drop>\<Esc>", 'tnix') + call assert_fails(1, 'Invalid register name') + catch /^Vim\%((\a\+)\)\=:E353/ + call assert_true(1, 'error caught') + endtry + bw! +endfunc + +func! Test_edit_CTRL_V() + throw 'skipped: Nvim does not support test_override()' + if has("ebcdic") + return + endif + new + call setline(1, ['abc']) + call cursor(2, 1) + " force some redraws + set showmode showcmd + "call test_override_char_avail(1) + call test_override('ALL', 1) + call 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, '$')) + + if has("rightleft") && exists("+rl") + set rl + call setline(1, ['abc']) + call cursor(2, 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, '$')) + set norl + endif + + call test_override('ALL', 0) + set noshowmode showcmd + bw! +endfunc + +func! Test_edit_F1() + " Pressing <f1> + new + call feedkeys(":set im\<cr>\<f1>\<c-l>", 'tnix') + set noinsertmode + call assert_equal('help', &buftype) + bw + bw +endfunc + +func! Test_edit_F21() + " Pressing <f21> + " sends a netbeans command + if has("netbeans_intg") + new + " I have no idea what this is supposed to do :) + call feedkeys("A\<F21>\<F1>\<esc>", 'tnix') + bw + endif +endfunc + +func! Test_edit_HOME_END() + " Test Home/End Keys + new + set foldopen+=hor + call setline(1, ['abc', 'def']) + call cursor(1, 1) + call feedkeys("AX\<Home>Y\<esc>", 'tnix') + call cursor(2, 1) + call feedkeys("iZ\<End>Y\<esc>", 'tnix') + call assert_equal(['YabcX', 'ZdefY'], getline(1, '$')) + + set foldopen-=hor + bw! +endfunc + +func! Test_edit_INS() + " Test for Pressing <Insert> + new + call setline(1, ['abc', 'def']) + call cursor(1, 1) + call feedkeys("i\<Insert>ZYX>", 'tnix') + call assert_equal(['ZYX>', 'def'], getline(1, '$')) + call setline(1, ['abc', 'def']) + call cursor(1, 1) + call feedkeys("i\<Insert>Z\<Insert>YX>", 'tnix') + call assert_equal(['ZYX>bc', 'def'], getline(1, '$')) + bw! +endfunc + +func! Test_edit_LEFT_RIGHT() + " Left, Shift-Left, Right, Shift-Right + new + call setline(1, ['abc def ghi', 'ABC DEF GHI', 'ZZZ YYY XXX']) + let _ww=&ww + set ww= + call cursor(2, 1) + call feedkeys("i\<left>\<esc>", 'tnix') + call assert_equal([0, 2, 1, 0], getpos('.')) + " Is this a bug, <s-left> does not respect whichwrap option + call feedkeys("i\<s-left>\<esc>", 'tnix') + call assert_equal([0, 1, 8, 0], getpos('.')) + call feedkeys("i". repeat("\<s-left>", 3). "\<esc>", 'tnix') + call assert_equal([0, 1, 1, 0], getpos('.')) + call feedkeys("i\<right>\<esc>", 'tnix') + call assert_equal([0, 1, 1, 0], getpos('.')) + call feedkeys("i\<right>\<right>\<esc>", 'tnix') + call assert_equal([0, 1, 2, 0], getpos('.')) + call feedkeys("A\<right>\<esc>", 'tnix') + call assert_equal([0, 1, 11, 0], getpos('.')) + call feedkeys("A\<s-right>\<esc>", 'tnix') + call assert_equal([0, 2, 1, 0], getpos('.')) + call feedkeys("i\<s-right>\<esc>", 'tnix') + call assert_equal([0, 2, 4, 0], getpos('.')) + call cursor(3, 11) + call feedkeys("A\<right>\<esc>", 'tnix') + call feedkeys("A\<s-right>\<esc>", 'tnix') + call assert_equal([0, 3, 11, 0], getpos('.')) + call cursor(2, 11) + " <S-Right> does not respect 'whichwrap' option + call feedkeys("A\<s-right>\<esc>", 'tnix') + call assert_equal([0, 3, 1, 0], getpos('.')) + " Check motion when 'whichwrap' contains cursor keys for insert mode + set ww+=[,] + call cursor(2, 1) + call feedkeys("i\<left>\<esc>", 'tnix') + call assert_equal([0, 1, 11, 0], getpos('.')) + call cursor(2, 11) + call feedkeys("A\<right>\<esc>", 'tnix') + call assert_equal([0, 3, 1, 0], getpos('.')) + call cursor(2, 11) + call feedkeys("A\<s-right>\<esc>", 'tnix') + call assert_equal([0, 3, 1, 0], getpos('.')) + let &ww = _ww + bw! +endfunc + +func! Test_edit_MOUSE() + " This is a simple test, since we not really using the mouse here + if !has("mouse") + return + endif + 10new + call setline(1, range(1, 100)) + call cursor(1, 1) + set mouse=a + call feedkeys("A\<ScrollWheelDown>\<esc>", 'tnix') + call assert_equal([0, 4, 1, 0], getpos('.')) + " This should move by one pageDown, but only moves + " by one line when the test is run... + call feedkeys("A\<S-ScrollWheelDown>\<esc>", 'tnix') + call assert_equal([0, 5, 1, 0], getpos('.')) + set nostartofline + call feedkeys("A\<C-ScrollWheelDown>\<esc>", 'tnix') + call assert_equal([0, 6, 1, 0], getpos('.')) + call feedkeys("A\<LeftMouse>\<esc>", 'tnix') + call assert_equal([0, 6, 1, 0], getpos('.')) + call feedkeys("A\<RightMouse>\<esc>", 'tnix') + call assert_equal([0, 6, 1, 0], getpos('.')) + call cursor(1, 100) + norm! zt + " this should move by a screen up, but when the test + " is run, it moves up to the top of the buffer... + call feedkeys("A\<ScrollWheelUp>\<esc>", 'tnix') + call assert_equal([0, 1, 1, 0], getpos('.')) + call cursor(1, 30) + norm! zt + call feedkeys("A\<S-ScrollWheelUp>\<esc>", 'tnix') + call assert_equal([0, 1, 1, 0], getpos('.')) + call cursor(1, 30) + norm! zt + call feedkeys("A\<C-ScrollWheelUp>\<esc>", 'tnix') + call assert_equal([0, 1, 1, 0], getpos('.')) + %d + call setline(1, repeat(["12345678901234567890"], 100)) + call cursor(2, 1) + call feedkeys("A\<ScrollWheelRight>\<esc>", 'tnix') + call assert_equal([0, 2, 20, 0], getpos('.')) + call feedkeys("A\<ScrollWheelLeft>\<esc>", 'tnix') + call assert_equal([0, 2, 20, 0], getpos('.')) + call feedkeys("A\<S-ScrollWheelRight>\<esc>", 'tnix') + call assert_equal([0, 2, 20, 0], getpos('.')) + call feedkeys("A\<S-ScrollWheelLeft>\<esc>", 'tnix') + call assert_equal([0, 2, 20, 0], getpos('.')) + call feedkeys("A\<C-ScrollWheelRight>\<esc>", 'tnix') + call assert_equal([0, 2, 20, 0], getpos('.')) + call feedkeys("A\<C-ScrollWheelLeft>\<esc>", 'tnix') + call assert_equal([0, 2, 20, 0], getpos('.')) + set mouse& startofline + bw! +endfunc + +func! Test_edit_PAGEUP_PAGEDOWN() + 10new + call setline(1, repeat(['abc def ghi'], 30)) + call cursor(1, 1) + call feedkeys("i\<PageDown>\<esc>", 'tnix') + call assert_equal([0, 9, 1, 0], getpos('.')) + call feedkeys("i\<PageDown>\<esc>", 'tnix') + call assert_equal([0, 17, 1, 0], getpos('.')) + call feedkeys("i\<PageDown>\<esc>", 'tnix') + call assert_equal([0, 25, 1, 0], getpos('.')) + call feedkeys("i\<PageDown>\<esc>", 'tnix') + call assert_equal([0, 30, 1, 0], getpos('.')) + call feedkeys("i\<PageDown>\<esc>", 'tnix') + call assert_equal([0, 30, 1, 0], getpos('.')) + call feedkeys("A\<PageUp>\<esc>", 'tnix') + call assert_equal([0, 29, 1, 0], getpos('.')) + call feedkeys("A\<PageUp>\<esc>", 'tnix') + call assert_equal([0, 21, 1, 0], getpos('.')) + call feedkeys("A\<PageUp>\<esc>", 'tnix') + call assert_equal([0, 13, 1, 0], getpos('.')) + call feedkeys("A\<PageUp>\<esc>", 'tnix') + call assert_equal([0, 5, 1, 0], getpos('.')) + call feedkeys("A\<PageUp>\<esc>", 'tnix') + call assert_equal([0, 5, 11, 0], getpos('.')) + " <S-Up> is the same as <PageUp> + " <S-Down> is the same as <PageDown> + call cursor(1, 1) + call feedkeys("i\<S-Down>\<esc>", 'tnix') + call assert_equal([0, 9, 1, 0], getpos('.')) + call feedkeys("i\<S-Down>\<esc>", 'tnix') + call assert_equal([0, 17, 1, 0], getpos('.')) + call feedkeys("i\<S-Down>\<esc>", 'tnix') + call assert_equal([0, 25, 1, 0], getpos('.')) + call feedkeys("i\<S-Down>\<esc>", 'tnix') + call assert_equal([0, 30, 1, 0], getpos('.')) + call feedkeys("i\<S-Down>\<esc>", 'tnix') + call assert_equal([0, 30, 1, 0], getpos('.')) + call feedkeys("A\<S-Up>\<esc>", 'tnix') + call assert_equal([0, 29, 1, 0], getpos('.')) + call feedkeys("A\<S-Up>\<esc>", 'tnix') + call assert_equal([0, 21, 1, 0], getpos('.')) + call feedkeys("A\<S-Up>\<esc>", 'tnix') + call assert_equal([0, 13, 1, 0], getpos('.')) + call feedkeys("A\<S-Up>\<esc>", 'tnix') + call assert_equal([0, 5, 1, 0], getpos('.')) + call feedkeys("A\<S-Up>\<esc>", 'tnix') + call assert_equal([0, 5, 11, 0], getpos('.')) + set nostartofline + call cursor(30, 11) + norm! zt + call feedkeys("A\<PageUp>\<esc>", 'tnix') + call assert_equal([0, 29, 11, 0], getpos('.')) + call feedkeys("A\<PageUp>\<esc>", 'tnix') + call assert_equal([0, 21, 11, 0], getpos('.')) + call feedkeys("A\<PageUp>\<esc>", 'tnix') + call assert_equal([0, 13, 11, 0], getpos('.')) + call feedkeys("A\<PageUp>\<esc>", 'tnix') + call assert_equal([0, 5, 11, 0], getpos('.')) + call feedkeys("A\<PageUp>\<esc>", 'tnix') + call assert_equal([0, 5, 11, 0], getpos('.')) + call cursor(1, 1) + call feedkeys("A\<PageDown>\<esc>", 'tnix') + call assert_equal([0, 9, 11, 0], getpos('.')) + call feedkeys("A\<PageDown>\<esc>", 'tnix') + call assert_equal([0, 17, 11, 0], getpos('.')) + call feedkeys("A\<PageDown>\<esc>", 'tnix') + call assert_equal([0, 25, 11, 0], getpos('.')) + call feedkeys("A\<PageDown>\<esc>", 'tnix') + call assert_equal([0, 30, 11, 0], getpos('.')) + call feedkeys("A\<PageDown>\<esc>", 'tnix') + call assert_equal([0, 30, 11, 0], getpos('.')) + " <S-Up> is the same as <PageUp> + " <S-Down> is the same as <PageDown> + call cursor(30, 11) + norm! zt + call feedkeys("A\<S-Up>\<esc>", 'tnix') + call assert_equal([0, 29, 11, 0], getpos('.')) + call feedkeys("A\<S-Up>\<esc>", 'tnix') + call assert_equal([0, 21, 11, 0], getpos('.')) + call feedkeys("A\<S-Up>\<esc>", 'tnix') + call assert_equal([0, 13, 11, 0], getpos('.')) + call feedkeys("A\<S-Up>\<esc>", 'tnix') + call assert_equal([0, 5, 11, 0], getpos('.')) + call feedkeys("A\<S-Up>\<esc>", 'tnix') + call assert_equal([0, 5, 11, 0], getpos('.')) + call cursor(1, 1) + call feedkeys("A\<S-Down>\<esc>", 'tnix') + call assert_equal([0, 9, 11, 0], getpos('.')) + call feedkeys("A\<S-Down>\<esc>", 'tnix') + call assert_equal([0, 17, 11, 0], getpos('.')) + call feedkeys("A\<S-Down>\<esc>", 'tnix') + call assert_equal([0, 25, 11, 0], getpos('.')) + call feedkeys("A\<S-Down>\<esc>", 'tnix') + call assert_equal([0, 30, 11, 0], getpos('.')) + call feedkeys("A\<S-Down>\<esc>", 'tnix') + call assert_equal([0, 30, 11, 0], getpos('.')) + bw! +endfunc + +func! Test_edit_forbidden() + new + " 1) edit in the sandbox is not allowed + call setline(1, 'a') + com! Sandbox :sandbox call feedkeys("i\<del>\<esc>", 'tnix') + call assert_fails(':Sandbox', 'E48:') + com! Sandbox :sandbox exe "norm! i\<del>" + call assert_fails(':Sandbox', 'E48:') + delcom Sandbox + call assert_equal(['a'], getline(1,'$')) + " 2) edit with textlock set + fu! DoIt() + call feedkeys("i\<del>\<esc>", 'tnix') + endfu + au InsertCharPre <buffer> :call DoIt() + try + call feedkeys("ix\<esc>", 'tnix') + call assert_fails(1, 'textlock') + catch /^Vim\%((\a\+)\)\=:E523/ " catch E523: not allowed here + endtry + " TODO: Might be a bug: should x really be inserted here + call assert_equal(['xa'], getline(1, '$')) + delfu DoIt + try + call feedkeys("ix\<esc>", 'tnix') + call assert_fails(1, 'unknown function') + catch /^Vim\%((\a\+)\)\=:E117/ " catch E117: unknown function + endtry + au! InsertCharPre + " 3) edit when completion is shown + fun! Complete(findstart, base) + if a:findstart + return col('.') + else + call feedkeys("i\<del>\<esc>", 'tnix') + return [] + endif + endfun + set completefunc=Complete + try + call feedkeys("i\<c-x>\<c-u>\<esc>", 'tnix') + call assert_fails(1, 'change in complete function') + catch /^Vim\%((\a\+)\)\=:E523/ " catch E523 + endtry + delfu Complete + set completefunc= + if has("rightleft") && exists("+fkmap") + " 4) 'R' when 'fkmap' and 'revins' is set. + set revins fkmap + try + normal Ri + call assert_fails(1, "R with 'fkmap' and 'ri' set") + catch + finally + set norevins nofkmap + endtry + endif + bw! +endfunc + +func! Test_edit_rightleft() + " Cursor in rightleft mode moves differently + if !exists("+rightleft") + return + endif + call NewWindow(10, 20) + call setline(1, ['abc', 'def', 'ghi']) + call cursor(1, 2) + set rightleft + " Screen looks as expected + let lines = ScreenLines([1, 4], winwidth(0)) + let expect = [ + \" cba", + \" fed", + \" ihg", + \" ~"] + call assert_equal(join(expect, "\n"), join(lines, "\n")) + " 2) right moves to the left + call feedkeys("i\<right>\<esc>x", 'txin') + call assert_equal(['bc', 'def', 'ghi'], getline(1,'$')) + call cursor(1, 2) + call feedkeys("i\<s-right>\<esc>", 'txin') + call cursor(1, 2) + call feedkeys("i\<c-right>\<esc>", 'txin') + " Screen looks as expected + let lines = ScreenLines([1, 4], winwidth(0)) + let expect = [ + \" cb", + \" fed", + \" ihg", + \" ~"] + call assert_equal(join(expect, "\n"), join(lines, "\n")) + " 2) left moves to the right + call setline(1, ['abc', 'def', 'ghi']) + call cursor(1, 2) + call feedkeys("i\<left>\<esc>x", 'txin') + call assert_equal(['ac', 'def', 'ghi'], getline(1,'$')) + call cursor(1, 2) + call feedkeys("i\<s-left>\<esc>", 'txin') + call cursor(1, 2) + call feedkeys("i\<c-left>\<esc>", 'txin') + " Screen looks as expected + let lines = ScreenLines([1, 4], winwidth(0)) + let expect = [ + \" ca", + \" fed", + \" ihg", + \" ~"] + call assert_equal(join(expect, "\n"), join(lines, "\n")) + set norightleft + bw! +endfunc + +func Test_edit_quit() + edit foo.txt + split + new + call setline(1, 'hello') + 3wincmd w + redraw! + call assert_fails('1q', 'E37:') + bwipe! foo.txt + only +endfunc + diff --git a/src/nvim/testdir/test_erasebackword.vim b/src/nvim/testdir/test_erasebackword.vim new file mode 100644 index 0000000000..098d6edfcb --- /dev/null +++ b/src/nvim/testdir/test_erasebackword.vim @@ -0,0 +1,25 @@ + +func Test_erasebackword() + if !has('multi_byte') + return + endif + + set encoding=utf-8 + enew + + exe "normal o wwwã“ã‚“ã«ã¡ã‚世界ワールドvim \<C-W>" + call assert_equal(' wwwã“ã‚“ã«ã¡ã‚世界ワールド', getline('.')) + exe "normal o wwwã“ã‚“ã«ã¡ã‚世界ワールドvim \<C-W>\<C-W>" + call assert_equal(' wwwã“ã‚“ã«ã¡ã‚世界', getline('.')) + exe "normal o wwwã“ã‚“ã«ã¡ã‚世界ワールドvim \<C-W>\<C-W>\<C-W>" + call assert_equal(' wwwã“ã‚“ã«ã¡ã‚', getline('.')) + exe "normal o wwwã“ã‚“ã«ã¡ã‚世界ワールドvim \<C-W>\<C-W>\<C-W>\<C-W>" + call assert_equal(' www', getline('.')) + exe "normal o wwwã“ã‚“ã«ã¡ã‚世界ワールドvim \<C-W>\<C-W>\<C-W>\<C-W>\<C-W>" + call assert_equal(' ', getline('.')) + exe "normal o wwwã“ã‚“ã«ã¡ã‚世界ワールドvim \<C-W>\<C-W>\<C-W>\<C-W>\<C-W>\<C-W>" + call assert_equal('', getline('.')) + + enew! + set encoding& +endfunc diff --git a/src/nvim/testdir/test_exists.vim b/src/nvim/testdir/test_exists.vim new file mode 100644 index 0000000000..fd34be83b0 --- /dev/null +++ b/src/nvim/testdir/test_exists.vim @@ -0,0 +1,321 @@ +" Tests for the exists() function +func Test_exists() + augroup myagroup + autocmd! BufEnter *.my echo "myfile edited" + autocmd! FuncUndefined UndefFun exec "fu UndefFun()\nendfu" + augroup END + set rtp+=./sautest + + " valid autocmd group + call assert_equal(1, exists('#myagroup')) + " valid autocmd group with garbage + call assert_equal(0, exists('#myagroup+b')) + " Valid autocmd group and event + call assert_equal(1, exists('#myagroup#BufEnter')) + " Valid autocmd group, event and pattern + call assert_equal(1, exists('#myagroup#BufEnter#*.my')) + " Valid autocmd event + call assert_equal(1, exists('#BufEnter')) + " Valid autocmd event and pattern + call assert_equal(1, exists('#BufEnter#*.my')) + " Non-existing autocmd group or event + call assert_equal(0, exists('#xyzagroup')) + " Non-existing autocmd group and valid autocmd event + call assert_equal(0, exists('#xyzagroup#BufEnter')) + " Valid autocmd group and event with no matching pattern + call assert_equal(0, exists('#myagroup#CmdwinEnter')) + " Valid autocmd group and non-existing autocmd event + call assert_equal(0, exists('#myagroup#xyzacmd')) + " Valid autocmd group and event and non-matching pattern + call assert_equal(0, exists('#myagroup#BufEnter#xyzpat')) + " Valid autocmd event and non-matching pattern + call assert_equal(0, exists('#BufEnter#xyzpat')) + " Empty autocmd group, event and pattern + call assert_equal(0, exists('###')) + " Empty autocmd group and event or empty event and pattern + call assert_equal(0, exists('##')) + " Valid autocmd event + call assert_equal(1, exists('##FileReadCmd')) + " Non-existing autocmd event + call assert_equal(0, exists('##MySpecialCmd')) + + " Existing and working option (long form) + call assert_equal(1, exists('&textwidth')) + " Existing and working option (short form) + call assert_equal(1, exists('&tw')) + " Existing and working option with garbage + call assert_equal(0, exists('&tw-')) + " Global option + call assert_equal(1, exists('&g:errorformat')) + " Local option + call assert_equal(1, exists('&l:errorformat')) + " Negative form of existing and working option (long form) + call assert_equal(0, exists('&nojoinspaces')) + " Negative form of existing and working option (short form) + call assert_equal(0, exists('&nojs')) + " Non-existing option + call assert_equal(0, exists('&myxyzoption')) + + " Existing and working option (long form) + call assert_equal(1, exists('+incsearch')) + " Existing and working option with garbage + call assert_equal(0, exists('+incsearch!1')) + " Existing and working option (short form) + call assert_equal(1, exists('+is')) + " Existing option that is hidden. + call assert_equal(0, exists('+autoprint')) + + " Existing environment variable + let $EDITOR_NAME = 'Vim Editor' + call assert_equal(1, exists('$EDITOR_NAME')) + " Non-existing environment variable + call assert_equal(0, exists('$NON_ENV_VAR')) + + " Valid internal function + call assert_equal(1, exists('*bufnr')) + " Valid internal function with () + call assert_equal(1, exists('*bufnr()')) + " Non-existing internal function + call assert_equal(0, exists('*myxyzfunc')) + " Valid internal function with garbage + call assert_equal(0, exists('*bufnr&6')) + " Valid user defined function + call assert_equal(1, exists('*Test_exists')) + " Non-existing user defined function + call assert_equal(0, exists('*MyxyzFunc')) + " Function that may be created by FuncUndefined event + call assert_equal(0, exists('*UndefFun')) + " Function that may be created by script autoloading + call assert_equal(0, exists('*footest#F')) + + " Valid internal command (full match) + call assert_equal(2, exists(':edit')) + " Valid internal command (full match) with garbage + call assert_equal(0, exists(':edit/a')) + " Valid internal command (partial match) + call assert_equal(1, exists(':q')) + " Non-existing internal command + call assert_equal(0, exists(':invalidcmd')) + + " User defined command (full match) + command! MyCmd :echo 'My command' + call assert_equal(2, exists(':MyCmd')) + " User defined command (partial match) + command! MyOtherCmd :echo 'Another command' + call assert_equal(3, exists(':My')) + + " Command modifier + call assert_equal(2, exists(':rightbelow')) + + " Non-existing user defined command (full match) + delcommand MyCmd + call assert_equal(0, exists(':MyCmd')) + + " Non-existing user defined command (partial match) + delcommand MyOtherCmd + call assert_equal(0, exists(':My')) + + " Valid local variable + let local_var = 1 + call assert_equal(1, exists('local_var')) + " Valid local variable with garbage + call assert_equal(0, exists('local_var%n')) + " Non-existing local variable + unlet local_var + call assert_equal(0, exists('local_var')) + + " Non-existing autoload variable that may be autoloaded + call assert_equal(0, exists('footest#x')) + + " Valid local list + let local_list = ["blue", "orange"] + call assert_equal(1, exists('local_list')) + " Valid local list item + call assert_equal(1, exists('local_list[1]')) + " Valid local list item with garbage + call assert_equal(0, exists('local_list[1]+5')) + " Invalid local list item + call assert_equal(0, exists('local_list[2]')) + " Non-existing local list + unlet local_list + call assert_equal(0, exists('local_list')) + " Valid local dictionary + let local_dict = {"xcord":100, "ycord":2} + call assert_equal(1, exists('local_dict')) + " Non-existing local dictionary + unlet local_dict + call assert_equal(0, exists('local_dict')) + " Existing local curly-brace variable + let str = "local" + let curly_{str}_var = 1 + call assert_equal(1, exists('curly_{str}_var')) + " Non-existing local curly-brace variable + unlet curly_{str}_var + call assert_equal(0, exists('curly_{str}_var')) + + " Existing global variable + let g:global_var = 1 + call assert_equal(1, exists('g:global_var')) + " Existing global variable with garbage + call assert_equal(0, exists('g:global_var-n')) + " Non-existing global variable + unlet g:global_var + call assert_equal(0, exists('g:global_var')) + " Existing global list + let g:global_list = ["blue", "orange"] + call assert_equal(1, exists('g:global_list')) + " Non-existing global list + unlet g:global_list + call assert_equal(0, exists('g:global_list')) + " Existing global dictionary + let g:global_dict = {"xcord":100, "ycord":2} + call assert_equal(1, exists('g:global_dict')) + " Non-existing global dictionary + unlet g:global_dict + call assert_equal(0, exists('g:global_dict')) + " Existing global curly-brace variable + let str = "global" + let g:curly_{str}_var = 1 + call assert_equal(1, exists('g:curly_{str}_var')) + " Non-existing global curly-brace variable + unlet g:curly_{str}_var + call assert_equal(0, exists('g:curly_{str}_var')) + + " Existing window variable + let w:window_var = 1 + call assert_equal(1, exists('w:window_var')) + " Non-existing window variable + unlet w:window_var + call assert_equal(0, exists('w:window_var')) + " Existing window list + let w:window_list = ["blue", "orange"] + call assert_equal(1, exists('w:window_list')) + " Non-existing window list + unlet w:window_list + call assert_equal(0, exists('w:window_list')) + " Existing window dictionary + let w:window_dict = {"xcord":100, "ycord":2} + call assert_equal(1, exists('w:window_dict')) + " Non-existing window dictionary + unlet w:window_dict + call assert_equal(0, exists('w:window_dict')) + " Existing window curly-brace variable + let str = "window" + let w:curly_{str}_var = 1 + call assert_equal(1, exists('w:curly_{str}_var')) + " Non-existing window curly-brace variable + unlet w:curly_{str}_var + call assert_equal(0, exists('w:curly_{str}_var')) + + " Existing tab variable + let t:tab_var = 1 + call assert_equal(1, exists('t:tab_var')) + " Non-existing tab variable + unlet t:tab_var + call assert_equal(0, exists('t:tab_var')) + " Existing tab list + let t:tab_list = ["blue", "orange"] + call assert_equal(1, exists('t:tab_list')) + " Non-existing tab list + unlet t:tab_list + call assert_equal(0, exists('t:tab_list')) + " Existing tab dictionary + let t:tab_dict = {"xcord":100, "ycord":2} + call assert_equal(1, exists('t:tab_dict')) + " Non-existing tab dictionary + unlet t:tab_dict + call assert_equal(0, exists('t:tab_dict')) + " Existing tab curly-brace variable + let str = "tab" + let t:curly_{str}_var = 1 + call assert_equal(1, exists('t:curly_{str}_var')) + " Non-existing tab curly-brace variable + unlet t:curly_{str}_var + call assert_equal(0, exists('t:curly_{str}_var')) + + " Existing buffer variable + let b:buffer_var = 1 + call assert_equal(1, exists('b:buffer_var')) + " Non-existing buffer variable + unlet b:buffer_var + call assert_equal(0, exists('b:buffer_var')) + " Existing buffer list + let b:buffer_list = ["blue", "orange"] + call assert_equal(1, exists('b:buffer_list')) + " Non-existing buffer list + unlet b:buffer_list + call assert_equal(0, exists('b:buffer_list')) + " Existing buffer dictionary + let b:buffer_dict = {"xcord":100, "ycord":2} + call assert_equal(1, exists('b:buffer_dict')) + " Non-existing buffer dictionary + unlet b:buffer_dict + call assert_equal(0, exists('b:buffer_dict')) + " Existing buffer curly-brace variable + let str = "buffer" + let b:curly_{str}_var = 1 + call assert_equal(1, exists('b:curly_{str}_var')) + " Non-existing buffer curly-brace variable + unlet b:curly_{str}_var + call assert_equal(0, exists('b:curly_{str}_var')) + + " Existing Vim internal variable + call assert_equal(1, exists('v:version')) + " Non-existing Vim internal variable + call assert_equal(0, exists('v:non_exists_var')) + + " Existing script-local variable + let s:script_var = 1 + call assert_equal(1, exists('s:script_var')) + " Non-existing script-local variable + unlet s:script_var + call assert_equal(0, exists('s:script_var')) + " Existing script-local list + let s:script_list = ["blue", "orange"] + call assert_equal(1, exists('s:script_list')) + " Non-existing script-local list + unlet s:script_list + call assert_equal(0, exists('s:script_list')) + " Existing script-local dictionary + let s:script_dict = {"xcord":100, "ycord":2} + call assert_equal(1, exists('s:script_dict')) + " Non-existing script-local dictionary + unlet s:script_dict + call assert_equal(0, exists('s:script_dict')) + " Existing script curly-brace variable + let str = "script" + let s:curly_{str}_var = 1 + call assert_equal(1, exists('s:curly_{str}_var')) + " Non-existing script-local curly-brace variable + unlet s:curly_{str}_var + call assert_equal(0, exists('s:curly_{str}_var')) + + " Existing script-local function + function! s:my_script_func() + endfunction + + echo '*s:my_script_func: 1' + call assert_equal(1, exists('*s:my_script_func')) + + " Non-existing script-local function + delfunction s:my_script_func + + call assert_equal(0, exists('*s:my_script_func')) + unlet str + + call assert_equal(1, g:footest#x) + call assert_equal(0, footest#F()) + call assert_equal(0, UndefFun()) +endfunc + +" exists() test for Function arguments +func FuncArg_Tests(func_arg, ...) + call assert_equal(1, exists('a:func_arg')) + call assert_equal(0, exists('a:non_exists_arg')) + call assert_equal(1, exists('a:1')) + call assert_equal(0, exists('a:2')) +endfunc + +func Test_exists_funcarg() + call FuncArg_Tests("arg1", "arg2") +endfunc diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim index d32facaa98..ad967c528c 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -439,3 +439,8 @@ func Test_funcref() call assert_equal(2, OneByRef()) call assert_fails('echo funcref("{")', 'E475:') endfunc + +func Test_empty_concatenate() + call assert_equal('b', 'a'[4:0] . 'b') + call assert_equal('b', 'b' . 'a'[4:0]) +endfunc diff --git a/src/nvim/testdir/test_expr_utf8.vim b/src/nvim/testdir/test_expr_utf8.vim index 9ea6d8872b..1737a9f745 100644 --- a/src/nvim/testdir/test_expr_utf8.vim +++ b/src/nvim/testdir/test_expr_utf8.vim @@ -3,7 +3,7 @@ if !has('multi_byte') finish endif -func Test_strgetchar_utf8() +func Test_strgetchar() call assert_equal(char2nr('á'), strgetchar('áxb', 0)) call assert_equal(char2nr('x'), strgetchar('áxb', 1)) @@ -16,7 +16,7 @@ func Test_strgetchar_utf8() call assert_equal(char2nr('ã„'), strgetchar('ã‚aã„', 2)) endfunc -func Test_strcharpart_utf8() +func Test_strcharpart() call assert_equal('áxb', strcharpart('áxb', 0)) call assert_equal('á', strcharpart('áxb', 0, 1)) call assert_equal('x', strcharpart('áxb', 1, 1)) diff --git a/src/nvim/testdir/test_farsi.vim b/src/nvim/testdir/test_farsi.vim index e094191599..9ff2653af4 100644 --- a/src/nvim/testdir/test_farsi.vim +++ b/src/nvim/testdir/test_farsi.vim @@ -1,4 +1,5 @@ " Simplistic testing of Farsi mode. +" Note: must be edited with latin1 encoding. if !has('farsi') || has('nvim') " Not supported in Nvim. #6192 finish @@ -82,3 +83,51 @@ func Test_farsi_map() set noaltkeymap bwipe! endfunc + +func Test_input_farsi() + new + setlocal rightleft fkmap + " numbers switch input direction + call feedkeys("aabc0123456789.+-^%#=xyz\<Esc>", 'tx') + call assert_equal("\x8cÌν®¥ª«¦¹¸·¶µ´³²±°Ô\x93Õ", getline('.')) + + " all non-number special chars with spaces + call feedkeys("oB E F H I K L M O P Q R T U W Y ` ! @ # $ % ^ & * () - _ = + \\ | : \" . / < > ? \<Esc>", 'tx') + call assert_equal("¡ ô ú À ö æ ç  [ ] ÷ ó ò ð õ ñ ¢ £ § ® ¤ ¥ ª ¬ è ¨© é ½ « ë ê º » ¦ ¯ ¾ ¼ ¿ ", getline('.')) + + " all non-number special chars without spaces + call feedkeys("oBEFHIKLMOPQRTUWY`!@#$%^&*()-_=+\\|:\"./<>?\<Esc>",'tx') + call assert_equal("¡ôúÀöæçÂ[]÷óòðõñ¢£§®¤¥ª¬è¨©é½«ë꺻¦¯¾¼¿", getline('.')) + + " all letter chars with spaces + call feedkeys("oa A b c C d D e f g G h i j J k l m n N o p q r s S t u v V w x X y z Z ; \ , [ ] \<Esc>", 'tx') + call assert_equal("Ñ ù Ì Î Ï á þ Æ Ã Ü ø Á à Å ü Þ Ý Ä Ë Ë Ê É Ó Ù Ð û Ø Ö Í Í Ò Ô Ô × Õ ý Ú ß Ç È ", getline('.')) + + " all letter chars without spaces + call feedkeys("oaAbcCdDefgGhijJklmnNopqrsStuvVwxXyzZ;\,[]\<Esc>", 'tx') + call assert_equal("\x8cùÌÎÏ\x9fî\x86\x83ÜøÁ\x9d\x85\x80\x9c\x9b\x84ËË\x8a\x89\x8e\x96\x8bì\x95\x90ÍÍ\x8dÔÔ\x93Õý\x97ß\x87\x88", getline('.')) + + bwipe! +endfunc + +func Test_command_line_farsi() + set allowrevins altkeymap + + " letter characters with spaces + call feedkeys(":\"\<C-_>a A b c C d D e f g G h i j J k l m n N o p q r s S t u v V w x X y z Z ; \\ , [ ]\<CR>", 'tx') + call assert_equal("\"\x88 Ç ß ë Ú Õ Õ × Ô Ô Ò Í Í Ö Ø û Ð Ù Ó É Ê Ë Ë Ä Ý Þ ü Å à Á ø Ü Ã Æ þ á Ï Î Ì ù Ñ", getreg(':')) + + " letter characters without spaces + call feedkeys(":\"\<C-_>aAbcCdDefgGhijJklmnNopqrsStuvVwxXyzZ;\\,[]\<CR>", 'tx') + call assert_equal("\"\x88\x87ßëÚÕÕ\x93ÔÔ\x8dÍÍ\x90\x95ì\x8b\x96\x8e\x89\x8aËË\x84\x9b\x9c\x80\x85\x9dÁøÜ\x83\x86î\x9fÏÎÌù\x8c", getreg(':')) + + " other characters with spaces + call feedkeys(":\"\<C-_>0 1 2 3 4 5 6 7 8 9 ` . ! \" $ % ^ & / () = \\ ? + - _ * : # ~ @ < > { } | B E F H I K L M O P Q R T U W Y\<CR>", 'tx') + call assert_equal("\"ñ õ ð ò ó ÷ ] [ Â ç æ ö À ú ô ¡ ê } { ¼ ¾ § ~ ® º è é « ¿ ë ½ ©¨ ¯ ¬ ª ¥ ¤ » £ ¦ ¢ ¹ ¸ · ¶ µ ´ ³ ² ± °", getreg(':')) + + " other characters without spaces + call feedkeys(":\"\<C-_>0123456789`.!\"$%^&/()=\\?+-_*:#~@<>{}|BEFHIKLMOPQRTUWY\<CR>", 'tx') + call assert_equal("\"ñõðòó÷][ÂçæöÀúô¡ê}{¼¾§~®ºè髿뽩¨¯¬ª¥¤»£¦¢¹¸·¶µ´³²±°", getreg(':')) + + set noallowrevins noaltkeymap +endfunc diff --git a/src/nvim/testdir/test_file_size.vim b/src/nvim/testdir/test_file_size.vim new file mode 100644 index 0000000000..3e78a7b23c --- /dev/null +++ b/src/nvim/testdir/test_file_size.vim @@ -0,0 +1,58 @@ +" Inserts 2 million lines with consecutive integers starting from 1 +" (essentially, the output of GNU's seq 1 2000000), writes them to Xtest +" and writes its cksum to test.out. +" +" We need 2 million lines to trigger a call to mf_hash_grow(). If it would mess +" up the lines the checksum would differ. +" +" cksum is part of POSIX and so should be available on most Unixes. +" If it isn't available then the test will be skipped. +func Test_File_Size() + if !executable('cksum') + return + endif + + new + set fileformat=unix undolevels=-1 + for i in range(1, 2000000, 100) + call append(i, range(i, i + 99)) + endfor + + 1delete + w! Xtest + let res = systemlist('cksum Xtest')[0] + let res = substitute(res, "\r", "", "") + call assert_equal('3678979763 14888896 Xtest', res) + + enew! + call delete('Xtest') + set fileformat& undolevels& +endfunc + +" Test for writing and reading a file of over 100 Kbyte +func Test_File_Read_Write() + enew! + + " Create a file with the following contents + " 1 line: "This is the start" + " 3001 lines: "This is the leader" + " 1 line: "This is the middle" + " 3001 lines: "This is the trailer" + " 1 line: "This is the end" + call append(0, "This is the start") + call append(1, repeat(["This is the leader"], 3001)) + call append(3002, "This is the middle") + call append(3003, repeat(["This is the trailer"], 3001)) + call append(6004, "This is the end") + + write! Xtest + enew! + edit! Xtest + + call assert_equal("This is the start", getline(1)) + call assert_equal("This is the middle", getline(3003)) + call assert_equal("This is the end", getline(6005)) + + enew! + call delete("Xtest") +endfunc diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim new file mode 100644 index 0000000000..43dc383f3d --- /dev/null +++ b/src/nvim/testdir/test_filetype.vim @@ -0,0 +1,557 @@ +" Test :setfiletype + +func Test_detection() + filetype on + augroup filetypedetect + au BufNewFile,BufRead * call assert_equal(1, did_filetype()) + augroup END + new something.vim + call assert_equal('vim', &filetype) + + bwipe! + filetype off +endfunc + +func Test_conf_type() + filetype on + call writefile(['# some comment', 'must be conf'], 'Xfile') + augroup filetypedetect + au BufNewFile,BufRead * call assert_equal(0, did_filetype()) + augroup END + split Xfile + call assert_equal('conf', &filetype) + + bwipe! + call delete('Xfile') + filetype off +endfunc + +func Test_other_type() + filetype on + augroup filetypedetect + au BufNewFile,BufRead * call assert_equal(0, did_filetype()) + au BufNewFile,BufRead Xfile setf testfile + au BufNewFile,BufRead * call assert_equal(1, did_filetype()) + augroup END + call writefile(['# some comment', 'must be conf'], 'Xfile') + split Xfile + call assert_equal('testfile', &filetype) + + bwipe! + call delete('Xfile') + filetype off +endfunc + +" Filetypes detected just from matching the file name. +let s:filename_checks = { + \ 'a2ps': ['/etc/a2ps.cfg', '/etc/a2ps/file.cfg', 'a2psrc', '.a2psrc'], + \ 'a65': ['file.a65'], + \ 'abap': ['file.abap'], + \ 'abc': ['file.abc'], + \ 'abel': ['file.abl'], + \ 'acedb': ['file.wrm'], + \ 'ada': ['file.adb', 'file.ads', 'file.ada', 'file.gpr'], + \ 'ahdl': ['file.tdf'], + \ 'alsaconf': ['.asoundrc', '/usr/share/alsa/alsa.conf', '/etc/asound.conf'], + \ 'aml': ['file.aml'], + \ 'ampl': ['file.run'], + \ 'ant': ['build.xml'], + \ 'apache': ['.htaccess', '/etc/httpd/file.conf'], + \ 'applescript': ['file.scpt'], + \ 'aptconf': ['apt.conf', '/.aptitude/config'], + \ 'arch': ['.arch-inventory'], + \ 'arduino': ['file.ino', 'file.pde'], + \ 'art': ['file.art'], + \ 'asciidoc': ['file.asciidoc', 'file.adoc'], + \ 'asn': ['file.asn', 'file.asn1'], + \ 'atlas': ['file.atl', 'file.as'], + \ 'autohotkey': ['file.ahk'], + \ 'autoit': ['file.au3'], + \ 'automake': ['GNUmakefile.am'], + \ 'ave': ['file.ave'], + \ 'awk': ['file.awk'], + \ 'b': ['file.mch', 'file.ref', 'file.imp'], + \ 'bc': ['file.bc'], + \ 'bdf': ['file.bdf'], + \ 'bib': ['file.bib'], + \ 'bindzone': ['named.root'], + \ 'blank': ['file.bl'], + \ 'bst': ['file.bst'], + \ 'bzr': ['bzr_log.any'], + \ 'c': ['enlightenment/file.cfg', 'file.qc', 'file.c'], + \ 'cabal': ['file.cabal'], + \ 'calendar': ['calendar'], + \ 'catalog': ['catalog'], + \ 'cdl': ['file.cdl'], + \ 'cdrdaoconf': ['/etc/cdrdao.conf', '/etc/defaults/cdrdao', '/etc/default/cdrdao', '.cdrdao'], + \ 'cdrtoc': ['file.toc'], + \ 'cf': ['file.cfm', 'file.cfi', 'file.cfc'], + \ 'cfengine': ['cfengine.conf'], + \ 'cfg': ['file.cfg', 'file.hgrc', 'filehgrc'], + \ 'ch': ['file.chf'], + \ 'chaiscript': ['file.chai'], + \ 'chaskell': ['file.chs'], + \ 'chill': ['file..ch'], + \ 'chordpro': ['file.chopro', 'file.crd', 'file.cho', 'file.crdpro', 'file.chordpro'], + \ 'cl': ['file.eni'], + \ 'clean': ['file.dcl', 'file.icl'], + \ 'clojure': ['file.clj', 'file.cljs', 'file.cljx', 'file.cljc'], + \ 'cmake': ['CMakeLists.txt', 'file.cmake', 'file.cmake.in'], + \ 'cmusrc': ['any/.cmus/autosave', 'any/.cmus/rc', 'any/.cmus/command-history', 'any/.cmus/file.theme', 'any/cmus/rc', 'any/cmus/file.theme'], + \ 'cobol': ['file.cbl', 'file.cob', 'file.lib'], + \ 'coco': ['file.atg'], + \ 'conaryrecipe': ['file.recipe'], + \ 'conf': ['auto.master'], + \ 'config': ['configure.in', 'configure.ac'], + \ 'context': ['tex/context/any/file.tex', 'file.mkii', 'file.mkiv', 'file.mkvi'], + \ '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'], + \ 'csc': ['file.csc'], + \ 'csdl': ['file.csdl'], + \ 'csp': ['file.csp', 'file.fdr'], + \ 'css': ['file.css'], + \ 'cterm': ['file.con'], + \ 'cucumber': ['file.feature'], + \ 'cuda': ['file.cu'], + \ 'cupl': ['file.pld'], + \ 'cuplsim': ['file.si'], + \ 'cvs': ['cvs123'], + \ 'cvsrc': ['.cvsrc'], + \ 'cynpp': ['file.cyn'], + \ 'datascript': ['file.ds'], + \ 'dcd': ['file.dcd'], + \ 'debcontrol': ['/debian/control'], + \ 'debsources': ['/etc/apt/sources.list', '/etc/apt/sources.list.d/file.list'], + \ 'def': ['file.def'], + \ 'denyhosts': ['denyhosts.conf'], + \ 'desc': ['file.desc'], + \ 'desktop': ['file.desktop', '.directory'], + \ 'dictconf': ['dict.conf', '.dictrc'], + \ 'dictdconf': ['dictd.conf'], + \ 'diff': ['file.diff', 'file.rej'], + \ 'dircolors': ['.dir_colors', '.dircolors', '/etc/DIR_COLORS'], + \ 'dnsmasq': ['/etc/dnsmasq.conf'], + \ 'dockerfile': ['Dockerfile', 'file.Dockerfile'], + \ 'dosbatch': ['file.bat', 'file.sys'], + \ 'dosini': ['.editorconfig', '/etc/yum.conf', 'file.ini'], + \ 'dot': ['file.dot'], + \ 'dracula': ['file.drac', 'file.drc', 'filelvs', 'filelpe'], + \ 'dsl': ['file.dsl'], + \ 'dtd': ['file.dtd'], + \ 'dts': ['file.dts', 'file.dtsi'], + \ 'dylan': ['file.dylan'], + \ 'dylanintr': ['file.intr'], + \ 'dylanlid': ['file.lid'], + \ 'ecd': ['file.ecd'], + \ 'edif': ['file.edf', 'file.edif', 'file.edo'], + \ 'elinks': ['/etc/elinks.conf', '/.elinks/elinks.conf'], + \ 'elmfilt': ['filter-rules'], + \ 'erlang': ['file.erl', 'file.hrl', 'file.yaws'], + \ 'eruby': ['file.erb', 'file.rhtml'], + \ 'esmtprc': ['anyesmtprc'], + \ 'esqlc': ['file.ec', 'file.EC'], + \ 'esterel': ['file.strl'], + \ 'eterm': ['anyEterm/file.cfg'], + \ 'exim': ['exim.conf'], + \ 'expect': ['file.exp'], + \ 'exports': ['exports'], + \ 'factor': ['file.factor'], + \ 'falcon': ['file.fal'], + \ 'fan': ['file.fan', 'file.fwt'], + \ 'fetchmail': ['.fetchmailrc'], + \ 'fgl': ['file.4gl', 'file.4gh', 'file.m4gl'], + \ 'focexec': ['file.fex', 'file.focexec'], + \ 'forth': ['file.fs', 'file.ft'], + \ 'fortran': ['file.f', 'file.for', 'file.fortran', 'file.fpp', 'file.ftn', 'file.f77', 'file.f90', 'file.f95', 'file.f03', 'file.f08'], + \ 'framescript': ['file.fsl'], + \ 'freebasic': ['file.fb', 'file.bi'], + \ 'fstab': ['fstab', 'mtab'], + \ 'gdb': ['.gdbinit'], + \ 'gdmo': ['file.mo', 'file.gdmo'], + \ 'gedcom': ['file.ged', 'lltxxxxx.txt'], + \ 'gitcommit': ['COMMIT_EDITMSG', 'MERGE_MSG', 'TAG_EDITMSG'], + \ 'gitconfig': ['file.git/config', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config'], + \ 'gitolite': ['gitolite.conf'], + \ 'gitrebase': ['git-rebase-todo'], + \ 'gitsendemail': ['.gitsendemail.msg.xxxxxx'], + \ 'gkrellmrc': ['gkrellmrc', 'gkrellmrc_x'], + \ 'gnash': ['gnashrc', '.gnashrc', 'gnashpluginrc', '.gnashpluginrc'], + \ 'gnuplot': ['file.gpi'], + \ 'go': ['file.go'], + \ 'gp': ['file.gp', '.gprc'], + \ 'gpg': ['/.gnupg/options', '/.gnupg/gpg.conf', '/usr/any/gnupg/options.skel'], + \ 'grads': ['file.gs'], + \ 'gretl': ['file.gretl'], + \ 'groovy': ['file.gradle', 'file.groovy'], + \ 'group': ['any/etc/group', 'any/etc/group-', 'any/etc/group.edit', 'any/etc/gshadow', 'any/etc/gshadow-', 'any/etc/gshadow.edit', 'any/var/backups/group.bak', 'any/var/backups/gshadow.bak'], + \ 'grub': ['/boot/grub/menu.lst', '/boot/grub/grub.conf', '/etc/grub.conf'], + \ 'gsp': ['file.gsp'], + \ 'gtkrc': ['.gtkrc', 'gtkrc'], + \ 'haml': ['file.haml'], + \ 'hamster': ['file.hsc', 'file.hsm'], + \ 'haskell': ['file.hs', 'file.hs-boot'], + \ 'haste': ['file.ht'], + \ 'hastepreproc': ['file.htpp'], + \ 'hb': ['file.hb'], + \ 'hercules': ['file.vc', 'file.ev', 'file.sum', 'file.errsum'], + \ 'hex': ['file.hex', 'file.h32'], + \ 'hgcommit': ['hg-editor-file.txt'], + \ 'hog': ['file.hog', 'snort.conf', 'vision.conf'], + \ 'hostconf': ['/etc/host.conf'], + \ 'hostsaccess': ['/etc/hosts.allow', '/etc/hosts.deny'], + \ 'htmlcheetah': ['file.tmpl'], + \ 'htmlm4': ['file.html.m4'], + \ 'httest': ['file.htt', 'file.htb'], + \ 'ibasic': ['file.iba', 'file.ibi'], + \ 'icemenu': ['/.icewm/menu'], + \ 'icon': ['file.icn'], + \ 'indent': ['.indent.pro', 'indentrc'], + \ 'inform': ['file.inf', 'file.INF'], + \ 'initng': ['/etc/initng/any/file.i', 'file.ii'], + \ 'inittab': ['inittab'], + \ 'ipfilter': ['ipf.conf', 'ipf6.conf', 'ipf.rules'], + \ 'iss': ['file.iss'], + \ 'ist': ['file.ist', 'file.mst'], + \ 'j': ['file.ijs'], + \ 'jal': ['file.jal', 'file.JAL'], + \ 'jam': ['file.jpl', 'file.jpr'], + \ 'java': ['file.java', 'file.jav'], + \ 'javacc': ['file.jj', 'file.jjt'], + \ 'javascript': ['file.js', 'file.javascript', 'file.es', 'file.jsx', 'file.mjs'], + \ 'jess': ['file.clp'], + \ 'jgraph': ['file.jgr'], + \ 'jovial': ['file.jov', 'file.j73', 'file.jovial'], + \ 'jproperties': ['file.properties', 'file.properties_xx', 'file.properties_xx_xx'], + \ 'json': ['file.json', 'file.jsonp', 'file.webmanifest'], + \ 'jsp': ['file.jsp'], + \ 'kconfig': ['Kconfig', 'Kconfig.debug'], + \ 'kivy': ['file.kv'], + \ 'kix': ['file.kix'], + \ 'kscript': ['file.ks'], + \ 'kwt': ['file.k'], + \ 'lace': ['file.ace', 'file.ACE'], + \ 'latte': ['file.latte', 'file.lte'], + \ 'ld': ['file.ld'], + \ 'ldif': ['file.ldif'], + \ 'less': ['file.less'], + \ 'lex': ['file.lex', 'file.l', 'file.lxx', 'file.l++'], + \ 'lftp': ['lftp.conf', '.lftprc', 'anylftp/rc'], + \ 'lhaskell': ['file.lhs'], + \ 'libao': ['/etc/libao.conf', '/.libao'], + \ 'lifelines': ['file.ll'], + \ 'lilo': ['lilo.conf'], + \ 'limits': ['/etc/limits', '/etc/anylimits.conf', '/etc/anylimits.d/file.conf'], + \ 'liquid': ['file.liquid'], + \ 'lisp': ['sbclrc', '.sbclrc'], + \ 'lite': ['file.lite', 'file.lt'], + \ 'litestep': ['/LiteStep/any/file.rc'], + \ 'loginaccess': ['/etc/login.access'], + \ 'logindefs': ['/etc/login.defs'], + \ 'logtalk': ['file.lgt'], + \ 'lotos': ['file.lot', 'file.lotos'], + \ 'lout': ['file.lou', 'file.lout'], + \ 'lprolog': ['file.sig'], + \ 'lsl': ['file.lsl'], + \ 'lss': ['file.lss'], + \ 'lua': ['file.lua', 'file.rockspec', 'file.nse'], + \ 'lynx': ['lynx.cfg'], + \ 'm4': ['file.at'], + \ 'mail': ['snd.123', '.letter', '.letter.123', '.followup', '.article', '.article.123', 'pico.123', 'mutt-xx-xxx', 'muttng-xx-xxx', 'ae123.txt', 'file.eml'], + \ 'mailaliases': ['/etc/mail/aliases', '/etc/aliases'], + \ 'mailcap': ['.mailcap', 'mailcap'], + \ 'make': ['file.mk', 'file.mak', 'file.dsp'], + \ 'mallard': ['file.page'], + \ 'manconf': ['/etc/man.conf', 'man.config'], + \ 'map': ['file.map'], + \ 'maple': ['file.mv', 'file.mpl', 'file.mws'], + \ 'markdown': ['file.markdown', 'file.mdown', 'file.mkd', 'file.mkdn', 'file.mdwn', 'file.md'], + \ 'mason': ['file.mason', 'file.mhtml', 'file.comp'], + \ 'master': ['file.mas', 'file.master'], + \ 'mel': ['file.mel'], + \ 'messages': ['/log/auth', '/log/cron', '/log/daemon', '/log/debug', '/log/kern', '/log/lpr', '/log/mail', '/log/messages', '/log/news/news', '/log/syslog', '/log/user', + \ '/log/auth.log', '/log/cron.log', '/log/daemon.log', '/log/debug.log', '/log/kern.log', '/log/lpr.log', '/log/mail.log', '/log/messages.log', '/log/news/news.log', '/log/syslog.log', '/log/user.log', + \ '/log/auth.err', '/log/cron.err', '/log/daemon.err', '/log/debug.err', '/log/kern.err', '/log/lpr.err', '/log/mail.err', '/log/messages.err', '/log/news/news.err', '/log/syslog.err', '/log/user.err', + \ '/log/auth.info', '/log/cron.info', '/log/daemon.info', '/log/debug.info', '/log/kern.info', '/log/lpr.info', '/log/mail.info', '/log/messages.info', '/log/news/news.info', '/log/syslog.info', '/log/user.info', + \ '/log/auth.warn', '/log/cron.warn', '/log/daemon.warn', '/log/debug.warn', '/log/kern.warn', '/log/lpr.warn', '/log/mail.warn', '/log/messages.warn', '/log/news/news.warn', '/log/syslog.warn', '/log/user.warn', + \ '/log/auth.crit', '/log/cron.crit', '/log/daemon.crit', '/log/debug.crit', '/log/kern.crit', '/log/lpr.crit', '/log/mail.crit', '/log/messages.crit', '/log/news/news.crit', '/log/syslog.crit', '/log/user.crit', + \ '/log/auth.notice', '/log/cron.notice', '/log/daemon.notice', '/log/debug.notice', '/log/kern.notice', '/log/lpr.notice', '/log/mail.notice', '/log/messages.notice', '/log/news/news.notice', '/log/syslog.notice', '/log/user.notice'], + \ 'mf': ['file.mf'], + \ 'mgl': ['file.mgl'], + \ 'mgp': ['file.mgp'], + \ 'mib': ['file.mib', 'file.my'], + \ 'mix': ['file.mix', 'file.mixal'], + \ 'mma': ['file.nb'], + \ 'mmp': ['file.mmp'], + \ 'modconf': ['/etc/modules.conf', '/etc/modules', '/etc/conf.modules'], + \ 'modula2': ['file.m2', 'file.mi'], + \ 'monk': ['file.isc', 'file.monk', 'file.ssc', 'file.tsc'], + \ 'moo': ['file.moo'], + \ 'mp': ['file.mp'], + \ 'mplayerconf': ['mplayer.conf', '/.mplayer/config'], + \ 'mrxvtrc': ['mrxvtrc', '.mrxvtrc'], + \ 'msidl': ['file.odl', 'file.mof'], + \ 'msql': ['file.msql'], + \ 'mupad': ['file.mu'], + \ 'mush': ['file.mush'], + \ 'muttrc': ['Muttngrc', 'Muttrc'], + \ 'mysql': ['file.mysql'], + \ 'n1ql': ['file.n1ql', 'file.nql'], + \ 'named': ['namedfile.conf', 'rndcfile.conf'], + \ 'nanorc': ['/etc/nanorc', 'file.nanorc'], + \ 'ncf': ['file.ncf'], + \ 'netrc': ['.netrc'], + \ 'ninja': ['file.ninja'], + \ 'nqc': ['file.nqc'], + \ 'nroff': ['file.tr', 'file.nr', 'file.roff', 'file.tmac', 'file.mom'], + \ 'nsis': ['file.nsi', 'file.nsh'], + \ 'obj': ['file.obj'], + \ 'ocaml': ['file.ml', 'file.mli', 'file.mll', 'file.mly', '.ocamlinit'], + \ 'occam': ['file.occ'], + \ 'omnimark': ['file.xom', 'file.xin'], + \ 'openroad': ['file.or'], + \ 'ora': ['file.ora'], + \ 'pamconf': ['/etc/pam.conf'], + \ 'papp': ['file.papp', 'file.pxml', 'file.pxsl'], + \ 'pascal': ['file.pas', 'file.dpr'], + \ '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'], + \ 'perl': ['file.plx', 'file.al', 'file.psgi', 'gitolite.rc', '.gitolite.rc', 'example.gitolite.rc'], + \ 'perl6': ['file.p6', 'file.pm6', 'file.pl6'], + \ 'pf': ['pf.conf'], + \ 'pfmain': ['main.cf'], + \ 'php': ['file.php', 'file.php9', 'file.phtml', 'file.ctp'], + \ 'pike': ['file.pike', 'file.lpc', 'file.ulpc', 'file.pmod'], + \ 'pilrc': ['file.rcp'], + \ 'pine': ['.pinerc', 'pinerc', '.pinercex', 'pinercex'], + \ 'pinfo': ['/etc/pinforc', '/.pinforc'], + \ 'pli': ['file.pli', 'file.pl1'], + \ 'plm': ['file.plm', 'file.p36', 'file.pac'], + \ 'plp': ['file.plp'], + \ 'plsql': ['file.pls', 'file.plsql'], + \ 'po': ['file.po', 'file.pot'], + \ 'pod': ['file.pod'], + \ 'pod6': ['file.pod6'], + \ 'postscr': ['file.ps', 'file.pfa', 'file.afm', 'file.eps', 'file.epsf', 'file.epsi', 'file.ai'], + \ 'pov': ['file.pov'], + \ 'povini': ['.povrayrc'], + \ 'ppd': ['file.ppd'], + \ 'ppwiz': ['file.it', 'file.ih'], + \ 'privoxy': ['file.action'], + \ 'proc': ['file.pc'], + \ 'procmail': ['.procmail', '.procmailrc'], + \ 'prolog': ['file.pdb'], + \ 'promela': ['file.pml'], + \ 'proto': ['file.proto'], + \ 'protocols': ['/etc/protocols'], + \ 'psf': ['file.psf'], + \ 'pyrex': ['file.pyx', 'file.pxd'], + \ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', 'file.ptl'], + \ 'quake': ['anybaseq2/file.cfg', 'anyid1/file.cfg', 'quake3/file.cfg'], + \ 'radiance': ['file.rad', 'file.mat'], + \ 'ratpoison': ['.ratpoisonrc', 'ratpoisonrc'], + \ 'rc': ['file.rc', 'file.rch'], + \ 'rcs': ['file,v'], + \ 'readline': ['.inputrc', 'inputrc'], + \ 'remind': ['.reminders', 'file.remind', 'file.rem'], + \ 'resolv': ['resolv.conf'], + \ 'reva': ['file.frt'], + \ 'rexx': ['file.rex', 'file.orx', 'file.rxo', 'file.rxj', 'file.jrexx', 'file.rexxj', 'file.rexx', 'file.testGroup', 'file.testUnit'], + \ 'rib': ['file.rib'], + \ 'rnc': ['file.rnc'], + \ 'rng': ['file.rng'], + \ 'robots': ['robots.txt'], + \ 'rpcgen': ['file.x'], + \ 'rpl': ['file.rpl'], + \ 'rst': ['file.rst'], + \ 'rtf': ['file.rtf'], + \ 'ruby': ['.irbrc', 'irbrc', 'file.rb', 'file.rbw', 'file.gemspec', 'file.ru', 'Gemfile', 'file.builder', 'file.rxml', 'file.rjs', 'file.rant', 'file.rake'], + \ 'rust': ['file.rs'], + \ 'samba': ['smb.conf'], + \ 'sas': ['file.sas'], + \ 'sass': ['file.sass'], + \ 'sather': ['file.sa'], + \ 'sbt': ['file.sbt'], + \ 'scala': ['file.scala'], + \ 'scheme': ['file.scm', 'file.ss', 'file.rkt'], + \ 'scilab': ['file.sci', 'file.sce'], + \ 'screen': ['.screenrc', 'screenrc'], + \ 'scss': ['file.scss'], + \ 'sd': ['file.sd'], + \ 'sdc': ['file.sdc'], + \ 'sdl': ['file.sdl', 'file.pr'], + \ 'sed': ['file.sed'], + \ 'sensors': ['/etc/sensors.conf', '/etc/sensors3.conf'], + \ 'services': ['/etc/services'], + \ 'setserial': ['/etc/serial.conf'], + \ 'sh': ['/etc/udev/cdsymlinks.conf'], + \ 'sieve': ['file.siv'], + \ 'simula': ['file.sim'], + \ 'sinda': ['file.sin', 'file.s85'], + \ 'sisu': ['file.sst', 'file.ssm', 'file.ssi', 'file.-sst', 'file._sst', 'file.sst.meta', 'file.-sst.meta', 'file._sst.meta'], + \ 'skill': ['file.il', 'file.ils', 'file.cdf'], + \ 'slang': ['file.sl'], + \ 'slice': ['file.ice'], + \ 'slpconf': ['/etc/slp.conf'], + \ 'slpreg': ['/etc/slp.reg'], + \ 'slpspi': ['/etc/slp.spi'], + \ 'slrnrc': ['.slrnrc'], + \ 'slrnsc': ['file.score'], + \ 'sm': ['sendmail.cf'], + \ 'smarty': ['file.tpl'], + \ 'smcl': ['file.hlp', 'file.ihlp', 'file.smcl'], + \ 'smith': ['file.smt', 'file.smith'], + \ 'sml': ['file.sml'], + \ 'snobol4': ['file.sno', 'file.spt'], + \ 'spec': ['file.spec'], + \ 'spice': ['file.sp', 'file.spice'], + \ 'spup': ['file.speedup', 'file.spdata', 'file.spd'], + \ 'spyce': ['file.spy', 'file.spi'], + \ 'sql': ['file.tyb', 'file.typ', 'file.tyc', 'file.pkb', 'file.pks'], + \ 'sqlj': ['file.sqlj'], + \ 'sqr': ['file.sqr', 'file.sqi'], + \ 'squid': ['squid.conf'], + \ 'srec': ['file.s19', 'file.s28', 'file.s37', 'file.mot', 'file.srec'], + \ 'sshconfig': ['ssh_config', '/.ssh/config'], + \ 'sshdconfig': ['sshd_config'], + \ 'st': ['file.st'], + \ 'stata': ['file.ado', 'file.do', 'file.imata', 'file.mata'], + \ 'stp': ['file.stp'], + \ 'sudoers': ['any/etc/sudoers', 'sudoers.tmp'], + \ 'svg': ['file.svg'], + \ 'svn': ['svn-commitfile.tmp'], + \ 'sysctl': ['/etc/sysctl.conf', '/etc/sysctl.d/file.conf'], + \ 'systemd': ['any/systemd/file.automount', 'any/systemd/file.mount', 'any/systemd/file.path', 'any/systemd/file.service', 'any/systemd/file.socket', 'any/systemd/file.swap', 'any/systemd/file.target', 'any/systemd/file.timer'], + \ 'systemverilog': ['file.sv', 'file.svh'], + \ 'tags': ['tags'], + \ 'tak': ['file.tak'], + \ 'taskdata': ['pending.data', 'completed.data', 'undo.data'], + \ 'taskedit': ['file.task'], + \ 'tcl': ['file.tcl', 'file.tk', 'file.itcl', 'file.itk', 'file.jacl'], + \ 'teraterm': ['file.ttl'], + \ 'terminfo': ['file.ti'], + \ 'tex': ['file.latex', 'file.sty', 'file.dtx', 'file.ltx', 'file.bbl'], + \ 'texinfo': ['file.texinfo', 'file.texi', 'file.txi'], + \ 'texmf': ['texmf.cnf'], + \ 'text': ['file.text', 'README'], + \ 'tf': ['file.tf', '.tfrc', 'tfrc'], + \ 'tidy': ['.tidyrc', 'tidyrc'], + \ 'tilde': ['file.t.html'], + \ 'tli': ['file.tli'], + \ 'tmux': ['tmuxfile.conf', '.tmuxfile.conf'], + \ 'tpp': ['file.tpp'], + \ 'treetop': ['file.treetop'], + \ 'trustees': ['trustees.conf'], + \ 'tsalt': ['file.slt'], + \ 'tsscl': ['file.tsscl'], + \ 'tssgm': ['file.tssgm'], + \ 'tssop': ['file.tssop'], + \ 'twig': ['file.twig'], + \ 'uc': ['file.uc'], + \ 'udevconf': ['/etc/udev/udev.conf'], + \ 'udevperm': ['/etc/udev/permissions.d/file.permissions'], + \ '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'], + \ 'upstreamdat': ['upstream.dat', 'UPSTREAM.DAT', 'upstream.file.dat', 'UPSTREAM.FILE.DAT', 'file.upstream.dat', 'FILE.UPSTREAM.DAT'], + \ 'upstreaminstalllog': ['upstreaminstall.log', 'UPSTREAMINSTALL.LOG', 'upstreaminstall.file.log', 'UPSTREAMINSTALL.FILE.LOG', 'file.upstreaminstall.log', 'FILE.UPSTREAMINSTALL.LOG'], + \ 'upstreamlog': ['fdrupstream.log', 'upstream.log', 'UPSTREAM.LOG', 'upstream.file.log', 'UPSTREAM.FILE.LOG', 'file.upstream.log', 'FILE.UPSTREAM.LOG', 'UPSTREAM-file.log', 'UPSTREAM-FILE.LOG'], + \ 'usserverlog': ['usserver.log', 'USSERVER.LOG', 'usserver.file.log', 'USSERVER.FILE.LOG', 'file.usserver.log', 'FILE.USSERVER.LOG'], + \ 'usw2kagtlog': ['usw2kagt.log', 'USW2KAGT.LOG', 'usw2kagt.file.log', 'USW2KAGT.FILE.LOG', 'file.usw2kagt.log', 'FILE.USW2KAGT.LOG'], + \ 'vb': ['file.sba', 'file.vb', 'file.vbs', 'file.dsm', 'file.ctl'], + \ 'vera': ['file.vr', 'file.vri', 'file.vrh'], + \ 'verilog': ['file.v'], + \ 'verilogams': ['file.va', 'file.vams'], + \ 'vgrindefs': ['vgrindefs'], + \ 'vhdl': ['file.hdl', 'file.vhd', 'file.vhdl', 'file.vbe', 'file.vst'], + \ 'vim': ['file.vim', 'file.vba', '.exrc', '_exrc'], + \ 'viminfo': ['.viminfo', '_viminfo'], + \ 'vmasm': ['file.mar'], + \ 'voscm': ['file.cm'], + \ 'vrml': ['file.wrl'], + \ 'vroom': ['file.vroom'], + \ 'webmacro': ['file.wm'], + \ 'wget': ['.wgetrc', 'wgetrc'], + \ 'winbatch': ['file.wbt'], + \ 'wml': ['file.wml'], + \ 'wsml': ['file.wsml'], + \ 'wvdial': ['wvdial.conf', '.wvdialrc'], + \ 'xdefaults': ['.Xdefaults', '.Xpdefaults', '.Xresources', 'xdm-config', 'file.ad'], + \ 'xhtml': ['file.xhtml', 'file.xht'], + \ 'xinetd': ['/etc/xinetd.conf'], + \ 'xmath': ['file.msc', 'file.msf'], + \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.ts', 'file.ui', 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul'], + \ 'xmodmap': ['anyXmodmap'], + \ 'xf86conf': ['xorg.conf', 'xorg.conf-4'], + \ 'xpm2': ['file.xpm2'], + \ 'xquery': ['file.xq', 'file.xql', 'file.xqm', 'file.xquery', 'file.xqy'], + \ 'xs': ['file.xs'], + \ 'xsd': ['file.xsd'], + \ 'xslt': ['file.xsl', 'file.xslt'], + \ 'yacc': ['file.yy', 'file.yxx', 'file.y++'], + \ 'yaml': ['file.yaml', 'file.yml'], + \ 'z8a': ['file.z8a'], + \ 'zimbu': ['file.zu'], + \ 'zimbutempl': ['file.zut'], + \ 'zsh': ['.zprofile', '/etc/zprofile', '.zfbfmarks', 'file.zsh'], + \ + \ 'aap': ['file.aap'], + \ 'help': [$VIMRUNTIME . '/doc/help.txt'], + \ 'xpm': ['file.xpm'], + \ } + +let s:filename_case_checks = { + \ 'modula2': ['file.DEF', 'file.MOD'], + \ } + +func CheckItems(checks) + for [ft, names] in items(a:checks) + for i in range(0, len(names) - 1) + new + try + exe 'edit ' . names[i] + catch + call assert_report('cannot edit "' . names[i] . '": ' . v:errmsg) + endtry + call assert_equal(ft, &filetype, 'with file name: ' . names[i]) + bwipe! + endfor + endfor +endfunc + +func Test_filetype_detection() + filetype on + call CheckItems(s:filename_checks) + if has('fname_case') + call CheckItems(s:filename_case_checks) + endif + filetype off +endfunc + +" Filetypes detected from the file contents by scripts.vim +let s:script_checks = { + \ 'virata': [['% Virata'], + \ ['', '% Virata'], + \ ['', '', '% Virata'], + \ ['', '', '', '% Virata'], + \ ['', '', '', '', '% Virata']], + \ 'strace': [['execve("/usr/bin/pstree", ["pstree"], 0x7ff0 /* 63 vars */) = 0'], + \ ['15:17:47 execve("/usr/bin/pstree", ["pstree"], ... "_=/usr/bin/strace"]) = 0'], + \ ['__libc_start_main and something']], + \ } + +func Test_script_detection() + filetype on + for [ft, files] in items(s:script_checks) + for file in files + call writefile(file, 'Xtest') + split Xtest + call assert_equal(ft, &filetype, 'for text: ' . string(file)) + bwipe! + endfor + endfor + call delete('Xtest') + filetype off +endfunc + diff --git a/src/nvim/testdir/test_find_complete.vim b/src/nvim/testdir/test_find_complete.vim new file mode 100644 index 0000000000..4732109ed0 --- /dev/null +++ b/src/nvim/testdir/test_find_complete.vim @@ -0,0 +1,157 @@ +" Tests for the 'find' command completion. + +" Do all the tests in a separate window to avoid E211 when we recursively +" delete the Xfind directory during cleanup +func Test_find_complete() + set belloff=all + + " On windows a stale "Xfind" directory may exist, remove it so that + " we start from a clean state. + call delete("Xfind", "rf") + let cwd = getcwd() + let test_out = cwd . '/test.out' + call mkdir('Xfind') + cd Xfind + + new + set path= + call assert_fails('call feedkeys(":find\t\n", "xt")', 'E345:') + close + + new + set path=. + call assert_fails('call feedkeys(":find\t\n", "xt")', 'E32:') + close + + new + set path=.,, + call assert_fails('call feedkeys(":find\t\n", "xt")', 'E32:') + close + + new + set path=./** + call assert_fails('call feedkeys(":find\t\n", "xt")', 'E32:') + close + + " We shouldn't find any file till this point + + call mkdir('in/path', 'p') + exe 'cd ' . cwd + call writefile(['Holy Grail'], 'Xfind/file.txt') + call writefile(['Jimmy Hoffa'], 'Xfind/in/file.txt') + call writefile(['Another Holy Grail'], 'Xfind/in/stuff.txt') + call writefile(['E.T.'], 'Xfind/in/path/file.txt') + + new + set path=Xfind/** + call feedkeys(":find file\t\n", "xt") + call assert_equal('Holy Grail', getline(1)) + call feedkeys(":find file\t\t\n", "xt") + call assert_equal('Jimmy Hoffa', getline(1)) + call feedkeys(":find file\t\t\t\n", "xt") + call assert_equal('E.T.', getline(1)) + + " Rerun the previous three find completions, using fullpath in 'path' + exec "set path=" . cwd . "/Xfind/**" + + call feedkeys(":find file\t\n", "xt") + call assert_equal('Holy Grail', getline(1)) + call feedkeys(":find file\t\t\n", "xt") + call assert_equal('Jimmy Hoffa', getline(1)) + call feedkeys(":find file\t\t\t\n", "xt") + call assert_equal('E.T.', getline(1)) + + " Same steps again, using relative and fullpath items that point to the same + " recursive location. + " This is to test that there are no duplicates in the completion list. + set path+=Xfind/** + call feedkeys(":find file\t\n", "xt") + call assert_equal('Holy Grail', getline(1)) + call feedkeys(":find file\t\t\n", "xt") + call assert_equal('Jimmy Hoffa', getline(1)) + call feedkeys(":find file\t\t\t\n", "xt") + call assert_equal('E.T.', getline(1)) + call feedkeys(":find file\t\t\n", "xt") + + " Test find completion for directory of current buffer, which at this point + " is Xfind/in/file.txt. + set path=. + call feedkeys(":find st\t\n", "xt") + call assert_equal('Another Holy Grail', getline(1)) + + " Test find completion for empty path item ",," which is the current + " directory + cd Xfind + set path=,, + call feedkeys(":find f\t\n", "xt") + call assert_equal('Holy Grail', getline(1)) + + " Test shortening of + " + " foo/x/bar/voyager.txt + " foo/y/bar/voyager.txt + " + " When current directory is above foo/ they should be shortened to (in order + " of appearance): + " + " x/bar/voyager.txt + " y/bar/voyager.txt + call mkdir('foo/x/bar', 'p') + call mkdir('foo/y/bar', 'p') + call writefile(['Voyager 1'], 'foo/x/bar/voyager.txt') + call writefile(['Voyager 2'], 'foo/y/bar/voyager.txt') + + exec "set path=" . cwd . "/Xfind/**" + call feedkeys(":find voyager\t\n", "xt") + call assert_equal('Voyager 1', getline(1)) + call feedkeys(":find voyager\t\t\n", "xt") + call assert_equal('Voyager 2', getline(1)) + + " + " When current directory is .../foo/y/bar they should be shortened to (in + " order of appearance): + " + " ./voyager.txt + " x/bar/voyager.txt + cd foo/y/bar + call feedkeys(":find voyager\t\n", "xt") + call assert_equal('Voyager 2', getline(1)) + call feedkeys(":find voyager\t\t\n", "xt") + call assert_equal('Voyager 1', getline(1)) + + " Check the opposite too: + cd ../../x/bar + call feedkeys(":find voyager\t\n", "xt") + call assert_equal('Voyager 1', getline(1)) + call feedkeys(":find voyager\t\t\n", "xt") + call assert_equal('Voyager 2', getline(1)) + + " Check for correct handling of shorten_fname()'s behavior on windows + exec "cd " . cwd . "/Xfind/in" + call feedkeys(":find file\t\n", "xt") + call assert_equal('Jimmy Hoffa', getline(1)) + + " Test for relative to current buffer 'path' item + exec "cd " . cwd . "/Xfind/" + set path=./path + " Open the file where Jimmy Hoffa is found + e in/file.txt + " Find the file containing 'E.T.' in the Xfind/in/path directory + call feedkeys(":find file\t\n", "xt") + call assert_equal('E.T.', getline(1)) + + " Test that completion works when path=.,, + set path=.,, + " Open Jimmy Hoffa file + e in/file.txt + call assert_equal('Jimmy Hoffa', getline(1)) + + " Search for the file containing Holy Grail in same directory as in/path.txt + call feedkeys(":find stu\t\n", "xt") + call assert_equal('Another Holy Grail', getline(1)) + + enew | only + exe 'cd ' . cwd + call delete('Xfind', 'rf') + set path& +endfunc diff --git a/src/nvim/testdir/test_findfile.vim b/src/nvim/testdir/test_findfile.vim new file mode 100644 index 0000000000..d9a89801ea --- /dev/null +++ b/src/nvim/testdir/test_findfile.vim @@ -0,0 +1,25 @@ +" Test for findfile() +" +func Test_findfile() + new + let cwd=getcwd() + cd .. + + " Tests may be run from a shadow directory, so an extra cd needs to be done to + " get above src/ + if fnamemodify(getcwd(), ':t') != 'src' + cd ../.. + else + cd .. + endif + set ssl + + call assert_equal('src/nvim/testdir/test_findfile.vim', findfile('test_findfile.vim','src/nvim/test*')) + exe "cd" cwd + cd .. + call assert_equal('testdir/test_findfile.vim', findfile('test_findfile.vim','test*')) + call assert_equal('testdir/test_findfile.vim', findfile('test_findfile.vim','testdir')) + + exe "cd" cwd + q! +endfunc diff --git a/src/nvim/testdir/test_fixeol.vim b/src/nvim/testdir/test_fixeol.vim new file mode 100644 index 0000000000..32cb059e26 --- /dev/null +++ b/src/nvim/testdir/test_fixeol.vim @@ -0,0 +1,48 @@ +" Tests for 'fixeol' and 'eol' +func Test_fixeol() + " first write two test files – with and without trailing EOL + " use Unix fileformat for consistency + set ff=unix + enew! + call setline('.', 'with eol') + w! XXEol + enew! + set noeol nofixeol + call setline('.', 'without eol') + w! XXNoEol + set eol fixeol + bwipe XXEol XXNoEol + + " try editing files with 'fixeol' disabled + e! XXEol + normal ostays eol + set nofixeol + w! XXTestEol + e! XXNoEol + normal ostays without + set nofixeol + w! XXTestNoEol + bwipe! XXEol XXNoEol XXTestEol XXTestNoEol + set fixeol + + " Append "END" to each file so that we can see what the last written char + " was. + normal ggdGaEND + w >>XXEol + w >>XXNoEol + w >>XXTestEol + w >>XXTestNoEol + + call assert_equal(['with eol', 'END'], readfile('XXEol')) + call assert_equal(['without eolEND'], readfile('XXNoEol')) + call assert_equal(['with eol', 'stays eol', 'END'], readfile('XXTestEol')) + call assert_equal(['without eol', 'stays withoutEND'], + \ readfile('XXTestNoEol')) + + call delete('XXEol') + call delete('XXNoEol') + call delete('XXTestEol') + call delete('XXTestNoEol') + set ff& fixeol& eol& + enew! +endfunc diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim index 46c54e8614..7c6d38d7ec 100644 --- a/src/nvim/testdir/test_fold.vim +++ b/src/nvim/testdir/test_fold.vim @@ -360,3 +360,24 @@ func! Test_move_folds_around_indent() call assert_equal([0, 1, 1, 1, 1, 0, 0, 0, 1, 1], map(range(1, line('$')), 'foldlevel(v:val)')) bw! endfunc + +" test for patch 7.3.637 +" Cannot catch the error caused by a foldopen when there is no fold. +func Test_foldopen_exception() + enew! + let a = 'No error caught' + try + foldopen + catch + let a = matchstr(v:exception,'^[^ ]*') + endtry + call assert_equal('Vim(foldopen):E490:', a) + + let a = 'No error caught' + try + foobar + catch + let a = matchstr(v:exception,'^[^ ]*') + endtry + call assert_match('E492:', a) +endfunc diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 0ce034b63e..398e9ab331 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -311,3 +311,45 @@ func! Test_mode() bwipe! iunmap <F2> endfunc + +func Test_getbufvar() + let bnr = bufnr('%') + let b:var_num = '1234' + let def_num = '5678' + call assert_equal('1234', getbufvar(bnr, 'var_num')) + call assert_equal('1234', getbufvar(bnr, 'var_num', def_num)) + + let bd = getbufvar(bnr, '') + call assert_equal('1234', bd['var_num']) + call assert_true(exists("bd['changedtick']")) + call assert_equal(2, len(bd)) + + let bd2 = getbufvar(bnr, '', def_num) + call assert_equal(bd, bd2) + + unlet b:var_num + call assert_equal(def_num, getbufvar(bnr, 'var_num', def_num)) + call assert_equal('', getbufvar(bnr, 'var_num')) + + let bd = getbufvar(bnr, '') + call assert_equal(1, len(bd)) + let bd = getbufvar(bnr, '',def_num) + call assert_equal(1, len(bd)) + + call assert_equal('', getbufvar(9999, '')) + call assert_equal(def_num, getbufvar(9999, '', def_num)) + unlet def_num + + call assert_equal(0, getbufvar(bnr, '&autoindent')) + call assert_equal(0, getbufvar(bnr, '&autoindent', 1)) + + " Open new window with forced option values + set fileformats=unix,dos + new ++ff=dos ++bin ++enc=iso-8859-2 + call assert_equal('dos', getbufvar(bufnr('%'), '&fileformat')) + call assert_equal(1, getbufvar(bufnr('%'), '&bin')) + call assert_equal('iso-8859-2', getbufvar(bufnr('%'), '&fenc')) + close + + set fileformats& +endfunc diff --git a/src/nvim/testdir/test_hardcopy.vim b/src/nvim/testdir/test_hardcopy.vim index ea9790d134..7aea704e86 100644 --- a/src/nvim/testdir/test_hardcopy.vim +++ b/src/nvim/testdir/test_hardcopy.vim @@ -50,6 +50,7 @@ endfunc " We don't check much of the contents. func Test_with_syntax() if has('postscript') + edit test_hardcopy.vim set printoptions=syntax:y syn on hardcopy > Xhardcopy diff --git a/src/nvim/testdir/test_help_tagjump.vim b/src/nvim/testdir/test_help_tagjump.vim index 65d99c644c..06c48d8e76 100644 --- a/src/nvim/testdir/test_help_tagjump.vim +++ b/src/nvim/testdir/test_help_tagjump.vim @@ -18,6 +18,52 @@ func Test_help_tagjump() call assert_true(getline('.') =~ '\*help.txt\*') helpclose + help | + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*bar\*') + helpclose + + help "* + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*quotestar\*') + helpclose + + help sp?it + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*:split\*') + helpclose + + help :? + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*:?\*') + helpclose + + help FileW*Post + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*FileWritePost\*') + helpclose + + help `ls` + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*:ls\*') + helpclose + + help ^X + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*CTRL-X\*') + helpclose + + help i_^_CTRL-D + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*i_^_CTRL-D\*') + helpclose + + exec "help \<C-V>" + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*CTRL-V\*') + helpclose + + exec "help! ('textwidth'" call assert_equal("help", &filetype) call assert_true(getline('.') =~ "\\*'textwidth'\\*") @@ -47,6 +93,16 @@ func Test_help_tagjump() call assert_equal("help", &filetype) call assert_true(getline('.') =~ '\*{address}\*') helpclose + + exusage + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*:index\*') + helpclose + + viusage + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*normal-index\*') + helpclose endfunc let s:langs = ['en', 'ab', 'ja'] diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim new file mode 100644 index 0000000000..c307e33cbf --- /dev/null +++ b/src/nvim/testdir/test_ins_complete.vim @@ -0,0 +1,219 @@ + +" Test for insert expansion +func Test_ins_complete() + edit test_ins_complete.vim + " The files in the current directory interferes with the files + " used by this test. So use a separate directory for the test. + call mkdir('Xdir') + cd Xdir + + set ff=unix + call writefile(["test11\t36Gepeto\t/Tag/", + \ "asd\ttest11file\t36G", + \ "Makefile\tto\trun"], 'Xtestfile') + call writefile(['', 'start of testfile', + \ 'ru', + \ 'run1', + \ 'run2', + \ 'STARTTEST', + \ 'ENDTEST', + \ 'end of testfile'], 'Xtestdata') + set ff& + + enew! + edit Xtestdata + new + call append(0, ['#include "Xtestfile"', '']) + call cursor(2, 1) + + set cot= + set cpt=.,w + " add-expands (word from next line) from other window + exe "normal iru\<C-N>\<C-N>\<C-X>\<C-N>\<Esc>\<C-A>" + call assert_equal('run1 run3', getline('.')) + " add-expands (current buffer first) + exe "normal o\<C-P>\<C-X>\<C-N>" + call assert_equal('run3 run3', getline('.')) + " Local expansion, ends in an empty line (unless it becomes a global + " expansion) + exe "normal o\<C-X>\<C-P>\<C-P>\<C-P>\<C-P>\<C-P>" + call assert_equal('', getline('.')) + " starts Local and switches to global add-expansion + exe "normal o\<C-X>\<C-P>\<C-P>\<C-X>\<C-X>\<C-N>\<C-X>\<C-N>\<C-N>" + call assert_equal('run1 run2', getline('.')) + + set cpt=.,w,i + " i-add-expands and switches to local + exe "normal OM\<C-N>\<C-X>\<C-N>\<C-X>\<C-N>\<C-X>\<C-X>\<C-X>\<C-P>" + call assert_equal("Makefile\tto\trun3", getline('.')) + " add-expands lines (it would end in an empty line if it didn't ignored + " itself) + exe "normal o\<C-X>\<C-L>\<C-X>\<C-L>\<C-P>\<C-P>" + call assert_equal("Makefile\tto\trun3", getline('.')) + call assert_equal("Makefile\tto\trun3", getline(line('.') - 1)) + + set cpt=kXtestfile + " checks k-expansion, and file expansion (use Xtest11 instead of test11, + " because TEST11.OUT may match first on DOS) + write Xtest11.one + write Xtest11.two + exe "normal o\<C-N>\<Esc>IX\<Esc>A\<C-X>\<C-F>\<C-N>" + call assert_equal('Xtest11.two', getline('.')) + + " use CTRL-X CTRL-F to complete Xtest11.one, remove it and then use CTRL-X + " CTRL-F again to verify this doesn't cause trouble. + exe "normal oXt\<C-X>\<C-F>\<BS>\<BS>\<BS>\<BS>\<BS>\<BS>\<BS>\<BS>\<C-X>\<C-F>" + call assert_equal('Xtest11.one', getline('.')) + normal ddk + + set cpt=w + " checks make_cyclic in other window + exe "normal oST\<C-N>\<C-P>\<C-P>\<C-P>\<C-P>" + call assert_equal('STARTTEST', getline('.')) + + set cpt=u nohid + " checks unloaded buffer expansion + only + exe "normal oEN\<C-N>" + call assert_equal('ENDTEST', getline('.')) + " checks adding mode abortion + exe "normal ounl\<C-N>\<C-X>\<C-X>\<C-P>" + call assert_equal('unless', getline('.')) + + set cpt=t,d def=^\\k* tags=Xtestfile notagbsearch + " tag expansion, define add-expansion interrupted + exe "normal o\<C-X>\<C-]>\<C-X>\<C-D>\<C-X>\<C-D>\<C-X>\<C-X>\<C-D>\<C-X>\<C-D>\<C-X>\<C-D>\<C-X>\<C-D>" + call assert_equal('test11file 36Gepeto /Tag/ asd', getline('.')) + " t-expansion + exe "normal oa\<C-N>\<Esc>" + call assert_equal('asd', getline('.')) + + %bw! + call delete('Xtestfile') + call delete('Xtest11.one') + call delete('Xtest11.two') + call delete('Xtestdata') + set cpt& cot& def& tags& tagbsearch& hidden& + cd .. + call delete('Xdir', 'rf') +endfunc + +function! s:CompleteDone_CompleteFuncDict( findstart, base ) + if a:findstart + return 0 + endif + + return { + \ 'words': [ + \ { + \ 'word': 'aword', + \ 'abbr': 'wrd', + \ 'menu': 'extra text', + \ 'info': 'words are cool', + \ 'kind': 'W', + \ 'user_data': 'test' + \ } + \ ] + \ } +endfunction + +function! s:CompleteDone_CheckCompletedItemDict() + call assert_equal( 'aword', v:completed_item[ 'word' ] ) + call assert_equal( 'wrd', v:completed_item[ 'abbr' ] ) + call assert_equal( 'extra text', v:completed_item[ 'menu' ] ) + call assert_equal( 'words are cool', v:completed_item[ 'info' ] ) + call assert_equal( 'W', v:completed_item[ 'kind' ] ) + call assert_equal( 'test', v:completed_item[ 'user_data' ] ) + + let s:called_completedone = 1 +endfunction + +function Test_CompleteDoneDict() + au CompleteDone * :call <SID>CompleteDone_CheckCompletedItemDict() + + set completefunc=<SID>CompleteDone_CompleteFuncDict + execute "normal a\<C-X>\<C-U>\<C-Y>" + set completefunc& + + call assert_equal( 'test', v:completed_item[ 'user_data' ] ) + call assert_true( s:called_completedone ) + + let s:called_completedone = 0 + au! CompleteDone +endfunc + +function! s:CompleteDone_CompleteFuncDictNoUserData( findstart, base ) + if a:findstart + return 0 + endif + + return { + \ 'words': [ + \ { + \ 'word': 'aword', + \ 'abbr': 'wrd', + \ 'menu': 'extra text', + \ 'info': 'words are cool', + \ 'kind': 'W' + \ } + \ ] + \ } +endfunction + +function! s:CompleteDone_CheckCompletedItemDictNoUserData() + call assert_equal( 'aword', v:completed_item[ 'word' ] ) + call assert_equal( 'wrd', v:completed_item[ 'abbr' ] ) + call assert_equal( 'extra text', v:completed_item[ 'menu' ] ) + call assert_equal( 'words are cool', v:completed_item[ 'info' ] ) + call assert_equal( 'W', v:completed_item[ 'kind' ] ) + call assert_equal( '', v:completed_item[ 'user_data' ] ) + + let s:called_completedone = 1 +endfunction + +function Test_CompleteDoneDictNoUserData() + au CompleteDone * :call <SID>CompleteDone_CheckCompletedItemDictNoUserData() + + set completefunc=<SID>CompleteDone_CompleteFuncDictNoUserData + execute "normal a\<C-X>\<C-U>\<C-Y>" + set completefunc& + + call assert_equal( '', v:completed_item[ 'user_data' ] ) + call assert_true( s:called_completedone ) + + let s:called_completedone = 0 + au! CompleteDone +endfunc + +function! s:CompleteDone_CompleteFuncList( findstart, base ) + if a:findstart + return 0 + endif + + return [ 'aword' ] +endfunction + +function! s:CompleteDone_CheckCompletedItemList() + call assert_equal( 'aword', v:completed_item[ 'word' ] ) + call assert_equal( '', v:completed_item[ 'abbr' ] ) + call assert_equal( '', v:completed_item[ 'menu' ] ) + call assert_equal( '', v:completed_item[ 'info' ] ) + call assert_equal( '', v:completed_item[ 'kind' ] ) + call assert_equal( '', v:completed_item[ 'user_data' ] ) + + let s:called_completedone = 1 +endfunction + +function Test_CompleteDoneList() + au CompleteDone * :call <SID>CompleteDone_CheckCompletedItemList() + + set completefunc=<SID>CompleteDone_CompleteFuncList + execute "normal a\<C-X>\<C-U>\<C-Y>" + set completefunc& + + call assert_equal( '', v:completed_item[ 'user_data' ] ) + call assert_true( s:called_completedone ) + + let s:called_completedone = 0 + au! CompleteDone +endfunc diff --git a/src/nvim/testdir/test_let.vim b/src/nvim/testdir/test_let.vim new file mode 100644 index 0000000000..24c6ef5e01 --- /dev/null +++ b/src/nvim/testdir/test_let.vim @@ -0,0 +1,27 @@ +" Tests for the :let command. + +func Test_let() + " Test to not autoload when assigning. It causes internal error. + set runtimepath+=./sautest + let Test104#numvar = function('tr') + call assert_equal("function('tr')", string(Test104#numvar)) + + let a = 1 + let b = 2 + + let out = execute('let a b') + let s = "\na #1\nb #2" + call assert_equal(s, out) + + let out = execute('let {0 == 1 ? "a" : "b"}') + let s = "\nb #2" + call assert_equal(s, out) + + let out = execute('let {0 == 1 ? "a" : "b"} a') + let s = "\nb #2\na #1" + call assert_equal(s, out) + + let out = execute('let a {0 == 1 ? "a" : "b"}') + let s = "\na #1\nb #2" + call assert_equal(s, out) +endfunc diff --git a/src/nvim/testdir/test_lineending.vim b/src/nvim/testdir/test_lineending.vim new file mode 100644 index 0000000000..5be3be8db3 --- /dev/null +++ b/src/nvim/testdir/test_lineending.vim @@ -0,0 +1,19 @@ +" Tests for saving/loading a file with some lines ending in +" CTRL-M, some not +func Test_lineending() + let l = ["this line ends in a\<CR>", + \ "this one doesn't", + \ "this one does\<CR>", + \ "and the last one doesn't"] + set fileformat=dos + enew! + call append(0, l) + $delete + write Xfile1 + bwipe Xfile1 + edit Xfile1 + let t = getline(1, '$') + call assert_equal(l, t) + new | only + call delete('Xfile1') +endfunc diff --git a/src/nvim/testdir/test_lispwords.vim b/src/nvim/testdir/test_lispwords.vim new file mode 100644 index 0000000000..4c05504cf1 --- /dev/null +++ b/src/nvim/testdir/test_lispwords.vim @@ -0,0 +1,82 @@ +" Tests for 'lispwords' settings being global-local + +set nocompatible viminfo+=nviminfo + +func Test_global_local_lispwords() + setglobal lispwords=foo,bar,baz + setlocal lispwords-=foo | setlocal lispwords+=quux + call assert_equal('foo,bar,baz', &g:lispwords) + call assert_equal('bar,baz,quux', &l:lispwords) + call assert_equal('bar,baz,quux', &lispwords) + + setlocal lispwords< + call assert_equal('foo,bar,baz', &g:lispwords) + call assert_equal('foo,bar,baz', &l:lispwords) + call assert_equal('foo,bar,baz', &lispwords) +endfunc + +func Test_lisp_indent() + enew! + + call append(0, [ + \ '(defun html-file (base)', + \ '(format nil "~(~A~).html" base))', + \ '', + \ '(defmacro page (name title &rest body)', + \ '(let ((ti (gensym)))', + \ '`(with-open-file (*standard-output*', + \ '(html-file ,name)', + \ ':direction :output', + \ ':if-exists :supersede)', + \ '(let ((,ti ,title))', + \ '(as title ,ti)', + \ '(with center ', + \ '(as h2 (string-upcase ,ti)))', + \ '(brs 3)', + \ ',@body))))', + \ '', + \ ';;; Utilities for generating links', + \ '', + \ '(defmacro with-link (dest &rest body)', + \ '`(progn', + \ '(format t "<a href=\"~A\">" (html-file ,dest))', + \ ',@body', + \ '(princ "</a>")))' + \ ]) + set lisp + set lispwords& + let save_copt = &cpoptions + set cpoptions+=p + normal 1G=G + + call assert_equal([ + \ '(defun html-file (base)', + \ ' (format nil "~(~A~).html" base))', + \ '', + \ '(defmacro page (name title &rest body)', + \ ' (let ((ti (gensym)))', + \ ' `(with-open-file (*standard-output*', + \ ' (html-file ,name)', + \ ' :direction :output', + \ ' :if-exists :supersede)', + \ ' (let ((,ti ,title))', + \ ' (as title ,ti)', + \ ' (with center ', + \ ' (as h2 (string-upcase ,ti)))', + \ ' (brs 3)', + \ ' ,@body))))', + \ '', + \ ';;; Utilities for generating links', + \ '', + \ '(defmacro with-link (dest &rest body)', + \ ' `(progn', + \ ' (format t "<a href=\"~A\">" (html-file ,dest))', + \ ' ,@body', + \ ' (princ "</a>")))', + \ '' + \ ], getline(1, "$")) + + enew! + let &cpoptions=save_copt + set nolisp +endfunc diff --git a/src/nvim/testdir/test_listchars.vim b/src/nvim/testdir/test_listchars.vim new file mode 100644 index 0000000000..57ea7ca5a9 --- /dev/null +++ b/src/nvim/testdir/test_listchars.vim @@ -0,0 +1,63 @@ +" Tests for 'listchars' display with 'list' and :list + +source view_util.vim + +func Test_listchars() + enew! + set ff=unix + set list + + set listchars+=tab:>-,space:.,trail:< + call append(0, [ + \ ' aa ', + \ ' bb ', + \ ' cccc ', + \ 'dd ee ', + \ ' ' + \ ]) + let expected = [ + \ '>-------aa>-----$', + \ '..bb>---<<$', + \ '...cccc><$', + \ 'dd........ee<<>-$', + \ '<$' + \ ] + redraw! + for i in range(1, 5) + call cursor(i, 1) + call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$'))) + endfor + + set listchars-=trail:< + let expected = [ + \ '>-------aa>-----$', + \ '..bb>---..$', + \ '...cccc>.$', + \ 'dd........ee..>-$', + \ '.$' + \ ] + redraw! + for i in range(1, 5) + call cursor(i, 1) + call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$'))) + endfor + + set listchars+=trail:< + set nolist + normal ggdG + call append(0, [ + \ ' fff ', + \ ' gg ', + \ ' h ', + \ 'iii ', + \ ]) + let l = split(execute("%list"), "\n") + call assert_equal([ + \ '..fff>--<<$', + \ '>-------gg>-----$', + \ '.....h>-$', + \ 'iii<<<<><<$', '$'], l) + + enew! + set listchars& ff& +endfunc diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim new file mode 100644 index 0000000000..023332c90a --- /dev/null +++ b/src/nvim/testdir/test_listdict.vim @@ -0,0 +1,603 @@ +" Tests for the List and Dict types + +func TearDown() + " Run garbage collection after every test + call test_garbagecollect_now() +endfunc + +" Tests for List type + +" List creation +func Test_list_create() + " Creating List directly with different types + let l = [1, 'as''d', [1, 2, function("strlen")], {'a': 1},] + call assert_equal("[1, 'as''d', [1, 2, function('strlen')], {'a': 1}]", string(l)) + call assert_equal({'a' : 1}, l[-1]) + call assert_equal(1, l[-4]) + let x = 10 + try + let x = l[-5] + catch + call assert_match('E684:', v:exception) + endtry + call assert_equal(10, x) +endfunc + +" List slices +func Test_list_slice() + let l = [1, 'as''d', [1, 2, function("strlen")], {'a': 1},] + call assert_equal([1, 'as''d', [1, 2, function('strlen')], {'a': 1}], l[:]) + call assert_equal(['as''d', [1, 2, function('strlen')], {'a': 1}], l[1:]) + call assert_equal([1, 'as''d', [1, 2, function('strlen')]], l[:-2]) + call assert_equal([1, 'as''d', [1, 2, function('strlen')], {'a': 1}], l[0:8]) + call assert_equal([], l[8:-1]) +endfunc + +" List identity +func Test_list_identity() + let l = [1, 'as''d', [1, 2, function("strlen")], {'a': 1},] + let ll = l + let lx = copy(l) + call assert_true(l == ll) + call assert_false(l isnot ll) + call assert_true(l is ll) + call assert_true(l == lx) + call assert_false(l is lx) + call assert_true(l isnot lx) +endfunc + +" removing items with :unlet +func Test_list_unlet() + let l = [1, 'as''d', [1, 2, function("strlen")], {'a': 1},] + unlet l[2] + call assert_equal([1, 'as''d', {'a': 1}], l) + let l = range(8) + unlet l[:3] + unlet l[1:] + call assert_equal([4], l) + + " removing items out of range: silently skip items that don't exist + let l = [0, 1, 2, 3] + call assert_fails('unlet l[2:1]', 'E684') + let l = [0, 1, 2, 3] + unlet l[2:2] + call assert_equal([0, 1, 3], l) + let l = [0, 1, 2, 3] + unlet l[2:3] + call assert_equal([0, 1], l) + let l = [0, 1, 2, 3] + unlet l[2:4] + call assert_equal([0, 1], l) + let l = [0, 1, 2, 3] + unlet l[2:5] + call assert_equal([0, 1], l) + let l = [0, 1, 2, 3] + call assert_fails('unlet l[-1:2]', 'E684') + let l = [0, 1, 2, 3] + unlet l[-2:2] + call assert_equal([0, 1, 3], l) + let l = [0, 1, 2, 3] + unlet l[-3:2] + call assert_equal([0, 3], l) + let l = [0, 1, 2, 3] + unlet l[-4:2] + call assert_equal([3], l) + let l = [0, 1, 2, 3] + unlet l[-5:2] + call assert_equal([3], l) + let l = [0, 1, 2, 3] + unlet l[-6:2] + call assert_equal([3], l) +endfunc + +" assignment to a list +func Test_list_assign() + let l = [0, 1, 2, 3] + let [va, vb] = l[2:3] + call assert_equal([2, 3], [va, vb]) + call assert_fails('let [va, vb] = l', 'E687') + call assert_fails('let [va, vb] = l[1:1]', 'E688') +endfunc + +" test for range assign +func Test_list_range_assign() + let l = [0] + let l[:] = [1, 2] + call assert_equal([1, 2], l) +endfunc + +" Tests for Dictionary type + +func Test_dict() + " Creating Dictionary directly with different types + let d = {001: 'asd', 'b': [1, 2, function('strlen')], -1: {'a': 1},} + call assert_equal("{'1': 'asd', 'b': [1, 2, function('strlen')], '-1': {'a': 1}}", string(d)) + call assert_equal('asd', d.1) + call assert_equal(['-1', '1', 'b'], sort(keys(d))) + call assert_equal(['asd', [1, 2, function('strlen')], {'a': 1}], values(d)) + let v = [] + for [key, val] in items(d) + call extend(v, [key, val]) + unlet key val + endfor + call assert_equal(['1','asd','b',[1, 2, function('strlen')],'-1',{'a': 1}], v) + + call extend(d, {3:33, 1:99}) + call extend(d, {'b':'bbb', 'c':'ccc'}, "keep") + call assert_fails("call extend(d, {3:333,4:444}, 'error')", 'E737') + call assert_equal({'c': 'ccc', '1': 99, 'b': [1, 2, function('strlen')], '3': 33, '-1': {'a': 1}}, d) + call filter(d, 'v:key =~ ''[ac391]''') + call assert_equal({'c': 'ccc', '1': 99, '3': 33, '-1': {'a': 1}}, d) +endfunc + +" Dictionary identity +func Test_dict_identity() + let d = {001: 'asd', 'b': [1, 2, function('strlen')], -1: {'a': 1},} + let dd = d + let dx = copy(d) + call assert_true(d == dd) + call assert_false(d isnot dd) + call assert_true(d is dd) + call assert_true(d == dx) + call assert_false(d is dx) + call assert_true(d isnot dx) +endfunc + +" removing items with :unlet +func Test_dict_unlet() + let d = {'b':'bbb', '1': 99, '3': 33, '-1': {'a': 1}} + unlet d.b + unlet d[-1] + call assert_equal({'1': 99, '3': 33}, d) +endfunc + +" manipulating a big Dictionary (hashtable.c has a border of 1000 entries) +func Test_dict_big() + let d = {} + for i in range(1500) + let d[i] = 3000 - i + endfor + call assert_equal([3000, 2900, 2001, 1600, 1501], [d[0], d[100], d[999], d[1400], d[1499]]) + let str = '' + try + let n = d[1500] + catch + let str=substitute(v:exception, '\v(.{14}).*( \d{4}).*', '\1\2', '') + endtry + call assert_equal('Vim(let):E716: 1500', str) + + " lookup each items + for i in range(1500) + call assert_equal(3000 - i, d[i]) + endfor + let i += 1 + + " delete even items + while i >= 2 + let i -= 2 + unlet d[i] + endwhile + call assert_equal('NONE', get(d, 1500 - 100, 'NONE')) + call assert_equal(2999, d[1]) + + " delete odd items, checking value, one intentionally wrong + let d[33] = 999 + let i = 1 + while i < 1500 + if i != 33 + call assert_equal(3000 - i, d[i]) + else + call assert_equal(999, d[i]) + endif + unlet d[i] + let i += 2 + endwhile + call assert_equal({}, d) + unlet d +endfunc + +" Dictionary function +func Test_dict_func() + let d = {} + func d.func(a) dict + return a:a . len(self.data) + endfunc + let d.data = [1,2,3] + call assert_equal('len: 3', d.func('len: ')) + let x = d.func('again: ') + call assert_equal('again: 3', x) + let Fn = d.func + call assert_equal('xxx3', Fn('xxx')) +endfunc + +" Function in script-local List or Dict +func Test_script_local_dict_func() + let g:dict = {} + function g:dict.func() dict + return 'g:dict.func' . self.foo[1] . self.foo[0]('asdf') + endfunc + let g:dict.foo = ['-', 2, 3] + call insert(g:dict.foo, function('strlen')) + call assert_equal('g:dict.func-4', g:dict.func()) + unlet g:dict +endfunc + +" Nasty: remove func from Dict that's being called (works) +func Test_dict_func_remove_in_use() + let d = {1:1} + func d.func(a) + return "a:" . a:a + endfunc + let expected = 'a:' . string(get(d, 'func')) + call assert_equal(expected, d.func(string(remove(d, 'func')))) +endfunc + +" Nasty: deepcopy() dict that refers to itself (fails when noref used) +func Test_dict_deepcopy() + let d = {1:1, 2:2} + let l = [4, d, 6] + let d[3] = l + let dc = deepcopy(d) + call assert_fails('call deepcopy(d, 1)', 'E698') + let l2 = [0, l, l, 3] + let l[1] = l2 + let l3 = deepcopy(l2) + call assert_true(l3[1] is l3[2]) +endfunc + +" Locked variables +func Test_list_locked_var() + let expected = [ + \ [['0000-000', 'ppppppp'], + \ ['0000-000', 'ppppppp'], + \ ['0000-000', 'ppppppp']], + \ [['1000-000', 'ppppppF'], + \ ['0000-000', 'ppppppp'], + \ ['0000-000', 'ppppppp']], + \ [['1100-100', 'ppFppFF'], + \ ['0000-000', 'ppppppp'], + \ ['0000-000', 'ppppppp']], + \ [['1110-110', 'pFFpFFF'], + \ ['0010-010', 'pFppFpp'], + \ ['0000-000', 'ppppppp']], + \ [['1111-111', 'FFFFFFF'], + \ ['0011-011', 'FFpFFpp'], + \ ['0000-000', 'ppppppp']] + \ ] + for depth in range(5) + for u in range(3) + unlet! l + let l = [0, [1, [2, 3]], {4: 5, 6: {7: 8}}] + exe "lockvar " . depth . " l" + if u == 1 + exe "unlockvar l" + elseif u == 2 + exe "unlockvar " . depth . " l" + endif + let ps = islocked("l").islocked("l[1]").islocked("l[1][1]").islocked("l[1][1][0]").'-'.islocked("l[2]").islocked("l[2]['6']").islocked("l[2]['6'][7]") + call assert_equal(expected[depth][u][0], ps) + let ps = '' + try + let l[1][1][0] = 99 + let ps .= 'p' + catch + let ps .= 'F' + endtry + try + let l[1][1] = [99] + let ps .= 'p' + catch + let ps .= 'F' + endtry + try + let l[1] = [99] + let ps .= 'p' + catch + let ps .= 'F' + endtry + try + let l[2]['6'][7] = 99 + let ps .= 'p' + catch + let ps .= 'F' + endtry + try + let l[2][6] = {99: 99} + let ps .= 'p' + catch + let ps .= 'F' + endtry + try + let l[2] = {99: 99} + let ps .= 'p' + catch + let ps .= 'F' + endtry + try + let l = [99] + let ps .= 'p' + catch + let ps .= 'F' + endtry + call assert_equal(expected[depth][u][1], ps) + endfor + endfor +endfunc + +" Unletting locked variables +func Test_list_locked_var_unlet() + let expected = [ + \ [['0000-000', 'ppppppp'], + \ ['0000-000', 'ppppppp'], + \ ['0000-000', 'ppppppp']], + \ [['1000-000', 'ppFppFp'], + \ ['0000-000', 'ppppppp'], + \ ['0000-000', 'ppppppp']], + \ [['1100-100', 'pFFpFFp'], + \ ['0000-000', 'ppppppp'], + \ ['0000-000', 'ppppppp']], + \ [['1110-110', 'FFFFFFp'], + \ ['0010-010', 'FppFppp'], + \ ['0000-000', 'ppppppp']], + \ [['1111-111', 'FFFFFFp'], + \ ['0011-011', 'FppFppp'], + \ ['0000-000', 'ppppppp']] + \ ] + + for depth in range(5) + for u in range(3) + unlet! l + let l = [0, [1, [2, 3]], {4: 5, 6: {7: 8}}] + exe "lockvar " . depth . " l" + if u == 1 + exe "unlockvar l" + elseif u == 2 + exe "unlockvar " . depth . " l" + endif + let ps = islocked("l").islocked("l[1]").islocked("l[1][1]").islocked("l[1][1][0]").'-'.islocked("l[2]").islocked("l[2]['6']").islocked("l[2]['6'][7]") + call assert_equal(expected[depth][u][0], ps) + let ps = '' + try + unlet l[2]['6'][7] + let ps .= 'p' + catch + let ps .= 'F' + endtry + try + unlet l[2][6] + let ps .= 'p' + catch + let ps .= 'F' + endtry + try + unlet l[2] + let ps .= 'p' + catch + let ps .= 'F' + endtry + try + unlet l[1][1][0] + let ps .= 'p' + catch + let ps .= 'F' + endtry + try + unlet l[1][1] + let ps .= 'p' + catch + let ps .= 'F' + endtry + try + unlet l[1] + let ps .= 'p' + catch + let ps .= 'F' + endtry + try + unlet l + let ps .= 'p' + catch + let ps .= 'F' + endtry + call assert_equal(expected[depth][u][1], ps) + endfor + endfor +endfunc + +" Locked variables and :unlet or list / dict functions + +" No :unlet after lock on dict: +func Test_dict_lock_unlet() + unlet! d + let d = {'a': 99, 'b': 100} + lockvar 1 d + call assert_fails('unlet d.a', 'E741') +endfunc + +" unlet after lock on dict item +func Test_dict_item_lock_unlet() + unlet! d + let d = {'a': 99, 'b': 100} + lockvar d.a + unlet d.a + call assert_equal({'b' : 100}, d) +endfunc + +" filter() after lock on dict item +func Test_dict_lock_filter() + unlet! d + let d = {'a': 99, 'b': 100} + lockvar d.a + call filter(d, 'v:key != "a"') + call assert_equal({'b' : 100}, d) +endfunc + +" map() after lock on dict +func Test_dict_lock_map() + unlet! d + let d = {'a': 99, 'b': 100} + lockvar 1 d + call map(d, 'v:val + 200') + call assert_equal({'a' : 299, 'b' : 300}, d) +endfunc + +" No extend() after lock on dict item +func Test_dict_lock_extend() + unlet! d + let d = {'a': 99, 'b': 100} + lockvar d.a + call assert_fails("call extend(d, {'a' : 123})", 'E741') + call assert_equal({'a': 99, 'b': 100}, d) +endfunc + +" No remove() of write-protected scope-level variable +func! Tfunc(this_is_a_long_parameter_name) + call assert_fails("call remove(a:, 'this_is_a_long_parameter_name')", 'E795') +endfun +func Test_dict_scope_var_remove() + call Tfunc('testval') +endfunc + +" No extend() of write-protected scope-level variable +func! Tfunc(this_is_a_long_parameter_name) + call assert_fails("call extend(a:, {'this_is_a_long_parameter_name': 1234})", 'E742') +endfunc +func Test_dict_scope_var_extend() + call Tfunc('testval') +endfunc + +" No :unlet of variable in locked scope +func Test_lock_var_unlet() + let b:testvar = 123 + lockvar 1 b: + call assert_fails('unlet b:testvar', 'E741:') + unlockvar 1 b: + unlet! b:testvar +endfunc + +" No :let += of locked list variable +func Test_let_lock_list() + let l = ['a', 'b', 3] + lockvar 1 l + call assert_fails("let l += ['x']", 'E741:') + call assert_equal(['a', 'b', 3], l) + + unlet l + let l = [1, 2, 3, 4] + lockvar! l + call assert_equal([1, 2, 3, 4], l) + unlockvar l[1] + call assert_fails('unlet l[0:1]', 'E741:') + call assert_equal([1, 2, 3, 4], l) + call assert_fails('unlet l[1:2]', 'E741:') + call assert_equal([1, 2, 3, 4], l) + unlockvar l[1] + call assert_fails('let l[0:1] = [0, 1]', 'E741:') + call assert_equal([1, 2, 3, 4], l) + call assert_fails('let l[1:2] = [0, 1]', 'E741:') + call assert_equal([1, 2, 3, 4], l) + unlet l +endfunc + +" lockvar/islocked() triggering script autoloading +func Test_lockvar_script_autoload() + let old_rtp = &rtp + set rtp+=./sautest + lockvar g:footest#x + unlockvar g:footest#x + call assert_equal(-1, islocked('g:footest#x')) + call assert_equal(0, exists('g:footest#x')) + call assert_equal(1, g:footest#x) + let &rtp = old_rtp +endfunc + +" a:000 function argument test +func s:arg_list_test(...) + call assert_fails('let a:000 = [1, 2]', 'E46:') + call assert_fails('let a:000[0] = 9', 'E742:') + call assert_fails('let a:000[2] = [9, 10]', 'E742:') + call assert_fails('let a:000[3] = {9 : 10}', 'E742:') + + " now the tests that should pass + let a:000[2][1] = 9 + call extend(a:000[2], [5, 6]) + let a:000[3][5] = 8 + let a:000[3]['a'] = 12 + call assert_equal([1, 2, [3, 9, 5, 6], {'a': 12, '5': 8}], a:000) +endfunc + +func Test_func_arg_list() + call s:arg_list_test(1, 2, [3, 4], {5: 6}) +endfunc + +" Tests for reverse(), sort(), uniq() +func Test_reverse_sort_uniq() + let l = ['-0', 'A11', 2, 2, 'xaaa', 4, 'foo', 'foo6', 'foo', [0, 1, 2], 'x8', [0, 1, 2], 1.5] + call assert_equal(['-0', 'A11', 2, 'xaaa', 4, 'foo', 'foo6', 'foo', [0, 1, 2], 'x8', [0, 1, 2], 1.5], uniq(copy(l))) + call assert_equal([1.5, [0, 1, 2], 'x8', [0, 1, 2], 'foo', 'foo6', 'foo', 4, 'xaaa', 2, 2, 'A11', '-0'], reverse(l)) + call assert_equal([1.5, [0, 1, 2], 'x8', [0, 1, 2], 'foo', 'foo6', 'foo', 4, 'xaaa', 2, 2, 'A11', '-0'], reverse(reverse(l))) + call assert_equal(['-0', 'A11', 'foo', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 2, 4, [0, 1, 2], [0, 1, 2]], sort(l)) + call assert_equal([[0, 1, 2], [0, 1, 2], 4, 2, 2, 1.5, 'xaaa', 'x8', 'foo6', 'foo', 'foo', 'A11', '-0'], reverse(sort(l))) + call assert_equal(['-0', 'A11', 'foo', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 2, 4, [0, 1, 2], [0, 1, 2]], sort(reverse(sort(l)))) + call assert_equal(['-0', 'A11', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 4, [0, 1, 2]], uniq(sort(l))) + + let l=[7, 9, 'one', 18, 12, 22, 'two', 10.0e-16, -1, 'three', 0xff, 0.22, 'four'] + call assert_equal([-1, 'one', 'two', 'three', 'four', 1.0e-15, 0.22, 7, 9, 12, 18, 22, 255], sort(copy(l), 'n')) + + let l=[7, 9, 18, 12, 22, 10.0e-16, -1, 0xff, 0, -0, 0.22, 'bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', {}, []] + call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 1)) + call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 'i')) + call assert_equal(['BAR', 'Bar', 'FOO', 'FOOBAR', 'Foo', 'bar', 'foo', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l))) +endfunc + +" splitting a string to a List +func Test_str_split() + call assert_equal(['aa', 'bb'], split(' aa bb ')) + call assert_equal(['aa', 'bb'], split(' aa bb ', '\W\+', 0)) + call assert_equal(['', 'aa', 'bb', ''], split(' aa bb ', '\W\+', 1)) + call assert_equal(['', '', 'aa', '', 'bb', '', ''], split(' aa bb ', '\W', 1)) + call assert_equal(['aa', '', 'bb'], split(':aa::bb:', ':', 0)) + call assert_equal(['', 'aa', '', 'bb', ''], split(':aa::bb:', ':', 1)) + call assert_equal(['aa', '', 'bb', 'cc', ''], split('aa,,bb, cc,', ',\s*', 1)) + call assert_equal(['a', 'b', 'c'], split('abc', '\zs')) + call assert_equal(['', 'a', '', 'b', '', 'c', ''], split('abc', '\zs', 1)) +endfunc + +" compare recursively linked list and dict +func Test_listdict_compare() + let l = [1, 2, 3, 4] + let d = {'1': 1, '2': l, '3': 3} + let l[1] = d + call assert_true(l == l) + call assert_true(d == d) + call assert_false(l != deepcopy(l)) + call assert_false(d != deepcopy(d)) +endfunc + + " compare complex recursively linked list and dict +func Test_listdict_compare_complex() + let l = [] + call add(l, l) + let dict4 = {"l": l} + call add(dict4.l, dict4) + let lcopy = deepcopy(l) + let dict4copy = deepcopy(dict4) + call assert_true(l == lcopy) + call assert_true(dict4 == dict4copy) +endfunc + +func Test_listdict_extend() + " Pass the same List to extend() + let l = [1, 2, 3, 4, 5] + call extend(l, l) + call assert_equal([1, 2, 3, 4, 5, 1, 2, 3, 4, 5], l) + + " Pass the same Dict to extend() + let d = { 'a': {'b': 'B'}} + call extend(d, d) + call assert_equal({'a': {'b': 'B'}}, d) + + " Pass the same Dict to extend() with "error" + call assert_fails("call extend(d, d, 'error')", 'E737:') + call assert_equal({'a': {'b': 'B'}}, d) +endfunc diff --git a/src/nvim/testdir/test_listlbr.vim b/src/nvim/testdir/test_listlbr.vim index 7856ee82ab..d28dbc444c 100644 --- a/src/nvim/testdir/test_listlbr.vim +++ b/src/nvim/testdir/test_listlbr.vim @@ -1,5 +1,8 @@ " Test for linebreak and list option (non-utf8) +" Nvim does not allow setting 'encoding', so skip this test. +finish + set encoding=latin1 scriptencoding latin1 diff --git a/src/nvim/testdir/test_listlbr_utf8.vim b/src/nvim/testdir/test_listlbr_utf8.vim index 980d67d49d..56a4cc9b31 100644 --- a/src/nvim/testdir/test_listlbr_utf8.vim +++ b/src/nvim/testdir/test_listlbr_utf8.vim @@ -194,6 +194,33 @@ func Test_multibyte_sign_and_colorcolumn() call s:close_windows() endfunc +func Test_illegal_byte_and_breakat() + call s:test_windows("setl sbr= brk+=<") + vert resize 18 + call setline(1, repeat("\x80", 6)) + redraw! + let lines = s:screen_lines([1, 2], winwidth(0)) + let expect = [ +\ "<80><80><80><80><8", +\ "0><80> ", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows('setl brk&vim') +endfunc + +func Test_multibyte_wrap_and_breakat() + call s:test_windows("setl sbr= brk+=>") + call setline(1, repeat('a', 17) . repeat('ã‚', 2)) + redraw! + let lines = s:screen_lines([1, 2], winwidth(0)) + let expect = [ +\ "aaaaaaaaaaaaaaaaaã‚>", +\ "ã‚ ", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows('setl brk&vim') +endfunc + func Test_chinese_char_on_wrap_column() call s:test_windows("setl nolbr wrap sbr=") syntax off diff --git a/src/nvim/testdir/test_makeencoding.py b/src/nvim/testdir/test_makeencoding.py new file mode 100644 index 0000000000..041edadc0a --- /dev/null +++ b/src/nvim/testdir/test_makeencoding.py @@ -0,0 +1,67 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Test program for :make, :grep and :cgetfile. + +from __future__ import print_function, unicode_literals +import locale +import io +import sys + +def set_output_encoding(enc=None): + """Set the encoding of stdout and stderr + + arguments: + enc -- Encoding name. + If omitted, locale.getpreferredencoding() is used. + """ + if enc is None: + enc = locale.getpreferredencoding() + + def get_text_writer(fo, **kwargs): + kw = dict(kwargs) + kw.setdefault('errors', 'backslashreplace') # use \uXXXX style + kw.setdefault('closefd', False) + + if sys.version_info[0] < 3: + # Work around for Python 2.x + # New line conversion isn't needed here. Done in somewhere else. + writer = io.open(fo.fileno(), mode='w', newline='', **kw) + write = writer.write # save the original write() function + enc = locale.getpreferredencoding() + def convwrite(s): + if isinstance(s, bytes): + write(s.decode(enc)) # convert to unistr + else: + write(s) + try: + writer.flush() # needed on Windows + except IOError: + pass + writer.write = convwrite + else: + writer = io.open(fo.fileno(), mode='w', **kw) + return writer + + sys.stdout = get_text_writer(sys.stdout, encoding=enc) + sys.stderr = get_text_writer(sys.stderr, encoding=enc) + + +def main(): + enc = 'utf-8' + if len(sys.argv) > 1: + enc = sys.argv[1] + set_output_encoding(enc) + + message_tbl = { + 'utf-8': 'ÀÈÌÒÙ ã“ã‚“ã«ã¡ã¯ ä½ å¥½', + 'latin1': 'ÀÈÌÒÙ', + 'cp932': 'ã“ã‚“ã«ã¡ã¯', + 'cp936': 'ä½ å¥½', + } + + print('Xfoobar.c(10) : %s (%s)' % (message_tbl[enc], enc)) + + +if __name__ == "__main__": + main() diff --git a/src/nvim/testdir/test_makeencoding.vim b/src/nvim/testdir/test_makeencoding.vim new file mode 100644 index 0000000000..a3d5538a47 --- /dev/null +++ b/src/nvim/testdir/test_makeencoding.vim @@ -0,0 +1,106 @@ +" Tests for 'makeencoding'. +if !has('multi_byte') + finish +endif + +source shared.vim + +let s:python = PythonProg() +if s:python == '' + " Can't run this test. + finish +endif + +let s:script = 'test_makeencoding.py' + +let s:message_tbl = { + \ 'utf-8': 'ÀÈÌÒÙ ã“ã‚“ã«ã¡ã¯ ä½ å¥½', + \ 'latin1': 'ÀÈÌÒÙ', + \ 'cp932': 'ã“ã‚“ã«ã¡ã¯', + \ 'cp936': 'ä½ å¥½', + \} + + +" Tests for :cgetfile and :lgetfile. +func Test_getfile() + set errorfile=Xerror.txt + set errorformat=%f(%l)\ :\ %m + + " :cgetfile + for enc in keys(s:message_tbl) + let &makeencoding = enc + exec "silent !" . s:python . " " . s:script . " " . enc . " > " . &errorfile + cgetfile + copen + call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", + \ getline('.')) + cclose + endfor + + " :lgetfile + for enc in keys(s:message_tbl) + let &makeencoding = enc + exec "silent !" . s:python . " " . s:script . " " . enc . " > " . &errorfile + lgetfile + lopen + call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", + \ getline('.')) + lclose + endfor + + call delete(&errorfile) +endfunc + + +" Tests for :grep and :lgrep. +func Test_grep() + let &grepprg = s:python + set grepformat=%f(%l)\ :\ %m + + " :grep + for enc in keys(s:message_tbl) + let &makeencoding = enc + exec "silent grep! " . s:script . " " . enc + copen + call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", + \ getline('.')) + cclose + endfor + + " :lgrep + for enc in keys(s:message_tbl) + let &makeencoding = enc + exec "silent lgrep! " . s:script . " " . enc + lopen + call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", + \ getline('.')) + lclose + endfor +endfunc + + +" Tests for :make and :lmake. +func Test_make() + let &makeprg = s:python + set errorformat=%f(%l)\ :\ %m + + " :make + for enc in keys(s:message_tbl) + let &makeencoding = enc + exec "silent make! " . s:script . " " . enc + copen + call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", + \ getline('.')) + cclose + endfor + + " :lmake + for enc in keys(s:message_tbl) + let &makeencoding = enc + exec "silent lmake! " . s:script . " " . enc + lopen + call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", + \ getline('.')) + lclose + endfor +endfunc diff --git a/src/nvim/testdir/test_mksession_utf8.vim b/src/nvim/testdir/test_mksession_utf8.vim index c05a1d3b6d..8ffbba2a1c 100644 --- a/src/nvim/testdir/test_mksession_utf8.vim +++ b/src/nvim/testdir/test_mksession_utf8.vim @@ -99,6 +99,7 @@ func Test_mksession_utf8() call delete('test_mks.out') call delete(tmpfile) let &wrap = wrap_save + set sessionoptions& splitbelow& fileencoding& endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index 4747d5704d..1d15c7f83d 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -1,5 +1,7 @@ " Test for various Normal mode commands +source shared.vim + func! Setup_NewWindow() 10new call setline(1, range(1,100)) @@ -1069,7 +1071,6 @@ func! Test_normal18_z_fold() endfunc func! Test_normal19_z_spell() - throw "skipped: Nvim 'spell' requires download" if !has("spell") || !has('syntax') return endif @@ -1120,6 +1121,7 @@ func! Test_normal19_z_spell() let a=execute('unsilent norm! V$zG') call assert_match("Word '2 goood' added to .*", a) let fname=matchstr(a, 'to\s\+\zs\f\+$') + let fname=Fix_truncated_tmpfile(fname) let cnt=readfile(fname) call assert_equal('2 goood', cnt[0]) @@ -2335,3 +2337,45 @@ func! Test_normal54_Ctrl_bsl() " clean up bw! endfunc + +" Test for the gr (virtual replace) command +" Test for the bug fixed by 7.4.387 +func Test_gr_command() + enew! + let save_cpo = &cpo + call append(0, ['First line', 'Second line', 'Third line']) + exe "normal i\<C-G>u" + call cursor(2, 1) + set cpo-=X + normal 4gro + call assert_equal('oooond line', getline(2)) + undo + set cpo+=X + normal 4gro + call assert_equal('ooooecond line', getline(2)) + let &cpo = save_cpo + enew! +endfunc + +" When splitting a window the changelist position is wrong. +" Test the changelist position after splitting a window. +" Test for the bug fixed by 7.4.386 +func Test_changelist() + let save_ul = &ul + enew! + call append('$', ['1', '2']) + exe "normal i\<C-G>u" + exe "normal Gkylpa\<C-G>u" + set ul=100 + exe "normal Gylpa\<C-G>u" + set ul=100 + normal gg + vsplit + normal g; + call assert_equal([3, 2], [line('.'), col('.')]) + normal g; + call assert_equal([2, 2], [line('.'), col('.')]) + call assert_fails('normal g;', 'E662:') + %bwipe! + let &ul = save_ul +endfunc diff --git a/src/nvim/testdir/test_number.vim b/src/nvim/testdir/test_number.vim new file mode 100644 index 0000000000..59debcea0d --- /dev/null +++ b/src/nvim/testdir/test_number.vim @@ -0,0 +1,254 @@ +" Test for 'number' and 'relativenumber' + +source view_util.vim + +func! s:screen_lines(start, end) abort + return ScreenLines([a:start, a:end], 8) +endfunc + +func! s:compare_lines(expect, actual) + call assert_equal(a:expect, a:actual) +endfunc + +func! s:test_windows(h, w) abort + call NewWindow(a:h, a:w) +endfunc + +func! s:close_windows() abort + call CloseWindow() +endfunc + +func! s:validate_cursor() abort + " update skipcol. + " wincol(): + " f_wincol + " -> validate_cursor + " -> curs_columns + call wincol() +endfunc + +func Test_set_options() + set nu rnu + call assert_equal(1, &nu) + call assert_equal(1, &rnu) + + call s:test_windows(10, 20) + call assert_equal(1, &nu) + call assert_equal(1, &rnu) + call s:close_windows() + + set nu& rnu& +endfunc + +func Test_set_global_and_local() + " setlocal must NOT reset the other global value + set nonu nornu + setglobal nu + setlocal rnu + call assert_equal(1, &g:nu) + + set nonu nornu + setglobal rnu + setlocal nu + call assert_equal(1, &g:rnu) + + " setglobal MUST reset the other global value + set nonu nornu + setglobal nu + setglobal rnu + call assert_equal(1, &g:nu) + + set nonu nornu + setglobal rnu + setglobal nu + call assert_equal(1, &g:rnu) + + " set MUST reset the other global value + set nonu nornu + set nu + set rnu + call assert_equal(1, &g:nu) + + set nonu nornu + set rnu + set nu + call assert_equal(1, &g:rnu) + + set nu& rnu& +endfunc + +func Test_number() + call s:test_windows(10, 20) + call setline(1, ["abcdefghij", "klmnopqrst", "uvwxyzABCD", "EFGHIJKLMN", "OPQRSTUVWX", "YZ"]) + setl number + let lines = s:screen_lines(1, 4) + let expect = [ +\ " 1 abcd", +\ " 2 klmn", +\ " 3 uvwx", +\ " 4 EFGH", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows() +endfunc + +func Test_relativenumber() + call s:test_windows(10, 20) + call setline(1, ["abcdefghij", "klmnopqrst", "uvwxyzABCD", "EFGHIJKLMN", "OPQRSTUVWX", "YZ"]) + 3 + setl relativenumber + let lines = s:screen_lines(1, 6) + let expect = [ +\ " 2 abcd", +\ " 1 klmn", +\ " 0 uvwx", +\ " 1 EFGH", +\ " 2 OPQR", +\ " 3 YZ ", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows() +endfunc + +func Test_number_with_relativenumber() + call s:test_windows(10, 20) + call setline(1, ["abcdefghij", "klmnopqrst", "uvwxyzABCD", "EFGHIJKLMN", "OPQRSTUVWX", "YZ"]) + 4 + setl number relativenumber + let lines = s:screen_lines(1, 6) + let expect = [ +\ " 3 abcd", +\ " 2 klmn", +\ " 1 uvwx", +\ "4 EFGH", +\ " 1 OPQR", +\ " 2 YZ ", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows() +endfunc + +func Test_number_with_linewrap1() + call s:test_windows(3, 20) + normal! 61ia + setl number wrap + call s:validate_cursor() + let lines = s:screen_lines(1, 3) + let expect = [ +\ "--1 aaaa", +\ " aaaa", +\ " aaaa", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows() +endfunc + +" Pending: https://groups.google.com/forum/#!topic/vim_dev/tzNKP7EDWYI +func XTest_number_with_linewrap2() + call s:test_windows(3, 20) + normal! 61ia + setl number wrap + call s:validate_cursor() + 0 + call s:validate_cursor() + let lines = s:screen_lines(1, 3) + let expect = [ +\ " 1 aaaa", +\ " aaaa", +\ " aaaa", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows() +endfunc + +" Pending: https://groups.google.com/forum/#!topic/vim_dev/tzNKP7EDWYI +func XTest_number_with_linewrap3() + call s:test_windows(4, 20) + normal! 81ia + setl number wrap + call s:validate_cursor() + setl nonumber + call s:validate_cursor() + let lines = s:screen_lines(1, 4) + let expect = [ +\ "aaaaaaaa", +\ "aaaaaaaa", +\ "aaaaaaaa", +\ "a ", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows() +endfunc + +func Test_numberwidth() + call s:test_windows(10, 20) + call setline(1, repeat(['aaaa'], 10)) + setl number numberwidth=6 + let lines = s:screen_lines(1, 3) + let expect = [ +\ " 1 aa", +\ " 2 aa", +\ " 3 aa", +\ ] + call s:compare_lines(expect, lines) + + set relativenumber + let lines = s:screen_lines(1, 3) + let expect = [ +\ "1 aa", +\ " 1 aa", +\ " 2 aa", +\ ] + call s:compare_lines(expect, lines) + + set nonumber + let lines = s:screen_lines(1, 3) + let expect = [ +\ " 0 aa", +\ " 1 aa", +\ " 2 aa", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows() +endfunc + +func Test_numberwidth_adjusted() + call s:test_windows(10, 20) + call setline(1, repeat(['aaaa'], 10000)) + setl number numberwidth=4 + let lines = s:screen_lines(1, 3) + let expect = [ +\ " 1 aa", +\ " 2 aa", +\ " 3 aa", +\ ] + call s:compare_lines(expect, lines) + + $ + let lines = s:screen_lines(8, 10) + let expect = [ +\ " 9998 aa", +\ " 9999 aa", +\ "10000 aa", +\ ] + call s:compare_lines(expect, lines) + + setl relativenumber + let lines = s:screen_lines(8, 10) + let expect = [ +\ " 2 aa", +\ " 1 aa", +\ "10000 aa", +\ ] + call s:compare_lines(expect, lines) + + setl nonumber + let lines = s:screen_lines(8, 10) + let expect = [ +\ " 2 aaaa", +\ " 1 aaaa", +\ " 0 aaaa", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows() +endfunc diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index 08ee00e352..a15d15213a 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -119,12 +119,109 @@ func Check_dir_option(name) call assert_fails("set " . a:name . "=/not.*there", "E474:") endfunc +func Test_cinkeys() + " This used to cause invalid memory access + set cindent cinkeys=0 + norm a + set cindent& cinkeys& +endfunc + func Test_dictionary() call Check_dir_option('dictionary') endfunc func Test_thesaurus() call Check_dir_option('thesaurus') +endfun + +func Test_set_completion() + call feedkeys(":set di\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set dictionary diff diffexpr diffopt digraph directory display', @:) + + " Expand boolan options. When doing :set no<Tab> + " vim displays the options names without "no" but completion uses "no...". + call feedkeys(":set nodi\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set nodiff digraph', @:) + + call feedkeys(":set invdi\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set invdiff digraph', @:) + + " Expand abbreviation of options. + call feedkeys(":set ts\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set tabstop thesaurus', @:) + + " Expand current value + call feedkeys(":set fileencodings=\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set fileencodings=ucs-bom,utf-8,default,latin1', @:) + + call feedkeys(":set fileencodings:\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set fileencodings:ucs-bom,utf-8,default,latin1', @:) + + " Expand directories. + call feedkeys(":set cdpath=./\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_match('./samples/ ', @:) + call assert_notmatch('./small.vim ', @:) + + " Expand files and directories. + call feedkeys(":set tags=./\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_match('./samples/ ./sautest/ ./setup.vim ./shared.vim', @:) + + call feedkeys(":set tags=./\\\\ dif\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set tags=./\\ diff diffexpr diffopt', @:) +endfunc + +func Test_set_errors() + call assert_fails('set scroll=-1', 'E49:') + call assert_fails('set backupcopy=', 'E474:') + call assert_fails('set regexpengine=3', 'E474:') + call assert_fails('set history=10001', 'E474:') + call assert_fails('set numberwidth=11', 'E474:') + call assert_fails('set colorcolumn=-a') + call assert_fails('set colorcolumn=a') + call assert_fails('set colorcolumn=1,') + call assert_fails('set cmdheight=-1', 'E487:') + call assert_fails('set cmdwinheight=-1', 'E487:') + if has('conceal') + call assert_fails('set conceallevel=-1', 'E487:') + call assert_fails('set conceallevel=4', 'E474:') + endif + call assert_fails('set helpheight=-1', 'E487:') + call assert_fails('set history=-1', 'E487:') + call assert_fails('set report=-1', 'E487:') + call assert_fails('set shiftwidth=-1', 'E487:') + call assert_fails('set sidescroll=-1', 'E487:') + call assert_fails('set tabstop=-1', 'E487:') + call assert_fails('set textwidth=-1', 'E487:') + call assert_fails('set timeoutlen=-1', 'E487:') + call assert_fails('set updatecount=-1', 'E487:') + call assert_fails('set updatetime=-1', 'E487:') + call assert_fails('set winheight=-1', 'E487:') + call assert_fails('set tabstop!', 'E488:') + call assert_fails('set xxx', 'E518:') + call assert_fails('set beautify?', 'E518:') + call assert_fails('set undolevels=x', 'E521:') + call assert_fails('set tabstop=', 'E521:') + call assert_fails('set comments=-', 'E524:') + call assert_fails('set comments=a', 'E525:') + call assert_fails('set foldmarker=x', 'E536:') + 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. + call assert_fails('set guicursor=i-ci,r-cr:h', 'E545:') + call assert_fails('set guicursor=i-ci', 'E545:') + call assert_fails('set guicursor=x', 'E545:') + call assert_fails('set guicursor=r-cr:horx', 'E548:') + call assert_fails('set guicursor=r-cr:hor0', 'E549:') + endif + call assert_fails('set backupext=~ patchmode=~', 'E589:') + call assert_fails('set winminheight=10 winheight=9', 'E591:') + call assert_fails('set winminwidth=10 winwidth=9', 'E592:') + call assert_fails("set showbreak=\x01", 'E595:') + call assert_fails('set t_foo=', 'E846:') endfunc func Test_complete() diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index e1ba142d1c..f5bdd717dd 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -570,4 +570,42 @@ func Test_completion_clear_candidate_list() endfunc +func Test_popup_and_preview_autocommand() + " This used to crash Vim + if !has('python') + return + endif + let h = winheight(0) + if h < 15 + return + endif + new + augroup MyBufAdd + au! + au BufAdd * nested tab sball + augroup END + set omnifunc=pythoncomplete#Complete + call setline(1, 'import os') + " make the line long + call setline(2, ' os.') + $ + call feedkeys("A\<C-X>\<C-O>\<C-N>\<C-N>\<C-N>\<enter>\<esc>", 'tx') + call assert_equal("import os", getline(1)) + call assert_match(' os.\(EX_IOERR\|O_CREAT\)$', getline(2)) + call assert_equal(1, winnr('$')) + " previewwindow option is not set + call assert_equal(0, &previewwindow) + norm! gt + call assert_equal(0, &previewwindow) + norm! gT + call assert_equal(12, tabpagenr('$')) + tabonly + pclose + augroup MyBufAdd + au! + augroup END + augroup! MyBufAdd + bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_put.vim b/src/nvim/testdir/test_put.vim new file mode 100644 index 0000000000..38c812bc9c --- /dev/null +++ b/src/nvim/testdir/test_put.vim @@ -0,0 +1,36 @@ + +func Test_put_block() + if !has('multi_byte') + return + endif + new + call feedkeys("i\<C-V>u2500\<CR>x\<ESC>", 'x') + call feedkeys("\<C-V>y", 'x') + call feedkeys("gg0p", 'x') + call assert_equal("\u2500x", getline(1)) + bwipe! +endfunc + +func Test_put_char_block() + new + call setline(1, ['Line 1', 'Line 2']) + f Xfile_put + " visually select both lines and put the cursor at the top of the visual + " selection and then put the buffer name over it + exe "norm! G0\<c-v>ke\"%p" + call assert_equal(['Xfile_put 1', 'Xfile_put 2'], getline(1,2)) + bw! +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)) + " clean up + bw! + call setreg('a', a[0], a[1]) +endfunc diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index aff5fc2eed..dd177fd633 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -24,19 +24,21 @@ func s:setup_commands(cchar) command! -nargs=* Xgetbuffer <mods>cgetbuffer <args> command! -nargs=* Xaddbuffer <mods>caddbuffer <args> command! -nargs=* Xrewind <mods>crewind <args> - command! -nargs=* -bang Xnext <mods>cnext<bang> <args> - command! -nargs=* -bang Xprev <mods>cprev<bang> <args> + command! -count -nargs=* -bang Xnext <mods><count>cnext<bang> <args> + command! -count -nargs=* -bang Xprev <mods><count>cprev<bang> <args> command! -nargs=* -bang Xfirst <mods>cfirst<bang> <args> command! -nargs=* -bang Xlast <mods>clast<bang> <args> command! -nargs=* -bang Xnfile <mods>cnfile<bang> <args> command! -nargs=* -bang Xpfile <mods>cpfile<bang> <args> command! -nargs=* Xexpr <mods>cexpr <args> - command! -nargs=* Xvimgrep <mods>vimgrep <args> + command! -range -nargs=* Xvimgrep <mods><count>vimgrep <args> + command! -nargs=* Xvimgrepadd <mods>vimgrepadd <args> command! -nargs=* Xgrep <mods> grep <args> command! -nargs=* Xgrepadd <mods> grepadd <args> command! -nargs=* Xhelpgrep helpgrep <args> let g:Xgetlist = function('getqflist') let g:Xsetlist = function('setqflist') + call setqflist([], 'f') else command! -nargs=* -bang Xlist <mods>llist<bang> <args> command! -nargs=* Xgetexpr <mods>lgetexpr <args> @@ -54,19 +56,21 @@ func s:setup_commands(cchar) command! -nargs=* Xgetbuffer <mods>lgetbuffer <args> command! -nargs=* Xaddbuffer <mods>laddbuffer <args> command! -nargs=* Xrewind <mods>lrewind <args> - command! -nargs=* -bang Xnext <mods>lnext<bang> <args> - command! -nargs=* -bang Xprev <mods>lprev<bang> <args> + command! -count -nargs=* -bang Xnext <mods><count>lnext<bang> <args> + command! -count -nargs=* -bang Xprev <mods><count>lprev<bang> <args> command! -nargs=* -bang Xfirst <mods>lfirst<bang> <args> command! -nargs=* -bang Xlast <mods>llast<bang> <args> command! -nargs=* -bang Xnfile <mods>lnfile<bang> <args> command! -nargs=* -bang Xpfile <mods>lpfile<bang> <args> command! -nargs=* Xexpr <mods>lexpr <args> - command! -nargs=* Xvimgrep <mods>lvimgrep <args> + command! -range -nargs=* Xvimgrep <mods><count>lvimgrep <args> + command! -nargs=* Xvimgrepadd <mods>lvimgrepadd <args> command! -nargs=* Xgrep <mods> lgrep <args> command! -nargs=* Xgrepadd <mods> lgrepadd <args> command! -nargs=* Xhelpgrep lhelpgrep <args> let g:Xgetlist = function('getloclist', [0]) let g:Xsetlist = function('setloclist', [0]) + call setloclist(0, [], 'f') endif endfunc @@ -74,6 +78,9 @@ endfunc func XlistTests(cchar) call s:setup_commands(a:cchar) + if a:cchar == 'l' + call assert_fails('llist', 'E776:') + endif " With an empty list, command should return error Xgetexpr [] silent! Xlist @@ -85,49 +92,52 @@ func XlistTests(cchar) \ 'non-error 3', 'Xtestfile3:3:1:Line3'] " List only valid entries - redir => result - Xlist - redir END - let l = split(result, "\n") + let l = split(execute('Xlist', ''), "\n") call assert_equal([' 2 Xtestfile1:1 col 3: Line1', \ ' 4 Xtestfile2:2 col 2: Line2', \ ' 6 Xtestfile3:3 col 1: Line3'], l) " List all the entries - redir => result - Xlist! - redir END - let l = split(result, "\n") + 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) " List a range of errors - redir => result - Xlist 3,6 - redir END - let l = split(result, "\n") + let l = split(execute('Xlist 3,6', ''), "\n") call assert_equal([' 4 Xtestfile2:2 col 2: Line2', \ ' 6 Xtestfile3:3 col 1: Line3'], l) - redir => result - Xlist! 3,4 - redir END - let l = split(result, "\n") + let l = split(execute('Xlist! 3,4', ''), "\n") call assert_equal([' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l) - redir => result - Xlist -6,-4 - redir END - let l = split(result, "\n") + let l = split(execute('Xlist -6,-4', ''), "\n") call assert_equal([' 2 Xtestfile1:1 col 3: Line1'], l) - redir => result - Xlist! -5,-3 - redir END - let l = split(result, "\n") + let l = split(execute('Xlist! -5,-3', ''), "\n") call assert_equal([' 2 Xtestfile1:1 col 3: Line1', \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l) + + " Test for '+' + let l = split(execute('Xlist! +2', ''), "\n") + call assert_equal([' 2 Xtestfile1:1 col 3: Line1', + \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l) + + " Different types of errors + call g:Xsetlist([{'lnum':10,'col':5,'type':'W', 'text':'Warning','nr':11}, + \ {'lnum':20,'col':10,'type':'e','text':'Error','nr':22}, + \ {'lnum':30,'col':15,'type':'i','text':'Info','nr':33}, + \ {'lnum':40,'col':20,'type':'x', 'text':'Other','nr':44}, + \ {'lnum':50,'col':25,'type':"\<C-A>",'text':'one','nr':55}]) + let l = split(execute('Xlist', ""), "\n") + call assert_equal([' 1:10 col 5 warning 11: Warning', + \ ' 2:20 col 10 error 22: Error', + \ ' 3:30 col 15 info 33: Info', + \ ' 4:40 col 20 x 44: Other', + \ ' 5:50 col 25 55: one'], l) + + " Error cases + call assert_fails('Xlist abc', 'E488:') endfunc func Test_clist() @@ -141,6 +151,9 @@ endfunc func XageTests(cchar) call s:setup_commands(a:cchar) + let list = [{'bufnr': bufnr('%'), 'lnum': 1}] + call g:Xsetlist(list) + " Jumping to a non existent list should return error silent! Xolder 99 call assert_true(v:errmsg ==# 'E380: At bottom of quickfix stack') @@ -174,11 +187,7 @@ func XageTests(cchar) endfunc func Test_cage() - let list = [{'bufnr': 1, 'lnum': 1}] - call setqflist(list) call XageTests('c') - - call setloclist(0, list) call XageTests('l') endfunc @@ -187,6 +196,11 @@ endfunc func XwindowTests(cchar) call s:setup_commands(a:cchar) + " Opening the location list window without any errors should fail + if a:cchar == 'l' + call assert_fails('lopen', 'E776:') + endif + " Create a list with no valid entries Xgetexpr ['non-error 1', 'non-error 2', 'non-error 3'] @@ -227,6 +241,19 @@ func XwindowTests(cchar) " Calling cwindow should close the quickfix window with no valid errors Xwindow call assert_true(winnr('$') == 1) + + if a:cchar == 'c' + " Opening the quickfix window in multiple tab pages should reuse the + " quickfix buffer + Xgetexpr ['Xtestfile1:1:3:Line1', 'Xtestfile2:2:2:Line2', + \ 'Xtestfile3:3:1:Line3'] + Xopen + let qfbufnum = bufnr('%') + tabnew + Xopen + call assert_equal(qfbufnum, bufnr('%')) + new | only | tabonly + endif endfunc func Test_cwindow() @@ -316,6 +343,23 @@ func XbufferTests(cchar) \ l[3].lnum == 750 && l[3].col == 25 && l[3].text ==# 'Line 750') enew! + " Check for invalid buffer + call assert_fails('Xbuffer 199', 'E474:') + + " Check for unloaded buffer + edit Xtestfile1 + let bnr = bufnr('%') + enew! + call assert_fails('Xbuffer ' . bnr, 'E681:') + + " Check for invalid range + " Using Xbuffer will not run the range check in the cbuffer/lbuffer + " commands. So directly call the commands. + if (a:cchar == 'c') + call assert_fails('900,999cbuffer', 'E16:') + else + call assert_fails('900,999lbuffer', 'E16:') + endif endfunc func Test_cbuffer() @@ -338,13 +382,22 @@ endfunc func Xtest_browse(cchar) call s:setup_commands(a:cchar) + " Jumping to first or next location list entry without any error should + " result in failure + if a:cchar == 'l' + call assert_fails('lfirst', 'E776:') + call assert_fails('lnext', 'E776:') + endif + call s:create_test_file('Xqftestfile1') call s:create_test_file('Xqftestfile2') Xgetexpr ['Xqftestfile1:5:Line5', \ 'Xqftestfile1:6:Line6', \ 'Xqftestfile2:10:Line10', - \ 'Xqftestfile2:11:Line11'] + \ 'Xqftestfile2:11:Line11', + \ 'RegularLine1', + \ 'RegularLine2'] Xfirst call assert_fails('Xprev', 'E553') @@ -356,6 +409,7 @@ func Xtest_browse(cchar) call assert_equal('Xqftestfile1', bufname('%')) call assert_equal(6, line('.')) Xlast + Xprev call assert_equal('Xqftestfile2', bufname('%')) call assert_equal(11, line('.')) call assert_fails('Xnext', 'E553') @@ -364,6 +418,16 @@ func Xtest_browse(cchar) call assert_equal('Xqftestfile1', bufname('%')) call assert_equal(5, line('.')) + 10Xnext + call assert_equal('Xqftestfile2', bufname('%')) + call assert_equal(11, line('.')) + 10Xprev + call assert_equal('Xqftestfile1', bufname('%')) + call assert_equal(5, line('.')) + + Xexpr "" + call assert_fails('Xnext', 'E42:') + call delete('Xqftestfile1') call delete('Xqftestfile2') endfunc @@ -383,8 +447,32 @@ func s:test_xhelpgrep(cchar) let title_text = ':lhelpgrep quickfix' endif call assert_true(w:quickfix_title =~ title_text, w:quickfix_title) + + " Jumping to a help topic should open the help window + only + Xnext + call assert_true(&buftype == 'help') + call assert_true(winnr('$') == 2) + " Jumping to the next match should reuse the help window + Xnext + call assert_true(&buftype == 'help') + call assert_true(winnr() == 1) + call assert_true(winnr('$') == 2) + " Jumping to the next match from the quickfix window should reuse the help + " window + Xopen + Xnext + call assert_true(&buftype == 'help') + call assert_true(winnr() == 1) + call assert_true(winnr('$') == 2) + " This wipes out the buffer, make sure that doesn't cause trouble. Xclose + + new | only + + " Search for non existing help string + call assert_fails('Xhelpgrep a1b2c3', 'E480:') endfunc func Test_helpgrep() @@ -521,10 +609,7 @@ func Test_locationlist() lrewind enew lopen - lnext - lnext - lnext - lnext + 4lnext vert split wincmd L lopen @@ -578,7 +663,7 @@ func Test_locationlist() wincmd n | only augroup! testgroup - endfunc +endfunc func Test_locationlist_curwin_was_closed() augroup testgroup @@ -597,7 +682,7 @@ func Test_locationlist_curwin_was_closed() call assert_fails('lrewind', 'E924:') augroup! testgroup - endfunc +endfunc func Test_locationlist_cross_tab_jump() call writefile(['loclistfoo'], 'loclistfoo') @@ -734,7 +819,7 @@ func Test_efm1() call delete('Xerrorfile1') call delete('Xerrorfile2') call delete('Xtestfile') - endfunc +endfunc " Test for quickfix directory stack support func s:dir_stack_tests(cchar) @@ -893,30 +978,44 @@ func Test_efm2() call assert_equal(l[0].pattern, '^\VLine search text\$') call assert_equal(l[0].lnum, 0) + let l = split(execute('clist', ''), "\n") + call assert_equal([' 1 Xtestfile:^\VLine search text\$: '], l) + " Test for %P, %Q and %t format specifiers let lines=["[Xtestfile1]", \ "(1,17) error: ';' missing", \ "(21,2) warning: variable 'z' not defined", \ "(67,3) error: end of file found before string ended", + \ "--", \ "", \ "[Xtestfile2]", + \ "--", \ "", \ "[Xtestfile3]", \ "NEW compiler v1.1", \ "(2,2) warning: variable 'x' not defined", - \ "(67,3) warning: 's' already defined" + \ "(67,3) warning: 's' already defined", + \ "--" \] - set efm=%+P[%f],(%l\\,%c)%*[\ ]%t%*[^:]:\ %m,%-Q + set efm=%+P[%f]%r,(%l\\,%c)%*[\ ]%t%*[^:]:\ %m,%+Q--%r + " To exercise the push/pop file functionality in quickfix, the test files + " need to be created. + call writefile(['Line1'], 'Xtestfile1') + call writefile(['Line2'], 'Xtestfile2') + call writefile(['Line3'], 'Xtestfile3') cexpr "" for l in lines caddexpr l endfor let l = getqflist() - call assert_equal(9, len(l)) + call assert_equal(12, len(l)) call assert_equal(21, l[2].lnum) call assert_equal(2, l[2].col) call assert_equal('w', l[2].type) call assert_equal('e', l[3].type) + call delete('Xtestfile1') + call delete('Xtestfile2') + call delete('Xtestfile3') " Tests for %E, %C and %Z format specifiers let lines = ["Error 275", @@ -968,6 +1067,25 @@ func Test_efm2() call assert_equal(1, l[4].valid) call assert_equal('unittests/dbfacadeTest.py', bufname(l[4].bufnr)) + " The following sequence of commands used to crash Vim + set efm=%W%m + cgetexpr ['msg1'] + let l = getqflist() + call assert_equal(1, len(l), string(l)) + call assert_equal('msg1', l[0].text) + set efm=%C%m + lexpr 'msg2' + let l = getloclist(0) + call assert_equal(1, len(l), string(l)) + call assert_equal('msg2', l[0].text) + lopen + call setqflist([], 'r') + caddbuf + let l = getqflist() + call assert_equal(1, len(l), string(l)) + call assert_equal('|| msg2', l[0].text) + + new | only let &efm = save_efm endfunc @@ -1064,6 +1182,32 @@ func SetXlistTests(cchar, bnum) call g:Xsetlist([]) let l = g:Xgetlist() call assert_equal(0, len(l)) + + " Tests for setting the 'valid' flag + call g:Xsetlist([{'bufnr':a:bnum, 'lnum':4, 'valid':0}]) + Xwindow + call assert_equal(1, winnr('$')) + let l = g:Xgetlist() + call g:Xsetlist(l) + call assert_equal(0, g:Xgetlist()[0].valid) + call g:Xsetlist([{'text':'Text1', 'valid':1}]) + Xwindow + call assert_equal(2, winnr('$')) + Xclose + let save_efm = &efm + set efm=%m + Xgetexpr 'TestMessage' + let l = g:Xgetlist() + call g:Xsetlist(l) + call assert_equal(1, g:Xgetlist()[0].valid) + let &efm = save_efm + + " Error cases: + " Refer to a non-existing buffer and pass a non-dictionary type + call assert_fails("call g:Xsetlist([{'bufnr':998, 'lnum':4}," . + \ " {'bufnr':999, 'lnum':5}])", 'E92:') + call g:Xsetlist([[1, 2,3]]) + call assert_equal(0, len(g:Xgetlist())) endfunc func Test_setqflist() @@ -1082,7 +1226,8 @@ func Xlist_empty_middle(cchar) call s:setup_commands(a:cchar) " create three quickfix lists - Xvimgrep Test_ test_quickfix.vim + let @/ = 'Test_' + Xvimgrep // test_quickfix.vim let testlen = len(g:Xgetlist()) call assert_true(testlen > 0) Xvimgrep empty test_quickfix.vim @@ -1290,18 +1435,18 @@ func Test_switchbuf() let winid = win_getid() cfirst | cnext call assert_equal(winid, win_getid()) - cnext | cnext + 2cnext call assert_equal(winid, win_getid()) - cnext | cnext + 2cnext call assert_equal(winid, win_getid()) enew set switchbuf=useopen cfirst | cnext call assert_equal(file1_winid, win_getid()) - cnext | cnext + 2cnext call assert_equal(file2_winid, win_getid()) - cnext | cnext + 2cnext call assert_equal(file2_winid, win_getid()) enew | only @@ -1311,9 +1456,9 @@ func Test_switchbuf() tabfirst cfirst | cnext call assert_equal(2, tabpagenr()) - cnext | cnext + 2cnext call assert_equal(3, tabpagenr()) - cnext | cnext + 2cnext call assert_equal(3, tabpagenr()) tabfirst | tabonly | enew @@ -1351,11 +1496,25 @@ func Test_switchbuf() call assert_equal(2, winnr('$')) call assert_equal(1, bufwinnr('Xqftestfile3')) + " If only quickfix window is open in the current tabpage, jumping to an + " entry with 'switchubf' set to 'usetab' should search in other tabpages. enew | only + set switchbuf=usetab + tabedit Xqftestfile1 + tabedit Xqftestfile2 + tabedit Xqftestfile3 + tabfirst + copen | only + clast + call assert_equal(4, tabpagenr()) + tabfirst | tabonly | enew | only call delete('Xqftestfile1') call delete('Xqftestfile2') call delete('Xqftestfile3') + set switchbuf&vim + + enew | only endfunc func Xadjust_qflnum(cchar) @@ -1468,12 +1627,17 @@ endfunc func XbottomTests(cchar) call s:setup_commands(a:cchar) - call g:Xsetlist([{'filename': 'foo', 'lnum': 42}]) + " Calling lbottom without any errors should fail + if a:cchar == 'l' + call assert_fails('lbottom', 'E776:') + endif + + call g:Xsetlist([{'filename': 'foo', 'lnum': 42}]) Xopen let wid = win_getid() call assert_equal(1, line('.')) wincmd w - call g:Xsetlist([{'filename': 'var', 'lnum': 24}], 'a') + call g:Xsetlist([{'filename': 'var', 'lnum': 24}], 'a') Xbottom call win_gotoid(wid) call assert_equal(2, line('.')) @@ -1489,10 +1653,9 @@ endfunc func HistoryTest(cchar) call s:setup_commands(a:cchar) - call assert_fails(a:cchar . 'older 99', 'E380:') " clear all lists after the first one, then replace the first one. call g:Xsetlist([]) - Xolder + call assert_fails('Xolder 99', 'E380:') let entry = {'filename': 'foo', 'lnum': 42} call g:Xsetlist([entry], 'r') call g:Xsetlist([entry, entry]) @@ -1535,6 +1698,7 @@ func Xproperty_tests(cchar) call assert_fails('call g:Xsetlist([], "a", [])', 'E715:') " Set and get the title + call g:Xsetlist([]) Xopen wincmd p call g:Xsetlist([{'filename':'foo', 'lnum':27}]) @@ -1561,6 +1725,22 @@ func Xproperty_tests(cchar) call g:Xsetlist([], ' ', {'title' : 'N3'}) call assert_equal('N2', g:Xgetlist({'nr':2, 'title':1}).title) + " Changing the title of an earlier quickfix list + call g:Xsetlist([], ' ', {'title' : 'NewTitle', 'nr' : 2}) + call assert_equal('NewTitle', g:Xgetlist({'nr':2, 'title':1}).title) + + " Changing the title of an invalid quickfix list + call assert_equal(-1, g:Xsetlist([], ' ', + \ {'title' : 'SomeTitle', 'nr' : 99})) + call assert_equal(-1, g:Xsetlist([], ' ', + \ {'title' : 'SomeTitle', 'nr' : 'abc'})) + + if a:cchar == 'c' + copen + call assert_equal({'winid':win_getid()}, getqflist({'winid':1})) + cclose + endif + " Invalid arguments call assert_fails('call g:Xgetlist([])', 'E715') call assert_fails('call g:Xsetlist([], "a", [])', 'E715') @@ -1568,16 +1748,148 @@ func Xproperty_tests(cchar) call assert_equal(-1, s) call assert_equal({}, g:Xgetlist({'abc':1})) + call assert_equal({}, g:Xgetlist({'nr':99, 'title':1})) + call assert_equal({}, g:Xgetlist({'nr':[], 'title':1})) if a:cchar == 'l' - call assert_equal({}, getloclist(99, {'title': 1})) + call assert_equal({}, getloclist(99, {'title': 1})) endif - endfunc + + " Context related tests + call g:Xsetlist([], 'a', {'context':[1,2,3]}) + call test_garbagecollect_now() + let d = g:Xgetlist({'context':1}) + call assert_equal([1,2,3], d.context) + call g:Xsetlist([], 'a', {'context':{'color':'green'}}) + let d = g:Xgetlist({'context':1}) + call assert_equal({'color':'green'}, d.context) + call g:Xsetlist([], 'a', {'context':"Context info"}) + let d = g:Xgetlist({'context':1}) + call assert_equal("Context info", d.context) + call g:Xsetlist([], 'a', {'context':246}) + let d = g:Xgetlist({'context':1}) + call assert_equal(246, d.context) + if a:cchar == 'l' + " Test for copying context across two different location lists + new | only + let w1_id = win_getid() + let l = [1] + call setloclist(0, [], 'a', {'context':l}) + new + let w2_id = win_getid() + call add(l, 2) + call assert_equal([1, 2], getloclist(w1_id, {'context':1}).context) + call assert_equal([1, 2], getloclist(w2_id, {'context':1}).context) + unlet! l + call assert_equal([1, 2], getloclist(w2_id, {'context':1}).context) + only + call setloclist(0, [], 'f') + call assert_equal({}, getloclist(0, {'context':1})) + endif + + " Test for changing the context of previous quickfix lists + call g:Xsetlist([], 'f') + Xexpr "One" + Xexpr "Two" + Xexpr "Three" + call g:Xsetlist([], ' ', {'context' : [1], 'nr' : 1}) + call g:Xsetlist([], ' ', {'context' : [2], 'nr' : 2}) + " Also, check for setting the context using quickfix list number zero. + call g:Xsetlist([], ' ', {'context' : [3], 'nr' : 0}) + call test_garbagecollect_now() + let l = g:Xgetlist({'nr' : 1, 'context' : 1}) + call assert_equal([1], l.context) + let l = g:Xgetlist({'nr' : 2, 'context' : 1}) + call assert_equal([2], l.context) + let l = g:Xgetlist({'nr' : 3, 'context' : 1}) + call assert_equal([3], l.context) + + " Test for changing the context through reference and for garbage + " collection of quickfix context + let l = ["red"] + call g:Xsetlist([], ' ', {'context' : l}) + call add(l, "blue") + let x = g:Xgetlist({'context' : 1}) + call add(x.context, "green") + call assert_equal(["red", "blue", "green"], l) + call assert_equal(["red", "blue", "green"], x.context) + unlet l + call test_garbagecollect_now() + let m = g:Xgetlist({'context' : 1}) + call assert_equal(["red", "blue", "green"], m.context) + + " Test for setting/getting items + Xexpr "" + let qfprev = g:Xgetlist({'nr':0}) + call g:Xsetlist([], ' ', {'title':'Green', + \ 'items' : [{'filename':'F1', 'lnum':10}]}) + let qfcur = g:Xgetlist({'nr':0}) + call assert_true(qfcur.nr == qfprev.nr + 1) + let l = g:Xgetlist({'items':1}) + call assert_equal('F1', bufname(l.items[0].bufnr)) + call assert_equal(10, l.items[0].lnum) + call g:Xsetlist([], 'a', {'items' : [{'filename':'F2', 'lnum':20}, + \ {'filename':'F2', 'lnum':30}]}) + let l = g:Xgetlist({'items':1}) + call assert_equal('F2', bufname(l.items[2].bufnr)) + call assert_equal(30, l.items[2].lnum) + call g:Xsetlist([], 'r', {'items' : [{'filename':'F3', 'lnum':40}]}) + let l = g:Xgetlist({'items':1}) + call assert_equal('F3', bufname(l.items[0].bufnr)) + call assert_equal(40, l.items[0].lnum) + call g:Xsetlist([], 'r', {'items' : []}) + let l = g:Xgetlist({'items':1}) + call assert_equal(0, len(l.items)) + + " Save and restore the quickfix stack + call g:Xsetlist([], 'f') + call assert_equal(0, g:Xgetlist({'nr':'$'}).nr) + Xexpr "File1:10:Line1" + Xexpr "File2:20:Line2" + Xexpr "File3:30:Line3" + let last_qf = g:Xgetlist({'nr':'$'}).nr + call assert_equal(3, last_qf) + let qstack = [] + for i in range(1, last_qf) + let qstack = add(qstack, g:Xgetlist({'nr':i, 'all':1})) + endfor + call g:Xsetlist([], 'f') + for i in range(len(qstack)) + call g:Xsetlist([], ' ', qstack[i]) + endfor + call assert_equal(3, g:Xgetlist({'nr':'$'}).nr) + call assert_equal(10, g:Xgetlist({'nr':1, 'items':1}).items[0].lnum) + call assert_equal(20, g:Xgetlist({'nr':2, 'items':1}).items[0].lnum) + call assert_equal(30, g:Xgetlist({'nr':3, 'items':1}).items[0].lnum) + call g:Xsetlist([], 'f') + + " Swap two quickfix lists + Xexpr "File1:10:Line10" + Xexpr "File2:20:Line20" + Xexpr "File3:30:Line30" + call g:Xsetlist([], 'r', {'nr':1,'title':'Colors','context':['Colors']}) + call g:Xsetlist([], 'r', {'nr':2,'title':'Fruits','context':['Fruits']}) + let l1=g:Xgetlist({'nr':1,'all':1}) + let l2=g:Xgetlist({'nr':2,'all':1}) + let l1.nr=2 + let l2.nr=1 + call g:Xsetlist([], 'r', l1) + call g:Xsetlist([], 'r', l2) + let newl1=g:Xgetlist({'nr':1,'all':1}) + let newl2=g:Xgetlist({'nr':2,'all':1}) + call assert_equal(':Fruits', newl1.title) + call assert_equal(['Fruits'], newl1.context) + call assert_equal('Line20', newl1.items[0].text) + call assert_equal(':Colors', newl2.title) + call assert_equal(['Colors'], newl2.context) + call assert_equal('Line10', newl2.items[0].text) + call g:Xsetlist([], 'f') +endfunc func Test_qf_property() call Xproperty_tests('c') call Xproperty_tests('l') - endfunc +endfunc " Tests for the QuickFixCmdPre/QuickFixCmdPost autocommands func QfAutoCmdHandler(loc, cmd) @@ -1673,3 +1985,227 @@ func Test_dirstack_cleanup() caddbuffer let &efm = save_efm endfunc + +" Tests for jumping to entries from the location list window and quickfix +" window +func Test_cwindow_jump() + set efm=%f%%%l%%%m + lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"] + lopen | only + lfirst + call assert_true(winnr('$') == 2) + call assert_true(winnr() == 1) + " Location list for the new window should be set + call assert_true(getloclist(0)[2].text == 'Line 30') + + " Open a scratch buffer + " Open a new window and create a location list + " Open the location list window and close the other window + " Jump to an entry. + " Should create a new window and jump to the entry. The scrtach buffer + " should not be used. + enew | only + set buftype=nofile + below new + lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"] + lopen + 2wincmd c + lnext + call assert_true(winnr('$') == 3) + call assert_true(winnr() == 2) + + " Open two windows with two different location lists + " Open the location list window and close the previous window + " Jump to an entry in the location list window + " Should open the file in the first window and not set the location list. + enew | only + lgetexpr ["F1%5%Line 5"] + below new + lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"] + lopen + 2wincmd c + lnext + call assert_true(winnr() == 1) + call assert_true(getloclist(0)[0].text == 'Line 5') + + enew | only + cgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"] + copen + cnext + call assert_true(winnr('$') == 2) + call assert_true(winnr() == 1) + + enew | only + set efm&vim +endfunc + +func XvimgrepTests(cchar) + call s:setup_commands(a:cchar) + + call writefile(['Editor:VIM vim', + \ 'Editor:Emacs EmAcS', + \ 'Editor:Notepad NOTEPAD'], 'Xtestfile1') + call writefile(['Linux', 'MacOS', 'MS-Windows'], 'Xtestfile2') + + " Error cases + call assert_fails('Xvimgrep /abc *', 'E682:') + + let @/='' + call assert_fails('Xvimgrep // *', 'E35:') + + call assert_fails('Xvimgrep abc', 'E683:') + call assert_fails('Xvimgrep a1b2c3 Xtestfile1', 'E480:') + call assert_fails('Xvimgrep pat Xa1b2c3', 'E480:') + + Xexpr "" + Xvimgrepadd Notepad Xtestfile1 + Xvimgrepadd MacOS Xtestfile2 + let l = g:Xgetlist() + call assert_equal(2, len(l)) + call assert_equal('Editor:Notepad NOTEPAD', l[0].text) + + Xvimgrep #\cvim#g Xtestfile? + let l = g:Xgetlist() + call assert_equal(2, len(l)) + call assert_equal(8, l[0].col) + call assert_equal(12, l[1].col) + + 1Xvimgrep ?Editor? Xtestfile* + let l = g:Xgetlist() + call assert_equal(1, len(l)) + call assert_equal('Editor:VIM vim', l[0].text) + + edit +3 Xtestfile2 + Xvimgrep +\cemacs+j Xtestfile1 + let l = g:Xgetlist() + call assert_equal('Xtestfile2', bufname('')) + call assert_equal('Editor:Emacs EmAcS', l[0].text) + + call delete('Xtestfile1') + call delete('Xtestfile2') +endfunc + +" Tests for the :vimgrep command +func Test_vimgrep() + call XvimgrepTests('c') + call XvimgrepTests('l') +endfunc + +func XfreeTests(cchar) + call s:setup_commands(a:cchar) + + enew | only + + " Deleting the quickfix stack should work even When the current list is + " somewhere in the middle of the stack + Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15'] + Xexpr ['Xfile2:20:20:Line 20', 'Xfile2:25:25:Line 25'] + Xexpr ['Xfile3:30:30:Line 30', 'Xfile3:35:35:Line 35'] + Xolder + call g:Xsetlist([], 'f') + call assert_equal(0, len(g:Xgetlist())) + + " After deleting the stack, adding a new list should create a stack with a + " single list. + Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15'] + call assert_equal(1, g:Xgetlist({'all':1}).nr) + + " Deleting the stack from a quickfix window should update/clear the + " quickfix/location list window. + Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15'] + Xexpr ['Xfile2:20:20:Line 20', 'Xfile2:25:25:Line 25'] + Xexpr ['Xfile3:30:30:Line 30', 'Xfile3:35:35:Line 35'] + Xolder + Xwindow + call g:Xsetlist([], 'f') + call assert_equal(2, winnr('$')) + call assert_equal(1, line('$')) + Xclose + + " Deleting the stack from a non-quickfix window should update/clear the + " quickfix/location list window. + Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15'] + Xexpr ['Xfile2:20:20:Line 20', 'Xfile2:25:25:Line 25'] + Xexpr ['Xfile3:30:30:Line 30', 'Xfile3:35:35:Line 35'] + Xolder + Xwindow + wincmd p + call g:Xsetlist([], 'f') + call assert_equal(0, len(g:Xgetlist())) + wincmd p + call assert_equal(2, winnr('$')) + call assert_equal(1, line('$')) + + " After deleting the location list stack, if the location list window is + " opened, then a new location list should be created. So opening the + " location list window again should not create a new window. + if a:cchar == 'l' + lexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15'] + wincmd p + lopen + call assert_equal(2, winnr('$')) + endif + Xclose +endfunc + +" Tests for the quickifx free functionality +func Test_qf_free() + call XfreeTests('c') + call XfreeTests('l') +endfunc + +" Test for buffer overflow when parsing lines and adding new entries to +" the quickfix list. +func Test_bufoverflow() + set efm=%f:%l:%m + cgetexpr ['File1:100:' . repeat('x', 1025)] + + set efm=%+GCompiler:\ %.%#,%f:%l:%m + cgetexpr ['Compiler: ' . repeat('a', 1015), 'File1:10:Hello World'] + + set efm=%DEntering\ directory\ %f,%f:%l:%m + cgetexpr ['Entering directory ' . repeat('a', 1006), + \ 'File1:10:Hello World'] + set efm&vim +endfunc + +func Test_cclose_from_copen() + augroup QF_Test + au! + au FileType qf :cclose + augroup END + copen + augroup QF_Test + au! + augroup END + augroup! QF_Test +endfunc + +" Tests for getting the quickfix stack size +func XsizeTests(cchar) + call s:setup_commands(a:cchar) + + call g:Xsetlist([], 'f') + call assert_equal(0, g:Xgetlist({'nr':'$'}).nr) + call assert_equal(1, len(g:Xgetlist({'nr':'$', 'all':1}))) + call assert_equal(0, len(g:Xgetlist({'nr':0}))) + + Xexpr "File1:10:Line1" + Xexpr "File2:20:Line2" + Xexpr "File3:30:Line3" + Xolder | Xolder + call assert_equal(3, g:Xgetlist({'nr':'$'}).nr) + call g:Xsetlist([], 'f') + + Xexpr "File1:10:Line1" + Xexpr "File2:20:Line2" + Xexpr "File3:30:Line3" + Xolder | Xolder + call g:Xsetlist([], 'a', {'nr':'$', 'title':'Compiler'}) + call assert_equal('Compiler', g:Xgetlist({'nr':3, 'all':1}).title) +endfunc + +func Test_Qf_Size() + call XsizeTests('c') + call XsizeTests('l') +endfunc diff --git a/src/nvim/testdir/test_recover.vim b/src/nvim/testdir/test_recover.vim new file mode 100644 index 0000000000..46d884a97c --- /dev/null +++ b/src/nvim/testdir/test_recover.vim @@ -0,0 +1,63 @@ +" Test :recover + +func Test_recover_root_dir() + " This used to access invalid memory. + split Xtest + set dir=/ + call assert_fails('recover', 'E305:') + close! + + if has('win32') || filewritable('/') == 2 + " can write in / directory on MS-Windows + set dir=/notexist/ + endif + call assert_fails('split Xtest', 'E303:') + set dir& +endfunc + +" Inserts 10000 lines with text to fill the swap file with two levels of pointer +" blocks. Then recovers from the swap file and checks all text is restored. +" +" We need about 10000 lines of 100 characters to get two levels of pointer +" blocks. +func Test_swap_file() + set directory=. + set fileformat=unix undolevels=-1 + edit! Xtest + let text = "\tabcdefghijklmnoparstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnoparstuvwxyz0123456789" + let i = 1 + let linecount = 10000 + while i <= linecount + call append(i - 1, i . text) + let i += 1 + endwhile + $delete + preserve + " get the name of the swap file + let swname = split(execute("swapname"))[0] + let swname = substitute(swname, '[[:blank:][:cntrl:]]*\(.\{-}\)[[:blank:][:cntrl:]]*$', '\1', '') + " make a copy of the swap file in Xswap + set binary + exe 'sp ' . swname + w! Xswap + set nobinary + new + only! + bwipe! Xtest + call rename('Xswap', swname) + recover Xtest + call delete(swname) + let linedollar = line('$') + call assert_equal(linecount, linedollar) + if linedollar < linecount + let linecount = linedollar + endif + let i = 1 + while i <= linecount + call assert_equal(i . text, getline(i)) + let i += 1 + endwhile + + set undolevels& + enew! | only +endfunc diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim new file mode 100644 index 0000000000..8528412806 --- /dev/null +++ b/src/nvim/testdir/test_regexp_latin.vim @@ -0,0 +1,32 @@ +" Tests for regexp in latin1 encoding +set encoding=latin1 +scriptencoding latin1 + +func s:equivalence_test() + let str = "AÀÁÂÃÄÅ B C D EÈÉÊË F G H IÌÍÎÏ J K L M NÑ OÒÓÔÕÖØ P Q R S T UÙÚÛÜ V W X YÝ Z aàáâãäå b c d eèéêë f g h iìíîï j k l m nñ oòóôõöø p q r s t uùúûü v w x yýÿ z" + let groups = split(str) + for group1 in groups + for c in split(group1, '\zs') + " next statement confirms that equivalence class matches every + " character in group + call assert_match('^[[=' . c . '=]]*$', group1) + for group2 in groups + if group2 != group1 + " next statement converts that equivalence class doesn't match + " a character in any other group + call assert_equal(-1, match(group2, '[[=' . c . '=]]')) + endif + endfor + endfor + endfor +endfunc + +func Test_equivalence_re1() + set re=1 + call s:equivalence_test() +endfunc + +func Test_equivalence_re2() + set re=2 + call s:equivalence_test() +endfunc diff --git a/src/nvim/testdir/test_scrollbind.vim b/src/nvim/testdir/test_scrollbind.vim new file mode 100644 index 0000000000..baa24f1979 --- /dev/null +++ b/src/nvim/testdir/test_scrollbind.vim @@ -0,0 +1,32 @@ +" Test for 'scrollbind' causing an unexpected scroll of one of the windows. +func Test_scrollbind() + " We don't want the status line to cause problems: + set laststatus=0 + let totalLines = &lines * 20 + let middle = totalLines / 2 + new | only + for i in range(1, totalLines) + call setline(i, 'LINE ' . i) + endfor + exe string(middle) + normal zt + normal M + aboveleft vert new + for i in range(1, totalLines) + call setline(i, 'line ' . i) + endfor + exe string(middle) + normal zt + normal M + " Execute the following two commands at once to reproduce the problem. + setl scb | wincmd p + setl scb + wincmd w + let topLineLeft = line('w0') + wincmd p + let topLineRight = line('w0') + setl noscrollbind + wincmd p + setl noscrollbind + call assert_equal(0, topLineLeft - topLineRight) +endfunc diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index a333e7f206..5da9397be5 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -2,13 +2,13 @@ func Test_search_cmdline() " See test/functional/legacy/search_spec.lua - throw 'skipped: Nvim does not support test_disable_char_avail()' + 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_disable_char_avail(1) + call test_override("char_avail", 1) new call setline(1, [' 1', ' 2 these', ' 3 the', ' 4 their', ' 5 there', ' 6 their', ' 7 the', ' 8 them', ' 9 these', ' 10 foobar']) " Test 1 @@ -195,19 +195,19 @@ func Test_search_cmdline() call assert_equal(' 3 the', getline('.')) " clean up - call test_disable_char_avail(0) + call test_override("char_avail", 0) bw! endfunc func Test_search_cmdline2() " See test/functional/legacy/search_spec.lua - throw 'skipped: Nvim does not support test_disable_char_avail()' + 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_disable_char_avail(1) + call test_override("char_avail", 1) new call setline(1, [' 1', ' 2 these', ' 3 the theother']) " Test 1 @@ -269,7 +269,7 @@ func Test_search_cmdline2() " clean up set noincsearch - call test_disable_char_avail(0) + call test_override("char_avail", 0) bw! endfunc @@ -298,3 +298,158 @@ func Test_searchpair() q! endfunc +func Test_searchc() + " These commands used to cause memory overflow in searchc(). + new + norm ixx + exe "norm 0t\u93cf" + bw! +endfunc + +func Test_search_cmdline3() + 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 + 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! +endfunc + +func Test_search_cmdline4() + " See test/functional/legacy/search_spec.lua + 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 the first', ' 2 the second', ' 3 the third']) + set incsearch + $ + call feedkeys("?the\<c-g>\<cr>", 'tx') + call assert_equal(' 3 the third', getline('.')) + $ + call feedkeys("?the\<c-g>\<c-g>\<cr>", 'tx') + call assert_equal(' 1 the first', getline('.')) + $ + call feedkeys("?the\<c-g>\<c-g>\<c-g>\<cr>", 'tx') + call assert_equal(' 2 the second', getline('.')) + $ + call feedkeys("?the\<c-t>\<cr>", 'tx') + call assert_equal(' 1 the first', getline('.')) + $ + call feedkeys("?the\<c-t>\<c-t>\<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('.')) + " clean up + set noincsearch + call test_override("char_avail", 0) + bw! +endfunc + +func Test_search_cmdline5() + if !exists('+incsearch') + return + endif + " 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']) + 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('.')) + " clean up + set noincsearch + bw! +endfunc + +" Tests for regexp with various magic settings +func Test_search_regexp() + enew! + + put ='1 a aa abb abbccc' + exe 'normal! /a*b\{2}c\+/e' . "\<CR>" + call assert_equal([0, 2, 17, 0], getpos('.')) + + put ='2 d dd dee deefff' + exe 'normal! /\Md\*e\{2}f\+/e' . "\<CR>" + call assert_equal([0, 3, 17, 0], getpos('.')) + + set nomagic + put ='3 g gg ghh ghhiii' + exe 'normal! /g\*h\{2}i\+/e' . "\<CR>" + call assert_equal([0, 4, 17, 0], getpos('.')) + + put ='4 j jj jkk jkklll' + exe 'normal! /\mj*k\{2}l\+/e' . "\<CR>" + call assert_equal([0, 5, 17, 0], getpos('.')) + + put ='5 m mm mnn mnnooo' + exe 'normal! /\vm*n{2}o+/e' . "\<CR>" + call assert_equal([0, 6, 17, 0], getpos('.')) + + put ='6 x ^aa$ x' + exe 'normal! /\V^aa$' . "\<CR>" + call assert_equal([0, 7, 5, 0], getpos('.')) + + set magic + put ='7 (a)(b) abbaa' + exe 'normal! /\v(a)(b)\2\1\1/e' . "\<CR>" + call assert_equal([0, 8, 14, 0], getpos('.')) + + put ='8 axx [ab]xx' + exe 'normal! /\V[ab]\(\[xy]\)\1' . "\<CR>" + call assert_equal([0, 9, 7, 0], getpos('.')) + + set undolevels=100 + put ='9 foobar' + put ='' + exe "normal! a\<C-G>u\<Esc>" + normal G + exe 'normal! dv?bar?' . "\<CR>" + call assert_equal('9 foo', getline('.')) + call assert_equal([0, 10, 5, 0], getpos('.')) + call assert_equal(10, line('$')) + normal u + call assert_equal('9 foobar', getline('.')) + call assert_equal([0, 10, 6, 0], getpos('.')) + call assert_equal(11, line('$')) + + set undolevels& + enew! +endfunc + +" Test for search('multi-byte char', 'bce') +func Test_search_multibyte() + if !has('multi_byte') + return + endif + let save_enc = &encoding + set encoding=utf8 + enew! + call append('$', 'A') + call cursor(2, 1) + call assert_equal(2, search('A', 'bce', line('.'))) + enew! + let &encoding = save_enc +endfunc diff --git a/src/nvim/testdir/test_sha256.vim b/src/nvim/testdir/test_sha256.vim new file mode 100644 index 0000000000..dd4707977e --- /dev/null +++ b/src/nvim/testdir/test_sha256.vim @@ -0,0 +1,22 @@ +" Tests for the sha256() function. + +if !has('cryptv') || !exists('*sha256') + finish +endif + +function Test_sha256() + " test for empty string: + call assert_equal(sha256(""), 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855') + + "'test for 1 char: + call assert_equal(sha256("a"), 'ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb') + " + "test for 3 chars: + call assert_equal(sha256("abc"), 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad') + + " test for contains meta char: + call assert_equal(sha256("foo\nbar"), '807eff6267f3f926a21d234f7b0cf867a86f47e07a532f15e8cc39ed110ca776') + + " test for contains non-ascii char: + call assert_equal(sha256("\xde\xad\xbe\xef"), '5f78c33274e43fa9de5659265c1d917e25c03722dcb0b8d27db8d5feaa813953') +endfunction diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim index 75dbd74b34..a967435346 100644 --- a/src/nvim/testdir/test_signs.vim +++ b/src/nvim/testdir/test_signs.vim @@ -182,7 +182,7 @@ func Test_sign_invalid_commands() call assert_fails('sign define Sign1 xxx', 'E475:') call assert_fails('sign undefine', 'E156:') call assert_fails('sign list xxx', 'E155:') - call assert_fails('sign place 1 buffer=', 'E158:') + call assert_fails('sign place 1 buffer=999', 'E158:') call assert_fails('sign define Sign2 text=', 'E239:') endfunc diff --git a/src/nvim/testdir/test_source_utf8.vim b/src/nvim/testdir/test_source_utf8.vim index edb76fc43d..c29c2ec1f3 100644 --- a/src/nvim/testdir/test_source_utf8.vim +++ b/src/nvim/testdir/test_source_utf8.vim @@ -31,3 +31,33 @@ func Test_source_latin() bwipe! call delete('Xscript') endfunc + +" Test for sourcing a file with CTRL-V's at the end of the line +func Test_source_ctrl_v() + call writefile(['map __1 afirst', + \ 'map __2 asecond', + \ 'map __3 athird', + \ 'map __4 afourth', + \ 'map __5 afifth', + \ "map __1 asd\<C-V>", + \ "map __2 asd\<C-V>\<C-V>", + \ "map __3 asd\<C-V>\<C-V>", + \ "map __4 asd\<C-V>\<C-V>\<C-V>", + \ "map __5 asd\<C-V>\<C-V>\<C-V>", + \ ], 'Xtestfile') + source Xtestfile + enew! + exe "normal __1\<Esc>\<Esc>__2\<Esc>__3\<Esc>\<Esc>__4\<Esc>__5\<Esc>" + exe "%s/\<C-J>/0/g" + call assert_equal(['sd', + \ "map __2 asd\<Esc>secondsd\<Esc>sd0map __5 asd0fifth"], + \ getline(1, 2)) + + enew! + call delete('Xtestfile') + unmap __1 + unmap __2 + unmap __3 + unmap __4 + unmap __5 +endfunc diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim new file mode 100644 index 0000000000..21f2363731 --- /dev/null +++ b/src/nvim/testdir/test_spell.vim @@ -0,0 +1,821 @@ +" Test spell checking + +if !has('spell') + finish +endif + +func TearDown() + set nospell + call delete('Xtest.aff') + call delete('Xtest.dic') + call delete('Xtest.latin1.add') + call delete('Xtest.latin1.add.spl') + call delete('Xtest.latin1.spl') + call delete('Xtest.latin1.sug') +endfunc + +func Test_wrap_search() + new + call setline(1, ['The', '', 'A plong line with two zpelling mistakes', '', 'End']) + set spell wrapscan + normal ]s + call assert_equal('plong', expand('<cword>')) + normal ]s + call assert_equal('zpelling', expand('<cword>')) + normal ]s + call assert_equal('plong', expand('<cword>')) + bwipe! + set nospell +endfunc + +func Test_curswant() + new + call setline(1, ['Another plong line', 'abcdefghijklmnopq']) + set spell wrapscan + normal 0]s + call assert_equal('plong', expand('<cword>')) + normal j + call assert_equal(9, getcurpos()[2]) + normal 0[s + call assert_equal('plong', expand('<cword>')) + normal j + call assert_equal(9, getcurpos()[2]) + + normal 0]S + call assert_equal('plong', expand('<cword>')) + normal j + call assert_equal(9, getcurpos()[2]) + normal 0[S + call assert_equal('plong', expand('<cword>')) + normal j + call assert_equal(9, getcurpos()[2]) + + normal 1G0 + call assert_equal('plong', spellbadword()[0]) + normal j + call assert_equal(9, getcurpos()[2]) + + bwipe! + set nospell +endfunc + +func Test_z_equal_on_invalid_utf8_word() + split + set spell + call setline(1, "\xff") + norm z= + set nospell + bwipe! +endfunc + +func Test_spellreall() + new + set spell + call assert_fails('spellrepall', 'E752:') + call setline(1, ['A speling mistake. The same speling mistake.', + \ 'Another speling mistake.']) + call feedkeys(']s1z=', 'tx') + call assert_equal('A spelling mistake. The same speling mistake.', getline(1)) + call assert_equal('Another speling mistake.', getline(2)) + spellrepall + call assert_equal('A spelling mistake. The same spelling mistake.', getline(1)) + call assert_equal('Another spelling mistake.', getline(2)) + call assert_fails('spellrepall', 'E753:') + set spell& + bwipe! +endfunc + +func Test_zz_basic() + call LoadAffAndDic(g:test_data_aff1, g:test_data_dic1) + call RunGoodBad("wrong OK puts. Test the end", + \ "bad: inputs comment ok Ok. test d\xE9\xF4l end the", + \["Comment", "deol", "d\xE9\xF4r", "input", "OK", "output", "outputs", "outtest", "put", "puts", + \ "test", "testen", "testn", "the end", "uk", "wrong"], + \[ + \ ["bad", ["put", "uk", "OK"]], + \ ["inputs", ["input", "puts", "outputs"]], + \ ["comment", ["Comment", "outtest", "the end"]], + \ ["ok", ["OK", "uk", "put"]], + \ ["Ok", ["OK", "Uk", "Put"]], + \ ["test", ["Test", "testn", "testen"]], + \ ["d\xE9\xF4l", ["deol", "d\xE9\xF4r", "test"]], + \ ["end", ["put", "uk", "test"]], + \ ["the", ["put", "uk", "test"]], + \ ] + \ ) + + call assert_equal("gebletegek", soundfold('goobledygoook')) + call assert_equal("kepereneven", soundfold('kóopërÿnôven')) + call assert_equal("everles gesvets etele", soundfold('oeverloos gezwets edale')) +endfunc + +" Postponed prefixes +func Test_zz_prefixes() + call LoadAffAndDic(g:test_data_aff2, g:test_data_dic1) + call RunGoodBad("puts", + \ "bad: inputs comment ok Ok end the. test d\xE9\xF4l", + \ ["Comment", "deol", "d\xE9\xF4r", "OK", "put", "input", "output", "puts", "outputs", "test", "outtest", "testen", "testn", "the end", "uk", "wrong"], + \ [ + \ ["bad", ["put", "uk", "OK"]], + \ ["inputs", ["input", "puts", "outputs"]], + \ ["comment", ["Comment"]], + \ ["ok", ["OK", "uk", "put"]], + \ ["Ok", ["OK", "Uk", "Put"]], + \ ["end", ["put", "uk", "deol"]], + \ ["the", ["put", "uk", "test"]], + \ ["test", ["Test", "testn", "testen"]], + \ ["d\xE9\xF4l", ["deol", "d\xE9\xF4r", "test"]], + \ ]) +endfunc + +"Compound words +func Test_zz_compound() + call LoadAffAndDic(g:test_data_aff3, g:test_data_dic3) + call RunGoodBad("foo m\xEF foobar foofoobar barfoo barbarfoo", + \ "bad: bar la foom\xEF barm\xEF m\xEFfoo m\xEFbar m\xEFm\xEF lala m\xEFla lam\xEF foola labar", + \ ["foo", "m\xEF"], + \ [ + \ ["bad", ["foo", "m\xEF"]], + \ ["bar", ["barfoo", "foobar", "foo"]], + \ ["la", ["m\xEF", "foo"]], + \ ["foom\xEF", ["foo m\xEF", "foo", "foofoo"]], + \ ["barm\xEF", ["barfoo", "m\xEF", "barbar"]], + \ ["m\xEFfoo", ["m\xEF foo", "foo", "foofoo"]], + \ ["m\xEFbar", ["foobar", "barbar", "m\xEF"]], + \ ["m\xEFm\xEF", ["m\xEF m\xEF", "m\xEF"]], + \ ["lala", []], + \ ["m\xEFla", ["m\xEF", "m\xEF m\xEF"]], + \ ["lam\xEF", ["m\xEF", "m\xEF m\xEF"]], + \ ["foola", ["foo", "foobar", "foofoo"]], + \ ["labar", ["barbar", "foobar"]], + \ ]) + + call LoadAffAndDic(g:test_data_aff4, g:test_data_dic4) + call RunGoodBad("word util bork prebork start end wordutil wordutils pro-ok bork borkbork borkborkbork borkborkborkbork borkborkborkborkbork tomato tomatotomato startend startword startwordword startwordend startwordwordend startwordwordwordend prebork preborkbork preborkborkbork nouword", + \ "bad: wordutilize pro borkborkborkborkborkbork tomatotomatotomato endstart endend startstart wordend wordstart preborkprebork preborkpreborkbork startwordwordwordwordend borkpreborkpreborkbork utilsbork startnouword", + \ ["bork", "prebork", "end", "pro-ok", "start", "tomato", "util", "utilize", "utils", "word", "nouword"], + \ [ + \ ["bad", ["end", "bork", "word"]], + \ ["wordutilize", ["word utilize", "wordutils", "wordutil"]], + \ ["pro", ["bork", "word", "end"]], + \ ["borkborkborkborkborkbork", ["bork borkborkborkborkbork", "borkbork borkborkborkbork", "borkborkbork borkborkbork"]], + \ ["tomatotomatotomato", ["tomato tomatotomato", "tomatotomato tomato", "tomato tomato tomato"]], + \ ["endstart", ["end start", "start"]], + \ ["endend", ["end end", "end"]], + \ ["startstart", ["start start"]], + \ ["wordend", ["word end", "word", "wordword"]], + \ ["wordstart", ["word start", "bork start"]], + \ ["preborkprebork", ["prebork prebork", "preborkbork", "preborkborkbork"]], + \ ["preborkpreborkbork", ["prebork preborkbork", "preborkborkbork", "preborkborkborkbork"]], + \ ["startwordwordwordwordend", ["startwordwordwordword end", "startwordwordwordword", "start wordwordwordword end"]], + \ ["borkpreborkpreborkbork", ["bork preborkpreborkbork", "bork prebork preborkbork", "bork preborkprebork bork"]], + \ ["utilsbork", ["utilbork", "utils bork", "util bork"]], + \ ["startnouword", ["start nouword", "startword", "startborkword"]], + \ ]) + +endfunc + +"Test affix flags with two characters +func Test_zz_affix() + call LoadAffAndDic(g:test_data_aff5, g:test_data_dic5) + call RunGoodBad("fooa1 fooa\xE9 bar prebar barbork prebarbork startprebar start end startend startmiddleend nouend", + \ "bad: foo fooa2 prabar probarbirk middle startmiddle middleend endstart startprobar startnouend", + \ ["bar", "barbork", "end", "fooa1", "fooa\xE9", "nouend", "prebar", "prebarbork", "start"], + \ [ + \ ["bad", ["bar", "end", "fooa1"]], + \ ["foo", ["fooa1", "fooa\xE9", "bar"]], + \ ["fooa2", ["fooa1", "fooa\xE9", "bar"]], + \ ["prabar", ["prebar", "bar", "bar bar"]], + \ ["probarbirk", ["prebarbork"]], + \ ["middle", []], + \ ["startmiddle", ["startmiddleend", "startmiddlebar"]], + \ ["middleend", []], + \ ["endstart", ["end start", "start"]], + \ ["startprobar", ["startprebar", "start prebar", "startbar"]], + \ ["startnouend", ["start nouend", "startend"]], + \ ]) + + call LoadAffAndDic(g:test_data_aff6, g:test_data_dic6) + call RunGoodBad("meea1 meea\xE9 bar prebar barbork prebarbork leadprebar lead end leadend leadmiddleend", + \ "bad: mee meea2 prabar probarbirk middle leadmiddle middleend endlead leadprobar", + \ ["bar", "barbork", "end", "lead", "meea1", "meea\xE9", "prebar", "prebarbork"], + \ [ + \ ["bad", ["bar", "end", "lead"]], + \ ["mee", ["meea1", "meea\xE9", "bar"]], + \ ["meea2", ["meea1", "meea\xE9", "lead"]], + \ ["prabar", ["prebar", "bar", "leadbar"]], + \ ["probarbirk", ["prebarbork"]], + \ ["middle", []], + \ ["leadmiddle", ["leadmiddleend", "leadmiddlebar"]], + \ ["middleend", []], + \ ["endlead", ["end lead", "lead", "end end"]], + \ ["leadprobar", ["leadprebar", "lead prebar", "leadbar"]], + \ ]) + + call LoadAffAndDic(g:test_data_aff7, g:test_data_dic7) + call RunGoodBad("meea1 meea\xE9 bar prebar barmeat prebarmeat leadprebar lead tail leadtail leadmiddletail", + \ "bad: mee meea2 prabar probarmaat middle leadmiddle middletail taillead leadprobar", + \ ["bar", "barmeat", "lead", "meea1", "meea\xE9", "prebar", "prebarmeat", "tail"], + \ [ + \ ["bad", ["bar", "lead", "tail"]], + \ ["mee", ["meea1", "meea\xE9", "bar"]], + \ ["meea2", ["meea1", "meea\xE9", "lead"]], + \ ["prabar", ["prebar", "bar", "leadbar"]], + \ ["probarmaat", ["prebarmeat"]], + \ ["middle", []], + \ ["leadmiddle", ["leadmiddlebar"]], + \ ["middletail", []], + \ ["taillead", ["tail lead", "tail"]], + \ ["leadprobar", ["leadprebar", "lead prebar", "leadbar"]], + \ ]) +endfunc + +func Test_zz_NOSLITSUGS() + call LoadAffAndDic(g:test_data_aff8, g:test_data_dic8) + call RunGoodBad("foo bar faabar", "bad: foobar barfoo", + \ ["bar", "faabar", "foo"], + \ [ + \ ["bad", ["bar", "foo"]], + \ ["foobar", ["faabar", "foo bar", "bar"]], + \ ["barfoo", ["bar foo", "bar", "foo"]], + \ ]) +endfunc + +" Numbers +func Test_zz_Numbers() + call LoadAffAndDic(g:test_data_aff9, g:test_data_dic9) + call RunGoodBad("0b1011 0777 1234 0x01ff", "", + \ ["bar", "foo"], + \ [ + \ ]) +endfunc + +function FirstSpellWord() + call feedkeys("/^start:\n", 'tx') + normal ]smm + let [str, a] = spellbadword() + return str +endfunc + +function SecondSpellWord() + normal `m]s + let [str, a] = spellbadword() + return str +endfunc + +"Test with SAL instead of SOFO items; test automatic reloading +func Test_zz_sal_and_addition() + throw 'skipped: Nvim does not support enc=latin1' + set enc=latin1 + set spellfile= + call writefile(g:test_data_dic1, "Xtest.dic") + call writefile(g:test_data_aff_sal, "Xtest.aff") + mkspell! Xtest Xtest + set spl=Xtest.latin1.spl spell + call assert_equal('kbltykk', soundfold('goobledygoook')) + call assert_equal('kprnfn', soundfold('kóopërÿnôven')) + call assert_equal('*fls kswts tl', soundfold('oeverloos gezwets edale')) + + "also use an addition file + call writefile(["/regions=usgbnz", "elequint/2", "elekwint/3"], "Xtest.latin1.add") + mkspell! Xtest.latin1.add.spl Xtest.latin1.add + + bwipe! + call setline(1, ["start: elequint test elekwint test elekwent asdf"]) + + set spellfile=Xtest.latin1.add + call assert_equal("elekwent", FirstSpellWord()) + + set spl=Xtest_us.latin1.spl + call assert_equal("elequint", FirstSpellWord()) + call assert_equal("elekwint", SecondSpellWord()) + + set spl=Xtest_gb.latin1.spl + call assert_equal("elekwint", FirstSpellWord()) + call assert_equal("elekwent", SecondSpellWord()) + + set spl=Xtest_nz.latin1.spl + call assert_equal("elequint", FirstSpellWord()) + call assert_equal("elekwent", SecondSpellWord()) + + set spl=Xtest_ca.latin1.spl + call assert_equal("elequint", FirstSpellWord()) + call assert_equal("elekwint", SecondSpellWord()) +endfunc + +func Test_region_error() + messages clear + call writefile(["/regions=usgbnz", "elequint/0"], "Xtest.latin1.add") + mkspell! Xtest.latin1.add.spl Xtest.latin1.add + call assert_match('Invalid region nr in Xtest.latin1.add line 2: 0', execute('messages')) + call delete('Xtest.latin1.add') + call delete('Xtest.latin1.add.spl') +endfunc + +" Check using z= in new buffer (crash fixed by patch 7.4a.028). +func Test_zeq_crash() + new + set maxmem=512 spell + call feedkeys('iasdz=:\"', 'tx') + + bwipe! +endfunc + +func LoadAffAndDic(aff_contents, dic_contents) + throw 'skipped: Nvim does not support enc=latin1' + set enc=latin1 + set spellfile= + call writefile(a:aff_contents, "Xtest.aff") + call writefile(a:dic_contents, "Xtest.dic") + " Generate a .spl file from a .dic and .aff file. + mkspell! Xtest Xtest + " use that spell file + set spl=Xtest.latin1.spl spell +endfunc + +func ListWords() + spelldump + %yank + quit + return split(@", "\n") +endfunc + +func TestGoodBadBase() + exe '1;/^good:' + normal 0f:]s + let prevbad = '' + let result = [] + while 1 + let [bad, a] = spellbadword() + if bad == '' || bad == prevbad || bad == 'badend' + break + endif + let prevbad = bad + let lst = spellsuggest(bad, 3) + normal mm + + call add(result, [bad, lst]) + normal `m]s + endwhile + return result +endfunc + +func RunGoodBad(good, bad, expected_words, expected_bad_words) + bwipe! + call setline(1, ["good: ", a:good, a:bad, " badend "]) + let words = ListWords() + call assert_equal(a:expected_words, words[1:-1]) + let bad_words = TestGoodBadBase() + call assert_equal(a:expected_bad_words, bad_words) + bwipe! +endfunc + +let g:test_data_aff1 = [ + \"SET ISO8859-1", + \"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ", + \"", + \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF", + \"", + \"SOFOFROM abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xBF", + \"SOFOTO ebctefghejklnnepkrstevvkesebctefghejklnnepkrstevvkeseeeeeeeceeeeeeeedneeeeeeeeeeepseeeeeeeeceeeeeeeedneeeeeeeeeeep?", + \"", + \"MIDWORD\t'-", + \"", + \"KEP =", + \"RAR ?", + \"BAD !", + \"", + \"PFX I N 1", + \"PFX I 0 in .", + \"", + \"PFX O Y 1", + \"PFX O 0 out .", + \"", + \"SFX S Y 2", + \"SFX S 0 s [^s]", + \"SFX S 0 es s", + \"", + \"SFX N N 3", + \"SFX N 0 en [^n]", + \"SFX N 0 nen n", + \"SFX N 0 n .", + \"", + \"REP 3", + \"REP g ch", + \"REP ch g", + \"REP svp s.v.p.", + \"", + \"MAP 9", + \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5", + \"MAP e\xE8\xE9\xEA\xEB", + \"MAP i\xEC\xED\xEE\xEF", + \"MAP o\xF2\xF3\xF4\xF5\xF6", + \"MAP u\xF9\xFA\xFB\xFC", + \"MAP n\xF1", + \"MAP c\xE7", + \"MAP y\xFF\xFD", + \"MAP s\xDF", + \ ] +let g:test_data_dic1 = [ + \"123456", + \"test/NO", + \"# comment", + \"wrong", + \"Comment", + \"OK", + \"uk", + \"put/ISO", + \"the end", + \"deol", + \"d\xE9\xF4r", + \ ] +let g:test_data_aff2 = [ + \"SET ISO8859-1", + \"", + \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF", + \"", + \"PFXPOSTPONE", + \"", + \"MIDWORD\t'-", + \"", + \"KEP =", + \"RAR ?", + \"BAD !", + \"", + \"PFX I N 1", + \"PFX I 0 in .", + \"", + \"PFX O Y 1", + \"PFX O 0 out [a-z]", + \"", + \"SFX S Y 2", + \"SFX S 0 s [^s]", + \"SFX S 0 es s", + \"", + \"SFX N N 3", + \"SFX N 0 en [^n]", + \"SFX N 0 nen n", + \"SFX N 0 n .", + \"", + \"REP 3", + \"REP g ch", + \"REP ch g", + \"REP svp s.v.p.", + \"", + \"MAP 9", + \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5", + \"MAP e\xE8\xE9\xEA\xEB", + \"MAP i\xEC\xED\xEE\xEF", + \"MAP o\xF2\xF3\xF4\xF5\xF6", + \"MAP u\xF9\xFA\xFB\xFC", + \"MAP n\xF1", + \"MAP c\xE7", + \"MAP y\xFF\xFD", + \"MAP s\xDF", + \ ] +let g:test_data_aff3 = [ + \"SET ISO8859-1", + \"", + \"COMPOUNDMIN 3", + \"COMPOUNDRULE m*", + \"NEEDCOMPOUND x", + \ ] +let g:test_data_dic3 = [ + \"1234", + \"foo/m", + \"bar/mx", + \"m\xEF/m", + \"la/mx", + \ ] +let g:test_data_aff4 = [ + \"SET ISO8859-1", + \"", + \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF", + \"", + \"COMPOUNDRULE m+", + \"COMPOUNDRULE sm*e", + \"COMPOUNDRULE sm+", + \"COMPOUNDMIN 3", + \"COMPOUNDWORDMAX 3", + \"COMPOUNDFORBIDFLAG t", + \"", + \"COMPOUNDSYLMAX 5", + \"SYLLABLE a\xE1e\xE9i\xEDo\xF3\xF6\xF5u\xFA\xFC\xFBy/aa/au/ea/ee/ei/ie/oa/oe/oo/ou/uu/ui", + \"", + \"MAP 9", + \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5", + \"MAP e\xE8\xE9\xEA\xEB", + \"MAP i\xEC\xED\xEE\xEF", + \"MAP o\xF2\xF3\xF4\xF5\xF6", + \"MAP u\xF9\xFA\xFB\xFC", + \"MAP n\xF1", + \"MAP c\xE7", + \"MAP y\xFF\xFD", + \"MAP s\xDF", + \"", + \"NEEDAFFIX x", + \"", + \"PFXPOSTPONE", + \"", + \"MIDWORD '-", + \"", + \"SFX q N 1", + \"SFX q 0 -ok .", + \"", + \"SFX a Y 2", + \"SFX a 0 s .", + \"SFX a 0 ize/t .", + \"", + \"PFX p N 1", + \"PFX p 0 pre .", + \"", + \"PFX P N 1", + \"PFX P 0 nou .", + \ ] +let g:test_data_dic4 = [ + \"1234", + \"word/mP", + \"util/am", + \"pro/xq", + \"tomato/m", + \"bork/mp", + \"start/s", + \"end/e", + \ ] +let g:test_data_aff5 = [ + \"SET ISO8859-1", + \"", + \"FLAG long", + \"", + \"NEEDAFFIX !!", + \"", + \"COMPOUNDRULE ssmm*ee", + \"", + \"NEEDCOMPOUND xx", + \"COMPOUNDPERMITFLAG pp", + \"", + \"SFX 13 Y 1", + \"SFX 13 0 bork .", + \"", + \"SFX a1 Y 1", + \"SFX a1 0 a1 .", + \"", + \"SFX a\xE9 Y 1", + \"SFX a\xE9 0 a\xE9 .", + \"", + \"PFX zz Y 1", + \"PFX zz 0 pre/pp .", + \"", + \"PFX yy Y 1", + \"PFX yy 0 nou .", + \ ] +let g:test_data_dic5 = [ + \"1234", + \"foo/a1a\xE9!!", + \"bar/zz13ee", + \"start/ss", + \"end/eeyy", + \"middle/mmxx", + \ ] +let g:test_data_aff6 = [ + \"SET ISO8859-1", + \"", + \"FLAG caplong", + \"", + \"NEEDAFFIX A!", + \"", + \"COMPOUNDRULE sMm*Ee", + \"", + \"NEEDCOMPOUND Xx", + \"", + \"COMPOUNDPERMITFLAG p", + \"", + \"SFX N3 Y 1", + \"SFX N3 0 bork .", + \"", + \"SFX A1 Y 1", + \"SFX A1 0 a1 .", + \"", + \"SFX A\xE9 Y 1", + \"SFX A\xE9 0 a\xE9 .", + \"", + \"PFX Zz Y 1", + \"PFX Zz 0 pre/p .", + \ ] +let g:test_data_dic6 = [ + \"1234", + \"mee/A1A\xE9A!", + \"bar/ZzN3Ee", + \"lead/s", + \"end/Ee", + \"middle/MmXx", + \ ] +let g:test_data_aff7 = [ + \"SET ISO8859-1", + \"", + \"FLAG num", + \"", + \"NEEDAFFIX 9999", + \"", + \"COMPOUNDRULE 2,77*123", + \"", + \"NEEDCOMPOUND 1", + \"COMPOUNDPERMITFLAG 432", + \"", + \"SFX 61003 Y 1", + \"SFX 61003 0 meat .", + \"", + \"SFX 391 Y 1", + \"SFX 391 0 a1 .", + \"", + \"SFX 111 Y 1", + \"SFX 111 0 a\xE9 .", + \"", + \"PFX 17 Y 1", + \"PFX 17 0 pre/432 .", + \ ] +let g:test_data_dic7 = [ + \"1234", + \"mee/391,111,9999", + \"bar/17,61003,123", + \"lead/2", + \"tail/123", + \"middle/77,1", + \ ] +let g:test_data_aff8 = [ + \"SET ISO8859-1", + \"", + \"NOSPLITSUGS", + \ ] +let g:test_data_dic8 = [ + \"1234", + \"foo", + \"bar", + \"faabar", + \ ] +let g:test_data_aff9 = [ + \ ] +let g:test_data_dic9 = [ + \"1234", + \"foo", + \"bar", + \ ] +let g:test_data_aff_sal = [ + \"SET ISO8859-1", + \"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ", + \"", + \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF", + \"", + \"MIDWORD\t'-", + \"", + \"KEP =", + \"RAR ?", + \"BAD !", + \"", + \"PFX I N 1", + \"PFX I 0 in .", + \"", + \"PFX O Y 1", + \"PFX O 0 out .", + \"", + \"SFX S Y 2", + \"SFX S 0 s [^s]", + \"SFX S 0 es s", + \"", + \"SFX N N 3", + \"SFX N 0 en [^n]", + \"SFX N 0 nen n", + \"SFX N 0 n .", + \"", + \"REP 3", + \"REP g ch", + \"REP ch g", + \"REP svp s.v.p.", + \"", + \"MAP 9", + \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5", + \"MAP e\xE8\xE9\xEA\xEB", + \"MAP i\xEC\xED\xEE\xEF", + \"MAP o\xF2\xF3\xF4\xF5\xF6", + \"MAP u\xF9\xFA\xFB\xFC", + \"MAP n\xF1", + \"MAP c\xE7", + \"MAP y\xFF\xFD", + \"MAP s\xDF", + \"", + \"SAL AH(AEIOUY)-^ *H", + \"SAL AR(AEIOUY)-^ *R", + \"SAL A(HR)^ *", + \"SAL A^ *", + \"SAL AH(AEIOUY)- H", + \"SAL AR(AEIOUY)- R", + \"SAL A(HR) _", + \"SAL \xC0^ *", + \"SAL \xC5^ *", + \"SAL BB- _", + \"SAL B B", + \"SAL CQ- _", + \"SAL CIA X", + \"SAL CH X", + \"SAL C(EIY)- S", + \"SAL CK K", + \"SAL COUGH^ KF", + \"SAL CC< C", + \"SAL C K", + \"SAL DG(EIY) K", + \"SAL DD- _", + \"SAL D T", + \"SAL \xC9< E", + \"SAL EH(AEIOUY)-^ *H", + \"SAL ER(AEIOUY)-^ *R", + \"SAL E(HR)^ *", + \"SAL ENOUGH^$ *NF", + \"SAL E^ *", + \"SAL EH(AEIOUY)- H", + \"SAL ER(AEIOUY)- R", + \"SAL E(HR) _", + \"SAL FF- _", + \"SAL F F", + \"SAL GN^ N", + \"SAL GN$ N", + \"SAL GNS$ NS", + \"SAL GNED$ N", + \"SAL GH(AEIOUY)- K", + \"SAL GH _", + \"SAL GG9 K", + \"SAL G K", + \"SAL H H", + \"SAL IH(AEIOUY)-^ *H", + \"SAL IR(AEIOUY)-^ *R", + \"SAL I(HR)^ *", + \"SAL I^ *", + \"SAL ING6 N", + \"SAL IH(AEIOUY)- H", + \"SAL IR(AEIOUY)- R", + \"SAL I(HR) _", + \"SAL J K", + \"SAL KN^ N", + \"SAL KK- _", + \"SAL K K", + \"SAL LAUGH^ LF", + \"SAL LL- _", + \"SAL L L", + \"SAL MB$ M", + \"SAL MM M", + \"SAL M M", + \"SAL NN- _", + \"SAL N N", + \"SAL OH(AEIOUY)-^ *H", + \"SAL OR(AEIOUY)-^ *R", + \"SAL O(HR)^ *", + \"SAL O^ *", + \"SAL OH(AEIOUY)- H", + \"SAL OR(AEIOUY)- R", + \"SAL O(HR) _", + \"SAL PH F", + \"SAL PN^ N", + \"SAL PP- _", + \"SAL P P", + \"SAL Q K", + \"SAL RH^ R", + \"SAL ROUGH^ RF", + \"SAL RR- _", + \"SAL R R", + \"SAL SCH(EOU)- SK", + \"SAL SC(IEY)- S", + \"SAL SH X", + \"SAL SI(AO)- X", + \"SAL SS- _", + \"SAL S S", + \"SAL TI(AO)- X", + \"SAL TH @", + \"SAL TCH-- _", + \"SAL TOUGH^ TF", + \"SAL TT- _", + \"SAL T T", + \"SAL UH(AEIOUY)-^ *H", + \"SAL UR(AEIOUY)-^ *R", + \"SAL U(HR)^ *", + \"SAL U^ *", + \"SAL UH(AEIOUY)- H", + \"SAL UR(AEIOUY)- R", + \"SAL U(HR) _", + \"SAL V^ W", + \"SAL V F", + \"SAL WR^ R", + \"SAL WH^ W", + \"SAL W(AEIOU)- W", + \"SAL X^ S", + \"SAL X KS", + \"SAL Y(AEIOU)- Y", + \"SAL ZZ- _", + \"SAL Z S", + \ ] diff --git a/src/nvim/testdir/test_stat.vim b/src/nvim/testdir/test_stat.vim index dee0d13e84..1239fe9427 100644 --- a/src/nvim/testdir/test_stat.vim +++ b/src/nvim/testdir/test_stat.vim @@ -1,20 +1,41 @@ " Tests for stat functions and checktime -func Test_existent_file() +func CheckFileTime(doSleep) let fname = 'Xtest.tmp' + let result = 0 let ts = localtime() + if a:doSleep + sleep 1 + endif let fl = ['Hello World!'] call writefile(fl, fname) let tf = getftime(fname) + if a:doSleep + sleep 1 + endif let te = localtime() - call assert_true(ts <= tf && tf <= te) - call assert_equal(strlen(fl[0] . "\n"), getfsize(fname)) - call assert_equal('file', getftype(fname)) - call assert_equal('rw-', getfperm(fname)[0:2]) + let time_correct = (ts <= tf && tf <= te) + if a:doSleep || time_correct + call assert_true(time_correct) + call assert_equal(strlen(fl[0] . "\n"), getfsize(fname)) + call assert_equal('file', getftype(fname)) + call assert_equal('rw-', getfperm(fname)[0:2]) + let result = 1 + endif call delete(fname) + return result +endfunc + +func Test_existent_file() + " On some systems the file timestamp is rounded to a multiple of 2 seconds. + " We need to sleep to handle that, but that makes the test slow. First try + " without the sleep, and if it fails try again with the sleep. + if CheckFileTime(0) == 0 + call CheckFileTime(1) + endif endfunc func Test_existent_directory() diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim index 82898df92d..cf85bd58ac 100644 --- a/src/nvim/testdir/test_statusline.vim +++ b/src/nvim/testdir/test_statusline.vim @@ -1,19 +1,39 @@ -function! StatuslineWithCaughtError() +" Test 'statusline' +" +" Not tested yet: +" %a +" %N +" %T +" %X +" %* + +source view_util.vim + +func s:get_statusline() + return ScreenLines(&lines - 1, &columns)[0] +endfunc + +func StatuslineWithCaughtError() let s:func_in_statusline_called = 1 try call eval('unknown expression') catch endtry return '' -endfunction +endfunc -function! StatuslineWithError() +func StatuslineWithError() let s:func_in_statusline_called = 1 call eval('unknown expression') return '' -endfunction +endfunc -function! Test_caught_error_in_statusline() +" Function used to display syntax group. +func SyntaxItem() + return synIDattr(synID(line("."),col("."),1),"name") +endfunc + +func Test_caught_error_in_statusline() let s:func_in_statusline_called = 0 set laststatus=2 let statusline = '%{StatuslineWithCaughtError()}' @@ -22,9 +42,9 @@ function! Test_caught_error_in_statusline() call assert_true(s:func_in_statusline_called) call assert_equal(statusline, &statusline) set statusline= -endfunction +endfunc -function! Test_statusline_will_be_disabled_with_error() +func Test_statusline_will_be_disabled_with_error() let s:func_in_statusline_called = 0 set laststatus=2 let statusline = '%{StatuslineWithError()}' @@ -36,4 +56,219 @@ function! Test_statusline_will_be_disabled_with_error() call assert_true(s:func_in_statusline_called) call assert_equal('', &statusline) set statusline= -endfunction +endfunc + +func Test_statusline() + new Xstatusline + only + set laststatus=2 + set splitbelow + call setline(1, range(1, 200)) + + " %b: Value of character under cursor. + " %B: As above, in hexadecimal. + call cursor(180, 2) + set statusline=%b,%B + call assert_match('^56,38\s*$', s:get_statusline()) + + " %o: Byte number in file of byte under cursor, first byte is 1. + " %O: As above, in hexadecimal. + set statusline=%o,%O + set fileformat=dos + call assert_match('^789,315\s*$', s:get_statusline()) + set fileformat=mac + call assert_match('^610,262\s*$', s:get_statusline()) + set fileformat=unix + call assert_match('^610,262\s*$', s:get_statusline()) + set fileformat& + + " %f: Path to the file in the buffer, as typed or relative to current dir. + set statusline=%f + call assert_match('^Xstatusline\s*$', s:get_statusline()) + + " %F: Full path to the file in the buffer. + set statusline=%F + call assert_match('/testdir/Xstatusline\s*$', s:get_statusline()) + + " %h: Help buffer flag, text is "[help]". + " %H: Help buffer flag, text is ",HLP". + set statusline=%h,%H + call assert_match('^,\s*$', s:get_statusline()) + help + call assert_match('^\[Help\],HLP\s*$', s:get_statusline()) + helpclose + + " %k: Value of "b:keymap_name" or 'keymap' + " when :lmap mappings are being used: <keymap>" + set statusline=%k + if has('keymap') + set keymap=esperanto + call assert_match('^<Eo>\s*$', s:get_statusline()) + set keymap& + else + call assert_match('^\s*$', s:get_statusline()) + endif + + " %l: Line number. + " %L: Number of line in buffer. + " %c: Column number. + set statusline=%l/%L,%c + call assert_match('^180/200,2\s*$', s:get_statusline()) + + " %m: Modified flag, text is "[+]", "[-]" if 'modifiable' is off. + " %M: Modified flag, text is ",+" or ",-". + set statusline=%m%M + call assert_match('^\[+\],+\s*$', s:get_statusline()) + set nomodifiable + call assert_match('^\[+-\],+-\s*$', s:get_statusline()) + write + call assert_match('^\[-\],-\s*$', s:get_statusline()) + set modifiable& + call assert_match('^\s*$', s:get_statusline()) + + " %n: Buffer number. + set statusline=%n + call assert_match('^'.bufnr('%').'\s*$', s:get_statusline()) + + " %p: Percentage through file in lines as in CTRL-G. + " %P: Percentage through file of displayed window. + set statusline=%p,%P + 0 + call assert_match('^0,Top\s*$', s:get_statusline()) + norm G + call assert_match('^100,Bot\s*$', s:get_statusline()) + 180 + " Don't check the exact percentage as it depends on the window size + call assert_match('^90,\(Top\|Bot\|\d\+%\)\s*$', s:get_statusline()) + + " %q: "[Quickfix List]", "[Location List]" or empty. + set statusline=%q + call assert_match('^\s*$', s:get_statusline()) + copen + call assert_match('^\[Quickfix List\]\s*$', s:get_statusline()) + cclose + lexpr getline(1, 2) + lopen + call assert_match('^\[Location List\]\s*$', s:get_statusline()) + lclose + + " %r: Readonly flag, text is "[RO]". + " %R: Readonly flag, text is ",RO". + set statusline=%r,%R + call assert_match('^,\s*$', s:get_statusline()) + help + call assert_match('^\[RO\],RO\s*$', s:get_statusline()) + helpclose + + " %t: File name (tail) of file in the buffer. + set statusline=%t + call assert_match('^Xstatusline\s*$', s:get_statusline()) + + " %v: Virtual column number. + " %V: Virtual column number as -{num}. Not displayed if equal to 'c'. + call cursor(180, 2) + set statusline=%v,%V + call assert_match('^2,\s*$', s:get_statusline()) + set virtualedit=all + norm 10| + call assert_match('^10,-10\s*$', s:get_statusline()) + set virtualedit& + + " %w: Preview window flag, text is "[Preview]". + " %W: Preview window flag, text is ",PRV". + set statusline=%w%W + call assert_match('^\s*$', s:get_statusline()) + pedit + wincmd j + call assert_match('^\[Preview\],PRV\s*$', s:get_statusline()) + pclose + + " %y: Type of file in the buffer, e.g., "[vim]". See 'filetype'. + " %Y: Type of file in the buffer, e.g., ",VIM". See 'filetype'. + set statusline=%y\ %Y + call assert_match('^\s*$', s:get_statusline()) + setfiletype vim + call assert_match('^\[vim\] VIM\s*$', s:get_statusline()) + + " %=: Separation point between left and right aligned items. + set statusline=foo%=bar + call assert_match('^foo\s\+bar\s*$', s:get_statusline()) + + " Test min/max width, leading zeroes, left/right justify. + set statusline=%04B + call cursor(180, 2) + call assert_match('^0038\s*$', s:get_statusline()) + set statusline=#%4B# + call assert_match('^# 38#\s*$', s:get_statusline()) + set statusline=#%-4B# + call assert_match('^#38 #\s*$', s:get_statusline()) + set statusline=%.6f + call assert_match('^<sline\s*$', s:get_statusline()) + + " %<: Where to truncate. + exe 'set statusline=a%<b' . repeat('c', 1000) . 'd' + call assert_match('^a<c*d$', s:get_statusline()) + exe 'set statusline=a' . repeat('b', 1000) . '%<c' + call assert_match('^ab*>$', s:get_statusline()) + + "%{: Evaluate expression between '%{' and '}' and substitute result. + syntax on + set statusline=%{SyntaxItem()} + call assert_match('^vimNumber\s*$', s:get_statusline()) + s/^/"/ + call assert_match('^vimLineComment\s*$', s:get_statusline()) + syntax off + + "%(: Start of item group. + set statusline=ab%(cd%q%)de + call assert_match('^abde\s*$', s:get_statusline()) + copen + call assert_match('^abcd\[Quickfix List\1]de\s*$', s:get_statusline()) + cclose + + " %#: Set highlight group. The name must follow and then a # again. + set statusline=ab%#Todo#cd%#Error#ef + call assert_match('^abcdef\s*$', s:get_statusline()) + let sa1=screenattr(&lines - 1, 1) + let sa2=screenattr(&lines - 1, 3) + let sa3=screenattr(&lines - 1, 5) + call assert_notequal(sa1, sa2) + call assert_notequal(sa1, sa3) + call assert_notequal(sa2, sa3) + call assert_equal(sa1, screenattr(&lines - 1, 2)) + call assert_equal(sa2, screenattr(&lines - 1, 4)) + call assert_equal(sa3, screenattr(&lines - 1, 6)) + call assert_equal(sa3, screenattr(&lines - 1, 7)) + + " %*: Set highlight group to User{N} + set statusline=a%1*b%0*c + call assert_match('^abc\s*$', s:get_statusline()) + let sa1=screenattr(&lines - 1, 1) + let sa2=screenattr(&lines - 1, 2) + let sa3=screenattr(&lines - 1, 3) + call assert_equal(sa1, sa3) + call assert_notequal(sa1, sa2) + + " %%: a percent sign. + set statusline=10%% + call assert_match('^10%\s*$', s:get_statusline()) + + " %!: evaluated expression is used as the option value + set statusline=%!2*3+1 + call assert_match('7\s*$', s:get_statusline()) + + " Check statusline in current and non-current window + " with the 'fillchars' option. + set fillchars=stl:^,stlnc:=,vert:\|,fold:-,diff:- + vsplit + set statusline=x%=y + call assert_match('^x^\+y^x=\+y$', s:get_statusline()) + set fillchars& + close + + %bw! + call delete('Xstatusline') + set statusline& + set laststatus& + set splitbelow& +endfunc diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index e2b6de03c3..75ce24de46 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -39,3 +39,285 @@ function! Test_multiline_subst() call assert_equal('xxxxx', getline(13)) enew! endfunction + +function! Test_substitute_variants() + " Validate that all the 2-/3-letter variants which embed the flags into the + " command name actually work. + enew! + let ln = 'Testing string' + let variants = [ + \ { 'cmd': ':s/Test/test/c', 'exp': 'testing string', 'prompt': 'y' }, + \ { 'cmd': ':s/foo/bar/ce', 'exp': ln }, + \ { 'cmd': ':s/t/r/cg', 'exp': 'Tesring srring', 'prompt': 'a' }, + \ { 'cmd': ':s/t/r/ci', 'exp': 'resting string', 'prompt': 'y' }, + \ { 'cmd': ':s/t/r/cI', 'exp': 'Tesring string', 'prompt': 'y' }, + \ { 'cmd': ':s/t/r/cn', 'exp': ln }, + \ { 'cmd': ':s/t/r/cp', 'exp': 'Tesring string', 'prompt': 'y' }, + \ { 'cmd': ':s/t/r/cl', 'exp': 'Tesring string', 'prompt': 'y' }, + \ { 'cmd': ':s/t/r/gc', 'exp': 'Tesring srring', 'prompt': 'a' }, + \ { 'cmd': ':s/foo/bar/ge', 'exp': ln }, + \ { 'cmd': ':s/t/r/g', 'exp': 'Tesring srring' }, + \ { 'cmd': ':s/t/r/gi', 'exp': 'resring srring' }, + \ { 'cmd': ':s/t/r/gI', 'exp': 'Tesring srring' }, + \ { 'cmd': ':s/t/r/gn', 'exp': ln }, + \ { 'cmd': ':s/t/r/gp', 'exp': 'Tesring srring' }, + \ { 'cmd': ':s/t/r/gl', 'exp': 'Tesring srring' }, + \ { 'cmd': ':s//r/gr', 'exp': 'Testr strr' }, + \ { 'cmd': ':s/t/r/ic', 'exp': 'resting string', 'prompt': 'y' }, + \ { 'cmd': ':s/foo/bar/ie', 'exp': ln }, + \ { 'cmd': ':s/t/r/i', 'exp': 'resting string' }, + \ { 'cmd': ':s/t/r/iI', 'exp': 'Tesring string' }, + \ { 'cmd': ':s/t/r/in', 'exp': ln }, + \ { 'cmd': ':s/t/r/ip', 'exp': 'resting string' }, + \ { 'cmd': ':s//r/ir', 'exp': 'Testr string' }, + \ { 'cmd': ':s/t/r/Ic', 'exp': 'Tesring string', 'prompt': 'y' }, + \ { 'cmd': ':s/foo/bar/Ie', 'exp': ln }, + \ { 'cmd': ':s/t/r/Ig', 'exp': 'Tesring srring' }, + \ { 'cmd': ':s/t/r/Ii', 'exp': 'resting string' }, + \ { 'cmd': ':s/t/r/I', 'exp': 'Tesring string' }, + \ { 'cmd': ':s/t/r/Ip', 'exp': 'Tesring string' }, + \ { 'cmd': ':s/t/r/Il', 'exp': 'Tesring string' }, + \ { 'cmd': ':s//r/Ir', 'exp': 'Testr string' }, + \ { 'cmd': ':s//r/rc', 'exp': 'Testr string', 'prompt': 'y' }, + \ { 'cmd': ':s//r/rg', 'exp': 'Testr strr' }, + \ { 'cmd': ':s//r/ri', 'exp': 'Testr string' }, + \ { 'cmd': ':s//r/rI', 'exp': 'Testr string' }, + \ { 'cmd': ':s//r/rn', 'exp': 'Testing string' }, + \ { 'cmd': ':s//r/rp', 'exp': 'Testr string' }, + \ { 'cmd': ':s//r/rl', 'exp': 'Testr string' }, + \ { 'cmd': ':s//r/r', 'exp': 'Testr string' }, + \] + + for var in variants + for run in [1, 2] + let cmd = var.cmd + if run == 2 && cmd =~ "/.*/.*/." + " Change :s/from/to/{flags} to :s{flags} + let cmd = substitute(cmd, '/.*/', '', '') + endif + call setline(1, [ln]) + let msg = printf('using "%s"', cmd) + let @/='ing' + let v:errmsg = '' + call feedkeys(cmd . "\<CR>" . get(var, 'prompt', ''), 'ntx') + " No error should exist (matters for testing e flag) + call assert_equal('', v:errmsg, msg) + call assert_equal(var.exp, getline('.'), msg) + endfor + endfor +endfunction + +func Test_substitute_repeat() + " This caused an invalid memory access. + split Xfile + s/^/x + call feedkeys("Qsc\<CR>y", 'tx') + bwipe! +endfunc + +" Tests for *sub-replace-special* and *sub-replace-expression* on :substitute. + +" Execute a list of :substitute command tests +func Run_SubCmd_Tests(tests) + enew! + for t in a:tests + let start = line('.') + 1 + let end = start + len(t[2]) - 1 + exe "normal o" . t[0] + call cursor(start, 1) + exe t[1] + call assert_equal(t[2], getline(start, end), t[1]) + endfor + enew! +endfunc + +func Test_sub_cmd_1() + set magic + set cpo& + + " List entry format: [input, cmd, output] + let tests = [['A', 's/A/&&/', ['AA']], + \ ['B', 's/B/\&/', ['&']], + \ ['C123456789', 's/C\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)/\0\9\8\7\6\5\4\3\2\1/', ['C123456789987654321']], + \ ['D', 's/D/d/', ['d']], + \ ['E', 's/E/~/', ['d']], + \ ['F', 's/F/\~/', ['~']], + \ ['G', 's/G/\ugg/', ['Gg']], + \ ['H', 's/H/\Uh\Eh/', ['Hh']], + \ ['I', 's/I/\lII/', ['iI']], + \ ['J', 's/J/\LJ\EJ/', ['jJ']], + \ ['K', 's/K/\Uk\ek/', ['Kk']], + \ ['lLl', "s/L/\<C-V>\<C-M>/", ["l\<C-V>", 'l']], + \ ['mMm', 's/M/\r/', ['m', 'm']], + \ ['nNn', "s/N/\\\<C-V>\<C-M>/", ["n\<C-V>", 'n']], + \ ['oOo', 's/O/\n/', ["o\no"]], + \ ['pPp', 's/P/\b/', ["p\<C-H>p"]], + \ ['qQq', 's/Q/\t/', ["q\tq"]], + \ ['rRr', 's/R/\\/', ['r\r']], + \ ['sSs', 's/S/\c/', ['scs']], + \ ['tTt', "s/T/\<C-V>\<C-J>/", ["t\<C-V>\<C-J>t"]], + \ ['U', 's/U/\L\uuUu\l\EU/', ['UuuU']], + \ ['V', 's/V/\U\lVvV\u\Ev/', ['vVVv']] + \ ] + call Run_SubCmd_Tests(tests) +endfunc + +func Test_sub_cmd_2() + set nomagic + set cpo& + + " List entry format: [input, cmd, output] + let tests = [['A', 's/A/&&/', ['&&']], + \ ['B', 's/B/\&/', ['B']], + \ ['C123456789', 's/\mC\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)/\0\9\8\7\6\5\4\3\2\1/', ['C123456789987654321']], + \ ['D', 's/D/d/', ['d']], + \ ['E', 's/E/~/', ['~']], + \ ['F', 's/F/\~/', ['~']], + \ ['G', 's/G/\ugg/', ['Gg']], + \ ['H', 's/H/\Uh\Eh/', ['Hh']], + \ ['I', 's/I/\lII/', ['iI']], + \ ['J', 's/J/\LJ\EJ/', ['jJ']], + \ ['K', 's/K/\Uk\ek/', ['Kk']], + \ ['lLl', "s/L/\<C-V>\<C-M>/", ["l\<C-V>", 'l']], + \ ['mMm', 's/M/\r/', ['m', 'm']], + \ ['nNn', "s/N/\\\<C-V>\<C-M>/", ["n\<C-V>", 'n']], + \ ['oOo', 's/O/\n/', ["o\no"]], + \ ['pPp', 's/P/\b/', ["p\<C-H>p"]], + \ ['qQq', 's/Q/\t/', ["q\tq"]], + \ ['rRr', 's/R/\\/', ['r\r']], + \ ['sSs', 's/S/\c/', ['scs']], + \ ['tTt', "s/T/\<C-V>\<C-J>/", ["t\<C-V>\<C-J>t"]], + \ ['U', 's/U/\L\uuUu\l\EU/', ['UuuU']], + \ ['V', 's/V/\U\lVvV\u\Ev/', ['vVVv']] + \ ] + call Run_SubCmd_Tests(tests) +endfunc + +func Test_sub_cmd_3() + set nomagic + set cpo& + + " List entry format: [input, cmd, output] + let tests = [['aAa', "s/A/\\='\\'/", ['a\a']], + \ ['bBb', "s/B/\\='\\\\'/", ['b\\b']], + \ ['cCc', "s/C/\\='\<C-V>\<C-M>'/", ["c\<C-V>", 'c']], + \ ['dDd', "s/D/\\='\\\<C-V>\<C-M>'/", ["d\\\<C-V>", 'd']], + \ ['eEe', "s/E/\\='\\\\\<C-V>\<C-M>'/", ["e\\\\\<C-V>", 'e']], + \ ['fFf', "s/F/\\='\r'/", ['f', 'f']], + \ ['gGg', "s/G/\\='\<C-V>\<C-J>'/", ["g\<C-V>", 'g']], + \ ['hHh', "s/H/\\='\\\<C-V>\<C-J>'/", ["h\\\<C-V>", 'h']], + \ ['iIi', "s/I/\\='\\\\\<C-V>\<C-J>'/", ["i\\\\\<C-V>", 'i']], + \ ['jJj', "s/J/\\='\n'/", ['j', 'j']], + \ ['kKk', 's/K/\="\r"/', ['k', 'k']], + \ ['lLl', 's/L/\="\n"/', ['l', 'l']] + \ ] + call Run_SubCmd_Tests(tests) +endfunc + +" Test for submatch() on :substitue. +func Test_sub_cmd_4() + set magic& + set cpo& + + " List entry format: [input, cmd, output] + let tests = [ ['aAa', "s/A/\\=substitute(submatch(0), '.', '\\', '')/", + \ ['a\a']], + \ ['bBb', "s/B/\\=substitute(submatch(0), '.', '\\', '')/", + \ ['b\b']], + \ ['cCc', "s/C/\\=substitute(submatch(0), '.', '\<C-V>\<C-M>', '')/", + \ ["c\<C-V>", 'c']], + \ ['dDd', "s/D/\\=substitute(submatch(0), '.', '\\\<C-V>\<C-M>', '')/", + \ ["d\<C-V>", 'd']], + \ ['eEe', "s/E/\\=substitute(submatch(0), '.', '\\\\\<C-V>\<C-M>', '')/", + \ ["e\\\<C-V>", 'e']], + \ ['fFf', "s/F/\\=substitute(submatch(0), '.', '\\r', '')/", + \ ['f', 'f']], + \ ['gGg', 's/G/\=substitute(submatch(0), ".", "\<C-V>\<C-J>", "")/', + \ ["g\<C-V>", 'g']], + \ ['hHh', 's/H/\=substitute(submatch(0), ".", "\\\<C-V>\<C-J>", "")/', + \ ["h\<C-V>", 'h']], + \ ['iIi', 's/I/\=substitute(submatch(0), ".", "\\\\\<C-V>\<C-J>", "")/', + \ ["i\\\<C-V>", 'i']], + \ ['jJj', "s/J/\\=substitute(submatch(0), '.', '\\n', '')/", + \ ['j', 'j']], + \ ['kKk', "s/K/\\=substitute(submatch(0), '.', '\\r', '')/", + \ ['k', 'k']], + \ ['lLl', "s/L/\\=substitute(submatch(0), '.', '\\n', '')/", + \ ['l', 'l']], + \ ] + call Run_SubCmd_Tests(tests) +endfunc + +func Test_sub_cmd_5() + set magic& + set cpo& + + " List entry format: [input, cmd, output] + let tests = [ ['A123456789', 's/A\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)/\=submatch(0) . submatch(9) . submatch(8) . submatch(7) . submatch(6) . submatch(5) . submatch(4) . submatch(3) . submatch(2) . submatch(1)/', ['A123456789987654321']], + \ ['B123456789', 's/B\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)/\=string([submatch(0, 1), submatch(9, 1), submatch(8, 1), submatch(7, 1), submatch(6, 1), submatch(5, 1), submatch(4, 1), submatch(3, 1), submatch(2, 1), submatch(1, 1)])/', ["[['B123456789'], ['9'], ['8'], ['7'], ['6'], ['5'], ['4'], ['3'], ['2'], ['1']]"]], + \ ] + call Run_SubCmd_Tests(tests) +endfunc + +" Test for *:s%* on :substitute. +func Test_sub_cmd_6() + throw "skipped: Nvim removed POSIX-related 'cpoptions' flags" + set magic& + set cpo+=/ + + " List entry format: [input, cmd, output] + let tests = [ ['A', 's/A/a/', ['a']], + \ ['B', 's/B/%/', ['a']], + \ ] + call Run_SubCmd_Tests(tests) + + set cpo-=/ + let tests = [ ['C', 's/C/c/', ['c']], + \ ['D', 's/D/%/', ['%']], + \ ] + call Run_SubCmd_Tests(tests) + + set cpo& +endfunc + +" Test for :s replacing \n with line break. +func Test_sub_cmd_7() + set magic& + set cpo& + + " List entry format: [input, cmd, output] + let tests = [ ["A\<C-V>\<C-M>A", 's/A./\=submatch(0)/', ['A', 'A']], + \ ["B\<C-V>\<C-J>B", 's/B./\=submatch(0)/', ['B', 'B']], + \ ["C\<C-V>\<C-J>C", 's/C./\=strtrans(string(submatch(0, 1)))/', [strtrans("['C\<C-J>']C")]], + \ ["D\<C-V>\<C-J>\nD", 's/D.\nD/\=strtrans(string(submatch(0, 1)))/', [strtrans("['D\<C-J>', 'D']")]], + \ ["E\<C-V>\<C-J>\n\<C-V>\<C-J>\n\<C-V>\<C-J>\n\<C-V>\<C-J>\n\<C-V>\<C-J>E", 's/E\_.\{-}E/\=strtrans(string(submatch(0, 1)))/', [strtrans("['E\<C-J>', '\<C-J>', '\<C-J>', '\<C-J>', '\<C-J>E']")]], + \ ] + call Run_SubCmd_Tests(tests) + + exe "normal oQ\nQ\<Esc>k" + call assert_fails('s/Q[^\n]Q/\=submatch(0)."foobar"/', 'E486') + enew! +endfunc + +func TitleString() + let check = 'foo' =~ 'bar' + return "" +endfunc + +func Test_sub_cmd_8() + set titlestring=%{TitleString()} + + enew! + call append(0, ['', 'test_one', 'test_two']) + call cursor(1,1) + /^test_one/s/.*/\="foo\nbar"/ + call assert_equal('foo', getline(2)) + call assert_equal('bar', getline(3)) + call feedkeys(':/^test_two/s/.*/\="foo\nbar"/c', "t") + call feedkeys("\<CR>y", "xt") + call assert_equal('foo', getline(4)) + call assert_equal('bar', getline(5)) + + enew! + set titlestring& +endfunc diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 6c084dd2a7..8465fe7d45 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -1,5 +1,7 @@ " Test for syntax and syntax iskeyword option +source view_util.vim + func GetSyntaxItem(pat) let c = '' let a = ['a', getreg('a'), getregtype('a')] @@ -152,9 +154,231 @@ func Test_syntax_completion() call feedkeys(":syn sync \<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"syn sync ccomment clear fromstart linebreaks= linecont lines= match maxlines= minlines= region', @:) + " Check that clearing "Aap" avoids it showing up before Boolean. + hi Aap ctermfg=blue + call feedkeys(":syn list \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_match('^"syn list Aap Boolean Character ', @:) + hi clear Aap + call feedkeys(":syn list \<C-A>\<C-B>\"\<CR>", 'tx') call assert_match('^"syn list Boolean Character ', @:) call feedkeys(":syn match \<C-A>\<C-B>\"\<CR>", 'tx') call assert_match('^"syn match Boolean Character ', @:) -endfunc
\ No newline at end of file +endfunc + +func Test_syntax_arg_skipped() + syn clear + syntax case ignore + if 0 + syntax case match + endif + call assert_match('case ignore', execute('syntax case')) + + syn keyword Foo foo + call assert_match('Foo', execute('syntax')) + syn clear + call assert_match('case match', execute('syntax case')) + call assert_notmatch('Foo', execute('syntax')) + + if has('conceal') + syn clear + syntax conceal on + if 0 + syntax conceal off + endif + call assert_match('conceal on', execute('syntax conceal')) + syn clear + call assert_match('conceal off', execute('syntax conceal')) + endif + + syntax conceal on + syntax conceal off + call assert_match('conceal off', execute('syntax conceal')) + + syntax region Bar start=/</ end=/>/ + if 0 + syntax region NotTest start=/</ end=/>/ contains=@Spell + endif + call assert_match('Bar', execute('syntax')) + call assert_notmatch('NotTest', execute('syntax')) + call assert_notmatch('Spell', execute('syntax')) + + hi Foo ctermfg=blue + let a = execute('hi Foo') + if 0 + syntax rest + endif + call assert_equal(a, execute('hi Foo')) + hi clear Bar + hi clear Foo + + set ft=tags + syn off + if 0 + syntax enable + endif + call assert_match('No Syntax items defined', execute('syntax')) + syntax enable + call assert_match('tagComment', execute('syntax')) + set ft= + + syn clear + if 0 + syntax include @Spell nothing + endif + call assert_notmatch('Spell', execute('syntax')) + + syn clear + syn iskeyword 48-57,$,_ + call assert_match('48-57,$,_', execute('syntax iskeyword')) + if 0 + syn clear + syn iskeyword clear + endif + call assert_match('48-57,$,_', execute('syntax iskeyword')) + syn iskeyword clear + call assert_match('not set', execute('syntax iskeyword')) + syn iskeyword 48-57,$,_ + syn clear + call assert_match('not set', execute('syntax iskeyword')) + + syn clear + syn keyword Foo foo + if 0 + syn keyword NotAdded bar + endif + call assert_match('Foo', execute('syntax')) + call assert_notmatch('NotAdded', execute('highlight')) + + syn clear + syn keyword Foo foo + call assert_match('Foo', execute('syntax')) + call assert_match('Foo', execute('syntax list')) + call assert_notmatch('Foo', execute('if 0 | syntax | endif')) + call assert_notmatch('Foo', execute('if 0 | syntax list | endif')) + + syn clear + syn match Fopi /asdf/ + if 0 + syn match Fopx /asdf/ + endif + call assert_match('Fopi', execute('syntax')) + call assert_notmatch('Fopx', execute('syntax')) + + syn clear + syn spell toplevel + call assert_match('spell toplevel', execute('syntax spell')) + if 0 + syn spell notoplevel + endif + call assert_match('spell toplevel', execute('syntax spell')) + syn spell notoplevel + call assert_match('spell notoplevel', execute('syntax spell')) + syn spell default + call assert_match('spell default', execute('syntax spell')) + + syn clear + if 0 + syntax cluster Spell + endif + call assert_notmatch('Spell', execute('syntax')) + + syn clear + syn keyword Foo foo + syn sync ccomment + syn sync maxlines=5 + if 0 + syn sync maxlines=11 + endif + call assert_match('on C-style comments', execute('syntax sync')) + call assert_match('maximal 5 lines', execute('syntax sync')) + syn sync clear + if 0 + syn sync ccomment + endif + call assert_notmatch('on C-style comments', execute('syntax sync')) + + syn clear +endfunc + +func Test_invalid_arg() + call assert_fails('syntax case asdf', 'E390:') + call assert_fails('syntax conceal asdf', 'E390:') + call assert_fails('syntax spell asdf', 'E390:') +endfunc + +func Test_syn_sync() + syntax region HereGroup start=/this/ end=/that/ + syntax sync match SyncHere grouphere HereGroup "pattern" + call assert_match('SyncHere', execute('syntax sync')) + syn sync clear + call assert_notmatch('SyncHere', execute('syntax sync')) + syn clear +endfunc + +func Test_syn_clear() + syntax keyword Foo foo + syntax keyword Bar tar + call assert_match('Foo', execute('syntax')) + call assert_match('Bar', execute('syntax')) + call assert_equal('Foo', synIDattr(hlID("Foo"), "name")) + syn clear Foo + call assert_notmatch('Foo', execute('syntax')) + call assert_match('Bar', execute('syntax')) + call assert_equal('Foo', synIDattr(hlID("Foo"), "name")) + syn clear Foo Bar + call assert_notmatch('Foo', execute('syntax')) + call assert_notmatch('Bar', execute('syntax')) + hi clear Foo + call assert_equal('Foo', synIDattr(hlID("Foo"), "name")) + hi clear Bar +endfunc + +func Test_invalid_name() + syn clear + syn keyword Nop yes + call assert_fails("syntax keyword Wr\x17ong bar", 'E669:') + syntax keyword @Wrong bar + call assert_match('W18:', execute('1messages')) + syn clear + hi clear Nop + hi clear @Wrong +endfunc + + +func Test_conceal() + if !has('conceal') + return + endif + + new + call setline(1, ['', '123456']) + syn match test23 "23" conceal cchar=X + syn match test45 "45" conceal + + set conceallevel=0 + call assert_equal('123456 ', ScreenLines(2, 7)[0]) + call assert_equal([[0, '', 0], [0, '', 0], [0, '', 0], [0, '', 0], [0, '', 0], [0, '', 0]], map(range(1, 6), 'synconcealed(2, v:val)')) + + set conceallevel=1 + call assert_equal('1X 6 ', ScreenLines(2, 7)[0]) + call assert_equal([[0, '', 0], [1, 'X', 1], [1, 'X', 1], [1, ' ', 2], [1, ' ', 2], [0, '', 0]], map(range(1, 6), 'synconcealed(2, v:val)')) + + set conceallevel=1 + set listchars=conceal:Y + call assert_equal([[0, '', 0], [1, 'X', 1], [1, 'X', 1], [1, 'Y', 2], [1, 'Y', 2], [0, '', 0]], map(range(1, 6), 'synconcealed(2, v:val)')) + call assert_equal('1XY6 ', ScreenLines(2, 7)[0]) + + set conceallevel=2 + call assert_match('1X6 ', ScreenLines(2, 7)[0]) + call assert_equal([[0, '', 0], [1, 'X', 1], [1, 'X', 1], [1, '', 2], [1, '', 2], [0, '', 0]], map(range(1, 6), 'synconcealed(2, v:val)')) + + set conceallevel=3 + call assert_match('16 ', ScreenLines(2, 7)[0]) + call assert_equal([[0, '', 0], [1, '', 1], [1, '', 1], [1, '', 2], [1, '', 2], [0, '', 0]], map(range(1, 6), 'synconcealed(2, v:val)')) + + syn clear + set conceallevel& + bw! +endfunc diff --git a/src/nvim/testdir/test_system.vim b/src/nvim/testdir/test_system.vim new file mode 100644 index 0000000000..ce9d110d82 --- /dev/null +++ b/src/nvim/testdir/test_system.vim @@ -0,0 +1,92 @@ +" Tests for system() and systemlist() + +function! Test_System() + if !executable('echo') || !executable('cat') || !executable('wc') + return + endif + let out = system('echo 123') + " On Windows we may get a trailing space. + if out != "123 \n" + call assert_equal("123\n", out) + endif + + let out = systemlist('echo 123') + " On Windows we may get a trailing space and CR. + if out != ["123 \r"] + call assert_equal(['123'], out) + endif + + call assert_equal('123', system('cat', '123')) + call assert_equal(['123'], systemlist('cat', '123')) + call assert_equal(["as\<NL>df"], systemlist('cat', ["as\<NL>df"])) + + new Xdummy + call setline(1, ['asdf', "pw\<NL>er", 'xxxx']) + let out = system('wc -l', bufnr('%')) + " On OS/X we get leading spaces + let out = substitute(out, '^ *', '', '') + call assert_equal("3\n", out) + + let out = systemlist('wc -l', bufnr('%')) + " On Windows we may get a trailing CR. + if out != ["3\r"] + " On OS/X we get leading spaces + if type(out) == v:t_list + let out[0] = substitute(out[0], '^ *', '', '') + endif + call assert_equal(['3'], out) + endif + + let out = systemlist('cat', bufnr('%')) + " On Windows we may get a trailing CR. + if out != ["asdf\r", "pw\<NL>er\r", "xxxx\r"] + call assert_equal(['asdf', "pw\<NL>er", 'xxxx'], out) + endif + bwipe! + + call assert_fails('call system("wc -l", 99999)', 'E86:') +endfunction + +function! Test_system_exmode() + if has('unix') " echo $? only works on Unix + let cmd = ' -es --headless -u NONE -c "source Xscript" +q; echo "result=$?"' + " Need to put this in a script, "catch" isn't found after an unknown + " function. + call writefile(['try', 'call doesnotexist()', 'catch', 'endtry'], 'Xscript') + let a = system(v:progpath . cmd) + call assert_match('result=0', a) + call assert_equal(0, v:shell_error) + endif + + " Error before try does set error flag. + call writefile(['call nosuchfunction()', 'try', 'call doesnotexist()', 'catch', 'endtry'], 'Xscript') + if has('unix') " echo $? only works on Unix + let a = system(v:progpath . cmd) + call assert_notequal('0', a[0]) + endif + + let cmd = ' -es --headless -u NONE -c "source Xscript" +q' + let a = system(v:progpath . cmd) + call assert_notequal(0, v:shell_error) + call delete('Xscript') + + if has('unix') " echo $? only works on Unix + let cmd = ' -es --headless -u NONE -c "call doesnotexist()" +q; echo $?' + let a = system(v:progpath. cmd) + call assert_notequal(0, a[0]) + endif + + let cmd = ' -es --headless -u NONE -c "call doesnotexist()" +q' + let a = system(v:progpath. cmd) + call assert_notequal(0, v:shell_error) + + if has('unix') " echo $? only works on Unix + let cmd = ' -es --headless -u NONE -c "call doesnotexist()|let a=1" +q; echo $?' + let a = system(v:progpath. cmd) + call assert_notequal(0, a[0]) + endif + + let cmd = ' -es --headless -u NONE -c "call doesnotexist()|let a=1" +q' + let a = system(v:progpath. cmd) + call assert_notequal(0, v:shell_error) +endfunc diff --git a/src/nvim/testdir/test_tab.vim b/src/nvim/testdir/test_tab.vim new file mode 100644 index 0000000000..b847dbd962 --- /dev/null +++ b/src/nvim/testdir/test_tab.vim @@ -0,0 +1,45 @@ + +" Tests for "r<Tab>" with 'smarttab' and 'expandtab' set/not set. +" Also test that dv_ works correctly +func Test_smarttab() + enew! + set smarttab expandtab ts=8 sw=4 + " make sure that backspace works, no matter what termcap is used + exe "set t_kD=\<C-V>x7f t_kb=\<C-V>x08" + call append(0, ['start text', + \ "\t\tsome test text", + \ 'test text', + \ "\t\tother test text", + \ ' a cde', + \ ' f ghi', + \ 'test text', + \ ' Second line beginning with whitespace' + \ ]) + call cursor(1, 1) + exe "normal /some\<CR>" + exe "normal r\t" + call assert_equal("\t\t ome test text", getline('.')) + set noexpandtab + exe "normal /other\<CR>" + exe "normal r\t" + call assert_equal("\t\t ther test text", getline('.')) + + " Test replacing with Tabs and then backspacing to undo it + exe "normal j0wR\t\t\t\<BS>\<BS>\<BS>" + call assert_equal(" a cde", getline('.')) + " Test replacing with Tabs + exe "normal j0wR\t\t\t" + call assert_equal(" \t\thi", getline('.')) + + " Test that copyindent works with expandtab set + set expandtab smartindent copyindent ts=8 sw=8 sts=8 + exe "normal jo{\<CR>x" + call assert_equal('{', getline(line('.') - 1)) + call assert_equal(' x', getline('.')) + set nosol + exe "normal /Second line/\<CR>" + exe "normal fwdv_" + call assert_equal(' with whitespace', getline('.')) + enew! + set expandtab& smartindent& copyindent& ts& sw& sts& +endfunc diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim index 33139fcda0..180563ebbd 100644 --- a/src/nvim/testdir/test_tabpage.vim +++ b/src/nvim/testdir/test_tabpage.vim @@ -473,5 +473,24 @@ func Test_tabnext_on_buf_unload2() endwhile endfunc +func Test_close_on_quitpre() + " This once caused a crash + edit Xtest + new + only + set bufhidden=delete + au QuitPre <buffer> close + tabnew tab1 + tabnew tab2 + 1tabn + q! + call assert_equal(1, tabpagenr()) + call assert_equal(2, tabpagenr('$')) + " clean up + while tabpagenr('$') > 1 + bwipe! + endwhile + buf Xtest +endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index 0d697b3f3e..d5ce193bc6 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -95,4 +95,41 @@ function Test_keyword_jump() call delete('Xinclude') endfunction +" Test for jumping to a tag with 'hidden' set, with symbolic link in path of +" tag. This only works for Unix, because of the symbolic link. +func Test_tag_symbolic() + if !has('unix') + return + endif + set hidden + call delete("Xtest.dir", "rf") + call system("ln -s . Xtest.dir") + " Create a tags file with the current directory name inserted. + call writefile([ + \ "SECTION_OFF " . getcwd() . "/Xtest.dir/Xtest.c /^#define SECTION_OFF 3$/", + \ '', + \ ], 'Xtags') + call writefile(['#define SECTION_OFF 3', + \ '#define NUM_SECTIONS 3'], 'Xtest.c') + + " Try jumping to a tag, but with a path that contains a symbolic link. When + " wrong, this will give the ATTENTION message. The next space will then be + " eaten by hit-return, instead of moving the cursor to 'd'. + set tags=Xtags + enew! + call append(0, 'SECTION_OFF') + call cursor(1,1) + exe "normal \<C-]> " + call assert_equal('Xtest.c', expand('%:t')) + call assert_equal(2, col('.')) + + set hidden& + set tags& + enew! + call delete('Xtags') + call delete('Xtest.c') + call delete("Xtest.dir", "rf") + %bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim new file mode 100644 index 0000000000..999566c6ac --- /dev/null +++ b/src/nvim/testdir/test_textformat.vim @@ -0,0 +1,168 @@ +" Tests for the various 'formatoptions' settings +func Test_text_format() + enew! + + setl noai tw=2 fo=t + call append('$', [ + \ '{', + \ ' ', + \ '', + \ '}']) + exe "normal /^{/+1\n0" + normal gRa b + let lnum = line('.') + call assert_equal([ + \ 'a', + \ 'b'], getline(lnum - 1, lnum)) + + normal ggdG + setl ai tw=2 fo=tw + call append('$', [ + \ '{', + \ 'a b ', + \ '', + \ 'a ', + \ '}']) + exe "normal /^{/+1\n0" + normal gqgqjjllab + let lnum = line('.') + call assert_equal([ + \ 'a ', + \ 'b ', + \ '', + \ 'a ', + \ 'b'], getline(lnum - 4, lnum)) + + normal ggdG + setl tw=3 fo=t + call append('$', [ + \ '{', + \ "a \<C-A>", + \ '}']) + exe "normal /^{/+1\n0" + exe "normal gqgqo\na \<C-V>\<C-A>" + let lnum = line('.') + call assert_equal([ + \ 'a', + \ "\<C-A>", + \ '', + \ 'a', + \ "\<C-A>"], getline(lnum - 4, lnum)) + + normal ggdG + setl tw=2 fo=tcq1 comments=:# + call append('$', [ + \ '{', + \ 'a b', + \ '#a b', + \ '}']) + exe "normal /^{/+1\n0" + exe "normal gqgqjgqgqo\na b\n#a b" + let lnum = line('.') + call assert_equal([ + \ 'a b', + \ '#a b', + \ '', + \ 'a b', + \ '#a b'], getline(lnum - 4, lnum)) + + normal ggdG + setl tw=5 fo=tcn comments=:# + call append('$', [ + \ '{', + \ ' 1 a', + \ '# 1 a', + \ '}']) + exe "normal /^{/+1\n0" + exe "normal A b\<Esc>jA b" + let lnum = line('.') + call assert_equal([ + \ ' 1 a', + \ ' b', + \ '# 1 a', + \ '# b'], getline(lnum - 3, lnum)) + + normal ggdG + setl tw=5 fo=t2a si + call append('$', [ + \ '{', + \ '', + \ ' x a', + \ ' b', + \ ' c', + \ '', + \ '}']) + exe "normal /^{/+3\n0" + exe "normal i \<Esc>A_" + let lnum = line('.') + call assert_equal([ + \ '', + \ ' x a', + \ ' b_', + \ ' c', + \ ''], getline(lnum - 2, lnum + 2)) + + normal ggdG + setl tw=5 fo=qn comments=:# + call append('$', [ + \ '{', + \ '# 1 a b', + \ '}']) + exe "normal /^{/+1\n5|" + normal gwap + call assert_equal(5, col('.')) + let lnum = line('.') + call assert_equal([ + \ '# 1 a', + \ '# b'], getline(lnum, lnum + 1)) + + normal ggdG + setl tw=5 fo=q2 comments=:# + call append('$', [ + \ '{', + \ '# x', + \ '# a b', + \ '}']) + exe "normal /^{/+1\n0" + normal gwap + let lnum = line('.') + call assert_equal([ + \ '# x a', + \ '# b'], getline(lnum, lnum + 1)) + + normal ggdG + setl tw& fo=a + call append('$', [ + \ '{', + \ ' 1aa', + \ ' 2bb', + \ '}']) + exe "normal /^{/+2\n0" + normal I^^ + call assert_equal('{ 1aa ^^2bb }', getline('.')) + + normal ggdG + setl tw=20 fo=an12wcq comments=s1:/*,mb:*,ex:*/ + call append('$', [ + \ '/* abc def ghi jkl ', + \ ' * mno pqr stu', + \ ' */']) + exe "normal /mno pqr/\n" + normal A vwx yz + let lnum = line('.') + call assert_equal([ + \ ' * mno pqr stu ', + \ ' * vwx yz', + \ ' */'], getline(lnum - 1, lnum + 1)) + + normal ggdG + setl tw=12 fo=tqnc comments=:# + call setline('.', '# 1 xxxxx') + normal A foobar + call assert_equal([ + \ '# 1 xxxxx', + \ '# foobar'], getline(1, 2)) + + setl ai& tw& fo& si& comments& + enew! +endfunc diff --git a/src/nvim/testdir/test_textobjects.vim b/src/nvim/testdir/test_textobjects.vim index 630ae5d3a4..684f197f5f 100644 --- a/src/nvim/testdir/test_textobjects.vim +++ b/src/nvim/testdir/test_textobjects.vim @@ -4,7 +4,7 @@ if !has('textobjects') finish endif -function! CpoM(line, useM, expected) +func CpoM(line, useM, expected) new if a:useM @@ -28,16 +28,127 @@ function! CpoM(line, useM, expected) call assert_equal(getreg('"'), a:expected[2]) q! -endfunction +endfunc -function! Test_inner_block_without_cpo_M() +func Test_inner_block_without_cpo_M() call CpoM('(red \(blue) green)', 0, ['red \(blue', 'red \(blue', '']) -endfunction +endfunc -function! Test_inner_block_with_cpo_M_left_backslash() +func Test_inner_block_with_cpo_M_left_backslash() call CpoM('(red \(blue) green)', 1, ['red \(blue) green', 'blue', 'red \(blue) green']) -endfunction +endfunc -function! Test_inner_block_with_cpo_M_right_backslash() +func Test_inner_block_with_cpo_M_right_backslash() call CpoM('(red (blue\) green)', 1, ['red (blue\) green', 'blue\', 'red (blue\) green']) -endfunction +endfunc + +func Test_quote_selection_selection_exclusive() + new + call setline(1, "a 'bcde' f") + set selection=exclusive + exe "norm! fdvhi'y" + call assert_equal('bcde', @") + set selection&vim + bw! +endfunc + +" Tests for string and html text objects +func Test_string_html_objects() + enew! + + let t = '"wo\"rd\\" foo' + put =t + normal! da" + call assert_equal('foo', getline('.')) + + let t = "'foo' 'bar' 'piep'" + put =t + normal! 0va'a'rx + call assert_equal("xxxxxxxxxxxx'piep'", getline('.')) + + let t = "bla bla `quote` blah" + put =t + normal! 02f`da` + call assert_equal("bla bla blah", getline('.')) + + let t = 'out " in "noXno"' + put =t + normal! 0fXdi" + call assert_equal('out " in ""', getline('.')) + + let t = "\"'\" 'blah' rep 'buh'" + put =t + normal! 03f'vi'ry + call assert_equal("\"'\" 'blah'yyyyy'buh'", getline('.')) + + set quoteescape=+*- + let t = "bla `s*`d-`+++`l**` b`la" + put =t + normal! di` + call assert_equal("bla `` b`la", getline('.')) + + let t = 'voo "nah" sdf " asdf" sdf " sdf" sd' + put =t + normal! $F"va"oha"i"rz + call assert_equal('voo "zzzzzzzzzzzzzzzzzzzzzzzzzzzzsd', getline('.')) + + let t = "-<b>asdf<i>Xasdf</i>asdf</b>-" + put =t + normal! fXdit + call assert_equal('-<b>asdf<i></i>asdf</b>-', getline('.')) + + let t = "-<b>asdX<i>a<i />sdf</i>asdf</b>-" + put =t + normal! 0fXdit + call assert_equal('-<b></b>-', getline('.')) + + let t = "-<b>asdf<i>Xasdf</i>asdf</b>-" + put =t + normal! fXdat + call assert_equal('-<b>asdfasdf</b>-', getline('.')) + + let t = "-<b>asdX<i>as<b />df</i>asdf</b>-" + put =t + normal! 0fXdat + call assert_equal('--', getline('.')) + + let t = "-<b>\ninnertext object\n</b>" + put =t + normal! dit + call assert_equal('-<b></b>', getline('.')) + + set quoteescape& + enew! +endfunc + +" Tests for match() and matchstr() +func Test_match() + call assert_equal("b", matchstr("abcd", ".", 0, 2)) + call assert_equal("bc", matchstr("abcd", "..", 0, 2)) + call assert_equal("c", matchstr("abcd", ".", 2, 0)) + call assert_equal("a", matchstr("abcd", ".", 0, -1)) + call assert_equal(-1, match("abcd", ".", 0, 5)) + call assert_equal(0 , match("abcd", ".", 0, -1)) + call assert_equal(0 , match('abc', '.', 0, 1)) + call assert_equal(1 , match('abc', '.', 0, 2)) + call assert_equal(2 , match('abc', '.', 0, 3)) + call assert_equal(-1, match('abc', '.', 0, 4)) + call assert_equal(1 , match('abc', '.', 1, 1)) + call assert_equal(2 , match('abc', '.', 2, 1)) + call assert_equal(-1, match('abc', '.', 3, 1)) + call assert_equal(3 , match('abc', '$', 0, 1)) + call assert_equal(-1, match('abc', '$', 0, 2)) + call assert_equal(3 , match('abc', '$', 1, 1)) + call assert_equal(3 , match('abc', '$', 2, 1)) + call assert_equal(3 , match('abc', '$', 3, 1)) + call assert_equal(-1, match('abc', '$', 4, 1)) + call assert_equal(0 , match('abc', '\zs', 0, 1)) + call assert_equal(1 , match('abc', '\zs', 0, 2)) + call assert_equal(2 , match('abc', '\zs', 0, 3)) + call assert_equal(3 , match('abc', '\zs', 0, 4)) + call assert_equal(-1, match('abc', '\zs', 0, 5)) + call assert_equal(1 , match('abc', '\zs', 1, 1)) + call assert_equal(2 , match('abc', '\zs', 2, 1)) + call assert_equal(3 , match('abc', '\zs', 3, 1)) + call assert_equal(-1, match('abc', '\zs', 4, 1)) +endfunc diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index d377062780..81ac2b6171 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -119,7 +119,7 @@ func Test_paused() let slept = WaitFor('g:val == 1') call assert_equal(1, g:val) if has('reltime') - call assert_inrange(0, 60, slept) + call assert_inrange(0, 100, slept) else call assert_inrange(0, 10, slept) endif diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim index 171618f6f7..2bc6073d52 100644 --- a/src/nvim/testdir/test_undo.vim +++ b/src/nvim/testdir/test_undo.vim @@ -265,3 +265,26 @@ func Test_undofile_earlier() call delete('Xfile') call delete('Xundofile') 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() + enew! + let @a="Ox\<Esc>jAy\<Esc>kdd" + edit +/^$ test_undo.vim + normal @au + call assert_equal(0, &modified) + return + new + normal @au + call assert_equal(0, &modified) + only! + let @a='' +endfunc + +func Test_redo_empty_line() + new + exe "norm\x16r\x160" + exe "norm." + bwipe! +endfunc diff --git a/src/nvim/testdir/test_unlet.vim b/src/nvim/testdir/test_unlet.vim index 96ba752d9f..3f06058d03 100644 --- a/src/nvim/testdir/test_unlet.vim +++ b/src/nvim/testdir/test_unlet.vim @@ -24,3 +24,7 @@ func Test_not_existing() call assert_true(v:exception =~ ':E108:') endtry endfunc + +func Test_unlet_fails() + call assert_fails('unlet v:["count"]', 'E46:') +endfunc diff --git a/src/nvim/testdir/test_utf8_comparisons.vim b/src/nvim/testdir/test_utf8_comparisons.vim new file mode 100644 index 0000000000..576e86142f --- /dev/null +++ b/src/nvim/testdir/test_utf8_comparisons.vim @@ -0,0 +1,95 @@ +" Tests for case-insensitive UTF-8 comparisons (utf_strnicmp() in mbyte.c) +" Also test "g~ap". + +if !has("multi_byte") + finish +endif + +function! Ch(a, op, b, expected) + call assert_equal(eval(printf('"%s" %s "%s"', a:a, a:op, a:b)), a:expected, + \ printf('"%s" %s "%s" should return %d', a:a, a:op, a:b, a:expected)) +endfunction + +function! Chk(a, b, result) + if a:result == 0 + call Ch(a:a, '==?', a:b, 1) + call Ch(a:a, '!=?', a:b, 0) + call Ch(a:a, '<=?', a:b, 1) + call Ch(a:a, '>=?', a:b, 1) + call Ch(a:a, '<?', a:b, 0) + call Ch(a:a, '>?', a:b, 0) + elseif a:result > 0 + call Ch(a:a, '==?', a:b, 0) + call Ch(a:a, '!=?', a:b, 1) + call Ch(a:a, '<=?', a:b, 0) + call Ch(a:a, '>=?', a:b, 1) + call Ch(a:a, '<?', a:b, 0) + call Ch(a:a, '>?', a:b, 1) + else + call Ch(a:a, '==?', a:b, 0) + call Ch(a:a, '!=?', a:b, 1) + call Ch(a:a, '<=?', a:b, 1) + call Ch(a:a, '>=?', a:b, 0) + call Ch(a:a, '<?', a:b, 1) + call Ch(a:a, '>?', a:b, 0) + endif +endfunction + +function! Check(a, b, result) + call Chk(a:a, a:b, a:result) + call Chk(a:b, a:a, -a:result) +endfunction + +function! LT(a, b) + call Check(a:a, a:b, -1) +endfunction + +function! GT(a, b) + call Check(a:a, a:b, 1) +endfunction + +function! EQ(a, b) + call Check(a:a, a:b, 0) +endfunction + +function Test_comparisons() + call EQ('', '') + call LT('', 'a') + call EQ('abc', 'abc') + call EQ('Abc', 'abC') + call LT('ab', 'abc') + call LT('AB', 'abc') + call LT('ab', 'aBc') + call EQ('\xd0\xb9\xd1\x86\xd1\x83\xd0\xba\xd0\xb5\xd0\xbd', '\xd0\xb9\xd0\xa6\xd0\xa3\xd0\xba\xd0\x95\xd0\xbd') + call LT('\xd0\xb9\xd1\x86\xd1\x83\xd0\xba\xd0\xb5\xd0\xbd', '\xd0\xaf\xd1\x86\xd1\x83\xd0\xba\xd0\xb5\xd0\xbd') + call EQ('\xe2\x84\xaa', 'k') + call LT('\xe2\x84\xaa', 'kkkkkk') + call EQ('\xe2\x84\xaa\xe2\x84\xaa\xe2\x84\xaa', 'kkk') + call LT('kk', '\xe2\x84\xaa\xe2\x84\xaa\xe2\x84\xaa') + call EQ('\xe2\x84\xaa\xe2\x84\xa6k\xe2\x84\xaak\xcf\x89', 'k\xcf\x89\xe2\x84\xaakk\xe2\x84\xa6') + call EQ('Abc\x80', 'AbC\x80') + call LT('Abc\x80', 'AbC\x81') + call LT('Abc', 'AbC\x80') + call LT('abc\x80DEF', 'abc\x80def') " case folding stops at the first bad character + call LT('\xc3XYZ', '\xc3xyz') + call EQ('\xef\xbc\xba', '\xef\xbd\x9a') " FF3A (upper), FF5A (lower) + call GT('\xef\xbc\xba', '\xef\xbc\xff') " first string is ok and equals \xef\xbd\x9a after folding, second string is illegal and was left unchanged, then the strings were bytewise compared + call LT('\xc3', '\xc3\x83') + call EQ('\xc3\xa3xYz', '\xc3\x83XyZ') + for n in range(0x60, 0xFF) + call LT(printf('xYz\x%.2X', n-1), printf('XyZ\x%.2X', n)) + endfor + for n in range(0x80, 0xBF) + call EQ(printf('xYz\xc2\x%.2XUvW', n), printf('XyZ\xc2\x%.2XuVw', n)) + endfor + for n in range(0xC0, 0xFF) + call LT(printf('xYz\xc2\x%.2XUvW', n), printf('XyZ\xc2\x%.2XuVw', n)) + endfor +endfunction + +" test that g~ap changes one paragraph only. +function Test_gap() + new + call feedkeys("iabcd\n\ndefggg0g~ap", "tx") + call assert_equal(["ABCD", "", "defg"], getline(1,3)) +endfunction diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim index 4e0f1bbd2f..c449fc91b0 100644 --- a/src/nvim/testdir/test_vimscript.vim +++ b/src/nvim/testdir/test_vimscript.vim @@ -1215,6 +1215,62 @@ func Test_bitwise_functions() call assert_fails("call invert({})", 'E728:') endfunc +" Test trailing text after :endfunction {{{1 +func Test_endfunction_trailing() + call assert_false(exists('*Xtest')) + + exe "func Xtest()\necho 'hello'\nendfunc\nlet done = 'yes'" + call assert_true(exists('*Xtest')) + call assert_equal('yes', done) + delfunc Xtest + unlet done + + exe "func Xtest()\necho 'hello'\nendfunc|let done = 'yes'" + call assert_true(exists('*Xtest')) + call assert_equal('yes', done) + delfunc Xtest + unlet done + + " trailing line break + exe "func Xtest()\necho 'hello'\nendfunc\n" + call assert_true(exists('*Xtest')) + delfunc Xtest + + set verbose=1 + exe "func Xtest()\necho 'hello'\nendfunc \" garbage" + call assert_true(exists('*Xtest')) + delfunc Xtest + + call assert_fails("func Xtest()\necho 'hello'\nendfunc garbage", 'E946') + call assert_true(exists('*Xtest')) + delfunc Xtest + set verbose=0 + + function Foo() + echo 'hello' + endfunction | echo 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' + delfunc Foo +endfunc + +func Test_delfunction_force() + delfunc! Xtest + delfunc! Xtest + func Xtest() + echo 'nothing' + endfunc + delfunc! Xtest + delfunc! Xtest +endfunc + +" Test using bang after user command {{{1 +func Test_user_command_with_bang() + command -bang Nieuw let nieuw = 1 + Ni! + call assert_equal(1, nieuw) + unlet nieuw + delcommand Nieuw +endfunc + "------------------------------------------------------------------------------- " Modelines {{{1 " vim: ts=8 sw=4 tw=80 fdm=marker diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index 1694adbd32..0f2e7e493e 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -3,6 +3,7 @@ if !has('visual') finish endif + func Test_block_shift_multibyte() " Uses double-wide character. if !has('multi_byte') @@ -43,3 +44,111 @@ func Test_dotregister_paste() call assert_equal('hello world world', getline(1)) q! endfunc + +func Test_Visual_inner_quote() + new + normal oxX + normal vki' + bwipe! +endfunc + +" Test for visual block shift and tab characters. +func Test_block_shift_tab() + enew! + call append(0, repeat(['one two three'], 5)) + call cursor(1,1) + exe "normal i\<C-G>u" + exe "normal fe\<C-V>4jR\<Esc>ugvr1" + call assert_equal('on1 two three', getline(1)) + call assert_equal('on1 two three', getline(2)) + call assert_equal('on1 two three', getline(5)) + + enew! + call append(0, repeat(['abcdefghijklmnopqrstuvwxyz'], 5)) + call cursor(1,1) + exe "normal \<C-V>4jI \<Esc>j<<11|D" + exe "normal j7|a\<Tab>\<Tab>" + exe "normal j7|a\<Tab>\<Tab> " + exe "normal j7|a\<Tab> \<Tab>\<Esc>4k13|\<C-V>4j<" + call assert_equal(' abcdefghijklmnopqrstuvwxyz', getline(1)) + call assert_equal('abcdefghij', getline(2)) + call assert_equal(" abc\<Tab> defghijklmnopqrstuvwxyz", getline(3)) + call assert_equal(" abc\<Tab> defghijklmnopqrstuvwxyz", getline(4)) + call assert_equal(" abc\<Tab> defghijklmnopqrstuvwxyz", getline(5)) + + %s/\s\+//g + call cursor(1,1) + exe "normal \<C-V>4jI \<Esc>j<<" + exe "normal j7|a\<Tab>\<Tab>" + exe "normal j7|a\<Tab>\<Tab>\<Tab>\<Tab>\<Tab>" + exe "normal j7|a\<Tab> \<Tab>\<Tab>\<Esc>4k13|\<C-V>4j3<" + call assert_equal(' abcdefghijklmnopqrstuvwxyz', getline(1)) + call assert_equal('abcdefghij', getline(2)) + call assert_equal(" abc\<Tab> defghijklmnopqrstuvwxyz", getline(3)) + call assert_equal(" abc\<Tab>\<Tab>defghijklmnopqrstuvwxyz", getline(4)) + call assert_equal(" abc\<Tab> defghijklmnopqrstuvwxyz", getline(5)) + + enew! +endfunc + +" Tests Blockwise Visual when there are TABs before the text. +func Test_blockwise_visual() + enew! + call append(0, ['123456', + \ '234567', + \ '345678', + \ '', + \ 'test text test tex start here', + \ "\t\tsome text", + \ "\t\ttest text", + \ 'test text']) + call cursor(1,1) + exe "normal /start here$\<CR>" + exe 'normal "by$' . "\<C-V>jjlld" + exe "normal /456$\<CR>" + exe "normal \<C-V>jj" . '"bP' + call assert_equal(['123start here56', + \ '234start here67', + \ '345start here78', + \ '', + \ 'test text test tex rt here', + \ "\t\tsomext", + \ "\t\ttesext"], getline(1, 7)) + + enew! +endfunc + +" Test Virtual replace mode. +func Test_virtual_replace() + throw 'skipped: TODO: ' + exe "set t_kD=\<C-V>x7f t_kb=\<C-V>x08" + enew! + exe "normal a\nabcdefghi\njk\tlmn\n opq rst\n\<C-D>uvwxyz" + call cursor(1,1) + set ai bs=2 + exe "normal gR0\<C-D> 1\nA\nBCDEFGHIJ\n\tKL\nMNO\nPQR" + call assert_equal([' 1', + \ ' A', + \ ' BCDEFGHIJ', + \ ' KL', + \ ' MNO', + \ ' PQR', + \ ], getline(1, 6)) + normal G + mark a + inoremap <C-D> <Del> + exe "normal o0\<C-D>\nabcdefghi\njk\tlmn\n opq\trst\n\<C-D>uvwxyz\n" + exe "normal 'ajgR0\<C-D> 1\nA\nBCDEFGHIJ\n\tKL\nMNO\nPQR" . repeat("\<BS>", 29) + call assert_equal([' 1', + \ 'abcdefghi', + \ 'jk lmn', + \ ' opq rst', + \ 'uvwxyz'], getline(7, 11)) + normal G + exe "normal iab\tcdefghi\tjkl" + exe "normal 0gRAB......CDEFGHI.J\<Esc>o" + exe "normal iabcdefghijklmnopqrst\<Esc>0gRAB\tIJKLMNO\tQR" + call assert_equal(['AB......CDEFGHI.Jkl', + \ 'AB IJKLMNO QRst'], getline(12, 13)) + enew! +endfunc diff --git a/src/nvim/testdir/test_winbuf_close.vim b/src/nvim/testdir/test_winbuf_close.vim new file mode 100644 index 0000000000..ed64dd79b7 --- /dev/null +++ b/src/nvim/testdir/test_winbuf_close.vim @@ -0,0 +1,124 @@ +" Test for commands that close windows and/or buffers: +" :quit +" :close +" :hide +" :only +" :sall +" :all +" :ball +" :buf +" :edit +" +func Test_winbuf_close() + enew | only + + call writefile(['testtext 1'], 'Xtest1') + call writefile(['testtext 2'], 'Xtest2') + call writefile(['testtext 3'], 'Xtest3') + + next! Xtest1 Xtest2 + call setline(1, 'testtext 1 1') + + " test for working :n when hidden set + set hidden + next + call assert_equal('Xtest2', bufname('%')) + + " test for failing :rew when hidden not set + set nohidden + call setline(1, 'testtext 2 2') + call assert_fails('rewind', 'E37') + call assert_equal('Xtest2', bufname('%')) + call assert_equal('testtext 2 2', getline(1)) + + " test for working :rew when hidden set + set hidden + rewind + call assert_equal('Xtest1', bufname('%')) + call assert_equal('testtext 1 1', getline(1)) + + " test for :all keeping a buffer when it's modified + set nohidden + call setline(1, 'testtext 1 1 1') + split + next Xtest2 Xtest3 + all + 1wincmd w + call assert_equal('Xtest1', bufname('%')) + call assert_equal('testtext 1 1 1', getline(1)) + + " test abandoning changed buffer, should be unloaded even when 'hidden' set + set hidden + call setline(1, 'testtext 1 1 1 1') + quit! + call assert_equal('Xtest2', bufname('%')) + call assert_equal('testtext 2 2', getline(1)) + unhide + call assert_equal('Xtest2', bufname('%')) + call assert_equal('testtext 2 2', getline(1)) + + " test ":hide" hides anyway when 'hidden' not set + set nohidden + call setline(1, 'testtext 2 2 2') + hide + call assert_equal('Xtest3', bufname('%')) + call assert_equal('testtext 3', getline(1)) + + " test ":edit" failing in modified buffer when 'hidden' not set + call setline(1, 'testtext 3 3') + call assert_fails('edit Xtest1', 'E37') + call assert_equal('Xtest3', bufname('%')) + call assert_equal('testtext 3 3', getline(1)) + + " test ":edit" working in modified buffer when 'hidden' set + set hidden + edit Xtest1 + call assert_equal('Xtest1', bufname('%')) + call assert_equal('testtext 1', getline(1)) + + " test ":close" not hiding when 'hidden' not set in modified buffer + split Xtest3 + set nohidden + call setline(1, 'testtext 3 3 3') + call assert_fails('close', 'E37') + call assert_equal('Xtest3', bufname('%')) + call assert_equal('testtext 3 3 3', getline(1)) + + " test ":close!" does hide when 'hidden' not set in modified buffer; + call setline(1, 'testtext 3 3 3 3') + close! + call assert_equal('Xtest1', bufname('%')) + call assert_equal('testtext 1', getline(1)) + + set nohidden + + " test ":all!" hides changed buffer + split Xtest4 + call setline(1, 'testtext 4') + all! + 1wincmd w + call assert_equal('Xtest2', bufname('%')) + call assert_equal('testtext 2 2 2', getline(1)) + + " test ":q!" and hidden buffer. + bwipe! Xtest1 Xtest2 Xtest3 Xtest4 + split Xtest1 + wincmd w + bwipe! + set modified + bot split Xtest2 + set modified + bot split Xtest3 + set modified + wincmd t + hide + call assert_equal('Xtest2', bufname('%')) + quit! + call assert_equal('Xtest3', bufname('%')) + call assert_fails('silent! quit!', 'E162') + call assert_equal('Xtest1', bufname('%')) + + call delete('Xtest1') + call delete('Xtest2') + call delete('Xtest3') +endfunc diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index 9d61921988..139d29a48b 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -306,20 +306,14 @@ func Test_window_width() set winfixwidth vsplit Xc let [ww1, ww2, ww3] = [winwidth(1), winwidth(2), winwidth(3)] - " FIXME: commented out: I would expect the width of 2nd window to - " remain 2 but it's actually 1?! - "call assert_equal(2, winwidth(2)) + call assert_equal(2, winwidth(2)) call assert_inrange(ww3, ww3 + 1, ww1) 3wincmd > - " FIXME: commented out: I would expect the width of 2nd window to - " remain 2 but it's actually 1?! - "call assert_equal(2, winwidth(2)) + call assert_equal(2, winwidth(2)) call assert_equal(ww1 + 3, winwidth(1)) call assert_equal(ww3 - 3, winwidth(3)) wincmd = - " FIXME: commented out: I would expect the width of 2nd window to - " remain 2 but it's actually 1?! - "call assert_equal(2, winwidth(2)) + call assert_equal(2, winwidth(2)) call assert_equal(ww1, winwidth(1)) call assert_equal(ww3, winwidth(3)) diff --git a/src/nvim/testdir/unix.vim b/src/nvim/testdir/unix.vim index a7daacf8cf..ce2beff7fe 100644 --- a/src/nvim/testdir/unix.vim +++ b/src/nvim/testdir/unix.vim @@ -2,6 +2,12 @@ " Always use "sh", don't use the value of "$SHELL". set shell=sh +if has('win32') + set shellcmdflag=-c shellxquote= shellxescape= shellquote= + let &shellredir = '>%s 2>&1' + set shellslash +endif + " Don't depend on system locale, always use utf-8 set encoding=utf-8 diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 96bb692db9..b04a6ce4f9 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -47,9 +47,11 @@ void term_input_init(TermInput *input, Loop *loop) termkey_set_canonflags(input->tk, curflags | TERMKEY_CANON_DELBS); // setup input handle #ifdef WIN32 - uv_tty_init(loop, &input->tty_in, 0, 1); + uv_tty_init(&loop->uv, &input->tty_in, 0, 1); uv_tty_set_mode(&input->tty_in, UV_TTY_MODE_RAW); - rstream_init_stream(&input->read_stream, &input->tty_in, 0xfff); + rstream_init_stream(&input->read_stream, + (uv_stream_t *)&input->tty_in, + 0xfff); #else rstream_init_fd(loop, &input->read_stream, input->in_fd, 0xfff); #endif diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c index fdc33f0a77..2f07e83158 100644 --- a/src/nvim/tui/terminfo.c +++ b/src/nvim/tui/terminfo.c @@ -9,7 +9,10 @@ #include <unibilium.h> #include "nvim/log.h" +#include "nvim/globals.h" #include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/option.h" #include "nvim/tui/terminfo.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -87,8 +90,8 @@ bool terminfo_is_term_family(const char *term, const char *family) size_t tlen = strlen(term); size_t flen = strlen(family); return tlen >= flen - && 0 == memcmp(term, family, flen) \ - // Per the commentary in terminfo, minus sign is the suffix separator. + && 0 == memcmp(term, family, flen) + // Per commentary in terminfo, minus is the only valid suffix separator. && ('\0' == term[flen] || '-' == term[flen]); } @@ -166,3 +169,87 @@ unibi_term *terminfo_from_builtin(const char *term, char **termname) unibi_set_bool(ut, unibi_back_color_erase, false); return ut; } + +/// Dumps termcap info to the messages area. +/// Serves a similar purpose as Vim `:set termcap` (removed in Nvim). +/// +/// @note adapted from unibilium unibi-dump.c +void terminfo_info_msg(const unibi_term *const ut) +{ + if (exiting) { + return; + } + msg_puts_title("\n\n--- Terminal info --- {{{\n"); + + char *term; + get_tty_option("term", &term); + msg_printf_attr(0, "&term: %s\n", term); + msg_printf_attr(0, "Description: %s\n", unibi_get_name(ut)); + const char **a = unibi_get_aliases(ut); + if (*a) { + msg_puts("Aliases: "); + do { + msg_printf_attr(0, "%s%s\n", *a, a[1] ? " | " : ""); + a++; + } while (*a); + } + + msg_puts("Boolean capabilities:\n"); + for (enum unibi_boolean i = unibi_boolean_begin_ + 1; + i < unibi_boolean_end_; i++) { + msg_printf_attr(0, " %-25s %-10s = %s\n", unibi_name_bool(i), + unibi_short_name_bool(i), + unibi_get_bool(ut, i) ? "true" : "false"); + } + + msg_puts("Numeric capabilities:\n"); + for (enum unibi_numeric i = unibi_numeric_begin_ + 1; + i < unibi_numeric_end_; i++) { + int n = unibi_get_num(ut, i); // -1 means "empty" + msg_printf_attr(0, " %-25s %-10s = %hd\n", unibi_name_num(i), + unibi_short_name_num(i), n); + } + + msg_puts("String capabilities:\n"); + for (enum unibi_string i = unibi_string_begin_ + 1; + i < unibi_string_end_; i++) { + const char *s = unibi_get_str(ut, i); + if (s) { + msg_printf_attr(0, " %-25s %-10s = ", unibi_name_str(i), + unibi_short_name_str(i)); + // Most of these strings will contain escape sequences. + msg_outtrans_special((char_u *)s, false); + msg_putchar('\n'); + } + } + + if (unibi_count_ext_bool(ut)) { + msg_puts("Extended boolean capabilities:\n"); + for (size_t i = 0; i < unibi_count_ext_bool(ut); i++) { + msg_printf_attr(0, " %-25s = %s\n", + unibi_get_ext_bool_name(ut, i), + unibi_get_ext_bool(ut, i) ? "true" : "false"); + } + } + + if (unibi_count_ext_num(ut)) { + msg_puts("Extended numeric capabilities:\n"); + for (size_t i = 0; i < unibi_count_ext_num(ut); i++) { + msg_printf_attr(0, " %-25s = %hd\n", + unibi_get_ext_num_name(ut, i), + unibi_get_ext_num(ut, i)); + } + } + + if (unibi_count_ext_str(ut)) { + msg_puts("Extended string capabilities:\n"); + for (size_t i = 0; i < unibi_count_ext_str(ut); i++) { + msg_printf_attr(0, " %-25s = ", unibi_get_ext_str_name(ut, i)); + msg_outtrans_special((char_u *)unibi_get_ext_str(ut, i), false); + msg_putchar('\n'); + } + } + + msg_puts("}}}\n"); + xfree(term); +} diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 2436295ad4..72a25b0b59 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -69,7 +69,6 @@ typedef struct { typedef struct { UIBridgeData *bridge; Loop *loop; - bool stop; unibi_var_t params[9]; char buf[OUTBUF_SIZE]; size_t bufpos; @@ -96,6 +95,7 @@ typedef struct { bool immediate_wrap_after_last_column; bool mouse_enabled; bool busy, is_invisible; + bool cork, overflow; cursorentry_T cursor_shapes[SHAPE_IDX_COUNT]; HlAttrs print_attrs; bool default_attr; @@ -123,9 +123,8 @@ static bool cursor_style_enabled = false; UI *tui_start(void) { - UI *ui = xcalloc(1, sizeof(UI)); + UI *ui = xcalloc(1, sizeof(UI)); // Freed by ui_bridge_stop(). ui->stop = tui_stop; - ui->rgb = p_tgc; ui->resize = tui_resize; ui->clear = tui_clear; ui->eol_clear = tui_eol_clear; @@ -143,14 +142,12 @@ UI *tui_start(void) ui->put = tui_put; ui->bell = tui_bell; ui->visual_bell = tui_visual_bell; - ui->update_fg = tui_update_fg; - ui->update_bg = tui_update_bg; - ui->update_sp = tui_update_sp; + ui->default_colors_set = tui_default_colors_set; ui->flush = tui_flush; ui->suspend = tui_suspend; ui->set_title = tui_set_title; ui->set_icon = tui_set_icon; - ui->event = tui_event; + ui->option_set= tui_option_set; memset(ui->ui_ext, 0, sizeof(ui->ui_ext)); @@ -167,24 +164,6 @@ static size_t unibi_pre_fmt_str(TUIData *data, unsigned int unibi_index, return unibi_run(str, data->params, buf, len); } -/// Emits some termcodes after Nvim startup, which were observed to slowdown -/// rendering during startup in tmux 2.3 (+focus-events). #7649 -static void terminfo_after_startup_event(void **argv) -{ - UI *ui = argv[0]; - bool defer = argv[1] != NULL; // clever(?) boolean without malloc() dance. - TUIData *data = ui->data; - if (defer) { // We're on the main-loop. Now forward to the TUI loop. - loop_schedule(data->loop, - event_create(terminfo_after_startup_event, 2, ui, NULL)); - return; - } - // Enable bracketed paste - unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste); - // Enable focus reporting - unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting); -} - static void termname_set_event(void **argv) { char *termname = argv[0]; @@ -200,6 +179,8 @@ static void terminfo_start(UI *ui) data->default_attr = false; data->is_invisible = true; data->busy = false; + data->cork = false; + data->overflow = false; data->showing_mode = SHAPE_IDX_N; data->unibi_ext.enable_mouse = -1; data->unibi_ext.disable_mouse = -1; @@ -236,7 +217,8 @@ static void terminfo_start(UI *ui) const char *vte_version_env = os_getenv("VTE_VERSION"); long vte_version = vte_version_env ? strtol(vte_version_env, NULL, 10) : 0; bool iterm_env = termprg && strstr(termprg, "iTerm.app"); - bool konsole = os_getenv("KONSOLE_PROFILE_NAME") + bool konsole = terminfo_is_term_family(term, "konsole") + || os_getenv("KONSOLE_PROFILE_NAME") || os_getenv("KONSOLE_DBUS_SESSION"); patch_terminfo_bugs(data, term, colorterm, vte_version, konsole, iterm_env); @@ -262,6 +244,9 @@ static void terminfo_start(UI *ui) unibi_out(ui, unibi_enter_ca_mode); unibi_out(ui, unibi_keypad_xmit); unibi_out(ui, unibi_clear_screen); + // Enable bracketed paste + unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste); + uv_loop_init(&data->write_loop); if (data->out_isatty) { uv_tty_init(&data->write_loop, &data->output_handle.tty, data->out_fd, 0); @@ -274,9 +259,6 @@ static void terminfo_start(UI *ui) uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0); uv_pipe_open(&data->output_handle.pipe, data->out_fd); } - - loop_schedule(&main_loop, - event_create(terminfo_after_startup_event, 2, ui, ui)); } static void terminfo_stop(UI *ui) @@ -294,7 +276,7 @@ static void terminfo_stop(UI *ui) unibi_out_ext(ui, data->unibi_ext.disable_bracketed_paste); // Disable focus reporting unibi_out_ext(ui, data->unibi_ext.disable_focus_reporting); - flush_buf(ui, true); + flush_buf(ui); uv_tty_reset_mode(); uv_close((uv_handle_t *)&data->output_handle, NULL); uv_run(&data->write_loop, UV_RUN_DEFAULT); @@ -315,6 +297,16 @@ static void tui_terminal_start(UI *ui) term_input_start(&data->input); } +static void tui_terminal_after_startup(UI *ui) + FUNC_ATTR_NONNULL_ALL +{ + TUIData *data = ui->data; + + // Emit this after Nvim startup, not during. This works around a tmux + // 2.3 bug(?) which caused slow drawing during startup. #7649 + unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting); +} + static void tui_terminal_stop(UI *ui) { TUIData *data = ui->data; @@ -327,11 +319,17 @@ static void tui_terminal_stop(UI *ui) static void tui_stop(UI *ui) { tui_terminal_stop(ui); - TUIData *data = ui->data; - data->stop = true; + // Flag UI as "stopped". + ui->data = NULL; +} + +/// Returns true if UI `ui` is stopped. +static bool tui_is_stopped(UI *ui) +{ + return ui->data == NULL; } -// Main function of the TUI thread +/// Main function of the TUI thread. static void tui_main(UIBridgeData *bridge, UI *ui) { Loop tui_loop; @@ -352,13 +350,25 @@ static void tui_main(UIBridgeData *bridge, UI *ui) #endif term_input_init(&data->input, &tui_loop); tui_terminal_start(ui); - data->stop = false; - // allow the main thread to continue, we are ready to start handling UI - // callbacks + // Allow main thread to continue, we are ready to handle UI callbacks. CONTINUE(bridge); - while (!data->stop) { + loop_schedule_deferred(&main_loop, + event_create(show_termcap_event, 1, data->ut)); + + // "Active" loop: first ~100 ms of startup. + for (size_t ms = 0; ms < 100 && !tui_is_stopped(ui);) { + ms += (loop_poll_events(&tui_loop, 20) ? 20 : 1); + } + if (!tui_is_stopped(ui)) { + tui_terminal_after_startup(ui); + // Tickle `main_loop` with a dummy event, else the initial "focus-gained" + // terminal response may not get processed until user hits a key. + loop_schedule_deferred(&main_loop, event_create(tui_dummy_event, 0)); + } + // "Passive" (I/O-driven) loop: TUI thread "main loop". + while (!tui_is_stopped(ui)) { loop_poll_events(&tui_loop, -1); // tui_loop.events is never processed } @@ -370,9 +380,13 @@ static void tui_main(UIBridgeData *bridge, UI *ui) loop_close(&tui_loop, false); kv_destroy(data->invalid_regions); xfree(data); - xfree(ui); } +static void tui_dummy_event(void **argv) +{ +} + +/// Handoff point between the main (ui_bridge) thread and the TUI thread. static void tui_scheduler(Event event, void *d) { UI *ui = d; @@ -395,36 +409,59 @@ static void sigwinch_cb(SignalWatcher *watcher, int signum, void *data) ui_schedule_refresh(); } -static bool attrs_differ(HlAttrs a1, HlAttrs a2) +static bool attrs_differ(HlAttrs a1, HlAttrs a2, bool rgb) { - return a1.foreground != a2.foreground || a1.background != a2.background - || a1.bold != a2.bold || a1.italic != a2.italic - || a1.undercurl != a2.undercurl || a1.underline != a2.underline - || a1.reverse != a2.reverse; + if (rgb) { + // TODO(bfredl): when we start to support special color, + // rgb_sp_color must be added here + return a1.rgb_fg_color != a2.rgb_fg_color + || a1.rgb_bg_color != a2.rgb_bg_color + || a1.rgb_ae_attr != a2.rgb_ae_attr; + } else { + return a1.cterm_fg_color != a2.cterm_fg_color + || a1.cterm_bg_color != a2.cterm_bg_color + || a1.cterm_ae_attr != a2.cterm_ae_attr; + } } static void update_attrs(UI *ui, HlAttrs attrs) { TUIData *data = ui->data; - if (!attrs_differ(attrs, data->print_attrs)) { + if (!attrs_differ(attrs, data->print_attrs, ui->rgb)) { return; } data->print_attrs = attrs; UGrid *grid = &data->grid; - int fg = attrs.foreground != -1 ? attrs.foreground : grid->fg; - int bg = attrs.background != -1 ? attrs.background : grid->bg; + int fg = ui->rgb ? attrs.rgb_fg_color : (attrs.cterm_fg_color - 1); + if (fg == -1) { + fg = ui->rgb ? grid->clear_attrs.rgb_fg_color + : (grid->clear_attrs.cterm_fg_color - 1); + } + + int bg = ui->rgb ? attrs.rgb_bg_color : (attrs.cterm_bg_color - 1); + if (bg == -1) { + bg = ui->rgb ? grid->clear_attrs.rgb_bg_color + : (grid->clear_attrs.cterm_bg_color - 1); + } + + int attr = ui->rgb ? attrs.rgb_ae_attr : attrs.cterm_ae_attr; + bool bold = attr & HL_BOLD; + bool italic = attr & HL_ITALIC; + bool reverse = attr & HL_INVERSE; + bool standout = attr & HL_STANDOUT; + bool underline = attr & (HL_UNDERLINE), undercurl = attr & (HL_UNDERCURL); if (unibi_get_str(data->ut, unibi_set_attributes)) { - if (attrs.bold || attrs.reverse || attrs.underline || attrs.undercurl) { + if (bold || reverse || underline || undercurl) { UNIBI_SET_NUM_VAR(data->params[0], 0); // standout - UNIBI_SET_NUM_VAR(data->params[1], attrs.underline || attrs.undercurl); - UNIBI_SET_NUM_VAR(data->params[2], attrs.reverse); + UNIBI_SET_NUM_VAR(data->params[1], underline || undercurl); + UNIBI_SET_NUM_VAR(data->params[2], reverse); UNIBI_SET_NUM_VAR(data->params[3], 0); // blink UNIBI_SET_NUM_VAR(data->params[4], 0); // dim - UNIBI_SET_NUM_VAR(data->params[5], attrs.bold); + UNIBI_SET_NUM_VAR(data->params[5], bold); UNIBI_SET_NUM_VAR(data->params[6], 0); // blank UNIBI_SET_NUM_VAR(data->params[7], 0); // protect UNIBI_SET_NUM_VAR(data->params[8], 0); // alternate character set @@ -436,17 +473,20 @@ static void update_attrs(UI *ui, HlAttrs attrs) if (!data->default_attr) { unibi_out(ui, unibi_exit_attribute_mode); } - if (attrs.bold) { + if (bold) { unibi_out(ui, unibi_enter_bold_mode); } - if (attrs.underline || attrs.undercurl) { + if (underline || undercurl) { unibi_out(ui, unibi_enter_underline_mode); } - if (attrs.reverse) { + if (standout) { + unibi_out(ui, unibi_enter_standout_mode); + } + if (reverse) { unibi_out(ui, unibi_enter_reverse_mode); } } - if (attrs.italic) { + if (italic) { unibi_out(ui, unibi_enter_italics_mode); } if (ui->rgb) { @@ -476,8 +516,7 @@ static void update_attrs(UI *ui, HlAttrs attrs) } data->default_attr = fg == -1 && bg == -1 - && !attrs.bold && !attrs.italic && !attrs.underline && !attrs.undercurl - && !attrs.reverse; + && !bold && !italic && !underline && !undercurl && !reverse; } static void final_column_wrap(UI *ui) @@ -486,7 +525,7 @@ static void final_column_wrap(UI *ui) UGrid *grid = &data->grid; if (grid->col == ui->width) { grid->col = 0; - if (grid->row < ui->height) { + if (grid->row < MIN(ui->height, grid->height - 1)) { grid->row++; } } @@ -519,7 +558,7 @@ static bool cheap_to_print(UI *ui, int row, int col, int next) UCell *cell = grid->cells[row] + col; while (next) { next--; - if (attrs_differ(cell->attrs, data->print_attrs)) { + if (attrs_differ(cell->attrs, data->print_attrs, ui->rgb)) { if (data->default_attr) { return false; } @@ -644,13 +683,12 @@ static void clear_region(UI *ui, int top, int bot, int left, int right) int saved_col = grid->col; bool cleared = false; - if (grid->bg == -1 && right == ui->width -1) { + bool nobg = ui->rgb ? grid->clear_attrs.rgb_bg_color == -1 + : grid->clear_attrs.cterm_bg_color == 0; + if (nobg && right == ui->width -1) { // Background is set to the default color and the right edge matches the // screen end, try to use terminal codes for clearing the requested area. - HlAttrs clear_attrs = HLATTRS_INIT; - clear_attrs.foreground = grid->fg; - clear_attrs.background = grid->bg; - update_attrs(ui, clear_attrs); + update_attrs(ui, grid->clear_attrs); if (left == 0) { if (bot == ui->height - 1) { if (top == 0) { @@ -773,6 +811,7 @@ static void tui_clear(UI *ui) TUIData *data = ui->data; UGrid *grid = &data->grid; ugrid_clear(grid); + kv_size(data->invalid_regions) = 0; clear_region(ui, grid->top, grid->bot, grid->left, grid->right); } @@ -791,7 +830,7 @@ static void tui_cursor_goto(UI *ui, Integer row, Integer col) CursorShape tui_cursor_decode_shape(const char *shape_str) { - CursorShape shape = 0; + CursorShape shape; if (strequal(shape_str, "block")) { shape = SHAPE_BLOCK; } else if (strequal(shape_str, "vertical")) { @@ -799,7 +838,8 @@ CursorShape tui_cursor_decode_shape(const char *shape_str) } else if (strequal(shape_str, "horizontal")) { shape = SHAPE_HOR; } else { - EMSG2(_(e_invarg2), shape_str); + WLOG("Unknown shape value '%s'", shape_str); + shape = SHAPE_BLOCK; } return shape; } @@ -885,22 +925,22 @@ static void tui_set_mode(UI *ui, ModeShape mode) } TUIData *data = ui->data; cursorentry_T c = data->cursor_shapes[mode]; - int shape = c.shape; if (c.id != 0 && ui->rgb) { int attr = syn_id2attr(c.id); if (attr > 0) { - attrentry_T *aep = syn_cterm_attr2entry(attr); + HlAttrs *aep = syn_cterm_attr2entry(attr); UNIBI_SET_NUM_VAR(data->params[0], aep->rgb_bg_color); unibi_out_ext(ui, data->unibi_ext.set_cursor_color); } } - switch (shape) { + int shape; + switch (c.shape) { + default: abort(); break; case SHAPE_BLOCK: shape = 1; break; case SHAPE_HOR: shape = 3; break; case SHAPE_VER: shape = 5; break; - default: WLOG("Unknown shape value %d", shape); break; } UNIBI_SET_NUM_VAR(data->params[0], shape + (int)(c.blinkon == 0)); unibi_out_ext(ui, data->unibi_ext.set_cursor_style); @@ -945,10 +985,7 @@ static void tui_scroll(UI *ui, Integer count) cursor_goto(ui, grid->top, grid->left); // also set default color attributes or some terminals can become funny if (scroll_clears_to_current_colour) { - HlAttrs clear_attrs = HLATTRS_INIT; - clear_attrs.foreground = grid->fg; - clear_attrs.background = grid->bg; - update_attrs(ui, clear_attrs); + update_attrs(ui, grid->clear_attrs); } if (count > 0) { @@ -1013,19 +1050,16 @@ static void tui_visual_bell(UI *ui) unibi_out(ui, unibi_flash_screen); } -static void tui_update_fg(UI *ui, Integer fg) +static void tui_default_colors_set(UI *ui, Integer rgb_fg, Integer rgb_bg, + Integer rgb_sp, + Integer cterm_fg, Integer cterm_bg) { - ((TUIData *)ui->data)->grid.fg = (int)fg; -} - -static void tui_update_bg(UI *ui, Integer bg) -{ - ((TUIData *)ui->data)->grid.bg = (int)bg; -} - -static void tui_update_sp(UI *ui, Integer sp) -{ - // Do nothing; 'special' color is for GUI only + UGrid *grid = &((TUIData *)ui->data)->grid; + grid->clear_attrs.rgb_fg_color = (int)rgb_fg; + grid->clear_attrs.rgb_bg_color = (int)rgb_bg; + grid->clear_attrs.rgb_sp_color = (int)rgb_sp; + grid->clear_attrs.cterm_fg_color = (int)cterm_fg; + grid->clear_attrs.cterm_bg_color = (int)cterm_bg; } static void tui_flush(UI *ui) @@ -1050,6 +1084,7 @@ static void tui_flush(UI *ui) while (kv_size(data->invalid_regions)) { Rect r = kv_pop(data->invalid_regions); + assert(r.bot < grid->height && r.right < grid->width); UGRID_FOREACH_CELL(grid, r.top, r.bot, r.left, r.right, { cursor_goto(ui, row, col); print_cell(ui, cell); @@ -1058,7 +1093,25 @@ static void tui_flush(UI *ui) cursor_goto(ui, saved_row, saved_col); - flush_buf(ui, true); + flush_buf(ui); +} + +/// Dumps termcap info to the messages area, if 'verbose' >= 3. +static void show_termcap_event(void **argv) +{ + if (p_verbose < 3) { + return; + } + const unibi_term *const ut = argv[0]; + if (!ut) { + abort(); + } + verbose_enter(); + // XXX: (future) if unibi_term is modified (e.g. after a terminal + // query-response) this is a race condition. + terminfo_info_msg(ut); + verbose_leave(); + verbose_stop(); // flush now } #ifdef UNIX @@ -1076,6 +1129,7 @@ static void suspend_event(void **argv) loop_poll_events(data->loop, -1); } tui_terminal_start(ui); + tui_terminal_after_startup(ui); if (enable_mouse) { tui_mouse_on(ui); } @@ -1113,10 +1167,13 @@ static void tui_set_icon(UI *ui, String icon) { } -// NB: if we start to use this, the ui_bridge must be updated -// to make a copy for the tui thread -static void tui_event(UI *ui, char *name, Array args, bool *args_consumed) +static void tui_option_set(UI *ui, String name, Object value) { + TUIData *data = ui->data; + if (strequal(name.data, "termguicolors")) { + ui->rgb = value.data.boolean; + invalidate(ui, 0, data->grid.height-1, 0, data->grid.width-1); + } } static void invalidate(UI *ui, int top, int bot, int left, int right) @@ -1219,8 +1276,18 @@ static void unibi_goto(UI *ui, int row, int col) } \ if (str) { \ unibi_var_t vars[26 + 26]; \ + size_t orig_pos = data->bufpos; \ + \ memset(&vars, 0, sizeof(vars)); \ + data->cork = true; \ +retry: \ unibi_format(vars, vars + 26, str, data->params, out, ui, NULL, NULL); \ + if (data->overflow) { \ + data->bufpos = orig_pos; \ + flush_buf(ui); \ + goto retry; \ + } \ + data->cork = false; \ } \ } while (0) static void unibi_out(UI *ui, int unibi_index) @@ -1239,8 +1306,17 @@ static void out(void *ctx, const char *str, size_t len) TUIData *data = ui->data; size_t available = sizeof(data->buf) - data->bufpos; + if (data->cork && data->overflow) { + return; + } + if (len > available) { - flush_buf(ui, false); + if (data->cork) { + data->overflow = true; + return; + } else { + flush_buf(ui); + } } memcpy(data->buf + data->bufpos, str, len); @@ -1460,35 +1536,37 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, } } - // Some terminals cannot be trusted to report DECSCUSR support. So we keep - // blacklist for when we should not trust the reported features. - if (!((vte_version != 0 && vte_version < 3900) || konsole)) { - // Dickey ncurses terminfo has included the Ss and Se capabilities, - // pioneered by tmux, since 2011-07-14. So adding them to terminal types, - // that do actually have such control sequences but lack the correct - // definitions in terminfo, is a fixup, not an augmentation. + // Blacklist of terminals that cannot be trusted to report DECSCUSR support. + if (!(st || (vte_version != 0 && vte_version < 3900) || konsole)) { data->unibi_ext.reset_cursor_style = unibi_find_ext_str(ut, "Se"); data->unibi_ext.set_cursor_style = unibi_find_ext_str(ut, "Ss"); } + + // Dickey ncurses terminfo includes Ss/Se capabilities since 2011-07-14. So + // adding them to terminal types, that have such control sequences but lack + // the correct terminfo entries, is a fixup, not an augmentation. if (-1 == data->unibi_ext.set_cursor_style) { - // The DECSCUSR sequence to change the cursor shape is widely supported by - // several terminal types. https://github.com/gnachman/iTerm2/pull/92 + // DECSCUSR (cursor shape) sequence is widely supported by several terminal + // types. https://github.com/gnachman/iTerm2/pull/92 // xterm extension: vertical bar - if (!konsole && ((xterm && !vte_version) // anything claiming xterm compat - // per MinTTY 0.4.3-1 release notes from 2009 - || putty - // per https://bugzilla.gnome.org/show_bug.cgi?id=720821 - || (vte_version >= 3900) - || tmux // per tmux manual page - // https://lists.gnu.org/archive/html/screen-devel/2013-03/msg00000.html - || screen - || rxvt // per command.C - // per analysis of VT100Terminal.m - || iterm || iterm_pretending_xterm - || teraterm // per TeraTerm "Supported Control Functions" doco - // Some linux-type terminals (such as console-terminal-emulator - // from the nosh toolset) implement implement the xterm extension. - || (linuxvt && (xterm_version || (vte_version > 0) || colorterm)))) { + if (!konsole + && ((xterm && !vte_version) // anything claiming xterm compat + // per MinTTY 0.4.3-1 release notes from 2009 + || putty + // per https://bugzilla.gnome.org/show_bug.cgi?id=720821 + || (vte_version >= 3900) + || tmux // per tmux manual page + // https://lists.gnu.org/archive/html/screen-devel/2013-03/msg00000.html + || screen + || st // #7641 + || rxvt // per command.C + // per analysis of VT100Terminal.m + || iterm || iterm_pretending_xterm + || teraterm // per TeraTerm "Supported Control Functions" doco + // Some linux-type terminals implement the xterm extension. + // Example: console-terminal-emulator from the nosh toolset. + || (linuxvt + && (xterm_version || (vte_version > 0) || colorterm)))) { data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss", "\x1b[%p1%d q"); if (-1 == data->unibi_ext.reset_cursor_style) { @@ -1577,11 +1655,13 @@ static void augment_terminfo(TUIData *data, const char *term, || konsole // per commentary in VT102Emulation.cpp || teraterm // per TeraTerm "Supported Control Functions" doco || rxvt) { // per command.C - data->unibi_ext.resize_screen = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.resize_screen = (int)unibi_add_ext_str(ut, + "ext.resize_screen", "\x1b[8;%p1%d;%p2%dt"); } if (putty || xterm || rxvt) { - data->unibi_ext.reset_scroll_region = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.reset_scroll_region = (int)unibi_add_ext_str(ut, + "ext.reset_scroll_region", "\x1b[r"); } @@ -1639,25 +1719,33 @@ 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. - data->unibi_ext.enable_lr_margin = (int)unibi_add_ext_str(ut, NULL, + 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, NULL, + 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, NULL, + data->unibi_ext.enable_bracketed_paste = (int)unibi_add_ext_str(ut, + "ext.enable_bpaste", "\x1b[?2004h"); - data->unibi_ext.disable_bracketed_paste = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.disable_bracketed_paste = (int)unibi_add_ext_str(ut, + "ext.disable_bpaste", "\x1b[?2004l"); - data->unibi_ext.enable_focus_reporting = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.enable_focus_reporting = (int)unibi_add_ext_str(ut, + "ext.enable_focus", rxvt ? "\x1b]777;focus;on\x7" : "\x1b[?1004h"); - data->unibi_ext.disable_focus_reporting = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.disable_focus_reporting = (int)unibi_add_ext_str(ut, + "ext.disable_focus", rxvt ? "\x1b]777;focus;off\x7" : "\x1b[?1004l"); - data->unibi_ext.enable_mouse = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.enable_mouse = (int)unibi_add_ext_str(ut, + "ext.enable_mouse", "\x1b[?1002h\x1b[?1006h"); - data->unibi_ext.disable_mouse = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.disable_mouse = (int)unibi_add_ext_str(ut, + "ext.disable_mouse", "\x1b[?1002l\x1b[?1006l"); } -static void flush_buf(UI *ui, bool toggle_cursor) +static void flush_buf(UI *ui) { uv_write_t req; uv_buf_t bufs[3]; @@ -1668,26 +1756,26 @@ static void flush_buf(UI *ui, bool toggle_cursor) return; } - if (toggle_cursor && !data->is_invisible) { + if (!data->is_invisible) { // cursor is visible. Write a "cursor invisible" command before writing the // buffer. bufp->base = data->invis; - bufp->len = data->invislen; + bufp->len = UV_BUF_LEN(data->invislen); bufp++; data->is_invisible = true; } if (data->bufpos > 0) { bufp->base = data->buf; - bufp->len = data->bufpos; + bufp->len = UV_BUF_LEN(data->bufpos); bufp++; } - if (toggle_cursor && !data->busy && data->is_invisible) { + if (!data->busy && data->is_invisible) { // not busy and the cursor is invisible. Write a "cursor normal" command // after writing the buffer. bufp->base = data->norm; - bufp->len = data->normlen; + bufp->len = UV_BUF_LEN(data->normlen); bufp++; data->is_invisible = data->busy; } @@ -1696,6 +1784,7 @@ static void flush_buf(UI *ui, bool toggle_cursor) bufs, (unsigned)(bufp - bufs), NULL); uv_run(&data->write_loop, UV_RUN_DEFAULT); data->bufpos = 0; + data->overflow = false; } #if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18 @@ -1739,6 +1828,12 @@ static const char *tui_tk_ti_getstr(const char *name, const char *value, if (value != NULL && strequal(stty_erase, value)) { return stty_erase[0] == DEL ? CTRL_H_STR : DEL_STR; } + } else if (strequal(name, "key_mouse")) { + DLOG("libtermkey:kmous=%s", value); + // If key_mouse is found, libtermkey uses its terminfo driver (driver-ti.c) + // for mouse input, which by accident only supports X10 protocol. + // Force libtermkey to fallback to its CSI driver (driver-csi.c). #7948 + return NULL; } return value; diff --git a/src/nvim/ugrid.c b/src/nvim/ugrid.c index 2b5e96ee60..6d420ef2f8 100644 --- a/src/nvim/ugrid.c +++ b/src/nvim/ugrid.c @@ -17,7 +17,7 @@ void ugrid_init(UGrid *grid) { grid->attrs = HLATTRS_INIT; - grid->fg = grid->bg = -1; + grid->clear_attrs = HLATTRS_INIT; grid->cells = NULL; } @@ -107,6 +107,7 @@ UCell *ugrid_put(UGrid *grid, uint8_t *text, size_t size) UCell *cell = grid->cells[grid->row] + grid->col; cell->data[size] = 0; cell->attrs = grid->attrs; + assert(size <= CELLBYTES); if (text) { memcpy(cell->data, text, size); @@ -118,9 +119,7 @@ UCell *ugrid_put(UGrid *grid, uint8_t *text, size_t size) static void clear_region(UGrid *grid, int top, int bot, int left, int right) { - HlAttrs clear_attrs = HLATTRS_INIT; - clear_attrs.foreground = grid->fg; - clear_attrs.background = grid->bg; + HlAttrs clear_attrs = grid->clear_attrs; UGRID_FOREACH_CELL(grid, top, bot, left, right, { cell->data[0] = ' '; cell->data[1] = 0; @@ -135,6 +134,7 @@ static void destroy_cells(UGrid *grid) xfree(grid->cells[i]); } xfree(grid->cells); + grid->cells = NULL; } } diff --git a/src/nvim/ugrid.h b/src/nvim/ugrid.h index 1cf047502d..60c9068eb1 100644 --- a/src/nvim/ugrid.h +++ b/src/nvim/ugrid.h @@ -7,15 +7,17 @@ typedef struct ucell UCell; typedef struct ugrid UGrid; +#define CELLBYTES (4 * (MAX_MCO+1)) + struct ucell { - char data[6 * MAX_MCO + 1]; + char data[CELLBYTES + 1]; HlAttrs attrs; }; struct ugrid { int top, bot, left, right; int row, col; - int bg, fg; + HlAttrs clear_attrs; int width, height; HlAttrs attrs; UCell **cells; diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 3b8b3ac9a7..42366fdb76 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -49,13 +49,13 @@ #define MAX_UI_COUNT 16 static UI *uis[MAX_UI_COUNT]; -static bool ui_ext[UI_WIDGETS] = { 0 }; +static bool ui_ext[kUIExtCount] = { 0 }; static size_t ui_count = 0; static int row = 0, col = 0; static struct { int top, bot, left, right; } sr; -static int current_attr_code = 0; +static int current_attr_code = -1; static bool pending_cursor_update = false; static int busy = 0; static int height, width; @@ -107,8 +107,9 @@ static char uilog_last_event[1024] = { 0 }; } \ } while (0) #endif -#define CNT(...) SELECT_NTH(__VA_ARGS__, MORE, MORE, MORE, MORE, ZERO, ignore) -#define SELECT_NTH(a1, a2, a3, a4, a5, a6, ...) a6 +#define CNT(...) SELECT_NTH(__VA_ARGS__, MORE, MORE, MORE, \ + MORE, MORE, ZERO, ignore) +#define SELECT_NTH(a1, a2, a3, a4, a5, a6, a7, ...) a7 #define UI_CALL_HELPER(c, ...) UI_CALL_HELPER2(c, __VA_ARGS__) // Resolves to UI_CALL_MORE or UI_CALL_ZERO. #define UI_CALL_HELPER2(c, ...) UI_CALL_##c(__VA_ARGS__) @@ -145,6 +146,9 @@ void ui_builtin_stop(void) bool ui_rgb_attached(void) { + if (!headless_mode && p_tgc) { + return true; + } for (size_t i = 0; i < ui_count; i++) { if (uis[i]->rgb) { return true; @@ -168,84 +172,61 @@ void ui_event(char *name, Array args) } -/// Converts an attrentry_T into an HlAttrs +/// Converts an HlAttrs into Dictionary /// /// @param[in] aep data to convert /// @param use_rgb use 'gui*' settings if true, else resorts to 'cterm*' -HlAttrs attrentry2hlattrs(const attrentry_T *aep, bool use_rgb) +Dictionary hlattrs2dict(const HlAttrs *aep, bool use_rgb) { assert(aep); - - HlAttrs attrs = HLATTRS_INIT; - int mask = 0; - - mask = use_rgb ? aep->rgb_ae_attr : aep->cterm_ae_attr; - - attrs.bold = mask & HL_BOLD; - attrs.underline = mask & HL_UNDERLINE; - attrs.undercurl = mask & HL_UNDERCURL; - attrs.italic = mask & HL_ITALIC; - attrs.reverse = mask & (HL_INVERSE | HL_STANDOUT); - - if (use_rgb) { - if (aep->rgb_fg_color != -1) { - attrs.foreground = aep->rgb_fg_color; - } - - if (aep->rgb_bg_color != -1) { - attrs.background = aep->rgb_bg_color; - } - - if (aep->rgb_sp_color != -1) { - attrs.special = aep->rgb_sp_color; - } - } else { - if (cterm_normal_fg_color != aep->cterm_fg_color) { - attrs.foreground = aep->cterm_fg_color - 1; - } - - if (cterm_normal_bg_color != aep->cterm_bg_color) { - attrs.background = aep->cterm_bg_color - 1; - } - } - - return attrs; -} - -Dictionary hlattrs2dict(HlAttrs attrs) -{ Dictionary hl = ARRAY_DICT_INIT; + int mask = use_rgb ? aep->rgb_ae_attr : aep->cterm_ae_attr; - if (attrs.bold) { + if (mask & HL_BOLD) { PUT(hl, "bold", BOOLEAN_OBJ(true)); } - if (attrs.underline) { + if (mask & HL_STANDOUT) { + PUT(hl, "standout", BOOLEAN_OBJ(true)); + } + + if (mask & HL_UNDERLINE) { PUT(hl, "underline", BOOLEAN_OBJ(true)); } - if (attrs.undercurl) { + if (mask & HL_UNDERCURL) { PUT(hl, "undercurl", BOOLEAN_OBJ(true)); } - if (attrs.italic) { + if (mask & HL_ITALIC) { PUT(hl, "italic", BOOLEAN_OBJ(true)); } - if (attrs.reverse) { + if (mask & HL_INVERSE) { PUT(hl, "reverse", BOOLEAN_OBJ(true)); } - if (attrs.foreground != -1) { - PUT(hl, "foreground", INTEGER_OBJ(attrs.foreground)); - } - if (attrs.background != -1) { - PUT(hl, "background", INTEGER_OBJ(attrs.background)); - } + if (use_rgb) { + if (aep->rgb_fg_color != -1) { + PUT(hl, "foreground", INTEGER_OBJ(aep->rgb_fg_color)); + } + + if (aep->rgb_bg_color != -1) { + PUT(hl, "background", INTEGER_OBJ(aep->rgb_bg_color)); + } - if (attrs.special != -1) { - PUT(hl, "special", INTEGER_OBJ(attrs.special)); + if (aep->rgb_sp_color != -1) { + PUT(hl, "special", INTEGER_OBJ(aep->rgb_sp_color)); + } + } else { + if (cterm_normal_fg_color != aep->cterm_fg_color) { + PUT(hl, "foreground", INTEGER_OBJ(aep->cterm_fg_color - 1)); + } + + if (cterm_normal_bg_color != aep->cterm_bg_color) { + PUT(hl, "background", INTEGER_OBJ(aep->cterm_bg_color - 1)); + } } return hl; @@ -263,8 +244,8 @@ void ui_refresh(void) } int width = INT_MAX, height = INT_MAX; - bool ext_widgets[UI_WIDGETS]; - for (UIWidget i = 0; (int)i < UI_WIDGETS; i++) { + bool ext_widgets[kUIExtCount]; + for (UIExtension i = 0; (int)i < kUIExtCount; i++) { ext_widgets[i] = true; } @@ -272,7 +253,7 @@ void ui_refresh(void) UI *ui = uis[i]; width = MIN(ui->width, width); height = MIN(ui->height, height); - for (UIWidget i = 0; (int)i < UI_WIDGETS; i++) { + for (UIExtension i = 0; (int)i < kUIExtCount; i++) { ext_widgets[i] &= ui->ui_ext[i]; } } @@ -284,12 +265,15 @@ void ui_refresh(void) screen_resize(width, height); p_lz = save_p_lz; - for (UIWidget i = 0; (int)i < UI_WIDGETS; i++) { - ui_set_external(i, ext_widgets[i]); + for (UIExtension i = 0; (int)i < kUIExtCount; i++) { + ui_ext[i] = ext_widgets[i]; + ui_call_option_set(cstr_as_string((char *)ui_ext_names[i]), + BOOLEAN_OBJ(ext_widgets[i])); } ui_mode_info_set(); old_mode_idx = -1; ui_cursor_shape(); + current_attr_code = -1; } static void ui_refresh_event(void **argv) @@ -307,6 +291,11 @@ void ui_resize(int new_width, int new_height) width = new_width; height = new_height; + // TODO(bfredl): update default colors when they changed, NOT on resize. + ui_call_default_colors_set(normal_fg, normal_bg, normal_sp, + cterm_normal_fg_color, cterm_normal_bg_color); + + // Deprecated: UI_CALL(update_fg, (ui->rgb ? normal_fg : cterm_normal_fg_color - 1)); UI_CALL(update_bg, (ui->rgb ? normal_bg : cterm_normal_bg_color - 1)); UI_CALL(update_sp, (ui->rgb ? normal_sp : -1)); @@ -339,6 +328,7 @@ void ui_attach_impl(UI *ui) } uis[ui_count++] = ui; + ui_refresh_options(); ui_refresh(); } @@ -399,26 +389,28 @@ void ui_reset_scroll_region(void) ui_call_set_scroll_region(sr.top, sr.bot, sr.left, sr.right); } -void ui_start_highlight(int attr_code) +void ui_set_highlight(int attr_code) { + if (current_attr_code == attr_code) { + return; + } current_attr_code = attr_code; - if (!ui_count) { - return; + HlAttrs attrs = HLATTRS_INIT; + + if (attr_code != 0) { + HlAttrs *aep = syn_cterm_attr2entry(attr_code); + if (aep) { + attrs = *aep; + } } - set_highlight_args(current_attr_code); + UI_CALL(highlight_set, attrs); } -void ui_stop_highlight(void) +void ui_clear_highlight(void) { - current_attr_code = HL_NORMAL; - - if (!ui_count) { - return; - } - - set_highlight_args(current_attr_code); + ui_set_highlight(0); } void ui_puts(uint8_t *str) @@ -446,6 +438,12 @@ void ui_puts(uint8_t *str) ui_linefeed(); } p += clen; + + if (p_wd) { // 'writedelay': flush & delay each time. + ui_flush(); + uint64_t wd = (uint64_t)labs(p_wd); + os_delay(wd, false); + } } } @@ -489,26 +487,6 @@ void ui_flush(void) ui_call_flush(); } -static void set_highlight_args(int attr_code) -{ - HlAttrs rgb_attrs = HLATTRS_INIT; - HlAttrs cterm_attrs = rgb_attrs; - - if (attr_code == HL_NORMAL) { - goto end; - } - attrentry_T *aep = syn_cterm_attr2entry(attr_code); - - if (!aep) { - goto end; - } - - rgb_attrs = attrentry2hlattrs(aep, true); - cterm_attrs = attrentry2hlattrs(aep, false); - -end: - UI_CALL(highlight_set, (ui->rgb ? rgb_attrs : cterm_attrs)); -} void ui_linefeed(void) { @@ -548,15 +526,23 @@ void ui_cursor_shape(void) } /// Returns true if `widget` is externalized. -bool ui_is_external(UIWidget widget) +bool ui_is_external(UIExtension widget) { return ui_ext[widget]; } -/// Sets `widget` as "external". -/// Such widgets are not drawn by Nvim; external UIs are expected to handle -/// higher-level UI events and present the data. -void ui_set_external(UIWidget widget, bool external) +Array ui_array(void) { - ui_ext[widget] = external; + Array all_uis = ARRAY_DICT_INIT; + for (size_t i = 0; i < ui_count; i++) { + Dictionary dic = ARRAY_DICT_INIT; + PUT(dic, "width", INTEGER_OBJ(uis[i]->width)); + PUT(dic, "height", INTEGER_OBJ(uis[i]->height)); + PUT(dic, "rgb", BOOLEAN_OBJ(uis[i]->rgb)); + for (UIExtension j = 0; j < kUIExtCount; j++) { + PUT(dic, ui_ext_names[j], BOOLEAN_OBJ(uis[i]->ui_ext[j])); + } + ADD(all_uis, DICTIONARY_OBJ(dic)); + } + return all_uis; } diff --git a/src/nvim/ui.h b/src/nvim/ui.h index f1ea0716e6..48896a6a3f 100644 --- a/src/nvim/ui.h +++ b/src/nvim/ui.h @@ -13,29 +13,27 @@ typedef enum { kUIPopupmenu, kUITabline, kUIWildmenu, -} UIWidget; -#define UI_WIDGETS (kUIWildmenu + 1) + kUIExtCount, +} UIExtension; -typedef struct { - bool bold, underline, undercurl, italic, reverse; - int foreground, background, special; -} HlAttrs; +EXTERN const char *ui_ext_names[] INIT(= { + "ext_cmdline", + "ext_popupmenu", + "ext_tabline", + "ext_wildmenu" +}); -#define HLATTRS_INIT \ - ((HlAttrs){ false, false, false, false, false, -1, -1, -1 }) typedef struct ui_t UI; struct ui_t { bool rgb; - bool ui_ext[UI_WIDGETS]; ///< Externalized widgets + bool ui_ext[kUIExtCount]; ///< Externalized widgets int width, height; void *data; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ui_events.generated.h" #endif - void (*event)(UI *ui, char *name, Array args, bool *args_consumed); void (*stop)(UI *ui); }; diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c index 5585886612..56e0c0c454 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -59,16 +59,15 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler) rv->bridge.put = ui_bridge_put; rv->bridge.bell = ui_bridge_bell; rv->bridge.visual_bell = ui_bridge_visual_bell; - rv->bridge.update_fg = ui_bridge_update_fg; - rv->bridge.update_bg = ui_bridge_update_bg; - rv->bridge.update_sp = ui_bridge_update_sp; + rv->bridge.default_colors_set = ui_bridge_default_colors_set; rv->bridge.flush = ui_bridge_flush; rv->bridge.suspend = ui_bridge_suspend; rv->bridge.set_title = ui_bridge_set_title; rv->bridge.set_icon = ui_bridge_set_icon; + rv->bridge.option_set = ui_bridge_option_set; rv->scheduler = scheduler; - for (UIWidget i = 0; (int)i < UI_WIDGETS; i++) { + for (UIExtension i = 0; (int)i < kUIExtCount; i++) { rv->bridge.ui_ext[i] = ui->ui_ext[i]; } @@ -82,6 +81,7 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler) abort(); } + // Suspend the main thread until CONTINUE is called by the UI thread. while (!rv->ready) { uv_cond_wait(&rv->cond, &rv->mutex); } @@ -106,6 +106,9 @@ static void ui_thread_run(void *data) static void ui_bridge_stop(UI *b) { + // Detach brigde first, so that "stop" is the last event the TUI loop + // receives from the main thread. #8041 + ui_detach_impl(b); UIBridgeData *bridge = (UIBridgeData *)b; bool stopped = bridge->stopped = false; UI_BRIDGE_CALL(b, stop, 1, b); @@ -116,12 +119,12 @@ static void ui_bridge_stop(UI *b) if (stopped) { break; } - loop_poll_events(&main_loop, 10); + loop_poll_events(&main_loop, 10); // Process one event. } uv_thread_join(&bridge->ui_thread); uv_mutex_destroy(&bridge->mutex); uv_cond_destroy(&bridge->cond); - ui_detach_impl(b); + xfree(bridge->ui); // Threads joined, now safe to free UI container. #7922 xfree(b); } static void ui_bridge_stop_event(void **argv) @@ -149,7 +152,7 @@ static void ui_bridge_suspend(UI *b) uv_mutex_lock(&data->mutex); UI_BRIDGE_CALL(b, suspend, 1, b); data->ready = false; - // suspend the main thread until CONTINUE is called by the UI thread + // Suspend the main thread until CONTINUE is called by the UI thread. while (!data->ready) { uv_cond_wait(&data->cond, &data->mutex); } diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 3d7ebc8837..35857510fc 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -2096,8 +2096,8 @@ void undo_time(long step, int sec, int file, int absolute) uhp = uhp->uh_prev.ptr; if (uhp == NULL || uhp->uh_walk != mark) { - /* Need to redo more but can't find it... */ - EMSG2(_(e_intern2), "undo_time()"); + // Need to redo more but can't find it... + internal_error("undo_time()"); break; } } @@ -2163,8 +2163,8 @@ static void u_undoredo(int undo) if (top > curbuf->b_ml.ml_line_count || top >= bot || bot > curbuf->b_ml.ml_line_count + 1) { unblock_autocmds(); - EMSG(_("E438: u_undo: line numbers wrong")); - changed(); /* don't want UNCHANGED now */ + IEMSG(_("E438: u_undo: line numbers wrong")); + changed(); // don't want UNCHANGED now return; } @@ -2268,12 +2268,14 @@ static void u_undoredo(int undo) curhead->uh_entry = newlist; curhead->uh_flags = new_flags; - if ((old_flags & UH_EMPTYBUF) && bufempty()) + if ((old_flags & UH_EMPTYBUF) && BUFEMPTY()) { curbuf->b_ml.ml_flags |= ML_EMPTY; - if (old_flags & UH_CHANGED) + } + if (old_flags & UH_CHANGED) { changed(); - else + } else { unchanged(curbuf, FALSE); + } /* * restore marks from before undo/redo @@ -2655,7 +2657,7 @@ static void u_unch_branch(u_header_T *uhp) static u_entry_T *u_get_headentry(void) { if (curbuf->b_u_newhead == NULL || curbuf->b_u_newhead->uh_entry == NULL) { - EMSG(_("E439: undo list corrupt")); + IEMSG(_("E439: undo list corrupt")); return NULL; } return curbuf->b_u_newhead->uh_entry; @@ -2684,11 +2686,11 @@ static void u_getbot(void) extra = curbuf->b_ml.ml_line_count - uep->ue_lcount; uep->ue_bot = uep->ue_top + uep->ue_size + 1 + extra; if (uep->ue_bot < 1 || uep->ue_bot > curbuf->b_ml.ml_line_count) { - EMSG(_("E440: undo line missing")); - uep->ue_bot = uep->ue_top + 1; /* assume all lines deleted, will - * get all the old lines back - * without deleting the current - * ones */ + IEMSG(_("E440: undo line missing")); + uep->ue_bot = uep->ue_top + 1; // assume all lines deleted, will + // get all the old lines back + // without deleting the current + // ones } curbuf->b_u_newhead->uh_getbot_entry = NULL; @@ -2941,17 +2943,20 @@ bool curbufIsChanged(void) && (curbuf->b_changed || file_ff_differs(curbuf, true))); } -/* - * For undotree(): Append the list of undo blocks at "first_uhp" to "list". - * Recursive. - */ -void u_eval_tree(u_header_T *first_uhp, list_T *list) +/// Append the list of undo blocks to a newly allocated list +/// +/// For use in undotree(). Recursive. +/// +/// @param[in] first_uhp Undo blocks list to start with. +/// +/// @return [allocated] List with a representation of undo blocks. +list_T *u_eval_tree(const u_header_T *const first_uhp) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET { - u_header_T *uhp = first_uhp; - dict_T *dict; + list_T *const list = tv_list_alloc(kListLenMayKnow); - while (uhp != NULL) { - dict = tv_dict_alloc(); + for (const u_header_T *uhp = first_uhp; uhp != NULL; uhp = uhp->uh_prev.ptr) { + dict_T *const dict = tv_dict_alloc(); tv_dict_add_nr(dict, S_LEN("seq"), (varnumber_T)uhp->uh_seq); tv_dict_add_nr(dict, S_LEN("time"), (varnumber_T)uhp->uh_time); if (uhp == curbuf->b_u_newhead) { @@ -2965,14 +2970,12 @@ void u_eval_tree(u_header_T *first_uhp, list_T *list) } if (uhp->uh_alt_next.ptr != NULL) { - list_T *alt_list = tv_list_alloc(); - // Recursive call to add alternate undo tree. - u_eval_tree(uhp->uh_alt_next.ptr, alt_list); - tv_dict_add_list(dict, S_LEN("alt"), alt_list); + tv_dict_add_list(dict, S_LEN("alt"), u_eval_tree(uhp->uh_alt_next.ptr)); } tv_list_append_dict(list, dict); - uhp = uhp->uh_prev.ptr; } + + return list; } diff --git a/src/nvim/version.c b/src/nvim/version.c index 8d85855c6e..77ae849d2e 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -30,8 +30,8 @@ // for ":version", ":intro", and "nvim --version" #ifndef NVIM_VERSION_MEDIUM #define NVIM_VERSION_MEDIUM "v" STR(NVIM_VERSION_MAJOR)\ - "." STR(NVIM_VERSION_MINOR) "." STR(NVIM_VERSION_PATCH)\ - NVIM_VERSION_PRERELEASE +"." STR(NVIM_VERSION_MINOR) "." STR(NVIM_VERSION_PATCH)\ +NVIM_VERSION_PRERELEASE #endif #define NVIM_VERSION_LONG "NVIM " NVIM_VERSION_MEDIUM @@ -47,37 +47,241 @@ char *version_cflags = "Compilation: " NVIM_VERSION_CFLAGS; static char *features[] = { #ifdef HAVE_ACL - "+acl", +"+acl", #else - "-acl", +"-acl", #endif #if (defined(HAVE_ICONV_H) && defined(USE_ICONV)) || defined(DYNAMIC_ICONV) # ifdef DYNAMIC_ICONV - "+iconv/dyn", +"+iconv/dyn", # else - "+iconv", +"+iconv", # endif #else - "-iconv", +"-iconv", #endif #ifdef HAVE_JEMALLOC - "+jemalloc", +"+jemalloc", #else - "-jemalloc", +"-jemalloc", #endif #ifdef FEAT_TUI - "+tui", +"+tui", #else - "-tui", +"-tui", #endif - NULL +NULL }; // clang-format off static const int included_patches[] = { + 1561, + // 1560, + // 1559, + // 1558, + // 1557, + // 1556, + // 1555, + // 1554, + // 1553, + // 1552, + // 1551, + // 1550, + // 1549, + // 1548, + // 1547, + // 1546, + // 1545, + // 1544, + // 1543, + // 1542, + // 1541, + // 1540, + // 1539, + // 1538, + // 1537, + // 1536, + // 1535, + // 1534, + // 1533, + // 1532, + // 1531, + // 1530, + // 1529, + // 1528, + // 1527, + // 1526, + // 1525, + // 1524, + // 1523, + // 1522, + // 1521, + // 1520, + // 1519, + // 1518, + // 1517, + // 1516, + // 1515, + // 1514, + // 1513, + // 1512, + // 1511, + // 1510, + // 1509, + // 1508, + // 1507, + // 1506, + // 1505, + // 1504, + // 1503, + // 1502, + // 1501, + // 1500, + // 1499, + // 1498, + // 1497, + // 1496, + // 1495, + // 1494, + 1493, + // 1492, + // 1491, + // 1490, + // 1489, + // 1488, + // 1487, + // 1486, + // 1485, + // 1484, + 1483, + // 1482, + // 1481, + // 1480, + // 1479, + // 1478, + // 1477, + // 1476, + 1475, + // 1474, + // 1473, + // 1472, + // 1471, + // 1470, + // 1469, + // 1468, + // 1467, + // 1466, + // 1465, + // 1464, + // 1463, + // 1462, + // 1461, + // 1460, + // 1459, + // 1458, + // 1457, + // 1456, + // 1455, + // 1454, + // 1453, + // 1452, + // 1451, + // 1450, + // 1449, + // 1448, + // 1447, + // 1446, + // 1445, + // 1444, + // 1443, + 1442, + // 1441, + // 1440, + 1439, + // 1438, + // 1437, + // 1436, + 1435, + // 1434, + // 1433, + // 1432, + // 1431, + // 1430, + // 1429, + // 1428, + // 1427, + // 1426, + // 1425, + // 1424, + // 1423, + // 1422, + // 1421, + // 1420, + 1419, + // 1418, + // 1417, + // 1416, + // 1415, + // 1414, + // 1413, + // 1412, + // 1411, + // 1410, + // 1409, + // 1408, + // 1407, + // 1406, + // 1405, + // 1404, + 1403, + 1402, + // 1401, + // 1400, + // 1399, + // 1398, + // 1397, + 1396, + // 1395, + // 1394, + 1393, + // 1392, + // 1391, + // 1390, + // 1389, + // 1388, + // 1387, + // 1386, + // 1385, + // 1384, + // 1383, + // 1382, + // 1381, + // 1380, + // 1379, + // 1378, + // 1377, + // 1376, + // 1375, + // 1374, + // 1373, + // 1372, + // 1371, + 1370, + // 1369, + // 1368, + // 1367, + // 1366, + 1365, + // 1364, + // 1363, + // 1362, + // 1361, + // 1360, + // 1359, + // 1358, 1357, // 1356, // 1355, @@ -102,11 +306,11 @@ static const int included_patches[] = { // 1336, // 1335, // 1334, - // 1333, + 1333, // 1332, // 1331, // 1330, - // 1329, + 1329, // 1328, // 1327, // 1326, @@ -131,9 +335,9 @@ static const int included_patches[] = { // 1307, // 1306, // 1305, - // 1304, + 1304, // 1303, - // 1302, + 1302, // 1301, // 1300, // 1299, @@ -146,7 +350,7 @@ static const int included_patches[] = { // 1292, // 1291, // 1290, - // 1289, + 1289, // 1288, // 1287, // 1286, @@ -164,7 +368,7 @@ static const int included_patches[] = { // 1274, // 1273, // 1272, - // 1271, + 1271, // 1270, // 1269, // 1268, @@ -173,7 +377,7 @@ static const int included_patches[] = { // 1265, // 1264, // 1263, - // 1262, + 1262, // 1261, // 1260, // 1259, @@ -185,7 +389,7 @@ static const int included_patches[] = { // 1253, // 1252, // 1251, - // 1250, + 1250, // 1249, // 1248, // 1247, @@ -197,7 +401,7 @@ static const int included_patches[] = { // 1241, // 1240, // 1239, - // 1238, + 1238, // 1237, // 1236, // 1235, @@ -205,18 +409,18 @@ static const int included_patches[] = { // 1233, // 1232, // 1231, - // 1230, - // 1229, + 1230, + 1229, // 1228, // 1227, - // 1226, - // 1225, - // 1224, - // 1223, - // 1222, - // 1221, + 1226, + 1225, + 1224, + 1223, + 1222, + 1221, // 1220, - // 1219, + 1219, // 1218, // 1217, // 1216, @@ -225,11 +429,190 @@ static const int included_patches[] = { // 1213, // 1212, // 1211, - // 1210, + 1210, // 1209, // 1208, - // 1207, + 1207, 1206, + 1205, + 1204, + // 1203, + // 1202, + // 1201, + 1200, + // 1199, + // 1198, + // 1197, + // 1196, + // 1195, + // 1194, + // 1193, + // 1192, + // 1191, + // 1190, + 1189, + 1188, + // 1187, + 1186, + // 1185, + // 1184, + // 1183, + // 1182, + // 1181, + // 1180, + // 1179, + // 1178, + // 1177, + // 1176, + // 1175, + // 1174, + // 1173, + // 1172, + // 1171, + // 1170, + // 1169, + // 1168, + // 1167, + // 1166, + // 1165, + // 1164, + // 1163, + // 1162, + // 1161, + // 1160, + // 1159, + 1158, + // 1157, + // 1156, + // 1155, + // 1154, + // 1153, + // 1152, + // 1151, + // 1150, + // 1149, + // 1148, + // 1147, + // 1146, + // 1145, + // 1144, + // 1143, + // 1142, + // 1141, + // 1140, + // 1139, + // 1138, + // 1137, + // 1136, + // 1135, + // 1134, + // 1133, + // 1132, + // 1131, + // 1130, + // 1129, + // 1128, + // 1127, + // 1126, + // 1125, + // 1124, + // 1123, + // 1122, + // 1121, + // 1120, + // 1119, + // 1118, + // 1117, + // 1116, + // 1115, + // 1114, + // 1113, + // 1112, + // 1111, + // 1110, + // 1109, + 1108, + // 1107, + // 1106, + // 1105, + // 1104, + // 1103, + // 1102, + // 1101, + // 1100, + // 1099, + // 1098, + // 1097, + // 1096, + // 1095, + // 1094, + // 1093, + // 1092, + // 1091, + // 1090, + // 1089, + // 1088, + // 1087, + // 1086, + // 1085, + // 1084, + // 1083, + // 1082, + // 1081, + // 1080, + // 1079, + // 1078, + // 1077, + // 1076, + // 1075, + // 1074, + // 1073, + // 1072, + // 1071, + // 1070, + // 1069, + // 1068, + // 1067, + // 1066, + // 1065, + // 1064, + // 1063, + // 1062, + // 1061, + // 1060, + // 1059, + // 1058, + // 1057, + // 1056, + // 1055, + // 1054, + // 1053, + // 1052, + // 1051, + // 1050, + // 1049, + // 1048, + // 1047, + // 1046, + // 1045, + // 1044, + // 1043, + // 1042, + // 1041, + // 1040, + // 1039, + // 1038, + // 1037, + // 1036, + // 1035, + // 1034, + // 1033, + // 1032, + // 1031, + // 1030, + // 1029, + // 1028, + // 1027, // 1026, 1025, 1024, @@ -237,7 +620,7 @@ static const int included_patches[] = { // 1022, // 1021, // 1020, - // 1019, + 1019, // 1018, // 1017, // 1016, @@ -249,7 +632,7 @@ static const int included_patches[] = { // 1010, // 1009, // 1008, - // 1007, + 1007, // 1006, // 1005, // 1004, @@ -281,8 +664,8 @@ static const int included_patches[] = { // 978, // 977, // 976, - // 975, - // 974, + 975, + 974, // 973, // 972, // 971, @@ -294,14 +677,14 @@ static const int included_patches[] = { // 965, // 964, // 963, - // 962, + 962, // 961, // 960, // 959, // 958, // 957, // 956, - // 955, + 955, // 954, // 953, // 952, @@ -350,7 +733,7 @@ static const int included_patches[] = { // 909, // 908, // 907, - // 906, + 906, // 905, // 904, // 903, @@ -361,8 +744,8 @@ static const int included_patches[] = { // 898, // 897, // 896, - // 895, - // 894, + 895, + 894, // 893, // 892, // 891, @@ -394,11 +777,11 @@ static const int included_patches[] = { // 865, // 864, // 863, - // 862, - // 861, + 862, + 861, // 860, // 859, - // 858, + 858, // 857, // 856, // 855, @@ -409,7 +792,7 @@ static const int included_patches[] = { // 850, // 849, // 848, - // 847, + 847, // 846, // 845, // 844, @@ -464,7 +847,7 @@ static const int included_patches[] = { // 795, // 794, // 793, - // 792, + 792, // 791, // 790, // 789, @@ -520,7 +903,7 @@ static const int included_patches[] = { // 739, // 738, // 737, - // 736, + 736, // 735, // 734, // 733, @@ -533,9 +916,9 @@ static const int included_patches[] = { // 726, // 725, // 724, - // 723, + 723, // 722, - // 721, + 721, // 720, // 719, // 718, @@ -553,26 +936,26 @@ static const int included_patches[] = { // 706, // 705, // 704, - // 703, + 703, // 702, // 701, - // 700, - // 699, + 700, + 699, // 698, // 697, // 696, // 695, // 694, // 693, - // 692, + 692, // 691, // 690, - // 689, + 689, // 688, // 687, // 686, // 685, - // 684, + 684, // 683, // 682, // 681, @@ -580,33 +963,33 @@ static const int included_patches[] = { 679, 678, // 677, - // 676, + 676, // 675, - // 674, - // 673, - // 672, + 674, + 673, + 672, // 671, // 670, // 669, - // 668, - // 667, - // 666, - // 665, - // 664, + 668, + 667, + 666, + 665, + 664, // 663, - // 662, - // 661, - // 660, - // 659, - // 658, - // 657, - // 656, - // 655, - // 654, + 662, + 661, + 660, + 659, + 658, + 657, + 656, + 655, + 654, // 653, 652, // 651, - // 650, + 650, // 649, // 648, // 647, @@ -615,14 +998,14 @@ static const int included_patches[] = { // 644, // 643, // 642, - // 641, + 641, // 640, // 639, // 638, // 637, // 636, // 635, - // 634, + 634, // 633, // 632, // 631, @@ -634,7 +1017,7 @@ static const int included_patches[] = { // 625, // 624, // 623, - // 622, + 622, // 621, // 620, // 619, @@ -643,46 +1026,46 @@ static const int included_patches[] = { // 616, // 615, 614, - // 613, + 613, 612, // 611, // 610, // 609, - // 608, + 608, 607, - // 606, + 606, 605, // 604, // 603, // 602, - // 601, - // 600, - // 599, + 601, + 600, + 599, // 598, - // 597, + 597, // 596, - // 595, + 595, // 594, // 593, // 592, - // 591, - // 590, + 591, + 590, // 589, // 588, // 587, // 586, // 585, - // 584, + 584, // 583, - // 582, + 582, // 581, - // 580, - // 579, + 580, + 579, // 578, // 577, // 576, // 575, - // 574, + 574, // 573, // 572, 571, @@ -691,7 +1074,7 @@ static const int included_patches[] = { // 568, // 567, // 566, - // 565, + 565, // 564, // 563, // 562, @@ -720,7 +1103,7 @@ static const int included_patches[] = { // 539, // 538, // 537, - // 536, + 536, // 535, // 534, // 533, @@ -728,7 +1111,7 @@ static const int included_patches[] = { // 531, // 530, // 529, - // 528, + 528, // 527, // 526, // 525, @@ -739,7 +1122,7 @@ static const int included_patches[] = { // 520, // 519, 518, - // 517, + 517, // 516, // 515, // 514, @@ -766,27 +1149,27 @@ static const int included_patches[] = { // 493, // 492, // 491, - // 490, + 490, // 489, // 488, 487, 486, 485, - // 484, + 484, 483, 482, // 481, - // 480, - // 479, + 480, + 479, 478, 477, // 476, // 475, // 474, - // 473, - // 472, - // 471, - // 470, + 473, + 472, + 471, + 470, // 469, // 468, // 467, @@ -798,7 +1181,7 @@ static const int included_patches[] = { 461, // 460, 459, - // 458, + 458, 457, // 456, // 455, @@ -808,35 +1191,35 @@ static const int included_patches[] = { // 451, // 450, // 449, - // 448, + 448, // 447, - // 446, + 446, // 445, - // 444, - // 443, - // 442, + 444, + 443, + 442, // 441, - // 440, - // 439, + 440, + 439, // 438, 437, // 436, // 435, - // 434, - // 433, + 434, + 433, // 432, - // 431, + 431, // 430, // 429, // 428, - // 427, - // 426, + 427, + 426, // 425, - // 424, - // 423, + 424, + 423, // 422, - // 421, - // 420, + 421, + 420, // 419, // 418, // 417, @@ -850,39 +1233,39 @@ static const int included_patches[] = { // 409, 408, 407, - // 406, - // 405 NA - // 404, + 406, + 405, + 404, // 403, - // 402, + 402, // 401, - // 400 NA + 400, // 399, - // 398, + 398, // 397, // 396, // 395, 394, 393, // 392, - // 391, + 391, 390, - // 389, + 389, 388, // 387, // 386, - // 385, + 385, // 384, // 383, // 382, - // 381, - // 380, - // 379, + 381, + 380, + 379, 378, 377, 376, // 375, - // 374, + 374, // 373, // 372, // 371, @@ -892,20 +1275,20 @@ static const int included_patches[] = { // 367, // 366, // 365, - // 364, + 364, // 363, // 362, // 361, 360, - // 359, - // 358, + 359, + 358, // 357, // 356, // 355, // 354, 353, - // 352, - // 351, + 352, + 351, // 350, // 349, // 348, @@ -917,12 +1300,12 @@ static const int included_patches[] = { // 342, 341, // 340, - // 339, - // 338, - // 337, - // 336, - // 335, - // 334, + 339, + 338, + 337, + 336, + 335, + 334, 333, // 332, 331, @@ -933,73 +1316,73 @@ static const int included_patches[] = { 326, 325, 324, - // 323, + 323, 322, // 321, - // 320, + 320, 319, // 318, // 317, // 316, - // 315, - // 314, + 315, + 314, // 313, // 312, 311, - // 310, - // 309, + 310, + 309, 308, 307, 306, 305, // 304, // 303, - // 302 NA - // 301, + 302, + 301, 300, - // 299, + 299, 298, 297, // 296, 295, 294, - // 293, + 293, 292, 291, 290, 289, - // 288 NA + 288, 287, // 286, - // 285 NA - // 284 NA + 285, + 284, 283, 282, - // 281 NA + 281, 280, - // 279 NA - // 278 NA - // 277 NA - // 276 NA + 279, + 278, + 277, + 276, 275, 274, - // 273 NA - // 272 NA - // 271 NA - // 270 NA - // 269 NA - // 268 NA - // 267 NA + 273, + 272, + 271, + 270, + 269, + 268, + 267, 266, // 265, // 264, // 263, // 262, // 261, - // 260 NA + 260, 259, 258, - // 257 NA + 257, // 256, // 255, // 254, @@ -1007,118 +1390,118 @@ static const int included_patches[] = { // 252, // 251, 250, - // 249 NA - // 248 NA + 249, + 248, 247, - // 246 NA + 246, 245, - // 244, + 244, 243, 242, - // 241 NA - // 240 NA - // 239 NA + 241, + 240, + 239, // 238, 237, - // 236, + 236, 235, - // 234, + 234, // 233, - // 232 NA + 232, // 231, // 230, 229, // 228, - // 227, + 227, 226, - // 225, + 225, 224, 223, - // 222, - // 221 NA - // 220, + 222, + 221, + 220, 219, 218, - // 217 NA + 217, // 216, - // 215, - // 214, - // 213 NA - // 212, - // 211 NA + 215, + 214, + 213, + 212, + 211, // 210, 209, 208, // 207, 206, 205, - // 204, - // 203 NA - // 202, - // 201, - // 200, - // 199 NA - // 198, + 204, + 203, + 202, + 201, + 200, + 199, + 198, // 197, 196, 195, 194, - // 193 NA - // 192 NA - // 191 NA + 193, + 192, + 191, 190, 189, 188, - // 187 NA + 187, 186, - // 185, + 185, // 184, - // 183 NA + 183, 182, 181, - // 180 NA + 180, 179, 178, 177, 176, - // 175, + 175, 174, - // 173 NA + 173, 172, - // 171 NA - // 170 NA - // 169 NA + 171, + 170, + 169, 168, 167, - // 166 NA + 166, 165, 164, - // 163 NA - // 162 NA - // 161 NA - // 160, + 163, + 162, + 161, + 160, 159, 158, 157, 156, 155, - // 154, - // 153, - // 152 NA - // 151, + 154, + 153, + 152, + 151, 150, 149, 148, 147, 146, - // 145 NA - // 144 NA + 145, + 144, 143, 142, - // 141 NA + 141, 140, - // 139 NA - // 138 NA + 139, + 138, 137, 136, 135, @@ -1126,137 +1509,137 @@ static const int included_patches[] = { 133, 132, 131, - // 130 NA - // 129 NA + 130, + 129, 128, 127, 126, 125, 124, - // 123 NA - // 122 NA + 123, + 122, 121, - // 120 NA + 120, 119, 118, - // 117 NA + 117, 116, - // 115 NA - // 114 NA - // 113 NA + 115, + 114, + 113, 112, 111, 110, - // 109 NA - // 108 NA - // 107 NA + 109, + 108, + 107, 106, - // 105 NA + 105, 104, - // 103 NA + 103, 102, 101, 100, 99, - // 98 NA - // 97 NA + 98, + 97, 96, - // 95 NA - // 94 NA - // 93 NA + 95, + 94, + 93, 92, 91, 90, - // 89 NA + 89, 88, - // 87 NA + 87, 86, 85, 84, 83, - // 82 NA + 82, 81, - // 80 NA + 80, 79, 78, - // 77 NA - // 76 NA + 77, + 76, 75, - // 74, + 74, 73, - // 72 NA - // 71 NA - // 70 NA + 72, + 71, + 70, 69, 68, - // 67 NA + 67, 66, - // 65 NA + 65, 64, - // 63 NA + 63, 62, - // 61 NA + 61, 60, - // 59 NA + 59, 58, 57, 56, - // 55 NA - // 54 NA + 55, + 54, 53, 52, - // 51 NA - // 50 NA + 51, + 50, 49, - // 48 NA + 48, 47, 46, - // 45 NA + 45, 44, 43, 42, 41, 40, - // 39 NA + 39, 38, 37, - // 36 NA + 36, 35, 34, 33, 32, 31, - // 30 NA - // 29 NA - // 28 NA - // 27 NA + 30, + 29, + 28, + 27, 26, 25, - // 24 NA + 24, 23, - // 22 NA - // 21 NA + 22, + 21, 20, 19, - // 18 NA + 18, 17, - // 16 NA - // 15 NA - // 14 NA - // 13 NA + 16, + 15, + 14, + 13, 12, - // 11 NA - // 10 NA - // 9 NA + 11, + 10, + 9, 8, - // 7 NA + 7, 6, - // 5 NA + 5, 4, 3, 2, 1, - 0 + 0, }; // clang-format on @@ -1480,7 +1863,7 @@ static void version_msg(char *s) /// Show the intro message when not editing a file. void maybe_intro_message(void) { - if (bufempty() + if (BUFEMPTY() && (curbuf->b_fname == NULL) && (firstwin->w_next == NULL) && (vim_strchr(p_shm, SHM_INTRO) == NULL)) { diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 7da3c8246f..0c13d331c8 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -26,13 +26,6 @@ /// length of a buffer to store a number in ASCII (64 bits binary + NUL) enum { NUMBUFLEN = 65 }; -// flags for vim_str2nr() -#define STR2NR_BIN 1 -#define STR2NR_OCT 2 -#define STR2NR_HEX 4 -#define STR2NR_ALL (STR2NR_BIN + STR2NR_OCT + STR2NR_HEX) -#define STR2NR_FORCE 8 // only when ONE of the above is used - #define MAX_TYPENR 65535 #define ROOT_UID 0 @@ -162,6 +155,7 @@ enum { EXPAND_USER_ADDR_TYPE, EXPAND_PACKADD, EXPAND_MESSAGES, + EXPAND_CHECKHEALTH, }; @@ -319,4 +313,8 @@ enum { FOLD_TEXT_LEN = 51 }; //!< buffer size for get_foldtext() # define OPEN_CHR_FILES #endif +// Replacement for nchar used by nv_replace(). +#define REPLACE_CR_NCHAR -1 +#define REPLACE_NL_NCHAR -2 + #endif // NVIM_VIM_H diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c new file mode 100644 index 0000000000..ee59dc8c96 --- /dev/null +++ b/src/nvim/viml/parser/expressions.c @@ -0,0 +1,3103 @@ +// 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 + +/// VimL expression parser + +// Planned incompatibilities (to be included into vim_diff.txt when this parser +// will be an actual part of VimL evaluation process): +// +// 1. Expressions are first fully parsed and only then executed. This means +// that while ":echo [system('touch abc')" will create file "abc" in Vim and +// only then raise syntax error regarding missing comma in list in Neovim +// trying to execute that will immediately raise syntax error regarding +// missing list end without actually executing anything. +// 2. Expressions are first fully parsed, without considering any runtime +// information. This means things like that "d.a" does not change its +// meaning depending on type of "d" (or whether Vim is currently executing or +// skipping). For compatibility reasons the dot thus may either be “concat +// or subscript†operator or just “concat†operator. +// 3. Expressions parser is aware whether it is called for :echo or <C-r>=. +// This means that while "<C-r>=1 | 2<CR>" is equivalent to "<C-r>=1<CR>" +// because "| 2" part is left to be treated as a command separator and then +// ignored in Neovim it is an error. +// 4. Expressions parser has generally better error reporting. But for +// compatibility reasons most errors have error code E15 while error messages +// are significantly different from Vim’s E15. Also some error codes were +// retired because of being harder to emulate or because of them being +// a result of differences in parsing process: e.g. with ":echo {a, b}" Vim +// will attempt to parse expression as lambda, fail, check whether it is +// a curly-braces-name, fail again, and evaluate that as a dictionary, giving +// error regarding undefined variable "a" (or about missing colon). Neovim +// will not try to evaluate anything here: comma right after an argument name +// means that expression may not be anything, but lambda, so the resulting +// error message will never be about missing variable or colon: it will be +// about missing arrow (or a continuation of argument list). +// 5. Failing to parse expression always gives exactly one error message: no +// more stack of error messages like > +// +// :echo [1, +// E697: Missing end of List ']': +// E15: Invalid expression: [1, +// +// < , just exactly one E697 message. +// 6. Some expressions involving calling parenthesis which are treated +// separately by Vim even when not separated by spaces are treated as one +// expression by Neovim: e.g. ":echo (1)(1)" will yield runtime error after +// failing to call "1", while Vim will echo "1 1". Reasoning is the same: +// type of what is in the first expression is generally not known when +// parsing, so to have separate expressions like this separate them with +// spaces. +// 7. 'isident' no longer applies to environment variables, they always include +// ASCII alphanumeric characters and underscore and nothing except this. + +#include <stdbool.h> +#include <stddef.h> +#include <assert.h> +#include <string.h> + +#include "nvim/vim.h" +#include "nvim/memory.h" +#include "nvim/types.h" +#include "nvim/charset.h" +#include "nvim/ascii.h" +#include "nvim/assert.h" +#include "nvim/lib/kvec.h" +#include "nvim/eval/typval.h" + +#include "nvim/viml/parser/expressions.h" +#include "nvim/viml/parser/parser.h" + +#define vim_str2nr(s, ...) vim_str2nr((const char_u *)(s), __VA_ARGS__) + +typedef kvec_withinit_t(ExprASTNode **, 16) ExprASTStack; + +/// Which nodes may be wanted +typedef enum { + /// Operators: function call, subscripts, binary operators, … + /// + /// For unrestricted expressions. + kENodeOperator, + /// Values: literals, variables, nested expressions, unary operators. + /// + /// For unrestricted expressions as well, implies that top item in AST stack + /// points to NULL. + kENodeValue, +} ExprASTWantedNode; + +/// Parse type: what is being parsed currently +typedef enum { + /// Parsing regular VimL expression + kEPTExpr = 0, + /// Parsing lambda arguments + /// + /// Just like parsing function arguments, but it is valid to be ended with an + /// arrow only. + kEPTLambdaArguments, + /// Assignment: parsing for :let + kEPTAssignment, + /// Single assignment: used when lists are not allowed (i.e. when nesting) + kEPTSingleAssignment, +} ExprASTParseType; + +typedef kvec_withinit_t(ExprASTParseType, 4) ExprASTParseTypeStack; + +/// Operator priority level +typedef enum { + kEOpLvlInvalid = 0, + kEOpLvlComplexIdentifier, + kEOpLvlParens, + kEOpLvlAssignment, + kEOpLvlArrow, + kEOpLvlComma, + kEOpLvlColon, + kEOpLvlTernaryValue, + kEOpLvlTernary, + kEOpLvlOr, + kEOpLvlAnd, + kEOpLvlComparison, + kEOpLvlAddition, ///< Addition, subtraction and concatenation. + kEOpLvlMultiplication, ///< Multiplication, division and modulo. + kEOpLvlUnary, ///< Unary operations: not, minus, plus. + kEOpLvlSubscript, ///< Subscripts. + kEOpLvlValue, ///< Values: literals, variables, nested expressions, … +} ExprOpLvl; + +/// Operator associativity +typedef enum { + kEOpAssNo= 'n', ///< Not associative / not applicable. + kEOpAssLeft = 'l', ///< Left associativity. + kEOpAssRight = 'r', ///< Right associativity. +} ExprOpAssociativity; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "viml/parser/expressions.c.generated.h" +#endif + +/// Character used as a separator in autoload function/variable names. +#define AUTOLOAD_CHAR '#' + +/// Scale number by a given factor +/// +/// Used to apply exponent to a number. Idea taken from uClibc. +/// +/// @param[in] num Number to scale. Does not bother doing anything if it is +/// zero. +/// @param[in] base Base, should be 10 since non-decimal floating-point +/// numbers are not supported. +/// @param[in] exponent Exponent to scale by. +/// @param[in] exponent_negative True if exponent is negative. +static inline float_T scale_number(const float_T num, + const uint8_t base, + const uvarnumber_T exponent, + const bool exponent_negative) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_CONST +{ + if (num == 0 || exponent == 0) { + return num; + } + assert(base); + uvarnumber_T exp = exponent; + float_T p_base = (float_T)base; + float_T ret = num; + while (exp) { + if (exp & 1) { + if (exponent_negative) { + ret /= p_base; + } else { + ret *= p_base; + } + } + exp >>= 1; + p_base *= p_base; + } + return ret; +} + +/// Get next token for the VimL expression input +/// +/// @param pstate Parser state. +/// @param[in] flags Flags, @see LexExprFlags. +/// +/// @return Next token. +LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + LexExprToken ret = { + .type = kExprLexInvalid, + .start = pstate->pos, + }; + ParserLine pline; + if (!viml_parser_get_remaining_line(pstate, &pline)) { + ret.type = kExprLexEOC; + return ret; + } + if (pline.size <= 0) { + ret.len = 0; + ret.type = kExprLexEOC; + goto viml_pexpr_next_token_adv_return; + } + ret.len = 1; + const uint8_t schar = (uint8_t)pline.data[0]; +#define GET_CCS(ret, pline) \ + do { \ + if (ret.len < pline.size \ + && strchr("?#", pline.data[ret.len]) != NULL) { \ + ret.data.cmp.ccs = \ + (ExprCaseCompareStrategy)pline.data[ret.len]; \ + ret.len++; \ + } else { \ + ret.data.cmp.ccs = kCCStrategyUseOption; \ + } \ + } while (0) + switch (schar) { + // Paired brackets. +#define BRACKET(typ, opning, clsing) \ + case opning: \ + case clsing: { \ + ret.type = typ; \ + ret.data.brc.closing = (schar == clsing); \ + break; \ + } + BRACKET(kExprLexParenthesis, '(', ')') + BRACKET(kExprLexBracket, '[', ']') + BRACKET(kExprLexFigureBrace, '{', '}') +#undef BRACKET + + // Single character tokens without data. +#define CHAR(typ, ch) \ + case ch: { \ + ret.type = typ; \ + break; \ + } + CHAR(kExprLexQuestion, '?') + CHAR(kExprLexColon, ':') + CHAR(kExprLexComma, ',') +#undef CHAR + + // Multiplication/division/modulo. +#define MUL(mul_type, ch) \ + case ch: { \ + ret.type = kExprLexMultiplication; \ + ret.data.mul.type = mul_type; \ + break; \ + } + MUL(kExprLexMulMul, '*') + MUL(kExprLexMulDiv, '/') + MUL(kExprLexMulMod, '%') +#undef MUL + +#define CHARREG(typ, cond) \ + do { \ + ret.type = typ; \ + for (; (ret.len < pline.size \ + && cond(pline.data[ret.len])) \ + ; ret.len++) { \ + } \ + } while (0) + + // Whitespace. + case ' ': + case TAB: { + CHARREG(kExprLexSpacing, ascii_iswhite); + break; + } + + // Control character, except for NUL, NL and TAB. + case Ctrl_A: case Ctrl_B: case Ctrl_C: case Ctrl_D: case Ctrl_E: + case Ctrl_F: case Ctrl_G: case Ctrl_H: + + case Ctrl_K: case Ctrl_L: case Ctrl_M: case Ctrl_N: case Ctrl_O: + case Ctrl_P: case Ctrl_Q: case Ctrl_R: case Ctrl_S: case Ctrl_T: + case Ctrl_U: case Ctrl_V: case Ctrl_W: case Ctrl_X: case Ctrl_Y: + case Ctrl_Z: { +#define ISCTRL(schar) (schar < ' ') + CHARREG(kExprLexInvalid, ISCTRL); + ret.data.err.type = kExprLexSpacing; + ret.data.err.msg = + _("E15: Invalid control character present in input: %.*s"); + break; +#undef ISCTRL + } + + // Number. + case '0': case '1': case '2': case '3': case '4': case '5': case '6': + case '7': case '8': case '9': { + ret.data.num.is_float = false; + ret.data.num.base = 10; + size_t frac_start = 0; + size_t exp_start = 0; + size_t frac_end = 0; + bool exp_negative = false; + CHARREG(kExprLexNumber, ascii_isdigit); + if (flags & kELFlagAllowFloat) { + const LexExprToken non_float_ret = ret; + if (pline.size > ret.len + 1 + && pline.data[ret.len] == '.' + && ascii_isdigit(pline.data[ret.len + 1])) { + ret.len++; + frac_start = ret.len; + frac_end = ret.len; + ret.data.num.is_float = true; + for (; ret.len < pline.size && ascii_isdigit(pline.data[ret.len]) + ; ret.len++) { + // A small optimization: trailing zeroes in fractional part do not + // add anything to significand, so it is useless to include them in + // frac_end. + if (pline.data[ret.len] != '0') { + frac_end = ret.len + 1; + } + } + if (pline.size > ret.len + 1 + && (pline.data[ret.len] == 'e' + || pline.data[ret.len] == 'E') + && ((pline.size > ret.len + 2 + && (pline.data[ret.len + 1] == '+' + || pline.data[ret.len + 1] == '-') + && ascii_isdigit(pline.data[ret.len + 2])) + || ascii_isdigit(pline.data[ret.len + 1]))) { + ret.len++; + if (pline.data[ret.len] == '+' + || (exp_negative = (pline.data[ret.len] == '-'))) { + ret.len++; + } + exp_start = ret.len; + CHARREG(kExprLexNumber, ascii_isdigit); + } + } + if (pline.size > ret.len + && (pline.data[ret.len] == '.' + || ASCII_ISALPHA(pline.data[ret.len]))) { + ret = non_float_ret; + } + } + // TODO(ZyX-I): detect overflows + if (ret.data.num.is_float) { + // Vim used to use string2float here which in turn uses strtod(). There + // are two problems with this approach: + // 1. strtod() is locale-dependent. Not sure how it is worked around so + // that I do not see relevant bugs, but it still does not look like + // a good idea. + // 2. strtod() does not accept length argument. + // + // The below variant of parsing floats was recognized as acceptable + // because it is basically how uClibc does the thing: it generates + // a number ignoring decimal point (but recording its position), then + // uses recorded position to scale number down when processing exponent. + float_T significand_part = 0; + uvarnumber_T exp_part = 0; + const size_t frac_size = (size_t)(frac_end - frac_start); + for (size_t i = 0; i < frac_end; i++) { + if (i == frac_start - 1) { + continue; + } + significand_part = significand_part * 10 + (pline.data[i] - '0'); + } + if (exp_start) { + vim_str2nr(pline.data + exp_start, NULL, NULL, 0, NULL, &exp_part, + (int)(ret.len - exp_start)); + } + if (exp_negative) { + exp_part += frac_size; + } else { + if (exp_part < frac_size) { + exp_negative = true; + exp_part = frac_size - exp_part; + } else { + exp_part -= frac_size; + } + } + ret.data.num.val.floating = scale_number(significand_part, 10, exp_part, + exp_negative); + } else { + int len; + int prep; + vim_str2nr(pline.data, &prep, &len, STR2NR_ALL, NULL, + &ret.data.num.val.integer, (int)pline.size); + ret.len = (size_t)len; + const uint8_t bases[] = { + [0] = 10, + ['0'] = 8, + ['x'] = 16, ['X'] = 16, + ['b'] = 2, ['B'] = 2, + }; + ret.data.num.base = bases[prep]; + } + break; + } + +#define ISWORD_OR_AUTOLOAD(x) \ + (ascii_isident(x) || (x) == AUTOLOAD_CHAR) + + // Environment variable. + case '$': { + CHARREG(kExprLexEnv, ascii_isident); + break; + } + + // Normal variable/function name. + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': + case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': + case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': + case 'v': case 'w': case 'x': case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': + case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': + case 'V': case 'W': case 'X': case 'Y': case 'Z': + case '_': { + ret.data.var.scope = 0; + ret.data.var.autoload = false; + CHARREG(kExprLexPlainIdentifier, ascii_isident); + // "is" and "isnot" operators. + if (!(flags & kELFlagIsNotCmp) + && ((ret.len == 2 && memcmp(pline.data, "is", 2) == 0) + || (ret.len == 5 && memcmp(pline.data, "isnot", 5) == 0))) { + ret.type = kExprLexComparison; + ret.data.cmp.type = kExprCmpIdentical; + ret.data.cmp.inv = (ret.len == 5); + GET_CCS(ret, pline); + // Scope: `s:`, etc. + } else if (ret.len == 1 + && pline.size > 1 + && memchr(EXPR_VAR_SCOPE_LIST, schar, + sizeof(EXPR_VAR_SCOPE_LIST)) != NULL + && pline.data[ret.len] == ':' + && !(flags & kELFlagForbidScope)) { + ret.len++; + ret.data.var.scope = (ExprVarScope)schar; + CHARREG(kExprLexPlainIdentifier, ISWORD_OR_AUTOLOAD); + ret.data.var.autoload = ( + memchr(pline.data + 2, AUTOLOAD_CHAR, ret.len - 2) + != NULL); + // Previous CHARREG stopped at autoload character in order to make it + // possible to detect `is#`. Continue now with autoload characters + // included. + // + // Warning: there is ambiguity for the lexer: `is#Foo(1)` is a call of + // function `is#Foo()`, `1is#Foo(1)` is a comparison `1 is# Foo(1)`. This + // needs to be resolved on the higher level where context is available. + } else if (pline.size > ret.len + && pline.data[ret.len] == AUTOLOAD_CHAR) { + ret.data.var.autoload = true; + CHARREG(kExprLexPlainIdentifier, ISWORD_OR_AUTOLOAD); + } + break; + } + +#undef ISWORD_OR_AUTOLOAD +#undef CHARREG + + // Option. + case '&': { +#define OPTNAMEMISS(ret) \ + do { \ + ret.type = kExprLexInvalid; \ + ret.data.err.type = kExprLexOption; \ + ret.data.err.msg = _("E112: Option name missing: %.*s"); \ + } while (0) + if (pline.size > 1 && pline.data[1] == '&') { + ret.type = kExprLexAnd; + ret.len++; + break; + } + if (pline.size == 1 || !ASCII_ISALPHA(pline.data[1])) { + OPTNAMEMISS(ret); + break; + } + ret.type = kExprLexOption; + if (pline.size > 2 + && pline.data[2] == ':' + && memchr(EXPR_OPT_SCOPE_LIST, pline.data[1], + sizeof(EXPR_OPT_SCOPE_LIST)) != NULL) { + ret.len += 2; + ret.data.opt.scope = (ExprOptScope)pline.data[1]; + ret.data.opt.name = pline.data + 3; + } else { + ret.data.opt.scope = kExprOptScopeUnspecified; + ret.data.opt.name = pline.data + 1; + } + const char *p = ret.data.opt.name; + const char *const e = pline.data + pline.size; + if (e - p >= 4 && p[0] == 't' && p[1] == '_') { + ret.data.opt.len = 4; + ret.len += 4; + } else { + for (; p < e && ASCII_ISALPHA(*p); p++) { + } + ret.data.opt.len = (size_t)(p - ret.data.opt.name); + if (ret.data.opt.len == 0) { + OPTNAMEMISS(ret); + } else { + ret.len += ret.data.opt.len; + } + } + break; +#undef OPTNAMEMISS + } + + // Register. + case '@': { + ret.type = kExprLexRegister; + if (pline.size > 1) { + ret.len++; + ret.data.reg.name = (uint8_t)pline.data[1]; + } else { + ret.data.reg.name = -1; + } + break; + } + + // Single quoted string. + case '\'': { + ret.type = kExprLexSingleQuotedString; + ret.data.str.closed = false; + for (; ret.len < pline.size && !ret.data.str.closed; ret.len++) { + if (pline.data[ret.len] == '\'') { + if (ret.len + 1 < pline.size && pline.data[ret.len + 1] == '\'') { + ret.len++; + } else { + ret.data.str.closed = true; + } + } + } + break; + } + + // Double quoted string. + case '"': { + ret.type = kExprLexDoubleQuotedString; + ret.data.str.closed = false; + for (; ret.len < pline.size && !ret.data.str.closed; ret.len++) { + if (pline.data[ret.len] == '\\') { + if (ret.len + 1 < pline.size) { + ret.len++; + } + } else if (pline.data[ret.len] == '"') { + ret.data.str.closed = true; + } + } + break; + } + + // Unary not, (un)equality and regex (not) match comparison operators. + case '!': + case '=': { + if (pline.size == 1) { + ret.type = (schar == '!' ? kExprLexNot : kExprLexAssignment); + ret.data.ass.type = kExprAsgnPlain; + break; + } + ret.type = kExprLexComparison; + ret.data.cmp.inv = (schar == '!'); + if (pline.data[1] == '=') { + ret.data.cmp.type = kExprCmpEqual; + ret.len++; + } else if (pline.data[1] == '~') { + ret.data.cmp.type = kExprCmpMatches; + ret.len++; + } else if (schar == '!') { + ret.type = kExprLexNot; + } else { + ret.type = kExprLexAssignment; + ret.data.ass.type = kExprAsgnPlain; + } + GET_CCS(ret, pline); + break; + } + + // Less/greater [or equal to] comparison operators. + case '>': + case '<': { + ret.type = kExprLexComparison; + const bool haseqsign = (pline.size > 1 && pline.data[1] == '='); + if (haseqsign) { + ret.len++; + } + GET_CCS(ret, pline); + ret.data.cmp.inv = (schar == '<'); + ret.data.cmp.type = ((ret.data.cmp.inv ^ haseqsign) + ? kExprCmpGreaterOrEqual + : kExprCmpGreater); + break; + } + + // Minus sign, arrow from lambdas or augmented assignment. + case '-': { + if (pline.size > 1 && pline.data[1] == '>') { + ret.len++; + ret.type = kExprLexArrow; + } else if (pline.size > 1 && pline.data[1] == '=') { + ret.len++; + ret.type = kExprLexAssignment; + ret.data.ass.type = kExprAsgnSubtract; + } else { + ret.type = kExprLexMinus; + } + break; + } + + // Sign or augmented assignment. +#define CHAR_OR_ASSIGN(ch, ch_type, ass_type) \ + case ch: { \ + if (pline.size > 1 && pline.data[1] == '=') { \ + ret.len++; \ + ret.type = kExprLexAssignment; \ + ret.data.ass.type = ass_type; \ + } else { \ + ret.type = ch_type; \ + } \ + break; \ + } + CHAR_OR_ASSIGN('+', kExprLexPlus, kExprAsgnAdd) + CHAR_OR_ASSIGN('.', kExprLexDot, kExprAsgnConcat) +#undef CHAR_OR_ASSIGN + + // Expression end because Ex command ended. + case NUL: + case NL: { + if (flags & kELFlagForbidEOC) { + ret.type = kExprLexInvalid; + ret.data.err.msg = _("E15: Unexpected EOC character: %.*s"); + ret.data.err.type = kExprLexSpacing; + } else { + ret.type = kExprLexEOC; + } + break; + } + + case '|': { + if (pline.size >= 2 && pline.data[ret.len] == '|') { + // "||" is or. + ret.len++; + ret.type = kExprLexOr; + } else if (flags & kELFlagForbidEOC) { + // Note: `<C-r>=1 | 2<CR>` actually yields 1 in Vim without any + // errors. This will be changed here. + ret.type = kExprLexInvalid; + ret.data.err.msg = _("E15: Unexpected EOC character: %.*s"); + ret.data.err.type = kExprLexOr; + } else { + ret.type = kExprLexEOC; + } + break; + } + + // Everything else is not valid. + default: { + ret.len = (size_t)utfc_ptr2len_len((const char_u *)pline.data, + (int)pline.size); + ret.type = kExprLexInvalid; + ret.data.err.type = kExprLexPlainIdentifier; + ret.data.err.msg = _("E15: Unidentified character: %.*s"); + break; + } + } +#undef GET_CCS +viml_pexpr_next_token_adv_return: + if (!(flags & kELFlagPeek)) { + viml_parser_advance(pstate, ret.len); + } + return ret; +} + +static const char *const eltkn_type_tab[] = { + [kExprLexInvalid] = "Invalid", + [kExprLexMissing] = "Missing", + [kExprLexSpacing] = "Spacing", + [kExprLexEOC] = "EOC", + + [kExprLexQuestion] = "Question", + [kExprLexColon] = "Colon", + [kExprLexOr] = "Or", + [kExprLexAnd] = "And", + [kExprLexComparison] = "Comparison", + [kExprLexPlus] = "Plus", + [kExprLexMinus] = "Minus", + [kExprLexDot] = "Dot", + [kExprLexMultiplication] = "Multiplication", + + [kExprLexNot] = "Not", + + [kExprLexNumber] = "Number", + [kExprLexSingleQuotedString] = "SingleQuotedString", + [kExprLexDoubleQuotedString] = "DoubleQuotedString", + [kExprLexOption] = "Option", + [kExprLexRegister] = "Register", + [kExprLexEnv] = "Env", + [kExprLexPlainIdentifier] = "PlainIdentifier", + + [kExprLexBracket] = "Bracket", + [kExprLexFigureBrace] = "FigureBrace", + [kExprLexParenthesis] = "Parenthesis", + [kExprLexComma] = "Comma", + [kExprLexArrow] = "Arrow", + [kExprLexAssignment] = "Assignment", +}; + +const char *const eltkn_cmp_type_tab[] = { + [kExprCmpEqual] = "Equal", + [kExprCmpMatches] = "Matches", + [kExprCmpGreater] = "Greater", + [kExprCmpGreaterOrEqual] = "GreaterOrEqual", + [kExprCmpIdentical] = "Identical", +}; + +const char *const expr_asgn_type_tab[] = { + [kExprAsgnPlain] = "Plain", + [kExprAsgnAdd] = "Add", + [kExprAsgnSubtract] = "Subtract", + [kExprAsgnConcat] = "Concat", +}; + +const char *const ccs_tab[] = { + [kCCStrategyUseOption] = "UseOption", + [kCCStrategyMatchCase] = "MatchCase", + [kCCStrategyIgnoreCase] = "IgnoreCase", +}; + +static const char *const eltkn_mul_type_tab[] = { + [kExprLexMulMul] = "Mul", + [kExprLexMulDiv] = "Div", + [kExprLexMulMod] = "Mod", +}; + +static const char *const eltkn_opt_scope_tab[] = { + [kExprOptScopeUnspecified] = "Unspecified", + [kExprOptScopeGlobal] = "Global", + [kExprOptScopeLocal] = "Local", +}; + +/// Represent token as a string +/// +/// Intended for testing and debugging purposes. +/// +/// @param[in] pstate Parser state, needed to get token string from it. May be +/// NULL, in which case in place of obtaining part of the +/// string represented by token only token length is +/// returned. +/// @param[in] token Token to represent. +/// @param[out] ret_size Return string size, for cases like NULs inside +/// a string. May be NULL. +/// +/// @return Token represented in a string form, in a static buffer (overwritten +/// on each call). +const char *viml_pexpr_repr_token(const ParserState *const pstate, + const LexExprToken token, + size_t *const ret_size) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + static char ret[1024]; + char *p = ret; + const char *const e = &ret[1024] - 1; +#define ADDSTR(...) \ + do { \ + p += snprintf(p, (size_t)(sizeof(ret) - (size_t)(p - ret)), __VA_ARGS__); \ + if (p >= e) { \ + goto viml_pexpr_repr_token_end; \ + } \ + } while (0) + ADDSTR("%zu:%zu:%s", token.start.line, token.start.col, + eltkn_type_tab[token.type]); + switch (token.type) { +#define TKNARGS(tkn_type, ...) \ + case tkn_type: { \ + ADDSTR(__VA_ARGS__); \ + break; \ + } + TKNARGS(kExprLexComparison, "(type=%s,ccs=%s,inv=%i)", + eltkn_cmp_type_tab[token.data.cmp.type], + ccs_tab[token.data.cmp.ccs], + (int)token.data.cmp.inv) + TKNARGS(kExprLexMultiplication, "(type=%s)", + eltkn_mul_type_tab[token.data.mul.type]) + TKNARGS(kExprLexAssignment, "(type=%s)", + expr_asgn_type_tab[token.data.ass.type]) + TKNARGS(kExprLexRegister, "(name=%s)", intchar2str(token.data.reg.name)) + case kExprLexDoubleQuotedString: + TKNARGS(kExprLexSingleQuotedString, "(closed=%i)", + (int)token.data.str.closed) + TKNARGS(kExprLexOption, "(scope=%s,name=%.*s)", + eltkn_opt_scope_tab[token.data.opt.scope], + (int)token.data.opt.len, token.data.opt.name) + TKNARGS(kExprLexPlainIdentifier, "(scope=%s,autoload=%i)", + intchar2str((int)token.data.var.scope), + (int)token.data.var.autoload) + TKNARGS(kExprLexNumber, "(is_float=%i,base=%i,val=%lg)", + (int)token.data.num.is_float, + (int)token.data.num.base, + (double)(token.data.num.is_float + ? (double)token.data.num.val.floating + : (double)token.data.num.val.integer)) + TKNARGS(kExprLexInvalid, "(msg=%s)", token.data.err.msg) + default: { + // No additional arguments. + break; + } +#undef TKNARGS + } + if (pstate == NULL) { + ADDSTR("::%zu", token.len); + } else { + *p++ = ':'; + memmove( + p, &pstate->reader.lines.items[token.start.line].data[token.start.col], + token.len); + p += token.len; + *p = NUL; + } +#undef ADDSTR +viml_pexpr_repr_token_end: + if (ret_size != NULL) { + *ret_size = (size_t)(p - ret); + } + return ret; +} + +const char *const east_node_type_tab[] = { + [kExprNodeMissing] = "Missing", + [kExprNodeOpMissing] = "OpMissing", + [kExprNodeTernary] = "Ternary", + [kExprNodeTernaryValue] = "TernaryValue", + [kExprNodeRegister] = "Register", + [kExprNodeSubscript] = "Subscript", + [kExprNodeListLiteral] = "ListLiteral", + [kExprNodeUnaryPlus] = "UnaryPlus", + [kExprNodeBinaryPlus] = "BinaryPlus", + [kExprNodeNested] = "Nested", + [kExprNodeCall] = "Call", + [kExprNodePlainIdentifier] = "PlainIdentifier", + [kExprNodePlainKey] = "PlainKey", + [kExprNodeComplexIdentifier] = "ComplexIdentifier", + [kExprNodeUnknownFigure] = "UnknownFigure", + [kExprNodeLambda] = "Lambda", + [kExprNodeDictLiteral] = "DictLiteral", + [kExprNodeCurlyBracesIdentifier] = "CurlyBracesIdentifier", + [kExprNodeComma] = "Comma", + [kExprNodeColon] = "Colon", + [kExprNodeArrow] = "Arrow", + [kExprNodeComparison] = "Comparison", + [kExprNodeConcat] = "Concat", + [kExprNodeConcatOrSubscript] = "ConcatOrSubscript", + [kExprNodeInteger] = "Integer", + [kExprNodeFloat] = "Float", + [kExprNodeSingleQuotedString] = "SingleQuotedString", + [kExprNodeDoubleQuotedString] = "DoubleQuotedString", + [kExprNodeOr] = "Or", + [kExprNodeAnd] = "And", + [kExprNodeUnaryMinus] = "UnaryMinus", + [kExprNodeBinaryMinus] = "BinaryMinus", + [kExprNodeNot] = "Not", + [kExprNodeMultiplication] = "Multiplication", + [kExprNodeDivision] = "Division", + [kExprNodeMod] = "Mod", + [kExprNodeOption] = "Option", + [kExprNodeEnvironment] = "Environment", + [kExprNodeAssignment] = "Assignment", +}; + +/// Represent `int` character as a string +/// +/// Converts +/// - ASCII digits into '{digit}' +/// - ASCII printable characters into a single-character strings +/// - everything else to numbers. +/// +/// @param[in] ch Character to convert. +/// +/// @return Converted string, stored in a static buffer (overriden after each +/// call). +static const char *intchar2str(const int ch) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + static char buf[sizeof(int) * 3 + 1]; + if (' ' <= ch && ch < 0x7f) { + if (ascii_isdigit(ch)) { + buf[0] = '\''; + buf[1] = (char)ch; + buf[2] = '\''; + buf[3] = NUL; + } else { + buf[0] = (char)ch; + buf[1] = NUL; + } + } else { + snprintf(buf, sizeof(buf), "%i", ch); + } + return buf; +} + +#ifdef UNIT_TESTING +#include <stdio.h> + +REAL_FATTR_UNUSED +static inline void viml_pexpr_debug_print_ast_node( + const ExprASTNode *const *const eastnode_p, + const char *const prefix) +{ + if (*eastnode_p == NULL) { + fprintf(stderr, "%s %p : NULL\n", prefix, (void *)eastnode_p); + } else { + fprintf(stderr, "%s %p : %p : %s : %zu:%zu:%zu\n", + prefix, (void *)eastnode_p, (void *)(*eastnode_p), + east_node_type_tab[(*eastnode_p)->type], (*eastnode_p)->start.line, + (*eastnode_p)->start.col, (*eastnode_p)->len); + } +} + +REAL_FATTR_UNUSED +static inline void viml_pexpr_debug_print_ast_stack( + const ExprASTStack *const ast_stack, + const char *const msg) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE +{ + fprintf(stderr, "\n%sstack: %zu:\n", msg, kv_size(*ast_stack)); + for (size_t i = 0; i < kv_size(*ast_stack); i++) { + viml_pexpr_debug_print_ast_node( + (const ExprASTNode *const *)kv_A(*ast_stack, i), + "-"); + } +} + +REAL_FATTR_UNUSED +static inline void viml_pexpr_debug_print_token( + const ParserState *const pstate, const LexExprToken token) + FUNC_ATTR_ALWAYS_INLINE +{ + fprintf(stderr, "\ntkn: %s\n", viml_pexpr_repr_token(pstate, token, NULL)); +} +#define PSTACK(msg) \ + viml_pexpr_debug_print_ast_stack(&ast_stack, #msg) +#define PSTACK_P(msg) \ + viml_pexpr_debug_print_ast_stack(ast_stack, #msg) +#define PNODE_P(eastnode_p, msg) \ + viml_pexpr_debug_print_ast_node((const ExprASTNode *const *)eastnode_p, \ + (#msg)) +#define PTOKEN(tkn) \ + viml_pexpr_debug_print_token(pstate, tkn) +#endif + +const uint8_t node_maxchildren[] = { + [kExprNodeMissing] = 0, + [kExprNodeOpMissing] = 2, + [kExprNodeTernary] = 2, + [kExprNodeTernaryValue] = 2, + [kExprNodeRegister] = 0, + [kExprNodeSubscript] = 2, + [kExprNodeListLiteral] = 1, + [kExprNodeUnaryPlus] = 1, + [kExprNodeBinaryPlus] = 2, + [kExprNodeNested] = 1, + [kExprNodeCall] = 2, + [kExprNodePlainIdentifier] = 0, + [kExprNodePlainKey] = 0, + [kExprNodeComplexIdentifier] = 2, + [kExprNodeUnknownFigure] = 1, + [kExprNodeLambda] = 2, + [kExprNodeDictLiteral] = 1, + [kExprNodeCurlyBracesIdentifier] = 1, + [kExprNodeComma] = 2, + [kExprNodeColon] = 2, + [kExprNodeArrow] = 2, + [kExprNodeComparison] = 2, + [kExprNodeConcat] = 2, + [kExprNodeConcatOrSubscript] = 2, + [kExprNodeInteger] = 0, + [kExprNodeFloat] = 0, + [kExprNodeSingleQuotedString] = 0, + [kExprNodeDoubleQuotedString] = 0, + [kExprNodeOr] = 2, + [kExprNodeAnd] = 2, + [kExprNodeUnaryMinus] = 1, + [kExprNodeBinaryMinus] = 2, + [kExprNodeNot] = 1, + [kExprNodeMultiplication] = 2, + [kExprNodeDivision] = 2, + [kExprNodeMod] = 2, + [kExprNodeOption] = 0, + [kExprNodeEnvironment] = 0, + [kExprNodeAssignment] = 2, +}; + +/// Free memory occupied by AST +/// +/// @param ast AST stack to free. +void viml_pexpr_free_ast(ExprAST ast) +{ + ExprASTStack ast_stack; + kvi_init(ast_stack); + kvi_push(ast_stack, &ast.root); + while (kv_size(ast_stack)) { + ExprASTNode **const cur_node = kv_last(ast_stack); +#ifndef NDEBUG + // Explicitly check for AST recursiveness. + for (size_t i = 0 ; i < kv_size(ast_stack) - 1 ; i++) { + assert(*kv_A(ast_stack, i) != *cur_node); + } +#endif + if (*cur_node == NULL) { + assert(kv_size(ast_stack) == 1); + kv_drop(ast_stack, 1); + } else if ((*cur_node)->children != NULL) { +#ifndef NDEBUG + const uint8_t maxchildren = node_maxchildren[(*cur_node)->type]; + assert(maxchildren > 0); + assert(maxchildren <= 2); + assert(maxchildren == 1 + ? (*cur_node)->children->next == NULL + : ((*cur_node)->children->next == NULL + || (*cur_node)->children->next->next == NULL)); +#endif + kvi_push(ast_stack, &(*cur_node)->children); + } else if ((*cur_node)->next != NULL) { + kvi_push(ast_stack, &(*cur_node)->next); + } else if (*cur_node != NULL) { + kv_drop(ast_stack, 1); + switch ((*cur_node)->type) { + case kExprNodeDoubleQuotedString: + case kExprNodeSingleQuotedString: { + xfree((*cur_node)->data.str.value); + break; + } + case kExprNodeMissing: + case kExprNodeOpMissing: + case kExprNodeTernary: + case kExprNodeTernaryValue: + case kExprNodeRegister: + case kExprNodeSubscript: + case kExprNodeListLiteral: + case kExprNodeUnaryPlus: + case kExprNodeBinaryPlus: + case kExprNodeNested: + case kExprNodeCall: + case kExprNodePlainIdentifier: + case kExprNodePlainKey: + case kExprNodeComplexIdentifier: + case kExprNodeUnknownFigure: + case kExprNodeLambda: + case kExprNodeDictLiteral: + case kExprNodeCurlyBracesIdentifier: + case kExprNodeAssignment: + case kExprNodeComma: + case kExprNodeColon: + case kExprNodeArrow: + case kExprNodeComparison: + case kExprNodeConcat: + case kExprNodeConcatOrSubscript: + case kExprNodeInteger: + case kExprNodeFloat: + case kExprNodeOr: + case kExprNodeAnd: + case kExprNodeUnaryMinus: + case kExprNodeBinaryMinus: + case kExprNodeNot: + case kExprNodeMultiplication: + case kExprNodeDivision: + case kExprNodeMod: + case kExprNodeOption: + case kExprNodeEnvironment: { + break; + } + } + xfree(*cur_node); + *cur_node = NULL; + } + } + kvi_destroy(ast_stack); +} + +// Binary operator precedence and associativity: +// +// Operator | Precedence | Associativity +// ---------+------------+----------------- +// || | 2 | left +// && | 3 | left +// cmp* | 4 | not associative +// + - . | 5 | left +// * / % | 6 | left +// +// * comparison operators: +// +// == ==# ==? != !=# !=? +// =~ =~# =~? !~ !~# !~? +// > ># >? <= <=# <=? +// < <# <? >= >=# >=? +// is is# is? isnot isnot# isnot? + +/// Allocate a new node and set some of the values +/// +/// @param[in] type Node type to allocate. +/// @param[in] level Node level to allocate +static inline ExprASTNode *viml_pexpr_new_node(const ExprASTNodeType type) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC +{ + ExprASTNode *ret = xmalloc(sizeof(*ret)); + ret->type = type; + ret->children = NULL; + ret->next = NULL; + return ret; +} + +static struct { + ExprOpLvl lvl; + ExprOpAssociativity ass; +} node_type_to_node_props[] = { + [kExprNodeMissing] = { kEOpLvlInvalid, kEOpAssNo, }, + [kExprNodeOpMissing] = { kEOpLvlMultiplication, kEOpAssNo }, + + [kExprNodeNested] = { kEOpLvlParens, kEOpAssNo }, + // Note: below nodes are kEOpLvlSubscript for “binary operator†itself, but + // kEOpLvlParens when it comes to inside the parenthesis. + [kExprNodeCall] = { kEOpLvlParens, kEOpAssNo }, + [kExprNodeSubscript] = { kEOpLvlParens, kEOpAssNo }, + + [kExprNodeUnknownFigure] = { kEOpLvlParens, kEOpAssLeft }, + [kExprNodeLambda] = { kEOpLvlParens, kEOpAssNo }, + [kExprNodeDictLiteral] = { kEOpLvlParens, kEOpAssNo }, + [kExprNodeListLiteral] = { kEOpLvlParens, kEOpAssNo }, + + [kExprNodeArrow] = { kEOpLvlArrow, kEOpAssNo }, + + // Right associativity for comma because this means easier access to arguments + // list, etc: for "[a, b, c, d]" you can access "a" in one step if it is + // represented as "list(comma(a, comma(b, comma(c, d))))" then if it is + // "list(comma(comma(comma(a, b), c), d))" in which case you will need to + // traverse all three comma() structures. And with comma operator (including + // actual comma operator from C which is not present in VimL) nobody cares + // about associativity, only about order of execution. + [kExprNodeComma] = { kEOpLvlComma, kEOpAssRight }, + + // Colons are not eligible for chaining, so nobody cares about associativity. + [kExprNodeColon] = { kEOpLvlColon, kEOpAssNo }, + + [kExprNodeTernary] = { kEOpLvlTernary, kEOpAssRight }, + + [kExprNodeOr] = { kEOpLvlOr, kEOpAssLeft }, + + [kExprNodeAnd] = { kEOpLvlAnd, kEOpAssLeft }, + + [kExprNodeTernaryValue] = { kEOpLvlTernaryValue, kEOpAssRight }, + + [kExprNodeComparison] = { kEOpLvlComparison, kEOpAssRight }, + + [kExprNodeBinaryPlus] = { kEOpLvlAddition, kEOpAssLeft }, + [kExprNodeBinaryMinus] = { kEOpLvlAddition, kEOpAssLeft }, + [kExprNodeConcat] = { kEOpLvlAddition, kEOpAssLeft }, + + [kExprNodeMultiplication] = { kEOpLvlMultiplication, kEOpAssLeft }, + [kExprNodeDivision] = { kEOpLvlMultiplication, kEOpAssLeft }, + [kExprNodeMod] = { kEOpLvlMultiplication, kEOpAssLeft }, + + [kExprNodeUnaryPlus] = { kEOpLvlUnary, kEOpAssNo }, + [kExprNodeUnaryMinus] = { kEOpLvlUnary, kEOpAssNo }, + [kExprNodeNot] = { kEOpLvlUnary, kEOpAssNo }, + + [kExprNodeConcatOrSubscript] = { kEOpLvlSubscript, kEOpAssLeft }, + + [kExprNodeCurlyBracesIdentifier] = { kEOpLvlComplexIdentifier, kEOpAssLeft }, + + [kExprNodeAssignment] = { kEOpLvlAssignment, kEOpAssLeft }, + + [kExprNodeComplexIdentifier] = { kEOpLvlValue, kEOpAssLeft }, + + [kExprNodePlainIdentifier] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodePlainKey] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeRegister] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeInteger] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeFloat] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeDoubleQuotedString] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeSingleQuotedString] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeOption] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeEnvironment] = { kEOpLvlValue, kEOpAssNo }, +}; + +/// Get AST node priority level +/// +/// Used primary to reduce line length, so keep the name short. +/// +/// @param[in] node Node to get priority for. +/// +/// @return Node priority level. +static inline ExprOpLvl node_lvl(const ExprASTNode node) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT +{ + return node_type_to_node_props[node.type].lvl; +} + +/// Get AST node associativity, to be used for operator nodes primary +/// +/// Used primary to reduce line length, so keep the name short. +/// +/// @param[in] node Node to get priority for. +/// +/// @return Node associativity. +static inline ExprOpAssociativity node_ass(const ExprASTNode node) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT +{ + return node_type_to_node_props[node.type].ass; +} + +/// Handle binary operator +/// +/// This function is responsible for handling priority levels as well. +/// +/// @param[in] pstate Parser state, used for error reporting. +/// @param ast_stack AST stack. May be popped of some values and will +/// definitely receive new ones. +/// @param bop_node New node to handle. +/// @param[out] want_node_p New value of want_node. +/// @param[out] ast_err Location where error is saved, if any. +/// +/// @return True if no errors occurred, false otherwise. +static bool viml_pexpr_handle_bop(const ParserState *const pstate, + ExprASTStack *const ast_stack, + ExprASTNode *const bop_node, + ExprASTWantedNode *const want_node_p, + ExprASTError *const ast_err) + FUNC_ATTR_NONNULL_ALL +{ + bool ret = true; + ExprASTNode **top_node_p = NULL; + ExprASTNode *top_node; + ExprOpLvl top_node_lvl; + ExprOpAssociativity top_node_ass; + assert(kv_size(*ast_stack)); + const ExprOpLvl bop_node_lvl = ((bop_node->type == kExprNodeCall + || bop_node->type == kExprNodeSubscript) + ? kEOpLvlSubscript + : node_lvl(*bop_node)); +#ifndef NDEBUG + const ExprOpAssociativity bop_node_ass = ( + (bop_node->type == kExprNodeCall + || bop_node->type == kExprNodeSubscript) + ? kEOpAssLeft + : node_ass(*bop_node)); +#endif + do { + ExprASTNode **new_top_node_p = kv_last(*ast_stack); + ExprASTNode *new_top_node = *new_top_node_p; + assert(new_top_node != NULL); + const ExprOpLvl new_top_node_lvl = node_lvl(*new_top_node); + const ExprOpAssociativity new_top_node_ass = node_ass(*new_top_node); + assert(bop_node_lvl != new_top_node_lvl + || bop_node_ass == new_top_node_ass); + if (top_node_p != NULL + && ((bop_node_lvl > new_top_node_lvl + || (bop_node_lvl == new_top_node_lvl + && new_top_node_ass == kEOpAssNo)))) { + break; + } + kv_drop(*ast_stack, 1); + top_node_p = new_top_node_p; + top_node = new_top_node; + top_node_lvl = new_top_node_lvl; + top_node_ass = new_top_node_ass; + if (bop_node_lvl == top_node_lvl && top_node_ass == kEOpAssRight) { + break; + } + } while (kv_size(*ast_stack)); + if (top_node_ass == kEOpAssLeft || top_node_lvl != bop_node_lvl) { + // outer(op(x,y)) -> outer(new_op(op(x,y),*)) + // + // Before: top_node_p = outer(*), points to op(x,y) + // Other stack elements unknown + // + // After: top_node_p = outer(*), points to new_op(op(x,y)) + // &bop_node->children->next = new_op(op(x,y),*), points to NULL + *top_node_p = bop_node; + bop_node->children = top_node; + assert(bop_node->children->next == NULL); + kvi_push(*ast_stack, top_node_p); + kvi_push(*ast_stack, &bop_node->children->next); + } else { + assert(top_node_lvl == bop_node_lvl && top_node_ass == kEOpAssRight); + assert(top_node->children != NULL && top_node->children->next != NULL); + // outer(op(x,y)) -> outer(op(x,new_op(y,*))) + // + // Before: top_node_p = outer(*), points to op(x,y) + // Other stack elements unknown + // + // After: top_node_p = outer(*), points to op(x,new_op(y)) + // &top_node->children->next = op(x,*), points to new_op(y) + // &bop_node->children->next = new_op(y,*), points to NULL + bop_node->children = top_node->children->next; + top_node->children->next = bop_node; + assert(bop_node->children->next == NULL); + kvi_push(*ast_stack, top_node_p); + kvi_push(*ast_stack, &top_node->children->next); + kvi_push(*ast_stack, &bop_node->children->next); + // TODO(ZyX-I): Make this not error, but treat like Python does + if (bop_node->type == kExprNodeComparison) { + east_set_error(pstate, ast_err, + _("E15: Operator is not associative: %.*s"), + bop_node->start); + ret = false; + } + } + *want_node_p = kENodeValue; + return ret; +} + +/// ParserPosition literal based on ParserPosition pos with columns shifted +/// +/// Function does not check whether resulting position is valid. +/// +/// @param[in] pos Position to shift. +/// @param[in] shift Number of bytes to shift. +/// +/// @return Shifted position. +static inline ParserPosition shifted_pos(const ParserPosition pos, + const size_t shift) + FUNC_ATTR_CONST FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return (ParserPosition) { .line = pos.line, .col = pos.col + shift }; +} + +/// ParserPosition literal based on ParserPosition pos with specified column +/// +/// Function does not check whether remaining position is valid. +/// +/// @param[in] pos Position to adjust. +/// @param[in] new_col New column. +/// +/// @return Shifted position. +static inline ParserPosition recol_pos(const ParserPosition pos, + const size_t new_col) + FUNC_ATTR_CONST FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return (ParserPosition) { .line = pos.line, .col = new_col }; +} + +/// Get highlight group name +#define HL(g) (is_invalid ? "NvimInvalid" #g : "Nvim" #g) + +/// Highlight current token with the given group +#define HL_CUR_TOKEN(g) \ + viml_parser_highlight(pstate, cur_token.start, cur_token.len, \ + HL(g)) + +/// Allocate new node, saving some values +#define NEW_NODE(type) \ + viml_pexpr_new_node(type) + +/// Set position of the given node to position from the given token +/// +/// @param cur_node Node to modify. +/// @param cur_token Token to set position from. +#define POS_FROM_TOKEN(cur_node, cur_token) \ + do { \ + (cur_node)->start = cur_token.start; \ + (cur_node)->len = cur_token.len; \ + } while (0) + +/// Allocate new node and set its position from the current token +/// +/// If previous token happened to contain spacing then it will be included. +/// +/// @param cur_node Variable to save allocated node to. +/// @param typ Node type. +#define NEW_NODE_WITH_CUR_POS(cur_node, typ) \ + do { \ + (cur_node) = NEW_NODE(typ); \ + POS_FROM_TOKEN((cur_node), cur_token); \ + if (prev_token.type == kExprLexSpacing) { \ + (cur_node)->start = prev_token.start; \ + (cur_node)->len += prev_token.len; \ + } \ + } while (0) + +/// Check whether it is possible to have next expression after current +/// +/// For :echo: `:echo @a @a` is a valid expression. `:echo (@a @a)` is not. +#define MAY_HAVE_NEXT_EXPR \ + (kv_size(ast_stack) == 1) + +/// Add operator node +/// +/// @param[in] cur_node Node to add. +#define ADD_OP_NODE(cur_node) \ + is_invalid |= !viml_pexpr_handle_bop(pstate, &ast_stack, cur_node, \ + &want_node, &ast.err) + +/// Record missing operator: for things like +/// +/// :echo @a @a +/// +/// (allowed) or +/// +/// :echo (@a @a) +/// +/// (parsed as OpMissing(@a, @a)). +#define OP_MISSING \ + do { \ + if (flags & kExprFlagsMulti && MAY_HAVE_NEXT_EXPR) { \ + /* Multiple expressions allowed, return without calling */ \ + /* viml_parser_advance(). */ \ + goto viml_pexpr_parse_end; \ + } else { \ + assert(*top_node_p != NULL); \ + ERROR_FROM_TOKEN_AND_MSG(cur_token, _("E15: Missing operator: %.*s")); \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeOpMissing); \ + cur_node->len = 0; \ + ADD_OP_NODE(cur_node); \ + goto viml_pexpr_parse_process_token; \ + } \ + } while (0) + +/// Record missing value: for things like "* 5" +/// +/// @param[in] msg Error message. +#define ADD_VALUE_IF_MISSING(msg) \ + do { \ + if (want_node == kENodeValue) { \ + ERROR_FROM_TOKEN_AND_MSG(cur_token, (msg)); \ + NEW_NODE_WITH_CUR_POS((*top_node_p), kExprNodeMissing); \ + (*top_node_p)->len = 0; \ + want_node = kENodeOperator; \ + } \ + } while (0) + +/// Set AST error, unless AST already is not correct +/// +/// @param[out] ret_ast AST to set error in. +/// @param[in] pstate Parser state, used to get error message argument. +/// @param[in] msg Error message, assumed to be already translated and +/// containing a single %token "%.*s". +/// @param[in] start Position at which error occurred. +static inline void east_set_error(const ParserState *const pstate, + ExprASTError *const ret_ast_err, + const char *const msg, + const ParserPosition start) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE +{ + if (ret_ast_err->msg != NULL) { + return; + } + 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; +} + +/// Set error from the given token and given message +#define ERROR_FROM_TOKEN_AND_MSG(cur_token, msg) \ + do { \ + is_invalid = true; \ + east_set_error(pstate, &ast.err, msg, cur_token.start); \ + } while (0) + +/// Like #ERROR_FROM_TOKEN_AND_MSG, but gets position from a node +#define ERROR_FROM_NODE_AND_MSG(node, msg) \ + do { \ + is_invalid = true; \ + east_set_error(pstate, &ast.err, msg, node->start); \ + } while (0) + +/// Set error from the given kExprLexInvalid token +#define ERROR_FROM_TOKEN(cur_token) \ + ERROR_FROM_TOKEN_AND_MSG(cur_token, cur_token.data.err.msg) + +/// Select figure brace type, altering highlighting as well if needed +/// +/// @param[out] node Node to modify type. +/// @param[in] new_type New type, one of ExprASTNodeType values without +/// kExprNode prefix. +/// @param[in] hl Corresponding highlighting, passed as an argument to #HL. +#define SELECT_FIGURE_BRACE_TYPE(node, new_type, hl) \ + do { \ + ExprASTNode *const node_ = (node); \ + assert(node_->type == kExprNodeUnknownFigure \ + || node_->type == kExprNode##new_type); \ + node_->type = kExprNode##new_type; \ + if (pstate->colors) { \ + kv_A(*pstate->colors, node_->data.fig.opening_hl_idx).group = \ + HL(hl); \ + } \ + } while (0) + +/// Add identifier which should constitute complex identifier node +/// +/// This one is to be called only in case want_node is kENodeOperator. +/// +/// @param new_ident_node_code Code used to create a new identifier node and +/// update want_node and ast_stack, without +/// a trailing semicolon. +/// @param hl Highlighting name to use, passed as an argument to #HL. +#define ADD_IDENT(new_ident_node_code, hl) \ + do { \ + assert(want_node == kENodeOperator); \ + /* Operator: may only be curly braces name, but only under certain */ \ + /* conditions. */ \ +\ + /* First condition is that there is no space before a part of complex */ \ + /* identifier. */ \ + if (prev_token.type == kExprLexSpacing) { \ + OP_MISSING; \ + } \ + switch ((*top_node_p)->type) { \ + /* Second is that previous node is one of the identifiers: */ \ + /* complex, plain, curly braces. */ \ +\ + /* TODO(ZyX-I): Extend syntax to allow ${expr}. This is needed to */ \ + /* handle environment variables like those bash uses for */ \ + /* `export -f`: their names consist not only of alphanumeric */ \ + /* characetrs. */ \ + case kExprNodeComplexIdentifier: \ + case kExprNodePlainIdentifier: \ + case kExprNodeCurlyBracesIdentifier: { \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComplexIdentifier); \ + cur_node->len = 0; \ + cur_node->children = *top_node_p; \ + *top_node_p = cur_node; \ + kvi_push(ast_stack, &cur_node->children->next); \ + ExprASTNode **const new_top_node_p = kv_last(ast_stack); \ + assert(*new_top_node_p == NULL); \ + new_ident_node_code; \ + *new_top_node_p = cur_node; \ + HL_CUR_TOKEN(hl); \ + break; \ + } \ + default: { \ + OP_MISSING; \ + break; \ + } \ + } \ + } while (0) + +/// Determine whether given parse type is an assignment +/// +/// @param[in] pt Checked parse type. +/// +/// @return true if parsing an assignment, false otherwise. +static inline bool pt_is_assignment(const ExprASTParseType pt) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT +{ + return (pt == kEPTAssignment || pt == kEPTSingleAssignment); +} + +/// Structure used to define “string shifts†necessary to map string +/// highlighting to actual strings. +typedef struct { + size_t start; ///< Where special character starts in original string. + size_t orig_len; ///< Length of orininal string (e.g. 4 for "\x80"). + size_t act_len; ///< Length of resulting character(s) (e.g. 1 for "\x80"). + bool escape_not_known; ///< True if escape sequence in original is not known. +} StringShift; + +/// Parse and highlight single- or double-quoted string +/// +/// Function is supposed to detect and highlight regular expressions (but does +/// not do now). +/// +/// @param[out] pstate Parser state which also contains a place where +/// highlighting is saved. +/// @param[out] node Node where string parsing results are saved. +/// @param[in] token Token to highlight. +/// @param[in] ast_stack Parser AST stack, used to detect whether current +/// string is a regex. +/// @param[in] is_invalid Whether currently processed token is not valid. +static void parse_quoted_string(ParserState *const pstate, + ExprASTNode *const node, + const LexExprToken token, + const ExprASTStack ast_stack, + const bool is_invalid) + FUNC_ATTR_NONNULL_ALL +{ + const ParserLine pline = pstate->reader.lines.items[token.start.line]; + const char *const s = pline.data + token.start.col; + const char *const e = s + token.len - token.data.str.closed; + const char *p = s + 1; + const bool is_double = (token.type == kExprLexDoubleQuotedString); + size_t size = token.len - token.data.str.closed - 1; + kvec_withinit_t(StringShift, 16) shifts; + kvi_init(shifts); + if (!is_double) { + viml_parser_highlight(pstate, token.start, 1, HL(SingleQuote)); + while (p < e) { + const char *const chunk_e = memchr(p, '\'', (size_t)(e - p)); + if (chunk_e == NULL) { + break; + } + size--; + p = chunk_e + 2; + if (pstate->colors) { + kvi_push(shifts, ((StringShift) { + .start = token.start.col + (size_t)(chunk_e - s), + .orig_len = 2, + .act_len = 1, + .escape_not_known = false, + })); + } + } + node->data.str.size = size; + if (size == 0) { + node->data.str.value = NULL; + } else { + char *v_p; + v_p = node->data.str.value = xmallocz(size); + p = s + 1; + while (p < e) { + const char *const chunk_e = memchr(p, '\'', (size_t)(e - p)); + if (chunk_e == NULL) { + memcpy(v_p, p, (size_t)(e - p)); + break; + } + memcpy(v_p, p, (size_t)(chunk_e - p)); + v_p += (size_t)(chunk_e - p) + 1; + v_p[-1] = '\''; + p = chunk_e + 2; + } + } + } else { + viml_parser_highlight(pstate, token.start, 1, HL(DoubleQuote)); + for (p = s + 1; p < e; p++) { + if (*p == '\\' && p + 1 < e) { + p++; + if (p + 1 == e) { + size--; + break; + } + switch (*p) { + // A "\<x>" form occupies at least 4 characters, and produces up to + // 6 characters: reserve space for 2 extra, but do not compute actual + // length just now, it would be costy. + case '<': { + size += 2; + break; + } + // Hexadecimal, always single byte, but at least three bytes each. + case 'x': case 'X': { + size--; + if (ascii_isxdigit(p[1])) { + size--; + if (p + 2 < e && ascii_isxdigit(p[2])) { + size--; + } + } + break; + } + // Unicode + // + // \uF takes 1 byte which is 2 bytes less then escape sequence. + // \uFF: 2 bytes, 2 bytes less. + // \uFFF: 3 bytes, 2 bytes less. + // \uFFFF: 3 bytes, 3 bytes less. + // \UFFFFF: 4 bytes, 3 bytes less. + // \UFFFFFF: 5 bytes, 3 bytes less. + // \UFFFFFFF: 6 bytes, 3 bytes less. + // \U7FFFFFFF: 6 bytes, 4 bytes less. + case 'u': case 'U': { + const char *const esc_start = p; + size_t n = (*p == 'u' ? 4 : 8); + int nr = 0; + p++; + while (p + 1 < e && n-- && ascii_isxdigit(p[1])) { + p++; + nr = (nr << 4) + hex2nr(*p); + } + // Escape length: (esc_start - 1) points to "\\", esc_start to "u" + // or "U", p to the byte after last byte. So escape sequence + // occupies p - (esc_start - 1), but it stands for a utf_char2len + // bytes. + size -= (size_t)((p - (esc_start - 1)) - utf_char2len(nr)); + p--; + break; + } + // Octal, always single byte, but at least two bytes each. + case '0': case '1': case '2': case '3': case '4': case '5': case '6': + case '7': { + size--; + p++; + if (*p >= '0' && *p <= '7') { + size--; + p++; + if (p < e && *p >= '0' && *p <= '7') { + size--; + p++; + } + } + break; + } + default: { + size--; + break; + } + } + } + } + if (size == 0) { + node->data.str.value = NULL; + node->data.str.size = 0; + } else { + char *v_p; + v_p = node->data.str.value = xmalloc(size); + p = s + 1; + while (p < e) { + const char *const chunk_e = memchr(p, '\\', (size_t)(e - p)); + if (chunk_e == NULL) { + memcpy(v_p, p, (size_t)(e - p)); + v_p += e - p; + break; + } + memcpy(v_p, p, (size_t)(chunk_e - p)); + v_p += (size_t)(chunk_e - p); + p = chunk_e + 1; + if (p == e) { + *v_p++ = '\\'; + break; + } + bool is_unknown = false; + const char *const v_p_start = v_p; + switch (*p) { +#define SINGLE_CHAR_ESC(ch, real_ch) \ + case ch: { \ + *v_p++ = real_ch; \ + p++; \ + break; \ + } + SINGLE_CHAR_ESC('b', BS) + SINGLE_CHAR_ESC('e', ESC) + SINGLE_CHAR_ESC('f', FF) + SINGLE_CHAR_ESC('n', NL) + SINGLE_CHAR_ESC('r', CAR) + SINGLE_CHAR_ESC('t', TAB) + SINGLE_CHAR_ESC('"', '"') + SINGLE_CHAR_ESC('\\', '\\') +#undef SINGLE_CHAR_ESC + + // Hexadecimal or unicode. + case 'X': case 'x': case 'u': case 'U': { + if (p + 1 < e && ascii_isxdigit(p[1])) { + size_t n; + int nr; + bool is_hex = (*p == 'x' || *p == 'X'); + + if (is_hex) { + n = 2; + } else if (*p == 'u') { + n = 4; + } else { + n = 8; + } + nr = 0; + while (p + 1 < e && n-- && ascii_isxdigit(p[1])) { + p++; + nr = (nr << 4) + hex2nr(*p); + } + p++; + if (is_hex) { + *v_p++ = (char)nr; + } else { + v_p += utf_char2bytes(nr, (char_u *)v_p); + } + } else { + is_unknown = true; + *v_p++ = *p; + p++; + } + break; + } + // Octal: "\1", "\12", "\123". + case '0': case '1': case '2': case '3': case '4': case '5': case '6': + case '7': { + uint8_t ch = (uint8_t)(*p++ - '0'); + if (p < e && *p >= '0' && *p <= '7') { + ch = (uint8_t)((ch << 3) + *p++ - '0'); + if (p < e && *p >= '0' && *p <= '7') { + ch = (uint8_t)((ch << 3) + *p++ - '0'); + } + } + *v_p++ = (char)ch; + break; + } + // Special key, e.g.: "\<C-W>" + case '<': { + const size_t special_len = ( + trans_special((const char_u **)&p, (size_t)(e - p), + (char_u *)v_p, true, true)); + if (special_len != 0) { + v_p += special_len; + } else { + is_unknown = true; + mb_copy_char((const char_u **)&p, (char_u **)&v_p); + } + break; + } + default: { + is_unknown = true; + mb_copy_char((const char_u **)&p, (char_u **)&v_p); + break; + } + } + if (pstate->colors) { + kvi_push(shifts, ((StringShift) { + .start = token.start.col + (size_t)(chunk_e - s), + .orig_len = (size_t)(p - chunk_e), + .act_len = (size_t)(v_p - (char *)v_p_start), + .escape_not_known = is_unknown, + })); + } + } + node->data.str.size = (size_t)(v_p - node->data.str.value); + } + } + if (pstate->colors) { + // TODO(ZyX-I): use ast_stack to determine and highlight regular expressions + // TODO(ZyX-I): use ast_stack to determine and highlight printf format str + // TODO(ZyX-I): use ast_stack to determine and highlight expression strings + size_t next_col = token.start.col + 1; + const char *const body_str = (is_double + ? HL(DoubleQuotedBody) + : HL(SingleQuotedBody)); + const char *const esc_str = (is_double + ? HL(DoubleQuotedEscape) + : HL(SingleQuotedQuote)); + const char *const ukn_esc_str = (is_double + ? HL(DoubleQuotedUnknownEscape) + : HL(SingleQuotedUnknownEscape)); + for (size_t i = 0; i < kv_size(shifts); i++) { + const StringShift cur_shift = kv_A(shifts, i); + if (cur_shift.start > next_col) { + viml_parser_highlight(pstate, recol_pos(token.start, next_col), + cur_shift.start - next_col, + body_str); + } + viml_parser_highlight(pstate, recol_pos(token.start, cur_shift.start), + cur_shift.orig_len, + (cur_shift.escape_not_known + ? ukn_esc_str + : esc_str)); + next_col = cur_shift.start + cur_shift.orig_len; + } + if (next_col - token.start.col < token.len - token.data.str.closed) { + viml_parser_highlight(pstate, recol_pos(token.start, next_col), + (token.start.col + + token.len + - token.data.str.closed + - next_col), + body_str); + } + } + if (token.data.str.closed) { + if (is_double) { + viml_parser_highlight(pstate, shifted_pos(token.start, token.len - 1), + 1, HL(DoubleQuote)); + } else { + viml_parser_highlight(pstate, shifted_pos(token.start, token.len - 1), + 1, HL(SingleQuote)); + } + } + kvi_destroy(shifts); +} + +/// Additional flags to pass to lexer depending on want_node +static const int want_node_to_lexer_flags[] = { + [kENodeValue] = kELFlagIsNotCmp, + [kENodeOperator] = kELFlagForbidScope, +}; + +/// Number of characters to highlight as NumberPrefix depending on the base +static const uint8_t base_to_prefix_length[] = { + [2] = 2, + [8] = 1, + [10] = 0, + [16] = 2, +}; + +/// Parse one VimL expression +/// +/// @param pstate Parser state. +/// @param[in] flags Additional flags, see ExprParserFlags +/// +/// @return Parsed AST. +ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + ExprAST ast = { + .err = { + .msg = NULL, + .arg_len = 0, + .arg = NULL, + }, + .root = NULL, + }; + // Expression stack contains current branch in AST tree: that is + // - Stack item 0 contains root of the tree, i.e. &ast->root. + // - Stack item i points to the previous stack items’ last child. + // + // When parser expects “value†node that is something like identifier or "[" + // (list start) last stack item contains NULL. Otherwise last stack item is + // supposed to contain last “finished†value: e.g. "1" or "+(1, 1)" (node + // representing "1+1"). + ExprASTStack ast_stack; + kvi_init(ast_stack); + kvi_push(ast_stack, &ast.root); + ExprASTWantedNode want_node = kENodeValue; + ExprASTParseTypeStack pt_stack; + kvi_init(pt_stack); + kvi_push(pt_stack, kEPTExpr); + if (flags & kExprFlagsParseLet) { + kvi_push(pt_stack, kEPTAssignment); + } + LexExprToken prev_token = { .type = kExprLexMissing }; + bool highlighted_prev_spacing = false; + // Lambda node, valid when parsing lambda arguments only. + ExprASTNode *lambda_node = NULL; + size_t asgn_level = 0; + do { + const bool is_concat_or_subscript = ( + want_node == kENodeValue + && kv_size(ast_stack) > 1 + && (*kv_Z(ast_stack, 1))->type == kExprNodeConcatOrSubscript); + const int lexer_additional_flags = ( + kELFlagPeek + | ((flags & kExprFlagsDisallowEOC) ? kELFlagForbidEOC : 0) + | ((want_node == kENodeValue + && (kv_size(ast_stack) == 1 + || ((*kv_Z(ast_stack, 1))->type != kExprNodeConcat + && ((*kv_Z(ast_stack, 1))->type + != kExprNodeConcatOrSubscript)))) + ? kELFlagAllowFloat + : 0)); + LexExprToken cur_token = viml_pexpr_next_token( + pstate, want_node_to_lexer_flags[want_node] | lexer_additional_flags); + if (cur_token.type == kExprLexEOC) { + break; + } + LexExprTokenType tok_type = cur_token.type; + const bool token_invalid = (tok_type == kExprLexInvalid); + bool is_invalid = token_invalid; +viml_pexpr_parse_process_token: + // May use different flags this time. + cur_token = viml_pexpr_next_token( + pstate, want_node_to_lexer_flags[want_node] | lexer_additional_flags); + if (tok_type == kExprLexSpacing) { + if (is_invalid) { + HL_CUR_TOKEN(Spacing); + } else { + // Do not do anything: let regular spacing be highlighted as normal. + // This also allows later to highlight spacing as invalid. + } + goto viml_pexpr_parse_cycle_end; + } else if (is_invalid && prev_token.type == kExprLexSpacing + && !highlighted_prev_spacing) { + viml_parser_highlight(pstate, prev_token.start, prev_token.len, + HL(Spacing)); + is_invalid = false; + highlighted_prev_spacing = true; + } + const ParserLine pline = pstate->reader.lines.items[cur_token.start.line]; + ExprASTNode **const top_node_p = kv_last(ast_stack); + assert(kv_size(ast_stack) >= 1); + ExprASTNode *cur_node = NULL; +#ifndef NDEBUG + const bool want_value = (want_node == kENodeValue); + assert(want_value == (*top_node_p == NULL)); + assert(kv_A(ast_stack, 0) == &ast.root); + // Check that stack item i + 1 points to stack items’ i *last* child. + for (size_t i = 0; i + 1 < kv_size(ast_stack); i++) { + const bool item_null = (want_value && i + 2 == kv_size(ast_stack)); + assert((&(*kv_A(ast_stack, i))->children == kv_A(ast_stack, i + 1) + && (item_null + ? (*kv_A(ast_stack, i))->children == NULL + : (*kv_A(ast_stack, i))->children->next == NULL)) + || ((&(*kv_A(ast_stack, i))->children->next + == kv_A(ast_stack, i + 1)) + && (item_null + ? (*kv_A(ast_stack, i))->children->next == NULL + : (*kv_A(ast_stack, i))->children->next->next == NULL))); + } +#endif + // Note: in Vim whether expression "cond?d.a:2" is valid depends both on + // "cond" and whether "d" is a dictionary: expression is valid if condition + // is true and "d" is a dictionary (with "a" key or it will complain about + // missing one, but this is not relevant); if any of the requirements is + // broken then this thing is parsed as "d . a:2" yielding missing colon + // error. This parser does not allow such ambiguity, especially because it + // simply can’t: whether "d" is a dictionary is not known at the parsing + // time. + // + // Here example will always contain a concat with "a:2" sucking colon, + // making expression invalid both because there is no longer a spare colon + // for ternary and because concatenating dictionary with anything is not + // valid. There are more cases when this will make a difference though. + const bool node_is_key = ( + is_concat_or_subscript + && (cur_token.type == kExprLexPlainIdentifier + ? (!cur_token.data.var.autoload + && cur_token.data.var.scope == kExprVarScopeMissing) + : (cur_token.type == kExprLexNumber)) + && prev_token.type != kExprLexSpacing); + if (is_concat_or_subscript && !node_is_key) { + // Note: in Vim "d. a" (this is the reason behind `prev_token.type != + // kExprLexSpacing` part of the condition) as well as any other "d.{expr}" + // where "{expr}" does not look like a key is invalid whenever "d" happens + // to be a dictionary. Since parser has no idea whether preceding + // expression is actually a dictionary it can’t outright reject anything, + // so it turns kExprNodeConcatOrSubscript into kExprNodeConcat instead, + // which will yield different errors then Vim does in a number of + // circumstances, and in any case runtime and not parse time errors. + (*kv_Z(ast_stack, 1))->type = kExprNodeConcat; + } + // Pop some stack pt_stack items in case of misplaced nodes. + const bool is_single_assignment = kv_last(pt_stack) == kEPTSingleAssignment; + switch (kv_last(pt_stack)) { + case kEPTExpr: { + break; + } + case kEPTLambdaArguments: { + if ((want_node == kENodeOperator + && tok_type != kExprLexComma + && tok_type != kExprLexArrow) + || (want_node == kENodeValue + && !(cur_token.type == kExprLexPlainIdentifier + && cur_token.data.var.scope == kExprVarScopeMissing + && !cur_token.data.var.autoload) + && tok_type != kExprLexArrow)) { + lambda_node->data.fig.type_guesses.allow_lambda = false; + if (lambda_node->children != NULL + && lambda_node->children->type == kExprNodeComma) { + // If lambda has comma child this means that parser has already seen + // at least "{arg1,", so node cannot possibly be anything, but + // lambda. + + // Vim may give E121 or E720 in this case, but it does not look + // right to have either because both are results of reevaluation + // possibly-lambda node as a dictionary and here this is not going + // to happen. + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Expected lambda arguments list or arrow: %.*s")); + } else { + // Else it may appear that possibly-lambda node is actually + // a dictionary or curly-braces-name identifier. + lambda_node = NULL; + kv_drop(pt_stack, 1); + } + } + break; + } + case kEPTSingleAssignment: + case kEPTAssignment: { + if (want_node == kENodeValue + && tok_type != kExprLexBracket + && tok_type != kExprLexPlainIdentifier + && (tok_type != kExprLexFigureBrace || cur_token.data.brc.closing) + && !(node_is_key && tok_type == kExprLexNumber) + && tok_type != kExprLexEnv + && tok_type != kExprLexOption + && tok_type != kExprLexRegister) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Expected value part of assignment lvalue: %.*s")); + kv_drop(pt_stack, 1); + } else if (want_node == kENodeOperator + && tok_type != kExprLexBracket + && (tok_type != kExprLexFigureBrace + || cur_token.data.brc.closing) + && tok_type != kExprLexDot + && (tok_type != kExprLexComma || !is_single_assignment) + && tok_type != kExprLexAssignment + // Curly brace identifiers: will contain plain identifier or + // another curly brace in position where operator is wanted. + && !((tok_type == kExprLexPlainIdentifier + || (tok_type == kExprLexFigureBrace + && !cur_token.data.brc.closing)) + && prev_token.type != kExprLexSpacing)) { + if (flags & kExprFlagsMulti && MAY_HAVE_NEXT_EXPR) { + goto viml_pexpr_parse_end; + } + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Expected assignment operator or subscript: %.*s")); + kv_drop(pt_stack, 1); + } + assert(kv_size(pt_stack)); + break; + } + } + assert(kv_size(pt_stack)); + const ExprASTParseType cur_pt = kv_last(pt_stack); + assert(lambda_node == NULL || cur_pt == kEPTLambdaArguments); + switch (tok_type) { + case kExprLexMissing: + case kExprLexSpacing: + case kExprLexEOC: { + assert(false); + } + case kExprLexInvalid: { + ERROR_FROM_TOKEN(cur_token); + tok_type = cur_token.data.err.type; + goto viml_pexpr_parse_process_token; + } + case kExprLexRegister: { + if (want_node == kENodeOperator) { + // Register in operator position: e.g. @a @a + OP_MISSING; + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeRegister); + cur_node->data.reg.name = cur_token.data.reg.name; + *top_node_p = cur_node; + want_node = kENodeOperator; + HL_CUR_TOKEN(Register); + break; + } +#define SIMPLE_UB_OP(op) \ + case kExprLex##op: { \ + if (want_node == kENodeValue) { \ + /* Value level: assume unary operator. */ \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnary##op); \ + *top_node_p = cur_node; \ + kvi_push(ast_stack, &cur_node->children); \ + HL_CUR_TOKEN(Unary##op); \ + } else { \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeBinary##op); \ + ADD_OP_NODE(cur_node); \ + HL_CUR_TOKEN(Binary##op); \ + } \ + want_node = kENodeValue; \ + break; \ + } + SIMPLE_UB_OP(Plus) + SIMPLE_UB_OP(Minus) +#undef SIMPLE_UB_OP +#define SIMPLE_B_OP(op, msg) \ + case kExprLex##op: { \ + ADD_VALUE_IF_MISSING(_("E15: Unexpected " msg ": %.*s")); \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNode##op); \ + HL_CUR_TOKEN(op); \ + ADD_OP_NODE(cur_node); \ + break; \ + } + SIMPLE_B_OP(Or, "or operator") + SIMPLE_B_OP(And, "and operator") +#undef SIMPLE_B_OP + case kExprLexMultiplication: { + ADD_VALUE_IF_MISSING( + _("E15: Unexpected multiplication-like operator: %.*s")); + switch (cur_token.data.mul.type) { +#define MUL_OP(lex_op_tail, node_op_tail) \ + case kExprLexMul##lex_op_tail: { \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNode##node_op_tail); \ + HL_CUR_TOKEN(node_op_tail); \ + break; \ + } + MUL_OP(Mul, Multiplication) + MUL_OP(Div, Division) + MUL_OP(Mod, Mod) +#undef MUL_OP + } + ADD_OP_NODE(cur_node); + break; + } + case kExprLexOption: { + if (want_node == kENodeOperator) { + OP_MISSING; + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeOption); + if (cur_token.type == kExprLexInvalid) { + assert(cur_token.len == 1 + || (cur_token.len == 3 + && pline.data[cur_token.start.col + 2] == ':')); + cur_node->data.opt.ident = ( + pline.data + cur_token.start.col + cur_token.len); + cur_node->data.opt.ident_len = 0; + cur_node->data.opt.scope = ( + cur_token.len == 3 + ? (ExprOptScope)pline.data[cur_token.start.col + 1] + : kExprOptScopeUnspecified); + } else { + cur_node->data.opt.ident = cur_token.data.opt.name; + cur_node->data.opt.ident_len = cur_token.data.opt.len; + cur_node->data.opt.scope = cur_token.data.opt.scope; + } + *top_node_p = cur_node; + want_node = kENodeOperator; + viml_parser_highlight(pstate, cur_token.start, 1, HL(OptionSigil)); + const size_t scope_shift = ( + cur_token.data.opt.scope == kExprOptScopeUnspecified ? 0 : 2); + if (scope_shift) { + viml_parser_highlight(pstate, shifted_pos(cur_token.start, 1), 1, + HL(OptionScope)); + viml_parser_highlight(pstate, shifted_pos(cur_token.start, 2), 1, + HL(OptionScopeDelimiter)); + } + viml_parser_highlight( + pstate, shifted_pos(cur_token.start, scope_shift + 1), + cur_token.len - (scope_shift + 1), HL(OptionName)); + break; + } + case kExprLexEnv: { + if (want_node == kENodeOperator) { + OP_MISSING; + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeEnvironment); + cur_node->data.env.ident = pline.data + cur_token.start.col + 1; + cur_node->data.env.ident_len = cur_token.len - 1; + if (cur_node->data.env.ident_len == 0) { + ERROR_FROM_TOKEN_AND_MSG(cur_token, + _("E15: Environment variable name missing")); + } + *top_node_p = cur_node; + want_node = kENodeOperator; + viml_parser_highlight(pstate, cur_token.start, 1, HL(EnvironmentSigil)); + viml_parser_highlight(pstate, shifted_pos(cur_token.start, 1), + cur_token.len - 1, HL(EnvironmentName)); + break; + } + case kExprLexNot: { + if (want_node == kENodeOperator) { + OP_MISSING; + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeNot); + *top_node_p = cur_node; + kvi_push(ast_stack, &cur_node->children); + HL_CUR_TOKEN(Not); + break; + } + case kExprLexComparison: { + ADD_VALUE_IF_MISSING( + _("E15: Expected value, got comparison operator: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComparison); + if (cur_token.type == kExprLexInvalid) { + cur_node->data.cmp.ccs = kCCStrategyUseOption; + cur_node->data.cmp.type = kExprCmpEqual; + cur_node->data.cmp.inv = false; + } else { + cur_node->data.cmp.ccs = cur_token.data.cmp.ccs; + cur_node->data.cmp.type = cur_token.data.cmp.type; + cur_node->data.cmp.inv = cur_token.data.cmp.inv; + } + ADD_OP_NODE(cur_node); + if (cur_token.data.cmp.ccs != kCCStrategyUseOption) { + viml_parser_highlight(pstate, cur_token.start, cur_token.len - 1, + HL(Comparison)); + viml_parser_highlight( + pstate, shifted_pos(cur_token.start, cur_token.len - 1), 1, + HL(ComparisonModifier)); + } else { + HL_CUR_TOKEN(Comparison); + } + want_node = kENodeValue; + break; + } + case kExprLexComma: { + assert(!(want_node == kENodeValue && cur_pt == kEPTLambdaArguments)); + if (want_node == kENodeValue) { + // Value level: comma appearing here is not valid. + // Note: in Vim string(,x) will give E116, this is not the case here. + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Expected value, got comma: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeMissing); + cur_node->len = 0; + *top_node_p = cur_node; + want_node = kENodeOperator; + } + if (cur_pt == kEPTLambdaArguments) { + assert(lambda_node != NULL); + assert(lambda_node->data.fig.type_guesses.allow_lambda); + SELECT_FIGURE_BRACE_TYPE(lambda_node, Lambda, Lambda); + } + if (kv_size(ast_stack) < 2) { + goto viml_pexpr_parse_invalid_comma; + } + for (size_t i = 1; i < kv_size(ast_stack); i++) { + ExprASTNode *const *const eastnode_p = + (ExprASTNode *const *)kv_Z(ast_stack, i); + const ExprASTNodeType eastnode_type = (*eastnode_p)->type; + const ExprOpLvl eastnode_lvl = node_lvl(**eastnode_p); + if (eastnode_type == kExprNodeLambda) { + assert(cur_pt == kEPTLambdaArguments + && want_node == kENodeOperator); + break; + } else if (eastnode_type == kExprNodeDictLiteral + || eastnode_type == kExprNodeListLiteral + || eastnode_type == kExprNodeCall) { + break; + } else if (eastnode_type == kExprNodeComma + || eastnode_type == kExprNodeColon + || eastnode_lvl > kEOpLvlComma) { + // Do nothing + } else { +viml_pexpr_parse_invalid_comma: + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Comma outside of call, lambda or literal: %.*s")); + break; + } + if (i == kv_size(ast_stack) - 1) { + goto viml_pexpr_parse_invalid_comma; + } + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComma); + ADD_OP_NODE(cur_node); + HL_CUR_TOKEN(Comma); + break; + } +#define EXP_VAL_COLON "E15: Expected value, got colon: %.*s" + case kExprLexColon: { + bool is_ternary = false; + if (kv_size(ast_stack) < 2) { + goto viml_pexpr_parse_invalid_colon; + } + bool can_be_ternary = true; + bool is_subscript = false; + for (size_t i = 1; i < kv_size(ast_stack); i++) { + ExprASTNode *const *const eastnode_p = + (ExprASTNode *const *)kv_Z(ast_stack, i); + const ExprASTNodeType eastnode_type = (*eastnode_p)->type; + const ExprOpLvl eastnode_lvl = node_lvl(**eastnode_p); + STATIC_ASSERT(kEOpLvlTernary > kEOpLvlComma, + "Unexpected operator priorities"); + if (can_be_ternary && eastnode_type == kExprNodeTernaryValue + && !(*eastnode_p)->data.ter.got_colon) { + kv_drop(ast_stack, i); + (*eastnode_p)->start = cur_token.start; + (*eastnode_p)->len = cur_token.len; + if (prev_token.type == kExprLexSpacing) { + (*eastnode_p)->start = prev_token.start; + (*eastnode_p)->len += prev_token.len; + } + is_ternary = true; + (*eastnode_p)->data.ter.got_colon = true; + ADD_VALUE_IF_MISSING(_(EXP_VAL_COLON)); + assert((*eastnode_p)->children != NULL); + assert((*eastnode_p)->children->next == NULL); + kvi_push(ast_stack, &(*eastnode_p)->children->next); + break; + } else if (eastnode_type == kExprNodeUnknownFigure) { + SELECT_FIGURE_BRACE_TYPE(*eastnode_p, DictLiteral, Dict); + break; + } else if (eastnode_type == kExprNodeDictLiteral) { + break; + } else if (eastnode_type == kExprNodeSubscript) { + is_subscript = true; + can_be_ternary = false; + assert(!is_ternary); + break; + } else if (eastnode_type == kExprNodeColon) { + goto viml_pexpr_parse_invalid_colon; + } else if (eastnode_lvl >= kEOpLvlTernaryValue) { + // Do nothing + } else if (eastnode_lvl >= kEOpLvlComma) { + can_be_ternary = false; + } else { + goto viml_pexpr_parse_invalid_colon; + } + if (i == kv_size(ast_stack) - 1) { + goto viml_pexpr_parse_invalid_colon; + } + } + if (is_subscript) { + assert(kv_size(ast_stack) > 1); + // Colon immediately following subscript start: it is empty subscript + // part like a[:2]. + if (want_node == kENodeValue + && (*kv_Z(ast_stack, 1))->type == kExprNodeSubscript) { + NEW_NODE_WITH_CUR_POS(*top_node_p, kExprNodeMissing); + (*top_node_p)->len = 0; + want_node = kENodeOperator; + } else { + ADD_VALUE_IF_MISSING(_(EXP_VAL_COLON)); + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); + ADD_OP_NODE(cur_node); + HL_CUR_TOKEN(SubscriptColon); + } else { + goto viml_pexpr_parse_valid_colon; +viml_pexpr_parse_invalid_colon: + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Colon outside of dictionary or ternary operator: %.*s")); +viml_pexpr_parse_valid_colon: + ADD_VALUE_IF_MISSING(_(EXP_VAL_COLON)); + if (is_ternary) { + HL_CUR_TOKEN(TernaryColon); + } else { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); + ADD_OP_NODE(cur_node); + HL_CUR_TOKEN(Colon); + } + } + want_node = kENodeValue; + break; + } +#undef EXP_VAL_COLON + case kExprLexBracket: { + if (cur_token.data.brc.closing) { + ExprASTNode **new_top_node_p = NULL; + // Always drop the topmost value: + // + // 1. When want_node != kENodeValue topmost item on stack is + // a *finished* left operand, which may as well be "{@a}" which + // needs not be finished again. + // 2. Otherwise it is pointing to NULL what nobody wants. + kv_drop(ast_stack, 1); + if (!kv_size(ast_stack)) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeListLiteral); + cur_node->len = 0; + if (want_node != kENodeValue) { + cur_node->children = *top_node_p; + } + *top_node_p = cur_node; + goto viml_pexpr_parse_bracket_closing_error; + } + if (want_node == kENodeValue) { + // It is OK to want value if + // + // 1. It is empty list literal, in which case top node will be + // ListLiteral. + // 2. It is list literal with trailing comma, in which case top node + // will be that comma. + // 3. It is subscript with colon, but without one of the values: + // e.g. "a[:]", "a[1:]", top node will be colon in this case. + if ((*kv_last(ast_stack))->type != kExprNodeListLiteral + && (*kv_last(ast_stack))->type != kExprNodeComma + && (*kv_last(ast_stack))->type != kExprNodeColon) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Expected value, got closing bracket: %.*s")); + } + } else { + if (!kv_size(ast_stack)) { + new_top_node_p = top_node_p; + goto viml_pexpr_parse_bracket_closing_error; + } + } + do { + new_top_node_p = kv_pop(ast_stack); + } while (kv_size(ast_stack) + && (new_top_node_p == NULL + || ((*new_top_node_p)->type != kExprNodeListLiteral + && (*new_top_node_p)->type != kExprNodeSubscript))); + ExprASTNode *new_top_node = *new_top_node_p; + switch (new_top_node->type) { + case kExprNodeListLiteral: { + if (pt_is_assignment(cur_pt) && new_top_node->children == NULL) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E475: Unable to assign to empty list: %.*s")); + } + HL_CUR_TOKEN(List); + break; + } + case kExprNodeSubscript: { + HL_CUR_TOKEN(SubscriptBracket); + break; + } + default: { +viml_pexpr_parse_bracket_closing_error: + assert(!kv_size(ast_stack)); + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Unexpected closing figure brace: %.*s")); + HL_CUR_TOKEN(List); + break; + } + } + kvi_push(ast_stack, new_top_node_p); + want_node = kENodeOperator; + if (kv_size(ast_stack) <= asgn_level) { + assert(kv_size(ast_stack) == asgn_level); + asgn_level = 0; + if (cur_pt == kEPTAssignment) { + assert(ast.err.msg); + } else if (cur_pt == kEPTExpr + && kv_size(pt_stack) > 1 + && pt_is_assignment(kv_Z(pt_stack, 1))) { + kv_drop(pt_stack, 1); + } + } + if (cur_pt == kEPTSingleAssignment && kv_size(ast_stack) == 1) { + kv_drop(pt_stack, 1); + } + } else { + if (want_node == kENodeValue) { + // Value means list literal or list assignment. + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeListLiteral); + *top_node_p = cur_node; + kvi_push(ast_stack, &cur_node->children); + want_node = kENodeValue; + if (cur_pt == kEPTAssignment) { + // Additional assignment parse type allows to easily forbid nested + // lists. + kvi_push(pt_stack, kEPTSingleAssignment); + } else if (cur_pt == kEPTSingleAssignment) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E475: Nested lists not allowed when assigning: %.*s")); + } + HL_CUR_TOKEN(List); + } else { + // Operator means subscript, also in assignment. But in assignment + // subscript may be pretty much any expression, so need to push + // kEPTExpr. + if (prev_token.type == kExprLexSpacing) { + OP_MISSING; + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeSubscript); + ADD_OP_NODE(cur_node); + HL_CUR_TOKEN(SubscriptBracket); + if (pt_is_assignment(cur_pt)) { + assert(want_node == kENodeValue); // Subtract 1 for NULL at top. + asgn_level = kv_size(ast_stack) - 1; + kvi_push(pt_stack, kEPTExpr); + } + } + } + break; + } + case kExprLexFigureBrace: { + if (cur_token.data.brc.closing) { + ExprASTNode **new_top_node_p = NULL; + // Always drop the topmost value: + // + // 1. When want_node != kENodeValue topmost item on stack is + // a *finished* left operand, which may as well be "{@a}" which + // needs not be finished again. + // 2. Otherwise it is pointing to NULL what nobody wants. + kv_drop(ast_stack, 1); + if (!kv_size(ast_stack)) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnknownFigure); + cur_node->data.fig.type_guesses.allow_lambda = false; + cur_node->data.fig.type_guesses.allow_dict = false; + cur_node->data.fig.type_guesses.allow_ident = false; + cur_node->len = 0; + if (want_node != kENodeValue) { + cur_node->children = *top_node_p; + } + *top_node_p = cur_node; + new_top_node_p = top_node_p; + goto viml_pexpr_parse_figure_brace_closing_error; + } + if (want_node == kENodeValue) { + if ((*kv_last(ast_stack))->type != kExprNodeUnknownFigure + && (*kv_last(ast_stack))->type != kExprNodeComma) { + // kv_last being UnknownFigure may occur for empty dictionary + // literal, while Comma is expected in case of non-empty one. + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Expected value, got closing figure brace: %.*s")); + } + } else { + if (!kv_size(ast_stack)) { + new_top_node_p = top_node_p; + goto viml_pexpr_parse_figure_brace_closing_error; + } + } + do { + new_top_node_p = kv_pop(ast_stack); + } while (kv_size(ast_stack) + && (new_top_node_p == NULL + || ((*new_top_node_p)->type != kExprNodeUnknownFigure + && (*new_top_node_p)->type != kExprNodeDictLiteral + && ((*new_top_node_p)->type + != kExprNodeCurlyBracesIdentifier) + && (*new_top_node_p)->type != kExprNodeLambda))); + ExprASTNode *new_top_node = *new_top_node_p; + switch (new_top_node->type) { + case kExprNodeUnknownFigure: { + if (new_top_node->children == NULL) { + // No children of curly braces node indicates empty dictionary. + assert(want_node == kENodeValue); + assert(new_top_node->data.fig.type_guesses.allow_dict); + SELECT_FIGURE_BRACE_TYPE(new_top_node, DictLiteral, Dict); + HL_CUR_TOKEN(Dict); + } else if (new_top_node->data.fig.type_guesses.allow_ident) { + SELECT_FIGURE_BRACE_TYPE(new_top_node, CurlyBracesIdentifier, + Curly); + HL_CUR_TOKEN(Curly); + } else { + // If by this time type of the node has not already been + // guessed, but it definitely is not a curly braces name then + // it is invalid for sure. + ERROR_FROM_NODE_AND_MSG( + new_top_node, + _("E15: Don't know what figure brace means: %.*s")); + if (pstate->colors) { + // Will reset to NvimInvalidFigureBrace. + kv_A(*pstate->colors, + new_top_node->data.fig.opening_hl_idx).group = ( + HL(FigureBrace)); + } + HL_CUR_TOKEN(FigureBrace); + } + break; + } + case kExprNodeDictLiteral: { + HL_CUR_TOKEN(Dict); + break; + } + case kExprNodeCurlyBracesIdentifier: { + HL_CUR_TOKEN(Curly); + break; + } + case kExprNodeLambda: { + HL_CUR_TOKEN(Lambda); + break; + } + default: { +viml_pexpr_parse_figure_brace_closing_error: + assert(!kv_size(ast_stack)); + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Unexpected closing figure brace: %.*s")); + HL_CUR_TOKEN(FigureBrace); + break; + } + } + kvi_push(ast_stack, new_top_node_p); + want_node = kENodeOperator; + if (kv_size(ast_stack) <= asgn_level) { + assert(kv_size(ast_stack) == asgn_level); + if (cur_pt == kEPTExpr + && kv_size(pt_stack) > 1 + && pt_is_assignment(kv_Z(pt_stack, 1))) { + kv_drop(pt_stack, 1); + asgn_level = 0; + } + } + } else { + if (want_node == kENodeValue) { + HL_CUR_TOKEN(FigureBrace); + // Value: may be any of lambda, dictionary literal and curly braces + // name. + + // Though if we are in an assignment this may only be a curly braces + // name. + if (pt_is_assignment(cur_pt)) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeCurlyBracesIdentifier); + cur_node->data.fig.type_guesses.allow_lambda = false; + cur_node->data.fig.type_guesses.allow_dict = false; + cur_node->data.fig.type_guesses.allow_ident = true; + kvi_push(pt_stack, kEPTExpr); + } else { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnknownFigure); + cur_node->data.fig.type_guesses.allow_lambda = true; + cur_node->data.fig.type_guesses.allow_dict = true; + cur_node->data.fig.type_guesses.allow_ident = true; + } + if (pstate->colors) { + cur_node->data.fig.opening_hl_idx = kv_size(*pstate->colors) - 1; + } + *top_node_p = cur_node; + kvi_push(ast_stack, &cur_node->children); + kvi_push(pt_stack, kEPTLambdaArguments); + lambda_node = cur_node; + } else { + ADD_IDENT( + do { + NEW_NODE_WITH_CUR_POS(cur_node, + kExprNodeCurlyBracesIdentifier); + cur_node->data.fig.opening_hl_idx = kv_size(*pstate->colors); + cur_node->data.fig.type_guesses.allow_lambda = false; + cur_node->data.fig.type_guesses.allow_dict = false; + cur_node->data.fig.type_guesses.allow_ident = true; + kvi_push(ast_stack, &cur_node->children); + if (pt_is_assignment(cur_pt)) { + kvi_push(pt_stack, kEPTExpr); + } + want_node = kENodeValue; + } while (0), + Curly); + } + if (pt_is_assignment(cur_pt) + && !pt_is_assignment(kv_last(pt_stack))) { + assert(want_node == kENodeValue); // Subtract 1 for NULL at top. + asgn_level = kv_size(ast_stack) - 1; + } + } + break; + } + case kExprLexArrow: { + if (cur_pt == kEPTLambdaArguments) { + kv_drop(pt_stack, 1); + assert(kv_size(pt_stack)); + if (want_node == kENodeValue) { + // Wanting value means trailing comma and NULL at the top of the + // stack. + kv_drop(ast_stack, 1); + } + assert(kv_size(ast_stack) >= 1); + while ((*kv_last(ast_stack))->type != kExprNodeLambda + && (*kv_last(ast_stack))->type != kExprNodeUnknownFigure) { + kv_drop(ast_stack, 1); + } + assert((*kv_last(ast_stack)) == lambda_node); + SELECT_FIGURE_BRACE_TYPE(lambda_node, Lambda, Lambda); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeArrow); + if (lambda_node->children == NULL) { + assert(want_node == kENodeValue); + lambda_node->children = cur_node; + kvi_push(ast_stack, &lambda_node->children); + } else { + assert(lambda_node->children->next == NULL); + lambda_node->children->next = cur_node; + kvi_push(ast_stack, &lambda_node->children->next); + } + kvi_push(ast_stack, &cur_node->children); + lambda_node = NULL; + } else { + // Only first branch is valid. + ADD_VALUE_IF_MISSING(_("E15: Unexpected arrow: %.*s")); + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Arrow outside of lambda: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeArrow); + ADD_OP_NODE(cur_node); + } + want_node = kENodeValue; + HL_CUR_TOKEN(Arrow); + break; + } + case kExprLexPlainIdentifier: { + const ExprVarScope scope = (cur_token.type == kExprLexInvalid + ? kExprVarScopeMissing + : cur_token.data.var.scope); + if (want_node == kENodeValue) { + want_node = kENodeOperator; + NEW_NODE_WITH_CUR_POS(cur_node, + (node_is_key + ? kExprNodePlainKey + : kExprNodePlainIdentifier)); + cur_node->data.var.scope = scope; + const size_t scope_shift = (scope == kExprVarScopeMissing ? 0 : 2); + cur_node->data.var.ident = (pline.data + cur_token.start.col + + scope_shift); + cur_node->data.var.ident_len = cur_token.len - scope_shift; + *top_node_p = cur_node; + if (scope_shift) { + assert(!node_is_key); + viml_parser_highlight(pstate, cur_token.start, 1, + HL(IdentifierScope)); + viml_parser_highlight(pstate, shifted_pos(cur_token.start, 1), 1, + HL(IdentifierScopeDelimiter)); + } + viml_parser_highlight(pstate, shifted_pos(cur_token.start, + scope_shift), + cur_token.len - scope_shift, + (node_is_key + ? HL(IdentifierKey) + : HL(IdentifierName))); + } else { + if (scope == kExprVarScopeMissing) { + ADD_IDENT( + do { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodePlainIdentifier); + cur_node->data.var.scope = scope; + cur_node->data.var.ident = pline.data + cur_token.start.col; + cur_node->data.var.ident_len = cur_token.len; + want_node = kENodeOperator; + } while (0), + IdentifierName); + } else { + OP_MISSING; + } + } + break; + } + case kExprLexNumber: { + if (want_node != kENodeValue) { + OP_MISSING; + } + if (node_is_key) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodePlainKey); + cur_node->data.var.ident = pline.data + cur_token.start.col; + cur_node->data.var.ident_len = cur_token.len; + HL_CUR_TOKEN(IdentifierKey); + } else if (cur_token.data.num.is_float) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeFloat); + cur_node->data.flt.value = cur_token.data.num.val.floating; + HL_CUR_TOKEN(Float); + } else { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeInteger); + cur_node->data.num.value = cur_token.data.num.val.integer; + const uint8_t prefix_length = base_to_prefix_length[ + cur_token.data.num.base]; + viml_parser_highlight(pstate, cur_token.start, prefix_length, + HL(NumberPrefix)); + viml_parser_highlight( + pstate, shifted_pos(cur_token.start, prefix_length), + cur_token.len - prefix_length, HL(Number)); + } + want_node = kENodeOperator; + *top_node_p = cur_node; + break; + } + case kExprLexDot: { + ADD_VALUE_IF_MISSING(_("E15: Unexpected dot: %.*s")); + if (prev_token.type == kExprLexSpacing) { + if (cur_pt == kEPTAssignment) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Cannot concatenate in assignments: %.*s")); + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeConcat); + HL_CUR_TOKEN(Concat); + } else { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeConcatOrSubscript); + HL_CUR_TOKEN(ConcatOrSubscript); + } + ADD_OP_NODE(cur_node); + break; + } + case kExprLexParenthesis: { + if (cur_token.data.brc.closing) { + if (want_node == kENodeValue) { + if (kv_size(ast_stack) > 1) { + const ExprASTNode *const prev_top_node = *kv_Z(ast_stack, 1); + if (prev_top_node->type == kExprNodeCall) { + // Function call without arguments, this is not an error. + // But further code does not expect NULL nodes. + kv_drop(ast_stack, 1); + goto viml_pexpr_parse_no_paren_closing_error; + } + } + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Expected value, got parenthesis: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeMissing); + cur_node->len = 0; + *top_node_p = cur_node; + } else { + // Always drop the topmost value: when want_node != kENodeValue + // topmost item on stack is a *finished* left operand, which may as + // well be "(@a)" which needs not be finished again. + kv_drop(ast_stack, 1); + } +viml_pexpr_parse_no_paren_closing_error: {} + ExprASTNode **new_top_node_p = NULL; + while (kv_size(ast_stack) + && (new_top_node_p == NULL + || ((*new_top_node_p)->type != kExprNodeNested + && (*new_top_node_p)->type != kExprNodeCall))) { + new_top_node_p = kv_pop(ast_stack); + } + if (new_top_node_p != NULL + && ((*new_top_node_p)->type == kExprNodeNested + || (*new_top_node_p)->type == kExprNodeCall)) { + if ((*new_top_node_p)->type == kExprNodeNested) { + HL_CUR_TOKEN(NestingParenthesis); + } else { + HL_CUR_TOKEN(CallingParenthesis); + } + } else { + // “Always drop the topmost value†branch has got rid of the single + // value stack had, so there is nothing known to enclose. Correct + // this. + if (new_top_node_p == NULL) { + new_top_node_p = top_node_p; + } + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Unexpected closing parenthesis: %.*s")); + HL_CUR_TOKEN(NestingParenthesis); + cur_node = NEW_NODE(kExprNodeNested); + cur_node->start = cur_token.start; + cur_node->len = 0; + // Unexpected closing parenthesis, assume that it was wanted to + // enclose everything in (). + cur_node->children = *new_top_node_p; + *new_top_node_p = cur_node; + assert(cur_node->next == NULL); + } + kvi_push(ast_stack, new_top_node_p); + want_node = kENodeOperator; + } else { + if (want_node == kENodeValue) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeNested); + *top_node_p = cur_node; + kvi_push(ast_stack, &cur_node->children); + HL_CUR_TOKEN(NestingParenthesis); + } else if (want_node == kENodeOperator) { + if (prev_token.type == kExprLexSpacing) { + // For some reason "function (args)" is a function call, but + // "(funcref) (args)" is not. AFAIR this somehow involves + // compatibility and Bram was commenting that this is + // intentionally inconsistent and he is not very happy with the + // situation himself. + if ((*top_node_p)->type != kExprNodePlainIdentifier + && (*top_node_p)->type != kExprNodeComplexIdentifier + && (*top_node_p)->type != kExprNodeCurlyBracesIdentifier) { + OP_MISSING; + } + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeCall); + ADD_OP_NODE(cur_node); + HL_CUR_TOKEN(CallingParenthesis); + } else { + // Currently it is impossible to reach this. + assert(false); + } + want_node = kENodeValue; + } + break; + } + case kExprLexQuestion: { + ADD_VALUE_IF_MISSING(_("E15: Expected value, got question mark: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeTernary); + ADD_OP_NODE(cur_node); + HL_CUR_TOKEN(Ternary); + ExprASTNode *ter_val_node; + NEW_NODE_WITH_CUR_POS(ter_val_node, kExprNodeTernaryValue); + ter_val_node->data.ter.got_colon = false; + assert(cur_node->children != NULL); + assert(cur_node->children->next == NULL); + assert(kv_last(ast_stack) == &cur_node->children->next); + *kv_last(ast_stack) = ter_val_node; + kvi_push(ast_stack, &ter_val_node->children); + break; + } + case kExprLexDoubleQuotedString: + case kExprLexSingleQuotedString: { + const bool is_double = (tok_type == kExprLexDoubleQuotedString); + if (!cur_token.data.str.closed) { + // It is weird, but Vim has two identical errors messages with + // different error numbers: "E114: Missing quote" and + // "E115: Missing quote". + ERROR_FROM_TOKEN_AND_MSG( + cur_token, (is_double + ? _("E114: Missing double quote: %.*s") + : _("E115: Missing single quote: %.*s"))); + } + if (want_node == kENodeOperator) { + OP_MISSING; + } + NEW_NODE_WITH_CUR_POS( + cur_node, (is_double + ? kExprNodeDoubleQuotedString + : kExprNodeSingleQuotedString)); + *top_node_p = cur_node; + parse_quoted_string(pstate, cur_node, cur_token, ast_stack, is_invalid); + want_node = kENodeOperator; + break; + } + case kExprLexAssignment: { + if (cur_pt == kEPTAssignment) { + kv_drop(pt_stack, 1); + } else if (cur_pt == kEPTSingleAssignment) { + kv_drop(pt_stack, 2); + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E475: Expected closing bracket to end list assignment " + "lvalue: %.*s")); + } else { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Misplaced assignment: %.*s")); + } + assert(kv_size(pt_stack)); + assert(kv_last(pt_stack) == kEPTExpr); + ADD_VALUE_IF_MISSING(_("E15: Unexpected assignment: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeAssignment); + cur_node->data.ass.type = cur_token.data.ass.type; + switch (cur_token.data.ass.type) { +#define HL_ASGN(asgn, hl) \ + case kExprAsgn##asgn: { HL_CUR_TOKEN(hl); break; } + HL_ASGN(Plain, PlainAssignment) + HL_ASGN(Add, AssignmentWithAddition) + HL_ASGN(Subtract, AssignmentWithSubtraction) + HL_ASGN(Concat, AssignmentWithConcatenation) +#undef HL_ASGN + } + ADD_OP_NODE(cur_node); + break; + } + } +viml_pexpr_parse_cycle_end: + prev_token = cur_token; + highlighted_prev_spacing = false; + viml_parser_advance(pstate, cur_token.len); + } while (true); +viml_pexpr_parse_end: + assert(kv_size(pt_stack)); + assert(kv_size(ast_stack)); + if (want_node == kENodeValue + // Blacklist some parse type entries as their presence means better error + // message in the other branch. + && kv_last(pt_stack) != kEPTLambdaArguments) { + east_set_error(pstate, &ast.err, _("E15: Expected value, got EOC: %.*s"), + pstate->pos); + } else if (kv_size(ast_stack) != 1) { + // Something may be wrong, check whether it really is. + + // Pointer to ast.root must never be dropped, so “!= 1†is expected to be + // the same as “> 1â€. + assert(kv_size(ast_stack)); + // Topmost stack item must be a *finished* value, so it must not be + // analyzed. E.g. it may contain an already finished nested expression. + kv_drop(ast_stack, 1); + while (ast.err.msg == NULL && kv_size(ast_stack)) { + const ExprASTNode *const cur_node = (*kv_pop(ast_stack)); + // This should only happen when want_node == kENodeValue. + assert(cur_node != NULL); + // TODO(ZyX-I): Rehighlight as invalid? + switch (cur_node->type) { + case kExprNodeOpMissing: + case kExprNodeMissing: { + // Error should’ve been already reported. + break; + } + case kExprNodeCall: { + east_set_error( + pstate, &ast.err, + _("E116: Missing closing parenthesis for function call: %.*s"), + cur_node->start); + break; + } + case kExprNodeNested: { + east_set_error( + pstate, &ast.err, + _("E110: Missing closing parenthesis for nested expression" + ": %.*s"), + cur_node->start); + break; + } + case kExprNodeListLiteral: { + // For whatever reason "[1" yields "E696: Missing comma in list" error + // in Vim while "[1," yields E697. + east_set_error( + pstate, &ast.err, + _("E697: Missing end of List ']': %.*s"), + cur_node->start); + break; + } + case kExprNodeDictLiteral: { + // Same problem like with list literal with E722 (missing comma) vs + // E723, but additionally just "{" yields only E15. + east_set_error( + pstate, &ast.err, + _("E723: Missing end of Dictionary '}': %.*s"), + cur_node->start); + break; + } + case kExprNodeUnknownFigure: { + east_set_error( + pstate, &ast.err, + _("E15: Missing closing figure brace: %.*s"), + cur_node->start); + break; + } + case kExprNodeLambda: { + east_set_error( + pstate, &ast.err, + _("E15: Missing closing figure brace for lambda: %.*s"), + cur_node->start); + break; + } + case kExprNodeCurlyBracesIdentifier: { + // Until trailing "}" it is impossible to distinguish curly braces + // identifier and dictionary, so it must not appear in the stack like + // this. + assert(false); + } + case kExprNodeInteger: + case kExprNodeFloat: + case kExprNodeSingleQuotedString: + case kExprNodeDoubleQuotedString: + case kExprNodeOption: + case kExprNodeEnvironment: + case kExprNodeRegister: + case kExprNodePlainIdentifier: + case kExprNodePlainKey: { + // These are plain values and not containers, for them it should only + // be possible to show up in the topmost stack element, but it was + // unconditionally popped at the start. + assert(false); + } + case kExprNodeComma: + case kExprNodeColon: + case kExprNodeArrow: { + // It is actually only valid inside something else, but everything + // where one of the above is valid requires to be closed and thus is + // to be caught later. + break; + } + case kExprNodeSubscript: + case kExprNodeConcatOrSubscript: + case kExprNodeComplexIdentifier: + case kExprNodeAssignment: + case kExprNodeMod: + case kExprNodeDivision: + case kExprNodeMultiplication: + case kExprNodeNot: + case kExprNodeAnd: + case kExprNodeOr: + case kExprNodeConcat: + case kExprNodeComparison: + case kExprNodeUnaryMinus: + case kExprNodeUnaryPlus: + case kExprNodeBinaryMinus: + case kExprNodeTernary: + case kExprNodeBinaryPlus: { + // It is OK to see these in the stack. + break; + } + case kExprNodeTernaryValue: { + if (!cur_node->data.ter.got_colon) { + // Actually Vim throws E109 in more cases. + east_set_error( + pstate, &ast.err, _("E109: Missing ':' after '?': %.*s"), + cur_node->start); + } + break; + } + } + } + } + kvi_destroy(ast_stack); + return ast; +} // NOLINT(readability/fn_size) + +#undef NEW_NODE +#undef HL diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h new file mode 100644 index 0000000000..23e172da75 --- /dev/null +++ b/src/nvim/viml/parser/expressions.h @@ -0,0 +1,389 @@ +#ifndef NVIM_VIML_PARSER_EXPRESSIONS_H +#define NVIM_VIML_PARSER_EXPRESSIONS_H + +#include <stddef.h> +#include <stdint.h> +#include <stdbool.h> + +#include "nvim/types.h" +#include "nvim/viml/parser/parser.h" +#include "nvim/eval/typval.h" + +// Defines whether to ignore case: +// == kCCStrategyUseOption +// ==# kCCStrategyMatchCase +// ==? kCCStrategyIgnoreCase +typedef enum { + kCCStrategyUseOption = 0, // 0 for xcalloc + kCCStrategyMatchCase = '#', + kCCStrategyIgnoreCase = '?', +} ExprCaseCompareStrategy; + +/// Lexer token type +typedef enum { + kExprLexInvalid = 0, ///< Invalid token, indicaten an error. + kExprLexMissing, ///< Missing token, for use in parser. + kExprLexSpacing, ///< Spaces, tabs, newlines, etc. + kExprLexEOC, ///< End of command character: NL, |, just end of stream. + + kExprLexQuestion, ///< Question mark, for use in ternary. + kExprLexColon, ///< Colon, for use in ternary. + kExprLexOr, ///< Logical or operator. + kExprLexAnd, ///< Logical and operator. + kExprLexComparison, ///< One of the comparison operators. + kExprLexPlus, ///< Plus sign. + kExprLexMinus, ///< Minus sign. + kExprLexDot, ///< Dot: either concat or subscript, also part of the float. + kExprLexMultiplication, ///< Multiplication, division or modulo operator. + + kExprLexNot, ///< Not: !. + + kExprLexNumber, ///< Integer number literal, or part of a float. + kExprLexSingleQuotedString, ///< Single quoted string literal. + kExprLexDoubleQuotedString, ///< Double quoted string literal. + kExprLexOption, ///< &optionname option value. + kExprLexRegister, ///< @r register value. + kExprLexEnv, ///< Environment $variable value. + kExprLexPlainIdentifier, ///< Identifier without scope: `abc`, `foo#bar`. + + kExprLexBracket, ///< Bracket, either opening or closing. + kExprLexFigureBrace, ///< Figure brace, either opening or closing. + kExprLexParenthesis, ///< Parenthesis, either opening or closing. + kExprLexComma, ///< Comma. + kExprLexArrow, ///< Arrow, like from lambda expressions. + kExprLexAssignment, ///< Assignment: `=` or `{op}=`. + // XXX When modifying this enum you need to also modify eltkn_type_tab in + // expressions.c and tests and, possibly, viml_pexpr_repr_token. +} LexExprTokenType; + +typedef enum { + kExprCmpEqual, ///< Equality, unequality. + kExprCmpMatches, ///< Matches regex, not matches regex. + kExprCmpGreater, ///< `>` or `<=` + kExprCmpGreaterOrEqual, ///< `>=` or `<`. + kExprCmpIdentical, ///< `is` or `isnot` +} ExprComparisonType; + +/// All possible option scopes +typedef enum { + kExprOptScopeUnspecified = 0, + kExprOptScopeGlobal = 'g', + kExprOptScopeLocal = 'l', +} ExprOptScope; + +/// All possible assignment types: `=` and `{op}=`. +typedef enum { + kExprAsgnPlain = 0, ///< Plain assignment: `=`. + kExprAsgnAdd, ///< Assignment augmented with addition: `+=`. + kExprAsgnSubtract, ///< Assignment augmented with subtraction: `-=`. + kExprAsgnConcat, ///< Assignment augmented with concatenation: `.=`. +} ExprAssignmentType; + +#define EXPR_OPT_SCOPE_LIST \ + ((char[]){ kExprOptScopeGlobal, kExprOptScopeLocal }) + +/// All possible variable scopes +typedef enum { + kExprVarScopeMissing = 0, + kExprVarScopeScript = 's', + kExprVarScopeGlobal = 'g', + kExprVarScopeVim = 'v', + kExprVarScopeBuffer = 'b', + kExprVarScopeWindow = 'w', + kExprVarScopeTabpage = 't', + kExprVarScopeLocal = 'l', + kExprVarScopeArguments = 'a', +} ExprVarScope; + +#define EXPR_VAR_SCOPE_LIST \ + ((char[]) { \ + kExprVarScopeScript, kExprVarScopeGlobal, kExprVarScopeVim, \ + kExprVarScopeBuffer, kExprVarScopeWindow, kExprVarScopeTabpage, \ + kExprVarScopeLocal, kExprVarScopeBuffer, kExprVarScopeArguments, \ + }) + +/// Lexer token +typedef struct { + ParserPosition start; + size_t len; + LexExprTokenType type; + union { + struct { + ExprComparisonType type; ///< Comparison type. + ExprCaseCompareStrategy ccs; ///< Case comparison strategy. + bool inv; ///< True if comparison is to be inverted. + } cmp; ///< For kExprLexComparison. + + struct { + enum { + kExprLexMulMul, ///< Real multiplication. + kExprLexMulDiv, ///< Division. + kExprLexMulMod, ///< Modulo. + } type; ///< Multiplication type. + } mul; ///< For kExprLexMultiplication. + + struct { + bool closing; ///< True if bracket/etc is a closing one. + } brc; ///< For brackets/braces/parenthesis. + + struct { + int name; ///< Register name, may be -1 if name not present. + } reg; ///< For kExprLexRegister. + + struct { + bool closed; ///< True if quote was closed. + } str; ///< For kExprLexSingleQuotedString and kExprLexDoubleQuotedString. + + struct { + const char *name; ///< Option name start. + size_t len; ///< Option name length. + ExprOptScope scope; ///< Option scope: &l:, &g: or not specified. + } opt; ///< Option properties. + + struct { + ExprVarScope scope; ///< Scope character or 0 if not present. + bool autoload; ///< Has autoload characters. + } var; ///< For kExprLexPlainIdentifier + + struct { + LexExprTokenType type; ///< Suggested type for parsing incorrect code. + const char *msg; ///< Error message. + } err; ///< For kExprLexInvalid + + struct { + union { + float_T floating; + uvarnumber_T integer; + } val; ///< Number value. + uint8_t base; ///< Base: 2, 8, 10 or 16. + bool is_float; ///< True if number is a floating-point. + } num; ///< For kExprLexNumber + + struct { + ExprAssignmentType type; + } ass; ///< For kExprLexAssignment + } data; ///< Additional data, if needed. +} LexExprToken; + +typedef enum { + /// If set, “pointer†to the current byte in pstate will not be shifted + kELFlagPeek = (1 << 0), + /// Determines whether scope is allowed to come before the identifier + kELFlagForbidScope = (1 << 1), + /// Determines whether floating-point numbers are allowed + /// + /// I.e. whether dot is a decimal point separator or is not a part of + /// a number at all. + kELFlagAllowFloat = (1 << 2), + /// Determines whether `is` and `isnot` are seen as comparison operators + /// + /// If set they are supposed to be just regular identifiers. + kELFlagIsNotCmp = (1 << 3), + /// Determines whether EOC tokens are allowed + /// + /// If set then it will yield Invalid token with E15 in place of EOC one if + /// “EOC†is something like "|". It is fine with emitting EOC at the end of + /// string still, with or without this flag set. + kELFlagForbidEOC = (1 << 4), + // XXX Whenever you add a new flag, alter klee_assume() statement in + // viml_expressions_lexer.c. +} LexExprFlags; + +/// Expression AST node type +typedef enum { + kExprNodeMissing = 0, + kExprNodeOpMissing, + kExprNodeTernary, ///< Ternary operator. + kExprNodeTernaryValue, ///< Ternary operator, colon. + kExprNodeRegister, ///< Register. + kExprNodeSubscript, ///< Subscript. + kExprNodeListLiteral, ///< List literal. + kExprNodeUnaryPlus, + kExprNodeBinaryPlus, + kExprNodeNested, ///< Nested parenthesised expression. + kExprNodeCall, ///< Function call. + /// Plain identifier: simple variable/function name + /// + /// Looks like "string", "g:Foo", etc: consists from a single + /// kExprLexPlainIdentifier token. + kExprNodePlainIdentifier, + /// Plain dictionary key, for use with kExprNodeConcatOrSubscript + kExprNodePlainKey, + /// Complex identifier: variable/function name with curly braces + kExprNodeComplexIdentifier, + /// Figure brace expression which is not yet known + /// + /// May resolve to any of kExprNodeDictLiteral, kExprNodeLambda or + /// kExprNodeCurlyBracesIdentifier. + kExprNodeUnknownFigure, + kExprNodeLambda, ///< Lambda. + kExprNodeDictLiteral, ///< Dictionary literal. + kExprNodeCurlyBracesIdentifier, ///< Part of the curly braces name. + kExprNodeComma, ///< Comma “operatorâ€. + kExprNodeColon, ///< Colon “operatorâ€. + kExprNodeArrow, ///< Arrow “operatorâ€. + kExprNodeComparison, ///< Various comparison operators. + /// Concat operator + /// + /// To be only used in cases when it is known for sure it is not a subscript. + kExprNodeConcat, + /// Concat or subscript operator + /// + /// For cases when it is not obvious whether expression is a concat or + /// a subscript. May only have either number or plain identifier as the second + /// child. To make it easier to avoid curly braces in place of + /// kExprNodePlainIdentifier node kExprNodePlainKey is used. + kExprNodeConcatOrSubscript, + kExprNodeInteger, ///< Integral number. + kExprNodeFloat, ///< Floating-point number. + kExprNodeSingleQuotedString, + kExprNodeDoubleQuotedString, + kExprNodeOr, + kExprNodeAnd, + kExprNodeUnaryMinus, + kExprNodeBinaryMinus, + kExprNodeNot, + kExprNodeMultiplication, + kExprNodeDivision, + kExprNodeMod, + kExprNodeOption, + kExprNodeEnvironment, + kExprNodeAssignment, + // XXX When modifying this list also modify east_node_type_tab both in parser + // and in tests, and you most likely will also have to alter list of + // highlight groups stored in highlight_init_cmdline variable. +} ExprASTNodeType; + +typedef struct expr_ast_node ExprASTNode; + +/// Structure representing one AST node +struct expr_ast_node { + ExprASTNodeType type; ///< Node type. + /// Node children: e.g. for 1 + 2 nodes 1 and 2 will be children of +. + ExprASTNode *children; + /// Next node: e.g. for 1 + 2 child nodes 1 and 2 are put into a single-linked + /// list: `(+)->children` references only node 1, node 2 is in + /// `(+)->children->next`. + ExprASTNode *next; + ParserPosition start; + size_t len; + union { + struct { + int name; ///< Register name, may be -1 if name not present. + } reg; ///< For kExprNodeRegister. + struct { + /// Which nodes UnknownFigure can’t possibly represent. + struct { + /// True if UnknownFigure may actually represent dictionary literal. + bool allow_dict; + /// True if UnknownFigure may actually represent lambda. + bool allow_lambda; + /// True if UnknownFigure may actually be part of curly braces name. + bool allow_ident; + } type_guesses; + /// Highlight chunk index, used for rehighlighting if needed + size_t opening_hl_idx; + } fig; ///< For kExprNodeUnknownFigure. + struct { + ExprVarScope scope; ///< Scope character or 0 if not present. + /// Actual identifier without scope. + /// + /// Points to inside parser reader state. + const char *ident; + size_t ident_len; ///< Actual identifier length. + } var; ///< For kExprNodePlainIdentifier and kExprNodePlainKey. + struct { + bool got_colon; ///< True if colon was seen. + } ter; ///< For kExprNodeTernaryValue. + struct { + ExprComparisonType type; ///< Comparison type. + ExprCaseCompareStrategy ccs; ///< Case comparison strategy. + bool inv; ///< True if comparison is to be inverted. + } cmp; ///< For kExprNodeComparison. + struct { + uvarnumber_T value; + } num; ///< For kExprNodeInteger. + struct { + float_T value; + } flt; ///< For kExprNodeFloat. + struct { + char *value; + size_t size; + } str; ///< For kExprNodeSingleQuotedString and + ///< kExprNodeDoubleQuotedString. + struct { + const char *ident; ///< Option name start. + size_t ident_len; ///< Option name length. + ExprOptScope scope; ///< Option scope: &l:, &g: or not specified. + } opt; ///< For kExprNodeOption. + struct { + const char *ident; ///< Environment variable name start. + size_t ident_len; ///< Environment variable name length. + } env; ///< For kExprNodeEnvironment. + struct { + ExprAssignmentType type; + } ass; ///< For kExprNodeAssignment + } data; +}; + +enum { + /// Allow multiple expressions in a row: e.g. for :echo + /// + /// Parser will still parse only one of them though. + kExprFlagsMulti = (1 << 0), + /// Allow NL, NUL and bar to be EOC + /// + /// When parsing expressions input by user bar is assumed to be a binary + /// operator and other two are spacings. + kExprFlagsDisallowEOC = (1 << 1), + /// Parse :let argument + /// + /// That mean that top level node must be an assignment and first nodes + /// belong to lvalues. + kExprFlagsParseLet = (1 << 2), + // XXX whenever you add a new flag, alter klee_assume() statement in + // viml_expressions_parser.c, nvim_parse_expression() flags parsing + // alongside with its documentation and flag sets in check_parsing() + // function in expressions parser functional and unit tests. +} ExprParserFlags; + +/// AST error definition +typedef struct { + /// Error message. Must contain a single printf format atom: %.*s. + const char *msg; + /// Error message argument: points to the location of the error. + const char *arg; + /// Message argument length: length till the end of string. + int arg_len; +} ExprASTError; + +/// Structure representing complety AST for one expression +typedef struct { + /// When AST is not correct this message will be printed. + /// + /// Uses `emsgf(msg, arg_len, arg);`, `msg` is assumed to contain only `%.*s`. + ExprASTError err; + /// Root node of the AST. + ExprASTNode *root; +} ExprAST; + +/// Array mapping ExprASTNodeType to maximum amount of children node may have +extern const uint8_t node_maxchildren[]; + +/// Array mapping ExprASTNodeType values to their stringified versions +extern const char *const east_node_type_tab[]; + +/// Array mapping ExprComparisonType values to their stringified versions +extern const char *const eltkn_cmp_type_tab[]; + +/// Array mapping ExprCaseCompareStrategy values to their stringified versions +extern const char *const ccs_tab[]; + +/// Array mapping ExprAssignmentType values to their stringified versions +extern const char *const expr_asgn_type_tab[]; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "viml/parser/expressions.h.generated.h" +#endif + +#endif // NVIM_VIML_PARSER_EXPRESSIONS_H diff --git a/src/nvim/viml/parser/parser.c b/src/nvim/viml/parser/parser.c new file mode 100644 index 0000000000..8d26d08ea7 --- /dev/null +++ b/src/nvim/viml/parser/parser.c @@ -0,0 +1,16 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +#include "nvim/viml/parser/parser.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "viml/parser/parser.c.generated.h" +#endif + + +void parser_simple_get_line(void *cookie, ParserLine *ret_pline) +{ + ParserLine **plines_p = (ParserLine **)cookie; + *ret_pline = **plines_p; + (*plines_p)++; +} diff --git a/src/nvim/viml/parser/parser.h b/src/nvim/viml/parser/parser.h new file mode 100644 index 0000000000..7ac49709d8 --- /dev/null +++ b/src/nvim/viml/parser/parser.h @@ -0,0 +1,244 @@ +#ifndef NVIM_VIML_PARSER_PARSER_H +#define NVIM_VIML_PARSER_PARSER_H + +#include <stdbool.h> +#include <stddef.h> +#include <assert.h> + +#include "nvim/lib/kvec.h" +#include "nvim/func_attr.h" +#include "nvim/mbyte.h" +#include "nvim/memory.h" + +/// One parsed line +typedef struct { + const char *data; ///< Parsed line pointer + size_t size; ///< Parsed line size + bool allocated; ///< True if line may be freed. +} ParserLine; + +/// Line getter type for parser +/// +/// Line getter must return {NULL, 0} for EOF. +typedef void (*ParserLineGetter)(void *cookie, ParserLine *ret_pline); + +/// Parser position in the input +typedef struct { + size_t line; ///< Line index in ParserInputReader.lines. + size_t col; ///< Byte index in the line. +} ParserPosition; + +/// Parser state item. +typedef struct { + enum { + kPTopStateParsingCommand = 0, + kPTopStateParsingExpression, + } type; + union { + struct { + enum { + kExprUnknown = 0, + } type; + } expr; + } data; +} ParserStateItem; + +/// Structure defining input reader +typedef struct { + /// Function used to get next line. + ParserLineGetter get_line; + /// Data for get_line function. + void *cookie; + /// All lines obtained by get_line. + kvec_withinit_t(ParserLine, 4) lines; + /// Conversion, for :scriptencoding. + vimconv_T conv; +} ParserInputReader; + +/// Highlighted region definition +/// +/// Note: one chunk may highlight only one line. +typedef struct { + ParserPosition start; ///< Start of the highlight: line and column. + size_t end_col; ///< End column, points to the start of the next character. + const char *group; ///< Highlight group. +} ParserHighlightChunk; + +/// Highlighting defined by a parser +typedef kvec_withinit_t(ParserHighlightChunk, 16) ParserHighlight; + +/// Structure defining parser state +typedef struct { + /// Line reader. + ParserInputReader reader; + /// Position up to which input was parsed. + ParserPosition pos; + /// Parser state stack. + kvec_withinit_t(ParserStateItem, 16) stack; + /// Highlighting support. + ParserHighlight *colors; + /// True if line continuation can be used. + bool can_continuate; +} ParserState; + +static inline void viml_parser_init( + ParserState *const ret_pstate, + const ParserLineGetter get_line, void *const cookie, + ParserHighlight *const colors) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1, 2); + +/// Initialize a new parser state instance +/// +/// @param[out] ret_pstate Parser state to initialize. +/// @param[in] get_line Line getter function. +/// @param[in] cookie Argument for the get_line function. +/// @param[in] colors Where to save highlighting. May be NULL if it is not +/// needed. +static inline void viml_parser_init( + ParserState *const ret_pstate, + const ParserLineGetter get_line, void *const cookie, + ParserHighlight *const colors) +{ + *ret_pstate = (ParserState) { + .reader = { + .get_line = get_line, + .cookie = cookie, + .conv = MBYTE_NONE_CONV, + }, + .pos = { 0, 0 }, + .colors = colors, + .can_continuate = false, + }; + kvi_init(ret_pstate->reader.lines); + kvi_init(ret_pstate->stack); +} + +static inline void viml_parser_destroy(ParserState *const pstate) + REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE; + +/// Free all memory allocated by the parser on heap +/// +/// @param pstate Parser state to free. +static inline void viml_parser_destroy(ParserState *const pstate) +{ + for (size_t i = 0; i < kv_size(pstate->reader.lines); i++) { + ParserLine pline = kv_A(pstate->reader.lines, i); + if (pline.allocated) { + xfree((void *)pline.data); + } + } + kvi_destroy(pstate->reader.lines); + kvi_destroy(pstate->stack); +} + +static inline void viml_preader_get_line(ParserInputReader *const preader, + ParserLine *const ret_pline) + REAL_FATTR_NONNULL_ALL; + +/// Get one line from ParserInputReader +static inline void viml_preader_get_line(ParserInputReader *const preader, + ParserLine *const ret_pline) +{ + ParserLine pline; + preader->get_line(preader->cookie, &pline); + if (preader->conv.vc_type != CONV_NONE && pline.size) { + ParserLine cpline = { + .allocated = true, + .size = pline.size, + }; + cpline.data = (char *)string_convert(&preader->conv, + (char_u *)pline.data, + &cpline.size); + if (pline.allocated) { + xfree((void *)pline.data); + } + pline = cpline; + } + kvi_push(preader->lines, pline); + *ret_pline = pline; +} + +static inline bool viml_parser_get_remaining_line(ParserState *const pstate, + ParserLine *const ret_pline) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; + +/// Get currently parsed line, shifted to pstate->pos.col +/// +/// @param pstate Parser state to operate on. +/// +/// @return True if there is a line, false in case of EOF. +static inline bool viml_parser_get_remaining_line(ParserState *const pstate, + ParserLine *const ret_pline) +{ + const size_t num_lines = kv_size(pstate->reader.lines); + if (pstate->pos.line == num_lines) { + viml_preader_get_line(&pstate->reader, ret_pline); + } else { + *ret_pline = kv_last(pstate->reader.lines); + } + assert(pstate->pos.line == kv_size(pstate->reader.lines) - 1); + if (ret_pline->data != NULL) { + ret_pline->data += pstate->pos.col; + ret_pline->size -= pstate->pos.col; + } + return ret_pline->data != NULL; +} + +static inline void viml_parser_advance(ParserState *const pstate, + const size_t len) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL; + +/// Advance position by a given number of bytes +/// +/// At maximum advances to the next line. +/// +/// @param pstate Parser state to advance. +/// @param[in] len Number of bytes to advance. +static inline void viml_parser_advance(ParserState *const pstate, + const size_t len) +{ + assert(pstate->pos.line == kv_size(pstate->reader.lines) - 1); + const ParserLine pline = kv_last(pstate->reader.lines); + if (pstate->pos.col + len >= pline.size) { + pstate->pos.line++; + pstate->pos.col = 0; + } else { + pstate->pos.col += len; + } +} + +static inline void viml_parser_highlight(ParserState *const pstate, + const ParserPosition start, + const size_t end_col, + const char *const group) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL; + +/// Record highlighting of some region of text +/// +/// @param pstate Parser state to work with. +/// @param[in] start Start position of the highlight. +/// @param[in] len Highlighting chunk length. +/// @param[in] group Highlight group. +static inline void viml_parser_highlight(ParserState *const pstate, + const ParserPosition start, + const size_t len, + const char *const group) +{ + if (pstate->colors == NULL || len == 0) { + return; + } + assert(kv_size(*pstate->colors) == 0 + || kv_Z(*pstate->colors, 0).start.line < start.line + || kv_Z(*pstate->colors, 0).end_col <= start.col); + kvi_push(*pstate->colors, ((ParserHighlightChunk) { + .start = start, + .end_col = start.col + len, + .group = group, + })); +} + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "viml/parser/parser.h.generated.h" +#endif + +#endif // NVIM_VIML_PARSER_PARSER_H diff --git a/src/nvim/window.c b/src/nvim/window.c index 4e4eb297aa..b4ef901d94 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -641,11 +641,12 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) if (oldwin->w_width - new_size - 1 < p_wmw) do_equal = TRUE; - /* We don't like to take lines for the new window from a - * 'winfixwidth' window. Take them from a window to the left or right - * instead, if possible. */ - if (oldwin->w_p_wfw) - win_setwidth_win(oldwin->w_width + new_size, oldwin); + // We don't like to take lines for the new window from a + // 'winfixwidth' window. Take them from a window to the left or right + // instead, if possible. Add one for the separator. + if (oldwin->w_p_wfw) { + win_setwidth_win(oldwin->w_width + new_size + 1, oldwin); + } /* Only make all windows the same width if one of them (except oldwin) * is wider than one of the split windows. */ @@ -1724,7 +1725,6 @@ void close_windows(buf_T *buf, int keep_curwin) { tabpage_T *tp, *nexttp; int h = tabline_height(); - int count = tabpage_index(NULL); ++RedrawingDisabled; @@ -1762,10 +1762,6 @@ void close_windows(buf_T *buf, int keep_curwin) --RedrawingDisabled; - if (count != tabpage_index(NULL)) { - apply_autocmds(EVENT_TABCLOSED, NULL, NULL, false, curbuf); - } - redraw_tabline = true; if (h != tabline_height()) { shell_new_rows(); @@ -1848,7 +1844,6 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, // Since goto_tabpage_tp above did not trigger *Enter autocommands, do // that now. - apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, curbuf); apply_autocmds(EVENT_WINENTER, NULL, NULL, false, curbuf); apply_autocmds(EVENT_TABENTER, NULL, NULL, false, curbuf); if (old_curbuf != curbuf) { @@ -1991,6 +1986,14 @@ int win_close(win_T *win, int free_buf) * the screen space. */ wp = win_free_mem(win, &dir, NULL); + if (help_window) { + // Closing the help window moves the cursor back to the original window. + win_T *tmpwp = get_snapshot_focus(SNAP_HELP_IDX); + if (tmpwp != NULL) { + wp = tmpwp; + } + } + /* Make sure curwin isn't invalid. It can cause severe trouble when * printing an error message. For win_equal() curbuf needs to be valid * too. */ @@ -2100,19 +2103,29 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) /* When closing the last window in a tab page remove the tab page. */ if (tp->tp_firstwin == tp->tp_lastwin) { - if (tp == first_tabpage) + char_u prev_idx[NUMBUFLEN]; + if (has_event(EVENT_TABCLOSED)) { + vim_snprintf((char *)prev_idx, NUMBUFLEN, "%i", tabpage_index(tp)); + } + + if (tp == first_tabpage) { first_tabpage = tp->tp_next; - else { + } else { for (ptp = first_tabpage; ptp != NULL && ptp->tp_next != tp; - ptp = ptp->tp_next) - ; + ptp = ptp->tp_next) { + // loop + } if (ptp == NULL) { - EMSG2(_(e_intern2), "win_close_othertab()"); + internal_error("win_close_othertab()"); return; } ptp->tp_next = tp->tp_next; } - free_tp = TRUE; + free_tp = true; + + if (has_event(EVENT_TABCLOSED)) { + apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, win->w_buffer); + } } /* Free the memory used for the window. */ @@ -2845,7 +2858,7 @@ close_others ( if (bufIsChanged(wp->w_buffer)) continue; } - win_close(wp, !P_HID(wp->w_buffer) && !bufIsChanged(wp->w_buffer)); + win_close(wp, !buf_hide(wp->w_buffer) && !bufIsChanged(wp->w_buffer)); } if (message && !ONE_WINDOW) @@ -5421,6 +5434,27 @@ static win_T *restore_snapshot_rec(frame_T *sn, frame_T *fr) return wp; } +/// Gets the focused window (the one holding the cursor) of the snapshot. +static win_T *get_snapshot_focus(int idx) +{ + if (curtab->tp_snapshot[idx] == NULL) { + return NULL; + } + + frame_T *sn = curtab->tp_snapshot[idx]; + // This should be equivalent to the recursive algorithm found in + // restore_snapshot as far as traveling nodes go. + while (sn->fr_child != NULL || sn->fr_next != NULL) { + while (sn->fr_child != NULL) { + sn = sn->fr_child; + } + if (sn->fr_next != NULL) { + sn = sn->fr_next; + } + } + + return sn->fr_win; +} /* * Set "win" to be the curwin and "tp" to be the current tab page. @@ -5577,49 +5611,48 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, } // Set up position matches - if (pos_list != NULL) - { - linenr_T toplnum = 0; - linenr_T botlnum = 0; - listitem_T *li; - int i; - - for (i = 0, li = pos_list->lv_first; li != NULL && i < MAXPOSMATCH; - i++, li = li->li_next) { - linenr_T lnum = 0; - colnr_T col = 0; - int len = 1; - list_T *subl; - listitem_T *subli; + if (pos_list != NULL) { + linenr_T toplnum = 0; + linenr_T botlnum = 0; + + int i = 0; + TV_LIST_ITER(pos_list, li, { + linenr_T lnum = 0; + colnr_T col = 0; + int len = 1; bool error = false; - if (li->li_tv.v_type == VAR_LIST) { - subl = li->li_tv.vval.v_list; - if (subl == NULL) { - goto fail; - } - subli = subl->lv_first; + if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) { + const list_T *const subl = TV_LIST_ITEM_TV(li)->vval.v_list; + const listitem_T *subli = tv_list_first(subl); if (subli == NULL) { + emsgf(_("E5030: Empty list at position %d"), + (int)tv_list_idx_of_item(pos_list, li)); goto fail; } - lnum = tv_get_number_chk(&subli->li_tv, &error); + lnum = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); if (error) { goto fail; } - if (lnum == 0) { - --i; + if (lnum <= 0) { continue; } m->pos.pos[i].lnum = lnum; - subli = subli->li_next; + subli = TV_LIST_ITEM_NEXT(subl, subli); if (subli != NULL) { - col = tv_get_number_chk(&subli->li_tv, &error); + col = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); if (error) { goto fail; } - subli = subli->li_next; + if (col < 0) { + continue; + } + subli = TV_LIST_ITEM_NEXT(subl, subli); if (subli != NULL) { - len = tv_get_number_chk(&subli->li_tv, &error); + len = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); + if (len < 0) { + continue; + } if (error) { goto fail; } @@ -5627,16 +5660,16 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, } m->pos.pos[i].col = col; m->pos.pos[i].len = len; - } else if (li->li_tv.v_type == VAR_NUMBER) { - if (li->li_tv.vval.v_number == 0) { - --i; + } else if (TV_LIST_ITEM_TV(li)->v_type == VAR_NUMBER) { + if (TV_LIST_ITEM_TV(li)->vval.v_number <= 0) { continue; } - m->pos.pos[i].lnum = li->li_tv.vval.v_number; + m->pos.pos[i].lnum = TV_LIST_ITEM_TV(li)->vval.v_number; m->pos.pos[i].col = 0; m->pos.pos[i].len = 0; } else { - EMSG(_("List or number required")); + emsgf(_("E5031: List or number required at position %d"), + (int)tv_list_idx_of_item(pos_list, li)); goto fail; } if (toplnum == 0 || lnum < toplnum) { @@ -5645,7 +5678,11 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, if (botlnum == 0 || lnum >= botlnum) { botlnum = lnum + 1; } - } + i++; + if (i >= MAXPOSMATCH) { + break; + } + }); // Calculate top and bottom lines for redrawing area if (toplnum != 0){ @@ -5885,13 +5922,15 @@ void win_get_tabwin(handle_T id, int *tabnr, int *winnr) } } -void win_id2tabwin(typval_T *argvars, list_T *list) +void win_id2tabwin(typval_T *const argvars, typval_T *const rettv) { int winnr = 1; int tabnr = 1; handle_T id = (handle_T)tv_get_number(&argvars[0]); win_get_tabwin(id, &tabnr, &winnr); + + list_T *const list = tv_list_alloc_ret(rettv, 2); tv_list_append_number(list, tabnr); tv_list_append_number(list, winnr); } diff --git a/test/README.md b/test/README.md index 010a2c9c12..58aa6a06aa 100644 --- a/test/README.md +++ b/test/README.md @@ -168,16 +168,13 @@ minutes](http://learnxinyminutes.com/docs/lua/). Do not silently skip the test with `if-else`. If a functional test depends on some external factor (e.g. the existence of `md5sum` on `$PATH`), *and* you can't mock or fake the dependency, then skip the test via `pending()` if the - external factor is missing. This ensures that the *total* test-count (success - + fail + error + pending) is the same in all environments. + external factor is missing. This ensures that the *total* test-count + (success + fail + error + pending) is the same in all environments. - *Note:* `pending()` is ignored if it is missing an argument _unless_ it is - [contained in an `it()` - block](https://github.com/neovim/neovim/blob/d21690a66e7eb5ebef18046c7a79ef898966d786/test/functional/ex_cmds/grep_spec.lua#L11). - Provide empty function argument if the `pending()` call is outside of - `it()` + [contained in an `it()` block](https://github.com/neovim/neovim/blob/d21690a66e7eb5ebef18046c7a79ef898966d786/test/functional/ex_cmds/grep_spec.lua#L11). + Provide empty function argument if the `pending()` call is outside of `it()` ([example](https://github.com/neovim/neovim/commit/5c1dc0fbe7388528875aff9d7b5055ad718014de#diff-bf80b24c724b0004e8418102f68b0679R18)). -- Use `make testlint` for using the shipped luacheck program ([supported by - syntastic](https://github.com/scrooloose/syntastic/blob/d6b96c079be137c83009827b543a83aa113cc011/doc/syntastic-checkers.txt#L3546)) +- Use `make testlint` for using the shipped luacheck program ([supported by syntastic](https://github.com/scrooloose/syntastic/blob/d6b96c079be137c83009827b543a83aa113cc011/doc/syntastic-checkers.txt#L3546)) to lint all tests. ### Where tests go @@ -341,3 +338,9 @@ disables tracing (the fastest, but you get no data if tests crash and there was no core dump generated), `1` or empty/undefined leaves only C function cals and returns in the trace (faster then recording everything), `2` records all function calls, returns and lua source lines exuecuted. + +`NVIM_TEST_TRACE_ON_ERROR` (U) (1): makes unit tests yield trace on error in +addition to regular error message. + +`NVIM_TEST_MAXTRACE` (U) (N): specifies maximum number of trace lines to keep. +Default is 1024. diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua index 2297a0760f..fed53a3dfd 100644 --- a/test/functional/api/highlight_spec.lua +++ b/test/functional/api/highlight_spec.lua @@ -99,5 +99,14 @@ describe('highlight api',function() eq(false, err) eq('Invalid highlight name: ', string.match(emsg, 'Invalid.*')) + + -- Test "standout" attribute. #8054 + eq({ underline = true, }, + meths.get_hl_by_name('cursorline', 0)); + command('hi CursorLine cterm=standout,underline term=standout,underline gui=standout,underline') + command('set cursorline') + eq({ underline = true, standout = true, }, + meths.get_hl_by_name('cursorline', 0)); + end) end) diff --git a/test/functional/api/proc_spec.lua b/test/functional/api/proc_spec.lua new file mode 100644 index 0000000000..d99c26b6c2 --- /dev/null +++ b/test/functional/api/proc_spec.lua @@ -0,0 +1,81 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local eq = helpers.eq +local funcs = helpers.funcs +local iswin = helpers.iswin +local nvim_argv = helpers.nvim_argv +local ok = helpers.ok +local request = helpers.request +local retry = helpers.retry +local NIL = helpers.NIL + +describe('api', function() + before_each(clear) + + describe('nvim_get_proc_children', function() + it('returns child process ids', function() + local this_pid = funcs.getpid() + + local job1 = funcs.jobstart(nvim_argv) + retry(nil, nil, function() + eq(1, #request('nvim_get_proc_children', this_pid)) + end) + + local job2 = funcs.jobstart(nvim_argv) + retry(nil, nil, function() + eq(2, #request('nvim_get_proc_children', this_pid)) + end) + + funcs.jobstop(job1) + retry(nil, nil, function() + eq(1, #request('nvim_get_proc_children', this_pid)) + end) + + funcs.jobstop(job2) + retry(nil, nil, function() + eq(0, #request('nvim_get_proc_children', this_pid)) + end) + end) + + it('validates input', function() + local status, rv = pcall(request, "nvim_get_proc_children", -1) + eq(false, status) + eq("Invalid pid: -1", string.match(rv, "Invalid.*")) + + status, rv = pcall(request, "nvim_get_proc_children", 0) + eq(false, status) + eq("Invalid pid: 0", string.match(rv, "Invalid.*")) + + -- Assume PID 99999 does not exist. + status, rv = pcall(request, "nvim_get_proc_children", 99999) + eq(true, status) + eq({}, rv) + end) + end) + + describe('nvim_get_proc', function() + it('returns process info', function() + local pid = funcs.getpid() + local pinfo = request('nvim_get_proc', pid) + eq((iswin() and 'nvim.exe' or 'nvim'), pinfo.name) + ok(pinfo.pid == pid) + ok(type(pinfo.ppid) == 'number' and pinfo.ppid ~= pid) + end) + + it('validates input', function() + local status, rv = pcall(request, "nvim_get_proc", -1) + eq(false, status) + eq("Invalid pid: -1", string.match(rv, "Invalid.*")) + + status, rv = pcall(request, "nvim_get_proc", 0) + eq(false, status) + eq("Invalid pid: 0", string.match(rv, "Invalid.*")) + + -- Assume PID 99999 does not exist. + status, rv = pcall(request, "nvim_get_proc", 99999) + eq(true, status) + eq(NIL, rv) + end) + end) +end) diff --git a/test/functional/api/server_notifications_spec.lua b/test/functional/api/server_notifications_spec.lua index 9d7cfb9b78..1d64ae7103 100644 --- a/test/functional/api/server_notifications_spec.lua +++ b/test/functional/api/server_notifications_spec.lua @@ -1,7 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) -local eq, clear, eval, command, nvim, next_message = +local eq, clear, eval, command, nvim, next_msg = helpers.eq, helpers.clear, helpers.eval, helpers.command, helpers.nvim, - helpers.next_message + helpers.next_msg local meths = helpers.meths describe('notify', function() @@ -15,10 +15,10 @@ describe('notify', function() describe('passing a valid channel id', function() it('sends the notification/args to the corresponding channel', function() eval('rpcnotify('..channel..', "test-event", 1, 2, 3)') - eq({'notification', 'test-event', {1, 2, 3}}, next_message()) + eq({'notification', 'test-event', {1, 2, 3}}, next_msg()) command('au FileType lua call rpcnotify('..channel..', "lua!")') command('set filetype=lua') - eq({'notification', 'lua!', {}}, next_message()) + eq({'notification', 'lua!', {}}, next_msg()) end) end) @@ -28,13 +28,13 @@ describe('notify', function() eval('rpcnotify(0, "event1", 1, 2, 3)') eval('rpcnotify(0, "event2", 4, 5, 6)') eval('rpcnotify(0, "event2", 7, 8, 9)') - eq({'notification', 'event2', {4, 5, 6}}, next_message()) - eq({'notification', 'event2', {7, 8, 9}}, next_message()) + eq({'notification', 'event2', {4, 5, 6}}, next_msg()) + eq({'notification', 'event2', {7, 8, 9}}, next_msg()) nvim('unsubscribe', 'event2') nvim('subscribe', 'event1') eval('rpcnotify(0, "event2", 10, 11, 12)') eval('rpcnotify(0, "event1", 13, 14, 15)') - eq({'notification', 'event1', {13, 14, 15}}, next_message()) + eq({'notification', 'event1', {13, 14, 15}}, next_msg()) end) it('does not crash for deeply nested variable', function() @@ -42,7 +42,7 @@ describe('notify', function() local nest_level = 1000 meths.command(('call map(range(%u), "extend(g:, {\'l\': [g:l]})")'):format(nest_level - 1)) eval('rpcnotify('..channel..', "event", g:l)') - local msg = next_message() + local msg = next_msg() eq('notification', msg[1]) eq('event', msg[2]) local act_ret = msg[3] diff --git a/test/functional/api/server_requests_spec.lua b/test/functional/api/server_requests_spec.lua index 37ac532d18..18229b54ff 100644 --- a/test/functional/api/server_requests_spec.lua +++ b/test/functional/api/server_requests_spec.lua @@ -6,7 +6,7 @@ local Paths = require('test.config.paths') local clear, nvim, eval = helpers.clear, helpers.nvim, helpers.eval local eq, neq, run, stop = helpers.eq, helpers.neq, helpers.run, helpers.stop local nvim_prog, command, funcs = helpers.nvim_prog, helpers.command, helpers.funcs -local source, next_message = helpers.source, helpers.next_message +local source, next_msg = helpers.source, helpers.next_msg local ok = helpers.ok local meths = helpers.meths local spawn, nvim_argv = helpers.spawn, helpers.nvim_argv @@ -258,12 +258,12 @@ describe('server -> client', function() it('rpc and text stderr can be combined', function() eq("ok",funcs.rpcrequest(jobid, "poll")) funcs.rpcnotify(jobid, "ping") - eq({'notification', 'pong', {}}, next_message()) + eq({'notification', 'pong', {}}, next_msg()) eq("done!",funcs.rpcrequest(jobid, "write_stderr", "fluff\n")) - eq({'notification', 'stderr', {0, {'fluff', ''}}}, next_message()) + eq({'notification', 'stderr', {0, {'fluff', ''}}}, next_msg()) funcs.rpcrequest(jobid, "exit") - eq({'notification', 'stderr', {0, {''}}}, next_message()) - eq({'notification', 'exit', {0, 0}}, next_message()) + eq({'notification', 'stderr', {0, {''}}}, next_msg()) + eq({'notification', 'exit', {0, 0}}, next_msg()) end) end) diff --git a/test/functional/api/ui_spec.lua b/test/functional/api/ui_spec.lua new file mode 100644 index 0000000000..b028a50b02 --- /dev/null +++ b/test/functional/api/ui_spec.lua @@ -0,0 +1,37 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local eq = helpers.eq +local eval = helpers.eval +local expect_err = helpers.expect_err +local meths = helpers.meths +local request = helpers.request + +describe('nvim_ui_attach()', function() + before_each(function() + clear() + end) + it('handles very large width/height #2180', function() + local screen = Screen.new(999, 999) + screen:attach() + eq(999, eval('&lines')) + eq(999, eval('&columns')) + end) + it('invalid option returns error', function() + expect_err('No such UI option: foo', + meths.ui_attach, 80, 24, { foo={'foo'} }) + end) + it('validates channel arg', function() + expect_err('UI not attached to channel: 1', + request, 'nvim_ui_try_resize', 40, 10) + expect_err('UI not attached to channel: 1', + request, 'nvim_ui_set_option', 'rgb', true) + expect_err('UI not attached to channel: 1', + request, 'nvim_ui_detach') + + local screen = Screen.new() + screen:attach({rgb=false}) + expect_err('UI already attached to channel: 1', + request, 'nvim_ui_attach', 40, 10, { rgb=false }) + end) +end) diff --git a/test/functional/api/version_spec.lua b/test/functional/api/version_spec.lua index d23f058f69..7bf54c0d1e 100644 --- a/test/functional/api/version_spec.lua +++ b/test/functional/api/version_spec.lua @@ -1,6 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) local mpack = require('mpack') local clear, funcs, eq = helpers.clear, helpers.funcs, helpers.eq +local call = helpers.call local function read_mpack_file(fname) local fd = io.open(fname, 'rb') @@ -18,7 +19,7 @@ describe("api_info()['version']", function() before_each(clear) it("returns API level", function() - local version = helpers.call('api_info')['version'] + local version = call('api_info')['version'] local current = version['api_level'] local compat = version['api_compatible'] eq("number", type(current)) @@ -27,7 +28,7 @@ describe("api_info()['version']", function() end) it("returns Nvim version", function() - local version = helpers.call('api_info')['version'] + local version = call('api_info')['version'] local major = version['major'] local minor = version['minor'] local patch = version['patch'] @@ -147,3 +148,14 @@ describe("api functions", function() end) end) + +describe("ui_options in metadata", function() + it('are correct', function() + -- TODO(bfredl) once a release freezes this into metadata, + -- instead check that all old options are present + local api = helpers.call('api_info') + local options = api.ui_options + eq({'rgb', 'ext_cmdline', 'ext_popupmenu', + 'ext_tabline', 'ext_wildmenu'}, options) + end) +end) diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index b849304d45..718294d941 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -1,5 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') +local global_helpers = require('test.helpers') + local NIL = helpers.NIL local clear, nvim, eq, neq = helpers.clear, helpers.nvim, helpers.eq, helpers.neq local ok, nvim_async, feed = helpers.ok, helpers.nvim_async, helpers.feed @@ -9,6 +11,11 @@ local funcs = helpers.funcs local request = helpers.request local meth_pcall = helpers.meth_pcall local command = helpers.command +local iswin = helpers.iswin + +local intchar2lua = global_helpers.intchar2lua +local format_string = global_helpers.format_string +local mergedicts_copy = global_helpers.mergedicts_copy describe('api', function() before_each(clear) @@ -31,7 +38,7 @@ describe('api', function() os.remove(fname) end) - it("VimL error: fails (VimL error), does NOT update v:errmsg", function() + it("parse error: fails (specific error), does NOT update v:errmsg", function() -- Most API methods return generic errors (or no error) if a VimL -- expression fails; nvim_command returns the VimL error details. local status, rv = pcall(nvim, "command", "bogus_command") @@ -39,6 +46,85 @@ describe('api', function() eq("E492:", string.match(rv, "E%d*:")) -- VimL error was returned. eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated. end) + + it("runtime error: fails (specific error)", function() + local status, rv = pcall(nvim, "command_output", "buffer 23487") + eq(false, status) -- nvim_command() failed. + eq("E86: Buffer 23487 does not exist", string.match(rv, "E%d*:.*")) + eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated. + end) + end) + + describe('nvim_command_output', function() + it('does not induce hit-enter prompt', function() + -- Induce a hit-enter prompt use nvim_input (non-blocking). + nvim('command', 'set cmdheight=1') + nvim('input', [[:echo "hi\nhi2"<CR>]]) + + -- Verify hit-enter prompt. + eq({mode='r', blocking=true}, nvim("get_mode")) + nvim('input', [[<C-c>]]) + + -- Verify NO hit-enter prompt. + nvim('command_output', [[echo "hi\nhi2"]]) + eq({mode='n', blocking=false}, nvim("get_mode")) + end) + + it('captures command output', function() + eq('this is\nspinal tap', + nvim('command_output', [[echo "this is\nspinal tap"]])) + eq('no line ending!', + nvim('command_output', [[echon "no line ending!"]])) + end) + + it('captures empty command output', function() + eq('', nvim('command_output', 'echo')) + end) + + it('captures single-char command output', function() + eq('x', nvim('command_output', 'echo "x"')) + end) + + it('captures multiple commands', function() + eq('foo\n 1 %a "[No Name]" line 1', + nvim('command_output', 'echo "foo" | ls')) + end) + + it('captures nested execute()', function() + eq('\nnested1\nnested2\n 1 %a "[No Name]" line 1', + nvim('command_output', + [[echo execute('echo "nested1\nnested2"') | ls]])) + end) + + it('captures nested nvim_command_output()', function() + eq('nested1\nnested2\n 1 %a "[No Name]" line 1', + nvim('command_output', + [[echo nvim_command_output('echo "nested1\nnested2"') | ls]])) + end) + + it('returns shell |:!| output', function() + local win_lf = iswin() and '\r' or '' + eq(':!echo foo\r\n\nfoo'..win_lf..'\n', nvim('command_output', [[!echo foo]])) + end) + + it("parse error: fails (specific error), does NOT update v:errmsg", function() + local status, rv = pcall(nvim, "command_output", "bogus commannnd") + eq(false, status) -- nvim_command_output() failed. + eq("E492: Not an editor command: bogus commannnd", + string.match(rv, "E%d*:.*")) + eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated. + -- Verify NO hit-enter prompt. + eq({mode='n', blocking=false}, nvim("get_mode")) + end) + + it("runtime error: fails (specific error)", function() + local status, rv = pcall(nvim, "command_output", "buffer 42") + eq(false, status) -- nvim_command_output() failed. + eq("E86: Buffer 42 does not exist", string.match(rv, "E%d*:.*")) + eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated. + -- Verify NO hit-enter prompt. + eq({mode='n', blocking=false}, nvim("get_mode")) + end) end) describe('nvim_eval', function() @@ -495,7 +581,8 @@ describe('api', function() screen:set_default_attr_ids({ [0] = {bold=true, foreground=Screen.colors.Blue}, [1] = {foreground = Screen.colors.White, background = Screen.colors.Red}, - [2] = {bold = true, foreground = Screen.colors.SeaGreen} + [2] = {bold = true, foreground = Screen.colors.SeaGreen}, + [3] = {bold = true, reverse = true}, }) end) @@ -516,11 +603,11 @@ describe('api', function() it('shows return prompt when more than &cmdheight lines', function() nvim_async('err_write', 'something happened\nvery bad\n') screen:expect([[ + | {0:~ }| {0:~ }| {0:~ }| - {0:~ }| - {0:~ }| + {3: }| {1:something happened} | {1:very bad} | {2:Press ENTER or type command to continue}^ | @@ -530,9 +617,9 @@ describe('api', function() it('shows return prompt after all lines are shown', function() nvim_async('err_write', 'FAILURE\nERROR\nEXCEPTION\nTRACEBACK\n') screen:expect([[ + | {0:~ }| - {0:~ }| - {0:~ }| + {3: }| {1:FAILURE} | {1:ERROR} | {1:EXCEPTION} | @@ -560,11 +647,11 @@ describe('api', function() -- shows up to &cmdheight lines nvim_async('err_write', 'more fail\ntoo fail\n') screen:expect([[ + | {0:~ }| {0:~ }| {0:~ }| - {0:~ }| - {0:~ }| + {3: }| {1:more fail} | {1:too fail} | {2:Press ENTER or type command to continue}^ | @@ -661,7 +748,7 @@ describe('api', function() end) end) - describe('list_runtime_paths', function() + describe('nvim_list_runtime_paths', function() it('returns nothing with empty &runtimepath', function() meths.set_option('runtimepath', '') eq({}, meths.list_runtime_paths()) @@ -710,4 +797,247 @@ describe('api', function() ok(err:match(': Wrong type for argument 1, expecting String') ~= nil) end) + describe('nvim_parse_expression', function() + before_each(function() + meths.set_option('isident', '') + end) + local function simplify_east_api_node(line, east_api_node) + if east_api_node == NIL then + return nil + end + if east_api_node.children then + for k, v in pairs(east_api_node.children) do + east_api_node.children[k] = simplify_east_api_node(line, v) + end + end + local typ = east_api_node.type + if typ == 'Register' then + typ = typ .. ('(name=%s)'):format( + tostring(intchar2lua(east_api_node.name))) + east_api_node.name = nil + elseif typ == 'PlainIdentifier' then + typ = typ .. ('(scope=%s,ident=%s)'):format( + tostring(intchar2lua(east_api_node.scope)), east_api_node.ident) + east_api_node.scope = nil + east_api_node.ident = nil + elseif typ == 'PlainKey' then + typ = typ .. ('(key=%s)'):format(east_api_node.ident) + east_api_node.ident = nil + elseif typ == 'Comparison' then + typ = typ .. ('(type=%s,inv=%u,ccs=%s)'):format( + east_api_node.cmp_type, east_api_node.invert and 1 or 0, + east_api_node.ccs_strategy) + east_api_node.ccs_strategy = nil + east_api_node.cmp_type = nil + east_api_node.invert = nil + elseif typ == 'Integer' then + typ = typ .. ('(val=%u)'):format(east_api_node.ivalue) + east_api_node.ivalue = nil + elseif typ == 'Float' then + typ = typ .. format_string('(val=%e)', east_api_node.fvalue) + east_api_node.fvalue = nil + elseif typ == 'SingleQuotedString' or typ == 'DoubleQuotedString' then + typ = format_string('%s(val=%q)', typ, east_api_node.svalue) + east_api_node.svalue = nil + elseif typ == 'Option' then + typ = ('%s(scope=%s,ident=%s)'):format( + typ, + tostring(intchar2lua(east_api_node.scope)), + east_api_node.ident) + east_api_node.ident = nil + east_api_node.scope = nil + elseif typ == 'Environment' then + typ = ('%s(ident=%s)'):format(typ, east_api_node.ident) + east_api_node.ident = nil + elseif typ == 'Assignment' then + local aug = east_api_node.augmentation + if aug == '' then aug = 'Plain' end + typ = ('%s(%s)'):format(typ, aug) + east_api_node.augmentation = nil + end + typ = ('%s:%u:%u:%s'):format( + typ, east_api_node.start[1], east_api_node.start[2], + line:sub(east_api_node.start[2] + 1, + east_api_node.start[2] + 1 + east_api_node.len - 1)) + assert(east_api_node.start[2] + east_api_node.len - 1 <= #line) + for k, _ in pairs(east_api_node.start) do + assert(({true, true})[k]) + end + east_api_node.start = nil + east_api_node.type = nil + east_api_node.len = nil + local can_simplify = true + for _, _ in pairs(east_api_node) do + if can_simplify then can_simplify = false end + end + if can_simplify then + return typ + else + east_api_node[1] = typ + return east_api_node + end + end + local function simplify_east_api(line, east_api) + if east_api.error then + east_api.err = east_api.error + east_api.error = nil + east_api.err.msg = east_api.err.message + east_api.err.message = nil + end + if east_api.ast then + east_api.ast = {simplify_east_api_node(line, east_api.ast)} + if #east_api.ast == 0 then + east_api.ast = nil + end + end + if east_api.len == #line then + east_api.len = nil + end + return east_api + end + local function simplify_east_hl(line, east_hl) + for i, v in ipairs(east_hl) do + east_hl[i] = ('%s:%u:%u:%s'):format( + v[4], + v[1], + v[2], + line:sub(v[2] + 1, v[3])) + end + return east_hl + end + local FLAGS_TO_STR = { + [0] = "", + [1] = "m", + [2] = "E", + [3] = "mE", + [4] = "l", + [5] = "lm", + [6] = "lE", + [7] = "lmE", + } + local function _check_parsing(opts, str, exp_ast, exp_highlighting_fs, + nz_flags_exps) + if type(str) ~= 'string' then + return + end + local zflags = opts.flags[1] + nz_flags_exps = nz_flags_exps or {} + for _, flags in ipairs(opts.flags) do + local err, msg = pcall(function() + local east_api = meths.parse_expression(str, FLAGS_TO_STR[flags], true) + local east_hl = east_api.highlight + east_api.highlight = nil + local ast = simplify_east_api(str, east_api) + local hls = simplify_east_hl(str, east_hl) + local exps = { + ast = exp_ast, + hl_fs = exp_highlighting_fs, + } + local add_exps = nz_flags_exps[flags] + if not add_exps and flags == 3 + zflags then + add_exps = nz_flags_exps[1 + zflags] or nz_flags_exps[2 + zflags] + end + if add_exps then + if add_exps.ast then + exps.ast = mergedicts_copy(exps.ast, add_exps.ast) + end + if add_exps.hl_fs then + exps.hl_fs = mergedicts_copy(exps.hl_fs, add_exps.hl_fs) + end + end + eq(exps.ast, ast) + if exp_highlighting_fs then + local exp_highlighting = {} + local next_col = 0 + for i, h in ipairs(exps.hl_fs) do + exp_highlighting[i], next_col = h(next_col) + end + eq(exp_highlighting, hls) + end + end) + if not err then + if type(msg) == 'table' then + local merr, new_msg = pcall( + format_string, 'table error:\n%s\n\n(%r)', msg.message, msg) + if merr then + msg = new_msg + else + msg = format_string('table error without .message:\n(%r)', + msg) + end + elseif type(msg) ~= 'string' then + msg = format_string('non-string non-table error:\n%r', msg) + end + error(format_string('Error while processing test (%r, %s):\n%s', + str, FLAGS_TO_STR[flags], msg)) + end + end + end + local function hl(group, str, shift) + return function(next_col) + local col = next_col + (shift or 0) + return (('%s:%u:%u:%s'):format( + 'Nvim' .. group, + 0, + col, + str)), (col + #str) + end + end + local function fmtn(typ, args, rest) + if (typ == 'UnknownFigure' + or typ == 'DictLiteral' + or typ == 'CurlyBracesIdentifier' + or typ == 'Lambda') then + return ('%s%s'):format(typ, rest) + elseif typ == 'DoubleQuotedString' or typ == 'SingleQuotedString' then + if args:sub(-4) == 'NULL' then + args = args:sub(1, -5) .. '""' + end + return ('%s(%s)%s'):format(typ, args, rest) + end + end + assert:set_parameter('TableFormatLevel', 1000000) + require('test.unit.viml.expressions.parser_tests')( + it, _check_parsing, hl, fmtn) + end) + + describe('nvim_list_uis', function() + it('returns empty if --headless', function() + -- --embed implies --headless. + eq({}, nvim("list_uis")) + end) + it('returns attached UIs', function() + local screen = Screen.new(20, 4) + screen:attach() + local expected = { + { + ext_cmdline = false, + ext_popupmenu = false, + ext_tabline = false, + ext_wildmenu = false, + height = 4, + rgb = true, + width = 20, + } + } + eq(expected, nvim("list_uis")) + + screen:detach() + screen = Screen.new(44, 99) + screen:attach({ rgb = false }) + expected = { + { + ext_cmdline = false, + ext_popupmenu = false, + ext_tabline = false, + ext_wildmenu = false, + height = 99, + rgb = false, + width = 44, + } + } + eq(expected, nvim("list_uis")) + end) + end) + end) diff --git a/test/functional/autocmd/bufenter_spec.lua b/test/functional/autocmd/bufenter_spec.lua index fef9838050..e14ddb3316 100644 --- a/test/functional/autocmd/bufenter_spec.lua +++ b/test/functional/autocmd/bufenter_spec.lua @@ -31,4 +31,17 @@ describe('autocmd BufEnter', function() eq(1, eval("exists('g:dir_bufenter')")) -- Did BufEnter for the directory. eq(2, eval("bufnr('%')")) -- Switched to the dir buffer. end) + + it('triggered by ":split normal|:help|:bw"', function() + command("split normal") + command("wincmd j") + command("helptags runtime/doc") + command("help") + command("wincmd L") + command("autocmd BufEnter normal let g:bufentered = 1") + command("bw") + eq(1, eval('bufnr("%")')) -- The cursor is back to the bottom window + eq(0, eval("exists('g:bufentered')")) -- The autocmd hasn't been triggered + end) + end) diff --git a/test/functional/autocmd/cmdline_spec.lua b/test/functional/autocmd/cmdline_spec.lua index 8d56687f7d..3f0504d02f 100644 --- a/test/functional/autocmd/cmdline_spec.lua +++ b/test/functional/autocmd/cmdline_spec.lua @@ -5,7 +5,7 @@ local clear = helpers.clear local command = helpers.command local eq = helpers.eq local expect = helpers.expect -local next_msg = helpers.next_message +local next_msg = helpers.next_msg local feed = helpers.feed local meths = helpers.meths @@ -59,24 +59,25 @@ describe('cmdline autocommands', function() [1] = {bold = true, foreground = Screen.colors.Blue1}, [2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, [3] = {bold = true, foreground = Screen.colors.SeaGreen4}, + [4] = {bold = true, reverse = true}, }) command("autocmd CmdlineEnter * echoerr 'FAIL'") command("autocmd CmdlineLeave * echoerr 'very error'") feed(':') screen:expect([[ + | {1:~ }| {1:~ }| {1:~ }| - {1:~ }| - {1:~ }| + {4: }| : | {2:E5500: autocmd has thrown an exception: Vim(echoerr):FAIL} | :^ | ]]) feed("put ='lorem ipsum'<cr>") screen:expect([[ - {1:~ }| - {1:~ }| + | + {4: }| : | {2:E5500: autocmd has thrown an exception: Vim(echoerr):FAIL} | :put ='lorem ipsum' | diff --git a/test/functional/autocmd/dirchanged_spec.lua b/test/functional/autocmd/dirchanged_spec.lua index 63cf0bc410..7979e91a4c 100644 --- a/test/functional/autocmd/dirchanged_spec.lua +++ b/test/functional/autocmd/dirchanged_spec.lua @@ -154,4 +154,11 @@ describe('autocmd DirChanged', function() eq('Failed to change directory', string.match(err, ': (.*)')) eq({cwd=dirs[2], scope='global'}, eval('g:ev')) end) + + it('works when local to buffer', function() + command('let g:triggered = 0') + command('autocmd DirChanged <buffer> let g:triggered = 1') + command('cd '..dirs[1]) + eq(1, eval('g:triggered')) + end) end) diff --git a/test/functional/autocmd/filetype_spec.lua b/test/functional/autocmd/filetype_spec.lua new file mode 100644 index 0000000000..e6fa7ab6bb --- /dev/null +++ b/test/functional/autocmd/filetype_spec.lua @@ -0,0 +1,16 @@ +local helpers = require('test.functional.helpers')(after_each) + +local eval = helpers.eval +local clear = helpers.clear +local command = helpers.command + +describe('autocmd FileType', function() + before_each(clear) + + it("is triggered by :help only once", function() + command("let g:foo = 0") + command("autocmd FileType help let g:foo = g:foo + 1") + command("help help") + assert.same(1, eval('g:foo')) + end) +end) diff --git a/test/functional/autocmd/tabclose_spec.lua b/test/functional/autocmd/tabclose_spec.lua index 1431c69589..b7c33dc3d8 100644 --- a/test/functional/autocmd/tabclose_spec.lua +++ b/test/functional/autocmd/tabclose_spec.lua @@ -2,32 +2,67 @@ local helpers = require('test.functional.helpers')(after_each) local clear, nvim, eq = helpers.clear, helpers.nvim, helpers.eq describe('TabClosed', function() - describe('au TabClosed', function() - describe('with * as <afile>', function() - it('matches when closing any tab', function() - clear() - nvim('command', 'au! TabClosed * echom "tabclosed:".expand("<afile>").":".expand("<amatch>").":".tabpagenr()') - repeat - nvim('command', 'tabnew') - until nvim('eval', 'tabpagenr()') == 6 -- current tab is now 6 - eq("\ntabclosed:6:6:5", nvim('command_output', 'tabclose')) -- close last 6, current tab is now 5 - eq("\ntabclosed:5:5:4", nvim('command_output', 'close')) -- close last window on tab, closes tab - eq("\ntabclosed:2:2:3", nvim('command_output', '2tabclose')) -- close tab 2, current tab is now 3 - eq("\ntabclosed:1:1:2\ntabclosed:1:1:1", nvim('command_output', 'tabonly')) -- close tabs 1 and 2 - end) - end) - describe('with NR as <afile>', function() - it('matches when closing a tab whose index is NR', function() - nvim('command', 'au! TabClosed 2 echom "tabclosed:match"') - repeat - nvim('command', 'tabnew') - until nvim('eval', 'tabpagenr()') == 5 -- current tab is now 5 - -- sanity check, we shouldn't match on tabs with numbers other than 2 - eq("\ntabclosed:5:5:4", nvim('command_output', 'tabclose')) - -- close tab page 2, current tab is now 3 - eq("\ntabclosed:2:2:3\ntabclosed:match", nvim('command_output', '2tabclose')) - end) - end) + before_each(clear) + + describe('au TabClosed', function() + describe('with * as <afile>', function() + it('matches when closing any tab', function() + nvim('command', 'au! TabClosed * echom "tabclosed:".expand("<afile>").":".expand("<amatch>").":".tabpagenr()') + repeat + nvim('command', 'tabnew') + until nvim('eval', 'tabpagenr()') == 6 -- current tab is now 6 + eq("tabclosed:6:6:5", nvim('command_output', 'tabclose')) -- close last 6, current tab is now 5 + eq("tabclosed:5:5:4", nvim('command_output', 'close')) -- close last window on tab, closes tab + eq("tabclosed:2:2:3", nvim('command_output', '2tabclose')) -- close tab 2, current tab is now 3 + eq("tabclosed:1:1:2\ntabclosed:1:1:1", nvim('command_output', 'tabonly')) -- close tabs 1 and 2 + end) + + it('is triggered when closing a window via bdelete from another tab', function() + nvim('command', 'au! TabClosed * echom "tabclosed:".expand("<afile>").":".expand("<amatch>").":".tabpagenr()') + nvim('command', '1tabedit Xtestfile') + nvim('command', '1tabedit Xtestfile') + nvim('command', 'normal! 1gt') + eq({1, 3}, nvim('eval', '[tabpagenr(), tabpagenr("$")]')) + eq("tabclosed:2:2:1\ntabclosed:2:2:1", nvim('command_output', 'bdelete Xtestfile')) + eq({1, 1}, nvim('eval', '[tabpagenr(), tabpagenr("$")]')) + end) + + it('is triggered when closing a window via bdelete from current tab', function() + nvim('command', 'au! TabClosed * echom "tabclosed:".expand("<afile>").":".expand("<amatch>").":".tabpagenr()') + nvim('command', 'file Xtestfile1') + nvim('command', '1tabedit Xtestfile2') + nvim('command', '1tabedit Xtestfile2') + + -- Only one tab is closed, and the alternate file is used for the other. + eq({2, 3}, nvim('eval', '[tabpagenr(), tabpagenr("$")]')) + eq("tabclosed:2:2:2", nvim('command_output', 'bdelete Xtestfile2')) + eq('Xtestfile1', nvim('eval', 'bufname("")')) + end) + end) + + describe('with NR as <afile>', function() + it('matches when closing a tab whose index is NR', function() + nvim('command', 'au! TabClosed * echom "tabclosed:".expand("<afile>").":".expand("<amatch>").":".tabpagenr()') + nvim('command', 'au! TabClosed 2 echom "tabclosed:match"') + repeat + nvim('command', 'tabnew') + until nvim('eval', 'tabpagenr()') == 7 -- current tab is now 7 + -- sanity check, we shouldn't match on tabs with numbers other than 2 + eq("tabclosed:7:7:6", nvim('command_output', 'tabclose')) + -- close tab page 2, current tab is now 5 + eq("tabclosed:2:2:5\ntabclosed:match", nvim('command_output', '2tabclose')) + end) + end) + + describe('with close', function() + it('is triggered', function() + nvim('command', 'au! TabClosed * echom "tabclosed:".expand("<afile>").":".expand("<amatch>").":".tabpagenr()') + nvim('command', 'tabedit Xtestfile') + eq({2, 2}, nvim('eval', '[tabpagenr(), tabpagenr("$")]')) + eq("tabclosed:2:2:1", nvim('command_output', 'close')) + eq({1, 1}, nvim('eval', '[tabpagenr(), tabpagenr("$")]')) + end) end) + end) end) diff --git a/test/functional/autocmd/tabnewentered_spec.lua b/test/functional/autocmd/tabnewentered_spec.lua index bdbe677132..59cac07b34 100644 --- a/test/functional/autocmd/tabnewentered_spec.lua +++ b/test/functional/autocmd/tabnewentered_spec.lua @@ -7,14 +7,14 @@ describe('TabNewEntered', function() it('matches when entering any new tab', function() clear() nvim('command', 'au! TabNewEntered * echom "tabnewentered:".tabpagenr().":".bufnr("")') - eq("\ntabnewentered:2:2", nvim('command_output', 'tabnew')) - eq("\n\"test.x2\" [New File]\ntabnewentered:3:3", nvim('command_output', 'tabnew test.x2')) + eq("tabnewentered:2:2", nvim('command_output', 'tabnew')) + eq("tabnewentered:3:3", nvim('command_output', 'tabnew test.x2')) end) end) describe('with FILE as <afile>', function() it('matches when opening a new tab for FILE', function() nvim('command', 'au! TabNewEntered Xtest-tabnewentered echom "tabnewentered:match"') - eq('\n"Xtest-tabnewentered" [New File]\ntabnewentered:4:4\ntabnewentered:match', + eq('tabnewentered:4:4\ntabnewentered:match', nvim('command_output', 'tabnew Xtest-tabnewentered')) end) end) @@ -24,7 +24,7 @@ describe('TabNewEntered', function() nvim('command', 'au! TabNewEntered * echom "entered"') nvim('command', 'tabnew test.x2') nvim('command', 'split') - eq('\nentered', nvim('command_output', 'execute "normal \\<C-W>T"')) + eq('entered', nvim('command_output', 'execute "normal \\<C-W>T"')) end) end) end) diff --git a/test/functional/autocmd/termclose_spec.lua b/test/functional/autocmd/termclose_spec.lua index c6c30494dd..0804579a4f 100644 --- a/test/functional/autocmd/termclose_spec.lua +++ b/test/functional/autocmd/termclose_spec.lua @@ -1,11 +1,13 @@ +local luv = require('luv') local helpers = require('test.functional.helpers')(after_each) local clear, command, nvim, nvim_dir = helpers.clear, helpers.command, helpers.nvim, helpers.nvim_dir local eval, eq, retry = helpers.eval, helpers.eq, helpers.retry +local ok = helpers.ok +local iswin = helpers.iswin -if helpers.pending_win32(pending) then return end describe('TermClose event', function() before_each(function() @@ -22,7 +24,7 @@ describe('TermClose event', function() end) it('triggers when long-running terminal job gets stopped', function() - nvim('set_option', 'shell', 'sh') + nvim('set_option', 'shell', iswin() and 'cmd.exe' or 'sh') command('autocmd TermClose * let g:test_termclose = 23') command('terminal') command('call jobstop(b:terminal_job_id)') @@ -30,6 +32,7 @@ describe('TermClose event', function() end) it('kills job trapping SIGTERM', function() + if helpers.pending_win32(pending) then return end nvim('set_option', 'shell', 'sh') nvim('set_option', 'shellcmdflag', '-c') command([[ let g:test_job = jobstart('trap "" TERM && echo 1 && sleep 60', { ]] @@ -37,14 +40,19 @@ describe('TermClose event', function() .. [[ 'on_exit': {-> execute('let g:test_job_exited = 1')}}) ]]) retry(nil, nil, function() eq(1, eval('get(g:, "test_job_started", 0)')) end) - local start = os.time() + luv.update_time() + local start = luv.now() command('call jobstop(g:test_job)') retry(nil, nil, function() eq(1, eval('get(g:, "test_job_exited", 0)')) end) - local duration = os.time() - start - eq(2, duration) + luv.update_time() + local duration = luv.now() - start + -- Nvim begins SIGTERM after KILL_TIMEOUT_MS. + ok(duration >= 2000) + ok(duration <= 4000) -- Epsilon for slow CI end) it('kills pty job trapping SIGHUP and SIGTERM', function() + if helpers.pending_win32(pending) then return end nvim('set_option', 'shell', 'sh') nvim('set_option', 'shellcmdflag', '-c') command([[ let g:test_job = jobstart('trap "" HUP TERM && echo 1 && sleep 60', { ]] @@ -53,13 +61,15 @@ describe('TermClose event', function() .. [[ 'on_exit': {-> execute('let g:test_job_exited = 1')}}) ]]) retry(nil, nil, function() eq(1, eval('get(g:, "test_job_started", 0)')) end) - local start = os.time() + luv.update_time() + local start = luv.now() command('call jobstop(g:test_job)') retry(nil, nil, function() eq(1, eval('get(g:, "test_job_exited", 0)')) end) - local duration = os.time() - start - -- nvim starts sending kill after 2*KILL_TIMEOUT_MS - helpers.ok(4 <= duration) - helpers.ok(duration <= 7) -- <= 4 + delta because of slow CI + luv.update_time() + local duration = luv.now() - start + -- Nvim begins SIGKILL after (2 * KILL_TIMEOUT_MS). + ok(duration >= 4000) + ok(duration <= 7000) -- Epsilon for slow CI end) it('reports the correct <abuf>', function() diff --git a/test/functional/clipboard/clipboard_provider_spec.lua b/test/functional/clipboard/clipboard_provider_spec.lua index a3ea3b568f..a40c080a6d 100644 --- a/test/functional/clipboard/clipboard_provider_spec.lua +++ b/test/functional/clipboard/clipboard_provider_spec.lua @@ -83,7 +83,14 @@ local function basic_register_test(noblock) end describe('clipboard', function() - before_each(clear) + local screen + + before_each(function() + clear() + screen = Screen.new(72, 4) + screen:attach() + command("set display-=msgsep") + end) it('unnamed register works without provider', function() eq('"', eval('v:register')) @@ -92,8 +99,6 @@ describe('clipboard', function() it('`:redir @+>` with invalid g:clipboard shows exactly one error #7184', function() - local screen = Screen.new(72, 4) - screen:attach() command("let g:clipboard = 'bogus'") feed_command('redir @+> | :silent echo system("cat CONTRIBUTING.md") | redir END') screen:expect([[ @@ -106,8 +111,6 @@ describe('clipboard', function() it('`:redir @+>|bogus_cmd|redir END` + invalid g:clipboard must not recurse #7184', function() - local screen = Screen.new(72, 4) - screen:attach() command("let g:clipboard = 'bogus'") feed_command('redir @+> | bogus_cmd | redir END') screen:expect([[ @@ -123,8 +126,6 @@ describe('clipboard', function() eq('', eval('provider#clipboard#Executable()')) eq('clipboard: invalid g:clipboard', eval('provider#clipboard#Error()')) - local screen = Screen.new(72, 4) - screen:attach() command("let g:clipboard = 'bogus'") -- Explicit clipboard attempt, should show a hint message. feed_command('let @+="foo"') @@ -493,10 +494,10 @@ describe('clipboard', function() feed_command("let g:test_clip['+'] = ['such', 'plus', 'stuff']") feed_command("registers") screen:expect([[ - ~ | - ~ | - ~ | - ~ | + | + {0:~ }| + {0:~ }| + {4: }| :registers | {1:--- Registers ---} | "* some{2:^J}star data{2:^J} | @@ -504,10 +505,11 @@ describe('clipboard', function() ": let g:test_clip['+'] = ['such', 'plus', 'stuff'] | {3:Press ENTER or type command to continue}^ | ]], { + [0] = {bold = true, foreground = Screen.colors.Blue}, [1] = {bold = true, foreground = Screen.colors.Fuchsia}, [2] = {foreground = Screen.colors.Blue}, - [3] = {bold = true, foreground = Screen.colors.SeaGreen}}, - {{bold = true, foreground = Screen.colors.Blue}}) + [3] = {bold = true, foreground = Screen.colors.SeaGreen}, + [4] = {bold = true, reverse = true}}) feed('<cr>') -- clear out of Press ENTER screen end) diff --git a/test/functional/core/channels_spec.lua b/test/functional/core/channels_spec.lua index 765e3c5919..f79f208b69 100644 --- a/test/functional/core/channels_spec.lua +++ b/test/functional/core/channels_spec.lua @@ -1,7 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear, eq, eval, next_msg, ok, source = helpers.clear, helpers.eq, - helpers.eval, helpers.next_message, helpers.ok, helpers.source + helpers.eval, helpers.next_msg, helpers.ok, helpers.source local command, funcs, meths = helpers.command, helpers.funcs, helpers.meths local sleep = helpers.sleep local spawn, nvim_argv = helpers.spawn, helpers.nvim_argv @@ -246,6 +246,22 @@ describe('channels', function() eq({"notification", "exit", {id, 0, {'10 PRINT "NVIM"', '20 GOTO 10', ''}}}, next_msg()) + -- if dict is reused the new value is not stored, + -- but nvim also does not crash + command("let id = jobstart(['cat'], g:job_opts)") + id = eval("g:id") + + command([[call chansend(id, "cat text\n")]]) + sleep(10) + command("call chanclose(id, 'stdin')") + + -- old value was not overwritten + eq({"notification", "exit", {id, 0, {'10 PRINT "NVIM"', + '20 GOTO 10', ''}}}, next_msg()) + + -- and an error was thrown. + eq("E5210: dict key 'stdout' already set for buffered stream in channel "..id, eval('v:errmsg')) + -- reset dictionary source([[ let g:job_opts = { @@ -261,6 +277,5 @@ describe('channels', function() -- works correctly with no output eq({"notification", "exit", {id, 1, {''}}}, next_msg()) - end) end) diff --git a/test/functional/core/exit_spec.lua b/test/functional/core/exit_spec.lua index 188a6a2c11..80c65e4544 100644 --- a/test/functional/core/exit_spec.lua +++ b/test/functional/core/exit_spec.lua @@ -1,6 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) local command = helpers.command +local feed_command = helpers.feed_command local eval = helpers.eval local eq = helpers.eq local run = helpers.run @@ -33,6 +34,18 @@ describe('v:exiting', function() end run(on_request, nil, on_setup) end) + it('is 0 on exit from ex-mode involving try-catch', function() + local function on_setup() + command('autocmd VimLeavePre * call rpcrequest('..cid..', "")') + command('autocmd VimLeave * call rpcrequest('..cid..', "")') + feed_command('call feedkey("Q")','try', 'call NoFunction()', 'catch', 'echo "bye"', 'endtry', 'quit') + end + local function on_request() + eq(0, eval('v:exiting')) + return '' + end + run(on_request, nil, on_setup) + end) end) describe(':cquit', function() diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index e957650c88..24bff423df 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -2,15 +2,21 @@ local helpers = require('test.functional.helpers')(after_each) local clear, eq, eval, exc_exec, feed_command, feed, insert, neq, next_msg, nvim, nvim_dir, ok, source, write_file, mkdir, rmdir = helpers.clear, helpers.eq, helpers.eval, helpers.exc_exec, helpers.feed_command, helpers.feed, - helpers.insert, helpers.neq, helpers.next_message, helpers.nvim, + helpers.insert, helpers.neq, helpers.next_msg, helpers.nvim, helpers.nvim_dir, helpers.ok, helpers.source, helpers.write_file, helpers.mkdir, helpers.rmdir local command = helpers.command +local funcs = helpers.funcs +local retry = helpers.retry +local meths = helpers.meths +local NIL = helpers.NIL local wait = helpers.wait local iswin = helpers.iswin local get_pathsep = helpers.get_pathsep +local pathroot = helpers.pathroot local nvim_set = helpers.nvim_set local expect_twostreams = helpers.expect_twostreams +local expect_msg_seq = helpers.expect_msg_seq local Screen = require('test.functional.ui.screen') describe('jobs', function() @@ -58,12 +64,12 @@ describe('jobs', function() it('changes to given / directory', function() nvim('command', "let g:job_opts.cwd = '/'") if iswin() then - nvim('command', "let j = jobstart('pwd|%{$_.Path}', g:job_opts)") + nvim('command', "let j = jobstart('(Get-Location).Path', g:job_opts)") else nvim('command', "let j = jobstart('pwd', g:job_opts)") end eq({'notification', 'stdout', - {0, {(iswin() and [[C:\]] or '/'), ''}}}, next_msg()) + {0, {pathroot(), ''}}}, next_msg()) eq({'notification', 'stdout', {0, {''}}}, next_msg()) eq({'notification', 'exit', {0, 0}}, next_msg()) end) @@ -73,13 +79,22 @@ describe('jobs', function() mkdir(dir) nvim('command', "let g:job_opts.cwd = '" .. dir .. "'") if iswin() then - nvim('command', "let j = jobstart('pwd|%{$_.Path}', g:job_opts)") + nvim('command', "let j = jobstart('(Get-Location).Path', g:job_opts)") else nvim('command', "let j = jobstart('pwd', g:job_opts)") end - eq({'notification', 'stdout', {0, {dir, ''}}}, next_msg()) - eq({'notification', 'stdout', {0, {''}}}, next_msg()) - eq({'notification', 'exit', {0, 0}}, next_msg()) + expect_msg_seq( + { {'notification', 'stdout', {0, {dir, ''} } }, + {'notification', 'stdout', {0, {''} } }, + {'notification', 'exit', {0, 0} } + }, + -- Alternative sequence: + { {'notification', 'stdout', {0, {dir} } }, + {'notification', 'stdout', {0, {'', ''} } }, + {'notification', 'stdout', {0, {''} } }, + {'notification', 'exit', {0, 0} } + } + ) rmdir(dir) end) @@ -104,13 +119,13 @@ describe('jobs', function() end) it('returns -1 when target is not executable #5465', function() - if helpers.pending_win32(pending) then return end local function new_job() return eval([[jobstart('')]]) end local executable_jobid = new_job() - local nonexecutable_jobid = eval( - "jobstart(['./test/functional/fixtures/non_executable.txt'])") + local nonexecutable_jobid = eval("jobstart(['"..(iswin() + and './test/functional/fixtures' + or './test/functional/fixtures/non_executable.txt').."'])") eq(-1, nonexecutable_jobid) -- Should _not_ throw an error. eq("", eval("v:errmsg")) @@ -122,11 +137,10 @@ describe('jobs', function() -- TODO: hangs on Windows if helpers.pending_win32(pending) then return end nvim('command', "let g:job_opts.on_stderr = function('OnEvent')") - nvim('command', "call jobstart('echo', g:job_opts)") + nvim('command', [[call jobstart('echo ""', g:job_opts)]]) expect_twostreams({{'notification', 'stdout', {0, {'', ''}}}, {'notification', 'stdout', {0, {''}}}}, {{'notification', 'stderr', {0, {''}}}}) - eq({'notification', 'exit', {0, 0}}, next_msg()) end) @@ -242,7 +256,6 @@ describe('jobs', function() end) it('will not leak memory if we leave a job running', function() - if helpers.pending_win32(pending) then return end -- TODO: Need `cat`. nvim('command', "call jobstart(['cat', '-'], g:job_opts)") end) @@ -283,19 +296,19 @@ describe('jobs', function() nvim('command', 'let g:job_opts.user = {"n": 5, "s": "str", "l": [1]}') nvim('command', [[call jobstart('echo "foo"', g:job_opts)]]) local data = {n = 5, s = 'str', l = {1}} - eq({'notification', 'stdout', {data, {'foo', ''}}}, next_msg()) - eq({'notification', 'stdout', {data, {''}}}, next_msg()) + expect_msg_seq( + { {'notification', 'stdout', {data, {'foo', ''}}}, + {'notification', 'stdout', {data, {''}}}, + }, + -- Alternative sequence: + { {'notification', 'stdout', {data, {'foo'}}}, + {'notification', 'stdout', {data, {'', ''}}}, + {'notification', 'stdout', {data, {''}}}, + } + ) eq({'notification', 'exit', {data, 0}}, next_msg()) end) - it('can omit options', function() - if helpers.pending_win32(pending) then return end - neq(0, nvim('eval', 'delete(".Xtestjob")')) - nvim('command', "call jobstart(['touch', '.Xtestjob'])") - nvim('command', "sleep 100m") - eq(0, nvim('eval', 'delete(".Xtestjob")')) - end) - it('can omit data callbacks', function() nvim('command', 'unlet g:job_opts.on_stdout') nvim('command', 'let g:job_opts.user = 5') @@ -307,8 +320,16 @@ describe('jobs', function() nvim('command', 'unlet g:job_opts.on_exit') nvim('command', 'let g:job_opts.user = 5') nvim('command', [[call jobstart('echo "foo"', g:job_opts)]]) - eq({'notification', 'stdout', {5, {'foo', ''}}}, next_msg()) - eq({'notification', 'stdout', {5, {''}}}, next_msg()) + expect_msg_seq( + { {'notification', 'stdout', {5, {'foo', ''} } }, + {'notification', 'stdout', {5, {''} } }, + }, + -- Alternative sequence: + { {'notification', 'stdout', {5, {'foo'} } }, + {'notification', 'stdout', {5, {'', ''} } }, + {'notification', 'stdout', {5, {''} } }, + } + ) end) it('will pass return code with the exit event', function() @@ -330,7 +351,6 @@ describe('jobs', function() end) it('can redefine callbacks being used by a job', function() - if helpers.pending_win32(pending) then return end -- TODO: Need `cat`. local screen = Screen.new() screen:attach() screen:set_default_attr_ids({ @@ -345,7 +365,7 @@ describe('jobs', function() \ 'on_stderr': function('g:JobHandler'), \ 'on_exit': function('g:JobHandler') \ } - let job = jobstart('cat -', g:callbacks) + let job = jobstart(['cat', '-'], g:callbacks) ]]) wait() source([[ @@ -410,7 +430,14 @@ describe('jobs', function() let g:job_opts = {'on_stdout': Callback} call jobstart('echo "some text"', g:job_opts) ]]) - eq({'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, next_msg()) + expect_msg_seq( + { {'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, + }, + -- Alternative sequence: + { {'notification', '1', {'foo', 'bar', {'some text'}, 'stdout'}}, + {'notification', '1', {'foo', 'bar', {'', ''}, 'stdout'}}, + } + ) end) it('jobstart() works with closures', function() @@ -423,7 +450,14 @@ describe('jobs', function() let g:job_opts = {'on_stdout': MkFun()} call jobstart('echo "some text"', g:job_opts) ]]) - eq({'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, next_msg()) + expect_msg_seq( + { {'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, + }, + -- Alternative sequence: + { {'notification', '1', {'foo', 'bar', {'some text'}, 'stdout'}}, + {'notification', '1', {'foo', 'bar', {'', ''}, 'stdout'}}, + } + ) end) it('jobstart() works when closure passed directly to `jobstart`', function() @@ -431,7 +465,14 @@ describe('jobs', function() let g:job_opts = {'on_stdout': {id, data, event -> rpcnotify(g:channel, '1', 'foo', 'bar', Normalize(data), event)}} call jobstart('echo "some text"', g:job_opts) ]]) - eq({'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, next_msg()) + expect_msg_seq( + { {'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, + }, + -- Alternative sequence: + { {'notification', '1', {'foo', 'bar', {'some text'}, 'stdout'}}, + {'notification', '1', {'foo', 'bar', {'', ''}, 'stdout'}}, + } + ) end) describe('jobwait', function() @@ -490,7 +531,7 @@ describe('jobs', function() eq({'notification', 'wait', {{-3, 5}}}, next_msg()) end) - it('will return -2 when interrupted', function() + it('will return -2 when interrupted without timeout', function() feed_command('call rpcnotify(g:channel, "ready") | '.. 'call rpcnotify(g:channel, "wait", '.. 'jobwait([jobstart("sleep 10; exit 55")]))') @@ -499,6 +540,15 @@ describe('jobs', function() eq({'notification', 'wait', {{-2}}}, next_msg()) end) + it('will return -2 when interrupted with timeout', function() + feed_command('call rpcnotify(g:channel, "ready") | '.. + 'call rpcnotify(g:channel, "wait", '.. + 'jobwait([jobstart("sleep 10; exit 55")], 10000))') + eq({'notification', 'ready', {}}, next_msg()) + feed('<c-c>') + eq({'notification', 'wait', {{-2}}}, next_msg()) + end) + it('can be called recursively', function() if helpers.pending_win32(pending) then return end -- TODO: Need `cat`. source([[ @@ -590,6 +640,57 @@ describe('jobs', function() ok(string.find(err, "E475: Invalid argument: job cannot have both 'pty' and 'rpc' options set") ~= nil) end) + it('does not crash when repeatedly failing to start shell', function() + source([[ + set shell=nosuchshell + func! DoIt() + call jobstart('true') + call jobstart('true') + endfunc + ]]) + -- The crash only triggered if both jobs are cleaned up on the same event + -- loop tick. This is also prevented by try-block, so feed must be used. + feed_command("call DoIt()") + feed('<cr>') -- press RETURN + eq(2,eval('1+1')) + end) + + it('jobstop() kills entire process tree #6530', function() + command('set shell& shellcmdflag& shellquote& shellpipe& shellredir& shellxquote&') + + -- XXX: Using `nvim` isn't a good test, it reaps its children on exit. + -- local c = 'call jobstart([v:progpath, "-u", "NONE", "-i", "NONE", "--headless"])' + -- local j = eval("jobstart([v:progpath, '-u', 'NONE', '-i', 'NONE', '--headless', '-c', '" + -- ..c.."', '-c', '"..c.."'])") + + -- Create child with several descendants. + local j = (iswin() + and eval([=[jobstart('start /b cmd /c "ping 127.0.0.1 -n 1 -w 30000 > NUL"]=] + ..[=[ & start /b cmd /c "ping 127.0.0.1 -n 1 -w 40000 > NUL"]=] + ..[=[ & start /b cmd /c "ping 127.0.0.1 -n 1 -w 50000 > NUL"')]=]) + or eval("jobstart('sleep 30 | sleep 30 | sleep 30')")) + local ppid = funcs.jobpid(j) + local children + retry(nil, nil, function() + children = meths.get_proc_children(ppid) + eq(3, #children) + end) + -- Assert that nvim_get_proc() sees the children. + for _, child_pid in ipairs(children) do + local info = meths.get_proc(child_pid) + -- eq((iswin() and 'nvim.exe' or 'nvim'), info.name) + eq(ppid, info.ppid) + end + -- Kill the root of the tree. + funcs.jobstop(j) + -- Assert that the children were killed. + retry(nil, nil, function() + for _, child_pid in ipairs(children) do + eq(NIL, meths.get_proc(child_pid)) + end + end) + end) + describe('running tty-test program', function() if helpers.pending_win32(pending) then return end local function next_chunk() diff --git a/test/functional/eval/api_functions_spec.lua b/test/functional/eval/api_functions_spec.lua index fea4a87a26..6f440c7d82 100644 --- a/test/functional/eval/api_functions_spec.lua +++ b/test/functional/eval/api_functions_spec.lua @@ -106,7 +106,8 @@ describe('api functions', function() it('have metadata accessible with api_info()', function() local api_keys = eval("sort(keys(api_info()))") - eq({'error_types', 'functions', 'types', 'ui_events', 'version'}, api_keys) + eq({'error_types', 'functions', 'types', + 'ui_events', 'ui_options', 'version'}, api_keys) end) it('are highlighted by vim.vim syntax file', function() diff --git a/test/functional/eval/backtick_expansion_spec.lua b/test/functional/eval/backtick_expansion_spec.lua index 81e8e295fa..b1b44cfa8b 100644 --- a/test/functional/eval/backtick_expansion_spec.lua +++ b/test/functional/eval/backtick_expansion_spec.lua @@ -21,11 +21,19 @@ describe("backtick expansion", function() end) it("with default 'shell'", function() - if helpers.pending_win32(pending) then return end -- Need win32 shell fixes - command(":silent args `echo ***2`") + if helpers.iswin() then + command(":silent args `dir /b *2`") + else + command(":silent args `echo ***2`") + end eq({ "file2", }, eval("argv()")) - command(":silent args `echo */*4`") - eq({ "subdir/file4", }, eval("argv()")) + if helpers.iswin() then + command(":silent args `dir /s/b *4`") + eq({ "subdir\\file4", }, eval("map(argv(), 'fnamemodify(v:val, \":.\")')")) + else + command(":silent args `echo */*4`") + eq({ "subdir/file4", }, eval("argv()")) + end end) it("with shell=fish", function() diff --git a/test/functional/eval/buf_functions_spec.lua b/test/functional/eval/buf_functions_spec.lua index db50874c53..7de58766b9 100644 --- a/test/functional/eval/buf_functions_spec.lua +++ b/test/functional/eval/buf_functions_spec.lua @@ -14,6 +14,7 @@ local curbufmeths = helpers.curbufmeths local curwinmeths = helpers.curwinmeths local curtabmeths = helpers.curtabmeths local get_pathsep = helpers.get_pathsep +local rmdir = helpers.rmdir local fname = 'Xtest-functional-eval-buf_functions' local fname2 = fname .. '.2' @@ -61,7 +62,7 @@ describe('bufname() function', function() lfs.mkdir(dirname) end) after_each(function() - lfs.rmdir(dirname) + rmdir(dirname) end) it('returns expected buffer name', function() eq('', funcs.bufname('%')) -- Buffer has no name yet @@ -143,7 +144,7 @@ describe('bufwinnr() function', function() lfs.mkdir(dirname) end) after_each(function() - lfs.rmdir(dirname) + rmdir(dirname) end) it('returns expected window number', function() eq(1, funcs.bufwinnr('%')) diff --git a/test/functional/eval/execute_spec.lua b/test/functional/eval/execute_spec.lua index 91966ed3dd..925e311c7d 100644 --- a/test/functional/eval/execute_spec.lua +++ b/test/functional/eval/execute_spec.lua @@ -9,6 +9,7 @@ local funcs = helpers.funcs local Screen = require('test.functional.ui.screen') local command = helpers.command local feed = helpers.feed +local iswin = helpers.iswin describe('execute()', function() before_each(clear) @@ -105,22 +106,30 @@ describe('execute()', function() end) it('does not corrupt the command display #5422', function() - local screen = Screen.new(70, 5) + local screen = Screen.new(70, 7) screen:attach() feed(':echo execute("hi ErrorMsg")<CR>') screen:expect([[ - ~ | - ~ | + | + {1:~ }| + {1:~ }| + {2: }| :echo execute("hi ErrorMsg") | ErrorMsg xxx ctermfg=15 ctermbg=1 guifg=White guibg=Red | - Press ENTER or type command to continue^ | - ]]) + {3:Press ENTER or type command to continue}^ | + ]], { + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {bold = true, reverse = true}, + [3] = {bold = true, foreground = Screen.colors.SeaGreen4}, + }) feed('<CR>') end) - -- This matches Vim behavior. - it('does not capture shell-command output', function() - eq('\n:!echo "foo"\13\n', funcs.execute('!echo "foo"')) + -- This deviates from vim behavior, but is consistent + -- with how nvim currently displays the output. + it('does capture shell-command output', function() + local win_lf = iswin() and '\13' or '' + eq('\n:!echo foo\r\n\nfoo'..win_lf..'\n', funcs.execute('!echo foo')) end) describe('{silent} argument', function() diff --git a/test/functional/eval/fnamemodify_spec.lua b/test/functional/eval/fnamemodify_spec.lua new file mode 100644 index 0000000000..fe6b50a544 --- /dev/null +++ b/test/functional/eval/fnamemodify_spec.lua @@ -0,0 +1,39 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local eq = helpers.eq +local iswin = helpers.iswin +local fnamemodify = helpers.funcs.fnamemodify +local command = helpers.command +local write_file = helpers.write_file + +describe('fnamemodify()', function() + setup(function() + write_file('Xtest-fnamemodify.txt', [[foobar]]) + end) + + before_each(clear) + + teardown(function() + os.remove('Xtest-fnamemodify.txt') + end) + + it('works', function() + local root = helpers.pathroot() + eq(root, fnamemodify([[/]], ':p:h')) + eq(root, fnamemodify([[/]], ':p')) + if iswin() then + eq(root, fnamemodify([[\]], ':p:h')) + eq(root, fnamemodify([[\]], ':p')) + command('set shellslash') + root = string.sub(root, 1, -2)..'/' + eq(root, fnamemodify([[\]], ':p:h')) + eq(root, fnamemodify([[\]], ':p')) + eq(root, fnamemodify([[/]], ':p:h')) + eq(root, fnamemodify([[/]], ':p')) + end + end) + + it(':8 works', function() + eq('Xtest-fnamemodify.txt', fnamemodify([[Xtest-fnamemodify.txt]], ':8')) + end) +end) diff --git a/test/functional/eval/getline_spec.lua b/test/functional/eval/getline_spec.lua new file mode 100644 index 0000000000..3c56bde094 --- /dev/null +++ b/test/functional/eval/getline_spec.lua @@ -0,0 +1,39 @@ +local helpers = require('test.functional.helpers')(after_each) + +local call = helpers.call +local clear = helpers.clear +local eq = helpers.eq +local expect = helpers.expect + +describe('getline()', function() + before_each(function() + clear() + call('setline', 1, {'a', 'b', 'c'}) + expect([[ + a + b + c]]) + end) + + it('returns empty string for invalid line', function() + eq('', call('getline', -1)) + eq('', call('getline', 0)) + eq('', call('getline', 4)) + end) + + it('returns empty list for invalid range', function() + eq({}, call('getline', 2, 1)) + eq({}, call('getline', -1, 1)) + eq({}, call('getline', 4, 4)) + end) + + it('returns value of valid line', function() + eq('b', call('getline', 2)) + eq('a', call('getline', '.')) + end) + + it('returns value of valid range', function() + eq({'a', 'b'}, call('getline', 1, 2)) + eq({'a', 'b', 'c'}, call('getline', 1, 4)) + end) +end) diff --git a/test/functional/eval/has_spec.lua b/test/functional/eval/has_spec.lua index 78c4e08fde..a3af2d1a20 100644 --- a/test/functional/eval/has_spec.lua +++ b/test/functional/eval/has_spec.lua @@ -57,4 +57,10 @@ describe('has()', function() eq(0, funcs.has("unnamedplus")) end end) + + it('"wsl"', function() + if 1 == funcs.has('win32') or 1 == funcs.has('mac') then + eq(0, funcs.has('wsl')) + end + end) end) diff --git a/test/functional/eval/hostname_spec.lua b/test/functional/eval/hostname_spec.lua index 6d5b64b929..6112cf64e3 100644 --- a/test/functional/eval/hostname_spec.lua +++ b/test/functional/eval/hostname_spec.lua @@ -1,7 +1,9 @@ local helpers = require('test.functional.helpers')(after_each) +local eq = helpers.eq local ok = helpers.ok local call = helpers.call local clear = helpers.clear +local iswin = helpers.iswin describe('hostname()', function() before_each(clear) @@ -11,7 +13,8 @@ describe('hostname()', function() ok(string.len(actual) > 0) if call('executable', 'hostname') == 1 then local expected = string.gsub(call('system', 'hostname'), '[\n\r]', '') - helpers.eq(expected, actual) + eq((iswin() and expected:upper() or expected), + (iswin() and actual:upper() or actual)) end end) end) diff --git a/test/functional/eval/input_spec.lua b/test/functional/eval/input_spec.lua index 1e6b107c60..777f49462d 100644 --- a/test/functional/eval/input_spec.lua +++ b/test/functional/eval/input_spec.lua @@ -58,6 +58,7 @@ before_each(function() RBP2={background=Screen.colors.Yellow}, RBP3={background=Screen.colors.Green}, RBP4={background=Screen.colors.Blue}, + SEP={bold = true, reverse = true}, }) end) @@ -65,9 +66,9 @@ describe('input()', function() it('works with multiline prompts', function() feed([[:call input("Test\nFoo")<CR>]]) screen:expect([[ + | {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {SEP: }| Test | Foo^ | ]]) @@ -75,9 +76,9 @@ describe('input()', function() it('works with multiline prompts and :echohl', function() feed([[:echohl Test | call input("Test\nFoo")<CR>]]) screen:expect([[ + | {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {SEP: }| {T:Test} | {T:Foo}^ | ]]) @@ -242,17 +243,17 @@ describe('input()', function() it('is not hidden by :silent', function() feed([[:silent call input('Foo: ')<CR>]]) screen:expect([[ + | {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {SEP: }| Foo: ^ | | ]]) feed('Bar') screen:expect([[ + | {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {SEP: }| Foo: Bar^ | | ]]) @@ -263,9 +264,9 @@ describe('inputdialog()', function() it('works with multiline prompts', function() feed([[:call inputdialog("Test\nFoo")<CR>]]) screen:expect([[ + | {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {SEP: }| Test | Foo^ | ]]) @@ -273,9 +274,9 @@ describe('inputdialog()', function() it('works with multiline prompts and :echohl', function() feed([[:echohl Test | call inputdialog("Test\nFoo")<CR>]]) screen:expect([[ + | {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {SEP: }| {T:Test} | {T:Foo}^ | ]]) diff --git a/test/functional/eval/interrupt_spec.lua b/test/functional/eval/interrupt_spec.lua new file mode 100644 index 0000000000..7f4ca95317 --- /dev/null +++ b/test/functional/eval/interrupt_spec.lua @@ -0,0 +1,61 @@ +local helpers = require('test.functional.helpers')(after_each) + +local command = helpers.command +local meths = helpers.meths +local clear = helpers.clear +local sleep = helpers.sleep +local wait = helpers.wait +local feed = helpers.feed +local eq = helpers.eq + +local dur +local min_dur = 8 +local len = 131072 + +describe('List support code', function() + if not pending('does not actually allows interrupting with just got_int', function() end) then return end + -- The following tests are confirmed to work with os_breakcheck() just before + -- `if (got_int) {break;}` in tv_list_copy and list_join_inner() and not to + -- work without. + setup(function() + clear() + dur = 0 + while true do + command(([[ + let rt = reltime() + let bl = range(%u) + let dur = reltimestr(reltime(rt)) + ]]):format(len)) + dur = tonumber(meths.get_var('dur')) + if dur >= min_dur then + -- print(('Using len %u, dur %g'):format(len, dur)) + break + else + len = len * 2 + end + end + end) + it('allows interrupting copy', function() + feed(':let t_rt = reltime()<CR>:let t_bl = copy(bl)<CR>') + sleep(min_dur / 16 * 1000) + feed('<C-c>') + wait() + command('let t_dur = reltimestr(reltime(t_rt))') + local t_dur = tonumber(meths.get_var('t_dur')) + if t_dur >= dur / 8 then + eq(nil, ('Took too long to cancel: %g >= %g'):format(t_dur, dur / 8)) + end + end) + it('allows interrupting join', function() + feed(':let t_rt = reltime()<CR>:let t_j = join(bl)<CR>') + sleep(min_dur / 16 * 1000) + feed('<C-c>') + wait() + command('let t_dur = reltimestr(reltime(t_rt))') + local t_dur = tonumber(meths.get_var('t_dur')) + print(('t_dur: %g'):format(t_dur)) + if t_dur >= dur / 8 then + eq(nil, ('Took too long to cancel: %g >= %g'):format(t_dur, dur / 8)) + end + end) +end) diff --git a/test/functional/eval/match_functions_spec.lua b/test/functional/eval/match_functions_spec.lua index 3150d89f62..7989b22b5e 100644 --- a/test/functional/eval/match_functions_spec.lua +++ b/test/functional/eval/match_functions_spec.lua @@ -1,9 +1,11 @@ local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') local eq = helpers.eq local clear = helpers.clear local funcs = helpers.funcs local command = helpers.command +local exc_exec = helpers.exc_exec before_each(clear) @@ -59,3 +61,95 @@ describe('matchadd()', function() }}, funcs.getmatches()) end) end) + +describe('matchaddpos()', function() + it('errors out on invalid input', function() + command('hi clear PreProc') + eq('Vim(let):E5030: Empty list at position 0', + exc_exec('let val = matchaddpos("PreProc", [[]])')) + eq('Vim(let):E5030: Empty list at position 1', + exc_exec('let val = matchaddpos("PreProc", [1, v:_null_list])')) + eq('Vim(let):E5031: List or number required at position 1', + exc_exec('let val = matchaddpos("PreProc", [1, v:_null_dict])')) + end) + it('works with 0 lnum', function() + command('hi clear PreProc') + eq(4, funcs.matchaddpos('PreProc', {1}, 3, 4)) + eq({{ + group='PreProc', + pos1 = {1}, + priority=3, + id=4, + }}, funcs.getmatches()) + funcs.matchdelete(4) + eq(4, funcs.matchaddpos('PreProc', {{0}, 1}, 3, 4)) + eq({{ + group='PreProc', + pos1 = {1}, + priority=3, + id=4, + }}, funcs.getmatches()) + funcs.matchdelete(4) + eq(4, funcs.matchaddpos('PreProc', {0, 1}, 3, 4)) + eq({{ + group='PreProc', + pos1 = {1}, + priority=3, + id=4, + }}, funcs.getmatches()) + end) + it('works with negative numbers', function() + command('hi clear PreProc') + eq(4, funcs.matchaddpos('PreProc', {-10, 1}, 3, 4)) + eq({{ + group='PreProc', + pos1 = {1}, + priority=3, + id=4, + }}, funcs.getmatches()) + funcs.matchdelete(4) + eq(4, funcs.matchaddpos('PreProc', {{-10}, 1}, 3, 4)) + eq({{ + group='PreProc', + pos1 = {1}, + priority=3, + id=4, + }}, funcs.getmatches()) + funcs.matchdelete(4) + eq(4, funcs.matchaddpos('PreProc', {{2, -1}, 1}, 3, 4)) + eq({{ + group='PreProc', + pos1 = {1}, + priority=3, + id=4, + }}, funcs.getmatches()) + funcs.matchdelete(4) + eq(4, funcs.matchaddpos('PreProc', {{2, 0, -1}, 1}, 3, 4)) + eq({{ + group='PreProc', + pos1 = {1}, + priority=3, + id=4, + }}, funcs.getmatches()) + end) + it('works with zero length', function() + local screen = Screen.new(40, 5) + screen:attach() + funcs.setline(1, 'abcdef') + command('hi PreProc guifg=Red') + eq(4, funcs.matchaddpos('PreProc', {{1, 2, 0}}, 3, 4)) + eq({{ + group='PreProc', + pos1 = {1, 2, 0}, + priority=3, + id=4, + }}, funcs.getmatches()) + screen:expect([[ + ^a{1:b}cdef | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]], {[1] = {foreground = Screen.colors.Red}, [2] = {bold = true, foreground = Screen.colors.Blue1}}) + end) +end) diff --git a/test/functional/eval/msgpack_functions_spec.lua b/test/functional/eval/msgpack_functions_spec.lua index b241635dfe..a8a413f68b 100644 --- a/test/functional/eval/msgpack_functions_spec.lua +++ b/test/functional/eval/msgpack_functions_spec.lua @@ -463,7 +463,8 @@ describe('msgpackparse() function', function() eval(cmd) eval(cmd) -- do it again (try to force segfault) local api_info = eval(cmd) -- do it again - eq({'error_types', 'functions', 'types', 'ui_events', 'version'}, api_info) + eq({'error_types', 'functions', 'types', + 'ui_events', 'ui_options', 'version'}, api_info) end) it('fails when called with no arguments', function() @@ -628,7 +629,7 @@ describe('msgpackdump() function', function() it('fails to dump a recursive (key) map in a special dict', function() command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": []}') command('call add(todump._VAL, [todump, 0])') - eq('Vim(call):E5005: Unable to dump msgpackdump() argument, index 0: container references itself in index 1', + eq('Vim(call):E5005: Unable to dump msgpackdump() argument, index 0: container references itself in index 0', exc_exec('call msgpackdump([todump])')) end) diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua index 6fd30caec9..afe999e1fa 100644 --- a/test/functional/eval/null_spec.lua +++ b/test/functional/eval/null_spec.lua @@ -41,43 +41,9 @@ describe('NULL', function() end describe('list', function() -- Incorrect behaviour - - -- FIXME map() should not return 0 without error - null_expr_test('does not crash map()', 'map(L, "v:val")', 0, 0) - -- FIXME map() should not return 0 without error - null_expr_test('does not crash filter()', 'filter(L, "1")', 0, 0) - -- FIXME map() should at least return L - null_expr_test('makes map() return v:_null_list', 'map(L, "v:val") is# L', 0, 0) - -- FIXME filter() should at least return L - null_expr_test('makes filter() return v:_null_list', 'map(L, "1") is# L', 0, 0) - -- FIXME add() should not return 1 at all - null_expr_test('does not crash add()', 'add(L, 0)', 0, 1) - null_expr_test('does not crash extend()', 'extend(L, [1])', 'E742: Cannot change value of extend() argument', 0) - null_expr_test('does not crash extend() (second position)', 'extend([1], L)', 0, {1}) - -- FIXME should be accepted by inputlist() - null_expr_test('is accepted as an empty list by inputlist()', - '[feedkeys("\\n"), inputlist(L)]', 'E686: Argument of inputlist() must be a List', {0, 0}) - -- FIXME should be accepted by writefile(), return {0, {}} - null_expr_test('is accepted as an empty list by writefile()', - ('[writefile(L, "%s"), readfile("%s")]'):format(tmpfname, tmpfname), - 'E484: Can\'t open file ' .. tmpfname, {0, {}}) - -- FIXME should give error message - null_expr_test('does not crash remove()', 'remove(L, 0)', 0, 0) - -- FIXME should return 0 - null_expr_test('is accepted by setqflist()', 'setqflist(L)', 0, -1) - -- FIXME should return 0 - null_expr_test('is accepted by setloclist()', 'setloclist(1, L)', 0, -1) - -- FIXME should return 0 - null_expr_test('is accepted by setmatches()', 'setmatches(L)', 0, -1) - -- FIXME should return empty list or error out - null_expr_test('is accepted by sort()', 'sort(L)', 0, 0) - -- FIXME Should return 1 - null_expr_test('is accepted by sort()', 'sort(L) is L', 0, 0) - -- FIXME should not error out - null_test('is accepted by :cexpr', 'cexpr L', 'Vim(cexpr):E777: String or List expected') - -- FIXME should not error out - null_test('is accepted by :lexpr', 'lexpr L', 'Vim(lexpr):E777: String or List expected') - null_test('is accepted by :for', 'for x in L|throw x|endfor', 0) + -- FIXME Should error out with different message + null_test('makes :unlet act as if it is not a list', ':unlet L[0]', + 'Vim(unlet):E689: Can only index a List or Dictionary') -- Subjectable behaviour @@ -85,20 +51,19 @@ describe('NULL', function() null_expr_test('is equal to empty list', 'L == []', 0, 0) -- FIXME Should return 1 null_expr_test('is equal to empty list (reverse order)', '[] == L', 0, 0) - -- FIXME Should return 1 - null_expr_test('is not locked', 'islocked("v:_null_list")', 0, 0) - - -- Crashes - - -- null_expr_test('does not crash setreg', 'setreg("x", L)', 0, 0) - -- null_expr_test('does not crash setline', 'setline(1, L)', 0, 0) - -- null_expr_test('does not crash system()', 'system("cat", L)', 0, '') - -- null_expr_test('does not crash systemlist()', 'systemlist("cat", L)', 0, {}) -- Correct behaviour + null_expr_test('can be indexed with error message for empty list', 'L[0]', + 'E684: list index out of range: 0\nE15: Invalid expression: L[0]', nil) + null_expr_test('can be splice-indexed', 'L[:]', 0, {}) + null_expr_test('is not locked', 'islocked("v:_null_list")', 0, 0) + null_test('is accepted by :for', 'for x in L|throw x|endfor', 0) null_expr_test('does not crash append()', 'append(1, L)', 0, 0, function() eq({''}, curbufmeths.get_lines(0, -1, false)) end) + null_expr_test('does not crash setline()', 'setline(1, L)', 0, 0, function() + eq({''}, curbufmeths.get_lines(0, -1, false)) + end) null_expr_test('is identical to itself', 'L is L', 0, 1) null_expr_test('can be sliced', 'L[:]', 0, {}) null_expr_test('can be copied', 'copy(L)', 0, {}) @@ -111,6 +76,8 @@ describe('NULL', function() null_expr_test('does not crash line()', 'line(L)', 0, 0) null_expr_test('does not crash count()', 'count(L, 1)', 0, 0) null_expr_test('does not crash cursor()', 'cursor(L)', 'E474: Invalid argument', -1) + null_expr_test('does not crash map()', 'map(L, "v:val")', 0, {}) + null_expr_test('does not crash filter()', 'filter(L, "1")', 0, {}) null_expr_test('is empty', 'empty(L)', 0, 1) null_expr_test('does not crash get()', 'get(L, 1, 10)', 0, 10) null_expr_test('has zero length', 'len(L)', 0, 0) @@ -126,6 +93,44 @@ describe('NULL', function() null_expr_test('is equal to itself', 'L == L', 0, 1) null_expr_test('is not not equal to itself', 'L != L', 0, 0) null_expr_test('counts correctly', 'count([L], L)', 0, 1) + null_expr_test('makes map() return v:_null_list', 'map(L, "v:val") is# L', 0, 1) + null_expr_test('makes filter() return v:_null_list', 'filter(L, "1") is# L', 0, 1) + null_test('is treated by :let as empty list', ':let [l] = L', 'Vim(let):E688: More targets than List items') + null_expr_test('is accepted as an empty list by inputlist()', '[feedkeys("\\n"), inputlist(L)]', + 'Type number and <Enter> or click with mouse (empty cancels): ', {0, 0}) + null_expr_test('is accepted as an empty list by writefile()', + ('[writefile(L, "%s"), readfile("%s")]'):format(tmpfname, tmpfname), + 0, {0, {}}) + null_expr_test('makes add() error out', 'add(L, 0)', + 'E742: Cannot change value of add() argument', 1) + null_expr_test('makes insert() error out', 'insert(L, 1)', + 'E742: Cannot change value of insert() argument', 0) + null_expr_test('does not crash remove()', 'remove(L, 0)', + 'E742: Cannot change value of remove() argument', 0) + null_expr_test('makes reverse() error out', 'reverse(L)', + 'E742: Cannot change value of reverse() argument', 0) + null_expr_test('makes sort() error out', 'sort(L)', + 'E742: Cannot change value of sort() argument', 0) + null_expr_test('makes uniq() error out', 'uniq(L)', + 'E742: Cannot change value of uniq() argument', 0) + null_expr_test('does not crash extend()', 'extend(L, [1])', 'E742: Cannot change value of extend() argument', 0) + null_expr_test('does not crash extend() (second position)', 'extend([1], L)', 0, {1}) + null_expr_test('makes join() return empty string', 'join(L, "")', 0, '') + null_expr_test('makes msgpackdump() return empty list', 'msgpackdump(L)', 0, {}) + null_expr_test('does not crash system()', 'system("cat", L)', 0, '') + null_expr_test('does not crash setreg', 'setreg("x", L)', 0, 0) + null_expr_test('does not crash systemlist()', 'systemlist("cat", L)', 0, {}) + null_test('does not make Neovim crash when v:oldfiles gets assigned to that', ':let v:oldfiles = L|oldfiles', 0) + null_expr_test('does not make complete() crash or error out', + 'execute(":normal i\\<C-r>=complete(1, L)[-1]\\n")', + '', '\n', function() + eq({''}, curbufmeths.get_lines(0, -1, false)) + end) + null_expr_test('is accepted by setmatches()', 'setmatches(L)', 0, 0) + null_expr_test('is accepted by setqflist()', 'setqflist(L)', 0, 0) + null_expr_test('is accepted by setloclist()', 'setloclist(1, L)', 0, 0) + null_test('is accepted by :cexpr', 'cexpr L', 0) + null_test('is accepted by :lexpr', 'lexpr L', 0) end) describe('dict', function() it('does not crash when indexing NULL dict', function() @@ -134,5 +139,9 @@ describe('NULL', function() end) null_expr_test('makes extend error out', 'extend(D, {})', 'E742: Cannot change value of extend() argument', 0) null_expr_test('makes extend do nothing', 'extend({1: 2}, D)', 0, {['1']=2}) + null_expr_test('does not crash map()', 'map(D, "v:val")', 0, {}) + null_expr_test('does not crash filter()', 'filter(D, "1")', 0, {}) + null_expr_test('makes map() return v:_null_dict', 'map(D, "v:val") is# D', 0, 1) + null_expr_test('makes filter() return v:_null_dict', 'filter(D, "1") is# D', 0, 1) end) end) diff --git a/test/functional/eval/server_spec.lua b/test/functional/eval/server_spec.lua index 393616838e..4e4aed864b 100644 --- a/test/functional/eval/server_spec.lua +++ b/test/functional/eval/server_spec.lua @@ -1,31 +1,40 @@ - local helpers = require('test.functional.helpers')(after_each) local eq, neq, eval = helpers.eq, helpers.neq, helpers.eval local command = helpers.command local clear, funcs, meths = helpers.clear, helpers.funcs, helpers.meths -local os_name = helpers.os_name +local iswin = helpers.iswin +local ok = helpers.ok +local matches = helpers.matches local function clear_serverlist() - for _, server in pairs(funcs.serverlist()) do - funcs.serverstop(server) - end + for _, server in pairs(funcs.serverlist()) do + funcs.serverstop(server) + end end -describe('serverstart(), serverstop()', function() +describe('server', function() before_each(clear) - it('sets $NVIM_LISTEN_ADDRESS on first invocation', function() + it('serverstart() sets $NVIM_LISTEN_ADDRESS on first invocation', function() -- Unset $NVIM_LISTEN_ADDRESS command('let $NVIM_LISTEN_ADDRESS = ""') local s = eval('serverstart()') assert(s ~= nil and s:len() > 0, "serverstart() returned empty") eq(s, eval('$NVIM_LISTEN_ADDRESS')) - command("call serverstop('"..s.."')") + eq(1, eval("serverstop('"..s.."')")) eq('', eval('$NVIM_LISTEN_ADDRESS')) end) - it('sets v:servername _only_ on nvim startup unless all servers are stopped', + it('sets new v:servername if $NVIM_LISTEN_ADDRESS is invalid', function() + clear({env={NVIM_LISTEN_ADDRESS='.'}}) + eq('.', eval('$NVIM_LISTEN_ADDRESS')) + local servers = funcs.serverlist() + eq(1, #servers) + ok(string.len(servers[1]) > 4) -- Like /tmp/nvim…/… or \\.\pipe\… + end) + + it('sets v:servername at startup or if all servers were stopped', function() local initial_server = meths.get_vvar('servername') assert(initial_server ~= nil and initial_server:len() > 0, @@ -38,24 +47,23 @@ describe('serverstart(), serverstop()', function() neq(initial_server, s) -- serverstop() does _not_ modify v:servername... - funcs.serverstop(s) + eq(1, funcs.serverstop(s)) eq(initial_server, meths.get_vvar('servername')) -- ...unless we stop _all_ servers. - funcs.serverstop(funcs.serverlist()[1]) + eq(1, funcs.serverstop(funcs.serverlist()[1])) eq('', meths.get_vvar('servername')) -- v:servername will take the next available server. - local servername = (os_name() == 'windows' - and [[\\.\pipe\Xtest-functional-server-pipe]] - or 'Xtest-functional-server-socket') + local servername = (iswin() and [[\\.\pipe\Xtest-functional-server-pipe]] + or 'Xtest-functional-server-socket') funcs.serverstart(servername) eq(servername, meths.get_vvar('servername')) end) - it('serverstop() ignores invalid input', function() - command("call serverstop('')") - command("call serverstop('bogus-socket-name')") + it('serverstop() returns false for invalid input', function() + eq(0, eval("serverstop('')")) + eq(0, eval("serverstop('bogus-socket-name')")) end) it('parses endpoints correctly', function() @@ -96,17 +104,13 @@ describe('serverstart(), serverstop()', function() funcs.serverstart('127.0.0.1:65536') -- invalid port eq({}, funcs.serverlist()) end) -end) - -describe('serverlist()', function() - before_each(clear) - it('returns the list of servers', function() + it('serverlist() returns the list of servers', function() -- There should already be at least one server. local n = eval('len(serverlist())') - -- Add a few - local servs = (os_name() == 'windows' + -- Add some servers. + local servs = (iswin() and { [[\\.\pipe\Xtest-pipe0934]], [[\\.\pipe\Xtest-pipe4324]] } or { [[Xtest-pipe0934]], [[Xtest-pipe4324]] }) for _, s in ipairs(servs) do @@ -120,9 +124,31 @@ describe('serverlist()', function() -- The new servers should be at the end of the list. for i = 1, #servs do eq(servs[i], new_servs[i + n]) - command("call serverstop('"..servs[i].."')") + eq(1, eval("serverstop('"..servs[i].."')")) end -- After serverstop() the servers should NOT be in the list. eq(n, eval('len(serverlist())')) end) end) + +describe('startup --listen', function() + it('validates', function() + clear() + + local cmd = { unpack(helpers.nvim_argv) } + table.insert(cmd, '--listen') + matches('nvim.*: Argument missing after: "%-%-listen"', funcs.system(cmd)) + + cmd = { unpack(helpers.nvim_argv) } + table.insert(cmd, '--listen2') + matches('nvim.*: Garbage after option argument: "%-%-listen2"', funcs.system(cmd)) + end) + + it('sets v:servername, overrides $NVIM_LISTEN_ADDRESS', function() + local addr = (iswin() and [[\\.\pipe\Xtest-listen-pipe]] + or 'Xtest-listen-pipe') + clear({ env={ NVIM_LISTEN_ADDRESS='Xtest-env-pipe' }, + args={ '--listen', addr } }) + eq(addr, meths.get_vvar('servername')) + end) +end) diff --git a/test/functional/eval/system_spec.lua b/test/functional/eval/system_spec.lua index 7e213e2156..201426c40b 100644 --- a/test/functional/eval/system_spec.lua +++ b/test/functional/eval/system_spec.lua @@ -5,6 +5,7 @@ local eq, call, clear, eval, feed_command, feed, nvim = helpers.eq, helpers.call, helpers.clear, helpers.eval, helpers.feed_command, helpers.feed, helpers.nvim local command = helpers.command +local exc_exec = helpers.exc_exec local iswin = helpers.iswin local Screen = require('test.functional.ui.screen') @@ -89,7 +90,9 @@ describe('system()', function() end) it('does NOT run in shell', function() - if not iswin() then + if iswin() then + eq("%PATH%\n", eval("system(['powershell', '-NoProfile', '-NoLogo', '-ExecutionPolicy', 'RemoteSigned', '-Command', 'echo', '%PATH%'])")) + else eq("* $PATH %PATH%\n", eval("system(['echo', '*', '$PATH', '%PATH%'])")) end end) @@ -117,33 +120,47 @@ describe('system()', function() end end) - describe('executes shell function if passed a string', function() + describe('executes shell function', function() local screen before_each(function() - clear() - screen = Screen.new() - screen:attach() + clear() + screen = Screen.new() + screen:attach() end) after_each(function() - screen:detach() + screen:detach() end) if iswin() then + local function test_more() + eq('root = true', eval([[get(split(system('"more" ".editorconfig"'), "\n"), 0, '')]])) + end + local function test_shell_unquoting() + eval([[system('"ping" "-n" "1" "127.0.0.1"')]]) + eq(0, eval('v:shell_error')) + eq('"a b"\n', eval([[system('cmd /s/c "cmd /s/c "cmd /s/c "echo "a b""""')]])) + eq('"a b"\n', eval([[system('powershell -NoProfile -NoLogo -ExecutionPolicy RemoteSigned -Command echo ''\^"a b\^"''')]])) + end + it('with shell=cmd.exe', function() command('set shell=cmd.exe') eq('""\n', eval([[system('echo ""')]])) eq('"a b"\n', eval([[system('echo "a b"')]])) eq('a \nb\n', eval([[system('echo a & echo b')]])) eq('a \n', eval([[system('echo a 2>&1')]])) + test_more() eval([[system('cd "C:\Program Files"')]]) eq(0, eval('v:shell_error')) + test_shell_unquoting() end) it('with shell=cmd', function() command('set shell=cmd') eq('"a b"\n', eval([[system('echo "a b"')]])) + test_more() + test_shell_unquoting() end) it('with shell=$COMSPEC', function() @@ -151,6 +168,8 @@ describe('system()', function() if comspecshell == 'cmd.exe' then command('set shell=$COMSPEC') eq('"a b"\n', eval([[system('echo "a b"')]])) + test_more() + test_shell_unquoting() else pending('$COMSPEC is not cmd.exe: ' .. comspecshell) end @@ -184,8 +203,10 @@ describe('system()', function() ]]) end) - it('`yes` and is interrupted with CTRL-C', function() - feed(':call system("yes")<cr>') + it('`yes` interrupted with CTRL-C', function() + feed(':call system("' .. (iswin() + and 'for /L %I in (1,0,2) do @echo y' + or 'yes') .. '")<cr>') screen:expect([[ | ~ | @@ -200,8 +221,11 @@ describe('system()', function() ~ | ~ | ~ | - :call system("yes") | - ]]) +]] .. (iswin() + and [[ + :call system("for /L %I in (1,0,2) do @echo y") |]] + or [[ + :call system("yes") |]])) feed('<c-c>') screen:expect([[ ^ | @@ -231,6 +255,8 @@ describe('system()', function() end end) it('to backgrounded command does not crash', function() + -- cmd.exe doesn't background a command with & + if iswin() then return end -- This is indeterminate, just exercise the codepath. May get E5677. feed_command('call system("echo -n echoed &")') local v_errnum = string.match(eval("v:errmsg"), "^E%d*:") @@ -246,14 +272,20 @@ describe('system()', function() eq("input", eval('system("cat -", "input")')) end) it('to backgrounded command does not crash', function() + -- cmd.exe doesn't background a command with & + if iswin() then return end -- This is indeterminate, just exercise the codepath. May get E5677. - feed_command('call system("cat - &")') + feed_command('call system("cat - &", "input")') local v_errnum = string.match(eval("v:errmsg"), "^E%d*:") if v_errnum then eq("E5677:", v_errnum) end eq(2, eval("1+1")) -- Still alive? end) + it('works with an empty string', function() + eq("test\n", eval('system("echo test", "")')) + eq(2, eval("1+1")) -- Still alive? + end) end) describe('passing a lot of input', function() @@ -271,9 +303,12 @@ describe('system()', function() end) end) - describe('input passed as Number', function() - it('stringifies the input', function() - eq('1', eval('system("cat", 1)')) + describe('Number input', function() + it('is treated as a buffer id', function() + command("put ='text in buffer 1'") + eq('\ntext in buffer 1\n', eval('system("cat", 1)')) + eq('Vim(echo):E86: Buffer 42 does not exist', + exc_exec('echo system("cat", 42)')) end) end) @@ -284,7 +319,7 @@ describe('system()', function() after_each(delete_file(fname)) it('replaces NULs by SOH characters', function() - eq('part1\001part2\001part3\n', eval('system("cat '..fname..'")')) + eq('part1\001part2\001part3\n', eval([[system('"cat" "]]..fname..[["')]])) end) end) @@ -351,7 +386,7 @@ describe('systemlist()', function() end end) - describe('exectues shell function', function() + describe('executes shell function', function() local screen before_each(function() @@ -384,7 +419,7 @@ describe('systemlist()', function() ]]) end) - it('`yes` and is interrupted with CTRL-C', function() + it('`yes` interrupted with CTRL-C', function() feed(':call systemlist("yes | xargs")<cr>') screen:expect([[ | @@ -442,12 +477,14 @@ describe('systemlist()', function() describe('with output containing NULs', function() local fname = 'Xtest' - before_each(create_file_with_nuls(fname)) + before_each(function() + command('set ff=unix') + create_file_with_nuls(fname)() + end) after_each(delete_file(fname)) it('replaces NULs by newline characters', function() - if helpers.pending_win32(pending) then return end - eq({'part1\npart2\npart3'}, eval('systemlist("cat '..fname..'")')) + eq({'part1\npart2\npart3'}, eval([[systemlist('"cat" "]]..fname..[["')]])) end) end) diff --git a/test/functional/ex_cmds/bang_filter_spec.lua b/test/functional/ex_cmds/bang_filter_spec.lua deleted file mode 100644 index aaec983b73..0000000000 --- a/test/functional/ex_cmds/bang_filter_spec.lua +++ /dev/null @@ -1,51 +0,0 @@ --- Specs for bang/filter commands - -local helpers = require('test.functional.helpers')(after_each) -local feed, command, clear = helpers.feed, helpers.command, helpers.clear -local mkdir, write_file, rmdir = helpers.mkdir, helpers.write_file, helpers.rmdir - -if helpers.pending_win32(pending) then return end - -local Screen = require('test.functional.ui.screen') - - -describe('issues', function() - local screen - - before_each(function() - clear() - rmdir('bang_filter_spec') - mkdir('bang_filter_spec') - write_file('bang_filter_spec/f1', 'f1') - write_file('bang_filter_spec/f2', 'f2') - write_file('bang_filter_spec/f3', 'f3') - screen = Screen.new() - screen:attach() - end) - - after_each(function() - rmdir('bang_filter_spec') - end) - - it('#3269 Last line of shell output is not truncated', function() - command([[nnoremap <silent>\l :!ls bang_filter_spec<cr>]]) - feed([[\l]]) - screen:expect([[ - ~ | - ~ | - ~ | - ~ | - ~ | - ~ | - ~ | - ~ | - :!ls bang_filter_spec | - | - f1 | - f2 | - f3 | - Press ENTER or type command to continue^ | - ]]) - end) - -end) diff --git a/test/functional/ex_cmds/cd_spec.lua b/test/functional/ex_cmds/cd_spec.lua index 059cb26d5d..bc2b365b30 100644 --- a/test/functional/ex_cmds/cd_spec.lua +++ b/test/functional/ex_cmds/cd_spec.lua @@ -8,8 +8,7 @@ local call = helpers.call local clear = helpers.clear local command = helpers.command local exc_exec = helpers.exc_exec - -if helpers.pending_win32(pending) then return end +local pathsep = helpers.get_pathsep() -- These directories will be created for testing local directories = { @@ -75,8 +74,8 @@ for _, cmd in ipairs {'cd', 'chdir'} do eq(0, lwd(globalwin, tabnr)) -- Window with local dir reports as such - eq(globalDir .. '/' .. directories.window, cwd(localwin)) - eq(globalDir .. '/' .. directories.window, cwd(localwin, tabnr)) + eq(globalDir .. pathsep .. directories.window, cwd(localwin)) + eq(globalDir .. pathsep .. directories.window, cwd(localwin, tabnr)) eq(1, lwd(localwin)) eq(1, lwd(localwin, tabnr)) @@ -86,7 +85,7 @@ for _, cmd in ipairs {'cd', 'chdir'} do eq(0, lwd(globalwin, tabnr)) -- From new tab page, local window reports as such - eq(globalDir .. '/' .. directories.window, cwd(localwin, tabnr)) + eq(globalDir .. pathsep .. directories.window, cwd(localwin, tabnr)) eq(1, lwd(localwin, tabnr)) end) @@ -109,14 +108,14 @@ for _, cmd in ipairs {'cd', 'chdir'} do eq(0, lwd(-1, globaltab)) -- new tab reports local - eq(globalDir .. '/' .. directories.tab, cwd(-1, 0)) - eq(globalDir .. '/' .. directories.tab, cwd(-1, localtab)) + eq(globalDir .. pathsep .. directories.tab, cwd(-1, 0)) + eq(globalDir .. pathsep .. directories.tab, cwd(-1, localtab)) eq(1, lwd(-1, 0)) eq(1, lwd(-1, localtab)) command('tabnext') -- From original tab page, local reports as such - eq(globalDir .. '/' .. directories.tab, cwd(-1, localtab)) + eq(globalDir .. pathsep .. directories.tab, cwd(-1, localtab)) eq(1, lwd(-1, localtab)) end) end) @@ -147,17 +146,17 @@ for _, cmd in ipairs {'cd', 'chdir'} do -- Create a new tab and change directory command('tabnew') command('silent t' .. cmd .. ' ' .. directories.tab) - eq(globalDir .. '/' .. directories.tab, tcwd()) + eq(globalDir .. pathsep .. directories.tab, tcwd()) -- Create a new tab and verify it has inherited the directory command('tabnew') - eq(globalDir .. '/' .. directories.tab, tcwd()) + eq(globalDir .. pathsep .. directories.tab, tcwd()) -- Change tab and change back, verify that directories are correct command('tabnext') eq(globalDir, tcwd()) command('tabprevious') - eq(globalDir .. '/' .. directories.tab, tcwd()) + eq(globalDir .. pathsep .. directories.tab, tcwd()) end) end) @@ -173,7 +172,7 @@ for _, cmd in ipairs {'cd', 'chdir'} do -- Change tab-local working directory and verify it is different command('silent t' .. cmd .. ' ' .. directories.tab) - eq(globalDir .. '/' .. directories.tab, cwd()) + eq(globalDir .. pathsep .. directories.tab, cwd()) eq(cwd(), tcwd()) -- working directory maches tab directory eq(1, tlwd()) eq(cwd(), wcwd()) -- still no window-directory @@ -183,16 +182,16 @@ for _, cmd in ipairs {'cd', 'chdir'} do command('new') eq(1, tlwd()) -- Still tab-local working directory eq(0, wlwd()) -- Still no window-local working directory - eq(globalDir .. '/' .. directories.tab, cwd()) + eq(globalDir .. pathsep .. directories.tab, cwd()) command('silent l' .. cmd .. ' ../' .. directories.window) - eq(globalDir .. '/' .. directories.window, cwd()) - eq(globalDir .. '/' .. directories.tab, tcwd()) + eq(globalDir .. pathsep .. directories.window, cwd()) + eq(globalDir .. pathsep .. directories.tab, tcwd()) eq(1, wlwd()) -- Verify the first window still has the tab local directory command('wincmd w') - eq(globalDir .. '/' .. directories.tab, cwd()) - eq(globalDir .. '/' .. directories.tab, tcwd()) + eq(globalDir .. pathsep .. directories.tab, cwd()) + eq(globalDir .. pathsep .. directories.tab, tcwd()) eq(0, wlwd()) -- No window-local directory -- Change back to initial tab and verify working directory has stayed @@ -203,10 +202,10 @@ for _, cmd in ipairs {'cd', 'chdir'} do -- Verify global changes don't affect local ones command('silent ' .. cmd .. ' ' .. directories.global) - eq(globalDir .. '/' .. directories.global, cwd()) + eq(globalDir .. pathsep .. directories.global, cwd()) command('tabnext') - eq(globalDir .. '/' .. directories.tab, cwd()) - eq(globalDir .. '/' .. directories.tab, tcwd()) + eq(globalDir .. pathsep .. directories.tab, cwd()) + eq(globalDir .. pathsep .. directories.tab, tcwd()) eq(0, wlwd()) -- Still no window-local directory in this window -- Unless the global change happened in a tab with local directory @@ -220,9 +219,9 @@ for _, cmd in ipairs {'cd', 'chdir'} do -- But not in a window with its own local directory command('tabnext | wincmd w') - eq(globalDir .. '/' .. directories.window, cwd() ) + eq(globalDir .. pathsep .. directories.window, cwd() ) eq(0 , tlwd()) - eq(globalDir .. '/' .. directories.window, wcwd()) + eq(globalDir .. pathsep .. directories.window, wcwd()) end) end) end @@ -280,6 +279,9 @@ describe("getcwd()", function () end) it("returns empty string if working directory does not exist", function() + if helpers.iswin() then + return + end command("cd "..directories.global) command("call delete('../"..directories.global.."', 'd')") eq("", helpers.eval("getcwd()")) diff --git a/test/functional/ex_cmds/cmd_map_spec.lua b/test/functional/ex_cmds/cmd_map_spec.lua new file mode 100644 index 0000000000..77d025dcc7 --- /dev/null +++ b/test/functional/ex_cmds/cmd_map_spec.lua @@ -0,0 +1,772 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local feed_command = helpers.feed_command +local feed = helpers.feed +local eq = helpers.eq +local expect = helpers.expect +local eval = helpers.eval +local funcs = helpers.funcs +local insert = helpers.insert +local exc_exec = helpers.exc_exec +local Screen = require('test.functional.ui.screen') + +describe('mappings with <Cmd>', function() + local screen + local function cmdmap(lhs, rhs) + feed_command('noremap '..lhs..' <Cmd>'..rhs..'<cr>') + feed_command('noremap! '..lhs..' <Cmd>'..rhs..'<cr>') + end + + before_each(function() + clear() + screen = Screen.new(65, 8) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [3] = {bold = true, foreground = Screen.colors.SeaGreen4}, + [4] = {bold = true}, + [5] = {background = Screen.colors.LightGrey}, + [6] = {foreground = Screen.colors.Blue1}, + [7] = {bold = true, reverse = true}, + }) + screen:attach() + + cmdmap('<F3>', 'let m = mode(1)') + cmdmap('<F4>', 'normal! ww') + cmdmap('<F5>', 'normal! "ay') + cmdmap('<F6>', 'throw "very error"') + feed_command([[ + function! TextObj() + if mode() !=# "v" + normal! v + end + call cursor(1,3) + normal! o + call cursor(2,4) + endfunction]]) + cmdmap('<F7>', 'call TextObj()') + insert([[ + some short lines + of test text]]) + feed('gg') + cmdmap('<F8>', 'startinsert') + cmdmap('<F9>', 'stopinsert') + feed_command("abbr foo <Cmd>let g:y = 17<cr>bar") + end) + + it('can be displayed', function() + feed_command('map <F3>') + screen:expect([[ + ^some short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {6:<F3>} {6:*} {6:<Cmd>}let m = mode(1){6:<CR>} | + ]]) + end) + + it('handles invalid mappings', function() + feed_command('let x = 0') + feed_command('noremap <F3> <Cmd><Cmd>let x = 1<cr>') + feed('<F3>') + screen:expect([[ + ^some short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:E5521: <Cmd> mapping must end with <CR> before second <Cmd>} | + ]]) + + feed_command('noremap <F3> <Cmd><F3>let x = 2<cr>') + feed('<F3>') + screen:expect([[ + ^some short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:E5522: <Cmd> mapping must not include <F3> key} | + ]]) + + feed_command('noremap <F3> <Cmd>let x = 3') + feed('<F3>') + screen:expect([[ + ^some short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:E5520: <Cmd> mapping must end with <CR>} | + ]]) + eq(0, eval('x')) + end) + + it('works in various modes and sees correct `mode()` value', function() + -- normal mode + feed('<F3>') + eq('n', eval('m')) + + -- visual mode + feed('v<F3>') + eq('v', eval('m')) + -- didn't leave visual mode + eq('v', eval('mode(1)')) + feed('<esc>') + eq('n', eval('mode(1)')) + + -- visual mapping in select mode + feed('gh<F3>') + eq('v', eval('m')) + -- didn't leave select mode + eq('s', eval('mode(1)')) + feed('<esc>') + eq('n', eval('mode(1)')) + + -- select mode mapping + feed_command('snoremap <F3> <Cmd>let m = mode(1)<cr>') + feed('gh<F3>') + eq('s', eval('m')) + -- didn't leave select mode + eq('s', eval('mode(1)')) + feed('<esc>') + eq('n', eval('mode(1)')) + + -- operator-pending mode + feed("d<F3>") + eq('no', eval('m')) + -- did leave operator-pending mode + eq('n', eval('mode(1)')) + + --insert mode + feed('i<F3>') + eq('i', eval('m')) + eq('i', eval('mode(1)')) + + -- replace mode + feed("<Ins><F3>") + eq('R', eval('m')) + eq('R', eval('mode(1)')) + feed('<esc>') + eq('n', eval('mode(1)')) + + -- virtual replace mode + feed("gR<F3>") + eq('Rv', eval('m')) + eq('Rv', eval('mode(1)')) + feed('<esc>') + eq('n', eval('mode(1)')) + + -- langmap works, but is not distinguished in mode(1) + feed(":set iminsert=1<cr>i<F3>") + eq('i', eval('m')) + eq('i', eval('mode(1)')) + feed('<esc>') + eq('n', eval('mode(1)')) + + feed(':<F3>') + eq('c', eval('m')) + eq('c', eval('mode(1)')) + feed('<esc>') + eq('n', eval('mode(1)')) + + -- terminal mode + feed_command('tnoremap <F3> <Cmd>let m = mode(1)<cr>') + feed_command('split | terminal') + feed('i') + eq('t', eval('mode(1)')) + feed('<F3>') + eq('t', eval('m')) + eq('t', eval('mode(1)')) + end) + + it('works in normal mode', function() + cmdmap('<F2>', 'let s = [mode(1), v:count, v:register]') + + -- check v:count and v:register works + feed('<F2>') + eq({'n', 0, '"'}, eval('s')) + feed('7<F2>') + eq({'n', 7, '"'}, eval('s')) + feed('"e<F2>') + eq({'n', 0, 'e'}, eval('s')) + feed('5"k<F2>') + eq({'n', 5, 'k'}, eval('s')) + feed('"+2<F2>') + eq({'n', 2, '+'}, eval('s')) + + -- text object enters visual mode + feed('<F7>') + screen:expect([[ + so{5:me short lines} | + {5:of }^test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- VISUAL --} | + ]]) + feed('<esc>') + + -- startinsert + feed('<F8>') + eq('i', eval('mode(1)')) + feed('<esc>') + + eq('n', eval('mode(1)')) + cmdmap(',a', 'call feedkeys("aalpha") \\| let g:a = getline(2)') + cmdmap(',b', 'call feedkeys("abeta", "x") \\| let g:b = getline(2)') + + feed(',a<F3>') + screen:expect([[ + some short lines | + of alpha^test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- INSERT --} | + ]]) + -- feedkeys were not executed immediately + eq({'n', 'of test text'}, eval('[m,a]')) + eq('i', eval('mode(1)')) + feed('<esc>') + + feed(',b<F3>') + screen:expect([[ + some short lines | + of alphabet^atest text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + -- feedkeys(..., 'x') was executed immediately, but insert mode gets aborted + eq({'n', 'of alphabetatest text'}, eval('[m,b]')) + eq('n', eval('mode(1)')) + end) + + it('works in :normal command', function() + feed_command('noremap ,x <Cmd>call append(1, "xx")\\| call append(1, "aa")<cr>') + feed_command('noremap ,f <Cmd>nosuchcommand<cr>') + feed_command('noremap ,e <Cmd>throw "very error"\\| call append(1, "yy")<cr>') + feed_command('noremap ,m <Cmd>echoerr "The message."\\| call append(1, "zz")<cr>') + feed_command('noremap ,w <Cmd>for i in range(5)\\|if i==1\\|echoerr "Err"\\|endif\\|call append(1, i)\\|endfor<cr>') + + feed(":normal ,x<cr>") + screen:expect([[ + ^some short lines | + aa | + xx | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + + eq('Vim:E492: Not an editor command: nosuchcommand', exc_exec("normal ,f")) + eq('very error', exc_exec("normal ,e")) + eq('Vim(echoerr):The message.', exc_exec("normal ,m")) + feed('w') + screen:expect([[ + some ^short lines | + aa | + xx | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + + feed_command(':%d') + eq('Vim(echoerr):Err', exc_exec("normal ,w")) + screen:expect([[ + ^ | + 0 | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + --No lines in buffer-- | + ]]) + + feed_command(':%d') + feed_command(':normal ,w') + screen:expect([[ + ^ | + 4 | + 3 | + 2 | + 1 | + 0 | + {1:~ }| + {2:Err} | + ]]) + end) + + it('works in visual mode', function() + -- can extend visual mode + feed('v<F4>') + screen:expect([[ + {5:some short }^lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- VISUAL --} | + ]]) + eq('v', funcs.mode(1)) + + -- can invoke operator, ending visual mode + feed('<F5>') + eq('n', funcs.mode(1)) + eq({'some short l'}, funcs.getreg('a',1,1)) + + -- error doesn't interrupt visual mode + feed('ggvw<F6>') + screen:expect([[ + {5:some }short lines | + of test text | + {1:~ }| + {1:~ }| + {7: }| + {2:Error detected while processing :} | + {2:E605: Exception not caught: very error} | + {3:Press ENTER or type command to continue}^ | + ]]) + feed('<cr>') + eq('E605: Exception not caught: very error', eval('v:errmsg')) + -- still in visual mode, <cr> was consumed by the error prompt + screen:expect([[ + {5:some }^short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- VISUAL --} | + ]]) + eq('v', funcs.mode(1)) + feed('<F7>') + screen:expect([[ + so{5:me short lines} | + {5:of }^test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- VISUAL --} | + ]]) + eq('v', funcs.mode(1)) + + -- startinsert gives "-- (insert) VISUAL --" mode + feed('<F8>') + screen:expect([[ + so{5:me short lines} | + {5:of }^test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- (insert) VISUAL --} | + ]]) + eq('v', eval('mode(1)')) + feed('<esc>') + eq('i', eval('mode(1)')) + end) + + it('works in select mode', function() + feed_command('snoremap <F1> <cmd>throw "very error"<cr>') + feed_command('snoremap <F2> <cmd>normal! <c-g>"by<cr>') + -- can extend select mode + feed('gh<F4>') + screen:expect([[ + {5:some short }^lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- SELECT --} | + ]]) + eq('s', funcs.mode(1)) + + -- visual mapping in select mode restart selct mode after operator + feed('<F5>') + eq('s', funcs.mode(1)) + eq({'some short l'}, funcs.getreg('a',1,1)) + + -- select mode mapping works, and does not restart select mode + feed('<F2>') + eq('n', funcs.mode(1)) + eq({'some short l'}, funcs.getreg('b',1,1)) + + -- error doesn't interrupt temporary visual mode + feed('<esc>ggvw<c-g><F6>') + screen:expect([[ + {5:some }short lines | + of test text | + {1:~ }| + {1:~ }| + {7: }| + {2:Error detected while processing :} | + {2:E605: Exception not caught: very error} | + {3:Press ENTER or type command to continue}^ | + ]]) + feed('<cr>') + eq('E605: Exception not caught: very error', eval('v:errmsg')) + -- still in visual mode, <cr> was consumed by the error prompt + screen:expect([[ + {5:some }^short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- VISUAL --} | + ]]) + -- quirk: restoration of select mode is not performed + eq('v', funcs.mode(1)) + + -- error doesn't interrupt select mode + feed('<esc>ggvw<c-g><F1>') + screen:expect([[ + {5:some }short lines | + of test text | + {1:~ }| + {1:~ }| + {7: }| + {2:Error detected while processing :} | + {2:E605: Exception not caught: very error} | + {3:Press ENTER or type command to continue}^ | + ]]) + feed('<cr>') + eq('E605: Exception not caught: very error', eval('v:errmsg')) + -- still in select mode, <cr> was consumed by the error prompt + screen:expect([[ + {5:some }^short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- SELECT --} | + ]]) + -- quirk: restoration of select mode is not performed + eq('s', funcs.mode(1)) + + feed('<F7>') + screen:expect([[ + so{5:me short lines} | + {5:of }^test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- SELECT --} | + ]]) + eq('s', funcs.mode(1)) + + -- startinsert gives "-- SELECT (insert) --" mode + feed('<F8>') + screen:expect([[ + so{5:me short lines} | + {5:of }^test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- (insert) SELECT --} | + ]]) + eq('s', eval('mode(1)')) + feed('<esc>') + eq('i', eval('mode(1)')) + end) + + + it('works in operator-pending mode', function() + feed('d<F4>') + expect([[ + lines + of test text]]) + eq({'some short '}, funcs.getreg('"',1,1)) + feed('.') + expect([[ + test text]]) + eq({'lines', 'of '}, funcs.getreg('"',1,1)) + feed('uu') + expect([[ + some short lines + of test text]]) + + -- error aborts operator-pending, operator not performed + feed('d<F6>') + screen:expect([[ + some short lines | + of test text | + {1:~ }| + {1:~ }| + {7: }| + {2:Error detected while processing :} | + {2:E605: Exception not caught: very error} | + {3:Press ENTER or type command to continue}^ | + ]]) + feed('<cr>') + eq('E605: Exception not caught: very error', eval('v:errmsg')) + expect([[ + some short lines + of test text]]) + + feed('"bd<F7>') + expect([[ + soest text]]) + eq(funcs.getreg('b',1,1), {'me short lines', 'of t'}) + + -- startinsert aborts operator + feed('d<F8>') + eq('i', eval('mode(1)')) + expect([[ + soest text]]) + end) + + it('works in insert mode', function() + + -- works the same as <c-o>w<c-o>w + feed('iindeed <F4>little ') + screen:expect([[ + indeed some short little ^lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- INSERT --} | + ]]) + + feed('<F6>') + screen:expect([[ + indeed some short little lines | + of test text | + {1:~ }| + {1:~ }| + {7: }| + {2:Error detected while processing :} | + {2:E605: Exception not caught: very error} | + {3:Press ENTER or type command to continue}^ | + ]]) + + + feed('<cr>') + eq('E605: Exception not caught: very error', eval('v:errmsg')) + -- still in insert + screen:expect([[ + indeed some short little ^lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- INSERT --} | + ]]) + eq('i', eval('mode(1)')) + + -- When entering visual mode from InsertEnter autocmd, an async event, or + -- a <cmd> mapping, vim ends up in undocumented "INSERT VISUAL" mode. If a + -- vim patch decides to disable this mode, this test is expected to fail. + feed('<F7>stuff ') + screen:expect([[ + in{5:deed some short little lines} | + {5:of stuff }^test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- INSERT VISUAL --} | + ]]) + expect([[ + indeed some short little lines + of stuff test text]]) + + feed('<F5>') + eq(funcs.getreg('a',1,1), {'deed some short little lines', 'of stuff t'}) + + -- still in insert + screen:expect([[ + in^deed some short little lines | + of stuff test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- INSERT --} | + ]]) + eq('i', eval('mode(1)')) + + -- also works as part of abbreviation + feed('<space>foo ') + screen:expect([[ + in bar ^deed some short little lines | + of stuff test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- INSERT --} | + ]]) + eq(17, eval('g:y')) + + -- :startinsert does nothing + feed('<F8>') + eq('i', eval('mode(1)')) + + -- :stopinsert works + feed('<F9>') + eq('n', eval('mode(1)')) + end) + + it('works in cmdline mode', function() + cmdmap('<F2>', 'call setcmdpos(2)') + feed(':text<F3>') + eq('c', eval('m')) + -- didn't leave cmdline mode + eq('c', eval('mode(1)')) + feed('<cr>') + eq('n', eval('mode(1)')) + screen:expect([[ + ^some short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:E492: Not an editor command: text} | + ]]) + + feed(':echo 2<F6>') + screen:expect([[ + some short lines | + of test text | + {1:~ }| + {7: }| + :echo 2 | + {2:Error detected while processing :} | + {2:E605: Exception not caught: very error} | + :echo 2^ | + ]]) + eq('E605: Exception not caught: very error', eval('v:errmsg')) + -- didn't leave cmdline mode + eq('c', eval('mode(1)')) + feed('+2<cr>') + screen:expect([[ + some short lines | + of test text | + {7: }| + :echo 2 | + {2:Error detected while processing :} | + {2:E605: Exception not caught: very error} | + 4 | + {3:Press ENTER or type command to continue}^ | + ]]) + -- however, message scrolling may cause extra CR prompt + -- This is consistent with output from async events. + feed('<cr>') + screen:expect([[ + ^some short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + eq('n', eval('mode(1)')) + + feed(':let g:x = 3<F4>') + screen:expect([[ + some short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :let g:x = 3^ | + ]]) + feed('+2<cr>') + -- cursor was moved in the background + screen:expect([[ + some short ^lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :let g:x = 3+2 | + ]]) + eq(5, eval('g:x')) + + feed(':let g:y = 7<F8>') + screen:expect([[ + some short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :let g:y = 7^ | + ]]) + eq('c', eval('mode(1)')) + feed('+2<cr>') + -- startinsert takes effect after leaving cmdline mode + screen:expect([[ + some short ^lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:-- INSERT --} | + ]]) + eq('i', eval('mode(1)')) + eq(9, eval('g:y')) + + end) + +end) + diff --git a/test/functional/ex_cmds/dict_notifications_spec.lua b/test/functional/ex_cmds/dict_notifications_spec.lua index e3b4a1c504..3d550588e7 100644 --- a/test/functional/ex_cmds/dict_notifications_spec.lua +++ b/test/functional/ex_cmds/dict_notifications_spec.lua @@ -1,6 +1,6 @@ local helpers = require('test.functional.helpers')(after_each) local clear, nvim, source = helpers.clear, helpers.nvim, helpers.source -local eq, next_msg = helpers.eq, helpers.next_message +local eq, next_msg = helpers.eq, helpers.next_msg local exc_exec = helpers.exc_exec local command = helpers.command local eval = helpers.eval diff --git a/test/functional/ex_cmds/drop_spec.lua b/test/functional/ex_cmds/drop_spec.lua index 9105b84367..30dbd27d37 100644 --- a/test/functional/ex_cmds/drop_spec.lua +++ b/test/functional/ex_cmds/drop_spec.lua @@ -44,14 +44,14 @@ describe(":drop", function() feed_command("edit tmp2") feed_command("drop tmp1") screen:expect([[ - {2:|}^ | - {0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }| + {2:│}^ | + {0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }| {2:tmp2 }{1:tmp1 }| :drop tmp1 | ]]) @@ -64,14 +64,14 @@ describe(":drop", function() feed("iABC<esc>") feed_command("drop tmp3") screen:expect([[ - ^ {2:|} | - {0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }| - {1:tmp3 }{2:|}{0:~ }| - ABC {2:|}{0:~ }| - {0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }| + ^ {2:│} | + {0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }| + {1:tmp3 }{2:│}{0:~ }| + ABC {2:│}{0:~ }| + {0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }| {2:tmp2 [+] tmp1 }| "tmp3" [New File] | ]]) diff --git a/test/functional/ex_cmds/map_spec.lua b/test/functional/ex_cmds/map_spec.lua new file mode 100644 index 0000000000..84d5bc2335 --- /dev/null +++ b/test/functional/ex_cmds/map_spec.lua @@ -0,0 +1,28 @@ +local helpers = require("test.functional.helpers")(after_each) + +local eq = helpers.eq +local feed = helpers.feed +local meths = helpers.meths +local clear = helpers.clear +local command = helpers.command +local expect = helpers.expect + +describe(':*map', function() + before_each(clear) + + it('are not affected by &isident', function() + meths.set_var('counter', 0) + command('nnoremap <C-x> :let counter+=1<CR>') + meths.set_option('isident', ('%u'):format(('>'):byte())) + command('nnoremap <C-y> :let counter+=1<CR>') + -- &isident used to disable keycode parsing here as well + feed('\24\25<C-x><C-y>') + eq(4, meths.get_var('counter')) + end) + + it(':imap <M-">', function() + command('imap <M-"> foo') + feed('i-<M-">-') + expect('-foo-') + end) +end) diff --git a/test/functional/ex_cmds/mksession_spec.lua b/test/functional/ex_cmds/mksession_spec.lua index 5d658f10bb..a5b327095e 100644 --- a/test/functional/ex_cmds/mksession_spec.lua +++ b/test/functional/ex_cmds/mksession_spec.lua @@ -6,6 +6,7 @@ local command = helpers.command local get_pathsep = helpers.get_pathsep local eq = helpers.eq local funcs = helpers.funcs +local rmdir = helpers.rmdir local file_prefix = 'Xtest-functional-ex_cmds-mksession_spec' @@ -20,7 +21,7 @@ describe(':mksession', function() after_each(function() os.remove(session_file) - lfs.rmdir(tab_dir) + rmdir(tab_dir) end) it('restores tab-local working directories', function() diff --git a/test/functional/ex_cmds/mkview_spec.lua b/test/functional/ex_cmds/mkview_spec.lua index 97a49dbbd5..fef8065b2e 100644 --- a/test/functional/ex_cmds/mkview_spec.lua +++ b/test/functional/ex_cmds/mkview_spec.lua @@ -24,7 +24,7 @@ describe(':mkview', function() after_each(function() -- Remove any views created in the view directory rmdir(view_dir) - lfs.rmdir(local_dir) + rmdir(local_dir) end) it('viewoption curdir restores local current directory', function() diff --git a/test/functional/ex_cmds/oldfiles_spec.lua b/test/functional/ex_cmds/oldfiles_spec.lua index 4002855c24..448326cdfb 100644 --- a/test/functional/ex_cmds/oldfiles_spec.lua +++ b/test/functional/ex_cmds/oldfiles_spec.lua @@ -29,6 +29,7 @@ describe(':oldfiles', function() it('shows most recently used files', function() local screen = Screen.new(100, 5) screen:attach() + feed_command("set display-=msgsep") feed_command('edit testfile1') feed_command('edit testfile2') feed_command('wshada') diff --git a/test/functional/ex_cmds/sign_spec.lua b/test/functional/ex_cmds/sign_spec.lua index b37e6e8563..df0f5db860 100644 --- a/test/functional/ex_cmds/sign_spec.lua +++ b/test/functional/ex_cmds/sign_spec.lua @@ -16,8 +16,8 @@ describe('sign', function() nvim('command', 'sign place 34 line=3 name=Foo buffer='..buf2) -- now unplace without specifying a buffer nvim('command', 'sign unplace 34') - eq("\n--- Signs ---\n", nvim('command_output', 'sign place buffer='..buf1)) - eq("\n--- Signs ---\n", nvim('command_output', 'sign place buffer='..buf2)) + eq("--- Signs ---\n", nvim('command_output', 'sign place buffer='..buf1)) + eq("--- Signs ---\n", nvim('command_output', 'sign place buffer='..buf2)) end) end) end) diff --git a/test/functional/ex_cmds/write_spec.lua b/test/functional/ex_cmds/write_spec.lua index 863d439080..bcf83698bb 100644 --- a/test/functional/ex_cmds/write_spec.lua +++ b/test/functional/ex_cmds/write_spec.lua @@ -10,8 +10,6 @@ local feed_command = helpers.feed_command local funcs = helpers.funcs local meths = helpers.meths -if helpers.pending_win32(pending) then return end - local fname = 'Xtest-functional-ex_cmds-write' local fname_bak = fname .. '~' local fname_broken = fname_bak .. 'broken' @@ -36,7 +34,11 @@ describe(':write', function() it('&backupcopy=auto preserves symlinks', function() command('set backupcopy=auto') write_file('test_bkc_file.txt', 'content0') - command("silent !ln -s test_bkc_file.txt test_bkc_link.txt") + if helpers.iswin() then + command("silent !mklink test_bkc_link.txt test_bkc_file.txt") + else + command("silent !ln -s test_bkc_file.txt test_bkc_link.txt") + end source([[ edit test_bkc_link.txt call setline(1, ['content1']) @@ -49,7 +51,11 @@ describe(':write', function() it('&backupcopy=no replaces symlink with new file', function() command('set backupcopy=no') write_file('test_bkc_file.txt', 'content0') - command("silent !ln -s test_bkc_file.txt test_bkc_link.txt") + if helpers.iswin() then + command("silent !mklink test_bkc_link.txt test_bkc_file.txt") + else + command("silent !ln -s test_bkc_file.txt test_bkc_link.txt") + end source([[ edit test_bkc_link.txt call setline(1, ['content1']) @@ -82,8 +88,10 @@ describe(':write', function() command('let $HOME=""') eq(funcs.fnamemodify('.', ':p:h'), funcs.fnamemodify('.', ':p:h:~')) -- Message from check_overwrite - eq(('\nE17: "'..funcs.fnamemodify('.', ':p:h')..'" is a directory'), - redir_exec('write .')) + if not helpers.iswin() then + eq(('\nE17: "'..funcs.fnamemodify('.', ':p:h')..'" is a directory'), + redir_exec('write .')) + end meths.set_option('writeany', true) -- Message from buf_write eq(('\nE502: "." is a directory'), @@ -100,9 +108,16 @@ describe(':write', function() funcs.setfperm(fname, 'r--------') eq('Vim(write):E505: "Xtest-functional-ex_cmds-write" is read-only (add ! to override)', exc_exec('write')) - os.remove(fname) - os.remove(fname_bak) + if helpers.iswin() then + eq(0, os.execute('del /q/f ' .. fname)) + eq(0, os.execute('rd /q/s ' .. fname_bak)) + else + eq(true, os.remove(fname)) + eq(true, os.remove(fname_bak)) + end write_file(fname_bak, 'TTYX') + -- FIXME: exc_exec('write!') outputs 0 in Windows + if helpers.iswin() then return end lfs.link(fname_bak .. ('/xxxxx'):rep(20), fname, true) eq('Vim(write):E166: Can\'t open linked file for writing', exc_exec('write!')) diff --git a/test/functional/fixtures/shell-test.c b/test/functional/fixtures/shell-test.c index 8dbec2aaee..a744d5df46 100644 --- a/test/functional/fixtures/shell-test.c +++ b/test/functional/fixtures/shell-test.c @@ -4,6 +4,18 @@ #include <stdio.h> #include <string.h> #include <stdint.h> +#ifdef _MSC_VER +#include <Windows.h> +#define usleep(usecs) Sleep(usecs/1000) +#else +#include <unistd.h> +#endif + +static void wait(void) +{ + fflush(stdout); + usleep(10*1000); +} static void help(void) { @@ -61,6 +73,22 @@ int main(int argc, char **argv) for (uint8_t i = 0; i < number; i++) { printf("%d: %s\n", (int) i, argv[3]); } + } else if (strcmp(argv[1], "UTF-8") == 0) { + // test split-up UTF-8 sequence + printf("\xc3"); wait(); + printf("\xa5\n"); wait(); + + // split up a 2+2 grapheme clusters all possible ways + printf("ref: \xc3\xa5\xcc\xb2\n"); wait(); + + printf("1: \xc3"); wait(); + printf("\xa5\xcc\xb2\n"); wait(); + + printf("2: \xc3\xa5"); wait(); + printf("\xcc\xb2\n"); wait(); + + printf("3: \xc3\xa5\xcc"); wait(); + printf("\xb2\n"); wait(); } else { fprintf(stderr, "Unknown first argument\n"); return 3; diff --git a/test/functional/fixtures/shell_data.txt b/test/functional/fixtures/shell_data.txt Binary files differnew file mode 100644 index 0000000000..ef3506c5b1 --- /dev/null +++ b/test/functional/fixtures/shell_data.txt diff --git a/test/functional/fixtures/tty-test.c b/test/functional/fixtures/tty-test.c index edcbe23f86..4f0858acdb 100644 --- a/test/functional/fixtures/tty-test.c +++ b/test/functional/fixtures/tty-test.c @@ -41,6 +41,7 @@ static void walk_cb(uv_handle_t *handle, void *arg) } } +#ifndef WIN32 static void sig_handler(int signum) { switch (signum) { @@ -57,6 +58,7 @@ static void sig_handler(int signum) return; } } +#endif #ifdef WIN32 static void sigwinch_cb(uv_signal_t *handle, int signum) @@ -94,7 +96,14 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf) uv_tty_init(&write_loop, &out, fileno(stdout), 0); uv_write_t req; - uv_buf_t b = {.base = buf->base, .len = (size_t)cnt}; + uv_buf_t b = { + .base = buf->base, +#ifdef WIN32 + .len = (ULONG)cnt +#else + .len = (size_t)cnt +#endif + }; uv_write(&req, STRUCT_CAST(uv_stream_t, &out), &b, 1, NULL); uv_run(&write_loop, UV_RUN_DEFAULT); diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index da334d4ac6..bf11042dd6 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -14,10 +14,13 @@ local check_cores = global_helpers.check_cores local check_logs = global_helpers.check_logs local neq = global_helpers.neq local eq = global_helpers.eq +local expect_err = global_helpers.expect_err local ok = global_helpers.ok local map = global_helpers.map +local matches = global_helpers.matches local filter = global_helpers.filter local dedent = global_helpers.dedent +local table_flatten = global_helpers.table_flatten local start_dir = lfs.currentdir() -- XXX: NVIM_PROG takes precedence, QuickBuild sets it. @@ -96,14 +99,14 @@ local function request(method, ...) return rv end -local function next_message() - return session:next_message() +local function next_msg(timeout) + return session:next_message(timeout and timeout or 10000) end local function expect_twostreams(msgs1, msgs2) local pos1, pos2 = 1, 1 while pos1 <= #msgs1 or pos2 <= #msgs2 do - local msg = next_message() + local msg = next_msg() if pos1 <= #msgs1 and pcall(eq, msgs1[pos1], msg) then pos1 = pos1 + 1 elseif pos2 <= #msgs2 then @@ -116,6 +119,46 @@ local function expect_twostreams(msgs1, msgs2) end end +-- Expects a sequence of next_msg() results. If multiple sequences are +-- passed they are tried until one succeeds, in order of shortest to longest. +local function expect_msg_seq(...) + if select('#', ...) < 1 then + error('need at least 1 argument') + end + local seqs = {...} + table.sort(seqs, function(a, b) -- Sort ascending, by (shallow) length. + return #a < #b + end) + + local actual_seq = {} + local final_error = '' + local function cat_err(err1, err2) + if err1 == nil then + return err2 + end + return string.format('%s\n%s\n%s', err1, string.rep('=', 78), err2) + end + for anum = 1, #seqs do + local expected_seq = seqs[anum] + -- Collect enough messages to compare the next expected sequence. + while #actual_seq < #expected_seq do + local msg = next_msg(10000) -- Big timeout for ASAN/valgrind. + if msg == nil then + error(cat_err(final_error, + string.format('got %d messages, expected %d', + #actual_seq, #expected_seq))) + end + table.insert(actual_seq, msg) + end + local status, result = pcall(eq, expected_seq, actual_seq) + if status then + return result + end + final_error = cat_err(final_error, result) + end + error(final_error) +end + local function call_and_stop_on_error(...) local status, result = copcall(...) -- luacheck: ignore if not status then @@ -261,6 +304,7 @@ local function retry(max, max_ms, fn) if status then return result end + luv.update_time() -- Update cached value of luv.now() (libuv: uv_now()). if (max and tries >= max) or (luv.now() - start_time > timeout) then if type(result) == "string" then result = "\nretry() attempts: "..tostring(tries).."\n"..result @@ -333,8 +377,8 @@ local function feed_command(...) end -- Dedent the given text and write it to the file name. -local function write_file(name, text, dont_dedent) - local file = io.open(name, 'w') +local function write_file(name, text, no_dedent, append) + local file = io.open(name, (append and 'a' or 'w')) if type(text) == 'table' then -- Byte blob local bytes = text @@ -342,7 +386,7 @@ local function write_file(name, text, dont_dedent) for _, char in ipairs(bytes) do text = ('%s%c'):format(text, char) end - elseif not dont_dedent then + elseif not no_dedent then text = dedent(text) end file:write(text) @@ -382,9 +426,8 @@ end local function set_shell_powershell() source([[ - set shell=powershell shellquote=\" shellpipe=\| shellredir=> - set shellcmdflag=\ -NoLogo\ -NoProfile\ -ExecutionPolicy\ RemoteSigned\ -Command - let &shellxquote=' ' + set shell=powershell shellquote=( shellpipe=\| shellredir=> shellxquote= + set shellcmdflag=-NoLogo\ -NoProfile\ -ExecutionPolicy\ RemoteSigned\ -Command ]]) end @@ -600,7 +643,12 @@ local function redir_exec(cmd) end local function get_pathsep() - return funcs.fnamemodify('.', ':p'):sub(-1) + return iswin() and '\\' or '/' +end + +local function pathroot() + local pathsep = package.config:sub(1,1) + return iswin() and (nvim_dir:sub(1,2)..pathsep) or '/' end -- Returns a valid, platform-independent $NVIM_LISTEN_ADDRESS. @@ -613,7 +661,7 @@ local function new_pipename() end local function missing_provider(provider) - if provider == 'ruby' then + if provider == 'ruby' or provider == 'node' then local prog = funcs['provider#' .. provider .. '#Detect']() return prog == '' and (provider .. ' not detected') or false elseif provider == 'python' or provider == 'python3' then @@ -644,7 +692,7 @@ local function alter_slashes(obj) end local function hexdump(str) - local len = string.len( str ) + local len = string.len(str) local dump = "" local hex = "" local asc = "" @@ -652,96 +700,99 @@ local function hexdump(str) for i = 1, len do if 1 == i % 8 then dump = dump .. hex .. asc .. "\n" - hex = string.format( "%04x: ", i - 1 ) + hex = string.format("%04x: ", i - 1) asc = "" end - local ord = string.byte( str, i ) - hex = hex .. string.format( "%02x ", ord ) + local ord = string.byte(str, i) + hex = hex .. string.format("%02x ", ord) if ord >= 32 and ord <= 126 then - asc = asc .. string.char( ord ) + asc = asc .. string.char(ord) else asc = asc .. "." end end - return dump .. hex - .. string.rep( " ", 8 - len % 8 ) .. asc - + return dump .. hex .. string.rep(" ", 8 - len % 8) .. asc end local module = { - prepend_argv = prepend_argv, + NIL = mpack.NIL, + alter_slashes = alter_slashes, + buffer = buffer, + bufmeths = bufmeths, + call = nvim_call, clear = clear, + command = nvim_command, connect = connect, - retry = retry, - spawn = spawn, + curbuf = curbuf, + curbuf_contents = curbuf_contents, + curbufmeths = curbufmeths, + curtab = curtab, + curtabmeths = curtabmeths, + curwin = curwin, + curwinmeths = curwinmeths, dedent = dedent, - source = source, - rawfeed = rawfeed, - insert = insert, - iswin = iswin, - feed = feed, - feed_command = feed_command, - eval = nvim_eval, - call = nvim_call, - command = nvim_command, - request = request, - next_message = next_message, - expect_twostreams = expect_twostreams, - run = run, - stop = stop, eq = eq, - neq = neq, + eval = nvim_eval, + exc_exec = exc_exec, expect = expect, expect_any = expect_any, - ok = ok, - map = map, + expect_err = expect_err, + expect_msg_seq = expect_msg_seq, + expect_twostreams = expect_twostreams, + feed = feed, + feed_command = feed_command, filter = filter, + funcs = funcs, + get_pathsep = get_pathsep, + hexdump = hexdump, + insert = insert, + iswin = iswin, + map = map, + matches = matches, + merge_args = merge_args, + meth_pcall = meth_pcall, + meths = meths, + missing_provider = missing_provider, + mkdir = lfs.mkdir, + neq = neq, + new_pipename = new_pipename, + next_msg = next_msg, nvim = nvim, + nvim_argv = nvim_argv, nvim_async = nvim_async, + nvim_dir = nvim_dir, nvim_prog = nvim_prog, - nvim_argv = nvim_argv, nvim_set = nvim_set, - nvim_dir = nvim_dir, - buffer = buffer, - window = window, - tabpage = tabpage, - curbuf = curbuf, - curwin = curwin, - curtab = curtab, - curbuf_contents = curbuf_contents, - wait = wait, - sleep = sleep, - set_session = set_session, - write_file = write_file, - read_file = read_file, + ok = ok, os_name = os_name, - rmdir = rmdir, - mkdir = lfs.mkdir, - exc_exec = exc_exec, - redir_exec = redir_exec, - merge_args = merge_args, - funcs = funcs, - meths = meths, - bufmeths = bufmeths, - winmeths = winmeths, - tabmeths = tabmeths, - uimeths = uimeths, - curbufmeths = curbufmeths, - curwinmeths = curwinmeths, - curtabmeths = curtabmeths, + pathroot = pathroot, pending_win32 = pending_win32, - skip_fragile = skip_fragile, + prepend_argv = prepend_argv, + rawfeed = rawfeed, + read_file = read_file, + redir_exec = redir_exec, + request = request, + retry = retry, + rmdir = rmdir, + run = run, + set_session = set_session, set_shell_powershell = set_shell_powershell, + skip_fragile = skip_fragile, + sleep = sleep, + source = source, + spawn = spawn, + stop = stop, + table_flatten = table_flatten, + tabmeths = tabmeths, + tabpage = tabpage, tmpname = tmpname, - meth_pcall = meth_pcall, - NIL = mpack.NIL, - get_pathsep = get_pathsep, - missing_provider = missing_provider, - alter_slashes = alter_slashes, - hexdump = hexdump, - new_pipename = new_pipename, + uimeths = uimeths, + wait = wait, + window = window, + winmeths = winmeths, + write_file = write_file, } return function(after_each) diff --git a/test/functional/insert/insert_spec.lua b/test/functional/insert/insert_spec.lua new file mode 100644 index 0000000000..427954f5a6 --- /dev/null +++ b/test/functional/insert/insert_spec.lua @@ -0,0 +1,41 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert +local command = helpers.command +local eq = helpers.eq +local expect = helpers.expect +local funcs = helpers.funcs + +describe('insert-mode', function() + before_each(function() + clear() + end) + + it('CTRL-@', function() + -- Inserts last-inserted text, leaves insert-mode. + insert('hello') + feed('i<C-@>x') + expect('hellhello') + + -- C-Space is the same as C-@. + -- CTRL-SPC inserts last-inserted text, leaves insert-mode. + feed('i<C-Space>x') + expect('hellhellhello') + + -- CTRL-A inserts last inserted text + feed('i<C-A>x') + expect('hellhellhellhelloxo') + end) + + it('ALT/META #8213', function() + -- Mapped ALT-chord behaves as mapped. + command('inoremap <M-l> meta-l') + command('inoremap <A-j> alt-j') + feed('i<M-l> xxx <A-j><M-h>a<A-h>') + expect('meta-l xxx alt-j') + eq({ 0, 1, 14, 0, }, funcs.getpos('.')) + -- Unmapped ALT-chord behaves as ESC+c. + command('iunmap <M-l>') + feed('0i<M-l>') + eq({ 0, 1, 2, 0, }, funcs.getpos('.')) + end) +end) diff --git a/test/functional/insert/last_inserted_spec.lua b/test/functional/insert/last_inserted_spec.lua deleted file mode 100644 index dce23a3790..0000000000 --- a/test/functional/insert/last_inserted_spec.lua +++ /dev/null @@ -1,22 +0,0 @@ -local helpers = require('test.functional.helpers')(after_each) -local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert -local expect = helpers.expect - -clear() - -describe('insert-mode', function() - it('CTRL-@ inserts last-inserted text, leaves insert-mode', function() - insert('hello') - feed('i<C-@>x') - expect('hellhello') - end) - -- C-Space is the same as C-@ - it('CTRL-SPC inserts last-inserted text, leaves insert-mode', function() - feed('i<C-Space>x') - expect('hellhellhello') - end) - it('CTRL-A inserts last inserted text', function() - feed('i<C-A>x') - expect('hellhellhellhelloxo') - end) -end) diff --git a/test/functional/legacy/003_cindent_spec.lua b/test/functional/legacy/003_cindent_spec.lua index 58e87354fb..1cede8a7d7 100644 --- a/test/functional/legacy/003_cindent_spec.lua +++ b/test/functional/legacy/003_cindent_spec.lua @@ -1,4 +1,5 @@ -- Test for 'cindent'. +-- For new tests, consider putting them in test_cindent.vim. -- -- There are 50+ test command blocks (the stuff between STARTTEST and ENDTEST) -- in the original test. These have been converted to "it" test cases here. @@ -1956,7 +1957,8 @@ describe('cindent', function() } ]=]) - feed_command('set tw=0 wm=60 columns=80 noai fo=croq') + feed_command('set tw=0 noai fo=croq') + feed_command('let &wm = &columns - 20') feed_command('/serious/e') feed('a about life, the universe, and the rest<esc>') diff --git a/test/functional/legacy/008_autocommands_spec.lua b/test/functional/legacy/008_autocommands_spec.lua index 7474f1e068..453638ce45 100644 --- a/test/functional/legacy/008_autocommands_spec.lua +++ b/test/functional/legacy/008_autocommands_spec.lua @@ -5,9 +5,10 @@ local helpers = require('test.functional.helpers')(after_each) local feed, source = helpers.feed, helpers.source local clear, feed_command, expect, eq, eval = helpers.clear, helpers.feed_command, helpers.expect, helpers.eq, helpers.eval local write_file, wait, dedent = helpers.write_file, helpers.wait, helpers.dedent -local io = require('io') +local read_file = helpers.read_file describe('autocommands that delete and unload buffers:', function() + local test_file = 'Xtest-008_autocommands.out' local text1 = dedent([[ start of Xxx1 test @@ -18,7 +19,7 @@ describe('autocommands that delete and unload buffers:', function() write_file('Xxx2', text2..'\n') end) teardown(function() - os.remove('test.out') + os.remove(test_file) os.remove('Xxx1') os.remove('Xxx2') end) @@ -65,7 +66,8 @@ describe('autocommands that delete and unload buffers:', function() endwhile endfunc func WriteToOut() - edit! test.out + edit! ]]..test_file..[[ + $put ='VimLeave done' write endfunc @@ -86,6 +88,6 @@ describe('autocommands that delete and unload buffers:', function() feed_command('q') wait() eq('VimLeave done', - string.match(io.open('test.out', 'r'):read('*all'), "^%s*(.-)%s*$")) + string.match(read_file(test_file), "^%s*(.-)%s*$")) end) end) diff --git a/test/functional/legacy/011_autocommands_spec.lua b/test/functional/legacy/011_autocommands_spec.lua index d969a8bd37..c2667d28d2 100644 --- a/test/functional/legacy/011_autocommands_spec.lua +++ b/test/functional/legacy/011_autocommands_spec.lua @@ -18,10 +18,9 @@ local clear, feed_command, expect, eq, neq, dedent, write_file, feed = helpers.clear, helpers.feed_command, helpers.expect, helpers.eq, helpers.neq, helpers.dedent, helpers.write_file, helpers.feed -if helpers.pending_win32(pending) then return end - local function has_gzip() - return os.execute('gzip --help >/dev/null 2>&1') == 0 + local null = helpers.iswin() and 'nul' or '/dev/null' + return os.execute('gzip --help >' .. null .. ' 2>&1') == 0 end local function prepare_gz_file(name, text) @@ -142,6 +141,7 @@ describe('file reading, writing and bufnew and filter autocommands', function() end) it('FilterReadPre, FilterReadPost', function() + if helpers.pending_win32(pending) then return end -- Write a special input file for this test block. write_file('test.out', dedent([[ startstart diff --git a/test/functional/legacy/025_jump_tag_hidden_spec.lua b/test/functional/legacy/025_jump_tag_hidden_spec.lua index 0d51b4da26..dd89a3680e 100644 --- a/test/functional/legacy/025_jump_tag_hidden_spec.lua +++ b/test/functional/legacy/025_jump_tag_hidden_spec.lua @@ -5,8 +5,6 @@ local helpers = require('test.functional.helpers')(after_each) local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert local feed_command, expect = helpers.feed_command, helpers.expect -if helpers.pending_win32(pending) then return end - describe('jump to a tag with hidden set', function() setup(clear) @@ -25,12 +23,17 @@ describe('jump to a tag with hidden set', function() feed_command('set hidden') -- Create a link from test25.dir to the current directory. - feed_command('!rm -f test25.dir') - feed_command('!ln -s . test25.dir') + if helpers.iswin() then + feed_command('!rd /q/s test25.dir') + feed_command('!mklink /j test25.dir .') + else + feed_command('!rm -f test25.dir') + feed_command('!ln -s . test25.dir') + end -- Create tags.text, with the current directory name inserted. feed_command('/tags line') - feed_command('r !pwd') + feed_command('r !' .. (helpers.iswin() and 'cd' or 'pwd')) feed('d$/test<cr>') feed('hP:.w! tags.test<cr>') @@ -39,7 +42,13 @@ describe('jump to a tag with hidden set', function() -- space will then be eaten by hit-return, instead of moving the cursor to 'd'. feed_command('set tags=tags.test') feed('G<C-]> x:yank a<cr>') - feed_command('!rm -f Xxx test25.dir tags.test') + feed_command("call delete('tags.test')") + feed_command("call delete('Xxx')") + if helpers.iswin() then + feed_command('!rd /q test25.dir') + else + feed_command('!rm -f test25.dir') + end -- Put @a and remove empty line feed_command('%d') diff --git a/test/functional/legacy/030_fileformats_spec.lua b/test/functional/legacy/030_fileformats_spec.lua index 7384fdf847..2fd51602d8 100644 --- a/test/functional/legacy/030_fileformats_spec.lua +++ b/test/functional/legacy/030_fileformats_spec.lua @@ -5,8 +5,6 @@ local feed, clear, command = helpers.feed, helpers.clear, helpers.command local eq, write_file = helpers.eq, helpers.write_file local wait = helpers.wait -if helpers.pending_win32(pending) then return end - describe('fileformats option', function() setup(function() clear() diff --git a/test/functional/legacy/051_highlight_spec.lua b/test/functional/legacy/051_highlight_spec.lua index 2ef74196ee..40f70de2ec 100644 --- a/test/functional/legacy/051_highlight_spec.lua +++ b/test/functional/legacy/051_highlight_spec.lua @@ -8,8 +8,6 @@ local eq = helpers.eq local wait = helpers.wait local exc_exec = helpers.exc_exec -if helpers.pending_win32(pending) then return end - describe(':highlight', function() setup(clear) diff --git a/test/functional/legacy/059_utf8_spell_checking_spec.lua b/test/functional/legacy/059_utf8_spell_checking_spec.lua index 120e469ab2..8630ac58ef 100644 --- a/test/functional/legacy/059_utf8_spell_checking_spec.lua +++ b/test/functional/legacy/059_utf8_spell_checking_spec.lua @@ -5,8 +5,6 @@ local feed, insert, source = helpers.feed, helpers.insert, helpers.source local clear, feed_command, expect = helpers.clear, helpers.feed_command, helpers.expect local write_file, call = helpers.write_file, helpers.call -if helpers.pending_win32(pending) then return end - local function write_latin1(name, text) text = call('iconv', text, 'utf-8', 'latin-1') write_file(name, text) @@ -507,8 +505,13 @@ describe("spell checking with 'encoding' set to utf-8", function() -- Vim function in the original legacy test. local function test_one(aff, dic) -- Generate a .spl file from a .dic and .aff file. - os.execute('cp -f Xtest'..aff..'.aff Xtest.aff') - os.execute('cp -f Xtest'..dic..'.dic Xtest.dic') + if helpers.iswin() then + os.execute('copy /y Xtest'..aff..'.aff Xtest.aff') + os.execute('copy /y Xtest'..dic..'.dic Xtest.dic') + else + os.execute('cp -f Xtest'..aff..'.aff Xtest.aff') + os.execute('cp -f Xtest'..dic..'.dic Xtest.dic') + end source([[ set spellfile= function! SpellDumpNoShow() @@ -559,7 +562,11 @@ describe("spell checking with 'encoding' set to utf-8", function() feed_command([[$put =soundfold('kóopërÿnôven')]]) feed_command([[$put =soundfold('oeverloos gezwets edale')]]) -- And now with SAL instead of SOFO items; test automatic reloading. - os.execute('cp -f Xtest-sal.aff Xtest.aff') + if helpers.iswin() then + os.execute('copy /y Xtest-sal.aff Xtest.aff') + else + os.execute('cp -f Xtest-sal.aff Xtest.aff') + end feed_command('mkspell! Xtest Xtest') feed_command([[$put =soundfold('goobledygoook')]]) feed_command([[$put =soundfold('kóopërÿnôven')]]) diff --git a/test/functional/legacy/063_match_and_matchadd_spec.lua b/test/functional/legacy/063_match_and_matchadd_spec.lua index a505a2db30..518d79861b 100644 --- a/test/functional/legacy/063_match_and_matchadd_spec.lua +++ b/test/functional/legacy/063_match_and_matchadd_spec.lua @@ -114,9 +114,11 @@ describe('063: Test for ":match", "matchadd()" and related functions', function( command("call clearmatches()") eq('\nE714: List required', redir_exec("let rf1 = setmatches(0)")) eq(-1, eval('rf1')) - eq('\nE474: Invalid argument', redir_exec("let rf2 = setmatches([0])")) + eq('\nE474: List item 0 is either not a dictionary or an empty one', + redir_exec("let rf2 = setmatches([0])")) eq(-1, eval('rf2')) - eq('\nE474: Invalid argument', redir_exec("let rf3 = setmatches([{'wrong key': 'wrong value'}])")) + eq('\nE474: List item 0 is missing one of the required keys', + redir_exec("let rf3 = setmatches([{'wrong key': 'wrong value'}])")) eq(-1, eval('rf3')) -- Check that "matchaddpos()" positions matches correctly diff --git a/test/functional/legacy/077_mf_hash_grow_spec.lua b/test/functional/legacy/077_mf_hash_grow_spec.lua index c692127213..4719a3ecbf 100644 --- a/test/functional/legacy/077_mf_hash_grow_spec.lua +++ b/test/functional/legacy/077_mf_hash_grow_spec.lua @@ -18,7 +18,8 @@ describe('mf_hash_grow()', function() setup(clear) -- Check to see if cksum exists, otherwise skip the test - if os.execute('which cksum 2>&1 > /dev/null') ~= 0 then + local null = helpers.iswin() and 'nul' or '/dev/null' + if os.execute('cksum --help >' .. null .. ' 2>&1') ~= 0 then pending('was not tested because cksum was not found', function() end) else it('is working', function() diff --git a/test/functional/legacy/089_number_relnumber_findfile_spec.lua b/test/functional/legacy/089_number_relnumber_findfile_spec.lua deleted file mode 100644 index 6708fd50b7..0000000000 --- a/test/functional/legacy/089_number_relnumber_findfile_spec.lua +++ /dev/null @@ -1,116 +0,0 @@ --- - Some tests for setting 'number' and 'relativenumber' --- This is not all that useful now that the options are no longer reset when --- setting the other. - -local helpers = require('test.functional.helpers')(after_each) -local feed = helpers.feed -local clear, expect, source = helpers.clear, helpers.expect, helpers.source - -describe("setting 'number' and 'relativenumber'", function() - setup(clear) - - it('is working', function() - source([[ - set hidden nu rnu - redir @a | set nu? | set rnu? | redir END - e! xx - redir @b | set nu? | set rnu? | redir END - e! # - $put ='results:' - $put a - $put b - - set nonu nornu - setglobal nu - setlocal rnu - redir @c | setglobal nu? | redir END - set nonu nornu - setglobal rnu - setlocal nu - redir @d | setglobal rnu? | redir END - $put =':setlocal must NOT reset the other global value' - $put c - $put d - - set nonu nornu - setglobal nu - setglobal rnu - redir @e | setglobal nu? | redir END - set nonu nornu - setglobal rnu - setglobal nu - redir @f | setglobal rnu? | redir END - $put =':setglobal MUST reset the other global value' - $put e - $put f - - set nonu nornu - set nu - set rnu - redir @g | setglobal nu? | redir END - set nonu nornu - set rnu - set nu - redir @h | setglobal rnu? | redir END - $put =':set MUST reset the other global value' - $put g - $put h - ]]) - - -- Remove empty line - feed('ggdd') - - -- Assert buffer contents. - expect([[ - results: - - number - relativenumber - - number - relativenumber - :setlocal must NOT reset the other global value - - number - - relativenumber - :setglobal MUST reset the other global value - - number - - relativenumber - :set MUST reset the other global value - - number - - relativenumber]]) - end) -end) - --- - Some tests for findfile() function -describe('findfile', function() - setup(clear) - - it('is working', function() - -- Assume test is being run from project root - source([[ - $put ='Testing findfile' - $put ='' - set ssl - $put =findfile('vim.c','src/nvim/ap*') - cd src/nvim - $put =findfile('vim.c','ap*') - $put =findfile('vim.c','api') - ]]) - - -- Remove empty line - feed('ggdd') - - expect([[ - Testing findfile - - src/nvim/api/vim.c - api/vim.c - api/vim.c]]) - end) -end) diff --git a/test/functional/legacy/093_mksession_cursor_cols_latin1_spec.lua b/test/functional/legacy/093_mksession_cursor_cols_latin1_spec.lua index b1221ff8b6..f09fd9a6e5 100644 --- a/test/functional/legacy/093_mksession_cursor_cols_latin1_spec.lua +++ b/test/functional/legacy/093_mksession_cursor_cols_latin1_spec.lua @@ -7,8 +7,6 @@ local helpers = require('test.functional.helpers')(after_each) local feed, insert = helpers.feed, helpers.insert local clear, feed_command, expect = helpers.clear, helpers.feed_command, helpers.expect -if helpers.pending_win32(pending) then return end - describe('store cursor position in session file in Latin-1', function() setup(clear) diff --git a/test/functional/legacy/096_location_list_spec.lua b/test/functional/legacy/096_location_list_spec.lua index 85c4fe0ec4..b21a2085f6 100644 --- a/test/functional/legacy/096_location_list_spec.lua +++ b/test/functional/legacy/096_location_list_spec.lua @@ -11,10 +11,10 @@ local source = helpers.source local clear, command, expect = helpers.clear, helpers.command, helpers.expect describe('location list', function() + local test_file = 'Xtest-096_location_list.out' setup(clear) - teardown(function() - os.remove('test.out') + os.remove(test_file) end) it('is working', function() @@ -70,9 +70,9 @@ describe('location list', function() endfor ]]) - -- Set up the result buffer "test.out". + -- Set up the result buffer. command('enew') - command('w! test.out') + command('w! '..test_file) command('b 1') -- Test A. @@ -99,7 +99,7 @@ describe('location list', function() command([[let locationListFileName = substitute(getline(line('.')), '\([^|]*\)|.*', '\1', '')]]) command('wincmd n') command('wincmd K') - command('b test.out') + command('b '..test_file) -- Prepare test output and write it to the result buffer. command([[let fileName = substitute(fileName, '\\', '/', 'g')]]) @@ -132,7 +132,7 @@ describe('location list', function() command('let numberOfWindowsOpen = winnr("$")') command('wincmd n') command('wincmd K') - command('b test.out') + command('b '..test_file) -- Prepare test output and write it to the result buffer. command('call append(line("$"), "Test B:")') @@ -170,7 +170,7 @@ describe('location list', function() command('let bufferName = expand("%")') command('wincmd n') command('wincmd K') - command('b test.out') + command('b '..test_file) -- Prepare test output and write it to the result buffer. command([[let bufferName = substitute(bufferName, '\\', '/', 'g')]]) diff --git a/test/functional/legacy/097_glob_path_spec.lua b/test/functional/legacy/097_glob_path_spec.lua index 6b63a317f1..907f0665ae 100644 --- a/test/functional/legacy/097_glob_path_spec.lua +++ b/test/functional/legacy/097_glob_path_spec.lua @@ -6,15 +6,19 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local command, expect = helpers.command, helpers.expect -if helpers.pending_win32(pending) then return end - describe('glob() and globpath()', function() setup(clear) setup(function() - os.execute("mkdir -p sautest/autoload") - os.execute("touch sautest/autoload/Test104.vim") - os.execute("touch sautest/autoload/footest.vim") + if helpers.iswin() then + os.execute("md sautest\\autoload") + os.execute(".>sautest\\autoload\\Test104.vim 2>nul") + os.execute(".>sautest\\autoload\\footest.vim 2>nul") + else + os.execute("mkdir -p sautest/autoload") + os.execute("touch sautest/autoload/Test104.vim") + os.execute("touch sautest/autoload/footest.vim") + end end) it('is working', function() @@ -24,29 +28,55 @@ describe('glob() and globpath()', function() -- Consistent sorting of file names command('set nofileignorecase') - command([[$put =glob('Xxx\{')]]) - command([[$put =glob('Xxx\$')]]) + if helpers.iswin() then + command([[$put =glob('Xxx{')]]) + command([[$put =glob('Xxx$')]]) + + command('silent w! Xxx{') + command([[w! Xxx$]]) + command([[$put =glob('Xxx{')]]) + command([[$put =glob('Xxx$')]]) + + command([[$put =string(globpath('sautest\autoload', '*.vim'))]]) + command([[$put =string(globpath('sautest\autoload', '*.vim', 0, 1))]]) + expect([=[ + + - command('silent w! Xxx{') - command([[w! Xxx\$]]) - command([[$put =glob('Xxx\{')]]) - command([[$put =glob('Xxx\$')]]) + Xxx{ + Xxx$ + 'sautest\autoload\Test104.vim + sautest\autoload\footest.vim' + ['sautest\autoload\Test104.vim', 'sautest\autoload\footest.vim']]=]) + else + command([[$put =glob('Xxx\{')]]) + command([[$put =glob('Xxx\$')]]) - command("$put =string(globpath('sautest/autoload', '*.vim'))") - command("$put =string(globpath('sautest/autoload', '*.vim', 0, 1))") + command('silent w! Xxx{') + command([[w! Xxx\$]]) + command([[$put =glob('Xxx\{')]]) + command([[$put =glob('Xxx\$')]]) - expect([=[ + command("$put =string(globpath('sautest/autoload', '*.vim'))") + command("$put =string(globpath('sautest/autoload', '*.vim', 0, 1))") + expect([=[ - Xxx{ - Xxx$ - 'sautest/autoload/Test104.vim - sautest/autoload/footest.vim' - ['sautest/autoload/Test104.vim', 'sautest/autoload/footest.vim']]=]) + Xxx{ + Xxx$ + 'sautest/autoload/Test104.vim + sautest/autoload/footest.vim' + ['sautest/autoload/Test104.vim', 'sautest/autoload/footest.vim']]=]) + end end) teardown(function() - os.execute("rm -rf sautest Xxx{ Xxx$") + if helpers.iswin() then + os.execute('del /q/f Xxx{ Xxx$') + os.execute('rd /q sautest') + else + os.execute("rm -rf sautest Xxx{ Xxx$") + end end) end) 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 836a0f8f24..239f60341a 100644 --- a/test/functional/legacy/107_adjust_window_and_contents_spec.lua +++ b/test/functional/legacy/107_adjust_window_and_contents_spec.lua @@ -8,8 +8,6 @@ local clear = helpers.clear local insert = helpers.insert local command = helpers.command -if helpers.pending_win32(pending) then return end - describe('107', function() setup(clear) diff --git a/test/functional/legacy/arglist_spec.lua b/test/functional/legacy/arglist_spec.lua index 191f145095..bd65e549ef 100644 --- a/test/functional/legacy/arglist_spec.lua +++ b/test/functional/legacy/arglist_spec.lua @@ -4,8 +4,6 @@ local helpers = require('test.functional.helpers')(after_each) local clear, command, eq = helpers.clear, helpers.command, helpers.eq local eval, exc_exec, neq = helpers.eval, helpers.exc_exec, helpers.neq -if helpers.pending_win32(pending) then return end - describe('argument list commands', function() before_each(clear) @@ -222,20 +220,19 @@ describe('argument list commands', function() eq({'a', 'b'}, eval('argv()')) eq('b', eval('expand("%:t")')) command('argedit a') - eq({'a', 'b'}, eval('argv()')) + eq({'a', 'b', 'a'}, eval('argv()')) eq('a', eval('expand("%:t")')) command('argedit c') - eq({'a', 'c', 'b'}, eval('argv()')) + eq({'a', 'b', 'a', 'c'}, eval('argv()')) command('0argedit x') - eq({'x', 'a', 'c', 'b'}, eval('argv()')) + eq({'x', 'a', 'b', 'a', 'c'}, eval('argv()')) command('enew! | set modified') assert_fails('argedit y', 'E37:') command('argedit! y') - eq({'x', 'y', 'a', 'c', 'b'}, eval('argv()')) + eq({'x', 'y', 'y', 'a', 'b', 'a', 'c'}, eval('argv()')) command('%argd') - -- Nvim allows unescaped spaces in filename on all platforms. #6010 command('argedit a b') - eq({'a b'}, eval('argv()')) + eq({'a', 'b'}, eval('argv()')) end) it('test for :argdelete command', function() diff --git a/test/functional/legacy/delete_spec.lua b/test/functional/legacy/delete_spec.lua index aeaab335e8..5ef456bfe3 100644 --- a/test/functional/legacy/delete_spec.lua +++ b/test/functional/legacy/delete_spec.lua @@ -2,8 +2,6 @@ local helpers = require('test.functional.helpers')(after_each) local clear, source = helpers.clear, helpers.source local eq, eval, command = helpers.eq, helpers.eval, helpers.command -if helpers.pending_win32(pending) then return end - describe('Test for delete()', function() before_each(clear) @@ -48,7 +46,11 @@ describe('Test for delete()', function() split Xfile call setline(1, ['a', 'b']) wq - silent !ln -s Xfile Xlink + if has('win32') + silent !mklink Xlink Xfile + else + silent !ln -s Xfile Xlink + endif ]]) -- Delete the link, not the file eq(0, eval("delete('Xlink')")) @@ -58,7 +60,11 @@ describe('Test for delete()', function() it('symlink directory delete', function() command("call mkdir('Xdir1')") - command("silent !ln -s Xdir1 Xlink") + if helpers.iswin() then + command("silent !mklink /j Xlink Xdir1") + else + command("silent !ln -s Xdir1 Xlink") + end eq(1, eval("isdirectory('Xdir1')")) eq(1, eval("isdirectory('Xlink')")) -- Delete the link, not the directory @@ -78,7 +84,11 @@ describe('Test for delete()', function() w Xdir3/subdir/Xfile w Xdir4/Xfile close - silent !ln -s ../Xdir4 Xdir3/Xlink + if has('win32') + silent !mklink /j Xdir3\Xlink Xdir4 + else + silent !ln -s ../Xdir4 Xdir3/Xlink + endif ]]) eq(1, eval("isdirectory('Xdir3')")) diff --git a/test/functional/legacy/edit_spec.lua b/test/functional/legacy/edit_spec.lua new file mode 100644 index 0000000000..91d602924c --- /dev/null +++ b/test/functional/legacy/edit_spec.lua @@ -0,0 +1,25 @@ +-- Test for edit functions +-- See also: src/nvim/testdir/test_edit.vim + +local helpers = require('test.functional.helpers')(after_each) +local source = helpers.source +local eq, eval = helpers.eq, helpers.eval +local funcs = helpers.funcs +local clear = helpers.clear + +describe('edit', function() + before_each(clear) + + it('reset insertmode from i_ctrl-r_=', function() + source([=[ + call setline(1, ['abc']) + call cursor(1, 4) + call feedkeys(":set im\<cr>ZZZ\<c-r>=setbufvar(1,'&im', 0)\<cr>",'tnix') + ]=]) + eq({'abZZZc'}, funcs.getline(1,'$')) + eq({0, 1, 1, 0}, funcs.getpos('.')) + eq(0, eval('&im')) + end) + +end) + diff --git a/test/functional/legacy/fixeol_spec.lua b/test/functional/legacy/fixeol_spec.lua index 801451b300..50236e8617 100644 --- a/test/functional/legacy/fixeol_spec.lua +++ b/test/functional/legacy/fixeol_spec.lua @@ -4,15 +4,14 @@ local helpers = require('test.functional.helpers')(after_each) local feed = helpers.feed local clear, feed_command, expect = helpers.clear, helpers.feed_command, helpers.expect -if helpers.pending_win32(pending) then return end - describe('fixeol', function() local function rmtestfiles() - os.remove('test.out') - os.remove('XXEol') - os.remove('XXNoEol') - os.remove('XXTestEol') - os.remove('XXTestNoEol') + feed_command('%bwipeout!') + feed_command('call delete("test.out")') + feed_command('call delete("XXEol")') + feed_command('call delete("XXNoEol")') + feed_command('call delete("XXTestEol")') + feed_command('call delete("XXTestNoEol")') end setup(function() clear() diff --git a/test/functional/legacy/fnamemodify_spec.lua b/test/functional/legacy/fnamemodify_spec.lua index d8ecbfe058..7e859bf0cf 100644 --- a/test/functional/legacy/fnamemodify_spec.lua +++ b/test/functional/legacy/fnamemodify_spec.lua @@ -4,8 +4,6 @@ local helpers = require('test.functional.helpers')(after_each) local clear, source = helpers.clear, helpers.source local call, eq, nvim = helpers.call, helpers.eq, helpers.meths -if helpers.pending_win32(pending) then return end - local function expected_empty() eq({}, nvim.get_vvar('errors')) end @@ -16,17 +14,21 @@ describe('filename modifiers', function() source([=[ func Test_fnamemodify() - let tmpdir = resolve('/tmp') + if has('win32') + set shellslash + else + set shell=sh + endif + let tmpdir = resolve($TMPDIR) + call assert_true(isdirectory(tmpdir)) execute 'cd '. tmpdir - set shell=sh - set shellslash let $HOME=fnamemodify('.', ':p:h:h:h') call assert_equal('/', fnamemodify('.', ':p')[-1:]) - call assert_equal('p', fnamemodify('.', ':p:h')[-1:]) + call assert_equal(tmpdir[strchars(tmpdir) - 1], fnamemodify('.', ':p:h')[-1:]) call assert_equal('t', fnamemodify('test.out', ':p')[-1:]) call assert_equal('test.out', fnamemodify('test.out', ':.')) call assert_equal('../testdir/a', fnamemodify('../testdir/a', ':.')) - call assert_equal('test.out', fnamemodify('test.out', ':~')) + call assert_equal(fnamemodify(tmpdir, ':~').'/test.out', fnamemodify('test.out', ':~')) call assert_equal('../testdir/a', fnamemodify('../testdir/a', ':~')) call assert_equal('a', fnamemodify('../testdir/a', ':t')) call assert_equal('', fnamemodify('.', ':p:t')) @@ -53,8 +55,10 @@ describe('filename modifiers', function() quit call assert_equal("'abc\ndef'", fnamemodify("abc\ndef", ':S')) - set shell=tcsh - call assert_equal("'abc\\\ndef'", fnamemodify("abc\ndef", ':S')) + if executable('tcsh') + set shell=tcsh + call assert_equal("'abc\\\ndef'", fnamemodify("abc\ndef", ':S')) + endif endfunc func Test_expand() diff --git a/test/functional/legacy/getcwd_spec.lua b/test/functional/legacy/getcwd_spec.lua index 8fb31ccd22..eae13da528 100644 --- a/test/functional/legacy/getcwd_spec.lua +++ b/test/functional/legacy/getcwd_spec.lua @@ -4,8 +4,6 @@ local helpers = require('test.functional.helpers')(after_each) local eq, eval, source = helpers.eq, helpers.eval, helpers.source local call, clear, command = helpers.call, helpers.clear, helpers.command -if helpers.pending_win32(pending) then return end - describe('getcwd', function() before_each(clear) diff --git a/test/functional/legacy/packadd_spec.lua b/test/functional/legacy/packadd_spec.lua index 2dfd36142b..fb308475c0 100644 --- a/test/functional/legacy/packadd_spec.lua +++ b/test/functional/legacy/packadd_spec.lua @@ -9,17 +9,15 @@ local function expected_empty() eq({}, nvim.get_vvar('errors')) end -if helpers.pending_win32(pending) then return end - describe('packadd', function() before_each(function() clear() source([=[ func SetUp() - let s:topdir = expand('%:p:h') . '/Xdir' + let s:topdir = expand(expand('%:p:h') . '/Xdir') exe 'set packpath=' . s:topdir - let s:plugdir = s:topdir . '/pack/mine/opt/mytest' + let s:plugdir = expand(s:topdir . '/pack/mine/opt/mytest') endfunc func TearDown() @@ -52,8 +50,8 @@ describe('packadd', function() call assert_equal(77, g:plugin_also_works) call assert_true(17, g:ftdetect_works) call assert_true(len(&rtp) > len(rtp)) - call assert_true(&rtp =~ (s:plugdir . '\($\|,\)')) - call assert_true(&rtp =~ (s:plugdir . '/after$')) + call assert_true(&rtp =~ (escape(s:plugdir, '\') . '\($\|,\)')) + call assert_true(&rtp =~ escape(expand(s:plugdir . '/after$'), '\')) " Check exception call assert_fails("packadd directorynotfound", 'E919:') @@ -74,7 +72,7 @@ describe('packadd', function() packadd! mytest call assert_true(len(&rtp) > len(rtp)) - call assert_true(&rtp =~ (s:plugdir . '\($\|,\)')) + call assert_true(&rtp =~ (escape(s:plugdir, '\') . '\($\|,\)')) call assert_equal(0, g:plugin_works) " check the path is not added twice @@ -84,17 +82,18 @@ describe('packadd', function() endfunc func Test_packadd_symlink_dir() - if !has('unix') - return - endif - let top2_dir = s:topdir . '/Xdir2' - let real_dir = s:topdir . '/Xsym' + let top2_dir = expand(s:topdir . '/Xdir2') + let real_dir = expand(s:topdir . '/Xsym') call mkdir(real_dir, 'p') - exec "silent! !ln -s Xsym" top2_dir - let &rtp = top2_dir . ',' . top2_dir . '/after' + if has('win32') + exec "silent! !mklink /d" top2_dir "Xsym" + else + exec "silent! !ln -s Xsym" top2_dir + endif + let &rtp = top2_dir . ',' . expand(top2_dir . '/after') let &packpath = &rtp - let s:plugdir = top2_dir . '/pack/mine/opt/mytest' + let s:plugdir = expand(top2_dir . '/pack/mine/opt/mytest') call mkdir(s:plugdir . '/plugin', 'p') exe 'split ' . s:plugdir . '/plugin/test.vim' @@ -105,7 +104,7 @@ describe('packadd', function() packadd mytest " Must have been inserted in the middle, not at the end - call assert_true(&rtp =~ '/pack/mine/opt/mytest,') + call assert_true(&rtp =~ escape(expand('/pack/mine/opt/mytest').',', '\')) call assert_equal(44, g:plugin_works) " No change when doing it again. @@ -115,7 +114,7 @@ describe('packadd', function() set rtp& let rtp = &rtp - exec "silent !rm" top2_dir + exec "silent !" (has('win32') ? "rd /q/s" : "rm") top2_dir endfunc func Test_packloadall() diff --git a/test/functional/legacy/search_spec.lua b/test/functional/legacy/search_spec.lua index 5f71861821..277d8d6c7f 100644 --- a/test/functional/legacy/search_spec.lua +++ b/test/functional/legacy/search_spec.lua @@ -6,6 +6,7 @@ local eq = helpers.eq local eval = helpers.eval local feed = helpers.feed local funcs = helpers.funcs +local wait = helpers.wait describe('search cmdline', function() local screen @@ -471,4 +472,113 @@ describe('search cmdline', function() coladd = 0, skipcol = 0, curswant = 0}, funcs.winsaveview()) end) + + it("CTRL-G with 'incsearch' and ? goes in the right direction", function() + -- oldtest: Test_search_cmdline4(). + screen:detach() + screen = Screen.new(40, 4) + screen:attach() + screen:set_default_attr_ids({ + inc = {reverse = true}, + err = { foreground = Screen.colors.Grey100, background = Screen.colors.Red }, + more = { bold = true, foreground = Screen.colors.SeaGreen4 }, + tilde = { bold = true, foreground = Screen.colors.Blue1 }, + }) + command('enew!') + funcs.setline(1, {' 1 the first', ' 2 the second', ' 3 the third'}) + command('set laststatus=0 shortmess+=s') + command('set incsearch') + command('$') + -- Send the input in chunks, so the cmdline logic regards it as + -- "interactive". This mimics Vim's test_override("char_avail"). + -- (See legacy test: test_search.vim) + feed('?the') + wait() + feed('<c-g>') + wait() + feed('<cr>') + screen:expect([[ + 1 the first | + 2 the second | + 3 ^the third | + ?the | + ]]) + + command('$') + feed('?the') + wait() + feed('<c-g>') + wait() + feed('<c-g>') + wait() + feed('<cr>') + screen:expect([[ + 1 ^the first | + 2 the second | + 3 the third | + ?the | + ]]) + + command('$') + feed('?the') + wait() + feed('<c-g>') + wait() + feed('<c-g>') + wait() + feed('<c-g>') + wait() + feed('<cr>') + screen:expect([[ + 1 the first | + 2 ^the second | + 3 the third | + ?the | + ]]) + + command('$') + feed('?the') + wait() + feed('<c-t>') + wait() + feed('<cr>') + screen:expect([[ + 1 ^the first | + 2 the second | + 3 the third | + ?the | + ]]) + + command('$') + feed('?the') + wait() + feed('<c-t>') + wait() + feed('<c-t>') + wait() + feed('<cr>') + screen:expect([[ + 1 the first | + 2 the second | + 3 ^the third | + ?the | + ]]) + + command('$') + feed('?the') + wait() + feed('<c-t>') + wait() + feed('<c-t>') + wait() + feed('<c-t>') + wait() + feed('<cr>') + screen:expect([[ + 1 the first | + 2 ^the second | + 3 the third | + ?the | + ]]) + end) end) diff --git a/test/functional/legacy/wordcount_spec.lua b/test/functional/legacy/wordcount_spec.lua index 5412903866..0c8bd2cdcc 100644 --- a/test/functional/legacy/wordcount_spec.lua +++ b/test/functional/legacy/wordcount_spec.lua @@ -6,8 +6,6 @@ local clear, command = helpers.clear, helpers.command local eq, eval = helpers.eq, helpers.eval local wait = helpers.wait -if helpers.pending_win32(pending) then return end - describe('wordcount', function() before_each(clear) diff --git a/test/functional/lua/overrides_spec.lua b/test/functional/lua/overrides_spec.lua index 8ca5fe57ba..007d40874f 100644 --- a/test/functional/lua/overrides_spec.lua +++ b/test/functional/lua/overrides_spec.lua @@ -87,6 +87,7 @@ describe('debug.debug', function() E = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, cr = {bold = true, foreground = Screen.colors.SeaGreen4}, }) + command("set display-=msgsep") end) it('works', function() command([[lua diff --git a/test/functional/normal/K_spec.lua b/test/functional/normal/K_spec.lua index 43e598633c..174313d80e 100644 --- a/test/functional/normal/K_spec.lua +++ b/test/functional/normal/K_spec.lua @@ -2,8 +2,6 @@ local helpers = require('test.functional.helpers')(after_each) local eq, clear, eval, feed = helpers.eq, helpers.clear, helpers.eval, helpers.feed -if helpers.pending_win32(pending) then return end - describe('K', function() local test_file = 'K_spec_out' before_each(function() @@ -29,7 +27,7 @@ describe('K', function() it("invokes non-prefixed 'keywordprg' as shell command", function() helpers.source([[ let @a='fnord' - set keywordprg=echo\ fnord\ >>]]) + set keywordprg=echo\ fnord>>]]) -- K on the text "K_spec_out" resolves to `!echo fnord >> K_spec_out`. feed('i'..test_file..'<ESC>K') diff --git a/test/functional/normal/langmap_spec.lua b/test/functional/normal/langmap_spec.lua new file mode 100644 index 0000000000..e4349a22e7 --- /dev/null +++ b/test/functional/normal/langmap_spec.lua @@ -0,0 +1,280 @@ +local helpers = require('test.functional.helpers')(after_each) + +local eq, neq, call = helpers.eq, helpers.neq, helpers.call +local eval, feed, clear = helpers.eval, helpers.feed, helpers.clear +local command, insert, expect = helpers.command, helpers.insert, helpers.expect +local feed_command = helpers.feed_command +local curwin = helpers.curwin + +describe("'langmap'", function() + before_each(function() + clear() + insert('iii www') + command('set langmap=iw,wi') + feed('gg0') + end) + + it("converts keys in normal mode", function() + feed('ix') + expect('iii ww') + feed('whello<esc>') + expect('iii helloww') + end) + it("gives characters that are mapped by :nmap.", function() + command('map i 0x') + feed('w') + expect('ii www') + end) + describe("'langnoremap' option.", function() + before_each(function() + command('nmapclear') + end) + it("'langnoremap' is by default ON", function() + eq(eval('&langnoremap'), 1) + end) + it("Results of maps are not converted when 'langnoremap' ON.", + function() + command('nmap x i') + feed('xdl<esc>') + expect('dliii www') + end) + it("applies when deciding whether to map recursively", function() + command('nmap l i') + command('nmap w j') + feed('ll') + expect('liii www') + end) + it("does not stop applying 'langmap' on first character of a mapping", + function() + command('1t1') + command('1t1') + command('goto 1') + command('nmap w j') + feed('iiahello') + expect([[ + iii www + iii www + ihelloii www]]) + end) + it("Results of maps are converted when 'langnoremap' OFF.", + function() + command('set nolangnoremap') + command('nmap x i') + feed('xdl<esc>') + expect('iii ww') + end) + end) + -- e.g. CTRL-W_j , mj , 'j and "jp + it('conversions are applied to keys in middle of command', + function() + -- Works in middle of window command + feed('<C-w>s') + local origwin = curwin() + feed('<C-w>i') + neq(curwin(), origwin) + -- Works when setting a mark + feed('yy3p3gg0mwgg0mi') + eq(call('getpos', "'i"), {0, 3, 1, 0}) + eq(call('getpos', "'w"), {0, 1, 1, 0}) + feed('3dd') + -- Works when moving to a mark + feed("'i") + eq(call('getpos', '.'), {0, 1, 1, 0}) + -- Works when selecting a register + feed('qillqqwhhq') + eq(eval('@i'), 'hh') + eq(eval('@w'), 'll') + feed('a<C-r>i<esc>') + expect('illii www') + feed('"ip') + expect('illllii www') + -- Works with i_CTRL-O + feed('0a<C-O>ihi<esc>') + expect('illllii hiwww') + end) + + describe('exceptions', function() + -- All "command characters" that 'langmap' does not apply to. + -- These tests consist of those places where some subset of ASCII + -- characters define certain commands, yet 'langmap' is not applied to + -- them. + -- n.b. I think these shouldn't be exceptions. + it(':s///c confirmation', function() + command('set langmap=yn,ny') + feed('qa') + feed_command('s/i/w/gc') + feed('yynq') + expect('wwi www') + feed('u@a') + expect('wwi www') + eq(eval('@a'), ':s/i/w/gc\ryyn') + end) + it('insert-mode CTRL-G', function() + command('set langmap=jk,kj') + command('d') + insert([[ + hello + hello + hello]]) + expect([[ + hello + hello + hello]]) + feed('qa') + feed('gg3|ahello<C-G>jx<esc>') + feed('q') + expect([[ + helhellolo + helxlo + hello]]) + eq(eval('@a'), 'gg3|ahellojx') + end) + it('command-line CTRL-\\', function() + command('set langmap=en,ne') + feed(':<C-\\>e\'hello\'\r<C-B>put ="<C-E>"<CR>') + expect([[ + iii www + hello]]) + end) + it('command-line CTRL-R', function() + helpers.source([[ + let i_value = 0 + let j_value = 0 + call setreg('i', 'i_value') + call setreg('j', 'j_value') + set langmap=ij,ji + ]]) + feed(':let <C-R>i=1<CR>') + eq(eval('i_value'), 1) + eq(eval('j_value'), 0) + end) + -- it('-- More -- prompt', function() + -- -- The 'b' 'j' 'd' 'f' commands at the -- More -- prompt + -- end) + it('ask yes/no after backwards range', function() + command('set langmap=yn,ny') + feed('dd') + insert([[ + hello + there + these + are + some + lines + ]]) + feed_command('4,2d') + feed('n') + expect([[ + hello + there + these + are + some + lines + ]]) + end) + it('prompt for number', function() + command('set langmap=12,21') + helpers.source([[ + let gotten_one = 0 + function Map() + let answer = inputlist(['a', '1.', '2.', '3.']) + if answer == 1 + let g:gotten_one = 1 + endif + endfunction + nnoremap x :call Map()<CR> + ]]) + feed('x1<CR>') + eq(eval('gotten_one'), 1) + command('let g:gotten_one = 0') + feed_command('call Map()') + feed('1<CR>') + eq(eval('gotten_one'), 1) + end) + end) + it('conversions are not applied during setreg()', + function() + call('setreg', 'i', 'ww') + eq(eval('@i'), 'ww') + end) + it('conversions not applied in insert mode', function() + feed('aiiiwww') + expect('iiiiwwwii www') + end) + it('conversions not applied in search mode', function() + feed('/iii<cr>x') + expect('ii www') + end) + it('conversions not applied in cmdline mode', function() + feed(':call append(1, "iii")<cr>') + expect([[ + iii www + iii]]) + end) + + local function testrecording(command_string, expect_string, setup_function) + if setup_function then setup_function() end + feed('qa' .. command_string .. 'q') + expect(expect_string) + eq(helpers.funcs.nvim_replace_termcodes(command_string, true, true, true), + eval('@a')) + if setup_function then setup_function() end + -- n.b. may need nvim_replace_termcodes() here. + feed('@a') + expect(expect_string) + end + + local function local_setup() + -- Can't use `insert` as it uses `i` and we've swapped the meaning of that + -- with the `langmap` setting. + command('%d') + command("put ='hello'") + command('1d') + end + + it('does not affect recording special keys', function() + testrecording('A<BS><esc>', 'hell', local_setup) + testrecording('>><lt><lt>', 'hello', local_setup) + command('nnoremap \\ x') + testrecording('\\', 'ello', local_setup) + testrecording('A<C-V><BS><esc>', 'hello<BS>', local_setup) + end) + pending('Translates modified keys correctly', function() + command('nnoremap <M-i> x') + command('nnoremap <M-w> l') + testrecording('<M-w>', 'ello', local_setup) + testrecording('<M-i>x', 'hllo', local_setup) + end) + pending('handles multi-byte characters', function() + command('set langmap=ïx') + testrecording('ï', 'ello', local_setup) + -- The test below checks that what's recorded is correct. + -- It doesn't check the behaviour, as in order to cause some behaviour we + -- need to map the multi-byte character, and there is a known bug + -- preventing this from working (see the test below). + command('set langmap=xï') + testrecording('x', 'hello', local_setup) + end) + pending('handles multibyte mappings', function() + -- See this vim issue for the problem, may as well add a test. + -- https://github.com/vim/vim/issues/297 + command('set langmap=ïx') + command('nnoremap x diw') + testrecording('ï', '', local_setup) + command('set nolangnoremap') + command('set langmap=xï') + command('nnoremap ï ix<esc>') + testrecording('x', 'xhello', local_setup) + end) + -- This test is to ensure the behaviour doesn't change from what's already + -- around. I (hardenedapple) personally think this behaviour should be + -- changed. + it('treats control modified keys as characters', function() + command('nnoremap <C-w> iw<esc>') + command('nnoremap <C-i> ii<esc>') + testrecording('<C-w>', 'whello', local_setup) + testrecording('<C-i>', 'ihello', local_setup) + end) + +end) diff --git a/test/functional/options/autochdir_spec.lua b/test/functional/options/autochdir_spec.lua index 209531515c..2fce0a5ed9 100644 --- a/test/functional/options/autochdir_spec.lua +++ b/test/functional/options/autochdir_spec.lua @@ -3,8 +3,6 @@ local clear = helpers.clear local eq = helpers.eq local getcwd = helpers.funcs.getcwd -if helpers.pending_win32(pending) then return end - describe("'autochdir'", function() it('given on the shell gets processed properly', function() local targetdir = 'test/functional/fixtures' @@ -12,9 +10,10 @@ describe("'autochdir'", function() -- By default 'autochdir' is off, thus getcwd() returns the repo root. clear(targetdir..'/tty-test.c') local rootdir = getcwd() + local expected = rootdir .. '/' .. targetdir -- With 'autochdir' on, we should get the directory of tty-test.c. clear('--cmd', 'set autochdir', targetdir..'/tty-test.c') - eq(rootdir..'/'..targetdir, getcwd()) + eq(helpers.iswin() and expected:gsub('/', '\\') or expected, getcwd()) end) end) diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index b83b7b8eee..9e29baba2d 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -7,23 +7,13 @@ local command = helpers.command local clear = helpers.clear local eval = helpers.eval local eq = helpers.eq +local insert = helpers.insert local neq = helpers.neq local mkdir = helpers.mkdir local rmdir = helpers.rmdir -local function init_session(...) - local args = { helpers.nvim_prog, '-i', 'NONE', '--embed', - '--cmd', helpers.nvim_set } - for _, v in ipairs({...}) do - table.insert(args, v) - end - helpers.set_session(helpers.spawn(args)) -end - describe('startup defaults', function() describe(':filetype', function() - if helpers.pending_win32(pending) then return end - local function expect_filetype(expected) local screen = Screen.new(50, 4) screen:attach() @@ -36,50 +26,70 @@ describe('startup defaults', function() ) end - it('enabled by `-u NORC`', function() - init_session('-u', 'NORC') + it('all ON after `-u NORC`', function() + clear('-u', 'NORC') expect_filetype( 'filetype detection:ON plugin:ON indent:ON |') end) - it('disabled by `-u NONE`', function() - init_session('-u', 'NONE') + it('all ON after `:syntax …` #7765', function() + clear('-u', 'NORC', '--cmd', 'syntax on') expect_filetype( - 'filetype detection:OFF plugin:OFF indent:OFF |') + 'filetype detection:ON plugin:ON indent:ON |') + clear('-u', 'NORC', '--cmd', 'syntax off') + expect_filetype( + 'filetype detection:ON plugin:ON indent:ON |') end) - it('overridden by early `filetype on`', function() - init_session('-u', 'NORC', '--cmd', 'filetype on') + it('all OFF after `-u NONE`', function() + clear('-u', 'NONE') expect_filetype( - 'filetype detection:ON plugin:OFF indent:OFF |') + 'filetype detection:OFF plugin:OFF indent:OFF |') end) - it('overridden by early `filetype plugin on`', function() - init_session('-u', 'NORC', '--cmd', 'filetype plugin on') + it('explicit OFF stays OFF', function() + clear('-u', 'NORC', '--cmd', + 'syntax off | filetype off | filetype plugin indent off') + expect_filetype( + 'filetype detection:OFF plugin:OFF indent:OFF |') + clear('-u', 'NORC', '--cmd', 'syntax off | filetype plugin indent off') + expect_filetype( + 'filetype detection:ON plugin:OFF indent:OFF |') + clear('-u', 'NORC', '--cmd', 'filetype indent off') expect_filetype( 'filetype detection:ON plugin:ON indent:OFF |') + clear('-u', 'NORC', '--cmd', 'syntax off | filetype off') + expect_filetype( + 'filetype detection:OFF plugin:(on) indent:(on) |') + -- Swap the order. + clear('-u', 'NORC', '--cmd', 'filetype off | syntax off') + expect_filetype( + 'filetype detection:OFF plugin:(on) indent:(on) |') end) - it('overridden by early `filetype indent on`', function() - init_session('-u', 'NORC', '--cmd', 'filetype indent on') + it('all ON after early `:filetype … on`', function() + -- `:filetype … on` should not change the defaults. #7765 + -- Only an explicit `:filetype … off` sets OFF. + + clear('-u', 'NORC', '--cmd', 'filetype on') expect_filetype( - 'filetype detection:ON plugin:OFF indent:ON |') + 'filetype detection:ON plugin:ON indent:ON |') + clear('-u', 'NORC', '--cmd', 'filetype plugin on') + expect_filetype( + 'filetype detection:ON plugin:ON indent:ON |') + clear('-u', 'NORC', '--cmd', 'filetype indent on') + expect_filetype( + 'filetype detection:ON plugin:ON indent:ON |') end) - it('adjusted by late `filetype off`', function() - init_session('-u', 'NORC', '-c', 'filetype off') + it('late `:filetype … off` stays OFF', function() + clear('-u', 'NORC', '-c', 'filetype off') expect_filetype( 'filetype detection:OFF plugin:(on) indent:(on) |') - end) - - it('adjusted by late `filetype plugin off`', function() - init_session('-u', 'NORC', '-c', 'filetype plugin off') + clear('-u', 'NORC', '-c', 'filetype plugin off') expect_filetype( 'filetype detection:ON plugin:OFF indent:ON |') - end) - - it('adjusted by late `filetype indent off`', function() - init_session('-u', 'NORC', '-c', 'filetype indent off') + clear('-u', 'NORC', '-c', 'filetype indent off') expect_filetype( 'filetype detection:ON plugin:ON indent:OFF |') end) @@ -87,27 +97,59 @@ describe('startup defaults', function() describe('syntax', function() it('enabled by `-u NORC`', function() - init_session('-u', 'NORC') + clear('-u', 'NORC') eq(1, eval('g:syntax_on')) end) it('disabled by `-u NONE`', function() - init_session('-u', 'NONE') + clear('-u', 'NONE') eq(0, eval('exists("g:syntax_on")')) end) - it('overridden by early `syntax off`', function() - init_session('-u', 'NORC', '--cmd', 'syntax off') + it('`:syntax off` stays off', function() + -- early + clear('-u', 'NORC', '--cmd', 'syntax off') + eq(0, eval('exists("g:syntax_on")')) + -- late + clear('-u', 'NORC', '-c', 'syntax off') eq(0, eval('exists("g:syntax_on")')) end) + end) - it('adjusted by late `syntax off`', function() - init_session('-u', 'NORC', '-c', 'syntax off') - eq(0, eval('exists("g:syntax_on")')) + describe("'fillchars'", function() + it('vert/fold flags', function() + clear() + local screen = Screen.new(50, 5) + screen:attach() + command('set laststatus=0') + insert([[ + 1 + 2 + 3 + 4]]) + command('normal! ggjzfj') + command('vsp') + screen:expect([[ + 1 │1 | + ^+-- 2 lines: 2··········│+-- 2 lines: 2·········| + 4 │4 | + ~ │~ | + | + ]]) + + -- ambiwidth=double defaults to single-byte fillchars. + command('set ambiwidth=double') + screen:expect([[ + 1 |1 | + ^+-- 2 lines: 2----------|+-- 2 lines: 2---------| + 4 |4 | + ~ |~ | + | + ]]) end) end) - describe('packpath', function() + describe("'packpath'", function() it('defaults to &runtimepath', function() eq(meths.get_option('runtimepath'), meths.get_option('packpath')) end) diff --git a/test/functional/options/num_options_spec.lua b/test/functional/options/num_options_spec.lua new file mode 100644 index 0000000000..ed17ffdd3c --- /dev/null +++ b/test/functional/options/num_options_spec.lua @@ -0,0 +1,97 @@ +-- Tests for :setlocal and :setglobal + +local helpers = require('test.functional.helpers')(after_each) +local clear, feed_command, eval, eq, meths = + helpers.clear, helpers.feed_command, helpers.eval, helpers.eq, helpers.meths + +local function should_fail(opt, value, errmsg) + feed_command('setglobal ' .. opt .. '=' .. value) + eq(errmsg, eval("v:errmsg"):match("E%d*")) + feed_command('let v:errmsg = ""') + feed_command('setlocal ' .. opt .. '=' .. value) + eq(errmsg, eval("v:errmsg"):match("E%d*")) + feed_command('let v:errmsg = ""') + local status, err = pcall(meths.set_option, opt, value) + eq(status, false) + eq(errmsg, err:match("E%d*")) + eq('', eval("v:errmsg")) +end + +local function should_succeed(opt, value) + feed_command('setglobal ' .. opt .. '=' .. value) + feed_command('setlocal ' .. opt .. '=' .. value) + meths.set_option(opt, value) + eq(value, meths.get_option(opt)) + eq('', eval("v:errmsg")) +end + +describe(':setlocal', function() + before_each(clear) + + it('setlocal sets only local value', function() + eq(0, meths.get_option('iminsert')) + feed_command('setlocal iminsert=1') + eq(0, meths.get_option('iminsert')) + eq(0, meths.get_option('imsearch')) + feed_command('setlocal imsearch=1') + eq(0, meths.get_option('imsearch')) + end) +end) + +describe(':set validation', function() + before_each(clear) + + it('setlocal and setglobal validate values', function() + should_fail('shiftwidth', -10, 'E487') + should_succeed('shiftwidth', 0) + should_fail('tabstop', -10, 'E487') + should_fail('winheight', -10, 'E487') + should_fail('winheight', 0, 'E487') + should_fail('winminheight', -1, 'E487') + should_succeed('winminheight', 0) + should_fail('winwidth', 0, 'E487') + should_fail('helpheight', -1, 'E487') + should_fail('maxcombine', 7, 'E474') + should_fail('iminsert', 3, 'E474') + should_fail('imsearch', 3, 'E474') + should_fail('titlelen', -1, 'E487') + should_fail('cmdheight', 0, 'E487') + should_fail('updatecount', -1, 'E487') + should_fail('textwidth', -1, 'E487') + should_fail('tabstop', 0, 'E487') + should_fail('timeoutlen', -1, 'E487') + should_fail('history', 1000000, 'E474') + should_fail('regexpengine', -1, 'E474') + should_fail('regexpengine', 3, 'E474') + should_succeed('regexpengine', 2) + should_fail('report', -1, 'E487') + should_succeed('report', 0) + should_fail('scrolloff', -1, 'E49') + should_fail('sidescrolloff', -1, 'E487') + should_fail('sidescroll', -1, 'E487') + should_fail('cmdwinheight', 0, 'E487') + should_fail('updatetime', -1, 'E487') + + should_fail('foldlevel', -5, 'E487') + should_fail('foldcolumn', 13, 'E474') + should_fail('conceallevel', 4, 'E474') + should_fail('numberwidth', 11, 'E474') + should_fail('numberwidth', 0, 'E487') + + -- If smaller than 1 this one is set to 'lines'-1 + feed_command('setglobal window=-10') + meths.set_option('window', -10) + eq(23, meths.get_option('window')) + eq('', eval("v:errmsg")) + end) + + it('set wmh/wh wmw/wiw checks', function() + feed_command('set winheight=2') + feed_command('set winminheight=3') + eq('E591', eval("v:errmsg"):match("E%d*")) + + feed_command('set winwidth=2') + feed_command('set winminwidth=3') + eq('E592', eval("v:errmsg"):match("E%d*")) + end) +end) diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua index 8ee0f258d0..f2d5e433db 100644 --- a/test/functional/plugin/health_spec.lua +++ b/test/functional/plugin/health_spec.lua @@ -6,6 +6,7 @@ local clear = helpers.clear local curbuf_contents = helpers.curbuf_contents local command = helpers.command local eq = helpers.eq +local getcompletion = helpers.funcs.getcompletion describe(':checkhealth', function() it("detects invalid $VIMRUNTIME", function() @@ -31,6 +32,11 @@ describe(':checkhealth', function() eq("ERROR: $VIM is invalid: zub", string.match(curbuf_contents(), "ERROR: $VIM .* zub")) end) + it('completions can be listed via getcompletion()', function() + clear() + eq('nvim', getcompletion('nvim', 'checkhealth')[1]) + eq('provider', getcompletion('prov', 'checkhealth')[1]) + end) end) describe('health.vim', function() diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua new file mode 100644 index 0000000000..e5da7932a5 --- /dev/null +++ b/test/functional/plugin/man_spec.lua @@ -0,0 +1,135 @@ +local helpers = require('test.functional.helpers')(after_each) +local plugin_helpers = require('test.functional.plugin.helpers') + +local Screen = require('test.functional.ui.screen') + +local command, eval, rawfeed = helpers.command, helpers.eval, helpers.rawfeed + +before_each(function() + plugin_helpers.reset() + helpers.clear() + command('syntax on') + command('set filetype=man') +end) + +describe(':Man', function() + describe('man.lua: highlight_line()', function() + local screen + + before_each(function() + command('syntax off') -- Ignore syntax groups + screen = Screen.new(52, 5) + screen:set_default_attr_ids({ + b = { bold = true }, + i = { italic = true }, + u = { underline = true }, + bi = { bold = true, italic = true }, + biu = { bold = true, italic = true, underline = true }, + }) + screen:set_default_attr_ignore({ + { foreground = Screen.colors.Blue }, -- control chars + { bold = true, foreground = Screen.colors.Blue } -- empty line '~'s + }) + screen:attach() + end) + + after_each(function() + screen:detach() + end) + + it('clears backspaces from text and adds highlights', function() + rawfeed([[ + ithis i<C-v><C-h>is<C-v><C-h>s a<C-v><C-h>a test + with _<C-v><C-h>o_<C-v><C-h>v_<C-v><C-h>e_<C-v><C-h>r_<C-v><C-h>s_<C-v><C-h>t_<C-v><C-h>r_<C-v><C-h>u_<C-v><C-h>c_<C-v><C-h>k text<ESC>]]) + + screen:expect([[ + this i^His^Hs a^Ha test | + with _^Ho_^Hv_^He_^Hr_^Hs_^Ht_^Hr_^Hu_^Hc_^Hk tex^t | + ~ | + ~ | + | + ]]) + + eval('man#init_pager()') + + screen:expect([[ + ^this {b:is} {b:a} test | + with {u:overstruck} text | + ~ | + ~ | + | + ]]) + end) + + it('clears escape sequences from text and adds highlights', function() + rawfeed([[ + ithis <C-v><ESC>[1mis <C-v><ESC>[3ma <C-v><ESC>[4mtest<C-v><ESC>[0m + <C-v><ESC>[4mwith<C-v><ESC>[24m <C-v><ESC>[4mescaped<C-v><ESC>[24m <C-v><ESC>[4mtext<C-v><ESC>[24m<ESC>]]) + + screen:expect([=[ + this ^[[1mis ^[[3ma ^[[4mtest^[[0m | + ^[[4mwith^[[24m ^[[4mescaped^[[24m ^[[4mtext^[[24^m | + ~ | + ~ | + | + ]=]) + + eval('man#init_pager()') + + screen:expect([[ + ^this {b:is }{bi:a }{biu:test} | + {u:with} {u:escaped} {u:text} | + ~ | + ~ | + | + ]]) + end) + + it('highlights multibyte text', function() + rawfeed([[ + ithis i<C-v><C-h>is<C-v><C-h>s ã‚<C-v><C-h>ã‚ test + with _<C-v><C-h>ö_<C-v><C-h>v_<C-v><C-h>e_<C-v><C-h>r_<C-v><C-h>s_<C-v><C-h>t_<C-v><C-h>r_<C-v><C-h>u_<C-v><C-h>̃_<C-v><C-h>c_<C-v><C-h>k te<C-v><ESC>[3mxt¶<C-v><ESC>[0m<ESC>]]) + eval('man#init_pager()') + + screen:expect([[ + ^this {b:is} {b:ã‚} test | + with {u:överstrũck} te{i:xt¶} | + ~ | + ~ | + | + ]]) + end) + + it('highlights underscores based on context', function() + rawfeed([[ + i_<C-v><C-h>_b<C-v><C-h>be<C-v><C-h>eg<C-v><C-h>gi<C-v><C-h>in<C-v><C-h>ns<C-v><C-h>s + m<C-v><C-h>mi<C-v><C-h>id<C-v><C-h>d_<C-v><C-h>_d<C-v><C-h>dl<C-v><C-h>le<C-v><C-h>e + _<C-v><C-h>m_<C-v><C-h>i_<C-v><C-h>d_<C-v><C-h>__<C-v><C-h>d_<C-v><C-h>l_<C-v><C-h>e<ESC>]]) + eval('man#init_pager()') + + screen:expect([[ + {b:^_begins} | + {b:mid_dle} | + {u:mid_dle} | + ~ | + | + ]]) + end) + + it('highlights various bullet formats', function() + rawfeed([[ + i· ·<C-v><C-h>· + +<C-v><C-h>o + +<C-v><C-h>+<C-v><C-h>o<C-v><C-h>o double<ESC>]]) + eval('man#init_pager()') + + screen:expect([[ + ^· {b:·} | + {b:·} | + {b:·} double | + ~ | + | + ]]) + end) + end) +end) diff --git a/test/functional/plugin/msgpack_spec.lua b/test/functional/plugin/msgpack_spec.lua index 5ba19708cf..4b014cbc73 100644 --- a/test/functional/plugin/msgpack_spec.lua +++ b/test/functional/plugin/msgpack_spec.lua @@ -8,7 +8,7 @@ local NIL = helpers.NIL local plugin_helpers = require('test.functional.plugin.helpers') local reset = plugin_helpers.reset -describe('In autoload/msgpack.vim', function() +describe('autoload/msgpack.vim', function() before_each(reset) local sp = function(typ, val) diff --git a/test/functional/plugin/shada_spec.lua b/test/functional/plugin/shada_spec.lua index dbc78e63f0..5a5b4df1ef 100644 --- a/test/functional/plugin/shada_spec.lua +++ b/test/functional/plugin/shada_spec.lua @@ -3,6 +3,7 @@ local eq, nvim_eval, nvim_command, nvim, exc_exec, funcs, nvim_feed, curbuf = helpers.eq, helpers.eval, helpers.command, helpers.nvim, helpers.exc_exec, helpers.funcs, helpers.feed, helpers.curbuf local neq = helpers.neq +local read_file = helpers.read_file local mpack = require('mpack') @@ -43,9 +44,7 @@ local wshada, _, fname = get_shada_rw('Xtest-functional-plugin-shada.shada') local wshada_tmp, _, fname_tmp = get_shada_rw('Xtest-functional-plugin-shada.shada.tmp.f') -if helpers.pending_win32(pending) then return end - -describe('In autoload/shada.vim', function() +describe('autoload/shada.vim', function() local epoch = os.date('%Y-%m-%dT%H:%M:%S', 0) before_each(function() reset() @@ -2138,8 +2137,9 @@ describe('In autoload/shada.vim', function() end) end) -describe('In plugin/shada.vim', function() +describe('plugin/shada.vim', function() local epoch = os.date('%Y-%m-%dT%H:%M:%S', 0) + local eol = helpers.iswin() and '\r\n' or '\n' before_each(function() reset() os.remove(fname) @@ -2153,9 +2153,7 @@ describe('In plugin/shada.vim', function() end) local shada_eq = function(expected, fname_) - local fd = io.open(fname_) - local mpack_result = fd:read('*a') - fd:close() + local mpack_result = read_file(fname_) mpack_eq(expected, mpack_result) end @@ -2279,7 +2277,7 @@ describe('In plugin/shada.vim', function() ' + f file name ["foo"]', ' + l line number 2', ' + c column -200', - }, '\n') .. '\n', io.open(fname .. '.tst'):read('*a')) + }, eol) .. eol, read_file(fname .. '.tst')) shada_eq({{ timestamp=0, type=8, @@ -2303,6 +2301,7 @@ describe('In plugin/shada.vim', function() describe('event FileWriteCmd', function() it('works', function() + if helpers.pending_win32(pending) then return end nvim('set_var', 'shada#add_own_header', 0) curbuf('set_lines', 0, 1, true, { 'Jump with timestamp ' .. epoch .. ':', @@ -2326,7 +2325,7 @@ describe('In plugin/shada.vim', function() 'Jump with timestamp ' .. epoch .. ':', ' % Key________ Description Value', ' + n name \'A\'', - }, '\n') .. '\n', io.open(fname .. '.tst'):read('*a')) + }, eol) .. eol, read_file(fname .. '.tst')) shada_eq({{ timestamp=0, type=8, @@ -2383,7 +2382,7 @@ describe('In plugin/shada.vim', function() ' + f file name ["foo"]', ' + l line number 2', ' + c column -200', - }, '\n') .. '\n', io.open(fname .. '.tst'):read('*a')) + }, eol) .. eol, read_file(fname .. '.tst')) shada_eq({{ timestamp=0, type=8, diff --git a/test/functional/provider/nodejs_spec.lua b/test/functional/provider/nodejs_spec.lua new file mode 100644 index 0000000000..0a12b1a154 --- /dev/null +++ b/test/functional/provider/nodejs_spec.lua @@ -0,0 +1,61 @@ +local helpers = require('test.functional.helpers')(after_each) +local eq, clear = helpers.eq, helpers.clear +local missing_provider = helpers.missing_provider +local command = helpers.command +local write_file = helpers.write_file +local eval = helpers.eval +local retry = helpers.retry + +do + clear() + if missing_provider('node') then + pending("Missing nodejs host, or nodejs version is too old.", function()end) + return + end +end + +before_each(function() + clear() + command([[let $NODE_PATH = get(split(system('npm root -g'), "\n"), 0, '')]]) +end) + +describe('nodejs host', function() + teardown(function () + os.remove('Xtest-nodejs-hello.js') + os.remove('Xtest-nodejs-hello-plugin.js') + end) + + it('works', function() + local fname = 'Xtest-nodejs-hello.js' + write_file(fname, [[ + const socket = process.env.NVIM_LISTEN_ADDRESS; + const neovim = require('neovim'); + const nvim = neovim.attach({socket: socket}); + nvim.command('let g:job_out = "hello"'); + nvim.command('call jobstop(g:job_id)'); + ]]) + command('let g:job_id = jobstart(["node", "'..fname..'"])') + retry(nil, 1000, function() eq('hello', eval('g:job_out')) end) + end) + it('plugin works', function() + local fname = 'Xtest-nodejs-hello-plugin.js' + write_file(fname, [[ + const socket = process.env.NVIM_LISTEN_ADDRESS; + const neovim = require('neovim'); + const nvim = neovim.attach({socket: socket}); + + class TestPlugin { + hello() { + this.nvim.command('let g:job_out = "hello-plugin"') + } + } + + const PluginClass = neovim.Plugin(TestPlugin); + const plugin = new PluginClass(nvim); + plugin.hello(); + nvim.command('call jobstop(g:job_id)'); + ]]) + command('let g:job_id = jobstart(["node", "'..fname..'"])') + retry(nil, 1000, function() eq('hello-plugin', eval('g:job_out')) end) + end) +end) diff --git a/test/functional/provider/python3_spec.lua b/test/functional/provider/python3_spec.lua index aa50f53451..93ac3ae017 100644 --- a/test/functional/provider/python3_spec.lua +++ b/test/functional/provider/python3_spec.lua @@ -3,20 +3,18 @@ local eval, command, feed = helpers.eval, helpers.command, helpers.feed local eq, clear, insert = helpers.eq, helpers.clear, helpers.insert local expect, write_file = helpers.expect, helpers.write_file local feed_command = helpers.feed_command +local source = helpers.source local missing_provider = helpers.missing_provider do clear() - local err = missing_provider('python3') - if err then - pending( - 'Python 3 (or the Python 3 neovim module) is broken or missing:\n' .. err, - function() end) + if missing_provider('python3') then + pending('Python 3 (or the neovim module) is broken/missing', function() end) return end end -describe('python3 commands and functions', function() +describe('python3 provider', function() before_each(function() clear() command('python3 import vim') @@ -85,4 +83,20 @@ describe('python3 commands and functions', function() it('py3eval', function() eq({1, 2, {['key'] = 'val'}}, eval([[py3eval('[1, 2, {"key": "val"}]')]])) end) + + it('RPC call to expand("<afile>") during BufDelete #5245 #5617', function() + source([=[ + python3 << EOF + import vim + def foo(): + vim.eval('expand("<afile>:p")') + vim.eval('bufnr(expand("<afile>:p"))') + EOF + autocmd BufDelete * python3 foo() + autocmd BufUnload * python3 foo()]=]) + feed_command("exe 'split' tempname()") + feed_command("bwipeout!") + feed_command('help help') + eq(2, eval('1+1')) -- Still alive? + end) end) diff --git a/test/functional/provider/python_spec.lua b/test/functional/provider/python_spec.lua index 25f5e0a6d0..2fa74e9644 100644 --- a/test/functional/provider/python_spec.lua +++ b/test/functional/provider/python_spec.lua @@ -16,11 +16,8 @@ local missing_provider = helpers.missing_provider do clear() - local err = missing_provider('python') - if err then - pending( - 'Python 2 (or the Python 2 neovim module) is broken or missing:\n' .. err, - function() end) + if missing_provider('python') then + pending('Python 2 (or the neovim module) is broken/missing', function() end) return end end diff --git a/test/functional/provider/ruby_spec.lua b/test/functional/provider/ruby_spec.lua index c70f90da1c..e049ac7c41 100644 --- a/test/functional/provider/ruby_spec.lua +++ b/test/functional/provider/ruby_spec.lua @@ -1,23 +1,23 @@ local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local command = helpers.command +local curbufmeths = helpers.curbufmeths local eq = helpers.eq +local eval = helpers.eval +local expect = helpers.expect local feed = helpers.feed -local clear = helpers.clear +local feed_command = helpers.feed_command local funcs = helpers.funcs -local meths = helpers.meths local insert = helpers.insert -local expect = helpers.expect -local command = helpers.command -local write_file = helpers.write_file -local curbufmeths = helpers.curbufmeths +local meths = helpers.meths local missing_provider = helpers.missing_provider +local write_file = helpers.write_file do clear() if missing_provider('ruby') then - pending( - "Cannot find the neovim RubyGem. Try :checkhealth", - function() end) + pending("Missing neovim RubyGem.", function() end) return end end @@ -92,3 +92,11 @@ describe(':rubydo command', function() eq(false, curbufmeths.get_option('modified')) end) end) + +describe('ruby provider', function() + it('RPC call to expand("<afile>") during BufDelete #5245 #5617', function() + command([=[autocmd BufDelete * ruby VIM::evaluate('expand("<afile>")')]=]) + feed_command('help help') + eq(2, eval('1+1')) -- Still alive? + end) +end) diff --git a/test/functional/shada/helpers.lua b/test/functional/shada/helpers.lua index 8e2c0cc1f6..1312d762d8 100644 --- a/test/functional/shada/helpers.lua +++ b/test/functional/shada/helpers.lua @@ -6,15 +6,14 @@ local write_file, merge_args = helpers.write_file, helpers.merge_args local mpack = require('mpack') local tmpname = helpers.tmpname() -local additional_cmd = '' +local append_argv = nil -local function nvim_argv(shada_file) +local function nvim_argv(shada_file, embed) local argv = {nvim_prog, '-u', 'NONE', '-i', shada_file or tmpname, '-N', '--cmd', 'set shortmess+=I background=light noswapfile', - '--cmd', additional_cmd, - '--embed'} - if helpers.prepend_argv then - return merge_args(helpers.prepend_argv, argv) + embed or '--embed'} + if helpers.prepend_argv or append_argv then + return merge_args(helpers.prepend_argv, argv, append_argv) else return argv end @@ -26,12 +25,20 @@ local reset = function(shada_file) end local set_additional_cmd = function(s) - additional_cmd = s + append_argv = {'--cmd', s} +end + +local function add_argv(...) + if select('#', ...) == 0 then + append_argv = nil + else + append_argv = {...} + end end local clear = function() os.remove(tmpname) - set_additional_cmd('') + append_argv = nil end local get_shada_rw = function(fname) @@ -80,7 +87,9 @@ end return { reset=reset, set_additional_cmd=set_additional_cmd, + add_argv=add_argv, clear=clear, get_shada_rw=get_shada_rw, read_shada_file=read_shada_file, + nvim_argv=nvim_argv, } diff --git a/test/functional/shada/marks_spec.lua b/test/functional/shada/marks_spec.lua index fa760ceb5b..4cceae1aa3 100644 --- a/test/functional/shada/marks_spec.lua +++ b/test/functional/shada/marks_spec.lua @@ -9,6 +9,8 @@ local shada_helpers = require('test.functional.shada.helpers') local reset, set_additional_cmd, clear = shada_helpers.reset, shada_helpers.set_additional_cmd, shada_helpers.clear +local add_argv = shada_helpers.add_argv +local nvim_argv = shada_helpers.nvim_argv local nvim_current_line = function() return curwinmeths.get_cursor()[1] @@ -17,8 +19,10 @@ end describe('ShaDa support code', function() local testfilename = 'Xtestfile-functional-shada-marks' local testfilename_2 = 'Xtestfile-functional-shada-marks-2' + local non_existent_testfilename = testfilename .. '.nonexistent' before_each(function() reset() + os.remove(non_existent_testfilename) local fd = io.open(testfilename, 'w') fd:write('test\n') fd:write('test2\n') @@ -214,4 +218,23 @@ describe('ShaDa support code', function() nvim_command('" sync 2') eq(2, nvim_current_line()) end) + + -- -c temporary sets lnum to zero to make `+/pat` work, so calling setpcmark() + -- during -c used to add item with zero lnum to jump list. + it('does not create incorrect file for non-existent buffers when writing from -c', + function() + add_argv('--cmd', 'silent edit ' .. non_existent_testfilename, '-c', 'qall') + local argv = nvim_argv(nil, '--headless') + eq('', funcs.system(argv)) + eq(0, exc_exec('rshada')) + end) + + it('does not create incorrect file for non-existent buffers opened from -c', + function() + add_argv('-c', 'silent edit ' .. non_existent_testfilename, + '-c', 'autocmd VimEnter * qall') + local argv = nvim_argv(nil, '--headless') + eq('', funcs.system(argv)) + eq(0, exc_exec('rshada')) + end) end) diff --git a/test/functional/shada/merging_spec.lua b/test/functional/shada/merging_spec.lua index 7a15c8908b..a628baff53 100644 --- a/test/functional/shada/merging_spec.lua +++ b/test/functional/shada/merging_spec.lua @@ -525,6 +525,85 @@ describe('ShaDa marks support code', function() eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) end) + it('can merge with file with mark 9 as the only numeric mark', function() + wshada('\007\001\014\130\161f\196\006' .. mock_file_path .. '-\161n9') + eq(0, exc_exec(sdrcmd())) + nvim_command('normal! `9oabc') + eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = {} + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 7 and v.value.f == mock_file_path .. '-' then + local name = ('%c'):format(v.value.n) + found[name] = (found[name] or 0) + 1 + end + end + eq({['0']=1, ['1']=1}, found) + end) + + it('removes duplicates while merging', function() + wshada('\007\001\014\130\161f\196\006' .. mock_file_path .. '-\161n9' + .. '\007\001\014\130\161f\196\006' .. mock_file_path .. '-\161n9') + eq(0, exc_exec(sdrcmd())) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 7 and v.value.f == mock_file_path .. '-' then + print(require('test.helpers').format_luav(v)) + found = found + 1 + end + end + eq(1, found) + end) + + it('does not leak when no append is performed due to too many marks', + function() + wshada('\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'a\161n0' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'b\161n1' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161n2' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'd\161n3' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'e\161n4' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'f\161n5' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'g\161n6' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'h\161n7' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'i\161n8' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'j\161n9' + .. '\007\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'k\161n9') + eq(0, exc_exec(sdrcmd())) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = {} + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 7 and v.value.f:sub(1, #mock_file_path) == mock_file_path then + found[#found + 1] = v.value.f:sub(#v.value.f) + end + end + eq({'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}, found) + end) + + it('does not leak when last mark in file removes some of the earlier ones', + function() + wshada('\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'a\161n0' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'b\161n1' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161n2' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'd\161n3' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'e\161n4' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'f\161n5' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'g\161n6' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'h\161n7' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'i\161n8' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'j\161n9' + .. '\007\003\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'k\161n9') + eq(0, exc_exec(sdrcmd())) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = {} + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 7 and v.value.f:sub(1, #mock_file_path) == mock_file_path then + found[#found + 1] = v.value.f:sub(#v.value.f) + end + end + eq({'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k'}, found) + end) + it('uses last A mark with gt timestamp from file when reading with !', function() wshada('\007\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. '-\161nA') @@ -563,13 +642,14 @@ describe('ShaDa marks support code', function() nvim_command('normal! `A') eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) eq(0, exc_exec('wshada ' .. shada_fname)) - local found = 0 + local found = {} for _, v in ipairs(read_shada_file(shada_fname)) do - if v.type == 7 and v.value.f == '' .. mock_file_path .. '-' then - found = found + 1 + if v.type == 7 and v.value.f == mock_file_path .. '-' then + local name = ('%c'):format(v.value.n) + found[name] = (found[name] or 0) + 1 end end - eq(1, found) + eq({['0']=1, A=1}, found) end) it('uses last A mark with eq timestamp from instance when writing', @@ -580,30 +660,33 @@ describe('ShaDa marks support code', function() nvim_command('normal! `A') eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) eq(0, exc_exec('wshada ' .. shada_fname)) - local found = 0 + local found = {} for _, v in ipairs(read_shada_file(shada_fname)) do if v.type == 7 and v.value.f == mock_file_path .. '-' then - found = found + 1 + local name = ('%c'):format(v.value.n) + found[name] = (found[name] or 0) + 1 end end - eq(1, found) + eq({['0']=1, A=1}, found) end) - it('uses last A mark with gt timestamp from file when writing', - function() + it('uses last A mark with gt timestamp from file when writing', function() wshada('\007\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. '-\161nA') eq(0, exc_exec(sdrcmd())) wshada('\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. '?\161nA') nvim_command('normal! `A') eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) eq(0, exc_exec('wshada ' .. shada_fname)) - local found = 0 + local found = {} for _, v in ipairs(read_shada_file(shada_fname)) do - if v.type == 7 and v.value.f == '' .. mock_file_path .. '?' then - found = found + 1 + if v.type == 7 then + local name = ('%c'):format(v.value.n) + local t = found[name] or {} + t[v.value.f] = (t[v.value.f] or 0) + 1 + found[name] = t end end - eq(1, found) + eq({['0']={[mock_file_path .. '-']=1}, A={[mock_file_path .. '?']=1}}, found) end) it('uses last a mark with gt timestamp from instance when reading', diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua index ca44026852..720855860a 100644 --- a/test/functional/shada/shada_spec.lua +++ b/test/functional/shada/shada_spec.lua @@ -181,13 +181,13 @@ describe('ShaDa support code', function() nvim_command('set shada+=%') nvim_command('wshada! ' .. shada_fname) local readme_fname = funcs.resolve(paths.test_source_path) .. '/README.md' - eq({[7]=1, [8]=2, [9]=1, [10]=4, [11]=1}, find_file(readme_fname)) + eq({[7]=2, [8]=2, [9]=1, [10]=4, [11]=1}, find_file(readme_fname)) nvim_command('set shada+=r~') nvim_command('wshada! ' .. shada_fname) eq({}, find_file(readme_fname)) nvim_command('set shada-=r~') nvim_command('wshada! ' .. shada_fname) - eq({[7]=1, [8]=2, [9]=1, [10]=4, [11]=1}, find_file(readme_fname)) + eq({[7]=2, [8]=2, [9]=1, [10]=4, [11]=1}, find_file(readme_fname)) nvim_command('set shada+=r' .. funcs.escape( funcs.escape(paths.test_source_path, '$~'), ' "\\,')) nvim_command('wshada! ' .. shada_fname) @@ -206,7 +206,7 @@ describe('ShaDa support code', function() nvim_command('undo') nvim_command('set shada+=%') nvim_command('wshada! ' .. shada_fname) - eq({[7]=1, [8]=2, [9]=1, [10]=4, [11]=2}, find_file(fname)) + eq({[7]=2, [8]=2, [9]=1, [10]=4, [11]=2}, find_file(fname)) nvim_command('set shada+=r' .. pwd .. '/ÐБВ') nvim_command('wshada! ' .. shada_fname) eq({}, find_file(fname)) diff --git a/test/functional/terminal/edit_spec.lua b/test/functional/terminal/edit_spec.lua index d2b2d8a60c..84d7ae6e9c 100644 --- a/test/functional/terminal/edit_spec.lua +++ b/test/functional/terminal/edit_spec.lua @@ -36,6 +36,7 @@ describe(':edit term://*', function() local scr = get_screen(columns, lines) local rep = 'a' meths.set_option('shellcmdflag', 'REP ' .. rep) + command('set shellxquote=') -- win: avoid extra quotes local rep_size = rep:byte() -- 'a' => 97 local sb = 10 command('autocmd TermOpen * :setlocal scrollback='..tostring(sb) diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua index e015df10db..4f22f7385d 100644 --- a/test/functional/terminal/ex_terminal_spec.lua +++ b/test/functional/terminal/ex_terminal_spec.lua @@ -2,12 +2,15 @@ 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 nvim_dir, source, eq = helpers.nvim_dir, helpers.source, helpers.eq +local feed = helpers.feed local feed_command, eval = helpers.feed_command, helpers.eval +local funcs = helpers.funcs local retry = helpers.retry +local ok = helpers.ok local iswin = helpers.iswin +local command = helpers.command describe(':terminal', function() - if helpers.pending_win32(pending) then return end local screen before_each(function() @@ -24,10 +27,17 @@ describe(':terminal', function() echomsg "msg3" ]]) -- Invoke a command that emits frequent terminal activity. - feed_command([[terminal while true; do echo X; done]]) - helpers.feed([[<C-\><C-N>]]) + if iswin() then + feed_command([[terminal for /L \%I in (1,0,2) do echo \%I]]) + else + feed_command([[terminal while true; do echo X; done]]) + end + feed([[<C-\><C-N>]]) wait() - screen:sleep(10) -- Let some terminal activity happen. + -- Wait for some terminal activity. + retry(nil, 4000, function() + ok(funcs.line('$') > 6) + end) feed_command("messages") screen:expect([[ msg1 | @@ -38,8 +48,12 @@ describe(':terminal', function() end) it("in normal-mode :split does not move cursor", function() - feed_command([[terminal while true; do echo foo; sleep .1; done]]) - helpers.feed([[<C-\><C-N>M]]) -- move cursor away from last line + if iswin() then + feed_command([[terminal for /L \\%I in (1,0,2) do ( echo foo & ping -w 100 -n 1 127.0.0.1 > nul )]]) + else + feed_command([[terminal while true; do echo foo; sleep .1; done]]) + end + feed([[<C-\><C-N>M]]) -- move cursor away from last line wait() eq(3, eval("line('$')")) -- window height eq(2, eval("line('.')")) -- cursor is in the middle @@ -49,6 +63,32 @@ describe(':terminal', function() eq(2, eval("line('.')")) -- cursor stays where we put it end) + it('Enter/Leave does not increment jumplist #3723', function() + feed_command('terminal') + local function enter_and_leave() + local lines_before = funcs.line('$') + -- 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() + feed('<CR><CR><CR><CR>') + wait() + feed([[<C-\><C-N>]]) + wait() + -- Wait for >=1 lines to be created. + retry(nil, 4000, function() + ok(funcs.line('$') > lines_before) + end) + end + enter_and_leave() + enter_and_leave() + enter_and_leave() + ok(funcs.line('$') > 6) -- Verify assumption. + local jumps = funcs.split(funcs.execute('jumps'), '\n') + eq(' jump line col file/text', jumps[1]) + eq(3, #jumps) + end) + end) describe(':terminal (with fake shell)', function() @@ -104,6 +144,7 @@ describe(':terminal (with fake shell)', function() end) it('executes a given command through the shell', function() + command('set shellxquote=') -- win: avoid extra quotes terminal_with_fake_shell('echo hi') screen:expect([[ ^ready $ echo hi | @@ -115,6 +156,7 @@ describe(':terminal (with fake shell)', function() it("executes a given command through the shell, when 'shell' has arguments", function() nvim('set_option', 'shell', nvim_dir..'/shell-test -t jeff') + command('set shellxquote=') -- win: avoid extra quotes terminal_with_fake_shell('echo hi') screen:expect([[ ^jeff $ echo hi | @@ -125,6 +167,7 @@ describe(':terminal (with fake shell)', function() end) it('allows quotes and slashes', function() + command('set shellxquote=') -- win: avoid extra quotes terminal_with_fake_shell([[echo 'hello' \ "world"]]) screen:expect([[ ^ready $ echo 'hello' \ "world" | @@ -144,12 +187,12 @@ describe(':terminal (with fake shell)', function() it('ignores writes if the backing stream closes', function() terminal_with_fake_shell() - helpers.feed('iiXXXXXXX') + feed('iiXXXXXXX') wait() -- 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). - helpers.feed('iiYYYYYYY') + feed('iiYYYYYYY') eq(2, eval("1+1")) -- Still alive? end) @@ -168,7 +211,7 @@ describe(':terminal (with fake shell)', function() :terminal | ]]) eq('term://', string.match(eval('bufname("%")'), "^term://")) - helpers.feed([[<C-\><C-N>]]) + feed([[<C-\><C-N>]]) feed_command([[find */shadacat.py]]) if iswin() then eq('scripts\\shadacat.py', eval('bufname("%")')) @@ -178,6 +221,7 @@ describe(':terminal (with fake shell)', function() end) it('works with gf', function() + command('set shellxquote=') -- win: avoid extra quotes terminal_with_fake_shell([[echo "scripts/shadacat.py"]]) screen:expect([[ ^ready $ echo "scripts/shadacat.py" | @@ -185,9 +229,9 @@ describe(':terminal (with fake shell)', function() [Process exited 0] | :terminal echo "scripts/shadacat.py" | ]]) - helpers.feed([[<C-\><C-N>]]) + feed([[<C-\><C-N>]]) eq('term://', string.match(eval('bufname("%")'), "^term://")) - helpers.feed([[ggf"lgf]]) + feed([[ggf"lgf]]) eq('scripts/shadacat.py', eval('bufname("%")')) end) diff --git a/test/functional/terminal/mouse_spec.lua b/test/functional/terminal/mouse_spec.lua index 5e5558ee0a..a21d9f0a56 100644 --- a/test/functional/terminal/mouse_spec.lua +++ b/test/functional/terminal/mouse_spec.lua @@ -98,41 +98,41 @@ describe('terminal mouse', function() before_each(function() feed('<c-\\><c-n>:vsp<cr>') screen:expect([[ - line28 |line28 | - line29 |line29 | - line30 |line30 | - rows: 5, cols: 24 |rows: 5, cols: 24 | - {2:^ } |{2: } | + line28 │line28 | + line29 │line29 | + line30 │line30 | + rows: 5, cols: 24 │rows: 5, cols: 24 | + {2:^ } │{2: } | ========== ========== | :vsp | ]]) feed(':enew | set number<cr>') screen:expect([[ - {7: 1 }^ |line28 | - {4:~ }|line29 | - {4:~ }|line30 | - {4:~ }|rows: 5, cols: 24 | - {4:~ }|{2: } | + {7: 1 }^ │line28 | + {4:~ }│line29 | + {4:~ }│line30 | + {4:~ }│rows: 5, cols: 24 | + {4:~ }│{2: } | ========== ========== | :enew | set number | ]]) feed('30iline\n<esc>') screen:expect([[ - {7: 27 }line |line28 | - {7: 28 }line |line29 | - {7: 29 }line |line30 | - {7: 30 }line |rows: 5, cols: 24 | - {7: 31 }^ |{2: } | + {7: 27 }line │line28 | + {7: 28 }line │line29 | + {7: 29 }line │line30 | + {7: 30 }line │rows: 5, cols: 24 | + {7: 31 }^ │{2: } | ========== ========== | | ]]) feed('<c-w>li') screen:expect([[ - {7: 27 }line |line28 | - {7: 28 }line |line29 | - {7: 29 }line |line30 | - {7: 30 }line |rows: 5, cols: 24 | - {7: 31 } |{1: } | + {7: 27 }line │line28 | + {7: 28 }line │line29 | + {7: 29 }line │line30 | + {7: 30 }line │rows: 5, cols: 24 | + {7: 31 } │{1: } | ========== ========== | {3:-- TERMINAL --} | ]]) @@ -140,11 +140,11 @@ describe('terminal mouse', function() thelpers.enable_mouse() thelpers.feed_data('mouse enabled\n') screen:expect([[ - {7: 27 }line |line29 | - {7: 28 }line |line30 | - {7: 29 }line |rows: 5, cols: 24 | - {7: 30 }line |mouse enabled | - {7: 31 } |{1: } | + {7: 27 }line │line29 | + {7: 28 }line │line30 | + {7: 29 }line │rows: 5, cols: 24 | + {7: 30 }line │mouse enabled | + {7: 31 } │{1: } | ========== ========== | {3:-- TERMINAL --} | ]]) @@ -153,21 +153,21 @@ describe('terminal mouse', function() it('wont lose focus if another window is scrolled', function() feed('<ScrollWheelUp><0,0><ScrollWheelUp><0,0>') screen:expect([[ - {7: 21 }line |line29 | - {7: 22 }line |line30 | - {7: 23 }line |rows: 5, cols: 24 | - {7: 24 }line |mouse enabled | - {7: 25 }line |{1: } | + {7: 21 }line │line29 | + {7: 22 }line │line30 | + {7: 23 }line │rows: 5, cols: 24 | + {7: 24 }line │mouse enabled | + {7: 25 }line │{1: } | ========== ========== | {3:-- TERMINAL --} | ]]) feed('<S-ScrollWheelDown><0,0>') screen:expect([[ - {7: 26 }line |line29 | - {7: 27 }line |line30 | - {7: 28 }line |rows: 5, cols: 24 | - {7: 29 }line |mouse enabled | - {7: 30 }line |{1: } | + {7: 26 }line │line29 | + {7: 27 }line │line30 | + {7: 28 }line │rows: 5, cols: 24 | + {7: 29 }line │mouse enabled | + {7: 30 }line │{1: } | ========== ========== | {3:-- TERMINAL --} | ]]) @@ -176,11 +176,11 @@ describe('terminal mouse', function() it('will lose focus if another window is clicked', function() feed('<LeftMouse><5,1>') screen:expect([[ - {7: 27 }line |line29 | - {7: 28 }l^ine |line30 | - {7: 29 }line |rows: 5, cols: 24 | - {7: 30 }line |mouse enabled | - {7: 31 } |{2: } | + {7: 27 }line │line29 | + {7: 28 }l^ine │line30 | + {7: 29 }line │rows: 5, cols: 24 | + {7: 30 }line │mouse enabled | + {7: 31 } │{2: } | ========== ========== | | ]]) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index d5f6a21d1d..2f9abfd3f6 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -4,13 +4,20 @@ local global_helpers = require('test.helpers') local uname = global_helpers.uname local helpers = require('test.functional.helpers')(after_each) local thelpers = require('test.functional.terminal.helpers') -local feed_data = thelpers.feed_data +local Screen = require('test.functional.ui.screen') +local eq = helpers.eq local feed_command = helpers.feed_command +local feed_data = thelpers.feed_data local clear = helpers.clear +local command = helpers.command +local eval = helpers.eval local nvim_dir = helpers.nvim_dir local retry = helpers.retry local nvim_prog = helpers.nvim_prog local nvim_set = helpers.nvim_set +local ok = helpers.ok +local read_file = helpers.read_file +local wait = helpers.wait if helpers.pending_win32(pending) then return end @@ -21,9 +28,6 @@ describe('tui', function() clear() screen = thelpers.screen_setup(0, '["'..nvim_prog ..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile noshowcmd noruler undodir=. directory=. viewdir=. backupdir=."]') - -- right now pasting can be really slow in the TUI, especially in ASAN. - -- this will be fixed later but for now we require a high timeout. - screen.timeout = 60000 screen:expect([[ {1: } | {4:~ }| @@ -39,6 +43,28 @@ describe('tui', function() screen:detach() end) + it('rapid resize #7572 #7628', function() + -- Need buffer rows to provoke the behavior. + feed_data(":edit test/functional/fixtures/bigfile.txt:") + command('call jobresize(b:terminal_job_id, 58, 9)') + command('call jobresize(b:terminal_job_id, 62, 13)') + command('call jobresize(b:terminal_job_id, 100, 42)') + command('call jobresize(b:terminal_job_id, 37, 1000)') + -- Resize to <5 columns. + screen:try_resize(4, 44) + command('call jobresize(b:terminal_job_id, 4, 1000)') + -- Resize to 1 row, then to 1 column, then increase rows to 4. + screen:try_resize(44, 1) + command('call jobresize(b:terminal_job_id, 44, 1)') + screen:try_resize(1, 1) + command('call jobresize(b:terminal_job_id, 1, 1)') + screen:try_resize(1, 4) + command('call jobresize(b:terminal_job_id, 1, 4)') + screen:try_resize(57, 17) + command('call jobresize(b:terminal_job_id, 57, 17)') + eq(2, eval("1+1")) -- Still alive? + end) + it('accepts basic utf-8 input', function() feed_data('iabc\ntest1\ntest2') screen:expect([[ @@ -89,16 +115,12 @@ describe('tui', function() ]]) end) - it('does not mangle unmapped ALT-key chord', function() - -- Vim represents ALT/META by setting the "high bit" of the modified key; - -- we do _not_. #3982 - -- - -- Example: for input ALT+j: - -- * Vim (Nvim prior to #3982) sets high-bit, inserts "ê". - -- * Nvim (after #3982) inserts "j". - feed_data('i\027j') + it('interprets ESC+key as ALT chord', function() + -- Vim represents ALT/META by setting the "high bit" of the modified key: + -- ALT+j inserts "ê". Nvim does not (#3982). + feed_data('i\022\027j') screen:expect([[ - j{1: } | + <M-j>{1: } | {4:~ }| {4:~ }| {4:~ }| @@ -125,6 +147,9 @@ describe('tui', function() end) it('automatically sends <Paste> for bracketed paste sequences', function() + -- Pasting can be really slow in the TUI, specially in ASAN. + -- This will be fixed later but for now we require a high timeout. + screen.timeout = 60000 feed_data('i\027[200~') screen:expect([[ {1: } | @@ -158,6 +183,8 @@ describe('tui', function() end) it('can handle arbitrarily long bursts of input', function() + -- Need extra time for this test, specially in ASAN. + screen.timeout = 60000 feed_command('set ruler') local t = {} for i = 1, 3000 do @@ -174,6 +201,58 @@ describe('tui', function() {3:-- TERMINAL --} | ]]) end) + + it('allows termguicolors to be set at runtime', function() + screen:set_option('rgb', true) + screen:set_default_attr_ids({ + [1] = {reverse = true}, + [2] = {foreground = 13, special = Screen.colors.Grey0}, + [3] = {special = Screen.colors.Grey0, bold = true, reverse = true}, + [4] = {bold = true}, + [5] = {special = Screen.colors.Grey0, reverse = true, foreground = 4}, + [6] = {foreground = 4, special = Screen.colors.Grey0}, + [7] = {special = Screen.colors.Grey0, reverse = true, foreground = Screen.colors.SeaGreen4}, + [8] = {foreground = Screen.colors.SeaGreen4, special = Screen.colors.Grey0}, + [9] = {special = Screen.colors.Grey0, bold = true, foreground = Screen.colors.Blue1}, + }) + + feed_data(':hi SpecialKey ctermfg=3 guifg=SeaGreen\n') + feed_data('i') + feed_data('\022\007') -- ctrl+g + feed_data('\028\014') -- crtl+\ ctrl+N + feed_data(':set termguicolors?\n') + screen:expect([[ + {5:^}{6:G} | + {2:~ }| + {2:~ }| + {2:~ }| + {3:[No Name] [+] }| + notermguicolors | + {4:-- TERMINAL --} | + ]]) + + feed_data(':set termguicolors\n') + screen:expect([[ + {7:^}{8:G} | + {9:~ }| + {9:~ }| + {9:~ }| + {3:[No Name] [+] }| + :set termguicolors | + {4:-- TERMINAL --} | + ]]) + + feed_data(':set notermguicolors\n') + screen:expect([[ + {5:^}{6:G} | + {2:~ }| + {2:~ }| + {2:~ }| + {3:[No Name] [+] }| + :set notermguicolors | + {4:-- TERMINAL --} | + ]]) + end) end) describe('tui with non-tty file descriptors', function() @@ -390,7 +469,7 @@ describe("tui 't_Co' (terminal colors)", function() nvim_set)) feed_data(":echo &t_Co\n") - helpers.wait() + wait() local tline if maxcolors == 8 or maxcolors == 16 then tline = "~ " @@ -639,6 +718,7 @@ end) describe("tui 'term' option", function() local screen local is_bsd = not not string.find(string.lower(uname()), 'bsd') + local is_macos = not not string.find(string.lower(uname()), 'darwin') local function assert_term(term_envvar, term_expected) clear() @@ -664,11 +744,62 @@ describe("tui 'term' option", function() end) it('gets system-provided term if $TERM is valid', function() - if is_bsd then -- BSD lacks terminfo, we always use builtin there. + if is_bsd then -- BSD lacks terminfo, builtin is always used. assert_term("xterm", "builtin_xterm") + elseif is_macos then + local status, _ = pcall(assert_term, "xterm", "xterm") + if not status then + pending("macOS: unibilium could not find terminfo", function() end) + end else assert_term("xterm", "xterm") end end) end) + +-- These tests require `thelpers` because --headless/--embed +-- does not initialize the TUI. +describe("tui", function() + local screen + local logfile = 'Xtest_tui_verbose_log' + after_each(function() + os.remove(logfile) + end) + + -- Runs (child) `nvim` in a TTY (:terminal), to start the builtin TUI. + local function nvim_tui(extra_args) + clear() + -- This is ugly because :term/termopen() forces TERM=xterm-256color. + -- TODO: Revisit this after jobstart/termopen accept `env` dict. + local cmd = string.format( + [=[['sh', '-c', 'LANG=C %s -u NONE -i NONE %s --cmd "%s"']]=], + nvim_prog, + extra_args or "", + nvim_set) + screen = thelpers.screen_setup(0, cmd) + end + + it('-V3log logs terminfo values', function() + nvim_tui('-V3'..logfile) + + -- Wait for TUI to start. + feed_data('Gitext') + screen:expect([[ + text{1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]) + + retry(nil, 3000, function() -- Wait for log file to be flushed. + local log = read_file('Xtest_tui_verbose_log') or '' + eq('--- Terminal info --- {{{\n', string.match(log, '--- Terminal.-\n')) + ok(#log > 50) + end) + end) + +end) diff --git a/test/functional/ui/bufhl_spec.lua b/test/functional/ui/bufhl_spec.lua index 2143c01139..5b38921e50 100644 --- a/test/functional/ui/bufhl_spec.lua +++ b/test/functional/ui/bufhl_spec.lua @@ -2,13 +2,11 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert -local command, request, neq = helpers.command, helpers.request, helpers.neq - -if helpers.pending_win32(pending) then return end +local command, neq = helpers.command, helpers.neq +local curbufmeths = helpers.curbufmeths describe('Buffer highlighting', function() local screen - local curbuf before_each(function() clear() @@ -27,21 +25,14 @@ describe('Buffer highlighting', function() [9] = {foreground = Screen.colors.SlateBlue, underline = true}, [10] = {foreground = Screen.colors.Red} }) - curbuf = request('nvim_get_current_buf') end) after_each(function() screen:detach() end) - local function add_hl(...) - return request('nvim_buf_add_highlight', curbuf, ...) - end - - local function clear_hl(...) - return request('nvim_buf_clear_highlight', curbuf, ...) - end - + local add_hl = curbufmeths.add_highlight + local clear_hl = curbufmeths.clear_highlight it('works', function() insert([[ diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua index d87ce72599..3c316d1cfa 100644 --- a/test/functional/ui/cmdline_highlight_spec.lua +++ b/test/functional/ui/cmdline_highlight_spec.lua @@ -24,6 +24,7 @@ before_each(function() clear() screen = Screen.new(40, 8) screen:attach() + command("set display-=msgsep") source([[ highlight RBP1 guibg=Red highlight RBP2 guibg=Yellow @@ -144,7 +145,13 @@ before_each(function() EOB={bold = true, foreground = Screen.colors.Blue1}, ERR={foreground = Screen.colors.Grey100, background = Screen.colors.Red}, SK={foreground = Screen.colors.Blue}, - PE={bold = true, foreground = Screen.colors.SeaGreen4} + PE={bold = true, foreground = Screen.colors.SeaGreen4}, + NUM={foreground = Screen.colors.Blue2}, + NPAR={foreground = Screen.colors.Yellow}, + SQ={foreground = Screen.colors.Blue3}, + SB={foreground = Screen.colors.Blue4}, + E={foreground = Screen.colors.Red, background = Screen.colors.Blue}, + M={bold = true}, }) end) @@ -731,6 +738,22 @@ describe('Command-line coloring', function() feed('<CR><CR>') eq('', meths.get_var('out')) end) + it('does not crash when callback has caught not-a-editor-command exception', + function() + source([[ + function CaughtExc(cmdline) abort + try + gibberish + catch + " Do nothing + endtry + return [] + endfunction + ]]) + set_color_cb('CaughtExc') + start_prompt('1') + eq(1, meths.eval('1')) + end) end) describe('Ex commands coloring support', function() it('works', function() @@ -843,7 +866,7 @@ describe('Ex commands coloring support', function() {EOB:~ }| | ]]) - eq('\nError detected while processing :\nE605: Exception not caught: 42', + eq('Error detected while processing :\nE605: Exception not caught: 42', meths.command_output('messages')) end) it('errors out when failing to get callback', function() @@ -863,7 +886,10 @@ describe('Ex commands coloring support', function() end) describe('Expressions coloring support', function() it('works', function() - meths.set_var('Nvim_color_expr', 'RainBowParens') + meths.command('hi clear NvimNumber') + meths.command('hi clear NvimNestingParenthesis') + meths.command('hi NvimNumber guifg=Blue2') + meths.command('hi NvimNestingParenthesis guifg=Yellow') feed(':echo <C-r>=(((1)))') screen:expect([[ | @@ -873,21 +899,103 @@ describe('Expressions coloring support', function() {EOB:~ }| {EOB:~ }| {EOB:~ }| - ={RBP1:(}{RBP2:(}{RBP3:(}1{RBP3:)}{RBP2:)}{RBP1:)}^ | + ={NPAR:(((}{NUM:1}{NPAR:)))}^ | ]]) end) - it('errors out when failing to get callback', function() + it('does not use Nvim_color_expr', function() meths.set_var('Nvim_color_expr', 42) + -- Used to error out due to failing to get callback. + meths.command('hi clear NvimNumber') + meths.command('hi NvimNumber guifg=Blue2') feed(':<C-r>=1') screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + ={NUM:1}^ | + ]]) + end) + it('works correctly with non-ASCII and control characters', function() + meths.command('hi clear NvimStringBody') + meths.command('hi clear NvimStringQuote') + meths.command('hi clear NvimInvalid') + meths.command('hi NvimStringQuote guifg=Blue3') + meths.command('hi NvimStringBody guifg=Blue4') + meths.command('hi NvimInvalid guifg=Red guibg=Blue') + feed('i<C-r>="«»"«»') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + ={SQ:"}{SB:«»}{SQ:"}{E:«»}^ | + ]]) + feed('<C-c>') + screen:expect([[ + ^ | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {M:-- INSERT --} | + ]]) + feed('<Esc>') + screen:expect([[ + ^ | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + | + ]]) + feed(':<C-\\>e"<C-v><C-x>"<C-v><C-x>') + -- TODO(ZyX-I): Parser highlighting should not override special character + -- highlighting. + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + ={SQ:"}{SB:^X}{SQ:"}{ERR:^X}^ | + ]]) + feed('<C-c>') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :^ | + ]]) + funcs.setreg('a', {'\192'}) + feed('<C-r>="<C-r><C-r>a"<C-r><C-r>a"foo"') + -- TODO(ZyX-I): Parser highlighting should not override special character + -- highlighting. + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| {EOB:~ }| {EOB:~ }| {EOB:~ }| - = | - {ERR:E5409: Unable to get g:Nvim_color_expr c}| - {ERR:allback: Vim:E6000: Argument is not a fu}| - {ERR:nction or function name} | - =1^ | + ={SQ:"}{SB:<c0>}{SQ:"}{E:<c0>"}{SB:foo}{E:"}^ | ]]) end) end) diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index 0f8302b036..f8680678ef 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -10,12 +10,19 @@ describe('external cmdline', function() local last_level = 0 local cmdline = {} local block = nil + local wild_items = nil + local wild_selected = nil before_each(function() clear() cmdline, block = {}, nil screen = Screen.new(25, 5) screen:attach({rgb=true, ext_cmdline=true}) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {reverse = true}, + [3] = {bold = true, reverse = true}, + }) screen:set_on_event_handler(function(name, data) if name == "cmdline_show" then local content, pos, firstc, prompt, indent, level = unpack(data) @@ -38,6 +45,12 @@ describe('external cmdline', function() block[#block+1] = data[1] elseif name == "cmdline_block_hide" then block = nil + elseif name == "wildmenu_show" then + wild_items = data[1] + elseif name == "wildmenu_select" then + wild_selected = data[1] + elseif name == "wildmenu_hide" then + wild_items, wild_selected = nil, nil end end) end) @@ -66,9 +79,9 @@ describe('external cmdline', function() feed(':') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq(1, last_level) @@ -84,9 +97,9 @@ describe('external cmdline', function() feed('sign') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq({{ @@ -101,9 +114,9 @@ describe('external cmdline', function() feed('<Left>') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq({{ @@ -118,9 +131,9 @@ describe('external cmdline', function() feed('<bs>') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq({{ @@ -135,9 +148,9 @@ describe('external cmdline', function() feed('<Esc>') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq({}, cmdline) @@ -148,9 +161,9 @@ describe('external cmdline', function() feed(':call input("input", "default")<cr>') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq({{ @@ -164,9 +177,9 @@ describe('external cmdline', function() feed('<cr>') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq({}, cmdline) @@ -178,9 +191,9 @@ describe('external cmdline', function() feed(':xx<c-r>') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq({{ @@ -196,9 +209,9 @@ describe('external cmdline', function() feed('=') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq({{ @@ -226,7 +239,11 @@ describe('external cmdline', function() prompt = "", special = {'"', true}, },{ - content = { { {}, "1+2" } }, + content = { + { {}, "1" }, + { {}, "+" }, + { {}, "2" }, + }, firstc = "=", indent = 0, pos = 3, @@ -234,9 +251,9 @@ describe('external cmdline', function() }} screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq(expectation, cmdline) @@ -249,9 +266,9 @@ describe('external cmdline', function() -- focus is at external cmdline anyway. screen:expect([[ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| ^ | ]], nil, nil, function() eq(expectation, cmdline) @@ -261,9 +278,9 @@ describe('external cmdline', function() feed('<cr>') screen:expect([[ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| ^ | ]], nil, nil, function() eq({{ @@ -278,9 +295,9 @@ describe('external cmdline', function() feed('<esc>') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq({}, cmdline) @@ -291,9 +308,9 @@ describe('external cmdline', function() feed(':function Foo()<cr>') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq({{ @@ -303,41 +320,70 @@ describe('external cmdline', function() pos = 0, prompt = "", }}, cmdline) - eq({{{{}, 'function Foo()'}}}, block) + eq({ { { {}, 'function Foo()'} } }, block) end) feed('line1<cr>') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() - eq({{{{}, 'function Foo()'}}, - {{{}, ' line1'}}}, block) + eq({ { { {}, 'function Foo()'} }, + { { {}, ' line1'} } }, block) end) block = {} command("redraw!") screen:expect([[ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| ^ | ]], nil, nil, function() - eq({{{{}, 'function Foo()'}}, - {{{}, ' line1'}}}, block) + eq({ { { {}, 'function Foo()'} }, + { { {}, ' line1'} } }, block) end) + feed('endfunction<cr>') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]], nil, nil, function() + eq(nil, block) + end) + + -- Try once more, to check buffer is reinitialized. #8007 + feed(':function Bar()<cr>') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]], nil, nil, function() + eq({{ + content = { { {}, "" } }, + firstc = ":", + indent = 2, + pos = 0, + prompt = "", + }}, cmdline) + eq({ { { {}, 'function Bar()'} } }, block) + end) feed('endfunction<cr>') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq(nil, block) @@ -348,9 +394,9 @@ describe('external cmdline', function() feed(':make') screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq({{ @@ -365,9 +411,9 @@ describe('external cmdline', function() feed('<c-f>') screen:expect([[ | - [No Name] | - :make^ | - [Command Line] | + {2:[No Name] }| + {1::}make^ | + {3:[Command Line] }| | ]], nil, nil, function() eq({}, cmdline) @@ -377,9 +423,9 @@ describe('external cmdline', function() feed(':yank') screen:expect([[ | - [No Name] | - :make^ | - [Command Line] | + {2:[No Name] }| + {1::}make^ | + {3:[Command Line] }| | ]], nil, nil, function() eq({nil, { @@ -395,9 +441,9 @@ describe('external cmdline', function() command("redraw!") screen:expect([[ | - [No Name] | - :make | - [Command Line] | + {2:[No Name] }| + {1::}make | + {3:[Command Line] }| ^ | ]], nil, nil, function() eq({nil, { @@ -412,9 +458,9 @@ describe('external cmdline', function() feed("<c-c>") screen:expect([[ | - [No Name] | - :make^ | - [Command Line] | + {2:[No Name] }| + {1::}make^ | + {3:[Command Line] }| | ]], nil, nil, function() eq({}, cmdline) @@ -423,9 +469,9 @@ describe('external cmdline', function() feed("<c-c>") screen:expect([[ | - [No Name] | - :make^ | - [Command Line] | + {2:[No Name] }| + {1::}make^ | + {3:[Command Line] }| | ]], nil, nil, function() eq({{ @@ -441,9 +487,9 @@ describe('external cmdline', function() command("redraw!") screen:expect([[ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| ^ | ]], nil, nil, function() eq({{ @@ -460,9 +506,9 @@ describe('external cmdline', function() feed(":call inputsecret('secret:')<cr>abc123") screen:expect([[ ^ | - ~ | - ~ | - ~ | + {1:~ }| + {1:~ }| + {1:~ }| | ]], nil, nil, function() eq({{ @@ -476,50 +522,160 @@ describe('external cmdline', function() end) it('works with highlighted cmdline', function() - source([[ - highlight RBP1 guibg=Red - highlight RBP2 guibg=Yellow - highlight RBP3 guibg=Green - highlight RBP4 guibg=Blue - let g:NUM_LVLS = 4 - function RainBowParens(cmdline) - let ret = [] - let i = 0 - let lvl = 0 - while i < len(a:cmdline) - if a:cmdline[i] is# '(' - call add(ret, [i, i + 1, 'RBP' . ((lvl % g:NUM_LVLS) + 1)]) - let lvl += 1 - elseif a:cmdline[i] is# ')' - let lvl -= 1 - call add(ret, [i, i + 1, 'RBP' . ((lvl % g:NUM_LVLS) + 1)]) - endif - let i += 1 - endwhile - return ret - endfunction - map <f5> :let x = input({'prompt':'>','highlight':'RainBowParens'})<cr> - "map <f5> :let x = input({'prompt':'>'})<cr> - ]]) - screen:set_default_attr_ids({ - RBP1={background = Screen.colors.Red}, - RBP2={background = Screen.colors.Yellow}, - RBP3={background = Screen.colors.Green}, - RBP4={background = Screen.colors.Blue}, - EOB={bold = true, foreground = Screen.colors.Blue1}, - ERR={foreground = Screen.colors.Grey100, background = Screen.colors.Red}, - SK={foreground = Screen.colors.Blue}, - PE={bold = true, foreground = Screen.colors.SeaGreen4} - }) - feed('<f5>(a(b)a)') - screen:expect([[ - ^ | - {EOB:~ }| - {EOB:~ }| - {EOB:~ }| - | - ]], nil, nil, function() - expect_cmdline(1, '{RBP1:(}a{RBP2:(}b{RBP2:)}a{RBP1:)}') - end) + source([[ + highlight RBP1 guibg=Red + highlight RBP2 guibg=Yellow + highlight RBP3 guibg=Green + highlight RBP4 guibg=Blue + let g:NUM_LVLS = 4 + function RainBowParens(cmdline) + let ret = [] + let i = 0 + let lvl = 0 + while i < len(a:cmdline) + if a:cmdline[i] is# '(' + call add(ret, [i, i + 1, 'RBP' . ((lvl % g:NUM_LVLS) + 1)]) + let lvl += 1 + elseif a:cmdline[i] is# ')' + let lvl -= 1 + call add(ret, [i, i + 1, 'RBP' . ((lvl % g:NUM_LVLS) + 1)]) + endif + let i += 1 + endwhile + return ret + endfunction + map <f5> :let x = input({'prompt':'>','highlight':'RainBowParens'})<cr> + "map <f5> :let x = input({'prompt':'>'})<cr> + ]]) + screen:set_default_attr_ids({ + RBP1={background = Screen.colors.Red}, + RBP2={background = Screen.colors.Yellow}, + RBP3={background = Screen.colors.Green}, + RBP4={background = Screen.colors.Blue}, + EOB={bold = true, foreground = Screen.colors.Blue1}, + ERR={foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + SK={foreground = Screen.colors.Blue}, + PE={bold = true, foreground = Screen.colors.SeaGreen4} + }) + feed('<f5>(a(b)a)') + screen:expect([[ + ^ | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + | + ]], nil, nil, function() + expect_cmdline(1, '{RBP1:(}a{RBP2:(}b{RBP2:)}a{RBP1:)}') + end) + end) + + it('works together with ext_wildmenu', function() + local expected = { + 'define', + 'jump', + 'list', + 'place', + 'undefine', + 'unplace', + } + + command('set wildmode=full') + command('set wildmenu') + screen:set_option('ext_wildmenu', true) + feed(':sign <tab>') + + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]], nil, nil, function() + eq({{ + content = { { {}, "sign define"} }, + firstc = ":", + indent = 0, + pos = 11, + prompt = "" + }}, cmdline) + eq(expected, wild_items) + eq(0, wild_selected) + end) + + feed('<tab>') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]], nil, nil, function() + eq({{ + content = { { {}, "sign jump"} }, + firstc = ":", + indent = 0, + pos = 9, + prompt = "" + }}, cmdline) + eq(expected, wild_items) + eq(1, wild_selected) + end) + + feed('<left><left>') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]], nil, nil, function() + eq({{ + content = { { {}, "sign "} }, + firstc = ":", + indent = 0, + pos = 5, + prompt = "" + }}, cmdline) + eq(expected, wild_items) + eq(-1, wild_selected) + end) + + feed('<right>') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]], nil, nil, function() + eq({{ + content = { { {}, "sign define"} }, + firstc = ":", + indent = 0, + pos = 11, + prompt = "" + }}, cmdline) + eq(expected, wild_items) + eq(0, wild_selected) + end) + + feed('a') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]], nil, nil, function() + eq({{ + content = { { {}, "sign definea"} }, + firstc = ":", + indent = 0, + pos = 12, + prompt = "" + }}, cmdline) + eq(nil, wild_items) + eq(nil, wild_selected) + end) end) end) diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua index b47210a777..812c095add 100644 --- a/test/functional/ui/cursor_spec.lua +++ b/test/functional/ui/cursor_spec.lua @@ -194,8 +194,8 @@ describe('ui/cursor', function() if m.blinkoff then m.blinkoff = 400 end if m.blinkwait then m.blinkwait = 700 end end - if m.hl_id then m.hl_id = 48 end - if m.id_lm then m.id_lm = 49 end + if m.hl_id then m.hl_id = 49 end + if m.id_lm then m.id_lm = 50 end end -- Assert the new expectation. diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index 2252e3580f..ab3b1c3cac 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -94,6 +94,7 @@ describe('highlight defaults', function() clear() screen = Screen.new() screen:attach() + command("set display-=msgsep") end) after_each(function() @@ -108,12 +109,12 @@ describe('highlight defaults', function() }) feed_command('sp', 'vsp', 'vsp') screen:expect([[ - ^ {2:|} {2:|} | - {0:~ }{2:|}{0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }{2:|}{0:~ }| + ^ {2:│} {2:│} | + {0:~ }{2:│}{0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }{2:│}{0:~ }| {1:[No Name] }{2:[No Name] [No Name] }| | {0:~ }| @@ -126,12 +127,12 @@ describe('highlight defaults', function() -- navigate to verify that the attributes are properly moved feed('<c-w>j') screen:expect([[ - {2:|} {2:|} | - {0:~ }{2:|}{0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }{2:|}{0:~ }| + {2:│} {2:│} | + {0:~ }{2:│}{0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }{2:│}{0:~ }| {2:[No Name] [No Name] [No Name] }| ^ | {0:~ }| @@ -146,12 +147,12 @@ describe('highlight defaults', function() -- (upstream vim has the same behavior) feed('<c-w>k<c-w>l') screen:expect([[ - {2:|}^ {2:|} | - {0:~ }{2:|}{0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }{2:|}{0:~ }| + {2:│}^ {2:│} | + {0:~ }{2:│}{0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }{2:│}{0:~ }| {2:[No Name] }{1:[No Name] }{2:[No Name] }| | {0:~ }| @@ -163,12 +164,12 @@ describe('highlight defaults', function() ]]) feed('<c-w>l') screen:expect([[ - {2:|} {2:|}^ | - {0:~ }{2:|}{0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }{2:|}{0:~ }| + {2:│} {2:│}^ | + {0:~ }{2:│}{0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }{2:│}{0:~ }| {2:[No Name] [No Name] }{1:[No Name] }| | {0:~ }| @@ -180,12 +181,12 @@ describe('highlight defaults', function() ]]) feed('<c-w>h<c-w>h') screen:expect([[ - ^ {2:|} {2:|} | - {0:~ }{2:|}{0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }{2:|}{0:~ }| - {0:~ }{2:|}{0:~ }{2:|}{0:~ }| + ^ {2:│} {2:│} | + {0:~ }{2:│}{0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }{2:│}{0:~ }| + {0:~ }{2:│}{0:~ }{2:│}{0:~ }| {1:[No Name] }{2:[No Name] [No Name] }| | {0:~ }| @@ -312,7 +313,7 @@ describe('highlight defaults', function() end) end) -describe('guisp (special/undercurl)', function() +describe('highlight', function() local screen before_each(function() @@ -321,7 +322,31 @@ describe('guisp (special/undercurl)', function() screen:attach() end) - it('can be set and is applied like foreground or background', function() + it('cterm=standout gui=standout', function() + screen:detach() + screen = Screen.new(20,5) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {standout = true, bold = true, underline = true, + background = Screen.colors.Gray90, foreground = Screen.colors.Blue1}, + [3] = {standout = true, underline = true, + background = Screen.colors.Gray90} + }) + feed_command('hi CursorLine cterm=standout,underline gui=standout,underline') + feed_command('set cursorline') + feed_command('set listchars=space:.,eol:¬,tab:>-,extends:>,precedes:<,trail:* list') + feed('i\t abcd <cr>\t abcd <cr><esc>k') + screen:expect([[ + {1:>-------.}abcd{1:*¬} | + {2:^>-------.}{3:abcd}{2:*¬}{3: }| + {1:¬} | + {1:~ }| + | + ]]) + end) + + it('guisp (special/undercurl)', function() feed_command('syntax on') feed_command('syn keyword TmpKeyword neovim') feed_command('syn keyword TmpKeyword1 special') @@ -650,6 +675,76 @@ describe("'listchars' highlight", function() end) end) +describe("MsgSeparator highlight and msgsep fillchar", function() + before_each(clear) + it("works", function() + local screen = Screen.new(50,5) + screen:set_default_attr_ids({ + [1] = {bold=true, foreground=Screen.colors.Blue}, + [2] = {bold=true, reverse=true}, + [3] = {bold = true, foreground = Screen.colors.SeaGreen4}, + [4] = {background = Screen.colors.Cyan, bold = true, reverse = true}, + [5] = {bold = true, background = Screen.colors.Magenta} + }) + screen:attach() + + -- defaults + feed_command("ls") + screen:expect([[ + | + {2: }| + :ls | + 1 %a "[No Name]" line 1 | + {3:Press ENTER or type command to continue}^ | + ]]) + feed('<cr>') + + feed_command("set fillchars+=msgsep:-") + feed_command("ls") + screen:expect([[ + | + {2:--------------------------------------------------}| + :ls | + 1 %a "[No Name]" line 1 | + {3:Press ENTER or type command to continue}^ | + ]]) + + -- linked to StatusLine per default + feed_command("hi StatusLine guibg=Cyan") + feed_command("ls") + screen:expect([[ + | + {4:--------------------------------------------------}| + :ls | + 1 %a "[No Name]" line 1 | + {3:Press ENTER or type command to continue}^ | + ]]) + + -- but can be unlinked + feed_command("hi clear MsgSeparator") + feed_command("hi MsgSeparator guibg=Magenta gui=bold") + feed_command("ls") + screen:expect([[ + | + {5:--------------------------------------------------}| + :ls | + 1 %a "[No Name]" line 1 | + {3:Press ENTER or type command to continue}^ | + ]]) + + -- when display doesn't contain msgsep, these options have no effect + feed_command("set display-=msgsep") + feed_command("ls") + screen:expect([[ + {1:~ }| + {1:~ }| + :ls | + 1 %a "[No Name]" line 1 | + {3:Press ENTER or type command to continue}^ | + ]]) + end) +end) + describe("'winhighlight' highlight", function() local screen @@ -683,6 +778,9 @@ describe("'winhighlight' highlight", function() [22] = {bold = true, foreground = Screen.colors.SeaGreen4}, [23] = {background = Screen.colors.LightMagenta}, [24] = {background = Screen.colors.WebGray}, + [25] = {bold = true, foreground = Screen.colors.Green1}, + [26] = {background = Screen.colors.Red}, + [27] = {background = Screen.colors.DarkBlue, bold = true, foreground = Screen.colors.Green1}, }) command("hi Background1 guibg=DarkBlue") command("hi Background2 guibg=DarkGreen") @@ -905,6 +1003,7 @@ describe("'winhighlight' highlight", function() end) it('background applies also to non-text', function() + command('set sidescroll=0') insert('Lorem ipsum dolor sit amet ') command('set shiftwidth=2') feed('>>') @@ -951,6 +1050,39 @@ describe("'winhighlight' highlight", function() ]]) end) + it("background doesn't override syntax background", function() + command('syntax on') + command('syntax keyword Foobar foobar') + command('syntax keyword Article the') + command('hi Foobar guibg=#FF0000') + command('hi Article guifg=#00FF00 gui=bold') + insert('the foobar was foobar') + screen:expect([[ + {25:the} {26:foobar} was {26:fooba}| + {26:^r} | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + + -- winhl=Normal:Group with background doesn't override syntax background, + -- but does combine with syntax foreground. + command('set winhl=Normal:Background1') + screen:expect([[ + {27:the}{1: }{26:foobar}{1: was }{26:fooba}| + {26:^r}{1: }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]) + end) + it('can override NonText, Conceal and EndOfBuffer', function() curbufmeths.set_lines(0,-1,true, {"raa\000"}) command('call matchaddpos("Conceal", [[1,2]], 0, -1, {"conceal": "#"})') diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua index 53fd17c309..27e4066d9f 100644 --- a/test/functional/ui/inccommand_spec.lua +++ b/test/functional/ui/inccommand_spec.lua @@ -12,6 +12,7 @@ local insert = helpers.insert local meths = helpers.meths local neq = helpers.neq local ok = helpers.ok +local retry = helpers.retry local source = helpers.source local wait = helpers.wait local nvim = helpers.nvim @@ -62,6 +63,7 @@ local function common_setup(screen, inccommand, text) command("syntax on") command("set nohlsearch") command("hi Substitute guifg=red guibg=yellow") + command("set display-=msgsep") screen:attach() screen:set_default_attr_ids({ [1] = {foreground = Screen.colors.Fuchsia}, @@ -91,22 +93,30 @@ local function common_setup(screen, inccommand, text) end end -describe(":substitute, inccommand=split does not trigger preview", function() +describe(":substitute, inccommand=split", function() before_each(function() clear() common_setup(nil, "split", default_text) end) - it("if invoked by a script ", function() + -- Test the tests: verify that the `1==bufnr('$')` assertion + -- in the "no preview" tests (below) actually means something. + it("previews interactive cmdline", function() + feed(':%s/tw/MO/g') + retry(nil, 1000, function() + eq(2, eval("bufnr('$')")) + end) + end) + + it("no preview if invoked by a script", function() source('%s/tw/MO/g') wait() eq(1, eval("bufnr('$')")) - -- sanity check: assert the buffer state expect(default_text:gsub("tw", "MO")) end) - it("if invoked by feedkeys()", function() + it("no preview if invoked by feedkeys()", function() -- in a script... source([[:call feedkeys(":%s/tw/MO/g\<CR>")]]) wait() @@ -114,15 +124,12 @@ describe(":substitute, inccommand=split does not trigger preview", function() feed([[:call feedkeys(":%s/tw/MO/g\<CR>")<CR>]]) wait() eq(1, eval("bufnr('$')")) - -- sanity check: assert the buffer state expect(default_text:gsub("tw", "MO")) end) end) describe(":substitute, 'inccommand' preserves", function() - if helpers.pending_win32(pending) then return end - before_each(clear) it('listed buffers (:ls)', function() @@ -285,8 +292,6 @@ describe(":substitute, 'inccommand' preserves", function() end) describe(":substitute, 'inccommand' preserves undo", function() - if helpers.pending_win32(pending) then return end - local cases = { "", "split", "nosplit" } local substrings = { @@ -700,8 +705,6 @@ describe(":substitute, 'inccommand' preserves undo", function() end) describe(":substitute, inccommand=split", function() - if helpers.pending_win32(pending) then return end - local screen = Screen.new(30,15) before_each(function() @@ -1169,8 +1172,6 @@ describe(":substitute, inccommand=split", function() end) describe("inccommand=nosplit", function() - if helpers.pending_win32(pending) then return end - local screen = Screen.new(20,10) before_each(function() @@ -1356,8 +1357,6 @@ describe("inccommand=nosplit", function() end) describe(":substitute, 'inccommand' with a failing expression", function() - if helpers.pending_win32(pending) then return end - local screen = Screen.new(20,10) local cases = { "", "split", "nosplit" } @@ -1621,8 +1620,6 @@ describe("'inccommand' autocommands", function() end) describe("'inccommand' split windows", function() - if helpers.pending_win32(pending) then return end - local screen local function refresh() clear() @@ -1642,26 +1639,26 @@ describe("'inccommand' split windows", function() feed_command("split") feed(":%s/tw") screen:expect([[ - Inc substitution on {10:|}Inc substitution on| - {12:tw}o lines {10:|}{12:tw}o lines | - {10:|} | - {15:~ }{10:|}{15:~ }| - {15:~ }{10:|}{15:~ }| - {15:~ }{10:|}{15:~ }| - {15:~ }{10:|}{15:~ }| - {15:~ }{10:|}{15:~ }| - {15:~ }{10:|}{15:~ }| - {15:~ }{10:|}{15:~ }| - {15:~ }{10:|}{15:~ }| - {15:~ }{10:|}{15:~ }| - {15:~ }{10:|}{15:~ }| - {15:~ }{10:|}{15:~ }| - {11:[No Name] [+] }{10:|}{15:~ }| - Inc substitution on {10:|}{15:~ }| - {12:tw}o lines {10:|}{15:~ }| - {10:|}{15:~ }| - {15:~ }{10:|}{15:~ }| - {15:~ }{10:|}{15:~ }| + Inc substitution on {10:│}Inc substitution on| + {12:tw}o lines {10:│}{12:tw}o lines | + {10:│} | + {15:~ }{10:│}{15:~ }| + {15:~ }{10:│}{15:~ }| + {15:~ }{10:│}{15:~ }| + {15:~ }{10:│}{15:~ }| + {15:~ }{10:│}{15:~ }| + {15:~ }{10:│}{15:~ }| + {15:~ }{10:│}{15:~ }| + {15:~ }{10:│}{15:~ }| + {15:~ }{10:│}{15:~ }| + {15:~ }{10:│}{15:~ }| + {15:~ }{10:│}{15:~ }| + {11:[No Name] [+] }{10:│}{15:~ }| + Inc substitution on {10:│}{15:~ }| + {12:tw}o lines {10:│}{15:~ }| + {10:│}{15:~ }| + {15:~ }{10:│}{15:~ }| + {15:~ }{10:│}{15:~ }| {10:[No Name] [+] [No Name] [+] }| |2| {12:tw}o lines | {15:~ }| @@ -1681,20 +1678,20 @@ describe("'inccommand' split windows", function() feed(":%s/tw") screen:expect([[ - Inc substitution on {10:|}Inc substitution on| - {12:tw}o lines {10:|}{12:tw}o lines | - {10:|} | - {15:~ }{10:|}{15:~ }| - {15:~ }{10:|}{15:~ }| - {15:~ }{10:|}{15:~ }| - {15:~ }{10:|}{15:~ }| - {15:~ }{10:|}{15:~ }| - {15:~ }{10:|}{15:~ }| - {15:~ }{10:|}{15:~ }| - {15:~ }{10:|}{15:~ }| - {15:~ }{10:|}{15:~ }| - {15:~ }{10:|}{15:~ }| - {15:~ }{10:|}{15:~ }| + Inc substitution on {10:│}Inc substitution on| + {12:tw}o lines {10:│}{12:tw}o lines | + {10:│} | + {15:~ }{10:│}{15:~ }| + {15:~ }{10:│}{15:~ }| + {15:~ }{10:│}{15:~ }| + {15:~ }{10:│}{15:~ }| + {15:~ }{10:│}{15:~ }| + {15:~ }{10:│}{15:~ }| + {15:~ }{10:│}{15:~ }| + {15:~ }{10:│}{15:~ }| + {15:~ }{10:│}{15:~ }| + {15:~ }{10:│}{15:~ }| + {15:~ }{10:│}{15:~ }| {11:[No Name] [+] }{10:[No Name] [+] }| Inc substitution on | {12:tw}o lines | diff --git a/test/functional/ui/input_spec.lua b/test/functional/ui/input_spec.lua index 29d974b709..3dd9a2506e 100644 --- a/test/functional/ui/input_spec.lua +++ b/test/functional/ui/input_spec.lua @@ -1,11 +1,11 @@ local helpers = require('test.functional.helpers')(after_each) local clear, feed_command, nvim = helpers.clear, helpers.feed_command, helpers.nvim -local feed, next_message, eq = helpers.feed, helpers.next_message, helpers.eq +local feed, next_msg, eq = helpers.feed, helpers.next_msg, helpers.eq +local command = helpers.command local expect = helpers.expect +local write_file = helpers.write_file local Screen = require('test.functional.ui.screen') -if helpers.pending_win32(pending) then return end - describe('mappings', function() local cid @@ -17,7 +17,7 @@ describe('mappings', function() local check_mapping = function(mapping, expected) feed(mapping) - eq({'notification', 'mapped', {expected}}, next_message()) + eq({'notification', 'mapped', {expected}}, next_msg()) end before_each(function() @@ -126,3 +126,97 @@ describe('input utf sequences that contain CSI/K_SPECIAL', function() expect('…') end) end) + +describe('input non-printable chars', function() + after_each(function() + os.remove('Xtest-overwrite') + end) + + it("doesn't crash when echoing them back", function() + write_file("Xtest-overwrite", [[foobar]]) + clear() + local screen = Screen.new(60,8) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [3] = {bold = true, foreground = Screen.colors.SeaGreen4} + }) + screen:attach() + command("set display-=msgsep") + + feed_command("e Xtest-overwrite") + screen:expect([[ + ^foobar | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "Xtest-overwrite" [noeol] 1L, 6C | + ]]) + + -- The timestamp is in second resolution, wait two seconds to be sure. + screen:sleep(2000) + write_file("Xtest-overwrite", [[smurf]]) + feed_command("w") + screen:expect([[ + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "Xtest-overwrite" | + {2:WARNING: The file has been changed since reading it!!!} | + {3:Do you really want to write to it (y/n)?}^ | + ]]) + + feed("u") + screen:expect([[ + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "Xtest-overwrite" | + {2:WARNING: The file has been changed since reading it!!!} | + {3:Do you really want to write to it (y/n)?}u | + {3:Do you really want to write to it (y/n)?}^ | + ]]) + + feed("\005") + screen:expect([[ + {1:~ }| + {1:~ }| + {1:~ }| + "Xtest-overwrite" | + {2:WARNING: The file has been changed since reading it!!!} | + {3:Do you really want to write to it (y/n)?}u | + {3:Do you really want to write to it (y/n)?} | + {3:Do you really want to write to it (y/n)?}^ | + ]]) + + feed("n") + screen:expect([[ + {1:~ }| + {1:~ }| + "Xtest-overwrite" | + {2:WARNING: The file has been changed since reading it!!!} | + {3:Do you really want to write to it (y/n)?}u | + {3:Do you really want to write to it (y/n)?} | + {3:Do you really want to write to it (y/n)?}n | + {3:Press ENTER or type command to continue}^ | + ]]) + + feed("<cr>") + screen:expect([[ + ^foobar | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end) +end) diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua index 3daf92eea0..debd324977 100644 --- a/test/functional/ui/mouse_spec.lua +++ b/test/functional/ui/mouse_spec.lua @@ -3,8 +3,7 @@ local Screen = require('test.functional.ui.screen') local clear, feed, meths = helpers.clear, helpers.feed, helpers.meths local insert, feed_command = helpers.insert, helpers.feed_command local eq, funcs = helpers.eq, helpers.funcs - -if helpers.pending_win32(pending) then return end +local command = helpers.command describe('ui/mouse/input', function() local screen @@ -27,6 +26,7 @@ describe('ui/mouse/input', function() [4] = {reverse = true}, [5] = {bold = true, reverse = true}, }) + command("set display-=msgsep") feed('itesting<cr>mouse<cr>support and selection<esc>') screen:expect([[ testing | @@ -639,12 +639,12 @@ describe('ui/mouse/input', function() screen:try_resize(53, 14) feed_command('sp', 'vsp') screen:expect([[ - lines {4:|}lines | - to {4:|}to | - test {4:|}test | - mouse scrolling {4:|}mouse scrolling | - ^ {4:|} | - {0:~ }{4:|}{0:~ }| + lines {4:│}lines | + to {4:│}to | + test {4:│}test | + mouse scrolling {4:│}mouse scrolling | + ^ {4:│} | + {0:~ }{4:│}{0:~ }| {5:[No Name] [+] }{4:[No Name] [+] }| to | test | @@ -656,12 +656,12 @@ describe('ui/mouse/input', function() ]]) feed('<ScrollWheelDown><0,0>') screen:expect([[ - mouse scrolling {4:|}lines | - ^ {4:|}to | - {0:~ }{4:|}test | - {0:~ }{4:|}mouse scrolling | - {0:~ }{4:|} | - {0:~ }{4:|}{0:~ }| + mouse scrolling {4:│}lines | + ^ {4:│}to | + {0:~ }{4:│}test | + {0:~ }{4:│}mouse scrolling | + {0:~ }{4:│} | + {0:~ }{4:│}{0:~ }| {5:[No Name] [+] }{4:[No Name] [+] }| to | test | @@ -673,12 +673,12 @@ describe('ui/mouse/input', function() ]]) feed('<ScrollWheelUp><27,0>') screen:expect([[ - mouse scrolling {4:|}text | - ^ {4:|}with | - {0:~ }{4:|}many | - {0:~ }{4:|}lines | - {0:~ }{4:|}to | - {0:~ }{4:|}test | + mouse scrolling {4:│}text | + ^ {4:│}with | + {0:~ }{4:│}many | + {0:~ }{4:│}lines | + {0:~ }{4:│}to | + {0:~ }{4:│}test | {5:[No Name] [+] }{4:[No Name] [+] }| to | test | @@ -690,12 +690,12 @@ describe('ui/mouse/input', function() ]]) feed('<ScrollWheelUp><27,7><ScrollWheelUp>') screen:expect([[ - mouse scrolling {4:|}text | - ^ {4:|}with | - {0:~ }{4:|}many | - {0:~ }{4:|}lines | - {0:~ }{4:|}to | - {0:~ }{4:|}test | + mouse scrolling {4:│}text | + ^ {4:│}with | + {0:~ }{4:│}many | + {0:~ }{4:│}lines | + {0:~ }{4:│}to | + {0:~ }{4:│}test | {5:[No Name] [+] }{4:[No Name] [+] }| Inserting | text | @@ -708,6 +708,7 @@ describe('ui/mouse/input', function() end) it('horizontal scrolling', function() + command('set sidescroll=0') feed("<esc>:set nowrap<cr>") feed("a <esc>20Ab<esc>") @@ -752,17 +753,19 @@ describe('ui/mouse/input', function() feed_command('set concealcursor=n') feed_command('set nowrap') - feed_command('syntax match NonText "\\<amet\\>" conceal') - feed_command('syntax match NonText "\\cs\\|g." conceal cchar=X') - feed_command('syntax match NonText "\\%(lo\\|cl\\)." conceal') - feed_command('syntax match NonText "Lo" conceal cchar=Y') + feed_command('set shiftwidth=2 tabstop=4 list listchars=tab:>-') + feed_command('syntax match NonText "\\*" conceal') + feed_command('syntax match NonText "cats" conceal cchar=X') + feed_command('syntax match NonText "x" conceal cchar=>') + -- First column is there to retain the tabs. insert([[ - Lorem ipsum dolor sit amet, consetetur sadipscing elitr. - Stet clita kasd gubergren, no sea takimata sanctus est. + |Section *t1* + | *t2* *t3* *t4* + |x ç§ã¯çŒ«ãŒå¤§å¥½ã *cats* ✨ðŸˆâœ¨ ]]) - feed('gg') + feed('gg<c-v>Gxgg') end) it('(level 1) click on non-wrapped lines', function() @@ -770,93 +773,138 @@ describe('ui/mouse/input', function() feed('<esc><LeftMouse><0,0>') screen:expect([[ - {c:^Y}rem ip{c:X}um do{c: } {c:X}it {c: }, con| - {c:X}tet {c: }ta ka{c:X}d {c:X}ber{c:X}en, no| + ^Section{0:>>--->--->---}{c: }t1{c: } | + {0:>--->--->---} {c: }t2{c: } {c: }t3{c: } {c: }| + {c:>} ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>---}{c: X } {0:>}| | {0:~ }| {0:~ }| - {0:~ }| | ]]) feed('<esc><LeftMouse><1,0>') screen:expect([[ - {c:Y}^rem ip{c:X}um do{c: } {c:X}it {c: }, con| - {c:X}tet {c: }ta ka{c:X}d {c:X}ber{c:X}en, no| + S^ection{0:>>--->--->---}{c: }t1{c: } | + {0:>--->--->---} {c: }t2{c: } {c: }t3{c: } {c: }| + {c:>} ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>---}{c: X } {0:>}| | {0:~ }| {0:~ }| + | + ]]) + + feed('<esc><LeftMouse><21,0>') + screen:expect([[ + Section{0:>>--->--->---}{c: }^t1{c: } | + {0:>--->--->---} {c: }t2{c: } {c: }t3{c: } {c: }| + {c:>} ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>---}{c: X } {0:>}| + | + {0:~ }| {0:~ }| | ]]) - feed('<esc><LeftMouse><15,0>') + feed('<esc><LeftMouse><21,1>') screen:expect([[ - {c:Y}rem ip{c:X}um do{c: } {c:^X}it {c: }, con| - {c:X}tet {c: }ta ka{c:X}d {c:X}ber{c:X}en, no| + Section{0:>>--->--->---}{c: }t1{c: } | + {0:>--->--->---} {c: }t2{c: } {c: }t^3{c: } {c: }| + {c:>} ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>---}{c: X } {0:>}| | {0:~ }| {0:~ }| + | + ]]) + + feed('<esc><LeftMouse><0,2>') + screen:expect([[ + Section{0:>>--->--->---}{c: }t1{c: } | + {0:>--->--->---} {c: }t2{c: } {c: }t3{c: } {c: }| + {c:^>} ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>---}{c: X } {0:>}| + | + {0:~ }| {0:~ }| | ]]) - feed('<esc><LeftMouse><15,1>') + feed('<esc><LeftMouse><7,2>') screen:expect([[ - {c:Y}rem ip{c:X}um do{c: } {c:X}it {c: }, con| - {c:X}tet {c: }ta ka{c:X}d {c:X}^ber{c:X}en, no| + Section{0:>>--->--->---}{c: }t1{c: } | + {0:>--->--->---} {c: }t2{c: } {c: }t3{c: } {c: }| + {c:>} ç§ã¯^猫ãŒå¤§å¥½ã{0:>---}{c: X } {0:>}| | {0:~ }| {0:~ }| + | + ]]) + + feed('<esc><LeftMouse><21,2>') + screen:expect([[ + Section{0:>>--->--->---}{c: }t1{c: } | + {0:>--->--->---} {c: }t2{c: } {c: }t3{c: } {c: }| + {c:>} ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>---}{c: ^X } {0:>}| + | + {0:~ }| {0:~ }| | ]]) + end) -- level 1 - non wrapped it('(level 1) click on wrapped lines', function() feed_command('let &conceallevel=1', 'let &wrap=1', 'echo') - feed('<esc><LeftMouse><0,0>') + feed('<esc><LeftMouse><24,1>') screen:expect([[ - {c:^Y}rem ip{c:X}um do{c: } {c:X}it {c: } | - , con{c:X}etetur {c:X}adip{c:X}cin{c:X} | - elitr. | - {c:X}tet {c: }ta ka{c:X}d {c:X}ber{c:X}en | - , no {c:X}ea takimata {c:X}anctu{c:X}| - e{c:X}t. | + Section{0:>>--->--->---}{c: }t1{c: } | + {0:>--->--->---} {c: }t2{c: } {c: }t3{c: } {c:^ }| + t4{c: } | + {c:>} ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>---}{c: X} | + {c: } ✨ðŸˆâœ¨ | + | | ]]) - feed('<esc><LeftMouse><6,1>') + feed('<esc><LeftMouse><0,2>') screen:expect([[ - {c:Y}rem ip{c:X}um do{c: } {c:X}it {c: } | - , con{c:X}^etetur {c:X}adip{c:X}cin{c:X} | - elitr. | - {c:X}tet {c: }ta ka{c:X}d {c:X}ber{c:X}en | - , no {c:X}ea takimata {c:X}anctu{c:X}| - e{c:X}t. | + Section{0:>>--->--->---}{c: }t1{c: } | + {0:>--->--->---} {c: }t2{c: } {c: }t3{c: } {c: }| + ^t4{c: } | + {c:>} ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>---}{c: X} | + {c: } ✨ðŸˆâœ¨ | + | | ]]) - feed('<esc><LeftMouse><15,1>') + feed('<esc><LeftMouse><8,3>') screen:expect([[ - {c:Y}rem ip{c:X}um do{c: } {c:X}it {c: } | - , con{c:X}etetur {c:X}a^dip{c:X}cin{c:X} | - elitr. | - {c:X}tet {c: }ta ka{c:X}d {c:X}ber{c:X}en | - , no {c:X}ea takimata {c:X}anctu{c:X}| - e{c:X}t. | + Section{0:>>--->--->---}{c: }t1{c: } | + {0:>--->--->---} {c: }t2{c: } {c: }t3{c: } {c: }| + t4{c: } | + {c:>} ç§ã¯çŒ«^ãŒå¤§å¥½ã{0:>---}{c: X} | + {c: } ✨ðŸˆâœ¨ | + | | ]]) - feed('<esc><LeftMouse><15,3>') + feed('<esc><LeftMouse><21,3>') screen:expect([[ - {c:Y}rem ip{c:X}um do{c: } {c:X}it {c: } | - , con{c:X}etetur {c:X}adip{c:X}cin{c:X} | - elitr. | - {c:X}tet {c: }ta ka{c:X}d {c:X}^ber{c:X}en | - , no {c:X}ea takimata {c:X}anctu{c:X}| - e{c:X}t. | + Section{0:>>--->--->---}{c: }t1{c: } | + {0:>--->--->---} {c: }t2{c: } {c: }t3{c: } {c: }| + t4{c: } | + {c:>} ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>---}{c: ^X} | + {c: } ✨ðŸˆâœ¨ | + | + | + ]]) + + feed('<esc><LeftMouse><4,4>') + screen:expect([[ + Section{0:>>--->--->---}{c: }t1{c: } | + {0:>--->--->---} {c: }t2{c: } {c: }t3{c: } {c: }| + t4{c: } | + {c:>} ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>---}{c: X} | + {c: } ✨^ðŸˆâœ¨ | + | | ]]) end) -- level 1 - wrapped @@ -865,46 +913,68 @@ describe('ui/mouse/input', function() it('(level 2) click on non-wrapped lines', function() feed_command('let &conceallevel=2', 'echo') - feed('<esc><LeftMouse><0,0>') + feed('<esc><LeftMouse><20,0>') screen:expect([[ - {c:^Y}rem ip{c:X}um do {c:X}it , con{c:X}e| - {c:X}tet ta ka{c:X}d {c:X}ber{c:X}en, no | + Section{0:>>--->--->---}^t1 | + {0:>--->--->---} t2 t3 t4 | + {c:>} ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>---}{c:X} ✨{0:>}| | {0:~ }| {0:~ }| - {0:~ }| | ]]) - feed('<esc><LeftMouse><1,0>') + feed('<esc><LeftMouse><14,1>') screen:expect([[ - {c:Y}^rem ip{c:X}um do {c:X}it , con{c:X}e| - {c:X}tet ta ka{c:X}d {c:X}ber{c:X}en, no | + Section{0:>>--->--->---}t1 | + {0:>--->--->---} ^t2 t3 t4 | + {c:>} ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>---}{c:X} ✨{0:>}| | {0:~ }| {0:~ }| - {0:~ }| | ]]) - feed('<esc><LeftMouse><15,0>') + feed('<esc><LeftMouse><18,1>') screen:expect([[ - {c:Y}rem ip{c:X}um do {c:X}^it , con{c:X}e| - {c:X}tet ta ka{c:X}d {c:X}ber{c:X}en, no | + Section{0:>>--->--->---}t1 | + {0:>--->--->---} t2 t^3 t4 | + {c:>} ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>---}{c:X} ✨{0:>}| | {0:~ }| {0:~ }| + | + ]]) + + feed('<esc><LeftMouse><0,2>') -- Weirdness + screen:expect([[ + Section{0:>>--->--->---}t1 | + {0:>--->--->---} t2 t3 t4 | + {c:^>} ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>---}{c:X} ✨{0:>}| + | + {0:~ }| {0:~ }| | ]]) - feed('<esc><LeftMouse><15,1>') + feed('<esc><LeftMouse><8,2>') screen:expect([[ - {c:Y}rem ip{c:X}um do {c:X}it , con{c:X}e| - {c:X}tet ta ka{c:X}d {c:X}b^er{c:X}en, no | + Section{0:>>--->--->---}t1 | + {0:>--->--->---} t2 t3 t4 | + {c:>} ç§ã¯çŒ«^ãŒå¤§å¥½ã{0:>---}{c:X} ✨{0:>}| | {0:~ }| {0:~ }| + | + ]]) + + feed('<esc><LeftMouse><20,2>') + screen:expect([[ + Section{0:>>--->--->---}t1 | + {0:>--->--->---} t2 t3 t4 | + {c:>} ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>---}{c:^X} ✨{0:>}| + | + {0:~ }| {0:~ }| | ]]) @@ -913,47 +983,108 @@ describe('ui/mouse/input', function() it('(level 2) click on wrapped lines', function() feed_command('let &conceallevel=2', 'let &wrap=1', 'echo') - feed('<esc><LeftMouse><0,0>') + feed('<esc><LeftMouse><20,0>') screen:expect([[ - {c:^Y}rem ip{c:X}um do {c:X}it | - , con{c:X}etetur {c:X}adip{c:X}cin{c:X} | - elitr. | - {c:X}tet ta ka{c:X}d {c:X}ber{c:X}en | - , no {c:X}ea takimata {c:X}anctu{c:X}| - e{c:X}t. | + Section{0:>>--->--->---}^t1 | + {0:>--->--->---} t2 t3 | + t4 | + {c:>} ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>---}{c:X} | + ✨ðŸˆâœ¨ | + | | ]]) - feed('<esc><LeftMouse><6,1>') + feed('<esc><LeftMouse><14,1>') screen:expect([[ - {c:Y}rem ip{c:X}um do {c:X}it | - , con{c:X}^etetur {c:X}adip{c:X}cin{c:X} | - elitr. | - {c:X}tet ta ka{c:X}d {c:X}ber{c:X}en | - , no {c:X}ea takimata {c:X}anctu{c:X}| - e{c:X}t. | + Section{0:>>--->--->---}t1 | + {0:>--->--->---} ^t2 t3 | + t4 | + {c:>} ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>---}{c:X} | + ✨ðŸˆâœ¨ | + | | ]]) - feed('<esc><LeftMouse><15,1>') + feed('<esc><LeftMouse><18,1>') screen:expect([[ - {c:Y}rem ip{c:X}um do {c:X}it | - , con{c:X}etetur {c:X}a^dip{c:X}cin{c:X} | - elitr. | - {c:X}tet ta ka{c:X}d {c:X}ber{c:X}en | - , no {c:X}ea takimata {c:X}anctu{c:X}| - e{c:X}t. | + Section{0:>>--->--->---}t1 | + {0:>--->--->---} t2 t^3 | + t4 | + {c:>} ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>---}{c:X} | + ✨ðŸˆâœ¨ | + | | ]]) - feed('<esc><LeftMouse><15,3>') + -- NOTE: The click would ideally be on the 't' in 't4', but wrapping + -- caused the invisible '*' right before 't4' to remain on the previous + -- screen line. This is being treated as expected because fixing this is + -- out of scope for mouse clicks. Should the wrapping behavior of + -- concealed characters change in the future, this case should be + -- reevaluated. + feed('<esc><LeftMouse><0,2>') screen:expect([[ - {c:Y}rem ip{c:X}um do {c:X}it | - , con{c:X}etetur {c:X}adip{c:X}cin{c:X} | - elitr. | - {c:X}tet ta ka{c:X}d {c:X}b^er{c:X}en | - , no {c:X}ea takimata {c:X}anctu{c:X}| - e{c:X}t. | + Section{0:>>--->--->---}t1 | + {0:>--->--->---} t2 t3 ^ | + t4 | + {c:>} ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>---}{c:X} | + ✨ðŸˆâœ¨ | + | + | + ]]) + + feed('<esc><LeftMouse><1,2>') + screen:expect([[ + Section{0:>>--->--->---}t1 | + {0:>--->--->---} t2 t3 | + t^4 | + {c:>} ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>---}{c:X} | + ✨ðŸˆâœ¨ | + | + | + ]]) + + feed('<esc><LeftMouse><0,3>') + screen:expect([[ + Section{0:>>--->--->---}t1 | + {0:>--->--->---} t2 t3 | + t4 | + {c:^>} ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>---}{c:X} | + ✨ðŸˆâœ¨ | + | + | + ]]) + + feed('<esc><LeftMouse><20,3>') + screen:expect([[ + Section{0:>>--->--->---}t1 | + {0:>--->--->---} t2 t3 | + t4 | + {c:>} ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>---}{c:^X} | + ✨ðŸˆâœ¨ | + | + | + ]]) + + feed('<esc><LeftMouse><1,4>') + screen:expect([[ + Section{0:>>--->--->---}t1 | + {0:>--->--->---} t2 t3 | + t4 | + {c:>} ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>---}{c:X} | + ^✨ðŸˆâœ¨ | + | + | + ]]) + + feed('<esc><LeftMouse><5,4>') + screen:expect([[ + Section{0:>>--->--->---}t1 | + {0:>--->--->---} t2 t3 | + t4 | + {c:>} ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>---}{c:X} | + ✨ðŸˆ^✨ | + | | ]]) end) -- level 2 - wrapped @@ -962,47 +1093,47 @@ describe('ui/mouse/input', function() it('(level 3) click on non-wrapped lines', function() feed_command('let &conceallevel=3', 'echo') - feed('<esc><LeftMouse><0,0>') + feed('<esc><LeftMouse><0,2>') screen:expect([[ - ^rem ipum do it , conetetu| - tet ta kad beren, no ea t| + Section{0:>>--->--->---}t1 | + {0:>--->--->---} t2 t3 t4 | + ^ ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>----} ✨ðŸˆ| | {0:~ }| {0:~ }| - {0:~ }| | ]]) - feed('<esc><LeftMouse><1,0>') + feed('<esc><LeftMouse><1,2>') screen:expect([[ - r^em ipum do it , conetetu| - tet ta kad beren, no ea t| + Section{0:>>--->--->---}t1 | + {0:>--->--->---} t2 t3 t4 | + ^ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>----} ✨ðŸˆ| | {0:~ }| {0:~ }| - {0:~ }| | ]]) - feed('<esc><LeftMouse><15,0>') + feed('<esc><LeftMouse><13,2>') screen:expect([[ - rem ipum do it ^, conetetu| - tet ta kad beren, no ea t| + Section{0:>>--->--->---}t1 | + {0:>--->--->---} t2 t3 t4 | + ç§ã¯çŒ«ãŒå¤§å¥½^ã{0:>----} ✨ðŸˆ| | {0:~ }| {0:~ }| - {0:~ }| | ]]) - feed('<esc><LeftMouse><15,1>') + feed('<esc><LeftMouse><20,2>') screen:expect([[ - rem ipum do it , conetetu| - tet ta kad bere^n, no ea t| + Section{0:>>--->--->---}t1 | + {0:>--->--->---} t2 t3 t4 | + ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>----}^ ✨ðŸˆ| | {0:~ }| {0:~ }| - {0:~ }| | ]]) end) -- level 3 - non wrapped @@ -1010,49 +1141,94 @@ describe('ui/mouse/input', function() it('(level 3) click on wrapped lines', function() feed_command('let &conceallevel=3', 'let &wrap=1', 'echo') - feed('<esc><LeftMouse><0,0>') + feed('<esc><LeftMouse><14,1>') + screen:expect([[ + Section{0:>>--->--->---}t1 | + {0:>--->--->---} ^t2 t3 | + t4 | + ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>----} | + ✨ðŸˆâœ¨ | + | + | + ]]) + + feed('<esc><LeftMouse><18,1>') screen:expect([[ - ^rem ipum do it | - , conetetur adipcin | - elitr. | - tet ta kad beren | - , no ea takimata anctu | - et. | + Section{0:>>--->--->---}t1 | + {0:>--->--->---} t2 t^3 | + t4 | + ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>----} | + ✨ðŸˆâœ¨ | + | | ]]) - feed('<esc><LeftMouse><6,1>') + feed('<esc><LeftMouse><1,2>') screen:expect([[ - rem ipum do it | - , cone^tetur adipcin | - elitr. | - tet ta kad beren | - , no ea takimata anctu | - et. | + Section{0:>>--->--->---}t1 | + {0:>--->--->---} t2 t3 | + t^4 | + ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>----} | + ✨ðŸˆâœ¨ | + | | ]]) - feed('<esc><LeftMouse><15,1>') + feed('<esc><LeftMouse><0,3>') screen:expect([[ - rem ipum do it | - , conetetur adi^pcin | - elitr. | - tet ta kad beren | - , no ea takimata anctu | - et. | + Section{0:>>--->--->---}t1 | + {0:>--->--->---} t2 t3 | + t4 | + ^ ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>----} | + ✨ðŸˆâœ¨ | + | | ]]) - feed('<esc><LeftMouse><15,3>') + feed('<esc><LeftMouse><20,3>') screen:expect([[ - rem ipum do it | - , conetetur adipcin | - elitr. | - tet ta kad bere^n | - , no ea takimata anctu | - et. | + Section{0:>>--->--->---}t1 | + {0:>--->--->---} t2 t3 | + t4 | + ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>----}^ | + ✨ðŸˆâœ¨ | + | | ]]) + + feed('<esc><LeftMouse><1,4>') + screen:expect([[ + Section{0:>>--->--->---}t1 | + {0:>--->--->---} t2 t3 | + t4 | + ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>----} | + ^✨ðŸˆâœ¨ | + | + | + ]]) + + feed('<esc><LeftMouse><3,4>') + screen:expect([[ + Section{0:>>--->--->---}t1 | + {0:>--->--->---} t2 t3 | + t4 | + ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>----} | + ✨^ðŸˆâœ¨ | + | + | + ]]) + + feed('<esc><LeftMouse><5,4>') + screen:expect([[ + Section{0:>>--->--->---}t1 | + {0:>--->--->---} t2 t3 | + t4 | + ç§ã¯çŒ«ãŒå¤§å¥½ã{0:>----} | + ✨ðŸˆ^✨ | + | + | + ]]) + end) -- level 3 - wrapped end) end) diff --git a/test/functional/ui/options_spec.lua b/test/functional/ui/options_spec.lua new file mode 100644 index 0000000000..62b08c0967 --- /dev/null +++ b/test/functional/ui/options_spec.lua @@ -0,0 +1,110 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local command = helpers.command +local eq = helpers.eq + +describe('ui receives option updates', function() + local screen + + before_each(function() + clear() + screen = Screen.new(20,5) + end) + + after_each(function() + screen:detach() + end) + + local defaults = { + ambiwidth='single', + arabicshape=true, + emoji=true, + guifont='', + guifontset='', + guifontwide='', + linespace=0, + showtabline=1, + termguicolors=false, + ext_cmdline=false, + ext_popupmenu=false, + ext_tabline=false, + ext_wildmenu=false, + } + + it("for defaults", function() + screen:attach() + screen:expect(function() + eq(defaults, screen.options) + end) + end) + + it("when setting options", function() + screen:attach() + local changed = {} + for k,v in pairs(defaults) do + changed[k] = v + end + + command("set termguicolors") + changed.termguicolors = true + screen:expect(function() + eq(changed, screen.options) + end) + + command("set guifont=Comic\\ Sans") + changed.guifont = "Comic Sans" + screen:expect(function() + eq(changed, screen.options) + end) + + command("set showtabline=0") + changed.showtabline = 0 + screen:expect(function() + eq(changed, screen.options) + end) + + command("set linespace=13") + changed.linespace = 13 + screen:expect(function() + eq(changed, screen.options) + end) + + command("set linespace=-11") + changed.linespace = -11 + screen:expect(function() + eq(changed, screen.options) + end) + + command("set all&") + screen:expect(function() + eq(defaults, screen.options) + end) + end) + + it('with UI extensions', function() + local changed = {} + for k,v in pairs(defaults) do + changed[k] = v + end + + screen:attach({ext_cmdline=true, ext_wildmenu=true}) + changed.ext_cmdline = true + changed.ext_wildmenu = true + screen:expect(function() + eq(changed, screen.options) + end) + + screen:set_option('ext_popupmenu', true) + changed.ext_popupmenu = true + screen:expect(function() + eq(changed, screen.options) + end) + + screen:set_option('ext_wildmenu', false) + changed.ext_wildmenu = false + screen:expect(function() + eq(changed, screen.options) + end) + end) +end) diff --git a/test/functional/ui/output_spec.lua b/test/functional/ui/output_spec.lua index c6d564e8dc..02ca566bd8 100644 --- a/test/functional/ui/output_spec.lua +++ b/test/functional/ui/output_spec.lua @@ -1,14 +1,24 @@ -local session = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local helpers = require('test.functional.helpers')(after_each) local child_session = require('test.functional.terminal.helpers') - -if session.pending_win32(pending) then return end +local mkdir, write_file, rmdir = helpers.mkdir, helpers.write_file, helpers.rmdir +local eq = helpers.eq +local eval = helpers.eval +local feed = helpers.feed +local feed_command = helpers.feed_command +local iswin = helpers.iswin +local clear = helpers.clear +local command = helpers.command +local nvim_dir = helpers.nvim_dir describe("shell command :!", function() + if helpers.pending_win32(pending) then return end + local screen before_each(function() - session.clear() - screen = child_session.screen_setup(0, '["'..session.nvim_prog.. - '", "-u", "NONE", "-i", "NONE", "--cmd", "'..session.nvim_set..'"]') + clear() + screen = child_session.screen_setup(0, '["'..helpers.nvim_prog.. + '", "-u", "NONE", "-i", "NONE", "--cmd", "'..helpers.nvim_set..'"]') screen:expect([[ {1: } | {4:~ }| @@ -30,18 +40,18 @@ describe("shell command :!", function() -- to avoid triggering a UI flush. child_session.feed_data(":!printf foo; sleep 200\n") screen:expect([[ + | {4:~ }| {4:~ }| - {4:~ }| + {5: }| :!printf foo; sleep 200 | - | foo | {3:-- TERMINAL --} | ]]) end) it("throttles shell-command output greater than ~10KB", function() - if os.getenv("TRAVIS") and session.os_name() == "osx" then + if os.getenv("TRAVIS") and helpers.os_name() == "osx" then pending("[Unreliable on Travis macOS.]", function() end) return end @@ -56,13 +66,166 @@ describe("shell command :!", function() -- Final chunk of output should always be displayed, never skipped. -- (Throttling is non-deterministic, this test is merely a sanity check.) screen:expect([[ - XXXXXXXXXX 2996 | XXXXXXXXXX 2997 | XXXXXXXXXX 2998 | XXXXXXXXXX 2999 | XXXXXXXXXX 3000 | + | {10:Press ENTER or type command to continue}{1: } | {3:-- TERMINAL --} | ]]) end) end) + +describe("shell command :!", function() + before_each(function() + clear() + end) + + it("cat a binary file #4142", function() + feed(":exe 'silent !cat '.shellescape(v:progpath)<CR>") + eq(2, eval('1+1')) -- Still alive? + end) + + it([[display \x08 char #4142]], function() + feed(":silent !echo \08<CR>") + eq(2, eval('1+1')) -- Still alive? + end) + + it([[handles control codes]], function() + if iswin() then + pending('missing printf', function() end) + return + end + local screen = Screen.new(50, 4) + screen:attach() + command("set display-=msgsep") + -- Print TAB chars. #2958 + feed([[:!printf '1\t2\t3'<CR>]]) + screen:expect([[ + ~ | + :!printf '1\t2\t3' | + 1 2 3 | + Press ENTER or type command to continue^ | + ]]) + feed([[<CR>]]) + -- Print BELL control code. #4338 + screen.bell = false + feed([[:!printf '\x07\x07\x07\x07text'<CR>]]) + screen:expect([[ + ~ | + :!printf '\x07\x07\x07\x07text' | + text | + Press ENTER or type command to continue^ | + ]], nil, nil, function() + eq(true, screen.bell) + end) + feed([[<CR>]]) + -- Print BS control code. + feed([[:echo system('printf ''\x08\n''')<CR>]]) + screen:expect([[ + ~ | + ^H | + | + Press ENTER or type command to continue^ | + ]]) + feed([[<CR>]]) + -- Print LF control code. + feed([[:!printf '\n'<CR>]]) + screen:expect([[ + :!printf '\n' | + | + | + Press ENTER or type command to continue^ | + ]]) + feed([[<CR>]]) + end) + + describe('', function() + local screen + before_each(function() + rmdir('bang_filter_spec') + mkdir('bang_filter_spec') + write_file('bang_filter_spec/f1', 'f1') + write_file('bang_filter_spec/f2', 'f2') + write_file('bang_filter_spec/f3', 'f3') + screen = Screen.new(53,10) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {foreground = Screen.colors.Blue1}, + [3] = {bold = true, foreground = Screen.colors.SeaGreen4}, + [4] = {bold = true, reverse = true}, + }) + screen:attach() + end) + + after_each(function() + rmdir('bang_filter_spec') + end) + + it("doesn't truncate Last line of shell output #3269", function() + command(helpers.iswin() + and [[nnoremap <silent>\l :!dir /b bang_filter_spec<cr>]] + or [[nnoremap <silent>\l :!ls bang_filter_spec<cr>]]) + local result = (helpers.iswin() + and [[:!dir /b bang_filter_spec]] + or [[:!ls bang_filter_spec ]]) + feed([[\l]]) + screen:expect([[ + | + {1:~ }| + {1:~ }| + {4: }| + ]]..result..[[ | + f1 | + f2 | + f3 | + | + {3:Press ENTER or type command to continue}^ | + ]]) + end) + + it('handles binary and multibyte data', function() + feed_command('!cat test/functional/fixtures/shell_data.txt') + screen.bell = false + screen:expect([[ + | + {1:~ }| + {4: }| + :!cat test/functional/fixtures/shell_data.txt | + {2:^@^A^B^C^D^E^F^H} | + {2:^N^O^P^Q^R^S^T^U^V^W^X^Y^Z^[^\^]^^^_} | + ö 한글 {2:<a5><c3>} | + t {2:<ff>} | + | + {3:Press ENTER or type command to continue}^ | + ]], nil, nil, function() + eq(true, screen.bell) + end) + end) + + it('handles multibyte sequences split over buffer boundaries', function() + command('cd '..nvim_dir) + local cmd + if iswin() then + cmd = '!shell-test UTF-8 ' + else + cmd = '!./shell-test UTF-8' + end + feed_command(cmd) + -- Note: only the first example of split composed char works + screen:expect([[ + | + {4: }| + :]]..cmd..[[ | + Ã¥ | + ref: å̲ | + 1: å̲ | + 2: Ã¥ ̲ | + 3: Ã¥ ̲ | + | + {3:Press ENTER or type command to continue}^ | + ]]) + end) + end) +end) diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index a6b7fb2997..52e108f389 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -137,6 +137,7 @@ function Screen.new(width, height) visual_bell = false, suspended = false, mode = 'normal', + options = {}, _default_attr_ids = nil, _default_attr_ignore = nil, _mouse_enabled = true, @@ -176,6 +177,10 @@ function Screen:try_resize(columns, rows) self:sleep(0.1) end +function Screen:set_option(option, value) + uimeths.set_option(option, value) +end + -- Asserts that `expected` eventually matches the screen state. -- -- expected: Expected screen state (string). Each line represents a screen @@ -450,6 +455,9 @@ function Screen:_handle_visual_bell() self.visual_bell = true end +function Screen:_handle_default_colors_set() +end + function Screen:_handle_update_fg(fg) self._fg = fg end @@ -478,6 +486,10 @@ function Screen:_handle_set_icon(icon) self.icon = icon end +function Screen:_handle_option_set(name, value) + self.options[name] = value +end + function Screen:_clear_block(top, bot, left, right) for i = top, bot do self:_clear_row_section(i, left, right) diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index b31d9cb32f..8a1f9b0d19 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -4,7 +4,6 @@ local spawn, set_session, clear = helpers.spawn, helpers.set_session, helpers.cl local feed, command = helpers.feed, helpers.command local insert = helpers.insert local eq = helpers.eq -local eval = helpers.eval local iswin = helpers.iswin describe('screen', function() @@ -189,12 +188,12 @@ describe('Screen', function() command('vsp') command('vsp') screen:expect([[ - ^ {3:|} {3:|} | - {0:~ }{3:|}{0:~ }{3:|}{0:~ }| - {0:~ }{3:|}{0:~ }{3:|}{0:~ }| - {0:~ }{3:|}{0:~ }{3:|}{0:~ }| - {0:~ }{3:|}{0:~ }{3:|}{0:~ }| - {0:~ }{3:|}{0:~ }{3:|}{0:~ }| + ^ {3:│} {3:│} | + {0:~ }{3:│}{0:~ }{3:│}{0:~ }| + {0:~ }{3:│}{0:~ }{3:│}{0:~ }| + {0:~ }{3:│}{0:~ }{3:│}{0:~ }| + {0:~ }{3:│}{0:~ }{3:│}{0:~ }| + {0:~ }{3:│}{0:~ }{3:│}{0:~ }| {1:[No Name] }{3:[No Name] [No Name] }| | {0:~ }| @@ -206,12 +205,12 @@ describe('Screen', function() ]]) insert('hello') screen:expect([[ - hell^o {3:|}hello {3:|}hello | - {0:~ }{3:|}{0:~ }{3:|}{0:~ }| - {0:~ }{3:|}{0:~ }{3:|}{0:~ }| - {0:~ }{3:|}{0:~ }{3:|}{0:~ }| - {0:~ }{3:|}{0:~ }{3:|}{0:~ }| - {0:~ }{3:|}{0:~ }{3:|}{0:~ }| + hell^o {3:│}hello {3:│}hello | + {0:~ }{3:│}{0:~ }{3:│}{0:~ }| + {0:~ }{3:│}{0:~ }{3:│}{0:~ }| + {0:~ }{3:│}{0:~ }{3:│}{0:~ }| + {0:~ }{3:│}{0:~ }{3:│}{0:~ }| + {0:~ }{3:│}{0:~ }{3:│}{0:~ }| {1:[No Name] [+] }{3:[No Name] [+] [No Name] [+] }| hello | {0:~ }| @@ -232,12 +231,12 @@ describe('Screen', function() command('vsp') insert('hello') screen:expect([[ - hell^o {3:|}hello {3:|}hello | - {0:~ }{3:|}{0:~ }{3:|}{0:~ }| - {0:~ }{3:|}{0:~ }{3:|}{0:~ }| - {0:~ }{3:|}{0:~ }{3:|}{0:~ }| - {0:~ }{3:|}{0:~ }{3:|}{0:~ }| - {0:~ }{3:|}{0:~ }{3:|}{0:~ }| + hell^o {3:│}hello {3:│}hello | + {0:~ }{3:│}{0:~ }{3:│}{0:~ }| + {0:~ }{3:│}{0:~ }{3:│}{0:~ }| + {0:~ }{3:│}{0:~ }{3:│}{0:~ }| + {0:~ }{3:│}{0:~ }{3:│}{0:~ }| + {0:~ }{3:│}{0:~ }{3:│}{0:~ }| {1:[No Name] [+] }{3:[No Name] [+] [No Name] [+] }| hello | {0:~ }| @@ -269,12 +268,12 @@ describe('Screen', function() command('tabprevious') screen:expect([[ {2: }{6:4}{2:+ [No Name] }{4: + [No Name] }{3: }{4:X}| - hell^o {3:|}hello {3:|}hello | - {0:~ }{3:|}{0:~ }{3:|}{0:~ }| - {0:~ }{3:|}{0:~ }{3:|}{0:~ }| - {0:~ }{3:|}{0:~ }{3:|}{0:~ }| - {0:~ }{3:|}{0:~ }{3:|}{0:~ }| - {0:~ }{3:|}{0:~ }{3:|}{0:~ }| + hell^o {3:│}hello {3:│}hello | + {0:~ }{3:│}{0:~ }{3:│}{0:~ }| + {0:~ }{3:│}{0:~ }{3:│}{0:~ }| + {0:~ }{3:│}{0:~ }{3:│}{0:~ }| + {0:~ }{3:│}{0:~ }{3:│}{0:~ }| + {0:~ }{3:│}{0:~ }{3:│}{0:~ }| {1:[No Name] [+] }{3:[No Name] [+] [No Name] [+] }| hello | {0:~ }| @@ -355,7 +354,8 @@ describe('Screen', function() ]]) end) - it('execute command with multi-line output', function() + it('execute command with multi-line output without msgsep', function() + command("set display-=msgsep") feed(':ls<cr>') screen:expect([[ {0:~ }| @@ -375,6 +375,28 @@ describe('Screen', function() ]]) feed('<cr>') -- skip the "Press ENTER..." state or tests will hang end) + + it('execute command with multi-line output and with msgsep', function() + command("set display+=msgsep") + feed(':ls<cr>') + screen:expect([[ + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {1: }| + :ls | + 1 %a "[No Name]" line 1 | + {7:Press ENTER or type command to continue}^ | + ]]) + feed('<cr>') -- skip the "Press ENTER..." state or tests will hang + end) end) describe('scrolling and clearing', function() @@ -398,12 +420,12 @@ describe('Screen', function() command('vsp') command('vsp') screen:expect([[ - and {3:|}and {3:|}and | - clearing {3:|}clearing {3:|}clearing | - in {3:|}in {3:|}in | - split {3:|}split {3:|}split | - windows {3:|}windows {3:|}windows | - ^ {3:|} {3:|} | + and {3:│}and {3:│}and | + clearing {3:│}clearing {3:│}clearing | + in {3:│}in {3:│}in | + split {3:│}split {3:│}split | + windows {3:│}windows {3:│}windows | + ^ {3:│} {3:│} | {1:[No Name] [+] }{3:[No Name] [+] [No Name] [+] }| clearing | in | @@ -418,12 +440,12 @@ describe('Screen', function() it('only affects the current scroll region', function() feed('6k') screen:expect([[ - ^scrolling {3:|}and {3:|}and | - and {3:|}clearing {3:|}clearing | - clearing {3:|}in {3:|}in | - in {3:|}split {3:|}split | - split {3:|}windows {3:|}windows | - windows {3:|} {3:|} | + ^scrolling {3:│}and {3:│}and | + and {3:│}clearing {3:│}clearing | + clearing {3:│}in {3:│}in | + in {3:│}split {3:│}split | + split {3:│}windows {3:│}windows | + windows {3:│} {3:│} | {1:[No Name] [+] }{3:[No Name] [+] [No Name] [+] }| clearing | in | @@ -435,12 +457,12 @@ describe('Screen', function() ]]) feed('<c-w>l') screen:expect([[ - scrolling {3:|}and {3:|}and | - and {3:|}clearing {3:|}clearing | - clearing {3:|}in {3:|}in | - in {3:|}split {3:|}split | - split {3:|}windows {3:|}windows | - windows {3:|}^ {3:|} | + scrolling {3:│}and {3:│}and | + and {3:│}clearing {3:│}clearing | + clearing {3:│}in {3:│}in | + in {3:│}split {3:│}split | + split {3:│}windows {3:│}windows | + windows {3:│}^ {3:│} | {3:[No Name] [+] }{1:[No Name] [+] }{3:<Name] [+] }| clearing | in | @@ -452,12 +474,12 @@ describe('Screen', function() ]]) feed('gg') screen:expect([[ - scrolling {3:|}^Inserting {3:|}and | - and {3:|}text {3:|}clearing | - clearing {3:|}with {3:|}in | - in {3:|}many {3:|}split | - split {3:|}lines {3:|}windows | - windows {3:|}to {3:|} | + scrolling {3:│}^Inserting {3:│}and | + and {3:│}text {3:│}clearing | + clearing {3:│}with {3:│}in | + in {3:│}many {3:│}split | + split {3:│}lines {3:│}windows | + windows {3:│}to {3:│} | {3:[No Name] [+] }{1:[No Name] [+] }{3:<Name] [+] }| clearing | in | @@ -469,12 +491,12 @@ describe('Screen', function() ]]) feed('7j') screen:expect([[ - scrolling {3:|}with {3:|}and | - and {3:|}many {3:|}clearing | - clearing {3:|}lines {3:|}in | - in {3:|}to {3:|}split | - split {3:|}test {3:|}windows | - windows {3:|}^scrolling {3:|} | + scrolling {3:│}with {3:│}and | + and {3:│}many {3:│}clearing | + clearing {3:│}lines {3:│}in | + in {3:│}to {3:│}split | + split {3:│}test {3:│}windows | + windows {3:│}^scrolling {3:│} | {3:[No Name] [+] }{1:[No Name] [+] }{3:<Name] [+] }| clearing | in | @@ -486,12 +508,12 @@ describe('Screen', function() ]]) feed('2j') screen:expect([[ - scrolling {3:|}lines {3:|}and | - and {3:|}to {3:|}clearing | - clearing {3:|}test {3:|}in | - in {3:|}scrolling {3:|}split | - split {3:|}and {3:|}windows | - windows {3:|}^clearing {3:|} | + scrolling {3:│}lines {3:│}and | + and {3:│}to {3:│}clearing | + clearing {3:│}test {3:│}in | + in {3:│}scrolling {3:│}split | + split {3:│}and {3:│}windows | + windows {3:│}^clearing {3:│} | {3:[No Name] [+] }{1:[No Name] [+] }{3:<Name] [+] }| clearing | in | @@ -503,12 +525,12 @@ describe('Screen', function() ]]) feed('5k') screen:expect([[ - scrolling {3:|}^lines {3:|}and | - and {3:|}to {3:|}clearing | - clearing {3:|}test {3:|}in | - in {3:|}scrolling {3:|}split | - split {3:|}and {3:|}windows | - windows {3:|}clearing {3:|} | + scrolling {3:│}^lines {3:│}and | + and {3:│}to {3:│}clearing | + clearing {3:│}test {3:│}in | + in {3:│}scrolling {3:│}split | + split {3:│}and {3:│}windows | + windows {3:│}clearing {3:│} | {3:[No Name] [+] }{1:[No Name] [+] }{3:<Name] [+] }| clearing | in | @@ -520,12 +542,12 @@ describe('Screen', function() ]]) feed('k') screen:expect([[ - scrolling {3:|}^many {3:|}and | - and {3:|}lines {3:|}clearing | - clearing {3:|}to {3:|}in | - in {3:|}test {3:|}split | - split {3:|}scrolling {3:|}windows | - windows {3:|}and {3:|} | + scrolling {3:│}^many {3:│}and | + and {3:│}lines {3:│}clearing | + clearing {3:│}to {3:│}in | + in {3:│}test {3:│}split | + split {3:│}scrolling {3:│}windows | + windows {3:│}and {3:│} | {3:[No Name] [+] }{1:[No Name] [+] }{3:<Name] [+] }| clearing | in | @@ -573,6 +595,7 @@ describe('Screen', function() command('nnoremap <F1> :echo "TEST"<CR>') feed(':ls<CR>') screen:expect([[ + | {0:~ }| {0:~ }| {0:~ }| @@ -582,8 +605,7 @@ describe('Screen', function() {0:~ }| {0:~ }| {0:~ }| - {0:~ }| - {0:~ }| + {1: }| :ls | 1 %a "[No Name]" line 1 | {7:Press ENTER or type command to continue}^ | @@ -608,21 +630,3 @@ describe('Screen', function() end) end) end) - -describe('nvim_ui_attach()', function() - before_each(function() - clear() - end) - it('handles very large width/height #2180', function() - local screen = Screen.new(999, 999) - screen:attach() - eq(999, eval('&lines')) - eq(999, eval('&columns')) - end) - it('invalid option returns error', function() - local screen = Screen.new() - local status, rv = pcall(function() screen:attach({foo={'foo'}}) end) - eq(false, status) - eq('No such ui option', rv:match("No such .*")) - end) -end) diff --git a/test/functional/ui/searchhl_spec.lua b/test/functional/ui/searchhl_spec.lua index 11b18d015f..9f273e8dc9 100644 --- a/test/functional/ui/searchhl_spec.lua +++ b/test/functional/ui/searchhl_spec.lua @@ -2,8 +2,8 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert local feed_command = helpers.feed_command - -if helpers.pending_win32(pending) then return end +local eq = helpers.eq +local eval = helpers.eval describe('search highlighting', function() local screen @@ -101,7 +101,30 @@ describe('search highlighting', function() feed("gg/li") screen:expect([[ the first {3:li}ne | - in a little file | + in a {2:li}ttle file | + | + {1:~ }| + {1:~ }| + {1:~ }| + /li^ | + ]]) + + -- check that consecutive matches are caught by C-g/C-t + feed("<C-g>") + screen:expect([[ + the first {2:li}ne | + in a {3:li}ttle file | + | + {1:~ }| + {1:~ }| + {1:~ }| + /li^ | + ]]) + + feed("<C-t>") + screen:expect([[ + the first {3:li}ne | + in a {2:li}ttle file | | {1:~ }| {1:~ }| @@ -134,7 +157,7 @@ describe('search highlighting', function() feed("/fir") screen:expect([[ the {3:fir}st line | - in a {2:lit}tle file | + in a little file | | {1:~ }| {1:~ }| @@ -146,13 +169,107 @@ describe('search highlighting', function() feed("<esc>/ttle") screen:expect([[ the first line | - in a {2:li}{3:ttle} file | + in a li{3:ttle} file | | {1:~ }| {1:~ }| {1:~ }| /ttle^ | ]]) + + -- cancelling search resets to the old search term + feed('<esc>') + screen:expect([[ + the first line | + in a {2:^lit}tle file | + | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + eq('lit', eval('@/')) + + -- cancelling inc search restores the hl state + feed(':noh<cr>') + screen:expect([[ + the first line | + in a ^little file | + | + {1:~ }| + {1:~ }| + {1:~ }| + :noh | + ]]) + + feed('/first') + screen:expect([[ + the {3:first} line | + in a little file | + | + {1:~ }| + {1:~ }| + {1:~ }| + /first^ | + ]]) + feed('<esc>') + screen:expect([[ + the first line | + in a ^little file | + | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + + -- test that pressing C-g in an empty command line does not move the cursor + feed('/<C-g>') + screen:expect([[ + the first line | + in a little file | + | + {1:~ }| + {1:~ }| + {1:~ }| + /^ | + ]]) + + -- same, for C-t + feed('<ESC>/<C-t>') + screen:expect([[ + the first line | + in a little file | + | + {1:~ }| + {1:~ }| + {1:~ }| + /^ | + ]]) + + -- 8.0.1304, test that C-g and C-t works with incsearch and empty pattern + feed('<esc>/fi<CR>') + feed('//') + screen:expect([[ + the {3:fi}rst line | + in a little {2:fi}le | + | + {1:~ }| + {1:~ }| + {1:~ }| + //^ | + ]]) + + feed('<C-g>') + screen:expect([[ + the {2:fi}rst line | + in a little {3:fi}le | + | + {1:~ }| + {1:~ }| + {1:~ }| + //^ | + ]]) end) it('works with incsearch and offset', function() @@ -165,7 +282,7 @@ describe('search highlighting', function() feed("gg/mat/e") screen:expect([[ not the {3:mat}ch you're looking for | - the match is here | + the {2:mat}ch is here | {1:~ }| {1:~ }| {1:~ }| @@ -176,7 +293,7 @@ describe('search highlighting', function() -- Search with count and /e offset fixed in Vim patch 7.4.532. feed("<esc>2/mat/e") screen:expect([[ - not the match you're looking for | + not the {2:mat}ch you're looking for | the {3:mat}ch is here | {1:~ }| {1:~ }| diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua index e5c96f2ec0..c00d99cf90 100644 --- a/test/functional/ui/sign_spec.lua +++ b/test/functional/ui/sign_spec.lua @@ -2,8 +2,6 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local clear, feed, command = helpers.clear, helpers.feed, helpers.command -if helpers.pending_win32(pending) then return end - describe('Signs', function() local screen diff --git a/test/functional/ui/spell_spec.lua b/test/functional/ui/spell_spec.lua new file mode 100644 index 0000000000..913f1b9bed --- /dev/null +++ b/test/functional/ui/spell_spec.lua @@ -0,0 +1,49 @@ +-- Test for scenarios involving 'spell' + +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 + +describe("'spell'", function() + local screen + + before_each(function() + clear() + screen = Screen.new(80, 8) + screen:attach() + screen:set_default_attr_ids( { + [0] = {bold=true, foreground=Screen.colors.Blue}, + [1] = {special = Screen.colors.Red, undercurl = true} + }) + end) + + after_each(function() + screen:detach() + end) + + it('joins long lines #7937', function() + feed_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, + quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse + cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat + non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + ]]) + feed('ggJJJJJJ0') + screen:expect([[ + {1:^Lorem} {1:ipsum} dolor sit {1:amet}, {1:consectetur} {1:adipiscing} {1:elit}, {1:sed} do {1:eiusmod} {1:tempor} {1:i}| + {1:ncididunt} {1:ut} {1:labore} {1:et} {1:dolore} {1:magna} {1:aliqua}. {1:Ut} {1:enim} ad minim {1:veniam}, {1:quis} {1:nostru}| + {1:d} {1:exercitation} {1:ullamco} {1:laboris} {1:nisi} {1:ut} {1:aliquip} ex ea {1:commodo} {1:consequat}. {1:Duis} {1:aut}| + {1:e} {1:irure} dolor in {1:reprehenderit} in {1:voluptate} {1:velit} {1:esse} {1:cillum} {1:dolore} {1:eu} {1:fugiat} {1:n}| + {1:ulla} {1:pariatur}. {1:Excepteur} {1:sint} {1:occaecat} {1:cupidatat} non {1:proident}, {1:sunt} in culpa {1:qui}| + {1:officia} {1:deserunt} {1:mollit} {1:anim} id est {1:laborum}. | + {0:~ }| + | + ]]) + end) +end) diff --git a/test/functional/ui/syntax_conceal_spec.lua b/test/functional/ui/syntax_conceal_spec.lua index 28a104360d..e7a7004c1e 100644 --- a/test/functional/ui/syntax_conceal_spec.lua +++ b/test/functional/ui/syntax_conceal_spec.lua @@ -3,8 +3,6 @@ local Screen = require('test.functional.ui.screen') local clear, feed, command = helpers.clear, helpers.feed, helpers.command local insert = helpers.insert -if helpers.pending_win32(pending) then return end - describe('Screen', function() local screen diff --git a/test/functional/ui/wildmode_spec.lua b/test/functional/ui/wildmode_spec.lua index 042969357e..c6ddc78618 100644 --- a/test/functional/ui/wildmode_spec.lua +++ b/test/functional/ui/wildmode_spec.lua @@ -60,8 +60,7 @@ describe("'wildmenu'", function() command('set wildmenu wildmode=full') command('set scrollback=4') if iswin() then - if helpers.pending_win32(pending) then return end - -- feed([[:terminal 1,2,3,4,5 | foreach-object -process {echo $_; sleep 0.1}]]) + feed([[:terminal for /L \%I in (1,1,5000) do @(echo foo & echo foo & echo foo)<cr>]]) else feed([[:terminal for i in $(seq 1 5000); do printf 'foo\nfoo\nfoo\n'; sleep 0.1; done<cr>]]) end diff --git a/test/functional/viml/completion_spec.lua b/test/functional/viml/completion_spec.lua index b70ef724b7..216ccb3744 100644 --- a/test/functional/viml/completion_spec.lua +++ b/test/functional/viml/completion_spec.lua @@ -5,8 +5,6 @@ local eval, eq, neq = helpers.eval, helpers.eq, helpers.neq local feed_command, source, expect = helpers.feed_command, helpers.source, helpers.expect local meths = helpers.meths -if helpers.pending_win32(pending) then return end - describe('completion', function() local screen @@ -61,7 +59,8 @@ describe('completion', function() it('returns expected dict in normal completion', function() feed('ifoo<ESC>o<C-x><C-n>') eq('foo', eval('getline(2)')) - eq({word = 'foo', abbr = '', menu = '', info = '', kind = ''}, + eq({word = 'foo', abbr = '', menu = '', + info = '', kind = '', user_data = ''}, eval('v:completed_item')) end) it('is readonly', function() @@ -86,13 +85,18 @@ describe('completion', function() feed_command('let v:completed_item.kind = "bar"') neq(nil, string.find(eval('v:errmsg'), '^E46: ')) feed_command('let v:errmsg = ""') + + feed_command('let v:completed_item.user_data = "bar"') + neq(nil, string.find(eval('v:errmsg'), '^E46: ')) + feed_command('let v:errmsg = ""') end) it('returns expected dict in omni completion', function() source([[ function! TestOmni(findstart, base) abort return a:findstart ? 0 : [{'word': 'foo', 'abbr': 'bar', \ 'menu': 'baz', 'info': 'foobar', 'kind': 'foobaz'}, - \ {'word': 'word', 'abbr': 'abbr', 'menu': 'menu', 'info': 'info', 'kind': 'kind'}] + \ {'word': 'word', 'abbr': 'abbr', 'menu': 'menu', + \ 'info': 'info', 'kind': 'kind'}] endfunction setlocal omnifunc=TestOmni ]]) @@ -109,7 +113,7 @@ describe('completion', function() {3:-- Omni completion (^O^N^P) }{4:match 1 of 2} | ]]) eq({word = 'foo', abbr = 'bar', menu = 'baz', - info = 'foobar', kind = 'foobaz'}, + info = 'foobar', kind = 'foobaz', user_data = ''}, eval('v:completed_item')) end) end) diff --git a/test/helpers.lua b/test/helpers.lua index aef6302da3..efc0e911f1 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -39,15 +39,28 @@ local check_logs_useless_lines = { ['See README_MISSING_SYSCALL_OR_IOCTL for guidance']=3, } -local eq = function(exp, act) - return assert.are.same(exp, act) +local function eq(expected, actual) + return assert.are.same(expected, actual) end -local neq = function(exp, act) - return assert.are_not.same(exp, act) +local function neq(expected, actual) + return assert.are_not.same(expected, actual) end -local ok = function(res) +local function ok(res) return assert.is_true(res) end +local function matches(pat, actual) + if nil ~= string.match(actual, pat) then + return true + end + error(string.format('Pattern does not match.\nPattern:\n%s\nActual:\n%s', pat, actual)) +end +-- Expect an error matching pattern `pat`. +local function expect_err(pat, ...) + local fn = select(1, ...) + local fn_args = {...} + table.remove(fn_args, 1) + assert.error_matches(function() return fn(unpack(fn_args)) end, pat) +end -- initial_path: directory to recurse into -- re: include pattern (string) @@ -296,6 +309,9 @@ local function repeated_read_cmd(...) end local function shallowcopy(orig) + if type(orig) ~= 'table' then + return orig + end local copy = {} for orig_key, orig_value in pairs(orig) do copy[orig_key] = orig_value @@ -303,6 +319,92 @@ local function shallowcopy(orig) return copy end +local deepcopy + +local function id(v) + return v +end + +local deepcopy_funcs = { + table = function(orig) + local copy = {} + for k, v in pairs(orig) do + copy[deepcopy(k)] = deepcopy(v) + end + return copy + end, + number = id, + string = id, + ['nil'] = id, + boolean = id, +} + +deepcopy = function(orig) + return deepcopy_funcs[type(orig)](orig) +end + +local REMOVE_THIS = {} + +local function mergedicts_copy(d1, d2) + local ret = shallowcopy(d1) + for k, v in pairs(d2) do + if d2[k] == REMOVE_THIS then + ret[k] = nil + elseif type(d1[k]) == 'table' and type(v) == 'table' then + ret[k] = mergedicts_copy(d1[k], v) + else + ret[k] = v + end + end + return ret +end + +-- dictdiff: find a diff so that mergedicts_copy(d1, diff) is equal to d2 +-- +-- Note: does not do copies of d2 values used. +local function dictdiff(d1, d2) + local ret = {} + local hasdiff = false + for k, v in pairs(d1) do + if d2[k] == nil then + hasdiff = true + ret[k] = REMOVE_THIS + elseif type(v) == type(d2[k]) then + if type(v) == 'table' then + local subdiff = dictdiff(v, d2[k]) + if subdiff ~= nil then + hasdiff = true + ret[k] = subdiff + end + elseif v ~= d2[k] then + ret[k] = d2[k] + hasdiff = true + end + else + ret[k] = d2[k] + hasdiff = true + end + end + for k, v in pairs(d2) do + if d1[k] == nil then + ret[k] = shallowcopy(v) + hasdiff = true + end + end + if hasdiff then + return ret + else + return nil + end +end + +local function updated(d, d2) + for k, v in pairs(d2) do + d[k] = v + end + return d +end + local function concat_tables(...) local ret = {} for i = 1, select('#', ...) do @@ -339,24 +441,208 @@ local function dedent(str, leave_indent) return str end +local function format_float(v) + -- On windows exponent appears to have three digits and not two + local ret = ('%.6e'):format(v) + local l, f, es, e = ret:match('^(%-?%d)%.(%d+)e([+%-])0*(%d%d+)$') + return l .. '.' .. f .. 'e' .. es .. e +end + +local SUBTBL = { + '\\000', '\\001', '\\002', '\\003', '\\004', + '\\005', '\\006', '\\007', '\\008', '\\t', + '\\n', '\\011', '\\012', '\\r', '\\014', + '\\015', '\\016', '\\017', '\\018', '\\019', + '\\020', '\\021', '\\022', '\\023', '\\024', + '\\025', '\\026', '\\027', '\\028', '\\029', + '\\030', '\\031', +} + +local format_luav + +format_luav = function(v, indent, opts) + opts = opts or {} + local linesep = '\n' + local next_indent_arg = nil + local indent_shift = opts.indent_shift or ' ' + local next_indent + local nl = '\n' + if indent == nil then + indent = '' + linesep = '' + next_indent = '' + nl = ' ' + else + next_indent_arg = indent .. indent_shift + next_indent = indent .. indent_shift + end + local ret = '' + if type(v) == 'string' then + if opts.literal_strings then + ret = v + else + local quote = opts.dquote_strings and '"' or '\'' + ret = quote .. tostring(v):gsub( + opts.dquote_strings and '["\\]' or '[\'\\]', + '\\%0'):gsub( + '[%z\1-\31]', function(match) + return SUBTBL[match:byte() + 1] + end) .. quote + end + elseif type(v) == 'table' then + if v == REMOVE_THIS then + ret = 'REMOVE_THIS' + else + local processed_keys = {} + ret = '{' .. linesep + local non_empty = false + for i, subv in ipairs(v) do + ret = ('%s%s%s,%s'):format(ret, next_indent, + format_luav(subv, next_indent_arg, opts), nl) + processed_keys[i] = true + non_empty = true + end + for k, subv in pairs(v) do + if not processed_keys[k] then + if type(k) == 'string' and k:match('^[a-zA-Z_][a-zA-Z0-9_]*$') then + ret = ret .. next_indent .. k .. ' = ' + else + ret = ('%s%s[%s] = '):format(ret, next_indent, + format_luav(k, nil, opts)) + end + ret = ret .. format_luav(subv, next_indent_arg, opts) .. ',' .. nl + non_empty = true + end + end + if nl == ' ' and non_empty then + ret = ret:sub(1, -3) + end + ret = ret .. indent .. '}' + end + elseif type(v) == 'number' then + if v % 1 == 0 then + ret = ('%d'):format(v) + else + ret = format_float(v) + end + elseif type(v) == 'nil' then + ret = 'nil' + elseif type(v) == 'boolean' then + ret = (v and 'true' or 'false') + else + print(type(v)) + -- Not implemented yet + assert(false) + end + return ret +end + +local function format_string(fmt, ...) + local i = 0 + local args = {...} + local function getarg() + i = i + 1 + return args[i] + end + local ret = fmt:gsub('%%[0-9*]*%.?[0-9*]*[cdEefgGiouXxqsr%%]', function(match) + local subfmt = match:gsub('%*', function() + return tostring(getarg()) + end) + local arg = nil + if subfmt:sub(-1) ~= '%' then + arg = getarg() + end + if subfmt:sub(-1) == 'r' or subfmt:sub(-1) == 'q' then + -- %r is like built-in %q, but it is supposed to single-quote strings and + -- not double-quote them, and also work not only for strings. + -- Builtin %q is replaced here as it gives invalid and inconsistent with + -- luajit results for e.g. "\e" on lua: luajit transforms that into `\27`, + -- lua leaves as-is. + arg = format_luav(arg, nil, {dquote_strings = (subfmt:sub(-1) == 'q')}) + subfmt = subfmt:sub(1, -2) .. 's' + end + if subfmt == '%e' then + return format_float(arg) + else + return subfmt:format(arg) + end + end) + return ret +end + +local function intchar2lua(ch) + ch = tonumber(ch) + return (20 <= ch and ch < 127) and ('%c'):format(ch) or ch +end + +local fixtbl_metatable = { + __newindex = function() + assert(false) + end, +} + +local function fixtbl(tbl) + return setmetatable(tbl, fixtbl_metatable) +end + +local function fixtbl_rec(tbl) + for _, v in pairs(tbl) do + if type(v) == 'table' then + fixtbl_rec(v) + end + end + return fixtbl(tbl) +end + +-- From https://github.com/premake/premake-core/blob/master/src/base/table.lua +local function table_flatten(arr) + local result = {} + local function _table_flatten(_arr) + local n = #_arr + for i = 1, n do + local v = _arr[i] + if type(v) == "table" then + _table_flatten(v) + elseif v then + table.insert(result, v) + end + end + end + _table_flatten(arr) + return result +end + return { - eq = eq, - neq = neq, - ok = ok, + REMOVE_THIS = REMOVE_THIS, + argss_to_cmd = argss_to_cmd, + check_cores = check_cores, check_logs = check_logs, - uname = uname, - tmpname = tmpname, - map = map, + concat_tables = concat_tables, + dedent = dedent, + deepcopy = deepcopy, + dictdiff = dictdiff, + eq = eq, + expect_err = expect_err, filter = filter, + fixtbl = fixtbl, + fixtbl_rec = fixtbl_rec, + format_luav = format_luav, + format_string = format_string, glob = glob, - check_cores = check_cores, hasenv = hasenv, - which = which, - argss_to_cmd = argss_to_cmd, + intchar2lua = intchar2lua, + map = map, + matches = matches, + mergedicts_copy = mergedicts_copy, + neq = neq, + ok = ok, popen_r = popen_r, popen_w = popen_w, repeated_read_cmd = repeated_read_cmd, shallowcopy = shallowcopy, - concat_tables = concat_tables, - dedent = dedent, + table_flatten = table_flatten, + tmpname = tmpname, + uname = uname, + updated = updated, + which = which, } diff --git a/test/symbolic/klee/nvim/charset.c b/test/symbolic/klee/nvim/charset.c new file mode 100644 index 0000000000..95853a6834 --- /dev/null +++ b/test/symbolic/klee/nvim/charset.c @@ -0,0 +1,172 @@ +#include <stdbool.h> + +#include "nvim/ascii.h" +#include "nvim/macros.h" +#include "nvim/charset.h" +#include "nvim/eval/typval.h" +#include "nvim/vim.h" + +int hex2nr(int c) +{ + if ((c >= 'a') && (c <= 'f')) { + return c - 'a' + 10; + } + + if ((c >= 'A') && (c <= 'F')) { + return c - 'A' + 10; + } + return c - '0'; +} + +void vim_str2nr(const char_u *const start, int *const prep, int *const len, + const int what, varnumber_T *const nptr, + uvarnumber_T *const unptr, const int maxlen) +{ + const char *ptr = (const char *)start; +#define STRING_ENDED(ptr) \ + (!(maxlen == 0 || (int)((ptr) - (const char *)start) < maxlen)) + int pre = 0; // default is decimal + const bool negative = (ptr[0] == '-'); + uvarnumber_T un = 0; + + if (negative) { + ptr++; + } + + if (what & STR2NR_FORCE) { + // When forcing main consideration is skipping the prefix. Octal and decimal + // numbers have no prefixes to skip. pre is not set. + switch ((unsigned)what & (~(unsigned)STR2NR_FORCE)) { + case STR2NR_HEX: { + if (!STRING_ENDED(ptr + 2) + && ptr[0] == '0' + && (ptr[1] == 'x' || ptr[1] == 'X') + && ascii_isxdigit(ptr[2])) { + ptr += 2; + } + goto vim_str2nr_hex; + } + case STR2NR_BIN: { + if (!STRING_ENDED(ptr + 2) + && ptr[0] == '0' + && (ptr[1] == 'b' || ptr[1] == 'B') + && ascii_isbdigit(ptr[2])) { + ptr += 2; + } + goto vim_str2nr_bin; + } + case STR2NR_OCT: { + goto vim_str2nr_oct; + } + case 0: { + goto vim_str2nr_dec; + } + default: { + assert(false); + } + } + } else if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN)) + && !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 + if ((what & STR2NR_HEX) + && !STRING_ENDED(ptr + 2) + && (pre == 'X' || pre == 'x') + && ascii_isxdigit(ptr[2])) { + ptr += 2; + goto vim_str2nr_hex; + } + // Detect binary: 0b or 0B follwed by 0 or 1 + if ((what & STR2NR_BIN) + && !STRING_ENDED(ptr + 2) + && (pre == 'B' || pre == 'b') + && ascii_isbdigit(ptr[2])) { + ptr += 2; + goto vim_str2nr_bin; + } + // Detect octal number: zero followed by octal digits without '8' or '9' + pre = 0; + if (!(what & STR2NR_OCT)) { + goto vim_str2nr_dec; + } + for (int i = 2; !STRING_ENDED(ptr + i) && ascii_isdigit(ptr[i]); i++) { + if (ptr[i] > '7') { + goto vim_str2nr_dec; + } + } + pre = '0'; + goto vim_str2nr_oct; + } else { + goto vim_str2nr_dec; + } + + // Do the string-to-numeric conversion "manually" to avoid sscanf quirks. + assert(false); // Should’ve used goto earlier. +#define PARSE_NUMBER(base, cond, conv) \ + do { \ + while (!STRING_ENDED(ptr) && (cond)) { \ + /* avoid ubsan error for overflow */ \ + if (un < UVARNUMBER_MAX / base) { \ + un = base * un + (uvarnumber_T)(conv); \ + } else { \ + un = UVARNUMBER_MAX; \ + } \ + ptr++; \ + } \ + } while (0) + switch (pre) { + case 'b': + case 'B': { +vim_str2nr_bin: + PARSE_NUMBER(2, (*ptr == '0' || *ptr == '1'), (*ptr - '0')); + break; + } + case '0': { +vim_str2nr_oct: + PARSE_NUMBER(8, ('0' <= *ptr && *ptr <= '7'), (*ptr - '0')); + break; + } + case 0: { +vim_str2nr_dec: + PARSE_NUMBER(10, (ascii_isdigit(*ptr)), (*ptr - '0')); + break; + } + case 'x': + case 'X': { +vim_str2nr_hex: + PARSE_NUMBER(16, (ascii_isxdigit(*ptr)), (hex2nr(*ptr))); + break; + } + } +#undef PARSE_NUMBER + + if (prep != NULL) { + *prep = pre; + } + + if (len != NULL) { + *len = (int)(ptr - (const char *)start); + } + + if (nptr != NULL) { + if (negative) { // account for leading '-' for decimal numbers + // avoid ubsan error for overflow + if (un > VARNUMBER_MAX) { + *nptr = VARNUMBER_MIN; + } else { + *nptr = -(varnumber_T)un; + } + } else { + if (un > VARNUMBER_MAX) { + un = VARNUMBER_MAX; + } + *nptr = (varnumber_T)un; + } + } + + if (unptr != NULL) { + *unptr = un; + } +#undef STRING_ENDED +} diff --git a/test/symbolic/klee/nvim/garray.c b/test/symbolic/klee/nvim/garray.c new file mode 100644 index 0000000000..260870c3c2 --- /dev/null +++ b/test/symbolic/klee/nvim/garray.c @@ -0,0 +1,195 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +/// @file garray.c +/// +/// Functions for handling growing arrays. + +#include <string.h> +#include <inttypes.h> + +#include "nvim/vim.h" +#include "nvim/ascii.h" +#include "nvim/log.h" +#include "nvim/memory.h" +#include "nvim/path.h" +#include "nvim/garray.h" +#include "nvim/strings.h" + +// #include "nvim/globals.h" +#include "nvim/memline.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "garray.c.generated.h" +#endif + +/// Clear an allocated growing array. +void ga_clear(garray_T *gap) +{ + xfree(gap->ga_data); + + // Initialize growing array without resetting itemsize or growsize + gap->ga_data = NULL; + gap->ga_maxlen = 0; + gap->ga_len = 0; +} + +/// Clear a growing array that contains a list of strings. +/// +/// @param gap +void ga_clear_strings(garray_T *gap) +{ + GA_DEEP_CLEAR_PTR(gap); +} + +/// Initialize a growing array. +/// +/// @param gap +/// @param itemsize +/// @param growsize +void ga_init(garray_T *gap, int itemsize, int growsize) +{ + gap->ga_data = NULL; + gap->ga_maxlen = 0; + gap->ga_len = 0; + gap->ga_itemsize = itemsize; + ga_set_growsize(gap, growsize); +} + +/// A setter for the growsize that guarantees it will be at least 1. +/// +/// @param gap +/// @param growsize +void ga_set_growsize(garray_T *gap, int growsize) +{ + if (growsize < 1) { + WLOG("trying to set an invalid ga_growsize: %d", growsize); + gap->ga_growsize = 1; + } else { + gap->ga_growsize = growsize; + } +} + +/// Make room in growing array "gap" for at least "n" items. +/// +/// @param gap +/// @param n +void ga_grow(garray_T *gap, int n) +{ + if (gap->ga_maxlen - gap->ga_len >= n) { + // the garray still has enough space, do nothing + return; + } + + if (gap->ga_growsize < 1) { + WLOG("ga_growsize(%d) is less than 1", gap->ga_growsize); + } + + // the garray grows by at least growsize + if (n < gap->ga_growsize) { + n = gap->ga_growsize; + } + int new_maxlen = gap->ga_len + n; + + size_t new_size = (size_t)(gap->ga_itemsize * new_maxlen); + size_t old_size = (size_t)(gap->ga_itemsize * gap->ga_maxlen); + + // reallocate and clear the new memory + char *pp = xrealloc(gap->ga_data, new_size); + memset(pp + old_size, 0, new_size - old_size); + + gap->ga_maxlen = new_maxlen; + gap->ga_data = pp; +} + +/// For a growing array that contains a list of strings: concatenate all the +/// strings with sep as separator. +/// +/// @param gap +/// @param sep +/// +/// @returns the concatenated strings +char_u *ga_concat_strings_sep(const garray_T *gap, const char *sep) + FUNC_ATTR_NONNULL_RET +{ + const size_t nelem = (size_t) gap->ga_len; + const char **strings = gap->ga_data; + + if (nelem == 0) { + return (char_u *) xstrdup(""); + } + + size_t len = 0; + for (size_t i = 0; i < nelem; i++) { + len += strlen(strings[i]); + } + + // add some space for the (num - 1) separators + len += (nelem - 1) * strlen(sep); + char *const ret = xmallocz(len); + + char *s = ret; + for (size_t i = 0; i < nelem - 1; i++) { + s = xstpcpy(s, strings[i]); + s = xstpcpy(s, sep); + } + strcpy(s, strings[nelem - 1]); + + return (char_u *) ret; +} + +/// For a growing array that contains a list of strings: concatenate all the +/// strings with a separating comma. +/// +/// @param gap +/// +/// @returns the concatenated strings +char_u* ga_concat_strings(const garray_T *gap) FUNC_ATTR_NONNULL_RET +{ + return ga_concat_strings_sep(gap, ","); +} + +/// Concatenate a string to a growarray which contains characters. +/// When "s" is NULL does not do anything. +/// +/// WARNING: +/// - Does NOT copy the NUL at the end! +/// - The parameter may not overlap with the growing array +/// +/// @param gap +/// @param s +void ga_concat(garray_T *gap, const char_u *restrict s) +{ + if (s == NULL) { + return; + } + + ga_concat_len(gap, (const char *restrict) s, strlen((char *) s)); +} + +/// Concatenate a string to a growarray which contains characters +/// +/// @param[out] gap Growarray to modify. +/// @param[in] s String to concatenate. +/// @param[in] len String length. +void ga_concat_len(garray_T *const gap, const char *restrict s, + const size_t len) + FUNC_ATTR_NONNULL_ALL +{ + if (len) { + ga_grow(gap, (int) len); + char *data = gap->ga_data; + memcpy(data + gap->ga_len, s, len); + gap->ga_len += (int) len; + } +} + +/// Append one byte to a growarray which contains bytes. +/// +/// @param gap +/// @param c +void ga_append(garray_T *gap, char c) +{ + GA_APPEND(char, gap, c); +} + diff --git a/test/symbolic/klee/nvim/gettext.c b/test/symbolic/klee/nvim/gettext.c new file mode 100644 index 0000000000..b9cc98d770 --- /dev/null +++ b/test/symbolic/klee/nvim/gettext.c @@ -0,0 +1,4 @@ +char *gettext(const char *s) +{ + return (char *)s; +} diff --git a/test/symbolic/klee/nvim/keymap.c b/test/symbolic/klee/nvim/keymap.c new file mode 100644 index 0000000000..a341a73689 --- /dev/null +++ b/test/symbolic/klee/nvim/keymap.c @@ -0,0 +1,539 @@ +#include <stdbool.h> + +#include "nvim/types.h" +#include "nvim/keymap.h" +#include "nvim/ascii.h" +#include "nvim/eval/typval.h" + +#define MOD_KEYS_ENTRY_SIZE 5 + +static char_u modifier_keys_table[] = +{ + MOD_MASK_SHIFT, '&', '9', '@', '1', + MOD_MASK_SHIFT, '&', '0', '@', '2', + MOD_MASK_SHIFT, '*', '1', '@', '4', + MOD_MASK_SHIFT, '*', '2', '@', '5', + MOD_MASK_SHIFT, '*', '3', '@', '6', + MOD_MASK_SHIFT, '*', '4', 'k', 'D', + MOD_MASK_SHIFT, '*', '5', 'k', 'L', + MOD_MASK_SHIFT, '*', '7', '@', '7', + MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_END, '@', '7', + MOD_MASK_SHIFT, '*', '9', '@', '9', + MOD_MASK_SHIFT, '*', '0', '@', '0', + MOD_MASK_SHIFT, '#', '1', '%', '1', + MOD_MASK_SHIFT, '#', '2', 'k', 'h', + MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_HOME, 'k', 'h', + MOD_MASK_SHIFT, '#', '3', 'k', 'I', + MOD_MASK_SHIFT, '#', '4', 'k', 'l', + MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_LEFT, 'k', 'l', + MOD_MASK_SHIFT, '%', 'a', '%', '3', + MOD_MASK_SHIFT, '%', 'b', '%', '4', + MOD_MASK_SHIFT, '%', 'c', '%', '5', + MOD_MASK_SHIFT, '%', 'd', '%', '7', + MOD_MASK_SHIFT, '%', 'e', '%', '8', + MOD_MASK_SHIFT, '%', 'f', '%', '9', + MOD_MASK_SHIFT, '%', 'g', '%', '0', + MOD_MASK_SHIFT, '%', 'h', '&', '3', + MOD_MASK_SHIFT, '%', 'i', 'k', 'r', + MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_RIGHT, 'k', 'r', + MOD_MASK_SHIFT, '%', 'j', '&', '5', + MOD_MASK_SHIFT, '!', '1', '&', '6', + MOD_MASK_SHIFT, '!', '2', '&', '7', + MOD_MASK_SHIFT, '!', '3', '&', '8', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_UP, 'k', 'u', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_DOWN, 'k', 'd', + + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF1, KS_EXTRA, (int)KE_XF1, + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF2, KS_EXTRA, (int)KE_XF2, + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF3, KS_EXTRA, (int)KE_XF3, + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF4, KS_EXTRA, (int)KE_XF4, + + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F1, 'k', '1', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F2, 'k', '2', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F3, 'k', '3', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F4, 'k', '4', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F5, 'k', '5', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F6, 'k', '6', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F7, 'k', '7', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F8, 'k', '8', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F9, 'k', '9', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F10, 'k', ';', + + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F11, 'F', '1', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F12, 'F', '2', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F13, 'F', '3', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F14, 'F', '4', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F15, 'F', '5', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F16, 'F', '6', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F17, 'F', '7', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F18, 'F', '8', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F19, 'F', '9', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F20, 'F', 'A', + + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F21, 'F', 'B', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F22, 'F', 'C', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F23, 'F', 'D', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F24, 'F', 'E', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F25, 'F', 'F', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F26, 'F', 'G', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F27, 'F', 'H', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F28, 'F', 'I', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F29, 'F', 'J', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F30, 'F', 'K', + + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F31, 'F', 'L', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F32, 'F', 'M', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F33, 'F', 'N', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F34, 'F', 'O', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F35, 'F', 'P', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F36, 'F', 'Q', + MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F37, 'F', 'R', + + MOD_MASK_SHIFT, 'k', 'B', KS_EXTRA, (int)KE_TAB, + + NUL +}; + +int simplify_key(const int key, int *modifiers) +{ + if (*modifiers & (MOD_MASK_SHIFT | MOD_MASK_CTRL | MOD_MASK_ALT)) { + // TAB is a special case. + if (key == TAB && (*modifiers & MOD_MASK_SHIFT)) { + *modifiers &= ~MOD_MASK_SHIFT; + return K_S_TAB; + } + const int key0 = KEY2TERMCAP0(key); + const int key1 = KEY2TERMCAP1(key); + for (int i = 0; modifier_keys_table[i] != NUL; i += MOD_KEYS_ENTRY_SIZE) { + if (key0 == modifier_keys_table[i + 3] + && key1 == modifier_keys_table[i + 4] + && (*modifiers & modifier_keys_table[i])) { + *modifiers &= ~modifier_keys_table[i]; + return TERMCAP2KEY(modifier_keys_table[i + 1], + modifier_keys_table[i + 2]); + } + } + } + return key; +} + +int handle_x_keys(const int key) + FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT +{ + switch (key) { + case K_XUP: return K_UP; + case K_XDOWN: return K_DOWN; + case K_XLEFT: return K_LEFT; + case K_XRIGHT: return K_RIGHT; + case K_XHOME: return K_HOME; + case K_ZHOME: return K_HOME; + case K_XEND: return K_END; + case K_ZEND: return K_END; + case K_XF1: return K_F1; + case K_XF2: return K_F2; + case K_XF3: return K_F3; + case K_XF4: return K_F4; + case K_S_XF1: return K_S_F1; + case K_S_XF2: return K_S_F2; + case K_S_XF3: return K_S_F3; + case K_S_XF4: return K_S_F4; + } + return key; +} + +static const struct key_name_entry { + int key; // Special key code or ascii value + const char *name; // Name of key +} key_names_table[] = { + { ' ', "Space" }, + { TAB, "Tab" }, + { K_TAB, "Tab" }, + { NL, "NL" }, + { NL, "NewLine" }, // Alternative name + { NL, "LineFeed" }, // Alternative name + { NL, "LF" }, // Alternative name + { CAR, "CR" }, + { CAR, "Return" }, // Alternative name + { CAR, "Enter" }, // Alternative name + { K_BS, "BS" }, + { K_BS, "BackSpace" }, // Alternative name + { ESC, "Esc" }, + { CSI, "CSI" }, + { K_CSI, "xCSI" }, + { '|', "Bar" }, + { '\\', "Bslash" }, + { K_DEL, "Del" }, + { K_DEL, "Delete" }, // Alternative name + { K_KDEL, "kDel" }, + { K_UP, "Up" }, + { K_DOWN, "Down" }, + { K_LEFT, "Left" }, + { K_RIGHT, "Right" }, + { K_XUP, "xUp" }, + { K_XDOWN, "xDown" }, + { K_XLEFT, "xLeft" }, + { K_XRIGHT, "xRight" }, + + { K_F1, "F1" }, + { K_F2, "F2" }, + { K_F3, "F3" }, + { K_F4, "F4" }, + { K_F5, "F5" }, + { K_F6, "F6" }, + { K_F7, "F7" }, + { K_F8, "F8" }, + { K_F9, "F9" }, + { K_F10, "F10" }, + + { K_F11, "F11" }, + { K_F12, "F12" }, + { K_F13, "F13" }, + { K_F14, "F14" }, + { K_F15, "F15" }, + { K_F16, "F16" }, + { K_F17, "F17" }, + { K_F18, "F18" }, + { K_F19, "F19" }, + { K_F20, "F20" }, + + { K_F21, "F21" }, + { K_F22, "F22" }, + { K_F23, "F23" }, + { K_F24, "F24" }, + { K_F25, "F25" }, + { K_F26, "F26" }, + { K_F27, "F27" }, + { K_F28, "F28" }, + { K_F29, "F29" }, + { K_F30, "F30" }, + + { K_F31, "F31" }, + { K_F32, "F32" }, + { K_F33, "F33" }, + { K_F34, "F34" }, + { K_F35, "F35" }, + { K_F36, "F36" }, + { K_F37, "F37" }, + + { K_XF1, "xF1" }, + { K_XF2, "xF2" }, + { K_XF3, "xF3" }, + { K_XF4, "xF4" }, + + { K_HELP, "Help" }, + { K_UNDO, "Undo" }, + { K_INS, "Insert" }, + { K_INS, "Ins" }, // Alternative name + { K_KINS, "kInsert" }, + { K_HOME, "Home" }, + { K_KHOME, "kHome" }, + { K_XHOME, "xHome" }, + { K_ZHOME, "zHome" }, + { K_END, "End" }, + { K_KEND, "kEnd" }, + { K_XEND, "xEnd" }, + { K_ZEND, "zEnd" }, + { K_PAGEUP, "PageUp" }, + { K_PAGEDOWN, "PageDown" }, + { K_KPAGEUP, "kPageUp" }, + { K_KPAGEDOWN, "kPageDown" }, + + { K_KPLUS, "kPlus" }, + { K_KMINUS, "kMinus" }, + { K_KDIVIDE, "kDivide" }, + { K_KMULTIPLY, "kMultiply" }, + { K_KENTER, "kEnter" }, + { K_KPOINT, "kPoint" }, + + { K_K0, "k0" }, + { K_K1, "k1" }, + { K_K2, "k2" }, + { K_K3, "k3" }, + { K_K4, "k4" }, + { K_K5, "k5" }, + { K_K6, "k6" }, + { K_K7, "k7" }, + { K_K8, "k8" }, + { K_K9, "k9" }, + + { '<', "lt" }, + + { K_MOUSE, "Mouse" }, + { K_LEFTMOUSE, "LeftMouse" }, + { K_LEFTMOUSE_NM, "LeftMouseNM" }, + { K_LEFTDRAG, "LeftDrag" }, + { K_LEFTRELEASE, "LeftRelease" }, + { K_LEFTRELEASE_NM, "LeftReleaseNM" }, + { K_MIDDLEMOUSE, "MiddleMouse" }, + { K_MIDDLEDRAG, "MiddleDrag" }, + { K_MIDDLERELEASE, "MiddleRelease" }, + { K_RIGHTMOUSE, "RightMouse" }, + { K_RIGHTDRAG, "RightDrag" }, + { K_RIGHTRELEASE, "RightRelease" }, + { K_MOUSEDOWN, "ScrollWheelUp" }, + { K_MOUSEUP, "ScrollWheelDown" }, + { K_MOUSELEFT, "ScrollWheelRight" }, + { K_MOUSERIGHT, "ScrollWheelLeft" }, + { K_MOUSEDOWN, "MouseDown" }, // OBSOLETE: Use + { K_MOUSEUP, "MouseUp" }, // ScrollWheelXXX instead + { K_X1MOUSE, "X1Mouse" }, + { K_X1DRAG, "X1Drag" }, + { K_X1RELEASE, "X1Release" }, + { K_X2MOUSE, "X2Mouse" }, + { K_X2DRAG, "X2Drag" }, + { K_X2RELEASE, "X2Release" }, + { K_DROP, "Drop" }, + { K_ZERO, "Nul" }, + { K_SNR, "SNR" }, + { K_PLUG, "Plug" }, + { K_PASTE, "Paste" }, + { 0, NULL } +}; + +int get_special_key_code(const char_u *name) +{ + for (int i = 0; key_names_table[i].name != NULL; i++) { + const char *const table_name = key_names_table[i].name; + int j; + for (j = 0; ascii_isident(name[j]) && table_name[j] != NUL; j++) { + if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC(name[j])) { + break; + } + } + if (!ascii_isident(name[j]) && table_name[j] == NUL) { + return key_names_table[i].key; + } + } + + return 0; +} + + +static const struct modmasktable { + short mod_mask; ///< Bit-mask for particular key modifier. + short mod_flag; ///< Bit(s) for particular key modifier. + char_u name; ///< Single letter name of modifier. +} mod_mask_table[] = { + {MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'M'}, + {MOD_MASK_META, MOD_MASK_META, (char_u)'T'}, + {MOD_MASK_CTRL, MOD_MASK_CTRL, (char_u)'C'}, + {MOD_MASK_SHIFT, MOD_MASK_SHIFT, (char_u)'S'}, + {MOD_MASK_MULTI_CLICK, MOD_MASK_2CLICK, (char_u)'2'}, + {MOD_MASK_MULTI_CLICK, MOD_MASK_3CLICK, (char_u)'3'}, + {MOD_MASK_MULTI_CLICK, MOD_MASK_4CLICK, (char_u)'4'}, + {MOD_MASK_CMD, MOD_MASK_CMD, (char_u)'D'}, + // 'A' must be the last one + {MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'A'}, + {0, 0, NUL} +}; + +int name_to_mod_mask(int c) +{ + c = TOUPPER_ASC(c); + for (size_t i = 0; mod_mask_table[i].mod_mask != 0; i++) { + if (c == mod_mask_table[i].name) { + return mod_mask_table[i].mod_flag; + } + } + return 0; +} + +static int extract_modifiers(int key, int *modp) +{ + int modifiers = *modp; + + if (!(modifiers & MOD_MASK_CMD)) { // Command-key is special + if ((modifiers & MOD_MASK_SHIFT) && ASCII_ISALPHA(key)) { + key = TOUPPER_ASC(key); + modifiers &= ~MOD_MASK_SHIFT; + } + } + if ((modifiers & MOD_MASK_CTRL) + && ((key >= '?' && key <= '_') || ASCII_ISALPHA(key))) { + key = Ctrl_chr(key); + modifiers &= ~MOD_MASK_CTRL; + if (key == 0) { // <C-@> is <Nul> + key = K_ZERO; + } + } + + *modp = modifiers; + return key; +} + +int find_special_key(const char_u **srcp, const size_t src_len, int *const modp, + const bool keycode, const bool keep_x_key, + const bool in_string) +{ + const char_u *last_dash; + const char_u *end_of_name; + const char_u *src; + const char_u *bp; + const char_u *const end = *srcp + src_len - 1; + int modifiers; + int bit; + int key; + uvarnumber_T n; + int l; + + if (src_len == 0) { + return 0; + } + + src = *srcp; + if (src[0] != '<') { + return 0; + } + + // Find end of modifier list + last_dash = src; + for (bp = src + 1; bp <= end && (*bp == '-' || ascii_isident(*bp)); bp++) { + if (*bp == '-') { + last_dash = bp; + if (bp + 1 <= end) { + l = utfc_ptr2len_len(bp + 1, (int)(end - bp) + 1); + // Anything accepted, like <C-?>. + // <C-"> or <M-"> are not special in strings as " is + // the string delimiter. With a backslash it works: <M-\"> + if (end - bp > l && !(in_string && bp[1] == '"') && bp[2] == '>') { + bp += l; + } else if (end - bp > 2 && in_string && bp[1] == '\\' + && bp[2] == '"' && bp[3] == '>') { + bp += 2; + } + } + } + if (end - bp > 3 && bp[0] == 't' && bp[1] == '_') { + bp += 3; // skip t_xx, xx may be '-' or '>' + } else if (end - bp > 4 && STRNICMP(bp, "char-", 5) == 0) { + vim_str2nr(bp + 5, NULL, &l, STR2NR_ALL, NULL, NULL, 0); + bp += l + 5; + break; + } + } + + if (bp <= end && *bp == '>') { // found matching '>' + end_of_name = bp + 1; + + /* Which modifiers are given? */ + modifiers = 0x0; + for (bp = src + 1; bp < last_dash; bp++) { + if (*bp != '-') { + bit = name_to_mod_mask(*bp); + if (bit == 0x0) { + break; // Illegal modifier name + } + modifiers |= bit; + } + } + + // Legal modifier name. + if (bp >= last_dash) { + if (STRNICMP(last_dash + 1, "char-", 5) == 0 + && ascii_isdigit(last_dash[6])) { + // <Char-123> or <Char-033> or <Char-0x33> + vim_str2nr(last_dash + 6, NULL, NULL, STR2NR_ALL, NULL, &n, 0); + key = (int)n; + } else { + int off = 1; + + // Modifier with single letter, or special key name. + if (in_string && last_dash[1] == '\\' && last_dash[2] == '"') { + off = 2; + } + l = mb_ptr2len(last_dash + 1); + if (modifiers != 0 && last_dash[l + 1] == '>') { + key = PTR2CHAR(last_dash + off); + } else { + key = get_special_key_code(last_dash + off); + if (!keep_x_key) { + key = handle_x_keys(key); + } + } + } + + // get_special_key_code() may return NUL for invalid + // special key name. + if (key != NUL) { + // Only use a modifier when there is no special key code that + // includes the modifier. + key = simplify_key(key, &modifiers); + + if (!keycode) { + // don't want keycode, use single byte code + if (key == K_BS) { + key = BS; + } else if (key == K_DEL || key == K_KDEL) { + key = DEL; + } + } + + // Normal Key with modifier: + // Try to make a single byte code (except for Alt/Meta modifiers). + if (!IS_SPECIAL(key)) { + key = extract_modifiers(key, &modifiers); + } + + *modp = modifiers; + *srcp = end_of_name; + return key; + } + } + } + return 0; +} + +char_u *add_char2buf(int c, char_u *s) +{ + char_u temp[MB_MAXBYTES + 1]; + const int len = utf_char2bytes(c, temp); + for (int i = 0; i < len; ++i) { + c = temp[i]; + // Need to escape K_SPECIAL and CSI like in the typeahead buffer. + if (c == K_SPECIAL) { + *s++ = K_SPECIAL; + *s++ = KS_SPECIAL; + *s++ = KE_FILLER; + } else { + *s++ = c; + } + } + return s; +} + +unsigned int trans_special(const char_u **srcp, const size_t src_len, + char_u *const dst, const bool keycode, + const bool in_string) +{ + 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; + } + + // Put the appropriate modifier in a string. + if (modifiers != 0) { + dst[dlen++] = K_SPECIAL; + dst[dlen++] = KS_MODIFIER; + dst[dlen++] = (char_u)modifiers; + } + + if (IS_SPECIAL(key)) { + dst[dlen++] = K_SPECIAL; + dst[dlen++] = (char_u)KEY2TERMCAP0(key); + dst[dlen++] = KEY2TERMCAP1(key); + } else if (has_mbyte && !keycode) { + dlen += (unsigned int)(*mb_char2bytes)(key, dst + dlen); + } else if (keycode) { + char_u *after = add_char2buf(key, dst + dlen); + assert(after >= dst && (uintmax_t)(after - dst) <= UINT_MAX); + dlen = (unsigned int)(after - dst); + } else { + dst[dlen++] = (char_u)key; + } + + return dlen; +} diff --git a/test/symbolic/klee/nvim/mbyte.c b/test/symbolic/klee/nvim/mbyte.c new file mode 100644 index 0000000000..f98a531206 --- /dev/null +++ b/test/symbolic/klee/nvim/mbyte.c @@ -0,0 +1,266 @@ +#include <stddef.h> +#include <inttypes.h> +#include <assert.h> +#include <stdbool.h> + +#include "nvim/types.h" +#include "nvim/mbyte.h" +#include "nvim/ascii.h" + +const uint8_t utf8len_tab_zero[] = { + //1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 2 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 4 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 6 + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 8 + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // A + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // C + 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,0,0, // E +}; + +const uint8_t utf8len_tab[] = { + // ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?A ?B ?C ?D ?E ?F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B? + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C? + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // D? + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // E? + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1, // F? +}; + +int utf_ptr2char(const char_u *const p) +{ + if (p[0] < 0x80) { // Be quick for ASCII. + return p[0]; + } + + const uint8_t len = utf8len_tab_zero[p[0]]; + if (len > 1 && (p[1] & 0xc0) == 0x80) { + if (len == 2) { + return ((p[0] & 0x1f) << 6) + (p[1] & 0x3f); + } + if ((p[2] & 0xc0) == 0x80) { + if (len == 3) { + return (((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6) + + (p[2] & 0x3f)); + } + if ((p[3] & 0xc0) == 0x80) { + if (len == 4) { + return (((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12) + + ((p[2] & 0x3f) << 6) + (p[3] & 0x3f)); + } + if ((p[4] & 0xc0) == 0x80) { + if (len == 5) { + return (((p[0] & 0x03) << 24) + ((p[1] & 0x3f) << 18) + + ((p[2] & 0x3f) << 12) + ((p[3] & 0x3f) << 6) + + (p[4] & 0x3f)); + } + if ((p[5] & 0xc0) == 0x80 && len == 6) { + return (((p[0] & 0x01) << 30) + ((p[1] & 0x3f) << 24) + + ((p[2] & 0x3f) << 18) + ((p[3] & 0x3f) << 12) + + ((p[4] & 0x3f) << 6) + (p[5] & 0x3f)); + } + } + } + } + } + // Illegal value: just return the first byte. + return p[0]; +} + +bool utf_composinglike(const char_u *p1, const char_u *p2) +{ + return false; +} + +char_u *string_convert(const vimconv_T *conv, char_u *data, size_t *size) +{ + return NULL; +} + +int utf_ptr2len_len(const char_u *p, int size) +{ + int len; + int i; + int m; + + len = utf8len_tab[*p]; + if (len == 1) + return 1; /* NUL, ascii or illegal lead byte */ + if (len > size) + m = size; /* incomplete byte sequence. */ + else + m = len; + for (i = 1; i < m; ++i) + if ((p[i] & 0xc0) != 0x80) + return 1; + return len; +} + +int utfc_ptr2len_len(const char_u *p, int size) +{ + int len; + int prevlen; + + if (size < 1 || *p == NUL) + return 0; + if (p[0] < 0x80 && (size == 1 || p[1] < 0x80)) /* be quick for ASCII */ + return 1; + + /* Skip over first UTF-8 char, stopping at a NUL byte. */ + len = utf_ptr2len_len(p, size); + + /* Check for illegal byte and incomplete byte sequence. */ + if ((len == 1 && p[0] >= 0x80) || len > size) + return 1; + + /* + * Check for composing characters. We can handle only the first six, but + * skip all of them (otherwise the cursor would get stuck). + */ + prevlen = 0; + while (len < size) { + int len_next_char; + + if (p[len] < 0x80) + break; + + /* + * Next character length should not go beyond size to ensure that + * UTF_COMPOSINGLIKE(...) does not read beyond size. + */ + len_next_char = utf_ptr2len_len(p + len, size - len); + if (len_next_char > size - len) + break; + + if (!UTF_COMPOSINGLIKE(p + prevlen, p + len)) + break; + + /* Skip over composing char */ + prevlen = len; + len += len_next_char; + } + return len; +} + +int utf_char2len(const int c) +{ + if (c < 0x80) { + return 1; + } else if (c < 0x800) { + return 2; + } else if (c < 0x10000) { + return 3; + } else if (c < 0x200000) { + return 4; + } else if (c < 0x4000000) { + return 5; + } else { + return 6; + } +} + +int utf_char2bytes(const int c, char_u *const buf) +{ + if (c < 0x80) { // 7 bits + buf[0] = c; + return 1; + } else if (c < 0x800) { // 11 bits + buf[0] = 0xc0 + ((unsigned)c >> 6); + buf[1] = 0x80 + (c & 0x3f); + return 2; + } else if (c < 0x10000) { // 16 bits + buf[0] = 0xe0 + ((unsigned)c >> 12); + buf[1] = 0x80 + (((unsigned)c >> 6) & 0x3f); + buf[2] = 0x80 + (c & 0x3f); + return 3; + } else if (c < 0x200000) { // 21 bits + buf[0] = 0xf0 + ((unsigned)c >> 18); + buf[1] = 0x80 + (((unsigned)c >> 12) & 0x3f); + buf[2] = 0x80 + (((unsigned)c >> 6) & 0x3f); + buf[3] = 0x80 + (c & 0x3f); + return 4; + } else if (c < 0x4000000) { // 26 bits + buf[0] = 0xf8 + ((unsigned)c >> 24); + buf[1] = 0x80 + (((unsigned)c >> 18) & 0x3f); + buf[2] = 0x80 + (((unsigned)c >> 12) & 0x3f); + buf[3] = 0x80 + (((unsigned)c >> 6) & 0x3f); + buf[4] = 0x80 + (c & 0x3f); + return 5; + } else { // 31 bits + buf[0] = 0xfc + ((unsigned)c >> 30); + buf[1] = 0x80 + (((unsigned)c >> 24) & 0x3f); + buf[2] = 0x80 + (((unsigned)c >> 18) & 0x3f); + buf[3] = 0x80 + (((unsigned)c >> 12) & 0x3f); + buf[4] = 0x80 + (((unsigned)c >> 6) & 0x3f); + buf[5] = 0x80 + (c & 0x3f); + return 6; + } +} + +int utf_ptr2len(const char_u *const p) +{ + if (*p == NUL) { + return 0; + } + const int len = utf8len_tab[*p]; + for (int i = 1; i < len; i++) { + if ((p[i] & 0xc0) != 0x80) { + return 1; + } + } + return len; +} + +int utfc_ptr2len(const char_u *const p) +{ + uint8_t b0 = (uint8_t)(*p); + + if (b0 == NUL) { + return 0; + } + if (b0 < 0x80 && p[1] < 0x80) { // be quick for ASCII + return 1; + } + + // Skip over first UTF-8 char, stopping at a NUL byte. + int len = utf_ptr2len(p); + + // Check for illegal byte. + if (len == 1 && b0 >= 0x80) { + return 1; + } + + // Check for composing characters. We can handle only the first six, but + // skip all of them (otherwise the cursor would get stuck). + int prevlen = 0; + for (;;) { + if (p[len] < 0x80 || !UTF_COMPOSINGLIKE(p + prevlen, p + len)) { + return len; + } + + // Skip over composing char. + prevlen = len; + len += utf_ptr2len(p + len); + } +} + +void mb_copy_char(const char_u **fp, char_u **tp) +{ + const size_t l = utfc_ptr2len(*fp); + + memmove(*tp, *fp, (size_t)l); + *tp += l; + *fp += l; +} diff --git a/test/symbolic/klee/nvim/memory.c b/test/symbolic/klee/nvim/memory.c new file mode 100644 index 0000000000..df422cea3e --- /dev/null +++ b/test/symbolic/klee/nvim/memory.c @@ -0,0 +1,101 @@ +#include <stdlib.h> +#include <assert.h> + +#include "nvim/lib/ringbuf.h" + +enum { RB_SIZE = 1024 }; + +typedef struct { + void *ptr; + size_t size; +} AllocRecord; + +RINGBUF_TYPEDEF(AllocRecords, AllocRecord) +RINGBUF_INIT(AllocRecords, arecs, AllocRecord, RINGBUF_DUMMY_FREE) +RINGBUF_STATIC(static, AllocRecords, AllocRecord, arecs, RB_SIZE) + +size_t allocated_memory = 0; +size_t ever_allocated_memory = 0; + +size_t allocated_memory_limit = SIZE_MAX; + +void *xmalloc(const size_t size) +{ + void *ret = malloc(size); + allocated_memory += size; + ever_allocated_memory += size; + assert(allocated_memory <= allocated_memory_limit); + assert(arecs_rb_length(&arecs) < RB_SIZE); + arecs_rb_push(&arecs, (AllocRecord) { + .ptr = ret, + .size = size, + }); + return ret; +} + +void xfree(void *const p) +{ + if (p == NULL) { + return; + } + RINGBUF_FORALL(&arecs, AllocRecord, arec) { + if (arec->ptr == p) { + allocated_memory -= arec->size; + arecs_rb_remove(&arecs, arecs_rb_find_idx(&arecs, arec)); + return; + } + } + assert(false); +} + +void *xrealloc(void *const p, size_t new_size) +{ + void *ret = realloc(p, new_size); + RINGBUF_FORALL(&arecs, AllocRecord, arec) { + if (arec->ptr == p) { + allocated_memory -= arec->size; + allocated_memory += new_size; + if (new_size > arec->size) { + ever_allocated_memory += (new_size - arec->size); + } + arec->ptr = ret; + arec->size = new_size; + return ret; + } + } + assert(false); + return (void *)(intptr_t)1; +} + +char *xstrdup(const char *str) + FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET + FUNC_ATTR_NONNULL_ALL +{ + return xmemdupz(str, strlen(str)); +} + +void *xmallocz(size_t size) + FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT +{ + size_t total_size = size + 1; + assert(total_size > size); + + void *ret = xmalloc(total_size); + ((char *)ret)[size] = 0; + + return ret; +} + +char *xstpcpy(char *restrict dst, const char *restrict src) + FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + const size_t len = strlen(src); + return (char *)memcpy(dst, src, len + 1) + len; +} + +void *xmemdupz(const void *data, size_t len) + FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_NONNULL_ALL +{ + return memcpy(xmallocz(len), data, len); +} diff --git a/test/symbolic/klee/run.sh b/test/symbolic/klee/run.sh new file mode 100755 index 0000000000..0234a935b5 --- /dev/null +++ b/test/symbolic/klee/run.sh @@ -0,0 +1,102 @@ +#!/bin/sh + +set -e +set -x +test -z "$POSH_VERSION" && set -u + +PROJECT_SOURCE_DIR=. +PROJECT_BINARY_DIR="$PROJECT_SOURCE_DIR/build" +KLEE_TEST_DIR="$PROJECT_SOURCE_DIR/test/symbolic/klee" +KLEE_BIN_DIR="$PROJECT_BINARY_DIR/klee" +KLEE_OUT_DIR="$KLEE_BIN_DIR/out" + +help() { + echo "Usage:" + echo + echo " $0 -c fname" + echo " $0 fname" + echo " $0 -s" + echo + echo "First form compiles executable out of test/symbolic/klee/{fname}.c." + echo "Compiled executable is placed into build/klee/{fname}. Must first" + echo "successfully compile Neovim in order to generate declarations." + echo + echo "Second form runs KLEE in a docker container using file " + echo "test/symbolic/klee/{fname.c}. Bitcode is placed into build/klee/a.bc," + echo "results are placed into build/klee/out/. The latter is first deleted if" + echo "it exists." + echo + echo "Third form runs ktest-tool which prints errors found by KLEE via " + echo "the same container used to run KLEE." +} + +main() { + local compile= + local print_errs= + if test "$1" = "--help" ; then + help + return + fi + if test "$1" = "-s" ; then + print_errs=1 + shift + elif test "$1" = "-c" ; then + compile=1 + shift + fi + if test -z "$print_errs" ; then + local test="$1" ; shift + fi + + local includes= + includes="$includes -I$KLEE_TEST_DIR" + includes="$includes -I/home/klee/klee_src/include" + includes="$includes -I$PROJECT_SOURCE_DIR/src" + includes="$includes -I$PROJECT_BINARY_DIR/src/nvim/auto" + includes="$includes -I$PROJECT_BINARY_DIR/include" + includes="$includes -I$PROJECT_BINARY_DIR/config" + includes="$includes -I/host-includes" + + local defines= + defines="$defines -DMIN_LOG_LEVEL=9999" + defines="$defines -DINCLUDE_GENERATED_DECLARATIONS" + + test -z "$compile" && defines="$defines -DUSE_KLEE" + + test -d "$KLEE_BIN_DIR" || mkdir -p "$KLEE_BIN_DIR" + + if test -z "$compile" ; then + local line1='cd /image' + if test -z "$print_errs" ; then + test -d "$KLEE_OUT_DIR" && rm -r "$KLEE_OUT_DIR" + + line1="$line1 && $(echo clang \ + $includes $defines \ + -o "$KLEE_BIN_DIR/a.bc" -emit-llvm -g -c \ + "$KLEE_TEST_DIR/$test.c")" + line1="$line1 && klee --libc=uclibc --posix-runtime " + line1="$line1 '--output-dir=$KLEE_OUT_DIR' '$KLEE_BIN_DIR/a.bc'" + fi + local line2="for t in '$KLEE_OUT_DIR'/*.err" + line2="$line2 ; do ktest-tool --write-ints" + line2="$line2 \"\$(printf '%s' \"\$t\" | sed -e 's@\\.[^/]*\$@.ktest@')\"" + line2="$line2 ; done" + printf '%s\n%s\n' "$line1" "$line2" | \ + docker run \ + --volume "$(cd "$PROJECT_SOURCE_DIR" && pwd)":/image \ + --volume "/usr/include":/host-includes \ + --interactive \ + --rm \ + --ulimit='stack=-1:-1' \ + klee/klee \ + /bin/sh -x + else + clang \ + $includes $defines \ + -o "$KLEE_BIN_DIR/$test" \ + -O0 -g \ + "$KLEE_TEST_DIR/$test.c" + fi +} + +main "$@" diff --git a/test/symbolic/klee/viml_expressions_lexer.c b/test/symbolic/klee/viml_expressions_lexer.c new file mode 100644 index 0000000000..ee7dc312e9 --- /dev/null +++ b/test/symbolic/klee/viml_expressions_lexer.c @@ -0,0 +1,105 @@ +#ifdef USE_KLEE +# include <klee/klee.h> +#else +# include <string.h> +# include <stdio.h> +#endif +#include <stddef.h> +#include <stdint.h> +#include <assert.h> + +#include "nvim/viml/parser/expressions.h" +#include "nvim/viml/parser/parser.h" +#include "nvim/mbyte.h" + +#include "nvim/memory.c" +#include "nvim/mbyte.c" +#include "nvim/charset.c" +#include "nvim/garray.c" +#include "nvim/gettext.c" +#include "nvim/keymap.c" +#include "nvim/viml/parser/expressions.c" + +#define INPUT_SIZE 7 + +uint8_t avoid_optimizing_out; + +void simple_get_line(void *cookie, ParserLine *ret_pline) +{ + ParserLine **plines_p = (ParserLine **)cookie; + *ret_pline = **plines_p; + (*plines_p)++; +} + +int main(const int argc, const char *const *const argv, + const char *const *const environ) +{ + char input[INPUT_SIZE]; + uint8_t shift; + int flags; + avoid_optimizing_out = argc; + +#ifndef USE_KLEE + sscanf(argv[2], "%d", &flags); +#endif + +#ifdef USE_KLEE + klee_make_symbolic(input, sizeof(input), "input"); + klee_make_symbolic(&shift, sizeof(shift), "shift"); + klee_make_symbolic(&flags, sizeof(flags), "flags"); + klee_assume(shift < INPUT_SIZE); + klee_assume(flags <= (kELFlagPeek|kELFlagAllowFloat|kELFlagForbidEOC + |kELFlagForbidScope|kELFlagIsNotCmp)); +#endif + + ParserLine plines[] = { + { +#ifdef USE_KLEE + .data = &input[shift], + .size = sizeof(input) - shift, +#else + .data = (const char *)argv[1], + .size = strlen(argv[1]), +#endif + .allocated = false, + }, + { + .data = NULL, + .size = 0, + .allocated = false, + }, + }; +#ifdef USE_KLEE + assert(plines[0].size <= INPUT_SIZE); + assert((plines[0].data[0] != 5) | (plines[0].data[0] != argc)); +#endif + ParserLine *cur_pline = &plines[0]; + + ParserState pstate = { + .reader = { + .get_line = simple_get_line, + .cookie = &cur_pline, + .lines = KV_INITIAL_VALUE, + .conv.vc_type = CONV_NONE, + }, + .pos = { 0, 0 }, + .colors = NULL, + .can_continuate = false, + }; + kvi_init(pstate.reader.lines); + + allocated_memory_limit = 0; + LexExprToken token = viml_pexpr_next_token(&pstate, flags); + if (flags & kELFlagPeek) { + assert(pstate.pos.line == 0 && pstate.pos.col == 0); + } else { + assert((pstate.pos.line == 0) + ? (pstate.pos.col > 0) + : (pstate.pos.line == 1 && pstate.pos.col == 0)); + } + assert(allocated_memory == 0); + assert(ever_allocated_memory == 0); +#ifndef USE_KLEE + fprintf(stderr, "tkn: %s\n", viml_pexpr_repr_token(&pstate, token, NULL)); +#endif +} diff --git a/test/symbolic/klee/viml_expressions_parser.c b/test/symbolic/klee/viml_expressions_parser.c new file mode 100644 index 0000000000..9a876ed3fa --- /dev/null +++ b/test/symbolic/klee/viml_expressions_parser.c @@ -0,0 +1,117 @@ +#ifdef USE_KLEE +# include <klee/klee.h> +#else +# include <string.h> +#endif +#include <stddef.h> +#include <stdint.h> +#include <assert.h> + +#include "nvim/viml/parser/expressions.h" +#include "nvim/viml/parser/parser.h" +#include "nvim/mbyte.h" + +#include "nvim/memory.c" +#include "nvim/mbyte.c" +#include "nvim/charset.c" +#include "nvim/garray.c" +#include "nvim/gettext.c" +#include "nvim/viml/parser/expressions.c" +#include "nvim/keymap.c" + +#define INPUT_SIZE 50 + +uint8_t avoid_optimizing_out; + +void simple_get_line(void *cookie, ParserLine *ret_pline) +{ + ParserLine **plines_p = (ParserLine **)cookie; + *ret_pline = **plines_p; + (*plines_p)++; +} + +int main(const int argc, const char *const *const argv, + const char *const *const environ) +{ + char input[INPUT_SIZE]; + uint8_t shift; + unsigned flags; + const bool peek = false; + avoid_optimizing_out = argc; + +#ifndef USE_KLEE + sscanf(argv[2], "%d", &flags); +#endif + +#ifdef USE_KLEE + klee_make_symbolic(input, sizeof(input), "input"); + klee_make_symbolic(&shift, sizeof(shift), "shift"); + klee_make_symbolic(&flags, sizeof(flags), "flags"); + klee_assume(shift < INPUT_SIZE); + klee_assume( + flags <= (kExprFlagsMulti|kExprFlagsDisallowEOC|kExprFlagsParseLet)); +#endif + + ParserLine plines[] = { + { +#ifdef USE_KLEE + .data = &input[shift], + .size = sizeof(input) - shift, +#else + .data = argv[1], + .size = strlen(argv[1]), +#endif + .allocated = false, + }, + { + .data = NULL, + .size = 0, + .allocated = false, + }, + }; +#ifdef USE_KLEE + assert(plines[0].size <= INPUT_SIZE); + assert((plines[0].data[0] != 5) | (plines[0].data[0] != argc)); +#endif + ParserLine *cur_pline = &plines[0]; + + ParserHighlight colors; + kvi_init(colors); + + ParserState pstate = { + .reader = { + .get_line = simple_get_line, + .cookie = &cur_pline, + .lines = KV_INITIAL_VALUE, + .conv.vc_type = CONV_NONE, + }, + .pos = { 0, 0 }, + .colors = &colors, + .can_continuate = false, + }; + kvi_init(pstate.reader.lines); + + const ExprAST ast = viml_pexpr_parse(&pstate, (int)flags); + assert(ast.root != NULL || ast.err.msg); + if (flags & kExprFlagsParseLet) { + assert(ast.err.msg != NULL + || ast.root->type == kExprNodeAssignment + || (ast.root->type == kExprNodeListLiteral + && ast.root->children != NULL) + || ast.root->type == kExprNodeComplexIdentifier + || ast.root->type == kExprNodeCurlyBracesIdentifier + || ast.root->type == kExprNodePlainIdentifier + || ast.root->type == kExprNodeRegister + || ast.root->type == kExprNodeEnvironment + || ast.root->type == kExprNodeOption + || ast.root->type == kExprNodeSubscript + || ast.root->type == kExprNodeConcatOrSubscript); + } + // Can’t possibly have more highlight tokens then there are bytes in string. + assert(kv_size(colors) <= INPUT_SIZE - shift); + kvi_destroy(colors); + // Not destroying pstate.reader.lines because there is no way it could exceed + // its limits in the current circumstances. + viml_pexpr_free_ast(ast); + assert(allocated_memory == 0); +} diff --git a/test/unit/charset/vim_str2nr_spec.lua b/test/unit/charset/vim_str2nr_spec.lua new file mode 100644 index 0000000000..891e6def09 --- /dev/null +++ b/test/unit/charset/vim_str2nr_spec.lua @@ -0,0 +1,320 @@ +local helpers = require("test.unit.helpers")(after_each) +local bit = require('bit') + +local itp = helpers.gen_itp(it) + +local child_call_once = helpers.child_call_once +local cimport = helpers.cimport +local ffi = helpers.ffi + +local lib = cimport('./src/nvim/charset.h') + +local ARGTYPES + +child_call_once(function() + ARGTYPES = { + num = ffi.typeof('varnumber_T[1]'), + unum = ffi.typeof('uvarnumber_T[1]'), + pre = ffi.typeof('int[1]'), + len = ffi.typeof('int[1]'), + } +end) + +local icnt = -42 +local ucnt = 4242 + +local function arginit(arg) + if arg == 'unum' then + ucnt = ucnt + 1 + return ARGTYPES[arg]({ucnt}) + else + icnt = icnt - 1 + return ARGTYPES[arg]({icnt}) + end +end + +local function argreset(arg, args) + if arg == 'unum' then + ucnt = ucnt + 1 + args[arg][0] = ucnt + else + icnt = icnt - 1 + args[arg][0] = icnt + end +end + +local function test_vim_str2nr(s, what, exp, maxlen) + local bits = {} + for k, _ in pairs(exp) do + bits[#bits + 1] = k + end + maxlen = maxlen or #s + local args = {} + for k, _ in pairs(ARGTYPES) do + args[k] = arginit(k) + end + for case = 0, ((2 ^ (#bits)) - 1) do + local cv = {} + for b = 0, (#bits - 1) do + if bit.band(case, (2 ^ b)) == 0 then + local k = bits[b + 1] + argreset(k, args) + cv[k] = args[k] + end + end + lib.vim_str2nr(s, cv.pre, cv.len, what, cv.num, cv.unum, maxlen) + for cck, ccv in pairs(cv) do + if exp[cck] ~= tonumber(ccv[0]) then + error(('Failed check (%s = %d) in test (s=%s, w=%u, m=%d): %d'):format( + cck, exp[cck], s, tonumber(what), maxlen, tonumber(ccv[0]) + )) + end + end + end +end + +local _itp = itp +itp = function(...) + collectgarbage('restart') + _itp(...) +end + +describe('vim_str2nr()', function() + itp('works fine when it has nothing to do', function() + test_vim_str2nr('', 0, {len = 0, num = 0, unum = 0, pre = 0}, 0) + test_vim_str2nr('', lib.STR2NR_ALL, {len = 0, num = 0, unum = 0, pre = 0}, 0) + test_vim_str2nr('', lib.STR2NR_BIN, {len = 0, num = 0, unum = 0, pre = 0}, 0) + test_vim_str2nr('', lib.STR2NR_OCT, {len = 0, num = 0, unum = 0, pre = 0}, 0) + test_vim_str2nr('', lib.STR2NR_HEX, {len = 0, num = 0, unum = 0, pre = 0}, 0) + test_vim_str2nr('', lib.STR2NR_FORCE + lib.STR2NR_DEC, {len = 0, num = 0, unum = 0, pre = 0}, 0) + test_vim_str2nr('', lib.STR2NR_FORCE + lib.STR2NR_BIN, {len = 0, num = 0, unum = 0, pre = 0}, 0) + test_vim_str2nr('', lib.STR2NR_FORCE + lib.STR2NR_OCT, {len = 0, num = 0, unum = 0, pre = 0}, 0) + test_vim_str2nr('', lib.STR2NR_FORCE + lib.STR2NR_HEX, {len = 0, num = 0, unum = 0, pre = 0}, 0) + end) + itp('works with decimal numbers', function() + for _, flags in ipairs({ + 0, + lib.STR2NR_BIN, + lib.STR2NR_OCT, + lib.STR2NR_HEX, + lib.STR2NR_BIN + lib.STR2NR_OCT, + lib.STR2NR_BIN + lib.STR2NR_HEX, + lib.STR2NR_OCT + lib.STR2NR_HEX, + lib.STR2NR_ALL, + lib.STR2NR_FORCE + lib.STR2NR_DEC, + }) do + -- Check that all digits are recognized + test_vim_str2nr( '12345', flags, {len = 5, num = 12345, unum = 12345, pre = 0}, 0) + test_vim_str2nr( '67890', flags, {len = 5, num = 67890, unum = 67890, pre = 0}, 0) + test_vim_str2nr( '12345A', flags, {len = 5, num = 12345, unum = 12345, pre = 0}, 0) + test_vim_str2nr( '67890A', flags, {len = 5, num = 67890, unum = 67890, pre = 0}, 0) + + test_vim_str2nr( '42', flags, {len = 2, num = 42, unum = 42, pre = 0}, 0) + test_vim_str2nr( '42', flags, {len = 1, num = 4, unum = 4, pre = 0}, 1) + test_vim_str2nr( '42', flags, {len = 2, num = 42, unum = 42, pre = 0}, 2) + test_vim_str2nr( '42', flags, {len = 2, num = 42, unum = 42, pre = 0}, 3) -- includes NUL byte in maxlen + + test_vim_str2nr( '42x', flags, {len = 2, num = 42, unum = 42, pre = 0}, 0) + test_vim_str2nr( '42x', flags, {len = 2, num = 42, unum = 42, pre = 0}, 3) + + test_vim_str2nr('-42', flags, {len = 3, num = -42, unum = 42, pre = 0}, 3) + test_vim_str2nr('-42', flags, {len = 1, num = 0, unum = 0, pre = 0}, 1) + + test_vim_str2nr('-42x', flags, {len = 3, num = -42, unum = 42, pre = 0}, 0) + test_vim_str2nr('-42x', flags, {len = 3, num = -42, unum = 42, pre = 0}, 4) + end + end) + itp('works with binary numbers', function() + for _, flags in ipairs({ + lib.STR2NR_BIN, + lib.STR2NR_BIN + lib.STR2NR_OCT, + lib.STR2NR_BIN + lib.STR2NR_HEX, + lib.STR2NR_ALL, + lib.STR2NR_FORCE + lib.STR2NR_BIN, + }) do + local bin + local BIN + if flags > lib.STR2NR_FORCE then + bin = 0 + BIN = 0 + else + bin = ('b'):byte() + BIN = ('B'):byte() + end + + test_vim_str2nr( '0b101', flags, {len = 5, num = 5, unum = 5, pre = bin}, 0) + test_vim_str2nr( '0b101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1) + test_vim_str2nr( '0b101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 2) + test_vim_str2nr( '0b101', flags, {len = 3, num = 1, unum = 1, pre = bin}, 3) + test_vim_str2nr( '0b101', flags, {len = 4, num = 2, unum = 2, pre = bin}, 4) + test_vim_str2nr( '0b101', flags, {len = 5, num = 5, unum = 5, pre = bin}, 5) + test_vim_str2nr( '0b101', flags, {len = 5, num = 5, unum = 5, pre = bin}, 6) + + test_vim_str2nr( '0b1012', flags, {len = 5, num = 5, unum = 5, pre = bin}, 0) + test_vim_str2nr( '0b1012', flags, {len = 5, num = 5, unum = 5, pre = bin}, 6) + + test_vim_str2nr('-0b101', flags, {len = 6, num = -5, unum = 5, pre = bin}, 0) + test_vim_str2nr('-0b101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1) + test_vim_str2nr('-0b101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 2) + test_vim_str2nr('-0b101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 3) + test_vim_str2nr('-0b101', flags, {len = 4, num = -1, unum = 1, pre = bin}, 4) + test_vim_str2nr('-0b101', flags, {len = 5, num = -2, unum = 2, pre = bin}, 5) + test_vim_str2nr('-0b101', flags, {len = 6, num = -5, unum = 5, pre = bin}, 6) + test_vim_str2nr('-0b101', flags, {len = 6, num = -5, unum = 5, pre = bin}, 7) + + test_vim_str2nr('-0b1012', flags, {len = 6, num = -5, unum = 5, pre = bin}, 0) + test_vim_str2nr('-0b1012', flags, {len = 6, num = -5, unum = 5, pre = bin}, 7) + + test_vim_str2nr( '0B101', flags, {len = 5, num = 5, unum = 5, pre = BIN}, 0) + test_vim_str2nr( '0B101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1) + test_vim_str2nr( '0B101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 2) + test_vim_str2nr( '0B101', flags, {len = 3, num = 1, unum = 1, pre = BIN}, 3) + test_vim_str2nr( '0B101', flags, {len = 4, num = 2, unum = 2, pre = BIN}, 4) + test_vim_str2nr( '0B101', flags, {len = 5, num = 5, unum = 5, pre = BIN}, 5) + test_vim_str2nr( '0B101', flags, {len = 5, num = 5, unum = 5, pre = BIN}, 6) + + test_vim_str2nr( '0B1012', flags, {len = 5, num = 5, unum = 5, pre = BIN}, 0) + test_vim_str2nr( '0B1012', flags, {len = 5, num = 5, unum = 5, pre = BIN}, 6) + + test_vim_str2nr('-0B101', flags, {len = 6, num = -5, unum = 5, pre = BIN}, 0) + test_vim_str2nr('-0B101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1) + test_vim_str2nr('-0B101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 2) + test_vim_str2nr('-0B101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 3) + test_vim_str2nr('-0B101', flags, {len = 4, num = -1, unum = 1, pre = BIN}, 4) + test_vim_str2nr('-0B101', flags, {len = 5, num = -2, unum = 2, pre = BIN}, 5) + test_vim_str2nr('-0B101', flags, {len = 6, num = -5, unum = 5, pre = BIN}, 6) + test_vim_str2nr('-0B101', flags, {len = 6, num = -5, unum = 5, pre = BIN}, 7) + + test_vim_str2nr('-0B1012', flags, {len = 6, num = -5, unum = 5, pre = BIN}, 0) + test_vim_str2nr('-0B1012', flags, {len = 6, num = -5, unum = 5, pre = BIN}, 7) + + if flags > lib.STR2NR_FORCE then + test_vim_str2nr('-101', flags, {len = 4, num = -5, unum = 5, pre = 0}, 0) + end + end + end) + itp('works with octal numbers', function() + for _, flags in ipairs({ + lib.STR2NR_OCT, + lib.STR2NR_OCT + lib.STR2NR_BIN, + lib.STR2NR_OCT + lib.STR2NR_HEX, + lib.STR2NR_ALL, + lib.STR2NR_FORCE + lib.STR2NR_OCT, + }) do + local oct + if flags > lib.STR2NR_FORCE then + oct = 0 + else + oct = ('0'):byte() + end + + -- Check that all digits are recognized + test_vim_str2nr( '012345670', flags, {len = 9, num = 2739128, unum = 2739128, pre = oct}, 0) + + test_vim_str2nr( '054', flags, {len = 3, num = 44, unum = 44, pre = oct}, 0) + test_vim_str2nr( '054', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1) + test_vim_str2nr( '054', flags, {len = 2, num = 5, unum = 5, pre = oct}, 2) + test_vim_str2nr( '054', flags, {len = 3, num = 44, unum = 44, pre = oct}, 3) + test_vim_str2nr( '0548', flags, {len = 3, num = 44, unum = 44, pre = oct}, 3) + test_vim_str2nr( '054', flags, {len = 3, num = 44, unum = 44, pre = oct}, 4) + + test_vim_str2nr( '054x', flags, {len = 3, num = 44, unum = 44, pre = oct}, 4) + test_vim_str2nr( '054x', flags, {len = 3, num = 44, unum = 44, pre = oct}, 0) + + test_vim_str2nr('-054', flags, {len = 4, num = -44, unum = 44, pre = oct}, 0) + test_vim_str2nr('-054', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1) + test_vim_str2nr('-054', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 2) + test_vim_str2nr('-054', flags, {len = 3, num = -5, unum = 5, pre = oct}, 3) + test_vim_str2nr('-054', flags, {len = 4, num = -44, unum = 44, pre = oct}, 4) + test_vim_str2nr('-0548', flags, {len = 4, num = -44, unum = 44, pre = oct}, 4) + test_vim_str2nr('-054', flags, {len = 4, num = -44, unum = 44, pre = oct}, 5) + + test_vim_str2nr('-054x', flags, {len = 4, num = -44, unum = 44, pre = oct}, 5) + test_vim_str2nr('-054x', flags, {len = 4, num = -44, unum = 44, pre = oct}, 0) + + if flags > lib.STR2NR_FORCE then + test_vim_str2nr('-54', flags, {len = 3, num = -44, unum = 44, pre = 0}, 0) + test_vim_str2nr('-0548', flags, {len = 4, num = -44, unum = 44, pre = 0}, 5) + test_vim_str2nr('-0548', flags, {len = 4, num = -44, unum = 44, pre = 0}, 0) + else + test_vim_str2nr('-0548', flags, {len = 5, num = -548, unum = 548, pre = 0}, 5) + test_vim_str2nr('-0548', flags, {len = 5, num = -548, unum = 548, pre = 0}, 0) + end + end + end) + itp('works with hexadecimal numbers', function() + for _, flags in ipairs({ + lib.STR2NR_HEX, + lib.STR2NR_HEX + lib.STR2NR_BIN, + lib.STR2NR_HEX + lib.STR2NR_OCT, + lib.STR2NR_ALL, + lib.STR2NR_FORCE + lib.STR2NR_HEX, + }) do + local hex + local HEX + if flags > lib.STR2NR_FORCE then + hex = 0 + HEX = 0 + else + hex = ('x'):byte() + HEX = ('X'):byte() + end + + -- Check that all digits are recognized + test_vim_str2nr('0x12345', flags, {len = 7, num = 74565, unum = 74565, pre = hex}, 0) + test_vim_str2nr('0x67890', flags, {len = 7, num = 424080, unum = 424080, pre = hex}, 0) + test_vim_str2nr('0xABCDEF', flags, {len = 8, num = 11259375, unum = 11259375, pre = hex}, 0) + test_vim_str2nr('0xabcdef', flags, {len = 8, num = 11259375, unum = 11259375, pre = hex}, 0) + + test_vim_str2nr( '0x101', flags, {len = 5, num = 257, unum =257, pre = hex}, 0) + test_vim_str2nr( '0x101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1) + test_vim_str2nr( '0x101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 2) + test_vim_str2nr( '0x101', flags, {len = 3, num = 1, unum = 1, pre = hex}, 3) + test_vim_str2nr( '0x101', flags, {len = 4, num = 16, unum = 16, pre = hex}, 4) + test_vim_str2nr( '0x101', flags, {len = 5, num = 257, unum =257, pre = hex}, 5) + test_vim_str2nr( '0x101', flags, {len = 5, num = 257, unum =257, pre = hex}, 6) + + test_vim_str2nr( '0x101G', flags, {len = 5, num = 257, unum =257, pre = hex}, 0) + test_vim_str2nr( '0x101G', flags, {len = 5, num = 257, unum =257, pre = hex}, 6) + + test_vim_str2nr('-0x101', flags, {len = 6, num =-257, unum =257, pre = hex}, 0) + test_vim_str2nr('-0x101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1) + test_vim_str2nr('-0x101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 2) + test_vim_str2nr('-0x101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 3) + test_vim_str2nr('-0x101', flags, {len = 4, num = -1, unum = 1, pre = hex}, 4) + test_vim_str2nr('-0x101', flags, {len = 5, num = -16, unum = 16, pre = hex}, 5) + test_vim_str2nr('-0x101', flags, {len = 6, num =-257, unum =257, pre = hex}, 6) + test_vim_str2nr('-0x101', flags, {len = 6, num =-257, unum =257, pre = hex}, 7) + + test_vim_str2nr('-0x101G', flags, {len = 6, num =-257, unum =257, pre = hex}, 0) + test_vim_str2nr('-0x101G', flags, {len = 6, num =-257, unum =257, pre = hex}, 7) + + test_vim_str2nr( '0X101', flags, {len = 5, num = 257, unum =257, pre = HEX}, 0) + test_vim_str2nr( '0X101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1) + test_vim_str2nr( '0X101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 2) + test_vim_str2nr( '0X101', flags, {len = 3, num = 1, unum = 1, pre = HEX}, 3) + test_vim_str2nr( '0X101', flags, {len = 4, num = 16, unum = 16, pre = HEX}, 4) + test_vim_str2nr( '0X101', flags, {len = 5, num = 257, unum =257, pre = HEX}, 5) + test_vim_str2nr( '0X101', flags, {len = 5, num = 257, unum =257, pre = HEX}, 6) + + test_vim_str2nr( '0X101G', flags, {len = 5, num = 257, unum =257, pre = HEX}, 0) + test_vim_str2nr( '0X101G', flags, {len = 5, num = 257, unum =257, pre = HEX}, 6) + + test_vim_str2nr('-0X101', flags, {len = 6, num =-257, unum =257, pre = HEX}, 0) + test_vim_str2nr('-0X101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1) + test_vim_str2nr('-0X101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 2) + test_vim_str2nr('-0X101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 3) + test_vim_str2nr('-0X101', flags, {len = 4, num = -1, unum = 1, pre = HEX}, 4) + test_vim_str2nr('-0X101', flags, {len = 5, num = -16, unum = 16, pre = HEX}, 5) + test_vim_str2nr('-0X101', flags, {len = 6, num =-257, unum =257, pre = HEX}, 6) + test_vim_str2nr('-0X101', flags, {len = 6, num =-257, unum =257, pre = HEX}, 7) + + test_vim_str2nr('-0X101G', flags, {len = 6, num =-257, unum =257, pre = HEX}, 0) + test_vim_str2nr('-0X101G', flags, {len = 6, num =-257, unum =257, pre = HEX}, 7) + + if flags > lib.STR2NR_FORCE then + test_vim_str2nr('-101', flags, {len = 4, num = -257, unum = 257, pre = 0}, 0) + end + end + end) +end) diff --git a/test/unit/eval/helpers.lua b/test/unit/eval/helpers.lua index 5bc482216e..3d1c42c3a0 100644 --- a/test/unit/eval/helpers.lua +++ b/test/unit/eval/helpers.lua @@ -1,12 +1,13 @@ local helpers = require('test.unit.helpers')(nil) +local ptr2key = helpers.ptr2key local cimport = helpers.cimport local to_cstr = helpers.to_cstr local ffi = helpers.ffi local eq = helpers.eq local eval = cimport('./src/nvim/eval.h', './src/nvim/eval/typval.h', - './src/nvim/hashtab.h') + './src/nvim/hashtab.h', './src/nvim/memory.h') local null_string = {[true]='NULL string'} local null_list = {[true]='NULL list'} @@ -23,10 +24,19 @@ local nil_value = {[true]='nil'} local lua2typvalt +local function tv_list_item_alloc() + return ffi.cast('listitem_T*', eval.xmalloc(ffi.sizeof('listitem_T'))) +end + +local function tv_list_item_free(li) + eval.tv_clear(li.li_tv) + eval.xfree(li) +end + local function li_alloc(nogc) - local gcfunc = eval.tv_list_item_free + local gcfunc = tv_list_item_free if nogc then gcfunc = nil end - local li = ffi.gc(eval.tv_list_item_alloc(), gcfunc) + local li = ffi.gc(tv_list_item_alloc(), gcfunc) li.li_next = nil li.li_prev = nil li.li_tv = {v_type=eval.VAR_UNKNOWN, v_lock=eval.VAR_UNLOCKED} @@ -40,7 +50,7 @@ local function populate_list(l, lua_l, processed) processed[lua_l] = l for i = 1, #lua_l do local item_tv = ffi.gc(lua2typvalt(lua_l[i], processed), nil) - local item_li = eval.tv_list_item_alloc() + local item_li = tv_list_item_alloc() item_li.li_tv = item_tv eval.tv_list_append(l, item_li) end @@ -91,10 +101,6 @@ local function populate_partial(pt, lua_pt, processed) return pt end -local ptr2key = function(ptr) - return tostring(ptr) -end - local lst2tbl local dct2tbl @@ -306,7 +312,7 @@ local lua2typvalt_type_tab = { processed[l].lv_refcount = processed[l].lv_refcount + 1 return typvalt(eval.VAR_LIST, {v_list=processed[l]}) end - local lst = populate_list(eval.tv_list_alloc(), l, processed) + local lst = populate_list(eval.tv_list_alloc(#l), l, processed) return typvalt(eval.VAR_LIST, {v_list=lst}) end, [dict_type] = function(l, processed) @@ -427,7 +433,8 @@ local function int(n) end local function list(...) - return populate_list(ffi.gc(eval.tv_list_alloc(), eval.tv_list_unref), + return populate_list(ffi.gc(eval.tv_list_alloc(select('#', ...)), + eval.tv_list_unref), {...}, {}) end @@ -536,6 +543,7 @@ return { typvalt=typvalt, li_alloc=li_alloc, + tv_list_item_free=tv_list_item_free, dict_iter=dict_iter, list_iter=list_iter, diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua index bec74f05fc..919a42fbb9 100644 --- a/test/unit/eval/typval_spec.lua +++ b/test/unit/eval/typval_spec.lua @@ -41,6 +41,7 @@ local tbl2callback = eval_helpers.tbl2callback local dict_watchers = eval_helpers.dict_watchers local concat_tables = global_helpers.concat_tables +local map = global_helpers.map local lib = cimport('./src/nvim/eval/typval.h', './src/nvim/memory.h', './src/nvim/mbyte.h', './src/nvim/garray.h', @@ -80,8 +81,6 @@ local function get_alloc_rets(exp_log, res) return exp_log end -local to_cstr_nofree = function(v) return lib.xstrdup(v) end - local alloc_log = alloc_log_new() before_each(function() @@ -121,118 +120,76 @@ end describe('typval.c', function() describe('list', function() describe('item', function() - describe('alloc()/free()', function() + describe('remove()', function() itp('works', function() - local li = li_alloc(true) - neq(nil, li) - lib.tv_list_item_free(li) - alloc_log:check({ - a.li(li), - a.freed(li), - }) - end) - itp('also frees the value', function() - local li - local s - local l - local tv - li = li_alloc(true) - li.li_tv.v_type = lib.VAR_NUMBER - li.li_tv.vval.v_number = 10 - lib.tv_list_item_free(li) - alloc_log:check({ - a.li(li), - a.freed(li), - }) - - li = li_alloc(true) - li.li_tv.v_type = lib.VAR_FLOAT - li.li_tv.vval.v_float = 10.5 - lib.tv_list_item_free(li) - alloc_log:check({ - a.li(li), - a.freed(li), - }) - - li = li_alloc(true) - li.li_tv.v_type = lib.VAR_STRING - li.li_tv.vval.v_string = nil - lib.tv_list_item_free(li) + local l = list(1, 2, 3, 4, 5, 6, 7) + neq(nil, l) + local lis = list_items(l) alloc_log:check({ - a.li(li), - a.freed(alloc_log.null), - a.freed(li), + a.list(l), + a.li(lis[1]), + a.li(lis[2]), + a.li(lis[3]), + a.li(lis[4]), + a.li(lis[5]), + a.li(lis[6]), + a.li(lis[7]), }) - li = li_alloc(true) - li.li_tv.v_type = lib.VAR_STRING - s = to_cstr_nofree('test') - li.li_tv.vval.v_string = s - lib.tv_list_item_free(li) + eq(lis[2], lib.tv_list_item_remove(l, lis[1])) alloc_log:check({ - a.li(li), - a.str(s, #('test')), - a.freed(s), - a.freed(li), + a.freed(table.remove(lis, 1)), }) + eq(lis, list_items(l)) - li = li_alloc(true) - li.li_tv.v_type = lib.VAR_LIST - l = ffi.gc(list(), nil) - l.lv_refcount = 2 - li.li_tv.vval.v_list = l - lib.tv_list_item_free(li) + eq(lis[7], lib.tv_list_item_remove(l, lis[6])) alloc_log:check({ - a.li(li), - a.list(l), - a.freed(li), + a.freed(table.remove(lis)), }) - eq(1, l.lv_refcount) + eq(lis, list_items(l)) - li = li_alloc(true) - tv = lua2typvalt({}) - tv.vval.v_dict.dv_refcount = 2 - li.li_tv = tv - lib.tv_list_item_free(li) + eq(lis[4], lib.tv_list_item_remove(l, lis[3])) alloc_log:check({ - a.li(li), - a.dict(tv.vval.v_dict), - a.freed(li), + a.freed(table.remove(lis, 3)), }) - eq(1, tv.vval.v_dict.dv_refcount) + eq(lis, list_items(l)) end) - end) - describe('remove()', function() - itp('works', function() - local l = list(1, 2, 3, 4, 5, 6, 7) + itp('also frees the value', function() + local l = list('a', 'b', 'c', 'd') neq(nil, l) local lis = list_items(l) alloc_log:check({ a.list(l), + a.str(lis[1].li_tv.vval.v_string, 1), a.li(lis[1]), + a.str(lis[2].li_tv.vval.v_string, 1), a.li(lis[2]), + a.str(lis[3].li_tv.vval.v_string, 1), a.li(lis[3]), + a.str(lis[4].li_tv.vval.v_string, 1), a.li(lis[4]), - a.li(lis[5]), - a.li(lis[6]), - a.li(lis[7]), }) + local strings = map(function(li) return li.li_tv.vval.v_string end, + lis) - lib.tv_list_item_remove(l, lis[1]) + eq(lis[2], lib.tv_list_item_remove(l, lis[1])) alloc_log:check({ + a.freed(table.remove(strings, 1)), a.freed(table.remove(lis, 1)), }) eq(lis, list_items(l)) - lib.tv_list_item_remove(l, lis[6]) + eq(lis[3], lib.tv_list_item_remove(l, lis[2])) alloc_log:check({ - a.freed(table.remove(lis)), + a.freed(table.remove(strings, 2)), + a.freed(table.remove(lis, 2)), }) eq(lis, list_items(l)) - lib.tv_list_item_remove(l, lis[3]) + eq(nil, lib.tv_list_item_remove(l, lis[2])) alloc_log:check({ - a.freed(table.remove(lis, 3)), + a.freed(table.remove(strings, 2)), + a.freed(table.remove(lis, 2)), }) eq(lis, list_items(l)) end) @@ -257,19 +214,19 @@ describe('typval.c', function() a.li(lis[7]), }) - lib.tv_list_item_remove(l, lis[4]) + eq(lis[5], lib.tv_list_item_remove(l, lis[4])) alloc_log:check({a.freed(lis[4])}) eq({lis[1], lis[5], lis[7]}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item}) - lib.tv_list_item_remove(l, lis[2]) + eq(lis[3], lib.tv_list_item_remove(l, lis[2])) alloc_log:check({a.freed(lis[2])}) eq({lis[1], lis[5], lis[7]}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item}) - lib.tv_list_item_remove(l, lis[7]) + eq(nil, lib.tv_list_item_remove(l, lis[7])) alloc_log:check({a.freed(lis[7])}) eq({lis[1], lis[5], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil}) - lib.tv_list_item_remove(l, lis[1]) + eq(lis[3], lib.tv_list_item_remove(l, lis[1])) alloc_log:check({a.freed(lis[1])}) eq({lis[3], lis[5], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil}) @@ -449,7 +406,7 @@ describe('typval.c', function() }) end) end) - describe('remove_items()', function() + describe('drop_items()', function() itp('works', function() local l_tv = lua2typvalt({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}) local l = l_tv.vval.v_list @@ -462,21 +419,92 @@ describe('typval.c', function() } alloc_log:clear() - lib.tv_list_remove_items(l, lis[1], lis[3]) + lib.tv_list_drop_items(l, lis[1], lis[3]) eq({4, 5, 6, 7, 8, 9, 10, 11, 12, 13}, typvalt2lua(l_tv)) eq({lis[4], lis[7], lis[13]}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item}) - lib.tv_list_remove_items(l, lis[11], lis[13]) + lib.tv_list_drop_items(l, lis[11], lis[13]) eq({4, 5, 6, 7, 8, 9, 10}, typvalt2lua(l_tv)) eq({lis[4], lis[7], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil}) - lib.tv_list_remove_items(l, lis[6], lis[8]) + lib.tv_list_drop_items(l, lis[6], lis[8]) eq({4, 5, 9, 10}, typvalt2lua(l_tv)) eq({lis[4], lis[9], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil}) + lib.tv_list_drop_items(l, lis[4], lis[10]) + eq(empty_list, typvalt2lua(l_tv)) + eq({true, true, true}, {lws[1].lw_item == nil, lws[2].lw_item == nil, lws[3].lw_item == nil}) + + lib.tv_list_watch_remove(l, lws[1]) + lib.tv_list_watch_remove(l, lws[2]) + lib.tv_list_watch_remove(l, lws[3]) + + alloc_log:check({}) + end) + end) + describe('remove_items()', function() + itp('works', function() + local l_tv = lua2typvalt({'1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13'}) + local l = l_tv.vval.v_list + local lis = list_items(l) + local strings = map(function(li) return li.li_tv.vval.v_string end, lis) + -- Three watchers: pointing to first, middle and last elements. + local lws = { + list_watch(l, lis[1]), + list_watch(l, lis[7]), + list_watch(l, lis[13]), + } + alloc_log:clear() + + lib.tv_list_remove_items(l, lis[1], lis[3]) + eq({'4', '5', '6', '7', '8', '9', '10', '11', '12', '13'}, typvalt2lua(l_tv)) + eq({lis[4], lis[7], lis[13]}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item}) + alloc_log:check({ + a.freed(strings[1]), + a.freed(lis[1]), + a.freed(strings[2]), + a.freed(lis[2]), + a.freed(strings[3]), + a.freed(lis[3]), + }) + + lib.tv_list_remove_items(l, lis[11], lis[13]) + eq({'4', '5', '6', '7', '8', '9', '10'}, typvalt2lua(l_tv)) + eq({lis[4], lis[7], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil}) + alloc_log:check({ + a.freed(strings[11]), + a.freed(lis[11]), + a.freed(strings[12]), + a.freed(lis[12]), + a.freed(strings[13]), + a.freed(lis[13]), + }) + + lib.tv_list_remove_items(l, lis[6], lis[8]) + eq({'4', '5', '9', '10'}, typvalt2lua(l_tv)) + eq({lis[4], lis[9], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil}) + alloc_log:check({ + a.freed(strings[6]), + a.freed(lis[6]), + a.freed(strings[7]), + a.freed(lis[7]), + a.freed(strings[8]), + a.freed(lis[8]), + }) + lib.tv_list_remove_items(l, lis[4], lis[10]) eq(empty_list, typvalt2lua(l_tv)) eq({true, true, true}, {lws[1].lw_item == nil, lws[2].lw_item == nil, lws[3].lw_item == nil}) + alloc_log:check({ + a.freed(strings[4]), + a.freed(lis[4]), + a.freed(strings[5]), + a.freed(lis[5]), + a.freed(strings[9]), + a.freed(lis[9]), + a.freed(strings[10]), + a.freed(lis[10]), + }) lib.tv_list_watch_remove(l, lws[1]) lib.tv_list_watch_remove(l, lws[2]) @@ -678,6 +706,66 @@ describe('typval.c', function() eq({int(-100500), int(100500)}, typvalt2lua(l_tv)) end) end) + describe('tv()', function() + itp('works', function() + local l_tv = lua2typvalt(empty_list) + local l = l_tv.vval.v_list + + local l_l_tv = lua2typvalt(empty_list) + alloc_log:clear() + local l_l = l_l_tv.vval.v_list + eq(1, l_l.lv_refcount) + lib.tv_list_append_tv(l, l_l_tv) + eq(2, l_l.lv_refcount) + eq(l_l, l.lv_first.li_tv.vval.v_list) + alloc_log:check({ + a.li(l.lv_first), + }) + + local l_s_tv = lua2typvalt('test') + alloc_log:check({ + a.str(l_s_tv.vval.v_string, 'test'), + }) + lib.tv_list_append_tv(l, l_s_tv) + alloc_log:check({ + a.li(l.lv_last), + a.str(l.lv_last.li_tv.vval.v_string, 'test'), + }) + + eq({empty_list, 'test'}, typvalt2lua(l_tv)) + end) + end) + describe('owned tv()', function() + itp('works', function() + local l_tv = lua2typvalt(empty_list) + local l = l_tv.vval.v_list + + local l_l_tv = lua2typvalt(empty_list) + alloc_log:clear() + local l_l = l_l_tv.vval.v_list + eq(1, l_l.lv_refcount) + lib.tv_list_append_owned_tv(l, l_l_tv) + eq(1, l_l.lv_refcount) + l_l.lv_refcount = l_l.lv_refcount + 1 + eq(l_l, l.lv_first.li_tv.vval.v_list) + alloc_log:check({ + a.li(l.lv_first), + }) + + local l_s_tv = ffi.gc(lua2typvalt('test'), nil) + alloc_log:check({ + a.str(l_s_tv.vval.v_string, 'test'), + }) + lib.tv_list_append_owned_tv(l, l_s_tv) + eq(l_s_tv.vval.v_string, l.lv_last.li_tv.vval.v_string) + l_s_tv.vval.v_string = nil + alloc_log:check({ + a.li(l.lv_last), + }) + + eq({empty_list, 'test'}, typvalt2lua(l_tv)) + end) + end) end) describe('copy()', function() local function tv_list_copy(...) @@ -2319,7 +2407,7 @@ describe('typval.c', function() describe('list ret()', function() itp('works', function() local rettv = typvalt(lib.VAR_UNKNOWN) - local l = lib.tv_list_alloc_ret(rettv) + local l = lib.tv_list_alloc_ret(rettv, 0) eq(empty_list, typvalt2lua(rettv)) eq(rettv.vval.v_list, l) end) diff --git a/test/unit/formatc.lua b/test/unit/formatc.lua index e288081960..2fd37c599a 100644 --- a/test/unit/formatc.lua +++ b/test/unit/formatc.lua @@ -65,11 +65,12 @@ local tokens = P { "tokens"; identifier = Ct(C(R("az","AZ","__") * R("09","az","AZ","__")^0) * Cc"identifier"), -- Single character in a string - string_char = R("az","AZ","09") + S"$%^&*()_-+={[}]:;@~#<,>.!?/ \t" + (P"\\" * S[[ntvbrfa\?'"0x]]), + sstring_char = R("\001&","([","]\255") + (P"\\" * S[[ntvbrfa\?'"0x]]), + dstring_char = R("\001!","#[","]\255") + (P"\\" * S[[ntvbrfa\?'"0x]]), -- String literal - string = Ct(C(P"'" * (V"string_char" + P'"')^0 * P"'" + - P'"' * (V"string_char" + P"'")^0 * P'"') * Cc"string"), + string = Ct(C(P"'" * (V"sstring_char" + P'"')^0 * P"'" + + P'"' * (V"dstring_char" + P"'")^0 * P'"') * Cc"string"), -- Operator operator = Ct(C(P">>=" + P"<<=" + P"..." + diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index ef2ee9b8e9..f8143a0125 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -138,7 +138,7 @@ local function filter_complex_blocks(body) for line in body:gmatch("[^\r\n]+") do if not (string.find(line, "(^)", 1, true) ~= nil or string.find(line, "_ISwupper", 1, true) - or string.find(line, "_Float128") + or string.find(line, "_Float") or string.find(line, "msgpack_zone_push_finalizer") or string.find(line, "msgpack_unpacker_reserve_buffer") or string.find(line, "UUID_NULL") -- static const uuid_t UUID_NULL = {...} @@ -316,7 +316,7 @@ local function alloc_log_new() eq(exp, self.log) self:clear() end - function log:clear_tmp_allocs() + function log:clear_tmp_allocs(clear_null_frees) local toremove = {} local allocs = {} for i, v in ipairs(self.log) do @@ -328,6 +328,8 @@ local function alloc_log_new() if v.func == 'free' then toremove[#toremove + 1] = i end + elseif clear_null_frees and v.args[1] == self.null then + toremove[#toremove + 1] = i end if v.func == 'realloc' then allocs[tostring(v.ret)] = i @@ -528,9 +530,13 @@ local hook_numlen = 5 local hook_msglen = 1 + 1 + 1 + (1 + hook_fnamelen) + (1 + hook_sfnamelen) + (1 + hook_numlen) + 1 local tracehelp = dedent([[ + Trace: either in the format described below or custom debug output starting + with `>`. Latter lines still have the same width in byte. + ┌ Trace type: _r_eturn from function , function _c_all, _l_ine executed, │ _t_ail return, _C_ount (should not actually appear), - │ _s_aved from previous run for reference. + │ _s_aved from previous run for reference, _>_ for custom debug + │ output. │┠Function type: _L_ua function, _C_ function, _m_ain part of chunk, │┃ function that did _t_ail call. │┃┌ Function name type: _g_lobal, _l_ocal, _m_ethod, _f_ield, _u_pvalue, @@ -628,14 +634,24 @@ end local trace_end_msg = ('E%s\n'):format((' '):rep(hook_msglen - 2)) +local _debug_log + +local debug_log = only_separate(function(...) + return _debug_log(...) +end) + local function itp_child(wr, func) - init() - collectgarbage('stop') - child_sethook(wr) - local err, emsg = pcall(func) - collectgarbage('restart') - collectgarbage() - debug.sethook() + _debug_log = function(s) + s = s:sub(1, hook_msglen - 2) + sc.write(wr, '>' .. s .. (' '):rep(hook_msglen - 2 - #s) .. '\n') + end + local err, emsg = pcall(init) + if err then + collectgarbage('stop') + child_sethook(wr) + err, emsg = pcall(func) + debug.sethook() + end emsg = tostring(emsg) sc.write(wr, trace_end_msg) if not err then @@ -644,19 +660,21 @@ local function itp_child(wr, func) end sc.write(wr, ('-\n%05u\n%s'):format(#emsg, emsg)) deinit() - sc.close(wr) - sc.exit(1) else sc.write(wr, '+\n') deinit() - sc.close(wr) - sc.exit(0) end + collectgarbage('restart') + collectgarbage() + sc.write(wr, '$\n') + sc.close(wr) + sc.exit(err and 0 or 1) end local function check_child_err(rd) local trace = {} local did_traceline = false + local maxtrace = tonumber(os.getenv('NVIM_TEST_MAXTRACE')) or 1024 while true do local traceline = sc.read(rd, hook_msglen) if #traceline ~= hook_msglen then @@ -671,36 +689,48 @@ local function check_child_err(rd) break end trace[#trace + 1] = traceline + if #trace > maxtrace then + table.remove(trace, 1) + end end local res = sc.read(rd, 2) - if #res ~= 2 then - local error - if #trace == 0 then - error = '\nTest crashed, no trace available\n' - else - error = '\nTest crashed, trace:\n' .. tracehelp - for i = 1, #trace do - error = error .. trace[i] + if #res == 2 then + local err = '' + if res ~= '+\n' then + eq('-\n', res) + local len_s = sc.read(rd, 5) + local len = tonumber(len_s) + neq(0, len) + if os.getenv('NVIM_TEST_TRACE_ON_ERROR') == '1' and #trace ~= 0 then + err = '\nTest failed, trace:\n' .. tracehelp + for _, traceline in ipairs(trace) do + err = err .. traceline + end end + err = err .. sc.read(rd, len + 1) end - if not did_traceline then - error = error .. '\nNo end of trace occurred' + local eres = sc.read(rd, 2) + if eres ~= '$\n' then + if #trace == 0 then + err = '\nTest crashed, no trace available\n' + else + err = '\nTest crashed, trace:\n' .. tracehelp + for i = 1, #trace do + err = err .. trace[i] + end + end + if not did_traceline then + err = err .. '\nNo end of trace occurred' + end + local cc_err, cc_emsg = pcall(check_cores, Paths.test_luajit_prg, true) + if not cc_err then + err = err .. '\ncheck_cores failed: ' .. cc_emsg + end end - local cc_err, cc_emsg = pcall(check_cores, Paths.test_luajit_prg, true) - if not cc_err then - error = error .. '\ncheck_cores failed: ' .. cc_emsg + if err ~= '' then + assert.just_fail(err) end - assert.just_fail(error) end - if res == '+\n' then - return - end - eq('-\n', res) - local len_s = sc.read(rd, 5) - local len = tonumber(len_s) - neq(0, len) - local err = sc.read(rd, len + 1) - assert.just_fail(err) end local function itp_parent(rd, pid, allow_failure) @@ -754,6 +784,60 @@ end cimport('./src/nvim/types.h', './src/nvim/main.h', './src/nvim/os/time.h') +local function conv_enum(etab, eval) + local n = tonumber(eval) + return etab[n] or n +end + +local function array_size(arr) + return ffi.sizeof(arr) / ffi.sizeof(arr[0]) +end + +local function kvi_size(kvi) + return array_size(kvi.init_array) +end + +local function kvi_init(kvi) + kvi.capacity = kvi_size(kvi) + kvi.items = kvi.init_array + return kvi +end + +local function kvi_destroy(kvi) + if kvi.items ~= kvi.init_array then + lib.xfree(kvi.items) + end +end + +local function kvi_new(ct) + return kvi_init(ffi.new(ct)) +end + +local function make_enum_conv_tab(m, values, skip_pref, set_cb) + child_call_once(function() + local ret = {} + for _, v in ipairs(values) do + local str_v = v + if v:sub(1, #skip_pref) == skip_pref then + str_v = v:sub(#skip_pref + 1) + end + ret[tonumber(m[v])] = str_v + end + set_cb(ret) + end) +end + +local function ptr2addr(ptr) + return tonumber(ffi.cast('intptr_t', ffi.cast('void *', ptr))) +end + +local s = ffi.new('char[64]', {0}) + +local function ptr2key(ptr) + ffi.C.snprintf(s, ffi.sizeof(s), '%p', ffi.cast('void *', ptr)) + return ffi.string(s) +end + local module = { cimport = cimport, cppimport = cppimport, @@ -774,6 +858,16 @@ local module = { child_call_once = child_call_once, child_cleanup_once = child_cleanup_once, sc = sc, + conv_enum = conv_enum, + array_size = array_size, + kvi_destroy = kvi_destroy, + kvi_size = kvi_size, + kvi_init = kvi_init, + kvi_new = kvi_new, + make_enum_conv_tab = make_enum_conv_tab, + ptr2addr = ptr2addr, + ptr2key = ptr2key, + debug_log = debug_log, } return function() return module diff --git a/test/unit/keymap_spec.lua b/test/unit/keymap_spec.lua new file mode 100644 index 0000000000..595a19eb17 --- /dev/null +++ b/test/unit/keymap_spec.lua @@ -0,0 +1,71 @@ +local helpers = require("test.unit.helpers")(after_each) +local itp = helpers.gen_itp(it) + +local ffi = helpers.ffi +local eq = helpers.eq +local neq = helpers.neq + +local keymap = helpers.cimport("./src/nvim/keymap.h") + +describe('keymap.c', function() + + describe('find_special_key()', function() + local srcp = ffi.new('const unsigned char *[1]') + local modp = ffi.new('int[1]') + + itp('no keycode', function() + srcp[0] = 'abc' + eq(0, keymap.find_special_key(srcp, 3, modp, false, false, false)) + end) + + itp('keycode with multiple modifiers', function() + srcp[0] = '<C-M-S-A>' + neq(0, keymap.find_special_key(srcp, 9, modp, false, false, false)) + neq(0, modp[0]) + end) + + itp('case-insensitive', function() + -- Compare other capitalizations to this. + srcp[0] = '<C-A>' + local all_caps_key = + keymap.find_special_key(srcp, 5, modp, false, false, false) + local all_caps_mod = modp[0] + + srcp[0] = '<C-a>' + eq(all_caps_key, + keymap.find_special_key(srcp, 5, modp, false, false, false)) + eq(all_caps_mod, modp[0]) + + srcp[0] = '<c-A>' + eq(all_caps_key, + keymap.find_special_key(srcp, 5, modp, false, false, false)) + eq(all_caps_mod, modp[0]) + + srcp[0] = '<c-a>' + eq(all_caps_key, + keymap.find_special_key(srcp, 5, modp, false, false, false)) + eq(all_caps_mod, modp[0]) + end) + + itp('double-quote in keycode #7411', function() + -- Unescaped with in_string=false + srcp[0] = '<C-">' + eq(string.byte('"'), + keymap.find_special_key(srcp, 5, modp, false, false, false)) + + -- Unescaped with in_string=true + eq(0, keymap.find_special_key(srcp, 5, modp, false, false, true)) + + -- Escaped with in_string=false + srcp[0] = '<C-\\">' + -- Should fail because the key is invalid + -- (more than 1 non-modifier character). + eq(0, keymap.find_special_key(srcp, 6, modp, false, false, false)) + + -- Escaped with in_string=true + eq(string.byte('"'), + keymap.find_special_key(srcp, 6, modp, false, false, true)) + end) + end) + +end) diff --git a/test/unit/os/fs_spec.lua b/test/unit/os/fs_spec.lua index 667dbeed77..ae6dfe6423 100644 --- a/test/unit/os/fs_spec.lua +++ b/test/unit/os/fs_spec.lua @@ -199,7 +199,7 @@ describe('fs.c', function() itp('returns the absolute path when given an executable inside $PATH', function() local fullpath = exe('ls') - eq(1, fs.path_is_absolute_path(to_cstr(fullpath))) + eq(1, fs.path_is_absolute(to_cstr(fullpath))) end) itp('returns the absolute path when given an executable relative to the current dir', function() diff --git a/test/unit/path_spec.lua b/test/unit/path_spec.lua index ed597eaed7..e8ce660ce1 100644 --- a/test/unit/path_spec.lua +++ b/test/unit/path_spec.lua @@ -261,7 +261,7 @@ describe('path.c', function() end) end) -describe('path_shorten_fname_if_possible', function() +describe('path_try_shorten_fname', function() local cwd = lfs.currentdir() before_each(function() @@ -273,22 +273,22 @@ describe('path_shorten_fname_if_possible', function() lfs.rmdir('ut_directory') end) - describe('path_shorten_fname_if_possible', function() + describe('path_try_shorten_fname', function() itp('returns shortened path if possible', function() lfs.chdir('ut_directory') local full = to_cstr(lfs.currentdir() .. '/subdir/file.txt') - eq('subdir/file.txt', (ffi.string(cimp.path_shorten_fname_if_possible(full)))) + eq('subdir/file.txt', (ffi.string(cimp.path_try_shorten_fname(full)))) end) itp('returns `full_path` if a shorter version is not possible', function() local old = lfs.currentdir() lfs.chdir('ut_directory') local full = old .. '/subdir/file.txt' - eq(full, (ffi.string(cimp.path_shorten_fname_if_possible(to_cstr(full))))) + eq(full, (ffi.string(cimp.path_try_shorten_fname(to_cstr(full))))) end) itp('returns NULL if `full_path` is NULL', function() - eq(NULL, (cimp.path_shorten_fname_if_possible(NULL))) + eq(NULL, (cimp.path_try_shorten_fname(NULL))) end) end) end) @@ -585,22 +585,22 @@ describe('path.c', function() end) end) - describe('path_is_absolute_path', function() - local function path_is_absolute_path(filename) + describe('path_is_absolute', function() + local function path_is_absolute(filename) filename = to_cstr(filename) - return cimp.path_is_absolute_path(filename) + return cimp.path_is_absolute(filename) end itp('returns true if filename starts with a slash', function() - eq(OK, path_is_absolute_path('/some/directory/')) + eq(OK, path_is_absolute('/some/directory/')) end) itp('returns true if filename starts with a tilde', function() - eq(OK, path_is_absolute_path('~/in/my/home~/directory')) + eq(OK, path_is_absolute('~/in/my/home~/directory')) end) itp('returns false if filename starts not with slash nor tilde', function() - eq(FAIL, path_is_absolute_path('not/in/my/home~/directory')) + eq(FAIL, path_is_absolute('not/in/my/home~/directory')) end) end) end) diff --git a/test/unit/viml/expressions/lexer_spec.lua b/test/unit/viml/expressions/lexer_spec.lua new file mode 100644 index 0000000000..1b57a24ad5 --- /dev/null +++ b/test/unit/viml/expressions/lexer_spec.lua @@ -0,0 +1,428 @@ +local helpers = require('test.unit.helpers')(after_each) +local global_helpers = require('test.helpers') +local itp = helpers.gen_itp(it) +local viml_helpers = require('test.unit.viml.helpers') + +local child_call_once = helpers.child_call_once +local conv_enum = helpers.conv_enum +local cimport = helpers.cimport +local ffi = helpers.ffi +local eq = helpers.eq + +local conv_ccs = viml_helpers.conv_ccs +local new_pstate = viml_helpers.new_pstate +local conv_cmp_type = viml_helpers.conv_cmp_type +local pstate_set_str = viml_helpers.pstate_set_str +local conv_expr_asgn_type = viml_helpers.conv_expr_asgn_type + +local shallowcopy = global_helpers.shallowcopy +local intchar2lua = global_helpers.intchar2lua + +local lib = cimport('./src/nvim/viml/parser/expressions.h') + +local eltkn_type_tab, eltkn_mul_type_tab, eltkn_opt_scope_tab +child_call_once(function() + eltkn_type_tab = { + [tonumber(lib.kExprLexInvalid)] = 'Invalid', + [tonumber(lib.kExprLexMissing)] = 'Missing', + [tonumber(lib.kExprLexSpacing)] = 'Spacing', + [tonumber(lib.kExprLexEOC)] = 'EOC', + + [tonumber(lib.kExprLexQuestion)] = 'Question', + [tonumber(lib.kExprLexColon)] = 'Colon', + [tonumber(lib.kExprLexOr)] = 'Or', + [tonumber(lib.kExprLexAnd)] = 'And', + [tonumber(lib.kExprLexComparison)] = 'Comparison', + [tonumber(lib.kExprLexPlus)] = 'Plus', + [tonumber(lib.kExprLexMinus)] = 'Minus', + [tonumber(lib.kExprLexDot)] = 'Dot', + [tonumber(lib.kExprLexMultiplication)] = 'Multiplication', + + [tonumber(lib.kExprLexNot)] = 'Not', + + [tonumber(lib.kExprLexNumber)] = 'Number', + [tonumber(lib.kExprLexSingleQuotedString)] = 'SingleQuotedString', + [tonumber(lib.kExprLexDoubleQuotedString)] = 'DoubleQuotedString', + [tonumber(lib.kExprLexOption)] = 'Option', + [tonumber(lib.kExprLexRegister)] = 'Register', + [tonumber(lib.kExprLexEnv)] = 'Env', + [tonumber(lib.kExprLexPlainIdentifier)] = 'PlainIdentifier', + + [tonumber(lib.kExprLexBracket)] = 'Bracket', + [tonumber(lib.kExprLexFigureBrace)] = 'FigureBrace', + [tonumber(lib.kExprLexParenthesis)] = 'Parenthesis', + [tonumber(lib.kExprLexComma)] = 'Comma', + [tonumber(lib.kExprLexArrow)] = 'Arrow', + + [tonumber(lib.kExprLexAssignment)] = 'Assignment', + } + + eltkn_mul_type_tab = { + [tonumber(lib.kExprLexMulMul)] = 'Mul', + [tonumber(lib.kExprLexMulDiv)] = 'Div', + [tonumber(lib.kExprLexMulMod)] = 'Mod', + } + + eltkn_opt_scope_tab = { + [tonumber(lib.kExprOptScopeUnspecified)] = 'Unspecified', + [tonumber(lib.kExprOptScopeGlobal)] = 'Global', + [tonumber(lib.kExprOptScopeLocal)] = 'Local', + } +end) + +local function conv_eltkn_type(typ) + return conv_enum(eltkn_type_tab, typ) +end + +local bracket_types = { + Bracket = true, + FigureBrace = true, + Parenthesis = true, +} + +local function eltkn2lua(pstate, tkn) + local ret = { + type = conv_eltkn_type(tkn.type), + } + pstate_set_str(pstate, tkn.start, tkn.len, ret) + if not ret.error and (#(ret.str) ~= ret.len) then + ret.error = '#str /= len' + end + if ret.type == 'Comparison' then + ret.data = { + type = conv_cmp_type(tkn.data.cmp.type), + ccs = conv_ccs(tkn.data.cmp.ccs), + inv = (not not tkn.data.cmp.inv), + } + elseif ret.type == 'Multiplication' then + ret.data = { type = conv_enum(eltkn_mul_type_tab, tkn.data.mul.type) } + elseif bracket_types[ret.type] then + ret.data = { closing = (not not tkn.data.brc.closing) } + elseif ret.type == 'Register' then + ret.data = { name = intchar2lua(tkn.data.reg.name) } + elseif (ret.type == 'SingleQuotedString' + or ret.type == 'DoubleQuotedString') then + ret.data = { closed = (not not tkn.data.str.closed) } + elseif ret.type == 'Option' then + ret.data = { + scope = conv_enum(eltkn_opt_scope_tab, tkn.data.opt.scope), + name = ffi.string(tkn.data.opt.name, tkn.data.opt.len), + } + elseif ret.type == 'PlainIdentifier' then + ret.data = { + scope = intchar2lua(tkn.data.var.scope), + autoload = (not not tkn.data.var.autoload), + } + elseif ret.type == 'Number' then + ret.data = { + is_float = (not not tkn.data.num.is_float), + base = tonumber(tkn.data.num.base), + } + ret.data.val = tonumber(tkn.data.num.is_float + and tkn.data.num.val.floating + or tkn.data.num.val.integer) + elseif ret.type == 'Assignment' then + ret.data = { type = conv_expr_asgn_type(tkn.data.ass.type) } + elseif ret.type == 'Invalid' then + ret.data = { error = ffi.string(tkn.data.err.msg) } + end + return ret, tkn +end + +local function next_eltkn(pstate, flags) + return eltkn2lua(pstate, lib.viml_pexpr_next_token(pstate, flags)) +end + +describe('Expressions lexer', function() + local flags = 0 + local should_advance = true + local function check_advance(pstate, bytes_to_advance, initial_col) + local tgt = initial_col + bytes_to_advance + if should_advance then + if pstate.reader.lines.items[0].size == tgt then + eq(1, pstate.pos.line) + eq(0, pstate.pos.col) + else + eq(0, pstate.pos.line) + eq(tgt, pstate.pos.col) + end + else + eq(0, pstate.pos.line) + eq(initial_col, pstate.pos.col) + end + end + local function singl_eltkn_test(typ, str, data) + local pstate = new_pstate({str}) + eq({data=data, len=#str, start={col=0, line=0}, str=str, type=typ}, + next_eltkn(pstate, flags)) + check_advance(pstate, #str, 0) + if not ( + typ == 'Spacing' + or (typ == 'Register' and str == '@') + or ((typ == 'SingleQuotedString' or typ == 'DoubleQuotedString') + and not data.closed) + ) then + pstate = new_pstate({str .. ' '}) + eq({data=data, len=#str, start={col=0, line=0}, str=str, type=typ}, + next_eltkn(pstate, flags)) + check_advance(pstate, #str, 0) + end + pstate = new_pstate({'x' .. str}) + pstate.pos.col = 1 + eq({data=data, len=#str, start={col=1, line=0}, str=str, type=typ}, + next_eltkn(pstate, flags)) + check_advance(pstate, #str, 1) + end + local function scope_test(scope) + singl_eltkn_test('PlainIdentifier', scope .. ':test#var', {autoload=true, scope=scope}) + singl_eltkn_test('PlainIdentifier', scope .. ':', {autoload=false, scope=scope}) + end + local function comparison_test(op, inv_op, cmp_type) + singl_eltkn_test('Comparison', op, {type=cmp_type, inv=false, ccs='UseOption'}) + singl_eltkn_test('Comparison', inv_op, {type=cmp_type, inv=true, ccs='UseOption'}) + singl_eltkn_test('Comparison', op .. '#', {type=cmp_type, inv=false, ccs='MatchCase'}) + singl_eltkn_test('Comparison', inv_op .. '#', {type=cmp_type, inv=true, ccs='MatchCase'}) + singl_eltkn_test('Comparison', op .. '?', {type=cmp_type, inv=false, ccs='IgnoreCase'}) + singl_eltkn_test('Comparison', inv_op .. '?', {type=cmp_type, inv=true, ccs='IgnoreCase'}) + end + local function simple_test(pstate_arg, exp_type, exp_len, exp) + local pstate = new_pstate(pstate_arg) + exp = shallowcopy(exp) + exp.type = exp_type + exp.len = exp_len or #(pstate_arg[0]) + exp.start = { col = 0, line = 0 } + eq(exp, next_eltkn(pstate, flags)) + end + local function stable_tests() + singl_eltkn_test('Parenthesis', '(', {closing=false}) + singl_eltkn_test('Parenthesis', ')', {closing=true}) + singl_eltkn_test('Bracket', '[', {closing=false}) + singl_eltkn_test('Bracket', ']', {closing=true}) + singl_eltkn_test('FigureBrace', '{', {closing=false}) + singl_eltkn_test('FigureBrace', '}', {closing=true}) + singl_eltkn_test('Question', '?') + singl_eltkn_test('Colon', ':') + singl_eltkn_test('Dot', '.') + singl_eltkn_test('Assignment', '.=', {type='Concat'}) + singl_eltkn_test('Plus', '+') + singl_eltkn_test('Assignment', '+=', {type='Add'}) + singl_eltkn_test('Comma', ',') + singl_eltkn_test('Multiplication', '*', {type='Mul'}) + singl_eltkn_test('Multiplication', '/', {type='Div'}) + singl_eltkn_test('Multiplication', '%', {type='Mod'}) + singl_eltkn_test('Spacing', ' \t\t \t\t') + singl_eltkn_test('Spacing', ' ') + singl_eltkn_test('Spacing', '\t') + singl_eltkn_test('Invalid', '\x01\x02\x03', {error='E15: Invalid control character present in input: %.*s'}) + singl_eltkn_test('Number', '0123', {is_float=false, base=8, val=83}) + singl_eltkn_test('Number', '01234567', {is_float=false, base=8, val=342391}) + singl_eltkn_test('Number', '012345678', {is_float=false, base=10, val=12345678}) + singl_eltkn_test('Number', '0x123', {is_float=false, base=16, val=291}) + singl_eltkn_test('Number', '0x56FF', {is_float=false, base=16, val=22271}) + singl_eltkn_test('Number', '0xabcdef', {is_float=false, base=16, val=11259375}) + singl_eltkn_test('Number', '0xABCDEF', {is_float=false, base=16, val=11259375}) + singl_eltkn_test('Number', '0x0', {is_float=false, base=16, val=0}) + singl_eltkn_test('Number', '00', {is_float=false, base=8, val=0}) + singl_eltkn_test('Number', '0b0', {is_float=false, base=2, val=0}) + singl_eltkn_test('Number', '0b010111', {is_float=false, base=2, val=23}) + singl_eltkn_test('Number', '0b100111', {is_float=false, base=2, val=39}) + singl_eltkn_test('Number', '0', {is_float=false, base=10, val=0}) + singl_eltkn_test('Number', '9', {is_float=false, base=10, val=9}) + singl_eltkn_test('Env', '$abc') + singl_eltkn_test('Env', '$') + singl_eltkn_test('PlainIdentifier', 'test', {autoload=false, scope=0}) + singl_eltkn_test('PlainIdentifier', '_test', {autoload=false, scope=0}) + singl_eltkn_test('PlainIdentifier', '_test_foo', {autoload=false, scope=0}) + singl_eltkn_test('PlainIdentifier', 't', {autoload=false, scope=0}) + singl_eltkn_test('PlainIdentifier', 'test5', {autoload=false, scope=0}) + singl_eltkn_test('PlainIdentifier', 't0', {autoload=false, scope=0}) + singl_eltkn_test('PlainIdentifier', 'test#var', {autoload=true, scope=0}) + singl_eltkn_test('PlainIdentifier', 'test#var#val###', {autoload=true, scope=0}) + singl_eltkn_test('PlainIdentifier', 't#####', {autoload=true, scope=0}) + singl_eltkn_test('And', '&&') + singl_eltkn_test('Or', '||') + singl_eltkn_test('Invalid', '&', {error='E112: Option name missing: %.*s'}) + singl_eltkn_test('Option', '&opt', {scope='Unspecified', name='opt'}) + singl_eltkn_test('Option', '&t_xx', {scope='Unspecified', name='t_xx'}) + singl_eltkn_test('Option', '&t_\r\r', {scope='Unspecified', name='t_\r\r'}) + singl_eltkn_test('Option', '&t_\t\t', {scope='Unspecified', name='t_\t\t'}) + singl_eltkn_test('Option', '&t_ ', {scope='Unspecified', name='t_ '}) + singl_eltkn_test('Option', '&g:opt', {scope='Global', name='opt'}) + singl_eltkn_test('Option', '&l:opt', {scope='Local', name='opt'}) + singl_eltkn_test('Invalid', '&l:', {error='E112: Option name missing: %.*s'}) + singl_eltkn_test('Invalid', '&g:', {error='E112: Option name missing: %.*s'}) + singl_eltkn_test('Register', '@', {name=-1}) + singl_eltkn_test('Register', '@a', {name='a'}) + singl_eltkn_test('Register', '@\r', {name=13}) + singl_eltkn_test('Register', '@ ', {name=' '}) + singl_eltkn_test('Register', '@\t', {name=9}) + singl_eltkn_test('SingleQuotedString', '\'test', {closed=false}) + singl_eltkn_test('SingleQuotedString', '\'test\'', {closed=true}) + singl_eltkn_test('SingleQuotedString', '\'\'\'\'', {closed=true}) + singl_eltkn_test('SingleQuotedString', '\'x\'\'\'', {closed=true}) + singl_eltkn_test('SingleQuotedString', '\'\'\'x\'', {closed=true}) + singl_eltkn_test('SingleQuotedString', '\'\'\'', {closed=false}) + singl_eltkn_test('SingleQuotedString', '\'x\'\'', {closed=false}) + singl_eltkn_test('SingleQuotedString', '\'\'\'x', {closed=false}) + singl_eltkn_test('DoubleQuotedString', '"test', {closed=false}) + singl_eltkn_test('DoubleQuotedString', '"test"', {closed=true}) + singl_eltkn_test('DoubleQuotedString', '"\\""', {closed=true}) + singl_eltkn_test('DoubleQuotedString', '"x\\""', {closed=true}) + singl_eltkn_test('DoubleQuotedString', '"\\"x"', {closed=true}) + singl_eltkn_test('DoubleQuotedString', '"\\"', {closed=false}) + singl_eltkn_test('DoubleQuotedString', '"x\\"', {closed=false}) + singl_eltkn_test('DoubleQuotedString', '"\\"x', {closed=false}) + singl_eltkn_test('Not', '!') + singl_eltkn_test('Assignment', '=', {type='Plain'}) + comparison_test('==', '!=', 'Equal') + comparison_test('=~', '!~', 'Matches') + comparison_test('>', '<=', 'Greater') + comparison_test('>=', '<', 'GreaterOrEqual') + singl_eltkn_test('Minus', '-') + singl_eltkn_test('Assignment', '-=', {type='Subtract'}) + singl_eltkn_test('Arrow', '->') + singl_eltkn_test('Invalid', '~', {error='E15: Unidentified character: %.*s'}) + simple_test({{data=nil, size=0}}, 'EOC', 0, {error='start.col >= #pstr'}) + simple_test({''}, 'EOC', 0, {error='start.col >= #pstr'}) + simple_test({'2.'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2e5'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.x'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.2.'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.0x'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.0e'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.0e+'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.0e-'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.0e+x'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.0e-x'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.0e+1a'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.0e-1a'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'0b102'}, 'Number', 4, {data={is_float=false, base=2, val=2}, str='0b10'}) + simple_test({'10F'}, 'Number', 2, {data={is_float=false, base=10, val=10}, str='10'}) + simple_test({'0x0123456789ABCDEFG'}, 'Number', 18, {data={is_float=false, base=16, val=81985529216486895}, str='0x0123456789ABCDEF'}) + simple_test({{data='00', size=2}}, 'Number', 2, {data={is_float=false, base=8, val=0}, str='00'}) + simple_test({{data='009', size=2}}, 'Number', 2, {data={is_float=false, base=8, val=0}, str='00'}) + simple_test({{data='01', size=1}}, 'Number', 1, {data={is_float=false, base=10, val=0}, str='0'}) + end + + local function regular_scope_tests() + scope_test('s') + scope_test('g') + scope_test('v') + scope_test('b') + scope_test('w') + scope_test('t') + scope_test('l') + scope_test('a') + + simple_test({'g:'}, 'PlainIdentifier', 2, {data={scope='g', autoload=false}, str='g:'}) + simple_test({'g:is#foo'}, 'PlainIdentifier', 8, {data={scope='g', autoload=true}, str='g:is#foo'}) + simple_test({'g:isnot#foo'}, 'PlainIdentifier', 11, {data={scope='g', autoload=true}, str='g:isnot#foo'}) + end + + local function regular_is_tests() + comparison_test('is', 'isnot', 'Identical') + + simple_test({'is'}, 'Comparison', 2, {data={type='Identical', inv=false, ccs='UseOption'}, str='is'}) + simple_test({'isnot'}, 'Comparison', 5, {data={type='Identical', inv=true, ccs='UseOption'}, str='isnot'}) + simple_test({'is?'}, 'Comparison', 3, {data={type='Identical', inv=false, ccs='IgnoreCase'}, str='is?'}) + simple_test({'isnot?'}, 'Comparison', 6, {data={type='Identical', inv=true, ccs='IgnoreCase'}, str='isnot?'}) + simple_test({'is#'}, 'Comparison', 3, {data={type='Identical', inv=false, ccs='MatchCase'}, str='is#'}) + simple_test({'isnot#'}, 'Comparison', 6, {data={type='Identical', inv=true, ccs='MatchCase'}, str='isnot#'}) + simple_test({'is#foo'}, 'Comparison', 3, {data={type='Identical', inv=false, ccs='MatchCase'}, str='is#'}) + simple_test({'isnot#foo'}, 'Comparison', 6, {data={type='Identical', inv=true, ccs='MatchCase'}, str='isnot#'}) + end + + local function regular_number_tests() + simple_test({'2.0'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.0e5'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.0e+5'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({'2.0e-5'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + end + + local function regular_eoc_tests() + singl_eltkn_test('EOC', '|') + singl_eltkn_test('EOC', '\0') + singl_eltkn_test('EOC', '\n') + end + + itp('works (single tokens, zero flags)', function() + stable_tests() + + regular_eoc_tests() + regular_scope_tests() + regular_is_tests() + regular_number_tests() + end) + itp('peeks', function() + flags = tonumber(lib.kELFlagPeek) + should_advance = false + stable_tests() + + regular_eoc_tests() + regular_scope_tests() + regular_is_tests() + regular_number_tests() + end) + itp('forbids scope', function() + flags = tonumber(lib.kELFlagForbidScope) + stable_tests() + + regular_eoc_tests() + regular_is_tests() + regular_number_tests() + + simple_test({'g:'}, 'PlainIdentifier', 1, {data={scope=0, autoload=false}, str='g'}) + end) + itp('allows floats', function() + flags = tonumber(lib.kELFlagAllowFloat) + stable_tests() + + regular_eoc_tests() + regular_scope_tests() + regular_is_tests() + + simple_test({'2.2'}, 'Number', 3, {data={is_float=true, base=10, val=2.2}, str='2.2'}) + simple_test({'2.0e5'}, 'Number', 5, {data={is_float=true, base=10, val=2e5}, str='2.0e5'}) + simple_test({'2.0e+5'}, 'Number', 6, {data={is_float=true, base=10, val=2e5}, str='2.0e+5'}) + simple_test({'2.0e-5'}, 'Number', 6, {data={is_float=true, base=10, val=2e-5}, str='2.0e-5'}) + simple_test({'2.500000e-5'}, 'Number', 11, {data={is_float=true, base=10, val=2.5e-5}, str='2.500000e-5'}) + simple_test({'2.5555e2'}, 'Number', 8, {data={is_float=true, base=10, val=2.5555e2}, str='2.5555e2'}) + simple_test({'2.5555e+2'}, 'Number', 9, {data={is_float=true, base=10, val=2.5555e2}, str='2.5555e+2'}) + simple_test({'2.5555e-2'}, 'Number', 9, {data={is_float=true, base=10, val=2.5555e-2}, str='2.5555e-2'}) + simple_test({{data='2.5e-5', size=3}}, + 'Number', 3, {data={is_float=true, base=10, val=2.5}, str='2.5'}) + simple_test({{data='2.5e5', size=4}}, + 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'}) + simple_test({{data='2.5e-50', size=6}}, + 'Number', 6, {data={is_float=true, base=10, val=2.5e-5}, str='2.5e-5'}) + end) + itp('treats `is` as an identifier', function() + flags = tonumber(lib.kELFlagIsNotCmp) + stable_tests() + + regular_eoc_tests() + regular_scope_tests() + regular_number_tests() + + simple_test({'is'}, 'PlainIdentifier', 2, {data={scope=0, autoload=false}, str='is'}) + simple_test({'isnot'}, 'PlainIdentifier', 5, {data={scope=0, autoload=false}, str='isnot'}) + simple_test({'is?'}, 'PlainIdentifier', 2, {data={scope=0, autoload=false}, str='is'}) + simple_test({'isnot?'}, 'PlainIdentifier', 5, {data={scope=0, autoload=false}, str='isnot'}) + simple_test({'is#'}, 'PlainIdentifier', 3, {data={scope=0, autoload=true}, str='is#'}) + simple_test({'isnot#'}, 'PlainIdentifier', 6, {data={scope=0, autoload=true}, str='isnot#'}) + simple_test({'is#foo'}, 'PlainIdentifier', 6, {data={scope=0, autoload=true}, str='is#foo'}) + simple_test({'isnot#foo'}, 'PlainIdentifier', 9, {data={scope=0, autoload=true}, str='isnot#foo'}) + end) + itp('forbids EOC', function() + flags = tonumber(lib.kELFlagForbidEOC) + stable_tests() + + regular_scope_tests() + regular_is_tests() + regular_number_tests() + + singl_eltkn_test('Invalid', '|', {error='E15: Unexpected EOC character: %.*s'}) + singl_eltkn_test('Invalid', '\0', {error='E15: Unexpected EOC character: %.*s'}) + singl_eltkn_test('Invalid', '\n', {error='E15: Unexpected EOC character: %.*s'}) + end) +end) diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua new file mode 100644 index 0000000000..73388e5dd2 --- /dev/null +++ b/test/unit/viml/expressions/parser_spec.lua @@ -0,0 +1,540 @@ +local helpers = require('test.unit.helpers')(after_each) +local global_helpers = require('test.helpers') +local itp = helpers.gen_itp(it) +local viml_helpers = require('test.unit.viml.helpers') + +local make_enum_conv_tab = helpers.make_enum_conv_tab +local child_call_once = helpers.child_call_once +local alloc_log_new = helpers.alloc_log_new +local kvi_destroy = helpers.kvi_destroy +local conv_enum = helpers.conv_enum +local debug_log = helpers.debug_log +local ptr2key = helpers.ptr2key +local cimport = helpers.cimport +local ffi = helpers.ffi +local neq = helpers.neq +local eq = helpers.eq + +local conv_ccs = viml_helpers.conv_ccs +local new_pstate = viml_helpers.new_pstate +local conv_cmp_type = viml_helpers.conv_cmp_type +local pstate_set_str = viml_helpers.pstate_set_str +local conv_expr_asgn_type = viml_helpers.conv_expr_asgn_type + +local mergedicts_copy = global_helpers.mergedicts_copy +local format_string = global_helpers.format_string +local format_luav = global_helpers.format_luav +local intchar2lua = global_helpers.intchar2lua +local dictdiff = global_helpers.dictdiff + +local lib = cimport('./src/nvim/viml/parser/expressions.h', + './src/nvim/syntax.h') + +local alloc_log = alloc_log_new() + +local predefined_hl_defs = { + -- From highlight_init_both + Conceal=true, + Cursor=true, + lCursor=true, + DiffText=true, + ErrorMsg=true, + IncSearch=true, + ModeMsg=true, + NonText=true, + PmenuSbar=true, + StatusLine=true, + StatusLineNC=true, + TabLineFill=true, + TabLineSel=true, + TermCursor=true, + VertSplit=true, + WildMenu=true, + EndOfBuffer=true, + QuickFixLine=true, + Substitute=true, + Whitespace=true, + + -- From highlight_init_(dark|light) + ColorColumn=true, + CursorColumn=true, + CursorLine=true, + CursorLineNr=true, + DiffAdd=true, + DiffChange=true, + DiffDelete=true, + Directory=true, + FoldColumn=true, + Folded=true, + LineNr=true, + MatchParen=true, + MoreMsg=true, + Pmenu=true, + PmenuSel=true, + PmenuThumb=true, + Question=true, + Search=true, + SignColumn=true, + SpecialKey=true, + SpellBad=true, + SpellCap=true, + SpellLocal=true, + SpellRare=true, + TabLine=true, + Title=true, + Visual=true, + WarningMsg=true, + Normal=true, + + -- From syncolor.vim, if &background + Comment=true, + Constant=true, + Special=true, + Identifier=true, + Statement=true, + PreProc=true, + Type=true, + Underlined=true, + Ignore=true, + + -- From syncolor.vim, below if &background + Error=true, + Todo=true, + + -- From syncolor.vim, links at the bottom + String=true, + Character=true, + Number=true, + Boolean=true, + Float=true, + Function=true, + Conditional=true, + Repeat=true, + Label=true, + Operator=true, + Keyword=true, + Exception=true, + Include=true, + Define=true, + Macro=true, + PreCondit=true, + StorageClass=true, + Structure=true, + Typedef=true, + Tag=true, + SpecialChar=true, + Delimiter=true, + SpecialComment=true, + Debug=true, +} + +local nvim_hl_defs = {} + +child_call_once(function() + local i = 0 + while lib.highlight_init_cmdline[i] ~= nil do + local hl_args = lib.highlight_init_cmdline[i] + local s = ffi.string(hl_args) + local err, msg = pcall(function() + if s:sub(1, 13) == 'default link ' then + local new_grp, grp_link = s:match('^default link (%w+) (%w+)$') + neq(nil, new_grp) + -- Note: group to link to must be already defined at the time of + -- linking, otherwise it will be created as cleared. So existence + -- of the group is checked here and not in the next pass over + -- nvim_hl_defs. + eq(true, not not (nvim_hl_defs[grp_link] + or predefined_hl_defs[grp_link])) + eq(false, not not (nvim_hl_defs[new_grp] + or predefined_hl_defs[new_grp])) + nvim_hl_defs[new_grp] = {'link', grp_link} + else + local new_grp, grp_args = s:match('^(%w+) (.*)') + neq(nil, new_grp) + eq(false, not not (nvim_hl_defs[new_grp] + or predefined_hl_defs[new_grp])) + nvim_hl_defs[new_grp] = {'definition', grp_args} + end + end) + if not err then + msg = format_string( + 'Error while processing string %s at position %u:\n%s', s, i, msg) + error(msg) + end + i = i + 1 + end + for k, _ in ipairs(nvim_hl_defs) do + eq('Nvim', k:sub(1, 4)) + -- NvimInvalid + -- 12345678901 + local err, msg = pcall(function() + if k:sub(5, 11) == 'Invalid' then + neq(nil, nvim_hl_defs['Nvim' .. k:sub(12)]) + else + neq(nil, nvim_hl_defs['NvimInvalid' .. k:sub(5)]) + end + end) + if not err then + msg = format_string('Error while processing group %s:\n%s', k, msg) + error(msg) + end + end +end) + +local function hls_to_hl_fs(hls) + local ret = {} + local next_col = 0 + for i, v in ipairs(hls) do + local group, line, col, str = v:match('^Nvim([a-zA-Z]+):(%d+):(%d+):(.*)$') + col = tonumber(col) + line = tonumber(line) + assert(line == 0) + local col_shift = col - next_col + assert(col_shift >= 0) + next_col = col + #str + ret[i] = format_string('hl(%r, %r%s)', + group, + str, + (col_shift == 0 + and '' + or (', %u'):format(col_shift))) + end + return ret +end + +local function format_check(expr, format_check_data, opts) + -- That forces specific order. + local zflags = opts.flags[1] + local zdata = format_check_data[zflags] + local dig_len + if opts.funcname then + print(format_string('\n%s(%r, {', opts.funcname, expr)) + dig_len = #opts.funcname + 2 + else + print(format_string('\n_check_parsing(%r, %r, {', opts, expr)) + dig_len = #('_check_parsing(, \'') + #(format_string('%r', opts)) + end + local digits = ' --' .. (' '):rep(dig_len - #(' --')) + local digits2 = digits:sub(1, -10) + for i = 0, #expr - 1 do + if i % 10 == 0 then + digits2 = ('%s%10u'):format(digits2, i / 10) + end + digits = ('%s%u'):format(digits, i % 10) + end + print(digits) + if #expr > 10 then + print(digits2) + end + if zdata.ast.len then + print((' len = %u,'):format(zdata.ast.len)) + end + print(' ast = ' .. format_luav(zdata.ast.ast, ' ') .. ',') + if zdata.ast.err then + print(' err = {') + print(' arg = ' .. format_luav(zdata.ast.err.arg) .. ',') + print(' msg = ' .. format_luav(zdata.ast.err.msg) .. ',') + print(' },') + end + print('}, {') + for _, v in ipairs(zdata.hl_fs) do + print(' ' .. v .. ',') + end + local diffs = {} + local diffs_num = 0 + for flags, v in pairs(format_check_data) do + if flags ~= zflags then + diffs[flags] = dictdiff(zdata, v) + if diffs[flags] then + if flags == 3 + zflags then + if (dictdiff(format_check_data[1 + zflags], + format_check_data[3 + zflags]) == nil + or dictdiff(format_check_data[2 + zflags], + format_check_data[3 + zflags]) == nil) + then + diffs[flags] = nil + else + diffs_num = diffs_num + 1 + end + else + diffs_num = diffs_num + 1 + end + end + end + end + if diffs_num ~= 0 then + print('}, {') + local flags = 1 + while diffs_num ~= 0 do + if diffs[flags] then + diffs_num = diffs_num - 1 + local diff = diffs[flags] + print((' [%u] = {'):format(flags)) + if diff.ast then + print(' ast = ' .. format_luav(diff.ast, ' ') .. ',') + end + if diff.hl_fs then + print(' hl_fs = ' .. format_luav(diff.hl_fs, ' ', { + literal_strings=true + }) .. ',') + end + print(' },') + end + flags = flags + 1 + end + end + print('})') +end + +local east_node_type_tab +make_enum_conv_tab(lib, { + 'kExprNodeMissing', + 'kExprNodeOpMissing', + 'kExprNodeTernary', + 'kExprNodeTernaryValue', + 'kExprNodeRegister', + 'kExprNodeSubscript', + 'kExprNodeListLiteral', + 'kExprNodeUnaryPlus', + 'kExprNodeBinaryPlus', + 'kExprNodeNested', + 'kExprNodeCall', + 'kExprNodePlainIdentifier', + 'kExprNodePlainKey', + 'kExprNodeComplexIdentifier', + 'kExprNodeUnknownFigure', + 'kExprNodeLambda', + 'kExprNodeDictLiteral', + 'kExprNodeCurlyBracesIdentifier', + 'kExprNodeComma', + 'kExprNodeColon', + 'kExprNodeArrow', + 'kExprNodeComparison', + 'kExprNodeConcat', + 'kExprNodeConcatOrSubscript', + 'kExprNodeInteger', + 'kExprNodeFloat', + 'kExprNodeSingleQuotedString', + 'kExprNodeDoubleQuotedString', + 'kExprNodeOr', + 'kExprNodeAnd', + 'kExprNodeUnaryMinus', + 'kExprNodeBinaryMinus', + 'kExprNodeNot', + 'kExprNodeMultiplication', + 'kExprNodeDivision', + 'kExprNodeMod', + 'kExprNodeOption', + 'kExprNodeEnvironment', + 'kExprNodeAssignment', +}, 'kExprNode', function(ret) east_node_type_tab = ret end) + +local function conv_east_node_type(typ) + return conv_enum(east_node_type_tab, typ) +end + +local eastnodelist2lua + +local function eastnode2lua(pstate, eastnode, checked_nodes) + local key = ptr2key(eastnode) + if checked_nodes[key] then + checked_nodes[key].duplicate_key = key + return { duplicate = key } + end + local typ = conv_east_node_type(eastnode.type) + local ret = {} + checked_nodes[key] = ret + ret.children = eastnodelist2lua(pstate, eastnode.children, checked_nodes) + local str = pstate_set_str(pstate, eastnode.start, eastnode.len) + local ret_str + if str.error then + ret_str = 'error:' .. str.error + else + ret_str = ('%u:%u:%s'):format(str.start.line, str.start.col, str.str) + end + if typ == 'Register' then + typ = typ .. ('(name=%s)'):format( + tostring(intchar2lua(eastnode.data.reg.name))) + elseif typ == 'PlainIdentifier' then + typ = typ .. ('(scope=%s,ident=%s)'):format( + tostring(intchar2lua(eastnode.data.var.scope)), + ffi.string(eastnode.data.var.ident, eastnode.data.var.ident_len)) + elseif typ == 'PlainKey' then + typ = typ .. ('(key=%s)'):format( + ffi.string(eastnode.data.var.ident, eastnode.data.var.ident_len)) + elseif (typ == 'UnknownFigure' or typ == 'DictLiteral' + or typ == 'CurlyBracesIdentifier' or typ == 'Lambda') then + typ = typ .. ('(%s)'):format( + (eastnode.data.fig.type_guesses.allow_lambda and '\\' or '-') + .. (eastnode.data.fig.type_guesses.allow_dict and 'd' or '-') + .. (eastnode.data.fig.type_guesses.allow_ident and 'i' or '-')) + elseif typ == 'Comparison' then + typ = typ .. ('(type=%s,inv=%u,ccs=%s)'):format( + conv_cmp_type(eastnode.data.cmp.type), eastnode.data.cmp.inv and 1 or 0, + conv_ccs(eastnode.data.cmp.ccs)) + elseif typ == 'Integer' then + typ = typ .. ('(val=%u)'):format(tonumber(eastnode.data.num.value)) + elseif typ == 'Float' then + typ = typ .. format_string('(val=%e)', tonumber(eastnode.data.flt.value)) + elseif typ == 'SingleQuotedString' or typ == 'DoubleQuotedString' then + if eastnode.data.str.value == nil then + typ = typ .. '(val=NULL)' + else + local s = ffi.string(eastnode.data.str.value, eastnode.data.str.size) + typ = format_string('%s(val=%q)', typ, s) + end + elseif typ == 'Option' then + typ = ('%s(scope=%s,ident=%s)'):format( + typ, + tostring(intchar2lua(eastnode.data.opt.scope)), + ffi.string(eastnode.data.opt.ident, eastnode.data.opt.ident_len)) + elseif typ == 'Environment' then + typ = ('%s(ident=%s)'):format( + typ, + ffi.string(eastnode.data.env.ident, eastnode.data.env.ident_len)) + elseif typ == 'Assignment' then + typ = ('%s(%s)'):format(typ, conv_expr_asgn_type(eastnode.data.ass.type)) + end + ret_str = typ .. ':' .. ret_str + local can_simplify = not ret.children + if can_simplify then + ret = ret_str + else + ret[1] = ret_str + end + return ret +end + +eastnodelist2lua = function(pstate, eastnode, checked_nodes) + local ret = {} + while eastnode ~= nil do + ret[#ret + 1] = eastnode2lua(pstate, eastnode, checked_nodes) + eastnode = eastnode.next + end + if #ret == 0 then + ret = nil + end + return ret +end + +local function east2lua(str, pstate, east) + local checked_nodes = {} + local len = tonumber(pstate.pos.col) + if pstate.pos.line == 1 then + len = tonumber(pstate.reader.lines.items[0].size) + end + if type(str) == 'string' and len == #str then + len = nil + end + return { + err = east.err.msg ~= nil and { + msg = ffi.string(east.err.msg), + arg = ffi.string(east.err.arg, east.err.arg_len), + } or nil, + len = len, + ast = eastnodelist2lua(pstate, east.root, checked_nodes), + } +end + +local function phl2lua(pstate) + local ret = {} + for i = 0, (tonumber(pstate.colors.size) - 1) do + local chunk = pstate.colors.items[i] + local chunk_tbl = pstate_set_str( + pstate, chunk.start, chunk.end_col - chunk.start.col, { + group = ffi.string(chunk.group), + }) + ret[i + 1] = ('%s:%u:%u:%s'):format( + chunk_tbl.group, + chunk_tbl.start.line, + chunk_tbl.start.col, + chunk_tbl.str) + end + return ret +end + +child_call_once(function() + assert:set_parameter('TableFormatLevel', 1000000) +end) + +describe('Expressions parser', function() + local function _check_parsing(opts, str, exp_ast, exp_highlighting_fs, + nz_flags_exps) + local zflags = opts.flags[1] + nz_flags_exps = nz_flags_exps or {} + local format_check_data = {} + for _, flags in ipairs(opts.flags) do + debug_log(('Running test case (%s, %u)'):format(str, flags)) + local err, msg = pcall(function() + if os.getenv('NVIM_TEST_PARSER_SPEC_PRINT_TEST_CASE') == '1' then + print(str, flags) + end + alloc_log:check({}) + + local pstate = new_pstate({str}) + local east = lib.viml_pexpr_parse(pstate, flags) + local ast = east2lua(str, pstate, east) + local hls = phl2lua(pstate) + if exp_ast == nil then + format_check_data[flags] = {ast=ast, hl_fs=hls_to_hl_fs(hls)} + else + local exps = { + ast = exp_ast, + hl_fs = exp_highlighting_fs, + } + local add_exps = nz_flags_exps[flags] + if not add_exps and flags == 3 + zflags then + add_exps = nz_flags_exps[1 + zflags] or nz_flags_exps[2 + zflags] + end + if add_exps then + if add_exps.ast then + exps.ast = mergedicts_copy(exps.ast, add_exps.ast) + end + if add_exps.hl_fs then + exps.hl_fs = mergedicts_copy(exps.hl_fs, add_exps.hl_fs) + end + end + eq(exps.ast, ast) + if exp_highlighting_fs then + local exp_highlighting = {} + local next_col = 0 + for i, h in ipairs(exps.hl_fs) do + exp_highlighting[i], next_col = h(next_col) + end + eq(exp_highlighting, hls) + end + end + lib.viml_pexpr_free_ast(east) + kvi_destroy(pstate.colors) + alloc_log:clear_tmp_allocs(true) + alloc_log:check({}) + end) + if not err then + msg = format_string('Error while processing test (%r, %u):\n%s', + str, flags, msg) + error(msg) + end + end + if exp_ast == nil then + format_check(str, format_check_data, opts) + end + end + local function hl(group, str, shift) + return function(next_col) + if nvim_hl_defs['Nvim' .. group] == nil then + error(('Unknown group: Nvim%s'):format(group)) + end + local col = next_col + (shift or 0) + return (('%s:%u:%u:%s'):format( + 'Nvim' .. group, + 0, + col, + str)), (col + #str) + end + end + local function fmtn(typ, args, rest) + return ('%s(%s)%s'):format(typ, args, rest) + end + require('test.unit.viml.expressions.parser_tests')( + itp, _check_parsing, hl, fmtn) +end) diff --git a/test/unit/viml/expressions/parser_tests.lua b/test/unit/viml/expressions/parser_tests.lua new file mode 100644 index 0000000000..da61672bb1 --- /dev/null +++ b/test/unit/viml/expressions/parser_tests.lua @@ -0,0 +1,8317 @@ +local global_helpers = require('test.helpers') + +local REMOVE_THIS = global_helpers.REMOVE_THIS + +return function(itp, _check_parsing, hl, fmtn) + local function check_parsing(...) + return _check_parsing({flags={0, 1, 2, 3}, funcname='check_parsing'}, ...) + end + local function check_asgn_parsing(...) + return _check_parsing({ + flags={4, 5, 6, 7}, + funcname='check_asgn_parsing', + }, ...) + end + itp('works with + and @a', function() + check_parsing('@a', { + ast = { + 'Register(name=a):0:0:@a', + }, + }, { + hl('Register', '@a'), + }) + check_parsing('+@a', { + ast = { + { + 'UnaryPlus:0:0:+', + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + }, { + hl('UnaryPlus', '+'), + hl('Register', '@a'), + }) + check_parsing('@a+@b', { + ast = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + 'Register(name=b):0:3:@b', + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('Register', '@b'), + }) + check_parsing('@a+@b+@c', { + ast = { + { + 'BinaryPlus:0:5:+', + children = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + 'Register(name=b):0:3:@b', + }, + }, + 'Register(name=c):0:6:@c', + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('Register', '@b'), + hl('BinaryPlus', '+'), + hl('Register', '@c'), + }) + check_parsing('+@a+@b', { + ast = { + { + 'BinaryPlus:0:3:+', + children = { + { + 'UnaryPlus:0:0:+', + children = { + 'Register(name=a):0:1:@a', + }, + }, + 'Register(name=b):0:4:@b', + }, + }, + }, + }, { + hl('UnaryPlus', '+'), + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('Register', '@b'), + }) + check_parsing('+@a++@b', { + ast = { + { + 'BinaryPlus:0:3:+', + children = { + { + 'UnaryPlus:0:0:+', + children = { + 'Register(name=a):0:1:@a', + }, + }, + { + 'UnaryPlus:0:4:+', + children = { + 'Register(name=b):0:5:@b', + }, + }, + }, + }, + }, + }, { + hl('UnaryPlus', '+'), + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('UnaryPlus', '+'), + hl('Register', '@b'), + }) + check_parsing('@a@b', { + ast = { + { + 'OpMissing:0:2:', + children = { + 'Register(name=a):0:0:@a', + 'Register(name=b):0:2:@b', + }, + }, + }, + err = { + arg = '@b', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('Register', '@a'), + hl('InvalidRegister', '@b'), + }, { + [1] = { + ast = { + len = 2, + err = REMOVE_THIS, + ast = { + 'Register(name=a):0:0:@a' + }, + }, + hl_fs = { + [2] = REMOVE_THIS, + }, + }, + }) + check_parsing(' @a \t @b', { + ast = { + { + 'OpMissing:0:3:', + children = { + 'Register(name=a):0:0: @a', + 'Register(name=b):0:3: \t @b', + }, + }, + }, + err = { + arg = '@b', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('Register', '@a', 1), + hl('InvalidSpacing', ' \t '), + hl('Register', '@b'), + }, { + [1] = { + ast = { + len = 6, + err = REMOVE_THIS, + ast = { + 'Register(name=a):0:0: @a' + }, + }, + hl_fs = { + [2] = REMOVE_THIS, + [3] = REMOVE_THIS, + }, + }, + }) + check_parsing('+', { + ast = { + 'UnaryPlus:0:0:+', + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('UnaryPlus', '+'), + }) + check_parsing(' +', { + ast = { + 'UnaryPlus:0:0: +', + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('UnaryPlus', '+', 1), + }) + check_parsing('@a+ ', { + ast = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + }) + end) + itp('works with @a, + and parenthesis', function() + check_parsing('(@a)', { + ast = { + { + 'Nested:0:0:(', + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('NestingParenthesis', ')'), + }) + check_parsing('()', { + ast = { + { + 'Nested:0:0:(', + children = { + 'Missing:0:1:', + }, + }, + }, + err = { + arg = ')', + msg = 'E15: Expected value, got parenthesis: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('InvalidNestingParenthesis', ')'), + }) + check_parsing(')', { + ast = { + { + 'Nested:0:0:', + children = { + 'Missing:0:0:', + }, + }, + }, + err = { + arg = ')', + msg = 'E15: Expected value, got parenthesis: %.*s', + }, + }, { + hl('InvalidNestingParenthesis', ')'), + }) + check_parsing('+)', { + ast = { + { + 'Nested:0:1:', + children = { + { + 'UnaryPlus:0:0:+', + children = { + 'Missing:0:1:', + }, + }, + }, + }, + }, + err = { + arg = ')', + msg = 'E15: Expected value, got parenthesis: %.*s', + }, + }, { + hl('UnaryPlus', '+'), + hl('InvalidNestingParenthesis', ')'), + }) + check_parsing('+@a(@b)', { + ast = { + { + 'UnaryPlus:0:0:+', + children = { + { + 'Call:0:3:(', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:4:@b', + }, + }, + }, + }, + }, + }, { + hl('UnaryPlus', '+'), + hl('Register', '@a'), + hl('CallingParenthesis', '('), + hl('Register', '@b'), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a+@b(@c)', { + ast = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + { + 'Call:0:5:(', + children = { + 'Register(name=b):0:3:@b', + 'Register(name=c):0:6:@c', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('Register', '@b'), + hl('CallingParenthesis', '('), + hl('Register', '@c'), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a()', { + ast = { + { + 'Call:0:2:(', + children = { + 'Register(name=a):0:0:@a', + }, + }, + }, + }, { + hl('Register', '@a'), + hl('CallingParenthesis', '('), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a ()', { + ast = { + { + 'OpMissing:0:2:', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:2: (', + children = { + 'Missing:0:4:', + }, + }, + }, + }, + }, + err = { + arg = '()', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('Register', '@a'), + hl('InvalidSpacing', ' '), + hl('NestingParenthesis', '('), + hl('InvalidNestingParenthesis', ')'), + }, { + [1] = { + ast = { + len = 3, + err = REMOVE_THIS, + ast = { + 'Register(name=a):0:0:@a', + }, + }, + hl_fs = { + [2] = REMOVE_THIS, + [3] = REMOVE_THIS, + [4] = REMOVE_THIS, + }, + }, + }) + check_parsing('@a + (@b)', { + ast = { + { + 'BinaryPlus:0:2: +', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:4: (', + children = { + 'Register(name=b):0:6:@b', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('Register', '@b'), + hl('NestingParenthesis', ')'), + }) + check_parsing('@a + (+@b)', { + ast = { + { + 'BinaryPlus:0:2: +', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:4: (', + children = { + { + 'UnaryPlus:0:6:+', + children = { + 'Register(name=b):0:7:@b', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('UnaryPlus', '+'), + hl('Register', '@b'), + hl('NestingParenthesis', ')'), + }) + check_parsing('@a + (@b + @c)', { + ast = { + { + 'BinaryPlus:0:2: +', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:4: (', + children = { + { + 'BinaryPlus:0:8: +', + children = { + 'Register(name=b):0:6:@b', + 'Register(name=c):0:10: @c', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('Register', '@b'), + hl('BinaryPlus', '+', 1), + hl('Register', '@c', 1), + hl('NestingParenthesis', ')'), + }) + check_parsing('(@a)+@b', { + ast = { + { + 'BinaryPlus:0:4:+', + children = { + { + 'Nested:0:0:(', + children = { + 'Register(name=a):0:1:@a', + }, + }, + 'Register(name=b):0:5:@b', + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('NestingParenthesis', ')'), + hl('BinaryPlus', '+'), + hl('Register', '@b'), + }) + check_parsing('@a+(@b)(@c)', { + -- 01234567890 + ast = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + { + 'Call:0:7:(', + children = { + { + 'Nested:0:3:(', + children = { 'Register(name=b):0:4:@b' }, + }, + 'Register(name=c):0:8:@c', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('NestingParenthesis', '('), + hl('Register', '@b'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@c'), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a+((@b))(@c)', { + -- 01234567890123456890123456789 + -- 0 1 2 + ast = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + { + 'Call:0:9:(', + children = { + { + 'Nested:0:3:(', + children = { + { + 'Nested:0:4:(', + children = { 'Register(name=b):0:5:@b' } + }, + }, + }, + 'Register(name=c):0:10:@c', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('NestingParenthesis', '('), + hl('NestingParenthesis', '('), + hl('Register', '@b'), + hl('NestingParenthesis', ')'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@c'), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a+((@b))+@c', { + -- 01234567890123456890123456789 + -- 0 1 2 + ast = { + { + 'BinaryPlus:0:9:+', + children = { + { + 'BinaryPlus:0:2:+', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:3:(', + children = { + { + 'Nested:0:4:(', + children = { 'Register(name=b):0:5:@b' } + }, + }, + }, + }, + }, + 'Register(name=c):0:10:@c', + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('NestingParenthesis', '('), + hl('NestingParenthesis', '('), + hl('Register', '@b'), + hl('NestingParenthesis', ')'), + hl('NestingParenthesis', ')'), + hl('BinaryPlus', '+'), + hl('Register', '@c'), + }) + check_parsing( + '@a + (@b + @c) + @d(@e) + (+@f) + ((+@g(@h))(@j)(@k))(@l)', {--[[ + | | | | | | | | || | | || | | ||| || || || || + 000000000011111111112222222222333333333344444444445555555 + 012345678901234567890123456789012345678901234567890123456 + ]] + ast = {{ + 'BinaryPlus:0:31: +', + children = { + { + 'BinaryPlus:0:23: +', + children = { + { + 'BinaryPlus:0:14: +', + children = { + { + 'BinaryPlus:0:2: +', + children = { + 'Register(name=a):0:0:@a', + { + 'Nested:0:4: (', + children = { + { + 'BinaryPlus:0:8: +', + children = { + 'Register(name=b):0:6:@b', + 'Register(name=c):0:10: @c', + }, + }, + }, + }, + }, + }, + { + 'Call:0:19:(', + children = { + 'Register(name=d):0:16: @d', + 'Register(name=e):0:20:@e', + }, + }, + }, + }, + { + 'Nested:0:25: (', + children = { + { + 'UnaryPlus:0:27:+', + children = { + 'Register(name=f):0:28:@f', + }, + }, + }, + }, + }, + }, + { + 'Call:0:53:(', + children = { + { + 'Nested:0:33: (', + children = { + { + 'Call:0:48:(', + children = { + { + 'Call:0:44:(', + children = { + { + 'Nested:0:35:(', + children = { + { + 'UnaryPlus:0:36:+', + children = { + { + 'Call:0:39:(', + children = { + 'Register(name=g):0:37:@g', + 'Register(name=h):0:40:@h', + }, + }, + }, + }, + }, + }, + 'Register(name=j):0:45:@j', + }, + }, + 'Register(name=k):0:49:@k', + }, + }, + }, + }, + 'Register(name=l):0:54:@l', + }, + }, + }, + }}, + }, { + hl('Register', '@a'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('Register', '@b'), + hl('BinaryPlus', '+', 1), + hl('Register', '@c', 1), + hl('NestingParenthesis', ')'), + hl('BinaryPlus', '+', 1), + hl('Register', '@d', 1), + hl('CallingParenthesis', '('), + hl('Register', '@e'), + hl('CallingParenthesis', ')'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('UnaryPlus', '+'), + hl('Register', '@f'), + hl('NestingParenthesis', ')'), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('NestingParenthesis', '('), + hl('UnaryPlus', '+'), + hl('Register', '@g'), + hl('CallingParenthesis', '('), + hl('Register', '@h'), + hl('CallingParenthesis', ')'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@j'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@k'), + hl('CallingParenthesis', ')'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@l'), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a)', { + -- 012 + ast = { + { + 'Nested:0:2:', + children = { + 'Register(name=a):0:0:@a', + }, + }, + }, + err = { + arg = ')', + msg = 'E15: Unexpected closing parenthesis: %.*s', + }, + }, { + hl('Register', '@a'), + hl('InvalidNestingParenthesis', ')'), + }) + check_parsing('(@a', { + -- 012 + ast = { + { + 'Nested:0:0:(', + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + err = { + arg = '(@a', + msg = 'E110: Missing closing parenthesis for nested expression: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + }) + check_parsing('@a(@b', { + -- 01234 + ast = { + { + 'Call:0:2:(', + children = { + 'Register(name=a):0:0:@a', + 'Register(name=b):0:3:@b', + }, + }, + }, + err = { + arg = '(@b', + msg = 'E116: Missing closing parenthesis for function call: %.*s', + }, + }, { + hl('Register', '@a'), + hl('CallingParenthesis', '('), + hl('Register', '@b'), + }) + check_parsing('@a(@b, @c, @d, @e)', { + -- 012345678901234567 + -- 0 1 + ast = { + { + 'Call:0:2:(', + children = { + 'Register(name=a):0:0:@a', + { + 'Comma:0:5:,', + children = { + 'Register(name=b):0:3:@b', + { + 'Comma:0:9:,', + children = { + 'Register(name=c):0:6: @c', + { + 'Comma:0:13:,', + children = { + 'Register(name=d):0:10: @d', + 'Register(name=e):0:14: @e', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('CallingParenthesis', '('), + hl('Register', '@b'), + hl('Comma', ','), + hl('Register', '@c', 1), + hl('Comma', ','), + hl('Register', '@d', 1), + hl('Comma', ','), + hl('Register', '@e', 1), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a(@b(@c))', { + -- 01234567890123456789012345678901234567 + -- 0 1 2 3 + ast = { + { + 'Call:0:2:(', + children = { + 'Register(name=a):0:0:@a', + { + 'Call:0:5:(', + children = { + 'Register(name=b):0:3:@b', + 'Register(name=c):0:6:@c', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('CallingParenthesis', '('), + hl('Register', '@b'), + hl('CallingParenthesis', '('), + hl('Register', '@c'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', ')'), + }) + check_parsing('@a(@b(@c(@d(@e), @f(@g(@h), @i(@j)))))', { + -- 01234567890123456789012345678901234567 + -- 0 1 2 3 + ast = { + { + 'Call:0:2:(', + children = { + 'Register(name=a):0:0:@a', + { + 'Call:0:5:(', + children = { + 'Register(name=b):0:3:@b', + { + 'Call:0:8:(', + children = { + 'Register(name=c):0:6:@c', + { + 'Comma:0:15:,', + children = { + { + 'Call:0:11:(', + children = { + 'Register(name=d):0:9:@d', + 'Register(name=e):0:12:@e', + }, + }, + { + 'Call:0:19:(', + children = { + 'Register(name=f):0:16: @f', + { + 'Comma:0:26:,', + children = { + { + 'Call:0:22:(', + children = { + 'Register(name=g):0:20:@g', + 'Register(name=h):0:23:@h', + }, + }, + { + 'Call:0:30:(', + children = { + 'Register(name=i):0:27: @i', + 'Register(name=j):0:31:@j', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('CallingParenthesis', '('), + hl('Register', '@b'), + hl('CallingParenthesis', '('), + hl('Register', '@c'), + hl('CallingParenthesis', '('), + hl('Register', '@d'), + hl('CallingParenthesis', '('), + hl('Register', '@e'), + hl('CallingParenthesis', ')'), + hl('Comma', ','), + hl('Register', '@f', 1), + hl('CallingParenthesis', '('), + hl('Register', '@g'), + hl('CallingParenthesis', '('), + hl('Register', '@h'), + hl('CallingParenthesis', ')'), + hl('Comma', ','), + hl('Register', '@i', 1), + hl('CallingParenthesis', '('), + hl('Register', '@j'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', ')'), + }) + check_parsing('()()', { + -- 0123 + ast = { + { + 'Call:0:2:(', + children = { + { + 'Nested:0:0:(', + children = { + 'Missing:0:1:', + }, + }, + }, + }, + }, + err = { + arg = ')()', + msg = 'E15: Expected value, got parenthesis: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('InvalidNestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('CallingParenthesis', ')'), + }) + check_parsing('(@a)()', { + -- 012345 + ast = { + { + 'Call:0:4:(', + children = { + { + 'Nested:0:0:(', + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('CallingParenthesis', ')'), + }) + check_parsing('(@a)(@b)', { + -- 01234567 + ast = { + { + 'Call:0:4:(', + children = { + { + 'Nested:0:0:(', + children = { + 'Register(name=a):0:1:@a', + }, + }, + 'Register(name=b):0:5:@b', + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@b'), + hl('CallingParenthesis', ')'), + }) + check_parsing('(@a) (@b)', { + -- 012345678 + ast = { + { + 'OpMissing:0:4:', + children = { + { + 'Nested:0:0:(', + children = { + 'Register(name=a):0:1:@a', + }, + }, + { + 'Nested:0:4: (', + children = { + 'Register(name=b):0:6:@b', + }, + }, + }, + }, + }, + err = { + arg = '(@b)', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('NestingParenthesis', ')'), + hl('InvalidSpacing', ' '), + hl('NestingParenthesis', '('), + hl('Register', '@b'), + hl('NestingParenthesis', ')'), + }, { + [1] = { + ast = { + len = 5, + ast = { + { + 'Nested:0:0:(', + children = { + 'Register(name=a):0:1:@a', + REMOVE_THIS, + }, + }, + }, + err = REMOVE_THIS, + }, + hl_fs = { + [4] = REMOVE_THIS, + [5] = REMOVE_THIS, + [6] = REMOVE_THIS, + [7] = REMOVE_THIS, + }, + }, + }) + end) + itp('works with variable names, including curly braces ones', function() + check_parsing('var', { + ast = { + 'PlainIdentifier(scope=0,ident=var):0:0:var', + }, + }, { + hl('IdentifierName', 'var'), + }) + check_parsing('g:var', { + ast = { + 'PlainIdentifier(scope=g,ident=var):0:0:g:var', + }, + }, { + hl('IdentifierScope', 'g'), + hl('IdentifierScopeDelimiter', ':'), + hl('IdentifierName', 'var'), + }) + check_parsing('g:', { + ast = { + 'PlainIdentifier(scope=g,ident=):0:0:g:', + }, + }, { + hl('IdentifierScope', 'g'), + hl('IdentifierScopeDelimiter', ':'), + }) + check_parsing('{a}', { + -- 012 + ast = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + }, + }, { + hl('Curly', '{'), + hl('IdentifierName', 'a'), + hl('Curly', '}'), + }) + check_parsing('{a:b}', { + -- 012 + ast = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + 'PlainIdentifier(scope=a,ident=b):0:1:a:b', + }, + }, + }, + }, { + hl('Curly', '{'), + hl('IdentifierScope', 'a'), + hl('IdentifierScopeDelimiter', ':'), + hl('IdentifierName', 'b'), + hl('Curly', '}'), + }) + check_parsing('{a:@b}', { + -- 012345 + ast = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + { + 'OpMissing:0:3:', + children={ + 'PlainIdentifier(scope=a,ident=):0:1:a:', + 'Register(name=b):0:3:@b', + }, + }, + }, + }, + }, + err = { + arg = '@b}', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('Curly', '{'), + hl('IdentifierScope', 'a'), + hl('IdentifierScopeDelimiter', ':'), + hl('InvalidRegister', '@b'), + hl('Curly', '}'), + }) + check_parsing('{@a}', { + ast = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + }, { + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + }) + check_parsing('{@a}{@b}', { + -- 01234567 + ast = { + { + 'ComplexIdentifier:0:4:', + children = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + 'Register(name=a):0:1:@a', + }, + }, + { + fmtn('CurlyBracesIdentifier', '--i', ':0:4:{'), + children = { + 'Register(name=b):0:5:@b', + }, + }, + }, + }, + }, + }, { + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + hl('Curly', '{'), + hl('Register', '@b'), + hl('Curly', '}'), + }) + check_parsing('g:{@a}', { + -- 01234567 + ast = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=g,ident=):0:0:g:', + { + fmtn('CurlyBracesIdentifier', '--i', ':0:2:{'), + children = { + 'Register(name=a):0:3:@a', + }, + }, + }, + }, + }, + }, { + hl('IdentifierScope', 'g'), + hl('IdentifierScopeDelimiter', ':'), + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + }) + check_parsing('{@a}_test', { + -- 012345678 + ast = { + { + 'ComplexIdentifier:0:4:', + children = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + 'Register(name=a):0:1:@a', + }, + }, + 'PlainIdentifier(scope=0,ident=_test):0:4:_test', + }, + }, + }, + }, { + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + hl('IdentifierName', '_test'), + }) + check_parsing('g:{@a}_test', { + -- 01234567890 + ast = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=g,ident=):0:0:g:', + { + 'ComplexIdentifier:0:6:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:2:{'), + children = { + 'Register(name=a):0:3:@a', + }, + }, + 'PlainIdentifier(scope=0,ident=_test):0:6:_test', + }, + }, + }, + }, + }, + }, { + hl('IdentifierScope', 'g'), + hl('IdentifierScopeDelimiter', ':'), + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + hl('IdentifierName', '_test'), + }) + check_parsing('g:{@a}_test()', { + -- 0123456789012 + ast = { + { + 'Call:0:11:(', + children = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=g,ident=):0:0:g:', + { + 'ComplexIdentifier:0:6:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:2:{'), + children = { + 'Register(name=a):0:3:@a', + }, + }, + 'PlainIdentifier(scope=0,ident=_test):0:6:_test', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('IdentifierScope', 'g'), + hl('IdentifierScopeDelimiter', ':'), + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + hl('IdentifierName', '_test'), + hl('CallingParenthesis', '('), + hl('CallingParenthesis', ')'), + }) + check_parsing('{@a} ()', { + -- 0123456789012 + ast = { + { + 'Call:0:4: (', + children = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + }, + }, + }, { + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + hl('CallingParenthesis', '(', 1), + hl('CallingParenthesis', ')'), + }) + check_parsing('g:{@a} ()', { + -- 0123456789012 + ast = { + { + 'Call:0:6: (', + children = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=g,ident=):0:0:g:', + { + fmtn('CurlyBracesIdentifier', '--i', ':0:2:{'), + children = { + 'Register(name=a):0:3:@a', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('IdentifierScope', 'g'), + hl('IdentifierScopeDelimiter', ':'), + hl('Curly', '{'), + hl('Register', '@a'), + hl('Curly', '}'), + hl('CallingParenthesis', '(', 1), + hl('CallingParenthesis', ')'), + }) + check_parsing('{@a', { + -- 012 + ast = { + { + fmtn('UnknownFigure', '-di', ':0:0:{'), + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + err = { + arg = '{@a', + msg = 'E15: Missing closing figure brace: %.*s', + }, + }, { + hl('FigureBrace', '{'), + hl('Register', '@a'), + }) + check_parsing('a ()', { + -- 0123 + ast = { + { + 'Call:0:1: (', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('CallingParenthesis', '(', 1), + hl('CallingParenthesis', ')'), + }) + end) + itp('works with lambdas and dictionaries', function() + check_parsing('{}', { + ast = { + fmtn('DictLiteral', '-di', ':0:0:{'), + }, + }, { + hl('Dict', '{'), + hl('Dict', '}'), + }) + check_parsing('{->@a}', { + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + { + 'Arrow:0:1:->', + children = { + 'Register(name=a):0:3:@a', + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + }) + check_parsing('{->@a+@b}', { + -- 012345678 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + { + 'Arrow:0:1:->', + children = { + { + 'BinaryPlus:0:5:+', + children = { + 'Register(name=a):0:3:@a', + 'Register(name=b):0:6:@b', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('BinaryPlus', '+'), + hl('Register', '@b'), + hl('Lambda', '}'), + }) + check_parsing('{a->@a}', { + -- 012345678 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Arrow:0:2:->', + children = { + 'Register(name=a):0:4:@a', + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + }) + check_parsing('{a,b->@a}', { + -- 012345678 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + { + 'Arrow:0:4:->', + children = { + 'Register(name=a):0:6:@a', + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + }) + check_parsing('{a,b,c->@a}', { + -- 01234567890 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Comma:0:4:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3:b', + 'PlainIdentifier(scope=0,ident=c):0:5:c', + }, + }, + }, + }, + { + 'Arrow:0:6:->', + children = { + 'Register(name=a):0:8:@a', + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('Comma', ','), + hl('IdentifierName', 'c'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + }) + check_parsing('{a,b,c,d->@a}', { + -- 0123456789012 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Comma:0:4:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3:b', + { + 'Comma:0:6:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:5:c', + 'PlainIdentifier(scope=0,ident=d):0:7:d', + }, + }, + }, + }, + }, + }, + { + 'Arrow:0:8:->', + children = { + 'Register(name=a):0:10:@a', + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('Comma', ','), + hl('IdentifierName', 'c'), + hl('Comma', ','), + hl('IdentifierName', 'd'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + }) + check_parsing('{a,b,c,d,->@a}', { + -- 01234567890123 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Comma:0:4:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3:b', + { + 'Comma:0:6:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:5:c', + { + 'Comma:0:8:,', + children = { + 'PlainIdentifier(scope=0,ident=d):0:7:d', + }, + }, + }, + }, + }, + }, + }, + }, + { + 'Arrow:0:9:->', + children = { + 'Register(name=a):0:11:@a', + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('Comma', ','), + hl('IdentifierName', 'c'), + hl('Comma', ','), + hl('IdentifierName', 'd'), + hl('Comma', ','), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + }) + check_parsing('{a,b->{c,d->{e,f->@a}}}', { + -- 01234567890123456789012 + -- 0 1 2 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + { + 'Arrow:0:4:->', + children = { + { + fmtn('Lambda', '\\di', ':0:6:{'), + children = { + { + 'Comma:0:8:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:7:c', + 'PlainIdentifier(scope=0,ident=d):0:9:d', + }, + }, + { + 'Arrow:0:10:->', + children = { + { + fmtn('Lambda', '\\di', ':0:12:{'), + children = { + { + 'Comma:0:14:,', + children = { + 'PlainIdentifier(scope=0,ident=e):0:13:e', + 'PlainIdentifier(scope=0,ident=f):0:15:f', + }, + }, + { + 'Arrow:0:16:->', + children = { + 'Register(name=a):0:18:@a', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('Arrow', '->'), + hl('Lambda', '{'), + hl('IdentifierName', 'c'), + hl('Comma', ','), + hl('IdentifierName', 'd'), + hl('Arrow', '->'), + hl('Lambda', '{'), + hl('IdentifierName', 'e'), + hl('Comma', ','), + hl('IdentifierName', 'f'), + hl('Arrow', '->'), + hl('Register', '@a'), + hl('Lambda', '}'), + hl('Lambda', '}'), + hl('Lambda', '}'), + }) + check_parsing('{a,b->c,d}', { + -- 0123456789 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + { + 'Arrow:0:4:->', + children = { + { + 'Comma:0:7:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:6:c', + 'PlainIdentifier(scope=0,ident=d):0:8:d', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = ',d}', + msg = 'E15: Comma outside of call, lambda or literal: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('Arrow', '->'), + hl('IdentifierName', 'c'), + hl('InvalidComma', ','), + hl('IdentifierName', 'd'), + hl('Lambda', '}'), + }) + check_parsing('a,b,c,d', { + -- 0123456789 + ast = { + { + 'Comma:0:1:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Comma:0:3:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + { + 'Comma:0:5:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:4:c', + 'PlainIdentifier(scope=0,ident=d):0:6:d', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = ',b,c,d', + msg = 'E15: Comma outside of call, lambda or literal: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('InvalidComma', ','), + hl('IdentifierName', 'b'), + hl('InvalidComma', ','), + hl('IdentifierName', 'c'), + hl('InvalidComma', ','), + hl('IdentifierName', 'd'), + }) + check_parsing('a,b,c,d,', { + -- 0123456789 + ast = { + { + 'Comma:0:1:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Comma:0:3:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + { + 'Comma:0:5:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:4:c', + { + 'Comma:0:7:,', + children = { + 'PlainIdentifier(scope=0,ident=d):0:6:d', + }, + }, + }, + }, + }, + }, + }, + }, + }, + err = { + arg = ',b,c,d,', + msg = 'E15: Comma outside of call, lambda or literal: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('InvalidComma', ','), + hl('IdentifierName', 'b'), + hl('InvalidComma', ','), + hl('IdentifierName', 'c'), + hl('InvalidComma', ','), + hl('IdentifierName', 'd'), + hl('InvalidComma', ','), + }) + check_parsing(',', { + -- 0123456789 + ast = { + { + 'Comma:0:0:,', + children = { + 'Missing:0:0:', + }, + }, + }, + err = { + arg = ',', + msg = 'E15: Expected value, got comma: %.*s', + }, + }, { + hl('InvalidComma', ','), + }) + check_parsing('{,a->@a}', { + -- 0123456789 + ast = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + { + 'Arrow:0:3:->', + children = { + { + 'Comma:0:1:,', + children = { + 'Missing:0:1:', + 'PlainIdentifier(scope=0,ident=a):0:2:a', + }, + }, + 'Register(name=a):0:5:@a', + }, + }, + }, + }, + }, + err = { + arg = ',a->@a}', + msg = 'E15: Expected value, got comma: %.*s', + }, + }, { + hl('Curly', '{'), + hl('InvalidComma', ','), + hl('IdentifierName', 'a'), + hl('InvalidArrow', '->'), + hl('Register', '@a'), + hl('Curly', '}'), + }) + check_parsing('}', { + -- 0123456789 + ast = { + fmtn('UnknownFigure', '---', ':0:0:'), + }, + err = { + arg = '}', + msg = 'E15: Unexpected closing figure brace: %.*s', + }, + }, { + hl('InvalidFigureBrace', '}'), + }) + check_parsing('{->}', { + -- 0123456789 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + 'Arrow:0:1:->', + }, + }, + }, + err = { + arg = '}', + msg = 'E15: Expected value, got closing figure brace: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('Arrow', '->'), + hl('InvalidLambda', '}'), + }) + check_parsing('{a,b}', { + -- 0123456789 + ast = { + { + fmtn('Lambda', '-di', ':0:0:{'), + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + }, + }, + }, + err = { + arg = '}', + msg = 'E15: Expected lambda arguments list or arrow: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('InvalidLambda', '}'), + }) + check_parsing('{a,}', { + -- 0123456789 + ast = { + { + fmtn('Lambda', '-di', ':0:0:{'), + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + }, + }, + }, + err = { + arg = '}', + msg = 'E15: Expected lambda arguments list or arrow: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('InvalidLambda', '}'), + }) + check_parsing('{@a:@b}', { + -- 0123456789 + ast = { + { + fmtn('DictLiteral', '-di', ':0:0:{'), + children = { + { + 'Colon:0:3::', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:4:@b', + }, + }, + }, + }, + }, + }, { + hl('Dict', '{'), + hl('Register', '@a'), + hl('Colon', ':'), + hl('Register', '@b'), + hl('Dict', '}'), + }) + check_parsing('{@a:@b,@c:@d}', { + -- 0123456789012 + -- 0 1 + ast = { + { + fmtn('DictLiteral', '-di', ':0:0:{'), + children = { + { + 'Comma:0:6:,', + children = { + { + 'Colon:0:3::', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:4:@b', + }, + }, + { + 'Colon:0:9::', + children = { + 'Register(name=c):0:7:@c', + 'Register(name=d):0:10:@d', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Dict', '{'), + hl('Register', '@a'), + hl('Colon', ':'), + hl('Register', '@b'), + hl('Comma', ','), + hl('Register', '@c'), + hl('Colon', ':'), + hl('Register', '@d'), + hl('Dict', '}'), + }) + check_parsing('{@a:@b,@c:@d,@e:@f,}', { + -- 01234567890123456789 + -- 0 1 + ast = { + { + fmtn('DictLiteral', '-di', ':0:0:{'), + children = { + { + 'Comma:0:6:,', + children = { + { + 'Colon:0:3::', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:4:@b', + }, + }, + { + 'Comma:0:12:,', + children = { + { + 'Colon:0:9::', + children = { + 'Register(name=c):0:7:@c', + 'Register(name=d):0:10:@d', + }, + }, + { + 'Comma:0:18:,', + children = { + { + 'Colon:0:15::', + children = { + 'Register(name=e):0:13:@e', + 'Register(name=f):0:16:@f', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Dict', '{'), + hl('Register', '@a'), + hl('Colon', ':'), + hl('Register', '@b'), + hl('Comma', ','), + hl('Register', '@c'), + hl('Colon', ':'), + hl('Register', '@d'), + hl('Comma', ','), + hl('Register', '@e'), + hl('Colon', ':'), + hl('Register', '@f'), + hl('Comma', ','), + hl('Dict', '}'), + }) + check_parsing('{@a:@b,@c:@d,@e:@f,@g:}', { + -- 01234567890123456789012 + -- 0 1 2 + ast = { + { + fmtn('DictLiteral', '-di', ':0:0:{'), + children = { + { + 'Comma:0:6:,', + children = { + { + 'Colon:0:3::', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:4:@b', + }, + }, + { + 'Comma:0:12:,', + children = { + { + 'Colon:0:9::', + children = { + 'Register(name=c):0:7:@c', + 'Register(name=d):0:10:@d', + }, + }, + { + 'Comma:0:18:,', + children = { + { + 'Colon:0:15::', + children = { + 'Register(name=e):0:13:@e', + 'Register(name=f):0:16:@f', + }, + }, + { + 'Colon:0:21::', + children = { + 'Register(name=g):0:19:@g', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '}', + msg = 'E15: Expected value, got closing figure brace: %.*s', + }, + }, { + hl('Dict', '{'), + hl('Register', '@a'), + hl('Colon', ':'), + hl('Register', '@b'), + hl('Comma', ','), + hl('Register', '@c'), + hl('Colon', ':'), + hl('Register', '@d'), + hl('Comma', ','), + hl('Register', '@e'), + hl('Colon', ':'), + hl('Register', '@f'), + hl('Comma', ','), + hl('Register', '@g'), + hl('Colon', ':'), + hl('InvalidDict', '}'), + }) + check_parsing('{@a:@b,}', { + -- 01234567890123 + -- 0 1 + ast = { + { + fmtn('DictLiteral', '-di', ':0:0:{'), + children = { + { + 'Comma:0:6:,', + children = { + { + 'Colon:0:3::', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:4:@b', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Dict', '{'), + hl('Register', '@a'), + hl('Colon', ':'), + hl('Register', '@b'), + hl('Comma', ','), + hl('Dict', '}'), + }) + check_parsing('{({f -> g})(@h)(@i)}', { + -- 01234567890123456789 + -- 0 1 + ast = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + { + 'Call:0:15:(', + children = { + { + 'Call:0:11:(', + children = { + { + 'Nested:0:1:(', + children = { + { + fmtn('Lambda', '\\di', ':0:2:{'), + children = { + 'PlainIdentifier(scope=0,ident=f):0:3:f', + { + 'Arrow:0:4: ->', + children = { + 'PlainIdentifier(scope=0,ident=g):0:7: g', + }, + }, + }, + }, + }, + }, + 'Register(name=h):0:12:@h', + }, + }, + 'Register(name=i):0:16:@i', + }, + }, + }, + }, + }, + }, { + hl('Curly', '{'), + hl('NestingParenthesis', '('), + hl('Lambda', '{'), + hl('IdentifierName', 'f'), + hl('Arrow', '->', 1), + hl('IdentifierName', 'g', 1), + hl('Lambda', '}'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@h'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@i'), + hl('CallingParenthesis', ')'), + hl('Curly', '}'), + }) + check_parsing('a:{b()}c', { + -- 01234567 + ast = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=a,ident=):0:0:a:', + { + 'ComplexIdentifier:0:7:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:2:{'), + children = { + { + 'Call:0:4:(', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + }, + }, + 'PlainIdentifier(scope=0,ident=c):0:7:c', + }, + }, + }, + }, + }, + }, { + hl('IdentifierScope', 'a'), + hl('IdentifierScopeDelimiter', ':'), + hl('Curly', '{'), + hl('IdentifierName', 'b'), + hl('CallingParenthesis', '('), + hl('CallingParenthesis', ')'), + hl('Curly', '}'), + hl('IdentifierName', 'c'), + }) + check_parsing('a:{{b, c -> @d + @e + ({f -> g})(@h)}(@i)}j', { + -- 01234567890123456789012345678901234567890123456 + -- 0 1 2 3 4 + ast = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=a,ident=):0:0:a:', + { + 'ComplexIdentifier:0:42:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:2:{'), + children = { + { + 'Call:0:37:(', + children = { + { + fmtn('Lambda', '\\di', ':0:3:{'), + children = { + { + 'Comma:0:5:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:4:b', + 'PlainIdentifier(scope=0,ident=c):0:6: c', + }, + }, + { + 'Arrow:0:8: ->', + children = { + { + 'BinaryPlus:0:19: +', + children = { + { + 'BinaryPlus:0:14: +', + children = { + 'Register(name=d):0:11: @d', + 'Register(name=e):0:16: @e', + }, + }, + { + 'Call:0:32:(', + children = { + { + 'Nested:0:21: (', + children = { + { + fmtn('Lambda', '\\di', ':0:23:{'), + children = { + 'PlainIdentifier(scope=0,ident=f):0:24:f', + { + 'Arrow:0:25: ->', + children = { + 'PlainIdentifier(scope=0,ident=g):0:28: g', + }, + }, + }, + }, + }, + }, + 'Register(name=h):0:33:@h', + }, + }, + }, + }, + }, + }, + }, + }, + 'Register(name=i):0:38:@i', + }, + }, + }, + }, + 'PlainIdentifier(scope=0,ident=j):0:42:j', + }, + }, + }, + }, + }, + }, { + hl('IdentifierScope', 'a'), + hl('IdentifierScopeDelimiter', ':'), + hl('Curly', '{'), + hl('Lambda', '{'), + hl('IdentifierName', 'b'), + hl('Comma', ','), + hl('IdentifierName', 'c', 1), + hl('Arrow', '->', 1), + hl('Register', '@d', 1), + hl('BinaryPlus', '+', 1), + hl('Register', '@e', 1), + hl('BinaryPlus', '+', 1), + hl('NestingParenthesis', '(', 1), + hl('Lambda', '{'), + hl('IdentifierName', 'f'), + hl('Arrow', '->', 1), + hl('IdentifierName', 'g', 1), + hl('Lambda', '}'), + hl('NestingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('Register', '@h'), + hl('CallingParenthesis', ')'), + hl('Lambda', '}'), + hl('CallingParenthesis', '('), + hl('Register', '@i'), + hl('CallingParenthesis', ')'), + hl('Curly', '}'), + hl('IdentifierName', 'j'), + }) + check_parsing('{@a + @b : @c + @d, @e + @f : @g + @i}', { + -- 01234567890123456789012345678901234567 + -- 0 1 2 3 + ast = { + { + fmtn('DictLiteral', '-di', ':0:0:{'), + children = { + { + 'Comma:0:18:,', + children = { + { + 'Colon:0:8: :', + children = { + { + 'BinaryPlus:0:3: +', + children = { + 'Register(name=a):0:1:@a', + 'Register(name=b):0:5: @b', + }, + }, + { + 'BinaryPlus:0:13: +', + children = { + 'Register(name=c):0:10: @c', + 'Register(name=d):0:15: @d', + }, + }, + }, + }, + { + 'Colon:0:27: :', + children = { + { + 'BinaryPlus:0:22: +', + children = { + 'Register(name=e):0:19: @e', + 'Register(name=f):0:24: @f', + }, + }, + { + 'BinaryPlus:0:32: +', + children = { + 'Register(name=g):0:29: @g', + 'Register(name=i):0:34: @i', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Dict', '{'), + hl('Register', '@a'), + hl('BinaryPlus', '+', 1), + hl('Register', '@b', 1), + hl('Colon', ':', 1), + hl('Register', '@c', 1), + hl('BinaryPlus', '+', 1), + hl('Register', '@d', 1), + hl('Comma', ','), + hl('Register', '@e', 1), + hl('BinaryPlus', '+', 1), + hl('Register', '@f', 1), + hl('Colon', ':', 1), + hl('Register', '@g', 1), + hl('BinaryPlus', '+', 1), + hl('Register', '@i', 1), + hl('Dict', '}'), + }) + check_parsing('-> -> ->', { + -- 01234567 + ast = { + { + 'Arrow:0:0:->', + children = { + 'Missing:0:0:', + { + 'Arrow:0:2: ->', + children = { + 'Missing:0:2:', + { + 'Arrow:0:5: ->', + children = { + 'Missing:0:5:', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '-> -> ->', + msg = 'E15: Unexpected arrow: %.*s', + }, + }, { + hl('InvalidArrow', '->'), + hl('InvalidArrow', '->', 1), + hl('InvalidArrow', '->', 1), + }) + check_parsing('a -> b -> c -> d', { + -- 0123456789012345 + -- 0 1 + ast = { + { + 'Arrow:0:1: ->', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Arrow:0:6: ->', + children = { + 'PlainIdentifier(scope=0,ident=b):0:4: b', + { + 'Arrow:0:11: ->', + children = { + 'PlainIdentifier(scope=0,ident=c):0:9: c', + 'PlainIdentifier(scope=0,ident=d):0:14: d', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '-> b -> c -> d', + msg = 'E15: Arrow outside of lambda: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('InvalidArrow', '->', 1), + hl('IdentifierName', 'b', 1), + hl('InvalidArrow', '->', 1), + hl('IdentifierName', 'c', 1), + hl('InvalidArrow', '->', 1), + hl('IdentifierName', 'd', 1), + }) + check_parsing('{a -> b -> c}', { + -- 0123456789012 + -- 0 1 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Arrow:0:2: ->', + children = { + { + 'Arrow:0:7: ->', + children = { + 'PlainIdentifier(scope=0,ident=b):0:5: b', + 'PlainIdentifier(scope=0,ident=c):0:10: c', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '-> c}', + msg = 'E15: Arrow outside of lambda: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Arrow', '->', 1), + hl('IdentifierName', 'b', 1), + hl('InvalidArrow', '->', 1), + hl('IdentifierName', 'c', 1), + hl('Lambda', '}'), + }) + check_parsing('{a: -> b}', { + -- 012345678 + ast = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + { + 'Arrow:0:3: ->', + children = { + 'PlainIdentifier(scope=a,ident=):0:1:a:', + 'PlainIdentifier(scope=0,ident=b):0:6: b', + }, + }, + }, + }, + }, + err = { + arg = '-> b}', + msg = 'E15: Arrow outside of lambda: %.*s', + }, + }, { + hl('Curly', '{'), + hl('IdentifierScope', 'a'), + hl('IdentifierScopeDelimiter', ':'), + hl('InvalidArrow', '->', 1), + hl('IdentifierName', 'b', 1), + hl('Curly', '}'), + }) + + check_parsing('{a:b -> b}', { + -- 0123456789 + ast = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + { + 'Arrow:0:4: ->', + children = { + 'PlainIdentifier(scope=a,ident=b):0:1:a:b', + 'PlainIdentifier(scope=0,ident=b):0:7: b', + }, + }, + }, + }, + }, + err = { + arg = '-> b}', + msg = 'E15: Arrow outside of lambda: %.*s', + }, + }, { + hl('Curly', '{'), + hl('IdentifierScope', 'a'), + hl('IdentifierScopeDelimiter', ':'), + hl('IdentifierName', 'b'), + hl('InvalidArrow', '->', 1), + hl('IdentifierName', 'b', 1), + hl('Curly', '}'), + }) + + check_parsing('{a#b -> b}', { + -- 0123456789 + ast = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + { + 'Arrow:0:4: ->', + children = { + 'PlainIdentifier(scope=0,ident=a#b):0:1:a#b', + 'PlainIdentifier(scope=0,ident=b):0:7: b', + }, + }, + }, + }, + }, + err = { + arg = '-> b}', + msg = 'E15: Arrow outside of lambda: %.*s', + }, + }, { + hl('Curly', '{'), + hl('IdentifierName', 'a#b'), + hl('InvalidArrow', '->', 1), + hl('IdentifierName', 'b', 1), + hl('Curly', '}'), + }) + check_parsing('{a : b : c}', { + -- 01234567890 + -- 0 1 + ast = { + { + fmtn('DictLiteral', '-di', ':0:0:{'), + children = { + { + 'Colon:0:2: :', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Colon:0:6: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:4: b', + 'PlainIdentifier(scope=0,ident=c):0:8: c', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = ': c}', + msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', + }, + }, { + hl('Dict', '{'), + hl('IdentifierName', 'a'), + hl('Colon', ':', 1), + hl('IdentifierName', 'b', 1), + hl('InvalidColon', ':', 1), + hl('IdentifierName', 'c', 1), + hl('Dict', '}'), + }) + check_parsing('{', { + -- 0 + ast = { + fmtn('UnknownFigure', '\\di', ':0:0:{'), + }, + err = { + arg = '{', + msg = 'E15: Missing closing figure brace: %.*s', + }, + }, { + hl('FigureBrace', '{'), + }) + check_parsing('{a', { + -- 01 + ast = { + { + fmtn('UnknownFigure', '\\di', ':0:0:{'), + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + }, + err = { + arg = '{a', + msg = 'E15: Missing closing figure brace: %.*s', + }, + }, { + hl('FigureBrace', '{'), + hl('IdentifierName', 'a'), + }) + check_parsing('{a,b', { + -- 0123 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + }, + }, + }, + err = { + arg = '{a,b', + msg = 'E15: Missing closing figure brace for lambda: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + }) + check_parsing('{a,b->', { + -- 012345 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + 'Arrow:0:4:->', + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('Arrow', '->'), + }) + check_parsing('{a,b->c', { + -- 0123456 + ast = { + { + fmtn('Lambda', '\\di', ':0:0:{'), + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + { + 'Arrow:0:4:->', + children = { + 'PlainIdentifier(scope=0,ident=c):0:6:c', + }, + }, + }, + }, + }, + err = { + arg = '{a,b->c', + msg = 'E15: Missing closing figure brace for lambda: %.*s', + }, + }, { + hl('Lambda', '{'), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b'), + hl('Arrow', '->'), + hl('IdentifierName', 'c'), + }) + check_parsing('{a : b', { + -- 012345 + ast = { + { + fmtn('DictLiteral', '-di', ':0:0:{'), + children = { + { + 'Colon:0:2: :', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + }, + }, + }, + err = { + arg = '{a : b', + msg = 'E723: Missing end of Dictionary \'}\': %.*s', + }, + }, { + hl('Dict', '{'), + hl('IdentifierName', 'a'), + hl('Colon', ':', 1), + hl('IdentifierName', 'b', 1), + }) + check_parsing('{a : b,', { + -- 0123456 + ast = { + { + fmtn('DictLiteral', '-di', ':0:0:{'), + children = { + { + 'Comma:0:6:,', + children = { + { + 'Colon:0:2: :', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Dict', '{'), + hl('IdentifierName', 'a'), + hl('Colon', ':', 1), + hl('IdentifierName', 'b', 1), + hl('Comma', ','), + }) + end) + itp('works with ternary operator', function() + check_parsing('a ? b : c', { + -- 012345678 + ast = { + { + 'Ternary:0:1: ?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:5: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + 'PlainIdentifier(scope=0,ident=c):0:7: c', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Ternary', '?', 1), + hl('IdentifierName', 'b', 1), + hl('TernaryColon', ':', 1), + hl('IdentifierName', 'c', 1), + }) + check_parsing('@a?@b?@c:@d:@e', { + -- 01234567890123 + -- 0 1 + ast = { + { + 'Ternary:0:2:?', + children = { + 'Register(name=a):0:0:@a', + { + 'TernaryValue:0:11::', + children = { + { + 'Ternary:0:5:?', + children = { + 'Register(name=b):0:3:@b', + { + 'TernaryValue:0:8::', + children = { + 'Register(name=c):0:6:@c', + 'Register(name=d):0:9:@d', + }, + }, + }, + }, + 'Register(name=e):0:12:@e', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('Ternary', '?'), + hl('Register', '@c'), + hl('TernaryColon', ':'), + hl('Register', '@d'), + hl('TernaryColon', ':'), + hl('Register', '@e'), + }) + check_parsing('@a?@b:@c?@d:@e', { + -- 01234567890123 + -- 0 1 + ast = { + { + 'Ternary:0:2:?', + children = { + 'Register(name=a):0:0:@a', + { + 'TernaryValue:0:5::', + children = { + 'Register(name=b):0:3:@b', + { + 'Ternary:0:8:?', + children = { + 'Register(name=c):0:6:@c', + { + 'TernaryValue:0:11::', + children = { + 'Register(name=d):0:9:@d', + 'Register(name=e):0:12:@e', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('TernaryColon', ':'), + hl('Register', '@c'), + hl('Ternary', '?'), + hl('Register', '@d'), + hl('TernaryColon', ':'), + hl('Register', '@e'), + }) + check_parsing('@a?@b?@c?@d:@e?@f:@g:@h?@i:@j:@k', { + -- 01234567890123456789012345678901 + -- 0 1 2 3 + ast = { + { + 'Ternary:0:2:?', + children = { + 'Register(name=a):0:0:@a', + { + 'TernaryValue:0:29::', + children = { + { + 'Ternary:0:5:?', + children = { + 'Register(name=b):0:3:@b', + { + 'TernaryValue:0:20::', + children = { + { + 'Ternary:0:8:?', + children = { + 'Register(name=c):0:6:@c', + { + 'TernaryValue:0:11::', + children = { + 'Register(name=d):0:9:@d', + { + 'Ternary:0:14:?', + children = { + 'Register(name=e):0:12:@e', + { + 'TernaryValue:0:17::', + children = { + 'Register(name=f):0:15:@f', + 'Register(name=g):0:18:@g', + }, + }, + }, + }, + }, + }, + }, + }, + { + 'Ternary:0:23:?', + children = { + 'Register(name=h):0:21:@h', + { + 'TernaryValue:0:26::', + children = { + 'Register(name=i):0:24:@i', + 'Register(name=j):0:27:@j', + }, + }, + }, + }, + }, + }, + }, + }, + 'Register(name=k):0:30:@k', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('Ternary', '?'), + hl('Register', '@c'), + hl('Ternary', '?'), + hl('Register', '@d'), + hl('TernaryColon', ':'), + hl('Register', '@e'), + hl('Ternary', '?'), + hl('Register', '@f'), + hl('TernaryColon', ':'), + hl('Register', '@g'), + hl('TernaryColon', ':'), + hl('Register', '@h'), + hl('Ternary', '?'), + hl('Register', '@i'), + hl('TernaryColon', ':'), + hl('Register', '@j'), + hl('TernaryColon', ':'), + hl('Register', '@k'), + }) + check_parsing('?', { + -- 0 + ast = { + { + 'Ternary:0:0:?', + children = { + 'Missing:0:0:', + 'TernaryValue:0:0:?', + }, + }, + }, + err = { + arg = '?', + msg = 'E15: Expected value, got question mark: %.*s', + }, + }, { + hl('InvalidTernary', '?'), + }) + + check_parsing('?:', { + -- 01 + ast = { + { + 'Ternary:0:0:?', + children = { + 'Missing:0:0:', + { + 'TernaryValue:0:1::', + children = { + 'Missing:0:1:', + }, + }, + }, + }, + }, + err = { + arg = '?:', + msg = 'E15: Expected value, got question mark: %.*s', + }, + }, { + hl('InvalidTernary', '?'), + hl('InvalidTernaryColon', ':'), + }) + + check_parsing('?::', { + -- 012 + ast = { + { + 'Colon:0:2::', + children = { + { + 'Ternary:0:0:?', + children = { + 'Missing:0:0:', + { + 'TernaryValue:0:1::', + children = { + 'Missing:0:1:', + 'Missing:0:2:', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '?::', + msg = 'E15: Expected value, got question mark: %.*s', + }, + }, { + hl('InvalidTernary', '?'), + hl('InvalidTernaryColon', ':'), + hl('InvalidColon', ':'), + }) + + check_parsing('a?b', { + -- 012 + ast = { + { + 'Ternary:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + }, + }, + }, + err = { + arg = '?b', + msg = 'E109: Missing \':\' after \'?\': %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('Ternary', '?'), + hl('IdentifierName', 'b'), + }) + check_parsing('a?b:', { + -- 0123 + ast = { + { + 'Ternary:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:1:?', + children = { + 'PlainIdentifier(scope=b,ident=):0:2:b:', + }, + }, + }, + }, + }, + err = { + arg = '?b:', + msg = 'E109: Missing \':\' after \'?\': %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('Ternary', '?'), + hl('IdentifierScope', 'b'), + hl('IdentifierScopeDelimiter', ':'), + }) + + check_parsing('a?b::c', { + -- 012345 + ast = { + { + 'Ternary:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:4::', + children = { + 'PlainIdentifier(scope=b,ident=):0:2:b:', + 'PlainIdentifier(scope=0,ident=c):0:5:c', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Ternary', '?'), + hl('IdentifierScope', 'b'), + hl('IdentifierScopeDelimiter', ':'), + hl('TernaryColon', ':'), + hl('IdentifierName', 'c'), + }) + + check_parsing('a?b :', { + -- 01234 + ast = { + { + 'Ternary:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:3: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('Ternary', '?'), + hl('IdentifierName', 'b'), + hl('TernaryColon', ':', 1), + }) + + check_parsing('(@a?@b:@c)?@d:@e', { + -- 0123456789012345 + -- 0 1 + ast = { + { + 'Ternary:0:10:?', + children = { + { + 'Nested:0:0:(', + children = { + { + 'Ternary:0:3:?', + children = { + 'Register(name=a):0:1:@a', + { + 'TernaryValue:0:6::', + children = { + 'Register(name=b):0:4:@b', + 'Register(name=c):0:7:@c', + }, + }, + }, + }, + }, + }, + { + 'TernaryValue:0:13::', + children = { + 'Register(name=d):0:11:@d', + 'Register(name=e):0:14:@e', + }, + }, + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('TernaryColon', ':'), + hl('Register', '@c'), + hl('NestingParenthesis', ')'), + hl('Ternary', '?'), + hl('Register', '@d'), + hl('TernaryColon', ':'), + hl('Register', '@e'), + }) + + check_parsing('(@a?@b:@c)?(@d?@e:@f):(@g?@h:@i)', { + -- 01234567890123456789012345678901 + -- 0 1 2 3 + ast = { + { + 'Ternary:0:10:?', + children = { + { + 'Nested:0:0:(', + children = { + { + 'Ternary:0:3:?', + children = { + 'Register(name=a):0:1:@a', + { + 'TernaryValue:0:6::', + children = { + 'Register(name=b):0:4:@b', + 'Register(name=c):0:7:@c', + }, + }, + }, + }, + }, + }, + { + 'TernaryValue:0:21::', + children = { + { + 'Nested:0:11:(', + children = { + { + 'Ternary:0:14:?', + children = { + 'Register(name=d):0:12:@d', + { + 'TernaryValue:0:17::', + children = { + 'Register(name=e):0:15:@e', + 'Register(name=f):0:18:@f', + }, + }, + }, + }, + }, + }, + { + 'Nested:0:22:(', + children = { + { + 'Ternary:0:25:?', + children = { + 'Register(name=g):0:23:@g', + { + 'TernaryValue:0:28::', + children = { + 'Register(name=h):0:26:@h', + 'Register(name=i):0:29:@i', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('TernaryColon', ':'), + hl('Register', '@c'), + hl('NestingParenthesis', ')'), + hl('Ternary', '?'), + hl('NestingParenthesis', '('), + hl('Register', '@d'), + hl('Ternary', '?'), + hl('Register', '@e'), + hl('TernaryColon', ':'), + hl('Register', '@f'), + hl('NestingParenthesis', ')'), + hl('TernaryColon', ':'), + hl('NestingParenthesis', '('), + hl('Register', '@g'), + hl('Ternary', '?'), + hl('Register', '@h'), + hl('TernaryColon', ':'), + hl('Register', '@i'), + hl('NestingParenthesis', ')'), + }) + + check_parsing('(@a?@b:@c)?@d?@e:@f:@g?@h:@i', { + -- 0123456789012345678901234567 + -- 0 1 2 + ast = { + { + 'Ternary:0:10:?', + children = { + { + 'Nested:0:0:(', + children = { + { + 'Ternary:0:3:?', + children = { + 'Register(name=a):0:1:@a', + { + 'TernaryValue:0:6::', + children = { + 'Register(name=b):0:4:@b', + 'Register(name=c):0:7:@c', + }, + }, + }, + }, + }, + }, + { + 'TernaryValue:0:19::', + children = { + { + 'Ternary:0:13:?', + children = { + 'Register(name=d):0:11:@d', + { + 'TernaryValue:0:16::', + children = { + 'Register(name=e):0:14:@e', + 'Register(name=f):0:17:@f', + }, + }, + }, + }, + { + 'Ternary:0:22:?', + children = { + 'Register(name=g):0:20:@g', + { + 'TernaryValue:0:25::', + children = { + 'Register(name=h):0:23:@h', + 'Register(name=i):0:26:@i', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('TernaryColon', ':'), + hl('Register', '@c'), + hl('NestingParenthesis', ')'), + hl('Ternary', '?'), + hl('Register', '@d'), + hl('Ternary', '?'), + hl('Register', '@e'), + hl('TernaryColon', ':'), + hl('Register', '@f'), + hl('TernaryColon', ':'), + hl('Register', '@g'), + hl('Ternary', '?'), + hl('Register', '@h'), + hl('TernaryColon', ':'), + hl('Register', '@i'), + }) + check_parsing('a?b{cdef}g:h', { + -- 012345678901 + -- 0 1 + ast = { + { + 'Ternary:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:10::', + children = { + { + 'ComplexIdentifier:0:3:', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + { + 'ComplexIdentifier:0:9:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:3:{'), + children = { + 'PlainIdentifier(scope=0,ident=cdef):0:4:cdef', + }, + }, + 'PlainIdentifier(scope=0,ident=g):0:9:g', + }, + }, + }, + }, + 'PlainIdentifier(scope=0,ident=h):0:11:h', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Ternary', '?'), + hl('IdentifierName', 'b'), + hl('Curly', '{'), + hl('IdentifierName', 'cdef'), + hl('Curly', '}'), + hl('IdentifierName', 'g'), + hl('TernaryColon', ':'), + hl('IdentifierName', 'h'), + }) + check_parsing('a ? b : c : d', { + -- 0123456789012 + -- 0 1 + ast = { + { + 'Colon:0:9: :', + children = { + { + 'Ternary:0:1: ?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:5: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + 'PlainIdentifier(scope=0,ident=c):0:7: c', + }, + }, + }, + }, + 'PlainIdentifier(scope=0,ident=d):0:11: d', + }, + }, + }, + err = { + arg = ': d', + msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('Ternary', '?', 1), + hl('IdentifierName', 'b', 1), + hl('TernaryColon', ':', 1), + hl('IdentifierName', 'c', 1), + hl('InvalidColon', ':', 1), + hl('IdentifierName', 'd', 1), + }) + end) + itp('works with comparison operators', function() + check_parsing('a == b', { + -- 012345 + ast = { + { + 'Comparison(type=Equal,inv=0,ccs=UseOption):0:1: ==', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '==', 1), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a ==? b', { + -- 0123456 + ast = { + { + 'Comparison(type=Equal,inv=0,ccs=IgnoreCase):0:1: ==?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '==', 1), + hl('ComparisonModifier', '?'), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a ==# b', { + -- 0123456 + ast = { + { + 'Comparison(type=Equal,inv=0,ccs=MatchCase):0:1: ==#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '==', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a !=# b', { + -- 0123456 + ast = { + { + 'Comparison(type=Equal,inv=1,ccs=MatchCase):0:1: !=#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '!=', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a <=# b', { + -- 0123456 + ast = { + { + 'Comparison(type=Greater,inv=1,ccs=MatchCase):0:1: <=#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '<=', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a >=# b', { + -- 0123456 + ast = { + { + 'Comparison(type=GreaterOrEqual,inv=0,ccs=MatchCase):0:1: >=#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '>=', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a ># b', { + -- 012345 + ast = { + { + 'Comparison(type=Greater,inv=0,ccs=MatchCase):0:1: >#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '>', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a <# b', { + -- 012345 + ast = { + { + 'Comparison(type=GreaterOrEqual,inv=1,ccs=MatchCase):0:1: <#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '<', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a is#b', { + -- 012345 + ast = { + { + 'Comparison(type=Identical,inv=0,ccs=MatchCase):0:1: is#', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5:b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', 'is', 1), + hl('ComparisonModifier', '#'), + hl('IdentifierName', 'b'), + }) + + check_parsing('a is?b', { + -- 012345 + ast = { + { + 'Comparison(type=Identical,inv=0,ccs=IgnoreCase):0:1: is?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:5:b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', 'is', 1), + hl('ComparisonModifier', '?'), + hl('IdentifierName', 'b'), + }) + + check_parsing('a isnot b', { + -- 012345678 + ast = { + { + 'Comparison(type=Identical,inv=1,ccs=UseOption):0:1: isnot', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:7: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', 'isnot', 1), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a < b < c', { + -- 012345678 + ast = { + { + 'Comparison(type=GreaterOrEqual,inv=1,ccs=UseOption):0:1: <', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Comparison(type=GreaterOrEqual,inv=1,ccs=UseOption):0:5: <', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + 'PlainIdentifier(scope=0,ident=c):0:7: c', + }, + }, + }, + }, + }, + err = { + arg = ' < c', + msg = 'E15: Operator is not associative: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '<', 1), + hl('IdentifierName', 'b', 1), + hl('InvalidComparison', '<', 1), + hl('IdentifierName', 'c', 1), + }) + + check_parsing('a < b <# c', { + -- 012345678 + ast = { + { + 'Comparison(type=GreaterOrEqual,inv=1,ccs=UseOption):0:1: <', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Comparison(type=GreaterOrEqual,inv=1,ccs=MatchCase):0:5: <#', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + 'PlainIdentifier(scope=0,ident=c):0:8: c', + }, + }, + }, + }, + }, + err = { + arg = ' <# c', + msg = 'E15: Operator is not associative: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('Comparison', '<', 1), + hl('IdentifierName', 'b', 1), + hl('InvalidComparison', '<', 1), + hl('InvalidComparisonModifier', '#'), + hl('IdentifierName', 'c', 1), + }) + + check_parsing('a += b', { + -- 012345 + ast = { + { + 'Assignment(Add):0:1: +=', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + }, + err = { + arg = '+= b', + msg = 'E15: Misplaced assignment: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('InvalidAssignmentWithAddition', '+=', 1), + hl('IdentifierName', 'b', 1), + }) + check_parsing('a + b == c + d', { + -- 01234567890123 + -- 0 1 + ast = { + { + 'Comparison(type=Equal,inv=0,ccs=UseOption):0:5: ==', + children = { + { + 'BinaryPlus:0:1: +', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:3: b', + }, + }, + { + 'BinaryPlus:0:10: +', + children = { + 'PlainIdentifier(scope=0,ident=c):0:8: c', + 'PlainIdentifier(scope=0,ident=d):0:12: d', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('BinaryPlus', '+', 1), + hl('IdentifierName', 'b', 1), + hl('Comparison', '==', 1), + hl('IdentifierName', 'c', 1), + hl('BinaryPlus', '+', 1), + hl('IdentifierName', 'd', 1), + }) + check_parsing('+ a == + b', { + -- 0123456789 + ast = { + { + 'Comparison(type=Equal,inv=0,ccs=UseOption):0:3: ==', + children = { + { + 'UnaryPlus:0:0:+', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1: a', + }, + }, + { + 'UnaryPlus:0:6: +', + children = { + 'PlainIdentifier(scope=0,ident=b):0:8: b', + }, + }, + }, + }, + }, + }, { + hl('UnaryPlus', '+'), + hl('IdentifierName', 'a', 1), + hl('Comparison', '==', 1), + hl('UnaryPlus', '+', 1), + hl('IdentifierName', 'b', 1), + }) + end) + itp('works with concat/subscript', function() + check_parsing('.', { + -- 0 + ast = { + { + 'ConcatOrSubscript:0:0:.', + children = { + 'Missing:0:0:', + }, + }, + }, + err = { + arg = '.', + msg = 'E15: Unexpected dot: %.*s', + }, + }, { + hl('InvalidConcatOrSubscript', '.'), + }) + + check_parsing('a.', { + -- 01 + ast = { + { + 'ConcatOrSubscript:0:1:.', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('ConcatOrSubscript', '.'), + }) + + check_parsing('a.b', { + -- 012 + ast = { + { + 'ConcatOrSubscript:0:1:.', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainKey(key=b):0:2:b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', 'b'), + }) + + check_parsing('1.2', { + -- 012 + ast = { + 'Float(val=1.200000e+00):0:0:1.2', + }, + }, { + hl('Float', '1.2'), + }) + + check_parsing('1.2 + 1.3e-5', { + -- 012345678901 + -- 0 1 + ast = { + { + 'BinaryPlus:0:3: +', + children = { + 'Float(val=1.200000e+00):0:0:1.2', + 'Float(val=1.300000e-05):0:5: 1.3e-5', + }, + }, + }, + }, { + hl('Float', '1.2'), + hl('BinaryPlus', '+', 1), + hl('Float', '1.3e-5', 1), + }) + + check_parsing('a . 1.2 + 1.3e-5', { + -- 0123456789012345 + -- 0 1 + ast = { + { + 'BinaryPlus:0:7: +', + children = { + { + 'Concat:0:1: .', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'ConcatOrSubscript:0:5:.', + children = { + 'Integer(val=1):0:3: 1', + 'PlainKey(key=2):0:6:2', + }, + }, + }, + }, + 'Float(val=1.300000e-05):0:9: 1.3e-5', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Concat', '.', 1), + hl('Number', '1', 1), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '2'), + hl('BinaryPlus', '+', 1), + hl('Float', '1.3e-5', 1), + }) + + check_parsing('1.3e-5 + 1.2 . a', { + -- 0123456789012345 + -- 0 1 + ast = { + { + 'Concat:0:12: .', + children = { + { + 'BinaryPlus:0:6: +', + children = { + 'Float(val=1.300000e-05):0:0:1.3e-5', + 'Float(val=1.200000e+00):0:8: 1.2', + }, + }, + 'PlainIdentifier(scope=0,ident=a):0:14: a', + }, + }, + }, + }, { + hl('Float', '1.3e-5'), + hl('BinaryPlus', '+', 1), + hl('Float', '1.2', 1), + hl('Concat', '.', 1), + hl('IdentifierName', 'a', 1), + }) + + check_parsing('1.3e-5 + a . 1.2', { + -- 0123456789012345 + -- 0 1 + ast = { + { + 'Concat:0:10: .', + children = { + { + 'BinaryPlus:0:6: +', + children = { + 'Float(val=1.300000e-05):0:0:1.3e-5', + 'PlainIdentifier(scope=0,ident=a):0:8: a', + }, + }, + { + 'ConcatOrSubscript:0:14:.', + children = { + 'Integer(val=1):0:12: 1', + 'PlainKey(key=2):0:15:2', + }, + }, + }, + }, + }, + }, { + hl('Float', '1.3e-5'), + hl('BinaryPlus', '+', 1), + hl('IdentifierName', 'a', 1), + hl('Concat', '.', 1), + hl('Number', '1', 1), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '2'), + }) + + check_parsing('1.2.3', { + -- 01234 + ast = { + { + 'ConcatOrSubscript:0:3:.', + children = { + { + 'ConcatOrSubscript:0:1:.', + children = { + 'Integer(val=1):0:0:1', + 'PlainKey(key=2):0:2:2', + }, + }, + 'PlainKey(key=3):0:4:3', + }, + }, + }, + }, { + hl('Number', '1'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '2'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '3'), + }) + + check_parsing('a.1.2', { + -- 01234 + ast = { + { + 'ConcatOrSubscript:0:3:.', + children = { + { + 'ConcatOrSubscript:0:1:.', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainKey(key=1):0:2:1', + }, + }, + 'PlainKey(key=2):0:4:2', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '1'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '2'), + }) + + check_parsing('a . 1.2', { + -- 0123456 + ast = { + { + 'Concat:0:1: .', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'ConcatOrSubscript:0:5:.', + children = { + 'Integer(val=1):0:3: 1', + 'PlainKey(key=2):0:6:2', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Concat', '.', 1), + hl('Number', '1', 1), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '2'), + }) + + check_parsing('+a . +b', { + -- 0123456 + ast = { + { + 'Concat:0:2: .', + children = { + { + 'UnaryPlus:0:0:+', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + { + 'UnaryPlus:0:4: +', + children = { + 'PlainIdentifier(scope=0,ident=b):0:6:b', + }, + }, + }, + }, + }, + }, { + hl('UnaryPlus', '+'), + hl('IdentifierName', 'a'), + hl('Concat', '.', 1), + hl('UnaryPlus', '+', 1), + hl('IdentifierName', 'b'), + }) + + check_parsing('a. b', { + -- 0123 + ast = { + { + 'Concat:0:1:.', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:2: b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierName', 'b', 1), + }) + + check_parsing('a. 1', { + -- 0123 + ast = { + { + 'Concat:0:1:.', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'Integer(val=1):0:2: 1', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('ConcatOrSubscript', '.'), + hl('Number', '1', 1), + }) + + check_parsing('a[1][2][3[4', { + -- 01234567890 + -- 0 1 + ast = { + { + 'Subscript:0:7:[', + children = { + { + 'Subscript:0:4:[', + children = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'Integer(val=1):0:2:1', + }, + }, + 'Integer(val=2):0:5:2', + }, + }, + { + 'Subscript:0:9:[', + children = { + 'Integer(val=3):0:8:3', + 'Integer(val=4):0:10:4', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('Number', '1'), + hl('SubscriptBracket', ']'), + hl('SubscriptBracket', '['), + hl('Number', '2'), + hl('SubscriptBracket', ']'), + hl('SubscriptBracket', '['), + hl('Number', '3'), + hl('SubscriptBracket', '['), + hl('Number', '4'), + }) + end) + itp('works with bracket subscripts', function() + check_parsing(':', { + -- 0 + ast = { + { + 'Colon:0:0::', + children = { + 'Missing:0:0:', + }, + }, + }, + err = { + arg = ':', + msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', + }, + }, { + hl('InvalidColon', ':'), + }) + check_parsing('a[]', { + -- 012 + ast = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + }, + }, + err = { + arg = ']', + msg = 'E15: Expected value, got closing bracket: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('InvalidSubscriptBracket', ']'), + }) + check_parsing('a[b:]', { + -- 01234 + ast = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=b,ident=):0:2:b:', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('IdentifierScope', 'b'), + hl('IdentifierScopeDelimiter', ':'), + hl('SubscriptBracket', ']'), + }) + + check_parsing('a[b:c]', { + -- 012345 + ast = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=b,ident=c):0:2:b:c', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('IdentifierScope', 'b'), + hl('IdentifierScopeDelimiter', ':'), + hl('IdentifierName', 'c'), + hl('SubscriptBracket', ']'), + }) + check_parsing('a[b : c]', { + -- 01234567 + ast = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Colon:0:3: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + 'PlainIdentifier(scope=0,ident=c):0:5: c', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'b'), + hl('SubscriptColon', ':', 1), + hl('IdentifierName', 'c', 1), + hl('SubscriptBracket', ']'), + }) + + check_parsing('a[: b]', { + -- 012345 + ast = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Colon:0:2::', + children = { + 'Missing:0:2:', + 'PlainIdentifier(scope=0,ident=b):0:3: b', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('SubscriptColon', ':'), + hl('IdentifierName', 'b', 1), + hl('SubscriptBracket', ']'), + }) + + check_parsing('a[b :]', { + -- 012345 + ast = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Colon:0:3: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'b'), + hl('SubscriptColon', ':', 1), + hl('SubscriptBracket', ']'), + }) + check_parsing('a[b][c][d](e)(f)(g)', { + -- 0123456789012345678 + -- 0 1 + ast = { + { + 'Call:0:16:(', + children = { + { + 'Call:0:13:(', + children = { + { + 'Call:0:10:(', + children = { + { + 'Subscript:0:7:[', + children = { + { + 'Subscript:0:4:[', + children = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + 'PlainIdentifier(scope=0,ident=c):0:5:c', + }, + }, + 'PlainIdentifier(scope=0,ident=d):0:8:d', + }, + }, + 'PlainIdentifier(scope=0,ident=e):0:11:e', + }, + }, + 'PlainIdentifier(scope=0,ident=f):0:14:f', + }, + }, + 'PlainIdentifier(scope=0,ident=g):0:17:g', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'b'), + hl('SubscriptBracket', ']'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'c'), + hl('SubscriptBracket', ']'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'd'), + hl('SubscriptBracket', ']'), + hl('CallingParenthesis', '('), + hl('IdentifierName', 'e'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('IdentifierName', 'f'), + hl('CallingParenthesis', ')'), + hl('CallingParenthesis', '('), + hl('IdentifierName', 'g'), + hl('CallingParenthesis', ')'), + }) + check_parsing('{a}{b}{c}[d][e][f]', { + -- 012345678901234567 + -- 0 1 + ast = { + { + 'Subscript:0:15:[', + children = { + { + 'Subscript:0:12:[', + children = { + { + 'Subscript:0:9:[', + children = { + { + 'ComplexIdentifier:0:3:', + children = { + { + fmtn('CurlyBracesIdentifier', '-di', ':0:0:{'), + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + { + 'ComplexIdentifier:0:6:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:3:{'), + children = { + 'PlainIdentifier(scope=0,ident=b):0:4:b', + }, + }, + { + fmtn('CurlyBracesIdentifier', '--i', ':0:6:{'), + children = { + 'PlainIdentifier(scope=0,ident=c):0:7:c', + }, + }, + }, + }, + }, + }, + 'PlainIdentifier(scope=0,ident=d):0:10:d', + }, + }, + 'PlainIdentifier(scope=0,ident=e):0:13:e', + }, + }, + 'PlainIdentifier(scope=0,ident=f):0:16:f', + }, + }, + }, + }, { + hl('Curly', '{'), + hl('IdentifierName', 'a'), + hl('Curly', '}'), + hl('Curly', '{'), + hl('IdentifierName', 'b'), + hl('Curly', '}'), + hl('Curly', '{'), + hl('IdentifierName', 'c'), + hl('Curly', '}'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'd'), + hl('SubscriptBracket', ']'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'e'), + hl('SubscriptBracket', ']'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'f'), + hl('SubscriptBracket', ']'), + }) + end) + itp('supports list literals', function() + check_parsing('[]', { + -- 01 + ast = { + 'ListLiteral:0:0:[', + }, + }, { + hl('List', '['), + hl('List', ']'), + }) + + check_parsing('[a]', { + -- 012 + ast = { + { + 'ListLiteral:0:0:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('List', ']'), + }) + + check_parsing('[a, b]', { + -- 012345 + ast = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3: b', + }, + }, + }, + }, + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b', 1), + hl('List', ']'), + }) + + check_parsing('[a, b, c]', { + -- 012345678 + ast = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Comma:0:5:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + 'PlainIdentifier(scope=0,ident=c):0:6: c', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b', 1), + hl('Comma', ','), + hl('IdentifierName', 'c', 1), + hl('List', ']'), + }) + + check_parsing('[a, b, c, ]', { + -- 01234567890 + -- 0 1 + ast = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Comma:0:5:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + { + 'Comma:0:8:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:6: c', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b', 1), + hl('Comma', ','), + hl('IdentifierName', 'c', 1), + hl('Comma', ','), + hl('List', ']', 1), + }) + + check_parsing('[a : b, c : d]', { + -- 01234567890123 + -- 0 1 + ast = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:6:,', + children = { + { + 'Colon:0:2: :', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + { + 'Colon:0:9: :', + children = { + 'PlainIdentifier(scope=0,ident=c):0:7: c', + 'PlainIdentifier(scope=0,ident=d):0:11: d', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = ': b, c : d]', + msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('InvalidColon', ':', 1), + hl('IdentifierName', 'b', 1), + hl('Comma', ','), + hl('IdentifierName', 'c', 1), + hl('InvalidColon', ':', 1), + hl('IdentifierName', 'd', 1), + hl('List', ']'), + }) + + check_parsing(']', { + -- 0 + ast = { + 'ListLiteral:0:0:', + }, + err = { + arg = ']', + msg = 'E15: Unexpected closing figure brace: %.*s', + }, + }, { + hl('InvalidList', ']'), + }) + + check_parsing('a]', { + -- 01 + ast = { + { + 'ListLiteral:0:1:', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + }, + }, + err = { + arg = ']', + msg = 'E15: Unexpected closing figure brace: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('InvalidList', ']'), + }) + + check_parsing('[] []', { + -- 01234 + ast = { + { + 'OpMissing:0:2:', + children = { + 'ListLiteral:0:0:[', + 'ListLiteral:0:2: [', + }, + }, + }, + err = { + arg = '[]', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('List', '['), + hl('List', ']'), + hl('InvalidSpacing', ' '), + hl('List', '['), + hl('List', ']'), + }, { + [1] = { + ast = { + len = 3, + err = REMOVE_THIS, + ast = { + 'ListLiteral:0:0:[', + }, + }, + hl_fs = { + [3] = REMOVE_THIS, + [4] = REMOVE_THIS, + [5] = REMOVE_THIS, + }, + }, + }) + + check_parsing('[][]', { + -- 0123 + ast = { + { + 'Subscript:0:2:[', + children = { + 'ListLiteral:0:0:[', + }, + }, + }, + err = { + arg = ']', + msg = 'E15: Expected value, got closing bracket: %.*s', + }, + }, { + hl('List', '['), + hl('List', ']'), + hl('SubscriptBracket', '['), + hl('InvalidSubscriptBracket', ']'), + }) + + check_parsing('[', { + -- 0 + ast = { + 'ListLiteral:0:0:[', + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('List', '['), + }) + + check_parsing('[1', { + -- 01 + ast = { + { + 'ListLiteral:0:0:[', + children = { + 'Integer(val=1):0:1:1', + }, + }, + }, + err = { + arg = '[1', + msg = 'E697: Missing end of List \']\': %.*s', + }, + }, { + hl('List', '['), + hl('Number', '1'), + }) + end) + itp('works with strings', function() + check_parsing('\'abc\'', { + -- 01234 + ast = { + fmtn('SingleQuotedString', 'val="abc"', ':0:0:\'abc\''), + }, + }, { + hl('SingleQuote', '\''), + hl('SingleQuotedBody', 'abc'), + hl('SingleQuote', '\''), + }) + check_parsing('"abc"', { + -- 01234 + ast = { + fmtn('DoubleQuotedString', 'val="abc"', ':0:0:"abc"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedBody', 'abc'), + hl('DoubleQuote', '"'), + }) + check_parsing('\'\'', { + -- 01 + ast = { + fmtn('SingleQuotedString', 'val=NULL', ':0:0:\'\''), + }, + }, { + hl('SingleQuote', '\''), + hl('SingleQuote', '\''), + }) + check_parsing('""', { + -- 01 + ast = { + fmtn('DoubleQuotedString', 'val=NULL', ':0:0:""'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuote', '"'), + }) + check_parsing('"', { + -- 0 + ast = { + fmtn('DoubleQuotedString', 'val=NULL', ':0:0:"'), + }, + err = { + arg = '"', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuote', '"'), + }) + check_parsing('\'', { + -- 0 + ast = { + fmtn('SingleQuotedString', 'val=NULL', ':0:0:\''), + }, + err = { + arg = '\'', + msg = 'E115: Missing single quote: %.*s', + }, + }, { + hl('InvalidSingleQuote', '\''), + }) + check_parsing('"a', { + -- 01 + ast = { + fmtn('DoubleQuotedString', 'val="a"', ':0:0:"a'), + }, + err = { + arg = '"a', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuote', '"'), + hl('InvalidDoubleQuotedBody', 'a'), + }) + check_parsing('\'a', { + -- 01 + ast = { + fmtn('SingleQuotedString', 'val="a"', ':0:0:\'a'), + }, + err = { + arg = '\'a', + msg = 'E115: Missing single quote: %.*s', + }, + }, { + hl('InvalidSingleQuote', '\''), + hl('InvalidSingleQuotedBody', 'a'), + }) + check_parsing('\'abc\'\'def\'', { + -- 0123456789 + ast = { + fmtn('SingleQuotedString', 'val="abc\'def"', ':0:0:\'abc\'\'def\''), + }, + }, { + hl('SingleQuote', '\''), + hl('SingleQuotedBody', 'abc'), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuotedBody', 'def'), + hl('SingleQuote', '\''), + }) + check_parsing('\'abc\'\'', { + -- 012345 + ast = { + fmtn('SingleQuotedString', 'val="abc\'"', ':0:0:\'abc\'\''), + }, + err = { + arg = '\'abc\'\'', + msg = 'E115: Missing single quote: %.*s', + }, + }, { + hl('InvalidSingleQuote', '\''), + hl('InvalidSingleQuotedBody', 'abc'), + hl('InvalidSingleQuotedQuote', '\'\''), + }) + check_parsing('\'\'\'\'\'\'\'\'', { + -- 01234567 + ast = { + fmtn('SingleQuotedString', 'val="\'\'\'"', ':0:0:\'\'\'\'\'\'\'\''), + }, + }, { + hl('SingleQuote', '\''), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuote', '\''), + }) + check_parsing('\'\'\'a\'\'\'\'bc\'', { + -- 01234567890 + -- 0 1 + ast = { + fmtn('SingleQuotedString', 'val="\'a\'\'bc"', ':0:0:\'\'\'a\'\'\'\'bc\''), + }, + }, { + hl('SingleQuote', '\''), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuotedBody', 'a'), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuotedQuote', '\'\''), + hl('SingleQuotedBody', 'bc'), + hl('SingleQuote', '\''), + }) + check_parsing('"\\"\\"\\"\\""', { + -- 0123456789 + ast = { + fmtn('DoubleQuotedString', 'val="\\"\\"\\"\\""', ':0:0:"\\"\\"\\"\\""'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuote', '"'), + }) + check_parsing('"abc\\"def\\"ghi\\"jkl\\"mno"', { + -- 0123456789012345678901234 + -- 0 1 2 + ast = { + fmtn('DoubleQuotedString', 'val="abc\\"def\\"ghi\\"jkl\\"mno"', ':0:0:"abc\\"def\\"ghi\\"jkl\\"mno"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedBody', 'abc'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedBody', 'def'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedBody', 'ghi'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedBody', 'jkl'), + hl('DoubleQuotedEscape', '\\"'), + hl('DoubleQuotedBody', 'mno'), + hl('DoubleQuote', '"'), + }) + check_parsing('"\\b\\e\\f\\r\\t\\\\"', { + -- 0123456789012345 + -- 0 1 + ast = { + [[DoubleQuotedString(val="\008\027\012\r\t\\"):0:0:"\b\e\f\r\t\\"]], + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\b'), + hl('DoubleQuotedEscape', '\\e'), + hl('DoubleQuotedEscape', '\\f'), + hl('DoubleQuotedEscape', '\\r'), + hl('DoubleQuotedEscape', '\\t'), + hl('DoubleQuotedEscape', '\\\\'), + hl('DoubleQuote', '"'), + }) + check_parsing('"\\n\n"', { + -- 01234 + ast = { + fmtn('DoubleQuotedString', 'val="\\n\\n"', ':0:0:"\\n\n"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\n'), + hl('DoubleQuotedBody', '\n'), + hl('DoubleQuote', '"'), + }) + check_parsing('"\\x00"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\\000"', ':0:0:"\\x00"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\x00'), + hl('DoubleQuote', '"'), + }) + check_parsing('"\\xFF"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\255"', ':0:0:"\\xFF"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\xFF'), + hl('DoubleQuote', '"'), + }) + check_parsing('"\\xF"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\\015"', ':0:0:"\\xF"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\xF'), + hl('DoubleQuote', '"'), + }) + check_parsing('"\\u00AB"', { + -- 01234567 + ast = { + fmtn('DoubleQuotedString', 'val="«"', ':0:0:"\\u00AB"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\u00AB'), + hl('DoubleQuote', '"'), + }) + check_parsing('"\\U000000AB"', { + -- 01234567 + ast = { + fmtn('DoubleQuotedString', 'val="«"', ':0:0:"\\U000000AB"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U000000AB'), + hl('DoubleQuote', '"'), + }) + check_parsing('"\\x"', { + -- 0123 + ast = { + fmtn('DoubleQuotedString', 'val="x"', ':0:0:"\\x"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\x'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\x', { + -- 012 + ast = { + fmtn('DoubleQuotedString', 'val="x"', ':0:0:"\\x'), + }, + err = { + arg = '"\\x', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuote', '"'), + hl('InvalidDoubleQuotedUnknownEscape', '\\x'), + }) + + check_parsing('"\\xF', { + -- 0123 + ast = { + fmtn('DoubleQuotedString', 'val="\\015"', ':0:0:"\\xF'), + }, + err = { + arg = '"\\xF', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuote', '"'), + hl('InvalidDoubleQuotedEscape', '\\xF'), + }) + + check_parsing('"\\u"', { + -- 0123 + ast = { + fmtn('DoubleQuotedString', 'val="u"', ':0:0:"\\u"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\u'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\u', { + -- 012 + ast = { + fmtn('DoubleQuotedString', 'val="u"', ':0:0:"\\u'), + }, + err = { + arg = '"\\u', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuote', '"'), + hl('InvalidDoubleQuotedUnknownEscape', '\\u'), + }) + + check_parsing('"\\U', { + -- 012 + ast = { + fmtn('DoubleQuotedString', 'val="U"', ':0:0:"\\U'), + }, + err = { + arg = '"\\U', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuote', '"'), + hl('InvalidDoubleQuotedUnknownEscape', '\\U'), + }) + + check_parsing('"\\U"', { + -- 0123 + ast = { + fmtn('DoubleQuotedString', 'val="U"', ':0:0:"\\U"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\U'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\xFX"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\\015X"', ':0:0:"\\xFX"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\xF'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\XFX"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\\015X"', ':0:0:"\\XFX"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\XF'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\xX"', { + -- 01234 + ast = { + fmtn('DoubleQuotedString', 'val="xX"', ':0:0:"\\xX"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\x'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\XX"', { + -- 01234 + ast = { + fmtn('DoubleQuotedString', 'val="XX"', ':0:0:"\\XX"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\X'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\uX"', { + -- 01234 + ast = { + fmtn('DoubleQuotedString', 'val="uX"', ':0:0:"\\uX"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\u'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\UX"', { + -- 01234 + ast = { + fmtn('DoubleQuotedString', 'val="UX"', ':0:0:"\\UX"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\U'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\x0X"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\\000X"', ':0:0:"\\x0X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\x0'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\X0X"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\\000X"', ':0:0:"\\X0X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\X0'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\u0X"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\\000X"', ':0:0:"\\u0X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\u0'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U0X"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\\000X"', ':0:0:"\\U0X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U0'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\x00X"', { + -- 0123456 + ast = { + fmtn('DoubleQuotedString', 'val="\\000X"', ':0:0:"\\x00X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\x00'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\X00X"', { + -- 0123456 + ast = { + fmtn('DoubleQuotedString', 'val="\\000X"', ':0:0:"\\X00X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\X00'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\u00X"', { + -- 0123456 + ast = { + fmtn('DoubleQuotedString', 'val="\\000X"', ':0:0:"\\u00X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\u00'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U00X"', { + -- 0123456 + ast = { + fmtn('DoubleQuotedString', 'val="\\000X"', ':0:0:"\\U00X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U00'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\u000X"', { + -- 01234567 + ast = { + fmtn('DoubleQuotedString', 'val="\\000X"', ':0:0:"\\u000X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\u000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U000X"', { + -- 01234567 + ast = { + fmtn('DoubleQuotedString', 'val="\\000X"', ':0:0:"\\U000X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\u0000X"', { + -- 012345678 + ast = { + fmtn('DoubleQuotedString', 'val="\\000X"', ':0:0:"\\u0000X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\u0000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U0000X"', { + -- 012345678 + ast = { + fmtn('DoubleQuotedString', 'val="\\000X"', ':0:0:"\\U0000X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U0000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U00000X"', { + -- 0123456789 + ast = { + fmtn('DoubleQuotedString', 'val="\\000X"', ':0:0:"\\U00000X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U00000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U000000X"', { + -- 01234567890 + -- 0 1 + ast = { + fmtn('DoubleQuotedString', 'val="\\000X"', ':0:0:"\\U000000X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U000000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U0000000X"', { + -- 012345678901 + -- 0 1 + ast = { + fmtn('DoubleQuotedString', 'val="\\000X"', ':0:0:"\\U0000000X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U0000000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U00000000X"', { + -- 0123456789012 + -- 0 1 + ast = { + fmtn('DoubleQuotedString', 'val="\\000X"', ':0:0:"\\U00000000X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U00000000'), + hl('DoubleQuotedBody', 'X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\x000X"', { + -- 01234567 + ast = { + fmtn('DoubleQuotedString', 'val="\\0000X"', ':0:0:"\\x000X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\x00'), + hl('DoubleQuotedBody', '0X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\X000X"', { + -- 01234567 + ast = { + fmtn('DoubleQuotedString', 'val="\\0000X"', ':0:0:"\\X000X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\X00'), + hl('DoubleQuotedBody', '0X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\u00000X"', { + -- 0123456789 + ast = { + fmtn('DoubleQuotedString', 'val="\\0000X"', ':0:0:"\\u00000X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\u0000'), + hl('DoubleQuotedBody', '0X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\U000000000X"', { + -- 01234567890123 + -- 0 1 + ast = { + fmtn('DoubleQuotedString', 'val="\\0000X"', ':0:0:"\\U000000000X"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\U00000000'), + hl('DoubleQuotedBody', '0X'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\0"', { + -- 0123 + ast = { + fmtn('DoubleQuotedString', 'val="\\000"', ':0:0:"\\0"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\0'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\00"', { + -- 01234 + ast = { + fmtn('DoubleQuotedString', 'val="\\000"', ':0:0:"\\00"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\00'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\000"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\\000"', ':0:0:"\\000"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\000'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\0000"', { + -- 0123456 + ast = { + fmtn('DoubleQuotedString', 'val="\\0000"', ':0:0:"\\0000"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\000'), + hl('DoubleQuotedBody', '0'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\8"', { + -- 0123 + ast = { + fmtn('DoubleQuotedString', 'val="8"', ':0:0:"\\8"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\8'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\08"', { + -- 01234 + ast = { + fmtn('DoubleQuotedString', 'val="\\0008"', ':0:0:"\\08"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\0'), + hl('DoubleQuotedBody', '8'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\008"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\\0008"', ':0:0:"\\008"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\00'), + hl('DoubleQuotedBody', '8'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\0008"', { + -- 0123456 + ast = { + fmtn('DoubleQuotedString', 'val="\\0008"', ':0:0:"\\0008"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\000'), + hl('DoubleQuotedBody', '8'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\777"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\255"', ':0:0:"\\777"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\777'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\050"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\40"', ':0:0:"\\050"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\050'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\<C-u>"', { + -- 012345 + ast = { + fmtn('DoubleQuotedString', 'val="\\021"', ':0:0:"\\<C-u>"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\<C-u>'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\<', { + -- 012 + ast = { + fmtn('DoubleQuotedString', 'val="<"', ':0:0:"\\<'), + }, + err = { + arg = '"\\<', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuote', '"'), + hl('InvalidDoubleQuotedUnknownEscape', '\\<'), + }) + + check_parsing('"\\<"', { + -- 0123 + ast = { + fmtn('DoubleQuotedString', 'val="<"', ':0:0:"\\<"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\<'), + hl('DoubleQuote', '"'), + }) + + check_parsing('"\\<C-u"', { + -- 0123456 + ast = { + fmtn('DoubleQuotedString', 'val="<C-u"', ':0:0:"\\<C-u"'), + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedUnknownEscape', '\\<'), + hl('DoubleQuotedBody', 'C-u'), + hl('DoubleQuote', '"'), + }) + end) + itp('works with multiplication-like operators', function() + check_parsing('2+2*2', { + -- 01234 + ast = { + { + 'BinaryPlus:0:1:+', + children = { + 'Integer(val=2):0:0:2', + { + 'Multiplication:0:3:*', + children = { + 'Integer(val=2):0:2:2', + 'Integer(val=2):0:4:2', + }, + }, + }, + }, + }, + }, { + hl('Number', '2'), + hl('BinaryPlus', '+'), + hl('Number', '2'), + hl('Multiplication', '*'), + hl('Number', '2'), + }) + + check_parsing('2+2*', { + -- 0123 + ast = { + { + 'BinaryPlus:0:1:+', + children = { + 'Integer(val=2):0:0:2', + { + 'Multiplication:0:3:*', + children = { + 'Integer(val=2):0:2:2', + }, + }, + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Number', '2'), + hl('BinaryPlus', '+'), + hl('Number', '2'), + hl('Multiplication', '*'), + }) + + check_parsing('2+*2', { + -- 0123 + ast = { + { + 'BinaryPlus:0:1:+', + children = { + 'Integer(val=2):0:0:2', + { + 'Multiplication:0:2:*', + children = { + 'Missing:0:2:', + 'Integer(val=2):0:3:2', + }, + }, + }, + }, + }, + err = { + arg = '*2', + msg = 'E15: Unexpected multiplication-like operator: %.*s', + }, + }, { + hl('Number', '2'), + hl('BinaryPlus', '+'), + hl('InvalidMultiplication', '*'), + hl('Number', '2'), + }) + + check_parsing('2+2/2', { + -- 01234 + ast = { + { + 'BinaryPlus:0:1:+', + children = { + 'Integer(val=2):0:0:2', + { + 'Division:0:3:/', + children = { + 'Integer(val=2):0:2:2', + 'Integer(val=2):0:4:2', + }, + }, + }, + }, + }, + }, { + hl('Number', '2'), + hl('BinaryPlus', '+'), + hl('Number', '2'), + hl('Division', '/'), + hl('Number', '2'), + }) + + check_parsing('2+2/', { + -- 0123 + ast = { + { + 'BinaryPlus:0:1:+', + children = { + 'Integer(val=2):0:0:2', + { + 'Division:0:3:/', + children = { + 'Integer(val=2):0:2:2', + }, + }, + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Number', '2'), + hl('BinaryPlus', '+'), + hl('Number', '2'), + hl('Division', '/'), + }) + + check_parsing('2+/2', { + -- 0123 + ast = { + { + 'BinaryPlus:0:1:+', + children = { + 'Integer(val=2):0:0:2', + { + 'Division:0:2:/', + children = { + 'Missing:0:2:', + 'Integer(val=2):0:3:2', + }, + }, + }, + }, + }, + err = { + arg = '/2', + msg = 'E15: Unexpected multiplication-like operator: %.*s', + }, + }, { + hl('Number', '2'), + hl('BinaryPlus', '+'), + hl('InvalidDivision', '/'), + hl('Number', '2'), + }) + + check_parsing('2+2%2', { + -- 01234 + ast = { + { + 'BinaryPlus:0:1:+', + children = { + 'Integer(val=2):0:0:2', + { + 'Mod:0:3:%', + children = { + 'Integer(val=2):0:2:2', + 'Integer(val=2):0:4:2', + }, + }, + }, + }, + }, + }, { + hl('Number', '2'), + hl('BinaryPlus', '+'), + hl('Number', '2'), + hl('Mod', '%'), + hl('Number', '2'), + }) + + check_parsing('2+2%', { + -- 0123 + ast = { + { + 'BinaryPlus:0:1:+', + children = { + 'Integer(val=2):0:0:2', + { + 'Mod:0:3:%', + children = { + 'Integer(val=2):0:2:2', + }, + }, + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Number', '2'), + hl('BinaryPlus', '+'), + hl('Number', '2'), + hl('Mod', '%'), + }) + + check_parsing('2+%2', { + -- 0123 + ast = { + { + 'BinaryPlus:0:1:+', + children = { + 'Integer(val=2):0:0:2', + { + 'Mod:0:2:%', + children = { + 'Missing:0:2:', + 'Integer(val=2):0:3:2', + }, + }, + }, + }, + }, + err = { + arg = '%2', + msg = 'E15: Unexpected multiplication-like operator: %.*s', + }, + }, { + hl('Number', '2'), + hl('BinaryPlus', '+'), + hl('InvalidMod', '%'), + hl('Number', '2'), + }) + end) + itp('works with -', function() + check_parsing('@a', { + ast = { + 'Register(name=a):0:0:@a', + }, + }, { + hl('Register', '@a'), + }) + check_parsing('-@a', { + ast = { + { + 'UnaryMinus:0:0:-', + children = { + 'Register(name=a):0:1:@a', + }, + }, + }, + }, { + hl('UnaryMinus', '-'), + hl('Register', '@a'), + }) + check_parsing('@a-@b', { + ast = { + { + 'BinaryMinus:0:2:-', + children = { + 'Register(name=a):0:0:@a', + 'Register(name=b):0:3:@b', + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryMinus', '-'), + hl('Register', '@b'), + }) + check_parsing('@a-@b-@c', { + ast = { + { + 'BinaryMinus:0:5:-', + children = { + { + 'BinaryMinus:0:2:-', + children = { + 'Register(name=a):0:0:@a', + 'Register(name=b):0:3:@b', + }, + }, + 'Register(name=c):0:6:@c', + }, + }, + }, + }, { + hl('Register', '@a'), + hl('BinaryMinus', '-'), + hl('Register', '@b'), + hl('BinaryMinus', '-'), + hl('Register', '@c'), + }) + check_parsing('-@a-@b', { + ast = { + { + 'BinaryMinus:0:3:-', + children = { + { + 'UnaryMinus:0:0:-', + children = { + 'Register(name=a):0:1:@a', + }, + }, + 'Register(name=b):0:4:@b', + }, + }, + }, + }, { + hl('UnaryMinus', '-'), + hl('Register', '@a'), + hl('BinaryMinus', '-'), + hl('Register', '@b'), + }) + check_parsing('-@a--@b', { + ast = { + { + 'BinaryMinus:0:3:-', + children = { + { + 'UnaryMinus:0:0:-', + children = { + 'Register(name=a):0:1:@a', + }, + }, + { + 'UnaryMinus:0:4:-', + children = { + 'Register(name=b):0:5:@b', + }, + }, + }, + }, + }, + }, { + hl('UnaryMinus', '-'), + hl('Register', '@a'), + hl('BinaryMinus', '-'), + hl('UnaryMinus', '-'), + hl('Register', '@b'), + }) + check_parsing('-', { + ast = { + 'UnaryMinus:0:0:-', + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('UnaryMinus', '-'), + }) + check_parsing(' -', { + ast = { + 'UnaryMinus:0:0: -', + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('UnaryMinus', '-', 1), + }) + check_parsing('@a- ', { + ast = { + { + 'BinaryMinus:0:2:-', + children = { + 'Register(name=a):0:0:@a', + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Register', '@a'), + hl('BinaryMinus', '-'), + }) + end) + itp('works with logical operators', function() + check_parsing('a && b || c && d', { + -- 0123456789012345 + -- 0 1 + ast = { + { + 'Or:0:6: ||', + children = { + { + 'And:0:1: &&', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:4: b', + }, + }, + { + 'And:0:11: &&', + children = { + 'PlainIdentifier(scope=0,ident=c):0:9: c', + 'PlainIdentifier(scope=0,ident=d):0:14: d', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('And', '&&', 1), + hl('IdentifierName', 'b', 1), + hl('Or', '||', 1), + hl('IdentifierName', 'c', 1), + hl('And', '&&', 1), + hl('IdentifierName', 'd', 1), + }) + + check_parsing('&& a', { + -- 0123 + ast = { + { + 'And:0:0:&&', + children = { + 'Missing:0:0:', + 'PlainIdentifier(scope=0,ident=a):0:2: a', + }, + }, + }, + err = { + arg = '&& a', + msg = 'E15: Unexpected and operator: %.*s', + }, + }, { + hl('InvalidAnd', '&&'), + hl('IdentifierName', 'a', 1), + }) + + check_parsing('|| a', { + -- 0123 + ast = { + { + 'Or:0:0:||', + children = { + 'Missing:0:0:', + 'PlainIdentifier(scope=0,ident=a):0:2: a', + }, + }, + }, + err = { + arg = '|| a', + msg = 'E15: Unexpected or operator: %.*s', + }, + }, { + hl('InvalidOr', '||'), + hl('IdentifierName', 'a', 1), + }) + + check_parsing('a||', { + -- 012 + ast = { + { + 'Or:0:1:||', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('Or', '||'), + }) + + check_parsing('a&&', { + -- 012 + ast = { + { + 'And:0:1:&&', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('And', '&&'), + }) + + check_parsing('(&&)', { + -- 0123 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'And:0:1:&&', + children = { + 'Missing:0:1:', + 'Missing:0:3:', + }, + }, + }, + }, + }, + err = { + arg = '&&)', + msg = 'E15: Unexpected and operator: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('InvalidAnd', '&&'), + hl('InvalidNestingParenthesis', ')'), + }) + + check_parsing('(||)', { + -- 0123 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'Or:0:1:||', + children = { + 'Missing:0:1:', + 'Missing:0:3:', + }, + }, + }, + }, + }, + err = { + arg = '||)', + msg = 'E15: Unexpected or operator: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('InvalidOr', '||'), + hl('InvalidNestingParenthesis', ')'), + }) + + check_parsing('(a||)', { + -- 01234 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'Or:0:2:||', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'Missing:0:4:', + }, + }, + }, + }, + }, + err = { + arg = ')', + msg = 'E15: Expected value, got parenthesis: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('IdentifierName', 'a'), + hl('Or', '||'), + hl('InvalidNestingParenthesis', ')'), + }) + + check_parsing('(a&&)', { + -- 01234 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'And:0:2:&&', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'Missing:0:4:', + }, + }, + }, + }, + }, + err = { + arg = ')', + msg = 'E15: Expected value, got parenthesis: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('IdentifierName', 'a'), + hl('And', '&&'), + hl('InvalidNestingParenthesis', ')'), + }) + + check_parsing('(&&a)', { + -- 01234 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'And:0:1:&&', + children = { + 'Missing:0:1:', + 'PlainIdentifier(scope=0,ident=a):0:3:a', + }, + }, + }, + }, + }, + err = { + arg = '&&a)', + msg = 'E15: Unexpected and operator: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('InvalidAnd', '&&'), + hl('IdentifierName', 'a'), + hl('NestingParenthesis', ')'), + }) + + check_parsing('(||a)', { + -- 01234 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'Or:0:1:||', + children = { + 'Missing:0:1:', + 'PlainIdentifier(scope=0,ident=a):0:3:a', + }, + }, + }, + }, + }, + err = { + arg = '||a)', + msg = 'E15: Unexpected or operator: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('InvalidOr', '||'), + hl('IdentifierName', 'a'), + hl('NestingParenthesis', ')'), + }) + end) + itp('works with &opt', function() + check_parsing('&', { + -- 0 + ast = { + 'Option(scope=0,ident=):0:0:&', + }, + err = { + arg = '&', + msg = 'E112: Option name missing: %.*s', + }, + }, { + hl('InvalidOptionSigil', '&'), + }) + + check_parsing('&opt', { + -- 0123 + ast = { + 'Option(scope=0,ident=opt):0:0:&opt', + }, + }, { + hl('OptionSigil', '&'), + hl('OptionName', 'opt'), + }) + + check_parsing('&l:opt', { + -- 012345 + ast = { + 'Option(scope=l,ident=opt):0:0:&l:opt', + }, + }, { + hl('OptionSigil', '&'), + hl('OptionScope', 'l'), + hl('OptionScopeDelimiter', ':'), + hl('OptionName', 'opt'), + }) + + check_parsing('&g:opt', { + -- 012345 + ast = { + 'Option(scope=g,ident=opt):0:0:&g:opt', + }, + }, { + hl('OptionSigil', '&'), + hl('OptionScope', 'g'), + hl('OptionScopeDelimiter', ':'), + hl('OptionName', 'opt'), + }) + + check_parsing('&s:opt', { + -- 012345 + ast = { + { + 'Colon:0:2::', + children = { + 'Option(scope=0,ident=s):0:0:&s', + 'PlainIdentifier(scope=0,ident=opt):0:3:opt', + }, + }, + }, + err = { + arg = ':opt', + msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', + }, + }, { + hl('OptionSigil', '&'), + hl('OptionName', 's'), + hl('InvalidColon', ':'), + hl('IdentifierName', 'opt'), + }) + + check_parsing('& ', { + -- 01 + ast = { + 'Option(scope=0,ident=):0:0:&', + }, + err = { + arg = '& ', + msg = 'E112: Option name missing: %.*s', + }, + }, { + hl('InvalidOptionSigil', '&'), + }) + + check_parsing('&-', { + -- 01 + ast = { + { + 'BinaryMinus:0:1:-', + children = { + 'Option(scope=0,ident=):0:0:&', + }, + }, + }, + err = { + arg = '&-', + msg = 'E112: Option name missing: %.*s', + }, + }, { + hl('InvalidOptionSigil', '&'), + hl('BinaryMinus', '-'), + }) + + check_parsing('&A', { + -- 01 + ast = { + 'Option(scope=0,ident=A):0:0:&A', + }, + }, { + hl('OptionSigil', '&'), + hl('OptionName', 'A'), + }) + + check_parsing('&xxx_yyy', { + -- 01234567 + ast = { + { + 'OpMissing:0:4:', + children = { + 'Option(scope=0,ident=xxx):0:0:&xxx', + 'PlainIdentifier(scope=0,ident=_yyy):0:4:_yyy', + }, + }, + }, + err = { + arg = '_yyy', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('OptionSigil', '&'), + hl('OptionName', 'xxx'), + hl('InvalidIdentifierName', '_yyy'), + }, { + [1] = { + ast = { + len = 4, + err = REMOVE_THIS, + ast = { + 'Option(scope=0,ident=xxx):0:0:&xxx', + }, + }, + hl_fs = { + [3] = REMOVE_THIS, + }, + }, + }) + + check_parsing('(1+&)', { + -- 01234 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'BinaryPlus:0:2:+', + children = { + 'Integer(val=1):0:1:1', + 'Option(scope=0,ident=):0:3:&', + }, + }, + }, + }, + }, + err = { + arg = '&)', + msg = 'E112: Option name missing: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('Number', '1'), + hl('BinaryPlus', '+'), + hl('InvalidOptionSigil', '&'), + hl('NestingParenthesis', ')'), + }) + + check_parsing('(&+1)', { + -- 01234 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'BinaryPlus:0:2:+', + children = { + 'Option(scope=0,ident=):0:1:&', + 'Integer(val=1):0:3:1', + }, + }, + }, + }, + }, + err = { + arg = '&+1)', + msg = 'E112: Option name missing: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('InvalidOptionSigil', '&'), + hl('BinaryPlus', '+'), + hl('Number', '1'), + hl('NestingParenthesis', ')'), + }) + end) + itp('works with $ENV', function() + check_parsing('$', { + -- 0 + ast = { + 'Environment(ident=):0:0:$', + }, + err = { + arg = '$', + msg = 'E15: Environment variable name missing', + }, + }, { + hl('InvalidEnvironmentSigil', '$'), + }) + + check_parsing('$g:A', { + -- 0123 + ast = { + { + 'Colon:0:2::', + children = { + 'Environment(ident=g):0:0:$g', + 'PlainIdentifier(scope=0,ident=A):0:3:A', + }, + }, + }, + err = { + arg = ':A', + msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', + }, + }, { + hl('EnvironmentSigil', '$'), + hl('EnvironmentName', 'g'), + hl('InvalidColon', ':'), + hl('IdentifierName', 'A'), + }) + + check_parsing('$A', { + -- 01 + ast = { + 'Environment(ident=A):0:0:$A', + }, + }, { + hl('EnvironmentSigil', '$'), + hl('EnvironmentName', 'A'), + }) + + check_parsing('$ABC', { + -- 0123 + ast = { + 'Environment(ident=ABC):0:0:$ABC', + }, + }, { + hl('EnvironmentSigil', '$'), + hl('EnvironmentName', 'ABC'), + }) + + check_parsing('(1+$)', { + -- 01234 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'BinaryPlus:0:2:+', + children = { + 'Integer(val=1):0:1:1', + 'Environment(ident=):0:3:$', + }, + }, + }, + }, + }, + err = { + arg = '$)', + msg = 'E15: Environment variable name missing', + }, + }, { + hl('NestingParenthesis', '('), + hl('Number', '1'), + hl('BinaryPlus', '+'), + hl('InvalidEnvironmentSigil', '$'), + hl('NestingParenthesis', ')'), + }) + + check_parsing('($+1)', { + -- 01234 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'BinaryPlus:0:2:+', + children = { + 'Environment(ident=):0:1:$', + 'Integer(val=1):0:3:1', + }, + }, + }, + }, + }, + err = { + arg = '$+1)', + msg = 'E15: Environment variable name missing', + }, + }, { + hl('NestingParenthesis', '('), + hl('InvalidEnvironmentSigil', '$'), + hl('BinaryPlus', '+'), + hl('Number', '1'), + hl('NestingParenthesis', ')'), + }) + + check_parsing('$_ABC', { + -- 01234 + ast = { + 'Environment(ident=_ABC):0:0:$_ABC', + }, + }, { + hl('EnvironmentSigil', '$'), + hl('EnvironmentName', '_ABC'), + }) + + check_parsing('$_', { + -- 01 + ast = { + 'Environment(ident=_):0:0:$_', + }, + }, { + hl('EnvironmentSigil', '$'), + hl('EnvironmentName', '_'), + }) + + check_parsing('$ABC_DEF', { + -- 01234567 + ast = { + 'Environment(ident=ABC_DEF):0:0:$ABC_DEF', + }, + }, { + hl('EnvironmentSigil', '$'), + hl('EnvironmentName', 'ABC_DEF'), + }) + end) + itp('works with unary !', function() + check_parsing('!', { + -- 0 + ast = { + 'Not:0:0:!', + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Not', '!'), + }) + + check_parsing('!!', { + -- 01 + ast = { + { + 'Not:0:0:!', + children = { + 'Not:0:1:!', + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Not', '!'), + hl('Not', '!'), + }) + + check_parsing('!!1', { + -- 012 + ast = { + { + 'Not:0:0:!', + children = { + { + 'Not:0:1:!', + children = { + 'Integer(val=1):0:2:1', + }, + }, + }, + }, + }, + }, { + hl('Not', '!'), + hl('Not', '!'), + hl('Number', '1'), + }) + + check_parsing('!1', { + -- 01 + ast = { + { + 'Not:0:0:!', + children = { + 'Integer(val=1):0:1:1', + }, + }, + }, + }, { + hl('Not', '!'), + hl('Number', '1'), + }) + + check_parsing('(!1)', { + -- 0123 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'Not:0:1:!', + children = { + 'Integer(val=1):0:2:1', + }, + }, + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Not', '!'), + hl('Number', '1'), + hl('NestingParenthesis', ')'), + }) + + check_parsing('(!)', { + -- 012 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'Not:0:1:!', + children = { + 'Missing:0:2:', + }, + }, + }, + }, + }, + err = { + arg = ')', + msg = 'E15: Expected value, got parenthesis: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('Not', '!'), + hl('InvalidNestingParenthesis', ')'), + }) + + check_parsing('(1!2)', { + -- 01234 + ast = { + { + 'Nested:0:0:(', + children = { + { + 'OpMissing:0:2:', + children = { + 'Integer(val=1):0:1:1', + { + 'Not:0:2:!', + children = { + 'Integer(val=2):0:3:2', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '!2)', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('NestingParenthesis', '('), + hl('Number', '1'), + hl('InvalidNot', '!'), + hl('Number', '2'), + hl('NestingParenthesis', ')'), + }) + + check_parsing('1!2', { + -- 012 + ast = { + { + 'OpMissing:0:1:', + children = { + 'Integer(val=1):0:0:1', + { + 'Not:0:1:!', + children = { + 'Integer(val=2):0:2:2', + }, + }, + }, + }, + }, + err = { + arg = '!2', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('Number', '1'), + hl('InvalidNot', '!'), + hl('Number', '2'), + }, { + [1] = { + ast = { + len = 1, + err = REMOVE_THIS, + ast = { + 'Integer(val=1):0:0:1', + }, + }, + hl_fs = { + [2] = REMOVE_THIS, + [3] = REMOVE_THIS, + }, + }, + }) + end) + itp('highlights numbers with prefix', function() + check_parsing('0xABCDEF', { + -- 01234567 + ast = { + 'Integer(val=11259375):0:0:0xABCDEF', + }, + }, { + hl('NumberPrefix', '0x'), + hl('Number', 'ABCDEF'), + }) + + check_parsing('0Xabcdef', { + -- 01234567 + ast = { + 'Integer(val=11259375):0:0:0Xabcdef', + }, + }, { + hl('NumberPrefix', '0X'), + hl('Number', 'abcdef'), + }) + + check_parsing('0XABCDEF', { + -- 01234567 + ast = { + 'Integer(val=11259375):0:0:0XABCDEF', + }, + }, { + hl('NumberPrefix', '0X'), + hl('Number', 'ABCDEF'), + }) + + check_parsing('0xabcdef', { + -- 01234567 + ast = { + 'Integer(val=11259375):0:0:0xabcdef', + }, + }, { + hl('NumberPrefix', '0x'), + hl('Number', 'abcdef'), + }) + + check_parsing('0b001', { + -- 01234 + ast = { + 'Integer(val=1):0:0:0b001', + }, + }, { + hl('NumberPrefix', '0b'), + hl('Number', '001'), + }) + + check_parsing('0B001', { + -- 01234 + ast = { + 'Integer(val=1):0:0:0B001', + }, + }, { + hl('NumberPrefix', '0B'), + hl('Number', '001'), + }) + + check_parsing('0B00', { + -- 0123 + ast = { + 'Integer(val=0):0:0:0B00', + }, + }, { + hl('NumberPrefix', '0B'), + hl('Number', '00'), + }) + + check_parsing('00', { + -- 01 + ast = { + 'Integer(val=0):0:0:00', + }, + }, { + hl('NumberPrefix', '0'), + hl('Number', '0'), + }) + + check_parsing('001', { + -- 012 + ast = { + 'Integer(val=1):0:0:001', + }, + }, { + hl('NumberPrefix', '0'), + hl('Number', '01'), + }) + + check_parsing('01', { + -- 01 + ast = { + 'Integer(val=1):0:0:01', + }, + }, { + hl('NumberPrefix', '0'), + hl('Number', '1'), + }) + + check_parsing('1', { + -- 0 + ast = { + 'Integer(val=1):0:0:1', + }, + }, { + hl('Number', '1'), + }) + end) + itp('works (KLEE tests)', function() + check_parsing('\0002&A:\000', { + len = 0, + ast = nil, + err = { + arg = '\0002&A:\000', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + }, { + [2] = { + ast = { + len = REMOVE_THIS, + ast = { + { + 'Colon:0:4::', + children = { + { + 'OpMissing:0:2:', + children = { + 'Integer(val=2):0:1:2', + 'Option(scope=0,ident=A):0:2:&A', + }, + }, + }, + }, + }, + err = { + msg = 'E15: Unexpected EOC character: %.*s', + }, + }, + hl_fs = { + hl('InvalidSpacing', '\0'), + hl('Number', '2'), + hl('InvalidOptionSigil', '&'), + hl('InvalidOptionName', 'A'), + hl('InvalidColon', ':'), + hl('InvalidSpacing', '\0'), + }, + }, + [3] = { + ast = { + len = 2, + ast = { + 'Integer(val=2):0:1:2', + }, + err = { + msg = 'E15: Unexpected EOC character: %.*s', + }, + }, + hl_fs = { + hl('InvalidSpacing', '\0'), + hl('Number', '2'), + }, + }, + }) + check_parsing({data='01', size=1}, { + len = 1, + ast = { + 'Integer(val=0):0:0:0', + }, + }, { + hl('Number', '0'), + }) + check_parsing({data='001', size=2}, { + len = 2, + ast = { + 'Integer(val=0):0:0:00', + }, + }, { + hl('NumberPrefix', '0'), + hl('Number', '0'), + }) + check_parsing('"\\U\\', { + -- 0123 + ast = { + [[DoubleQuotedString(val="U\\"):0:0:"\U\]], + }, + err = { + arg = '"\\U\\', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuote', '"'), + hl('InvalidDoubleQuotedUnknownEscape', '\\U'), + hl('InvalidDoubleQuotedBody', '\\'), + }) + check_parsing('"\\U', { + -- 012 + ast = { + fmtn('DoubleQuotedString', 'val="U"', ':0:0:"\\U'), + }, + err = { + arg = '"\\U', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuote', '"'), + hl('InvalidDoubleQuotedUnknownEscape', '\\U'), + }) + check_parsing('|"\\U\\', { + -- 01234 + len = 0, + err = { + arg = '|"\\U\\', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + }, { + [2] = { + ast = { + len = REMOVE_THIS, + ast = { + { + 'Or:0:0:|', + children = { + 'Missing:0:0:', + fmtn('DoubleQuotedString', 'val="U\\\\"', ':0:1:"\\U\\'), + }, + }, + }, + err = { + msg = 'E15: Unexpected EOC character: %.*s', + }, + }, + hl_fs = { + hl('InvalidOr', '|'), + hl('InvalidDoubleQuote', '"'), + hl('InvalidDoubleQuotedUnknownEscape', '\\U'), + hl('InvalidDoubleQuotedBody', '\\'), + }, + }, + }) + check_parsing('|"\\e"', { + -- 01234 + len = 0, + err = { + arg = '|"\\e"', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + }, { + [2] = { + ast = { + len = REMOVE_THIS, + ast = { + { + 'Or:0:0:|', + children = { + 'Missing:0:0:', + fmtn('DoubleQuotedString', 'val="\\027"', ':0:1:"\\e"'), + }, + }, + }, + err = { + msg = 'E15: Unexpected EOC character: %.*s', + }, + }, + hl_fs = { + hl('InvalidOr', '|'), + hl('DoubleQuote', '"'), + hl('DoubleQuotedEscape', '\\e'), + hl('DoubleQuote', '"'), + }, + }, + }) + check_parsing('|\029', { + -- 01 + len = 0, + err = { + arg = '|\029', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + }, { + [2] = { + ast = { + len = REMOVE_THIS, + ast = { + { + 'Or:0:0:|', + children = { + 'Missing:0:0:', + 'PlainIdentifier(scope=0,ident=\029):0:1:\029', + }, + }, + }, + err = { + msg = 'E15: Unexpected EOC character: %.*s', + }, + }, + hl_fs = { + hl('InvalidOr', '|'), + hl('InvalidIdentifierName', '\029'), + }, + }, + }) + check_parsing('"\\<', { + -- 012 + ast = { + fmtn('DoubleQuotedString', 'val="<"', ':0:0:"\\<'), + }, + err = { + arg = '"\\<', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuote', '"'), + hl('InvalidDoubleQuotedUnknownEscape', '\\<'), + }) + check_parsing('"\\1', { + -- 01 2 + ast = { + fmtn('DoubleQuotedString', 'val="\\001"', ':0:0:"\\1'), + }, + err = { + arg = '"\\1', + msg = 'E114: Missing double quote: %.*s', + }, + }, { + hl('InvalidDoubleQuote', '"'), + hl('InvalidDoubleQuotedEscape', '\\1'), + }) + check_parsing('}l', { + -- 01 + ast = { + { + 'OpMissing:0:1:', + children = { + fmtn('UnknownFigure', '---', ':0:0:'), + 'PlainIdentifier(scope=0,ident=l):0:1:l', + }, + }, + }, + err = { + arg = '}l', + msg = 'E15: Unexpected closing figure brace: %.*s', + }, + }, { + hl('InvalidFigureBrace', '}'), + hl('InvalidIdentifierName', 'l'), + }, { + [1] = { + ast = { + len = 1, + ast = { + fmtn('UnknownFigure', '---', ':0:0:'), + }, + }, + hl_fs = { + [2] = REMOVE_THIS, + }, + }, + }) + check_parsing(':?\000\000\000\000\000\000\000', { + len = 2, + ast = { + { + 'Colon:0:0::', + children = { + 'Missing:0:0:', + { + 'Ternary:0:1:?', + children = { + 'Missing:0:1:', + 'TernaryValue:0:1:?', + }, + }, + }, + }, + }, + err = { + arg = ':?\000\000\000\000\000\000\000', + msg = 'E15: Colon outside of dictionary or ternary operator: %.*s', + }, + }, { + hl('InvalidColon', ':'), + hl('InvalidTernary', '?'), + }, { + [2] = { + ast = { + len = REMOVE_THIS, + }, + hl_fs = { + [3] = hl('InvalidSpacing', '\0'), + [4] = hl('InvalidSpacing', '\0'), + [5] = hl('InvalidSpacing', '\0'), + [6] = hl('InvalidSpacing', '\0'), + [7] = hl('InvalidSpacing', '\0'), + [8] = hl('InvalidSpacing', '\0'), + [9] = hl('InvalidSpacing', '\0'), + }, + }, + }) + end) + itp('works with assignments', function() + check_asgn_parsing('a=b', { + -- 012 + ast = { + { + 'Assignment(Plain):0:1:=', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('PlainAssignment', '='), + hl('IdentifierName', 'b'), + }) + + check_asgn_parsing('a+=b', { + -- 0123 + ast = { + { + 'Assignment(Add):0:1:+=', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('AssignmentWithAddition', '+='), + hl('IdentifierName', 'b'), + }) + + check_asgn_parsing('a-=b', { + -- 0123 + ast = { + { + 'Assignment(Subtract):0:1:-=', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('AssignmentWithSubtraction', '-='), + hl('IdentifierName', 'b'), + }) + + check_asgn_parsing('a.=b', { + -- 0123 + ast = { + { + 'Assignment(Concat):0:1:.=', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('AssignmentWithConcatenation', '.='), + hl('IdentifierName', 'b'), + }) + + check_asgn_parsing('a', { + -- 0 + ast = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + }, { + hl('IdentifierName', 'a'), + }) + + check_asgn_parsing('a b', { + -- 012 + ast = { + { + 'OpMissing:0:1:', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainIdentifier(scope=0,ident=b):0:1: b', + }, + }, + }, + err = { + arg = 'b', + msg = 'E15: Expected assignment operator or subscript: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('InvalidSpacing', ' '), + hl('IdentifierName', 'b'), + }, { + [5] = { + ast = { + len = 2, + ast = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + err = REMOVE_THIS, + }, + hl_fs = { + [2] = REMOVE_THIS, + [3] = REMOVE_THIS, + } + }, + }) + + check_asgn_parsing('[a, b, c]', { + -- 012345678 + ast = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'Comma:0:5:,', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + 'PlainIdentifier(scope=0,ident=c):0:6: c', + }, + }, + }, + }, + }, + }, + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b', 1), + hl('Comma', ','), + hl('IdentifierName', 'c', 1), + hl('List', ']'), + }) + + check_asgn_parsing('[a, b]', { + -- 012345 + ast = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:2:,', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3: b', + }, + }, + }, + }, + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('Comma', ','), + hl('IdentifierName', 'b', 1), + hl('List', ']'), + }) + + check_asgn_parsing('[a]', { + -- 012 + ast = { + { + 'ListLiteral:0:0:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('List', ']'), + }) + + check_asgn_parsing('[]', { + -- 01 + ast = { + 'ListLiteral:0:0:[', + }, + err = { + arg = ']', + msg = 'E475: Unable to assign to empty list: %.*s', + }, + }, { + hl('List', '['), + hl('InvalidList', ']'), + }) + + check_asgn_parsing('a[1] += 3', { + -- 012345678 + ast = { + { + 'Assignment(Add):0:4: +=', + children = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'Integer(val=1):0:2:1', + }, + }, + 'Integer(val=3):0:7: 3', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('Number', '1'), + hl('SubscriptBracket', ']'), + hl('AssignmentWithAddition', '+=', 1), + hl('Number', '3', 1), + }) + + check_asgn_parsing('a[1 + 2] += 3', { + -- 0123456789012 + -- 0 1 + ast = { + { + 'Assignment(Add):0:8: +=', + children = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'BinaryPlus:0:3: +', + children = { + 'Integer(val=1):0:2:1', + 'Integer(val=2):0:5: 2', + }, + }, + }, + }, + 'Integer(val=3):0:11: 3', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('Number', '1'), + hl('BinaryPlus', '+', 1), + hl('Number', '2', 1), + hl('SubscriptBracket', ']'), + hl('AssignmentWithAddition', '+=', 1), + hl('Number', '3', 1), + }) + + check_asgn_parsing('a[{-> {b{3}: 4}[5]}()] += 6', { + -- 012345678901234567890123456 + -- 0 1 2 + ast = { + { + 'Assignment(Add):0:22: +=', + children = { + { + 'Subscript:0:1:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'Call:0:19:(', + children = { + { + fmtn('Lambda', '\\di', ':0:2:{'), + children = { + { + 'Arrow:0:3:->', + children = { + { + 'Subscript:0:15:[', + children = { + { + fmtn('DictLiteral', '-di', ':0:5: {'), + children = { + { + 'Colon:0:11::', + children = { + { + 'ComplexIdentifier:0:8:', + children = { + 'PlainIdentifier(scope=0,ident=b):0:7:b', + { + fmtn('CurlyBracesIdentifier', '--i', ':0:8:{'), + children = { + 'Integer(val=3):0:9:3', + }, + }, + }, + }, + 'Integer(val=4):0:12: 4', + }, + }, + }, + }, + 'Integer(val=5):0:16:5', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + 'Integer(val=6):0:25: 6', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('Lambda', '{'), + hl('Arrow', '->'), + hl('Dict', '{', 1), + hl('IdentifierName', 'b'), + hl('Curly', '{'), + hl('Number', '3'), + hl('Curly', '}'), + hl('Colon', ':'), + hl('Number', '4', 1), + hl('Dict', '}'), + hl('SubscriptBracket', '['), + hl('Number', '5'), + hl('SubscriptBracket', ']'), + hl('Lambda', '}'), + hl('CallingParenthesis', '('), + hl('CallingParenthesis', ')'), + hl('SubscriptBracket', ']'), + hl('AssignmentWithAddition', '+=', 1), + hl('Number', '6', 1), + }) + + check_asgn_parsing('a{1}.2[{-> {b{3}: 4}[5]}()]', { + -- 012345678901234567890123456 + -- 0 1 2 + ast = { + { + 'Subscript:0:6:[', + children = { + { + 'ConcatOrSubscript:0:4:.', + children = { + { + 'ComplexIdentifier:0:1:', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + fmtn('CurlyBracesIdentifier', '--i', ':0:1:{'), + children = { + 'Integer(val=1):0:2:1', + }, + }, + }, + }, + 'PlainKey(key=2):0:5:2', + }, + }, + { + 'Call:0:24:(', + children = { + { + fmtn('Lambda', '\\di', ':0:7:{'), + children = { + { + 'Arrow:0:8:->', + children = { + { + 'Subscript:0:20:[', + children = { + { + fmtn('DictLiteral', '-di', ':0:10: {'), + children = { + { + 'Colon:0:16::', + children = { + { + 'ComplexIdentifier:0:13:', + children = { + 'PlainIdentifier(scope=0,ident=b):0:12:b', + { + fmtn('CurlyBracesIdentifier', '--i', ':0:13:{'), + children = { + 'Integer(val=3):0:14:3', + }, + }, + }, + }, + 'Integer(val=4):0:17: 4', + }, + }, + }, + }, + 'Integer(val=5):0:21:5', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Curly', '{'), + hl('Number', '1'), + hl('Curly', '}'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '2'), + hl('SubscriptBracket', '['), + hl('Lambda', '{'), + hl('Arrow', '->'), + hl('Dict', '{', 1), + hl('IdentifierName', 'b'), + hl('Curly', '{'), + hl('Number', '3'), + hl('Curly', '}'), + hl('Colon', ':'), + hl('Number', '4', 1), + hl('Dict', '}'), + hl('SubscriptBracket', '['), + hl('Number', '5'), + hl('SubscriptBracket', ']'), + hl('Lambda', '}'), + hl('CallingParenthesis', '('), + hl('CallingParenthesis', ')'), + hl('SubscriptBracket', ']'), + }) + + check_asgn_parsing('a', { + -- 0 + ast = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + }, + }, { + hl('IdentifierName', 'a'), + }) + + check_asgn_parsing('{a}', { + -- 012 + ast = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:0:{'), + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + }, + }, { + hl('FigureBrace', '{'), + hl('IdentifierName', 'a'), + hl('Curly', '}'), + }) + + check_asgn_parsing('{a}b', { + -- 0123 + ast = { + { + 'ComplexIdentifier:0:3:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:0:{'), + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + }, + }, { + hl('FigureBrace', '{'), + hl('IdentifierName', 'a'), + hl('Curly', '}'), + hl('IdentifierName', 'b'), + }) + + check_asgn_parsing('a{b}c', { + -- 01234 + ast = { + { + 'ComplexIdentifier:0:1:', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'ComplexIdentifier:0:4:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:1:{'), + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + 'PlainIdentifier(scope=0,ident=c):0:4:c', + }, + }, + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Curly', '{'), + hl('IdentifierName', 'b'), + hl('Curly', '}'), + hl('IdentifierName', 'c'), + }) + + check_asgn_parsing('a{b}c[0]', { + -- 01234567 + ast = { + { + 'Subscript:0:5:[', + children = { + { + 'ComplexIdentifier:0:1:', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'ComplexIdentifier:0:4:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:1:{'), + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + 'PlainIdentifier(scope=0,ident=c):0:4:c', + }, + }, + }, + }, + 'Integer(val=0):0:6:0', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Curly', '{'), + hl('IdentifierName', 'b'), + hl('Curly', '}'), + hl('IdentifierName', 'c'), + hl('SubscriptBracket', '['), + hl('Number', '0'), + hl('SubscriptBracket', ']'), + }) + + check_asgn_parsing('a{b}c.0', { + -- 0123456 + ast = { + { + 'ConcatOrSubscript:0:5:.', + children = { + { + 'ComplexIdentifier:0:1:', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'ComplexIdentifier:0:4:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:1:{'), + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + 'PlainIdentifier(scope=0,ident=c):0:4:c', + }, + }, + }, + }, + 'PlainKey(key=0):0:6:0', + }, + }, + }, + }, { + hl('IdentifierName', 'a'), + hl('Curly', '{'), + hl('IdentifierName', 'b'), + hl('Curly', '}'), + hl('IdentifierName', 'c'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '0'), + }) + + check_asgn_parsing('[a{b}c[0].0]', { + -- 012345678901 + -- 0 1 + ast = { + { + 'ListLiteral:0:0:[', + children = { + { + 'ConcatOrSubscript:0:9:.', + children = { + { + 'Subscript:0:6:[', + children = { + { + 'ComplexIdentifier:0:2:', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + { + 'ComplexIdentifier:0:5:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:2:{'), + children = { + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + 'PlainIdentifier(scope=0,ident=c):0:5:c', + }, + }, + }, + }, + 'Integer(val=0):0:7:0', + }, + }, + 'PlainKey(key=0):0:10:0', + }, + }, + }, + }, + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('Curly', '{'), + hl('IdentifierName', 'b'), + hl('Curly', '}'), + hl('IdentifierName', 'c'), + hl('SubscriptBracket', '['), + hl('Number', '0'), + hl('SubscriptBracket', ']'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', '0'), + hl('List', ']'), + }) + + check_asgn_parsing('{a}{b}', { + -- 012345 + ast = { + { + 'ComplexIdentifier:0:3:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:0:{'), + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + { + fmtn('CurlyBracesIdentifier', '--i', ':0:3:{'), + children = { + 'PlainIdentifier(scope=0,ident=b):0:4:b', + }, + }, + }, + }, + }, + }, { + hl('FigureBrace', '{'), + hl('IdentifierName', 'a'), + hl('Curly', '}'), + hl('Curly', '{'), + hl('IdentifierName', 'b'), + hl('Curly', '}'), + }) + + check_asgn_parsing('a.b{c}{d}', { + -- 012345678 + ast = { + { + 'OpMissing:0:3:', + children = { + { + 'ConcatOrSubscript:0:1:.', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + 'PlainKey(key=b):0:2:b', + }, + }, + { + 'ComplexIdentifier:0:6:', + children = { + { + fmtn('CurlyBracesIdentifier', '--i', ':0:3:{'), + children = { + 'PlainIdentifier(scope=0,ident=c):0:4:c', + }, + }, + { + fmtn('CurlyBracesIdentifier', '--i', ':0:6:{'), + children = { + 'PlainIdentifier(scope=0,ident=d):0:7:d', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '{c}{d}', + msg = 'E15: Missing operator: %.*s', + }, + }, { + hl('IdentifierName', 'a'), + hl('ConcatOrSubscript', '.'), + hl('IdentifierKey', 'b'), + hl('InvalidFigureBrace', '{'), + hl('IdentifierName', 'c'), + hl('Curly', '}'), + hl('Curly', '{'), + hl('IdentifierName', 'd'), + hl('Curly', '}'), + }) + + check_asgn_parsing('[a] = 1', { + -- 0123456 + ast = { + { + 'Assignment(Plain):0:3: =', + children = { + { + 'ListLiteral:0:0:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + 'Integer(val=1):0:5: 1', + }, + }, + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('List', ']'), + hl('PlainAssignment', '=', 1), + hl('Number', '1', 1), + }) + + check_asgn_parsing('[a[b], [c, [d, [e]]]] = 1', { + -- 0123456789012345678901234 + -- 0 1 2 + ast = { + { + 'Assignment(Plain):0:21: =', + children = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:5:,', + children = { + { + 'Subscript:0:2:[', + children = { + 'PlainIdentifier(scope=0,ident=a):0:1:a', + 'PlainIdentifier(scope=0,ident=b):0:3:b', + }, + }, + { + 'ListLiteral:0:6: [', + children = { + { + 'Comma:0:9:,', + children = { + 'PlainIdentifier(scope=0,ident=c):0:8:c', + { + 'ListLiteral:0:10: [', + children = { + { + 'Comma:0:13:,', + children = { + 'PlainIdentifier(scope=0,ident=d):0:12:d', + { + 'ListLiteral:0:14: [', + children = { + 'PlainIdentifier(scope=0,ident=e):0:16:e', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + 'Integer(val=1):0:23: 1', + }, + }, + }, + err = { + arg = '[c, [d, [e]]]] = 1', + msg = 'E475: Nested lists not allowed when assigning: %.*s', + }, + }, { + hl('List', '['), + hl('IdentifierName', 'a'), + hl('SubscriptBracket', '['), + hl('IdentifierName', 'b'), + hl('SubscriptBracket', ']'), + hl('Comma', ','), + hl('InvalidList', '[', 1), + hl('IdentifierName', 'c'), + hl('Comma', ','), + hl('InvalidList', '[', 1), + hl('IdentifierName', 'd'), + hl('Comma', ','), + hl('InvalidList', '[', 1), + hl('IdentifierName', 'e'), + hl('List', ']'), + hl('List', ']'), + hl('List', ']'), + hl('List', ']'), + hl('PlainAssignment', '=', 1), + hl('Number', '1', 1), + }) + + check_asgn_parsing('$X += 1', { + -- 0123456 + ast = { + { + 'Assignment(Add):0:2: +=', + children = { + 'Environment(ident=X):0:0:$X', + 'Integer(val=1):0:5: 1', + }, + }, + }, + }, { + hl('EnvironmentSigil', '$'), + hl('EnvironmentName', 'X'), + hl('AssignmentWithAddition', '+=', 1), + hl('Number', '1', 1), + }) + + check_asgn_parsing('@a .= 1', { + -- 0123456 + ast = { + { + 'Assignment(Concat):0:2: .=', + children = { + 'Register(name=a):0:0:@a', + 'Integer(val=1):0:5: 1', + }, + }, + }, + }, { + hl('Register', '@a'), + hl('AssignmentWithConcatenation', '.=', 1), + hl('Number', '1', 1), + }) + + check_asgn_parsing('&option -= 1', { + -- 012345678901 + -- 0 1 + ast = { + { + 'Assignment(Subtract):0:7: -=', + children = { + 'Option(scope=0,ident=option):0:0:&option', + 'Integer(val=1):0:10: 1', + }, + }, + }, + }, { + hl('OptionSigil', '&'), + hl('OptionName', 'option'), + hl('AssignmentWithSubtraction', '-=', 1), + hl('Number', '1', 1), + }) + + check_asgn_parsing('[$X, @a, &l:option] = [1, 2, 3]', { + -- 0123456789012345678901234567890 + -- 0 1 2 3 + ast = { + { + 'Assignment(Plain):0:19: =', + children = { + { + 'ListLiteral:0:0:[', + children = { + { + 'Comma:0:3:,', + children = { + 'Environment(ident=X):0:1:$X', + { + 'Comma:0:7:,', + children = { + 'Register(name=a):0:4: @a', + 'Option(scope=l,ident=option):0:8: &l:option', + }, + }, + }, + }, + }, + }, + { + 'ListLiteral:0:21: [', + children = { + { + 'Comma:0:24:,', + children = { + 'Integer(val=1):0:23:1', + { + 'Comma:0:27:,', + children = { + 'Integer(val=2):0:25: 2', + 'Integer(val=3):0:28: 3', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('List', '['), + hl('EnvironmentSigil', '$'), + hl('EnvironmentName', 'X'), + hl('Comma', ','), + hl('Register', '@a', 1), + hl('Comma', ','), + hl('OptionSigil', '&', 1), + hl('OptionScope', 'l'), + hl('OptionScopeDelimiter', ':'), + hl('OptionName', 'option'), + hl('List', ']'), + hl('PlainAssignment', '=', 1), + hl('List', '[', 1), + hl('Number', '1'), + hl('Comma', ','), + hl('Number', '2', 1), + hl('Comma', ','), + hl('Number', '3', 1), + hl('List', ']'), + }) + end) + itp('works with non-ASCII characters', function() + check_parsing('"«»"«»', { + -- 013568 + ast = { + { + 'OpMissing:0:6:', + children = { + 'DoubleQuotedString(val="«»"):0:0:"«»"', + { + 'ComplexIdentifier:0:8:', + children = { + 'PlainIdentifier(scope=0,ident=«):0:6:«', + 'PlainIdentifier(scope=0,ident=»):0:8:»', + }, + }, + }, + }, + }, + err = { + arg = '«»', + msg = 'E15: Unidentified character: %.*s', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedBody', '«»'), + hl('DoubleQuote', '"'), + hl('InvalidIdentifierName', '«'), + hl('InvalidIdentifierName', '»'), + }, { + [1] = { + ast = { + ast = { + 'DoubleQuotedString(val="«»"):0:0:"«»"', + }, + len = 6, + }, + hl_fs = { + [5] = REMOVE_THIS, + [4] = REMOVE_THIS, + }, + }, + }) + check_parsing('"\192"\192"foo"', { + -- 01 23 45678 + ast = { + { + 'OpMissing:0:3:', + children = { + 'DoubleQuotedString(val="\192"):0:0:"\192"', + { + 'OpMissing:0:4:', + children = { + 'PlainIdentifier(scope=0,ident=\192):0:3:\192', + 'DoubleQuotedString(val="foo"):0:4:"foo"', + }, + }, + }, + }, + }, + err = { + arg = '\192"foo"', + msg = 'E15: Unidentified character: %.*s', + }, + }, { + hl('DoubleQuote', '"'), + hl('DoubleQuotedBody', '\192'), + hl('DoubleQuote', '"'), + hl('InvalidIdentifierName', '\192'), + hl('InvalidDoubleQuote', '"'), + hl('InvalidDoubleQuotedBody', 'foo'), + hl('InvalidDoubleQuote', '"'), + }, { + [1] = { + ast = { + ast = { + 'DoubleQuotedString(val="\192"):0:0:"\192"', + }, + len = 3, + }, + hl_fs = { + [4] = REMOVE_THIS, + [5] = REMOVE_THIS, + [6] = REMOVE_THIS, + [7] = REMOVE_THIS, + }, + }, + }) + end) +end diff --git a/test/unit/viml/helpers.lua b/test/unit/viml/helpers.lua new file mode 100644 index 0000000000..9d8102e023 --- /dev/null +++ b/test/unit/viml/helpers.lua @@ -0,0 +1,130 @@ +local helpers = require('test.unit.helpers')(nil) + +local ffi = helpers.ffi +local cimport = helpers.cimport +local kvi_new = helpers.kvi_new +local kvi_init = helpers.kvi_init +local conv_enum = helpers.conv_enum +local make_enum_conv_tab = helpers.make_enum_conv_tab + +local lib = cimport('./src/nvim/viml/parser/expressions.h') + +local function new_pstate(strings) + local strings_idx = 0 + local function get_line(_, ret_pline) + strings_idx = strings_idx + 1 + local str = strings[strings_idx] + local data, size + if type(str) == 'string' then + data = str + size = #str + elseif type(str) == 'nil' then + data = nil + size = 0 + elseif type(str) == 'table' then + data = str.data + size = str.size + elseif type(str) == 'function' then + data, size = str() + size = size or 0 + end + ret_pline.data = data + ret_pline.size = size + ret_pline.allocated = false + end + local state = { + reader = { + get_line = get_line, + cookie = nil, + conv = { + vc_type = 0, + vc_factor = 1, + vc_fail = false, + }, + }, + pos = { line = 0, col = 0 }, + colors = kvi_new('ParserHighlight'), + can_continuate = false, + } + local ret = ffi.new('ParserState', state) + kvi_init(ret.reader.lines) + kvi_init(ret.stack) + return ret +end + +local function pline2lua(pline) + return ffi.string(pline.data, pline.size) +end + +local function pstate_str(pstate, start, len) + local str = nil + local err = nil + if start.line < pstate.reader.lines.size then + local pstr = pline2lua(pstate.reader.lines.items[start.line]) + if start.col >= #pstr then + err = 'start.col >= #pstr' + else + str = pstr:sub(tonumber(start.col) + 1, tonumber(start.col + len)) + end + else + err = 'start.line >= pstate.reader.lines.size' + end + return str, err +end + +local function pstate_set_str(pstate, start, len, ret) + ret = ret or {} + ret.start = { + line = tonumber(start.line), + col = tonumber(start.col) + } + ret.len = tonumber(len) + ret.str, ret.error = pstate_str(pstate, start, len) + return ret +end + +local eltkn_cmp_type_tab +make_enum_conv_tab(lib, { + 'kExprCmpEqual', + 'kExprCmpMatches', + 'kExprCmpGreater', + 'kExprCmpGreaterOrEqual', + 'kExprCmpIdentical', +}, 'kExprCmp', function(ret) eltkn_cmp_type_tab = ret end) + +local function conv_cmp_type(typ) + return conv_enum(eltkn_cmp_type_tab, typ) +end + +local ccs_tab +make_enum_conv_tab(lib, { + 'kCCStrategyUseOption', + 'kCCStrategyMatchCase', + 'kCCStrategyIgnoreCase', +}, 'kCCStrategy', function(ret) ccs_tab = ret end) + +local function conv_ccs(ccs) + return conv_enum(ccs_tab, ccs) +end + +local expr_asgn_type_tab +make_enum_conv_tab(lib, { + 'kExprAsgnPlain', + 'kExprAsgnAdd', + 'kExprAsgnSubtract', + 'kExprAsgnConcat', +}, 'kExprAsgn', function(ret) expr_asgn_type_tab = ret end) + +local function conv_expr_asgn_type(expr_asgn_type) + return conv_enum(expr_asgn_type_tab, expr_asgn_type) +end + +return { + conv_ccs = conv_ccs, + pline2lua = pline2lua, + pstate_str = pstate_str, + new_pstate = new_pstate, + conv_cmp_type = conv_cmp_type, + pstate_set_str = pstate_set_str, + conv_expr_asgn_type = conv_expr_asgn_type, +} diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index ab9ff1f60d..30503e3c4a 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -2,6 +2,9 @@ cmake_minimum_required (VERSION 2.8.7) project(NVIM_DEPS) +# Needed for: check_c_compiler_flag() +include(CheckCCompilerFlag) + # Point CMake at any custom modules we may ship list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") @@ -11,6 +14,13 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() +set(DEFAULT_MAKE_CFLAGS CFLAGS+=-g) + +check_c_compiler_flag(-Og HAS_OG_FLAG) +if(HAS_OG_FLAG) + set(DEFAULT_MAKE_CFLAGS CFLAGS+=-Og ${DEFAULT_MAKE_CFLAGS}) +endif() + set(DEPS_INSTALL_DIR "${CMAKE_BINARY_DIR}/usr" CACHE PATH "Dependencies install directory.") set(DEPS_BIN_DIR "${DEPS_INSTALL_DIR}/bin" CACHE PATH "Dependencies binary install directory.") set(DEPS_LIB_DIR "${DEPS_INSTALL_DIR}/lib" CACHE PATH "Dependencies library install directory.") @@ -19,6 +29,7 @@ set(DEPS_DOWNLOAD_DIR "${DEPS_BUILD_DIR}/downloads" CACHE PATH "Dependencies dow option(USE_BUNDLED "Use bundled dependencies." ON) +option(USE_BUNDLED_GPERF "Use the bundled version of gperf." ${USE_BUNDLED}) option(USE_BUNDLED_JEMALLOC "Use the bundled jemalloc." ${USE_BUNDLED}) option(USE_BUNDLED_UNIBILIUM "Use the bundled unibilium." ${USE_BUNDLED}) option(USE_BUNDLED_LIBTERMKEY "Use the bundled libtermkey." ${USE_BUNDLED}) @@ -32,14 +43,21 @@ 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) -if(USE_BUNDLED AND (NOT WIN32)) - option(USE_BUNDLED_GPERF "Use the bundled version of gperf." ON) +if(USE_BUNDLED AND MSVC) + option(USE_BUNDLED_GETTEXT "Use the bundled version of gettext." ON) else() - option(USE_BUNDLED_GPERF "Use the bundled version of gperf." OFF) + option(USE_BUNDLED_GETTEXT "Use the bundled version of gettext." OFF) endif() option(USE_EXISTING_SRC_DIR "Skip download of deps sources in case of existing source directory." OFF) +if(WIN32) + find_package(Git) + if(NOT Git_FOUND) + message(FATAL_ERROR "Git is required to apply patches for Windows.") + endif() +endif() + if(UNIX) find_program(MAKE_PRG NAMES gmake make) if(MAKE_PRG) @@ -90,29 +108,24 @@ include(ExternalProject) set(LIBUV_URL https://github.com/libuv/libuv/archive/v1.12.0.tar.gz) set(LIBUV_SHA256 41ce914a88da21d3b07a76023beca57576ca5b376c6ac440c80bc581cbca1250) -set(MSGPACK_URL https://github.com/msgpack/msgpack-c/archive/cpp-2.1.3.tar.gz) -set(MSGPACK_SHA256 42ff5c213fd24bd4388c45c1f21d84b476678ce6366ea4d4f4086618a1d2cd23) +set(MSGPACK_URL https://github.com/msgpack/msgpack-c/releases/download/cpp-2.1.5/msgpack-2.1.5.tar.gz) +set(MSGPACK_SHA256 6126375af9b204611b9d9f154929f4f747e4599e6ae8443b337915dcf2899d2b) -set(LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/82151a4514e6538086f3f5e01cb8d4b22287b14f.tar.gz) -set(LUAJIT_SHA256 8bc4e96ebab74e12ab84e751360e864714289bb089b51b6f396fa9a97df69798) +set(LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/7dbf0b05f1228c1c719866db5e5f3d58f87f74c8.tar.gz) +set(LUAJIT_SHA256 cbae019b5e396164eb5f0d07777b55cc03931bb944f83c61a010c053c9f5fd5b) set(LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz) set(LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333) # NOTE: Version must match LUAROCKS_VERSION in third-party/cmake/BuildLuarocks.cmake -set(LUAROCKS_URL https://github.com/luarocks/luarocks/archive/v2.4.2.tar.gz) -set(LUAROCKS_SHA256 eef88c2429c715a7beb921e4b1ba571dddb7c74a250fbb0d3cc0d4be7a5865d9) +set(LUAROCKS_URL https://github.com/luarocks/luarocks/archive/v2.4.3.tar.gz) +set(LUAROCKS_SHA256 ea1881d6954f2a98c34f93674571c8f0cbdbc28dedb3fa3cb56b6a91886d1a99) -set(UNIBILIUM_URL https://github.com/mauke/unibilium/archive/v1.2.0.tar.gz) -set(UNIBILIUM_SHA256 623af1099515e673abfd3cae5f2fa808a09ca55dda1c65a7b5c9424eb304ead8) +set(UNIBILIUM_URL https://github.com/mauke/unibilium/archive/v2.0.0.tar.gz) +set(UNIBILIUM_SHA256 78997d38d4c8177c60d3d0c1aa8c53fd0806eb21825b7b335b1768d7116bc1c1) -if(WIN32) -set(LIBTERMKEY_URL https://github.com/equalsraf/libtermkey/archive/tb-windows.zip) -set(LIBTERMKEY_SHA256 c81e33e38662b151a49847ff4feef4f8c4b2a66f3e159a28b575cbc9bcd8ffea) -else() set(LIBTERMKEY_URL http://www.leonerd.org.uk/code/libtermkey/libtermkey-0.20.tar.gz) set(LIBTERMKEY_SHA256 6c0d87c94ab9915e76ecd313baec08dedf3bd56de83743d9aa923a081935d2f5) -endif() set(LIBVTERM_URL https://github.com/neovim/libvterm/archive/a9c7c6fd20fa35e0ad3e0e98901ca12dfca9c25c.tar.gz) set(LIBVTERM_SHA256 1a4272be91d9614dc183a503786df83b6584e4afaab7feaaa5409f841afbd796) @@ -139,8 +152,11 @@ set(WIN32YANK_X86_SHA256 62f34e5a46c5d4a7b3f3b512e1ff7b77fedd432f42581cbe825233a set(WIN32YANK_X86_64_URL https://github.com/equalsraf/win32yank/releases/download/v0.0.4/win32yank-x64.zip) set(WIN32YANK_X86_64_SHA256 33a747a92da60fb65e668edbf7661d3d902411a2d545fe9dc08623cecd142a20) -set(WINPTY_URL https://github.com/rprichard/winpty/releases/download/0.4.2/winpty-0.4.2-msvc2015.zip) -set(WINPTY_SHA256 b465f2584ff394b3fe27c01aa1dcfc679583c1ee951d0e83de3f859d8b8305b8) +set(WINPTY_URL https://github.com/rprichard/winpty/releases/download/0.4.3/winpty-0.4.3-msvc2015.zip) +set(WINPTY_SHA256 35a48ece2ff4acdcbc8299d4920de53eb86b1fb41e64d2fe5ae7898931bcee89) + +set(GETTEXT_URL https://ftp.gnu.org/pub/gnu/gettext/gettext-0.19.8.1.tar.gz) +set(GETTEXT_SHA256 ff942af0e438ced4a8b0ea4b0b6e0d6d657157c5e2364de57baa279c1c125c43) if(USE_BUNDLED_UNIBILIUM) include(BuildUnibilium) @@ -186,6 +202,10 @@ if(USE_BUNDLED_GPERF) include(BuildGperf) endif() +if(USE_BUNDLED_GETTEXT) + include(BuildGettext) +endif() + include(GetBinaryDeps) if(WIN32) diff --git a/third-party/cmake/BuildGettext.cmake b/third-party/cmake/BuildGettext.cmake new file mode 100644 index 0000000000..1dd206a91b --- /dev/null +++ b/third-party/cmake/BuildGettext.cmake @@ -0,0 +1,34 @@ +if(MSVC) + + ExternalProject_Add(gettext + PREFIX ${DEPS_BUILD_DIR} + URL ${GETTEXT_URL} + DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/gettext + DOWNLOAD_COMMAND ${CMAKE_COMMAND} + -DPREFIX=${DEPS_BUILD_DIR} + -DDOWNLOAD_DIR=${DEPS_DOWNLOAD_DIR}/gettext + -DURL=${GETTEXT_URL} + -DEXPECTED_SHA256=${GETTEXT_SHA256} + -DTARGET=gettext + -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR} + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake + PATCH_COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/gettext init + COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/gettext apply --ignore-whitespace + ${CMAKE_CURRENT_SOURCE_DIR}/patches/gettext-Fix-compilation-on-a-system-without-alloca.patch + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/GettextCMakeLists.txt + ${DEPS_BUILD_DIR}/src/gettext/gettext-runtime/CMakeLists.txt + COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/gettext/gettext-runtime + -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} + # Pass toolchain + -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE} + INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE}) + +else() + message(FATAL_ERROR "Trying to build gettext in an unsupported system ${CMAKE_SYSTEM_NAME}/${CMAKE_C_COMPILER_ID}") +endif() + +list(APPEND THIRD_PARTY_DEPS gettext) diff --git a/third-party/cmake/BuildGperf.cmake b/third-party/cmake/BuildGperf.cmake index 494d0d9717..fa8b35f383 100644 --- a/third-party/cmake/BuildGperf.cmake +++ b/third-party/cmake/BuildGperf.cmake @@ -36,16 +36,28 @@ function(BuildGperf) INSTALL_COMMAND "${_gperf_INSTALL_COMMAND}") endfunction() -set(GPERF_BUILDARGS CC=${HOSTDEPS_C_COMPILER} LD=${HOSTDEPS_C_COMPILER}) - if(UNIX OR (MINGW AND CMAKE_CROSSCOMPILING)) BuildGperf( CONFIGURE_COMMAND ${DEPS_BUILD_DIR}/src/gperf/configure - --prefix=${HOSTDEPS_INSTALL_DIR} + --prefix=${HOSTDEPS_INSTALL_DIR} MAKE=${MAKE_PRG} INSTALL_COMMAND ${MAKE_PRG} install) +elseif(MSVC OR MINGW) + + BuildGperf( + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/GperfCMakeLists.txt + ${DEPS_BUILD_DIR}/src/gperf/CMakeLists.txt + COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/gperf/CMakeLists.txt + -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + -DCMAKE_CXX_COMPILER=${CMAKE_CXX_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() message(FATAL_ERROR "Trying to build gperf in an unsupported system ${CMAKE_SYSTEM_NAME}/${CMAKE_C_COMPILER_ID}") endif() - diff --git a/third-party/cmake/BuildLibtermkey.cmake b/third-party/cmake/BuildLibtermkey.cmake index 4b581c2a01..8147c47e1e 100644 --- a/third-party/cmake/BuildLibtermkey.cmake +++ b/third-party/cmake/BuildLibtermkey.cmake @@ -1,5 +1,3 @@ -find_package(PkgConfig REQUIRED) - if(WIN32) ExternalProject_Add(libtermkey PREFIX ${DEPS_BUILD_DIR} @@ -13,17 +11,27 @@ ExternalProject_Add(libtermkey -DTARGET=libtermkey -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake - CONFIGURE_COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/libtermkey - -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} - # Pass toolchain - -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN} - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} - # Hack to avoid -rdynamic in Mingw - -DCMAKE_SHARED_LIBRARY_LINK_C_FLAGS="" - -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + PATCH_COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/libtermkey init + COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/libtermkey apply --ignore-whitespace + ${CMAKE_CURRENT_SOURCE_DIR}/patches/libtermkey-Add-support-for-Windows.patch + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/libtermkeyCMakeLists.txt + ${DEPS_BUILD_DIR}/src/libtermkey/CMakeLists.txt + COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/libtermkey + -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} + # Pass toolchain + -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + # Hack to avoid -rdynamic in Mingw + -DCMAKE_SHARED_LIBRARY_LINK_C_FLAGS="" + -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DUNIBILIUM_INCLUDE_DIRS=${DEPS_INSTALL_DIR}/include + -DUNIBILIUM_LIBRARIES=${DEPS_LIB_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}unibilium${CMAKE_STATIC_LIBRARY_SUFFIX} BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE} INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE}) else() +find_package(PkgConfig REQUIRED) + ExternalProject_Add(libtermkey PREFIX ${DEPS_BUILD_DIR} URL ${LIBTERMKEY_URL} @@ -43,11 +51,9 @@ ExternalProject_Add(libtermkey PREFIX=${DEPS_INSTALL_DIR} PKG_CONFIG_PATH=${DEPS_LIB_DIR}/pkgconfig CFLAGS=-fPIC + ${DEFAULT_MAKE_CFLAGS} install) endif() list(APPEND THIRD_PARTY_DEPS libtermkey) -if(NOT WIN32) - # There is no unibilium build recipe for Windows yet - add_dependencies(libtermkey unibilium) -endif() +add_dependencies(libtermkey unibilium) diff --git a/third-party/cmake/BuildLibuv.cmake b/third-party/cmake/BuildLibuv.cmake index 06b527e5a2..8ac7624be6 100644 --- a/third-party/cmake/BuildLibuv.cmake +++ b/third-party/cmake/BuildLibuv.cmake @@ -43,7 +43,7 @@ set(UNIX_CFGCMD sh ${DEPS_BUILD_DIR}/src/libuv/autogen.sh && if(UNIX) BuildLibuv( - CONFIGURE_COMMAND ${UNIX_CFGCMD} + CONFIGURE_COMMAND ${UNIX_CFGCMD} MAKE=${MAKE_PRG} INSTALL_COMMAND ${MAKE_PRG} V=1 install) elseif(MINGW AND CMAKE_CROSSCOMPILING) @@ -70,24 +70,19 @@ elseif(MINGW) elseif(WIN32 AND MSVC) - find_package(PythonInterp 2.6 REQUIRED) - if(NOT PYTHONINTERP_FOUND OR PYTHON_VERSION_MAJOR GREATER 2) - message(FATAL_ERROR "Python2 is required to build libuv on windows, use -DPYTHON_EXECUTABLE to set a python interpreter") - endif() - - include(TargetArch) - if("${TARGET_ARCH}" STREQUAL "X86_64") - set(TARGET_ARCH x64) - elseif(TARGET_ARCH STREQUAL "X86") - set(TARGET_ARCH x86) - endif() - string(TOLOWER ${CMAKE_BUILD_TYPE} LOWERCASE_BUILD_TYPE) set(UV_OUTPUT_DIR ${DEPS_BUILD_DIR}/src/libuv/${CMAKE_BUILD_TYPE}) - BuildLibUv( - BUILD_COMMAND set PYTHON=${PYTHON_EXECUTABLE} COMMAND ${DEPS_BUILD_DIR}/src/libuv/vcbuild.bat shared ${LOWERCASE_BUILD_TYPE} ${TARGET_ARCH} - INSTALL_COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_INSTALL_DIR}/lib - COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_INSTALL_DIR}/bin - COMMAND ${CMAKE_COMMAND} -E copy ${UV_OUTPUT_DIR}/libuv.lib ${DEPS_INSTALL_DIR}/lib + BuildLibUv(BUILD_IN_SOURCE + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/LibuvCMakeLists.txt + ${DEPS_BUILD_DIR}/src/libuv/CMakeLists.txt + COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/libuv/CMakeLists.txt + -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DBUILD_SHARED_LIBS=ON + -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} # Some applications (lua-client/luarocks) look for uv.lib instead of libuv.lib COMMAND ${CMAKE_COMMAND} -E copy ${UV_OUTPUT_DIR}/libuv.lib ${DEPS_INSTALL_DIR}/lib/uv.lib COMMAND ${CMAKE_COMMAND} -E copy ${UV_OUTPUT_DIR}/libuv.dll ${DEPS_INSTALL_DIR}/bin/ diff --git a/third-party/cmake/BuildLibvterm.cmake b/third-party/cmake/BuildLibvterm.cmake index 5ea8314da5..e4649986af 100644 --- a/third-party/cmake/BuildLibvterm.cmake +++ b/third-party/cmake/BuildLibvterm.cmake @@ -6,7 +6,7 @@ function(BuildLibvterm) cmake_parse_arguments(_libvterm "" "" - "CONFIGURE_COMMAND;BUILD_COMMAND;INSTALL_COMMAND" + "PATCH_COMMAND;CONFIGURE_COMMAND;BUILD_COMMAND;INSTALL_COMMAND" ${ARGN}) if(NOT _libvterm_CONFIGURE_COMMAND AND NOT _libvterm_BUILD_COMMAND @@ -26,6 +26,7 @@ function(BuildLibvterm) -DTARGET=libvterm -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake + PATCH_COMMAND "${_libvterm_PATCH_COMMAND}" CONFIGURE_COMMAND "" BUILD_IN_SOURCE 1 CONFIGURE_COMMAND "${_libvterm_CONFIGURE_COMMAND}" @@ -34,11 +35,19 @@ function(BuildLibvterm) endfunction() if(WIN32) - # MinGW + if(MSVC) + set(LIBVTERM_PATCH_COMMAND + ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/libvterm init + COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/libvterm apply --ignore-whitespace + ${CMAKE_CURRENT_SOURCE_DIR}/patches/libvterm-Remove-VLAs-for-MSVC.patch) + endif() set(LIBVTERM_CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_CURRENT_SOURCE_DIR}/cmake/LibvtermCMakeLists.txt - ${DEPS_BUILD_DIR}/src/libvterm/CMakeLists.txt - COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/libvterm + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/LibvtermCMakeLists.txt + ${DEPS_BUILD_DIR}/src/libvterm/CMakeLists.txt + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Libvterm-tbl2inc_c.cmake + ${DEPS_BUILD_DIR}/src/libvterm/tbl2inc_c.cmake + COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/libvterm -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} "-DCMAKE_C_FLAGS:STRING=${CMAKE_C_COMPILER_ARG1} -fPIC" @@ -47,12 +56,14 @@ if(WIN32) set(LIBVTERM_INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE}) else() set(LIBVTERM_INSTALL_COMMAND ${MAKE_PRG} CC=${DEPS_C_COMPILER} - PREFIX=${DEPS_INSTALL_DIR} - CFLAGS=-fPIC - install) + PREFIX=${DEPS_INSTALL_DIR} + CFLAGS=-fPIC + ${DEFAULT_MAKE_CFLAGS} + install) endif() -BuildLibvterm(CONFIGURE_COMMAND ${LIBVTERM_CONFIGURE_COMMAND} +BuildLibvterm(PATCH_COMMAND ${LIBVTERM_PATCH_COMMAND} + CONFIGURE_COMMAND ${LIBVTERM_CONFIGURE_COMMAND} BUILD_COMMAND ${LIBVTERM_BUILD_COMMAND} INSTALL_COMMAND ${LIBVTERM_INSTALL_COMMAND}) diff --git a/third-party/cmake/BuildLua.cmake b/third-party/cmake/BuildLua.cmake index ea1371d1d5..da617e3ccd 100644 --- a/third-party/cmake/BuildLua.cmake +++ b/third-party/cmake/BuildLua.cmake @@ -71,6 +71,7 @@ set(LUA_CONFIGURE_COMMAND -e "s@-lncurses@@g" -i ${DEPS_BUILD_DIR}/src/lua/src/Makefile && sed -e "/#define LUA_USE_READLINE/d" + -e "s@\\(#define LUA_ROOT[ ]*\"\\)/usr/local@\\1${DEPS_INSTALL_DIR}@" -i ${DEPS_BUILD_DIR}/src/lua/src/luaconf.h) set(LUA_INSTALL_TOP_ARG "INSTALL_TOP=${DEPS_INSTALL_DIR}") set(LUA_BUILD_COMMAND @@ -84,14 +85,13 @@ BuildLua(CONFIGURE_COMMAND ${LUA_CONFIGURE_COMMAND} BUILD_COMMAND ${LUA_BUILD_COMMAND} INSTALL_COMMAND ${LUA_INSTALL_COMMAND}) list(APPEND THIRD_PARTY_DEPS lua) -add_dependencies(lua busted) set(BUSTED ${DEPS_INSTALL_DIR}/bin/busted) set(BUSTED_LUA ${BUSTED}-lua) add_custom_command(OUTPUT ${BUSTED_LUA} COMMAND sed -e 's/^exec/exec $$LUA_DEBUGGER/' -e 's/jit//g' < ${BUSTED} > ${BUSTED_LUA} && chmod +x ${BUSTED_LUA} - DEPENDS lua) + DEPENDS lua busted) add_custom_target(busted-lua DEPENDS ${DEPS_INSTALL_DIR}/bin/busted-lua) diff --git a/third-party/cmake/BuildLuajit.cmake b/third-party/cmake/BuildLuajit.cmake index c8eee282bf..2fda221b12 100644 --- a/third-party/cmake/BuildLuajit.cmake +++ b/third-party/cmake/BuildLuajit.cmake @@ -35,10 +35,16 @@ function(BuildLuajit) INSTALL_COMMAND "${_luajit_INSTALL_COMMAND}") endfunction() +if(CMAKE_SYSTEM_NAME MATCHES "OpenBSD") + set(AMD64_ABI "LDFLAGS=-lpthread -lc++abi") +else() + set(AMD64_ABI "") +endif() set(INSTALLCMD_UNIX ${MAKE_PRG} CFLAGS=-fPIC CFLAGS+=-DLUAJIT_DISABLE_JIT CFLAGS+=-DLUA_USE_APICHECK CFLAGS+=-DLUA_USE_ASSERT + ${AMD64_ABI} CCDEBUG+=-g Q= install) @@ -101,7 +107,10 @@ elseif(MSVC) COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/luajit/src/luajit.exe ${DEPS_INSTALL_DIR}/bin COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/luajit/src/lua51.dll ${DEPS_INSTALL_DIR}/bin COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_INSTALL_DIR}/lib - COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/luajit/src/lua51.lib ${DEPS_INSTALL_DIR}/lib + # Luarocks searches for lua51.lib + COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/luajit/src/lua51.lib ${DEPS_INSTALL_DIR}/lib/lua51.lib + # Luv searches for luajit.lib + COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/luajit/src/lua51.lib ${DEPS_INSTALL_DIR}/lib/luajit.lib COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_INSTALL_DIR}/include/luajit-2.0 COMMAND ${CMAKE_COMMAND} -DFROM_GLOB=${DEPS_BUILD_DIR}/src/luajit/src/*.h -DTO=${DEPS_INSTALL_DIR}/include/luajit-2.0 -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CopyFilesGlob.cmake) diff --git a/third-party/cmake/BuildLuarocks.cmake b/third-party/cmake/BuildLuarocks.cmake index ef8a8450f1..b5fcf7f3e5 100644 --- a/third-party/cmake/BuildLuarocks.cmake +++ b/third-party/cmake/BuildLuarocks.cmake @@ -12,7 +12,7 @@ function(BuildLuarocks) cmake_parse_arguments(_luarocks "" "" - "CONFIGURE_COMMAND;BUILD_COMMAND;INSTALL_COMMAND" + "PATCH_COMMAND;CONFIGURE_COMMAND;BUILD_COMMAND;INSTALL_COMMAND" ${ARGN}) if(NOT _luarocks_CONFIGURE_COMMAND AND NOT _luarocks_BUILD_COMMAND @@ -32,6 +32,7 @@ function(BuildLuarocks) -DTARGET=luarocks -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake + PATCH_COMMAND "${_luarocks_PATCH_COMMAND}" BUILD_IN_SOURCE 1 CONFIGURE_COMMAND "${_luarocks_CONFIGURE_COMMAND}" BUILD_COMMAND "${_luarocks_BUILD_COMMAND}" @@ -50,28 +51,43 @@ if(NOT MSVC) # version already knows, and passing them here breaks the build set(LUAROCKS_BUILDARGS CC=${HOSTDEPS_C_COMPILER} LD=${HOSTDEPS_C_COMPILER}) endif() +if(WIN32) + # Use our bundled curl.exe for downloading packages + set(LUAROCKS_BUILDARGS ${LUAROCKS_BUILDARGS} CURL=${DEPS_BIN_DIR}/curl.exe) +endif() + if(UNIX OR (MINGW AND CMAKE_CROSSCOMPILING)) if(USE_BUNDLED_LUAJIT) list(APPEND LUAROCKS_OPTS --with-lua=${HOSTDEPS_INSTALL_DIR} - --with-lua-include=${HOSTDEPS_INSTALL_DIR}/include/luajit-2.0) + --with-lua-include=${HOSTDEPS_INSTALL_DIR}/include/luajit-2.0 + --lua-suffix=jit) + elseif(USE_BUNDLED_LUA) + list(APPEND LUAROCKS_OPTS + --with-lua=${HOSTDEPS_INSTALL_DIR}) endif() BuildLuarocks( CONFIGURE_COMMAND ${DEPS_BUILD_DIR}/src/luarocks/configure --prefix=${HOSTDEPS_INSTALL_DIR} --force-config ${LUAROCKS_OPTS} - --lua-suffix=jit - INSTALL_COMMAND ${MAKE_PRG} bootstrap) + INSTALL_COMMAND ${MAKE_PRG} -j1 bootstrap) elseif(MSVC OR MINGW) if(MINGW) - set(MINGW_FLAG /MW) + set(COMPILER_FLAG /MW) + elseif(MSVC) + set(COMPILER_FLAG /MSVC) endif() # Ignore USE_BUNDLED_LUAJIT - always ON for native Win32 - BuildLuarocks(INSTALL_COMMAND install.bat /FORCECONFIG /NOREG /NOADMIN /Q /F + BuildLuarocks( + PATCH_COMMAND + ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/luarocks init + COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/luarocks apply --ignore-whitespace + ${CMAKE_CURRENT_SOURCE_DIR}/patches/luarocks-Change-default-downloader-to-curl.patch + INSTALL_COMMAND install.bat /FORCECONFIG /NOREG /NOADMIN /Q /F /LUA ${DEPS_INSTALL_DIR} /LIB ${DEPS_LIB_DIR} /BIN ${DEPS_BIN_DIR} @@ -79,10 +95,12 @@ elseif(MSVC OR MINGW) /P ${DEPS_INSTALL_DIR}/${LUAROCKS_VERSION} /TREE ${DEPS_INSTALL_DIR} /SCRIPTS ${DEPS_BIN_DIR} /CMOD ${DEPS_BIN_DIR} - ${MINGW_FLAG} + ${COMPILER_FLAG} /LUAMOD ${DEPS_BIN_DIR}/lua) set(LUAROCKS_BINARY ${DEPS_INSTALL_DIR}/${LUAROCKS_VERSION}/luarocks.bat) + add_dependencies(luarocks wintools) + else() message(FATAL_ERROR "Trying to build luarocks in an unsupported system ${CMAKE_SYSTEM_NAME}/${CMAKE_C_COMPILER_ID}") endif() @@ -94,63 +112,88 @@ if(USE_BUNDLED_LUAJIT) if(MINGW AND CMAKE_CROSSCOMPILING) add_dependencies(luarocks luajit_host) endif() +elseif(USE_BUNDLED_LUA) + add_dependencies(luarocks lua) endif() -# Each target depends on the previous module, this serializes all calls to -# luarocks since it is unhappy to be called in parallel. +# DEPENDS on the previous module, because Luarocks breaks if parallel. add_custom_command(OUTPUT ${HOSTDEPS_LIB_DIR}/luarocks/rocks/mpack COMMAND ${LUAROCKS_BINARY} - ARGS build mpack ${LUAROCKS_BUILDARGS} + ARGS build mpack 1.0.7-0 ${LUAROCKS_BUILDARGS} DEPENDS luarocks) add_custom_target(mpack DEPENDS ${HOSTDEPS_LIB_DIR}/luarocks/rocks/mpack) list(APPEND THIRD_PARTY_DEPS mpack) - +# DEPENDS on the previous module, because Luarocks breaks if parallel. add_custom_command(OUTPUT ${HOSTDEPS_LIB_DIR}/luarocks/rocks/lpeg COMMAND ${LUAROCKS_BINARY} - ARGS build lpeg ${LUAROCKS_BUILDARGS} + ARGS build lpeg 1.0.1-1 ${LUAROCKS_BUILDARGS} DEPENDS mpack) add_custom_target(lpeg DEPENDS ${HOSTDEPS_LIB_DIR}/luarocks/rocks/lpeg) list(APPEND THIRD_PARTY_DEPS lpeg) +# DEPENDS on the previous module, because Luarocks breaks if parallel. add_custom_command(OUTPUT ${HOSTDEPS_LIB_DIR}/luarocks/rocks/inspect COMMAND ${LUAROCKS_BINARY} - ARGS build inspect ${LUAROCKS_BUILDARGS} - DEPENDS mpack) + ARGS build inspect 3.1.1-0 ${LUAROCKS_BUILDARGS} + DEPENDS lpeg) add_custom_target(inspect DEPENDS ${HOSTDEPS_LIB_DIR}/luarocks/rocks/inspect) list(APPEND THIRD_PARTY_DEPS inspect) -if(USE_BUNDLED_BUSTED) - add_custom_command(OUTPUT ${HOSTDEPS_LIB_DIR}/luarocks/rocks/penlight/1.3.2-2 +if((NOT USE_BUNDLED_LUAJIT) AND USE_BUNDLED_LUA) + # DEPENDS on the previous module, because Luarocks breaks if parallel. + add_custom_command(OUTPUT ${HOSTDEPS_LIB_DIR}/luarocks/rocks/luabitop COMMAND ${LUAROCKS_BINARY} - ARGS build penlight 1.3.2-2 ${LUAROCKS_BUILDARGS} + ARGS build luabitop 1.0.2-3 ${LUAROCKS_BUILDARGS} DEPENDS inspect) + add_custom_target(luabitop + DEPENDS ${HOSTDEPS_LIB_DIR}/luarocks/rocks/luabitop) + + list(APPEND THIRD_PARTY_DEPS luabitop) +endif() + +if(USE_BUNDLED_BUSTED) + if((NOT USE_BUNDLED_LUAJIT) AND USE_BUNDLED_LUA) + set(PENLIGHT_DEPENDS luabitop) + else() + set(PENLIGHT_DEPENDS inspect) + endif() + + # DEPENDS on the previous module, because Luarocks breaks if parallel. + add_custom_command(OUTPUT ${HOSTDEPS_LIB_DIR}/luarocks/rocks/penlight + COMMAND ${LUAROCKS_BINARY} + ARGS build penlight 1.5.4-1 ${LUAROCKS_BUILDARGS} + DEPENDS ${PENLIGHT_DEPENDS}) add_custom_target(penlight - DEPENDS ${HOSTDEPS_LIB_DIR}/luarocks/rocks/penlight/1.3.2-2) + DEPENDS ${HOSTDEPS_LIB_DIR}/luarocks/rocks/penlight) if(WIN32) set(BUSTED_EXE "${HOSTDEPS_BIN_DIR}/busted.bat") + set(LUACHECK_EXE "${HOSTDEPS_BIN_DIR}/luacheck.bat") else() set(BUSTED_EXE "${HOSTDEPS_BIN_DIR}/busted") + set(LUACHECK_EXE "${HOSTDEPS_BIN_DIR}/luacheck") endif() + # DEPENDS on the previous module, because Luarocks breaks if parallel. add_custom_command(OUTPUT ${BUSTED_EXE} COMMAND ${LUAROCKS_BINARY} - ARGS build https://raw.githubusercontent.com/Olivine-Labs/busted/v2.0.rc12-1/busted-2.0.rc12-1.rockspec ${LUAROCKS_BUILDARGS} + ARGS build busted 2.0.rc12-1 ${LUAROCKS_BUILDARGS} DEPENDS penlight) add_custom_target(busted DEPENDS ${BUSTED_EXE}) - add_custom_command(OUTPUT ${HOSTDEPS_BIN_DIR}/luacheck + # DEPENDS on the previous module, because Luarocks breaks if parallel. + add_custom_command(OUTPUT ${LUACHECK_EXE} COMMAND ${LUAROCKS_BINARY} - ARGS build https://raw.githubusercontent.com/mpeterv/luacheck/master/luacheck-scm-1.rockspec ${LUAROCKS_BUILDARGS} + ARGS build luacheck 0.21.2-1 ${LUAROCKS_BUILDARGS} DEPENDS busted) add_custom_target(luacheck - DEPENDS ${HOSTDEPS_BIN_DIR}/luacheck) + DEPENDS ${LUACHECK_EXE}) set(LUV_DEPS luacheck luv-static) if(MINGW AND CMAKE_CROSSCOMPILING) @@ -160,6 +203,7 @@ if(USE_BUNDLED_BUSTED) if(USE_BUNDLED_LIBUV) list(APPEND LUV_ARGS LIBUV_DIR=${HOSTDEPS_INSTALL_DIR}) endif() + # DEPENDS on the previous module, because Luarocks breaks if parallel. add_custom_command(OUTPUT ${HOSTDEPS_LIB_DIR}/luarocks/rocks/luv COMMAND ${LUAROCKS_BINARY} ARGS make ${LUAROCKS_BUILDARGS} ${LUV_ARGS} @@ -168,9 +212,10 @@ if(USE_BUNDLED_BUSTED) add_custom_target(luv DEPENDS ${HOSTDEPS_LIB_DIR}/luarocks/rocks/luv) + # DEPENDS on the previous module, because Luarocks breaks if parallel. add_custom_command(OUTPUT ${HOSTDEPS_LIB_DIR}/luarocks/rocks/nvim-client COMMAND ${LUAROCKS_BINARY} - ARGS build https://raw.githubusercontent.com/neovim/lua-client/0.0.1-26/nvim-client-0.0.1-26.rockspec ${LUAROCKS_BUILDARGS} + ARGS build nvim-client 0.0.1-26 ${LUAROCKS_BUILDARGS} DEPENDS luv) add_custom_target(nvim-client DEPENDS ${HOSTDEPS_LIB_DIR}/luarocks/rocks/nvim-client) diff --git a/third-party/cmake/BuildLuv.cmake b/third-party/cmake/BuildLuv.cmake index 2a786dd8f3..339264746c 100644 --- a/third-party/cmake/BuildLuv.cmake +++ b/third-party/cmake/BuildLuv.cmake @@ -37,9 +37,17 @@ set(LUV_SRC_DIR ${DEPS_BUILD_DIR}/src/luv) set(LUV_INCLUDE_FLAGS "-I${DEPS_INSTALL_DIR}/include -I${DEPS_INSTALL_DIR}/include/luajit-2.0") +# Replace luv default rockspec with the alternate one under the "rockspecs" +# directory set(LUV_PATCH_COMMAND - ${CMAKE_COMMAND} -DLUV_SRC_DIR=${LUV_SRC_DIR} - -P ${PROJECT_SOURCE_DIR}/cmake/PatchLuv.cmake) + ${CMAKE_COMMAND} -E copy_directory ${LUV_SRC_DIR}/rockspecs ${LUV_SRC_DIR}) +if(MINGW) + set(LUV_PATCH_COMMAND + ${LUV_PATCH_COMMAND} + COMMAND ${GIT_EXECUTABLE} -C ${LUV_SRC_DIR} init + COMMAND ${GIT_EXECUTABLE} -C ${LUV_SRC_DIR} apply --ignore-whitespace + ${CMAKE_CURRENT_SOURCE_DIR}/patches/luv-Add-missing-definitions-for-MinGW.patch) +endif() set(LUV_CONFIGURE_COMMAND_COMMON ${CMAKE_COMMAND} ${LUV_SRC_DIR} @@ -78,7 +86,12 @@ else() "-DCMAKE_C_FLAGS:STRING=${CMAKE_C_COMPILER_ARG1} ${LUV_INCLUDE_FLAGS} -fPIC") endif() -set(LUV_BUILD_COMMAND ${CMAKE_COMMAND} --build .) +if(CMAKE_GENERATOR MATCHES "Unix Makefiles" AND + (CMAKE_SYSTEM_NAME MATCHES ".*BSD" OR CMAKE_SYSTEM_NAME MATCHES "DragonFly")) + set(LUV_BUILD_COMMAND ${CMAKE_COMMAND} "-DCMAKE_MAKE_PROGRAM=gmake" --build .) +else() + set(LUV_BUILD_COMMAND ${CMAKE_COMMAND} --build .) +endif() set(LUV_INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install) BuildLuv(PATCH_COMMAND ${LUV_PATCH_COMMAND} diff --git a/third-party/cmake/BuildMsgpack.cmake b/third-party/cmake/BuildMsgpack.cmake index 779cb1ebfe..616b6e5f83 100644 --- a/third-party/cmake/BuildMsgpack.cmake +++ b/third-party/cmake/BuildMsgpack.cmake @@ -70,9 +70,6 @@ elseif(MSVC) # Make sure we use the same generator, otherwise we may # accidentaly end up using different MSVC runtimes -DCMAKE_GENERATOR=${CMAKE_GENERATOR}) - # Place the DLL in the bin folder - set(MSGPACK_INSTALL_COMMAND ${MSGPACK_INSTALL_COMMAND} - COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_INSTALL_DIR}/lib/msgpack.dll ${DEPS_INSTALL_DIR}/bin) endif() BuildMsgpack(CONFIGURE_COMMAND ${MSGPACK_CONFIGURE_COMMAND} diff --git a/third-party/cmake/BuildUnibilium.cmake b/third-party/cmake/BuildUnibilium.cmake index a5ec6c01eb..f5342e3d34 100644 --- a/third-party/cmake/BuildUnibilium.cmake +++ b/third-party/cmake/BuildUnibilium.cmake @@ -1,25 +1,49 @@ if(WIN32) - message(STATUS "Building Unibilium in Windows is not supported (skipping)") - return() + ExternalProject_Add(unibilium + PREFIX ${DEPS_BUILD_DIR} + URL ${UNIBILIUM_URL} + DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/unibilium + DOWNLOAD_COMMAND ${CMAKE_COMMAND} + -DPREFIX=${DEPS_BUILD_DIR} + -DDOWNLOAD_DIR=${DEPS_DOWNLOAD_DIR}/unibilium + -DURL=${UNIBILIUM_URL} + -DEXPECTED_SHA256=${UNIBILIUM_SHA256} + -DTARGET=unibilium + -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR} + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/UnibiliumCMakeLists.txt + ${DEPS_BUILD_DIR}/src/unibilium/CMakeLists.txt + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/msvc-compat/unistd.h + ${DEPS_BUILD_DIR}/src/unibilium/msvc-compat/unistd.h + COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/unibilium + -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} + # Pass toolchain + -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE} + INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE}) +else() + ExternalProject_Add(unibilium + PREFIX ${DEPS_BUILD_DIR} + URL ${UNIBILIUM_URL} + DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/unibilium + DOWNLOAD_COMMAND ${CMAKE_COMMAND} + -DPREFIX=${DEPS_BUILD_DIR} + -DDOWNLOAD_DIR=${DEPS_DOWNLOAD_DIR}/unibilium + -DURL=${UNIBILIUM_URL} + -DEXPECTED_SHA256=${UNIBILIUM_SHA256} + -DTARGET=unibilium + -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR} + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake + CONFIGURE_COMMAND "" + BUILD_IN_SOURCE 1 + BUILD_COMMAND ${MAKE_PRG} CC=${DEPS_C_COMPILER} + PREFIX=${DEPS_INSTALL_DIR} + CFLAGS=-fPIC + INSTALL_COMMAND ${MAKE_PRG} PREFIX=${DEPS_INSTALL_DIR} install) endif() -ExternalProject_Add(unibilium - PREFIX ${DEPS_BUILD_DIR} - URL ${UNIBILIUM_URL} - DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/unibilium - DOWNLOAD_COMMAND ${CMAKE_COMMAND} - -DPREFIX=${DEPS_BUILD_DIR} - -DDOWNLOAD_DIR=${DEPS_DOWNLOAD_DIR}/unibilium - -DURL=${UNIBILIUM_URL} - -DEXPECTED_SHA256=${UNIBILIUM_SHA256} - -DTARGET=unibilium - -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR} - -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake - CONFIGURE_COMMAND "" - BUILD_IN_SOURCE 1 - BUILD_COMMAND ${MAKE_PRG} CC=${DEPS_C_COMPILER} - PREFIX=${DEPS_INSTALL_DIR} - CFLAGS=-fPIC - INSTALL_COMMAND ${MAKE_PRG} PREFIX=${DEPS_INSTALL_DIR} install) - list(APPEND THIRD_PARTY_DEPS unibilium) diff --git a/third-party/cmake/GettextCMakeLists.txt b/third-party/cmake/GettextCMakeLists.txt new file mode 100644 index 0000000000..60ee3b6f99 --- /dev/null +++ b/third-party/cmake/GettextCMakeLists.txt @@ -0,0 +1,83 @@ +cmake_minimum_required(VERSION 2.8.7) +project(libintl C) + +include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) + +file(READ config.h.in CONFIG_CONTENT) +string(REPLACE "#undef HAVE_GETCWD" "#define HAVE_GETCWD 1" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef uintmax_t" " + #if _WIN64 + # define intmax_t long long + # define uintmax_t unsigned long long + #elif _WIN32 + # define intmax_t long + # define uintmax_t unsigned long + #endif" + CONFIG_CONTENT ${CONFIG_CONTENT}) +file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/config.h ${CONFIG_CONTENT}) + +set(HAVE_NEWLOCALE 0) +set(HAVE_POSIX_PRINTF 0) +set(HAVE_SNPRINTF 0) +set(HAVE_ASPRINTF 0) +set(HAVE_WPRINTF 0) +configure_file(intl/libgnuintl.in.h libgnuintl.h) + +set(LOCALDIR "gettext") +add_definitions(-DLOCALEDIR=\"${LOCALDIR}\" + -DLOCALE_ALIAS_PATH=\"${LOCALDIR}\" + -DLIBDIR=\"${LOCALDIR}\" + -DINSTALLDIR=\"${LOCALDIR}\") + +add_definitions(-DBUILDING_LIBINTL + -DIN_LIBINTL + -DENABLE_RELOCATABLE=1 + -DIN_LIBRARY + -DNO_XMALLOC + -Dset_relocation_prefix=libintl_set_relocation_prefix + -Drelocate=libintl_relocate + -DHAVE_CONFIG_H + -D_CRT_SECURE_NO_WARNINGS) + +FILE(GLOB SOURCES + intl/bindtextdom.c + intl/dcgettext.c + intl/dcigettext.c + intl/dcngettext.c + intl/dgettext.c + intl/dngettext.c + intl/explodename.c + intl/finddomain.c + intl/gettext.c + intl/hash-string.c + intl/l10nflist.c + intl/langprefs.c + intl/loadmsgcat.c + intl/localcharset.c + intl/localealias.c + intl/localename.c + intl/lock.c + intl/log.c + intl/ngettext.c + intl/plural-exp.c + intl/plural.c + intl/printf.c + intl/relocatable.c + intl/setlocale.c + intl/textdomain.c + intl/threadlib.c + intl/version.c) + +add_library(libintl ${SOURCES}) + +include(GNUInstallDirs) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/libgnuintl.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + RENAME libintl.h) + +install(TARGETS libintl + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/third-party/cmake/GperfCMakeLists.txt b/third-party/cmake/GperfCMakeLists.txt new file mode 100644 index 0000000000..32837fc166 --- /dev/null +++ b/third-party/cmake/GperfCMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 2.8.7) +project(gperf LANGUAGES C CXX) + +add_executable(gperf + lib/getline.cc + lib/hash.cc + lib/getopt.c + lib/getopt1.c + src/version.cc + src/positions.cc + src/options.cc + src/keyword.cc + src/keyword-list.cc + src/input.cc + src/bool-array.cc + src/hash-table.cc + src/search.cc + src/output.cc + src/main.cc) + +include_directories(lib) + +# Copy the config.h template without modifying it +# because none of the definitions are necessary +execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/src/config.h.in ${CMAKE_BINARY_DIR}/config/config.h) +include_directories(${CMAKE_BINARY_DIR}/config) + +include(GNUInstallDirs) +install(TARGETS gperf + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/third-party/cmake/LibuvCMakeLists.txt b/third-party/cmake/LibuvCMakeLists.txt new file mode 100644 index 0000000000..063e4291f2 --- /dev/null +++ b/third-party/cmake/LibuvCMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 2.8.7) +project(libuv LANGUAGES C) + +file(GLOB UV_SOURCES_COMMON src/*.c) +file(GLOB UV_SOURCES_WIN src/win/*.c) + +add_library(libuv ${UV_SOURCES_COMMON} ${UV_SOURCES_WIN}) +target_compile_definitions(libuv PRIVATE WIN32_LEAN_AND_MEAN "_WIN32_WINNT=0x0600") +target_link_libraries(libuv iphlpapi psapi shell32 userenv ws2_32) +target_include_directories(libuv PUBLIC ./include PRIVATE ./src) +set_target_properties(libuv PROPERTIES DEFINE_SYMBOL BUILDING_UV_SHARED) + +install(FILES + include/tree.h + include/uv.h + include/uv-version.h + include/uv-errno.h + include/uv-threadpool.h + include/uv-win.h + DESTINATION include) + +include(GNUInstallDirs) +install(TARGETS libuv + PUBLIC_HEADER + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/third-party/cmake/Libvterm-tbl2inc_c.cmake b/third-party/cmake/Libvterm-tbl2inc_c.cmake new file mode 100644 index 0000000000..b1ee0246f1 --- /dev/null +++ b/third-party/cmake/Libvterm-tbl2inc_c.cmake @@ -0,0 +1,163 @@ +cmake_minimum_required(VERSION 2.8.7) + +set(HEX_ALPHABET "0123456789abcdef") + +function(ConvertToHex dec hex) + while(dec GREATER 0) + math(EXPR _val "${dec} % 16") + math(EXPR dec "${dec} / 16") + string(SUBSTRING ${HEX_ALPHABET} ${_val} 1 _val) + set(_res "${_val}${_res}") + endwhile() + # Pad the result with the number of zeros + # specified by the optional third argument + if(${ARGC} EQUAL 3) + set(padding ${ARGV2}) + string(LENGTH ${_res} _resLen) + if(_resLen LESS ${padding}) + math(EXPR _neededPadding "${padding} - ${_resLen}") + foreach(i RANGE 1 ${_neededPadding}) + set(_res "0${_res}") + endforeach() + endif() + endif() + set(${hex} "0x${_res}" PARENT_SCOPE) +endfunction() + +function(ConvertFromHex hex dec) + string(TOLOWER ${hex} hex) + string(LENGTH "${hex}" _strlen) + set(_res 0) + while(_strlen GREATER 0) + math(EXPR _res "${_res} * 16") + string(SUBSTRING "${hex}" 0 1 NIBBLE) + string(SUBSTRING "${hex}" 1 -1 hex) + string(FIND ${HEX_ALPHABET} ${NIBBLE} value) + if(value EQUAL -1) + message(FATAL_ERROR "Invalid hex character '${NIBBLE}'") + endif() + math(EXPR _res "${_res} + ${value}") + string(LENGTH "${hex}" _strlen) + endwhile() + set(${dec} ${_res} PARENT_SCOPE) +endfunction() + +# Based on http://www.json.org/JSON_checker/utf8_decode.c +function(DecodeUtf8 hexBytes codePoint) + string(SUBSTRING ${hexBytes} 0 2 hexByte1) + ConvertFromHex(${hexByte1} byte1) + # Zero continuations (0 to 127) + math(EXPR out "${byte1} & 128") + if(out EQUAL 0) + set(${codePoint} ${byte1} PARENT_SCOPE) + return() + endif() + # One continuation (128 to 2047) + math(EXPR out "${byte1} & 224") + if(out EQUAL 192) + string(SUBSTRING ${hexBytes} 2 2 hexByte2) + ConvertFromHex(${hexByte2} byte2) + math(EXPR result "((${byte1} & 31) << 6) | ${byte2}") + if(result GREATER 127) + set(${codePoint} ${result} PARENT_SCOPE) + return() + endif() + else() + # Two continuations (2048 to 55295 and 57344 to 65535) + math(EXPR result "${byte1} & 240") + if(result EQUAL 224) + string(SUBSTRING ${hexBytes} 2 2 hexByte2) + string(SUBSTRING ${hexBytes} 4 2 hexByte3) + ConvertFromHex(${hexByte2} byte2) + ConvertFromHex(${hexByte3} byte3) + math(EXPR result "${byte2} | ${byte3}") + if(result GREATER -1) + math(EXPR result "((${byte1} & 15) << 12) | (${byte2} << 6) | ${byte3}") + if((result GREATER 2047) AND (result LESS 55296 OR result GREATER 57343)) + set(${codePoint} ${result} PARENT_SCOPE) + return() + endif() + endif() + else() + # Three continuations (65536 to 1114111) + math(EXPR result "${byte1} & 248") + if(result EQUAL 224) + string(SUBSTRING ${hexBytes} 2 2 hexByte2) + string(SUBSTRING ${hexBytes} 4 2 hexByte3) + string(SUBSTRING ${hexBytes} 6 2 hexByte4) + ConvertFromHex(${hexByte2} byte2) + ConvertFromHex(${hexByte3} byte3) + ConvertFromHex(${hexByte4} byte4) + math(EXPR result "${byte2} | ${byte3} | ${byte4}") + if(result GREATER -1) + math(EXPR result "((c & 7) << 18) | (c1 << 12) | (c2 << 6) | c3") + if((result GREATER 65535) AND (result LESS 1114112)) + set(${codePoint} ${result} PARENT_SCOPE) + return() + endif() + endif() + endif() + endif() + endif() + message(FATAL_ERROR "Invalid UTF-8 encoding") +endfunction() + +set(inputFile ${CMAKE_ARGV3}) +set(outputFile ${CMAKE_ARGV4}) +# Get the file contents in text and hex-encoded format because +# CMake doesn't provide functions for converting between the two +file(READ "${inputFile}" contents) +file(READ "${inputFile}" hexContents HEX) + +# Convert the text contents into a list of lines by escaping +# the list separator ';' and then replacing new line characters +# with the list separator +string(REGEX REPLACE ";" "\\\\;" contents ${contents}) +string(REGEX REPLACE "\n" ";" contents ${contents}) + +get_filename_component(encname ${inputFile} NAME_WE) +set(output + "static const struct StaticTableEncoding encoding_${encname} = {\n" + " { .decode = &decode_table },\n" + " {") +set(hexIndex 0) +foreach(line ${contents}) + string(LENGTH ${line} lineLength) + # Convert "A" to 0x41 + string(FIND ${line} "\"" beginQuote) + if(NOT ${beginQuote} EQUAL -1) + string(FIND ${line} "\"" endQuote REVERSE) + if(${beginQuote} EQUAL ${endQuote}) + message(FATAL_ERROR "Line contains only one quote") + endif() + math(EXPR beginHexQuote "${hexIndex} + (${beginQuote} + 1)*2") + math(EXPR endHexQuote "${hexIndex} + (${endQuote} + 1)*2") + math(EXPR quoteLen "${endHexQuote} - ${beginHexQuote} - 1") + string(SUBSTRING ${hexContents} ${beginHexQuote} ${quoteLen} hexQuote) + DecodeUtf8(${hexQuote} codePoint) + ConvertToHex(${codePoint} hexCodePoint 4) + STRING(REGEX REPLACE "\"(.+)\"" ${hexCodePoint} line ${line}) + endif() + # Strip comment + string(REGEX REPLACE "[ \t\n]*#.*" "" line ${line}) + # Convert 3/1 to [0x31] + string(REGEX REPLACE "^([0-9]+)/([0-9]+).*" "\\1;\\2" numbers ${line}) + list(GET numbers 0 upperBits) + list(GET numbers 1 lowerBits) + math(EXPR res "${upperBits}*16 + ${lowerBits}") + ConvertToHex(${res} hex 2) + string(REGEX REPLACE "^([0-9]+)/([0-9]+)" "[${hex}]" line ${line}) + # Convert U+0041 to 0x0041 + string(REPLACE "U+" "0x" line ${line}) + # Indent and append a comma + set(line " ${line},") + set(output "${output}\n${line}") + # Increment the index by the number of characters in the line, + # plus one for the new line character then multiple by two for the hex digit index + math(EXPR hexIndex "${hexIndex} + 2*(${lineLength} + 1)") +endforeach() +set(output "${output}\n" + " }\n" + "}\;\n") + +file(WRITE ${outputFile} ${output}) diff --git a/third-party/cmake/LibvtermCMakeLists.txt b/third-party/cmake/LibvtermCMakeLists.txt index 72183e4b4c..27d0d11e9f 100644 --- a/third-party/cmake/LibvtermCMakeLists.txt +++ b/third-party/cmake/LibvtermCMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 2.8.11) project(libvterm LANGUAGES C) include(GNUInstallDirs) -find_package(Perl REQUIRED) +find_package(Perl) if(MSVC) add_definitions(/W3 -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE) @@ -18,11 +18,19 @@ foreach(file ${TBL_FILES}) set(tname encoding/${basename}.inc) add_custom_command(OUTPUT COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/encoding/ - COMMAND ${PERL_EXECUTABLE} -CSD ${CMAKE_SOURCE_DIR}/tbl2inc_c.pl ${file} > ${CMAKE_BINARY_DIR}/${tname} + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/tbl2inc_c.cmake ${file} ${CMAKE_BINARY_DIR}/${tname} COMMENT "Generating ${tname}" OUTPUT ${CMAKE_BINARY_DIR}/${tname} ) list(APPEND TBL_FILES_HEADERS ${tname}) + # Only used for verifying that the output of tbl2inc_c.cmake is correct + set(tname encoding-test/${basename}.inc) + add_custom_command(OUTPUT + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/encoding-test/ + COMMAND ${PERL_EXECUTABLE} -CSD ${CMAKE_SOURCE_DIR}/tbl2inc_c.pl ${file} > ${CMAKE_BINARY_DIR}/${tname} + COMMENT "Generating ${tname}" + OUTPUT ${CMAKE_BINARY_DIR}/${tname} + ) endforeach() include_directories(${CMAKE_SOURCE_DIR}/include) @@ -60,13 +68,24 @@ set_target_properties(harness PROPERTIES # run-test.pl expects to find the harness in t/.libs/ RUNTIME_OUTPUT_DIRECTORY t/.libs) -file(GLOB TESTFILES ${CMAKE_SOURCE_DIR}/t/*.test) -add_custom_target(check) -foreach(testfile ${TESTFILES}) - get_filename_component(target_name ${testfile} NAME_WE) - add_custom_target(${target_name} - COMMAND ${PERL_EXECUTABLE} ${CMAKE_SOURCE_DIR}/t/run-test.pl ${testfile} - COMMENT "**${target_name} **" - DEPENDS harness) - add_dependencies(check ${target_name}) -endforeach() +if(Perl_FOUND) + file(GLOB TESTFILES ${CMAKE_SOURCE_DIR}/t/*.test) + add_custom_target(check) + foreach(testfile ${TESTFILES}) + get_filename_component(target_name ${testfile} NAME_WE) + add_custom_target(${target_name} + COMMAND ${PERL_EXECUTABLE} ${CMAKE_SOURCE_DIR}/t/run-test.pl ${testfile} + COMMENT "**${target_name} **" + DEPENDS harness) + add_dependencies(check ${target_name}) + endforeach() + + foreach(header_path ${TBL_FILES_HEADERS}) + get_filename_component(header_name ${header_path} NAME) + set(perl_header_path ${CMAKE_BINARY_DIR}/encoding-test/${header_name}) + add_custom_target(test-${header_name} + COMMAND ${CMAKE_COMMAND} -E compare_files + ${header_path} ${perl_header_path} + DEPENDS ${header_path} ${perl_header_path}) + endforeach() +endif() diff --git a/third-party/cmake/PatchLuv.cmake b/third-party/cmake/PatchLuv.cmake deleted file mode 100644 index 96595a2f30..0000000000 --- a/third-party/cmake/PatchLuv.cmake +++ /dev/null @@ -1,29 +0,0 @@ -# replace luv default rockspec with the alternate one under the "rockspecs" -# directory -file(GLOB LUV_ROCKSPEC RELATIVE ${LUV_SRC_DIR} ${LUV_SRC_DIR}/*.rockspec) -file(RENAME ${LUV_SRC_DIR}/rockspecs/${LUV_ROCKSPEC} ${LUV_SRC_DIR}/${LUV_ROCKSPEC}) - -# Some versions of mingw are missing defines required by luv dns module, add -# them now -set(LUV_SRC_DNS_C_DEFS -"#ifndef AI_NUMERICSERV -# define AI_NUMERICSERV 0x0008 -#endif -#ifndef AI_ALL -# define AI_ALL 0x00000100 -#endif -#ifndef AI_ADDRCONFIG -# define AI_ADDRCONFIG 0x00000400 -#endif -#ifndef AI_V4MAPPED -# define AI_V4MAPPED 0x00000800 -#endif") - -file(READ ${LUV_SRC_DIR}/src/dns.c LUV_SRC_DNS_C) -string(REPLACE - "\n#include <netdb.h>" - "\n#include <netdb.h>\n#else\n${LUV_SRC_DNS_C_DEFS}" - LUV_SRC_DNS_C_PATCHED - "${LUV_SRC_DNS_C}") -file(WRITE ${LUV_SRC_DIR}/src/dns.c "${LUV_SRC_DNS_C_PATCHED}") - diff --git a/third-party/cmake/UnibiliumCMakeLists.txt b/third-party/cmake/UnibiliumCMakeLists.txt new file mode 100644 index 0000000000..3c419654c4 --- /dev/null +++ b/third-party/cmake/UnibiliumCMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 2.8.7) +project(unibilium LANGUAGES C) + +file(GLOB SRC_FILES ${PROJECT_SOURCE_DIR}/*.c) +add_library(unibilium ${SRC_FILES}) +set_target_properties(unibilium PROPERTIES PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/unibilium.h + VERSION "${VERSION_MAJOR}.${VERSION_MINOR}") + +if(NOT WIN32) + execute_process(COMMAND "shell ncursesw6-config --terminfo-dirs 2>/dev/null || \ + ncurses6-config --terminfo-dirs 2>/dev/null || \ + ncursesw5-config --terminfo-dirs 2>/dev/null || \ + ncurses5-config --terminfo-dirs 2>/dev/null || \ + echo '/etc/terminfo:/lib/terminfo:/usr/share/terminfo:/usr/lib/terminfo:/usr/local/share/terminfo:/usr/local/lib/terminfo'" + OUTPUT_VARIABLE TERMINFO_DIRS) +endif() +target_compile_definitions(unibilium PUBLIC TERMINFO_DIRS ${TERMINFO_DIRS}) + +if(MSVC) + target_include_directories(unibilium PUBLIC ${PROJECT_SOURCE_DIR}/msvc-compat) +endif() + +include(GNUInstallDirs) +install(TARGETS unibilium + PUBLIC_HEADER + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/third-party/cmake/libtermkeyCMakeLists.txt b/third-party/cmake/libtermkeyCMakeLists.txt new file mode 100644 index 0000000000..cb57631c1c --- /dev/null +++ b/third-party/cmake/libtermkeyCMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 2.8.7) +project(libtermkey) + +add_definitions(-D _CRT_SECURE_NO_WARNINGS) +add_definitions(-DHAVE_UNIBILIUM) +if(NOT MSVC) + add_definitions(-std=c99) +endif() + +include_directories(${PROJECT_BINARY_DIR}/t) +include_directories(SYSTEM ${UNIBILIUM_INCLUDE_DIRS}) + +add_library(termkey termkey.c driver-csi.c driver-ti.c) +set_target_properties(termkey PROPERTIES + PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/termkey.h) +target_link_libraries(termkey ${UNIBILIUM_LIBRARIES}) + +include(GNUInstallDirs) +install(TARGETS termkey + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + +enable_testing() +file(GLOB TESTSOURCES "t/[0-9]*.c") +foreach(f ${TESTSOURCES}) + get_filename_component(t ${f} NAME_WE) + if(${t} STREQUAL 05read) + continue() + endif() + + add_executable("test_${t}" ${f} t/taplib.c) + target_link_libraries("test_${t}" termkey) + add_test("${t}" "test_${t}") +endforeach() diff --git a/third-party/msvc-compat/unistd.h b/third-party/msvc-compat/unistd.h new file mode 100644 index 0000000000..d9ee57b54b --- /dev/null +++ b/third-party/msvc-compat/unistd.h @@ -0,0 +1,10 @@ +#ifndef GUARD_UNIBILIUM_UNISTD_H_ +#define GUARD_UNIBILIUM_UNISTD_H_ + +#ifdef _WIN64 +typedef unsigned __int64 ssize_t; +#else +typedef _W64 unsigned int ssize_t; +#endif + +#endif diff --git a/third-party/patches/gettext-Fix-compilation-on-a-system-without-alloca.patch b/third-party/patches/gettext-Fix-compilation-on-a-system-without-alloca.patch new file mode 100644 index 0000000000..5c472c470f --- /dev/null +++ b/third-party/patches/gettext-Fix-compilation-on-a-system-without-alloca.patch @@ -0,0 +1,28 @@ +From 1d12aeb7334104f77070361492ff7cc8225503f5 Mon Sep 17 00:00:00 2001 +From: Daiki Ueno <ueno@gnu.org> +Date: Mon, 14 Nov 2016 13:27:58 +0100 +Subject: [PATCH] intl: Fix compilation on a system without alloca + +* gettext-runtime/intl/dcigettext.c (DCIGETTEXT): Fix typo 'tmp_dirname' +-> 'resolved_dirname'. Reported by Egor Pugin in: +http://lists.gnu.org/archive/html/bug-gettext/2016-09/msg00008.html +--- + gettext-runtime/intl/dcigettext.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/gettext-runtime/intl/dcigettext.c b/gettext-runtime/intl/dcigettext.c +index 83bd77574..92f6fd685 100644 +--- a/gettext-runtime/intl/dcigettext.c ++++ b/gettext-runtime/intl/dcigettext.c +@@ -634,7 +634,7 @@ DCIGETTEXT (const char *domainname, const char *msgid1, const char *msgid2, + for (;;) + { + resolved_dirname = (char *) alloca (path_max + dirname_len); +- ADD_BLOCK (block_list, tmp_dirname); ++ ADD_BLOCK (block_list, resolved_dirname); + + __set_errno (0); + ret = getcwd (resolved_dirname, path_max); +-- +2.16.1.windows.4 + diff --git a/third-party/patches/libtermkey-Add-support-for-Windows.patch b/third-party/patches/libtermkey-Add-support-for-Windows.patch new file mode 100644 index 0000000000..b839e94d05 --- /dev/null +++ b/third-party/patches/libtermkey-Add-support-for-Windows.patch @@ -0,0 +1,170 @@ +From fbe91a958816d85fa93665eb8f7a7a8e05eb9650 Mon Sep 17 00:00:00 2001 +From: Rui Abreu Ferreira <raf-ep@gmx.com> +Date: Tue, 5 Apr 2016 00:12:41 +0100 +Subject: [PATCH] Add support for Windows + +Ported termkey for windows. + +- The TERMKEY_FLAG_NOTERMIOS is ignore in Windows, since there is no termios. +- The termkey_waitkey() function is not implemented in windows, since there + is no poll() alternative. +- The CMake recipe only supports unibilium, not curses. +--- + driver-ti.c | 8 +++++++- + termkey-internal.h | 11 ++++++++++- + termkey.c | 22 ++++++++++++++++++---- + 3 files changed, 35 insertions(+), 6 deletions(-) + +diff --git a/driver-ti.c b/driver-ti.c +index e673ab7..f5f8052 100644 +--- a/driver-ti.c ++++ b/driver-ti.c +@@ -17,7 +17,9 @@ + #include <ctype.h> + #include <stdio.h> + #include <string.h> +-#include <unistd.h> ++#ifndef _WIN32 ++# include <unistd.h> ++#endif + #include <sys/types.h> + #include <sys/stat.h> + +@@ -338,8 +340,10 @@ static int start_driver(TermKey *tk, void *info) + if(fstat(tk->fd, &statbuf) == -1) + return 0; + ++#ifndef _WIN32 + if(S_ISFIFO(statbuf.st_mode)) + return 1; ++#endif + + // Can't call putp or tputs because they suck and don't give us fd control + len = strlen(start_string); +@@ -367,8 +371,10 @@ static int stop_driver(TermKey *tk, void *info) + if(fstat(tk->fd, &statbuf) == -1) + return 0; + ++#ifndef _WIN32 + if(S_ISFIFO(statbuf.st_mode)) + return 1; ++#endif + + /* The terminfo database will contain keys in application cursor key mode. + * We may need to enable that mode +diff --git a/termkey-internal.h b/termkey-internal.h +index 52829b3..b796729 100644 +--- a/termkey-internal.h ++++ b/termkey-internal.h +@@ -4,7 +4,14 @@ + #include "termkey.h" + + #include <stdint.h> +-#include <termios.h> ++#ifndef _WIN32 ++# include <termios.h> ++#endif ++ ++#ifdef _MSC_VER ++#include <BaseTsd.h> ++typedef SSIZE_T ssize_t; ++#endif + + struct TermKeyDriver + { +@@ -41,8 +48,10 @@ struct TermKey { + size_t hightide; /* Position beyond buffstart at which peekkey() should next start + * normally 0, but see also termkey_interpret_csi */ + ++#ifndef _WIN32 + struct termios restore_termios; + char restore_termios_valid; ++#endif + + TermKey_Terminfo_Getstr_Hook *ti_getstr_hook; + void *ti_getstr_hook_data; +diff --git a/termkey.c b/termkey.c +index 2f01f3a..145b99f 100644 +--- a/termkey.c ++++ b/termkey.c +@@ -3,14 +3,20 @@ + + #include <ctype.h> + #include <errno.h> +-#include <poll.h> +-#include <unistd.h> ++#ifndef _WIN32 ++# include <poll.h> ++# include <unistd.h> ++# include <strings.h> ++#endif + #include <string.h> +-#include <strings.h> + + #include <stdio.h> + +-#define strcaseeq(a,b) (strcasecmp(a,b) == 0) ++#ifdef _MSC_VER ++# define strcaseeq(a,b) (_stricmp(a,b) == 0) ++#else ++# define strcaseeq(a,b) (strcasecmp(a,b) == 0) ++#endif + + void termkey_check_version(int major, int minor) + { +@@ -282,7 +288,9 @@ static TermKey *termkey_alloc(void) + tk->buffsize = 256; /* bytes */ + tk->hightide = 0; + ++#ifndef _WIN32 + tk->restore_termios_valid = 0; ++#endif + + tk->ti_getstr_hook = NULL; + tk->ti_getstr_hook_data = NULL; +@@ -483,6 +491,7 @@ int termkey_start(TermKey *tk) + if(tk->is_started) + return 1; + ++#ifndef _WIN32 + if(tk->fd != -1 && !(tk->flags & TERMKEY_FLAG_NOTERMIOS)) { + struct termios termios; + if(tcgetattr(tk->fd, &termios) == 0) { +@@ -517,6 +526,7 @@ int termkey_start(TermKey *tk) + tcsetattr(tk->fd, TCSANOW, &termios); + } + } ++#endif + + struct TermKeyDriverNode *p; + for(p = tk->drivers; p; p = p->next) +@@ -542,8 +552,10 @@ int termkey_stop(TermKey *tk) + if(p->driver->stop_driver) + (*p->driver->stop_driver)(tk, p->info); + ++#ifndef _WIN32 + if(tk->restore_termios_valid) + tcsetattr(tk->fd, TCSANOW, &tk->restore_termios); ++#endif + + tk->is_started = 0; + +@@ -1046,6 +1058,7 @@ TermKeyResult termkey_getkey_force(TermKey *tk, TermKeyKey *key) + return ret; + } + ++#ifndef _WIN32 + TermKeyResult termkey_waitkey(TermKey *tk, TermKeyKey *key) + { + if(tk->fd == -1) { +@@ -1105,6 +1118,7 @@ retry: + + /* UNREACHABLE */ + } ++#endif + + TermKeyResult termkey_advisereadable(TermKey *tk) + { +-- +2.16.1.windows.4 + diff --git a/third-party/patches/libvterm-Remove-VLAs-for-MSVC.patch b/third-party/patches/libvterm-Remove-VLAs-for-MSVC.patch new file mode 100644 index 0000000000..e999c0fa9b --- /dev/null +++ b/third-party/patches/libvterm-Remove-VLAs-for-MSVC.patch @@ -0,0 +1,50 @@ +From eb386b1d82f7d07363c9133b7aa06902ccd555fe Mon Sep 17 00:00:00 2001 +Date: Tue, 27 Feb 2018 17:54:20 -0600 +Subject: [PATCH] Remove VLAs for MSVC + +VLAs are replaced with calls to _alloca() because MSVC does not support them. +--- + src/state.c | 7 ++++--- + 1 file changed, 4 insertions(+), 3 deletions(-) + +diff --git a/src/state.c b/src/state.c +index 84299df..f9aabb3 100644 +--- a/src/state.c ++++ b/src/state.c +@@ -1,5 +1,6 @@ + #include "vterm_internal.h" + ++#include <malloc.h> + #include <stdio.h> + #include <string.h> + +@@ -236,7 +237,7 @@ static int on_text(const char bytes[], size_t len, void *user) + VTermPos oldpos = state->pos; + + // We'll have at most len codepoints +- uint32_t codepoints[len]; ++ uint32_t* codepoints = _alloca(len * sizeof(uint32_t)); + int npoints = 0; + size_t eaten = 0; + +@@ -313,7 +314,7 @@ static int on_text(const char bytes[], size_t len, void *user) + + int width = 0; + +- uint32_t chars[glyph_ends - glyph_starts + 1]; ++ uint32_t* chars = _alloca((glyph_ends - glyph_starts + 1) * sizeof(uint32_t)); + + for( ; i < glyph_ends; i++) { + chars[i - glyph_starts] = codepoints[i]; +@@ -512,7 +513,7 @@ static int settermprop_int(VTermState *state, VTermProp prop, int v) + + static int settermprop_string(VTermState *state, VTermProp prop, const char *str, size_t len) + { +- char strvalue[len+1]; ++ char* strvalue = _alloca(len+1); + strncpy(strvalue, str, len); + strvalue[len] = 0; + +-- +2.16.1.windows.4 + diff --git a/third-party/patches/luarocks-Change-default-downloader-to-curl.patch b/third-party/patches/luarocks-Change-default-downloader-to-curl.patch new file mode 100644 index 0000000000..b7109a3b53 --- /dev/null +++ b/third-party/patches/luarocks-Change-default-downloader-to-curl.patch @@ -0,0 +1,24 @@ +From 69313032fad04743c96bc8f2a029b691857488f9 Mon Sep 17 00:00:00 2001 +Date: Thu, 1 Mar 2018 21:41:29 -0600 +Subject: [PATCH] Change default downloader to curl + +--- + install.bat | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/install.bat b/install.bat +index 09cf9aa..76e4059 100644 +--- a/install.bat ++++ b/install.bat +@@ -1037,7 +1037,7 @@ f:write(S[=[ + site_config.LUAROCKS_UNAME_M=[[$UNAME_M]] + site_config.LUAROCKS_ROCKS_TREE=[[$TREE_ROOT]] + site_config.LUAROCKS_PREFIX=[[$PREFIX]] +-site_config.LUAROCKS_DOWNLOADER=[[wget]] ++site_config.LUAROCKS_DOWNLOADER=[[curl]] + site_config.LUAROCKS_MD5CHECKER=[[md5sum]] + ]=]) + if FORCE_CONFIG then +-- +2.16.1.windows.4 + diff --git a/third-party/patches/luv-Add-missing-definitions-for-MinGW.patch b/third-party/patches/luv-Add-missing-definitions-for-MinGW.patch new file mode 100644 index 0000000000..8954ae21c3 --- /dev/null +++ b/third-party/patches/luv-Add-missing-definitions-for-MinGW.patch @@ -0,0 +1,24 @@ +diff --git a/src/dns.c b/src/dns.c +index 8634157..5f36625 100644 +--- a/src/dns.c ++++ b/src/dns.c +@@ -20,6 +20,19 @@ + #include <sys/types.h> + #include <sys/socket.h> + #include <netdb.h> ++#elif __MINGW32__ ++# ifndef AI_NUMERICSERV ++# define AI_NUMERICSERV 0x0008 ++# endif ++# ifndef AI_ALL ++# define AI_ALL 0x00000100 ++# endif ++# ifndef AI_ADDRCONFIG ++# define AI_ADDRCONFIG 0x00000400 ++# endif ++# ifndef AI_V4MAPPED ++# define AI_V4MAPPED 0x00000800 ++# endif + #endif + + static void luv_pushaddrinfo(lua_State* L, struct addrinfo* res) { |