aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore21
-rw-r--r--.travis.yml19
-rwxr-xr-xci/before_install.sh2
-rw-r--r--ci/common/test.sh2
-rw-r--r--ci/snap/.snapcraft_payload194
-rwxr-xr-xci/snap/deploy.sh42
-rw-r--r--cmake/FindLua.cmake2
-rw-r--r--runtime/autoload/health/provider.vim44
-rw-r--r--runtime/autoload/provider/clipboard.vim72
-rw-r--r--runtime/autoload/provider/node.vim19
-rw-r--r--runtime/autoload/provider/perl.vim28
-rw-r--r--runtime/autoload/provider/ruby.vim3
-rw-r--r--runtime/doc/api.txt96
-rw-r--r--runtime/doc/autocmd.txt174
-rw-r--r--runtime/doc/cmdline.txt1
-rw-r--r--runtime/doc/develop.txt6
-rw-r--r--runtime/doc/digraph.txt1
-rw-r--r--runtime/doc/eval.txt65
-rw-r--r--runtime/doc/help.txt4
-rw-r--r--runtime/doc/if_perl.txt268
-rw-r--r--runtime/doc/index.txt3
-rw-r--r--runtime/doc/intro.txt93
-rw-r--r--runtime/doc/lsp.txt73
-rw-r--r--runtime/doc/lua.txt193
-rw-r--r--runtime/doc/mbyte.txt208
-rw-r--r--runtime/doc/message.txt2
-rw-r--r--runtime/doc/mlang.txt3
-rw-r--r--runtime/doc/nvim_terminal_emulator.txt2
-rw-r--r--runtime/doc/options.txt44
-rw-r--r--runtime/doc/provider.txt14
-rw-r--r--runtime/doc/quickref.txt2
-rw-r--r--runtime/doc/starting.txt175
-rw-r--r--runtime/doc/ui.txt4
-rw-r--r--runtime/doc/usr_02.txt2
-rw-r--r--runtime/doc/vim_diff.txt19
-rw-r--r--runtime/filetype.vim6
-rw-r--r--runtime/lua/vim/highlight.lua2
-rw-r--r--runtime/lua/vim/lsp.lua10
-rw-r--r--runtime/lua/vim/lsp/rpc.lua1
-rw-r--r--runtime/lua/vim/lsp/util.lua10
-rw-r--r--runtime/lua/vim/shared.lua18
-rw-r--r--runtime/lua/vim/treesitter.lua39
-rw-r--r--runtime/lua/vim/treesitter/highlighter.lua28
-rw-r--r--runtime/lua/vim/treesitter/query.lua51
-rwxr-xr-xscripts/gen_vimdoc.py40
-rwxr-xr-xscripts/vim-patch.sh3
-rw-r--r--snap/snapcraft.yaml31
-rw-r--r--src/nvim/CMakeLists.txt14
-rw-r--r--src/nvim/api/buffer.c401
-rw-r--r--src/nvim/api/private/helpers.c2
-rw-r--r--src/nvim/api/ui_events.in.h2
-rw-r--r--src/nvim/api/vim.c20
-rw-r--r--src/nvim/buffer_defs.h1
-rw-r--r--src/nvim/buffer_updates.c42
-rw-r--r--src/nvim/buffer_updates.h1
-rw-r--r--src/nvim/change.c19
-rw-r--r--src/nvim/digraph.c1
-rw-r--r--src/nvim/edit.c8
-rw-r--r--src/nvim/eval.c13
-rw-r--r--src/nvim/eval.lua1
-rw-r--r--src/nvim/eval/decode.c2
-rw-r--r--src/nvim/eval/funcs.c10
-rw-r--r--src/nvim/eval/userfunc.c3
-rw-r--r--src/nvim/ex_cmds.c54
-rw-r--r--src/nvim/ex_cmds.lua10
-rw-r--r--src/nvim/ex_cmds2.c21
-rw-r--r--src/nvim/extmark.c481
-rw-r--r--src/nvim/extmark.h22
-rw-r--r--src/nvim/extmark_defs.h16
-rw-r--r--src/nvim/fileio.c1
-rw-r--r--src/nvim/fold.c139
-rw-r--r--src/nvim/indent.c11
-rw-r--r--src/nvim/lua/converter.c7
-rw-r--r--src/nvim/lua/executor.c35
-rw-r--r--src/nvim/lua/treesitter.c133
-rw-r--r--src/nvim/main.c2
-rw-r--r--src/nvim/map.c2
-rw-r--r--src/nvim/map.h1
-rw-r--r--src/nvim/memline.c64
-rw-r--r--src/nvim/memline_defs.h2
-rw-r--r--src/nvim/ops.c74
-rw-r--r--src/nvim/option.c56
-rw-r--r--src/nvim/option_defs.h2
-rw-r--r--src/nvim/options.lua12
-rw-r--r--src/nvim/os/env.c4
-rw-r--r--src/nvim/os/time.c9
-rw-r--r--src/nvim/po/check.vim1
-rw-r--r--src/nvim/regexp.c17
-rw-r--r--src/nvim/regexp_nfa.c2
-rw-r--r--src/nvim/screen.c10
-rw-r--r--src/nvim/spell.c9
-rw-r--r--src/nvim/spellfile.c18
-rw-r--r--src/nvim/testdir/check.vim56
-rw-r--r--src/nvim/testdir/runtest.vim14
-rw-r--r--src/nvim/testdir/shared.vim6
-rw-r--r--src/nvim/testdir/test_diffmode.vim180
-rw-r--r--src/nvim/testdir/test_display.vim77
-rw-r--r--src/nvim/testdir/test_filetype.vim2
-rw-r--r--src/nvim/testdir/test_perl.vim225
-rw-r--r--src/nvim/testdir/test_textformat.vim20
-rw-r--r--src/nvim/testdir/test_window_cmd.vim42
-rw-r--r--src/nvim/tui/tui.c40
-rw-r--r--src/nvim/types.h2
-rw-r--r--src/nvim/ui_bridge.c1
-rw-r--r--src/nvim/version.c16
-rw-r--r--src/nvim/viml/parser/expressions.c2
-rw-r--r--src/nvim/window.c2
-rw-r--r--src/tree_sitter/alloc.h6
-rw-r--r--src/tree_sitter/lexer.c2
-rw-r--r--src/tree_sitter/parser.c81
-rw-r--r--src/tree_sitter/query.c416
-rw-r--r--src/tree_sitter/stack.c11
-rw-r--r--src/tree_sitter/treesitter_commit_hash.txt2
-rw-r--r--test/functional/api/extmark_spec.lua68
-rw-r--r--test/functional/helpers.lua10
-rw-r--r--test/functional/lua/buffer_updates_spec.lua195
-rw-r--r--test/functional/lua/treesitter_spec.lua123
-rw-r--r--test/functional/options/defaults_spec.lua220
-rw-r--r--test/functional/plugin/lsp_spec.lua40
-rw-r--r--test/functional/provider/clipboard_spec.lua10
-rw-r--r--test/functional/provider/perl_spec.lua50
-rw-r--r--test/functional/ui/bufhl_spec.lua12
-rw-r--r--test/functional/ui/options_spec.lua8
-rw-r--r--test/helpers.lua26
-rw-r--r--test/unit/os/env_spec.lua13
125 files changed, 3847 insertions, 2025 deletions
diff --git a/.gitignore b/.gitignore
index 0888a4e30f..ab301bd336 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,13 +1,14 @@
# Tools
-.ropeproject/
+/venv/
compile_commands.json
-# Visual Studio
+
+# IDEs
/.vs/
+/.vscode/
+/.idea/
# Build/deps dir
/build/
-/cmake-build-debug/
-/dist/
/.deps/
/tmp/
/.clangd/
@@ -20,8 +21,6 @@ compile_commands.json
*.o
*.so
-tags
-
/src/nvim/po/vim.pot
/src/nvim/po/*.ck
@@ -57,14 +56,12 @@ tags
# local make targets
local.mk
-# runtime/doc
+# Generated from :help docs
+tags
/runtime/doc/*.html
/runtime/doc/tags.ref
/runtime/doc/errors.log
-# Don't include the mpack files.
+
+# Generated by gen_vimdoc.py:
/runtime/doc/*.mpack
-# Also don't include intermediary doc output
/tmp-*-doc
-
-# CLion
-/.idea/
diff --git a/.travis.yml b/.travis.yml
index 34ff492bb6..4aebae3986 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,4 +1,4 @@
-dist: xenial
+dist: bionic
language: c
@@ -99,12 +99,21 @@ jobs:
- stage: baseline
name: clang-asan
os: linux
- compiler: clang
+ compiler: clang-11
# Use Lua so that ASAN can test our embedded Lua support. 8fec4d53d0f6
env:
- CLANG_SANITIZER=ASAN_UBSAN
- CMAKE_FLAGS="$CMAKE_FLAGS -DPREFER_LUA=ON"
+ - SYMBOLIZER=asan_symbolize-11
- *common-job-env
+ addons:
+ apt:
+ sources:
+ - sourceline: 'deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-11 main'
+ key_url: 'https://apt.llvm.org/llvm-snapshot.gpg.key'
+ packages:
+ - *common-apt-packages
+ - clang-11
- name: gcc-coverage (gcc 9)
os: linux
compiler: gcc-9
@@ -116,15 +125,15 @@ jobs:
- BUSTED_ARGS="--coverage"
- *common-job-env
addons:
+ snaps:
+ - name: powershell
+ confinement: classic
apt:
sources:
- sourceline: 'ppa:ubuntu-toolchain-r/test'
- - sourceline: 'deb [arch=amd64] https://packages.microsoft.com/ubuntu/16.04/prod xenial main'
- key_url: 'https://packages.microsoft.com/keys/microsoft.asc'
packages:
- *common-apt-packages
- gcc-9
- - powershell
- if: branch = master AND commit_message !~ /\[skip.lint\]/
name: lint
os: linux
diff --git a/ci/before_install.sh b/ci/before_install.sh
index 1cf60edf73..c3fd8bdbde 100755
--- a/ci/before_install.sh
+++ b/ci/before_install.sh
@@ -22,7 +22,7 @@ if [[ "${TRAVIS_OS_NAME}" != osx ]] && command -v pyenv; then
echo 'Setting Python versions via pyenv'
# Prefer Python 2 over 3 (more conservative).
- pyenv global 2.7.15:3.7.1
+ pyenv global 2.7:3.8
echo 'Updated Python info:'
(
diff --git a/ci/common/test.sh b/ci/common/test.sh
index b2fbeaf2da..4ef6260339 100644
--- a/ci/common/test.sh
+++ b/ci/common/test.sh
@@ -82,7 +82,7 @@ valgrind_check() {
check_sanitizer() {
if test -n "${CLANG_SANITIZER}"; then
- check_logs "${1}" "*san.*"
+ check_logs "${1}" "*san.*" | ${SYMBOLIZER:-cat}
fi
}
diff --git a/ci/snap/.snapcraft_payload b/ci/snap/.snapcraft_payload
new file mode 100644
index 0000000000..29f895fad6
--- /dev/null
+++ b/ci/snap/.snapcraft_payload
@@ -0,0 +1,194 @@
+{
+ "ref": "refs/heads/master",
+ "before": "66b136c43c12df3dcf8f19ff48f206ad2e4f43fc",
+ "after": "1bf69c32217cc455603ce8aa2b5415d9717f0fa2",
+ "repository": {
+ "id": 292861950,
+ "node_id": "MDEwOlJlcG9zaXRvcnkyOTI4NjE5NTA=",
+ "name": "neovim-snap",
+ "full_name": "hurricanehrndz/neovim-snap",
+ "private": false,
+ "owner": {
+ "name": "hurricanehrndz",
+ "email": "hurricanehrndz@users.noreply.github.com",
+ "login": "hurricanehrndz",
+ "id": 5804237,
+ "node_id": "MDQ6VXNlcjU4MDQyMzc=",
+ "avatar_url": "https://avatars0.githubusercontent.com/u/5804237?v=4",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/hurricanehrndz",
+ "html_url": "https://github.com/hurricanehrndz",
+ "followers_url": "https://api.github.com/users/hurricanehrndz/followers",
+ "following_url": "https://api.github.com/users/hurricanehrndz/following{/other_user}",
+ "gists_url": "https://api.github.com/users/hurricanehrndz/gists{/gist_id}",
+ "starred_url": "https://api.github.com/users/hurricanehrndz/starred{/owner}{/repo}",
+ "subscriptions_url": "https://api.github.com/users/hurricanehrndz/subscriptions",
+ "organizations_url": "https://api.github.com/users/hurricanehrndz/orgs",
+ "repos_url": "https://api.github.com/users/hurricanehrndz/repos",
+ "events_url": "https://api.github.com/users/hurricanehrndz/events{/privacy}",
+ "received_events_url": "https://api.github.com/users/hurricanehrndz/received_events",
+ "type": "User",
+ "site_admin": false
+ },
+ "html_url": "https://github.com/hurricanehrndz/neovim-snap",
+ "description": "snap build for neovim",
+ "fork": false,
+ "url": "https://github.com/hurricanehrndz/neovim-snap",
+ "forks_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/forks",
+ "keys_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/keys{/key_id}",
+ "collaborators_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/collaborators{/collaborator}",
+ "teams_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/teams",
+ "hooks_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/hooks",
+ "issue_events_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/issues/events{/number}",
+ "events_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/events",
+ "assignees_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/assignees{/user}",
+ "branches_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/branches{/branch}",
+ "tags_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/tags",
+ "blobs_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/git/blobs{/sha}",
+ "git_tags_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/git/tags{/sha}",
+ "git_refs_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/git/refs{/sha}",
+ "trees_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/git/trees{/sha}",
+ "statuses_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/statuses/{sha}",
+ "languages_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/languages",
+ "stargazers_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/stargazers",
+ "contributors_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/contributors",
+ "subscribers_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/subscribers",
+ "subscription_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/subscription",
+ "commits_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/commits{/sha}",
+ "git_commits_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/git/commits{/sha}",
+ "comments_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/comments{/number}",
+ "issue_comment_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/issues/comments{/number}",
+ "contents_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/contents/{+path}",
+ "compare_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/compare/{base}...{head}",
+ "merges_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/merges",
+ "archive_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/{archive_format}{/ref}",
+ "downloads_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/downloads",
+ "issues_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/issues{/number}",
+ "pulls_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/pulls{/number}",
+ "milestones_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/milestones{/number}",
+ "notifications_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/notifications{?since,all,participating}",
+ "labels_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/labels{/name}",
+ "releases_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/releases{/id}",
+ "deployments_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/deployments",
+ "created_at": 1599227980,
+ "updated_at": "2020-09-04T14:02:38Z",
+ "pushed_at": 1599228352,
+ "git_url": "git://github.com/hurricanehrndz/neovim-snap.git",
+ "ssh_url": "git@github.com:hurricanehrndz/neovim-snap.git",
+ "clone_url": "https://github.com/hurricanehrndz/neovim-snap.git",
+ "svn_url": "https://github.com/hurricanehrndz/neovim-snap",
+ "homepage": null,
+ "size": 0,
+ "stargazers_count": 0,
+ "watchers_count": 0,
+ "language": null,
+ "has_issues": true,
+ "has_projects": true,
+ "has_downloads": true,
+ "has_wiki": true,
+ "has_pages": false,
+ "forks_count": 0,
+ "mirror_url": null,
+ "archived": false,
+ "disabled": false,
+ "open_issues_count": 0,
+ "license": {
+ "key": "mit",
+ "name": "MIT License",
+ "spdx_id": "MIT",
+ "url": "https://api.github.com/licenses/mit",
+ "node_id": "MDc6TGljZW5zZTEz"
+ },
+ "forks": 0,
+ "open_issues": 0,
+ "watchers": 0,
+ "default_branch": "master",
+ "stargazers": 0,
+ "master_branch": "master"
+ },
+ "pusher": {
+ "name": "hurricanehrndz",
+ "email": "hurricanehrndz@users.noreply.github.com"
+ },
+ "sender": {
+ "login": "hurricanehrndz",
+ "id": 5804237,
+ "node_id": "MDQ6VXNlcjU4MDQyMzc=",
+ "avatar_url": "https://avatars0.githubusercontent.com/u/5804237?v=4",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/hurricanehrndz",
+ "html_url": "https://github.com/hurricanehrndz",
+ "followers_url": "https://api.github.com/users/hurricanehrndz/followers",
+ "following_url": "https://api.github.com/users/hurricanehrndz/following{/other_user}",
+ "gists_url": "https://api.github.com/users/hurricanehrndz/gists{/gist_id}",
+ "starred_url": "https://api.github.com/users/hurricanehrndz/starred{/owner}{/repo}",
+ "subscriptions_url": "https://api.github.com/users/hurricanehrndz/subscriptions",
+ "organizations_url": "https://api.github.com/users/hurricanehrndz/orgs",
+ "repos_url": "https://api.github.com/users/hurricanehrndz/repos",
+ "events_url": "https://api.github.com/users/hurricanehrndz/events{/privacy}",
+ "received_events_url": "https://api.github.com/users/hurricanehrndz/received_events",
+ "type": "User",
+ "site_admin": false
+ },
+ "created": false,
+ "deleted": false,
+ "forced": false,
+ "base_ref": null,
+ "compare": "https://github.com/hurricanehrndz/neovim-snap/compare/66b136c43c12...1bf69c32217c",
+ "commits": [
+ {
+ "id": "1bf69c32217cc455603ce8aa2b5415d9717f0fa2",
+ "tree_id": "62ea83a2349be8c930c45fdc199f71b08bf5927e",
+ "distinct": true,
+ "message": "Build of latest tag",
+ "timestamp": "2020-09-04T14:05:40Z",
+ "url": "https://github.com/hurricanehrndz/neovim-snap/commit/1bf69c32217cc455603ce8aa2b5415d9717f0fa2",
+ "author": {
+ "name": "Carlos Hernandez",
+ "email": "carlos@techbyte.ca",
+ "username": "hurricanehrndz"
+ },
+ "committer": {
+ "name": "Carlos Hernandez",
+ "email": "carlos@techbyte.ca",
+ "username": "hurricanehrndz"
+ },
+ "added": [
+
+ ],
+ "removed": [
+
+ ],
+ "modified": [
+ "snap/snapcraft.yaml"
+ ]
+ }
+ ],
+ "head_commit": {
+ "id": "1bf69c32217cc455603ce8aa2b5415d9717f0fa2",
+ "tree_id": "62ea83a2349be8c930c45fdc199f71b08bf5927e",
+ "distinct": true,
+ "message": "Build of latest tag",
+ "timestamp": "2020-09-04T14:05:40Z",
+ "url": "https://github.com/hurricanehrndz/neovim-snap/commit/1bf69c32217cc455603ce8aa2b5415d9717f0fa2",
+ "author": {
+ "name": "Carlos Hernandez",
+ "email": "carlos@techbyte.ca",
+ "username": "hurricanehrndz"
+ },
+ "committer": {
+ "name": "Carlos Hernandez",
+ "email": "carlos@techbyte.ca",
+ "username": "hurricanehrndz"
+ },
+ "added": [
+
+ ],
+ "removed": [
+
+ ],
+ "modified": [
+ "snap/snapcraft.yaml"
+ ]
+ }
+}
diff --git a/ci/snap/deploy.sh b/ci/snap/deploy.sh
index 5fbd52d775..579c48e933 100755
--- a/ci/snap/deploy.sh
+++ b/ci/snap/deploy.sh
@@ -3,19 +3,37 @@
set -e
set -o pipefail
-# not a tagged release, abort
-# [[ "$TRAVIS_TAG" != "$TRAVIS_BRANCH" ]] && exit 0
+SNAP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+WEBHOOK_PAYLOAD="$(cat "${SNAP_DIR}/.snapcraft_payload")"
+PAYLOAD_SIG="${SECRET_SNAP_SIG}"
-mkdir -p .snapcraft
-# shellcheck disable=SC2154
-openssl aes-256-cbc -K "$encrypted_ece1c4844832_key" -iv "$encrypted_ece1c4844832_iv" \
- -in ci/snap/travis_snapcraft.cfg -out .snapcraft/snapcraft.cfg -d
-SNAP=$(find ./ -name "*.snap")
+snap_realease_needed() {
+ last_committed_tag="$(git tag -l --sort=refname|head -1)"
+ last_snap_release="$(snap info nvim | awk '$1 == "latest/edge:" { print $2 }' | perl -lpe 's/v\d.\d.\d-//g')"
+ git fetch -f --tags
+ git checkout "${last_committed_tag}" 2> /dev/null
+ last_git_release="$(git describe --first-parent 2> /dev/null | perl -lpe 's/v\d.\d.\d-//g')"
-# TODO(justinmk): This always does `edge` until we enable tagged builds.
-if [[ "$SNAP" =~ "dirty" || "$SNAP" =~ "nightly" ]]; then
- snapcraft push "$SNAP" --release edge
-else
- snapcraft push "$SNAP" --release candidate
+ if [[ -z "$(echo $last_snap_release | perl -ne "print if /${last_git_release}.*/")" ]]; then
+ return 0
+ fi
+ return 1
+}
+
+
+trigger_snapcraft_webhook() {
+ [[ -n "${PAYLOAD_SIG}" ]] || exit
+ echo "Triggering new snap relase via webhook..."
+ curl -X POST \
+ -H "Content-Type: application/json" \
+ -H "X-Hub-Signature: sha1=${PAYLOAD_SIG}" \
+ --data "${WEBHOOK_PAYLOAD}" \
+ https://snapcraft.io/nvim/webhook/notify
+}
+
+
+if $(snap_realease_needed); then
+ echo "New snap release required"
+ trigger_snapcraft_webhook
fi
diff --git a/cmake/FindLua.cmake b/cmake/FindLua.cmake
index b669a49f29..7ba13e1f56 100644
--- a/cmake/FindLua.cmake
+++ b/cmake/FindLua.cmake
@@ -42,7 +42,7 @@ unset(_lua_append_versions)
# this is a function only to have all the variables inside go away automatically
function(_lua_set_version_vars)
- set(LUA_VERSIONS5 5.3 5.2 5.1 5.0)
+ set(LUA_VERSIONS5 5.4 5.3 5.2 5.1 5.0)
if (Lua_FIND_VERSION_EXACT)
if (Lua_FIND_VERSION_COUNT GREATER 1)
diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim
index 0482cb7f3c..e8e38f581f 100644
--- a/runtime/autoload/health/provider.vim
+++ b/runtime/autoload/health/provider.vim
@@ -573,7 +573,7 @@ function! s:check_ruby() abort
endif
call health#report_info('Ruby: '. s:system('ruby -v'))
- let host = provider#ruby#Detect()
+ let [host, err] = provider#ruby#Detect()
if empty(host)
call health#report_warn('`neovim-ruby-host` not found.',
\ ['Run `gem install neovim` to ensure the neovim RubyGem is installed.',
@@ -636,7 +636,7 @@ function! s:check_node() abort
call health#report_warn('node.js on this system does not support --inspect-brk so $NVIM_NODE_HOST_DEBUG is ignored.')
endif
- let host = provider#node#Detect()
+ let [host, err] = provider#node#Detect()
if empty(host)
call health#report_warn('Missing "neovim" npm (or yarn) package.',
\ ['Run in shell: npm install -g neovim',
@@ -689,29 +689,31 @@ function! s:check_perl() abort
return
endif
- if !executable('perl') || !executable('cpanm')
- call health#report_warn(
- \ '`perl` and `cpanm` must be in $PATH.',
- \ ['Install Perl and cpanminus and verify that `perl` and `cpanm` commands work.'])
- return
+ let [perl_exec, perl_errors] = provider#perl#Detect()
+ if empty(perl_exec)
+ if !empty(perl_errors)
+ call health#report_error('perl provider error:', perl_errors)
+ else
+ call health#report_warn('No usable perl executable found')
+ endif
+ return
endif
- let perl_v = get(split(s:system(['perl', '-W', '-e', 'print $^V']), "\n"), 0, '')
- call health#report_info('Perl: '. perl_v)
+
+ call health#report_info('perl executable: '. perl_exec)
+
+ " we cannot use cpanm that is on the path, as it may not be for the perl
+ " set with g:perl_host_prog
+ call s:system([perl_exec, '-W', '-MApp::cpanminus', '-e', ''])
if s:shell_error
- call health#report_warn('Nvim perl host does not support '.perl_v)
- " Skip further checks, they are nonsense if perl is too old.
- return
+ return [perl_exec, '"App::cpanminus" module is not installed']
endif
- let host = provider#perl#Detect()
- if empty(host)
- call health#report_warn('Missing "Neovim::Ext" cpan module.',
- \ ['Run in shell: cpanm Neovim::Ext'])
- return
- endif
- call health#report_info('Nvim perl host: '. host)
+ let latest_cpan_cmd = [perl_exec,
+ \ '-MApp::cpanminus::fatscript', '-e',
+ \ 'my $app = App::cpanminus::script->new;
+ \ $app->parse_options ("--info", "-q", "Neovim::Ext");
+ \ exit $app->doit']
- let latest_cpan_cmd = 'cpanm --info -q Neovim::Ext'
let latest_cpan = s:system(latest_cpan_cmd)
if s:shell_error || empty(latest_cpan)
call health#report_error('Failed to run: '. latest_cpan_cmd,
@@ -735,7 +737,7 @@ function! s:check_perl() abort
return
endif
- let current_cpan_cmd = [host, '-W', '-MNeovim::Ext', '-e', 'print $Neovim::Ext::VERSION']
+ let current_cpan_cmd = [perl_exec, '-W', '-MNeovim::Ext', '-e', 'print $Neovim::Ext::VERSION']
let current_cpan = s:system(current_cpan_cmd)
if s:shell_error
call health#report_error('Failed to run: '. string(current_cpan_cmd),
diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim
index a96a0a61b7..275d18a5a9 100644
--- a/runtime/autoload/provider/clipboard.vim
+++ b/runtime/autoload/provider/clipboard.vim
@@ -35,8 +35,7 @@ endfunction
let s:selections = { '*': s:selection, '+': copy(s:selection) }
function! s:try_cmd(cmd, ...) abort
- let argv = split(a:cmd, " ")
- let out = systemlist(argv, (a:0 ? a:1 : ['']), 1)
+ let out = systemlist(a:cmd, (a:0 ? a:1 : ['']), 1)
if v:shell_error
if !exists('s:did_error_try_cmd')
echohl WarningMsg
@@ -55,6 +54,10 @@ function! s:cmd_ok(cmd) abort
return v:shell_error == 0
endfunction
+function! s:split_cmd(cmd) abort
+ return (type(a:cmd) == v:t_string) ? split(a:cmd, " ") : a:cmd
+endfunction
+
let s:cache_enabled = 1
let s:err = ''
@@ -71,44 +74,50 @@ function! provider#clipboard#Executable() abort
return ''
endif
- let s:copy = get(g:clipboard, 'copy', { '+': v:null, '*': v:null })
- let s:paste = get(g:clipboard, 'paste', { '+': v:null, '*': v:null })
+ let s:copy = {}
+ let s:copy['+'] = s:split_cmd(get(g:clipboard.copy, '+', v:null))
+ let s:copy['*'] = s:split_cmd(get(g:clipboard.copy, '*', v:null))
+
+ let s:paste = {}
+ let s:paste['+'] = s:split_cmd(get(g:clipboard.paste, '+', v:null))
+ let s:paste['*'] = s:split_cmd(get(g:clipboard.paste, '*', v:null))
+
let s:cache_enabled = get(g:clipboard, 'cache_enabled', 0)
return get(g:clipboard, 'name', 'g:clipboard')
elseif has('mac')
- let s:copy['+'] = 'pbcopy'
- let s:paste['+'] = 'pbpaste'
+ let s:copy['+'] = ['pbcopy']
+ let s:paste['+'] = ['pbpaste']
let s:copy['*'] = s:copy['+']
let s:paste['*'] = s:paste['+']
let s:cache_enabled = 0
return 'pbcopy'
elseif exists('$WAYLAND_DISPLAY') && executable('wl-copy') && executable('wl-paste')
- let s:copy['+'] = 'wl-copy --foreground --type text/plain'
- let s:paste['+'] = 'wl-paste --no-newline'
- let s:copy['*'] = 'wl-copy --foreground --primary --type text/plain'
- let s:paste['*'] = 'wl-paste --no-newline --primary'
+ let s:copy['+'] = ['wl-copy', '--foreground', '--type', 'text/plain']
+ let s:paste['+'] = ['wl-paste', '--no-newline']
+ let s:copy['*'] = ['wl-copy', '--foreground', '--primary', '--type', 'text/plain']
+ let s:paste['*'] = ['wl-paste', '--no-newline', '--primary']
return 'wl-copy'
elseif exists('$DISPLAY') && executable('xclip')
- let s:copy['+'] = 'xclip -quiet -i -selection clipboard'
- let s:paste['+'] = 'xclip -o -selection clipboard'
- let s:copy['*'] = 'xclip -quiet -i -selection primary'
- let s:paste['*'] = 'xclip -o -selection primary'
+ let s:copy['+'] = ['xclip', '-quiet', '-i', '-selection', 'clipboard']
+ let s:paste['+'] = ['xclip', '-o', '-selection', 'clipboard']
+ let s:copy['*'] = ['xclip', '-quiet', '-i', '-selection', 'primary']
+ let s:paste['*'] = ['xclip', '-o', '-selection', 'primary']
return 'xclip'
elseif exists('$DISPLAY') && executable('xsel') && s:cmd_ok('xsel -o -b')
- let s:copy['+'] = 'xsel --nodetach -i -b'
- let s:paste['+'] = 'xsel -o -b'
- let s:copy['*'] = 'xsel --nodetach -i -p'
- let s:paste['*'] = 'xsel -o -p'
+ let s:copy['+'] = ['xsel', '--nodetach', '-i', '-b']
+ let s:paste['+'] = ['xsel', '-o', '-b']
+ let s:copy['*'] = ['xsel', '--nodetach', '-i', '-p']
+ let s:paste['*'] = ['xsel', '-o', '-p']
return 'xsel'
elseif executable('lemonade')
- let s:copy['+'] = 'lemonade copy'
- let s:paste['+'] = 'lemonade paste'
- let s:copy['*'] = 'lemonade copy'
- let s:paste['*'] = 'lemonade paste'
+ let s:copy['+'] = ['lemonade', 'copy']
+ let s:paste['+'] = ['lemonade', 'paste']
+ let s:copy['*'] = ['lemonade', 'copy']
+ let s:paste['*'] = ['lemonade', 'paste']
return 'lemonade'
elseif executable('doitclient')
- let s:copy['+'] = 'doitclient wclip'
- let s:paste['+'] = 'doitclient wclip -r'
+ let s:copy['+'] = ['doitclient', 'wclip']
+ let s:paste['+'] = ['doitclient', 'wclip', '-r']
let s:copy['*'] = s:copy['+']
let s:paste['*'] = s:paste['+']
return 'doitclient'
@@ -118,14 +127,14 @@ function! provider#clipboard#Executable() abort
else
let win32yank = 'win32yank.exe'
endif
- let s:copy['+'] = win32yank.' -i --crlf'
- let s:paste['+'] = win32yank.' -o --lf'
+ let s:copy['+'] = [win32yank, '-i', '--crlf']
+ let s:paste['+'] = [win32yank, '-o', '--lf']
let s:copy['*'] = s:copy['+']
let s:paste['*'] = s:paste['+']
return 'win32yank'
elseif exists('$TMUX') && executable('tmux')
- let s:copy['+'] = 'tmux load-buffer -'
- let s:paste['+'] = 'tmux save-buffer -'
+ let s:copy['+'] = ['tmux', 'load-buffer', '-']
+ let s:paste['+'] = ['tmux', 'save-buffer', '-']
let s:copy['*'] = s:copy['+']
let s:paste['*'] = s:paste['+']
return 'tmux'
@@ -169,16 +178,15 @@ function! s:clipboard.set(lines, regtype, reg) abort
let s:selections[a:reg] = copy(s:selection)
let selection = s:selections[a:reg]
let selection.data = [a:lines, a:regtype]
- let argv = split(s:copy[a:reg], " ")
- let selection.argv = argv
+ let selection.argv = s:copy[a:reg]
let selection.detach = s:cache_enabled
let selection.cwd = "/"
- let jobid = jobstart(argv, selection)
+ let jobid = jobstart(selection.argv, selection)
if jobid > 0
call jobsend(jobid, a:lines)
call jobclose(jobid, 'stdin')
" xclip does not close stdout when receiving input via stdin
- if argv[0] ==# 'xclip'
+ if selection.argv[0] ==# 'xclip'
call jobclose(jobid, 'stdout')
endif
let selection.owner = jobid
diff --git a/runtime/autoload/provider/node.vim b/runtime/autoload/provider/node.vim
index c5d5e87729..17b6137816 100644
--- a/runtime/autoload/provider/node.vim
+++ b/runtime/autoload/provider/node.vim
@@ -48,14 +48,15 @@ function! provider#node#can_inspect() abort
endfunction
function! provider#node#Detect() abort
+ let minver = [6, 0]
if exists('g:node_host_prog')
- return expand(g:node_host_prog)
+ return [expand(g:node_host_prog), '']
endif
if !executable('node')
- return ''
+ return ['', 'node not found (or not executable)']
endif
- if !s:is_minimum_version(v:null, 6, 0)
- return ''
+ if !s:is_minimum_version(v:null, minver[0], minver[1])
+ return ['', printf('node version %s.%s not found', minver[0], minver[1])]
endif
let npm_opts = {}
@@ -75,7 +76,7 @@ function! provider#node#Detect() abort
if has('unix')
let yarn_default_path = $HOME . '/.config/yarn/global/' . yarn_opts.entry_point
if filereadable(yarn_default_path)
- return yarn_default_path
+ return [yarn_default_path, '']
endif
endif
let yarn_opts.job_id = jobstart('yarn global dir', yarn_opts)
@@ -85,18 +86,18 @@ function! provider#node#Detect() abort
if !empty(npm_opts)
let result = jobwait([npm_opts.job_id])
if result[0] == 0 && npm_opts.result != ''
- return npm_opts.result
+ return [npm_opts.result, '']
endif
endif
if !empty(yarn_opts)
let result = jobwait([yarn_opts.job_id])
if result[0] == 0 && yarn_opts.result != ''
- return yarn_opts.result
+ return [yarn_opts.result, '']
endif
endif
- return ''
+ return ['', 'failed to detect node']
endfunction
function! provider#node#Prog() abort
@@ -142,7 +143,7 @@ endfunction
let s:err = ''
-let s:prog = provider#node#Detect()
+let [s:prog, s:_] = provider#node#Detect()
let g:loaded_node_provider = empty(s:prog) ? 1 : 2
if g:loaded_node_provider != 2
diff --git a/runtime/autoload/provider/perl.vim b/runtime/autoload/provider/perl.vim
index 36ca2bbf14..24f2b018bb 100644
--- a/runtime/autoload/provider/perl.vim
+++ b/runtime/autoload/provider/perl.vim
@@ -5,15 +5,25 @@ endif
let s:loaded_perl_provider = 1
function! provider#perl#Detect() abort
- " use g:perl_host_prof if set or check if perl is on the path
+ " use g:perl_host_prog if set or check if perl is on the path
let prog = exepath(get(g:, 'perl_host_prog', 'perl'))
if empty(prog)
- return ''
+ return ['', '']
+ endif
+
+ " if perl is available, make sure we have 5.22+
+ call system([prog, '-e', 'use v5.22'])
+ if v:shell_error
+ return ['', 'Perl version is too old, 5.22+ required']
endif
" if perl is available, make sure the required module is available
call system([prog, '-W', '-MNeovim::Ext', '-e', ''])
- return v:shell_error ? '' : prog
+ if v:shell_error
+ return ['', '"Neovim::Ext" cpan module is not installed']
+ endif
+
+ return [prog, '']
endfunction
function! provider#perl#Prog() abort
@@ -46,7 +56,7 @@ function! provider#perl#Call(method, args) abort
if !exists('s:host')
try
- let s:host = remote#host#Require('perl')
+ let s:host = remote#host#Require('legacy-perl-provider')
catch
let s:err = v:exception
echohl WarningMsg
@@ -58,12 +68,16 @@ function! provider#perl#Call(method, args) abort
return call('rpcrequest', insert(insert(a:args, 'perl_'.a:method), s:host))
endfunction
-let s:err = ''
-let s:prog = provider#perl#Detect()
+let [s:prog, s:err] = provider#perl#Detect()
let g:loaded_perl_provider = empty(s:prog) ? 1 : 2
if g:loaded_perl_provider != 2
let s:err = 'Cannot find perl or the required perl module'
endif
-call remote#host#RegisterPlugin('perl-provider', 'perl', [])
+
+" The perl provider plugin will run in a separate instance of the perl
+" host.
+call remote#host#RegisterClone('legacy-perl-provider', 'perl')
+call remote#host#RegisterPlugin('legacy-perl-provider', 'ScriptHost.pm', [])
+
diff --git a/runtime/autoload/provider/ruby.vim b/runtime/autoload/provider/ruby.vim
index f843050df9..1f49c623ac 100644
--- a/runtime/autoload/provider/ruby.vim
+++ b/runtime/autoload/provider/ruby.vim
@@ -5,7 +5,8 @@ endif
let g:loaded_ruby_provider = 1
function! provider#ruby#Detect() abort
- return s:prog
+ let e = empty(s:prog) ? 'missing ruby or ruby-host' : ''
+ return [s:prog, e]
endfunction
function! provider#ruby#Prog() abort
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index 7ac7f7d7ea..b80ca9edd7 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -70,7 +70,7 @@ Nvim instance:
nvim = MessagePack::RPC::Client.new(MessagePack::RPC::UNIXTransport.new, ENV['NVIM_LISTEN_ADDRESS'])
result = nvim.call(:nvim_command, 'echo "hello world!"')
<
-A better way is to use the Python REPL with the `neovim` package, where API
+A better way is to use the Python REPL with the "pynvim" package, where API
functions can be called interactively:
>
>>> from pynvim import attach
@@ -336,7 +336,7 @@ callbacks. These callbacks are called frequently in various contexts;
|textlock| prevents changing buffer contents and window layout (use
|vim.schedule| to defer such operations to the main loop instead).
-|nvim_buf_attach| will take keyword args for the callbacks. "on_lines" will
+|nvim_buf_attach()| will take keyword args for the callbacks. "on_lines" will
receive parameters ("lines", {buf}, {changedtick}, {firstline}, {lastline},
{new_lastline}, {old_byte_size}[, {old_utf32_size}, {old_utf16_size}]).
Unlike remote channel events the text contents are not passed. The new text can
@@ -355,7 +355,7 @@ was changed. The parameters recieved are ("changedtick", {buf}, {changedtick}).
*api-lua-detach*
In-process Lua callbacks can detach by returning `true`. This will detach all
-callbacks attached with the same |nvim_buf_attach| call.
+callbacks attached with the same |nvim_buf_attach()| call.
==============================================================================
@@ -537,6 +537,12 @@ nvim__put_attr({id}, {start_row}, {start_col}, {end_row}, {end_col})
should probably be derived from a reformed bufhl/virttext
interface with full support for multi-line ranges etc
+nvim__screenshot({path}) *nvim__screenshot()*
+ TODO: Documentation
+
+ Attributes: ~
+ {fast}
+
nvim__stats() *nvim__stats()*
Gets internal stats.
@@ -1565,12 +1571,6 @@ affected.
You can use |nvim_buf_is_loaded()| or |nvim_buf_line_count()|
to check whether a buffer is loaded.
- *nvim__buf_add_decoration()*
-nvim__buf_add_decoration({buffer}, {ns_id}, {hl_group}, {start_row},
- {start_col}, {end_row}, {end_col},
- {virt_text})
- TODO: Documentation
-
*nvim__buf_redraw_range()*
nvim__buf_redraw_range({buffer}, {first}, {last})
TODO: Documentation
@@ -1603,19 +1603,20 @@ nvim_buf_add_highlight({buffer}, {src_id}, {hl_group}, {line},
marks do.
Namespaces are used for batch deletion/updating of a set of
- highlights. To create a namespace, use |nvim_create_namespace|
- which returns a namespace id. Pass it in to this function as
- `ns_id` to add highlights to the namespace. All highlights in
- the same namespace can then be cleared with single call to
- |nvim_buf_clear_namespace|. If the highlight never will be
- deleted by an API call, pass `ns_id = -1` .
+ highlights. To create a namespace, use
+ |nvim_create_namespace()| which returns a namespace id. Pass
+ it in to this function as `ns_id` to add highlights to the
+ namespace. All highlights in the same namespace can then be
+ cleared with single call to |nvim_buf_clear_namespace()|. If
+ the highlight never will be deleted by an API call, pass
+ `ns_id = -1` .
As a shorthand, `ns_id = 0` can be used to create a new
namespace for the highlight, the allocated id is then
returned. If `hl_group` is the empty string no highlight is
added, but a new `ns_id` is still returned. This is supported
for backwards compatibility, new code should use
- |nvim_create_namespace| to create a new empty namespace.
+ |nvim_create_namespace()| to create a new empty namespace.
Parameters: ~
{buffer} Buffer handle, or 0 for current buffer
@@ -1766,13 +1767,16 @@ nvim_buf_get_commands({buffer}, {opts}) *nvim_buf_get_commands()*
Map of maps describing commands.
*nvim_buf_get_extmark_by_id()*
-nvim_buf_get_extmark_by_id({buffer}, {ns_id}, {id})
+nvim_buf_get_extmark_by_id({buffer}, {ns_id}, {id}, {opts})
Returns position for a given extmark id
Parameters: ~
{buffer} Buffer handle, or 0 for current buffer
{ns_id} Namespace id from |nvim_create_namespace()|
{id} Extmark id
+ {opts} Optional parameters. Keys:
+ • limit: Maximum number of marks to return
+ • details Whether to include the details dict
Return: ~
(row, col) tuple or empty list () if extmark id was absent
@@ -1821,6 +1825,7 @@ nvim_buf_get_extmarks({buffer}, {ns_id}, {start}, {end}, {opts})
extmark id (whose position defines the bound)
{opts} Optional parameters. Keys:
• limit: Maximum number of marks to return
+ • details Whether to include the details dict
Return: ~
List of [extmark_id, row, col] tuples in "traversal
@@ -1919,28 +1924,6 @@ nvim_buf_get_var({buffer}, {name}) *nvim_buf_get_var()*
Return: ~
Variable value
- *nvim_buf_get_virtual_text()*
-nvim_buf_get_virtual_text({buffer}, {line})
- Get the virtual text (annotation) for a buffer line.
-
- The virtual text is returned as list of lists, whereas the
- inner lists have either one or two elements. The first element
- is the actual text, the optional second element is the
- highlight group.
-
- The format is exactly the same as given to
- nvim_buf_set_virtual_text().
-
- If there is no virtual text associated with the given line, an
- empty list is returned.
-
- Parameters: ~
- {buffer} Buffer handle, or 0 for current buffer
- {line} Line to get the virtual text from (zero-indexed)
-
- Return: ~
- List of virtual text chunks
-
nvim_buf_is_loaded({buffer}) *nvim_buf_is_loaded()*
Checks if a buffer is valid and loaded. See |api-buffer| for
more info about unloaded buffers.
@@ -1974,22 +1957,35 @@ nvim_buf_line_count({buffer}) *nvim_buf_line_count()*
Line count, or 0 for unloaded buffer. |api-buffer|
*nvim_buf_set_extmark()*
-nvim_buf_set_extmark({buffer}, {ns_id}, {id}, {line}, {col}, {opts})
+nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {opts})
Creates or updates an extmark.
To create a new extmark, pass id=0. The extmark id will be
- returned. It is also allowed to create a new mark by passing
- in a previously unused id, but the caller must then keep track
- of existing and unused ids itself. (Useful over RPC, to avoid
+ returned. To move an existing mark, pass its id.
+
+ It is also allowed to create a new mark by passing in a
+ previously unused id, but the caller must then keep track of
+ existing and unused ids itself. (Useful over RPC, to avoid
waiting for the return value.)
+ Using the optional arguments, it is possible to use this to
+ highlight a range of text, and also to associate virtual text
+ to the mark.
+
Parameters: ~
{buffer} Buffer handle, or 0 for current buffer
{ns_id} Namespace id from |nvim_create_namespace()|
- {id} Extmark id, or 0 to create new
{line} Line number where to place the mark
{col} Column where to place the mark
- {opts} Optional parameters. Currently not used.
+ {opts} Optional parameters.
+ • id : id of the extmark to edit.
+ • end_line : ending line of the mark, 0-based
+ inclusive.
+ • end_col : ending col of the mark, 0-based
+ inclusive.
+ • hl_group : name of the highlight group used to
+ highlight this mark.
+ • virt_text : virtual text to link to this mark.
Return: ~
Id of the created/updated extmark
@@ -2066,12 +2062,12 @@ nvim_buf_set_virtual_text({buffer}, {src_id}, {line}, {chunks},
Namespaces are used to support batch deletion/updating of
virtual text. To create a namespace, use
- |nvim_create_namespace|. Virtual text is cleared using
- |nvim_buf_clear_namespace|. The same `ns_id` can be used for
+ |nvim_create_namespace()|. Virtual text is cleared using
+ |nvim_buf_clear_namespace()|. The same `ns_id` can be used for
both virtual text and highlights added by
- |nvim_buf_add_highlight|, both can then be cleared with a
- single call to |nvim_buf_clear_namespace|. If the virtual text
- never will be cleared by an API call, pass `ns_id = -1` .
+ |nvim_buf_add_highlight()|, both can then be cleared with a
+ single call to |nvim_buf_clear_namespace()|. If the virtual
+ text never will be cleared by an API call, pass `ns_id = -1` .
As a shorthand, `ns_id = 0` can be used to create a new
namespace for the virtual text, the allocated id is then
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt
index f1753b75cc..a6872d0af5 100644
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -206,169 +206,9 @@ autocommands, this doesn't happen.
You can use the 'eventignore' option to ignore a number of events or all
events.
- *autocommand-events* *{event}*
-Vim recognizes the following events. Vim ignores the case of event names
-(e.g., you can use "BUFread" or "bufread" instead of "BufRead").
-
-First an overview by function with a short explanation. Then the list
-alphabetically with full explanations |autocmd-events-abc|.
-
-Name triggered by ~
-
- Reading
-|BufNewFile| starting to edit a file that doesn't exist
-|BufReadPre| starting to edit a new buffer, before reading the file
-|BufRead| starting to edit a new buffer, after reading the file
-|BufReadPost| starting to edit a new buffer, after reading the file
-|BufReadCmd| before starting to edit a new buffer |Cmd-event|
-
-|FileReadPre| before reading a file with a ":read" command
-|FileReadPost| after reading a file with a ":read" command
-|FileReadCmd| before reading a file with a ":read" command |Cmd-event|
-
-|FilterReadPre| before reading a file from a filter command
-|FilterReadPost| after reading a file from a filter command
-
-|StdinReadPre| before reading from stdin into the buffer
-|StdinReadPost| After reading from the stdin into the buffer
-
- Writing
-|BufWrite| starting to write the whole buffer to a file
-|BufWritePre| starting to write the whole buffer to a file
-|BufWritePost| after writing the whole buffer to a file
-|BufWriteCmd| before writing the whole buffer to a file |Cmd-event|
-
-|FileWritePre| starting to write part of a buffer to a file
-|FileWritePost| after writing part of a buffer to a file
-|FileWriteCmd| before writing part of a buffer to a file |Cmd-event|
-
-|FileAppendPre| starting to append to a file
-|FileAppendPost| after appending to a file
-|FileAppendCmd| before appending to a file |Cmd-event|
-
-|FilterWritePre| starting to write a file for a filter command or diff
-|FilterWritePost| after writing a file for a filter command or diff
-
- Buffers
-|BufAdd| just after adding a buffer to the buffer list
-|BufDelete| before deleting a buffer from the buffer list
-|BufWipeout| before completely deleting a buffer
-
-|BufFilePre| before changing the name of the current buffer
-|BufFilePost| after changing the name of the current buffer
-
-|BufEnter| after entering a buffer
-|BufLeave| before leaving to another buffer
-|BufWinEnter| after a buffer is displayed in a window
-|BufWinLeave| before a buffer is removed from a window
-
-|BufUnload| before unloading a buffer
-|BufHidden| just after a buffer has become hidden
-|BufNew| just after creating a new buffer
-
-|SwapExists| detected an existing swap file
-|TermOpen| starting a terminal job
-|TermEnter| entering Terminal-mode
-|TermLeave| leaving Terminal-mode
-|TermClose| stopping a terminal job
-|ChanOpen| after a channel opened
-|ChanInfo| after a channel has its state changed
-
- Options
-|FileType| when the 'filetype' option has been set
-|Syntax| when the 'syntax' option has been set
-|OptionSet| after setting any option
-
- Startup and exit
-|VimEnter| after doing all the startup stuff
-|UIEnter| after a UI attaches
-|UILeave| after a UI detaches
-|TermResponse| after the terminal response to t_RV is received
-|QuitPre| when using `:quit`, before deciding whether to exit
-|ExitPre| when using a command that may make Vim exit
-|VimLeavePre| before exiting Nvim, before writing the shada file
-|VimLeave| before exiting Nvim, after writing the shada file
-|VimResume| after Nvim is resumed
-|VimSuspend| before Nvim is suspended
-
- Various
-|DiffUpdated| after diffs have been updated
-|DirChanged| after the |current-directory| was changed
-
-|FileChangedShell| Vim notices that a file changed since editing started
-|FileChangedShellPost| after handling a file changed since editing started
-|FileChangedRO| before making the first change to a read-only file
-
-|ShellCmdPost| after executing a shell command
-|ShellFilterPost| after filtering with a shell command
-
-|CmdUndefined| a user command is used but it isn't defined
-|FuncUndefined| a user function is used but it isn't defined
-|SpellFileMissing| a spell file is used but it can't be found
-|SourcePre| before sourcing a Vim script
-|SourcePost| after sourcing a Vim script
-|SourceCmd| before sourcing a Vim script |Cmd-event|
-
-|VimResized| after the Vim window size changed
-|FocusGained| Nvim got focus
-|FocusLost| Nvim lost focus
-|CursorHold| the user doesn't press a key for a while
-|CursorHoldI| the user doesn't press a key for a while in Insert mode
-|CursorMoved| the cursor was moved in Normal mode
-|CursorMovedI| the cursor was moved in Insert mode
-
-|WinClosed| after closing a window
-|WinNew| after creating a new window
-|WinEnter| after entering another window
-|WinLeave| before leaving a window
-|TabEnter| after entering another tab page
-|TabLeave| before leaving a tab page
-|TabNew| when creating a new tab page
-|TabNewEntered| after entering a new tab page
-|TabClosed| after closing a tab page
-|CmdlineChanged| after a change was made to the command-line text
-|CmdlineEnter| after entering cmdline mode
-|CmdlineLeave| before leaving cmdline mode
-|CmdwinEnter| after entering the command-line window
-|CmdwinLeave| before leaving the command-line window
-
-|InsertEnter| starting Insert mode
-|InsertChange| when typing <Insert> while in Insert or Replace mode
-|InsertLeave| when leaving Insert mode
-|InsertCharPre| when a character was typed in Insert mode, before
- inserting it
-
-|TextYankPost| when some text is yanked or deleted
-
-|TextChanged| after a change was made to the text in Normal mode
-|TextChangedI| after a change was made to the text in Insert mode
- when popup menu is not visible
-|TextChangedP| after a change was made to the text in Insert mode
- when popup menu visible
-
-|ColorSchemePre| before loading a color scheme
-|ColorScheme| after loading a color scheme
-
-|RemoteReply| a reply from a server Vim was received
-
-|QuickFixCmdPre| before a quickfix command is run
-|QuickFixCmdPost| after a quickfix command is run
-
-|SessionLoadPost| after loading a session file
-
-|MenuPopup| just before showing the popup menu
-|CompleteChanged| after popup menu changed, not fired on popup menu hide
-|CompleteDonePre| after Insert mode completion is done, before clearing
- info
-|CompleteDone| after Insert mode completion is done, after clearing
- info
-
-|User| to be used in combination with ":doautocmd"
-|Signal| after Nvim receives a signal
-
-
-
-The alphabetical list of autocommand events: *autocmd-events-abc*
+
+ *events* *{event}*
+Nvim recognizes the following events. Names are case-insensitive.
*BufAdd*
BufAdd Just after creating a new buffer which is
@@ -642,7 +482,7 @@ CursorHold When the user doesn't press a key for the time
Hint: to force an update of the status lines
use: >
:let &ro = &ro
-
+<
*CursorHoldI*
CursorHoldI Like CursorHold, but in Insert mode. Not
triggered when waiting for another key, e.g.
@@ -1111,13 +951,13 @@ VimEnter After doing all the startup stuff, including
if v:vim_did_enter
call s:init()
else
- au VimEnter * call s:init()
+ au VimEnter * call s:init()
endif
< *VimLeave*
VimLeave Before exiting Vim, just after writing the
.shada file. Executed only once, like
VimLeavePre.
-< Use |v:dying| to detect an abnormal exit.
+ Use |v:dying| to detect an abnormal exit.
Use |v:exiting| to get the exit code.
Not triggered if |v:dying| is 2 or more.
*VimLeavePre*
@@ -1126,7 +966,7 @@ VimLeavePre Before exiting Vim, just before writing the
if there is a match with the name of what
happens to be the current buffer when exiting.
Mostly useful with a "*" pattern. >
- :autocmd VimLeavePre * call CleanupStuff()
+ :autocmd VimLeavePre * call CleanupStuff()
< Use |v:dying| to detect an abnormal exit.
Use |v:exiting| to get the exit code.
Not triggered if |v:dying| is 2 or more.
diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt
index b31177ce0e..163dc81804 100644
--- a/runtime/doc/cmdline.txt
+++ b/runtime/doc/cmdline.txt
@@ -556,6 +556,7 @@ followed by another Vim command:
:lfdo
:make
:normal
+ :perlfile
:promptfind
:promptrepl
:pyfile
diff --git a/runtime/doc/develop.txt b/runtime/doc/develop.txt
index 09c5b7c4ad..aec0178da2 100644
--- a/runtime/doc/develop.txt
+++ b/runtime/doc/develop.txt
@@ -103,8 +103,10 @@ Examples:
The provider framework invokes VimL from C. It is composed of two functions
in eval.c:
-- eval_call_provider(name, method, arguments): calls provider#{name}#Call
- with the method and arguments.
+- eval_call_provider(name, method, arguments, discard): calls
+ provider#{name}#Call with the method and arguments. If discard is true, any
+ value returned by the provider will be discarded and and empty value be
+ returned.
- eval_has_provider(name): Checks the `g:loaded_{name}_provider` variable
which must be set to 2 by the provider script to indicate that it is
"enabled and working". Called by |has()| to check if features are available.
diff --git a/runtime/doc/digraph.txt b/runtime/doc/digraph.txt
index 7f807b5eee..271b8c2597 100644
--- a/runtime/doc/digraph.txt
+++ b/runtime/doc/digraph.txt
@@ -913,6 +913,7 @@ char digraph hex dec official name ~
‟ 9" 201F 8223 DOUBLE HIGH-REVERSED-9 QUOTATION MARK
† /- 2020 8224 DAGGER
‡ /= 2021 8225 DOUBLE DAGGER
+• oo 2022 8226 BULLET
‥ .. 2025 8229 TWO DOT LEADER
… ,. 2026 8230 HORIZONTAL ELLIPSIS
‰ %0 2030 8240 PER MILLE SIGN
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index ca03ee0374..5127a9f390 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -2288,6 +2288,7 @@ nr2char({expr}[, {utf8}]) String single char with ASCII/UTF8 value {expr}
nvim_...({args}...) any call nvim |api| functions
or({expr}, {expr}) Number bitwise OR
pathshorten({expr}) String shorten directory names in a path
+perleval({expr}) any evaluate |perl| expression
pow({x}, {y}) Float {x} to the power of {y}
prevnonblank({lnum}) Number line nr of non-blank line <= {lnum}
printf({fmt}, {expr1}...) String format text
@@ -6423,6 +6424,21 @@ pathshorten({expr}) *pathshorten()*
< ~/.c/n/a/file1.vim ~
It doesn't matter if the path exists or not.
+perleval({expr}) *perleval()*
+ Evaluate |perl| expression {expr} and return its result
+ converted to Vim data structures.
+ Numbers and strings are returned as they are (strings are
+ copied though).
+ Lists are represented as Vim |List| type.
+ Dictionaries are represented as Vim |Dictionary| type,
+ non-string keys result in error.
+
+ Note: If you want an array or hash, {expr} must return a
+ reference to it.
+ Example: >
+ :echo perleval('[1 .. 4]')
+< [1, 2, 3, 4]
+
pow({x}, {y}) *pow()*
Return the power of {x} to the exponent {y} as a |Float|.
{x} and {y} must evaluate to a |Float| or a |Number|.
@@ -7754,26 +7770,23 @@ sha256({string}) *sha256()*
shellescape({string} [, {special}]) *shellescape()*
Escape {string} for use as a shell command argument.
- On Windows when 'shellslash' is not set, it
- will enclose {string} in double quotes and double all double
- quotes within {string}.
- Otherwise, it will enclose {string} in single quotes and
- replace all "'" with "'\''".
-
- When the {special} argument is present and it's a non-zero
- Number or a non-empty String (|non-zero-arg|), then special
- items such as "!", "%", "#" and "<cword>" will be preceded by
- a backslash. This backslash will be removed again by the |:!|
- command.
- The "!" character will be escaped (again with a |non-zero-arg|
- {special}) when 'shell' contains "csh" in the tail. That is
- because for csh and tcsh "!" is used for history replacement
- even when inside single quotes.
+ On Windows when 'shellslash' is not set, encloses {string} in
+ double-quotes and doubles all double-quotes within {string}.
+ Otherwise encloses {string} in single-quotes and replaces all
+ "'" with "'\''".
+
+ If {special} is a ||non-zero-arg|:
+ - Special items such as "!", "%", "#" and "<cword>" will be
+ preceded by a backslash. The backslash will be removed again
+ by the |:!| command.
+ - The <NL> character is escaped.
- With a |non-zero-arg| {special} the <NL> character is also
- escaped. When 'shell' containing "csh" in the tail it's
- escaped a second time.
+ If 'shell' contains "csh" in the tail:
+ - The "!" character will be escaped. This is because csh and
+ tcsh use "!" for history replacement even in single-quotes.
+ - The <NL> character is escaped (twice if {special} is
+ a ||non-zero-arg|).
Example of use with a |:!| command: >
:exe '!dir ' . shellescape(expand('<cfile>'), 1)
@@ -8246,15 +8259,13 @@ sqrt({expr}) *sqrt()*
stdioopen({opts}) *stdioopen()*
- In a nvim launched with the |--headless| option, this opens
- stdin and stdout as a |channel|. This function can only be
- invoked once per instance. See |channel-stdio| for more
- information and examples. Note that stderr is not handled by
- this function, see |v:stderr|.
+ With |--headless| this opens stdin and stdout as a |channel|.
+ May be called only once. See |channel-stdio|. stderr is not
+ handled by this function, see |v:stderr|.
- Returns a |channel| ID. Close the stdio descriptors with |chanclose()|.
- Use |chansend()| to send data to stdout, and
- |rpcrequest()| and |rpcnotify()| to communicate over RPC.
+ Close the stdio handles with |chanclose()|. Use |chansend()|
+ to send data to stdout, and |rpcrequest()| and |rpcnotify()|
+ to communicate over RPC.
{opts} is a dictionary with these keys:
|on_stdin| : callback invoked when stdin is written to.
@@ -8262,7 +8273,7 @@ stdioopen({opts}) *stdioopen()*
rpc : If set, |msgpack-rpc| will be used to communicate
over stdio
Returns:
- - The channel ID on success (this is always 1)
+ - |channel-id| on success (value is always 1)
- 0 on invalid arguments
diff --git a/runtime/doc/help.txt b/runtime/doc/help.txt
index a384b5f876..203699435b 100644
--- a/runtime/doc/help.txt
+++ b/runtime/doc/help.txt
@@ -137,7 +137,7 @@ Special issues ~
Programming language support ~
|indent.txt| automatic indenting for C and other languages
-|lsp.txt| Language Server Protocol (LSP)
+|lsp.txt| Language Server Protocol (LSP)
|syntax.txt| syntax highlighting
|filetype.txt| settings done specifically for a type of file
|quickfix.txt| commands for a quick edit-compile-fix cycle
@@ -168,9 +168,9 @@ Versions ~
|vi_diff.txt| Main differences between Vim and Vi
*standard-plugin-list*
Standard plugins ~
+|matchit.txt| Extended |%| matching
|pi_gzip.txt| Reading and writing compressed files
|pi_health.txt| Healthcheck framework
-|pi_matchit.txt| Extended |%| matching
|pi_msgpack.txt| msgpack utilities
|pi_netrw.txt| Reading and writing files over a network
|pi_paren.txt| Highlight matching parens
diff --git a/runtime/doc/if_perl.txt b/runtime/doc/if_perl.txt
new file mode 100644
index 0000000000..f1d07ddb20
--- /dev/null
+++ b/runtime/doc/if_perl.txt
@@ -0,0 +1,268 @@
+*if_perl.txt* Nvim
+
+
+ VIM REFERENCE MANUAL by Jacques Germishuys
+
+The perl Interface to Vim *if_perl* *perl*
+
+See |provider-perl| for more information.
+
+ Type |gO| to see the table of contents.
+
+==============================================================================
+1. Commands *perl-commands*
+
+ *:perl*
+:[range]perl {stmt}
+ Execute perl statement {stmt}. The current package is
+ "main". A simple check if the `:perl` command is
+ working: >
+ :perl print "Hello"
+
+:[range]perl << [endmarker]
+{script}
+{endmarker}
+ Execute perl script {script}. Useful for including
+ perl code in Vim scripts. Requires perl, see
+ |script-here|.
+
+The {endmarker} below the {script} must NOT be preceded by any white space.
+
+If [endmarker] is omitted from after the "<<", a dot '.' must be used after
+{script}, like for the |:append| and |:insert| commands.
+
+Example: >
+ function! MyVimMethod()
+ perl << EOF
+ sub my_vim_method
+ {
+ print "Hello World!\n";
+ }
+ EOF
+ endfunction
+
+To see what version of perl you have: >
+
+ :perl print $^V
+<
+ *:perldo*
+:[range]perldo {cmd} Execute perl command {cmd} for each line in the[range],
+ with $_ being set to the test of each line in turn,
+ without a trailing <EOL>. In addition to $_, $line and
+ $linenr is also set to the line content and line number
+ respectively. Setting $_ will change the text, but note
+ that it is not possible to add or delete lines using
+ this command.
+ The default for [range] is the whole file: "1,$".
+
+Examples:
+>
+ :perldo $_ = reverse($_);
+ :perldo $_ = "".$linenr." => $line";
+
+One can use `:perldo` in conjunction with `:perl` to filter a range using
+perl. For example: >
+
+ :perl << EOF
+ sub perl_vim_string_replace
+ {
+ my $line = shift;
+ my $needle = $vim->eval('@a');
+ my $replacement = $vim->eval('@b');
+ $line =~ s/$needle/$replacement/g;
+ return $line;
+ }
+ EOF
+ :let @a='somevalue'
+ :let @b='newvalue'
+ :'<,'>perldo $_ = perl_vim_string_replace($_)
+<
+ *:perlfile*
+:[range]perlfile {file}
+ Execute the perl script in {file}. The whole
+ argument is used as a single file name.
+
+Both of these commands do essentially the same thing - they execute a piece of
+perl code, with the "current range" set to the given line range.
+
+In the case of :perl, the code to execute is in the command-line.
+In the case of :perlfile, the code to execute is the contents of the given file.
+
+perl commands cannot be used in the |sandbox|.
+
+To pass arguments you need to set @ARGV explicitly. Example: >
+
+ :perl @ARGV = ("foo", "bar");
+ :perlfile myscript.pl
+
+Here are some examples *perl-examples* >
+
+ :perl print "Hello"
+ :perl $current->line (uc ($current->line))
+ :perl my $str = $current->buffer->[42]; print "Set \$str to: $str"
+
+Note that changes (such as the "use" statements) persist from one command
+to the next.
+
+==============================================================================
+2. The VIM module *perl-vim*
+
+Perl code gets all of its access to Neovim via the "VIM" module.
+
+Overview >
+ print "Hello" # displays a message
+ VIM::Msg("Hello") # displays a message
+ VIM::SetOption("ai") # sets a vim option
+ $nbuf = VIM::Buffers() # returns the number of buffers
+ @buflist = VIM::Buffers() # returns array of all buffers
+ $mybuf = (VIM::Buffers('a.c'))[0] # returns buffer object for 'a.c'
+ @winlist = VIM::Windows() # returns array of all windows
+ $nwin = VIM::Windows() # returns the number of windows
+ ($success, $v) = VIM::Eval('&path') # $v: option 'path', $success: 1
+ ($success, $v) = VIM::Eval('&xyz') # $v: '' and $success: 0
+ $v = VIM::Eval('expand("<cfile>")') # expands <cfile>
+ $curwin->SetHeight(10) # sets the window height
+ @pos = $curwin->Cursor() # returns (row, col) array
+ @pos = (10, 10)
+ $curwin->Cursor(@pos) # sets cursor to @pos
+ $curwin->Cursor(10,10) # sets cursor to row 10 col 10
+ $mybuf = $curwin->Buffer() # returns the buffer object for window
+ $curbuf->Name() # returns buffer name
+ $curbuf->Number() # returns buffer number
+ $curbuf->Count() # returns the number of lines
+ $l = $curbuf->Get(10) # returns line 10
+ @l = $curbuf->Get(1 .. 5) # returns lines 1 through 5
+ $curbuf->Delete(10) # deletes line 10
+ $curbuf->Delete(10, 20) # delete lines 10 through 20
+ $curbuf->Append(10, "Line") # appends a line
+ $curbuf->Append(10, "L1", "L2", "L3") # appends 3 lines
+ @l = ("L1", "L2", "L3")
+ $curbuf->Append(10, @l) # appends L1, L2 and L3
+ $curbuf->Set(10, "Line") # replaces line 10
+ $curbuf->Set(10, "Line1", "Line2") # replaces lines 10 and 11
+ $curbuf->Set(10, @l) # replaces 3 lines
+
+Module Functions:
+
+ *perl-Msg*
+VIM::Msg({msg})
+ Displays the message {msg}.
+
+ *perl-SetOption*
+VIM::SetOption({arg}) Sets a vim option. {arg} can be any argument that the
+ ":set" command accepts. Note that this means that no
+ spaces are allowed in the argument! See |:set|.
+
+ *perl-Buffers*
+VIM::Buffers([{bn}...]) With no arguments, returns a list of all the buffers
+ in an array context or returns the number of buffers
+ in a scalar context. For a list of buffer names or
+ numbers {bn}, returns a list of the buffers matching
+ {bn}, using the same rules as Vim's internal
+ |bufname()| function.
+ WARNING: the list becomes invalid when |:bwipe| is
+ used.
+
+ *perl-Windows*
+VIM::Windows([{wn}...]) With no arguments, returns a list of all the windows
+ in an array context or returns the number of windows
+ in a scalar context. For a list of window numbers
+ {wn}, returns a list of the windows with those
+ numbers.
+ WARNING: the list becomes invalid when a window is
+ closed.
+
+ *perl-DoCommand*
+VIM::DoCommand({cmd}) Executes Ex command {cmd}.
+
+ *perl-Eval*
+VIM::Eval({expr}) Evaluates {expr} and returns (success, value) in list
+ context or just value in scalar context.
+ success=1 indicates that val contains the value of
+ {expr}; success=0 indicates a failure to evaluate
+ the expression. '@x' returns the contents of register
+ x, '&x' returns the value of option x, 'x' returns the
+ value of internal |variables| x, and '$x' is equivalent
+ to perl's $ENV{x}. All |functions| accessible from
+ the command-line are valid for {expr}.
+ A |List| is turned into a string by joining the items
+ and inserting line breaks.
+
+==============================================================================
+3. VIM::Buffer objects *perl-buffer*
+
+Methods:
+
+ *perl-Buffer-Name*
+Name() Returns the filename for the Buffer.
+
+ *perl-Buffer-Number*
+Number() Returns the number of the Buffer.
+
+ *perl-Buffer-Count*
+Count() Returns the number of lines in the Buffer.
+
+ *perl-Buffer-Get*
+Get({lnum}, {lnum}?, ...)
+ Returns a text string of line {lnum} in the Buffer
+ for each {lnum} specified. An array can be passed
+ with a list of {lnum}'s specified.
+
+ *perl-Buffer-Delete*
+Delete({lnum}, {lnum}?)
+ Deletes line {lnum} in the Buffer. With the second
+ {lnum}, deletes the range of lines from the first
+ {lnum} to the second {lnum}.
+
+ *perl-Buffer-Append*
+Append({lnum}, {line}, {line}?, ...)
+ Appends each {line} string after Buffer line {lnum}.
+ The list of {line}s can be an array.
+
+ *perl-Buffer-Set*
+Set({lnum}, {line}, {line}?, ...)
+ Replaces one or more Buffer lines with specified
+ {lines}s, starting at Buffer line {lnum}. The list of
+ {line}s can be an array. If the arguments are
+ invalid, replacement does not occur.
+
+==============================================================================
+4. VIM::Window objects *perl-window*
+
+Methods:
+ *perl-Window-SetHeight*
+SetHeight({height})
+ Sets the Window height to {height}, within screen
+ limits.
+
+ *perl-Window-GetCursor*
+Cursor({row}?, {col}?)
+ With no arguments, returns a (row, col) array for the
+ current cursor position in the Window. With {row} and
+ {col} arguments, sets the Window's cursor position to
+ {row} and {col}. Note that {col} is numbered from 0,
+ Perl-fashion, and thus is one less than the value in
+ Vim's ruler.
+
+Buffer() *perl-Window-Buffer*
+ Returns the Buffer object corresponding to the given
+ Window.
+
+==============================================================================
+5. Lexical variables *perl-globals*
+
+There are multiple lexical variables.
+
+$curwin The current Window object.
+$curbuf The current Buffer object.
+$vim A Neovim::Ext object.
+$nvim The same as $nvim.
+$current A Neovim::Ext::Current object.
+
+These are also available via the "main" package:
+
+$main::curwin The current Window object.
+$main::curbuf The current Buffer object.
+
+==============================================================================
+ vim:tw=78:ts=8:noet:ft=help:norl:
diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt
index bdab10c0e4..afcacad460 100644
--- a/runtime/doc/index.txt
+++ b/runtime/doc/index.txt
@@ -1441,6 +1441,9 @@ tag command action ~
|:packloadall| :packl[oadall] load all packages under 'packpath'
|:pclose| :pc[lose] close preview window
|:pedit| :ped[it] edit file in the preview window
+|:perl| :perl execute perl command
+|:perldo| :perldo execute perl command for each line
+|:perfile| :perlfile execute perl script file
|:print| :p[rint] print lines
|:profdel| :profd[el] stop profiling a function or script
|:profile| :prof[ile] profiling functions and scripts
diff --git a/runtime/doc/intro.txt b/runtime/doc/intro.txt
index 3c3753df78..59b1f44e39 100644
--- a/runtime/doc/intro.txt
+++ b/runtime/doc/intro.txt
@@ -4,21 +4,15 @@
NVIM REFERENCE MANUAL
-Introduction to Vim *ref* *reference*
+Nvim *ref* *reference*
Type |gO| to see the table of contents.
==============================================================================
Introduction *intro*
-Vim stands for Vi IMproved. It used to be Vi IMitation, but there are so many
-improvements that a name change was appropriate. Vim is a text editor which
-includes almost all the commands from the Unix program "Vi" and a lot of new
-ones. It is very useful for editing programs and other plain text.
- All commands are given with the keyboard. This has the advantage that you
-can keep your fingers on the keyboard and your eyes on the screen. For those
-who want it, there is mouse support and a GUI version with scrollbars and
-menus (see |gui.txt|).
+Vim is a text editor which includes most commands from the Unix program "Vi"
+and many new ones.
An overview of this manual can be found in the file "help.txt", |help.txt|.
It can be accessed from within Vim with the <Help> or <F1> key and with the
@@ -28,16 +22,15 @@ is not located in the default place. You can jump to subjects like with tags:
Use CTRL-] to jump to a subject under the cursor, use CTRL-T to jump back.
*pronounce*
-Vim is pronounced as one word, like Jim. Nvim is pronounced as N-vim, or,
-continuing with the Jim simile, N-Jim, which sounds like Ninja.
+Vim is pronounced as one word, like Jim. So Nvim is N-Jim, which sounds like
+"Ninja". Starting Nvim is like performing a roundhouse kick.
-This manual is a reference for all the Vim commands and options. This is not
-an introduction to the use of Vi or Vim, it gets a bit complicated here and
-there. For beginners, there is a hands-on |tutor|. To learn using Vim, read
-the user manual |usr_toc.txt|.
+This manual is a reference for all Nvim editor and API features. It is not an
+introduction; instead for beginners, there is a hands-on |tutor| and a user
+manual |usr_toc.txt|.
*book*
-There are many books on Vi and Vim. We recommend these books:
+There are many books on Vi and Vim. We recommend:
"Practical Vim" by Drew Neil
"Modern Vim" by Drew Neil
@@ -48,7 +41,7 @@ tasks with Vim. "Modern Vim" explores new features in Nvim and Vim 8.
"Vim - Vi Improved" by Steve Oualline
-This is the first book dedicated to Vim. Parts of it were included in the
+This was the first book dedicated to Vim. Parts of it were included in the
user manual. |frombook| ISBN: 0735710015
For more information try one of these:
https://iccf-holland.org/click5.html
@@ -63,11 +56,9 @@ Nvim on the interwebs *internet*
Nvim FAQ: https://github.com/neovim/neovim/wiki/FAQ
Downloads: https://github.com/neovim/neovim/releases
Vim FAQ: https://vimhelp.appspot.com/vim_faq.txt.html
- Vim home page: https://www.vim.org/
- *bugs* *bug-report* *bugreport.vim* *feature-request*
-
+ *bugs* *bug-report*
Report bugs and request features here:
https://github.com/neovim/neovim/issues
@@ -97,7 +88,7 @@ Neovim development is funded separately from Vim:
https://neovim.io/#sponsor
==============================================================================
-Credits *credits* *author* *Bram* *Moolenaar*
+Credits *credits*
Most of Vim was written by Bram Moolenaar <Bram@vim.org>.
@@ -185,25 +176,21 @@ the ideas from all these people: They keep Vim alive!
*love* *peace* *friendship* *gross-national-happiness*
-In this documentation there are several references to other versions of Vi:
+Documentation may refer to other versions of Vi:
*Vi* *vi*
Vi "the original". Without further remarks this is the version
of Vi that appeared in Sun OS 4.x. ":version" returns
- "Version 3.7, 6/7/85". Sometimes other versions are referred
- to. Only runs under Unix. Source code only available with a
- license.
+ "Version 3.7, 6/7/85". Source code only available with a license.
*Nvi*
Nvi The "New" Vi. The version of Vi that comes with BSD 4.4 and FreeBSD.
Very good compatibility with the original Vi, with a few extensions.
The version used is 1.79. ":version" returns "Version 1.79
- (10/23/96)". There has been no release the last few years, although
- there is a development version 1.81.
- Source code is freely available.
+ (10/23/96)". Source code is freely available.
*Elvis*
Elvis Another Vi clone, made by Steve Kirkendall. Very compact but isn't
- as flexible as Vim.
- The version used is 2.1. It is still being developed. Source code is
- freely available.
+ as flexible as Vim. Source code is freely available.
+
+Vim Nvim is based on Vim. https://www.vim.org/
==============================================================================
Notation *notation*
@@ -387,37 +374,24 @@ notation meaning equivalent decimal value(s) ~
<D-…> command-key or "super" key *<D-*
-----------------------------------------------------------------------
-Note: The shifted cursor keys, the help key, and the undo key are only
-available on a few terminals.
-
-Note: There are two codes for the delete key. 127 is the decimal ASCII value
-for the delete key, which is always recognized. Some delete keys send another
-value, in which case this value is obtained from the |terminfo| entry "key_dc".
-Both values have the same effect.
+Note:
-Note: The keypad keys are used in the same way as the corresponding "normal"
-keys. For example, <kHome> has the same effect as <Home>. If a keypad key
-sends the same raw key code as its non-keypad equivalent, it will be
-recognized as the non-keypad code. For example, when <kHome> sends the same
-code as <Home>, when pressing <kHome> Vim will think <Home> was pressed.
-Mapping <kHome> will not work then.
-
-Note: If numlock is on, the |TUI| receives plain ASCII values, so
-mappings to <k0> - <k9> and <kPoint> will not work.
-
-Note: Nvim supports mapping multibyte chars with modifiers such as `<M-ä>`.
-Which combinations actually are usable depends on the terminal emulator or GUI.
+- Availability of some keys (<Help>, <S-Right>, …) depends on the UI or host
+ terminal.
+- If numlock is on the |TUI| receives plain ASCII values, so mapping <k0>,
+ <k1>, ..., <k9> and <kPoint> will not work.
+- Nvim supports mapping multibyte chars with modifiers such as `<M-ä>`. Which
+ combinations actually work depends on the the UI or host terminal.
*<>*
Examples are often given in the <> notation. Sometimes this is just to make
clear what you need to type, but often it can be typed literally, e.g., with
the ":map" command. The rules are:
- 1. Any printable characters are typed directly, except backslash and '<'
- 2. A backslash is represented with "\\", double backslash, or "<Bslash>".
- 3. A real '<' is represented with "\<" or "<lt>". When there is no
- confusion possible, a '<' can be used directly.
- 4. "<key>" means the special key typed. This is the notation explained in
- the table above. A few examples:
+ 1. Printable characters are typed directly, except backslash and "<"
+ 2. Backslash is represented with "\\", double backslash, or "<Bslash>".
+ 3. Literal "<" is represented with "\<" or "<lt>". When there is no
+ confusion possible, "<" can be used directly.
+ 4. "<key>" means the special key typed (see the table above). Examples:
<Esc> Escape key
<C-G> CTRL-G
<Up> cursor up key
@@ -437,11 +411,6 @@ one always works.
To get a literal "<lt>" in a mapping: >
:map <C-L> <lt>lt>
-For mapping, abbreviation and menu commands you can then copy-paste the
-examples and use them directly. Or type them literally, including the '<' and
-'>' characters. This does NOT work for other commands, like ":set" and
-":autocmd"!
-
==============================================================================
Modes, introduction *vim-modes-intro* *vim-modes*
@@ -599,7 +568,7 @@ Q or gQ Switch to Ex mode. This is like typing ":" commands
Use the ":vi" command |:visual| to exit this mode.
==============================================================================
-The window contents *window-contents*
+Window contents *window-contents*
In Normal mode and Insert/Replace mode the screen window will show the current
contents of the buffer: What You See Is What You Get. There are two
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index 9db478ac41..0300ba55b3 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -4,7 +4,7 @@
NVIM REFERENCE MANUAL
-LSP client/framework *lsp*
+LSP client/framework *lsp* *LSP*
Nvim supports the Language Server Protocol (LSP), which means it acts as
a client to LSP servers and includes a Lua framework `vim.lsp` for building
@@ -419,7 +419,7 @@ client() *vim.lsp.client*
|vim.lsp.get_active_clients()|.
• Methods:
- • request(method, params, [callback], bufnr) Send a request
+ • request(method, params, [callback], bufnr) Sends a request
to the server. This is a thin wrapper around
{client.rpc.request} with some additional checking. If
{callback} is not specified, it will use
@@ -431,13 +431,13 @@ client() *vim.lsp.client*
If {status} is `true` , the function returns {request_id}
as the second result. You can use this with
`client.cancel_request(request_id)` to cancel the request.
- • notify(method, params) Send a notification to an LSP
+ • notify(method, params) Sends a notification to an LSP
server. Returns: a boolean to indicate if the notification
was successful. If it is false, then it will always be
false (the client has shutdown).
• cancel_request(id) Cancels a request with a given request
id. Returns: same as `notify()` .
- • stop([force]) Stop a client, optionally with force. By
+ • stop([force]) Stops a client, optionally with force. By
default, it will just ask the server to shutdown without
force. If you request to stop a client which has
previously been requested to shutdown, it will
@@ -647,7 +647,7 @@ stop_client({client_id}, {force}) *vim.lsp.stop_client()*
object. To stop all clients:
>
- vim.lsp.stop_client(lsp.get_active_clients())
+ vim.lsp.stop_client(vim.lsp.get_active_clients())
<
By default asks the server to shutdown, unless stop was
@@ -688,7 +688,7 @@ clear_references() *vim.lsp.buf.clear_references()*
Removes document highlights from current buffer.
code_action({context}) *vim.lsp.buf.code_action()*
- Select a code action from the input list that is available at
+ Selects a code action from the input list that is available at
the current cursor position.
Parameters: ~
@@ -733,7 +733,7 @@ document_symbol() *vim.lsp.buf.document_symbol()*
window.
execute_command({command}) *vim.lsp.buf.execute_command()*
- Execute an LSP server command.
+ Executes an LSP server command.
Parameters: ~
{command} A valid `ExecuteCommandParams` object
@@ -755,7 +755,7 @@ formatting({options}) *vim.lsp.buf.formatting()*
*vim.lsp.buf.formatting_sync()*
formatting_sync({options}, {timeout_ms})
- Perform |vim.lsp.buf.formatting()| synchronously.
+ Performs |vim.lsp.buf.formatting()| synchronously.
Useful for running on save, to make sure buffer is formatted
prior to being saved. {timeout_ms} is passed on to
@@ -790,15 +790,13 @@ outgoing_calls() *vim.lsp.buf.outgoing_calls()*
*vim.lsp.buf.range_formatting()*
range_formatting({options}, {start_pos}, {end_pos})
- Perform |vim.lsp.buf.formatting()| synchronously.
-
- Useful for running on save, to make sure buffer is formatted
- prior to being saved. {timeout_ms} is passed on to
- |vim.lsp.buf_request_sync()|.
+ Formats a given range.
Parameters: ~
- {options} Table with valid `FormattingOptions` entries
- {timeout_ms} (number) Request timeout
+ {options} Table with valid `FormattingOptions` entries.
+ {start_pos} ({number, number}, optional) mark-indexed
+ position. Defaults to the end of the last
+ visual selection.
references({context}) *vim.lsp.buf.references()*
Lists all the references to the symbol under the cursor in the
@@ -918,7 +916,7 @@ rpc_response_error({code}, {message}, {data})
*vim.lsp.rpc.start()*
start({cmd}, {cmd_args}, {handlers}, {extra_spawn_params})
- Start an LSP server process and create an LSP RPC client
+ Starts an LSP server process and create an LSP RPC client
object to interact with it.
Parameters: ~
@@ -959,9 +957,6 @@ Lua module: vim.lsp.util *lsp-util*
*vim.lsp.util.apply_text_document_edit()*
apply_text_document_edit({text_document_edit})
- Apply a `TextDocumentEdit` , which is a list of changes to a
- single document.
-
Parameters: ~
{text_document_edit} (table) a `TextDocumentEdit` object
@@ -984,7 +979,7 @@ apply_workspace_edit({workspace_edit})
{workspace_edit} (table) `WorkspaceEdit`
buf_clear_diagnostics({bufnr}) *vim.lsp.util.buf_clear_diagnostics()*
- Clear diagnostics for a buffer.
+ Clears diagnostics for a buffer.
Parameters: ~
{bufnr} (number) buffer id
@@ -1037,7 +1032,7 @@ buf_diagnostics_save_positions({bufnr}, {diagnostics})
*vim.lsp.util.buf_diagnostics_signs()*
buf_diagnostics_signs({bufnr}, {diagnostics})
- Place signs for each diagnostic in the sign column.
+ Places signs for each diagnostic in the sign column.
Sign characters can be customized with the following commands:
>
@@ -1058,7 +1053,7 @@ buf_diagnostics_underline({bufnr}, {diagnostics})
*vim.lsp.util.buf_diagnostics_virtual_text()*
buf_diagnostics_virtual_text({bufnr}, {diagnostics})
- Given a list of diagnostics, set the corresponding virtual
+ Given a list of diagnostics, sets the corresponding virtual
text for a buffer.
Parameters: ~
@@ -1067,7 +1062,7 @@ buf_diagnostics_virtual_text({bufnr}, {diagnostics})
*vim.lsp.util.buf_highlight_references()*
buf_highlight_references({bufnr}, {references})
- Show a list of document highlights for a certain buffer.
+ Shows a list of document highlights for a certain buffer.
Parameters: ~
{bufnr} buffer id
@@ -1089,7 +1084,7 @@ character_offset({buf}, {row}, {col}) *vim.lsp.util.character_offset()*
*vim.lsp.util.close_preview_autocmd()*
close_preview_autocmd({events}, {winnr})
- Create autocommands to close a preview window when events
+ Creates autocommands to close a preview window when events
happen.
Parameters: ~
@@ -1101,7 +1096,7 @@ close_preview_autocmd({events}, {winnr})
*vim.lsp.util.convert_input_to_markdown_lines()*
convert_input_to_markdown_lines({input}, {contents})
- Convert any of `MarkedString` | `MarkedString[]` |
+ Converts any of `MarkedString` | `MarkedString[]` |
`MarkupContent` into a list of lines containing valid
markdown. Useful to populate the hover window for
`textDocument/hover` , for parsing the result of
@@ -1121,7 +1116,7 @@ convert_input_to_markdown_lines({input}, {contents})
*vim.lsp.util.convert_signature_help_to_markdown_lines()*
convert_signature_help_to_markdown_lines({signature_help})
- Convert `textDocument/SignatureHelp` response to markdown
+ Converts `textDocument/SignatureHelp` response to markdown
lines.
Parameters: ~
@@ -1163,11 +1158,11 @@ extract_completion_items({result})
*vim.lsp.util.fancy_floating_markdown()*
fancy_floating_markdown({contents}, {opts})
- Convert markdown into syntax highlighted regions by stripping
+ Converts markdown into syntax highlighted regions by stripping
the code blocks and converting them into highlighted code.
This will by default insert a blank line separator after those
code block regions to improve readability. The result is shown
- in a floating preview
+ in a floating preview.
Parameters: ~
{contents} table of lines to show in window
@@ -1193,9 +1188,9 @@ focusable_float({unique_name}, {fn}) *vim.lsp.util.focusable_float()*
*vim.lsp.util.focusable_preview()*
focusable_preview({unique_name}, {fn})
- Focus/unfocus the floating preview window associated with the
- current buffer via the window variable `unique_name` . If no
- such preview window exists, make a new one.
+ Focuses/unfocuses the floating preview window associated with
+ the current buffer via the window variable `unique_name` . If
+ no such preview window exists, makes a new one.
Parameters: ~
{unique_name} (string) Window variable
@@ -1206,7 +1201,7 @@ focusable_preview({unique_name}, {fn})
be created
get_effective_tabstop({bufnr}) *vim.lsp.util.get_effective_tabstop()*
- Get visual width of tabstop.
+ Returns visual width of tabstop.
Parameters: ~
{bufnr} (optional, number): Buffer handle, defaults to
@@ -1219,7 +1214,7 @@ get_effective_tabstop({bufnr}) *vim.lsp.util.get_effective_tabstop()*
|softtabstop|
get_line_diagnostics() *vim.lsp.util.get_line_diagnostics()*
- Get list of diagnostics for the current line.
+ Gets list of diagnostics for the current line.
Return: ~
(table) list of `Diagnostic` tables
@@ -1229,7 +1224,7 @@ get_line_diagnostics() *vim.lsp.util.get_line_diagnostics()*
*vim.lsp.util.get_severity_highlight_name()*
get_severity_highlight_name({severity})
- Get the name of a severity's highlight group.
+ Gets the name of a severity's highlight group.
Parameters: ~
{severity} A member of
@@ -1319,7 +1314,7 @@ make_text_document_params() *vim.lsp.util.make_text_document_params()*
*vim.lsp.util.open_floating_preview()*
open_floating_preview({contents}, {filetype}, {opts})
- Show contents in a floating window
+ Shows contents in a floating window.
Parameters: ~
{contents} table of lines to show in window
@@ -1340,7 +1335,7 @@ parse_snippet({input}) *vim.lsp.util.parse_snippet()*
(string) parsed snippet
preview_location({location}) *vim.lsp.util.preview_location()*
- Preview a location in a floating window
+ Previews a location in a floating window
behavior depends on type of location:
• for Location, range is shown (e.g., function definition)
@@ -1355,7 +1350,7 @@ preview_location({location}) *vim.lsp.util.preview_location()*
or nil
set_lines({lines}, {A}, {B}, {new_lines}) *vim.lsp.util.set_lines()*
- Replace text in a range with new text.
+ Replaces text in a range with new text.
CAUTION: Changes in-place!
@@ -1389,7 +1384,7 @@ show_line_diagnostics() *vim.lsp.util.show_line_diagnostics()*
hover window.
symbols_to_items({symbols}, {bufnr}) *vim.lsp.util.symbols_to_items()*
- Convert symbols to quickfix list items
+ Converts symbols to quickfix list items.
Parameters: ~
{symbols} DocumentSymbol[] or SymbolInformation[]
@@ -1414,7 +1409,7 @@ text_document_completion_list_to_complete_items({result}, {prefix})
|complete-items|
trim_empty_lines({lines}) *vim.lsp.util.trim_empty_lines()*
- Remove empty lines from the beginning and end.
+ Removes empty lines from the beginning and end.
Parameters: ~
{lines} (table) list of lines to trim
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index f336ba0c36..509ed7bf2c 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -9,7 +9,7 @@ Lua engine *lua* *Lua*
Type |gO| to see the table of contents.
==============================================================================
-Introduction *lua-intro*
+INTRODUCTION *lua-intro*
The Lua 5.1 language is builtin and always available. Try this command to get
an idea of what lurks beneath: >
@@ -30,7 +30,7 @@ finds and loads Lua modules. The conventions are similar to VimL plugins,
with some extra features. See |lua-require-example| for a walkthrough.
==============================================================================
-Importing Lua modules *lua-require*
+IMPORTING LUA MODULES *lua-require*
*lua-package-path*
Nvim automatically adjusts `package.path` and `package.cpath` according to
@@ -233,7 +233,7 @@ lua/charblob.lua: >
}
==============================================================================
-Commands *lua-commands*
+COMMANDS *lua-commands*
These commands execute a Lua chunk from either the command line (:lua, :luado)
or a file (:luafile) on the given line [range]. As always in Lua, each chunk
@@ -456,7 +456,7 @@ management. Try this command to see available functions: >
:lua print(vim.inspect(vim.loop))
-Reference: http://docs.libuv.org
+Reference: https://github.com/luvit/luv/blob/master/docs.md
Examples: https://github.com/luvit/luv/tree/master/examples
*E5560* *lua-loop-callbacks*
@@ -622,6 +622,15 @@ Node methods *lua-treesitter-node*
tsnode:parent() *tsnode:parent()*
Get the node's immediate parent.
+tsnode:iter_children() *tsnode:iter_children()*
+ Iterates over all the direct children of {tsnode}, regardless of
+ wether they are named or not.
+ Returns the child node plus the eventual field name corresponding to
+ this child node.
+
+tsnode:field({name}) *tsnode:field()*
+ Returns a table of the nodes corresponding to the {name} field.
+
tsnode:child_count() *tsnode:child_count()*
Get the node's number of children.
@@ -747,14 +756,15 @@ Here is a list of built-in predicates :
((node1) @left (node2) @right (#eq? @left @right))
<
`match?` *ts-predicate-match?*
- This will match if the provived lua regex matches the text
+ `vim-match?` *ts-predicate-vim-match?*
+ This will match if the provived vim regex matches the text
corresponding to a node : >
((idenfitier) @constant (#match? @constant "^[A-Z_]+$"))
< Note: the `^` and `$` anchors will respectively match the
start and end of the node's text.
- `vim-match?` *ts-predicate-vim-match?*
- This will match the same way than |match?| but using vim
+ `lua-match?` *ts-predicate-lua-match?*
+ This will match the same way than |match?| but using lua
regexes.
`contains?` *ts-predicate-contains?*
@@ -773,6 +783,11 @@ vim.treesitter.query.add_predicate({name}, {handler})
This adds a predicate with the name {name} to be used in queries.
{handler} should be a function whose signature will be : >
handler(match, pattern, bufnr, predicate)
+<
+ *vim.treesitter.query.list_predicates()*
+vim.treesitter.query.list_predicates()
+
+This lists the currently available predicates to use in queries.
Treesitter syntax highlighting (WIP) *lua-treesitter-highlight*
@@ -891,11 +906,6 @@ vim.api.{func}({...}) *vim.api*
Example: call the "nvim_get_current_line()" API function: >
print(tostring(vim.api.nvim_get_current_line()))
-vim.call({func}, {...}) *vim.call()*
- Invokes |vim-function| or |user-function| {func} with arguments {...}.
- See also |vim.fn|. Equivalent to: >
- vim.fn[func]({...})
-
vim.in_fast_event() *vim.in_fast_event()*
Returns true if the code is executing as part of a "fast" event
handler, where most of the API is disabled. These are low-level events
@@ -1055,22 +1065,6 @@ vim.wait({time}, {callback} [, {interval}]) *vim.wait()*
end
<
-vim.fn.{func}({...}) *vim.fn*
- Invokes |vim-function| or |user-function| {func} with arguments {...}.
- To call autoload functions, use the syntax: >
- vim.fn['some#function']({...})
-<
- Unlike vim.api.|nvim_call_function| this converts directly between Vim
- objects and Lua objects. If the Vim function returns a float, it will
- be represented directly as a Lua number. Empty lists and dictionaries
- both are represented by an empty table.
-
- Note: |v:null| values as part of the return value is represented as
- |vim.NIL| special value
-
- Note: vim.fn keys are generated lazily, thus `pairs(vim.fn)` only
- enumerates functions that were called at least once.
-
vim.type_idx *vim.type_idx*
Type index for use in |lua-special-tbl|. Specifying one of the
values from |vim.types| allows typing the empty table (it is
@@ -1106,64 +1100,103 @@ vim.types *vim.types*
`vim.types.dictionary` will not change or that `vim.types` table will
only contain values for these three types.
-==============================================================================
-Vim Internal Variables *lua-vim-internal-variables*
-
-Built-in Vim dictionaries can be accessed and set idiomatically in Lua by each
-of the following tables.
-
-To set a value: >
-
- vim.g.my_global_variable = 5
-<
+------------------------------------------------------------------------------
+LUA-VIMSCRIPT BRIDGE *lua-vimscript*
-To read a value: >
+Nvim Lua provides an interface to Vimscript variables and functions, and
+editor commands and options.
- print(vim.g.my_global_variable)
-<
+vim.call({func}, {...}) *vim.call()*
+ Invokes |vim-function| or |user-function| {func} with arguments {...}.
+ See also |vim.fn|.
+ Equivalent to: >
+ vim.fn[func]({...})
-To delete a value: >
+vim.cmd({cmd}) *vim.cmd()*
+ Invokes an Ex command (the ":" commands, Vimscript statements).
+ See also |ex-cmd-index|.
+ Example: >
+ vim.cmd('echo 42')
- vim.g.my_global_variable = nil
+vim.fn.{func}({...}) *vim.fn*
+ Invokes |vim-function| or |user-function| {func} with arguments {...}.
+ To call autoload functions, use the syntax: >
+ vim.fn['some#function']({...})
<
+ Unlike vim.api.|nvim_call_function| this converts directly between Vim
+ objects and Lua objects. If the Vim function returns a float, it will
+ be represented directly as a Lua number. Empty lists and dictionaries
+ both are represented by an empty table.
-vim.g *vim.g*
- Table with values from |g:|
- Keys with no values set will result in `nil`.
-
-vim.b *vim.b*
- Gets a buffer-scoped (b:) variable for the current buffer.
- Keys with no values set will result in `nil`.
-
-vim.w *vim.w*
- Gets a window-scoped (w:) variable for the current window.
- Keys with no values set will result in `nil`.
+ Note: |v:null| values as part of the return value is represented as
+ |vim.NIL| special value
-vim.t *vim.t*
- Gets a tabpage-scoped (t:) variable for the current table.
- Keys with no values set will result in `nil`.
+ Note: vim.fn keys are generated lazily, thus `pairs(vim.fn)` only
+ enumerates functions that were called at least once.
-vim.v *vim.v*
- Gets a v: variable.
- Keys with no values set will result in `nil`.
+ *lua-vim-variables*
+The Vim editor global dictionaries |g:| |w:| |b:| |t:| |v:| can be accessed
+from Lua conveniently and idiomatically by referencing the `vim.*` Lua tables
+described below. In this way you can easily read and modify global Vimscript
+variables from Lua.
-Vim Internal Options *lua-vim-internal-options*
+Example: >
-Read, set and clear vim |options| in Lua by each of the following tables.
+ vim.g.foo = 5 -- Set the g:foo Vimscript variable.
+ print(vim.g.foo) -- Get and print the g:foo Vimscript variable.
+ vim.g.foo = nil -- Delete (:unlet) the Vimscript variable.
+
+vim.g *vim.g*
+ Global (|g:|) editor variables.
+ Key with no value returns `nil`.
+
+vim.b *vim.b*
+ Buffer-scoped (|b:|) variables for the current buffer.
+ Invalid or unset key returns `nil`.
+
+vim.w *vim.w*
+ Window-scoped (|w:|) variables for the current window.
+ Invalid or unset key returns `nil`.
+
+vim.t *vim.t*
+ Tabpage-scoped (|t:|) variables for the current tabpage.
+ Invalid or unset key returns `nil`.
+
+vim.v *vim.v*
+ |v:| variables.
+ Invalid or unset key returns `nil`.
+
+vim.env *vim.env*
+ Environment variables defined in the editor session.
+ See |expand-env| and |:let-environment| for the Vimscript behavior.
+ Invalid or unset key returns `nil`.
+ Example: >
+ vim.env.FOO = 'bar'
+ print(vim.env.TERM)
+<
+ *lua-vim-options*
+From Lua you can work with editor |options| by reading and setting items in
+these Lua tables:
-vim.o *vim.o*
- Table with values from |options|
- Invalid keys will result in an error.
+vim.o *vim.o*
+ Get or set editor options, like |:set|. Invalid key is an error.
+ Example: >
+ vim.o.cmdheight = 4
+ print(vim.o.columns)
-vim.bo *vim.bo*
- Gets a buffer-scoped option for the current buffer.
- Invalid keys will result in an error.
+vim.bo *vim.bo*
+ Get or set buffer-scoped |local-options|. Invalid key is an error.
+ Example: >
+ vim.bo.buflisted = true
+ print(vim.bo.comments)
-vim.wo *vim.wo*
- Gets a window-scoped option for the current window.
- Invalid keys will result in an error.
+vim.wo *vim.wo*
+ Get or set window-scoped |local-options|. Invalid key is an error.
+ Example: >
+ vim.wo.cursorcolumn = true
+ print(vim.wo.foldmarker)
==============================================================================
@@ -1419,17 +1452,21 @@ tbl_flatten({t}) *vim.tbl_flatten()*
Fromhttps://github.com/premake/premake-core/blob/master/src/base/table.lua
tbl_isempty({t}) *vim.tbl_isempty()*
+ Checks if a table is empty.
+
+ Parameters: ~
+ {t} Table to check
+
See also: ~
- Fromhttps://github.com/premake/premake-core/blob/master/src/base/table.lua@paramt Table to check
+ https://github.com/premake/premake-core/blob/master/src/base/table.lua
tbl_islist({t}) *vim.tbl_islist()*
- Determine whether a Lua table can be treated as an array.
+ Tests if a Lua table can be treated as an array.
- An empty table `{}` will default to being treated as an array.
- Use `vim.emtpy_dict()` to create a table treated as an empty
- dict. Empty tables returned by `rpcrequest()` and `vim.fn`
- functions can be checked using this function whether they
- represent empty API arrays and vimL lists.
+ Empty table `{}` is assumed to be an array, unless it was
+ created by |vim.empty_dict()| or returned as a dict-like |API|
+ or Vimscript result, for example from |rpcrequest()| or
+ |vim.fn|.
Parameters: ~
{t} Table
diff --git a/runtime/doc/mbyte.txt b/runtime/doc/mbyte.txt
index 127c46c27d..a6410a09cb 100644
--- a/runtime/doc/mbyte.txt
+++ b/runtime/doc/mbyte.txt
@@ -71,24 +71,15 @@ If you are working in a terminal (emulator) you must make sure it accepts
UTF-8, the encoding which Vim is working with. Otherwise only ASCII can
be displayed and edited correctly.
-For the GUI you must select fonts that work with UTF-8. This
-is the difficult part. It depends on the system you are using, the locale and
-a few other things.
-
-For X11 you can set the 'guifontset' option to a list of fonts that together
-cover the characters that are used. Example for Korean: >
-
- :set guifontset=k12,r12
-
-Alternatively, you can set 'guifont' and 'guifontwide'. 'guifont' is used for
-the single-width characters, 'guifontwide' for the double-width characters.
-Thus the 'guifontwide' font must be exactly twice as wide as 'guifont'.
-Example for UTF-8: >
+For the GUI you must select fonts that work with UTF-8. You can set 'guifont'
+and 'guifontwide'. 'guifont' is used for the single-width characters,
+'guifontwide' for the double-width characters. Thus the 'guifontwide' font
+must be exactly twice as wide as 'guifont'. Example for UTF-8: >
:set guifont=-misc-fixed-medium-r-normal-*-18-120-100-100-c-90-iso10646-1
:set guifontwide=-misc-fixed-medium-r-normal-*-18-120-100-100-c-180-iso10646-1
-You can also set 'guifont' alone, Vim will try to find a matching
+You can also set 'guifont' alone, the Nvim GUI will try to find a matching
'guifontwide' for you.
@@ -267,16 +258,16 @@ Recognized 'fileencoding' values include: *encoding-values*
1 cp1258 Vietnamese
1 cp{number} MS-Windows: any installed single-byte codepage
2 cp932 Japanese (Windows only)
-2 euc-jp Japanese (Unix only)
-2 sjis Japanese (Unix only)
-2 cp949 Korean (Unix and Windows)
-2 euc-kr Korean (Unix only)
+2 euc-jp Japanese
+2 sjis Japanese
+2 cp949 Korean
+2 euc-kr Korean
2 cp936 simplified Chinese (Windows only)
-2 euc-cn simplified Chinese (Unix only)
-2 cp950 traditional Chinese (on Unix alias for big5)
-2 big5 traditional Chinese (on Windows alias for cp950)
-2 euc-tw traditional Chinese (Unix only)
-2 2byte-{name} Unix: any double-byte encoding (Vim specific name)
+2 euc-cn simplified Chinese
+2 cp950 traditional Chinese (alias for big5)
+2 big5 traditional Chinese (alias for cp950)
+2 euc-tw traditional Chinese
+2 2byte-{name} any double-byte encoding (Vim-specific name)
2 cp{number} MS-Windows: any installed double-byte codepage
u utf-8 32 bit UTF-8 encoded Unicode (ISO/IEC 10646-1)
u ucs-2 16 bit UCS-2 encoded Unicode (ISO/IEC 10646-1)
@@ -298,14 +289,14 @@ the same encoding is used and it's called latin1. 'isprint' can be used to
display the characters 0x80 - 0xA0 or not.
Several aliases can be used, they are translated to one of the names above.
-An incomplete list:
+Incomplete list:
1 ansi same as latin1 (obsolete, for backward compatibility)
-2 japan Japanese: on Unix "euc-jp", on MS-Windows cp932
-2 korea Korean: on Unix "euc-kr", on MS-Windows cp949
-2 prc simplified Chinese: on Unix "euc-cn", on MS-Windows cp936
+2 japan Japanese: "euc-jp"
+2 korea Korean: "euc-kr"
+2 prc simplified Chinese: "euc-cn"
2 chinese same as "prc"
-2 taiwan traditional Chinese: on Unix "euc-tw", on MS-Windows cp950
+2 taiwan traditional Chinese: "euc-tw"
u utf8 same as utf-8
u unicode same as ucs-2
u ucs2be same as ucs-2 (big endian)
@@ -394,148 +385,6 @@ conversion needs to be done. These conversions are supported:
request a very large buffer, more than Vim is willing to provide).
Try getting another iconv() implementation.
- *iconv-dynamic*
-On MS-Windows Vim can be compiled with the |+iconv/dyn| feature. This means
-Vim will search for the "iconv.dll" and "libiconv.dll" libraries. When
-neither of them can be found Vim will still work but some conversions won't be
-possible.
-
-==============================================================================
-Fonts on X11 *mbyte-fonts-X11*
-
-Unfortunately, using fonts in X11 is complicated. The name of a single-byte
-font is a long string. For multi-byte fonts we need several of these...
-
-First of all, Vim only accepts fixed-width fonts for displaying text. You
-cannot use proportionally spaced fonts. This excludes many of the available
-(and nicer looking) fonts. However, for menus and tooltips any font can be
-used.
-
-Note that Display and Input are independent. It is possible to see your
-language even though you have no input method for it.
-
-You should get a default font for menus and tooltips that works, but it might
-be ugly. Read the following to find out how to select a better font.
-
-
-X LOGICAL FONT DESCRIPTION (XLFD)
- *XLFD*
-XLFD is the X font name and contains the information about the font size,
-charset, etc. The name is in this format:
-
-FOUNDRY-FAMILY-WEIGHT-SLANT-WIDTH-STYLE-PIXEL-POINT-X-Y-SPACE-AVE-CR-CE
-
-Each field means:
-
-- FOUNDRY: FOUNDRY field. The company that created the font.
-- FAMILY: FAMILY_NAME field. Basic font family name. (helvetica, gothic,
- times, etc)
-- WEIGHT: WEIGHT_NAME field. How thick the letters are. (light, medium,
- bold, etc)
-- SLANT: SLANT field.
- r: Roman (no slant)
- i: Italic
- o: Oblique
- ri: Reverse Italic
- ro: Reverse Oblique
- ot: Other
- number: Scaled font
-- WIDTH: SETWIDTH_NAME field. Width of characters. (normal, condensed,
- narrow, double wide)
-- STYLE: ADD_STYLE_NAME field. Extra info to describe font. (Serif, Sans
- Serif, Informal, Decorated, etc)
-- PIXEL: PIXEL_SIZE field. Height, in pixels, of characters.
-- POINT: POINT_SIZE field. Ten times height of characters in points.
-- X: RESOLUTION_X field. X resolution (dots per inch).
-- Y: RESOLUTION_Y field. Y resolution (dots per inch).
-- SPACE: SPACING field.
- p: Proportional
- m: Monospaced
- c: CharCell
-- AVE: AVERAGE_WIDTH field. Ten times average width in pixels.
-- CR: CHARSET_REGISTRY field. The name of the charset group.
-- CE: CHARSET_ENCODING field. The rest of the charset name. For some
- charsets, such as JIS X 0208, if this field is 0, code points has
- the same value as GL, and GR if 1.
-
-For example, in case of a 16 dots font corresponding to JIS X 0208, it is
-written like:
- -misc-fixed-medium-r-normal--16-110-100-100-c-160-jisx0208.1990-0
-
-
-X FONTSET
- *fontset* *xfontset*
-A single-byte charset is typically associated with one font. For multi-byte
-charsets a combination of fonts is often used. This means that one group of
-characters are used from one font and another group from another font (which
-might be double wide). This collection of fonts is called a fontset.
-
-Which fonts are required in a fontset depends on the current locale. X
-windows maintains a table of which groups of characters are required for a
-locale. You have to specify all the fonts that a locale requires in the
-'guifontset' option.
-
-NOTE: The fontset always uses the current locale, even though 'encoding' may
-be set to use a different charset. In that situation you might want to use
-'guifont' and 'guifontwide' instead of 'guifontset'.
-
-Example:
- |charset| language "groups of characters" ~
- GB2312 Chinese (simplified) ISO-8859-1 and GB 2312
- Big5 Chinese (traditional) ISO-8859-1 and Big5
- CNS-11643 Chinese (traditional) ISO-8859-1, CNS 11643-1 and CNS 11643-2
- EUC-JP Japanese JIS X 0201 and JIS X 0208
- EUC-KR Korean ISO-8859-1 and KS C 5601 (KS X 1001)
-
-You can search for fonts using the xlsfonts command. For example, when you're
-searching for a font for KS C 5601: >
- xlsfonts | grep ksc5601
-
-This is complicated and confusing. You might want to consult the X-Windows
-documentation if there is something you don't understand.
-
- *base_font_name_list*
-When you have found the names of the fonts you want to use, you need to set
-the 'guifontset' option. You specify the list by concatenating the font names
-and putting a comma in between them.
-
-For example, when you use the ja_JP.eucJP locale, this requires JIS X 0201
-and JIS X 0208. You could supply a list of fonts that explicitly specifies
-the charsets, like: >
-
- :set guifontset=-misc-fixed-medium-r-normal--14-130-75-75-c-140-jisx0208.1983-0,
- \-misc-fixed-medium-r-normal--14-130-75-75-c-70-jisx0201.1976-0
-
-Alternatively, you can supply a base font name list that omits the charset
-name, letting X-Windows select font characters required for the locale. For
-example: >
-
- :set guifontset=-misc-fixed-medium-r-normal--14-130-75-75-c-140,
- \-misc-fixed-medium-r-normal--14-130-75-75-c-70
-
-Alternatively, you can supply a single base font name that allows X-Windows to
-select from all available fonts. For example: >
-
- :set guifontset=-misc-fixed-medium-r-normal--14-*
-
-Alternatively, you can specify alias names. See the fonts.alias file in the
-fonts directory (e.g., /usr/X11R6/lib/X11/fonts/). For example: >
-
- :set guifontset=k14,r14
-<
- *E253*
-Note that in East Asian fonts, the standard character cell is square. When
-mixing a Latin font and an East Asian font, the East Asian font width should
-be twice the Latin font width.
-
-If 'guifontset' is not empty, the "font" argument of the |:highlight| command
-is also interpreted as a fontset. For example, you should use for
-highlighting: >
- :hi Comment font=english_font,your_font
-If you use a wrong "font" argument you will get an error message.
-Also make sure that you set 'guifontset' before setting fonts for highlight
-groups.
-
==============================================================================
Input on X11 *mbyte-XIM*
@@ -647,10 +496,6 @@ Note that Display and Input are independent. It is possible to see your
language even though you have no input method for it. But when your Display
method doesn't match your Input method, the text will be displayed wrong.
- Note: You can not use IM unless you specify 'guifontset'.
- Therefore, Latin users, you have to also use 'guifontset'
- if you use IM.
-
To input your language you should run the |IM-server| which supports your
language and |conversion-server| if needed.
@@ -962,9 +807,9 @@ Vim has comprehensive UTF-8 support. It works well in:
- MS-Windows GUI
- several other platforms
-Double-width characters are supported. This works best with 'guifontwide' or
-'guifontset'. When using only 'guifont' the wide characters are drawn in the
-normal width and a space to fill the gap.
+Double-width characters are supported. Works best with 'guifontwide'. When
+using only 'guifont' the wide characters are drawn in the normal width and
+a space to fill the gap.
*bom-bytes*
When reading a file a BOM (Byte Order Mark) can be used to recognize the
@@ -1031,7 +876,6 @@ this:
1. Set 'guifont' and let Vim find a matching 'guifontwide'
2. Set 'guifont' and 'guifontwide'
-3. Set 'guifontset'
See the documentation for each option for details. Example: >
@@ -1077,10 +921,7 @@ not everybody is able to type a composing character.
==============================================================================
Overview of options *mbyte-options*
-These options are relevant for editing multi-byte files. Check the help in
-options.txt for detailed information.
-
-'encoding' Internal text encoding, always "utf-8".
+These options are relevant for editing multi-byte files.
'fileencoding' Encoding of a file. When it's different from "utf-8"
conversion is done when reading or writing the file.
@@ -1096,9 +937,6 @@ options.txt for detailed information.
languages where a sequence of characters can be broken
anywhere.
-'guifontset' The list of font names used for a multi-byte encoding. When
- this option is not empty, it replaces 'guifont'.
-
'keymap' Specify the name of a keyboard mapping.
==============================================================================
diff --git a/runtime/doc/message.txt b/runtime/doc/message.txt
index 43b1eb5e0c..745160da8a 100644
--- a/runtime/doc/message.txt
+++ b/runtime/doc/message.txt
@@ -359,7 +359,7 @@ the other way around. It should be used like this: {foo,bar}. This matches
ml_get: invalid lnum: {number}
This is an internal Vim error. Please try to find out how it can be
-reproduced, and submit a bug report |bugreport.vim|.
+reproduced, and submit a |bug-report|.
*E173* >
{number} more files to edit
diff --git a/runtime/doc/mlang.txt b/runtime/doc/mlang.txt
index 2a10a7051d..5217b2c160 100644
--- a/runtime/doc/mlang.txt
+++ b/runtime/doc/mlang.txt
@@ -124,8 +124,7 @@ maintainer of the translation and ask him to update it. You can find the
name and e-mail address of the translator in
"$VIMRUNTIME/lang/menu_<lang>.vim".
-To set the font (or fontset) to use for the menus, use the |:highlight|
-command. Example: >
+To set the font to use for the menus, use the |:highlight| command. Example: >
:highlight Menu font=k12,r12
diff --git a/runtime/doc/nvim_terminal_emulator.txt b/runtime/doc/nvim_terminal_emulator.txt
index a96d118667..bec2b362ea 100644
--- a/runtime/doc/nvim_terminal_emulator.txt
+++ b/runtime/doc/nvim_terminal_emulator.txt
@@ -92,7 +92,7 @@ Mouse input has the following behavior:
the terminal wont lose focus and the hovered window will be scrolled.
==============================================================================
-Configuration *terminal-configuration*
+Configuration *terminal-config*
Options: 'modified', 'scrollback'
Events: |TermOpen|, |TermEnter|, |TermLeave|, |TermClose|
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 2cfc6e374b..190d6f9229 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -118,8 +118,7 @@ A few special texts:
Option was set with command line argument |-c|, +, |-S| or
|-q|.
Last set from environment variable ~
- Option was set from an environment variable, $VIMINIT,
- $GVIMINIT or $EXINIT.
+ Option was set from $VIMINIT.
Last set from error handler ~
Option was cleared when evaluating it resulted in an error.
@@ -2796,9 +2795,6 @@ A jump table for the options with a short description can be found at |Q_op|.
font names a list can be specified, font names separated with commas.
The first valid font is used.
- On systems where 'guifontset' is supported (X11) and 'guifontset' is
- not empty, then 'guifont' is not used.
-
Spaces after a comma are ignored. To include a comma in a font name
precede it with a backslash. Setting an option requires an extra
backslash before a space and a backslash. See also
@@ -2850,45 +2846,16 @@ A jump table for the options with a short description can be found at |Q_op|.
:set guifont=courier_new:h12:w5:b:cRUSSIAN
:set guifont=Andale_Mono:h7.5:w4.5
<
-
- *'guifontset'* *'gfs'*
- *E250* *E252* *E234* *E597* *E598*
-'guifontset' 'gfs' string (default "")
- global
- When not empty, specifies two (or more) fonts to be used. The first
- one for normal English, the second one for your special language. See
- |xfontset|.
- Setting this option also means that all font names will be handled as
- a fontset name. Also the ones used for the "font" argument of the
- |:highlight| command.
- The fonts must match with the current locale. If fonts for the
- character sets that the current locale uses are not included, setting
- 'guifontset' will fail.
- Note the difference between 'guifont' and 'guifontset': In 'guifont'
- the comma-separated names are alternative names, one of which will be
- used. In 'guifontset' the whole string is one fontset name,
- including the commas. It is not possible to specify alternative
- fontset names.
- This example works on many X11 systems: >
- :set guifontset=-*-*-medium-r-normal--16-*-*-*-c-*-*-*
-<
*'guifontwide'* *'gfw'* *E231* *E533* *E534*
'guifontwide' 'gfw' string (default "")
global
- When not empty, specifies a comma-separated list of fonts to be used
- for double-width characters. The first font that can be loaded is
- used.
+ Comma-separated list of fonts to be used for double-width characters.
+ The first font that can be loaded is used.
Note: The size of these fonts must be exactly twice as wide as the one
specified with 'guifont' and the same height.
- 'guifontwide' is only used when 'guifontset' is empty or invalid.
- When 'guifont' is set and a valid font is found in it and
- 'guifontwide' is empty Vim will attempt to find a matching
- double-width font and set 'guifontwide' to it.
-
- Windows +multibyte only: *guifontwide_win_mbyte*
-
- If set and valid, 'guifontwide' is used for IME instead of 'guifont'.
+ When 'guifont' has a valid font and 'guifontwide' is empty Vim will
+ attempt to set 'guifontwide' to a matching double-width font.
*'guioptions'* *'go'*
'guioptions' 'go' string (default "egmrLT" (MS-Windows))
@@ -4055,7 +4022,6 @@ A jump table for the options with a short description can be found at |Q_op|.
*'mousefocus'* *'mousef'* *'nomousefocus'* *'nomousef'*
'mousefocus' 'mousef' boolean (default off)
global
- {only works in the GUI}
The window that the mouse pointer is on is automatically activated.
When changing the window layout or window focus in another way, the
mouse pointer is moved to the window with keyboard focus. Off is the
diff --git a/runtime/doc/provider.txt b/runtime/doc/provider.txt
index 0a6cdc60e8..f944689d0b 100644
--- a/runtime/doc/provider.txt
+++ b/runtime/doc/provider.txt
@@ -129,9 +129,13 @@ To use the RVM "system" Ruby installation: >
==============================================================================
Perl integration *provider-perl*
-Nvim supports Perl |remote-plugin|s.
+Nvim supports Perl |remote-plugin|s on Unix platforms. Support for polling STDIN
+on MS-Windows is currently lacking from all known event loop implementations.
+The Vim legacy |perl-vim| interface is also supported (which is itself
+implemented as a Nvim remote-plugin).
https://github.com/jacquesg/p5-Neovim-Ext
+Note: Only perl versions from 5.22 onward are supported.
PERL QUICKSTART~
@@ -212,12 +216,12 @@ For example this configuration integrates the tmux clipboard: >
let g:clipboard = {
\ 'name': 'myClipboard',
\ 'copy': {
- \ '+': 'tmux load-buffer -',
- \ '*': 'tmux load-buffer -',
+ \ '+': ['tmux', 'load-buffer', '-'],
+ \ '*': ['tmux', 'load-buffer', '-'],
\ },
\ 'paste': {
- \ '+': 'tmux save-buffer -',
- \ '*': 'tmux save-buffer -',
+ \ '+': ['tmux', 'save-buffer', '-'],
+ \ '*': ['tmux', 'save-buffer', '-'],
\ },
\ 'cache_enabled': 1,
\ }
diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt
index 224f14a18b..4a47fd4b57 100644
--- a/runtime/doc/quickref.txt
+++ b/runtime/doc/quickref.txt
@@ -711,7 +711,6 @@ Short explanation of each option: *option-list*
'grepprg' 'gp' program to use for ":grep"
'guicursor' 'gcr' GUI: settings for cursor shape and blinking
'guifont' 'gfn' GUI: Name(s) of font(s) to be used
-'guifontset' 'gfs' GUI: Names of multi-byte fonts to be used
'guifontwide' 'gfw' list of font names for double-wide characters
'guioptions' 'go' GUI: Which components and options are used
'guitablabel' 'gtl' GUI: custom label for a tab page
@@ -1106,7 +1105,6 @@ Context-sensitive completion on the command-line:
------------------------------------------------------------------------------
*Q_st* Starting Vim
-|-vim| vim [options] start editing with an empty buffer
|-file| vim [options] {file} .. start editing one or more files
|--| vim [options] - read file from stdin
|-tag| vim [options] -t {tag} edit the file associated with {tag}
diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt
index 0ded6a9060..d0faeb9cb7 100644
--- a/runtime/doc/starting.txt
+++ b/runtime/doc/starting.txt
@@ -9,20 +9,20 @@ Starting Vim *starting*
Type |gO| to see the table of contents.
==============================================================================
-1. Vim arguments *vim-arguments*
+Nvim arguments *vim-arguments*
-Most often, Vim is started to edit a single file with the command
+Most often, Nvim is started to edit a single file with the command: >
- nvim filename *-vim*
+ nvim filename
-More generally, Vim is started with:
+More generally, Nvim is started with: >
nvim [option | filename] ..
Option arguments and file name arguments can be mixed, and any number of them
can be given. However, watch out for options that take an argument.
-The following items may be used to choose how to start editing:
+The following items decide how to start editing:
*-file* *---*
filename One or more file names. The first one will be the current
@@ -231,9 +231,8 @@ argument.
-b Binary mode. File I/O will only recognize <NL> to separate
lines. The 'expandtab' option will be reset. The 'textwidth'
option is set to 0. 'modeline' is reset. The 'binary' option
- is set. This is done after reading the init.vim/exrc files
- but before reading any file in the arglist. See also
- |edit-binary|.
+ is set. This is done after reading the |vimrc| but before
+ reading any file in the arglist. See also |edit-binary|.
*-l*
-l Lisp mode. Sets the 'lisp' and 'showmatch' options on.
@@ -398,7 +397,7 @@ argument.
primary listen address |v:servername| to {addr}. |serverstart()|
==============================================================================
-2. Initialization *initialization* *startup*
+Initialization *initialization* *startup*
At startup, Vim checks environment variables and files and sets values
accordingly. Vim proceeds in this order:
@@ -414,45 +413,47 @@ accordingly. Vim proceeds in this order:
The |-V| argument can be used to display or log what happens next,
useful for debugging the initializations.
-3. Execute Ex commands, from environment variables and/or files
- An environment variable (e.g. $VIMINIT) is read as one Ex command
- line, where multiple commands must be separated with '|' or <NL>.
+3. Wait for UI to connect.
+ Nvim started with |--embed| waits for the UI to connect before
+ proceeding to load user configuration.
+
+4. Load user config (execute Ex commands from files, environment, …).
+ $VIMINIT environment variable is read as one Ex command line (separate
+ multiple commands with '|' or <NL>).
*config* *init.vim* *vimrc* *exrc*
- A file that contains initialization commands is generically called
- a "vimrc" or config file. Each line in a vimrc file is executed as an
- Ex command line. See also |vimrc-intro| and |base-directories|.
+ A file containing init commands is generically called a "vimrc" or
+ "config". Each line in such a file is executed as an Ex command.
+ |vimrc-intro| |base-directories|
- The Nvim config file is named "init.vim", located at:
+ The Nvim config file is "init.vim", located at:
Unix ~/.config/nvim/init.vim
Windows ~/AppData/Local/nvim/init.vim
- Or if |$XDG_CONFIG_HOME| is defined:
+ or if |$XDG_CONFIG_HOME| is defined:
$XDG_CONFIG_HOME/nvim/init.vim
- If Nvim was started with "-u filename", the file "filename" is used.
- All following initializations until 4. are skipped. $MYVIMRC is not
- set.
+ If Nvim was started with "-u {file}" then {file} is used as the config
+ and all initializations until 5. are skipped. $MYVIMRC is not set.
"nvim -u NORC" can be used to skip these initializations without
reading a file. "nvim -u NONE" also skips plugins and syntax
highlighting. |-u|
- If Nvim was started with |-es|, all following initializations until 4.
- are skipped.
+ If Nvim was started with |-es| all initializations until 5. are
+ skipped.
*system-vimrc* *sysinit.vim*
a. The system vimrc file is read for initializations. If
nvim/sysinit.vim file exists in one of $XDG_CONFIG_DIRS, it will be
- used. Otherwise, the system vimrc file is used. The path of this file
- is shown with the ":version" command. Mostly it's "$VIM/sysinit.vim".
+ used. Otherwise the system vimrc file is used. The path of this file
+ is given by the |:version| command. Usually it's "$VIM/sysinit.vim".
*VIMINIT* *EXINIT* *$MYVIMRC*
- b. Four places are searched for initializations. The first that exists
- is used, the others are ignored. The $MYVIMRC environment variable is
- set to the file that was first found, unless $MYVIMRC was already set
- and when using VIMINIT.
- - Environment variable $VIMINIT, used as an Ex command line.
- - User |config| file: $XDG_CONFIG_HOME/nvim/init.vim.
- - Other config file: {xdg_config_dir}/nvim/init.vim where
- {xdg_config_dir} is one of the directories in $XDG_CONFIG_DIRS.
- - Environment variable $EXINIT, used as an Ex command line.
+ b. Locations searched for initializations, in order of preference:
+ - $VIMINIT environment variable (Ex command line).
+ - User |config|: $XDG_CONFIG_HOME/nvim/init.vim.
+ - Other config: {dir}/nvim/init.vim where {dir} is any directory
+ in $XDG_CONFIG_DIRS.
+ - $EXINIT environment variable (Ex command line).
+ |$MYVIMRC| is set to the first valid location unless it was already
+ set or when using $VIMINIT.
c. If the 'exrc' option is on (which is NOT the default), the current
directory is searched for two files. The first that exists is used,
@@ -460,7 +461,7 @@ accordingly. Vim proceeds in this order:
- The file ".nvimrc"
- The file ".exrc"
-4. Enable filetype and indent plugins.
+5. Enable filetype and indent plugins.
This does the same as the commands: >
:runtime! filetype.vim
:runtime! ftplugin.vim
@@ -468,13 +469,13 @@ accordingly. Vim proceeds in this order:
< Skipped if ":filetype … off" was called or if the "-u NONE" command
line argument was given.
-5. Enable syntax highlighting.
+6. Enable syntax highlighting.
This does the same as the command: >
:runtime! syntax/syntax.vim
< Skipped if ":syntax off" was called or if the "-u NONE" command
line argument was given.
-6. Load the plugin scripts. *load-plugins*
+7. Load the plugin scripts. *load-plugins*
This does the same as the command: >
:runtime! plugin/**/*.vim
< The result is that all directories in the 'runtimepath' option will be
@@ -503,26 +504,26 @@ accordingly. Vim proceeds in this order:
if packages have been found, but that should not add a directory
ending in "after".
-7. Set 'shellpipe' and 'shellredir'
+8. Set 'shellpipe' and 'shellredir'
The 'shellpipe' and 'shellredir' options are set according to the
value of the 'shell' option, unless they have been set before.
This means that Vim will figure out the values of 'shellpipe' and
'shellredir' for you, unless you have set them yourself.
-8. Set 'updatecount' to zero, if "-n" command argument used
+9. Set 'updatecount' to zero, if "-n" command argument used
-9. Set binary options
+10. Set binary options
If the "-b" flag was given to Vim, the options for binary editing will
be set now. See |-b|.
-10. Read the ShaDa file
+11. Read the ShaDa file
See |shada-file|.
-11. Read the quickfix file
+12. Read the quickfix file
If the "-q" flag was given to Vim, the quickfix file is read. If this
fails, Vim exits.
-12. Open all windows
+13. Open all windows
When the |-o| flag was given, windows will be opened (but not
displayed yet).
When the |-p| flag was given, tab pages will be created (but not
@@ -531,7 +532,7 @@ accordingly. Vim proceeds in this order:
If the "-q" flag was given to Vim, the first error is jumped to.
Buffers for all windows will be loaded.
-13. Execute startup commands
+14. Execute startup commands
If a "-t" flag was given to Vim, the tag is jumped to.
The commands given with the |-c| and |+cmd| arguments are executed.
If the 'insertmode' option is set, Insert mode is entered.
@@ -540,29 +541,6 @@ accordingly. Vim proceeds in this order:
The |VimEnter| autocommands are executed.
-Some hints on using initializations ~
-
-Standard setup:
-Create a vimrc file to set the default settings and mappings for all your edit
-sessions. Put it in a place so that it will be found by 3b:
- ~/.config/nvim/init.vim (Unix)
- ~/AppData/Local/nvim/init.vim (Win32)
-
-Local setup:
-Put all commands that you need for editing a specific directory only into a
-vimrc file and place it in that directory under the name ".nvimrc" ("_nvimrc"
-for Windows). NOTE: To make Vim look for these special files you
-have to turn on the option 'exrc'. See |trojan-horse| too.
-
-System setup:
-This only applies if you are managing a Unix system with several users and
-want to set the defaults for all users. Create a vimrc file with commands
-for default settings and mappings and put it in the place that is given with
-the ":version" command. NOTE: System vimrc file needs specific compilation
-options (one needs to define SYS_VIMRC_FILE macros). If :version command does
-not show anything like this, consider contacting the nvim package maintainer.
-
-
Saving the current state of Vim to a file ~
Whenever you have changed values of options or when you have created a
@@ -570,20 +548,6 @@ mapping, then you may want to save them in a vimrc file for later use. See
|save-settings| about saving the current state of settings to a file.
-Avoiding setup problems for Vi users ~
-
-Vi uses the variable EXINIT and the file "~/.exrc". So if you do not want to
-interfere with Vi, then use the variable VIMINIT and the file init.vim
-instead.
-
-
-MS-DOS line separators: ~
-
-On Windows systems Vim assumes that all the vimrc files have <CR> <NL> pairs
-as line separators. This will give problems if you have a file with only
-<NL>s and have a line like ":map xx yy^M". The trailing ^M will be ignored.
-
-
Avoiding trojan horses ~
*trojan-horse*
While reading the "vimrc" or the "exrc" file in the current directory, some
@@ -606,7 +570,7 @@ it possible for another user to create a nasty vimrc and make you the owner.
Be careful!
When using tag search commands, executing the search command (the last
part of the line in the tags file) is always done in secure mode. This works
-just like executing a command from a vimrc/exrc in the current directory.
+just like executing a command from a vimrc in the current directory.
If Vim startup is slow ~
@@ -620,27 +584,29 @@ moment (use the Vim argument "-i NONE", |-i|). Try reducing the number of
lines stored in a register with ":set shada='20,<50,s10". |shada-file|.
-Intro message ~
- *:intro*
-When Vim starts without a file name, an introductory message is displayed (for
-those who don't know what Vim is). It is removed as soon as the display is
-redrawn in any way. To see the message again, use the ":intro" command (if
-there is not enough room, you will see only part of it).
- To avoid the intro message on startup, add the 'I' flag to 'shortmess'.
+Troubleshooting broken configurations ~
+ *bisect*
+The extreme flexibility of editors like Vim and Emacs means that any plugin or
+setting can affect the entire editor in ways that are not initially obvious.
- *info-message*
-The |--help| and |--version| arguments cause Nvim to print a message and then
-exit. Normally the message is sent to stdout, thus can be redirected to a
-file with: >
+To find the cause of a problem in your config, you must "bisect" it:
+1. Remove or disable half of your `init.vim`.
+2. Restart Nvim.
+3. If the problem still occurs, goto 1.
+4. If the problem is gone, restore half of the removed lines.
+5. Continue narrowing your config in this way, until you find the setting or
+ plugin causing the issue.
- nvim --help >file
-From inside Nvim: >
-
- :read !nvim --help
+Intro message ~
+ *:intro*
+When Vim starts without a file name, an introductory message is displayed. It
+is removed as soon as the display is redrawn. To see the message again, use
+the ":intro" command. To avoid the intro message on startup, add the "I" flag
+to 'shortmess'.
==============================================================================
-3. $VIM and $VIMRUNTIME
+$VIM and $VIMRUNTIME
*$VIM*
The environment variable "$VIM" is used to locate various user files for Nvim,
such as the user startup script |init.vim|. This depends on the system, see
@@ -683,9 +649,9 @@ greps in the help files) you might be able to use this: >
VIMRUNTIME="$(nvim --clean --headless --cmd 'echo $VIMRUNTIME|q')"
==============================================================================
-4. Suspending *suspend*
+Suspending *suspend*
- *iconize* *iconise* *CTRL-Z* *v_CTRL-Z*
+ *CTRL-Z* *v_CTRL-Z*
CTRL-Z Suspend Nvim, like ":stop".
Works in Normal and in Visual mode. In Insert and
Command-line mode, the CTRL-Z is inserted as a normal
@@ -706,7 +672,7 @@ CTRL-Z Suspend Nvim, like ":stop".
In the GUI, suspending is implementation-defined.
==============================================================================
-5. Exiting *exiting*
+Exiting *exiting*
There are several ways to exit Vim:
- Close the last window with `:quit`. Only when there are no changes.
@@ -719,7 +685,7 @@ When using `:cquit` or when there was an error message Vim exits with exit
code 1. Errors can be avoided by using `:silent!` or with `:catch`.
==============================================================================
-6. Saving settings *save-settings*
+Saving settings *save-settings*
Mostly you will edit your vimrc files manually. This gives you the greatest
flexibility. There are a few commands to generate a vimrc file automatically.
@@ -776,7 +742,7 @@ these steps:
You need to escape special characters, esp. spaces.
==============================================================================
-7. Views and Sessions *views-sessions*
+Views and Sessions *views-sessions*
This is introduced in sections |21.4| and |21.5| of the user manual.
@@ -920,7 +886,7 @@ To automatically save and restore views for *.c files: >
au BufWinEnter *.c silent! loadview
==============================================================================
-8. The ShaDa file *shada* *shada-file*
+Shada ("shared data") file *shada* *shada-file*
If you exit Vim and later start it again, you would normally lose a lot of
information. The ShaDa file can be used to remember that information, which
@@ -1358,7 +1324,7 @@ file when reading and include:
complete MessagePack object.
==============================================================================
-9. Standard Paths *standard-path*
+Standard Paths *standard-path*
Nvim stores configuration and data in standard locations. Plugins are strongly
encouraged to follow this pattern also. Use |stdpath()| to get the paths.
@@ -1389,4 +1355,5 @@ debugging, plugins and RPC clients. >
Usually the file is ~/.local/share/nvim/log unless that path is inaccessible
or if $NVIM_LOG_FILE was set before |startup|.
+
vim:noet:tw=78:ts=8:ft=help:norl:
diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt
index 2817c1015b..e5c6b9b1b7 100644
--- a/runtime/doc/ui.txt
+++ b/runtime/doc/ui.txt
@@ -183,9 +183,9 @@ the editor.
'ambiwidth'
'emoji'
'guifont'
- 'guifontset'
'guifontwide'
'linespace'
+ 'mousefocus'
'pumblend'
'showtabline'
'termguicolors'
@@ -719,7 +719,7 @@ events, which the UI must handle.
kind
Name indicating the message kind:
- "" (empty) Unknown, report a |feature-request|
+ "" (empty) Unknown (consider a feature-request: |bugs|)
"confirm" |confirm()| or |:confirm| dialog
"confirm_sub" |:substitute| confirm dialog |:s_c|
"emsg" Error (|errors|, internal error, |:throw|, …)
diff --git a/runtime/doc/usr_02.txt b/runtime/doc/usr_02.txt
index 9ebbd11ac7..c8fd7c3e35 100644
--- a/runtime/doc/usr_02.txt
+++ b/runtime/doc/usr_02.txt
@@ -652,7 +652,7 @@ Summary: *help-summary* >
22) Autocommand events can be found by their name: >
:help BufWinLeave
< To see all possible events: >
- :help autocommand-events
+ :help events
23) Command-line switches always start with "-". So for the help of the -f
command switch of Vim use: >
diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
index a7e040bc93..4bcea3e3fe 100644
--- a/runtime/doc/vim_diff.txt
+++ b/runtime/doc/vim_diff.txt
@@ -13,7 +13,7 @@ the differences.
Type |gO| to see the table of contents.
==============================================================================
-1. Configuration *nvim-configuration*
+1. Configuration *nvim-config*
- Use `$XDG_CONFIG_HOME/nvim/init.vim` instead of `.vimrc` for configuration.
- Use `$XDG_CONFIG_HOME/nvim` instead of `.vim` to store configuration files.
@@ -66,8 +66,8 @@ the differences.
- 'wildmenu' is enabled
- 'wildoptions' defaults to "pum,tagfile"
-- The |man.vim| plugin is enabled, to provide the |:Man| command.
-- The |matchit| plugin is enabled. To disable it in your config: >
+- |man.vim| plugin is enabled, so |:Man| is available by default.
+- |matchit| plugin is enabled. To disable it in your config: >
:let loaded_matchit = 1
==============================================================================
@@ -173,6 +173,7 @@ Functions:
|msgpackdump()|, |msgpackparse()| provide msgpack de/serialization
|stdpath()|
|system()|, |systemlist()| can run {cmd} directly (without 'shell')
+ |tabpagenr()| "#" argument
Highlight groups:
|highlight-blend| controls blend level for a highlight group
@@ -187,18 +188,18 @@ Highlight groups:
|hl-TermCursorNC|
|hl-Whitespace| highlights 'listchars' whitespace
-Input:
+Input/Mappings:
+ |<Cmd>| pseudokey
+
ALT (|META|) chords always work (even in the |TUI|). Map |<M-| with any key:
<M-1>, <M-BS>, <M-Del>, <M-Ins>, <M-/>, <M-\>, <M-Space>, <M-Enter>, etc.
Case-sensitive: <M-a> and <M-A> are two different keycodes.
ALT in insert-mode behaves like <Esc> if not mapped. |i_ALT|
-Mappings:
- |<Cmd>| pseudokey
-
Normal commands:
- "Outline": Type |gO| in |:Man| and |:help| pages to see a document outline.
+ |g<Tab>| goes to the last-accessed tabpage.
+ |gO| shows a filetype-defined "outline" of the current buffer.
Options:
'cpoptions' flags: |cpo-_|
@@ -407,7 +408,6 @@ Some legacy Vim features are not implemented:
- |if_lua|: Nvim Lua API is not compatible with Vim's "if_lua"
- *if_mzscheme*
-- *if_perl*
- |if_py|: *python-bindeval* *python-Function* are not supported
- *if_tcl*
@@ -472,6 +472,7 @@ Options:
'encoding' ("utf-8" is always used)
'esckeys'
'guioptions' "t" flag was removed
+ *'guifontset'* *'gfs'* (Use 'guifont' instead.)
*'guipty'* (Nvim uses pipes and PTYs consistently on all platforms.)
'highlight' (Names of builtin |highlight-groups| cannot be changed.)
*'imactivatefunc'* *'imaf'*
diff --git a/runtime/filetype.vim b/runtime/filetype.vim
index 41a9188905..32bd6daba0 100644
--- a/runtime/filetype.vim
+++ b/runtime/filetype.vim
@@ -236,10 +236,10 @@ au BufNewFile,BufRead */etc/blkid.tab,*/etc/blkid.tab.old setf xml
au BufNewFile,BufRead *bsd,*.bsdl setf bsdl
" Bazel (http://bazel.io)
-autocmd BufRead,BufNewFile *.bzl,WORKSPACE,BUILD.bazel setf bzl
+autocmd BufRead,BufNewFile *.bzl,*.bazel,WORKSPACE setf bzl
if has("fname_case")
" There is another check for BUILD further below.
- autocmd BufRead,BufNewFile BUILD setf bzl
+ autocmd BufRead,BufNewFile *.BUILD,BUILD setf bzl
endif
" C or lpc
@@ -2041,7 +2041,7 @@ au BufNewFile,BufRead bzr_log.* setf bzr
" Bazel build file
if !has("fname_case")
- au BufNewFile,BufRead BUILD setf bzl
+ au BufNewFile,BufRead *.BUILD,BUILD setf bzl
endif
" BIND zone
diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua
index ce0a3de520..705b34dc99 100644
--- a/runtime/lua/vim/highlight.lua
+++ b/runtime/lua/vim/highlight.lua
@@ -14,7 +14,7 @@ function highlight.range(bufnr, ns, higroup, start, finish, rtype, inclusive)
inclusive = inclusive or false
-- sanity check
- if start[2] < 0 or finish[2] < start[2] then return end
+ if start[2] < 0 or finish[1] < start[1] then return end
local region = vim.region(bufnr, start, finish, rtype, inclusive)
for linenr, cols in pairs(region) do
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 47dca40208..585528dd5a 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -761,8 +761,12 @@ do
text_document_did_change_handler = function(_, bufnr, changedtick,
firstline, lastline, new_lastline, old_byte_size, old_utf32_size,
old_utf16_size)
- local _ = log.debug() and log.debug("on_lines", bufnr, changedtick, firstline,
- lastline, new_lastline, old_byte_size, old_utf32_size, old_utf16_size, nvim_buf_get_lines(bufnr, firstline, new_lastline, true))
+
+ local _ = log.debug() and log.debug(
+ string.format("on_lines bufnr: %s, changedtick: %s, firstline: %s, lastline: %s, new_lastline: %s, old_byte_size: %s, old_utf32_size: %s, old_utf16_size: %s",
+ bufnr, changedtick, firstline, lastline, new_lastline, old_byte_size, old_utf32_size, old_utf16_size),
+ nvim_buf_get_lines(bufnr, firstline, new_lastline, true)
+ )
-- Don't do anything if there are no clients attached.
if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then
@@ -922,7 +926,7 @@ end
--- To stop all clients:
---
--- <pre>
---- vim.lsp.stop_client(lsp.get_active_clients())
+--- vim.lsp.stop_client(vim.lsp.get_active_clients())
--- </pre>
---
--- By default asks the server to shutdown, unless stop was requested
diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua
index 680e1ba6ae..64080cf4f2 100644
--- a/runtime/lua/vim/lsp/rpc.lua
+++ b/runtime/lua/vim/lsp/rpc.lua
@@ -376,7 +376,6 @@ local function start(cmd, cmd_args, handlers, extra_spawn_params)
--@param params (table): Parameters for the invoked LSP method
--@returns (bool) `true` if notification could be sent, `false` if not
local function notify(method, params)
- local _ = log.debug() and log.debug("rpc.notify", method, params)
return encode_and_send {
jsonrpc = "2.0";
method = method;
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 3ec7311d65..24cb454e5b 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -299,10 +299,9 @@ end
---
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
local function sort_completion_items(items)
- if items[1] and items[1].sortText then
- table.sort(items, function(a, b) return a.sortText < b.sortText
- end)
- end
+ table.sort(items, function(a, b)
+ return (a.sortText or a.label) < (b.sortText or b.label)
+ end)
end
--@private
@@ -1438,6 +1437,9 @@ local function make_position_param()
local row, col = unpack(api.nvim_win_get_cursor(0))
row = row - 1
local line = api.nvim_buf_get_lines(0, row, row+1, true)[1]
+ if not line then
+ return { line = 0; character = 0; }
+ end
col = str_utfindex(line, col)
return { line = row; character = col; }
end
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua
index 6e427665f2..5c89c63f7b 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -190,10 +190,10 @@ function vim.tbl_contains(t, value)
return false
end
--- Returns true if the table is empty, and contains no indexed or keyed values.
---
---@see From https://github.com/premake/premake-core/blob/master/src/base/table.lua
---
+--- Checks if a table is empty.
+---
+--@see https://github.com/premake/premake-core/blob/master/src/base/table.lua
+---
--@param t Table to check
function vim.tbl_isempty(t)
assert(type(t) == 'table', string.format("Expected table, got %s", type(t)))
@@ -347,13 +347,11 @@ function vim.tbl_flatten(t)
return result
end
---- Determine whether a Lua table can be treated as an array.
+--- Tests if a Lua table can be treated as an array.
---
---- An empty table `{}` will default to being treated as an array.
---- Use `vim.emtpy_dict()` to create a table treated as an
---- empty dict. Empty tables returned by `rpcrequest()` and
---- `vim.fn` functions can be checked using this function
---- whether they represent empty API arrays and vimL lists.
+--- Empty table `{}` is assumed to be an array, unless it was created by
+--- |vim.empty_dict()| or returned as a dict-like |API| or Vimscript result,
+--- for example from |rpcrequest()| or |vim.fn|.
---
--@param t Table
--@returns `true` if array-like table, else `false`.
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index 550dee1e3f..3a475b8f98 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -33,16 +33,23 @@ function Parser:parse()
return self.tree, changes
end
-function Parser:_on_lines(bufnr, changed_tick, start_row, old_stop_row, stop_row, old_byte_size)
- local start_byte = a.nvim_buf_get_offset(bufnr,start_row)
- local stop_byte = a.nvim_buf_get_offset(bufnr,stop_row)
- local old_stop_byte = start_byte + old_byte_size
- self._parser:edit(start_byte,old_stop_byte,stop_byte,
- start_row,0,old_stop_row,0,stop_row,0)
+function Parser:_on_bytes(bufnr, changed_tick,
+ start_row, start_col, start_byte,
+ old_row, old_col, old_byte,
+ new_row, new_col, new_byte)
+ local old_end_col = old_col + ((old_row == 0) and start_col or 0)
+ local new_end_col = new_col + ((new_row == 0) and start_col or 0)
+ self._parser:edit(start_byte,start_byte+old_byte,start_byte+new_byte,
+ start_row, start_col,
+ start_row+old_row, old_end_col,
+ start_row+new_row, new_end_col)
self.valid = false
- for _, cb in ipairs(self.lines_cbs) do
- cb(bufnr, changed_tick, start_row, old_stop_row, stop_row, old_byte_size)
+ for _, cb in ipairs(self.bytes_cbs) do
+ cb(bufnr, changed_tick,
+ start_row, start_col, start_byte,
+ old_row, old_col, old_byte,
+ new_row, new_col, new_byte)
end
end
@@ -88,12 +95,12 @@ function M._create_parser(bufnr, lang, id)
local self = setmetatable({bufnr=bufnr, lang=lang, valid=false}, Parser)
self._parser = vim._create_ts_parser(lang)
self.changedtree_cbs = {}
- self.lines_cbs = {}
+ self.bytes_cbs = {}
self:parse()
- -- TODO(bfredl): use weakref to self, so that the parser is free'd is no plugin is
- -- using it.
- local function lines_cb(_, ...)
- return self:_on_lines(...)
+ -- TODO(bfredl): use weakref to self, so that the parser is free'd is no plugin is
+ -- using it.
+ local function bytes_cb(_, ...)
+ return self:_on_bytes(...)
end
local detach_cb = nil
if id ~= nil then
@@ -103,7 +110,7 @@ function M._create_parser(bufnr, lang, id)
end
end
end
- a.nvim_buf_attach(self.bufnr, false, {on_lines=lines_cb, on_detach=detach_cb})
+ a.nvim_buf_attach(self.bufnr, false, {on_bytes=bytes_cb, on_detach=detach_cb})
return self
end
@@ -138,8 +145,8 @@ function M.get_parser(bufnr, lang, buf_attach_cbs)
table.insert(parsers[id].changedtree_cbs, buf_attach_cbs.on_changedtree)
end
- if buf_attach_cbs and buf_attach_cbs.on_lines then
- table.insert(parsers[id].lines_cbs, buf_attach_cbs.on_lines)
+ if buf_attach_cbs and buf_attach_cbs.on_bytes then
+ table.insert(parsers[id].bytes_cbs, buf_attach_cbs.on_bytes)
end
return parsers[id]
diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua
index 681d2c6324..718088e0ad 100644
--- a/runtime/lua/vim/treesitter/highlighter.lua
+++ b/runtime/lua/vim/treesitter/highlighter.lua
@@ -60,14 +60,11 @@ function TSHighlighter.new(query, bufnr, ft)
ft,
{
on_changedtree = function(...) self:on_changedtree(...) end,
- on_lines = function() self.root = self.parser:parse():root() end
+ on_bytes = function() self.parser:parse() end
}
)
self.buf = self.parser.bufnr
-
- local tree = self.parser:parse()
- self.root = tree:root()
self:set_query(query)
self.edit_count = 0
self.redraw_count = 0
@@ -98,7 +95,8 @@ function TSHighlighter:get_hl_from_capture(capture)
return vim.split(name, '.', true)[1]
else
-- Default to false to avoid recomputing
- return TSHighlighter.hl_map[name]
+ local hl = TSHighlighter.hl_map[name]
+ return hl and a.nvim_get_hl_id_by_name(hl) or 0
end
end
@@ -125,27 +123,25 @@ function TSHighlighter:set_query(query)
end
})
- self:on_changedtree({{self.root:range()}})
+ self:on_changedtree({{self.parser:parse():root():range()}})
end
function TSHighlighter:on_changedtree(changes)
-- Get a fresh root
- self.root = self.parser.tree:root()
+ local root = self.parser:parse():root()
for _, ch in ipairs(changes or {}) do
- -- Try to be as exact as possible
- local changed_node = self.root:descendant_for_range(ch[1], ch[2], ch[3], ch[4])
-
- a.nvim_buf_clear_namespace(self.buf, ts_hs_ns, ch[1], ch[3])
+ a.nvim_buf_clear_namespace(self.buf, ts_hs_ns, ch[1], ch[3]+1)
- for capture, node in self.query:iter_captures(changed_node, self.buf, ch[1], ch[3] + 1) do
+ for capture, node in self.query:iter_captures(root, self.buf, ch[1], ch[3] + 1) do
local start_row, start_col, end_row, end_col = node:range()
local hl = self.hl_cache[capture]
if hl then
- a.nvim__buf_add_decoration(self.buf, ts_hs_ns, hl,
- start_row, start_col,
- end_row, end_col,
- {})
+ a.nvim_buf_set_extmark(self.buf, ts_hs_ns, start_row, start_col, {
+ end_col = end_col,
+ end_line = end_row,
+ hl_group = hl
+ })
end
end
end
diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index 17f61b24f1..ca27a50c6a 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -17,7 +17,7 @@ local M = {}
function M.parse_query(lang, query)
language.require_language(lang)
local self = setmetatable({}, Query)
- self.query = vim._ts_parse_query(lang, vim.fn.escape(query,'\\'))
+ self.query = vim._ts_parse_query(lang, query)
self.info = self.query:inspect()
self.captures = self.info.captures
return self
@@ -60,7 +60,7 @@ local predicate_handlers = {
return true
end,
- ["match?"] = function(match, _, bufnr, predicate)
+ ["lua-match?"] = function(match, _, bufnr, predicate)
local node = match[predicate[2]]
local regex = predicate[3]
local start_row, _, end_row, _ = node:range()
@@ -71,7 +71,7 @@ local predicate_handlers = {
return string.find(M.get_node_text(node, bufnr), regex)
end,
- ["vim-match?"] = (function()
+ ["match?"] = (function()
local magic_prefixes = {['\\v']=true, ['\\m']=true, ['\\M']=true, ['\\V']=true}
local function check_magic(str)
if string.len(str) < 2 or magic_prefixes[string.sub(str,1,2)] then
@@ -82,7 +82,7 @@ local predicate_handlers = {
local compiled_vim_regexes = setmetatable({}, {
__index = function(t, pattern)
- local res = vim.regex(check_magic(pattern))
+ local res = vim.regex(check_magic(vim.fn.escape(pattern, '\\')))
rawset(t, pattern, res)
return res
end
@@ -114,6 +114,9 @@ local predicate_handlers = {
end
}
+-- As we provide lua-match? also expose vim-match?
+predicate_handlers["vim-match?"] = predicate_handlers["match?"]
+
--- Adds a new predicates to be used in queries
--
-- @param name the name of the predicate, without leading #
@@ -127,25 +130,43 @@ function M.add_predicate(name, handler, force)
predicate_handlers[name] = handler
end
+--- Returns the list of currently supported predicates
+function M.list_predicates()
+ return vim.tbl_keys(predicate_handlers)
+end
+
+local function xor(x, y)
+ return (x or y) and not (x and y)
+end
+
function Query:match_preds(match, pattern, bufnr)
local preds = self.info.patterns[pattern]
- if not preds then
- return true
- end
- for _, pred in pairs(preds) do
+
+ for _, pred in pairs(preds or {}) do
-- Here we only want to return if a predicate DOES NOT match, and
-- continue on the other case. This way unknown predicates will not be considered,
-- which allows some testing and easier user extensibility (#12173).
-- Also, tree-sitter strips the leading # from predicates for us.
+ local pred_name
+ local is_not
if string.sub(pred[1], 1, 4) == "not-" then
- local pred_name = string.sub(pred[1], 5)
- if predicate_handlers[pred_name] and
- predicate_handlers[pred_name](match, pattern, bufnr, pred) then
- return false
- end
+ pred_name = string.sub(pred[1], 5)
+ is_not = true
+ else
+ pred_name = pred[1]
+ is_not = false
+ end
+
+ local handler = predicate_handlers[pred_name]
+
+ if not handler then
+ a.nvim_err_writeln(string.format("No handler for %s", pred[1]))
+ return false
+ end
+
+ local pred_matches = handler(match, pattern, bufnr, pred)
- elseif predicate_handlers[pred[1]] and
- not predicate_handlers[pred[1]](match, pattern, bufnr, pred) then
+ if not xor(is_not, pred_matches) then
return false
end
end
diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py
index 4b04ebb5fb..f754452c02 100755
--- a/scripts/gen_vimdoc.py
+++ b/scripts/gen_vimdoc.py
@@ -58,7 +58,6 @@ if sys.version_info < MIN_PYTHON_VERSION:
sys.exit(1)
DEBUG = ('DEBUG' in os.environ)
-TARGET = os.environ.get('TARGET', None)
INCLUDE_C_DECL = ('INCLUDE_C_DECL' in os.environ)
INCLUDE_DEPRECATED = ('INCLUDE_DEPRECATED' in os.environ)
@@ -69,6 +68,7 @@ base_dir = os.path.dirname(os.path.dirname(script_path))
out_dir = os.path.join(base_dir, 'tmp-{target}-doc')
filter_cmd = '%s %s' % (sys.executable, script_path)
seen_funcs = set()
+msgs = [] # Messages to show on exit.
lua2dox_filter = os.path.join(base_dir, 'scripts', 'lua2dox_filter')
CONFIG = {
@@ -192,7 +192,7 @@ xrefs = set()
# Raises an error with details about `o`, if `cond` is in object `o`,
# or if `cond()` is callable and returns True.
-def debug_this(cond, o):
+def debug_this(o, cond=True):
name = ''
if not isinstance(o, str):
try:
@@ -206,6 +206,23 @@ def debug_this(cond, o):
raise RuntimeError('xxx: {}\n{}'.format(name, o))
+# Appends a message to a list which will be printed on exit.
+def msg(s):
+ msgs.append(s)
+
+
+# Print all collected messages.
+def msg_report():
+ for m in msgs:
+ print(f' {m}')
+
+
+# Print collected messages, then throw an exception.
+def fail(s):
+ msg_report()
+ raise RuntimeError(s)
+
+
def find_first(parent, name):
"""Finds the first matching node within parent."""
sub = parent.getElementsByTagName(name)
@@ -842,7 +859,7 @@ def delete_lines_below(filename, tokenstr):
fp.writelines(lines[0:i])
-def main(config, args=None):
+def main(config, args):
"""Generates:
1. Vim :help docs
@@ -851,7 +868,7 @@ def main(config, args=None):
Doxygen is called and configured through stdin.
"""
for target in CONFIG:
- if TARGET is not None and target != TARGET:
+ if args.target is not None and target != args.target:
continue
mpack_file = os.path.join(
base_dir, 'runtime', 'doc',
@@ -916,9 +933,10 @@ def main(config, args=None):
filename = get_text(find_first(compound, 'name'))
if filename.endswith('.c') or filename.endswith('.lua'):
+ xmlfile = os.path.join(base,
+ '{}.xml'.format(compound.getAttribute('refid')))
# Extract unformatted (*.mpack).
- fn_map, _ = extract_from_xml(os.path.join(base, '{}.xml'.format(
- compound.getAttribute('refid'))), target, width=9999)
+ fn_map, _ = extract_from_xml(xmlfile, target, width=9999)
# Extract formatted (:help).
functions_text, deprecated_text = fmt_doxygen_xml_as_vimhelp(
os.path.join(base, '{}.xml'.format(
@@ -951,7 +969,8 @@ def main(config, args=None):
sections[filename] = (title, helptag, doc)
fn_map_full.update(fn_map)
- assert sections
+ if len(sections) == 0:
+ fail(f'no sections for target: {target}')
if len(sections) > len(CONFIG[target]['section_order']):
raise RuntimeError(
'found new modules "{}"; update the "section_order" map'.format(
@@ -964,7 +983,7 @@ def main(config, args=None):
try:
title, helptag, section_doc = sections.pop(filename)
except KeyError:
- print("Warning:", filename, "has empty docs, skipping")
+ msg(f'warning: empty docs, skipping (target={target}): {filename}')
continue
i += 1
if filename not in CONFIG[target]['append_only']:
@@ -991,6 +1010,8 @@ def main(config, args=None):
if not args.keep_tmpfiles:
shutil.rmtree(output_dir)
+ msg_report()
+
def filter_source(filename):
name, extension = os.path.splitext(filename)
@@ -1008,11 +1029,14 @@ def filter_source(filename):
def parse_args():
+ targets = ', '.join(CONFIG.keys())
ap = argparse.ArgumentParser()
ap.add_argument('source_filter', nargs='*',
help="Filter source file(s)")
ap.add_argument('-k', '--keep-tmpfiles', action='store_true',
help="Keep temporary files")
+ ap.add_argument('-t', '--target',
+ help=f'One of ({targets}), defaults to "all"')
return ap.parse_args()
diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh
index 9c4349abca..03f52bd162 100755
--- a/scripts/vim-patch.sh
+++ b/scripts/vim-patch.sh
@@ -346,7 +346,8 @@ submit_pr() {
local patches
# Extract just the "vim-patch:X.Y.ZZZZ" or "vim-patch:sha" portion of each log
patches=("$(git log --grep=vim-patch --reverse --format='%s' "${git_remote}"/master..HEAD | sed 's/: .*//')")
- patches=("${patches[@]//vim-patch:}") # Remove 'vim-patch:' prefix for each item in array.
+ # shellcheck disable=SC2206
+ patches=(${patches[@]//vim-patch:}) # Remove 'vim-patch:' prefix for each item in array.
local pr_title="${patches[*]}" # Create space-separated string from array.
pr_title="${pr_title// /,}" # Replace spaces with commas.
diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml
index 7d1e7bdbb0..57bcb72d5d 100644
--- a/snap/snapcraft.yaml
+++ b/snap/snapcraft.yaml
@@ -1,5 +1,5 @@
name: nvim
-base: core20
+base: core18
adopt-info: nvim
summary: Vim-fork focused on extensibility and agility.
description: |
@@ -25,16 +25,23 @@ apps:
parts:
nvim:
- source: .
+ source: https://github.com/neovim/neovim.git
override-pull: |
snapcraftctl pull
+ latest_tag="$(git tag -l --sort=refname|head -1)"
+ git checkout "${latest_tag}"
major="$(awk '/NVIM_VERSION_MAJOR/{gsub(")","",$2); print $2}' CMakeLists.txt)"
minor="$(awk '/NVIM_VERSION_MINOR/{gsub(")","",$2); print $2}' CMakeLists.txt)"
patch="$(awk '/NVIM_VERSION_PATCH/{gsub(")","",$2); print $2}' CMakeLists.txt)"
version_prefix="v$major.$minor.$patch"
git_described="$(git describe --first-parent --dirty 2> /dev/null | perl -lpe 's/v\d.\d.\d-//g')"
git_described="${git_described:-$(git describe --first-parent --tags --always --dirty)}"
- snapcraftctl set-version "${version_prefix}-${git_described}"
+ if [ "${version_prefix}" != "${git_described}" ]; then
+ VERSION="${version_prefix}-${git_described}-${latest_tag}"
+ else
+ VERSION="${version_prefix}-${latest_tag}"
+ fi
+ snapcraftctl set-version "${VERSION}"
plugin: make
make-parameters:
- CMAKE_BUILD_TYPE=RelWithDebInfo
@@ -42,7 +49,23 @@ parts:
- CMAKE_FLAGS=-DPREFER_LUA=ON
- DEPS_CMAKE_FLAGS="-DUSE_BUNDLED_LUA=ON -DUSE_BUNDLED_LUAJIT=OFF"
override-build: |
- snapcraftctl build
+ echo "Building on $SNAP_ARCH"
+ set -x
+ case "$SNAP_ARCH" in
+ "arm64" | "ppc64el" | "s390x")
+ make -j"${SNAPCRAFT_PARALLEL_BUILD_COUNT}" \
+ CMAKE_BUILD_TYPE=RelWithDebInfo \
+ CMAKE_INSTALL_PREFIX=/usr \
+ CMAKE_FLAGS=-DPREFER_LUA=ON \
+ DEPS_CMAKE_FLAGS="-DUSE_BUNDLED_LUA=ON -DUSE_BUNDLED_LUAJIT=OFF"
+ ;;
+ *)
+ make -j"${SNAPCRAFT_PARALLEL_BUILD_COUNT}" \
+ CMAKE_BUILD_TYPE=RelWithDebInfo \
+ CMAKE_INSTALL_PREFIX=/usr
+ ;;
+ esac
+ make DESTDIR="$SNAPCRAFT_PART_INSTALL" install
# Fix Desktop file
sed -i 's|^Exec=nvim|Exec=/snap/bin/nvim.nvim|' ${SNAPCRAFT_PART_INSTALL}/usr/share/applications/nvim.desktop
sed -i 's|^TryExec=nvim|TryExec=/snap/bin/nvim.nvim|' ${SNAPCRAFT_PART_INSTALL}/usr/share/applications/nvim.desktop
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index 7b4438b896..2d98f1a659 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -624,9 +624,19 @@ if(CLANG_ASAN_UBSAN)
message(STATUS "Enabling Clang address sanitizer and undefined behavior sanitizer for nvim.")
check_c_compiler_flag(-fno-sanitize-recover=all SANITIZE_RECOVER_ALL)
if(SANITIZE_RECOVER_ALL)
- set(SANITIZE_RECOVER -fno-sanitize-recover=all) # Clang 3.6+
+ if(TRAVIS_CI_BUILD)
+ # Try to recover from all sanitize issues so we get reports about all failures
+ set(SANITIZE_RECOVER -fsanitize-recover=all) # Clang 3.6+
+ else()
+ set(SANITIZE_RECOVER -fno-sanitize-recover=all) # Clang 3.6+
+ endif()
else()
- set(SANITIZE_RECOVER -fno-sanitize-recover) # Clang 3.5-
+ if(TRAVIS_CI_BUILD)
+ # Try to recover from all sanitize issues so we get reports about all failures
+ set(SANITIZE_RECOVER -fsanitize-recover) # Clang 3.5-
+ else()
+ set(SANITIZE_RECOVER -fno-sanitize-recover) # Clang 3.5-
+ endif()
endif()
set_property(TARGET nvim APPEND PROPERTY COMPILE_DEFINITIONS EXITFREE)
set_property(TARGET nvim APPEND PROPERTY COMPILE_OPTIONS ${SANITIZE_RECOVER} -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=address -fsanitize=undefined -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/src/.asan-blacklist)
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 8e61976c4b..15065760b3 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -175,8 +175,7 @@ Boolean nvim_buf_attach(uint64_t channel_id,
}
cb.on_lines = v->data.luaref;
v->data.luaref = LUA_NOREF;
- } else if (is_lua && strequal("_on_bytes", k.data)) {
- // NB: undocumented, untested and incomplete interface!
+ } else if (is_lua && strequal("on_bytes", k.data)) {
if (v->type != kObjectTypeLuaRef) {
api_set_error(err, kErrorTypeValidation, "callback is not a function");
goto error;
@@ -213,10 +212,10 @@ Boolean nvim_buf_attach(uint64_t channel_id,
error:
// TODO(bfredl): ASAN build should check that the ref table is empty?
- executor_free_luaref(cb.on_lines);
- executor_free_luaref(cb.on_bytes);
- executor_free_luaref(cb.on_changedtick);
- executor_free_luaref(cb.on_detach);
+ api_free_luaref(cb.on_lines);
+ api_free_luaref(cb.on_bytes);
+ api_free_luaref(cb.on_changedtick);
+ api_free_luaref(cb.on_detach);
return false;
}
@@ -248,10 +247,10 @@ Boolean nvim_buf_detach(uint64_t channel_id,
static void buf_clear_luahl(buf_T *buf, bool force)
{
if (buf->b_luahl || force) {
- executor_free_luaref(buf->b_luahl_start);
- executor_free_luaref(buf->b_luahl_window);
- executor_free_luaref(buf->b_luahl_line);
- executor_free_luaref(buf->b_luahl_end);
+ api_free_luaref(buf->b_luahl_start);
+ api_free_luaref(buf->b_luahl_window);
+ api_free_luaref(buf->b_luahl_line);
+ api_free_luaref(buf->b_luahl_end);
}
buf->b_luahl_start = LUA_NOREF;
buf->b_luahl_window = LUA_NOREF;
@@ -1108,15 +1107,67 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
return rv;
}
+static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict)
+{
+ Array rv = ARRAY_DICT_INIT;
+ if (id) {
+ ADD(rv, INTEGER_OBJ((Integer)extmark.mark_id));
+ }
+ ADD(rv, INTEGER_OBJ(extmark.row));
+ ADD(rv, INTEGER_OBJ(extmark.col));
+
+ if (add_dict) {
+ Dictionary dict = ARRAY_DICT_INIT;
+
+ if (extmark.end_row >= 0) {
+ PUT(dict, "end_row", INTEGER_OBJ(extmark.end_row));
+ PUT(dict, "end_col", INTEGER_OBJ(extmark.end_col));
+ }
+
+ if (extmark.decor) {
+ Decoration *decor = extmark.decor;
+ if (decor->hl_id) {
+ String name = cstr_to_string((const char *)syn_id2name(decor->hl_id));
+ PUT(dict, "hl_group", STRING_OBJ(name));
+ }
+ if (kv_size(decor->virt_text)) {
+ Array chunks = ARRAY_DICT_INIT;
+ for (size_t i = 0; i < decor->virt_text.size; i++) {
+ Array chunk = ARRAY_DICT_INIT;
+ VirtTextChunk *vtc = &decor->virt_text.items[i];
+ ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text)));
+ if (vtc->hl_id > 0) {
+ ADD(chunk,
+ STRING_OBJ(cstr_to_string(
+ (const char *)syn_id2name(vtc->hl_id))));
+ }
+ ADD(chunks, ARRAY_OBJ(chunk));
+ }
+ PUT(dict, "virt_text", ARRAY_OBJ(chunks));
+ }
+ }
+
+ if (dict.size) {
+ ADD(rv, DICTIONARY_OBJ(dict));
+ }
+ }
+
+ return rv;
+}
+
/// Returns position for a given extmark id
///
/// @param buffer Buffer handle, or 0 for current buffer
/// @param ns_id Namespace id from |nvim_create_namespace()|
/// @param id Extmark id
+/// @param opts Optional parameters. Keys:
+/// - limit: Maximum number of marks to return
+/// - details Whether to include the details dict
/// @param[out] err Error details, if any
/// @return (row, col) tuple or empty list () if extmark id was absent
ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
- Integer id, Error *err)
+ Integer id, Dictionary opts,
+ Error *err)
FUNC_API_SINCE(7)
{
Array rv = ARRAY_DICT_INIT;
@@ -1132,13 +1183,31 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
return rv;
}
+ bool details = false;
+ for (size_t i = 0; i < opts.size; i++) {
+ String k = opts.items[i].key;
+ Object *v = &opts.items[i].value;
+ if (strequal("details", k.data)) {
+ if (v->type == kObjectTypeBoolean) {
+ details = v->data.boolean;
+ } else if (v->type == kObjectTypeInteger) {
+ details = v->data.integer;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "details is not an boolean");
+ return rv;
+ }
+ } else {
+ api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ return rv;
+ }
+ }
+
+
ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id);
if (extmark.row < 0) {
return rv;
}
- ADD(rv, INTEGER_OBJ((Integer)extmark.row));
- ADD(rv, INTEGER_OBJ((Integer)extmark.col));
- return rv;
+ return extmark_to_array(extmark, false, (bool)details);
}
/// Gets extmarks in "traversal order" from a |charwise| region defined by
@@ -1181,10 +1250,12 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
/// (whose position defines the bound)
/// @param opts Optional parameters. Keys:
/// - limit: Maximum number of marks to return
+/// - details Whether to include the details dict
/// @param[out] err Error details, if any
/// @return List of [extmark_id, row, col] tuples in "traversal order".
-Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start,
- Object end, Dictionary opts, Error *err)
+Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id,
+ Object start, Object end,
+ Dictionary opts, Error *err)
FUNC_API_SINCE(7)
{
Array rv = ARRAY_DICT_INIT;
@@ -1198,7 +1269,9 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start,
api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
return rv;
}
+
Integer limit = -1;
+ bool details = false;
for (size_t i = 0; i < opts.size; i++) {
String k = opts.items[i].key;
@@ -1209,6 +1282,15 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start,
return rv;
}
limit = v->data.integer;
+ } else if (strequal("details", k.data)) {
+ if (v->type == kObjectTypeBoolean) {
+ details = v->data.boolean;
+ } else if (v->type == kObjectTypeInteger) {
+ details = v->data.integer;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "details is not an boolean");
+ return rv;
+ }
} else {
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
return rv;
@@ -1241,16 +1323,11 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start,
}
- ExtmarkArray marks = extmark_get(buf, (uint64_t)ns_id, l_row, l_col, u_row,
- u_col, (int64_t)limit, reverse);
+ ExtmarkInfoArray marks = extmark_get(buf, (uint64_t)ns_id, l_row, l_col,
+ u_row, u_col, (int64_t)limit, reverse);
for (size_t i = 0; i < kv_size(marks); i++) {
- Array mark = ARRAY_DICT_INIT;
- ExtmarkInfo extmark = kv_A(marks, i);
- ADD(mark, INTEGER_OBJ((Integer)extmark.mark_id));
- ADD(mark, INTEGER_OBJ(extmark.row));
- ADD(mark, INTEGER_OBJ(extmark.col));
- ADD(rv, ARRAY_OBJ(mark));
+ ADD(rv, ARRAY_OBJ(extmark_to_array(kv_A(marks, i), true, (bool)details)));
}
kv_destroy(marks);
@@ -1260,27 +1337,36 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start,
/// Creates or updates an extmark.
///
/// To create a new extmark, pass id=0. The extmark id will be returned.
-// To move an existing mark, pass its id.
+/// To move an existing mark, pass its id.
///
/// It is also allowed to create a new mark by passing in a previously unused
/// id, but the caller must then keep track of existing and unused ids itself.
/// (Useful over RPC, to avoid waiting for the return value.)
///
+/// Using the optional arguments, it is possible to use this to highlight
+/// a range of text, and also to associate virtual text to the mark.
+///
/// @param buffer Buffer handle, or 0 for current buffer
/// @param ns_id Namespace id from |nvim_create_namespace()|
-/// @param id Extmark id, or 0 to create new
/// @param line Line number where to place the mark
/// @param col Column where to place the mark
-/// @param opts Optional parameters. Currently not used.
+/// @param opts Optional parameters.
+/// - id : id of the extmark to edit.
+/// - end_line : ending line of the mark, 0-based inclusive.
+/// - end_col : ending col of the mark, 0-based inclusive.
+/// - hl_group : name of the highlight group used to highlight
+/// this mark.
+/// - virt_text : virtual text to link to this mark.
/// @param[out] err Error details, if any
/// @return Id of the created/updated extmark
-Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id,
+Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id,
Integer line, Integer col,
Dictionary opts, Error *err)
FUNC_API_SINCE(7)
{
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
+ api_set_error(err, kErrorTypeValidation, "Invalid buffer id");
return 0;
}
@@ -1289,11 +1375,6 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id,
return 0;
}
- if (opts.size > 0) {
- api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
- return 0;
- }
-
size_t len = 0;
if (line < 0 || line > buf->b_ml.ml_line_count) {
api_set_error(err, kErrorTypeValidation, "line value outside range");
@@ -1309,18 +1390,113 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id,
return 0;
}
- uint64_t id_num;
- if (id >= 0) {
- id_num = (uint64_t)id;
- } else {
- api_set_error(err, kErrorTypeValidation, "Invalid mark id");
- return 0;
+ uint64_t id = 0;
+ int line2 = -1, hl_id = 0;
+ colnr_T col2 = 0;
+ VirtText virt_text = KV_INITIAL_VALUE;
+ for (size_t i = 0; i < opts.size; i++) {
+ String k = opts.items[i].key;
+ Object *v = &opts.items[i].value;
+ if (strequal("id", k.data)) {
+ if (v->type != kObjectTypeInteger || v->data.integer <= 0) {
+ api_set_error(err, kErrorTypeValidation,
+ "id is not a positive integer");
+ goto error;
+ }
+
+ id = (uint64_t)v->data.integer;
+ } else if (strequal("end_line", k.data)) {
+ if (v->type != kObjectTypeInteger) {
+ api_set_error(err, kErrorTypeValidation,
+ "end_line is not an integer");
+ goto error;
+ }
+ if (v->data.integer < 0 || v->data.integer > buf->b_ml.ml_line_count) {
+ api_set_error(err, kErrorTypeValidation,
+ "end_line value outside range");
+ goto error;
+ }
+
+ line2 = (int)v->data.integer;
+ } else if (strequal("end_col", k.data)) {
+ if (v->type != kObjectTypeInteger) {
+ api_set_error(err, kErrorTypeValidation,
+ "end_col is not an integer");
+ goto error;
+ }
+ if (v->data.integer < 0 || v->data.integer > MAXCOL) {
+ api_set_error(err, kErrorTypeValidation,
+ "end_col value outside range");
+ goto error;
+ }
+
+ col2 = (colnr_T)v->data.integer;
+ } else if (strequal("hl_group", k.data)) {
+ String hl_group;
+ switch (v->type) {
+ case kObjectTypeString:
+ hl_group = v->data.string;
+ hl_id = syn_check_group(
+ (char_u *)(hl_group.data),
+ (int)hl_group.size);
+ break;
+ case kObjectTypeInteger:
+ hl_id = (int)v->data.integer;
+ break;
+ default:
+ api_set_error(err, kErrorTypeValidation,
+ "hl_group is not valid.");
+ goto error;
+ }
+ } else if (strequal("virt_text", k.data)) {
+ if (v->type != kObjectTypeArray) {
+ api_set_error(err, kErrorTypeValidation,
+ "virt_text is not an Array");
+ goto error;
+ }
+ virt_text = parse_virt_text(v->data.array, err);
+ if (ERROR_SET(err)) {
+ goto error;
+ }
+ } else {
+ api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ goto error;
+ }
+ }
+
+ if (col2 >= 0) {
+ if (line2 >= 0) {
+ len = STRLEN(ml_get_buf(buf, (linenr_T)line2+1, false));
+ } else {
+ // reuse len from before
+ line2 = (int)line;
+ }
+ if (col2 > (Integer)len) {
+ api_set_error(err, kErrorTypeValidation,
+ "end_col value outside range");
+ goto error;
+ }
+ } else if (line2 >= 0) {
+ col2 = 0;
}
- id_num = extmark_set(buf, (uint64_t)ns_id, id_num,
- (int)line, (colnr_T)col, kExtmarkUndo);
+ Decoration *decor = NULL;
+ if (kv_size(virt_text)) {
+ decor = xcalloc(1, sizeof(*decor));
+ decor->hl_id = hl_id;
+ decor->virt_text = virt_text;
+ } else if (hl_id) {
+ decor = decoration_hl(hl_id);
+ }
- return (Integer)id_num;
+ id = extmark_set(buf, (uint64_t)ns_id, id,
+ (int)line, (colnr_T)col, line2, col2, decor, kExtmarkUndo);
+
+ return (Integer)id;
+
+error:
+ clear_virttext(&virt_text);
+ return 0;
}
/// Removes an extmark.
@@ -1358,17 +1534,17 @@ Boolean nvim_buf_del_extmark(Buffer buffer,
/// like signs and marks do.
///
/// Namespaces are used for batch deletion/updating of a set of highlights. To
-/// create a namespace, use |nvim_create_namespace| which returns a namespace
+/// create a namespace, use |nvim_create_namespace()| which returns a namespace
/// id. Pass it in to this function as `ns_id` to add highlights to the
/// namespace. All highlights in the same namespace can then be cleared with
-/// single call to |nvim_buf_clear_namespace|. If the highlight never will be
+/// single call to |nvim_buf_clear_namespace()|. If the highlight never will be
/// deleted by an API call, pass `ns_id = -1`.
///
/// As a shorthand, `ns_id = 0` can be used to create a new namespace for the
/// highlight, the allocated id is then returned. If `hl_group` is the empty
/// string no highlight is added, but a new `ns_id` is still returned. This is
/// supported for backwards compatibility, new code should use
-/// |nvim_create_namespace| to create a new empty namespace.
+/// |nvim_create_namespace()| to create a new empty namespace.
///
/// @param buffer Buffer handle, or 0 for current buffer
/// @param ns_id namespace to use or -1 for ungrouped highlight
@@ -1412,9 +1588,9 @@ Integer nvim_buf_add_highlight(Buffer buffer,
return src_id;
}
- int hlg_id = 0;
+ int hl_id = 0;
if (hl_group.size > 0) {
- hlg_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size);
+ hl_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size);
} else {
return src_id;
}
@@ -1425,10 +1601,10 @@ Integer nvim_buf_add_highlight(Buffer buffer,
end_line++;
}
- extmark_add_decoration(buf, ns_id, hlg_id,
- (int)line, (colnr_T)col_start,
- end_line, (colnr_T)col_end,
- VIRTTEXT_EMPTY);
+ ns_id = extmark_set(buf, ns_id, 0,
+ (int)line, (colnr_T)col_start,
+ end_line, (colnr_T)col_end,
+ decoration_hl(hl_id), kExtmarkUndo);
return src_id;
}
@@ -1470,7 +1646,7 @@ void nvim_buf_clear_namespace(Buffer buffer,
/// Clears highlights and virtual text from namespace and range of lines
///
-/// @deprecated use |nvim_buf_clear_namespace|.
+/// @deprecated use |nvim_buf_clear_namespace()|.
///
/// @param buffer Buffer handle, or 0 for current buffer
/// @param ns_id Namespace to clear, or -1 to clear all.
@@ -1534,11 +1710,11 @@ free_exit:
/// begin one cell (|lcs-eol| or space) after the ordinary text.
///
/// Namespaces are used to support batch deletion/updating of virtual text.
-/// To create a namespace, use |nvim_create_namespace|. Virtual text is
-/// cleared using |nvim_buf_clear_namespace|. The same `ns_id` can be used for
-/// both virtual text and highlights added by |nvim_buf_add_highlight|, both
-/// can then be cleared with a single call to |nvim_buf_clear_namespace|. If the
-/// virtual text never will be cleared by an API call, pass `ns_id = -1`.
+/// To create a namespace, use |nvim_create_namespace()|. Virtual text is
+/// cleared using |nvim_buf_clear_namespace()|. The same `ns_id` can be used for
+/// both virtual text and highlights added by |nvim_buf_add_highlight()|, both
+/// can then be cleared with a single call to |nvim_buf_clear_namespace()|. If
+/// the virtual text never will be cleared by an API call, pass `ns_id = -1`.
///
/// As a shorthand, `ns_id = 0` can be used to create a new namespace for the
/// virtual text, the allocated id is then returned.
@@ -1592,113 +1768,11 @@ Integer nvim_buf_set_virtual_text(Buffer buffer,
return src_id;
}
- extmark_add_decoration(buf, ns_id, 0,
- (int)line, 0, -1, -1,
- virt_text);
- return src_id;
-}
-
-/// Get the virtual text (annotation) for a buffer line.
-///
-/// The virtual text is returned as list of lists, whereas the inner lists have
-/// either one or two elements. The first element is the actual text, the
-/// optional second element is the highlight group.
-///
-/// The format is exactly the same as given to nvim_buf_set_virtual_text().
-///
-/// If there is no virtual text associated with the given line, an empty list
-/// is returned.
-///
-/// @param buffer Buffer handle, or 0 for current buffer
-/// @param line Line to get the virtual text from (zero-indexed)
-/// @param[out] err Error details, if any
-/// @return List of virtual text chunks
-Array nvim_buf_get_virtual_text(Buffer buffer, Integer line, Error *err)
- FUNC_API_SINCE(7)
-{
- Array chunks = ARRAY_DICT_INIT;
-
- buf_T *buf = find_buffer_by_handle(buffer, err);
- if (!buf) {
- return chunks;
- }
-
- if (line < 0 || line >= MAXLNUM) {
- api_set_error(err, kErrorTypeValidation, "Line number outside range");
- return chunks;
- }
-
- VirtText *virt_text = extmark_find_virttext(buf, (int)line, 0);
-
- if (!virt_text) {
- return chunks;
- }
-
- for (size_t i = 0; i < virt_text->size; i++) {
- Array chunk = ARRAY_DICT_INIT;
- VirtTextChunk *vtc = &virt_text->items[i];
- ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text)));
- if (vtc->hl_id > 0) {
- ADD(chunk, STRING_OBJ(cstr_to_string(
- (const char *)syn_id2name(vtc->hl_id))));
- }
- ADD(chunks, ARRAY_OBJ(chunk));
- }
-
- return chunks;
-}
-
-Integer nvim__buf_add_decoration(Buffer buffer, Integer ns_id, String hl_group,
- Integer start_row, Integer start_col,
- Integer end_row, Integer end_col,
- Array virt_text,
- Error *err)
-{
- buf_T *buf = find_buffer_by_handle(buffer, err);
- if (!buf) {
- return 0;
- }
+ Decoration *decor = xcalloc(1, sizeof(*decor));
+ decor->virt_text = virt_text;
- if (!ns_initialized((uint64_t)ns_id)) {
- api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
- return 0;
- }
-
-
- if (start_row < 0 || start_row >= MAXLNUM || end_row > MAXCOL) {
- api_set_error(err, kErrorTypeValidation, "Line number outside range");
- return 0;
- }
-
- if (start_col < 0 || start_col > MAXCOL || end_col > MAXCOL) {
- api_set_error(err, kErrorTypeValidation, "Column value outside range");
- return 0;
- }
- if (end_row < 0 || end_col < 0) {
- end_row = -1;
- end_col = -1;
- }
-
- if (start_row >= buf->b_ml.ml_line_count
- || end_row >= buf->b_ml.ml_line_count) {
- // safety check, we can't add marks outside the range
- return 0;
- }
-
- int hlg_id = 0;
- if (hl_group.size > 0) {
- hlg_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size);
- }
-
- VirtText vt = parse_virt_text(virt_text, err);
- if (ERROR_SET(err)) {
- return 0;
- }
-
- uint64_t mark_id = extmark_add_decoration(buf, (uint64_t)ns_id, hlg_id,
- (int)start_row, (colnr_T)start_col,
- (int)end_row, (colnr_T)end_col, vt);
- return (Integer)mark_id;
+ extmark_set(buf, ns_id, 0, (int)line, 0, -1, -1, decor, kExtmarkUndo);
+ return src_id;
}
Dictionary nvim__buf_stats(Buffer buffer, Error *err)
@@ -1721,6 +1795,7 @@ Dictionary nvim__buf_stats(Buffer buffer, Error *err)
// NB: this should be zero at any time API functions are called,
// this exists to debug issues
PUT(rv, "dirty_bytes", INTEGER_OBJ((Integer)buf->deleted_bytes));
+ PUT(rv, "dirty_bytes2", INTEGER_OBJ((Integer)buf->deleted_bytes2));
u_header_T *uhp = NULL;
if (buf->b_u_curhead != NULL) {
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index f194b6b474..13f77d2d85 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1198,7 +1198,7 @@ void api_free_object(Object value)
break;
case kObjectTypeLuaRef:
- executor_free_luaref(value.data.luaref);
+ api_free_luaref(value.data.luaref);
break;
default:
diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h
index ab31db39e9..ef5e90bf5c 100644
--- a/src/nvim/api/ui_events.in.h
+++ b/src/nvim/api/ui_events.in.h
@@ -36,6 +36,8 @@ void set_title(String title)
FUNC_API_SINCE(3);
void set_icon(String icon)
FUNC_API_SINCE(3);
+void screenshot(String path)
+ FUNC_API_SINCE(7) FUNC_API_REMOTE_IMPL;
void option_set(String name, Object value)
FUNC_API_SINCE(4) FUNC_API_BRIDGE_IMPL;
// Stop event is not exported as such, represented by EOF in the msgpack stream.
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 305d5e6968..9155ffcfb8 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -204,9 +204,9 @@ Integer nvim_get_hl_id_by_name(String name)
///
/// On execution error: does not fail, but updates v:errmsg.
///
-/// If you need to input sequences like <C-o> use |nvim_replace_termcodes|
-/// to replace the termcodes and then pass the resulting string to
-/// nvim_feedkeys. You'll also want to enable escape_csi.
+/// If you need to input sequences like <C-o> use |nvim_replace_termcodes| to
+/// replace the termcodes and then pass the resulting string to nvim_feedkeys.
+/// You'll also want to enable escape_csi.
///
/// Example:
/// <pre>
@@ -475,7 +475,7 @@ Object nvim_execute_lua(String code, Array args, Error *err)
FUNC_API_DEPRECATED_SINCE(7)
FUNC_API_REMOTE_ONLY
{
- return executor_exec_lua_api(code, args, err);
+ return nlua_exec(code, args, err);
}
/// Execute Lua code. Parameters (if any) are available as `...` inside the
@@ -494,7 +494,7 @@ Object nvim_exec_lua(String code, Array args, Error *err)
FUNC_API_SINCE(7)
FUNC_API_REMOTE_ONLY
{
- return executor_exec_lua_api(code, args, err);
+ return nlua_exec(code, args, err);
}
/// Calls a VimL function.
@@ -2477,7 +2477,7 @@ Array nvim_get_proc_children(Integer pid, Error *err)
Array a = ARRAY_DICT_INIT;
ADD(a, INTEGER_OBJ(pid));
String s = cstr_to_string("return vim._os_proc_children(select(1, ...))");
- Object o = nvim_exec_lua(s, a, err);
+ Object o = nlua_exec(s, a, err);
api_free_string(s);
api_free_array(a);
if (o.type == kObjectTypeArray) {
@@ -2523,7 +2523,7 @@ Object nvim_get_proc(Integer pid, Error *err)
Array a = ARRAY_DICT_INIT;
ADD(a, INTEGER_OBJ(pid));
String s = cstr_to_string("return vim._os_proc_info(select(1, ...))");
- Object o = nvim_exec_lua(s, a, err);
+ Object o = nlua_exec(s, a, err);
api_free_string(s);
api_free_array(a);
if (o.type == kObjectTypeArray && o.data.array.size == 0) {
@@ -2627,3 +2627,9 @@ void nvim__put_attr(Integer id, Integer start_row, Integer start_col,
decorations_add_luahl_attr(attr, (int)start_row, (colnr_T)start_col,
(int)end_row, (colnr_T)end_col);
}
+
+void nvim__screenshot(String path)
+ FUNC_API_FAST
+{
+ ui_call_screenshot(path);
+}
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 550f8a5e40..b3c95f9362 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -835,6 +835,7 @@ struct file_buffer {
// tree-sitter) or the corresponding UTF-32/UTF-16 size (like LSP) of the
// deleted text.
size_t deleted_bytes;
+ size_t deleted_bytes2;
size_t deleted_codepoints;
size_t deleted_codeunits;
diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c
index e6393bf02c..fc671ad9e2 100644
--- a/src/nvim/buffer_updates.c
+++ b/src/nvim/buffer_updates.c
@@ -2,6 +2,7 @@
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
#include "nvim/buffer_updates.h"
+#include "nvim/extmark.h"
#include "nvim/memline.h"
#include "nvim/api/private/helpers.h"
#include "nvim/msgpack_rpc/channel.h"
@@ -157,7 +158,7 @@ void buf_updates_unregister_all(buf_T *buf)
args.items[0] = BUFFER_OBJ(buf->handle);
textlock++;
- executor_exec_lua_cb(cb.on_detach, "detach", args, false, NULL);
+ nlua_call_ref(cb.on_detach, "detach", args, false, NULL);
textlock--;
}
free_update_callbacks(cb);
@@ -265,7 +266,7 @@ void buf_updates_send_changes(buf_T *buf,
args.items[7] = INTEGER_OBJ((Integer)deleted_codeunits);
}
textlock++;
- Object res = executor_exec_lua_cb(cb.on_lines, "lines", args, true, NULL);
+ Object res = nlua_call_ref(cb.on_lines, "lines", args, true, NULL);
textlock--;
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
@@ -281,12 +282,14 @@ void buf_updates_send_changes(buf_T *buf,
kv_size(buf->update_callbacks) = j;
}
-void buf_updates_send_splice(buf_T *buf,
- linenr_T start_line, colnr_T start_col,
- linenr_T oldextent_line, colnr_T oldextent_col,
- linenr_T newextent_line, colnr_T newextent_col)
+void buf_updates_send_splice(
+ buf_T *buf,
+ int start_row, colnr_T start_col, bcount_t start_byte,
+ int old_row, colnr_T old_col, bcount_t old_byte,
+ int new_row, colnr_T new_col, bcount_t new_byte)
{
- if (!buf_updates_active(buf)) {
+ if (!buf_updates_active(buf)
+ || (old_byte == 0 && new_byte == 0)) {
return;
}
@@ -296,7 +299,7 @@ void buf_updates_send_splice(buf_T *buf,
BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i);
bool keep = true;
if (cb.on_bytes != LUA_NOREF) {
- FIXED_TEMP_ARRAY(args, 8);
+ FIXED_TEMP_ARRAY(args, 11);
// the first argument is always the buffer handle
args.items[0] = BUFFER_OBJ(buf->handle);
@@ -304,15 +307,18 @@ void buf_updates_send_splice(buf_T *buf,
// next argument is b:changedtick
args.items[1] = INTEGER_OBJ(buf_get_changedtick(buf));
- args.items[2] = INTEGER_OBJ(start_line);
+ args.items[2] = INTEGER_OBJ(start_row);
args.items[3] = INTEGER_OBJ(start_col);
- args.items[4] = INTEGER_OBJ(oldextent_line);
- args.items[5] = INTEGER_OBJ(oldextent_col);
- args.items[6] = INTEGER_OBJ(newextent_line);
- args.items[7] = INTEGER_OBJ(newextent_col);
+ args.items[4] = INTEGER_OBJ(start_byte);
+ args.items[5] = INTEGER_OBJ(old_row);
+ args.items[6] = INTEGER_OBJ(old_col);
+ args.items[7] = INTEGER_OBJ(old_byte);
+ args.items[8] = INTEGER_OBJ(new_row);
+ args.items[9] = INTEGER_OBJ(new_col);
+ args.items[10] = INTEGER_OBJ(new_byte);
textlock++;
- Object res = executor_exec_lua_cb(cb.on_bytes, "bytes", args, true, NULL);
+ Object res = nlua_call_ref(cb.on_bytes, "bytes", args, true, NULL);
textlock--;
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
@@ -347,8 +353,8 @@ void buf_updates_changedtick(buf_T *buf)
args.items[1] = INTEGER_OBJ(buf_get_changedtick(buf));
textlock++;
- Object res = executor_exec_lua_cb(cb.on_changedtick, "changedtick",
- args, true, NULL);
+ Object res = nlua_call_ref(cb.on_changedtick, "changedtick",
+ args, true, NULL);
textlock--;
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
@@ -382,6 +388,6 @@ void buf_updates_changedtick_single(buf_T *buf, uint64_t channel_id)
static void free_update_callbacks(BufUpdateCallbacks cb)
{
- executor_free_luaref(cb.on_lines);
- executor_free_luaref(cb.on_changedtick);
+ api_free_luaref(cb.on_lines);
+ api_free_luaref(cb.on_changedtick);
}
diff --git a/src/nvim/buffer_updates.h b/src/nvim/buffer_updates.h
index b2d0a62270..961fec879b 100644
--- a/src/nvim/buffer_updates.h
+++ b/src/nvim/buffer_updates.h
@@ -2,6 +2,7 @@
#define NVIM_BUFFER_UPDATES_H
#include "nvim/buffer_defs.h"
+#include "nvim/extmark.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "buffer_updates.h.generated.h"
diff --git a/src/nvim/change.c b/src/nvim/change.c
index 51afb40b40..b8bc08b747 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -366,7 +366,7 @@ void changed_bytes(linenr_T lnum, colnr_T col)
static void inserted_bytes(linenr_T lnum, colnr_T col, int old, int new)
{
if (curbuf_splice_pending == 0) {
- extmark_splice(curbuf, (int)lnum-1, col, 0, old, 0, new, kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)lnum-1, col, old, new, kExtmarkUndo);
}
changed_bytes(lnum, col);
@@ -1597,7 +1597,7 @@ int open_line(
if (curwin->w_cursor.lnum + 1 < curbuf->b_ml.ml_line_count
|| curwin->w_p_diff) {
mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L,
- kExtmarkUndo);
+ kExtmarkNOOP);
}
did_append = true;
} else {
@@ -1611,6 +1611,7 @@ int open_line(
}
ml_replace(curwin->w_cursor.lnum, p_extra, true);
changed_bytes(curwin->w_cursor.lnum, 0);
+ // TODO(vigoux): extmark_splice_cols here??
curwin->w_cursor.lnum--;
did_append = false;
}
@@ -1676,6 +1677,9 @@ int open_line(
truncate_spaces(saved_line);
}
ml_replace(curwin->w_cursor.lnum, saved_line, false);
+ extmark_splice_cols(
+ curbuf, (int)curwin->w_cursor.lnum,
+ 0, curwin->w_cursor.col, (int)STRLEN(saved_line), kExtmarkUndo);
saved_line = NULL;
if (did_append) {
changed_lines(curwin->w_cursor.lnum, curwin->w_cursor.col,
@@ -1691,8 +1695,9 @@ int open_line(
// Always move extmarks - Here we move only the line where the
// cursor is, the previous mark_adjust takes care of the lines after
int cols_added = mincol-1+less_cols_off-less_cols;
- extmark_splice(curbuf, (int)lnum-1, mincol-1, 0, less_cols_off,
- 1, cols_added, kExtmarkUndo);
+ extmark_splice(curbuf, (int)lnum-1, mincol-1,
+ 0, less_cols_off, less_cols_off,
+ 1, cols_added, 1 + cols_added, kExtmarkUndo);
} else {
changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col);
}
@@ -1704,8 +1709,10 @@ int open_line(
}
if (did_append) {
changed_lines(curwin->w_cursor.lnum, 0, curwin->w_cursor.lnum, 1L, true);
- extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1,
- 0, 0, 0, 1, 0, kExtmarkUndo);
+ // bail out and just get the final lenght of the line we just manipulated
+ bcount_t extra = (bcount_t)STRLEN(ml_get(curwin->w_cursor.lnum));
+ extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, 0,
+ 0, 0, 0, 1, 0, 1+extra, kExtmarkUndo);
}
curbuf_splice_pending--;
diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c
index 65d95ff158..dd32cef1e3 100644
--- a/src/nvim/digraph.c
+++ b/src/nvim/digraph.c
@@ -856,6 +856,7 @@ static digr_T digraphdefault[] =
{ '9', '"', 0x201f },
{ '/', '-', 0x2020 },
{ '/', '=', 0x2021 },
+ { 'o', 'o', 0x2022 },
{ '.', '.', 0x2025 },
{ ',', '.', 0x2026 },
{ '%', '0', 0x2030 },
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index ea38221dc7..1e149da1dc 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -1919,10 +1919,10 @@ change_indent (
// TODO(bfredl): test for crazy edge cases, like we stand on a TAB or
// something? does this even do the right text change then?
int delta = orig_col - new_col;
- extmark_splice(curbuf, curwin->w_cursor.lnum-1, new_col,
- 0, delta < 0 ? -delta : 0,
- 0, delta > 0 ? delta : 0,
- kExtmarkUndo);
+ extmark_splice_cols(curbuf, curwin->w_cursor.lnum-1, new_col,
+ delta < 0 ? -delta : 0,
+ delta > 0 ? delta : 0,
+ kExtmarkUndo);
}
}
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 5aeb6fa746..32830c5d7f 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -7075,7 +7075,7 @@ void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv)
do {
size_t dir_len;
const char *dir;
- iter = vim_env_iter(':', dirs, iter, &dir, &dir_len);
+ iter = vim_env_iter(ENV_SEPCHAR, dirs, iter, &dir, &dir_len);
if (dir != NULL && dir_len > 0) {
char *dir_with_nvim = xmemdupz(dir, dir_len);
dir_with_nvim = concat_fnames_realloc(dir_with_nvim, "nvim", true);
@@ -10383,10 +10383,13 @@ void script_host_eval(char *name, typval_T *argvars, typval_T *rettv)
list_T *args = tv_list_alloc(1);
tv_list_append_string(args, (const char *)argvars[0].vval.v_string, -1);
- *rettv = eval_call_provider(name, "eval", args);
+ *rettv = eval_call_provider(name, "eval", args, false);
}
-typval_T eval_call_provider(char *provider, char *method, list_T *arguments)
+/// @param discard Clears the value returned by the provider and returns
+/// an empty typval_T.
+typval_T eval_call_provider(char *provider, char *method, list_T *arguments,
+ bool discard)
{
if (!eval_has_provider(provider)) {
emsgf("E319: No \"%s\" provider found. Run \":checkhealth provider\"",
@@ -10445,6 +10448,10 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)
provider_call_nesting--;
assert(provider_call_nesting >= 0);
+ if (discard) {
+ tv_clear(&rettv);
+ }
+
return rettv;
}
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index be16ddd7f6..372c950825 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -256,6 +256,7 @@ return {
py3eval={args=1},
pyeval={args=1},
pyxeval={args=1},
+ perleval={args=1},
range={args={1, 3}},
readdir={args={1, 2}},
readfile={args={1, 3}},
diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c
index daba304f00..638fef331a 100644
--- a/src/nvim/eval/decode.c
+++ b/src/nvim/eval/decode.c
@@ -586,7 +586,7 @@ parse_json_number_check:
if (p == ints) {
emsgf(_("E474: Missing number after minus sign: %.*s"), LENP(s, e));
goto parse_json_number_fail;
- } else if (p == fracs || exps_s == fracs + 1) {
+ } else if (p == fracs || (fracs != NULL && exps_s == fracs + 1)) {
emsgf(_("E474: Missing number after decimal dot: %.*s"), LENP(s, e));
goto parse_json_number_fail;
} else if (p == exps) {
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index ac560124bf..3a4b4f2a50 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -5482,7 +5482,7 @@ static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
- executor_eval_lua(cstr_as_string((char *)str), &argvars[1], rettv);
+ nlua_typval_eval(cstr_as_string((char *)str), &argvars[1], rettv);
}
/*
@@ -6374,6 +6374,14 @@ static void f_pyxeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+///
+/// "perleval()" function
+///
+static void f_perleval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ script_host_eval("perl", argvars, rettv);
+}
+
/*
* "range()" function
*/
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index c4a7a210f1..1b80b22213 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -1395,8 +1395,7 @@ call_func(
if (is_luafunc(partial)) {
if (len > 0) {
error = ERROR_NONE;
- executor_call_lua((const char *)funcname, len,
- argvars, argcount, rettv);
+ nlua_typval_call((const char *)funcname, len, argvars, argcount, rettv);
}
} else if (!builtin_function((const char *)rfname, -1)) {
// User defined function.
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 519978f4fb..9be6adcd61 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -326,14 +326,19 @@ static int linelen(int *has_tab)
int save;
int len;
- /* find the first non-blank character */
+ // Get the line. If it's empty bail out early (could be the empty string
+ // for an unloaded buffer).
line = get_cursor_line_ptr();
+ if (*line == NUL) {
+ return 0;
+ }
+ // find the first non-blank character
first = skipwhite(line);
- /* find the character after the last non-blank character */
+ // find the character after the last non-blank character
for (last = first + STRLEN(first);
- last > first && ascii_iswhite(last[-1]); --last)
- ;
+ last > first && ascii_iswhite(last[-1]); last--) {
+ }
save = *last;
*last = NUL;
// Get line length.
@@ -846,6 +851,11 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
return OK;
}
+ bcount_t start_byte = ml_find_line_or_offset(curbuf, line1, NULL, true);
+ bcount_t end_byte = ml_find_line_or_offset(curbuf, line2+1, NULL, true);
+ bcount_t extent_byte = end_byte-start_byte;
+ bcount_t dest_byte = ml_find_line_or_offset(curbuf, dest+1, NULL, true);
+
num_lines = line2 - line1 + 1;
/*
@@ -880,6 +890,8 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
last_line = curbuf->b_ml.ml_line_count;
mark_adjust_nofold(line1, line2, last_line - line2, 0L, kExtmarkNOOP);
changed_lines(last_line - num_lines + 1, 0, last_line + 1, num_lines, false);
+ int line_off = 0;
+ bcount_t byte_off = 0;
if (dest >= line2) {
mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, kExtmarkNOOP);
FOR_ALL_TAB_WINDOWS(tab, win) {
@@ -889,6 +901,8 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
}
curbuf->b_op_start.lnum = dest - num_lines + 1;
curbuf->b_op_end.lnum = dest;
+ line_off = -num_lines;
+ byte_off = -extent_byte;
} else {
mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, kExtmarkNOOP);
FOR_ALL_TAB_WINDOWS(tab, win) {
@@ -904,11 +918,10 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
-(last_line - dest - extra), 0L, kExtmarkNOOP);
// extmarks are handled separately
- int size = line2-line1+1;
- int off = dest >= line2 ? -size : 0;
- extmark_move_region(curbuf, line1-1, 0,
- line2-line1+1, 0,
- dest+off, 0, kExtmarkUndo);
+ extmark_move_region(curbuf, line1-1, 0, start_byte,
+ line2-line1+1, 0, extent_byte,
+ dest+line_off, 0, dest_byte+byte_off,
+ kExtmarkUndo);
changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra, false);
@@ -3908,6 +3921,18 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
ADJUST_SUB_FIRSTLNUM();
+ // TODO(bfredl): adjust also in preview, because decorations?
+ // this has some robustness issues, will look into later.
+ bool do_splice = !preview;
+ bcount_t replaced_bytes = 0;
+ lpos_T start = regmatch.startpos[0], end = regmatch.endpos[0];
+ if (do_splice) {
+ for (i = 0; i < nmatch-1; i++) {
+ replaced_bytes += STRLEN(ml_get(lnum_start+i)) + 1;
+ }
+ replaced_bytes += end.col - start.col;
+ }
+
// Now the trick is to replace CTRL-M chars with a real line
// break. This would make it impossible to insert a CTRL-M in
@@ -3951,17 +3976,14 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
current_match.end.col = new_endcol;
current_match.end.lnum = lnum;
- // TODO(bfredl): adjust in preview, because decorations?
- // this has some robustness issues, will look into later.
- if (!preview) {
- lpos_T start = regmatch.startpos[0], end = regmatch.endpos[0];
+ if (do_splice) {
int matchcols = end.col - ((end.lnum == start.lnum)
? start.col : 0);
int subcols = new_endcol - ((lnum == lnum_start) ? start_col : 0);
extmark_splice(curbuf, lnum_start-1, start_col,
- end.lnum-start.lnum, matchcols,
- lnum-lnum_start, subcols, kExtmarkUndo);
- }
+ end.lnum-start.lnum, matchcols, replaced_bytes,
+ lnum-lnum_start, subcols, sublen-1, kExtmarkUndo);
+ }
}
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index 252af409c0..a01f92df27 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -1927,13 +1927,19 @@ return {
command='perl',
flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, SBOXOK, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
- func='ex_script_ni',
+ func='ex_perl',
},
{
command='perldo',
flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
- func='ex_ni',
+ func='ex_perldo',
+ },
+ {
+ command='perlfile',
+ flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, RESTRICT),
+ addr_type=ADDR_LINES,
+ func='ex_perlfile',
},
{
command='pedit',
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index 7a06cb7ca6..3e169f7a4e 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -935,6 +935,21 @@ void ex_pydo3(exarg_T *eap)
script_host_do_range("python3", eap);
}
+void ex_perl(exarg_T *eap)
+{
+ script_host_execute("perl", eap);
+}
+
+void ex_perlfile(exarg_T *eap)
+{
+ script_host_execute_file("perl", eap);
+}
+
+void ex_perldo(exarg_T *eap)
+{
+ script_host_do_range("perl", eap);
+}
+
// Command line expansion for :profile.
static enum {
PEXP_SUBCMD, ///< expand :profile sub-commands
@@ -4157,7 +4172,7 @@ static void script_host_execute(char *name, exarg_T *eap)
tv_list_append_number(args, (int)eap->line1);
tv_list_append_number(args, (int)eap->line2);
- (void)eval_call_provider(name, "execute", args);
+ (void)eval_call_provider(name, "execute", args, true);
}
}
@@ -4172,7 +4187,7 @@ static void script_host_execute_file(char *name, exarg_T *eap)
// current range
tv_list_append_number(args, (int)eap->line1);
tv_list_append_number(args, (int)eap->line2);
- (void)eval_call_provider(name, "execute_file", args);
+ (void)eval_call_provider(name, "execute_file", args, true);
}
static void script_host_do_range(char *name, exarg_T *eap)
@@ -4181,7 +4196,7 @@ static void script_host_do_range(char *name, exarg_T *eap)
tv_list_append_number(args, (int)eap->line1);
tv_list_append_number(args, (int)eap->line2);
tv_list_append_string(args, (const char *)eap->arg, -1);
- (void)eval_call_provider(name, "do_range", args);
+ (void)eval_call_provider(name, "do_range", args, true);
}
/// ":drop"
diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c
index 1457a1172d..3a04908ccb 100644
--- a/src/nvim/extmark.c
+++ b/src/nvim/extmark.c
@@ -1,29 +1,24 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-// Implements extended marks for plugins. Each mark exists in a btree of
-// lines containing btrees of columns.
+// Implements extended marks for plugins. Marks sit in a MarkTree
+// datastructure which provides both efficient mark insertations/lookups
+// and adjustment to text changes. See marktree.c for more details.
//
-// The btree provides efficient range lookups.
// A map of pointers to the marks is used for fast lookup by mark id.
//
-// Marks are moved by calls to extmark_splice. Additionally mark_adjust
-// might adjust extmarks to line inserts/deletes.
+// Marks are moved by calls to extmark_splice. Some standard interfaces
+// mark_adjust and inserted_bytes already adjust marks, check if these are
+// being used before adding extmark_splice calls!
//
// Undo/Redo of marks is implemented by storing the call arguments to
// extmark_splice. The list of arguments is applied in extmark_apply_undo.
-// The only case where we have to copy extmarks is for the area being effected
-// by a delete.
+// We have to copy extmark positions when the extmarks are within a
+// deleted/changed region.
//
// Marks live in namespaces that allow plugins/users to segregate marks
// from other users.
//
-// For possible ideas for efficency improvements see:
-// http://blog.atom.io/2015/06/16/optimizing-an-important-atom-primitive.html
-// TODO(bfredl): These ideas could be used for an enhanced btree, which
-// wouldn't need separate line and column layers.
-// Other implementations exist in gtk and tk toolkits.
-//
// Deleting marks only happens when explicitly calling extmark_del, deleteing
// over a range of marks will only move the marks. Deleting on a mark will
// leave it in same position unless it is on the EOL of a line.
@@ -48,6 +43,13 @@
# include "extmark.c.generated.h"
#endif
+static PMap(uint64_t) *hl_decors;
+
+void extmark_init(void)
+{
+ hl_decors = pmap_new(uint64_t)();
+}
+
static ExtmarkNs *buf_ns_ref(buf_T *buf, uint64_t ns_id, bool put) {
if (!buf->b_extmark_ns) {
if (!put) {
@@ -71,7 +73,8 @@ static ExtmarkNs *buf_ns_ref(buf_T *buf, uint64_t ns_id, bool put) {
/// must not be used during iteration!
/// @returns the mark id
uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id,
- int row, colnr_T col, ExtmarkOp op)
+ int row, colnr_T col, int end_row, colnr_T end_col,
+ Decoration *decor, ExtmarkOp op)
{
ExtmarkNs *ns = buf_ns_ref(buf, ns_id, true);
mtpos_t old_pos;
@@ -82,7 +85,7 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id,
} else {
uint64_t old_mark = map_get(uint64_t, uint64_t)(ns->map, id);
if (old_mark) {
- if (old_mark & MARKTREE_PAIRED_FLAG) {
+ if (old_mark & MARKTREE_PAIRED_FLAG || end_row > -1) {
extmark_del(buf, ns_id, id);
} else {
// TODO(bfredl): we need to do more if "revising" a decoration mark.
@@ -90,7 +93,12 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id,
old_pos = marktree_lookup(buf->b_marktree, old_mark, itr);
assert(itr->node);
if (old_pos.row == row && old_pos.col == col) {
- map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, old_mark);
+ ExtmarkItem it = map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index,
+ old_mark);
+ if (it.decor) {
+ decoration_redraw(buf, row, row, it.decor);
+ free_decoration(it.decor);
+ }
mark = marktree_revise(buf->b_marktree, itr);
goto revised;
}
@@ -101,11 +109,17 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id,
}
}
- mark = marktree_put(buf->b_marktree, row, col, true);
+ if (end_row > -1) {
+ mark = marktree_put_pair(buf->b_marktree,
+ row, col, true,
+ end_row, end_col, false);
+ } else {
+ mark = marktree_put(buf->b_marktree, row, col, true);
+ }
+
revised:
map_put(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark,
- (ExtmarkItem){ ns_id, id, 0,
- KV_INITIAL_VALUE });
+ (ExtmarkItem){ ns_id, id, decor });
map_put(uint64_t, uint64_t)(ns->map, id, mark);
if (op != kExtmarkNoUndo) {
@@ -114,6 +128,10 @@ revised:
// adding new marks to old undo headers.
u_extmark_set(buf, mark, row, col);
}
+
+ if (decor) {
+ decoration_redraw(buf, row, end_row > -1 ? end_row : row, decor);
+ }
return id;
}
@@ -152,27 +170,23 @@ bool extmark_del(buf_T *buf, uint64_t ns_id, uint64_t id)
assert(pos.row >= 0);
marktree_del_itr(buf->b_marktree, itr, false);
ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark);
+ mtpos_t pos2 = pos;
if (mark & MARKTREE_PAIRED_FLAG) {
- mtpos_t pos2 = marktree_lookup(buf->b_marktree,
- mark|MARKTREE_END_FLAG, itr);
+ pos2 = marktree_lookup(buf->b_marktree, mark|MARKTREE_END_FLAG, itr);
assert(pos2.row >= 0);
marktree_del_itr(buf->b_marktree, itr, false);
- if (item.hl_id && pos2.row >= pos.row) {
- redraw_buf_range_later(buf, pos.row+1, pos2.row+1);
- }
}
- if (kv_size(item.virt_text)) {
- redraw_buf_line_later(buf, pos.row+1);
+ if (item.decor) {
+ decoration_redraw(buf, pos.row, pos2.row, item.decor);
+ free_decoration(item.decor);
}
- clear_virttext(&item.virt_text);
map_del(uint64_t, uint64_t)(ns->map, id);
map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark);
// TODO(bfredl): delete it from current undo header, opportunistically?
-
return true;
}
@@ -202,9 +216,11 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id,
}
// the value is either zero or the lnum (row+1) if highlight was present.
- static Map(uint64_t, uint64_t) *delete_set = NULL;
+ static Map(uint64_t, ssize_t) *delete_set = NULL;
+ typedef struct { Decoration *decor; int row1; } DecorItem;
+ static kvec_t(DecorItem) decors;
if (delete_set == NULL) {
- delete_set = map_new(uint64_t, uint64_t)();
+ delete_set = map_new(uint64_t, ssize_t)();
}
MarkTreeIter itr[1] = { 0 };
@@ -216,14 +232,16 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id,
|| (mark.row == u_row && mark.col > u_col)) {
break;
}
- uint64_t *del_status = map_ref(uint64_t, uint64_t)(delete_set, mark.id,
- false);
+ ssize_t *del_status = map_ref(uint64_t, ssize_t)(delete_set, mark.id,
+ false);
if (del_status) {
marktree_del_itr(buf->b_marktree, itr, false);
- map_del(uint64_t, uint64_t)(delete_set, mark.id);
- if (*del_status > 0) {
- redraw_buf_range_later(buf, (linenr_T)(*del_status), mark.row+1);
+ if (*del_status >= 0) { // we had a decor_id
+ DecorItem it = kv_A(decors, *del_status);
+ decoration_redraw(buf, it.row1, mark.row, it.decor);
+ free_decoration(it.decor);
}
+ map_del(uint64_t, ssize_t)(delete_set, mark.id);
continue;
}
@@ -233,15 +251,21 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id,
assert(item.ns_id > 0 && item.mark_id > 0);
if (item.mark_id > 0 && (item.ns_id == ns_id || all_ns)) {
- if (kv_size(item.virt_text)) {
- redraw_buf_line_later(buf, mark.row+1);
- }
- clear_virttext(&item.virt_text);
marks_cleared = true;
if (mark.id & MARKTREE_PAIRED_FLAG) {
uint64_t other = mark.id ^ MARKTREE_END_FLAG;
- uint64_t status = item.hl_id ? ((uint64_t)mark.row+1) : 0;
- map_put(uint64_t, uint64_t)(delete_set, other, status);
+ ssize_t decor_id = -1;
+ if (item.decor) {
+ // Save the decoration and the first pos. Clear the decoration
+ // later when we know the full range.
+ decor_id = (ssize_t)kv_size(decors);
+ kv_push(decors,
+ ((DecorItem) { .decor = item.decor, .row1 = mark.row }));
+ }
+ map_put(uint64_t, ssize_t)(delete_set, other, decor_id);
+ } else if (item.decor) {
+ decoration_redraw(buf, mark.row, mark.row, item.decor);
+ free_decoration(item.decor);
}
ExtmarkNs *my_ns = all_ns ? buf_ns_ref(buf, item.ns_id, false) : ns;
map_del(uint64_t, uint64_t)(my_ns->map, item.mark_id);
@@ -251,16 +275,20 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id,
marktree_itr_next(buf->b_marktree, itr);
}
}
- uint64_t id, status;
- map_foreach(delete_set, id, status, {
+ uint64_t id;
+ ssize_t decor_id;
+ map_foreach(delete_set, id, decor_id, {
mtpos_t pos = marktree_lookup(buf->b_marktree, id, itr);
assert(itr->node);
marktree_del_itr(buf->b_marktree, itr, false);
- if (status > 0) {
- redraw_buf_range_later(buf, (linenr_T)status, pos.row+1);
+ if (decor_id >= 0) {
+ DecorItem it = kv_A(decors, decor_id);
+ decoration_redraw(buf, it.row1, pos.row, it.decor);
+ free_decoration(it.decor);
}
});
- map_clear(uint64_t, uint64_t)(delete_set);
+ map_clear(uint64_t, ssize_t)(delete_set);
+ kv_size(decors) = 0;
return marks_cleared;
}
@@ -270,31 +298,44 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id,
// will be searched to the start, or end
// dir can be set to control the order of the array
// amount = amount of marks to find or -1 for all
-ExtmarkArray extmark_get(buf_T *buf, uint64_t ns_id,
- int l_row, colnr_T l_col,
- int u_row, colnr_T u_col,
- int64_t amount, bool reverse)
+ExtmarkInfoArray extmark_get(buf_T *buf, uint64_t ns_id,
+ int l_row, colnr_T l_col,
+ int u_row, colnr_T u_col,
+ int64_t amount, bool reverse)
{
- ExtmarkArray array = KV_INITIAL_VALUE;
- MarkTreeIter itr[1] = { 0 };
+ ExtmarkInfoArray array = KV_INITIAL_VALUE;
+ MarkTreeIter itr[1];
// Find all the marks
marktree_itr_get_ext(buf->b_marktree, (mtpos_t){ l_row, l_col },
itr, reverse, false, NULL);
int order = reverse ? -1 : 1;
while ((int64_t)kv_size(array) < amount) {
mtmark_t mark = marktree_itr_current(itr);
+ mtpos_t endpos = { -1, -1 };
if (mark.row < 0
|| (mark.row - u_row) * order > 0
|| (mark.row == u_row && (mark.col - u_col) * order > 0)) {
break;
}
+ if (mark.id & MARKTREE_END_FLAG) {
+ goto next_mark;
+ } else if (mark.id & MARKTREE_PAIRED_FLAG) {
+ endpos = marktree_lookup(buf->b_marktree, mark.id | MARKTREE_END_FLAG,
+ NULL);
+ }
+
+
ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index,
mark.id);
if (item.ns_id == ns_id) {
kv_push(array, ((ExtmarkInfo) { .ns_id = item.ns_id,
.mark_id = item.mark_id,
- .row = mark.row, .col = mark.col }));
+ .row = mark.row, .col = mark.col,
+ .end_row = endpos.row,
+ .end_col = endpos.col,
+ .decor = item.decor }));
}
+next_mark:
if (reverse) {
marktree_itr_prev(buf->b_marktree, itr);
} else {
@@ -308,7 +349,7 @@ ExtmarkArray extmark_get(buf_T *buf, uint64_t ns_id,
ExtmarkInfo extmark_from_id(buf_T *buf, uint64_t ns_id, uint64_t id)
{
ExtmarkNs *ns = buf_ns_ref(buf, ns_id, false);
- ExtmarkInfo ret = { 0, 0, -1, -1 };
+ ExtmarkInfo ret = { 0, 0, -1, -1, -1, -1, NULL };
if (!ns) {
return ret;
}
@@ -319,12 +360,22 @@ ExtmarkInfo extmark_from_id(buf_T *buf, uint64_t ns_id, uint64_t id)
}
mtpos_t pos = marktree_lookup(buf->b_marktree, mark, NULL);
+ mtpos_t endpos = { -1, -1 };
+ if (mark & MARKTREE_PAIRED_FLAG) {
+ endpos = marktree_lookup(buf->b_marktree, mark | MARKTREE_END_FLAG, NULL);
+ }
assert(pos.row >= 0);
+ ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index,
+ mark);
+
ret.ns_id = ns_id;
ret.mark_id = id;
ret.row = pos.row;
ret.col = pos.col;
+ ret.end_row = endpos.row;
+ ret.end_col = endpos.col;
+ ret.decor = item.decor;
return ret;
}
@@ -352,7 +403,7 @@ void extmark_free_all(buf_T *buf)
map_foreach(buf->b_extmark_index, id, item, {
(void)id;
- clear_virttext(&item.virt_text);
+ free_decoration(item.decor);
});
map_free(uint64_t, ExtmarkItem)(buf->b_extmark_index);
buf->b_extmark_index = NULL;
@@ -428,18 +479,18 @@ void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo)
// Undo
ExtmarkSplice splice = undo_info.data.splice;
if (undo) {
- extmark_splice(curbuf,
- splice.start_row, splice.start_col,
- splice.newextent_row, splice.newextent_col,
- splice.oldextent_row, splice.oldextent_col,
- kExtmarkNoUndo);
+ extmark_splice_impl(curbuf,
+ splice.start_row, splice.start_col, splice.start_byte,
+ splice.new_row, splice.new_col, splice.new_byte,
+ splice.old_row, splice.old_col, splice.old_byte,
+ kExtmarkNoUndo);
} else {
- extmark_splice(curbuf,
- splice.start_row, splice.start_col,
- splice.oldextent_row, splice.oldextent_col,
- splice.newextent_row, splice.newextent_col,
- kExtmarkNoUndo);
+ extmark_splice_impl(curbuf,
+ splice.start_row, splice.start_col, splice.start_byte,
+ splice.old_row, splice.old_col, splice.old_byte,
+ splice.new_row, splice.new_col, splice.new_byte,
+ kExtmarkNoUndo);
}
// kExtmarkSavePos
} else if (undo_info.type == kExtmarkSavePos) {
@@ -458,15 +509,15 @@ void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo)
ExtmarkMove move = undo_info.data.move;
if (undo) {
extmark_move_region(curbuf,
- move.new_row, move.new_col,
- move.extent_row, move.extent_col,
- move.start_row, move.start_col,
+ move.new_row, move.new_col, move.new_byte,
+ move.extent_row, move.extent_col, move.extent_byte,
+ move.start_row, move.start_col, move.start_byte,
kExtmarkNoUndo);
} else {
extmark_move_region(curbuf,
- move.start_row, move.start_col,
- move.extent_row, move.extent_col,
- move.new_row, move.new_col,
+ move.start_row, move.start_col, move.start_byte,
+ move.extent_row, move.extent_col, move.extent_byte,
+ move.new_row, move.new_col, move.new_byte,
kExtmarkNoUndo);
}
}
@@ -481,51 +532,74 @@ void extmark_adjust(buf_T *buf,
long amount_after,
ExtmarkOp undo)
{
- if (!curbuf_splice_pending) {
- int old_extent, new_extent;
- if (amount == MAXLNUM) {
- old_extent = (int)(line2 - line1+1);
- new_extent = (int)(amount_after + old_extent);
- } else {
- // A region is either deleted (amount == MAXLNUM) or
- // added (line2 == MAXLNUM). The only other case is :move
- // which is handled by a separate entry point extmark_move_region.
- assert(line2 == MAXLNUM);
- old_extent = 0;
- new_extent = (int)amount;
- }
- extmark_splice(buf,
- (int)line1-1, 0,
- old_extent, 0,
- new_extent, 0, undo);
+ if (curbuf_splice_pending) {
+ return;
}
+ bcount_t start_byte = ml_find_line_or_offset(buf, line1, NULL, true);
+ bcount_t old_byte = 0, new_byte = 0;
+ int old_row, new_row;
+ if (amount == MAXLNUM) {
+ old_row = (int)(line2 - line1+1);
+ // TODO(bfredl): ej kasta?
+ old_byte = (bcount_t)buf->deleted_bytes2;
+
+ new_row = (int)(amount_after + old_row);
+ } else {
+ // A region is either deleted (amount == MAXLNUM) or
+ // added (line2 == MAXLNUM). The only other case is :move
+ // which is handled by a separate entry point extmark_move_region.
+ assert(line2 == MAXLNUM);
+ old_row = 0;
+ new_row = (int)amount;
+ }
+ if (new_row > 0) {
+ new_byte = ml_find_line_or_offset(buf, line1+new_row, NULL, true)
+ - start_byte;
+ }
+ extmark_splice_impl(buf,
+ (int)line1-1, 0, start_byte,
+ old_row, 0, old_byte,
+ new_row, 0, new_byte, undo);
}
void extmark_splice(buf_T *buf,
int start_row, colnr_T start_col,
- int oldextent_row, colnr_T oldextent_col,
- int newextent_row, colnr_T newextent_col,
+ int old_row, colnr_T old_col, bcount_t old_byte,
+ int new_row, colnr_T new_col, bcount_t new_byte,
ExtmarkOp undo)
{
- buf_updates_send_splice(buf, start_row, start_col,
- oldextent_row, oldextent_col,
- newextent_row, newextent_col);
+ long offset = ml_find_line_or_offset(buf, start_row+1, NULL, true);
+ extmark_splice_impl(buf, start_row, start_col, offset+start_col,
+ old_row, old_col, old_byte, new_row, new_col, new_byte,
+ undo);
+}
+
+void extmark_splice_impl(buf_T *buf,
+ int start_row, colnr_T start_col, bcount_t start_byte,
+ int old_row, colnr_T old_col, bcount_t old_byte,
+ int new_row, colnr_T new_col, bcount_t new_byte,
+ ExtmarkOp undo)
+{
+ curbuf->deleted_bytes2 = 0;
+ buf_updates_send_splice(buf, start_row, start_col, start_byte,
+ old_row, old_col, old_byte,
+ new_row, new_col, new_byte);
- if (undo == kExtmarkUndo && (oldextent_row > 0 || oldextent_col > 0)) {
+ if (undo == kExtmarkUndo && (old_row > 0 || old_col > 0)) {
// Copy marks that would be effected by delete
// TODO(bfredl): Be "smart" about gravity here, left-gravity at the
// beginning and right-gravity at the end need not be preserved.
// Also be smart about marks that already have been saved (important for
// merge!)
- int end_row = start_row + oldextent_row;
- int end_col = (oldextent_row ? 0 : start_col) + oldextent_col;
+ int end_row = start_row + old_row;
+ int end_col = (old_row ? 0 : start_col) + old_col;
u_extmark_copy(buf, start_row, start_col, end_row, end_col);
}
marktree_splice(buf->b_marktree, start_row, start_col,
- oldextent_row, oldextent_col,
- newextent_row, newextent_col);
+ old_row, old_col,
+ new_row, new_col);
if (undo == kExtmarkUndo) {
u_header_T *uhp = u_force_get_undo_header(buf);
@@ -537,25 +611,29 @@ void extmark_splice(buf_T *buf,
// TODO(bfredl): this is quite rudimentary. We merge small (within line)
// inserts with each other and small deletes with each other. Add full
// merge algorithm later.
- if (oldextent_row == 0 && newextent_row == 0 && kv_size(uhp->uh_extmark)) {
+ if (old_row == 0 && new_row == 0 && kv_size(uhp->uh_extmark)) {
ExtmarkUndoObject *item = &kv_A(uhp->uh_extmark,
kv_size(uhp->uh_extmark)-1);
if (item->type == kExtmarkSplice) {
ExtmarkSplice *splice = &item->data.splice;
- if (splice->start_row == start_row && splice->oldextent_row == 0
- && splice->newextent_row == 0) {
- if (oldextent_col == 0 && start_col >= splice->start_col
- && start_col <= splice->start_col+splice->newextent_col) {
- splice->newextent_col += newextent_col;
+ if (splice->start_row == start_row && splice->old_row == 0
+ && splice->new_row == 0) {
+ if (old_col == 0 && start_col >= splice->start_col
+ && start_col <= splice->start_col+splice->new_col) {
+ splice->new_col += new_col;
+ splice->new_byte += new_byte;
merged = true;
- } else if (newextent_col == 0
- && start_col == splice->start_col+splice->newextent_col) {
- splice->oldextent_col += oldextent_col;
+ } else if (new_col == 0
+ && start_col == splice->start_col+splice->new_col) {
+ splice->old_col += old_col;
+ splice->old_byte += old_byte;
merged = true;
- } else if (newextent_col == 0
- && start_col + oldextent_col == splice->start_col) {
+ } else if (new_col == 0
+ && start_col + old_col == splice->start_col) {
splice->start_col = start_col;
- splice->oldextent_col += oldextent_col;
+ splice->start_byte = start_byte;
+ splice->old_col += old_col;
+ splice->old_byte += old_byte;
merged = true;
}
}
@@ -566,10 +644,13 @@ void extmark_splice(buf_T *buf,
ExtmarkSplice splice;
splice.start_row = start_row;
splice.start_col = start_col;
- splice.oldextent_row = oldextent_row;
- splice.oldextent_col = oldextent_col;
- splice.newextent_row = newextent_row;
- splice.newextent_col = newextent_col;
+ splice.start_byte = start_byte;
+ splice.old_row = old_row;
+ splice.old_col = old_col;
+ splice.old_byte = old_byte;
+ splice.new_row = new_row;
+ splice.new_col = new_col;
+ splice.new_byte = new_byte;
kv_push(uhp->uh_extmark,
((ExtmarkUndoObject){ .type = kExtmarkSplice,
@@ -584,30 +665,31 @@ void extmark_splice_cols(buf_T *buf,
ExtmarkOp undo)
{
extmark_splice(buf, start_row, start_col,
- 0, old_col,
- 0, new_col, undo);
+ 0, old_col, old_col,
+ 0, new_col, new_col, undo);
}
-void extmark_move_region(buf_T *buf,
- int start_row, colnr_T start_col,
- int extent_row, colnr_T extent_col,
- int new_row, colnr_T new_col,
- ExtmarkOp undo)
+void extmark_move_region(
+ buf_T *buf,
+ int start_row, colnr_T start_col, bcount_t start_byte,
+ int extent_row, colnr_T extent_col, bcount_t extent_byte,
+ int new_row, colnr_T new_col, bcount_t new_byte,
+ ExtmarkOp undo)
{
// TODO(bfredl): this is not synced to the buffer state inside the callback.
// But unless we make the undo implementation smarter, this is not ensured
// anyway.
- buf_updates_send_splice(buf, start_row, start_col,
- extent_row, extent_col,
- 0, 0);
+ buf_updates_send_splice(buf, start_row, start_col, start_byte,
+ extent_row, extent_col, extent_byte,
+ 0, 0, 0);
marktree_move_region(buf->b_marktree, start_row, start_col,
extent_row, extent_col,
new_row, new_col);
- buf_updates_send_splice(buf, new_row, new_col,
- 0, 0,
- extent_row, extent_col);
+ buf_updates_send_splice(buf, new_row, new_col, new_byte,
+ 0, 0, 0,
+ extent_row, extent_col, extent_byte);
if (undo == kExtmarkUndo) {
@@ -619,10 +701,13 @@ void extmark_move_region(buf_T *buf,
ExtmarkMove move;
move.start_row = start_row;
move.start_col = start_col;
+ move.start_byte = start_byte;
move.extent_row = extent_row;
move.extent_col = extent_col;
+ move.extent_byte = extent_byte;
move.new_row = new_row;
move.new_col = new_col;
+ move.new_byte = new_byte;
kv_push(uhp->uh_extmark,
((ExtmarkUndoObject){ .type = kExtmarkMove,
@@ -642,50 +727,6 @@ uint64_t src2ns(Integer *src_id)
}
}
-/// Adds a decoration to a buffer.
-///
-/// Unlike matchaddpos() highlights, these follow changes to the the buffer
-/// texts. Decorations are represented internally and in the API as extmarks.
-///
-/// @param buf The buffer to add decorations to
-/// @param ns_id A valid namespace id.
-/// @param hl_id Id of the highlight group to use (or zero)
-/// @param start_row The line to highlight
-/// @param start_col First column to highlight
-/// @param end_row The line to highlight
-/// @param end_col The last column to highlight
-/// @param virt_text Virtual text (currently placed at the EOL of start_row)
-/// @return The extmark id inside the namespace
-uint64_t extmark_add_decoration(buf_T *buf, uint64_t ns_id, int hl_id,
- int start_row, colnr_T start_col,
- int end_row, colnr_T end_col,
- VirtText virt_text)
-{
- ExtmarkNs *ns = buf_ns_ref(buf, ns_id, true);
- ExtmarkItem item;
- item.ns_id = ns_id;
- item.mark_id = ns->free_id++;
- item.hl_id = hl_id;
- item.virt_text = virt_text;
-
- uint64_t mark;
-
- if (end_row > -1) {
- mark = marktree_put_pair(buf->b_marktree,
- start_row, start_col, true,
- end_row, end_col, false);
- } else {
- mark = marktree_put(buf->b_marktree, start_row, start_col, true);
- }
-
- map_put(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark, item);
- map_put(uint64_t, uint64_t)(ns->map, item.mark_id, mark);
-
- redraw_buf_range_later(buf, start_row+1,
- (end_row >= 0 ? end_row : start_row) + 1);
- return item.mark_id;
-}
-
/// Add highlighting to a buffer, bounded by two cursor positions,
/// with an offset.
///
@@ -705,6 +746,7 @@ void bufhl_add_hl_pos_offset(buf_T *buf,
{
colnr_T hl_start = 0;
colnr_T hl_end = 0;
+ Decoration *decor = decoration_hl(hl_id);
// TODO(bfredl): if decoration had blocky mode, we could avoid this loop
for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum ++) {
@@ -729,10 +771,44 @@ void bufhl_add_hl_pos_offset(buf_T *buf,
hl_start = pos_start.col + offset;
hl_end = pos_end.col + offset;
}
- (void)extmark_add_decoration(buf, (uint64_t)src_id, hl_id,
- (int)lnum-1, hl_start,
- (int)lnum-1+end_off, hl_end,
- VIRTTEXT_EMPTY);
+ (void)extmark_set(buf, (uint64_t)src_id, 0,
+ (int)lnum-1, hl_start, (int)lnum-1+end_off, hl_end,
+ decor, kExtmarkUndo);
+ }
+}
+
+Decoration *decoration_hl(int hl_id)
+{
+ assert(hl_id > 0);
+ Decoration **dp = (Decoration **)pmap_ref(uint64_t)(hl_decors,
+ (uint64_t)hl_id, true);
+ if (*dp) {
+ return *dp;
+ }
+
+ Decoration *decor = xcalloc(1, sizeof(*decor));
+ decor->hl_id = hl_id;
+ decor->shared = true;
+ *dp = decor;
+ return decor;
+}
+
+void decoration_redraw(buf_T *buf, int row1, int row2, Decoration *decor)
+{
+ if (decor->hl_id && row2 >= row1) {
+ redraw_buf_range_later(buf, row1+1, row2+1);
+ }
+
+ if (kv_size(decor->virt_text)) {
+ redraw_buf_line_later(buf, row1+1);
+ }
+}
+
+void free_decoration(Decoration *decor)
+{
+ if (decor && !decor->shared) {
+ clear_virttext(&decor->virt_text);
+ xfree(decor);
}
}
@@ -757,8 +833,8 @@ VirtText *extmark_find_virttext(buf_T *buf, int row, uint64_t ns_id)
ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index,
mark.id, false);
if (item && (ns_id == 0 || ns_id == item->ns_id)
- && kv_size(item->virt_text)) {
- return &item->virt_text;
+ && item->decor && kv_size(item->decor->virt_text)) {
+ return &item->decor->virt_text;
}
marktree_itr_next(buf->b_marktree, itr);
}
@@ -787,7 +863,6 @@ bool decorations_redraw_start(buf_T *buf, int top_row,
if (mark.row < 0) { // || mark.row > end_row
break;
}
- // TODO(bfredl): dedicated flag for being a decoration?
if ((mark.row < top_row && mark.id&MARKTREE_END_FLAG)) {
goto next_mark;
}
@@ -797,25 +872,30 @@ bool decorations_redraw_start(buf_T *buf, int top_row,
uint64_t start_id = mark.id & ~MARKTREE_END_FLAG;
ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index,
start_id, false);
+ if (!item || !item->decor) {
+ // TODO(bfredl): dedicated flag for being a decoration?
+ goto next_mark;
+ }
+ Decoration *decor = item->decor;
+
if ((!(mark.id&MARKTREE_END_FLAG) && altpos.row < top_row
- && item && !kv_size(item->virt_text))
+ && item && !kv_size(decor->virt_text))
|| ((mark.id&MARKTREE_END_FLAG) && altpos.row >= top_row)) {
goto next_mark;
}
- if (item && (item->hl_id > 0 || kv_size(item->virt_text))) {
- int attr_id = item->hl_id > 0 ? syn_id2attr(item->hl_id) : 0;
- VirtText *vt = kv_size(item->virt_text) ? &item->virt_text : NULL;
- HlRange range;
- if (mark.id&MARKTREE_END_FLAG) {
- range = (HlRange){ altpos.row, altpos.col, mark.row, mark.col,
- attr_id, vt };
- } else {
- range = (HlRange){ mark.row, mark.col, altpos.row,
- altpos.col, attr_id, vt };
- }
- kv_push(state->active, range);
+ int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0;
+ VirtText *vt = kv_size(decor->virt_text) ? &decor->virt_text : NULL;
+ HlRange range;
+ if (mark.id&MARKTREE_END_FLAG) {
+ range = (HlRange){ altpos.row, altpos.col, mark.row, mark.col,
+ attr_id, vt };
+ } else {
+ range = (HlRange){ mark.row, mark.col, altpos.row,
+ altpos.col, attr_id, vt };
}
+ kv_push(state->active, range);
+
next_mark:
if (marktree_itr_node_done(state->itr)) {
break;
@@ -860,21 +940,24 @@ int decorations_redraw_col(buf_T *buf, int col, DecorationRedrawState *state)
ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index,
mark.id, false);
+ if (!item || !item->decor) {
+ // TODO(bfredl): dedicated flag for being a decoration?
+ goto next_mark;
+ }
+ Decoration *decor = item->decor;
if (endpos.row < mark.row
|| (endpos.row == mark.row && endpos.col <= mark.col)) {
- if (item && !kv_size(item->virt_text)) {
+ if (item && !kv_size(decor->virt_text)) {
goto next_mark;
}
}
- if (item && (item->hl_id > 0 || kv_size(item->virt_text))) {
- int attr_id = item->hl_id > 0 ? syn_id2attr(item->hl_id) : 0;
- VirtText *vt = kv_size(item->virt_text) ? &item->virt_text : NULL;
- kv_push(state->active, ((HlRange){ mark.row, mark.col,
- endpos.row, endpos.col,
- attr_id, vt }));
- }
+ int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0;
+ VirtText *vt = kv_size(decor->virt_text) ? &decor->virt_text : NULL;
+ kv_push(state->active, ((HlRange){ mark.row, mark.col,
+ endpos.row, endpos.col,
+ attr_id, vt }));
next_mark:
marktree_itr_next(buf->b_marktree, state->itr);
diff --git a/src/nvim/extmark.h b/src/nvim/extmark.h
index b5eb0db3b6..534e97a7f4 100644
--- a/src/nvim/extmark.h
+++ b/src/nvim/extmark.h
@@ -13,19 +13,28 @@ typedef struct
uint64_t mark_id;
int row;
colnr_T col;
+ int end_row;
+ colnr_T end_col;
+ Decoration *decor;
} ExtmarkInfo;
-typedef kvec_t(ExtmarkInfo) ExtmarkArray;
+typedef kvec_t(ExtmarkInfo) ExtmarkInfoArray;
+
+// TODO(bfredl): good enough name for now.
+typedef ptrdiff_t bcount_t;
// delete the columns between mincol and endcol
typedef struct {
int start_row;
colnr_T start_col;
- int oldextent_row;
- colnr_T oldextent_col;
- int newextent_row;
- colnr_T newextent_col;
+ int old_row;
+ colnr_T old_col;
+ int new_row;
+ colnr_T new_col;
+ bcount_t start_byte;
+ bcount_t old_byte;
+ bcount_t new_byte;
} ExtmarkSplice;
// adjust marks after :move operation
@@ -36,6 +45,9 @@ typedef struct {
int extent_col;
int new_row;
int new_col;
+ bcount_t start_byte;
+ bcount_t extent_byte;
+ bcount_t new_byte;
} ExtmarkMove;
// extmark was updated
diff --git a/src/nvim/extmark_defs.h b/src/nvim/extmark_defs.h
index c927048981..76804db848 100644
--- a/src/nvim/extmark_defs.h
+++ b/src/nvim/extmark_defs.h
@@ -14,12 +14,20 @@ typedef kvec_t(VirtTextChunk) VirtText;
typedef struct
{
- uint64_t ns_id;
- uint64_t mark_id;
int hl_id; // highlight group
- // TODO(bfredl): virt_text is pretty larger than the rest,
- // pointer indirection?
VirtText virt_text;
+ // TODO(bfredl): style, signs, etc
+ bool shared; // shared decoration, don't free
+} Decoration;
+
+typedef struct
+{
+ uint64_t ns_id;
+ uint64_t mark_id;
+ // TODO(bfredl): a lot of small allocations. Should probably use
+ // kvec_t(Decoration) as an arena. Alternatively, store ns_id/mark_id
+ // _inline_ in MarkTree and use the map only for decorations.
+ Decoration *decor;
} ExtmarkItem;
typedef struct undo_object ExtmarkUndoObject;
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index 7740673bbe..286f2b4fca 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -1797,6 +1797,7 @@ failed:
linecnt--;
}
curbuf->deleted_bytes = 0;
+ curbuf->deleted_bytes2 = 0;
curbuf->deleted_codepoints = 0;
curbuf->deleted_codeunits = 0;
linecnt = curbuf->b_ml.ml_line_count - linecnt;
diff --git a/src/nvim/fold.c b/src/nvim/fold.c
index 61a85171e8..9994ad3ea8 100644
--- a/src/nvim/fold.c
+++ b/src/nvim/fold.c
@@ -573,31 +573,36 @@ void foldCreate(win_T *wp, linenr_T start, linenr_T end)
// Find the place to insert the new fold
gap = &wp->w_folds;
- for (;; ) {
- if (!foldFind(gap, start_rel, &fp))
- break;
- if (fp->fd_top + fp->fd_len > end_rel) {
- /* New fold is completely inside this fold: Go one level deeper. */
- gap = &fp->fd_nested;
- start_rel -= fp->fd_top;
- end_rel -= fp->fd_top;
- if (use_level || fp->fd_flags == FD_LEVEL) {
- use_level = true;
- if (level >= wp->w_p_fdl) {
+ if (gap->ga_len == 0) {
+ i = 0;
+ } else {
+ for (;;) {
+ if (!foldFind(gap, start_rel, &fp)) {
+ break;
+ }
+ if (fp->fd_top + fp->fd_len > end_rel) {
+ // New fold is completely inside this fold: Go one level deeper.
+ gap = &fp->fd_nested;
+ start_rel -= fp->fd_top;
+ end_rel -= fp->fd_top;
+ if (use_level || fp->fd_flags == FD_LEVEL) {
+ use_level = true;
+ if (level >= wp->w_p_fdl) {
+ closed = true;
+ }
+ } else if (fp->fd_flags == FD_CLOSED) {
closed = true;
}
- } else if (fp->fd_flags == FD_CLOSED) {
- closed = true;
+ level++;
+ } else {
+ // This fold and new fold overlap: Insert here and move some folds
+ // inside the new fold.
+ break;
}
- level++;
- } else {
- /* This fold and new fold overlap: Insert here and move some folds
- * inside the new fold. */
- break;
}
+ i = (int)(fp - (fold_T *)gap->ga_data);
}
- i = (int)(fp - (fold_T *)gap->ga_data);
ga_grow(gap, 1);
{
fp = (fold_T *)gap->ga_data + i;
@@ -788,13 +793,15 @@ void foldUpdate(win_T *wp, linenr_T top, linenr_T bot)
return;
}
- // Mark all folds from top to bot as maybe-small.
- fold_T *fp;
- (void)foldFind(&wp->w_folds, top, &fp);
- while (fp < (fold_T *)wp->w_folds.ga_data + wp->w_folds.ga_len
- && fp->fd_top < bot) {
- fp->fd_small = kNone;
- fp++;
+ if (wp->w_folds.ga_len > 0) {
+ // Mark all folds from top to bot as maybe-small.
+ fold_T *fp;
+ (void)foldFind(&wp->w_folds, top, &fp);
+ while (fp < (fold_T *)wp->w_folds.ga_data + wp->w_folds.ga_len
+ && fp->fd_top < bot) {
+ fp->fd_small = kNone;
+ fp++;
+ }
}
if (foldmethodIsIndent(wp)
@@ -1058,11 +1065,16 @@ void cloneFoldGrowArray(garray_T *from, garray_T *to)
* the first fold below it (careful: it can be beyond the end of the array!).
* Returns FALSE when there is no fold that contains "lnum".
*/
-static int foldFind(const garray_T *gap, linenr_T lnum, fold_T **fpp)
+static bool foldFind(const garray_T *gap, linenr_T lnum, fold_T **fpp)
{
linenr_T low, high;
fold_T *fp;
+ if (gap->ga_len == 0) {
+ *fpp = NULL;
+ return false;
+ }
+
/*
* Perform a binary search.
* "low" is lowest index of possible match.
@@ -1086,7 +1098,7 @@ static int foldFind(const garray_T *gap, linenr_T lnum, fold_T **fpp)
}
}
*fpp = fp + low;
- return FALSE;
+ return false;
}
/* foldLevelWin() {{{2 */
@@ -1220,9 +1232,10 @@ setManualFoldWin(
gap = &wp->w_folds;
for (;; ) {
if (!foldFind(gap, lnum, &fp)) {
- /* If there is a following fold, continue there next time. */
- if (fp < (fold_T *)gap->ga_data + gap->ga_len)
+ // If there is a following fold, continue there next time.
+ if (fp != NULL && fp < (fold_T *)gap->ga_data + gap->ga_len) {
next = fp->fd_top + off;
+ }
break;
}
@@ -1389,6 +1402,10 @@ static void foldMarkAdjustRecurse(
linenr_T last;
linenr_T top;
+ if (gap->ga_len == 0) {
+ return;
+ }
+
/* In Insert mode an inserted line at the top of a fold is considered part
* of the fold, otherwise it isn't. */
if ((State & INSERT) && amount == (linenr_T)1 && line2 == MAXLNUM)
@@ -1727,8 +1744,7 @@ static void foldDelMarker(
STRCPY(newline + (p - line), p + len);
ml_replace_buf(buf, lnum, newline, false);
extmark_splice_cols(buf, (int)lnum-1, (int)(p - line),
- (int)len,
- 0, kExtmarkUndo);
+ (int)len, 0, kExtmarkUndo);
}
break;
}
@@ -2265,14 +2281,15 @@ static linenr_T foldUpdateIEMSRecurse(
/* Find an existing fold to re-use. Preferably one that
* includes startlnum, otherwise one that ends just before
* startlnum or starts after it. */
- if (foldFind(gap, startlnum, &fp)
- || (fp < ((fold_T *)gap->ga_data) + gap->ga_len
- && fp->fd_top <= firstlnum)
- || foldFind(gap, firstlnum - concat, &fp)
- || (fp < ((fold_T *)gap->ga_data) + gap->ga_len
- && ((lvl < level && fp->fd_top < flp->lnum)
- || (lvl >= level
- && fp->fd_top <= flp->lnum_save)))) {
+ if (gap->ga_len > 0
+ && (foldFind(gap, startlnum, &fp)
+ || (fp < ((fold_T *)gap->ga_data) + gap->ga_len
+ && fp->fd_top <= firstlnum)
+ || foldFind(gap, firstlnum - concat, &fp)
+ || (fp < ((fold_T *)gap->ga_data) + gap->ga_len
+ && ((lvl < level && fp->fd_top < flp->lnum)
+ || (lvl >= level
+ && fp->fd_top <= flp->lnum_save))))) {
if (fp->fd_top + fp->fd_len + concat > firstlnum) {
/* Use existing fold for the new fold. If it starts
* before where we started looking, extend it. If it
@@ -2363,7 +2380,11 @@ static linenr_T foldUpdateIEMSRecurse(
} else {
/* Insert new fold. Careful: ga_data may be NULL and it
* may change! */
- i = (int)(fp - (fold_T *)gap->ga_data);
+ if (gap->ga_len == 0) {
+ i = 0;
+ } else {
+ i = (int)(fp - (fold_T *)gap->ga_data);
+ }
foldInsert(gap, i);
fp = (fold_T *)gap->ga_data + i;
/* The new fold continues until bot, unless we find the
@@ -2559,9 +2580,10 @@ static void foldInsert(garray_T *gap, int i)
ga_grow(gap, 1);
fp = (fold_T *)gap->ga_data + i;
- if (i < gap->ga_len)
+ if (gap->ga_len > 0 && i < gap->ga_len) {
memmove(fp + 1, fp, sizeof(fold_T) * (size_t)(gap->ga_len - i));
- ++gap->ga_len;
+ }
+ gap->ga_len++;
ga_init(&fp->fd_nested, (int)sizeof(fold_T), 10);
}
@@ -2596,17 +2618,18 @@ static void foldSplit(buf_T *buf, garray_T *const gap,
* any between top and bot, they have been removed by the caller. */
garray_T *const gap1 = &fp->fd_nested;
garray_T *const gap2 = &fp[1].fd_nested;
- (void)(foldFind(gap1, bot + 1 - fp->fd_top, &fp2));
- const int len = (int)((fold_T *)gap1->ga_data + gap1->ga_len - fp2);
- if (len > 0) {
- ga_grow(gap2, len);
- for (int idx = 0; idx < len; idx++) {
- ((fold_T *)gap2->ga_data)[idx] = fp2[idx];
- ((fold_T *)gap2->ga_data)[idx].fd_top
- -= fp[1].fd_top - fp->fd_top;
- }
- gap2->ga_len = len;
- gap1->ga_len -= len;
+ if (foldFind(gap1, bot + 1 - fp->fd_top, &fp2)) {
+ const int len = (int)((fold_T *)gap1->ga_data + gap1->ga_len - fp2);
+ if (len > 0) {
+ ga_grow(gap2, len);
+ for (int idx = 0; idx < len; idx++) {
+ ((fold_T *)gap2->ga_data)[idx] = fp2[idx];
+ ((fold_T *)gap2->ga_data)[idx].fd_top
+ -= fp[1].fd_top - fp->fd_top;
+ }
+ gap2->ga_len = len;
+ gap1->ga_len -= len;
+ }
}
fp->fd_len = top - fp->fd_top;
fold_changed = true;
@@ -2641,7 +2664,7 @@ static void foldRemove(
return; // nothing to do
}
- for (;; ) {
+ while (gap->ga_len > 0) {
// Find fold that includes top or a following one.
if (foldFind(gap, top, &fp) && fp->fd_top < top) {
// 2: or 3: need to delete nested folds
@@ -2657,7 +2680,8 @@ static void foldRemove(
fold_changed = true;
continue;
}
- if (fp >= (fold_T *)(gap->ga_data) + gap->ga_len
+ if (gap->ga_data == NULL
+ || fp >= (fold_T *)(gap->ga_data) + gap->ga_len
|| fp->fd_top > bot) {
// 6: Found a fold below bot, can stop looking.
break;
@@ -2738,7 +2762,8 @@ static void truncate_fold(win_T *const wp, fold_T *fp, linenr_T end)
}
#define FOLD_END(fp) ((fp)->fd_top + (fp)->fd_len - 1)
-#define VALID_FOLD(fp, gap) ((fp) < ((fold_T *)(gap)->ga_data + (gap)->ga_len))
+#define VALID_FOLD(fp, gap) \
+ ((gap)->ga_len > 0 && (fp) < ((fold_T *)(gap)->ga_data + (gap)->ga_len))
#define FOLD_INDEX(fp, gap) ((size_t)(fp - ((fold_T *)(gap)->ga_data)))
void foldMoveRange(
win_T *const wp, garray_T *gap,
diff --git a/src/nvim/indent.c b/src/nvim/indent.c
index fb277b25fd..bb0fdfec01 100644
--- a/src/nvim/indent.c
+++ b/src/nvim/indent.c
@@ -297,11 +297,12 @@ int set_indent(int size, int flags)
if (!(flags & SIN_UNDO) || (u_savesub(curwin->w_cursor.lnum) == OK)) {
ml_replace(curwin->w_cursor.lnum, newline, false);
if (!(flags & SIN_NOMARK)) {
- extmark_splice(curbuf,
- (int)curwin->w_cursor.lnum-1, skipcols,
- 0, (int)(p-oldline) - skipcols,
- 0, (int)(s-newline) - skipcols,
- kExtmarkUndo);
+ extmark_splice_cols(curbuf,
+ (int)curwin->w_cursor.lnum-1,
+ skipcols,
+ (int)(p-oldline) - skipcols,
+ (int)(s-newline) - skipcols,
+ kExtmarkUndo);
}
if (flags & SIN_CHANGED) {
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
index 32e804d213..030df69caa 100644
--- a/src/nvim/lua/converter.c
+++ b/src/nvim/lua/converter.c
@@ -1249,6 +1249,13 @@ type_error:
return ret;
}
+LuaRef nlua_pop_LuaRef(lua_State *const lstate, Error *err)
+{
+ LuaRef rv = nlua_ref(lstate, -1);
+ lua_pop(lstate, 1);
+ return rv;
+}
+
#define GENERATE_INDEX_FUNCTION(type) \
type nlua_pop_##type(lua_State *lstate, Error *err) \
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT \
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index 5ad9731a97..7722f9cdc3 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -845,7 +845,7 @@ void nlua_unref(lua_State *lstate, LuaRef ref)
}
}
-void executor_free_luaref(LuaRef ref)
+void api_free_luaref(LuaRef ref)
{
lua_State *const lstate = nlua_enter();
nlua_unref(lstate, ref);
@@ -879,8 +879,8 @@ LuaRef nlua_newref(lua_State *lstate, LuaRef original_ref)
/// @param[out] ret_tv Location where result will be saved.
///
/// @return Result of the execution.
-void executor_eval_lua(const String str, typval_T *const arg,
- typval_T *const ret_tv)
+void nlua_typval_eval(const String str, typval_T *const arg,
+ typval_T *const ret_tv)
FUNC_ATTR_NONNULL_ALL
{
#define EVALHEADER "local _A=select(1,...) return ("
@@ -902,8 +902,8 @@ void executor_eval_lua(const String str, typval_T *const arg,
}
}
-void executor_call_lua(const char *str, size_t len, typval_T *const args,
- int argcount, typval_T *ret_tv)
+void nlua_typval_call(const char *str, size_t len, typval_T *const args,
+ int argcount, typval_T *ret_tv)
FUNC_ATTR_NONNULL_ALL
{
#define CALLHEADER "return "
@@ -1006,14 +1006,14 @@ int typval_exec_lua_callable(
/// Execute Lua string
///
-/// Used for nvim_exec_lua().
+/// Used for nvim_exec_lua() and internally to execute a lua string.
///
/// @param[in] str String to execute.
/// @param[in] args array of ... args
/// @param[out] err Location where error will be saved.
///
/// @return Return value of the execution.
-Object executor_exec_lua_api(const String str, const Array args, Error *err)
+Object nlua_exec(const String str, const Array args, Error *err)
{
lua_State *const lstate = nlua_enter();
@@ -1040,17 +1040,30 @@ Object executor_exec_lua_api(const String str, const Array args, Error *err)
return nlua_pop_Object(lstate, false, err);
}
-Object executor_exec_lua_cb(LuaRef ref, const char *name, Array args,
- bool retval, Error *err)
+/// call a LuaRef as a function (or table with __call metamethod)
+///
+/// @param ref the reference to call (not consumed)
+/// @param name if non-NULL, sent to callback as first arg
+/// if NULL, only args are used
+/// @param retval if true, convert return value to Object
+/// if false, discard return value
+/// @param err Error details, if any (if NULL, errors are echoed)
+/// @return Return value of function, if retval was set. Otherwise NIL.
+Object nlua_call_ref(LuaRef ref, const char *name, Array args,
+ bool retval, Error *err)
{
lua_State *const lstate = nlua_enter();
nlua_pushref(lstate, ref);
- lua_pushstring(lstate, name);
+ int nargs = (int)args.size;
+ if (name != NULL) {
+ lua_pushstring(lstate, name);
+ nargs++;
+ }
for (size_t i = 0; i < args.size; i++) {
nlua_push_Object(lstate, args.items[i], false);
}
- if (lua_pcall(lstate, (int)args.size+1, retval ? 1 : 0, 0)) {
+ if (lua_pcall(lstate, nargs, retval ? 1 : 0, 0)) {
// if err is passed, the caller will deal with the error.
if (err) {
size_t len;
diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c
index 138031237e..8be4b6f376 100644
--- a/src/nvim/lua/treesitter.c
+++ b/src/nvim/lua/treesitter.c
@@ -62,6 +62,7 @@ static struct luaL_Reg node_meta[] = {
{ "end_", node_end },
{ "type", node_type },
{ "symbol", node_symbol },
+ { "field", node_field },
{ "named", node_named },
{ "missing", node_missing },
{ "has_error", node_has_error },
@@ -73,6 +74,7 @@ static struct luaL_Reg node_meta[] = {
{ "descendant_for_range", node_descendant_for_range },
{ "named_descendant_for_range", node_named_descendant_for_range },
{ "parent", node_parent },
+ { "iter_children", node_iter_children },
{ "_rawquery", node_rawquery },
{ NULL, NULL }
};
@@ -84,12 +86,17 @@ static struct luaL_Reg query_meta[] = {
{ NULL, NULL }
};
-// cursor is not exposed, but still needs garbage collection
+// cursors are not exposed, but still needs garbage collection
static struct luaL_Reg querycursor_meta[] = {
{ "__gc", querycursor_gc },
{ NULL, NULL }
};
+static struct luaL_Reg treecursor_meta[] = {
+ { "__gc", treecursor_gc },
+ { NULL, NULL }
+};
+
static PMap(cstr_t) *langs;
static void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta)
@@ -116,6 +123,7 @@ void tslua_init(lua_State *L)
build_meta(L, "treesitter_node", node_meta);
build_meta(L, "treesitter_query", query_meta);
build_meta(L, "treesitter_querycursor", querycursor_meta);
+ build_meta(L, "treesitter_treecursor", treecursor_meta);
}
int tslua_has_language(lua_State *L)
@@ -278,22 +286,21 @@ static const char *input_cb(void *payload, uint32_t byte_index,
}
char_u *line = ml_get_buf(bp, position.row+1, false);
size_t len = STRLEN(line);
-
if (position.column > len) {
*bytes_read = 0;
- } else {
- size_t tocopy = MIN(len-position.column, BUFSIZE);
-
- memcpy(buf, line+position.column, tocopy);
- // Translate embedded \n to NUL
- memchrsub(buf, '\n', '\0', tocopy);
- *bytes_read = (uint32_t)tocopy;
- if (tocopy < BUFSIZE) {
- // now add the final \n. If it didn't fit, input_cb will be called again
- // on the same line with advanced column.
- buf[tocopy] = '\n';
- (*bytes_read)++;
- }
+ return "";
+ }
+ size_t tocopy = MIN(len-position.column, BUFSIZE);
+
+ memcpy(buf, line+position.column, tocopy);
+ // Translate embedded \n to NUL
+ memchrsub(buf, '\n', '\0', tocopy);
+ *bytes_read = (uint32_t)tocopy;
+ if (tocopy < BUFSIZE) {
+ // now add the final \n. If it didn't fit, input_cb will be called again
+ // on the same line with advanced column.
+ buf[tocopy] = '\n';
+ (*bytes_read)++;
}
return buf;
#undef BUFSIZE
@@ -646,6 +653,34 @@ static int node_symbol(lua_State *L)
return 1;
}
+static int node_field(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+
+ size_t name_len;
+ const char *field_name = luaL_checklstring(L, 2, &name_len);
+
+ TSTreeCursor cursor = ts_tree_cursor_new(node);
+
+ lua_newtable(L); // [table]
+ unsigned int curr_index = 0;
+
+ if (ts_tree_cursor_goto_first_child(&cursor)) {
+ do {
+ if (!STRCMP(field_name, ts_tree_cursor_current_field_name(&cursor))) {
+ push_node(L, ts_tree_cursor_current_node(&cursor), 1); // [table, node]
+ lua_rawseti(L, -2, ++curr_index);
+ }
+ } while (ts_tree_cursor_goto_next_sibling(&cursor));
+ }
+
+ ts_tree_cursor_delete(&cursor);
+ return 1;
+}
+
static int node_named(lua_State *L)
{
TSNode node;
@@ -746,6 +781,74 @@ static int node_named_descendant_for_range(lua_State *L)
return 1;
}
+static int node_next_child(lua_State *L)
+{
+ TSTreeCursor *ud = luaL_checkudata(
+ L, lua_upvalueindex(1), "treesitter_treecursor");
+ if (!ud) {
+ return 0;
+ }
+
+ TSNode source;
+ if (!node_check(L, lua_upvalueindex(2), &source)) {
+ return 0;
+ }
+
+ // First call should return first child
+ if (ts_node_eq(source, ts_tree_cursor_current_node(ud))) {
+ if (ts_tree_cursor_goto_first_child(ud)) {
+ goto push;
+ } else {
+ goto end;
+ }
+ }
+
+ if (ts_tree_cursor_goto_next_sibling(ud)) {
+push:
+ push_node(
+ L,
+ ts_tree_cursor_current_node(ud),
+ lua_upvalueindex(2)); // [node]
+
+ const char * field = ts_tree_cursor_current_field_name(ud);
+
+ if (field != NULL) {
+ lua_pushstring(L, ts_tree_cursor_current_field_name(ud));
+ } else {
+ lua_pushnil(L);
+ } // [node, field_name_or_nil]
+ return 2;
+ }
+
+end:
+ return 0;
+}
+
+static int node_iter_children(lua_State *L)
+{
+ TSNode source;
+ if (!node_check(L, 1, &source)) {
+ return 0;
+ }
+
+ TSTreeCursor *ud = lua_newuserdata(L, sizeof(TSTreeCursor)); // [udata]
+ *ud = ts_tree_cursor_new(source);
+
+ lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_treecursor"); // [udata, mt]
+ lua_setmetatable(L, -2); // [udata]
+ lua_pushvalue(L, 1); // [udata, source_node]
+ lua_pushcclosure(L, node_next_child, 2);
+
+ return 1;
+}
+
+static int treecursor_gc(lua_State *L)
+{
+ TSTreeCursor *ud = luaL_checkudata(L, 1, "treesitter_treecursor");
+ ts_tree_cursor_delete(ud);
+ return 0;
+}
+
static int node_parent(lua_State *L)
{
TSNode node;
diff --git a/src/nvim/main.c b/src/nvim/main.c
index f79fb57eae..1374c5eb5d 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -21,6 +21,7 @@
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
+#include "nvim/extmark.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
#include "nvim/getchar.h"
@@ -160,6 +161,7 @@ void early_init(mparm_T *paramp)
env_init();
fs_init();
handle_init();
+ extmark_init();
eval_init(); // init global variables
init_path(argv0 ? argv0 : "nvim");
init_normal_cmds(); // Init the table of Normal mode commands.
diff --git a/src/nvim/map.c b/src/nvim/map.c
index cba39f24b3..0c6bad7cb6 100644
--- a/src/nvim/map.c
+++ b/src/nvim/map.c
@@ -184,7 +184,7 @@ MAP_IMPL(uint64_t, uint64_t, DEFAULT_INITIALIZER)
#define EXTMARK_NS_INITIALIZER { 0, 0 }
MAP_IMPL(uint64_t, ExtmarkNs, EXTMARK_NS_INITIALIZER)
#define KVEC_INITIALIZER { .size = 0, .capacity = 0, .items = NULL }
-#define EXTMARK_ITEM_INITIALIZER { 0, 0, 0, KVEC_INITIALIZER }
+#define EXTMARK_ITEM_INITIALIZER { 0, 0, NULL }
MAP_IMPL(uint64_t, ExtmarkItem, EXTMARK_ITEM_INITIALIZER)
MAP_IMPL(handle_T, ptr_t, DEFAULT_INITIALIZER)
#define MSGPACK_HANDLER_INITIALIZER { .fn = NULL, .fast = false }
diff --git a/src/nvim/map.h b/src/nvim/map.h
index 0ad7865bf0..63a18f4129 100644
--- a/src/nvim/map.h
+++ b/src/nvim/map.h
@@ -73,6 +73,7 @@ MAP_DECLS(String, handle_T)
#define pmap_has(T) map_has(T, ptr_t)
#define pmap_key(T) map_key(T, ptr_t)
#define pmap_put(T) map_put(T, ptr_t)
+#define pmap_ref(T) map_ref(T, ptr_t)
/// @see pmap_del2
#define pmap_del(T) map_del(T, ptr_t)
#define pmap_clear(T) map_clear(T, ptr_t)
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index d5788d96b3..f9390bcb88 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -257,11 +257,12 @@ int ml_open(buf_T *buf)
/*
* init fields in memline struct
*/
- buf->b_ml.ml_stack_size = 0; /* no stack yet */
- buf->b_ml.ml_stack = NULL; /* no stack yet */
- buf->b_ml.ml_stack_top = 0; /* nothing in the stack */
- buf->b_ml.ml_locked = NULL; /* no cached block */
- buf->b_ml.ml_line_lnum = 0; /* no cached line */
+ buf->b_ml.ml_stack_size = 0; // no stack yet
+ buf->b_ml.ml_stack = NULL; // no stack yet
+ buf->b_ml.ml_stack_top = 0; // nothing in the stack
+ buf->b_ml.ml_locked = NULL; // no cached block
+ buf->b_ml.ml_line_lnum = 0; // no cached line
+ buf->b_ml.ml_line_offset = 0;
buf->b_ml.ml_chunksize = NULL;
if (cmdmod.noswapfile) {
@@ -831,11 +832,12 @@ void ml_recover(bool checkext)
/*
* init fields in memline struct
*/
- buf->b_ml.ml_stack_size = 0; /* no stack yet */
- buf->b_ml.ml_stack = NULL; /* no stack yet */
- buf->b_ml.ml_stack_top = 0; /* nothing in the stack */
- buf->b_ml.ml_line_lnum = 0; /* no cached line */
- buf->b_ml.ml_locked = NULL; /* no locked block */
+ buf->b_ml.ml_stack_size = 0; // no stack yet
+ buf->b_ml.ml_stack = NULL; // no stack yet
+ buf->b_ml.ml_stack_top = 0; // nothing in the stack
+ buf->b_ml.ml_line_lnum = 0; // no cached line
+ buf->b_ml.ml_line_offset = 0;
+ buf->b_ml.ml_locked = NULL; // no locked block
buf->b_ml.ml_flags = 0;
/*
@@ -2403,12 +2405,13 @@ void ml_add_deleted_len_buf(buf_T *buf, char_u *ptr, ssize_t len)
if (len == -1) {
len = STRLEN(ptr);
}
- buf->deleted_bytes += len+1;
- if (buf->update_need_codepoints) {
- mb_utflen(ptr, len, &buf->deleted_codepoints,
- &buf->deleted_codeunits);
- buf->deleted_codepoints++; // NL char
- buf->deleted_codeunits++;
+ curbuf->deleted_bytes += len+1;
+ curbuf->deleted_bytes2 += len+1;
+ if (curbuf->update_need_codepoints) {
+ mb_utflen(ptr, len, &curbuf->deleted_codepoints,
+ &curbuf->deleted_codeunits);
+ curbuf->deleted_codepoints++; // NL char
+ curbuf->deleted_codeunits++;
}
}
@@ -2831,6 +2834,7 @@ static void ml_flush_line(buf_T *buf)
}
buf->b_ml.ml_line_lnum = 0;
+ buf->b_ml.ml_line_offset = 0;
}
/*
@@ -3980,10 +3984,10 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype)
/// Find offset for line or line with offset.
///
/// @param buf buffer to use
-/// @param lnum if > 0, find offset of lnum, store offset in offp
+/// @param lnum if > 0, find offset of lnum, return offset
/// if == 0, return line with offset *offp
-/// @param offp Location where offset of line is stored, or to read offset to
-/// use to find line. In the later case, store remaining offset.
+/// @param offp offset to use to find line, store remaining column offset
+/// Should be NULL when getting offset of line
/// @param no_ff ignore 'fileformat' option, always use one byte for NL.
///
/// @return -1 if information is not available
@@ -4003,8 +4007,22 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff)
int ffdos = !no_ff && (get_fileformat(buf) == EOL_DOS);
int extra = 0;
- // take care of cached line first
- ml_flush_line(buf);
+ // take care of cached line first. Only needed if the cached line is before
+ // the requested line. Additionally cache the value for the cached line.
+ // This is used by the extmark code which needs the byte offset of the edited
+ // line. So when doing multiple small edits on the same line the value is
+ // only calculated once.
+ //
+ // NB: caching doesn't work with 'fileformat'. This is not a problem for
+ // bytetracking, as bytetracking ignores 'fileformat' option. But calling
+ // line2byte() will invalidate the cache for the time being (this function
+ // was never cached to start with anyway).
+ bool can_cache = (lnum != 0 && !ffdos && buf->b_ml.ml_line_lnum == lnum);
+ if (lnum == 0 || buf->b_ml.ml_line_lnum < lnum || !no_ff) {
+ ml_flush_line(curbuf);
+ } else if (can_cache && buf->b_ml.ml_line_offset > 0) {
+ return buf->b_ml.ml_line_offset;
+ }
if (buf->b_ml.ml_usedchunks == -1
|| buf->b_ml.ml_chunksize == NULL
@@ -4100,6 +4118,10 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff)
}
}
+ if (can_cache && size > 0) {
+ buf->b_ml.ml_line_offset = size;
+ }
+
return size;
}
diff --git a/src/nvim/memline_defs.h b/src/nvim/memline_defs.h
index edd933b2cd..9a6f29a908 100644
--- a/src/nvim/memline_defs.h
+++ b/src/nvim/memline_defs.h
@@ -57,6 +57,8 @@ typedef struct memline {
linenr_T ml_line_lnum; // line number of cached line, 0 if not valid
char_u *ml_line_ptr; // pointer to cached line
+ size_t ml_line_offset; // cached byte offset of ml_line_lnum
+ int ml_line_offset_ff; // fileformat of cached line
bhdr_T *ml_locked; // block used by last ml_get
linenr_T ml_locked_low; // first line in ml_locked
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index d31328219f..1f55d2c315 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -496,9 +496,9 @@ static void shift_block(oparg_T *oap, int amount)
// replace the line
ml_replace(curwin->w_cursor.lnum, newp, false);
changed_bytes(curwin->w_cursor.lnum, (colnr_T)bd.textcol);
- extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, startcol,
- 0, oldlen, 0, newlen,
- kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)curwin->w_cursor.lnum-1, startcol,
+ oldlen, newlen,
+ kExtmarkUndo);
State = oldstate;
curwin->w_cursor.col = oldcol;
p_ri = old_p_ri;
@@ -595,9 +595,8 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def
STRMOVE(newp + offset, oldp);
ml_replace(lnum, newp, false);
- extmark_splice(curbuf, (int)lnum-1, startcol,
- 0, skipped,
- 0, offset-startcol, kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)lnum-1, startcol,
+ skipped, offset-startcol, kExtmarkUndo);
if (lnum == oap->end.lnum) {
/* Set "']" mark to the end of the block instead of the end of
@@ -1534,10 +1533,9 @@ int op_delete(oparg_T *oap)
// replace the line
ml_replace(lnum, newp, false);
- extmark_splice(curbuf, (int)lnum-1, bd.textcol,
- 0, bd.textlen,
- 0, bd.startspaces+bd.endspaces,
- kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)lnum-1, bd.textcol,
+ bd.textlen, bd.startspaces+bd.endspaces,
+ kExtmarkUndo);
}
check_cursor_col();
@@ -1661,17 +1659,20 @@ int op_delete(oparg_T *oap)
curpos = curwin->w_cursor; // remember curwin->w_cursor
curwin->w_cursor.lnum++;
del_lines(oap->line_count - 2, false);
+ bcount_t deleted_bytes = (bcount_t)curbuf->deleted_bytes2 - startpos.col;
// delete from start of line until op_end
n = (oap->end.col + 1 - !oap->inclusive);
curwin->w_cursor.col = 0;
(void)del_bytes((colnr_T)n, !virtual_op,
oap->op_type == OP_DELETE && !oap->is_VIsual);
+ deleted_bytes += n;
curwin->w_cursor = curpos; // restore curwin->w_cursor
(void)do_join(2, false, false, false, false);
curbuf_splice_pending--;
extmark_splice(curbuf, (int)startpos.lnum-1, startpos.col,
- (int)oap->line_count-1, n, 0, 0, kExtmarkUndo);
+ (int)oap->line_count-1, n, deleted_bytes,
+ 0, 0, 0, kExtmarkUndo);
}
}
@@ -1711,7 +1712,7 @@ static inline void pbyte(pos_T lp, int c)
assert(c <= UCHAR_MAX);
*(ml_get_buf(curbuf, lp.lnum, true) + lp.col) = (char_u)c;
if (!curbuf_splice_pending) {
- extmark_splice(curbuf, (int)lp.lnum-1, lp.col, 0, 1, 0, 1, kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)lp.lnum-1, lp.col, 1, 1, kExtmarkUndo);
}
}
@@ -1856,6 +1857,7 @@ int op_replace(oparg_T *oap, int c)
}
// replace the line
ml_replace(curwin->w_cursor.lnum, newp, false);
+ curbuf_splice_pending++;
linenr_T baselnum = curwin->w_cursor.lnum;
if (after_p != NULL) {
ml_append(curwin->w_cursor.lnum++, after_p, (int)after_p_len, false);
@@ -1863,9 +1865,10 @@ int op_replace(oparg_T *oap, int c)
oap->end.lnum++;
xfree(after_p);
}
+ curbuf_splice_pending--;
extmark_splice(curbuf, (int)baselnum-1, bd.textcol,
- 0, bd.textlen,
- newrows, newcols, kExtmarkUndo);
+ 0, bd.textlen, bd.textlen,
+ newrows, newcols, newrows+newcols, kExtmarkUndo);
}
} else {
// Characterwise or linewise motion replace.
@@ -2399,9 +2402,8 @@ int op_change(oparg_T *oap)
oldp += bd.textcol;
STRMOVE(newp + offset, oldp);
ml_replace(linenr, newp, false);
- extmark_splice(curbuf, (int)linenr-1, bd.textcol,
- 0, 0,
- 0, vpos.coladd+(int)ins_len, kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)linenr-1, bd.textcol,
+ 0, vpos.coladd+(int)ins_len, kExtmarkUndo);
}
}
check_cursor();
@@ -3182,10 +3184,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
assert(columns >= 0);
memmove(ptr, oldp + bd.textcol + delcount, (size_t)columns);
ml_replace(curwin->w_cursor.lnum, newp, false);
- extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, bd.textcol,
- 0, delcount,
- 0, (int)totlen,
- kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)curwin->w_cursor.lnum-1, bd.textcol,
+ delcount, (int)totlen, kExtmarkUndo);
++curwin->w_cursor.lnum;
if (i == 0)
@@ -3309,9 +3309,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
if (totlen && (restart_edit != 0 || (flags & PUT_CURSEND)))
++curwin->w_cursor.col;
changed_bytes(lnum, col);
- extmark_splice(curbuf, (int)lnum-1, col,
- 0, 0,
- 0, (int)totlen, kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)lnum-1, col,
+ 0, (int)totlen, kExtmarkUndo);
} else {
// Insert at least one line. When y_type is kMTCharWise, break the first
// line in two.
@@ -3375,13 +3374,23 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
}
}
+ bcount_t totsize = 0;
+ int lastsize = 0;
+ if (y_type == kMTCharWise
+ || (y_type == kMTLineWise && flags & PUT_LINE_SPLIT)) {
+ for (i = 0; i < y_size-1; i++) {
+ totsize += (bcount_t)STRLEN(y_array[i]) + 1;
+ }
+ lastsize = (int)STRLEN(y_array[y_size-1]);
+ totsize += lastsize;
+ }
if (y_type == kMTCharWise) {
- extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0,
- (int)y_size-1, (int)STRLEN(y_array[y_size-1]),
+ extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0, 0,
+ (int)y_size-1, lastsize, totsize,
kExtmarkUndo);
} else if (y_type == kMTLineWise && flags & PUT_LINE_SPLIT) {
- extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0,
- (int)y_size+1, 0, kExtmarkUndo);
+ extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0, 0,
+ (int)y_size+1, 0, totsize+1, kExtmarkUndo);
}
}
@@ -3825,9 +3834,10 @@ int do_join(size_t count,
}
if (t > 0 && curbuf_splice_pending == 0) {
+ colnr_T removed = (int)(curr- curr_start);
extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, sumsize,
- 1, (int)(curr- curr_start),
- 0, spaces[t],
+ 1, removed, removed + 1,
+ 0, spaces[t], spaces[t],
kExtmarkUndo);
}
currsize = (int)STRLEN(curr);
@@ -5919,7 +5929,7 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet)
const char regname = (char)name;
tv_list_append_string(args, &regname, 1);
- typval_T result = eval_call_provider("clipboard", "get", args);
+ typval_T result = eval_call_provider("clipboard", "get", args, false);
if (result.v_type != VAR_LIST) {
if (result.v_type == VAR_NUMBER && result.vval.v_number == 0) {
@@ -6067,7 +6077,7 @@ static void set_clipboard(int name, yankreg_T *reg)
tv_list_append_string(args, &regtype, 1); // -V614
tv_list_append_string(args, ((char[]) { (char)name }), 1);
- (void)eval_call_provider("clipboard", "set", args);
+ (void)eval_call_provider("clipboard", "set", args, true);
}
/// Avoid slow things (clipboard) during batch operations (while/for-loops).
diff --git a/src/nvim/option.c b/src/nvim/option.c
index a70a634966..8d74cead8d 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -347,22 +347,23 @@ static char *strcpy_comma_escaped(char *dest, const char *src, const size_t len)
return &dest[len + shift];
}
-/// Compute length of a colon-separated value, doubled and with some suffixes
+/// Compute length of a ENV_SEPCHAR-separated value, doubled and with some
+/// suffixes
///
-/// @param[in] val Colon-separated array value.
+/// @param[in] val ENV_SEPCHAR-separated array value.
/// @param[in] common_suf_len Length of the common suffix which is appended to
/// each item in the array, twice.
/// @param[in] single_suf_len Length of the suffix which is appended to each
/// item in the array once.
///
-/// @return Length of the comma-separated string array that contains each item
-/// in the original array twice with suffixes with given length
+/// @return Length of the ENV_SEPCHAR-separated string array that contains each
+/// item in the original array twice with suffixes with given length
/// (common_suf is present after each new item, single_suf is present
/// after half of the new items) and with commas after each item, commas
/// inside the values are escaped.
-static inline size_t compute_double_colon_len(const char *const val,
- const size_t common_suf_len,
- const size_t single_suf_len)
+static inline size_t compute_double_env_sep_len(const char *const val,
+ const size_t common_suf_len,
+ const size_t single_suf_len)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
{
if (val == NULL || *val == NUL) {
@@ -373,7 +374,7 @@ static inline size_t compute_double_colon_len(const char *const val,
do {
size_t dir_len;
const char *dir;
- iter = vim_env_iter(':', val, iter, &dir, &dir_len);
+ iter = vim_env_iter(ENV_SEPCHAR, val, iter, &dir, &dir_len);
if (dir != NULL && dir_len > 0) {
ret += ((dir_len + memcnt(dir, ',', dir_len) + common_suf_len
+ !after_pathsep(dir, dir + dir_len)) * 2
@@ -385,13 +386,13 @@ static inline size_t compute_double_colon_len(const char *const val,
#define NVIM_SIZE (sizeof("nvim") - 1)
-/// Add directories to a comma-separated array from a colon-separated one
+/// Add directories to a ENV_SEPCHAR-separated array from a colon-separated one
///
/// Commas are escaped in process. To each item PATHSEP "nvim" is appended in
/// addition to suf1 and suf2.
///
/// @param[in,out] dest Destination comma-separated array.
-/// @param[in] val Source colon-separated array.
+/// @param[in] val Source ENV_SEPCHAR-separated array.
/// @param[in] suf1 If not NULL, suffix appended to destination. Prior to it
/// directory separator is appended. Suffix must not contain
/// commas.
@@ -404,10 +405,10 @@ static inline size_t compute_double_colon_len(const char *const val,
/// Otherwise in reverse.
///
/// @return (dest + appended_characters_length)
-static inline char *add_colon_dirs(char *dest, const char *const val,
- const char *const suf1, const size_t len1,
- const char *const suf2, const size_t len2,
- const bool forward)
+static inline char *add_env_sep_dirs(char *dest, const char *const val,
+ const char *const suf1, const size_t len1,
+ const char *const suf2, const size_t len2,
+ const bool forward)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1)
{
if (val == NULL || *val == NUL) {
@@ -417,8 +418,8 @@ static inline char *add_colon_dirs(char *dest, const char *const val,
do {
size_t dir_len;
const char *dir;
- iter = (forward ? vim_env_iter : vim_env_iter_rev)(':', val, iter, &dir,
- &dir_len);
+ iter = (forward ? vim_env_iter : vim_env_iter_rev)(ENV_SEPCHAR, val, iter,
+ &dir, &dir_len);
if (dir != NULL && dir_len > 0) {
dest = strcpy_comma_escaped(dest, dir, dir_len);
if (!after_pathsep(dest - 1, dest)) {
@@ -581,10 +582,11 @@ static void set_runtimepath_default(bool clean_arg)
rtp_size += libdir_len + memcnt(libdir, ',', libdir_len) + 1;
}
}
- rtp_size += compute_double_colon_len(data_dirs, NVIM_SIZE + 1 + SITE_SIZE + 1,
- AFTER_SIZE + 1);
- rtp_size += compute_double_colon_len(config_dirs, NVIM_SIZE + 1,
- AFTER_SIZE + 1);
+ rtp_size += compute_double_env_sep_len(data_dirs,
+ NVIM_SIZE + 1 + SITE_SIZE + 1,
+ AFTER_SIZE + 1);
+ rtp_size += compute_double_env_sep_len(config_dirs, NVIM_SIZE + 1,
+ AFTER_SIZE + 1);
if (rtp_size == 0) {
return;
}
@@ -592,20 +594,20 @@ static void set_runtimepath_default(bool clean_arg)
char *rtp_cur = rtp;
rtp_cur = add_dir(rtp_cur, config_home, config_len, kXDGConfigHome,
NULL, 0, NULL, 0);
- rtp_cur = add_colon_dirs(rtp_cur, config_dirs, NULL, 0, NULL, 0, true);
+ rtp_cur = add_env_sep_dirs(rtp_cur, config_dirs, NULL, 0, NULL, 0, true);
rtp_cur = add_dir(rtp_cur, data_home, data_len, kXDGDataHome,
"site", SITE_SIZE, NULL, 0);
- rtp_cur = add_colon_dirs(rtp_cur, data_dirs, "site", SITE_SIZE, NULL, 0,
- true);
+ rtp_cur = add_env_sep_dirs(rtp_cur, data_dirs, "site", SITE_SIZE, NULL, 0,
+ true);
rtp_cur = add_dir(rtp_cur, vimruntime, vimruntime_len, kXDGNone,
NULL, 0, NULL, 0);
rtp_cur = add_dir(rtp_cur, libdir, libdir_len, kXDGNone, NULL, 0, NULL, 0);
- rtp_cur = add_colon_dirs(rtp_cur, data_dirs, "site", SITE_SIZE,
- "after", AFTER_SIZE, false);
+ rtp_cur = add_env_sep_dirs(rtp_cur, data_dirs, "site", SITE_SIZE,
+ "after", AFTER_SIZE, false);
rtp_cur = add_dir(rtp_cur, data_home, data_len, kXDGDataHome,
"site", SITE_SIZE, "after", AFTER_SIZE);
- rtp_cur = add_colon_dirs(rtp_cur, config_dirs, "after", AFTER_SIZE, NULL, 0,
- false);
+ rtp_cur = add_env_sep_dirs(rtp_cur, config_dirs, "after", AFTER_SIZE, NULL, 0,
+ false);
rtp_cur = add_dir(rtp_cur, config_home, config_len, kXDGConfigHome,
"after", AFTER_SIZE, NULL, 0);
// Strip trailing comma.
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index ecaa941082..02fa7ac216 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -453,7 +453,6 @@ EXTERN char_u *p_header; // 'printheader'
EXTERN int p_prompt; // 'prompt'
EXTERN char_u *p_guicursor; // 'guicursor'
EXTERN char_u *p_guifont; // 'guifont'
-EXTERN char_u *p_guifontset; // 'guifontset'
EXTERN char_u *p_guifontwide; // 'guifontwide'
EXTERN char_u *p_hf; // 'helpfile'
EXTERN long p_hh; // 'helpheight'
@@ -513,6 +512,7 @@ EXTERN long p_mle; // 'modelineexpr'
EXTERN long p_mls; // 'modelines'
EXTERN char_u *p_mouse; // 'mouse'
EXTERN char_u *p_mousem; // 'mousemodel'
+EXTERN long p_mousef; // 'mousefocus'
EXTERN long p_mouset; // 'mousetime'
EXTERN int p_more; // 'more'
EXTERN char_u *p_opfunc; // 'operatorfunc'
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 60a38dc2e3..f1221a52a2 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -1022,15 +1022,6 @@ return {
defaults={if_true={vi=""}}
},
{
- full_name='guifontset', abbreviation='gfs',
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- vi_def=true,
- varname='p_guifontset',
- redraw={'ui_option'},
- defaults={if_true={vi=""}}
- },
- {
full_name='guifontwide', abbreviation='gfw',
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
@@ -1597,7 +1588,8 @@ return {
full_name='mousefocus', abbreviation='mousef',
type='bool', scope={'global'},
vi_def=true,
- enable_if=false,
+ redraw={'ui_option'},
+ varname='p_mousef',
defaults={if_true={vi=false}}
},
{
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
index 082ad58223..879266e3d4 100644
--- a/src/nvim/os/env.c
+++ b/src/nvim/os/env.c
@@ -1176,7 +1176,9 @@ bool os_setenv_append_path(const char *fname)
temp[0] = NUL;
} else {
xstrlcpy(temp, path, newlen);
- xstrlcat(temp, ENV_SEPSTR, newlen);
+ if (ENV_SEPCHAR != path[pathlen - 1]) {
+ xstrlcat(temp, ENV_SEPSTR, newlen);
+ }
}
xstrlcat(temp, os_buf, newlen);
os_setenv("PATH", temp, 1);
diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c
index 346e40e02e..4b6533cd0c 100644
--- a/src/nvim/os/time.c
+++ b/src/nvim/os/time.c
@@ -56,6 +56,8 @@ uint64_t os_now(void)
/// Sleeps for `ms` milliseconds.
///
+/// @see uv_sleep() (libuv v1.34.0)
+///
/// @param ms Number of milliseconds to sleep
/// @param ignoreinput If true, only SIGINT (CTRL-C) can interrupt.
void os_delay(uint64_t ms, bool ignoreinput)
@@ -72,6 +74,8 @@ void os_delay(uint64_t ms, bool ignoreinput)
/// Sleeps for `us` microseconds.
///
+/// @see uv_sleep() (libuv v1.34.0)
+///
/// @param us Number of microseconds to sleep.
/// @param ignoreinput If true, ignore all input (including SIGINT/CTRL-C).
/// If false, waiting is aborted on any input.
@@ -172,10 +176,11 @@ char *os_ctime_r(const time_t *restrict clock, char *restrict result,
struct tm *clock_local_ptr = os_localtime_r(clock, &clock_local);
// MSVC returns NULL for an invalid value of seconds.
if (clock_local_ptr == NULL) {
- snprintf(result, result_len, "%s\n", _("(Invalid)"));
+ xstrlcpy(result, _("(Invalid)"), result_len);
} else {
- strftime(result, result_len, "%a %b %d %H:%M:%S %Y\n", clock_local_ptr);
+ strftime(result, result_len, _("%a %b %d %H:%M:%S %Y"), clock_local_ptr);
}
+ xstrlcat(result, "\n", result_len);
return result;
}
diff --git a/src/nvim/po/check.vim b/src/nvim/po/check.vim
index 650c6155e2..d55d4cfa4d 100644
--- a/src/nvim/po/check.vim
+++ b/src/nvim/po/check.vim
@@ -25,6 +25,7 @@ func! GetMline()
" remove '%', not used for formatting.
let idline = substitute(idline, "'%'", '', 'g')
+ let idline = substitute(idline, "%%", '', 'g')
" remove '%' used for plural forms.
let idline = substitute(idline, '\\nPlural-Forms: .\+;\\n', '', '')
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index 9705896e9b..bcf02af4ef 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -1270,8 +1270,9 @@ static regprog_T *bt_regcomp(char_u *expr, int re_flags)
int len;
int flags;
- if (expr == NULL)
- EMSG_RET_NULL(_(e_null));
+ if (expr == NULL) {
+ IEMSG_RET_NULL(_(e_null));
+ }
init_class_tab();
@@ -3483,7 +3484,7 @@ static long bt_regexec_both(char_u *line,
/* Be paranoid... */
if (prog == NULL || line == NULL) {
- EMSG(_(e_null));
+ IEMSG(_(e_null));
goto theend;
}
@@ -4789,7 +4790,7 @@ static bool regmatch(
break;
default:
- EMSG(_(e_re_corr));
+ IEMSG(_(e_re_corr));
#ifdef REGEXP_DEBUG
printf("Illegal op code %d\n", op);
#endif
@@ -5147,7 +5148,7 @@ static bool regmatch(
* We get here only if there's trouble -- normally "case END" is
* the terminating point.
*/
- EMSG(_(e_re_corr));
+ IEMSG(_(e_re_corr));
#ifdef REGEXP_DEBUG
printf("Premature EOL\n");
#endif
@@ -5552,8 +5553,8 @@ do_class:
}
break;
- default: /* Oh dear. Called inappropriately. */
- EMSG(_(e_re_corr));
+ default: // Oh dear. Called inappropriately.
+ IEMSG(_(e_re_corr));
#ifdef REGEXP_DEBUG
printf("Called regrepeat with op code %d\n", OP(p));
#endif
@@ -6911,7 +6912,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest,
}
} else if (*s == NUL) { // we hit NUL.
if (copy) {
- EMSG(_(e_re_damg));
+ IEMSG(_(e_re_damg));
}
goto exit;
} else {
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
index 506c4e87db..7cd1ae93d2 100644
--- a/src/nvim/regexp_nfa.c
+++ b/src/nvim/regexp_nfa.c
@@ -6519,7 +6519,7 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol,
/* Be paranoid... */
if (prog == NULL || line == NULL) {
- EMSG(_(e_null));
+ IEMSG(_(e_null));
goto theend;
}
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 4292ed2865..3c2e1ccaf5 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -513,7 +513,7 @@ int update_screen(int type)
FIXED_TEMP_ARRAY(args, 2);
args.items[0] = BUFFER_OBJ(buf->handle);
args.items[1] = INTEGER_OBJ(display_tick);
- executor_exec_lua_cb(buf->b_luahl_start, "start", args, false, &err);
+ nlua_call_ref(buf->b_luahl_start, "start", args, false, &err);
if (ERROR_SET(&err)) {
ELOG("error in luahl start: %s", err.msg);
api_clear_error(&err);
@@ -1251,7 +1251,7 @@ static void win_update(win_T *wp)
args.items[3] = INTEGER_OBJ(knownmax);
// TODO(bfredl): we could allow this callback to change mod_top, mod_bot.
// For now the "start" callback is expected to use nvim__buf_redraw_range.
- executor_exec_lua_cb(buf->b_luahl_window, "window", args, false, &err);
+ nlua_call_ref(buf->b_luahl_window, "window", args, false, &err);
if (ERROR_SET(&err)) {
ELOG("error in luahl window: %s", err.msg);
api_clear_error(&err);
@@ -2356,8 +2356,7 @@ win_line (
args.items[2] = INTEGER_OBJ(lnum-1);
lua_attr_active = true;
extra_check = true;
- Object o = executor_exec_lua_cb(buf->b_luahl_line, "line",
- args, true, &err);
+ Object o = nlua_call_ref(buf->b_luahl_line, "line", args, true, &err);
lua_attr_active = false;
if (o.type == kObjectTypeString) {
// TODO(bfredl): this is a bit of a hack. A final API should use an
@@ -2554,6 +2553,7 @@ win_line (
}
// If this line has a sign with line highlighting set line_attr.
+ // TODO(bfredl, vigoux): this should not take priority over decorations!
v = buf_getsigntype(wp->w_buffer, lnum, SIGN_LINEHL, 0, 1);
if (v != 0) {
line_attr = sign_get_attr((int)v, SIGN_LINEHL);
@@ -6216,7 +6216,7 @@ void win_grid_alloc(win_T *wp)
|| grid->Rows != rows
|| grid->Columns != cols) {
if (want_allocation) {
- grid_alloc(grid, rows, cols, wp->w_grid.valid, wp->w_grid.valid);
+ grid_alloc(grid, rows, cols, wp->w_grid.valid, false);
grid->valid = true;
} else {
// Single grid mode, all rendering will be redirected to default_grid.
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index 95948dac78..dc1bfe25b4 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -4405,8 +4405,6 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
}
break;
- FALLTHROUGH;
-
case STATE_INS:
// Insert one byte. Repeat this for each possible byte at this
// node.
@@ -5663,6 +5661,9 @@ check_suggestions (
int len;
hlf_T attr;
+ if (gap->ga_len == 0) {
+ return;
+ }
stp = &SUG(*gap, 0);
for (int i = gap->ga_len - 1; i >= 0; --i) {
// Need to append what follows to check for "the the".
@@ -5765,14 +5766,14 @@ cleanup_suggestions (
)
FUNC_ATTR_NONNULL_ALL
{
- suggest_T *stp = &SUG(*gap, 0);
-
if (gap->ga_len > 0) {
// Sort the list.
qsort(gap->ga_data, (size_t)gap->ga_len, sizeof(suggest_T), sug_compare);
// Truncate the list to the number of suggestions that will be displayed.
if (gap->ga_len > keep) {
+ suggest_T *const stp = &SUG(*gap, 0);
+
for (int i = keep; i < gap->ga_len; i++) {
xfree(stp[i].st_word);
}
diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c
index a7d26e2a8e..09d8646c6d 100644
--- a/src/nvim/spellfile.c
+++ b/src/nvim/spellfile.c
@@ -984,15 +984,17 @@ nextone:
static char_u *read_cnt_string(FILE *fd, int cnt_bytes, int *cntp)
{
int cnt = 0;
- int i;
char_u *str;
// read the length bytes, MSB first
- for (i = 0; i < cnt_bytes; ++i)
- cnt = (cnt << 8) + getc(fd);
- if (cnt < 0) {
- *cntp = SP_TRUNCERROR;
- return NULL;
+ for (int i = 0; i < cnt_bytes; i++) {
+ const int c = getc(fd);
+
+ if (c == EOF) {
+ *cntp = SP_TRUNCERROR;
+ return NULL;
+ }
+ cnt = (cnt << 8) + (unsigned)c;
}
*cntp = cnt;
if (cnt == 0)
@@ -3038,9 +3040,9 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile)
spin->si_msg_count = 999999;
// Read and ignore the first line: word count.
- (void)vim_fgets(line, MAXLINELEN, fd);
- if (!ascii_isdigit(*skipwhite(line)))
+ if (vim_fgets(line, MAXLINELEN, fd) || !ascii_isdigit(*skipwhite(line))) {
EMSG2(_("E760: No word count in %s"), fname);
+ }
// Read all the lines in the file one by one.
// The words are converted to 'encoding' here, before being added to
diff --git a/src/nvim/testdir/check.vim b/src/nvim/testdir/check.vim
index 57a8eb57b8..073873bcb0 100644
--- a/src/nvim/testdir/check.vim
+++ b/src/nvim/testdir/check.vim
@@ -1,6 +1,46 @@
source shared.vim
source term_util.vim
+" Command to check for the presence of a feature.
+command -nargs=1 CheckFeature call CheckFeature(<f-args>)
+func CheckFeature(name)
+ if !has(a:name)
+ throw 'Skipped: ' .. a:name .. ' feature missing'
+ endif
+endfunc
+
+" Command to check for the presence of a working option.
+command -nargs=1 CheckOption call CheckOption(<f-args>)
+func CheckOption(name)
+ if !exists('+' .. a:name)
+ throw 'Skipped: ' .. a:name .. ' option not supported'
+ endif
+endfunc
+
+" Command to check for the presence of a function.
+command -nargs=1 CheckFunction call CheckFunction(<f-args>)
+func CheckFunction(name)
+ if !exists('*' .. a:name)
+ throw 'Skipped: ' .. a:name .. ' function missing'
+ endif
+endfunc
+
+" Command to check for running on MS-Windows
+command CheckMSWindows call CheckMSWindows()
+func CheckMSWindows()
+ if !has('win32')
+ throw 'Skipped: only works on MS-Windows'
+ endif
+endfunc
+
+" Command to check for running on Unix
+command CheckUnix call CheckUnix()
+func CheckUnix()
+ if !has('unix')
+ throw 'Skipped: only works on Unix'
+ endif
+endfunc
+
" Command to check that making screendumps is supported.
" Caller must source screendump.vim
command CheckScreendump call CheckScreendump()
@@ -9,3 +49,19 @@ func CheckScreendump()
throw 'Skipped: cannot make screendumps'
endif
endfunc
+
+" Command to check that we can Run Vim in a terminal window
+command CheckRunVimInTerminal call CheckRunVimInTerminal()
+func CheckRunVimInTerminal()
+ if !CanRunVimInTerminal()
+ throw 'Skipped: cannot run Vim in a terminal window'
+ endif
+endfunc
+
+" Command to check that we can run the GUI
+command CheckCanRunGui call CheckCanRunGui()
+func CheckCanRunGui()
+ if !has('gui') || ($DISPLAY == "" && !has('gui_running'))
+ throw 'Skipped: cannot start the GUI'
+ endif
+endfunc
diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim
index 4f16aa807c..765ba2cbb6 100644
--- a/src/nvim/testdir/runtest.vim
+++ b/src/nvim/testdir/runtest.vim
@@ -101,6 +101,8 @@ let &runtimepath .= ','.expand($BUILD_DIR).'/runtime/'
" Always use forward slashes.
set shellslash
+let s:t_bold = &t_md
+let s:t_normal = &t_me
if has('win32')
" avoid prompt that is long or contains a line break
let $PROMPT = '$P$G'
@@ -209,7 +211,15 @@ func RunTheTest(test)
let message = 'Executed ' . a:test
if has('reltime')
- let message ..= ' in ' .. reltimestr(reltime(func_start)) .. ' seconds'
+ let message ..= repeat(' ', 50 - len(message))
+ let time = reltime(func_start)
+ if has('float') && reltimefloat(time) > 0.1
+ let message = s:t_bold .. message
+ endif
+ let message ..= ' in ' .. reltimestr(time) .. ' seconds'
+ if has('float') && reltimefloat(time) > 0.1
+ let message ..= s:t_normal
+ endif
endif
call add(s:messages, message)
let s:done += 1
@@ -277,7 +287,9 @@ func FinishTesting()
let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test')
endif
if s:done > 0 && has('reltime')
+ let message = s:t_bold .. message .. repeat(' ', 40 - len(message))
let message ..= ' in ' .. reltimestr(reltime(s:start_time)) .. ' seconds'
+ let message ..= s:t_normal
endif
echo message
call add(s:messages, message)
diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim
index 41ff9b2bd6..6180d542ff 100644
--- a/src/nvim/testdir/shared.vim
+++ b/src/nvim/testdir/shared.vim
@@ -271,7 +271,7 @@ func GetVimCommand(...)
let cmd = cmd . ' -u ' . name
endif
let cmd .= ' --headless -i NONE'
- let cmd = substitute(cmd, 'VIMRUNTIME=.*VIMRUNTIME;', '', '')
+ let cmd = substitute(cmd, 'VIMRUNTIME=\S\+', '', '')
" If using valgrind, make sure every run uses a different log file.
if cmd =~ 'valgrind.*--log-file='
@@ -329,7 +329,3 @@ func RunVimPiped(before, after, arguments, pipecmd)
endif
return 1
endfunc
-
-func CanRunGui()
- return has('gui') && ($DISPLAY != "" || has('gui_running'))
-endfunc
diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim
index a1f1dd3bab..f09a64c329 100644
--- a/src/nvim/testdir/test_diffmode.vim
+++ b/src/nvim/testdir/test_diffmode.vim
@@ -724,10 +724,140 @@ func Test_diff_lastline()
bwipe!
endfunc
+func Test_diff_screen()
+ CheckScreendump
+ CheckFeature menu
+
+ " clean up already existing swap files, just in case
+ call delete('.Xfile1.swp')
+ call delete('.Xfile2.swp')
+
+ " Test 1: Add a line in beginning of file 2
+ call WriteDiffFiles(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+ let buf = RunVimInTerminal('-d Xfile1 Xfile2', {})
+ " Set autoread mode, so that Vim won't complain once we re-write the test
+ " files
+ call term_sendkeys(buf, ":set autoread\<CR>\<c-w>w:set autoread\<CR>\<c-w>w")
+
+ call VerifyBoth(buf, 'Test_diff_01', '')
+
+ " Test 2: Add a line in beginning of file 1
+ call WriteDiffFiles(buf, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+ call VerifyBoth(buf, 'Test_diff_02', '')
+
+ " Test 3: Add a line at the end of file 2
+ call WriteDiffFiles(buf, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
+ call VerifyBoth(buf, 'Test_diff_03', '')
+
+ " Test 4: Add a line at the end of file 1
+ call WriteDiffFiles(buf, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+ call VerifyBoth(buf, 'Test_diff_04', '')
+
+ " Test 5: Add a line in the middle of file 2, remove on at the end of file 1
+ call WriteDiffFiles(buf, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], [1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10])
+ call VerifyBoth(buf, 'Test_diff_05', '')
+
+ " Test 6: Add a line in the middle of file 1, remove on at the end of file 2
+ call WriteDiffFiles(buf, [1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
+ call VerifyBoth(buf, 'Test_diff_06', '')
+
+ " Variants on test 6 with different context settings
+ call term_sendkeys(buf, ":set diffopt+=context:2\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_06.2', {})
+ call term_sendkeys(buf, ":set diffopt-=context:2\<cr>")
+ call term_sendkeys(buf, ":set diffopt+=context:1\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_06.1', {})
+ call term_sendkeys(buf, ":set diffopt-=context:1\<cr>")
+ call term_sendkeys(buf, ":set diffopt+=context:0\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_06.0', {})
+ call term_sendkeys(buf, ":set diffopt-=context:0\<cr>")
+
+ " Test 7 - 9: Test normal/patience/histogram diff algorithm
+ call WriteDiffFiles(buf, ['#include <stdio.h>', '', '// Frobs foo heartily', 'int frobnitz(int foo)', '{',
+ \ ' int i;', ' for(i = 0; i < 10; i++)', ' {', ' printf("Your answer is: ");',
+ \ ' printf("%d\n", foo);', ' }', '}', '', 'int fact(int n)', '{', ' if(n > 1)', ' {',
+ \ ' return fact(n-1) * n;', ' }', ' return 1;', '}', '', 'int main(int argc, char **argv)',
+ \ '{', ' frobnitz(fact(10));', '}'],
+ \ ['#include <stdio.h>', '', 'int fib(int n)', '{', ' if(n > 2)', ' {',
+ \ ' return fib(n-1) + fib(n-2);', ' }', ' return 1;', '}', '', '// Frobs foo heartily',
+ \ 'int frobnitz(int foo)', '{', ' int i;', ' for(i = 0; i < 10; i++)', ' {',
+ \ ' printf("%d\n", foo);', ' }', '}', '',
+ \ 'int main(int argc, char **argv)', '{', ' frobnitz(fib(10));', '}'])
+ call term_sendkeys(buf, ":diffupdate!\<cr>")
+ call term_sendkeys(buf, ":set diffopt+=internal\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_07', {})
+
+ call term_sendkeys(buf, ":set diffopt+=algorithm:patience\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_08', {})
+
+ call term_sendkeys(buf, ":set diffopt+=algorithm:histogram\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_09', {})
+
+ " Test 10-11: normal/indent-heuristic
+ call term_sendkeys(buf, ":set diffopt&vim\<cr>")
+ call WriteDiffFiles(buf, ['', ' def finalize(values)', '', ' values.each do |v|', ' v.finalize', ' end'],
+ \ ['', ' def finalize(values)', '', ' values.each do |v|', ' v.prepare', ' end', '',
+ \ ' values.each do |v|', ' v.finalize', ' end'])
+ call term_sendkeys(buf, ":diffupdate!\<cr>")
+ call term_sendkeys(buf, ":set diffopt+=internal\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_10', {})
+
+ " Leave trailing : at commandline!
+ call term_sendkeys(buf, ":set diffopt+=indent-heuristic\<cr>:\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_11', {}, 'one')
+ " shouldn't matter, if indent-algorithm comes before or after the algorithm
+ call term_sendkeys(buf, ":set diffopt&\<cr>")
+ call term_sendkeys(buf, ":set diffopt+=indent-heuristic,algorithm:patience\<cr>:\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_11', {}, 'two')
+ call term_sendkeys(buf, ":set diffopt&\<cr>")
+ call term_sendkeys(buf, ":set diffopt+=algorithm:patience,indent-heuristic\<cr>:\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_11', {}, 'three')
+
+ " Test 12: diff the same file
+ call WriteDiffFiles(buf, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+ call VerifyBoth(buf, 'Test_diff_12', '')
+
+ " Test 13: diff an empty file
+ call WriteDiffFiles(buf, [], [])
+ call VerifyBoth(buf, 'Test_diff_13', '')
+
+ " Test 14: test diffopt+=icase
+ call WriteDiffFiles(buf, ['a', 'b', 'cd'], ['A', 'b', 'cDe'])
+ call VerifyBoth(buf, 'Test_diff_14', " diffopt+=filler diffopt+=icase")
+
+ " Test 15-16: test diffopt+=iwhite
+ call WriteDiffFiles(buf, ['int main()', '{', ' printf("Hello, World!");', ' return 0;', '}'],
+ \ ['int main()', '{', ' if (0)', ' {', ' printf("Hello, World!");', ' return 0;', ' }', '}'])
+ call term_sendkeys(buf, ":diffupdate!\<cr>")
+ call term_sendkeys(buf, ":set diffopt&vim diffopt+=filler diffopt+=iwhite\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_15', {})
+ call term_sendkeys(buf, ":set diffopt+=internal\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_16', {})
+
+ " Test 17: test diffopt+=iblank
+ call WriteDiffFiles(buf, ['a', ' ', 'cd', 'ef', 'xxx'], ['a', 'cd', '', 'ef', 'yyy'])
+ call VerifyInternal(buf, 'Test_diff_17', " diffopt+=iblank")
+
+ " Test 18: test diffopt+=iblank,iwhite / iwhiteall / iwhiteeol
+ call VerifyInternal(buf, 'Test_diff_18', " diffopt+=iblank,iwhite")
+ call VerifyInternal(buf, 'Test_diff_18', " diffopt+=iblank,iwhiteall")
+ call VerifyInternal(buf, 'Test_diff_18', " diffopt+=iblank,iwhiteeol")
+
+ " Test 19: test diffopt+=iwhiteeol
+ call WriteDiffFiles(buf, ['a ', 'x', 'cd', 'ef', 'xx xx', 'foo', 'bar'], ['a', 'x', 'c d', ' ef', 'xx xx', 'foo', '', 'bar'])
+ call VerifyInternal(buf, 'Test_diff_19', " diffopt+=iwhiteeol")
+
+ " Test 19: test diffopt+=iwhiteall
+ call VerifyInternal(buf, 'Test_diff_20', " diffopt+=iwhiteall")
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xfile1')
+ call delete('Xfile2')
+endfunc
+
func Test_diff_with_cursorline()
- if !CanRunVimInTerminal()
- throw 'Skipped: cannot run Vim in a terminal window'
- endif
+ CheckScreendump
call writefile([
\ 'hi CursorLine ctermbg=red ctermfg=white',
@@ -751,13 +881,45 @@ func Test_diff_with_cursorline()
call delete('Xtest_diff_cursorline')
endfunc
+func Test_diff_with_syntax()
+ CheckScreendump
+
+ let lines =<< trim END
+ void doNothing() {
+ int x = 0;
+ char *s = "hello";
+ return 5;
+ }
+ END
+ call writefile(lines, 'Xprogram1.c')
+ let lines =<< trim END
+ void doSomething() {
+ int x = 0;
+ char *s = "there";
+ return 5;
+ }
+ END
+ call writefile(lines, 'Xprogram2.c')
+
+ let lines =<< trim END
+ edit Xprogram1.c
+ diffsplit Xprogram2.c
+ END
+ call writefile(lines, 'Xtest_diff_syntax')
+ let buf = RunVimInTerminal('-S Xtest_diff_syntax', {})
+
+ call VerifyScreenDump(buf, 'Test_diff_syntax_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xtest_diff_syntax')
+ call delete('Xprogram1.c')
+ call delete('Xprogram2.c')
+endfunc
+
func Test_diff_of_diff()
- if !CanRunVimInTerminal()
- throw 'Skipped: cannot run Vim in a terminal window'
- endif
- if !has("rightleft")
- throw 'Skipped: rightleft not supported'
- endif
+ CheckScreendump
+ CheckFeature rightleft
call writefile([
\ 'call setline(1, ["aa","bb","cc","@@ -3,2 +5,7 @@","dd","ee","ff"])',
diff --git a/src/nvim/testdir/test_display.vim b/src/nvim/testdir/test_display.vim
index 1c2f5a05ff..429253a863 100644
--- a/src/nvim/testdir/test_display.vim
+++ b/src/nvim/testdir/test_display.vim
@@ -6,11 +6,12 @@
" endif
source view_util.vim
+source check.vim
+source screendump.vim
+
+func Test_display_foldcolumn()
+ CheckFeature folding
-func! Test_display_foldcolumn()
- if !has("folding")
- return
- endif
new
vnew
vert resize 25
@@ -26,10 +27,10 @@ func! Test_display_foldcolumn()
call cursor(2, 1)
norm! zt
- let lines=ScreenLines([1,2], winwidth(0))
+ let lines = ScreenLines([1,2], winwidth(0))
call assert_equal(expect, lines)
set fdc=2
- let lines=ScreenLines([1,2], winwidth(0))
+ let lines = ScreenLines([1,2], winwidth(0))
let expect = [
\ " e more noise blah blah<",
\ " 82> more stuff here "
@@ -41,9 +42,8 @@ func! Test_display_foldcolumn()
endfunc
func! Test_display_foldtext_mbyte()
- if !has("folding")
- return
- endif
+ CheckFeature folding
+
call NewWindow(10, 40)
call append(0, range(1,20))
exe "set foldmethod=manual foldtext=foldtext() fillchars=fold:\u2500,vert:\u2502 fdc=2"
@@ -70,6 +70,42 @@ func! Test_display_foldtext_mbyte()
bw!
endfunc
+" check that win_ins_lines() and win_del_lines() work when t_cs is empty.
+func Test_scroll_without_region()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, range(1, 20))
+ set t_cs=
+ set laststatus=2
+ END
+ call writefile(lines, 'Xtestscroll')
+ let buf = RunVimInTerminal('-S Xtestscroll', #{rows: 10})
+
+ call VerifyScreenDump(buf, 'Test_scroll_no_region_1', {})
+
+ call term_sendkeys(buf, ":3delete\<cr>")
+ call VerifyScreenDump(buf, 'Test_scroll_no_region_2', {})
+
+ call term_sendkeys(buf, ":4put\<cr>")
+ call VerifyScreenDump(buf, 'Test_scroll_no_region_3', {})
+
+ call term_sendkeys(buf, ":undo\<cr>")
+ call term_sendkeys(buf, ":undo\<cr>")
+ call term_sendkeys(buf, ":set laststatus=0\<cr>")
+ call VerifyScreenDump(buf, 'Test_scroll_no_region_4', {})
+
+ call term_sendkeys(buf, ":3delete\<cr>")
+ call VerifyScreenDump(buf, 'Test_scroll_no_region_5', {})
+
+ call term_sendkeys(buf, ":4put\<cr>")
+ call VerifyScreenDump(buf, 'Test_scroll_no_region_6', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xtestscroll')
+endfunc
+
func Test_display_listchars_precedes()
set fillchars+=vert:\|
call NewWindow(10, 10)
@@ -125,3 +161,26 @@ func Test_display_listchars_precedes()
set list& listchars& wrap&
bw!
endfunc
+
+" Check that win_lines() works correctly with the number_only parameter=TRUE
+" should break early to optimize cost of drawing, but needs to make sure
+" that the number column is correctly highlighted.
+func Test_scroll_CursorLineNr_update()
+ CheckScreendump
+
+ let lines =<< trim END
+ hi CursorLineNr ctermfg=73 ctermbg=236
+ set nu rnu cursorline cursorlineopt=number
+ exe ":norm! o\<esc>110ia\<esc>"
+ END
+ let filename = 'Xdrawscreen'
+ call writefile(lines, filename)
+ let buf = RunVimInTerminal('-S '.filename, #{rows: 5, cols: 50})
+ call term_sendkeys(buf, "k")
+ call term_wait(buf)
+ call VerifyScreenDump(buf, 'Test_winline_rnu', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete(filename)
+endfunc
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index 529f237a97..617e3dfe41 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -75,6 +75,7 @@ let s:filename_checks = {
\ 'ave': ['file.ave'],
\ 'awk': ['file.awk', 'file.gawk'],
\ 'b': ['file.mch', 'file.ref', 'file.imp'],
+ \ 'bzl': ['file.bazel', 'file.bzl', 'WORKSPACE'],
\ 'bc': ['file.bc'],
\ 'bdf': ['file.bdf'],
\ 'bib': ['file.bib'],
@@ -526,6 +527,7 @@ let s:filename_checks = {
let s:filename_case_checks = {
\ 'modula2': ['file.DEF', 'file.MOD'],
+ \ 'bzl': ['file.BUILD', 'BUILD'],
\ }
func CheckItems(checks)
diff --git a/src/nvim/testdir/test_perl.vim b/src/nvim/testdir/test_perl.vim
new file mode 100644
index 0000000000..2343f389fa
--- /dev/null
+++ b/src/nvim/testdir/test_perl.vim
@@ -0,0 +1,225 @@
+" Tests for Perl interface
+
+if !has('perl') || has('win32')
+ finish
+endif
+
+perl $SIG{__WARN__} = sub { die "Unexpected warnings from perl: @_" };
+
+func Test_change_buffer()
+ call setline(line('$'), ['1 line 1'])
+ perl VIM::DoCommand("normal /^1\n")
+ perl $curline = VIM::Eval("line('.')")
+ perl $curbuf->Set($curline, "1 changed line 1")
+ call assert_equal('1 changed line 1', getline('$'))
+endfunc
+
+func Test_evaluate_list()
+ call setline(line('$'), ['2 line 2'])
+ perl VIM::DoCommand("normal /^2\n")
+ perl $curline = VIM::Eval("line('.')")
+ let l = ["abc", "def"]
+ perl << EOF
+ $l = VIM::Eval("l");
+ $curbuf->Append($curline, $l);
+EOF
+endfunc
+
+funct Test_VIM_Blob()
+ call assert_equal('0z', perleval('VIM::Blob("")'))
+ "call assert_equal('0z31326162', 'VIM::Blob("12ab")'->perleval())
+ call assert_equal('0z00010203', perleval('VIM::Blob("\x00\x01\x02\x03")'))
+ call assert_equal('0z8081FEFF', perleval('VIM::Blob("\x80\x81\xfe\xff")'))
+endfunc
+
+func Test_buffer_Delete()
+ new
+ call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])
+ perl $curbuf->Delete(7)
+ perl $curbuf->Delete(2, 5)
+ perl $curbuf->Delete(10)
+ call assert_equal(['a', 'f', 'h'], getline(1, '$'))
+ bwipe!
+endfunc
+
+func Test_buffer_Append()
+ new
+ perl $curbuf->Append(1, '1')
+ perl $curbuf->Append(2, '2', '3', '4')
+ call assert_equal(['', '1', '2', '3', '4'], getline(1, '$'))
+ perl @l = ('5' ..'7')
+ perl $curbuf->Append(0, @l)
+ call assert_equal(['5', '6', '7', '', '1', '2', '3', '4'], getline(1, '$'))
+ bwipe!
+endfunc
+
+func Test_buffer_Set()
+ new
+ call setline(1, ['1', '2', '3', '4', '5'])
+ perl $curbuf->Set(2, 'a', 'b', 'c')
+ perl $curbuf->Set(4, 'A', 'B', 'C')
+ call assert_equal(['1', 'a', 'b', 'A', 'B'], getline(1, '$'))
+ bwipe!
+endfunc
+
+func Test_buffer_Get()
+ new
+ call setline(1, ['1', '2', '3', '4'])
+ call assert_equal('2:3', perleval('join(":", $curbuf->Get(2, 3))'))
+ bwipe!
+endfunc
+
+func Test_buffer_Count()
+ new
+ call setline(1, ['a', 'b', 'c'])
+ call assert_equal(3, perleval('$curbuf->Count()'))
+ bwipe!
+endfunc
+
+func Test_buffer_Name()
+ new
+ call assert_equal('', perleval('$curbuf->Name()'))
+ bwipe!
+ new Xfoo
+ call assert_equal('Xfoo', perleval('$curbuf->Name()'))
+ bwipe!
+endfunc
+
+func Test_buffer_Number()
+ call assert_equal(bufnr('%'), perleval('$curbuf->Number()'))
+endfunc
+
+func Test_window_Cursor()
+ new
+ call setline(1, ['line1', 'line2'])
+ perl $curwin->Cursor(2, 3)
+ call assert_equal('2:3', perleval('join(":", $curwin->Cursor())'))
+ " Col is numbered from 0 in Perl, and from 1 in Vim script.
+ call assert_equal([0, 2, 4, 0], getpos('.'))
+ bwipe!
+endfunc
+
+func Test_window_SetHeight()
+ new
+ perl $curwin->SetHeight(2)
+ call assert_equal(2, winheight(0))
+ bwipe!
+endfunc
+
+func Test_VIM_Windows()
+ new
+ " VIM::Windows() without argument in scalar and list context.
+ perl $winnr = VIM::Windows()
+ perl @winlist = VIM::Windows()
+ perl $curbuf->Append(0, $winnr, scalar(@winlist))
+ call assert_equal(['2', '2', ''], getline(1, '$'))
+
+ "" VIM::Windows() with window number argument.
+ perl (VIM::Windows(VIM::Eval('winnr()')))[0]->Buffer()->Set(1, 'bar')
+ call assert_equal('bar', getline(1))
+ bwipe!
+endfunc
+
+func Test_VIM_Buffers()
+ new Xbar
+ " VIM::Buffers() without argument in scalar and list context.
+ perl $nbuf = VIM::Buffers()
+ perl @buflist = VIM::Buffers()
+
+ " VIM::Buffers() with argument.
+ perl $curbuf = (VIM::Buffers('Xbar'))[0]
+ perl $curbuf->Append(0, $nbuf, scalar(@buflist))
+ call assert_equal(['2', '2', ''], getline(1, '$'))
+ bwipe!
+endfunc
+
+func Test_perleval()
+ call assert_false(perleval('undef'))
+
+ "" scalar
+ call assert_equal(0, perleval('0'))
+ call assert_equal(2, perleval('2'))
+ call assert_equal(-2, perleval('-2'))
+ if has('float')
+ call assert_equal(2.5, perleval('2.5'))
+ else
+ call assert_equal(2, perleval('2.5'))
+ end
+
+ call assert_equal('abc', perleval('"abc"'))
+
+ "" ref
+ call assert_equal([], perleval('[]'))
+ call assert_equal(['word', 42, [42],{}], perleval('["word", 42, [42], {}]'))
+
+ call assert_equal({}, perleval('{}'))
+ call assert_equal({'foo': 'bar'}, perleval('{foo => "bar"}'))
+
+ perl our %h; our @a;
+ let a = perleval('[\%h, \%h, \@a, \@a]')
+ echo a
+ call assert_equal(a[0], a[1])
+ call assert_equal(a[2], a[3])
+ perl undef %h; undef @a;
+
+ call assert_equal('*VIM', perleval('"*VIM"'))
+endfunc
+
+func Test_perldo()
+ sp __TEST__
+ exe 'read ' g:testname
+ perldo s/perl/vieux_chameau/g
+ 1
+ call assert_false(search('\Cperl'))
+ bw!
+
+ " Check deleting lines does not trigger ml_get error.
+ new
+ call setline(1, ['one', 'two', 'three'])
+ perldo VIM::DoCommand("%d_")
+ bwipe!
+
+ "" Check switching to another buffer does not trigger ml_get error.
+ new
+ let wincount = winnr('$')
+ call setline(1, ['one', 'two', 'three'])
+ perldo VIM::DoCommand("new")
+ call assert_equal(wincount + 1, winnr('$'))
+ bwipe!
+ bwipe!
+endfunc
+
+func Test_VIM_package()
+ perl VIM::DoCommand('let l:var = "foo"')
+ call assert_equal(l:var, 'foo')
+
+ set noet
+ perl VIM::SetOption('et')
+ call assert_true(&et)
+endfunc
+
+func Test_set_cursor()
+ " Check that setting the cursor position works.
+ new
+ call setline(1, ['first line', 'second line'])
+ normal gg
+ perldo $curwin->Cursor(1, 5)
+ call assert_equal([1, 6], [line('.'), col('.')])
+
+ " Check that movement after setting cursor position keeps current column.
+ normal j
+ call assert_equal([2, 6], [line('.'), col('.')])
+endfunc
+
+" Test for various heredoc syntax
+func Test_perl_heredoc()
+ perl << END
+VIM::DoCommand('let s = "A"')
+END
+ perl <<
+VIM::DoCommand('let s ..= "B"')
+.
+ call assert_equal('AB', s)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim
index 75673adf0a..2223be952c 100644
--- a/src/nvim/testdir/test_textformat.vim
+++ b/src/nvim/testdir/test_textformat.vim
@@ -1,4 +1,7 @@
" Tests for the various 'formatoptions' settings
+
+source check.vim
+
func Test_text_format()
enew!
@@ -490,6 +493,23 @@ func Test_format_list_auto()
set fo& ai& bs&
endfunc
+func Test_crash_github_issue_5095()
+ CheckFeature autocmd
+
+ " This used to segfault, see https://github.com/vim/vim/issues/5095
+ augroup testing
+ au BufNew x center
+ augroup END
+
+ next! x
+
+ bw
+ augroup testing
+ au!
+ augroup END
+ augroup! testing
+endfunc
+
" Test for formatting multi-byte text with 'fo=t'
func Test_tw_2_fo_t()
new
diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim
index aaa291f87d..9f47ee2904 100644
--- a/src/nvim/testdir/test_window_cmd.vim
+++ b/src/nvim/testdir/test_window_cmd.vim
@@ -841,4 +841,46 @@ func Test_winnr()
only | tabonly
endfunc
+func Test_window_resize()
+ " Vertical :resize (absolute, relative, min and max size).
+ vsplit
+ vert resize 8
+ call assert_equal(8, winwidth(0))
+ vert resize +2
+ call assert_equal(10, winwidth(0))
+ vert resize -2
+ call assert_equal(8, winwidth(0))
+ vert resize
+ call assert_equal(&columns - 2, winwidth(0))
+ vert resize 0
+ call assert_equal(1, winwidth(0))
+ vert resize 99999
+ call assert_equal(&columns - 2, winwidth(0))
+
+ %bwipe!
+
+ " Horizontal :resize (with absolute, relative size, min and max size).
+ split
+ resize 8
+ call assert_equal(8, winheight(0))
+ resize +2
+ call assert_equal(10, winheight(0))
+ resize -2
+ call assert_equal(8, winheight(0))
+ resize
+ call assert_equal(&lines - 4, winheight(0))
+ resize 0
+ call assert_equal(1, winheight(0))
+ resize 99999
+ call assert_equal(&lines - 4, winheight(0))
+
+ " :resize with explicit window number.
+ let other_winnr = winnr('j')
+ exe other_winnr .. 'resize 10'
+ call assert_equal(10, winheight(other_winnr))
+ call assert_equal(&lines - 10 - 3, winheight(0))
+
+ %bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index 3b71066094..dde17726fd 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -108,6 +108,7 @@ typedef struct {
bool cork, overflow;
bool cursor_color_changed;
bool is_starting;
+ FILE *screenshot;
cursorentry_T cursor_shapes[SHAPE_IDX_COUNT];
HlAttrs clear_attrs;
kvec_t(HlAttrs) attrs;
@@ -167,6 +168,7 @@ UI *tui_start(void)
ui->suspend = tui_suspend;
ui->set_title = tui_set_title;
ui->set_icon = tui_set_icon;
+ ui->screenshot = tui_screenshot;
ui->option_set= tui_option_set;
ui->raw_line = tui_raw_line;
@@ -412,6 +414,7 @@ static void tui_main(UIBridgeData *bridge, UI *ui)
data->bridge = bridge;
data->loop = &tui_loop;
data->is_starting = true;
+ data->screenshot = NULL;
kv_init(data->invalid_regions);
signal_watcher_init(data->loop, &data->winch_handle, ui);
signal_watcher_init(data->loop, &data->cont_handle, data);
@@ -1317,6 +1320,31 @@ static void tui_set_icon(UI *ui, String icon)
{
}
+static void tui_screenshot(UI *ui, String path)
+{
+ TUIData *data = ui->data;
+ UGrid *grid = &data->grid;
+ flush_buf(ui);
+ grid->row = 0;
+ grid->col = 0;
+
+ FILE *f = fopen(path.data, "w");
+ data->screenshot = f;
+ fprintf(f, "%d,%d\n", grid->height, grid->width);
+ unibi_out(ui, unibi_clear_screen);
+ for (int i = 0; i < grid->height; i++) {
+ cursor_goto(ui, i, 0);
+ for (int j = 0; j < grid->width; j++) {
+ print_cell(ui, &grid->cells[i][j]);
+ }
+ }
+ flush_buf(ui);
+ data->screenshot = NULL;
+
+ fclose(f);
+}
+
+
static void tui_option_set(UI *ui, String name, Object value)
{
TUIData *data = ui->data;
@@ -2054,9 +2082,15 @@ static void flush_buf(UI *ui)
}
}
- uv_write(&req, STRUCT_CAST(uv_stream_t, &data->output_handle),
- bufs, (unsigned)(bufp - bufs), NULL);
- uv_run(&data->write_loop, UV_RUN_DEFAULT);
+ if (data->screenshot) {
+ for (size_t i = 0; i < (size_t)(bufp - bufs); i++) {
+ fwrite(bufs[i].base, bufs[i].len, 1, data->screenshot);
+ }
+ } else {
+ uv_write(&req, STRUCT_CAST(uv_stream_t, &data->output_handle),
+ bufs, (unsigned)(bufp - bufs), NULL);
+ uv_run(&data->write_loop, UV_RUN_DEFAULT);
+ }
data->bufpos = 0;
data->overflow = false;
}
diff --git a/src/nvim/types.h b/src/nvim/types.h
index 87560a43da..91420c087f 100644
--- a/src/nvim/types.h
+++ b/src/nvim/types.h
@@ -16,7 +16,7 @@ typedef uint32_t u8char_T;
// Opaque handle used by API clients to refer to various objects in vim
typedef int handle_T;
-// Opaque handle to a lua value. Must be free with `executor_free_luaref` when
+// Opaque handle to a lua value. Must be free with `api_free_luaref` when
// not needed anymore! LUA_NOREF represents missing reference, i e to indicate
// absent callback etc.
typedef int LuaRef;
diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c
index 9a1988739c..25f45b8fe6 100644
--- a/src/nvim/ui_bridge.c
+++ b/src/nvim/ui_bridge.c
@@ -61,6 +61,7 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler)
rv->bridge.suspend = ui_bridge_suspend;
rv->bridge.set_title = ui_bridge_set_title;
rv->bridge.set_icon = ui_bridge_set_icon;
+ rv->bridge.screenshot = ui_bridge_screenshot;
rv->bridge.option_set = ui_bridge_option_set;
rv->bridge.raw_line = ui_bridge_raw_line;
rv->bridge.inspect = ui_bridge_inspect;
diff --git a/src/nvim/version.c b/src/nvim/version.c
index bf80de6026..32cb0091a3 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -464,7 +464,7 @@ static const int included_patches[] = {
1457,
1456,
// 1455,
- // 1454,
+ 1454,
1453,
1452,
1451,
@@ -2119,13 +2119,13 @@ void list_in_columns(char_u **items, int size, int current)
void list_lua_version(void)
{
- typval_T luaver_tv;
- typval_T arg = { .v_type = VAR_UNKNOWN }; // No args.
- char *luaver_expr = "((jit and jit.version) and jit.version or _VERSION)";
- executor_eval_lua(cstr_as_string(luaver_expr), &arg, &luaver_tv);
- assert(luaver_tv.v_type == VAR_STRING);
- MSG(luaver_tv.vval.v_string);
- xfree(luaver_tv.vval.v_string);
+ char *code = "return ((jit and jit.version) and jit.version or _VERSION)";
+ Error err = ERROR_INIT;
+ Object ret = nlua_exec(cstr_as_string(code), (Array)ARRAY_DICT_INIT, &err);
+ assert(!ERROR_SET(&err));
+ assert(ret.type == kObjectTypeString);
+ MSG(ret.data.string.data);
+ api_free_object(ret);
}
void list_version(void)
diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c
index b77b80a5f3..44b6ab5f5a 100644
--- a/src/nvim/viml/parser/expressions.c
+++ b/src/nvim/viml/parser/expressions.c
@@ -1431,7 +1431,7 @@ static inline void east_set_error(const ParserState *const pstate,
const ParserLine pline = pstate->reader.lines.items[start.line];
ret_ast_err->msg = msg;
ret_ast_err->arg_len = (int)(pline.size - start.col);
- ret_ast_err->arg = pline.data + start.col;
+ ret_ast_err->arg = pline.data ? pline.data + start.col : NULL;
}
/// Set error from the given token and given message
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 0fff93d984..cec0dfd67f 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -6986,7 +6986,7 @@ void win_findbuf(typval_T *argvars, list_T *list)
int bufnr = tv_get_number(&argvars[0]);
FOR_ALL_TAB_WINDOWS(tp, wp) {
- if (wp->w_buffer->b_fnum == bufnr) {
+ if (!wp->w_closing && wp->w_buffer->b_fnum == bufnr) {
tv_list_append_number(list, wp->handle);
}
}
diff --git a/src/tree_sitter/alloc.h b/src/tree_sitter/alloc.h
index d3c6b5eca8..32c90f23c8 100644
--- a/src/tree_sitter/alloc.h
+++ b/src/tree_sitter/alloc.h
@@ -58,7 +58,7 @@ static inline bool ts_toggle_allocation_recording(bool value) {
static inline void *ts_malloc(size_t size) {
void *result = malloc(size);
if (size > 0 && !result) {
- fprintf(stderr, "tree-sitter failed to allocate %lu bytes", size);
+ fprintf(stderr, "tree-sitter failed to allocate %zu bytes", size);
exit(1);
}
return result;
@@ -67,7 +67,7 @@ static inline void *ts_malloc(size_t size) {
static inline void *ts_calloc(size_t count, size_t size) {
void *result = calloc(count, size);
if (count > 0 && !result) {
- fprintf(stderr, "tree-sitter failed to allocate %lu bytes", count * size);
+ fprintf(stderr, "tree-sitter failed to allocate %zu bytes", count * size);
exit(1);
}
return result;
@@ -76,7 +76,7 @@ static inline void *ts_calloc(size_t count, size_t size) {
static inline void *ts_realloc(void *buffer, size_t size) {
void *result = realloc(buffer, size);
if (size > 0 && !result) {
- fprintf(stderr, "tree-sitter failed to reallocate %lu bytes", size);
+ fprintf(stderr, "tree-sitter failed to reallocate %zu bytes", size);
exit(1);
}
return result;
diff --git a/src/tree_sitter/lexer.c b/src/tree_sitter/lexer.c
index 3f8a4c0ae8..a3c29544d3 100644
--- a/src/tree_sitter/lexer.c
+++ b/src/tree_sitter/lexer.c
@@ -73,7 +73,6 @@ static void ts_lexer__get_chunk(Lexer *self) {
// code that spans the current position.
static void ts_lexer__get_lookahead(Lexer *self) {
uint32_t position_in_chunk = self->current_position.bytes - self->chunk_start;
- const uint8_t *chunk = (const uint8_t *)self->chunk + position_in_chunk;
uint32_t size = self->chunk_size - position_in_chunk;
if (size == 0) {
@@ -82,6 +81,7 @@ static void ts_lexer__get_lookahead(Lexer *self) {
return;
}
+ const uint8_t *chunk = (const uint8_t *)self->chunk + position_in_chunk;
UnicodeDecodeFunction decode = self->input.encoding == TSInputEncodingUTF8
? ts_decode_utf8
: ts_decode_utf16;
diff --git a/src/tree_sitter/parser.c b/src/tree_sitter/parser.c
index dd222cd3c4..79cad797a0 100644
--- a/src/tree_sitter/parser.c
+++ b/src/tree_sitter/parser.c
@@ -292,6 +292,7 @@ static bool ts_parser__better_version_exists(
return true;
case ErrorComparisonPreferRight:
if (ts_stack_can_merge(self->stack, i, version)) return true;
+ break;
default:
break;
}
@@ -355,10 +356,14 @@ static Subtree ts_parser__lex(
StackVersion version,
TSStateId parse_state
) {
+ TSLexMode lex_mode = self->language->lex_modes[parse_state];
+ if (lex_mode.lex_state == (uint16_t)-1) {
+ LOG("no_lookahead_after_non_terminal_extra");
+ return NULL_SUBTREE;
+ }
+
Length start_position = ts_stack_position(self->stack, version);
Subtree external_token = ts_stack_last_external_token(self->stack, version);
- TSLexMode lex_mode = self->language->lex_modes[parse_state];
- if (lex_mode.lex_state == (uint16_t)-1) return NULL_SUBTREE;
const bool *valid_external_tokens = ts_language_enabled_external_tokens(
self->language,
lex_mode.external_lex_state
@@ -761,20 +766,26 @@ static StackVersion ts_parser__reduce(
int dynamic_precedence,
uint16_t production_id,
bool is_fragile,
- bool is_extra
+ bool end_of_non_terminal_extra
) {
uint32_t initial_version_count = ts_stack_version_count(self->stack);
- uint32_t removed_version_count = 0;
- StackSliceArray pop = ts_stack_pop_count(self->stack, version, count);
+ // Pop the given number of nodes from the given version of the parse stack.
+ // If stack versions have previously merged, then there may be more than one
+ // path back through the stack. For each path, create a new parent node to
+ // contain the popped children, and push it onto the stack in place of the
+ // children.
+ StackSliceArray pop = ts_stack_pop_count(self->stack, version, count);
+ uint32_t removed_version_count = 0;
for (uint32_t i = 0; i < pop.size; i++) {
StackSlice slice = pop.contents[i];
StackVersion slice_version = slice.version - removed_version_count;
- // Error recovery can sometimes cause lots of stack versions to merge,
- // such that a single pop operation can produce a lots of slices.
- // Avoid creating too many stack versions in that situation.
- if (i > 0 && slice_version > MAX_VERSION_COUNT + MAX_VERSION_COUNT_OVERFLOW) {
+ // This is where new versions are added to the parse stack. The versions
+ // will all be sorted and truncated at the end of the outer parsing loop.
+ // Allow the maximum version count to be temporarily exceeded, but only
+ // by a limited threshold.
+ if (slice_version > MAX_VERSION_COUNT + MAX_VERSION_COUNT_OVERFLOW) {
ts_stack_remove_version(self->stack, slice_version);
ts_subtree_array_delete(&self->tree_pool, &slice.subtrees);
removed_version_count++;
@@ -826,7 +837,9 @@ static StackVersion ts_parser__reduce(
TSStateId state = ts_stack_state(self->stack, slice_version);
TSStateId next_state = ts_language_next_state(self->language, state, symbol);
- if (is_extra) parent.ptr->extra = true;
+ if (end_of_non_terminal_extra && next_state == state) {
+ parent.ptr->extra = true;
+ }
if (is_fragile || pop.size > 1 || initial_version_count > 1) {
parent.ptr->fragile_left = true;
parent.ptr->fragile_right = true;
@@ -963,6 +976,7 @@ static bool ts_parser__do_all_potential_reductions(
.dynamic_precedence = action.params.reduce.dynamic_precedence,
.production_id = action.params.reduce.production_id,
});
+ break;
default:
break;
}
@@ -1339,23 +1353,26 @@ static bool ts_parser__advance(
);
}
- // Otherwise, re-run the lexer.
- if (!lookahead.ptr) {
- lookahead = ts_parser__lex(self, version, state);
- if (lookahead.ptr) {
- ts_parser__set_cached_token(self, position, last_external_token, lookahead);
- ts_language_table_entry(self->language, state, ts_subtree_symbol(lookahead), &table_entry);
- }
+ bool needs_lex = !lookahead.ptr;
+ for (;;) {
+ // Otherwise, re-run the lexer.
+ if (needs_lex) {
+ needs_lex = false;
+ lookahead = ts_parser__lex(self, version, state);
+
+ if (lookahead.ptr) {
+ ts_parser__set_cached_token(self, position, last_external_token, lookahead);
+ ts_language_table_entry(self->language, state, ts_subtree_symbol(lookahead), &table_entry);
+ }
- // When parsing a non-terminal extra, a null lookahead indicates the
- // end of the rule. The reduction is stored in the EOF table entry.
- // After the reduction, the lexer needs to be run again.
- else {
- ts_language_table_entry(self->language, state, ts_builtin_sym_end, &table_entry);
+ // When parsing a non-terminal extra, a null lookahead indicates the
+ // end of the rule. The reduction is stored in the EOF table entry.
+ // After the reduction, the lexer needs to be run again.
+ else {
+ ts_language_table_entry(self->language, state, ts_builtin_sym_end, &table_entry);
+ }
}
- }
- for (;;) {
// If a cancellation flag or a timeout was provided, then check every
// time a fixed number of parse actions has been processed.
if (++self->operation_count == OP_COUNT_PER_TIMEOUT_CHECK) {
@@ -1407,12 +1424,12 @@ static bool ts_parser__advance(
case TSParseActionTypeReduce: {
bool is_fragile = table_entry.action_count > 1;
- bool is_extra = lookahead.ptr == NULL;
+ bool end_of_non_terminal_extra = lookahead.ptr == NULL;
LOG("reduce sym:%s, child_count:%u", SYM_NAME(action.params.reduce.symbol), action.params.reduce.child_count);
StackVersion reduction_version = ts_parser__reduce(
self, version, action.params.reduce.symbol, action.params.reduce.child_count,
action.params.reduce.dynamic_precedence, action.params.reduce.production_id,
- is_fragile, is_extra
+ is_fragile, end_of_non_terminal_extra
);
if (reduction_version != STACK_VERSION_NONE) {
last_reduction_version = reduction_version;
@@ -1452,8 +1469,10 @@ static bool ts_parser__advance(
// (and completing the non-terminal extra rule) run the lexer again based
// on the current parse state.
if (!lookahead.ptr) {
- lookahead = ts_parser__lex(self, version, state);
+ needs_lex = true;
+ continue;
}
+
ts_language_table_entry(
self->language,
state,
@@ -1463,6 +1482,11 @@ static bool ts_parser__advance(
continue;
}
+ if (!lookahead.ptr) {
+ ts_stack_pause(self->stack, version, ts_builtin_sym_end);
+ return true;
+ }
+
// If there were no parse actions for the current lookahead token, then
// it is not valid in this state. If the current lookahead token is a
// keyword, then switch to treating it as the normal word token if that
@@ -1500,6 +1524,9 @@ static bool ts_parser__advance(
// push each of its children. Then try again to process the current
// lookahead.
if (ts_parser__breakdown_top_of_stack(self, version)) {
+ state = ts_stack_state(self->stack, version);
+ ts_subtree_release(&self->tree_pool, lookahead);
+ needs_lex = true;
continue;
}
diff --git a/src/tree_sitter/query.c b/src/tree_sitter/query.c
index 59902dee3b..b887b74ff6 100644
--- a/src/tree_sitter/query.c
+++ b/src/tree_sitter/query.c
@@ -11,7 +11,6 @@
// #define LOG(...) fprintf(stderr, __VA_ARGS__)
#define LOG(...)
-#define MAX_STATE_COUNT 256
#define MAX_CAPTURE_LIST_COUNT 32
#define MAX_STEP_CAPTURE_COUNT 3
@@ -49,7 +48,6 @@ typedef struct {
uint16_t alternative_index;
uint16_t depth;
bool contains_captures: 1;
- bool is_pattern_start: 1;
bool is_immediate: 1;
bool is_last_child: 1;
bool is_pass_through: 1;
@@ -119,9 +117,10 @@ typedef struct {
uint16_t step_index;
uint16_t pattern_index;
uint16_t capture_list_id;
- uint16_t consumed_capture_count: 14;
+ uint16_t consumed_capture_count: 12;
bool seeking_immediate_match: 1;
bool has_in_progress_alternatives: 1;
+ bool dead: 1;
} QueryState;
typedef Array(TSQueryCapture) CaptureList;
@@ -172,6 +171,7 @@ struct TSQueryCursor {
TSPoint start_point;
TSPoint end_point;
bool ascending;
+ bool halted;
};
static const TSQueryError PARENT_DONE = -1;
@@ -448,7 +448,6 @@ static QueryStep query_step__new(
.alternative_index = NONE,
.contains_captures = false,
.is_last_child = false,
- .is_pattern_start = false,
.is_pass_through = false,
.is_dead_end = false,
.is_immediate = is_immediate,
@@ -546,6 +545,23 @@ static inline void ts_query__pattern_map_insert(
) {
uint32_t index;
ts_query__pattern_map_search(self, symbol, &index);
+
+ // Ensure that the entries are sorted not only by symbol, but also
+ // by pattern_index. This way, states for earlier patterns will be
+ // initiated first, which allows the ordering of the states array
+ // to be maintained more efficiently.
+ while (index < self->pattern_map.size) {
+ PatternEntry *entry = &self->pattern_map.contents[index];
+ if (
+ self->steps.contents[entry->step_index].symbol == symbol &&
+ entry->pattern_index < pattern_index
+ ) {
+ index++;
+ } else {
+ break;
+ }
+ }
+
array_insert(&self->pattern_map, index, ((PatternEntry) {
.step_index = start_step_index,
.pattern_index = pattern_index,
@@ -715,7 +731,7 @@ static TSQueryError ts_query__parse_pattern(
uint32_t *capture_count,
bool is_immediate
) {
- uint32_t starting_step_index = self->steps.size;
+ const uint32_t starting_step_index = self->steps.size;
if (stream->next == 0) return TSQueryErrorSyntax;
@@ -804,8 +820,8 @@ static TSQueryError ts_query__parse_pattern(
}
}
- // A pound character indicates the start of a predicate.
- else if (stream->next == '#') {
+ // A dot/pound character indicates the start of a predicate.
+ else if (stream->next == '.' || stream->next == '#') {
stream_advance(stream);
return ts_query__parse_predicate(self, stream);
}
@@ -951,7 +967,6 @@ static TSQueryError ts_query__parse_pattern(
stream_skip_whitespace(stream);
// Parse the pattern
- uint32_t step_index = self->steps.size;
TSQueryError e = ts_query__parse_pattern(
self,
stream,
@@ -972,7 +987,22 @@ static TSQueryError ts_query__parse_pattern(
stream->input = field_name;
return TSQueryErrorField;
}
- self->steps.contents[step_index].field = field_id;
+
+ uint32_t step_index = starting_step_index;
+ QueryStep *step = &self->steps.contents[step_index];
+ for (;;) {
+ step->field = field_id;
+ if (
+ step->alternative_index != NONE &&
+ step->alternative_index > step_index &&
+ step->alternative_index < self->steps.size
+ ) {
+ step_index = step->alternative_index;
+ step = &self->steps.contents[step_index];
+ } else {
+ break;
+ }
+ }
}
else {
@@ -1041,15 +1071,16 @@ static TSQueryError ts_query__parse_pattern(
length
);
+ uint32_t step_index = starting_step_index;
for (;;) {
query_step__add_capture(step, capture_id);
if (
step->alternative_index != NONE &&
- step->alternative_index > starting_step_index &&
+ step->alternative_index > step_index &&
step->alternative_index < self->steps.size
) {
- starting_step_index = step->alternative_index;
- step = &self->steps.contents[starting_step_index];
+ step_index = step->alternative_index;
+ step = &self->steps.contents[step_index];
} else {
break;
}
@@ -1152,7 +1183,6 @@ TSQuery *ts_query_new(
// Maintain a map that can look up patterns for a given root symbol.
for (;;) {
QueryStep *step = &self->steps.contents[start_step_index];
- step->is_pattern_start = true;
ts_query__pattern_map_insert(self, step->symbol, start_step_index, pattern_index);
if (step->symbol == WILDCARD_SYMBOL) {
self->wildcard_root_pattern_count++;
@@ -1162,6 +1192,7 @@ TSQuery *ts_query_new(
// then add multiple entries to the pattern map.
if (step->alternative_index != NONE) {
start_step_index = step->alternative_index;
+ step->alternative_index = NONE;
} else {
break;
}
@@ -1221,6 +1252,9 @@ const TSQueryPredicateStep *ts_query_predicates_for_pattern(
) {
Slice slice = self->predicates_by_pattern.contents[pattern_index];
*step_count = slice.length;
+ if (self->predicate_steps.contents == NULL) {
+ return NULL;
+ }
return &self->predicate_steps.contents[slice.offset];
}
@@ -1271,6 +1305,7 @@ TSQueryCursor *ts_query_cursor_new(void) {
TSQueryCursor *self = ts_malloc(sizeof(TSQueryCursor));
*self = (TSQueryCursor) {
.ascending = false,
+ .halted = false,
.states = array_new(),
.finished_states = array_new(),
.capture_list_pool = capture_list_pool_new(),
@@ -1279,8 +1314,8 @@ TSQueryCursor *ts_query_cursor_new(void) {
.start_point = {0, 0},
.end_point = POINT_MAX,
};
- array_reserve(&self->states, MAX_STATE_COUNT);
- array_reserve(&self->finished_states, MAX_CAPTURE_LIST_COUNT);
+ array_reserve(&self->states, 8);
+ array_reserve(&self->finished_states, 8);
return self;
}
@@ -1304,6 +1339,7 @@ void ts_query_cursor_exec(
self->next_state_id = 0;
self->depth = 0;
self->ascending = false;
+ self->halted = false;
self->query = query;
}
@@ -1347,6 +1383,7 @@ static bool ts_query_cursor__first_in_progress_capture(
*pattern_index = UINT32_MAX;
for (unsigned i = 0; i < self->states.size; i++) {
const QueryState *state = &self->states.contents[i];
+ if (state->dead) continue;
const CaptureList *captures = capture_list_pool_get(
&self->capture_list_pool,
state->capture_list_id
@@ -1441,65 +1478,138 @@ void ts_query_cursor__compare_captures(
}
}
-static bool ts_query_cursor__add_state(
+static void ts_query_cursor__add_state(
TSQueryCursor *self,
const PatternEntry *pattern
) {
- if (self->states.size >= MAX_STATE_COUNT) {
- LOG(" too many states");
- return false;
+ QueryStep *step = &self->query->steps.contents[pattern->step_index];
+ uint32_t start_depth = self->depth - step->depth;
+
+ // Keep the states array in ascending order of start_depth and pattern_index,
+ // so that it can be processed more efficiently elsewhere. Usually, there is
+ // no work to do here because of two facts:
+ // * States with lower start_depth are naturally added first due to the
+ // order in which nodes are visited.
+ // * Earlier patterns are naturally added first because of the ordering of the
+ // pattern_map data structure that's used to initiate matches.
+ //
+ // This loop is only needed in cases where two conditions hold:
+ // * A pattern consists of more than one sibling node, so that its states
+ // remain in progress after exiting the node that started the match.
+ // * The first node in the pattern matches against multiple nodes at the
+ // same depth.
+ //
+ // An example of this is the pattern '((comment)* (function))'. If multiple
+ // `comment` nodes appear in a row, then we may initiate a new state for this
+ // pattern while another state for the same pattern is already in progress.
+ // If there are multiple patterns like this in a query, then this loop will
+ // need to execute in order to keep the states ordered by pattern_index.
+ uint32_t index = self->states.size;
+ while (index > 0) {
+ QueryState *prev_state = &self->states.contents[index - 1];
+ if (prev_state->start_depth < start_depth) break;
+ if (prev_state->start_depth == start_depth) {
+ if (prev_state->pattern_index < pattern->pattern_index) break;
+ if (prev_state->pattern_index == pattern->pattern_index) {
+ // Avoid unnecessarily inserting an unnecessary duplicate state,
+ // which would be immediately pruned by the longest-match criteria.
+ if (prev_state->step_index == pattern->step_index) return;
+ }
+ }
+ index--;
}
+
LOG(
" start state. pattern:%u, step:%u\n",
pattern->pattern_index,
pattern->step_index
);
- QueryStep *step = &self->query->steps.contents[pattern->step_index];
- array_push(&self->states, ((QueryState) {
+ array_insert(&self->states, index, ((QueryState) {
.capture_list_id = NONE,
.step_index = pattern->step_index,
.pattern_index = pattern->pattern_index,
- .start_depth = self->depth - step->depth,
+ .start_depth = start_depth,
.consumed_capture_count = 0,
- .seeking_immediate_match = false,
+ .seeking_immediate_match = true,
+ .has_in_progress_alternatives = false,
+ .dead = false,
}));
- return true;
}
-// Duplicate the given state and insert the newly-created state immediately after
-// the given state in the `states` array.
-static QueryState *ts_query__cursor_copy_state(
+// Acquire a capture list for this state. If there are no capture lists left in the
+// pool, this will steal the capture list from another existing state, and mark that
+// other state as 'dead'.
+static CaptureList *ts_query_cursor__prepare_to_capture(
TSQueryCursor *self,
- const QueryState *state
+ QueryState *state,
+ unsigned state_index_to_preserve
) {
- if (self->states.size >= MAX_STATE_COUNT) {
- LOG(" too many states");
- return NULL;
+ if (state->capture_list_id == NONE) {
+ state->capture_list_id = capture_list_pool_acquire(&self->capture_list_pool);
+
+ // If there are no capture lists left in the pool, then terminate whichever
+ // state has captured the earliest node in the document, and steal its
+ // capture list.
+ if (state->capture_list_id == NONE) {
+ uint32_t state_index, byte_offset, pattern_index;
+ if (
+ ts_query_cursor__first_in_progress_capture(
+ self,
+ &state_index,
+ &byte_offset,
+ &pattern_index
+ ) &&
+ state_index != state_index_to_preserve
+ ) {
+ LOG(
+ " abandon state. index:%u, pattern:%u, offset:%u.\n",
+ state_index, pattern_index, byte_offset
+ );
+ QueryState *other_state = &self->states.contents[state_index];
+ state->capture_list_id = other_state->capture_list_id;
+ other_state->capture_list_id = NONE;
+ other_state->dead = true;
+ CaptureList *list = capture_list_pool_get_mut(
+ &self->capture_list_pool,
+ state->capture_list_id
+ );
+ array_clear(list);
+ return list;
+ } else {
+ LOG(" ran out of capture lists");
+ return NULL;
+ }
+ }
}
+ return capture_list_pool_get_mut(&self->capture_list_pool, state->capture_list_id);
+}
- // If the state has captures, copy its capture list.
+// Duplicate the given state and insert the newly-created state immediately after
+// the given state in the `states` array. Ensures that the given state reference is
+// still valid, even if the states array is reallocated.
+static QueryState *ts_query_cursor__copy_state(
+ TSQueryCursor *self,
+ QueryState **state_ref
+) {
+ const QueryState *state = *state_ref;
+ uint32_t state_index = state - self->states.contents;
QueryState copy = *state;
- copy.capture_list_id = state->capture_list_id;
+ copy.capture_list_id = NONE;
+
+ // If the state has captures, copy its capture list.
if (state->capture_list_id != NONE) {
- copy.capture_list_id = capture_list_pool_acquire(&self->capture_list_pool);
- if (copy.capture_list_id == NONE) {
- LOG(" too many capture lists");
- return NULL;
- }
+ CaptureList *new_captures = ts_query_cursor__prepare_to_capture(self, &copy, state_index);
+ if (!new_captures) return NULL;
const CaptureList *old_captures = capture_list_pool_get(
&self->capture_list_pool,
state->capture_list_id
);
- CaptureList *new_captures = capture_list_pool_get_mut(
- &self->capture_list_pool,
- copy.capture_list_id
- );
array_push_all(new_captures, old_captures);
}
- uint32_t index = (state - self->states.contents) + 1;
- array_insert(&self->states, index, copy);
- return &self->states.contents[index];
+ array_insert(&self->states, state_index + 1, copy);
+ *state_ref = &self->states.contents[state_index];
+ return &self->states.contents[state_index + 1];
}
// Walk the tree, processing patterns until at least one pattern finishes,
@@ -1507,18 +1617,30 @@ static QueryState *ts_query__cursor_copy_state(
// `finished_states` array. Multiple patterns can finish on the same node. If
// there are no more matches, return `false`.
static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
- do {
+ bool did_match = false;
+ for (;;) {
+ if (self->halted) {
+ while (self->states.size > 0) {
+ QueryState state = array_pop(&self->states);
+ capture_list_pool_release(
+ &self->capture_list_pool,
+ state.capture_list_id
+ );
+ }
+ }
+
+ if (did_match || self->halted) return did_match;
+
if (self->ascending) {
LOG("leave node. type:%s\n", ts_node_type(ts_tree_cursor_current_node(&self->cursor)));
// Leave this node by stepping to its next sibling or to its parent.
- bool did_move = true;
if (ts_tree_cursor_goto_next_sibling(&self->cursor)) {
self->ascending = false;
} else if (ts_tree_cursor_goto_parent(&self->cursor)) {
self->depth--;
} else {
- did_move = false;
+ self->halted = true;
}
// After leaving a node, remove any states that cannot make further progress.
@@ -1530,10 +1652,11 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
// If a state completed its pattern inside of this node, but was deferred from finishing
// in order to search for longer matches, mark it as finished.
if (step->depth == PATTERN_DONE_MARKER) {
- if (state->start_depth > self->depth || !did_move) {
+ if (state->start_depth > self->depth || self->halted) {
LOG(" finish pattern %u\n", state->pattern_index);
state->id = self->next_state_id++;
array_push(&self->finished_states, *state);
+ did_match = true;
deleted_count++;
continue;
}
@@ -1560,10 +1683,6 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
}
}
self->states.size -= deleted_count;
-
- if (!did_move) {
- return self->finished_states.size > 0;
- }
} else {
// If this node is before the selected range, then avoid descending into it.
TSNode node = ts_tree_cursor_current_node(&self->cursor);
@@ -1581,7 +1700,10 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
if (
self->end_byte <= ts_node_start_byte(node) ||
point_lte(self->end_point, ts_node_start_point(node))
- ) return false;
+ ) {
+ self->halted = true;
+ continue;
+ }
// Get the properties of the current node.
TSSymbol symbol = ts_node_symbol(node);
@@ -1613,7 +1735,7 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
// If this node matches the first step of the pattern, then add a new
// state at the start of this pattern.
if (step->field && field_id != step->field) continue;
- if (!ts_query_cursor__add_state(self, pattern)) break;
+ ts_query_cursor__add_state(self, pattern);
}
// Add new states for any patterns whose root node matches this node.
@@ -1625,7 +1747,7 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
// If this node matches the first step of the pattern, then add a new
// state at the start of this pattern.
if (step->field && field_id != step->field) continue;
- if (!ts_query_cursor__add_state(self, pattern)) break;
+ ts_query_cursor__add_state(self, pattern);
// Advance to the next pattern whose root node matches this node.
i++;
@@ -1693,12 +1815,8 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
// parent, then this query state cannot simply be updated in place. It must be
// split into two states: one that matches this node, and one which skips over
// this node, to preserve the possibility of matching later siblings.
- if (
- later_sibling_can_match &&
- !step->is_pattern_start &&
- step->contains_captures
- ) {
- if (ts_query__cursor_copy_state(self, state)) {
+ if (later_sibling_can_match && step->contains_captures) {
+ if (ts_query_cursor__copy_state(self, &state)) {
LOG(
" split state for capture. pattern:%u, step:%u\n",
state->pattern_index,
@@ -1709,45 +1827,14 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
}
// If the current node is captured in this pattern, add it to the capture list.
- // For the first capture in a pattern, lazily acquire a capture list.
if (step->capture_ids[0] != NONE) {
- if (state->capture_list_id == NONE) {
- state->capture_list_id = capture_list_pool_acquire(&self->capture_list_pool);
-
- // If there are no capture lists left in the pool, then terminate whichever
- // state has captured the earliest node in the document, and steal its
- // capture list.
- if (state->capture_list_id == NONE) {
- uint32_t state_index, byte_offset, pattern_index;
- if (ts_query_cursor__first_in_progress_capture(
- self,
- &state_index,
- &byte_offset,
- &pattern_index
- )) {
- LOG(
- " abandon state. index:%u, pattern:%u, offset:%u.\n",
- state_index, pattern_index, byte_offset
- );
- state->capture_list_id = self->states.contents[state_index].capture_list_id;
- array_erase(&self->states, state_index);
- if (state_index < i) {
- i--;
- state--;
- }
- } else {
- LOG(" too many finished states.\n");
- array_erase(&self->states, i);
- i--;
- continue;
- }
- }
+ CaptureList *capture_list = ts_query_cursor__prepare_to_capture(self, state, UINT32_MAX);
+ if (!capture_list) {
+ array_erase(&self->states, i);
+ i--;
+ continue;
}
- CaptureList *capture_list = capture_list_pool_get_mut(
- &self->capture_list_pool,
- state->capture_list_id
- );
for (unsigned j = 0; j < MAX_STEP_CAPTURE_COUNT; j++) {
uint16_t capture_id = step->capture_ids[j];
if (step->capture_ids[j] == NONE) break;
@@ -1770,10 +1857,9 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
state->step_index
);
- // If this state's next step has an 'alternative' step (the step is either optional,
- // or is the end of a repetition), then copy the state in order to pursue both
- // alternatives. The alternative step itself may have an alternative, so this is
- // an interative process.
+ // If this state's next step has an alternative step, then copy the state in order
+ // to pursue both alternatives. The alternative step itself may have an alternative,
+ // so this is an interative process.
unsigned end_index = i + 1;
for (unsigned j = i; j < end_index; j++) {
QueryState *state = &self->states.contents[j];
@@ -1785,25 +1871,27 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
continue;
}
- QueryState *copy = ts_query__cursor_copy_state(self, state);
if (next_step->is_pass_through) {
state->step_index++;
j--;
}
+
+ QueryState *copy = ts_query_cursor__copy_state(self, &state);
if (copy) {
- copy_count++;
+ LOG(
+ " split state for branch. pattern:%u, from_step:%u, to_step:%u, immediate:%d, capture_count: %u\n",
+ copy->pattern_index,
+ copy->step_index,
+ next_step->alternative_index,
+ next_step->alternative_is_immediate,
+ capture_list_pool_get(&self->capture_list_pool, copy->capture_list_id)->size
+ );
end_index++;
+ copy_count++;
copy->step_index = next_step->alternative_index;
if (next_step->alternative_is_immediate) {
copy->seeking_immediate_match = true;
}
- LOG(
- " split state for branch. pattern:%u, step:%u, step:%u, immediate:%d\n",
- copy->pattern_index,
- state->step_index,
- copy->step_index,
- copy->seeking_immediate_match
- );
}
}
}
@@ -1811,59 +1899,77 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
for (unsigned i = 0; i < self->states.size; i++) {
QueryState *state = &self->states.contents[i];
- bool did_remove = false;
+ if (state->dead) {
+ array_erase(&self->states, i);
+ i--;
+ continue;
+ }
// Enfore the longest-match criteria. When a query pattern contains optional or
- // repeated nodes, this is necesssary to avoid multiple redundant states, where
+ // repeated nodes, this is necessary to avoid multiple redundant states, where
// one state has a strict subset of another state's captures.
+ bool did_remove = false;
for (unsigned j = i + 1; j < self->states.size; j++) {
QueryState *other_state = &self->states.contents[j];
+
+ // Query states are kept in ascending order of start_depth and pattern_index.
+ // Since the longest-match criteria is only used for deduping matches of the same
+ // pattern and root node, we only need to perform pairwise comparisons within a
+ // small slice of the states array.
if (
- state->pattern_index == other_state->pattern_index &&
- state->start_depth == other_state->start_depth
- ) {
- bool left_contains_right, right_contains_left;
- ts_query_cursor__compare_captures(
- self,
- state,
- other_state,
- &left_contains_right,
- &right_contains_left
- );
- if (left_contains_right) {
- if (state->step_index == other_state->step_index) {
- LOG(
- " drop shorter state. pattern: %u, step_index: %u\n",
- state->pattern_index,
- state->step_index
- );
- capture_list_pool_release(&self->capture_list_pool, other_state->capture_list_id);
- array_erase(&self->states, j);
- j--;
- continue;
- }
- other_state->has_in_progress_alternatives = true;
+ other_state->start_depth != state->start_depth ||
+ other_state->pattern_index != state->pattern_index
+ ) break;
+
+ bool left_contains_right, right_contains_left;
+ ts_query_cursor__compare_captures(
+ self,
+ state,
+ other_state,
+ &left_contains_right,
+ &right_contains_left
+ );
+ if (left_contains_right) {
+ if (state->step_index == other_state->step_index) {
+ LOG(
+ " drop shorter state. pattern: %u, step_index: %u\n",
+ state->pattern_index,
+ state->step_index
+ );
+ capture_list_pool_release(&self->capture_list_pool, other_state->capture_list_id);
+ array_erase(&self->states, j);
+ j--;
+ continue;
}
- if (right_contains_left) {
- if (state->step_index == other_state->step_index) {
- LOG(
- " drop shorter state. pattern: %u, step_index: %u\n",
- state->pattern_index,
- state->step_index
- );
- capture_list_pool_release(&self->capture_list_pool, state->capture_list_id);
- array_erase(&self->states, i);
- did_remove = true;
- break;
- }
- state->has_in_progress_alternatives = true;
+ other_state->has_in_progress_alternatives = true;
+ }
+ if (right_contains_left) {
+ if (state->step_index == other_state->step_index) {
+ LOG(
+ " drop shorter state. pattern: %u, step_index: %u\n",
+ state->pattern_index,
+ state->step_index
+ );
+ capture_list_pool_release(&self->capture_list_pool, state->capture_list_id);
+ array_erase(&self->states, i);
+ i--;
+ did_remove = true;
+ break;
}
+ state->has_in_progress_alternatives = true;
}
}
// If there the state is at the end of its pattern, remove it from the list
// of in-progress states and add it to the list of finished states.
if (!did_remove) {
+ LOG(
+ " keep state. pattern: %u, start_depth: %u, step_index: %u, capture_count: %u\n",
+ state->pattern_index,
+ state->start_depth,
+ state->step_index,
+ capture_list_pool_get(&self->capture_list_pool, state->capture_list_id)->size
+ );
QueryStep *next_step = &self->query->steps.contents[state->step_index];
if (next_step->depth == PATTERN_DONE_MARKER) {
if (state->has_in_progress_alternatives) {
@@ -1873,6 +1979,7 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
state->id = self->next_state_id++;
array_push(&self->finished_states, *state);
array_erase(&self->states, state - self->states.contents);
+ did_match = true;
i--;
}
}
@@ -1886,9 +1993,7 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
self->ascending = true;
}
}
- } while (self->finished_states.size == 0);
-
- return true;
+ }
}
bool ts_query_cursor_next_match(
@@ -2028,7 +2133,10 @@ bool ts_query_cursor_next_capture(
// If there are no finished matches that are ready to be returned, then
// continue finding more matches.
- if (!ts_query_cursor__advance(self)) return false;
+ if (
+ !ts_query_cursor__advance(self) &&
+ self->finished_states.size == 0
+ ) return false;
}
}
diff --git a/src/tree_sitter/stack.c b/src/tree_sitter/stack.c
index 6ceee2577f..6a8d897c37 100644
--- a/src/tree_sitter/stack.c
+++ b/src/tree_sitter/stack.c
@@ -571,7 +571,12 @@ void ts_stack_record_summary(Stack *self, StackVersion version, unsigned max_dep
};
array_init(session.summary);
stack__iter(self, version, summarize_stack_callback, &session, -1);
- self->heads.contents[version].summary = session.summary;
+ StackHead *head = &self->heads.contents[version];
+ if (head->summary) {
+ array_delete(head->summary);
+ ts_free(head->summary);
+ }
+ head->summary = session.summary;
}
StackSummary *ts_stack_get_summary(Stack *self, StackVersion version) {
@@ -743,6 +748,10 @@ bool ts_stack_print_dot_graph(Stack *self, const TSLanguage *language, FILE *f)
ts_stack_error_cost(self, i)
);
+ if (head->summary) {
+ fprintf(f, "\nsummary_size: %u", head->summary->size);
+ }
+
if (head->last_external_token.ptr) {
const ExternalScannerState *state = &head->last_external_token.ptr->external_scanner_state;
const char *data = ts_external_scanner_state_data(state);
diff --git a/src/tree_sitter/treesitter_commit_hash.txt b/src/tree_sitter/treesitter_commit_hash.txt
index bd7fcfbe76..322cdd24a6 100644
--- a/src/tree_sitter/treesitter_commit_hash.txt
+++ b/src/tree_sitter/treesitter_commit_hash.txt
@@ -1 +1 @@
-81d533d2d1b580fdb507accabc91ceddffb5b6f0
+87df53a99b51bce0d1e901cd6838f24e1c7a4073
diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua
index 9ea35e50a2..a2a188d036 100644
--- a/test/functional/api/extmark_spec.lua
+++ b/test/functional/api/extmark_spec.lua
@@ -17,22 +17,14 @@ local function expect(contents)
return eq(contents, helpers.curbuf_contents())
end
-local function check_undo_redo(ns, mark, sr, sc, er, ec) --s = start, e = end
- local rv = curbufmeths.get_extmark_by_id(ns, mark)
- eq({er, ec}, rv)
- feed("u")
- rv = curbufmeths.get_extmark_by_id(ns, mark)
- eq({sr, sc}, rv)
- feed("<c-r>")
- rv = curbufmeths.get_extmark_by_id(ns, mark)
- eq({er, ec}, rv)
-end
-
local function set_extmark(ns_id, id, line, col, opts)
if opts == nil then
opts = {}
end
- return curbufmeths.set_extmark(ns_id, id, line, col, opts)
+ if id ~= nil and id ~= 0 then
+ opts.id = id
+ end
+ return curbufmeths.set_extmark(ns_id, line, col, opts)
end
local function get_extmarks(ns_id, start, end_, opts)
@@ -42,6 +34,24 @@ local function get_extmarks(ns_id, start, end_, opts)
return curbufmeths.get_extmarks(ns_id, start, end_, opts)
end
+local function get_extmark_by_id(ns_id, id, opts)
+ if opts == nil then
+ opts = {}
+ end
+ return curbufmeths.get_extmark_by_id(ns_id, id, opts)
+end
+
+local function check_undo_redo(ns, mark, sr, sc, er, ec) --s = start, e = end
+ local rv = get_extmark_by_id(ns, mark)
+ eq({er, ec}, rv)
+ feed("u")
+ rv = get_extmark_by_id(ns, mark)
+ eq({sr, sc}, rv)
+ feed("<c-r>")
+ rv = get_extmark_by_id(ns, mark)
+ eq({er, ec}, rv)
+end
+
local function batch_set(ns_id, positions)
local ids = {}
for _, pos in ipairs(positions) do
@@ -93,7 +103,7 @@ describe('API/extmarks', function()
it('adds, updates and deletes marks', function()
local rv = set_extmark(ns, marks[1], positions[1][1], positions[1][2])
eq(marks[1], rv)
- rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ rv = get_extmark_by_id(ns, marks[1])
eq({positions[1][1], positions[1][2]}, rv)
-- Test adding a second mark on same row works
rv = set_extmark(ns, marks[2], positions[2][1], positions[2][2])
@@ -102,14 +112,14 @@ describe('API/extmarks', function()
-- Test an update, (same pos)
rv = set_extmark(ns, marks[1], positions[1][1], positions[1][2])
eq(marks[1], rv)
- rv = curbufmeths.get_extmark_by_id(ns, marks[2])
+ rv = get_extmark_by_id(ns, marks[2])
eq({positions[2][1], positions[2][2]}, rv)
-- Test an update, (new pos)
row = positions[1][1]
col = positions[1][2] + 1
rv = set_extmark(ns, marks[1], row, col)
eq(marks[1], rv)
- rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ rv = get_extmark_by_id(ns, marks[1])
eq({row, col}, rv)
-- remove the test marks
@@ -432,7 +442,7 @@ describe('API/extmarks', function()
~ |
|
]])
- local rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ local rv = get_extmark_by_id(ns, marks[1])
eq({0, 6}, rv)
check_undo_redo(ns, marks[1], 0, 3, 0, 6)
end)
@@ -906,9 +916,9 @@ describe('API/extmarks', function()
-- Set the mark before the cursor, should stay there
set_extmark(ns, marks[2], 0, 10)
feed("i<cr><esc>")
- local rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ local rv = get_extmark_by_id(ns, marks[1])
eq({1, 3}, rv)
- rv = curbufmeths.get_extmark_by_id(ns, marks[2])
+ rv = get_extmark_by_id(ns, marks[2])
eq({0, 10}, rv)
check_undo_redo(ns, marks[1], 0, 12, 1, 3)
end)
@@ -921,12 +931,12 @@ describe('API/extmarks', function()
feed("0iint <esc>A {<cr><esc>0i1M1<esc>")
set_extmark(ns, marks[1], 1, 1)
feed("0i<c-f><esc>")
- local rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ local rv = get_extmark_by_id(ns, marks[1])
eq({1, 3}, rv)
check_undo_redo(ns, marks[1], 1, 1, 1, 3)
-- now check when cursor at eol
feed("uA<c-f><esc>")
- rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ rv = get_extmark_by_id(ns, marks[1])
eq({1, 3}, rv)
end)
@@ -937,12 +947,12 @@ describe('API/extmarks', function()
feed("0i<tab><esc>")
set_extmark(ns, marks[1], 0, 3)
feed("bi<c-d><esc>")
- local rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ local rv = get_extmark_by_id(ns, marks[1])
eq({0, 1}, rv)
check_undo_redo(ns, marks[1], 0, 3, 0, 1)
-- check when cursor at eol
feed("uA<c-d><esc>")
- rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ rv = get_extmark_by_id(ns, marks[1])
eq({0, 1}, rv)
end)
@@ -1072,7 +1082,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[5], 2, 0, 3, 0)
feed('u')
feed([[:1,2s:3:\rxx<cr>]])
- eq({1, 3}, curbufmeths.get_extmark_by_id(ns, marks[3]))
+ eq({1, 3}, get_extmark_by_id(ns, marks[3]))
end)
it('substitions over multiple lines with replace in substition', function()
@@ -1311,16 +1321,16 @@ describe('API/extmarks', function()
eq("Invalid ns_id", pcall_err(set_extmark, ns_invalid, marks[1], positions[1][1], positions[1][2]))
eq("Invalid ns_id", pcall_err(curbufmeths.del_extmark, ns_invalid, marks[1]))
eq("Invalid ns_id", pcall_err(get_extmarks, ns_invalid, positions[1], positions[2]))
- eq("Invalid ns_id", pcall_err(curbufmeths.get_extmark_by_id, ns_invalid, marks[1]))
+ eq("Invalid ns_id", pcall_err(get_extmark_by_id, ns_invalid, marks[1]))
end)
it('when col = line-length, set the mark on eol', function()
set_extmark(ns, marks[1], 0, -1)
- local rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ local rv = get_extmark_by_id(ns, marks[1])
eq({0, init_text:len()}, rv)
-- Test another
set_extmark(ns, marks[1], 0, -1)
- rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ rv = get_extmark_by_id(ns, marks[1])
eq({0, init_text:len()}, rv)
end)
@@ -1333,7 +1343,7 @@ describe('API/extmarks', function()
local invalid_col = init_text:len() + 1
local invalid_lnum = 3
eq('line value outside range', pcall_err(set_extmark, ns, marks[1], invalid_lnum, invalid_col))
- eq({}, curbufmeths.get_extmark_by_id(ns, marks[1]))
+ eq({}, get_extmark_by_id(ns, marks[1]))
end)
it('bug from check_col in extmark_set', function()
@@ -1357,14 +1367,14 @@ describe('API/extmarks', function()
it('can set a mark to other buffer', function()
local buf = request('nvim_create_buf', 0, 1)
request('nvim_buf_set_lines', buf, 0, -1, 1, {"", ""})
- local id = bufmeths.set_extmark(buf, ns, 0, 1, 0, {})
+ local id = bufmeths.set_extmark(buf, ns, 1, 0, {})
eq({{id, 1, 0}}, bufmeths.get_extmarks(buf, ns, 0, -1, {}))
end)
it('does not crash with append/delete/undo seqence', function()
meths.exec([[
let ns = nvim_create_namespace('myplugin')
- call nvim_buf_set_extmark(0, ns, 0, 0, 0, {})
+ call nvim_buf_set_extmark(0, ns, 0, 0, {})
call append(0, '')
%delete
undo]],false)
diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua
index e8435cd3b7..e4fb95442c 100644
--- a/test/functional/helpers.lua
+++ b/test/functional/helpers.lua
@@ -769,14 +769,14 @@ end
function module.missing_provider(provider)
if provider == 'ruby' or provider == 'node' or provider == 'perl' then
- local prog = module.funcs['provider#' .. provider .. '#Detect']()
- return prog == '' and (provider .. ' not detected') or false
+ local e = module.funcs['provider#'..provider..'#Detect']()[2]
+ return e ~= '' and e or false
elseif provider == 'python' or provider == 'python3' then
local py_major_version = (provider == 'python3' and 3 or 2)
- local errors = module.funcs['provider#pythonx#Detect'](py_major_version)[2]
- return errors ~= '' and errors or false
+ local e = module.funcs['provider#pythonx#Detect'](py_major_version)[2]
+ return e ~= '' and e or false
else
- assert(false, 'Unknown provider: ' .. provider)
+ assert(false, 'Unknown provider: '..provider)
end
end
diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua
index 77f8189bb9..5be47070a7 100644
--- a/test/functional/lua/buffer_updates_spec.lua
+++ b/test/functional/lua/buffer_updates_spec.lua
@@ -1,12 +1,16 @@
-- Test suite for testing interactions with API bindings
local helpers = require('test.functional.helpers')(after_each)
+local inspect = require'vim.inspect'
+
local command = helpers.command
local meths = helpers.meths
local clear = helpers.clear
local eq = helpers.eq
+local fail = helpers.fail
local exec_lua = helpers.exec_lua
local feed = helpers.feed
+local deepcopy = helpers.deepcopy
local origlines = {"original line 1",
"original line 2",
@@ -16,32 +20,37 @@ local origlines = {"original line 1",
"original line 6",
" indented line"}
-describe('lua: buffer event callbacks', function()
- before_each(function()
- clear()
- exec_lua([[
- local events = {}
+local function attach_buffer(evname)
+ exec_lua([[
+ local evname = ...
+ local events = {}
- function test_register(bufnr, id, changedtick, utf_sizes)
- local function callback(...)
- table.insert(events, {id, ...})
- if test_unreg == id then
- return true
- end
- end
- local opts = {on_lines=callback, on_detach=callback, utf_sizes=utf_sizes}
- if changedtick then
- opts.on_changedtick = callback
+ function test_register(bufnr, id, changedtick, utf_sizes)
+ local function callback(...)
+ table.insert(events, {id, ...})
+ if test_unreg == id then
+ return true
end
- vim.api.nvim_buf_attach(bufnr, false, opts)
end
-
- function get_events()
- local ret_events = events
- events = {}
- return ret_events
+ local opts = {[evname]=callback, on_detach=callback, utf_sizes=utf_sizes}
+ if changedtick then
+ opts.on_changedtick = callback
end
- ]])
+ vim.api.nvim_buf_attach(bufnr, false, opts)
+ end
+
+ function get_events()
+ local ret_events = events
+ events = {}
+ return ret_events
+ end
+ ]], evname)
+end
+
+describe('lua buffer event callbacks: on_lines', function()
+ before_each(function()
+ clear()
+ attach_buffer('on_lines')
end)
@@ -62,7 +71,7 @@ describe('lua: buffer event callbacks', function()
local function check_events(expected)
local events = exec_lua("return get_events(...)" )
if utf_sizes then
- -- this test case uses ASCII only, so sizes sshould be the same.
+ -- this test case uses ASCII only, so sizes should be the same.
-- Unicode is tested below.
for _, event in ipairs(expected) do
event[9] = event[8]
@@ -216,4 +225,144 @@ describe('lua: buffer event callbacks', function()
eq(1, meths.get_var('listener_cursor_line'))
end)
+ it('does not SEGFAULT when calling win_findbuf in on_detach', function()
+
+ exec_lua[[
+ local buf = vim.api.nvim_create_buf(false, false)
+
+ vim.cmd"split"
+ vim.api.nvim_win_set_buf(0, buf)
+
+ vim.api.nvim_buf_attach(buf, false, {
+ on_detach = function(_, buf)
+ vim.fn.win_findbuf(buf)
+ end
+ })
+ ]]
+
+ command("q!")
+ helpers.assert_alive()
+ end)
+
+end)
+
+describe('lua: nvim_buf_attach on_bytes', function()
+ before_each(function()
+ clear()
+ attach_buffer('on_bytes')
+ end)
+
+ -- verifying the sizes with nvim_buf_get_offset is nice (checks we cannot
+ -- assert the wrong thing), but masks errors with unflushed lines (as
+ -- nvim_buf_get_offset forces a flush of the memline). To be safe run the
+ -- test both ways.
+ local function setup_eventcheck(verify)
+ meths.buf_set_lines(0, 0, -1, true, origlines)
+ local shadow = deepcopy(origlines)
+ local shadowbytes = table.concat(shadow, '\n') .. '\n'
+ -- TODO: while we are brewing the real strong coffe,
+ -- verify should check buf_get_offset after every check_events
+ if verify then
+ meths.buf_get_offset(0, meths.buf_line_count(0))
+ end
+ exec_lua("return test_register(...)", 0, "test1",false, nil)
+ meths.buf_get_changedtick(0)
+
+ local verify_name = "test1"
+ local function check_events(expected)
+ local events = exec_lua("return get_events(...)" )
+
+ if not pcall(eq, expected, events) then
+ local msg = 'unexpected byte updates received.\n\nBABBLA MER \n\n'
+
+ msg = msg .. 'received events:\n'
+ for _, e in ipairs(events) do
+ msg = msg .. ' ' .. inspect(e) .. ';\n'
+ end
+ msg = msg .. '\nexpected events:\n'
+ for _, e in ipairs(expected) do
+ msg = msg .. ' ' .. inspect(e) .. ';\n'
+ end
+ fail(msg)
+ end
+
+ if verify then
+ for _, event in ipairs(events) do
+ if event[1] == verify_name and event[2] == "bytes" then
+ local _, _, _, _, _, _, start_byte, _, _, old_byte, _, _, new_byte = unpack(event)
+ local before = string.sub(shadowbytes, 1, start_byte)
+ -- no text in the tests will contain 0xff bytes (invalid UTF-8)
+ -- so we can use it as marker for unknown bytes
+ local unknown = string.rep('\255', new_byte)
+ local after = string.sub(shadowbytes, start_byte + old_byte + 1)
+ shadowbytes = before .. unknown .. after
+ end
+ end
+ local text = meths.buf_get_lines(0, 0, -1, true)
+ local bytes = table.concat(text, '\n') .. '\n'
+ eq(string.len(bytes), string.len(shadowbytes), shadowbytes)
+ for i = 1, string.len(shadowbytes) do
+ local shadowbyte = string.sub(shadowbytes, i, i)
+ if shadowbyte ~= '\255' then
+ eq(string.sub(bytes, i, i), shadowbyte, i)
+ end
+ end
+ end
+ end
+
+ return check_events
+ end
+
+ -- Yes, we can do both
+ local function do_both(verify)
+ it('single and multiple join', function()
+ local check_events = setup_eventcheck(verify)
+ feed 'ggJ'
+ check_events {
+ {'test1', 'bytes', 1, 3, 0, 15, 15, 1, 0, 1, 0, 1, 1};
+ }
+
+ feed '3J'
+ check_events {
+ {'test1', 'bytes', 1, 5, 0, 31, 31, 1, 0, 1, 0, 1, 1};
+ {'test1', 'bytes', 1, 5, 0, 47, 47, 1, 0, 1, 0, 1, 1};
+ }
+ end)
+
+ it('opening lines', function()
+ local check_events = setup_eventcheck(verify)
+ -- meths.buf_set_option(0, 'autoindent', true)
+ feed 'Go'
+ check_events {
+ { "test1", "bytes", 1, 4, 7, 0, 114, 0, 0, 0, 1, 0, 1 };
+ }
+ feed '<cr>'
+ check_events {
+ { "test1", "bytes", 1, 5, 7, 0, 114, 0, 0, 0, 1, 0, 1 };
+ }
+ end)
+
+ it('opening lines with autoindent', function()
+ local check_events = setup_eventcheck(verify)
+ meths.buf_set_option(0, 'autoindent', true)
+ feed 'Go'
+ check_events {
+ { "test1", "bytes", 1, 4, 7, 0, 114, 0, 0, 0, 1, 0, 5 };
+ }
+ feed '<cr>'
+ check_events {
+ { "test1", "bytes", 1, 4, 8, 0, 115, 0, 4, 4, 0, 0, 0 };
+ { "test1", "bytes", 1, 5, 7, 4, 118, 0, 0, 0, 1, 4, 5 };
+ }
+ end)
+ end
+
+ describe('(with verify) handles', function()
+ do_both(true)
+ end)
+
+ describe('(without verify) handles', function()
+ do_both(false)
+ end)
end)
+
diff --git a/test/functional/lua/treesitter_spec.lua b/test/functional/lua/treesitter_spec.lua
index b0ac9e079a..2c9107a65a 100644
--- a/test/functional/lua/treesitter_spec.lua
+++ b/test/functional/lua/treesitter_spec.lua
@@ -127,6 +127,58 @@ void ui_refresh(void)
}
}]]
+ it('allows to iterate over nodes children', function()
+ if not check_parser() then return end
+
+ insert(test_text);
+
+ local res = exec_lua([[
+ parser = vim.treesitter.get_parser(0, "c")
+
+ func_node = parser:parse():root():child(0)
+
+ res = {}
+ for node, field in func_node:iter_children() do
+ table.insert(res, {node:type(), field})
+ end
+ return res
+ ]])
+
+ eq({
+ {"primitive_type", "type"},
+ {"function_declarator", "declarator"},
+ {"compound_statement", "body"}
+ }, res)
+ end)
+
+ it('allows to get a child by field', function()
+ if not check_parser() then return end
+
+ insert(test_text);
+
+ local res = exec_lua([[
+ parser = vim.treesitter.get_parser(0, "c")
+
+ func_node = parser:parse():root():child(0)
+
+ local res = {}
+ for _, node in ipairs(func_node:field("type")) do
+ table.insert(res, {node:type(), node:range()})
+ end
+ return res
+ ]])
+
+ eq({{ "primitive_type", 0, 0, 0, 4 }}, res)
+
+ local res_fail = exec_lua([[
+ parser = vim.treesitter.get_parser(0, "c")
+
+ return #func_node:field("foo") == 0
+ ]])
+
+ assert(res_fail)
+ end)
+
local query = [[
((call_expression function: (identifier) @minfunc (argument_list (identifier) @min_id)) (eq? @minfunc "MIN"))
"for" @keyword
@@ -198,6 +250,35 @@ void ui_refresh(void)
}, res)
end)
+ it('allow loading query with escaped quotes and capture them with `lua-match?` and `vim-match?`', function()
+ if not check_parser() then return end
+
+ insert('char* astring = "Hello World!";')
+
+ local res = exec_lua([[
+ cquery = vim.treesitter.parse_query("c", '((_) @quote (vim-match? @quote "^\\"$")) ((_) @quote (lua-match? @quote "^\\"$"))')
+ parser = vim.treesitter.get_parser(0, "c")
+ tree = parser:parse()
+ res = {}
+ for pattern, match in cquery:iter_matches(tree:root(), 0, 0, 1) do
+ -- can't transmit node over RPC. just check the name and range
+ local mrepr = {}
+ for cid,node in pairs(match) do
+ table.insert(mrepr, {cquery.captures[cid], node:type(), node:range()})
+ end
+ table.insert(res, {pattern, mrepr})
+ end
+ return res
+ ]])
+
+ eq({
+ { 1, { { "quote", '"', 0, 16, 0, 17 } } },
+ { 2, { { "quote", '"', 0, 16, 0, 17 } } },
+ { 1, { { "quote", '"', 0, 29, 0, 30 } } },
+ { 2, { { "quote", '"', 0, 29, 0, 30 } } },
+ }, res)
+ end)
+
it('allows to add predicates', function()
insert([[
int main(void) {
@@ -231,6 +312,18 @@ void ui_refresh(void)
]], custom_query)
eq({{0, 4, 0, 8}}, res)
+
+ local res_list = exec_lua[[
+ local query = require'vim.treesitter.query'
+
+ local list = query.list_predicates()
+
+ table.sort(list)
+
+ return list
+ ]]
+
+ eq({ 'contains?', 'eq?', 'is-main?', 'lua-match?', 'match?', 'vim-match?' }, res_list)
end)
it('supports highlighting', function()
@@ -280,7 +373,7 @@ static int nlua_schedule(lua_State *const lstate)
; Use lua regexes
((identifier) @Identifier (#contains? @Identifier "lua_"))
-((identifier) @Constant (#match? @Constant "^[A-Z_]+$"))
+((identifier) @Constant (#lua-match? @Constant "^[A-Z_]+$"))
((identifier) @Normal (#vim-match? @Constant "^lstate$"))
((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right) (#eq? @WarningMsg.left @WarningMsg.right))
@@ -352,6 +445,32 @@ static int nlua_schedule(lua_State *const lstate)
|
]]}
+ feed("5Goc<esc>dd")
+ if true == true then
+ pending('reenable this check in luahl PR')
+ return
+ end
+ screen:expect{grid=[[
+ {2:/// Schedule Lua callback on main loop's event queue} |
+ {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) |
+ { |
+ {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} |
+ || {6:lstate} != {6:lstate}) { |
+ {11:^lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); |
+ {4:return} {11:lua_error}(lstate); |
+ } |
+ |
+ {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); |
+ |
+ multiqueue_put(main_loop.events, {11:nlua_schedule_event}, |
+ {5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
+ {4:return} {5:0}; |
+ } |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
feed('7Go*/<esc>')
screen:expect{grid=[[
{2:/// Schedule Lua callback on main loop's event queue} |
@@ -361,7 +480,7 @@ static int nlua_schedule(lua_State *const lstate)
|| {6:lstate} != {6:lstate}) { |
{11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); |
{4:return} {11:lua_error}(lstate); |
- {8:*^/} |
+ *^/ |
} |
|
{7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); |
diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua
index 11ce26410d..92d077ed14 100644
--- a/test/functional/options/defaults_spec.lua
+++ b/test/functional/options/defaults_spec.lua
@@ -290,9 +290,6 @@ describe('XDG-based defaults', function()
end)
end)
- -- TODO(jkeyes): tests below fail on win32 because of path separator.
- if helpers.pending_win32(pending) then return end
-
local function vimruntime_and_libdir()
local vimruntime = eval('$VIMRUNTIME')
-- libdir is hard to calculate reliably across various ci platforms
@@ -301,71 +298,78 @@ describe('XDG-based defaults', function()
return vimruntime, libdir
end
+ local env_sep = iswin() and ';' or ':'
+ local data_dir = iswin() and 'nvim-data' or 'nvim'
+ local root_path = iswin() and 'C:' or ''
+
describe('with too long XDG variables', function()
before_each(function()
clear({env={
- XDG_CONFIG_HOME=('/x'):rep(4096),
- XDG_CONFIG_DIRS=(('/a'):rep(2048)
- .. ':' .. ('/b'):rep(2048)
- .. (':/c'):rep(512)),
- XDG_DATA_HOME=('/X'):rep(4096),
- XDG_DATA_DIRS=(('/A'):rep(2048)
- .. ':' .. ('/B'):rep(2048)
- .. (':/C'):rep(512)),
+ XDG_CONFIG_HOME=(root_path .. ('/x'):rep(4096)),
+ XDG_CONFIG_DIRS=(root_path .. ('/a'):rep(2048)
+ .. env_sep.. root_path .. ('/b'):rep(2048)
+ .. (env_sep .. root_path .. '/c'):rep(512)),
+ XDG_DATA_HOME=(root_path .. ('/X'):rep(4096)),
+ XDG_DATA_DIRS=(root_path .. ('/A'):rep(2048)
+ .. env_sep .. root_path .. ('/B'):rep(2048)
+ .. (env_sep .. root_path .. '/C'):rep(512)),
}})
end)
it('are correctly set', function()
local vimruntime, libdir = vimruntime_and_libdir()
- eq((('/x'):rep(4096) .. '/nvim'
- .. ',' .. ('/a'):rep(2048) .. '/nvim'
- .. ',' .. ('/b'):rep(2048) .. '/nvim'
- .. (',' .. '/c/nvim'):rep(512)
- .. ',' .. ('/X'):rep(4096) .. '/nvim/site'
- .. ',' .. ('/A'):rep(2048) .. '/nvim/site'
- .. ',' .. ('/B'):rep(2048) .. '/nvim/site'
- .. (',' .. '/C/nvim/site'):rep(512)
+ eq(((root_path .. ('/x'):rep(4096) .. '/nvim'
+ .. ',' .. root_path .. ('/a'):rep(2048) .. '/nvim'
+ .. ',' .. root_path .. ('/b'):rep(2048) .. '/nvim'
+ .. (',' .. root_path .. '/c/nvim'):rep(512)
+ .. ',' .. root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/site'
+ .. ',' .. root_path .. ('/A'):rep(2048) .. '/nvim/site'
+ .. ',' .. root_path .. ('/B'):rep(2048) .. '/nvim/site'
+ .. (',' .. root_path .. '/C/nvim/site'):rep(512)
.. ',' .. vimruntime
.. ',' .. libdir
- .. (',' .. '/C/nvim/site/after'):rep(512)
- .. ',' .. ('/B'):rep(2048) .. '/nvim/site/after'
- .. ',' .. ('/A'):rep(2048) .. '/nvim/site/after'
- .. ',' .. ('/X'):rep(4096) .. '/nvim/site/after'
- .. (',' .. '/c/nvim/after'):rep(512)
- .. ',' .. ('/b'):rep(2048) .. '/nvim/after'
- .. ',' .. ('/a'):rep(2048) .. '/nvim/after'
- .. ',' .. ('/x'):rep(4096) .. '/nvim/after'
- ), meths.get_option('runtimepath'))
+ .. (',' .. root_path .. '/C/nvim/site/after'):rep(512)
+ .. ',' .. root_path .. ('/B'):rep(2048) .. '/nvim/site/after'
+ .. ',' .. root_path .. ('/A'):rep(2048) .. '/nvim/site/after'
+ .. ',' .. root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/site/after'
+ .. (',' .. root_path .. '/c/nvim/after'):rep(512)
+ .. ',' .. root_path .. ('/b'):rep(2048) .. '/nvim/after'
+ .. ',' .. root_path .. ('/a'):rep(2048) .. '/nvim/after'
+ .. ',' .. root_path .. ('/x'):rep(4096) .. '/nvim/after'
+ ):gsub('\\', '/')), (meths.get_option('runtimepath')):gsub('\\', '/'))
meths.command('set runtimepath&')
meths.command('set backupdir&')
meths.command('set directory&')
meths.command('set undodir&')
meths.command('set viewdir&')
- eq((('/x'):rep(4096) .. '/nvim'
- .. ',' .. ('/a'):rep(2048) .. '/nvim'
- .. ',' .. ('/b'):rep(2048) .. '/nvim'
- .. (',' .. '/c/nvim'):rep(512)
- .. ',' .. ('/X'):rep(4096) .. '/nvim/site'
- .. ',' .. ('/A'):rep(2048) .. '/nvim/site'
- .. ',' .. ('/B'):rep(2048) .. '/nvim/site'
- .. (',' .. '/C/nvim/site'):rep(512)
+ eq(((root_path .. ('/x'):rep(4096) .. '/nvim'
+ .. ',' .. root_path .. ('/a'):rep(2048) .. '/nvim'
+ .. ',' .. root_path .. ('/b'):rep(2048) .. '/nvim'
+ .. (',' .. root_path .. '/c/nvim'):rep(512)
+ .. ',' .. root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/site'
+ .. ',' .. root_path .. ('/A'):rep(2048) .. '/nvim/site'
+ .. ',' .. root_path .. ('/B'):rep(2048) .. '/nvim/site'
+ .. (',' .. root_path .. '/C/nvim/site'):rep(512)
.. ',' .. vimruntime
.. ',' .. libdir
- .. (',' .. '/C/nvim/site/after'):rep(512)
- .. ',' .. ('/B'):rep(2048) .. '/nvim/site/after'
- .. ',' .. ('/A'):rep(2048) .. '/nvim/site/after'
- .. ',' .. ('/X'):rep(4096) .. '/nvim/site/after'
- .. (',' .. '/c/nvim/after'):rep(512)
- .. ',' .. ('/b'):rep(2048) .. '/nvim/after'
- .. ',' .. ('/a'):rep(2048) .. '/nvim/after'
- .. ',' .. ('/x'):rep(4096) .. '/nvim/after'
- ), meths.get_option('runtimepath'))
- eq('.,' .. ('/X'):rep(4096) .. '/nvim/backup',
- meths.get_option('backupdir'))
- eq(('/X'):rep(4096) .. '/nvim/swap//', meths.get_option('directory'))
- eq(('/X'):rep(4096) .. '/nvim/undo', meths.get_option('undodir'))
- eq(('/X'):rep(4096) .. '/nvim/view', meths.get_option('viewdir'))
+ .. (',' .. root_path .. '/C/nvim/site/after'):rep(512)
+ .. ',' .. root_path .. ('/B'):rep(2048) .. '/nvim/site/after'
+ .. ',' .. root_path .. ('/A'):rep(2048) .. '/nvim/site/after'
+ .. ',' .. root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/site/after'
+ .. (',' .. root_path .. '/c/nvim/after'):rep(512)
+ .. ',' .. root_path .. ('/b'):rep(2048) .. '/nvim/after'
+ .. ',' .. root_path .. ('/a'):rep(2048) .. '/nvim/after'
+ .. ',' .. root_path .. ('/x'):rep(4096) .. '/nvim/after'
+ ):gsub('\\', '/')), (meths.get_option('runtimepath')):gsub('\\', '/'))
+ eq('.,' .. root_path .. ('/X'):rep(4096).. '/' .. data_dir .. '/backup',
+ (meths.get_option('backupdir'):gsub('\\', '/')))
+ eq(root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/swap//',
+ (meths.get_option('directory')):gsub('\\', '/'))
+ eq(root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/undo',
+ (meths.get_option('undodir')):gsub('\\', '/'))
+ eq(root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/view',
+ (meths.get_option('viewdir')):gsub('\\', '/'))
end)
end)
@@ -381,53 +385,61 @@ describe('XDG-based defaults', function()
it('are not expanded', function()
local vimruntime, libdir = vimruntime_and_libdir()
- eq(('$XDG_DATA_HOME/nvim'
+ eq((('$XDG_DATA_HOME/nvim'
.. ',$XDG_DATA_DIRS/nvim'
- .. ',$XDG_CONFIG_HOME/nvim/site'
+ .. ',$XDG_CONFIG_HOME/' .. data_dir .. '/site'
.. ',$XDG_CONFIG_DIRS/nvim/site'
.. ',' .. vimruntime
.. ',' .. libdir
.. ',$XDG_CONFIG_DIRS/nvim/site/after'
- .. ',$XDG_CONFIG_HOME/nvim/site/after'
+ .. ',$XDG_CONFIG_HOME/' .. data_dir .. '/site/after'
.. ',$XDG_DATA_DIRS/nvim/after'
.. ',$XDG_DATA_HOME/nvim/after'
- ), meths.get_option('runtimepath'))
+ ):gsub('\\', '/')), (meths.get_option('runtimepath')):gsub('\\', '/'))
meths.command('set runtimepath&')
meths.command('set backupdir&')
meths.command('set directory&')
meths.command('set undodir&')
meths.command('set viewdir&')
- eq(('$XDG_DATA_HOME/nvim'
+ eq((('$XDG_DATA_HOME/nvim'
.. ',$XDG_DATA_DIRS/nvim'
- .. ',$XDG_CONFIG_HOME/nvim/site'
+ .. ',$XDG_CONFIG_HOME/' .. data_dir .. '/site'
.. ',$XDG_CONFIG_DIRS/nvim/site'
.. ',' .. vimruntime
.. ',' .. libdir
.. ',$XDG_CONFIG_DIRS/nvim/site/after'
- .. ',$XDG_CONFIG_HOME/nvim/site/after'
+ .. ',$XDG_CONFIG_HOME/' .. data_dir .. '/site/after'
.. ',$XDG_DATA_DIRS/nvim/after'
.. ',$XDG_DATA_HOME/nvim/after'
- ), meths.get_option('runtimepath'))
- eq('.,$XDG_CONFIG_HOME/nvim/backup', meths.get_option('backupdir'))
- eq('$XDG_CONFIG_HOME/nvim/swap//', meths.get_option('directory'))
- eq('$XDG_CONFIG_HOME/nvim/undo', meths.get_option('undodir'))
- eq('$XDG_CONFIG_HOME/nvim/view', meths.get_option('viewdir'))
+ ):gsub('\\', '/')), (meths.get_option('runtimepath')):gsub('\\', '/'))
+ eq(('.,$XDG_CONFIG_HOME/' .. data_dir .. '/backup'),
+ meths.get_option('backupdir'):gsub('\\', '/'))
+ eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/swap//'),
+ meths.get_option('directory'):gsub('\\', '/'))
+ eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/undo'),
+ meths.get_option('undodir'):gsub('\\', '/'))
+ eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/view'),
+ meths.get_option('viewdir'):gsub('\\', '/'))
meths.command('set all&')
eq(('$XDG_DATA_HOME/nvim'
.. ',$XDG_DATA_DIRS/nvim'
- .. ',$XDG_CONFIG_HOME/nvim/site'
+ .. ',$XDG_CONFIG_HOME/' .. data_dir .. '/site'
.. ',$XDG_CONFIG_DIRS/nvim/site'
.. ',' .. vimruntime
.. ',' .. libdir
.. ',$XDG_CONFIG_DIRS/nvim/site/after'
- .. ',$XDG_CONFIG_HOME/nvim/site/after'
+ .. ',$XDG_CONFIG_HOME/' .. data_dir .. '/site/after'
.. ',$XDG_DATA_DIRS/nvim/after'
.. ',$XDG_DATA_HOME/nvim/after'
- ), meths.get_option('runtimepath'))
- eq('.,$XDG_CONFIG_HOME/nvim/backup', meths.get_option('backupdir'))
- eq('$XDG_CONFIG_HOME/nvim/swap//', meths.get_option('directory'))
- eq('$XDG_CONFIG_HOME/nvim/undo', meths.get_option('undodir'))
- eq('$XDG_CONFIG_HOME/nvim/view', meths.get_option('viewdir'))
+ ):gsub('\\', '/'), (meths.get_option('runtimepath')):gsub('\\', '/'))
+ eq(('.,$XDG_CONFIG_HOME/' .. data_dir .. '/backup'),
+ meths.get_option('backupdir'):gsub('\\', '/'))
+ eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/swap//'),
+ meths.get_option('directory'):gsub('\\', '/'))
+ eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/undo'),
+ meths.get_option('undodir'):gsub('\\', '/'))
+ eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/view'),
+ meths.get_option('viewdir'):gsub('\\', '/'))
end)
end)
@@ -435,53 +447,58 @@ describe('XDG-based defaults', function()
before_each(function()
clear({env={
XDG_CONFIG_HOME=', , ,',
- XDG_CONFIG_DIRS=',-,-,:-,-,-',
+ XDG_CONFIG_DIRS=',-,-,' .. env_sep .. '-,-,-',
XDG_DATA_HOME=',=,=,',
- XDG_DATA_DIRS=',≡,≡,:≡,≡,≡',
+ XDG_DATA_DIRS=',≡,≡,' .. env_sep .. '≡,≡,≡',
}})
end)
it('are escaped properly', function()
local vimruntime, libdir = vimruntime_and_libdir()
- eq(('\\, \\, \\,/nvim'
- .. ',\\,-\\,-\\,/nvim'
- .. ',-\\,-\\,-/nvim'
- .. ',\\,=\\,=\\,/nvim/site'
- .. ',\\,≡\\,≡\\,/nvim/site'
- .. ',≡\\,≡\\,≡/nvim/site'
+ local path_sep = iswin() and '\\' or '/'
+ eq(('\\, \\, \\,' .. path_sep .. 'nvim'
+ .. ',\\,-\\,-\\,' .. path_sep .. 'nvim'
+ .. ',-\\,-\\,-' .. path_sep .. 'nvim'
+ .. ',\\,=\\,=\\,' .. path_sep .. data_dir .. path_sep .. 'site'
+ .. ',\\,≡\\,≡\\,' .. path_sep .. 'nvim' .. path_sep .. 'site'
+ .. ',≡\\,≡\\,≡' .. path_sep .. 'nvim' .. path_sep .. 'site'
.. ',' .. vimruntime
.. ',' .. libdir
- .. ',≡\\,≡\\,≡/nvim/site/after'
- .. ',\\,≡\\,≡\\,/nvim/site/after'
- .. ',\\,=\\,=\\,/nvim/site/after'
- .. ',-\\,-\\,-/nvim/after'
- .. ',\\,-\\,-\\,/nvim/after'
- .. ',\\, \\, \\,/nvim/after'
+ .. ',≡\\,≡\\,≡' .. path_sep .. 'nvim' .. path_sep .. 'site' .. path_sep .. 'after'
+ .. ',\\,≡\\,≡\\,' .. path_sep .. 'nvim' .. path_sep .. 'site' .. path_sep .. 'after'
+ .. ',\\,=\\,=\\,' .. path_sep.. data_dir .. path_sep .. 'site' .. path_sep .. 'after'
+ .. ',-\\,-\\,-' .. path_sep .. 'nvim' .. path_sep .. 'after'
+ .. ',\\,-\\,-\\,' .. path_sep .. 'nvim' .. path_sep .. 'after'
+ .. ',\\, \\, \\,' .. path_sep .. 'nvim' .. path_sep .. 'after'
), meths.get_option('runtimepath'))
meths.command('set runtimepath&')
meths.command('set backupdir&')
meths.command('set directory&')
meths.command('set undodir&')
meths.command('set viewdir&')
- eq(('\\, \\, \\,/nvim'
- .. ',\\,-\\,-\\,/nvim'
- .. ',-\\,-\\,-/nvim'
- .. ',\\,=\\,=\\,/nvim/site'
- .. ',\\,≡\\,≡\\,/nvim/site'
- .. ',≡\\,≡\\,≡/nvim/site'
+ eq(('\\, \\, \\,' .. path_sep .. 'nvim'
+ .. ',\\,-\\,-\\,' .. path_sep ..'nvim'
+ .. ',-\\,-\\,-' .. path_sep ..'nvim'
+ .. ',\\,=\\,=\\,' .. path_sep ..'' .. data_dir .. '' .. path_sep ..'site'
+ .. ',\\,≡\\,≡\\,' .. path_sep ..'nvim' .. path_sep ..'site'
+ .. ',≡\\,≡\\,≡' .. path_sep ..'nvim' .. path_sep ..'site'
.. ',' .. vimruntime
.. ',' .. libdir
- .. ',≡\\,≡\\,≡/nvim/site/after'
- .. ',\\,≡\\,≡\\,/nvim/site/after'
- .. ',\\,=\\,=\\,/nvim/site/after'
- .. ',-\\,-\\,-/nvim/after'
- .. ',\\,-\\,-\\,/nvim/after'
- .. ',\\, \\, \\,/nvim/after'
+ .. ',≡\\,≡\\,≡' .. path_sep ..'nvim' .. path_sep ..'site' .. path_sep ..'after'
+ .. ',\\,≡\\,≡\\,' .. path_sep ..'nvim' .. path_sep ..'site' .. path_sep ..'after'
+ .. ',\\,=\\,=\\,' .. path_sep ..'' .. data_dir .. '' .. path_sep ..'site' .. path_sep ..'after'
+ .. ',-\\,-\\,-' .. path_sep ..'nvim' .. path_sep ..'after'
+ .. ',\\,-\\,-\\,' .. path_sep ..'nvim' .. path_sep ..'after'
+ .. ',\\, \\, \\,' .. path_sep ..'nvim' .. path_sep ..'after'
), meths.get_option('runtimepath'))
- eq('.,\\,=\\,=\\,/nvim/backup', meths.get_option('backupdir'))
- eq('\\,=\\,=\\,/nvim/swap//', meths.get_option('directory'))
- eq('\\,=\\,=\\,/nvim/undo', meths.get_option('undodir'))
- eq('\\,=\\,=\\,/nvim/view', meths.get_option('viewdir'))
+ eq('.,\\,=\\,=\\,' .. path_sep .. data_dir .. '' .. path_sep ..'backup',
+ meths.get_option('backupdir'))
+ eq('\\,=\\,=\\,' .. path_sep ..'' .. data_dir .. '' .. path_sep ..'swap' .. (path_sep):rep(2),
+ meths.get_option('directory'))
+ eq('\\,=\\,=\\,' .. path_sep ..'' .. data_dir .. '' .. path_sep ..'undo',
+ meths.get_option('undodir'))
+ eq('\\,=\\,=\\,' .. path_sep ..'' .. data_dir .. '' .. path_sep ..'view',
+ meths.get_option('viewdir'))
end)
end)
end)
@@ -491,6 +508,7 @@ describe('stdpath()', function()
-- Windows appends 'nvim-data' instead of just 'nvim' to prevent collisions
-- due to XDG_CONFIG_HOME and XDG_DATA_HOME being the same.
local datadir = iswin() and 'nvim-data' or 'nvim'
+ local env_sep = iswin() and ';' or ':'
it('acceptance', function()
clear() -- Do not explicitly set any env vars.
@@ -634,13 +652,13 @@ describe('stdpath()', function()
local function set_paths_via_system(var_name, paths)
local env = base_env()
- env[var_name] = table.concat(paths, ':')
+ env[var_name] = table.concat(paths, env_sep)
clear({env=env})
end
local function set_paths_at_runtime(var_name, paths)
clear({env=base_env()})
- meths.set_var('env_val', table.concat(paths, ':'))
+ meths.set_var('env_val', table.concat(paths, env_sep))
command(('let $%s=g:env_val'):format(var_name))
end
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
index 1002011999..f514f4ea6f 100644
--- a/test/functional/plugin/lsp_spec.lua
+++ b/test/functional/plugin/lsp_spec.lua
@@ -971,34 +971,34 @@ describe('LSP', function()
local prefix = 'foo'
local completion_list = {
-- resolves into label
- { label='foobar' },
- { label='foobar', textEdit={} },
+ { label='foobar', sortText="a" },
+ { label='foobar', sortText="b", textEdit={} },
-- resolves into insertText
- { label='foocar', insertText='foobar' },
- { label='foocar', insertText='foobar', textEdit={} },
+ { label='foocar', sortText="c", insertText='foobar' },
+ { label='foocar', sortText="d", insertText='foobar', textEdit={} },
-- resolves into textEdit.newText
- { label='foocar', insertText='foodar', textEdit={newText='foobar'} },
- { label='foocar', textEdit={newText='foobar'} },
+ { label='foocar', sortText="e", insertText='foodar', textEdit={newText='foobar'} },
+ { label='foocar', sortText="f", textEdit={newText='foobar'} },
-- real-world snippet text
- { label='foocar', insertText='foodar', textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} },
- { label='foocar', insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', textEdit={} },
+ { label='foocar', sortText="g", insertText='foodar', textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} },
+ { label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', textEdit={} },
-- nested snippet tokens
- { label='foocar', insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', textEdit={} },
+ { label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', textEdit={} },
-- plain text
- { label='foocar', insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} },
+ { label='foocar', sortText="j", insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} },
}
local completion_list_items = {items=completion_list}
local expected = {
- { abbr = 'foobar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label = 'foobar' } } } } },
- { abbr = 'foobar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foobar', textEdit={} } } } } },
- { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foobar' } } } } },
- { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foobar', textEdit={} } } } } },
- { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar', textEdit={newText='foobar'} } } } } },
- { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', textEdit={newText='foobar'} } } } } },
- { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar(place holder, more ...holder{})', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar', textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} } } } } },
- { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ1, var2 *typ2) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', textEdit={} } } } } },
- { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ2,typ3 tail) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', textEdit={} } } } } },
- { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(${1:var1})', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} } } } } },
+ { abbr = 'foobar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label = 'foobar', sortText="a" } } } } },
+ { abbr = 'foobar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foobar', sortText="b", textEdit={} } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="c", insertText='foobar' } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="d", insertText='foobar', textEdit={} } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="e", insertText='foodar', textEdit={newText='foobar'} } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="f", textEdit={newText='foobar'} } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar(place holder, more ...holder{})', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="g", insertText='foodar', textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ1, var2 *typ2) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', textEdit={} } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ2,typ3 tail) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', textEdit={} } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(${1:var1})', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="j", insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} } } } } },
}
eq(expected, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], completion_list, prefix))
diff --git a/test/functional/provider/clipboard_spec.lua b/test/functional/provider/clipboard_spec.lua
index da9dd09129..1431054494 100644
--- a/test/functional/provider/clipboard_spec.lua
+++ b/test/functional/provider/clipboard_spec.lua
@@ -153,6 +153,16 @@ describe('clipboard', function()
eq('', eval('provider#clipboard#Error()'))
end)
+ it('g:clipboard using lists', function()
+ source([[let g:clipboard = {
+ \ 'name': 'custom',
+ \ 'copy': { '+': ['any', 'command'], '*': ['some', 'other'] },
+ \ 'paste': { '+': ['any', 'command'], '*': ['some', 'other'] },
+ \}]])
+ eq('custom', eval('provider#clipboard#Executable()'))
+ eq('', eval('provider#clipboard#Error()'))
+ end)
+
it('g:clipboard using VimL functions', function()
-- Implements a fake clipboard provider. cache_enabled is meaningless here.
source([[let g:clipboard = {
diff --git a/test/functional/provider/perl_spec.lua b/test/functional/provider/perl_spec.lua
index 7b446e4ab3..125674660b 100644
--- a/test/functional/provider/perl_spec.lua
+++ b/test/functional/provider/perl_spec.lua
@@ -5,6 +5,10 @@ local command = helpers.command
local write_file = helpers.write_file
local eval = helpers.eval
local retry = helpers.retry
+local curbufmeths = helpers.curbufmeths
+local insert = helpers.insert
+local expect = helpers.expect
+local feed = helpers.feed
do
clear()
@@ -19,7 +23,51 @@ before_each(function()
clear()
end)
-describe('perl host', function()
+describe('legacy perl provider', function()
+ if helpers.pending_win32(pending) then return end
+
+ it('feature test', function()
+ eq(1, eval('has("perl")'))
+ end)
+
+ it(':perl command', function()
+ command('perl $vim->vars->{set_by_perl} = [100, 0];')
+ eq({100, 0}, eval('g:set_by_perl'))
+ end)
+
+ it(':perlfile command', function()
+ local fname = 'perlfile.pl'
+ write_file(fname, '$vim->command("let set_by_perlfile = 123")')
+ command('perlfile perlfile.pl')
+ eq(123, eval('g:set_by_perlfile'))
+ os.remove(fname)
+ end)
+
+ it(':perldo command', function()
+ -- :perldo 1; doesn't change $_,
+ -- the buffer should not be changed
+ command('normal :perldo 1;')
+ eq(false, curbufmeths.get_option('modified'))
+ -- insert some text
+ insert('abc\ndef\nghi')
+ expect([[
+ abc
+ def
+ ghi]])
+ -- go to top and select and replace the first two lines
+ feed('ggvj:perldo $_ = reverse ($_)."$linenr"<CR>')
+ expect([[
+ cba1
+ fed2
+ ghi]])
+ end)
+
+ it('perleval()', function()
+ eq({1, 2, {['key'] = 'val'}}, eval([[perleval('[1, 2, {"key" => "val"}]')]]))
+ end)
+end)
+
+describe('perl provider', function()
if helpers.pending_win32(pending) then return end
teardown(function ()
os.remove('Xtest-perl-hello.pl')
diff --git a/test/functional/ui/bufhl_spec.lua b/test/functional/ui/bufhl_spec.lua
index 3cb592c714..d7269d2c29 100644
--- a/test/functional/ui/bufhl_spec.lua
+++ b/test/functional/ui/bufhl_spec.lua
@@ -690,7 +690,7 @@ describe('Buffer highlighting', function()
end)
it('can be retrieved', function()
- local get_virtual_text = curbufmeths.get_virtual_text
+ local get_extmarks = curbufmeths.get_extmarks
local line_count = curbufmeths.line_count
local s1 = {{'Köttbullar', 'Comment'}, {'Kräuterbutter'}}
@@ -699,12 +699,14 @@ describe('Buffer highlighting', function()
-- TODO: only a virtual text from the same ns curretly overrides
-- an existing virtual text. We might add a prioritation system.
set_virtual_text(id1, 0, s1, {})
- eq(s1, get_virtual_text(0))
+ eq({{1, 0, 0, {virt_text = s1}}}, get_extmarks(id1, {0,0}, {0, -1}, {details=true}))
- set_virtual_text(-1, line_count(), s2, {})
- eq(s2, get_virtual_text(line_count()))
+ -- TODO: is this really valid? shouldn't the max be line_count()-1?
+ local lastline = line_count()
+ set_virtual_text(id1, line_count(), s2, {})
+ eq({{3, lastline, 0, {virt_text = s2}}}, get_extmarks(id1, {lastline,0}, {lastline, -1}, {details=true}))
- eq({}, get_virtual_text(line_count() + 9000))
+ eq({}, get_extmarks(id1, {lastline+9000,0}, {lastline+9000, -1}, {}))
end)
it('is not highlighted by visual selection', function()
diff --git a/test/functional/ui/options_spec.lua b/test/functional/ui/options_spec.lua
index 9646c3fdad..2f113f6ac6 100644
--- a/test/functional/ui/options_spec.lua
+++ b/test/functional/ui/options_spec.lua
@@ -14,10 +14,10 @@ describe('UI receives option updates', function()
arabicshape=true,
emoji=true,
guifont='',
- guifontset='',
guifontwide='',
linespace=0,
pumblend=0,
+ mousefocus=false,
showtabline=1,
termguicolors=false,
ttimeout=true,
@@ -110,6 +110,12 @@ describe('UI receives option updates', function()
eq(expected, screen.options)
end)
+ command("set mousefocus")
+ expected.mousefocus = true
+ screen:expect(function()
+ eq(expected, screen.options)
+ end)
+
command("set nottimeout")
expected.ttimeout = false
screen:expect(function()
diff --git a/test/helpers.lua b/test/helpers.lua
index 40b93d9935..2e0258afed 100644
--- a/test/helpers.lua
+++ b/test/helpers.lua
@@ -82,6 +82,17 @@ end
function module.ok(res, msg, logfile)
return dumplog(logfile, assert.is_true, res, msg)
end
+
+-- TODO(bfredl): this should "failure" not "error" (issue with dumplog() )
+local function epicfail(state, arguments, _)
+ state.failure_message = arguments[1]
+ return false
+end
+assert:register("assertion", "epicfail", epicfail)
+function module.fail(msg, logfile)
+ return dumplog(logfile, assert.epicfail, msg)
+end
+
function module.matches(pat, actual)
if nil ~= string.match(actual, pat) then
return true
@@ -200,14 +211,25 @@ function module.check_logs()
end
end
fd:close()
- os.remove(file)
if #lines > 0 then
+ local status, f
local out = io.stdout
+ if os.getenv('SYMBOLIZER') then
+ status, f = pcall(module.popen_r, os.getenv('SYMBOLIZER'), '-l', file)
+ end
out:write(start_msg .. '\n')
- out:write('= ' .. table.concat(lines, '\n= ') .. '\n')
+ if status then
+ for line in f:lines() do
+ out:write('= '..line..'\n')
+ end
+ f:close()
+ else
+ out:write('= ' .. table.concat(lines, '\n= ') .. '\n')
+ end
out:write(select(1, start_msg:gsub('.', '=')) .. '\n')
table.insert(runtime_errors, file)
end
+ os.remove(file)
end
end
end
diff --git a/test/unit/os/env_spec.lua b/test/unit/os/env_spec.lua
index ad05b134e0..e7cb5e5d5e 100644
--- a/test/unit/os/env_spec.lua
+++ b/test/unit/os/env_spec.lua
@@ -78,15 +78,22 @@ describe('env.c', function()
end)
describe('os_setenv_append_path', function()
- itp('appends /foo/bar to $PATH', function()
+ itp('appends :/foo/bar to $PATH', function()
local original_path = os.getenv('PATH')
- eq(true, cimp.os_setenv_append_path(to_cstr('/foo/bar/baz')))
+ eq(true, cimp.os_setenv_append_path(to_cstr('/foo/bar/baz.exe')))
eq(original_path..':/foo/bar', os.getenv('PATH'))
end)
+ itp('avoids redundant separator when appending to $PATH #7377', function()
+ os_setenv('PATH', '/a/b/c:', true)
+ eq(true, cimp.os_setenv_append_path(to_cstr('/foo/bar/baz.exe')))
+ -- Must not have duplicate separators. #7377
+ eq('/a/b/c:/foo/bar', os.getenv('PATH'))
+ end)
+
itp('returns false if `fname` is not absolute', function()
local original_path = os.getenv('PATH')
- eq(false, cimp.os_setenv_append_path(to_cstr('foo/bar/baz')))
+ eq(false, cimp.os_setenv_append_path(to_cstr('foo/bar/baz.exe')))
eq(original_path, os.getenv('PATH'))
end)
end)