aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml20
-rwxr-xr-x.github/workflows/env.sh2
-rw-r--r--.github/workflows/release.yml6
-rw-r--r--.github/workflows/vim-patches.yml (renamed from .github/workflows/nightly.yaml)7
-rw-r--r--.gitignore5
-rw-r--r--README.md20
-rw-r--r--ci/common/test.sh2
-rw-r--r--contrib/flake.nix1
-rw-r--r--runtime/autoload/health/provider.vim10
-rw-r--r--runtime/autoload/tutor.vim4
-rw-r--r--runtime/delmenu.vim30
-rw-r--r--runtime/doc/api.txt7
-rw-r--r--runtime/doc/channel.txt77
-rw-r--r--runtime/doc/eval.txt29
-rw-r--r--runtime/doc/lsp.txt61
-rw-r--r--runtime/doc/lua.txt14
-rw-r--r--runtime/doc/options.txt8
-rw-r--r--runtime/doc/provider.txt2
-rw-r--r--runtime/doc/treesitter.txt524
-rw-r--r--runtime/doc/usr_41.txt1
-rw-r--r--runtime/filetype.vim19
-rw-r--r--runtime/lua/vim/lsp.lua360
-rw-r--r--runtime/lua/vim/lsp/buf.lua111
-rw-r--r--runtime/lua/vim/lsp/diagnostic.lua14
-rw-r--r--runtime/lua/vim/lsp/rpc.lua2
-rw-r--r--runtime/lua/vim/lsp/util.lua107
-rw-r--r--runtime/lua/vim/treesitter.lua38
-rw-r--r--runtime/lua/vim/treesitter/health.lua4
-rw-r--r--runtime/lua/vim/treesitter/highlighter.lua22
-rw-r--r--runtime/lua/vim/treesitter/language.lua20
-rw-r--r--runtime/lua/vim/treesitter/languagetree.lua226
-rw-r--r--runtime/lua/vim/treesitter/query.lua173
-rw-r--r--runtime/menu.vim10
-rwxr-xr-xscripts/gen_vimdoc.py76
-rw-r--r--scripts/lua2dox.lua21
-rwxr-xr-xscripts/pvscheck.sh2
-rw-r--r--src/nvim/CMakeLists.txt2
-rw-r--r--src/nvim/api/buffer.c37
-rw-r--r--src/nvim/api/private/helpers.c1
-rw-r--r--src/nvim/api/vim.c11
-rw-r--r--src/nvim/api/window.c2
-rw-r--r--src/nvim/buffer.c2
-rw-r--r--src/nvim/change.c4
-rw-r--r--src/nvim/decoration.c69
-rw-r--r--src/nvim/decoration.h22
-rw-r--r--src/nvim/edit.c19
-rw-r--r--src/nvim/eval.c143
-rw-r--r--src/nvim/eval.h22
-rw-r--r--src/nvim/eval.lua1
-rw-r--r--src/nvim/eval/funcs.c72
-rw-r--r--src/nvim/eval/userfunc.c11
-rw-r--r--src/nvim/event/libuv_process.c2
-rw-r--r--src/nvim/ex_cmds.c11
-rw-r--r--src/nvim/ex_cmds.lua6
-rw-r--r--src/nvim/ex_cmds2.c17
-rw-r--r--src/nvim/ex_docmd.c1
-rw-r--r--src/nvim/ex_eval.c9
-rw-r--r--src/nvim/ex_session.c68
-rw-r--r--src/nvim/extmark.c1
-rw-r--r--src/nvim/fileio.c8
-rw-r--r--src/nvim/lua/executor.c8
-rw-r--r--src/nvim/lua/vim.lua20
-rw-r--r--src/nvim/main.c2
-rw-r--r--src/nvim/message.c12
-rw-r--r--src/nvim/msgpack_rpc/channel.c2
-rw-r--r--src/nvim/normal.c4
-rw-r--r--src/nvim/ops.c8
-rw-r--r--src/nvim/option.c16
-rw-r--r--src/nvim/os/pty_process_unix.c4
-rw-r--r--src/nvim/os/pty_process_win.c6
-rw-r--r--src/nvim/os/time.c1
-rw-r--r--src/nvim/regexp.c7
-rw-r--r--src/nvim/screen.c109
-rw-r--r--src/nvim/search.c9
-rw-r--r--src/nvim/sign.c24
-rw-r--r--src/nvim/syntax.c22
-rw-r--r--src/nvim/tag.c4
-rw-r--r--src/nvim/testdir/test_alot.vim1
-rw-r--r--src/nvim/testdir/test_alot_utf8.vim1
-rw-r--r--src/nvim/testdir/test_autocmd.vim14
-rw-r--r--src/nvim/testdir/test_backspace_opt.vim20
-rw-r--r--src/nvim/testdir/test_excmd.vim2
-rw-r--r--src/nvim/testdir/test_expr.vim9
-rw-r--r--src/nvim/testdir/test_filetype.vim3
-rw-r--r--src/nvim/testdir/test_functions.vim16
-rw-r--r--src/nvim/testdir/test_menu.vim6
-rw-r--r--src/nvim/testdir/test_mksession.vim18
-rw-r--r--src/nvim/testdir/test_prompt_buffer.vim195
-rw-r--r--src/nvim/testdir/test_search.vim533
-rw-r--r--src/nvim/testdir/test_shift.vim117
-rw-r--r--src/nvim/testdir/test_signs.vim21
-rw-r--r--src/nvim/testdir/test_syntax.vim45
-rw-r--r--src/nvim/ui_compositor.c3
-rw-r--r--src/nvim/window.c3
-rw-r--r--test/functional/api/vim_spec.lua38
-rw-r--r--test/functional/autoread/focus_spec.lua7
-rw-r--r--test/functional/lua/buffer_updates_spec.lua116
-rw-r--r--test/functional/plugin/lsp_spec.lua141
-rw-r--r--test/functional/treesitter/highlight_spec.lua2
-rw-r--r--test/functional/treesitter/parser_spec.lua59
-rw-r--r--test/functional/ui/cmdline_spec.lua1
-rw-r--r--test/functional/ui/decorations_spec.lua151
-rw-r--r--test/functional/ui/float_spec.lua253
-rw-r--r--test/functional/ui/sign_spec.lua18
-rw-r--r--test/helpers.lua6
-rw-r--r--third-party/CMakeLists.txt6
106 files changed, 3553 insertions, 1086 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index bd90aeb932..2f2b3f102f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -13,7 +13,7 @@ jobs:
matrix:
include:
- flavor: asan
- cc: clang-11
+ cc: clang-12
runner: ubuntu-20.04
os: linux
- flavor: lint
@@ -38,12 +38,6 @@ jobs:
- name: Setup commom environment variables
run: ./.github/workflows/env.sh ${{ matrix.flavor }}
- - name: Setup clang repository
- if: matrix.flavor == 'asan' || matrix.flavor == 'tsan'
- run: |
- wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
- sudo add-apt-repository 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-11 main'
-
- name: Install apt packages
if: matrix.os == 'linux'
run: |
@@ -52,7 +46,11 @@ jobs:
- name: Install new clang
if: matrix.flavor == 'asan' || matrix.flavor == 'tsan'
- run: sudo apt-get install -y clang-11
+ run: |
+ wget https://apt.llvm.org/llvm.sh
+ chmod a+x llvm.sh
+ sudo ./llvm.sh 12
+ rm llvm.sh
- name: Install brew packages
if: matrix.os == 'osx'
@@ -91,8 +89,8 @@ jobs:
runs-on: windows-2016
if: github.event.pull_request.draft == false
env:
- DEPS_BUILD_DIR: "C:/projects/nvim-deps"
- DEPS_PREFIX: "C:/projects/nvim-deps/usr"
+ DEPS_BUILD_DIR: ${{ format('{0}/nvim-deps', github.workspace) }}
+ DEPS_PREFIX: ${{ format('{0}/nvim-deps/usr', github.workspace) }}
strategy:
fail-fast: false
@@ -104,7 +102,7 @@ jobs:
- uses: actions/cache@v2
with:
- path: C:\projects\nvim-deps
+ path: ${{ env.DEPS_BUILD_DIR }}
key: ${{ matrix.config }}-${{ hashFiles('third-party\**') }}
- name: Run CI
diff --git a/.github/workflows/env.sh b/.github/workflows/env.sh
index cc1cef5cc4..459ed669eb 100755
--- a/.github/workflows/env.sh
+++ b/.github/workflows/env.sh
@@ -34,7 +34,7 @@ case "$FLAVOR" in
BUILD_FLAGS="$BUILD_FLAGS -DPREFER_LUA=ON"
cat <<EOF >> "$GITHUB_ENV"
CLANG_SANITIZER=ASAN_UBSAN
-SYMBOLIZER=asan_symbolize-11
+SYMBOLIZER=asan_symbolize-12
ASAN_OPTIONS=detect_leaks=1:check_initialization_order=1:log_path=$GITHUB_WORKSPACE/build/log/asan
UBSAN_OPTIONS=print_stacktrace=1 log_path=$GITHUB_WORKSPACE/build/log/ubsan
EOF
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 43fe1d5101..e5064760d2 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -110,8 +110,8 @@ jobs:
windows:
runs-on: windows-2016
env:
- DEPS_BUILD_DIR: "C:/projects/nvim-deps"
- DEPS_PREFIX: "C:/projects/nvim-deps/usr"
+ DEPS_BUILD_DIR: ${{ format('{0}/nvim-deps', github.workspace) }}
+ DEPS_PREFIX: ${{ format('{0}/nvim-deps/usr', github.workspace) }}
strategy:
matrix:
include:
@@ -137,6 +137,8 @@ jobs:
publish:
needs: [linux, appimage, macOS, windows]
runs-on: ubuntu-20.04
+ permissions:
+ contents: write
steps:
- uses: actions/download-artifact@v2
- if: github.event_name == 'workflow_dispatch'
diff --git a/.github/workflows/nightly.yaml b/.github/workflows/vim-patches.yml
index 431ccd8b61..5742b51158 100644
--- a/.github/workflows/nightly.yaml
+++ b/.github/workflows/vim-patches.yml
@@ -1,4 +1,4 @@
-name: Nightly
+name: vim-patches
on:
schedule:
- cron: '3 3 * * *'
@@ -6,6 +6,9 @@ on:
jobs:
update-vim-patches:
runs-on: ubuntu-20.04
+ permissions:
+ contents: write
+ pull-requests: write
env:
VIM_SOURCE_DIR: ${{ format('{0}/vim-src', github.workspace) }}
VERSION_BRANCH: marvim/ci-version-update
@@ -46,4 +49,4 @@ jobs:
git add -u
git commit -m 'version.c: update [skip ci]'
git push --force https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY} ${VERSION_BRANCH}
- gh pr create --fill --label vim-patch --base master --head ${VERSION_BRANCH} || true
+ gh pr create --fill --label vim-patch --base ${GITHUB_REF#refs/heads/} --head ${VERSION_BRANCH} || true
diff --git a/.gitignore b/.gitignore
index 670340a519..c1726ede14 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,7 +29,7 @@ compile_commands.json
/.nvimlog
# Generated by scripts/vim-patch.sh
-/.vim-src/
+/.vim-src
# Generated by old (Vim) tests.
/src/nvim/testdir/del
@@ -66,3 +66,6 @@ tags
# Generated by gen_vimdoc.py:
/runtime/doc/*.mpack
/tmp-*-doc
+
+# vim patches
+/vim-*.patch
diff --git a/README.md b/README.md
index f4d92b77a1..ddd6c6a60b 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,10 @@ Pre-built packages for Windows, macOS, and Linux are found on the
Install from source
-------------------
+See the [Building Neovim](https://github.com/neovim/neovim/wiki/Building-Neovim) wiki page for details.
+
The build is CMake-based, but a Makefile is provided as a convenience.
+After installing the dependencies, run the following command.
make CMAKE_BUILD_TYPE=RelWithDebInfo
sudo make install
@@ -62,29 +65,12 @@ To install to a non-default location:
make CMAKE_INSTALL_PREFIX=/full/path/
make install
-To skip bundled (`third-party/*`) dependencies:
-
-1. Install the dependencies using a package manager.
- ```
- sudo apt install gperf luajit luarocks libuv1-dev libluajit-5.1-dev libunibilium-dev libmsgpack-dev libtermkey-dev libvterm-dev libutf8proc-dev
- sudo luarocks build mpack
- sudo luarocks build lpeg
- sudo luarocks build inspect
- ```
-2. Build with `USE_BUNDLED=OFF`:
- ```
- make CMAKE_BUILD_TYPE=RelWithDebInfo USE_BUNDLED=OFF
- sudo make install
- ```
-
To inspect the build, these CMake features are useful:
- `cmake --build build --target help` lists all build targets.
- `build/CMakeCache.txt` (or `cmake -LAH build/`) contains the resolved values of all CMake variables.
- `build/compile_commands.json` shows the full compiler invocations for each translation unit.
-See the [Building Neovim](https://github.com/neovim/neovim/wiki/Building-Neovim) wiki page for details.
-
Transitioning from Vim
--------------------
diff --git a/ci/common/test.sh b/ci/common/test.sh
index 118e181dfa..92c15c8ba1 100644
--- a/ci/common/test.sh
+++ b/ci/common/test.sh
@@ -34,7 +34,7 @@ check_core_dumps() {
cores="$(find /cores/ -type f -print)"
local _sudo='sudo'
else
- cores="$(find ./ -type f -name 'core.*' -print)"
+ cores="$(find ./ -type f \( -name 'core.*' -o -name core -o -name nvim.core \) -print)"
local _sudo=
fi
diff --git a/contrib/flake.nix b/contrib/flake.nix
index c86bba6809..e75ff0356b 100644
--- a/contrib/flake.nix
+++ b/contrib/flake.nix
@@ -72,6 +72,7 @@
jq # jq for scripts/vim-patch.sh -r
shellcheck # for `make shlint`
doxygen # for script/gen_vimdoc.py
+ clang-tools # for clangd to find the correct headers
]);
shellHook = oa.shellHook + ''
diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim
index 112dd4354f..de540405e6 100644
--- a/runtime/autoload/health/provider.vim
+++ b/runtime/autoload/health/provider.vim
@@ -400,8 +400,6 @@ function! s:check_python(version) abort
endfor
endif
- let pip = 'pip' . (a:version == 2 ? '' : '3')
-
if empty(python_exe)
" No Python executable can import 'neovim'. Check if any Python executable
" can import 'pynvim'. If so, that Python failed to import 'neovim' as
@@ -413,9 +411,9 @@ function! s:check_python(version) abort
\ 'Detected pip upgrade failure: Python executable can import "pynvim" but '
\ . 'not "neovim": '. pynvim_exe,
\ "Use that Python version to reinstall \"pynvim\" and optionally \"neovim\".\n"
- \ . pip ." uninstall pynvim neovim\n"
- \ . pip ." install pynvim\n"
- \ . pip ." install neovim # only if needed by third-party software")
+ \ . pynvim_exe ." -m pip uninstall pynvim neovim\n"
+ \ . pynvim_exe ." -m pip install pynvim\n"
+ \ . pynvim_exe ." -m pip install neovim # only if needed by third-party software")
endif
else
let [pyversion, current, latest, status] = s:version_info(python_exe)
@@ -440,7 +438,7 @@ function! s:check_python(version) abort
if s:is_bad_response(current)
call health#report_error(
\ "pynvim is not installed.\nError: ".current,
- \ ['Run in shell: '. pip .' install pynvim'])
+ \ ['Run in shell: '. python_exe .' -m pip install pynvim'])
endif
if s:is_bad_response(latest)
diff --git a/runtime/autoload/tutor.vim b/runtime/autoload/tutor.vim
index 6afe64de84..abf5c5e2c8 100644
--- a/runtime/autoload/tutor.vim
+++ b/runtime/autoload/tutor.vim
@@ -104,6 +104,10 @@ function! tutor#CheckLine(line)
if exists('b:tutor_metadata') && has_key(b:tutor_metadata, 'expect')
let bufn = bufnr('%')
let ctext = getline(a:line)
+ let signs = sign_getplaced('.', {'lnum': a:line})[0].signs
+ if !empty(signs)
+ call sign_unplace('', {'id': signs[0].id})
+ endif
if b:tutor_metadata['expect'][string(a:line)] == -1 || ctext ==# b:tutor_metadata['expect'][string(a:line)]
exe "sign place ".b:tutor_sign_id." line=".a:line." name=tutorok buffer=".bufn
else
diff --git a/runtime/delmenu.vim b/runtime/delmenu.vim
index 81df87d346..5c20290152 100644
--- a/runtime/delmenu.vim
+++ b/runtime/delmenu.vim
@@ -2,24 +2,30 @@
" Warning: This also deletes all menus defined by the user!
"
" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2001 May 27
+" Last Change: 2019 Dec 10
aunmenu *
-silent! unlet did_install_default_menus
-silent! unlet did_install_syntax_menu
-if exists("did_menu_trans")
+unlet! g:did_install_default_menus
+unlet! g:did_install_syntax_menu
+
+if exists('g:did_menu_trans')
menutrans clear
- unlet did_menu_trans
+ unlet g:did_menu_trans
endif
-silent! unlet find_help_dialog
+unlet! g:find_help_dialog
-silent! unlet menutrans_help_dialog
-silent! unlet menutrans_path_dialog
-silent! unlet menutrans_tags_dialog
-silent! unlet menutrans_textwidth_dialog
-silent! unlet menutrans_fileformat_dialog
-silent! unlet menutrans_no_file
+unlet! g:menutrans_fileformat_choices
+unlet! g:menutrans_fileformat_dialog
+unlet! g:menutrans_help_dialog
+unlet! g:menutrans_no_file
+unlet! g:menutrans_path_dialog
+unlet! g:menutrans_set_lang_to
+unlet! g:menutrans_spell_add_ARG_to_word_list
+unlet! g:menutrans_spell_change_ARG_to
+unlet! g:menutrans_spell_ignore_ARG
+unlet! g:menutrans_tags_dialog
+unlet! g:menutrans_textwidth_dialog
" vim: set sw=2 :
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index 0c17fa1669..0d85d6b539 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -1212,6 +1212,9 @@ nvim_open_term({buffer}, {opts}) *nvim_open_term()*
{buffer} the buffer to use (expected to be empty)
{opts} Optional parameters. Reserved for future use.
+ Return: ~
+ Channel id, or 0 on error
+
nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()*
Open a new window.
@@ -2280,6 +2283,10 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {opts})
color
• "blend": blend with background text color.
+ • hl_eol : when true, for a multiline highlight
+ covering the EOL of a line, continue the
+ highlight for the rest of the screen line
+ (just like for diff and cursorline highlight).
• ephemeral : for use with
|nvim_set_decoration_provider| callbacks. The
mark will only be used for the current redraw
diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt
index 967f4b26f2..656bb10c45 100644
--- a/runtime/doc/channel.txt
+++ b/runtime/doc/channel.txt
@@ -174,4 +174,81 @@ Put this in `uppercase.vim` and run: >
nvim --headless --cmd "source uppercase.vim"
==============================================================================
+5. Using a prompt buffer *prompt-buffer*
+
+If you want to type input for the job in a Vim window you have a few options:
+- Use a normal buffer and handle all possible commands yourself.
+ This will be complicated, since there are so many possible commands.
+- Use a terminal window. This works well if what you type goes directly to
+ the job and the job output is directly displayed in the window.
+ See |terminal|.
+- Use a window with a prompt buffer. This works well when entering a line for
+ the job in Vim while displaying (possibly filtered) output from the job.
+
+A prompt buffer is created by setting 'buftype' to "prompt". You would
+normally only do that in a newly created buffer.
+
+The user can edit and enter one line of text at the very last line of the
+buffer. When pressing Enter in the prompt line the callback set with
+|prompt_setcallback()| is invoked. It would normally send the line to a job.
+Another callback would receive the output from the job and display it in the
+buffer, below the prompt (and above the next prompt).
+
+Only the text in the last line, after the prompt, is editable. The rest of the
+buffer is not modifiable with Normal mode commands. It can be modified by
+calling functions, such as |append()|. Using other commands may mess up the
+buffer.
+
+After setting 'buftype' to "prompt" Vim does not automatically start Insert
+mode, use `:startinsert` if you want to enter Insert mode, so that the user
+can start typing a line.
+
+The text of the prompt can be set with the |prompt_setprompt()| function. If
+no prompt is set with |prompt_setprompt()|, "% " is used. You can get the
+effective prompt text for a buffer, with |prompt_getprompt()|.
+
+The user can go to Normal mode and navigate through the buffer. This can be
+useful to see older output or copy text.
+
+Any command that starts Insert mode, such as "a", "i", "A" and "I", will move
+the cursor to the last line. "A" will move to the end of the line, "I" to the
+start of the line.
+
+Here is an example for Unix. It starts a shell in the background and prompts
+for the next shell command. Output from the shell is displayed above the
+prompt. >
+
+ " Function handling a line of text that has been typed.
+ func TextEntered(text)
+ " Send the text to a shell with Enter appended.
+ call chansend(g:shell_job, [a:text, ''])
+ endfunc
+
+ " Function handling output from the shell: Added above the prompt.
+ func GotOutput(channel, msg, name)
+ call append(line("$") - 1, a:msg)
+ endfunc
+
+ " Function handling the shell exit: close the window.
+ func JobExit(job, status, event)
+ quit!
+ endfunc
+
+ " Start a shell in the background.
+ let shell_job = jobstart(["/bin/sh"], #{
+ \ on_stdout: function('GotOutput'),
+ \ on_stderr: function('GotOutput'),
+ \ on_exit: function('JobExit'),
+ \ })
+
+ new
+ set buftype=prompt
+ let buf = bufnr('')
+ call prompt_setcallback(buf, function("TextEntered"))
+ call prompt_setprompt(buf, "shell command: ")
+
+ " start accepting shell commands
+ startinsert
+<
+
vim:tw=78:ts=8:noet:ft=help:norl:
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index c3736d9a3e..b8dcfd0ff4 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -2309,6 +2309,7 @@ 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
+prompt_getprompt({buf}) String get prompt text
prompt_setcallback({buf}, {expr}) none set prompt callback function
prompt_setinterrupt({buf}, {text}) none set prompt interrupt function
prompt_setprompt({buf}, {text}) none set prompt text
@@ -5361,9 +5362,8 @@ input({opts})
prompt "" Same as {prompt} in the first form.
default "" Same as {text} in the first form.
completion nothing Same as {completion} in the first form.
- cancelreturn "" Same as {cancelreturn} from
- |inputdialog()|. Also works with
- input().
+ cancelreturn "" The value returned when the dialog is
+ cancelled.
highlight nothing Highlight handler: |Funcref|.
The highlighting set with |:echohl| is used for the prompt.
@@ -6747,6 +6747,13 @@ printf({fmt}, {expr1} ...) *printf()*
of "%" items. If there are not sufficient or too many
arguments an error is given. Up to 18 arguments can be used.
+prompt_getprompt({buf}) *prompt_getprompt()*
+ Returns the effective prompt text for buffer {buf}. {buf} can
+ be a buffer name or number. See |prompt-buffer|.
+
+ If the buffer doesn't exist or isn't a prompt buffer, an empty
+ string is returned.
+
prompt_setcallback({buf}, {expr}) *prompt_setcallback()*
Set prompt callback for buffer {buf} to {expr}. When {expr}
is an empty string the callback is removed. This has only
@@ -10099,6 +10106,8 @@ This function can then be called with: >
The recursiveness of user functions is restricted with the |'maxfuncdepth'|
option.
+It is also possible to use `:eval`. It does not support a range.
+
AUTOMATICALLY LOADING FUNCTIONS ~
*autoload-functions*
@@ -10532,6 +10541,20 @@ text...
Unlock the internal variable {name}. Does the
opposite of |:lockvar|.
+ *:eval*
+:eval {expr} Evaluate {expr} and discard the result. Example: >
+ :eval append(Filter(Getlist()), '$')
+
+< The expression is supposed to have a side effect,
+ since the resulting value is not used. In the example
+ the `append()` call appends the List with text to the
+ buffer. This is similar to `:call` but works with any
+ expression.
+
+ The command can be shortened to `:ev` or `:eva`, but
+ these are hard to recognize and therefore not to be
+ used.
+
:if {expr1} *:if* *:end* *:endif* *:en* *E171* *E579* *E580*
:en[dif] Execute the commands until the next matching ":else"
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index 3c0dbf96c5..5c2ee568c5 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -592,14 +592,34 @@ buf_request({bufnr}, {method}, {params}, {handler})
You could instead iterate all clients and call their
`cancel_request()` methods.
+ *vim.lsp.buf_request_all()*
+buf_request_all({bufnr}, {method}, {params}, {callback})
+ Sends an async request for all active clients attached to the
+ buffer. Executes the callback on the combined result.
+ Parameters are the same as |vim.lsp.buf_request()| but the
+ return result and callback are different.
+
+ Parameters: ~
+ {bufnr} (number) Buffer handle, or 0 for current.
+ {method} (string) LSP method name
+ {params} (optional, table) Parameters to send to the
+ server
+ {callback} (function) The callback to call when all
+ requests are finished.
+
+ Return: ~
+ (function) A function that will cancel all requests which
+ is the same as the one returned from `buf_request` .
+
*vim.lsp.buf_request_sync()*
buf_request_sync({bufnr}, {method}, {params}, {timeout_ms})
- Sends a request to a server and waits for the response.
+ Sends a request to all server and waits for the response of
+ all of them.
- Calls |vim.lsp.buf_request()| but blocks Nvim while awaiting
- the result. Parameters are the same as |vim.lsp.buf_request()|
- but the return result is different. Wait maximum of
- {timeout_ms} (default 100) ms.
+ Calls |vim.lsp.buf_request_all()| but blocks Nvim while
+ awaiting the result. Parameters are the same as
+ |vim.lsp.buf_request()| but the return result is different.
+ Wait maximum of {timeout_ms} (default 100) ms.
Parameters: ~
{bufnr} (number) Buffer handle, or 0 for current.
@@ -678,6 +698,9 @@ client_is_stopped({client_id}) *vim.lsp.client_is_stopped()*
Return: ~
true if client is stopped, false otherwise.
+flush({client}) *vim.lsp.flush()*
+ TODO: Documentation
+
get_active_clients() *vim.lsp.get_active_clients()*
Gets all active clients.
@@ -708,6 +731,15 @@ get_log_path() *vim.lsp.get_log_path()*
Return: ~
(String) Path to logfile.
+init({client}, {bufnr}) *vim.lsp.init()*
+ client_id → state
+
+ state pending_change?: function that the timer starts to
+ trigger didChange pending_changes: list of tables with the
+ pending changesets; for incremental_sync only
+ use_incremental_sync: bool buffers?: table (bufnr → lines);
+ for incremental sync only timer?: uv_timer
+
omnifunc({findstart}, {base}) *vim.lsp.omnifunc()*
Implements 'omnifunc' compatible LSP completion.
@@ -727,6 +759,16 @@ omnifunc({findstart}, {base}) *vim.lsp.omnifunc()*
|complete-items|
|CompleteDone|
+ *vim.lsp.prepare()*
+prepare({bufnr}, {firstline}, {new_lastline}, {changedtick})
+ TODO: Documentation
+
+reset({client_id}) *vim.lsp.reset()*
+ TODO: Documentation
+
+reset_buf({client}, {bufnr}) *vim.lsp.reset_buf()*
+ TODO: Documentation
+
set_log_level({level}) *vim.lsp.set_log_level()*
Sets the global log level for LSP logging.
@@ -849,6 +891,11 @@ start_client({config}) *vim.lsp.start_client()*
• allow_incremental_sync (bool, default
true): Allow using incremental sync
for buffer edits
+ • debounce_text_changes (number,
+ default nil): Debounce didChange
+ notifications to the server by the
+ given number in milliseconds. No
+ debounce occurs if nil
Return: ~
Client id. |vim.lsp.get_client_by_id()| Note: client may
@@ -1311,6 +1358,10 @@ on_publish_diagnostics({_}, {_}, {params}, {client_id}, {_}, {config})
• Update diagnostics in InsertMode or wait
until InsertLeave
+ • severity_sort: (default=false)
+ • Sort diagnostics (and thus signs and virtual
+ text)
+
reset({client_id}, {buffer_client_map}) *vim.lsp.diagnostic.reset()*
Clear diagnotics and diagnostic cache
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index c2fc25431c..be01966d42 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -18,7 +18,8 @@ an idea of what lurks beneath: >
Nvim includes a "standard library" |lua-stdlib| for Lua. It complements the
"editor stdlib" (|functions| and Ex commands) and the |API|, all of which can
-be used from Lua code.
+be used from Lua code. A good overview of using Lua in neovim is given by
+https://github.com/nanotee/nvim-lua-guide.
Module conflicts are resolved by "last wins". For example if both of these
are on 'runtimepath':
@@ -831,6 +832,7 @@ LUA-VIMSCRIPT BRIDGE *lua-vimscript*
Nvim Lua provides an interface to Vimscript variables and functions, and
editor commands and options.
+See also https://github.com/nanotee/nvim-lua-guide.
vim.call({func}, {...}) *vim.call()*
Invokes |vim-function| or |user-function| {func} with arguments {...}.
@@ -839,10 +841,18 @@ vim.call({func}, {...}) *vim.call()*
vim.fn[func]({...})
vim.cmd({cmd}) *vim.cmd()*
- Invokes an Ex command (the ":" commands, Vimscript statements).
+ Executes multiple lines of Vimscript at once. It is an alias to
+ |nvim_exec()|, where `output` is set to false. Thus it works identical
+ to |:source|.
See also |ex-cmd-index|.
Example: >
vim.cmd('echo 42')
+ vim.cmd([[
+ augroup My_group
+ autocmd!
+ autocmd FileType c setlocal cindent
+ augroup END
+ ]])
vim.fn.{func}({...}) *vim.fn*
Invokes |vim-function| or |user-function| {func} with arguments {...}.
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 04310ca8d4..63a9db9d0d 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -4818,7 +4818,7 @@ A jump table for the options with a short description can be found at |Q_op|.
|xdg| ($XDG_CONFIG_DIRS, defaults to /etc/xdg). This also contains
preferences from system administrator.
3. Data home directory, for plugins installed by user.
- Given by `stdpath("data")`. |$XDG_DATA_HOME|
+ Given by `stdpath("data")/site`. |$XDG_DATA_HOME|
4. nvim/site subdirectories for each directory in $XDG_DATA_DIRS.
This is for plugins which were installed by system administrator,
but are not part of the Nvim distribution. XDG_DATA_DIRS defaults
@@ -5555,6 +5555,12 @@ A jump table for the options with a short description can be found at |Q_op|.
"number" display signs in the 'number' column. If the number
column is not present, then behaves like 'auto'.
+ Note regarding 'orphaned signs': with signcolumn numbers higher than
+ 1, deleting lines will also remove the associated signs automatically,
+ in contrast to the default Vim behavior of keeping and grouping them.
+ This is done in order for the signcolumn appearence not appear weird
+ during line deletion.
+
*'smartcase'* *'scs'* *'nosmartcase'* *'noscs'*
'smartcase' 'scs' boolean (default off)
diff --git a/runtime/doc/provider.txt b/runtime/doc/provider.txt
index f944689d0b..be895f9e4e 100644
--- a/runtime/doc/provider.txt
+++ b/runtime/doc/provider.txt
@@ -88,7 +88,7 @@ Example using pyenv: >
pyenv install 3.4.4
pyenv virtualenv 3.4.4 py3nvim
pyenv activate py3nvim
- pip install pynvim
+ python3 -m pip install pynvim
pyenv which python # Note the path
The last command reports the interpreter path, add it to your init.vim: >
let g:python3_host_prog = '/path/to/py3nvim/bin/python'
diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt
index 343f4a62c2..1f4b5d3097 100644
--- a/runtime/doc/treesitter.txt
+++ b/runtime/doc/treesitter.txt
@@ -102,14 +102,14 @@ tsnode:field({name}) *tsnode:field()*
tsnode:child_count() *tsnode:child_count()*
Get the node's number of children.
-tsnode:child({index}) *tsnode:child()*
+tsnode:child({index}) *tsnode:child()*
Get the node's child at the given {index}, where zero represents the
first child.
-tsnode:named_child_count() *tsnode:named_child_count()*
+tsnode:named_child_count() *tsnode:named_child_count()*
Get the node's number of named children.
-tsnode:named_child({index}) *tsnode:named_child()*
+tsnode:named_child({index}) *tsnode:named_child()*
Get the node's named child at the given {index}, where zero represents
the first named child.
@@ -146,7 +146,7 @@ tsnode:has_error() *tsnode:has_error()*
tsnode:sexpr() *tsnode:sexpr()*
Get an S-expression representing the node as a string.
-tsnode:id() *tsnode:id()*
+tsnode:id() *tsnode:id()*
Get an unique identier for the node inside its own tree.
No guarantees are made about this identifer's internal representation,
@@ -156,16 +156,16 @@ tsnode:id() *tsnode:id()*
NB: the id is not guaranteed to be unique for nodes from different trees.
tsnode:descendant_for_range({start_row}, {start_col}, {end_row}, {end_col})
- *tsnode:descendant_for_range()*
+ *tsnode:descendant_for_range()*
Get the smallest node within this node that spans the given range of
(row, column) positions
tsnode:named_descendant_for_range({start_row}, {start_col}, {end_row}, {end_col})
- *tsnode:named_descendant_for_range()*
+ *tsnode:named_descendant_for_range()*
Get the smallest named node within this node that spans the given
range of (row, column) positions
-Query methods *lua-treesitter-query*
+Query *lua-treesitter-query*
Tree-sitter queries are supported, with some limitations. Currently, the only
supported match predicate is `eq?` (both comparing a capture against a string
@@ -178,65 +178,6 @@ and predicates. A `capture` allows you to associate names with a specific
node in a pattern. A `predicate` adds arbitrary metadata and conditional data
to a match.
-vim.treesitter.parse_query({lang}, {query})
- *vim.treesitter.parse_query()*
- Parse {query} as a string. (If the query is in a file, the caller
- should read the contents into a string before calling).
-
- Returns a `Query` (see |lua-treesitter-query|) object which can be used to
- search nodes in the syntax tree for the patterns defined in {query}
- using `iter_*` methods below. Exposes `info` and `captures` with
- additional information about the {query}.
- - `captures` contains the list of unique capture names defined in
- {query}.
- -` info.captures` also points to `captures`.
- - `info.patterns` contains information about predicates.
-
-
-query:iter_captures({node}, {bufnr}, {start_row}, {end_row})
- *query:iter_captures()*
- Iterate over all captures from all matches inside {node}.
- {bufnr} is needed if the query contains predicates, then the caller
- must ensure to use a freshly parsed tree consistent with the current
- text of the buffer. {start_row} and {end_row} can be used to limit
- matches inside a row range (this is typically used with root node
- as the node, i e to get syntax highlight matches in the current
- viewport). When omitted the start and end row values are used from
- the given node.
-
- The iterator returns three values, a numeric id identifying the capture,
- the captured node, and metadata from any directives processing the match.
- The following example shows how to get captures by name:
->
- for id, node, metadata in query:iter_captures(tree:root(), bufnr, first, last) do
- local name = query.captures[id] -- name of the capture in the query
- -- typically useful info about the node:
- local type = node:type() -- type of the captured node
- local row1, col1, row2, col2 = node:range() -- range of the capture
- ... use the info here ...
- end
-<
-query:iter_matches({node}, {bufnr}, {start_row}, {end_row})
- *query:iter_matches()*
- Iterate over all matches within a node. The arguments are the same as
- for |query:iter_captures()| but the iterated values are different:
- an (1-based) index of the pattern in the query, a table mapping
- capture indices to nodes, and metadata from any directives processing the match.
- If the query has more than one pattern the capture table might be sparse,
- and e.g. `pairs()` method should be used over `ipairs`.
- Here an example iterating over all captures in every match:
->
- for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, first, last) do
- for id, node in pairs(match) do
- local name = query.captures[id]
- -- `node` was captured by the `name` capture in the match
-
- local node_data = metadata[id] -- Node level metadata
-
- ... use the info here ...
- end
- end
-
Treesitter Query Predicates *lua-treesitter-predicates*
When writing queries for treesitter, one might use `predicates`, that is,
@@ -298,28 +239,6 @@ Here is a list of built-in directives:
`({capture_id}, {start_row}, {start_col}, {end_row}, {end_col}, {key?})`
The default key is "offset".
- *vim.treesitter.query.add_predicate()*
-vim.treesitter.query.add_predicate({name}, {handler})
-
-This adds a predicate with the name {name} to be used in queries.
-{handler} should be a function whose signature will be : >
- handler(match, pattern, bufnr, predicate)
-<
- *vim.treesitter.query.list_predicates()*
-vim.treesitter.query.list_predicates()
-
-This lists the currently available predicates to use in queries.
-
- *vim.treesitter.query.add_directive()*
-vim.treesitter.query.add_directive({name}, {handler})
-
-This adds a directive with the name {name} to be used in queries.
-{handler} should be a function whose signature will be : >
- handler(match, pattern, bufnr, predicate, metadata)
-Handlers can set match level data by setting directly on the metadata object `metadata.key = value`
-Handlers can set node level data by using the capture id on the metadata table
-`metadata[capture_id].key = value`
-
Treesitter syntax highlighting (WIP) *lua-treesitter-highlight*
NOTE: This is a partially implemented feature, and not usable as a default
@@ -364,5 +283,434 @@ identical identifiers, highlighting both as |hl-WarningMsg|: >
((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right)
(eq? @WarningMsg.left @WarningMsg.right))
+<
+
+==============================================================================
+Lua module: vim.treesitter *lua-treesitter-core*
+
+get_parser({bufnr}, {lang}, {opts}) *get_parser()*
+ Gets the parser for this bufnr / ft combination.
+
+ If needed this will create the parser. Unconditionnally attach
+ the provided callback
+
+ Parameters: ~
+ {bufnr} The buffer the parser should be tied to
+ {lang} The filetype of this parser
+ {opts} Options object to pass to the created language
+ tree
+
+ Return: ~
+ The parser
+
+get_string_parser({str}, {lang}, {opts}) *get_string_parser()*
+ Gets a string parser
+
+ Parameters: ~
+ {str} The string to parse
+ {lang} The language of this string
+ {opts} Options to pass to the created language tree
+
+
+==============================================================================
+Lua module: vim.treesitter.language *treesitter-language*
+
+inspect_language({lang}) *inspect_language()*
+ Inspects the provided language.
+
+ Inspecting provides some useful informations on the language
+ like node names, ...
+
+ Parameters: ~
+ {lang} The language.
+
+require_language({lang}, {path}, {silent}) *require_language()*
+ Asserts that the provided language is installed, and
+ optionally provide a path for the parser
+
+ Parsers are searched in the `parser` runtime directory.
+
+ Parameters: ~
+ {lang} The language the parser should parse
+ {path} Optional path the parser is located at
+ {silent} Don't throw an error if language not found
+
+
+==============================================================================
+Lua module: vim.treesitter.query *treesitter-query*
+
+add_directive({name}, {handler}, {force}) *add_directive()*
+ Adds a new directive to be used in queries
+
+ Parameters: ~
+ {name} the name of the directive, without leading #
+ {handler} the handler function to be used signature will
+ be (match, pattern, bufnr, predicate)
+
+add_predicate({name}, {handler}, {force}) *add_predicate()*
+ Adds a new predicate to be used in queries
+
+ Parameters: ~
+ {name} the name of the predicate, without leading #
+ {handler} the handler function to be used signature will
+ be (match, pattern, bufnr, predicate)
+
+get_node_text({node}, {source}) *get_node_text()*
+ Gets the text corresponding to a given node
+
+ Parameters: ~
+ {node} the node
+ {bsource} The buffer or string from which the node is
+ extracted
+
+get_query({lang}, {query_name}) *get_query()*
+ Returns the runtime query {query_name} for {lang}.
+
+ Parameters: ~
+ {lang} The language to use for the query
+ {query_name} The name of the query (i.e. "highlights")
+
+ Return: ~
+ The corresponding query, parsed.
+
+ *get_query_files()*
+get_query_files({lang}, {query_name}, {is_included})
+ Gets the list of files used to make up a query
+
+ Parameters: ~
+ {lang} The language
+ {query_name} The name of the query to load
+ {is_included} Internal parameter, most of the time left
+ as `nil`
+
+list_predicates() *list_predicates()*
+ TODO: Documentation
+
+parse_query({lang}, {query}) *parse_query()*
+ Parse {query} as a string. (If the query is in a file, the
+ caller should read the contents into a string before calling).
+
+ Returns a `Query` (see |lua-treesitter-query|) object which
+ can be used to search nodes in the syntax tree for the
+ patterns defined in {query} using `iter_*` methods below.
+
+ Exposes `info` and `captures` with additional information about the {query}.
+ • `captures` contains the list of unique capture names defined
+ in {query}. - `info.captures` also points to `captures` .
+ • `info.patterns` contains information about predicates.
+
+ Parameters: ~
+ {lang} The language
+ {query} A string containing the query (s-expr syntax)
+
+ Return: ~
+ The query
+
+ *Query:iter_captures()*
+Query:iter_captures({self}, {node}, {source}, {start}, {stop})
+ Iterate over all captures from all matches inside {node}
+
+ {source} is needed if the query contains predicates, then the
+ caller must ensure to use a freshly parsed tree consistent
+ with the current text of the buffer (if relevent). {start_row}
+ and {end_row} can be used to limit matches inside a row range
+ (this is typically used with root node as the node, i e to get
+ syntax highlight matches in the current viewport). When
+ omitted the start and end row values are used from the given
+ node.
+
+ The iterator returns three values, a numeric id identifying
+ the capture, the captured node, and metadata from any
+ directives processing the match. The following example shows
+ how to get captures by name:
+>
+
+ for id, node, metadata in query:iter_captures(tree:root(), bufnr, first, last) do
+ local name = query.captures[id] -- name of the capture in the query
+ -- typically useful info about the node:
+ local type = node:type() -- type of the captured node
+ local row1, col1, row2, col2 = node:range() -- range of the capture
+ ... use the info here ...
+ end
+<
+
+ Parameters: ~
+ {node} The node under which the search will occur
+ {source} The source buffer or string to exctract text
+ from
+ {start} The starting line of the search
+ {stop} The stopping line of the search (end-exclusive)
+ {self}
+
+ Return: ~
+ The matching capture id
+ The captured node
+
+ *Query:iter_matches()*
+Query:iter_matches({self}, {node}, {source}, {start}, {stop})
+ Iterates the matches of self on a given range.
+
+ Iterate over all matches within a node. The arguments are the
+ same as for |query:iter_captures()| but the iterated values
+ are different: an (1-based) index of the pattern in the query,
+ a table mapping capture indices to nodes, and metadata from
+ any directives processing the match. If the query has more
+ than one pattern the capture table might be sparse, and e.g.
+ `pairs()` method should be used over `ipairs` . Here an
+ example iterating over all captures in every match:
+>
+
+ for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, first, last) do
+ for id, node in pairs(match) do
+ local name = query.captures[id]
+ -- `node` was captured by the `name` capture in the match
+
+ local node_data = metadata[id] -- Node level metadata
+
+ ... use the info here ...
+ end
+ end
+<
+
+ Parameters: ~
+ {node} The node under which the search will occur
+ {source} The source buffer or string to search
+ {start} The starting line of the search
+ {stop} The stopping line of the search (end-exclusive)
+ {self}
+
+ Return: ~
+ The matching pattern id
+ The matching match
+
+set_query({lang}, {query_name}, {text}) *set_query()*
+ Sets the runtime query {query_name} for {lang}
+
+ This allows users to override any runtime files and/or
+ configuration set by plugins.
+
+ Parameters: ~
+ {lang} string: The language to use for the query
+ {query_name} string: The name of the query (i.e.
+ "highlights")
+ {text} string: The query text (unparsed).
+
+
+==============================================================================
+Lua module: vim.treesitter.highlighter *treesitter-highlighter*
+
+new({tree}, {opts}) *highlighter.new()*
+ Creates a new highlighter using
+
+ Parameters: ~
+ {tree} The language tree to use for highlighting
+ {opts} Table used to configure the highlighter
+ • queries: Table to overwrite queries used by the
+ highlighter
+
+TSHighlighter:destroy({self}) *TSHighlighter:destroy()*
+ Removes all internal references to the highlighter
+
+ Parameters: ~
+ {self}
+
+TSHighlighter:get_query({self}, {lang}) *TSHighlighter:get_query()*
+ Gets the query used for
+
+ Parameters: ~
+ {lang} A language used by the highlighter.
+ {self}
+
+
+==============================================================================
+Lua module: vim.treesitter.languagetree *treesitter-languagetree*
+
+LanguageTree:add_child({self}, {lang}) *LanguageTree:add_child()*
+ Adds a child language to this tree.
+
+ If the language already exists as a child, it will first be
+ removed.
+
+ Parameters: ~
+ {lang} The language to add.
+ {self}
+
+LanguageTree:children({self}) *LanguageTree:children()*
+ Returns a map of language to child tree.
+
+ Parameters: ~
+ {self}
+
+LanguageTree:contains({self}, {range}) *LanguageTree:contains()*
+ Determines wether This goes down the tree to recursively check childs.
+
+ Parameters: ~
+ {range} is contained in this language tree
+
+ Parameters: ~
+ {range} A range, that is a `{ start_line, start_col,
+ end_line, end_col }` table.
+ {self}
+
+LanguageTree:destroy({self}) *LanguageTree:destroy()*
+ Destroys this language tree and all its children.
+
+ Any cleanup logic should be performed here. Note, this DOES
+ NOT remove this tree from a parent. `remove_child` must be called on the parent to remove it.
+
+ Parameters: ~
+ {self}
+
+ *LanguageTree:for_each_child()*
+LanguageTree:for_each_child({self}, {fn}, {include_self})
+ Invokes the callback for each LanguageTree and it's children
+ recursively
+
+ Parameters: ~
+ {fn} The function to invoke. This is invoked
+ with arguments (tree: LanguageTree, lang:
+ string)
+ {include_self} Whether to include the invoking tree in
+ the results.
+ {self}
+
+LanguageTree:for_each_tree({self}, {fn}) *LanguageTree:for_each_tree()*
+ Invokes the callback for each treesitter trees recursively.
+
+ Note, this includes the invoking language tree's trees as
+ well.
+
+ Parameters: ~
+ {fn} The callback to invoke. The callback is invoked
+ with arguments (tree: TSTree, languageTree:
+ LanguageTree)
+ {self}
+
+LanguageTree:included_regions({self}) *LanguageTree:included_regions()*
+ Gets the set of included regions
+
+ Parameters: ~
+ {self}
+
+LanguageTree:invalidate({self}, {reload}) *LanguageTree:invalidate()*
+ Invalidates this parser and all its children
+
+ Parameters: ~
+ {self}
+
+LanguageTree:is_valid({self}) *LanguageTree:is_valid()*
+ Determines whether this tree is valid. If the tree is invalid, `parse()` must be called to get the an updated tree.
+
+ Parameters: ~
+ {self}
+
+LanguageTree:lang({self}) *LanguageTree:lang()*
+ Gets the language of this tree node.
+
+ Parameters: ~
+ {self}
+
+ *LanguageTree:language_for_range()*
+LanguageTree:language_for_range({self}, {range})
+ Gets the appropriate language that contains
+
+ Parameters: ~
+ {range} A text range, see |LanguageTree:contains|
+ {self}
+
+LanguageTree:parse({self}) *LanguageTree:parse()*
+ Parses all defined regions using a treesitter parser for the
+ language this tree represents. This will run the injection
+ query for this language to determine if any child languages
+ should be created.
+
+ Parameters: ~
+ {self}
+
+LanguageTree:register_cbs({self}, {cbs}) *LanguageTree:register_cbs()*
+ Registers callbacks for the parser
+
+ Parameters: ~
+ {cbs} An `nvim_buf_attach` -like table argument with the following keys : `on_bytes` : see `nvim_buf_attach` , but this will be called after the parsers callback. `on_changedtree` : a callback that will be called every time the
+ tree has syntactical changes. it will only be
+ passed one argument, that is a table of the ranges
+ (as node ranges) that changed. `on_child_added` : emitted when a child is added to the tree. `on_child_removed` : emitted when a child is removed from the tree.
+ {self}
+
+LanguageTree:remove_child({self}, {lang}) *LanguageTree:remove_child()*
+ Removes a child language from this tree.
+
+ Parameters: ~
+ {lang} The language to remove.
+ {self}
+
+ *LanguageTree:set_included_regions()*
+LanguageTree:set_included_regions({self}, {regions})
+ Sets the included regions that should be parsed by this
+ parser. A region is a set of nodes and/or ranges that will be
+ parsed in the same context.
+
+ For example, `{ { node1 }, { node2} }` is two separate
+ regions. This will be parsed by the parser in two different
+ contexts... thus resulting in two separate trees.
+
+ `{ { node1, node2 } }` is a single region consisting of two
+ nodes. This will be parsed by the parser in a single
+ context... thus resulting in a single tree.
+
+ This allows for embedded languages to be parsed together
+ across different nodes, which is useful for templating
+ languages like ERB and EJS.
+
+ Note, this call invalidates the tree and requires it to be
+ parsed again.
+
+ Parameters: ~
+ {regions} A list of regions this tree should manage and
+ parse.
+ {self}
+
+LanguageTree:source({self}) *LanguageTree:source()*
+ Returns the source content of the language tree (bufnr or
+ string).
+
+ Parameters: ~
+ {self}
+
+LanguageTree:trees({self}) *LanguageTree:trees()*
+ Returns all trees this language tree contains. Does not
+ include child languages.
+
+ Parameters: ~
+ {self}
+
+new({source}, {lang}, {opts}) *languagetree.new()*
+ Represents a single treesitter parser for a language. The
+ language can contain child languages with in its range, hence
+ the tree.
+
+ Parameters: ~
+ {source} Can be a bufnr or a string of text to
+ parse
+ {lang} The language this tree represents
+ {opts} Options table
+ {opts.injections} A table of language to injection query
+ strings. This is useful for overriding
+ the built-in runtime file searching for
+ the injection language query per
+ language.
+
+
+==============================================================================
+Lua module: vim.treesitter.health *treesitter-health*
+
+check_health() *check_health()*
+ TODO: Documentation
+
+list_parsers() *list_parsers()*
+ Lists the parsers currently installed
+
+ Return: ~
+ A list of parsers
vim:tw=78:ts=8:ft=help:norl:
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
index 21f5dcc815..41948f577e 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -978,6 +978,7 @@ Tags: *tag-functions*
settagstack() modify the tag stack of a window
Prompt Buffer: *promptbuffer-functions*
+ prompt_getprompt() get the effective prompt text for a buffer
prompt_setcallback() set prompt callback for a buffer
prompt_setinterrupt() set interrupt callback for a buffer
prompt_setprompt() set the prompt text for a buffer
diff --git a/runtime/filetype.vim b/runtime/filetype.vim
index b7157a14e7..724a96cb01 100644
--- a/runtime/filetype.vim
+++ b/runtime/filetype.vim
@@ -413,6 +413,10 @@ endif
" Lynx config files
au BufNewFile,BufRead lynx.cfg setf lynx
+" Modula-3 configuration language (must be before *.cfg and *makefile)
+au BufNewFile,BufRead *.quake,cm3.cfg setf m3quake
+au BufNewFile,BufRead m3makefile,m3overrides setf m3build
+
" Quake
au BufNewFile,BufRead *baseq[2-3]/*.cfg,*id1/*.cfg setf quake
au BufNewFile,BufRead *quake[1-3]/*.cfg setf quake
@@ -592,7 +596,7 @@ au BufNewFile,BufRead *.fan,*.fwt setf fan
au BufNewFile,BufRead *.factor setf factor
" Fennel
-autocmd BufRead,BufNewFile *.fnl setf fennel
+autocmd BufRead,BufNewFile *.fnl setf fennel
" Fetchmail RC file
au BufNewFile,BufRead .fetchmailrc setf fetchmail
@@ -636,7 +640,7 @@ au BufNewFile,BufRead *.mo,*.gdmo setf gdmo
au BufNewFile,BufRead *.ged,lltxxxxx.txt setf gedcom
" Gift (Moodle)
-autocmd BufRead,BufNewFile *.gift setf gift
+autocmd BufRead,BufNewFile *.gift setf gift
" Git
au BufNewFile,BufRead COMMIT_EDITMSG,MERGE_MSG,TAG_EDITMSG setf gitcommit
@@ -707,7 +711,7 @@ au BufNewFile,BufRead .gtkrc,gtkrc setf gtkrc
au BufNewFile,BufRead *.haml setf haml
" Hamster Classic | Playground files
-au BufNewFile,BufRead *.hsm setf hamster
+au BufNewFile,BufRead *.hsm setf hamster
" Haskell
au BufNewFile,BufRead *.hs,*.hsc,*.hs-boot setf haskell
@@ -1041,10 +1045,10 @@ au BufNewFile,BufRead *.mod
\ setf modsim3 |
\ endif
-" Modula 2 (.md removed in favor of Markdown)
+" Modula-2 (.md removed in favor of Markdown)
au BufNewFile,BufRead *.m2,*.DEF,*.MOD,*.mi setf modula2
-" Modula 3 (.m3, .i3, .mg, .ig)
+" Modula-3 (.m3, .i3, .mg, .ig)
au BufNewFile,BufRead *.[mi][3g] setf modula3
" Monk
@@ -1334,12 +1338,15 @@ au BufNewFile,BufRead *.pdb setf prolog
" Promela
au BufNewFile,BufRead *.pml setf promela
+" Property Specification Language (PSL)
+au BufNewFile,BufRead *.psl setf psl
+
" Google protocol buffers
au BufNewFile,BufRead *.proto setf proto
au BufNewFile,BufRead *.pbtxt setf pbtxt
" Poke
-au BufNewFile,BufRead *.pk setf poke
+au BufNewFile,BufRead *.pk setf poke
" Protocols
au BufNewFile,BufRead */etc/protocols setf protocols
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 563ffc479e..93ec9ed624 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -232,6 +232,12 @@ local function validate_client_config(config)
flags = { config.flags, "t", true };
get_language_id = { config.get_language_id, "f", true };
}
+ assert(
+ (not config.flags
+ or not config.flags.debounce_text_changes
+ or type(config.flags.debounce_text_changes) == 'number'),
+ "flags.debounce_text_changes must be nil or a number with the debounce time in milliseconds"
+ )
local cmd, cmd_args = lsp._cmd_parts(config.cmd)
local offset_encoding = valid_encodings.UTF16
@@ -260,21 +266,171 @@ local function buf_get_full_text(bufnr)
end
--@private
+--- Memoizes a function. On first run, the function return value is saved and
+--- immediately returned on subsequent runs. If the function returns a multival,
+--- only the first returned value will be memoized and returned. The function will only be run once,
+--- even if it has side-effects.
+---
+--@param fn (function) Function to run
+--@returns (function) Memoized function
+local function once(fn)
+ local value
+ local ran = false
+ return function(...)
+ if not ran then
+ value = fn(...)
+ ran = true
+ end
+ return value
+ end
+end
+
+
+local changetracking = {}
+do
+ --- client_id → state
+ ---
+ --- state
+ --- pending_change?: function that the timer starts to trigger didChange
+ --- pending_changes: list of tables with the pending changesets; for incremental_sync only
+ --- use_incremental_sync: bool
+ --- buffers?: table (bufnr → lines); for incremental sync only
+ --- timer?: uv_timer
+ local state_by_client = {}
+
+ function changetracking.init(client, bufnr)
+ local state = state_by_client[client.id]
+ if not state then
+ state = {
+ pending_changes = {};
+ use_incremental_sync = (
+ if_nil(client.config.flags.allow_incremental_sync, true)
+ and client.resolved_capabilities.text_document_did_change == protocol.TextDocumentSyncKind.Incremental
+ );
+ }
+ state_by_client[client.id] = state
+ end
+ if not state.use_incremental_sync then
+ return
+ end
+ if not state.buffers then
+ state.buffers = {}
+ end
+ state.buffers[bufnr] = nvim_buf_get_lines(bufnr, 0, -1, true)
+ end
+
+ function changetracking.reset_buf(client, bufnr)
+ local state = state_by_client[client.id]
+ if state then
+ changetracking._reset_timer(state)
+ if state.buffers then
+ state.buffers[bufnr] = nil
+ end
+ end
+ end
+
+ function changetracking.reset(client_id)
+ local state = state_by_client[client_id]
+ if state then
+ state_by_client[client_id] = nil
+ changetracking._reset_timer(state)
+ end
+ end
+
+ function changetracking.prepare(bufnr, firstline, new_lastline, changedtick)
+ local incremental_changes = function(client)
+ local cached_buffers = state_by_client[client.id].buffers
+ local lines = nvim_buf_get_lines(bufnr, 0, -1, true)
+ local startline = math.min(firstline + 1, math.min(#cached_buffers[bufnr], #lines))
+ local endline = math.min(-(#lines - new_lastline), -1)
+ local incremental_change = vim.lsp.util.compute_diff(
+ cached_buffers[bufnr], lines, startline, endline, client.offset_encoding or 'utf-16')
+ cached_buffers[bufnr] = lines
+ return incremental_change
+ end
+ local full_changes = once(function()
+ return {
+ text = buf_get_full_text(bufnr);
+ };
+ end)
+ local uri = vim.uri_from_bufnr(bufnr)
+ return function(client)
+ if client.resolved_capabilities.text_document_did_change == protocol.TextDocumentSyncKind.None then
+ return
+ end
+ local state = state_by_client[client.id]
+ local debounce = client.config.flags.debounce_text_changes
+ if not debounce then
+ local changes = state.use_incremental_sync and incremental_changes(client) or full_changes()
+ client.notify("textDocument/didChange", {
+ textDocument = {
+ uri = uri;
+ version = changedtick;
+ };
+ contentChanges = { changes, }
+ })
+ return
+ end
+ changetracking._reset_timer(state)
+ if state.use_incremental_sync then
+ -- This must be done immediately and cannot be delayed
+ -- The contents would further change and startline/endline may no longer fit
+ table.insert(state.pending_changes, incremental_changes(client))
+ end
+ state.pending_change = function()
+ state.pending_change = nil
+ if client.is_stopped() then
+ return
+ end
+ local contentChanges
+ if state.use_incremental_sync then
+ contentChanges = state.pending_changes
+ state.pending_changes = {}
+ else
+ contentChanges = { full_changes(), }
+ end
+ client.notify("textDocument/didChange", {
+ textDocument = {
+ uri = uri;
+ version = changedtick;
+ };
+ contentChanges = contentChanges
+ })
+ end
+ state.timer = vim.loop.new_timer()
+ -- Must use schedule_wrap because `full_changes()` calls nvim_buf_get_lines
+ state.timer:start(debounce, 0, vim.schedule_wrap(state.pending_change))
+ end
+ end
+
+ function changetracking._reset_timer(state)
+ if state.timer then
+ state.timer:stop()
+ state.timer:close()
+ state.timer = nil
+ end
+ end
+
+ --- Flushes any outstanding change notification.
+ function changetracking.flush(client)
+ local state = state_by_client[client.id]
+ if state then
+ changetracking._reset_timer(state)
+ if state.pending_change then
+ state.pending_change()
+ end
+ end
+ end
+end
+
+
+--@private
--- Default handler for the 'textDocument/didOpen' LSP notification.
---
--@param bufnr (Number) Number of the buffer, or 0 for current
--@param client Client object
local function text_document_did_open_handler(bufnr, client)
- local use_incremental_sync = (
- if_nil(client.config.flags.allow_incremental_sync, true)
- and client.resolved_capabilities.text_document_did_change == protocol.TextDocumentSyncKind.Incremental
- )
- if use_incremental_sync then
- if not client._cached_buffers then
- client._cached_buffers = {}
- end
- client._cached_buffers[bufnr] = nvim_buf_get_lines(bufnr, 0, -1, true)
- end
+ changetracking.init(client, bufnr)
if not client.resolved_capabilities.text_document_open_close then
return
end
@@ -327,6 +483,13 @@ end
--- result. You can use this with `client.cancel_request(request_id)`
--- to cancel the request.
---
+--- - request_sync(method, params, timeout_ms, bufnr)
+--- Sends a request to the server and synchronously waits for the response.
+--- This is a wrapper around {client.request}
+--- Returns: { err=err, result=result }, a dictionary, where `err` and `result` come from
+--- the |lsp-handler|. On timeout, cancel or error, returns `(nil, err)` where `err` is a
+--- string describing the failure reason. If the request was unsuccessful returns `nil`.
+---
--- - notify(method, params)
--- Sends a notification to an LSP server.
--- Returns: a boolean to indicate if the notification was successful. If
@@ -469,6 +632,9 @@ end
--- server in the initialize request. Invalid/empty values will default to "off"
--@param flags: A table with flags for the client. The current (experimental) flags are:
--- - allow_incremental_sync (bool, default true): Allow using incremental sync for buffer edits
+--- - debounce_text_changes (number, default nil): Debounce didChange
+--- notifications to the server by the given number in milliseconds. No debounce
+--- occurs if nil
---
--@returns Client id. |vim.lsp.get_client_by_id()| Note: client may not be
--- fully initialized. Use `on_init` to do any actions once
@@ -563,6 +729,7 @@ function lsp.start_client(config)
uninitialized_clients[client_id] = nil
lsp.diagnostic.reset(client_id, all_buffer_active_clients)
+ changetracking.reset(client_id)
all_client_active_buffers[client_id] = nil
for _, client_ids in pairs(all_buffer_active_clients) do
client_ids[client_id] = nil
@@ -721,6 +888,9 @@ function lsp.start_client(config)
handler = resolve_handler(method)
or error(string.format("not found: %q request handler for client %q.", method, client.name))
end
+ -- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state
+ changetracking.flush(client)
+
local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, handler, bufnr)
return rpc.request(method, params, function(err, result)
handler(err, method, result, client_id, bufnr)
@@ -728,6 +898,42 @@ function lsp.start_client(config)
end
--@private
+ --- Sends a request to the server and synchronously waits for the response.
+ ---
+ --- This is a wrapper around {client.request}
+ ---
+ --@param method (string) LSP method name.
+ --@param params (table) LSP request params.
+ --@param timeout_ms (number, optional, default=1000) Maximum time in
+ ---milliseconds to wait for a result.
+ --@param bufnr (number) Buffer handle (0 for current).
+ --@returns { err=err, result=result }, a dictionary, where `err` and `result` come from the |lsp-handler|.
+ ---On timeout, cancel or error, returns `(nil, err)` where `err` is a
+ ---string describing the failure reason. If the request was unsuccessful
+ ---returns `nil`.
+ --@see |vim.lsp.buf_request_sync()|
+ function client.request_sync(method, params, timeout_ms, bufnr)
+ local request_result = nil
+ local function _sync_handler(err, _, result)
+ request_result = { err = err, result = result }
+ end
+
+ local success, request_id = client.request(method, params, _sync_handler,
+ bufnr)
+ if not success then return nil end
+
+ local wait_result, reason = vim.wait(timeout_ms or 1000, function()
+ return request_result ~= nil
+ end, 10)
+
+ if not wait_result then
+ client.cancel_request(request_id)
+ return nil, wait_result_reason[reason]
+ end
+ return request_result
+ end
+
+ --@private
--- Sends a notification to an LSP server.
---
--@param method (string) LSP method name.
@@ -753,7 +959,7 @@ function lsp.start_client(config)
-- Track this so that we can escalate automatically if we've alredy tried a
-- graceful shutdown
- local tried_graceful_shutdown = false
+ local graceful_shutdown_failed = false
--@private
--- Stops a client, optionally with force.
---
@@ -765,6 +971,7 @@ function lsp.start_client(config)
function client.stop(force)
lsp.diagnostic.reset(client_id, all_buffer_active_clients)
+ changetracking.reset(client_id)
all_client_active_buffers[client_id] = nil
for _, client_ids in pairs(all_buffer_active_clients) do
client_ids[client_id] = nil
@@ -774,11 +981,10 @@ function lsp.start_client(config)
if handle:is_closing() then
return
end
- if force or (not client.initialized) or tried_graceful_shutdown then
+ if force or (not client.initialized) or graceful_shutdown_failed then
handle:kill(15)
return
end
- tried_graceful_shutdown = true
-- Sending a signal after a process has exited is acceptable.
rpc.request('shutdown', nil, function(err, _)
if err == nil then
@@ -786,6 +992,7 @@ function lsp.start_client(config)
else
-- If there was an error in the shutdown request, then term to be safe.
handle:kill(15)
+ graceful_shutdown_failed = true
end
end)
end
@@ -816,20 +1023,6 @@ function lsp.start_client(config)
end
--@private
---- Memoizes a function. On first run, the function return value is saved and
---- immediately returned on subsequent runs.
----
---@param fn (function) Function to run
---@returns (function) Memoized function
-local function once(fn)
- local value
- return function(...)
- if not value then value = fn(...) end
- return value
- end
-end
-
---@private
--@fn text_document_did_change_handler(_, bufnr, changedtick, firstline, lastline, new_lastline, old_byte_size, old_utf32_size, old_utf16_size)
--- Notify all attached clients that a buffer has changed.
local text_document_did_change_handler
@@ -848,45 +1041,9 @@ do
if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then
return
end
-
util.buf_versions[bufnr] = changedtick
-
- local incremental_changes = function(client)
- local lines = nvim_buf_get_lines(bufnr, 0, -1, true)
- local startline = math.min(firstline + 1, math.min(#client._cached_buffers[bufnr], #lines))
- local endline = math.min(-(#lines - new_lastline), -1)
- local incremental_change = vim.lsp.util.compute_diff(
- client._cached_buffers[bufnr], lines, startline, endline, client.offset_encoding or "utf-16")
- client._cached_buffers[bufnr] = lines
- return incremental_change
- end
-
- local full_changes = once(function()
- return {
- text = buf_get_full_text(bufnr);
- };
- end)
-
- local uri = vim.uri_from_bufnr(bufnr)
- for_each_buffer_client(bufnr, function(client)
- local allow_incremental_sync = if_nil(client.config.flags.allow_incremental_sync, true)
- local text_document_did_change = client.resolved_capabilities.text_document_did_change
- local changes
- if text_document_did_change == protocol.TextDocumentSyncKind.None then
- return
- elseif not allow_incremental_sync or text_document_did_change == protocol.TextDocumentSyncKind.Full then
- changes = full_changes(client)
- elseif text_document_did_change == protocol.TextDocumentSyncKind.Incremental then
- changes = incremental_changes(client)
- end
- client.notify("textDocument/didChange", {
- textDocument = {
- uri = uri;
- version = changedtick;
- };
- contentChanges = { changes; }
- })
- end)
+ local compute_change_and_notify = changetracking.prepare(bufnr, firstline, new_lastline, changedtick)
+ for_each_buffer_client(bufnr, compute_change_and_notify)
end
end
@@ -956,9 +1113,7 @@ function lsp.buf_attach_client(bufnr, client_id)
if client.resolved_capabilities.text_document_open_close then
client.notify('textDocument/didClose', params)
end
- if client._cached_buffers then
- client._cached_buffers[bufnr] = nil
- end
+ changetracking.reset_buf(client, bufnr)
end)
util.buf_versions[bufnr] = nil
all_buffer_active_clients[bufnr] = nil
@@ -1133,42 +1288,77 @@ function lsp.buf_request(bufnr, method, params, handler)
return client_request_ids, _cancel_all_requests
end
---- Sends a request to a server and waits for the response.
+---Sends an async request for all active clients attached to the buffer.
+---Executes the callback on the combined result.
+---Parameters are the same as |vim.lsp.buf_request()| but the return result and callback are
+---different.
---
---- Calls |vim.lsp.buf_request()| but blocks Nvim while awaiting the result.
+--@param bufnr (number) Buffer handle, or 0 for current.
+--@param method (string) LSP method name
+--@param params (optional, table) Parameters to send to the server
+--@param callback (function) The callback to call when all requests are finished.
+-- Unlike `buf_request`, this will collect all the responses from each server instead of handling them.
+-- A map of client_id:request_result will be provided to the callback
+--
+--@returns (function) A function that will cancel all requests which is the same as the one returned from `buf_request`.
+function lsp.buf_request_all(bufnr, method, params, callback)
+ local request_results = {}
+ local result_count = 0
+ local expected_result_count = 0
+ local cancel, client_request_ids
+
+ local set_expected_result_count = once(function()
+ for _ in pairs(client_request_ids) do
+ expected_result_count = expected_result_count + 1
+ end
+ end)
+
+ local function _sync_handler(err, _, result, client_id)
+ request_results[client_id] = { error = err, result = result }
+ result_count = result_count + 1
+ set_expected_result_count()
+
+ if result_count >= expected_result_count then
+ callback(request_results)
+ end
+ end
+
+ client_request_ids, cancel = lsp.buf_request(bufnr, method, params, _sync_handler)
+
+ return cancel
+end
+
+--- Sends a request to all server and waits for the response of all of them.
+---
+--- Calls |vim.lsp.buf_request_all()| but blocks Nvim while awaiting the result.
--- Parameters are the same as |vim.lsp.buf_request()| but the return result is
---- different. Wait maximum of {timeout_ms} (default 100) ms.
+--- different. Wait maximum of {timeout_ms} (default 1000) ms.
---
--@param bufnr (number) Buffer handle, or 0 for current.
--@param method (string) LSP method name
--@param params (optional, table) Parameters to send to the server
---@param timeout_ms (optional, number, default=100) Maximum time in
+--@param timeout_ms (optional, number, default=1000) Maximum time in
--- milliseconds to wait for a result.
---
--@returns Map of client_id:request_result. On timeout, cancel or error,
--- returns `(nil, err)` where `err` is a string describing the failure
--- reason.
function lsp.buf_request_sync(bufnr, method, params, timeout_ms)
- local request_results = {}
- local result_count = 0
- local function _sync_handler(err, _, result, client_id)
- request_results[client_id] = { error = err, result = result }
- result_count = result_count + 1
- end
- local client_request_ids, cancel = lsp.buf_request(bufnr, method, params, _sync_handler)
- local expected_result_count = 0
- for _ in pairs(client_request_ids) do
- expected_result_count = expected_result_count + 1
- end
+ local request_results
+
+ local cancel = lsp.buf_request_all(bufnr, method, params, function(it)
+ request_results = it
+ end)
- local wait_result, reason = vim.wait(timeout_ms or 100, function()
- return result_count >= expected_result_count
+ local wait_result, reason = vim.wait(timeout_ms or 1000, function()
+ return request_results ~= nil
end, 10)
if not wait_result then
cancel()
return nil, wait_result_reason[reason]
end
+
return request_results
end
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index 31116985e2..341a3e82fc 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -111,6 +111,39 @@ function M.completion(context)
return request('textDocument/completion', params)
end
+--@private
+--- If there is more than one client that supports the given method,
+--- asks the user to select one.
+--
+--@returns The client that the user selected or nil
+local function select_client(method)
+ local clients = vim.tbl_values(vim.lsp.buf_get_clients());
+ clients = vim.tbl_filter(function (client)
+ return client.supports_method(method)
+ end, clients)
+ -- better UX when choices are always in the same order (between restarts)
+ table.sort(clients, function (a, b) return a.name < b.name end)
+
+ if #clients > 1 then
+ local choices = {}
+ for k,v in ipairs(clients) do
+ table.insert(choices, string.format("%d %s", k, v.name))
+ end
+ local user_choice = vim.fn.confirm(
+ "Select a language server:",
+ table.concat(choices, "\n"),
+ 0,
+ "Question"
+ )
+ if user_choice == 0 then return nil end
+ return clients[user_choice]
+ elseif #clients < 1 then
+ return nil
+ else
+ return clients[1]
+ end
+end
+
--- Formats the current buffer.
---
--@param options (optional, table) Can be used to specify FormattingOptions.
@@ -119,8 +152,11 @@ end
--
--@see https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting
function M.formatting(options)
+ local client = select_client("textDocument/formatting")
+ if client == nil then return end
+
local params = util.make_formatting_params(options)
- return request('textDocument/formatting', params)
+ return client.request("textDocument/formatting", params)
end
--- Performs |vim.lsp.buf.formatting()| synchronously.
@@ -134,14 +170,62 @@ end
---
--@param options Table with valid `FormattingOptions` entries
--@param timeout_ms (number) Request timeout
+--@see |vim.lsp.buf.formatting_seq_sync|
function M.formatting_sync(options, timeout_ms)
+ local client = select_client("textDocument/formatting")
+ if client == nil then return end
+
local params = util.make_formatting_params(options)
- local result = vim.lsp.buf_request_sync(0, "textDocument/formatting", params, timeout_ms)
- if not result or vim.tbl_isempty(result) then return end
- local _, formatting_result = next(result)
- result = formatting_result.result
- if not result then return end
- vim.lsp.util.apply_text_edits(result)
+ local result, err = client.request_sync("textDocument/formatting", params, timeout_ms)
+ if result and result.result then
+ util.apply_text_edits(result.result)
+ elseif err then
+ vim.notify("vim.lsp.buf.formatting_sync: " .. err, vim.log.levels.WARN)
+ end
+end
+
+--- Formats the current buffer by sequentially requesting formatting from attached clients.
+---
+--- Useful when multiple clients with formatting capability are attached.
+---
+--- Since it's synchronous, can be used for running on save, to make sure buffer is formatted
+--- prior to being saved. {timeout_ms} is passed on to the |vim.lsp.client| `request_sync` method.
+--- Example:
+--- <pre>
+--- vim.api.nvim_command[[autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_seq_sync()]]
+--- </pre>
+---
+--@param options (optional, table) `FormattingOptions` entries
+--@param timeout_ms (optional, number) Request timeout
+--@param order (optional, table) List of client names. Formatting is requested from clients
+---in the following order: first all clients that are not in the `order` list, then
+---the remaining clients in the order as they occur in the `order` list.
+function M.formatting_seq_sync(options, timeout_ms, order)
+ local clients = vim.tbl_values(vim.lsp.buf_get_clients());
+
+ -- sort the clients according to `order`
+ for _, client_name in ipairs(order or {}) do
+ -- if the client exists, move to the end of the list
+ for i, client in ipairs(clients) do
+ if client.name == client_name then
+ table.insert(clients, table.remove(clients, i))
+ break
+ end
+ end
+ end
+
+ -- loop through the clients and make synchronous formatting requests
+ for _, client in ipairs(clients) do
+ if client.resolved_capabilities.document_formatting then
+ local params = util.make_formatting_params(options)
+ local result, err = client.request_sync("textDocument/formatting", params, timeout_ms)
+ if result and result.result then
+ util.apply_text_edits(result.result)
+ elseif err then
+ vim.notify(string.format("vim.lsp.buf.formatting_seq_sync: (%s) %s", client.name, err), vim.log.levels.WARN)
+ end
+ end
+ end
end
--- Formats a given range.
@@ -152,15 +236,12 @@ end
--@param end_pos ({number, number}, optional) mark-indexed position.
---Defaults to the end of the last visual selection.
function M.range_formatting(options, start_pos, end_pos)
- validate { options = {options, 't', true} }
- local sts = vim.bo.softtabstop;
- options = vim.tbl_extend('keep', options or {}, {
- tabSize = (sts > 0 and sts) or (sts < 0 and vim.bo.shiftwidth) or vim.bo.tabstop;
- insertSpaces = vim.bo.expandtab;
- })
+ local client = select_client("textDocument/rangeFormatting")
+ if client == nil then return end
+
local params = util.make_given_range_params(start_pos, end_pos)
- params.options = options
- return request('textDocument/rangeFormatting', params)
+ params.options = util.make_formatting_params(options).options
+ return client.request("textDocument/rangeFormatting", params)
end
--- Renames all references to the symbol under the cursor.
diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua
index bd7ef9cfdc..6f2f846a3b 100644
--- a/runtime/lua/vim/lsp/diagnostic.lua
+++ b/runtime/lua/vim/lsp/diagnostic.lua
@@ -406,9 +406,7 @@ function M.get_line_diagnostics(bufnr, line_nr, opts, client_id)
line_diagnostics = filter_by_severity_limit(opts.severity_limit, line_diagnostics)
end
- if opts.severity_sort then
- table.sort(line_diagnostics, function(a, b) return a.severity < b.severity end)
- end
+ table.sort(line_diagnostics, function(a, b) return a.severity < b.severity end)
return line_diagnostics
end
@@ -997,6 +995,8 @@ end
--- - See |vim.lsp.diagnostic.set_signs()|
--- - update_in_insert: (default=false)
--- - Update diagnostics in InsertMode or wait until InsertLeave
+--- - severity_sort: (default=false)
+--- - Sort diagnostics (and thus signs and virtual text)
function M.on_publish_diagnostics(_, _, params, client_id, _, config)
local uri = params.uri
local bufnr = vim.uri_to_bufnr(uri)
@@ -1007,6 +1007,10 @@ function M.on_publish_diagnostics(_, _, params, client_id, _, config)
local diagnostics = params.diagnostics
+ if config and if_nil(config.severity_sort, false) then
+ table.sort(diagnostics, function(a, b) return a.severity > b.severity end)
+ end
+
-- Always save the diagnostics, even if the buf is not loaded.
-- Language servers may report compile or build errors via diagnostics
-- Users should be able to find these, even if they're in files which
@@ -1034,6 +1038,7 @@ function M.display(diagnostics, bufnr, client_id, config)
underline = true,
virtual_text = true,
update_in_insert = false,
+ severity_sort = false,
}, config)
-- TODO(tjdevries): Consider how we can make this a "standardized" kind of thing for |lsp-handlers|.
@@ -1116,7 +1121,6 @@ end
---@return table {popup_bufnr, win_id}
function M.show_line_diagnostics(opts, bufnr, line_nr, client_id)
opts = opts or {}
- opts.severity_sort = if_nil(opts.severity_sort, true)
local show_header = if_nil(opts.show_header, true)
@@ -1140,7 +1144,7 @@ function M.show_line_diagnostics(opts, bufnr, line_nr, client_id)
local message_lines = vim.split(diagnostic.message, '\n', true)
table.insert(lines, prefix..message_lines[1])
- table.insert(highlights, {#prefix + 1, hiname})
+ table.insert(highlights, {#prefix, hiname})
for j = 2, #message_lines do
table.insert(lines, message_lines[j])
table.insert(highlights, {0, hiname})
diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua
index 1aa8326514..0cabd1a0d4 100644
--- a/runtime/lua/vim/lsp/rpc.lua
+++ b/runtime/lua/vim/lsp/rpc.lua
@@ -518,7 +518,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
send_response(decoded.id, err, result)
end)
-- This works because we are expecting vim.NIL here
- elseif decoded.id and (decoded.result or decoded.error) then
+ elseif decoded.id and (decoded.result ~= vim.NIL or decoded.error ~= vim.NIL) then
-- Server Result
decoded.error = convert_NIL(decoded.error)
decoded.result = convert_NIL(decoded.result)
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index a070cb5306..ce8468aa8a 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -18,6 +18,40 @@ end
local M = {}
+local default_border = {
+ {"", "NormalFloat"},
+ {"", "NormalFloat"},
+ {"", "NormalFloat"},
+ {" ", "NormalFloat"},
+ {"", "NormalFloat"},
+ {"", "NormalFloat"},
+ {"", "NormalFloat"},
+ {" ", "NormalFloat"},
+}
+
+--@private
+-- Check the border given by opts or the default border for the additional
+-- size it adds to a float.
+--@returns size of border in height and width
+local function get_border_size(opts)
+ local border = opts and opts.border or default_border
+ local height = 0
+ local width = 0
+
+ if type(border) == 'string' then
+ -- 'single', 'double', etc.
+ height = 2
+ width = 2
+ else
+ height = height + vim.fn.strdisplaywidth(border[2][1]) -- top
+ height = height + vim.fn.strdisplaywidth(border[6][1]) -- bottom
+ width = width + vim.fn.strdisplaywidth(border[4][1]) -- right
+ width = width + vim.fn.strdisplaywidth(border[8][1]) -- left
+ end
+
+ return { height = height, width = width }
+end
+
--@private
local function split_lines(value)
return split(value, '\n', true)
@@ -436,6 +470,7 @@ function M.apply_text_document_edit(text_document_edit, index)
-- `VersionedTextDocumentIdentifier`s version may be null
-- https://microsoft.github.io/language-server-protocol/specification#versionedTextDocumentIdentifier
if should_check_version and (text_document.version
+ and text_document.version > 0
and M.buf_versions[bufnr]
and M.buf_versions[bufnr] > text_document.version) then
print("Buffer ", text_document.uri, " newer than edits.")
@@ -856,7 +891,7 @@ function M.make_floating_popup_options(width, height, opts)
else
anchor = anchor..'S'
height = math.min(lines_above, height)
- row = 0
+ row = -get_border_size(opts).height
end
if vim.fn.wincol() + width <= api.nvim_get_option('columns') then
@@ -875,19 +910,27 @@ function M.make_floating_popup_options(width, height, opts)
row = row + (opts.offset_y or 0),
style = 'minimal',
width = width,
- border = opts.border or {
- {"", "NormalFloat"},
- {"", "NormalFloat"},
- {"", "NormalFloat"},
- {" ", "NormalFloat"},
- {"", "NormalFloat"},
- {"", "NormalFloat"},
- {"", "NormalFloat"},
- {" ", "NormalFloat"}
- },
+ border = opts.border or default_border,
}
end
+local function _should_add_to_tagstack(new_item)
+ local stack = vim.fn.gettagstack()
+
+ -- Check if we're at the bottom of the tagstack.
+ if stack.curidx <= 1 then return true end
+
+ local top_item = stack.items[stack.curidx-1]
+
+ -- Check if the item at the top of the tagstack is exactly the
+ -- same as the one we want to push.
+ if top_item.tagname ~= new_item.tagname then return true end
+ for i, v in ipairs(top_item.from) do
+ if v ~= new_item.from[i] then return true end
+ end
+ return false
+end
+
--- Jumps to a location.
---
--@param location (`Location`|`LocationLink`)
@@ -896,22 +939,36 @@ function M.jump_to_location(location)
-- location may be Location or LocationLink
local uri = location.uri or location.targetUri
if uri == nil then return end
- local bufnr = vim.uri_to_bufnr(uri)
- -- Save position in jumplist
- vim.cmd "normal! m'"
- -- Push a new item into tagstack
- local from = {vim.fn.bufnr('%'), vim.fn.line('.'), vim.fn.col('.'), 0}
- local items = {{tagname=vim.fn.expand('<cword>'), from=from}}
- vim.fn.settagstack(vim.fn.win_getid(), {items=items}, 't')
+ local from_bufnr = vim.fn.bufnr('%')
+ local from = {from_bufnr, vim.fn.line('.'), vim.fn.col('.'), 0}
+ local item = {tagname=vim.fn.expand('<cword>'), from=from}
+
+ -- Save position in jumplist
+ vim.cmd("mark '")
--- Jump to new location (adjusting for UTF-16 encoding of characters)
+ local bufnr = vim.uri_to_bufnr(uri)
api.nvim_set_current_buf(bufnr)
api.nvim_buf_set_option(0, 'buflisted', true)
local range = location.range or location.targetSelectionRange
local row = range.start.line
local col = get_line_byte_from_position(0, range.start)
+ -- This prevents the tagstack to be filled with items that provide
+ -- no motion when CTRL-T is pressed because they're both the source
+ -- and the destination.
+ local motionless =
+ bufnr == from_bufnr and
+ row+1 == from[2] and col+1 == from[3]
+ if not motionless and _should_add_to_tagstack(item) then
+ local winid = vim.fn.win_getid()
+ local items = {item}
+ vim.fn.settagstack(winid, {items=items}, 't')
+ end
+
+ -- Jump to new location
api.nvim_win_set_cursor(0, {row + 1, col})
+
return true
end
@@ -1185,6 +1242,20 @@ function M._make_floating_popup_size(contents, opts)
width = math.max(line_widths[i], width)
end
end
+
+ local border_width = get_border_size(opts).width
+ local screen_width = api.nvim_win_get_width(0)
+ width = math.min(width, screen_width)
+
+ -- make sure borders are always inside the screen
+ if width + border_width > screen_width then
+ width = width - (width + border_width - screen_width)
+ end
+
+ if wrap_at and wrap_at > width then
+ wrap_at = width
+ end
+
if max_width then
width = math.min(width, max_width)
wrap_at = math.min(wrap_at or max_width, max_width)
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index f223c7b8c8..de997b2d86 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -25,12 +25,12 @@ setmetatable(M, {
})
--- Creates a new parser.
---
--- It is not recommended to use this, use vim.treesitter.get_parser() instead.
---
--- @param bufnr The buffer the parser will be tied to
--- @param lang The language of the parser
--- @param opts Options to pass to the language tree
+---
+--- It is not recommended to use this, use vim.treesitter.get_parser() instead.
+---
+--- @param bufnr The buffer the parser will be tied to
+--- @param lang The language of the parser
+--- @param opts Options to pass to the created language tree
function M._create_parser(bufnr, lang, opts)
language.require_language(lang)
if bufnr == 0 then
@@ -41,10 +41,12 @@ function M._create_parser(bufnr, lang, opts)
local self = LanguageTree.new(bufnr, lang, opts)
+ ---@private
local function bytes_cb(_, ...)
self:_on_bytes(...)
end
+ ---@private
local function detach_cb(_, ...)
if parsers[bufnr] == self then
parsers[bufnr] = nil
@@ -52,6 +54,7 @@ function M._create_parser(bufnr, lang, opts)
self:_on_detach(...)
end
+ ---@private
local function reload_cb(_, ...)
self:_on_reload(...)
end
@@ -64,15 +67,15 @@ function M._create_parser(bufnr, lang, opts)
end
--- Gets the parser for this bufnr / ft combination.
---
--- If needed this will create the parser.
--- Unconditionnally attach the provided callback
---
--- @param bufnr The buffer the parser should be tied to
--- @param ft The filetype of this parser
--- @param opts Options object to pass to the parser
---
--- @returns The parser
+---
+--- If needed this will create the parser.
+--- Unconditionnally attach the provided callback
+---
+--- @param bufnr The buffer the parser should be tied to
+--- @param lang The filetype of this parser
+--- @param opts Options object to pass to the created language tree
+---
+--- @returns The parser
function M.get_parser(bufnr, lang, opts)
opts = opts or {}
@@ -92,6 +95,11 @@ function M.get_parser(bufnr, lang, opts)
return parsers[bufnr]
end
+--- Gets a string parser
+---
+--- @param str The string to parse
+--- @param lang The language of this string
+--- @param opts Options to pass to the created language tree
function M.get_string_parser(str, lang, opts)
vim.validate {
str = { str, 'string' },
diff --git a/runtime/lua/vim/treesitter/health.lua b/runtime/lua/vim/treesitter/health.lua
index dd0b11a6c7..e031ba1bd6 100644
--- a/runtime/lua/vim/treesitter/health.lua
+++ b/runtime/lua/vim/treesitter/health.lua
@@ -1,10 +1,14 @@
local M = {}
local ts = vim.treesitter
+--- Lists the parsers currently installed
+---
+---@return A list of parsers
function M.list_parsers()
return vim.api.nvim_get_runtime_file('parser/*', true)
end
+--- Performs a healthcheck for treesitter integration
function M.check_health()
local report_info = vim.fn['health#report_info']
local report_ok = vim.fn['health#report_ok']
diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua
index fe7e1052c9..84b6a5f135 100644
--- a/runtime/lua/vim/treesitter/highlighter.lua
+++ b/runtime/lua/vim/treesitter/highlighter.lua
@@ -70,11 +70,13 @@ TSHighlighter.hl_map = {
["include"] = "Include",
}
+---@private
local function is_highlight_name(capture_name)
local firstc = string.sub(capture_name, 1, 1)
return firstc ~= string.lower(firstc)
end
+---@private
function TSHighlighterQuery.new(lang, query_string)
local self = setmetatable({}, { __index = TSHighlighterQuery })
@@ -99,10 +101,12 @@ function TSHighlighterQuery.new(lang, query_string)
return self
end
+---@private
function TSHighlighterQuery:query()
return self._query
end
+---@private
--- Get the hl from capture.
--- Returns a tuple { highlight_name: string, is_builtin: bool }
function TSHighlighterQuery:_get_hl_from_capture(capture)
@@ -116,6 +120,11 @@ function TSHighlighterQuery:_get_hl_from_capture(capture)
end
end
+--- Creates a new highlighter using @param tree
+---
+--- @param tree The language tree to use for highlighting
+--- @param opts Table used to configure the highlighter
+--- - queries: Table to overwrite queries used by the highlighter
function TSHighlighter.new(tree, opts)
local self = setmetatable({}, TSHighlighter)
@@ -165,12 +174,14 @@ function TSHighlighter.new(tree, opts)
return self
end
+--- Removes all internal references to the highlighter
function TSHighlighter:destroy()
if TSHighlighter.active[self.bufnr] then
TSHighlighter.active[self.bufnr] = nil
end
end
+---@private
function TSHighlighter:get_highlight_state(tstree)
if not self._highlight_states[tstree] then
self._highlight_states[tstree] = {
@@ -182,24 +193,31 @@ function TSHighlighter:get_highlight_state(tstree)
return self._highlight_states[tstree]
end
+---@private
function TSHighlighter:reset_highlight_state()
self._highlight_states = {}
end
+---@private
function TSHighlighter:on_bytes(_, _, start_row, _, _, _, _, _, new_end)
a.nvim__buf_redraw_range(self.bufnr, start_row, start_row + new_end + 1)
end
+---@private
function TSHighlighter:on_detach()
self:destroy()
end
+---@private
function TSHighlighter:on_changedtree(changes)
for _, ch in ipairs(changes or {}) do
a.nvim__buf_redraw_range(self.bufnr, ch[1], ch[3]+1)
end
end
+--- Gets the query used for @param lang
+---
+--- @param lang A language used by the highlighter.
function TSHighlighter:get_query(lang)
if not self._queries[lang] then
self._queries[lang] = TSHighlighterQuery.new(lang)
@@ -208,6 +226,7 @@ function TSHighlighter:get_query(lang)
return self._queries[lang]
end
+---@private
local function on_line_impl(self, buf, line)
self.tree:for_each_tree(function(tstree, tree)
if not tstree then return end
@@ -251,6 +270,7 @@ local function on_line_impl(self, buf, line)
end, true)
end
+---@private
function TSHighlighter._on_line(_, _win, buf, line, _)
local self = TSHighlighter.active[buf]
if not self then return end
@@ -258,6 +278,7 @@ function TSHighlighter._on_line(_, _win, buf, line, _)
on_line_impl(self, buf, line)
end
+---@private
function TSHighlighter._on_buf(_, buf)
local self = TSHighlighter.active[buf]
if self then
@@ -265,6 +286,7 @@ function TSHighlighter._on_buf(_, buf)
end
end
+---@private
function TSHighlighter._on_win(_, _win, buf, _topline)
local self = TSHighlighter.active[buf]
if not self then
diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua
index eed28e0e41..6dc37c7848 100644
--- a/runtime/lua/vim/treesitter/language.lua
+++ b/runtime/lua/vim/treesitter/language.lua
@@ -3,12 +3,12 @@ local a = vim.api
local M = {}
--- Asserts that the provided language is installed, and optionally provide a path for the parser
---
--- Parsers are searched in the `parser` runtime directory.
---
--- @param lang The language the parser should parse
--- @param path Optional path the parser is located at
--- @param silent Don't throw an error if language not found
+---
+--- Parsers are searched in the `parser` runtime directory.
+---
+--- @param lang The language the parser should parse
+--- @param path Optional path the parser is located at
+--- @param silent Don't throw an error if language not found
function M.require_language(lang, path, silent)
if vim._ts_has_language(lang) then
return true
@@ -37,10 +37,10 @@ function M.require_language(lang, path, silent)
end
--- Inspects the provided language.
---
--- Inspecting provides some useful informations on the language like node names, ...
---
--- @param lang The language.
+---
+--- Inspecting provides some useful informations on the language like node names, ...
+---
+--- @param lang The language.
function M.inspect_language(lang)
M.require_language(lang)
return vim._ts_inspect_language(lang)
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index 4168c1e365..899d90e464 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -5,21 +5,26 @@ local language = require'vim.treesitter.language'
local LanguageTree = {}
LanguageTree.__index = LanguageTree
--- Represents a single treesitter parser for a language.
--- The language can contain child languages with in its range,
--- hence the tree.
---
--- @param source Can be a bufnr or a string of text to parse
--- @param lang The language this tree represents
--- @param opts Options table
--- @param opts.queries A table of language to injection query strings.
--- This is useful for overriding the built-in runtime file
--- searching for the injection language query per language.
+--- Represents a single treesitter parser for a language.
+--- The language can contain child languages with in its range,
+--- hence the tree.
+---
+--- @param source Can be a bufnr or a string of text to parse
+--- @param lang The language this tree represents
+--- @param opts Options table
+--- @param opts.injections A table of language to injection query strings.
+--- This is useful for overriding the built-in runtime file
+--- searching for the injection language query per language.
function LanguageTree.new(source, lang, opts)
language.require_language(lang)
opts = opts or {}
- local custom_queries = opts.queries or {}
+ if opts.queries then
+ a.nvim_err_writeln("'queries' is no longer supported. Use 'injections' now")
+ opts.injections = opts.queries
+ end
+
+ local injections = opts.injections or {}
local self = setmetatable({
_source = source,
_lang = lang,
@@ -27,8 +32,8 @@ function LanguageTree.new(source, lang, opts)
_regions = {},
_trees = {},
_opts = opts,
- _injection_query = custom_queries[lang]
- and query.parse_query(lang, custom_queries[lang])
+ _injection_query = injections[lang]
+ and query.parse_query(lang, injections[lang])
or query.get_query(lang, "injections"),
_valid = false,
_parser = vim._create_ts_parser(lang),
@@ -45,7 +50,7 @@ function LanguageTree.new(source, lang, opts)
return self
end
--- Invalidates this parser and all its children
+--- Invalidates this parser and all its children
function LanguageTree:invalidate(reload)
self._valid = false
@@ -59,38 +64,38 @@ function LanguageTree:invalidate(reload)
end
end
--- Returns all trees this language tree contains.
--- Does not include child languages.
+--- Returns all trees this language tree contains.
+--- Does not include child languages.
function LanguageTree:trees()
return self._trees
end
--- Gets the language of this tree layer.
+--- Gets the language of this tree node.
function LanguageTree:lang()
return self._lang
end
--- Determines whether this tree is valid.
--- If the tree is invalid, `parse()` must be called
--- to get the an updated tree.
+--- Determines whether this tree is valid.
+--- If the tree is invalid, `parse()` must be called
+--- to get the an updated tree.
function LanguageTree:is_valid()
return self._valid
end
--- Returns a map of language to child tree.
+--- Returns a map of language to child tree.
function LanguageTree:children()
return self._children
end
--- Returns the source content of the language tree (bufnr or string).
+--- Returns the source content of the language tree (bufnr or string).
function LanguageTree:source()
return self._source
end
--- Parses all defined regions using a treesitter parser
--- for the language this tree represents.
--- This will run the injection query for this language to
--- determine if any child languages should be created.
+--- Parses all defined regions using a treesitter parser
+--- for the language this tree represents.
+--- This will run the injection query for this language to
+--- determine if any child languages should be created.
function LanguageTree:parse()
if self._valid then
return self._trees
@@ -164,9 +169,10 @@ function LanguageTree:parse()
return self._trees, changes
end
--- Invokes the callback for each LanguageTree and it's children recursively
--- @param fn The function to invoke. This is invoked with arguments (tree: LanguageTree, lang: string)
--- @param include_self Whether to include the invoking tree in the results.
+--- Invokes the callback for each LanguageTree and it's children recursively
+---
+--- @param fn The function to invoke. This is invoked with arguments (tree: LanguageTree, lang: string)
+--- @param include_self Whether to include the invoking tree in the results.
function LanguageTree:for_each_child(fn, include_self)
if include_self then
fn(self, self._lang)
@@ -177,10 +183,12 @@ function LanguageTree:for_each_child(fn, include_self)
end
end
--- Invokes the callback for each treesitter trees recursively.
--- Note, this includes the invoking language tree's trees as well.
--- @param fn The callback to invoke. The callback is invoked with arguments
--- (tree: TSTree, languageTree: LanguageTree)
+--- Invokes the callback for each treesitter trees recursively.
+---
+--- Note, this includes the invoking language tree's trees as well.
+---
+--- @param fn The callback to invoke. The callback is invoked with arguments
+--- (tree: TSTree, languageTree: LanguageTree)
function LanguageTree:for_each_tree(fn)
for _, tree in ipairs(self._trees) do
fn(tree, self)
@@ -191,9 +199,11 @@ function LanguageTree:for_each_tree(fn)
end
end
--- Adds a child language to this tree.
--- If the language already exists as a child, it will first be removed.
--- @param lang The language to add.
+--- Adds a child language to this tree.
+---
+--- If the language already exists as a child, it will first be removed.
+---
+--- @param lang The language to add.
function LanguageTree:add_child(lang)
if self._children[lang] then
self:remove_child(lang)
@@ -207,8 +217,9 @@ function LanguageTree:add_child(lang)
return self._children[lang]
end
--- Removes a child language from this tree.
--- @param lang The language to remove.
+--- Removes a child language from this tree.
+---
+--- @param lang The language to remove.
function LanguageTree:remove_child(lang)
local child = self._children[lang]
@@ -220,10 +231,11 @@ function LanguageTree:remove_child(lang)
end
end
--- Destroys this language tree and all its children.
--- Any cleanup logic should be performed here.
--- Note, this DOES NOT remove this tree from a parent.
--- `remove_child` must be called on the parent to remove it.
+--- Destroys this language tree and all its children.
+---
+--- Any cleanup logic should be performed here.
+--- Note, this DOES NOT remove this tree from a parent.
+--- `remove_child` must be called on the parent to remove it.
function LanguageTree:destroy()
-- Cleanup here
for _, child in ipairs(self._children) do
@@ -231,23 +243,23 @@ function LanguageTree:destroy()
end
end
--- Sets the included regions that should be parsed by this parser.
--- A region is a set of nodes and/or ranges that will be parsed in the same context.
---
--- For example, `{ { node1 }, { node2} }` is two separate regions.
--- This will be parsed by the parser in two different contexts... thus resulting
--- in two separate trees.
---
--- `{ { node1, node2 } }` is a single region consisting of two nodes.
--- This will be parsed by the parser in a single context... thus resulting
--- in a single tree.
---
--- This allows for embedded languages to be parsed together across different
--- nodes, which is useful for templating languages like ERB and EJS.
---
--- Note, this call invalidates the tree and requires it to be parsed again.
---
--- @param regions A list of regions this tree should manage and parse.
+--- Sets the included regions that should be parsed by this parser.
+--- A region is a set of nodes and/or ranges that will be parsed in the same context.
+---
+--- For example, `{ { node1 }, { node2} }` is two separate regions.
+--- This will be parsed by the parser in two different contexts... thus resulting
+--- in two separate trees.
+---
+--- `{ { node1, node2 } }` is a single region consisting of two nodes.
+--- This will be parsed by the parser in a single context... thus resulting
+--- in a single tree.
+---
+--- This allows for embedded languages to be parsed together across different
+--- nodes, which is useful for templating languages like ERB and EJS.
+---
+--- Note, this call invalidates the tree and requires it to be parsed again.
+---
+--- @param regions A list of regions this tree should manage and parse.
function LanguageTree:set_included_regions(regions)
-- TODO(vigoux): I don't think string parsers are useful for now
if type(self._source) == "number" then
@@ -276,16 +288,18 @@ function LanguageTree:set_included_regions(regions)
self:invalidate()
end
--- Gets the set of included regions
+--- Gets the set of included regions
function LanguageTree:included_regions()
return self._regions
end
--- Gets language injection points by language.
--- This is where most of the injection processing occurs.
--- TODO: Allow for an offset predicate to tailor the injection range
--- instead of using the entire nodes range.
--- @private
+--- Gets language injection points by language.
+---
+--- This is where most of the injection processing occurs.
+---
+--- TODO: Allow for an offset predicate to tailor the injection range
+--- instead of using the entire nodes range.
+--- @private
function LanguageTree:_get_injections()
if not self._injection_query then return {} end
@@ -297,33 +311,50 @@ function LanguageTree:_get_injections()
for pattern, match, metadata in self._injection_query:iter_matches(root_node, self._source, start_line, end_line+1) do
local lang = nil
- local injection_node = nil
- local combined = false
+ local ranges = {}
+ local combined = metadata.combined
+
+ -- Directives can configure how injections are captured as well as actual node captures.
+ -- This allows more advanced processing for determining ranges and language resolution.
+ if metadata.content then
+ local content = metadata.content
+
+ -- Allow for captured nodes to be used
+ if type(content) == "number" then
+ content = {match[content]}
+ end
+
+ if content then
+ vim.list_extend(ranges, content)
+ end
+ end
+
+ if metadata.language then
+ lang = metadata.language
+ end
-- You can specify the content and language together
-- using a tag with the language, for example
-- @javascript
for id, node in pairs(match) do
- local data = metadata[id]
local name = self._injection_query.captures[id]
- local offset_range = data and data.offset
-- Lang should override any other language tag
- if name == "language" then
+ if name == "language" and not lang then
lang = query.get_node_text(node, self._source)
elseif name == "combined" then
combined = true
- elseif name == "content" then
- injection_node = offset_range or node
+ elseif name == "content" and #ranges == 0 then
+ table.insert(ranges, node)
-- Ignore any tags that start with "_"
-- Allows for other tags to be used in matches
elseif string.sub(name, 1, 1) ~= "_" then
- if lang == nil then
+ if not lang then
lang = name
end
- if not injection_node then
- injection_node = offset_range or node
+ if #ranges == 0 then
+ table.insert(ranges, node)
end
end
end
@@ -337,21 +368,21 @@ function LanguageTree:_get_injections()
injections[tree_index][lang] = {}
end
- -- Key by pattern so we can either combine each node to parse in the same
- -- context or treat each node independently.
+ -- Key this by pattern. If combined is set to true all captures of this pattern
+ -- will be parsed by treesitter as the same "source".
+ -- If combined is false, each "region" will be parsed as a single source.
if not injections[tree_index][lang][pattern] then
- injections[tree_index][lang][pattern] = { combined = combined, nodes = {} }
+ injections[tree_index][lang][pattern] = { combined = combined, regions = {} }
end
- table.insert(injections[tree_index][lang][pattern].nodes, injection_node)
+ table.insert(injections[tree_index][lang][pattern].regions, ranges)
end
end
local result = {}
-- Generate a map by lang of node lists.
- -- Each list is a set of ranges that should be parsed
- -- together.
+ -- Each list is a set of ranges that should be parsed together.
for _, lang_map in ipairs(injections) do
for lang, patterns in pairs(lang_map) do
if not result[lang] then
@@ -360,10 +391,10 @@ function LanguageTree:_get_injections()
for _, entry in pairs(patterns) do
if entry.combined then
- table.insert(result[lang], entry.nodes)
+ table.insert(result[lang], vim.tbl_flatten(entry.regions))
else
- for _, node in ipairs(entry.nodes) do
- table.insert(result[lang], {node})
+ for _, ranges in ipairs(entry.regions) do
+ table.insert(result[lang], ranges)
end
end
end
@@ -373,12 +404,14 @@ function LanguageTree:_get_injections()
return result
end
+---@private
function LanguageTree:_do_callback(cb_name, ...)
for _, cb in ipairs(self._callbacks[cb_name]) do
cb(...)
end
end
+---@private
function LanguageTree:_on_bytes(bufnr, changed_tick,
start_row, start_col, start_byte,
old_row, old_col, old_byte,
@@ -403,24 +436,26 @@ function LanguageTree:_on_bytes(bufnr, changed_tick,
new_row, new_col, new_byte)
end
+---@private
function LanguageTree:_on_reload()
self:invalidate(true)
end
+---@private
function LanguageTree:_on_detach(...)
self:invalidate(true)
self:_do_callback('detach', ...)
end
--- Registers callbacks for the parser
--- @param cbs An `nvim_buf_attach`-like table argument with the following keys :
--- `on_bytes` : see `nvim_buf_attach`, but this will be called _after_ the parsers callback.
--- `on_changedtree` : a callback that will be called every time the tree has syntactical changes.
--- it will only be passed one argument, that is a table of the ranges (as node ranges) that
--- changed.
--- `on_child_added` : emitted when a child is added to the tree.
--- `on_child_removed` : emitted when a child is removed from the tree.
+--- @param cbs An `nvim_buf_attach`-like table argument with the following keys :
+--- `on_bytes` : see `nvim_buf_attach`, but this will be called _after_ the parsers callback.
+--- `on_changedtree` : a callback that will be called every time the tree has syntactical changes.
+--- it will only be passed one argument, that is a table of the ranges (as node ranges) that
+--- changed.
+--- `on_child_added` : emitted when a child is added to the tree.
+--- `on_child_removed` : emitted when a child is removed from the tree.
function LanguageTree:register_cbs(cbs)
if not cbs then return end
@@ -445,6 +480,7 @@ function LanguageTree:register_cbs(cbs)
end
end
+---@private
local function tree_contains(tree, range)
local start_row, start_col, end_row, end_col = tree:root():range()
local start_fits = start_row < range[1] or (start_row == range[1] and start_col <= range[2])
@@ -457,6 +493,11 @@ local function tree_contains(tree, range)
return false
end
+--- Determines wether @param range is contained in this language tree
+---
+--- This goes down the tree to recursively check childs.
+---
+--- @param range A range, that is a `{ start_line, start_col, end_line, end_col }` table.
function LanguageTree:contains(range)
for _, tree in pairs(self._trees) do
if tree_contains(tree, range) then
@@ -467,6 +508,9 @@ function LanguageTree:contains(range)
return false
end
+--- Gets the appropriate language that contains @param range
+---
+--- @param range A text range, see |LanguageTree:contains|
function LanguageTree:language_for_range(range)
for _, child in pairs(self._children) do
if child:contains(range) then
diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index f40e1d5294..db6d7e4dc0 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -8,6 +8,7 @@ Query.__index = Query
local M = {}
+---@private
local function dedupe_files(files)
local result = {}
local seen = {}
@@ -22,6 +23,7 @@ local function dedupe_files(files)
return result
end
+---@private
local function safe_read(filename, read_quantifier)
local file, err = io.open(filename, 'r')
if not file then
@@ -32,6 +34,11 @@ local function safe_read(filename, read_quantifier)
return content
end
+--- Gets the list of files used to make up a query
+---
+--- @param lang The language
+--- @param query_name The name of the query to load
+--- @param is_included Internal parameter, most of the time left as `nil`
function M.get_query_files(lang, query_name, is_included)
local query_path = string.format('queries/%s/%s.scm', lang, query_name)
local lang_files = dedupe_files(a.nvim_get_runtime_file(query_path, true))
@@ -79,6 +86,7 @@ function M.get_query_files(lang, query_name, is_included)
return query_files
end
+---@private
local function read_query_files(filenames)
local contents = {}
@@ -89,17 +97,6 @@ local function read_query_files(filenames)
return table.concat(contents, '')
end
-local match_metatable = {
- __index = function(tbl, key)
- rawset(tbl, key, {})
- return tbl[key]
- end
-}
-
-local function new_match_metadata()
- return setmetatable({}, match_metatable)
-end
-
--- The explicitly set queries from |vim.treesitter.query.set_query()|
local explicit_queries = setmetatable({}, {
__index = function(t, k)
@@ -114,19 +111,20 @@ local explicit_queries = setmetatable({}, {
---
--- This allows users to override any runtime files and/or configuration
--- set by plugins.
----@param lang string: The language to use for the query
----@param query_name string: The name of the query (i.e. "highlights")
----@param text string: The query text (unparsed).
+---
+--- @param lang string: The language to use for the query
+--- @param query_name string: The name of the query (i.e. "highlights")
+--- @param text string: The query text (unparsed).
function M.set_query(lang, query_name, text)
explicit_queries[lang][query_name] = M.parse_query(lang, text)
end
--- Returns the runtime query {query_name} for {lang}.
---
--- @param lang The language to use for the query
--- @param query_name The name of the query (i.e. "highlights")
---
--- @return The corresponding query, parsed.
+---
+--- @param lang The language to use for the query
+--- @param query_name The name of the query (i.e. "highlights")
+---
+--- @return The corresponding query, parsed.
function M.get_query(lang, query_name)
if explicit_queries[lang][query_name] then
return explicit_queries[lang][query_name]
@@ -140,12 +138,23 @@ function M.get_query(lang, query_name)
end
end
---- Parses a query.
---
--- @param language The language
--- @param query A string containing the query (s-expr syntax)
---
--- @returns The query
+--- Parse {query} as a string. (If the query is in a file, the caller
+--- should read the contents into a string before calling).
+---
+--- Returns a `Query` (see |lua-treesitter-query|) object which can be used to
+--- search nodes in the syntax tree for the patterns defined in {query}
+--- using `iter_*` methods below.
+---
+--- Exposes `info` and `captures` with additional information about the {query}.
+--- - `captures` contains the list of unique capture names defined in
+--- {query}.
+--- -` info.captures` also points to `captures`.
+--- - `info.patterns` contains information about predicates.
+---
+--- @param lang The language
+--- @param query A string containing the query (s-expr syntax)
+---
+--- @returns The query
function M.parse_query(lang, query)
language.require_language(lang)
local self = setmetatable({}, Query)
@@ -158,8 +167,9 @@ end
-- TODO(vigoux): support multiline nodes too
--- Gets the text corresponding to a given node
--- @param node the node
--- @param bufnr the buffer from which the node is extracted.
+---
+--- @param node the node
+--- @param bsource The buffer or string from which the node is extracted
function M.get_node_text(node, source)
local start_row, start_col, start_byte = node:start()
local end_row, end_col, end_byte = node:end_()
@@ -211,6 +221,7 @@ local predicate_handlers = {
["match?"] = (function()
local magic_prefixes = {['\\v']=true, ['\\m']=true, ['\\M']=true, ['\\V']=true}
+ ---@private
local function check_magic(str)
if string.len(str) < 2 or magic_prefixes[string.sub(str,1,2)] then
return str
@@ -259,7 +270,7 @@ predicate_handlers["vim-match?"] = predicate_handlers["match?"]
-- Directives store metadata or perform side effects against a match.
-- Directives should always end with a `!`.
-- Directive handler receive the following arguments
--- (match, pattern, bufnr, predicate)
+-- (match, pattern, bufnr, predicate, metadata)
local directive_handlers = {
["set!"] = function(_, _, _, pred, metadata)
if #pred == 4 then
@@ -279,7 +290,6 @@ local directive_handlers = {
local start_col_offset = pred[4] or 0
local end_row_offset = pred[5] or 0
local end_col_offset = pred[6] or 0
- local key = pred[7] or "offset"
range[1] = range[1] + start_row_offset
range[2] = range[2] + start_col_offset
@@ -288,16 +298,16 @@ local directive_handlers = {
-- If this produces an invalid range, we just skip it.
if range[1] < range[3] or (range[1] == range[3] and range[2] <= range[4]) then
- metadata[pred[2]][key] = range
+ metadata.content = {range}
end
end
}
--- Adds a new predicate to be used in queries
---
--- @param name the name of the predicate, without leading #
--- @param handler the handler function to be used
--- signature will be (match, pattern, bufnr, predicate)
+---
+--- @param name the name of the predicate, without leading #
+--- @param handler the handler function to be used
+--- signature will be (match, pattern, bufnr, predicate)
function M.add_predicate(name, handler, force)
if predicate_handlers[name] and not force then
error(string.format("Overriding %s", name))
@@ -307,10 +317,10 @@ function M.add_predicate(name, handler, force)
end
--- Adds a new directive to be used in queries
---
--- @param name the name of the directive, without leading #
--- @param handler the handler function to be used
--- signature will be (match, pattern, bufnr, predicate)
+---
+--- @param name the name of the directive, without leading #
+--- @param handler the handler function to be used
+--- signature will be (match, pattern, bufnr, predicate)
function M.add_directive(name, handler, force)
if directive_handlers[name] and not force then
error(string.format("Overriding %s", name))
@@ -324,14 +334,17 @@ function M.list_predicates()
return vim.tbl_keys(predicate_handlers)
end
+---@private
local function xor(x, y)
return (x or y) and not (x and y)
end
+---@private
local function is_directive(name)
return string.sub(name, -1) == "!"
end
+---@private
function Query:match_preds(match, pattern, source)
local preds = self.info.patterns[pattern]
@@ -370,7 +383,7 @@ function Query:match_preds(match, pattern, source)
return true
end
---- Applies directives against a match and pattern.
+---@private
function Query:apply_directives(match, pattern, source, metadata)
local preds = self.info.patterns[pattern]
@@ -392,6 +405,7 @@ end
--- Returns the start and stop value if set else the node's range.
-- When the node's range is used, the stop is incremented by 1
-- to make the search inclusive.
+---@private
local function value_or_node_range(start, stop, node)
if start == nil and stop == nil then
local node_start, _, node_stop, _ = node:range()
@@ -401,15 +415,36 @@ local function value_or_node_range(start, stop, node)
return start, stop
end
---- Iterates of the captures of self on a given range.
---
--- @param node The node under which the search will occur
--- @param buffer The source buffer to search
--- @param start The starting line of the search
--- @param stop The stopping line of the search (end-exclusive)
---
--- @returns The matching capture id
--- @returns The captured node
+--- Iterate over all captures from all matches inside {node}
+---
+--- {source} is needed if the query contains predicates, then the caller
+--- must ensure to use a freshly parsed tree consistent with the current
+--- text of the buffer (if relevent). {start_row} and {end_row} can be used to limit
+--- matches inside a row range (this is typically used with root node
+--- as the node, i e to get syntax highlight matches in the current
+--- viewport). When omitted the start and end row values are used from the given node.
+---
+--- The iterator returns three values, a numeric id identifying the capture,
+--- the captured node, and metadata from any directives processing the match.
+--- The following example shows how to get captures by name:
+---
+--- <pre>
+--- for id, node, metadata in query:iter_captures(tree:root(), bufnr, first, last) do
+--- local name = query.captures[id] -- name of the capture in the query
+--- -- typically useful info about the node:
+--- local type = node:type() -- type of the captured node
+--- local row1, col1, row2, col2 = node:range() -- range of the capture
+--- ... use the info here ...
+--- end
+--- </pre>
+---
+--- @param node The node under which the search will occur
+--- @param source The source buffer or string to exctract text from
+--- @param start The starting line of the search
+--- @param stop The stopping line of the search (end-exclusive)
+---
+--- @returns The matching capture id
+--- @returns The captured node
function Query:iter_captures(node, source, start, stop)
if type(source) == "number" and source == 0 then
source = vim.api.nvim_get_current_buf()
@@ -418,9 +453,10 @@ function Query:iter_captures(node, source, start, stop)
start, stop = value_or_node_range(start, stop, node)
local raw_iter = node:_rawquery(self.query, true, start, stop)
+ ---@private
local function iter()
local capture, captured_node, match = raw_iter()
- local metadata = new_match_metadata()
+ local metadata = {}
if match ~= nil then
local active = self:match_preds(match, match.pattern, source)
@@ -437,14 +473,35 @@ function Query:iter_captures(node, source, start, stop)
end
--- Iterates the matches of self on a given range.
---
--- @param node The node under which the search will occur
--- @param buffer The source buffer to search
--- @param start The starting line of the search
--- @param stop The stopping line of the search (end-exclusive)
---
--- @returns The matching pattern id
--- @returns The matching match
+---
+--- Iterate over all matches within a node. The arguments are the same as
+--- for |query:iter_captures()| but the iterated values are different:
+--- an (1-based) index of the pattern in the query, a table mapping
+--- capture indices to nodes, and metadata from any directives processing the match.
+--- If the query has more than one pattern the capture table might be sparse,
+--- and e.g. `pairs()` method should be used over `ipairs`.
+--- Here an example iterating over all captures in every match:
+---
+--- <pre>
+--- for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, first, last) do
+--- for id, node in pairs(match) do
+--- local name = query.captures[id]
+--- -- `node` was captured by the `name` capture in the match
+---
+--- local node_data = metadata[id] -- Node level metadata
+---
+--- ... use the info here ...
+--- end
+--- end
+--- </pre>
+---
+--- @param node The node under which the search will occur
+--- @param source The source buffer or string to search
+--- @param start The starting line of the search
+--- @param stop The stopping line of the search (end-exclusive)
+---
+--- @returns The matching pattern id
+--- @returns The matching match
function Query:iter_matches(node, source, start, stop)
if type(source) == "number" and source == 0 then
source = vim.api.nvim_get_current_buf()
@@ -455,7 +512,7 @@ function Query:iter_matches(node, source, start, stop)
local raw_iter = node:_rawquery(self.query, false, start, stop)
local function iter()
local pattern, match = raw_iter()
- local metadata = new_match_metadata()
+ local metadata = {}
if match ~= nil then
local active = self:match_preds(match, pattern, source)
diff --git a/runtime/menu.vim b/runtime/menu.vim
index cd56eb5583..78306a57b8 100644
--- a/runtime/menu.vim
+++ b/runtime/menu.vim
@@ -2,7 +2,7 @@
" You can also use this as a start for your own set of menus.
"
" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2019 Jan 27
+" Last Change: 2019 Dec 10
" Note that ":an" (short for ":anoremenu") is often used to make a menu work
" in all modes and avoid side effects from mappings defined by the user.
@@ -690,11 +690,11 @@ func! s:BMShow(...)
let g:bmenu_priority = a:1
endif
- " remove old menu, if exists; keep one entry to avoid a torn off menu to
- " disappear.
- silent! unmenu &Buffers
+ " Remove old menu, if exists; keep one entry to avoid a torn off menu to
+ " disappear. Use try/catch to avoid setting v:errmsg
+ try | unmenu &Buffers | catch | endtry
exe 'noremenu ' . g:bmenu_priority . ".1 &Buffers.Dummy l"
- silent! unmenu! &Buffers
+ try | unmenu! &Buffers | catch | endtry
" create new menu; set 'cpo' to include the <CR>
let cpo_save = &cpo
diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py
index b4d896fecc..d46306d41a 100755
--- a/scripts/gen_vimdoc.py
+++ b/scripts/gen_vimdoc.py
@@ -186,6 +186,48 @@ CONFIG = {
'module_override': {},
'append_only': [],
},
+ 'treesitter': {
+ 'mode': 'lua',
+ 'filename': 'treesitter.txt',
+ 'section_start_token': '*lua-treesitter-core*',
+ 'section_order': [
+ 'treesitter.lua',
+ 'language.lua',
+ 'query.lua',
+ 'highlighter.lua',
+ 'languagetree.lua',
+ 'health.lua',
+ ],
+ 'files': ' '.join([
+ os.path.join(base_dir, 'runtime/lua/vim/treesitter.lua'),
+ os.path.join(base_dir, 'runtime/lua/vim/treesitter/'),
+ ]),
+ 'file_patterns': '*.lua',
+ 'fn_name_prefix': '',
+ 'section_name': {},
+ 'section_fmt': lambda name: (
+ 'Lua module: vim.treesitter'
+ if name.lower() == 'treesitter'
+ else f'Lua module: vim.treesitter.{name.lower()}'),
+ 'helptag_fmt': lambda name: (
+ '*lua-treesitter-core*'
+ if name.lower() == 'treesitter'
+ else f'*treesitter-{name.lower()}*'),
+ 'fn_helptag_fmt': lambda fstem, name: (
+ f'*{name}()*'
+ if name != 'new'
+ else f'*{fstem}.{name}()*'),
+ # 'fn_helptag_fmt': lambda fstem, name: (
+ # f'*vim.treesitter.{name}()*'
+ # if fstem == 'treesitter'
+ # else (
+ # '*vim.lsp.client*'
+ # # HACK. TODO(justinmk): class/structure support in lua2dox
+ # if 'lsp.client' == f'{fstem}.{name}'
+ # else f'*vim.lsp.{fstem}.{name}()*')),
+ 'module_override': {},
+ 'append_only': [],
+ }
}
param_exclude = (
@@ -666,15 +708,6 @@ def extract_from_xml(filename, target, width):
annotations = filter(None, map(lambda x: annotation_map.get(x),
annotations.split()))
- if not fmt_vimhelp:
- pass
- else:
- fstem = '?'
- if '.' in compoundname:
- fstem = compoundname.split('.')[0]
- fstem = CONFIG[target]['module_override'].get(fstem, fstem)
- vimtag = CONFIG[target]['fn_helptag_fmt'](fstem, name)
-
params = []
type_length = 0
@@ -695,17 +728,37 @@ def extract_from_xml(filename, target, width):
if fmt_vimhelp and param_type.endswith('*'):
param_type = param_type.strip('* ')
param_name = '*' + param_name
+
type_length = max(type_length, len(param_type))
params.append((param_type, param_name))
+ # Handle Object Oriented style functions here.
+ # We make sure they have "self" in the parameters,
+ # and a parent function
+ if return_type.startswith('function') \
+ and len(return_type.split(' ')) >= 2 \
+ and any(x[1] == 'self' for x in params):
+ split_return = return_type.split(' ')
+ name = f'{split_return[1]}:{name}'
+
c_args = []
for param_type, param_name in params:
c_args.append((' ' if fmt_vimhelp else '') + (
'%s %s' % (param_type.ljust(type_length), param_name)).strip())
+ if not fmt_vimhelp:
+ pass
+ else:
+ fstem = '?'
+ if '.' in compoundname:
+ fstem = compoundname.split('.')[0]
+ fstem = CONFIG[target]['module_override'].get(fstem, fstem)
+ vimtag = CONFIG[target]['fn_helptag_fmt'](fstem, name)
+
prefix = '%s(' % name
suffix = '%s)' % ', '.join('{%s}' % a[1] for a in params
if a[0] not in ('void', 'Error'))
+
if not fmt_vimhelp:
c_decl = '%s %s(%s);' % (return_type, name, ', '.join(c_args))
signature = prefix + suffix
@@ -774,7 +827,9 @@ def extract_from_xml(filename, target, width):
xrefs.clear()
- fns = collections.OrderedDict(sorted(fns.items()))
+ fns = collections.OrderedDict(sorted(
+ fns.items(),
+ key=lambda key_item_tuple: key_item_tuple[0].lower()))
deprecated_fns = collections.OrderedDict(sorted(deprecated_fns.items()))
return (fns, deprecated_fns)
@@ -1002,6 +1057,7 @@ def main(config, args):
title, helptag, section_doc = sections.pop(filename)
except KeyError:
msg(f'warning: empty docs, skipping (target={target}): {filename}')
+ msg(f' existing docs: {sections.keys()}')
continue
i += 1
if filename not in CONFIG[target]['append_only']:
diff --git a/scripts/lua2dox.lua b/scripts/lua2dox.lua
index 1dc4c0a5a0..0b36a1e061 100644
--- a/scripts/lua2dox.lua
+++ b/scripts/lua2dox.lua
@@ -491,6 +491,27 @@ function TLua2DoX_filter.readfile(this,AppStamp,Filename)
end
end
+ -- Big hax
+ if string.find(fn, ":") then
+ -- TODO: We need to add a first parameter of "SELF" here
+ -- local colon_place = string.find(fn, ":")
+ -- local name = string.sub(fn, 1, colon_place)
+ fn = fn:gsub(":", ".", 1)
+ outStream:writeln("/// @param self")
+
+ local paren_start = string.find(fn, "(", 1, true)
+ local paren_finish = string.find(fn, ")", 1, true)
+
+ -- Nothing in between the parens
+ local comma
+ if paren_finish == paren_start + 1 then
+ comma = ""
+ else
+ comma = ", "
+ end
+ fn = string.sub(fn, 1, paren_start) .. "self" .. comma .. string.sub(fn, paren_start + 1)
+ end
+
-- add vanilla function
outStream:writeln(fn_type .. 'function ' .. fn .. '{}')
end
diff --git a/scripts/pvscheck.sh b/scripts/pvscheck.sh
index f054f6e6fe..f3371b485e 100755
--- a/scripts/pvscheck.sh
+++ b/scripts/pvscheck.sh
@@ -389,7 +389,7 @@ run_analysis() {(
detect_url() {
local url="${1:-detect}"
if test "$url" = detect ; then
- curl --silent -L 'https://www.viva64.com/en/pvs-studio-download/' \
+ curl --silent -L 'https://pvs-studio.com/en/pvs-studio/download-all/' \
| grep -o 'https\{0,1\}://[^"<>]\{1,\}/pvs-studio[^/"<>]*-x86_64\.tgz' \
|| echo FAILED
else
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index 2c9d655a15..8b422b3abe 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -506,11 +506,9 @@ if(WIN32)
"file(MAKE_DIRECTORY \"${PROJECT_BINARY_DIR}/windows_runtime_deps/platforms\")")
foreach(DEP_FILE IN ITEMS
ca-bundle.crt
- cat.exe
curl.exe
diff.exe
tee.exe
- tidy.exe
win32yank.exe
winpty-agent.exe
winpty.dll
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index c55dc39605..cc5a62a170 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -1426,6 +1426,10 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id,
/// - "eol": right after eol character (default)
/// - "overlay": display over the specified column, without
/// shifting the underlying text.
+/// - "right_align": display right aligned in the window.
+/// - virt_text_win_col : position the virtual text at a fixed
+/// window column (starting from the first
+/// text column)
/// - virt_text_hide : hide the virtual text when the background
/// text is selected or hidden due to
/// horizontal scroll 'nowrap'
@@ -1437,6 +1441,10 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id,
/// default
/// - "combine": combine with background text color
/// - "blend": blend with background text color.
+/// - hl_eol : when true, for a multiline highlight covering the
+/// EOL of a line, continue the highlight for the rest
+/// of the screen line (just like for diff and
+/// cursorline highlight).
///
/// - ephemeral : for use with |nvim_set_decoration_provider|
/// callbacks. The mark will only be used for the current
@@ -1570,17 +1578,33 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id,
decor.virt_text_pos = kVTEndOfLine;
} else if (strequal("overlay", str.data)) {
decor.virt_text_pos = kVTOverlay;
+ } else if (strequal("right_align", str.data)) {
+ decor.virt_text_pos = kVTRightAlign;
} else {
api_set_error(err, kErrorTypeValidation,
"virt_text_pos: invalid value");
goto error;
}
+ } else if (strequal("virt_text_win_col", k.data)) {
+ if (v->type != kObjectTypeInteger) {
+ api_set_error(err, kErrorTypeValidation,
+ "virt_text_win_col is not a Number of the correct size");
+ goto error;
+ }
+
+ decor.col = (int)v->data.integer;
+ decor.virt_text_pos = kVTWinCol;
} else if (strequal("virt_text_hide", k.data)) {
decor.virt_text_hide = api_object_to_bool(*v,
"virt_text_hide", false, err);
if (ERROR_SET(err)) {
goto error;
}
+ } else if (strequal("hl_eol", k.data)) {
+ decor.hl_eol = api_object_to_bool(*v, "hl_eol", false, err);
+ if (ERROR_SET(err)) {
+ goto error;
+ }
} else if (strequal("hl_mode", k.data)) {
if (v->type != kObjectTypeString) {
api_set_error(err, kErrorTypeValidation,
@@ -1664,12 +1688,21 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id,
col2 = 0;
}
+ if (decor.virt_text_pos == kVTRightAlign) {
+ decor.col = 0;
+ for (size_t i = 0; i < kv_size(decor.virt_text); i++) {
+ decor.col += mb_string2cells((char_u *)kv_A(decor.virt_text, i).text);
+ }
+ }
+
+
Decoration *d = NULL;
if (ephemeral) {
d = &decor;
} else if (kv_size(decor.virt_text)
- || decor.priority != DECOR_PRIORITY_BASE) {
+ || decor.priority != DECOR_PRIORITY_BASE
+ || decor.hl_eol) {
// TODO(bfredl): this is a bit sketchy. eventually we should
// have predefined decorations for both marks/ephemerals
d = xcalloc(1, sizeof(*d));
@@ -1680,7 +1713,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id,
// TODO(bfredl): synergize these two branches even more
if (ephemeral && decor_state.buf == buf) {
- decor_add_ephemeral((int)line, (int)col, line2, col2, &decor, 0);
+ decor_add_ephemeral((int)line, (int)col, line2, col2, &decor);
} else {
if (ephemeral) {
api_set_error(err, kErrorTypeException, "not yet implemented");
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index c73a9195c3..24ba6110c4 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1765,6 +1765,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
{ "double", { "╔", "═", "╗", "║", "╝", "═", "╚", "║" }, false },
{ "single", { "┌", "─", "┐", "│", "┘", "─", "└", "│" }, false },
{ "shadow", { "", "", " ", " ", " ", " ", " ", "" }, true },
+ { "solid", { " ", " ", " ", " ", " ", " ", " ", " " }, false },
{ NULL, { { NUL } } , false },
};
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index b5e53beabe..c363c77afb 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -104,10 +104,14 @@ String nvim_exec(String src, Boolean output, Error *err)
}
try_start();
- msg_silent++;
+ if (output) {
+ msg_silent++;
+ }
do_source_str(src.data, "nvim_exec()");
- capture_ga = save_capture_ga;
- msg_silent = save_msg_silent;
+ if (output) {
+ capture_ga = save_capture_ga;
+ msg_silent = save_msg_silent;
+ }
try_end(err);
if (ERROR_SET(err)) {
@@ -1263,6 +1267,7 @@ fail:
/// @param buffer the buffer to use (expected to be empty)
/// @param opts Optional parameters. Reserved for future use.
/// @param[out] err Error details, if any
+/// @return Channel id, or 0 on error
Integer nvim_open_term(Buffer buffer, Dictionary opts, Error *err)
FUNC_API_SINCE(7)
{
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index 89fa2f86fb..f942d6b19f 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -381,7 +381,7 @@ Integer nvim_win_get_number(Window window, Error *err)
}
int tabnr;
- win_get_tabwin(window, &tabnr, &rv);
+ win_get_tabwin(win->handle, &tabnr, &rv);
return rv;
}
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index c98f2786c2..ce4163fccf 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -1844,7 +1844,7 @@ buf_T *buflist_new(char_u *ffname_arg, char_u *sfname_arg, linenr_T lnum,
EMSG(_("W14: Warning: List of file names overflow"));
if (emsg_silent == 0) {
ui_flush();
- os_delay(3000L, true); // make sure it is noticed
+ os_delay(3001L, true); // make sure it is noticed
}
top_file_num = 1;
}
diff --git a/src/nvim/change.c b/src/nvim/change.c
index 38bd591eca..74e27ca880 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -68,7 +68,7 @@ void change_warning(int col)
(void)msg_end();
if (msg_silent == 0 && !silent_mode && ui_active()) {
ui_flush();
- os_delay(1000L, true); // give the user time to think about it
+ os_delay(1002L, true); // give the user time to think about it
}
curbuf->b_did_warn = true;
redraw_cmdline = false; // don't redraw and erase the message
@@ -109,7 +109,7 @@ void changed(void)
// and don't let the emsg() set msg_scroll.
if (need_wait_return && emsg_silent == 0) {
ui_flush();
- os_delay(2000L, true);
+ os_delay(2002L, true);
wait_return(true);
msg_scroll = save_msg_scroll;
} else {
diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c
index 52a48ae6fb..ca1d141dd8 100644
--- a/src/nvim/decoration.c
+++ b/src/nvim/decoration.c
@@ -144,9 +144,9 @@ bool decor_redraw_reset(buf_T *buf, DecorState *state)
state->row = -1;
state->buf = buf;
for (size_t i = 0; i < kv_size(state->active); i++) {
- HlRange item = kv_A(state->active, i);
+ DecorRange item = kv_A(state->active, i);
if (item.virt_text_owned) {
- clear_virttext(&item.virt_text);
+ clear_virttext(&item.decor.virt_text);
}
}
kv_size(state->active) = 0;
@@ -190,14 +190,14 @@ bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state)
if (mark.id&MARKTREE_END_FLAG) {
decor_add(state, altpos.row, altpos.col, mark.row, mark.col,
- decor, false, 0);
+ decor, false);
} else {
if (altpos.row == -1) {
altpos.row = mark.row;
altpos.col = mark.col;
}
decor_add(state, mark.row, mark.col, altpos.row, altpos.col,
- decor, false, 0);
+ decor, false);
}
next_mark:
@@ -222,22 +222,23 @@ bool decor_redraw_line(buf_T *buf, int row, DecorState *state)
}
static void decor_add(DecorState *state, int start_row, int start_col,
- int end_row, int end_col, Decoration *decor, bool owned,
- DecorPriority priority)
+ int end_row, int end_col, Decoration *decor, bool owned)
{
int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0;
- HlRange range = { start_row, start_col, end_row, end_col,
- attr_id, MAX(priority, decor->priority),
- decor->virt_text,
- decor->virt_text_pos, decor->virt_text_hide, decor->hl_mode,
+ DecorRange range = { start_row, start_col, end_row, end_col,
+ *decor, attr_id,
kv_size(decor->virt_text) && owned, -1 };
+ if (decor->virt_text_pos == kVTEndOfLine) {
+ range.win_col = -2; // handled separately
+ }
+
kv_pushp(state->active);
size_t index;
for (index = kv_size(state->active)-1; index > 0; index--) {
- HlRange item = kv_A(state->active, index-1);
- if (item.priority <= range.priority) {
+ DecorRange item = kv_A(state->active, index-1);
+ if (item.decor.priority <= range.decor.priority) {
break;
}
kv_A(state->active, index) = kv_A(state->active, index-1);
@@ -245,7 +246,7 @@ static void decor_add(DecorState *state, int start_row, int start_col,
kv_A(state->active, index) = range;
}
-int decor_redraw_col(buf_T *buf, int col, int virt_col, bool hidden,
+int decor_redraw_col(buf_T *buf, int col, int win_col, bool hidden,
DecorState *state)
{
if (col <= state->col_until) {
@@ -291,7 +292,7 @@ int decor_redraw_col(buf_T *buf, int col, int virt_col, bool hidden,
}
decor_add(state, mark.row, mark.col, endpos.row, endpos.col,
- decor, false, 0);
+ decor, false);
next_mark:
marktree_itr_next(buf->b_marktree, state->itr);
@@ -300,11 +301,11 @@ next_mark:
int attr = 0;
size_t j = 0;
for (size_t i = 0; i < kv_size(state->active); i++) {
- HlRange item = kv_A(state->active, i);
+ DecorRange item = kv_A(state->active, i);
bool active = false, keep = true;
if (item.end_row < state->row
|| (item.end_row == state->row && item.end_col <= col)) {
- if (!(item.start_row >= state->row && kv_size(item.virt_text))) {
+ if (!(item.start_row >= state->row && kv_size(item.decor.virt_text))) {
keep = false;
}
} else {
@@ -324,13 +325,14 @@ next_mark:
attr = hl_combine_attr(attr, item.attr_id);
}
if ((item.start_row == state->row && item.start_col <= col)
- && kv_size(item.virt_text) && item.virt_col == -1) {
- item.virt_col = (item.virt_text_hide && hidden) ? -2 : virt_col;
+ && kv_size(item.decor.virt_text)
+ && item.decor.virt_text_pos == kVTOverlay && item.win_col == -1) {
+ item.win_col = (item.decor.virt_text_hide && hidden) ? -2 : win_col;
}
if (keep) {
kv_A(state->active, j++) = item;
} else if (item.virt_text_owned) {
- clear_virttext(&item.virt_text);
+ clear_virttext(&item.decor.virt_text);
}
}
kv_size(state->active) = j;
@@ -343,28 +345,39 @@ void decor_redraw_end(DecorState *state)
state->buf = NULL;
}
-VirtText decor_redraw_virt_text(buf_T *buf, DecorState *state)
+VirtText decor_redraw_eol(buf_T *buf, DecorState *state, int *eol_attr,
+ bool *aligned)
{
decor_redraw_col(buf, MAXCOL, MAXCOL, false, state);
+ VirtText text = VIRTTEXT_EMPTY;
for (size_t i = 0; i < kv_size(state->active); i++) {
- HlRange item = kv_A(state->active, i);
- if (item.start_row == state->row && kv_size(item.virt_text)
- && item.virt_text_pos == kVTEndOfLine) {
- return item.virt_text;
+ DecorRange item = kv_A(state->active, i);
+ if (item.start_row == state->row && kv_size(item.decor.virt_text)) {
+ if (!kv_size(text) && item.decor.virt_text_pos == kVTEndOfLine) {
+ text = item.decor.virt_text;
+ } else if (item.decor.virt_text_pos == kVTRightAlign
+ || item.decor.virt_text_pos == kVTWinCol) {
+ *aligned = true;
+ }
+ }
+
+
+ if (item.decor.hl_eol && item.start_row <= state->row) {
+ *eol_attr = hl_combine_attr(*eol_attr, item.attr_id);
}
}
- return VIRTTEXT_EMPTY;
+
+ return text;
}
void decor_add_ephemeral(int start_row, int start_col, int end_row, int end_col,
- Decoration *decor, DecorPriority priority)
+ Decoration *decor)
{
if (end_row == -1) {
end_row = start_row;
end_col = start_col;
}
- decor_add(&decor_state, start_row, start_col, end_row, end_col, decor, true,
- priority);
+ decor_add(&decor_state, start_row, start_col, end_row, end_col, decor, true);
}
diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h
index c5424a1642..4cebc0b731 100644
--- a/src/nvim/decoration.h
+++ b/src/nvim/decoration.h
@@ -21,6 +21,8 @@ typedef uint16_t DecorPriority;
typedef enum {
kVTEndOfLine,
kVTOverlay,
+ kVTWinCol,
+ kVTRightAlign,
} VirtTextPos;
typedef enum {
@@ -37,33 +39,29 @@ struct Decoration
VirtTextPos virt_text_pos;
bool virt_text_hide;
HlMode hl_mode;
+ bool hl_eol;
// TODO(bfredl): style, signs, etc
DecorPriority priority;
bool shared; // shared decoration, don't free
+ int col; // fixed col value, like win_col
};
#define DECORATION_INIT { 0, KV_INITIAL_VALUE, kVTEndOfLine, false, \
- kHlModeUnknown, DECOR_PRIORITY_BASE, false }
+ kHlModeUnknown, false, DECOR_PRIORITY_BASE, false, 0 }
typedef struct {
int start_row;
int start_col;
int end_row;
int end_col;
- int attr_id;
- // TODO(bfredl): embed decoration instead, perhaps using an arena
- // for ephemerals?
- DecorPriority priority;
- VirtText virt_text;
- VirtTextPos virt_text_pos;
- bool virt_text_hide;
- HlMode hl_mode;
+ Decoration decor;
+ int attr_id; // cached lookup of decor.hl_id
bool virt_text_owned;
- int virt_col;
-} HlRange;
+ int win_col;
+} DecorRange;
typedef struct {
MarkTreeIter itr[1];
- kvec_t(HlRange) active;
+ kvec_t(DecorRange) active;
buf_T *buf;
int top_row;
int row;
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index ea13052f25..999cc74185 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -1604,13 +1604,20 @@ void edit_putchar(int c, bool highlight)
}
}
+/// Return the effective prompt for the specified buffer.
+char_u *buf_prompt_text(const buf_T *const buf)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (buf->b_prompt_text == NULL) {
+ return (char_u *)"% ";
+ }
+ return buf->b_prompt_text;
+}
+
// Return the effective prompt for the current buffer.
-char_u *prompt_text(void)
+char_u *prompt_text(void) FUNC_ATTR_WARN_UNUSED_RESULT
{
- if (curbuf->b_prompt_text == NULL) {
- return (char_u *)"% ";
- }
- return curbuf->b_prompt_text;
+ return buf_prompt_text(curbuf);
}
// Prepare for prompt mode: Make sure the last line has the prompt text.
@@ -2058,7 +2065,7 @@ static bool check_compl_option(bool dict_opt)
vim_beep(BO_COMPL);
setcursor();
ui_flush();
- os_delay(2000L, false);
+ os_delay(2004L, false);
}
return false;
}
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index b310fd49b0..05d429c7d5 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -3417,8 +3417,7 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate)
{
typval_T var2;
char_u *p;
- exptype_T type = TYPE_UNKNOWN;
- bool type_is = false; // true for "is" and "isnot"
+ exprtype_T type = EXPR_UNKNOWN;
int len = 2;
bool ic;
@@ -3430,35 +3429,42 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate)
p = *arg;
switch (p[0]) {
- case '=': if (p[1] == '=')
- type = TYPE_EQUAL;
- else if (p[1] == '~')
- type = TYPE_MATCH;
+ case '=':
+ if (p[1] == '=') {
+ type = EXPR_EQUAL;
+ } else if (p[1] == '~') {
+ type = EXPR_MATCH;
+ }
break;
- case '!': if (p[1] == '=')
- type = TYPE_NEQUAL;
- else if (p[1] == '~')
- type = TYPE_NOMATCH;
+ case '!':
+ if (p[1] == '=') {
+ type = EXPR_NEQUAL;
+ } else if (p[1] == '~') {
+ type = EXPR_NOMATCH;
+ }
break;
- case '>': if (p[1] != '=') {
- type = TYPE_GREATER;
+ case '>':
+ if (p[1] != '=') {
+ type = EXPR_GREATER;
len = 1;
- } else
- type = TYPE_GEQUAL;
+ } else {
+ type = EXPR_GEQUAL;
+ }
break;
- case '<': if (p[1] != '=') {
- type = TYPE_SMALLER;
+ case '<':
+ if (p[1] != '=') {
+ type = EXPR_SMALLER;
len = 1;
- } else
- type = TYPE_SEQUAL;
+ } else {
+ type = EXPR_SEQUAL;
+ }
break;
case 'i': if (p[1] == 's') {
if (p[2] == 'n' && p[3] == 'o' && p[4] == 't') {
len = 5;
}
if (!isalnum(p[len]) && p[len] != '_') {
- type = len == 2 ? TYPE_EQUAL : TYPE_NEQUAL;
- type_is = true;
+ type = len == 2 ? EXPR_IS : EXPR_ISNOT;
}
}
break;
@@ -3467,7 +3473,7 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate)
/*
* If there is a comparative operator, use it.
*/
- if (type != TYPE_UNKNOWN) {
+ if (type != EXPR_UNKNOWN) {
// extra question mark appended: ignore case
if (p[len] == '?') {
ic = true;
@@ -3486,7 +3492,7 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate)
return FAIL;
}
if (evaluate) {
- const int ret = typval_compare(rettv, &var2, type, type_is, ic);
+ const int ret = typval_compare(rettv, &var2, type, ic);
tv_clear(&var2);
return ret;
@@ -10582,27 +10588,27 @@ bool invoke_prompt_interrupt(void)
int typval_compare(
typval_T *typ1, // first operand
typval_T *typ2, // second operand
- exptype_T type, // operator
- bool type_is, // true for "is" and "isnot"
+ exprtype_T type, // operator
bool ic // ignore case
)
FUNC_ATTR_NONNULL_ALL
{
varnumber_T n1, n2;
+ const bool type_is = type == EXPR_IS || type == EXPR_ISNOT;
if (type_is && typ1->v_type != typ2->v_type) {
// For "is" a different type always means false, for "notis"
// it means true.
- n1 = type == TYPE_NEQUAL;
+ n1 = type == EXPR_ISNOT;
} else if (typ1->v_type == VAR_LIST || typ2->v_type == VAR_LIST) {
if (type_is) {
n1 = typ1->v_type == typ2->v_type
&& typ1->vval.v_list == typ2->vval.v_list;
- if (type == TYPE_NEQUAL) {
+ if (type == EXPR_ISNOT) {
n1 = !n1;
}
} else if (typ1->v_type != typ2->v_type
- || (type != TYPE_EQUAL && type != TYPE_NEQUAL)) {
+ || (type != EXPR_EQUAL && type != EXPR_NEQUAL)) {
if (typ1->v_type != typ2->v_type) {
EMSG(_("E691: Can only compare List with List"));
} else {
@@ -10613,7 +10619,7 @@ int typval_compare(
} else {
// Compare two Lists for being equal or unequal.
n1 = tv_list_equal(typ1->vval.v_list, typ2->vval.v_list, ic, false);
- if (type == TYPE_NEQUAL) {
+ if (type == EXPR_NEQUAL) {
n1 = !n1;
}
}
@@ -10621,11 +10627,11 @@ int typval_compare(
if (type_is) {
n1 = typ1->v_type == typ2->v_type
&& typ1->vval.v_dict == typ2->vval.v_dict;
- if (type == TYPE_NEQUAL) {
+ if (type == EXPR_ISNOT) {
n1 = !n1;
}
} else if (typ1->v_type != typ2->v_type
- || (type != TYPE_EQUAL && type != TYPE_NEQUAL)) {
+ || (type != EXPR_EQUAL && type != EXPR_NEQUAL)) {
if (typ1->v_type != typ2->v_type) {
EMSG(_("E735: Can only compare Dictionary with Dictionary"));
} else {
@@ -10636,12 +10642,13 @@ int typval_compare(
} else {
// Compare two Dictionaries for being equal or unequal.
n1 = tv_dict_equal(typ1->vval.v_dict, typ2->vval.v_dict, ic, false);
- if (type == TYPE_NEQUAL) {
+ if (type == EXPR_NEQUAL) {
n1 = !n1;
}
}
} else if (tv_is_func(*typ1) || tv_is_func(*typ2)) {
- if (type != TYPE_EQUAL && type != TYPE_NEQUAL) {
+ if (type != EXPR_EQUAL && type != EXPR_NEQUAL
+ && type != EXPR_IS && type != EXPR_ISNOT) {
EMSG(_("E694: Invalid operation for Funcrefs"));
tv_clear(typ1);
return FAIL;
@@ -10663,43 +10670,47 @@ int typval_compare(
} else {
n1 = tv_equal(typ1, typ2, ic, false);
}
- if (type == TYPE_NEQUAL) {
+ if (type == EXPR_NEQUAL || type == EXPR_ISNOT) {
n1 = !n1;
}
} else if ((typ1->v_type == VAR_FLOAT || typ2->v_type == VAR_FLOAT)
- && type != TYPE_MATCH && type != TYPE_NOMATCH) {
+ && type != EXPR_MATCH && type != EXPR_NOMATCH) {
// If one of the two variables is a float, compare as a float.
// When using "=~" or "!~", always compare as string.
const float_T f1 = tv_get_float(typ1);
const float_T f2 = tv_get_float(typ2);
n1 = false;
switch (type) {
- case TYPE_EQUAL: n1 = f1 == f2; break;
- case TYPE_NEQUAL: n1 = f1 != f2; break;
- case TYPE_GREATER: n1 = f1 > f2; break;
- case TYPE_GEQUAL: n1 = f1 >= f2; break;
- case TYPE_SMALLER: n1 = f1 < f2; break;
- case TYPE_SEQUAL: n1 = f1 <= f2; break;
- case TYPE_UNKNOWN:
- case TYPE_MATCH:
- case TYPE_NOMATCH: break;
+ case EXPR_IS:
+ case EXPR_EQUAL: n1 = f1 == f2; break;
+ case EXPR_ISNOT:
+ case EXPR_NEQUAL: n1 = f1 != f2; break;
+ case EXPR_GREATER: n1 = f1 > f2; break;
+ case EXPR_GEQUAL: n1 = f1 >= f2; break;
+ case EXPR_SMALLER: n1 = f1 < f2; break;
+ case EXPR_SEQUAL: n1 = f1 <= f2; break;
+ case EXPR_UNKNOWN:
+ case EXPR_MATCH:
+ case EXPR_NOMATCH: break; // avoid gcc warning
}
} else if ((typ1->v_type == VAR_NUMBER || typ2->v_type == VAR_NUMBER)
- && type != TYPE_MATCH && type != TYPE_NOMATCH) {
+ && type != EXPR_MATCH && type != EXPR_NOMATCH) {
// If one of the two variables is a number, compare as a number.
// When using "=~" or "!~", always compare as string.
n1 = tv_get_number(typ1);
n2 = tv_get_number(typ2);
switch (type) {
- case TYPE_EQUAL: n1 = n1 == n2; break;
- case TYPE_NEQUAL: n1 = n1 != n2; break;
- case TYPE_GREATER: n1 = n1 > n2; break;
- case TYPE_GEQUAL: n1 = n1 >= n2; break;
- case TYPE_SMALLER: n1 = n1 < n2; break;
- case TYPE_SEQUAL: n1 = n1 <= n2; break;
- case TYPE_UNKNOWN:
- case TYPE_MATCH:
- case TYPE_NOMATCH: break;
+ case EXPR_IS:
+ case EXPR_EQUAL: n1 = n1 == n2; break;
+ case EXPR_ISNOT:
+ case EXPR_NEQUAL: n1 = n1 != n2; break;
+ case EXPR_GREATER: n1 = n1 > n2; break;
+ case EXPR_GEQUAL: n1 = n1 >= n2; break;
+ case EXPR_SMALLER: n1 = n1 < n2; break;
+ case EXPR_SEQUAL: n1 = n1 <= n2; break;
+ case EXPR_UNKNOWN:
+ case EXPR_MATCH:
+ case EXPR_NOMATCH: break; // avoid gcc warning
}
} else {
char buf1[NUMBUFLEN];
@@ -10707,28 +10718,30 @@ int typval_compare(
const char *const s1 = tv_get_string_buf(typ1, buf1);
const char *const s2 = tv_get_string_buf(typ2, buf2);
int i;
- if (type != TYPE_MATCH && type != TYPE_NOMATCH) {
+ if (type != EXPR_MATCH && type != EXPR_NOMATCH) {
i = mb_strcmp_ic(ic, s1, s2);
} else {
i = 0;
}
n1 = false;
switch (type) {
- case TYPE_EQUAL: n1 = i == 0; break;
- case TYPE_NEQUAL: n1 = i != 0; break;
- case TYPE_GREATER: n1 = i > 0; break;
- case TYPE_GEQUAL: n1 = i >= 0; break;
- case TYPE_SMALLER: n1 = i < 0; break;
- case TYPE_SEQUAL: n1 = i <= 0; break;
-
- case TYPE_MATCH:
- case TYPE_NOMATCH:
+ case EXPR_IS:
+ case EXPR_EQUAL: n1 = i == 0; break;
+ case EXPR_ISNOT:
+ case EXPR_NEQUAL: n1 = i != 0; break;
+ case EXPR_GREATER: n1 = i > 0; break;
+ case EXPR_GEQUAL: n1 = i >= 0; break;
+ case EXPR_SMALLER: n1 = i < 0; break;
+ case EXPR_SEQUAL: n1 = i <= 0; break;
+
+ case EXPR_MATCH:
+ case EXPR_NOMATCH:
n1 = pattern_match((char_u *)s2, (char_u *)s1, ic);
- if (type == TYPE_NOMATCH) {
+ if (type == EXPR_NOMATCH) {
n1 = !n1;
}
break;
- case TYPE_UNKNOWN: break; // Avoid gcc warning.
+ case EXPR_UNKNOWN: break; // avoid gcc warning
}
}
tv_clear(typ1);
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index a62d87fcc4..3da4bb8655 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -230,16 +230,18 @@ typedef enum
/// types for expressions.
typedef enum {
- TYPE_UNKNOWN = 0,
- TYPE_EQUAL, ///< ==
- TYPE_NEQUAL, ///< !=
- TYPE_GREATER, ///< >
- TYPE_GEQUAL, ///< >=
- TYPE_SMALLER, ///< <
- TYPE_SEQUAL, ///< <=
- TYPE_MATCH, ///< =~
- TYPE_NOMATCH, ///< !~
-} exptype_T;
+ EXPR_UNKNOWN = 0,
+ EXPR_EQUAL, ///< ==
+ EXPR_NEQUAL, ///< !=
+ EXPR_GREATER, ///< >
+ EXPR_GEQUAL, ///< >=
+ EXPR_SMALLER, ///< <
+ EXPR_SEQUAL, ///< <=
+ EXPR_MATCH, ///< =~
+ EXPR_NOMATCH, ///< !~
+ EXPR_IS, ///< is
+ EXPR_ISNOT, ///< isnot
+} exprtype_T;
/// Type for dict_list function
typedef enum {
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index b10e99fc08..77e7c7b3a9 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -251,6 +251,7 @@ return {
pow={args=2},
prevnonblank={args=1},
printf={args=varargs(1)},
+ prompt_getprompt={args=1},
prompt_setcallback={args={2, 2}},
prompt_setinterrupt={args={2, 2}},
prompt_setprompt={args={2, 2}},
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 0d288e2cc2..6d328953f6 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -602,12 +602,7 @@ static void f_bufname(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (argvars[0].v_type == VAR_UNKNOWN) {
buf = curbuf;
} else {
- if (!tv_check_str_or_nr(&argvars[0])) {
- return;
- }
- emsg_off++;
- buf = tv_get_buf(&argvars[0], false);
- emsg_off--;
+ buf = tv_get_buf_from_arg(&argvars[0]);
}
if (buf != NULL && buf->b_fname != NULL) {
rettv->vval.v_string = (char_u *)xstrdup((char *)buf->b_fname);
@@ -627,6 +622,9 @@ static void f_bufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (argvars[0].v_type == VAR_UNKNOWN) {
buf = curbuf;
} else {
+ // Don't use tv_get_buf_from_arg(); we continue if the buffer wasn't found
+ // and the second argument isn't zero, but we want to return early if the
+ // first argument isn't a string or number so only one error is shown.
if (!tv_check_str_or_nr(&argvars[0])) {
return;
}
@@ -653,18 +651,12 @@ static void f_bufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static void buf_win_common(typval_T *argvars, typval_T *rettv, bool get_nr)
{
- if (!tv_check_str_or_nr(&argvars[0])) {
+ const buf_T *const buf = tv_get_buf_from_arg(&argvars[0]);
+ if (buf == NULL) { // no need to search if invalid arg or buffer not found
rettv->vval.v_number = -1;
return;
}
- emsg_off++;
- buf_T *buf = tv_get_buf(&argvars[0], true);
- if (buf == NULL) { // no need to search if buffer was not found
- rettv->vval.v_number = -1;
- goto end;
- }
-
int winnr = 0;
int winid;
bool found_buf = false;
@@ -677,8 +669,6 @@ static void buf_win_common(typval_T *argvars, typval_T *rettv, bool get_nr)
}
}
rettv->vval.v_number = (found_buf ? (get_nr ? winnr : winid) : -1);
-end:
- emsg_off--;
}
/// "bufwinid(nr)" function
@@ -731,6 +721,18 @@ buf_T *tv_get_buf(typval_T *tv, int curtab_only)
return buf;
}
+/// Like tv_get_buf() but give an error message if the type is wrong.
+buf_T *tv_get_buf_from_arg(typval_T *const tv) FUNC_ATTR_NONNULL_ALL
+{
+ if (!tv_check_str_or_nr(tv)) {
+ return NULL;
+ }
+ emsg_off++;
+ buf_T *const buf = tv_get_buf(tv, false);
+ emsg_off--;
+ return buf;
+}
+
/// Get the buffer from "arg" and give an error and return NULL if it is not
/// valid.
buf_T * get_buf_arg(typval_T *arg)
@@ -2799,13 +2801,9 @@ static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
} else if (argvars[0].v_type != VAR_UNKNOWN) {
// Information about one buffer. Argument specifies the buffer
- if (tv_check_num(&argvars[0])) { // issue errmsg if type error
- emsg_off++;
- argbuf = tv_get_buf(&argvars[0], false);
- emsg_off--;
- if (argbuf == NULL) {
- return;
- }
+ argbuf = tv_get_buf_from_arg(&argvars[0]);
+ if (argbuf == NULL) {
+ return;
}
}
@@ -2875,13 +2873,7 @@ static void get_buffer_lines(buf_T *buf,
*/
static void f_getbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- buf_T *buf = NULL;
-
- if (tv_check_str_or_nr(&argvars[0])) {
- emsg_off++;
- buf = tv_get_buf(&argvars[0], false);
- emsg_off--;
- }
+ buf_T *const buf = tv_get_buf_from_arg(&argvars[0]);
const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf);
const linenr_T end = (argvars[2].v_type == VAR_UNKNOWN
@@ -6499,6 +6491,26 @@ static void f_prompt_setinterrupt(typval_T *argvars,
buf->b_prompt_interrupt= interrupt_callback;
}
+/// "prompt_getprompt({buffer})" function
+void f_prompt_getprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // return an empty string by default, e.g. it's not a prompt buffer
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ buf_T *const buf = tv_get_buf_from_arg(&argvars[0]);
+ if (buf == NULL) {
+ return;
+ }
+
+ if (!bt_prompt(buf)) {
+ return;
+ }
+
+ rettv->vval.v_string = vim_strsave(buf_prompt_text(buf));
+}
+
// "prompt_setprompt({buffer}, {text})" function
static void f_prompt_setprompt(typval_T *argvars,
typval_T *rettv, FunPtr fptr)
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index 689d05e079..00260bc3f7 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -833,6 +833,8 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
bool islambda = false;
char_u numbuf[NUMBUFLEN];
char_u *name;
+ typval_T *tv_to_free[MAX_FUNC_ARGS];
+ int tv_to_free_len = 0;
proftime_T wait_start;
proftime_T call_start;
int started_profiling = false;
@@ -985,6 +987,11 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
v->di_tv = isdefault ? def_rettv : argvars[i];
v->di_tv.v_lock = VAR_FIXED;
+ if (isdefault) {
+ // Need to free this later, no matter where it's stored.
+ tv_to_free[tv_to_free_len++] = &v->di_tv;
+ }
+
if (addlocal) {
// Named arguments can be accessed without the "a:" prefix in lambda
// expressions. Add to the l: dict.
@@ -1209,7 +1216,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
did_emsg |= save_did_emsg;
depth--;
-
+ for (int i = 0; i < tv_to_free_len; i++) {
+ tv_clear(tv_to_free[i]);
+ }
cleanup_function_call(fc);
if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) {
diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c
index 0b1ecb12e2..c02f730431 100644
--- a/src/nvim/event/libuv_process.c
+++ b/src/nvim/event/libuv_process.c
@@ -82,7 +82,7 @@ int libuv_process_spawn(LibuvProcess *uvproc)
int status;
if ((status = uv_spawn(&proc->loop->uv, &uvproc->uv, &uvproc->uvopts))) {
- ELOG("uv_spawn failed: %s", uv_strerror(status));
+ ELOG("uv_spawn(%s) failed: %s", uvproc->uvopts.file, uv_strerror(status));
if (uvproc->uvopts.env) {
os_free_fullenv(uvproc->uvopts.env);
}
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index b191e8cf67..3e330b88a2 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -968,12 +968,6 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
mark_adjust_nofold(last_line - num_lines + 1, last_line,
-(last_line - dest - extra), 0L, kExtmarkNOOP);
- // extmarks are handled separately
- 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);
// send update regarding the new lines that were added
@@ -995,6 +989,11 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
smsg(_("%" PRId64 " lines moved"), (int64_t)num_lines);
}
+ extmark_move_region(curbuf, line1-1, 0, start_byte,
+ line2-line1+1, 0, extent_byte,
+ dest+line_off, 0, dest_byte+byte_off,
+ kExtmarkUndo);
+
/*
* Leave the cursor on the last of the moved lines.
*/
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index 2965ea7496..d99383303b 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -928,6 +928,12 @@ module.cmds = {
func='ex_edit',
},
{
+ command='eval',
+ flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN),
+ addr_type='ADDR_NONE',
+ func='ex_eval',
+ },
+ {
command='ex',
flags=bit.bor(BANG, FILE1, CMDARG, ARGOPT, TRLBAR),
addr_type='ADDR_NONE',
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index e394edb032..317ca465e1 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -876,7 +876,7 @@ debuggy_find(
debug_newval = typval_tostring(bp->dbg_val);
line = true;
} else {
- if (typval_compare(tv, bp->dbg_val, TYPE_EQUAL, true, false) == OK
+ if (typval_compare(tv, bp->dbg_val, EXPR_IS, false) == OK
&& tv->vval.v_number == false) {
line = true;
debug_oldval = typval_tostring(bp->dbg_val);
@@ -2719,16 +2719,13 @@ static char_u *get_str_line(int c, void *cookie, int indent, bool do_concat)
while (!(p->buf[i] == '\n' || p->buf[i] == '\0')) {
i++;
}
- char buf[2046];
- char *dst;
- dst = xstpncpy(buf, (char *)p->buf + p->offset, i - p->offset);
- if ((uint32_t)(dst - buf) != i - p->offset) {
- smsg(_(":source error parsing command %s"), p->buf);
- return NULL;
- }
- buf[i - p->offset] = '\0';
+ size_t line_length = i - p->offset;
+ garray_T ga;
+ ga_init(&ga, (int)sizeof(char_u), (int)line_length);
+ ga_concat_len(&ga, (char *)p->buf + p->offset, line_length);
+ ga_append(&ga, '\0');
p->offset = i + 1;
- return (char_u *)xstrdup(buf);
+ return ga.ga_data;
}
static int source_using_linegetter(void *cookie,
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index d1eddfc74f..ae5c334592 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -1857,6 +1857,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
case CMD_echoerr:
case CMD_echomsg:
case CMD_echon:
+ case CMD_eval:
case CMD_execute:
case CMD_filter:
case CMD_help:
diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c
index 0917c6dd02..5ca88002f1 100644
--- a/src/nvim/ex_eval.c
+++ b/src/nvim/ex_eval.c
@@ -788,6 +788,15 @@ void report_discard_pending(int pending, void *value)
}
}
+// ":eval".
+void ex_eval(exarg_T *eap)
+{
+ typval_T tv;
+
+ if (eval0(eap->arg, &tv, &eap->nextcmd, !eap->skip) == OK) {
+ tv_clear(&tv);
+ }
+}
/*
* ":if".
diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c
index 09453e100d..b11ec4ad05 100644
--- a/src/nvim/ex_session.c
+++ b/src/nvim/ex_session.c
@@ -690,18 +690,16 @@ static int makeopens(FILE *fd, char_u *dirnow)
return FAIL;
}
- //
- // Save current window layout.
- //
- PUTLINE_FAIL("set splitbelow splitright");
- if (ses_win_rec(fd, tab_topframe) == FAIL) {
- return FAIL;
- }
- if (!p_sb && put_line(fd, "set nosplitbelow") == FAIL) {
- return FAIL;
- }
- if (!p_spr && put_line(fd, "set nosplitright") == FAIL) {
- return FAIL;
+ if (tab_topframe->fr_layout != FR_LEAF) {
+ // Save current window layout.
+ PUTLINE_FAIL("let s:save_splitbelow = &splitbelow");
+ PUTLINE_FAIL("let s:save_splitright = &splitright");
+ PUTLINE_FAIL("set splitbelow splitright");
+ if (ses_win_rec(fd, tab_topframe) == FAIL) {
+ return FAIL;
+ }
+ PUTLINE_FAIL("let &splitbelow = s:save_splitbelow");
+ PUTLINE_FAIL("let &splitright = s:save_splitright");
}
//
@@ -720,22 +718,26 @@ static int makeopens(FILE *fd, char_u *dirnow)
}
}
- // Go to the first window.
- PUTLINE_FAIL("wincmd t");
-
- // If more than one window, see if sizes can be restored.
- // First set 'winheight' and 'winwidth' to 1 to avoid the windows being
- // resized when moving between windows.
- // Do this before restoring the view, so that the topline and the
- // cursor can be set. This is done again below.
- // winminheight and winminwidth need to be set to avoid an error if the
- // user has set winheight or winwidth.
- if (fprintf(fd,
- "set winminheight=0\n"
- "set winheight=1\n"
- "set winminwidth=0\n"
- "set winwidth=1\n") < 0) {
- return FAIL;
+ if (tab_firstwin->w_next != NULL) {
+ // Go to the first window.
+ PUTLINE_FAIL("wincmd t");
+
+ // If more than one window, see if sizes can be restored.
+ // First set 'winheight' and 'winwidth' to 1 to avoid the windows
+ // being resized when moving between windows.
+ // Do this before restoring the view, so that the topline and the
+ // cursor can be set. This is done again below.
+ // winminheight and winminwidth need to be set to avoid an error if
+ // the user has set winheight or winwidth.
+ PUTLINE_FAIL("let s:save_winminheight = &winminheight");
+ PUTLINE_FAIL("let s:save_winminwidth = &winminwidth");
+ if (fprintf(fd,
+ "set winminheight=0\n"
+ "set winheight=1\n"
+ "set winminwidth=0\n"
+ "set winwidth=1\n") < 0) {
+ return FAIL;
+ }
}
if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) {
return FAIL;
@@ -817,18 +819,20 @@ static int makeopens(FILE *fd, char_u *dirnow)
return FAIL;
}
- // Re-apply options.
+ // Re-apply 'winheight', 'winwidth' and 'shortmess'.
if (fprintf(fd,
"set winheight=%" PRId64 " winwidth=%" PRId64
- " winminheight=%" PRId64 " winminwidth=%" PRId64
" shortmess=%s\n",
(int64_t)p_wh,
(int64_t)p_wiw,
- (int64_t)p_wmh,
- (int64_t)p_wmw,
p_shm) < 0) {
return FAIL;
}
+ if (tab_firstwin->w_next != NULL) {
+ // Restore 'winminheight' and 'winminwidth'.
+ PUTLINE_FAIL("let &winminheight = s:save_winminheight");
+ PUTLINE_FAIL("let &winminwidth = s:save_winminwidth");
+ }
//
// Lastly, execute the x.vim file if it exists.
diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c
index cacbeddb32..2906a2196b 100644
--- a/src/nvim/extmark.c
+++ b/src/nvim/extmark.c
@@ -702,6 +702,7 @@ void extmark_move_region(
int new_row, colnr_T new_col, bcount_t new_byte,
ExtmarkOp undo)
{
+ curbuf->deleted_bytes2 = 0;
// 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.
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index 65bd809436..792ef81665 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -4947,11 +4947,11 @@ int buf_check_timestamp(buf_T *buf)
(void)msg_end();
if (emsg_silent == 0) {
ui_flush();
- /* give the user some time to think about it */
- os_delay(1000L, true);
+ // give the user some time to think about it
+ os_delay(1004L, true);
- /* don't redraw and erase the message */
- redraw_cmdline = FALSE;
+ // don't redraw and erase the message
+ redraw_cmdline = false;
}
}
already_warned = TRUE;
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index 9b8e9ff8cc..f99a2dd0fe 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -17,6 +17,7 @@
#include "nvim/api/vim.h"
#include "nvim/msgpack_rpc/channel.h"
#include "nvim/vim.h"
+#include "nvim/extmark.h"
#include "nvim/ex_getln.h"
#include "nvim/ex_cmds2.h"
#include "nvim/map.h"
@@ -1243,13 +1244,16 @@ void ex_luado(exarg_T *const eap)
break;
}
lua_pushvalue(lstate, -1);
- lua_pushstring(lstate, (const char *)ml_get_buf(curbuf, l, false));
+ const char *old_line = (const char *)ml_get_buf(curbuf, l, false);
+ lua_pushstring(lstate, old_line);
lua_pushnumber(lstate, (lua_Number)l);
if (lua_pcall(lstate, 2, 1, 0)) {
nlua_error(lstate, _("E5111: Error calling lua: %.*s"));
break;
}
if (lua_isstring(lstate, -1)) {
+ size_t old_line_len = STRLEN(old_line);
+
size_t new_line_len;
const char *const new_line = lua_tolstring(lstate, -1, &new_line_len);
char *const new_line_transformed = xmemdupz(new_line, new_line_len);
@@ -1259,7 +1263,7 @@ void ex_luado(exarg_T *const eap)
}
}
ml_replace(l, (char_u *)new_line_transformed, false);
- changed_bytes(l, 0);
+ inserted_bytes(l, 0, (int)old_line_len, (int)new_line_len);
}
lua_pop(lstate, 1);
}
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
index eb54ff28ee..3994c5bc5b 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -309,7 +309,9 @@ setmetatable(vim, {
})
-- An easier alias for commands.
-vim.cmd = vim.api.nvim_command
+vim.cmd = function(command)
+ return vim.api.nvim_exec(command, false)
+end
-- These are the vim.env/v/g/o/bo/wo variable magic accessors.
do
@@ -398,7 +400,10 @@ do
wfw = true; winbl = true; winblend = true; winfixheight = true;
winfixwidth = true; winhighlight = true; winhl = true; wrap = true;
}
+
+ --@private
local function new_buf_opt_accessor(bufnr)
+ --@private
local function get(k)
if window_options[k] then
return a.nvim_err_writeln(k.." is a window option, not a buffer option")
@@ -408,23 +413,34 @@ do
end
return a.nvim_buf_get_option(bufnr or 0, k)
end
+
+ --@private
local function set(k, v)
if window_options[k] then
return a.nvim_err_writeln(k.." is a window option, not a buffer option")
end
return a.nvim_buf_set_option(bufnr or 0, k, v)
end
+
return make_meta_accessor(get, set)
end
vim.bo = new_buf_opt_accessor(nil)
+
+ --@private
local function new_win_opt_accessor(winnr)
+
+ --@private
local function get(k)
if winnr == nil and type(k) == "number" then
return new_win_opt_accessor(k)
end
return a.nvim_win_get_option(winnr or 0, k)
end
- local function set(k, v) return a.nvim_win_set_option(winnr or 0, k, v) end
+
+ --@private
+ local function set(k, v)
+ return a.nvim_win_set_option(winnr or 0, k, v)
+ end
return make_meta_accessor(get, set)
end
vim.wo = new_win_opt_accessor(nil)
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 7064f2a068..56cd97f133 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -375,7 +375,7 @@ int main(int argc, char **argv)
// Does ":filetype plugin indent on".
filetype_maybe_enable();
// Sources syntax/syntax.vim, which calls `:filetype on`.
- syn_maybe_on();
+ syn_maybe_enable();
}
// Read all the plugin files.
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 7c98d3c6b5..1783f62247 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -2265,12 +2265,14 @@ void msg_scroll_up(bool may_throttle)
/// per screen update.
///
/// NB: The bookkeeping is quite messy, and rests on a bunch of poorly
-/// documented assumtions. For instance that the message area always grows while
-/// being throttled, messages are only being output on the last line etc.
+/// documented assumptions. For instance that the message area always grows
+/// while being throttled, messages are only being output on the last line
+/// etc.
///
-/// Probably message scrollback storage should reimplented as a file_buffer, and
-/// message scrolling in TUI be reimplemented as a modal floating window. Then
-/// we get throttling "for free" using standard redraw_later code paths.
+/// Probably message scrollback storage should be reimplemented as a
+/// file_buffer, and message scrolling in TUI be reimplemented as a modal
+/// floating window. Then we get throttling "for free" using standard
+/// redraw_later code paths.
void msg_scroll_flush(void)
{
if (msg_grid.throttled) {
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
index a0b439ac45..a2d8859c68 100644
--- a/src/nvim/msgpack_rpc/channel.c
+++ b/src/nvim/msgpack_rpc/channel.c
@@ -219,7 +219,7 @@ static void receive_msgpack(Stream *stream, RBuffer *rbuf, size_t c,
char buf[256];
snprintf(buf, sizeof(buf), "ch %" PRIu64 " was closed by the client",
channel->id);
- call_set_error(channel, buf, WARN_LOG_LEVEL);
+ call_set_error(channel, buf, INFO_LOG_LEVEL);
goto end;
}
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index f016ef6813..c948881eca 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -630,9 +630,9 @@ static void normal_redraw_mode_message(NormalState *s)
ui_cursor_shape(); // show different cursor shape
ui_flush();
if (msg_scroll || emsg_on_display) {
- os_delay(1000L, true); // wait at least one second
+ os_delay(1003L, true); // wait at least one second
}
- os_delay(3000L, false); // wait up to three seconds
+ os_delay(3003L, false); // wait up to three seconds
State = save_State;
msg_scroll = false;
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 2cd71f2360..190ca2e93b 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -1676,12 +1676,18 @@ int op_delete(oparg_T *oap)
curbuf_splice_pending++;
pos_T startpos = curwin->w_cursor; // start position for delete
+ bcount_t deleted_bytes = (bcount_t)STRLEN(
+ ml_get(startpos.lnum)) + 1 - startpos.col;
truncate_line(true); // delete from cursor to end of line
curpos = curwin->w_cursor; // remember curwin->w_cursor
curwin->w_cursor.lnum++;
+
+ for (linenr_T i = 1; i <= oap->line_count - 2; i++) {
+ deleted_bytes += (bcount_t)STRLEN(
+ ml_get(startpos.lnum + i)) + 1;
+ }
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);
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 914b92618c..666c526a18 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -7583,9 +7583,19 @@ int csh_like_shell(void)
/// buffer signs and on user configuration.
int win_signcol_count(win_T *wp)
{
+ return win_signcol_configured(wp, NULL);
+}
+
+/// Return the number of requested sign columns, based on user / configuration.
+int win_signcol_configured(win_T *wp, int *is_fixed)
+{
int minimum = 0, maximum = 1, needed_signcols;
const char *scl = (const char *)wp->w_p_scl;
+ if (is_fixed) {
+ *is_fixed = 1;
+ }
+
// Note: It checks "no" or "number" in 'signcolumn' option
if (*scl == 'n'
&& (*(scl + 1) == 'o' || (*(scl + 1) == 'u'
@@ -7603,7 +7613,11 @@ int win_signcol_count(win_T *wp)
return 1;
}
- // auto or auto:<NUM>
+ if (is_fixed) {
+ // auto or auto:<NUM>
+ *is_fixed = 0;
+ }
+
if (!strncmp(scl, "auto:", 5)) {
// Variable depending on a configuration
maximum = scl[5] - '0';
diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c
index d794969ab5..36d6dbe2db 100644
--- a/src/nvim/os/pty_process_unix.c
+++ b/src/nvim/os/pty_process_unix.c
@@ -175,7 +175,7 @@ static void init_child(PtyProcess *ptyproc)
Process *proc = (Process *)ptyproc;
if (proc->cwd && os_chdir(proc->cwd) != 0) {
- ELOG("chdir failed: %s", strerror(errno));
+ ELOG("chdir(%s) failed: %s", proc->cwd, strerror(errno));
return;
}
@@ -184,7 +184,7 @@ static void init_child(PtyProcess *ptyproc)
assert(proc->env);
environ = tv_dict_to_env(proc->env);
execvp(prog, proc->argv);
- ELOG("execvp failed: %s: %s", strerror(errno), prog);
+ ELOG("execvp(%s) failed: %s", prog, strerror(errno));
_exit(122); // 122 is EXEC_FAILED in the Vim source.
}
diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c
index 94444e4d23..2bf73d08e6 100644
--- a/src/nvim/os/pty_process_win.c
+++ b/src/nvim/os/pty_process_win.c
@@ -203,11 +203,13 @@ int pty_process_spawn(PtyProcess *ptyproc)
cleanup:
if (status) {
// In the case of an error of MultiByteToWideChar or CreateProcessW.
- ELOG("pty_process_spawn: %s: error code: %d", emsg, status);
+ ELOG("pty_process_spawn(%s): %s: error code: %d",
+ proc->argv[0], emsg, status);
status = os_translate_sys_error(status);
} else if (err != NULL) {
status = (int)winpty_error_code(err);
- ELOG("pty_process_spawn: %s: error code: %d", emsg, status);
+ ELOG("pty_process_spawn(%s): %s: error code: %d",
+ proc->argv[0], emsg, status);
status = translate_winpty_error(status);
}
winpty_error_free(err);
diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c
index e7e0dc4013..9ea74716aa 100644
--- a/src/nvim/os/time.c
+++ b/src/nvim/os/time.c
@@ -62,6 +62,7 @@ uint64_t os_now(void)
/// @param ignoreinput If true, only SIGINT (CTRL-C) can interrupt.
void os_delay(uint64_t ms, bool ignoreinput)
{
+ DLOG("%" PRIu64 " ms", ms);
if (ignoreinput) {
if (ms > INT_MAX) {
ms = INT_MAX;
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index d7693c7a6f..184f5da97d 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -6665,6 +6665,10 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest,
int len = 0; /* init for GCC */
static char_u *eval_result = NULL;
+ // We need to keep track of how many backslashes we escape, so that the byte
+ // counts for `extmark_splice` are correct.
+ int num_escaped = 0;
+
// Be paranoid...
if ((source == NULL && expr == NULL) || dest == NULL) {
EMSG(_(e_null));
@@ -6840,6 +6844,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest,
// later. Used to insert a literal CR.
default:
if (backslash) {
+ num_escaped += 1;
if (copy) {
*dst = '\\';
}
@@ -6979,7 +6984,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest,
*dst = NUL;
exit:
- return (int)((dst - dest) + 1);
+ return (int)((dst - dest) + 1 - num_escaped);
}
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 9fb2eb2772..5151d82c1b 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -2101,6 +2101,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
bool search_attr_from_match = false; // if search_attr is from :match
bool has_decor = false; // this buffer has decoration
bool do_virttext = false; // draw virtual text for this line
+ int win_col_offset; // offsett for window columns
char_u buf_fold[FOLD_TEXT_LEN + 1]; // Hold value returned by get_foldtext
@@ -2790,6 +2791,10 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
}
}
+ if (draw_state == WL_NR && n_extra == 0) {
+ win_col_offset = off;
+ }
+
if (wp->w_briopt_sbr && draw_state == WL_BRI - 1
&& n_extra == 0 && *p_sbr != NUL) {
// draw indent after showbreak value
@@ -2904,7 +2909,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
&& vcol >= (long)wp->w_virtcol)
|| (number_only && draw_state > WL_NR))
&& filler_todo <= 0) {
- draw_virt_text(buf, &col, grid->Columns);
+ draw_virt_text(buf, win_col_offset, &col, grid->Columns);
grid_put_linebuf(grid, row, 0, col, -grid->Columns, wp->w_p_rl, wp,
wp->w_hl_attr_normal, false);
// Pretend we have finished updating the window. Except when
@@ -3945,13 +3950,15 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
VirtText virt_text = KV_INITIAL_VALUE;
+ bool has_aligned = false;
if (err_text) {
int hl_err = syn_check_group((char_u *)S_LEN("ErrorMsg"));
kv_push(virt_text, ((VirtTextChunk){ .text = err_text,
.hl_id = hl_err }));
do_virttext = true;
} else if (has_decor) {
- virt_text = decor_redraw_virt_text(wp->w_buffer, &decor_state);
+ virt_text = decor_redraw_eol(wp->w_buffer, &decor_state, &line_attr,
+ &has_aligned);
if (kv_size(virt_text)) {
do_virttext = true;
}
@@ -3963,7 +3970,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
grid->Columns * (row - startrow + 1) + v
&& lnum != wp->w_cursor.lnum)
|| draw_color_col || line_attr_lowprio || line_attr
- || diff_hlf != (hlf_T)0 || do_virttext)) {
+ || diff_hlf != (hlf_T)0 || do_virttext
+ || has_aligned)) {
int rightmost_vcol = 0;
int i;
@@ -4001,7 +4009,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
}
int base_attr = hl_combine_attr(line_attr_lowprio, diff_attr);
- if (base_attr || line_attr) {
+ if (base_attr || line_attr || has_aligned) {
rightmost_vcol = INT_MAX;
}
@@ -4079,7 +4087,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
}
}
- draw_virt_text(buf, &col, grid->Columns);
+ draw_virt_text(buf, win_col_offset, &col, grid->Columns);
grid_put_linebuf(grid, row, 0, col, grid->Columns, wp->w_p_rl, wp,
wp->w_hl_attr_normal, false);
row++;
@@ -4300,7 +4308,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
&& !wp->w_p_rl; // Not right-to-left.
int draw_col = col - boguscols;
- draw_virt_text(buf, &draw_col, grid->Columns);
+ draw_virt_text(buf, win_col_offset, &draw_col, grid->Columns);
grid_put_linebuf(grid, row, 0, draw_col, grid->Columns, wp->w_p_rl,
wp, wp->w_hl_attr_normal, wrap);
if (wrap) {
@@ -4377,51 +4385,62 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
return row;
}
-void draw_virt_text(buf_T *buf, int *end_col, int max_col)
+void draw_virt_text(buf_T *buf, int col_off, int *end_col, int max_col)
{
DecorState *state = &decor_state;
+ int right_pos = max_col;
for (size_t i = 0; i < kv_size(state->active); i++) {
- HlRange *item = &kv_A(state->active, i);
- if (item->start_row == state->row && kv_size(item->virt_text)
- && item->virt_text_pos == kVTOverlay
- && item->virt_col >= 0) {
- VirtText vt = item->virt_text;
- LineState s = LINE_STATE("");
- int virt_attr = 0;
- int col = item->virt_col;
- size_t virt_pos = 0;
- item->virt_col = -2; // deactivate
+ DecorRange *item = &kv_A(state->active, i);
+ if (item->start_row == state->row && kv_size(item->decor.virt_text)) {
+ if (item->win_col == -1) {
+ if (item->decor.virt_text_pos == kVTRightAlign) {
+ right_pos -= item->decor.col;
+ item->win_col = right_pos;
+ } else if (item->decor.virt_text_pos == kVTWinCol) {
+ item->win_col = MAX(item->decor.col+col_off, 0);
+ }
+ }
+ if (item->win_col < 0) {
+ continue;
+ }
+ VirtText vt = item->decor.virt_text;
+ HlMode hl_mode = item->decor.hl_mode;
+ LineState s = LINE_STATE("");
+ int virt_attr = 0;
+ int col = item->win_col;
+ size_t virt_pos = 0;
+ item->win_col = -2; // deactivate
- while (col < max_col) {
- if (!*s.p) {
- if (virt_pos == kv_size(vt)) {
- break;
- }
- s.p = kv_A(vt, virt_pos).text;
- int hl_id = kv_A(vt, virt_pos).hl_id;
- virt_attr = hl_id > 0 ? syn_id2attr(hl_id) : 0;
- virt_pos++;
- continue;
- }
- int attr;
- bool through = false;
- if (item->hl_mode == kHlModeCombine) {
- attr = hl_combine_attr(linebuf_attr[col], virt_attr);
- } else if (item->hl_mode == kHlModeBlend) {
- through = (*s.p == ' ');
- attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through);
- } else {
- attr = virt_attr;
+ while (col < max_col) {
+ if (!*s.p) {
+ if (virt_pos == kv_size(vt)) {
+ break;
}
- schar_T dummy[2];
- int cells = line_putchar(&s, through ? dummy : &linebuf_char[col],
- max_col-col, false);
+ s.p = kv_A(vt, virt_pos).text;
+ int hl_id = kv_A(vt, virt_pos).hl_id;
+ virt_attr = hl_id > 0 ? syn_id2attr(hl_id) : 0;
+ virt_pos++;
+ continue;
+ }
+ int attr;
+ bool through = false;
+ if (hl_mode == kHlModeCombine) {
+ attr = hl_combine_attr(linebuf_attr[col], virt_attr);
+ } else if (hl_mode == kHlModeBlend) {
+ through = (*s.p == ' ');
+ attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through);
+ } else {
+ attr = virt_attr;
+ }
+ schar_T dummy[2];
+ int cells = line_putchar(&s, through ? dummy : &linebuf_char[col],
+ max_col-col, false);
+ linebuf_attr[col++] = attr;
+ if (cells > 1) {
linebuf_attr[col++] = attr;
- if (cells > 1) {
- linebuf_attr[col++] = attr;
- }
}
- *end_col = MAX(*end_col, col);
+ }
+ *end_col = MAX(*end_col, col);
}
}
}
@@ -6226,7 +6245,7 @@ void check_for_delay(int check_msg_scroll)
&& !did_wait_return
&& emsg_silent == 0) {
ui_flush();
- os_delay(1000L, true);
+ os_delay(1006L, true);
emsg_on_display = false;
if (check_msg_scroll) {
msg_scroll = false;
diff --git a/src/nvim/search.c b/src/nvim/search.c
index c4479a077e..abe05bbd12 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -2373,10 +2373,11 @@ showmatch(
* brief pause, unless 'm' is present in 'cpo' and a character is
* available.
*/
- if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL)
- os_delay(p_mat * 100L, true);
- else if (!char_avail())
- os_delay(p_mat * 100L, false);
+ if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL) {
+ os_delay(p_mat * 100L + 8, true);
+ } else if (!char_avail()) {
+ os_delay(p_mat * 100L + 9, false);
+ }
curwin->w_cursor = save_cursor; // restore cursor position
*so = save_so;
*siso = save_siso;
diff --git a/src/nvim/sign.c b/src/nvim/sign.c
index c7dc1a5b22..97e64c6c4c 100644
--- a/src/nvim/sign.c
+++ b/src/nvim/sign.c
@@ -18,6 +18,7 @@
#include "nvim/move.h"
#include "nvim/screen.h"
#include "nvim/syntax.h"
+#include "nvim/option.h"
/// Struct to hold the sign properties.
typedef struct sign sign_T;
@@ -726,16 +727,30 @@ void sign_mark_adjust(
long amount_after
)
{
- sign_entry_T *sign; // a sign in a b_signlist
- linenr_T new_lnum; // new line number to assign to sign
+ sign_entry_T *sign; // a sign in a b_signlist
+ sign_entry_T *next; // the next sign in a b_signlist
+ sign_entry_T *last = NULL; // pointer to pointer to current sign
+ sign_entry_T **lastp = NULL; // pointer to pointer to current sign
+ linenr_T new_lnum; // new line number to assign to sign
+ int is_fixed = 0;
+ int signcol = win_signcol_configured(curwin, &is_fixed);
curbuf->b_signcols_max = -1;
+ lastp = &curbuf->b_signlist;
- FOR_ALL_SIGNS_IN_BUF(curbuf, sign) {
+ for (sign = curbuf->b_signlist; sign != NULL; sign = next) {
+ next = sign->se_next;
new_lnum = sign->se_lnum;
if (sign->se_lnum >= line1 && sign->se_lnum <= line2) {
if (amount != MAXLNUM) {
new_lnum += amount;
+ } else if (!is_fixed || signcol >= 2) {
+ *lastp = next;
+ if (next) {
+ next->se_prev = last;
+ }
+ xfree(sign);
+ continue;
}
} else if (sign->se_lnum > line2) {
new_lnum += amount_after;
@@ -746,6 +761,9 @@ void sign_mark_adjust(
if (sign->se_lnum >= line1 && new_lnum <= curbuf->b_ml.ml_line_count) {
sign->se_lnum = new_lnum;
}
+
+ last = sign;
+ lastp = &sign->se_next;
}
}
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index 825aef1465..ed886ab7f9 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -3469,13 +3469,13 @@ static void syn_cmd_onoff(exarg_T *eap, char *name)
}
}
-void syn_maybe_on(void)
+void syn_maybe_enable(void)
{
if (!did_syntax_onoff) {
exarg_T ea;
ea.arg = (char_u *)"";
ea.skip = false;
- syn_cmd_onoff(&ea, "syntax");
+ syn_cmd_enable(&ea, false);
}
}
@@ -5306,13 +5306,17 @@ get_id_list(
xfree(name);
break;
}
- if (name[1] == 'A')
- id = SYNID_ALLBUT;
- else if (name[1] == 'T')
- id = SYNID_TOP;
- else
- id = SYNID_CONTAINED;
- id += current_syn_inc_tag;
+ if (name[1] == 'A') {
+ id = SYNID_ALLBUT + current_syn_inc_tag;
+ } else if (name[1] == 'T') {
+ if (curwin->w_s->b_syn_topgrp >= SYNID_CLUSTER) {
+ id = curwin->w_s->b_syn_topgrp;
+ } else {
+ id = SYNID_TOP + current_syn_inc_tag;
+ }
+ } else {
+ id = SYNID_CONTAINED + current_syn_inc_tag;
+ }
} else if (name[1] == '@') {
if (skip) {
id = -1;
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index 588821f260..a6310344e9 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -625,7 +625,7 @@ do_tag(
}
if (ic && !msg_scrolled && msg_silent == 0) {
ui_flush();
- os_delay(1000L, true);
+ os_delay(1007L, true);
}
}
@@ -2853,7 +2853,7 @@ static int jumpto_tag(
MSG(_("E435: Couldn't find tag, just guessing!"));
if (!msg_scrolled && msg_silent == 0) {
ui_flush();
- os_delay(1000L, true);
+ os_delay(1010L, true);
}
}
retval = OK;
diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim
index 71af3eead7..e50602ccad 100644
--- a/src/nvim/testdir/test_alot.vim
+++ b/src/nvim/testdir/test_alot.vim
@@ -35,6 +35,7 @@ source test_popup.vim
source test_put.vim
source test_rename.vim
source test_scroll_opt.vim
+source test_shift.vim
source test_sort.vim
source test_sha256.vim
source test_suspend.vim
diff --git a/src/nvim/testdir/test_alot_utf8.vim b/src/nvim/testdir/test_alot_utf8.vim
index be0bd01413..70f14320a6 100644
--- a/src/nvim/testdir/test_alot_utf8.vim
+++ b/src/nvim/testdir/test_alot_utf8.vim
@@ -6,7 +6,6 @@
source test_charsearch_utf8.vim
source test_expr_utf8.vim
-source test_listlbr_utf8.vim
source test_matchadd_conceal_utf8.vim
source test_mksession_utf8.vim
source test_regexp_utf8.vim
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index 5e99edf233..5611560b1b 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -276,28 +276,28 @@ func Test_augroup_warning()
augroup TheWarning
au VimEnter * echo 'entering'
augroup END
- call assert_true(match(execute('au VimEnter'), "TheWarning.*VimEnter") >= 0)
+ call assert_match("TheWarning.*VimEnter", execute('au VimEnter'))
redir => res
augroup! TheWarning
redir END
- call assert_true(match(res, "W19:") >= 0)
- call assert_true(match(execute('au VimEnter'), "-Deleted-.*VimEnter") >= 0)
+ call assert_match("W19:", res)
+ call assert_match("-Deleted-.*VimEnter", execute('au VimEnter'))
" check "Another" does not take the pace of the deleted entry
augroup Another
augroup END
- call assert_true(match(execute('au VimEnter'), "-Deleted-.*VimEnter") >= 0)
+ call assert_match("-Deleted-.*VimEnter", execute('au VimEnter'))
augroup! Another
" no warning for postpone aucmd delete
augroup StartOK
au VimEnter * call RemoveGroup()
augroup END
- call assert_true(match(execute('au VimEnter'), "StartOK.*VimEnter") >= 0)
+ call assert_match("StartOK.*VimEnter", execute('au VimEnter'))
redir => res
doautocmd VimEnter
redir END
- call assert_true(match(res, "W19:") < 0)
+ call assert_notmatch("W19:", res)
au! VimEnter
endfunc
@@ -325,7 +325,7 @@ func Test_augroup_deleted()
au VimEnter * echo
augroup end
augroup! x
- call assert_true(match(execute('au VimEnter'), "-Deleted-.*VimEnter") >= 0)
+ call assert_match("-Deleted-.*VimEnter", execute('au VimEnter'))
au! VimEnter
endfunc
diff --git a/src/nvim/testdir/test_backspace_opt.vim b/src/nvim/testdir/test_backspace_opt.vim
index d680b442db..11459991ea 100644
--- a/src/nvim/testdir/test_backspace_opt.vim
+++ b/src/nvim/testdir/test_backspace_opt.vim
@@ -1,15 +1,5 @@
" Tests for 'backspace' settings
-func Exec(expr)
- let str=''
- try
- exec a:expr
- catch /.*/
- let str=v:exception
- endtry
- return str
-endfunc
-
func Test_backspace_option()
set backspace=
call assert_equal('', &backspace)
@@ -41,10 +31,10 @@ func Test_backspace_option()
set backspace-=eol
call assert_equal('', &backspace)
" Check the error
- call assert_equal(0, match(Exec('set backspace=ABC'), '.*E474'))
- call assert_equal(0, match(Exec('set backspace+=def'), '.*E474'))
+ call assert_fails('set backspace=ABC', 'E474:')
+ call assert_fails('set backspace+=def', 'E474:')
" NOTE: Vim doesn't check following error...
- "call assert_equal(0, match(Exec('set backspace-=ghi'), '.*E474'))
+ "call assert_fails('set backspace-=ghi', 'E474:')
" Check backwards compatibility with version 5.4 and earlier
set backspace=0
@@ -55,8 +45,8 @@ func Test_backspace_option()
call assert_equal('2', &backspace)
set backspace=3
call assert_equal('3', &backspace)
- call assert_false(match(Exec('set backspace=4'), '.*E474'))
- call assert_false(match(Exec('set backspace=10'), '.*E474'))
+ call assert_fails('set backspace=4', 'E474:')
+ call assert_fails('set backspace=10', 'E474:')
" Cleared when 'compatible' is set
" set compatible
diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim
index 98a3e60368..15557056ee 100644
--- a/src/nvim/testdir/test_excmd.vim
+++ b/src/nvim/testdir/test_excmd.vim
@@ -47,7 +47,7 @@ func Test_buffers_lastused()
endfor
call assert_equal(['bufb', 'bufa', 'bufc'], names)
- call assert_match('[0-2] seconds ago', bufs[1][1])
+ call assert_match('[0-2] seconds\= ago', bufs[1][1])
bwipeout bufa
bwipeout bufb
diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim
index 09d79979ce..0b41a1127a 100644
--- a/src/nvim/testdir/test_expr.vim
+++ b/src/nvim/testdir/test_expr.vim
@@ -501,3 +501,12 @@ func Test_empty_concatenate()
call assert_equal('b', 'a'[4:0] . 'b')
call assert_equal('b', 'b' . 'a'[4:0])
endfunc
+
+func Test_eval_after_if()
+ let s:val = ''
+ func SetVal(x)
+ let s:val ..= a:x
+ endfunc
+ if 0 | eval SetVal('a') | endif | call SetVal('b')
+ call assert_equal('b', s:val)
+endfunc
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index 1a98dc6451..3cfc964f0a 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -275,6 +275,8 @@ let s:filename_checks = {
\ 'lss': ['file.lss'],
\ 'lua': ['file.lua', 'file.rockspec', 'file.nse'],
\ 'lynx': ['lynx.cfg'],
+ \ 'm3build': ['m3makefile', 'm3overrides'],
+ \ 'm3quake': ['file.quake', 'cm3.cfg'],
\ 'm4': ['file.at'],
\ 'mail': ['snd.123', '.letter', '.letter.123', '.followup', '.article', '.article.123', 'pico.123', 'mutt-xx-xxx', 'muttng-xx-xxx', 'ae123.txt', 'file.eml'],
\ 'mailaliases': ['/etc/mail/aliases', '/etc/aliases'],
@@ -374,6 +376,7 @@ let s:filename_checks = {
\ 'ps1': ['file.ps1', 'file.psd1', 'file.psm1', 'file.pssc'],
\ 'ps1xml': ['file.ps1xml'],
\ 'psf': ['file.psf'],
+ \ 'psl': ['file.psl'],
\ 'puppet': ['file.pp'],
\ 'pyrex': ['file.pyx', 'file.pxd'],
\ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', 'file.ptl', 'file.pyi', 'SConstruct'],
diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
index 555f549743..93f567b3a0 100644
--- a/src/nvim/testdir/test_functions.vim
+++ b/src/nvim/testdir/test_functions.vim
@@ -1071,10 +1071,10 @@ func Test_inputlist()
endfunc
func Test_balloon_show()
- if has('balloon_eval')
- " This won't do anything but must not crash either.
- call balloon_show('hi!')
- endif
+ CheckFeature balloon_eval
+
+ " This won't do anything but must not crash either.
+ call balloon_show('hi!')
endfunc
func Test_shellescape()
@@ -1448,4 +1448,12 @@ func Test_nr2char()
call assert_equal("\x80\xfc\b\xfd\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX", eval('"\<M-' .. nr2char(0x40000000) .. '>"'))
endfunc
+func HasDefault(msg = 'msg')
+ return a:msg
+endfunc
+
+func Test_default_arg_value()
+ call assert_equal('msg', HasDefault())
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_menu.vim b/src/nvim/testdir/test_menu.vim
index 055d944b15..de6d4aa359 100644
--- a/src/nvim/testdir/test_menu.vim
+++ b/src/nvim/testdir/test_menu.vim
@@ -11,7 +11,13 @@ func Test_load_menu()
call assert_report('error while loading menus: ' . v:exception)
endtry
call assert_match('browse confirm w', execute(':menu File.Save'))
+
+ let v:errmsg = ''
+ doautocmd LoadBufferMenu VimEnter
+ call assert_equal('', v:errmsg)
+
source $VIMRUNTIME/delmenu.vim
+ call assert_equal('', v:errmsg)
endfunc
func Test_translate_menu()
diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim
index 8486f3ff68..7bb76ad9eb 100644
--- a/src/nvim/testdir/test_mksession.vim
+++ b/src/nvim/testdir/test_mksession.vim
@@ -680,6 +680,24 @@ func Test_mksession_winpos()
set sessionoptions&
endfunc
+" Test for mksession without options restores winminheight
+func Test_mksession_winminheight()
+ set sessionoptions-=options
+ split
+ mksession! Xtest_mks.out
+ let found_restore = 0
+ let lines = readfile('Xtest_mks.out')
+ for line in lines
+ if line =~ '= s:save_winmin\(width\|height\)'
+ let found_restore += 1
+ endif
+ endfor
+ call assert_equal(2, found_restore)
+ call delete('Xtest_mks.out')
+ close
+ set sessionoptions&
+endfunc
+
" Test for mksession with 'compatible' option
func Test_mksession_compatible()
throw 'skipped: Nvim does not support "compatible" option'
diff --git a/src/nvim/testdir/test_prompt_buffer.vim b/src/nvim/testdir/test_prompt_buffer.vim
new file mode 100644
index 0000000000..6fc5850be3
--- /dev/null
+++ b/src/nvim/testdir/test_prompt_buffer.vim
@@ -0,0 +1,195 @@
+" Tests for setting 'buftype' to "prompt"
+
+source check.vim
+" Nvim's channel implementation differs from Vim's
+" CheckFeature channel
+
+source shared.vim
+source screendump.vim
+
+func CanTestPromptBuffer()
+ " We need to use a terminal window to be able to feed keys without leaving
+ " Insert mode.
+ " Nvim's terminal implementation differs from Vim's
+ " CheckFeature terminal
+
+ " TODO: make the tests work on MS-Windows
+ CheckNotMSWindows
+endfunc
+
+func WriteScript(name)
+ call writefile([
+ \ 'func TextEntered(text)',
+ \ ' if a:text == "exit"',
+ \ ' " Reset &modified to allow the buffer to be closed.',
+ \ ' set nomodified',
+ \ ' stopinsert',
+ \ ' close',
+ \ ' else',
+ \ ' " Add the output above the current prompt.',
+ \ ' call append(line("$") - 1, "Command: \"" . a:text . "\"")',
+ \ ' " Reset &modified to allow the buffer to be closed.',
+ \ ' set nomodified',
+ \ ' call timer_start(20, {id -> TimerFunc(a:text)})',
+ \ ' endif',
+ \ 'endfunc',
+ \ '',
+ \ 'func TimerFunc(text)',
+ \ ' " Add the output above the current prompt.',
+ \ ' call append(line("$") - 1, "Result: \"" . a:text . "\"")',
+ \ ' " Reset &modified to allow the buffer to be closed.',
+ \ ' set nomodified',
+ \ 'endfunc',
+ \ '',
+ \ 'call setline(1, "other buffer")',
+ \ 'set nomodified',
+ \ 'new',
+ \ 'set buftype=prompt',
+ \ 'call prompt_setcallback(bufnr(""), function("TextEntered"))',
+ \ 'eval bufnr("")->prompt_setprompt("cmd: ")',
+ \ 'startinsert',
+ \ ], a:name)
+endfunc
+
+func Test_prompt_basic()
+ throw 'skipped: TODO'
+ call CanTestPromptBuffer()
+ let scriptName = 'XpromptscriptBasic'
+ call WriteScript(scriptName)
+
+ let buf = RunVimInTerminal('-S ' . scriptName, {})
+ call WaitForAssert({-> assert_equal('cmd:', term_getline(buf, 1))})
+
+ call term_sendkeys(buf, "hello\<CR>")
+ call WaitForAssert({-> assert_equal('cmd: hello', term_getline(buf, 1))})
+ call WaitForAssert({-> assert_equal('Command: "hello"', term_getline(buf, 2))})
+ call WaitForAssert({-> assert_equal('Result: "hello"', term_getline(buf, 3))})
+
+ call term_sendkeys(buf, "exit\<CR>")
+ call WaitForAssert({-> assert_equal('other buffer', term_getline(buf, 1))})
+
+ call StopVimInTerminal(buf)
+ call delete(scriptName)
+endfunc
+
+func Test_prompt_editing()
+ throw 'skipped: TODO'
+ call CanTestPromptBuffer()
+ let scriptName = 'XpromptscriptEditing'
+ call WriteScript(scriptName)
+
+ let buf = RunVimInTerminal('-S ' . scriptName, {})
+ call WaitForAssert({-> assert_equal('cmd:', term_getline(buf, 1))})
+
+ let bs = "\<BS>"
+ call term_sendkeys(buf, "hello" . bs . bs)
+ call WaitForAssert({-> assert_equal('cmd: hel', term_getline(buf, 1))})
+
+ let left = "\<Left>"
+ call term_sendkeys(buf, left . left . left . bs . '-')
+ call WaitForAssert({-> assert_equal('cmd: -hel', term_getline(buf, 1))})
+
+ let end = "\<End>"
+ call term_sendkeys(buf, end . "x")
+ call WaitForAssert({-> assert_equal('cmd: -helx', term_getline(buf, 1))})
+
+ call term_sendkeys(buf, "\<C-U>exit\<CR>")
+ call WaitForAssert({-> assert_equal('other buffer', term_getline(buf, 1))})
+
+ call StopVimInTerminal(buf)
+ call delete(scriptName)
+endfunc
+
+func Test_prompt_garbage_collect()
+ func MyPromptCallback(x, text)
+ " NOP
+ endfunc
+ func MyPromptInterrupt(x)
+ " NOP
+ endfunc
+
+ new
+ set buftype=prompt
+ " Nvim doesn't support method call syntax yet.
+ " eval bufnr('')->prompt_setcallback(function('MyPromptCallback', [{}]))
+ " eval bufnr('')->prompt_setinterrupt(function('MyPromptInterrupt', [{}]))
+ eval prompt_setcallback(bufnr(''), function('MyPromptCallback', [{}]))
+ eval prompt_setinterrupt(bufnr(''), function('MyPromptInterrupt', [{}]))
+ call test_garbagecollect_now()
+ " Must not crash
+ call feedkeys("\<CR>\<C-C>", 'xt')
+ call assert_true(v:true)
+
+ call assert_fails("call prompt_setcallback(bufnr(), [])", 'E921:')
+ call assert_equal(0, prompt_setcallback({}, ''))
+ call assert_fails("call prompt_setinterrupt(bufnr(), [])", 'E921:')
+ call assert_equal(0, prompt_setinterrupt({}, ''))
+
+ delfunc MyPromptCallback
+ bwipe!
+endfunc
+
+" Test for editing the prompt buffer
+func Test_prompt_buffer_edit()
+ new
+ set buftype=prompt
+ normal! i
+ call assert_beeps('normal! dd')
+ call assert_beeps('normal! ~')
+ call assert_beeps('normal! o')
+ call assert_beeps('normal! O')
+ call assert_beeps('normal! p')
+ call assert_beeps('normal! P')
+ call assert_beeps('normal! u')
+ call assert_beeps('normal! ra')
+ call assert_beeps('normal! s')
+ call assert_beeps('normal! S')
+ call assert_beeps("normal! \<C-A>")
+ call assert_beeps("normal! \<C-X>")
+ " pressing CTRL-W in the prompt buffer should trigger the window commands
+ call assert_equal(1, winnr())
+ " In Nvim, CTRL-W commands aren't usable from insert mode in a prompt buffer
+ " exe "normal A\<C-W>\<C-W>"
+ " call assert_equal(2, winnr())
+ " wincmd w
+ close!
+ call assert_equal(0, prompt_setprompt([], ''))
+endfunc
+
+func Test_prompt_buffer_getbufinfo()
+ new
+ call assert_equal('', prompt_getprompt('%'))
+ call assert_equal('', prompt_getprompt(bufnr('%')))
+ let another_buffer = bufnr('%')
+
+ set buftype=prompt
+ call assert_equal('% ', prompt_getprompt('%'))
+ call prompt_setprompt( bufnr( '%' ), 'This is a test: ' )
+ call assert_equal('This is a test: ', prompt_getprompt('%'))
+
+ call prompt_setprompt( bufnr( '%' ), '' )
+ " Nvim doesn't support method call syntax yet.
+ " call assert_equal('', '%'->prompt_getprompt())
+ call assert_equal('', prompt_getprompt('%'))
+
+ call prompt_setprompt( bufnr( '%' ), 'Another: ' )
+ call assert_equal('Another: ', prompt_getprompt('%'))
+ let another = bufnr('%')
+
+ new
+
+ call assert_equal('', prompt_getprompt('%'))
+ call assert_equal('Another: ', prompt_getprompt(another))
+
+ " Doesn't exist
+ let buffers_before = len( getbufinfo() )
+ call assert_equal('', prompt_getprompt( bufnr('$') + 1))
+ call assert_equal(buffers_before, len( getbufinfo()))
+
+ " invalid type
+ call assert_fails('call prompt_getprompt({})', 'E728:')
+
+ %bwipe!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim
index 7aa01c61ca..75d42b986b 100644
--- a/src/nvim/testdir/test_search.vim
+++ b/src/nvim/testdir/test_search.vim
@@ -7,9 +7,8 @@ source check.vim
" See test/functional/legacy/search_spec.lua
func Test_search_cmdline()
CheckFunction test_override
- if !exists('+incsearch')
- return
- endif
+ CheckOption incsearch
+
" need to disable char_avail,
" so that expansion of commandline works
call test_override("char_avail", 1)
@@ -206,9 +205,8 @@ endfunc
" See test/functional/legacy/search_spec.lua
func Test_search_cmdline2()
CheckFunction test_override
- if !exists('+incsearch')
- return
- endif
+ CheckOption incsearch
+
" need to disable char_avail,
" so that expansion of commandline works
call test_override("char_avail", 1)
@@ -369,9 +367,8 @@ func Incsearch_cleanup()
endfunc
func Test_search_cmdline3()
- if !exists('+incsearch')
- return
- endif
+ CheckOption incsearch
+
call Cmdline3_prep()
1
" first match
@@ -382,9 +379,8 @@ func Test_search_cmdline3()
endfunc
func Test_search_cmdline3s()
- if !exists('+incsearch')
- return
- endif
+ CheckOption incsearch
+
call Cmdline3_prep()
1
call feedkeys(":%s/the\<c-l>/xxx\<cr>", 'tx')
@@ -408,9 +404,8 @@ func Test_search_cmdline3s()
endfunc
func Test_search_cmdline3g()
- if !exists('+incsearch')
- return
- endif
+ CheckOption incsearch
+
call Cmdline3_prep()
1
call feedkeys(":g/the\<c-l>/d\<cr>", 'tx')
@@ -431,9 +426,8 @@ func Test_search_cmdline3g()
endfunc
func Test_search_cmdline3v()
- if !exists('+incsearch')
- return
- endif
+ CheckOption incsearch
+
call Cmdline3_prep()
1
call feedkeys(":v/the\<c-l>/d\<cr>", 'tx')
@@ -450,9 +444,8 @@ endfunc
" See test/functional/legacy/search_spec.lua
func Test_search_cmdline4()
CheckFunction test_override
- if !exists('+incsearch')
- return
- endif
+ CheckOption incsearch
+
" need to disable char_avail,
" so that expansion of commandline works
call test_override("char_avail", 1)
@@ -484,9 +477,8 @@ func Test_search_cmdline4()
endfunc
func Test_search_cmdline5()
- if !exists('+incsearch')
- return
- endif
+ CheckOption incsearch
+
" Do not call test_override("char_avail", 1) so that <C-g> and <C-t> work
" regardless char_avail.
new
@@ -503,6 +495,46 @@ func Test_search_cmdline5()
bw!
endfunc
+func Test_search_cmdline6()
+ " Test that consecutive matches
+ " are caught by <c-g>/<c-t>
+ CheckFunction test_override
+ CheckOption incsearch
+
+ " need to disable char_avail,
+ " so that expansion of commandline works
+ call test_override("char_avail", 1)
+ new
+ call setline(1, [' bbvimb', ''])
+ set incsearch
+ " first match
+ norm! gg0
+ call feedkeys("/b\<cr>", 'tx')
+ call assert_equal([0,1,2,0], getpos('.'))
+ " second match
+ norm! gg0
+ call feedkeys("/b\<c-g>\<cr>", 'tx')
+ call assert_equal([0,1,3,0], getpos('.'))
+ " third match
+ norm! gg0
+ call feedkeys("/b\<c-g>\<c-g>\<cr>", 'tx')
+ call assert_equal([0,1,7,0], getpos('.'))
+ " first match again
+ norm! gg0
+ call feedkeys("/b\<c-g>\<c-g>\<c-g>\<cr>", 'tx')
+ call assert_equal([0,1,2,0], getpos('.'))
+ set nowrapscan
+ " last match
+ norm! gg0
+ call feedkeys("/b\<c-g>\<c-g>\<c-g>\<cr>", 'tx')
+ call assert_equal([0,1,7,0], getpos('.'))
+ " clean up
+ set wrapscan&vim
+ set noincsearch
+ call test_override("char_avail", 0)
+ bw!
+endfunc
+
func Test_search_cmdline7()
CheckFunction test_override
" Test that pressing <c-g> in an empty command line
@@ -598,26 +630,226 @@ func Test_search_regexp()
enew!
endfunc
-" Test for search('multi-byte char', 'bce')
-func Test_search_multibyte()
- let save_enc = &encoding
- set encoding=utf8
- enew!
- call append('$', 'A')
- call cursor(2, 1)
- call assert_equal(2, search('A', 'bce', line('.')))
- enew!
- let &encoding = save_enc
+func Test_search_cmdline_incsearch_highlight()
+ CheckFunction test_override
+ CheckOption incsearch
+
+ set incsearch hlsearch
+ " need to disable char_avail,
+ " so that expansion of commandline works
+ call test_override("char_avail", 1)
+ new
+ call setline(1, ['aaa 1 the first', ' 2 the second', ' 3 the third'])
+
+ 1
+ call feedkeys("/second\<cr>", 'tx')
+ call assert_equal('second', @/)
+ call assert_equal(' 2 the second', getline('.'))
+
+ " Canceling search won't change @/
+ 1
+ let @/ = 'last pattern'
+ call feedkeys("/third\<C-c>", 'tx')
+ call assert_equal('last pattern', @/)
+ call feedkeys("/third\<Esc>", 'tx')
+ call assert_equal('last pattern', @/)
+ call feedkeys("/3\<bs>\<bs>", 'tx')
+ call assert_equal('last pattern', @/)
+ call feedkeys("/third\<c-g>\<c-t>\<Esc>", 'tx')
+ call assert_equal('last pattern', @/)
+
+ " clean up
+ set noincsearch nohlsearch
+ bw!
endfunc
-" Similar to Test_incsearch_substitute() but with a screendump halfway.
-func Test_incsearch_substitute_dump()
- if !exists('+incsearch')
+func Test_search_cmdline_incsearch_highlight_attr()
+ CheckOption incsearch
+ CheckFeature terminal
+ CheckNotGui
+
+ let h = winheight(0)
+ if h < 3
return
endif
+
+ " Prepare buffer text
+ let lines = ['abb vim vim vi', 'vimvivim']
+ call writefile(lines, 'Xsearch.txt')
+ let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile', 'Xsearch.txt'], {'term_rows': 3})
+
+ call WaitForAssert({-> assert_equal(lines, [term_getline(buf, 1), term_getline(buf, 2)])})
+ " wait for vim to complete initialization
+ call term_wait(buf)
+
+ " Get attr of normal(a0), incsearch(a1), hlsearch(a2) highlight
+ call term_sendkeys(buf, ":set incsearch hlsearch\<cr>")
+ call term_sendkeys(buf, '/b')
+ call term_wait(buf, 200)
+ let screen_line1 = term_scrape(buf, 1)
+ call assert_true(len(screen_line1) > 2)
+ " a0: attr_normal
+ let a0 = screen_line1[0].attr
+ " a1: attr_incsearch
+ let a1 = screen_line1[1].attr
+ " a2: attr_hlsearch
+ let a2 = screen_line1[2].attr
+ call assert_notequal(a0, a1)
+ call assert_notequal(a0, a2)
+ call assert_notequal(a1, a2)
+ call term_sendkeys(buf, "\<cr>gg0")
+
+ " Test incremental highlight search
+ call term_sendkeys(buf, "/vim")
+ call term_wait(buf, 200)
+ " Buffer:
+ " abb vim vim vi
+ " vimvivim
+ " Search: /vim
+ let attr_line1 = [a0,a0,a0,a0,a1,a1,a1,a0,a2,a2,a2,a0,a0,a0]
+ let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2]
+ call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
+ call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr'))
+
+ " Test <C-g>
+ call term_sendkeys(buf, "\<C-g>\<C-g>")
+ call term_wait(buf, 200)
+ let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a2,a2,a2,a0,a0,a0]
+ let attr_line2 = [a1,a1,a1,a0,a0,a2,a2,a2]
+ call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
+ call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr'))
+
+ " Test <C-t>
+ call term_sendkeys(buf, "\<C-t>")
+ call term_wait(buf, 200)
+ let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a1,a1,a1,a0,a0,a0]
+ let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2]
+ call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
+ call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr'))
+
+ " Type Enter and a1(incsearch highlight) should become a2(hlsearch highlight)
+ call term_sendkeys(buf, "\<cr>")
+ call term_wait(buf, 200)
+ let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a2,a2,a2,a0,a0,a0]
+ let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2]
+ call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
+ call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr'))
+
+ " Test nohlsearch. a2(hlsearch highlight) should become a0(normal highlight)
+ call term_sendkeys(buf, ":1\<cr>")
+ call term_sendkeys(buf, ":set nohlsearch\<cr>")
+ call term_sendkeys(buf, "/vim")
+ call term_wait(buf, 200)
+ let attr_line1 = [a0,a0,a0,a0,a1,a1,a1,a0,a0,a0,a0,a0,a0,a0]
+ let attr_line2 = [a0,a0,a0,a0,a0,a0,a0,a0]
+ call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
+ call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr'))
+ call delete('Xsearch.txt')
+
+ call delete('Xsearch.txt')
+ bwipe!
+endfunc
+
+func Test_incsearch_cmdline_modifier()
+ CheckFunction test_override
+ CheckOption incsearch
+
+ call test_override("char_avail", 1)
+ new
+ call setline(1, ['foo'])
+ set incsearch
+ " Test that error E14 does not occur in parsing command modifier.
+ call feedkeys("V:tab", 'tx')
+
+ call Incsearch_cleanup()
+endfunc
+
+func Test_incsearch_scrolling()
if !CanRunVimInTerminal()
throw 'Skipped: cannot make screendumps'
endif
+ call assert_equal(0, &scrolloff)
+ call writefile([
+ \ 'let dots = repeat(".", 120)',
+ \ 'set incsearch cmdheight=2 scrolloff=0',
+ \ 'call setline(1, [dots, dots, dots, "", "target", dots, dots])',
+ \ 'normal gg',
+ \ 'redraw',
+ \ ], 'Xscript')
+ let buf = RunVimInTerminal('-S Xscript', {'rows': 9, 'cols': 70})
+ " Need to send one key at a time to force a redraw
+ call term_sendkeys(buf, '/')
+ sleep 100m
+ call term_sendkeys(buf, 't')
+ sleep 100m
+ call term_sendkeys(buf, 'a')
+ sleep 100m
+ call term_sendkeys(buf, 'r')
+ sleep 100m
+ call term_sendkeys(buf, 'g')
+ call VerifyScreenDump(buf, 'Test_incsearch_scrolling_01', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+ call delete('Xscript')
+endfunc
+
+func Test_incsearch_search_dump()
+ CheckOption incsearch
+ CheckScreendump
+
+ call writefile([
+ \ 'set incsearch hlsearch scrolloff=0',
+ \ 'for n in range(1, 8)',
+ \ ' call setline(n, "foo " . n)',
+ \ 'endfor',
+ \ '3',
+ \ ], 'Xis_search_script')
+ let buf = RunVimInTerminal('-S Xis_search_script', {'rows': 9, 'cols': 70})
+ " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
+ " the 'ambiwidth' check.
+ sleep 100m
+
+ " Need to send one key at a time to force a redraw.
+ call term_sendkeys(buf, '/fo')
+ call VerifyScreenDump(buf, 'Test_incsearch_search_01', {})
+ call term_sendkeys(buf, "\<Esc>")
+ sleep 100m
+
+ call term_sendkeys(buf, '/\v')
+ call VerifyScreenDump(buf, 'Test_incsearch_search_02', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call StopVimInTerminal(buf)
+ call delete('Xis_search_script')
+endfunc
+
+func Test_incsearch_substitute()
+ CheckFunction test_override
+ CheckOption incsearch
+
+ call test_override("char_avail", 1)
+ new
+ set incsearch
+ for n in range(1, 10)
+ call setline(n, 'foo ' . n)
+ endfor
+ 4
+ call feedkeys(":.,.+2s/foo\<BS>o\<BS>o/xxx\<cr>", 'tx')
+ call assert_equal('foo 3', getline(3))
+ call assert_equal('xxx 4', getline(4))
+ call assert_equal('xxx 5', getline(5))
+ call assert_equal('xxx 6', getline(6))
+ call assert_equal('foo 7', getline(7))
+
+ call Incsearch_cleanup()
+endfunc
+
+" Similar to Test_incsearch_substitute() but with a screendump halfway.
+func Test_incsearch_substitute_dump()
+ CheckOption incsearch
+ CheckScreendump
+
call writefile([
\ 'set incsearch hlsearch scrolloff=0',
\ 'for n in range(1, 10)',
@@ -724,12 +956,8 @@ func Test_incsearch_substitute_dump()
endfunc
func Test_incsearch_highlighting()
- if !exists('+incsearch')
- return
- endif
- if !CanRunVimInTerminal()
- throw 'Skipped: cannot make screendumps'
- endif
+ CheckOption incsearch
+ CheckScreendump
call writefile([
\ 'set incsearch hlsearch',
@@ -745,16 +973,40 @@ func Test_incsearch_highlighting()
call term_sendkeys(buf, ":%s;ello/the")
call VerifyScreenDump(buf, 'Test_incsearch_substitute_15', {})
call term_sendkeys(buf, "<Esc>")
+
+ call StopVimInTerminal(buf)
+ call delete('Xis_subst_hl_script')
+endfunc
+
+func Test_incsearch_with_change()
+ CheckFeature timers
+ CheckOption incsearch
+ CheckScreendump
+
+ call writefile([
+ \ 'set incsearch hlsearch scrolloff=0',
+ \ 'call setline(1, ["one", "two ------ X", "three"])',
+ \ 'call timer_start(200, { _ -> setline(2, "x")})',
+ \ ], 'Xis_change_script')
+ let buf = RunVimInTerminal('-S Xis_change_script', {'rows': 9, 'cols': 70})
+ " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
+ " the 'ambiwidth' check.
+ sleep 300m
+
+ " Highlight X, it will be deleted by the timer callback.
+ call term_sendkeys(buf, ':%s/X')
+ call VerifyScreenDump(buf, 'Test_incsearch_change_01', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call StopVimInTerminal(buf)
+ call delete('Xis_change_script')
endfunc
" Similar to Test_incsearch_substitute_dump() for :sort
func Test_incsearch_sort_dump()
- if !exists('+incsearch')
- return
- endif
- if !CanRunVimInTerminal()
- throw 'Skipped: cannot make screendumps'
- endif
+ CheckOption incsearch
+ CheckScreendump
+
call writefile([
\ 'set incsearch hlsearch scrolloff=0',
\ 'call setline(1, ["another one 2", "that one 3", "the one 1"])',
@@ -778,12 +1030,9 @@ endfunc
" Similar to Test_incsearch_substitute_dump() for :vimgrep famiry
func Test_incsearch_vimgrep_dump()
- if !exists('+incsearch')
- return
- endif
- if !CanRunVimInTerminal()
- throw 'Skipped: cannot make screendumps'
- endif
+ CheckOption incsearch
+ CheckScreendump
+
call writefile([
\ 'set incsearch hlsearch scrolloff=0',
\ 'call setline(1, ["another one 2", "that one 3", "the one 1"])',
@@ -820,9 +1069,8 @@ endfunc
func Test_keep_last_search_pattern()
CheckFunction test_override
- if !exists('+incsearch')
- return
- endif
+ CheckOption incsearch
+
new
call setline(1, ['foo', 'foo', 'foo'])
set incsearch
@@ -842,9 +1090,8 @@ endfunc
func Test_word_under_cursor_after_match()
CheckFunction test_override
- if !exists('+incsearch')
- return
- endif
+ CheckOption incsearch
+
new
call setline(1, 'foo bar')
set incsearch
@@ -862,9 +1109,8 @@ endfunc
func Test_subst_word_under_cursor()
CheckFunction test_override
- if !exists('+incsearch')
- return
- endif
+ CheckOption incsearch
+
new
call setline(1, ['int SomeLongName;', 'for (xxx = 1; xxx < len; ++xxx)'])
set incsearch
@@ -878,130 +1124,6 @@ func Test_subst_word_under_cursor()
set noincsearch
endfunc
-func Test_incsearch_with_change()
- if !has('timers') || !exists('+incsearch') || !CanRunVimInTerminal()
- throw 'Skipped: cannot make screendumps and/or timers feature and/or incsearch option missing'
- endif
-
- call writefile([
- \ 'set incsearch hlsearch scrolloff=0',
- \ 'call setline(1, ["one", "two ------ X", "three"])',
- \ 'call timer_start(200, { _ -> setline(2, "x")})',
- \ ], 'Xis_change_script')
- let buf = RunVimInTerminal('-S Xis_change_script', {'rows': 9, 'cols': 70})
- " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
- " the 'ambiwidth' check.
- sleep 300m
-
- " Highlight X, it will be deleted by the timer callback.
- call term_sendkeys(buf, ':%s/X')
- call VerifyScreenDump(buf, 'Test_incsearch_change_01', {})
- call term_sendkeys(buf, "\<Esc>")
-
- call StopVimInTerminal(buf)
- call delete('Xis_change_script')
-endfunc
-
-func Test_incsearch_cmdline_modifier()
- CheckFunction test_override
- if !exists('+incsearch')
- return
- endif
- call test_override("char_avail", 1)
- new
- call setline(1, ['foo'])
- set incsearch
- " Test that error E14 does not occur in parsing command modifier.
- call feedkeys("V:tab", 'tx')
-
- call Incsearch_cleanup()
-endfunc
-
-func Test_incsearch_scrolling()
- if !CanRunVimInTerminal()
- return
- endif
- call assert_equal(0, &scrolloff)
- call writefile([
- \ 'let dots = repeat(".", 120)',
- \ 'set incsearch cmdheight=2 scrolloff=0',
- \ 'call setline(1, [dots, dots, dots, "", "target", dots, dots])',
- \ 'normal gg',
- \ 'redraw',
- \ ], 'Xscript')
- let buf = RunVimInTerminal('-S Xscript', {'rows': 9, 'cols': 70})
- " Need to send one key at a time to force a redraw
- call term_sendkeys(buf, '/')
- sleep 100m
- call term_sendkeys(buf, 't')
- sleep 100m
- call term_sendkeys(buf, 'a')
- sleep 100m
- call term_sendkeys(buf, 'r')
- sleep 100m
- call term_sendkeys(buf, 'g')
- call VerifyScreenDump(buf, 'Test_incsearch_scrolling_01', {})
-
- call term_sendkeys(buf, "\<Esc>")
- call StopVimInTerminal(buf)
- call delete('Xscript')
-endfunc
-
-func Test_incsearch_search_dump()
- if !exists('+incsearch')
- return
- endif
- if !CanRunVimInTerminal()
- return
- endif
- call writefile([
- \ 'set incsearch hlsearch scrolloff=0',
- \ 'for n in range(1, 8)',
- \ ' call setline(n, "foo " . n)',
- \ 'endfor',
- \ '3',
- \ ], 'Xis_search_script')
- let buf = RunVimInTerminal('-S Xis_search_script', {'rows': 9, 'cols': 70})
- " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
- " the 'ambiwidth' check.
- sleep 100m
-
- " Need to send one key at a time to force a redraw.
- call term_sendkeys(buf, '/fo')
- call VerifyScreenDump(buf, 'Test_incsearch_search_01', {})
- call term_sendkeys(buf, "\<Esc>")
- sleep 100m
-
- call term_sendkeys(buf, '/\v')
- call VerifyScreenDump(buf, 'Test_incsearch_search_02', {})
- call term_sendkeys(buf, "\<Esc>")
-
- call StopVimInTerminal(buf)
- call delete('Xis_search_script')
-endfunc
-
-func Test_incsearch_substitute()
- CheckFunction test_override
- if !exists('+incsearch')
- return
- endif
- call test_override("char_avail", 1)
- new
- set incsearch
- for n in range(1, 10)
- call setline(n, 'foo ' . n)
- endfor
- 4
- call feedkeys(":.,.+2s/foo\<BS>o\<BS>o/xxx\<cr>", 'tx')
- call assert_equal('foo 3', getline(3))
- call assert_equal('xxx 4', getline(4))
- call assert_equal('xxx 5', getline(5))
- call assert_equal('xxx 6', getline(6))
- call assert_equal('foo 7', getline(7))
-
- call Incsearch_cleanup()
-endfunc
-
func Test_incsearch_substitute_long_line()
CheckFunction test_override
new
@@ -1018,9 +1140,8 @@ func Test_incsearch_substitute_long_line()
endfunc
func Test_search_undefined_behaviour()
- if !has("terminal")
- return
- endif
+ CheckFeature terminal
+
let h = winheight(0)
if h < 3
return
@@ -1036,6 +1157,18 @@ func Test_search_undefined_behaviour2()
call search("\%UC0000000")
endfunc
+" Test for search('multi-byte char', 'bce')
+func Test_search_multibyte()
+ let save_enc = &encoding
+ set encoding=utf8
+ enew!
+ call append('$', 'A')
+ call cursor(2, 1)
+ call assert_equal(2, search('A', 'bce', line('.')))
+ enew!
+ let &encoding = save_enc
+endfunc
+
" This was causing E874. Also causes an invalid read?
func Test_look_behind()
new
@@ -1074,9 +1207,8 @@ func Test_search_Ctrl_L_combining()
" ' ̇' U+0307 Dec:775 COMBINING DOT ABOVE &#x307; /\%u307\Z "\u0307"
" ' ̣' U+0323 Dec:803 COMBINING DOT BELOW &#x323; /\%u323 "\u0323"
" Those should also appear on the commandline
- if !exists('+incsearch')
- return
- endif
+ CheckOption incsearch
+
call Cmdline3_prep()
1
let bufcontent = ['', 'Miạ̀́̇m']
@@ -1126,9 +1258,8 @@ endfunc
func Test_incsearch_add_char_under_cursor()
CheckFunction test_override
- if !exists('+incsearch')
- return
- endif
+ CheckOption incsearch
+
set incsearch
new
call setline(1, ['find match', 'anything'])
@@ -1213,7 +1344,7 @@ func Test_search_smartcase_utf8()
close!
endfunc
-func Test_zzzz_incsearch_highlighting_newline()
+func Test_incsearch_highlighting_newline()
CheckRunVimInTerminal
CheckOption incsearch
CheckScreendump
@@ -1226,20 +1357,16 @@ func Test_zzzz_incsearch_highlighting_newline()
[CODE]
call writefile(commands, 'Xincsearch_nl')
let buf = RunVimInTerminal('-S Xincsearch_nl', {'rows': 5, 'cols': 10})
- " Need to send one key at a time to force a redraw
call term_sendkeys(buf, '/test')
- sleep 100m
call VerifyScreenDump(buf, 'Test_incsearch_newline1', {})
+ " Need to send one key at a time to force a redraw
call term_sendkeys(buf, '\n')
- sleep 100m
call VerifyScreenDump(buf, 'Test_incsearch_newline2', {})
call term_sendkeys(buf, 'x')
- sleep 100m
call VerifyScreenDump(buf, 'Test_incsearch_newline3', {})
call term_sendkeys(buf, 'x')
call VerifyScreenDump(buf, 'Test_incsearch_newline4', {})
call term_sendkeys(buf, "\<CR>")
- sleep 100m
call VerifyScreenDump(buf, 'Test_incsearch_newline5', {})
call StopVimInTerminal(buf)
diff --git a/src/nvim/testdir/test_shift.vim b/src/nvim/testdir/test_shift.vim
new file mode 100644
index 0000000000..ec357dac88
--- /dev/null
+++ b/src/nvim/testdir/test_shift.vim
@@ -0,0 +1,117 @@
+" Test shifting lines with :> and :<
+
+source check.vim
+
+func Test_ex_shift_right()
+ set shiftwidth=2
+
+ " shift right current line.
+ call setline(1, range(1, 5))
+ 2
+ >
+ 3
+ >>
+ call assert_equal(['1',
+ \ ' 2',
+ \ ' 3',
+ \ '4',
+ \ '5'], getline(1, '$'))
+
+ " shift right with range.
+ call setline(1, range(1, 4))
+ 2,3>>
+ call assert_equal(['1',
+ \ ' 2',
+ \ ' 3',
+ \ '4',
+ \ '5'], getline(1, '$'))
+
+ " shift right with range and count.
+ call setline(1, range(1, 4))
+ 2>3
+ call assert_equal(['1',
+ \ ' 2',
+ \ ' 3',
+ \ ' 4',
+ \ '5'], getline(1, '$'))
+
+ bw!
+ set shiftwidth&
+endfunc
+
+func Test_ex_shift_left()
+ set shiftwidth=2
+
+ call setline(1, range(1, 5))
+ %>>>
+
+ " left shift current line.
+ 2<
+ 3<<
+ 4<<<<<
+ call assert_equal([' 1',
+ \ ' 2',
+ \ ' 3',
+ \ '4',
+ \ ' 5'], getline(1, '$'))
+
+ " shift right with range.
+ call setline(1, range(1, 5))
+ %>>>
+ 2,3<<
+ call assert_equal([' 1',
+ \ ' 2',
+ \ ' 3',
+ \ ' 4',
+ \ ' 5'], getline(1, '$'))
+
+ " shift right with range and count.
+ call setline(1, range(1, 5))
+ %>>>
+ 2<<3
+ call assert_equal([' 1',
+ \ ' 2',
+ \ ' 3',
+ \ ' 4',
+ \ ' 5'], getline(1, '$'))
+
+ bw!
+ set shiftwidth&
+endfunc
+
+func Test_ex_shift_rightleft()
+ CheckFeature rightleft
+
+ set shiftwidth=2 rightleft
+
+ call setline(1, range(1, 4))
+ 2,3<<
+ call assert_equal(['1',
+ \ ' 2',
+ \ ' 3',
+ \ '4'], getline(1, '$'))
+
+ 3,4>
+ call assert_equal(['1',
+ \ ' 2',
+ \ ' 3',
+ \ '4'], getline(1, '$'))
+
+ bw!
+ set rightleft& shiftwidth&
+endfunc
+
+func Test_ex_shift_errors()
+ call assert_fails('><', 'E488:')
+ call assert_fails('<>', 'E488:')
+
+ call assert_fails('>!', 'E477:')
+ call assert_fails('<!', 'E477:')
+
+ " call assert_fails('2,1>', 'E493:')
+ call assert_fails('execute "2,1>"', 'E493:')
+ " call assert_fails('2,1<', 'E493:')
+ call assert_fails('execute "2,1<"', 'E493:')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim
index f6b96c1e5d..9753100375 100644
--- a/src/nvim/testdir/test_signs.vim
+++ b/src/nvim/testdir/test_signs.vim
@@ -1628,26 +1628,7 @@ func Test_sign_lnum_adjust()
" Delete the line with the sign
call deletebufline('', 4)
let l = sign_getplaced(bufnr(''))
- call assert_equal(4, l[0].signs[0].lnum)
-
- " Undo the delete operation
- undo
- let l = sign_getplaced(bufnr(''))
- call assert_equal(5, l[0].signs[0].lnum)
-
- " Break the undo
- let &undolevels=&undolevels
-
- " Delete few lines at the end of the buffer including the line with the sign
- " Sign line number should not change (as it is placed outside of the buffer)
- call deletebufline('', 3, 6)
- let l = sign_getplaced(bufnr(''))
- call assert_equal(5, l[0].signs[0].lnum)
-
- " Undo the delete operation. Sign should be restored to the previous line
- undo
- let l = sign_getplaced(bufnr(''))
- call assert_equal(5, l[0].signs[0].lnum)
+ call assert_equal(0, len(l[0].signs))
sign unplace * group=*
sign undefine sign1
diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim
index 66cb0bbe22..875e23894f 100644
--- a/src/nvim/testdir/test_syntax.vim
+++ b/src/nvim/testdir/test_syntax.vim
@@ -24,6 +24,32 @@ func GetSyntaxItem(pat)
return c
endfunc
+func AssertHighlightGroups(lnum, startcol, expected, trans = 1, msg = "")
+ " Assert that the characters starting at a given (line, col)
+ " sequentially match the expected highlight groups.
+ " If groups are provided as a string, each character is assumed to be a
+ " group and spaces represent no group, useful for visually describing tests.
+ let l:expectedGroups = type(a:expected) == v:t_string
+ "\ ? a:expected->split('\zs')->map({_, v -> trim(v)})
+ \ ? map(split(a:expected, '\zs'), {_, v -> trim(v)})
+ \ : a:expected
+ let l:errors = 0
+ " let l:msg = (a:msg->empty() ? "" : a:msg .. ": ")
+ let l:msg = (empty(a:msg) ? "" : a:msg .. ": ")
+ \ .. "Wrong highlight group at " .. a:lnum .. ","
+
+ " for l:i in range(a:startcol, a:startcol + l:expectedGroups->len() - 1)
+ " let l:errors += synID(a:lnum, l:i, a:trans)
+ " \ ->synIDattr("name")
+ " \ ->assert_equal(l:expectedGroups[l:i - 1],
+ for l:i in range(a:startcol, a:startcol + len(l:expectedGroups) - 1)
+ let l:errors +=
+ \ assert_equal(synIDattr(synID(a:lnum, l:i, a:trans), "name"),
+ \ l:expectedGroups[l:i - 1],
+ \ l:msg .. l:i)
+ endfor
+endfunc
+
func Test_syn_iskeyword()
new
call setline(1, [
@@ -707,3 +733,22 @@ func Test_syntax_foldlevel()
quit!
endfunc
+
+func Test_syn_include_contains_TOP()
+ let l:case = "TOP in included syntax means its group list name"
+ new
+ syntax include @INCLUDED syntax/c.vim
+ syntax region FencedCodeBlockC start=/```c/ end=/```/ contains=@INCLUDED
+
+ call setline(1, ['```c', '#if 0', 'int', '#else', 'int', '#endif', '```' ])
+ let l:expected = ["cCppOutIf2"]
+ eval AssertHighlightGroups(3, 1, l:expected, 1)
+ " cCppOutElse has contains=TOP
+ let l:expected = ["cType"]
+ eval AssertHighlightGroups(5, 1, l:expected, 1, l:case)
+ syntax clear
+ bw!
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c
index a2e9266fbb..c1e4a40ef2 100644
--- a/src/nvim/ui_compositor.c
+++ b/src/nvim/ui_compositor.c
@@ -180,7 +180,8 @@ bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width,
if (kv_A(layers, insert_at-1) == &pum_grid && (grid != &msg_grid)) {
insert_at--;
}
- if (insert_at > 1 && !on_top) {
+ if (curwin && kv_A(layers, insert_at-1) == &curwin->w_grid_alloc
+ && !on_top) {
insert_at--;
}
// not found: new grid
diff --git a/src/nvim/window.c b/src/nvim/window.c
index c482d265ff..d1163399f5 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -772,9 +772,8 @@ void ui_ext_win_position(win_T *wp)
wp->w_winrow = comp_row;
wp->w_wincol = comp_col;
bool valid = (wp->w_redr_type == 0);
- bool on_top = (curwin == wp) || !curwin->w_floating;
ui_comp_put_grid(&wp->w_grid_alloc, comp_row, comp_col,
- wp->w_height_outer, wp->w_width_outer, valid, on_top);
+ wp->w_height_outer, wp->w_width_outer, valid, false);
ui_check_cursor_grid(wp->w_grid_alloc.handle);
wp->w_grid_alloc.focusable = wp->w_float_config.focusable;
if (!valid) {
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index 3db44f3f11..6926022ee3 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -193,6 +193,44 @@ describe('API', function()
eq('', nvim('exec', 'echo', true))
eq('foo 42', nvim('exec', 'echo "foo" 42', true))
end)
+
+ it('displays messages when output=false', function()
+ local screen = Screen.new(40, 8)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [0] = {bold=true, foreground=Screen.colors.Blue},
+ })
+ meths.exec("echo 'hello'", false)
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ hello |
+ ]]}
+ end)
+
+ it('does\'t display messages when output=true', function()
+ local screen = Screen.new(40, 8)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [0] = {bold=true, foreground=Screen.colors.Blue},
+ })
+ meths.exec("echo 'hello'", true)
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]]}
+ end)
end)
describe('nvim_command', function()
diff --git a/test/functional/autoread/focus_spec.lua b/test/functional/autoread/focus_spec.lua
index 1d52e9948f..3f9a0ad09b 100644
--- a/test/functional/autoread/focus_spec.lua
+++ b/test/functional/autoread/focus_spec.lua
@@ -9,6 +9,7 @@ local feed_data = thelpers.feed_data
if helpers.pending_win32(pending) then return end
describe('autoread TUI FocusGained/FocusLost', function()
+ local f1 = 'xtest-foo'
local screen
before_each(function()
@@ -17,8 +18,12 @@ describe('autoread TUI FocusGained/FocusLost', function()
..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile noshowcmd noruler"]')
end)
+ teardown(function()
+ os.remove(f1)
+ end)
+
it('external file change', function()
- local path = 'xtest-foo'
+ local path = f1
local expected_addition = [[
line 1
line 2
diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua
index abca018059..5da8452a51 100644
--- a/test/functional/lua/buffer_updates_spec.lua
+++ b/test/functional/lua/buffer_updates_spec.lua
@@ -461,6 +461,36 @@ describe('lua: nvim_buf_attach on_bytes', function()
}
end)
+ it("deleting lines", function()
+ local check_events = setup_eventcheck(verify, origlines)
+
+ feed("dd")
+
+ check_events {
+ { "test1", "bytes", 1, 3, 0, 0, 0, 1, 0, 16, 0, 0, 0 };
+ }
+
+ feed("d2j")
+
+ check_events {
+ { "test1", "bytes", 1, 4, 0, 0, 0, 3, 0, 48, 0, 0, 0 };
+ }
+
+ feed("ld<c-v>2j")
+
+ check_events {
+ { "test1", "bytes", 1, 5, 0, 1, 1, 0, 1, 1, 0, 0, 0 };
+ { "test1", "bytes", 1, 5, 1, 1, 16, 0, 1, 1, 0, 0, 0 };
+ { "test1", "bytes", 1, 5, 2, 1, 31, 0, 1, 1, 0, 0, 0 };
+ }
+
+ feed("vjwd")
+
+ check_events {
+ { "test1", "bytes", 1, 10, 0, 1, 1, 1, 9, 23, 0, 0, 0 };
+ }
+ end)
+
it("changing lines", function()
local check_events = setup_eventcheck(verify, origlines)
@@ -537,20 +567,65 @@ describe('lua: nvim_buf_attach on_bytes', function()
end)
it('inccomand=nosplit and substitute', function()
- local check_events = setup_eventcheck(verify, {"abcde"})
+ local check_events = setup_eventcheck(verify,
+ {"abcde", "12345"})
meths.set_option('inccommand', 'nosplit')
- feed ':%s/bcd/'
+ -- linewise substitute
+ feed(':%s/bcd/')
check_events {
{ "test1", "bytes", 1, 3, 0, 1, 1, 0, 3, 3, 0, 0, 0 };
{ "test1", "bytes", 1, 5, 0, 1, 1, 0, 0, 0, 0, 3, 3 };
}
- feed 'a'
+ feed('a')
check_events {
{ "test1", "bytes", 1, 3, 0, 1, 1, 0, 3, 3, 0, 1, 1 };
{ "test1", "bytes", 1, 5, 0, 1, 1, 0, 1, 1, 0, 3, 3 };
}
+
+ feed("<esc>")
+
+ -- splitting lines
+ feed([[:%s/abc/\r]])
+ check_events {
+ { "test1", "bytes", 1, 3, 0, 0, 0, 0, 3, 3, 1, 0, 1 };
+ { "test1", "bytes", 1, 6, 0, 0, 0, 1, 0, 1, 0, 3, 3 };
+ }
+
+ feed("<esc>")
+ -- multi-line regex
+ feed([[:%s/de\n123/a]])
+
+ check_events {
+ { "test1", "bytes", 1, 3, 0, 3, 3, 1, 3, 6, 0, 1, 1 };
+ { "test1", "bytes", 1, 6, 0, 3, 3, 0, 1, 1, 1, 3, 6 };
+ }
+
+ feed("<esc>")
+ -- replacing with unicode
+ feed(":%s/b/→")
+
+ check_events {
+ { "test1", "bytes", 1, 3, 0, 1, 1, 0, 1, 1, 0, 3, 3 };
+ { "test1", "bytes", 1, 5, 0, 1, 1, 0, 3, 3, 0, 1, 1 };
+ }
+
+ feed("<esc>")
+ -- replacing with escaped characters
+ feed([[:%s/b/\\]])
+ check_events {
+ { "test1", "bytes", 1, 3, 0, 1, 1, 0, 1, 1, 0, 1, 1 };
+ { "test1", "bytes", 1, 5, 0, 1, 1, 0, 1, 1, 0, 1, 1 };
+ }
+
+ feed("<esc>")
+ -- replacing with expression register
+ feed([[:%s/b/\=5+5]])
+ check_events {
+ { "test1", "bytes", 1, 3, 0, 1, 1, 0, 1, 1, 0, 2, 2 };
+ { "test1", "bytes", 1, 5, 0, 1, 1, 0, 2, 2, 0, 1, 1 };
+ }
end)
it('nvim_buf_set_text insert', function()
@@ -826,6 +901,41 @@ describe('lua: nvim_buf_attach on_bytes', function()
end)
+ it(":luado", function()
+ local check_events = setup_eventcheck(verify, {"abc", "12345"})
+
+ command(".luado return 'a'")
+
+ check_events {
+ { "test1", "bytes", 1, 3, 0, 0, 0, 0, 3, 3, 0, 1, 1 };
+ }
+
+ command("luado return 10")
+
+ check_events {
+ { "test1", "bytes", 1, 4, 0, 0, 0, 0, 1, 1, 0, 2, 2 };
+ { "test1", "bytes", 1, 5, 1, 0, 3, 0, 5, 5, 0, 2, 2 };
+ }
+
+ end)
+
+ it("flushes deleted bytes on move", function()
+ local check_events = setup_eventcheck(verify, {"AAA", "BBB", "CCC", "DDD"})
+
+ feed(":.move+1<cr>")
+
+ check_events {
+ { "test1", "bytes", 1, 5, 0, 0, 0, 1, 0, 4, 0, 0, 0 };
+ { "test1", "bytes", 1, 5, 1, 0, 4, 0, 0, 0, 1, 0, 4 };
+ }
+
+ feed("jd2j")
+
+ check_events {
+ { "test1", "bytes", 1, 6, 2, 0, 8, 2, 0, 8, 0, 0, 0 };
+ }
+ end)
+
teardown(function()
os.remove "Xtest-reload"
os.remove "Xtest-undofile"
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
index 66b33cc9e1..6eda515fb6 100644
--- a/test/functional/plugin/lsp_spec.lua
+++ b/test/functional/plugin/lsp_spec.lua
@@ -1124,14 +1124,14 @@ describe('LSP', function()
'2nd line of 语text';
}, buf_lines(target_bufnr))
end)
- it('correctly goes ahead with the edit if the version is vim.NIL', function()
- -- we get vim.NIL when we decode json null value.
- local json = exec_lua[[
- return vim.fn.json_decode("{ \"a\": 1, \"b\": null }")
- ]]
- eq(json.b, exec_lua("return vim.NIL"))
-
- exec_lua('vim.lsp.util.apply_text_document_edit(...)', text_document_edit(exec_lua("return vim.NIL")))
+ it('always accepts edit with version = 0', function()
+ exec_lua([[
+ local args = {...}
+ local bufnr = select(1, ...)
+ local text_edit = select(2, ...)
+ vim.lsp.util.buf_versions[bufnr] = 10
+ vim.lsp.util.apply_text_document_edit(text_edit)
+ ]], target_bufnr, text_document_edit(0))
eq({
'First ↥ 🤦 🦄 line of text';
'2nd line of 语text';
@@ -1820,20 +1820,36 @@ describe('LSP', function()
end)
describe('lsp.util.jump_to_location', function()
- local target_bufnr
+ local default_target_bufnr
+ local default_target_uri = 'file://fake/uri'
- before_each(function()
- target_bufnr = exec_lua [[
- local bufnr = vim.uri_to_bufnr("file://fake/uri")
- local lines = {"1st line of text", "å å ɧ 汉语 ↥ 🤦 🦄"}
+ local create_buf = function(uri, lines)
+ for i, line in ipairs(lines) do
+ lines[i] = '"' .. line .. '"'
+ end
+ lines = table.concat(lines, ", ")
+
+ -- Let's set "hidden" to true in order to avoid errors when switching
+ -- between buffers in test.
+ local code = string.format([[
+ vim.api.nvim_set_option('hidden', true)
+
+ local bufnr = vim.uri_to_bufnr("%s")
+ local lines = {%s}
vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines)
return bufnr
- ]]
+ ]], uri, lines)
+
+ return exec_lua(code)
+ end
+
+ before_each(function()
+ default_target_bufnr = create_buf(default_target_uri, {'1st line of text', 'å å ɧ 汉语 ↥ 🤦 🦄'})
end)
- local location = function(start_line, start_char, end_line, end_char)
+ local location = function(uri, start_line, start_char, end_line, end_char)
return {
- uri = "file://fake/uri",
+ uri = uri,
range = {
start = { line = start_line, character = start_char },
["end"] = { line = end_line, character = end_char },
@@ -1841,9 +1857,9 @@ describe('LSP', function()
}
end
- local jump = function(msg)
+ local jump = function(bufnr, msg)
eq(true, exec_lua('return vim.lsp.util.jump_to_location(...)', msg))
- eq(target_bufnr, exec_lua[[return vim.fn.bufnr('%')]])
+ eq(bufnr, exec_lua[[return vim.fn.bufnr('%')]])
return {
line = exec_lua[[return vim.fn.line('.')]],
col = exec_lua[[return vim.fn.col('.')]],
@@ -1851,13 +1867,13 @@ describe('LSP', function()
end
it('jumps to a Location', function()
- local pos = jump(location(0, 9, 0, 9))
+ local pos = jump(default_target_bufnr, location(default_target_uri, 0, 9, 0, 9))
eq(1, pos.line)
eq(10, pos.col)
end)
it('jumps to a LocationLink', function()
- local pos = jump({
+ local pos = jump(default_target_bufnr, {
targetUri = "file://fake/uri",
targetSelectionRange = {
start = { line = 0, character = 4 },
@@ -1873,11 +1889,94 @@ describe('LSP', function()
end)
it('jumps to the correct multibyte column', function()
- local pos = jump(location(1, 2, 1, 2))
+ local pos = jump(default_target_bufnr, location(default_target_uri, 1, 2, 1, 2))
eq(2, pos.line)
eq(4, pos.col)
eq('å', exec_lua[[return vim.fn.expand('<cword>')]])
end)
+
+ it('should not push item to tagstack if destination is the same as source', function()
+ -- Set cursor at the 2nd line, 1st character. This is the source position
+ -- for the test, and will also be the destination one, making the cursor
+ -- "motionless", thus not triggering a push to the tagstack.
+ exec_lua(string.format([[
+ vim.api.nvim_win_set_buf(0, %d)
+ vim.api.nvim_win_set_cursor(0, {2, 0})
+ ]], default_target_bufnr))
+
+ -- Jump to 'f' in 'foobar', at the 2nd line.
+ jump(default_target_bufnr, location(default_target_uri, 1, 0, 1, 0))
+
+ local stack = exec_lua[[return vim.fn.gettagstack()]]
+ eq(0, stack.length)
+ end)
+
+ it('should not push the same item from same buffer twice to tagstack', function()
+ -- Set cursor at the 2nd line, 5th character.
+ exec_lua(string.format([[
+ vim.api.nvim_win_set_buf(0, %d)
+ vim.api.nvim_win_set_cursor(0, {2, 4})
+ ]], default_target_bufnr))
+
+ local stack
+
+ -- Jump to 1st line, 1st column.
+ jump(default_target_bufnr, location(default_target_uri, 0, 0, 0, 0))
+
+ stack = exec_lua[[return vim.fn.gettagstack()]]
+ eq({default_target_bufnr, 2, 5, 0}, stack.items[1].from)
+
+ -- Go back to 5th character at 2nd line, which is currently at the top of
+ -- the tagstack.
+ exec_lua(string.format([[
+ vim.api.nvim_win_set_cursor(0, {2, 4})
+ ]], default_target_bufnr))
+
+ -- Jump again to 1st line, 1st column. Since we're jumping from the same
+ -- position we have just jumped from, this jump shouldn't be pushed to
+ -- the tagstack.
+ jump(default_target_bufnr, location(default_target_uri, 0, 0, 0, 0))
+
+ stack = exec_lua[[return vim.fn.gettagstack()]]
+ eq({default_target_bufnr, 2, 5, 0}, stack.items[1].from)
+ eq(1, stack.length)
+ end)
+
+ it('should not push the same item from another buffer twice to tagstack', function()
+ local target_uri = 'file://foo/bar'
+ local target_bufnr = create_buf(target_uri, {'this is a line', 'foobar'})
+
+ -- Set cursor at the 1st line, 3rd character of the default test buffer.
+ exec_lua(string.format([[
+ vim.api.nvim_win_set_buf(0, %d)
+ vim.api.nvim_win_set_cursor(0, {1, 2})
+ ]], default_target_bufnr))
+
+ local stack
+
+ -- Jump to 1st line, 1st column of a different buffer from the source
+ -- position.
+ jump(target_bufnr, location(target_uri, 0, 0, 0, 0))
+
+ stack = exec_lua[[return vim.fn.gettagstack()]]
+ eq({default_target_bufnr, 1, 3, 0}, stack.items[1].from)
+
+ -- Go back to 3rd character at 1st line of the default test buffer, which
+ -- is currently at the top of the tagstack.
+ exec_lua(string.format([[
+ vim.api.nvim_win_set_buf(0, %d)
+ vim.api.nvim_win_set_cursor(0, {1, 2})
+ ]], default_target_bufnr))
+
+ -- Jump again to 1st line, 1st column of the different buffer. Since
+ -- we're jumping from the same position we have just jumped from, this
+ -- jump shouldn't be pushed to the tagstack.
+ jump(target_bufnr, location(target_uri, 0, 0, 0, 0))
+
+ stack = exec_lua[[return vim.fn.gettagstack()]]
+ eq({default_target_bufnr, 1, 3, 0}, stack.items[1].from)
+ eq(1, stack.length)
+ end)
end)
describe('lsp.util._make_floating_popup_size', function()
diff --git a/test/functional/treesitter/highlight_spec.lua b/test/functional/treesitter/highlight_spec.lua
index d80d0fdbaf..05e0c5fe2c 100644
--- a/test/functional/treesitter/highlight_spec.lua
+++ b/test/functional/treesitter/highlight_spec.lua
@@ -445,7 +445,7 @@ describe('treesitter highlighting', function()
exec_lua [[
local parser = vim.treesitter.get_parser(0, "c", {
- queries = {c = "(preproc_def (preproc_arg) @c) (preproc_function_def value: (preproc_arg) @c)"}
+ injections = {c = "(preproc_def (preproc_arg) @c) (preproc_function_def value: (preproc_arg) @c)"}
})
local highlighter = vim.treesitter.highlighter
test_hl = highlighter.new(parser, {queries = {c = hl_query}})
diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua
index f99362fbdf..72ff6f2fb6 100644
--- a/test/functional/treesitter/parser_spec.lua
+++ b/test/functional/treesitter/parser_spec.lua
@@ -468,7 +468,7 @@ int x = INT_MAX;
it("should inject a language", function()
exec_lua([[
parser = vim.treesitter.get_parser(0, "c", {
- queries = {
+ injections = {
c = "(preproc_def (preproc_arg) @c) (preproc_function_def value: (preproc_arg) @c)"}})
]])
@@ -489,7 +489,7 @@ int x = INT_MAX;
it("should inject a language", function()
exec_lua([[
parser = vim.treesitter.get_parser(0, "c", {
- queries = {
+ injections = {
c = "(preproc_def (preproc_arg) @c @combined) (preproc_function_def value: (preproc_arg) @c @combined)"}})
]])
@@ -506,11 +506,39 @@ int x = INT_MAX;
end)
end)
+ describe("when providing parsing information through a directive", function()
+ it("should inject a language", function()
+ exec_lua([=[
+ vim.treesitter.add_directive("inject-clang!", function(match, _, _, pred, metadata)
+ metadata.language = "c"
+ metadata.combined = true
+ metadata.content = pred[2]
+ end)
+
+ parser = vim.treesitter.get_parser(0, "c", {
+ injections = {
+ c = "(preproc_def ((preproc_arg) @_c (#inject-clang! @_c)))" ..
+ "(preproc_function_def value: ((preproc_arg) @_a (#inject-clang! @_a)))"}})
+ ]=])
+
+ eq("table", exec_lua("return type(parser:children().c)"))
+ eq(2, exec_lua("return #parser:children().c:trees()"))
+ eq({
+ {0, 0, 7, 0}, -- root tree
+ {3, 14, 5, 18}, -- VALUE 123
+ -- VALUE1 123
+ -- VALUE2 123
+ {1, 26, 2, 68} -- READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y))
+ -- READ_STRING_OK(x, y) (char_u *)read_string((x), (size_t)(y))
+ }, get_ranges())
+ end)
+ end)
+
describe("when using the offset directive", function()
it("should shift the range by the directive amount", function()
exec_lua([[
parser = vim.treesitter.get_parser(0, "c", {
- queries = {
+ injections = {
c = "(preproc_def ((preproc_arg) @c (#offset! @c 0 2 0 -1))) (preproc_function_def value: (preproc_arg) @c)"}})
]])
@@ -538,7 +566,7 @@ int x = INT_MAX;
it("should return the correct language tree", function()
local result = exec_lua([[
parser = vim.treesitter.get_parser(0, "c", {
- queries = { c = "(preproc_def (preproc_arg) @c)"}})
+ injections = { c = "(preproc_def (preproc_arg) @c)"}})
local sub_tree = parser:language_for_range({1, 18, 1, 19})
@@ -572,28 +600,5 @@ int x = INT_MAX;
eq(result, "value")
end)
end)
-
- describe("when setting for a capture match", function()
- it("should set/get the data correctly", function()
- insert([[
- int x = 3;
- ]])
-
- local result = exec_lua([[
- local result
-
- query = vim.treesitter.parse_query("c", '((number_literal) @number (#set! @number "key" "value"))')
- parser = vim.treesitter.get_parser(0, "c")
-
- for pattern, match, metadata in query:iter_matches(parser:parse()[1]:root(), 0) do
- result = metadata[pattern].key
- end
-
- return result
- ]])
-
- eq(result, "value")
- end)
- end)
end)
end)
diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua
index 29a76c7a07..0ea8bab957 100644
--- a/test/functional/ui/cmdline_spec.lua
+++ b/test/functional/ui/cmdline_spec.lua
@@ -759,6 +759,7 @@ local function test_cmdline(linegrid)
end)
it("doesn't send invalid events when aborting mapping #10000", function()
+ command('set notimeout')
command('cnoremap ab c')
feed(':xa')
diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua
index 295a54aec8..09638df6c5 100644
--- a/test/functional/ui/decorations_spec.lua
+++ b/test/functional/ui/decorations_spec.lua
@@ -29,6 +29,7 @@ describe('decorations providers', function()
[10] = {italic = true, background = Screen.colors.Magenta};
[11] = {foreground = Screen.colors.Red, background = tonumber('0x005028')};
[12] = {foreground = tonumber('0x990000')};
+ [13] = {background = Screen.colors.LightBlue};
}
end)
@@ -331,10 +332,70 @@ describe('decorations providers', function()
|
]]}
end)
+
+ it('can have virtual text of the style: right_align', function()
+ insert(mulholland)
+ setup_provider [[
+ local hl = a.nvim_get_hl_id_by_name "ErrorMsg"
+ local test_ns = a.nvim_create_namespace "mulholland"
+ function on_do(event, ...)
+ if event == "line" then
+ local win, buf, line = ...
+ a.nvim_buf_set_extmark(buf, test_ns, line, 0, {
+ virt_text = {{'+'}, {string.rep(' ', line+1), 'ErrorMsg'}};
+ virt_text_pos='right_align';
+ ephemeral = true;
+ })
+ end
+ end
+ ]]
+
+ screen:expect{grid=[[
+ // just to see if there was an acciden+{2: }|
+ // on Mulholland Drive +{2: }|
+ try_start(); +{2: }|
+ bufref_T save_buf; +{2: }|
+ switch_buffer(&save_buf, buf); +{2: }|
+ posp = getmark(mark, false); +{2: }|
+ restore_buffer(&save_buf);^ +{2: }|
+ |
+ ]]}
+ end)
+
+ it('can highlight beyond EOL', function()
+ insert(mulholland)
+ setup_provider [[
+ local test_ns = a.nvim_create_namespace "veberod"
+ function on_do(event, ...)
+ if event == "line" then
+ local win, buf, line = ...
+ if string.find(a.nvim_buf_get_lines(buf, line, line+1, true)[1], "buf") then
+ a.nvim_buf_set_extmark(buf, test_ns, line, 0, {
+ end_line = line+1;
+ hl_group = 'DiffAdd';
+ hl_eol = true;
+ ephemeral = true;
+ })
+ end
+ end
+ end
+ ]]
+
+ screen:expect{grid=[[
+ // just to see if there was an accident |
+ // on Mulholland Drive |
+ try_start(); |
+ {13:bufref_T save_buf; }|
+ {13:switch_buffer(&save_buf, buf); }|
+ posp = getmark(mark, false); |
+ {13:restore_buffer(&save_buf);^ }|
+ |
+ ]]}
+ end)
end)
describe('extmark decorations', function()
- local screen
+ local screen, ns
before_each( function()
clear()
screen = Screen.new(50, 15)
@@ -365,6 +426,8 @@ describe('extmark decorations', function()
[23] = {foreground = Screen.colors.Magenta1, background = Screen.colors.LightGrey};
[24] = {bold = true};
}
+
+ ns = meths.create_namespace 'test'
end)
local example_text = [[
@@ -385,7 +448,6 @@ end]]
insert(example_text)
feed 'gg'
- local ns = meths.create_namespace 'test'
for i = 1,9 do
meths.buf_set_extmark(0, ns, i, 0, { virt_text={{'|', 'LineNr'}}, virt_text_pos='overlay'})
if i == 3 or (i >= 6 and i <= 9) then
@@ -452,7 +514,6 @@ end]]
it('can have virtual text of overlay position and styling', function()
insert(example_text)
feed 'gg'
- local ns = meths.create_namespace 'test'
command 'set ft=lua'
command 'syntax on'
@@ -540,4 +601,88 @@ end]]
{24:-- VISUAL LINE --} |
]]}
end)
+
+ it('can have virtual text of fixed win_col position', function()
+ insert(example_text)
+ feed 'gg'
+ meths.buf_set_extmark(0, ns, 1, 0, { virt_text={{'Very', 'ErrorMsg'}}, virt_text_win_col=31, hl_mode='blend'})
+ meths.buf_set_extmark(0, ns, 2, 10, { virt_text={{'Much', 'ErrorMsg'}}, virt_text_win_col=31, hl_mode='blend'})
+ meths.buf_set_extmark(0, ns, 3, 15, { virt_text={{'Error', 'ErrorMsg'}}, virt_text_win_col=31, hl_mode='blend'})
+ meths.buf_set_extmark(0, ns, 7, 21, { virt_text={{'-', 'NonText'}}, virt_text_win_col=4, hl_mode='blend'})
+
+ screen:expect{grid=[[
+ ^for _,item in ipairs(items) do |
+ local text, hl_id_cell, cou{4:Very} unpack(item) |
+ if hl_id_cell ~= nil then {4:Much} |
+ hl_id = hl_id_cell {4:Error} |
+ end |
+ for _ = 1, (count or 1) do |
+ local cell = line[colpos] |
+ {1:-} cell.text = text |
+ cell.hl_id = hl_id |
+ colpos = colpos+1 |
+ end |
+ end |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed '3G12|i<cr><esc>'
+ screen:expect{grid=[[
+ for _,item in ipairs(items) do |
+ local text, hl_id_cell, cou{4:Very} unpack(item) |
+ if hl_i {4:Much} |
+ ^d_cell ~= nil then |
+ hl_id = hl_id_cell {4:Error} |
+ end |
+ for _ = 1, (count or 1) do |
+ local cell = line[colpos] |
+ {1:-} cell.text = text |
+ cell.hl_id = hl_id |
+ colpos = colpos+1 |
+ end |
+ end |
+ {1:~ }|
+ |
+ ]]}
+
+ feed 'u:<cr>'
+ screen:expect{grid=[[
+ for _,item in ipairs(items) do |
+ local text, hl_id_cell, cou{4:Very} unpack(item) |
+ if hl_i^d_cell ~= nil then {4:Much} |
+ hl_id = hl_id_cell {4:Error} |
+ end |
+ for _ = 1, (count or 1) do |
+ local cell = line[colpos] |
+ {1:-} cell.text = text |
+ cell.hl_id = hl_id |
+ colpos = colpos+1 |
+ end |
+ end |
+ {1:~ }|
+ {1:~ }|
+ : |
+ ]]}
+
+ feed '8|i<cr><esc>'
+ screen:expect{grid=[[
+ for _,item in ipairs(items) do |
+ local text, hl_id_cell, cou{4:Very} unpack(item) |
+ if |
+ ^hl_id_cell ~= nil then {4:Much} |
+ hl_id = hl_id_cell {4:Error} |
+ end |
+ for _ = 1, (count or 1) do |
+ local cell = line[colpos] |
+ {1:-} cell.text = text |
+ cell.hl_id = hl_id |
+ colpos = colpos+1 |
+ end |
+ end |
+ {1:~ }|
+ |
+ ]]}
+ end)
end)
diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua
index 965b9f160c..ccb13a69d2 100644
--- a/test/functional/ui/float_spec.lua
+++ b/test/functional/ui/float_spec.lua
@@ -690,6 +690,49 @@ describe('float window', function()
]]}
end
+ meths.win_set_config(win, {border="solid"})
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 5
+ {5: }|
+ {5: }{1: halloj! }{5: }|
+ {5: }{1: BORDAA }{5: }|
+ {5: }|
+ ]], float_pos={
+ [5] = { { id = 1002 }, "NW", 1, 2, 5, true }
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0};
+ [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }{5: }{0: }|
+ {0:~ }{5: }{1: halloj! }{5: }{0: }|
+ {0:~ }{5: }{1: BORDAA }{5: }{0: }|
+ {0:~ }{5: }{0: }|
+ |
+ ]]}
+ end
+
-- support: ascii char, UTF-8 char, composed char, highlight per char
meths.win_set_config(win, {border={"x", {"å", "ErrorMsg"}, {"\\"}, {"n̈̊", "Search"}}})
if multigrid then
@@ -5974,6 +6017,216 @@ describe('float window', function()
]])
end
end)
+
+ it("correctly orders multiple opened floats (current last)", function()
+ local buf = meths.create_buf(false,false)
+ local win = meths.open_win(buf, false, {relative='editor', width=20, height=2, row=2, col=5})
+ meths.win_set_option(win, "winhl", "Normal:ErrorMsg,EndOfBuffer:ErrorMsg")
+
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ {7: }|
+ {7:~ }|
+ ]], float_pos={
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true };
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }{7: }{0: }|
+ {0:~ }{7:~ }{0: }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]]}
+ end
+
+ exec_lua [[
+ local buf = vim.api.nvim_create_buf(false,false)
+ local win = vim.api.nvim_open_win(buf, false, {relative='editor', width=16, height=2, row=3, col=8})
+ vim.api.nvim_win_set_option(win, "winhl", "EndOfBuffer:Normal")
+ buf = vim.api.nvim_create_buf(false,false)
+ win = vim.api.nvim_open_win(buf, true, {relative='editor', width=12, height=2, row=4, col=10})
+ vim.api.nvim_win_set_option(win, "winhl", "Normal:Search,EndOfBuffer:Search")
+ ]]
+
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ {7: }|
+ {7:~ }|
+ ## grid 5
+ {1: }|
+ {1:~ }|
+ ## grid 6
+ {17:^ }|
+ {17:~ }|
+ ]], float_pos={
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true };
+ [5] = { { id = 1002 }, "NW", 1, 3, 8, true };
+ [6] = { { id = 1003 }, "NW", 1, 4, 10, true };
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0};
+ [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0};
+ [6] = {win = {id = 1003}, topline = 0, botline = 2, curline = 0, curcol = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }{7: }{0: }|
+ {0:~ }{7:~ }{1: }{7: }{0: }|
+ {0:~ }{1:~ }{17:^ }{1: }{0: }|
+ {0:~ }{17:~ }{0: }|
+ |
+ ]]}
+ end
+ end)
+
+ it("correctly orders multiple opened floats (non-current last)", function()
+ local buf = meths.create_buf(false,false)
+ local win = meths.open_win(buf, false, {relative='editor', width=20, height=2, row=2, col=5})
+ meths.win_set_option(win, "winhl", "Normal:ErrorMsg,EndOfBuffer:ErrorMsg")
+
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ {7: }|
+ {7:~ }|
+ ]], float_pos={
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true };
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }{7: }{0: }|
+ {0:~ }{7:~ }{0: }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]]}
+ end
+
+ exec_lua [[
+ local buf = vim.api.nvim_create_buf(false,false)
+ local win = vim.api.nvim_open_win(buf, true, {relative='editor', width=12, height=2, row=4, col=10})
+ vim.api.nvim_win_set_option(win, "winhl", "Normal:Search,EndOfBuffer:Search")
+ buf = vim.api.nvim_create_buf(false,false)
+ win = vim.api.nvim_open_win(buf, false, {relative='editor', width=16, height=2, row=3, col=8})
+ vim.api.nvim_win_set_option(win, "winhl", "EndOfBuffer:Normal")
+ ]]
+
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ {7: }|
+ {7:~ }|
+ ## grid 5
+ {17:^ }|
+ {17:~ }|
+ ## grid 6
+ {1: }|
+ {1:~ }|
+ ]], float_pos={
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true };
+ [5] = { { id = 1002 }, "NW", 1, 4, 10, true };
+ [6] = { { id = 1003 }, "NW", 1, 3, 8, true };
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0};
+ [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0};
+ [6] = {win = {id = 1003}, topline = 0, botline = 2, curline = 0, curcol = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }{7: }{0: }|
+ {0:~ }{7:~ }{1: }{7: }{0: }|
+ {0:~ }{1:~ }{17:^ }{1: }{0: }|
+ {0:~ }{17:~ }{0: }|
+ |
+ ]]}
+ end
+ end)
end
describe('with ext_multigrid', function()
diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua
index 1937102782..06c92a4b10 100644
--- a/test/functional/ui/sign_spec.lua
+++ b/test/functional/ui/sign_spec.lua
@@ -264,6 +264,24 @@ describe('Signs', function()
{0:~ }|
|
]]}
+ -- line deletion deletes signs.
+ command('2d')
+ screen:expect([[
+ {1:>>}XX{2: }{6: 1 }a |
+ XX{1:>>}WW{6: 2 }^c |
+ {2: }{6: 3 } |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
end)
it('auto-resize sign column with minimum size (#13783)', function()
diff --git a/test/helpers.lua b/test/helpers.lua
index 8dbd82cb8c..12d9f19187 100644
--- a/test/helpers.lua
+++ b/test/helpers.lua
@@ -365,7 +365,11 @@ function module.check_cores(app, force)
db_cmd = lldb_db_cmd
else
initial_path = '.'
- re = '/core[^/]*$'
+ if 'freebsd' == module.uname() then
+ re = '/nvim.core$'
+ else
+ re = '/core[^/]*$'
+ end
exc_re = { '^/%.deps$', '^/%'..deps_prefix()..'$', local_tmpdir, '^/%node_modules$' }
db_cmd = gdb_db_cmd
random_skip = true
diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt
index d0e7cdc9e3..351f517945 100644
--- a/third-party/CMakeLists.txt
+++ b/third-party/CMakeLists.txt
@@ -175,9 +175,9 @@ set(LUA_COMPAT53_SHA256 bec3a23114a3d9b3218038309657f0f506ad10dfbc03bb54e91da7e5
set(GPERF_URL https://github.com/neovim/deps/raw/ff5b4b18a87397a8564016071ae64f64bcd8c635/opt/gperf-3.1.tar.gz)
set(GPERF_SHA256 588546b945bba4b70b6a3a616e80b4ab466e3f33024a352fc2198112cdbb3ae2)
-# 7za.exe cat.exe curl.exe ca-bundle.crt diff.exe tee.exe tidy.exe xxd.exe
-set(WINTOOLS_URL https://github.com/neovim/deps/raw/2f9acbecf06365c10baa3c0087f34a54c9c6f949/opt/win32tools.zip)
-set(WINTOOLS_SHA256 8bfce7e3a365721a027ce842f2ec1cf878f1726233c215c05964aac07300798c)
+# cat.exe curl.exe ca-bundle.crt diff.exe tee.exe xxd.exe
+set(WINTOOLS_URL https://github.com/neovim/deps/raw/da3520b568054ce057e6168243ff50eea223bfa0/opt/win32tools.zip)
+set(WINTOOLS_SHA256 190149d369ae1cd266bc39bceb2d1c061833a23640dfabd4089082c1a7824421)
set(WINGUI_URL https://github.com/equalsraf/neovim-qt/releases/download/v0.2.16/neovim-qt.zip)
set(WINGUI_SHA256 aad95a1f8413a9ebf36fc0298d0dfd7d786abf88cb0f4ae9f7ec895b70c7b312)