aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.builds/openbsd.yml18
-rw-r--r--.clangd2
-rw-r--r--.github/labeler.yml8
-rw-r--r--.github/workflows/release.yml11
-rw-r--r--CMakeLists.txt18
-rw-r--r--runtime/autoload/dist/ft.vim11
-rw-r--r--runtime/doc/api.txt60
-rw-r--r--runtime/doc/autocmd.txt6
-rw-r--r--runtime/doc/change.txt30
-rw-r--r--runtime/doc/cmdline.txt3
-rw-r--r--runtime/doc/eval.txt17
-rw-r--r--runtime/doc/index.txt14
-rw-r--r--runtime/doc/lsp.txt66
-rw-r--r--runtime/doc/map.txt2
-rw-r--r--runtime/doc/options.txt3
-rw-r--r--runtime/doc/quickfix.txt9
-rw-r--r--runtime/doc/repeat.txt14
-rw-r--r--runtime/doc/spell.txt17
-rw-r--r--runtime/doc/starting.txt17
-rw-r--r--runtime/doc/syntax.txt3
-rw-r--r--runtime/doc/tips.txt2
-rw-r--r--runtime/doc/treesitter.txt67
-rw-r--r--runtime/doc/ui.txt14
-rw-r--r--runtime/doc/usr_05.txt2
-rw-r--r--runtime/doc/usr_41.txt13
-rw-r--r--runtime/doc/various.txt3
-rw-r--r--runtime/doc/vim_diff.txt3
-rw-r--r--runtime/filetype.vim20
-rw-r--r--runtime/ftplugin.vim4
-rw-r--r--runtime/indent.vim3
-rw-r--r--runtime/lua/vim/lsp.lua1
-rw-r--r--runtime/lua/vim/lsp/buf.lua9
-rw-r--r--runtime/lua/vim/lsp/codelens.lua231
-rw-r--r--runtime/lua/vim/lsp/diagnostic.lua20
-rw-r--r--runtime/lua/vim/lsp/handlers.lua8
-rw-r--r--runtime/lua/vim/lsp/rpc.lua13
-rw-r--r--runtime/lua/vim/lsp/util.lua158
-rw-r--r--runtime/spell/en.utf-8.splbin609337 -> 621617 bytes
-rw-r--r--runtime/syntax/lsp_markdown.vim10
-rw-r--r--runtime/syntax/synload.vim3
-rwxr-xr-xscripts/gen_vimdoc.py1
-rwxr-xr-xscripts/release.sh3
-rwxr-xr-xscripts/vim-patch.sh2
-rw-r--r--src/nvim/api/buffer.c34
-rw-r--r--src/nvim/api/private/helpers.c1
-rw-r--r--src/nvim/api/ui_events.in.h3
-rw-r--r--src/nvim/api/vim.c38
-rw-r--r--src/nvim/buffer.c2
-rw-r--r--src/nvim/eval.c77
-rw-r--r--src/nvim/eval/funcs.c61
-rw-r--r--src/nvim/eval/typval.c76
-rw-r--r--src/nvim/eval/typval.h2
-rw-r--r--src/nvim/eval/userfunc.c8
-rw-r--r--src/nvim/event/multiqueue.c6
-rw-r--r--src/nvim/event/multiqueue.h2
-rw-r--r--src/nvim/ex_cmds.c30
-rw-r--r--src/nvim/ex_cmds.lua6
-rw-r--r--src/nvim/ex_cmds2.c42
-rw-r--r--src/nvim/ex_docmd.c4
-rw-r--r--src/nvim/ex_getln.c32
-rw-r--r--src/nvim/fold.c5
-rw-r--r--src/nvim/globals.h1
-rw-r--r--src/nvim/lua/executor.c28
-rw-r--r--src/nvim/lua/treesitter.c1
-rw-r--r--src/nvim/lua/vim.lua5
-rw-r--r--src/nvim/macros.h2
-rw-r--r--src/nvim/main.c27
-rw-r--r--src/nvim/marktree.c1
-rw-r--r--src/nvim/memline.c1
-rw-r--r--src/nvim/normal.c19
-rw-r--r--src/nvim/normal.h2
-rw-r--r--src/nvim/ops.c21
-rw-r--r--src/nvim/option.c7
-rw-r--r--src/nvim/path.c48
-rw-r--r--src/nvim/popupmnu.c2
-rw-r--r--src/nvim/quickfix.c153
-rw-r--r--src/nvim/runtime.c22
-rw-r--r--src/nvim/screen.c26
-rw-r--r--src/nvim/spell.c68
-rw-r--r--src/nvim/spell_defs.h7
-rw-r--r--src/nvim/spellfile.c61
-rw-r--r--src/nvim/syntax.c4
-rw-r--r--src/nvim/testdir/test_autocmd.vim75
-rw-r--r--src/nvim/testdir/test_ex_mode.vim19
-rw-r--r--src/nvim/testdir/test_filetype.vim42
-rw-r--r--src/nvim/testdir/test_fold.vim20
-rw-r--r--src/nvim/testdir/test_global.vim4
-rw-r--r--src/nvim/testdir/test_let.vim10
-rw-r--r--src/nvim/testdir/test_normal.vim155
-rw-r--r--src/nvim/testdir/test_number.vim25
-rw-r--r--src/nvim/testdir/test_quickfix.vim151
-rw-r--r--src/nvim/testdir/test_spell.vim218
-rw-r--r--src/nvim/testdir/test_spell_utf8.vim773
-rw-r--r--src/nvim/testdir/test_spellfile.vim240
-rw-r--r--src/nvim/testdir/test_startup_utf8.vim3
-rw-r--r--src/nvim/testdir/test_visual.vim55
-rw-r--r--src/nvim/vim.h1
-rw-r--r--test/functional/autocmd/autocmd_spec.lua11
-rw-r--r--test/functional/core/startup_spec.lua70
-rw-r--r--test/functional/ex_cmds/source_spec.lua71
-rw-r--r--test/functional/fixtures/fake-lsp-server.lua17
-rw-r--r--test/functional/helpers.lua5
-rw-r--r--test/functional/legacy/011_autocommands_spec.lua9
-rw-r--r--test/functional/legacy/packadd_spec.lua10
-rw-r--r--test/functional/lua/runtime_spec.lua141
-rw-r--r--test/functional/plugin/lsp/codelens_spec.lua62
-rw-r--r--test/functional/plugin/lsp_spec.lua162
-rw-r--r--test/functional/ui/float_spec.lua90
-rw-r--r--test/functional/ui/messages_spec.lua4
-rw-r--r--test/functional/ui/spell_spec.lua3
-rw-r--r--test/functional/ui/tabline_spec.lua43
-rw-r--r--test/unit/eval/typval_spec.lua2
-rw-r--r--test/unit/marktree_spec.lua15
-rw-r--r--third-party/CMakeLists.txt18
114 files changed, 3587 insertions, 791 deletions
diff --git a/.builds/openbsd.yml b/.builds/openbsd.yml
index 0ffc8aa786..422fa366b6 100644
--- a/.builds/openbsd.yml
+++ b/.builds/openbsd.yml
@@ -1,17 +1,17 @@
# sourcehut CI: https://builds.sr.ht/~jmk/neovim
-image: openbsd/6.7
+image: openbsd/6.9
packages:
-- autoconf-2.69p2
-- automake-1.15.1
+- autoconf-2.71
+- automake-1.16.3
- cmake
-- gettext-runtime-0.20.1p1
-- gettext-tools-0.20.1p3
+- gettext-runtime-0.21p1
+- gettext-tools-0.21p1
- gmake
- libtool
-- ninja-1.10.0
-- unzip-6.0p13
+- ninja-1.10.2p0
+- unzip-6.0p14
sources:
- https://github.com/neovim/neovim
@@ -23,8 +23,8 @@ environment:
tasks:
- build-deps: |
- export AUTOCONF_VERSION=2.69
- export AUTOMAKE_VERSION=1.15
+ export AUTOCONF_VERSION=2.71
+ export AUTOMAKE_VERSION=1.16
mkdir neovim/.deps
cd neovim/.deps
cmake -G Ninja ../third-party/
diff --git a/.clangd b/.clangd
new file mode 100644
index 0000000000..d7911aaf64
--- /dev/null
+++ b/.clangd
@@ -0,0 +1,2 @@
+CompileFlags:
+ CompilationDatabase: build/ # Search build/ directory for compile_commands.json
diff --git a/.github/labeler.yml b/.github/labeler.yml
index 100f97c5f2..8a7c0639f3 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -14,6 +14,9 @@
- runtime/lua/vim/treesitter.lua
- runtime/lua/vim/treesitter/*
+"dependencies":
+ - third-party/**/*
+
"topic: spell":
- src/nvim/spell*
@@ -39,3 +42,8 @@
"topic: diff":
- src/nvim/diff.*
+
+"topic: build":
+ - CMakeLists.txt
+ - "**/CMakeLists.txt"
+ - "**/*.cmake"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 8c2333e68d..def934c597 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -13,6 +13,7 @@ on:
- v[0-9]+.[0-9]+.[0-9]+
# Build on the oldest supported images, so we have broader compatibility
+# Upgrade to gcc-11 to prevent it from using its builtins (#14150)
jobs:
linux:
runs-on: ubuntu-18.04
@@ -26,11 +27,11 @@ jobs:
- name: Install dependencies
run: |
sudo apt-get update
- sudo apt-get install -y autoconf automake build-essential cmake gettext gperf libtool-bin locales ninja-build pkg-config unzip
+ sudo apt-get install -y autoconf automake build-essential cmake gcc-11 gettext gperf libtool-bin locales ninja-build pkg-config unzip
- name: Build release
id: build
run: |
- make CMAKE_BUILD_TYPE=RelWithDebInfo CMAKE_EXTRA_FLAGS="-DCMAKE_INSTALL_PREFIX:PATH="
+ CC=gcc-11 make CMAKE_BUILD_TYPE=RelWithDebInfo CMAKE_EXTRA_FLAGS="-DCMAKE_INSTALL_PREFIX:PATH="
printf '::set-output name=version::%s\n' "$(./build/bin/nvim --version | head -n 3 | sed -z 's/\n/%0A/g')"
printf '::set-output name=release::%s\n' "$(./build/bin/nvim --version | head -n 1)"
make DESTDIR="$GITHUB_WORKSPACE/build/release/nvim-linux64" install
@@ -51,11 +52,11 @@ jobs:
- name: Install dependencies
run: |
sudo apt-get update
- sudo apt-get install -y autoconf automake build-essential cmake gettext gperf libtool-bin locales ninja-build pkg-config unzip
+ sudo apt-get install -y autoconf automake build-essential cmake gcc-11 gettext gperf libtool-bin locales ninja-build pkg-config unzip
- if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name != 'nightly')
- run: make appimage-latest
+ run: CC=gcc-11 make appimage-latest
- if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name == 'nightly')
- run: make appimage-nightly
+ run: CC=gcc-11 make appimage-nightly
- uses: actions/upload-artifact@v2
with:
name: appimage
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c22ab8dbae..346600740e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -23,9 +23,23 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON)
# Prefer our bundled versions of dependencies.
if(DEFINED ENV{DEPS_BUILD_DIR})
- set(DEPS_PREFIX "$ENV{DEPS_BUILD_DIR}/usr" CACHE PATH "Path prefix for finding dependencies")
+ if(CMAKE_SYSTEM_NAME MATCHES "OpenBSD")
+ # pkg-config 29.2 has a bug on OpenBSD which causes it to drop any paths that
+ # *contain* system include paths. To avoid this, we prefix what would be
+ # "/usr/include" as "/_usr/include".
+ # This check is also performed in the third-party/CMakeLists.txt and in the
+ # else clause following here.
+ # https://github.com/neovim/neovim/pull/14745#issuecomment-860201794
+ set(DEPS_PREFIX "$ENV{DEPS_BUILD_DIR}/_usr" CACHE PATH "Path prefix for finding dependencies")
+ else()
+ set(DEPS_PREFIX "$ENV{DEPS_BUILD_DIR}/usr" CACHE PATH "Path prefix for finding dependencies")
+ endif()
else()
- set(DEPS_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/.deps/usr" CACHE PATH "Path prefix for finding dependencies")
+ if(CMAKE_SYSTEM_NAME MATCHES "OpenBSD")
+ set(DEPS_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/.deps/_usr" CACHE PATH "Path prefix for finding dependencies")
+ else()
+ set(DEPS_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/.deps/usr" CACHE PATH "Path prefix for finding dependencies")
+ endif()
# When running from within CLion or Visual Studio,
# build bundled dependencies automatically.
if(NOT EXISTS ${DEPS_PREFIX}
diff --git a/runtime/autoload/dist/ft.vim b/runtime/autoload/dist/ft.vim
index 1ac74b5785..ac80659113 100644
--- a/runtime/autoload/dist/ft.vim
+++ b/runtime/autoload/dist/ft.vim
@@ -172,6 +172,17 @@ func dist#ft#FTent()
setf dtd
endfunc
+func dist#ft#ExCheck()
+ let lines = getline(1, min([line("$"), 100]))
+ if exists('g:filetype_euphoria')
+ exe 'setf ' . g:filetype_euphoria
+ elseif match(lines, '^--\|^ifdef\>\|^include\>') > -1
+ setf euphoria3
+ else
+ setf elixir
+ endif
+endfunc
+
func dist#ft#EuphoriaCheck()
if exists('g:filetype_euphoria')
exe 'setf ' . g:filetype_euphoria
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index 6b3b0f7762..07c45c9298 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -422,7 +422,7 @@ Buffer text can be highlighted by typical mechanisms (syntax highlighting,
options from the current window; specify `style=minimal` in |nvim_open_win()|
to disable various visual features such as the 'number' column.
-Currently, floating windows don't support widgets like border or scrollbar.
+Currently, floating windows don't support some widgets like scrollbar.
Example: create a float with scratch buffer: >
@@ -1325,33 +1325,37 @@ nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()*
and clearing the |EndOfBuffer| region in
'winhighlight'.
- • `border`: style of (optional) window border. This can
- either be a string or an array. the string
- values are:
- • "none" No border. This is the default
- • "single" a single line box
- • "double" a double line box
- • "shadow" a drop shadow effect by blending
- with the background. If it is an array it
- should be an array of eight items or any
- divisor of eight. The array will specifify
- the eight chars building up the border in a
- clockwise fashion starting with the top-left
- corner. As, an example, the double box style
- could be specified as: [ "╔", "═" ,"╗", "║",
- "╝", "═", "╚", "║" ] if the number of chars
- are less than eight, they will be repeated.
- Thus an ASCII border could be specified as:
- [ "/", "-", "\\", "|" ] or all chars the
- same as: [ "x" ] An empty string can be used
- to turn off a specific border, for instance:
- [ "", "", "", ">", "", "", "", "<" ] will
- only make vertical borders but not
- horizontal ones. By default `FloatBorder`
- highlight is used which links to `VertSplit`
- when not defined. It could also be specified
- by character: [ {"+", "MyCorner"}, {"x",
- "MyBorder"} ]
+ • `border`: Style of (optional) window border. This can
+ either be a string or an array. The string
+ values are
+ • "none": No border (default).
+ • "single": A single line box.
+ • "double": A double line box.
+ • "rounded": Like "single", but with rounded
+ corners ("╭" etc.).
+ • "solid": Adds padding by a single whitespace
+ cell.
+ • "shadow": A drop shadow effect by blending
+ with the background.
+ • If it is an array, it should have a length
+ of eight or any divisor of eight. The array
+ will specifify the eight chars building up
+ the border in a clockwise fashion starting
+ with the top-left corner. As an example, the
+ double box style could be specified as [
+ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ]. If
+ the number of chars are less than eight,
+ they will be repeated. Thus an ASCII border
+ could be specified as [ "/", "-", "\\", "|"
+ ], or all chars the same as [ "x" ]. An
+ empty string can be used to turn off a
+ specific border, for instance, [ "", "", "",
+ ">", "", "", "", "<" ] will only make
+ vertical borders but not horizontal ones. By
+ default, `FloatBorder` highlight is used,
+ which links to `VertSplit` when not defined.
+ It could also be specified by character: [
+ {"+", "MyCorner"}, {"x", "MyBorder"} ].
• `noautocmd` : If true then no buffer-related
autocommand events such as |BufEnter|,
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt
index bf94383ec4..9ee1954514 100644
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -831,16 +831,16 @@ ShellFilterPost After executing a shell command with
":{range}!cmd", ":w !cmd" or ":r !cmd".
Can be used to check for any changed files.
*SourcePre*
-SourcePre Before sourcing a Vim script. |:source|
+SourcePre Before sourcing a vim/lua file. |:source|
<afile> is the name of the file being sourced.
*SourcePost*
-SourcePost After sourcing a Vim script. |:source|
+SourcePost After sourcing a vim/lua file. |:source|
<afile> is the name of the file being sourced.
Not triggered when sourcing was interrupted.
Also triggered after a SourceCmd autocommand
was triggered.
*SourceCmd*
-SourceCmd When sourcing a Vim script. |:source|
+SourceCmd When sourcing a vim/lua file. |:source|
<afile> is the name of the file being sourced.
The autocommand must source this file.
|Cmd-event|
diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt
index 924401be74..7a63a89986 100644
--- a/runtime/doc/change.txt
+++ b/runtime/doc/change.txt
@@ -913,22 +913,7 @@ This replaces an end-of-line with a new line containing the value of $HOME. >
This replaces each 'E' character with a euro sign. Read more in |<Char->|.
-4.3 Search and replace *search-replace*
-
- *:pro* *:promptfind*
-:promptf[ind] [string]
- Put up a Search dialog. When [string] is given, it is
- used as the initial search string.
- {only for Win32 GUI}
-
- *:promptr* *:promptrepl*
-:promptr[epl] [string]
- Put up a Search/Replace dialog. When [string] is
- given, it is used as the initial search string.
- {only for Win32 GUI}
-
-
-4.4 Changing tabs *change-tabs*
+4.3 Changing tabs *change-tabs*
*:ret* *:retab* *:retab!*
:[range]ret[ab][!] [new_tabstop]
Replace all sequences of white-space containing a
@@ -1007,6 +992,10 @@ inside of strings can change! Also see 'softtabstop' option. >
cursor to the end of line (which is more logical,
but not Vi-compatible) use ":map Y y$".
+ *zy*
+["x]zy{motion} Yank {motion} text [into register x]. Only differs
+ from `y` when selecting a block of text, see |v_zy|.
+
*v_y*
{Visual}["x]y Yank the highlighted text [into register x] (for
{Visual} see |Visual-mode|).
@@ -1015,6 +1004,12 @@ inside of strings can change! Also see 'softtabstop' option. >
{Visual}["x]Y Yank the highlighted lines [into register x] (for
{Visual} see |Visual-mode|).
+ *v_zy*
+{Visual}["x]zy Yank the highlighted text [into register x]. Trailing
+ whitespace at the end of each line of a selected block
+ won't be yanked. Especially useful in combination
+ with `zp`. (for {Visual} see |Visual-mode|)
+
*:y* *:yank* *E850*
:[range]y[ank] [x] Yank [range] lines [into register x]. Yanking to the
"* or "+ registers is possible only when the
@@ -1094,7 +1089,8 @@ inside of strings can change! Also see 'softtabstop' option. >
["x]zp or *zp* *zP*
["x]zP Like "p" and "P", except without adding trailing spaces
when pasting a block. Thus the inserted text will not
- always be a rectangle.
+ always be a rectangle. Especially useful in
+ combination with |v_zy|.
You can use these commands to copy text from one place to another. Do this
by first getting the text into a register with a yank, delete or change
diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt
index dcdc2384dc..2db694cf07 100644
--- a/runtime/doc/cmdline.txt
+++ b/runtime/doc/cmdline.txt
@@ -532,7 +532,6 @@ that see the '"' as part of their argument:
:normal
:ownsyntax
:popup
- :promptfind (and the like)
:registers
:return
:sort
@@ -571,8 +570,6 @@ followed by another Vim command:
:make
:normal
:perlfile
- :promptfind
- :promptrepl
:pyfile
:python
:registers
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index b7214d1390..bf7f2b21de 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -3693,6 +3693,8 @@ expand({expr} [, {nosuf} [, {list}]]) *expand()*
line number
<sflnum> script file line number, also when in
a function
+ <SID> "<SNR>123_" where "123" is the
+ current script ID |<SID>|
<cword> word under the cursor
<cWORD> WORD under the cursor
<client> the {clientid} of the last received
@@ -7877,7 +7879,8 @@ setqflist({list} [, {action}[, {what}]]) *setqflist()*
the last quickfix list.
quickfixtextfunc
function to get the text to display in the
- quickfix window. Refer to
+ quickfix window. The value can be the name of
+ a function or a funcref or a lambda. Refer to
|quickfix-window-function| for an explanation
of how to write the function and an example.
title quickfix list title text. See |quickfix-title|
@@ -8230,9 +8233,8 @@ spellbadword([{sentence}])
echo spellbadword("the quik brown fox")
< ['quik', 'bad'] ~
- The spelling information for the current window is used. The
- 'spell' option must be set and the value of 'spelllang' is
- used.
+ The spelling information for the current window and the value
+ of 'spelllang' are used.
*spellsuggest()*
spellsuggest({word} [, {max} [, {capital}]])
@@ -8254,8 +8256,7 @@ spellsuggest({word} [, {max} [, {capital}]])
although it may appear capitalized.
The spelling information for the current window is used. The
- 'spell' option must be set and the values of 'spelllang' and
- 'spellsuggest' are used.
+ values of 'spelllang' and 'spellsuggest' are used.
split({expr} [, {pattern} [, {keepempty}]]) *split()*
@@ -10195,7 +10196,9 @@ text...
{endmarker}
Set internal variable {var-name} to a |List|
containing the lines of text bounded by the string
- {endmarker}.
+ {endmarker}. The lines of text is used as a
+ |literal-string|.
+ {endmarker} must not contain white space.
{endmarker} cannot start with a lower case character.
The last line should end only with the {endmarker}
string without any other character. Watch out for
diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt
index 17258f896d..2aafc075a6 100644
--- a/runtime/doc/index.txt
+++ b/runtime/doc/index.txt
@@ -865,6 +865,7 @@ tag char note action in Normal mode ~
|zv| zv open enough folds to view the cursor line
|zw| zw permanently mark word as incorrectly spelled
|zx| zx re-apply 'foldlevel' and do "zv"
+|zy| zy yank without trailing spaces
|zz| zz redraw, cursor line at center of window
|z<Left>| z<Left> same as "zh"
|z<Right>| z<Right> same as "zl"
@@ -1200,6 +1201,7 @@ tag command action ~
|:cgetfile| :cg[etfile] read file with error messages
|:changes| :changes print the change list
|:chdir| :chd[ir] change directory
+|:checkhealth| :checkh[ealth] run healthchecks
|:checkpath| :che[ckpath] list included files
|:checktime| :checkt[ime] check timestamp of loaded buffers
|:chistory| :chi[story] list the error lists
@@ -1277,6 +1279,7 @@ tag command action ~
|:endtry| :endt[ry] end previous :try
|:endwhile| :endw[hile] end previous :while
|:enew| :ene[w] edit a new, unnamed buffer
+|:eval| :ev[al] evaluate an expression and discard the result
|:ex| :ex same as ":edit"
|:execute| :exe[cute] execute result of expressions
|:exit| :exi[t] same as ":xit"
@@ -1450,14 +1453,12 @@ tag command action ~
|:packloadall| :packl[oadall] load all packages under 'packpath'
|:pclose| :pc[lose] close preview window
|:pedit| :ped[it] edit file in the preview window
-|:perl| :perl execute perl command
-|:perldo| :perldo execute perl command for each line
-|:perfile| :perlfile execute perl script file
+|:perl| :pe[rl] execute perl command
+|:perldo| :perld[o] execute perl command for each line
+|:perlfile| :perlf[ile] execute perl script file
|:print| :p[rint] print lines
|:profdel| :profd[el] stop profiling a function or script
|:profile| :prof[ile] profiling functions and scripts
-|:promptfind| :pro[mptfind] open GUI dialog for searching
-|:promptrepl| :promptr[epl] open GUI dialog for search/replace
|:pop| :po[p] jump to older entry in tag stack
|:popup| :popu[p] popup a menu by name
|:ppop| :pp[op] ":pop" in preview window
@@ -1536,7 +1537,6 @@ tag command action ~
buffer list
|:scriptnames| :scr[iptnames] list names of all sourced Vim scripts
|:scriptencoding| :scripte[ncoding] encoding used in sourced Vim script
-|:scriptversion| :scriptv[ersion] version of Vim script used
|:scscope| :scs[cope] split window and execute cscope command
|:set| :se[t] show or set options
|:setfiletype| :setf[iletype] set 'filetype', unless it was set already
@@ -1548,8 +1548,6 @@ tag command action ~
|:sign| :sig[n] manipulate signs
|:silent| :sil[ent] run a command silently
|:sleep| :sl[eep] do nothing for a few seconds
-|:sleep!| :sl[eep]! do nothing for a few seconds, without the
- cursor visible
|:slast| :sla[st] split window and go to last file in the
argument list
|:smagic| :sm[agic] :substitute with 'magic'
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index 531374620a..d6ef761bcb 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -135,12 +135,6 @@ FAQ *lsp-faq*
autocmd BufWritePre *.rs lua vim.lsp.buf.formatting_sync(nil, 1000)
<
- *vim.lsp.callbacks*
-- Q: What happened to `vim.lsp.callbacks`?
- A: After better defining the interface of |lsp-handler|s, we thought it best
- to remove the generic usage of `callbacks` and transform to `handlers`.
- Due to this, `vim.lsp.callbacks` was renamed to |vim.lsp.handlers|.
-
*lsp-vs-treesitter*
- Q: How do LSP and Treesitter compare?
A: LSP requires a client and language server. The language server uses
@@ -525,6 +519,15 @@ LspDiagnosticsSignHint
Used for "Hint" signs in sign column.
See |vim.lsp.diagnostic.set_signs()|
+ *lsp-highlight-codelens*
+
+Highlight groups related to |lsp-codelens| functionality.
+
+ *hl-LspCodeLens*
+LspCodeLens
+ Used to color the virtual text of the codelens. See
+ |nvim_buf_set_virtual_text()|.
+
==============================================================================
AUTOCOMMANDS *lsp-autocommands*
@@ -947,9 +950,6 @@ add_workspace_folder({workspace_folder})
not provided, the user will be prompted for a path using
|input()|.
-call_hierarchy({method}) *vim.lsp.buf.call_hierarchy()*
- TODO: Documentation
-
clear_references() *vim.lsp.buf.clear_references()*
Removes document highlights from current buffer.
@@ -1550,6 +1550,10 @@ show_line_diagnostics({opts}, {bufnr}, {line_nr}, {client_id})
{opts} table Configuration table
• show_header (boolean, default true): Show
"Diagnostics:" header.
+ • Plus all the opts for
+ |vim.lsp.diagnostic.get_line_diagnostics()|
+ and |vim.lsp.util.open_floating_preview()|
+ can be used here.
{bufnr} number The buffer number
{line_nr} number The line number
{client_id} number|nil the client id
@@ -1559,6 +1563,50 @@ show_line_diagnostics({opts}, {bufnr}, {line_nr}, {client_id})
==============================================================================
+Lua module: vim.lsp.codelens *lsp-codelens*
+
+display({lenses}, {bufnr}, {client_id}) *vim.lsp.codelens.display()*
+ Display the lenses using virtual text
+
+ Parameters: ~
+ {lenses} table of lenses to display ( `CodeLens[] |
+ null` )
+ {bufnr} number
+ {client_id} number
+
+get({bufnr}) *vim.lsp.codelens.get()*
+ Return all lenses for the given buffer
+
+ Return: ~
+ table ( `CodeLens[]` )
+
+ *vim.lsp.codelens.on_codelens()*
+on_codelens({err}, {_}, {result}, {client_id}, {bufnr})
+ |lsp-handler| for the method `textDocument/codeLens`
+
+refresh() *vim.lsp.codelens.refresh()*
+ Refresh the codelens for the current buffer
+
+ It is recommended to trigger this using an autocmd or via
+ keymap.
+>
+ autocmd BufEnter,CursorHold,InsertLeave <buffer> lua vim.lsp.codelens.refresh()
+<
+
+run() *vim.lsp.codelens.run()*
+ Run the code lens in the current line
+
+save({lenses}, {bufnr}, {client_id}) *vim.lsp.codelens.save()*
+ Store lenses for a specific buffer and client
+
+ Parameters: ~
+ {lenses} table of lenses to store ( `CodeLens[] |
+ null` )
+ {bufnr} number
+ {client_id} number
+
+
+==============================================================================
Lua module: vim.lsp.handlers *lsp-handlers*
*vim.lsp.handlers.progress_handler()*
diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt
index 77cbf7d9b7..10d503e180 100644
--- a/runtime/doc/map.txt
+++ b/runtime/doc/map.txt
@@ -855,7 +855,7 @@ Here is an example that counts the number of spaces with <F4>: >
set clipboard= selection=inclusive
let commands = #{line: "'[V']y", char: "`[v`]y", block: "`[\<c-v>`]y"}
silent exe 'noautocmd keepjumps normal! ' .. get(commands, a:type, '')
- echom getreg('"')->count(' ')
+ echom count(getreg('"'), ' ')
finally
call setreg('"', reg_save)
call setpos("'<", visual_marks_save[0])
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index cc9696e536..af65241bbf 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -4607,7 +4607,8 @@ A jump table for the options with a short description can be found at |Q_op|.
customize the information displayed in the quickfix or location window
for each entry in the corresponding quickfix or location list. See
|quickfix-window-function| for an explanation of how to write the
- function and an example.
+ function and an example. The value can be the name of a function or a
+ lambda.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt
index a937cfee98..563fb0c962 100644
--- a/runtime/doc/quickfix.txt
+++ b/runtime/doc/quickfix.txt
@@ -1208,8 +1208,8 @@ not "b:current_compiler". What the command actually does is the following:
- Delete the "current_compiler" and "b:current_compiler" variables.
- Define the "CompilerSet" user command. With "!" it does ":set", without "!"
it does ":setlocal".
-- Execute ":runtime! compiler/{name}.vim". The plugins are expected to set
- options with "CompilerSet" and set the "current_compiler" variable to the
+- Execute ":runtime! compiler/{name}.(vim|lua)". The plugins are expected to
+ set options with "CompilerSet" and set the "current_compiler" variable to the
name of the compiler.
- Delete the "CompilerSet" user command.
- Set "b:current_compiler" to the value of "current_compiler".
@@ -1919,7 +1919,10 @@ The function should return a single line of text to display in the quickfix
window for each entry from start_idx to end_idx. The function can obtain
information about the entries using the |getqflist()| function and specifying
the quickfix list identifier "id". For a location list, getloclist() function
-can be used with the 'winid' argument.
+can be used with the 'winid' argument. If an empty list is returned, then the
+default format is used to display all the entries. If an item in the returned
+list is an empty string, then the default format is used to display the
+corresponding entry.
If a quickfix or location list specific customization is needed, then the
'quickfixtextfunc' attribute of the list can be set using the |setqflist()| or
diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt
index dd05084652..6755747dcf 100644
--- a/runtime/doc/repeat.txt
+++ b/runtime/doc/repeat.txt
@@ -172,9 +172,11 @@ Using Vim scripts *using-scripts*
For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|.
*:so* *:source* *load-vim-script*
-:so[urce] {file} Read Ex commands from {file}. These are commands that
- start with a ":".
+:so[urce] {file} Runs vim or lua {file}
Triggers the |SourcePre| autocommand.
+
+ Note: Only files ending with `.lua` is sourced as
+ lua file. Anything else is assumed to be vimscript.
*:source!*
:so[urce]! {file} Read Vim commands from {file}. These are commands
that are executed from Normal mode, like you type
@@ -187,8 +189,10 @@ For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|.
*:ru* *:runtime*
:ru[ntime][!] [where] {file} ..
- Read Ex commands from {file} in each directory given
- by 'runtimepath' and/or 'packpath'. There is no error
+ Source vim/lua {file} in each directory given by
+ 'runtimepath' and/or 'packpath'. The vim files are
+ executed in same mannar as |:source| and lua files
+ similarly as |:luafile|. There is no error
for non-existing files.
Example: >
@@ -244,6 +248,8 @@ For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|.
Note that {name} is the directory name, not the name
of the .vim file. All the files matching the pattern
pack/*/opt/{name}/plugin/**/*.vim ~
+ and
+ pack/*/opt/{name}/plugin/**/*.lua ~
will be sourced. This allows for using subdirectories
below "plugin", just like with plugins in
'runtimepath'.
diff --git a/runtime/doc/spell.txt b/runtime/doc/spell.txt
index f722747ce9..22d7cdf491 100644
--- a/runtime/doc/spell.txt
+++ b/runtime/doc/spell.txt
@@ -110,6 +110,23 @@ zuG Undo |zW| and |zG|, remove the word from the internal
:spellw[rong]! {word} Add {word} as a wrong (bad) word to the internal word
list, like with |zW|.
+ *:spellra* *:spellrare*
+:[count]spellr[are] {word}
+ Add {word} as a rare word to 'spellfile', similar to
+ |zw|. Without count the first name is used, with
+ a count of two the second entry, etc.
+
+ There are no normal mode commands to mark words as
+ rare as this is a fairly uncommon command and all
+ intuitive commands for this are already taken. If you
+ want you can add mappings with e.g.: >
+ nnoremap z? :exe ':spellrare ' . expand('<cWORD>')<CR>
+ nnoremap z/ :exe ':spellrare! ' . expand('<cWORD>')<CR>
+< |:spellundo|, |zuw|, or |zuW| can be used to undo this.
+
+:spellr[rare]! {word} Add {word} as a rare word to the internal word
+ list, similar to |zW|.
+
:[count]spellu[ndo] {word} *:spellu* *:spellundo*
Like |zuw|. [count] used as with |:spellgood|.
diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt
index ae9022c56c..5431ce3bd8 100644
--- a/runtime/doc/starting.txt
+++ b/runtime/doc/starting.txt
@@ -416,11 +416,11 @@ accordingly. Vim proceeds in this order:
the same time. Each line in a "init.vim" is executed as an Ex command
line. See also |vimrc-intro| and |base-directories|.
- The Nvim config file is "init.vim", located at:
- Unix ~/.config/nvim/init.vim
- Windows ~/AppData/Local/nvim/init.vim
+ The config file is located at:
+ Unix ~/.config/nvim/init.vim (or init.lua)
+ Windows ~/AppData/Local/nvim/init.vim (or init.lua)
or if |$XDG_CONFIG_HOME| is defined:
- $XDG_CONFIG_HOME/nvim/init.vim
+ $XDG_CONFIG_HOME/nvim/init.vim (or init.lua)
If Nvim was started with "-u {file}" then {file} is used as the config
and all initializations until 5. are skipped. $MYVIMRC is not set.
@@ -469,10 +469,15 @@ accordingly. Vim proceeds in this order:
7. Load the plugin scripts. *load-plugins*
This does the same as the command: >
:runtime! plugin/**/*.vim
+ :runtime! plugin/**/*.lua
< The result is that all directories in the 'runtimepath' option will be
searched for the "plugin" sub-directory and all files ending in ".vim"
- will be sourced (in alphabetical order per directory), also in
- subdirectories.
+ and ".lua" will be sourced (in alphabetical order per directory),
+ also in subdirectories. First all the "*.vim" files will be sourced and
+ then all the "*.lua" files will be sourced. If two files with same
+ name but different extensions exists they will be treated in same
+ manner. For example when both "foo.vim" and "foo.lua" exists then
+ first "foo.vim" will be sourced then "foo.lua" will be sourced.
However, directories in 'runtimepath' ending in "after" are skipped
here and only loaded after packages, see below.
Loading plugins won't be done when:
diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt
index b159f655fa..bf649b5940 100644
--- a/runtime/doc/syntax.txt
+++ b/runtime/doc/syntax.txt
@@ -4749,8 +4749,9 @@ in their own color.
feature it will output "unknown".
:colo[rscheme] {name} Load color scheme {name}. This searches 'runtimepath'
- for the file "colors/{name}.vim". The first one that
+ for the file "colors/{name}.(vim|lua)". The first one that
is found is loaded.
+ Note: "colors/{name}.vim" is tried first.
Also searches all plugins in 'packpath', first below
"start" and then under "opt".
diff --git a/runtime/doc/tips.txt b/runtime/doc/tips.txt
index bcb2e48cbf..27b6ca2885 100644
--- a/runtime/doc/tips.txt
+++ b/runtime/doc/tips.txt
@@ -462,7 +462,7 @@ the current window, try this custom `:HelpCurwin` command:
execute mods .. ' helpclose'
let s:did_open_help = v:true
endif
- if !getcompletion(a:subject, 'help')->empty()
+ if !empty(getcompletion(a:subject, 'help'))
execute mods .. ' edit ' .. &helpfile
endif
return 'help ' .. a:subject
diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt
index d6bca55bd6..416ea3a08a 100644
--- a/runtime/doc/treesitter.txt
+++ b/runtime/doc/treesitter.txt
@@ -13,25 +13,6 @@ VIM.TREESITTER *lua-treesitter*
Nvim integrates the tree-sitter library for incremental parsing of buffers.
-Currently Nvim does not provide the tree-sitter parsers, instead these must
-be built separately, for instance using the tree-sitter utility. The only
-exception is a C parser being included in official builds for testing
-purposes. Parsers are searched for as `parser/{lang}.*` in any 'runtimepath'
-directory. A parser can also be loaded manually using a full path: >
-
- vim.treesitter.require_language("python", "/path/to/python.so")
-
-<Create a parser for a buffer and a given language (if another plugin uses the
-same buffer/language combination, it will be safely reused). Use >
-
- parser = vim.treesitter.get_parser(bufnr, lang)
-
-<`bufnr=0` can be used for current buffer. `lang` will default to 'filetype' (this
-doesn't work yet for some filetypes like "cpp") Currently, the parser will be
-retained for the lifetime of a buffer but this is subject to change. A plugin
-should keep a reference to the parser object as long as it wants incremental
-updates.
-
*vim.treesitter.language_version*
To check which language version is compiled with neovim, the number is stored
within `vim.treesitter.language_version`. This number is not too helpful
@@ -41,10 +22,25 @@ compiled grammars.
Parser files *treesitter-parsers*
Parsers are the heart of tree-sitter. They are libraries that tree-sitter will
-search for in the `parser` runtime directory.
+search for in the `parser` runtime directory. Currently Nvim does not provide
+the tree-sitter parsers, instead these must be built separately, for instance
+using the tree-sitter utility. The only exception is a C parser being included
+in official builds for testing purposes. Parsers are searched for as
+`parser/{lang}.*` in any 'runtimepath' directory.
+A parser can also be loaded manually using a full path: >
+
+ vim.treesitter.require_language("python", "/path/to/python.so")
+
+<Create a parser for a buffer and a given language (if another plugin uses the
+same buffer/language combination, it will be safely reused). Use >
+
+ parser = vim.treesitter.get_parser(bufnr, lang)
+
+<`bufnr=0` can be used for current buffer. `lang` will default to 'filetype'.
+Currently, the parser will be retained for the lifetime of a buffer but this
+is subject to change. A plugin should keep a reference to the parser object as
+long as it wants incremental updates.
-For a parser to be available for a given language, there must be a file named
-`{lang}.so` within the parser directory.
Parser methods *lua-treesitter-parser*
@@ -59,11 +55,11 @@ it should call `parse()` again. If the buffer wasn't edited, the same tree will
be returned again without extra work. If the buffer was parsed before,
incremental parsing will be done of the changed parts.
-NB: to use the parser directly inside a |nvim_buf_attach| Lua callback, you must
-call `get_parser()` before you register your callback. But preferably parsing
-shouldn't be done directly in the change callback anyway as they will be very
-frequent. Rather a plugin that does any kind of analysis on a tree should use
-a timer to throttle too frequent updates.
+Note: to use the parser directly inside a |nvim_buf_attach| Lua callback, you
+must call `get_parser()` before you register your callback. But preferably
+parsing shouldn't be done directly in the change callback anyway as they will
+be very frequent. Rather a plugin that does any kind of analysis on a tree
+should use a timer to throttle too frequent updates.
tsparser:set_included_regions({region_list}) *tsparser:set_included_regions()*
Changes the regions the parser should consider. This is used for
@@ -153,7 +149,8 @@ tsnode:id() *tsnode:id()*
except for being a primitive lua type with value equality (so not a table).
Presently it is a (non-printable) string.
- NB: the id is not guaranteed to be unique for nodes from different trees.
+ Note: 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()*
@@ -167,9 +164,14 @@ tsnode:named_descendant_for_range({start_row}, {start_col}, {end_row}, {end_col}
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
-and two captures against each other).
+Tree-sitter queries are supported, they are a way to do pattern-matching over
+a tree, using a simple to write lisp-like format. See
+https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax for more
+information on how to write queries.
+
+Note: The perdicates listed in the web page above differ from those Neovim
+supports. See |lua-treesitter-predicates| for a complete list of predicates
+supported by Neovim.
A `query` consists of one or more patterns. A `pattern` is defined over node
types in the syntax tree. A `match` corresponds to specific elements of the
@@ -214,6 +216,9 @@ Here is a list of built-in predicates :
<
`any-of?` *ts-predicate-any-of?*
Will check if the text is the same as any of the following
+ This is the recommended way to check if the node matches one
+ of many keywords for exemple, as it has been optimized for
+ this.
arguments : >
((identifier) @foo (#any-of? @foo "foo" "bar"))
<
diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt
index 82406898c8..e7be14e732 100644
--- a/runtime/doc/ui.txt
+++ b/runtime/doc/ui.txt
@@ -219,8 +219,8 @@ the editor.
["busy_start"]
["busy_stop"]
- Nvim started or stopped being busy, and possibly not responsive to
- user input. This could be indicated to the user by hiding the cursor.
+ Indicates to the UI that it must stop rendering the cursor. This event
+ is misnamed and does not actually have anything to do with busyness.
["suspend"]
|:suspend| command or |CTRL-Z| mapping is used. A terminal client (or
@@ -631,11 +631,13 @@ Tabline Events *ui-tabline*
Activated by the `ext_tabline` |ui-option|.
-["tabline_update", curtab, tabs]
+["tabline_update", curtab, tabs, curbuf, buffers]
Tabline was updated. UIs should present this data in a custom tabline
- widget.
- curtab: Current Tabpage
- tabs: List of Dicts [{ "tab": Tabpage, "name": String }, ...]
+ widget. Note: options `curbuf` + `buffers` were added in API7.
+ curtab: Current Tabpage
+ tabs: List of Dicts [{ "tab": Tabpage, "name": String }, ...]
+ curbuf: Current buffer handle.
+ buffers: List of Dicts [{ "buffer": buffer handle, "name": String}, ...]
==============================================================================
Cmdline Events *ui-cmdline*
diff --git a/runtime/doc/usr_05.txt b/runtime/doc/usr_05.txt
index d8634ac6ed..d0206ba82d 100644
--- a/runtime/doc/usr_05.txt
+++ b/runtime/doc/usr_05.txt
@@ -411,7 +411,7 @@ Examples for the "stuff" filetype on Unix: >
The <filetype> part is the name of the filetype the plugin is to be used for.
Only files of this filetype will use the settings from the plugin. The <name>
part of the plugin file doesn't matter, you can use it to have several plugins
-for the same filetype. Note that it must end in ".vim".
+for the same filetype. Note that it must end in ".vim" or ".lua".
Further reading:
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
index f92cb3c509..a190bf2f27 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -99,8 +99,6 @@ and the value of the variable i. Since i is one, this will print:
Then there is the ":let i += 1" command. This does the same thing as
":let i = i + 1". This adds one to the variable i and assigns the new value
to the same variable.
-Note: this is how it works in legacy Vim script, which is what we discuss in
-this file.
The example was given to explain the commands, but would you really want to
make such a loop, it can be written much more compact: >
@@ -120,24 +118,23 @@ Numbers can be decimal, hexadecimal, octal or binary.
A hexadecimal number starts with "0x" or "0X". For example "0x1f" is decimal
31.
-An octal number starts with "0o", "0O" or a zero and another digit. "0o17" is
-decimal 15. Using just a zero prefix is not supported in Vim9 script.
+An octal number starts with a zero and another digit. "017" is decimal 15.
A binary number starts with "0b" or "0B". For example "0b101" is decimal 5.
A decimal number is just digits. Careful: don't put a zero before a decimal
-number, it will be interpreted as an octal number in legacy script!
+number, it will be interpreted as an octal number!
The ":echo" command always prints decimal numbers. Example: >
- :echo 0x7f 0o36
+ :echo 0x7f 036
< 127 30 ~
A number is made negative with a minus sign. This also works for hexadecimal,
octal and binary numbers. A minus sign is also used for subtraction. Compare
this with the previous example: >
- :echo 0x7f -0o36
+ :echo 0x7f -036
< 97 ~
White space in an expression is ignored. However, it's recommended to use it
@@ -145,7 +142,7 @@ for separating items, to make the expression easier to read. For example, to
avoid the confusion with a negative number above, put a space between the
minus sign and the following number: >
- :echo 0x7f - 0o36
+ :echo 0x7f - 036
==============================================================================
*41.2* Variables
diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt
index d5c07d9622..ec91b6e29a 100644
--- a/runtime/doc/various.txt
+++ b/runtime/doc/various.txt
@@ -506,7 +506,8 @@ gO Show a filetype-specific, navigable "outline" of the
Queued messages are processed during the sleep.
*:sl!* *:sleep!*
-:[N]sl[eep]! [N] [m] Same as above, but hide the cursor.
+:[N]sl[eep]! [N] [m] Same as above. Unlike Vim, it does not hide the
+ cursor. |vim-differences|
==============================================================================
2. Using Vim like less or more *less*
diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
index bb30495d77..27c4b82aca 100644
--- a/runtime/doc/vim_diff.txt
+++ b/runtime/doc/vim_diff.txt
@@ -421,7 +421,10 @@ Commands:
:mode (no longer accepts an argument)
:open
:Print
+ :promptfind
+ :promptrepl
:shell
+ :sleep! (does not hide the cursor; same as :sleep)
:smile
:tearoff
diff --git a/runtime/filetype.vim b/runtime/filetype.vim
index ed70ac5ce8..2617d8ffc0 100644
--- a/runtime/filetype.vim
+++ b/runtime/filetype.vim
@@ -389,7 +389,7 @@ au BufNewFile,BufRead *.cfm,*.cfi,*.cfc setf cf
" Configure scripts
au BufNewFile,BufRead configure.in,configure.ac setf config
-" CUDA Cumpute Unified Device Architecture
+" CUDA Compute Unified Device Architecture
au BufNewFile,BufRead *.cu,*.cuh setf cuda
" Dockerfilb; Podman uses the same syntax with name Containerfile
@@ -404,8 +404,15 @@ au BufNewFile,BufRead *enlightenment/*.cfg setf c
" Eterm
au BufNewFile,BufRead *Eterm/*.cfg setf eterm
+" Elixir or Euphoria
+au BufNewFile,BufRead *.ex call dist#ft#ExCheck()
+
+" Elixir
+au BufRead,BufNewFile mix.lock,*.exs setf elixir
+au BufRead,BufNewFile *.eex,*.leex setf eelixir
+
" Euphoria 3 or 4
-au BufNewFile,BufRead *.eu,*.ew,*.ex,*.exu,*.exw call dist#ft#EuphoriaCheck()
+au BufNewFile,BufRead *.eu,*.ew,*.exu,*.exw call dist#ft#EuphoriaCheck()
if has("fname_case")
au BufNewFile,BufRead *.EU,*.EW,*.EX,*.EXU,*.EXW call dist#ft#EuphoriaCheck()
endif
@@ -851,6 +858,12 @@ au BufNewFile,BufRead *.jov,*.j73,*.jovial setf jovial
" JSON
au BufNewFile,BufRead *.json,*.jsonp,*.webmanifest setf json
+" JSON Patch (RFC 6902)
+au BufNewFile,BufRead *.json-patch setf json
+
+" Jupyter Notebook is also json
+au BufNewFile,BufRead *.ipynb setf json
+
" Kixtart
au BufNewFile,BufRead *.kix setf kix
@@ -1492,7 +1505,7 @@ au BufNewFile,BufRead *.sass setf sass
au BufNewFile,BufRead *.sa setf sather
" Scala
-au BufNewFile,BufRead *.scala setf scala
+au BufNewFile,BufRead *.scala,*.sc setf scala
" SBT - Scala Build Tool
au BufNewFile,BufRead *.sbt setf sbt
@@ -2303,6 +2316,7 @@ au BufNewFile,BufRead *.txt
" Use the filetype detect plugins. They may overrule any of the previously
" detected filetypes.
runtime! ftdetect/*.vim
+runtime! ftdetect/*.lua
" NOTE: The above command could have ended the filetypedetect autocmd group
" and started another one. Let's make sure it has ended to get to a consistent
diff --git a/runtime/ftplugin.vim b/runtime/ftplugin.vim
index a434b9372b..feef949dba 100644
--- a/runtime/ftplugin.vim
+++ b/runtime/ftplugin.vim
@@ -28,7 +28,9 @@ augroup filetypeplugin
" When there is a dot it is used to separate filetype names. Thus for
" "aaa.bbb" load "aaa" and then "bbb".
for name in split(s, '\.')
- exe 'runtime! ftplugin/' . name . '.vim ftplugin/' . name . '_*.vim ftplugin/' . name . '/*.vim'
+ exe 'runtime! ftplugin/' . name . '.vim ftplugin/' . name . '_*.vim ftplugin/' . name . '/*.vim'
+ " Load lua ftplugins
+ exe printf('runtime! ftplugin/%s.lua ftplugin/%s_*.lua ftplugin/%s/*.lua', name, name, name)
endfor
endif
endfunc
diff --git a/runtime/indent.vim b/runtime/indent.vim
index 12f03648ca..2b64dd44b9 100644
--- a/runtime/indent.vim
+++ b/runtime/indent.vim
@@ -24,7 +24,8 @@ augroup filetypeindent
" When there is a dot it is used to separate filetype names. Thus for
" "aaa.bbb" load "indent/aaa.vim" and then "indent/bbb.vim".
for name in split(s, '\.')
- exe 'runtime! indent/' . name . '.vim'
+ exe 'runtime! indent/' . name . '.vim'
+ exe 'runtime! indent/' . name . '.lua'
endfor
endif
endfunc
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 5a606188dd..75faf9bcc7 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -20,6 +20,7 @@ local lsp = {
buf = require'vim.lsp.buf';
diagnostic = require'vim.lsp.diagnostic';
+ codelens = require'vim.lsp.codelens';
util = util;
-- Allow raw RPC access.
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index 5dd7109bb0..b13d662ccb 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -156,7 +156,7 @@ function M.formatting(options)
if client == nil then return end
local params = util.make_formatting_params(options)
- return client.request("textDocument/formatting", params)
+ return client.request("textDocument/formatting", params, nil, vim.api.nvim_get_current_buf())
end
--- Performs |vim.lsp.buf.formatting()| synchronously.
@@ -176,7 +176,7 @@ function M.formatting_sync(options, timeout_ms)
if client == nil then return end
local params = util.make_formatting_params(options)
- local result, err = client.request_sync("textDocument/formatting", params, timeout_ms)
+ local result, err = client.request_sync("textDocument/formatting", params, timeout_ms, vim.api.nvim_get_current_buf())
if result and result.result then
util.apply_text_edits(result.result)
elseif err then
@@ -218,7 +218,7 @@ function M.formatting_seq_sync(options, timeout_ms, order)
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)
+ local result, err = client.request_sync("textDocument/formatting", params, timeout_ms, vim.api.nvim_get_current_buf())
if result and result.result then
util.apply_text_edits(result.result)
elseif err then
@@ -297,6 +297,7 @@ local function pick_call_hierarchy_item(call_hierarchy_items)
return choice
end
+--@private
local function call_hierarchy(method)
local params = util.make_position_params()
request('textDocument/prepareCallHierarchy', params, function(err, _, result)
@@ -338,7 +339,7 @@ end
--- Add the folder at path to the workspace folders. If {path} is
--- not provided, the user will be prompted for a path using |input()|.
function M.add_workspace_folder(workspace_folder)
- workspace_folder = workspace_folder or npcall(vfn.input, "Workspace Folder: ", vfn.expand('%:p:h'))
+ workspace_folder = workspace_folder or npcall(vfn.input, "Workspace Folder: ", vfn.expand('%:p:h'), 'dir')
vim.api.nvim_command("redraw")
if not (workspace_folder and #workspace_folder > 0) then return end
if vim.fn.isdirectory(workspace_folder) == 0 then
diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua
new file mode 100644
index 0000000000..fbd37e3830
--- /dev/null
+++ b/runtime/lua/vim/lsp/codelens.lua
@@ -0,0 +1,231 @@
+local util = require('vim.lsp.util')
+local api = vim.api
+local M = {}
+
+--- bufnr → true|nil
+--- to throttle refreshes to at most one at a time
+local active_refreshes = {}
+
+--- bufnr -> client_id -> lenses
+local lens_cache_by_buf = setmetatable({}, {
+ __index = function(t, b)
+ local key = b > 0 and b or api.nvim_get_current_buf()
+ return rawget(t, key)
+ end
+})
+
+local namespaces = setmetatable({}, {
+ __index = function(t, key)
+ local value = api.nvim_create_namespace('vim_lsp_codelens:' .. key)
+ rawset(t, key, value)
+ return value
+ end;
+})
+
+--@private
+M.__namespaces = namespaces
+
+
+--@private
+local function execute_lens(lens, bufnr, client_id)
+ local line = lens.range.start.line
+ api.nvim_buf_clear_namespace(bufnr, namespaces[client_id], line, line + 1)
+
+ -- Need to use the client that returned the lens → must not use buf_request
+ local client = vim.lsp.get_client_by_id(client_id)
+ assert(client, 'Client is required to execute lens, client_id=' .. client_id)
+ client.request('workspace/executeCommand', lens.command, function(...)
+ local result = vim.lsp.handlers['workspace/executeCommand'](...)
+ M.refresh()
+ return result
+ end, bufnr)
+end
+
+
+--- Return all lenses for the given buffer
+---
+---@return table (`CodeLens[]`)
+function M.get(bufnr)
+ local lenses_by_client = lens_cache_by_buf[bufnr]
+ if not lenses_by_client then return {} end
+ local lenses = {}
+ for _, client_lenses in pairs(lenses_by_client) do
+ vim.list_extend(lenses, client_lenses)
+ end
+ return lenses
+end
+
+
+--- Run the code lens in the current line
+---
+function M.run()
+ local line = api.nvim_win_get_cursor(0)[1]
+ local bufnr = api.nvim_get_current_buf()
+ local options = {}
+ local lenses_by_client = lens_cache_by_buf[bufnr] or {}
+ for client, lenses in pairs(lenses_by_client) do
+ for _, lens in pairs(lenses) do
+ if lens.range.start.line == (line - 1) then
+ table.insert(options, {client=client, lens=lens})
+ end
+ end
+ end
+ if #options == 0 then
+ vim.notify('No executable codelens found at current line')
+ elseif #options == 1 then
+ local option = options[1]
+ execute_lens(option.lens, bufnr, option.client)
+ else
+ local options_strings = {"Code lenses:"}
+ for i, option in ipairs(options) do
+ table.insert(options_strings, string.format('%d. %s', i, option.lens.command.title))
+ end
+ local choice = vim.fn.inputlist(options_strings)
+ if choice < 1 or choice > #options then
+ return
+ end
+ local option = options[choice]
+ execute_lens(option.lens, bufnr, option.client)
+ end
+end
+
+
+--- Display the lenses using virtual text
+---
+---@param lenses table of lenses to display (`CodeLens[] | null`)
+---@param bufnr number
+---@param client_id number
+function M.display(lenses, bufnr, client_id)
+ if not lenses or not next(lenses) then
+ return
+ end
+ local lenses_by_lnum = {}
+ for _, lens in pairs(lenses) do
+ local line_lenses = lenses_by_lnum[lens.range.start.line]
+ if not line_lenses then
+ line_lenses = {}
+ lenses_by_lnum[lens.range.start.line] = line_lenses
+ end
+ table.insert(line_lenses, lens)
+ end
+ local ns = namespaces[client_id]
+ local num_lines = api.nvim_buf_line_count(bufnr)
+ for i = 0, num_lines do
+ local line_lenses = lenses_by_lnum[i]
+ api.nvim_buf_clear_namespace(bufnr, ns, i, i + 1)
+ local chunks = {}
+ for _, lens in pairs(line_lenses or {}) do
+ local text = lens.command and lens.command.title or 'Unresolved lens ...'
+ table.insert(chunks, {text, 'LspCodeLens' })
+ end
+ if #chunks > 0 then
+ api.nvim_buf_set_virtual_text(bufnr, ns, i, chunks, {})
+ end
+ end
+end
+
+
+--- Store lenses for a specific buffer and client
+---
+---@param lenses table of lenses to store (`CodeLens[] | null`)
+---@param bufnr number
+---@param client_id number
+function M.save(lenses, bufnr, client_id)
+ local lenses_by_client = lens_cache_by_buf[bufnr]
+ if not lenses_by_client then
+ lenses_by_client = {}
+ lens_cache_by_buf[bufnr] = lenses_by_client
+ local ns = namespaces[client_id]
+ api.nvim_buf_attach(bufnr, false, {
+ on_detach = function(b) lens_cache_by_buf[b] = nil end,
+ on_lines = function(_, b, _, first_lnum, last_lnum)
+ api.nvim_buf_clear_namespace(b, ns, first_lnum, last_lnum)
+ end
+ })
+ end
+ lenses_by_client[client_id] = lenses
+end
+
+
+--@private
+local function resolve_lenses(lenses, bufnr, client_id, callback)
+ lenses = lenses or {}
+ local num_lens = vim.tbl_count(lenses)
+ if num_lens == 0 then
+ callback()
+ return
+ end
+
+ --@private
+ local function countdown()
+ num_lens = num_lens - 1
+ if num_lens == 0 then
+ callback()
+ end
+ end
+ local ns = namespaces[client_id]
+ local client = vim.lsp.get_client_by_id(client_id)
+ for _, lens in pairs(lenses or {}) do
+ if lens.command then
+ countdown()
+ else
+ client.request('codeLens/resolve', lens, function(_, _, result)
+ if result and result.command then
+ lens.command = result.command
+ -- Eager display to have some sort of incremental feedback
+ -- Once all lenses got resolved there will be a full redraw for all lenses
+ -- So that multiple lens per line are properly displayed
+ api.nvim_buf_set_virtual_text(
+ bufnr,
+ ns,
+ lens.range.start.line,
+ {{ lens.command.title, 'LspCodeLens' },},
+ {}
+ )
+ end
+ countdown()
+ end, bufnr)
+ end
+ end
+end
+
+
+--- |lsp-handler| for the method `textDocument/codeLens`
+---
+function M.on_codelens(err, _, result, client_id, bufnr)
+ assert(not err, vim.inspect(err))
+
+ M.save(result, bufnr, client_id)
+
+ -- Eager display for any resolved (and unresolved) lenses and refresh them
+ -- once resolved.
+ M.display(result, bufnr, client_id)
+ resolve_lenses(result, bufnr, client_id, function()
+ M.display(result, bufnr, client_id)
+ active_refreshes[bufnr] = nil
+ end)
+end
+
+
+--- Refresh the codelens for the current buffer
+---
+--- It is recommended to trigger this using an autocmd or via keymap.
+---
+--- <pre>
+--- autocmd BufEnter,CursorHold,InsertLeave <buffer> lua vim.lsp.codelens.refresh()
+--- </pre>
+---
+function M.refresh()
+ local params = {
+ textDocument = util.make_text_document_params()
+ }
+ local bufnr = api.nvim_get_current_buf()
+ if active_refreshes[bufnr] then
+ return
+ end
+ active_refreshes[bufnr] = true
+ vim.lsp.buf_request(0, 'textDocument/codeLens', params)
+end
+
+
+return M
diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua
index dabe400e0d..64dde78f17 100644
--- a/runtime/lua/vim/lsp/diagnostic.lua
+++ b/runtime/lua/vim/lsp/diagnostic.lua
@@ -271,8 +271,12 @@ local function set_diagnostic_cache(diagnostics, bufnr, client_id)
end
-- Account for servers that place diagnostics on terminating newline
if buf_line_count > 0 then
- local start = diagnostic.range.start
- start.line = math.min(start.line, buf_line_count - 1)
+ diagnostic.range.start.line = math.max(math.min(
+ diagnostic.range.start.line, buf_line_count - 1
+ ), 0)
+ diagnostic.range["end"].line = math.max(math.min(
+ diagnostic.range["end"].line, buf_line_count - 1
+ ), 0)
end
end
@@ -317,9 +321,9 @@ function M.save(diagnostics, bufnr, client_id)
-- Clean up our data when the buffer unloads.
api.nvim_buf_attach(bufnr, false, {
- on_detach = function(b)
+ on_detach = function(_, b)
clear_diagnostic_cache(b, client_id)
- _diagnostic_cleanup[bufnr][client_id] = nil
+ _diagnostic_cleanup[b][client_id] = nil
end
})
end
@@ -1119,6 +1123,8 @@ end
--- </pre>
---@param opts table Configuration table
--- - show_header (boolean, default true): Show "Diagnostics:" header.
+--- - Plus all the opts for |vim.lsp.diagnostic.get_line_diagnostics()|
+--- and |vim.lsp.util.open_floating_preview()| can be used here.
---@param bufnr number The buffer number
---@param line_nr number The line number
---@param client_id number|nil the client id
@@ -1208,9 +1214,9 @@ function M.set_loclist(opts)
if severity then
return d.severity == severity
end
- severity = to_severity(opts.severity_limit)
- if severity then
- return d.severity == severity
+ local severity_limit = to_severity(opts.severity_limit)
+ if severity_limit then
+ return d.severity <= severity_limit
end
return true
end
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index 6ae54ea253..41852b9d88 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -187,12 +187,15 @@ M['textDocument/publishDiagnostics'] = function(...)
return require('vim.lsp.diagnostic').on_publish_diagnostics(...)
end
+M['textDocument/codeLens'] = function(...)
+ return require('vim.lsp.codelens').on_codelens(...)
+end
+
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
M['textDocument/references'] = function(_, _, result)
if not result then return end
util.set_qflist(util.locations_to_items(result))
api.nvim_command("copen")
- api.nvim_command("wincmd p")
end
--@private
@@ -207,7 +210,6 @@ local symbol_handler = function(_, _, result, _, bufnr)
util.set_qflist(util.symbols_to_items(result, bufnr))
api.nvim_command("copen")
- api.nvim_command("wincmd p")
end
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol
M['textDocument/documentSymbol'] = symbol_handler
@@ -298,7 +300,6 @@ local function location_handler(_, method, result)
if #result > 1 then
util.set_qflist(util.locations_to_items(result))
api.nvim_command("copen")
- api.nvim_command("wincmd p")
end
else
util.jump_to_location(result)
@@ -379,7 +380,6 @@ local make_call_hierarchy_handler = function(direction)
end
util.set_qflist(items)
api.nvim_command("copen")
- api.nvim_command("wincmd p")
end
end
diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua
index 98835d6708..4c5f02af9d 100644
--- a/runtime/lua/vim/lsp/rpc.lua
+++ b/runtime/lua/vim/lsp/rpc.lua
@@ -50,8 +50,13 @@ recursive_convert_NIL = function(v, tbl_processed)
return nil
elseif not tbl_processed[v] and type(v) == 'table' then
tbl_processed[v] = true
+ local inside_list = vim.tbl_islist(v)
return vim.tbl_map(function(x)
- return recursive_convert_NIL(x, tbl_processed)
+ if not inside_list or (inside_list and type(x) == "table") then
+ return recursive_convert_NIL(x, tbl_processed)
+ else
+ return x
+ end
end, v)
end
@@ -444,7 +449,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
method = method;
params = params;
}
- if result then
+ if result and message_callbacks then
message_callbacks[message_id] = schedule_wrap(callback)
return result, message_id
else
@@ -543,14 +548,14 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
-- - The server will not send a result callback after this cancellation.
-- - If the server sent this cancellation ACK after sending the result, the user of this RPC
-- client will ignore the result themselves.
- if result_id then
+ if result_id and message_callbacks then
message_callbacks[result_id] = nil
end
return
end
end
- local callback = message_callbacks[result_id]
+ local callback = message_callbacks and message_callbacks[result_id]
if callback then
message_callbacks[result_id] = nil
validate {
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index cb9a7cbed5..195e3a0e65 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -50,7 +50,7 @@ local function get_border_size(opts)
local width = 0
if type(border) == 'string' then
- local border_size = {none = {0, 0}, single = {2, 2}, double = {2, 2}, shadow = {1, 1}}
+ local border_size = {none = {0, 0}, single = {2, 2}, double = {2, 2}, rounded = {2, 2}, solid = {2, 2}, shadow = {1, 1}}
if border_size[border] == nil then
error("floating preview border is not correct. Please refer to the docs |vim.api.nvim_open_win()|"
.. vim.inspect(border))
@@ -806,14 +806,20 @@ function M.convert_input_to_markdown_lines(input, contents)
assert(type(input) == 'table', "Expected a table for Hover.contents")
-- MarkupContent
if input.kind then
- -- The kind can be either plaintext or markdown. However, either way we
- -- will just be rendering markdown, so we handle them both the same way.
- -- TODO these can have escaped/sanitized html codes in markdown. We
- -- should make sure we handle this correctly.
+ -- The kind can be either plaintext or markdown.
+ -- If it's plaintext, then wrap it in a <text></text> block
-- Some servers send input.value as empty, so let's ignore this :(
+ input.value = input.value or ''
+
+ if input.kind == "plaintext" then
+ -- wrap this in a <text></text> block so that stylize_markdown
+ -- can properly process it as plaintext
+ input.value = string.format("<text>\n%s\n</text>", input.value or "")
+ end
+
-- assert(type(input.value) == 'string')
- list_extend(contents, split_lines(input.value or ''))
+ list_extend(contents, split_lines(input.value))
-- MarkupString variation 2
elseif input.language then
-- Some servers send input.value as empty, so let's ignore this :(
@@ -861,7 +867,7 @@ function M.convert_signature_help_to_markdown_lines(signature_help, ft)
end
local label = signature.label
if ft then
- -- wrap inside a code block so fancy_markdown can render it properly
+ -- wrap inside a code block so stylize_markdown can render it properly
label = ("```%s\n%s\n```"):format(ft, label)
end
vim.list_extend(contents, vim.split(label, '\n', true))
@@ -1005,7 +1011,7 @@ function M.preview_location(location, opts)
local syntax = api.nvim_buf_get_option(bufnr, 'syntax')
if syntax == "" then
-- When no syntax is set, we use filetype as fallback. This might not result
- -- in a valid syntax definition. See also ft detection in fancy_floating_win.
+ -- in a valid syntax definition. See also ft detection in stylize_markdown.
-- An empty syntax is more common now with TreeSitter, since TS disables syntax.
syntax = api.nvim_buf_get_option(bufnr, 'filetype')
end
@@ -1023,53 +1029,6 @@ local function find_window_by_var(name, value)
end
end
---- Enters/leaves the focusable window associated with the current buffer via the
---window - variable `unique_name`. If no such window exists, run the function
---{fn}.
----
---@param unique_name (string) Window variable
---@param fn (function) should return create a new window and return a tuple of
----({focusable_buffer_id}, {window_id}). if {focusable_buffer_id} is a valid
----buffer id, the newly created window will be the new focus associated with
----the current buffer via the tag `unique_name`.
---@returns (pbufnr, pwinnr) if `fn()` has created a new window; nil otherwise
----@deprecated please use open_floating_preview directly
-function M.focusable_float(unique_name, fn)
- vim.notify("focusable_float is deprecated. Please use open_floating_preview and pass focus_id = [unique_name] instead", vim.log.levels.WARN)
- -- Go back to previous window if we are in a focusable one
- if npcall(api.nvim_win_get_var, 0, unique_name) then
- return api.nvim_command("wincmd p")
- end
- local bufnr = api.nvim_get_current_buf()
- do
- local win = find_window_by_var(unique_name, bufnr)
- if win and api.nvim_win_is_valid(win) and vim.fn.pumvisible() == 0 then
- api.nvim_set_current_win(win)
- api.nvim_command("stopinsert")
- return
- end
- end
- local pbufnr, pwinnr = fn()
- if pbufnr then
- api.nvim_win_set_var(pwinnr, unique_name, bufnr)
- return pbufnr, pwinnr
- end
-end
-
---- Focuses/unfocuses the floating preview window associated with the current
---- buffer via the window variable `unique_name`. If no such preview window
---- exists, makes a new one.
----
---@param unique_name (string) Window variable
---@param fn (function) The return values of this function will be passed
----directly to |vim.lsp.util.open_floating_preview()|, in the case that a new
----floating window should be created
----@deprecated please use open_floating_preview directly
-function M.focusable_preview(unique_name, fn)
- vim.notify("focusable_preview is deprecated. Please use open_floating_preview and pass focus_id = [unique_name] instead", vim.log.levels.WARN)
- return M.open_floating_preview(fn(), {focus_id = unique_name})
-end
-
--- Trims empty lines from input and pad top and bottom with empty lines
---
---@param contents table of lines to trim and pad
@@ -1097,12 +1056,19 @@ function M._trim(contents, opts)
return contents
end
-
-
---- @deprecated please use open_floating_preview directly
-function M.fancy_floating_markdown(contents, opts)
- vim.notify("fancy_floating_markdown is deprecated. Please use open_floating_preview and pass focus_id = [unique_name] instead", vim.log.levels.WARN)
- return M.open_floating_preview(contents, "markdown", opts)
+-- Generates a table mapping markdown code block lang to vim syntax,
+-- based on g:markdown_fenced_languages
+-- @return a table of lang -> syntax mappings
+-- @private
+local function get_markdown_fences()
+ local fences = {}
+ for _, fence in pairs(vim.g.markdown_fenced_languages or {}) do
+ local lang, syntax = fence:match("^(.*)=(.*)$")
+ if lang then
+ fences[lang] = syntax
+ end
+ end
+ return fences
end
--- Converts markdown into syntax highlighted regions by stripping the code
@@ -1134,26 +1100,50 @@ function M.stylize_markdown(bufnr, contents, opts)
}
opts = opts or {}
+ -- table of fence types to {ft, begin, end}
+ -- when ft is nil, we get the ft from the regex match
+ local matchers = {
+ block = {nil, "```+([a-zA-Z0-9_]*)", "```+"},
+ pre = {"", "<pre>", "</pre>"},
+ code = {"", "<code>", "</code>"},
+ text = {"plaintex", "<text>", "</text>"},
+ }
+
+ local match_begin = function(line)
+ for type, pattern in pairs(matchers) do
+ local ret = line:match(string.format("^%%s*%s%%s*$", pattern[2]))
+ if ret then
+ return {
+ type = type,
+ ft = pattern[1] or ret
+ }
+ end
+ end
+ end
+
+ local match_end = function(line, match)
+ local pattern = matchers[match.type]
+ return line:match(string.format("^%%s*%s%%s*$", pattern[3]))
+ end
+
+ -- Clean up
+ contents = M._trim(contents, opts)
+
local stripped = {}
local highlights = {}
+ -- keep track of lnums that contain markdown
+ local markdown_lines = {}
do
local i = 1
while i <= #contents do
local line = contents[i]
- -- TODO(ashkan): use a more strict regex for filetype?
- local ft = line:match("^```([a-zA-Z0-9_]*)$")
- -- local ft = line:match("^```(.*)$")
- -- TODO(ashkan): validate the filetype here.
- local is_pre = line:match("^%s*<pre>%s*$")
- if is_pre then
- ft = ""
- end
- if ft then
+ local match = match_begin(line)
+ if match then
local start = #stripped
i = i + 1
while i <= #contents do
line = contents[i]
- if line == "```" or (is_pre and line:match("^%s*</pre>%s*$")) then
+ if match_end(line, match) then
i = i + 1
break
end
@@ -1161,23 +1151,30 @@ function M.stylize_markdown(bufnr, contents, opts)
i = i + 1
end
table.insert(highlights, {
- ft = ft;
+ ft = match.ft;
start = start + 1;
- finish = #stripped + 1 - 1;
+ finish = #stripped;
})
else
table.insert(stripped, line)
+ markdown_lines[#stripped] = true
i = i + 1
end
end
end
- -- Clean up
- stripped = M._trim(stripped, opts)
-- Compute size of float needed to show (wrapped) lines
opts.wrap_at = opts.wrap_at or (vim.wo["wrap"] and api.nvim_win_get_width(0))
local width, height = M._make_floating_popup_size(stripped, opts)
+ local sep_line = string.rep("─", math.min(width, opts.wrap_at or width))
+
+ for l in pairs(markdown_lines) do
+ if stripped[l]:match("^---+$") then
+ stripped[l] = sep_line
+ end
+ end
+
-- Insert blank line separator after code block
local insert_separator = opts.separator
if insert_separator == nil then insert_separator = true end
@@ -1188,10 +1185,8 @@ function M.stylize_markdown(bufnr, contents, opts)
h.finish = h.finish + offset
-- check if a seperator already exists and use that one instead of creating a new one
if h.finish + 1 <= #stripped then
- if stripped[h.finish + 1]:match("^---+$") then
- stripped[h.finish + 1] = string.rep("─", math.min(width, opts.wrap_at or width))
- else
- table.insert(stripped, h.finish + 1, string.rep("─", math.min(width, opts.wrap_at or width)))
+ if stripped[h.finish + 1] ~= sep_line then
+ table.insert(stripped, h.finish + 1, sep_line)
offset = offset + 1
height = height + 1
end
@@ -1199,6 +1194,7 @@ function M.stylize_markdown(bufnr, contents, opts)
end
end
+
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, stripped)
local idx = 1
@@ -1206,11 +1202,13 @@ function M.stylize_markdown(bufnr, contents, opts)
-- keep track of syntaxes we already inlcuded.
-- no need to include the same syntax more than once
local langs = {}
+ local fences = get_markdown_fences()
local function apply_syntax_to_region(ft, start, finish)
if ft == "" then
vim.cmd(string.format("syntax region markdownCode start=+\\%%%dl+ end=+\\%%%dl+ keepend extend", start, finish + 1))
return
end
+ ft = fences[ft] or ft
local name = ft..idx
idx = idx + 1
local lang = "@"..ft:upper()
@@ -1239,7 +1237,7 @@ function M.stylize_markdown(bufnr, contents, opts)
apply_syntax_to_region(h.ft, h.start, h.finish)
last = h.finish + 1
end
- if last < #stripped then
+ if last <= #stripped then
apply_syntax_to_region("lsp_markdown", last, #stripped)
end
end)
diff --git a/runtime/spell/en.utf-8.spl b/runtime/spell/en.utf-8.spl
index 83b9b8f7c2..e4b1e1cce7 100644
--- a/runtime/spell/en.utf-8.spl
+++ b/runtime/spell/en.utf-8.spl
Binary files differ
diff --git a/runtime/syntax/lsp_markdown.vim b/runtime/syntax/lsp_markdown.vim
index d9b50be54c..90d3185673 100644
--- a/runtime/syntax/lsp_markdown.vim
+++ b/runtime/syntax/lsp_markdown.vim
@@ -10,8 +10,14 @@ execute 'source' expand('<sfile>:p:h') .. '/markdown.vim'
syn cluster mkdNonListItem add=mkdEscape,mkdNbsp
-syntax region mkdEscape matchgroup=mkdEscape start=/\\\ze[\\\x60*{}\[\]()#+\-,.!_>~|"$%&'\/:;<=?@^ ]/ end=/.\zs/ keepend contains=mkdEscapeCh oneline concealends
-syntax match mkdEscapeCh /./ contained
+syn clear markdownEscape
+syntax region markdownEscape matchgroup=markdownEscape start=/\\\ze[\\\x60*{}\[\]()#+\-,.!_>~|"$%&'\/:;<=?@^ ]/ end=/./ containedin=ALL keepend oneline concealends
+
+" conceal html entities
syntax match mkdNbsp /&nbsp;/ conceal cchar=
+syntax match mkdLt /&lt;/ conceal cchar=<
+syntax match mkdGt /&gt;/ conceal cchar=>
+syntax match mkdAmp /&amp;/ conceal cchar=&
+syntax match mkdQuot /&quot;/ conceal cchar="
hi def link mkdEscape special
diff --git a/runtime/syntax/synload.vim b/runtime/syntax/synload.vim
index f373161c7c..3863a84c1a 100644
--- a/runtime/syntax/synload.vim
+++ b/runtime/syntax/synload.vim
@@ -55,7 +55,8 @@ fun! s:SynSet()
" load each in sequence. Skip empty entries.
for name in split(s, '\.')
if !empty(name)
- exe "runtime! syntax/" . name . ".vim syntax/" . name . "/*.vim"
+ exe "runtime! syntax/" . name . ".vim syntax/" . name . "/*.vim"
+ exe "runtime! syntax/" . name . ".lua syntax/" . name . "/*.lua"
endif
endfor
endif
diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py
index d46306d41a..18a2839702 100755
--- a/scripts/gen_vimdoc.py
+++ b/scripts/gen_vimdoc.py
@@ -154,6 +154,7 @@ CONFIG = {
'lsp.lua',
'buf.lua',
'diagnostic.lua',
+ 'codelens.lua',
'handlers.lua',
'util.lua',
'log.lua',
diff --git a/scripts/release.sh b/scripts/release.sh
index 5b4902a2d7..4d1484b77a 100755
--- a/scripts/release.sh
+++ b/scripts/release.sh
@@ -64,10 +64,9 @@ _do_release_commit() {
if ! test "$ARG1" = '--use-current-commit' ; then
echo "Building changelog since ${__LAST_TAG}..."
- __CHANGELOG="$(./scripts/git-log-pretty-since.sh "$__LAST_TAG" 'vim-patch:[^[:space:]]')"
git add CMakeLists.txt
- git commit --edit -m "${__RELEASE_MSG} ${__CHANGELOG}"
+ (echo "${__RELEASE_MSG}"; ./scripts/git-log-pretty-since.sh "$__LAST_TAG" 'vim-patch:[^[:space:]]') | git commit --edit -F -
fi
git tag --sign -a v"${__VERSION}" -m "NVIM v${__VERSION}"
diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh
index 86552c0c8d..d53de42c6c 100755
--- a/scripts/vim-patch.sh
+++ b/scripts/vim-patch.sh
@@ -372,7 +372,7 @@ submit_pr() {
pr_title="${pr_title// /,}" # Replace spaces with commas.
local pr_message
- pr_message="$(printf '[RFC] vim-patch:%s\n\n%s\n' "${pr_title#,}" "${pr_body}")"
+ pr_message="$(printf 'vim-patch:%s\n\n%s\n' "${pr_title#,}" "${pr_body}")"
if [[ $push_first -ne 0 ]]; then
echo "Pushing to 'origin/${checked_out_branch}'."
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index f84e8c99a4..b371c08d2a 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -1489,21 +1489,6 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id,
return 0;
}
- size_t len = 0;
- if (line < 0 || line > buf->b_ml.ml_line_count) {
- api_set_error(err, kErrorTypeValidation, "line value outside range");
- return 0;
- } else if (line < buf->b_ml.ml_line_count) {
- len = STRLEN(ml_get_buf(buf, (linenr_T)line+1, false));
- }
-
- if (col == -1) {
- col = (Integer)len;
- } else if (col < -1 || col > (Integer)len) {
- api_set_error(err, kErrorTypeValidation, "col value outside range");
- return 0;
- }
-
bool ephemeral = false;
uint64_t id = 0;
@@ -1674,6 +1659,22 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id,
}
}
+ size_t len = 0;
+ if (line < 0 || line > buf->b_ml.ml_line_count) {
+ api_set_error(err, kErrorTypeValidation, "line value outside range");
+ return 0;
+ } else if (line < buf->b_ml.ml_line_count) {
+ len = ephemeral ? MAXCOL : STRLEN(ml_get_buf(buf, (linenr_T)line+1, false));
+ }
+
+ if (col == -1) {
+ col = (Integer)len;
+ } else if (col < -1 || col > (Integer)len) {
+ api_set_error(err, kErrorTypeValidation, "col value outside range");
+ return 0;
+ }
+
+
// Only error out if they try to set end_right_gravity without
// setting end_col or end_line
if (line2 == -1 && col2 == -1 && end_gravity_set) {
@@ -1684,7 +1685,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id,
if (col2 >= 0) {
if (line2 >= 0 && line2 < buf->b_ml.ml_line_count) {
- len = STRLEN(ml_get_buf(buf, (linenr_T)line2 + 1, false));
+ len = ephemeral ? MAXCOL : STRLEN(
+ ml_get_buf(buf, (linenr_T)line2 + 1, false));
} else if (line2 == buf->b_ml.ml_line_count) {
// We are trying to add an extmark past final newline
len = 0;
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 4b1c2d4baa..c7d261ba18 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1770,6 +1770,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
{ "double", { "╔", "═", "╗", "║", "╝", "═", "╚", "║" }, false },
{ "single", { "┌", "─", "┐", "│", "┘", "─", "└", "│" }, false },
{ "shadow", { "", "", " ", " ", " ", " ", " ", "" }, true },
+ { "rounded", { "╭", "─", "╮", "│", "╯", "─", "╰", "│" }, false },
{ "solid", { " ", " ", " ", " ", " ", " ", " ", " " }, false },
{ NULL, { { NUL } } , false },
};
diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h
index 11e21a88ea..35d39a34d7 100644
--- a/src/nvim/api/ui_events.in.h
+++ b/src/nvim/api/ui_events.in.h
@@ -130,7 +130,8 @@ void popupmenu_hide(void)
void popupmenu_select(Integer selected)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
-void tabline_update(Tabpage current, Array tabs)
+void tabline_update(Tabpage current, Array tabs,
+ Buffer current_buffer, Array buffers)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void cmdline_show(Array content, Integer pos, String firstc, String prompt,
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 60535b13b3..349cc0e7da 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -1437,28 +1437,30 @@ void nvim_chan_send(Integer chan, String data, Error *err)
/// end-of-buffer region is hidden by setting `eob` flag of
/// 'fillchars' to a space char, and clearing the
/// |EndOfBuffer| region in 'winhighlight'.
-/// - `border`: style of (optional) window border. This can either be a string
-/// or an array. the string values are:
-/// - "none" No border. This is the default
-/// - "single" a single line box
-/// - "double" a double line box
-/// - "shadow" a drop shadow effect by blending with the background.
-/// If it is an array it should be an array of eight items or any divisor of
+/// - `border`: Style of (optional) window border. This can either be a string
+/// or an array. The string values are
+/// - "none": No border (default).
+/// - "single": A single line box.
+/// - "double": A double line box.
+/// - "rounded": Like "single", but with rounded corners ("╭" etc.).
+/// - "solid": Adds padding by a single whitespace cell.
+/// - "shadow": A drop shadow effect by blending with the background.
+/// - If it is an array, it should have a length of eight or any divisor of
/// eight. The array will specifify the eight chars building up the border
-/// in a clockwise fashion starting with the top-left corner. As, an
-/// example, the double box style could be specified as:
-/// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ]
-/// if the number of chars are less than eight, they will be repeated. Thus
-/// an ASCII border could be specified as:
-/// [ "/", "-", "\\", "|" ]
-/// or all chars the same as:
-/// [ "x" ]
-/// An empty string can be used to turn off a specific border, for instance:
+/// in a clockwise fashion starting with the top-left corner. As an
+/// example, the double box style could be specified as
+/// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ].
+/// If the number of chars are less than eight, they will be repeated. Thus
+/// an ASCII border could be specified as
+/// [ "/", "-", "\\", "|" ],
+/// or all chars the same as
+/// [ "x" ].
+/// An empty string can be used to turn off a specific border, for instance,
/// [ "", "", "", ">", "", "", "", "<" ]
/// will only make vertical borders but not horizontal ones.
-/// By default `FloatBorder` highlight is used which links to `VertSplit`
+/// By default, `FloatBorder` highlight is used, which links to `VertSplit`
/// when not defined. It could also be specified by character:
-/// [ {"+", "MyCorner"}, {"x", "MyBorder"} ]
+/// [ {"+", "MyCorner"}, {"x", "MyBorder"} ].
/// - `noautocmd`: If true then no buffer-related autocommand events such as
/// |BufEnter|, |BufLeave| or |BufWinEnter| may fire from
/// calling this function.
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 6a50264e0f..0b72dd1885 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -5665,7 +5665,7 @@ bool buf_contents_changed(buf_T *buf)
void
wipe_buffer(
buf_T *buf,
- int aucmd // When true trigger autocommands.
+ bool aucmd // When true trigger autocommands.
)
{
if (!aucmd) {
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index a3fa9c986f..ff019d1e07 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -2340,10 +2340,8 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,
if (get_var_tv((const char *)lp->ll_name, (int)STRLEN(lp->ll_name),
&tv, &di, true, false) == OK) {
if ((di == NULL
- || (!var_check_ro(di->di_flags, (const char *)lp->ll_name,
- TV_CSTRING)
- && !tv_check_lock(di->di_tv.v_lock, (const char *)lp->ll_name,
- TV_CSTRING)))
+ || (!var_check_ro(di->di_flags, lp->ll_name, TV_CSTRING)
+ && !tv_check_lock(&di->di_tv, lp->ll_name, TV_CSTRING)))
&& eexe_mod_op(&tv, rettv, op) == OK) {
set_var(lp->ll_name, lp->ll_name_len, &tv, false);
}
@@ -2353,10 +2351,10 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,
set_var_const(lp->ll_name, lp->ll_name_len, rettv, copy, is_const);
}
*endp = cc;
- } else if (tv_check_lock(lp->ll_newkey == NULL
- ? lp->ll_tv->v_lock
- : lp->ll_tv->vval.v_dict->dv_lock,
- (const char *)lp->ll_name, TV_CSTRING)) {
+ } else if (var_check_lock(lp->ll_newkey == NULL
+ ? lp->ll_tv->v_lock
+ : lp->ll_tv->vval.v_dict->dv_lock,
+ lp->ll_name, TV_CSTRING)) {
} else if (lp->ll_range) {
listitem_T *ll_li = lp->ll_li;
int ll_n1 = lp->ll_n1;
@@ -2369,9 +2367,8 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,
// Check whether any of the list items is locked
for (ri = tv_list_first(rettv->vval.v_list);
ri != NULL && ll_li != NULL; ) {
- if (tv_check_lock(TV_LIST_ITEM_TV(ll_li)->v_lock,
- (const char *)lp->ll_name,
- TV_CSTRING)) {
+ if (var_check_lock(TV_LIST_ITEM_TV(ll_li)->v_lock, lp->ll_name,
+ TV_CSTRING)) {
return;
}
ri = TV_LIST_ITEM_NEXT(rettv->vval.v_list, ri);
@@ -2795,13 +2792,13 @@ static int do_unlet_var(lval_T *lp, char_u *name_end, exarg_T *eap,
} else if ((lp->ll_list != NULL
// ll_list is not NULL when lvalue is not in a list, NULL lists
// yield E689.
- && tv_check_lock(tv_list_locked(lp->ll_list),
- (const char *)lp->ll_name,
- lp->ll_name_len))
+ && var_check_lock(tv_list_locked(lp->ll_list),
+ lp->ll_name,
+ lp->ll_name_len))
|| (lp->ll_dict != NULL
- && tv_check_lock(lp->ll_dict->dv_lock,
- (const char *)lp->ll_name,
- lp->ll_name_len))) {
+ && var_check_lock(lp->ll_dict->dv_lock,
+ lp->ll_name,
+ lp->ll_name_len))) {
return FAIL;
} else if (lp->ll_range) {
assert(lp->ll_list != NULL);
@@ -2810,9 +2807,9 @@ static int do_unlet_var(lval_T *lp, char_u *name_end, exarg_T *eap,
listitem_T *last_li = first_li;
for (;;) {
listitem_T *const li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li);
- if (tv_check_lock(TV_LIST_ITEM_TV(lp->ll_li)->v_lock,
- (const char *)lp->ll_name,
- lp->ll_name_len)) {
+ if (var_check_lock(TV_LIST_ITEM_TV(lp->ll_li)->v_lock,
+ lp->ll_name,
+ lp->ll_name_len)) {
return false;
}
lp->ll_li = li;
@@ -2897,11 +2894,11 @@ int do_unlet(const char *const name, const size_t name_len, const bool forceit)
dictitem_T *const di = TV_DICT_HI2DI(hi);
if (var_check_fixed(di->di_flags, (const char *)name, TV_CSTRING)
|| var_check_ro(di->di_flags, (const char *)name, TV_CSTRING)
- || tv_check_lock(d->dv_lock, (const char *)name, TV_CSTRING)) {
+ || var_check_lock(d->dv_lock, name, TV_CSTRING)) {
return FAIL;
}
- if (tv_check_lock(d->dv_lock, (const char *)name, TV_CSTRING)) {
+ if (var_check_lock(d->dv_lock, name, TV_CSTRING)) {
return FAIL;
}
@@ -5962,14 +5959,14 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
if (argvars[0].v_type == VAR_LIST) {
tv_copy(&argvars[0], rettv);
if ((l = argvars[0].vval.v_list) == NULL
- || (!map && tv_check_lock(tv_list_locked(l), arg_errmsg,
- TV_TRANSLATE))) {
+ || (!map
+ && var_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE))) {
return;
}
} else if (argvars[0].v_type == VAR_DICT) {
tv_copy(&argvars[0], rettv);
if ((d = argvars[0].vval.v_dict) == NULL
- || (!map && tv_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) {
+ || (!map && var_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) {
return;
}
} else {
@@ -6002,7 +5999,7 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
di = TV_DICT_HI2DI(hi);
if (map
- && (tv_check_lock(di->di_tv.v_lock, arg_errmsg, TV_TRANSLATE)
+ && (var_check_lock(di->di_tv.v_lock, arg_errmsg, TV_TRANSLATE)
|| var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE))) {
break;
}
@@ -6029,8 +6026,8 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
for (listitem_T *li = tv_list_first(l); li != NULL;) {
if (map
- && tv_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg,
- TV_TRANSLATE)) {
+ && var_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg,
+ TV_TRANSLATE)) {
break;
}
vimvars[VV_KEY].vv_nr = idx;
@@ -7200,12 +7197,15 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg)
r = FAIL;
} else if (arg->v_type == VAR_FUNC || arg->v_type == VAR_STRING) {
char_u *name = arg->vval.v_string;
- if (name != NULL) {
+ if (name == NULL) {
+ r = FAIL;
+ } else if (*name == NUL) {
+ callback->type = kCallbackNone;
+ callback->data.funcref = NULL;
+ } else {
func_ref(name);
callback->data.funcref = vim_strsave(name);
callback->type = kCallbackFuncref;
- } else {
- r = FAIL;
}
} else if (nlua_is_table_from_lua(arg)) {
char_u *name = nlua_register_table_as_callable(arg);
@@ -7216,8 +7216,10 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg)
} else {
r = FAIL;
}
- } else if (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0) {
+ } else if (arg->v_type == VAR_SPECIAL
+ || (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0)) {
callback->type = kCallbackNone;
+ callback->data.funcref = NULL;
} else {
r = FAIL;
}
@@ -7324,14 +7326,7 @@ void add_timer_info(typval_T *rettv, timer_T *timer)
return;
}
- if (timer->callback.type == kCallbackPartial) {
- di->di_tv.v_type = VAR_PARTIAL;
- di->di_tv.vval.v_partial = timer->callback.data.partial;
- timer->callback.data.partial->pt_refcount++;
- } else if (timer->callback.type == kCallbackFuncref) {
- di->di_tv.v_type = VAR_FUNC;
- di->di_tv.vval.v_string = vim_strsave(timer->callback.data.funcref);
- }
+ callback_put(&timer->callback, &di->di_tv);
}
void add_timer_info_all(typval_T *rettv)
@@ -8949,7 +8944,7 @@ static void set_var_const(const char *name, const size_t name_len,
// existing variable, need to clear the value
if (var_check_ro(v->di_flags, name, name_len)
- || tv_check_lock(v->di_tv.v_lock, name, name_len)) {
+ || var_check_lock(v->di_tv.v_lock, name, name_len)) {
return;
}
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index ce09d268ea..1ba31bfe68 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -268,7 +268,8 @@ static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = 1; // Default: failed.
if (argvars[0].v_type == VAR_LIST) {
list_T *const l = argvars[0].vval.v_list;
- if (!tv_check_lock(tv_list_locked(l), N_("add() argument"), TV_TRANSLATE)) {
+ if (!var_check_lock(tv_list_locked(l), N_("add() argument"),
+ TV_TRANSLATE)) {
tv_list_append_tv(l, &argvars[1]);
tv_copy(&argvars[0], rettv);
}
@@ -2277,9 +2278,9 @@ static void f_flatten(typval_T *argvars, typval_T *rettv, FunPtr fptr)
list = argvars[0].vval.v_list;
if (list != NULL
- && !tv_check_lock(tv_list_locked(list),
- N_("flatten() argument"),
- TV_TRANSLATE)
+ && !var_check_lock(tv_list_locked(list),
+ N_("flatten() argument"),
+ TV_TRANSLATE)
&& tv_list_flatten(list, maxdepth) == OK) {
tv_copy(&argvars[0], rettv);
}
@@ -2299,7 +2300,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
list_T *const l1 = argvars[0].vval.v_list;
list_T *const l2 = argvars[1].vval.v_list;
- if (!tv_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) {
+ if (!var_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) {
listitem_T *item;
if (argvars[2].v_type != VAR_UNKNOWN) {
before = (long)tv_get_number_chk(&argvars[2], &error);
@@ -2328,13 +2329,13 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
dict_T *const d1 = argvars[0].vval.v_dict;
dict_T *const d2 = argvars[1].vval.v_dict;
if (d1 == NULL) {
- const bool locked = tv_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE);
+ const bool locked = var_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE);
(void)locked;
assert(locked == true);
} else if (d2 == NULL) {
// Do nothing
tv_copy(&argvars[0], rettv);
- } else if (!tv_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) {
+ } else if (!var_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) {
const char *action = "force";
// Check the third argument.
if (argvars[2].v_type != VAR_UNKNOWN) {
@@ -4845,8 +4846,8 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (argvars[0].v_type != VAR_LIST) {
EMSG2(_(e_listarg), "insert()");
- } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
- N_("insert() argument"), TV_TRANSLATE)) {
+ } else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
+ N_("insert() argument"), TV_TRANSLATE)) {
long before = 0;
if (argvars[2].v_type != VAR_UNKNOWN) {
before = tv_get_number_chk(&argvars[2], &error);
@@ -7079,7 +7080,7 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (argvars[2].v_type != VAR_UNKNOWN) {
EMSG2(_(e_toomanyarg), "remove()");
} else if ((d = argvars[0].vval.v_dict) != NULL
- && !tv_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE)) {
+ && !var_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE)) {
const char *key = tv_get_string_chk(&argvars[1]);
if (key != NULL) {
di = tv_dict_find(d, key, -1);
@@ -7098,8 +7099,8 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
} else if (argvars[0].v_type != VAR_LIST) {
EMSG2(_(e_listdictarg), "remove()");
- } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
- arg_errmsg, TV_TRANSLATE)) {
+ } else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
+ arg_errmsg, TV_TRANSLATE)) {
bool error = false;
idx = tv_get_number_chk(&argvars[1], &error);
@@ -7374,8 +7375,8 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
list_T *l;
if (argvars[0].v_type != VAR_LIST) {
EMSG2(_(e_listarg), "reverse()");
- } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
- N_("reverse() argument"), TV_TRANSLATE)) {
+ } else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
+ N_("reverse() argument"), TV_TRANSLATE)) {
tv_list_reverse(l);
tv_list_set_ret(rettv, l);
}
@@ -9462,7 +9463,7 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort)
EMSG2(_(e_listarg), sort ? "sort()" : "uniq()");
} else {
list_T *const l = argvars[0].vval.v_list;
- if (tv_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) {
+ if (var_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) {
goto theend;
}
tv_list_set_ret(rettv, l);
@@ -9661,6 +9662,18 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr)
const char *word = "";
hlf_T attr = HLF_COUNT;
size_t len = 0;
+ const int wo_spell_save = curwin->w_p_spell;
+
+ if (!curwin->w_p_spell) {
+ did_set_spelllang(curwin);
+ curwin->w_p_spell = true;
+ }
+
+ if (*curwin->w_s->b_p_spl == NUL) {
+ EMSG(_(e_no_spell));
+ curwin->w_p_spell = wo_spell_save;
+ return;
+ }
if (argvars[0].v_type == VAR_UNKNOWN) {
// Find the start and length of the badly spelled word.
@@ -9669,7 +9682,7 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr)
word = (char *)get_cursor_pos_ptr();
curwin->w_set_curswant = true;
}
- } else if (curwin->w_p_spell && *curbuf->b_s.b_p_spl != NUL) {
+ } else if (*curbuf->b_s.b_p_spl != NUL) {
const char *str = tv_get_string_chk(&argvars[0]);
int capcol = -1;
@@ -9687,6 +9700,7 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
}
+ curwin->w_p_spell = wo_spell_save;
assert(len <= INT_MAX);
tv_list_alloc_ret(rettv, 2);
@@ -9708,8 +9722,20 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr)
int maxcount;
garray_T ga = GA_EMPTY_INIT_VALUE;
bool need_capital = false;
+ const int wo_spell_save = curwin->w_p_spell;
+
+ if (!curwin->w_p_spell) {
+ did_set_spelllang(curwin);
+ curwin->w_p_spell = true;
+ }
+
+ if (*curwin->w_s->b_p_spl == NUL) {
+ EMSG(_(e_no_spell));
+ curwin->w_p_spell = wo_spell_save;
+ return;
+ }
- if (curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) {
+ if (*curwin->w_s->b_p_spl != NUL) {
const char *const str = tv_get_string(&argvars[0]);
if (argvars[1].v_type != VAR_UNKNOWN) {
maxcount = tv_get_number_chk(&argvars[1], &typeerr);
@@ -9736,6 +9762,7 @@ f_spellsuggest_return:
tv_list_append_allocated_string(rettv->vval.v_list, p);
}
ga_clear(&ga);
+ curwin->w_p_spell = wo_spell_save;
}
static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr)
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index 61de83fc21..7221dc8bc9 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -1162,6 +1162,49 @@ void callback_free(Callback *callback)
}
}
callback->type = kCallbackNone;
+ callback->data.funcref = NULL;
+}
+
+/// Copy a callback into a typval_T.
+void callback_put(Callback *cb, typval_T *tv)
+ FUNC_ATTR_NONNULL_ALL
+{
+ switch (cb->type) {
+ case kCallbackPartial:
+ tv->v_type = VAR_PARTIAL;
+ tv->vval.v_partial = cb->data.partial;
+ cb->data.partial->pt_refcount++;
+ break;
+ case kCallbackFuncref:
+ tv->v_type = VAR_FUNC;
+ tv->vval.v_string = vim_strsave(cb->data.funcref);
+ func_ref(cb->data.funcref);
+ break;
+ default:
+ tv->v_type = VAR_SPECIAL;
+ tv->vval.v_special = kSpecialVarNull;
+ break;
+ }
+}
+
+// Copy callback from "src" to "dest", incrementing the refcounts.
+void callback_copy(Callback *dest, Callback *src)
+ FUNC_ATTR_NONNULL_ALL
+{
+ dest->type = src->type;
+ switch (src->type) {
+ case kCallbackPartial:
+ dest->data.partial = src->data.partial;
+ dest->data.partial->pt_refcount++;
+ break;
+ case kCallbackFuncref:
+ dest->data.funcref = vim_strsave(src->data.funcref);
+ func_ref(src->data.funcref);
+ break;
+ default:
+ dest->data.funcref = NULL;
+ break;
+ }
}
/// Remove watcher from a dictionary
@@ -1951,7 +1994,7 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2,
} else if (*action == 'f' && di2 != di1) {
typval_T oldtv;
- if (tv_check_lock(di1->di_tv.v_lock, arg_errmsg, arg_errmsg_len)
+ if (var_check_lock(di1->di_tv.v_lock, arg_errmsg, arg_errmsg_len)
|| var_check_ro(di1->di_flags, arg_errmsg, arg_errmsg_len)) {
break;
}
@@ -2582,7 +2625,7 @@ bool tv_islocked(const typval_T *const tv)
///
/// Also gives an error message when typval is locked.
///
-/// @param[in] lock Lock status.
+/// @param[in] tv Typval.
/// @param[in] name Variable name, used in the error message.
/// @param[in] name_len Variable name length. Use #TV_TRANSLATE to translate
/// variable name and compute the length. Use #TV_CSTRING
@@ -2596,10 +2639,37 @@ bool tv_islocked(const typval_T *const tv)
/// gettext.
///
/// @return true if variable is locked, false otherwise.
-bool tv_check_lock(const VarLockStatus lock, const char *name,
+bool tv_check_lock(const typval_T *tv, const char *name,
size_t name_len)
FUNC_ATTR_WARN_UNUSED_RESULT
{
+ VarLockStatus lock = VAR_UNLOCKED;
+
+ switch (tv->v_type) {
+ // case VAR_BLOB:
+ // if (tv->vval.v_blob != NULL)
+ // lock = tv->vval.v_blob->bv_lock;
+ // break;
+ case VAR_LIST:
+ if (tv->vval.v_list != NULL) {
+ lock = tv->vval.v_list->lv_lock;
+ }
+ break;
+ case VAR_DICT:
+ if (tv->vval.v_dict != NULL) {
+ lock = tv->vval.v_dict->dv_lock;
+ }
+ break;
+ default:
+ break;
+ }
+ return var_check_lock(tv->v_lock, name, name_len)
+ || (lock != VAR_UNLOCKED && var_check_lock(lock, name, name_len));
+}
+
+/// @return true if variable "name" is locked (immutable)
+bool var_check_lock(VarLockStatus lock, const char *name, size_t name_len)
+{
const char *error_message = NULL;
switch (lock) {
case VAR_UNLOCKED: {
diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h
index 2b4612016b..050b84efec 100644
--- a/src/nvim/eval/typval.h
+++ b/src/nvim/eval/typval.h
@@ -120,7 +120,7 @@ typedef enum {
VAR_DICT, ///< Dictionary, .v_dict is used.
VAR_FLOAT, ///< Floating-point value, .v_float is used.
VAR_BOOL, ///< true, false
- VAR_SPECIAL, ///< Special value (true, false, null), .v_special
+ VAR_SPECIAL, ///< Special value (null), .v_special
///< is used.
VAR_PARTIAL, ///< Partial, .v_partial is used.
} VarType;
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index f5d1b1e870..5ffc06ec44 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -2455,13 +2455,13 @@ void ex_function(exarg_T *eap)
goto erret;
}
if (fudi.fd_di == NULL) {
- if (tv_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg,
- TV_CSTRING)) {
+ if (var_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg,
+ TV_CSTRING)) {
// Can't add a function to a locked dictionary
goto erret;
}
- } else if (tv_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg,
- TV_CSTRING)) {
+ } else if (var_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg,
+ TV_CSTRING)) {
// Can't change an existing function if it is locked
goto erret;
}
diff --git a/src/nvim/event/multiqueue.c b/src/nvim/event/multiqueue.c
index 1e6d62135c..f534fc483f 100644
--- a/src/nvim/event/multiqueue.c
+++ b/src/nvim/event/multiqueue.c
@@ -73,7 +73,7 @@ struct multiqueue_item {
struct multiqueue {
MultiQueue *parent;
QUEUE headtail; // circularly-linked
- put_callback put_cb;
+ PutCallback put_cb;
void *data;
size_t size;
};
@@ -91,7 +91,7 @@ typedef struct {
static Event NILEVENT = { .handler = NULL, .argv = {NULL} };
-MultiQueue *multiqueue_new_parent(put_callback put_cb, void *data)
+MultiQueue *multiqueue_new_parent(PutCallback put_cb, void *data)
{
return multiqueue_new(NULL, put_cb, data);
}
@@ -104,7 +104,7 @@ MultiQueue *multiqueue_new_child(MultiQueue *parent)
return multiqueue_new(parent, NULL, NULL);
}
-static MultiQueue *multiqueue_new(MultiQueue *parent, put_callback put_cb,
+static MultiQueue *multiqueue_new(MultiQueue *parent, PutCallback put_cb,
void *data)
{
MultiQueue *rv = xmalloc(sizeof(MultiQueue));
diff --git a/src/nvim/event/multiqueue.h b/src/nvim/event/multiqueue.h
index a688107665..dc60fbb4c7 100644
--- a/src/nvim/event/multiqueue.h
+++ b/src/nvim/event/multiqueue.h
@@ -7,7 +7,7 @@
#include "nvim/lib/queue.h"
typedef struct multiqueue MultiQueue;
-typedef void (*put_callback)(MultiQueue *multiq, void *data);
+typedef void (*PutCallback)(MultiQueue *multiq, void *data);
#define multiqueue_put(q, h, ...) \
multiqueue_put_event(q, event_create(h, __VA_ARGS__));
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 8e0761f80c..4af7794317 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -3344,6 +3344,15 @@ static char_u *sub_parse_flags(char_u *cmd, subflags_T *subflags,
return cmd;
}
+static int check_regexp_delim(int c)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (isalpha(c)) {
+ EMSG(_("E146: Regular expressions can't be delimited by letters"));
+ return FAIL;
+ }
+ return OK;
+}
/// Perform a substitution from line eap->line1 to line eap->line2 using the
/// command pointed to by eap->arg which should be of the form:
@@ -3408,16 +3417,14 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
/* new pattern and substitution */
if (eap->cmd[0] == 's' && *cmd != NUL && !ascii_iswhite(*cmd)
&& vim_strchr((char_u *)"0123456789cegriIp|\"", *cmd) == NULL) {
- /* don't accept alphanumeric for separator */
- if (isalpha(*cmd)) {
- EMSG(_("E146: Regular expressions can't be delimited by letters"));
+ // don't accept alphanumeric for separator
+ if (check_regexp_delim(*cmd) == FAIL) {
return NULL;
}
- /*
- * undocumented vi feature:
- * "\/sub/" and "\?sub?" use last used search pattern (almost like
- * //sub/r). "\&sub&" use last substitute pattern (like //sub/).
- */
+
+ // undocumented vi feature:
+ // "\/sub/" and "\?sub?" use last used search pattern (almost like
+ // //sub/r). "\&sub&" use last substitute pattern (like //sub/).
if (*cmd == '\\') {
++cmd;
if (vim_strchr((char_u *)"/?&", *cmd) == NULL) {
@@ -4457,6 +4464,8 @@ void ex_global(exarg_T *eap)
} else if (*cmd == NUL) {
EMSG(_("E148: Regular expression missing from global"));
return;
+ } else if (check_regexp_delim(*cmd) == FAIL) {
+ return;
} else {
delim = *cmd; /* get the delimiter */
if (delim)
@@ -4775,8 +4784,9 @@ void ex_help(exarg_T *eap)
* window. */
if (empty_fnum != 0 && curbuf->b_fnum != empty_fnum) {
buf = buflist_findnr(empty_fnum);
- if (buf != NULL && buf->b_nwindows == 0)
- wipe_buffer(buf, TRUE);
+ if (buf != NULL && buf->b_nwindows == 0) {
+ wipe_buffer(buf, true);
+ }
}
/* keep the previous alternate file */
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index d99383303b..7b971f464f 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -2568,6 +2568,12 @@ module.cmds = {
func='ex_spellrepall',
},
{
+ command='spellrare',
+ flags=bit.bor(BANG, RANGE, NEEDARG, EXTRA, TRLBAR),
+ addr_type='ADDR_OTHER',
+ func='ex_spell',
+ },
+ {
command='spellundo',
flags=bit.bor(BANG, RANGE, NEEDARG, EXTRA, TRLBAR),
addr_type='ADDR_OTHER',
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index 56a14887df..9d500a8ddb 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -27,6 +27,7 @@
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
#include "nvim/getchar.h"
+#include "nvim/globals.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
@@ -53,6 +54,7 @@
#include "nvim/os/fs_defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/private/defs.h"
+#include "nvim/lua/executor.h"
/// Growarray to store info about already sourced scripts.
@@ -2421,6 +2423,7 @@ void ex_compiler(exarg_T *eap)
if (*eap->arg == NUL) {
// List all compiler scripts.
do_cmdline_cmd("echo globpath(&rtp, 'compiler/*.vim')"); // NOLINT
+ do_cmdline_cmd("echo globpath(&rtp, 'compiler/*.lua')"); // NOLINT
} else {
size_t bufsize = STRLEN(eap->arg) + 14;
buf = xmalloc(bufsize);
@@ -2445,7 +2448,11 @@ void ex_compiler(exarg_T *eap)
snprintf((char *)buf, bufsize, "compiler/%s.vim", eap->arg);
if (source_in_path(p_rtp, buf, DIP_ALL) == FAIL) {
- EMSG2(_("E666: compiler not supported: %s"), eap->arg);
+ // Try lua compiler
+ snprintf((char *)buf, bufsize, "compiler/%s.lua", eap->arg);
+ if (source_in_path(p_rtp, buf, DIP_ALL) == FAIL) {
+ EMSG2(_("E666: compiler not supported: %s"), eap->arg);
+ }
}
xfree(buf);
@@ -2656,8 +2663,14 @@ static void cmd_source_buffer(const exarg_T *eap)
.curr_lnum = eap->line1,
.final_lnum = eap->line2,
};
- source_using_linegetter((void *)&cookie, get_buffer_line,
- ":source (no file)");
+ if (curbuf != NULL && curbuf->b_fname
+ && path_with_extension((const char *)curbuf->b_fname, "lua")) {
+ nlua_source_using_linegetter(get_buffer_line, (void *)&cookie,
+ ":source (no file)");
+ } else {
+ source_using_linegetter((void *)&cookie, get_buffer_line,
+ ":source (no file)");
+ }
}
/// ":source" and associated commands.
@@ -2769,7 +2782,8 @@ int do_source_str(const char *cmd, const char *traceback_name)
return source_using_linegetter((void *)&cookie, get_str_line, traceback_name);
}
-/// Reads the file `fname` and executes its lines as Ex commands.
+/// When fname is a 'lua' file nlua_exec_file() is invoked to source it.
+/// Otherwise reads the file `fname` and executes its lines as Ex commands.
///
/// This function may be called recursively!
///
@@ -2988,9 +3002,23 @@ int do_source(char_u *fname, int check_other, int is_vimrc)
firstline = p;
}
- // Call do_cmdline, which will call getsourceline() to get the lines.
- do_cmdline(firstline, getsourceline, (void *)&cookie,
- DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_REPEAT);
+ if (path_with_extension((const char *)fname, "lua")) {
+ // TODO(shadmansaleh): Properly handle :verbose for lua
+ // For now change currennt_sctx before sourcing lua files
+ // So verbose doesn't say everything was done in line 1 since we don't know
+ const sctx_T current_sctx_backup = current_sctx;
+ const linenr_T sourcing_lnum_backup = sourcing_lnum;
+ current_sctx.sc_lnum = 0;
+ sourcing_lnum = 0;
+ // Source the file as lua
+ nlua_exec_file((const char *)fname);
+ current_sctx = current_sctx_backup;
+ sourcing_lnum = sourcing_lnum_backup;
+ } else {
+ // Call do_cmdline, which will call getsourceline() to get the lines.
+ do_cmdline(firstline, getsourceline, (void *)&cookie,
+ DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_REPEAT);
+ }
retval = OK;
if (l_do_profiling == PROF_YES) {
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 29347def4c..57d1339bb0 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -4184,10 +4184,6 @@ static char_u *invalid_range(exarg_T *eap)
}
break;
case ADDR_UNSIGNED:
- if (eap->line2 < 0) {
- return (char_u *)_(e_invrange);
- }
- break;
case ADDR_NONE:
// Will give an error elsewhere.
break;
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 75ed5dc0e5..f63987136f 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -5115,11 +5115,12 @@ ExpandFromContext (
}
if (xp->xp_context == EXPAND_COLORS) {
char *directories[] = { "colors", NULL };
- return ExpandRTDir(pat, DIP_START + DIP_OPT, num_file, file, directories);
+ return ExpandRTDir(pat, DIP_START + DIP_OPT + DIP_LUA, num_file, file,
+ directories);
}
if (xp->xp_context == EXPAND_COMPILER) {
char *directories[] = { "compiler", NULL };
- return ExpandRTDir(pat, 0, num_file, file, directories);
+ return ExpandRTDir(pat, DIP_LUA, num_file, file, directories);
}
if (xp->xp_context == EXPAND_OWNSYNTAX) {
char *directories[] = { "syntax", NULL };
@@ -5127,7 +5128,7 @@ ExpandFromContext (
}
if (xp->xp_context == EXPAND_FILETYPE) {
char *directories[] = { "syntax", "indent", "ftplugin", NULL };
- return ExpandRTDir(pat, 0, num_file, file, directories);
+ return ExpandRTDir(pat, DIP_LUA, num_file, file, directories);
}
if (xp->xp_context == EXPAND_CHECKHEALTH) {
char *directories[] = { "autoload/health", NULL };
@@ -5567,6 +5568,7 @@ static int ExpandUserList(expand_T *xp, int *num_file, char_u ***file)
/// 'packpath'/pack/ * /start/ * /{dirnames}/{pat}.vim
/// When "flags" has DIP_OPT: search also from 'opt' of 'packpath':
/// 'packpath'/pack/ * /opt/ * /{dirnames}/{pat}.vim
+/// When "flags" has DIP_LUA: search also performed for .lua files
/// "dirnames" is an array with one or more directory names.
static int ExpandRTDir(char_u *pat, int flags, int *num_file, char_u ***file,
char *dirnames[])
@@ -5584,6 +5586,10 @@ static int ExpandRTDir(char_u *pat, int flags, int *num_file, char_u ***file,
char_u *s = xmalloc(size);
snprintf((char *)s, size, "%s/%s*.vim", dirnames[i], pat);
globpath(p_rtp, s, &ga, 0);
+ if (flags & DIP_LUA) {
+ snprintf((char *)s, size, "%s/%s*.lua", dirnames[i], pat);
+ globpath(p_rtp, s, &ga, 0);
+ }
xfree(s);
}
@@ -5593,6 +5599,10 @@ static int ExpandRTDir(char_u *pat, int flags, int *num_file, char_u ***file,
char_u *s = xmalloc(size);
snprintf((char *)s, size, "pack/*/start/*/%s/%s*.vim", dirnames[i], pat); // NOLINT
globpath(p_pp, s, &ga, 0);
+ if (flags & DIP_LUA) {
+ snprintf((char *)s, size, "pack/*/start/*/%s/%s*.lua", dirnames[i], pat); // NOLINT
+ globpath(p_pp, s, &ga, 0);
+ }
xfree(s);
}
@@ -5601,6 +5611,10 @@ static int ExpandRTDir(char_u *pat, int flags, int *num_file, char_u ***file,
char_u *s = xmalloc(size);
snprintf((char *)s, size, "start/*/%s/%s*.vim", dirnames[i], pat); // NOLINT
globpath(p_pp, s, &ga, 0);
+ if (flags & DIP_LUA) {
+ snprintf((char *)s, size, "start/*/%s/%s*.lua", dirnames[i], pat); // NOLINT
+ globpath(p_pp, s, &ga, 0);
+ }
xfree(s);
}
}
@@ -5611,6 +5625,10 @@ static int ExpandRTDir(char_u *pat, int flags, int *num_file, char_u ***file,
char_u *s = xmalloc(size);
snprintf((char *)s, size, "pack/*/opt/*/%s/%s*.vim", dirnames[i], pat); // NOLINT
globpath(p_pp, s, &ga, 0);
+ if (flags & DIP_LUA) {
+ snprintf((char *)s, size, "pack/*/opt/*/%s/%s*.lua", dirnames[i], pat); // NOLINT
+ globpath(p_pp, s, &ga, 0);
+ }
xfree(s);
}
@@ -5619,6 +5637,10 @@ static int ExpandRTDir(char_u *pat, int flags, int *num_file, char_u ***file,
char_u *s = xmalloc(size);
snprintf((char *)s, size, "opt/*/%s/%s*.vim", dirnames[i], pat); // NOLINT
globpath(p_pp, s, &ga, 0);
+ if (flags & DIP_LUA) {
+ snprintf((char *)s, size, "opt/*/%s/%s*.lua", dirnames[i], pat); // NOLINT
+ globpath(p_pp, s, &ga, 0);
+ }
xfree(s);
}
}
@@ -5627,7 +5649,9 @@ static int ExpandRTDir(char_u *pat, int flags, int *num_file, char_u ***file,
char_u *match = ((char_u **)ga.ga_data)[i];
char_u *s = match;
char_u *e = s + STRLEN(s);
- if (e - s > 4 && STRNICMP(e - 4, ".vim", 4) == 0) {
+ if (e - s > 4 && (STRNICMP(e - 4, ".vim", 4) == 0
+ || ((flags & DIP_LUA)
+ && STRNICMP(e - 4, ".lua", 4) == 0))) {
e -= 4;
for (s = e; s > match; MB_PTR_BACK(match, s)) {
if (vim_ispathsep(*s)) {
diff --git a/src/nvim/fold.c b/src/nvim/fold.c
index 5032646d7e..ad8418034a 100644
--- a/src/nvim/fold.c
+++ b/src/nvim/fold.c
@@ -2227,8 +2227,9 @@ static linenr_T foldUpdateIEMSRecurse(
if (getlevel == foldlevelMarker && flp->start <= flp->lvl - level
&& flp->lvl > 0) {
(void)foldFind(gap, startlnum - 1, &fp);
- if (fp >= ((fold_T *)gap->ga_data) + gap->ga_len
- || fp->fd_top >= startlnum) {
+ if (fp != NULL
+ && (fp >= ((fold_T *)gap->ga_data) + gap->ga_len
+ || fp->fd_top >= startlnum)) {
fp = NULL;
}
}
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 0ce2b586e3..7c7ce5e65f 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -878,6 +878,7 @@ EXTERN char_u e_invexpr2[] INIT(= N_("E15: Invalid expression: %s"));
EXTERN char_u e_invrange[] INIT(= N_("E16: Invalid range"));
EXTERN char_u e_invcmd[] INIT(= N_("E476: Invalid command"));
EXTERN char_u e_isadir2[] INIT(= N_("E17: \"%s\" is a directory"));
+EXTERN char_u e_no_spell[] INIT(= N_("E756: Spell checking is not possible"));
EXTERN char_u e_invchan[] INIT(= N_("E900: Invalid channel id"));
EXTERN char_u e_invchanjob[] INIT(= N_("E900: Invalid channel id: not a job"));
EXTERN char_u e_jobtblfull[] INIT(= N_("E901: Job table is full"));
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index 0a52cc16cb..4d4286354b 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -1161,6 +1161,34 @@ static void nlua_typval_exec(const char *lcmd, size_t lcmd_len,
}
}
+int nlua_source_using_linegetter(LineGetter fgetline,
+ void *cookie, char *name)
+{
+ const linenr_T save_sourcing_lnum = sourcing_lnum;
+ const sctx_T save_current_sctx = current_sctx;
+ current_sctx.sc_sid = SID_STR;
+ current_sctx.sc_seq = 0;
+ current_sctx.sc_lnum = 0;
+ sourcing_lnum = 0;
+
+ garray_T ga;
+ char_u *line = NULL;
+
+ ga_init(&ga, (int)sizeof(char_u *), 10);
+ while ((line = fgetline(0, cookie, 0, false)) != NULL) {
+ GA_APPEND(char_u *, &ga, line);
+ }
+ char *code = (char *)ga_concat_strings_sep(&ga, "\n");
+ size_t len = strlen(code);
+ nlua_typval_exec(code, len, name, NULL, 0, false, NULL);
+
+ sourcing_lnum = save_sourcing_lnum;
+ current_sctx = save_current_sctx;
+ ga_clear_strings(&ga);
+ xfree(code);
+ return OK;
+}
+
/// Call a LuaCallable given some typvals
///
/// Used to call any lua callable passed from Lua into VimL
diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c
index c186928ae2..11d30aae09 100644
--- a/src/nvim/lua/treesitter.c
+++ b/src/nvim/lua/treesitter.c
@@ -1073,6 +1073,7 @@ static int node_rawquery(lua_State *L)
// TODO(bfredl): these are expensive allegedly,
// use a reuse list later on?
TSQueryCursor *cursor = ts_query_cursor_new();
+ ts_query_cursor_set_match_limit(cursor, 32);
ts_query_cursor_exec(cursor, query, node);
bool captures = lua_toboolean(L, 3);
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
index 5c9c5103a7..8cecaa51dd 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -349,6 +349,11 @@ function vim.region(bufnr, pos1, pos2, regtype, inclusive)
vim.fn.bufload(bufnr)
end
+ -- check that region falls within current buffer
+ local buf_line_count = vim.api.nvim_buf_line_count(bufnr)
+ pos1[1] = math.min(pos1[1], buf_line_count - 1)
+ pos2[1] = math.min(pos2[1], buf_line_count - 1)
+
-- in case of block selection, columns need to be adjusted for non-ASCII characters
-- TODO: handle double-width characters
local bufline
diff --git a/src/nvim/macros.h b/src/nvim/macros.h
index eb9357d027..e718254fb9 100644
--- a/src/nvim/macros.h
+++ b/src/nvim/macros.h
@@ -169,7 +169,7 @@
#if NVIM_HAS_ATTRIBUTE(fallthrough) \
&& (!defined(__apple_build_version__) || __apple_build_version__ >= 7000000)
-# define FALLTHROUGH __attribute__((fallthrough))
+# define FALLTHROUGH {} __attribute__((fallthrough))
#else
# define FALLTHROUGH
#endif
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 56cd97f133..7d7eba2105 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -1101,11 +1101,7 @@ static void command_line_scan(mparm_T *parmp)
size_t s_size = STRLEN(a) + 9;
char *s = xmalloc(s_size);
- if (path_with_extension(a, "lua")) {
- snprintf(s, s_size, "luafile %s", a);
- } else {
- snprintf(s, s_size, "so %s", a);
- }
+ snprintf(s, s_size, "so %s", a);
parmp->cmds_tofree[parmp->n_commands] = true;
parmp->commands[parmp->n_commands++] = s;
} else {
@@ -1367,7 +1363,8 @@ static void load_plugins(void)
{
if (p_lpl) {
char_u *rtp_copy = NULL;
- char_u *const plugin_pattern = (char_u *)"plugin/**/*.vim"; // NOLINT
+ char_u *const plugin_pattern_vim = (char_u *)"plugin/**/*.vim"; // NOLINT
+ char_u *const plugin_pattern_lua = (char_u *)"plugin/**/*.lua"; // NOLINT
// First add all package directories to 'runtimepath', so that their
// autoload directories can be found. Only if not done already with a
@@ -1380,7 +1377,10 @@ static void load_plugins(void)
}
source_in_path(rtp_copy == NULL ? p_rtp : rtp_copy,
- plugin_pattern,
+ plugin_pattern_vim,
+ DIP_ALL | DIP_NOAFTER);
+ source_in_path(rtp_copy == NULL ? p_rtp : rtp_copy,
+ plugin_pattern_lua,
DIP_ALL | DIP_NOAFTER);
TIME_MSG("loading plugins");
xfree(rtp_copy);
@@ -1392,7 +1392,8 @@ static void load_plugins(void)
}
TIME_MSG("loading packages");
- source_runtime(plugin_pattern, DIP_ALL | DIP_AFTER);
+ source_runtime(plugin_pattern_vim, DIP_ALL | DIP_AFTER);
+ source_runtime(plugin_pattern_lua, DIP_ALL | DIP_AFTER);
TIME_MSG("loading after plugins");
}
}
@@ -1810,7 +1811,7 @@ static bool do_user_initialization(void)
char_u *init_lua_path = (char_u *)stdpaths_user_conf_subpath("init.lua");
if (os_path_exists(init_lua_path)
- && nlua_exec_file((const char *)init_lua_path)) {
+ && do_source(init_lua_path, true, DOSO_VIMRC)) {
os_setenv("MYVIMRC", (const char *)init_lua_path, 1);
char_u *vimrc_path = (char_u *)stdpaths_user_conf_subpath("init.vim");
@@ -1883,12 +1884,8 @@ static void source_startup_scripts(const mparm_T *const parmp)
|| strequal(parmp->use_vimrc, "NORC")) {
// Do nothing.
} else {
- if (path_with_extension(parmp->use_vimrc, "lua")) {
- nlua_exec_file(parmp->use_vimrc);
- } else {
- if (do_source((char_u *)parmp->use_vimrc, false, DOSO_NONE) != OK) {
- EMSG2(_("E282: Cannot read from \"%s\""), parmp->use_vimrc);
- }
+ if (do_source((char_u *)parmp->use_vimrc, false, DOSO_NONE) != OK) {
+ EMSG2(_("E282: Cannot read from \"%s\""), parmp->use_vimrc);
}
}
} else if (!silent_mode) {
diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c
index 34acf64d83..feb54eae4a 100644
--- a/src/nvim/marktree.c
+++ b/src/nvim/marktree.c
@@ -356,6 +356,7 @@ void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
y = y->level ? y->ptr[0] : NULL;
}
}
+ itr->i--;
}
b->n_keys--;
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index e42b138253..cb2437b2b3 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -1207,6 +1207,7 @@ void ml_recover(bool checkext)
&& !(curbuf->b_ml.ml_flags & ML_EMPTY))
ml_delete(curbuf->b_ml.ml_line_count, false);
curbuf->b_flags |= BF_RECOVERED;
+ check_cursor();
recoverymode = FALSE;
if (got_int)
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 69afe1644e..44cdc09c0b 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -818,7 +818,7 @@ static bool normal_get_command_count(NormalState *s)
}
if (s->ca.count0 < 0) {
- // got too large!
+ // overflow
s->ca.count0 = 999999999L;
}
@@ -1025,10 +1025,14 @@ static int normal_execute(VimState *state, int key)
// If you give a count before AND after the operator, they are
// multiplied.
if (s->ca.count0) {
- s->ca.count0 *= s->ca.opcount;
+ s->ca.count0 = (long)((uint64_t)s->ca.count0 * (uint64_t)s->ca.opcount);
} else {
s->ca.count0 = s->ca.opcount;
}
+ if (s->ca.count0 < 0) {
+ // overflow
+ s->ca.count0 = 999999999L;
+ }
}
// Always remember the count. It will be set to zero (on the next call,
@@ -1866,6 +1870,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
}
} else {
curwin->w_p_lbr = lbr_saved;
+ oap->excl_tr_ws = cap->cmdchar == 'z';
(void)op_yank(oap, !gui_yank, false);
}
check_cursor_col();
@@ -4389,6 +4394,9 @@ dozet:
case 'p':
nv_put(cap);
break;
+ // "zy" Yank without trailing spaces
+ case 'y': nv_operator(cap);
+ break;
/* "zF": create fold command */
/* "zf": create fold operator */
@@ -4596,7 +4604,9 @@ dozet:
if (ptr == NULL && (len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0)
return;
assert(len <= INT_MAX);
- spell_add_word(ptr, (int)len, nchar == 'w' || nchar == 'W',
+ spell_add_word(ptr, (int)len,
+ nchar == 'w' || nchar == 'W'
+ ? SPELL_ADD_BAD : SPELL_ADD_GOOD,
(nchar == 'G' || nchar == 'W') ? 0 : (int)cap->count1,
undo);
}
@@ -5817,6 +5827,9 @@ static void nv_percent(cmdarg_T *cap)
curwin->w_cursor.lnum = (curbuf->b_ml.ml_line_count *
cap->count0 + 99L) / 100L;
}
+ if (curwin->w_cursor.lnum < 1) {
+ curwin->w_cursor.lnum = 1;
+ }
if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
}
diff --git a/src/nvim/normal.h b/src/nvim/normal.h
index 51170105ed..8e15e909d4 100644
--- a/src/nvim/normal.h
+++ b/src/nvim/normal.h
@@ -48,6 +48,8 @@ typedef struct oparg_S {
colnr_T end_vcol; // end col for block mode operator
long prev_opcount; // ca.opcount saved for K_EVENT
long prev_count0; // ca.count0 saved for K_EVENT
+ bool excl_tr_ws; // exclude trailing whitespace for yank of a
+ // block
} oparg_T;
/*
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 35496d372a..04a8fc8499 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -156,6 +156,9 @@ int get_op_type(int char1, int char2)
// subtract
return OP_NR_SUB;
}
+ if (char1 == 'z' && char2 == 'y') { // OP_YANK
+ return OP_YANK;
+ }
for (i = 0;; i++) {
if (opchars[i][0] == char1 && opchars[i][1] == char2) {
break;
@@ -2563,7 +2566,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
switch (reg->y_type) {
case kMTBlockWise:
block_prep(oap, &bd, lnum, false);
- yank_copy_line(reg, &bd, y_idx);
+ yank_copy_line(reg, &bd, y_idx, oap->excl_tr_ws);
break;
case kMTLineWise:
@@ -2627,7 +2630,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
bd.textlen = endcol - startcol + oap->inclusive;
}
bd.textstart = p + startcol;
- yank_copy_line(reg, &bd, y_idx);
+ yank_copy_line(reg, &bd, y_idx, false);
break;
}
// NOTREACHED
@@ -2714,7 +2717,11 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
return;
}
-static void yank_copy_line(yankreg_T *reg, struct block_def *bd, size_t y_idx)
+// Copy a block range into a register.
+// If "exclude_trailing_space" is set, do not copy trailing whitespaces.
+static void yank_copy_line(yankreg_T *reg, const struct block_def *bd,
+ size_t y_idx, bool exclude_trailing_space)
+ FUNC_ATTR_NONNULL_ALL
{
int size = bd->startspaces + bd->endspaces + bd->textlen;
assert(size >= 0);
@@ -2726,6 +2733,14 @@ static void yank_copy_line(yankreg_T *reg, struct block_def *bd, size_t y_idx)
pnew += bd->textlen;
memset(pnew, ' ', (size_t)bd->endspaces);
pnew += bd->endspaces;
+ if (exclude_trailing_space) {
+ int s = bd->textlen + bd->endspaces;
+
+ while (ascii_iswhite(*(bd->textstart + s - 1)) && s > 0) {
+ s = s - utf_head_off(bd->textstart, bd->textstart + s - 1) - 1;
+ pnew--;
+ }
+ }
*pnew = NUL;
}
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 67fb78bcc8..f57abe89cc 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -85,6 +85,7 @@
#include "nvim/api/private/helpers.h"
#include "nvim/os/input.h"
#include "nvim/os/lang.h"
+#include "nvim/quickfix.h"
/*
* The options that are local to a window or buffer have "indir" set to one of
@@ -3182,6 +3183,10 @@ ambw_end:
}
}
}
+ } else if (varp == &p_qftf) {
+ if (!qf_process_qftf_option()) {
+ errmsg = e_invarg;
+ }
} else {
// Options that are a list of flags.
p = NULL;
@@ -5370,7 +5375,7 @@ static int put_setstring(FILE *fd, char *cmd, char *name,
}
p = buf;
while (*p != NUL) {
- // for each comma seperated option part, append value to
+ // for each comma separated option part, append value to
// the option, :set rtp+=value
if (fprintf(fd, "%s %s+=", cmd, name) < 0) {
goto fail;
diff --git a/src/nvim/path.c b/src/nvim/path.c
index fe50be5ea1..6ac24182cc 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -180,6 +180,34 @@ const char *path_next_component(const char *fname)
return fname;
}
+/// Returns the length of the path head on the current platform.
+/// @return
+/// - 3 on windows
+/// - 1 otherwise
+int path_head_length(void)
+{
+#ifdef WIN32
+ return 3;
+#else
+ return 1;
+#endif
+}
+
+/// Returns true if path begins with characters denoting the head of a path
+/// (e.g. '/' on linux and 'D:' on windows).
+/// @param path The path to be checked.
+/// @return
+/// - True if path begins with a path head
+/// - False otherwise
+bool is_path_head(const char_u *path)
+{
+#ifdef WIN32
+ return isalpha(path[0]) && path[1] == ':';
+#else
+ return vim_ispathsep(*path);
+#endif
+}
+
/// Get a pointer to one character past the head of a path name.
/// Unix: after "/"; Win: after "c:\"
/// If there is no head, path is returned.
@@ -189,7 +217,7 @@ char_u *get_past_head(const char_u *path)
#ifdef WIN32
// May skip "c:"
- if (isalpha(path[0]) && path[1] == ':') {
+ if (is_path_head(path)) {
retval = path + 2;
}
#endif
@@ -1991,10 +2019,24 @@ char_u *path_shorten_fname(char_u *full_path, char_u *dir_name)
assert(dir_name != NULL);
size_t len = strlen((char *)dir_name);
+
+ // If dir_name is a path head, full_path can always be made relative.
+ if (len == (size_t)path_head_length() && is_path_head(dir_name)) {
+ return full_path + len;
+ }
+
+ // If full_path and dir_name do not match, it's impossible to make one
+ // relative to the other.
+ if (fnamencmp(dir_name, full_path, len) != 0) {
+ return NULL;
+ }
+
char_u *p = full_path + len;
- if (fnamencmp(dir_name, full_path, len) != 0
- || !vim_ispathsep(*p)) {
+ // If *p is not pointing to a path separator, this means that full_path's
+ // last directory name is longer than *dir_name's last directory, so they
+ // don't actually match.
+ if (!vim_ispathsep(*p)) {
return NULL;
}
diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c
index 7d452d6797..f620517aff 100644
--- a/src/nvim/popupmnu.c
+++ b/src/nvim/popupmnu.c
@@ -440,7 +440,7 @@ void pum_redraw(void)
}
if (ui_has(kUIMultigrid)) {
const char *anchor = pum_above ? "SW" : "NW";
- int row_off = pum_above ? pum_height : 0;
+ int row_off = pum_above ? -pum_height : 0;
ui_call_win_float_pos(pum_grid.handle, -1, cstr_to_string(anchor),
pum_anchor_grid, pum_row-row_off, pum_col-col_off,
false, pum_grid.zindex);
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 1a9bbe26f0..71624baaf4 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -100,7 +100,7 @@ typedef struct qf_list_S {
char_u *qf_title; ///< title derived from the command that created
///< the error list or set by setqflist
typval_T *qf_ctx; ///< context set by setqflist/setloclist
- char_u *qf_qftf; ///< 'quickfixtextfunc' setting for this list
+ Callback qftf_cb; ///< 'quickfixtextfunc' callback function
struct dir_stack_T *qf_dir_stack;
char_u *qf_directory;
@@ -541,6 +541,9 @@ static int efm_to_regpat(const char_u *efm, int len, efm_T *fmt_ptr,
static efm_T *fmt_start = NULL; // cached across qf_parse_line() calls
+// callback function for 'quickfixtextfunc'
+static Callback qftf_cb;
+
static void free_efm_list(efm_T **efm_first)
{
for (efm_T *efm_ptr = *efm_first; efm_ptr != NULL; efm_ptr = *efm_first) {
@@ -1978,7 +1981,7 @@ static int copy_loclist_entries(const qf_list_T *from_qfl, qf_list_T *to_qfl)
}
/// Copy the specified location list 'from_qfl' to 'to_qfl'.
-static int copy_loclist(const qf_list_T *from_qfl, qf_list_T *to_qfl)
+static int copy_loclist(qf_list_T *from_qfl, qf_list_T *to_qfl)
FUNC_ATTR_NONNULL_ALL
{
// Some of the fields are populated by qf_add_entry()
@@ -2000,11 +2003,7 @@ static int copy_loclist(const qf_list_T *from_qfl, qf_list_T *to_qfl)
} else {
to_qfl->qf_ctx = NULL;
}
- if (from_qfl->qf_qftf != NULL) {
- to_qfl->qf_qftf = vim_strsave(from_qfl->qf_qftf);
- } else {
- to_qfl->qf_qftf = NULL;
- }
+ callback_copy(&to_qfl->qftf_cb, &from_qfl->qftf_cb);
if (from_qfl->qf_count) {
if (copy_loclist_entries(from_qfl, to_qfl) == FAIL) {
@@ -3385,7 +3384,7 @@ static void qf_free(qf_list_T *qfl)
XFREE_CLEAR(qfl->qf_title);
tv_free(qfl->qf_ctx);
qfl->qf_ctx = NULL;
- XFREE_CLEAR(qfl->qf_qftf);
+ callback_free(&qfl->qftf_cb);
qfl->qf_id = 0;
qfl->qf_changedtick = 0L;
}
@@ -3860,6 +3859,41 @@ static buf_T *qf_find_buf(qf_info_T *qi)
return NULL;
}
+// Process the 'quickfixtextfunc' option value.
+bool qf_process_qftf_option(void)
+{
+ typval_T *tv;
+ Callback cb;
+
+ if (p_qftf == NULL || *p_qftf == NUL) {
+ callback_free(&qftf_cb);
+ return true;
+ }
+
+ if (*p_qftf == '{') {
+ // Lambda expression
+ tv = eval_expr(p_qftf);
+ if (tv == NULL) {
+ return false;
+ }
+ } else {
+ // treat everything else as a function name string
+ tv = xcalloc(1, sizeof(*tv));
+ tv->v_type = VAR_STRING;
+ tv->vval.v_string = vim_strsave(p_qftf);
+ }
+
+ if (!callback_from_typval(&cb, tv)) {
+ tv_free(tv);
+ return false;
+ }
+
+ callback_free(&qftf_cb);
+ qftf_cb = cb;
+ tv_free(tv);
+ return true;
+}
+
/// Update the w:quickfix_title variable in the quickfix/location list window in
/// all the tab pages.
static void qf_update_win_titlevar(qf_info_T *qi)
@@ -3891,7 +3925,15 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last)
int qf_winid = 0;
if (IS_LL_STACK(qi)) {
- qf_winid = curwin->handle;
+ if (curwin->w_llist == qi) {
+ win = curwin;
+ } else {
+ win = qf_find_win_with_loclist(qi);
+ if (win == NULL) {
+ return;
+ }
+ }
+ qf_winid = (int)win->handle;
}
if (old_last == NULL) {
@@ -3928,7 +3970,9 @@ static int qf_buf_add_line(qf_list_T *qfl, buf_T *buf, linenr_T lnum,
int len;
buf_T *errbuf;
- if (qftf_str != NULL) {
+ // If the 'quickfixtextfunc' function returned an non-empty custom string
+ // for this entry, then use it.
+ if (qftf_str != NULL && *qftf_str != NUL) {
STRLCPY(IObuff, qftf_str, IOSIZE);
} else {
if (qfp->qf_module != NULL) {
@@ -3997,22 +4041,25 @@ static int qf_buf_add_line(qf_list_T *qfl, buf_T *buf, linenr_T lnum,
return OK;
}
+// Call the 'quickfixtextfunc' function to get the list of lines to display in
+// the quickfix window for the entries 'start_idx' to 'end_idx'.
static list_T *call_qftf_func(qf_list_T *qfl,
int qf_winid,
long start_idx,
long end_idx)
{
- char_u *qftf = p_qftf;
+ Callback *cb = &qftf_cb;
list_T *qftf_list = NULL;
// If 'quickfixtextfunc' is set, then use the user-supplied function to get
// the text to display. Use the local value of 'quickfixtextfunc' if it is
// set.
- if (qfl->qf_qftf != NULL) {
- qftf = qfl->qf_qftf;
+ if (qfl->qftf_cb.type != kCallbackNone) {
+ cb = &qfl->qftf_cb;
}
- if (qftf != NULL && *qftf != NUL) {
+ if (cb != NULL && cb->type != kCallbackNone) {
typval_T args[1];
+ typval_T rettv;
// create the dict argument
dict_T *const dict = tv_dict_alloc_lock(VAR_FIXED);
@@ -4026,8 +4073,16 @@ static list_T *call_qftf_func(qf_list_T *qfl,
args[0].v_type = VAR_DICT;
args[0].vval.v_dict = dict;
- qftf_list = call_func_retlist(qftf, 1, args);
- dict->dv_refcount--;
+ qftf_list = NULL;
+
+ if (callback_call(cb, 1, args, &rettv)) {
+ if (rettv.v_type == VAR_LIST) {
+ qftf_list = rettv.vval.v_list;
+ tv_list_ref(qftf_list);
+ }
+ tv_clear(&rettv);
+ }
+ tv_dict_unref(dict);
}
return qftf_list;
@@ -4064,6 +4119,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last,
if (qfl != NULL) {
char_u dirname[MAXPATHL];
int prev_bufnr = -1;
+ bool invalid_val = false;
*dirname = NUL;
@@ -4086,10 +4142,15 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last,
while (lnum < qfl->qf_count) {
char_u *qftf_str = NULL;
- if (qftf_li != NULL) {
- // Use the text supplied by the user defined function
- qftf_str = (char_u *)tv_get_string_chk(TV_LIST_ITEM_TV(qftf_li));
+ // Use the text supplied by the user defined function (if any).
+ // If the returned value is not string, then ignore the rest
+ // of the returned values and use the default.
+ if (qftf_li != NULL && !invalid_val) {
+ qftf_str = (char_u *)tv_get_string_chk(TV_LIST_ITEM_TV(qftf_li));
+ if (qftf_str == NULL) {
+ invalid_val = true;
}
+ }
if (qf_buf_add_line(qfl, buf, lnum, qfp, dirname, qftf_str,
prev_bufnr != qfp->qf_fnum) == FAIL) {
@@ -5660,7 +5721,7 @@ static void wipe_dummy_buffer(buf_T *buf, char_u *dirname_start)
// work when got_int is set.
enter_cleanup(&cs);
- wipe_buffer(buf, FALSE);
+ wipe_buffer(buf, true);
// Restore the error/interrupt/exception state if not discarded by a
// new aborting error, interrupt, or uncaught exception.
@@ -5796,7 +5857,9 @@ enum {
QF_GETLIST_SIZE = 0x80,
QF_GETLIST_TICK = 0x100,
QF_GETLIST_FILEWINID = 0x200,
- QF_GETLIST_ALL = 0x3FF,
+ QF_GETLIST_QFBUFNR = 0x400,
+ QF_GETLIST_QFTF = 0x800,
+ QF_GETLIST_ALL = 0xFFF,
};
/// Parse text from 'di' and return the quickfix list items.
@@ -5894,6 +5957,9 @@ static int qf_getprop_keys2flags(const dict_T *what, bool loclist)
if (loclist && tv_dict_find(what, S_LEN("filewinid")) != NULL) {
flags |= QF_GETLIST_FILEWINID;
}
+ if (tv_dict_find(what, S_LEN("quickfixtextfunc")) != NULL) {
+ flags |= QF_GETLIST_QFTF;
+ }
return flags;
}
@@ -5985,6 +6051,9 @@ static int qf_getprop_defaults(qf_info_T *qi,
if ((status == OK) && locstack && (flags & QF_GETLIST_FILEWINID)) {
status = tv_dict_add_nr(retdict, S_LEN("filewinid"), 0);
}
+ if ((status == OK) && (flags & QF_GETLIST_QFTF)) {
+ status = tv_dict_add_str(retdict, S_LEN("quickfixtextfunc"), "");
+ }
return status;
}
@@ -6060,6 +6129,26 @@ static int qf_getprop_idx(qf_list_T *qfl, int eidx, dict_T *retdict)
return tv_dict_add_nr(retdict, S_LEN("idx"), eidx);
}
+/// Return the 'quickfixtextfunc' function of a quickfix/location list
+/// @return OK or FAIL
+static int qf_getprop_qftf(qf_list_T *qfl, dict_T *retdict)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int status;
+
+ if (qfl->qftf_cb.type != kCallbackNone) {
+ typval_T tv;
+
+ callback_put(&qfl->qftf_cb, &tv);
+ status = tv_dict_add_tv(retdict, S_LEN("quickfixtextfunc"), &tv);
+ tv_clear(&tv);
+ } else {
+ status = tv_dict_add_str(retdict, S_LEN("quickfixtextfunc"), "");
+ }
+
+ return status;
+}
+
/// Return quickfix/location list details (title) as a dictionary.
/// 'what' contains the details to return. If 'list_idx' is -1,
/// then current list is used. Otherwise the specified list is used.
@@ -6133,19 +6222,25 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
if ((status == OK) && (wp != NULL) && (flags & QF_GETLIST_FILEWINID)) {
status = qf_getprop_filewinid(wp, qi, retdict);
}
+ if ((status == OK) && (flags & QF_GETLIST_QFTF)) {
+ status = qf_getprop_qftf(qfl, retdict);
+ }
return status;
}
/// Set the current index in the specified quickfix list
-static int qf_setprop_qftf(qf_info_T *qi, qf_list_T *qfl,
- dictitem_T *di)
+/// @return OK
+static int qf_setprop_qftf(qf_list_T *qfl, dictitem_T *di)
+ FUNC_ATTR_NONNULL_ALL
{
- XFREE_CLEAR(qfl->qf_qftf);
- if (di->di_tv.v_type == VAR_STRING && di->di_tv.vval.v_string != NULL) {
- qfl->qf_qftf = vim_strsave(di->di_tv.vval.v_string);
- }
- return OK;
+ Callback cb;
+
+ callback_free(&qfl->qftf_cb);
+ if (callback_from_typval(&cb, &di->di_tv)) {
+ qfl->qftf_cb = cb;
+ }
+ return OK;
}
/// Add a new quickfix entry to list at 'qf_idx' in the stack 'qi' from the
@@ -6514,7 +6609,7 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action,
retval = qf_setprop_curidx(qi, qfl, di);
}
if ((di = tv_dict_find(what, S_LEN("quickfixtextfunc"))) != NULL) {
- retval = qf_setprop_qftf(qi, qfl, di);
+ retval = qf_setprop_qftf(qfl, di);
}
if (newlist || retval == OK) {
diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c
index 1fb7e3b434..c3cd210538 100644
--- a/src/nvim/runtime.c
+++ b/src/nvim/runtime.c
@@ -245,7 +245,8 @@ int source_in_path(char_u *path, char_u *name, int flags)
return do_in_path_and_pp(path, name, flags, source_callback, NULL);
}
-// Expand wildcards in "pat" and invoke do_source() for each match.
+// Expand wildcards in "pat" and invoke do_source()/nlua_exec_file()
+// for each match.
static void source_all_matches(char_u *pat)
{
int num_files;
@@ -405,17 +406,15 @@ theend:
/// Load scripts in "plugin" and "ftdetect" directories of the package.
static int load_pack_plugin(char_u *fname)
{
- static const char *plugpat = "%s/plugin/**/*.vim"; // NOLINT
static const char *ftpat = "%s/ftdetect/*.vim"; // NOLINT
- int retval = FAIL;
char *const ffname = fix_fname((char *)fname);
size_t len = strlen(ffname) + STRLEN(ftpat);
- char_u *pat = try_malloc(len + 1);
- if (pat == NULL) {
- goto theend;
- }
- vim_snprintf((char *)pat, len, plugpat, ffname);
+ char_u *pat = xmallocz(len);
+
+ vim_snprintf((char *)pat, len, "%s/plugin/**/*.vim", ffname); // NOLINT
+ source_all_matches(pat);
+ vim_snprintf((char *)pat, len, "%s/plugin/**/*.lua", ffname); // NOLINT
source_all_matches(pat);
char_u *cmd = vim_strsave((char_u *)"g:did_load_filetypes");
@@ -426,16 +425,15 @@ static int load_pack_plugin(char_u *fname)
do_cmdline_cmd("augroup filetypedetect");
vim_snprintf((char *)pat, len, ftpat, ffname);
source_all_matches(pat);
+ vim_snprintf((char *)pat, len, "%s/ftdetect/*.lua", ffname); // NOLINT
+ source_all_matches(pat);
do_cmdline_cmd("augroup END");
}
xfree(cmd);
xfree(pat);
- retval = OK;
-
-theend:
xfree(ffname);
- return retval;
+ return OK;
}
// used for "cookie" of add_pack_plugin()
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 844104e7d0..3446a944cd 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -1709,7 +1709,7 @@ static void win_update(win_T *wp, Providers *providers)
&& (wp->w_valid & (VALID_WCOL|VALID_WROW))
!= (VALID_WCOL|VALID_WROW)) {
// A win_line() call applied a fix to screen cursor column to
- // accomodate concealment of cursor line, but in this call to
+ // accommodate concealment of cursor line, but in this call to
// update_topline() the cursor's row or column got invalidated.
// If they are left invalid, setcursor() will recompute them
// but there won't be any further win_line() call to re-fix the
@@ -2757,8 +2757,9 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
}
if (wp->w_p_rl) { // reverse line numbers
// like rl_mirror(), but keep the space at the end
- char_u *p2 = skiptowhite(extra) - 1;
- for (char_u *p1 = extra; p1 < p2; p1++, p2--) {
+ char_u *p2 = skipwhite(extra);
+ p2 = skiptowhite(p2) - 1;
+ for (char_u *p1 = skipwhite(extra); p1 < p2; p1++, p2--) {
const int t = *p1;
*p1 = *p2;
*p2 = t;
@@ -7210,7 +7211,24 @@ void ui_ext_tabline_update(void)
ADD(tabs, DICTIONARY_OBJ(tab_info));
}
- ui_call_tabline_update(curtab->handle, tabs);
+
+ Array buffers = ARRAY_DICT_INIT;
+ FOR_ALL_BUFFERS(buf) {
+ // Do not include unlisted buffers
+ if (!buf->b_p_bl) {
+ continue;
+ }
+
+ Dictionary buffer_info = ARRAY_DICT_INIT;
+ PUT(buffer_info, "buffer", BUFFER_OBJ(buf->handle));
+
+ get_trans_bufname(buf);
+ PUT(buffer_info, "name", STRING_OBJ(cstr_to_string((char *)NameBuff)));
+
+ ADD(buffers, DICTIONARY_OBJ(buffer_info));
+ }
+
+ ui_call_tabline_update(curtab->handle, tabs, curbuf->handle, buffers);
}
/*
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index d1428b0117..771c2106db 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -441,7 +441,8 @@ size_t spell_check(
MB_PTR_ADV(mi.mi_fend);
}
- (void)spell_casefold(ptr, (int)(mi.mi_fend - ptr), mi.mi_fword, MAXWLEN + 1);
+ (void)spell_casefold(wp, ptr, (int)(mi.mi_fend - ptr), mi.mi_fword,
+ MAXWLEN + 1);
mi.mi_fwordlen = (int)STRLEN(mi.mi_fword);
if (camel_case) {
@@ -869,10 +870,11 @@ static void find_word(matchinf_T *mip, int mode)
if (slang->sl_compsylmax < MAXWLEN) {
// "fword" is only needed for checking syllables.
- if (ptr == mip->mi_word)
- (void)spell_casefold(ptr, wlen, fword, MAXWLEN);
- else
+ if (ptr == mip->mi_word) {
+ (void)spell_casefold(mip->mi_win, ptr, wlen, fword, MAXWLEN);
+ } else {
STRLCPY(fword, ptr, endlen[endidxcnt] + 1);
+ }
}
if (!can_compound(slang, fword, mip->mi_compflags))
continue;
@@ -1315,9 +1317,9 @@ static int fold_more(matchinf_T *mip)
MB_PTR_ADV(mip->mi_fend);
}
- (void)spell_casefold(p, (int)(mip->mi_fend - p),
- mip->mi_fword + mip->mi_fwordlen,
- MAXWLEN - mip->mi_fwordlen);
+ (void)spell_casefold(mip->mi_win, p, (int)(mip->mi_fend - p),
+ mip->mi_fword + mip->mi_fwordlen,
+ MAXWLEN - mip->mi_fwordlen);
flen = (int)STRLEN(mip->mi_fword + mip->mi_fwordlen);
mip->mi_fwordlen += flen;
return flen;
@@ -1341,7 +1343,7 @@ static bool no_spell_checking(win_T *wp)
{
if (!wp->w_p_spell || *wp->w_s->b_p_spl == NUL
|| GA_EMPTY(&wp->w_s->b_langp)) {
- EMSG(_("E756: Spell checking is not enabled"));
+ EMSG(_(e_no_spell));
return true;
}
return false;
@@ -2655,7 +2657,9 @@ static bool spell_iswordp_w(const int *p, const win_T *wp)
// Uses the character definitions from the .spl file.
// When using a multi-byte 'encoding' the length may change!
// Returns FAIL when something wrong.
-int spell_casefold(char_u *str, int len, char_u *buf, int buflen)
+int spell_casefold(const win_T *wp, char_u *str, int len, char_u *buf,
+ int buflen)
+ FUNC_ATTR_NONNULL_ALL
{
if (len >= buflen) {
buf[0] = NUL;
@@ -2670,8 +2674,22 @@ int spell_casefold(char_u *str, int len, char_u *buf, int buflen)
buf[outi] = NUL;
return FAIL;
}
- const int c = mb_cptr2char_adv((const char_u **)&p);
- outi += utf_char2bytes(SPELL_TOFOLD(c), buf + outi);
+ int c = mb_cptr2char_adv((const char_u **)&p);
+
+ // Exception: greek capital sigma 0x03A3 folds to 0x03C3, except
+ // when it is the last character in a word, then it folds to
+ // 0x03C2.
+ if (c == 0x03a3 || c == 0x03c2) {
+ if (p == str + len || !spell_iswordp(p, wp)) {
+ c = 0x03c2;
+ } else {
+ c = 0x03c3;
+ }
+ } else {
+ c = SPELL_TOFOLD(c);
+ }
+
+ outi += utf_char2bytes(c, buf + outi);
}
buf[outi] = NUL;
@@ -2753,9 +2771,17 @@ void spell_suggest(int count)
int selected = count;
int badlen = 0;
int msg_scroll_save = msg_scroll;
+ const int wo_spell_save = curwin->w_p_spell;
+
+ if (!curwin->w_p_spell) {
+ did_set_spelllang(curwin);
+ curwin->w_p_spell = true;
+ }
- if (no_spell_checking(curwin))
+ if (*curwin->w_s->b_p_spl == NUL) {
+ EMSG(_(e_no_spell));
return;
+ }
if (VIsual_active) {
// Use the Visually selected text as the bad word. But reject
@@ -2948,6 +2974,7 @@ void spell_suggest(int count)
spell_find_cleanup(&sug);
xfree(line);
+ curwin->w_p_spell = wo_spell_save;
}
// Check if the word at line "lnum" column "col" is required to start with a
@@ -3155,7 +3182,8 @@ spell_find_suggest (
if (su->su_badlen >= MAXWLEN)
su->su_badlen = MAXWLEN - 1; // just in case
STRLCPY(su->su_badword, su->su_badptr, su->su_badlen + 1);
- (void)spell_casefold(su->su_badptr, su->su_badlen, su->su_fbadword, MAXWLEN);
+ (void)spell_casefold(curwin, su->su_badptr, su->su_badlen, su->su_fbadword,
+ MAXWLEN);
// TODO(vim): make this work if the case-folded text is longer than the
// original text. Currently an illegal byte causes wrong pointer
@@ -3535,7 +3563,7 @@ static void suggest_try_change(suginfo_T *su)
STRCPY(fword, su->su_fbadword);
n = (int)STRLEN(fword);
p = su->su_badptr + su->su_badlen;
- (void)spell_casefold(p, (int)STRLEN(p), fword + n, MAXWLEN - n);
+ (void)spell_casefold(curwin, p, (int)STRLEN(p), fword + n, MAXWLEN - n);
for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) {
lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
@@ -5087,7 +5115,7 @@ stp_sal_score (
pbad = badsound;
else {
// soundfold the bad word with more characters following
- (void)spell_casefold(su->su_badptr, stp->st_orglen, fword, MAXWLEN);
+ (void)spell_casefold(curwin, su->su_badptr, stp->st_orglen, fword, MAXWLEN);
// When joining two words the sound often changes a lot. E.g., "t he"
// sounds like "t h" while "the" sounds like "@". Avoid that by
@@ -5742,7 +5770,9 @@ cleanup_suggestions (
xfree(stp[i].st_word);
}
gap->ga_len = keep;
- return stp[keep - 1].st_score;
+ if (keep >= 1) {
+ return stp[keep - 1].st_score;
+ }
}
}
return maxscore;
@@ -5800,10 +5830,10 @@ void spell_soundfold(slang_T *slang, char_u *inword, bool folded, char_u *res)
spell_soundfold_sofo(slang, inword, res);
else {
// SAL items used. Requires the word to be case-folded.
- if (folded)
+ if (folded) {
word = inword;
- else {
- (void)spell_casefold(inword, (int)STRLEN(inword), fword, MAXWLEN);
+ } else {
+ (void)spell_casefold(curwin, inword, (int)STRLEN(inword), fword, MAXWLEN);
word = fword;
}
diff --git a/src/nvim/spell_defs.h b/src/nvim/spell_defs.h
index e2c9ab7ae8..f07f5673f9 100644
--- a/src/nvim/spell_defs.h
+++ b/src/nvim/spell_defs.h
@@ -284,4 +284,11 @@ extern int did_set_spelltab;
extern char *e_format;
+// Values for "what" argument of spell_add_word()
+typedef enum {
+ SPELL_ADD_GOOD = 0,
+ SPELL_ADD_BAD = 1,
+ SPELL_ADD_RARE = 2,
+} SpellAddType;
+
#endif // NVIM_SPELL_DEFS_H
diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c
index 3c125959a9..0597f392e7 100644
--- a/src/nvim/spellfile.c
+++ b/src/nvim/spellfile.c
@@ -2942,9 +2942,9 @@ static void add_fromto(spellinfo_T *spin, garray_T *gap, char_u *from, char_u *t
char_u word[MAXWLEN];
fromto_T *ftp = GA_APPEND_VIA_PTR(fromto_T, gap);
- (void)spell_casefold(from, (int)STRLEN(from), word, MAXWLEN);
+ (void)spell_casefold(curwin, from, (int)STRLEN(from), word, MAXWLEN);
ftp->ft_from = getroom_save(spin, word);
- (void)spell_casefold(to, (int)STRLEN(to), word, MAXWLEN);
+ (void)spell_casefold(curwin, to, (int)STRLEN(to), word, MAXWLEN);
ftp->ft_to = getroom_save(spin, word);
}
@@ -3764,7 +3764,7 @@ store_word (
char_u *word,
int flags, // extra flags, WF_BANNED
int region, // supported region(s)
- char_u *pfxlist, // list of prefix IDs or NULL
+ const char_u *pfxlist, // list of prefix IDs or NULL
bool need_affix // only store word with affix ID
)
{
@@ -3772,25 +3772,28 @@ store_word (
int ct = captype(word, word + len);
char_u foldword[MAXWLEN];
int res = OK;
- char_u *p;
- (void)spell_casefold(word, len, foldword, MAXWLEN);
- for (p = pfxlist; res == OK; ++p) {
- if (!need_affix || (p != NULL && *p != NUL))
+ (void)spell_casefold(curwin, word, len, foldword, MAXWLEN);
+ for (const char_u *p = pfxlist; res == OK; p++) {
+ if (!need_affix || (p != NULL && *p != NUL)) {
res = tree_add_word(spin, foldword, spin->si_foldroot, ct | flags,
- region, p == NULL ? 0 : *p);
- if (p == NULL || *p == NUL)
+ region, p == NULL ? 0 : *p);
+ }
+ if (p == NULL || *p == NUL) {
break;
+ }
}
++spin->si_foldwcount;
if (res == OK && (ct == WF_KEEPCAP || (flags & WF_KEEPCAP))) {
- for (p = pfxlist; res == OK; ++p) {
- if (!need_affix || (p != NULL && *p != NUL))
+ for (const char_u *p = pfxlist; res == OK; p++) {
+ if (!need_affix || (p != NULL && *p != NUL)) {
res = tree_add_word(spin, word, spin->si_keeproot, flags,
- region, p == NULL ? 0 : *p);
- if (p == NULL || *p == NUL)
+ region, p == NULL ? 0 : *p);
+ }
+ if (p == NULL || *p == NUL) {
break;
+ }
}
++spin->si_keepwcount;
}
@@ -5287,13 +5290,16 @@ static void spell_message(const spellinfo_T *spin, char_u *str)
}
// ":[count]spellgood {word}"
-// ":[count]spellwrong {word}"
+// ":[count]spellwrong {word}"
// ":[count]spellundo {word}"
+// ":[count]spellrare {word}"
void ex_spell(exarg_T *eap)
{
- spell_add_word(eap->arg, (int)STRLEN(eap->arg), eap->cmdidx == CMD_spellwrong,
- eap->forceit ? 0 : (int)eap->line2,
- eap->cmdidx == CMD_spellundo);
+ spell_add_word(eap->arg, (int)STRLEN(eap->arg),
+ eap->cmdidx == CMD_spellwrong ? SPELL_ADD_BAD :
+ eap->cmdidx == CMD_spellrare ? SPELL_ADD_RARE : SPELL_ADD_GOOD,
+ eap->forceit ? 0 : (int)eap->line2,
+ eap->cmdidx == CMD_spellundo);
}
// Add "word[len]" to 'spellfile' as a good or bad word.
@@ -5301,10 +5307,10 @@ void
spell_add_word (
char_u *word,
int len,
- int bad,
- int idx, // "zG" and "zW": zero, otherwise index in
- // 'spellfile'
- bool undo // true for "zug", "zuG", "zuw" and "zuW"
+ SpellAddType what, // SPELL_ADD_ values
+ int idx, // "zG" and "zW": zero, otherwise index in
+ // 'spellfile'
+ bool undo // true for "zug", "zuG", "zuw" and "zuW"
)
{
FILE *fd = NULL;
@@ -5361,7 +5367,7 @@ spell_add_word (
fname = fnamebuf;
}
- if (bad || undo) {
+ if (what == SPELL_ADD_BAD || undo) {
// When the word appears as good word we need to remove that one,
// since its flags sort before the one with WF_BANNED.
fd = os_fopen((char *)fname, "r");
@@ -5419,13 +5425,16 @@ spell_add_word (
}
}
- if (fd == NULL)
+ if (fd == NULL) {
EMSG2(_(e_notopen), fname);
- else {
- if (bad)
+ } else {
+ if (what == SPELL_ADD_BAD) {
fprintf(fd, "%.*s/!\n", len, word);
- else
+ } else if (what == SPELL_ADD_RARE) {
+ fprintf(fd, "%.*s/?\n", len, word);
+ } else {
fprintf(fd, "%.*s\n", len, word);
+ }
fclose(fd);
home_replace(NULL, fname, NameBuff, MAXPATHL, TRUE);
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index ed886ab7f9..ce81f26d38 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -6438,6 +6438,10 @@ int load_colors(char_u *name)
apply_autocmds(EVENT_COLORSCHEMEPRE, name, curbuf->b_fname, false, curbuf);
snprintf((char *)buf, buflen, "colors/%s.vim", name);
retval = source_runtime(buf, DIP_START + DIP_OPT);
+ if (retval == FAIL) {
+ snprintf((char *)buf, buflen, "colors/%s.lua", name);
+ retval = source_runtime(buf, DIP_START + DIP_OPT);
+ }
xfree(buf);
apply_autocmds(EVENT_COLORSCHEME, name, curbuf->b_fname, FALSE, curbuf);
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index bb84fa498e..ad28118f16 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -108,19 +108,19 @@ func Test_bufunload()
autocmd BufWipeout * call add(s:li, "bufwipeout")
augroup END
- let s:li=[]
+ let s:li = []
new
setlocal bufhidden=
bunload
call assert_equal(["bufunload", "bufdelete"], s:li)
- let s:li=[]
+ let s:li = []
new
setlocal bufhidden=delete
bunload
call assert_equal(["bufunload", "bufdelete"], s:li)
- let s:li=[]
+ let s:li = []
new
setlocal bufhidden=unload
bwipeout
@@ -196,6 +196,29 @@ func Test_autocmd_bufunload_avoiding_SEGV_02()
bwipe! a.txt
endfunc
+func Test_autocmd_dummy_wipeout()
+ " prepare files
+ call writefile([''], 'Xdummywipetest1.txt')
+ call writefile([''], 'Xdummywipetest2.txt')
+ augroup test_bufunload_group
+ autocmd!
+ autocmd BufUnload * call add(s:li, "bufunload")
+ autocmd BufDelete * call add(s:li, "bufdelete")
+ autocmd BufWipeout * call add(s:li, "bufwipeout")
+ augroup END
+
+ let s:li = []
+ split Xdummywipetest1.txt
+ silent! vimgrep /notmatched/ Xdummywipetest*
+ call assert_equal(["bufunload", "bufwipeout"], s:li)
+
+ bwipeout
+ call delete('Xdummywipetest1.txt')
+ call delete('Xdummywipetest2.txt')
+ au! test_bufunload_group
+ augroup! test_bufunload_group
+endfunc
+
func Test_win_tab_autocmd()
let g:record = []
@@ -428,7 +451,7 @@ func Test_autocmd_bufwipe_in_SessLoadPost()
let content =<< trim [CODE]
set nocp noswapfile
- let v:swapchoice="e"
+ let v:swapchoice = "e"
augroup test_autocmd_sessionload
autocmd!
autocmd SessionLoadPost * exe bufnr("Xsomething") . "bw!"
@@ -537,92 +560,92 @@ func Test_OptionSet()
au OptionSet * :call s:AutoCommandOptionSet(expand("<amatch>"))
" 1: Setting number option"
- let g:options=[['number', 0, 1, 'global']]
+ let g:options = [['number', 0, 1, 'global']]
set nu
call assert_equal([], g:options)
call assert_equal(g:opt[0], g:opt[1])
" 2: Setting local number option"
- let g:options=[['number', 1, 0, 'local']]
+ let g:options = [['number', 1, 0, 'local']]
setlocal nonu
call assert_equal([], g:options)
call assert_equal(g:opt[0], g:opt[1])
" 3: Setting global number option"
- let g:options=[['number', 1, 0, 'global']]
+ let g:options = [['number', 1, 0, 'global']]
setglobal nonu
call assert_equal([], g:options)
call assert_equal(g:opt[0], g:opt[1])
" 4: Setting local autoindent option"
- let g:options=[['autoindent', 0, 1, 'local']]
+ let g:options = [['autoindent', 0, 1, 'local']]
setlocal ai
call assert_equal([], g:options)
call assert_equal(g:opt[0], g:opt[1])
" 5: Setting global autoindent option"
- let g:options=[['autoindent', 0, 1, 'global']]
+ let g:options = [['autoindent', 0, 1, 'global']]
setglobal ai
call assert_equal([], g:options)
call assert_equal(g:opt[0], g:opt[1])
" 6: Setting global autoindent option"
- let g:options=[['autoindent', 1, 0, 'global']]
+ let g:options = [['autoindent', 1, 0, 'global']]
set ai!
call assert_equal([], g:options)
call assert_equal(g:opt[0], g:opt[1])
" Should not print anything, use :noa
" 7: don't trigger OptionSet"
- let g:options=[['invalid', 1, 1, 'invalid']]
+ let g:options = [['invalid', 1, 1, 'invalid']]
noa set nonu
call assert_equal([['invalid', 1, 1, 'invalid']], g:options)
call assert_equal(g:opt[0], g:opt[1])
" 8: Setting several global list and number option"
- let g:options=[['list', 0, 1, 'global'], ['number', 0, 1, 'global']]
+ let g:options = [['list', 0, 1, 'global'], ['number', 0, 1, 'global']]
set list nu
call assert_equal([], g:options)
call assert_equal(g:opt[0], g:opt[1])
" 9: don't trigger OptionSet"
- let g:options=[['invalid', 1, 1, 'invalid'], ['invalid', 1, 1, 'invalid']]
+ let g:options = [['invalid', 1, 1, 'invalid'], ['invalid', 1, 1, 'invalid']]
noa set nolist nonu
call assert_equal([['invalid', 1, 1, 'invalid'], ['invalid', 1, 1, 'invalid']], g:options)
call assert_equal(g:opt[0], g:opt[1])
" 10: Setting global acd"
- let g:options=[['autochdir', 0, 1, 'local']]
+ let g:options = [['autochdir', 0, 1, 'local']]
setlocal acd
call assert_equal([], g:options)
call assert_equal(g:opt[0], g:opt[1])
" 11: Setting global autoread (also sets local value)"
- let g:options=[['autoread', 0, 1, 'global']]
+ let g:options = [['autoread', 0, 1, 'global']]
set ar
call assert_equal([], g:options)
call assert_equal(g:opt[0], g:opt[1])
" 12: Setting local autoread"
- let g:options=[['autoread', 1, 1, 'local']]
+ let g:options = [['autoread', 1, 1, 'local']]
setlocal ar
call assert_equal([], g:options)
call assert_equal(g:opt[0], g:opt[1])
" 13: Setting global autoread"
- let g:options=[['autoread', 1, 0, 'global']]
+ let g:options = [['autoread', 1, 0, 'global']]
setglobal invar
call assert_equal([], g:options)
call assert_equal(g:opt[0], g:opt[1])
" 14: Setting option backspace through :let"
- let g:options=[['backspace', '', 'eol,indent,start', 'global']]
- let &bs="eol,indent,start"
+ let g:options = [['backspace', '', 'eol,indent,start', 'global']]
+ let &bs = "eol,indent,start"
call assert_equal([], g:options)
call assert_equal(g:opt[0], g:opt[1])
" 15: Setting option backspace through setbufvar()"
- let g:options=[['backup', 0, 1, 'local']]
+ let g:options = [['backup', 0, 1, 'local']]
" try twice, first time, shouldn't trigger because option name is invalid,
" second time, it should trigger
call assert_fails("call setbufvar(1, '&l:bk', 1)", "E355")
@@ -632,13 +655,13 @@ func Test_OptionSet()
call assert_equal(g:opt[0], g:opt[1])
" 16: Setting number option using setwinvar"
- let g:options=[['number', 0, 1, 'local']]
+ let g:options = [['number', 0, 1, 'local']]
call setwinvar(0, '&number', 1)
call assert_equal([], g:options)
call assert_equal(g:opt[0], g:opt[1])
" 17: Setting key option, shouldn't trigger"
- let g:options=[['key', 'invalid', 'invalid1', 'invalid']]
+ let g:options = [['key', 'invalid', 'invalid1', 'invalid']]
setlocal key=blah
setlocal key=
call assert_equal([['key', 'invalid', 'invalid1', 'invalid']], g:options)
@@ -646,13 +669,13 @@ func Test_OptionSet()
" 18: Setting string option"
let oldval = &tags
- let g:options=[['tags', oldval, 'tagpath', 'global']]
+ let g:options = [['tags', oldval, 'tagpath', 'global']]
set tags=tagpath
call assert_equal([], g:options)
call assert_equal(g:opt[0], g:opt[1])
" 1l: Resetting string option"
- let g:options=[['tags', 'tagpath', oldval, 'global']]
+ let g:options = [['tags', 'tagpath', oldval, 'global']]
set tags&
call assert_equal([], g:options)
call assert_equal(g:opt[0], g:opt[1])
@@ -672,7 +695,7 @@ func Test_OptionSet_diffmode()
call test_override('starting', 1)
" 18: Changing an option when entering diff mode
new
- au OptionSet diff :let &l:cul=v:option_new
+ au OptionSet diff :let &l:cul = v:option_new
call setline(1, ['buffer 1', 'line2', 'line3', 'line4'])
call assert_equal(0, &l:cul)
@@ -1754,7 +1777,7 @@ func Test_autocmd_CmdWinEnter()
autocmd CmdWinEnter * quit
let winnr = winnr('$')
END
- let filename='XCmdWinEnter'
+ let filename = 'XCmdWinEnter'
call writefile(lines, filename)
let buf = RunVimInTerminal('-S '.filename, #{rows: 6})
diff --git a/src/nvim/testdir/test_ex_mode.vim b/src/nvim/testdir/test_ex_mode.vim
index f70cb261e0..1c645ad0f8 100644
--- a/src/nvim/testdir/test_ex_mode.vim
+++ b/src/nvim/testdir/test_ex_mode.vim
@@ -1,5 +1,8 @@
" Test editing line in Ex mode (see :help Q and :help gQ).
+source check.vim
+source shared.vim
+
" Helper function to test editing line in Q Ex mode
func Ex_Q(cmd)
" Is there a simpler way to test editing Ex line?
@@ -79,4 +82,20 @@ func Test_ex_mode_errors()
quit
endfunc
+func Test_ex_mode_count_overflow()
+ " this used to cause a crash
+ let lines =<< trim END
+ call feedkeys("\<Esc>Q\<CR>")
+ v9|9silent! vi|333333233333y32333333%O
+ call writefile(['done'], 'Xdidexmode')
+ qall!
+ END
+ call writefile(lines, 'Xexmodescript')
+ call assert_equal(1, RunVim([], [], '-e -s -S Xexmodescript -c qa'))
+ call assert_equal(['done'], readfile('Xdidexmode'))
+
+ call delete('Xdidexmode')
+ call delete('Xexmodescript')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index 09fdbf4e20..eb6151fbe1 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -161,6 +161,8 @@ let s:filename_checks = {
\ 'ecd': ['file.ecd'],
\ 'edif': ['file.edf', 'file.edif', 'file.edo'],
\ 'elinks': ['elinks.conf'],
+ \ 'elixir': ['file.ex', 'file.exs', 'mix.lock'],
+ \ 'eelixir': ['file.eex', 'file.leex'],
\ 'elm': ['file.elm'],
\ 'elmfilt': ['filter-rules'],
\ 'epuppet': ['file.epp'],
@@ -257,7 +259,7 @@ let s:filename_checks = {
\ 'jgraph': ['file.jgr'],
\ 'jovial': ['file.jov', 'file.j73', 'file.jovial'],
\ 'jproperties': ['file.properties', 'file.properties_xx', 'file.properties_xx_xx', 'some.properties_xx_xx_file'],
- \ 'json': ['file.json', 'file.jsonp', 'file.webmanifest', 'Pipfile.lock'],
+ \ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb'],
\ 'jsp': ['file.jsp'],
\ 'kconfig': ['Kconfig', 'Kconfig.debug', 'Kconfig.file'],
\ 'kivy': ['file.kv'],
@@ -422,7 +424,7 @@ let s:filename_checks = {
\ 'sass': ['file.sass'],
\ 'sather': ['file.sa'],
\ 'sbt': ['file.sbt'],
- \ 'scala': ['file.scala'],
+ \ 'scala': ['file.scala', 'file.sc'],
\ 'scheme': ['file.scm', 'file.ss', 'file.rkt'],
\ 'scilab': ['file.sci', 'file.sce'],
\ 'screen': ['.screenrc', 'screenrc'],
@@ -765,5 +767,41 @@ func Test_pp_file()
filetype off
endfunc
+func Test_ex_file()
+ filetype on
+
+ call writefile(['arbitrary content'], 'Xfile.ex')
+ split Xfile.ex
+ call assert_equal('elixir', &filetype)
+ bwipe!
+ let g:filetype_euphoria = 'euphoria4'
+ split Xfile.ex
+ call assert_equal('euphoria4', &filetype)
+ bwipe!
+ unlet g:filetype_euphoria
+
+ call writefile(['-- filetype euphoria comment'], 'Xfile.ex')
+ split Xfile.ex
+ call assert_equal('euphoria3', &filetype)
+ bwipe!
+
+ call writefile(['--filetype euphoria comment'], 'Xfile.ex')
+ split Xfile.ex
+ call assert_equal('euphoria3', &filetype)
+ bwipe!
+
+ call writefile(['ifdef '], 'Xfile.ex')
+ split Xfile.ex
+ call assert_equal('euphoria3', &filetype)
+ bwipe!
+
+ call writefile(['include '], 'Xfile.ex')
+ split Xfile.ex
+ call assert_equal('euphoria3', &filetype)
+ bwipe!
+
+ call delete('Xfile.ex')
+ filetype off
+endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim
index fcdf888b96..2cc5b47cb0 100644
--- a/src/nvim/testdir/test_fold.vim
+++ b/src/nvim/testdir/test_fold.vim
@@ -796,6 +796,26 @@ func Test_fold_delete_first_line()
set foldmethod&
endfunc
+func Test_undo_fold_deletion()
+ new
+ set fdm=marker
+ let lines =<< trim END
+ " {{{
+ " }}}1
+ " {{{
+ END
+ call setline(1, lines)
+ 3d
+ g/"/d
+ undo
+ redo
+ " eval getline(1, '$')->assert_equal([''])
+ eval assert_equal(getline(1, '$'), [''])
+
+ set fdm&vim
+ bwipe!
+endfunc
+
" this was crashing
func Test_move_no_folds()
new
diff --git a/src/nvim/testdir/test_global.vim b/src/nvim/testdir/test_global.vim
index 2de2c412de..8edc9c2608 100644
--- a/src/nvim/testdir/test_global.vim
+++ b/src/nvim/testdir/test_global.vim
@@ -36,4 +36,8 @@ func Test_global_error()
call assert_fails('g/\(/y', 'E476:')
endfunc
+func Test_wrong_delimiter()
+ call assert_fails('g x^bxd', 'E146:')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_let.vim b/src/nvim/testdir/test_let.vim
index a5cbd8f6a6..6cb736a38a 100644
--- a/src/nvim/testdir/test_let.vim
+++ b/src/nvim/testdir/test_let.vim
@@ -126,11 +126,16 @@ endfunction
func s:set_varg7(...) abort
let b = a:000
- call add(b, 1)
+ let b += [1]
endfunction
func s:set_varg8(...) abort
let b = a:000
+ call add(b, 1)
+endfunction
+
+func s:set_varg9(...) abort
+ let b = a:000
let b[0][0] = 1
endfunction
@@ -142,7 +147,8 @@ func Test_let_varg_fail()
call s:set_varg5([0])
call assert_fails('call s:set_varg6(1)', 'E742:')
call assert_fails('call s:set_varg7(1)', 'E742:')
- call s:set_varg8([0])
+ call assert_fails('call s:set_varg8(1)', 'E742:')
+ call s:set_varg9([0])
endfunction
func Test_let_utf8_environment()
diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim
index 4a00999c45..5c413d1e16 100644
--- a/src/nvim/testdir/test_normal.vim
+++ b/src/nvim/testdir/test_normal.vim
@@ -1111,161 +1111,6 @@ func Test_normal18_z_fold()
bw!
endfunc
-func Test_normal19_z_spell()
- if !has("spell") || !has('syntax')
- return
- endif
- new
- call append(0, ['1 good', '2 goood', '3 goood'])
- set spell spellfile=./Xspellfile.add spelllang=en
- let oldlang=v:lang
- lang C
-
- " Test for zg
- 1
- norm! ]s
- call assert_equal('2 goood', getline('.'))
- norm! zg
- 1
- let a=execute('unsilent :norm! ]s')
- call assert_equal('1 good', getline('.'))
- call assert_equal('search hit BOTTOM, continuing at TOP', a[1:])
- let cnt=readfile('./Xspellfile.add')
- call assert_equal('goood', cnt[0])
-
- " Test for zw
- 2
- norm! $zw
- 1
- norm! ]s
- call assert_equal('2 goood', getline('.'))
- let cnt=readfile('./Xspellfile.add')
- call assert_equal('#oood', cnt[0])
- call assert_equal('goood/!', cnt[1])
-
- " Test for zg in visual mode
- let a=execute('unsilent :norm! V$zg')
- call assert_equal("Word '2 goood' added to ./Xspellfile.add", a[1:])
- 1
- norm! ]s
- call assert_equal('3 goood', getline('.'))
- let cnt=readfile('./Xspellfile.add')
- call assert_equal('2 goood', cnt[2])
- " Remove "2 good" from spellfile
- 2
- let a=execute('unsilent norm! V$zw')
- call assert_equal("Word '2 goood' added to ./Xspellfile.add", a[1:])
- let cnt=readfile('./Xspellfile.add')
- call assert_equal('2 goood/!', cnt[3])
-
- " Test for zG
- let a=execute('unsilent norm! V$zG')
- call assert_match("Word '2 goood' added to .*", a)
- let fname=matchstr(a, 'to\s\+\zs\f\+$')
- let fname=Fix_truncated_tmpfile(fname)
- let cnt=readfile(fname)
- call assert_equal('2 goood', cnt[0])
-
- " Test for zW
- let a=execute('unsilent norm! V$zW')
- call assert_match("Word '2 goood' added to .*", a)
- let cnt=readfile(fname)
- call assert_equal('# goood', cnt[0])
- call assert_equal('2 goood/!', cnt[1])
-
- " Test for zuW
- let a=execute('unsilent norm! V$zuW')
- call assert_match("Word '2 goood' removed from .*", a)
- let cnt=readfile(fname)
- call assert_equal('# goood', cnt[0])
- call assert_equal('# goood/!', cnt[1])
-
- " Test for zuG
- let a=execute('unsilent norm! $zG')
- call assert_match("Word 'goood' added to .*", a)
- let cnt=readfile(fname)
- call assert_equal('# goood', cnt[0])
- call assert_equal('# goood/!', cnt[1])
- call assert_equal('goood', cnt[2])
- let a=execute('unsilent norm! $zuG')
- let cnt=readfile(fname)
- call assert_match("Word 'goood' removed from .*", a)
- call assert_equal('# goood', cnt[0])
- call assert_equal('# goood/!', cnt[1])
- call assert_equal('#oood', cnt[2])
- " word not found in wordlist
- let a=execute('unsilent norm! V$zuG')
- let cnt=readfile(fname)
- call assert_match("", a)
- call assert_equal('# goood', cnt[0])
- call assert_equal('# goood/!', cnt[1])
- call assert_equal('#oood', cnt[2])
-
- " Test for zug
- call delete('./Xspellfile.add')
- 2
- let a=execute('unsilent norm! $zg')
- let cnt=readfile('./Xspellfile.add')
- call assert_equal('goood', cnt[0])
- let a=execute('unsilent norm! $zug')
- call assert_match("Word 'goood' removed from \./Xspellfile.add", a)
- let cnt=readfile('./Xspellfile.add')
- call assert_equal('#oood', cnt[0])
- " word not in wordlist
- let a=execute('unsilent norm! V$zug')
- call assert_match('', a)
- let cnt=readfile('./Xspellfile.add')
- call assert_equal('#oood', cnt[0])
-
- " Test for zuw
- call delete('./Xspellfile.add')
- 2
- let a=execute('unsilent norm! Vzw')
- let cnt=readfile('./Xspellfile.add')
- call assert_equal('2 goood/!', cnt[0])
- let a=execute('unsilent norm! Vzuw')
- call assert_match("Word '2 goood' removed from \./Xspellfile.add", a)
- let cnt=readfile('./Xspellfile.add')
- call assert_equal('# goood/!', cnt[0])
- " word not in wordlist
- let a=execute('unsilent norm! $zug')
- call assert_match('', a)
- let cnt=readfile('./Xspellfile.add')
- call assert_equal('# goood/!', cnt[0])
-
- " add second entry to spellfile setting
- set spellfile=./Xspellfile.add,./Xspellfile2.add
- call delete('./Xspellfile.add')
- 2
- let a=execute('unsilent norm! $2zg')
- let cnt=readfile('./Xspellfile2.add')
- call assert_match("Word 'goood' added to ./Xspellfile2.add", a)
- call assert_equal('goood', cnt[0])
-
- " Test for :spellgood!
- let temp = execute(':spe!0/0')
- call assert_match('Invalid region', temp)
- let spellfile = matchstr(temp, 'Invalid region nr in \zs.*\ze line \d: 0')
- call assert_equal(['# goood', '# goood/!', '#oood', '0/0'], readfile(spellfile))
- call delete(spellfile)
-
- " clean up
- exe "lang" oldlang
- call delete("./Xspellfile.add")
- call delete("./Xspellfile2.add")
- call delete("./Xspellfile.add.spl")
- call delete("./Xspellfile2.add.spl")
-
- " zux -> no-op
- 2
- norm! $zux
- call assert_equal([], glob('Xspellfile.add',0,1))
- call assert_equal([], glob('Xspellfile2.add',0,1))
-
- set spellfile=
- bw!
-endfunc
-
func Test_normal20_exmode()
if !has("unix")
" Reading from redirected file doesn't work on MS-Windows
diff --git a/src/nvim/testdir/test_number.vim b/src/nvim/testdir/test_number.vim
index 81326bce14..eaabe3f67e 100644
--- a/src/nvim/testdir/test_number.vim
+++ b/src/nvim/testdir/test_number.vim
@@ -1,5 +1,6 @@
" Test for 'number' and 'relativenumber'
+source check.vim
source view_util.vim
func s:screen_lines(start, end) abort
@@ -263,3 +264,27 @@ func Test_relativenumber_uninitialised()
redraw
bwipe!
endfunc
+
+" Test for displaying line numbers with 'rightleft'
+func Test_number_rightleft()
+ CheckFeature rightleft
+ new
+ setlocal number
+ setlocal rightleft
+ call setline(1, range(1, 1000))
+ normal! 9Gzt
+ redraw!
+ call assert_match('^\s\+9 9$', Screenline(1))
+ normal! 10Gzt
+ redraw!
+ call assert_match('^\s\+01 10$', Screenline(1))
+ normal! 100Gzt
+ redraw!
+ call assert_match('^\s\+001 100$', Screenline(1))
+ normal! 1000Gzt
+ redraw!
+ call assert_match('^\s\+0001 1000$', Screenline(1))
+ bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index bf15f7f52b..6bd64caa6c 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -891,7 +891,7 @@ func Test_efm1()
Xtestfile:9: parse error before `asd'
make: *** [vim] Error 1
in file "Xtestfile" linenr 10: there is an error
-
+
2 returned
"Xtestfile", line 11 col 1; this is an error
"Xtestfile", line 12 col 2; this is another error
@@ -914,7 +914,7 @@ func Test_efm1()
x should be a dot
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 20
^
-
+
Does anyone know what is the problem and how to correction it?
"Xtestfile", line 21 col 9: What is the title of the quickfix window?
"Xtestfile", line 22 col 9: What is the title of the quickfix window?
@@ -2692,6 +2692,28 @@ func Test_cwindow_jump()
set efm&vim
endfunc
+func Test_cwindow_highlight()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, ['some', 'text', 'with', 'matches'])
+ write XCwindow
+ vimgrep e XCwindow
+ redraw
+ cwindow 4
+ END
+ call writefile(lines, 'XtestCwindow')
+ let buf = RunVimInTerminal('-S XtestCwindow', #{rows: 12})
+ call VerifyScreenDump(buf, 'Test_quickfix_cwindow_1', {})
+ call term_sendkeys(buf, ":cnext\<CR>")
+ call VerifyScreenDump(buf, 'Test_quickfix_cwindow_2', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('XtestCwindow')
+ call delete('XCwindow')
+endfunc
+
func XvimgrepTests(cchar)
call s:setup_commands(a:cchar)
@@ -3461,12 +3483,13 @@ func Xgetlist_empty_tests(cchar)
if a:cchar == 'c'
call assert_equal({'context' : '', 'id' : 0, 'idx' : 0,
\ 'items' : [], 'nr' : 0, 'size' : 0,
- \ 'title' : '', 'winid' : 0, 'changedtick': 0},
- \ g:Xgetlist({'all' : 0}))
+ \ 'title' : '', 'winid' : 0, 'changedtick': 0,
+ \ 'quickfixtextfunc' : ''}, g:Xgetlist({'all' : 0}))
else
call assert_equal({'context' : '', 'id' : 0, 'idx' : 0,
\ 'items' : [], 'nr' : 0, 'size' : 0, 'title' : '',
- \ 'winid' : 0, 'changedtick': 0, 'filewinid' : 0},
+ \ 'winid' : 0, 'changedtick': 0, 'filewinid' : 0,
+ \ 'quickfixtextfunc' : ''},
\ g:Xgetlist({'all' : 0}))
endif
@@ -3504,11 +3527,13 @@ func Xgetlist_empty_tests(cchar)
if a:cchar == 'c'
call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [],
\ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0,
+ \ 'quickfixtextfunc' : '',
\ 'changedtick' : 0}, g:Xgetlist({'id' : qfid, 'all' : 0}))
else
call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [],
\ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0,
- \ 'changedtick' : 0, 'filewinid' : 0},
+ \ 'changedtick' : 0, 'filewinid' : 0,
+ \ 'quickfixtextfunc' : ''},
\ g:Xgetlist({'id' : qfid, 'all' : 0}))
endif
@@ -3525,12 +3550,13 @@ func Xgetlist_empty_tests(cchar)
if a:cchar == 'c'
call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [],
\ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0,
- \ 'changedtick' : 0}, g:Xgetlist({'nr' : 5, 'all' : 0}))
+ \ 'changedtick' : 0,
+ \ 'quickfixtextfunc' : ''}, g:Xgetlist({'nr' : 5, 'all' : 0}))
else
call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [],
\ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0,
- \ 'changedtick' : 0, 'filewinid' : 0},
- \ g:Xgetlist({'nr' : 5, 'all' : 0}))
+ \ 'changedtick' : 0, 'filewinid' : 0,
+ \ 'quickfixtextfunc' : ''}, g:Xgetlist({'nr' : 5, 'all' : 0}))
endif
endfunc
@@ -4972,6 +4998,9 @@ func Xtest_qftextfunc(cchar)
set efm=%f:%l:%c:%m
set quickfixtextfunc=Tqfexpr
+ call assert_equal('Tqfexpr', &quickfixtextfunc)
+ call assert_equal('',
+ \ g:Xgetlist({'quickfixtextfunc' : 1}).quickfixtextfunc)
Xexpr ['F1:10:2:green', 'F1:20:4:blue']
Xwindow
call assert_equal('F1-L10C2-green', getline(1))
@@ -5008,12 +5037,15 @@ func Xtest_qftextfunc(cchar)
call assert_equal('Line 10, Col 2', getline(1))
call assert_equal('Line 20, Col 4', getline(2))
Xclose
+ call assert_equal(function('PerQfText'),
+ \ g:Xgetlist({'quickfixtextfunc' : 1}).quickfixtextfunc)
" Add entries to the list when the quickfix buffer is hidden
Xaddexpr ['F1:30:6:red']
Xwindow
call assert_equal('Line 30, Col 6', getline(3))
Xclose
call g:Xsetlist([], 'r', {'quickfixtextfunc' : ''})
+ call assert_equal('', g:Xgetlist({'quickfixtextfunc' : 1}).quickfixtextfunc)
set quickfixtextfunc&
delfunc PerQfText
@@ -5052,12 +5084,53 @@ func Xtest_qftextfunc(cchar)
" \ 'E730:')
Xexpr ['F1:10:2:green', 'F1:20:4:blue', 'F1:30:6:red']
call assert_fails('Xwindow', 'E730:')
- call assert_equal(['one', 'F1|20 col 4| blue', 'two'], getline(1, '$'))
+ call assert_equal(['one', 'F1|20 col 4| blue', 'F1|30 col 6| red'],
+ \ getline(1, '$'))
Xclose
set quickfixtextfunc&
delfunc Xqftext
delfunc Xqftext2
+
+ " set the global option to a lambda function
+ set quickfixtextfunc={d\ ->\ map(g:Xgetlist({'id'\ :\ d.id,\ 'items'\ :\ 1}).items[d.start_idx-1:d.end_idx-1],\ 'v:val.text')}
+ Xexpr ['F1:10:2:green', 'F1:20:4:blue']
+ Xwindow
+ call assert_equal(['green', 'blue'], getline(1, '$'))
+ Xclose
+ call assert_equal("{d -> map(g:Xgetlist({'id' : d.id, 'items' : 1}).items[d.start_idx-1:d.end_idx-1], 'v:val.text')}", &quickfixtextfunc)
+ set quickfixtextfunc&
+
+ " use a lambda function that returns an empty list
+ set quickfixtextfunc={d\ ->\ []}
+ Xexpr ['F1:10:2:green', 'F1:20:4:blue']
+ Xwindow
+ call assert_equal(['F1|10 col 2| green', 'F1|20 col 4| blue'],
+ \ getline(1, '$'))
+ Xclose
+ set quickfixtextfunc&
+
+ " use a lambda function that returns a list with empty strings
+ set quickfixtextfunc={d\ ->\ ['',\ '']}
+ Xexpr ['F1:10:2:green', 'F1:20:4:blue']
+ Xwindow
+ call assert_equal(['F1|10 col 2| green', 'F1|20 col 4| blue'],
+ \ getline(1, '$'))
+ Xclose
+ set quickfixtextfunc&
+
+ " set the per-quickfix list text function to a lambda function
+ call g:Xsetlist([], ' ',
+ \ {'quickfixtextfunc' :
+ \ {d -> map(g:Xgetlist({'id' : d.id, 'items' : 1}).items[d.start_idx-1:d.end_idx-1],
+ \ "'Line ' .. v:val.lnum .. ', Col ' .. v:val.col")}})
+ Xaddexpr ['F1:10:2:green', 'F1:20:4:blue']
+ Xwindow
+ call assert_equal('Line 10, Col 2', getline(1))
+ call assert_equal('Line 20, Col 4', getline(2))
+ Xclose
+ call assert_match("function('<lambda>\\d\\+')", string(g:Xgetlist({'quickfixtextfunc' : 1}).quickfixtextfunc))
+ call g:Xsetlist([], 'f')
endfunc
func Test_qftextfunc()
@@ -5065,4 +5138,62 @@ func Test_qftextfunc()
call Xtest_qftextfunc('l')
endfunc
+" Test for updating a location list for some other window and check that
+" 'qftextfunc' uses the correct location list.
+func Test_qftextfunc_other_loclist()
+ %bw!
+ call setloclist(0, [], 'f')
+
+ " create a window and a location list for it and open the location list
+ " window
+ lexpr ['F1:10:12:one', 'F1:20:14:two']
+ let w1_id = win_getid()
+ call setloclist(0, [], ' ',
+ \ {'lines': ['F1:10:12:one', 'F1:20:14:two'],
+ \ 'quickfixtextfunc':
+ \ {d -> map(getloclist(d.winid, {'id' : d.id,
+ \ 'items' : 1}).items[d.start_idx-1:d.end_idx-1],
+ \ "'Line ' .. v:val.lnum .. ', Col ' .. v:val.col")}})
+ lwindow
+ let w2_id = win_getid()
+
+ " create another window and a location list for it and open the location
+ " list window
+ topleft new
+ let w3_id = win_getid()
+ call setloclist(0, [], ' ',
+ \ {'lines': ['F2:30:32:eleven', 'F2:40:34:twelve'],
+ \ 'quickfixtextfunc':
+ \ {d -> map(getloclist(d.winid, {'id' : d.id,
+ \ 'items' : 1}).items[d.start_idx-1:d.end_idx-1],
+ \ "'Ligne ' .. v:val.lnum .. ', Colonne ' .. v:val.col")}})
+ lwindow
+ let w4_id = win_getid()
+
+ topleft new
+ lexpr ['F3:50:52:green', 'F3:60:54:blue']
+ let w5_id = win_getid()
+
+ " change the location list for some other window
+ call setloclist(0, [], 'r', {'lines': ['F3:55:56:aaa', 'F3:57:58:bbb']})
+ call setloclist(w1_id, [], 'r', {'lines': ['F1:62:63:bbb', 'F1:64:65:ccc']})
+ call setloclist(w3_id, [], 'r', {'lines': ['F2:76:77:ddd', 'F2:78:79:eee']})
+ call assert_equal(['Line 62, Col 63', 'Line 64, Col 65'],
+ \ getbufline(winbufnr(w2_id), 1, '$'))
+ call assert_equal(['Ligne 76, Colonne 77', 'Ligne 78, Colonne 79'],
+ \ getbufline(winbufnr(w4_id), 1, '$'))
+ call setloclist(w2_id, [], 'r', {'lines': ['F1:32:33:fff', 'F1:34:35:ggg']})
+ call setloclist(w4_id, [], 'r', {'lines': ['F2:46:47:hhh', 'F2:48:49:jjj']})
+ call assert_equal(['Line 32, Col 33', 'Line 34, Col 35'],
+ \ getbufline(winbufnr(w2_id), 1, '$'))
+ call assert_equal(['Ligne 46, Colonne 47', 'Ligne 48, Colonne 49'],
+ \ getbufline(winbufnr(w4_id), 1, '$'))
+
+ call win_gotoid(w5_id)
+ lwindow
+ call assert_equal(['F3|55 col 56| aaa', 'F3|57 col 58| bbb'],
+ \ getline(1, '$'))
+ %bw!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim
index ab8a998bb8..e525d06ea2 100644
--- a/src/nvim/testdir/test_spell.vim
+++ b/src/nvim/testdir/test_spell.vim
@@ -106,11 +106,14 @@ foobar/?
set spelllang=Xwords.spl
call assert_equal(['foobar', 'rare'], spellbadword('foo foobar'))
- " Typo should not be detected without the 'spell' option.
+ " Typo should be detected even without the 'spell' option.
set spelllang=en_gb nospell
call assert_equal(['', ''], spellbadword('centre'))
- call assert_equal(['', ''], spellbadword('My bycycle.'))
- call assert_equal(['', ''], spellbadword('A sentence. another sentence'))
+ call assert_equal(['bycycle', 'bad'], spellbadword('My bycycle.'))
+ call assert_equal(['another', 'caps'], spellbadword('A sentence. another sentence'))
+
+ set spelllang=
+ call assert_fails("call spellbadword('maxch')", 'E756:')
call delete('Xwords.spl')
call delete('Xwords')
@@ -172,6 +175,183 @@ func Test_spellreall()
bwipe!
endfunc
+" Test spellsuggest({word} [, {max} [, {capital}]])
+func Test_spellsuggest()
+ " Verify suggestions are given even when spell checking is not enabled.
+ set nospell
+ call assert_equal(['march', 'March'], spellsuggest('marrch', 2))
+
+ set spell
+
+ " With 1 argument.
+ call assert_equal(['march', 'March'], spellsuggest('marrch')[0:1])
+
+ " With 2 arguments.
+ call assert_equal(['march', 'March'], spellsuggest('marrch', 2))
+
+ " With 3 arguments.
+ call assert_equal(['march'], spellsuggest('marrch', 1, 0))
+ call assert_equal(['March'], spellsuggest('marrch', 1, 1))
+
+ " Test with digits and hyphen.
+ call assert_equal('Carbon-14', spellsuggest('Carbon-15')[0])
+
+ " Comment taken from spellsuggest.c explains the following test cases:
+ "
+ " If there are more UPPER than lower case letters suggest an
+ " ALLCAP word. Otherwise, if the first letter is UPPER then
+ " suggest ONECAP. Exception: "ALl" most likely should be "All",
+ " require three upper case letters.
+ call assert_equal(['THIRD', 'third'], spellsuggest('thIRD', 2))
+ call assert_equal(['third', 'THIRD'], spellsuggest('tHIrd', 2))
+ call assert_equal(['Third'], spellsuggest('THird', 1))
+ call assert_equal(['All'], spellsuggest('ALl', 1))
+
+ call assert_fails("call spellsuggest('maxch', [])", 'E745:')
+ call assert_fails("call spellsuggest('maxch', 2, [])", 'E745:')
+
+ set spelllang=
+ call assert_fails("call spellsuggest('maxch')", 'E756:')
+ set spelllang&
+
+ set spell&
+endfunc
+
+" Test 'spellsuggest' option with methods fast, best and double.
+func Test_spellsuggest_option_methods()
+ set spell
+
+ for e in ['utf-8']
+ exe 'set encoding=' .. e
+
+ set spellsuggest=fast
+ call assert_equal(['Stick', 'Stitch'], spellsuggest('Stich', 2), e)
+
+ " With best or double option, "Stitch" should become the top suggestion
+ " because of better phonetic matching.
+ set spellsuggest=best
+ call assert_equal(['Stitch', 'Stick'], spellsuggest('Stich', 2), e)
+
+ set spellsuggest=double
+ call assert_equal(['Stitch', 'Stick'], spellsuggest('Stich', 2), e)
+ endfor
+
+ set spell& spellsuggest& encoding&
+endfunc
+
+" Test 'spellsuggest' option with value file:{filename}
+func Test_spellsuggest_option_file()
+ set spell spellsuggest=file:Xspellsuggest
+ call writefile(['emacs/vim',
+ \ 'theribal/terrible',
+ \ 'teribal/terrrible',
+ \ 'terribal'],
+ \ 'Xspellsuggest')
+
+ call assert_equal(['vim'], spellsuggest('emacs', 2))
+ call assert_equal(['terrible'], spellsuggest('theribal',2))
+
+ " If the suggestion is misspelled (*terrrible* with 3 r),
+ " it should not be proposed.
+ " The entry for "terribal" should be ignored because of missing slash.
+ call assert_equal([], spellsuggest('teribal', 2))
+ call assert_equal([], spellsuggest('terribal', 2))
+
+ set spell spellsuggest=best,file:Xspellsuggest
+ call assert_equal(['vim', 'Emacs'], spellsuggest('emacs', 2))
+ call assert_equal(['terrible', 'tribal'], spellsuggest('theribal', 2))
+ call assert_equal(['tribal'], spellsuggest('teribal', 1))
+ call assert_equal(['tribal'], spellsuggest('terribal', 1))
+
+ call delete('Xspellsuggest')
+ call assert_fails("call spellsuggest('vim')", "E484: Can't open file Xspellsuggest")
+
+ set spellsuggest& spell&
+endfunc
+
+" Test 'spellsuggest' option with value {number}
+" to limit the number of suggestions
+func Test_spellsuggest_option_number()
+ set spell spellsuggest=2,best
+ new
+
+ " We limited the number of suggestions to 2, so selecting
+ " the 1st and 2nd suggestion should correct the word, but
+ " selecting a 3rd suggestion should do nothing.
+ call setline(1, 'A baord')
+ norm $1z=
+ call assert_equal('A board', getline(1))
+
+ call setline(1, 'A baord')
+ norm $2z=
+ call assert_equal('A bard', getline(1))
+
+ call setline(1, 'A baord')
+ norm $3z=
+ call assert_equal('A baord', getline(1))
+
+ let a = execute('norm $z=')
+ call assert_equal(
+ \ "\n"
+ \ .. "Change \"baord\" to:\n"
+ \ .. " 1 \"board\"\n"
+ \ .. " 2 \"bard\"\n"
+ \ .. "Type number and <Enter> or click with the mouse (q or empty cancels): ", a)
+
+ set spell spellsuggest=0
+ call assert_equal("\nSorry, no suggestions", execute('norm $z='))
+
+ " Unlike z=, function spellsuggest(...) should not be affected by the
+ " max number of suggestions (2) set by the 'spellsuggest' option.
+ call assert_equal(['board', 'bard', 'broad'], spellsuggest('baord', 3))
+
+ set spellsuggest& spell&
+ bwipe!
+endfunc
+
+" Test 'spellsuggest' option with value expr:{expr}
+func Test_spellsuggest_option_expr()
+ " A silly 'spellsuggest' function which makes suggestions all uppercase
+ " and makes the score of each suggestion the length of the suggested word.
+ " So shorter suggestions are preferred.
+ func MySuggest()
+ let spellsuggest_save = &spellsuggest
+ set spellsuggest=3,best
+ let result = map(spellsuggest(v:val, 3), "[toupper(v:val), len(v:val)]")
+ let &spellsuggest = spellsuggest_save
+ return result
+ endfunc
+
+ set spell spellsuggest=expr:MySuggest()
+ call assert_equal(['BARD', 'BOARD', 'BROAD'], spellsuggest('baord', 3))
+
+ new
+ call setline(1, 'baord')
+ let a = execute('norm z=')
+ call assert_equal(
+ \ "\n"
+ \ .. "Change \"baord\" to:\n"
+ \ .. " 1 \"BARD\"\n"
+ \ .. " 2 \"BOARD\"\n"
+ \ .. " 3 \"BROAD\"\n"
+ \ .. "Type number and <Enter> or click with the mouse (q or empty cancels): ", a)
+
+ " With verbose, z= should show the score i.e. word length with
+ " our SpellSuggest() function.
+ set verbose=1
+ let a = execute('norm z=')
+ call assert_equal(
+ \ "\n"
+ \ .. "Change \"baord\" to:\n"
+ \ .. " 1 \"BARD\" (4 - 0)\n"
+ \ .. " 2 \"BOARD\" (5 - 0)\n"
+ \ .. " 3 \"BROAD\" (5 - 0)\n"
+ \ .. "Type number and <Enter> or click with the mouse (q or empty cancels): ", a)
+
+ set spell& spellsuggest& verbose&
+ bwipe!
+endfunc
+
func Test_spellinfo()
throw 'skipped: Nvim does not support enc=latin1'
new
@@ -227,7 +407,7 @@ func Test_zz_basic()
\ )
call assert_equal("gebletegek", soundfold('goobledygoook'))
- call assert_equal("kepereneven", soundfold('koprnven'))
+ call assert_equal("kepereneven", soundfold('kóopërÿnôven'))
call assert_equal("everles gesvets etele", soundfold('oeverloos gezwets edale'))
endfunc
@@ -408,7 +588,7 @@ func Test_zz_sal_and_addition()
mkspell! Xtest Xtest
set spl=Xtest.latin1.spl spell
call assert_equal('kbltykk', soundfold('goobledygoook'))
- call assert_equal('kprnfn', soundfold('koprnven'))
+ call assert_equal('kprnfn', soundfold('kóopërÿnôven'))
call assert_equal('*fls kswts tl', soundfold('oeverloos gezwets edale'))
"also use an addition file
@@ -461,6 +641,34 @@ func Test_zeq_crash()
bwipe!
endfunc
+" Check that z= works even when 'nospell' is set. This test uses one of the
+" tests in Test_spellsuggest_option_number() just to verify that z= basically
+" works and that "E756: Spell checking is not enabled" is not generated.
+func Test_zeq_nospell()
+ new
+ set nospell spellsuggest=1,best
+ call setline(1, 'A baord')
+ try
+ norm $1z=
+ call assert_equal('A board', getline(1))
+ catch
+ call assert_report("Caught exception: " . v:exception)
+ endtry
+ set spell& spellsuggest&
+ bwipe!
+endfunc
+
+" Check that "E756: Spell checking is not possible" is reported when z= is
+" executed and 'spelllang' is empty.
+func Test_zeq_no_spelllang()
+ new
+ set spelllang= spellsuggest=1,best
+ call setline(1, 'A baord')
+ call assert_fails('normal $1z=', 'E756:')
+ set spelllang& spellsuggest&
+ bwipe!
+endfunc
+
" Check handling a word longer than MAXWLEN.
func Test_spell_long_word()
set enc=utf-8
diff --git a/src/nvim/testdir/test_spell_utf8.vim b/src/nvim/testdir/test_spell_utf8.vim
new file mode 100644
index 0000000000..cafdb97f28
--- /dev/null
+++ b/src/nvim/testdir/test_spell_utf8.vim
@@ -0,0 +1,773 @@
+" Test for spell checking with 'encoding' set to utf-8
+
+source check.vim
+CheckFeature spell
+
+scriptencoding utf-8
+
+func TearDown()
+ set nospell
+ call delete('Xtest.aff')
+ call delete('Xtest.dic')
+ call delete('Xtest.utf-8.add')
+ call delete('Xtest.utf-8.add.spl')
+ call delete('Xtest.utf-8.spl')
+ call delete('Xtest.utf-8.sug')
+endfunc
+
+let g:test_data_aff1 = [
+ \"SET ISO8859-1",
+ \"TRY esianrtolcdugmphbyfvkwjkqxz-ëéèêïîäàâöüû'ESIANRTOLCDUGMPHBYFVKWJKQXZ",
+ \"",
+ \"FOL àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþßÿ",
+ \"LOW àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþßÿ",
+ \"UPP ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßÿ",
+ \"",
+ \"SOFOFROM abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xBF",
+ \"SOFOTO ebctefghejklnnepkrstevvkesebctefghejklnnepkrstevvkeseeeeeeeceeeeeeeedneeeeeeeeeeepseeeeeeeeceeeeeeeedneeeeeeeeeeep?",
+ \"",
+ \"MIDWORD\t'-",
+ \"",
+ \"KEP =",
+ \"RAR ?",
+ \"BAD !",
+ \"",
+ \"PFX I N 1",
+ \"PFX I 0 in .",
+ \"",
+ \"PFX O Y 1",
+ \"PFX O 0 out .",
+ \"",
+ \"SFX S Y 2",
+ \"SFX S 0 s [^s]",
+ \"SFX S 0 es s",
+ \"",
+ \"SFX N N 3",
+ \"SFX N 0 en [^n]",
+ \"SFX N 0 nen n",
+ \"SFX N 0 n .",
+ \"",
+ \"REP 3",
+ \"REP g ch",
+ \"REP ch g",
+ \"REP svp s.v.p.",
+ \"",
+ \"MAP 9",
+ \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5",
+ \"MAP e\xE8\xE9\xEA\xEB",
+ \"MAP i\xEC\xED\xEE\xEF",
+ \"MAP o\xF2\xF3\xF4\xF5\xF6",
+ \"MAP u\xF9\xFA\xFB\xFC",
+ \"MAP n\xF1",
+ \"MAP c\xE7",
+ \"MAP y\xFF\xFD",
+ \"MAP s\xDF"
+ \ ]
+let g:test_data_dic1 = [
+ \"123456",
+ \"test/NO",
+ \"# comment",
+ \"wrong",
+ \"Comment",
+ \"OK",
+ \"uk",
+ \"put/ISO",
+ \"the end",
+ \"deol",
+ \"d\xE9\xF4r",
+ \ ]
+let g:test_data_aff2 = [
+ \"SET ISO8859-1",
+ \"",
+ \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
+ \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
+ \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF",
+ \"",
+ \"PFXPOSTPONE",
+ \"",
+ \"MIDWORD\t'-",
+ \"",
+ \"KEP =",
+ \"RAR ?",
+ \"BAD !",
+ \"",
+ \"PFX I N 1",
+ \"PFX I 0 in .",
+ \"",
+ \"PFX O Y 1",
+ \"PFX O 0 out [a-z]",
+ \"",
+ \"SFX S Y 2",
+ \"SFX S 0 s [^s]",
+ \"SFX S 0 es s",
+ \"",
+ \"SFX N N 3",
+ \"SFX N 0 en [^n]",
+ \"SFX N 0 nen n",
+ \"SFX N 0 n .",
+ \"",
+ \"REP 3",
+ \"REP g ch",
+ \"REP ch g",
+ \"REP svp s.v.p.",
+ \"",
+ \"MAP 9",
+ \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5",
+ \"MAP e\xE8\xE9\xEA\xEB",
+ \"MAP i\xEC\xED\xEE\xEF",
+ \"MAP o\xF2\xF3\xF4\xF5\xF6",
+ \"MAP u\xF9\xFA\xFB\xFC",
+ \"MAP n\xF1",
+ \"MAP c\xE7",
+ \"MAP y\xFF\xFD",
+ \"MAP s\xDF",
+ \ ]
+let g:test_data_aff3 = [
+ \"SET ISO8859-1",
+ \"",
+ \"COMPOUNDMIN 3",
+ \"COMPOUNDRULE m*",
+ \"NEEDCOMPOUND x",
+ \ ]
+let g:test_data_dic3 = [
+ \"1234",
+ \"foo/m",
+ \"bar/mx",
+ \"m\xEF/m",
+ \"la/mx",
+ \ ]
+let g:test_data_aff4 = [
+ \"SET ISO8859-1",
+ \"",
+ \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
+ \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
+ \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF",
+ \"",
+ \"COMPOUNDRULE m+",
+ \"COMPOUNDRULE sm*e",
+ \"COMPOUNDRULE sm+",
+ \"COMPOUNDMIN 3",
+ \"COMPOUNDWORDMAX 3",
+ \"COMPOUNDFORBIDFLAG t",
+ \"",
+ \"COMPOUNDSYLMAX 5",
+ \"SYLLABLE a\xE1e\xE9i\xEDo\xF3\xF6\xF5u\xFA\xFC\xFBy/aa/au/ea/ee/ei/ie/oa/oe/oo/ou/uu/ui",
+ \"",
+ \"MAP 9",
+ \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5",
+ \"MAP e\xE8\xE9\xEA\xEB",
+ \"MAP i\xEC\xED\xEE\xEF",
+ \"MAP o\xF2\xF3\xF4\xF5\xF6",
+ \"MAP u\xF9\xFA\xFB\xFC",
+ \"MAP n\xF1",
+ \"MAP c\xE7",
+ \"MAP y\xFF\xFD",
+ \"MAP s\xDF",
+ \"",
+ \"NEEDAFFIX x",
+ \"",
+ \"PFXPOSTPONE",
+ \"",
+ \"MIDWORD '-",
+ \"",
+ \"SFX q N 1",
+ \"SFX q 0 -ok .",
+ \"",
+ \"SFX a Y 2",
+ \"SFX a 0 s .",
+ \"SFX a 0 ize/t .",
+ \"",
+ \"PFX p N 1",
+ \"PFX p 0 pre .",
+ \"",
+ \"PFX P N 1",
+ \"PFX P 0 nou .",
+ \ ]
+let g:test_data_dic4 = [
+ \"1234",
+ \"word/mP",
+ \"util/am",
+ \"pro/xq",
+ \"tomato/m",
+ \"bork/mp",
+ \"start/s",
+ \"end/e",
+ \ ]
+let g:test_data_aff5 = [
+ \"SET ISO8859-1",
+ \"",
+ \"FLAG long",
+ \"",
+ \"NEEDAFFIX !!",
+ \"",
+ \"COMPOUNDRULE ssmm*ee",
+ \"",
+ \"NEEDCOMPOUND xx",
+ \"COMPOUNDPERMITFLAG pp",
+ \"",
+ \"SFX 13 Y 1",
+ \"SFX 13 0 bork .",
+ \"",
+ \"SFX a1 Y 1",
+ \"SFX a1 0 a1 .",
+ \"",
+ \"SFX a\xE9 Y 1",
+ \"SFX a\xE9 0 a\xE9 .",
+ \"",
+ \"PFX zz Y 1",
+ \"PFX zz 0 pre/pp .",
+ \"",
+ \"PFX yy Y 1",
+ \"PFX yy 0 nou .",
+ \ ]
+let g:test_data_dic5 = [
+ \"1234",
+ \"foo/a1a\xE9!!",
+ \"bar/zz13ee",
+ \"start/ss",
+ \"end/eeyy",
+ \"middle/mmxx",
+ \ ]
+let g:test_data_aff6 = [
+ \"SET ISO8859-1",
+ \"",
+ \"FLAG caplong",
+ \"",
+ \"NEEDAFFIX A!",
+ \"",
+ \"COMPOUNDRULE sMm*Ee",
+ \"",
+ \"NEEDCOMPOUND Xx",
+ \"",
+ \"COMPOUNDPERMITFLAG p",
+ \"",
+ \"SFX N3 Y 1",
+ \"SFX N3 0 bork .",
+ \"",
+ \"SFX A1 Y 1",
+ \"SFX A1 0 a1 .",
+ \"",
+ \"SFX A\xE9 Y 1",
+ \"SFX A\xE9 0 a\xE9 .",
+ \"",
+ \"PFX Zz Y 1",
+ \"PFX Zz 0 pre/p .",
+ \ ]
+let g:test_data_dic6 = [
+ \"1234",
+ \"mee/A1A\xE9A!",
+ \"bar/ZzN3Ee",
+ \"lead/s",
+ \"end/Ee",
+ \"middle/MmXx",
+ \ ]
+let g:test_data_aff7 = [
+ \"SET ISO8859-1",
+ \"",
+ \"FLAG num",
+ \"",
+ \"NEEDAFFIX 9999",
+ \"",
+ \"COMPOUNDRULE 2,77*123",
+ \"",
+ \"NEEDCOMPOUND 1",
+ \"COMPOUNDPERMITFLAG 432",
+ \"",
+ \"SFX 61003 Y 1",
+ \"SFX 61003 0 meat .",
+ \"",
+ \"SFX 0 Y 1",
+ \"SFX 0 0 zero .",
+ \"",
+ \"SFX 391 Y 1",
+ \"SFX 391 0 a1 .",
+ \"",
+ \"SFX 111 Y 1",
+ \"SFX 111 0 a\xE9 .",
+ \"",
+ \"PFX 17 Y 1",
+ \"PFX 17 0 pre/432 .",
+ \ ]
+let g:test_data_dic7 = [
+ \"1234",
+ \"mee/0,391,111,9999",
+ \"bar/17,61003,123",
+ \"lead/2",
+ \"tail/123",
+ \"middle/77,1",
+ \ ]
+let g:test_data_aff8 = [
+ \"SET ISO8859-1",
+ \"",
+ \"NOSPLITSUGS",
+ \ ]
+let g:test_data_dic8 = [
+ \"1234",
+ \"foo",
+ \"bar",
+ \"faabar",
+ \ ]
+let g:test_data_aff9 = [
+ \ ]
+let g:test_data_dic9 = [
+ \"1234",
+ \"foo",
+ \"bar",
+ \ ]
+let g:test_data_aff10 = [
+ \"COMPOUNDRULE se",
+ \"COMPOUNDPERMITFLAG p",
+ \"",
+ \"SFX A Y 1",
+ \"SFX A 0 able/Mp .",
+ \"",
+ \"SFX M Y 1",
+ \"SFX M 0 s .",
+ \ ]
+let g:test_data_dic10 = [
+ \"1234",
+ \"drink/As",
+ \"table/e",
+ \ ]
+let g:test_data_aff_sal = [
+ \"SET ISO8859-1",
+ \"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ",
+ \"",
+ \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
+ \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
+ \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF",
+ \"",
+ \"MIDWORD\t'-",
+ \"",
+ \"KEP =",
+ \"RAR ?",
+ \"BAD !",
+ \"",
+ \"PFX I N 1",
+ \"PFX I 0 in .",
+ \"",
+ \"PFX O Y 1",
+ \"PFX O 0 out .",
+ \"",
+ \"SFX S Y 2",
+ \"SFX S 0 s [^s]",
+ \"SFX S 0 es s",
+ \"",
+ \"SFX N N 3",
+ \"SFX N 0 en [^n]",
+ \"SFX N 0 nen n",
+ \"SFX N 0 n .",
+ \"",
+ \"REP 3",
+ \"REP g ch",
+ \"REP ch g",
+ \"REP svp s.v.p.",
+ \"",
+ \"MAP 9",
+ \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5",
+ \"MAP e\xE8\xE9\xEA\xEB",
+ \"MAP i\xEC\xED\xEE\xEF",
+ \"MAP o\xF2\xF3\xF4\xF5\xF6",
+ \"MAP u\xF9\xFA\xFB\xFC",
+ \"MAP n\xF1",
+ \"MAP c\xE7",
+ \"MAP y\xFF\xFD",
+ \"MAP s\xDF",
+ \"",
+ \"SAL AH(AEIOUY)-^ *H",
+ \"SAL AR(AEIOUY)-^ *R",
+ \"SAL A(HR)^ *",
+ \"SAL A^ *",
+ \"SAL AH(AEIOUY)- H",
+ \"SAL AR(AEIOUY)- R",
+ \"SAL A(HR) _",
+ \"SAL \xC0^ *",
+ \"SAL \xC5^ *",
+ \"SAL BB- _",
+ \"SAL B B",
+ \"SAL CQ- _",
+ \"SAL CIA X",
+ \"SAL CH X",
+ \"SAL C(EIY)- S",
+ \"SAL CK K",
+ \"SAL COUGH^ KF",
+ \"SAL CC< C",
+ \"SAL C K",
+ \"SAL DG(EIY) K",
+ \"SAL DD- _",
+ \"SAL D T",
+ \"SAL \xC9< E",
+ \"SAL EH(AEIOUY)-^ *H",
+ \"SAL ER(AEIOUY)-^ *R",
+ \"SAL E(HR)^ *",
+ \"SAL ENOUGH^$ *NF",
+ \"SAL E^ *",
+ \"SAL EH(AEIOUY)- H",
+ \"SAL ER(AEIOUY)- R",
+ \"SAL E(HR) _",
+ \"SAL FF- _",
+ \"SAL F F",
+ \"SAL GN^ N",
+ \"SAL GN$ N",
+ \"SAL GNS$ NS",
+ \"SAL GNED$ N",
+ \"SAL GH(AEIOUY)- K",
+ \"SAL GH _",
+ \"SAL GG9 K",
+ \"SAL G K",
+ \"SAL H H",
+ \"SAL IH(AEIOUY)-^ *H",
+ \"SAL IR(AEIOUY)-^ *R",
+ \"SAL I(HR)^ *",
+ \"SAL I^ *",
+ \"SAL ING6 N",
+ \"SAL IH(AEIOUY)- H",
+ \"SAL IR(AEIOUY)- R",
+ \"SAL I(HR) _",
+ \"SAL J K",
+ \"SAL KN^ N",
+ \"SAL KK- _",
+ \"SAL K K",
+ \"SAL LAUGH^ LF",
+ \"SAL LL- _",
+ \"SAL L L",
+ \"SAL MB$ M",
+ \"SAL MM M",
+ \"SAL M M",
+ \"SAL NN- _",
+ \"SAL N N",
+ \"SAL OH(AEIOUY)-^ *H",
+ \"SAL OR(AEIOUY)-^ *R",
+ \"SAL O(HR)^ *",
+ \"SAL O^ *",
+ \"SAL OH(AEIOUY)- H",
+ \"SAL OR(AEIOUY)- R",
+ \"SAL O(HR) _",
+ \"SAL PH F",
+ \"SAL PN^ N",
+ \"SAL PP- _",
+ \"SAL P P",
+ \"SAL Q K",
+ \"SAL RH^ R",
+ \"SAL ROUGH^ RF",
+ \"SAL RR- _",
+ \"SAL R R",
+ \"SAL SCH(EOU)- SK",
+ \"SAL SC(IEY)- S",
+ \"SAL SH X",
+ \"SAL SI(AO)- X",
+ \"SAL SS- _",
+ \"SAL S S",
+ \"SAL TI(AO)- X",
+ \"SAL TH @",
+ \"SAL TCH-- _",
+ \"SAL TOUGH^ TF",
+ \"SAL TT- _",
+ \"SAL T T",
+ \"SAL UH(AEIOUY)-^ *H",
+ \"SAL UR(AEIOUY)-^ *R",
+ \"SAL U(HR)^ *",
+ \"SAL U^ *",
+ \"SAL UH(AEIOUY)- H",
+ \"SAL UR(AEIOUY)- R",
+ \"SAL U(HR) _",
+ \"SAL V^ W",
+ \"SAL V F",
+ \"SAL WR^ R",
+ \"SAL WH^ W",
+ \"SAL W(AEIOU)- W",
+ \"SAL X^ S",
+ \"SAL X KS",
+ \"SAL Y(AEIOU)- Y",
+ \"SAL ZZ- _",
+ \"SAL Z S",
+ \ ]
+
+func LoadAffAndDic(aff_contents, dic_contents)
+ set enc=utf-8
+ set spellfile=
+ call writefile(a:aff_contents, "Xtest.aff")
+ call writefile(a:dic_contents, "Xtest.dic")
+ " Generate a .spl file from a .dic and .aff file.
+ mkspell! Xtest Xtest
+ " use that spell file
+ set spl=Xtest.utf-8.spl spell
+endfunc
+
+func ListWords()
+ spelldump
+ %yank
+ quit
+ return split(@", "\n")
+endfunc
+
+func TestGoodBadBase()
+ exe '1;/^good:'
+ normal 0f:]s
+ let prevbad = ''
+ let result = []
+ while 1
+ let [bad, a] = spellbadword()
+ if bad == '' || bad == prevbad || bad == 'badend'
+ break
+ endif
+ let prevbad = bad
+ " let lst = bad->spellsuggest(3)
+ let lst = spellsuggest(bad, 3)
+ normal mm
+
+ call add(result, [bad, lst])
+ normal `m]s
+ endwhile
+ return result
+endfunc
+
+func RunGoodBad(good, bad, expected_words, expected_bad_words)
+ %bwipe!
+ call setline(1, ['', "good: ", a:good, a:bad, " badend "])
+ let words = ListWords()
+ call assert_equal(a:expected_words, words[1:-1])
+ let bad_words = TestGoodBadBase()
+ call assert_equal(a:expected_bad_words, bad_words)
+ %bwipe!
+endfunc
+
+func Test_spell_basic()
+ call LoadAffAndDic(g:test_data_aff1, g:test_data_dic1)
+ call RunGoodBad("wrong OK puts. Test the end",
+ \ "bad: inputs comment ok Ok. test d\u00E9\u00F4l end the",
+ \["Comment", "deol", "d\u00E9\u00F4r", "input", "OK", "output", "outputs", "outtest", "put", "puts",
+ \ "test", "testen", "testn", "the end", "uk", "wrong"],
+ \[
+ \ ["bad", ["put", "uk", "OK"]],
+ \ ["inputs", ["input", "puts", "outputs"]],
+ \ ["comment", ["Comment", "outtest", "the end"]],
+ \ ["ok", ["OK", "uk", "put"]],
+ \ ["Ok", ["OK", "Uk", "Put"]],
+ \ ["test", ["Test", "testn", "testen"]],
+ \ ["d\u00E9\u00F4l", ["deol", "d\u00E9\u00F4r", "test"]],
+ \ ["end", ["put", "uk", "test"]],
+ \ ["the", ["put", "uk", "test"]],
+ \ ]
+ \ )
+
+ call assert_equal("gebletegek", soundfold('goobledygoook'))
+ " call assert_equal("kepereneven", 'kóopërÿnôven'->soundfold())
+ call assert_equal("kepereneven", soundfold('kóopërÿnôven'))
+ call assert_equal("everles gesvets etele", soundfold('oeverloos gezwets edale'))
+endfunc
+
+" Postponed prefixes
+func Test_spell_prefixes()
+ call LoadAffAndDic(g:test_data_aff2, g:test_data_dic1)
+ call RunGoodBad("puts",
+ \ "bad: inputs comment ok Ok end the. test d\u00E9\u00F4l",
+ \ ["Comment", "deol", "d\u00E9\u00F4r", "OK", "put", "input", "output", "puts", "outputs", "test", "outtest", "testen", "testn", "the end", "uk", "wrong"],
+ \ [
+ \ ["bad", ["put", "uk", "OK"]],
+ \ ["inputs", ["input", "puts", "outputs"]],
+ \ ["comment", ["Comment"]],
+ \ ["ok", ["OK", "uk", "put"]],
+ \ ["Ok", ["OK", "Uk", "Put"]],
+ \ ["end", ["put", "uk", "deol"]],
+ \ ["the", ["put", "uk", "test"]],
+ \ ["test", ["Test", "testn", "testen"]],
+ \ ["d\u00E9\u00F4l", ["deol", "d\u00E9\u00F4r", "test"]],
+ \ ])
+endfunc
+
+"Compound words
+func Test_spell_compound()
+ throw 'skipped: TODO: '
+ call LoadAffAndDic(g:test_data_aff3, g:test_data_dic3)
+ call RunGoodBad("foo m\u00EF foobar foofoobar barfoo barbarfoo",
+ \ "bad: bar la foom\u00EF barm\u00EF m\u00EFfoo m\u00EFbar m\u00EFm\u00EF lala m\u00EFla lam\u00EF foola labar",
+ \ ["foo", "m\u00EF"],
+ \ [
+ \ ["bad", ["foo", "m\u00EF"]],
+ \ ["bar", ["barfoo", "foobar", "foo"]],
+ \ ["la", ["m\u00EF", "foo"]],
+ \ ["foom\u00EF", ["foo m\u00EF", "foo", "foofoo"]],
+ \ ["barm\u00EF", ["barfoo", "m\u00EF", "barbar"]],
+ \ ["m\u00EFfoo", ["m\u00EF foo", "foo", "foofoo"]],
+ \ ["m\u00EFbar", ["foobar", "barbar", "m\u00EF"]],
+ \ ["m\u00EFm\u00EF", ["m\u00EF m\u00EF", "m\u00EF"]],
+ \ ["lala", []],
+ \ ["m\u00EFla", ["m\u00EF", "m\u00EF m\u00EF"]],
+ \ ["lam\u00EF", ["m\u00EF", "m\u00EF m\u00EF"]],
+ \ ["foola", ["foo", "foobar", "foofoo"]],
+ \ ["labar", ["barbar", "foobar"]],
+ \ ])
+
+ call LoadAffAndDic(g:test_data_aff4, g:test_data_dic4)
+ call RunGoodBad("word util bork prebork start end wordutil wordutils pro-ok bork borkbork borkborkbork borkborkborkbork borkborkborkborkbork tomato tomatotomato startend startword startwordword startwordend startwordwordend startwordwordwordend prebork preborkbork preborkborkbork nouword",
+ \ "bad: wordutilize pro borkborkborkborkborkbork tomatotomatotomato endstart endend startstart wordend wordstart preborkprebork preborkpreborkbork startwordwordwordwordend borkpreborkpreborkbork utilsbork startnouword",
+ \ ["bork", "prebork", "end", "pro-ok", "start", "tomato", "util", "utilize", "utils", "word", "nouword"],
+ \ [
+ \ ["bad", ["end", "bork", "word"]],
+ \ ["wordutilize", ["word utilize", "wordutils", "wordutil"]],
+ \ ["pro", ["bork", "word", "end"]],
+ \ ["borkborkborkborkborkbork", ["bork borkborkborkborkbork", "borkbork borkborkborkbork", "borkborkbork borkborkbork"]],
+ \ ["tomatotomatotomato", ["tomato tomatotomato", "tomatotomato tomato", "tomato tomato tomato"]],
+ \ ["endstart", ["end start", "start"]],
+ \ ["endend", ["end end", "end"]],
+ \ ["startstart", ["start start"]],
+ \ ["wordend", ["word end", "word", "wordword"]],
+ \ ["wordstart", ["word start", "bork start"]],
+ \ ["preborkprebork", ["prebork prebork", "preborkbork", "preborkborkbork"]],
+ \ ["preborkpreborkbork", ["prebork preborkbork", "preborkborkbork", "preborkborkborkbork"]],
+ \ ["startwordwordwordwordend", ["startwordwordwordword end", "startwordwordwordword", "start wordwordwordword end"]],
+ \ ["borkpreborkpreborkbork", ["bork preborkpreborkbork", "bork prebork preborkbork", "bork preborkprebork bork"]],
+ \ ["utilsbork", ["utilbork", "utils bork", "util bork"]],
+ \ ["startnouword", ["start nouword", "startword", "startborkword"]],
+ \ ])
+
+endfunc
+
+" Test affix flags with two characters
+func Test_spell_affix()
+ throw 'skipped: TODO: '
+ call LoadAffAndDic(g:test_data_aff5, g:test_data_dic5)
+ call RunGoodBad("fooa1 fooa\u00E9 bar prebar barbork prebarbork startprebar start end startend startmiddleend nouend",
+ \ "bad: foo fooa2 prabar probarbirk middle startmiddle middleend endstart startprobar startnouend",
+ \ ["bar", "barbork", "end", "fooa1", "fooa\u00E9", "nouend", "prebar", "prebarbork", "start"],
+ \ [
+ \ ["bad", ["bar", "end", "fooa1"]],
+ \ ["foo", ["fooa1", "fooa\u00E9", "bar"]],
+ \ ["fooa2", ["fooa1", "fooa\u00E9", "bar"]],
+ \ ["prabar", ["prebar", "bar", "bar bar"]],
+ \ ["probarbirk", ["prebarbork"]],
+ \ ["middle", []],
+ \ ["startmiddle", ["startmiddleend", "startmiddlebar"]],
+ \ ["middleend", []],
+ \ ["endstart", ["end start", "start"]],
+ \ ["startprobar", ["startprebar", "start prebar", "startbar"]],
+ \ ["startnouend", ["start nouend", "startend"]],
+ \ ])
+
+ call LoadAffAndDic(g:test_data_aff6, g:test_data_dic6)
+ call RunGoodBad("meea1 meea\u00E9 bar prebar barbork prebarbork leadprebar lead end leadend leadmiddleend",
+ \ "bad: mee meea2 prabar probarbirk middle leadmiddle middleend endlead leadprobar",
+ \ ["bar", "barbork", "end", "lead", "meea1", "meea\u00E9", "prebar", "prebarbork"],
+ \ [
+ \ ["bad", ["bar", "end", "lead"]],
+ \ ["mee", ["meea1", "meea\u00E9", "bar"]],
+ \ ["meea2", ["meea1", "meea\u00E9", "lead"]],
+ \ ["prabar", ["prebar", "bar", "leadbar"]],
+ \ ["probarbirk", ["prebarbork"]],
+ \ ["middle", []],
+ \ ["leadmiddle", ["leadmiddleend", "leadmiddlebar"]],
+ \ ["middleend", []],
+ \ ["endlead", ["end lead", "lead", "end end"]],
+ \ ["leadprobar", ["leadprebar", "lead prebar", "leadbar"]],
+ \ ])
+
+ call LoadAffAndDic(g:test_data_aff7, g:test_data_dic7)
+ call RunGoodBad("meea1 meezero meea\u00E9 bar prebar barmeat prebarmeat leadprebar lead tail leadtail leadmiddletail",
+ \ "bad: mee meea2 prabar probarmaat middle leadmiddle middletail taillead leadprobar",
+ \ ["bar", "barmeat", "lead", "meea1", "meea\u00E9", "meezero", "prebar", "prebarmeat", "tail"],
+ \ [
+ \ ["bad", ["bar", "lead", "tail"]],
+ \ ["mee", ["meea1", "meea\u00E9", "bar"]],
+ \ ["meea2", ["meea1", "meea\u00E9", "lead"]],
+ \ ["prabar", ["prebar", "bar", "leadbar"]],
+ \ ["probarmaat", ["prebarmeat"]],
+ \ ["middle", []],
+ \ ["leadmiddle", ["leadmiddlebar"]],
+ \ ["middletail", []],
+ \ ["taillead", ["tail lead", "tail"]],
+ \ ["leadprobar", ["leadprebar", "lead prebar", "leadbar"]],
+ \ ])
+endfunc
+
+func Test_spell_NOSLITSUGS()
+ call LoadAffAndDic(g:test_data_aff8, g:test_data_dic8)
+ call RunGoodBad("foo bar faabar", "bad: foobar barfoo",
+ \ ["bar", "faabar", "foo"],
+ \ [
+ \ ["bad", ["bar", "foo"]],
+ \ ["foobar", ["faabar", "foo bar", "bar"]],
+ \ ["barfoo", ["bar foo", "bar", "foo"]],
+ \ ])
+endfunc
+
+" Numbers
+func Test_spell_Numbers()
+ call LoadAffAndDic(g:test_data_aff9, g:test_data_dic9)
+ call RunGoodBad("0b1011 0777 1234 0x01ff", "",
+ \ ["bar", "foo"],
+ \ [
+ \ ])
+endfunc
+
+" Affix flags
+func Test_spell_affix_flags()
+ throw 'skipped: TODO: '
+ call LoadAffAndDic(g:test_data_aff10, g:test_data_dic10)
+ call RunGoodBad("drink drinkable drinkables drinktable drinkabletable",
+ \ "bad: drinks drinkstable drinkablestable",
+ \ ["drink", "drinkable", "drinkables", "table"],
+ \ [['bad', []],
+ \ ['drinks', ['drink']],
+ \ ['drinkstable', ['drinktable', 'drinkable', 'drink table']],
+ \ ['drinkablestable', ['drinkabletable', 'drinkables table', 'drinkable table']],
+ \ ])
+endfunc
+
+function FirstSpellWord()
+ call feedkeys("/^start:\n", 'tx')
+ normal ]smm
+ let [str, a] = spellbadword()
+ return str
+endfunc
+
+function SecondSpellWord()
+ normal `m]s
+ let [str, a] = spellbadword()
+ return str
+endfunc
+
+" Test with SAL instead of SOFO items; test automatic reloading
+func Test_spell_sal_and_addition()
+ set spellfile=
+ call writefile(g:test_data_dic1, "Xtest.dic")
+ call writefile(g:test_data_aff_sal, "Xtest.aff")
+ mkspell! Xtest Xtest
+ set spl=Xtest.utf-8.spl spell
+ call assert_equal('kbltykk', soundfold('goobledygoook'))
+ call assert_equal('kprnfn', soundfold('kóopërÿnôven'))
+ call assert_equal('*fls kswts tl', soundfold('oeverloos gezwets edale'))
+
+ "also use an addition file
+ call writefile(["/regions=usgbnz", "elequint/2", "elekwint/3"], "Xtest.utf-8.add")
+ mkspell! Xtest.utf-8.add.spl Xtest.utf-8.add
+
+ bwipe!
+ call setline(1, ["start: elequint test elekwint test elekwent asdf"])
+
+ set spellfile=Xtest.utf-8.add
+ call assert_equal("elekwent", FirstSpellWord())
+
+ set spl=Xtest_us.utf-8.spl
+ call assert_equal("elequint", FirstSpellWord())
+ call assert_equal("elekwint", SecondSpellWord())
+
+ set spl=Xtest_gb.utf-8.spl
+ call assert_equal("elekwint", FirstSpellWord())
+ call assert_equal("elekwent", SecondSpellWord())
+
+ set spl=Xtest_nz.utf-8.spl
+ call assert_equal("elequint", FirstSpellWord())
+ call assert_equal("elekwent", SecondSpellWord())
+
+ set spl=Xtest_ca.utf-8.spl
+ call assert_equal("elequint", FirstSpellWord())
+ call assert_equal("elekwint", SecondSpellWord())
+endfunc
+
+func Test_spellfile_value()
+ set spellfile=Xdir/Xtest.utf-8.add
+ set spellfile=Xdir/Xtest.utf-8.add,Xtest_other.add
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_spellfile.vim b/src/nvim/testdir/test_spellfile.vim
new file mode 100644
index 0000000000..729467b556
--- /dev/null
+++ b/src/nvim/testdir/test_spellfile.vim
@@ -0,0 +1,240 @@
+" Test for commands that operate on the spellfile.
+
+source shared.vim
+source check.vim
+
+CheckFeature spell
+CheckFeature syntax
+
+func Test_spell_normal()
+ new
+ call append(0, ['1 good', '2 goood', '3 goood'])
+ set spell spellfile=./Xspellfile.add spelllang=en
+ let oldlang=v:lang
+ lang C
+
+ " Test for zg
+ 1
+ norm! ]s
+ call assert_equal('2 goood', getline('.'))
+ norm! zg
+ 1
+ let a=execute('unsilent :norm! ]s')
+ call assert_equal('1 good', getline('.'))
+ call assert_equal('search hit BOTTOM, continuing at TOP', a[1:])
+ let cnt=readfile('./Xspellfile.add')
+ call assert_equal('goood', cnt[0])
+
+ " Test for zw
+ 2
+ norm! $zw
+ 1
+ norm! ]s
+ call assert_equal('2 goood', getline('.'))
+ let cnt=readfile('./Xspellfile.add')
+ call assert_equal('#oood', cnt[0])
+ call assert_equal('goood/!', cnt[1])
+
+ " Test for :spellrare
+ spellrare rare
+ let cnt=readfile('./Xspellfile.add')
+ call assert_equal(['#oood', 'goood/!', 'rare/?'], cnt)
+
+ " Make sure :spellundo works for rare words.
+ spellundo rare
+ let cnt=readfile('./Xspellfile.add')
+ call assert_equal(['#oood', 'goood/!', '#are/?'], cnt)
+
+ " Test for zg in visual mode
+ let a=execute('unsilent :norm! V$zg')
+ call assert_equal("Word '2 goood' added to ./Xspellfile.add", a[1:])
+ 1
+ norm! ]s
+ call assert_equal('3 goood', getline('.'))
+ let cnt=readfile('./Xspellfile.add')
+ call assert_equal('2 goood', cnt[3])
+ " Remove "2 good" from spellfile
+ 2
+ let a=execute('unsilent norm! V$zw')
+ call assert_equal("Word '2 goood' added to ./Xspellfile.add", a[1:])
+ let cnt=readfile('./Xspellfile.add')
+ call assert_equal('2 goood/!', cnt[4])
+
+ " Test for zG
+ let a=execute('unsilent norm! V$zG')
+ call assert_match("Word '2 goood' added to .*", a)
+ let fname=matchstr(a, 'to\s\+\zs\f\+$')
+ let cnt=readfile(fname)
+ call assert_equal('2 goood', cnt[0])
+
+ " Test for zW
+ let a=execute('unsilent norm! V$zW')
+ call assert_match("Word '2 goood' added to .*", a)
+ let cnt=readfile(fname)
+ call assert_equal('# goood', cnt[0])
+ call assert_equal('2 goood/!', cnt[1])
+
+ " Test for zuW
+ let a=execute('unsilent norm! V$zuW')
+ call assert_match("Word '2 goood' removed from .*", a)
+ let cnt=readfile(fname)
+ call assert_equal('# goood', cnt[0])
+ call assert_equal('# goood/!', cnt[1])
+
+ " Test for zuG
+ let a=execute('unsilent norm! $zG')
+ call assert_match("Word 'goood' added to .*", a)
+ let cnt=readfile(fname)
+ call assert_equal('# goood', cnt[0])
+ call assert_equal('# goood/!', cnt[1])
+ call assert_equal('goood', cnt[2])
+ let a=execute('unsilent norm! $zuG')
+ let cnt=readfile(fname)
+ call assert_match("Word 'goood' removed from .*", a)
+ call assert_equal('# goood', cnt[0])
+ call assert_equal('# goood/!', cnt[1])
+ call assert_equal('#oood', cnt[2])
+ " word not found in wordlist
+ let a=execute('unsilent norm! V$zuG')
+ let cnt=readfile(fname)
+ call assert_match("", a)
+ call assert_equal('# goood', cnt[0])
+ call assert_equal('# goood/!', cnt[1])
+ call assert_equal('#oood', cnt[2])
+
+ " Test for zug
+ call delete('./Xspellfile.add')
+ 2
+ let a=execute('unsilent norm! $zg')
+ let cnt=readfile('./Xspellfile.add')
+ call assert_equal('goood', cnt[0])
+ let a=execute('unsilent norm! $zug')
+ call assert_match("Word 'goood' removed from \./Xspellfile.add", a)
+ let cnt=readfile('./Xspellfile.add')
+ call assert_equal('#oood', cnt[0])
+ " word not in wordlist
+ let a=execute('unsilent norm! V$zug')
+ call assert_match('', a)
+ let cnt=readfile('./Xspellfile.add')
+ call assert_equal('#oood', cnt[0])
+
+ " Test for zuw
+ call delete('./Xspellfile.add')
+ 2
+ let a=execute('unsilent norm! Vzw')
+ let cnt=readfile('./Xspellfile.add')
+ call assert_equal('2 goood/!', cnt[0])
+ let a=execute('unsilent norm! Vzuw')
+ call assert_match("Word '2 goood' removed from \./Xspellfile.add", a)
+ let cnt=readfile('./Xspellfile.add')
+ call assert_equal('# goood/!', cnt[0])
+ " word not in wordlist
+ let a=execute('unsilent norm! $zug')
+ call assert_match('', a)
+ let cnt=readfile('./Xspellfile.add')
+ call assert_equal('# goood/!', cnt[0])
+
+ " add second entry to spellfile setting
+ set spellfile=./Xspellfile.add,./Xspellfile2.add
+ call delete('./Xspellfile.add')
+ 2
+ let a=execute('unsilent norm! $2zg')
+ let cnt=readfile('./Xspellfile2.add')
+ call assert_match("Word 'goood' added to ./Xspellfile2.add", a)
+ call assert_equal('goood', cnt[0])
+
+ " Test for :spellgood!
+ let temp = execute(':spe!0/0')
+ call assert_match('Invalid region', temp)
+ let spellfile = matchstr(temp, 'Invalid region nr in \zs.*\ze line \d: 0')
+ call assert_equal(['# goood', '# goood/!', '#oood', '0/0'], readfile(spellfile))
+
+ " Test for :spellrare!
+ :spellrare! raare
+ call assert_equal(['# goood', '# goood/!', '#oood', '0/0', 'raare/?'], readfile(spellfile))
+ call delete(spellfile)
+
+ " clean up
+ exe "lang" oldlang
+ call delete("./Xspellfile.add")
+ call delete("./Xspellfile2.add")
+ call delete("./Xspellfile.add.spl")
+ call delete("./Xspellfile2.add.spl")
+
+ " zux -> no-op
+ 2
+ norm! $zux
+ call assert_equal([], glob('Xspellfile.add',0,1))
+ call assert_equal([], glob('Xspellfile2.add',0,1))
+
+ set spellfile=
+ bw!
+endfunc
+
+" Test CHECKCOMPOUNDPATTERN (see :help spell-CHECKCOMPOUNDPATTERN)
+func Test_spellfile_CHECKCOMPOUNDPATTERN()
+ call writefile(['4',
+ \ 'one/c',
+ \ 'two/c',
+ \ 'three/c',
+ \ 'four'], 'XtestCHECKCOMPOUNDPATTERN.dic')
+ " Forbid compound words where first word ends with 'wo' and second starts with 'on'.
+ call writefile(['CHECKCOMPOUNDPATTERN 1',
+ \ 'CHECKCOMPOUNDPATTERN wo on',
+ \ 'COMPOUNDFLAG c'], 'XtestCHECKCOMPOUNDPATTERN.aff')
+
+ let output = execute('mkspell! XtestCHECKCOMPOUNDPATTERN-utf8.spl XtestCHECKCOMPOUNDPATTERN')
+ set spell spelllang=XtestCHECKCOMPOUNDPATTERN-utf8.spl
+
+ " Check valid words with and without valid compounds.
+ for goodword in ['one', 'two', 'three', 'four',
+ \ 'oneone', 'onetwo', 'onethree',
+ \ 'twotwo', 'twothree',
+ \ 'threeone', 'threetwo', 'threethree',
+ \ 'onetwothree', 'onethreetwo', 'twothreeone', 'oneoneone']
+ call assert_equal(['', ''], spellbadword(goodword), goodword)
+ endfor
+
+ " Compounds 'twoone' or 'threetwoone' should be forbidden by CHECKCOMPOUNPATTERN.
+ " 'four' does not have the 'c' flag in *.aff file so no compound.
+ " 'five' is not in the *.dic file.
+ for badword in ['five', 'onetwox',
+ \ 'twoone', 'threetwoone',
+ \ 'fourone', 'onefour']
+ call assert_equal([badword, 'bad'], spellbadword(badword))
+ endfor
+
+ set spell& spelllang&
+ call delete('XtestCHECKCOMPOUNDPATTERN.dic')
+ call delete('XtestCHECKCOMPOUNDPATTERN.aff')
+ call delete('XtestCHECKCOMPOUNDPATTERN-utf8.spl')
+endfunc
+
+" Test COMMON (better suggestions with common words, see :help spell-COMMON)
+func Test_spellfile_COMMON()
+ call writefile(['7',
+ \ 'and',
+ \ 'ant',
+ \ 'end',
+ \ 'any',
+ \ 'tee',
+ \ 'the',
+ \ 'ted'], 'XtestCOMMON.dic')
+ call writefile(['COMMON the and'], 'XtestCOMMON.aff')
+
+ let output = execute('mkspell! XtestCOMMON-utf8.spl XtestCOMMON')
+ set spell spelllang=XtestCOMMON-utf8.spl
+
+ " COMMON words 'and' and 'the' should be the top suggestions.
+ call assert_equal(['and', 'ant'], spellsuggest('anr', 2))
+ call assert_equal(['and', 'end'], spellsuggest('ond', 2))
+ call assert_equal(['the', 'ted'], spellsuggest('tha', 2))
+ call assert_equal(['the', 'tee'], spellsuggest('dhe', 2))
+
+ set spell& spelllang&
+ call delete('XtestCOMMON.dic')
+ call delete('XtestCOMMON.aff')
+ call delete('XtestCOMMON-utf8.spl')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_startup_utf8.vim b/src/nvim/testdir/test_startup_utf8.vim
index 1b3d2184a0..bb4304396e 100644
--- a/src/nvim/testdir/test_startup_utf8.vim
+++ b/src/nvim/testdir/test_startup_utf8.vim
@@ -1,5 +1,6 @@
" Tests for startup using utf-8.
+source check.vim
source shared.vim
source screendump.vim
@@ -73,7 +74,7 @@ func Test_detect_ambiwidth()
\ 'call test_option_not_set("ambiwidth")',
\ 'redraw',
\ ], 'Xscript')
- let buf = RunVimInTerminal('-S Xscript', {})
+ let buf = RunVimInTerminal('-S Xscript', #{keep_t_u7: 1})
call term_wait(buf)
call term_sendkeys(buf, "S\<C-R>=&ambiwidth\<CR>\<Esc>")
call WaitForAssert({-> assert_match('single', term_getline(buf, 1))})
diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim
index 21fd57b791..a40b0236e0 100644
--- a/src/nvim/testdir/test_visual.vim
+++ b/src/nvim/testdir/test_visual.vim
@@ -1006,4 +1006,59 @@ func Test_visual_put_in_block_using_zp()
bwipe!
endfunc
+func Test_visual_put_in_block_using_zy_and_zp()
+ new
+
+ " Test 1) Paste using zp - after the cursor without trailing spaces
+ call setline(1, ['/path;text', '/path;text', '/path;text', '',
+ \ 'texttext /subdir columntext',
+ \ 'texttext /longsubdir columntext',
+ \ 'texttext /longlongsubdir columntext'])
+ exe "normal! 5G0f/\<c-v>2jezy"
+ norm! 1G0f;hzp
+ call assert_equal(['/path/subdir;text', '/path/longsubdir;text', '/path/longlongsubdir;text'], getline(1, 3))
+
+ " Test 2) Paste using zP - in front of the cursor without trailing spaces
+ %d
+ call setline(1, ['/path;text', '/path;text', '/path;text', '',
+ \ 'texttext /subdir columntext',
+ \ 'texttext /longsubdir columntext',
+ \ 'texttext /longlongsubdir columntext'])
+ exe "normal! 5G0f/\<c-v>2jezy"
+ norm! 1G0f;zP
+ call assert_equal(['/path/subdir;text', '/path/longsubdir;text', '/path/longlongsubdir;text'], getline(1, 3))
+
+ " Test 3) Paste using p - with trailing spaces
+ %d
+ call setline(1, ['/path;text', '/path;text', '/path;text', '',
+ \ 'texttext /subdir columntext',
+ \ 'texttext /longsubdir columntext',
+ \ 'texttext /longlongsubdir columntext'])
+ exe "normal! 5G0f/\<c-v>2jezy"
+ norm! 1G0f;hp
+ call assert_equal(['/path/subdir ;text', '/path/longsubdir ;text', '/path/longlongsubdir;text'], getline(1, 3))
+
+ " Test 4) Paste using P - with trailing spaces
+ %d
+ call setline(1, ['/path;text', '/path;text', '/path;text', '',
+ \ 'texttext /subdir columntext',
+ \ 'texttext /longsubdir columntext',
+ \ 'texttext /longlongsubdir columntext'])
+ exe "normal! 5G0f/\<c-v>2jezy"
+ norm! 1G0f;P
+ call assert_equal(['/path/subdir ;text', '/path/longsubdir ;text', '/path/longlongsubdir;text'], getline(1, 3))
+
+ " Test 5) Yank with spaces inside the block
+ %d
+ call setline(1, ['/path;text', '/path;text', '/path;text', '',
+ \ 'texttext /sub dir/ columntext',
+ \ 'texttext /lon gsubdir/ columntext',
+ \ 'texttext /lon glongsubdir/ columntext'])
+ exe "normal! 5G0f/\<c-v>2jf/zy"
+ norm! 1G0f;zP
+ call assert_equal(['/path/sub dir/;text', '/path/lon gsubdir/;text', '/path/lon glongsubdir/;text'], getline(1, 3))
+ bwipe!
+endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/vim.h b/src/nvim/vim.h
index 0245c472ef..df4ab04eb6 100644
--- a/src/nvim/vim.h
+++ b/src/nvim/vim.h
@@ -313,6 +313,7 @@ enum { FOLD_TEXT_LEN = 51 }; //!< buffer size for get_foldtext()
#define DIP_NORTP 0x20 // do not use 'runtimepath'
#define DIP_NOAFTER 0x40 // skip "after" directories
#define DIP_AFTER 0x80 // only use "after" directories
+#define DIP_LUA 0x100 // also use ".lua" files
// Lowest number used for window ID. Cannot have this many windows per tab.
#define LOWEST_WIN_ID 1000
diff --git a/test/functional/autocmd/autocmd_spec.lua b/test/functional/autocmd/autocmd_spec.lua
index e62d3bb66b..93d71a9e45 100644
--- a/test/functional/autocmd/autocmd_spec.lua
+++ b/test/functional/autocmd/autocmd_spec.lua
@@ -101,6 +101,17 @@ describe('autocmd', function()
}, eval('g:evs'))
end)
+ it('WinClosed from root directory', function()
+ command('cd /')
+ command('let g:evs = []')
+ command('autocmd WinClosed * :call add(g:evs, ["WinClosed", expand("<afile>")])')
+ command('new')
+ command('close')
+ eq({
+ {'WinClosed', '1001'},
+ }, eval('g:evs'))
+ end)
+
it('v:vim_did_enter is 1 after VimEnter', function()
eq(1, eval('v:vim_did_enter'))
end)
diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua
index d5f03db03a..658dfbda60 100644
--- a/test/functional/core/startup_spec.lua
+++ b/test/functional/core/startup_spec.lua
@@ -11,6 +11,7 @@ local exec_lua = helpers.exec_lua
local feed = helpers.feed
local funcs = helpers.funcs
local mkdir = helpers.mkdir
+local mkdir_p = helpers.mkdir_p
local nvim_prog = helpers.nvim_prog
local nvim_set = helpers.nvim_set
local read_file = helpers.read_file
@@ -494,6 +495,75 @@ describe('user config init', function()
end)
end)
+describe('runtime:', function()
+ local xhome = 'Xhome'
+ local pathsep = helpers.get_pathsep()
+ local xconfig = xhome .. pathsep .. 'Xconfig'
+
+ setup(function()
+ mkdir_p(xconfig .. pathsep .. 'nvim')
+ end)
+
+ teardown(function()
+ rmdir(xhome)
+ end)
+
+ it('loads plugin/*.lua from XDG config home', function()
+ local plugin_folder_path = table.concat({xconfig, 'nvim', 'plugin'}, pathsep)
+ local plugin_file_path = table.concat({plugin_folder_path, 'plugin.lua'}, pathsep)
+ mkdir_p(plugin_folder_path)
+ write_file(plugin_file_path, [[ vim.g.lua_plugin = 1 ]])
+
+ clear{ args_rm={'-u'}, env={ XDG_CONFIG_HOME=xconfig }}
+
+ eq(1, eval('g:lua_plugin'))
+ rmdir(plugin_folder_path)
+ end)
+
+ it('loads plugin/*.lua from start plugins', function()
+ local plugin_path = table.concat({xconfig, 'nvim', 'pack', 'catagory',
+ 'start', 'test_plugin'}, pathsep)
+ local plugin_folder_path = table.concat({plugin_path, 'plugin'}, pathsep)
+ local plugin_file_path = table.concat({plugin_folder_path, 'plugin.lua'},
+ pathsep)
+ local profiler_file = 'test_startuptime.log'
+
+ mkdir_p(plugin_folder_path)
+ write_file(plugin_file_path, [[vim.g.lua_plugin = 2]])
+
+ clear{ args_rm={'-u'}, args={'--startuptime', profiler_file}, env={ XDG_CONFIG_HOME=xconfig }}
+
+ eq(2, eval('g:lua_plugin'))
+ -- Check if plugin_file_path is listed in :scriptname
+ local scripts = meths.exec(':scriptnames', true)
+ assert.Truthy(scripts:find(plugin_file_path))
+
+ -- Check if plugin_file_path is listed in startup profile
+ local profile_reader = io.open(profiler_file, 'r')
+ local profile_log = profile_reader:read('*a')
+ profile_reader:close()
+ assert.Truthy(profile_log :find(plugin_file_path))
+
+ os.remove(profiler_file)
+ rmdir(plugin_path)
+ end)
+
+ it('loads ftdetect/*.lua', function()
+ local ftdetect_folder = table.concat({xconfig, 'nvim', 'ftdetect'}, pathsep)
+ local ftdetect_file = table.concat({ftdetect_folder , 'new-ft.lua'}, pathsep)
+ mkdir_p(ftdetect_folder)
+ write_file(ftdetect_file , [[vim.g.lua_ftdetect = 1]])
+
+ -- TODO(shadmansaleh): Figure out why this test fails without
+ -- setting VIMRUNTIME
+ clear{ args_rm={'-u'}, env={XDG_CONFIG_HOME=xconfig,
+ VIMRUNTIME='runtime/'}}
+
+ eq(1, eval('g:lua_ftdetect'))
+ rmdir(ftdetect_folder)
+ end)
+end)
+
describe('user session', function()
local xhome = 'Xhome'
local pathsep = helpers.get_pathsep()
diff --git a/test/functional/ex_cmds/source_spec.lua b/test/functional/ex_cmds/source_spec.lua
index 16d0dfb6a1..37c97f519a 100644
--- a/test/functional/ex_cmds/source_spec.lua
+++ b/test/functional/ex_cmds/source_spec.lua
@@ -6,6 +6,11 @@ local clear = helpers.clear
local meths = helpers.meths
local feed = helpers.feed
local feed_command = helpers.feed_command
+local write_file = helpers.write_file
+local exec = helpers.exec
+local eval = helpers.eval
+local exec_capture = helpers.exec_capture
+local neq = helpers.neq
describe(':source', function()
before_each(function()
@@ -44,4 +49,70 @@ describe(':source', function()
command('source')
eq('4', meths.exec('echo luaeval("y")', true))
end)
+
+ it('can source lua files', function()
+ local test_file = 'test.lua'
+ write_file (test_file, [[vim.g.sourced_lua = 1]])
+
+ exec('source ' .. test_file)
+
+ eq(1, eval('g:sourced_lua'))
+ os.remove(test_file)
+ end)
+
+ it('can source selected region in lua file', function()
+ local test_file = 'test.lua'
+
+ write_file (test_file, [[
+ vim.g.b = 5
+ vim.g.b = 6
+ vim.g.b = 7
+ ]])
+
+ command('edit '..test_file)
+ feed('ggjV')
+ feed_command(':source')
+
+ eq(6, eval('g:b'))
+ os.remove(test_file)
+ end)
+
+ it('can source current lua buffer without argument', function()
+ local test_file = 'test.lua'
+
+ write_file (test_file, [[
+ vim.g.c = 10
+ vim.g.c = 11
+ vim.g.c = 12
+ ]])
+
+ command('edit '..test_file)
+ feed_command(':source')
+
+ eq(12, eval('g:c'))
+ os.remove(test_file)
+ end)
+
+ it("doesn't throw E484 for lua parsing/runtime errors", function()
+ local test_file = 'test.lua'
+
+ -- Does throw E484 for unreadable files
+ local ok, result = pcall(exec_capture, ":source "..test_file ..'noexisting')
+ eq(false, ok)
+ neq(nil, result:find("E484"))
+
+ -- Doesn't throw for parsing error
+ write_file (test_file, "vim.g.c = ")
+ ok, result = pcall(exec_capture, ":source "..test_file)
+ eq(false, ok)
+ eq(nil, result:find("E484"))
+ os.remove(test_file)
+
+ -- Doesn't throw for runtime error
+ write_file (test_file, "error('Cause error anyway :D')")
+ ok, result = pcall(exec_capture, ":source "..test_file)
+ eq(false, ok)
+ eq(nil, result:find("E484"))
+ os.remove(test_file)
+ end)
end)
diff --git a/test/functional/fixtures/fake-lsp-server.lua b/test/functional/fixtures/fake-lsp-server.lua
index bcd5e22492..b7fddc8f29 100644
--- a/test/functional/fixtures/fake-lsp-server.lua
+++ b/test/functional/fixtures/fake-lsp-server.lua
@@ -464,6 +464,23 @@ function tests.invalid_header()
io.stdout:write("Content-length: \r\n")
end
+function tests.decode_nil()
+ skeleton {
+ on_init = function(_)
+ return { capabilities = {} }
+ end;
+ body = function()
+ notify('start')
+ notify("workspace/executeCommand", {
+ arguments = { "EXTRACT_METHOD", {metadata = {field = vim.NIL}}, 3, 0, 6123, vim.NIL },
+ command = "refactor.perform",
+ title = "EXTRACT_METHOD"
+ })
+ notify('finish')
+ end;
+ }
+end
+
-- Tests will be indexed by TEST_NAME
local kill_timer = vim.loop.new_timer()
diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua
index 4acb1a7d8d..08ca14c3df 100644
--- a/test/functional/helpers.lua
+++ b/test/functional/helpers.lua
@@ -878,6 +878,11 @@ function module.os_kill(pid)
or 'kill -9 '..pid..' > /dev/null'))
end
+-- Create directories with non exsisting intermidiate directories
+function module.mkdir_p(path)
+ return module.meths.call_function('mkdir', {path, 'p'})
+end
+
module = global_helpers.tbl_extend('error', module, global_helpers)
return function(after_each)
diff --git a/test/functional/legacy/011_autocommands_spec.lua b/test/functional/legacy/011_autocommands_spec.lua
index 7cc31dc787..7b6f2f63e9 100644
--- a/test/functional/legacy/011_autocommands_spec.lua
+++ b/test/functional/legacy/011_autocommands_spec.lua
@@ -17,6 +17,7 @@ local lfs = require('lfs')
local clear, feed_command, expect, eq, neq, dedent, write_file, feed =
helpers.clear, helpers.feed_command, helpers.expect, helpers.eq, helpers.neq,
helpers.dedent, helpers.write_file, helpers.feed
+local command = helpers.command
local iswin = helpers.iswin
local read_file = helpers.read_file
@@ -28,7 +29,7 @@ end
local function prepare_gz_file(name, text)
write_file(name, text..'\n')
-- Compress the file with gzip.
- os.execute('gzip --force '..name)
+ command([[call system(['gzip', '--force', ']]..name..[['])]])
-- This should create the .gz file and delete the original.
neq(nil, lfs.attributes(name..'.gz'))
eq(nil, lfs.attributes(name))
@@ -54,7 +55,9 @@ describe('file reading, writing and bufnew and filter autocommands', function()
*/
]])
end)
- before_each(clear)
+ before_each(function ()
+ clear({env={GZIP=nil}})
+ end)
teardown(function()
os.remove('Xtestfile.gz')
os.remove('Xtest.c')
@@ -67,7 +70,6 @@ describe('file reading, writing and bufnew and filter autocommands', function()
it('FileReadPost (using gzip)', function()
prepare_gz_file('Xtestfile', text1)
- feed_command('let $GZIP = ""')
--execute('au FileChangedShell * echo "caught FileChangedShell"')
feed_command('set bin')
feed_command("au FileReadPost *.gz '[,']!gzip -d")
@@ -79,7 +81,6 @@ describe('file reading, writing and bufnew and filter autocommands', function()
it('BufReadPre, BufReadPost (using gzip)', function()
prepare_gz_file('Xtestfile', text1)
local gzip_data = read_file('Xtestfile.gz')
- feed_command('let $GZIP = ""')
-- Setup autocommands to decompress before reading and re-compress afterwards.
feed_command("au BufReadPre *.gz exe '!gzip -d ' . shellescape(expand('<afile>'))")
feed_command("au BufReadPre *.gz call rename(expand('<afile>:r'), expand('<afile>'))")
diff --git a/test/functional/legacy/packadd_spec.lua b/test/functional/legacy/packadd_spec.lua
index 486a1fe471..48cd3ef9f8 100644
--- a/test/functional/legacy/packadd_spec.lua
+++ b/test/functional/legacy/packadd_spec.lua
@@ -101,9 +101,14 @@ describe('packadd', function()
call setline(1, 'let g:plugin_works = 24')
wq
+ exe 'split ' . plugdir . '/plugin/test.lua'
+ call setline(1, 'vim.g.plugin_lua_works = 24')
+ wq
+
packadd other
call assert_equal(24, g:plugin_works)
+ call assert_equal(24, g:plugin_lua_works)
call assert_true(len(&rtp) > len(rtp))
call assert_match(Escape(plugdir) . '\($\|,\)', &rtp)
endfunc
@@ -117,13 +122,18 @@ describe('packadd', function()
exe 'split ' . s:plugdir . '/plugin/test.vim'
call setline(1, 'let g:plugin_works = 42')
wq
+ exe 'split ' . s:plugdir . '/plugin/test.lua'
+ call setline(1, 'let g:plugin_lua_works = 42')
+ wq
let g:plugin_works = 0
+ let g:plugin_lua_works = 0
packadd! mytest
call assert_true(len(&rtp) > len(rtp))
call assert_match(Escape(s:plugdir) . '\($\|,\)', &rtp)
call assert_equal(0, g:plugin_works)
+ call assert_equal(0, g:plugin_lua_works)
" check the path is not added twice
let new_rtp = &rtp
diff --git a/test/functional/lua/runtime_spec.lua b/test/functional/lua/runtime_spec.lua
new file mode 100644
index 0000000000..e9c34c9228
--- /dev/null
+++ b/test/functional/lua/runtime_spec.lua
@@ -0,0 +1,141 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local clear = helpers.clear
+local eq = helpers.eq
+local eval = helpers.eval
+local exec = helpers.exec
+local mkdir_p = helpers.mkdir_p
+local rmdir = helpers.rmdir
+local write_file = helpers.write_file
+
+describe('runtime:', function()
+ local plug_dir = 'Test_Plugin'
+ local sep = helpers.get_pathsep()
+ local init = 'dummy_init.lua'
+
+ setup(function()
+ io.open(init, 'w'):close() -- touch init file
+ clear{args = {'-u', init}}
+ exec('set rtp+=' .. plug_dir)
+ end)
+
+ teardown(function()
+ os.remove(init)
+ end)
+
+ before_each(function()
+ mkdir_p(plug_dir)
+ end)
+
+ after_each(function()
+ rmdir(plug_dir)
+ end)
+
+ describe('colors', function()
+ local colorscheme_folder = plug_dir .. sep .. 'colors'
+
+ it('loads lua colorscheme', function()
+ local colorscheme_file = colorscheme_folder .. sep .. 'new_colorscheme.lua'
+ mkdir_p(colorscheme_folder)
+ write_file(colorscheme_file, [[vim.g.lua_colorscheme = 1]])
+
+ exec('colorscheme new_colorscheme')
+
+ eq(1, eval('g:lua_colorscheme'))
+ rmdir(colorscheme_folder)
+ end)
+
+ it('loads vim colorscheme when both lua and vim version exist', function()
+ local colorscheme_file = colorscheme_folder .. sep .. 'new_colorscheme'
+ mkdir_p(colorscheme_folder)
+ write_file(colorscheme_file..'.vim', [[let g:colorscheme = 'vim']])
+ write_file(colorscheme_file..'.lua', [[vim.g.colorscheme = 'lua']])
+
+ exec('colorscheme new_colorscheme')
+
+ eq('vim', eval('g:colorscheme'))
+ rmdir(colorscheme_folder)
+ end)
+ end)
+
+ describe('compiler', function()
+ local compiler_folder = plug_dir .. sep .. 'compiler'
+
+ it('loads lua compilers', function()
+ local compiler_file = compiler_folder .. sep .. 'new_compiler.lua'
+ mkdir_p(compiler_folder)
+ write_file(compiler_file, [[vim.g.lua_compiler = 1]])
+
+ exec('compiler new_compiler')
+
+ eq(1, eval('g:lua_compiler'))
+ rmdir(compiler_folder)
+ end)
+
+ it('loads vim compilers when both lua and vim version exist', function()
+ local compiler_file = compiler_folder .. sep .. 'new_compiler'
+ mkdir_p(compiler_folder)
+ write_file(compiler_file..'.vim', [[let g:compiler = 'vim']])
+ write_file(compiler_file..'.lua', [[vim.g.compiler = 'lua']])
+
+ exec('compiler new_compiler')
+
+ eq('vim', eval('g:compiler'))
+ rmdir(compiler_folder)
+ end)
+ end)
+
+ describe('ftplugin', function()
+ local ftplugin_folder = table.concat({plug_dir, 'ftplugin'}, sep)
+
+ it('loads lua ftplugins', function()
+ local ftplugin_file = table.concat({ftplugin_folder , 'new-ft.lua'}, sep)
+ mkdir_p(ftplugin_folder)
+ write_file(ftplugin_file , [[vim.g.lua_ftplugin = 1]])
+
+ exec [[set filetype=new-ft]]
+ eq(1, eval('g:lua_ftplugin'))
+ rmdir(ftplugin_folder)
+ end)
+ end)
+
+ describe('indent', function()
+ local indent_folder = table.concat({plug_dir, 'indent'}, sep)
+
+ it('loads lua indents', function()
+ local indent_file = table.concat({indent_folder , 'new-ft.lua'}, sep)
+ mkdir_p(indent_folder)
+ write_file(indent_file , [[vim.g.lua_indent = 1]])
+
+ exec [[set filetype=new-ft]]
+ eq(1, eval('g:lua_indent'))
+ rmdir(indent_folder)
+ end)
+ end)
+
+ describe('syntax', function()
+ local syntax_folder = table.concat({plug_dir, 'syntax'}, sep)
+
+ it('loads lua syntaxes on filetype change', function()
+ local syntax_file = table.concat({syntax_folder , 'my-lang.lua'}, sep)
+ mkdir_p(syntax_folder)
+ write_file(syntax_file , [[vim.g.lua_syntax = 1]])
+
+ exec('set filetype=my-lang')
+ eq(1, eval('g:lua_syntax'))
+ rmdir(syntax_folder)
+ end)
+
+ it('loads lua syntaxes on syntax change', function()
+ local syntax_file = table.concat({syntax_folder , 'my-lang.lua'}, sep)
+ mkdir_p(syntax_folder)
+ write_file(syntax_file , [[vim.g.lua_syntax = 5]])
+
+ exec('set syntax=my-lang')
+ eq(5, eval('g:lua_syntax'))
+ rmdir(syntax_folder)
+ end)
+ end)
+
+end)
+
diff --git a/test/functional/plugin/lsp/codelens_spec.lua b/test/functional/plugin/lsp/codelens_spec.lua
new file mode 100644
index 0000000000..e09d93f7cc
--- /dev/null
+++ b/test/functional/plugin/lsp/codelens_spec.lua
@@ -0,0 +1,62 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local exec_lua = helpers.exec_lua
+local eq = helpers.eq
+
+describe('vim.lsp.codelens', function()
+ before_each(function()
+ helpers.clear()
+ exec_lua('require("vim.lsp")')
+ end)
+ after_each(helpers.clear)
+
+ it('on_codelens_stores_and_displays_lenses', function()
+ local fake_uri = "file://fake/uri"
+ local bufnr = exec_lua([[
+ fake_uri = ...
+ local bufnr = vim.uri_to_bufnr(fake_uri)
+ local lines = {'So', 'many', 'lines'}
+ vim.fn.bufload(bufnr)
+ vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
+ return bufnr
+ ]], fake_uri)
+
+ exec_lua([[
+ local bufnr = ...
+ local lenses = {
+ {
+ range = {
+ start = { line = 0, character = 0, },
+ ['end'] = { line = 0, character = 0 }
+ },
+ command = { title = 'Lens1', command = 'Dummy' }
+ },
+ }
+ vim.lsp.codelens.on_codelens(nil, 'textDocument/codeLens', lenses, 1, bufnr)
+ ]], bufnr)
+
+ local stored_lenses = exec_lua('return vim.lsp.codelens.get(...)', bufnr)
+ local expected = {
+ {
+ range = {
+ start = { line = 0, character = 0 },
+ ['end'] = { line = 0, character = 0 }
+ },
+ command = {
+ title = 'Lens1',
+ command = 'Dummy',
+ },
+ },
+ }
+ eq(expected, stored_lenses)
+
+ local virtual_text_chunks = exec_lua([[
+ local bufnr = ...
+ local ns = vim.lsp.codelens.__namespaces[1]
+ local extmarks = vim.api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, {})
+ return vim.api.nvim_buf_get_extmark_by_id(bufnr, ns, extmarks[1][1], { details = true })[3].virt_text
+ ]], bufnr)
+
+ eq({[1] = {'Lens1', 'LspCodeLens'}}, virtual_text_chunks)
+ end)
+end)
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
index 663271deab..35cc2d3075 100644
--- a/test/functional/plugin/lsp_spec.lua
+++ b/test/functional/plugin/lsp_spec.lua
@@ -105,8 +105,8 @@ local function test_rpc_server(config)
return NIL
end
if method == 'handler' then
- if config.on_callback then
- config.on_callback(unpack(args))
+ if config.on_handler then
+ config.on_handler(unpack(args))
end
end
return NIL
@@ -215,7 +215,7 @@ describe('LSP', function()
end)
it('should run correctly', function()
- local expected_callbacks = {
+ local expected_handlers = {
{NIL, "test", {}, 1};
}
test_rpc_server {
@@ -232,15 +232,15 @@ describe('LSP', function()
eq(0, signal, "exit signal", fake_lsp_logfile)
end;
-- Note that NIL must be used here.
- -- on_callback(err, method, result, client_id)
- on_callback = function(...)
- eq(table.remove(expected_callbacks), {...})
+ -- on_handler(err, method, result, client_id)
+ on_handler = function(...)
+ eq(table.remove(expected_handlers), {...})
end;
}
end)
it('should fail', function()
- local expected_callbacks = {
+ local expected_handlers = {
{NIL, "test", {}, 1};
}
test_rpc_server {
@@ -255,8 +255,8 @@ describe('LSP', function()
assert_log(pesc([[assert_eq failed: left == "\"shutdown\"", right == "\"test\""]]),
fake_lsp_logfile)
end;
- on_callback = function(...)
- eq(table.remove(expected_callbacks), {...}, "expected callback")
+ on_handler = function(...)
+ eq(table.remove(expected_handlers), {...}, "expected handler")
end;
}
end)
@@ -266,7 +266,7 @@ describe('LSP', function()
pending('hangs the build on openbsd #14028, re-enable with freeze timeout #14204')
return
end
- local expected_callbacks = {
+ local expected_handlers = {
{NIL, "shutdown", {}, 1, NIL};
{NIL, "test", {}, 1};
}
@@ -282,14 +282,14 @@ describe('LSP', function()
eq(0, code, "exit code", fake_lsp_logfile)
eq(0, signal, "exit signal", fake_lsp_logfile)
end;
- on_callback = function(...)
- eq(table.remove(expected_callbacks), {...}, "expected callback")
+ on_handler = function(...)
+ eq(table.remove(expected_handlers), {...}, "expected handler")
end;
}
end)
it('client should return settings via workspace/configuration handler', function()
- local expected_callbacks = {
+ local expected_handlers = {
{NIL, "shutdown", {}, 1};
{NIL, "workspace/configuration", { items = {
{ section = "testSetting1" };
@@ -307,8 +307,8 @@ describe('LSP', function()
eq(0, code, "exit code", fake_lsp_logfile)
eq(0, signal, "exit signal", fake_lsp_logfile)
end;
- on_callback = function(err, method, params, client_id)
- eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ on_handler = function(err, method, params, client_id)
+ eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler")
if method == 'start' then
exec_lua([=[
local client = vim.lsp.get_client_by_id(TEST_RPC_CLIENT_ID)
@@ -344,7 +344,7 @@ describe('LSP', function()
end)
it('should verify capabilities sent', function()
- local expected_callbacks = {
+ local expected_handlers = {
{NIL, "shutdown", {}, 1};
}
test_rpc_server {
@@ -361,14 +361,14 @@ describe('LSP', function()
eq(0, code, "exit code", fake_lsp_logfile)
eq(0, signal, "exit signal", fake_lsp_logfile)
end;
- on_callback = function(...)
- eq(table.remove(expected_callbacks), {...}, "expected callback")
+ on_handler = function(...)
+ eq(table.remove(expected_handlers), {...}, "expected handler")
end;
}
end)
it('client.supports_methods() should validate capabilities', function()
- local expected_callbacks = {
+ local expected_handlers = {
{NIL, "shutdown", {}, 1};
}
test_rpc_server {
@@ -395,14 +395,14 @@ describe('LSP', function()
eq(0, code, "exit code", fake_lsp_logfile)
eq(0, signal, "exit signal", fake_lsp_logfile)
end;
- on_callback = function(...)
- eq(table.remove(expected_callbacks), {...}, "expected callback")
+ on_handler = function(...)
+ eq(table.remove(expected_handlers), {...}, "expected handler")
end;
}
end)
it('should call unsupported_method when trying to call an unsupported method', function()
- local expected_callbacks = {
+ local expected_handlers = {
{NIL, "shutdown", {}, 1};
}
test_rpc_server {
@@ -412,7 +412,7 @@ describe('LSP', function()
BUFFER = vim.api.nvim_get_current_buf()
lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)
vim.lsp.handlers['textDocument/typeDefinition'] = function(err, method)
- vim.lsp._last_lsp_callback = { err = err; method = method }
+ vim.lsp._last_lsp_handler = { err = err; method = method }
end
vim.lsp._unsupported_method = function(method)
vim.lsp._last_unsupported_method = method
@@ -425,7 +425,7 @@ describe('LSP', function()
client.stop()
local method = exec_lua("return vim.lsp._last_unsupported_method")
eq("textDocument/typeDefinition", method)
- local lsp_cb_call = exec_lua("return vim.lsp._last_lsp_callback")
+ local lsp_cb_call = exec_lua("return vim.lsp._last_lsp_handler")
eq("fake-error", lsp_cb_call.err)
eq("textDocument/typeDefinition", lsp_cb_call.method)
exec_lua [[
@@ -436,14 +436,14 @@ describe('LSP', function()
eq(0, code, "exit code", fake_lsp_logfile)
eq(0, signal, "exit signal", fake_lsp_logfile)
end;
- on_callback = function(...)
- eq(table.remove(expected_callbacks), {...}, "expected callback")
+ on_handler = function(...)
+ eq(table.remove(expected_handlers), {...}, "expected handler")
end;
}
end)
it('shouldn\'t call unsupported_method when no client and trying to call an unsupported method', function()
- local expected_callbacks = {
+ local expected_handlers = {
{NIL, "shutdown", {}, 1};
}
test_rpc_server {
@@ -451,7 +451,7 @@ describe('LSP', function()
on_setup = function()
exec_lua([=[
vim.lsp.handlers['textDocument/typeDefinition'] = function(err, method)
- vim.lsp._last_lsp_callback = { err = err; method = method }
+ vim.lsp._last_lsp_handler = { err = err; method = method }
end
vim.lsp._unsupported_method = function(method)
vim.lsp._last_unsupported_method = method
@@ -463,20 +463,20 @@ describe('LSP', function()
on_init = function(client)
client.stop()
eq(NIL, exec_lua("return vim.lsp._last_unsupported_method"))
- eq(NIL, exec_lua("return vim.lsp._last_lsp_callback"))
+ eq(NIL, exec_lua("return vim.lsp._last_lsp_handler"))
end;
on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile)
eq(0, signal, "exit signal", fake_lsp_logfile)
end;
- on_callback = function(...)
- eq(table.remove(expected_callbacks), {...}, "expected callback")
+ on_handler = function(...)
+ eq(table.remove(expected_handlers), {...}, "expected handler")
end;
}
end)
it('should not send didOpen if the buffer closes before init', function()
- local expected_callbacks = {
+ local expected_handlers = {
{NIL, "shutdown", {}, 1};
{NIL, "finish", {}, 1};
}
@@ -509,8 +509,8 @@ describe('LSP', function()
eq(0, code, "exit code", fake_lsp_logfile)
eq(0, signal, "exit signal", fake_lsp_logfile)
end;
- on_callback = function(err, method, params, client_id)
- eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ on_handler = function(err, method, params, client_id)
+ eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler")
if method == 'finish' then
client.stop()
end
@@ -519,7 +519,7 @@ describe('LSP', function()
end)
it('should check the body sent attaching before init', function()
- local expected_callbacks = {
+ local expected_handlers = {
{NIL, "shutdown", {}, 1};
{NIL, "finish", {}, 1};
{NIL, "start", {}, 1};
@@ -552,11 +552,11 @@ describe('LSP', function()
eq(0, code, "exit code", fake_lsp_logfile)
eq(0, signal, "exit signal", fake_lsp_logfile)
end;
- on_callback = function(err, method, params, client_id)
+ on_handler = function(err, method, params, client_id)
if method == 'start' then
client.notify('finish')
end
- eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler")
if method == 'finish' then
client.stop()
end
@@ -565,7 +565,7 @@ describe('LSP', function()
end)
it('should check the body sent attaching after init', function()
- local expected_callbacks = {
+ local expected_handlers = {
{NIL, "shutdown", {}, 1};
{NIL, "finish", {}, 1};
{NIL, "start", {}, 1};
@@ -595,11 +595,11 @@ describe('LSP', function()
eq(0, code, "exit code", fake_lsp_logfile)
eq(0, signal, "exit signal", fake_lsp_logfile)
end;
- on_callback = function(err, method, params, client_id)
+ on_handler = function(err, method, params, client_id)
if method == 'start' then
client.notify('finish')
end
- eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler")
if method == 'finish' then
client.stop()
end
@@ -608,7 +608,7 @@ describe('LSP', function()
end)
it('should check the body and didChange full', function()
- local expected_callbacks = {
+ local expected_handlers = {
{NIL, "shutdown", {}, 1};
{NIL, "finish", {}, 1};
{NIL, "start", {}, 1};
@@ -638,7 +638,7 @@ describe('LSP', function()
eq(0, code, "exit code", fake_lsp_logfile)
eq(0, signal, "exit signal", fake_lsp_logfile)
end;
- on_callback = function(err, method, params, client_id)
+ on_handler = function(err, method, params, client_id)
if method == 'start' then
exec_lua [[
vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
@@ -647,7 +647,7 @@ describe('LSP', function()
]]
client.notify('finish')
end
- eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler")
if method == 'finish' then
client.stop()
end
@@ -656,7 +656,7 @@ describe('LSP', function()
end)
it('should check the body and didChange full with noeol', function()
- local expected_callbacks = {
+ local expected_handlers = {
{NIL, "shutdown", {}, 1};
{NIL, "finish", {}, 1};
{NIL, "start", {}, 1};
@@ -687,7 +687,7 @@ describe('LSP', function()
eq(0, code, "exit code", fake_lsp_logfile)
eq(0, signal, "exit signal", fake_lsp_logfile)
end;
- on_callback = function(err, method, params, client_id)
+ on_handler = function(err, method, params, client_id)
if method == 'start' then
exec_lua [[
vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
@@ -696,7 +696,7 @@ describe('LSP', function()
]]
client.notify('finish')
end
- eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler")
if method == 'finish' then
client.stop()
end
@@ -705,7 +705,7 @@ describe('LSP', function()
end)
it('should check the body and didChange incremental', function()
- local expected_callbacks = {
+ local expected_handlers = {
{NIL, "shutdown", {}, 1};
{NIL, "finish", {}, 1};
{NIL, "start", {}, 1};
@@ -736,7 +736,7 @@ describe('LSP', function()
eq(0, code, "exit code", fake_lsp_logfile)
eq(0, signal, "exit signal", fake_lsp_logfile)
end;
- on_callback = function(err, method, params, client_id)
+ on_handler = function(err, method, params, client_id)
if method == 'start' then
exec_lua [[
vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
@@ -745,7 +745,7 @@ describe('LSP', function()
]]
client.notify('finish')
end
- eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler")
if method == 'finish' then
client.stop()
end
@@ -755,7 +755,7 @@ describe('LSP', function()
-- TODO(askhan) we don't support full for now, so we can disable these tests.
pending('should check the body and didChange incremental normal mode editing', function()
- local expected_callbacks = {
+ local expected_handlers = {
{NIL, "shutdown", {}, 1};
{NIL, "finish", {}, 1};
{NIL, "start", {}, 1};
@@ -785,12 +785,12 @@ describe('LSP', function()
eq(0, code, "exit code", fake_lsp_logfile)
eq(0, signal, "exit signal", fake_lsp_logfile)
end;
- on_callback = function(err, method, params, client_id)
+ on_handler = function(err, method, params, client_id)
if method == 'start' then
helpers.command("normal! 1Go")
client.notify('finish')
end
- eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler")
if method == 'finish' then
client.stop()
end
@@ -799,7 +799,7 @@ describe('LSP', function()
end)
it('should check the body and didChange full with 2 changes', function()
- local expected_callbacks = {
+ local expected_handlers = {
{NIL, "shutdown", {}, 1};
{NIL, "finish", {}, 1};
{NIL, "start", {}, 1};
@@ -829,7 +829,7 @@ describe('LSP', function()
eq(0, code, "exit code", fake_lsp_logfile)
eq(0, signal, "exit signal", fake_lsp_logfile)
end;
- on_callback = function(err, method, params, client_id)
+ on_handler = function(err, method, params, client_id)
if method == 'start' then
exec_lua [[
vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
@@ -841,7 +841,7 @@ describe('LSP', function()
]]
client.notify('finish')
end
- eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler")
if method == 'finish' then
client.stop()
end
@@ -850,7 +850,7 @@ describe('LSP', function()
end)
it('should check the body and didChange full lifecycle', function()
- local expected_callbacks = {
+ local expected_handlers = {
{NIL, "shutdown", {}, 1};
{NIL, "finish", {}, 1};
{NIL, "start", {}, 1};
@@ -880,7 +880,7 @@ describe('LSP', function()
eq(0, code, "exit code", fake_lsp_logfile)
eq(0, signal, "exit signal", fake_lsp_logfile)
end;
- on_callback = function(err, method, params, client_id)
+ on_handler = function(err, method, params, client_id)
if method == 'start' then
exec_lua [[
vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
@@ -893,7 +893,7 @@ describe('LSP', function()
]]
client.notify('finish')
end
- eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback")
+ eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler")
if method == 'finish' then
client.stop()
end
@@ -904,7 +904,7 @@ describe('LSP', function()
describe("parsing tests", function()
it('should handle invalid content-length correctly', function()
- local expected_callbacks = {
+ local expected_handlers = {
{NIL, "shutdown", {}, 1};
{NIL, "finish", {}, 1};
{NIL, "start", {}, 1};
@@ -923,7 +923,49 @@ describe('LSP', function()
eq(0, signal, "exit signal", fake_lsp_logfile)
end;
on_handler = function(err, method, params, client_id)
- eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected handler")
+ eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler")
+ end;
+ }
+ end)
+
+ it('should not trim vim.NIL from the end of a list', function()
+ local expected_handlers = {
+ {NIL, "shutdown", {}, 1};
+ {NIL, "finish", {}, 1};
+ {NIL, "workspace/executeCommand", {
+ arguments = { "EXTRACT_METHOD", {metadata = {}}, 3, 0, 6123, NIL },
+ command = "refactor.perform",
+ title = "EXTRACT_METHOD"
+ }, 1};
+ {NIL, "start", {}, 1};
+ }
+ local client
+ test_rpc_server {
+ test_name = "decode_nil";
+ on_setup = function()
+ exec_lua [[
+ BUFFER = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, {
+ "testing";
+ "123";
+ })
+ ]]
+ end;
+ on_init = function(_client)
+ client = _client
+ exec_lua [[
+ assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
+ ]]
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code", fake_lsp_logfile)
+ eq(0, signal, "exit signal", fake_lsp_logfile)
+ end;
+ on_handler = function(err, method, params, client_id)
+ eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler")
+ if method == 'finish' then
+ client.stop()
+ end
end;
}
end)
diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua
index f3b840da21..9fa0ad08f1 100644
--- a/test/functional/ui/float_spec.lua
+++ b/test/functional/ui/float_spec.lua
@@ -711,6 +711,49 @@ describe('float window', function()
]]}
end
+ meths.win_set_config(win, {border="rounded"})
+ 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
+
meths.win_set_config(win, {border="solid"})
if multigrid then
screen:expect{grid=[[
@@ -2901,6 +2944,53 @@ describe('float window', function()
end
end)
+ it('command menu rendered above cursor (pum_above)', function()
+ command('set wildmenu wildmode=longest:full wildoptions=pum')
+ feed(':sign u<tab>')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ :sign un^ |
+ ## grid 4
+ {7: }|
+ {12:~ }|
+ {12:~ }|
+ {12:~ }|
+ ## grid 5
+ {1: undefine }|
+ {1: unplace }|
+ ]], float_pos={
+ [5] = {{id = -1}, "SW", 1, 6, 5, false, 250};
+ [4] = {{id = 1001}, "NW", 1, 2, 5, true, 50};
+ }}
+ else
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }{7: }{0: }|
+ {0:~ }{12:~ }{0: }|
+ {0:~ }{1: undefine }{0: }|
+ {0:~ }{1: unplace }{0: }|
+ :sign un^ |
+ ]]}
+ end
+ end)
+
it('with ext_popupmenu', function()
screen:set_option('ext_popupmenu', true)
feed('ix ')
diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua
index 72468392ee..c238898244 100644
--- a/test/functional/ui/messages_spec.lua
+++ b/test/functional/ui/messages_spec.lua
@@ -811,7 +811,7 @@ describe('ui/ext_messages', function()
{1:~ }|
{1:^~ }|
]], messages={
- {content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Helli"\nType number and <Enter> or click with the mouse (q or empty cancels): ' } }, kind = ""}
+ {content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\nType number and <Enter> or click with the mouse (q or empty cancels): ' } }, kind = ""}
}}
feed('1')
@@ -822,7 +822,7 @@ describe('ui/ext_messages', function()
{1:~ }|
{1:^~ }|
]], messages={
- {content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Helli"\nType number and <Enter> or click with the mouse (q or empty cancels): ' } }, kind = ""},
+ {content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\nType number and <Enter> or click with the mouse (q or empty cancels): ' } }, kind = ""},
{ content = { { "1" } }, kind = "" }
}}
diff --git a/test/functional/ui/spell_spec.lua b/test/functional/ui/spell_spec.lua
index 2c6e586665..de77100cc0 100644
--- a/test/functional/ui/spell_spec.lua
+++ b/test/functional/ui/spell_spec.lua
@@ -36,7 +36,7 @@ describe("'spell'", function()
feed('ggJJJJJJ0')
screen:expect([[
{1:^Lorem} {1:ipsum} dolor sit {1:amet}, {1:consectetur} {1:adipiscing} {1:elit}, {1:sed} do {1:eiusmod} {1:tempor} {1:i}|
- {1:ncididunt} {1:ut} {1:labore} {1:et} {1:dolore} {1:magna} {1:aliqua}. {1:Ut} {1:enim} ad minim {1:veniam}, {1:quis} {1:nostru}|
+ {1:ncididunt} {1:ut} {1:labore} et {1:dolore} {1:magna} {1:aliqua}. {1:Ut} {1:enim} ad minim {1:veniam}, {1:quis} {1:nostru}|
{1:d} {1:exercitation} {1:ullamco} {1:laboris} {1:nisi} {1:ut} {1:aliquip} ex ea {1:commodo} {1:consequat}. {1:Duis} {1:aut}|
{1:e} {1:irure} dolor in {1:reprehenderit} in {1:voluptate} {1:velit} {1:esse} {1:cillum} {1:dolore} {1:eu} {1:fugiat} {1:n}|
{1:ulla} {1:pariatur}. {1:Excepteur} {1:sint} {1:occaecat} {1:cupidatat} non {1:proident}, {1:sunt} in culpa {1:qui}|
@@ -44,6 +44,7 @@ describe("'spell'", function()
{0:~ }|
|
]])
+
end)
it('has correct highlight at start of line', function()
diff --git a/test/functional/ui/tabline_spec.lua b/test/functional/ui/tabline_spec.lua
index 23aae81745..ab8d63cda1 100644
--- a/test/functional/ui/tabline_spec.lua
+++ b/test/functional/ui/tabline_spec.lua
@@ -4,14 +4,17 @@ local clear, command, eq = helpers.clear, helpers.command, helpers.eq
describe('ui/ext_tabline', function()
local screen
- local event_tabs, event_curtab
+ local event_tabs, event_curtab, event_curbuf, event_buffers
before_each(function()
clear()
screen = Screen.new(25, 5)
screen:attach({rgb=true, ext_tabline=true})
- function screen:_handle_tabline_update(curtab, tabs)
- event_curtab, event_tabs = curtab, tabs
+ function screen:_handle_tabline_update(curtab, tabs, curbuf, buffers)
+ event_curtab = curtab
+ event_tabs = tabs
+ event_curbuf = curbuf
+ event_buffers = buffers
end
end)
@@ -45,4 +48,38 @@ describe('ui/ext_tabline', function()
eq(expected_tabs, event_tabs)
end}
end)
+
+ it('buffer UI events', function()
+ local expected_buffers_initial= {
+ {buffer = { id = 1 }, name = '[No Name]'},
+ }
+
+ screen:expect{grid=[[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]], condition=function()
+ eq({ id = 1}, event_curbuf)
+ eq(expected_buffers_initial, event_buffers)
+ end}
+
+ command("badd another-buffer")
+ command("bnext")
+
+ local expected_buffers = {
+ {buffer = { id = 2 }, name = 'another-buffer'},
+ }
+ screen:expect{grid=[[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]], condition=function()
+ eq({ id = 2 }, event_curbuf)
+ eq(expected_buffers, event_buffers)
+ end}
+ end)
end)
diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua
index 7c03005529..d81e272877 100644
--- a/test/unit/eval/typval_spec.lua
+++ b/test/unit/eval/typval_spec.lua
@@ -2623,7 +2623,7 @@ describe('typval.c', function()
describe('check_lock()', function()
local function tv_check_lock(lock, name, name_len, emsg)
return check_emsg(function()
- return lib.tv_check_lock(lock, name, name_len)
+ return lib.var_check_lock(lock, name, name_len)
end, emsg)
end
itp('works', function()
diff --git a/test/unit/marktree_spec.lua b/test/unit/marktree_spec.lua
index 56acc0f93e..cd9c7bef13 100644
--- a/test/unit/marktree_spec.lua
+++ b/test/unit/marktree_spec.lua
@@ -186,5 +186,20 @@ describe('marktree', function()
lib.marktree_check(tree)
shadoworder(tree, shadow, iter2)
end
+
+ -- Check iterator validity for 2 specific edge cases:
+ -- https://github.com/neovim/neovim/pull/14719
+ lib.marktree_clear(tree)
+ for i = 1,20 do
+ lib.marktree_put(tree, i, i, false)
+ end
+
+ lib.marktree_itr_get(tree, 10, 10, iter)
+ lib.marktree_del_itr(tree, iter, false)
+ eq(11, iter[0].node.key[iter[0].i].pos.col)
+
+ lib.marktree_itr_get(tree, 11, 11, iter)
+ lib.marktree_del_itr(tree, iter, false)
+ eq(12, iter[0].node.key[iter[0].i].pos.col)
end)
end)
diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt
index e248232909..f9da7c497a 100644
--- a/third-party/CMakeLists.txt
+++ b/third-party/CMakeLists.txt
@@ -21,7 +21,17 @@ if(HAS_OG_FLAG)
set(DEFAULT_MAKE_CFLAGS CFLAGS+=-Og ${DEFAULT_MAKE_CFLAGS})
endif()
-set(DEPS_INSTALL_DIR "${CMAKE_BINARY_DIR}/usr" CACHE PATH "Dependencies install directory.")
+if(CMAKE_SYSTEM_NAME MATCHES "OpenBSD")
+ # pkg-config 29.2 has a bug on OpenBSD which causes it to drop any paths that
+ # *contain* system include paths. To avoid this, we prefix what would be
+ # "/usr/include" as "/_usr/include".
+ # This check is also performed in the root CMakeLists.txt
+ # https://github.com/neovim/neovim/pull/14745#issuecomment-860201794
+ set(DEPS_INSTALL_DIR "${CMAKE_BINARY_DIR}/_usr" CACHE PATH "Dependencies install directory.")
+else()
+ set(DEPS_INSTALL_DIR "${CMAKE_BINARY_DIR}/usr" CACHE PATH "Dependencies install directory.")
+endif()
+
set(DEPS_BIN_DIR "${DEPS_INSTALL_DIR}/bin" CACHE PATH "Dependencies binary install directory.")
set(DEPS_LIB_DIR "${DEPS_INSTALL_DIR}/lib" CACHE PATH "Dependencies library install directory.")
set(DEPS_BUILD_DIR "${CMAKE_BINARY_DIR}/build" CACHE PATH "Dependencies build directory.")
@@ -199,9 +209,9 @@ set(LIBICONV_SHA256 ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc891
set(TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/5aa0bbb.tar.gz)
set(TREESITTER_C_SHA256 a5dcb37460d83002dfae7f9a208170ddbc9a047f231b9d6b75da7d36d707db2f)
-# This is 0.19.4+2, fixes some segfaults https://github.com/tree-sitter/tree-sitter/commit/89e1157a299596f3ce2155ba9fd69d5e2c03d3e6
-set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/89e1157a299596f3ce2155ba9fd69d5e2c03d3e6.zip)
-set(TREESITTER_SHA256 d5be9fd92cbf783680f921b2adccbd721b9aa8b2c445114a216b6544330f252c)
+# This is a bit after 0.19.5 because it fixes issues with queries
+set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/6ed42747a4e0faee9b65edbbacc86ed0caeae05c.zip)
+set(TREESITTER_SHA256 07b8d090ae856d4ea8a494c08900271545d44af2558278a27693f9a47d9e75e3)
if(USE_BUNDLED_UNIBILIUM)
include(BuildUnibilium)