diff options
174 files changed, 9054 insertions, 4620 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 20e5f0ad76..ab4bead7fb 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -6,7 +6,7 @@ freebsd_task: name: FreeBSD only_if: $BRANCH != "master" freebsd_instance: - image_family: freebsd-13-1 + image_family: freebsd-13-2 timeout_in: 30m install_script: - pkg install -y cmake gmake ninja unzip wget gettext python git diff --git a/.clang-tidy b/.clang-tidy index 1c7d13e2b0..e85ebb6758 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -41,3 +41,4 @@ Checks: > -readability-redundant-declaration, -readability-redundant-function-ptr-dereference, -readability-suspicious-call-argument, + -readability-non-const-parameter, diff --git a/.github/scripts/install_deps.sh b/.github/scripts/install_deps.sh index 32591eb8da..6a4e163feb 100755 --- a/.github/scripts/install_deps.sh +++ b/.github/scripts/install_deps.sh @@ -1,26 +1,20 @@ #!/bin/bash -SUDO="sudo" - while (($# > 0)); do case $1 in --test) # install test dependencies TEST=1 shift ;; - --container) # don't use sudo - SUDO="" - shift - ;; esac done os=$(uname -s) if [[ $os == Linux ]]; then - $SUDO apt-get update - $SUDO apt-get install -y build-essential cmake curl gettext ninja-build unzip + sudo apt-get update + sudo apt-get install -y build-essential cmake curl gettext ninja-build unzip if [[ -n $TEST ]]; then - $SUDO apt-get install -y locales-all cpanminus + sudo apt-get install -y locales-all cpanminus fi elif [[ $os == Darwin ]]; then brew update --quiet diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 19d443e9e2..982ec707ae 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,12 +1,5 @@ name: build on: - push: - paths: - - '**.cmake' - - '**/CMakeLists.txt' - - '**/CMakePresets.json' - - 'cmake.*/**' - - '.github/**' pull_request: branches: - 'master' @@ -19,7 +12,7 @@ on: - '.github/**' concurrency: - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.repository_owner == 'neovim' && github.sha || github.ref_name }} + group: ${{ github.workflow }}-${{ github.head_ref }} cancel-in-progress: true env: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a29ea46027..365c3fdf56 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,14 +1,15 @@ name: "CodeQL" +concurrency: + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} + cancel-in-progress: true + on: push: branches: [ "master" ] pull_request: # The branches below must be a subset of the branches above branches: [ "master" ] - schedule: - - cron: '42 0 * * 0' - workflow_dispatch: jobs: analyze: name: Analyze diff --git a/.github/workflows/notes.md b/.github/workflows/notes.md index 8f05c39d2a..7a87f936c3 100644 --- a/.github/workflows/notes.md +++ b/.github/workflows/notes.md @@ -27,6 +27,10 @@ ${NVIM_VERSION} ### Linux (x64) +Minimum glibc version to run these releases is 3.31. People requring releases +that work on older glibc versions can find them at +https://github.com/neovim/neovim-releases. + #### AppImage 1. Download **nvim.appimage** 2. Run `chmod u+x nvim.appimage && ./nvim.appimage` diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5260eec567..b19019d06d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,25 +21,9 @@ jobs: CC: gcc-10 outputs: version: ${{ steps.build.outputs.version }} - container: - image: ubuntu:18.04 - options: --privileged # Privileged mode is needed to load fuse module. steps: - - name: Prepare container - run: | - apt-get update - apt-get install -y software-properties-common - add-apt-repository -y ppa:ubuntu-toolchain-r/test # For gcc-10. - add-apt-repository -y ppa:git-core/ppa # For git>=2.18. - apt-get update - apt-get install -y git gcc-10 - apt-get install -y fuse libfuse2 # For linuxdeploy. - # Workaround for https://github.com/actions/checkout/issues/766. - git config --global --add safe.directory "$GITHUB_WORKSPACE" - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - run: ./.github/scripts/install_deps.sh --container + - run: ./.github/scripts/install_deps.sh - if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name != 'nightly') run: | echo 'NVIM_BUILD_TYPE=Release' >> $GITHUB_ENV @@ -76,8 +60,6 @@ jobs: runs-on: macos-11 steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Install dependencies run: ./.github/scripts/install_deps.sh - if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name != 'nightly') @@ -97,8 +79,6 @@ jobs: name: windows (MSVC_64) steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - run: .github/scripts/env.ps1 - name: Build deps run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 050c534549..585e0223f0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,9 @@ name: test on: push: + branches: + - 'master' + - 'release-[0-9]+.[0-9]+' pull_request: branches: - 'master' @@ -9,7 +12,7 @@ on: - 'contrib/**' concurrency: - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.repository_owner == 'neovim' && github.sha || github.ref_name }} + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} cancel-in-progress: true env: @@ -32,13 +35,15 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Homebrew - uses: Homebrew/actions/setup-homebrew@master - - name: Install dependencies + run: ./.github/scripts/install_deps.sh + + - name: Install stylua run: | - ./.github/scripts/install_deps.sh - brew install stylua + URL=$(curl -L https://api.github.com/repos/JohnnyMorganz/StyLua/releases/latest | jq -r '.assets[] | select(.name == "stylua-linux-x86_64.zip") | .browser_download_url') + wget --directory-prefix="$BIN_DIR" "$URL" + (cd "$BIN_DIR"; unzip stylua*.zip) + echo "$BIN_DIR" >> $GITHUB_PATH - uses: ./.github/actions/cache diff --git a/.styluaignore b/.styluaignore index 10482525f2..786a9ce4d3 100644 --- a/.styluaignore +++ b/.styluaignore @@ -1,5 +1,7 @@ /scripts /src /test +/build /runtime/lua/vim/re.lua /runtime/lua/vim/_meta/options.lua +/runtime/lua/coxpcall.lua diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e4beb3448..f0303be3eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -224,21 +224,17 @@ add_glob_target( FLAGS -ll ${PROJECT_SOURCE_DIR}/test/lua_runner.lua ${CMAKE_BINARY_DIR}/usr luacheck -q GLOB_DIRS runtime/ scripts/ src/ test/ GLOB_PAT *.lua - EXCLUDE runtime/lua/vim/_meta/.* - TOUCH_STRATEGY SINGLE) -add_dependencies(lintlua-luacheck lua-dev-deps) - -add_glob_target( - TARGET lintlua-stylua - COMMAND ${STYLUA_PRG} - FLAGS --color=always --check - GLOB_DIRS runtime/ - GLOB_PAT *.lua EXCLUDE - /runtime/lua/vim/re.lua - /runtime/lua/vim/_meta/.* + runtime/lua/vim/_meta/.* + runtime/lua/coxpcall.lua TOUCH_STRATEGY SINGLE) +add_dependencies(lintlua-luacheck lua-dev-deps) +# Don't use add_glob_target as .styluaignore won't be respected. +# https://github.com/JohnnyMorganz/StyLua/issues/751 +add_custom_target(lintlua-stylua + COMMAND ${STYLUA_PRG} --color=always --check . + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) add_custom_target(lintlua) add_dependencies(lintlua lintlua-luacheck lintlua-stylua) @@ -295,8 +291,6 @@ ExternalProject_Add(uncrustify CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS} EXCLUDE_FROM_ALL TRUE) -include(BuildLuarocks) - ExternalProject_Add(lua-dev-deps URL https://github.com/neovim/deps/raw/5a1f71cceb24990a0b15fd9a472a5f549f019248/opt/lua-dev-deps.tar.gz URL_HASH SHA256=27db2495f5eddc7fc191701ec9b291486853530c6125609d3197d03481e8d5a2 @@ -307,4 +301,3 @@ ExternalProject_Add(lua-dev-deps BUILD_COMMAND "" INSTALL_COMMAND "" EXCLUDE_FROM_ALL TRUE) -add_dependencies(test_deps lua-dev-deps) diff --git a/MAINTAIN.md b/MAINTAIN.md index 07e8796ac8..3e31fde118 100644 --- a/MAINTAIN.md +++ b/MAINTAIN.md @@ -69,7 +69,7 @@ remove existing features, or refactor parts of the code that would change user's workflow. In these cases, a deprecation policy is needed to properly inform users of the change. -In general, when a feature is slated to be removed it should: +When a (non-experimental) feature is slated to be removed it should: 1. Be _soft_ deprecated in the _next_ release - Use of the deprecated feature will still work. @@ -106,6 +106,10 @@ Feature removals which may benefit from community input or further discussion should also have a tracking issue (which should be linked to in the release notes). +Exceptions to this policy may be made (for experimental subsystems or when +there is broad consensus among maintainers). The rationale for the exception +should be stated explicitly and publicly. + Third-party dependencies ------------------------ @@ -143,6 +147,7 @@ These dependencies are "vendored" (inlined), we must update the sources manually * Run `scripts/gen_lsp.lua` to update. * `src/bit.c`: only for PUC lua: port of `require'bit'` from luajit https://bitop.luajit.org/ * [treesitter parsers](https://github.com/neovim/neovim/blob/fcc24e43e0b5f9d801a01ff2b8f78ce8c16dd551/cmake.deps/CMakeLists.txt#L197-L210) +* `runtime/lua/coxpcall.lua`: coxpcall (only needed for PUC lua, builtin to luajit) ### Forks diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt index 4ea8887e2d..a172e32bbb 100644 --- a/cmake.deps/deps.txt +++ b/cmake.deps/deps.txt @@ -4,8 +4,8 @@ LIBUV_SHA256 7aa66be3413ae10605e1f5c9ae934504ffe317ef68ea16fdaa83e23905c681bd MSGPACK_URL https://github.com/msgpack/msgpack-c/releases/download/c-6.0.0/msgpack-c-6.0.0.tar.gz MSGPACK_SHA256 3654f5e2c652dc52e0a993e270bb57d5702b262703f03771c152bba51602aeba -LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/41fb94defa8f830ce69a8122b03f6ac3216d392a.tar.gz -LUAJIT_SHA256 b518721280390e4cec1af30f6819d86756ce4234d82410a55a4e121855f64e08 +LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/7a77a3cd85ed49498cc3b17e70c46ad518aebb72.tar.gz +LUAJIT_SHA256 c1365590ac8bd05a760002e87fd40d0a0b34b59803fb3b65aa5792a0577daf2c LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333 @@ -60,5 +60,5 @@ TREESITTER_BASH_URL https://github.com/tree-sitter/tree-sitter-bash/archive/4936 TREESITTER_BASH_SHA256 99ebe9f2886efecc1a5e9e1360d804a1b49ad89976a66bb5c3871539cca5fb7e TREESITTER_MARKDOWN_URL https://github.com/MDeiml/tree-sitter-markdown/archive/v0.1.6.tar.gz TREESITTER_MARKDOWN_SHA256 34cbeadfbe07454a5040163d04b3118ef0ac427a5cba39089de7384992729088 -TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/a0d0b35c046681485fa19203cd121ce830968248.tar.gz -TREESITTER_SHA256 02c0e9d0ba72978dafcd86e0eb0980e23fc378989b988d95861e0621da70ce65 +TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/46af27796a76c72d8466627d499f2bca4af958ee.tar.gz +TREESITTER_SHA256 2e35fa4c4d66c34d04fdb4a3a6c3abe01a05dff571ed9553b15aabf25d870f69 diff --git a/cmake/BuildLuarocks.cmake b/cmake/BuildLuarocks.cmake deleted file mode 100644 index 2dc493a59b..0000000000 --- a/cmake/BuildLuarocks.cmake +++ /dev/null @@ -1,104 +0,0 @@ -# Luarocks recipe. Luarocks is only required when testing Neovim. -# NOTE: LuaRocks rocks need to "DEPENDS" on the previous module, because -# running luarocks in parallel will break, e.g. when some rocks have -# the same dependency. - -# The luarocks binary location -set(LUAROCKS_BINARY ${DEPS_BIN_DIR}/luarocks) - -# Arguments for calls to 'luarocks build' -if(NOT MSVC) - # In MSVC don't pass the compiler/linker to luarocks, the bundled - # version already knows, and passing them here breaks the build - set(LUAROCKS_BUILDARGS CC=${DEPS_C_COMPILER} LD=${DEPS_C_COMPILER}) -endif() - -if(UNIX) - if(PREFER_LUA) - find_package(Lua 5.1 EXACT REQUIRED) - get_filename_component(LUA_ROOT ${LUA_INCLUDE_DIR} DIRECTORY) - list(APPEND LUAROCKS_OPTS - --with-lua=${LUA_ROOT}) - else() - find_package(Luajit REQUIRED) - get_filename_component(LUA_ROOT ${LUAJIT_INCLUDE_DIR} DIRECTORY) - get_filename_component(LUA_ROOT ${LUA_ROOT} DIRECTORY) - list(APPEND LUAROCKS_OPTS - --with-lua=${LUA_ROOT} - --with-lua-include=${LUAJIT_INCLUDE_DIR} - --with-lua-interpreter=luajit) - endif() - - set(LUAROCKS_CONFIGURE_COMMAND ${DEPS_BUILD_DIR}/src/luarocks/configure - --prefix=${DEPS_INSTALL_DIR} --force-config ${LUAROCKS_OPTS}) - set(LUAROCKS_INSTALL_COMMAND ${MAKE_PRG} -j1 bootstrap) -elseif(MSVC OR MINGW) - if(MINGW) - set(COMPILER_FLAG /MW) - elseif(MSVC) - set(COMPILER_FLAG /MSVC) - endif() - - find_package(Luajit REQUIRED) - # Always assume bundled luajit for native Win32 - set(LUAROCKS_INSTALL_COMMAND install.bat /FORCECONFIG /NOREG /NOADMIN /Q /F - /LUA ${DEPS_PREFIX} - /INC ${LUAJIT_INCLUDE_DIR} - /P ${DEPS_INSTALL_DIR}/luarocks - /TREE ${DEPS_INSTALL_DIR} - /SCRIPTS ${DEPS_BIN_DIR} - ${COMPILER_FLAG}) - - set(LUAROCKS_BINARY ${DEPS_INSTALL_DIR}/luarocks/luarocks.bat) -else() - message(FATAL_ERROR "Trying to build luarocks in an unsupported system ${CMAKE_SYSTEM_NAME}/${CMAKE_C_COMPILER_ID}") -endif() - -ExternalProject_Add(luarocks - URL https://github.com/luarocks/luarocks/archive/v3.9.2.tar.gz - URL_HASH SHA256=a0b36cd68586cd79966d0106bb2e5a4f5523327867995fd66bee4237062b3e3b - DOWNLOAD_NO_PROGRESS TRUE - DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/luarocks - BUILD_IN_SOURCE 1 - CONFIGURE_COMMAND "${LUAROCKS_CONFIGURE_COMMAND}" - BUILD_COMMAND "" - INSTALL_COMMAND "${LUAROCKS_INSTALL_COMMAND}" - EXCLUDE_FROM_ALL TRUE) - -set(ROCKS_DIR ${DEPS_LIB_DIR}/luarocks/rocks) - -if(MSVC) - # Workaround for luarocks failing to find the md5sum.exe it is shipped with. - list(APPEND LUAROCKS_BUILDARGS MD5SUM=md5sum) - set(PATH PATH=${DEPS_INSTALL_DIR}/luarocks/tools;$ENV{PATH}) -endif() - -set(CURRENT_DEP luarocks) - -function(Download ROCK VER) - if(ARGV2) - set(OUTPUT ${ARGV2}) - else() - set(OUTPUT ${ROCKS_DIR}/${ROCK}) - endif() - add_custom_command(OUTPUT ${OUTPUT} - COMMAND ${CMAKE_COMMAND} -E env "${PATH}" ${LUAROCKS_BINARY} build ${ROCK} ${VER} ${LUAROCKS_BUILDARGS} - DEPENDS ${CURRENT_DEP}) - add_custom_target(${ROCK} DEPENDS ${OUTPUT}) - set(CURRENT_DEP ${ROCK} PARENT_SCOPE) -endfunction() - -if(WIN32) - set(LUACHECK_EXE "${DEPS_BIN_DIR}/luacheck.bat") -else() - set(LUACHECK_EXE "${DEPS_BIN_DIR}/luacheck") -endif() - -add_custom_target(test_deps) - -Download(luacheck 1.1.0-1 ${LUACHECK_EXE}) - -if(PREFER_LUA) - Download(coxpcall 1.17.0-1) - add_dependencies(test_deps coxpcall) -endif() diff --git a/cmake/RunTests.cmake b/cmake/RunTests.cmake index d470793a27..8d5b0d2402 100644 --- a/cmake/RunTests.cmake +++ b/cmake/RunTests.cmake @@ -64,6 +64,11 @@ endif() set(ENV{SYSTEM_NAME} ${CMAKE_HOST_SYSTEM_NAME}) # used by test/helpers.lua. +if(NOT WIN32) + # Tests assume POSIX "sh" and may fail if SHELL=fish. #24941 #6172 + set(ENV{SHELL} sh) +endif() + execute_process( # Note: because of "-ll" (low-level interpreter mode), some modules like # _editor.lua are not loaded. diff --git a/contrib/local.mk.example b/contrib/local.mk.example index 4bd026cf87..bda2c1b4dc 100644 --- a/contrib/local.mk.example +++ b/contrib/local.mk.example @@ -46,7 +46,6 @@ # DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_LIBUV=OFF # DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_LIBVTERM=OFF # DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_LUAJIT=OFF -# DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_LUAROCKS=OFF # DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_MSGPACK=OFF # DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_UNIBILIUM=OFF # DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_UTF8PROC=OFF diff --git a/runtime/autoload/cargo.vim b/runtime/autoload/cargo.vim new file mode 100644 index 0000000000..6696b3105f --- /dev/null +++ b/runtime/autoload/cargo.vim @@ -0,0 +1,149 @@ +" Last Modified: 2023-09-11 + +function! cargo#Load() + " Utility call to get this script loaded, for debugging +endfunction + +function! cargo#cmd(args) abort + " Trim trailing spaces. This is necessary since :terminal command parses + " trailing spaces as an empty argument. + let args = substitute(a:args, '\s\+$', '', '') + if exists('g:cargo_shell_command_runner') + let cmd = g:cargo_shell_command_runner + elseif has('terminal') + let cmd = 'terminal' + elseif has('nvim') + let cmd = 'noautocmd new | terminal' + else + let cmd = '!' + endif + execute cmd 'cargo' args +endfunction + +function! s:nearest_cargo(...) abort + " If the second argument is not specified, the first argument determines + " whether we will start from the current directory or the directory of the + " current buffer, otherwise, we start with the provided path on the + " second argument. + + let l:is_getcwd = get(a:, 1, 0) + if l:is_getcwd + let l:starting_path = get(a:, 2, getcwd()) + else + let l:starting_path = get(a:, 2, expand('%:p:h')) + endif + + return findfile('Cargo.toml', l:starting_path . ';') +endfunction + +function! cargo#nearestCargo(is_getcwd) abort + return s:nearest_cargo(a:is_getcwd) +endfunction + +function! cargo#nearestWorkspaceCargo(is_getcwd) abort + let l:nearest = s:nearest_cargo(a:is_getcwd) + while l:nearest !=# '' + for l:line in readfile(l:nearest, '', 0x100) + if l:line =~# '\V[workspace]' + return l:nearest + endif + endfor + let l:next = fnamemodify(l:nearest, ':p:h:h') + let l:nearest = s:nearest_cargo(0, l:next) + endwhile + return '' +endfunction + +function! cargo#nearestRootCargo(is_getcwd) abort + " Try to find a workspace Cargo.toml, and if not found, take the nearest + " regular Cargo.toml + let l:workspace_cargo = cargo#nearestWorkspaceCargo(a:is_getcwd) + if l:workspace_cargo !=# '' + return l:workspace_cargo + endif + return s:nearest_cargo(a:is_getcwd) +endfunction + + +function! cargo#build(args) + call cargo#cmd("build " . a:args) +endfunction + +function! cargo#check(args) + call cargo#cmd("check " . a:args) +endfunction + +function! cargo#clean(args) + call cargo#cmd("clean " . a:args) +endfunction + +function! cargo#doc(args) + call cargo#cmd("doc " . a:args) +endfunction + +function! cargo#new(args) + call cargo#cmd("new " . a:args) + cd `=a:args` +endfunction + +function! cargo#init(args) + call cargo#cmd("init " . a:args) +endfunction + +function! cargo#run(args) + call cargo#cmd("run " . a:args) +endfunction + +function! cargo#test(args) + call cargo#cmd("test " . a:args) +endfunction + +function! cargo#bench(args) + call cargo#cmd("bench " . a:args) +endfunction + +function! cargo#update(args) + call cargo#cmd("update " . a:args) +endfunction + +function! cargo#search(args) + call cargo#cmd("search " . a:args) +endfunction + +function! cargo#publish(args) + call cargo#cmd("publish " . a:args) +endfunction + +function! cargo#install(args) + call cargo#cmd("install " . a:args) +endfunction + +function! cargo#runtarget(args) + let l:filename = expand('%:p') + let l:read_manifest = system('cargo read-manifest') + let l:metadata = json_decode(l:read_manifest) + let l:targets = get(l:metadata, 'targets', []) + let l:did_run = 0 + for l:target in l:targets + let l:src_path = get(l:target, 'src_path', '') + let l:kinds = get(l:target, 'kind', []) + let l:name = get(l:target, 'name', '') + if l:src_path == l:filename + if index(l:kinds, 'example') != -1 + let l:did_run = 1 + call cargo#run("--example " . shellescape(l:name) . " " . a:args) + return + elseif index(l:kinds, 'bin') != -1 + let l:did_run = 1 + call cargo#run("--bin " . shellescape(l:name) . " " . a:args) + return + endif + endif + endfor + if l:did_run != 1 + call cargo#run(a:args) + return + endif +endfunction + +" vim: set et sw=4 sts=4 ts=8: diff --git a/runtime/autoload/cargo/quickfix.vim b/runtime/autoload/cargo/quickfix.vim new file mode 100644 index 0000000000..f2a006f6c5 --- /dev/null +++ b/runtime/autoload/cargo/quickfix.vim @@ -0,0 +1,29 @@ +" Last Modified: 2023-09-11 + +function! cargo#quickfix#CmdPre() abort + if &filetype ==# 'rust' && get(b:, 'current_compiler', '') ==# 'cargo' && + \ &makeprg =~ '\V\^cargo\ \.\*' + " Preserve the current directory, and 'lcd' to the nearest Cargo file. + let b:rust_compiler_cargo_qf_has_lcd = haslocaldir() + let b:rust_compiler_cargo_qf_prev_cd = getcwd() + let b:rust_compiler_cargo_qf_prev_cd_saved = 1 + let l:nearest = fnamemodify(cargo#nearestRootCargo(0), ':h') + execute 'lchdir! '.l:nearest + else + let b:rust_compiler_cargo_qf_prev_cd_saved = 0 + endif +endfunction + +function! cargo#quickfix#CmdPost() abort + if exists("b:rust_compiler_cargo_qf_prev_cd_saved") && b:rust_compiler_cargo_qf_prev_cd_saved + " Restore the current directory. + if b:rust_compiler_cargo_qf_has_lcd + execute 'lchdir! '.b:rust_compiler_cargo_qf_prev_cd + else + execute 'chdir! '.b:rust_compiler_cargo_qf_prev_cd + endif + let b:rust_compiler_cargo_qf_prev_cd_saved = 0 + endif +endfunction + +" vim: set et sw=4 sts=4 ts=8: diff --git a/runtime/autoload/rust.vim b/runtime/autoload/rust.vim index 4230332fa7..5ccbf4b382 100644 --- a/runtime/autoload/rust.vim +++ b/runtime/autoload/rust.vim @@ -1,207 +1,258 @@ -" Author: Lily Ballard " Description: Helper functions for Rust commands/mappings -" Last Modified: May 27, 2014 +" Last Modified: 2023-09-11 " For bugs, patches and license go to https://github.com/rust-lang/rust.vim +function! rust#Load() + " Utility call to get this script loaded, for debugging +endfunction + +function! rust#GetConfigVar(name, default) + " Local buffer variable with same name takes predeence over global + if has_key(b:, a:name) + return get(b:, a:name) + endif + if has_key(g:, a:name) + return get(g:, a:name) + endif + return a:default +endfunction + +" Include expression {{{1 + +function! rust#IncludeExpr(fname) abort + " Remove leading 'crate::' to deal with 2018 edition style 'use' + " statements + let l:fname = substitute(a:fname, '^crate::', '', '') + + " Remove trailing colons arising from lines like + " + " use foo::{Bar, Baz}; + let l:fname = substitute(l:fname, ':\+$', '', '') + + " Replace '::' with '/' + let l:fname = substitute(l:fname, '::', '/', 'g') + + " When we have + " + " use foo::bar::baz; + " + " we can't tell whether baz is a module or a function; and we can't tell + " which modules correspond to files. + " + " So we work our way up, trying + " + " foo/bar/baz.rs + " foo/bar.rs + " foo.rs + while l:fname !=# '.' + let l:path = findfile(l:fname) + if !empty(l:path) + return l:fname + endif + let l:fname = fnamemodify(l:fname, ':h') + endwhile + return l:fname +endfunction + " Jump {{{1 function! rust#Jump(mode, function) range - let cnt = v:count1 - normal! m' - if a:mode ==# 'v' - norm! gv - endif - let foldenable = &foldenable - set nofoldenable - while cnt > 0 - execute "call <SID>Jump_" . a:function . "()" - let cnt = cnt - 1 - endwhile - let &foldenable = foldenable + let cnt = v:count1 + normal! m' + if a:mode ==# 'v' + norm! gv + endif + let foldenable = &foldenable + set nofoldenable + while cnt > 0 + execute "call <SID>Jump_" . a:function . "()" + let cnt = cnt - 1 + endwhile + let &foldenable = foldenable endfunction function! s:Jump_Back() - call search('{', 'b') - keepjumps normal! w99[{ + call search('{', 'b') + keepjumps normal! w99[{ endfunction function! s:Jump_Forward() - normal! j0 - call search('{', 'b') - keepjumps normal! w99[{% - call search('{') + normal! j0 + call search('{', 'b') + keepjumps normal! w99[{% + call search('{') endfunction " Run {{{1 function! rust#Run(bang, args) - let args = s:ShellTokenize(a:args) - if a:bang - let idx = index(l:args, '--') - if idx != -1 - let rustc_args = idx == 0 ? [] : l:args[:idx-1] - let args = l:args[idx+1:] - else - let rustc_args = l:args - let args = [] - endif - else - let rustc_args = [] - endif - - let b:rust_last_rustc_args = l:rustc_args - let b:rust_last_args = l:args - - call s:WithPath(function("s:Run"), rustc_args, args) + let args = s:ShellTokenize(a:args) + if a:bang + let idx = index(l:args, '--') + if idx != -1 + let rustc_args = idx == 0 ? [] : l:args[:idx-1] + let args = l:args[idx+1:] + else + let rustc_args = l:args + let args = [] + endif + else + let rustc_args = [] + endif + + let b:rust_last_rustc_args = l:rustc_args + let b:rust_last_args = l:args + + call s:WithPath(function("s:Run"), rustc_args, args) endfunction function! s:Run(dict, rustc_args, args) - let exepath = a:dict.tmpdir.'/'.fnamemodify(a:dict.path, ':t:r') - if has('win32') - let exepath .= '.exe' - endif - - let relpath = get(a:dict, 'tmpdir_relpath', a:dict.path) - let rustc_args = [relpath, '-o', exepath] + a:rustc_args - - let rustc = exists("g:rustc_path") ? g:rustc_path : "rustc" - - let pwd = a:dict.istemp ? a:dict.tmpdir : '' - let output = s:system(pwd, shellescape(rustc) . " " . join(map(rustc_args, 'shellescape(v:val)'))) - if output != '' - echohl WarningMsg - echo output - echohl None - endif - if !v:shell_error - exe '!' . shellescape(exepath) . " " . join(map(a:args, 'shellescape(v:val)')) - endif + let exepath = a:dict.tmpdir.'/'.fnamemodify(a:dict.path, ':t:r') + if has('win32') + let exepath .= '.exe' + endif + + let relpath = get(a:dict, 'tmpdir_relpath', a:dict.path) + let rustc_args = [relpath, '-o', exepath] + a:rustc_args + + let rustc = exists("g:rustc_path") ? g:rustc_path : "rustc" + + let pwd = a:dict.istemp ? a:dict.tmpdir : '' + let output = s:system(pwd, shellescape(rustc) . " " . join(map(rustc_args, 'shellescape(v:val)'))) + if output !=# '' + echohl WarningMsg + echo output + echohl None + endif + if !v:shell_error + exe '!' . shellescape(exepath) . " " . join(map(a:args, 'shellescape(v:val)')) + endif endfunction " Expand {{{1 function! rust#Expand(bang, args) - let args = s:ShellTokenize(a:args) - if a:bang && !empty(l:args) - let pretty = remove(l:args, 0) - else - let pretty = "expanded" - endif - call s:WithPath(function("s:Expand"), pretty, args) + let args = s:ShellTokenize(a:args) + if a:bang && !empty(l:args) + let pretty = remove(l:args, 0) + else + let pretty = "expanded" + endif + call s:WithPath(function("s:Expand"), pretty, args) endfunction function! s:Expand(dict, pretty, args) - try - let rustc = exists("g:rustc_path") ? g:rustc_path : "rustc" - - if a:pretty =~? '^\%(everybody_loops$\|flowgraph=\)' - let flag = '--xpretty' - else - let flag = '--pretty' - endif - let relpath = get(a:dict, 'tmpdir_relpath', a:dict.path) - let args = [relpath, '-Z', 'unstable-options', l:flag, a:pretty] + a:args - let pwd = a:dict.istemp ? a:dict.tmpdir : '' - let output = s:system(pwd, shellescape(rustc) . " " . join(map(args, 'shellescape(v:val)'))) - if v:shell_error - echohl WarningMsg - echo output - echohl None - else - new - silent put =output - 1 - d - setl filetype=rust - setl buftype=nofile - setl bufhidden=hide - setl noswapfile - " give the buffer a nice name - let suffix = 1 - let basename = fnamemodify(a:dict.path, ':t:r') - while 1 - let bufname = basename - if suffix > 1 | let bufname .= ' ('.suffix.')' | endif - let bufname .= '.pretty.rs' - if bufexists(bufname) - let suffix += 1 - continue - endif - exe 'silent noautocmd keepalt file' fnameescape(bufname) - break - endwhile - endif - endtry + try + let rustc = exists("g:rustc_path") ? g:rustc_path : "rustc" + + if a:pretty =~? '^\%(everybody_loops$\|flowgraph=\)' + let flag = '--xpretty' + else + let flag = '--pretty' + endif + let relpath = get(a:dict, 'tmpdir_relpath', a:dict.path) + let args = [relpath, '-Z', 'unstable-options', l:flag, a:pretty] + a:args + let pwd = a:dict.istemp ? a:dict.tmpdir : '' + let output = s:system(pwd, shellescape(rustc) . " " . join(map(args, 'shellescape(v:val)'))) + if v:shell_error + echohl WarningMsg + echo output + echohl None + else + new + silent put =output + 1 + d + setl filetype=rust + setl buftype=nofile + setl bufhidden=hide + setl noswapfile + " give the buffer a nice name + let suffix = 1 + let basename = fnamemodify(a:dict.path, ':t:r') + while 1 + let bufname = basename + if suffix > 1 | let bufname .= ' ('.suffix.')' | endif + let bufname .= '.pretty.rs' + if bufexists(bufname) + let suffix += 1 + continue + endif + exe 'silent noautocmd keepalt file' fnameescape(bufname) + break + endwhile + endif + endtry endfunction function! rust#CompleteExpand(lead, line, pos) - if a:line[: a:pos-1] =~ '^RustExpand!\s*\S*$' - " first argument and it has a ! - let list = ["normal", "expanded", "typed", "expanded,identified", "flowgraph=", "everybody_loops"] - if !empty(a:lead) - call filter(list, "v:val[:len(a:lead)-1] == a:lead") - endif - return list - endif - - return glob(escape(a:lead, "*?[") . '*', 0, 1) + if a:line[: a:pos-1] =~# '^RustExpand!\s*\S*$' + " first argument and it has a ! + let list = ["normal", "expanded", "typed", "expanded,identified", "flowgraph=", "everybody_loops"] + if !empty(a:lead) + call filter(list, "v:val[:len(a:lead)-1] == a:lead") + endif + return list + endif + + return glob(escape(a:lead, "*?[") . '*', 0, 1) endfunction " Emit {{{1 function! rust#Emit(type, args) - let args = s:ShellTokenize(a:args) - call s:WithPath(function("s:Emit"), a:type, args) + let args = s:ShellTokenize(a:args) + call s:WithPath(function("s:Emit"), a:type, args) endfunction function! s:Emit(dict, type, args) - try - let output_path = a:dict.tmpdir.'/output' - - let rustc = exists("g:rustc_path") ? g:rustc_path : "rustc" - - let relpath = get(a:dict, 'tmpdir_relpath', a:dict.path) - let args = [relpath, '--emit', a:type, '-o', output_path] + a:args - let pwd = a:dict.istemp ? a:dict.tmpdir : '' - let output = s:system(pwd, shellescape(rustc) . " " . join(map(args, 'shellescape(v:val)'))) - if output != '' - echohl WarningMsg - echo output - echohl None - endif - if !v:shell_error - new - exe 'silent keepalt read' fnameescape(output_path) - 1 - d - if a:type == "llvm-ir" - setl filetype=llvm - let extension = 'll' - elseif a:type == "asm" - setl filetype=asm - let extension = 's' - endif - setl buftype=nofile - setl bufhidden=hide - setl noswapfile - if exists('l:extension') - " give the buffer a nice name - let suffix = 1 - let basename = fnamemodify(a:dict.path, ':t:r') - while 1 - let bufname = basename - if suffix > 1 | let bufname .= ' ('.suffix.')' | endif - let bufname .= '.'.extension - if bufexists(bufname) - let suffix += 1 - continue - endif - exe 'silent noautocmd keepalt file' fnameescape(bufname) - break - endwhile - endif - endif - endtry + try + let output_path = a:dict.tmpdir.'/output' + + let rustc = exists("g:rustc_path") ? g:rustc_path : "rustc" + + let relpath = get(a:dict, 'tmpdir_relpath', a:dict.path) + let args = [relpath, '--emit', a:type, '-o', output_path] + a:args + let pwd = a:dict.istemp ? a:dict.tmpdir : '' + let output = s:system(pwd, shellescape(rustc) . " " . join(map(args, 'shellescape(v:val)'))) + if output !=# '' + echohl WarningMsg + echo output + echohl None + endif + if !v:shell_error + new + exe 'silent keepalt read' fnameescape(output_path) + 1 + d + if a:type ==# "llvm-ir" + setl filetype=llvm + let extension = 'll' + elseif a:type ==# "asm" + setl filetype=asm + let extension = 's' + endif + setl buftype=nofile + setl bufhidden=hide + setl noswapfile + if exists('l:extension') + " give the buffer a nice name + let suffix = 1 + let basename = fnamemodify(a:dict.path, ':t:r') + while 1 + let bufname = basename + if suffix > 1 | let bufname .= ' ('.suffix.')' | endif + let bufname .= '.'.extension + if bufexists(bufname) + let suffix += 1 + continue + endif + exe 'silent noautocmd keepalt file' fnameescape(bufname) + break + endwhile + endif + endif + endtry endfunction " Utility functions {{{1 @@ -219,145 +270,154 @@ endfunction " existing path of the current buffer. If the path is inside of {dict.tmpdir} " then it is guaranteed to have a '.rs' extension. function! s:WithPath(func, ...) - let buf = bufnr('') - let saved = {} - let dict = {} - try - let saved.write = &write - set write - let dict.path = expand('%') - let pathisempty = empty(dict.path) - - " Always create a tmpdir in case the wrapped command wants it - let dict.tmpdir = tempname() - call mkdir(dict.tmpdir) - - if pathisempty || !saved.write - let dict.istemp = 1 - " if we're doing this because of nowrite, preserve the filename - if !pathisempty - let filename = expand('%:t:r').".rs" - else - let filename = 'unnamed.rs' - endif - let dict.tmpdir_relpath = filename - let dict.path = dict.tmpdir.'/'.filename - - let saved.mod = &mod - set nomod - - silent exe 'keepalt write! ' . fnameescape(dict.path) - if pathisempty - silent keepalt 0file - endif - else - let dict.istemp = 0 - update - endif - - call call(a:func, [dict] + a:000) - finally - if bufexists(buf) - for [opt, value] in items(saved) - silent call setbufvar(buf, '&'.opt, value) - unlet value " avoid variable type mismatches - endfor - endif - if has_key(dict, 'tmpdir') | silent call s:RmDir(dict.tmpdir) | endif - endtry + let buf = bufnr('') + let saved = {} + let dict = {} + try + let saved.write = &write + set write + let dict.path = expand('%') + let pathisempty = empty(dict.path) + + " Always create a tmpdir in case the wrapped command wants it + let dict.tmpdir = tempname() + call mkdir(dict.tmpdir) + + if pathisempty || !saved.write + let dict.istemp = 1 + " if we're doing this because of nowrite, preserve the filename + if !pathisempty + let filename = expand('%:t:r').".rs" + else + let filename = 'unnamed.rs' + endif + let dict.tmpdir_relpath = filename + let dict.path = dict.tmpdir.'/'.filename + + let saved.mod = &modified + set nomodified + + silent exe 'keepalt write! ' . fnameescape(dict.path) + if pathisempty + silent keepalt 0file + endif + else + let dict.istemp = 0 + update + endif + + call call(a:func, [dict] + a:000) + finally + if bufexists(buf) + for [opt, value] in items(saved) + silent call setbufvar(buf, '&'.opt, value) + unlet value " avoid variable type mismatches + endfor + endif + if has_key(dict, 'tmpdir') | silent call s:RmDir(dict.tmpdir) | endif + endtry endfunction function! rust#AppendCmdLine(text) - call setcmdpos(getcmdpos()) - let cmd = getcmdline() . a:text - return cmd + call setcmdpos(getcmdpos()) + let cmd = getcmdline() . a:text + return cmd endfunction " Tokenize the string according to sh parsing rules function! s:ShellTokenize(text) - " states: - " 0: start of word - " 1: unquoted - " 2: unquoted backslash - " 3: double-quote - " 4: double-quoted backslash - " 5: single-quote - let l:state = 0 - let l:current = '' - let l:args = [] - for c in split(a:text, '\zs') - if l:state == 0 || l:state == 1 " unquoted - if l:c ==# ' ' - if l:state == 0 | continue | endif - call add(l:args, l:current) - let l:current = '' - let l:state = 0 - elseif l:c ==# '\' - let l:state = 2 - elseif l:c ==# '"' - let l:state = 3 - elseif l:c ==# "'" - let l:state = 5 - else - let l:current .= l:c - let l:state = 1 - endif - elseif l:state == 2 " unquoted backslash - if l:c !=# "\n" " can it even be \n? - let l:current .= l:c - endif - let l:state = 1 - elseif l:state == 3 " double-quote - if l:c ==# '\' - let l:state = 4 - elseif l:c ==# '"' - let l:state = 1 - else - let l:current .= l:c - endif - elseif l:state == 4 " double-quoted backslash - if stridx('$`"\', l:c) >= 0 - let l:current .= l:c - elseif l:c ==# "\n" " is this even possible? - " skip it - else - let l:current .= '\'.l:c - endif - let l:state = 3 - elseif l:state == 5 " single-quoted - if l:c == "'" - let l:state = 1 - else - let l:current .= l:c - endif - endif - endfor - if l:state != 0 - call add(l:args, l:current) - endif - return l:args + " states: + " 0: start of word + " 1: unquoted + " 2: unquoted backslash + " 3: double-quote + " 4: double-quoted backslash + " 5: single-quote + let l:state = 0 + let l:current = '' + let l:args = [] + for c in split(a:text, '\zs') + if l:state == 0 || l:state == 1 " unquoted + if l:c ==# ' ' + if l:state == 0 | continue | endif + call add(l:args, l:current) + let l:current = '' + let l:state = 0 + elseif l:c ==# '\' + let l:state = 2 + elseif l:c ==# '"' + let l:state = 3 + elseif l:c ==# "'" + let l:state = 5 + else + let l:current .= l:c + let l:state = 1 + endif + elseif l:state == 2 " unquoted backslash + if l:c !=# "\n" " can it even be \n? + let l:current .= l:c + endif + let l:state = 1 + elseif l:state == 3 " double-quote + if l:c ==# '\' + let l:state = 4 + elseif l:c ==# '"' + let l:state = 1 + else + let l:current .= l:c + endif + elseif l:state == 4 " double-quoted backslash + if stridx('$`"\', l:c) >= 0 + let l:current .= l:c + elseif l:c ==# "\n" " is this even possible? + " skip it + else + let l:current .= '\'.l:c + endif + let l:state = 3 + elseif l:state == 5 " single-quoted + if l:c ==# "'" + let l:state = 1 + else + let l:current .= l:c + endif + endif + endfor + if l:state != 0 + call add(l:args, l:current) + endif + return l:args endfunction function! s:RmDir(path) - " sanity check; make sure it's not empty, /, or $HOME - if empty(a:path) - echoerr 'Attempted to delete empty path' - return 0 - elseif a:path == '/' || a:path == $HOME - echoerr 'Attempted to delete protected path: ' . a:path - return 0 - endif - return system("rm -rf " . shellescape(a:path)) + " sanity check; make sure it's not empty, /, or $HOME + if empty(a:path) + echoerr 'Attempted to delete empty path' + return 0 + elseif a:path ==# '/' || a:path ==# $HOME + let l:path = expand(a:path) + if l:path ==# '/' || l:path ==# $HOME + echoerr 'Attempted to delete protected path: ' . a:path + return 0 + endif + endif + + if !isdirectory(a:path) + return 0 + endif + + " delete() returns 0 when removing file successfully + return delete(a:path, 'rf') == 0 endfunction " Executes {cmd} with the cwd set to {pwd}, without changing Vim's cwd. " If {pwd} is the empty string then it doesn't change the cwd. function! s:system(pwd, cmd) - let cmd = a:cmd - if !empty(a:pwd) - let cmd = 'cd ' . shellescape(a:pwd) . ' && ' . cmd - endif - return system(cmd) + let cmd = a:cmd + if !empty(a:pwd) + let cmd = 'cd ' . shellescape(a:pwd) . ' && ' . cmd + endif + return system(cmd) endfunction " Playpen Support {{{1 @@ -366,10 +426,10 @@ endfunction " http://github.com/mattn/gist-vim function! s:has_webapi() if !exists("*webapi#http#post") - try - call webapi#http#post() - catch - endtry + try + call webapi#http#post() + catch + endtry endif return exists("*webapi#http#post") endfunction @@ -381,35 +441,130 @@ function! rust#Play(count, line1, line2, ...) abort let l:rust_shortener_url = get(g:, 'rust_shortener_url', 'https://is.gd/') if !s:has_webapi() - echohl ErrorMsg | echomsg ':RustPlay depends on webapi.vim (https://github.com/mattn/webapi-vim)' | echohl None - return + echohl ErrorMsg | echomsg ':RustPlay depends on webapi.vim (https://github.com/mattn/webapi-vim)' | echohl None + return endif let bufname = bufname('%') if a:count < 1 - let content = join(getline(a:line1, a:line2), "\n") + let content = join(getline(a:line1, a:line2), "\n") else - let save_regcont = @" - let save_regtype = getregtype('"') - silent! normal! gvy - let content = @" - call setreg('"', save_regcont, save_regtype) + let save_regcont = @" + let save_regtype = getregtype('"') + silent! normal! gvy + let content = @" + call setreg('"', save_regcont, save_regtype) endif - let body = l:rust_playpen_url."?code=".webapi#http#encodeURI(content) + let url = l:rust_playpen_url."?code=".webapi#http#encodeURI(content) - if strlen(body) > 5000 - echohl ErrorMsg | echomsg 'Buffer too large, max 5000 encoded characters ('.strlen(body).')' | echohl None - return + if strlen(url) > 5000 + echohl ErrorMsg | echomsg 'Buffer too large, max 5000 encoded characters ('.strlen(url).')' | echohl None + return endif - let payload = "format=simple&url=".webapi#http#encodeURI(body) + let payload = "format=simple&url=".webapi#http#encodeURI(url) let res = webapi#http#post(l:rust_shortener_url.'create.php', payload, {}) - let url = res.content + if res.status[0] ==# '2' + let url = res.content + endif + + let footer = '' + if exists('g:rust_clip_command') + call system(g:rust_clip_command, url) + if !v:shell_error + let footer = ' (copied to clipboard)' + endif + endif + redraw | echomsg 'Done: '.url.footer +endfunction + +" Run a test under the cursor or all tests {{{1 + +" Finds a test function name under the cursor. Returns empty string when a +" test function is not found. +function! s:SearchTestFunctionNameUnderCursor() abort + let cursor_line = line('.') - redraw | echomsg 'Done: '.url + " Find #[test] attribute + if search('\m\C#\[test\]', 'bcW') is 0 + return '' + endif + + " Move to an opening brace of the test function + let test_func_line = search('\m\C^\s*fn\s\+\h\w*\s*(.\+{$', 'eW') + if test_func_line is 0 + return '' + endif + + " Search the end of test function (closing brace) to ensure that the + " cursor position is within function definition + if maparg('<Plug>(MatchitNormalForward)') ==# '' + keepjumps normal! % + else + " Prefer matchit.vim official plugin to native % since the plugin + " provides better behavior than original % (#391) + " To load the plugin, run: + " :packadd matchit + execute 'keepjumps' 'normal' "\<Plug>(MatchitNormalForward)" + endif + if line('.') < cursor_line + return '' + endif + + return matchstr(getline(test_func_line), '\m\C^\s*fn\s\+\zs\h\w*') +endfunction + +function! rust#Test(mods, winsize, all, options) abort + let manifest = findfile('Cargo.toml', expand('%:p:h') . ';') + if manifest ==# '' + return rust#Run(1, '--test ' . a:options) + endif + + " <count> defaults to 0, but we prefer an empty string + let winsize = a:winsize ? a:winsize : '' + + if has('terminal') + if has('patch-8.0.910') + let cmd = printf('%s noautocmd %snew | terminal ++curwin ', a:mods, winsize) + else + let cmd = printf('%s terminal ', a:mods) + endif + elseif has('nvim') + let cmd = printf('%s noautocmd %snew | terminal ', a:mods, winsize) + else + let cmd = '!' + let manifest = shellescape(manifest) + endif + + if a:all + if a:options ==# '' + execute cmd . 'cargo test --manifest-path' manifest + else + execute cmd . 'cargo test --manifest-path' manifest a:options + endif + return + endif + + let saved = getpos('.') + try + let func_name = s:SearchTestFunctionNameUnderCursor() + finally + call setpos('.', saved) + endtry + if func_name ==# '' + echohl ErrorMsg + echomsg 'No test function was found under the cursor. Please add ! to command if you want to run all tests' + echohl None + return + endif + if a:options ==# '' + execute cmd . 'cargo test --manifest-path' manifest func_name + else + execute cmd . 'cargo test --manifest-path' manifest func_name a:options + endif endfunction " }}}1 -" vim: set noet sw=8 ts=8: +" vim: set et sw=4 sts=4 ts=8: diff --git a/runtime/autoload/rust/debugging.vim b/runtime/autoload/rust/debugging.vim new file mode 100644 index 0000000000..0e84183172 --- /dev/null +++ b/runtime/autoload/rust/debugging.vim @@ -0,0 +1,105 @@ +" Last Modified: 2023-09-11 + +" For debugging, inspired by https://github.com/w0rp/rust/blob/master/autoload/rust/debugging.vim + +let s:global_variable_list = [ + \ '_rustfmt_autosave_because_of_config', + \ 'ftplugin_rust_source_path', + \ 'loaded_syntastic_rust_cargo_checker', + \ 'loaded_syntastic_rust_filetype', + \ 'loaded_syntastic_rust_rustc_checker', + \ 'rust_bang_comment_leader', + \ 'rust_cargo_avoid_whole_workspace', + \ 'rust_clip_command', + \ 'rust_conceal', + \ 'rust_conceal_mod_path', + \ 'rust_conceal_pub', + \ 'rust_fold', + \ 'rust_last_args', + \ 'rust_last_rustc_args', + \ 'rust_original_delimitMate_excluded_regions', + \ 'rust_playpen_url', + \ 'rust_prev_delimitMate_quotes', + \ 'rust_recent_nearest_cargo_tol', + \ 'rust_recent_root_cargo_toml', + \ 'rust_recommended_style', + \ 'rust_set_conceallevel', + \ 'rust_set_conceallevel=1', + \ 'rust_set_foldmethod', + \ 'rust_set_foldmethod=1', + \ 'rust_shortener_url', + \ 'rustc_makeprg_no_percent', + \ 'rustc_path', + \ 'rustfmt_autosave', + \ 'rustfmt_autosave_if_config_present', + \ 'rustfmt_command', + \ 'rustfmt_emit_files', + \ 'rustfmt_fail_silently', + \ 'rustfmt_options', + \ 'syntastic_extra_filetypes', + \ 'syntastic_rust_cargo_fname', + \] + +function! s:Echo(message) abort + execute 'echo a:message' +endfunction + +function! s:EchoGlobalVariables() abort + for l:key in s:global_variable_list + if l:key !~# '^_' + call s:Echo('let g:' . l:key . ' = ' . string(get(g:, l:key, v:null))) + endif + + if has_key(b:, l:key) + call s:Echo('let b:' . l:key . ' = ' . string(b:[l:key])) + endif + endfor +endfunction + +function! rust#debugging#Info() abort + call cargo#Load() + call rust#Load() + call rustfmt#Load() + call s:Echo('rust.vim Global Variables:') + call s:Echo('') + call s:EchoGlobalVariables() + + silent let l:output = system(g:rustfmt_command . ' --version') + echo l:output + + let l:rustc = exists("g:rustc_path") ? g:rustc_path : "rustc" + silent let l:output = system(l:rustc . ' --version') + echo l:output + + silent let l:output = system('cargo --version') + echo l:output + + version + + if exists(":SyntasticInfo") + echo "----" + echo "Info from Syntastic:" + execute "SyntasticInfo" + endif +endfunction + +function! rust#debugging#InfoToClipboard() abort + redir @" + silent call rust#debugging#Info() + redir END + + call s:Echo('RustInfo copied to your clipboard') +endfunction + +function! rust#debugging#InfoToFile(filename) abort + let l:expanded_filename = expand(a:filename) + + redir => l:output + silent call rust#debugging#Info() + redir END + + call writefile(split(l:output, "\n"), l:expanded_filename) + call s:Echo('RustInfo written to ' . l:expanded_filename) +endfunction + +" vim: set et sw=4 sts=4 ts=8: diff --git a/runtime/autoload/rustfmt.vim b/runtime/autoload/rustfmt.vim index a689b5e00d..652e6af33a 100644 --- a/runtime/autoload/rustfmt.vim +++ b/runtime/autoload/rustfmt.vim @@ -1,107 +1,261 @@ " Author: Stephen Sugden <stephen@stephensugden.com> +" Last Modified: 2023-09-11 " " Adapted from https://github.com/fatih/vim-go " For bugs, patches and license go to https://github.com/rust-lang/rust.vim if !exists("g:rustfmt_autosave") - let g:rustfmt_autosave = 0 + let g:rustfmt_autosave = 0 endif if !exists("g:rustfmt_command") - let g:rustfmt_command = "rustfmt" + let g:rustfmt_command = "rustfmt" endif if !exists("g:rustfmt_options") - let g:rustfmt_options = "" + let g:rustfmt_options = "" endif if !exists("g:rustfmt_fail_silently") - let g:rustfmt_fail_silently = 0 + let g:rustfmt_fail_silently = 0 +endif + +function! rustfmt#DetectVersion() + " Save rustfmt '--help' for feature inspection + silent let s:rustfmt_help = system(g:rustfmt_command . " --help") + let s:rustfmt_unstable_features = s:rustfmt_help =~# "--unstable-features" + + " Build a comparable rustfmt version varible out of its `--version` output: + silent let l:rustfmt_version_full = system(g:rustfmt_command . " --version") + let l:rustfmt_version_list = matchlist(l:rustfmt_version_full, + \ '\vrustfmt ([0-9]+[.][0-9]+[.][0-9]+)') + if len(l:rustfmt_version_list) < 3 + let s:rustfmt_version = "0" + else + let s:rustfmt_version = l:rustfmt_version_list[1] + endif + return s:rustfmt_version +endfunction + +call rustfmt#DetectVersion() + +if !exists("g:rustfmt_emit_files") + let g:rustfmt_emit_files = s:rustfmt_version >= "0.8.2" +endif + +if !exists("g:rustfmt_file_lines") + let g:rustfmt_file_lines = s:rustfmt_help =~# "--file-lines JSON" endif let s:got_fmt_error = 0 +function! rustfmt#Load() + " Utility call to get this script loaded, for debugging +endfunction + +function! s:RustfmtWriteMode() + if g:rustfmt_emit_files + return "--emit=files" + else + return "--write-mode=overwrite" + endif +endfunction + +function! s:RustfmtConfigOptions() + let l:rustfmt_toml = findfile('rustfmt.toml', expand('%:p:h') . ';') + if l:rustfmt_toml !=# '' + return '--config-path '.shellescape(fnamemodify(l:rustfmt_toml, ":p")) + endif + + let l:_rustfmt_toml = findfile('.rustfmt.toml', expand('%:p:h') . ';') + if l:_rustfmt_toml !=# '' + return '--config-path '.shellescape(fnamemodify(l:_rustfmt_toml, ":p")) + endif + + " Default to edition 2018 in case no rustfmt.toml was found. + return '--edition 2018' +endfunction + function! s:RustfmtCommandRange(filename, line1, line2) - let l:arg = {"file": shellescape(a:filename), "range": [a:line1, a:line2]} - return printf("%s %s --write-mode=overwrite --file-lines '[%s]'", g:rustfmt_command, g:rustfmt_options, json_encode(l:arg)) + if g:rustfmt_file_lines == 0 + echo "--file-lines is not supported in the installed `rustfmt` executable" + return + endif + + let l:arg = {"file": shellescape(a:filename), "range": [a:line1, a:line2]} + let l:write_mode = s:RustfmtWriteMode() + let l:rustfmt_config = s:RustfmtConfigOptions() + + " FIXME: When --file-lines gets to be stable, add version range checking + " accordingly. + let l:unstable_features = s:rustfmt_unstable_features ? '--unstable-features' : '' + + let l:cmd = printf("%s %s %s %s %s --file-lines '[%s]' %s", g:rustfmt_command, + \ l:write_mode, g:rustfmt_options, + \ l:unstable_features, l:rustfmt_config, + \ json_encode(l:arg), shellescape(a:filename)) + return l:cmd endfunction -function! s:RustfmtCommand(filename) - return g:rustfmt_command . " --write-mode=overwrite " . g:rustfmt_options . " " . shellescape(a:filename) +function! s:RustfmtCommand() + let write_mode = g:rustfmt_emit_files ? '--emit=stdout' : '--write-mode=display' + let config = s:RustfmtConfigOptions() + return join([g:rustfmt_command, write_mode, config, g:rustfmt_options]) endfunction -function! s:RunRustfmt(command, curw, tmpname) - if exists("*systemlist") - let out = systemlist(a:command) - else - let out = split(system(a:command), '\r\?\n') - endif - - if v:shell_error == 0 || v:shell_error == 3 - " remove undo point caused via BufWritePre - try | silent undojoin | catch | endtry - - " Replace current file with temp file, then reload buffer - call rename(a:tmpname, expand('%')) - silent edit! - let &syntax = &syntax - - " only clear location list if it was previously filled to prevent - " clobbering other additions - if s:got_fmt_error - let s:got_fmt_error = 0 - call setloclist(0, []) - lwindow - endif - elseif g:rustfmt_fail_silently == 0 - " otherwise get the errors and put them in the location list - let errors = [] - - for line in out - " src/lib.rs:13:5: 13:10 error: expected `,`, or `}`, found `value` - let tokens = matchlist(line, '^\(.\{-}\):\(\d\+\):\(\d\+\):\s*\(\d\+:\d\+\s*\)\?\s*error: \(.*\)') - if !empty(tokens) - call add(errors, {"filename": @%, - \"lnum": tokens[2], - \"col": tokens[3], - \"text": tokens[5]}) - endif - endfor - - if empty(errors) - % | " Couldn't detect rustfmt error format, output errors - endif - - if !empty(errors) - call setloclist(0, errors, 'r') - echohl Error | echomsg "rustfmt returned error" | echohl None - endif - - let s:got_fmt_error = 1 - lwindow - " We didn't use the temp file, so clean up - call delete(a:tmpname) - endif - - call winrestview(a:curw) +function! s:DeleteLines(start, end) abort + silent! execute a:start . ',' . a:end . 'delete _' endfunction -function! rustfmt#FormatRange(line1, line2) - let l:curw = winsaveview() - let l:tmpname = expand("%:p:h") . "/." . expand("%:p:t") . ".rustfmt" - call writefile(getline(1, '$'), l:tmpname) +function! s:RunRustfmt(command, tmpname, from_writepre) + let l:view = winsaveview() + + let l:stderr_tmpname = tempname() + call writefile([], l:stderr_tmpname) + + let l:command = a:command . ' 2> ' . l:stderr_tmpname - let command = s:RustfmtCommandRange(l:tmpname, a:line1, a:line2) + if a:tmpname ==# '' + " Rustfmt in stdin/stdout mode - call s:RunRustfmt(command, l:curw, l:tmpname) + " chdir to the directory of the file + let l:has_lcd = haslocaldir() + let l:prev_cd = getcwd() + execute 'lchdir! '.expand('%:h') + + let l:buffer = getline(1, '$') + if exists("*systemlist") + silent let out = systemlist(l:command, l:buffer) + else + silent let out = split(system(l:command, + \ join(l:buffer, "\n")), '\r\?\n') + endif + else + if exists("*systemlist") + silent let out = systemlist(l:command) + else + silent let out = split(system(l:command), '\r\?\n') + endif + endif + + let l:stderr = readfile(l:stderr_tmpname) + + call delete(l:stderr_tmpname) + + let l:open_lwindow = 0 + if v:shell_error == 0 + if a:from_writepre + " remove undo point caused via BufWritePre + try | silent undojoin | catch | endtry + endif + + if a:tmpname ==# '' + let l:content = l:out + else + " take the tmpfile's content, this is better than rename + " because it preserves file modes. + let l:content = readfile(a:tmpname) + endif + + call s:DeleteLines(len(l:content), line('$')) + call setline(1, l:content) + + " only clear location list if it was previously filled to prevent + " clobbering other additions + if s:got_fmt_error + let s:got_fmt_error = 0 + call setloclist(0, []) + let l:open_lwindow = 1 + endif + elseif g:rustfmt_fail_silently == 0 && !a:from_writepre + " otherwise get the errors and put them in the location list + let l:errors = [] + + let l:prev_line = "" + for l:line in l:stderr + " error: expected one of `;` or `as`, found `extern` + " --> src/main.rs:2:1 + let tokens = matchlist(l:line, '^\s\+-->\s\(.\{-}\):\(\d\+\):\(\d\+\)$') + if !empty(tokens) + call add(l:errors, {"filename": @%, + \"lnum": tokens[2], + \"col": tokens[3], + \"text": l:prev_line}) + endif + let l:prev_line = l:line + endfor + + if !empty(l:errors) + call setloclist(0, l:errors, 'r') + echohl Error | echomsg "rustfmt returned error" | echohl None + else + echo "rust.vim: was not able to parse rustfmt messages. Here is the raw output:" + echo "\n" + for l:line in l:stderr + echo l:line + endfor + endif + + let s:got_fmt_error = 1 + let l:open_lwindow = 1 + endif + + " Restore the current directory if needed + if a:tmpname ==# '' + if l:has_lcd + execute 'lchdir! '.l:prev_cd + else + execute 'chdir! '.l:prev_cd + endif + endif + + " Open lwindow after we have changed back to the previous directory + if l:open_lwindow == 1 + lwindow + endif + + call winrestview(l:view) +endfunction + +function! rustfmt#FormatRange(line1, line2) + let l:tmpname = tempname() + call writefile(getline(1, '$'), l:tmpname) + let command = s:RustfmtCommandRange(l:tmpname, a:line1, a:line2) + call s:RunRustfmt(command, l:tmpname, v:false) + call delete(l:tmpname) endfunction function! rustfmt#Format() - let l:curw = winsaveview() - let l:tmpname = expand("%:p:h") . "/." . expand("%:p:t") . ".rustfmt" - call writefile(getline(1, '$'), l:tmpname) + call s:RunRustfmt(s:RustfmtCommand(), '', v:false) +endfunction - let command = s:RustfmtCommand(l:tmpname) +function! rustfmt#Cmd() + " Mainly for debugging + return s:RustfmtCommand() +endfunction + +function! rustfmt#PreWrite() + if !filereadable(expand("%@")) + return + endif + if rust#GetConfigVar('rustfmt_autosave_if_config_present', 0) + if findfile('rustfmt.toml', '.;') !=# '' || findfile('.rustfmt.toml', '.;') !=# '' + let b:rustfmt_autosave = 1 + let b:_rustfmt_autosave_because_of_config = 1 + endif + else + if has_key(b:, '_rustfmt_autosave_because_of_config') + unlet b:_rustfmt_autosave_because_of_config + unlet b:rustfmt_autosave + endif + endif + + if !rust#GetConfigVar("rustfmt_autosave", 0) + return + endif - call s:RunRustfmt(command, l:curw, l:tmpname) + call s:RunRustfmt(s:RustfmtCommand(), '', v:true) endfunction + + +" vim: set et sw=4 sts=4 ts=8: diff --git a/runtime/autoload/tohtml.vim b/runtime/autoload/tohtml.vim index 8a1ba14364..b1693efc5d 100644 --- a/runtime/autoload/tohtml.vim +++ b/runtime/autoload/tohtml.vim @@ -1,6 +1,6 @@ " Vim autoload file for the tohtml plugin. " Maintainer: Ben Fritz <fritzophrenic@gmail.com> -" Last Change: 2023 Jan 01 +" Last Change: 2023 Sep 03 " " Additional contributors: " @@ -307,7 +307,7 @@ func! tohtml#Convert2HTML(line1, line2) "{{{ let g:html_diff_win_num = 0 for window in win_list " switch to the next buffer to convert - exe ":" . bufwinnr(window) . "wincmd w" + exe ":" .. bufwinnr(window) .. "wincmd w" " figure out whether current charset and encoding will work, if not " default to UTF-8 @@ -355,7 +355,7 @@ func! tohtml#Diff2HTML(win_list, buf_list) "{{{ if !s:settings.no_doc if s:settings.use_xhtml if s:settings.encoding != "" - let xml_line = "<?xml version=\"1.0\" encoding=\"" . s:settings.encoding . "\"?>" + let xml_line = "<?xml version=\"1.0\" encoding=\"" .. s:settings.encoding .. "\"?>" else let xml_line = "<?xml version=\"1.0\"?>" endif @@ -387,34 +387,34 @@ func! tohtml#Diff2HTML(win_list, buf_list) "{{{ " contained in XML information if s:settings.encoding != "" && !s:settings.use_xhtml if s:html5 - call add(html, '<meta charset="' . s:settings.encoding . '"' . tag_close) + call add(html, '<meta charset="' .. s:settings.encoding .. '"' .. tag_close) else - call add(html, "<meta http-equiv=\"content-type\" content=\"text/html; charset=" . s:settings.encoding . '"' . tag_close) + call add(html, "<meta http-equiv=\"content-type\" content=\"text/html; charset=" .. s:settings.encoding .. '"' .. tag_close) endif endif call add(html, '<title>diff</title>') - call add(html, '<meta name="Generator" content="Vim/'.v:version/100.'.'.v:version%100.'"'.tag_close) - call add(html, '<meta name="plugin-version" content="'.g:loaded_2html_plugin.'"'.tag_close) + call add(html, '<meta name="Generator" content="Vim/'..v:version/100..'.'..v:version%100..'"'..tag_close) + call add(html, '<meta name="plugin-version" content="'..g:loaded_2html_plugin..'"'..tag_close) call add(html, '<meta name="settings" content="'. \ join(filter(keys(s:settings),'s:settings[v:val]'),','). - \ ',prevent_copy='.s:settings.prevent_copy. - \ ',use_input_for_pc='.s:settings.use_input_for_pc. - \ '"'.tag_close) + \ ',prevent_copy='..s:settings.prevent_copy. + \ ',use_input_for_pc='..s:settings.use_input_for_pc. + \ '"'..tag_close) call add(html, '<meta name="colorscheme" content="'. \ (exists('g:colors_name') \ ? g:colors_name - \ : 'none'). '"'.tag_close) + \ : 'none').. '"'..tag_close) call add(html, '</head>') let body_line_num = len(html) - call add(html, '<body'.(s:settings.line_ids ? ' onload="JumpToLine();"' : '').'>') + call add(html, '<body'..(s:settings.line_ids ? ' onload="JumpToLine();"' : '')..'>') endif - call add(html, "<table ".(s:settings.use_css? "" : "border='1' width='100%' ")."id='vimCodeElement".s:settings.id_suffix."'>") + call add(html, "<table "..(s:settings.use_css? "" : "border='1' width='100%' ").."id='vimCodeElement"..s:settings.id_suffix.."'>") call add(html, '<tr>') for buf in a:win_list - call add(html, '<th>'.bufname(buf).'</th>') + call add(html, '<th>'..bufname(buf)..'</th>') endfor call add(html, '</tr><tr>') @@ -423,7 +423,7 @@ func! tohtml#Diff2HTML(win_list, buf_list) "{{{ for buf in a:buf_list let temp = [] - exe bufwinnr(buf) . 'wincmd w' + exe bufwinnr(buf) .. 'wincmd w' " If text is folded because of user foldmethod settings, etc. we don't want " to act on everything in a fold by mistake. @@ -526,16 +526,16 @@ func! tohtml#Diff2HTML(win_list, buf_list) "{{{ endif let i = 1 - let name = "Diff" . (s:settings.use_xhtml ? ".xhtml" : ".html") + let name = "Diff" .. (s:settings.use_xhtml ? ".xhtml" : ".html") " Find an unused file name if current file name is already in use while filereadable(name) - let name = substitute(name, '\d*\.x\?html$', '', '') . i . '.' . fnamemodify(copy(name), ":t:e") + let name = substitute(name, '\d*\.x\?html$', '', '') .. i .. '.' .. fnamemodify(copy(name), ":t:e") let i += 1 endwhile let s:ei_sav = &eventignore set eventignore+=FileType - exe "topleft new " . name + exe "topleft new " .. name let &eventignore=s:ei_sav unlet s:ei_sav @@ -601,7 +601,7 @@ func! tohtml#Diff2HTML(win_list, buf_list) "{{{ \ "", \ " /* navigate upwards in the DOM tree to open all folds containing the line */", \ " var node = lineElem;", - \ " while (node && node.id != 'vimCodeElement".s:settings.id_suffix."')", + \ " while (node && node.id != 'vimCodeElement"..s:settings.id_suffix.."')", \ " {", \ " if (node.className == 'closed-fold')", \ " {", @@ -640,7 +640,7 @@ func! tohtml#Diff2HTML(win_list, buf_list) "{{{ call append(style_start, [ \ " function toggleFold(objID)", \ " {", - \ " for (win_num = 1; win_num <= ".len(a:buf_list)."; win_num++)", + \ " for (win_num = 1; win_num <= "..len(a:buf_list).."; win_num++)", \ " {", \ " var fold;", \ ' fold = document.getElementById("win"+win_num+objID);', @@ -660,7 +660,7 @@ func! tohtml#Diff2HTML(win_list, buf_list) "{{{ if s:uses_script " insert script tag if needed call append(style_start, [ - \ "<script" . (s:html5 ? "" : " type='text/javascript'") . ">", + \ "<script" .. (s:html5 ? "" : " type='text/javascript'") .. ">", \ s:settings.use_xhtml ? '//<![CDATA[' : "<!--"]) endif @@ -671,14 +671,14 @@ func! tohtml#Diff2HTML(win_list, buf_list) "{{{ " is pretty useless for really long lines. {{{ if s:settings.use_css call append(style_start, - \ ['<style' . (s:html5 ? '' : 'type="text/css"') . '>']+ + \ ['<style' .. (s:html5 ? '' : 'type="text/css"') .. '>']+ \ style+ \ [ s:settings.use_xhtml ? '' : '<!--', \ 'table { table-layout: fixed; }', \ 'html, body, table, tbody { width: 100%; margin: 0; padding: 0; }', \ 'table, td, th { border: 1px solid; }', \ 'td { vertical-align: top; }', - \ 'th, td { width: '.printf("%.1f",100.0/len(a:win_list)).'%; }', + \ 'th, td { width: '..printf("%.1f",100.0/len(a:win_list))..'%; }', \ 'td div { overflow: auto; }', \ s:settings.use_xhtml ? '' : '-->', \ '</style>' @@ -694,7 +694,7 @@ endfunc "}}} " Gets a single user option and sets it in the passed-in Dict, or gives it the " default value if the option doesn't actually exist. func! tohtml#GetOption(settings, option, default) "{{{ - if exists('g:html_'.a:option) + if exists('g:html_'..a:option) let a:settings[a:option] = g:html_{a:option} else let a:settings[a:option] = a:default @@ -713,10 +713,11 @@ func! tohtml#GetUserSettings() "{{{ let user_settings = {} " Define the correct option if the old option name exists and we haven't - " already defined the correct one. Maybe I'll put out a warning message about - " this sometime and remove the old option entirely at some even later time, - " but for now just silently accept the old option. + " already defined the correct one. if exists('g:use_xhtml') && !exists("g:html_use_xhtml") + echohl WarningMsg + echomsg "Warning: g:use_xhtml is deprecated, use g:html_use_xhtml" + echohl None let g:html_use_xhtml = g:use_xhtml endif @@ -739,7 +740,7 @@ func! tohtml#GetUserSettings() "{{{ call tohtml#GetOption(user_settings, 'whole_filler', 0 ) call tohtml#GetOption(user_settings, 'use_xhtml', 0 ) call tohtml#GetOption(user_settings, 'line_ids', user_settings.number_lines ) - call tohtml#GetOption(user_settings, 'use_input_for_pc', 'fallback') + call tohtml#GetOption(user_settings, 'use_input_for_pc', 'none') " }}} " override those settings that need it {{{ @@ -854,16 +855,16 @@ func! tohtml#GetUserSettings() "{{{ if user_settings.use_css if exists("g:html_prevent_copy") if user_settings.dynamic_folds && !user_settings.no_foldcolumn && g:html_prevent_copy =~# 'f' - let user_settings.prevent_copy .= 'f' + let user_settings.prevent_copy ..= 'f' endif if user_settings.number_lines && g:html_prevent_copy =~# 'n' - let user_settings.prevent_copy .= 'n' + let user_settings.prevent_copy ..= 'n' endif if &diff && g:html_prevent_copy =~# 'd' - let user_settings.prevent_copy .= 'd' + let user_settings.prevent_copy ..= 'd' endif if !user_settings.ignore_folding && g:html_prevent_copy =~# 't' - let user_settings.prevent_copy .= 't' + let user_settings.prevent_copy ..= 't' endif else let user_settings.prevent_copy = "" @@ -875,10 +876,10 @@ func! tohtml#GetUserSettings() "{{{ " enforce valid values for use_input_for_pc if user_settings.use_input_for_pc !~# 'fallback\|none\|all' - let user_settings.use_input_for_pc = 'fallback' + let user_settings.use_input_for_pc = 'none' echohl WarningMsg - echomsg '2html: "' . g:html_use_input_for_pc . '" is not valid for g:html_use_input_for_pc' - echomsg '2html: defaulting to "' . user_settings.use_input_for_pc . '"' + echomsg '2html: "' .. g:html_use_input_for_pc .. '" is not valid for g:html_use_input_for_pc' + echomsg '2html: defaulting to "' .. user_settings.use_input_for_pc .. '"' echohl None sleep 3 endif diff --git a/runtime/compiler/cargo.vim b/runtime/compiler/cargo.vim index bd48666bc9..aa9b01e93c 100644 --- a/runtime/compiler/cargo.vim +++ b/runtime/compiler/cargo.vim @@ -1,35 +1,51 @@ " Vim compiler file " Compiler: Cargo Compiler " Maintainer: Damien Radtke <damienradtke@gmail.com> -" Latest Revision: 2014 Sep 24 +" Latest Revision: 2023-09-11 " For bugs, patches and license go to https://github.com/rust-lang/rust.vim if exists('current_compiler') - finish + finish endif runtime compiler/rustc.vim let current_compiler = "cargo" +" vint: -ProhibitAbbreviationOption let s:save_cpo = &cpo set cpo&vim +" vint: +ProhibitAbbreviationOption if exists(':CompilerSet') != 2 - command -nargs=* CompilerSet setlocal <args> + command -nargs=* CompilerSet setlocal <args> endif if exists('g:cargo_makeprg_params') - execute 'CompilerSet makeprg=cargo\ '.escape(g:cargo_makeprg_params, ' \|"').'\ $*' + execute 'CompilerSet makeprg=cargo\ '.escape(g:cargo_makeprg_params, ' \|"').'\ $*' else - CompilerSet makeprg=cargo\ $* + CompilerSet makeprg=cargo\ $* endif +augroup RustCargoQuickFixHooks + autocmd! + autocmd QuickFixCmdPre make call cargo#quickfix#CmdPre() + autocmd QuickFixCmdPost make call cargo#quickfix#CmdPost() +augroup END + " Ignore general cargo progress messages CompilerSet errorformat+= - \%-G%\\s%#Downloading%.%#, - \%-G%\\s%#Compiling%.%#, - \%-G%\\s%#Finished%.%#, - \%-G%\\s%#error:\ Could\ not\ compile\ %.%#, - \%-G%\\s%#To\ learn\ more\\,%.%# + \%-G%\\s%#Downloading%.%#, + \%-G%\\s%#Checking%.%#, + \%-G%\\s%#Compiling%.%#, + \%-G%\\s%#Finished%.%#, + \%-G%\\s%#error:\ Could\ not\ compile\ %.%#, + \%-G%\\s%#To\ learn\ more\\,%.%#, + \%-G%\\s%#For\ more\ information\ about\ this\ error\\,%.%#, + \%-Gnote:\ Run\ with\ \`RUST_BACKTRACE=%.%#, + \%.%#panicked\ at\ \\'%m\\'\\,\ %f:%l:%c +" vint: -ProhibitAbbreviationOption let &cpo = s:save_cpo unlet s:save_cpo +" vint: +ProhibitAbbreviationOption + +" vim: set et sw=4 sts=4 ts=8: diff --git a/runtime/compiler/rustc.vim b/runtime/compiler/rustc.vim index 5e5b9a4e0a..efcf24ed80 100644 --- a/runtime/compiler/rustc.vim +++ b/runtime/compiler/rustc.vim @@ -1,46 +1,57 @@ " Vim compiler file " Compiler: Rust Compiler " Maintainer: Chris Morgan <me@chrismorgan.info> -" Latest Revision: 2013 Jul 12 +" Latest Revision: 2023-09-11 " For bugs, patches and license go to https://github.com/rust-lang/rust.vim if exists("current_compiler") - finish + finish endif let current_compiler = "rustc" -let s:cpo_save = &cpo +" vint: -ProhibitAbbreviationOption +let s:save_cpo = &cpo set cpo&vim +" vint: +ProhibitAbbreviationOption if exists(":CompilerSet") != 2 - command -nargs=* CompilerSet setlocal <args> + command -nargs=* CompilerSet setlocal <args> endif -if exists("g:rustc_makeprg_no_percent") && g:rustc_makeprg_no_percent != 0 - CompilerSet makeprg=rustc +if get(g:, 'rustc_makeprg_no_percent', 0) + CompilerSet makeprg=rustc else - CompilerSet makeprg=rustc\ \%:S + if has('patch-7.4.191') + CompilerSet makeprg=rustc\ \%:S + else + CompilerSet makeprg=rustc\ \"%\" + endif endif -" Old errorformat (before nightly 2016/08/10) +" New errorformat (after nightly 2016/08/10) CompilerSet errorformat= - \%f:%l:%c:\ %t%*[^:]:\ %m, - \%f:%l:%c:\ %*\\d:%*\\d\ %t%*[^:]:\ %m, - \%-G%f:%l\ %s, - \%-G%*[\ ]^, - \%-G%*[\ ]^%*[~], - \%-G%*[\ ]... + \%-G, + \%-Gerror:\ aborting\ %.%#, + \%-Gerror:\ Could\ not\ compile\ %.%#, + \%Eerror:\ %m, + \%Eerror[E%n]:\ %m, + \%Wwarning:\ %m, + \%Inote:\ %m, + \%C\ %#-->\ %f:%l:%c, + \%E\ \ left:%m,%C\ right:%m\ %f:%l:%c,%Z -" New errorformat (after nightly 2016/08/10) +" Old errorformat (before nightly 2016/08/10) CompilerSet errorformat+= - \%-G, - \%-Gerror:\ aborting\ %.%#, - \%-Gerror:\ Could\ not\ compile\ %.%#, - \%Eerror:\ %m, - \%Eerror[E%n]:\ %m, - \%Wwarning:\ %m, - \%Inote:\ %m, - \%C\ %#-->\ %f:%l:%c - -let &cpo = s:cpo_save -unlet s:cpo_save + \%f:%l:%c:\ %t%*[^:]:\ %m, + \%f:%l:%c:\ %*\\d:%*\\d\ %t%*[^:]:\ %m, + \%-G%f:%l\ %s, + \%-G%*[\ ]^, + \%-G%*[\ ]^%*[~], + \%-G%*[\ ]... + +" vint: -ProhibitAbbreviationOption +let &cpo = s:save_cpo +unlet s:save_cpo +" vint: +ProhibitAbbreviationOption + +" vim: set et sw=4 sts=4 ts=8: diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index e68aafe051..ffd90ec3d7 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -966,6 +966,8 @@ nvim_get_hl({ns_id}, {*opts}) *nvim_get_hl()* • id: (integer) Get a highlight definition by id. • link: (boolean, default true) Show linked group name instead of effective definition |:hi-link|. + • create: (boolean, default true) When highlight group + doesn't exist create it. Return: ~ Highlight groups as a map from group name to a highlight definition @@ -1789,9 +1791,9 @@ nvim_create_user_command({name}, {command}, {*opts}) For Lua usage see |lua-guide-commands-create|. Example: >vim - :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {'bang': v:true}) - :SayHello - Hello world! + :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {'bang': v:true}) + :SayHello + Hello world! < Parameters: ~ @@ -2039,10 +2041,14 @@ whether a buffer is loaded. nvim_buf_attach({buffer}, {send_buffer}, {opts}) *nvim_buf_attach()* Activates buffer-update events on a channel, or as Lua callbacks. - Example (Lua): capture buffer updates in a global `events` variable (use "vim.print(events)" to see its contents): >lua - events = {} - vim.api.nvim_buf_attach(0, false, { - on_lines=function(...) table.insert(events, {...}) end}) + Example (Lua): capture buffer updates in a global `events` variable (use + "vim.print(events)" to see its contents): >lua + events = {} + vim.api.nvim_buf_attach(0, false, { + on_lines = function(...) + table.insert(events, {...}) + end, + }) < Parameters: ~ @@ -2436,6 +2442,8 @@ nvim_buf_set_text({buffer}, {start_row}, {start_col}, {end_row}, {end_col}, Prefer |nvim_buf_set_lines()| if you are only adding or deleting entire lines. + Prefer |nvim_put()| if you want to insert text at the cursor position. + Attributes: ~ not allowed when |textlock| is active @@ -2449,6 +2457,7 @@ nvim_buf_set_text({buffer}, {start_row}, {start_col}, {end_row}, {end_col}, See also: ~ • |nvim_buf_set_lines()| + • |nvim_put()| nvim_buf_set_var({buffer}, {name}, {value}) *nvim_buf_set_var()* Sets a buffer-scoped (b:) variable @@ -2541,33 +2550,37 @@ nvim_buf_get_extmark_by_id({buffer}, {ns_id}, {id}, {opts}) 0-indexed (row, col) tuple or empty list () if extmark id was absent *nvim_buf_get_extmarks()* -nvim_buf_get_extmarks({buffer}, {ns_id}, {start}, {end}, {opts}) +nvim_buf_get_extmarks({buffer}, {ns_id}, {start}, {end}, {*opts}) Gets |extmarks| in "traversal order" from a |charwise| region defined by buffer positions (inclusive, 0-indexed |api-indexing|). Region can be given as (row,col) tuples, or valid extmark ids (whose positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1) respectively, thus the following are equivalent: >lua - vim.api.nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) - vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {}) + vim.api.nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) + vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {}) < If `end` is less than `start`, traversal works backwards. (Useful with `limit`, to get the first marks prior to a given position.) + Note: when using extmark ranges (marks with a end_row/end_col position) + the `overlap` option might be useful. Otherwise only the start position of + an extmark will be considered. + Example: >lua - local api = vim.api - local pos = api.nvim_win_get_cursor(0) - local ns = api.nvim_create_namespace('my-plugin') - -- Create new extmark at line 1, column 1. - local m1 = api.nvim_buf_set_extmark(0, ns, 0, 0, {}) - -- Create new extmark at line 3, column 1. - local m2 = api.nvim_buf_set_extmark(0, ns, 2, 0, {}) - -- Get extmarks only from line 3. - local ms = api.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {}) - -- Get all marks in this buffer + namespace. - local all = api.nvim_buf_get_extmarks(0, ns, 0, -1, {}) - vim.print(ms) + local api = vim.api + local pos = api.nvim_win_get_cursor(0) + local ns = api.nvim_create_namespace('my-plugin') + -- Create new extmark at line 1, column 1. + local m1 = api.nvim_buf_set_extmark(0, ns, 0, 0, {}) + -- Create new extmark at line 3, column 1. + local m2 = api.nvim_buf_set_extmark(0, ns, 2, 0, {}) + -- Get extmarks only from line 3. + local ms = api.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {}) + -- Get all marks in this buffer + namespace. + local all = api.nvim_buf_get_extmarks(0, ns, 0, -1, {}) + vim.print(ms) < Parameters: ~ @@ -2584,6 +2597,8 @@ nvim_buf_get_extmarks({buffer}, {ns_id}, {start}, {end}, {opts}) • details: Whether to include the details dict • hl_name: Whether to include highlight group name instead of id, true if omitted + • overlap: Also include marks which overlap the range, even + if their start position is less than `start` • type: Filter marks by type: "highlight", "sign", "virt_text" and "virt_lines" @@ -2603,6 +2618,11 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {*opts}) Using the optional arguments, it is possible to use this to highlight a range of text, and also to associate virtual text to the mark. + If present, the position defined by `end_col` and `end_row` should be + after the start position in order for the extmark to cover a range. An + earlier end position is not an error, but then it behaves like an empty + range (no highlighting). + Parameters: ~ • {buffer} Buffer handle, or 0 for current buffer • {ns_id} Namespace id from |nvim_create_namespace()| @@ -2773,7 +2793,9 @@ nvim_set_decoration_provider({ns_id}, {*opts}) • on_buf: called for each buffer being redrawn (before window callbacks) ["buf", bufnr, tick] • on_win: called when starting to redraw a specific window. - ["win", winid, bufnr, topline, botline_guess] + botline_guess is an approximation that does not exceed the + last line number. ["win", winid, bufnr, topline, + botline_guess] • on_line: called for each buffer line being redrawn. (The interaction with fold lines is subject to change) ["win", winid, bufnr, row] @@ -3044,6 +3066,7 @@ nvim_open_win({buffer}, {enter}, {*config}) *nvim_open_win()* Example (Lua): buffer-relative float (travels as buffer is scrolled) >lua vim.api.nvim_open_win(0, false, {relative='win', width=12, height=3, bufpos={100,10}}) + }) < Attributes: ~ @@ -3322,9 +3345,9 @@ nvim_create_autocmd({event}, {*opts}) *nvim_create_autocmd()* }) < - Note: `pattern` is NOT automatically expanded (unlike with |:autocmd|), thus names like - "$HOME" and "~" must be expanded explicitly: >lua - pattern = vim.fn.expand("~") .. "/some/path/*.py" + Note: `pattern` is NOT automatically expanded (unlike with |:autocmd|), + thus names like "$HOME" and "~" must be expanded explicitly: >lua + pattern = vim.fn.expand("~") .. "/some/path/*.py" < Parameters: ~ @@ -3429,17 +3452,17 @@ nvim_get_autocmds({*opts}) *nvim_get_autocmds()* Get all autocommands that match the corresponding {opts}. These examples will get autocommands matching ALL the given criteria: >lua - -- Matches all criteria - autocommands = vim.api.nvim_get_autocmds({ - group = "MyGroup", - event = {"BufEnter", "BufWinEnter"}, - pattern = {"*.c", "*.h"} - }) - - -- All commands from one group - autocommands = vim.api.nvim_get_autocmds({ - group = "MyGroup", - }) + -- Matches all criteria + autocommands = vim.api.nvim_get_autocmds({ + group = "MyGroup", + event = {"BufEnter", "BufWinEnter"}, + pattern = {"*.c", "*.h"} + }) + + -- All commands from one group + autocommands = vim.api.nvim_get_autocmds({ + group = "MyGroup", + }) < NOTE: When multiple patterns or events are provided, it will find all the diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 400cd27302..05db977809 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -5211,7 +5211,7 @@ printf({fmt}, {expr1} ...) *printf()* *printf-$* In certain languages, error and informative messages are more readable when the order of words is different from the - corresponding message in English. To accomodate translations + corresponding message in English. To accommodate translations having a different word order, positional arguments may be used to indicate this. For instance: >vim diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt index 81dd1e64bf..2e9312cf74 100644 --- a/runtime/doc/deprecated.txt +++ b/runtime/doc/deprecated.txt @@ -151,6 +151,8 @@ TREESITTER FUNCTIONS and |TSNode:type()| instead. - *vim.treesitter.query.get_query()* Use |vim.treesitter.query.get()| instead. +- *LanguageTree:for_each_child()* Use |LanguageTree:children()| + (non-recursive) instead. LUA - vim.register_keystroke_callback() Use |vim.on_key()| instead. diff --git a/runtime/doc/develop.txt b/runtime/doc/develop.txt index b3f7d1fadc..71c16659eb 100644 --- a/runtime/doc/develop.txt +++ b/runtime/doc/develop.txt @@ -192,7 +192,7 @@ Docstring format: `@note`, `@param`, `@returns` - Limited markdown is supported. - List-items start with `-` (useful to nest or "indent") -- Use `<pre>` for code samples. +- Use ``` for code samples. Code samples can be annotated as `vim` or `lua` Example: the help for |nvim_open_win()| is generated from a docstring defined @@ -202,10 +202,16 @@ in src/nvim/api/win_config.c like this: > /// ... /// /// Example (Lua): window-relative float - /// <pre>lua - /// vim.api.nvim_open_win(0, false, - /// {relative='win', row=3, col=3, width=12, height=3}) - /// </pre> + /// + /// ```lua + /// vim.api.nvim_open_win(0, false, { + /// relative='win', + /// row=3, + /// col=3, + /// width=12, + /// height=3, + /// }) + /// ``` /// /// @param buffer Buffer to display /// @param enter Enter the window @@ -232,7 +238,7 @@ Docstring format: `---@see`, `---@param`, `---@returns` - Limited markdown is supported. - List-items start with `-` (useful to nest or "indent") -- Use `<pre>` for code samples. +- Use ``` for code samples. Code samples can be annotated as `vim` or `lua` - Use `@nodoc` to prevent documentation generation. - Files which has `@meta` are only used for typing and documentation. @@ -244,12 +250,13 @@ vim.paste in runtime/lua/vim/_editor.lua like this: > --- (such as the |TUI|) pastes text into the editor. --- --- Example: To remove ANSI color codes when pasting: - --- <pre>lua + --- + --- ```lua --- vim.paste = (function() --- local overridden = vim.paste --- ... --- end)() - --- </pre> + --- ``` --- ---@see |paste| --- diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt index 866c32722a..e9ca9ee347 100644 --- a/runtime/doc/diagnostic.txt +++ b/runtime/doc/diagnostic.txt @@ -359,13 +359,11 @@ config({opts}, {namespace}) *vim.diagnostic.config()* followed by namespace configuration, and finally global configuration. For example, if a user enables virtual text globally with >lua - - vim.diagnostic.config({ virtual_text = true }) + vim.diagnostic.config({ virtual_text = true }) < and a diagnostic producer sets diagnostics with >lua - - vim.diagnostic.set(ns, 0, diagnostics, { virtual_text = false }) + vim.diagnostic.set(ns, 0, diagnostics, { virtual_text = false }) < then virtual text will not be enabled for those diagnostics. @@ -608,16 +606,14 @@ match({str}, {pat}, {groups}, {severity_map}, {defaults}) Parse a diagnostic from a string. For example, consider a line of output from a linter: > - - WARNING filename:27:3: Variable 'foo' does not exist + WARNING filename:27:3: Variable 'foo' does not exist < This can be parsed into a diagnostic |diagnostic-structure| with: >lua - - local s = "WARNING filename:27:3: Variable 'foo' does not exist" - local pattern = "^(%w+) %w+:(%d+):(%d+): (.+)$" - local groups = { "severity", "lnum", "col", "message" } - vim.diagnostic.match(s, pattern, groups, { WARNING = vim.diagnostic.WARN }) + local s = "WARNING filename:27:3: Variable 'foo' does not exist" + local pattern = "^(%w+) %w+:(%d+):(%d+): (.+)$" + local groups = { "severity", "lnum", "col", "message" } + vim.diagnostic.match(s, pattern, groups, { WARNING = vim.diagnostic.WARN }) < Parameters: ~ diff --git a/runtime/doc/filetype.txt b/runtime/doc/filetype.txt index bae3006c36..27ec92dbc6 100644 --- a/runtime/doc/filetype.txt +++ b/runtime/doc/filetype.txt @@ -136,35 +136,38 @@ what kind of file it is. This doesn't always work. A number of global variables can be used to overrule the filetype used for certain extensions: file name variable ~ - `*.asa` g:filetype_asa |ft-aspvbs-syntax| |ft-aspperl-syntax| - `*.asm` g:asmsyntax |ft-asm-syntax| - `*.asp` g:filetype_asp |ft-aspvbs-syntax| |ft-aspperl-syntax| - `*.bas` g:filetype_bas |ft-basic-syntax| + `*.asa` g:filetype_asa |ft-aspperl-syntax| + |ft-aspvbs-syntax| + `*.asm` g:asmsyntax |ft-asm-syntax| + `*.asp` g:filetype_asp |ft-aspperl-syntax| + |ft-aspvbs-syntax| + `*.bas` g:filetype_bas |ft-basic-syntax| `*.cfg` g:filetype_cfg `*.cls` g:filetype_cls - `*.csh` g:filetype_csh |ft-csh-syntax| + `*.csh` g:filetype_csh |ft-csh-syntax| `*.dat` g:filetype_dat - `*.f` g:filetype_f |ft-forth-syntax| - `*.frm` g:filetype_frm |ft-form-syntax| - `*.fs` g:filetype_fs |ft-forth-syntax| - `*.i` g:filetype_i |ft-progress-syntax| + `*.f` g:filetype_f |ft-forth-syntax| + `*.frm` g:filetype_frm |ft-form-syntax| + `*.fs` g:filetype_fs |ft-forth-syntax| + `*.h` g:c_syntax_for_h |ft-c-syntax| + `*.i` g:filetype_i |ft-progress-syntax| `*.inc` g:filetype_inc `*.lsl` g:filetype_lsl - `*.m` g:filetype_m |ft-mathematica-syntax| + `*.m` g:filetype_m |ft-mathematica-syntax| `*.mod` g:filetype_mod - `*.p` g:filetype_p |ft-pascal-syntax| + `*.p` g:filetype_p |ft-pascal-syntax| `*.pl` g:filetype_pl - `*.pp` g:filetype_pp |ft-pascal-syntax| + `*.pp` g:filetype_pp |ft-pascal-syntax| `*.prg` g:filetype_prg `*.r` g:filetype_r `*.sig` g:filetype_sig - `*.sql` g:filetype_sql |ft-sql-syntax| + `*.sql` g:filetype_sql |ft-sql-syntax| `*.src` g:filetype_src `*.sys` g:filetype_sys - `*.sh` g:bash_is_sh |ft-sh-syntax| - `*.tex` g:tex_flavor |ft-tex-plugin| + `*.sh` g:bash_is_sh |ft-sh-syntax| + `*.tex` g:tex_flavor |ft-tex-plugin| `*.typ` g:filetype_typ - `*.w` g:filetype_w |ft-cweb-syntax| + `*.w` g:filetype_w |ft-cweb-syntax| For a few filetypes the global variable is used only when the filetype could not be detected: diff --git a/runtime/doc/ft_rust.txt b/runtime/doc/ft_rust.txt index a5d5b558dc..b912f732b6 100644 --- a/runtime/doc/ft_rust.txt +++ b/runtime/doc/ft_rust.txt @@ -1,72 +1,74 @@ -*ft_rust.txt* Nvim - -This is documentation for the Rust filetype plugin. +*ft_rust.txt* Filetype plugin for Rust ============================================================================== -CONTENTS *rust* +CONTENTS *rust* -1. Introduction |rust-intro| -2. Settings |rust-settings| -3. Commands |rust-commands| -4. Mappings |rust-mappings| +1. Introduction |rust-intro| +2. Settings |rust-settings| +3. Commands |rust-commands| +4. Mappings |rust-mappings| ============================================================================== -INTRODUCTION *rust-intro* +INTRODUCTION *rust-intro* This plugin provides syntax and supporting functionality for the Rust -filetype. +filetype. It requires Vim 8 or higher for full functionality. Some commands +will not work on earlier versions. ============================================================================== -SETTINGS *rust-settings* +SETTINGS *rust-settings* This plugin has a few variables you can define in your vimrc that change the behavior of the plugin. - *g:rustc_path* -g:rustc_path~ +Some variables can be set buffer local (`:b` prefix), and the buffer local +will take precedence over the global `g:` counterpart. + + *g:rustc_path* +g:rustc_path ~ Set this option to the path to rustc for use in the |:RustRun| and - |:RustExpand| commands. If unset, "rustc" will be located in $PATH: > - let g:rustc_path = $HOME .. "/bin/rustc" + |:RustExpand| commands. If unset, `rustc` will be located in $PATH: >vim + let g:rustc_path = $HOME."/bin/rustc" < - *g:rustc_makeprg_no_percent* -g:rustc_makeprg_no_percent~ - Set this option to 1 to have 'makeprg' default to "rustc" instead of - "rustc %": > + *g:rustc_makeprg_no_percent* +g:rustc_makeprg_no_percent ~ + Set this option to 1 to have 'makeprg' default to `rustc` instead of + `rustc %`: >vim let g:rustc_makeprg_no_percent = 1 < - *g:rust_conceal* -g:rust_conceal~ - Set this option to turn on the basic |conceal| support: > + *g:rust_conceal* +g:rust_conceal ~ + Set this option to turn on the basic |conceal| support: >vim let g:rust_conceal = 1 < - *g:rust_conceal_mod_path* -g:rust_conceal_mod_path~ + *g:rust_conceal_mod_path* +g:rust_conceal_mod_path ~ Set this option to turn on |conceal| for the path connecting token - "::": > + "::": >vim let g:rust_conceal_mod_path = 1 < - *g:rust_conceal_pub* -g:rust_conceal_pub~ - Set this option to turn on |conceal| for the "pub" token: > + *g:rust_conceal_pub* +g:rust_conceal_pub ~ + Set this option to turn on |conceal| for the "pub" token: >vim let g:rust_conceal_pub = 1 < - *g:rust_recommended_style* -g:rust_recommended_style~ - Set this option to enable vim indentation and textwidth settings to - conform to style conventions of the rust standard library (i.e. use 4 - spaces for indents and sets 'textwidth' to 99). This option is enabled - by default. To disable it: > + *g:rust_recommended_style* +g:rust_recommended_style ~ + Set this option to enable vim indentation and textwidth settings to + conform to style conventions of the rust standard library (i.e. use 4 + spaces for indents and sets 'textwidth' to 99). This option is enabled + by default. To disable it: >vim let g:rust_recommended_style = 0 < - *g:rust_fold* -g:rust_fold~ - Set this option to turn on |folding|: > + *g:rust_fold* +g:rust_fold ~ + Set this option to turn on |folding|: >vim let g:rust_fold = 1 < Value Effect ~ @@ -77,62 +79,297 @@ g:rust_fold~ global value (all folds are closed by default). *g:rust_bang_comment_leader* -g:rust_bang_comment_leader~ +g:rust_bang_comment_leader ~ Set this option to 1 to preserve the leader on multi-line doc comments - using the /*! syntax: > + using the `/*!` syntax: >vim let g:rust_bang_comment_leader = 1 < - *g:ftplugin_rust_source_path* -g:ftplugin_rust_source_path~ + *g:rust_use_custom_ctags_defs* +g:rust_use_custom_ctags_defs ~ + Set this option to 1 if you have customized ctags definitions for Rust + and do not wish for those included with rust.vim to be used: >vim + let g:rust_use_custom_ctags_defs = 1 +< + + NOTE: rust.vim's built-in definitions are only used for the Tagbar Vim + plugin, if you have it installed, AND if Universal Ctags is not + detected. This is because Universal Ctags already has built-in + support for Rust when used with Tagbar. + + Also, note that when using ctags other than Universal Ctags, it is not + automatically used when generating |tags| files that Vim can use to + navigate to definitions across different source files. Feel free to + copy `rust.vim/ctags/rust.ctags` into your own `~/.ctags` if you wish + to generate |tags| files. + + *g:ftplugin_rust_source_path* +g:ftplugin_rust_source_path ~ Set this option to a path that should be prepended to 'path' for Rust - source files: > - let g:ftplugin_rust_source_path = $HOME .. '/dev/rust' + source files: >vim + let g:ftplugin_rust_source_path = $HOME . '/dev/rust' < *g:rustfmt_command* -g:rustfmt_command~ +g:rustfmt_command ~ Set this option to the name of the "rustfmt" executable in your $PATH. If - not specified it defaults to "rustfmt" : > + not specified it defaults to "rustfmt" : >vim let g:rustfmt_command = 'rustfmt' < *g:rustfmt_autosave* -g:rustfmt_autosave~ +g:rustfmt_autosave ~ Set this option to 1 to run |:RustFmt| automatically when saving a - buffer. If not specified it defaults to 0 : > + buffer. If not specified it defaults to 0 : >vim let g:rustfmt_autosave = 0 < + There is also a buffer-local b:rustfmt_autosave that can be set for + the same purpose, and can override the global setting. + + *g:rustfmt_autosave_if_config_present* +g:rustfmt_autosave_if_config_present ~ + Set this option to 1 to have *b:rustfmt_autosave* be set automatically + if a `rustfmt.toml` file is present in any parent directly leading to + the file being edited. If not set, default to 0: >vim + let g:rustfmt_autosave_if_config_present = 0 +< + This is useful to have `rustfmt` only execute on save, on projects + that have `rustfmt.toml` configuration. + + There is also a buffer-local b:rustfmt_autosave_if_config_present + that can be set for the same purpose, which can overrides the global + setting. + *g:rustfmt_fail_silently* -g:rustfmt_fail_silently~ +g:rustfmt_fail_silently ~ Set this option to 1 to prevent "rustfmt" from populating the - |location-list| with errors. If not specified it defaults to 0: > + |location-list| with errors. If not specified it defaults to 0: >vim let g:rustfmt_fail_silently = 0 < *g:rustfmt_options* -g:rustfmt_options~ +g:rustfmt_options ~ Set this option to a string of options to pass to "rustfmt". The write-mode is already set to "overwrite". If not specified it - defaults to '' : > + defaults to '' : >vim let g:rustfmt_options = '' < + *g:rustfmt_emit_files* +g:rustfmt_emit_files ~ + If not specified rust.vim tries to detect the right parameter to + pass to rustfmt based on its reported version. Otherwise, it + determines whether to run rustfmt with '--emit=files' (when 1 is + provided) instead of '--write-mode=overwrite'. >vim + let g:rustfmt_emit_files = 0 +< *g:rust_playpen_url* -g:rust_playpen_url~ - Set this option to override the URL for the playpen to use: > +g:rust_playpen_url ~ + Set this option to override the url for the playpen to use: >vim let g:rust_playpen_url = 'https://play.rust-lang.org/' < *g:rust_shortener_url* -g:rust_shortener_url~ - Set this option to override the URL for the URL shortener: > +g:rust_shortener_url ~ + Set this option to override the url for the url shortener: >vim let g:rust_shortener_url = 'https://is.gd/' < + *g:rust_clip_command* +g:rust_clip_command ~ + Set this option to the command used in your OS to copy the Rust Play + url to the clipboard: >vim + let g:rust_clip_command = 'xclip -selection clipboard' +< + *g:cargo_makeprg_params* +g:cargo_makeprg_params ~ + Set this option to the string of parameters to pass to cargo. If not + specified it defaults to `$*` : >vim + let g:cargo_makeprg_params = 'build' +< + + *g:cargo_shell_command_runner* +g:cargo_shell_command_runner ~ + Set this option to change how to run shell commands for cargo commands + |:Cargo|, |:Cbuild|, |:Crun|, ... + By default, |:terminal| is used to run shell command in terminal window + asynchronously. But if you prefer |:!| for running the commands, it can + be specified: >vim + let g:cargo_shell_command_runner = '!' +< + +------------------------------------------------------------------------------ +Integration with Syntastic *rust-syntastic* + +This plugin automatically integrates with the Syntastic checker. There are two +checkers provided: `rustc`, and `cargo`. The latter invokes `cargo` in order to +build code, and the former delivers a single edited '.rs' file as a compilation +target directly to the Rust compiler, `rustc`. + +Because Cargo is almost exclusively being used for building Rust code these +days, `cargo` is the default checker. >vim + + let g:syntastic_rust_checkers = ['cargo'] +< +If you would like to change it, you can set `g:syntastic_rust_checkers` to a +different value. + *g:rust_cargo_avoid_whole_workspace* + *b:rust_cargo_avoid_whole_workspace* +g:rust_cargo_avoid_whole_workspace ~ + When editing a crate that is part of a Cargo workspace, and this + option is set to 1 (the default), then `cargo` will be executed + directly in that crate directory instead of in the workspace + directory. Setting 0 prevents this behavior - however be aware that if + you are working in large workspace, Cargo commands may take more time, + plus the Syntastic error list may include all the crates in the + workspace. >vim + let g:rust_cargo_avoid_whole_workspace = 0 +< + *g:rust_cargo_check_all_targets* + *b:rust_cargo_check_all_targets* +g:rust_cargo_check_all_targets ~ + When set to 1, the `--all-targets` option will be passed to cargo when + Syntastic executes it, allowing the linting of all targets under the + package. + The default is 0. + + *g:rust_cargo_check_all_features* + *b:rust_cargo_check_all_features* +g:rust_cargo_check_all_features ~ + When set to 1, the `--all-features` option will be passed to cargo when + Syntastic executes it, allowing the linting of all features of the + package. + The default is 0. + + *g:rust_cargo_check_examples* + *b:rust_cargo_check_examples* +g:rust_cargo_check_examples ~ + When set to 1, the `--examples` option will be passed to cargo when + Syntastic executes it, to prevent the exclusion of examples from + linting. The examples are normally under the `examples/` directory of + the crate. + The default is 0. + + *g:rust_cargo_check_tests* + *b:rust_cargo_check_tests* +g:rust_cargo_check_tests ~ + When set to 1, the `--tests` option will be passed to cargo when + Syntastic executes it, to prevent the exclusion of tests from linting. + The tests are normally under the `tests/` directory of the crate. + The default is 0. + + *g:rust_cargo_check_benches* + *b:rust_cargo_check_benches* +g:rust_cargo_check_benches ~ + When set to 1, the `--benches` option will be passed to cargo when + Syntastic executes it. The benches are normally under the `benches/` + directory of the crate. + The default is 0. + +------------------------------------------------------------------------------ +Integration with auto-pairs *rust-auto-pairs* + +This plugin automatically configures the auto-pairs plugin not to duplicate +single quotes, which are used more often for lifetime annotations than for +single character literals. + + *g:rust_keep_autopairs_default* +g:rust_keep_autopairs_default ~ + + Don't override auto-pairs default for the Rust filetype. The default + is 0. + ============================================================================== -COMMANDS *rust-commands* +COMMANDS *rust-commands* + +Invoking Cargo ~ + +This plug defines very simple shortcuts for invoking Cargo from with Vim. + +:Cargo <args> *:Cargo* + Runs `cargo` with the provided arguments. + +:Cbuild <args> *:Cbuild* + Shortcut for `cargo build` . + +:Cclean <args> *:Cclean* + Shortcut for `cargo clean` . + +:Cdoc <args> *:Cdoc* + Shortcut for `cargo doc` . + +:Cinit <args> *:Cinit* + Shortcut for `cargo init` . + +:Crun <args> *:Crun* + Shortcut for `cargo run` . + +:Ctest <args> *:Ctest* + Shortcut for `cargo test` . + +:Cupdate <args> *:Cupdate* + Shortcut for `cargo update` . + +:Cbench <args> *:Cbench* + Shortcut for `cargo bench` . + +:Csearch <args> *:Csearch* + Shortcut for `cargo search` . + +:Cpublish <args> *:Cpublish* + Shortcut for `cargo publish` . + +:Cinstall <args> *:Cinstall* + Shortcut for `cargo install` . + +:Cruntarget <args> *:Cruntarget* + Shortcut for `cargo run --bin` or `cargo run --example`, + depending on the currently open buffer. + +Formatting ~ + +:RustFmt *:RustFmt* + Runs |g:rustfmt_command| on the current buffer. If + |g:rustfmt_options| is set then those will be passed to the + executable. + + If |g:rustfmt_fail_silently| is 0 (the default) then it + will populate the |location-list| with the errors from + |g:rustfmt_command|. If |g:rustfmt_fail_silently| is set to 1 + then it will not populate the |location-list|. -:RustRun [args] *:RustRun* +:RustFmtRange *:RustFmtRange* + Runs |g:rustfmt_command| with selected range. See + |:RustFmt| for any other information. + + +Playpen integration ~ + +:RustPlay *:RustPlay* + This command will only work if you have web-api.vim installed + (available at https://github.com/mattn/webapi-vim). It sends the + current selection, or if nothing is selected, the entirety of the + current buffer to the Rust playpen, and emits a message with the + shortened URL to the playpen. + + |g:rust_playpen_url| is the base URL to the playpen, by default + "https://play.rust-lang.org/". + + |g:rust_shortener_url| is the base url for the shorterner, by + default "https://is.gd/" + + |g:rust_clip_command| is the command to run to copy the + playpen url to the clipboard of your system. + + +Evaluation of a single Rust file ~ + +NOTE: These commands are useful only when working with standalone Rust files, +which is usually not the case for common Rust development. If you wish to +building Rust crates from with Vim can should use Vim's make, Syntastic, or +functionality from other plugins. + + +:RustRun [args] *:RustRun* :RustRun! [rustc-args] [--] [args] Compiles and runs the current file. If it has unsaved changes, it will be saved first using |:update|. If the current file is @@ -150,26 +387,26 @@ COMMANDS *rust-commands* If |g:rustc_path| is defined, it is used as the path to rustc. Otherwise it is assumed rustc can be found in $PATH. -:RustExpand [args] *:RustExpand* +:RustExpand [args] *:RustExpand* :RustExpand! [TYPE] [args] - Expands the current file using --pretty and displays the + Expands the current file using `--pretty` and displays the results in a new split. If the current file has unsaved changes, it will be saved first using |:update|. If the current file is an unnamed buffer, it will be written to a temporary file first. The arguments given to |:RustExpand| will be passed to rustc. - This is largely intended for specifying various --cfg + This is largely intended for specifying various `--cfg` configurations. If ! is specified, the first argument is the expansion type to - pass to rustc --pretty. Otherwise it will default to + pass to `rustc --pretty` . Otherwise it will default to "expanded". If |g:rustc_path| is defined, it is used as the path to rustc. Otherwise it is assumed rustc can be found in $PATH. -:RustEmitIr [args] *:RustEmitIr* +:RustEmitIr [args] *:RustEmitIr* Compiles the current file to LLVM IR and displays the results in a new split. If the current file has unsaved changes, it will be saved first using |:update|. If the current file is an @@ -180,7 +417,7 @@ COMMANDS *rust-commands* If |g:rustc_path| is defined, it is used as the path to rustc. Otherwise it is assumed rustc can be found in $PATH. -:RustEmitAsm [args] *:RustEmitAsm* +:RustEmitAsm [args] *:RustEmitAsm* Compiles the current file to assembly and displays the results in a new split. If the current file has unsaved changes, it will be saved first using |:update|. If the current file is an @@ -191,49 +428,52 @@ COMMANDS *rust-commands* If |g:rustc_path| is defined, it is used as the path to rustc. Otherwise it is assumed rustc can be found in $PATH. -:RustPlay *:RustPlay* - This command will only work if you have web-api.vim installed - (available at https://github.com/mattn/webapi-vim). It sends the - current selection, or if nothing is selected, the entirety of the - current buffer to the Rust playpen, and emits a message with the - shortened URL to the playpen. - |g:rust_playpen_url| is the base URL to the playpen, by default - "https://play.rust-lang.org/". +Running test(s) ~ - |g:rust_shortener_url| is the base URL for the shortener, by - default "https://is.gd/" +:[N]RustTest[!] [options] *:RustTest* + Runs a test under the cursor when the current buffer is in a + cargo project with "cargo test" command. If the command did + not find any test function under the cursor, it stops with an + error message. -:RustFmt *:RustFmt* - Runs |g:rustfmt_command| on the current buffer. If - |g:rustfmt_options| is set then those will be passed to the - executable. + When N is given, adjust the size of the new window to N lines + or columns. - If |g:rustfmt_fail_silently| is 0 (the default) then it - will populate the |location-list| with the errors from - |g:rustfmt_command|. If |g:rustfmt_fail_silently| is set to 1 - then it will not populate the |location-list|. + When ! is given, runs all tests regardless of current cursor + position. -:RustFmtRange *:RustFmtRange* - Runs |g:rustfmt_command| with selected range. See - |:RustFmt| for any other information. + When [options] is given, it is passed to "cargo" command + arguments. -============================================================================== -MAPPINGS *rust-mappings* + When the current buffer is outside cargo project, the command + runs `rustc --test` command instead of "cargo test" as + fallback. All tests are run regardless of adding ! since there + is no way to run specific test function with rustc. [options] + is passed to `rustc` command arguments in the case. -This plugin defines mappings for |[[| and |]]| to support hanging indents. + Takes optional modifiers (see |<mods>|): >vim + :tab RustTest + :belowright 16RustTest + :leftabove vert 80RustTest +< +rust.vim Debugging ~ -It also has a few other mappings: +:RustInfo *:RustInfo* + Emits debugging info of the Vim Rust plugin. - *rust_<D-r>* -<D-r> Executes |:RustRun| with no arguments. - Note: This binding is only available in MacVim. +:RustInfoToClipboard *:RustInfoClipboard* + Saves debugging info of the Vim Rust plugin to the default + register. - *rust_<D-R>* -<D-R> Populates the command line with |:RustRun|! using the - arguments given to the last invocation, but does not - execute it. - Note: This binding is only available in MacVim. +:RustInfoToFile [filename] *:RustInfoToFile* + Saves debugging info of the Vim Rust plugin to the the given + file, overwritting it. ============================================================================== - vim:tw=78:sw=4:ts=8:noet:ft=help:norl: +MAPPINGS *rust-mappings* + +This plugin defines mappings for |[[| and |]]| to support hanging indents. + + +vim:tw=78:sw=4:noet:ts=8:ft=help:norl: diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt index 7571551130..0fa5ffd598 100644 --- a/runtime/doc/insert.txt +++ b/runtime/doc/insert.txt @@ -119,7 +119,7 @@ CTRL-R {register} *i_CTRL-R* '/' the last search pattern ':' the last command-line '.' the last inserted text - *i_CTRL-R_-* + *i_CTRL-R_-* '-' the last small (less than a line) delete register. This is repeatable using |.| since it remembers the register to put instead of diff --git a/runtime/doc/intro.txt b/runtime/doc/intro.txt index a39d07e3f2..e1a92bc87b 100644 --- a/runtime/doc/intro.txt +++ b/runtime/doc/intro.txt @@ -121,7 +121,7 @@ Vim would never have become what it is now, without the help of these people! Eric Fischer Mac port, 'cindent', and other improvements Benji Fisher Answering lots of user questions Bill Foster Athena GUI port (later removed) - Google Lets me work on Vim one day a week + Google Let Bram work on Vim one day a week Loic Grenie xvim (ideas for multi windows version) Sven Guckes Vim promoter and previous WWW page maintainer Darren Hiebert Exuberant ctags diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index d2d7e9faac..5103cc223f 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -578,7 +578,7 @@ LspRequest *LspRequest* "data" table passed to the callback function. It triggers when the initial request is sent ({type} == `pending`) and - when the LSP server responds ({type} == `complete`). If a cancelation + when the LSP server responds ({type} == `complete`). If a cancellation is requested using `client.cancel_request(request_id)`, then this event will trigger with {type} == `cancel`. @@ -901,12 +901,11 @@ start({config}, {opts}) *vim.lsp.start()* the current buffer to the client. Example: >lua - - vim.lsp.start({ - name = 'my-server-name', - cmd = {'name-of-language-server-executable'}, - root_dir = vim.fs.dirname(vim.fs.find({'pyproject.toml', 'setup.py'}, { upward = true })[1]), - }) + vim.lsp.start({ + name = 'my-server-name', + cmd = {'name-of-language-server-executable'}, + root_dir = vim.fs.dirname(vim.fs.find({'pyproject.toml', 'setup.py'}, { upward = true })[1]), + }) < See |vim.lsp.start_client()| for all available options. The most important @@ -1078,9 +1077,9 @@ status() *vim.lsp.status()* stop_client({client_id}, {force}) *vim.lsp.stop_client()* Stops a client(s). - You can also use the `stop()` function on a |vim.lsp.client| object. To stop all clients: >lua - - vim.lsp.stop_client(vim.lsp.get_clients()) + You can also use the `stop()` function on a |vim.lsp.client| object. To + stop all clients: >lua + vim.lsp.stop_client(vim.lsp.get_clients()) < By default asks the server to shutdown, unless stop was requested already @@ -1196,10 +1195,10 @@ definition({options}) *vim.lsp.buf.definition()* document_highlight() *vim.lsp.buf.document_highlight()* Send request to the server to resolve document highlights for the current text document position. This request can be triggered by a key mapping or - by events such as `CursorHold` , e.g.: >vim - autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight() - autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight() - autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references() + by events such as `CursorHold`, e.g.: >vim + autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight() + autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight() + autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references() < Note: Usage of |vim.lsp.buf.document_highlight()| requires the following @@ -1242,12 +1241,12 @@ format({options}) *vim.lsp.buf.format()* buffer (0). • filter (function|nil): Predicate used to filter clients. Receives a client as argument and must return a boolean. - Clients matching the predicate are included. Example: • >lua + Clients matching the predicate are included. Example: >lua - -- Never request typescript-language-server for formatting - vim.lsp.buf.format { - filter = function(client) return client.name ~= "tsserver" end - } + -- Never request typescript-language-server for formatting + vim.lsp.buf.format { + filter = function(client) return client.name ~= "tsserver" end + } < • async boolean|nil If true the method won't block. Defaults to false. Editing the buffer while formatting @@ -1366,24 +1365,23 @@ on_diagnostic({_}, {result}, {ctx}, {config}) See |vim.diagnostic.config()| for configuration options. Handler-specific configuration can be set using |vim.lsp.with()|: >lua - - vim.lsp.handlers["textDocument/diagnostic"] = vim.lsp.with( - vim.lsp.diagnostic.on_diagnostic, { - -- Enable underline, use default values - underline = true, - -- Enable virtual text, override spacing to 4 - virtual_text = { - spacing = 4, - }, - -- Use a function to dynamically turn signs off - -- and on, using buffer local variables - signs = function(namespace, bufnr) - return vim.b[bufnr].show_signs == true - end, - -- Disable a feature - update_in_insert = false, - } - ) + vim.lsp.handlers["textDocument/diagnostic"] = vim.lsp.with( + vim.lsp.diagnostic.on_diagnostic, { + -- Enable underline, use default values + underline = true, + -- Enable virtual text, override spacing to 4 + virtual_text = { + spacing = 4, + }, + -- Use a function to dynamically turn signs off + -- and on, using buffer local variables + signs = function(namespace, bufnr) + return vim.b[bufnr].show_signs == true + end, + -- Disable a feature + update_in_insert = false, + } + ) < Parameters: ~ @@ -1395,24 +1393,23 @@ on_publish_diagnostics({_}, {result}, {ctx}, {config}) See |vim.diagnostic.config()| for configuration options. Handler-specific configuration can be set using |vim.lsp.with()|: >lua - - vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with( - vim.lsp.diagnostic.on_publish_diagnostics, { - -- Enable underline, use default values - underline = true, - -- Enable virtual text, override spacing to 4 - virtual_text = { - spacing = 4, - }, - -- Use a function to dynamically turn signs off - -- and on, using buffer local variables - signs = function(namespace, bufnr) - return vim.b[bufnr].show_signs == true - end, - -- Disable a feature - update_in_insert = false, - } - ) + vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with( + vim.lsp.diagnostic.on_publish_diagnostics, { + -- Enable underline, use default values + underline = true, + -- Enable virtual text, override spacing to 4 + virtual_text = { + spacing = 4, + }, + -- Use a function to dynamically turn signs off + -- and on, using buffer local variables + signs = function(namespace, bufnr) + return vim.b[bufnr].show_signs == true + end, + -- Disable a feature + update_in_insert = false, + } + ) < Parameters: ~ @@ -1457,7 +1454,7 @@ refresh() *vim.lsp.codelens.refresh()* It is recommended to trigger this using an autocmd or via keymap. Example: >vim - autocmd BufEnter,CursorHold,InsertLeave <buffer> lua vim.lsp.codelens.refresh() + autocmd BufEnter,CursorHold,InsertLeave <buffer> lua vim.lsp.codelens.refresh() < run() *vim.lsp.codelens.run()* @@ -1534,8 +1531,7 @@ start({bufnr}, {client_id}, {opts}) *vim.lsp.semantic_tokens.start()* server that supports it, you can delete the semanticTokensProvider table from the {server_capabilities} of your client in your |LspAttach| callback or your configuration's `on_attach` callback: >lua - - client.server_capabilities.semanticTokensProvider = nil + client.server_capabilities.semanticTokensProvider = nil < Parameters: ~ @@ -1565,41 +1561,44 @@ Lua module: vim.lsp.handlers *lsp-handlers* hover({_}, {result}, {ctx}, {config}) *vim.lsp.handlers.hover()* |lsp-handler| for the method "textDocument/hover" >lua - - vim.lsp.handlers["textDocument/hover"] = vim.lsp.with( - vim.lsp.handlers.hover, { - -- Use a sharp border with `FloatBorder` highlights - border = "single", - -- add the title in hover float window - title = "hover" - } - ) + vim.lsp.handlers["textDocument/hover"] = vim.lsp.with( + vim.lsp.handlers.hover, { + -- Use a sharp border with `FloatBorder` highlights + border = "single", + -- add the title in hover float window + title = "hover" + } + ) < Parameters: ~ • {config} (table) Configuration table. • border: (default=nil) • Add borders to the floating window - • See |nvim_open_win()| + • See |vim.lsp.util.open_floating_preview()| for more + options. *vim.lsp.handlers.signature_help()* signature_help({_}, {result}, {ctx}, {config}) - |lsp-handler| for the method "textDocument/signatureHelp". The active - parameter is highlighted with |hl-LspSignatureActiveParameter|. >lua - - vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with( - vim.lsp.handlers.signature_help, { - -- Use a sharp border with `FloatBorder` highlights - border = "single" - } - ) + |lsp-handler| for the method "textDocument/signatureHelp". + + The active parameter is highlighted with |hl-LspSignatureActiveParameter|. >lua + vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with( + vim.lsp.handlers.signature_help, { + -- Use a sharp border with `FloatBorder` highlights + border = "single" + } + ) < Parameters: ~ + • {result} (table) Response from the language server + • {ctx} (table) Client context • {config} (table) Configuration table. • border: (default=nil) • Add borders to the floating window - • See |nvim_open_win()| + • See |vim.lsp.util.open_floating_preview()| for more + options ============================================================================== @@ -1791,6 +1790,13 @@ make_floating_popup_options({width}, {height}, {opts}) • focusable (string or table) override `focusable` • zindex (string or table) override `zindex`, defaults to 50 • relative ("mouse"|"cursor") defaults to "cursor" + • anchor_bias ("auto"|"above"|"below") defaults to "auto" + • "auto": place window based on which side of the cursor + has more lines + • "above": place the window above the cursor unless there + are not enough lines to display the full window height. + • "below": place the window below the cursor unless there + are not enough lines to display the full window height. Return: ~ (table) Options @@ -1892,8 +1898,9 @@ open_floating_preview({contents}, {syntax}, {opts}) Parameters: ~ • {contents} (table) of lines to show in window • {syntax} (string) of syntax to set for opened buffer - • {opts} (table) with optional fields (additional keys are passed - on to |nvim_open_win()|) + • {opts} (table) with optional fields (additional keys are filtered + with |vim.lsp.util.make_floating_popup_options()| before + they are passed on to |nvim_open_win()|) • height: (integer) height of floating window • width: (integer) width of floating window • wrap: (boolean, default true) wrap long lines diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 71f001f4fc..6cfec45523 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -582,17 +582,20 @@ VIM.HIGHLIGHT *vim.highlight* Nvim includes a function for highlighting a selection on yank. -To enable it, add the following to your `init.vim` : >vim +To enable it, add the following to your `init.vim`: >vim au TextYankPost * silent! lua vim.highlight.on_yank() + < You can customize the highlight group and the duration of the highlight via: >vim au TextYankPost * silent! lua vim.highlight.on_yank {higroup="IncSearch", timeout=150} + < If you want to exclude visual selections from highlighting on yank, use: >vim au TextYankPost * silent! lua vim.highlight.on_yank {on_visual=false} + < vim.highlight.on_yank({opts}) *vim.highlight.on_yank()* @@ -674,7 +677,7 @@ vim.regex:match_line({bufnr}, {line_idx}, {start}, {end_}) vim.regex:match_str({str}) *regex:match_str()* Match the string against the regex. If the string should match the regex precisely, surround the regex with `^` and `$` . If there was a match, the byte indices for the beginning and end of the - match are returned. When there is no match, `nil` is returned. Because any integer is "truthy", `regex:match()` can be directly used as a condition in an if-statement. + match are returned. When there is no match, `nil` is returned. Because any integer is "truthy", `regex:match_str()` can be directly used as a condition in an if-statement. Parameters: ~ • {str} (string) @@ -688,19 +691,18 @@ vim.diff({a}, {b}, {opts}) *vim.diff()* either directly or via callback arguments, are 1-based. Examples: >lua + vim.diff('a\n', 'b\nc\n') + -- => + -- @@ -1 +1,2 @@ + -- -a + -- +b + -- +c - vim.diff('a\n', 'b\nc\n') - -- => - -- @ -1 +1,2 @ - -- -a - -- +b - -- +c - - vim.diff('a\n', 'b\nc\n', {result_type = 'indices'}) - -- => - -- { - -- {1, 1, 1, 2} - -- } + vim.diff('a\n', 'b\nc\n', {result_type = 'indices'}) + -- => + -- { + -- {1, 1, 1, 2} + -- } < Parameters: ~ @@ -781,16 +783,18 @@ vim.json.decode({str}, {opts}) *vim.json.decode()* • Decodes empty array as `{}` (empty Lua table). Example: >lua - - :lua vim.print(vim.json.decode('{"bar":[],"foo":{},"zub":null}')) - --> { bar = {}, foo = vim.empty_dict(), zub = vim.NIL } - - < Parameters: ~ • {str} Stringified JSON data. • {opts} Options map keys: • - luanil: { object: bool, array: bool } • `luanil.object=true` converts `null` in JSON objects to Lua `nil` instead of `vim.NIL` . • `luanil.array=true` converts `null` in JSON arrays to Lua `nil` instead of `vim.NIL` . + vim.print(vim.json.decode('{"bar":[],"foo":{},"zub":null}')) + -- { bar = {}, foo = vim.empty_dict(), zub = vim.NIL } +< Parameters: ~ - • {str} (string) - • {opts} table<string,|nil any> + • {str} (string) Stringified JSON data. + • {opts} table<string,any>|nil Options table with keys: + • luanil: (table) Table with keys: + • object: (boolean) When true, converts `null` in JSON + objects to Lua `nil` instead of |vim.NIL|. + • array: (boolean) When true, converts `null` in JSON arrays + to Lua `nil` instead of |vim.NIL|. Return: ~ any @@ -817,12 +821,11 @@ vim.spell.check({str}) *vim.spell.check()* the buffer. Consider calling this with |nvim_buf_call()|. Example: >lua - - vim.spell.check("the quik brown fox") - -- => - -- { - -- {'quik', 'bad', 5} - -- } + vim.spell.check("the quik brown fox") + -- => + -- { + -- {'quik', 'bad', 5} + -- } < Parameters: ~ @@ -978,14 +981,13 @@ vim.str_utf_end({str}, {index}) *vim.str_utf_end()* (character) that {index} points to. Examples: >lua + -- The character 'æ' is stored as the bytes '\xc3\xa6' (using UTF-8) - -- The character 'æ' is stored as the bytes '\xc3\xa6' (using UTF-8) - - -- Returns 0 because the index is pointing at the last byte of a character - vim.str_utf_end('æ', 2) + -- Returns 0 because the index is pointing at the last byte of a character + vim.str_utf_end('æ', 2) - -- Returns 1 because the index is pointing at the penultimate byte of a character - vim.str_utf_end('æ', 1) + -- Returns 1 because the index is pointing at the penultimate byte of a character + vim.str_utf_end('æ', 1) < Parameters: ~ @@ -1015,14 +1017,13 @@ vim.str_utf_start({str}, {index}) *vim.str_utf_start()* character. Examples: >lua + -- The character 'æ' is stored as the bytes '\xc3\xa6' (using UTF-8) - -- The character 'æ' is stored as the bytes '\xc3\xa6' (using UTF-8) + -- Returns 0 because the index is pointing at the first byte of a character + vim.str_utf_start('æ', 1) - -- Returns 0 because the index is pointing at the first byte of a character - vim.str_utf_start('æ', 1) - - -- Returns -1 because the index is pointing at the second byte of a character - vim.str_utf_start('æ', 2) + -- Returns -1 because the index is pointing at the second byte of a character + vim.str_utf_start('æ', 2) < Parameters: ~ @@ -1080,20 +1081,19 @@ vim.ui_attach({ns}, {options}, {callback}) *vim.ui_attach()* likewise experimental). Example (stub for a |ui-popupmenu| implementation): >lua + ns = vim.api.nvim_create_namespace('my_fancy_pum') - ns = vim.api.nvim_create_namespace('my_fancy_pum') - - vim.ui_attach(ns, {ext_popupmenu=true}, function(event, ...) - if event == "popupmenu_show" then - local items, selected, row, col, grid = ... - print("display pum ", #items) - elseif event == "popupmenu_select" then - local selected = ... - print("selected", selected) - elseif event == "popupmenu_hide" then - print("FIN") - end - end) + vim.ui_attach(ns, {ext_popupmenu=true}, function(event, ...) + if event == "popupmenu_show" then + local items, selected, row, col, grid = ... + print("display pum ", #items) + elseif event == "popupmenu_select" then + local selected = ... + print("selected", selected) + elseif event == "popupmenu_hide" then + print("FIN") + end + end) < Parameters: ~ @@ -1116,27 +1116,26 @@ vim.wait({time}, {callback}, {interval}, {fast_only}) *vim.wait()* time. Examples: >lua + --- + -- Wait for 100 ms, allowing other events to process + vim.wait(100, function() end) - --- - -- Wait for 100 ms, allowing other events to process - vim.wait(100, function() end) + --- + -- Wait for 100 ms or until global variable set. + vim.wait(100, function() return vim.g.waiting_for_var end) - --- - -- Wait for 100 ms or until global variable set. - vim.wait(100, function() return vim.g.waiting_for_var end) + --- + -- Wait for 1 second or until global variable set, checking every ~500 ms + vim.wait(1000, function() return vim.g.waiting_for_var end, 500) - --- - -- Wait for 1 second or until global variable set, checking every ~500 ms - vim.wait(1000, function() return vim.g.waiting_for_var end, 500) + --- + -- Schedule a function to set a value in 100ms + vim.defer_fn(function() vim.g.timer_result = true end, 100) - --- - -- Schedule a function to set a value in 100ms - vim.defer_fn(function() vim.g.timer_result = true end, 100) - - -- Would wait ten seconds if results blocked. Actually only waits 100 ms - if vim.wait(10000, function() return vim.g.timer_result end) then - print('Only waiting a little bit of time!') - end + -- Would wait ten seconds if results blocked. Actually only waits 100 ms + if vim.wait(10000, function() return vim.g.timer_result end) then + print('Only waiting a little bit of time!') + end < Parameters: ~ @@ -1171,6 +1170,7 @@ Lua list: >lua local list = { 1, 2, 3 } vim.fn.remove(list, 0) vim.print(list) --> "{ 1, 2, 3 }" + < vim.call({func}, {...}) *vim.call()* @@ -1430,13 +1430,12 @@ vim.bo *vim.bo* print(vim.bo.baz) -- error: invalid key < - Parameters: ~ - • {bufnr} (integer|nil) - vim.env *vim.env* Environment variables defined in the editor session. See |expand-env| and |:let-environment| for the Vimscript behavior. Invalid or unset key - returns `nil` . Example: >lua + returns `nil`. + + Example: >lua vim.env.FOO = 'bar' print(vim.env.TERM) < @@ -1497,31 +1496,30 @@ vim.cmd *vim.cmd()* callable function to the command. Example: >lua + vim.cmd('echo 42') + vim.cmd([[ + augroup My_group + autocmd! + autocmd FileType c setlocal cindent + augroup END + ]]) - vim.cmd('echo 42') - vim.cmd([[ - augroup My_group - autocmd! - autocmd FileType c setlocal cindent - augroup END - ]]) + -- Ex command :echo "foo" + -- Note string literals need to be double quoted. + vim.cmd('echo "foo"') + vim.cmd { cmd = 'echo', args = { '"foo"' } } + vim.cmd.echo({ args = { '"foo"' } }) + vim.cmd.echo('"foo"') - -- Ex command :echo "foo" - -- Note string literals need to be double quoted. - vim.cmd('echo "foo"') - vim.cmd { cmd = 'echo', args = { '"foo"' } } - vim.cmd.echo({ args = { '"foo"' } }) - vim.cmd.echo('"foo"') + -- Ex command :write! myfile.txt + vim.cmd('write! myfile.txt') + vim.cmd { cmd = 'write', args = { "myfile.txt" }, bang = true } + vim.cmd.write { args = { "myfile.txt" }, bang = true } + vim.cmd.write { "myfile.txt", bang = true } - -- Ex command :write! myfile.txt - vim.cmd('write! myfile.txt') - vim.cmd { cmd = 'write', args = { "myfile.txt" }, bang = true } - vim.cmd.write { args = { "myfile.txt" }, bang = true } - vim.cmd.write { "myfile.txt", bang = true } - - -- Ex command :colorscheme blue - vim.cmd('colorscheme blue') - vim.cmd.colorscheme('blue') + -- Ex command :colorscheme blue + vim.cmd('colorscheme blue') + vim.cmd.colorscheme('blue') < Parameters: ~ @@ -1579,9 +1577,8 @@ vim.keycode({str}) *vim.keycode()* Translate keycodes. Example: >lua - - local k = vim.keycode - vim.g.mapleader = k'<bs>' + local k = vim.keycode + vim.g.mapleader = k'<bs>' < Parameters: ~ @@ -1653,16 +1650,15 @@ vim.paste({lines}, {phase}) *vim.paste()* |TUI|) pastes text into the editor. Example: To remove ANSI color codes when pasting: >lua - - vim.paste = (function(overridden) - return function(lines, phase) - for i,line in ipairs(lines) do - -- Scrub ANSI color codes from paste input. - lines[i] = line:gsub('\27%[[0-9;mK]+', '') - end - overridden(lines, phase) - end - end)(vim.paste) + vim.paste = (function(overridden) + return function(lines, phase) + for i,line in ipairs(lines) do + -- Scrub ANSI color codes from paste input. + lines[i] = line:gsub('\27%[[0-9;mK]+', '') + end + overridden(lines, phase) + end + end)(vim.paste) < Parameters: ~ @@ -1684,8 +1680,7 @@ vim.print({...}) *vim.print()* "Pretty prints" the given arguments and returns them unmodified. Example: >lua - - local hl_normal = vim.print(vim.api.nvim_get_hl_by_name('Normal', true)) + local hl_normal = vim.print(vim.api.nvim_get_hl_by_name('Normal', true)) < Return: ~ @@ -1737,20 +1732,19 @@ vim.system({cmd}, {opts}, {on_exit}) *vim.system()* Run a system command Examples: >lua + local on_exit = function(obj) + print(obj.code) + print(obj.signal) + print(obj.stdout) + print(obj.stderr) + end - local on_exit = function(obj) - print(obj.code) - print(obj.signal) - print(obj.stdout) - print(obj.stderr) - end - - -- Run asynchronously - vim.system({'echo', 'hello'}, { text = true }, on_exit) + -- Run asynchronously + vim.system({'echo', 'hello'}, { text = true }, on_exit) - -- Run synchronously - local obj = vim.system({'echo', 'hello'}, { text = true }):wait() - -- { code = 0, signal = 0, stdout = 'hello', stderr = '' } + -- Run synchronously + local obj = vim.system({'echo', 'hello'}, { text = true }):wait() + -- { code = 0, signal = 0, stdout = 'hello', stderr = '' } < See |uv.spawn()| for more details. @@ -1772,7 +1766,7 @@ vim.system({cmd}, {opts}, {on_exit}) *vim.system()* • stdout: (boolean|function) Handle output from stdout. When passed as a function must have the signature `fun(err: string, data: string)`. Defaults to `true` - • stderr: (boolean|function) Handle output from stdout. + • stderr: (boolean|function) Handle output from stderr. When passed as a function must have the signature `fun(err: string, data: string)`. Defaults to `true`. • text: (boolean) Handle stdout and stderr as text. @@ -1894,12 +1888,9 @@ vim.defaulttable({create}) *vim.defaulttable()* If {create} is `nil`, this will create a defaulttable whose constructor function is this function, effectively allowing to create nested tables on - the fly: - - >lua - - local a = vim.defaulttable() - a.b.c = 1 + the fly: >lua + local a = vim.defaulttable() + a.b.c = 1 < Parameters: ~ @@ -1924,18 +1915,16 @@ vim.gsplit({s}, {sep}, {opts}) *vim.gsplit()* in "lazy" fashion (as opposed to |vim.split()| which is "eager"). Example: >lua - - for s in vim.gsplit(':aa::b:', ':', {plain=true}) do - print(s) - end + for s in vim.gsplit(':aa::b:', ':', {plain=true}) do + print(s) + end < If you want to also inspect the separator itself (instead of discarding it), use |string.gmatch()|. Example: >lua - - for word, num in ('foo111bar222'):gmatch('([^0-9]*)(d*)') do - print(('word: s num: s'):format(word, num)) - end + for word, num in ('foo111bar222'):gmatch('([^0-9]*)(%d*)') do + print(('word: %s num: %s'):format(word, num)) + end < Parameters: ~ @@ -2021,22 +2010,20 @@ vim.pesc({s}) *vim.pesc()* vim.ringbuf({size}) *vim.ringbuf()* Create a ring buffer limited to a maximal number of items. Once the buffer - is full, adding a new entry overrides the oldest entry. -> - - local ringbuf = vim.ringbuf(4) - ringbuf:push("a") - ringbuf:push("b") - ringbuf:push("c") - ringbuf:push("d") - ringbuf:push("e") -- overrides "a" - print(ringbuf:pop()) -- returns "b" - print(ringbuf:pop()) -- returns "c" - - -- Can be used as iterator. Pops remaining items: - for val in ringbuf do - print(val) - end + is full, adding a new entry overrides the oldest entry. >lua + local ringbuf = vim.ringbuf(4) + ringbuf:push("a") + ringbuf:push("b") + ringbuf:push("c") + ringbuf:push("d") + ringbuf:push("e") -- overrides "a" + print(ringbuf:pop()) -- returns "b" + print(ringbuf:pop()) -- returns "c" + + -- Can be used as iterator. Pops remaining items: + for val in ringbuf do + print(val) + end < Returns a Ringbuf instance with the following methods: @@ -2090,11 +2077,10 @@ vim.split({s}, {sep}, {opts}) *vim.split()* a table (unlike |vim.gsplit()|). Examples: >lua - - split(":aa::b:", ":") --> {'','aa','','b',''} - split("axaby", "ab?") --> {'','x','y'} - split("x*yz*o", "*", {plain=true}) --> {'x','yz','o'} - split("|x|y|z|", "|", {trimempty=true}) --> {'x', 'y', 'z'} + split(":aa::b:", ":") --> {'','aa','','b',''} + split("axaby", "ab?") --> {'','x','y'} + split("x*yz*o", "*", {plain=true}) --> {'x','yz','o'} + split("|x|y|z|", "|", {trimempty=true}) --> {'x', 'y', 'z'} < Parameters: ~ @@ -2137,11 +2123,10 @@ vim.tbl_contains({t}, {value}, {opts}) *vim.tbl_contains()* a predicate that is checked for each value. Example: >lua - - vim.tbl_contains({ 'a', { 'b', 'c' } }, function(v) - return vim.deep_equal(v, { 'b', 'c' }) - end, { predicate = true }) - -- true + vim.tbl_contains({ 'a', { 'b', 'c' } }, function(v) + return vim.deep_equal(v, { 'b', 'c' }) + end, { predicate = true }) + -- true < Parameters: ~ @@ -2158,12 +2143,9 @@ vim.tbl_contains({t}, {value}, {opts}) *vim.tbl_contains()* • |vim.list_contains()| for checking values in list-like tables vim.tbl_count({t}) *vim.tbl_count()* - Counts the number of non-nil values in table `t`. - - >lua - - vim.tbl_count({ a=1, b=2 }) --> 2 - vim.tbl_count({ 1, 2 }) --> 2 + Counts the number of non-nil values in table `t`. >lua + vim.tbl_count({ a=1, b=2 }) --> 2 + vim.tbl_count({ 1, 2 }) --> 2 < Parameters: ~ @@ -2237,9 +2219,8 @@ vim.tbl_get({o}, {...}) *vim.tbl_get()* arguments. Return `nil` if the key does not exist. Examples: >lua - - vim.tbl_get({ key = { nested_key = true }}, 'key', 'nested_key') == true - vim.tbl_get({ key = {}}, 'key', 'nested_key') == nil + vim.tbl_get({ key = { nested_key = true }}, 'key', 'nested_key') == true + vim.tbl_get({ key = {}}, 'key', 'nested_key') == nil < Parameters: ~ @@ -2340,36 +2321,33 @@ vim.validate({opt}) *vim.validate()* Validates a parameter specification (types and values). Usage example: >lua - - function user.new(name, age, hobbies) - vim.validate{ - name={name, 'string'}, - age={age, 'number'}, - hobbies={hobbies, 'table'}, - } - ... - end + function user.new(name, age, hobbies) + vim.validate{ + name={name, 'string'}, + age={age, 'number'}, + hobbies={hobbies, 'table'}, + } + ... + end < Examples with explicit argument values (can be run directly): >lua + vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}} + --> NOP (success) - vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}} - --> NOP (success) - - vim.validate{arg1={1, 'table'}} - --> error('arg1: expected table, got number') + vim.validate{arg1={1, 'table'}} + --> error('arg1: expected table, got number') - vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}} - --> error('arg1: expected even number, got 3') + vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}} + --> error('arg1: expected even number, got 3') < If multiple types are valid they can be given as a list. >lua + vim.validate{arg1={{'foo'}, {'table', 'string'}}, arg2={'foo', {'table', 'string'}}} + -- NOP (success) - vim.validate{arg1={{'foo'}, {'table', 'string'}}, arg2={'foo', {'table', 'string'}}} - --> NOP (success) - - vim.validate{arg1={1, {'string', 'table'}}} - --> error('arg1: expected string|table, got number') + vim.validate{arg1={1, {'string', 'table'}}} + -- error('arg1: expected string|table, got number') < Parameters: ~ @@ -2506,10 +2484,9 @@ vim.ui.input({opts}, {on_confirm}) *vim.ui.input()* work until `on_confirm`. Example: >lua - - vim.ui.input({ prompt = 'Enter value for shiftwidth: ' }, function(input) - vim.o.shiftwidth = tonumber(input) - end) + vim.ui.input({ prompt = 'Enter value for shiftwidth: ' }, function(input) + vim.o.shiftwidth = tonumber(input) + end) < Parameters: ~ @@ -2535,10 +2512,9 @@ vim.ui.open({path}) *vim.ui.open()* Expands "~/" and environment variables in filesystem paths. Examples: >lua - - vim.ui.open("https://neovim.io/") - vim.ui.open("~/path/to/file") - vim.ui.open("$VIMRUNTIME") + vim.ui.open("https://neovim.io/") + vim.ui.open("~/path/to/file") + vim.ui.open("$VIMRUNTIME") < Parameters: ~ @@ -2556,19 +2532,18 @@ vim.ui.select({items}, {opts}, {on_choice}) *vim.ui.select()* (potentially asynchronous) work until `on_choice`. Example: >lua - - vim.ui.select({ 'tabs', 'spaces' }, { - prompt = 'Select tabs or spaces:', - format_item = function(item) - return "I'd like to choose " .. item - end, - }, function(choice) - if choice == 'spaces' then - vim.o.expandtab = true - else - vim.o.expandtab = false - end - end) + vim.ui.select({ 'tabs', 'spaces' }, { + prompt = 'Select tabs or spaces:', + format_item = function(item) + return "I'd like to choose " .. item + end, + }, function(choice) + if choice == 'spaces' then + vim.o.expandtab = true + else + vim.o.expandtab = false + end + end) < Parameters: ~ @@ -2620,58 +2595,56 @@ vim.filetype.add({filetypes}) *vim.filetype.add()* See $VIMRUNTIME/lua/vim/filetype.lua for more examples. Example: >lua - - vim.filetype.add({ - extension = { - foo = 'fooscript', - bar = function(path, bufnr) - if some_condition() then - return 'barscript', function(bufnr) - -- Set a buffer variable - vim.b[bufnr].barscript_version = 2 + vim.filetype.add({ + extension = { + foo = 'fooscript', + bar = function(path, bufnr) + if some_condition() then + return 'barscript', function(bufnr) + -- Set a buffer variable + vim.b[bufnr].barscript_version = 2 + end end - end - return 'bar' - end, - }, - filename = { - ['.foorc'] = 'toml', - ['/etc/foo/config'] = 'toml', - }, - pattern = { - ['.*/etc/foo/.*'] = 'fooscript', - -- Using an optional priority - ['.*/etc/foo/.*%.conf'] = { 'dosini', { priority = 10 } }, - -- A pattern containing an environment variable - ['${XDG_CONFIG_HOME}/foo/git'] = 'git', - ['README.(a+)$'] = function(path, bufnr, ext) - if ext == 'md' then - return 'markdown' - elseif ext == 'rst' then - return 'rst' - end - end, - }, - }) + return 'bar' + end, + }, + filename = { + ['.foorc'] = 'toml', + ['/etc/foo/config'] = 'toml', + }, + pattern = { + ['.*‍/etc/foo/.*'] = 'fooscript', + -- Using an optional priority + ['.*‍/etc/foo/.*%.conf'] = { 'dosini', { priority = 10 } }, + -- A pattern containing an environment variable + ['${XDG_CONFIG_HOME}/foo/git'] = 'git', + ['README.(%a+)$'] = function(path, bufnr, ext) + if ext == 'md' then + return 'markdown' + elseif ext == 'rst' then + return 'rst' + end + end, + }, + }) < To add a fallback match on contents, use >lua - - vim.filetype.add { - pattern = { - ['.*'] = { - priority = -math.huge, - function(path, bufnr) - local content = vim.api.nvim_buf_get_lines(bufnr, 0, 1, false)[1] or '' - if vim.regex([[^#!.*\<mine\>]]):match_str(content) ~= nil then - return 'mine' - elseif vim.regex([[\<drawing\>]]):match_str(content) ~= nil then - return 'drawing' - end - end, - }, - }, - } + vim.filetype.add { + pattern = { + ['.*'] = { + priority = -math.huge, + function(path, bufnr) + local content = vim.api.nvim_buf_get_lines(bufnr, 0, 1, false)[1] or '' + if vim.regex([[^#!.*\\<mine\\>]]):match_str(content) ~= nil then + return 'mine' + elseif vim.regex([[\\<drawing\\>]]):match_str(content) ~= nil then + return 'drawing' + end + end, + }, + }, + } < Parameters: ~ @@ -2687,8 +2660,7 @@ vim.filetype.get_option({filetype}, {option}) files. Example: >lua - - vim.filetype.get_option('vim', 'commentstring') + vim.filetype.get_option('vim', 'commentstring') < Note: this uses |nvim_get_option_value()| but caches the result. This @@ -2717,21 +2689,18 @@ vim.filetype.match({args}) *vim.filetype.match()* the filetype. Each of the three options is specified using a key to the single argument - of this function. Example: - - >lua + of this function. Example: >lua + -- Using a buffer number + vim.filetype.match({ buf = 42 }) - -- Using a buffer number - vim.filetype.match({ buf = 42 }) + -- Override the filename of the given buffer + vim.filetype.match({ buf = 42, filename = 'foo.c' }) - -- Override the filename of the given buffer - vim.filetype.match({ buf = 42, filename = 'foo.c' }) + -- Using a filename without a buffer + vim.filetype.match({ filename = 'main.lua' }) - -- Using a filename without a buffer - vim.filetype.match({ filename = 'main.lua' }) - - -- Using file contents - vim.filetype.match({ contents = {'#!/usr/bin/env bash'} }) + -- Using file contents + vim.filetype.match({ contents = {'#!/usr/bin/env bash'} }) < Parameters: ~ @@ -2762,10 +2731,9 @@ Lua module: vim.keymap *vim.keymap* vim.keymap.del({modes}, {lhs}, {opts}) *vim.keymap.del()* Remove an existing mapping. Examples: >lua + vim.keymap.del('n', 'lhs') - vim.keymap.del('n', 'lhs') - - vim.keymap.del({'n', 'i', 'v'}, '<leader>w', { buffer = 5 }) + vim.keymap.del({'n', 'i', 'v'}, '<leader>w', { buffer = 5 }) < Parameters: ~ @@ -2778,19 +2746,18 @@ vim.keymap.del({modes}, {lhs}, {opts}) *vim.keymap.del()* vim.keymap.set({mode}, {lhs}, {rhs}, {opts}) *vim.keymap.set()* Adds a new |mapping|. Examples: >lua - - -- Map to a Lua function: - vim.keymap.set('n', 'lhs', function() print("real lua function") end) - -- Map to multiple modes: - vim.keymap.set({'n', 'v'}, '<leader>lr', vim.lsp.buf.references, { buffer = true }) - -- Buffer-local mapping: - vim.keymap.set('n', '<leader>w', "<cmd>w<cr>", { silent = true, buffer = 5 }) - -- Expr mapping: - vim.keymap.set('i', '<Tab>', function() - return vim.fn.pumvisible() == 1 and "<C-n>" or "<Tab>" - end, { expr = true }) - -- <Plug> mapping: - vim.keymap.set('n', '[%', '<Plug>(MatchitNormalMultiBackward)') + -- Map to a Lua function: + vim.keymap.set('n', 'lhs', function() print("real lua function") end) + -- Map to multiple modes: + vim.keymap.set({'n', 'v'}, '<leader>lr', vim.lsp.buf.references, { buffer = true }) + -- Buffer-local mapping: + vim.keymap.set('n', '<leader>w', "<cmd>w<cr>", { silent = true, buffer = 5 }) + -- Expr mapping: + vim.keymap.set('i', '<Tab>', function() + return vim.fn.pumvisible() == 1 and "<C-n>" or "<Tab>" + end, { expr = true }) + -- <Plug> mapping: + vim.keymap.set('n', '[%%', '<Plug>(MatchitNormalMultiBackward)') < Parameters: ~ @@ -2871,24 +2838,23 @@ vim.fs.find({names}, {opts}) *vim.fs.find()* narrow the search to find only that type. Examples: >lua + -- location of Cargo.toml from the current buffer's path + local cargo = vim.fs.find('Cargo.toml', { + upward = true, + stop = vim.uv.os_homedir(), + path = vim.fs.dirname(vim.api.nvim_buf_get_name(0)), + }) - -- location of Cargo.toml from the current buffer's path - local cargo = vim.fs.find('Cargo.toml', { - upward = true, - stop = vim.uv.os_homedir(), - path = vim.fs.dirname(vim.api.nvim_buf_get_name(0)), - }) + -- list all test directories under the runtime directory + local test_dirs = vim.fs.find( + {'test', 'tst', 'testdir'}, + {limit = math.huge, type = 'directory', path = './runtime/'} + ) - -- list all test directories under the runtime directory - local test_dirs = vim.fs.find( - {'test', 'tst', 'testdir'}, - {limit = math.huge, type = 'directory', path = './runtime/'} - ) - - -- get all files ending with .cpp or .hpp inside lib/ - local cpp_hpp = vim.fs.find(function(name, path) - return name:match('.*%.[ch]pp$') and path:match('[/\\]lib$') - end, {limit = math.huge, type = 'file'}) + -- get all files ending with .cpp or .hpp inside lib/ + local cpp_hpp = vim.fs.find(function(name, path) + return name:match('.*%.[ch]pp$') and path:match('[/\\\\]lib$') + end, {limit = math.huge, type = 'file'}) < Parameters: ~ @@ -2934,15 +2900,14 @@ vim.fs.normalize({path}, {opts}) *vim.fs.normalize()* variables are also expanded. Examples: >lua + vim.fs.normalize('C:\\\\Users\\\\jdoe') + -- 'C:/Users/jdoe' - vim.fs.normalize('C:\\Users\\jdoe') - --> 'C:/Users/jdoe' - - vim.fs.normalize('~/src/neovim') - --> '/home/jdoe/src/neovim' + vim.fs.normalize('~/src/neovim') + -- '/home/jdoe/src/neovim' - vim.fs.normalize('$XDG_CONFIG_HOME/nvim/init.vim') - --> '/Users/jdoe/.config/nvim/init.vim' + vim.fs.normalize('$XDG_CONFIG_HOME/nvim/init.vim') + -- '/Users/jdoe/.config/nvim/init.vim' < Parameters: ~ @@ -2958,18 +2923,17 @@ vim.fs.parents({start}) *vim.fs.parents()* Iterate over all the parents of the given path. Example: >lua + local root_dir + for dir in vim.fs.parents(vim.api.nvim_buf_get_name(0)) do + if vim.fn.isdirectory(dir .. "/.git") == 1 then + root_dir = dir + break + end + end - local root_dir - for dir in vim.fs.parents(vim.api.nvim_buf_get_name(0)) do - if vim.fn.isdirectory(dir .. "/.git") == 1 then - root_dir = dir - break - end - end - - if root_dir then - print("Found git repository at", root_dir) - end + if root_dir then + print("Found git repository at", root_dir) + end < Parameters: ~ @@ -3033,11 +2997,10 @@ spec. Plugins, and plugin managers, can use this to check available tools and dependencies on the current system. Example: >lua - - local v = vim.version.parse(vim.fn.system({'tmux', '-V'}), {strict=false}) - if vim.version.gt(v, {3, 2, 0}) then - -- ... - end + local v = vim.version.parse(vim.fn.system({'tmux', '-V'}), {strict=false}) + if vim.version.gt(v, {3, 2, 0}) then + -- ... + end < @@ -3050,34 +3013,33 @@ tested against a version, using |vim.version.range()|. Supported range specs are shown in the following table. Note: suffixed versions (1.2.3-rc1) are not matched. > - - 1.2.3 is 1.2.3 - =1.2.3 is 1.2.3 - >1.2.3 greater than 1.2.3 - <1.2.3 before 1.2.3 - >=1.2.3 at least 1.2.3 - ~1.2.3 is >=1.2.3 <1.3.0 "reasonably close to 1.2.3" - ^1.2.3 is >=1.2.3 <2.0.0 "compatible with 1.2.3" - ^0.2.3 is >=0.2.3 <0.3.0 (0.x.x is special) - ^0.0.1 is =0.0.1 (0.0.x is special) - ^1.2 is >=1.2.0 <2.0.0 (like ^1.2.0) - ~1.2 is >=1.2.0 <1.3.0 (like ~1.2.0) - ^1 is >=1.0.0 <2.0.0 "compatible with 1" - ~1 same "reasonably close to 1" - 1.x same - 1.* same - 1 same - * any version - x same - - 1.2.3 - 2.3.4 is >=1.2.3 <=2.3.4 - - Partial right: missing pieces treated as x (2.3 => 2.3.x). - 1.2.3 - 2.3 is >=1.2.3 <2.4.0 - 1.2.3 - 2 is >=1.2.3 <3.0.0 - - Partial left: missing pieces treated as 0 (1.2 => 1.2.0). - 1.2 - 2.3.0 is 1.2.0 - 2.3.0 + 1.2.3 is 1.2.3 + =1.2.3 is 1.2.3 + >1.2.3 greater than 1.2.3 + <1.2.3 before 1.2.3 + >=1.2.3 at least 1.2.3 + ~1.2.3 is >=1.2.3 <1.3.0 "reasonably close to 1.2.3" + ^1.2.3 is >=1.2.3 <2.0.0 "compatible with 1.2.3" + ^0.2.3 is >=0.2.3 <0.3.0 (0.x.x is special) + ^0.0.1 is =0.0.1 (0.0.x is special) + ^1.2 is >=1.2.0 <2.0.0 (like ^1.2.0) + ~1.2 is >=1.2.0 <1.3.0 (like ~1.2.0) + ^1 is >=1.0.0 <2.0.0 "compatible with 1" + ~1 same "reasonably close to 1" + 1.x same + 1.* same + 1 same + * any version + x same + + 1.2.3 - 2.3.4 is >=1.2.3 <=2.3.4 + + Partial right: missing pieces treated as x (2.3 => 2.3.x). + 1.2.3 - 2.3 is >=1.2.3 <2.4.0 + 1.2.3 - 2 is >=1.2.3 <3.0.0 + + Partial left: missing pieces treated as 0 (1.2 => 1.2.0). + 1.2 - 2.3.0 is 1.2.0 - 2.3.0 < @@ -3087,15 +3049,14 @@ vim.version.cmp({v1}, {v2}) *vim.version.cmp()* tuple, e.g. `{1, 0, 3}`). Example: >lua - - if vim.version.cmp({1,0,3}, {0,2,1}) == 0 then - -- ... - end - local v1 = vim.version.parse('1.0.3-pre') - local v2 = vim.version.parse('0.2.1') - if vim.version.cmp(v1, v2) == 0 then - -- ... - end + if vim.version.cmp({1,0,3}, {0,2,1}) == 0 then + -- ... + end + local v1 = vim.version.parse('1.0.3-pre') + local v2 = vim.version.parse('0.2.1') + if vim.version.cmp(v1, v2) == 0 then + -- ... + end < Note: ~ @@ -3150,9 +3111,9 @@ vim.version.lt({v1}, {v2}) *vim.version.lt()* vim.version.parse({version}, {opts}) *vim.version.parse()* Parses a semantic version string and returns a version object which can be - used with other `vim.version` functions. For example "1.0.1-rc1+build.2" returns: > - - { major = 1, minor = 0, patch = 1, prerelease = "rc1", build = "build.2" } + used with other `vim.version` functions. For example "1.0.1-rc1+build.2" + returns: > + { major = 1, minor = 0, patch = 1, prerelease = "rc1", build = "build.2" } < Parameters: ~ @@ -3171,29 +3132,26 @@ vim.version.parse({version}, {opts}) *vim.version.parse()* vim.version.range({spec}) *vim.version.range()* Parses a semver |version-range| "spec" and returns a range object: > - - { - from: Version - to: Version - has(v: string|Version) - } + { + from: Version + to: Version + has(v: string|Version) + } < `:has()` checks if a version is in the range (inclusive `from`, exclusive `to`). Example: >lua - - local r = vim.version.range('1.0.0 - 2.0.0') - print(r:has('1.9.9')) -- true - print(r:has('2.0.0')) -- false - print(r:has(vim.version())) -- check against current Nvim version + local r = vim.version.range('1.0.0 - 2.0.0') + print(r:has('1.9.9')) -- true + print(r:has('2.0.0')) -- false + print(r:has(vim.version())) -- check against current Nvim version < Or use cmp(), eq(), lt(), and gt() to compare `.to` and `.from` directly: >lua - - local r = vim.version.range('1.0.0 - 2.0.0') - print(vim.version.gt({1,0,3}, r.from) and vim.version.lt({1,0,3}, r.to)) + local r = vim.version.range('1.0.0 - 2.0.0') + print(vim.version.gt({1,0,3}, r.from) and vim.version.lt({1,0,3}, r.to)) < Parameters: ~ @@ -3228,43 +3186,42 @@ iterator runs out of values (for function iterators, this means that the first value returned by the function is nil). Examples: >lua + local it = vim.iter({ 1, 2, 3, 4, 5 }) + it:map(function(v) + return v * 3 + end) + it:rev() + it:skip(2) + it:totable() + -- { 9, 6, 3 } + + -- ipairs() is a function iterator which returns both the index (i) and the value (v) + vim.iter(ipairs({ 1, 2, 3, 4, 5 })):map(function(i, v) + if i > 2 then return v end + end):totable() + -- { 3, 4, 5 } + + local it = vim.iter(vim.gsplit('1,2,3,4,5', ',')) + it:map(function(s) return tonumber(s) end) + for i, d in it:enumerate() do + print(string.format("Column %d is %d", i, d)) + end + -- Column 1 is 1 + -- Column 2 is 2 + -- Column 3 is 3 + -- Column 4 is 4 + -- Column 5 is 5 + + vim.iter({ a = 1, b = 2, c = 3, z = 26 }):any(function(k, v) + return k == 'z' + end) + -- true - local it = vim.iter({ 1, 2, 3, 4, 5 }) - it:map(function(v) - return v * 3 - end) - it:rev() - it:skip(2) - it:totable() - -- { 9, 6, 3 } - - -- ipairs() is a function iterator which returns both the index (i) and the value (v) - vim.iter(ipairs({ 1, 2, 3, 4, 5 })):map(function(i, v) - if i > 2 then return v end - end):totable() - -- { 3, 4, 5 } - - local it = vim.iter(vim.gsplit('1,2,3,4,5', ',')) - it:map(function(s) return tonumber(s) end) - for i, d in it:enumerate() do - print(string.format("Column %d is %d", i, d)) - end - -- Column 1 is 1 - -- Column 2 is 2 - -- Column 3 is 3 - -- Column 4 is 4 - -- Column 5 is 5 - - vim.iter({ a = 1, b = 2, c = 3, z = 26 }):any(function(k, v) - return k == 'z' - end) - -- true - - local rb = vim.ringbuf(3) - rb:push("a") - rb:push("b") - vim.iter(rb):totable() - -- { "a", "b" } + local rb = vim.ringbuf(3) + rb:push("a") + rb:push("b") + vim.iter(rb):totable() + -- { "a", "b" } < @@ -3275,8 +3232,7 @@ filter({f}, {src}, {...}) *vim.iter.filter()* Filter a table or iterator. This is a convenience function that performs: >lua - - vim.iter(src):filter(f):totable() + vim.iter(src):filter(f):totable() < Parameters: ~ @@ -3325,26 +3281,23 @@ Iter:enumerate() *Iter:enumerate()* the iterator value. For list tables, prefer >lua - - vim.iter(ipairs(t)) + vim.iter(ipairs(t)) < over >lua - - vim.iter(t):enumerate() + vim.iter(t):enumerate() < as the former is faster. Example: >lua - - local it = vim.iter(vim.gsplit('abc', '')):enumerate() - it:next() - -- 1 'a' - it:next() - -- 2 'b' - it:next() - -- 3 'c' + local it = vim.iter(vim.gsplit('abc', '')):enumerate() + it:next() + -- 1 'a' + it:next() + -- 2 'b' + it:next() + -- 3 'c' < Return: ~ @@ -3354,8 +3307,7 @@ Iter:filter({f}) *Iter:filter()* Add a filter step to the iterator pipeline. Example: >lua - - local bufs = vim.iter(vim.api.nvim_list_bufs()):filter(vim.api.nvim_buf_is_loaded) + local bufs = vim.iter(vim.api.nvim_list_bufs()):filter(vim.api.nvim_buf_is_loaded) < Parameters: ~ @@ -3373,18 +3325,17 @@ Iter:find({f}) *Iter:find()* found. Examples: >lua + local it = vim.iter({ 3, 6, 9, 12 }) + it:find(12) + -- 12 - local it = vim.iter({ 3, 6, 9, 12 }) - it:find(12) - -- 12 - - local it = vim.iter({ 3, 6, 9, 12 }) - it:find(20) - -- nil + local it = vim.iter({ 3, 6, 9, 12 }) + it:find(20) + -- nil - local it = vim.iter({ 3, 6, 9, 12 }) - it:find(function(v) return v % 4 == 0 end) - -- 12 + local it = vim.iter({ 3, 6, 9, 12 }) + it:find(function(v) return v % 4 == 0 end) + -- 12 < Return: ~ @@ -3394,16 +3345,15 @@ Iter:fold({init}, {f}) *Iter:fold()* Fold ("reduce") an iterator or table into a single value. Examples: >lua - - -- Create a new table with only even values - local t = { a = 1, b = 2, c = 3, d = 4 } - local it = vim.iter(t) - it:filter(function(k, v) return v % 2 == 0 end) - it:fold({}, function(t, k, v) - t[k] = v - return t - end) - -- { b = 2, d = 4 } + -- Create a new table with only even values + local t = { a = 1, b = 2, c = 3, d = 4 } + local it = vim.iter(t) + it:filter(function(k, v) return v % 2 == 0 end) + it:fold({}, function(t, k, v) + t[k] = v + return t + end) + -- { b = 2, d = 4 } < Parameters: ~ @@ -3419,14 +3369,13 @@ Iter:last() *Iter:last()* Drains the iterator. Example: >lua + local it = vim.iter(vim.gsplit('abcdefg', '')) + it:last() + -- 'g' - local it = vim.iter(vim.gsplit('abcdefg', '')) - it:last() - -- 'g' - - local it = vim.iter({ 3, 6, 9, 12, 15 }) - it:last() - -- 15 + local it = vim.iter({ 3, 6, 9, 12, 15 }) + it:last() + -- 15 < Return: ~ @@ -3438,14 +3387,13 @@ Iter:map({f}) *Iter:map()* If the map function returns nil, the value is filtered from the iterator. Example: >lua - - local it = vim.iter({ 1, 2, 3, 4 }):map(function(v) - if v % 2 == 0 then - return v * 3 - end - end) - it:totable() - -- { 6, 12 } + local it = vim.iter({ 1, 2, 3, 4 }):map(function(v) + if v % 2 == 0 then + return v * 3 + end + end) + it:totable() + -- { 6, 12 } < Parameters: ~ @@ -3461,14 +3409,13 @@ Iter:next() *Iter:next()* Return the next value from the iterator. Example: >lua - - local it = vim.iter(string.gmatch('1 2 3', 'd+')):map(tonumber) - it:next() - -- 1 - it:next() - -- 2 - it:next() - -- 3 + local it = vim.iter(string.gmatch('1 2 3', '%d+')):map(tonumber) + it:next() + -- 1 + it:next() + -- 2 + it:next() + -- 3 < Return: ~ @@ -3480,12 +3427,11 @@ Iter:nextback() *Iter:nextback()* Only supported for iterators on list-like tables. Example: >lua - - local it = vim.iter({1, 2, 3, 4}) - it:nextback() - -- 4 - it:nextback() - -- 3 + local it = vim.iter({1, 2, 3, 4}) + it:nextback() + -- 4 + it:nextback() + -- 3 < Return: ~ @@ -3497,12 +3443,11 @@ Iter:nth({n}) *Iter:nth()* This function advances the iterator. Example: >lua - - local it = vim.iter({ 3, 6, 9, 12 }) - it:nth(2) - -- 6 - it:nth(2) - -- 12 + local it = vim.iter({ 3, 6, 9, 12 }) + it:nth(2) + -- 6 + it:nth(2) + -- 12 < Parameters: ~ @@ -3519,12 +3464,11 @@ Iter:nthback({n}) *Iter:nthback()* Only supported for iterators on list-like tables. Example: >lua - - local it = vim.iter({ 3, 6, 9, 12 }) - it:nthback(2) - -- 9 - it:nthback(2) - -- 3 + local it = vim.iter({ 3, 6, 9, 12 }) + it:nthback(2) + -- 9 + it:nthback(2) + -- 3 < Parameters: ~ @@ -3539,14 +3483,13 @@ Iter:peek() *Iter:peek()* Only supported for iterators on list-like tables. Example: >lua - - local it = vim.iter({ 3, 6, 9, 12 }) - it:peek() - -- 3 - it:peek() - -- 3 - it:next() - -- 3 + local it = vim.iter({ 3, 6, 9, 12 }) + it:peek() + -- 3 + it:peek() + -- 3 + it:next() + -- 3 < Return: ~ @@ -3558,14 +3501,13 @@ Iter:peekback() *Iter:peekback()* Only supported for iterators on list-like tables. Example: >lua - - local it = vim.iter({1, 2, 3, 4}) - it:peekback() - -- 4 - it:peekback() - -- 4 - it:nextback() - -- 4 + local it = vim.iter({1, 2, 3, 4}) + it:peekback() + -- 4 + it:peekback() + -- 4 + it:nextback() + -- 4 < Return: ~ @@ -3577,10 +3519,9 @@ Iter:rev() *Iter:rev()* Only supported for iterators on list-like tables. Example: >lua - - local it = vim.iter({ 3, 6, 9, 12 }):rev() - it:totable() - -- { 12, 9, 6, 3 } + local it = vim.iter({ 3, 6, 9, 12 }):rev() + it:totable() + -- { 12, 9, 6, 3 } < Return: ~ @@ -3596,12 +3537,11 @@ Iter:rfind({f}) *Iter:rfind()* Only supported for iterators on list-like tables. Examples: >lua - - local it = vim.iter({ 1, 2, 3, 2, 1 }):enumerate() - it:rfind(1) - -- 5 1 - it:rfind(1) - -- 1 1 + local it = vim.iter({ 1, 2, 3, 2, 1 }):enumerate() + it:rfind(1) + -- 5 1 + it:rfind(1) + -- 1 1 < Return: ~ @@ -3614,10 +3554,9 @@ Iter:skip({n}) *Iter:skip()* Skip values in the iterator. Example: >lua - - local it = vim.iter({ 3, 6, 9, 12 }):skip(2) - it:next() - -- 9 + local it = vim.iter({ 3, 6, 9, 12 }):skip(2) + it:next() + -- 9 < Parameters: ~ @@ -3632,12 +3571,11 @@ Iter:skipback({n}) *Iter:skipback()* Only supported for iterators on list-like tables. Example: >lua - - local it = vim.iter({ 1, 2, 3, 4, 5 }):skipback(2) - it:next() - -- 1 - it:nextback() - -- 3 + local it = vim.iter({ 1, 2, 3, 4, 5 }):skipback(2) + it:next() + -- 1 + it:nextback() + -- 3 < Parameters: ~ @@ -3669,15 +3607,14 @@ Iter:totable() *Iter:totable()* the iterator pipeline, each value will be included in a table. Examples: >lua + vim.iter(string.gmatch('100 20 50', '%d+')):map(tonumber):totable() + -- { 100, 20, 50 } - vim.iter(string.gmatch('100 20 50', 'd+')):map(tonumber):totable() - -- { 100, 20, 50 } + vim.iter({ 1, 2, 3 }):map(function(v) return v, 2 * v end):totable() + -- { { 1, 2 }, { 2, 4 }, { 3, 6 } } - vim.iter({ 1, 2, 3 }):map(function(v) return v, 2 * v end):totable() - -- { { 1, 2 }, { 2, 4 }, { 3, 6 } } - - vim.iter({ a = 1, b = 2, c = 3 }):filter(function(k, v) return v % 2 ~= 0 end):totable() - -- { { 'a', 1 }, { 'c', 3 } } + vim.iter({ a = 1, b = 2, c = 3 }):filter(function(k, v) return v % 2 ~= 0 end):totable() + -- { { 'a', 1 }, { 'c', 3 } } < The generated table is a list-like table with consecutive, numeric @@ -3691,8 +3628,7 @@ map({f}, {src}, {...}) *vim.iter.map()* Map and filter a table or iterator. This is a convenience function that performs: >lua - - vim.iter(src):map(f):totable() + vim.iter(src):map(f):totable() < Parameters: ~ @@ -3711,8 +3647,7 @@ totable({f}, {...}) *vim.iter.totable()* Collect an iterator into a table. This is a convenience function that performs: >lua - - vim.iter(f):totable() + vim.iter(f):totable() < Parameters: ~ diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 972f37f0e9..0162a99619 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -120,6 +120,8 @@ The following new APIs and features were added. indicator to see if a server supports a feature. Instead use `client.supports_method(<method>)`. It considers both the dynamic capabilities and static `server_capabilities`. + • Added a new `anchor_bias` option to |lsp-handlers| to aid in positioning of + floating windows. • Treesitter • Bundled parsers and queries (highlight, folds) for Markdown, Python, and @@ -133,7 +135,7 @@ The following new APIs and features were added. `vim.treesitter.language.register`. • The `#set!` directive now supports `injection.self` and `injection.parent` for injecting either the current node's language or the parent LanguageTree's language, respectively. - • Added `vim.treesitter.preview_query()`, for live editing of treesitter + • Added `vim.treesitter.query.edit()`, for live editing of treesitter queries. • Improved error messages for query parsing. @@ -219,6 +221,15 @@ The following changes to existing APIs or features add new behavior. "virtual_text" table, which gives users more control over how diagnostic virtual text is displayed. +• Extmarks now fully support multi-line ranges, and a single extmark can be + used to highlight a range of arbitrary length. The |nvim_buf_set_extmark()| + API function already allowed you to define such ranges, but highlight regions + were not rendered consistently for a range that covers more than one line break. + This has now been fixed. Signs defined as part of a multi-line extmark also + apply to every line in the range, not just the first. + In addition, |nvim_buf_get_extmarks()| has gained an "overlap" option to + return such ranges even if they started before the specified position. + ============================================================================== REMOVED FEATURES *news-removed* @@ -257,4 +268,7 @@ release. • `vim.loop` has been renamed to `vim.uv`. +• vim.treesitter.languagetree functions: + - |LanguageTree:for_each_child()| Use |LanguageTree:children()| (non-recursive) instead. + vim:tw=78:ts=8:sw=2:et:ft=help:norl: diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index e2ca9c1992..e1518c58bb 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -240,7 +240,7 @@ The options local to a window are remembered for each buffer. This also happens when the buffer is not loaded, but they are lost when the buffer is wiped out |:bwipe|. -Special local window options *special-local-window-option* +Special local window options *local-noglobal* The following local window options won't be copied over when new windows are created, thus they behave slightly differently: @@ -251,7 +251,7 @@ created, thus they behave slightly differently: 'winfixheight' specific to existing window 'winfixwidth' specific to existing window -Special local buffer options *special-local-buffer-option* +Special local buffer options The following local buffer options won't be copied over when new buffers are created, thus they behave slightly differently: @@ -945,7 +945,7 @@ A jump table for the options with a short description can be found at |Q_op|. backups if you don't care about losing the file. Note that environment variables are not expanded. If you want to use - $HOME you must expand it explicitly, e.g.: > + $HOME you must expand it explicitly, e.g.: >vim :let &backupskip = escape(expand('$HOME'), '\') .. '/tmp/*' < Note that the default also makes sure that "crontab -e" works (when a @@ -1095,7 +1095,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'bufhidden'* *'bh'* 'bufhidden' 'bh' string (default "") - local to buffer |special-local-buffer-option| + local to buffer |local-noglobal| This option specifies what happens when a buffer is no longer displayed in a window: <empty> follow the global 'hidden' option @@ -1127,7 +1127,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'buftype'* *'bt'* *E382* 'buftype' 'bt' string (default "") - local to buffer |special-local-buffer-option| + local to buffer |local-noglobal| The value of this option specifies the type of a buffer: <empty> normal buffer acwrite buffer will always be written with |BufWriteCmd|s @@ -2433,7 +2433,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'filetype'* *'ft'* 'filetype' 'ft' string (default "") - local to buffer |special-local-buffer-option| + local to buffer |local-noglobal| When this option is set, the FileType autocommand event is triggered. All autocommands that match with the value of this option will be executed. Thus the value of 'filetype' is used in place of the file @@ -2901,10 +2901,10 @@ A jump table for the options with a short description can be found at |Q_op|. n-v-c-sm:block,i-ci-ve:ver25-Cursor,r-cr-o:hor20 In Normal et al. modes, use a block cursor with the default colors defined by the host - terminal. In Insert-likes modes, use + terminal. In Insert-like modes, use a vertical bar cursor with colors from - "Cursor" highlight group. In Replace-likes - modes, use a underline cursor with + "Cursor" highlight group. In Replace-like + modes, use an underline cursor with default colors. i-ci:ver30-iCursor-blinkwait300-blinkon200-blinkoff150 In Insert and Command-line Insert mode, use a @@ -4109,7 +4109,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'modified'* *'mod'* *'nomodified'* *'nomod'* 'modified' 'mod' boolean (default off) - local to buffer |special-local-buffer-option| + local to buffer |local-noglobal| When on, the buffer is considered to be modified. This option is set when: 1. A change was made to the text since it was last written. Using the @@ -4558,7 +4558,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'previewwindow'* *'pvw'* *'nopreviewwindow'* *'nopvw'* *E590* 'previewwindow' 'pvw' boolean (default off) - local to window |special-local-window-option| + local to window |local-noglobal| Identifies the preview window. Only one window can have this option set. It's normally not set directly, but by using one of the commands |:ptag|, |:pedit|, etc. @@ -4628,7 +4628,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'readonly'* *'ro'* *'noreadonly'* *'noro'* 'readonly' 'ro' boolean (default off) - local to buffer |special-local-buffer-option| + local to buffer |local-noglobal| If on, writes fail unless you use a '!'. Protects you from accidentally overwriting a file. Default on when Vim is started in read-only mode ("vim -R") or when the executable is called "view". @@ -4890,7 +4890,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'scroll'* *'scr'* 'scroll' 'scr' number (default half the window height) - local to window |special-local-window-option| + local to window |local-noglobal| Number of lines to scroll with CTRL-U and CTRL-D commands. Will be set to half the number of lines in the window when the window size changes. This may happen when enabling the |status-line| or @@ -6240,7 +6240,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'syntax'* *'syn'* 'syntax' 'syn' string (default "") - local to buffer |special-local-buffer-option| + local to buffer |local-noglobal| When this option is set, the syntax with this name is loaded, unless syntax highlighting has been switched off with ":syntax off". Otherwise this option does not always reflect the current syntax (the @@ -7107,7 +7107,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'winfixheight'* *'wfh'* *'nowinfixheight'* *'nowfh'* 'winfixheight' 'wfh' boolean (default off) - local to window |special-local-window-option| + local to window |local-noglobal| Keep the window height when windows are opened or closed and 'equalalways' is set. Also for |CTRL-W_=|. Set by default for the |preview-window| and |quickfix-window|. @@ -7115,7 +7115,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'winfixwidth'* *'wfw'* *'nowinfixwidth'* *'nowfw'* 'winfixwidth' 'wfw' boolean (default off) - local to window |special-local-window-option| + local to window |local-noglobal| Keep the window width when windows are opened or closed and 'equalalways' is set. Also for |CTRL-W_=|. The width may be changed anyway when running out of room. diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index 6c0c542737..c15b41f9aa 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -559,7 +559,7 @@ The method used to prevent copying in the generated page depends on the value of |g:html_use_input_for_pc|. *g:html_use_input_for_pc* -Default: "fallback" +Default: "none" If |g:html_prevent_copy| is non-empty, then: When "all", read-only <input> elements are used in place of normal text for @@ -2125,11 +2125,11 @@ should set a variable in your vimrc file: > :let lpc_syntax_for_c = 1 If it doesn't work properly for some particular C or LPC files, use a -modeline. For a LPC file: +modeline. For a LPC file: > // vim:set ft=lpc: -For a C file that is recognized as LPC: +For a C file that is recognized as LPC: > // vim:set ft=c: @@ -2161,7 +2161,7 @@ LUA *lua.vim* *ft-lua-syntax* The Lua syntax file can be used for versions 4.0, 5.0, 5.1 and 5.2 (5.2 is the default). You can select one of these versions using the global variables lua_version and lua_subversion. For example, to activate Lua -5.1 syntax highlighting, set the variables like this: +5.1 syntax highlighting, set the variables like this: > :let lua_version = 5 :let lua_subversion = 1 diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 287985f75b..64b4ca7ca2 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -551,8 +551,7 @@ Lua module: vim.treesitter *lua-treesitter-core* foldexpr({lnum}) *vim.treesitter.foldexpr()* Returns the fold level for {lnum} in the current buffer. Can be set directly to 'foldexpr': >lua - - vim.wo.foldexpr = 'v:lua.vim.treesitter.foldexpr()' + vim.wo.foldexpr = 'v:lua.vim.treesitter.foldexpr()' < Parameters: ~ @@ -677,8 +676,8 @@ inspect_tree({opts}) *vim.treesitter.inspect_tree()* While in the window, press "a" to toggle display of anonymous nodes, "I" to toggle the display of the source language of each node, "o" to toggle - the query previewer, and press <Enter> to jump to the node under the - cursor in the source buffer. + the query editor, and press <Enter> to jump to the node under the cursor + in the source buffer. Can also be shown with `:InspectTree`. *:InspectTree* @@ -731,11 +730,6 @@ node_contains({node}, {range}) *vim.treesitter.node_contains()* Return: ~ (boolean) True if the {node} contains the {range} -preview_query() *vim.treesitter.preview_query()* - Open a window for live editing of a treesitter query. - - Can also be shown with `:PreviewQuery`. *:PreviewQuery* - start({bufnr}, {lang}) *vim.treesitter.start()* Starts treesitter highlighting for a buffer @@ -746,13 +740,12 @@ start({bufnr}, {lang}) *vim.treesitter.start()* the call to `start`. Example: >lua - - vim.api.nvim_create_autocmd( 'FileType', { pattern = 'tex', - callback = function(args) - vim.treesitter.start(args.buf, 'latex') - vim.bo[args.buf].syntax = 'on' -- only if additional legacy syntax is needed - end - }) + vim.api.nvim_create_autocmd( 'FileType', { pattern = 'tex', + callback = function(args) + vim.treesitter.start(args.buf, 'latex') + vim.bo[args.buf].syntax = 'on' -- only if additional legacy syntax is needed + end + }) < Parameters: ~ @@ -860,6 +853,14 @@ add_predicate({name}, {handler}, {force}) meanings • {force} (boolean|nil) +edit() *vim.treesitter.query.edit()* + Open a window for live editing of a treesitter query. + + Can also be shown with `:EditQuery`. *:EditQuery* + + Note that the editor opens a scratch buffer, and so queries aren't + persisted on disk. + get({lang}, {query_name}) *vim.treesitter.query.get()* Returns the runtime query {query_name} for {lang}. @@ -922,7 +923,7 @@ omnifunc({findstart}, {base}) *vim.treesitter.query.omnifunc()* Omnifunc for completing node names and predicates in treesitter queries. Use via >lua - vim.bo.omnifunc = 'v:lua.vim.treesitter.query.omnifunc' + vim.bo.omnifunc = 'v:lua.vim.treesitter.query.omnifunc' < parse({lang}, {query}) *vim.treesitter.query.parse()* @@ -958,14 +959,13 @@ Query:iter_captures({node}, {source}, {start}, {stop}) The iterator returns three values: a numeric id identifying the capture, the captured node, and metadata from any directives processing the match. The following example shows how to get captures by name: >lua - - for id, node, metadata in query:iter_captures(tree:root(), bufnr, first, last) do - local name = query.captures[id] -- name of the capture in the query - -- typically useful info about the node: - local type = node:type() -- type of the captured node - local row1, col1, row2, col2 = node:range() -- range of the capture - -- ... use the info here ... - end + for id, node, metadata in query:iter_captures(tree:root(), bufnr, first, last) do + local name = query.captures[id] -- name of the capture in the query + -- typically useful info about the node: + local type = node:type() -- type of the captured node + local row1, col1, row2, col2 = node:range() -- range of the capture + -- ... use the info here ... + end < Parameters: ~ @@ -988,18 +988,18 @@ Query:iter_matches({node}, {source}, {start}, {stop}, {opts}) (1-based) index of the pattern in the query, a table mapping capture indices to nodes, and metadata from any directives processing the match. If the query has more than one pattern, the capture table might be sparse - and e.g. `pairs()` method should be used over `ipairs` . Here is an example iterating over all captures in every match: >lua - - for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, first, last) do - for id, node in pairs(match) do - local name = query.captures[id] - -- `node` was captured by the `name` capture in the match - - local node_data = metadata[id] -- Node level metadata - - -- ... use the info here ... - end - end + and e.g. `pairs()` method should be used over `ipairs`. Here is an example + iterating over all captures in every match: >lua + for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, first, last) do + for id, node in pairs(match) do + local name = query.captures[id] + -- `node` was captured by the `name` capture in the match + + local node_data = metadata[id] -- Node level metadata + + -- ... use the info here ... + end + end < Parameters: ~ @@ -1039,11 +1039,8 @@ inject other languages, recursively. For example a Lua buffer containing some Vimscript commands needs multiple parsers to fully understand its contents. -To create a LanguageTree (parser object) for a given buffer and language, use: - ->lua - - local parser = vim.treesitter.get_parser(bufnr, lang) +To create a LanguageTree (parser object) for a given buffer and language, use: >lua + local parser = vim.treesitter.get_parser(bufnr, lang) < @@ -1052,11 +1049,8 @@ Note: currently the parser is retained for the lifetime of a buffer but this may change; a plugin should keep a reference to the parser object if it wants incremental updates. -Whenever you need to access the current syntax tree, parse the buffer: - ->lua - - local tree = parser:parse({ start_row, end_row }) +Whenever you need to access the current syntax tree, parse the buffer: >lua + local tree = parser:parse({ start_row, end_row }) < @@ -1094,15 +1088,6 @@ LanguageTree:destroy() *LanguageTree:destroy()* Note: This DOES NOT remove this tree from a parent. Instead, `remove_child` must be called on the parent to remove it. - *LanguageTree:for_each_child()* -LanguageTree:for_each_child({fn}, {include_self}) - Invokes the callback for each |LanguageTree| and its children recursively - - Parameters: ~ - • {fn} fun(tree: LanguageTree, lang: string) - • {include_self} (boolean|nil) Whether to include the invoking tree in - the results - LanguageTree:for_each_tree({fn}) *LanguageTree:for_each_tree()* Invokes the callback for each |LanguageTree| recursively. diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt index 842542bdc3..e0c0772f18 100644 --- a/runtime/doc/windows.txt +++ b/runtime/doc/windows.txt @@ -169,7 +169,7 @@ CTRL-W v *CTRL-W_v* it doesn't! CTRL-W n *CTRL-W_n* -CTRL-W CTRL_N *CTRL-W_CTRL-N* +CTRL-W CTRL-N *CTRL-W_CTRL-N* :[N]new [++opt] [+cmd] *:new* Create a new window and start editing an empty file in it. Make new window N high (default is to use half the existing diff --git a/runtime/ftplugin/forth.vim b/runtime/ftplugin/forth.vim index 5343784a21..d28c8484e1 100644 --- a/runtime/ftplugin/forth.vim +++ b/runtime/ftplugin/forth.vim @@ -1,7 +1,7 @@ " Vim filetype plugin " Language: Forth " Maintainer: Johan Kotlinski <kotlinski@gmail.com> -" Last Change: 2023 Aug 08 +" Last Change: 2023 Sep 15 " URL: https://github.com/jkotlinski/forth.vim if exists("b:did_ftplugin") @@ -58,6 +58,7 @@ if exists("loaded_matchit") && !exists("b:match_words") let b:match_ignorecase = 1 let b:match_words = s:matchit_patterns[1:]->join(',') let b:undo_ftplugin ..= "| unlet! b:match_ignorecase b:match_words" + unlet s:matchit_patterns endif if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter") @@ -68,4 +69,4 @@ endif let &cpo = s:cpo_save unlet s:cpo_save -unlet s:define_patterns s:include_patterns s:matchit_patterns +unlet s:define_patterns s:include_patterns diff --git a/runtime/ftplugin/perl.vim b/runtime/ftplugin/perl.vim index f3de81debe..7ea0ae980a 100644 --- a/runtime/ftplugin/perl.vim +++ b/runtime/ftplugin/perl.vim @@ -5,6 +5,8 @@ " Bugs/requests: https://github.com/vim-perl/vim-perl/issues " License: Vim License (see :help license) " Last Change: 2021 Nov 10 +" 2023 Sep 07 by Vim Project (safety check: don't execute perl +" from current directory) if exists("b:did_ftplugin") | finish | endif let b:did_ftplugin = 1 diff --git a/runtime/ftplugin/pod.vim b/runtime/ftplugin/pod.vim index 2a905ab354..61a4aa094a 100644 --- a/runtime/ftplugin/pod.vim +++ b/runtime/ftplugin/pod.vim @@ -5,11 +5,12 @@ " Homepage: https://github.com/vim-perl/vim-perl " Bugs/requests: https://github.com/vim-perl/vim-perl/issues " License: Vim License (see :help license) -" Last Change: 2021 Oct 19 +" Last Change: 2023 Jul 05 if exists("b:did_ftplugin") - finish + finish endif +let b:did_ftplugin = 1 let s:save_cpo = &cpo set cpo-=C @@ -37,12 +38,12 @@ if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter") let b:undo_ftplugin .= " | unlet! b:browsefilter" endif -function! s:jumpToSection(backwards) - let flags = a:backwards ? 'bsWz' : 'sWz' - if has('syntax_items') +function s:jumpToSection(direction) + let flags = a:direction == "backward" ? "bsWz" : "sWz" + if has("syntax_items") let skip = "synIDattr(synID(line('.'), col('.'), 1), 'name') !~# '\\<podCommand\\>'" else - let skip = '' + let skip = "" endif for i in range(v:count1) call search('^=\a', flags, 0, 0, skip) @@ -50,19 +51,17 @@ function! s:jumpToSection(backwards) endfunction if !exists("no_plugin_maps") && !exists("no_pod_maps") - nnoremap <silent> <buffer> ]] <Cmd>call <SID>jumpToSection()<CR> - vnoremap <silent> <buffer> ]] <Cmd>call <SID>jumpToSection()<CR> - nnoremap <silent> <buffer> ][ <Cmd>call <SID>jumpToSection()<CR> - vnoremap <silent> <buffer> ][ <Cmd>call <SID>jumpToSection()<CR> - nnoremap <silent> <buffer> [[ <Cmd>call <SID>jumpToSection(1)<CR> - vnoremap <silent> <buffer> [[ <Cmd>call <SID>jumpToSection(1)<CR> - nnoremap <silent> <buffer> [] <Cmd>call <SID>jumpToSection(1)<CR> - vnoremap <silent> <buffer> [] <Cmd>call <SID>jumpToSection(1)<CR> - let b:undo_ftplugin .= - \ " | silent! exe 'nunmap <buffer> ]]' | silent! exe 'vunmap <buffer> ]]'" . - \ " | silent! exe 'nunmap <buffer> ][' | silent! exe 'vunmap <buffer> ]['" . - \ " | silent! exe 'nunmap <buffer> ]]' | silent! exe 'vunmap <buffer> ]]'" . - \ " | silent! exe 'nunmap <buffer> []' | silent! exe 'vunmap <buffer> []'" + for s:mode in ["n", "o", "x"] + for s:lhs in ["]]", "]["] + execute s:mode . "noremap <silent> <buffer> " . s:lhs . " <Cmd>call <SID>jumpToSection('forward')<CR>" + let b:undo_ftplugin .= " | silent! execute '" . s:mode . "unmap <buffer> " . s:lhs . "'" + endfor + for s:lhs in ["[[", "[]"] + execute s:mode . "noremap <silent> <buffer> " . s:lhs . " <Cmd>call <SID>jumpToSection('backward')<CR>" + let b:undo_ftplugin .= " | silent! execute '" . s:mode . "unmap <buffer> " . s:lhs . "'" + endfor + endfor + unlet s:mode s:lhs endif let &cpo = s:save_cpo diff --git a/runtime/ftplugin/rust.vim b/runtime/ftplugin/rust.vim index ececcced22..7f1a86ea95 100644 --- a/runtime/ftplugin/rust.vim +++ b/runtime/ftplugin/rust.vim @@ -1,20 +1,26 @@ " Language: Rust " Description: Vim ftplugin for Rust " Maintainer: Chris Morgan <me@chrismorgan.info> -" Maintainer: Lily Ballard <lily@ballards.net> -" Last Change: June 08, 2016 -" For bugs, patches and license go to https://github.com/rust-lang/rust.vim +" Last Change: 2023-09-11 +" For bugs, patches and license go to https://github.com/rust-lang/rust.vim if exists("b:did_ftplugin") - finish + finish endif let b:did_ftplugin = 1 +" vint: -ProhibitAbbreviationOption let s:save_cpo = &cpo set cpo&vim - -augroup rust.vim -autocmd! +" vint: +ProhibitAbbreviationOption + +if get(b:, 'current_compiler', '') ==# '' + if strlen(findfile('Cargo.toml', '.;')) > 0 + compiler cargo + else + compiler rustc + endif +endif " Variables {{{1 @@ -22,13 +28,13 @@ autocmd! " comments, so we'll use that as our default, but make it easy to switch. " This does not affect indentation at all (I tested it with and without " leader), merely whether a leader is inserted by default or not. -if exists("g:rust_bang_comment_leader") && g:rust_bang_comment_leader != 0 - " Why is the `,s0:/*,mb:\ ,ex:*/` there, you ask? I don't understand why, - " but without it, */ gets indented one space even if there were no - " leaders. I'm fairly sure that's a Vim bug. - setlocal comments=s1:/*,mb:*,ex:*/,s0:/*,mb:\ ,ex:*/,:///,://!,:// +if get(g:, 'rust_bang_comment_leader', 0) + " Why is the `,s0:/*,mb:\ ,ex:*/` there, you ask? I don't understand why, + " but without it, */ gets indented one space even if there were no + " leaders. I'm fairly sure that's a Vim bug. + setlocal comments=s1:/*,mb:*,ex:*/,s0:/*,mb:\ ,ex:*/,:///,://!,:// else - setlocal comments=s0:/*!,m:\ ,ex:*/,s1:/*,mb:*,ex:*/,:///,://!,:// + setlocal comments=s0:/*!,ex:*/,s1:/*,mb:*,ex:*/,:///,://!,:// endif setlocal commentstring=//%s setlocal formatoptions-=t formatoptions+=croqnl @@ -39,13 +45,14 @@ silent! setlocal formatoptions+=j " otherwise it's better than nothing. setlocal smartindent nocindent -if !exists("g:rust_recommended_style") || g:rust_recommended_style != 0 - setlocal tabstop=4 shiftwidth=4 softtabstop=4 expandtab - setlocal textwidth=99 +if get(g:, 'rust_recommended_style', 1) + let b:rust_set_style = 1 + setlocal shiftwidth=4 softtabstop=4 expandtab + setlocal textwidth=99 endif -" This includeexpr isn't perfect, but it's a good start -setlocal includeexpr=substitute(v:fname,'::','/','g') +setlocal include=\\v^\\s*(pub\\s+)?use\\s+\\zs(\\f\|:)+ +setlocal includeexpr=rust#IncludeExpr(v:fname) setlocal suffixesadd=.rs @@ -54,51 +61,36 @@ if exists("g:ftplugin_rust_source_path") endif if exists("g:loaded_delimitMate") - if exists("b:delimitMate_excluded_regions") - let b:rust_original_delimitMate_excluded_regions = b:delimitMate_excluded_regions - endif - - let s:delimitMate_extra_excluded_regions = ',rustLifetimeCandidate,rustGenericLifetimeCandidate' - - " For this buffer, when delimitMate issues the `User delimitMate_map` - " event in the autocommand system, add the above-defined extra excluded - " regions to delimitMate's state, if they have not already been added. - autocmd User <buffer> - \ if expand('<afile>') ==# 'delimitMate_map' && match( - \ delimitMate#Get("excluded_regions"), - \ s:delimitMate_extra_excluded_regions) == -1 - \| let b:delimitMate_excluded_regions = - \ delimitMate#Get("excluded_regions") - \ . s:delimitMate_extra_excluded_regions - \|endif - - " For this buffer, when delimitMate issues the `User delimitMate_unmap` - " event in the autocommand system, delete the above-defined extra excluded - " regions from delimitMate's state (the deletion being idempotent and - " having no effect if the extra excluded regions are not present in the - " targeted part of delimitMate's state). - autocmd User <buffer> - \ if expand('<afile>') ==# 'delimitMate_unmap' - \| let b:delimitMate_excluded_regions = substitute( - \ delimitMate#Get("excluded_regions"), - \ '\C\V' . s:delimitMate_extra_excluded_regions, - \ '', 'g') - \|endif + if exists("b:delimitMate_excluded_regions") + let b:rust_original_delimitMate_excluded_regions = b:delimitMate_excluded_regions + endif + + augroup rust.vim.DelimitMate + autocmd! + + autocmd User delimitMate_map :call rust#delimitmate#onMap() + autocmd User delimitMate_unmap :call rust#delimitmate#onUnmap() + augroup END +endif + +" Integration with auto-pairs (https://github.com/jiangmiao/auto-pairs) +if exists("g:AutoPairsLoaded") && !get(g:, 'rust_keep_autopairs_default', 0) + let b:AutoPairs = {'(':')', '[':']', '{':'}','"':'"', '`':'`'} endif -if has("folding") && exists('g:rust_fold') && g:rust_fold != 0 - let b:rust_set_foldmethod=1 - setlocal foldmethod=syntax - if g:rust_fold == 2 - setlocal foldlevel< - else - setlocal foldlevel=99 - endif +if has("folding") && get(g:, 'rust_fold', 0) + let b:rust_set_foldmethod=1 + setlocal foldmethod=syntax + if g:rust_fold == 2 + setlocal foldlevel< + else + setlocal foldlevel=99 + endif endif -if has('conceal') && exists('g:rust_conceal') && g:rust_conceal != 0 - let b:rust_set_conceallevel=1 - setlocal conceallevel=2 +if has('conceal') && get(g:, 'rust_conceal', 0) + let b:rust_set_conceallevel=1 + setlocal conceallevel=2 endif " Motion Commands {{{1 @@ -126,72 +118,122 @@ command! -nargs=* -buffer RustEmitIr call rust#Emit("llvm-ir", <q-args>) command! -nargs=* -buffer RustEmitAsm call rust#Emit("asm", <q-args>) " See |:RustPlay| for docs -command! -range=% RustPlay :call rust#Play(<count>, <line1>, <line2>, <f-args>) +command! -range=% -buffer RustPlay :call rust#Play(<count>, <line1>, <line2>, <f-args>) " See |:RustFmt| for docs -command! -buffer RustFmt call rustfmt#Format() +command! -bar -buffer RustFmt call rustfmt#Format() " See |:RustFmtRange| for docs command! -range -buffer RustFmtRange call rustfmt#FormatRange(<line1>, <line2>) -" Mappings {{{1 +" See |:RustInfo| for docs +command! -bar -buffer RustInfo call rust#debugging#Info() + +" See |:RustInfoToClipboard| for docs +command! -bar -buffer RustInfoToClipboard call rust#debugging#InfoToClipboard() + +" See |:RustInfoToFile| for docs +command! -bar -nargs=1 -buffer RustInfoToFile call rust#debugging#InfoToFile(<f-args>) -" Bind ⌘R in MacVim to :RustRun -nnoremap <silent> <buffer> <D-r> :RustRun<CR> -" Bind ⌘⇧R in MacVim to :RustRun! pre-filled with the last args -nnoremap <buffer> <D-R> :RustRun! <C-r>=join(b:rust_last_rustc_args)<CR><C-\>erust#AppendCmdLine(' -- ' . join(b:rust_last_args))<CR> +" See |:RustTest| for docs +command! -buffer -nargs=* -count -bang RustTest call rust#Test(<q-mods>, <count>, <bang>0, <q-args>) if !exists("b:rust_last_rustc_args") || !exists("b:rust_last_args") - let b:rust_last_rustc_args = [] - let b:rust_last_args = [] + let b:rust_last_rustc_args = [] + let b:rust_last_args = [] endif " Cleanup {{{1 let b:undo_ftplugin = " - \ setlocal formatoptions< comments< commentstring< includeexpr< suffixesadd< - \|setlocal tabstop< shiftwidth< softtabstop< expandtab< textwidth< - \|if exists('b:rust_original_delimitMate_excluded_regions') - \|let b:delimitMate_excluded_regions = b:rust_original_delimitMate_excluded_regions - \|unlet b:rust_original_delimitMate_excluded_regions - \|else - \|unlet! b:delimitMate_excluded_regions - \|endif - \|if exists('b:rust_set_foldmethod') - \|setlocal foldmethod< foldlevel< - \|unlet b:rust_set_foldmethod - \|endif - \|if exists('b:rust_set_conceallevel') - \|setlocal conceallevel< - \|unlet b:rust_set_conceallevel - \|endif - \|unlet! b:rust_last_rustc_args b:rust_last_args - \|delcommand RustRun - \|delcommand RustExpand - \|delcommand RustEmitIr - \|delcommand RustEmitAsm - \|delcommand RustPlay - \|nunmap <buffer> <D-r> - \|nunmap <buffer> <D-R> - \|nunmap <buffer> [[ - \|nunmap <buffer> ]] - \|xunmap <buffer> [[ - \|xunmap <buffer> ]] - \|ounmap <buffer> [[ - \|ounmap <buffer> ]] - \|set matchpairs-=<:> - \" + \ setlocal formatoptions< comments< commentstring< include< includeexpr< suffixesadd< + \|if exists('b:rust_set_style') + \|setlocal tabstop< shiftwidth< softtabstop< expandtab< textwidth< + \|endif + \|if exists('b:rust_original_delimitMate_excluded_regions') + \|let b:delimitMate_excluded_regions = b:rust_original_delimitMate_excluded_regions + \|unlet b:rust_original_delimitMate_excluded_regions + \|else + \|unlet! b:delimitMate_excluded_regions + \|endif + \|if exists('b:rust_set_foldmethod') + \|setlocal foldmethod< foldlevel< + \|unlet b:rust_set_foldmethod + \|endif + \|if exists('b:rust_set_conceallevel') + \|setlocal conceallevel< + \|unlet b:rust_set_conceallevel + \|endif + \|unlet! b:rust_last_rustc_args b:rust_last_args + \|delcommand -buffer RustRun + \|delcommand -buffer RustExpand + \|delcommand -buffer RustEmitIr + \|delcommand -buffer RustEmitAsm + \|delcommand -buffer RustPlay + \|delcommand -buffer RustFmt + \|delcommand -buffer RustFmtRange + \|delcommand -buffer RustInfo + \|delcommand -buffer RustInfoToClipboard + \|delcommand -buffer RustInfoToFile + \|delcommand -buffer RustTest + \|nunmap <buffer> [[ + \|nunmap <buffer> ]] + \|xunmap <buffer> [[ + \|xunmap <buffer> ]] + \|ounmap <buffer> [[ + \|ounmap <buffer> ]] + \|setlocal matchpairs-=<:> + \|unlet b:match_skip + \" " }}}1 " Code formatting on save -if get(g:, "rustfmt_autosave", 0) - autocmd BufWritePre *.rs silent! call rustfmt#Format() -endif - +augroup rust.vim.PreWrite + autocmd! + autocmd BufWritePre *.rs silent! call rustfmt#PreWrite() augroup END +setlocal matchpairs+=<:> +" For matchit.vim (rustArrow stops `Fn() -> X` messing things up) +let b:match_skip = 's:comment\|string\|rustCharacter\|rustArrow' + +command! -buffer -nargs=+ Cargo call cargo#cmd(<q-args>) +command! -buffer -nargs=* Cbuild call cargo#build(<q-args>) +command! -buffer -nargs=* Ccheck call cargo#check(<q-args>) +command! -buffer -nargs=* Cclean call cargo#clean(<q-args>) +command! -buffer -nargs=* Cdoc call cargo#doc(<q-args>) +command! -buffer -nargs=+ Cnew call cargo#new(<q-args>) +command! -buffer -nargs=* Cinit call cargo#init(<q-args>) +command! -buffer -nargs=* Crun call cargo#run(<q-args>) +command! -buffer -nargs=* Ctest call cargo#test(<q-args>) +command! -buffer -nargs=* Cbench call cargo#bench(<q-args>) +command! -buffer -nargs=* Cupdate call cargo#update(<q-args>) +command! -buffer -nargs=* Csearch call cargo#search(<q-args>) +command! -buffer -nargs=* Cpublish call cargo#publish(<q-args>) +command! -buffer -nargs=* Cinstall call cargo#install(<q-args>) +command! -buffer -nargs=* Cruntarget call cargo#runtarget(<q-args>) + +let b:undo_ftplugin .= ' + \|delcommand -buffer Cargo + \|delcommand -buffer Cbuild + \|delcommand -buffer Ccheck + \|delcommand -buffer Cclean + \|delcommand -buffer Cdoc + \|delcommand -buffer Cnew + \|delcommand -buffer Cinit + \|delcommand -buffer Crun + \|delcommand -buffer Ctest + \|delcommand -buffer Cbench + \|delcommand -buffer Cupdate + \|delcommand -buffer Csearch + \|delcommand -buffer Cpublish + \|delcommand -buffer Cinstall + \|delcommand -buffer Cruntarget' + +" vint: -ProhibitAbbreviationOption let &cpo = s:save_cpo unlet s:save_cpo +" vint: +ProhibitAbbreviationOption -" vim: set noet sw=8 ts=8: +" vim: set et sw=4 sts=4 ts=8: diff --git a/runtime/ftplugin/zig.vim b/runtime/ftplugin/zig.vim index 2a081980cc..cfd7102b8d 100644 --- a/runtime/ftplugin/zig.vim +++ b/runtime/ftplugin/zig.vim @@ -39,7 +39,7 @@ endif let &l:define='\v(<fn>|<const>|<var>|^\s*\#\s*define)' -" Safety check: don't execute zip from current directory +" Safety check: don't execute zig from current directory if !exists('g:zig_std_dir') && exists('*json_decode') && \ executable('zig') && get(g:, 'zig_exec', get(g:, 'plugin_exec', 0)) \ && (fnamemodify(exepath("zig"), ":p:h") != s:tmp_cwd diff --git a/runtime/indent/perl.vim b/runtime/indent/perl.vim index 4c91fa1b33..a97c34da53 100644 --- a/runtime/indent/perl.vim +++ b/runtime/indent/perl.vim @@ -4,7 +4,7 @@ " Homepage: https://github.com/vim-perl/vim-perl " Bugs/requests: https://github.com/vim-perl/vim-perl/issues " License: Vim License (see :help license) -" Last Change: 2021 Sep 24 +" Last Change: 2022 Jun 14 " Suggestions and improvements by : " Aaron J. Sherman (use syntax for hints) @@ -133,6 +133,7 @@ function! GetPerlIndent() \ || synid == "perlHereDoc" \ || synid == "perlBraces" \ || synid == "perlStatementIndirObj" + \ || synid == "perlSubDeclaration" \ || synid =~ "^perlFiledescStatement" \ || synid =~ '^perl\(Sub\|Block\|Package\)Fold' let brace = strpart(line, bracepos, 1) diff --git a/runtime/indent/rust.vim b/runtime/indent/rust.vim index b27d93c3a2..7c055ec739 100644 --- a/runtime/indent/rust.vim +++ b/runtime/indent/rust.vim @@ -1,27 +1,26 @@ " Vim indent file " Language: Rust " Author: Chris Morgan <me@chrismorgan.info> -" Last Change: 2017 Jun 13 -" 2023 Aug 28 by Vim Project (undo_indent) +" Last Change: 2023-09-11 " For bugs, patches and license go to https://github.com/rust-lang/rust.vim " Only load this indent file when no other was loaded. if exists("b:did_indent") - finish + finish endif let b:did_indent = 1 setlocal cindent -setlocal cinoptions=L0,(0,Ws,J1,j1 -setlocal cinkeys=0{,0},!^F,o,O,0[,0] +setlocal cinoptions=L0,(s,Ws,J1,j1,m1 +setlocal cinkeys=0{,0},!^F,o,O,0[,0],0(,0) " Don't think cinwords will actually do anything at all... never mind -setlocal cinwords=for,if,else,while,loop,impl,mod,unsafe,trait,struct,enum,fn,extern +setlocal cinwords=for,if,else,while,loop,impl,mod,unsafe,trait,struct,enum,fn,extern,macro " Some preliminary settings setlocal nolisp " Make sure lisp indenting doesn't supersede us setlocal autoindent " indentexpr isn't much help otherwise " Also do indentkeys, otherwise # gets shoved to column 0 :-/ -setlocal indentkeys=0{,0},!^F,o,O,0[,0] +setlocal indentkeys=0{,0},!^F,o,O,0[,0],0(,0) setlocal indentexpr=GetRustIndent(v:lnum) @@ -29,204 +28,259 @@ let b:undo_indent = "setlocal cindent< cinoptions< cinkeys< cinwords< lisp< auto " Only define the function once. if exists("*GetRustIndent") - finish + finish endif +" vint: -ProhibitAbbreviationOption let s:save_cpo = &cpo set cpo&vim +" vint: +ProhibitAbbreviationOption " Come here when loading the script the first time. function! s:get_line_trimmed(lnum) - " Get the line and remove a trailing comment. - " Use syntax highlighting attributes when possible. - " NOTE: this is not accurate; /* */ or a line continuation could trick it - let line = getline(a:lnum) - let line_len = strlen(line) - if has('syntax_items') - " If the last character in the line is a comment, do a binary search for - " the start of the comment. synID() is slow, a linear search would take - " too long on a long line. - if synIDattr(synID(a:lnum, line_len, 1), "name") =~ 'Comment\|Todo' - let min = 1 - let max = line_len - while min < max - let col = (min + max) / 2 - if synIDattr(synID(a:lnum, col, 1), "name") =~ 'Comment\|Todo' - let max = col - else - let min = col + 1 - endif - endwhile - let line = strpart(line, 0, min - 1) - endif - return substitute(line, "\s*$", "", "") - else - " Sorry, this is not complete, nor fully correct (e.g. string "//"). - " Such is life. - return substitute(line, "\s*//.*$", "", "") - endif + " Get the line and remove a trailing comment. + " Use syntax highlighting attributes when possible. + " NOTE: this is not accurate; /* */ or a line continuation could trick it + let line = getline(a:lnum) + let line_len = strlen(line) + if has('syntax_items') + " If the last character in the line is a comment, do a binary search for + " the start of the comment. synID() is slow, a linear search would take + " too long on a long line. + if synIDattr(synID(a:lnum, line_len, 1), "name") =~? 'Comment\|Todo' + let min = 1 + let max = line_len + while min < max + let col = (min + max) / 2 + if synIDattr(synID(a:lnum, col, 1), "name") =~? 'Comment\|Todo' + let max = col + else + let min = col + 1 + endif + endwhile + let line = strpart(line, 0, min - 1) + endif + return substitute(line, "\s*$", "", "") + else + " Sorry, this is not complete, nor fully correct (e.g. string "//"). + " Such is life. + return substitute(line, "\s*//.*$", "", "") + endif endfunction function! s:is_string_comment(lnum, col) - if has('syntax_items') - for id in synstack(a:lnum, a:col) - let synname = synIDattr(id, "name") - if synname == "rustString" || synname =~ "^rustComment" - return 1 - endif - endfor - else - " without syntax, let's not even try - return 0 - endif + if has('syntax_items') + for id in synstack(a:lnum, a:col) + let synname = synIDattr(id, "name") + if synname ==# "rustString" || synname =~# "^rustComment" + return 1 + endif + endfor + else + " without syntax, let's not even try + return 0 + endif endfunction +if exists('*shiftwidth') + function! s:shiftwidth() + return shiftwidth() + endfunc +else + function! s:shiftwidth() + return &shiftwidth + endfunc +endif + function GetRustIndent(lnum) + " Starting assumption: cindent (called at the end) will do it right + " normally. We just want to fix up a few cases. + + let line = getline(a:lnum) + + if has('syntax_items') + let synname = synIDattr(synID(a:lnum, 1, 1), "name") + if synname ==# "rustString" + " If the start of the line is in a string, don't change the indent + return -1 + elseif synname =~? '\(Comment\|Todo\)' + \ && line !~# '^\s*/\*' " not /* opening line + if synname =~? "CommentML" " multi-line + if line !~# '^\s*\*' && getline(a:lnum - 1) =~# '^\s*/\*' + " This is (hopefully) the line after a /*, and it has no + " leader, so the correct indentation is that of the + " previous line. + return GetRustIndent(a:lnum - 1) + endif + endif + " If it's in a comment, let cindent take care of it now. This is + " for cases like "/*" where the next line should start " * ", not + " "* " as the code below would otherwise cause for module scope + " Fun fact: " /*\n*\n*/" takes two calls to get right! + return cindent(a:lnum) + endif + endif + + " cindent gets second and subsequent match patterns/struct members wrong, + " as it treats the comma as indicating an unfinished statement:: + " + " match a { + " b => c, + " d => e, + " f => g, + " }; + + " Search backwards for the previous non-empty line. + let prevlinenum = prevnonblank(a:lnum - 1) + let prevline = s:get_line_trimmed(prevlinenum) + while prevlinenum > 1 && prevline !~# '[^[:blank:]]' + let prevlinenum = prevnonblank(prevlinenum - 1) + let prevline = s:get_line_trimmed(prevlinenum) + endwhile + + " A standalone '{', '}', or 'where' + let l:standalone_open = line =~# '\V\^\s\*{\s\*\$' + let l:standalone_close = line =~# '\V\^\s\*}\s\*\$' + let l:standalone_where = line =~# '\V\^\s\*where\s\*\$' + if l:standalone_open || l:standalone_close || l:standalone_where + " ToDo: we can search for more items than 'fn' and 'if'. + let [l:found_line, l:col, l:submatch] = + \ searchpos('\<\(fn\)\|\(if\)\>', 'bnWp') + if l:found_line !=# 0 + " Now we count the number of '{' and '}' in between the match + " locations and the current line (there is probably a better + " way to compute this). + let l:i = l:found_line + let l:search_line = strpart(getline(l:i), l:col - 1) + let l:opens = 0 + let l:closes = 0 + while l:i < a:lnum + let l:search_line2 = substitute(l:search_line, '\V{', '', 'g') + let l:opens += strlen(l:search_line) - strlen(l:search_line2) + let l:search_line3 = substitute(l:search_line2, '\V}', '', 'g') + let l:closes += strlen(l:search_line2) - strlen(l:search_line3) + let l:i += 1 + let l:search_line = getline(l:i) + endwhile + if l:standalone_open || l:standalone_where + if l:opens ==# l:closes + return indent(l:found_line) + endif + else + " Expect to find just one more close than an open + if l:opens ==# l:closes + 1 + return indent(l:found_line) + endif + endif + endif + endif + + " A standalone 'where' adds a shift. + let l:standalone_prevline_where = prevline =~# '\V\^\s\*where\s\*\$' + if l:standalone_prevline_where + return indent(prevlinenum) + 4 + endif + + " Handle where clauses nicely: subsequent values should line up nicely. + if prevline[len(prevline) - 1] ==# "," + \ && prevline =~# '^\s*where\s' + return indent(prevlinenum) + 6 + endif - " Starting assumption: cindent (called at the end) will do it right - " normally. We just want to fix up a few cases. - - let line = getline(a:lnum) - - if has('syntax_items') - let synname = synIDattr(synID(a:lnum, 1, 1), "name") - if synname == "rustString" - " If the start of the line is in a string, don't change the indent - return -1 - elseif synname =~ '\(Comment\|Todo\)' - \ && line !~ '^\s*/\*' " not /* opening line - if synname =~ "CommentML" " multi-line - if line !~ '^\s*\*' && getline(a:lnum - 1) =~ '^\s*/\*' - " This is (hopefully) the line after a /*, and it has no - " leader, so the correct indentation is that of the - " previous line. - return GetRustIndent(a:lnum - 1) - endif - endif - " If it's in a comment, let cindent take care of it now. This is - " for cases like "/*" where the next line should start " * ", not - " "* " as the code below would otherwise cause for module scope - " Fun fact: " /*\n*\n*/" takes two calls to get right! - return cindent(a:lnum) - endif - endif - - " cindent gets second and subsequent match patterns/struct members wrong, - " as it treats the comma as indicating an unfinished statement:: - " - " match a { - " b => c, - " d => e, - " f => g, - " }; - - " Search backwards for the previous non-empty line. - let prevlinenum = prevnonblank(a:lnum - 1) - let prevline = s:get_line_trimmed(prevlinenum) - while prevlinenum > 1 && prevline !~ '[^[:blank:]]' - let prevlinenum = prevnonblank(prevlinenum - 1) - let prevline = s:get_line_trimmed(prevlinenum) - endwhile - - " Handle where clauses nicely: subsequent values should line up nicely. - if prevline[len(prevline) - 1] == "," - \ && prevline =~# '^\s*where\s' - return indent(prevlinenum) + 6 - endif - - "match newline after struct with generic bound like - "struct SomeThing<T> - "| <-- newline indent should same as prevline - if prevline[len(prevline) - 1] == ">" - \ && prevline =~# "\s*struct.*>$" - return indent(prevlinenum) - endif - - "match newline after where like: - "struct SomeThing<T> - "where - " T: Display, - if prevline =~# '^\s*where$' - return indent(prevlinenum) + 4 - endif - - if prevline[len(prevline) - 1] == "," - \ && s:get_line_trimmed(a:lnum) !~ '^\s*[\[\]{}]' - \ && prevline !~ '^\s*fn\s' - \ && prevline !~ '([^()]\+,$' - \ && s:get_line_trimmed(a:lnum) !~ '^\s*\S\+\s*=>' - " Oh ho! The previous line ended in a comma! I bet cindent will try to - " take this too far... For now, let's normally use the previous line's - " indent. - - " One case where this doesn't work out is where *this* line contains - " square or curly brackets; then we normally *do* want to be indenting - " further. - " - " Another case where we don't want to is one like a function - " definition with arguments spread over multiple lines: - " - " fn foo(baz: Baz, - " baz: Baz) // <-- cindent gets this right by itself - " - " Another case is similar to the previous, except calling a function - " instead of defining it, or any conditional expression that leaves - " an open paren: - " - " foo(baz, - " baz); - " - " if baz && (foo || - " bar) { - " - " Another case is when the current line is a new match arm. - " - " There are probably other cases where we don't want to do this as - " well. Add them as needed. - return indent(prevlinenum) - endif - - if !has("patch-7.4.355") - " cindent before 7.4.355 doesn't do the module scope well at all; e.g.:: - " - " static FOO : &'static [bool] = [ - " true, - " false, - " false, - " true, - " ]; - " - " uh oh, next statement is indented further! - - " Note that this does *not* apply the line continuation pattern properly; - " that's too hard to do correctly for my liking at present, so I'll just - " start with these two main cases (square brackets and not returning to - " column zero) - - call cursor(a:lnum, 1) - if searchpair('{\|(', '', '}\|)', 'nbW', - \ 's:is_string_comment(line("."), col("."))') == 0 - if searchpair('\[', '', '\]', 'nbW', - \ 's:is_string_comment(line("."), col("."))') == 0 - " Global scope, should be zero - return 0 - else - " At the module scope, inside square brackets only - "if getline(a:lnum)[0] == ']' || search('\[', '', '\]', 'nW') == a:lnum - if line =~ "^\\s*]" - " It's the closing line, dedent it - return 0 - else - return shiftwidth() - endif - endif - endif - endif - - " Fall back on cindent, which does it mostly right - return cindent(a:lnum) + let l:last_prevline_character = prevline[len(prevline) - 1] + + " A line that ends with '.<expr>;' is probably an end of a long list + " of method operations. + if prevline =~# '\V\^\s\*.' && l:last_prevline_character ==# ';' + call cursor(a:lnum - 1, 1) + let l:scope_start = searchpair('{\|(', '', '}\|)', 'nbW', + \ 's:is_string_comment(line("."), col("."))') + if l:scope_start != 0 && l:scope_start < a:lnum + return indent(l:scope_start) + 4 + endif + endif + + if l:last_prevline_character ==# "," + \ && s:get_line_trimmed(a:lnum) !~# '^\s*[\[\]{})]' + \ && prevline !~# '^\s*fn\s' + \ && prevline !~# '([^()]\+,$' + \ && s:get_line_trimmed(a:lnum) !~# '^\s*\S\+\s*=>' + " Oh ho! The previous line ended in a comma! I bet cindent will try to + " take this too far... For now, let's normally use the previous line's + " indent. + + " One case where this doesn't work out is where *this* line contains + " square or curly brackets; then we normally *do* want to be indenting + " further. + " + " Another case where we don't want to is one like a function + " definition with arguments spread over multiple lines: + " + " fn foo(baz: Baz, + " baz: Baz) // <-- cindent gets this right by itself + " + " Another case is similar to the previous, except calling a function + " instead of defining it, or any conditional expression that leaves + " an open paren: + " + " foo(baz, + " baz); + " + " if baz && (foo || + " bar) { + " + " Another case is when the current line is a new match arm. + " + " There are probably other cases where we don't want to do this as + " well. Add them as needed. + return indent(prevlinenum) + endif + + if !has("patch-7.4.355") + " cindent before 7.4.355 doesn't do the module scope well at all; e.g.:: + " + " static FOO : &'static [bool] = [ + " true, + " false, + " false, + " true, + " ]; + " + " uh oh, next statement is indented further! + + " Note that this does *not* apply the line continuation pattern properly; + " that's too hard to do correctly for my liking at present, so I'll just + " start with these two main cases (square brackets and not returning to + " column zero) + + call cursor(a:lnum, 1) + if searchpair('{\|(', '', '}\|)', 'nbW', + \ 's:is_string_comment(line("."), col("."))') == 0 + if searchpair('\[', '', '\]', 'nbW', + \ 's:is_string_comment(line("."), col("."))') == 0 + " Global scope, should be zero + return 0 + else + " At the module scope, inside square brackets only + "if getline(a:lnum)[0] == ']' || search('\[', '', '\]', 'nW') == a:lnum + if line =~# "^\\s*]" + " It's the closing line, dedent it + return 0 + else + return &shiftwidth + endif + endif + endif + endif + + " Fall back on cindent, which does it mostly right + return cindent(a:lnum) endfunction +" vint: -ProhibitAbbreviationOption let &cpo = s:save_cpo unlet s:save_cpo +" vint: +ProhibitAbbreviationOption + +" vim: set et sw=4 sts=4 ts=8: diff --git a/runtime/lua/coxpcall.lua b/runtime/lua/coxpcall.lua new file mode 100644 index 0000000000..6b179f1ef0 --- /dev/null +++ b/runtime/lua/coxpcall.lua @@ -0,0 +1,108 @@ +------------------------------------------------------------------------------- +-- Coroutine safe xpcall and pcall versions +-- +-- Encapsulates the protected calls with a coroutine based loop, so errors can +-- be dealed without the usual Lua 5.x pcall/xpcall issues with coroutines +-- yielding inside the call to pcall or xpcall. +-- +-- Authors: Roberto Ierusalimschy and Andre Carregal +-- Contributors: Thomas Harning Jr., Ignacio Burgueño, Fabio Mascarenhas +-- +-- Copyright 2005 - Kepler Project +-- +-- $Id: coxpcall.lua,v 1.13 2008/05/19 19:20:02 mascarenhas Exp $ +------------------------------------------------------------------------------- + +------------------------------------------------------------------------------- +-- Checks if (x)pcall function is coroutine safe +------------------------------------------------------------------------------- +local function isCoroutineSafe(func) + local co = coroutine.create(function() + return func(coroutine.yield, function() end) + end) + + coroutine.resume(co) + return coroutine.resume(co) +end + +-- No need to do anything if pcall and xpcall are already safe. +if isCoroutineSafe(pcall) and isCoroutineSafe(xpcall) then + copcall = pcall + coxpcall = xpcall + return { pcall = pcall, xpcall = xpcall, running = coroutine.running } +end + +------------------------------------------------------------------------------- +-- Implements xpcall with coroutines +------------------------------------------------------------------------------- +local performResume, handleReturnValue +local oldpcall, oldxpcall = pcall, xpcall +local pack = table.pack or function(...) return {n = select("#", ...), ...} end +local unpack = table.unpack or unpack +local running = coroutine.running +local coromap = setmetatable({}, { __mode = "k" }) + +function handleReturnValue(err, co, status, ...) + if not status then + return false, err(debug.traceback(co, (...)), ...) + end + if coroutine.status(co) == 'suspended' then + return performResume(err, co, coroutine.yield(...)) + else + return true, ... + end +end + +function performResume(err, co, ...) + return handleReturnValue(err, co, coroutine.resume(co, ...)) +end + +local function id(trace, ...) + return trace +end + +function coxpcall(f, err, ...) + local current = running() + if not current then + if err == id then + return oldpcall(f, ...) + else + if select("#", ...) > 0 then + local oldf, params = f, pack(...) + f = function() return oldf(unpack(params, 1, params.n)) end + end + return oldxpcall(f, err) + end + else + local res, co = oldpcall(coroutine.create, f) + if not res then + local newf = function(...) return f(...) end + co = coroutine.create(newf) + end + coromap[co] = current + return performResume(err, co, ...) + end +end + +local function corunning(coro) + if coro ~= nil then + assert(type(coro)=="thread", "Bad argument; expected thread, got: "..type(coro)) + else + coro = running() + end + while coromap[coro] do + coro = coromap[coro] + end + if coro == "mainthread" then return nil end + return coro +end + +------------------------------------------------------------------------------- +-- Implements pcall with coroutines +------------------------------------------------------------------------------- + +function copcall(f, ...) + return coxpcall(f, id, ...) +end + +return { pcall = copcall, xpcall = coxpcall, running = corunning } diff --git a/runtime/lua/vim/F.lua b/runtime/lua/vim/F.lua index 16c834b371..5ed60ca8ab 100644 --- a/runtime/lua/vim/F.lua +++ b/runtime/lua/vim/F.lua @@ -5,13 +5,14 @@ local F = {} --- If all arguments are nil, returns nil. --- --- Examples: ---- <pre> +--- +--- ```lua --- local a = nil --- local b = nil --- local c = 42 --- local d = true --- assert(vim.F.if_nil(a, b, c, d) == 42) ---- </pre> +--- ``` --- ---@param ... any ---@return any diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 68992a16bb..0215cae0cb 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -70,23 +70,24 @@ vim.log = { --- Run a system command --- --- Examples: ---- <pre>lua --- ---- local on_exit = function(obj) ---- print(obj.code) ---- print(obj.signal) ---- print(obj.stdout) ---- print(obj.stderr) ---- end +--- ```lua --- ---- -- Run asynchronously ---- vim.system({'echo', 'hello'}, { text = true }, on_exit) +--- local on_exit = function(obj) +--- print(obj.code) +--- print(obj.signal) +--- print(obj.stdout) +--- print(obj.stderr) +--- end --- ---- -- Run synchronously ---- local obj = vim.system({'echo', 'hello'}, { text = true }):wait() ---- -- { code = 0, signal = 0, stdout = 'hello', stderr = '' } +--- -- Run asynchronously +--- vim.system({'echo', 'hello'}, { text = true }, on_exit) --- ---- </pre> +--- -- Run synchronously +--- local obj = vim.system({'echo', 'hello'}, { text = true }):wait() +--- -- { code = 0, signal = 0, stdout = 'hello', stderr = '' } +--- +--- ``` --- --- See |uv.spawn()| for more details. --- @@ -104,7 +105,7 @@ vim.log = { --- Handle output from stdout. When passed as a function must have the signature `fun(err: string, data: string)`. --- Defaults to `true` --- - stderr: (boolean|function) ---- Handle output from stdout. When passed as a function must have the signature `fun(err: string, data: string)`. +--- Handle output from stderr. When passed as a function must have the signature `fun(err: string, data: string)`. --- Defaults to `true`. --- - text: (boolean) Handle stdout and stderr as text. Replaces `\r\n` with `\n`. --- - timeout: (integer) Run the command with a time limit. Upon timeout the process is sent the @@ -200,7 +201,8 @@ do --- (such as the |TUI|) pastes text into the editor. --- --- Example: To remove ANSI color codes when pasting: - --- <pre>lua + --- + --- ```lua --- vim.paste = (function(overridden) --- return function(lines, phase) --- for i,line in ipairs(lines) do @@ -210,7 +212,7 @@ do --- overridden(lines, phase) --- end --- end)(vim.paste) - --- </pre> + --- ``` --- ---@see |paste| ---@alias paste_phase -1 | 1 | 2 | 3 @@ -361,32 +363,33 @@ local VIM_CMD_ARG_MAX = 20 --- command. --- --- Example: ---- <pre>lua ---- vim.cmd('echo 42') ---- vim.cmd([[ ---- augroup My_group ---- autocmd! ---- autocmd FileType c setlocal cindent ---- augroup END ---- ]]) --- ---- -- Ex command :echo "foo" ---- -- Note string literals need to be double quoted. ---- vim.cmd('echo "foo"') ---- vim.cmd { cmd = 'echo', args = { '"foo"' } } ---- vim.cmd.echo({ args = { '"foo"' } }) ---- vim.cmd.echo('"foo"') +--- ```lua +--- vim.cmd('echo 42') +--- vim.cmd([[ +--- augroup My_group +--- autocmd! +--- autocmd FileType c setlocal cindent +--- augroup END +--- ]]) +--- +--- -- Ex command :echo "foo" +--- -- Note string literals need to be double quoted. +--- vim.cmd('echo "foo"') +--- vim.cmd { cmd = 'echo', args = { '"foo"' } } +--- vim.cmd.echo({ args = { '"foo"' } }) +--- vim.cmd.echo('"foo"') --- ---- -- Ex command :write! myfile.txt ---- vim.cmd('write! myfile.txt') ---- vim.cmd { cmd = 'write', args = { "myfile.txt" }, bang = true } ---- vim.cmd.write { args = { "myfile.txt" }, bang = true } ---- vim.cmd.write { "myfile.txt", bang = true } +--- -- Ex command :write! myfile.txt +--- vim.cmd('write! myfile.txt') +--- vim.cmd { cmd = 'write', args = { "myfile.txt" }, bang = true } +--- vim.cmd.write { args = { "myfile.txt" }, bang = true } +--- vim.cmd.write { "myfile.txt", bang = true } --- ---- -- Ex command :colorscheme blue ---- vim.cmd('colorscheme blue') ---- vim.cmd.colorscheme('blue') ---- </pre> +--- -- Ex command :colorscheme blue +--- vim.cmd('colorscheme blue') +--- vim.cmd.colorscheme('blue') +--- ``` --- ---@param command string|table Command(s) to execute. --- If a string, executes multiple lines of Vim script at once. In this @@ -871,9 +874,10 @@ end --- "Pretty prints" the given arguments and returns them unmodified. --- --- Example: ---- <pre>lua ---- local hl_normal = vim.print(vim.api.nvim_get_hl_by_name('Normal', true)) ---- </pre> +--- +--- ```lua +--- local hl_normal = vim.print(vim.api.nvim_get_hl_by_name('Normal', true)) +--- ``` --- --- @see |vim.inspect()| --- @see |:=| @@ -900,10 +904,12 @@ end --- Translate keycodes. --- --- Example: ---- <pre>lua ---- local k = vim.keycode ---- vim.g.mapleader = k'<bs>' ---- </pre> +--- +--- ```lua +--- local k = vim.keycode +--- vim.g.mapleader = k'<bs>' +--- ``` +--- --- @param str string String to be converted. --- @return string --- @see |nvim_replace_termcodes()| diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index dd67fdb38b..6573c68493 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -7,6 +7,13 @@ vim.api = {} --- @private --- @param buffer integer +--- @param keys boolean +--- @param dot boolean +--- @return string +function vim.api.nvim__buf_debug_extmarks(buffer, keys, dot) end + +--- @private +--- @param buffer integer --- @param first integer --- @param last integer function vim.api.nvim__buf_redraw_range(buffer, first, last) end @@ -120,11 +127,16 @@ function vim.api.nvim__unpack(str) end function vim.api.nvim_buf_add_highlight(buffer, ns_id, hl_group, line, col_start, col_end) end --- Activates buffer-update events on a channel, or as Lua callbacks. ---- Example (Lua): capture buffer updates in a global `events` variable (use "vim.print(events)" to see its contents): +--- Example (Lua): capture buffer updates in a global `events` variable (use +--- "vim.print(events)" to see its contents): +--- --- ```lua ---- events = {} ---- vim.api.nvim_buf_attach(0, false, { ---- on_lines=function(...) table.insert(events, {...}) end}) +--- events = {} +--- vim.api.nvim_buf_attach(0, false, { +--- on_lines = function(...) +--- table.insert(events, {...}) +--- end, +--- }) --- ``` --- --- @param buffer integer Buffer handle, or 0 for current buffer @@ -307,26 +319,32 @@ function vim.api.nvim_buf_get_extmark_by_id(buffer, ns_id, id, opts) end --- Region can be given as (row,col) tuples, or valid extmark ids (whose --- positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1) --- respectively, thus the following are equivalent: +--- --- ```lua ---- vim.api.nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) ---- vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {}) +--- vim.api.nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) +--- vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {}) --- ``` +--- --- If `end` is less than `start`, traversal works backwards. (Useful with --- `limit`, to get the first marks prior to a given position.) +--- Note: when using extmark ranges (marks with a end_row/end_col position) +--- the `overlap` option might be useful. Otherwise only the start position of +--- an extmark will be considered. --- Example: +--- --- ```lua ---- local api = vim.api ---- local pos = api.nvim_win_get_cursor(0) ---- local ns = api.nvim_create_namespace('my-plugin') ---- -- Create new extmark at line 1, column 1. ---- local m1 = api.nvim_buf_set_extmark(0, ns, 0, 0, {}) ---- -- Create new extmark at line 3, column 1. ---- local m2 = api.nvim_buf_set_extmark(0, ns, 2, 0, {}) ---- -- Get extmarks only from line 3. ---- local ms = api.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {}) ---- -- Get all marks in this buffer + namespace. ---- local all = api.nvim_buf_get_extmarks(0, ns, 0, -1, {}) ---- vim.print(ms) +--- local api = vim.api +--- local pos = api.nvim_win_get_cursor(0) +--- local ns = api.nvim_create_namespace('my-plugin') +--- -- Create new extmark at line 1, column 1. +--- local m1 = api.nvim_buf_set_extmark(0, ns, 0, 0, {}) +--- -- Create new extmark at line 3, column 1. +--- local m2 = api.nvim_buf_set_extmark(0, ns, 2, 0, {}) +--- -- Get extmarks only from line 3. +--- local ms = api.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {}) +--- -- Get all marks in this buffer + namespace. +--- local all = api.nvim_buf_get_extmarks(0, ns, 0, -1, {}) +--- vim.print(ms) --- ``` --- --- @param buffer integer Buffer handle, or 0 for current buffer @@ -337,11 +355,13 @@ function vim.api.nvim_buf_get_extmark_by_id(buffer, ns_id, id, opts) end --- @param end_ any End of range (inclusive): a 0-indexed (row, col) or valid --- extmark id (whose position defines the bound). --- `api-indexing` ---- @param opts table<string,any> Optional parameters. Keys: +--- @param opts vim.api.keyset.get_extmarks Optional parameters. Keys: --- • limit: Maximum number of marks to return --- • details: Whether to include the details dict --- • hl_name: Whether to include highlight group name instead --- of id, true if omitted +--- • overlap: Also include marks which overlap the range, even +--- if their start position is less than `start` --- • type: Filter marks by type: "highlight", "sign", --- "virt_text" and "virt_lines" --- @return any[] @@ -457,6 +477,10 @@ function vim.api.nvim_buf_line_count(buffer) end --- waiting for the return value.) --- Using the optional arguments, it is possible to use this to highlight a --- range of text, and also to associate virtual text to the mark. +--- If present, the position defined by `end_col` and `end_row` should be +--- after the start position in order for the extmark to cover a range. An +--- earlier end position is not an error, but then it behaves like an empty +--- range (no highlighting). --- --- @param buffer integer Buffer handle, or 0 for current buffer --- @param ns_id integer Namespace id from `nvim_create_namespace()` @@ -621,6 +645,7 @@ function vim.api.nvim_buf_set_option(buffer, name, value) end --- range, use `replacement = {}`. --- Prefer `nvim_buf_set_lines()` if you are only adding or deleting entire --- lines. +--- Prefer `nvim_put()` if you want to insert text at the cursor position. --- --- @param buffer integer Buffer handle, or 0 for current buffer --- @param start_row integer First line index @@ -758,6 +783,7 @@ function vim.api.nvim_command_output(command) end --- Create or get an autocommand group `autocmd-groups`. --- To get an existing group id, do: +--- --- ```lua --- local id = vim.api.nvim_create_augroup("MyGroup", { --- clear = false @@ -773,6 +799,7 @@ function vim.api.nvim_create_augroup(name, opts) end --- Creates an `autocommand` event handler, defined by `callback` (Lua function or Vimscript function name string) or `command` (Ex command string). --- Example using Lua callback: +--- --- ```lua --- vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { --- pattern = {"*.c", "*.h"}, @@ -781,17 +808,21 @@ function vim.api.nvim_create_augroup(name, opts) end --- end --- }) --- ``` +--- --- Example using an Ex command as the handler: +--- --- ```lua --- vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { --- pattern = {"*.c", "*.h"}, --- command = "echo 'Entering a C or C++ file'", --- }) --- ``` ---- Note: `pattern` is NOT automatically expanded (unlike with `:autocmd`), thus names like ---- "$HOME" and "~" must be expanded explicitly: +--- +--- Note: `pattern` is NOT automatically expanded (unlike with `:autocmd`), +--- thus names like "$HOME" and "~" must be expanded explicitly: +--- --- ```lua ---- pattern = vim.fn.expand("~") .. "/some/path/*.py" +--- pattern = vim.fn.expand("~") .. "/some/path/*.py" --- ``` --- --- @param event any (string|array) Event(s) that will trigger the handler @@ -853,10 +884,11 @@ function vim.api.nvim_create_namespace(name) end --- Creates a global `user-commands` command. --- For Lua usage see `lua-guide-commands-create`. --- Example: +--- --- ```vim ---- :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {'bang': v:true}) ---- :SayHello ---- Hello world! +--- :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {'bang': v:true}) +--- :SayHello +--- Hello world! --- ``` --- --- @param name string Name of the new user command. Must begin with an uppercase @@ -1067,6 +1099,7 @@ function vim.api.nvim_execute_lua(code, args) end --- with escape_ks=false) to replace `keycodes`, then pass the result to --- nvim_feedkeys(). --- Example: +--- --- ```vim --- :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true) --- :call nvim_feedkeys(key, 'n', v:false) @@ -1094,19 +1127,21 @@ function vim.api.nvim_get_api_info() end --- Get all autocommands that match the corresponding {opts}. --- These examples will get autocommands matching ALL the given criteria: +--- --- ```lua ---- -- Matches all criteria ---- autocommands = vim.api.nvim_get_autocmds({ ---- group = "MyGroup", ---- event = {"BufEnter", "BufWinEnter"}, ---- pattern = {"*.c", "*.h"} ---- }) ---- ---- -- All commands from one group ---- autocommands = vim.api.nvim_get_autocmds({ ---- group = "MyGroup", ---- }) +--- -- Matches all criteria +--- autocommands = vim.api.nvim_get_autocmds({ +--- group = "MyGroup", +--- event = {"BufEnter", "BufWinEnter"}, +--- pattern = {"*.c", "*.h"} +--- }) +--- +--- -- All commands from one group +--- autocommands = vim.api.nvim_get_autocmds({ +--- group = "MyGroup", +--- }) --- ``` +--- --- NOTE: When multiple patterns or events are provided, it will find all the --- autocommands that match any combination of them. --- @@ -1132,6 +1167,7 @@ function vim.api.nvim_get_chan_info(chan) end --- Returns the 24-bit RGB value of a `nvim_get_color_map()` color name or --- "#rrggbb" hexadecimal string. --- Example: +--- --- ```vim --- :echo nvim_get_color_by_name("Pink") --- :echo nvim_get_color_by_name("#cbcbcb") @@ -1193,6 +1229,8 @@ function vim.api.nvim_get_current_win() end --- • id: (integer) Get a highlight definition by id. --- • link: (boolean, default true) Show linked group name --- instead of effective definition `:hi-link`. +--- • create: (boolean, default true) When highlight group +--- doesn't exist create it. --- @return table<string,any> function vim.api.nvim_get_hl(ns_id, opts) end @@ -1452,14 +1490,18 @@ function vim.api.nvim_open_term(buffer, opts) end --- could let floats hover outside of the main window like a tooltip, but this --- should not be used to specify arbitrary WM screen positions. --- Example (Lua): window-relative float +--- --- ```lua --- vim.api.nvim_open_win(0, false, --- {relative='win', row=3, col=3, width=12, height=3}) --- ``` +--- --- Example (Lua): buffer-relative float (travels as buffer is scrolled) +--- --- ```lua --- vim.api.nvim_open_win(0, false, --- {relative='win', width=12, height=3, bufpos={100,10}}) +--- }) --- ``` --- --- @param buffer integer Buffer to display, or 0 for current buffer @@ -1774,7 +1816,9 @@ function vim.api.nvim_set_current_win(window) end --- • on_buf: called for each buffer being redrawn (before window --- callbacks) ["buf", bufnr, tick] --- • on_win: called when starting to redraw a specific window. ---- ["win", winid, bufnr, topline, botline_guess] +--- botline_guess is an approximation that does not exceed the +--- last line number. ["win", winid, bufnr, topline, +--- botline_guess] --- • on_line: called for each buffer line being redrawn. (The --- interaction with fold lines is subject to change) ["win", --- winid, bufnr, row] @@ -1835,10 +1879,13 @@ function vim.api.nvim_set_hl_ns_fast(ns_id) end --- Unlike `:map`, leading/trailing whitespace is accepted as part of the --- {lhs} or {rhs}. Empty {rhs} is `<Nop>`. `keycodes` are replaced as usual. --- Example: +--- --- ```vim --- call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true}) --- ``` +--- --- is equivalent to: +--- --- ```vim --- nmap <nowait> <Space><NL> <Nop> --- ``` diff --git a/runtime/lua/vim/_meta/api_keysets.lua b/runtime/lua/vim/_meta/api_keysets.lua index 08c29ebe7a..4d08563ce2 100644 --- a/runtime/lua/vim/_meta/api_keysets.lua +++ b/runtime/lua/vim/_meta/api_keysets.lua @@ -122,10 +122,18 @@ error('Cannot require a meta file') --- @class vim.api.keyset.get_commands --- @field builtin? boolean +--- @class vim.api.keyset.get_extmarks +--- @field limit? integer +--- @field details? boolean +--- @field hl_name? boolean +--- @field overlap? boolean +--- @field type? string + --- @class vim.api.keyset.get_highlight --- @field id? integer --- @field name? string --- @field link? boolean +--- @field create? boolean --- @class vim.api.keyset.highlight --- @field bold? boolean diff --git a/runtime/lua/vim/_meta/builtin.lua b/runtime/lua/vim/_meta/builtin.lua index d7b76a803c..0a6dd3e151 100644 --- a/runtime/lua/vim/_meta/builtin.lua +++ b/runtime/lua/vim/_meta/builtin.lua @@ -131,7 +131,8 @@ function vim.str_utf_pos(str) end --- The result can be added to {index} to get the starting byte of a character. --- --- Examples: ---- <pre>lua +--- +--- ```lua --- -- The character 'æ' is stored as the bytes '\xc3\xa6' (using UTF-8) --- --- -- Returns 0 because the index is pointing at the first byte of a character @@ -139,7 +140,7 @@ function vim.str_utf_pos(str) end --- --- -- Returns -1 because the index is pointing at the second byte of a character --- vim.str_utf_start('æ', 2) ---- </pre> +--- ``` --- --- @param str string --- @param index number @@ -150,7 +151,8 @@ function vim.str_utf_start(str, index) end --- to. --- --- Examples: ---- <pre>lua +--- +--- ```lua --- -- The character 'æ' is stored as the bytes '\xc3\xa6' (using UTF-8) --- --- -- Returns 0 because the index is pointing at the last byte of a character @@ -158,7 +160,7 @@ function vim.str_utf_start(str, index) end --- --- -- Returns 1 because the index is pointing at the penultimate byte of a character --- vim.str_utf_end('æ', 1) ---- </pre> +--- ``` --- --- @param str string --- @param index number @@ -204,7 +206,8 @@ function vim.schedule(callback) end --- this time. --- --- Examples: ---- <pre>lua +--- +--- ```lua --- --- --- --- -- Wait for 100 ms, allowing other events to process @@ -226,7 +229,7 @@ function vim.schedule(callback) end --- if vim.wait(10000, function() return vim.g.timer_result end) then --- print('Only waiting a little bit of time!') --- end ---- </pre> +--- ``` --- --- @param time integer Number of milliseconds to wait --- @param callback? fun(): boolean Optional callback. Waits until {callback} returns true @@ -258,22 +261,23 @@ function vim.wait(time, callback, interval, fast_only) end --- likewise experimental). --- --- Example (stub for a |ui-popupmenu| implementation): ---- <pre>lua ---- ---- ns = vim.api.nvim_create_namespace('my_fancy_pum') ---- ---- vim.ui_attach(ns, {ext_popupmenu=true}, function(event, ...) ---- if event == "popupmenu_show" then ---- local items, selected, row, col, grid = ... ---- print("display pum ", #items) ---- elseif event == "popupmenu_select" then ---- local selected = ... ---- print("selected", selected) ---- elseif event == "popupmenu_hide" then ---- print("FIN") ---- end ---- end) ---- </pre> +--- +--- ```lua +--- ns = vim.api.nvim_create_namespace('my_fancy_pum') +--- +--- vim.ui_attach(ns, {ext_popupmenu=true}, function(event, ...) +--- if event == "popupmenu_show" then +--- local items, selected, row, col, grid = ... +--- print("display pum ", #items) +--- elseif event == "popupmenu_select" then +--- local selected = ... +--- print("selected", selected) +--- elseif event == "popupmenu_hide" then +--- print("FIN") +--- end +--- end) +--- ``` +--- --- @param ns integer --- @param options table<string, any> --- @param callback fun() diff --git a/runtime/lua/vim/_meta/diff.lua b/runtime/lua/vim/_meta/diff.lua index 246ac0c75a..f265139448 100644 --- a/runtime/lua/vim/_meta/diff.lua +++ b/runtime/lua/vim/_meta/diff.lua @@ -6,24 +6,25 @@ --- either directly or via callback arguments, are 1-based. --- --- Examples: ---- <pre>lua ---- vim.diff('a\\n', 'b\\nc\\n') ---- -- => ---- -- @@ -1 +1,2 @@ ---- -- -a ---- -- +b ---- -- +c --- ---- vim.diff('a\\n', 'b\\nc\\n', {result_type = 'indices'}) ---- -- => ---- -- { ---- -- {1, 1, 1, 2} ---- -- } ---- </pre> +--- ```lua +--- vim.diff('a\n', 'b\nc\n') +--- -- => +--- -- @@ -1 +1,2 @@ +--- -- -a +--- -- +b +--- -- +c --- ---- @param a string First string to compare ---- @param b string Second string to compare ---- @param opts table<string,any> Optional parameters: +--- vim.diff('a\n', 'b\nc\n', {result_type = 'indices'}) +--- -- => +--- -- { +--- -- {1, 1, 1, 2} +--- -- } +--- ``` +--- +---@param a string First string to compare +---@param b string Second string to compare +---@param opts table<string,any> Optional parameters: --- - `on_hunk` (callback): --- Invoked for each hunk in the diff. Return a negative number --- to cancel the callback for any remaining hunks. @@ -64,6 +65,6 @@ --- Use the indent heuristic for the internal --- diff library. --- ---- @return string|table|nil +---@return string|table|nil --- See {opts.result_type}. `nil` if {opts.on_hunk} is given. function vim.diff(a, b, opts) end diff --git a/runtime/lua/vim/_meta/json.lua b/runtime/lua/vim/_meta/json.lua index 76a6c7b733..edf905c7c6 100644 --- a/runtime/lua/vim/_meta/json.lua +++ b/runtime/lua/vim/_meta/json.lua @@ -1,8 +1,8 @@ ---- @meta +---@meta -- luacheck: no unused args ---- @defgroup vim.json +---@defgroup vim.json --- --- This module provides encoding and decoding of Lua objects to and --- from JSON-encoded strings. Supports |vim.NIL| and |vim.empty_dict()|. @@ -14,24 +14,23 @@ --- - Decodes empty array as `{}` (empty Lua table). --- --- Example: ---- <pre>lua ---- :lua vim.print(vim.json.decode('{"bar":[],"foo":{},"zub":null}')) ---- --> { bar = {}, foo = vim.empty_dict(), zub = vim.NIL } ---- </pre> ---- Parameters: ~ ---- • {str} Stringified JSON data. ---- • {opts} Options map keys: ---- • luanil: { object: bool, array: bool } ---- • `luanil.object=true` converts `null` in JSON objects to ---- Lua `nil` instead of `vim.NIL`. ---- • `luanil.array=true` converts `null` in JSON arrays to Lua ---- `nil` instead of `vim.NIL`. ---- @param str string ---- @param opts? table<string, any> ---- @return any +--- +--- ```lua +--- vim.print(vim.json.decode('{"bar":[],"foo":{},"zub":null}')) +--- -- { bar = {}, foo = vim.empty_dict(), zub = vim.NIL } +--- ``` +--- +---@param str string Stringified JSON data. +---@param opts? table<string,any> Options table with keys: +--- - luanil: (table) Table with keys: +--- * object: (boolean) When true, converts `null` in JSON objects +--- to Lua `nil` instead of |vim.NIL|. +--- * array: (boolean) When true, converts `null` in JSON arrays +--- to Lua `nil` instead of |vim.NIL|. +---@return any function vim.json.decode(str, opts) end --- Encodes (or "packs") Lua object {obj} as JSON in a Lua string. ---- @param obj any ---- @return string +---@param obj any +---@return string function vim.json.encode(obj) end diff --git a/runtime/lua/vim/_meta/misc.lua b/runtime/lua/vim/_meta/misc.lua index 8a76755962..0d70e16314 100644 --- a/runtime/lua/vim/_meta/misc.lua +++ b/runtime/lua/vim/_meta/misc.lua @@ -5,9 +5,11 @@ --- Invokes |vim-function| or |user-function| {func} with arguments {...}. --- See also |vim.fn|. --- Equivalent to: ---- <pre>lua ---- vim.fn[func]({...}) ---- </pre> +--- +--- ```lua +--- vim.fn[func]({...}) +--- ``` +--- --- @param func fun() --- @param ... any function vim.call(func, ...) end diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 4fb3141df0..af676fa961 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -141,6 +141,7 @@ vim.bo.ai = vim.bo.autoindent --- :set autoread< --- ``` --- +--- --- @type boolean vim.o.autoread = true vim.o.ar = vim.o.autoread @@ -354,6 +355,7 @@ vim.go.bkc = vim.go.backupcopy --- ``` --- :set bdir=c:\\tmp,\ dir\\,with\\,commas,\\\ dir\ with\ spaces --- ``` +--- --- See also 'backup' and 'writebackup' options. --- If you want to hide your backup files on Unix, consider this value: --- ``` @@ -409,9 +411,9 @@ vim.go.bex = vim.go.backupext --- --- Note that environment variables are not expanded. If you want to use --- $HOME you must expand it explicitly, e.g.: ---- ``` ---- :let &backupskip = escape(expand('$HOME'), '\') .. '/tmp/*' --- +--- ```vim +--- :let &backupskip = escape(expand('$HOME'), '\') .. '/tmp/*' --- ``` --- Note that the default also makes sure that "crontab -e" works (when a --- backup would be made by renaming the original file crontab won't see @@ -833,6 +835,7 @@ vim.bo.cino = vim.bo.cinoptions --- set cinscopedecls+=signals,public\ slots,private\ slots --- ``` --- +--- --- @type string vim.o.cinscopedecls = "public,protected,private" vim.o.cinsd = vim.o.cinscopedecls @@ -916,11 +919,11 @@ vim.go.cwh = vim.go.cmdwinheight --- The screen column can be an absolute number, or a number preceded with --- '+' or '-', which is added to or subtracted from 'textwidth'. --- ``` ---- --- :set cc=+1 " highlight column after 'textwidth' --- :set cc=+1,+2,+3 " highlight three columns after 'textwidth' --- :hi ColorColumn ctermbg=lightgrey guibg=lightgrey --- ``` +--- --- When 'textwidth' is zero then the items with '-' and '+' are not used. --- A maximum of 256 columns are highlighted. --- @@ -1418,6 +1421,7 @@ vim.wo.crb = vim.wo.cursorbind --- au WinEnter * set cursorline cursorcolumn --- ``` --- +--- --- @type boolean vim.o.cursorcolumn = false vim.o.cuc = vim.o.cursorcolumn @@ -1499,6 +1503,7 @@ vim.go.debug = vim.o.debug --- let &l:define = '^\s*\ze\k\+\s*=\s*function(' --- ``` --- +--- --- @type string vim.o.define = "" vim.o.def = vim.o.define @@ -1679,6 +1684,7 @@ vim.go.dex = vim.go.diffexpr --- :set diffopt-=internal " do NOT use the internal diff parser --- ``` --- +--- --- @type string vim.o.diffopt = "internal,filler,closeoff" vim.o.dip = vim.o.diffopt @@ -1729,6 +1735,7 @@ vim.go.dg = vim.go.digraph --- ``` --- :set dir=c:\\tmp,\ dir\\,with\\,commas,\\\ dir\ with\ spaces --- ``` +--- --- Editing the same file twice will result in a warning. Using "/tmp" on --- is discouraged: if the system crashes you lose the swap file. And --- others on the computer may be able to see the files. @@ -1917,6 +1924,7 @@ vim.go.efm = vim.go.errorformat --- :set ei=WinEnter,WinLeave --- ``` --- +--- --- @type string vim.o.eventignore = "" vim.o.ei = vim.o.eventignore @@ -2634,14 +2642,12 @@ vim.go.gp = vim.go.grepprg --- To disable cursor-styling, reset the option: --- ``` --- :set guicursor= ---- --- ``` --- To enable mode shapes, "Cursor" highlight, and blinking: --- ``` --- :set guicursor=n-v-c:block,i-ci-ve:ver25,r-cr:hor20,o:hor50 --- \,a:blinkwait700-blinkoff400-blinkon250-Cursor/lCursor --- \,sm:block-blinkwait175-blinkoff150-blinkon175 ---- --- ``` --- The option is a comma-separated list of parts. Each part consists of a --- mode-list and an argument-list: @@ -2697,10 +2703,10 @@ vim.go.gp = vim.go.grepprg --- n-v-c-sm:block,i-ci-ve:ver25-Cursor,r-cr-o:hor20 --- In Normal et al. modes, use a block cursor --- with the default colors defined by the host ---- terminal. In Insert-likes modes, use +--- terminal. In Insert-like modes, use --- a vertical bar cursor with colors from ---- "Cursor" highlight group. In Replace-likes ---- modes, use a underline cursor with +--- "Cursor" highlight group. In Replace-like +--- modes, use an underline cursor with --- default colors. --- i-ci:ver30-iCursor-blinkwait300-blinkon200-blinkoff150 --- In Insert and Command-line Insert mode, use a @@ -2719,6 +2725,7 @@ vim.go.gp = vim.go.grepprg --- :highlight Cursor gui=NONE guifg=bg guibg=fg --- ``` --- +--- --- @type string vim.o.guicursor = "n-v-c-sm:block,i-ci-ve:ver25,r-cr-o:hor20" vim.o.gcr = vim.o.guicursor @@ -2790,6 +2797,7 @@ vim.go.gcr = vim.go.guicursor --- :set guifont=Andale_Mono:h7.5:w4.5 --- ``` --- +--- --- @type string vim.o.guifont = "" vim.o.gfn = vim.o.guifont @@ -2944,6 +2952,7 @@ vim.go.gtl = vim.go.guitablabel --- :let &guitabtooltip = "line one\nline two" --- ``` --- +--- --- @type string vim.o.guitabtooltip = "" vim.o.gtt = vim.o.guitabtooltip @@ -3206,6 +3215,7 @@ vim.go.inc = vim.go.include --- ``` --- :setlocal includeexpr=tr(v:fname,'.','/') --- ``` +--- --- Also used for the `gf` command if an unmodified file name can't be --- found. Allows doing "gf" on the name after an 'include' statement. --- Also used for `<cfile>`. @@ -3258,6 +3268,7 @@ vim.bo.inex = vim.bo.includeexpr --- 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 @@ -3565,6 +3576,7 @@ vim.go.kp = vim.go.keywordprg --- ``` --- :set langmap=zy,yz,ZY,YZ --- ``` +--- --- The 'langmap' option is a list of parts, separated with commas. Each --- part can be in one of two forms: --- 1. A list of pairs. Each pair is a "from" character immediately @@ -3762,6 +3774,7 @@ vim.go.lw = vim.go.lispwords --- ``` --- :set list lcs=tab:\ \ --- ``` +--- --- Note that list mode will also affect formatting (set with 'textwidth' --- or 'wrapmargin') when 'cpoptions' includes 'L'. See 'listchars' for --- changing the way tabs are displayed. @@ -3790,6 +3803,7 @@ vim.wo.list = vim.o.list --- >-- --- etc. --- ``` +--- --- tab:xyz The 'z' is always used, then 'x' is prepended, and --- then 'y' is used as many times as will fit. Thus --- "tab:<->" displays: @@ -3801,6 +3815,7 @@ vim.wo.list = vim.o.list --- <--> --- etc. --- ``` +--- --- When "tab:" is omitted, a tab is shown as ^I. --- *lcs-space* --- space:c Character to show for a space. When omitted, spaces @@ -3816,6 +3831,7 @@ vim.wo.list = vim.o.list --- ``` --- ---+---+-- --- ``` +--- --- *lcs-lead* --- lead:c Character to show for leading spaces. When omitted, --- leading spaces are blank. Overrides the "space" and @@ -3824,6 +3840,7 @@ vim.wo.list = vim.o.list --- ``` --- :set listchars+=tab:>-,lead:. --- ``` +--- --- *lcs-leadmultispace* --- leadmultispace:c... --- Like the `lcs-multispace` value, but for leading @@ -3834,6 +3851,7 @@ vim.wo.list = vim.o.list --- ``` --- ---+---+--XXX --- ``` +--- --- Where "XXX" denotes the first non-blank characters in --- the line. --- *lcs-trail* @@ -3941,6 +3959,7 @@ vim.go.mef = vim.go.makeef --- :set makeencoding=char " system locale is used --- ``` --- +--- --- @type string vim.o.makeencoding = "" vim.o.menc = vim.o.makeencoding @@ -3986,13 +4005,11 @@ vim.go.mp = vim.go.makeprg --- '>' (for HTML): --- ``` --- :set mps+=<:> ---- --- ``` --- A more exotic example, to jump between the '=' and ';' in an --- assignment, useful for languages like C and Java: --- ``` --- :au FileType c,cpp,java set mps+==:; ---- --- ``` --- For a more advanced way of using "%", see the matchit.vim plugin in --- the $VIMRUNTIME/plugin directory. `add-local-help` @@ -4078,6 +4095,7 @@ vim.go.mis = vim.go.menuitems --- ``` --- {start},{inc},{added} --- ``` +--- --- For most languages the uncompressed word tree fits in memory. {start} --- gives the amount of memory in Kbyte that can be used before any --- compression is done. It should be a bit smaller than the amount of @@ -4196,6 +4214,7 @@ vim.go.more = vim.o.more --- ``` --- :set mouse=nv --- ``` +--- --- To temporarily disable mouse support, hold the shift key while using --- the mouse. --- @@ -4299,6 +4318,7 @@ vim.go.mh = vim.go.mousehide --- :map <4-S-LeftDrag> <4-RightDrag> --- :map <4-S-LeftRelease> <4-RightRelease> --- ``` +--- --- Mouse commands requiring the CTRL modifier can be simulated by typing --- the "g" key before using the mouse: --- "g<LeftMouse>" is "<C-LeftMouse> (jump to tag under mouse click) @@ -4477,6 +4497,7 @@ vim.bo.nf = vim.bo.nrformats --- |there | 4 there | 1 there | 1 there --- ``` --- +--- --- @type boolean vim.o.number = false vim.o.nu = vim.o.number @@ -4710,10 +4731,10 @@ vim.wo.pvw = vim.wo.previewwindow --- the popupmenu using `highlight-blend`. For instance, to enable --- transparency but force the current selected element to be fully opaque: --- ``` ---- --- :set pumblend=15 --- :hi PmenuSel blend=0 --- ``` +--- --- UI-dependent. Works best with RGB colors. 'termguicolors' --- --- @type integer @@ -4986,6 +5007,7 @@ vim.go.ru = vim.go.ruler --- :set rulerformat=%15(%c%V\ %p%%%) --- ``` --- +--- --- @type string vim.o.rulerformat = "" vim.o.ruf = vim.o.rulerformat @@ -5363,6 +5385,7 @@ vim.go.ssop = vim.go.sessionoptions --- ``` --- :set shada='50,<1000,s100,:0,n~/nvim/shada --- ``` +--- --- '50 Marks will be remembered for the last 50 files you --- edited. --- <1000 Contents of registers (up to 1000 lines each) will be @@ -5449,7 +5472,6 @@ vim.go.sdf = vim.go.shadafile --- let &shellredir = '2>&1 | %%{ "$_" } | Out-File %s; exit $LastExitCode' --- let &shellpipe = '2>&1 | %%{ "$_" } | tee %s; exit $LastExitCode' --- set shellquote= shellxquote= ---- --- ``` --- This option cannot be set from a `modeline` or in the `sandbox`, for --- security reasons. @@ -5726,6 +5748,7 @@ vim.go.shm = vim.go.shortmess --- :setlocal showbreak=NONE --- ``` --- +--- --- @type string vim.o.showbreak = "" vim.o.sbr = vim.o.showbreak @@ -5858,15 +5881,16 @@ vim.go.ss = vim.go.sidescroll --- setlocal sidescrolloff< --- setlocal sidescrolloff=-1 --- ``` +--- --- Example: Try this together with 'sidescroll' and 'listchars' as --- in the following example to never allow the cursor to move --- onto the "extends" character: --- ``` ---- --- :set nowrap sidescroll=1 listchars=extends:>,precedes:< --- :set sidescrolloff=1 --- ``` --- +--- --- @type integer vim.o.sidescrolloff = 0 vim.o.siso = vim.o.sidescrolloff @@ -6171,6 +6195,7 @@ vim.bo.spo = vim.bo.spelloptions --- ``` --- :set sps=file:~/.config/nvim/sugg,best,expr:MySuggest() --- ``` +--- --- This option cannot be set from a `modeline` or in the `sandbox`, for --- security reasons. --- @@ -6263,6 +6288,7 @@ vim.go.sol = vim.go.startofline --- handler should be written with this in mind. --- --- Examples: +--- --- ```vim --- " Relative number with bar separator and click handlers: --- :set statuscolumn=%@SignCb@%s%=%T%@NumCb@%r│%T @@ -6282,7 +6308,6 @@ vim.go.sol = vim.go.startofline --- :let &stc='%#NonText#%{&nu?v:lnum:""}' . --- '%=%{&rnu&&(v:lnum%2)?"\ ".v:relnum:""}' . --- '%#LineNr#%{&rnu&&!(v:lnum%2)?"\ ".v:relnum:""}' ---- --- ``` --- WARNING: this expression is evaluated for each screen line so defining --- an expensive expression can negatively affect render performance. @@ -6522,6 +6547,7 @@ vim.wo.stc = vim.wo.statuscolumn --- :endfunction --- ``` --- +--- --- @type string vim.o.statusline = "" vim.o.stl = vim.o.statusline @@ -6553,6 +6579,7 @@ vim.go.su = vim.go.suffixes --- :set suffixesadd=.java --- ``` --- +--- --- @type string vim.o.suffixesadd = "" vim.o.sua = vim.o.suffixesadd @@ -7445,6 +7472,7 @@ vim.go.ww = vim.go.whichwrap --- :set wc=<Tab> --- ``` --- +--- --- @type integer vim.o.wildchar = 9 vim.o.wc = vim.o.wildchar @@ -7533,6 +7561,7 @@ vim.go.wic = vim.go.wildignorecase --- :cnoremap <Left> <Space><BS><Left> --- :cnoremap <Right> <Space><BS><Right> --- ``` +--- --- `hl-WildMenu` highlights the current match. --- --- @type boolean @@ -7762,6 +7791,7 @@ vim.go.wh = vim.go.winheight --- set winhighlight=Normal:MyNormal,NormalNC:MyNormalNC --- ``` --- +--- --- @type string vim.o.winhighlight = "" vim.o.winhl = vim.o.winhighlight diff --git a/runtime/lua/vim/_meta/regex.lua b/runtime/lua/vim/_meta/regex.lua index ab4f46d43f..58aa2be8c2 100644 --- a/runtime/lua/vim/_meta/regex.lua +++ b/runtime/lua/vim/_meta/regex.lua @@ -21,7 +21,7 @@ local regex = {} -- luacheck: no unused --- precisely, surround the regex with `^` and `$`. If there was a match, the --- byte indices for the beginning and end of the match are returned. When --- there is no match, `nil` is returned. Because any integer is "truthy", ---- `regex:match()` can be directly used as a condition in an if-statement. +--- `regex:match_str()` can be directly used as a condition in an if-statement. --- @param str string function regex:match_str(str) end diff --git a/runtime/lua/vim/_meta/spell.lua b/runtime/lua/vim/_meta/spell.lua index d55867f769..57f2180895 100644 --- a/runtime/lua/vim/_meta/spell.lua +++ b/runtime/lua/vim/_meta/spell.lua @@ -10,13 +10,14 @@ --- the buffer. Consider calling this with |nvim_buf_call()|. --- --- Example: ---- <pre>lua ---- vim.spell.check("the quik brown fox") ---- -- => ---- -- { ---- -- {'quik', 'bad', 5} ---- -- } ---- </pre> +--- +--- ```lua +--- vim.spell.check("the quik brown fox") +--- -- => +--- -- { +--- -- {'quik', 'bad', 5} +--- -- } +--- ``` --- --- @param str string --- @return {[1]: string, [2]: string, [3]: string}[] diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 6209ce0c73..efb779ad41 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -6194,7 +6194,7 @@ function vim.fn.prevnonblank(lnum) end --- *printf-$* --- In certain languages, error and informative messages are --- more readable when the order of words is different from the ---- corresponding message in English. To accomodate translations +--- corresponding message in English. To accommodate translations --- having a different word order, positional arguments may be --- used to indicate this. For instance: >vim --- diff --git a/runtime/lua/vim/_options.lua b/runtime/lua/vim/_options.lua index e1c125baf2..7b44f6b35f 100644 --- a/runtime/lua/vim/_options.lua +++ b/runtime/lua/vim/_options.lua @@ -7,11 +7,12 @@ ---"references". |lua-guide-variables| For example, using \`vim.fn.remove()\` on ---a Lua list copies the list object to Vimscript and does NOT modify the Lua ---list: ----<pre>lua ---- local list = { 1, 2, 3 } ---- vim.fn.remove(list, 0) ---- vim.print(list) --> "{ 1, 2, 3 }" ----</pre> +--- +--- ```lua +--- local list = { 1, 2, 3 } +--- vim.fn.remove(list, 0) +--- vim.print(list) --> "{ 1, 2, 3 }" +--- ``` ---@addtogroup lua-vimscript ---@brief <pre>help @@ -131,13 +132,17 @@ local function get_options_info(name) return info end ----Environment variables defined in the editor session. ----See |expand-env| and |:let-environment| for the Vimscript behavior. ----Invalid or unset key returns `nil`. ----Example: <pre>lua ---- vim.env.FOO = 'bar' ---- print(vim.env.TERM) ----</pre> +--- Environment variables defined in the editor session. +--- See |expand-env| and |:let-environment| for the Vimscript behavior. +--- Invalid or unset key returns `nil`. +--- +--- Example: +--- +--- ```lua +--- vim.env.FOO = 'bar' +--- print(vim.env.TERM) +--- ``` +--- ---@param var string vim.env = setmetatable({}, { __index = function(_, k) @@ -226,16 +231,18 @@ end ---global value of a |global-local| option, see |:setglobal|. ---</pre> ----Get or set |options|. Like `:set`. Invalid key is an error. +--- Get or set |options|. Like `:set`. Invalid key is an error. --- ----Note: this works on both buffer-scoped and window-scoped options using the ----current buffer and window. +--- Note: this works on both buffer-scoped and window-scoped options using the +--- current buffer and window. --- ----Example: <pre>lua ---- vim.o.cmdheight = 4 ---- print(vim.o.columns) ---- print(vim.o.foo) -- error: invalid key ----</pre> +--- Example: +--- +--- ```lua +--- vim.o.cmdheight = 4 +--- print(vim.o.columns) +--- print(vim.o.foo) -- error: invalid key +--- ``` vim.o = setmetatable({}, { __index = function(_, k) return api.nvim_get_option_value(k, {}) @@ -245,18 +252,20 @@ vim.o = setmetatable({}, { end, }) ----Get or set global |options|. Like `:setglobal`. Invalid key is ----an error. +--- Get or set global |options|. Like `:setglobal`. Invalid key is +--- an error. --- ----Note: this is different from |vim.o| because this accesses the global ----option value and thus is mostly useful for use with |global-local| ----options. +--- Note: this is different from |vim.o| because this accesses the global +--- option value and thus is mostly useful for use with |global-local| +--- options. --- ----Example: <pre>lua ---- vim.go.cmdheight = 4 ---- print(vim.go.columns) ---- print(vim.go.bar) -- error: invalid key ----</pre> +--- Example: +--- +--- ```lua +--- vim.go.cmdheight = 4 +--- print(vim.go.columns) +--- print(vim.go.bar) -- error: invalid key +--- ``` vim.go = setmetatable({}, { __index = function(_, k) return api.nvim_get_option_value(k, { scope = 'global' }) @@ -266,37 +275,39 @@ vim.go = setmetatable({}, { end, }) ----Get or set buffer-scoped |options| for the buffer with number {bufnr}. ----Like `:set` and `:setlocal`. If [{bufnr}] is omitted then the current ----buffer is used. Invalid {bufnr} or key is an error. +--- Get or set buffer-scoped |options| for the buffer with number {bufnr}. +--- Like `:set` and `:setlocal`. If [{bufnr}] is omitted then the current +--- buffer is used. Invalid {bufnr} or key is an error. --- ----Note: this is equivalent to both `:set` and `:setlocal`. +--- Note: this is equivalent to both `:set` and `:setlocal`. --- ----Example: <pre>lua ---- local bufnr = vim.api.nvim_get_current_buf() ---- vim.bo[bufnr].buflisted = true -- same as vim.bo.buflisted = true ---- print(vim.bo.comments) ---- print(vim.bo.baz) -- error: invalid key ----</pre> ----@param bufnr integer|nil +--- Example: +--- +--- ```lua +--- local bufnr = vim.api.nvim_get_current_buf() +--- vim.bo[bufnr].buflisted = true -- same as vim.bo.buflisted = true +--- print(vim.bo.comments) +--- print(vim.bo.baz) -- error: invalid key +--- ``` vim.bo = new_buf_opt_accessor() ----Get or set window-scoped |options| for the window with handle {winid} and ----buffer with number {bufnr}. Like `:setlocal` if {bufnr} is provided, like ----`:set` otherwise. If [{winid}] is omitted then the current window is ----used. Invalid {winid}, {bufnr} or key is an error. +--- Get or set window-scoped |options| for the window with handle {winid} and +--- buffer with number {bufnr}. Like `:setlocal` if {bufnr} is provided, like +--- `:set` otherwise. If [{winid}] is omitted then the current window is +--- used. Invalid {winid}, {bufnr} or key is an error. --- ----Note: only {bufnr} with value `0` (the current buffer in the window) is ----supported. +--- Note: only {bufnr} with value `0` (the current buffer in the window) is +--- supported. --- ----Example: <pre>lua ---- local winid = vim.api.nvim_get_current_win() ---- vim.wo[winid].number = true -- same as vim.wo.number = true ---- print(vim.wo.foldmarker) ---- print(vim.wo.quux) -- error: invalid key ---- vim.wo[winid][0].spell = false -- like ':setlocal nospell' +--- Example: --- ----</pre> +--- ```lua +--- local winid = vim.api.nvim_get_current_win() +--- vim.wo[winid].number = true -- same as vim.wo.number = true +--- print(vim.wo.foldmarker) +--- print(vim.wo.quux) -- error: invalid key +--- vim.wo[winid][0].spell = false -- like ':setlocal nospell' +--- ``` vim.wo = new_win_opt_accessor() ---@brief [[ @@ -795,77 +806,93 @@ end --- @diagnostic disable-next-line:unused-local used for gen_vimdoc local Option = {} -- luacheck: no unused ----Returns a Lua-representation of the option. Boolean, number and string ----values will be returned in exactly the same fashion. +--- Returns a Lua-representation of the option. Boolean, number and string +--- values will be returned in exactly the same fashion. --- ----For values that are comma-separated lists, an array will be returned with ----the values as entries in the array: <pre>lua ---- vim.cmd [[set wildignore=*.pyc,*.o]] +--- For values that are comma-separated lists, an array will be returned with +--- the values as entries in the array: --- ---- vim.print(vim.opt.wildignore:get()) ---- -- { "*.pyc", "*.o", } +--- ```lua +--- vim.cmd [[set wildignore=*.pyc,*.o]] --- ---- for _, ignore_pattern in ipairs(vim.opt.wildignore:get()) do ---- print("Will ignore:", ignore_pattern) ---- end ---- -- Will ignore: *.pyc ---- -- Will ignore: *.o ----</pre> +--- vim.print(vim.opt.wildignore:get()) +--- -- { "*.pyc", "*.o", } --- ----For values that are comma-separated maps, a table will be returned with ----the names as keys and the values as entries: <pre>lua ---- vim.cmd [[set listchars=space:_,tab:>~]] +--- for _, ignore_pattern in ipairs(vim.opt.wildignore:get()) do +--- print("Will ignore:", ignore_pattern) +--- end +--- -- Will ignore: *.pyc +--- -- Will ignore: *.o +--- ``` --- ---- vim.print(vim.opt.listchars:get()) ---- -- { space = "_", tab = ">~", } +--- For values that are comma-separated maps, a table will be returned with +--- the names as keys and the values as entries: --- ---- for char, representation in pairs(vim.opt.listchars:get()) do ---- print(char, "=>", representation) ---- end ----</pre> +--- ```lua +--- vim.cmd [[set listchars=space:_,tab:>~]] --- ----For values that are lists of flags, a set will be returned with the flags ----as keys and `true` as entries. <pre>lua ---- vim.cmd [[set formatoptions=njtcroql]] +--- vim.print(vim.opt.listchars:get()) +--- -- { space = "_", tab = ">~", } --- ---- vim.print(vim.opt.formatoptions:get()) ---- -- { n = true, j = true, c = true, ... } +--- for char, representation in pairs(vim.opt.listchars:get()) do +--- print(char, "=>", representation) +--- end +--- ``` +--- +--- For values that are lists of flags, a set will be returned with the flags +--- as keys and `true` as entries. +--- +--- ```lua +--- vim.cmd [[set formatoptions=njtcroql]] +--- +--- vim.print(vim.opt.formatoptions:get()) +--- -- { n = true, j = true, c = true, ... } +--- +--- local format_opts = vim.opt.formatoptions:get() +--- if format_opts.j then +--- print("J is enabled!") +--- end +--- ``` --- ---- local format_opts = vim.opt.formatoptions:get() ---- if format_opts.j then ---- print("J is enabled!") ---- end ----</pre> ---@return string|integer|boolean|nil value of option ---@diagnostic disable-next-line:unused-local used for gen_vimdoc function Option:get() end ----Append a value to string-style options. See |:set+=| +--- Append a value to string-style options. See |:set+=| +--- +--- These are equivalent: +--- +--- ```lua +--- vim.opt.formatoptions:append('j') +--- vim.opt.formatoptions = vim.opt.formatoptions + 'j' +--- ``` --- ----These are equivalent: <pre>lua ---- vim.opt.formatoptions:append('j') ---- vim.opt.formatoptions = vim.opt.formatoptions + 'j' ----</pre> ---@param value string Value to append ---- @diagnostic disable-next-line:unused-local used for gen_vimdoc +---@diagnostic disable-next-line:unused-local used for gen_vimdoc function Option:append(value) end -- luacheck: no unused ----Prepend a value to string-style options. See |:set^=| +--- Prepend a value to string-style options. See |:set^=| +--- +--- These are equivalent: +--- +--- ```lua +--- vim.opt.wildignore:prepend('*.o') +--- vim.opt.wildignore = vim.opt.wildignore ^ '*.o' +--- ``` --- ----These are equivalent: <pre>lua ---- vim.opt.wildignore:prepend('*.o') ---- vim.opt.wildignore = vim.opt.wildignore ^ '*.o' ----</pre> ---@param value string Value to prepend ---@diagnostic disable-next-line:unused-local used for gen_vimdoc function Option:prepend(value) end -- luacheck: no unused ----Remove a value from string-style options. See |:set-=| +--- Remove a value from string-style options. See |:set-=| +--- +--- These are equivalent: +--- +--- ```lua +--- vim.opt.wildignore:remove('*.pyc') +--- vim.opt.wildignore = vim.opt.wildignore - '*.pyc' +--- ``` --- ----These are equivalent: <pre>lua ---- vim.opt.wildignore:remove('*.pyc') ---- vim.opt.wildignore = vim.opt.wildignore - '*.pyc' ----</pre> ---@param value string Value to remove ---@diagnostic disable-next-line:unused-local used for gen_vimdoc function Option:remove(value) end -- luacheck: no unused diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index a1f3020c88..b8d3906b7f 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -553,14 +553,16 @@ end --- followed by namespace configuration, and finally global configuration. --- --- For example, if a user enables virtual text globally with ---- <pre>lua ---- vim.diagnostic.config({ virtual_text = true }) ---- </pre> +--- +--- ```lua +--- vim.diagnostic.config({ virtual_text = true }) +--- ``` --- --- and a diagnostic producer sets diagnostics with ---- <pre>lua ---- vim.diagnostic.set(ns, 0, diagnostics, { virtual_text = false }) ---- </pre> +--- +--- ```lua +--- vim.diagnostic.set(ns, 0, diagnostics, { virtual_text = false }) +--- ``` --- --- then virtual text will not be enabled for those diagnostics. --- @@ -1601,18 +1603,20 @@ end --- Parse a diagnostic from a string. --- --- For example, consider a line of output from a linter: ---- <pre> +--- +--- ``` --- WARNING filename:27:3: Variable 'foo' does not exist ---- </pre> +--- ``` --- --- This can be parsed into a diagnostic |diagnostic-structure| --- with: ---- <pre>lua ---- local s = "WARNING filename:27:3: Variable 'foo' does not exist" ---- local pattern = "^(%w+) %w+:(%d+):(%d+): (.+)$" ---- local groups = { "severity", "lnum", "col", "message" } ---- vim.diagnostic.match(s, pattern, groups, { WARNING = vim.diagnostic.WARN }) ---- </pre> +--- +--- ```lua +--- local s = "WARNING filename:27:3: Variable 'foo' does not exist" +--- local pattern = "^(%w+) %w+:(%d+):(%d+): (.+)$" +--- local groups = { "severity", "lnum", "col", "message" } +--- vim.diagnostic.match(s, pattern, groups, { WARNING = vim.diagnostic.WARN }) +--- ``` --- ---@param str string String to parse diagnostics from. ---@param pat string Lua pattern with capture groups. diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 0e8ecf1b07..d847c28f5c 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -669,6 +669,8 @@ local extension = { m2 = 'modula2', mi = 'modula2', lm3 = 'modula3', + mojo = 'mojo', + ['🔥'] = 'mojo', -- 🙄 ssc = 'monk', monk = 'monk', tsc = 'monk', @@ -2079,43 +2081,45 @@ end --- See $VIMRUNTIME/lua/vim/filetype.lua for more examples. --- --- Example: ---- <pre>lua ---- vim.filetype.add({ ---- extension = { ---- foo = 'fooscript', ---- bar = function(path, bufnr) ---- if some_condition() then ---- return 'barscript', function(bufnr) ---- -- Set a buffer variable ---- vim.b[bufnr].barscript_version = 2 ---- end ---- end ---- return 'bar' ---- end, ---- }, ---- filename = { ---- ['.foorc'] = 'toml', ---- ['/etc/foo/config'] = 'toml', ---- }, ---- pattern = { ---- ['.*/etc/foo/.*'] = 'fooscript', ---- -- Using an optional priority ---- ['.*/etc/foo/.*%.conf'] = { 'dosini', { priority = 10 } }, ---- -- A pattern containing an environment variable ---- ['${XDG_CONFIG_HOME}/foo/git'] = 'git', ---- ['README.(%a+)$'] = function(path, bufnr, ext) ---- if ext == 'md' then ---- return 'markdown' ---- elseif ext == 'rst' then ---- return 'rst' ---- end ---- end, ---- }, ---- }) ---- </pre> +--- +--- ```lua +--- vim.filetype.add({ +--- extension = { +--- foo = 'fooscript', +--- bar = function(path, bufnr) +--- if some_condition() then +--- return 'barscript', function(bufnr) +--- -- Set a buffer variable +--- vim.b[bufnr].barscript_version = 2 +--- end +--- end +--- return 'bar' +--- end, +--- }, +--- filename = { +--- ['.foorc'] = 'toml', +--- ['/etc/foo/config'] = 'toml', +--- }, +--- pattern = { +--- ['.*/etc/foo/.*'] = 'fooscript', +--- -- Using an optional priority +--- ['.*/etc/foo/.*%.conf'] = { 'dosini', { priority = 10 } }, +--- -- A pattern containing an environment variable +--- ['${XDG_CONFIG_HOME}/foo/git'] = 'git', +--- ['README.(%a+)$'] = function(path, bufnr, ext) +--- if ext == 'md' then +--- return 'markdown' +--- elseif ext == 'rst' then +--- return 'rst' +--- end +--- end, +--- }, +--- }) +--- ``` --- --- To add a fallback match on contents, use ---- <pre>lua +--- +--- ```lua --- vim.filetype.add { --- pattern = { --- ['.*'] = { @@ -2131,7 +2135,7 @@ end --- }, --- }, --- } ---- </pre> +--- ``` --- ---@param filetypes vim.filetype.add.filetypes A table containing new filetype maps (see example). function M.add(filetypes) @@ -2254,19 +2258,19 @@ end --- Each of the three options is specified using a key to the single argument of this function. --- Example: --- ---- <pre>lua ---- -- Using a buffer number ---- vim.filetype.match({ buf = 42 }) +--- ```lua +--- -- Using a buffer number +--- vim.filetype.match({ buf = 42 }) --- ---- -- Override the filename of the given buffer ---- vim.filetype.match({ buf = 42, filename = 'foo.c' }) +--- -- Override the filename of the given buffer +--- vim.filetype.match({ buf = 42, filename = 'foo.c' }) --- ---- -- Using a filename without a buffer ---- vim.filetype.match({ filename = 'main.lua' }) +--- -- Using a filename without a buffer +--- vim.filetype.match({ filename = 'main.lua' }) --- ---- -- Using file contents ---- vim.filetype.match({ contents = {'#!/usr/bin/env bash'} }) ---- </pre> +--- -- Using file contents +--- vim.filetype.match({ contents = {'#!/usr/bin/env bash'} }) +--- ``` --- ---@param args vim.filetype.match.args Table specifying which matching strategy to use. --- Accepted keys are: @@ -2402,9 +2406,10 @@ end --- is set, meaning it should respect all FileType autocmds and ftplugin files. --- --- Example: ---- <pre>lua ---- vim.filetype.get_option('vim', 'commentstring') ---- </pre> +--- +--- ```lua +--- vim.filetype.get_option('vim', 'commentstring') +--- ``` --- --- Note: this uses |nvim_get_option_value()| but caches the result. --- This means |ftplugin| and |FileType| autocommands are only diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index 7cd9e3cf04..22612a7255 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -5,7 +5,8 @@ local iswin = vim.uv.os_uname().sysname == 'Windows_NT' --- Iterate over all the parents of the given path. --- --- Example: ---- <pre>lua +--- +--- ```lua --- local root_dir --- for dir in vim.fs.parents(vim.api.nvim_buf_get_name(0)) do --- if vim.fn.isdirectory(dir .. "/.git") == 1 then @@ -17,7 +18,7 @@ local iswin = vim.uv.os_uname().sysname == 'Windows_NT' --- if root_dir then --- print("Found git repository at", root_dir) --- end ---- </pre> +--- ``` --- ---@param start (string) Initial path. ---@return fun(_, dir: string): string? # Iterator @@ -165,7 +166,8 @@ end --- to narrow the search to find only that type. --- --- Examples: ---- <pre>lua +--- +--- ```lua --- -- location of Cargo.toml from the current buffer's path --- local cargo = vim.fs.find('Cargo.toml', { --- upward = true, @@ -183,7 +185,7 @@ end --- local cpp_hpp = vim.fs.find(function(name, path) --- return name:match('.*%.[ch]pp$') and path:match('[/\\\\]lib$') --- end, {limit = math.huge, type = 'file'}) ---- </pre> +--- ``` --- ---@param names (string|string[]|fun(name: string, path: string): boolean) Names of the items to find. --- Must be base names, paths and globs are not supported when {names} is a string or a table. @@ -322,16 +324,17 @@ end --- variables are also expanded. --- --- Examples: ---- <pre>lua ---- vim.fs.normalize('C:\\\\Users\\\\jdoe') ---- --> 'C:/Users/jdoe' --- ---- vim.fs.normalize('~/src/neovim') ---- --> '/home/jdoe/src/neovim' +--- ```lua +--- vim.fs.normalize('C:\\\\Users\\\\jdoe') +--- -- 'C:/Users/jdoe' +--- +--- vim.fs.normalize('~/src/neovim') +--- -- '/home/jdoe/src/neovim' --- ---- vim.fs.normalize('$XDG_CONFIG_HOME/nvim/init.vim') ---- --> '/Users/jdoe/.config/nvim/init.vim' ---- </pre> +--- vim.fs.normalize('$XDG_CONFIG_HOME/nvim/init.vim') +--- -- '/Users/jdoe/.config/nvim/init.vim' +--- ``` --- ---@param path (string) Path to normalize ---@param opts table|nil Options: diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua index 14b0e71312..fc2fd43c97 100644 --- a/runtime/lua/vim/highlight.lua +++ b/runtime/lua/vim/highlight.lua @@ -1,23 +1,24 @@ ---@defgroup vim.highlight --- ----@brief ----Nvim includes a function for highlighting a selection on yank. +--- Nvim includes a function for highlighting a selection on yank. --- ----To enable it, add the following to your `init.vim`: ----<pre>vim ---- au TextYankPost * silent! lua vim.highlight.on_yank() ----</pre> +--- To enable it, add the following to your `init.vim`: --- ----You can customize the highlight group and the duration of ----the highlight via: ----<pre>vim ---- au TextYankPost * silent! lua vim.highlight.on_yank {higroup="IncSearch", timeout=150} ----</pre> +--- ```vim +--- au TextYankPost * silent! lua vim.highlight.on_yank() +--- ``` --- ----If you want to exclude visual selections from highlighting on yank, use: ----<pre>vim ---- au TextYankPost * silent! lua vim.highlight.on_yank {on_visual=false} ----</pre> +--- You can customize the highlight group and the duration of the highlight via: +--- +--- ```vim +--- au TextYankPost * silent! lua vim.highlight.on_yank {higroup="IncSearch", timeout=150} +--- ``` +--- +--- If you want to exclude visual selections from highlighting on yank, use: +--- +--- ```vim +--- au TextYankPost * silent! lua vim.highlight.on_yank {on_visual=false} +--- ``` local api = vim.api @@ -55,6 +56,9 @@ function M.range(bufnr, ns, higroup, start, finish, opts) local inclusive = opts.inclusive or false local priority = opts.priority or M.priorities.user + -- TODO: in case of 'v', 'V' (not block), this should calculate equivalent + -- bounds (row, col, end_row, end_col) as multiline regions are natively + -- supported now local region = vim.region(bufnr, start, finish, regtype, inclusive) for linenr, cols in pairs(region) do local end_row diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index 56c130dd0c..595baa7019 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -15,44 +15,45 @@ --- (for function iterators, this means that the first value returned by the function is nil). --- --- Examples: ---- <pre>lua ---- local it = vim.iter({ 1, 2, 3, 4, 5 }) ---- it:map(function(v) ---- return v * 3 ---- end) ---- it:rev() ---- it:skip(2) ---- it:totable() ---- -- { 9, 6, 3 } ---- ---- -- ipairs() is a function iterator which returns both the index (i) and the value (v) ---- vim.iter(ipairs({ 1, 2, 3, 4, 5 })):map(function(i, v) ---- if i > 2 then return v end ---- end):totable() ---- -- { 3, 4, 5 } ---- ---- local it = vim.iter(vim.gsplit('1,2,3,4,5', ',')) ---- it:map(function(s) return tonumber(s) end) ---- for i, d in it:enumerate() do ---- print(string.format("Column %d is %d", i, d)) ---- end ---- -- Column 1 is 1 ---- -- Column 2 is 2 ---- -- Column 3 is 3 ---- -- Column 4 is 4 ---- -- Column 5 is 5 ---- ---- vim.iter({ a = 1, b = 2, c = 3, z = 26 }):any(function(k, v) ---- return k == 'z' ---- end) ---- -- true ---- ---- local rb = vim.ringbuf(3) ---- rb:push("a") ---- rb:push("b") ---- vim.iter(rb):totable() ---- -- { "a", "b" } ---- </pre> +--- +--- ```lua +--- local it = vim.iter({ 1, 2, 3, 4, 5 }) +--- it:map(function(v) +--- return v * 3 +--- end) +--- it:rev() +--- it:skip(2) +--- it:totable() +--- -- { 9, 6, 3 } +--- +--- -- ipairs() is a function iterator which returns both the index (i) and the value (v) +--- vim.iter(ipairs({ 1, 2, 3, 4, 5 })):map(function(i, v) +--- if i > 2 then return v end +--- end):totable() +--- -- { 3, 4, 5 } +--- +--- local it = vim.iter(vim.gsplit('1,2,3,4,5', ',')) +--- it:map(function(s) return tonumber(s) end) +--- for i, d in it:enumerate() do +--- print(string.format("Column %d is %d", i, d)) +--- end +--- -- Column 1 is 1 +--- -- Column 2 is 2 +--- -- Column 3 is 3 +--- -- Column 4 is 4 +--- -- Column 5 is 5 +--- +--- vim.iter({ a = 1, b = 2, c = 3, z = 26 }):any(function(k, v) +--- return k == 'z' +--- end) +--- -- true +--- +--- local rb = vim.ringbuf(3) +--- rb:push("a") +--- rb:push("b") +--- vim.iter(rb):totable() +--- -- { "a", "b" } +--- ``` --- --- In addition to the |vim.iter()| function, the |vim.iter| module provides --- convenience functions like |vim.iter.filter()| and |vim.iter.totable()|. @@ -140,9 +141,10 @@ end --- Add a filter step to the iterator pipeline. --- --- Example: ---- <pre>lua +--- +--- ```lua --- local bufs = vim.iter(vim.api.nvim_list_bufs()):filter(vim.api.nvim_buf_is_loaded) ---- </pre> +--- ``` --- ---@param f function(...):bool Takes all values returned from the previous stage --- in the pipeline and returns false or nil if the @@ -176,7 +178,8 @@ end --- If the map function returns nil, the value is filtered from the iterator. --- --- Example: ---- <pre>lua +--- +--- ```lua --- local it = vim.iter({ 1, 2, 3, 4 }):map(function(v) --- if v % 2 == 0 then --- return v * 3 @@ -184,7 +187,7 @@ end --- end) --- it:totable() --- -- { 6, 12 } ---- </pre> +--- ``` --- ---@param f function(...):any Mapping function. Takes all values returned from --- the previous stage in the pipeline as arguments @@ -288,7 +291,8 @@ end --- pipeline, each value will be included in a table. --- --- Examples: ---- <pre>lua +--- +--- ```lua --- vim.iter(string.gmatch('100 20 50', '%d+')):map(tonumber):totable() --- -- { 100, 20, 50 } --- @@ -297,7 +301,7 @@ end --- --- vim.iter({ a = 1, b = 2, c = 3 }):filter(function(k, v) return v % 2 ~= 0 end):totable() --- -- { { 'a', 1 }, { 'c', 3 } } ---- </pre> +--- ``` --- --- The generated table is a list-like table with consecutive, numeric indices. --- To create a map-like table with arbitrary keys, use |Iter:fold()|. @@ -352,7 +356,8 @@ end --- Fold ("reduce") an iterator or table into a single value. --- --- Examples: ---- <pre>lua +--- +--- ```lua --- -- Create a new table with only even values --- local t = { a = 1, b = 2, c = 3, d = 4 } --- local it = vim.iter(t) @@ -362,7 +367,7 @@ end --- return t --- end) --- -- { b = 2, d = 4 } ---- </pre> +--- ``` --- ---@generic A --- @@ -398,7 +403,8 @@ end --- Return the next value from the iterator. --- --- Example: ---- <pre>lua +--- +--- ```lua --- --- local it = vim.iter(string.gmatch('1 2 3', '%d+')):map(tonumber) --- it:next() @@ -408,7 +414,7 @@ end --- it:next() --- -- 3 --- ---- </pre> +--- ``` --- ---@return any function Iter.next(self) -- luacheck: no unused args @@ -431,13 +437,14 @@ end --- Only supported for iterators on list-like tables. --- --- Example: ---- <pre>lua +--- +--- ```lua --- --- local it = vim.iter({ 3, 6, 9, 12 }):rev() --- it:totable() --- -- { 12, 9, 6, 3 } --- ---- </pre> +--- ``` --- ---@return Iter function Iter.rev(self) @@ -457,7 +464,8 @@ end --- Only supported for iterators on list-like tables. --- --- Example: ---- <pre>lua +--- +--- ```lua --- --- local it = vim.iter({ 3, 6, 9, 12 }) --- it:peek() @@ -467,7 +475,7 @@ end --- it:next() --- -- 3 --- ---- </pre> +--- ``` --- ---@return any function Iter.peek(self) -- luacheck: no unused args @@ -486,7 +494,8 @@ end --- Advances the iterator. Returns nil and drains the iterator if no value is found. --- --- Examples: ---- <pre>lua +--- +--- ```lua --- --- local it = vim.iter({ 3, 6, 9, 12 }) --- it:find(12) @@ -500,7 +509,7 @@ end --- it:find(function(v) return v % 4 == 0 end) --- -- 12 --- ---- </pre> +--- ``` --- ---@return any function Iter.find(self, f) @@ -536,7 +545,8 @@ end --- Only supported for iterators on list-like tables. --- --- Examples: ---- <pre>lua +--- +--- ```lua --- --- local it = vim.iter({ 1, 2, 3, 2, 1 }):enumerate() --- it:rfind(1) @@ -544,7 +554,7 @@ end --- it:rfind(1) --- -- 1 1 --- ---- </pre> +--- ``` --- ---@see Iter.find --- @@ -578,13 +588,14 @@ end --- Only supported for iterators on list-like tables. --- --- Example: ---- <pre>lua +--- +--- ```lua --- local it = vim.iter({1, 2, 3, 4}) --- it:nextback() --- -- 4 --- it:nextback() --- -- 3 ---- </pre> +--- ``` --- ---@return any function Iter.nextback(self) -- luacheck: no unused args @@ -604,7 +615,8 @@ end --- Only supported for iterators on list-like tables. --- --- Example: ---- <pre>lua +--- +--- ```lua --- local it = vim.iter({1, 2, 3, 4}) --- it:peekback() --- -- 4 @@ -612,7 +624,7 @@ end --- -- 4 --- it:nextback() --- -- 4 ---- </pre> +--- ``` --- ---@return any function Iter.peekback(self) -- luacheck: no unused args @@ -629,13 +641,14 @@ end --- Skip values in the iterator. --- --- Example: ---- <pre>lua +--- +--- ```lua --- --- local it = vim.iter({ 3, 6, 9, 12 }):skip(2) --- it:next() --- -- 9 --- ---- </pre> +--- ``` --- ---@param n number Number of values to skip. ---@return Iter @@ -661,13 +674,14 @@ end --- Only supported for iterators on list-like tables. --- --- Example: ---- <pre>lua +--- +--- ```lua --- local it = vim.iter({ 1, 2, 3, 4, 5 }):skipback(2) --- it:next() --- -- 1 --- it:nextback() --- -- 3 ---- </pre> +--- ``` --- ---@param n number Number of values to skip. ---@return Iter @@ -691,7 +705,8 @@ end --- This function advances the iterator. --- --- Example: ---- <pre>lua +--- +--- ```lua --- --- local it = vim.iter({ 3, 6, 9, 12 }) --- it:nth(2) @@ -699,7 +714,7 @@ end --- it:nth(2) --- -- 12 --- ---- </pre> +--- ``` --- ---@param n number The index of the value to return. ---@return any @@ -716,7 +731,8 @@ end --- Only supported for iterators on list-like tables. --- --- Example: ---- <pre>lua +--- +--- ```lua --- --- local it = vim.iter({ 3, 6, 9, 12 }) --- it:nthback(2) @@ -724,7 +740,7 @@ end --- it:nthback(2) --- -- 3 --- ---- </pre> +--- ``` --- ---@param n number The index of the value to return. ---@return any @@ -805,7 +821,8 @@ end --- Drains the iterator. --- --- Example: ---- <pre>lua +--- +--- ```lua --- --- local it = vim.iter(vim.gsplit('abcdefg', '')) --- it:last() @@ -815,7 +832,7 @@ end --- it:last() --- -- 15 --- ---- </pre> +--- ``` --- ---@return any function Iter.last(self) @@ -839,19 +856,22 @@ end --- Add an iterator stage that returns the current iterator count as well as the iterator value. --- --- For list tables, prefer ---- <pre>lua +--- +--- ```lua --- vim.iter(ipairs(t)) ---- </pre> +--- ``` --- --- over ---- <pre>lua +--- +--- ```lua --- vim.iter(t):enumerate() ---- </pre> +--- ``` --- --- as the former is faster. --- --- Example: ---- <pre>lua +--- +--- ```lua --- --- local it = vim.iter(vim.gsplit('abc', '')):enumerate() --- it:next() @@ -861,7 +881,7 @@ end --- it:next() --- -- 3 'c' --- ---- </pre> +--- ``` --- ---@return Iter function Iter.enumerate(self) @@ -959,9 +979,10 @@ end --- Collect an iterator into a table. --- --- This is a convenience function that performs: ---- <pre>lua +--- +--- ```lua --- vim.iter(f):totable() ---- </pre> +--- ``` --- ---@param f function Iterator function ---@return table @@ -972,9 +993,10 @@ end --- Filter a table or iterator. --- --- This is a convenience function that performs: ---- <pre>lua +--- +--- ```lua --- vim.iter(src):filter(f):totable() ---- </pre> +--- ``` --- ---@see |Iter:filter()| --- @@ -990,9 +1012,10 @@ end --- Map and filter a table or iterator. --- --- This is a convenience function that performs: ---- <pre>lua +--- +--- ```lua --- vim.iter(src):map(f):totable() ---- </pre> +--- ``` --- ---@see |Iter:map()| --- diff --git a/runtime/lua/vim/keymap.lua b/runtime/lua/vim/keymap.lua index 2b55ddc787..df593be097 100644 --- a/runtime/lua/vim/keymap.lua +++ b/runtime/lua/vim/keymap.lua @@ -2,20 +2,21 @@ local keymap = {} --- Adds a new |mapping|. --- Examples: ---- <pre>lua ---- -- Map to a Lua function: ---- vim.keymap.set('n', 'lhs', function() print("real lua function") end) ---- -- Map to multiple modes: ---- vim.keymap.set({'n', 'v'}, '<leader>lr', vim.lsp.buf.references, { buffer = true }) ---- -- Buffer-local mapping: ---- vim.keymap.set('n', '<leader>w', "<cmd>w<cr>", { silent = true, buffer = 5 }) ---- -- Expr mapping: ---- vim.keymap.set('i', '<Tab>', function() ---- return vim.fn.pumvisible() == 1 and "<C-n>" or "<Tab>" ---- end, { expr = true }) ---- -- <Plug> mapping: ---- vim.keymap.set('n', '[%%', '<Plug>(MatchitNormalMultiBackward)') ---- </pre> +--- +--- ```lua +--- -- Map to a Lua function: +--- vim.keymap.set('n', 'lhs', function() print("real lua function") end) +--- -- Map to multiple modes: +--- vim.keymap.set({'n', 'v'}, '<leader>lr', vim.lsp.buf.references, { buffer = true }) +--- -- Buffer-local mapping: +--- vim.keymap.set('n', '<leader>w', "<cmd>w<cr>", { silent = true, buffer = 5 }) +--- -- Expr mapping: +--- vim.keymap.set('i', '<Tab>', function() +--- return vim.fn.pumvisible() == 1 and "<C-n>" or "<Tab>" +--- end, { expr = true }) +--- -- <Plug> mapping: +--- vim.keymap.set('n', '[%%', '<Plug>(MatchitNormalMultiBackward)') +--- ``` --- ---@param mode string|table Mode short-name, see |nvim_set_keymap()|. --- Can also be list of modes to create mapping on multiple modes. @@ -80,11 +81,13 @@ end --- Remove an existing mapping. --- Examples: ---- <pre>lua ---- vim.keymap.del('n', 'lhs') --- ---- vim.keymap.del({'n', 'i', 'v'}, '<leader>w', { buffer = 5 }) ---- </pre> +--- ```lua +--- vim.keymap.del('n', 'lhs') +--- +--- vim.keymap.del({'n', 'i', 'v'}, '<leader>w', { buffer = 5 }) +--- ``` +--- ---@param opts table|nil A table of optional arguments: --- - "buffer": (number|boolean) Remove a mapping from the given buffer. --- When `0` or `true`, use the current buffer. diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index b0caf5af35..f68ca7e88c 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -809,13 +809,14 @@ end --- Attaches the current buffer to the client. --- --- Example: ---- <pre>lua +--- +--- ```lua --- vim.lsp.start({ --- name = 'my-server-name', --- cmd = {'name-of-language-server-executable'}, --- root_dir = vim.fs.dirname(vim.fs.find({'pyproject.toml', 'setup.py'}, { upward = true })[1]), --- }) ---- </pre> +--- ``` --- --- See |vim.lsp.start_client()| for all available options. The most important are: --- @@ -1955,8 +1956,6 @@ function lsp.buf_detach_client(bufnr, client_id) local namespace = lsp.diagnostic.get_namespace(client_id) vim.diagnostic.reset(namespace, bufnr) - - vim.notify(string.format('Detached buffer (id: %d) from client (id: %d)', bufnr, client_id)) end --- Checks if a buffer is attached for a particular client. @@ -1990,9 +1989,10 @@ end --- --- You can also use the `stop()` function on a |vim.lsp.client| object. --- To stop all clients: ---- <pre>lua +--- +--- ```lua --- vim.lsp.stop_client(vim.lsp.get_clients()) ---- </pre> +--- ``` --- --- By default asks the server to shutdown, unless stop was requested --- already for this client, then force-shutdown is attempted. @@ -2504,12 +2504,7 @@ end ---@param bufnr integer Buffer number ---@param fn function Function to run on each client attached to buffer --- {bufnr}. The function takes the client, client ID, and ---- buffer number as arguments. Example: ---- <pre>lua ---- vim.lsp.for_each_buffer_client(0, function(client, client_id, bufnr) ---- vim.print(client) ---- end) ---- </pre> +--- buffer number as arguments. ---@deprecated use lsp.get_clients({ bufnr = bufnr }) with regular loop function lsp.for_each_buffer_client(bufnr, fn) return for_each_buffer_client(bufnr, fn) diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 6cd0aa1e95..8a29fac2b5 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -168,13 +168,11 @@ end --- --- - filter (function|nil): --- Predicate used to filter clients. Receives a client as argument and must return a ---- boolean. Clients matching the predicate are included. Example: ---- ---- <pre>lua ---- -- Never request typescript-language-server for formatting ---- vim.lsp.buf.format { ---- filter = function(client) return client.name ~= "tsserver" end ---- } +--- boolean. Clients matching the predicate are included. Example: <pre>lua +--- -- Never request typescript-language-server for formatting +--- vim.lsp.buf.format { +--- filter = function(client) return client.name ~= "tsserver" end +--- } --- </pre> --- --- - async boolean|nil @@ -555,11 +553,12 @@ end --- Send request to the server to resolve document highlights for the current --- text document position. This request can be triggered by a key mapping or --- by events such as `CursorHold`, e.g.: ---- <pre>vim ---- autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight() ---- autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight() ---- autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references() ---- </pre> +--- +--- ```vim +--- autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight() +--- autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight() +--- autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references() +--- ``` --- --- Note: Usage of |vim.lsp.buf.document_highlight()| requires the following highlight groups --- to be defined or you won't be able to see the actual highlights. diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua index d581eb985f..384d09ee48 100644 --- a/runtime/lua/vim/lsp/codelens.lua +++ b/runtime/lua/vim/lsp/codelens.lua @@ -255,10 +255,10 @@ end --- It is recommended to trigger this using an autocmd or via keymap. --- --- Example: ---- <pre>vim ---- autocmd BufEnter,CursorHold,InsertLeave <buffer> lua vim.lsp.codelens.refresh() ---- </pre> --- +--- ```vim +--- autocmd BufEnter,CursorHold,InsertLeave <buffer> lua vim.lsp.codelens.refresh() +--- ``` function M.refresh() local params = { textDocument = util.make_text_document_params(), diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index 2a77992c4d..73ffa1a46c 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -203,7 +203,8 @@ end --- --- See |vim.diagnostic.config()| for configuration options. Handler-specific --- configuration can be set using |vim.lsp.with()|: ---- <pre>lua +--- +--- ```lua --- vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with( --- vim.lsp.diagnostic.on_publish_diagnostics, { --- -- Enable underline, use default values @@ -221,7 +222,7 @@ end --- update_in_insert = false, --- } --- ) ---- </pre> +--- ``` --- ---@param config table Configuration table (see |vim.diagnostic.config()|). function M.on_publish_diagnostics(_, result, ctx, config) @@ -263,7 +264,8 @@ end --- --- See |vim.diagnostic.config()| for configuration options. Handler-specific --- configuration can be set using |vim.lsp.with()|: ---- <pre>lua +--- +--- ```lua --- vim.lsp.handlers["textDocument/diagnostic"] = vim.lsp.with( --- vim.lsp.diagnostic.on_diagnostic, { --- -- Enable underline, use default values @@ -281,7 +283,7 @@ end --- update_in_insert = false, --- } --- ) ---- </pre> +--- ``` --- ---@param config table Configuration table (see |vim.diagnostic.config()|). function M.on_diagnostic(_, result, ctx, config) diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index a6b70ac911..4ea3dde81c 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -342,20 +342,22 @@ M[ms.textDocument_completion] = function(_, result, _, _) end --- |lsp-handler| for the method "textDocument/hover" ---- <pre>lua ---- vim.lsp.handlers["textDocument/hover"] = vim.lsp.with( ---- vim.lsp.handlers.hover, { ---- -- Use a sharp border with `FloatBorder` highlights ---- border = "single", ---- -- add the title in hover float window ---- title = "hover" ---- } ---- ) ---- </pre> +--- +--- ```lua +--- vim.lsp.handlers["textDocument/hover"] = vim.lsp.with( +--- vim.lsp.handlers.hover, { +--- -- Use a sharp border with `FloatBorder` highlights +--- border = "single", +--- -- add the title in hover float window +--- title = "hover" +--- } +--- ) +--- ``` +--- ---@param config table Configuration table. --- - border: (default=nil) --- - Add borders to the floating window ---- - See |nvim_open_win()| +--- - See |vim.lsp.util.open_floating_preview()| for more options. function M.hover(_, result, ctx, config) config = config or {} config.focus_id = ctx.method @@ -430,19 +432,24 @@ M[ms.textDocument_typeDefinition] = location_handler M[ms.textDocument_implementation] = location_handler --- |lsp-handler| for the method "textDocument/signatureHelp". +--- --- The active parameter is highlighted with |hl-LspSignatureActiveParameter|. ---- <pre>lua ---- vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with( ---- vim.lsp.handlers.signature_help, { ---- -- Use a sharp border with `FloatBorder` highlights ---- border = "single" ---- } ---- ) ---- </pre> +--- +--- ```lua +--- vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with( +--- vim.lsp.handlers.signature_help, { +--- -- Use a sharp border with `FloatBorder` highlights +--- border = "single" +--- } +--- ) +--- ``` +--- +---@param result table Response from the language server +---@param ctx table Client context ---@param config table Configuration table. --- - border: (default=nil) --- - Add borders to the floating window ---- - See |nvim_open_win()| +--- - See |vim.lsp.util.open_floating_preview()| for more options function M.signature_help(_, result, ctx, config) config = config or {} config.focus_id = ctx.method diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua index 5b20344bd3..a5831c0beb 100644 --- a/runtime/lua/vim/lsp/semantic_tokens.lua +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -555,9 +555,10 @@ local M = {} --- delete the semanticTokensProvider table from the {server_capabilities} of --- your client in your |LspAttach| callback or your configuration's --- `on_attach` callback: ---- <pre>lua ---- client.server_capabilities.semanticTokensProvider = nil ---- </pre> +--- +--- ```lua +--- client.server_capabilities.semanticTokensProvider = nil +--- ``` --- ---@param bufnr integer ---@param client_id integer diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index a6d17afa1b..54721865b7 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -454,23 +454,6 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) end end) - -- Some LSP servers are depending on the VSCode behavior. - -- The VSCode will re-locate the cursor position after applying TextEdit so we also do it. - local is_current_buf = api.nvim_get_current_buf() == bufnr or bufnr == 0 - local cursor = (function() - if not is_current_buf then - return { - row = -1, - col = -1, - } - end - local cursor = api.nvim_win_get_cursor(0) - return { - row = cursor[1] - 1, - col = cursor[2], - } - end)() - -- save and restore local marks since they get deleted by nvim_buf_set_lines local marks = {} for _, m in pairs(vim.fn.getmarklist(bufnr or vim.api.nvim_get_current_buf())) do @@ -480,7 +463,6 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) end -- Apply text edits. - local is_cursor_fixed = false local has_eol_text_edit = false for _, text_edit in ipairs(text_edits) do -- Normalize line ending @@ -527,20 +509,6 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) e.end_col = math.min(last_line_len, e.end_col) api.nvim_buf_set_text(bufnr, e.start_row, e.start_col, e.end_row, e.end_col, e.text) - - -- Fix cursor position. - local row_count = (e.end_row - e.start_row) + 1 - if e.end_row < cursor.row then - cursor.row = cursor.row + (#e.text - row_count) - is_cursor_fixed = true - elseif e.end_row == cursor.row and e.end_col <= cursor.col then - cursor.row = cursor.row + (#e.text - row_count) - cursor.col = #e.text[#e.text] + (cursor.col - e.end_col) - if #e.text == 1 then - cursor.col = cursor.col + e.start_col - end - is_cursor_fixed = true - end end end @@ -560,16 +528,6 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) end end - -- Apply fixed cursor position. - if is_cursor_fixed then - local is_valid_cursor = true - is_valid_cursor = is_valid_cursor and cursor.row < max - is_valid_cursor = is_valid_cursor and cursor.col <= #(get_line(bufnr, cursor.row) or '') - if is_valid_cursor then - api.nvim_win_set_cursor(0, { cursor.row + 1, cursor.col }) - end - end - -- Remove final line if needed local fix_eol = has_eol_text_edit fix_eol = fix_eol and (vim.bo[bufnr].eol or (vim.bo[bufnr].fixeol and not vim.bo[bufnr].binary)) @@ -1087,6 +1045,12 @@ end --- - focusable (string or table) override `focusable` --- - zindex (string or table) override `zindex`, defaults to 50 --- - relative ("mouse"|"cursor") defaults to "cursor" +--- - anchor_bias ("auto"|"above"|"below") defaults to "auto" +--- - "auto": place window based on which side of the cursor has more lines +--- - "above": place the window above the cursor unless there are not enough lines +--- to display the full window height. +--- - "below": place the window below the cursor unless there are not enough lines +--- to display the full window height. ---@return table Options function M.make_floating_popup_options(width, height, opts) validate({ @@ -1105,7 +1069,20 @@ function M.make_floating_popup_options(width, height, opts) or vim.fn.winline() - 1 local lines_below = vim.fn.winheight(0) - lines_above - if lines_above < lines_below then + local anchor_bias = opts.anchor_bias or 'auto' + + local anchor_below + + if anchor_bias == 'below' then + anchor_below = (lines_below > lines_above) or (height <= lines_below) + elseif anchor_bias == 'above' then + local anchor_above = (lines_above > lines_below) or (height <= lines_above) + anchor_below = not anchor_above + else + anchor_below = lines_below > lines_above + end + + if anchor_below then anchor = anchor .. 'N' height = math.min(lines_below, height) row = 1 @@ -1635,7 +1612,8 @@ end --- ---@param contents table of lines to show in window ---@param syntax string of syntax to set for opened buffer ----@param opts table with optional fields (additional keys are passed on to |nvim_open_win()|) +---@param opts table with optional fields (additional keys are filtered with |vim.lsp.util.make_floating_popup_options()| +--- before they are passed on to |nvim_open_win()|) --- - height: (integer) height of floating window --- - width: (integer) width of floating window --- - wrap: (boolean, default true) wrap long lines diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 422d49d746..0c38fa955a 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -65,19 +65,21 @@ end --- (as opposed to |vim.split()| which is "eager"). --- --- Example: ---- <pre>lua ---- for s in vim.gsplit(':aa::b:', ':', {plain=true}) do ---- print(s) ---- end ---- </pre> +--- +--- ```lua +--- for s in vim.gsplit(':aa::b:', ':', {plain=true}) do +--- print(s) +--- end +--- ``` --- --- If you want to also inspect the separator itself (instead of discarding it), use --- |string.gmatch()|. Example: ---- <pre>lua ---- for word, num in ('foo111bar222'):gmatch('([^0-9]*)(%d*)') do ---- print(('word: %s num: %s'):format(word, num)) ---- end ---- </pre> +--- +--- ```lua +--- for word, num in ('foo111bar222'):gmatch('([^0-9]*)(%d*)') do +--- print(('word: %s num: %s'):format(word, num)) +--- end +--- ``` --- --- @see |string.gmatch()| --- @see |vim.split()| @@ -165,12 +167,13 @@ end --- |vim.gsplit()|). --- --- Examples: ---- <pre>lua ---- split(":aa::b:", ":") --> {'','aa','','b',''} ---- split("axaby", "ab?") --> {'','x','y'} ---- split("x*yz*o", "*", {plain=true}) --> {'x','yz','o'} ---- split("|x|y|z|", "|", {trimempty=true}) --> {'x', 'y', 'z'} ---- </pre> +--- +--- ```lua +--- split(":aa::b:", ":") --> {'','aa','','b',''} +--- split("axaby", "ab?") --> {'','x','y'} +--- split("x*yz*o", "*", {plain=true}) --> {'x','yz','o'} +--- split("|x|y|z|", "|", {trimempty=true}) --> {'x', 'y', 'z'} +--- ``` --- ---@see |vim.gsplit()| ---@see |string.gmatch()| @@ -259,12 +262,13 @@ end --- a predicate that is checked for each value. --- --- Example: ---- <pre>lua ---- vim.tbl_contains({ 'a', { 'b', 'c' } }, function(v) ---- return vim.deep_equal(v, { 'b', 'c' }) ---- end, { predicate = true }) ---- -- true ---- </pre> +--- +--- ```lua +--- vim.tbl_contains({ 'a', { 'b', 'c' } }, function(v) +--- return vim.deep_equal(v, { 'b', 'c' }) +--- end, { predicate = true }) +--- -- true +--- ``` --- ---@see |vim.list_contains()| for checking values in list-like tables --- @@ -455,10 +459,11 @@ end --- Return `nil` if the key does not exist. --- --- Examples: ---- <pre>lua ---- vim.tbl_get({ key = { nested_key = true }}, 'key', 'nested_key') == true ---- vim.tbl_get({ key = {}}, 'key', 'nested_key') == nil ---- </pre> +--- +--- ```lua +--- vim.tbl_get({ key = { nested_key = true }}, 'key', 'nested_key') == true +--- vim.tbl_get({ key = {}}, 'key', 'nested_key') == nil +--- ``` --- ---@param o table Table to index ---@param ... any Optional keys (0 or more, variadic) via which to index the table @@ -626,10 +631,10 @@ end --- Counts the number of non-nil values in table `t`. --- ---- <pre>lua +--- ```lua --- vim.tbl_count({ a=1, b=2 }) --> 2 --- vim.tbl_count({ 1, 2 }) --> 2 ---- </pre> +--- ``` --- ---@see https://github.com/Tieske/Penlight/blob/master/lua/pl/tablex.lua ---@param t table Table @@ -703,38 +708,41 @@ end --- Validates a parameter specification (types and values). --- --- Usage example: ---- <pre>lua ---- function user.new(name, age, hobbies) ---- vim.validate{ ---- name={name, 'string'}, ---- age={age, 'number'}, ---- hobbies={hobbies, 'table'}, ---- } ---- ... ---- end ---- </pre> +--- +--- ```lua +--- function user.new(name, age, hobbies) +--- vim.validate{ +--- name={name, 'string'}, +--- age={age, 'number'}, +--- hobbies={hobbies, 'table'}, +--- } +--- ... +--- end +--- ``` --- --- Examples with explicit argument values (can be run directly): ---- <pre>lua ---- vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}} ---- --> NOP (success) --- ---- vim.validate{arg1={1, 'table'}} ---- --> error('arg1: expected table, got number') +--- ```lua +--- vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}} +--- --> NOP (success) --- ---- vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}} ---- --> error('arg1: expected even number, got 3') ---- </pre> +--- vim.validate{arg1={1, 'table'}} +--- --> error('arg1: expected table, got number') +--- +--- vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}} +--- --> error('arg1: expected even number, got 3') +--- ``` --- --- If multiple types are valid they can be given as a list. ---- <pre>lua ---- vim.validate{arg1={{'foo'}, {'table', 'string'}}, arg2={'foo', {'table', 'string'}}} ---- --> NOP (success) --- ---- vim.validate{arg1={1, {'string', 'table'}}} ---- --> error('arg1: expected string|table, got number') +--- ```lua +--- vim.validate{arg1={{'foo'}, {'table', 'string'}}, arg2={'foo', {'table', 'string'}}} +--- -- NOP (success) +--- +--- vim.validate{arg1={1, {'string', 'table'}}} +--- -- error('arg1: expected string|table, got number') --- ---- </pre> +--- ``` --- ---@param opt table Names of parameters to validate. Each key is a parameter --- name; each value is a tuple in one of these forms: @@ -866,10 +874,10 @@ end --- If {create} is `nil`, this will create a defaulttable whose constructor function is --- this function, effectively allowing to create nested tables on the fly: --- ---- <pre>lua +--- ```lua --- local a = vim.defaulttable() --- a.b.c = 1 ---- </pre> +--- ``` --- ---@param create function?(key:any):any The function called to create a missing value. ---@return table Empty table with metamethod @@ -938,7 +946,7 @@ do --- Create a ring buffer limited to a maximal number of items. --- Once the buffer is full, adding a new entry overrides the oldest entry. --- - --- <pre> + --- ```lua --- local ringbuf = vim.ringbuf(4) --- ringbuf:push("a") --- ringbuf:push("b") @@ -952,7 +960,7 @@ do --- for val in ringbuf do --- print(val) --- end - --- </pre> + --- ``` --- --- Returns a Ringbuf instance with the following methods: --- diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 4f84fc2e0f..cc8be72670 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -441,14 +441,15 @@ end --- In this case, add ``vim.bo.syntax = 'on'`` after the call to `start`. --- --- Example: ---- <pre>lua +--- +--- ```lua --- vim.api.nvim_create_autocmd( 'FileType', { pattern = 'tex', --- callback = function(args) --- vim.treesitter.start(args.buf, 'latex') --- vim.bo[args.buf].syntax = 'on' -- only if additional legacy syntax is needed --- end --- }) ---- </pre> +--- ``` --- ---@param bufnr (integer|nil) Buffer to be highlighted (default: current buffer) ---@param lang (string|nil) Language of the parser (default: buffer filetype) @@ -472,7 +473,7 @@ end --- Open a window that displays a textual representation of the nodes in the language tree. --- --- While in the window, press "a" to toggle display of anonymous nodes, "I" to toggle the ---- display of the source language of each node, "o" to toggle the query previewer, and press +--- display of the source language of each node, "o" to toggle the query editor, and press --- <Enter> to jump to the node under the cursor in the source buffer. --- --- Can also be shown with `:InspectTree`. *:InspectTree* @@ -494,17 +495,12 @@ function M.inspect_tree(opts) require('vim.treesitter.dev').inspect_tree(opts) end ---- Open a window for live editing of a treesitter query. ---- ---- Can also be shown with `:PreviewQuery`. *:PreviewQuery* -function M.preview_query() - require('vim.treesitter.dev').preview_query() -end - --- Returns the fold level for {lnum} in the current buffer. Can be set directly to 'foldexpr': ---- <pre>lua +--- +--- ```lua --- vim.wo.foldexpr = 'v:lua.vim.treesitter.foldexpr()' ---- </pre> +--- ``` +--- ---@param lnum integer|nil Line number to calculate fold level for ---@return string function M.foldexpr(lnum) diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index d82e04a5a8..8bc08c9c2e 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -299,7 +299,9 @@ local function on_changedtree(bufnr, foldinfo, tree_changes) local srow, _, erow = Range.unpack4(change) get_folds_levels(bufnr, foldinfo, srow, erow) end - foldupdate(bufnr) + if #tree_changes > 0 then + foldupdate(bufnr) + end end) end diff --git a/runtime/lua/vim/treesitter/_query_linter.lua b/runtime/lua/vim/treesitter/_query_linter.lua index 3dd0177a81..abf0bf345d 100644 --- a/runtime/lua/vim/treesitter/_query_linter.lua +++ b/runtime/lua/vim/treesitter/_query_linter.lua @@ -1,8 +1,6 @@ local api = vim.api local namespace = api.nvim_create_namespace('vim.treesitter.query_linter') --- those node names exist for every language -local BUILT_IN_NODE_NAMES = { '_', 'ERROR' } local M = {} @@ -10,11 +8,13 @@ local M = {} --- @field langs string[] --- @field clear boolean +--- @alias vim.treesitter.ParseError {msg: string, range: Range4} + --- @private --- Caches parse results for queries for each language. --- Entries of parse_cache[lang][query_text] will either be true for successful parse or contain the ---- error message of the parse ---- @type table<string,table<string,string|true>> +--- message and range of the parse error. +--- @type table<string,table<string,vim.treesitter.ParseError|true>> local parse_cache = {} --- Contains language dependent context for the query linter @@ -26,20 +26,16 @@ local parse_cache = {} --- @private --- Adds a diagnostic for node in the query buffer --- @param diagnostics Diagnostic[] ---- @param node TSNode ---- @param buf integer +--- @param range Range4 --- @param lint string --- @param lang string? -local function add_lint_for_node(diagnostics, node, buf, lint, lang) - local node_text = vim.treesitter.get_node_text(node, buf):gsub('\n', ' ') - --- @type string - local message = lint .. ': ' .. node_text - local error_range = { node:range() } +local function add_lint_for_node(diagnostics, range, lint, lang) + local message = lint:gsub('\n', ' ') diagnostics[#diagnostics + 1] = { - lnum = error_range[1], - end_lnum = error_range[3], - col = error_range[2], - end_col = error_range[4], + lnum = range[1], + end_lnum = range[3], + col = range[2], + end_col = range[4], severity = vim.diagnostic.ERROR, message = message, source = lang, @@ -92,6 +88,31 @@ local lint_query = [[;; query ]] --- @private +--- @param err string +--- @param node TSNode +--- @return vim.treesitter.ParseError +local function get_error_entry(err, node) + local start_line, start_col = node:range() + local line_offset, col_offset, msg = err:gmatch('.-:%d+: Query error at (%d+):(%d+)%. ([^:]+)')() ---@type string, string, string + start_line, start_col = + start_line + tonumber(line_offset) - 1, start_col + tonumber(col_offset) - 1 + local end_line, end_col = start_line, start_col + if msg:match('^Invalid syntax') or msg:match('^Impossible') then + -- Use the length of the underlined node + local underlined = vim.split(err, '\n')[2] + end_col = end_col + #underlined + elseif msg:match('^Invalid') then + -- Use the length of the problematic type/capture/field + end_col = end_col + #msg:match('"([^"]+)"') + end + + return { + msg = msg, + range = { start_line, start_col, end_line, end_col }, + } +end + +--- @private --- @param node TSNode --- @param buf integer --- @param lang string @@ -106,104 +127,19 @@ local function check_toplevel(node, buf, lang, diagnostics) local lang_cache = parse_cache[lang] if lang_cache[query_text] == nil then - local ok, err = pcall(vim.treesitter.query.parse, lang, query_text) + local cache_val, err = pcall(vim.treesitter.query.parse, lang, query_text) ---@type boolean|vim.treesitter.ParseError, string|Query - if not ok and type(err) == 'string' then - err = err:match('.-:%d+: (.+)') + if not cache_val and type(err) == 'string' then + cache_val = get_error_entry(err, node) end - lang_cache[query_text] = ok or err + lang_cache[query_text] = cache_val end local cache_entry = lang_cache[query_text] - if type(cache_entry) == 'string' then - add_lint_for_node(diagnostics, node, buf, cache_entry, lang) - end -end - ---- @private ---- @param node TSNode ---- @param buf integer ---- @param lang string ---- @param parser_info table ---- @param diagnostics Diagnostic[] -local function check_field(node, buf, lang, parser_info, diagnostics) - local field_name = vim.treesitter.get_node_text(node, buf) - if not vim.tbl_contains(parser_info.fields, field_name) then - add_lint_for_node(diagnostics, node, buf, 'Invalid field', lang) - end -end - ---- @private ---- @param node TSNode ---- @param buf integer ---- @param lang string ---- @param parser_info (table) ---- @param diagnostics Diagnostic[] -local function check_node(node, buf, lang, parser_info, diagnostics) - local node_type = vim.treesitter.get_node_text(node, buf) - local is_named = node_type:sub(1, 1) ~= '"' - - if not is_named then - node_type = node_type:gsub('"(.*)".*$', '%1'):gsub('\\(.)', '%1') - end - - local found = vim.tbl_contains(BUILT_IN_NODE_NAMES, node_type) - or vim.tbl_contains(parser_info.symbols, function(s) - return vim.deep_equal(s, { node_type, is_named }) - end, { predicate = true }) - - if not found then - add_lint_for_node(diagnostics, node, buf, 'Invalid node type', lang) - end -end - ---- @private ---- @param node TSNode ---- @param buf integer ---- @param is_predicate boolean ---- @return string -local function get_predicate_name(node, buf, is_predicate) - local name = vim.treesitter.get_node_text(node, buf) - if is_predicate then - if vim.startswith(name, 'not-') then - --- @type string - name = name:sub(string.len('not-') + 1) - end - return name .. '?' - end - return name .. '!' -end - ---- @private ---- @param predicate_node TSNode ---- @param predicate_type_node TSNode ---- @param buf integer ---- @param lang string? ---- @param diagnostics Diagnostic[] -local function check_predicate(predicate_node, predicate_type_node, buf, lang, diagnostics) - local type_string = vim.treesitter.get_node_text(predicate_type_node, buf) - - -- Quirk of the query grammar that directives are also predicates! - if type_string == '?' then - if - not vim.tbl_contains( - vim.treesitter.query.list_predicates(), - get_predicate_name(predicate_node, buf, true) - ) - then - add_lint_for_node(diagnostics, predicate_node, buf, 'Unknown predicate', lang) - end - elseif type_string == '!' then - if - not vim.tbl_contains( - vim.treesitter.query.list_directives(), - get_predicate_name(predicate_node, buf, false) - ) - then - add_lint_for_node(diagnostics, predicate_node, buf, 'Unknown directive', lang) - end + if type(cache_entry) ~= 'boolean' then + add_lint_for_node(diagnostics, cache_entry.range, cache_entry.msg, lang) end end @@ -214,8 +150,6 @@ end --- @param lang_context QueryLinterLanguageContext --- @param diagnostics Diagnostic[] local function lint_match(buf, match, query, lang_context, diagnostics) - local predicate --- @type TSNode - local predicate_type --- @type TSNode local lang = lang_context.lang local parser_info = lang_context.parser_info @@ -223,31 +157,16 @@ local function lint_match(buf, match, query, lang_context, diagnostics) local cap_id = query.captures[id] -- perform language-independent checks only for first lang - if lang_context.is_first_lang then - if cap_id == 'error' then - add_lint_for_node(diagnostics, node, buf, 'Syntax error') - elseif cap_id == 'predicate.name' then - predicate = node - elseif cap_id == 'predicate.type' then - predicate_type = node - end + if lang_context.is_first_lang and cap_id == 'error' then + local node_text = vim.treesitter.get_node_text(node, buf):gsub('\n', ' ') + add_lint_for_node(diagnostics, { node:range() }, 'Syntax error: ' .. node_text) end -- other checks rely on Neovim parser introspection - if lang and parser_info then - if cap_id == 'toplevel' then - check_toplevel(node, buf, lang, diagnostics) - elseif cap_id == 'field' then - check_field(node, buf, lang, parser_info, diagnostics) - elseif cap_id == 'node.named' or cap_id == 'node.anonymous' then - check_node(node, buf, lang, parser_info, diagnostics) - end + if lang and parser_info and cap_id == 'toplevel' then + check_toplevel(node, buf, lang, diagnostics) end end - - if predicate and predicate_type then - check_predicate(predicate, predicate_type, buf, lang, diagnostics) - end end --- @private @@ -339,7 +258,7 @@ function M.omnifunc(findstart, base) end end for _, s in pairs(parser_info.symbols) do - local text = s[2] and s[1] or '"' .. s[1]:gsub([[\]], [[\\]]) .. '"' + local text = s[2] and s[1] or '"' .. s[1]:gsub([[\]], [[\\]]) .. '"' ---@type string if text:find(base, 1, true) then table.insert(items, text) end diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index 72b6e3db4a..bc54853103 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -101,18 +101,18 @@ function TSTreeView:new(bufnr, lang) -- the root in the child tree to the {injections} table. local root = parser:parse(true)[1]:root() local injections = {} ---@type table<integer,table> - parser:for_each_child(function(child, lang_) - child:for_each_tree(function(tree) + for _, child in pairs(parser:children()) do + child:for_each_tree(function(tree, ltree) local r = tree:root() local node = root:named_descendant_for_range(r:range()) if node then injections[node:id()] = { - lang = lang_, + lang = ltree:lang(), root = r, } end end) - end) + end local nodes = traverse(root, 0, parser:lang(), injections, {}) @@ -351,11 +351,11 @@ function M.inspect_tree(opts) end, }) api.nvim_buf_set_keymap(b, 'n', 'o', '', { - desc = 'Toggle query previewer', + desc = 'Toggle query editor', callback = function() - local preview_w = vim.b[buf].dev_preview - if not preview_w or not close_win(preview_w) then - M.preview_query() + local edit_w = vim.b[buf].dev_edit + if not edit_w or not close_win(edit_w) then + M.edit_query() end end, }) @@ -464,16 +464,16 @@ function M.inspect_tree(opts) }) end -local preview_ns = api.nvim_create_namespace('treesitter/dev-preview') +local edit_ns = api.nvim_create_namespace('treesitter/dev-edit') ---@param query_win integer ---@param base_win integer -local function update_preview_highlights(query_win, base_win) +local function update_editor_highlights(query_win, base_win) local base_buf = api.nvim_win_get_buf(base_win) local query_buf = api.nvim_win_get_buf(query_win) local parser = vim.treesitter.get_parser(base_buf) local lang = parser:lang() - api.nvim_buf_clear_namespace(base_buf, preview_ns, 0, -1) + api.nvim_buf_clear_namespace(base_buf, edit_ns, 0, -1) local query_content = table.concat(api.nvim_buf_get_lines(query_buf, 0, -1, false), '\n') local ok_query, query = pcall(vim.treesitter.query.parse, lang, query_content) @@ -493,7 +493,7 @@ local function update_preview_highlights(query_win, base_win) local capture_name = query.captures[id] if capture_name == cursor_word then local lnum, col, end_lnum, end_col = node:range() - api.nvim_buf_set_extmark(base_buf, preview_ns, lnum, col, { + api.nvim_buf_set_extmark(base_buf, edit_ns, lnum, col, { end_row = end_lnum, end_col = end_col, hl_group = 'Visual', @@ -506,17 +506,17 @@ local function update_preview_highlights(query_win, base_win) end --- @private -function M.preview_query() +function M.edit_query() local buf = api.nvim_get_current_buf() local win = api.nvim_get_current_win() - -- Close any existing previewer window - if vim.b[buf].dev_preview then - close_win(vim.b[buf].dev_preview) + -- Close any existing editor window + if vim.b[buf].dev_edit then + close_win(vim.b[buf].dev_edit) end local cmd = '60vnew' - -- If the inspector is open, place the previewer above it. + -- If the inspector is open, place the editor above it. local base_win = vim.b[buf].dev_base ---@type integer? local base_buf = base_win and api.nvim_win_get_buf(base_win) local inspect_win = base_buf and vim.b[base_buf].dev_inspect @@ -537,20 +537,20 @@ function M.preview_query() local query_win = api.nvim_get_current_win() local query_buf = api.nvim_win_get_buf(query_win) - vim.b[buf].dev_preview = query_win + vim.b[buf].dev_edit = query_win vim.bo[query_buf].omnifunc = 'v:lua.vim.treesitter.query.omnifunc' set_dev_properties(query_win, query_buf) -- Note that omnifunc guesses the language based on the containing folder, -- so we add the parser's language to the buffer's name so that omnifunc -- can infer the language later. - api.nvim_buf_set_name(query_buf, string.format('%s/query_previewer.scm', lang)) + api.nvim_buf_set_name(query_buf, string.format('%s/query_editor.scm', lang)) - local group = api.nvim_create_augroup('treesitter/dev-preview', {}) + local group = api.nvim_create_augroup('treesitter/dev-edit', {}) api.nvim_create_autocmd({ 'TextChanged', 'InsertLeave' }, { group = group, buffer = query_buf, - desc = 'Update query previewer diagnostics when the query changes', + desc = 'Update query editor diagnostics when the query changes', callback = function() vim.treesitter.query.lint(query_buf, { langs = lang, clear = false }) end, @@ -558,37 +558,37 @@ function M.preview_query() api.nvim_create_autocmd({ 'TextChanged', 'InsertLeave', 'CursorMoved', 'BufEnter' }, { group = group, buffer = query_buf, - desc = 'Update query previewer highlights when the cursor moves', + desc = 'Update query editor highlights when the cursor moves', callback = function() if api.nvim_win_is_valid(win) then - update_preview_highlights(query_win, win) + update_editor_highlights(query_win, win) end end, }) api.nvim_create_autocmd('BufLeave', { group = group, buffer = query_buf, - desc = 'Clear the query previewer highlights when leaving the previewer', + desc = 'Clear highlights when leaving the query editor', callback = function() - api.nvim_buf_clear_namespace(buf, preview_ns, 0, -1) + api.nvim_buf_clear_namespace(buf, edit_ns, 0, -1) end, }) api.nvim_create_autocmd('BufLeave', { group = group, buffer = buf, - desc = 'Clear the query previewer highlights when leaving the source buffer', + desc = 'Clear the query editor highlights when leaving the source buffer', callback = function() if not api.nvim_buf_is_loaded(query_buf) then return true end - api.nvim_buf_clear_namespace(query_buf, preview_ns, 0, -1) + api.nvim_buf_clear_namespace(query_buf, edit_ns, 0, -1) end, }) api.nvim_create_autocmd('BufHidden', { group = group, buffer = buf, - desc = 'Close the previewer window when the source buffer is hidden', + desc = 'Close the editor window when the source buffer is hidden', once = true, callback = function() close_win(query_win) diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 56b075b723..8d4d6a9337 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -322,7 +322,7 @@ function TSHighlighter._on_win(_, _win, buf, topline, botline) if not self then return false end - self.tree:parse({ topline, botline }) + self.tree:parse({ topline, botline + 1 }) self:reset_highlight_state() self.redraw_count = self.redraw_count + 1 return true diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index 9695e2c41c..15bf666a1e 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -82,9 +82,8 @@ function M.add(lang, opts) filetype = { filetype, { 'string', 'table' }, true }, }) - M.register(lang, filetype) - if vim._ts_has_language(lang) then + M.register(lang, filetype) return end @@ -102,6 +101,7 @@ function M.add(lang, opts) end vim._ts_add_language(path, lang, symbol_name) + M.register(lang, filetype) end --- @param x string|string[] diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index e81778b269..79f36a27fd 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -7,9 +7,9 @@ --- --- To create a LanguageTree (parser object) for a given buffer and language, use: --- ---- <pre>lua ---- local parser = vim.treesitter.get_parser(bufnr, lang) ---- </pre> +--- ```lua +--- local parser = vim.treesitter.get_parser(bufnr, lang) +--- ``` --- --- (where `bufnr=0` means current buffer). `lang` defaults to 'filetype'. --- Note: currently the parser is retained for the lifetime of a buffer but this may change; @@ -17,9 +17,9 @@ --- --- Whenever you need to access the current syntax tree, parse the buffer: --- ---- <pre>lua ---- local tree = parser:parse({ start_row, end_row }) ---- </pre> +--- ```lua +--- local tree = parser:parse({ start_row, end_row }) +--- ``` --- --- This returns a table of immutable |treesitter-tree| objects representing the current state of --- the buffer. When the plugin wants to access the state after a (possible) edit it must call @@ -444,18 +444,21 @@ function LanguageTree:parse(range) range = range, }) - self:for_each_child(function(child) + for _, child in pairs(self._children) do child:parse(range) - end) + end return self._trees end +---@deprecated Misleading name. Use `LanguageTree:children()` (non-recursive) instead, +--- add recursion yourself if needed. --- Invokes the callback for each |LanguageTree| and its children recursively --- ---@param fn fun(tree: LanguageTree, lang: string) ---@param include_self boolean|nil Whether to include the invoking tree in the results function LanguageTree:for_each_child(fn, include_self) + vim.deprecate('LanguageTree:for_each_child()', 'LanguageTree:children()', '0.11') if include_self then fn(self, self._lang) end @@ -897,6 +900,20 @@ function LanguageTree:_edit( end return true end) + + for _, child in pairs(self._children) do + child:_edit( + start_byte, + end_byte_old, + end_byte_new, + start_row, + start_col, + end_row_old, + end_col_old, + end_row_new, + end_col_new + ) + end end ---@package @@ -943,20 +960,17 @@ function LanguageTree:_on_bytes( ) -- Edit trees together BEFORE emitting a bytes callback. - ---@private - self:for_each_child(function(child) - child:_edit( - start_byte, - start_byte + old_byte, - start_byte + new_byte, - start_row, - start_col, - start_row + old_row, - old_end_col, - start_row + new_row, - new_end_col - ) - end, true) + self:_edit( + start_byte, + start_byte + old_byte, + start_byte + new_byte, + start_row, + start_col, + start_row + old_row, + old_end_col, + start_row + new_row, + new_end_col + ) self:_do_callback( 'bytes', @@ -1017,9 +1031,9 @@ function LanguageTree:register_cbs(cbs, recursive) end if recursive then - self:for_each_child(function(child) + for _, child in pairs(self._children) do child:register_cbs(cbs, true) - end) + end end end diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 3093657313..d7973cc48f 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -692,7 +692,8 @@ end --- The iterator returns three values: a numeric id identifying the capture, --- the captured node, and metadata from any directives processing the match. --- The following example shows how to get captures by name: ---- <pre>lua +--- +--- ```lua --- for id, node, metadata in query:iter_captures(tree:root(), bufnr, first, last) do --- local name = query.captures[id] -- name of the capture in the query --- -- typically useful info about the node: @@ -700,7 +701,7 @@ end --- local row1, col1, row2, col2 = node:range() -- range of the capture --- -- ... use the info here ... --- end ---- </pre> +--- ``` --- ---@param node TSNode under which the search will occur ---@param source (integer|string) Source buffer or string to extract text from @@ -743,7 +744,8 @@ end --- If the query has more than one pattern, the capture table might be sparse --- and e.g. `pairs()` method should be used over `ipairs`. --- Here is an example iterating over all captures in every match: ---- <pre>lua +--- +--- ```lua --- for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, first, last) do --- for id, node in pairs(match) do --- local name = query.captures[id] @@ -754,7 +756,7 @@ end --- -- ... use the info here ... --- end --- end ---- </pre> +--- ``` --- ---@param node TSNode under which the search will occur ---@param source (integer|string) Source buffer or string to search @@ -824,11 +826,22 @@ end --- Omnifunc for completing node names and predicates in treesitter queries. --- --- Use via ---- <pre>lua ---- vim.bo.omnifunc = 'v:lua.vim.treesitter.query.omnifunc' ---- </pre> +--- +--- ```lua +--- vim.bo.omnifunc = 'v:lua.vim.treesitter.query.omnifunc' +--- ``` +--- function M.omnifunc(findstart, base) return require('vim.treesitter._query_linter').omnifunc(findstart, base) end +--- Open a window for live editing of a treesitter query. +--- +--- Can also be shown with `:EditQuery`. *:EditQuery* +--- +--- Note that the editor opens a scratch buffer, and so queries aren't persisted on disk. +function M.edit() + require('vim.treesitter.dev').edit_query() +end + return M diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua index cd90886489..b6ddf337ce 100644 --- a/runtime/lua/vim/ui.lua +++ b/runtime/lua/vim/ui.lua @@ -3,6 +3,23 @@ local M = {} --- Prompts the user to pick from a list of items, allowing arbitrary (potentially asynchronous) --- work until `on_choice`. --- +--- Example: +--- +--- ```lua +--- vim.ui.select({ 'tabs', 'spaces' }, { +--- prompt = 'Select tabs or spaces:', +--- format_item = function(item) +--- return "I'd like to choose " .. item +--- end, +--- }, function(choice) +--- if choice == 'spaces' then +--- vim.o.expandtab = true +--- else +--- vim.o.expandtab = false +--- end +--- end) +--- ``` +--- ---@param items table Arbitrary items ---@param opts table Additional options --- - prompt (string|nil) @@ -19,23 +36,6 @@ local M = {} --- Called once the user made a choice. --- `idx` is the 1-based index of `item` within `items`. --- `nil` if the user aborted the dialog. ---- ---- ---- Example: ---- <pre>lua ---- vim.ui.select({ 'tabs', 'spaces' }, { ---- prompt = 'Select tabs or spaces:', ---- format_item = function(item) ---- return "I'd like to choose " .. item ---- end, ---- }, function(choice) ---- if choice == 'spaces' then ---- vim.o.expandtab = true ---- else ---- vim.o.expandtab = false ---- end ---- end) ---- </pre> function M.select(items, opts, on_choice) vim.validate({ items = { items, 'table', false }, @@ -58,6 +58,14 @@ end --- Prompts the user for input, allowing arbitrary (potentially asynchronous) work until --- `on_confirm`. --- +--- Example: +--- +--- ```lua +--- vim.ui.input({ prompt = 'Enter value for shiftwidth: ' }, function(input) +--- vim.o.shiftwidth = tonumber(input) +--- end) +--- ``` +--- ---@param opts table Additional options. See |input()| --- - prompt (string|nil) --- Text of the prompt @@ -77,13 +85,6 @@ end --- `input` is what the user typed (it might be --- an empty string if nothing was entered), or --- `nil` if the user aborted the dialog. ---- ---- Example: ---- <pre>lua ---- vim.ui.input({ prompt = 'Enter value for shiftwidth: ' }, function(input) ---- vim.o.shiftwidth = tonumber(input) ---- end) ---- </pre> function M.input(opts, on_confirm) vim.validate({ on_confirm = { on_confirm, 'function', false }, @@ -110,11 +111,12 @@ end --- Expands "~/" and environment variables in filesystem paths. --- --- Examples: ---- <pre>lua +--- +--- ```lua --- vim.ui.open("https://neovim.io/") --- vim.ui.open("~/path/to/file") --- vim.ui.open("$VIMRUNTIME") ---- </pre> +--- ``` --- ---@param path string Path or URL to open --- diff --git a/runtime/lua/vim/version.lua b/runtime/lua/vim/version.lua index 056e1678ff..306eef90d3 100644 --- a/runtime/lua/vim/version.lua +++ b/runtime/lua/vim/version.lua @@ -5,12 +5,13 @@ --- available tools and dependencies on the current system. --- --- Example: ---- <pre>lua ---- local v = vim.version.parse(vim.fn.system({'tmux', '-V'}), {strict=false}) ---- if vim.version.gt(v, {3, 2, 0}) then ---- -- ... ---- end ---- </pre> +--- +--- ```lua +--- local v = vim.version.parse(vim.fn.system({'tmux', '-V'}), {strict=false}) +--- if vim.version.gt(v, {3, 2, 0}) then +--- -- ... +--- end +--- ``` --- --- \*vim.version()\* returns the version of the current Nvim process. --- @@ -21,35 +22,36 @@ --- --- Supported range specs are shown in the following table. --- Note: suffixed versions (1.2.3-rc1) are not matched. ---- <pre> ---- 1.2.3 is 1.2.3 ---- =1.2.3 is 1.2.3 ---- >1.2.3 greater than 1.2.3 ---- <1.2.3 before 1.2.3 ---- >=1.2.3 at least 1.2.3 ---- ~1.2.3 is >=1.2.3 <1.3.0 "reasonably close to 1.2.3" ---- ^1.2.3 is >=1.2.3 <2.0.0 "compatible with 1.2.3" ---- ^0.2.3 is >=0.2.3 <0.3.0 (0.x.x is special) ---- ^0.0.1 is =0.0.1 (0.0.x is special) ---- ^1.2 is >=1.2.0 <2.0.0 (like ^1.2.0) ---- ~1.2 is >=1.2.0 <1.3.0 (like ~1.2.0) ---- ^1 is >=1.0.0 <2.0.0 "compatible with 1" ---- ~1 same "reasonably close to 1" ---- 1.x same ---- 1.* same ---- 1 same ---- * any version ---- x same --- ---- 1.2.3 - 2.3.4 is >=1.2.3 <=2.3.4 +--- ``` +--- 1.2.3 is 1.2.3 +--- =1.2.3 is 1.2.3 +--- >1.2.3 greater than 1.2.3 +--- <1.2.3 before 1.2.3 +--- >=1.2.3 at least 1.2.3 +--- ~1.2.3 is >=1.2.3 <1.3.0 "reasonably close to 1.2.3" +--- ^1.2.3 is >=1.2.3 <2.0.0 "compatible with 1.2.3" +--- ^0.2.3 is >=0.2.3 <0.3.0 (0.x.x is special) +--- ^0.0.1 is =0.0.1 (0.0.x is special) +--- ^1.2 is >=1.2.0 <2.0.0 (like ^1.2.0) +--- ~1.2 is >=1.2.0 <1.3.0 (like ~1.2.0) +--- ^1 is >=1.0.0 <2.0.0 "compatible with 1" +--- ~1 same "reasonably close to 1" +--- 1.x same +--- 1.* same +--- 1 same +--- * any version +--- x same +--- +--- 1.2.3 - 2.3.4 is >=1.2.3 <=2.3.4 --- ---- Partial right: missing pieces treated as x (2.3 => 2.3.x). ---- 1.2.3 - 2.3 is >=1.2.3 <2.4.0 ---- 1.2.3 - 2 is >=1.2.3 <3.0.0 +--- Partial right: missing pieces treated as x (2.3 => 2.3.x). +--- 1.2.3 - 2.3 is >=1.2.3 <2.4.0 +--- 1.2.3 - 2 is >=1.2.3 <3.0.0 --- ---- Partial left: missing pieces treated as 0 (1.2 => 1.2.0). ---- 1.2 - 2.3.0 is 1.2.0 - 2.3.0 ---- </pre> +--- Partial left: missing pieces treated as 0 (1.2 => 1.2.0). +--- 1.2 - 2.3.0 is 1.2.0 - 2.3.0 +--- ``` local M = {} @@ -237,29 +239,32 @@ function VersionRange:has(version) end --- Parses a semver |version-range| "spec" and returns a range object: ---- <pre> ---- { ---- from: Version ---- to: Version ---- has(v: string|Version) ---- } ---- </pre> +--- +--- ``` +--- { +--- from: Version +--- to: Version +--- has(v: string|Version) +--- } +--- ``` --- --- `:has()` checks if a version is in the range (inclusive `from`, exclusive `to`). --- --- Example: ---- <pre>lua ---- local r = vim.version.range('1.0.0 - 2.0.0') ---- print(r:has('1.9.9')) -- true ---- print(r:has('2.0.0')) -- false ---- print(r:has(vim.version())) -- check against current Nvim version ---- </pre> +--- +--- ```lua +--- local r = vim.version.range('1.0.0 - 2.0.0') +--- print(r:has('1.9.9')) -- true +--- print(r:has('2.0.0')) -- false +--- print(r:has(vim.version())) -- check against current Nvim version +--- ``` --- --- Or use cmp(), eq(), lt(), and gt() to compare `.to` and `.from` directly: ---- <pre>lua ---- local r = vim.version.range('1.0.0 - 2.0.0') ---- print(vim.version.gt({1,0,3}, r.from) and vim.version.lt({1,0,3}, r.to)) ---- </pre> +--- +--- ```lua +--- local r = vim.version.range('1.0.0 - 2.0.0') +--- print(vim.version.gt({1,0,3}, r.from) and vim.version.lt({1,0,3}, r.to)) +--- ``` --- --- @see # https://github.com/npm/node-semver#ranges --- @@ -345,16 +350,17 @@ end --- specified literally as a `{major, minor, patch}` tuple, e.g. `{1, 0, 3}`). --- --- Example: ---- <pre>lua ---- if vim.version.cmp({1,0,3}, {0,2,1}) == 0 then ---- -- ... ---- end ---- local v1 = vim.version.parse('1.0.3-pre') ---- local v2 = vim.version.parse('0.2.1') ---- if vim.version.cmp(v1, v2) == 0 then ---- -- ... ---- end ---- </pre> +--- +--- ```lua +--- if vim.version.cmp({1,0,3}, {0,2,1}) == 0 then +--- -- ... +--- end +--- local v1 = vim.version.parse('1.0.3-pre') +--- local v2 = vim.version.parse('0.2.1') +--- if vim.version.cmp(v1, v2) == 0 then +--- -- ... +--- end +--- ``` --- --- @note Per semver, build metadata is ignored when comparing two otherwise-equivalent versions. --- @@ -399,9 +405,10 @@ end --- Parses a semantic version string and returns a version object which can be used with other --- `vim.version` functions. For example "1.0.1-rc1+build.2" returns: ---- <pre> ---- { major = 1, minor = 0, patch = 1, prerelease = "rc1", build = "build.2" } ---- </pre> +--- +--- ``` +--- { major = 1, minor = 0, patch = 1, prerelease = "rc1", build = "build.2" } +--- ``` --- --- @see # https://semver.org/spec/v2.0.0.html --- diff --git a/runtime/plugin/nvim.lua b/runtime/plugin/nvim.lua index 2ddccfcff6..13a54a5e20 100644 --- a/runtime/plugin/nvim.lua +++ b/runtime/plugin/nvim.lua @@ -19,6 +19,6 @@ vim.api.nvim_create_user_command('InspectTree', function(cmd) end end, { desc = 'Inspect treesitter language tree for buffer', count = true }) -vim.api.nvim_create_user_command('PreviewQuery', function() - vim.treesitter.preview_query() -end, { desc = 'Preview treesitter query' }) +vim.api.nvim_create_user_command('EditQuery', function() + vim.treesitter.query.edit() +end, { desc = 'Edit treesitter query' }) diff --git a/runtime/plugin/tohtml.vim b/runtime/plugin/tohtml.vim index 0369313f08..56eb2c15bf 100644 --- a/runtime/plugin/tohtml.vim +++ b/runtime/plugin/tohtml.vim @@ -1,6 +1,6 @@ " Vim plugin for converting a syntax highlighted file to HTML. " Maintainer: Ben Fritz <fritzophrenic@gmail.com> -" Last Change: 2023 Jan 01 +" Last Change: 2023 Sep 07 " " The core of the code is in $VIMRUNTIME/autoload/tohtml.vim and " $VIMRUNTIME/syntax/2html.vim @@ -8,11 +8,29 @@ if exists('g:loaded_2html_plugin') finish endif -let g:loaded_2html_plugin = 'vim9.0_v1' +let g:loaded_2html_plugin = 'vim9.0_v2' " " Changelog: {{{ -" 9.0_v1 (this version): - Implement g:html_no_doc and g:html_no_modeline +" 9.0_v2 (this version): - Warn if using deprecated g:use_xhtml option +" - Change default g:html_use_input_for_pc to "none" +" instead of "fallback". All modern browsers support +" the "user-select: none" and "content:" CSS +" properties so the older method relying on extra +" markup and unspecified browser/app clipboard +" handling is only needed in rare special cases. +" - Fix SourceForge issue #33: generate diff filler +" correctly when new lines have been added to or +" removed from end of buffer. +" - Fix SourceForge issue #32/Vim Github issue #8547: +" use translated highlight ID for styling the +" special-use group names (e.g. LineNr) used +" directly by name in the 2html processing. +" - Fix SourceForge issue #26, refactoring to use +" :let-heredoc style string assignment and +" additional fixes for ".." vs. "." style string +" concatenation. Requires Vim v8.1.1354 or higher. +" 9.0_v1 (Vim 9.0.1275): - Implement g:html_no_doc and g:html_no_modeline " for diff mode. Add tests. " (Vim 9.0.1122): NOTE: no version string update for this version! " - Bugfix for variable name in g:html_no_doc @@ -21,7 +39,8 @@ let g:loaded_2html_plugin = 'vim9.0_v1' " and g:html_no_modeline (partially included in Vim " runtime prior to version string update). " - Updates for new Vim9 string append style (i.e. use -" ".." instead of ".") +" ".." instead of "."). Requires Vim version +" 8.1.1114 or higher. " " 8.1 updates: {{{ " 8.1_v2 (Vim 8.1.2312): - Fix SourceForge issue #19: fix calculation of tab diff --git a/runtime/syntax/2html.vim b/runtime/syntax/2html.vim index 9f43e91309..5fbdad90f3 100644 --- a/runtime/syntax/2html.vim +++ b/runtime/syntax/2html.vim @@ -1,6 +1,6 @@ " Vim syntax support file " Maintainer: Ben Fritz <fritzophrenic@gmail.com> -" Last Change: 2023 Jan 01 +" Last Change: 2023 Sep 05 " " Additional contributors: " @@ -32,9 +32,9 @@ let s:end=line('$') " Font if exists("g:html_font") if type(g:html_font) == type([]) - let s:htmlfont = "'". join(g:html_font,"','") . "', monospace" + let s:htmlfont = "'".. join(g:html_font,"','") .. "', monospace" else - let s:htmlfont = "'". g:html_font . "', monospace" + let s:htmlfont = "'".. g:html_font .. "', monospace" endif else let s:htmlfont = "monospace" @@ -221,8 +221,8 @@ else endif " Find out the background and foreground color for use later -let s:fgc = s:HtmlColor(synIDattr(hlID("Normal"), "fg#", s:whatterm)) -let s:bgc = s:HtmlColor(synIDattr(hlID("Normal"), "bg#", s:whatterm)) +let s:fgc = s:HtmlColor(synIDattr(hlID("Normal")->synIDtrans(), "fg#", s:whatterm)) +let s:bgc = s:HtmlColor(synIDattr(hlID("Normal")->synIDtrans(), "bg#", s:whatterm)) if s:fgc == "" let s:fgc = ( &background == "dark" ? "#ffffff" : "#000000" ) endif @@ -234,41 +234,43 @@ if !s:settings.use_css " Return opening HTML tag for given highlight id function! s:HtmlOpening(id, extra_attrs) let a = "" - if synIDattr(a:id, "inverse") + let translated_ID = synIDtrans(a:id) + if synIDattr(translated_ID, "inverse") " For inverse, we always must set both colors (and exchange them) - let x = s:HtmlColor(synIDattr(a:id, "fg#", s:whatterm)) - let a = a . '<span '.a:extra_attrs.'style="background-color: ' . ( x != "" ? x : s:fgc ) . '">' - let x = s:HtmlColor(synIDattr(a:id, "bg#", s:whatterm)) - let a = a . '<font color="' . ( x != "" ? x : s:bgc ) . '">' + let x = s:HtmlColor(synIDattr(translated_ID, "fg#", s:whatterm)) + let a = a .. '<span '..a:extra_attrs..'style="background-color: ' .. ( x != "" ? x : s:fgc ) .. '">' + let x = s:HtmlColor(synIDattr(translated_ID, "bg#", s:whatterm)) + let a = a .. '<font color="' .. ( x != "" ? x : s:bgc ) .. '">' else - let x = s:HtmlColor(synIDattr(a:id, "bg#", s:whatterm)) + let x = s:HtmlColor(synIDattr(translated_ID, "bg#", s:whatterm)) if x != "" - let a = a . '<span '.a:extra_attrs.'style="background-color: ' . x . '">' + let a = a .. '<span '..a:extra_attrs..'style="background-color: ' .. x .. '">' elseif !empty(a:extra_attrs) - let a = a . '<span '.a:extra_attrs.'>' + let a = a .. '<span '..a:extra_attrs..'>' endif - let x = s:HtmlColor(synIDattr(a:id, "fg#", s:whatterm)) - if x != "" | let a = a . '<font color="' . x . '">' | endif + let x = s:HtmlColor(synIDattr(translated_ID, "fg#", s:whatterm)) + if x != "" | let a = a .. '<font color="' .. x .. '">' | endif endif - if synIDattr(a:id, "bold") | let a = a . "<b>" | endif - if synIDattr(a:id, "italic") | let a = a . "<i>" | endif - if synIDattr(a:id, "underline") | let a = a . "<u>" | endif + if synIDattr(translated_ID, "bold") | let a = a .. "<b>" | endif + if synIDattr(translated_ID, "italic") | let a = a .. "<i>" | endif + if synIDattr(translated_ID, "underline") | let a = a .. "<u>" | endif return a endfun " Return closing HTML tag for given highlight id function! s:HtmlClosing(id, has_extra_attrs) let a = "" - if synIDattr(a:id, "underline") | let a = a . "</u>" | endif - if synIDattr(a:id, "italic") | let a = a . "</i>" | endif - if synIDattr(a:id, "bold") | let a = a . "</b>" | endif - if synIDattr(a:id, "inverse") - let a = a . '</font></span>' + let translated_ID = synIDtrans(a:id) + if synIDattr(translated_ID, "underline") | let a = a .. "</u>" | endif + if synIDattr(translated_ID, "italic") | let a = a .. "</i>" | endif + if synIDattr(translated_ID, "bold") | let a = a .. "</b>" | endif + if synIDattr(translated_ID, "inverse") + let a = a .. '</font></span>' else - let x = s:HtmlColor(synIDattr(a:id, "fg#", s:whatterm)) - if x != "" | let a = a . '</font>' | endif - let x = s:HtmlColor(synIDattr(a:id, "bg#", s:whatterm)) - if x != "" || a:has_extra_attrs | let a = a . '</span>' | endif + let x = s:HtmlColor(synIDattr(translated_ID, "fg#", s:whatterm)) + if x != "" | let a = a .. '</font>' | endif + let x = s:HtmlColor(synIDattr(translated_ID, "bg#", s:whatterm)) + if x != "" || a:has_extra_attrs | let a = a .. '</span>' | endif endif return a endfun @@ -286,84 +288,102 @@ if s:settings.use_css " save CSS to a list of rules to add to the output at the end of processing " first, get the style names we need - let wrapperfunc_lines = [ - \ 'function! s:BuildStyleWrapper(style_id, diff_style_id, extra_attrs, text, make_unselectable, unformatted)', - \ '', - \ ' let l:style_name = synIDattr(a:style_id, "name", s:whatterm)' - \ ] + let s:wrapperfunc_lines = [] + call add(s:wrapperfunc_lines, []) + let s:wrapperfunc_lines[-1] =<< trim ENDLET + function! s:BuildStyleWrapper(style_id, diff_style_id, extra_attrs, text, make_unselectable, unformatted) + + let l:style_name = synIDattr(a:style_id, "name", s:whatterm) + ENDLET if &diff - let wrapperfunc_lines += [ - \ ' let l:diff_style_name = synIDattr(a:diff_style_id, "name", s:whatterm)'] - - " Add normal groups and diff groups to separate lists so we can order them to - " allow diff highlight to override normal highlight - - " if primary style IS a diff style, grab it from the diff cache instead - " (always succeeds because we pre-populate it) - let wrapperfunc_lines += [ - \ '', - \ ' if a:style_id == s:DIFF_D_ID || a:style_id == s:DIFF_A_ID ||'. - \ ' a:style_id == s:DIFF_C_ID || a:style_id == s:DIFF_T_ID', - \ ' let l:saved_style = get(s:diffstylelist,a:style_id)', - \ ' else' - \ ] + call add(s:wrapperfunc_lines, []) + let s:wrapperfunc_lines[-1] =<< trim ENDLET + let l:diff_style_name = synIDattr(a:diff_style_id, "name", s:whatterm) + ENDLET + + " Add normal groups and diff groups to separate lists so we can order them to + " allow diff highlight to override normal highlight + + " if primary style IS a diff style, grab it from the diff cache instead + " (always succeeds because we pre-populate it) + call add(s:wrapperfunc_lines, []) + let s:wrapperfunc_lines[-1] =<< trim ENDLET + + if a:style_id == s:DIFF_D_ID || a:style_id == s:DIFF_A_ID || a:style_id == s:DIFF_C_ID || a:style_id == s:DIFF_T_ID + let l:saved_style = get(s:diffstylelist,a:style_id) + else + ENDLET endif " get primary style info from cache or build it on the fly if not found - let wrapperfunc_lines += [ - \ ' let l:saved_style = get(s:stylelist,a:style_id)', - \ ' if type(l:saved_style) == type(0)', - \ ' unlet l:saved_style', - \ ' let l:saved_style = s:CSS1(a:style_id)', - \ ' if l:saved_style != ""', - \ ' let l:saved_style = "." . l:style_name . " { " . l:saved_style . "}"', - \ ' endif', - \ ' let s:stylelist[a:style_id]= l:saved_style', - \ ' endif' - \ ] + call add(s:wrapperfunc_lines, []) + let s:wrapperfunc_lines[-1] =<< trim ENDLET + let l:saved_style = get(s:stylelist,a:style_id) + if type(l:saved_style) == type(0) + unlet l:saved_style + let l:saved_style = s:CSS1(a:style_id) + if l:saved_style != "" + let l:saved_style = "." .. l:style_name .. " { " .. l:saved_style .. "}" + endif + let s:stylelist[a:style_id] = l:saved_style + endif + ENDLET if &diff - let wrapperfunc_lines += [ ' endif' ] + call add(s:wrapperfunc_lines, []) + let s:wrapperfunc_lines[-1] =<< trim ENDLET + endif + ENDLET endif +" Ignore this comment, just bypassing a highlighting issue: if " Build the wrapper tags around the text. It turns out that caching these " gives pretty much zero performance gain and adds a lot of logic. - let wrapperfunc_lines += [ - \ '', - \ ' if l:saved_style == "" && empty(a:extra_attrs)' - \ ] + call add(s:wrapperfunc_lines, []) + let s:wrapperfunc_lines[-1] =<< trim ENDLET + + if l:saved_style == "" && empty(a:extra_attrs) + ENDLET if &diff - let wrapperfunc_lines += [ - \ ' if a:diff_style_id <= 0' - \ ] + call add(s:wrapperfunc_lines, []) + let s:wrapperfunc_lines[-1] =<< trim ENDLET + if a:diff_style_id <= 0 + ENDLET endif " no surroundings if neither primary nor diff style has any info - let wrapperfunc_lines += [ - \ ' return a:text' - \ ] + call add(s:wrapperfunc_lines, []) + let s:wrapperfunc_lines[-1] =<< trim ENDLET + return a:text + ENDLET if &diff " no primary style, but diff style - let wrapperfunc_lines += [ - \ ' else', - \ ' return "<span class=\"" .l:diff_style_name . "\">".a:text."</span>"', - \ ' endif' - \ ] + call add(s:wrapperfunc_lines, []) + let s:wrapperfunc_lines[-1] =<< trim ENDLET + else + return '<span class="' ..l:diff_style_name .. '">'..a:text.."</span>" + endif + ENDLET endif + " Ignore this comment, just bypassing a highlighting issue: if + " open tag for non-empty primary style - let wrapperfunc_lines += [ - \ ' else'] + call add(s:wrapperfunc_lines, []) + let s:wrapperfunc_lines[-1] =<< trim ENDLET + else + ENDLET " non-empty primary style. handle either empty or non-empty diff style. " " separate the two classes by a space to apply them both if there is a diff " style name, unless the primary style is empty, then just use the diff style " name - let diffstyle = - \ (&diff ? '(a:diff_style_id <= 0 ? "" : " ". l:diff_style_name) .' - \ : "") + let s:diffstyle = + \ (&diff ? '(a:diff_style_id <= 0 ? "" : " " .. l:diff_style_name)..' + \ : '') if s:settings.prevent_copy == "" - let wrapperfunc_lines += [ - \ ' return "<span ".a:extra_attrs."class=\"" . l:style_name .'.diffstyle.'"\">".a:text."</span>"' - \ ] + call add(s:wrapperfunc_lines, []) + let s:wrapperfunc_lines[-1] =<< trim eval ENDLET + return "<span "..a:extra_attrs..'class="' .. l:style_name ..{s:diffstyle}'">'..a:text.."</span>" + ENDLET else " New method: use generated content in the CSS. The only thing needed here @@ -388,59 +408,76 @@ if s:settings.use_css " Note, if maxlength property needs to be added in the future, it will need " to use strchars(), because HTML specifies that the maxlength parameter " uses the number of unique codepoints for its limit. - let wrapperfunc_lines += [ - \ ' if a:make_unselectable', - \ ' return "<span ".a:extra_attrs."class=\"" . l:style_name .'.diffstyle.'"\"' - \ ] + call add(s:wrapperfunc_lines, []) + let s:wrapperfunc_lines[-1] =<< trim eval ENDLET + if a:make_unselectable + let return_span = "<span "..a:extra_attrs..'class="' .. l:style_name ..{s:diffstyle}'"' + ENDLET if s:settings.use_input_for_pc !=# 'all' - let wrapperfunc_lines[-1] .= ' " . "data-" . l:style_name . "-content=\"".a:text."\"' + call add(s:wrapperfunc_lines, []) + let s:wrapperfunc_lines[-1] =<< trim ENDLET + let return_span ..= " data-" .. l:style_name .. '-content="'..a:text..'"' + ENDLET endif - let wrapperfunc_lines[-1] .= '>' + call add(s:wrapperfunc_lines, []) + let s:wrapperfunc_lines[-1] =<< trim ENDLET + let return_span ..= '>' + ENDLET if s:settings.use_input_for_pc !=# 'none' - let wrapperfunc_lines[-1] .= - \ '<input'.s:unselInputType.' class=\"" . l:style_name .'.diffstyle.'"\"'. - \ ' value=\"".substitute(a:unformatted,''\s\+$'',"","")."\"'. - \ ' onselect=''this.blur(); return false;'''. - \ ' onmousedown=''this.blur(); return false;'''. - \ ' onclick=''this.blur(); return false;'''. - \ ' readonly=''readonly'''. - \ ' size=\"".strwidth(a:unformatted)."\"'. - \ (s:settings.use_xhtml ? '/' : '').'>' + call add(s:wrapperfunc_lines, []) + let s:wrapperfunc_lines[-1] =<< trim eval ENDLET + let return_span ..= '<input'..s:unselInputType..' class="' .. l:style_name ..{s:diffstyle}'"' + let return_span ..= ' value="'..substitute(a:unformatted,'\s\+$',"","")..'"' + let return_span ..= " onselect='this.blur(); return false;'" + let return_span ..= " onmousedown='this.blur(); return false;'" + let return_span ..= " onclick='this.blur(); return false;'" + let return_span ..= " readonly='readonly'" + let return_span ..= ' size="'..strwidth(a:unformatted)..'"' + let return_span ..= (s:settings.use_xhtml ? '/>' : '>') + ENDLET endif - let wrapperfunc_lines[-1] .= '</span>"' - let wrapperfunc_lines += [ - \ ' else', - \ ' return "<span ".a:extra_attrs."class=\"" . l:style_name .'. diffstyle .'"\">".a:text."</span>"' - \ ] + call add(s:wrapperfunc_lines, []) + let s:wrapperfunc_lines[-1] =<< trim eval ENDLET + return return_span..'</span>' + else + return "<span "..a:extra_attrs..'class="' .. l:style_name .. {s:diffstyle}'">'..a:text.."</span>" + endif + ENDLET endif - let wrapperfunc_lines += [ - \ ' endif', - \ 'endfun' - \ ] + call add(s:wrapperfunc_lines, []) + let s:wrapperfunc_lines[-1] =<< trim ENDLET + endif + endfun + ENDLET else " Non-CSS method just needs the wrapper. " " Functions used to get opening/closing automatically return null strings if " no styles exist. if &diff - let wrapperfunc_lines = [ - \ 'function! s:BuildStyleWrapper(style_id, diff_style_id, extra_attrs, text, unusedarg, unusedarg2)', - \ ' return s:HtmlOpening(a:style_id, a:extra_attrs).(a:diff_style_id <= 0 ? "" :'. - \ 's:HtmlOpening(a:diff_style_id, "")).a:text.'. - \ '(a:diff_style_id <= 0 ? "" : s:HtmlClosing(a:diff_style_id, 0)).s:HtmlClosing(a:style_id, !empty(a:extra_attrs))', - \ 'endfun' - \ ] + let s:wrapperfunc_lines =<< trim ENDLET + function! s:BuildStyleWrapper(style_id, diff_style_id, extra_attrs, text, unusedarg, unusedarg2) + if a:diff_style_id <= 0 + let l:diff_opening = s:HtmlOpening(a:diff_style_id, "") + let l:diff_closing = s:HtmlClosing(a:diff_style_id, 0) + else + let l:diff_opening = "" + let l:diff_closing = "" + endif + return s:HtmlOpening(a:style_id, a:extra_attrs)..l:diff_opening..a:text..l:diff_closing..s:HtmlClosing(a:style_id, !empty(a:extra_attrs)) + endfun + ENDLET else - let wrapperfunc_lines = [ - \ 'function! s:BuildStyleWrapper(style_id, diff_style_id, extra_attrs, text, unusedarg, unusedarg2)', - \ ' return s:HtmlOpening(a:style_id, a:extra_attrs).a:text.s:HtmlClosing(a:style_id, !empty(a:extra_attrs))', - \ 'endfun' - \ ] + let s:wrapperfunc_lines =<< trim ENDLET + function! s:BuildStyleWrapper(style_id, diff_style_id, extra_attrs, text, unusedarg, unusedarg2) + return s:HtmlOpening(a:style_id, a:extra_attrs)..a:text..s:HtmlClosing(a:style_id, !empty(a:extra_attrs)) + endfun + ENDLET endif endif " create the function we built line by line above -exec join(wrapperfunc_lines, "\n") +exec join(flatten(s:wrapperfunc_lines), "\n") let s:diff_mode = &diff @@ -471,7 +508,7 @@ function! s:HtmlFormat(text, style_id, diff_style_id, extra_attrs, make_unselect " Replace double spaces, leading spaces, and trailing spaces if needed if ' ' != s:HtmlSpace - let formatted = substitute(formatted, ' ', s:HtmlSpace . s:HtmlSpace, 'g') + let formatted = substitute(formatted, ' ', s:HtmlSpace .. s:HtmlSpace, 'g') let formatted = substitute(formatted, '^ ', s:HtmlSpace, 'g') let formatted = substitute(formatted, ' \+$', s:HtmlSpace, 'g') endif @@ -487,7 +524,7 @@ if s:settings.prevent_copy =~# 'n' if s:settings.line_ids function! s:HtmlFormat_n(text, style_id, diff_style_id, lnr) if a:lnr > 0 - return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, 'id="'.(exists('g:html_diff_win_num') ? 'W'.g:html_diff_win_num : "").'L'.a:lnr.s:settings.id_suffix.'" ', 1) + return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, 'id="'..(exists('g:html_diff_win_num') ? 'W'..g:html_diff_win_num : "")..'L'..a:lnr..s:settings.id_suffix..'" ', 1) else return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, "", 1) endif @@ -503,14 +540,14 @@ if s:settings.prevent_copy =~# 'n' " always be non-zero, however we don't want to use the <input> because that " won't work as nice for empty text function! s:HtmlFormat_n(text, style_id, diff_style_id, lnr) - return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, 'id="'.(exists('g:html_diff_win_num') ? 'W'.g:html_diff_win_num : "").'L'.a:lnr.s:settings.id_suffix.'" ', 0) + return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, 'id="'..(exists('g:html_diff_win_num') ? 'W'..g:html_diff_win_num : "")..'L'..a:lnr..s:settings.id_suffix..'" ', 0) endfun endif else if s:settings.line_ids function! s:HtmlFormat_n(text, style_id, diff_style_id, lnr) if a:lnr > 0 - return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, 'id="'.(exists('g:html_diff_win_num') ? 'W'.g:html_diff_win_num : "").'L'.a:lnr.s:settings.id_suffix.'" ', 0) + return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, 'id="'..(exists('g:html_diff_win_num') ? 'W'..g:html_diff_win_num : "")..'L'..a:lnr..s:settings.id_suffix..'" ', 0) else return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, "", 0) endif @@ -535,8 +572,8 @@ if s:settings.prevent_copy =~# 'f' " Simply space-pad to the desired width inside the generated content (note " that the FoldColumn definition includes a whitespace:pre rule) function! s:FoldColumn_build(char, len, numfill, char2, class, click) - return "<a href='#' class='".a:class."' onclick='".a:click."' data-FoldColumn-content='". - \ repeat(a:char, a:len).a:char2.repeat(' ', a:numfill). + return "<a href='#' class='"..a:class.."' onclick='"..a:click.."' data-FoldColumn-content='". + \ repeat(a:char, a:len)..a:char2..repeat(' ', a:numfill). \ "'></a>" endfun function! s:FoldColumn_fill() @@ -554,35 +591,38 @@ if s:settings.prevent_copy =~# 'f' " " Note, 'exec' commands do not recognize line continuations, so must " concatenate lines rather than continue them. - let build_fun_lines = [ - \ 'function! s:FoldColumn_build(char, len, numfill, char2, class, click)', - \ ' let l:input_open = "<input readonly=''readonly''".s:unselInputType.'. - \ ' " onselect=''this.blur(); return false;''".'. - \ ' " onmousedown=''this.blur(); ".a:click." return false;''".'. - \ ' " onclick=''return false;'' size=''".'. - \ ' string(a:len + (empty(a:char2) ? 0 : 1) + a:numfill) .'. - \ ' "'' "', - \ ' let l:common_attrs = "class=''FoldColumn'' value=''"', - \ ' let l:input_close = (s:settings.use_xhtml ? "'' />" : "''>")' - \ ] + let s:build_fun_lines = [] + call add(s:build_fun_lines, []) + let s:build_fun_lines[-1] =<< trim ENDLET + function! s:FoldColumn_build(char, len, numfill, char2, class, click) + let l:input_open = "<input readonly='readonly'"..s:unselInputType + let l:input_open ..= " onselect='this.blur(); return false;'" + let l:input_open ..= " onmousedown='this.blur(); "..a:click.." return false;'" + let l:input_open ..= " onclick='return false;' size='" + let l:input_open ..= string(a:len + (empty(a:char2) ? 0 : 1) + a:numfill) .. "' " + let l:common_attrs = "class='FoldColumn' value='" + let l:input_close = (s:settings.use_xhtml ? "' />" : "'>") + let l:return_span = "<span class='"..a:class.."'>" + let l:return_span ..= l:input_open..l:common_attrs..repeat(a:char, a:len)..(a:char2) + let l:return_span ..= l:input_close + ENDLET if s:settings.use_input_for_pc ==# 'fallback' - let build_fun_lines += [ - \ ' let l:gen_content_link ='. - \ ' "<a href=''#'' class=''FoldColumn'' onclick=''".a:click."'' data-FoldColumn-content=''".'. - \ ' repeat(a:char, a:len).a:char2.repeat('' '', a:numfill).'. - \ ' "''></a>"' - \ ] + call add(s:build_fun_lines, []) + let s:build_fun_lines[-1] =<< trim ENDLET + let l:return_span ..= "<a href='#' class='FoldColumn' onclick='"..a:click.."'" + let l:return_span ..= " data-FoldColumn-content='" + let l:return_span ..= repeat(a:char, a:len)..a:char2..repeat(' ', a:numfill) + let l:return_span ..= "'></a>" + ENDLET endif - let build_fun_lines += [ - \ ' return "<span class=''".a:class."''>".'. - \ ' l:input_open.l:common_attrs.repeat(a:char, a:len).(a:char2).'. - \ ' l:input_close.'. - \ (s:settings.use_input_for_pc ==# 'fallback' ? 'l:gen_content_link.' : ""). - \ ' "</span>"', - \ 'endfun' - \ ] + call add(s:build_fun_lines, []) + let s:build_fun_lines[-1] =<< trim ENDLET + let l:return_span ..= "</span>" + return l:return_span + endfun + ENDLET " create the function we built line by line above - exec join(build_fun_lines, "\n") + exec join(flatten(s:build_fun_lines), "\n") function! s:FoldColumn_fill() return s:FoldColumn_build(' ', s:foldcolumn, 0, '', 'FoldColumn', '') @@ -592,8 +632,8 @@ else " For normal fold columns, simply space-pad to the desired width (note that " the FoldColumn definition includes a whitespace:pre rule) function! s:FoldColumn_build(char, len, numfill, char2, class, click) - return "<a href='#' class='".a:class."' onclick='".a:click."'>". - \ repeat(a:char, a:len).a:char2.repeat(' ', a:numfill). + return "<a href='#' class='"..a:class.."' onclick='"..a:click.."'>". + \ repeat(a:char, a:len)..a:char2..repeat(' ', a:numfill). \ "</a>" endfun function! s:FoldColumn_fill() @@ -625,29 +665,30 @@ endif " Return CSS style describing given highlight id (can be empty) function! s:CSS1(id) let a = "" - if synIDattr(a:id, "inverse") + let translated_ID = synIDtrans(a:id) + if synIDattr(translated_ID, "inverse") " For inverse, we always must set both colors (and exchange them) - let x = s:HtmlColor(synIDattr(a:id, "bg#", s:whatterm)) - let a = a . "color: " . ( x != "" ? x : s:bgc ) . "; " - let x = s:HtmlColor(synIDattr(a:id, "fg#", s:whatterm)) - let a = a . "background-color: " . ( x != "" ? x : s:fgc ) . "; " + let x = s:HtmlColor(synIDattr(translated_ID, "bg#", s:whatterm)) + let a = a .. "color: " .. ( x != "" ? x : s:bgc ) .. "; " + let x = s:HtmlColor(synIDattr(translated_ID, "fg#", s:whatterm)) + let a = a .. "background-color: " .. ( x != "" ? x : s:fgc ) .. "; " else - let x = s:HtmlColor(synIDattr(a:id, "fg#", s:whatterm)) - if x != "" | let a = a . "color: " . x . "; " | endif - let x = s:HtmlColor(synIDattr(a:id, "bg#", s:whatterm)) + let x = s:HtmlColor(synIDattr(translated_ID, "fg#", s:whatterm)) + if x != "" | let a = a .. "color: " .. x .. "; " | endif + let x = s:HtmlColor(synIDattr(translated_ID, "bg#", s:whatterm)) if x != "" - let a = a . "background-color: " . x . "; " + let a = a .. "background-color: " .. x .. "; " " stupid hack because almost every browser seems to have at least one font " which shows 1px gaps between lines which have background - let a = a . "padding-bottom: 1px; " - elseif (a:id == s:FOLDED_ID || a:id == s:LINENR_ID || a:id == s:FOLD_C_ID) && !empty(s:settings.prevent_copy) + let a = a .. "padding-bottom: 1px; " + elseif (translated_ID == s:FOLDED_ID || translated_ID == s:LINENR_ID || translated_ID == s:FOLD_C_ID) && !empty(s:settings.prevent_copy) " input elements default to a different color than the rest of the page - let a = a . "background-color: " . s:bgc . "; " + let a = a .. "background-color: " .. s:bgc .. "; " endif endif - if synIDattr(a:id, "bold") | let a = a . "font-weight: bold; " | endif - if synIDattr(a:id, "italic") | let a = a . "font-style: italic; " | endif - if synIDattr(a:id, "underline") | let a = a . "text-decoration: underline; " | endif + if synIDattr(translated_ID, "bold") | let a = a .. "font-weight: bold; " | endif + if synIDattr(translated_ID, "italic") | let a = a .. "font-style: italic; " | endif + if synIDattr(translated_ID, "underline") | let a = a .. "text-decoration: underline; " | endif return a endfun @@ -720,7 +761,7 @@ if exists("g:loaded_2html_plugin") let s:pluginversion = g:loaded_2html_plugin else if !exists("g:unloaded_tohtml_plugin") - let s:main_plugin_path = expand("<sfile>:p:h:h")."/plugin/tohtml.vim" + let s:main_plugin_path = expand("<sfile>:p:h:h").."/plugin/tohtml.vim" if filereadable(s:main_plugin_path) let s:lines = readfile(s:main_plugin_path, "", 20) call filter(s:lines, 'v:val =~ "loaded_2html_plugin = "') @@ -743,12 +784,12 @@ let s:orgbufnr = winbufnr(0) let s:origwin_stl = &l:stl if expand("%") == "" if exists('g:html_diff_win_num') - exec 'new Untitled_win'.g:html_diff_win_num.'.'.(s:settings.use_xhtml ? 'x' : '').'html' + exec 'new Untitled_win'..g:html_diff_win_num..'.'.(s:settings.use_xhtml ? 'xhtml' : 'html') else - exec 'new Untitled.'.(s:settings.use_xhtml ? 'x' : '').'html' + exec 'new Untitled.'..(s:settings.use_xhtml ? 'xhtml' : 'html') endif else - exec 'new %.'.(s:settings.use_xhtml ? 'x' : '').'html' + exec 'new %.'..(s:settings.use_xhtml ? 'xhtml' : 'html') endif " Resize the new window to very small in order to make it draw faster @@ -795,7 +836,7 @@ let s:lines = [] if s:settings.use_xhtml if s:settings.encoding != "" - call add(s:lines, "<?xml version=\"1.0\" encoding=\"" . s:settings.encoding . "\"?>") + call add(s:lines, "<?xml version=\"1.0\" encoding=\"" .. s:settings.encoding .. "\"?>") else call add(s:lines, "<?xml version=\"1.0\"?>") endif @@ -808,9 +849,9 @@ let s:HtmlSpace = ' ' let s:LeadingSpace = ' ' let s:HtmlEndline = '' if s:settings.no_pre - let s:HtmlEndline = '<br' . s:tag_close + let s:HtmlEndline = '<br' .. s:tag_close let s:LeadingSpace = s:settings.use_xhtml ? ' ' : ' ' - let s:HtmlSpace = '\' . s:LeadingSpace + let s:HtmlSpace = '\' .. s:LeadingSpace endif " HTML header, with the title and generator ;-). Left free space for the CSS, @@ -823,30 +864,30 @@ if !s:settings.no_doc " contained in XML information (to avoid haggling over content type) if s:settings.encoding != "" && !s:settings.use_xhtml if s:html5 - call add(s:lines, '<meta charset="' . s:settings.encoding . '"' . s:tag_close) + call add(s:lines, '<meta charset="' .. s:settings.encoding .. '"' .. s:tag_close) else - call add(s:lines, "<meta http-equiv=\"content-type\" content=\"text/html; charset=" . s:settings.encoding . '"' . s:tag_close) + call add(s:lines, "<meta http-equiv=\"content-type\" content=\"text/html; charset=" .. s:settings.encoding .. '"' .. s:tag_close) endif endif call extend(s:lines, [ - \ ("<title>".expand("%:p:~")."</title>"), - \ ("<meta name=\"Generator\" content=\"Vim/".v:version/100.".".v:version%100.'"'.s:tag_close), - \ ("<meta name=\"plugin-version\" content=\"".s:pluginversion.'"'.s:tag_close) + \ ("<title>"..expand("%:p:~").."</title>"), + \ ("<meta name=\"Generator\" content=\"Vim/"..v:version/100.."."..v:version%100..'"'..s:tag_close), + \ ("<meta name=\"plugin-version\" content=\""..s:pluginversion..'"'..s:tag_close) \ ]) - call add(s:lines, '<meta name="syntax" content="'.s:current_syntax.'"'.s:tag_close) - call add(s:lines, '<meta name="settings" content="'. - \ join(filter(keys(s:settings),'s:settings[v:val]'),','). - \ ',prevent_copy='.s:settings.prevent_copy. - \ ',use_input_for_pc='.s:settings.use_input_for_pc. - \ '"'.s:tag_close) - call add(s:lines, '<meta name="colorscheme" content="'. + call add(s:lines, '<meta name="syntax" content="'..s:current_syntax..'"'..s:tag_close) + call add(s:lines, '<meta name="settings" content="'.. + \ join(filter(keys(s:settings),'s:settings[v:val]'),',').. + \ ',prevent_copy='..s:settings.prevent_copy.. + \ ',use_input_for_pc='..s:settings.use_input_for_pc.. + \ '"'..s:tag_close) + call add(s:lines, '<meta name="colorscheme" content="'.. \ (exists('g:colors_name') \ ? g:colors_name - \ : 'none'). '"'.s:tag_close) + \ : 'none').. '"'..s:tag_close) if s:settings.use_css call extend(s:lines, [ - \ "<style" . (s:html5 ? "" : " type=\"text/css\"") . ">", + \ "<style" .. (s:html5 ? "" : " type=\"text/css\"") .. ">", \ s:settings.use_xhtml ? "" : "<!--"]) let s:ieonly = [] if s:settings.dynamic_folds @@ -921,7 +962,7 @@ if !s:settings.no_doc if s:uses_script call extend(s:lines, [ \ "", - \ "<script" . (s:html5 ? "" : " type='text/javascript'") . ">", + \ "<script" .. (s:html5 ? "" : " type='text/javascript'") .. ">", \ s:settings.use_xhtml ? '//<![CDATA[' : "<!--"]) endif @@ -968,7 +1009,7 @@ if !s:settings.no_doc \ "", \ " /* navigate upwards in the DOM tree to open all folds containing the line */", \ " var node = lineElem;", - \ " while (node && node.id != 'vimCodeElement".s:settings.id_suffix."')", + \ " while (node && node.id != 'vimCodeElement"..s:settings.id_suffix.."')", \ " {", \ " if (node.className == 'closed-fold')", \ " {", @@ -1003,7 +1044,7 @@ if !s:settings.no_doc endif call extend(s:lines, ["</head>", - \ "<body".(s:settings.line_ids ? " onload='JumpToLine();'" : "").">"]) + \ "<body"..(s:settings.line_ids ? " onload='JumpToLine();'" : "")..">"]) endif if s:settings.no_pre @@ -1015,20 +1056,20 @@ else call extend(s:lines, ["<pre id='vimCodeElement" .. s:settings.id_suffix .. "'>"]) endif -exe s:orgwin . "wincmd w" +exe s:orgwin .. "wincmd w" " caches of style data " initialize to include line numbers if using them if s:settings.number_lines - let s:stylelist = { s:LINENR_ID : ".LineNr { " . s:CSS1( s:LINENR_ID ) . "}" } + let s:stylelist = { s:LINENR_ID : ".LineNr { " .. s:CSS1( s:LINENR_ID ) .. "}" } else let s:stylelist = {} endif let s:diffstylelist = { - \ s:DIFF_A_ID : ".DiffAdd { " . s:CSS1( s:DIFF_A_ID ) . "}", - \ s:DIFF_C_ID : ".DiffChange { " . s:CSS1( s:DIFF_C_ID ) . "}", - \ s:DIFF_D_ID : ".DiffDelete { " . s:CSS1( s:DIFF_D_ID ) . "}", - \ s:DIFF_T_ID : ".DiffText { " . s:CSS1( s:DIFF_T_ID ) . "}" + \ s:DIFF_A_ID : ".DiffAdd { " .. s:CSS1( s:DIFF_A_ID ) .. "}", + \ s:DIFF_C_ID : ".DiffChange { " .. s:CSS1( s:DIFF_C_ID ) .. "}", + \ s:DIFF_D_ID : ".DiffDelete { " .. s:CSS1( s:DIFF_D_ID ) .. "}", + \ s:DIFF_T_ID : ".DiffText { " .. s:CSS1( s:DIFF_T_ID ) .. "}" \ } " set up progress bar in the status line @@ -1046,17 +1087,17 @@ if !s:settings.no_progress \ g:colors_name != s:last_colors_name let s:last_colors_name = exists("g:colors_name") ? g:colors_name : "none" - let l:diffatr = synIDattr(hlID("DiffDelete"), "reverse", s:whatterm) ? "fg#" : "bg#" - let l:stlatr = synIDattr(hlID("StatusLine"), "reverse", s:whatterm) ? "fg#" : "bg#" + let l:diffatr = synIDattr(hlID("DiffDelete")->synIDtrans(), "reverse", s:whatterm) ? "fg#" : "bg#" + let l:stlatr = synIDattr(hlID("StatusLine")->synIDtrans(), "reverse", s:whatterm) ? "fg#" : "bg#" - let l:progbar_color = synIDattr(hlID("DiffDelete"), l:diffatr, s:whatterm) - let l:stl_color = synIDattr(hlID("StatusLine"), l:stlatr, s:whatterm) + let l:progbar_color = synIDattr(hlID("DiffDelete")->synIDtrans(), l:diffatr, s:whatterm) + let l:stl_color = synIDattr(hlID("StatusLine")->synIDtrans(), l:stlatr, s:whatterm) if "" == l:progbar_color - let l:progbar_color = synIDattr(hlID("DiffDelete"), "reverse", s:whatterm) ? s:fgc : s:bgc + let l:progbar_color = synIDattr(hlID("DiffDelete")->synIDtrans(), "reverse", s:whatterm) ? s:fgc : s:bgc endif if "" == l:stl_color - let l:stl_color = synIDattr(hlID("StatusLine"), "reverse", s:whatterm) ? s:fgc : s:bgc + let l:stl_color = synIDattr(hlID("StatusLine")->synIDtrans(), "reverse", s:whatterm) ? s:fgc : s:bgc endif if l:progbar_color == l:stl_color @@ -1086,13 +1127,13 @@ if !s:settings.no_progress endif echomsg "diff detected progbar color set to" l:progbar_color endif - exe "hi TOhtmlProgress_auto" s:whatterm."bg=".l:progbar_color + exe "hi TOhtmlProgress_auto" s:whatterm.."bg="..l:progbar_color endif endfun func! s:ProgressBar(title, max_value, winnr) let pgb=copy(s:progressbar) - let pgb.title = a:title.' ' + let pgb.title = a:title..' ' let pgb.max_value = a:max_value let pgb.winnr = a:winnr let pgb.cur_value = 0 @@ -1194,6 +1235,66 @@ if !s:settings.no_progress call s:SetProgbarColor() endif +let s:build_fun_lines = [] +call add(s:build_fun_lines, []) +let s:build_fun_lines[-1] =<< trim ENDLET + func! s:Add_diff_fill(lnum) + let l:filler = diff_filler(a:lnum) + if l:filler > 0 + let l:to_insert = l:filler + while l:to_insert > 0 + let l:new = repeat(s:difffillchar, 3) + + if l:to_insert > 2 && l:to_insert < l:filler && !s:settings.whole_filler + let l:new = l:new .. " " .. l:filler .. " inserted lines " + let l:to_insert = 2 + endif +ENDLET +call add(s:build_fun_lines, []) +if !s:settings.no_pre + let s:build_fun_lines[-1] =<< trim ENDLET + " HTML line wrapping is off--go ahead and fill to the margin + " TODO: what about when CSS wrapping is turned on? + let l:new = l:new .. repeat(s:difffillchar, &columns - strlen(l:new) - s:margin) + ENDLET +else + let s:build_fun_lines[-1] =<< trim ENDLET + let l:new = l:new .. repeat(s:difffillchar, 3) + ENDLET +endif +call add(s:build_fun_lines, []) +let s:build_fun_lines[-1] =<< trim ENDLET + let l:new = s:HtmlFormat_d(l:new, s:DIFF_D_ID, 0) +ENDLET +if s:settings.number_lines + call add(s:build_fun_lines, []) + let s:build_fun_lines[-1] =<< trim ENDLET + " Indent if line numbering is on. Indent gets style of line number + " column. + let l:new = s:HtmlFormat_n(repeat(' ', s:margin), s:LINENR_ID, 0, 0) .. l:new + ENDLET +endif +if s:settings.dynamic_folds && !s:settings.no_foldcolumn + call add(s:build_fun_lines, []) + let s:build_fun_lines[-1] =<< trim ENDLET + if s:foldcolumn > 0 + " Indent for foldcolumn if there is one. Assume it's empty, there should + " not be a fold for deleted lines in diff mode. + let l:new = s:FoldColumn_fill() .. l:new + endif + ENDLET +endif +" Ignore this comment, just bypassing a highlighting issue: if +call add(s:build_fun_lines, []) +let s:build_fun_lines[-1] =<< trim ENDLET + call add(s:lines, l:new..s:HtmlEndline) + let l:to_insert = l:to_insert - 1 + endwhile + endif + endfun +ENDLET +exec join(flatten(s:build_fun_lines), "\n") + " First do some preprocessing for dynamic folding. Do this for the entire file " so we don't accidentally start within a closed fold or something. let s:allfolds = [] @@ -1220,7 +1321,7 @@ if s:settings.dynamic_folds let s:newfold = {'firstline': s:lnum, 'lastline': foldclosedend(s:lnum), 'level': s:level,'type': "closed-fold"} call add(s:allfolds, s:newfold) " open the fold so we can find any contained folds - execute s:lnum."foldopen" + execute s:lnum.."foldopen" else if !s:settings.no_progress call s:pgb.incr() @@ -1252,7 +1353,7 @@ if s:settings.dynamic_folds call add(s:allfolds, s:newfold) endif " open the fold so we can find any contained folds - execute s:lnum."foldopen" + execute s:lnum.."foldopen" else if !s:settings.no_progress call s:pgb.incr() @@ -1339,7 +1440,7 @@ if s:settings.dynamic_folds " Note that only when a start and an end line is specified will a fold " containing the current range ever be removed. while leveladjust > 0 - exe g:html_start_line."foldopen" + exe g:html_start_line.."foldopen" let leveladjust -= 1 endwhile endif @@ -1399,47 +1500,11 @@ endif while s:lnum <= s:end " If there are filler lines for diff mode, show these above the line. - let s:filler = diff_filler(s:lnum) - if s:filler > 0 - let s:n = s:filler - while s:n > 0 - let s:new = repeat(s:difffillchar, 3) - - if s:n > 2 && s:n < s:filler && !s:settings.whole_filler - let s:new = s:new . " " . s:filler . " inserted lines " - let s:n = 2 - endif - - if !s:settings.no_pre - " HTML line wrapping is off--go ahead and fill to the margin - " TODO: what about when CSS wrapping is turned on? - let s:new = s:new . repeat(s:difffillchar, &columns - strlen(s:new) - s:margin) - else - let s:new = s:new . repeat(s:difffillchar, 3) - endif - - let s:new = s:HtmlFormat_d(s:new, s:DIFF_D_ID, 0) - if s:settings.number_lines - " Indent if line numbering is on. Indent gets style of line number - " column. - let s:new = s:HtmlFormat_n(repeat(' ', s:margin), s:LINENR_ID, 0, 0) . s:new - endif - if s:settings.dynamic_folds && !s:settings.no_foldcolumn && s:foldcolumn > 0 - " Indent for foldcolumn if there is one. Assume it's empty, there should - " not be a fold for deleted lines in diff mode. - let s:new = s:FoldColumn_fill() . s:new - endif - call add(s:lines, s:new.s:HtmlEndline) - - let s:n = s:n - 1 - endwhile - unlet s:n - endif - unlet s:filler + call s:Add_diff_fill(s:lnum) " Start the line with the line number. if s:settings.number_lines - let s:numcol = repeat(' ', s:margin - 1 - strlen(s:lnum)) . s:lnum . ' ' + let s:numcol = repeat(' ', s:margin - 1 - strlen(s:lnum)) .. s:lnum .. ' ' endif let s:new = "" @@ -1450,11 +1515,11 @@ while s:lnum <= s:end let s:new = foldtextresult(s:lnum) if !s:settings.no_pre " HTML line wrapping is off--go ahead and fill to the margin - let s:new = s:new . repeat(s:foldfillchar, &columns - strlen(s:new)) + let s:new = s:new .. repeat(s:foldfillchar, &columns - strlen(s:new)) endif " put numcol in a separate group for sake of unselectable text - let s:new = (s:settings.number_lines ? s:HtmlFormat_n(s:numcol, s:FOLDED_ID, 0, s:lnum): "") . s:HtmlFormat_t(s:new, s:FOLDED_ID, 0) + let s:new = (s:settings.number_lines ? s:HtmlFormat_n(s:numcol, s:FOLDED_ID, 0, s:lnum): "") .. s:HtmlFormat_t(s:new, s:FOLDED_ID, 0) " Skip to the end of the fold let s:new_lnum = foldclosedend(s:lnum) @@ -1475,7 +1540,7 @@ while s:lnum <= s:end if s:settings.dynamic_folds " First insert a closing for any open folds that end on this line while !empty(s:foldstack) && get(s:foldstack,0).lastline == s:lnum-1 - let s:new = s:new."</span></span>" + let s:new = s:new.."</span></span>" call remove(s:foldstack, 0) endwhile @@ -1483,9 +1548,9 @@ while s:lnum <= s:end let s:firstfold = 1 while !empty(s:allfolds) && get(s:allfolds,0).firstline == s:lnum let s:foldId = s:foldId + 1 - let s:new .= "<span id='" - let s:new .= (exists('g:html_diff_win_num') ? "win".g:html_diff_win_num : "") - let s:new .= "fold".s:foldId.s:settings.id_suffix."' class='".s:allfolds[0].type."'>" + let s:new ..= "<span id='" + let s:new ..= (exists('g:html_diff_win_num') ? "win"..g:html_diff_win_num : "") + let s:new ..= "fold"..s:foldId..s:settings.id_suffix.."' class='"..s:allfolds[0].type.."'>" " Unless disabled, add a fold column for the opening line of a fold. @@ -1496,20 +1561,20 @@ while s:lnum <= s:end if !s:settings.no_foldcolumn " add fold column that can open the new fold if s:allfolds[0].level > 1 && s:firstfold - let s:new = s:new . s:FoldColumn_build('|', s:allfolds[0].level - 1, 0, "", - \ 'toggle-open FoldColumn','javascript:toggleFold("fold'.s:foldstack[0].id.s:settings.id_suffix.'");') + let s:new = s:new .. s:FoldColumn_build('|', s:allfolds[0].level - 1, 0, "", + \ 'toggle-open FoldColumn','javascript:toggleFold("fold'..s:foldstack[0].id..s:settings.id_suffix..'");') endif " add the filler spaces separately from the '+' char so that it can be " shown/hidden separately during a hover unfold - let s:new = s:new . s:FoldColumn_build("+", 1, 0, "", - \ 'toggle-open FoldColumn', 'javascript:toggleFold("fold'.s:foldId.s:settings.id_suffix.'");') + let s:new = s:new .. s:FoldColumn_build("+", 1, 0, "", + \ 'toggle-open FoldColumn', 'javascript:toggleFold("fold'..s:foldId..s:settings.id_suffix..'");') " If this is not the last fold we're opening on this line, we need " to keep the filler spaces hidden if the fold is opened by mouse " hover. If it is the last fold to open in the line, we shouldn't hide " them, so don't apply the toggle-filler class. - let s:new = s:new . s:FoldColumn_build(" ", 1, s:foldcolumn - s:allfolds[0].level - 1, "", - \ 'toggle-open FoldColumn'. (get(s:allfolds, 1, {'firstline': 0}).firstline == s:lnum ?" toggle-filler" :""), - \ 'javascript:toggleFold("fold'.s:foldId.s:settings.id_suffix.'");') + let s:new = s:new .. s:FoldColumn_build(" ", 1, s:foldcolumn - s:allfolds[0].level - 1, "", + \ 'toggle-open FoldColumn'.. (get(s:allfolds, 1, {'firstline': 0}).firstline == s:lnum ?" toggle-filler" :""), + \ 'javascript:toggleFold("fold'..s:foldId..s:settings.id_suffix..'");') " add fold column that can close the new fold " only add extra blank space if we aren't opening another fold on the @@ -1522,12 +1587,12 @@ while s:lnum <= s:end if s:firstfold " the first fold in a line has '|' characters from folds opened in " previous lines, before the '-' for this fold - let s:new .= s:FoldColumn_build('|', s:allfolds[0].level - 1, s:extra_space, '-', - \ 'toggle-closed FoldColumn', 'javascript:toggleFold("fold'.s:foldId.s:settings.id_suffix.'");') + let s:new ..= s:FoldColumn_build('|', s:allfolds[0].level - 1, s:extra_space, '-', + \ 'toggle-closed FoldColumn', 'javascript:toggleFold("fold'..s:foldId..s:settings.id_suffix..'");') else " any subsequent folds in the line only add a single '-' - let s:new = s:new . s:FoldColumn_build("-", 1, s:extra_space, "", - \ 'toggle-closed FoldColumn', 'javascript:toggleFold("fold'.s:foldId.s:settings.id_suffix.'");') + let s:new = s:new .. s:FoldColumn_build("-", 1, s:extra_space, "", + \ 'toggle-closed FoldColumn', 'javascript:toggleFold("fold'..s:foldId..s:settings.id_suffix..'");') endif let s:firstfold = 0 endif @@ -1535,12 +1600,12 @@ while s:lnum <= s:end " Add fold text, moving the span ending to the next line so collapsing " of folds works correctly. " Put numcol in a separate group for sake of unselectable text. - let s:new = s:new . (s:settings.number_lines ? s:HtmlFormat_n(s:numcol, s:FOLDED_ID, 0, 0) : "") . substitute(s:HtmlFormat_t(foldtextresult(s:lnum), s:FOLDED_ID, 0), '</span>', s:HtmlEndline.'\n\0', '') - let s:new = s:new . "<span class='fulltext'>" + let s:new = s:new .. (s:settings.number_lines ? s:HtmlFormat_n(s:numcol, s:FOLDED_ID, 0, 0) : "") .. substitute(s:HtmlFormat_t(foldtextresult(s:lnum), s:FOLDED_ID, 0), '</span>', s:HtmlEndline..'\n\0', '') + let s:new = s:new .. "<span class='fulltext'>" " open the fold now that we have the fold text to allow retrieval of " fold text for subsequent folds - execute s:lnum."foldopen" + execute s:lnum.."foldopen" call insert(s:foldstack, remove(s:allfolds,0)) let s:foldstack[0].id = s:foldId endwhile @@ -1555,13 +1620,13 @@ while s:lnum <= s:end " add the empty foldcolumn for unfolded lines if there is a fold " column at all if s:foldcolumn > 0 - let s:new = s:new . s:FoldColumn_fill() + let s:new = s:new .. s:FoldColumn_fill() endif else " add the fold column for folds not on the opening line if get(s:foldstack, 0).firstline < s:lnum - let s:new = s:new . s:FoldColumn_build('|', s:foldstack[0].level, s:foldcolumn - s:foldstack[0].level, "", - \ 'FoldColumn', 'javascript:toggleFold("fold'.s:foldstack[0].id.s:settings.id_suffix.'");') + let s:new = s:new .. s:FoldColumn_build('|', s:foldstack[0].level, s:foldcolumn - s:foldstack[0].level, "", + \ 'FoldColumn', 'javascript:toggleFold("fold'..s:foldstack[0].id..s:settings.id_suffix..'");') endif endif endif @@ -1569,9 +1634,9 @@ while s:lnum <= s:end " Now continue with the unfolded line text if s:settings.number_lines - let s:new = s:new . s:HtmlFormat_n(s:numcol, s:LINENR_ID, 0, s:lnum) + let s:new = s:new .. s:HtmlFormat_n(s:numcol, s:LINENR_ID, 0, s:lnum) elseif s:settings.line_ids - let s:new = s:new . s:HtmlFormat_n("", s:LINENR_ID, 0, s:lnum) + let s:new = s:new .. s:HtmlFormat_n("", s:LINENR_ID, 0, s:lnum) endif " Get the diff attribute, if any. @@ -1611,7 +1676,7 @@ while s:lnum <= s:end if s:len < &columns && !s:settings.no_pre " Add spaces at the end of the raw text line to extend the changed " line to the full width. - let s:line = s:line . repeat(' ', &columns - virtcol([s:lnum, s:len]) - s:margin) + let s:line = s:line .. repeat(' ', &columns - virtcol([s:lnum, s:len]) - s:margin) let s:len = &columns endif else @@ -1649,11 +1714,11 @@ while s:lnum <= s:end " if the found tab is the first character in the text being " processed, we need to get the character prior to the text, " given by startcol. - let s:prevc = matchstr(s:line, '.\%' . (s:startcol + s:offset) . 'c') + let s:prevc = matchstr(s:line, '.\%' .. (s:startcol + s:offset) .. 'c') else " Otherwise, the byte index of the tab into s:expandedtab is " given by s:idx. - let s:prevc = matchstr(s:expandedtab, '.\%' . (s:idx + 1) . 'c') + let s:prevc = matchstr(s:expandedtab, '.\%' .. (s:idx + 1) .. 'c') endif let s:vcol = virtcol([s:lnum, s:startcol + s:idx + s:offset - len(s:prevc)]) @@ -1689,12 +1754,12 @@ while s:lnum <= s:end " Output the text with the same synID, with class set to the highlight ID " name, unless it has been concealed completely. if strlen(s:expandedtab) > 0 - let s:new = s:new . s:HtmlFormat(s:expandedtab, s:id, s:diff_id, "", 0) + let s:new = s:new .. s:HtmlFormat(s:expandedtab, s:id, s:diff_id, "", 0) endif endwhile endif - call extend(s:lines, split(s:new.s:HtmlEndline, '\n', 1)) + call extend(s:lines, split(s:new..s:HtmlEndline, '\n', 1)) if !s:settings.no_progress && s:pgb.needs_redraw redrawstatus let s:pgb.needs_redraw = 0 @@ -1706,17 +1771,21 @@ while s:lnum <= s:end endif endwhile +" Diff filler is returned based on what needs inserting *before* the given line. +" So to get diff filler at the end of the buffer, we need to use last line + 1 +call s:Add_diff_fill(s:end+1) + if s:settings.dynamic_folds " finish off any open folds while !empty(s:foldstack) - let s:lines[-1].="</span></span>" + let s:lines[-1]..="</span></span>" call remove(s:foldstack, 0) endwhile " add fold column to the style list if not already there let s:id = s:FOLD_C_ID if !has_key(s:stylelist, s:id) - let s:stylelist[s:id] = '.FoldColumn { ' . s:CSS1(s:id) . '}' + let s:stylelist[s:id] = '.FoldColumn { ' .. s:CSS1(s:id) .. '}' endif endif @@ -1734,7 +1803,7 @@ if !s:settings.no_doc call extend(s:lines, ["</body>", "</html>"]) endif -exe s:newwin . "wincmd w" +exe s:newwin .. "wincmd w" call setline(1, s:lines) unlet s:lines @@ -1757,17 +1826,17 @@ if s:settings.use_css && !s:settings.no_doc " Normal/global attributes if s:settings.no_pre - call append('.', "body { color: " . s:fgc . "; background-color: " . s:bgc . "; font-family: ". s:htmlfont ."; }") + call append('.', "body { color: " .. s:fgc .. "; background-color: " .. s:bgc .. "; font-family: ".. s:htmlfont .."; }") + else - call append('.', "pre { " . s:whitespace . "font-family: ". s:htmlfont ."; color: " . s:fgc . "; background-color: " . s:bgc . "; }") + call append('.', "pre { " .. s:whitespace .. "font-family: ".. s:htmlfont .."; color: " .. s:fgc .. "; background-color: " .. s:bgc .. "; }") + yank put execute "normal! ^cwbody\e" " body should not have the wrap formatting, only the pre section if s:whitespace != '' - exec 's#'.s:whitespace + exec 's#'..s:whitespace endif endif " fix browser inconsistencies (sometimes within the same browser) of different @@ -1778,13 +1847,13 @@ if s:settings.use_css && !s:settings.no_doc " like normal text if !empty(s:settings.prevent_copy) if s:settings.use_input_for_pc !=# "none" - call append('.', 'input { border: none; margin: 0; padding: 0; font-family: '.s:htmlfont.'; }') + call append('.', 'input { border: none; margin: 0; padding: 0; font-family: '..s:htmlfont..'; }') + " ch units for browsers which support them, em units for a somewhat " reasonable fallback. for w in range(1, 20, 1) call append('.', [ - \ "input[size='".w."'] { width: ".w."em; width: ".w."ch; }" + \ "input[size='"..w.."'] { width: "..w.."em; width: "..w.."ch; }" \ ]) + endfor @@ -1828,14 +1897,15 @@ if s:settings.use_css && !s:settings.no_doc endif for s:style_name in s:unselectable_styles call append('.', [ - \ ' .'.s:style_name.' { user-select: none; }', - \ ' [data-'.s:style_name.'-content]::before { content: attr(data-'.s:style_name.'-content); }', - \ ' [data-'.s:style_name.'-content]::before { padding-bottom: 1px; display: inline-block; /* match the 1-px padding of standard items with background */ }', - \ ' span[data-'.s:style_name.'-content]::before { cursor: default; }', + \ ' .'..s:style_name..' { user-select: none; }', + \ ' [data-'..s:style_name..'-content]::before { content: attr(data-'..s:style_name..'-content); }', + \ ' [data-'..s:style_name..'-content]::before { padding-bottom: 1px; display: inline-block; /* match the 1-px padding of standard items with background */ }', + \ ' span[data-'..s:style_name..'-content]::before { cursor: default; }', \ ]) +4 endfor if s:settings.use_input_for_pc !=# 'none' + " Note, the extra '}' is to match the "@supports" above call append('.', [ \ ' input { display: none; }', \ '}' @@ -1851,12 +1921,12 @@ if s:settings.use_css && !s:settings.no_doc " Make the cursor show active fold columns as active areas, and empty fold " columns as not interactive. call append('.', ['input.FoldColumn { cursor: pointer; }', - \ 'input.FoldColumn[value="'.repeat(' ', s:foldcolumn).'"] { cursor: default; }' + \ 'input.FoldColumn[value="'..repeat(' ', s:foldcolumn)..'"] { cursor: default; }' \ ]) +2 if s:settings.use_input_for_pc !=# 'all' call append('.', [ - \ 'a[data-FoldColumn-content="'.repeat(' ', s:foldcolumn).'"] { cursor: default; }' + \ 'a[data-FoldColumn-content="'..repeat(' ', s:foldcolumn)..'"] { cursor: default; }' \ ]) +1 end @@ -1884,7 +1954,7 @@ endif if !s:settings.use_css && !s:settings.no_doc " For Netscape 4, set <body> attributes too, though, strictly speaking, it's " incorrect. - execute '%s:<body\([^>]*\):<body bgcolor="' . s:bgc . '" text="' . s:fgc . '"\1>\r<font face="'. s:htmlfont .'"' + execute '%s:<body\([^>]*\):<body bgcolor="' .. s:bgc .. '" text="' .. s:fgc .. '"\1>\r<font face="'.. s:htmlfont ..'"' endif " Gather attributes for all other classes. Do diff first so that normal @@ -1935,7 +2005,7 @@ let @/ = s:old_search let &more = s:old_more " switch to original window to restore those settings -exe s:orgwin . "wincmd w" +exe s:orgwin .. "wincmd w" if !s:settings.expand_tabs let &l:isprint = s:old_isprint @@ -1945,7 +2015,7 @@ let &l:et = s:old_et let &l:scrollbind = s:old_bind " and back to the new window again to end there -exe s:newwin . "wincmd w" +exe s:newwin .. "wincmd w" let &l:stl = s:newwin_stl exec 'resize' s:old_winheight @@ -1982,10 +2052,13 @@ if !v:profiling delfunc s:progressbar.incr unlet s:pgb s:progressbar endif + + delfunc s:Add_diff_fill endif -unlet! s:new_lnum s:diffattr s:difffillchar s:foldfillchar s:HtmlSpace +unlet! s:new_lnum s:diffattr s:difffillchar s:foldfillchar s:HtmlSpace s:diffstyle unlet! s:LeadingSpace s:HtmlEndline s:firstfold s:numcol s:foldcolumn +unlet! s:wrapperfunc_lines s:build_fun_lines unlet s:foldstack s:allfolds s:foldId s:settings let &cpo = s:cpo_sav diff --git a/runtime/syntax/i3config.vim b/runtime/syntax/i3config.vim index caef244ce5..8bc2a6e03c 100644 --- a/runtime/syntax/i3config.vim +++ b/runtime/syntax/i3config.vim @@ -2,8 +2,9 @@ " Language: i3 config file " Original Author: Mohamed Boughaba <mohamed dot bgb at gmail dot com> " Maintainer: Quentin Hibon (github user hiqua) -" Version: 0.4 -" Last Change: 2022 Jun 05 +" Version: 0.4.22 +" Reference version (JosefLitos/i3config.vim): 4.22 +" Last Change: 2023-09-12 " References: " http://i3wm.org/docs/userguide.html#configuring @@ -18,247 +19,329 @@ endif scriptencoding utf-8 " Error -syn match i3ConfigError /.*/ +syn match i3ConfigError /.\+/ " Todo syn keyword i3ConfigTodo TODO FIXME XXX contained -" Comment -" Comments are started with a # and can only be used at the beginning of a line +" Helper type definitions +syn match i3ConfigSeparator /[,;]/ contained +syn match i3ConfigParen /[{}]/ contained +syn keyword i3ConfigBoolean yes no enabled disabled on off true false contained +syn region i3ConfigString start=/\W\@<="/ skip=/\\"/ end=/"/ contained contains=i3ConfigShCommand,i3ConfigShDelim,i3ConfigShOper,i3ConfigShParam,i3ConfigNumber,i3ConfigVariable,i3ConfigExecAction keepend extend +syn region i3ConfigString start=/\W\@<='/ end=/'/ contained contains=i3ConfigShCommand,i3ConfigShDelim,i3ConfigShOper,i3ConfigShParam,i3ConfigNumber,i3ConfigVariable,i3ConfigExecAction keepend extend +syn match i3ConfigColor /#\w\{3,8}/ contained +syn match i3ConfigNumber /[a-zA-Z_$-]\@<!-\?\d\+\w\@!/ contained + +" 4.1 Include directive +syn keyword i3ConfigIncludeKeyword include contained +syn match i3ConfigIncludeCommand /`[^`]*`/ contained contains=i3ConfigShDelim,i3ConfigShParam,i3ConfigShOper,i3ConfigShCommand +syn match i3ConfigInclude /^include .*$/ contains=i3ConfigIncludeKeyword,i3ConfigString,i3ConfigVariable,i3ConfigIncludeCommand + +" 4.2 Comments syn match i3ConfigComment /^\s*#.*$/ contains=i3ConfigTodo -" Font -" A FreeType font description is composed by: -" a font family, a style, a weight, a variant, a stretch and a size. -syn match i3ConfigFontSeparator /,/ contained -syn match i3ConfigFontSeparator /:/ contained +" 4.3 Fonts syn keyword i3ConfigFontKeyword font contained -syn match i3ConfigFontNamespace /\w\+:/ contained contains=i3ConfigFontSeparator -syn match i3ConfigFontContent /-\?\w\+\(-\+\|\s\+\|,\)/ contained contains=i3ConfigFontNamespace,i3ConfigFontSeparator,i3ConfigFontKeyword +syn match i3ConfigColonOperator /:/ contained +syn match i3ConfigFontNamespace /\w\+:/ contained contains=i3ConfigColonOperator syn match i3ConfigFontSize /\s\=\d\+\(px\)\?\s\?$/ contained -syn match i3ConfigFont /^\s*font\s\+.*$/ contains=i3ConfigFontContent,i3ConfigFontSeparator,i3ConfigFontSize,i3ConfigFontNamespace -syn match i3ConfigFont /^\s*font\s\+.*\(\\\_.*\)\?$/ contains=i3ConfigFontContent,i3ConfigFontSeparator,i3ConfigFontSize,i3ConfigFontNamespace -syn match i3ConfigFont /^\s*font\s\+.*\(\\\_.*\)\?[^\\]\+$/ contains=i3ConfigFontContent,i3ConfigFontSeparator,i3ConfigFontSize,i3ConfigFontNamespace -syn match i3ConfigFont /^\s*font\s\+\(\(.*\\\_.*\)\|\(.*[^\\]\+$\)\)/ contains=i3ConfigFontContent,i3ConfigFontSeparator,i3ConfigFontSize,i3ConfigFontNamespace - -" variables -syn match i3ConfigString /\(['"]\)\(.\{-}\)\1/ contained -syn match i3ConfigColor /#\w\{6}/ contained -syn match i3ConfigVariableModifier /+/ contained -syn match i3ConfigVariableAndModifier /+\w\+/ contained contains=i3ConfigVariableModifier -syn match i3ConfigVariable /\$\w\+\(\(-\w\+\)\+\)\?\(\s\|+\)\?/ contains=i3ConfigVariableModifier,i3ConfigVariableAndModifier -syn keyword i3ConfigInitializeKeyword set contained -syn match i3ConfigInitialize /^\s*set\s\+.*$/ contains=i3ConfigVariable,i3ConfigInitializeKeyword,i3ConfigColor,i3ConfigString - -" Include -syn keyword i3ConfigIncludeKeyword include contained -syn match i3ConfigInclude /^\s*include\s\+.*$/ contains=i3ConfigIncludeKeyword,i3ConfigString,i3ConfigVariable - -" Gaps -syn keyword i3ConfigGapStyleKeyword inner outer horizontal vertical top right bottom left current all set plus minus toggle up down contained -syn match i3ConfigGapStyle /^\s*\(gaps\)\s\+\(inner\|outer\|horizontal\|vertical\|left\|top\|right\|bottom\)\(\s\+\(current\|all\)\)\?\(\s\+\(set\|plus\|minus\|toggle\)\)\?\(\s\+\(-\?\d\+\|\$.*\)\)$/ contains=i3ConfigGapStyleKeyword,i3ConfigNumber,i3ConfigVariable -syn keyword i3ConfigSmartGapKeyword on inverse_outer off contained -syn match i3ConfigSmartGap /^\s*smart_gaps\s\+\(on\|inverse_outer\|off\)\s\?$/ contains=i3ConfigSmartGapKeyword -syn keyword i3ConfigSmartBorderKeyword on no_gaps contained -syn match i3ConfigSmartBorder /^\s*smart_borders\s\+\(on\|no_gaps\)\s\?$/ contains=i3ConfigSmartBorderKeyword - -" Keyboard bindings -syn keyword i3ConfigAction toggle fullscreen restart key import kill shrink grow contained -syn keyword i3ConfigAction focus move grow height width split layout resize restore reload mute unmute exit mode workspace container to contained -syn match i3ConfigModifier /\w\++\w\+\(\(+\w\+\)\+\)\?/ contained contains=i3ConfigVariableModifier -syn match i3ConfigNumber /\s\d\+/ contained -syn match i3ConfigUnit /\sp\(pt\|x\)/ contained -syn match i3ConfigUnitOr /\sor/ contained -syn keyword i3ConfigBindKeyword bindsym bindcode exec gaps border contained -syn match i3ConfigBindArgument /--\w\+\(\(-\w\+\)\+\)\?\s/ contained -syn match i3ConfigBind /^\s*\(bindsym\|bindcode\)\s\+.*$/ contains=i3ConfigVariable,i3ConfigBindKeyword,i3ConfigVariableAndModifier,i3ConfigNumber,i3ConfigUnit,i3ConfigUnitOr,i3ConfigBindArgument,i3ConfigModifier,i3ConfigAction,i3ConfigString,i3ConfigGapStyleKeyword,i3ConfigBorderStyleKeyword - -" Floating +syn region i3ConfigFont start=/^\s*font / skip=/\\$/ end=/$/ contains=i3ConfigFontKeyword,i3ConfigFontNamespace,i3ConfigFontSize,i3ConfigSeparator keepend + +" 4.4-4.5 Keyboard/Mouse bindings +syn keyword i3ConfigBindKeyword bindsym bindcode contained +syn match i3ConfigBindArgument /--\(release\|border\|whole-window\|exclude-titlebar\)/ contained +syn match i3ConfigBindModifier /+/ contained +syn match i3ConfigBindModkey /Ctrl\|Shift\|Mod[1-5]/ contained +syn match i3ConfigBindCombo /[$a-zA-Z0-9_+]\+ / contained contains=i3ConfigBindModifier,i3ConfigVariable,i3ConfigBindModkey +syn match i3ConfigBindComboLine /bind\(sym\|code\)\( --[a-z-]\+\)* [$a-zA-Z0-9_+]\+ / contained contains=i3ConfigBindKeyword,i3ConfigBindArgument,i3ConfigBindCombo +syn region i3ConfigBind start=/^\s*bind\(sym\|code\) / skip=/\\$/ end=/$/ contains=i3ConfigBindComboLine,i3ConfigCriteria,i3ConfigAction,i3ConfigSeparator,i3ConfigActionKeyword,i3ConfigOption,i3ConfigString,i3ConfigNumber,i3ConfigVariable,i3ConfigBoolean keepend + +" 4.6 Binding modes +syn region i3ConfigKeyword start=/^mode\( --pango_markup\)\? \([^'" {]\+\|'[^']\+'\|".\+"\)\s\+{$/ end=/^\s*}$/ contains=i3ConfigShParam,i3ConfigString,i3ConfigBind,i3ConfigComment,i3ConfigNumber,i3ConfigParen,i3ConfigVariable fold keepend extend + +" 4.7 Floating modifier +syn match i3ConfigKeyword /^floating_modifier [$a-zA-Z0-9+]\+$/ contains=i3ConfigVariable,i3ConfigBindModkey + +" 4.8 Floating window size syn keyword i3ConfigSizeSpecial x contained -syn match i3ConfigNegativeSize /-/ contained -syn match i3ConfigSize /-\?\d\+\s\?x\s\?-\?\d\+/ contained contains=i3ConfigSizeSpecial,i3ConfigNumber,i3ConfigNegativeSize -syn match i3ConfigFloatingModifier /^\s*floating_modifier\s\+\$\w\+\d\?/ contains=i3ConfigVariable -syn match i3ConfigFloating /^\s*floating_\(maximum\|minimum\)_size\s\+-\?\d\+\s\?x\s\?-\?\d\+/ contains=i3ConfigSize - -" Orientation -syn keyword i3ConfigOrientationKeyword vertical horizontal auto contained -syn match i3ConfigOrientation /^\s*default_orientation\s\+\(vertical\|horizontal\|auto\)\s\?$/ contains=i3ConfigOrientationKeyword - -" Layout -syn keyword i3ConfigLayoutKeyword default stacking tabbed contained -syn match i3ConfigLayout /^\s*workspace_layout\s\+\(default\|stacking\|tabbed\)\s\?$/ contains=i3ConfigLayoutKeyword - -" Border style -syn keyword i3ConfigBorderStyleKeyword none normal pixel contained -syn match i3ConfigBorderStyle /^\s*\(new_window\|new_float\|default_border\|default_floating_border\)\s\+\(none\|\(normal\|pixel\)\(\s\+\d\+\)\?\(\s\+\$\w\+\(\(-\w\+\)\+\)\?\(\s\|+\)\?\)\?\)\s\?$/ contains=i3ConfigBorderStyleKeyword,i3ConfigNumber,i3ConfigVariable - -" Hide borders and edges -syn keyword i3ConfigEdgeKeyword none vertical horizontal both smart smart_no_gaps contained -syn match i3ConfigEdge /^\s*hide_edge_borders\s\+\(none\|vertical\|horizontal\|both\|smart\|smart_no_gaps\)\s\?$/ contains=i3ConfigEdgeKeyword - -" Arbitrary commands for specific windows (for_window) -syn keyword i3ConfigCommandKeyword for_window contained -syn region i3ConfigWindowStringSpecial start=+"+ skip=+\\"+ end=+"+ contained contains=i3ConfigString -syn region i3ConfigWindowCommandSpecial start="\[" end="\]" contained contains=i3ConfigWindowStringSpacial,i3ConfigString -syn match i3ConfigArbitraryCommand /^\s*for_window\s\+.*$/ contains=i3ConfigWindowCommandSpecial,i3ConfigCommandKeyword,i3ConfigBorderStyleKeyword,i3ConfigLayoutKeyword,i3ConfigOrientationKeyword,Size,i3ConfigNumber - -" Disable focus open opening -syn keyword i3ConfigNoFocusKeyword no_focus contained -syn match i3ConfigDisableFocus /^\s*no_focus\s\+.*$/ contains=i3ConfigWindowCommandSpecial,i3ConfigNoFocusKeyword - -" Move client to specific workspace automatically -syn keyword i3ConfigAssignKeyword assign contained -syn match i3ConfigAssignSpecial /→/ contained -syn match i3ConfigAssign /^\s*assign\s\+.*$/ contains=i3ConfigAssignKeyword,i3ConfigWindowCommandSpecial,i3ConfigAssignSpecial +syn match i3ConfigSize / -\?\d\+ x -\?\d\+/ contained contains=i3ConfigSizeSpecial,i3ConfigNumber +syn match i3ConfigKeyword /^floating_\(maximum\|minimum\)_size .*$/ contains=i3ConfigSize -" X resources -syn keyword i3ConfigResourceKeyword set_from_resource contained -syn match i3ConfigResource /^\s*set_from_resource\s\+.*$/ contains=i3ConfigResourceKeyword,i3ConfigWindowCommandSpecial,i3ConfigColor,i3ConfigVariable +" 4.9 Orientation +syn keyword i3ConfigOrientationOpts vertical horizontal auto contained +syn match i3ConfigKeyword /^default_orientation \w*$/ contains=i3ConfigOrientationOpts -" Auto start applications -syn keyword i3ConfigExecKeyword exec exec_always contained -syn match i3ConfigNoStartupId /--no-startup-id/ contained " We are not using i3ConfigBindArgument as only no-startup-id is supported here -syn match i3ConfigExec /^\s*exec\(_always\)\?\s\+.*$/ contains=i3ConfigExecKeyword,i3ConfigNoStartupId,i3ConfigString +" 4.10 Layout mode +syn keyword i3ConfigWorkspaceLayoutOpts default stacking tabbed contained +syn match i3ConfigKeyword /^workspace_layout \w*$/ contains=i3ConfigWorkspaceLayoutOpts -" Automatically putting workspaces on specific screens -syn keyword i3ConfigWorkspaceKeyword workspace contained -syn keyword i3ConfigOutput output contained -syn match i3ConfigWorkspace /^\s*workspace\s\+.*$/ contains=i3ConfigWorkspaceKeyword,i3ConfigNumber,i3ConfigString,i3ConfigOutput +" 4.11 Title alignment +syn keyword i3ConfigTitleAlignOpts left center right contained +syn match i3ConfigKeyword /^title_align .*$/ contains=i3ConfigTitleAlignOpts -" Changing colors -syn keyword i3ConfigClientColorKeyword client focused focused_inactive unfocused urgent placeholder background contained -syn match i3ConfigClientColor /^\s*client.\w\+\s\+.*$/ contains=i3ConfigClientColorKeyword,i3ConfigColor,i3ConfigVariable +" 4.12 Border style +syn keyword i3ConfigBorderOpts none normal pixel contained +syn match i3ConfigKeyword /^default\(_floating\)\?_border .*$/ contains=i3ConfigBorderOpts,i3ConfigNumber,i3ConfigVariable -syn keyword i3ConfigTitleAlignKeyword left center right contained -syn match i3ConfigTitleAlign /^\s*title_align\s\+.*$/ contains=i3ConfigTitleAlignKeyword +" 4.13 Hide edge borders +syn keyword i3ConfigEdgeOpts none vertical horizontal both smart smart_no_gaps contained +syn match i3ConfigKeyword /^hide_edge_borders \w\+$/ contains=i3ConfigEdgeOpts -" Interprocess communication -syn match i3ConfigInterprocessKeyword /ipc-socket/ contained -syn match i3ConfigInterprocess /^\s*ipc-socket\s\+.*$/ contains=i3ConfigInterprocessKeyword +" 4.14 Smart Borders +syn keyword i3ConfigSmartBorderOpts no_gaps contained +syn match i3ConfigKeyword /^smart_borders \(on\|off\|no_gaps\)$/ contains=i3ConfigSmartBorderOpts,i3ConfigBoolean -" Mouse warping -syn keyword i3ConfigMouseWarpingKeyword mouse_warping contained -syn keyword i3ConfigMouseWarpingType output none contained -syn match i3ConfigMouseWarping /^\s*mouse_warping\s\+\(output\|none\)\s\?$/ contains=i3ConfigMouseWarpingKeyword,i3ConfigMouseWarpingType +" 4.15 Arbitrary commands +syn keyword i3ConfigForWindowKeyword for_window contained +syn region i3ConfigForWindow start=/^for_window / end=/$/ contains=i3ConfigForWindowKeyword,i3ConfigCriteria keepend -" Focus follows mouse -syn keyword i3ConfigFocusFollowsMouseKeyword focus_follows_mouse contained -syn keyword i3ConfigFocusFollowsMouseType yes no contained -syn match i3ConfigFocusFollowsMouse /^\s*focus_follows_mouse\s\+\(yes\|no\)\s\?$/ contains=i3ConfigFocusFollowsMouseKeyword,i3ConfigFocusFollowsMouseType +" 4.16 No opening focus +syn match i3ConfigKeyword /^no_focus .*$/ contains=i3ConfigCondition -" Popups during fullscreen mode -syn keyword i3ConfigPopupOnFullscreenKeyword popup_during_fullscreen contained -syn keyword i3ConfigPopuponFullscreenType smart ignore leave_fullscreen contained -syn match i3ConfigPopupOnFullscreen /^\s*popup_during_fullscreen\s\+\w\+\s\?$/ contains=i3ConfigPopupOnFullscreenKeyword,i3ConfigPopupOnFullscreenType +" 4.17 Variables +syn match i3ConfigVariable /\$[A-Z0-9a-z_:|[\]-]\+/ +syn keyword i3ConfigSetKeyword set contained +syn match i3ConfigSet /^set \$.*$/ contains=i3ConfigVariable,i3ConfigSetKeyword,i3ConfigColor,i3ConfigString,i3ConfigNoStartupId,i3ConfigNumber,i3ConfigShCommand,i3ConfigShDelim,i3ConfigShParam,i3ConfigShOper -" Focus wrapping -syn keyword i3ConfigFocusWrappingKeyword force_focus_wrapping focus_wrapping contained -syn keyword i3ConfigFocusWrappingType yes no contained -syn match i3ConfigFocusWrapping /^\s*\(force_\)\?focus_wrapping\s\+\(yes\|no\)\s\?$/ contains=i3ConfigFocusWrappingType,i3ConfigFocusWrappingKeyword +" 4.18 X resources +syn keyword i3ConfigResourceKeyword set_from_resource contained +syn match i3ConfigResource /^set_from_resource\s\+.*$/ contains=i3ConfigResourceKeyword,i3ConfigCondition,i3ConfigColor,i3ConfigVariable,i3ConfigString,i3ConfigNumber + +" 4.19 Assign clients to workspaces +syn keyword i3ConfigAssignKeyword assign contained +syn match i3ConfigAssignSpecial /→/ contained +syn match i3ConfigAssign /^assign .*$/ contains=i3ConfigAssignKeyword,i3ConfigAssignSpecial,i3ConfigCondition,i3ConfigVariable,i3ConfigString,i3ConfigNumber + +" 4.20 Executing shell commands +syn keyword i3ConfigExecKeyword exec contained +syn keyword i3ConfigExecAlwaysKeyword exec_always contained +syn match i3ConfigShCmdDelim /\$(/ contained +syn region i3ConfigShCommand start=/\$(/ end=/)/ contained contains=i3ConfigShCmdDelim,i3ConfigShCommand,i3ConfigShDelim,i3ConfigShOper,i3ConfigShParam,i3ConfigString,i3ConfigNumber,i3ConfigVariable keepend extend +syn match i3ConfigShDelim /[[\]{}();`]\+/ contained +syn match i3ConfigShOper /[<>&|+=~^*!.?]\+/ contained +syn match i3ConfigShParam /\<-[a-zA-Z0-9_-]\+\>/ contained containedin=i3ConfigVar +syn region i3ConfigExec start=/^\s*exec\(_always\)\?\( --no-startup-id\)\? [^{]/ skip=/\\$/ end=/$/ contains=i3ConfigExecKeyword,i3ConfigExecAlwaysKeyword,i3ConfigShCommand,i3ConfigShDelim,i3ConfigShOper,i3ConfigShParam,i3ConfigNumber,i3ConfigString,i3ConfigVariable,i3ConfigExecAction keepend + +" 4.21 Workspaces per output +syn keyword i3ConfigWorkspaceKeyword workspace contained +syn keyword i3ConfigWorkspaceOutput output contained +syn keyword i3ConfigWorkspaceDir prev next back_and_forth contained +syn region i3ConfigWorkspaceLine start=/^workspace / skip=/\\$/ end=/$/ contains=i3ConfigWorkspaceKeyword,i3ConfigNumber,i3ConfigString,i3ConfigGaps,i3ConfigWorkspaceOutput,i3ConfigVariable,i3ConfigBoolean,i3ConfigSeparator keepend -" Forcing Xinerama -syn keyword i3ConfigForceXineramaKeyword force_xinerama contained -syn match i3ConfigForceXinerama /^\s*force_xinerama\s\+\(yes\|no\)\s\?$/ contains=i3ConfigFocusWrappingType,i3ConfigForceXineramaKeyword +" 4.22 Changing colors +syn match i3ConfigDotOperator /\./ contained +syn keyword i3ConfigClientOpts focused focused_inactive unfocused urgent placeholder background contained +syn match i3ConfigKeyword /^client\..*$/ contains=i3ConfigDotOperator,i3ConfigClientOpts,i3ConfigColor,i3ConfigVariable -" Automatic back-and-forth when switching to the current workspace -syn keyword i3ConfigAutomaticSwitchKeyword workspace_auto_back_and_forth contained -syn match i3ConfigAutomaticSwitch /^\s*workspace_auto_back_and_forth\s\+\(yes\|no\)\s\?$/ contains=i3ConfigFocusWrappingType,i3ConfigAutomaticSwitchKeyword +" 4.23 Interprocess communication +syn match i3ConfigIpcKeyword /ipc-socket/ contained +syn match i3ConfigIpc /^ipc-socket .*$/ contains=i3ConfigIpcKeyword -" Delay urgency hint -syn keyword i3ConfigTimeUnit ms contained -syn keyword i3ConfigDelayUrgencyKeyword force_display_urgency_hint contained -syn match i3ConfigDelayUrgency /^\s*force_display_urgency_hint\s\+\d\+\s\+ms\s\?$/ contains=i3ConfigFocusWrappingType,i3ConfigDelayUrgencyKeyword,i3ConfigNumber,i3ConfigTimeUnit +" 4.24 Focus follows mouse +syn keyword i3ConfigFocusFollowsMouseOpts always contained +syn match i3ConfigFocusFollowsMouse /^focus_follows_mouse \(yes\|no\|always\)$/ contains=i3ConfigBoolean,i3ConfigFocusFollowsMouseOpts -" Focus on window activation -syn keyword i3ConfigFocusOnActivationKeyword focus_on_window_activation contained -syn keyword i3ConfigFocusOnActivationType smart urgent focus none contained -syn match i3ConfigFocusOnActivation /^\s*focus_on_window_activation\s\+\(smart\|urgent\|focus\|none\)\s\?$/ contains=i3ConfigFocusOnActivationKeyword,i3ConfigFocusOnActivationType +" 4.25 Mouse warping +syn keyword i3ConfigMouseWarpingOpts output container none contained +syn match i3ConfigMouseWarping /^mouse_warping \w*$/ contains=i3ConfigMouseWarpingOpts -" Automatic back-and-forth when switching to the current workspace -syn keyword i3ConfigDrawingMarksKeyword show_marks contained -syn match i3ConfigDrawingMarks /^\s*show_marks\s\+\(yes\|no\)\s\?$/ contains=i3ConfigFocusWrappingType,i3ConfigDrawingMarksKeyword +" 4.26 Popups while fullscreen +syn keyword i3ConfigPopupFullscreenOpts smart ignore leave_fullscreen contained +syn match i3ConfigPopupFullscreen /^popup_during_fullscreen \w*$/ contains=i3ConfigPopupFullscreenOpts -" Group mode/bar -syn keyword i3ConfigBlockKeyword mode bar colors i3bar_command status_command position exec mode hidden_state modifier id position output background statusline tray_output tray_padding separator separator_symbol workspace_min_width workspace_buttons strip_workspace_numbers binding_mode_indicator focused_workspace active_workspace inactive_workspace urgent_workspace binding_mode contained -syn region i3ConfigBlock start=+^\s*[^#]*s\?{$+ end=+^\s*[^#]*}$+ contains=i3ConfigBlockKeyword,i3ConfigString,i3ConfigBind,i3ConfigComment,i3ConfigFont,i3ConfigFocusWrappingType,i3ConfigColor,i3ConfigVariable transparent keepend extend +" 4.27 Focus wrapping +syn keyword i3ConfigFocusWrappingOpts force workspace contained +syn match i3ConfigFocusWrapping /^focus_wrapping \(yes\|no\|force\|workspace\)$/ contains=i3ConfigBoolean,i3ConfigFocusWrappingOpts -" Line continuation -syn region i3ConfigLineCont start=/^.*\\$/ end=/^.*$/ contains=i3ConfigBlockKeyword,i3ConfigString,i3ConfigBind,i3ConfigComment,i3ConfigFont,i3ConfigFocusWrappingType,i3ConfigColor,i3ConfigVariable transparent keepend extend +" 4.28 Forcing Xinerama +syn match i3ConfigForceXinerama /^force_xinerama \(yes\|no\)$/ contains=i3ConfigBoolean + +" 4.29 Automatic workspace back-and-forth +syn match i3ConfigAutomaticSwitch /^workspace_auto_back_and_forth \(yes\|no\)$/ contains=i3ConfigBoolean + +" 4.30 Delay urgency hint +syn keyword i3ConfigTimeUnit ms contained +syn match i3ConfigDelayUrgency /^force_display_urgency_hint \d\+ ms$/ contains=i3ConfigBoolean,i3ConfigNumber,i3ConfigTimeUnit + +" 4.31 Focus on window activation +syn keyword i3ConfigFocusOnActivationOpts smart urgent focus none contained +syn match i3ConfigFocusOnActivation /^focus_on_window_activation .*$/ contains=i3ConfigFocusOnActivationKeyword + +" 4.32 Show marks in title +syn match i3ConfigShowMarks /^show_marks \(yes\|no\)$/ contains=i3ConfigBoolean + +" 4.34 Tiling drag +syn keyword i3ConfigTilingDragOpts modifier titlebar contained +syn match i3ConfigTilingDrag /^tiling_drag\( off\|\( modifier\| titlebar\)\{1,2\}\)$/ contains=i3ConfigTilingOpts,i3ConfigBoolean + +" 4.35 Gaps +syn keyword i3ConfigGapsOpts inner outer horizontal vertical left right top bottom current all set plus minus toggle contained +syn region i3ConfigGaps start=/gaps/ skip=/\\$/ end=/\([,;]\|$\)/ contained contains=i3ConfigGapsOpts,i3ConfigNumber,i3ConfigVariable,i3ConfigSeparator keepend +syn match i3ConfigGapStyleLine /^gaps .*$/ contains=i3ConfigGaps +syn keyword i3ConfigSmartGapOpts inverse_outer contained +syn match i3ConfigSmartGap /^smart_gaps \(on\|off\|inverse_outer\)$/ contains=i3ConfigSmartGapOpts,i3ConfigBoolean + +" 5 Configuring bar +syn match i3ConfigBarModifier /^\s\+modifier [^ ]\+$/ contained contains=i3ConfigBindModifier,i3ConfigVariable,i3ConfigBindModkey +syn keyword i3ConfigBarOpts bar i3bar_command status_command mode hidden_state id position output tray_output tray_padding font separator_symbol workspace_buttons workspace_min_width strip_workspace_numbers strip_workspace_name binding_mode_indicator padding contained +syn region i3ConfigBarBlock start=/^bar {$/ end=/^}$/ contains=i3ConfigBarOpts,i3ConfigBarModifier,i3ConfigBind,i3ConfigString,i3ConfigComment,i3ConfigFont,i3ConfigBoolean,i3ConfigNumber,i3ConfigParen,i3ConfigColor,i3ConfigVariable,i3ConfigColorsBlock fold keepend extend + +" 5.16 Color block +syn match i3ConfigColorsOpts /\(focused_\)\?\(background\|statusline\|separator\)\|\(focused\|active\|inactive\|urgent\)_workspace\|binding_mode/ contained +syn region i3ConfigColorsBlock start=/^\s\+colors {$/ end=/^\s\+}$/ contained contains=i3ConfigColorsOpts,i3ConfigColor,i3ConfigVariable,i3ConfigComment,i3ConfigParen fold keepend extend + +" 6.0 Criteria-based commands +syn match i3ConfigConditionProp /\w\+\(-\w\+\)*/ contained +syn match i3ConfigConditionText /[^[ ]\+=/ contained contains=i3ConfigConditionProp,i3ConfigShOper +syn keyword i3ConfigConditionFocused __focused__ contained +syn region i3ConfigCondition start=/\[/ end=/\]/ contained contains=i3ConfigConditionText,i3ConfigShDelim,i3ConfigNumber,i3ConfigString,i3ConfigConditionFocused keepend extend +syn region i3ConfigCriteria start=/\[/ skip=/\\$/ end=/\(;\|$\)/ contained contains=i3ConfigCondition,i3ConfigAction,i3ConfigActionKeyword,i3ConfigOption,i3ConfigBoolean,i3ConfigNumber,i3ConfigVariable,i3ConfigSeparator keepend + +" 6.1 Actions through shell +syn match i3ConfigExecActionKeyword /i3-msg/ contained +syn region i3ConfigExecAction start=/[a-z3-]\+msg '/ skip=/\\$\| '/ end=/'/ contained contains=i3ConfigExecActionKeyword,i3ConfigShCommand,i3ConfigNumber,i3ConfigShOper,i3ConfigCriteria,i3ConfigAction,i3ConfigActionKeyword,i3ConfigOption,i3ConfigVariable keepend extend +syn region i3ConfigExecAction start=/[a-z3-]\+msg "/ skip=/\\$\| "/ end=/"/ contained contains=i3ConfigExecActionKeyword,i3ConfigShCommand,i3ConfigNumber,i3ConfigShOper,i3ConfigCriteria,i3ConfigAction,i3ConfigActionKeyword,i3ConfigOption,i3ConfigVariable keepend extend +syn region i3ConfigExecAction start=/[a-z3-]\+msg\( ['"-]\)\@!/ skip=/\\$/ end=/\([&|;})'"]\|$\)/ contained contains=i3ConfigExecActionKeyword,i3ConfigShCommand,i3ConfigNumber,i3ConfigShOper,i3ConfigCriteria,i3ConfigAction,i3ConfigActionKeyword,i3ConfigOption,i3ConfigVariable keepend +" 6.1 Executing applications (4.20) +syn region i3ConfigAction start=/exec/ skip=/\\$/ end=/\([,;]\|$\)/ contained contains=i3ConfigExecKeyword,i3ConfigShCommand,i3ConfigShDelim,i3ConfigShOper,i3ConfigShParam,i3ConfigNumber,i3ConfigString,i3ConfigVariable,i3ConfigSeparator keepend + +" 6.3 Manipulating layout +syn keyword i3ConfigLayoutKeyword layout contained +syn keyword i3ConfigLayoutOpts default tabbed stacking splitv splith toggle split all contained +syn region i3ConfigAction start=/layout/ skip=/\\$/ end=/\([,;]\|$\)/ contained contains=i3ConfigLayoutKeyword,i3ConfigLayoutOpts,i3ConfigSeparator keepend + +" 6.4 Focusing containers +syn keyword i3ConfigFocusKeyword focus contained +syn keyword i3ConfigFocusOpts left right up down parent child next prev sibling floating tiling mode_toggle contained +syn keyword i3ConfigFocusOutputOpts left right down up current primary nonprimary next prev contained +syn region i3ConfigFocusOutput start=/ output / skip=/\\$/ end=/\([,;]\|$\)/ contained contains=i3ConfigMoveType,i3ConfigWorkspaceOutput,i3ConfigFocusOutputOpts,i3ConfigString,i3ConfigNumber,i3ConfigSeparator keepend +syn match i3ConfigFocusOutputLine /^focus output .*$/ contains=i3ConfigFocusKeyword,i3ConfigFocusOutput +syn region i3ConfigAction start=/focus/ skip=/\\$/ end=/\([,;]\|$\)/ contained contains=i3ConfigFocusKeyword,i3ConfigFocusOpts,i3ConfigFocusOutput,i3ConfigString,i3ConfigSeparator keepend + +" 6.8 Focusing workspaces (4.21) +syn region i3ConfigAction start=/workspace / skip=/\\$/ end=/\([,;]\|$\)/ contained contains=i3ConfigWorkspaceKeyword,i3ConfigWorkspaceDir,i3ConfigNumber,i3ConfigString,i3ConfigGaps,i3ConfigWorkspaceOutput,i3ConfigVariable,i3ConfigBoolean,i3ConfigSeparator keepend + +" 6.9-6.11 Moving containers +syn keyword i3ConfigMoveKeyword move contained +syn keyword i3ConfigMoveDir left right down up position absolute center to contained +syn keyword i3ConfigMoveType window container workspace output mark mouse scratchpad contained +syn match i3ConfigUnit / px\| ppt/ contained +syn region i3ConfigAction start=/move/ skip=/\\$/ end=/\([,;]\|$\)/ contained contains=i3ConfigMoveKeyword,i3ConfigMoveDir,i3ConfigMoveType,i3ConfigWorkspaceDir,i3ConfigUnit,i3ConfigNumber,i3ConfigVariable,i3ConfigSeparator keepend + +" 6.12 Resizing containers/windows +syn keyword i3ConfigResizeKeyword resize contained +syn keyword i3ConfigResizeOpts grow shrink up down left right set width height or contained +syn region i3ConfigAction start=/resize/ skip=/\\$/ end=/\([,;]\|$\)/ contained contains=i3ConfigResizeKeyword,i3ConfigResizeOpts,i3ConfigNumber,i3ConfigUnit,i3ConfigSeparator keepend + +" 6.14 VIM-like marks +syn keyword i3ConfigMarkKeyword mark contained +syn match i3ConfigMark /mark\( --\(add\|replace\)\( --toggle\)\?\)\?/ contained contains=i3ConfigMarkKeyword +syn region i3ConfigAction start=/\<mark/ skip=/\\$/ end=/\([,;]\|$\)/ contained contains=i3ConfigMark,i3ConfigNumber,i3ConfigString,i3ConfigSeparator keepend + +" 6.24 Changing gaps (4.35) +syn region i3ConfigAction start=/gaps/ skip=/\\$/ end=/\([,;]\|$\)/ contained contains=i3ConfigGaps keepend + +" Commands useable in keybinds +syn keyword i3ConfigActionKeyword mode append_layout kill open fullscreen sticky split floating swap rename unmark show_marks title_window_icon title_format border restart reload exit scratchpad nop bar contained +syn keyword i3ConfigOption enable disable toggle key restore current horizontal vertical auto none normal pixel show container with id con_id contained " Define the highlighting. hi def link i3ConfigError Error hi def link i3ConfigTodo Todo -hi def link i3ConfigComment Comment -hi def link i3ConfigFontContent Type -hi def link i3ConfigFocusOnActivationType Type -hi def link i3ConfigPopupOnFullscreenType Type -hi def link i3ConfigOrientationKeyword Type -hi def link i3ConfigMouseWarpingType Type -hi def link i3ConfigFocusFollowsMouseType Type -hi def link i3ConfigGapStyleKeyword Type -hi def link i3ConfigTitleAlignKeyword Type -hi def link i3ConfigSmartGapKeyword Type -hi def link i3ConfigSmartBorderKeyword Type -hi def link i3ConfigLayoutKeyword Type -hi def link i3ConfigBorderStyleKeyword Type -hi def link i3ConfigEdgeKeyword Type -hi def link i3ConfigAction Type -hi def link i3ConfigCommand Type -hi def link i3ConfigOutput Type -hi def link i3ConfigWindowCommandSpecial Type -hi def link i3ConfigFocusWrappingType Type -hi def link i3ConfigUnitOr Type -hi def link i3ConfigFontSize Constant +hi def link i3ConfigKeyword Keyword +hi def link i3ConfigCommand Statement +hi def link i3ConfigOperator Operator +hi def link i3ConfigSeparator i3ConfigOperator +hi def link i3ConfigParen Delimiter +hi def link i3ConfigBoolean Boolean +hi def link i3ConfigString String hi def link i3ConfigColor Constant -hi def link i3ConfigNumber Constant -hi def link i3ConfigUnit Constant -hi def link i3ConfigVariableAndModifier Constant -hi def link i3ConfigTimeUnit Constant -hi def link i3ConfigModifier Constant -hi def link i3ConfigString Constant -hi def link i3ConfigNegativeSize Constant -hi def link i3ConfigInclude Constant -hi def link i3ConfigFontSeparator Special -hi def link i3ConfigVariableModifier Special -hi def link i3ConfigSizeSpecial Special -hi def link i3ConfigWindowSpecial Special -hi def link i3ConfigAssignSpecial Special -hi def link i3ConfigFontNamespace PreProc -hi def link i3ConfigBindArgument PreProc -hi def link i3ConfigNoStartupId PreProc -hi def link i3ConfigIncludeKeyword Identifier -hi def link i3ConfigFontKeyword Identifier -hi def link i3ConfigBindKeyword Identifier -hi def link i3ConfigOrientation Identifier -hi def link i3ConfigGapStyle Identifier -hi def link i3ConfigTitleAlign Identifier -hi def link i3ConfigSmartGap Identifier -hi def link i3ConfigSmartBorder Identifier -hi def link i3ConfigLayout Identifier -hi def link i3ConfigBorderStyle Identifier -hi def link i3ConfigEdge Identifier -hi def link i3ConfigFloating Identifier -hi def link i3ConfigFloatingModifier Identifier -hi def link i3ConfigCommandKeyword Identifier -hi def link i3ConfigNoFocusKeyword Identifier -hi def link i3ConfigInitializeKeyword Identifier -hi def link i3ConfigAssignKeyword Identifier -hi def link i3ConfigResourceKeyword Identifier -hi def link i3ConfigExecKeyword Identifier -hi def link i3ConfigWorkspaceKeyword Identifier -hi def link i3ConfigClientColorKeyword Identifier -hi def link i3ConfigInterprocessKeyword Identifier -hi def link i3ConfigMouseWarpingKeyword Identifier -hi def link i3ConfigFocusFollowsMouseKeyword Identifier -hi def link i3ConfigPopupOnFullscreenKeyword Identifier -hi def link i3ConfigFocusWrappingKeyword Identifier -hi def link i3ConfigForceXineramaKeyword Identifier -hi def link i3ConfigAutomaticSwitchKeyword Identifier -hi def link i3ConfigDelayUrgencyKeyword Identifier -hi def link i3ConfigFocusOnActivationKeyword Identifier -hi def link i3ConfigDrawingMarksKeyword Identifier -hi def link i3ConfigBlockKeyword Identifier -hi def link i3ConfigVariable Statement -hi def link i3ConfigArbitraryCommand Type - -let b:current_syntax = "i3config" +hi def link i3ConfigNumber Number +hi def link i3ConfigIncludeKeyword i3ConfigKeyword +hi def link i3ConfigInclude i3ConfigString +hi def link i3ConfigComment Comment +hi def link i3ConfigFontKeyword i3ConfigKeyword +hi def link i3ConfigColonOperator i3ConfigOperator +hi def link i3ConfigFontNamespace i3ConfigOption +hi def link i3ConfigFontSize i3ConfigNumber +hi def link i3ConfigFont i3ConfigString +hi def link i3ConfigBindKeyword i3ConfigKeyword +hi def link i3ConfigBindArgument i3ConfigShParam +hi def link i3ConfigBindModifier i3ConfigOperator +hi def link i3ConfigBindModkey Special +hi def link i3ConfigBindCombo SpecialChar +hi def link i3ConfigSizeSpecial i3ConfigOperator +hi def link i3ConfigOrientationOpts i3ConfigOption +hi def link i3ConfigWorkspaceLayoutOpts i3ConfigOption +hi def link i3ConfigTitleAlignOpts i3ConfigOption +hi def link i3ConfigBorderOpts i3ConfigOption +hi def link i3ConfigEdgeOpts i3ConfigOption +hi def link i3ConfigSmartBorderOpts i3ConfigOption +hi def link i3ConfigForWindowKeyword i3ConfigKeyword +hi def link i3ConfigVariable Variable +hi def link i3ConfigSetKeyword i3ConfigKeyword +hi def link i3ConfigResourceKeyword i3ConfigKeyword +hi def link i3ConfigResource i3ConfigString +hi def link i3ConfigAssignKeyword i3ConfigKeyword +hi def link i3ConfigAssignSpecial i3ConfigOption +hi def link i3ConfigExecKeyword i3ConfigCommand +hi def link i3ConfigExecAlwaysKeyword i3ConfigKeyword +hi def link i3ConfigShParam PreProc +hi def link i3ConfigShDelim Delimiter +hi def link i3ConfigShOper Operator +hi def link i3ConfigShCmdDelim i3ConfigShDelim +hi def link i3ConfigShCommand Normal +hi def link i3ConfigWorkspaceKeyword i3ConfigCommand +hi def link i3ConfigWorkspaceOutput i3ConfigOption +hi def link i3ConfigWorkspaceDir i3ConfigOption +hi def link i3ConfigDotOperator i3ConfigOperator +hi def link i3ConfigClientOpts i3ConfigOption +hi def link i3ConfigIpcKeyword i3ConfigKeyword +hi def link i3ConfigIpc i3ConfigString +hi def link i3ConfigFocusFollowsMouseOpts i3ConfigOption +hi def link i3ConfigFocusFollowsMouse i3ConfigKeyword +hi def link i3ConfigMouseWarpingOpts i3ConfigOption +hi def link i3ConfigMouseWarping i3ConfigKeyword +hi def link i3ConfigPopupFullscreenOpts i3ConfigOption +hi def link i3ConfigPopupFullscreen i3ConfigKeyword +hi def link i3ConfigFocusWrappingOpts i3ConfigOption +hi def link i3ConfigFocusWrapping i3ConfigKeyword +hi def link i3ConfigForceXinerama i3ConfigKeyword +hi def link i3ConfigAutomaticSwitch i3ConfigKeyword +hi def link i3ConfigTimeUnit i3ConfigNumber +hi def link i3ConfigDelayUrgency i3ConfigKeyword +hi def link i3ConfigFocusOnActivationOpts i3ConfigOption +hi def link i3ConfigFocusOnActivation i3ConfigKeyword +hi def link i3ConfigShowMarks i3ConfigCommand +hi def link i3ConfigTilingDragOpts i3ConfigOption +hi def link i3ConfigTilingDrag i3ConfigKeyword +hi def link i3ConfigGapsOpts i3ConfigOption +hi def link i3ConfigGaps i3ConfigCommand +hi def link i3ConfigSmartGapOpts i3ConfigOption +hi def link i3ConfigSmartGap i3ConfigKeyword +hi def link i3ConfigBarModifier i3ConfigKeyword +hi def link i3ConfigBarOpts i3ConfigKeyword +hi def link i3ConfigColorsOpts i3ConfigOption +hi def link i3ConfigColors i3ConfigKeyword +hi def link i3ConfigConditionProp i3ConfigShParam +hi def link i3ConfigConditionFocused Constant +hi def link i3ConfigExecActionKeyword i3ConfigShCommand +hi def link i3ConfigExecAction i3ConfigString +hi def link i3ConfigLayoutKeyword i3ConfigCommand +hi def link i3ConfigLayoutOpts i3ConfigOption +hi def link i3ConfigFocusKeyword i3ConfigCommand +hi def link i3ConfigFocusOpts i3ConfigOption +hi def link i3ConfigFocusOutputOpts i3ConfigOption +hi def link i3ConfigMoveKeyword i3ConfigCommand +hi def link i3ConfigMoveDir i3ConfigOption +hi def link i3ConfigMoveType i3ConfigOption +hi def link i3ConfigUnit i3ConfigNumber +hi def link i3ConfigResizeKeyword i3ConfigCommand +hi def link i3ConfigResizeOpts i3ConfigOption +hi def link i3ConfigMarkKeyword i3ConfigCommand +hi def link i3ConfigMark i3ConfigShParam +hi def link i3ConfigActionKeyword i3ConfigCommand +hi def link i3ConfigOption Type + +let b:current_syntax = "i3config"
\ No newline at end of file diff --git a/runtime/syntax/masm.vim b/runtime/syntax/masm.vim index 3be0fd45d1..85e457106d 100644 --- a/runtime/syntax/masm.vim +++ b/runtime/syntax/masm.vim @@ -2,7 +2,7 @@ " Language: Microsoft Macro Assembler (80x86) " Orig Author: Rob Brady <robb@datatone.com> " Maintainer: Wu Yongwei <wuyongwei@gmail.com> -" Last Change: 2022-04-24 20:07:04 +0800 +" Last Change: 2023-09-09 20:48:26 +0800 " Quit when a syntax file was already loaded if exists("b:current_syntax") @@ -194,8 +194,8 @@ syn keyword masmRegister R8W R9W R10W R11W R12W R13W R14W R15W syn keyword masmRegister R8B R9B R10B R11B R12B R13B R14B R15B " SSE/AVX registers -syn match masmRegister "\(X\|Y\)MM[0-9]\>" -syn match masmRegister "\(X\|Y\)MM1[0-5]\>" +syn match masmRegister "\(X\|Y\|Z\)MM[12]\?[0-9]\>" +syn match masmRegister "\(X\|Y\|Z\)MM3[01]\>" " Instruction prefixes syn keyword masmOpcode LOCK REP REPE REPNE REPNZ REPZ @@ -338,11 +338,192 @@ syn keyword masmOpcode VINSERTF128 VEXTRACTF128 VMASKMOVPS VMASKMOVPD syn keyword masmOpcode VPERMILPS VPERMILPD VPERM2F128 syn keyword masmOpcode VZEROALL VZEROUPPER +" AVX-2 (Haswell and later) +syn keyword masmOpcode VPBROADCASTB VPBROADCASTW VPBROADCASTD +syn keyword masmOpcode VPBROADCASTQ VBROADCASTI128 +syn keyword masmOpcode VINSERTI128 VEXTRACTI128 +syn keyword masmOpcode VGATHERDPD VGATHERQPD VGATHERDPS VGATHERQPS +syn keyword masmOpcode VPGATHERDD VPGATHERDQ VPGATHERQD VPGATHERQQ +syn keyword masmOpcode VPMASKMOVD VPMASKMOVQ +syn keyword masmOpcode PERMPS VPERMD VPERMPD VPERMQ VPERM2I128 +syn keyword masmOpcode VPBLENDD VPSLLVD VPSLLVQ VPSRLVD VPSRLVQ +syn keyword masmOpcode VPSRAVD + +" AVX-512 (Knights Landing/Skylake-X and later) +syn keyword masmOpcode KAND KANDN KMOV KUNPCK KNOT KOR KORTEST +syn keyword masmOpcode KSHIFTL KSHIFTR KXNOR KXOR KADD KTEST +syn keyword masmOpcode VBLENDMPD VBLENDMPS +syn keyword masmOpcode VPBLENDMD VPBLENDMQ VPBLENDMB VPBLENDMW +syn keyword masmOpcode VPCMPD VPCMPUD VPCMPQ VPCMPUQ +syn keyword masmOpcode VPCMPB VPCMPUB VPCMPW VPCMPUW +syn keyword masmOpcode VPTESTMD VPTESTMQ VPTESTNMD VPTESTNMQ +syn keyword masmOpcode VPTESTMB VPTESTMW VPTESTNMB VPTESTNMW +syn keyword masmOpcode VCOMPRESSPD VCOMPRESSPS VPCOMPRESSD VPCOMPRESSQ +syn keyword masmOpcode VEXPANDPD VEXPANDPS VPEXPANDD VPEXPANDQ +syn keyword masmOpcode VPERMB VPERMW VPERMT2B VPERMT2W VPERMI2PD +syn keyword masmOpcode VPERMI2PS VPERMI2D VPERMI2Q VPERMI2B VPERMI2W +syn keyword masmOpcode VPERMT2PS VPERMT2PD VPERMT2D VPERMT2Q +syn keyword masmOpcode VSHUFF32x4 VSHUFF64x2 VSHUFI32x4 VSHUFI64x2 +syn keyword masmOpcode VPMULTISHIFTQB VPTERNLOGD VPTERNLOGQ +syn keyword masmOpcode VPMOVQD VPMOVSQD VPMOVUSQD VPMOVQW VPMOVSQW +syn keyword masmOpcode VPMOVUSQW VPMOVQB VPMOVSQB VPMOVUSQB VPMOVDW +syn keyword masmOpcode VPMOVSDW VPMOVUSDW VPMOVDB VPMOVSDB VPMOVUSDB +syn keyword masmOpcode VPMOVWB VPMOVSWB VPMOVUSWB +syn keyword masmOpcode VCVTPS2UDQ VCVTPD2UDQ VCVTTPS2UDQ VCVTTPD2UDQ +syn keyword masmOpcode VCVTSS2USI VCVTSD2USI VCVTTSS2USI VCVTTSD2USI +syn keyword masmOpcode VCVTPS2QQ VCVTPD2QQ VCVTPS2UQQ VCVTPD2UQQ +syn keyword masmOpcode VCVTTPS2QQ VCVTTPD2QQ VCVTTPS2UQQ VCVTTPD2UQQ +syn keyword masmOpcode VCVTUDQ2PS VCVTUDQ2PD VCVTUSI2PS VCVTUSI2PD +syn keyword masmOpcode VCVTUSI2SD VCVTUSI2SS VCVTUQQ2PS VCVTUQQ2PD +syn keyword masmOpcode VCVTQQ2PD VCVTQQ2PS VGETEXPPD +syn keyword masmOpcode VGETEXPPS VGETEXPSD VGETEXPSS +syn keyword masmOpcode VGETMANTPD VGETMANTPS VGETMANTSD VGETMANTSS +syn keyword masmOpcode VFIXUPIMMPD VFIXUPIMMPS VFIXUPIMMSD VFIXUPIMMSS +syn keyword masmOpcode VRCP14PD VRCP14PS VRCP14SD VRCP14SS +syn keyword masmOpcode VRNDSCALEPS VRNDSCALEPD VRNDSCALESS VRNDSCALESD +syn keyword masmOpcode VRSQRT14PD VRSQRT14PS VRSQRT14SD VRSQRT14SS +syn keyword masmOpcode VSCALEFPS VSCALEFPD VSCALEFSS VSCALEFSD +syn keyword masmOpcode VBROADCASTI32X2 VBROADCASTI32X4 VBROADCASTI32X8 +syn keyword masmOpcode VBROADCASTI64X2 VBROADCASTI64X4 +syn keyword masmOpcode VALIGND VALIGNQ VDBPSADBW VPABSQ VPMAXSQ +syn keyword masmOpcode VPMAXUQ VPMINSQ VPMINUQ VPROLD VPROLVD VPROLQ +syn keyword masmOpcode VPROLVQ VPRORD VPRORVD VPRORQ VPRORVQ +syn keyword masmOpcode VPSCATTERDD VPSCATTERDQ VPSCATTERQD VPSCATTERQQ +syn keyword masmOpcode VSCATTERDPS VSCATTERDPD VSCATTERQPS VSCATTERQPD +syn keyword masmOpcode VPCONFLICTD VPCONFLICTQ VPLZCNTD VPLZCNTQ +syn keyword masmOpcode VPBROADCASTMB2Q VPBROADCASTMW2D +syn keyword masmOpcode VEXP2PD VEXP2PS +syn keyword masmOpcode VRCP28PD VRCP28PS VRCP28SD VRCP28SS +syn keyword masmOpcode VRSQRT28PD VRSQRT28PS VRSQRT28SD VRSQRT28SS +syn keyword masmOpcode VGATHERPF0DPS VGATHERPF0QPS VGATHERPF0DPD +syn keyword masmOpcode VGATHERPF0QPD VGATHERPF1DPS VGATHERPF1QPS +syn keyword masmOpcode VGATHERPF1DPD VGATHERPF1QPD VSCATTERPF0DPS +syn keyword masmOpcode VSCATTERPF0QPS VSCATTERPF0DPD VSCATTERPF0QPD +syn keyword masmOpcode VSCATTERPF1DPS VSCATTERPF1QPS VSCATTERPF1DPD +syn keyword masmOpcode VSCATTERPF1QPD +syn keyword masmOpcode V4FMADDPS V4FMADDSS V4FNMADDPS V4FNMADDSS +syn keyword masmOpcode VP4DPWSSD VP4DPWSSDS +syn keyword masmOpcode VFPCLASSPS VFPCLASSPD VFPCLASSSS VFPCLASSSD +syn keyword masmOpcode VRANGEPS VRANGEPD VRANGESS VRANGESD +syn keyword masmOpcode VREDUCEPS VREDUCEPD VREDUCESS VREDUCESD +syn keyword masmOpcode VPMOVM2D VPMOVM2Q VPMOVM2B VPMOVM2W VPMOVD2M +syn keyword masmOpcode VPMOVQ2M VPMOVB2M VPMOVW2M VPMULLQ +syn keyword masmOpcode VPCOMPRESSB VPCOMPRESSW VPEXPANDB VPEXPANDW +syn keyword masmOpcode VPSHLD VPSHLDV VPSHRD VPSHRDV +syn keyword masmOpcode VPDPBUSD VPDPBUSDS VPDPWSSD VPDPWSSDS +syn keyword masmOpcode VPMADD52LUQ VPMADD52HUQ +syn keyword masmOpcode VPOPCNTD VPOPCNTQ VPOPCNTB VPOPCNTW +syn keyword masmOpcode VPSHUFBITQMB VP2INTERSECTD VP2INTERSECTQ +syn keyword masmOpcode VGF2P8AFFINEINVQB VGF2P8AFFINEQB +syn keyword masmOpcode VGF2P8MULB VPCLMULQDQ +syn keyword masmOpcode VAESDEC VAESDECLAST VAESENC VAESENCLAST +syn keyword masmOpcode VCVTNE2PS2BF16 VCVTNEPS2BF16 VDPBF16PS +syn keyword masmOpcode VADDPH VADDSH VSUBPH VSUBSH VMULPH VMULSH +syn keyword masmOpcode VDIVPH VDIVSH VSQRTPH VSQRTSH +syn keyword masmOpcode VFMADD132PH VFMADD213PH VFMADD231PH +syn keyword masmOpcode VFMADD132SH VFMADD213SH VFMADD231SH +syn keyword masmOpcode VFNMADD132PH VFNMADD213PH VFNMADD231PH +syn keyword masmOpcode VFNMADD132SH VFNMADD213SH VFNMADD231SH +syn keyword masmOpcode VFMSUB132PH VFMSUB213PH VFMSUB231PH +syn keyword masmOpcode VFMSUB132SH VFMSUB213SH VFMSUB231SH +syn keyword masmOpcode VFNMSUB132PH VFNMSUB213PH VFNMSUB231PH +syn keyword masmOpcode VFNMSUB132SH VFNMSUB213SH VFNMSUB231SH +syn keyword masmOpcode VFMADDSUB132PH VFMADDSUB213PH VFMADDSUB231PH +syn keyword masmOpcode VFMSUBADD132PH VFMSUBADD213PH VFMSUBADD231PH +syn keyword masmOpcode VREDUCEPH VREDUCESH VRNDSCALEPH VRNDSCALESH +syn keyword masmOpcode VSCALEFPH VSCALEFSH VFMULCPH VFMULCSH VFCMULCPH +syn keyword masmOpcode VFCMULCSH VFMADDCPH VFMADDCSH VFCMADDCPH +syn keyword masmOpcode VFCMADDCSH VRCPPH VRCPSH VRSQRTPH VRSQRTSH +syn keyword masmOpcode VCMPPH VCMPSH VCOMISH VUCOMISH VMAXPH VMAXSH +syn keyword masmOpcode VMINPH VMINSH VFPCLASSPH VFPCLASSSH +syn keyword masmOpcode VCVTW2PH VCVTUW2PH VCVTDQ2PH VCVTUDQ2PH +syn keyword masmOpcode VCVTQQ2PH VCVTUQQ2PH VCVTPS2PHX VCVTPD2PH +syn keyword masmOpcode VCVTSI2SH VCVTUSI2SH VCVTSS2SH VCVTSD2SH +syn keyword masmOpcode VCVTPH2W VCVTTPH2W VCVTPH2UW VCVTTPH2UW +syn keyword masmOpcode VCVTPH2DQ VCVTTPH2DQ VCVTPH2UDQ VCVTTPH2UDQ +syn keyword masmOpcode VCVTPH2QQ VCVTTPH2QQ VCVTPH2UQQ VCVTTPH2UQQ +syn keyword masmOpcode VCVTPH2PSX VCVTPH2PD VCVTSH2SI VCVTTSH2SI +syn keyword masmOpcode VCVTSH2USI VCVTTSH2USI VCVTSH2SS VCVTSH2SD +syn keyword masmOpcode VGETEXPPH VGETEXPSH VGETMANTPH VGETMANTSH +syn keyword masmOpcode VMOVSH VMOVW VADDPD VADDPS VADDSD VADDSS +syn keyword masmOpcode VANDPD VANDPS VANDNPD VANDNPS +syn keyword masmOpcode VCMPPD VCMPPS VCMPSD VCMPSS +syn keyword masmOpcode VCOMISD VCOMISS VDIVPD VDIVPS VDIVSD VDIVSS +syn keyword masmOpcode VCVTDQ2PD VCVTDQ2PS VCVTPD2DQ VCVTPD2PS +syn keyword masmOpcode VCVTPH2PS VCVTPS2PH VCVTPS2DQ VCVTPS2PD +syn keyword masmOpcode VCVTSD2SI VCVTSD2SS VCVTSI2SD VCVTSI2SS +syn keyword masmOpcode VCVTSS2SD VCVTSS2SI VCVTTPD2DQ VCVTTPS2DQ +syn keyword masmOpcode VCVTTSD2SI VCVTTSS2SI VMAXPD VMAXPS +syn keyword masmOpcode VMAXSD VMAXSS VMINPD VMINPS VMINSD VMINSS +syn keyword masmOpcode VMOVAPD VMOVAPS VMOVD VMOVQ VMOVDDUP +syn keyword masmOpcode VMOVHLPS VMOVHPD VMOVHPS VMOVLHPS VMOVLPD +syn keyword masmOpcode VMOVLPS VMOVNTDQA VMOVNTDQ VMOVNTPD VMOVNTPS +syn keyword masmOpcode VMOVSD VMOVSHDUP VMOVSLDUP VMOVSS VMOVUPD +syn keyword masmOpcode VMOVUPS VMOVDQA32 VMOVDQA64 VMOVDQU8 +syn keyword masmOpcode VMOVDQU16 VMOVDQU32 VMOVDQU64 VMULPD VMULPS +syn keyword masmOpcode VMULSD VMULSS VORPD VORPS VSQRTPD VSQRTPS +syn keyword masmOpcode VSQRTSD VSQRTSS VSUBPD VSUBPS VSUBSD VSUBSS +syn keyword masmOpcode VUCOMISD VUCOMISS VUNPCKHPD VUNPCKHPS VUNPCKLPD +syn keyword masmOpcode VUNPCKLPS VXORPD VXORPS VEXTRACTPS VINSERTPS +syn keyword masmOpcode VPEXTRB VPEXTRW VPEXTRD VPEXTRQ VPINSRB VPINSRW +syn keyword masmOpcode VPINSRD VPINSRQ VPACKSSWB VPACKSSDW VPACKUSDW +syn keyword masmOpcode VPACKUSWB VPADDB VPADDW VPADDD VPADDQ VPADDSB +syn keyword masmOpcode VPADDSW VPADDUSB VPADDUSW VPANDD VPANDQ VPANDND +syn keyword masmOpcode VPANDNQ VPAVGB VPAVGW VPCMPEQB VPCMPEQW +syn keyword masmOpcode VPCMPEQD VPCMPEQQ VPCMPGTB VPCMPGTW VPCMPGTD +syn keyword masmOpcode VPCMPGTQ VPMAXSB VPMAXSW VPMAXSD VPMAXSQ +syn keyword masmOpcode VPMAXUB VPMAXUW VPMAXUD VPMAXUQ VPMINSB VPMINSW +syn keyword masmOpcode VPMINSD VPMINSQ VPMINUB VPMINUW VPMINUD VPMINUQ +syn keyword masmOpcode VPMOVSXBW VPMOVSXBD VPMOVSXBQ VPMOVSXWD +syn keyword masmOpcode VPMOVSXWQ VPMOVSXDQ VPMOVZXBW VPMOVZXBD +syn keyword masmOpcode VPMOVZXBQ VPMOVZXWD VPMOVZXWQ VPMOVZXDQ VPMULDQ +syn keyword masmOpcode VPMULUDQ VPMULHRSW VPMULHUW VPMULHW VPMULLD +syn keyword masmOpcode VPMULLQ VPMULLW VPORD VPORQ VPSUBB VPSUBW +syn keyword masmOpcode VPSUBD VPSUBQ VPSUBSB VPSUBSW VPSUBUSB VPSUBUSW +syn keyword masmOpcode VPUNPCKHBW VPUNPCKHWD VPUNPCKHDQ VPUNPCKHQDQ +syn keyword masmOpcode VPUNPCKLBW VPUNPCKLWD VPUNPCKLDQ VPUNPCKLQDQ +syn keyword masmOpcode VPXORD VPXORQ VPSADBW VPSHUFB VPSHUFHW VPSHUFLW +syn keyword masmOpcode VPSHUFD VPSLLDQ VPSLLW VPSLLD VPSLLQ VPSRAW +syn keyword masmOpcode VPSRAD VPSRAQ VPSRLDQ VPSRLW VPSRLD VPSRLQ +syn keyword masmOpcode VPSLLVW VPSRLVW VPSHUFPD VPSHUFPS VEXTRACTF32X4 +syn keyword masmOpcode VEXTRACTF64X2 VEXTRACTF32X8 VEXTRACTF64X4 +syn keyword masmOpcode VEXTRACTI32X4 VEXTRACTI64X2 VEXTRACTI32X8 +syn keyword masmOpcode VEXTRACTI64X4 VINSERTF32x4 VINSERTF64X2 +syn keyword masmOpcode VINSERTF32X8 VINSERTF64x4 VINSERTI32X4 +syn keyword masmOpcode VINSERTI64X2 VINSERTI32X8 VINSERTI64X4 +syn keyword masmOpcode VPABSB VPABSW VPABSD VPABSQ VPALIGNR +syn keyword masmOpcode VPMADDUBSW VPMADDWD +syn keyword masmOpcode VFMADD132PD VFMADD213PD VFMADD231PD +syn keyword masmOpcode VFMADD132PS VFMADD213PS VFMADD231PS +syn keyword masmOpcode VFMADD132SD VFMADD213SD VFMADD231SD +syn keyword masmOpcode VFMADD132SS VFMADD213SS VFMADD231SS +syn keyword masmOpcode VFMADDSUB132PD VFMADDSUB213PD VFMADDSUB231PD +syn keyword masmOpcode VFMADDSUB132PS VFMADDSUB213PS VFMADDSUB231PS +syn keyword masmOpcode VFMSUBADD132PD VFMSUBADD213PD VFMSUBADD231PD +syn keyword masmOpcode VFMSUBADD132PS VFMSUBADD213PS VFMSUBADD231PS +syn keyword masmOpcode VFMSUB132PD VFMSUB213PD VFMSUB231PD +syn keyword masmOpcode VFMSUB132PS VFMSUB213PS VFMSUB231PS +syn keyword masmOpcode VFMSUB132SD VFMSUB213SD VFMSUB231SD +syn keyword masmOpcode VFMSUB132SS VFMSUB213SS VFMSUB231SS +syn keyword masmOpcode VFNMADD132PD VFNMADD213PD VFNMADD231PD +syn keyword masmOpcode VFNMADD132PS VFNMADD213PS VFNMADD231PS +syn keyword masmOpcode VFNMADD132SD VFNMADD213SD VFNMADD231SD +syn keyword masmOpcode VFNMADD132SS VFNMADD213SS VFNMADD231SS +syn keyword masmOpcode VFNMSUB132PD VFNMSUB213PD VFNMSUB231PD +syn keyword masmOpcode VFNMSUB132PS VFNMSUB213PS VFNMSUB231PS +syn keyword masmOpcode VFNMSUB132SD VFNMSUB213SD VFNMSUB231SD +syn keyword masmOpcode VFNMSUB132SS VFNMSUB213SS VFNMSUB231SS +syn keyword masmOpcode VPSRAVW VPSRAVQ + " Other opcodes in Pentium and later processors syn keyword masmOpcode CMPXCHG8B CPUID UD2 syn keyword masmOpcode RSM RDMSR WRMSR RDPMC RDTSC SYSENTER SYSEXIT syn match masmOpcode "CMOV\(P[EO]\|\(N\?\([ABGL]E\?\|[CEOPSZ]\)\)\)\>" +" Not really used by MASM, but useful for viewing GCC-generated assembly code +" in Intel syntax +syn match masmHexadecimal "[-+]\?0[Xx]\x*" +syn keyword masmOpcode MOVABS " The default highlighting hi def link masmLabel PreProc diff --git a/runtime/syntax/mojo.vim b/runtime/syntax/mojo.vim new file mode 100644 index 0000000000..b7dae24a15 --- /dev/null +++ b/runtime/syntax/mojo.vim @@ -0,0 +1,316 @@ +" Vim syntax file +" Language: Mojo +" Maintainer: Mahmoud Abduljawad <me@mahmoudajawad.com> +" Last Change: 2023 Sep 09 +" Credits: Mahmoud Abduljawad <me@mahmoudajawad.com> +" Neil Schemenauer <nas@python.ca> +" Dmitry Vasiliev +" +" This is based on Vim Python highlighting +" +" - introduced highlighting of doctests +" - updated keywords, built-ins, and exceptions +" - corrected regular expressions for +" +" * functions +" * decorators +" * strings +" * escapes +" * numbers +" * space error +" +" - corrected synchronization +" - more highlighting is ON by default, except +" - space error highlighting is OFF by default +" +" Optional highlighting can be controlled using these variables. +" +" let mojo_no_builtin_highlight = 1 +" let mojo_no_doctest_code_highlight = 1 +" let mojo_no_doctest_highlight = 1 +" let mojo_no_exception_highlight = 1 +" let mojo_no_number_highlight = 1 +" let mojo_space_error_highlight = 1 +" +" All the options above can be switched on together. +" +" let mojo_highlight_all = 1 +" +" The use of Python 2 compatible syntax highlighting can be enforced. +" The straddling code (Python 2 and 3 compatible), up to Python 3.5, +" will be also supported. +" +" let mojo_use_python2_syntax = 1 +" +" This option will exclude all modern Python 3.6 or higher features. +" + +" quit when a syntax file was already loaded. +if exists("b:current_syntax") + finish +endif + +" We need nocompatible mode in order to continue lines with backslashes. +" Original setting will be restored. +let s:cpo_save = &cpo +set cpo&vim + +if exists("mojo_no_doctest_highlight") + let mojo_no_doctest_code_highlight = 1 +endif + +if exists("mojo_highlight_all") + if exists("mojo_no_builtin_highlight") + unlet mojo_no_builtin_highlight + endif + if exists("mojo_no_doctest_code_highlight") + unlet mojo_no_doctest_code_highlight + endif + if exists("mojo_no_doctest_highlight") + unlet mojo_no_doctest_highlight + endif + if exists("mojo_no_exception_highlight") + unlet mojo_no_exception_highlight + endif + if exists("mojo_no_number_highlight") + unlet mojo_no_number_highlight + endif + let mojo_space_error_highlight = 1 +endif + +" These keywords are based on Python syntax highlight, and adds to it struct, +" fn, alias, var, let +" +syn keyword mojoStatement False None True +syn keyword mojoStatement as assert break continue del global +syn keyword mojoStatement lambda nonlocal pass return with yield +syn keyword mojoStatement class def nextgroup=mojoFunction skipwhite +syn keyword mojoStatement struct fn nextgroup=mojoFunction skipwhite +syn keyword mojoStatement alias var let +syn keyword mojoConditional elif else if +syn keyword mojoRepeat for while +syn keyword mojoOperator and in is not or +syn keyword mojoException except finally raise try +syn keyword mojoInclude from import +syn keyword mojoAsync async await + +" Soft keywords +" These keywords do not mean anything unless used in the right context. +" See https://docs.python.org/3/reference/lexical_analysis.html#soft-keywords +" for more on this. +syn match mojoConditional "^\s*\zscase\%(\s\+.*:.*$\)\@=" +syn match mojoConditional "^\s*\zsmatch\%(\s\+.*:\s*\%(#.*\)\=$\)\@=" + +" Decorators +" A dot must be allowed because of @MyClass.myfunc decorators. +syn match mojoDecorator "@" display contained +syn match mojoDecoratorName "@\s*\h\%(\w\|\.\)*" display contains=pythonDecorator + +" Python 3.5 introduced the use of the same symbol for matrix multiplication: +" https://www.python.org/dev/peps/pep-0465/. We now have to exclude the +" symbol from highlighting when used in that context. +" Single line multiplication. +syn match mojoMatrixMultiply + \ "\%(\w\|[])]\)\s*@" + \ contains=ALLBUT,mojoDecoratorName,mojoDecorator,mojoFunction,mojoDoctestValue + \ transparent +" Multiplication continued on the next line after backslash. +syn match mojoMatrixMultiply + \ "[^\\]\\\s*\n\%(\s*\.\.\.\s\)\=\s\+@" + \ contains=ALLBUT,mojoDecoratorName,mojoDecorator,mojoFunction,mojoDoctestValue + \ transparent +" Multiplication in a parenthesized expression over multiple lines with @ at +" the start of each continued line; very similar to decorators and complex. +syn match mojoMatrixMultiply + \ "^\s*\%(\%(>>>\|\.\.\.\)\s\+\)\=\zs\%(\h\|\%(\h\|[[(]\).\{-}\%(\w\|[])]\)\)\s*\n\%(\s*\.\.\.\s\)\=\s\+@\%(.\{-}\n\%(\s*\.\.\.\s\)\=\s\+@\)*" + \ contains=ALLBUT,mojoDecoratorName,mojoDecorator,mojoFunction,mojoDoctestValue + \ transparent + +syn match mojoFunction "\h\w*" display contained + +syn match mojoComment "#.*$" contains=mojoTodo,@Spell +syn keyword mojoTodo FIXME NOTE NOTES TODO XXX contained + +" Triple-quoted strings can contain doctests. +syn region mojoString matchgroup=mojoQuotes + \ start=+[uU]\=\z(['"]\)+ end="\z1" skip="\\\\\|\\\z1" + \ contains=mojoEscape,@Spell +syn region mojoString matchgroup=mojoTripleQuotes + \ start=+[uU]\=\z('''\|"""\)+ end="\z1" keepend + \ contains=mojoEscape,mojoSpaceError,mojoDoctest,@Spell +syn region mojoRawString matchgroup=mojoQuotes + \ start=+[uU]\=[rR]\z(['"]\)+ end="\z1" skip="\\\\\|\\\z1" + \ contains=@Spell +syn region mojoRawString matchgroup=pythonTripleQuotes + \ start=+[uU]\=[rR]\z('''\|"""\)+ end="\z1" keepend + \ contains=pythonSpaceError,mojoDoctest,@Spell + +syn match mojoEscape +\\[abfnrtv'"\\]+ contained +syn match mojoEscape "\\\o\{1,3}" contained +syn match mojoEscape "\\x\x\{2}" contained +syn match mojoEscape "\%(\\u\x\{4}\|\\U\x\{8}\)" contained +" Python allows case-insensitive Unicode IDs: http://www.unicode.org/charts/ +syn match mojoEscape "\\N{\a\+\%(\s\a\+\)*}" contained +syn match mojoEscape "\\$" + +" It is very important to understand all details before changing the +" regular expressions below or their order. +" The word boundaries are *not* the floating-point number boundaries +" because of a possible leading or trailing decimal point. +" The expressions below ensure that all valid number literals are +" highlighted, and invalid number literals are not. For example, +" +" - a decimal point in '4.' at the end of a line is highlighted, +" - a second dot in 1.0.0 is not highlighted, +" - 08 is not highlighted, +" - 08e0 or 08j are highlighted, +" +" and so on, as specified in the 'Python Language Reference'. +" https://docs.python.org/reference/lexical_analysis.html#numeric-literals +if !exists("mojo_no_number_highlight") + " numbers (including complex) + syn match mojoNumber "\<0[oO]\%(_\=\o\)\+\>" + syn match mojoNumber "\<0[xX]\%(_\=\x\)\+\>" + syn match mojoNumber "\<0[bB]\%(_\=[01]\)\+\>" + syn match mojoNumber "\<\%([1-9]\%(_\=\d\)*\|0\+\%(_\=0\)*\)\>" + syn match mojoNumber "\<\d\%(_\=\d\)*[jJ]\>" + syn match mojoNumber "\<\d\%(_\=\d\)*[eE][+-]\=\d\%(_\=\d\)*[jJ]\=\>" + syn match mojoNumber + \ "\<\d\%(_\=\d\)*\.\%([eE][+-]\=\d\%(_\=\d\)*\)\=[jJ]\=\%(\W\|$\)\@=" + syn match mojoNumber + \ "\%(^\|\W\)\zs\%(\d\%(_\=\d\)*\)\=\.\d\%(_\=\d\)*\%([eE][+-]\=\d\%(_\=\d\)*\)\=[jJ]\=\>" +endif + +" The built-ins are added in the same order of appearance in Mojo stdlib docs +" https://docs.modular.com/mojo/lib.html +" +if !exists("mojo_no_builtin_highlight") + " Built-in functions + syn keyword mojoBuiltin slice constrained debug_assert put_new_line print + syn keyword mojoBuiltin print_no_newline len range rebind element_type + syn keyword mojoBuiltin ord chr atol isdigit index address string + " Built-in types + syn keyword mojoType Byte ListLiteral CoroutineContext Coroutine DType + syn keyword mojoType dtype type invalid bool int8 si8 unit8 ui8 int16 + syn keyword mojoType si16 unit16 ui16 int32 si32 uint32 ui32 int64 + syn keyword mojoType si64 uint64 ui64 bfloat16 bf16 float16 f16 float32 + syn keyword mojoType f32 float64 f64 Error FloatLiteral Int Attr SIMD + syn keyword mojoType Int8 UInt8 Int16 UInt16 Int32 UInt32 Int64 UInt64 + syn keyword mojoType Float16 Float32 Float64 element_type _65x13_type + syn keyword mojoType String StringLiteral StringRef Tuple AnyType + syn keyword mojoType NoneType None Lifetime + " avoid highlighting attributes as builtins + syn match mojoAttribute /\.\h\w*/hs=s+1 + \ contains=ALLBUT,mojoBuiltin,mojoFunction,mojoAsync + \ transparent +endif + +" From the 'Python Library Reference' class hierarchy at the bottom. +" http://docs.python.org/library/exceptions.html +if !exists("mojo_no_exception_highlight") + " builtin base exceptions (used mostly as base classes for other exceptions) + syn keyword mojoExceptions BaseException Exception + syn keyword mojoExceptions ArithmeticError BufferError LookupError + " builtin exceptions (actually raised) + syn keyword mojoExceptions AssertionError AttributeError EOFError + syn keyword mojoExceptions FloatingPointError GeneratorExit ImportError + syn keyword mojoExceptions IndentationError IndexError KeyError + syn keyword mojoExceptions KeyboardInterrupt MemoryError + syn keyword mojoExceptions ModuleNotFoundError NameError + syn keyword mojoExceptions NotImplementedError OSError OverflowError + syn keyword mojoExceptions RecursionError ReferenceError RuntimeError + syn keyword mojoExceptions StopAsyncIteration StopIteration SyntaxError + syn keyword mojoExceptions SystemError SystemExit TabError TypeError + syn keyword mojoExceptions UnboundLocalError UnicodeDecodeError + syn keyword mojoExceptions UnicodeEncodeError UnicodeError + syn keyword mojoExceptions UnicodeTranslateError ValueError + syn keyword mojoExceptions ZeroDivisionError + " builtin exception aliases for OSError + syn keyword mojoExceptions EnvironmentError IOError WindowsError + " builtin OS exceptions in Python 3 + syn keyword mojoExceptions BlockingIOError BrokenPipeError + syn keyword mojoExceptions ChildProcessError ConnectionAbortedError + syn keyword mojoExceptions ConnectionError ConnectionRefusedError + syn keyword mojoExceptions ConnectionResetError FileExistsError + syn keyword mojoExceptions FileNotFoundError InterruptedError + syn keyword mojoExceptions IsADirectoryError NotADirectoryError + syn keyword mojoExceptions PermissionError ProcessLookupError TimeoutError + " builtin warnings + syn keyword mojoExceptions BytesWarning DeprecationWarning FutureWarning + syn keyword mojoExceptions ImportWarning PendingDeprecationWarning + syn keyword mojoExceptions ResourceWarning RuntimeWarning + syn keyword mojoExceptions SyntaxWarning UnicodeWarning + syn keyword mojoExceptions UserWarning Warning +endif + +if exists("mojo_space_error_highlight") + " trailing whitespace + syn match mojoSpaceError display excludenl "\s\+$" + " mixed tabs and spaces + syn match mojoSpaceError display " \+\t" + syn match mojoSpaceError display "\t\+ " +endif + +" Do not spell doctests inside strings. +" Notice that the end of a string, either ''', or """, will end the contained +" doctest too. Thus, we do *not* need to have it as an end pattern. +if !exists("mojo_no_doctest_highlight") + if !exists("mojo_no_doctest_code_highlight") + syn region mojoDoctest + \ start="^\s*>>>\s" end="^\s*$" + \ contained contains=ALLBUT,mojoDoctest,mojoFunction,@Spell + syn region mojoDoctestValue + \ start=+^\s*\%(>>>\s\|\.\.\.\s\|"""\|'''\)\@!\S\++ end="$" + \ contained + else + syn region mojoDoctest + \ start="^\s*>>>" end="^\s*$" + \ contained contains=@NoSpell + endif +endif + +" Sync at the beginning of class, function, or method definition. +syn sync match mojoSync grouphere NONE "^\%(def\|class\)\s\+\h\w*\s*[(:]" + +" The default highlight links. Can be overridden later. +hi def link mojoStatement Statement +hi def link mojoConditional Conditional +hi def link mojoRepeat Repeat +hi def link mojoOperator Operator +hi def link mojoException Exception +hi def link mojoInclude Include +hi def link mojoAsync Statement +hi def link mojoDecorator Define +hi def link mojoDecoratorName Function +hi def link mojoFunction Function +hi def link mojoComment Comment +hi def link mojoTodo Todo +hi def link mojoString String +hi def link mojoRawString String +hi def link mojoQuotes String +hi def link mojoTripleQuotes mojoQuotes +hi def link mojoEscape Special +if !exists("mojo_no_number_highlight") + hi def link mojoNumber Number +endif +if !exists("mojo_no_builtin_highlight") + hi def link mojoBuiltin Function + hi def link mojoType Type +endif +if !exists("mojo_no_exception_highlight") + hi def link mojoExceptions Structure +endif +if exists("mojo_space_error_highlight") + hi def link mojoSpaceError Error +endif +if !exists("mojo_no_doctest_highlight") + hi def link mojoDoctest Special + hi def link mojoDoctestValue Define +endif + +let b:current_syntax = "mojo" + +let &cpo = s:cpo_save +unlet s:cpo_save + +" vim:set sw=2 sts=2 ts=8 noet: diff --git a/runtime/syntax/nasm.vim b/runtime/syntax/nasm.vim index 0eb82fad10..e1dfc1db12 100644 --- a/runtime/syntax/nasm.vim +++ b/runtime/syntax/nasm.vim @@ -8,7 +8,7 @@ " Peter Stanhope <dev.rptr@gmail.com> (Add missing 64-bit mode registers) " Frédéric Hamel <rederic.hamel123@gmail.com> (F16c support, partial AVX " support, other) -" Last Change: 2022 May 3 +" Last Change: 2023 Sep 7 " NASM Home: http://www.nasm.us/ @@ -250,12 +250,12 @@ syn match nasmSegRegister "\<[C-GS]S\>" syn match nasmSpcRegister "\<E\=IP\>" syn match nasmFpuRegister "\<ST\o\>" syn match nasmMmxRegister "\<MM\o\>" -syn match nasmSseRegister "\<XMM\o\>" +syn match nasmAvxRegister "\<[XYZ]MM\d\{1,2}\>" syn match nasmCtrlRegister "\<CR\o\>" syn match nasmDebugRegister "\<DR\o\>" syn match nasmTestRegister "\<TR\o\>" syn match nasmRegisterError "\<\(CR[15-9]\|DR[4-58-9]\|TR[0-28-9]\)\>" -syn match nasmRegisterError "\<X\=MM[8-9]\>" +syn match nasmRegisterError "\<[XYZ]MM\(3[2-9]\|[04-9]\d\)\>" syn match nasmRegisterError "\<ST\((\d)\|[8-9]\>\)" syn match nasmRegisterError "\<E\([A-D][HL]\|[C-GS]S\)\>" " Memory reference operand (address): diff --git a/runtime/syntax/rust.vim b/runtime/syntax/rust.vim index 57343301e0..55d3f14dc2 100644 --- a/runtime/syntax/rust.vim +++ b/runtime/syntax/rust.vim @@ -3,44 +3,57 @@ " Maintainer: Patrick Walton <pcwalton@mozilla.com> " Maintainer: Ben Blum <bblum@cs.cmu.edu> " Maintainer: Chris Morgan <me@chrismorgan.info> -" Last Change: Feb 24, 2016 +" Last Change: 2023-09-11 " For bugs, patches and license go to https://github.com/rust-lang/rust.vim if version < 600 - syntax clear + syntax clear elseif exists("b:current_syntax") - finish + finish endif " Syntax definitions {{{1 " Basic keywords {{{2 syn keyword rustConditional match if else -syn keyword rustRepeat for loop while +syn keyword rustRepeat loop while +" `:syn match` must be used to prioritize highlighting `for` keyword. +syn match rustRepeat /\<for\>/ +" Highlight `for` keyword in `impl ... for ... {}` statement. This line must +" be put after previous `syn match` line to overwrite it. +syn match rustKeyword /\%(\<impl\>.\+\)\@<=\<for\>/ +syn keyword rustRepeat in syn keyword rustTypedef type nextgroup=rustIdentifier skipwhite skipempty syn keyword rustStructure struct enum nextgroup=rustIdentifier skipwhite skipempty syn keyword rustUnion union nextgroup=rustIdentifier skipwhite skipempty contained syn match rustUnionContextual /\<union\_s\+\%([^[:cntrl:][:space:][:punct:][:digit:]]\|_\)\%([^[:cntrl:][:punct:][:space:]]\|_\)*/ transparent contains=rustUnion syn keyword rustOperator as +syn keyword rustExistential existential nextgroup=rustTypedef skipwhite skipempty contained +syn match rustExistentialContextual /\<existential\_s\+type/ transparent contains=rustExistential,rustTypedef syn match rustAssert "\<assert\(\w\)*!" contained syn match rustPanic "\<panic\(\w\)*!" contained +syn match rustAsync "\<async\%(\s\|\n\)\@=" syn keyword rustKeyword break -syn keyword rustKeyword box nextgroup=rustBoxPlacement skipwhite skipempty +syn keyword rustKeyword box syn keyword rustKeyword continue +syn keyword rustKeyword crate syn keyword rustKeyword extern nextgroup=rustExternCrate,rustObsoleteExternMod skipwhite skipempty syn keyword rustKeyword fn nextgroup=rustFuncName skipwhite skipempty -syn keyword rustKeyword in impl let +syn keyword rustKeyword impl let +syn keyword rustKeyword macro syn keyword rustKeyword pub nextgroup=rustPubScope skipwhite skipempty syn keyword rustKeyword return +syn keyword rustKeyword yield syn keyword rustSuper super -syn keyword rustKeyword unsafe where +syn keyword rustKeyword where +syn keyword rustUnsafeKeyword unsafe syn keyword rustKeyword use nextgroup=rustModPath skipwhite skipempty " FIXME: Scoped impl's name is also fallen in this category syn keyword rustKeyword mod trait nextgroup=rustIdentifier skipwhite skipempty syn keyword rustStorage move mut ref static const -syn match rustDefault /\<default\ze\_s\+\(impl\|fn\|type\|const\)\>/ - -syn keyword rustInvalidBareKeyword crate +syn match rustDefault /\<default\ze\_s\+\(impl\|fn\|type\|const\)\>/ +syn keyword rustAwait await +syn match rustKeyword /\<try\>!\@!/ display syn keyword rustPubScopeCrate crate contained syn match rustPubScopeDelim /[()]/ contained @@ -52,22 +65,14 @@ syn match rustExternCrateString /".*"\_s*as/ contained nextgroup=rustIdentifie syn keyword rustObsoleteExternMod mod contained nextgroup=rustIdentifier skipwhite skipempty syn match rustIdentifier contains=rustIdentifierPrime "\%([^[:cntrl:][:space:][:punct:][:digit:]]\|_\)\%([^[:cntrl:][:punct:][:space:]]\|_\)*" display contained -syn match rustFuncName "\%([^[:cntrl:][:space:][:punct:][:digit:]]\|_\)\%([^[:cntrl:][:punct:][:space:]]\|_\)*" display contained - -syn region rustBoxPlacement matchgroup=rustBoxPlacementParens start="(" end=")" contains=TOP contained -" Ideally we'd have syntax rules set up to match arbitrary expressions. Since -" we don't, we'll just define temporary contained rules to handle balancing -" delimiters. -syn region rustBoxPlacementBalance start="(" end=")" containedin=rustBoxPlacement transparent -syn region rustBoxPlacementBalance start="\[" end="\]" containedin=rustBoxPlacement transparent -" {} are handled by rustFoldBraces - -syn region rustMacroRepeat matchgroup=rustMacroRepeatDelimiters start="$(" end=")" contains=TOP nextgroup=rustMacroRepeatCount -syn match rustMacroRepeatCount ".\?[*+]" contained +syn match rustFuncName "\%(r#\)\=\%([^[:cntrl:][:space:][:punct:][:digit:]]\|_\)\%([^[:cntrl:][:punct:][:space:]]\|_\)*" display contained + +syn region rustMacroRepeat matchgroup=rustMacroRepeatDelimiters start="$(" end="),\=[*+]" contains=TOP syn match rustMacroVariable "$\w\+" +syn match rustRawIdent "\<r#\h\w*" contains=NONE " Reserved (but not yet used) keywords {{{2 -syn keyword rustReservedKeyword alignof become do offsetof priv pure sizeof typeof unsized yield abstract virtual final override macro +syn keyword rustReservedKeyword become do priv typeof unsized abstract virtual final override " Built-in types {{{2 syn keyword rustType isize usize char bool u8 u16 u32 u64 u128 f32 @@ -138,18 +143,37 @@ syn match rustMacro '#\w\(\w\)*' contains=rustAssert,rustPanic syn match rustEscapeError display contained /\\./ syn match rustEscape display contained /\\\([nrt0\\'"]\|x\x\{2}\)/ -syn match rustEscapeUnicode display contained /\\u{\x\{1,6}}/ +syn match rustEscapeUnicode display contained /\\u{\%(\x_*\)\{1,6}}/ syn match rustStringContinuation display contained /\\\n\s*/ -syn region rustString start=+b"+ skip=+\\\\\|\\"+ end=+"+ contains=rustEscape,rustEscapeError,rustStringContinuation -syn region rustString start=+"+ skip=+\\\\\|\\"+ end=+"+ contains=rustEscape,rustEscapeUnicode,rustEscapeError,rustStringContinuation,@Spell -syn region rustString start='b\?r\z(#*\)"' end='"\z1' contains=@Spell - -syn region rustAttribute start="#!\?\[" end="\]" contains=rustString,rustDerive,rustCommentLine,rustCommentBlock,rustCommentLineDocError,rustCommentBlockDocError +syn region rustString matchgroup=rustStringDelimiter start=+b"+ skip=+\\\\\|\\"+ end=+"+ contains=rustEscape,rustEscapeError,rustStringContinuation +syn region rustString matchgroup=rustStringDelimiter start=+"+ skip=+\\\\\|\\"+ end=+"+ contains=rustEscape,rustEscapeUnicode,rustEscapeError,rustStringContinuation,@Spell +syn region rustString matchgroup=rustStringDelimiter start='b\?r\z(#*\)"' end='"\z1' contains=@Spell + +" Match attributes with either arbitrary syntax or special highlighting for +" derives. We still highlight strings and comments inside of the attribute. +syn region rustAttribute start="#!\?\[" end="\]" contains=@rustAttributeContents,rustAttributeParenthesizedParens,rustAttributeParenthesizedCurly,rustAttributeParenthesizedBrackets,rustDerive +syn region rustAttributeParenthesizedParens matchgroup=rustAttribute start="\w\%(\w\)*("rs=e end=")"re=s transparent contained contains=rustAttributeBalancedParens,@rustAttributeContents +syn region rustAttributeParenthesizedCurly matchgroup=rustAttribute start="\w\%(\w\)*{"rs=e end="}"re=s transparent contained contains=rustAttributeBalancedCurly,@rustAttributeContents +syn region rustAttributeParenthesizedBrackets matchgroup=rustAttribute start="\w\%(\w\)*\["rs=e end="\]"re=s transparent contained contains=rustAttributeBalancedBrackets,@rustAttributeContents +syn region rustAttributeBalancedParens matchgroup=rustAttribute start="("rs=e end=")"re=s transparent contained contains=rustAttributeBalancedParens,@rustAttributeContents +syn region rustAttributeBalancedCurly matchgroup=rustAttribute start="{"rs=e end="}"re=s transparent contained contains=rustAttributeBalancedCurly,@rustAttributeContents +syn region rustAttributeBalancedBrackets matchgroup=rustAttribute start="\["rs=e end="\]"re=s transparent contained contains=rustAttributeBalancedBrackets,@rustAttributeContents +syn cluster rustAttributeContents contains=rustString,rustCommentLine,rustCommentBlock,rustCommentLineDocError,rustCommentBlockDocError syn region rustDerive start="derive(" end=")" contained contains=rustDeriveTrait " This list comes from src/libsyntax/ext/deriving/mod.rs " Some are deprecated (Encodable, Decodable) or to be removed after a new snapshot (Show). syn keyword rustDeriveTrait contained Clone Hash RustcEncodable RustcDecodable Encodable Decodable PartialEq Eq PartialOrd Ord Rand Show Debug Default FromPrimitive Send Sync Copy +" dyn keyword: It's only a keyword when used inside a type expression, so +" we make effort here to highlight it only when Rust identifiers follow it +" (not minding the case of pre-2018 Rust where a path starting with :: can +" follow). +" +" This is so that uses of dyn variable names such as in 'let &dyn = &2' +" and 'let dyn = 2' will not get highlighted as a keyword. +syn match rustKeyword "\<dyn\ze\_s\+\%([^[:cntrl:][:space:][:punct:][:digit:]]\|_\)" contains=rustDynKeyword +syn keyword rustDynKeyword dyn contained + " Number literals syn match rustDecNumber display "\<[0-9][0-9_]*\%([iu]\%(size\|8\|16\|32\|64\|128\)\)\=" syn match rustHexNumber display "\<0x[a-fA-F0-9_]\+\%([iu]\%(size\|8\|16\|32\|64\|128\)\)\=" @@ -168,29 +192,31 @@ syn match rustFloat display "\<[0-9][0-9_]*\%(\.[0-9][0-9_]*\)\=\%([eE syn match rustFloat display "\<[0-9][0-9_]*\%(\.[0-9][0-9_]*\)\=\%([eE][+-]\=[0-9_]\+\)\=\(f32\|f64\)" " For the benefit of delimitMate -syn region rustLifetimeCandidate display start=/&'\%(\([^'\\]\|\\\(['nrt0\\\"]\|x\x\{2}\|u{\x\{1,6}}\)\)'\)\@!/ end=/[[:cntrl:][:space:][:punct:]]\@=\|$/ contains=rustSigil,rustLifetime -syn region rustGenericRegion display start=/<\%('\|[^[cntrl:][:space:][:punct:]]\)\@=')\S\@=/ end=/>/ contains=rustGenericLifetimeCandidate +syn region rustLifetimeCandidate display start=/&'\%(\([^'\\]\|\\\(['nrt0\\\"]\|x\x\{2}\|u{\%(\x_*\)\{1,6}}\)\)'\)\@!/ end=/[[:cntrl:][:space:][:punct:]]\@=\|$/ contains=rustSigil,rustLifetime +syn region rustGenericRegion display start=/<\%('\|[^[:cntrl:][:space:][:punct:]]\)\@=')\S\@=/ end=/>/ contains=rustGenericLifetimeCandidate syn region rustGenericLifetimeCandidate display start=/\%(<\|,\s*\)\@<='/ end=/[[:cntrl:][:space:][:punct:]]\@=\|$/ contains=rustSigil,rustLifetime "rustLifetime must appear before rustCharacter, or chars will get the lifetime highlighting syn match rustLifetime display "\'\%([^[:cntrl:][:space:][:punct:][:digit:]]\|_\)\%([^[:cntrl:][:punct:][:space:]]\|_\)*" syn match rustLabel display "\'\%([^[:cntrl:][:space:][:punct:][:digit:]]\|_\)\%([^[:cntrl:][:punct:][:space:]]\|_\)*:" +syn match rustLabel display "\%(\<\%(break\|continue\)\s*\)\@<=\'\%([^[:cntrl:][:space:][:punct:][:digit:]]\|_\)\%([^[:cntrl:][:punct:][:space:]]\|_\)*" syn match rustCharacterInvalid display contained /b\?'\zs[\n\r\t']\ze'/ " The groups negated here add up to 0-255 but nothing else (they do not seem to go beyond ASCII). syn match rustCharacterInvalidUnicode display contained /b'\zs[^[:cntrl:][:graph:][:alnum:][:space:]]\ze'/ syn match rustCharacter /b'\([^\\]\|\\\(.\|x\x\{2}\)\)'/ contains=rustEscape,rustEscapeError,rustCharacterInvalid,rustCharacterInvalidUnicode -syn match rustCharacter /'\([^\\]\|\\\(.\|x\x\{2}\|u{\x\{1,6}}\)\)'/ contains=rustEscape,rustEscapeUnicode,rustEscapeError,rustCharacterInvalid +syn match rustCharacter /'\([^\\]\|\\\(.\|x\x\{2}\|u{\%(\x_*\)\{1,6}}\)\)'/ contains=rustEscape,rustEscapeUnicode,rustEscapeError,rustCharacterInvalid syn match rustShebang /\%^#![^[].*/ syn region rustCommentLine start="//" end="$" contains=rustTodo,@Spell syn region rustCommentLineDoc start="//\%(//\@!\|!\)" end="$" contains=rustTodo,@Spell syn region rustCommentLineDocError start="//\%(//\@!\|!\)" end="$" contains=rustTodo,@Spell contained syn region rustCommentBlock matchgroup=rustCommentBlock start="/\*\%(!\|\*[*/]\@!\)\@!" end="\*/" contains=rustTodo,rustCommentBlockNest,@Spell -syn region rustCommentBlockDoc matchgroup=rustCommentBlockDoc start="/\*\%(!\|\*[*/]\@!\)" end="\*/" contains=rustTodo,rustCommentBlockDocNest,@Spell +syn region rustCommentBlockDoc matchgroup=rustCommentBlockDoc start="/\*\%(!\|\*[*/]\@!\)" end="\*/" contains=rustTodo,rustCommentBlockDocNest,rustCommentBlockDocRustCode,@Spell syn region rustCommentBlockDocError matchgroup=rustCommentBlockDocError start="/\*\%(!\|\*[*/]\@!\)" end="\*/" contains=rustTodo,rustCommentBlockDocNestError,@Spell contained syn region rustCommentBlockNest matchgroup=rustCommentBlock start="/\*" end="\*/" contains=rustTodo,rustCommentBlockNest,@Spell contained transparent syn region rustCommentBlockDocNest matchgroup=rustCommentBlockDoc start="/\*" end="\*/" contains=rustTodo,rustCommentBlockDocNest,@Spell contained transparent syn region rustCommentBlockDocNestError matchgroup=rustCommentBlockDocError start="/\*" end="\*/" contains=rustTodo,rustCommentBlockDocNestError,@Spell contained transparent + " FIXME: this is a really ugly and not fully correct implementation. Most " importantly, a case like ``/* */*`` should have the final ``*`` not being in " a comment, but in practice at present it leaves comments open two levels @@ -203,13 +229,67 @@ syn region rustCommentBlockDocNestError matchgroup=rustCommentBlockDocError star " then you must deal with cases like ``/*/**/*/``. And don't try making it " worse with ``\%(/\@<!\*\)\@<!``, either... -syn keyword rustTodo contained TODO FIXME XXX NB NOTE +syn keyword rustTodo contained TODO FIXME XXX NB NOTE SAFETY + +" asm! macro {{{2 +syn region rustAsmMacro matchgroup=rustMacro start="\<asm!\s*(" end=")" contains=rustAsmDirSpec,rustAsmSym,rustAsmConst,rustAsmOptionsGroup,rustComment.*,rustString.* + +" Clobbered registers +syn keyword rustAsmDirSpec in out lateout inout inlateout contained nextgroup=rustAsmReg skipwhite skipempty +syn region rustAsmReg start="(" end=")" contained contains=rustString + +" Symbol operands +syn keyword rustAsmSym sym contained nextgroup=rustAsmSymPath skipwhite skipempty +syn region rustAsmSymPath start="\S" end=",\|)"me=s-1 contained contains=rustComment.*,rustIdentifier + +" Const +syn region rustAsmConstBalancedParens start="("ms=s+1 end=")" contained contains=@rustAsmConstExpr +syn cluster rustAsmConstExpr contains=rustComment.*,rust.*Number,rustString,rustAsmConstBalancedParens +syn region rustAsmConst start="const" end=",\|)"me=s-1 contained contains=rustStorage,@rustAsmConstExpr + +" Options +syn region rustAsmOptionsGroup start="options\s*(" end=")" contained contains=rustAsmOptions,rustAsmOptionsKey +syn keyword rustAsmOptionsKey options contained +syn keyword rustAsmOptions pure nomem readonly preserves_flags noreturn nostack att_syntax contained " Folding rules {{{2 " Trivial folding rules to begin with. " FIXME: use the AST to make really good folding syn region rustFoldBraces start="{" end="}" transparent fold +if !exists("b:current_syntax_embed") + let b:current_syntax_embed = 1 + syntax include @RustCodeInComment <sfile>:p:h/rust.vim + unlet b:current_syntax_embed + + " Currently regions marked as ```<some-other-syntax> will not get + " highlighted at all. In the future, we can do as vim-markdown does and + " highlight with the other syntax. But for now, let's make sure we find + " the closing block marker, because the rules below won't catch it. + syn region rustCommentLinesDocNonRustCode matchgroup=rustCommentDocCodeFence start='^\z(\s*//[!/]\s*```\).\+$' end='^\z1$' keepend contains=rustCommentLineDoc + + " We borrow the rules from rust’s src/librustdoc/html/markdown.rs, so that + " we only highlight as Rust what it would perceive as Rust (almost; it’s + " possible to trick it if you try hard, and indented code blocks aren’t + " supported because Markdown is a menace to parse and only mad dogs and + " Englishmen would try to handle that case correctly in this syntax file). + syn region rustCommentLinesDocRustCode matchgroup=rustCommentDocCodeFence start='^\z(\s*//[!/]\s*```\)[^A-Za-z0-9_-]*\%(\%(should_panic\|no_run\|ignore\|allow_fail\|rust\|test_harness\|compile_fail\|E\d\{4}\|edition201[58]\)\%([^A-Za-z0-9_-]\+\|$\)\)*$' end='^\z1$' keepend contains=@RustCodeInComment,rustCommentLineDocLeader + syn region rustCommentBlockDocRustCode matchgroup=rustCommentDocCodeFence start='^\z(\%(\s*\*\)\?\s*```\)[^A-Za-z0-9_-]*\%(\%(should_panic\|no_run\|ignore\|allow_fail\|rust\|test_harness\|compile_fail\|E\d\{4}\|edition201[58]\)\%([^A-Za-z0-9_-]\+\|$\)\)*$' end='^\z1$' keepend contains=@RustCodeInComment,rustCommentBlockDocStar + " Strictly, this may or may not be correct; this code, for example, would + " mishighlight: + " + " /** + " ```rust + " println!("{}", 1 + " * 1); + " ``` + " */ + " + " … but I don’t care. Balance of probability, and all that. + syn match rustCommentBlockDocStar /^\s*\*\s\?/ contained + syn match rustCommentLineDocLeader "^\s*//\%(//\@!\|!\)" contained +endif + " Default highlighting {{{1 hi def link rustDecNumber rustNumber hi def link rustHexNumber rustNumber @@ -219,7 +299,6 @@ hi def link rustIdentifierPrime rustIdentifier hi def link rustTrait rustType hi def link rustDeriveTrait rustTrait -hi def link rustMacroRepeatCount rustMacroRepeatDelimiters hi def link rustMacroRepeatDelimiters Macro hi def link rustMacroVariable Define hi def link rustSigil StorageClass @@ -228,6 +307,7 @@ hi def link rustEscapeUnicode rustEscape hi def link rustEscapeError Error hi def link rustStringContinuation Special hi def link rustString String +hi def link rustStringDelimiter String hi def link rustCharacterInvalid Error hi def link rustCharacterInvalidUnicode rustCharacterInvalid hi def link rustCharacter Character @@ -241,12 +321,15 @@ hi def link rustFloat Float hi def link rustArrowCharacter rustOperator hi def link rustOperator Operator hi def link rustKeyword Keyword +hi def link rustDynKeyword rustKeyword hi def link rustTypedef Keyword " More precise is Typedef, but it doesn't feel right for Rust hi def link rustStructure Keyword " More precise is Structure hi def link rustUnion rustStructure +hi def link rustExistential rustKeyword hi def link rustPubScopeDelim Delimiter hi def link rustPubScopeCrate rustKeyword hi def link rustSuper rustKeyword +hi def link rustUnsafeKeyword Exception hi def link rustReservedKeyword Error hi def link rustRepeat Conditional hi def link rustConditional Conditional @@ -260,10 +343,13 @@ hi def link rustFuncCall Function hi def link rustShebang Comment hi def link rustCommentLine Comment hi def link rustCommentLineDoc SpecialComment +hi def link rustCommentLineDocLeader rustCommentLineDoc hi def link rustCommentLineDocError Error hi def link rustCommentBlock rustCommentLine hi def link rustCommentBlockDoc rustCommentLineDoc +hi def link rustCommentBlockDocStar rustCommentBlockDoc hi def link rustCommentBlockDocError Error +hi def link rustCommentDocCodeFence rustCommentLineDoc hi def link rustAssert PreCondit hi def link rustPanic PreCondit hi def link rustMacro Macro @@ -276,11 +362,15 @@ hi def link rustStorage StorageClass hi def link rustObsoleteStorage Error hi def link rustLifetime Special hi def link rustLabel Label -hi def link rustInvalidBareKeyword Error hi def link rustExternCrate rustKeyword hi def link rustObsoleteExternMod Error -hi def link rustBoxPlacementParens Delimiter hi def link rustQuestionMark Special +hi def link rustAsync rustKeyword +hi def link rustAwait rustKeyword +hi def link rustAsmDirSpec rustKeyword +hi def link rustAsmSym rustKeyword +hi def link rustAsmOptions rustKeyword +hi def link rustAsmOptionsKey rustAttribute " Other Suggestions: " hi rustAttribute ctermfg=cyan @@ -293,3 +383,5 @@ syn sync minlines=200 syn sync maxlines=500 let b:current_syntax = "rust" + +" vim: set et sw=4 sts=4 ts=8: diff --git a/runtime/syntax/scala.vim b/runtime/syntax/scala.vim index c08e60e55a..cc098ce017 100644 --- a/runtime/syntax/scala.vim +++ b/runtime/syntax/scala.vim @@ -180,7 +180,7 @@ hi def link scalaNumber Number syn region scalaRoundBrackets start="(" end=")" skipwhite contained contains=scalaTypeDeclaration,scalaSquareBrackets,scalaRoundBrackets -syn region scalaSquareBrackets matchgroup=scalaSquareBracketsBrackets start="\[" end="\]" skipwhite nextgroup=scalaTypeExtension contains=scalaTypeDeclaration,scalaSquareBrackets,scalaTypeOperator,scalaTypeAnnotationParameter +syn region scalaSquareBrackets matchgroup=scalaSquareBracketsBrackets start="\[" end="\]" skipwhite nextgroup=scalaTypeExtension contains=scalaTypeDeclaration,scalaSquareBrackets,scalaTypeOperator,scalaTypeAnnotationParameter,scalaString syn match scalaTypeOperator /[-+=:<>]\+/ contained syn match scalaTypeAnnotationParameter /@\<[`_A-Za-z0-9$]\+\>/ contained hi def link scalaSquareBracketsBrackets Type diff --git a/runtime/syntax/swayconfig.vim b/runtime/syntax/swayconfig.vim index 6b36210252..290e8cc1ac 100644 --- a/runtime/syntax/swayconfig.vim +++ b/runtime/syntax/swayconfig.vim @@ -1,10 +1,10 @@ " Vim syntax file " Language: sway window manager config -" Original Author: James Eapen <james.eapen@vai.org> +" Original Author: Josef Litos " Maintainer: James Eapen <james.eapen@vai.org> -" Version: 0.2.1 -" Reference version (jamespeapen/swayconfig.vim): 0.12.1 -" Last Change: 2023 Mar 20 +" Version: 0.2.2 +" Reference version (JosefLitos/i3config.vim): 1.8.1 +" Last Change: 2023-09-12 " References: " http://i3wm.org/docs/userguide.html#configuring @@ -19,98 +19,110 @@ endif runtime! syntax/i3config.vim -scriptencoding utf-8 +" i3 extensions +syn match i3ConfigSet /^\s*set \$\w\+ .*$/ contains=i3ConfigVariable,i3ConfigSetKeyword,i3ConfigColor,i3ConfigString,i3ConfigNoStartupId,i3ConfigNumber,swayConfigOutputCommand,i3ConfigShCommand,i3ConfigShDelim,i3ConfigShParam,i3ConfigShOper -" Error -"syn match swayConfigError /.*/ +syn keyword i3ConfigActionKeyword opacity contained -" binding syn keyword swayConfigBindKeyword bindswitch bindgesture contained -syn match swayConfigBind /^\s*\(bindswitch\)\s\+.*$/ contains=i3ConfigVariable,i3ConfigBindKeyword,swayConfigBindKeyword,i3ConfigVariableAndModifier,i3ConfigNumber,i3ConfigUnit,i3ConfigUnitOr,i3ConfigBindArgument,i3ConfigModifier,i3ConfigAction,i3ConfigString,i3ConfigGapStyleKeyword,i3ConfigBorderStyleKeyword - -" bindgestures -syn keyword swayConfigBindGestureCommand swipe pinch hold contained -syn keyword swayConfigBindGestureDirection up down left right next prev contained -syn keyword swayConfigBindGesturePinchDirection inward outward clockwise counterclockwise contained -syn match swayConfigBindGestureHold /^\s*\(bindgesture\)\s\+hold\(:[1-5]\)\?\s\+.*$/ contains=swayConfigBindKeyword,swayConfigBindGestureCommand,swayConfigBindGestureDirection,i3ConfigWorkspaceKeyword,i3ConfigAction -syn match swayConfigBindGestureSwipe /^\s*\(bindgesture\)\s\+swipe\(:[3-5]\)\?:\(up\|down\|left\|right\)\s\+.*$/ contains=swayConfigBindKeyword,swayConfigBindGestureCommand,swayConfigBindGestureDirection,i3ConfigWorkspaceKeyword,i3ConfigAction -syn match swayConfigBindGesturePinch /^\s*\(bindgesture\)\s\+pinch\(:[2-5]\)\?:\(up\|down\|left\|right\|inward\|outward\|clockwise\|counterclockwise\)\(+\(up\|down\|left\|right\|inward\|outward\|clockwise\|counterclockwise\)\)\?.*$/ contains=swayConfigBindKeyword,swayConfigBindGestureCommand,swayConfigBindGestureDirection,swayConfigBindGesturePinchDirection,i3ConfigWorkspaceKeyword,i3ConfigAction - -" floating -syn keyword swayConfigFloatingKeyword floating contained -syn match swayConfigFloating /^\s*floating\s\+\(enable\|disable\|toggle\)\s*$/ contains=swayConfigFloatingKeyword - -syn clear i3ConfigFloatingModifier -syn keyword swayConfigFloatingModifier floating_modifier contained -syn match swayConfigFloatingMouseAction /^\s\?.*floating_modifier\s\S\+\s\?\(normal\|inverted\|none\)\?$/ contains=swayConfigFloatingModifier,i3ConfigVariable - -" Gaps -syn clear i3ConfigSmartBorderKeyword -syn clear i3ConfigSmartBorder -syn keyword swayConfigSmartBorderKeyword on no_gaps off contained -syn match swayConfigSmartBorder /^\s*smart_borders\s\+\(on\|no_gaps\|off\)\s\?$/ contains=swayConfigSmartBorderKeyword - -" Changing colors -syn keyword swayConfigClientColorKeyword focused_tab_title contained -syn match swayConfigClientColor /^\s*client.\w\+\s\+.*$/ contains=i3ConfigClientColorKeyword,i3ConfigColor,i3ConfigVariable,i3ConfigClientColorKeyword,swayConfigClientColorKeyword - -" Input config -syn keyword swayConfigInputKeyword input contained -syn match swayConfigInput /^\s*input\s\+.*$/ contains=swayConfigInputKeyword +syn match i3ConfigBindArgument /--\(locked\|to-code\|no-repeat\|input-device=[:0-9a-zA-Z_/-]\+\|no-warn\)/ contained +syn region i3ConfigBind start=/^\s*bind\(switch\|gesture\) / skip=/\\$/ end=/$/ contains=swayConfigBindKeyword,swayConfigBindswitch,swayConfigBindswitchArgument,swayConfigBindgesture,swayConfigBindgestureArgument,i3ConfigCriteria,i3ConfigAction,i3ConfigSeparator,i3ConfigActionKeyword,i3ConfigOption,i3ConfigString,i3ConfigNumber,i3ConfigVariable,i3ConfigBoolean keepend + +syn match swayConfigBindBlockHeader /^\s*bind\(sym\|code\) .*{$/ contained contains=i3ConfigBindKeyword,i3ConfigBindArgument,i3ConfigParen +syn match swayConfigBindBlockCombo /^\s\+\(--[a-z-]\+ \)*[$a-zA-Z0-9_+]\+ [a-z[]\@=/ contained contains=i3ConfigBindArgument,i3ConfigBindCombo +syn region i3ConfigBind start=/^\s*bind\(sym\|code\) .*{$/ end=/^\s*}$/ contains=swayConfigBindBlockHeader,swayConfigBindBlockCombo,i3ConfigCriteria,i3ConfigAction,i3ConfigSeparator,i3ConfigActionKeyword,i3ConfigOption,i3ConfigString,i3ConfigNumber,i3ConfigVariable,i3ConfigBoolean,i3ConfigComment,i3ConfigParen fold keepend extend + +syn keyword i3ConfigClientOpts focused_tab_title contained + +syn region swayConfigExecBlock start=/exec\(_always\)\? {/ end=/^}$/ contains=i3ConfigExecKeyword,i3ConfigExecAlwaysKeyword,i3ConfigShCommand,i3ConfigShDelim,i3ConfigShOper,i3ConfigShParam,i3ConfigNumber,i3ConfigString,i3ConfigVariable,i3ConfigComment fold keepend extend + +syn keyword swayConfigFloatingModifierOpts normal inverse contained +syn match i3ConfigKeyword /^floating_modifier [$a-zA-Z0-9+]\+ \(normal\|inverse\)$/ contains=i3ConfigVariable,i3ConfigBindModkey,swayConfigFloatingModifierOpts + +syn match i3ConfigEdge /^hide_edge_borders\( --i3\)\? \(none\|vertical\|horizontal\|both\|smart\|smart_no_gaps\)\s\?$/ contains=i3ConfigEdgeKeyword,i3ConfigShParam + +syn keyword i3ConfigBarBlockKeyword swaybar_command gaps height pango_markup status_edge_padding status_padding wrap_scroll tray_bindcode tray_bindsym icon_theme contained + +syn keyword i3ConfigExecActionKeyword swaymsg contained -" Seat config +" Sway-only options +" Xwayland +syn keyword swayConfigXOpt enable disable force contained +syn match i3ConfigKeyword /^xwayland .*$/ contains=swayConfigXOpt + +" Bindswitch +syn match swayConfigBindswitchArgument /--\(locked\|no-warn\|reload\)/ contained +syn keyword swayConfigBindswitchType lid tablet contained +syn keyword swayConfigBindswitchState toggle contained +syn match swayConfigBindswitch /\(lid\|tablet\):\(on\|off\|toggle\) / contained contains=swayConfigBindswitchType,i3ConfigColonOperator,swayConfigBindswitchState,i3ConfigBoolean +syn region i3ConfigBind start=/^\s*bindswitch\s\+.*{$/ end=/^\s*}$/ contains=swayConfigBindKeyword,swayConfigBindswitch,swayConfigBindswitchArgument,i3ConfigNumber,i3ConfigVariable,i3ConfigAction,i3ConfigActionKeyword,i3ConfigOption,i3ConfigSeparator,i3ConfigString,i3ConfigCriteria,swayConfigOutputCommand,i3ConfigBoolean,i3ConfigComment,i3ConfigParen fold keepend extend + +" Bindgesture +syn match swayConfigBindgestureArgument /--\(exact\|input-device=[:0-9a-zA-Z_/-]\+\|no-warn\)/ contained +syn keyword swayConfigBindgestureType hold swipe pinch contained +syn keyword swayConfigBindgestureDir up down left right inward outward clockwise counterclockwise contained +syn match swayConfigBindgesture /\(hold\(:[1-5]\)\?\|swipe\(:[3-5]\)\?\(:up\|:down\|:left\|:right\)\?\|pinch\(:[2-5]\)\?:\(+\?\(inward\|outward\|clockwise\|counterclockwise\|up\|down\|left\|right\)\)\+\) / contained contains=i3ConfigNumber,swayConfigBindgestureType,i3ConfigColonOperator,swayConfigBindgestureDir,i3ConfigBindModifier +syn region i3ConfigBind start=/^\s*bindgesture\s\+.*{$/ end=/^\s*}$/ contains=swayConfigBindKeyword,swayConfigBindgesture,swayConfigBindgestureArgument,i3ConfigCriteria,i3ConfigAction,i3ConfigSeparator,i3ConfigActionKeyword,i3ConfigOption,i3ConfigString,i3ConfigNumber,i3ConfigVariable,i3ConfigBoolean,i3ConfigParen fold keepend extend + +" Titlebar commands +syn match i3ConfigKeyword /^titlebar_border_thickness \(\d\+\|\$\S\+\)$/ contains=i3ConfigNumber,i3ConfigVariable +syn match i3ConfigKeyword /^titlebar_padding \(\d\+\|\$\S\+\)\( \d\+\)\?$/ contains=i3ConfigNumber,i3ConfigVariable + +syn match swayConfigDeviceOps /[*,:;]/ contained + +" Output monitors +syn keyword swayConfigOutputKeyword output contained +syn keyword swayConfigOutputOpts mode resolution res modeline position pos scale scale_filter subpixel background bg transform disable enable power dpms max_render_time adaptive_sync render_bit_depth contained +syn keyword swayConfigOutputOptVals linear nearest smart rgb bgr vrgb vbgr none normal flipped fill stretch fit center tile solid_color clockwise anticlockwise toggle contained +syn match swayConfigOutputFPS /@[0-9.]\+Hz/ contained +syn match swayConfigOutputMode / [0-9]\+x[0-9]\+\(@[0-9.]\+Hz\)\?/ contained contains=swayConfigOutputFPS,i3ConfigNumber +syn region i3ConfigAction start=/output/ skip=/\\$/ end=/\([,;]\|$\)/ contained contains=swayConfigOutputKeyword,swayConfigOutputMode,swayConfigOutputOpts,swayConfigOutputOptVals,i3ConfigVariable,i3ConfigNumber,i3ConfigString,i3ConfigColor,i3ConfigBoolean,swayConfigDeviceOps keepend +syn region swayConfigOutput start=/^output/ skip=/\\$/ end=/$/ contains=swayConfigOutputKeyword,swayConfigOutputMode,swayConfigOutputOpts,swayConfigOutputOptVals,i3ConfigVariable,i3ConfigNumber,i3ConfigString,i3ConfigColor,i3ConfigBoolean,swayConfigDeviceOps keepend +syn region swayConfigOutput start=/^output .* {$/ end=/}$/ contains=swayConfigOutputKeyword,swayConfigOutputMode,swayConfigOutputOpts,swayConfigOutputOptVals,i3ConfigVariable,i3ConfigNumber,i3ConfigString,i3ConfigColor,i3ConfigBoolean,swayConfigDeviceOps,i3ConfigParen keepend extend + +" Input devices +syn keyword swayConfigInputKeyword input contained +syn keyword swayConfigInputOpts xkb_layout xkb_variant xkb_rules xkb_switch_layout xkb_numlock xkb_file xkb_capslock xkb_model repeat_delay repeat_rate map_to_output map_to_region map_from_region tool_mode accel_profile dwt dwtp drag_lock drag click_method middle_emulation tap events calibration_matrix natural_scroll left_handed pointer_accel scroll_button scroll_factor scroll_method tap_button_map contained +syn keyword swayConfigInputOptVals absolute relative adaptive flat none button_areas clickfinger toggle two_finger edge on_button_down lrm lmr contained +syn match swayConfigColonPairVal /:[0-9a-z_-]\+/ contained contains=i3ConfigColonOperator +syn match swayConfigColonPair /[a-z]\+:[0-9a-z_-]\+/ contained contains=swayConfigColonPairVal +syn match swayConfigInputXkbOpts /xkb_options \([a-z]\+:[0-9a-z_-]\+,\?\)\+/ contained contains=swayConfigColonPair,swayConfigDeviceOps +syn region i3ConfigAction start=/input/ skip=/\\$/ end=/\([,;]\|$\)/ contained contains=swayConfigInputKeyword,swayConfigColonPair,swayConfigInputXkbOpts,swayConfigInputOpts,swayConfigInputOptVals,i3ConfigVariable,i3ConfigNumber,i3ConfigString,i3ConfigBoolean,swayConfigDeviceOps keepend +syn region i3ConfigInput start=/^input/ skip=/\\$/ end=/$/ contains=swayConfigInputKeyword,swayConfigColonPair,swayConfigInputXkbOpts,swayConfigInputOpts,swayConfigInputOptVals,i3ConfigVariable,i3ConfigNumber,i3ConfigString,i3ConfigBoolean,swayConfigDeviceOps keepend +syn region i3ConfigInput start=/^input .* {/ end=/}$/ contains=swayConfigInputKeyword,swayConfigColonPair,swayConfigInputXkbOpts,swayConfigInputOpts,swayConfigInputOptVals,i3ConfigVariable,i3ConfigNumber,i3ConfigString,i3ConfigBoolean,swayConfigDeviceOps,i3ConfigParen keepend extend + +" Seat syn keyword swayConfigSeatKeyword seat contained -syn match swayConfigSeat /^\s*seat\s\+.*$/ contains=swayConfigSeatKeyword - -" set display outputs -syn match swayConfigOutput /^\s*output\s\+.*$/ contains=i3ConfigOutput - -" set display focus -syn keyword swayConfigFocusKeyword focus contained -syn keyword swayConfigFocusType output contained -syn match swayConfigFocus /^\s*focus\soutput\s.*$/ contains=swayConfigFocusKeyword,swayConfigFocusType - -" mouse warping -syn keyword swayConfigMouseWarpingType container contained -syn match swayConfigMouseWarping /^\s*mouse_warping\s\+\(output\|container\|none\)\s\?$/ contains=i3ConfigMouseWarpingKeyword,i3ConfigMouseWarpingType,swayConfigMouseWarpingType - -" focus follows mouse -syn clear i3ConfigFocusFollowsMouseType -syn clear i3ConfigFocusFollowsMouse - -syn keyword swayConfigFocusFollowsMouseType yes no always contained -syn match swayConfigFocusFollowsMouse /^\s*focus_follows_mouse\s\+\(yes\|no\|always\)\s\?$/ contains=i3ConfigFocusFollowsMouseKeyword,swayConfigFocusFollowsMouseType - - -" xwayland -syn keyword swayConfigXwaylandKeyword xwayland contained -syn match swayConfigXwaylandModifier /^\s*xwayland\s\+\(enable\|disable\|force\)\s\?$/ contains=swayConfigXwaylandKeyword - -" Group mode/bar -syn clear i3ConfigBlock -syn region swayConfigBlock start=+.*s\?{$+ end=+^}$+ contains=i3ConfigBlockKeyword,i3ConfigString,i3ConfigBind,i3ConfigInitializeKeyword,i3ConfigComment,i3ConfigFont,i3ConfigFocusWrappingType,i3ConfigColor,i3ConfigVariable,swayConfigInputKeyword,swayConfigSeatKeyword,i3ConfigOutput transparent keepend extend - -"hi def link swayConfigError Error -hi def link i3ConfigFloating Error -hi def link swayConfigFloating Type -hi def link swayConfigFloatingMouseAction Type -hi def link swayConfigFocusKeyword Type -hi def link swayConfigSmartBorderKeyword Type -hi def link swayConfigInputKeyword Type -hi def link swayConfigSeatKeyword Type -hi def link swayConfigMouseWarpingType Type -hi def link swayConfigFocusFollowsMouseType Type -hi def link swayConfigBindGestureCommand Identifier -hi def link swayConfigBindGestureDirection Constant -hi def link swayConfigBindGesturePinchDirection Constant -hi def link swayConfigBindKeyword Identifier -hi def link swayConfigClientColorKeyword Identifier -hi def link swayConfigFloatingKeyword Identifier -hi def link swayConfigFloatingModifier Identifier -hi def link swayConfigFocusType Identifier -hi def link swayConfigSmartBorder Identifier -hi def link swayConfigXwaylandKeyword Identifier -hi def link swayConfigXwaylandModifier Type -hi def link swayConfigBindGesture PreProc +syn keyword swayConfigSeatOpts attach cursor fallback hide_cursor idle_inhibit idle_wake keyboard_grouping shortcuts_inhibitor pointer_constraint xcursor_theme contained +syn match swayConfigSeatOptVals /when-typing/ contained +syn keyword swayConfigSeatOptVals move set press release none smart activate deactivate toggle escape enable disable contained +syn region i3ConfigAction start=/seat/ skip=/\\$/ end=/\([,;]\|$\)/ contained contains=swayConfigSeatKeyword,i3ConfigString,i3ConfigNumber,i3ConfigBoolean,swayConfigSeatOptVals,swayConfigSeatOpts,swayConfigDeviceOps keepend +syn region swayConfigSeat start=/seat/ skip=/\\$/ end=/$/ contains=swayConfigSeatKeyword,i3ConfigString,i3ConfigNumber,i3ConfigBoolean,swayConfigSeatOptVals,swayConfigSeatOpts,swayConfigDeviceOps keepend +syn region swayConfigSeat start=/seat .* {$/ end=/}$/ contains=swayConfigSeatKeyword,i3ConfigString,i3ConfigNumber,i3ConfigBoolean,swayConfigSeatOptVals,swayConfigSeatOpts,swayConfigDeviceOps,i3ConfigParen keepend extend + +" Define the highlighting. +hi def link swayConfigFloatingModifierOpts i3ConfigOption +hi def link swayConfigBindKeyword i3ConfigBindKeyword +hi def link swayConfigXOpt i3ConfigOption +hi def link swayConfigBindswitchArgument i3ConfigBindArgument +hi def link swayConfigBindswitchType i3ConfigMoveType +hi def link swayConfigBindswitchState i3ConfigMoveDir +hi def link swayConfigBindgestureArgument i3ConfigBindArgument +hi def link swayConfigBindgestureType i3ConfigMoveType +hi def link swayConfigBindgestureDir i3ConfigMoveDir +hi def link swayConfigDeviceOps i3ConfigOperator +hi def link swayConfigOutputKeyword i3ConfigCommand +hi def link swayConfigOutputOptVals i3ConfigOption +hi def link swayConfigOutputOpts i3ConfigOption +hi def link swayConfigOutputFPS Constant +hi def link swayConfigInputKeyword i3ConfigCommand +hi def link swayConfigInputOptVals i3ConfigShParam +hi def link swayConfigInputOpts i3ConfigOption +hi def link swayConfigInputXkbOpts i3ConfigOption +hi def link swayConfigColonPairVal i3ConfigString +hi def link swayConfigColonPair i3ConfigShParam +hi def link swayConfigSeatKeyword i3ConfigCommand +hi def link swayConfigSeatOptVals i3ConfigOption +hi def link swayConfigSeatOpts i3ConfigOption let b:current_syntax = "swayconfig" diff --git a/scripts/gen_eval_files.lua b/scripts/gen_eval_files.lua index f8d69ce313..1afe3d5f46 100755 --- a/scripts/gen_eval_files.lua +++ b/scripts/gen_eval_files.lua @@ -213,17 +213,20 @@ end --- Convert vimdoc references to markdown literals --- Convert vimdoc codeblocks to markdown codeblocks +--- +--- Ensure code blocks have one empty line before the start fence and after the closing fence. +--- --- @param x string --- @return string local function norm_text(x) return ( x:gsub('|([^ ]+)|', '`%1`') - :gsub('>lua', '\n```lua') - :gsub('>vim', '\n```vim') - :gsub('\n<$', '\n```') - :gsub('\n<\n', '\n```\n') - :gsub('%s+>\n', '\n```\n') - :gsub('\n<%s+\n?', '\n```\n') + :gsub('\n*>lua', '\n\n```lua') + :gsub('\n*>vim', '\n\n```vim') + :gsub('\n+<$', '\n```') + :gsub('\n+<\n+', '\n```\n\n') + :gsub('%s+>\n+', '\n```\n') + :gsub('\n+<%s+\n?', '\n```\n') ) end @@ -552,26 +555,19 @@ end local function scope_more_doc(o) if vim.list_contains({ - 'previewwindow', - 'scroll', - 'winfixheight', - 'winfixwidth', - }, o.full_name) - then - return ' |special-local-window-option|' - end - - if - vim.list_contains({ 'bufhidden', 'buftype', 'filetype', 'modified', + 'previewwindow', 'readonly', + 'scroll', 'syntax', + 'winfixheight', + 'winfixwidth', }, o.full_name) then - return ' |special-local-buffer-option|' + return ' |local-noglobal|' end return '' diff --git a/scripts/gen_help_html.lua b/scripts/gen_help_html.lua index 5e06830336..c8e004e2ab 100644 --- a/scripts/gen_help_html.lua +++ b/scripts/gen_help_html.lua @@ -574,14 +574,15 @@ local function visit_node(root, level, lang_tree, headings, opt, stats) return '' end local el = in_heading and 'span' or 'code' - local s = ('%s<%s id="%s" class="%s">%s</%s>'):format(ws(), el, url_encode(tagname), cssclass, trimmed, el) + local encoded_tagname = url_encode(tagname) + local s = ('%s<%s id="%s" class="%s"><a href="#%s">%s</a></%s>'):format(ws(), el, encoded_tagname, cssclass, encoded_tagname, trimmed, el) if opt.old then s = fix_tab_after_conceal(s, node_text(root:next_sibling())) end if in_heading and prev ~= 'tag' then -- Don't set "id", let the heading use the tag as its "id" (used by search engines). - s = ('%s<%s class="%s">%s</%s>'):format(ws(), el, cssclass, trimmed, el) + s = ('%s<%s class="%s"><a href="#%s">%s</a></%s>'):format(ws(), el, cssclass, encoded_tagname, trimmed, el) -- Start the <span> container for tags in a heading. -- This makes "justify-content:space-between" right-align the tags. -- <h2>foo bar<span>tag1 tag2</span></h2> @@ -963,6 +964,7 @@ local function gen_css(fname) margin-bottom: 0; } + /* TODO: should this rule be deleted? help tags are rendered as <code> or <span>, not <a> */ a.help-tag, a.help-tag:focus, a.help-tag:hover { color: inherit; text-decoration: none; @@ -977,6 +979,14 @@ local function gen_css(fname) margin-right: 0; float: right; } + .help-tag a, + .help-tag-right a { + color: inherit; + } + .help-tag a:not(:hover), + .help-tag-right a:not(:hover) { + text-decoration: none; + } h1 .help-tag, h2 .help-tag, h3 .help-tag { font-size: smaller; } diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py index dfad1f000c..ed156e1422 100755 --- a/scripts/gen_vimdoc.py +++ b/scripts/gen_vimdoc.py @@ -440,7 +440,7 @@ def is_blank(text): return '' == clean_lines(text) -def get_text(n, preformatted=False): +def get_text(n): """Recursively concatenates all text in a node tree.""" text = '' if n.nodeType == n.TEXT_NODE: @@ -449,11 +449,13 @@ def get_text(n, preformatted=False): for node in n.childNodes: text += get_text(node) return '`{}`'.format(text) + if n.nodeName == 'sp': # space, used in "programlisting" nodes + return ' ' for node in n.childNodes: if node.nodeType == node.TEXT_NODE: text += node.data elif node.nodeType == node.ELEMENT_NODE: - text += get_text(node, preformatted) + text += get_text(node) return text @@ -571,7 +573,7 @@ def render_node(n, text, prefix='', indent='', width=text_width - indentation, # text += (int(not space_preceding) * ' ') if n.nodeName == 'preformatted': - o = get_text(n, preformatted=True) + o = get_text(n) ensure_nl = '' if o[-1] == '\n' else '\n' if o[0:4] == 'lua\n': text += '>lua{}{}\n<'.format(ensure_nl, o[3:-1]) @@ -581,7 +583,14 @@ def render_node(n, text, prefix='', indent='', width=text_width - indentation, text += o[4:-1] else: text += '>{}{}\n<'.format(ensure_nl, o) - + elif n.nodeName == 'programlisting': # codeblock (```) + o = get_text(n) + text += '>' + if 'filename' in n.attributes: + filename = n.attributes['filename'].value + text += filename.lstrip('.') + + text += '\n{}\n<'.format(textwrap.indent(o, ' ' * 4)) elif is_inline(n): text = doc_wrap(get_text(n), prefix=prefix, indent=indent, width=width) elif n.nodeName == 'verbatim': @@ -758,6 +767,27 @@ def para_as_map(parent, indent='', width=text_width - indentation, fmt_vimhelp=F return chunks, xrefs +def is_program_listing(para): + """ + Return True if `para` contains a "programlisting" (i.e. a Markdown code + block ```). + + Sometimes a <para> element will have only a single "programlisting" child + node, but othertimes it will have extra whitespace around the + "programlisting" node. + + @param para XML <para> node + @return True if <para> is a programlisting + """ + + # Remove any child text nodes that are only whitespace + children = [ + n for n in para.childNodes + if n.nodeType != n.TEXT_NODE or n.data.strip() != '' + ] + + return len(children) == 1 and children[0].nodeName == 'programlisting' + def fmt_node_as_vimhelp(parent: Element, width=text_width - indentation, indent='', fmt_vimhelp=False): """Renders (nested) Doxygen <para> nodes as Vim :help text. @@ -786,6 +816,15 @@ def fmt_node_as_vimhelp(parent: Element, width=text_width - indentation, indent= for child in parent.childNodes: para, _ = para_as_map(child, indent, width, fmt_vimhelp) + # 'programlisting' blocks are Markdown code blocks. Do not include + # these as a separate paragraph, but append to the last non-empty line + # in the text + if is_program_listing(child): + while rendered_blocks and rendered_blocks[-1] == '': + rendered_blocks.pop() + rendered_blocks[-1] += ' ' + para['text'] + continue + # Generate text from the gathered items. chunks = [para['text']] if len(para['note']) > 0: diff --git a/src/klib/kvec.h b/src/klib/kvec.h index f6674a0adf..fd9096e1ad 100644 --- a/src/klib/kvec.h +++ b/src/klib/kvec.h @@ -207,6 +207,16 @@ static inline void *_memcpy_free(void *const restrict dest, void *const restrict /* 2^x initial array size. */ \ kvi_resize(v, (v).capacity << 1) +/// fit at least "len" more items +#define kvi_ensure_more_space(v, len) \ + do { \ + if ((v).capacity < (v).size + len) { \ + (v).capacity = (v).size + len; \ + kv_roundup32((v).capacity); \ + kvi_resize((v), (v).capacity); \ + } \ + } while (0) + /// Get location where to store new element to a vector with preallocated array /// /// @param[in,out] v Vector to push to. @@ -223,6 +233,19 @@ static inline void *_memcpy_free(void *const restrict dest, void *const restrict #define kvi_push(v, x) \ (*kvi_pushp(v) = (x)) +/// Copy a vector to a preallocated vector +/// +/// @param[out] v1 destination +/// @param[in] v0 source (can be either vector or preallocated vector) +#define kvi_copy(v1, v0) \ + do { \ + if ((v1).capacity < (v0).size) { \ + kvi_resize(v1, (v0).size); \ + } \ + (v1).size = (v0).size; \ + memcpy((v1).items, (v0).items, sizeof((v1).items[0]) * (v0).size); \ + } while (0) + /// Free array of elements of a vector with preallocated array if needed /// /// @param[out] v Vector to free. diff --git a/src/nvim/README.md b/src/nvim/README.md index 0de0fb9a3f..4029777031 100644 --- a/src/nvim/README.md +++ b/src/nvim/README.md @@ -1,8 +1,7 @@ Nvim core ========= -Module-specific details are documented at the top of each module (`terminal.c`, -`screen.c`, …). +Module-specific details are documented at the top of each module (`terminal.c`, `undo.c`, …). See `:help dev` for guidelines. diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index aa0c2695ad..2e4d2a622d 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -49,19 +49,20 @@ static int64_t next_autocmd_id = 1; /// Get all autocommands that match the corresponding {opts}. /// /// These examples will get autocommands matching ALL the given criteria: -/// <pre>lua -/// -- Matches all criteria -/// autocommands = vim.api.nvim_get_autocmds({ -/// group = "MyGroup", -/// event = {"BufEnter", "BufWinEnter"}, -/// pattern = {"*.c", "*.h"} -/// }) /// -/// -- All commands from one group -/// autocommands = vim.api.nvim_get_autocmds({ -/// group = "MyGroup", -/// }) -/// </pre> +/// ```lua +/// -- Matches all criteria +/// autocommands = vim.api.nvim_get_autocmds({ +/// group = "MyGroup", +/// event = {"BufEnter", "BufWinEnter"}, +/// pattern = {"*.c", "*.h"} +/// }) +/// +/// -- All commands from one group +/// autocommands = vim.api.nvim_get_autocmds({ +/// group = "MyGroup", +/// }) +/// ``` /// /// NOTE: When multiple patterns or events are provided, it will find all the autocommands that /// match any combination of them. @@ -344,28 +345,31 @@ cleanup: /// function _name_ string) or `command` (Ex command string). /// /// Example using Lua callback: -/// <pre>lua -/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { -/// pattern = {"*.c", "*.h"}, -/// callback = function(ev) -/// print(string.format('event fired: \%s', vim.inspect(ev))) -/// end -/// }) -/// </pre> +/// +/// ```lua +/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { +/// pattern = {"*.c", "*.h"}, +/// callback = function(ev) +/// print(string.format('event fired: %s', vim.inspect(ev))) +/// end +/// }) +/// ``` /// /// Example using an Ex command as the handler: -/// <pre>lua -/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { -/// pattern = {"*.c", "*.h"}, -/// command = "echo 'Entering a C or C++ file'", -/// }) -/// </pre> +/// +/// ```lua +/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { +/// pattern = {"*.c", "*.h"}, +/// command = "echo 'Entering a C or C++ file'", +/// }) +/// ``` /// /// Note: `pattern` is NOT automatically expanded (unlike with |:autocmd|), thus names like "$HOME" /// and "~" must be expanded explicitly: -/// <pre>lua -/// pattern = vim.fn.expand("~") .. "/some/path/*.py" -/// </pre> +/// +/// ```lua +/// pattern = vim.fn.expand("~") .. "/some/path/*.py" +/// ``` /// /// @param event (string|array) Event(s) that will trigger the handler (`callback` or `command`). /// @param opts Options dict: @@ -619,11 +623,12 @@ cleanup: /// Create or get an autocommand group |autocmd-groups|. /// /// To get an existing group id, do: -/// <pre>lua -/// local id = vim.api.nvim_create_augroup("MyGroup", { -/// clear = false -/// }) -/// </pre> +/// +/// ```lua +/// local id = vim.api.nvim_create_augroup("MyGroup", { +/// clear = false +/// }) +/// ``` /// /// @param name String: The name of the group /// @param opts Dictionary Parameters diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index b8cb09ceb3..e8f9f809f2 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -85,11 +85,15 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err) /// /// Example (Lua): capture buffer updates in a global `events` variable /// (use "vim.print(events)" to see its contents): -/// <pre>lua -/// events = {} -/// vim.api.nvim_buf_attach(0, false, { -/// on_lines=function(...) table.insert(events, {...}) end}) -/// </pre> +/// +/// ```lua +/// events = {} +/// vim.api.nvim_buf_attach(0, false, { +/// on_lines = function(...) +/// table.insert(events, {...}) +/// end, +/// }) +/// ``` /// /// @see |nvim_buf_detach()| /// @see |api-buffer-updates-lua| @@ -504,7 +508,10 @@ end: /// /// Prefer |nvim_buf_set_lines()| if you are only adding or deleting entire lines. /// +/// Prefer |nvim_put()| if you want to insert text at the cursor position. +/// /// @see |nvim_buf_set_lines()| +/// @see |nvim_put()| /// /// @param channel_id /// @param buffer Buffer handle, or 0 for current buffer @@ -725,11 +732,12 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In FOR_ALL_TAB_WINDOWS(tp, win) { if (win->w_buffer == buf) { - // adjust cursor like an extmark ( i e it was inside last_part_len) - if (win->w_cursor.lnum == end_row && win->w_cursor.col > end_col) { - win->w_cursor.col -= col_extent - (colnr_T)last_item.size; + if (win->w_cursor.lnum >= start_row && win->w_cursor.lnum <= end_row) { + fix_cursor_cols(win, (linenr_T)start_row, (colnr_T)start_col, (linenr_T)end_row, + (colnr_T)end_col, (linenr_T)new_len, (colnr_T)last_item.size); + } else { + fix_cursor(win, (linenr_T)start_row, (linenr_T)end_row, (linenr_T)extra); } - fix_cursor(win, (linenr_T)start_row, (linenr_T)end_row, (linenr_T)extra); } } @@ -1339,6 +1347,79 @@ static void fix_cursor(win_T *win, linenr_T lo, linenr_T hi, linenr_T extra) invalidate_botline(win); } +/// Fix cursor position after replacing text +/// between (start_row, start_col) and (end_row, end_col). +/// +/// win->w_cursor.lnum is assumed to be >= start_row and <= end_row. +static void fix_cursor_cols(win_T *win, linenr_T start_row, colnr_T start_col, linenr_T end_row, + colnr_T end_col, linenr_T new_rows, colnr_T new_cols_at_end_row) +{ + colnr_T mode_col_adj = win == curwin && (State & MODE_INSERT) ? 0 : 1; + + colnr_T end_row_change_start = new_rows == 1 ? start_col : 0; + colnr_T end_row_change_end = end_row_change_start + new_cols_at_end_row; + + // check if cursor is after replaced range or not + if (win->w_cursor.lnum == end_row && win->w_cursor.col + mode_col_adj > end_col) { + // if cursor is after replaced range, it's shifted + // to keep it's position the same, relative to end_col + + linenr_T old_rows = end_row - start_row + 1; + win->w_cursor.lnum += new_rows - old_rows; + win->w_cursor.col += end_row_change_end - end_col; + } else { + // if cursor is inside replaced range + // and the new range got smaller, + // it's shifted to keep it inside the new range + // + // if cursor is before range or range did not + // got smaller, position is not changed + + colnr_T old_coladd = win->w_cursor.coladd; + + // it's easier to work with a single value here. + // col and coladd are fixed by a later call + // to check_cursor_col_win when necessary + win->w_cursor.col += win->w_cursor.coladd; + win->w_cursor.coladd = 0; + + linenr_T new_end_row = start_row + new_rows - 1; + + // make sure cursor row is in the new row range + if (win->w_cursor.lnum > new_end_row) { + win->w_cursor.lnum = new_end_row; + + // don't simply move cursor up, but to the end + // of new_end_row, if it's not at or after + // it already (in case virtualedit is active) + // column might be additionally adjusted below + // to keep it inside col range if needed + colnr_T len = (colnr_T)strlen(ml_get_buf(win->w_buffer, new_end_row)); + if (win->w_cursor.col < len) { + win->w_cursor.col = len; + } + } + + // if cursor is at the last row and + // it wasn't after eol before, move it exactly + // to end_row_change_end + if (win->w_cursor.lnum == new_end_row + && win->w_cursor.col > end_row_change_end && old_coladd == 0) { + win->w_cursor.col = end_row_change_end; + + // make sure cursor is inside range, not after it, + // except when doing so would move it before new range + if (win->w_cursor.col - mode_col_adj >= end_row_change_start) { + win->w_cursor.col -= mode_col_adj; + } + } + } + + check_cursor_col_win(win); + changed_cline_bef_curs(win); + invalidate_botline(win); +} + /// Initialise a string array either: /// - on the Lua stack (as a table) (if lstate is not NULL) /// - as an API array object (if lstate is NULL). diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index 2b09cfc4b2..808d4e0b8d 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -860,11 +860,12 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin /// For Lua usage see |lua-guide-commands-create|. /// /// Example: -/// <pre>vim -/// :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {'bang': v:true}) -/// :SayHello -/// Hello world! -/// </pre> +/// +/// ```vim +/// :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {'bang': v:true}) +/// :SayHello +/// Hello world! +/// ``` /// /// @param name Name of the new user command. Must begin with an uppercase letter. /// @param command Replacement command to execute when this user command is executed. When called diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 6ec1fc4ee0..05f62f6c7c 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -300,29 +300,35 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, /// Region can be given as (row,col) tuples, or valid extmark ids (whose /// positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1) /// respectively, thus the following are equivalent: -/// <pre>lua -/// vim.api.nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) -/// vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {}) -/// </pre> +/// +/// ```lua +/// vim.api.nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) +/// vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {}) +/// ``` /// /// If `end` is less than `start`, traversal works backwards. (Useful /// with `limit`, to get the first marks prior to a given position.) /// +/// Note: when using extmark ranges (marks with a end_row/end_col position) +/// the `overlap` option might be useful. Otherwise only the start position +/// of an extmark will be considered. +/// /// Example: -/// <pre>lua -/// local api = vim.api -/// local pos = api.nvim_win_get_cursor(0) -/// local ns = api.nvim_create_namespace('my-plugin') -/// -- Create new extmark at line 1, column 1. -/// local m1 = api.nvim_buf_set_extmark(0, ns, 0, 0, {}) -/// -- Create new extmark at line 3, column 1. -/// local m2 = api.nvim_buf_set_extmark(0, ns, 2, 0, {}) -/// -- Get extmarks only from line 3. -/// local ms = api.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {}) -/// -- Get all marks in this buffer + namespace. -/// local all = api.nvim_buf_get_extmarks(0, ns, 0, -1, {}) -/// vim.print(ms) -/// </pre> +/// +/// ```lua +/// local api = vim.api +/// local pos = api.nvim_win_get_cursor(0) +/// local ns = api.nvim_create_namespace('my-plugin') +/// -- Create new extmark at line 1, column 1. +/// local m1 = api.nvim_buf_set_extmark(0, ns, 0, 0, {}) +/// -- Create new extmark at line 3, column 1. +/// local m2 = api.nvim_buf_set_extmark(0, ns, 2, 0, {}) +/// -- Get extmarks only from line 3. +/// local ms = api.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {}) +/// -- Get all marks in this buffer + namespace. +/// local all = api.nvim_buf_get_extmarks(0, ns, 0, -1, {}) +/// vim.print(ms) +/// ``` /// /// @param buffer Buffer handle, or 0 for current buffer /// @param ns_id Namespace id from |nvim_create_namespace()| or -1 for all namespaces @@ -334,11 +340,13 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, /// - limit: Maximum number of marks to return /// - details: Whether to include the details dict /// - hl_name: Whether to include highlight group name instead of id, true if omitted +/// - overlap: Also include marks which overlap the range, even if +/// their start position is less than `start` /// - type: Filter marks by type: "highlight", "sign", "virt_text" and "virt_lines" /// @param[out] err Error details, if any /// @return List of [extmark_id, row, col] tuples in "traversal order". -Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object end, Dictionary opts, - Error *err) +Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object end, + Dict(get_extmarks) *opts, Error *err) FUNC_API_SINCE(7) { Array rv = ARRAY_DICT_INIT; @@ -348,63 +356,32 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e return rv; } - bool all_ns; - if (ns_id == -1) { - all_ns = true; - } else { - VALIDATE_INT(ns_initialized((uint32_t)ns_id), "ns_id", ns_id, { - return rv; - }); - all_ns = false; - } + VALIDATE_INT(ns_id == -1 || ns_initialized((uint32_t)ns_id), "ns_id", ns_id, { + return rv; + }); - Integer limit = -1; - bool details = false; - bool hl_name = true; - ExtmarkType type = kExtmarkNone; + bool details = opts->details; + bool hl_name = GET_BOOL_OR_TRUE(opts, get_extmarks, hl_name); - for (size_t i = 0; i < opts.size; i++) { - String k = opts.items[i].key; - Object *v = &opts.items[i].value; - if (strequal("limit", k.data)) { - VALIDATE_T("limit", kObjectTypeInteger, v->type, { - return rv; - }); - limit = v->data.integer; - } else if (strequal("details", k.data)) { - details = api_object_to_bool(*v, "details", false, err); - if (ERROR_SET(err)) { - return rv; - } - } else if (strequal("hl_name", k.data)) { - hl_name = api_object_to_bool(*v, "hl_name", false, err); - if (ERROR_SET(err)) { - return rv; - } - } else if (strequal("type", k.data)) { - VALIDATE_EXP(v->type == kObjectTypeString, "type", "String", api_typename(v->type), { - return rv; - }); - if (strequal(v->data.string.data, "sign")) { - type = kExtmarkSign; - } else if (strequal(v->data.string.data, "virt_text")) { - type = kExtmarkVirtText; - } else if (strequal(v->data.string.data, "virt_lines")) { - type = kExtmarkVirtLines; - } else if (strequal(v->data.string.data, "highlight")) { - type = kExtmarkHighlight; - } else { - VALIDATE_EXP(false, "type", "sign, virt_text, virt_lines or highlight", v->data.string.data, { - return rv; - }); - } + ExtmarkType type = kExtmarkNone; + if (HAS_KEY(opts, get_extmarks, type)) { + if (strequal(opts->type.data, "sign")) { + type = kExtmarkSign; + } else if (strequal(opts->type.data, "virt_text")) { + type = kExtmarkVirtText; + } else if (strequal(opts->type.data, "virt_lines")) { + type = kExtmarkVirtLines; + } else if (strequal(opts->type.data, "highlight")) { + type = kExtmarkHighlight; } else { - VALIDATE_S(false, "'opts' key", k.data, { + VALIDATE_EXP(false, "type", "sign, virt_text, virt_lines or highlight", opts->type.data, { return rv; }); } } + Integer limit = HAS_KEY(opts, get_extmarks, limit) ? opts->limit : -1; + if (limit == 0) { return rv; } else if (limit < 0) { @@ -429,11 +406,12 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e reverse = true; } + // note: ns_id=-1 allowed, represented as UINT32_MAX ExtmarkInfoArray marks = extmark_get(buf, (uint32_t)ns_id, l_row, l_col, u_row, - u_col, (int64_t)limit, reverse, all_ns, type); + u_col, (int64_t)limit, reverse, type, opts->overlap); for (size_t i = 0; i < kv_size(marks); i++) { - ADD(rv, ARRAY_OBJ(extmark_to_array(&kv_A(marks, i), true, (bool)details, hl_name))); + ADD(rv, ARRAY_OBJ(extmark_to_array(&kv_A(marks, i), true, details, hl_name))); } kv_destroy(marks); @@ -451,6 +429,11 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// Using the optional arguments, it is possible to use this to highlight /// a range of text, and also to associate virtual text to the mark. /// +/// If present, the position defined by `end_col` and `end_row` should be after +/// the start position in order for the extmark to cover a range. +/// An earlier end position is not an error, but then it behaves like an empty +/// range (no highlighting). +/// /// @param buffer Buffer handle, or 0 for current buffer /// @param ns_id Namespace id from |nvim_create_namespace()| /// @param line Line where to place the mark, 0-based. |api-indexing| @@ -1035,7 +1018,8 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start, /// window callbacks) /// ["buf", bufnr, tick] /// - on_win: called when starting to redraw a -/// specific window. +/// specific window. botline_guess is an approximation +/// that does not exceed the last line number. /// ["win", winid, bufnr, topline, botline_guess] /// - on_line: called for each buffer line being redrawn. /// (The interaction with fold lines is subject to change) @@ -1229,3 +1213,14 @@ free_exit: clear_virttext(&virt_text); return virt_text; } + +String nvim__buf_debug_extmarks(Buffer buffer, Boolean keys, Boolean dot, Error *err) + FUNC_API_SINCE(7) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + if (!buf) { + return NULL_STRING; + } + + return mt_inspect(buf->b_marktree, keys, dot); +} diff --git a/src/nvim/api/keysets.h b/src/nvim/api/keysets.h index 1f5c7069a9..4e5e7af619 100644 --- a/src/nvim/api/keysets.h +++ b/src/nvim/api/keysets.h @@ -51,6 +51,15 @@ typedef struct { } Dict(set_extmark); typedef struct { + OptionalKeys is_set__get_extmarks_; + Integer limit; + Boolean details; + Boolean hl_name; + Boolean overlap; + String type; +} Dict(get_extmarks); + +typedef struct { OptionalKeys is_set__keymap_; Boolean noremap; Boolean nowait; @@ -181,6 +190,7 @@ typedef struct { Integer id; String name; Boolean link; + Boolean create; } Dict(get_highlight); typedef struct { diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 411d63b921..916409b973 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -96,6 +96,7 @@ Integer nvim_get_hl_id_by_name(String name) /// - name: (string) Get a highlight definition by name. /// - id: (integer) Get a highlight definition by id. /// - link: (boolean, default true) Show linked group name instead of effective definition |:hi-link|. +/// - create: (boolean, default true) When highlight group doesn't exist create it. /// /// @param[out] err Error details, if any. /// @return Highlight groups as a map from group name to a highlight definition map as in |nvim_set_hl()|, @@ -211,10 +212,11 @@ void nvim_set_hl_ns_fast(Integer ns_id, Error *err) /// nvim_feedkeys(). /// /// Example: -/// <pre>vim -/// :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true) -/// :call nvim_feedkeys(key, 'n', v:false) -/// </pre> +/// +/// ```vim +/// :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true) +/// :call nvim_feedkeys(key, 'n', v:false) +/// ``` /// /// @param keys to be typed /// @param mode behavior flags, see |feedkeys()| @@ -1279,10 +1281,11 @@ void nvim_unsubscribe(uint64_t channel_id, String event) /// "#rrggbb" hexadecimal string. /// /// Example: -/// <pre>vim -/// :echo nvim_get_color_by_name("Pink") -/// :echo nvim_get_color_by_name("#cbcbcb") -/// </pre> +/// +/// ```vim +/// :echo nvim_get_color_by_name("Pink") +/// :echo nvim_get_color_by_name("#cbcbcb") +/// ``` /// /// @param name Color name or "#rrggbb" string /// @return 24-bit RGB value, or -1 for invalid argument. @@ -1419,14 +1422,16 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode) /// Empty {rhs} is |<Nop>|. |keycodes| are replaced as usual. /// /// Example: -/// <pre>vim -/// call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true}) -/// </pre> +/// +/// ```vim +/// call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true}) +/// ``` /// /// is equivalent to: -/// <pre>vim -/// nmap <nowait> <Space><NL> <Nop> -/// </pre> +/// +/// ```vim +/// nmap <nowait> <Space><NL> <Nop> +/// ``` /// /// @param channel_id /// @param mode Mode short-name (map command prefix: "n", "i", "v", "x", …) diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 3a3e6da2b1..63cf3bb701 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -56,16 +56,19 @@ /// this should not be used to specify arbitrary WM screen positions. /// /// Example (Lua): window-relative float -/// <pre>lua -/// vim.api.nvim_open_win(0, false, -/// {relative='win', row=3, col=3, width=12, height=3}) -/// </pre> +/// +/// ```lua +/// vim.api.nvim_open_win(0, false, +/// {relative='win', row=3, col=3, width=12, height=3}) +/// ``` /// /// Example (Lua): buffer-relative float (travels as buffer is scrolled) -/// <pre>lua -/// vim.api.nvim_open_win(0, false, -/// {relative='win', width=12, height=3, bufpos={100,10}}) -/// </pre> +/// +/// ```lua +/// vim.api.nvim_open_win(0, false, +/// {relative='win', width=12, height=3, bufpos={100,10}}) +/// }) +/// ``` /// /// @param buffer Buffer to display, or 0 for current buffer /// @param enter Enter the window (make it the current window) diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 5f94fbb014..3fa20f4e48 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -2524,7 +2524,7 @@ static bool arg_autocmd_flag_get(bool *flag, char **cmd_ptr, char *pattern, int } /// When kFalse: VimSuspend should be triggered next. -/// When kTrue: VimResume should be triggerd next. +/// When kTrue: VimResume should be triggered next. /// When kNone: Currently triggering VimSuspend or VimResume. static TriState pending_vimresume = kFalse; diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 8eacec4d5e..d2a5eab0a5 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -747,6 +747,7 @@ void buf_clear_file(buf_T *buf) void buf_clear(void) { linenr_T line_count = curbuf->b_ml.ml_line_count; + extmark_free_all(curbuf); // delete any extmarks while (!(curbuf->b_ml.ml_flags & ML_EMPTY)) { ml_delete((linenr_T)1, false); } diff --git a/src/nvim/bufwrite.c b/src/nvim/bufwrite.c index 2796ed1f0b..445e946543 100644 --- a/src/nvim/bufwrite.c +++ b/src/nvim/bufwrite.c @@ -1402,7 +1402,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en while ((fd = os_open(wfname, fflags, mode)) < 0) { // A forced write will try to create a new file if the old one // is still readonly. This may also happen when the directory - // is read-only. In that case the mch_remove() will fail. + // is read-only. In that case the os_remove() will fail. if (err.msg == NULL) { #ifdef UNIX FileInfo file_info; diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index d9d1417d2a..f4ca31040a 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -158,7 +158,7 @@ Decoration *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id) MarkTreeIter itr[1] = { 0 }; marktree_itr_get(buf->b_marktree, row, 0, itr); while (true) { - mtkey_t mark = marktree_itr_current(itr); + MTKey mark = marktree_itr_current(itr); if (mark.pos.row < 0 || mark.pos.row > row) { break; } else if (marktree_decor_level(mark) < kDecorLevelVisible) { @@ -189,7 +189,7 @@ bool decor_redraw_reset(win_T *wp, DecorState *state) return wp->w_buffer->b_marktree->n_keys; } -Decoration get_decor(mtkey_t mark) +Decoration get_decor(MTKey mark) { if (mark.decor_full) { return *mark.decor_full; @@ -211,50 +211,20 @@ bool decor_redraw_start(win_T *wp, int top_row, DecorState *state) { buf_T *buf = wp->w_buffer; state->top_row = top_row; - marktree_itr_get(buf->b_marktree, top_row, 0, state->itr); - if (!state->itr->node) { + if (!marktree_itr_get_overlap(buf->b_marktree, top_row, 0, state->itr)) { return false; } - marktree_itr_rewind(buf->b_marktree, state->itr); - while (true) { - mtkey_t mark = marktree_itr_current(state->itr); - if (mark.pos.row < 0) { // || mark.row > end_row - break; - } - if ((mark.pos.row < top_row && mt_end(mark)) - || marktree_decor_level(mark) < kDecorLevelVisible) { - goto next_mark; - } - - Decoration decor = get_decor(mark); + MTPair pair; - mtpos_t altpos = marktree_get_altpos(buf->b_marktree, mark, NULL); - - // Exclude start marks if the end mark position is above the top row - // Exclude end marks if we have already added the start mark - if ((mt_start(mark) && altpos.row < top_row && !decor_virt_pos(&decor)) - || (mt_end(mark) && altpos.row >= top_row)) { - goto next_mark; + while (marktree_itr_step_overlap(buf->b_marktree, state->itr, &pair)) { + if (marktree_decor_level(pair.start) < kDecorLevelVisible) { + continue; } - if (mt_end(mark)) { - decor_add(state, altpos.row, altpos.col, mark.pos.row, mark.pos.col, - &decor, false, mark.ns, mark.id); - } else { - if (altpos.row == -1) { - altpos.row = mark.pos.row; - altpos.col = mark.pos.col; - } - decor_add(state, mark.pos.row, mark.pos.col, altpos.row, altpos.col, - &decor, false, mark.ns, mark.id); - } + Decoration decor = get_decor(pair.start); -next_mark: - if (marktree_itr_node_done(state->itr)) { - marktree_itr_next(buf->b_marktree, state->itr); - break; - } - marktree_itr_next(buf->b_marktree, state->itr); + decor_add(state, pair.start.pos.row, pair.start.pos.col, pair.end_pos.row, pair.end_pos.col, + &decor, false, pair.start.ns, pair.start.id); } return true; // TODO(bfredl): check if available in the region @@ -268,7 +238,13 @@ bool decor_redraw_line(win_T *wp, int row, DecorState *state) state->row = row; state->col_until = -1; state->eol_col = -1; - return true; // TODO(bfredl): be more precise + + if (kv_size(state->active)) { + return true; + } + + MTKey k = marktree_itr_current(state->itr); + return (k.pos.row >= 0 && k.pos.row <= row); } static void decor_add(DecorState *state, int start_row, int start_col, int end_row, int end_col, @@ -292,6 +268,28 @@ static void decor_add(DecorState *state, int start_row, int start_col, int end_r kv_A(state->active, index) = range; } +/// Initialize the draw_col of a newly-added non-inline virtual text item. +static void decor_init_draw_col(int win_col, bool hidden, DecorRange *item) +{ + if (win_col < 0) { + item->draw_col = win_col; + } else if (item->decor.virt_text_pos == kVTOverlay) { + item->draw_col = (item->decor.virt_text_hide && hidden) ? INT_MIN : win_col; + } else { + item->draw_col = -1; + } +} + +void decor_recheck_draw_col(int win_col, bool hidden, DecorState *state) +{ + for (size_t i = 0; i < kv_size(state->active); i++) { + DecorRange *item = &kv_A(state->active, i); + if (item->draw_col == -3) { + decor_init_draw_col(win_col, hidden, item); + } + } +} + int decor_redraw_col(win_T *wp, int col, int win_col, bool hidden, DecorState *state) { buf_T *buf = wp->w_buffer; @@ -302,7 +300,7 @@ int decor_redraw_col(win_T *wp, int col, int win_col, bool hidden, DecorState *s while (true) { // TODO(bfredl): check duplicate entry in "intersection" // branch - mtkey_t mark = marktree_itr_current(state->itr); + MTKey mark = marktree_itr_current(state->itr); if (mark.pos.row < 0 || mark.pos.row > state->row) { break; } else if (mark.pos.row == state->row && mark.pos.col > col) { @@ -317,8 +315,7 @@ int decor_redraw_col(win_T *wp, int col, int win_col, bool hidden, DecorState *s Decoration decor = get_decor(mark); - mtpos_t endpos = marktree_get_altpos(buf->b_marktree, mark, NULL); - + MTPos endpos = marktree_get_altpos(buf->b_marktree, mark, NULL); if (endpos.row == -1) { endpos = mark.pos; } @@ -373,19 +370,10 @@ next_mark: if (active && item.decor.spell != kNone) { spell = item.decor.spell; } - if (item.start_row == state->row && decor_virt_pos(&item.decor) - && item.draw_col != INT_MIN) { - if (item.start_col <= col) { - if (item.decor.virt_text_pos == kVTOverlay && item.draw_col == -1) { - item.draw_col = (item.decor.virt_text_hide && hidden) ? INT_MIN : win_col; - } else if (item.draw_col == -3) { - item.draw_col = -1; - } - } else if (wp->w_p_wrap - && (item.decor.virt_text_pos == kVTRightAlign - || item.decor.virt_text_pos == kVTWinCol)) { - item.draw_col = -3; - } + if (item.start_row == state->row && item.start_col <= col + && decor_virt_pos(&item.decor) && item.draw_col == -1 + && item.decor.virt_text_pos != kVTInline) { + decor_init_draw_col(win_col, hidden, &item); } if (keep) { kv_A(state->active, j++) = item; @@ -412,8 +400,28 @@ void decor_redraw_signs(buf_T *buf, int row, int *num_signs, SignTextAttrs sattr MarkTreeIter itr[1] = { 0 }; marktree_itr_get(buf->b_marktree, row, 0, itr); + // TODO(bfredl): integrate with main decor loop. + if (!marktree_itr_get_overlap(buf->b_marktree, row, 0, itr)) { + return; + } + + MTPair pair; + while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) { + if (marktree_decor_level(pair.start) < kDecorLevelVisible) { + continue; + } + + Decoration *decor = pair.start.decor_full; + + if (!decor || !decor_has_sign(decor)) { + continue; + } + + decor_to_sign(decor, num_signs, sattrs, num_id, line_id, cul_id); + } + while (true) { - mtkey_t mark = marktree_itr_current(itr); + MTKey mark = marktree_itr_current(itr); if (mark.pos.row < 0 || mark.pos.row > row) { break; } @@ -428,43 +436,49 @@ void decor_redraw_signs(buf_T *buf, int row, int *num_signs, SignTextAttrs sattr goto next_mark; } - if (decor->sign_text) { - int j; - for (j = (*num_signs); j > 0; j--) { - if (sattrs[j - 1].priority >= decor->priority) { - break; - } - if (j < SIGN_SHOW_MAX) { - sattrs[j] = sattrs[j - 1]; - } + decor_to_sign(decor, num_signs, sattrs, num_id, line_id, cul_id); + +next_mark: + marktree_itr_next(buf->b_marktree, itr); + } +} + +static void decor_to_sign(Decoration *decor, int *num_signs, SignTextAttrs sattrs[], + HlPriId *num_id, HlPriId *line_id, HlPriId *cul_id) +{ + if (decor->sign_text) { + int j; + for (j = (*num_signs); j > 0; j--) { + if (sattrs[j - 1].priority >= decor->priority) { + break; } if (j < SIGN_SHOW_MAX) { - sattrs[j] = (SignTextAttrs) { - .text = decor->sign_text, - .hl_id = decor->sign_hl_id, - .priority = decor->priority - }; - (*num_signs)++; + sattrs[j] = sattrs[j - 1]; } } - - struct { HlPriId *dest; int hl; } cattrs[] = { - { line_id, decor->line_hl_id }, - { num_id, decor->number_hl_id }, - { cul_id, decor->cursorline_hl_id }, - { NULL, -1 }, - }; - for (int i = 0; cattrs[i].dest; i++) { - if (cattrs[i].hl != 0 && decor->priority >= cattrs[i].dest->priority) { - *cattrs[i].dest = (HlPriId) { - .hl_id = cattrs[i].hl, - .priority = decor->priority - }; - } + if (j < SIGN_SHOW_MAX) { + sattrs[j] = (SignTextAttrs) { + .text = decor->sign_text, + .hl_id = decor->sign_hl_id, + .priority = decor->priority + }; + (*num_signs)++; } + } -next_mark: - marktree_itr_next(buf->b_marktree, itr); + struct { HlPriId *dest; int hl; } cattrs[] = { + { line_id, decor->line_hl_id }, + { num_id, decor->number_hl_id }, + { cul_id, decor->cursorline_hl_id }, + { NULL, -1 }, + }; + for (int i = 0; cattrs[i].dest; i++) { + if (cattrs[i].hl != 0 && decor->priority >= cattrs[i].dest->priority) { + *cattrs[i].dest = (HlPriId) { + .hl_id = cattrs[i].hl, + .priority = decor->priority + }; + } } } @@ -488,7 +502,7 @@ int decor_signcols(buf_T *buf, DecorState *state, int row, int end_row, int max) MarkTreeIter itr[1] = { 0 }; marktree_itr_get(buf->b_marktree, 0, -1, itr); while (true) { - mtkey_t mark = marktree_itr_current(itr); + MTKey mark = marktree_itr_current(itr); if (mark.pos.row < 0 || mark.pos.row > end_row) { break; } @@ -525,7 +539,7 @@ int decor_signcols(buf_T *buf, DecorState *state, int row, int end_row, int max) goto next_mark; } - mtpos_t altpos = marktree_get_altpos(buf->b_marktree, mark, NULL); + MTPos altpos = marktree_get_altpos(buf->b_marktree, mark, NULL); if (mt_end(mark)) { if (mark.pos.row >= row && altpos.row <= end_row) { @@ -610,7 +624,7 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines, TriState has_fo MarkTreeIter itr[1] = { 0 }; marktree_itr_get(buf->b_marktree, start_row, 0, itr); while (true) { - mtkey_t mark = marktree_itr_current(itr); + MTKey mark = marktree_itr_current(itr); if (mark.pos.row < 0 || mark.pos.row >= end_row) { break; } else if (mt_end(mark) diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index 3d16aa803e..0f191aa870 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -84,7 +84,7 @@ typedef struct { bool virt_text_owned; /// Screen column to draw the virtual text. /// When -1, the virtual text may be drawn after deciding where. - /// When -3, the virtual text should be drawn on a later screen line. + /// When -3, the virtual text should be drawn on the next screen line. /// When INT_MIN, the virtual text should no longer be drawn. int draw_col; uint64_t ns_id; diff --git a/src/nvim/decoration_provider.c b/src/nvim/decoration_provider.c index 8e5809c4e0..63c9772fb8 100644 --- a/src/nvim/decoration_provider.c +++ b/src/nvim/decoration_provider.c @@ -126,9 +126,10 @@ void decor_providers_invoke_win(win_T *wp, DecorProviders *providers, { kvi_init(*line_providers); - linenr_T knownmax = ((wp->w_valid & VALID_BOTLINE) - ? wp->w_botline - : (wp->w_topline + wp->w_height_inner)); + linenr_T knownmax = MIN(wp->w_buffer->b_ml.ml_line_count, + ((wp->w_valid & VALID_BOTLINE) + ? wp->w_botline + : MAX(wp->w_topline + wp->w_height_inner, wp->w_botline))); for (size_t k = 0; k < kv_size(*providers); k++) { DecorProvider *p = kv_A(*providers, k); @@ -138,7 +139,7 @@ void decor_providers_invoke_win(win_T *wp, DecorProviders *providers, ADD_C(args, BUFFER_OBJ(wp->w_buffer->handle)); // TODO(bfredl): we are not using this, but should be first drawn line? ADD_C(args, INTEGER_OBJ(wp->w_topline - 1)); - ADD_C(args, INTEGER_OBJ(knownmax)); + ADD_C(args, INTEGER_OBJ(knownmax - 1)); if (decor_provider_invoke(p, "win", p->redraw_win, args, true)) { kvi_push(*line_providers, p); } diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 4b989fa59a..e1550e0ece 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -136,8 +136,6 @@ typedef struct { ///< or w_skipcol or concealing int skipped_cells; ///< nr of skipped cells for virtual text ///< to be added to wlv.vcol later - bool more_virt_inline_chunks; ///< indicates if there is more inline virtual text - ///< after n_extra } winlinevars_T; /// for line_putchar. Contains the state that needs to be remembered from @@ -868,10 +866,12 @@ static void apply_cursorline_highlight(win_T *wp, winlinevars_T *wlv) } } -/// Checks if there is more inline virtual text that need to be drawn -/// and sets has_more_virt_inline_chunks to reflect that. +/// Checks if there is more inline virtual text that need to be drawn. static bool has_more_inline_virt(winlinevars_T *wlv, ptrdiff_t v) { + if (wlv->virt_inline_i < kv_size(wlv->virt_inline)) { + return true; + } DecorState *state = &decor_state; for (size_t i = 0; i < kv_size(state->active); i++) { DecorRange *item = &kv_A(state->active, i); @@ -911,7 +911,6 @@ static void handle_inline_virtual_text(win_T *wp, winlinevars_T *wlv, ptrdiff_t break; } } - wlv->more_virt_inline_chunks = has_more_inline_virt(wlv, v); if (!kv_size(wlv->virt_inline)) { // no more inline virtual text here break; @@ -929,11 +928,6 @@ static void handle_inline_virtual_text(win_T *wp, winlinevars_T *wlv, ptrdiff_t wlv->c_final = NUL; wlv->extra_attr = vtc.hl_id ? syn_id2attr(vtc.hl_id) : 0; wlv->n_attr = mb_charlen(vtc.text); - - // Checks if there is more inline virtual text chunks that need to be drawn. - wlv->more_virt_inline_chunks = has_more_inline_virt(wlv, v) - || wlv->virt_inline_i < kv_size(wlv->virt_inline); - // If the text didn't reach until the first window // column we need to skip cells. if (wlv->skip_cells > 0) { @@ -1147,6 +1141,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl bool saved_search_attr_from_match = false; int win_col_offset = 0; // offset for window columns + bool area_active = false; // whether in Visual selection, for virtual text + bool decor_need_recheck = false; // call decor_recheck_draw_col() at next char char buf_fold[FOLD_TEXT_LEN]; // Hold value returned by get_foldtext @@ -1788,9 +1784,27 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl } if (has_decor && wlv.n_extra == 0) { - bool selected = (area_highlighting - && ((wlv.vcol >= wlv.fromcol && wlv.vcol < wlv.tocol) - || (noinvcur && wlv.vcol == wp->w_virtcol))); + // Duplicate the Visual area check after this block, + // but don't check inside p_extra here. + if (wlv.vcol == wlv.fromcol + || (wlv.vcol + 1 == wlv.fromcol + && (wlv.n_extra == 0 && utf_ptr2cells(ptr) > 1)) + || (vcol_prev == fromcol_prev + && vcol_prev < wlv.vcol + && wlv.vcol < wlv.tocol)) { + area_active = true; + } else if (area_active + && (wlv.vcol == wlv.tocol + || (noinvcur && wlv.vcol == wp->w_virtcol))) { + area_active = false; + } + + bool selected = (area_active || (area_highlighting && noinvcur + && wlv.vcol == wp->w_virtcol)); + if (decor_need_recheck) { + decor_recheck_draw_col(wlv.off, selected, &decor_state); + decor_need_recheck = false; + } extmark_attr = decor_redraw_col(wp, (colnr_T)v, wlv.off, selected, &decor_state); if (!has_fold && wp->w_buffer->b_virt_text_inline > 0) { @@ -1824,10 +1838,12 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl && vcol_prev < wlv.vcol // not at margin && wlv.vcol < wlv.tocol)) { *area_attr_p = vi_attr; // start highlighting + area_active = true; } else if (*area_attr_p != 0 && (wlv.vcol == wlv.tocol || (noinvcur && wlv.vcol == wp->w_virtcol))) { *area_attr_p = 0; // stop highlighting + area_active = false; } if (!has_fold && wlv.n_extra == 0) { @@ -2889,15 +2905,20 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl && !wp->w_p_wrap && wlv.filler_todo <= 0 && (wp->w_p_rl ? wlv.col == 0 : wlv.col == grid->cols - 1) - && !has_fold - && (*ptr != NUL - || lcs_eol_one > 0 - || (wlv.n_extra > 0 && (wlv.c_extra != NUL || *wlv.p_extra != NUL)) - || wlv.more_virt_inline_chunks)) { - c = wp->w_p_lcs_chars.ext; - wlv.char_attr = win_hl_attr(wp, HLF_AT); - mb_c = c; - mb_utf8 = check_mb_utf8(&c, u8cc); + && !has_fold) { + if (has_decor && *ptr == NUL && lcs_eol_one == 0) { + // Tricky: there might be a virtual text just _after_ the last char + decor_redraw_col(wp, (colnr_T)v, wlv.off, false, &decor_state); + } + if (*ptr != NUL + || lcs_eol_one > 0 + || (wlv.n_extra > 0 && (wlv.c_extra != NUL || *wlv.p_extra != NUL)) + || has_more_inline_virt(&wlv, v)) { + c = wp->w_p_lcs_chars.ext; + wlv.char_attr = win_hl_attr(wp, HLF_AT); + mb_c = c; + mb_utf8 = check_mb_utf8(&c, u8cc); + } } // advance to the next 'colorcolumn' @@ -3079,6 +3100,21 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl wlv.char_attr = saved_attr2; } + if (has_decor && wlv.filler_todo <= 0 + && (wp->w_p_rl ? (wlv.col < 0) : (wlv.col >= grid->cols))) { + // At the end of screen line: might need to peek for decorations just after + // this position. + if (!has_fold && wp->w_p_wrap && wlv.n_extra == 0) { + decor_redraw_col(wp, (int)(ptr - line), -3, false, &decor_state); + // Check position/hiding of virtual text again on next screen line. + decor_need_recheck = true; + } else if (has_fold || !wp->w_p_wrap) { + // Without wrapping, we might need to display right_align and win_col + // virt_text for the entire text line. + decor_redraw_col(wp, MAXCOL, -1, true, &decor_state); + } + } + // At end of screen line and there is more to come: Display the line // so far. If there is no more to display it is caught above. if ((wp->w_p_rl ? (wlv.col < 0) : (wlv.col >= grid->cols)) @@ -3089,7 +3125,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl || (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL && wlv.p_extra != at_end_str) || (wlv.n_extra != 0 && (wlv.c_extra != NUL || *wlv.p_extra != NUL)) - || wlv.more_virt_inline_chunks)) { + || has_more_inline_virt(&wlv, v))) { bool wrap = wp->w_p_wrap // Wrapping enabled. && wlv.filler_todo <= 0 // Not drawing diff filler lines. && lcs_eol_one != -1 // Haven't printed the lcs_eol character. @@ -3102,7 +3138,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl if (virt_line_offset >= 0) { draw_virt_text_item(buf, virt_line_offset, kv_A(virt_lines, virt_line_index).line, kHlModeReplace, grid->cols, 0); - } else { + } else if (wlv.filler_todo <= 0) { draw_virt_text(wp, buf, win_col_offset, &draw_col, grid->cols, wlv.row); } diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 06eb81be92..216f8a67db 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -4429,9 +4429,8 @@ static bool ins_tab(void) } } if (!(State & VREPLACE_FLAG)) { - extmark_splice_cols(curbuf, (int)fpos.lnum - 1, change_col, - cursor->col - change_col, fpos.col - change_col, - kExtmarkUndo); + inserted_bytes(fpos.lnum, change_col, + cursor->col - change_col, fpos.col - change_col); } } cursor->col -= i; diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index a9200d1f5f..e6efbe85d8 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -7472,7 +7472,7 @@ M.funcs = { *printf-$* In certain languages, error and informative messages are more readable when the order of words is different from the - corresponding message in English. To accomodate translations + corresponding message in English. To accommodate translations having a different word order, positional arguments may be used to indicate this. For instance: >vim diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 1ed0994222..93075cf0e2 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -6329,7 +6329,7 @@ static void reduce_string(typval_T *argvars, typval_T *expr, typval_T *rettv) } } -/// Implementaion of reduce() for Blob "argvars[0]" using the function "expr" +/// Implementation of reduce() for Blob "argvars[0]" using the function "expr" /// starting with the optional initial value "argvars[2]" and return the result /// in "rettv". static void reduce_blob(typval_T *argvars, typval_T *expr, typval_T *rettv) diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index 9b6427fef7..e5b1b88eef 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -1810,7 +1810,7 @@ static void getwinvar(typval_T *argvars, typval_T *rettv, int off) /// @param[in] tv typval to convert. /// @param[in] option Option name. /// @param[in] flags Option flags. -/// @param[out] error Whether an error occured. +/// @param[out] error Whether an error occurred. /// /// @return Typval converted to OptVal. Must be freed by caller. /// Returns NIL_OPTVAL for invalid option name. diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index a0618ce7d7..4f6b8f2c8f 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -3888,6 +3888,10 @@ static int do_sub(exarg_T *eap, const proftime_T timeout, const long cmdpreview_ nmatch = curbuf->b_ml.ml_line_count - sub_firstlnum + 1; current_match.end.lnum = sub_firstlnum + (linenr_T)nmatch; skip_match = true; + // safety check + if (nmatch < 0) { + goto skip; + } } // Save the line numbers for the preview buffer diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 08b010c153..09781f392a 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -2410,6 +2410,9 @@ static void cmdpreview_restore_state(CpInfo *cpinfo) buf->b_changed = cp_bufinfo.save_b_changed; + // Clear preview highlights. + extmark_clear(buf, (uint32_t)cmdpreview_ns, 0, 0, MAXLNUM, MAXCOL); + if (buf->b_u_seq_cur != cp_bufinfo.undo_info.save_b_u_seq_cur) { int count = 0; @@ -2439,9 +2442,6 @@ static void cmdpreview_restore_state(CpInfo *cpinfo) } buf->b_p_ul = cp_bufinfo.save_b_p_ul; // Restore 'undolevels' - - // Clear preview highlights. - extmark_clear(buf, (uint32_t)cmdpreview_ns, 0, 0, MAXLNUM, MAXCOL); } for (size_t i = 0; i < cpinfo->win_info.size; i++) { diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index 77e41cb5cf..5140fe199e 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -82,7 +82,7 @@ void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col id = ++*ns; } else { MarkTreeIter itr[1] = { 0 }; - mtkey_t old_mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr); + MTKey old_mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr); if (old_mark.id) { if (decor_state.running_on_lines) { if (err) { @@ -124,8 +124,8 @@ void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col } } - mtkey_t mark = { { row, col }, ns_id, id, 0, - mt_flags(right_gravity, decor_level), 0, NULL }; + MTKey mark = { { row, col }, ns_id, id, 0, + mt_flags(right_gravity, decor_level), 0, NULL }; if (decor_full) { mark.decor_full = decor; } else if (decor) { @@ -180,7 +180,7 @@ error: static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col) { MarkTreeIter itr[1] = { 0 }; - mtkey_t key = marktree_lookup(buf->b_marktree, mark, itr); + MTKey key = marktree_lookup(buf->b_marktree, mark, itr); if (key.pos.row == -1) { return false; } @@ -199,14 +199,14 @@ static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col) bool extmark_del(buf_T *buf, uint32_t ns_id, uint32_t id) { MarkTreeIter itr[1] = { 0 }; - mtkey_t key = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr); + MTKey key = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr); if (!key.id) { return false; } assert(key.pos.row >= 0); uint64_t other = marktree_del_itr(buf->b_marktree, itr, false); - mtkey_t key2 = key; + MTKey key2 = key; if (other) { key2 = marktree_lookup(buf->b_marktree, other, itr); @@ -250,7 +250,7 @@ bool extmark_clear(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_r MarkTreeIter itr[1] = { 0 }; marktree_itr_get(buf->b_marktree, l_row, l_col, itr); while (true) { - mtkey_t mark = marktree_itr_current(itr); + MTKey mark = marktree_itr_current(itr); if (mark.pos.row < 0 || mark.pos.row > u_row || (mark.pos.row == u_row && mark.pos.col > u_col)) { @@ -292,7 +292,7 @@ bool extmark_clear(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_r uint64_t id; ssize_t decor_id; map_foreach(&delete_set, id, decor_id, { - mtkey_t mark = marktree_lookup(buf->b_marktree, id, itr); + MTKey mark = marktree_lookup(buf->b_marktree, id, itr); assert(marktree_itr_valid(itr)); marktree_del_itr(buf->b_marktree, itr, false); if (decor_id >= 0) { @@ -313,17 +313,31 @@ bool extmark_clear(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_r /// dir can be set to control the order of the array /// amount = amount of marks to find or -1 for all ExtmarkInfoArray extmark_get(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_row, - colnr_T u_col, int64_t amount, bool reverse, bool all_ns, - ExtmarkType type_filter) + colnr_T u_col, int64_t amount, bool reverse, ExtmarkType type_filter, + bool overlap) { ExtmarkInfoArray array = KV_INITIAL_VALUE; MarkTreeIter itr[1]; - // Find all the marks - marktree_itr_get_ext(buf->b_marktree, mtpos_t(l_row, l_col), - itr, reverse, false, NULL); + + if (overlap) { + // Find all the marks overlapping the start position + if (!marktree_itr_get_overlap(buf->b_marktree, l_row, l_col, itr)) { + return array; + } + + MTPair pair; + while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) { + push_mark(&array, ns_id, type_filter, pair.start, pair.end_pos, pair.end_right_gravity); + } + } else { + // Find all the marks beginning with the start position + marktree_itr_get_ext(buf->b_marktree, MTPos(l_row, l_col), + itr, reverse, false, NULL); + } + int order = reverse ? -1 : 1; while ((int64_t)kv_size(array) < amount) { - mtkey_t mark = marktree_itr_current(itr); + MTKey mark = marktree_itr_current(itr); if (mark.pos.row < 0 || (mark.pos.row - u_row) * order > 0 || (mark.pos.row == u_row && (mark.pos.col - u_col) * order > 0)) { @@ -333,35 +347,8 @@ ExtmarkInfoArray extmark_get(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_co goto next_mark; } - uint16_t type_flags = kExtmarkNone; - if (type_filter != kExtmarkNone) { - Decoration *decor = mark.decor_full; - if (decor && (decor->sign_text || decor->number_hl_id)) { - type_flags |= kExtmarkSign; - } - if (decor && decor->virt_text.size) { - type_flags |= kExtmarkVirtText; - } - if (decor && decor->virt_lines.size) { - type_flags |= kExtmarkVirtLines; - } - if ((decor && (decor->line_hl_id || decor->cursorline_hl_id)) - || mark.hl_id) { - type_flags |= kExtmarkHighlight; - } - } - - if ((all_ns || mark.ns == ns_id) && type_flags & type_filter) { - mtkey_t end = marktree_get_alt(buf->b_marktree, mark, NULL); - kv_push(array, ((ExtmarkInfo) { .ns_id = mark.ns, - .mark_id = mark.id, - .row = mark.pos.row, .col = mark.pos.col, - .end_row = end.pos.row, - .end_col = end.pos.col, - .right_gravity = mt_right(mark), - .end_right_gravity = mt_right(end), - .decor = get_decor(mark) })); - } + MTKey end = marktree_get_alt(buf->b_marktree, mark, NULL); + push_mark(&array, ns_id, type_filter, mark, end.pos, mt_right(end)); next_mark: if (reverse) { marktree_itr_prev(buf->b_marktree, itr); @@ -372,16 +359,54 @@ next_mark: return array; } +static void push_mark(ExtmarkInfoArray *array, uint32_t ns_id, ExtmarkType type_filter, MTKey mark, + MTPos end_pos, bool end_right) +{ + if (!(ns_id == UINT32_MAX || mark.ns == ns_id)) { + return; + } + uint16_t type_flags = kExtmarkNone; + if (type_filter != kExtmarkNone) { + Decoration *decor = mark.decor_full; + if (decor && (decor->sign_text || decor->number_hl_id)) { + type_flags |= kExtmarkSign; + } + if (decor && decor->virt_text.size) { + type_flags |= kExtmarkVirtText; + } + if (decor && decor->virt_lines.size) { + type_flags |= kExtmarkVirtLines; + } + if ((decor && (decor->line_hl_id || decor->cursorline_hl_id)) + || mark.hl_id) { + type_flags |= kExtmarkHighlight; + } + + if (!(type_flags & type_filter)) { + return; + } + } + + kv_push(*array, ((ExtmarkInfo) { .ns_id = mark.ns, + .mark_id = mark.id, + .row = mark.pos.row, .col = mark.pos.col, + .end_row = end_pos.row, + .end_col = end_pos.col, + .right_gravity = mt_right(mark), + .end_right_gravity = end_right, + .decor = get_decor(mark) })); +} + /// Lookup an extmark by id ExtmarkInfo extmark_from_id(buf_T *buf, uint32_t ns_id, uint32_t id) { ExtmarkInfo ret = { 0, 0, -1, -1, -1, -1, false, false, DECORATION_INIT }; - mtkey_t mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, NULL); + MTKey mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, NULL); if (!mark.id) { return ret; } assert(mark.pos.row >= 0); - mtkey_t end = marktree_get_alt(buf->b_marktree, mark, NULL); + MTKey end = marktree_get_alt(buf->b_marktree, mark, NULL); ret.ns_id = ns_id; ret.mark_id = id; @@ -406,7 +431,7 @@ void extmark_free_all(buf_T *buf) MarkTreeIter itr[1] = { 0 }; marktree_itr_get(buf->b_marktree, 0, 0, itr); while (true) { - mtkey_t mark = marktree_itr_current(itr); + MTKey mark = marktree_itr_current(itr); if (mark.pos.row < 0) { break; } @@ -462,7 +487,7 @@ void u_extmark_copy(buf_T *buf, int l_row, colnr_T l_col, int u_row, colnr_T u_c MarkTreeIter itr[1] = { 0 }; marktree_itr_get(buf->b_marktree, (int32_t)l_row, l_col, itr); while (true) { - mtkey_t mark = marktree_itr_current(itr); + MTKey mark = marktree_itr_current(itr); if (mark.pos.row < 0 || mark.pos.row > u_row || (mark.pos.row == u_row && mark.pos.col > u_col)) { diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h index aeaadea73c..4ad7d4cdb4 100644 --- a/src/nvim/grid_defs.h +++ b/src/nvim/grid_defs.h @@ -39,7 +39,7 @@ enum { /// /// attrs[] contains the highlighting attribute for each cell. /// -/// vcols[] countain the virtual columns in the line. -1 means not available +/// vcols[] contains the virtual columns in the line. -1 means not available /// (below last line), MAXCOL means after the end of the line. /// /// line_offset[n] is the offset from chars[], attrs[] and vcols[] for the start diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 29e5db7a96..df4bffdac3 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -40,13 +40,13 @@ static bool hlstate_active = false; -static kvec_t(HlEntry) attr_entries = KV_INITIAL_VALUE; - -static Map(HlEntry, int) attr_entry_ids = MAP_INIT; +static Set(HlEntry) attr_entries = SET_INIT; static Map(int, int) combine_attr_entries = MAP_INIT; static Map(int, int) blend_attr_entries = MAP_INIT; static Map(int, int) blendthrough_attr_entries = MAP_INIT; +#define attr_entry(i) attr_entries.keys[i] + /// highlight entries private to a namespace static Map(ColorKey, ColorItem) ns_hls; typedef int NSHlAttr[HLF_COUNT + 1]; @@ -55,8 +55,8 @@ static PMap(int) ns_hl_attr; void highlight_init(void) { // index 0 is no attribute, add dummy entry: - kv_push(attr_entries, ((HlEntry){ .attr = HLATTRS_INIT, .kind = kHlUnknown, - .id1 = 0, .id2 = 0 })); + set_put(HlEntry, &attr_entries, ((HlEntry){ .attr = HLATTRS_INIT, .kind = kHlInvalid, + .id1 = 0, .id2 = 0 })); } /// @return true if hl table was reset @@ -77,6 +77,7 @@ bool highlight_use_hlstate(void) /// @return 0 for error. static int get_attr_entry(HlEntry entry) { + bool retried = false; if (!hlstate_active) { // This information will not be used, erase it and reduce the table size. entry.kind = kHlUnknown; @@ -84,17 +85,19 @@ static int get_attr_entry(HlEntry entry) entry.id2 = 0; } - int id = map_get(HlEntry, int)(&attr_entry_ids, entry); - if (id > 0) { - return id; +retry: {} + MhPutStatus status; + uint32_t k = set_put_idx(HlEntry, &attr_entries, entry, &status); + if (status == kMHExisting) { + return (int)k; } static bool recursive = false; - if (kv_size(attr_entries) > MAX_TYPENR) { + if (set_size(&attr_entries) > MAX_TYPENR) { // Running out of attribute entries! remove all attributes, and // compute new ones for all groups. // When called recursively, we are really out of numbers. - if (recursive) { + if (recursive || retried) { emsg(_("E424: Too many different highlighting attributes in use")); return 0; } @@ -107,17 +110,12 @@ static int get_attr_entry(HlEntry entry) // This entry is now invalid, don't put it return 0; } + retried = true; + goto retry; } - size_t next_id = kv_size(attr_entries); - if (next_id > INT_MAX) { - ELOG("The index on attr_entries has overflowed"); - return 0; - } - id = (int)next_id; - kv_push(attr_entries, entry); - - map_put(HlEntry, int)(&attr_entry_ids, entry, id); + // new attr id, send event to remote ui:s + int id = (int)k; Array inspect = hl_inspect(id); @@ -131,10 +129,10 @@ static int get_attr_entry(HlEntry entry) /// When a UI connects, we need to send it the table of highlights used so far. void ui_send_all_hls(UI *ui) { - for (size_t i = 1; i < kv_size(attr_entries); i++) { + for (size_t i = 1; i < set_size(&attr_entries); i++) { Array inspect = hl_inspect((int)i); - remote_ui_hl_attr_define(ui, (Integer)i, kv_A(attr_entries, i).attr, - kv_A(attr_entries, i).attr, inspect); + HlAttrs attr = attr_entry(i).attr; + remote_ui_hl_attr_define(ui, (Integer)i, attr, attr, inspect); api_free_array(inspect); } for (size_t hlf = 0; hlf < HLF_COUNT; hlf++) { @@ -364,7 +362,7 @@ void update_window_hl(win_T *wp, bool invalid) // if blend= attribute is not set, 'winblend' value overrides it. if (wp->w_floating && wp->w_p_winbl > 0) { - HlEntry entry = kv_A(attr_entries, wp->w_hl_attr_normal); + HlEntry entry = attr_entry(wp->w_hl_attr_normal); if (entry.attr.hl_blend == -1) { entry.attr.hl_blend = (int)wp->w_p_winbl; wp->w_hl_attr_normal = get_attr_entry(entry); @@ -401,7 +399,7 @@ void update_window_hl(win_T *wp, bool invalid) // if blend= attribute is not set, 'winblend' value overrides it. if (wp->w_floating && wp->w_p_winbl > 0) { - HlEntry entry = kv_A(attr_entries, wp->w_hl_attr_normalnc); + HlEntry entry = attr_entry(wp->w_hl_attr_normalnc); if (entry.attr.hl_blend == -1) { entry.attr.hl_blend = (int)wp->w_p_winbl; wp->w_hl_attr_normalnc = get_attr_entry(entry); @@ -490,8 +488,8 @@ int hl_get_term_attr(HlAttrs *aep) void clear_hl_tables(bool reinit) { if (reinit) { - kv_size(attr_entries) = 1; - map_clear(HlEntry, &attr_entry_ids); + set_clear(HlEntry, &attr_entries); + highlight_init(); map_clear(int, &combine_attr_entries); map_clear(int, &blend_attr_entries); map_clear(int, &blendthrough_attr_entries); @@ -500,8 +498,7 @@ void clear_hl_tables(bool reinit) highlight_changed(); screen_invalidate_highlights(); } else { - kv_destroy(attr_entries); - map_destroy(HlEntry, &attr_entry_ids); + set_destroy(HlEntry, &attr_entries); map_destroy(int, &combine_attr_entries); map_destroy(int, &blend_attr_entries); map_destroy(int, &blendthrough_attr_entries); @@ -809,11 +806,11 @@ static int hl_cterm2rgb_color(int nr) /// Get highlight attributes for a attribute code HlAttrs syn_attr2entry(int attr) { - if (attr <= 0 || attr >= (int)kv_size(attr_entries)) { + if (attr <= 0 || attr >= (int)set_size(&attr_entries)) { // invalid attribute code, or the tables were cleared return HLATTRS_INIT; } - return kv_A(attr_entries, attr).attr; + return attr_entry(attr).attr; } /// Gets highlight description for id `attr_id` as a map. @@ -825,7 +822,7 @@ Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Arena *arena, Error * return dic; } - if (attr_id <= 0 || attr_id >= (int)kv_size(attr_entries)) { + if (attr_id <= 0 || attr_id >= (int)set_size(&attr_entries)) { api_set_error(err, kErrorTypeException, "Invalid attribute id: %" PRId64, attr_id); return dic; @@ -1132,11 +1129,11 @@ Array hl_inspect(int attr) static void hl_inspect_impl(Array *arr, int attr) { Dictionary item = ARRAY_DICT_INIT; - if (attr <= 0 || attr >= (int)kv_size(attr_entries)) { + if (attr <= 0 || attr >= (int)set_size(&attr_entries)) { return; } - HlEntry e = kv_A(attr_entries, attr); + HlEntry e = attr_entry(attr); switch (e.kind) { case kHlSyntax: PUT(item, "kind", CSTR_TO_OBJ("syntax")); @@ -1165,6 +1162,7 @@ static void hl_inspect_impl(Array *arr, int attr) return; case kHlUnknown: + case kHlInvalid: return; } PUT(item, "id", INTEGER_OBJ(attr)); diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index f690b57148..ae58ff8696 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -219,6 +219,7 @@ typedef enum { kHlCombine, kHlBlend, kHlBlendThrough, + kHlInvalid, } HlKind; typedef struct { diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c index c4d140d1e1..b970e752bb 100644 --- a/src/nvim/highlight_group.c +++ b/src/nvim/highlight_group.c @@ -1569,7 +1569,13 @@ Dictionary ns_get_hl_defs(NS ns_id, Dict(get_highlight) *opts, Arena *arena, Err Boolean link = GET_BOOL_OR_TRUE(opts, get_highlight, link); int id = -1; if (HAS_KEY(opts, get_highlight, name)) { - id = syn_check_group(opts->name.data, opts->name.size); + Boolean create = GET_BOOL_OR_TRUE(opts, get_highlight, create); + id = create ? syn_check_group(opts->name.data, opts->name.size) + : syn_name2id_len(opts->name.data, opts->name.size); + if (id == 0 && !create) { + Dictionary attrs = ARRAY_DICT_INIT; + return attrs; + } } else if (HAS_KEY(opts, get_highlight, id)) { id = (int)opts->id; } diff --git a/src/nvim/keycodes.c b/src/nvim/keycodes.c index f2391cc541..55cd22f181 100644 --- a/src/nvim/keycodes.c +++ b/src/nvim/keycodes.c @@ -919,7 +919,8 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co // Check for special <> keycodes, like "<C-S-LeftMouse>" if (do_special && ((flags & REPTERM_DO_LT) || ((end - src) >= 3 && strncmp(src, "<lt>", 4) != 0))) { - // Replace <SID> by K_SNR <script-nr> _. + // Change <SID>Func to K_SNR <script-nr> _Func. This name is used + // for script-local user functions. // (room: 5 * 6 = 30 bytes; needed: 3 + <nr> + 1 <= 14) if (end - src >= 4 && STRNICMP(src, "<SID>", 5) == 0) { if (sid_arg < 0 || (sid_arg == 0 && current_sctx.sc_sid <= 0)) { diff --git a/src/nvim/macros.h b/src/nvim/macros.h index 57cb298572..5eaf97ff87 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -82,15 +82,6 @@ #define READBIN "rb" #define APPENDBIN "ab" -// mch_open_rw(): invoke os_open() with third argument for user R/W. -#if defined(UNIX) // open in rw------- mode -# define MCH_OPEN_RW(n, f) os_open((n), (f), (mode_t)0600) -#elif defined(MSWIN) -# define MCH_OPEN_RW(n, f) os_open((n), (f), S_IREAD | S_IWRITE) -#else -# define MCH_OPEN_RW(n, f) os_open((n), (f), 0) -#endif - #define REPLACE_NORMAL(s) (((s)& REPLACE_FLAG) && !((s)& VREPLACE_FLAG)) // MB_PTR_ADV(): advance a pointer to the next character, taking care of diff --git a/src/nvim/map.c b/src/nvim/map.c index e2c6443245..e1d0646083 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -25,6 +25,8 @@ #define equal_uint32_t equal_simple #define hash_int(x) hash_uint32_t((uint32_t)(x)) #define equal_int equal_simple +#define hash_int64_t(key) hash_uint64_t((uint64_t)key) +#define equal_int64_t equal_simple #if defined(ARCH_64) # define hash_ptr_t(key) hash_uint64_t((uint64_t)(key)) @@ -182,13 +184,20 @@ void mh_clear(MapHash *h) #undef VAL_NAME #undef KEY_NAME -#define KEY_NAME(x) x##HlEntry +#define KEY_NAME(x) x##int64_t #include "nvim/map_key_impl.c.h" -#define VAL_NAME(x) quasiquote(x, int) +#define VAL_NAME(x) quasiquote(x, ptr_t) +#include "nvim/map_value_impl.c.h" +#undef VAL_NAME +#define VAL_NAME(x) quasiquote(x, int64_t) #include "nvim/map_value_impl.c.h" #undef VAL_NAME #undef KEY_NAME +#define KEY_NAME(x) x##HlEntry +#include "nvim/map_key_impl.c.h" +#undef KEY_NAME + #define KEY_NAME(x) x##ColorKey #include "nvim/map_key_impl.c.h" #define VAL_NAME(x) quasiquote(x, ColorItem) diff --git a/src/nvim/map.h b/src/nvim/map.h index a84d533262..23a5ea36a3 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -27,6 +27,7 @@ static const ptr_t value_init_ptr_t = NULL; static const ssize_t value_init_ssize_t = -1; static const uint32_t value_init_uint32_t = 0; static const uint64_t value_init_uint64_t = 0; +static const int64_t value_init_int64_t = 0; static const String value_init_String = STRING_INIT; static const ColorItem value_init_ColorItem = COLOR_ITEM_INITIALIZER; @@ -123,6 +124,7 @@ KEY_DECLS(int) KEY_DECLS(cstr_t) KEY_DECLS(ptr_t) KEY_DECLS(uint64_t) +KEY_DECLS(int64_t) KEY_DECLS(uint32_t) KEY_DECLS(String) KEY_DECLS(HlEntry) @@ -137,8 +139,9 @@ MAP_DECLS(uint32_t, ptr_t) MAP_DECLS(uint64_t, ptr_t) MAP_DECLS(uint64_t, ssize_t) MAP_DECLS(uint64_t, uint64_t) +MAP_DECLS(int64_t, int64_t) +MAP_DECLS(int64_t, ptr_t) MAP_DECLS(uint32_t, uint32_t) -MAP_DECLS(HlEntry, int) MAP_DECLS(String, int) MAP_DECLS(int, String) MAP_DECLS(ColorKey, ColorItem) @@ -146,6 +149,7 @@ MAP_DECLS(ColorKey, ColorItem) #define set_has(T, set, key) set_has_##T(set, key) #define set_put(T, set, key) set_put_##T(set, key, NULL) #define set_put_ref(T, set, key, key_alloc) set_put_##T(set, key, key_alloc) +#define set_put_idx(T, set, key, status) mh_put_##T(set, key, status) #define set_del(T, set, key) set_del_##T(set, key) #define set_destroy(T, set) (xfree((set)->keys), xfree((set)->h.hash)) #define set_clear(T, set) mh_clear(&(set)->h) diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c index d07d176b6d..d8b8dbba29 100644 --- a/src/nvim/marktree.c +++ b/src/nvim/marktree.c @@ -14,8 +14,6 @@ // Use marktree_itr_current and marktree_itr_next/prev to read marks in a loop. // marktree_del_itr deletes the current mark of the iterator and implicitly // moves the iterator to the next mark. -// -// Work is ongoing to fully support ranges (mark pairs). // Copyright notice for kbtree (included in heavily modified form): // @@ -58,19 +56,27 @@ #include "nvim/memory.h" #include "nvim/pos.h" +// only for debug functions +#include "nvim/api/private/helpers.h" + #define T MT_BRANCH_FACTOR -#define ILEN (sizeof(mtnode_t) + (2 * T) * sizeof(void *)) +#define ILEN (sizeof(MTNode) + (2 * T) * sizeof(void *)) #define ID_INCR (((uint64_t)1) << 2) -#define rawkey(itr) ((itr)->node->key[(itr)->i]) +#define rawkey(itr) ((itr)->x->key[(itr)->i]) -static bool pos_leq(mtpos_t a, mtpos_t b) +static bool pos_leq(MTPos a, MTPos b) { return a.row < b.row || (a.row == b.row && a.col <= b.col); } -static void relative(mtpos_t base, mtpos_t *val) +static bool pos_less(MTPos a, MTPos b) +{ + return !pos_leq(b, a); +} + +static void relative(MTPos base, MTPos *val) { assert(pos_leq(base, *val)); if (val->row == base.row) { @@ -81,7 +87,7 @@ static void relative(mtpos_t base, mtpos_t *val) } } -static void unrelative(mtpos_t base, mtpos_t *val) +static void unrelative(MTPos base, MTPos *val) { if (val->row == 0) { val->row = base.row; @@ -91,7 +97,7 @@ static void unrelative(mtpos_t base, mtpos_t *val) } } -static void compose(mtpos_t *base, mtpos_t val) +static void compose(MTPos *base, MTPos val) { if (val.row == 0) { base->col += val.col; @@ -101,12 +107,21 @@ static void compose(mtpos_t *base, mtpos_t val) } } +// Used by `marktree_splice`. Need to keep track of marks which moved +// in order to repair intersections. +typedef struct { + uint64_t id; + MTNode *old, *new; + int old_i, new_i; +} Damage; +typedef kvec_withinit_t(Damage, 8) DamageList; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "marktree.c.generated.h" #endif #define mt_generic_cmp(a, b) (((b) < (a)) - ((a) < (b))) -static int key_cmp(mtkey_t a, mtkey_t b) +static int key_cmp(MTKey a, MTKey b) { int cmp = mt_generic_cmp(a.pos.row, b.pos.row); if (cmp != 0) { @@ -116,18 +131,25 @@ static int key_cmp(mtkey_t a, mtkey_t b) if (cmp != 0) { return cmp; } - // NB: keeping the events at the same pos sorted by id is actually not - // necessary only make sure that START is before END etc. - return mt_generic_cmp(a.flags, b.flags); + + // TODO(bfredl): MT_FLAG_REAL could go away if we fix marktree_getp_aux for real + const uint16_t cmp_mask = MT_FLAG_RIGHT_GRAVITY | MT_FLAG_END | MT_FLAG_REAL | MT_FLAG_LAST; + return mt_generic_cmp(a.flags & cmp_mask, b.flags & cmp_mask); } -static inline int marktree_getp_aux(const mtnode_t *x, mtkey_t k, int *r) +/// @return position of k if it exists in the node, otherwise the position +/// it should be inserted, which ranges from 0 to x->n _inclusively_ +/// @param match (optional) set to TRUE if match (pos, gravity) was found +static inline int marktree_getp_aux(const MTNode *x, MTKey k, bool *match) { - int tr, *rr, begin = 0, end = x->n; + bool dummy_match; + bool *m = match ? match : &dummy_match; + + int begin = 0, end = x->n; if (x->n == 0) { + *m = false; return -1; } - rr = r ? r : &tr; while (begin < end) { int mid = (begin + end) >> 1; if (key_cmp(x->key[mid], k) < 0) { @@ -137,47 +159,84 @@ static inline int marktree_getp_aux(const mtnode_t *x, mtkey_t k, int *r) } } if (begin == x->n) { - *rr = 1; return x->n - 1; + *m = false; + return x->n - 1; } - if ((*rr = key_cmp(k, x->key[begin])) < 0) { + if (!(*m = (key_cmp(k, x->key[begin]) == 0))) { begin--; } return begin; } -static inline void refkey(MarkTree *b, mtnode_t *x, int i) +static inline void refkey(MarkTree *b, MTNode *x, int i) { pmap_put(uint64_t)(b->id2node, mt_lookup_key(x->key[i]), x); } +static MTNode *id2node(MarkTree *b, uint64_t id) +{ + return pmap_get(uint64_t)(b->id2node, id); +} + // put functions // x must be an internal node, which is not full // x->ptr[i] should be a full node, i e x->ptr[i]->n == 2*T-1 -static inline void split_node(MarkTree *b, mtnode_t *x, const int i) +static inline void split_node(MarkTree *b, MTNode *x, const int i, MTKey next) { - mtnode_t *y = x->ptr[i]; - mtnode_t *z; - z = (mtnode_t *)xcalloc(1, y->level ? ILEN : sizeof(mtnode_t)); - b->n_nodes++; + MTNode *y = x->ptr[i]; + MTNode *z = marktree_alloc_node(b, y->level); z->level = y->level; z->n = T - 1; - memcpy(z->key, &y->key[T], sizeof(mtkey_t) * (T - 1)); + + // tricky: we might split a node in between inserting the start node and the end + // node of the same pair. Then we must not intersect this id yet (done later + // in marktree_intersect_pair). + uint64_t last_start = mt_end(next) ? mt_lookup_id(next.ns, next.id, false) : MARKTREE_END_FLAG; + + // no alloc in the common case (less than 4 intersects) + kvi_copy(z->intersect, y->intersect); + + if (!y->level) { + uint64_t pi = pseudo_index(y, 0); // note: sloppy pseudo-index + for (int j = 0; j < T; j++) { + MTKey k = y->key[j]; + uint64_t pi_end = pseudo_index_for_id(b, mt_lookup_id(k.ns, k.id, true), true); + if (mt_start(k) && pi_end > pi && mt_lookup_key(k) != last_start) { + intersect_node(b, z, mt_lookup_id(k.ns, k.id, false)); + } + } + + // note: y->key[T-1] is moved up and thus checked for both + for (int j = T - 1; j < (T * 2) - 1; j++) { + MTKey k = y->key[j]; + uint64_t pi_start = pseudo_index_for_id(b, mt_lookup_id(k.ns, k.id, false), true); + if (mt_end(k) && pi_start > 0 && pi_start < pi) { + intersect_node(b, y, mt_lookup_id(k.ns, k.id, false)); + } + } + } + + memcpy(z->key, &y->key[T], sizeof(MTKey) * (T - 1)); for (int j = 0; j < T - 1; j++) { refkey(b, z, j); } if (y->level) { - memcpy(z->ptr, &y->ptr[T], sizeof(mtnode_t *) * T); + memcpy(z->ptr, &y->ptr[T], sizeof(MTNode *) * T); for (int j = 0; j < T; j++) { z->ptr[j]->parent = z; + z->ptr[j]->p_idx = (int16_t)j; } } y->n = T - 1; memmove(&x->ptr[i + 2], &x->ptr[i + 1], - sizeof(mtnode_t *) * (size_t)(x->n - i)); + sizeof(MTNode *) * (size_t)(x->n - i)); x->ptr[i + 1] = z; z->parent = x; // == y->parent - memmove(&x->key[i + 1], &x->key[i], sizeof(mtkey_t) * (size_t)(x->n - i)); + for (int j = i + 1; j < x->n + 2; j++) { + x->ptr[j]->p_idx = (int16_t)j; + } + memmove(&x->key[i + 1], &x->key[i], sizeof(MTKey) * (size_t)(x->n - i)); // move key to internal layer: x->key[i] = y->key[T - 1]; @@ -190,25 +249,32 @@ static inline void split_node(MarkTree *b, mtnode_t *x, const int i) if (i > 0) { unrelative(x->key[i - 1].pos, &x->key[i].pos); } + + if (y->level) { + bubble_up(y); + bubble_up(z); + } else { + // code above goose here + } } // x must not be a full node (even if there might be internal space) -static inline void marktree_putp_aux(MarkTree *b, mtnode_t *x, mtkey_t k) +static inline void marktree_putp_aux(MarkTree *b, MTNode *x, MTKey k) { - int i; + // TODO(bfredl): ugh, make sure this is the _last_ valid (pos, gravity) position, + // to minimize movement + int i = marktree_getp_aux(x, k, NULL) + 1; if (x->level == 0) { - i = marktree_getp_aux(x, k, 0); - if (i != x->n - 1) { - memmove(&x->key[i + 2], &x->key[i + 1], - (size_t)(x->n - i - 1) * sizeof(mtkey_t)); + if (i != x->n) { + memmove(&x->key[i + 1], &x->key[i], + (size_t)(x->n - i) * sizeof(MTKey)); } - x->key[i + 1] = k; - refkey(b, x, i + 1); + x->key[i] = k; + refkey(b, x, i); x->n++; } else { - i = marktree_getp_aux(x, k, 0) + 1; if (x->ptr[i]->n == 2 * T - 1) { - split_node(b, x, i); + split_node(b, x, i, k); if (key_cmp(k, x->key[i]) > 0) { i++; } @@ -220,7 +286,7 @@ static inline void marktree_putp_aux(MarkTree *b, mtnode_t *x, mtkey_t k) } } -void marktree_put(MarkTree *b, mtkey_t key, int end_row, int end_col, bool end_right) +void marktree_put(MarkTree *b, MTKey key, int end_row, int end_col, bool end_right) { assert(!(key.flags & ~MT_FLAG_EXTERNAL_MASK)); if (end_row >= 0) { @@ -230,32 +296,151 @@ void marktree_put(MarkTree *b, mtkey_t key, int end_row, int end_col, bool end_r marktree_put_key(b, key); if (end_row >= 0) { - mtkey_t end_key = key; + MTKey end_key = key; end_key.flags = (uint16_t)((uint16_t)(key.flags & ~MT_FLAG_RIGHT_GRAVITY) |(uint16_t)MT_FLAG_END |(uint16_t)(end_right ? MT_FLAG_RIGHT_GRAVITY : 0)); - end_key.pos = (mtpos_t){ end_row, end_col }; + end_key.pos = (MTPos){ end_row, end_col }; marktree_put_key(b, end_key); + MarkTreeIter itr[1] = { 0 }, end_itr[1] = { 0 }; + marktree_lookup(b, mt_lookup_key(key), itr); + marktree_lookup(b, mt_lookup_key(end_key), end_itr); + + marktree_intersect_pair(b, mt_lookup_key(key), itr, end_itr, false); + } +} + +// this is currently not used very often, but if it was it should use binary search +static bool intersection_has(Intersection *x, uint64_t id) +{ + for (size_t i = 0; i < kv_size(*x); i++) { + if (kv_A(*x, i) == id) { + return true; + } else if (kv_A(*x, i) >= id) { + return false; + } } + return false; } -void marktree_put_key(MarkTree *b, mtkey_t k) +static void intersect_node(MarkTree *b, MTNode *x, uint64_t id) +{ + assert(!(id & MARKTREE_END_FLAG)); + kvi_pushp(x->intersect); + // optimized for the common case: new key is always in the end + for (ssize_t i = (ssize_t)kv_size(x->intersect) - 1; i >= 0; i--) { + if (i > 0 && kv_A(x->intersect, i - 1) > id) { + kv_A(x->intersect, i) = kv_A(x->intersect, i - 1); + } else { + kv_A(x->intersect, i) = id; + break; + } + } +} + +static void unintersect_node(MarkTree *b, MTNode *x, uint64_t id, bool strict) +{ + assert(!(id & MARKTREE_END_FLAG)); + bool seen = false; + size_t i; + for (i = 0; i < kv_size(x->intersect); i++) { + if (kv_A(x->intersect, i) < id) { + continue; + } else if (kv_A(x->intersect, i) == id) { + seen = true; + break; + } else { // (kv_A(x->intersect, i) > id) + break; + } + } + if (strict) { + assert(seen); + } + + if (seen) { + if (i < kv_size(x->intersect) - 1) { + memmove(&kv_A(x->intersect, i), &kv_A(x->intersect, i + 1), (kv_size(x->intersect) - i - 1) * + sizeof(kv_A(x->intersect, i))); + } + kv_size(x->intersect)--; + } +} + +/// @param itr mutated +/// @param end_itr not mutated +void marktree_intersect_pair(MarkTree *b, uint64_t id, MarkTreeIter *itr, MarkTreeIter *end_itr, + bool delete) +{ + int lvl = 0, maxlvl = MIN(itr->lvl, end_itr->lvl); +#define iat(itr, l, q) ((l == itr->lvl) ? itr->i + q : itr->s[l].i) + for (; lvl < maxlvl; lvl++) { + if (itr->s[lvl].i > end_itr->s[lvl].i) { + return; // empty range + } else if (itr->s[lvl].i < end_itr->s[lvl].i) { + break; // work to do + } + } + if (lvl == maxlvl && iat(itr, lvl, 1) > iat(end_itr, lvl, 0)) { + return; // empty range + } + + while (itr->x) { + bool skip = false; + if (itr->x == end_itr->x) { + if (itr->x->level == 0 || itr->i >= end_itr->i) { + break; + } else { + skip = true; + } + } else if (itr->lvl > lvl) { + skip = true; + } else { + if (iat(itr, lvl, 1) < iat(end_itr, lvl, 1)) { + skip = true; + } else { + lvl++; + } + } + + if (skip) { + if (itr->x->level) { + MTNode *x = itr->x->ptr[itr->i + 1]; + if (delete) { + unintersect_node(b, x, id, true); + } else { + intersect_node(b, x, id); + } + } + } + marktree_itr_next_skip(b, itr, skip, true, NULL); + } +#undef iat +} + +static MTNode *marktree_alloc_node(MarkTree *b, bool internal) +{ + MTNode *x = xcalloc(1, internal ? ILEN : sizeof(MTNode)); + kvi_init(x->intersect); + b->n_nodes++; + return x; +} + +void marktree_put_key(MarkTree *b, MTKey k) { k.flags |= MT_FLAG_REAL; // let's be real. if (!b->root) { - b->root = (mtnode_t *)xcalloc(1, ILEN); - b->n_nodes++; + b->root = marktree_alloc_node(b, true); } - mtnode_t *r, *s; + MTNode *r, *s; b->n_keys++; r = b->root; if (r->n == 2 * T - 1) { - b->n_nodes++; - s = (mtnode_t *)xcalloc(1, ILEN); + s = marktree_alloc_node(b, true); b->root = s; s->level = r->level + 1; s->n = 0; s->ptr[0] = r; r->parent = s; - split_node(b, s, 0); + r->p_idx = 0; + split_node(b, s, 0, k); r = s; } marktree_putp_aux(b, r, k); @@ -289,22 +474,31 @@ uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev) { int adjustment = 0; - mtnode_t *cur = itr->node; + MTNode *cur = itr->x; int curi = itr->i; uint64_t id = mt_lookup_key(cur->key[curi]); - // fprintf(stderr, "\nDELET %lu\n", id); - mtkey_t raw = rawkey(itr); + MTKey raw = rawkey(itr); uint64_t other = 0; - if (mt_paired(raw)) { - other = mt_lookup_id(raw.ns, raw.id, !mt_end(raw)); + if (mt_paired(raw) && !(raw.flags & MT_FLAG_ORPHANED)) { + other = mt_lookup_key_side(raw, !mt_end(raw)); + + MarkTreeIter other_itr[1]; + marktree_lookup(b, other, other_itr); + rawkey(other_itr).flags |= MT_FLAG_ORPHANED; + // Remove intersect markers. NB: must match exactly! + if (mt_start(raw)) { + MarkTreeIter this_itr[1] = { *itr }; // mutated copy + marktree_intersect_pair(b, id, this_itr, other_itr, true); + } else { + marktree_intersect_pair(b, other, other_itr, itr, true); + } } - if (itr->node->level) { + if (itr->x->level) { if (rev) { abort(); } else { - // fprintf(stderr, "INTERNAL %d\n", cur->level); // steal previous node marktree_itr_prev(b, itr); adjustment = -1; @@ -312,41 +506,72 @@ uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev) } // 3. - mtnode_t *x = itr->node; + MTNode *x = itr->x; assert(x->level == 0); - mtkey_t intkey = x->key[itr->i]; + MTKey intkey = x->key[itr->i]; if (x->n > itr->i + 1) { memmove(&x->key[itr->i], &x->key[itr->i + 1], - sizeof(mtkey_t) * (size_t)(x->n - itr->i - 1)); + sizeof(MTKey) * (size_t)(x->n - itr->i - 1)); } x->n--; + b->n_keys--; + pmap_del(uint64_t)(b->id2node, id, NULL); + // 4. // if (adjustment == 1) { // abort(); // } if (adjustment == -1) { int ilvl = itr->lvl - 1; - const mtnode_t *lnode = x; + MTNode *lnode = x; + uint64_t start_id = 0; + bool did_bubble = false; + if (mt_end(intkey)) { + start_id = mt_lookup_key_side(intkey, false); + } do { - const mtnode_t *const p = lnode->parent; + MTNode *p = lnode->parent; if (ilvl < 0) { abort(); } - const int i = itr->s[ilvl].i; + int i = itr->s[ilvl].i; assert(p->ptr[i] == lnode); if (i > 0) { unrelative(p->key[i - 1].pos, &intkey.pos); } + + if (p != cur && start_id) { + if (intersection_has(&p->ptr[0]->intersect, start_id)) { + // if not the first time, we need to undo the addition in the + // previous step (`intersect_node` just below) + int last = (lnode != x) ? 1 : 0; + for (int k = 0; k < p->n + last; k++) { // one less as p->ptr[n] is the last + unintersect_node(b, p->ptr[k], start_id, true); + } + intersect_node(b, p, start_id); + did_bubble = true; + } + } + lnode = p; ilvl--; } while (lnode != cur); - mtkey_t deleted = cur->key[curi]; + MTKey deleted = cur->key[curi]; cur->key[curi] = intkey; refkey(b, cur, curi); + // if `did_bubble` then we already added `start_id` to some parent + if (mt_end(cur->key[curi]) && !did_bubble) { + uint64_t pi = pseudo_index(x, 0); // note: sloppy pseudo-index + uint64_t pi_start = pseudo_index_for_id(b, start_id, true); + if (pi_start > 0 && pi_start < pi) { + intersect_node(b, x, start_id); + } + } + relative(intkey.pos, &deleted.pos); - mtnode_t *y = cur->ptr[curi + 1]; + MTNode *y = cur->ptr[curi + 1]; if (deleted.pos.row || deleted.pos.col) { while (y) { for (int k = 0; k < y->n; k++) { @@ -358,46 +583,48 @@ uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev) itr->i--; } - b->n_keys--; - pmap_del(uint64_t)(b->id2node, id, NULL); - // 5. bool itr_dirty = false; int rlvl = itr->lvl - 1; int *lasti = &itr->i; + MTPos ppos = itr->pos; while (x != b->root) { assert(rlvl >= 0); - mtnode_t *p = x->parent; + MTNode *p = x->parent; if (x->n >= T - 1) { // we are done, if this node is fine the rest of the tree will be break; } int pi = itr->s[rlvl].i; assert(p->ptr[pi] == x); + if (pi > 0) { + ppos.row -= p->key[pi - 1].pos.row; + ppos.col = itr->s[rlvl].oldcol; + } + // ppos is now the pos of p + if (pi > 0 && p->ptr[pi - 1]->n > T - 1) { *lasti += 1; itr_dirty = true; // steal one key from the left neighbour - pivot_right(b, p, pi - 1); + pivot_right(b, ppos, p, pi - 1); break; } else if (pi < p->n && p->ptr[pi + 1]->n > T - 1) { // steal one key from right neighbour - pivot_left(b, p, pi); + pivot_left(b, ppos, p, pi); break; } else if (pi > 0) { - // fprintf(stderr, "LEFT "); assert(p->ptr[pi - 1]->n == T - 1); // merge with left neighbour *lasti += T; x = merge_node(b, p, pi - 1); if (lasti == &itr->i) { // TRICKY: we merged the node the iterator was on - itr->node = x; + itr->x = x; } itr->s[rlvl].i--; itr_dirty = true; } else { - // fprintf(stderr, "RIGHT "); assert(pi < p->n && p->ptr[pi + 1]->n == T - 1); merge_node(b, p, pi); // no iter adjustment needed @@ -414,18 +641,18 @@ uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev) itr->lvl--; } if (b->root->level) { - mtnode_t *oldroot = b->root; + MTNode *oldroot = b->root; b->root = b->root->ptr[0]; b->root->parent = NULL; - xfree(oldroot); + marktree_free_node(b, oldroot); } else { // no items, nothing for iterator to point to // not strictly needed, should handle delete right-most mark anyway - itr->node = NULL; + itr->x = NULL; } } - if (itr->node && itr_dirty) { + if (itr->x && itr_dirty) { marktree_itr_fix_pos(b, itr); } @@ -441,10 +668,10 @@ uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev) marktree_itr_next(b, itr); marktree_itr_next(b, itr); } else { - if (itr->node && itr->i >= itr->node->n) { + if (itr->x && itr->i >= itr->x->n) { // we deleted the last key of a leaf node // go to the inner key after that. - assert(itr->node->level == 0); + assert(itr->x->level == 0); marktree_itr_next(b, itr); } } @@ -452,9 +679,229 @@ uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev) return other; } -static mtnode_t *merge_node(MarkTree *b, mtnode_t *p, int i) +/// similar to intersect_common but modify x and y in place to retain +/// only the items which are NOT in common +static void intersect_merge(Intersection *restrict m, Intersection *restrict x, + Intersection *restrict y) { - mtnode_t *x = p->ptr[i], *y = p->ptr[i + 1]; + size_t xi = 0, yi = 0; + size_t xn = 0, yn = 0; + while (xi < kv_size(*x) && yi < kv_size(*y)) { + if (kv_A(*x, xi) == kv_A(*y, yi)) { + // TODO(bfredl): kvi_pushp is actually quite complex, break out kvi_resize() to a function? + kvi_push(*m, kv_A(*x, xi)); + xi++; + yi++; + } else if (kv_A(*x, xi) < kv_A(*y, yi)) { + kv_A(*x, xn++) = kv_A(*x, xi++); + } else { + kv_A(*y, yn++) = kv_A(*y, yi++); + } + } + + if (xi < kv_size(*x)) { + memmove(&kv_A(*x, xn), &kv_A(*x, xi), sizeof(kv_A(*x, xn)) * (kv_size(*x) - xi)); + xn += kv_size(*x) - xi; + } + if (yi < kv_size(*y)) { + memmove(&kv_A(*y, yn), &kv_A(*y, yi), sizeof(kv_A(*y, yn)) * (kv_size(*y) - yi)); + yn += kv_size(*y) - yi; + } + + kv_size(*x) = xn; + kv_size(*y) = yn; +} + +// w used to be a child of x but it is now a child of y, adjust intersections accordingly +// @param[out] d are intersections which should be added to the old children of y +static void intersect_mov(Intersection *restrict x, Intersection *restrict y, + Intersection *restrict w, Intersection *restrict d) +{ + size_t wi = 0, yi = 0; + size_t wn = 0, yn = 0; + size_t xi = 0; + while (wi < kv_size(*w) || xi < kv_size(*x)) { + if (wi < kv_size(*w) && (xi >= kv_size(*x) || kv_A(*x, xi) >= kv_A(*w, wi))) { + if (xi < kv_size(*x) && kv_A(*x, xi) == kv_A(*w, wi)) { + xi++; + } + // now w < x strictly + while (yi < kv_size(*y) && kv_A(*y, yi) < kv_A(*w, wi)) { + kvi_push(*d, kv_A(*y, yi)); + yi++; + } + if (yi < kv_size(*y) && kv_A(*y, yi) == kv_A(*w, wi)) { + kv_A(*y, yn++) = kv_A(*y, yi++); + wi++; + } else { + kv_A(*w, wn++) = kv_A(*w, wi++); + } + } else { + // x < w strictly + while (yi < kv_size(*y) && kv_A(*y, yi) < kv_A(*x, xi)) { + kvi_push(*d, kv_A(*y, yi)); + yi++; + } + if (yi < kv_size(*y) && kv_A(*y, yi) == kv_A(*x, xi)) { + kv_A(*y, yn++) = kv_A(*y, yi++); + xi++; + } else { + // add kv_A(x, xi) at kv_A(w, wn), pushing up wi if wi == wn + if (wi == wn) { + size_t n = kv_size(*w) - wn; + kvi_pushp(*w); + if (n > 0) { + memmove(&kv_A(*w, wn + 1), &kv_A(*w, wn), n * sizeof(kv_A(*w, 0))); + } + kv_A(*w, wi) = kv_A(*x, xi); + wn++; + wi++; // no need to consider the added element again + } else { + assert(wn < wi); + kv_A(*w, wn++) = kv_A(*x, xi); + } + xi++; + } + } + } + if (yi < kv_size(*y)) { + // move remaining items to d + size_t n = kv_size(*y) - yi; // at least one + kvi_ensure_more_space(*d, n); + memcpy(&kv_A(*d, kv_size(*d)), &kv_A(*y, yi), n * sizeof(kv_A(*d, 0))); + kv_size(*d) += n; + } + kv_size(*w) = wn; + kv_size(*y) = yn; +} + +bool intersect_mov_test(uint64_t *x, size_t nx, uint64_t *y, size_t ny, uint64_t *win, size_t nwin, + uint64_t *wout, size_t *nwout, uint64_t *dout, size_t *ndout) +{ + // x is immutable in the context of intersect_mov. y might shrink, but we + // don't care about it (we get it the deleted ones in d) + Intersection xi = { .items = x, .size = nx }; + Intersection yi = { .items = y, .size = ny }; + + Intersection w; + kvi_init(w); + for (size_t i = 0; i < nwin; i++) { + kvi_push(w, win[i]); + } + Intersection d; + kvi_init(d); + + intersect_mov(&xi, &yi, &w, &d); + + if (w.size > *nwout || d.size > *ndout) { + return false; + } + + memcpy(wout, w.items, sizeof(w.items[0]) * w.size); + *nwout = w.size; + + memcpy(dout, d.items, sizeof(d.items[0]) * d.size); + *ndout = d.size; + + return true; +} + +/// intersection: i = x & y +static void intersect_common(Intersection *i, Intersection *x, Intersection *y) +{ + size_t xi = 0, yi = 0; + while (xi < kv_size(*x) && yi < kv_size(*y)) { + if (kv_A(*x, xi) == kv_A(*y, yi)) { + kvi_push(*i, kv_A(*x, xi)); + xi++; + yi++; + } else if (kv_A(*x, xi) < kv_A(*y, yi)) { + xi++; + } else { + yi++; + } + } +} + +// inplace union: x |= y +static void intersect_add(Intersection *x, Intersection *y) +{ + size_t xi = 0, yi = 0; + while (xi < kv_size(*x) && yi < kv_size(*y)) { + if (kv_A(*x, xi) == kv_A(*y, yi)) { + xi++; + yi++; + } else if (kv_A(*y, yi) < kv_A(*x, xi)) { + size_t n = kv_size(*x) - xi; // at least one + kvi_pushp(*x); + memmove(&kv_A(*x, xi + 1), &kv_A(*x, xi), n * sizeof(kv_A(*x, 0))); + kv_A(*x, xi) = kv_A(*y, yi); + xi++; // newly added element + yi++; + } else { + xi++; + } + } + if (yi < kv_size(*y)) { + size_t n = kv_size(*y) - yi; // at least one + kvi_ensure_more_space(*x, n); + memcpy(&kv_A(*x, kv_size(*x)), &kv_A(*y, yi), n * sizeof(kv_A(*x, 0))); + kv_size(*x) += n; + } +} + +// inplace assymetric difference: x &= ~y +static void intersect_sub(Intersection *restrict x, Intersection *restrict y) +{ + size_t xi = 0, yi = 0; + size_t xn = 0; + while (xi < kv_size(*x) && yi < kv_size(*y)) { + if (kv_A(*x, xi) == kv_A(*y, yi)) { + xi++; + yi++; + } else if (kv_A(*x, xi) < kv_A(*y, yi)) { + kv_A(*x, xn++) = kv_A(*x, xi++); + } else { + yi++; + } + } + if (xi < kv_size(*x)) { + size_t n = kv_size(*x) - xi; + if (xn < xi) { // otherwise xn == xi + memmove(&kv_A(*x, xn), &kv_A(*x, xi), n * sizeof(kv_A(*x, 0))); + } + xn += n; + } + kv_size(*x) = xn; +} + +/// x is a node which shrunk, or the half of a split +/// +/// this means that intervals which previously intersected all the (current) +/// child nodes, now instead intersects `x` itself. +static void bubble_up(MTNode *x) +{ + Intersection xi; + kvi_init(xi); + // due to invariants, the largest subset of _all_ subnodes is the intersection + // between the first and the last + intersect_common(&xi, &x->ptr[0]->intersect, &x->ptr[x->n]->intersect); + if (kv_size(xi)) { + for (int i = 0; i < x->n + 1; i++) { + intersect_sub(&x->ptr[i]->intersect, &xi); + } + intersect_add(&x->intersect, &xi); + } + kvi_destroy(xi); +} + +static MTNode *merge_node(MarkTree *b, MTNode *p, int i) +{ + MTNode *x = p->ptr[i], *y = p->ptr[i + 1]; + Intersection m; + kvi_init(m); + + intersect_merge(&m, &x->intersect, &y->intersect); x->key[x->n] = p->key[i]; refkey(b, x, x->n); @@ -462,35 +909,78 @@ static mtnode_t *merge_node(MarkTree *b, mtnode_t *p, int i) relative(p->key[i - 1].pos, &x->key[x->n].pos); } - memmove(&x->key[x->n + 1], y->key, (size_t)y->n * sizeof(mtkey_t)); + memmove(&x->key[x->n + 1], y->key, (size_t)y->n * sizeof(MTKey)); for (int k = 0; k < y->n; k++) { refkey(b, x, x->n + 1 + k); unrelative(x->key[x->n].pos, &x->key[x->n + 1 + k].pos); } if (x->level) { - memmove(&x->ptr[x->n + 1], y->ptr, ((size_t)y->n + 1) * sizeof(mtnode_t *)); - for (int k = 0; k < y->n + 1; k++) { - x->ptr[x->n + k + 1]->parent = x; + // bubble down: ranges that intersected old-x but not old-y or vice versa + // must be moved to their respective children + memmove(&x->ptr[x->n + 1], y->ptr, ((size_t)y->n + 1) * sizeof(MTNode *)); + for (int k = 0; k < x->n + 1; k++) { + // TODO(bfredl): dedicated impl for "Z |= Y" + for (size_t idx = 0; idx < kv_size(x->intersect); idx++) { + intersect_node(b, x->ptr[k], kv_A(x->intersect, idx)); + } + } + for (int ky = 0; ky < y->n + 1; ky++) { + int k = x->n + ky + 1; + // nodes that used to be in y, now the second half of x + x->ptr[k]->parent = x; + x->ptr[k]->p_idx = (int16_t)k; + // TODO(bfredl): dedicated impl for "Z |= X" + for (size_t idx = 0; idx < kv_size(y->intersect); idx++) { + intersect_node(b, x->ptr[k], kv_A(y->intersect, idx)); + } } } x->n += y->n + 1; - memmove(&p->key[i], &p->key[i + 1], (size_t)(p->n - i - 1) * sizeof(mtkey_t)); + memmove(&p->key[i], &p->key[i + 1], (size_t)(p->n - i - 1) * sizeof(MTKey)); memmove(&p->ptr[i + 1], &p->ptr[i + 2], - (size_t)(p->n - i - 1) * sizeof(mtkey_t *)); + (size_t)(p->n - i - 1) * sizeof(MTKey *)); + for (int j = i + 1; j < p->n; j++) { // note: one has been deleted + p->ptr[j]->p_idx = (int16_t)j; + } p->n--; - xfree(y); - b->n_nodes--; + marktree_free_node(b, y); + + kvi_destroy(x->intersect); + + // move of a kvec_withinit_t, messy! + // TODO(bfredl): special case version of intersect_merge(x_out, x_in_m_out, y) to avoid this + kvi_move(&x->intersect, &m); + return x; } +/// @param dest is overwritten (assumed to already been freed/moved) +/// @param src consumed (don't free or use) +void kvi_move(Intersection *dest, Intersection *src) +{ + dest->size = src->size; + dest->capacity = src->capacity; + if (src->items == src->init_array) { + memcpy(dest->init_array, src->init_array, src->size * sizeof(*src->init_array)); + dest->items = dest->init_array; + } else { + dest->items = src->items; + } +} + // TODO(bfredl): as a potential "micro" optimization, pivoting should balance // the two nodes instead of stealing just one key -static void pivot_right(MarkTree *b, mtnode_t *p, int i) +// x_pos is the absolute position of the key just before x (or a dummy key strictly less than any +// key inside x, if x is the first leaf) +static void pivot_right(MarkTree *b, MTPos p_pos, MTNode *p, const int i) { - mtnode_t *x = p->ptr[i], *y = p->ptr[i + 1]; - memmove(&y->key[1], y->key, (size_t)y->n * sizeof(mtkey_t)); + MTNode *x = p->ptr[i], *y = p->ptr[i + 1]; + memmove(&y->key[1], y->key, (size_t)y->n * sizeof(MTKey)); if (y->level) { - memmove(&y->ptr[1], y->ptr, ((size_t)y->n + 1) * sizeof(mtnode_t *)); + memmove(&y->ptr[1], y->ptr, ((size_t)y->n + 1) * sizeof(MTNode *)); + for (int j = 1; j < y->n + 2; j++) { + y->ptr[j]->p_idx = (int16_t)j; + } } y->key[0] = p->key[i]; refkey(b, y, 0); @@ -499,6 +989,7 @@ static void pivot_right(MarkTree *b, mtnode_t *p, int i) if (x->level) { y->ptr[0] = x->ptr[x->n]; y->ptr[0]->parent = y; + y->ptr[0]->p_idx = 0; } x->n--; y->n++; @@ -509,11 +1000,46 @@ static void pivot_right(MarkTree *b, mtnode_t *p, int i) for (int k = 1; k < y->n; k++) { unrelative(y->key[0].pos, &y->key[k].pos); } + + // repair intersections of x + if (x->level) { + // handle y and first new y->ptr[0] + Intersection d; + kvi_init(d); + // y->ptr[0] was moved from x to y + // adjust y->ptr[0] for a difference between the parents + // in addition, this might cause some intersection of the old y + // to bubble down to the old children of y (if y->ptr[0] wasn't intersected) + intersect_mov(&x->intersect, &y->intersect, &y->ptr[0]->intersect, &d); + if (kv_size(d)) { + for (int yi = 1; yi < y->n + 1; yi++) { + intersect_add(&y->ptr[yi]->intersect, &d); + } + } + kvi_destroy(d); + + bubble_up(x); + } else { + // if the last element of x used to be an end node, check if it now covers all of x + if (mt_end(p->key[i])) { + uint64_t pi = pseudo_index(x, 0); // note: sloppy pseudo-index + uint64_t start_id = mt_lookup_key_side(p->key[i], false); + uint64_t pi_start = pseudo_index_for_id(b, start_id, true); + if (pi_start > 0 && pi_start < pi) { + intersect_node(b, x, start_id); + } + } + + if (mt_start(y->key[0])) { + // no need for a check, just delet it if it was there + unintersect_node(b, y, mt_lookup_key(y->key[0]), false); + } + } } -static void pivot_left(MarkTree *b, mtnode_t *p, int i) +static void pivot_left(MarkTree *b, MTPos p_pos, MTNode *p, int i) { - mtnode_t *x = p->ptr[i], *y = p->ptr[i + 1]; + MTNode *x = p->ptr[i], *y = p->ptr[i + 1]; // reverse from how we "always" do it. but pivot_left // is just the inverse of pivot_right, so reverse it literally. @@ -532,40 +1058,88 @@ static void pivot_left(MarkTree *b, mtnode_t *p, int i) if (x->level) { x->ptr[x->n + 1] = y->ptr[0]; x->ptr[x->n + 1]->parent = x; + x->ptr[x->n + 1]->p_idx = (int16_t)(x->n + 1); } - memmove(y->key, &y->key[1], (size_t)(y->n - 1) * sizeof(mtkey_t)); + memmove(y->key, &y->key[1], (size_t)(y->n - 1) * sizeof(MTKey)); if (y->level) { - memmove(y->ptr, &y->ptr[1], (size_t)y->n * sizeof(mtnode_t *)); + memmove(y->ptr, &y->ptr[1], (size_t)y->n * sizeof(MTNode *)); + for (int j = 0; j < y->n; j++) { // note: last item deleted + y->ptr[j]->p_idx = (int16_t)j; + } } x->n++; y->n--; + + // repair intersections of x,y + if (x->level) { + // handle y and first new y->ptr[0] + Intersection d; + kvi_init(d); + // x->ptr[x->n] was moved from y to x + // adjust x->ptr[x->n] for a difference between the parents + // in addition, this might cause some intersection of the old x + // to bubble down to the old children of x (if x->ptr[n] wasn't intersected) + intersect_mov(&y->intersect, &x->intersect, &x->ptr[x->n]->intersect, &d); + if (kv_size(d)) { + for (int xi = 0; xi < x->n; xi++) { // ptr[x->n| deliberately skipped + intersect_add(&x->ptr[xi]->intersect, &d); + } + } + kvi_destroy(d); + + bubble_up(y); + } else { + // if the first element of y used to be an start node, check if it now covers all of y + if (mt_start(p->key[i])) { + uint64_t pi = pseudo_index(y, 0); // note: sloppy pseudo-index + + uint64_t end_id = mt_lookup_key_side(p->key[i], true); + uint64_t pi_end = pseudo_index_for_id(b, end_id, true); + + if (pi_end > pi) { + intersect_node(b, y, mt_lookup_key(p->key[i])); + } + } + + if (mt_end(x->key[x->n - 1])) { + // no need for a check, just delet it if it was there + unintersect_node(b, x, mt_lookup_key_side(x->key[x->n - 1], false), false); + } + } } /// frees all mem, resets tree to valid empty state void marktree_clear(MarkTree *b) { if (b->root) { - marktree_free_node(b->root); + marktree_free_subtree(b, b->root); b->root = NULL; } map_destroy(uint64_t, b->id2node); *b->id2node = (PMap(uint64_t)) MAP_INIT; b->n_keys = 0; - b->n_nodes = 0; + assert(b->n_nodes == 0); } -void marktree_free_node(mtnode_t *x) +void marktree_free_subtree(MarkTree *b, MTNode *x) { if (x->level) { for (int i = 0; i < x->n + 1; i++) { - marktree_free_node(x->ptr[i]); + marktree_free_subtree(b, x->ptr[i]); } } + marktree_free_node(b, x); +} + +static void marktree_free_node(MarkTree *b, MTNode *x) +{ + kvi_destroy(x->intersect); xfree(x); + b->n_nodes--; } /// NB: caller must check not pair! -void marktree_revise(MarkTree *b, MarkTreeIter *itr, uint8_t decor_level, mtkey_t key) +void marktree_revise(MarkTree *b, MarkTreeIter *itr, uint8_t decor_level, MTKey key) { // TODO(bfredl): clean up this mess and re-instantiate &= and |= forms // once we upgrade to a non-broken version of gcc in functionaltest-lua CI @@ -578,49 +1152,108 @@ void marktree_revise(MarkTree *b, MarkTreeIter *itr, uint8_t decor_level, mtkey_ rawkey(itr).priority = key.priority; } +/// @param itr iterator is invalid after call void marktree_move(MarkTree *b, MarkTreeIter *itr, int row, int col) { - mtkey_t key = rawkey(itr); - // TODO(bfredl): optimize when moving a mark within a leaf without moving it - // across neighbours! - marktree_del_itr(b, itr, false); - key.pos = (mtpos_t){ row, col }; + MTKey key = rawkey(itr); + MTNode *x = itr->x; + if (!x->level) { + bool internal = false; + MTPos newpos = MTPos(row, col); + if (x->parent != NULL) { + // strictly _after_ key before `x` + // (not optimal when x is very first leaf of the entire tree, but that's fine) + if (pos_less(itr->pos, newpos)) { + relative(itr->pos, &newpos); + + // strictly before the end of x. (this could be made sharper by + // finding the internal key just after x, but meh) + if (pos_less(newpos, x->key[x->n - 1].pos)) { + internal = true; + } + } + } else { + // tree is one node. newpos thus is already "relative" itr->pos + internal = true; + } + + if (internal) { + key.pos = newpos; + bool match; + // tricky: could minimize movement in either direction better + int new_i = marktree_getp_aux(x, key, &match); + if (!match) { + new_i++; + } + if (new_i == itr->i || key_cmp(key, x->key[new_i]) == 0) { + x->key[itr->i].pos = newpos; + } else if (new_i < itr->i) { + memmove(&x->key[new_i + 1], &x->key[new_i], sizeof(MTKey) * (size_t)(itr->i - new_i)); + x->key[new_i] = key; + } else if (new_i > itr->i) { + memmove(&x->key[itr->i], &x->key[itr->i + 1], sizeof(MTKey) * (size_t)(new_i - itr->i)); + x->key[new_i] = key; + } + return; + } + } + uint64_t other = marktree_del_itr(b, itr, false); + key.pos = (MTPos){ row, col }; marktree_put_key(b, key); - itr->node = NULL; // itr might become invalid by put + + if (other) { + marktree_restore_pair(b, key); + } + itr->x = NULL; // itr might become invalid by put +} + +void marktree_restore_pair(MarkTree *b, MTKey key) +{ + MarkTreeIter itr[1]; + MarkTreeIter end_itr[1]; + marktree_lookup(b, mt_lookup_key_side(key, false), itr); + marktree_lookup(b, mt_lookup_key_side(key, true), end_itr); + if (!itr->x || !end_itr->x) { + // this could happen if the other end is waiting to be restored later + // this function will be called again for the other end. + return; + } + rawkey(itr).flags &= (uint16_t) ~MT_FLAG_ORPHANED; + rawkey(end_itr).flags &= (uint16_t) ~MT_FLAG_ORPHANED; + + marktree_intersect_pair(b, mt_lookup_key_side(key, false), itr, end_itr, false); } // itr functions -// TODO(bfredl): static inline? bool marktree_itr_get(MarkTree *b, int32_t row, int col, MarkTreeIter *itr) { - return marktree_itr_get_ext(b, (mtpos_t){ row, col }, - itr, false, false, NULL); + return marktree_itr_get_ext(b, MTPos(row, col), itr, false, false, NULL); } -bool marktree_itr_get_ext(MarkTree *b, mtpos_t p, MarkTreeIter *itr, bool last, bool gravity, - mtpos_t *oldbase) +bool marktree_itr_get_ext(MarkTree *b, MTPos p, MarkTreeIter *itr, bool last, bool gravity, + MTPos *oldbase) { if (b->n_keys == 0) { - itr->node = NULL; + itr->x = NULL; return false; } - mtkey_t k = { .pos = p, .flags = gravity ? MT_FLAG_RIGHT_GRAVITY : 0 }; + MTKey k = { .pos = p, .flags = gravity ? MT_FLAG_RIGHT_GRAVITY : 0 }; if (last && !gravity) { k.flags = MT_FLAG_LAST; } - itr->pos = (mtpos_t){ 0, 0 }; - itr->node = b->root; + itr->pos = (MTPos){ 0, 0 }; + itr->x = b->root; itr->lvl = 0; if (oldbase) { oldbase[itr->lvl] = itr->pos; } while (true) { - itr->i = marktree_getp_aux(itr->node, k, 0) + 1; + itr->i = marktree_getp_aux(itr->x, k, 0) + 1; - if (itr->node->level == 0) { + if (itr->x->level == 0) { break; } @@ -628,10 +1261,10 @@ bool marktree_itr_get_ext(MarkTree *b, mtpos_t p, MarkTreeIter *itr, bool last, itr->s[itr->lvl].oldcol = itr->pos.col; if (itr->i > 0) { - compose(&itr->pos, itr->node->key[itr->i - 1].pos); - relative(itr->node->key[itr->i - 1].pos, &k.pos); + compose(&itr->pos, itr->x->key[itr->i - 1].pos); + relative(itr->x->key[itr->i - 1].pos, &k.pos); } - itr->node = itr->node->ptr[itr->i]; + itr->x = itr->x->ptr[itr->i]; itr->lvl++; if (oldbase) { oldbase[itr->lvl] = itr->pos; @@ -640,7 +1273,7 @@ bool marktree_itr_get_ext(MarkTree *b, mtpos_t p, MarkTreeIter *itr, bool last, if (last) { return marktree_itr_prev(b, itr); - } else if (itr->i >= itr->node->n) { + } else if (itr->i >= itr->x->n) { return marktree_itr_next(b, itr); } return true; @@ -648,19 +1281,20 @@ bool marktree_itr_get_ext(MarkTree *b, mtpos_t p, MarkTreeIter *itr, bool last, bool marktree_itr_first(MarkTree *b, MarkTreeIter *itr) { - itr->node = b->root; if (b->n_keys == 0) { + itr->x = NULL; return false; } + itr->x = b->root; itr->i = 0; itr->lvl = 0; - itr->pos = (mtpos_t){ 0, 0 }; - while (itr->node->level > 0) { + itr->pos = MTPos(0, 0); + while (itr->x->level > 0) { itr->s[itr->lvl].i = 0; itr->s[itr->lvl].oldcol = 0; itr->lvl++; - itr->node = itr->node->ptr[0]; + itr->x = itr->x->ptr[0]; } return true; } @@ -669,16 +1303,16 @@ bool marktree_itr_first(MarkTree *b, MarkTreeIter *itr) int marktree_itr_last(MarkTree *b, MarkTreeIter *itr) { if (b->n_keys == 0) { - itr->node = NULL; + itr->x = NULL; return false; } - itr->pos = (mtpos_t){ 0, 0 }; - itr->node = b->root; + itr->pos = MTPos(0, 0); + itr->x = b->root; itr->lvl = 0; while (true) { - itr->i = itr->node->n; + itr->i = itr->x->n; - if (itr->node->level == 0) { + if (itr->x->level == 0) { break; } @@ -686,63 +1320,71 @@ int marktree_itr_last(MarkTree *b, MarkTreeIter *itr) itr->s[itr->lvl].oldcol = itr->pos.col; assert(itr->i > 0); - compose(&itr->pos, itr->node->key[itr->i - 1].pos); + compose(&itr->pos, itr->x->key[itr->i - 1].pos); - itr->node = itr->node->ptr[itr->i]; + itr->x = itr->x->ptr[itr->i]; itr->lvl++; } itr->i--; return true; } -// TODO(bfredl): static inline bool marktree_itr_next(MarkTree *b, MarkTreeIter *itr) { - return marktree_itr_next_skip(b, itr, false, NULL); + return marktree_itr_next_skip(b, itr, false, false, NULL); } -static bool marktree_itr_next_skip(MarkTree *b, MarkTreeIter *itr, bool skip, mtpos_t oldbase[]) +static bool marktree_itr_next_skip(MarkTree *b, MarkTreeIter *itr, bool skip, bool preload, + MTPos oldbase[]) { - if (!itr->node) { + if (!itr->x) { return false; } itr->i++; - if (itr->node->level == 0 || skip) { - if (itr->i < itr->node->n) { + if (itr->x->level == 0 || skip) { + if (preload && itr->x->level == 0 && skip) { + // skip rest of this leaf node + itr->i = itr->x->n; + } else if (itr->i < itr->x->n) { // TODO(bfredl): this is the common case, // and could be handled by inline wrapper return true; } // we ran out of non-internal keys. Go up until we find an internal key - while (itr->i >= itr->node->n) { - itr->node = itr->node->parent; - if (itr->node == NULL) { + while (itr->i >= itr->x->n) { + itr->x = itr->x->parent; + if (itr->x == NULL) { return false; } itr->lvl--; itr->i = itr->s[itr->lvl].i; if (itr->i > 0) { - itr->pos.row -= itr->node->key[itr->i - 1].pos.row; + itr->pos.row -= itr->x->key[itr->i - 1].pos.row; itr->pos.col = itr->s[itr->lvl].oldcol; } } } else { // we stood at an "internal" key. Go down to the first non-internal // key after it. - while (itr->node->level > 0) { + while (itr->x->level > 0) { // internal key, there is always a child after if (itr->i > 0) { itr->s[itr->lvl].oldcol = itr->pos.col; - compose(&itr->pos, itr->node->key[itr->i - 1].pos); + compose(&itr->pos, itr->x->key[itr->i - 1].pos); } if (oldbase && itr->i == 0) { oldbase[itr->lvl + 1] = oldbase[itr->lvl]; } itr->s[itr->lvl].i = itr->i; - assert(itr->node->ptr[itr->i]->parent == itr->node); - itr->node = itr->node->ptr[itr->i]; - itr->i = 0; + assert(itr->x->ptr[itr->i]->parent == itr->x); itr->lvl++; + itr->x = itr->x->ptr[itr->i]; + if (preload && itr->x->level) { + itr->i = -1; + break; + } else { + itr->i = 0; + } } } return true; @@ -750,10 +1392,10 @@ static bool marktree_itr_next_skip(MarkTree *b, MarkTreeIter *itr, bool skip, mt bool marktree_itr_prev(MarkTree *b, MarkTreeIter *itr) { - if (!itr->node) { + if (!itr->x) { return false; } - if (itr->node->level == 0) { + if (itr->x->level == 0) { itr->i--; if (itr->i >= 0) { // TODO(bfredl): this is the common case, @@ -762,30 +1404,30 @@ bool marktree_itr_prev(MarkTree *b, MarkTreeIter *itr) } // we ran out of non-internal keys. Go up until we find a non-internal key while (itr->i < 0) { - itr->node = itr->node->parent; - if (itr->node == NULL) { + itr->x = itr->x->parent; + if (itr->x == NULL) { return false; } itr->lvl--; itr->i = itr->s[itr->lvl].i - 1; if (itr->i >= 0) { - itr->pos.row -= itr->node->key[itr->i].pos.row; + itr->pos.row -= itr->x->key[itr->i].pos.row; itr->pos.col = itr->s[itr->lvl].oldcol; } } } else { // we stood at an "internal" key. Go down to the last non-internal // key before it. - while (itr->node->level > 0) { + while (itr->x->level > 0) { // internal key, there is always a child before if (itr->i > 0) { itr->s[itr->lvl].oldcol = itr->pos.col; - compose(&itr->pos, itr->node->key[itr->i - 1].pos); + compose(&itr->pos, itr->x->key[itr->i - 1].pos); } itr->s[itr->lvl].i = itr->i; - assert(itr->node->ptr[itr->i]->parent == itr->node); - itr->node = itr->node->ptr[itr->i]; - itr->i = itr->node->n; + assert(itr->x->ptr[itr->i]->parent == itr->x); + itr->x = itr->x->ptr[itr->i]; + itr->i = itr->x->n; itr->lvl++; } itr->i--; @@ -793,33 +1435,22 @@ bool marktree_itr_prev(MarkTree *b, MarkTreeIter *itr) return true; } -void marktree_itr_rewind(MarkTree *b, MarkTreeIter *itr) -{ - if (!itr->node) { - return; - } - if (itr->node->level) { - marktree_itr_prev(b, itr); - } - itr->i = 0; -} - bool marktree_itr_node_done(MarkTreeIter *itr) { - return !itr->node || itr->i == itr->node->n - 1; + return !itr->x || itr->i == itr->x->n - 1; } -mtpos_t marktree_itr_pos(MarkTreeIter *itr) +MTPos marktree_itr_pos(MarkTreeIter *itr) { - mtpos_t pos = rawkey(itr).pos; + MTPos pos = rawkey(itr).pos; unrelative(itr->pos, &pos); return pos; } -mtkey_t marktree_itr_current(MarkTreeIter *itr) +MTKey marktree_itr_current(MarkTreeIter *itr) { - if (itr->node) { - mtkey_t key = rawkey(itr); + if (itr->x) { + MTKey key = rawkey(itr); key.pos = marktree_itr_pos(itr); return key; } @@ -831,47 +1462,198 @@ static bool itr_eq(MarkTreeIter *itr1, MarkTreeIter *itr2) return (&rawkey(itr1) == &rawkey(itr2)); } -static void itr_swap(MarkTreeIter *itr1, MarkTreeIter *itr2) +/// Get all marks which overlaps the position (row,col) +/// +/// After calling this function, use marktree_itr_step_overlap to step through +/// one overlapping mark at a time, until it returns false +/// +/// NOTE: It's possible to get all marks which overlaps a region (row,col) to (row_end,col_end) +/// To do this, first call marktree_itr_get_overlap with the start position and +/// keep calling marktree_itr_step_overlap until it returns false. +/// After this, as a second loop, keep calling the marktree_itr_next() until +/// the iterator is invalid or reaches past (row_end, col_end). In this loop, +/// consider all "start" marks (and unpaired marks if relevant), but skip over +/// all "end" marks, using mt_end(mark). +/// +/// @return false if we already know no marks can be found +/// even if "true" the first call to marktree_itr_step_overlap +/// could return false +bool marktree_itr_get_overlap(MarkTree *b, int row, int col, MarkTreeIter *itr) +{ + if (b->n_keys == 0) { + itr->x = NULL; + return false; + } + + itr->x = b->root; + itr->i = -1; + itr->lvl = 0; + itr->pos = MTPos(0, 0); + itr->intersect_pos = MTPos(row, col); + // intersect_pos but will be adjusted relative itr->x + itr->intersect_pos_x = MTPos(row, col); + itr->intersect_idx = 0; + return true; +} + +static inline MTPair pair_from(MTKey start, MTKey end) +{ + return (MTPair){ .start = start, .end_pos = end.pos, .end_right_gravity = mt_right(end) }; +} + +/// Step through all overlapping pairs at a position. +/// +/// This function must only be used with an iterator from |marktree_itr_step_overlap| +/// +/// @return true if a valid pair was found (returned as `pair`) +/// When all overlapping mark pairs have been found, false will be returned. `itr` +/// is then valid as an ordinary iterator at the (row, col) position specified in +/// marktree_itr_step_overlap +bool marktree_itr_step_overlap(MarkTree *b, MarkTreeIter *itr, MTPair *pair) +{ + // phase one: we start at the root node and step inwards towards itr->intersect_pos + // (the position queried in marktree_itr_get_overlap) + // + // For each node (ancestor node to the node containing the sought position) + // we return all intersecting intervals, one at a time + while (itr->i == -1) { + if (itr->intersect_idx < kv_size(itr->x->intersect)) { + uint64_t id = kv_A(itr->x->intersect, itr->intersect_idx++); + *pair = pair_from(marktree_lookup(b, id, NULL), + marktree_lookup(b, id|MARKTREE_END_FLAG, NULL)); + return true; + } + + if (itr->x->level == 0) { + itr->s[itr->lvl].i = itr->i = 0; + break; + } + + MTKey k = { .pos = itr->intersect_pos_x, .flags = 0 }; + itr->i = marktree_getp_aux(itr->x, k, 0) + 1; + + itr->s[itr->lvl].i = itr->i; + itr->s[itr->lvl].oldcol = itr->pos.col; + + if (itr->i > 0) { + compose(&itr->pos, itr->x->key[itr->i - 1].pos); + relative(itr->x->key[itr->i - 1].pos, &itr->intersect_pos_x); + } + itr->x = itr->x->ptr[itr->i]; + itr->lvl++; + itr->i = -1; + itr->intersect_idx = 0; + } + + // phase two: we now need to handle the node found at itr->intersect_pos + // first consider all start nodes in the node before this position. + while (itr->i < itr->x->n && pos_less(rawkey(itr).pos, itr->intersect_pos_x)) { + MTKey k = itr->x->key[itr->i++]; + itr->s[itr->lvl].i = itr->i; + if (mt_start(k)) { + MTKey end = marktree_lookup(b, mt_lookup_id(k.ns, k.id, true), NULL); + if (pos_less(end.pos, itr->intersect_pos)) { + continue; + } + + unrelative(itr->pos, &k.pos); + *pair = pair_from(k, end); + return true; // it's a start! + } + } + + // phase 2B: We also need to step to the end of this node and consider all end marks, which + // might end an interval overlapping itr->intersect_pos + while (itr->i < itr->x->n) { + MTKey k = itr->x->key[itr->i++]; + if (mt_end(k)) { + uint64_t id = mt_lookup_id(k.ns, k.id, false); + if (id2node(b, id) == itr->x) { + continue; + } + unrelative(itr->pos, &k.pos); + MTKey start = marktree_lookup(b, id, NULL); + if (pos_less(itr->intersect_pos, start.pos)) { + continue; + } + *pair = pair_from(start, k); + return true; // end of a range which began before us! + } + } + + // when returning false, get back to the queried position, to ensure the caller + // can keep using it as an ordinary iterator at the queried position. The docstring + // for marktree_itr_get_overlap explains how this is useful. + itr->i = itr->s[itr->lvl].i; + assert(itr->i >= 0); + if (itr->i >= itr->x->n) { + marktree_itr_next(b, itr); + } + + // either on or after the intersected position, bail out + return false; +} + +static void swap_keys(MarkTree *b, MarkTreeIter *itr1, MarkTreeIter *itr2, DamageList *damage) { - mtkey_t key1 = rawkey(itr1); - mtkey_t key2 = rawkey(itr2); + if (itr1->x != itr2->x) { + if (mt_paired(rawkey(itr1))) { + kvi_push(*damage, ((Damage){ mt_lookup_key(rawkey(itr1)), itr1->x, itr2->x, + itr1->i, itr2->i })); + } + if (mt_paired(rawkey(itr2))) { + kvi_push(*damage, ((Damage){ mt_lookup_key(rawkey(itr2)), itr2->x, itr1->x, + itr2->i, itr1->i })); + } + } + + MTKey key1 = rawkey(itr1); + MTKey key2 = rawkey(itr2); rawkey(itr1) = key2; rawkey(itr1).pos = key1.pos; rawkey(itr2) = key1; rawkey(itr2).pos = key2.pos; + refkey(b, itr1->x, itr1->i); + refkey(b, itr2->x, itr2->i); +} + +static int damage_cmp(const void *s1, const void *s2) +{ + Damage *d1 = (Damage *)s1, *d2 = (Damage *)s2; + assert(d1->id != d2->id); + return d1->id > d2->id; } bool marktree_splice(MarkTree *b, int32_t start_line, int start_col, int old_extent_line, int old_extent_col, int new_extent_line, int new_extent_col) { - mtpos_t start = { start_line, start_col }; - mtpos_t old_extent = { old_extent_line, old_extent_col }; - mtpos_t new_extent = { new_extent_line, new_extent_col }; + MTPos start = { start_line, start_col }; + MTPos old_extent = { old_extent_line, old_extent_col }; + MTPos new_extent = { new_extent_line, new_extent_col }; bool may_delete = (old_extent.row != 0 || old_extent.col != 0); bool same_line = old_extent.row == 0 && new_extent.row == 0; unrelative(start, &old_extent); unrelative(start, &new_extent); - MarkTreeIter itr[1] = { 0 }; - MarkTreeIter enditr[1] = { 0 }; + MarkTreeIter itr[1] = { 0 }, enditr[1] = { 0 }; - mtpos_t oldbase[MT_MAX_DEPTH] = { 0 }; + MTPos oldbase[MT_MAX_DEPTH] = { 0 }; marktree_itr_get_ext(b, start, itr, false, true, oldbase); - if (!itr->node) { + if (!itr->x) { // den e FÄRDIG return false; } - mtpos_t delta = { new_extent.row - old_extent.row, - new_extent.col - old_extent.col }; + MTPos delta = { new_extent.row - old_extent.row, + new_extent.col - old_extent.col }; if (may_delete) { - mtpos_t ipos = marktree_itr_pos(itr); + MTPos ipos = marktree_itr_pos(itr); if (!pos_leq(old_extent, ipos) || (old_extent.row == ipos.row && old_extent.col == ipos.col && !mt_right(rawkey(itr)))) { marktree_itr_get_ext(b, old_extent, enditr, true, true, NULL); - assert(enditr->node); + assert(enditr->x); // "assert" (itr <= enditr) } else { may_delete = false; @@ -880,14 +1662,16 @@ bool marktree_splice(MarkTree *b, int32_t start_line, int start_col, int old_ext bool past_right = false; bool moved = false; + DamageList damage; + kvi_init(damage); // Follow the general strategy of messing things up and fix them later // "oldbase" carries the information needed to calculate old position of // children. if (may_delete) { - while (itr->node && !past_right) { - mtpos_t loc_start = start; - mtpos_t loc_old = old_extent; + while (itr->x && !past_right) { + MTPos loc_start = start; + MTPos loc_old = old_extent; relative(itr->pos, &loc_start); relative(oldbase[itr->lvl], &loc_old); @@ -905,9 +1689,7 @@ continue_same_node: marktree_itr_prev(b, enditr); } if (!mt_right(rawkey(enditr))) { - itr_swap(itr, enditr); - refkey(b, itr->node, itr->i); - refkey(b, enditr->node, enditr->i); + swap_keys(b, itr, enditr, &damage); } else { past_right = true; // NOLINT (void)past_right; @@ -921,14 +1703,14 @@ continue_same_node: } moved = true; - if (itr->node->level) { + if (itr->x->level) { oldbase[itr->lvl + 1] = rawkey(itr).pos; unrelative(oldbase[itr->lvl], &oldbase[itr->lvl + 1]); rawkey(itr).pos = loc_start; - marktree_itr_next_skip(b, itr, false, oldbase); + marktree_itr_next_skip(b, itr, false, false, oldbase); } else { rawkey(itr).pos = loc_start; - if (itr->i < itr->node->n - 1) { + if (itr->i < itr->x->n - 1) { itr->i++; if (!past_right) { goto continue_same_node; @@ -938,10 +1720,10 @@ continue_same_node: } } } - while (itr->node) { - mtpos_t loc_new = new_extent; + while (itr->x) { + MTPos loc_new = new_extent; relative(itr->pos, &loc_new); - mtpos_t limit = old_extent; + MTPos limit = old_extent; relative(oldbase[itr->lvl], &limit); @@ -951,16 +1733,16 @@ past_continue_same_node: break; } - mtpos_t oldpos = rawkey(itr).pos; + MTPos oldpos = rawkey(itr).pos; rawkey(itr).pos = loc_new; moved = true; - if (itr->node->level) { + if (itr->x->level) { oldbase[itr->lvl + 1] = oldpos; unrelative(oldbase[itr->lvl], &oldbase[itr->lvl + 1]); - marktree_itr_next_skip(b, itr, false, oldbase); + marktree_itr_next_skip(b, itr, false, false, oldbase); } else { - if (itr->i < itr->node->n - 1) { + if (itr->i < itr->x->n - 1) { itr->i++; goto past_continue_same_node; } else { @@ -970,7 +1752,7 @@ past_continue_same_node: } } - while (itr->node) { + while (itr->x) { unrelative(oldbase[itr->lvl], &rawkey(itr).pos); int realrow = rawkey(itr).pos.row; assert(realrow >= old_extent.row); @@ -978,7 +1760,6 @@ past_continue_same_node: if (realrow == old_extent.row) { if (delta.col) { rawkey(itr).pos.col += delta.col; - moved = true; } } else { if (same_line) { @@ -994,22 +1775,78 @@ past_continue_same_node: if (done) { break; } - marktree_itr_next_skip(b, itr, true, NULL); + marktree_itr_next_skip(b, itr, true, false, NULL); + } + + if (kv_size(damage)) { + // TODO(bfredl): a full sort is not really needed. we just need a "start" node to find + // its corresponding "end" node. Set up some dedicated hash for this later (c.f. the + // "grow only" variant of khash_t branch) + qsort((void *)&kv_A(damage, 0), kv_size(damage), sizeof(kv_A(damage, 0)), + damage_cmp); + + for (size_t i = 0; i < kv_size(damage); i++) { + Damage d = kv_A(damage, i); + if (!(d.id & MARKTREE_END_FLAG)) { // start + if (i + 1 < kv_size(damage) && kv_A(damage, i + 1).id == (d.id | MARKTREE_END_FLAG)) { + Damage d2 = kv_A(damage, i + 1); + + // pair + marktree_itr_set_node(b, itr, d.old, d.old_i); + marktree_itr_set_node(b, enditr, d2.old, d2.old_i); + marktree_intersect_pair(b, d.id, itr, enditr, true); + marktree_itr_set_node(b, itr, d.new, d.new_i); + marktree_itr_set_node(b, enditr, d2.new, d2.new_i); + marktree_intersect_pair(b, d.id, itr, enditr, false); + + i++; // consume two items + continue; + } + + // d is lone start, end didn't move + MarkTreeIter endpos[1]; + marktree_lookup(b, d.id | MARKTREE_END_FLAG, endpos); + if (endpos->x) { + marktree_itr_set_node(b, itr, d.old, d.old_i); + *enditr = *endpos; + marktree_intersect_pair(b, d.id, itr, enditr, true); + marktree_itr_set_node(b, itr, d.new, d.new_i); + *enditr = *endpos; + marktree_intersect_pair(b, d.id, itr, enditr, false); + } + } else { + // d is lone end, start didn't move + MarkTreeIter startpos[1]; + uint64_t start_id = d.id & ~MARKTREE_END_FLAG; + + marktree_lookup(b, start_id, startpos); + if (startpos->x) { + *itr = *startpos; + marktree_itr_set_node(b, enditr, d.old, d.old_i); + marktree_intersect_pair(b, start_id, itr, enditr, true); + *itr = *startpos; + marktree_itr_set_node(b, enditr, d.new, d.new_i); + marktree_intersect_pair(b, start_id, itr, enditr, false); + } + } + } } + kvi_destroy(damage); + return moved; } void marktree_move_region(MarkTree *b, int start_row, colnr_T start_col, int extent_row, colnr_T extent_col, int new_row, colnr_T new_col) { - mtpos_t start = { start_row, start_col }, size = { extent_row, extent_col }; - mtpos_t end = size; + MTPos start = { start_row, start_col }, size = { extent_row, extent_col }; + MTPos end = size; unrelative(start, &end); MarkTreeIter itr[1] = { 0 }; marktree_itr_get_ext(b, start, itr, false, true, NULL); - kvec_t(mtkey_t) saved = KV_INITIAL_VALUE; - while (itr->node) { - mtkey_t k = marktree_itr_current(itr); + kvec_t(MTKey) saved = KV_INITIAL_VALUE; + while (itr->x) { + MTKey k = marktree_itr_current(itr); if (!pos_leq(k.pos, end) || (k.pos.row == end.row && k.pos.col == end.col && mt_right(k))) { break; @@ -1020,57 +1857,101 @@ void marktree_move_region(MarkTree *b, int start_row, colnr_T start_col, int ext } marktree_splice(b, start.row, start.col, size.row, size.col, 0, 0); - mtpos_t new = { new_row, new_col }; + MTPos new = { new_row, new_col }; marktree_splice(b, new.row, new.col, 0, 0, size.row, size.col); for (size_t i = 0; i < kv_size(saved); i++) { - mtkey_t item = kv_A(saved, i); + MTKey item = kv_A(saved, i); unrelative(new, &item.pos); marktree_put_key(b, item); + if (mt_paired(item)) { + // other end might be later in `saved`, this will safely bail out then + marktree_restore_pair(b, item); + } } kv_destroy(saved); } /// @param itr OPTIONAL. set itr to pos. -mtkey_t marktree_lookup_ns(MarkTree *b, uint32_t ns, uint32_t id, bool end, MarkTreeIter *itr) +MTKey marktree_lookup_ns(MarkTree *b, uint32_t ns, uint32_t id, bool end, MarkTreeIter *itr) { return marktree_lookup(b, mt_lookup_id(ns, id, end), itr); } +static uint64_t pseudo_index(MTNode *x, int i) +{ + int off = MT_LOG2_BRANCH * x->level; + uint64_t index = 0; + + while (x) { + index |= (uint64_t)(i + 1) << off; + off += MT_LOG2_BRANCH; + i = x->p_idx; + x = x->parent; + } + + return index; +} + +/// @param itr OPTIONAL. set itr to pos. +/// if sloppy, two keys at the same _leaf_ node has the same index +static uint64_t pseudo_index_for_id(MarkTree *b, uint64_t id, bool sloppy) +{ + MTNode *n = id2node(b, id); + if (n == NULL) { + return 0; // a valid pseudo-index is never zero! + } + + int i = 0; + if (n->level || !sloppy) { + for (i = 0; i < n->n; i++) { + if (mt_lookup_key(n->key[i]) == id) { + break; + } + } + assert(i < n->n); + if (n->level) { + i += 1; // internal key i comes after ptr[i] + } + } + + return pseudo_index(n, i); +} + /// @param itr OPTIONAL. set itr to pos. -mtkey_t marktree_lookup(MarkTree *b, uint64_t id, MarkTreeIter *itr) +MTKey marktree_lookup(MarkTree *b, uint64_t id, MarkTreeIter *itr) { - mtnode_t *n = pmap_get(uint64_t)(b->id2node, id); + MTNode *n = id2node(b, id); if (n == NULL) { if (itr) { - itr->node = NULL; + itr->x = NULL; } return MT_INVALID_KEY; } int i = 0; for (i = 0; i < n->n; i++) { if (mt_lookup_key(n->key[i]) == id) { - goto found; + return marktree_itr_set_node(b, itr, n, i); } } + abort(); -found: {} - mtkey_t key = n->key[i]; +} + +MTKey marktree_itr_set_node(MarkTree *b, MarkTreeIter *itr, MTNode *n, int i) +{ + MTKey key = n->key[i]; if (itr) { itr->i = i; - itr->node = n; + itr->x = n; itr->lvl = b->root->level - n->level; } while (n->parent != NULL) { - mtnode_t *p = n->parent; - for (i = 0; i < p->n + 1; i++) { - if (p->ptr[i] == n) { - goto found_node; - } - } - abort(); -found_node: + MTNode *p = n->parent; + i = n->p_idx; + assert(p->ptr[i] == n); + if (itr) { itr->s[b->root->level - p->level].i = i; } @@ -1085,14 +1966,14 @@ found_node: return key; } -mtpos_t marktree_get_altpos(MarkTree *b, mtkey_t mark, MarkTreeIter *itr) +MTPos marktree_get_altpos(MarkTree *b, MTKey mark, MarkTreeIter *itr) { return marktree_get_alt(b, mark, itr).pos; } -mtkey_t marktree_get_alt(MarkTree *b, mtkey_t mark, MarkTreeIter *itr) +MTKey marktree_get_alt(MarkTree *b, MTKey mark, MarkTreeIter *itr) { - mtkey_t end = MT_INVALID_KEY; + MTKey end = MT_INVALID_KEY; if (mt_paired(mark)) { end = marktree_lookup_ns(b, mark.ns, mark.id, !mt_end(mark), itr); } @@ -1101,8 +1982,8 @@ mtkey_t marktree_get_alt(MarkTree *b, mtkey_t mark, MarkTreeIter *itr) static void marktree_itr_fix_pos(MarkTree *b, MarkTreeIter *itr) { - itr->pos = (mtpos_t){ 0, 0 }; - mtnode_t *x = b->root; + itr->pos = (MTPos){ 0, 0 }; + MTNode *x = b->root; for (int lvl = 0; lvl < itr->lvl; lvl++) { itr->s[lvl].oldcol = itr->pos.col; int i = itr->s[lvl].i; @@ -1112,23 +1993,36 @@ static void marktree_itr_fix_pos(MarkTree *b, MarkTreeIter *itr) assert(x->level); x = x->ptr[i]; } - assert(x == itr->node); + assert(x == itr->x); } // for unit test -void marktree_put_test(MarkTree *b, uint32_t id, int row, int col, bool right_gravity) +void marktree_put_test(MarkTree *b, uint32_t ns, uint32_t id, int row, int col, bool right_gravity, + int end_row, int end_col, bool end_right) { - mtkey_t key = { { row, col }, UINT32_MAX, id, 0, - mt_flags(right_gravity, 0), 0, NULL }; - marktree_put(b, key, -1, -1, false); + MTKey key = { { row, col }, ns, id, 0, + mt_flags(right_gravity, 0), 0, NULL }; + marktree_put(b, key, end_row, end_col, end_right); } // for unit test -bool mt_right_test(mtkey_t key) +bool mt_right_test(MTKey key) { return mt_right(key); } +// for unit test +void marktree_del_pair_test(MarkTree *b, uint32_t ns, uint32_t id) +{ + MarkTreeIter itr[1]; + marktree_lookup_ns(b, ns, id, false, itr); + + uint64_t other = marktree_del_itr(b, itr, false); + assert(other); + marktree_lookup(b, other, itr); + marktree_del_itr(b, itr, false); +} + void marktree_check(MarkTree *b) { #ifndef NDEBUG @@ -1139,7 +2033,7 @@ void marktree_check(MarkTree *b) return; } - mtpos_t dummy; + MTPos dummy; bool last_right = false; size_t nkeys = marktree_check_node(b, b->root, &dummy, &last_right); assert(b->n_keys == nkeys); @@ -1151,7 +2045,7 @@ void marktree_check(MarkTree *b) } #ifndef NDEBUG -size_t marktree_check_node(MarkTree *b, mtnode_t *x, mtpos_t *last, bool *last_right) +size_t marktree_check_node(MarkTree *b, MTNode *x, MTPos *last, bool *last_right) { assert(x->n <= 2 * T - 1); // TODO(bfredl): too strict if checking "in repair" post-delete tree. @@ -1162,7 +2056,7 @@ size_t marktree_check_node(MarkTree *b, mtnode_t *x, mtpos_t *last, bool *last_r if (x->level) { n_keys += marktree_check_node(b, x->ptr[i], last, last_right); } else { - *last = (mtpos_t) { 0, 0 }; + *last = (MTPos) { 0, 0 }; } if (i > 0) { unrelative(x->key[i - 1].pos, last); @@ -1182,6 +2076,7 @@ size_t marktree_check_node(MarkTree *b, mtnode_t *x, mtpos_t *last, bool *last_r for (int i = 0; i < x->n + 1; i++) { assert(x->ptr[i]->parent == x); + assert(x->ptr[i]->p_idx == i); assert(x->ptr[i]->level == x->level - 1); // PARANOIA: check no double node ref for (int j = 0; j < i; j++) { @@ -1193,34 +2088,221 @@ size_t marktree_check_node(MarkTree *b, mtnode_t *x, mtpos_t *last, bool *last_r } return n_keys; } + +bool marktree_check_intersections(MarkTree *b) +{ + if (!b->root) { + return true; + } + PMap(ptr_t) checked = MAP_INIT; + + // 1. move x->intersect to checked[x] and reinit x->intersect + mt_recurse_nodes(b->root, &checked); + + // 2. iterate over all marks. for each START mark of a pair, + // intersect the nodes between the pair + MarkTreeIter itr[1]; + marktree_itr_first(b, itr); + while (true) { + MTKey mark = marktree_itr_current(itr); + if (mark.pos.row < 0) { + break; + } + + if (mt_start(mark)) { + MarkTreeIter start_itr[1]; + MarkTreeIter end_itr[1]; + uint64_t end_id = mt_lookup_id(mark.ns, mark.id, true); + MTKey k = marktree_lookup(b, end_id, end_itr); + if (k.pos.row >= 0) { + *start_itr = *itr; + marktree_intersect_pair(b, mt_lookup_key(mark), start_itr, end_itr, false); + } + } + + marktree_itr_next(b, itr); + } + + // 3. for each node check if the recreated intersection + // matches the old checked[x] intersection. + bool status = mt_recurse_nodes_compare(b->root, &checked); + + uint64_t *val; + map_foreach_value(&checked, val, { + xfree(val); + }); + map_destroy(ptr_t, &checked); + + return status; +} + +void mt_recurse_nodes(MTNode *x, PMap(ptr_t) *checked) +{ + if (kv_size(x->intersect)) { + kvi_push(x->intersect, (uint64_t)-1); // sentinel + uint64_t *val; + if (x->intersect.items == x->intersect.init_array) { + val = xmemdup(x->intersect.items, x->intersect.size * sizeof(*x->intersect.items)); + } else { + val = x->intersect.items; + } + pmap_put(ptr_t)(checked, x, val); + kvi_init(x->intersect); + } + + if (x->level) { + for (int i = 0; i < x->n + 1; i++) { + mt_recurse_nodes(x->ptr[i], checked); + } + } +} + +bool mt_recurse_nodes_compare(MTNode *x, PMap(ptr_t) *checked) +{ + uint64_t *ref = pmap_get(ptr_t)(checked, x); + if (ref != NULL) { + for (size_t i = 0;; i++) { + if (ref[i] == (uint64_t)-1) { + if (i != kv_size(x->intersect)) { + return false; + } + + break; + } else { + if (kv_size(x->intersect) <= i || ref[i] != kv_A(x->intersect, i)) { + return false; + } + } + } + } else { + if (kv_size(x->intersect)) { + return false; + } + } + + if (x->level) { + for (int i = 0; i < x->n + 1; i++) { + if (!mt_recurse_nodes_compare(x->ptr[i], checked)) { + return false; + } + } + } + + return true; +} + #endif -char *mt_inspect_rec(MarkTree *b) +// TODO(bfredl): kv_print +#define GA_PUT(x) ga_concat(ga, (char *)(x)) +#define GA_PRINT(fmt, ...) snprintf(buf, sizeof(buf), fmt, __VA_ARGS__); \ + GA_PUT(buf); + +String mt_inspect(MarkTree *b, bool keys, bool dot) { - garray_T ga; - ga_init(&ga, (int)sizeof(char), 80); - mtpos_t p = { 0, 0 }; - mt_inspect_node(b, &ga, b->root, p); - return ga.ga_data; + garray_T ga[1]; + ga_init(ga, (int)sizeof(char), 80); + MTPos p = { 0, 0 }; + if (b->root) { + if (dot) { + GA_PUT("digraph D {\n\n"); + mt_inspect_dotfile_node(b, ga, b->root, p, NULL); + GA_PUT("\n}"); + } else { + mt_inspect_node(b, ga, keys, b->root, p); + } + } + return ga_take_string(ga); } -void mt_inspect_node(MarkTree *b, garray_T *ga, mtnode_t *n, mtpos_t off) +void mt_inspect_node(MarkTree *b, garray_T *ga, bool keys, MTNode *n, MTPos off) { static char buf[1024]; - ga_concat(ga, "["); + GA_PUT("["); + if (keys && kv_size(n->intersect)) { + for (size_t i = 0; i < kv_size(n->intersect); i++) { + GA_PUT(i == 0 ? "{" : ";"); + // GA_PRINT("%"PRIu64, kv_A(n->intersect, i)); + GA_PRINT("%" PRIu64, mt_dbg_id(kv_A(n->intersect, i))); + } + GA_PUT("},"); + } if (n->level) { - mt_inspect_node(b, ga, n->ptr[0], off); + mt_inspect_node(b, ga, keys, n->ptr[0], off); } for (int i = 0; i < n->n; i++) { - mtpos_t p = n->key[i].pos; + MTPos p = n->key[i].pos; unrelative(off, &p); - snprintf(buf, sizeof(buf), "%d/%d", p.row, p.col); - ga_concat(ga, buf); + GA_PRINT("%d/%d", p.row, p.col); + if (keys) { + MTKey key = n->key[i]; + GA_PUT(":"); + if (mt_start(key)) { + GA_PUT("<"); + } + // GA_PRINT("%"PRIu64, mt_lookup_id(key.ns, key.id, false)); + GA_PRINT("%" PRIu32, key.id); + if (mt_end(key)) { + GA_PUT(">"); + } + } if (n->level) { - mt_inspect_node(b, ga, n->ptr[i + 1], p); + mt_inspect_node(b, ga, keys, n->ptr[i + 1], p); } else { ga_concat(ga, ","); } } ga_concat(ga, "]"); } + +void mt_inspect_dotfile_node(MarkTree *b, garray_T *ga, MTNode *n, MTPos off, char *parent) +{ + static char buf[1024]; + char namebuf[64]; + if (parent != NULL) { + snprintf(namebuf, sizeof namebuf, "%s_%c%d", parent, 'a' + n->level, n->p_idx); + } else { + snprintf(namebuf, sizeof namebuf, "Node"); + } + + GA_PRINT(" %s[shape=plaintext, label=<\n", namebuf); + GA_PUT(" <table border='0' cellborder='1' cellspacing='0'>\n"); + if (kv_size(n->intersect)) { + GA_PUT(" <tr><td>"); + for (size_t i = 0; i < kv_size(n->intersect); i++) { + if (i > 0) { + GA_PUT(", "); + } + GA_PRINT("%" PRIu64, mt_dbg_id(kv_A(n->intersect, i))); + } + GA_PUT("</td></tr>\n"); + } + + GA_PUT(" <tr><td>"); + for (int i = 0; i < n->n; i++) { + MTKey k = n->key[i]; + if (i > 0) { + GA_PUT(", "); + } + GA_PRINT("%d", k.id); + if (mt_paired(k)) { + GA_PUT(mt_end(k) ? "e" : "s"); + } + } + GA_PUT("</td></tr>\n"); + GA_PUT(" </table>\n"); + GA_PUT(">];\n"); + if (parent) { + GA_PRINT(" %s -> %s\n", parent, namebuf); + } + if (n->level) { + mt_inspect_dotfile_node(b, ga, n->ptr[0], off, namebuf); + } + for (int i = 0; i < n->n; i++) { + MTPos p = n->key[i].pos; + unrelative(off, &p); + if (n->level) { + mt_inspect_dotfile_node(b, ga, n->ptr[i + 1], p, namebuf); + } + } +} diff --git a/src/nvim/marktree.h b/src/nvim/marktree.h index cd56115b58..f53a54f3cc 100644 --- a/src/nvim/marktree.h +++ b/src/nvim/marktree.h @@ -6,29 +6,37 @@ #include <stddef.h> #include <stdint.h> +#include "klib/kvec.h" #include "nvim/assert.h" #include "nvim/garray.h" #include "nvim/map.h" #include "nvim/pos.h" #include "nvim/types.h" +// only for debug functions: +#include "api/private/defs.h" + struct mtnode_s; #define MT_MAX_DEPTH 20 #define MT_BRANCH_FACTOR 10 +// note max branch is actually 2*MT_BRANCH_FACTOR +// and strictly this is ceil(log2(2*MT_BRANCH_FACTOR + 1)) +// as we need a pseudo-index for "right before this node" +#define MT_LOG2_BRANCH 5 typedef struct { int32_t row; int32_t col; -} mtpos_t; -#define mtpos_t(r, c) ((mtpos_t){ .row = (r), .col = (c) }) +} MTPos; +#define MTPos(r, c) ((MTPos){ .row = (r), .col = (c) }) -typedef struct mtnode_s mtnode_t; +typedef struct mtnode_s MTNode; typedef struct { - mtpos_t pos; + MTPos pos; int lvl; - mtnode_t *node; + MTNode *x; int i; struct { int oldcol; @@ -36,33 +44,43 @@ typedef struct { } s[MT_MAX_DEPTH]; size_t intersect_idx; - mtpos_t intersect_pos; + MTPos intersect_pos; + MTPos intersect_pos_x; } MarkTreeIter; -#define marktree_itr_valid(itr) ((itr)->node != NULL) +#define marktree_itr_valid(itr) ((itr)->x != NULL) // Internal storage // // NB: actual marks have flags > 0, so we can use (row,col,0) pseudo-key for // "space before (row,col)" typedef struct { - mtpos_t pos; + MTPos pos; uint32_t ns; uint32_t id; int32_t hl_id; uint16_t flags; uint16_t priority; Decoration *decor_full; -} mtkey_t; -#define MT_INVALID_KEY (mtkey_t) { { -1, -1 }, 0, 0, 0, 0, 0, NULL } +} MTKey; + +typedef struct { + MTKey start; + MTPos end_pos; + bool end_right_gravity; +} MTPair; + +#define MT_INVALID_KEY (MTKey) { { -1, -1 }, 0, 0, 0, 0, 0, NULL } #define MT_FLAG_REAL (((uint16_t)1) << 0) #define MT_FLAG_END (((uint16_t)1) << 1) #define MT_FLAG_PAIRED (((uint16_t)1) << 2) -#define MT_FLAG_HL_EOL (((uint16_t)1) << 3) +// orphaned: the other side of this paired mark was deleted. this mark must be deleted very soon! +#define MT_FLAG_ORPHANED (((uint16_t)1) << 3) +#define MT_FLAG_HL_EOL (((uint16_t)1) << 4) #define DECOR_LEVELS 4 -#define MT_FLAG_DECOR_OFFSET 4 +#define MT_FLAG_DECOR_OFFSET 5 #define MT_FLAG_DECOR_MASK (((uint16_t)(DECOR_LEVELS - 1)) << MT_FLAG_DECOR_OFFSET) // next flag is (((uint16_t)1) << 6) @@ -73,39 +91,44 @@ typedef struct { #define MT_FLAG_EXTERNAL_MASK (MT_FLAG_DECOR_MASK | MT_FLAG_RIGHT_GRAVITY | MT_FLAG_HL_EOL) -#define MARKTREE_END_FLAG (((uint64_t)1) << 63) +// this is defined so that start and end of the same range have adjacent ids +#define MARKTREE_END_FLAG ((uint64_t)1) static inline uint64_t mt_lookup_id(uint32_t ns, uint32_t id, bool enda) { - return (uint64_t)ns << 32 | id | (enda ? MARKTREE_END_FLAG : 0); + return (uint64_t)ns << 33 | (id <<1) | (enda ? MARKTREE_END_FLAG : 0); } -#undef MARKTREE_END_FLAG -static inline uint64_t mt_lookup_key(mtkey_t key) +static inline uint64_t mt_lookup_key_side(MTKey key, bool end) +{ + return mt_lookup_id(key.ns, key.id, end); +} + +static inline uint64_t mt_lookup_key(MTKey key) { return mt_lookup_id(key.ns, key.id, key.flags & MT_FLAG_END); } -static inline bool mt_paired(mtkey_t key) +static inline bool mt_paired(MTKey key) { return key.flags & MT_FLAG_PAIRED; } -static inline bool mt_end(mtkey_t key) +static inline bool mt_end(MTKey key) { return key.flags & MT_FLAG_END; } -static inline bool mt_start(mtkey_t key) +static inline bool mt_start(MTKey key) { return mt_paired(key) && !mt_end(key); } -static inline bool mt_right(mtkey_t key) +static inline bool mt_right(MTKey key) { return key.flags & MT_FLAG_RIGHT_GRAVITY; } -static inline uint8_t marktree_decor_level(mtkey_t key) +static inline uint8_t marktree_decor_level(MTKey key) { return (uint8_t)((key.flags&MT_FLAG_DECOR_MASK) >> MT_FLAG_DECOR_OFFSET); } @@ -117,18 +140,27 @@ static inline uint16_t mt_flags(bool right_gravity, uint8_t decor_level) | (decor_level << MT_FLAG_DECOR_OFFSET)); } +typedef kvec_withinit_t(uint64_t, 4) Intersection; + struct mtnode_s { int32_t n; - int32_t level; + int16_t level; + int16_t p_idx; // index in parent + Intersection intersect; // TODO(bfredl): we could consider having a only-sometimes-valid // index into parent for faster "cached" lookup. - mtnode_t *parent; - mtkey_t key[2 * MT_BRANCH_FACTOR - 1]; - mtnode_t *ptr[]; + MTNode *parent; + MTKey key[2 * MT_BRANCH_FACTOR - 1]; + MTNode *ptr[]; }; +static inline uint64_t mt_dbg_id(uint64_t id) +{ + return (id>>1)&0xffffffff; +} + typedef struct { - mtnode_t *root; + MTNode *root; size_t n_keys, n_nodes; // TODO(bfredl): the pointer to node could be part of the larger // Map(uint64_t, ExtmarkItem) essentially; diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index fd9efb1387..6182646fe7 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -1648,7 +1648,7 @@ bool utf_allow_break_before(int cc) 0x2021, // ‡ double dagger 0x2026, // … horizontal ellipsis 0x2030, // ‰ per mille sign - 0x2031, // ‱ per then thousand sign + 0x2031, // ‱ per the thousand sign 0x203c, // ‼ double exclamation mark 0x2047, // ⁇ double question mark 0x2048, // ⁈ question exclamation mark diff --git a/src/nvim/memfile.c b/src/nvim/memfile.c index 333ff75f7b..6722d6bd8a 100644 --- a/src/nvim/memfile.c +++ b/src/nvim/memfile.c @@ -101,11 +101,9 @@ memfile_T *mf_open(char *fname, int flags) } mfp->mf_free_first = NULL; // free list is empty - mfp->mf_used_first = NULL; // used list is empty - mfp->mf_used_last = NULL; mfp->mf_dirty = MF_DIRTY_NO; - mf_hash_init(&mfp->mf_hash); - mf_hash_init(&mfp->mf_trans); + mfp->mf_hash = (PMap(int64_t)) MAP_INIT; + mfp->mf_trans = (Map(int64_t, int64_t)) MAP_INIT; mfp->mf_page_size = MEMFILE_PAGE_SIZE; // Try to set the page size equal to device's block size. Speeds up I/O a lot. @@ -182,15 +180,15 @@ void mf_close(memfile_T *mfp, bool del_file) } // free entries in used list - for (bhdr_T *hp = mfp->mf_used_first, *nextp; hp != NULL; hp = nextp) { - nextp = hp->bh_next; + bhdr_T *hp; + map_foreach_value(&mfp->mf_hash, hp, { mf_free_bhdr(hp); - } + }) while (mfp->mf_free_first != NULL) { // free entries in free list xfree(mf_rem_free(mfp)); } - mf_hash_free(&mfp->mf_hash); - mf_hash_free_all(&mfp->mf_trans); // free hashtable and its items + map_destroy(int64_t, &mfp->mf_hash); + map_destroy(int64_t, &mfp->mf_trans); // free hashtable and its items mf_free_fnames(mfp); xfree(mfp); } @@ -271,8 +269,7 @@ bhdr_T *mf_new(memfile_T *mfp, bool negative, unsigned page_count) hp->bh_flags = BH_LOCKED | BH_DIRTY; // new block is always dirty mfp->mf_dirty = MF_DIRTY_YES; hp->bh_page_count = page_count; - mf_ins_used(mfp, hp); - mf_ins_hash(mfp, hp); + pmap_put(int64_t)(&mfp->mf_hash, hp->bh_bnum, hp); // Init the data to all zero, to avoid reading uninitialized data. // This also avoids that the passwd file ends up in the swap file! @@ -294,7 +291,7 @@ bhdr_T *mf_get(memfile_T *mfp, blocknr_T nr, unsigned page_count) } // see if it is in the cache - bhdr_T *hp = mf_find_hash(mfp, nr); + bhdr_T *hp = pmap_get(int64_t)(&mfp->mf_hash, nr); if (hp == NULL) { // not in the hash list if (nr < 0 || nr >= mfp->mf_infile_count) { // can't be in the file return NULL; @@ -317,13 +314,11 @@ bhdr_T *mf_get(memfile_T *mfp, blocknr_T nr, unsigned page_count) return NULL; } } else { - mf_rem_used(mfp, hp); // remove from list, insert in front below - mf_rem_hash(mfp, hp); + pmap_del(int64_t)(&mfp->mf_hash, hp->bh_bnum, NULL); } hp->bh_flags |= BH_LOCKED; - mf_ins_used(mfp, hp); // put in front of used list - mf_ins_hash(mfp, hp); // put in front of hash list + pmap_put(int64_t)(&mfp->mf_hash, hp->bh_bnum, hp); // put in front of hash table return hp; } @@ -356,8 +351,7 @@ void mf_put(memfile_T *mfp, bhdr_T *hp, bool dirty, bool infile) void mf_free(memfile_T *mfp, bhdr_T *hp) { xfree(hp->bh_data); // free data - mf_rem_hash(mfp, hp); // get *hp out of the hash list - mf_rem_used(mfp, hp); // get *hp out of the used list + pmap_del(int64_t)(&mfp->mf_hash, hp->bh_bnum, NULL); // get *hp out of the hash table if (hp->bh_bnum < 0) { xfree(hp); // don't want negative numbers in free list mfp->mf_neg_count--; @@ -399,7 +393,8 @@ int mf_sync(memfile_T *mfp, int flags) // fails then we give up. int status = OK; bhdr_T *hp; - for (hp = mfp->mf_used_last; hp != NULL; hp = hp->bh_prev) { + // note, "last" block is typically earlier in the hash list + map_foreach_value(&mfp->mf_hash, hp, { if (((flags & MFS_ALL) || hp->bh_bnum >= 0) && (hp->bh_flags & BH_DIRTY) && (status == OK || (hp->bh_bnum >= 0 @@ -424,7 +419,7 @@ int mf_sync(memfile_T *mfp, int flags) break; } } - } + }) // If the whole list is flushed, the memfile is not dirty anymore. // In case of an error, dirty flag is also set, to avoid trying all the time. @@ -447,61 +442,15 @@ int mf_sync(memfile_T *mfp, int flags) /// These are blocks that need to be written to a newly created swapfile. void mf_set_dirty(memfile_T *mfp) { - for (bhdr_T *hp = mfp->mf_used_last; hp != NULL; hp = hp->bh_prev) { + bhdr_T *hp; + map_foreach_value(&mfp->mf_hash, hp, { if (hp->bh_bnum > 0) { hp->bh_flags |= BH_DIRTY; } - } + }) mfp->mf_dirty = MF_DIRTY_YES; } -/// Insert block in front of memfile's hash list. -static void mf_ins_hash(memfile_T *mfp, bhdr_T *hp) -{ - mf_hash_add_item(&mfp->mf_hash, (mf_hashitem_T *)hp); -} - -/// Remove block from memfile's hash list. -static void mf_rem_hash(memfile_T *mfp, bhdr_T *hp) -{ - mf_hash_rem_item(&mfp->mf_hash, (mf_hashitem_T *)hp); -} - -/// Lookup block with number "nr" in memfile's hash list. -static bhdr_T *mf_find_hash(memfile_T *mfp, blocknr_T nr) -{ - return (bhdr_T *)mf_hash_find(&mfp->mf_hash, nr); -} - -/// Insert block at the front of memfile's used list. -static void mf_ins_used(memfile_T *mfp, bhdr_T *hp) -{ - hp->bh_next = mfp->mf_used_first; - mfp->mf_used_first = hp; - hp->bh_prev = NULL; - if (hp->bh_next == NULL) { // list was empty, adjust last pointer - mfp->mf_used_last = hp; - } else { - hp->bh_next->bh_prev = hp; - } -} - -/// Remove block from memfile's used list. -static void mf_rem_used(memfile_T *mfp, bhdr_T *hp) -{ - if (hp->bh_next == NULL) { // last block in used list - mfp->mf_used_last = hp->bh_prev; - } else { - hp->bh_next->bh_prev = hp->bh_prev; - } - - if (hp->bh_prev == NULL) { // first block in used list - mfp->mf_used_first = hp->bh_next; - } else { - hp->bh_prev->bh_next = hp->bh_next; - } -} - /// Release as many blocks as possible. /// /// Used in case of out of memory @@ -520,17 +469,18 @@ bool mf_release_all(void) // Flush as many blocks as possible, only if there is a swapfile. if (mfp->mf_fd >= 0) { - for (bhdr_T *hp = mfp->mf_used_last; hp != NULL;) { + for (int i = 0; i < (int)map_size(&mfp->mf_hash);) { + bhdr_T *hp = mfp->mf_hash.values[i]; if (!(hp->bh_flags & BH_LOCKED) && (!(hp->bh_flags & BH_DIRTY) || mf_write(mfp, hp) != FAIL)) { - mf_rem_used(mfp, hp); - mf_rem_hash(mfp, hp); + pmap_del(int64_t)(&mfp->mf_hash, hp->bh_bnum, NULL); mf_free_bhdr(hp); - hp = mfp->mf_used_last; // restart, list was changed retval = true; + // Rerun with the same value of i. another item will have taken + // its place (or it was the last) } else { - hp = hp->bh_prev; + i++; } } } @@ -558,7 +508,7 @@ static void mf_free_bhdr(bhdr_T *hp) /// Insert a block in the free list. static void mf_ins_free(memfile_T *mfp, bhdr_T *hp) { - hp->bh_next = mfp->mf_free_first; + hp->bh_data = mfp->mf_free_first; mfp->mf_free_first = hp; } @@ -568,7 +518,7 @@ static void mf_ins_free(memfile_T *mfp, bhdr_T *hp) static bhdr_T *mf_rem_free(memfile_T *mfp) { bhdr_T *hp = mfp->mf_free_first; - mfp->mf_free_first = hp->bh_next; + mfp->mf_free_first = hp->bh_data; return hp; } @@ -637,7 +587,7 @@ static int mf_write(memfile_T *mfp, bhdr_T *hp) blocknr_T nr = hp->bh_bnum; // block nr which is being written if (nr > mfp->mf_infile_count) { // beyond end of file nr = mfp->mf_infile_count; - hp2 = mf_find_hash(mfp, nr); // NULL caught below + hp2 = pmap_get(int64_t)(&mfp->mf_hash, nr); // NULL caught below } else { hp2 = hp; } @@ -690,8 +640,6 @@ static int mf_trans_add(memfile_T *mfp, bhdr_T *hp) return OK; } - mf_blocknr_trans_item_T *np = xmalloc(sizeof(mf_blocknr_trans_item_T)); - // Get a new number for the block. // If the first item in the free list has sufficient pages, use its number. // Otherwise use mf_blocknr_max. @@ -714,15 +662,13 @@ static int mf_trans_add(memfile_T *mfp, bhdr_T *hp) mfp->mf_blocknr_max += page_count; } - np->nt_old_bnum = hp->bh_bnum; // adjust number - np->nt_new_bnum = new_bnum; - - mf_rem_hash(mfp, hp); // remove from old hash list + blocknr_T old_bnum = hp->bh_bnum; // adjust number + pmap_del(int64_t)(&mfp->mf_hash, hp->bh_bnum, NULL); hp->bh_bnum = new_bnum; - mf_ins_hash(mfp, hp); // insert in new hash list + pmap_put(int64_t)(&mfp->mf_hash, new_bnum, hp); // Insert "np" into "mf_trans" hashtable with key "np->nt_old_bnum". - mf_hash_add_item(&mfp->mf_trans, (mf_hashitem_T *)np); + map_put(int64_t, int64_t)(&mfp->mf_trans, old_bnum, new_bnum); return OK; } @@ -733,20 +679,16 @@ static int mf_trans_add(memfile_T *mfp, bhdr_T *hp) /// The old number When not found. blocknr_T mf_trans_del(memfile_T *mfp, blocknr_T old_nr) { - mf_blocknr_trans_item_T *np = - (mf_blocknr_trans_item_T *)mf_hash_find(&mfp->mf_trans, old_nr); - - if (np == NULL) { // not found + blocknr_T *num = map_ref(int64_t, int64_t)(&mfp->mf_trans, old_nr, false); + if (num == NULL) { // not found return old_nr; } mfp->mf_neg_count--; - blocknr_T new_bnum = np->nt_new_bnum; + blocknr_T new_bnum = *num; // remove entry from the trans list - mf_hash_rem_item(&mfp->mf_trans, (mf_hashitem_T *)np); - - xfree(np); + map_del(int64_t, int64_t)(&mfp->mf_trans, old_nr, NULL); return new_bnum; } @@ -810,7 +752,7 @@ static bool mf_do_open(memfile_T *mfp, char *fname, int flags) emsg(_("E300: Swap file already exists (symlink attack?)")); } else { // try to open the file - mfp->mf_fd = MCH_OPEN_RW(mfp->mf_fname, flags | O_NOFOLLOW); + mfp->mf_fd = os_open(mfp->mf_fname, flags | O_NOFOLLOW, S_IREAD | S_IWRITE); } // If the file cannot be opened, use memory only @@ -823,152 +765,3 @@ static bool mf_do_open(memfile_T *mfp, char *fname, int flags) return true; } - -// -// Implementation of mf_hashtab_T. -// - -/// The number of buckets in the hashtable is increased by a factor of -/// MHT_GROWTH_FACTOR when the average number of items per bucket -/// exceeds 2 ^ MHT_LOG_LOAD_FACTOR. -enum { - MHT_LOG_LOAD_FACTOR = 6, - MHT_GROWTH_FACTOR = 2, // must be a power of two -}; - -/// Initialize an empty hash table. -static void mf_hash_init(mf_hashtab_T *mht) -{ - CLEAR_POINTER(mht); - mht->mht_buckets = mht->mht_small_buckets; - mht->mht_mask = MHT_INIT_SIZE - 1; -} - -/// Free the array of a hash table. Does not free the items it contains! -/// The hash table must not be used again without another mf_hash_init() call. -static void mf_hash_free(mf_hashtab_T *mht) -{ - if (mht->mht_buckets != mht->mht_small_buckets) { - xfree(mht->mht_buckets); - } -} - -/// Free the array of a hash table and all the items it contains. -static void mf_hash_free_all(mf_hashtab_T *mht) -{ - for (size_t idx = 0; idx <= mht->mht_mask; idx++) { - mf_hashitem_T *next; - for (mf_hashitem_T *mhi = mht->mht_buckets[idx]; mhi != NULL; mhi = next) { - next = mhi->mhi_next; - xfree(mhi); - } - } - - mf_hash_free(mht); -} - -/// Find by key. -/// -/// @return A pointer to a mf_hashitem_T or NULL if the item was not found. -static mf_hashitem_T *mf_hash_find(mf_hashtab_T *mht, blocknr_T key) -{ - mf_hashitem_T *mhi = mht->mht_buckets[(size_t)key & mht->mht_mask]; - while (mhi != NULL && mhi->mhi_key != key) { - mhi = mhi->mhi_next; - } - return mhi; -} - -/// Add item to hashtable. Item must not be NULL. -static void mf_hash_add_item(mf_hashtab_T *mht, mf_hashitem_T *mhi) -{ - size_t idx = (size_t)mhi->mhi_key & mht->mht_mask; - mhi->mhi_next = mht->mht_buckets[idx]; - mhi->mhi_prev = NULL; - if (mhi->mhi_next != NULL) { - mhi->mhi_next->mhi_prev = mhi; - } - mht->mht_buckets[idx] = mhi; - - mht->mht_count++; - - /// Grow hashtable when we have more thank 2^MHT_LOG_LOAD_FACTOR - /// items per bucket on average. - if ((mht->mht_count >> MHT_LOG_LOAD_FACTOR) > mht->mht_mask) { - mf_hash_grow(mht); - } -} - -/// Remove item from hashtable. Item must be non NULL and within hashtable. -static void mf_hash_rem_item(mf_hashtab_T *mht, mf_hashitem_T *mhi) -{ - if (mhi->mhi_prev == NULL) { - mht->mht_buckets[(size_t)mhi->mhi_key & mht->mht_mask] = - mhi->mhi_next; - } else { - mhi->mhi_prev->mhi_next = mhi->mhi_next; - } - - if (mhi->mhi_next != NULL) { - mhi->mhi_next->mhi_prev = mhi->mhi_prev; - } - - mht->mht_count--; - - // We could shrink the table here, but it typically takes little memory, - // so why bother? -} - -/// Increase number of buckets in the hashtable by MHT_GROWTH_FACTOR and -/// rehash items. -static void mf_hash_grow(mf_hashtab_T *mht) -{ - size_t size = (mht->mht_mask + 1) * MHT_GROWTH_FACTOR * sizeof(void *); - mf_hashitem_T **buckets = xcalloc(1, size); - - int shift = 0; - while ((mht->mht_mask >> shift) != 0) { - shift++; - } - - for (size_t i = 0; i <= mht->mht_mask; i++) { - /// Traverse the items in the i-th original bucket and move them into - /// MHT_GROWTH_FACTOR new buckets, preserving their relative order - /// within each new bucket. Preserving the order is important because - /// mf_get() tries to keep most recently used items at the front of - /// each bucket. - /// - /// Here we strongly rely on the fact that hashes are computed modulo - /// a power of two. - - mf_hashitem_T *tails[MHT_GROWTH_FACTOR]; - CLEAR_FIELD(tails); - - for (mf_hashitem_T *mhi = mht->mht_buckets[i]; - mhi != NULL; mhi = mhi->mhi_next) { - size_t j = (mhi->mhi_key >> shift) & (MHT_GROWTH_FACTOR - 1); - if (tails[j] == NULL) { - buckets[i + (j << shift)] = mhi; - tails[j] = mhi; - mhi->mhi_prev = NULL; - } else { - tails[j]->mhi_next = mhi; - mhi->mhi_prev = tails[j]; - tails[j] = mhi; - } - } - - for (size_t j = 0; j < MHT_GROWTH_FACTOR; j++) { - if (tails[j] != NULL) { - tails[j]->mhi_next = NULL; - } - } - } - - if (mht->mht_buckets != mht->mht_small_buckets) { - xfree(mht->mht_buckets); - } - - mht->mht_buckets = buckets; - mht->mht_mask = (mht->mht_mask + 1) * MHT_GROWTH_FACTOR - 1; -} diff --git a/src/nvim/memfile_defs.h b/src/nvim/memfile_defs.h index 917dd6a905..bf9bb208a4 100644 --- a/src/nvim/memfile_defs.h +++ b/src/nvim/memfile_defs.h @@ -5,6 +5,7 @@ #include <stdint.h> #include <stdlib.h> +#include "nvim/map.h" #include "nvim/pos.h" #include "nvim/types.h" @@ -15,57 +16,20 @@ /// with negative numbers are currently in memory only. typedef int64_t blocknr_T; -/// A hash item. -/// -/// Items' keys are block numbers. -/// Items in the same bucket are organized into a doubly-linked list. -/// -/// Therefore, items can be arbitrary data structures beginning with pointers -/// for the list and and a block number key. -typedef struct mf_hashitem { - struct mf_hashitem *mhi_next; - struct mf_hashitem *mhi_prev; - blocknr_T mhi_key; -} mf_hashitem_T; - -/// Initial size for a hashtable. -#define MHT_INIT_SIZE 64 - -/// A chained hashtable with block numbers as keys and arbitrary data structures -/// as items. -/// -/// This is an intrusive data structure: we require that items begin with -/// mf_hashitem_T which contains the key and linked list pointers. List of items -/// in each bucket is doubly-linked. -typedef struct mf_hashtab { - size_t mht_mask; ///< mask used to mod hash value to array index - ///< (nr of items in array is 'mht_mask + 1') - size_t mht_count; ///< number of items inserted - mf_hashitem_T **mht_buckets; ///< points to the array of buckets (can be - ///< mht_small_buckets or a newly allocated array - ///< when mht_small_buckets becomes too small) - mf_hashitem_T *mht_small_buckets[MHT_INIT_SIZE]; ///< initial buckets -} mf_hashtab_T; - /// A block header. /// /// There is a block header for each previously used block in the memfile. /// /// The block may be linked in the used list OR in the free list. -/// The used blocks are also kept in hash lists. /// /// The used list is a doubly linked list, most recently used block first. /// The blocks in the used list have a block of memory allocated. -/// The hash lists are used to quickly find a block in the used list. /// The free list is a single linked list, not sorted. /// The blocks in the free list have no block of memory allocated and /// the contents of the block in the file (if any) is irrelevant. typedef struct bhdr { - mf_hashitem_T bh_hashitem; ///< header for hash table and key -#define bh_bnum bh_hashitem.mhi_key ///< block number, part of bh_hashitem + blocknr_T bh_bnum; ///< key used in hash table - struct bhdr *bh_next; ///< next block header in free or used list - struct bhdr *bh_prev; ///< previous block header in used list void *bh_data; ///< pointer to memory (for used block) unsigned bh_page_count; ///< number of pages in this block @@ -74,18 +38,6 @@ typedef struct bhdr { unsigned bh_flags; ///< BH_DIRTY or BH_LOCKED } bhdr_T; -/// A block number translation list item. -/// -/// When a block with a negative number is flushed to the file, it gets -/// a positive number. Because the reference to the block is still the negative -/// number, we remember the translation to the new positive number in the -/// double linked trans lists. The structure is the same as the hash lists. -typedef struct mf_blocknr_trans_item { - mf_hashitem_T nt_hashitem; ///< header for hash table and key -#define nt_old_bnum nt_hashitem.mhi_key ///< old, negative, number - blocknr_T nt_new_bnum; ///< new, positive, number -} mf_blocknr_trans_item_T; - typedef enum { MF_DIRTY_NO = 0, ///< no dirty blocks MF_DIRTY_YES, ///< there are dirty blocks @@ -98,10 +50,16 @@ typedef struct memfile { char *mf_ffname; ///< idem, full path int mf_fd; ///< file descriptor bhdr_T *mf_free_first; ///< first block header in free list - bhdr_T *mf_used_first; ///< mru block header in used list - bhdr_T *mf_used_last; ///< lru block header in used list - mf_hashtab_T mf_hash; ///< hash lists - mf_hashtab_T mf_trans; ///< trans lists + + /// The used blocks are kept in mf_hash. + /// mf_hash are used to quickly find a block in the used list. + PMap(int64_t) mf_hash; + + /// When a block with a negative number is flushed to the file, it gets + /// a positive number. Because the reference to the block is still the negative + /// number, we remember the translation to the new positive number. + Map(int64_t, int64_t) mf_trans; + blocknr_T mf_blocknr_max; ///< highest positive block number + 1 blocknr_T mf_blocknr_min; ///< lowest negative block number - 1 blocknr_T mf_neg_count; ///< number of negative blocks numbers diff --git a/src/nvim/memline.c b/src/nvim/memline.c index ff5f621611..dc9173910e 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -92,11 +92,6 @@ # include <time.h> #endif -typedef struct block0 ZERO_BL; // contents of the first block -typedef struct pointer_block PTR_BL; // contents of a pointer block -typedef struct data_block DATA_BL; // contents of a data block -typedef struct pointer_entry PTR_EN; // block/line-count pair - enum { DATA_ID = (('d' << 8) + 'a'), // data block id PTR_ID = (('p' << 8) + 't'), // pointer block id @@ -105,32 +100,32 @@ enum { }; // pointer to a block, used in a pointer block -struct pointer_entry { +typedef struct { blocknr_T pe_bnum; // block number linenr_T pe_line_count; // number of lines in this branch linenr_T pe_old_lnum; // lnum for this block (for recovery) int pe_page_count; // number of pages in block pe_bnum -}; +} PointerEntry; // A pointer block contains a list of branches in the tree. -struct pointer_block { +typedef struct { uint16_t pb_id; // ID for pointer block: PTR_ID uint16_t pb_count; // number of pointers in this block uint16_t pb_count_max; // maximum value for pb_count - PTR_EN pb_pointer[]; // list of pointers to blocks + PointerEntry pb_pointer[]; // list of pointers to blocks // followed by empty space until end of page -}; +} PointerBlock; // Value for pb_count_max. #define PB_COUNT_MAX(mfp) \ - (uint16_t)((mfp->mf_page_size - offsetof(PTR_BL, pb_pointer)) / sizeof(PTR_EN)) + (uint16_t)((mfp->mf_page_size - offsetof(PointerBlock, pb_pointer)) / sizeof(PointerEntry)) // A data block is a leaf in the tree. // // The text of the lines is at the end of the block. The text of the first line // in the block is put at the end, the text of the second line in front of it, // etc. Thus the order of the lines is the opposite of the line number. -struct data_block { +typedef struct { uint16_t db_id; // ID for data block: DATA_ID unsigned db_free; // free space available unsigned db_txt_start; // byte where text starts @@ -141,7 +136,7 @@ struct data_block { // followed by empty space up to db_txt_start // followed by the text in the lines until // end of page -}; +} DataBlock; // The low bits of db_index hold the actual index. The topmost bit is // used for the global command to be able to mark a line. @@ -153,7 +148,7 @@ struct data_block { #define DB_INDEX_MASK (~DB_MARKED) #define INDEX_SIZE (sizeof(unsigned)) // size of one db_index entry -#define HEADER_SIZE (offsetof(DATA_BL, db_index)) // size of data block header +#define HEADER_SIZE (offsetof(DataBlock, db_index)) // size of data block header enum { B0_FNAME_SIZE_ORG = 900, // what it was in older versions @@ -172,7 +167,8 @@ enum { B0_MAGIC_CHAR = 0x55, }; -// Block zero holds all info about the swap file. +// Block zero holds all info about the swap file. This is the first block in +// the file. // // NOTE: DEFINITION OF BLOCK 0 SHOULD NOT CHANGE! It would make all existing // swap files unusable! @@ -182,7 +178,7 @@ enum { // This block is built up of single bytes, to make it portable across // different machines. b0_magic_* is used to check the byte order and size of // variables, because the rest of the swap file is not portable. -struct block0 { +typedef struct { char b0_id[2]; ///< ID for block 0: BLOCK0_ID0 and BLOCK0_ID1. char b0_version[10]; // Vim version string char b0_page_size[4]; // number of bytes per page @@ -196,7 +192,7 @@ struct block0 { int b0_magic_int; // check for byte order of int int16_t b0_magic_short; // check for byte order of short char b0_magic_char; // check for last char -}; +} ZeroBlock; // Note: b0_dirty and b0_flags are put at the end of the file name. For very // long file names in older versions of Vim they are invalid. @@ -315,7 +311,7 @@ int ml_open(buf_T *buf) iemsg(_("E298: Didn't get block nr 0?")); goto error; } - ZERO_BL *b0p = hp->bh_data; + ZeroBlock *b0p = hp->bh_data; b0p->b0_id[0] = BLOCK0_ID0; b0p->b0_id[1] = BLOCK0_ID1; @@ -354,7 +350,7 @@ int ml_open(buf_T *buf) iemsg(_("E298: Didn't get block nr 1?")); goto error; } - PTR_BL *pp = hp->bh_data; + PointerBlock *pp = hp->bh_data; pp->pb_count = 1; pp->pb_pointer[0].pe_bnum = 2; pp->pb_pointer[0].pe_page_count = 1; @@ -369,7 +365,7 @@ int ml_open(buf_T *buf) goto error; } - DATA_BL *dp = hp->bh_data; + DataBlock *dp = hp->bh_data; dp->db_index[0] = --dp->db_txt_start; // at end of block dp->db_free -= 1 + (unsigned)INDEX_SIZE; dp->db_line_count = 1; @@ -608,14 +604,14 @@ void ml_timestamp(buf_T *buf) } /// Checks whether the IDs in b0 are valid. -static bool ml_check_b0_id(ZERO_BL *b0p) +static bool ml_check_b0_id(ZeroBlock *b0p) FUNC_ATTR_NONNULL_ALL { return b0p->b0_id[0] == BLOCK0_ID0 && b0p->b0_id[1] == BLOCK0_ID1; } /// Checks whether all strings in b0 are valid (i.e. nul-terminated). -static bool ml_check_b0_strings(ZERO_BL *b0p) +static bool ml_check_b0_strings(ZeroBlock *b0p) FUNC_ATTR_NONNULL_ALL { return (memchr(b0p->b0_version, NUL, 10) @@ -633,7 +629,7 @@ static void ml_upd_block0(buf_T *buf, upd_block0_T what) if (mfp == NULL || (hp = mf_get(mfp, 0, 1)) == NULL) { return; } - ZERO_BL *b0p = hp->bh_data; + ZeroBlock *b0p = hp->bh_data; if (ml_check_b0_id(b0p) == FAIL) { iemsg(_("E304: ml_upd_block0(): Didn't get block 0??")); } else { @@ -649,7 +645,7 @@ static void ml_upd_block0(buf_T *buf, upd_block0_T what) /// Write file name and timestamp into block 0 of a swap file. /// Also set buf->b_mtime. /// Don't use NameBuff[]!!! -static void set_b0_fname(ZERO_BL *b0p, buf_T *buf) +static void set_b0_fname(ZeroBlock *b0p, buf_T *buf) { if (buf->b_ffname == NULL) { b0p->b0_fname[0] = NUL; @@ -702,7 +698,7 @@ static void set_b0_fname(ZERO_BL *b0p, buf_T *buf) /// swapfile for "buf" are in the same directory. /// This is fail safe: if we are not sure the directories are equal the flag is /// not set. -static void set_b0_dir_flag(ZERO_BL *b0p, buf_T *buf) +static void set_b0_dir_flag(ZeroBlock *b0p, buf_T *buf) { if (same_directory(buf->b_ml.ml_mfp->mf_fname, buf->b_ffname)) { b0p->b0_flags |= B0_SAME_DIR; @@ -712,7 +708,7 @@ static void set_b0_dir_flag(ZERO_BL *b0p, buf_T *buf) } /// When there is room, add the 'fileencoding' to block zero. -static void add_b0_fenc(ZERO_BL *b0p, buf_T *buf) +static void add_b0_fenc(ZeroBlock *b0p, buf_T *buf) { const int size = B0_FNAME_SIZE_NOCRYPT; @@ -730,7 +726,7 @@ static void add_b0_fenc(ZERO_BL *b0p, buf_T *buf) /// Return true if the process with number "b0p->b0_pid" is still running. /// "swap_fname" is the name of the swap file, if it's from before a reboot then /// the result is false; -static bool swapfile_process_running(const ZERO_BL *b0p, const char *swap_fname) +static bool swapfile_process_running(const ZeroBlock *b0p, const char *swap_fname) { FileInfo st; double uptime; @@ -754,11 +750,11 @@ void ml_recover(bool checkext) memfile_T *mfp = NULL; char *fname_used = NULL; bhdr_T *hp = NULL; - ZERO_BL *b0p; + ZeroBlock *b0p; int b0_ff; char *b0_fenc = NULL; - PTR_BL *pp; - DATA_BL *dp; + PointerBlock *pp; + DataBlock *dp; infoptr_T *ip; bool directly; char *p; @@ -1467,7 +1463,7 @@ static bool process_still_running; void get_b0_dict(const char *fname, dict_T *d) { int fd; - struct block0 b0; + ZeroBlock b0; if ((fd = os_open(fname, O_RDONLY, 0)) >= 0) { if (read_eintr(fd, &b0, sizeof(b0)) == sizeof(b0)) { @@ -1506,7 +1502,7 @@ static time_t swapfile_info(char *fname) { assert(fname != NULL); int fd; - struct block0 b0; + ZeroBlock b0; time_t x = (time_t)0; #ifdef UNIX char uname[B0_UNAME_SIZE]; @@ -1596,7 +1592,7 @@ static time_t swapfile_info(char *fname) /// can be safely deleted. static bool swapfile_unchanged(char *fname) { - struct block0 b0; + ZeroBlock b0; // Swap file must exist. if (!os_path_exists(fname)) { @@ -1905,7 +1901,7 @@ errorret: goto errorret; } - DATA_BL *dp = hp->bh_data; + DataBlock *dp = hp->bh_data; char *ptr = (char *)dp + (dp->db_index[lnum - buf->b_ml.ml_locked_low] & DB_INDEX_MASK); buf->b_ml.ml_line_ptr = ptr; @@ -2037,7 +2033,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo // get line count (number of indexes in current block) before the insertion int line_count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low; - DATA_BL *dp = hp->bh_data; + DataBlock *dp = hp->bh_data; // If // - there is not enough room in the current block @@ -2120,13 +2116,13 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo int lines_moved; int data_moved = 0; // init to shut up gcc int total_moved = 0; // init to shut up gcc - DATA_BL *dp_right, *dp_left; + DataBlock *dp_right, *dp_left; int stack_idx; bool in_left; int lineadd; blocknr_T bnum_left, bnum_right; linenr_T lnum_left, lnum_right; - PTR_BL *pp_new; + PointerBlock *pp_new; // We are going to allocate a new data block. Depending on the // situation it will be put to the left or right of the existing @@ -2263,7 +2259,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo if ((hp = mf_get(mfp, ip->ip_bnum, 1)) == NULL) { return FAIL; } - PTR_BL *pp = hp->bh_data; // must be pointer block + PointerBlock *pp = hp->bh_data; // must be pointer block if (pp->pb_id != PTR_ID) { iemsg(_(e_pointer_block_id_wrong_three)); mf_put(mfp, hp, false, false); @@ -2276,7 +2272,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo if (pb_idx + 1 < (int)pp->pb_count) { memmove(&pp->pb_pointer[pb_idx + 2], &pp->pb_pointer[pb_idx + 1], - (size_t)(pp->pb_count - pb_idx - 1) * sizeof(PTR_EN)); + (size_t)(pp->pb_count - pb_idx - 1) * sizeof(PointerEntry)); } pp->pb_count++; pp->pb_pointer[pb_idx].pe_line_count = line_count_left; @@ -2349,7 +2345,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo if (total_moved) { memmove(&pp_new->pb_pointer[0], &pp->pb_pointer[pb_idx + 1], - (size_t)(total_moved) * sizeof(PTR_EN)); + (size_t)(total_moved) * sizeof(PointerEntry)); pp_new->pb_count = (uint16_t)total_moved; pp->pb_count = (uint16_t)(pp->pb_count - (total_moved - 1)); pp->pb_pointer[pb_idx + 1].pe_bnum = bnum_right; @@ -2542,7 +2538,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message) return FAIL; } - DATA_BL *dp = hp->bh_data; + DataBlock *dp = hp->bh_data; // compute line count (number of entries in block) before the delete int count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low + 2; int idx = lnum - buf->b_ml.ml_locked_low; @@ -2579,7 +2575,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message) if ((hp = mf_get(mfp, ip->ip_bnum, 1)) == NULL) { return FAIL; } - PTR_BL *pp = hp->bh_data; // must be pointer block + PointerBlock *pp = hp->bh_data; // must be pointer block if (pp->pb_id != PTR_ID) { iemsg(_(e_pointer_block_id_wrong_four)); mf_put(mfp, hp, false, false); @@ -2591,7 +2587,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message) } else { if (count != idx) { // move entries after the deleted one memmove(&pp->pb_pointer[idx], &pp->pb_pointer[idx + 1], - (size_t)(count - idx) * sizeof(PTR_EN)); + (size_t)(count - idx) * sizeof(PointerEntry)); } mf_put(mfp, hp, true, false); @@ -2651,7 +2647,7 @@ void ml_setmarked(linenr_T lnum) if ((hp = ml_find_line(curbuf, lnum, ML_FIND)) == NULL) { return; // give error message? } - DATA_BL *dp = hp->bh_data; + DataBlock *dp = hp->bh_data; dp->db_index[lnum - curbuf->b_ml.ml_locked_low] |= DB_MARKED; curbuf->b_ml.ml_flags |= ML_LOCKED_DIRTY; } @@ -2673,7 +2669,7 @@ linenr_T ml_firstmarked(void) if ((hp = ml_find_line(curbuf, lnum, ML_FIND)) == NULL) { return 0; // give error message? } - DATA_BL *dp = hp->bh_data; + DataBlock *dp = hp->bh_data; for (int i = lnum - curbuf->b_ml.ml_locked_low; lnum <= curbuf->b_ml.ml_locked_high; i++, lnum++) { @@ -2705,7 +2701,7 @@ void ml_clearmarked(void) if ((hp = ml_find_line(curbuf, lnum, ML_FIND)) == NULL) { return; // give error message? } - DATA_BL *dp = hp->bh_data; + DataBlock *dp = hp->bh_data; for (int i = lnum - curbuf->b_ml.ml_locked_low; lnum <= curbuf->b_ml.ml_locked_high; i++, lnum++) { @@ -2754,7 +2750,7 @@ static void ml_flush_line(buf_T *buf) if (hp == NULL) { siemsg(_("E320: Cannot find line %" PRId64), (int64_t)lnum); } else { - DATA_BL *dp = hp->bh_data; + DataBlock *dp = hp->bh_data; int idx = lnum - buf->b_ml.ml_locked_low; int start = ((dp->db_index[idx]) & DB_INDEX_MASK); char *old_line = (char *)dp + start; @@ -2822,7 +2818,7 @@ static bhdr_T *ml_new_data(memfile_T *mfp, bool negative, int page_count) { assert(page_count >= 0); bhdr_T *hp = mf_new(mfp, negative, (unsigned)page_count); - DATA_BL *dp = hp->bh_data; + DataBlock *dp = hp->bh_data; dp->db_id = DATA_ID; dp->db_txt_start = dp->db_txt_end = (unsigned)page_count * mfp->mf_page_size; dp->db_free = dp->db_txt_start - (unsigned)HEADER_SIZE; @@ -2835,7 +2831,7 @@ static bhdr_T *ml_new_data(memfile_T *mfp, bool negative, int page_count) static bhdr_T *ml_new_ptr(memfile_T *mfp) { bhdr_T *hp = mf_new(mfp, false, 1); - PTR_BL *pp = hp->bh_data; + PointerBlock *pp = hp->bh_data; pp->pb_id = PTR_ID; pp->pb_count = 0; pp->pb_count_max = PB_COUNT_MAX(mfp); @@ -2858,7 +2854,7 @@ static bhdr_T *ml_new_ptr(memfile_T *mfp) /// @return NULL for failure, pointer to block header otherwise static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) { - PTR_BL *pp; + PointerBlock *pp; bhdr_T *hp; int top; @@ -2935,7 +2931,7 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) high--; } - DATA_BL *dp = hp->bh_data; + DataBlock *dp = hp->bh_data; if (dp->db_id == DATA_ID) { // data block buf->b_ml.ml_locked = hp; buf->b_ml.ml_locked_low = low; @@ -2945,7 +2941,7 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) return hp; } - pp = (PTR_BL *)(dp); // must be pointer block + pp = (PointerBlock *)(dp); // must be pointer block if (pp->pb_id != PTR_ID) { iemsg(_(e_pointer_block_id_wrong)); goto error_block; @@ -3055,7 +3051,7 @@ static void ml_lineadd(buf_T *buf, int count) if ((hp = mf_get(mfp, ip->ip_bnum, 1)) == NULL) { break; } - PTR_BL *pp = hp->bh_data; // must be pointer block + PointerBlock *pp = hp->bh_data; // must be pointer block if (pp->pb_id != PTR_ID) { mf_put(mfp, hp, false, false); iemsg(_(e_pointer_block_id_wrong_two)); @@ -3371,7 +3367,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ if (!recoverymode && buf_fname != NULL && !buf->b_help && !(buf->b_flags & BF_DUMMY)) { int fd; - struct block0 b0; + ZeroBlock b0; int differ = false; // Try to read block 0 from the swap file to get the original @@ -3548,7 +3544,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ return fname; } -static int b0_magic_wrong(ZERO_BL *b0p) +static int b0_magic_wrong(ZeroBlock *b0p) { return b0p->b0_magic_long != B0_MAGIC_LONG || b0p->b0_magic_int != B0_MAGIC_INT @@ -3684,22 +3680,19 @@ static long char_to_long(const char *s_in) /// - 'fileencoding' void ml_setflags(buf_T *buf) { - bhdr_T *hp; - ZERO_BL *b0p; + ZeroBlock *b0p; if (!buf->b_ml.ml_mfp) { return; } - for (hp = buf->b_ml.ml_mfp->mf_used_last; hp != NULL; hp = hp->bh_prev) { - if (hp->bh_bnum == 0) { - b0p = hp->bh_data; - b0p->b0_dirty = buf->b_changed ? B0_DIRTY : 0; - b0p->b0_flags = (char)((b0p->b0_flags & ~B0_FF_MASK) | (uint8_t)(get_fileformat(buf) + 1)); - add_b0_fenc(b0p, buf); - hp->bh_flags |= BH_DIRTY; - mf_sync(buf->b_ml.ml_mfp, MFS_ZERO); - break; - } + bhdr_T *hp = pmap_get(int64_t)(&buf->b_ml.ml_mfp->mf_hash, 0); + if (hp) { + b0p = hp->bh_data; + b0p->b0_dirty = buf->b_changed ? B0_DIRTY : 0; + b0p->b0_flags = (char)((b0p->b0_flags & ~B0_FF_MASK) | (uint8_t)(get_fileformat(buf) + 1)); + add_b0_fenc(b0p, buf); + hp->bh_flags |= BH_DIRTY; + mf_sync(buf->b_ml.ml_mfp, MFS_ZERO); } } @@ -3772,7 +3765,7 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype) curchnk->mlcs_totalsize += len; if (updtype == ML_CHNK_ADDLINE) { int rest; - DATA_BL *dp; + DataBlock *dp; curchnk->mlcs_numlines++; // May resize here so we don't have to do it in both cases below @@ -3975,7 +3968,7 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff) || (hp = ml_find_line(buf, curline, ML_FIND)) == NULL) { return -1; } - DATA_BL *dp = hp->bh_data; + DataBlock *dp = hp->bh_data; int count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low + 1; // number of entries in block int idx; diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 1d35c97b39..3b2e45e2a4 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -618,8 +618,6 @@ static void free_menu(vimmenu_T **menup) { vimmenu_T *menu = *menup; - // Don't change *menup until after calling gui_mch_destroy_menu(). The - // MacOS code needs the original structure to properly delete the menu. *menup = menu->next; xfree(menu->name); xfree(menu->dname); diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index 76ac191a68..b8c80cadf5 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -220,7 +220,10 @@ static int get_fpos_of_mouse(pos_T *mpos) // compute the position in the buffer line from the posn on the screen bool below_buffer = mouse_comp_pos(wp, &row, &col, &mpos->lnum); - if (!below_buffer && *wp->w_p_stc != NUL && mouse_col < win_col_off(wp)) { + if (!below_buffer && *wp->w_p_stc != NUL + && (wp->w_p_rl + ? wincol >= wp->w_width_inner - win_col_off(wp) + : wincol < win_col_off(wp))) { return MOUSE_STATUSCOL; } @@ -675,6 +678,10 @@ popupexit: click_col = mouse_col; } + if (in_statuscol && wp->w_p_rl) { + click_col = wp->w_width_inner - click_col - 1; + } + if (click_defs != NULL) { switch (click_defs[click_col].type) { case kStlClickDisabled: @@ -1254,7 +1261,10 @@ retnomove: on_sep_line = grid == DEFAULT_GRID_HANDLE && col >= wp->w_width && col - wp->w_width + 1 == 1; on_winbar = row == -1 && wp->w_winbar_height != 0; on_statuscol = !below_window && !on_status_line && !on_sep_line && !on_winbar - && *wp->w_p_stc != NUL && col < win_col_off(wp); + && *wp->w_p_stc != NUL + && (wp->w_p_rl + ? col >= wp->w_width_inner - win_col_off(wp) + : col < win_col_off(wp)); // The rightmost character of the status line might be a vertical // separator character if there is no connecting window to the right. diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 3a9b36a914..b753d46d64 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -207,9 +207,15 @@ Object rpc_send_call(uint64_t id, const char *method_name, Array args, ArenaMem // Push the frame ChannelCallFrame frame = { request_id, false, false, NIL, NULL }; kv_push(rpc->call_stack, &frame); - LOOP_PROCESS_EVENTS_UNTIL(&main_loop, channel->events, -1, frame.returned); + LOOP_PROCESS_EVENTS_UNTIL(&main_loop, channel->events, -1, frame.returned || rpc->closed); (void)kv_pop(rpc->call_stack); + if (rpc->closed) { + api_set_error(err, kErrorTypeException, "Invalid channel: %" PRIu64, id); + channel_decref(channel); + return NIL; + } + if (frame.errored) { if (frame.result.type == kObjectTypeString) { api_set_error(err, kErrorTypeException, "%s", diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 1007925ccb..14f29682e1 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -630,6 +630,7 @@ EXTERN unsigned rdb_flags; #define RDB_NODELTA 0x008 #define RDB_LINE 0x010 #define RDB_FLUSH 0x020 +#define RDB_INTERSECT 0x040 EXTERN long p_rdt; // 'redrawtime' EXTERN long p_re; // 'regexpengine' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index b774df476f..3221e5b6e9 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -566,7 +566,7 @@ return { backups if you don't care about losing the file. Note that environment variables are not expanded. If you want to use - $HOME you must expand it explicitly, e.g.: > + $HOME you must expand it explicitly, e.g.: >vim :let &backupskip = escape(expand('$HOME'), '\') .. '/tmp/*' < Note that the default also makes sure that "crontab -e" works (when a @@ -3441,10 +3441,10 @@ return { n-v-c-sm:block,i-ci-ve:ver25-Cursor,r-cr-o:hor20 In Normal et al. modes, use a block cursor with the default colors defined by the host - terminal. In Insert-likes modes, use + terminal. In Insert-like modes, use a vertical bar cursor with colors from - "Cursor" highlight group. In Replace-likes - modes, use a underline cursor with + "Cursor" highlight group. In Replace-like + modes, use an underline cursor with default colors. i-ci:ver30-iCursor-blinkwait300-blinkon200-blinkoff150 In Insert and Command-line Insert mode, use a diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 979c6153aa..81e15bf841 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -1026,7 +1026,7 @@ static bool out_data_decide_throttle(size_t size) started = os_hrtime(); } else { uint64_t since = os_hrtime() - started; - if (since < (visit * 0.1L * NS_1_SECOND)) { + if (since < (visit * (NS_1_SECOND / 10))) { return true; } if (since > (3 * NS_1_SECOND)) { diff --git a/src/nvim/plines.c b/src/nvim/plines.c index 82554c7785..99f666ef3f 100644 --- a/src/nvim/plines.c +++ b/src/nvim/plines.c @@ -133,7 +133,7 @@ void init_chartabsize_arg(chartabsize_T *cts, win_T *wp, linenr_T lnum, colnr_T if (cts->cts_row >= 0 && wp->w_buffer->b_virt_text_inline > 0) { marktree_itr_get(wp->w_buffer->b_marktree, cts->cts_row, 0, cts->cts_iter); - mtkey_t mark = marktree_itr_current(cts->cts_iter); + MTKey mark = marktree_itr_current(cts->cts_iter); if (mark.pos.row == cts->cts_row) { cts->cts_has_virt_text = true; } @@ -222,7 +222,7 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) int tab_size = size; int col = (int)(s - line); while (true) { - mtkey_t mark = marktree_itr_current(cts->cts_iter); + MTKey mark = marktree_itr_current(cts->cts_iter); if (mark.pos.row != cts->cts_row || mark.pos.col > col) { break; } else if (mark.pos.col == col) { diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 72e21a9130..38e045a08b 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -1280,7 +1280,7 @@ static TriState decor_spell_nav_col(win_T *wp, linenr_T lnum, linenr_T *decor_ln decor_redraw_line(wp, lnum - 1, &decor_state); *decor_lnum = lnum; } - decor_redraw_col(wp, col, col, false, &decor_state); + decor_redraw_col(wp, col, 0, false, &decor_state); return decor_state.spell; } diff --git a/src/nvim/usercmd.c b/src/nvim/usercmd.c index e585818e75..7ccd13c3a7 100644 --- a/src/nvim/usercmd.c +++ b/src/nvim/usercmd.c @@ -130,23 +130,21 @@ static struct { char *find_ucmd(exarg_T *eap, char *p, int *full, expand_T *xp, int *complp) { int len = (int)(p - eap->cmd); - int j, k, matchlen = 0; - ucmd_T *uc; + int matchlen = 0; bool found = false; bool possible = false; - char *cp, *np; // Point into typed cmd and test name - garray_T *gap; bool amb_local = false; // Found ambiguous buffer-local command, // only full match global is accepted. // Look for buffer-local user commands first, then global ones. - gap = &prevwin_curwin()->w_buffer->b_ucmds; + garray_T *gap = &prevwin_curwin()->w_buffer->b_ucmds; while (true) { + int j; for (j = 0; j < gap->ga_len; j++) { - uc = USER_CMD_GA(gap, j); - cp = eap->cmd; - np = uc->uc_name; - k = 0; + ucmd_T *uc = USER_CMD_GA(gap, j); + char *cp = eap->cmd; + char *np = uc->uc_name; + int k = 0; while (k < len && *np != NUL && *cp++ == *np++) { k++; } @@ -447,17 +445,15 @@ int cmdcomplete_str_to_type(const char *complete_str) static void uc_list(char *name, size_t name_len) { - int i, j; bool found = false; - ucmd_T *cmd; - uint32_t a; // In cmdwin, the alternative buffer should be used. const garray_T *gap = &prevwin_curwin()->w_buffer->b_ucmds; while (true) { + int i; for (i = 0; i < gap->ga_len; i++) { - cmd = USER_CMD_GA(gap, i); - a = cmd->uc_argt; + ucmd_T *cmd = USER_CMD_GA(gap, i); + uint32_t a = cmd->uc_argt; // Skip commands which don't match the requested prefix and // commands filtered out. @@ -559,7 +555,7 @@ static void uc_list(char *name, size_t name_len) } while ((int64_t)len < 8 - over); // Address Type - for (j = 0; addr_type_complete[j].expand != ADDR_NONE; j++) { + for (int j = 0; addr_type_complete[j].expand != ADDR_NONE; j++) { if (addr_type_complete[j].expand != ADDR_LINES && addr_type_complete[j].expand == cmd->uc_addr_type) { int rc = snprintf(IObuff + len, IOSIZE - len, "%s", addr_type_complete[j].shortname); @@ -623,11 +619,11 @@ static void uc_list(char *name, size_t name_len) int parse_addr_type_arg(char *value, int vallen, cmd_addr_T *addr_type_arg) FUNC_ATTR_NONNULL_ALL { - int i, a, b; + int i; for (i = 0; addr_type_complete[i].expand != ADDR_NONE; i++) { - a = (int)strlen(addr_type_complete[i].name) == vallen; - b = strncmp(value, addr_type_complete[i].name, (size_t)vallen) == 0; + int a = (int)strlen(addr_type_complete[i].name) == vallen; + int b = strncmp(value, addr_type_complete[i].name, (size_t)vallen) == 0; if (a && b) { *addr_type_arg = addr_type_complete[i].expand; break; @@ -657,11 +653,10 @@ int parse_compl_arg(const char *value, int vallen, int *complp, uint32_t *argt, { const char *arg = NULL; size_t arglen = 0; - int i; int valend = vallen; // Look for any argument part - which is the part after any ',' - for (i = 0; i < vallen; i++) { + for (int i = 0; i < vallen; i++) { if (value[i] == ',') { arg = (char *)&value[i + 1]; arglen = (size_t)(vallen - i - 1); @@ -670,6 +665,7 @@ int parse_compl_arg(const char *value, int vallen, int *complp, uint32_t *argt, } } + int i; for (i = 0; i < (int)ARRAY_SIZE(command_complete); i++) { if (get_command_complete(i) == NULL) { continue; @@ -713,8 +709,6 @@ static int uc_scan_attr(char *attr, size_t len, uint32_t *argt, long *def, int * char **compl_arg, cmd_addr_T *addr_type_arg) FUNC_ATTR_NONNULL_ALL { - char *p; - if (len == 0) { emsg(_("E175: No attribute specified")); return FAIL; @@ -732,13 +726,12 @@ static int uc_scan_attr(char *attr, size_t len, uint32_t *argt, long *def, int * } else if (STRNICMP(attr, "bar", len) == 0) { *argt |= EX_TRLBAR; } else { - int i; char *val = NULL; size_t vallen = 0; size_t attrlen = len; // Look for the attribute name - which is the part before any '=' - for (i = 0; i < (int)len; i++) { + for (int i = 0; i < (int)len; i++) { if (attr[i] == '=') { val = &attr[i + 1]; vallen = len - (size_t)i - 1; @@ -772,7 +765,7 @@ wrong_nargs: if (vallen == 1 && *val == '%') { *argt |= EX_DFLALL; } else if (val != NULL) { - p = val; + char *p = val; if (*def >= 0) { two_count: emsg(_("E177: Count cannot be specified twice")); @@ -800,7 +793,7 @@ invalid_count: } if (val != NULL) { - p = val; + char *p = val; if (*def >= 0) { goto two_count; } @@ -878,7 +871,6 @@ int uc_add_command(char *name, size_t name_len, const char *rep, uint32_t argt, FUNC_ATTR_NONNULL_ARG(1, 3) { ucmd_T *cmd = NULL; - int i; int cmp = 1; char *rep_buf = NULL; garray_T *gap; @@ -899,12 +891,12 @@ int uc_add_command(char *name, size_t name_len, const char *rep, uint32_t argt, gap = &ucmds; } + int i; + // Search for the command in the already defined commands. for (i = 0; i < gap->ga_len; i++) { - size_t len; - cmd = USER_CMD_GA(gap, i); - len = strlen(cmd->uc_name); + size_t len = strlen(cmd->uc_name); cmp = strncmp(name, cmd->uc_name, name_len); if (cmp == 0) { if (name_len < len) { @@ -980,9 +972,7 @@ fail: /// ":command ..." void ex_command(exarg_T *eap) { - char *name; char *end; - char *p; uint32_t argt = 0; long def = -1; int flags = 0; @@ -990,9 +980,8 @@ void ex_command(exarg_T *eap) char *compl_arg = NULL; cmd_addr_T addr_type_arg = ADDR_NONE; int has_attr = (eap->arg[0] == '-'); - size_t name_len; - p = eap->arg; + char *p = eap->arg; // Check for attributes while (*p == '-') { @@ -1006,13 +995,13 @@ void ex_command(exarg_T *eap) } // Get the name (if any) and skip to the following argument. - name = p; + char *name = p; end = uc_validate_name(name); if (!end) { emsg(_("E182: Invalid command name")); goto theend; } - name_len = (size_t)(end - name); + size_t name_len = (size_t)(end - name); // If there is nothing after the name, and no attributes were specified, // we are listing commands @@ -1065,7 +1054,6 @@ void ex_delcommand(exarg_T *eap) int i = 0; ucmd_T *cmd = NULL; int res = -1; - garray_T *gap; const char *arg = eap->arg; bool buffer_only = false; @@ -1074,7 +1062,7 @@ void ex_delcommand(exarg_T *eap) arg = skipwhite(arg + 7); } - gap = &curbuf->b_ucmds; + garray_T *gap = &curbuf->b_ucmds; while (true) { for (i = 0; i < gap->ga_len; i++) { cmd = USER_CMD_GA(gap, i); @@ -1153,15 +1141,10 @@ bool uc_split_args_iter(const char *arg, size_t arglen, size_t *end, char *buf, static char *uc_split_args(const char *arg, char **args, const size_t *arglens, size_t argc, size_t *lenp) { - char *buf; - const char *p; - char *q; - int len; - // Precalculate length - len = 2; // Initial and final quotes + int len = 2; // Initial and final quotes if (args == NULL) { - p = arg; + const char *p = arg; while (*p) { if (p[0] == '\\' && p[1] == '\\') { @@ -1188,7 +1171,7 @@ static char *uc_split_args(const char *arg, char **args, const size_t *arglens, } } else { for (size_t i = 0; i < argc; i++) { - p = args[i]; + const char *p = args[i]; const char *arg_end = args[i] + arglens[i]; while (p < arg_end) { @@ -1209,13 +1192,13 @@ static char *uc_split_args(const char *arg, char **args, const size_t *arglens, } } - buf = xmalloc((size_t)len + 1); + char *buf = xmalloc((size_t)len + 1); - q = buf; + char *q = buf; *q++ = '"'; if (args == NULL) { - p = arg; + const char *p = arg; while (*p) { if (p[0] == '\\' && p[1] == '\\') { *q++ = '\\'; @@ -1242,7 +1225,7 @@ static char *uc_split_args(const char *arg, char **args, const size_t *arglens, } } else { for (size_t i = 0; i < argc; i++) { - p = args[i]; + const char *p = args[i]; const char *arg_end = args[i] + arglens[i]; while (p < arg_end) { @@ -1622,14 +1605,7 @@ static size_t uc_check_code(char *code, size_t len, char *buf, ucmd_T *cmd, exar int do_ucmd(exarg_T *eap, bool preview) { - char *buf; - char *p; - char *q; - - char *start; char *end = NULL; - char *ksp; - size_t len, totlen; size_t split_len = 0; char *split_buf = NULL; @@ -1654,18 +1630,19 @@ int do_ucmd(exarg_T *eap, bool preview) // Replace <> in the command by the arguments. // First round: "buf" is NULL, compute length, allocate "buf". // Second round: copy result into "buf". - buf = NULL; + char *buf = NULL; while (true) { - p = cmd->uc_rep; // source - q = buf; // destination - totlen = 0; + char *p = cmd->uc_rep; // source + char *q = buf; // destination + size_t totlen = 0; while (true) { - start = vim_strchr(p, '<'); + char *start = vim_strchr(p, '<'); if (start != NULL) { end = vim_strchr(start + 1, '>'); } if (buf != NULL) { + char *ksp; for (ksp = p; *ksp != NUL && (uint8_t)(*ksp) != K_SPECIAL; ksp++) {} if ((uint8_t)(*ksp) == K_SPECIAL && (start == NULL || ksp < start || end == NULL) @@ -1673,7 +1650,7 @@ int do_ucmd(exarg_T *eap, bool preview) // K_SPECIAL has been put in the buffer as K_SPECIAL // KS_SPECIAL KE_FILLER, like for mappings, but // do_cmdline() doesn't handle that, so convert it back. - len = (size_t)(ksp - p); + size_t len = (size_t)(ksp - p); if (len > 0) { memmove(q, p, len); q += len; @@ -1693,7 +1670,7 @@ int do_ucmd(exarg_T *eap, bool preview) end++; // Take everything up to the '<' - len = (size_t)(start - p); + size_t len = (size_t)(start - p); if (buf == NULL) { totlen += len; } else { diff --git a/src/nvim/version.h b/src/nvim/version.h index 484350edee..e0c7b76700 100644 --- a/src/nvim/version.h +++ b/src/nvim/version.h @@ -7,6 +7,9 @@ // defined in version.c extern char *Version; extern char *longVersion; +#ifndef NDEBUG +extern char *version_cflags; +#endif // // Vim version number, name, etc. Patchlevel is defined in version.c. diff --git a/src/nvim/window.c b/src/nvim/window.c index e72c32700d..c0d399c4c4 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -965,7 +965,13 @@ void ui_ext_win_position(win_T *wp, bool validate) if (c.relative == kFloatRelativeWindow) { Error dummy = ERROR_INIT; win_T *win = find_window_by_handle(c.window, &dummy); - if (win) { + api_clear_error(&dummy); + if (win != NULL) { + // When a floating window is anchored to another window, + // update the position of its anchored window first. + if (win->w_pos_changed && win->w_grid_alloc.chars != NULL && win_valid(win)) { + ui_ext_win_position(win, validate); + } grid = &win->w_grid; int row_off = 0, col_off = 0; grid_adjust(&grid, &row_off, &col_off); @@ -979,7 +985,6 @@ void ui_ext_win_position(win_T *wp, bool validate) col += tcol - 1; } } - api_clear_error(&dummy); } wp->w_grid_alloc.zindex = wp->w_float_config.zindex; @@ -1884,6 +1889,10 @@ static void win_exchange(int Prenum) beep_flush(); return; } + if (text_or_buf_locked()) { + beep_flush(); + return; + } frame_T *frp; @@ -5167,7 +5176,6 @@ static void win_free(win_T *wp, tabpage_T *tp) alist_unlink(wp->w_alist); // Don't execute autocommands while the window is halfway being deleted. - // gui_mch_destroy_scrollbar() may trigger a FocusGained event. block_autocmds(); clear_winopt(&wp->w_onebuf_opt); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 77fe7b3b82..e9be79edc0 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -27,7 +27,7 @@ if(LUA_HAS_FFI) -P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake DEPENDS ${UNITTEST_PREREQS} USES_TERMINAL) - add_dependencies(unittest test_deps) + add_dependencies(unittest lua-dev-deps) else() message(WARNING "disabling unit tests: no Luajit FFI in ${LUA_PRG}") endif() @@ -66,5 +66,5 @@ add_custom_target(benchmark DEPENDS ${BENCHMARK_PREREQS} USES_TERMINAL) -add_dependencies(functionaltest test_deps) -add_dependencies(benchmark test_deps) +add_dependencies(functionaltest lua-dev-deps) +add_dependencies(benchmark lua-dev-deps) diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua index 292e5a2d56..9833ebee4c 100644 --- a/test/functional/api/buffer_spec.lua +++ b/test/functional/api/buffer_spec.lua @@ -848,6 +848,710 @@ describe('api/buf', function() eq({1, 4}, meths.win_get_cursor(win2)) end) + describe('when text is being added right at cursor position #22526', function() + it('updates the cursor position in NORMAL mode', function() + insert([[ + abcd]]) + + -- position the cursor on 'c' + curwin('set_cursor', {1, 2}) + -- add 'xxx' before 'c' + set_text(0, 2, 0, 2, {'xxx'}) + eq({'abxxxcd'}, get_lines(0, -1, true)) + -- cursor should be on 'c' + eq({1, 5}, curwin('get_cursor')) + end) + + it('updates the cursor position only in non-current window when in INSERT mode', function() + insert([[ + abcd]]) + + -- position the cursor on 'c' + curwin('set_cursor', {1, 2}) + -- open vertical split + feed('<c-w>v') + -- get into INSERT mode to treat cursor + -- as being after 'b', not on 'c' + feed('i') + -- add 'xxx' between 'b' and 'c' + set_text(0, 2, 0, 2, {'xxx'}) + eq({'abxxxcd'}, get_lines(0, -1, true)) + -- in the current window cursor should stay after 'b' + eq({1, 2}, curwin('get_cursor')) + -- quit INSERT mode + feed('<esc>') + -- close current window + feed('<c-w>c') + -- in another window cursor should be on 'c' + eq({1, 5}, curwin('get_cursor')) + end) + end) + + describe('when text is being deleted right at cursor position', function() + it('leaves cursor at the same position in NORMAL mode', function() + insert([[ + abcd]]) + + -- position the cursor on 'b' + curwin('set_cursor', {1, 1}) + -- delete 'b' + set_text(0, 1, 0, 2, {}) + eq({'acd'}, get_lines(0, -1, true)) + -- cursor is now on 'c' + eq({1, 1}, curwin('get_cursor')) + end) + + it('leaves cursor at the same position in INSERT mode in current and non-current window', function() + insert([[ + abcd]]) + + -- position the cursor on 'b' + curwin('set_cursor', {1, 1}) + -- open vertical split + feed('<c-w>v') + -- get into INSERT mode to treat cursor + -- as being after 'a', not on 'b' + feed('i') + -- delete 'b' + set_text(0, 1, 0, 2, {}) + eq({'acd'}, get_lines(0, -1, true)) + -- cursor in the current window should stay after 'a' + eq({1, 1}, curwin('get_cursor')) + -- quit INSERT mode + feed('<esc>') + -- close current window + feed('<c-w>c') + -- cursor in non-current window should stay on 'c' + eq({1, 1}, curwin('get_cursor')) + end) + end) + + describe('when cursor is inside replaced row range', function() + it('keeps cursor at the same position if cursor is at start_row, but before start_col', function() + insert([[ + This should be first + then there is a line we do not want + and finally the last one]]) + + -- position the cursor on ' ' before 'first' + curwin('set_cursor', {1, 14}) + + set_text(0, 15, 2, 11, { + 'the line we do not want', + 'but hopefully', + }) + + eq({ + 'This should be the line we do not want', + 'but hopefully the last one', + }, get_lines(0, -1, true)) + -- cursor should stay at the same position + eq({1, 14}, curwin('get_cursor')) + end) + + it('keeps cursor at the same position if cursor is at start_row and column is still valid', function() + insert([[ + This should be first + then there is a line we do not want + and finally the last one]]) + + -- position the cursor on 'f' in 'first' + curwin('set_cursor', {1, 15}) + + set_text(0, 15, 2, 11, { + 'the line we do not want', + 'but hopefully', + }) + + eq({ + 'This should be the line we do not want', + 'but hopefully the last one', + }, get_lines(0, -1, true)) + -- cursor should stay at the same position + eq({1, 15}, curwin('get_cursor')) + end) + + it('adjusts cursor column to keep it valid if start_row got smaller', function() + insert([[ + This should be first + then there is a line we do not want + and finally the last one]]) + + -- position the cursor on 't' in 'first' + curwin('set_cursor', {1, 19}) + + local cursor = exec_lua([[ + vim.api.nvim_buf_set_text(0, 0, 15, 2, 24, {'last'}) + return vim.api.nvim_win_get_cursor(0) + ]]) + + eq({ 'This should be last' }, get_lines(0, -1, true)) + -- cursor should end up on 't' in 'last' + eq({1, 18}, curwin('get_cursor')) + -- immediate call to nvim_win_get_cursor should have returned the same position + eq({1, 18}, cursor) + end) + + it('adjusts cursor column to keep it valid if start_row got smaller in INSERT mode', function() + insert([[ + This should be first + then there is a line we do not want + and finally the last one]]) + + -- position the cursor on 't' in 'first' + curwin('set_cursor', {1, 19}) + -- enter INSERT mode to treat cursor as being after 't' + feed('a') + + local cursor = exec_lua([[ + vim.api.nvim_buf_set_text(0, 0, 15, 2, 24, {'last'}) + return vim.api.nvim_win_get_cursor(0) + ]]) + + eq({ 'This should be last' }, get_lines(0, -1, true)) + -- cursor should end up after 't' in 'last' + eq({1, 19}, curwin('get_cursor')) + -- immediate call to nvim_win_get_cursor should have returned the same position + eq({1, 19}, cursor) + end) + + it('adjusts cursor column to keep it valid in a row after start_row if it got smaller', function() + insert([[ + This should be first + then there is a line we do not want + and finally the last one]]) + + -- position the cursor on 'w' in 'want' + curwin('set_cursor', {2, 31}) + + local cursor = exec_lua([[ + vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, { + '1', + 'then 2', + 'and then', + }) + return vim.api.nvim_win_get_cursor(0) + ]]) + + eq({ + 'This should be 1', + 'then 2', + 'and then the last one', + }, get_lines(0, -1, true)) + -- cursor column should end up at the end of a row + eq({2, 5}, curwin('get_cursor')) + -- immediate call to nvim_win_get_cursor should have returned the same position + eq({2, 5}, cursor) + end) + + it('adjusts cursor column to keep it valid in a row after start_row if it got smaller in INSERT mode', function() + insert([[ + This should be first + then there is a line we do not want + and finally the last one]]) + + -- position the cursor on 'w' in 'want' + curwin('set_cursor', {2, 31}) + -- enter INSERT mode + feed('a') + + local cursor = exec_lua([[ + vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, { + '1', + 'then 2', + 'and then', + }) + return vim.api.nvim_win_get_cursor(0) + ]]) + + eq({ + 'This should be 1', + 'then 2', + 'and then the last one', + }, get_lines(0, -1, true)) + -- cursor column should end up at the end of a row + eq({2, 6}, curwin('get_cursor')) + -- immediate call to nvim_win_get_cursor should have returned the same position + eq({2, 6}, cursor) + end) + + it('adjusts cursor line and column to keep it inside replacement range', function() + insert([[ + This should be first + then there is a line we do not want + and finally the last one]]) + + -- position the cursor on 'n' in 'finally' + curwin('set_cursor', {3, 6}) + + local cursor = exec_lua([[ + vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, { + 'the line we do not want', + 'but hopefully', + }) + return vim.api.nvim_win_get_cursor(0) + ]]) + + eq({ + 'This should be the line we do not want', + 'but hopefully the last one', + }, get_lines(0, -1, true)) + -- cursor should end up on 'y' in 'hopefully' + -- to stay in the range, because it got smaller + eq({2, 12}, curwin('get_cursor')) + -- immediate call to nvim_win_get_cursor should have returned the same position + eq({2, 12}, cursor) + end) + + it('adjusts cursor line and column if replacement is empty', function() + insert([[ + This should be first + then there is a line we do not want + and finally the last one]]) + + -- position the cursor on 'r' in 'there' + curwin('set_cursor', {2, 8}) + + local cursor = exec_lua([[ + vim.api.nvim_buf_set_text(0, 0, 15, 2, 12, {}) + return vim.api.nvim_win_get_cursor(0) + ]]) + + eq({ 'This should be the last one' }, get_lines(0, -1, true)) + -- cursor should end up on the next column after deleted range + eq({1, 15}, curwin('get_cursor')) + -- immediate call to nvim_win_get_cursor should have returned the same position + eq({1, 15}, cursor) + end) + + it('adjusts cursor line and column if replacement is empty and start_col == 0', function() + insert([[ + This should be first + then there is a line we do not want + and finally the last one]]) + + -- position the cursor on 'r' in 'there' + curwin('set_cursor', {2, 8}) + + local cursor = exec_lua([[ + vim.api.nvim_buf_set_text(0, 0, 0, 2, 4, {}) + return vim.api.nvim_win_get_cursor(0) + ]]) + + eq({ 'finally the last one' }, get_lines(0, -1, true)) + -- cursor should end up in column 0 + eq({1, 0}, curwin('get_cursor')) + -- immediate call to nvim_win_get_cursor should have returned the same position + eq({1, 0}, cursor) + end) + + it('adjusts cursor column if replacement ends at cursor row, after cursor column', function() + insert([[ + This should be first + then there is a line we do not want + and finally the last one]]) + + -- position the cursor on 'y' in 'finally' + curwin('set_cursor', {3, 10}) + set_text(0, 15, 2, 11, { '1', 'this 2', 'and then' }) + + eq({ + 'This should be 1', + 'this 2', + 'and then the last one', + }, get_lines(0, -1, true)) + -- cursor should end up on 'n' in 'then' + eq({3, 7}, curwin('get_cursor')) + end) + + it('adjusts cursor column if replacement ends at cursor row, at cursor column in INSERT mode', function() + insert([[ + This should be first + then there is a line we do not want + and finally the last one]]) + + -- position the cursor on 'y' at 'finally' + curwin('set_cursor', {3, 10}) + -- enter INSERT mode to treat cursor as being between 'l' and 'y' + feed('i') + set_text(0, 15, 2, 11, { '1', 'this 2', 'and then' }) + + eq({ + 'This should be 1', + 'this 2', + 'and then the last one', + }, get_lines(0, -1, true)) + -- cursor should end up after 'n' in 'then' + eq({3, 8}, curwin('get_cursor')) + end) + + it('adjusts cursor column if replacement is inside of a single line', function() + insert([[ + This should be first + then there is a line we do not want + and finally the last one]]) + + -- position the cursor on 'y' in 'finally' + curwin('set_cursor', {3, 10}) + set_text(2, 4, 2, 11, { 'then' }) + + eq({ + 'This should be first', + 'then there is a line we do not want', + 'and then the last one', + }, get_lines(0, -1, true)) + -- cursor should end up on 'n' in 'then' + eq({3, 7}, curwin('get_cursor')) + end) + + it('does not move cursor column after end of a line', function() + insert([[ + This should be the only line here + !!!]]) + + -- position cursor on the last '1' + curwin('set_cursor', {2, 2}) + + local cursor = exec_lua([[ + vim.api.nvim_buf_set_text(0, 0, 33, 1, 3, {}) + return vim.api.nvim_win_get_cursor(0) + ]]) + + eq({ 'This should be the only line here' }, get_lines(0, -1, true)) + -- cursor should end up on '!' + eq({1, 32}, curwin('get_cursor')) + -- immediate call to nvim_win_get_cursor should have returned the same position + eq({1, 32}, cursor) + end) + + it('does not move cursor column before start of a line', function() + insert('\n!!!') + + -- position cursor on the last '1' + curwin('set_cursor', {2, 2}) + + local cursor = exec_lua([[ + vim.api.nvim_buf_set_text(0, 0, 0, 1, 3, {}) + return vim.api.nvim_win_get_cursor(0) + ]]) + + eq({ '' }, get_lines(0, -1, true)) + -- cursor should end up on '!' + eq({1, 0}, curwin('get_cursor')) + -- immediate call to nvim_win_get_cursor should have returned the same position + eq({1, 0}, cursor) + end) + + describe('with virtualedit', function() + it('adjusts cursor line and column to keep it inside replacement range if cursor is not after eol', function() + insert([[ + This should be first + then there is a line we do not want + and finally the last one]]) + + -- position cursor on 't' in 'want' + curwin('set_cursor', {2, 34}) + -- turn on virtualedit + command('set virtualedit=all') + + local cursor = exec_lua([[ + vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, { + 'the line we do not want', + 'but hopefully', + }) + return vim.api.nvim_win_get_cursor(0) + ]]) + + eq({ + 'This should be the line we do not want', + 'but hopefully the last one', + }, get_lines(0, -1, true)) + -- cursor should end up on 'y' in 'hopefully' + -- to stay in the range + eq({2, 12}, curwin('get_cursor')) + -- immediate call to nvim_win_get_cursor should have returned the same position + eq({2, 12}, cursor) + -- coladd should be 0 + eq(0, exec_lua([[ + return vim.fn.winsaveview().coladd + ]])) + end) + + it('does not change cursor screen column when cursor is after eol and row got shorter', function() + insert([[ + This should be first + then there is a line we do not want + and finally the last one]]) + + -- position cursor on 't' in 'want' + curwin('set_cursor', {2, 34}) + -- turn on virtualedit + command('set virtualedit=all') + -- move cursor after eol + exec_lua([[ + vim.fn.winrestview({ coladd = 5 }) + ]]) + + local cursor = exec_lua([[ + vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, { + 'the line we do not want', + 'but hopefully', + }) + return vim.api.nvim_win_get_cursor(0) + ]]) + + eq({ + 'This should be the line we do not want', + 'but hopefully the last one', + }, get_lines(0, -1, true)) + -- cursor should end up at eol of a new row + eq({2, 26}, curwin('get_cursor')) + -- immediate call to nvim_win_get_cursor should have returned the same position + eq({2, 26}, cursor) + -- coladd should be increased so that cursor stays in the same screen column + eq(13, exec_lua([[ + return vim.fn.winsaveview().coladd + ]])) + end) + + it('does not change cursor screen column when cursor is after eol and row got longer', function() + insert([[ + This should be first + then there is a line we do not want + and finally the last one]]) + + -- position cursor on 't' in 'first' + curwin('set_cursor', {1, 19}) + -- turn on virtualedit + command('set virtualedit=all') + -- move cursor after eol + exec_lua([[ + vim.fn.winrestview({ coladd = 21 }) + ]]) + + local cursor = exec_lua([[ + vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, { + 'the line we do not want', + 'but hopefully', + }) + return vim.api.nvim_win_get_cursor(0) + ]]) + + eq({ + 'This should be the line we do not want', + 'but hopefully the last one', + }, get_lines(0, -1, true)) + -- cursor should end up at eol of a new row + eq({1, 38}, curwin('get_cursor')) + -- immediate call to nvim_win_get_cursor should have returned the same position + eq({1, 38}, cursor) + -- coladd should be increased so that cursor stays in the same screen column + eq(2, exec_lua([[ + return vim.fn.winsaveview().coladd + ]])) + end) + + it('does not change cursor screen column when cursor is after eol and row extended past cursor column', function() + insert([[ + This should be first + then there is a line we do not want + and finally the last one]]) + + -- position cursor on 't' in 'first' + curwin('set_cursor', {1, 19}) + -- turn on virtualedit + command('set virtualedit=all') + -- move cursor after eol just a bit + exec_lua([[ + vim.fn.winrestview({ coladd = 3 }) + ]]) + + local cursor = exec_lua([[ + vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, { + 'the line we do not want', + 'but hopefully', + }) + return vim.api.nvim_win_get_cursor(0) + ]]) + + eq({ + 'This should be the line we do not want', + 'but hopefully the last one', + }, get_lines(0, -1, true)) + -- cursor should stay at the same screen column + eq({1, 22}, curwin('get_cursor')) + -- immediate call to nvim_win_get_cursor should have returned the same position + eq({1, 22}, cursor) + -- coladd should become 0 + eq(0, exec_lua([[ + return vim.fn.winsaveview().coladd + ]])) + end) + + it('does not change cursor screen column when cursor is after eol and row range decreased', function() + insert([[ + This should be first + then there is a line we do not want + and one more + and finally the last one]]) + + -- position cursor on 'e' in 'more' + curwin('set_cursor', {3, 11}) + -- turn on virtualedit + command('set virtualedit=all') + -- move cursor after eol + exec_lua([[ + vim.fn.winrestview({ coladd = 28 }) + ]]) + + local cursor = exec_lua([[ + vim.api.nvim_buf_set_text(0, 0, 15, 3, 11, { + 'the line we do not want', + 'but hopefully', + }) + return vim.api.nvim_win_get_cursor(0) + ]]) + + eq({ + 'This should be the line we do not want', + 'but hopefully the last one', + }, get_lines(0, -1, true)) + -- cursor should end up at eol of a new row + eq({2, 26}, curwin('get_cursor')) + -- immediate call to nvim_win_get_cursor should have returned the same position + eq({2, 26}, cursor) + -- coladd should be increased so that cursor stays in the same screen column + eq(13, exec_lua([[ + return vim.fn.winsaveview().coladd + ]])) + end) + end) + end) + + describe('when cursor is at end_row and after end_col', function() + it('adjusts cursor column when only a newline is added or deleted', function() + insert([[ + first line + second + line]]) + + -- position the cursor on 'i' + curwin('set_cursor', {3, 2}) + set_text(1, 6, 2, 0, {}) + eq({'first line', 'second line'}, get_lines(0, -1, true)) + -- cursor should stay on 'i' + eq({2, 8}, curwin('get_cursor')) + + -- add a newline back + set_text(1, 6, 1, 6, {'', ''}) + eq({'first line', 'second', ' line'}, get_lines(0, -1, true)) + -- cursor should return back to the original position + eq({3, 2}, curwin('get_cursor')) + end) + + it('adjusts cursor column if the range is not bound to either start or end of a line', function() + insert([[ + This should be first + then there is a line we do not want + and finally the last one]]) + + -- position the cursor on 'h' in 'the' + curwin('set_cursor', {3, 13}) + set_text(0, 14, 2, 11, {}) + eq({'This should be the last one'}, get_lines(0, -1, true)) + -- cursor should stay on 'h' + eq({1, 16}, curwin('get_cursor')) + -- add deleted lines back + set_text(0, 14, 0, 14, { + ' first', + 'then there is a line we do not want', + 'and finally', + }) + eq({ + 'This should be first', + 'then there is a line we do not want', + 'and finally the last one', + }, get_lines(0, -1, true)) + -- cursor should return back to the original position + eq({3, 13}, curwin('get_cursor')) + end) + + it('adjusts cursor column if replacing lines in range, not just deleting and adding', function() + insert([[ + This should be first + then there is a line we do not want + and finally the last one]]) + + -- position the cursor on 's' in 'last' + curwin('set_cursor', {3, 18}) + set_text(0, 15, 2, 11, { + 'the line we do not want', + 'but hopefully', + }) + + eq({ + 'This should be the line we do not want', + 'but hopefully the last one', + }, get_lines(0, -1, true)) + -- cursor should stay on 's' + eq({2, 20}, curwin('get_cursor')) + + set_text(0, 15, 1, 13, { + 'first', + 'then there is a line we do not want', + 'and finally', + }) + + eq({ + 'This should be first', + 'then there is a line we do not want', + 'and finally the last one', + }, get_lines(0, -1, true)) + -- cursor should return back to the original position + eq({3, 18}, curwin('get_cursor')) + end) + + it('does not move cursor column after end of a line', function() + insert([[ + This should be the only line here + ]]) + + -- position cursor at the empty line + curwin('set_cursor', {2, 0}) + + local cursor = exec_lua([[ + vim.api.nvim_buf_set_text(0, 0, 33, 1, 0, {'!'}) + return vim.api.nvim_win_get_cursor(0) + ]]) + + eq({ 'This should be the only line here!' }, get_lines(0, -1, true)) + -- cursor should end up on '!' + eq({1, 33}, curwin('get_cursor')) + -- immediate call to nvim_win_get_cursor should have returned the same position + eq({1, 33}, cursor) + end) + + it('does not move cursor column before start of a line', function() + insert('\n') + + eq({ '', '' }, get_lines(0, -1, true)) + + -- position cursor on the last '1' + curwin('set_cursor', {2, 2}) + + local cursor = exec_lua([[ + vim.api.nvim_buf_set_text(0, 0, 0, 1, 0, {''}) + return vim.api.nvim_win_get_cursor(0) + ]]) + + eq({ '' }, get_lines(0, -1, true)) + -- cursor should end up on '!' + eq({1, 0}, curwin('get_cursor')) + -- immediate call to nvim_win_get_cursor should have returned the same position + eq({1, 0}, cursor) + end) + end) + it('can handle NULs', function() set_text(0, 0, 0, 0, {'ab\0cd'}) eq('ab\0cd', curbuf_depr('get_line', 0)) diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index 6d8e3d8e0a..a917432dab 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -753,7 +753,14 @@ describe('API/extmarks', function() }) end) - -- TODO(bfredl): add more tests! + it('can get overlapping extmarks', function() + set_extmark(ns, 1, 0, 0, {end_row = 5, end_col=0}) + set_extmark(ns, 2, 2, 5, {end_row = 2, end_col=30}) + set_extmark(ns, 3, 0, 5, {end_row = 2, end_col=10}) + set_extmark(ns, 4, 0, 0, {end_row = 1, end_col=0}) + eq({{ 2, 2, 5 }}, get_extmarks(ns, {2, 0}, {2, -1}, { overlap=false })) + eq({{ 1, 0, 0 }, { 3, 0, 5}, {2, 2, 5}}, get_extmarks(ns, {2, 0}, {2, -1}, { overlap=true })) + end) end) it('replace works', function() diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua index 5fa2235018..492fd73223 100644 --- a/test/functional/api/highlight_spec.lua +++ b/test/functional/api/highlight_spec.lua @@ -439,6 +439,15 @@ describe('API: get highlight', function() eq('Highlight id out of bounds', pcall_err(meths.get_hl, 0, { name = 'Test set hl' })) end) + it('nvim_get_hl with create flag', function() + eq({}, nvim("get_hl", 0, {name = 'Foo', create = false})) + eq(0, funcs.hlexists('Foo')) + meths.get_hl(0, {name = 'Bar', create = true}) + eq(1, funcs.hlexists('Bar')) + meths.get_hl(0, {name = 'FooBar'}) + eq(1, funcs.hlexists('FooBar')) + end) + it('can get all highlights in current namespace', function() local ns = get_ns() meths.set_hl(ns, 'Test_hl', { bg = '#B4BEFE' }) diff --git a/test/functional/api/server_notifications_spec.lua b/test/functional/api/server_notifications_spec.lua index 53642858b2..bc43f6564d 100644 --- a/test/functional/api/server_notifications_spec.lua +++ b/test/functional/api/server_notifications_spec.lua @@ -6,9 +6,7 @@ local eq, clear, eval, command, nvim, next_msg = local meths = helpers.meths local exec_lua = helpers.exec_lua local retry = helpers.retry -local is_ci = helpers.is_ci local assert_alive = helpers.assert_alive -local skip = helpers.skip local testlog = 'Xtest-server-notify-log' @@ -90,7 +88,6 @@ describe('notify', function() end) it('cancels stale events on channel close', function() - skip(is_ci(), 'hangs on CI #14083 #15251') local catchan = eval("jobstart(['cat'], {'rpc': v:true})") local catpath = eval('exepath("cat")') eq({id=catchan, argv={catpath}, stream='job', mode='rpc', client = {}}, exec_lua ([[ diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index b98cf97e7e..6f5397a089 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -857,6 +857,11 @@ function module.testprg(name) return ('%s/%s%s'):format(module.nvim_dir, name, ext) end +function module.is_asan() + local version = module.eval('execute("verbose version")') + return version:match('-fsanitize=[a-z,]*address') +end + -- Returns a valid, platform-independent Nvim listen address. -- Useful for communicating with child instances. function module.new_pipename() diff --git a/test/functional/legacy/memory_usage_spec.lua b/test/functional/legacy/memory_usage_spec.lua index bf37315914..5f722e5190 100644 --- a/test/functional/legacy/memory_usage_spec.lua +++ b/test/functional/legacy/memory_usage_spec.lua @@ -11,14 +11,10 @@ local load_adjust = helpers.load_adjust local write_file = helpers.write_file local is_os = helpers.is_os local is_ci = helpers.is_ci - -local function isasan() - local version = eval('execute("verbose version")') - return version:match('-fsanitize=[a-z,]*address') -end +local is_asan = helpers.is_asan clear() -if isasan() then +if is_asan() then pending('ASAN build is difficult to estimate memory usage', function() end) return elseif is_os('win') then diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua index c19891a794..51e4548edb 100644 --- a/test/functional/lua/buffer_updates_spec.lua +++ b/test/functional/lua/buffer_updates_spec.lua @@ -826,53 +826,53 @@ describe('lua: nvim_buf_attach on_bytes', function() feed("<esc>u") check_events { - { "test1", "bytes", 1, 8, 0, 0, 0, 0, 1, 1, 0, 4, 4 }, - { "test1", "bytes", 1, 8, 0, 0, 0, 0, 4, 4, 0, 0, 0 } + { "test1", "bytes", 1, 9, 0, 0, 0, 0, 1, 1, 0, 4, 4 }, + { "test1", "bytes", 1, 9, 0, 0, 0, 0, 4, 4, 0, 0, 0 } } -- in REPLACE mode feed("R<tab><tab>") check_events { - { "test1", "bytes", 1, 9, 0, 0, 0, 0, 1, 1, 0, 1, 1 }, - { "test1", "bytes", 1, 10, 0, 1, 1, 0, 0, 0, 0, 1, 1 }, - { "test1", "bytes", 1, 11, 0, 2, 2, 0, 1, 1, 0, 1, 1 }, - { "test1", "bytes", 1, 12, 0, 3, 3, 0, 0, 0, 0, 1, 1 }, - { "test1", "bytes", 1, 13, 0, 0, 0, 0, 4, 4, 0, 1, 1 }, + { "test1", "bytes", 1, 10, 0, 0, 0, 0, 1, 1, 0, 1, 1 }, + { "test1", "bytes", 1, 11, 0, 1, 1, 0, 0, 0, 0, 1, 1 }, + { "test1", "bytes", 1, 12, 0, 2, 2, 0, 1, 1, 0, 1, 1 }, + { "test1", "bytes", 1, 13, 0, 3, 3, 0, 0, 0, 0, 1, 1 }, + { "test1", "bytes", 1, 14, 0, 0, 0, 0, 4, 4, 0, 1, 1 }, } feed("<esc>u") check_events { - { "test1", "bytes", 1, 14, 0, 0, 0, 0, 1, 1, 0, 4, 4 }, - { "test1", "bytes", 1, 14, 0, 2, 2, 0, 2, 2, 0, 1, 1 }, - { "test1", "bytes", 1, 14, 0, 0, 0, 0, 2, 2, 0, 1, 1 } + { "test1", "bytes", 1, 16, 0, 0, 0, 0, 1, 1, 0, 4, 4 }, + { "test1", "bytes", 1, 16, 0, 2, 2, 0, 2, 2, 0, 1, 1 }, + { "test1", "bytes", 1, 16, 0, 0, 0, 0, 2, 2, 0, 1, 1 } } -- in VISUALREPLACE mode feed("gR<tab><tab>") check_events { - { "test1", "bytes", 1, 15, 0, 0, 0, 0, 1, 1, 0, 1, 1 }; - { "test1", "bytes", 1, 16, 0, 1, 1, 0, 1, 1, 0, 1, 1 }; - { "test1", "bytes", 1, 17, 0, 2, 2, 0, 1, 1, 0, 1, 1 }; - { "test1", "bytes", 1, 18, 0, 3, 3, 0, 1, 1, 0, 1, 1 }; - { "test1", "bytes", 1, 19, 0, 3, 3, 0, 1, 1, 0, 0, 0 }; - { "test1", "bytes", 1, 20, 0, 3, 3, 0, 0, 0, 0, 1, 1 }; - { "test1", "bytes", 1, 22, 0, 2, 2, 0, 1, 1, 0, 0, 0 }; - { "test1", "bytes", 1, 23, 0, 2, 2, 0, 0, 0, 0, 1, 1 }; - { "test1", "bytes", 1, 25, 0, 1, 1, 0, 1, 1, 0, 0, 0 }; - { "test1", "bytes", 1, 26, 0, 1, 1, 0, 0, 0, 0, 1, 1 }; - { "test1", "bytes", 1, 28, 0, 0, 0, 0, 1, 1, 0, 0, 0 }; - { "test1", "bytes", 1, 29, 0, 0, 0, 0, 0, 0, 0, 1, 1 }; - { "test1", "bytes", 1, 31, 0, 0, 0, 0, 4, 4, 0, 1, 1 }; + { "test1", "bytes", 1, 17, 0, 0, 0, 0, 1, 1, 0, 1, 1 }; + { "test1", "bytes", 1, 18, 0, 1, 1, 0, 1, 1, 0, 1, 1 }; + { "test1", "bytes", 1, 19, 0, 2, 2, 0, 1, 1, 0, 1, 1 }; + { "test1", "bytes", 1, 20, 0, 3, 3, 0, 1, 1, 0, 1, 1 }; + { "test1", "bytes", 1, 21, 0, 3, 3, 0, 1, 1, 0, 0, 0 }; + { "test1", "bytes", 1, 22, 0, 3, 3, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 24, 0, 2, 2, 0, 1, 1, 0, 0, 0 }; + { "test1", "bytes", 1, 25, 0, 2, 2, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 27, 0, 1, 1, 0, 1, 1, 0, 0, 0 }; + { "test1", "bytes", 1, 28, 0, 1, 1, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 30, 0, 0, 0, 0, 1, 1, 0, 0, 0 }; + { "test1", "bytes", 1, 31, 0, 0, 0, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 33, 0, 0, 0, 0, 4, 4, 0, 1, 1 }; } -- inserting tab after other tabs command("set sw=4") feed("<esc>0a<tab>") check_events { - { "test1", "bytes", 1, 32, 0, 1, 1, 0, 0, 0, 0, 1, 1 }; - { "test1", "bytes", 1, 33, 0, 2, 2, 0, 0, 0, 0, 1, 1 }; - { "test1", "bytes", 1, 34, 0, 3, 3, 0, 0, 0, 0, 1, 1 }; - { "test1", "bytes", 1, 35, 0, 4, 4, 0, 0, 0, 0, 1, 1 }; - { "test1", "bytes", 1, 36, 0, 1, 1, 0, 4, 4, 0, 1, 1 }; + { "test1", "bytes", 1, 34, 0, 1, 1, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 35, 0, 2, 2, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 36, 0, 3, 3, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 37, 0, 4, 4, 0, 0, 0, 0, 1, 1 }; + { "test1", "bytes", 1, 38, 0, 1, 1, 0, 4, 4, 0, 1, 1 }; } end) diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua index 2c7b3ff324..6bdb9ed79d 100644 --- a/test/functional/lua/fs_spec.lua +++ b/test/functional/lua/fs_spec.lua @@ -1,4 +1,5 @@ local helpers = require('test.functional.helpers')(after_each) +local uv = require('luv') local clear = helpers.clear local exec_lua = helpers.exec_lua @@ -288,11 +289,12 @@ describe('vim.fs', function() eq('/', exec_lua [[ return vim.fs.normalize('/') ]]) end) it('works with ~', function() - eq( exec_lua([[ - local home = ... - return home .. '/src/foo' - ]], is_os('win') and vim.fs.normalize(os.getenv('USERPROFILE')) or os.getenv('HOME') - ) , exec_lua [[ return vim.fs.normalize('~/src/foo') ]]) + eq( + exec_lua([[ + local home = ... + return home .. '/src/foo' + ]], vim.fs.normalize(uv.os_homedir())), + exec_lua [[ return vim.fs.normalize('~/src/foo') ]]) end) it('works with environment variables', function() local xdg_config_home = test_build_dir .. '/.config' diff --git a/test/functional/plugin/lsp/utils_spec.lua b/test/functional/plugin/lsp/utils_spec.lua index c91fffa90f..f544255d81 100644 --- a/test/functional/plugin/lsp/utils_spec.lua +++ b/test/functional/plugin/lsp/utils_spec.lua @@ -1,4 +1,6 @@ local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local feed = helpers.feed local eq = helpers.eq local exec_lua = helpers.exec_lua @@ -85,4 +87,98 @@ describe('vim.lsp.util', function() eq(expected, stylize_markdown(lines, opts)) end) end) + + describe("make_floating_popup_options", function () + + local function assert_anchor(anchor_bias, expected_anchor) + local opts = exec_lua([[ + local args = { ... } + local anchor_bias = args[1] + return vim.lsp.util.make_floating_popup_options(30, 10, { anchor_bias = anchor_bias }) + ]], anchor_bias) + + eq(expected_anchor, string.sub(opts.anchor, 1, 1)) + end + + local screen + before_each(function () + helpers.clear() + screen = Screen.new(80, 80) + screen:attach() + feed("79i<CR><Esc>") -- fill screen with empty lines + end) + + describe('when on the first line it places window below', function () + before_each(function () + feed('gg') + end) + + it('for anchor_bias = "auto"', function () + assert_anchor('auto', 'N') + end) + + it('for anchor_bias = "above"', function () + assert_anchor('above', 'N') + end) + + it('for anchor_bias = "below"', function () + assert_anchor('below', 'N') + end) + end) + + describe('when on the last line it places window above', function () + before_each(function () + feed('G') + end) + + it('for anchor_bias = "auto"', function () + assert_anchor('auto', 'S') + end) + + it('for anchor_bias = "above"', function () + assert_anchor('above', 'S') + end) + + it('for anchor_bias = "below"', function () + assert_anchor('below', 'S') + end) + end) + + describe('with 20 lines above, 59 lines below', function () + before_each(function () + feed('gg20j') + end) + + it('places window below for anchor_bias = "auto"', function () + assert_anchor('auto', 'N') + end) + + it('places window above for anchor_bias = "above"', function () + assert_anchor('above', 'S') + end) + + it('places window below for anchor_bias = "below"', function () + assert_anchor('below', 'N') + end) + end) + + describe('with 59 lines above, 20 lines below', function () + before_each(function () + feed('G20k') + end) + + it('places window above for anchor_bias = "auto"', function () + assert_anchor('auto', 'S') + end) + + it('places window above for anchor_bias = "above"', function () + assert_anchor('above', 'S') + end) + + it('places window below for anchor_bias = "below"', function () + assert_anchor('below', 'N') + end) + end) + end) + end) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 3eb89b4556..e0a8badb67 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1787,7 +1787,7 @@ describe('LSP', function() eq({ 'First line of text'; }, buf_lines(1)) - eq({ 1, 6 }, funcs.nvim_win_get_cursor(0)) + eq({ 1, 17 }, funcs.nvim_win_get_cursor(0)) end) it('fix the cursor row', function() diff --git a/test/functional/terminal/edit_spec.lua b/test/functional/terminal/edit_spec.lua index db5dba7374..29361bc0fa 100644 --- a/test/functional/terminal/edit_spec.lua +++ b/test/functional/terminal/edit_spec.lua @@ -36,7 +36,6 @@ describe(':edit term://*', function() end) it("runs TermOpen early enough to set buffer-local 'scrollback'", function() - if helpers.skip(helpers.is_os('win')) then return end local columns, lines = 20, 4 local scr = get_screen(columns, lines) local rep = 97 diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index 62dbd57202..daa4b4bdb3 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -103,7 +103,7 @@ describe('decorations providers', function() ]]} check_trace { { "start", 4 }; - { "win", 1000, 1, 0, 8 }; + { "win", 1000, 1, 0, 6 }; { "line", 1000, 1, 0 }; { "line", 1000, 1, 1 }; { "line", 1000, 1, 2 }; @@ -128,7 +128,7 @@ describe('decorations providers', function() check_trace { { "start", 5 }; { "buf", 1, 5 }; - { "win", 1000, 1, 0, 8 }; + { "win", 1000, 1, 0, 6 }; { "line", 1000, 1, 6 }; { "end", 5 }; } @@ -195,7 +195,7 @@ describe('decorations providers', function() check_trace { { "start", 5 }; - { "win", 1000, 1, 0, 5 }; + { "win", 1000, 1, 0, 3 }; { "line", 1000, 1, 0 }; { "line", 1000, 1, 1 }; { "line", 1000, 1, 2 }; @@ -691,6 +691,12 @@ describe('extmark decorations', function() [33] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGray}; [34] = {background = Screen.colors.Yellow}; [35] = {background = Screen.colors.Yellow, bold = true, foreground = Screen.colors.Blue}; + [36] = {foreground = Screen.colors.Blue1, bold = true, background = Screen.colors.Red}; + [37] = {background = Screen.colors.WebGray, foreground = Screen.colors.DarkBlue}; + [38] = {background = Screen.colors.LightBlue}; + [39] = {foreground = Screen.colors.Blue1, background = Screen.colors.LightCyan1, bold = true}; + [40] = {reverse = true}; + [41] = {bold = true, reverse = true}; } ns = meths.create_namespace 'test' @@ -858,6 +864,104 @@ describe('extmark decorations', function() ]]} end) + it('overlay virtual text works with wrapped lines #25158', function() + screen:try_resize(50, 6) + insert(('ab'):rep(100)) + for i = 0, 9 do + meths.buf_set_extmark(0, ns, 0, 42 + i, { virt_text={{tostring(i), 'ErrorMsg'}}, virt_text_pos='overlay'}) + meths.buf_set_extmark(0, ns, 0, 91 + i, { virt_text={{tostring(i), 'ErrorMsg'}}, virt_text_pos='overlay', virt_text_hide=true}) + end + screen:expect{grid=[[ + ababababababababababababababababababababab{4:01234567}| + {4:89}abababababababababababababababababababa{4:012345678}| + {4:9}babababababababababababababababababababababababab| + ababababababababababababababababababababababababa^b| + {1:~ }| + | + ]]} + + command('set showbreak=++') + screen:expect{grid=[[ + ababababababababababababababababababababab{4:01234567}| + {1:++}{4:89}abababababababababababababababababababa{4:0123456}| + {1:++}{4:789}babababababababababababababababababababababab| + {1:++}abababababababababababababababababababababababab| + {1:++}ababa^b | + | + ]]} + + feed('2gkvg0') + screen:expect{grid=[[ + ababababababababababababababababababababab{4:01234567}| + {1:++}{4:89}abababababababababababababababababababa{4:0123456}| + {1:++}^a{18:babab}ababababababababababababababababababababab| + {1:++}abababababababababababababababababababababababab| + {1:++}ababab | + {24:-- VISUAL --} | + ]]} + + feed('o') + screen:expect{grid=[[ + ababababababababababababababababababababab{4:01234567}| + {1:++}{4:89}abababababababababababababababababababa{4:0123456}| + {1:++}{18:ababa}^bababababababababababababababababababababab| + {1:++}abababababababababababababababababababababababab| + {1:++}ababab | + {24:-- VISUAL --} | + ]]} + + feed('gk') + screen:expect{grid=[[ + ababababababababababababababababababababab{4:01234567}| + {1:++}{4:89}aba^b{18:ababababababababababababababababababababab}| + {1:++}{18:a}{4:89}babababababababababababababababababababababab| + {1:++}abababababababababababababababababababababababab| + {1:++}ababab | + {24:-- VISUAL --} | + ]]} + + feed('o') + screen:expect{grid=[[ + ababababababababababababababababababababab{4:01234567}| + {1:++}{4:89}aba{18:bababababababababababababababababababababab}| + {1:++}^a{4:89}babababababababababababababababababababababab| + {1:++}abababababababababababababababababababababababab| + {1:++}ababab | + {24:-- VISUAL --} | + ]]} + + feed('<Esc>$') + command('set number showbreak=') + screen:expect{grid=[[ + {2: 1 }ababababababababababababababababababababab{4:0123}| + {2: }{4:456789}abababababababababababababababababababa{4:0}| + {2: }{4:123456789}babababababababababababababababababab| + {2: }ababababababababababababababababababababababab| + {2: }abababababababa^b | + | + ]]} + + command('set cpoptions+=n') + screen:expect{grid=[[ + {2: 1 }ababababababababababababababababababababab{4:0123}| + {4:456789}abababababababababababababababababababa{4:01234}| + {4:56789}babababababababababababababababababababababab| + ababababababababababababababababababababababababab| + aba^b | + | + ]]} + + feed('0g$hi<Tab>') + screen:expect{grid=[[ + {2: 1 }ababababababababababababababababababababab{4:01} | + {4:^23456789}abababababababababababababababababababa{4:0}| + {4:123456789}babababababababababababababababababababab| + ababababababababababababababababababababababababab| + abababab | + {24:-- INSERT --} | + ]]} + end) + it('virt_text_hide hides overlay virtual text when extmark is off-screen', function() screen:try_resize(50, 3) command('set nowrap') @@ -1027,12 +1131,12 @@ describe('extmark decorations', function() it('can have virtual text of right_align and fixed win_col position', function() insert(example_text) feed 'gg' - meths.buf_set_extmark(0, ns, 1, 0, { virt_text={{'Very', 'ErrorMsg'}}, virt_text_win_col=31, hl_mode='blend'}) + meths.buf_set_extmark(0, ns, 1, 0, { virt_text={{'Very', 'ErrorMsg'}}, virt_text_win_col=31, hl_mode='blend'}) meths.buf_set_extmark(0, ns, 1, 0, { virt_text={{'VERY', 'ErrorMsg'}}, virt_text_pos='right_align', hl_mode='blend'}) - meths.buf_set_extmark(0, ns, 2, 10, { virt_text={{'Much', 'ErrorMsg'}}, virt_text_win_col=31, hl_mode='blend'}) + meths.buf_set_extmark(0, ns, 2, 10, { virt_text={{'Much', 'ErrorMsg'}}, virt_text_win_col=31, hl_mode='blend'}) meths.buf_set_extmark(0, ns, 2, 10, { virt_text={{'MUCH', 'ErrorMsg'}}, virt_text_pos='right_align', hl_mode='blend'}) - meths.buf_set_extmark(0, ns, 3, 15, { virt_text={{'Error', 'ErrorMsg'}}, virt_text_win_col=31, hl_mode='blend'}) - meths.buf_set_extmark(0, ns, 3, 15, { virt_text={{'ERROR', 'ErrorMsg'}}, virt_text_pos='right_align', hl_mode='blend'}) + meths.buf_set_extmark(0, ns, 3, 14, { virt_text={{'Error', 'ErrorMsg'}}, virt_text_win_col=31, hl_mode='blend'}) + meths.buf_set_extmark(0, ns, 3, 14, { virt_text={{'ERROR', 'ErrorMsg'}}, virt_text_pos='right_align', hl_mode='blend'}) meths.buf_set_extmark(0, ns, 7, 21, { virt_text={{'-', 'NonText'}}, virt_text_win_col=4, hl_mode='blend'}) meths.buf_set_extmark(0, ns, 7, 21, { virt_text={{'-', 'NonText'}}, virt_text_pos='right_align', hl_mode='blend'}) -- empty virt_text should not change anything @@ -1190,40 +1294,78 @@ describe('extmark decorations', function() | ]]} - command 'set cpoptions-=n nonumber nowrap' + command 'set cpoptions-=n nowrap' screen:expect{grid=[[ - for _,item in ipairs(items) do | - local text, hl_id_cell, cou{4:Very} unpack(ite{4:VERY}| - if | - hl_id_cell ~= nil then {4:Much} {4:MUCH}| - --^ -- -- -- -- -- -- --{4:Error}- -- -- h{4:ERROR}| - end | - for _ = 1, (count or 1) do | - local cell = line[colpos] | - {1:-} cell.text = text {1:-}| - cell.hl_id = hl_id | - colpos = colpos+1 | - end | - end | + {2: 1 }for _,item in ipairs(items) do | + {2: 2 } local text, hl_id_cell, cou{4:Very} unpack{4:VERY}| + {2: 3 } if | + {2: 4 }hl_id_cell ~= nil then {4:Much} {4:MUCH}| + {2: 5 } --^ -- -- -- -- -- -- --{4:Error}- -- {4:ERROR}| + {2: 6 } end | + {2: 7 } for _ = 1, (count or 1) do | + {2: 8 } local cell = line[colpos] | + {2: 9 } {1:-} cell.text = text {1:-}| + {2: 10 } cell.hl_id = hl_id | + {2: 11 } colpos = colpos+1 | + {2: 12 } end | + {2: 13 }end | {1:~ }| | ]]} - feed '8zl' + feed '12zl' screen:expect{grid=[[ - em in ipairs(items) do | - l text, hl_id_cell, count = unp{4:Very}item) {4:VERY}| - | - ll ~= nil then {4:Much} {4:MUCH}| - --^ -- -- -- -- -- -- -- -- -- -{4:Error}hl_id = h{4:ERROR}| - | - _ = 1, (count or 1) do | - local cell = line[colpos] | - cell{1:-}text = text {1:-}| - cell.hl_id = hl_id | - colpos = colpos+1 | - | + {2: 1 }n ipairs(items) do | + {2: 2 }xt, hl_id_cell, count = unpack({4:Very}) {4:VERY}| + {2: 3 } | + {2: 4 }= nil then {4:Much} {4:MUCH}| + {2: 5 }^- -- -- -- -- -- -- -- -- -- --{4:Error}d = h{4:ERROR}| + {2: 6 } | + {2: 7 }1, (count or 1) do | + {2: 8 }l cell = line[colpos] | + {2: 9 }.tex{1:-} = text {1:-}| + {2: 10 }.hl_id = hl_id | + {2: 11 }os = colpos+1 | + {2: 12 } | + {2: 13 } | + {1:~ }| | + ]]} + + feed('fhi<Tab>') + screen:expect{grid=[[ + {2: 1 }n ipairs(items) do | + {2: 2 }xt, hl_id_cell, count = unpack({4:Very}) {4:VERY}| + {2: 3 } | + {2: 4 }= nil then {4:Much} {4:MUCH}| + {2: 5 }- -- -- -- -- -- -- -- -- -- --{4:Error}^hl_id{4:ERROR}| + {2: 6 } | + {2: 7 }1, (count or 1) do | + {2: 8 }l cell = line[colpos] | + {2: 9 }.tex{1:-} = text {1:-}| + {2: 10 }.hl_id = hl_id | + {2: 11 }os = colpos+1 | + {2: 12 } | + {2: 13 } | + {1:~ }| + {24:-- INSERT --} | + ]]} + + feed('<Esc>0') + screen:expect{grid=[[ + {2: 1 }for _,item in ipairs(items) do | + {2: 2 } local text, hl_id_cell, cou{4:Very} unpack{4:VERY}| + {2: 3 } if | + {2: 4 }hl_id_cell ~= nil then {4:Much} {4:MUCH}| + {2: 5 }^ -- -- -- -- -- -- -- --{4:Error}- -- {4:ERROR}| + {2: 6 } end | + {2: 7 } for _ = 1, (count or 1) do | + {2: 8 } local cell = line[colpos] | + {2: 9 } {1:-} cell.text = text {1:-}| + {2: 10 } cell.hl_id = hl_id | + {2: 11 } colpos = colpos+1 | + {2: 12 } end | + {2: 13 }end | {1:~ }| | ]]} @@ -1235,20 +1377,21 @@ describe('extmark decorations', function() 22222 33333]]) command('1,2fold') - command('set nowrap') screen:try_resize(50, 3) feed('zb') -- XXX: the behavior of overlay virtual text at non-zero column is strange: -- 1. With 'wrap' it is never shown. -- 2. With 'nowrap' it is shown only if the extmark is hidden before leftcol. meths.buf_set_extmark(0, ns, 0, 0, { virt_text = {{'AA', 'Underlined'}}, hl_mode = 'combine', virt_text_pos = 'overlay' }) - meths.buf_set_extmark(0, ns, 0, 1, { virt_text = {{'BB', 'Underlined'}}, hl_mode = 'combine', virt_text_win_col = 10 }) + meths.buf_set_extmark(0, ns, 0, 5, { virt_text = {{'BB', 'Underlined'}}, hl_mode = 'combine', virt_text_win_col = 10 }) meths.buf_set_extmark(0, ns, 0, 2, { virt_text = {{'CC', 'Underlined'}}, hl_mode = 'combine', virt_text_pos = 'right_align' }) screen:expect{grid=[[ {29:AA}{33:- 2 lin}{29:BB}{33:: 11111·····························}{29:CC}| 3333^3 | | ]]} + command('set nowrap') + screen:expect_unchanged() feed('zl') screen:expect{grid=[[ {29:AA}{33:- 2 lin}{29:BB}{33:: 11111·····························}{29:CC}| @@ -1269,6 +1412,38 @@ describe('extmark decorations', function() ]]} end) + it('virtual text works below diff filler lines', function() + screen:try_resize(53, 8) + insert([[ + aaaaa + bbbbb + ccccc + ddddd + eeeee]]) + command('rightbelow vnew') + insert([[ + bbbbb + ccccc + ddddd + eeeee]]) + command('windo diffthis') + meths.buf_set_extmark(0, ns, 0, 0, { virt_text = {{'AA', 'Underlined'}}, virt_text_pos = 'overlay' }) + meths.buf_set_extmark(0, ns, 0, 0, { virt_text = {{'BB', 'Underlined'}}, virt_text_win_col = 10 }) + meths.buf_set_extmark(0, ns, 0, 0, { virt_text = {{'CC', 'Underlined'}}, virt_text_pos = 'right_align' }) + screen:expect{grid=[[ + {37: }{38:aaaaa }│{37: }{39:------------------------}| + {37: }bbbbb │{37: }{28:AA}bbb {28:BB} {28:CC}| + {37: }ccccc │{37: }ccccc | + {37: }ddddd │{37: }ddddd | + {37: }eeeee │{37: }eeee^e | + {1:~ }│{1:~ }| + {40:[No Name] [+] }{41:[No Name] [+] }| + | + ]]} + command('windo set wrap') + screen:expect_unchanged() + end) + it('can have virtual text which combines foreground and background groups', function() screen:set_default_attr_ids { [1] = {bold=true, foreground=Screen.colors.Blue}; @@ -1652,6 +1827,70 @@ describe('extmark decorations', function() {24:-- VISUAL BLOCK --} | ]]) end) + + it('supports multiline highlights', function() + insert(example_text) + feed 'gg' + for _,i in ipairs {1,2,3,5,6,7} do + for _,j in ipairs {2,5,10,15} do + meths.buf_set_extmark(0, ns, i, j, { end_col=j+2, hl_group = 'NonText'}) + end + end + screen:expect{grid=[[ + ^for _,item in ipairs(items) do | + {1: }l{1:oc}al {1:te}xt,{1: h}l_id_cell, count = unpack(item) | + {1: }i{1:f }hl_{1:id}_ce{1:ll} ~= nil then | + {1: } {1: } hl{1:_i}d ={1: h}l_id_cell | + end | + {1: }f{1:or} _ {1:= }1, {1:(c}ount or 1) do | + {1: } {1: } lo{1:ca}l c{1:el}l = line[colpos] | + {1: } {1: } ce{1:ll}.te{1:xt} = text | + cell.hl_id = hl_id | + colpos = colpos+1 | + end | + end | + {1:~ }| + {1:~ }| + | + ]]} + feed'5<c-e>' + screen:expect{grid=[[ + ^ {1: }f{1:or} _ {1:= }1, {1:(c}ount or 1) do | + {1: } {1: } lo{1:ca}l c{1:el}l = line[colpos] | + {1: } {1: } ce{1:ll}.te{1:xt} = text | + cell.hl_id = hl_id | + colpos = colpos+1 | + end | + end | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + meths.buf_set_extmark(0, ns, 1, 0, { end_line=8, end_col=10, hl_group = 'ErrorMsg'}) + screen:expect{grid=[[ + {4:^ }{36: }{4:f}{36:or}{4: _ }{36:= }{4:1, }{36:(c}{4:ount or 1) do} | + {4: }{36: }{4: }{36: }{4: lo}{36:ca}{4:l c}{36:el}{4:l = line[colpos]} | + {4: }{36: }{4: }{36: }{4: ce}{36:ll}{4:.te}{36:xt}{4: = text} | + {4: ce}ll.hl_id = hl_id | + colpos = colpos+1 | + end | + end | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + end) end) describe('decorations: inline virtual text', function() @@ -4136,7 +4375,6 @@ l5 end) it('can add multiple signs (single extmark)', function() - pending('TODO(lewis6991): Support ranged signs') insert(example_test3) feed 'gg' @@ -4158,7 +4396,6 @@ l5 end) it('can add multiple signs (multiple extmarks)', function() - pending('TODO(lewis6991): Support ranged signs') insert(example_test3) feed'gg' @@ -4219,7 +4456,6 @@ l5 end) it('can add multiple signs (multiple extmarks) 3', function() - pending('TODO(lewis6991): Support ranged signs') insert(example_test3) feed 'gg' @@ -4289,7 +4525,6 @@ l5 end) it('works with old signs (with range)', function() - pending('TODO(lewis6991): Support ranged signs') insert(example_test3) feed 'gg' @@ -4304,7 +4539,7 @@ l5 screen:expect{grid=[[ S3S4S1^l1 | - S2S3x l2 | + x S2S3l2 | S5S3{1: }l3 | S3{1: }l4 | S3{1: }l5 | @@ -4317,8 +4552,6 @@ l5 end) it('can add a ranged sign (with start out of view)', function() - pending('TODO(lewis6991): Support ranged signs') - insert(example_test3) command 'set signcolumn=yes:2' feed 'gg' diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 0cf8a124ff..e37b3ccb5f 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -3319,7 +3319,6 @@ describe('float window', function() ]]) end - meths.win_set_config(win, {relative='win', win=oldwin, row=1, col=10, anchor='NW'}) if multigrid then screen:expect{grid=[[ @@ -3765,6 +3764,198 @@ describe('float window', function() end end) + it('anchored to another floating window updated in the same call #14735', function() + feed('i<CR><CR><CR><Esc>') + + exec([[ + let b1 = nvim_create_buf(v:true, v:false) + let b2 = nvim_create_buf(v:true, v:false) + let b3 = nvim_create_buf(v:true, v:false) + let b4 = nvim_create_buf(v:true, v:false) + let b5 = nvim_create_buf(v:true, v:false) + let b6 = nvim_create_buf(v:true, v:false) + let b7 = nvim_create_buf(v:true, v:false) + let b8 = nvim_create_buf(v:true, v:false) + call setbufline(b1, 1, '1') + call setbufline(b2, 1, '2') + call setbufline(b3, 1, '3') + call setbufline(b4, 1, '4') + call setbufline(b5, 1, '5') + call setbufline(b6, 1, '6') + call setbufline(b7, 1, '7') + call setbufline(b8, 1, '8') + let o1 = #{relative: 'editor', row: 1, col: 10, width: 5, height: 1} + let w1 = nvim_open_win(b1, v:false, o1) + let o2 = extendnew(o1, #{col: 30}) + let w2 = nvim_open_win(b2, v:false, o2) + let o3 = extendnew(o1, #{relative: 'win', win: w1, anchor: 'NE', col: 0}) + let w3 = nvim_open_win(b3, v:false, o3) + let o4 = extendnew(o3, #{win: w2}) + let w4 = nvim_open_win(b4, v:false, o4) + let o5 = extendnew(o3, #{win: w3, anchor: 'SE', row: 0}) + let w5 = nvim_open_win(b5, v:false, o5) + let o6 = extendnew(o5, #{win: w4}) + let w6 = nvim_open_win(b6, v:false, o6) + let o7 = extendnew(o5, #{win: w5, anchor: 'SW', col: 5}) + let w7 = nvim_open_win(b7, v:false, o7) + let o8 = extendnew(o7, #{win: w6}) + let w8 = nvim_open_win(b8, v:false, o8) + ]]) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + | + | + | + ^ | + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {1:1 }| + ## grid 6 + {1:2 }| + ## grid 7 + {1:3 }| + ## grid 8 + {1:4 }| + ## grid 9 + {1:5 }| + ## grid 10 + {1:6 }| + ## grid 11 + {1:7 }| + ## grid 12 + {1:8 }| + ]], float_pos={ + [5] = {{id = 1002}, "NW", 1, 1, 10, true, 50}; + [6] = {{id = 1003}, "NW", 1, 1, 30, true, 50}; + [7] = {{id = 1004}, "NE", 5, 1, 0, true, 50}; + [8] = {{id = 1005}, "NE", 6, 1, 0, true, 50}; + [9] = {{id = 1006}, "SE", 7, 0, 0, true, 50}; + [10] = {{id = 1007}, "SE", 8, 0, 0, true, 50}; + [11] = {{id = 1008}, "SW", 9, 0, 5, true, 50}; + [12] = {{id = 1009}, "SW", 10, 0, 5, true, 50}; + }} + else + screen:expect([[ + {1:7 } {1:8 } | + {1:5 } {1:1 } {1:6 } {1:2 } | + {1:3 } {1:4 } | + ^ | + {0:~ }| + {0:~ }| + | + ]]) + end + + -- Reconfigure in different directions + exec([[ + let o1 = extendnew(o1, #{anchor: 'NW'}) + call nvim_win_set_config(w8, o1) + let o2 = extendnew(o2, #{anchor: 'NW'}) + call nvim_win_set_config(w4, o2) + let o3 = extendnew(o3, #{win: w8}) + call nvim_win_set_config(w2, o3) + let o4 = extendnew(o4, #{win: w4}) + call nvim_win_set_config(w1, o4) + let o5 = extendnew(o5, #{win: w2}) + call nvim_win_set_config(w6, o5) + let o6 = extendnew(o6, #{win: w1}) + call nvim_win_set_config(w3, o6) + let o7 = extendnew(o7, #{win: w6}) + call nvim_win_set_config(w5, o7) + let o8 = extendnew(o8, #{win: w3}) + call nvim_win_set_config(w7, o8) + ]]) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + | + | + | + ^ | + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {1:1 }| + ## grid 6 + {1:2 }| + ## grid 7 + {1:3 }| + ## grid 8 + {1:4 }| + ## grid 9 + {1:5 }| + ## grid 10 + {1:6 }| + ## grid 11 + {1:7 }| + ## grid 12 + {1:8 }| + ]], float_pos={ + [5] = {{id = 1002}, "NE", 8, 1, 0, true, 50}; + [6] = {{id = 1003}, "NE", 12, 1, 0, true, 50}; + [7] = {{id = 1004}, "SE", 5, 0, 0, true, 50}; + [8] = {{id = 1005}, "NW", 1, 1, 30, true, 50}; + [9] = {{id = 1006}, "SW", 10, 0, 5, true, 50}; + [10] = {{id = 1007}, "SE", 6, 0, 0, true, 50}; + [11] = {{id = 1008}, "SW", 7, 0, 5, true, 50}; + [12] = {{id = 1009}, "NW", 1, 1, 10, true, 50}; + }} + else + screen:expect([[ + {1:5 } {1:7 } | + {1:6 } {1:8 } {1:3 } {1:4 } | + {1:2 } {1:1 } | + ^ | + {0:~ }| + {0:~ }| + | + ]]) + end + + -- Not clear how cycles should behave, but they should not hang or crash + exec([[ + let o1 = extendnew(o1, #{relative: 'win', win: w7}) + call nvim_win_set_config(w1, o1) + let o2 = extendnew(o2, #{relative: 'win', win: w8}) + call nvim_win_set_config(w2, o2) + let o3 = extendnew(o3, #{win: w1}) + call nvim_win_set_config(w3, o3) + let o4 = extendnew(o4, #{win: w2}) + call nvim_win_set_config(w4, o4) + let o5 = extendnew(o5, #{win: w3}) + call nvim_win_set_config(w5, o5) + let o6 = extendnew(o6, #{win: w4}) + call nvim_win_set_config(w6, o6) + let o7 = extendnew(o7, #{win: w5}) + call nvim_win_set_config(w7, o7) + let o8 = extendnew(o8, #{win: w6}) + call nvim_win_set_config(w8, o8) + redraw + ]]) + end) + it('can be placed relative text in a window', function() screen:try_resize(30,5) local firstwin = meths.get_current_win().id diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 2307fd106b..be7c2f291c 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -1270,7 +1270,7 @@ end function Screen:render(headers, attr_state, preview) headers = headers and (self._options.ext_multigrid or self._options._debug_float) local rv = {} - for igrid,grid in pairs(self._grids) do + for igrid,grid in vim.spairs(self._grids) do if headers then local suffix = "" if igrid > 1 and self.win_position[igrid] == nil diff --git a/test/functional/ui/statuscolumn_spec.lua b/test/functional/ui/statuscolumn_spec.lua index ee235cd6b5..742976cbe2 100644 --- a/test/functional/ui/statuscolumn_spec.lua +++ b/test/functional/ui/statuscolumn_spec.lua @@ -532,6 +532,24 @@ describe('statuscolumn', function() eq('0 3 r 7', eval("g:testvar")) meths.input_mouse('right', 'press', '', 0, 3, 0) eq('0 4 r 7', eval("g:testvar")) + + command('rightbelow vsplit') + meths.input_mouse('left', 'press', '', 0, 0, 27) + eq('0 1 l 4', eval("g:testvar")) + meths.input_mouse('right', 'press', '', 0, 3, 27) + eq('0 1 r 7', eval("g:testvar")) + command('setlocal rightleft') + meths.input_mouse('left', 'press', '', 0, 0, 52) + eq('0 1 l 4', eval("g:testvar")) + meths.input_mouse('right', 'press', '', 0, 3, 52) + eq('0 1 r 7', eval("g:testvar")) + command('wincmd H') + meths.input_mouse('left', 'press', '', 0, 0, 25) + eq('0 1 l 4', eval("g:testvar")) + meths.input_mouse('right', 'press', '', 0, 3, 25) + eq('0 1 r 7', eval("g:testvar")) + command('close') + command('set laststatus=2 winbar=%f') command('let g:testvar = ""') -- Check that winbar click doesn't register as statuscolumn click diff --git a/test/functional/vimscript/system_spec.lua b/test/functional/vimscript/system_spec.lua index 762e8877ce..90aab48d61 100644 --- a/test/functional/vimscript/system_spec.lua +++ b/test/functional/vimscript/system_spec.lua @@ -335,12 +335,12 @@ describe('system()', function() if is_os('win') then eq("echoed\n", eval('system("echo echoed")')) else - eq("echoed", eval('system("echo -n echoed")')) + eq("echoed", eval('system("printf echoed")')) end end) it('to backgrounded command does not crash', function() -- This is indeterminate, just exercise the codepath. May get E5677. - feed_command('call system(has("win32") ? "start /b /wait cmd /c echo echoed" : "echo -n echoed &")') + feed_command('call system(has("win32") ? "start /b /wait cmd /c echo echoed" : "printf echoed &")') local v_errnum = string.match(eval("v:errmsg"), "^E%d*:") if v_errnum then eq("E5677:", v_errnum) diff --git a/test/old/testdir/check.vim b/test/old/testdir/check.vim index 281514db17..af1a80250c 100644 --- a/test/old/testdir/check.vim +++ b/test/old/testdir/check.vim @@ -100,6 +100,14 @@ func CheckLinux() endif endfunc +" Command to check for not running on a BSD system. +command CheckNotBSD call CheckNotBSD() +func CheckNotBSD() + if has('bsd') + throw 'Skipped: does not work on BSD' + endif +endfunc + " Command to check that making screendumps is supported. " Caller must source screendump.vim command CheckScreendump call CheckScreendump() diff --git a/test/old/testdir/test_autocmd.vim b/test/old/testdir/test_autocmd.vim index 79f8ee43c1..0afa3417ec 100644 --- a/test/old/testdir/test_autocmd.vim +++ b/test/old/testdir/test_autocmd.vim @@ -2002,8 +2002,8 @@ endfunc " Test for BufUnload autocommand that unloads all the other buffers func Test_bufunload_all() let g:test_is_flaky = 1 - call writefile(['Test file Xxx1'], 'Xxx1', 'D')" - call writefile(['Test file Xxx2'], 'Xxx2', 'D')" + call writefile(['Test file Xxx1'], 'Xxx1', 'D') + call writefile(['Test file Xxx2'], 'Xxx2', 'D') let content =<< trim [CODE] func UnloadAllBufs() diff --git a/test/old/testdir/test_edit.vim b/test/old/testdir/test_edit.vim index 0f3c62a092..03796ad816 100644 --- a/test/old/testdir/test_edit.vim +++ b/test/old/testdir/test_edit.vim @@ -1235,7 +1235,7 @@ func Test_edit_LEFT_RIGHT() endfunc func Test_edit_MOUSE() - " This is a simple test, since we not really using the mouse here + " This is a simple test, since we're not really using the mouse here CheckFeature mouse 10new call setline(1, range(1, 100)) @@ -1817,7 +1817,7 @@ func Test_edit_charconvert() close! set charconvert& - " 'charconvert' function doesn't create a output file + " 'charconvert' function doesn't create an output file func Cconv1() endfunc set charconvert=Cconv1() diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim index 1dd255ccf9..b2b6ad80bb 100644 --- a/test/old/testdir/test_filetype.vim +++ b/test/old/testdir/test_filetype.vim @@ -427,6 +427,7 @@ func s:GetFilenameChecks() abort \ 'mrxvtrc': ['mrxvtrc', '.mrxvtrc'], \ 'msidl': ['file.odl', 'file.mof'], \ 'msql': ['file.msql'], + \ 'mojo': ['file.mojo', 'file.🔥'], \ 'mupad': ['file.mu'], \ 'mush': ['file.mush'], \ 'muttrc': ['Muttngrc', 'Muttrc', '.muttngrc', '.muttngrc-file', '.muttrc', '.muttrc-file', '/.mutt/muttngrc', '/.mutt/muttngrc-file', '/.mutt/muttrc', '/.mutt/muttrc-file', '/.muttng/muttngrc', '/.muttng/muttngrc-file', '/.muttng/muttrc', '/.muttng/muttrc-file', '/etc/Muttrc.d/file', '/etc/Muttrc.d/file.rc', 'Muttngrc-file', 'Muttrc-file', 'any/.mutt/muttngrc', 'any/.mutt/muttngrc-file', 'any/.mutt/muttrc', 'any/.mutt/muttrc-file', 'any/.muttng/muttngrc', 'any/.muttng/muttngrc-file', 'any/.muttng/muttrc', 'any/.muttng/muttrc-file', 'any/etc/Muttrc.d/file', 'muttngrc', 'muttngrc-file', 'muttrc', 'muttrc-file'], diff --git a/test/old/testdir/test_functions.vim b/test/old/testdir/test_functions.vim index eff4e36f34..3e1e5a4816 100644 --- a/test/old/testdir/test_functions.vim +++ b/test/old/testdir/test_functions.vim @@ -290,6 +290,7 @@ endfunc func Test_strptime() CheckFunction strptime + CheckNotBSD CheckNotMSWindows if exists('$TZ') @@ -305,6 +306,8 @@ func Test_strptime() call assert_fails('call strptime()', 'E119:') call assert_fails('call strptime("xxx")', 'E119:') + " This fails on BSD 14 and returns + " -2209078800 instead of 0 call assert_equal(0, strptime("%Y", '')) call assert_equal(0, strptime("%Y", "xxx")) @@ -2630,7 +2633,7 @@ func Test_state() call term_sendkeys(buf, getstate) call WaitForAssert({-> assert_match('state: mSc; mode: n', term_getline(buf, 6))}, 1000) - " A operator is pending + " An operator is pending call term_sendkeys(buf, ":call RunTimer()\<CR>y") call TermWait(buf, 25) call term_sendkeys(buf, "y") diff --git a/test/old/testdir/test_listdict.vim b/test/old/testdir/test_listdict.vim index 0918e2cd98..fef7c6a9bc 100644 --- a/test/old/testdir/test_listdict.vim +++ b/test/old/testdir/test_listdict.vim @@ -346,7 +346,7 @@ func Test_dict_big() endtry call assert_equal('Vim(let):E716: "1500"', str) - " lookup each items + " lookup each item for i in range(1500) call assert_equal(3000 - i, d[i]) endfor diff --git a/test/old/testdir/test_normal.vim b/test/old/testdir/test_normal.vim index 08a3c57a94..3c21041899 100644 --- a/test/old/testdir/test_normal.vim +++ b/test/old/testdir/test_normal.vim @@ -347,7 +347,7 @@ func Test_normal06_formatprg() CheckNotMSWindows " uses sed to number non-empty lines - call writefile(['#!/bin/sh', 'sed ''/./=''|sed ''/./{', 'N', 's/\n/ /', '}'''], 'Xsed_format.sh') + call writefile(['#!/bin/sh', 'sed ''/./=''|sed ''/./{', 'N', 's/\n/ /', '}'''], 'Xsed_format.sh', 'D') call system('chmod +x ./Xsed_format.sh') let text = ['a', '', 'c', '', ' ', 'd', 'e'] let expected = ['1 a', '', '3 c', '', '5 ', '6 d', '7 e'] @@ -378,11 +378,10 @@ func Test_normal06_formatprg() " clean up set formatprg= setlocal formatprg= - call delete('Xsed_format.sh') endfunc func Test_normal07_internalfmt() - " basic test for internal formmatter to textwidth of 12 + " basic test for internal formatter to textwidth of 12 let list=range(1,11) call map(list, 'v:val." "') 10new @@ -2588,7 +2587,7 @@ func Test_normal33_g_cmd2() exe "norm! G0\<c-v>4k4ly" exe "norm! gvood" call assert_equal(['', 'abfgh', 'abfgh', 'abfgh', 'fgh', 'fgh', 'fgh', 'fgh', 'fgh'], getline(1,'$')) - " gv cannot be used in operator pending mode + " gv cannot be used in operator pending mode call assert_beeps('normal! cgv') " gv should beep without a previously selected visual area new diff --git a/test/old/testdir/test_substitute.vim b/test/old/testdir/test_substitute.vim index a6640aac30..8dff0cda52 100644 --- a/test/old/testdir/test_substitute.vim +++ b/test/old/testdir/test_substitute.vim @@ -1425,4 +1425,18 @@ func Test_z_substitute_expr_leak() delfunc SubExpr endfunc +func Test_substitute_expr_switch_win() + func R() + wincmd x + return 'XXXX' + endfunc + new Xfoobar + let bufnr = bufnr('%') + put ="abcdef" + silent! s/\%')/\=R() + call assert_fails(':%s/./\=R()/g', 'E565:') + delfunc R + exe bufnr .. "bw!" +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_vimscript.vim b/test/old/testdir/test_vimscript.vim index 9b1b7f7b23..6ce59e1a2e 100644 --- a/test/old/testdir/test_vimscript.vim +++ b/test/old/testdir/test_vimscript.vim @@ -3109,7 +3109,7 @@ endfunc " should be given. " " This test reuses the function MESSAGES() from the previous test. -" This functions checks the messages in g:msgfile. +" This function checks the messages in g:msgfile. "------------------------------------------------------------------------------- func Test_nested_while_error() @@ -3236,7 +3236,7 @@ endfunc " error messages should be given. " " This test reuses the function MESSAGES() from the previous test. -" This functions checks the messages in g:msgfile. +" This function checks the messages in g:msgfile. "------------------------------------------------------------------------------- func Test_nested_cont_break_error() @@ -3344,7 +3344,7 @@ endfunc " should be given. " " This test reuses the function MESSAGES() from the previous test. -" This functions checks the messages in g:msgfile. +" This function check the messages in g:msgfile. "------------------------------------------------------------------------------- func Test_nested_endtry_error() diff --git a/test/old/testdir/test_virtualedit.vim b/test/old/testdir/test_virtualedit.vim index f97b3f987d..6ff51e36fb 100644 --- a/test/old/testdir/test_virtualedit.vim +++ b/test/old/testdir/test_virtualedit.vim @@ -236,7 +236,7 @@ func Test_ve_completion() set virtualedit= endfunc -" Using "C" then then <CR> moves the last remaining character to the next +" Using "C" then <CR> moves the last remaining character to the next " line. (Mary Ellen Foster) func Test_ve_del_to_eol() new diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index e9b97266d0..43b6980702 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -849,6 +849,16 @@ local function ptr2key(ptr) return ffi.string(s) end +local function is_asan() + cimport('./src/nvim/version.h') + local status, res = pcall(function() return lib.version_cflags end) + if status then + return ffi.string(res):match('-fsanitize=[a-z,]*address') + else + return false + end +end + local module = { cimport = cimport, cppimport = cppimport, @@ -876,6 +886,7 @@ local module = { ptr2addr = ptr2addr, ptr2key = ptr2key, debug_log = debug_log, + is_asan = is_asan, } module = global_helpers.tbl_extend('error', module, global_helpers) return function() diff --git a/test/unit/marktree_spec.lua b/test/unit/marktree_spec.lua index 3c96bc5f58..32300c167c 100644 --- a/test/unit/marktree_spec.lua +++ b/test/unit/marktree_spec.lua @@ -87,13 +87,18 @@ local function dosplice(tree, shadow, start, old_extent, new_extent) shadowsplice(shadow, start, old_extent, new_extent) end +local ns = 10 local last_id = nil -local function put(tree, row, col, gravitate) +local function put(tree, row, col, gravitate, end_row, end_col, end_gravitate) last_id = last_id + 1 local my_id = last_id - lib.marktree_put_test(tree, my_id, row, col, gravitate); + end_row = end_row or -1 + end_col = end_col or -1 + end_gravitate = end_gravitate or false + + lib.marktree_put_test(tree, ns, my_id, row, col, gravitate, end_row, end_col, end_gravitate); return my_id end @@ -102,7 +107,7 @@ describe('marktree', function() last_id = 0 end) - itp('works', function() + itp('works', function() local tree = ffi.new("MarkTree[1]") -- zero initialized by luajit local shadow = {} local iter = ffi.new("MarkTreeIter[1]") @@ -129,7 +134,7 @@ describe('marktree', function() eq({}, id2pos) for i,ipos in pairs(shadow) do - local p = lib.marktree_lookup_ns(tree, -1, i, false, iter) + local p = lib.marktree_lookup_ns(tree, ns, i, false, iter) eq(ipos[1], p.pos.row) eq(ipos[2], p.pos.col) local k = lib.marktree_itr_current(iter) @@ -210,10 +215,224 @@ describe('marktree', function() lib.marktree_itr_get(tree, 10, 10, iter) lib.marktree_del_itr(tree, iter, false) - eq(11, iter[0].node.key[iter[0].i].pos.col) + eq(11, iter[0].x.key[iter[0].i].pos.col) lib.marktree_itr_get(tree, 11, 11, iter) lib.marktree_del_itr(tree, iter, false) - eq(12, iter[0].node.key[iter[0].i].pos.col) - end) + eq(12, iter[0].x.key[iter[0].i].pos.col) + end) + + itp("'intersect_mov' function works correctly", function() + local function mov(x, y, w) + local xa = ffi.new("uint64_t[?]", #x) + for i, xi in ipairs(x) do xa[i-1] = xi end + local ya = ffi.new("uint64_t[?]", #y) + for i, yi in ipairs(y) do ya[i-1] = yi end + local wa = ffi.new("uint64_t[?]", #w) + for i, wi in ipairs(w) do wa[i-1] = wi end + + local dummy_size = #x + #y + #w + local wouta = ffi.new("uint64_t[?]", dummy_size) + local douta = ffi.new("uint64_t[?]", dummy_size) + local wsize = ffi.new("size_t[1]") + wsize[0] = dummy_size + local dsize = ffi.new("size_t[1]") + dsize[0] = dummy_size + + local status = lib.intersect_mov_test(xa, #x, ya, #y, wa, #w, wouta, wsize, douta, dsize) + if status == 0 then error'wowza' end + + local wout, dout = {}, {} + for i = 0,tonumber(wsize[0])-1 do table.insert(wout, tonumber(wouta[i])) end + for i = 0,tonumber(dsize[0])-1 do table.insert(dout, tonumber(douta[i])) end + return {wout, dout} + end + + eq({{}, {}}, mov({}, {2, 3}, {2, 3})) + eq({{2, 3}, {}}, mov({}, {}, {2, 3})) + eq({{2, 3}, {}}, mov({2, 3}, {}, {})) + eq({{}, {2,3}}, mov({}, {2,3}, {})) + + eq({{1, 5}, {}}, mov({1,2,5}, {2, 3}, {3})) + eq({{1, 2}, {}}, mov({1,2,5}, {5, 10}, {10})) + eq({{1, 2}, {5}}, mov({1,2}, {5, 10}, {10})) + eq({{1,3,5,7,9}, {2,4,6,8,10}}, mov({1,3,5,7,9}, {2,4,6,8,10}, {})) + eq({{1,3,5,7,9}, {2,6,10}}, mov({1,3,5,7,9}, {2,4,6,8,10}, {4, 8})) + eq({{1,4,7}, {2,5,8}}, mov({1,3,4,6,7,9}, {2,3,5,6,8,9}, {})) + eq({{1,4,7}, {}}, mov({1,3,4,6,7,9}, {2,3,5,6,8,9}, {2,5,8})) + eq({{0,1,4,7,10}, {}}, mov({1,3,4,6,7,9}, {2,3,5,6,8,9}, {0,2,5,8,10})) + end) + + + local function check_intersections(tree) + lib.marktree_check(tree) + -- to debug stuff disable this branch + if true == true then + ok(lib.marktree_check_intersections(tree)) + return + end + + local str1 = lib.mt_inspect(tree, true, true) + local dot1 = ffi.string(str1.data, str1.size) + + local val = lib.marktree_check_intersections(tree) + if not val then + local str2 = lib.mt_inspect(tree, true, true) + local dot2 = ffi.string(str2.data, str2.size) + print("actual:\n\n".."Xafile.dot".."\n\nexpected:\n\n".."Xefile.dot".."\n") + print("nivå", tree[0].root.level); + io.stdout:flush() + local afil = io.open("Xafile.dot", "wb") + afil:write(dot1) + afil:close() + local efil = io.open("Xefile.dot", "wb") + efil:write(dot2) + efil:close() + ok(false) + else + ffi.C.xfree(str1.data) + end + end + + itp('works with intersections', function() + local tree = ffi.new("MarkTree[1]") -- zero initialized by luajit + + local ids = {} + + for i = 1,80 do + table.insert(ids, put(tree, 1, i, false, 2, 100-i, false)) + check_intersections(tree) + end + for i = 1,80 do + lib.marktree_del_pair_test(tree, ns, ids[i]) + check_intersections(tree) + end + ids = {} + + for i = 1,80 do + table.insert(ids, put(tree, 1, i, false, 2, 100-i, false)) + check_intersections(tree) + end + + for i = 1,10 do + for j = 1,8 do + local ival = (j-1)*10+i + lib.marktree_del_pair_test(tree, ns, ids[ival]) + check_intersections(tree) + end + end + end) + + itp('works with intersections with a big tree', function() + local tree = ffi.new("MarkTree[1]") -- zero initialized by luajit + + local ids = {} + + for i = 1,1000 do + table.insert(ids, put(tree, 1, i, false, 2, 1000-i, false)) + if i % 10 == 1 then + check_intersections(tree) + end + end + + check_intersections(tree) + eq(2000, tree[0].n_keys) + ok(tree[0].root.level >= 2) + + local iter = ffi.new("MarkTreeIter[1]") + + local k = 0 + for i = 1,20 do + for j = 1,50 do + k = k + 1 + local ival = (j-1)*20+i + if false == true then -- if there actually is a failure, this branch will fail out at the actual spot of the error + lib.marktree_lookup_ns(tree, ns, ids[ival], false, iter) + lib.marktree_del_itr(tree, iter, false) + check_intersections(tree) + + lib.marktree_lookup_ns(tree, ns, ids[ival], true, iter) + lib.marktree_del_itr(tree, iter, false) + check_intersections(tree) + else + lib.marktree_del_pair_test(tree, ns, ids[ival]) + if k % 5 == 1 then + check_intersections(tree) + end + end + end + end + + eq(0, tree[0].n_keys) + end) + + itp('works with intersections with a even bigger tree', function() + local tree = ffi.new("MarkTree[1]") -- zero initialized by luajit + + local ids = {} + + -- too much overhead on ASAN + local size_factor = helpers.is_asan() and 3 or 10 + + local at_row = {} + for i = 1, 10 do + at_row[i] = {} + end + + local size = 1000*size_factor + local k = 1 + while k <= size do + for row1 = 1,9 do + for row2 = row1,10 do -- note row2 can be == row1, leads to empty ranges being tested when k > size/2 + if k > size then + break + end + local id = put(tree, row1, k, false, row2, size-k, false) + table.insert(ids, id) + for i = row1+1, row2 do + table.insert(at_row[i], id) + end + --if tree[0].root.level == 4 then error("kk"..k) end + if k % 100*size_factor == 1 or (k < 2000 and k%100 == 1) then + check_intersections(tree) + end + k = k + 1 + end + end + end + + eq(2*size, tree[0].n_keys) + ok(tree[0].root.level >= 3) + check_intersections(tree) + + local iter = ffi.new("MarkTreeIter[1]") + local pair = ffi.new("MTPair[1]") + for i = 1,10 do + -- use array as set and not {[id]=true} map, to detect duplicates + local set = {} + eq(true, ffi.C.marktree_itr_get_overlap(tree, i, 0, iter)) + while ffi.C.marktree_itr_step_overlap(tree, iter, pair) do + local id = tonumber(pair[0].start.id) + table.insert(set, id) + end + table.sort(set) + eq(at_row[i], set) + end + + k = 0 + for i = 1,100 do + for j = 1,(10*size_factor) do + k = k + 1 + local ival = (j-1)*100+i + lib.marktree_del_pair_test(tree, ns, ids[ival]) + -- just a few stickprov, if there is trouble we need to check + -- everyone using the code in the "big tree" case above + if k % 100*size_factor == 0 or (k > 3000 and k % 200 == 0) then + check_intersections(tree) + end + end + end + + eq(0, tree[0].n_keys) + end) end) |