aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/labeler.yml8
-rw-r--r--CONTRIBUTING.md21
-rw-r--r--runtime/doc/autocmd.txt6
-rw-r--r--runtime/doc/change.txt13
-rw-r--r--runtime/doc/eval.txt8
-rw-r--r--runtime/doc/index.txt1
-rw-r--r--runtime/doc/quickfix.txt4
-rw-r--r--runtime/doc/repeat.txt14
-rw-r--r--runtime/doc/spell.txt17
-rw-r--r--runtime/doc/starting.txt9
-rw-r--r--runtime/doc/syntax.txt3
-rw-r--r--runtime/doc/usr_05.txt2
-rw-r--r--runtime/filetype.vim1
-rw-r--r--runtime/ftplugin.vim4
-rw-r--r--runtime/indent.vim3
-rw-r--r--runtime/lua/vim/lsp/diagnostic.lua8
-rw-r--r--runtime/spell/en.utf-8.splbin609337 -> 621617 bytes
-rw-r--r--runtime/syntax/synload.vim3
-rw-r--r--src/nvim/eval/funcs.c30
-rw-r--r--src/nvim/ex_cmds.lua6
-rw-r--r--src/nvim/ex_cmds2.c34
-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.c18
-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/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/popupmnu.c2
-rw-r--r--src/nvim/runtime.c22
-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_ex_mode.vim19
-rw-r--r--src/nvim/testdir/test_fold.vim20
-rw-r--r--src/nvim/testdir/test_normal.vim155
-rw-r--r--src/nvim/testdir/test_spell.vim218
-rw-r--r--src/nvim/testdir/test_spellfile.vim240
-rw-r--r--src/nvim/testdir/test_visual.vim55
-rw-r--r--src/nvim/vim.h1
-rw-r--r--test/functional/core/startup_spec.lua70
-rw-r--r--test/functional/ex_cmds/source_spec.lua46
-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/ui/float_spec.lua47
-rw-r--r--test/functional/ui/messages_spec.lua4
-rw-r--r--test/functional/ui/spell_spec.lua3
-rw-r--r--third-party/CMakeLists.txt6
55 files changed, 1246 insertions, 295 deletions
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/CONTRIBUTING.md b/CONTRIBUTING.md
index 64433e67e7..94c371b62d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -74,15 +74,26 @@ For Comment) and `[RDY]` (Ready).
### Commit messages
-Follow [commit message hygiene][hygiene] to *make reviews easier* and to make
-the VCS/git logs more valuable.
+Follow the [convential commits guidelines][conventional_commits] to *make reviews easier* and to make
+the VCS/git logs more valuable. The general structure of a commit message is as follows:
-- Try to keep the first line under 72 characters.
-- **Prefix the commit subject with a _scope_:** `doc:`, `test:`, `foo.c:`,
+```
+<type>([optional scope]): <description>
+
+[optional body]
+
+[optional footer(s)]
+```
+
+- **Prefix the commit subject with a _type_:** `doc:`, `test:`
`runtime:`, ...
- Subject line for commits with only style/lint changes can be a single
word: `style` or `lint`.
+- **Add the optional scope following <type> if possible:** `(lsp)`, `(treesitter)`, `(multigrid)`, ...
+- Try to keep the first line under 72 characters.
- A blank line must separate the subject from the description.
+- Breaking changes must be indicated at the very beginning of the footer or body section of a commit. A breaking change must consist of the uppercase text BREAKING CHANGE, followed by a colon, a space, and a description of what has changed about the API.
+- Check your commit message for spelling and grammatical mistakes.
- Use the _imperative voice_: "Fix bug" rather than "Fixed bug" or "Fixes bug."
### Automated builds (CI)
@@ -222,7 +233,7 @@ as context, use the `-W` argument as well.
[github-issues]: https://github.com/neovim/neovim/issues
[1820]: https://github.com/neovim/neovim/pull/1820
[hub]: https://hub.github.com/
-[hygiene]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
+[conventional_commits]: https://www.conventionalcommits.org
[style-guide]: http://neovim.io/develop/style-guide.xml
[ASan]: http://clang.llvm.org/docs/AddressSanitizer.html
[run-tests]: https://github.com/neovim/neovim/blob/master/test/README.md#running-tests
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..49c6d06fbf 100644
--- a/runtime/doc/change.txt
+++ b/runtime/doc/change.txt
@@ -1007,6 +1007,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 +1019,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 +1104,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/eval.txt b/runtime/doc/eval.txt
index b7214d1390..5397846042 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -8230,9 +8230,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 +8253,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()*
diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt
index 17258f896d..c54b588e6b 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"
diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt
index a937cfee98..ebc7e2a1b4 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".
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..2110b88330 100644
--- a/runtime/doc/starting.txt
+++ b/runtime/doc/starting.txt
@@ -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/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/filetype.vim b/runtime/filetype.vim
index ed70ac5ce8..89cc1a8fc5 100644
--- a/runtime/filetype.vim
+++ b/runtime/filetype.vim
@@ -2303,6 +2303,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/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua
index dabe400e0d..05b68472e7 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.min(
+ diagnostic.range.start.line, buf_line_count - 1
+ )
+ diagnostic.range["end"].line = math.min(
+ diagnostic.range["end"].line, buf_line_count - 1
+ )
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/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/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index ce09d268ea..4f9a9fcd68 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -9661,6 +9661,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 +9681,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 +9699,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 +9721,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 +9761,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/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..9abeee47f4 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,13 @@ 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");
+ } else {
+ source_using_linegetter((void *)&cookie, get_buffer_line,
+ ":source (no file)");
+ }
}
/// ":source" and associated commands.
@@ -2769,7 +2781,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,10 +3001,15 @@ 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);
- retval = OK;
+ if (path_with_extension((const char *)fname, "lua")) {
+ // Source the file as lua
+ retval = (int)nlua_exec_file((const char *)fname);
+ } 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) {
// Get "si" again, "script_items" may have been reallocated.
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..afc387ef38 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -1161,6 +1161,24 @@ static void nlua_typval_exec(const char *lcmd, size_t lcmd_len,
}
}
+int nlua_source_using_linegetter(LineGetter fgetline,
+ void *cookie, char *name)
+{
+ 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);
+ 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/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/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 2c8c7f0567..f2f6803665 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/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/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/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_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_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_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_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('kóopërÿnôven'))
+ 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('kóopërÿnôven'))
+ 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_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_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/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..a03e1ae9ce 100644
--- a/test/functional/ex_cmds/source_spec.lua
+++ b/test/functional/ex_cmds/source_spec.lua
@@ -6,6 +6,9 @@ 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
describe(':source', function()
before_each(function()
@@ -44,4 +47,47 @@ 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)
end)
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/ui/float_spec.lua b/test/functional/ui/float_spec.lua
index f3b840da21..3a9dcb8b01 100644
--- a/test/functional/ui/float_spec.lua
+++ b/test/functional/ui/float_spec.lua
@@ -2901,6 +2901,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/third-party/CMakeLists.txt b/third-party/CMakeLists.txt
index e248232909..df7f1a0ac2 100644
--- a/third-party/CMakeLists.txt
+++ b/third-party/CMakeLists.txt
@@ -199,9 +199,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)