aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml16
-rw-r--r--.github/workflows/reviews.yml2
-rw-r--r--.github/workflows/stale.yml25
-rw-r--r--contrib/luarc.json11
-rw-r--r--runtime/autoload/provider/clipboard.vim12
-rw-r--r--runtime/autoload/tutor.vim2
-rw-r--r--runtime/doc/api.txt6
-rw-r--r--runtime/doc/autocmd.txt4
-rw-r--r--runtime/doc/editing.txt2
-rw-r--r--runtime/doc/lua.txt3
-rw-r--r--runtime/doc/news.txt8
-rw-r--r--runtime/doc/options.txt35
-rw-r--r--runtime/doc/tagsrch.txt2
-rw-r--r--runtime/doc/vim_diff.txt1
-rw-r--r--runtime/lua/vim/filetype.lua1
-rw-r--r--runtime/lua/vim/ui.lua15
-rw-r--r--src/nvim/api/command.c1
-rw-r--r--src/nvim/api/keysets.lua2
-rw-r--r--src/nvim/api/options.c2
-rw-r--r--src/nvim/api/vim.c3
-rw-r--r--src/nvim/api/win_config.c118
-rw-r--r--src/nvim/auevents.lua1
-rw-r--r--src/nvim/autocmd.c4
-rw-r--r--src/nvim/buffer.c28
-rw-r--r--src/nvim/buffer_defs.h16
-rw-r--r--src/nvim/context.c2
-rw-r--r--src/nvim/debugger.c1
-rw-r--r--src/nvim/decoration.h1
-rw-r--r--src/nvim/drawscreen.c31
-rw-r--r--src/nvim/eval.c74
-rw-r--r--src/nvim/eval/executor.c4
-rw-r--r--src/nvim/eval/funcs.c42
-rw-r--r--src/nvim/eval/userfunc.c48
-rw-r--r--src/nvim/eval/vars.c23
-rw-r--r--src/nvim/ex_cmds.c24
-rw-r--r--src/nvim/ex_cmds_defs.h1
-rw-r--r--src/nvim/ex_docmd.c9
-rw-r--r--src/nvim/extmark_defs.h2
-rw-r--r--src/nvim/generators/gen_options.lua5
-rw-r--r--src/nvim/hardcopy.c14
-rw-r--r--src/nvim/highlight_defs.h2
-rw-r--r--src/nvim/highlight_group.c1
-rw-r--r--src/nvim/insexpand.c115
-rw-r--r--src/nvim/lua/executor.c3
-rw-r--r--src/nvim/mbyte.c2
-rw-r--r--src/nvim/memline.c14
-rw-r--r--src/nvim/msgpack_rpc/helpers.c5
-rw-r--r--src/nvim/normal.c2
-rw-r--r--src/nvim/ops.c7
-rw-r--r--src/nvim/option.c33
-rw-r--r--src/nvim/option_defs.h5
-rw-r--r--src/nvim/options.lua9
-rw-r--r--src/nvim/optionstr.c17
-rw-r--r--src/nvim/os/fileio.c8
-rw-r--r--src/nvim/os/fileio.h1
-rw-r--r--src/nvim/os/fs.c31
-rw-r--r--src/nvim/os/stdpaths.c3
-rw-r--r--src/nvim/quickfix.c13
-rw-r--r--src/nvim/regexp.c269
-rw-r--r--src/nvim/regexp_bt.c604
-rw-r--r--src/nvim/regexp_defs.h70
-rw-r--r--src/nvim/regexp_nfa.c414
-rw-r--r--src/nvim/screen.c52
-rw-r--r--src/nvim/shada.c55
-rw-r--r--src/nvim/spell.c2
-rw-r--r--src/nvim/spellfile.c9
-rw-r--r--src/nvim/statusline.c123
-rw-r--r--src/nvim/strings.c20
-rw-r--r--src/nvim/syntax.c19
-rw-r--r--src/nvim/tag.c68
-rw-r--r--src/nvim/terminal.c12
-rw-r--r--src/nvim/testdir/Makefile3
-rw-r--r--src/nvim/testdir/check.vim9
-rw-r--r--src/nvim/testdir/test42.inbin2387 -> 0 bytes
-rw-r--r--src/nvim/testdir/test42.okbin409 -> 0 bytes
-rw-r--r--src/nvim/testdir/test_autocmd.vim13
-rw-r--r--src/nvim/testdir/test_backup.vim27
-rw-r--r--src/nvim/testdir/test_buffer.vim20
-rw-r--r--src/nvim/testdir/test_bufline.vim16
-rw-r--r--src/nvim/testdir/test_cmdline.vim21
-rw-r--r--src/nvim/testdir/test_compiler.vim8
-rw-r--r--src/nvim/testdir/test_delete.vim14
-rw-r--r--src/nvim/testdir/test_diffmode.vim4
-rw-r--r--src/nvim/testdir/test_edit.vim3
-rw-r--r--src/nvim/testdir/test_excmd.vim18
-rw-r--r--src/nvim/testdir/test_expr.vim7
-rw-r--r--src/nvim/testdir/test_fileformat.vim9
-rw-r--r--src/nvim/testdir/test_filetype.vim1
-rw-r--r--src/nvim/testdir/test_fold.vim4
-rw-r--r--src/nvim/testdir/test_functions.vim1
-rw-r--r--src/nvim/testdir/test_help.vim54
-rw-r--r--src/nvim/testdir/test_help_tagjump.vim16
-rw-r--r--src/nvim/testdir/test_ins_complete.vim672
-rw-r--r--src/nvim/testdir/test_normal.vim248
-rw-r--r--src/nvim/testdir/test_quickfix.vim133
-rw-r--r--src/nvim/testdir/test_sleep.vim1
-rw-r--r--src/nvim/testdir/test_startup.vim15
-rw-r--r--src/nvim/testdir/test_tagfunc.vim250
-rw-r--r--src/nvim/testdir/test_textformat.vim16
-rw-r--r--src/nvim/testdir/test_viminfo.vim5
-rw-r--r--src/nvim/testdir/test_writefile.vim251
-rw-r--r--src/nvim/testdir/vim9.vim80
-rw-r--r--src/nvim/viml/parser/expressions.c34
-rw-r--r--src/nvim/window.c4
-rw-r--r--test/functional/api/command_spec.lua10
-rw-r--r--test/functional/ex_cmds/write_spec.lua27
-rw-r--r--test/functional/lua/ffi_spec.lua14
-rw-r--r--test/functional/lua/ui_spec.lua46
-rw-r--r--test/functional/terminal/buffer_spec.lua8
-rw-r--r--test/functional/ui/cursor_spec.lua2
-rw-r--r--test/functional/ui/float_spec.lua194
-rw-r--r--test/functional/vimscript/writefile_spec.lua20
-rw-r--r--test/unit/buffer_spec.lua3
113 files changed, 3532 insertions, 1338 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c4f7f9da36..53d52ec55a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -31,7 +31,6 @@ jobs:
- name: Install apt packages
run: |
- sudo add-apt-repository ppa:neovim-ppa/stable
sudo apt-get update
sudo apt-get install -y \
autoconf \
@@ -39,23 +38,9 @@ jobs:
build-essential \
cmake \
gettext \
- libluajit-5.1-dev \
- libmsgpack-dev \
- libtermkey-dev \
libtool-bin \
- libtree-sitter-dev \
- libunibilium-dev \
- libuv1-dev \
- libvterm-dev \
locales \
- lua-busted \
lua-check \
- lua-filesystem \
- lua-inspect \
- lua-lpeg \
- lua-luv-dev \
- lua-nvim \
- luajit \
ninja-build \
pkg-config
@@ -173,7 +158,6 @@ jobs:
libvterm-dev \
locales \
lua-busted \
- lua-check \
lua-filesystem \
lua-inspect \
lua-lpeg \
diff --git a/.github/workflows/reviews.yml b/.github/workflows/reviews.yml
index 34ce19d830..6c8c7ff8ed 100644
--- a/.github/workflows/reviews.yml
+++ b/.github/workflows/reviews.yml
@@ -1,7 +1,7 @@
name: "Request reviews"
on:
pull_request_target:
- types: [labeled, ready_for_review]
+ types: [labeled, ready_for_review, reopened]
jobs:
request-reviewer:
if: github.event.pull_request.state == 'open' && github.event.pull_request.draft == false
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
new file mode 100644
index 0000000000..a10f73531d
--- /dev/null
+++ b/.github/workflows/stale.yml
@@ -0,0 +1,25 @@
+name: 'Close stale issues and PRs'
+on:
+ schedule:
+ - cron: '30 1 * * *' # Run every day at 01:30
+ workflow_dispatch:
+
+jobs:
+ stale:
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ pull-requests: write
+ steps:
+ - uses: actions/stale@v6
+ with:
+ days-before-close: 30
+ days-before-stale: -1
+ stale-issue-label: needs:response
+ stale-pr-label: needs:response
+ close-issue-message: "This issue has been closed since a request for
+ information has not been answered for 30 days. It can be reopened
+ when the requested information is provided."
+ close-pr-message: "This PR has been closed since a request for
+ changes has not been answered for 30 days. It can be reopened when
+ the requested changes are provided."
diff --git a/contrib/luarc.json b/contrib/luarc.json
index 8d76d1261d..68d1d5800a 100644
--- a/contrib/luarc.json
+++ b/contrib/luarc.json
@@ -10,13 +10,16 @@
"before_each",
"after_each",
"setup",
- "teardown"
+ "teardown",
+ "lfs"
]
},
"workspace": {
- "library": {
- "runtime/lua": true
- },
+ "library": [
+ "runtime/lua",
+ "${3rd}/lfs/library"
+ ],
+ "checkThirdParty": false,
"maxPreload": 2000,
"preloadFileSize": 1000
},
diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim
index 026c01bce6..e4161290d9 100644
--- a/runtime/autoload/provider/clipboard.vim
+++ b/runtime/autoload/provider/clipboard.vim
@@ -97,18 +97,18 @@ function! provider#clipboard#Executable() abort
let s:copy['*'] = ['wl-copy', '--foreground', '--primary', '--type', 'text/plain']
let s:paste['*'] = ['wl-paste', '--no-newline', '--primary']
return 'wl-copy'
- elseif !empty($DISPLAY) && executable('xclip')
- let s:copy['+'] = ['xclip', '-quiet', '-i', '-selection', 'clipboard']
- let s:paste['+'] = ['xclip', '-o', '-selection', 'clipboard']
- let s:copy['*'] = ['xclip', '-quiet', '-i', '-selection', 'primary']
- let s:paste['*'] = ['xclip', '-o', '-selection', 'primary']
- return 'xclip'
elseif !empty($DISPLAY) && executable('xsel') && s:cmd_ok('xsel -o -b')
let s:copy['+'] = ['xsel', '--nodetach', '-i', '-b']
let s:paste['+'] = ['xsel', '-o', '-b']
let s:copy['*'] = ['xsel', '--nodetach', '-i', '-p']
let s:paste['*'] = ['xsel', '-o', '-p']
return 'xsel'
+ elseif !empty($DISPLAY) && executable('xclip')
+ let s:copy['+'] = ['xclip', '-quiet', '-i', '-selection', 'clipboard']
+ let s:paste['+'] = ['xclip', '-o', '-selection', 'clipboard']
+ let s:copy['*'] = ['xclip', '-quiet', '-i', '-selection', 'primary']
+ let s:paste['*'] = ['xclip', '-o', '-selection', 'primary']
+ return 'xclip'
elseif executable('lemonade')
let s:copy['+'] = ['lemonade', 'copy']
let s:paste['+'] = ['lemonade', 'paste']
diff --git a/runtime/autoload/tutor.vim b/runtime/autoload/tutor.vim
index abf5c5e2c8..4da4213826 100644
--- a/runtime/autoload/tutor.vim
+++ b/runtime/autoload/tutor.vim
@@ -104,7 +104,7 @@ function! tutor#CheckLine(line)
if exists('b:tutor_metadata') && has_key(b:tutor_metadata, 'expect')
let bufn = bufnr('%')
let ctext = getline(a:line)
- let signs = sign_getplaced('.', {'lnum': a:line})[0].signs
+ let signs = sign_getplaced(bufn, {'lnum': a:line})[0].signs
if !empty(signs)
call sign_unplace('', {'id': signs[0].id})
endif
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index 8928650a39..9faff82f06 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -1757,6 +1757,7 @@ nvim_create_user_command({name}, {command}, {*opts})
executed. When called from Lua, the command can also be a
Lua function. The function is called with a single table
argument that contains the following keys:
+ • name: (string) Command name
• args: (string) The args passed to the command, if any
|<args>|
• fargs: (table) The args split by unescaped whitespace
@@ -3068,6 +3069,11 @@ nvim_open_win({buffer}, {enter}, {*config}) *nvim_open_win()*
specified by character: [ {"+", "MyCorner"}, {"x",
"MyBorder"} ].
+ • title: Title (optional) in window border, String or list.
+ List is [text, highlight] tuples. if is string the default
+ highlight group is `FloatTitle`.
+ • title_pos: Title position must set with title option.
+ value can be of `left` `center` `right` default is left.
• noautocmd: If true then no buffer-related autocommand
events such as |BufEnter|, |BufLeave| or |BufWinEnter| may
fire from calling this function.
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt
index da41c92df6..c30c190301 100644
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -996,6 +996,10 @@ TextChangedP After a change was made to the text in the
current buffer in Insert mode, only when the
popup menu is visible. Otherwise the same as
TextChanged.
+ *TextChangedT*
+TextChangedT After a change was made to the text in the
+ current buffer in |Terminal-mode|. Otherwise
+ the same as TextChanged.
*TextYankPost*
TextYankPost Just after a |yank| or |deleting| command, but not
if the black hole register |quote_| is used nor
diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt
index 58a5cbc60c..76c528ef3c 100644
--- a/runtime/doc/editing.txt
+++ b/runtime/doc/editing.txt
@@ -438,6 +438,8 @@ Where {optname} is one of: *++ff* *++enc* *++bin* *++nobin* *++edit*
bad specifies behavior for bad characters
edit for |:read| only: keep option values as if editing
a file
+ p creates the parent directory (or directories) of
+ a filename if they do not exist
{value} cannot contain white space. It can be any valid value for these
options. Examples: >
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index c4e139bca7..cab2f49d94 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -1979,7 +1979,8 @@ input({opts}, {on_confirm}) *vim.ui.input()*
highlighting user inputs.
• {on_confirm} (function) ((input|nil) -> ()) Called once the user
confirms or abort the input. `input` is what the user
- typed. `nil` if the user aborted the dialog.
+ typed (it might be an empty string if nothing was
+ entered), or `nil` if the user aborted the dialog.
select({items}, {opts}, {on_choice}) *vim.ui.select()*
Prompts the user to pick a single item from a collection of entries
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 5f29fa8da9..d339df8479 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -41,9 +41,17 @@ The following new APIs or features were added.
• When using Nvim inside tmux 3.2 or later, the default clipboard provider
will now copy to the system clipboard. |provider-clipboard|
+
• 'splitkeep' option to control the scroll behavior of horizontal splits.
+
• |nvim_select_popupmenu_item()| now supports |cmdline-completion| popup menu.
+• |'diffopt'| now includes a `linematch` option to enable a second-stage diff
+ on individual hunks to provide much more accurate diffs. This option is also
+ available to |vim.diff()|
+
+ See https://github.com/neovim/neovim/pull/14537.
+
==============================================================================
CHANGED FEATURES *news-changes*
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 818c6d0115..4a277c146f 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -313,14 +313,23 @@ Note: In the future more global options can be made |global-local|. Using
*option-value-function*
-Some options ('completefunc', 'imactivatefunc', 'imstatusfunc', 'omnifunc',
-'operatorfunc', 'quickfixtextfunc' and 'tagfunc') are set to a function name
-or a function reference or a lambda function. Examples:
+Some options ('completefunc', 'omnifunc', 'operatorfunc', 'quickfixtextfunc',
+'tagfunc' and 'thesaurusfunc') are set to a function name or a function
+reference or a lambda function. When using a lambda it will be converted to
+the name, e.g. "<lambda>123". Examples:
>
set opfunc=MyOpFunc
- set opfunc=function("MyOpFunc")
- set opfunc=funcref("MyOpFunc")
- set opfunc={t\ ->\ MyOpFunc(t)}
+ set opfunc=function('MyOpFunc')
+ set opfunc=funcref('MyOpFunc')
+ set opfunc={a\ ->\ MyOpFunc(a)}
+ " set using a funcref variable
+ let Fn = function('MyTagFunc')
+ let &tagfunc = string(Fn)
+ " set using a lambda expression
+ let &tagfunc = {t -> MyTagFunc(t)}
+ " set using a variable with lambda expression
+ let L = {a, b, c -> MyTagFunc(a, b , c)}
+ let &tagfunc = L
<
Setting the filetype
@@ -1446,7 +1455,9 @@ A jump table for the options with a short description can be found at |Q_op|.
This option specifies a function to be used for Insert mode completion
with CTRL-X CTRL-U. |i_CTRL-X_CTRL-U|
See |complete-functions| for an explanation of how the function is
- invoked and what it should return.
+ invoked and what it should return. The value can be the name of a
+ function, a |lambda| or a |Funcref|. See |option-value-function| for
+ more information.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
@@ -4413,7 +4424,9 @@ A jump table for the options with a short description can be found at |Q_op|.
This option specifies a function to be used for Insert mode omni
completion with CTRL-X CTRL-O. |i_CTRL-X_CTRL-O|
See |complete-functions| for an explanation of how the function is
- invoked and what it should return.
+ invoked and what it should return. The value can be the name of a
+ function, a |lambda| or a |Funcref|. See |option-value-function| for
+ more information.
This option is usually set by a filetype plugin:
|:filetype-plugin-on|
This option cannot be set from a |modeline| or in the |sandbox|, for
@@ -6443,7 +6456,9 @@ A jump table for the options with a short description can be found at |Q_op|.
This option specifies a function to be used to perform tag searches.
The function gets the tag pattern and should return a List of matching
tags. See |tag-function| for an explanation of how to write the
- function and an example.
+ function and an example. The value can be the name of a function, a
+ |lambda| or a |Funcref|. See |option-value-function| for more
+ information.
*'taglength'* *'tl'*
'taglength' 'tl' number (default 0)
@@ -6566,6 +6581,8 @@ A jump table for the options with a short description can be found at |Q_op|.
global or local to buffer |global-local|
This option specifies a function to be used for thesaurus completion
with CTRL-X CTRL-T. |i_CTRL-X_CTRL-T| See |compl-thesaurusfunc|.
+ The value can be the name of a function, a |lambda| or a |Funcref|.
+ See |option-value-function| for more information.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
diff --git a/runtime/doc/tagsrch.txt b/runtime/doc/tagsrch.txt
index d079db0717..aab6f78315 100644
--- a/runtime/doc/tagsrch.txt
+++ b/runtime/doc/tagsrch.txt
@@ -909,6 +909,8 @@ If the function returns |v:null| instead of a List, a standard tag lookup will
be performed instead.
It is not allowed to change the tagstack from inside 'tagfunc'. *E986*
+It is not allowed to close a window or change window from inside 'tagfunc'.
+*E1299*
The following is a hypothetical example of a function used for 'tagfunc'. It
uses the output of |taglist()| to generate the result: a list of tags in the
diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
index da2b5e2466..fe6c28c809 100644
--- a/runtime/doc/vim_diff.txt
+++ b/runtime/doc/vim_diff.txt
@@ -267,6 +267,7 @@ Options:
'tabline' %@Func@foo%X can call any function on mouse-click
'winblend' pseudo-transparency in floating windows |api-floatwin|
'winhighlight' window-local highlights
+ 'diffopt' has the option `linematch`.
Signs:
Signs are removed if the associated line is deleted.
diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index 56659eff04..5aa968e211 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -1057,6 +1057,7 @@ local extension = {
hdl = 'vhdl',
vho = 'vhdl',
vbe = 'vhdl',
+ tape = 'vhs',
vim = 'vim',
vba = 'vim',
mar = 'vmasm',
diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua
index 6f1ce3089d..d9a3963afc 100644
--- a/runtime/lua/vim/ui.lua
+++ b/runtime/lua/vim/ui.lua
@@ -73,7 +73,8 @@ end
--- user inputs.
---@param on_confirm function ((input|nil) -> ())
--- Called once the user confirms or abort the input.
---- `input` is what the user typed.
+--- `input` is what the user typed (it might be
+--- an empty string if nothing was entered), or
--- `nil` if the user aborted the dialog.
---
--- Example:
@@ -88,11 +89,17 @@ function M.input(opts, on_confirm)
})
opts = (opts and not vim.tbl_isempty(opts)) and opts or vim.empty_dict()
+
+ -- Note that vim.fn.input({}) returns an empty string when cancelled.
+ -- vim.ui.input() should distinguish aborting from entering an empty string.
+ local _canceled = vim.NIL
+ opts = vim.tbl_extend('keep', opts, { cancelreturn = _canceled })
+
local input = vim.fn.input(opts)
- if #input > 0 then
- on_confirm(input)
- else
+ if input == _canceled then
on_confirm(nil)
+ else
+ on_confirm(input)
end
end
diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c
index 8cd2c0f8b8..752d3868d5 100644
--- a/src/nvim/api/command.c
+++ b/src/nvim/api/command.c
@@ -884,6 +884,7 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin
/// @param command Replacement command to execute when this user command is executed. When called
/// from Lua, the command can also be a Lua function. The function is called with a
/// single table argument that contains the following keys:
+/// - name: (string) Command name
/// - args: (string) The args passed to the command, if any |<args>|
/// - fargs: (table) The args split by unescaped whitespace (when more than one
/// argument is allowed), if any |<f-args>|
diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua
index ea8949bd2c..7e0d399573 100644
--- a/src/nvim/api/keysets.lua
+++ b/src/nvim/api/keysets.lua
@@ -81,6 +81,8 @@ return {
"focusable";
"zindex";
"border";
+ "title";
+ "title_pos";
"style";
"noautocmd";
};
diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c
index ec1f19cf6a..92f20fff48 100644
--- a/src/nvim/api/options.c
+++ b/src/nvim/api/options.c
@@ -487,7 +487,7 @@ static getoption_T access_option_value(char *key, long *numval, char **stringval
bool get, Error *err)
{
if (get) {
- return get_option_value(key, numval, stringval, opt_flags);
+ return get_option_value(key, numval, stringval, NULL, opt_flags);
} else {
char *errmsg;
if ((errmsg = set_option_value(key, *numval, *stringval, opt_flags))) {
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index beb48b8d7d..d3f8c768a0 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -2223,7 +2223,8 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
buf,
sizeof(buf),
str.data,
- false,
+ NULL,
+ 0,
fillchar,
maxwidth,
hltab_ptr,
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c
index 636b9566ce..648048e970 100644
--- a/src/nvim/api/win_config.c
+++ b/src/nvim/api/win_config.c
@@ -2,14 +2,17 @@
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
#include <assert.h>
+#include <inttypes.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
+#include "nvim/api/extmark.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/win_config.h"
#include "nvim/ascii.h"
+#include "nvim/buffer_defs.h"
#include "nvim/drawscreen.h"
#include "nvim/highlight_group.h"
#include "nvim/option.h"
@@ -134,6 +137,11 @@
/// By default, `FloatBorder` highlight is used, which links to `WinSeparator`
/// when not defined. It could also be specified by character:
/// [ {"+", "MyCorner"}, {"x", "MyBorder"} ].
+/// - title: Title (optional) in window border, String or list.
+/// List is [text, highlight] tuples. if is string the default
+/// highlight group is `FloatTitle`.
+/// - title_pos: Title position must set with title option.
+/// value can be of `left` `center` `right` default is left.
/// - noautocmd: If true then no buffer-related autocommand events such as
/// |BufEnter|, |BufLeave| or |BufWinEnter| may fire from
/// calling this function.
@@ -273,6 +281,21 @@ Dictionary nvim_win_get_config(Window window, Error *err)
}
}
PUT(rv, "border", ARRAY_OBJ(border));
+ if (config->title) {
+ Array titles = ARRAY_DICT_INIT;
+ VirtText title_datas = config->title_chunks;
+ for (size_t i = 0; i < title_datas.size; i++) {
+ Array tuple = ARRAY_DICT_INIT;
+ ADD(tuple, CSTR_TO_OBJ((const char *)title_datas.items[i].text));
+ if (title_datas.items[i].hl_id > 0) {
+ ADD(tuple,
+ STRING_OBJ(cstr_to_string((const char *)syn_id2name(title_datas.items[i].hl_id))));
+ }
+ ADD(titles, ARRAY_OBJ(tuple));
+ }
+ PUT(rv, "title", ARRAY_OBJ(titles));
+ PUT(rv, "title_pos", INTEGER_OBJ(config->title_pos));
+ }
}
}
@@ -330,7 +353,75 @@ static bool parse_float_bufpos(Array bufpos, lpos_T *out)
return true;
}
-static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
+static void parse_border_title(Object title, Object title_pos, FloatConfig *fconfig, Error *err)
+{
+ if (!parse_title_pos(title_pos, fconfig, err)) {
+ return;
+ }
+
+ if (title.type == kObjectTypeString) {
+ if (title.data.string.size == 0) {
+ fconfig->title = false;
+ return;
+ }
+ int hl_id = syn_check_group(S_LEN("FloatTitle"));
+ kv_push(fconfig->title_chunks, ((VirtTextChunk){ .text = xstrdup(title.data.string.data),
+ .hl_id = hl_id }));
+ fconfig->title_width = (int)mb_string2cells(title.data.string.data);
+ fconfig->title = true;
+ return;
+ }
+
+ if (title.type != kObjectTypeArray) {
+ api_set_error(err, kErrorTypeValidation, "title must be string or array");
+ return;
+ }
+
+ if (title.type == kObjectTypeArray && title.data.array.size == 0) {
+ api_set_error(err, kErrorTypeValidation, "title cannot be an empty array");
+ return;
+ }
+
+ fconfig->title_width = 0;
+ fconfig->title_chunks = parse_virt_text(title.data.array, err, &fconfig->title_width);
+
+ fconfig->title = true;
+ return;
+}
+
+static bool parse_title_pos(Object title_pos, FloatConfig *fconfig, Error *err)
+{
+ if (!HAS_KEY(title_pos)) {
+ fconfig->title_pos = kAlignLeft;
+ return true;
+ }
+
+ if (title_pos.type != kObjectTypeString) {
+ api_set_error(err, kErrorTypeValidation, "title_pos must be string");
+ return false;
+ }
+
+ if (title_pos.data.string.size == 0) {
+ fconfig->title_pos = kAlignLeft;
+ return true;
+ }
+
+ char *pos = title_pos.data.string.data;
+
+ if (strequal(pos, "left")) {
+ fconfig->title_pos = kAlignLeft;
+ } else if (strequal(pos, "center")) {
+ fconfig->title_pos = kAlignCenter;
+ } else if (strequal(pos, "right")) {
+ fconfig->title_pos = kAlignRight;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "invalid title_pos value");
+ return false;
+ }
+ return true;
+}
+
+static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
{
struct {
const char *name;
@@ -414,6 +505,8 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
String str = style.data.string;
if (str.size == 0 || strequal(str.data, "none")) {
fconfig->border = false;
+ // title does not work with border equal none
+ fconfig->title = false;
return;
}
for (size_t i = 0; defaults[i].name; i++) {
@@ -603,6 +696,29 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig,
return false;
}
+ if (HAS_KEY(config->title_pos)) {
+ if (!HAS_KEY(config->title)) {
+ api_set_error(err, kErrorTypeException, "title_pos requires title to be set");
+ return false;
+ }
+ }
+
+ if (HAS_KEY(config->title)) {
+ // title only work with border
+ if (!HAS_KEY(config->border) && !fconfig->border) {
+ api_set_error(err, kErrorTypeException, "title requires border to be set");
+ return false;
+ }
+
+ if (fconfig->title) {
+ clear_virttext(&fconfig->title_chunks);
+ }
+ parse_border_title(config->title, config->title_pos, fconfig, err);
+ if (ERROR_SET(err)) {
+ return false;
+ }
+ }
+
if (HAS_KEY(config->border)) {
parse_border_style(config->border, fconfig, err);
if (ERROR_SET(err)) {
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index 93a870fe04..65c22c922a 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -108,6 +108,7 @@ return {
'TextChanged', -- text was modified
'TextChangedI', -- text was modified in Insert mode(no popup)
'TextChangedP', -- text was modified in Insert mode(popup)
+ 'TextChangedT', -- text was modified in Terminal mode
'TextYankPost', -- after a yank or delete was done (y, d, c)
'UIEnter', -- after UI attaches
'UILeave', -- after UI detaches
diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c
index 5b5ea43d86..9845e6be13 100644
--- a/src/nvim/autocmd.c
+++ b/src/nvim/autocmd.c
@@ -779,7 +779,7 @@ void au_event_restore(char *old_ei)
// :autocmd * *.c show all autocommands for *.c files.
//
// Mostly a {group} argument can optionally appear before <event>.
-void do_autocmd(char *arg_in, int forceit)
+void do_autocmd(exarg_T *eap, char *arg_in, int forceit)
{
char *arg = arg_in;
char *envpat = NULL;
@@ -790,6 +790,7 @@ void do_autocmd(char *arg_in, int forceit)
int group;
if (*arg == '|') {
+ eap->nextcmd = arg + 1;
arg = "";
group = AUGROUP_ALL; // no argument, use all groups
} else {
@@ -806,6 +807,7 @@ void do_autocmd(char *arg_in, int forceit)
pat = skipwhite(pat);
if (*pat == '|') {
+ eap->nextcmd = pat + 1;
pat = "";
cmd = "";
} else {
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index ade5c35450..fa0b2a83c8 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -1971,8 +1971,11 @@ void free_buf_options(buf_T *buf, int free_p_ff)
clear_string_option(&buf->b_p_cinw);
clear_string_option(&buf->b_p_cpt);
clear_string_option(&buf->b_p_cfu);
+ callback_free(&buf->b_cfu_cb);
clear_string_option(&buf->b_p_ofu);
+ callback_free(&buf->b_ofu_cb);
clear_string_option(&buf->b_p_tsrfu);
+ callback_free(&buf->b_tsrfu_cb);
clear_string_option(&buf->b_p_gp);
clear_string_option(&buf->b_p_mp);
clear_string_option(&buf->b_p_efm);
@@ -1981,6 +1984,7 @@ void free_buf_options(buf_T *buf, int free_p_ff)
clear_string_option(&buf->b_p_tags);
clear_string_option(&buf->b_p_tc);
clear_string_option(&buf->b_p_tfu);
+ callback_free(&buf->b_tfu_cb);
clear_string_option(&buf->b_p_dict);
clear_string_option(&buf->b_p_tsr);
clear_string_option(&buf->b_p_qe);
@@ -3199,17 +3203,9 @@ void maketitle(void)
if (*p_titlestring != NUL) {
if (stl_syntax & STL_IN_TITLE) {
- int use_sandbox = false;
- const int called_emsg_before = called_emsg;
-
- use_sandbox = was_set_insecurely(curwin, "titlestring", 0);
- build_stl_str_hl(curwin, buf, sizeof(buf),
- p_titlestring, use_sandbox,
- 0, maxlen, NULL, NULL);
+ build_stl_str_hl(curwin, buf, sizeof(buf), p_titlestring,
+ "titlestring", 0, 0, maxlen, NULL, NULL);
title_str = buf;
- if (called_emsg > called_emsg_before) {
- set_string_option_direct("titlestring", -1, "", OPT_FREE, SID_ERROR);
- }
} else {
title_str = p_titlestring;
}
@@ -3313,16 +3309,8 @@ void maketitle(void)
icon_str = buf;
if (*p_iconstring != NUL) {
if (stl_syntax & STL_IN_ICON) {
- int use_sandbox = false;
- const int called_emsg_before = called_emsg;
-
- use_sandbox = was_set_insecurely(curwin, "iconstring", 0);
- build_stl_str_hl(curwin, icon_str, sizeof(buf),
- p_iconstring, use_sandbox,
- 0, 0, NULL, NULL);
- if (called_emsg > called_emsg_before) {
- set_string_option_direct("iconstring", -1, "", OPT_FREE, SID_ERROR);
- }
+ build_stl_str_hl(curwin, icon_str, sizeof(buf), p_iconstring,
+ "iconstring", 0, 0, 0, NULL, NULL);
} else {
icon_str = p_iconstring;
}
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 4e46a1aef2..6448c6b6f6 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -44,6 +44,8 @@ typedef struct {
#include "klib/kvec.h"
// for marktree
#include "nvim/marktree.h"
+// for float window title
+#include "nvim/extmark_defs.h"
#define GETFILE_SUCCESS(x) ((x) <= 0)
#define MODIFIABLE(buf) (buf->b_p_ma)
@@ -673,8 +675,11 @@ struct file_buffer {
char *b_p_csl; ///< 'completeslash'
#endif
char *b_p_cfu; ///< 'completefunc'
+ Callback b_cfu_cb; ///< 'completefunc' callback
char *b_p_ofu; ///< 'omnifunc'
+ Callback b_ofu_cb; ///< 'omnifunc' callback
char *b_p_tfu; ///< 'tagfunc'
+ Callback b_tfu_cb; ///< 'tagfunc' callback
int b_p_eof; ///< 'endoffile'
int b_p_eol; ///< 'endofline'
int b_p_fixeol; ///< 'fixendofline'
@@ -746,6 +751,7 @@ struct file_buffer {
char *b_p_dict; ///< 'dictionary' local value
char *b_p_tsr; ///< 'thesaurus' local value
char *b_p_tsrfu; ///< 'thesaurusfunc' local value
+ Callback b_tsrfu_cb; ///< 'thesaurusfunc' callback
long b_p_ul; ///< 'undolevels' local value
int b_p_udf; ///< 'undofile'
char *b_p_lw; ///< 'lispwords' local value
@@ -1048,6 +1054,12 @@ typedef enum {
kWinStyleMinimal, /// Minimal UI: no number column, eob markers, etc
} WinStyle;
+typedef enum {
+ kAlignLeft = 0,
+ kAlignCenter = 1,
+ kAlignRight = 2,
+} AlignTextPos;
+
typedef struct {
Window window;
lpos_T bufpos;
@@ -1060,10 +1072,14 @@ typedef struct {
int zindex;
WinStyle style;
bool border;
+ bool title;
bool shadow;
schar_T border_chars[8];
int border_hl_ids[8];
int border_attr[8];
+ AlignTextPos title_pos;
+ VirtText title_chunks;
+ int title_width;
bool noautocmd;
} FloatConfig;
diff --git a/src/nvim/context.c b/src/nvim/context.c
index 34692cdf64..c765b95733 100644
--- a/src/nvim/context.c
+++ b/src/nvim/context.c
@@ -131,7 +131,7 @@ bool ctx_restore(Context *ctx, const int flags)
}
char *op_shada;
- get_option_value("shada", NULL, &op_shada, OPT_GLOBAL);
+ get_option_value("shada", NULL, &op_shada, NULL, OPT_GLOBAL);
set_option_value("shada", 0L, "!,'100,%", OPT_GLOBAL);
if (flags & kCtxRegs) {
diff --git a/src/nvim/debugger.c b/src/nvim/debugger.c
index 3e0cf82740..e7c376621b 100644
--- a/src/nvim/debugger.c
+++ b/src/nvim/debugger.c
@@ -114,6 +114,7 @@ void do_debug(char *cmd)
for (;;) {
msg_scroll = true;
need_wait_return = false;
+
// Save the current typeahead buffer and replace it with an empty one.
// This makes sure we get input from the user here and don't interfere
// with the commands being executed. Reset "ex_normal_busy" to avoid
diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h
index bdbfd72a81..9ba621d7a4 100644
--- a/src/nvim/decoration.h
+++ b/src/nvim/decoration.h
@@ -28,7 +28,6 @@ typedef enum {
EXTERN const char *const hl_mode_str[] INIT(= { "", "replace", "combine", "blend" });
-typedef kvec_t(VirtTextChunk) VirtText;
#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE)
typedef kvec_t(struct virt_line { VirtText line; bool left_col; }) VirtLines;
diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c
index adf52ef6e4..30abcd1a31 100644
--- a/src/nvim/drawscreen.c
+++ b/src/nvim/drawscreen.c
@@ -60,11 +60,13 @@
#include <string.h>
#include "nvim/buffer.h"
+#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
#include "nvim/diff.h"
#include "nvim/drawscreen.h"
#include "nvim/ex_getln.h"
+#include "nvim/extmark_defs.h"
#include "nvim/grid.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
@@ -614,6 +616,20 @@ int update_screen(void)
return OK;
}
+static void win_border_redr_title(win_T *wp, ScreenGrid *grid, int col)
+{
+ VirtText title_chunks = wp->w_float_config.title_chunks;
+
+ for (size_t i = 0; i < title_chunks.size; i++) {
+ char *text = title_chunks.items[i].text;
+ int cell = (int)mb_string2cells(text);
+ int hl_id = title_chunks.items[i].hl_id;
+ int attr = hl_id ? syn_id2attr(hl_id) : 0;
+ grid_puts(grid, text, 0, col, attr);
+ col += cell;
+ }
+}
+
static void win_redr_border(win_T *wp)
{
wp->w_redr_border = false;
@@ -634,9 +650,24 @@ static void win_redr_border(win_T *wp)
if (adj[3]) {
grid_put_schar(grid, 0, 0, chars[0], attrs[0]);
}
+
for (int i = 0; i < icol; i++) {
grid_put_schar(grid, 0, i + adj[3], chars[1], attrs[1]);
}
+
+ if (wp->w_float_config.title) {
+ int title_col = 0;
+ int title_width = wp->w_float_config.title_width;
+ AlignTextPos title_pos = wp->w_float_config.title_pos;
+
+ if (title_pos == kAlignCenter) {
+ title_col = (icol - title_width) / 2 + 1;
+ } else {
+ title_col = title_pos == kAlignLeft ? 1 : icol - title_width + 1;
+ }
+
+ win_border_redr_title(wp, grid, title_col);
+ }
if (adj[1]) {
grid_put_schar(grid, 0, icol + adj[3], chars[2], attrs[2]);
}
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index f39cebefb2..f5f8840350 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -30,6 +30,7 @@
#include "nvim/ex_session.h"
#include "nvim/getchar.h"
#include "nvim/highlight_group.h"
+#include "nvim/insexpand.h"
#include "nvim/locale.h"
#include "nvim/lua/executor.h"
#include "nvim/mark.h"
@@ -49,6 +50,7 @@
#include "nvim/search.h"
#include "nvim/sign.h"
#include "nvim/syntax.h"
+#include "nvim/tag.h"
#include "nvim/ui.h"
#include "nvim/ui_compositor.h"
#include "nvim/undo.h"
@@ -1110,25 +1112,7 @@ fail:
return ret;
}
-/// Call Vim script function and return the result as a number
-///
-/// @param[in] func Function name.
-/// @param[in] argc Number of arguments.
-/// @param[in] argv Array with typval_T arguments.
-///
-/// @return -1 when calling function fails, result of function otherwise.
-varnumber_T call_func_retnr(const char *func, int argc, typval_T *argv)
- FUNC_ATTR_NONNULL_ALL
-{
- typval_T rettv;
- if (call_vim_function((char *)func, argc, argv, &rettv) == FAIL) {
- return -1;
- }
- varnumber_T retval = tv_get_number_chk(&rettv, NULL);
- tv_clear(&rettv);
- return retval;
-}
/// Call Vim script function and return the result as a string
///
/// @param[in] func Function name.
@@ -1151,6 +1135,7 @@ char *call_func_retstr(const char *const func, int argc, typval_T *argv)
tv_clear(&rettv);
return retval;
}
+
/// Call Vim script function and return the result as a List
///
/// @param[in] func Function name.
@@ -3705,10 +3690,10 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose)
int get_option_tv(const char **const arg, typval_T *const rettv, const bool evaluate)
FUNC_ATTR_NONNULL_ARG(1)
{
- int opt_flags;
+ int scope;
// Isolate the option name and find its value.
- char *option_end = (char *)find_option_end(arg, &opt_flags);
+ char *option_end = (char *)find_option_end(arg, &scope);
if (option_end == NULL) {
if (rettv != NULL) {
semsg(_("E112: Option name missing: %s"), *arg);
@@ -3728,7 +3713,7 @@ int get_option_tv(const char **const arg, typval_T *const rettv, const bool eval
char c = *option_end;
*option_end = NUL;
getoption_T opt_type = get_option_value(*arg, &numval,
- rettv == NULL ? NULL : &stringval, opt_flags);
+ rettv == NULL ? NULL : &stringval, NULL, scope);
if (opt_type == gov_unknown) {
if (rettv != NULL) {
@@ -4184,10 +4169,23 @@ bool garbage_collect(bool testing)
ABORTING(set_ref_dict)(buf->additional_data, copyID);
// buffer callback functions
- set_ref_in_callback(&buf->b_prompt_callback, copyID, NULL, NULL);
- set_ref_in_callback(&buf->b_prompt_interrupt, copyID, NULL, NULL);
+ ABORTING(set_ref_in_callback)(&buf->b_prompt_callback, copyID, NULL, NULL);
+ ABORTING(set_ref_in_callback)(&buf->b_prompt_interrupt, copyID, NULL, NULL);
+ ABORTING(set_ref_in_callback)(&buf->b_cfu_cb, copyID, NULL, NULL);
+ ABORTING(set_ref_in_callback)(&buf->b_ofu_cb, copyID, NULL, NULL);
+ ABORTING(set_ref_in_callback)(&buf->b_tsrfu_cb, copyID, NULL, NULL);
+ ABORTING(set_ref_in_callback)(&buf->b_tfu_cb, copyID, NULL, NULL);
}
+ // 'completefunc', 'omnifunc' and 'thesaurusfunc' callbacks
+ ABORTING(set_ref_in_insexpand_funcs)(copyID);
+
+ // 'operatorfunc' callback
+ ABORTING(set_ref_in_opfunc)(copyID);
+
+ // 'tagfunc' callback
+ ABORTING(set_ref_in_tagfunc)(copyID);
+
FOR_ALL_TAB_WINDOWS(tp, wp) {
// window-local variables
ABORTING(set_ref_in_item)(&wp->w_winvar.di_tv, copyID, NULL, NULL);
@@ -5028,9 +5026,8 @@ void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref)
if ((use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL) || is_funcref) {
name = s;
- trans_name = (char *)trans_function_name(&name, false,
- TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD
- | TFN_NO_DEREF, NULL, NULL);
+ trans_name = save_function_name(&name, false,
+ TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD | TFN_NO_DEREF, NULL);
if (*name != NUL) {
s = NULL;
}
@@ -5877,6 +5874,7 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg)
return true;
}
+/// @return whether the callback could be called.
bool callback_call(Callback *const callback, const int argcount_in, typval_T *const argvars_in,
typval_T *const rettv)
FUNC_ATTR_NONNULL_ALL
@@ -5926,8 +5924,8 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co
return call_func(name, -1, rettv, argcount_in, argvars_in, &funcexe);
}
-static bool set_ref_in_callback(Callback *callback, int copyID, ht_stack_T **ht_stack,
- list_stack_T **list_stack)
+bool set_ref_in_callback(Callback *callback, int copyID, ht_stack_T **ht_stack,
+ list_stack_T **list_stack)
{
typval_T tv;
switch (callback->type) {
@@ -7084,6 +7082,9 @@ char *set_cmdarg(exarg_T *eap, char *oldarg)
if (eap->bad_char != 0) {
len += 7 + 4; // " ++bad=" + "keep" or "drop"
}
+ if (eap->mkdir_p != 0) {
+ len += 4;
+ }
const size_t newval_len = len + 1;
char *newval = xmalloc(newval_len);
@@ -7117,6 +7118,11 @@ char *set_cmdarg(exarg_T *eap, char *oldarg)
snprintf(newval + strlen(newval), newval_len, " ++bad=%c",
eap->bad_char);
}
+
+ if (eap->mkdir_p) {
+ snprintf(newval, newval_len, " ++p");
+ }
+
vimvars[VV_CMDARG].vv_str = newval;
return oldval;
}
@@ -7802,19 +7808,19 @@ void ex_execute(exarg_T *eap)
///
/// @return NULL when no option name found. Otherwise pointer to the char
/// after the option name.
-const char *find_option_end(const char **const arg, int *const opt_flags)
+const char *find_option_end(const char **const arg, int *const scope)
{
const char *p = *arg;
p++;
if (*p == 'g' && p[1] == ':') {
- *opt_flags = OPT_GLOBAL;
+ *scope = OPT_GLOBAL;
p += 2;
} else if (*p == 'l' && p[1] == ':') {
- *opt_flags = OPT_LOCAL;
+ *scope = OPT_LOCAL;
p += 2;
} else {
- *opt_flags = 0;
+ *scope = 0;
}
if (!ASCII_ISALPHA(*p)) {
@@ -8712,9 +8718,9 @@ bool invoke_prompt_interrupt(void)
argv[0].v_type = VAR_UNKNOWN;
got_int = false; // don't skip executing commands
- callback_call(&curbuf->b_prompt_interrupt, 0, argv, &rettv);
+ int ret = callback_call(&curbuf->b_prompt_interrupt, 0, argv, &rettv);
tv_clear(&rettv);
- return true;
+ return ret != FAIL;
}
/// Compare "typ1" and "typ2". Put the result in "typ1".
diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c
index 0e0d0fe696..e253098df5 100644
--- a/src/nvim/eval/executor.c
+++ b/src/nvim/eval/executor.c
@@ -122,8 +122,8 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2, const char *cons
break;
}
const float_T f = (tv2->v_type == VAR_FLOAT
- ? tv2->vval.v_float
- : (float_T)tv_get_number(tv2));
+ ? tv2->vval.v_float
+ : (float_T)tv_get_number(tv2));
switch (*op) {
case '+':
tv1->vval.v_float += f; break;
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 5acc37bfb1..be48dc7732 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -1510,6 +1510,7 @@ static void f_deletebufline(typval_T *argvars, typval_T *rettv, EvalFuncData fpt
buf_T *curbuf_save = NULL;
win_T *curwin_save = NULL;
+ // After this don't use "return", goto "cleanup"!
if (!is_curbuf) {
VIsual_active = false;
curbuf_save = curbuf;
@@ -1530,34 +1531,35 @@ static void f_deletebufline(typval_T *argvars, typval_T *rettv, EvalFuncData fpt
}
if (u_save(first - 1, last + 1) == FAIL) {
- rettv->vval.v_number = 1; // FAIL
- } else {
- for (linenr_T lnum = first; lnum <= last; lnum++) {
- ml_delete(first, true);
- }
+ goto cleanup;
+ }
- FOR_ALL_TAB_WINDOWS(tp, wp) {
- if (wp->w_buffer == buf) {
- if (wp->w_cursor.lnum > last) {
- wp->w_cursor.lnum -= (linenr_T)count;
- } else if (wp->w_cursor.lnum > first) {
- wp->w_cursor.lnum = first;
- }
- if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) {
- wp->w_cursor.lnum = wp->w_buffer->b_ml.ml_line_count;
- }
+ for (linenr_T lnum = first; lnum <= last; lnum++) {
+ ml_delete(first, true);
+ }
+
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ if (wp->w_buffer == buf) {
+ if (wp->w_cursor.lnum > last) {
+ wp->w_cursor.lnum -= (linenr_T)count;
+ } else if (wp->w_cursor.lnum > first) {
+ wp->w_cursor.lnum = first;
+ }
+ if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) {
+ wp->w_cursor.lnum = wp->w_buffer->b_ml.ml_line_count;
}
}
- check_cursor_col();
- deleted_lines_mark(first, count);
}
+ check_cursor_col();
+ deleted_lines_mark(first, count);
+ rettv->vval.v_number = 0; // OK
+cleanup:
if (!is_curbuf) {
curbuf = curbuf_save;
curwin = curwin_save;
VIsual_active = save_VIsual_active;
}
- rettv->vval.v_number = 0; // OK
}
/// "did_filetype()" function
@@ -9966,6 +9968,7 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
bool binary = false;
bool append = false;
bool do_fsync = !!p_fs;
+ bool mkdir_p = false;
if (argvars[2].v_type != VAR_UNKNOWN) {
const char *const flags = tv_get_string_chk(&argvars[2]);
if (flags == NULL) {
@@ -9981,6 +9984,8 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
do_fsync = true; break;
case 'S':
do_fsync = false; break;
+ case 'p':
+ mkdir_p = true; break;
default:
// Using %s, p and not %c, *p to preserve multibyte characters
semsg(_("E5060: Unknown flag: %s"), p);
@@ -10000,6 +10005,7 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
emsg(_("E482: Can't open file with an empty name"));
} else if ((error = file_open(&fp, fname,
((append ? kFileAppend : kFileTruncate)
+ | (mkdir_p ? kFileMkDir : kFileCreate)
| kFileCreate), 0666)) != 0) {
semsg(_("E482: Can't open file %s for writing: %s"),
fname, os_strerror(error));
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index 638573a235..c01bd1e3df 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -1393,6 +1393,24 @@ func_call_skip_call:
return r;
}
+/// call the 'callback' function and return the result as a number.
+/// Returns -2 when calling the function fails. Uses argv[0] to argv[argc - 1]
+/// for the function arguments. argv[argc] should have type VAR_UNKNOWN.
+///
+/// @param argcount number of "argvars"
+/// @param argvars vars for arguments, must have "argcount" PLUS ONE elements!
+varnumber_T callback_call_retnr(Callback *callback, int argcount, typval_T *argvars)
+{
+ typval_T rettv;
+ if (!callback_call(callback, argcount, argvars, &rettv)) {
+ return -2;
+ }
+
+ varnumber_T retval = tv_get_number_chk(&rettv, NULL);
+ tv_clear(&rettv);
+ return retval;
+}
+
/// Give an error message for the result of a function.
/// Nothing if "error" is FCERR_NONE.
static void user_func_error(int error, const char_u *name)
@@ -1886,6 +1904,27 @@ theend:
return (char_u *)name;
}
+/// Call trans_function_name(), except that a lambda is returned as-is.
+/// Returns the name in allocated memory.
+char *save_function_name(char **name, bool skip, int flags, funcdict_T *fudi)
+{
+ char *p = *name;
+ char *saved;
+
+ if (strncmp(p, "<lambda>", 8) == 0) {
+ p += 8;
+ (void)getdigits(&p, false, 0);
+ saved = xstrndup(*name, (size_t)(p - *name));
+ if (fudi != NULL) {
+ CLEAR_POINTER(fudi);
+ }
+ } else {
+ saved = (char *)trans_function_name(&p, skip, flags, fudi, NULL);
+ }
+ *name = p;
+ return saved;
+}
+
#define MAX_FUNC_NESTING 50
/// List functions.
@@ -2000,14 +2039,7 @@ void ex_function(exarg_T *eap)
// s:func script-local function name
// g:func global function name, same as "func"
p = eap->arg;
- if (strncmp(p, "<lambda>", 8) == 0) {
- p += 8;
- (void)getdigits(&p, false, 0);
- name = xstrndup(eap->arg, (size_t)(p - eap->arg));
- CLEAR_FIELD(fudi);
- } else {
- name = (char *)trans_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi, NULL);
- }
+ name = save_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi);
paren = (vim_strchr(p, '(') != NULL);
if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) {
// Return on an invalid expression in braces, unless the expression
diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c
index 97630d4953..3a625df988 100644
--- a/src/nvim/eval/vars.c
+++ b/src/nvim/eval/vars.c
@@ -559,8 +559,6 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo
{
char *arg_end = NULL;
int len;
- int opt_flags;
- char *tofree = NULL;
// ":let $VAR = expr": Set environment variable.
if (*arg == '$') {
@@ -581,12 +579,12 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo
&& vim_strchr(endchars, *skipwhite(arg)) == NULL) {
emsg(_(e_letunexp));
} else if (!check_secure()) {
+ char *tofree = NULL;
const char c1 = name[len];
name[len] = NUL;
const char *p = tv_get_string_chk(tv);
if (p != NULL && op != NULL && *op == '.') {
char *s = vim_getenv(name);
-
if (s != NULL) {
tofree = concat_str(s, p);
p = (const char *)tofree;
@@ -610,7 +608,8 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo
return NULL;
}
// Find the end of the name.
- char *const p = (char *)find_option_end((const char **)&arg, &opt_flags);
+ int scope;
+ char *const p = (char *)find_option_end((const char **)&arg, &scope);
if (p == NULL
|| (endchars != NULL
&& vim_strchr(endchars, *skipwhite(p)) == NULL)) {
@@ -622,11 +621,13 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo
char *stringval = NULL;
const char *s = NULL;
bool failed = false;
+ uint32_t opt_p_flags;
+ char *tofree = NULL;
const char c1 = *p;
*p = NUL;
- opt_type = get_option_value(arg, &numval, &stringval, opt_flags);
+ opt_type = get_option_value(arg, &numval, &stringval, &opt_p_flags, scope);
if (opt_type == gov_bool
|| opt_type == gov_number
|| opt_type == gov_hidden_bool
@@ -635,8 +636,13 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo
n = (long)tv_get_number(tv);
}
- // Avoid setting a string option to the text "v:false" or similar.
- if (tv->v_type != VAR_BOOL && tv->v_type != VAR_SPECIAL) {
+ if ((opt_p_flags & P_FUNC) && tv_is_func(*tv)) {
+ // If the option can be set to a function reference or a lambda
+ // and the passed value is a function reference, then convert it to
+ // the name (string) of the function reference.
+ s = tofree = encode_tv2string(tv, NULL);
+ } else if (tv->v_type != VAR_BOOL && tv->v_type != VAR_SPECIAL) {
+ // Avoid setting a string option to the text "v:false" or similar.
s = tv_get_string_chk(tv);
}
@@ -673,7 +679,7 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo
if (!failed) {
if (opt_type != gov_string || s != NULL) {
- char *err = set_option_value(arg, n, s, opt_flags);
+ char *err = set_option_value(arg, n, s, scope);
arg_end = p;
if (err != NULL) {
emsg(_(err));
@@ -684,6 +690,7 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo
}
*p = c1;
xfree(stringval);
+ xfree(tofree);
}
// ":let @r = expr": Set register contents.
} else if (*arg == '@') {
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 1b850ab425..46870061a9 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -1772,6 +1772,17 @@ void ex_write(exarg_T *eap)
}
}
+#ifdef UNIX
+static int check_writable(const char *fname)
+{
+ if (os_nodetype(fname) == NODE_OTHER) {
+ semsg(_("E503: \"%s\" is not a file or writable device"), fname);
+ return FAIL;
+ }
+ return OK;
+}
+#endif
+
/// write current buffer to file 'eap->arg'
/// if 'eap->append' is true, append to the file
///
@@ -1829,7 +1840,11 @@ int do_write(exarg_T *eap)
// Writing to the current file is not allowed in readonly mode
// and a file name is required.
// "nofile" and "nowrite" buffers cannot be written implicitly either.
- if (!other && (bt_dontwrite_msg(curbuf) || check_fname() == FAIL
+ if (!other && (bt_dontwrite_msg(curbuf)
+ || check_fname() == FAIL
+#ifdef UNIX
+ || check_writable(curbuf->b_ffname) == FAIL
+#endif
|| check_readonly(&eap->forceit, curbuf))) {
goto theend;
}
@@ -1907,6 +1922,13 @@ int do_write(exarg_T *eap)
fname = curbuf->b_sfname;
}
+ if (eap->mkdir_p) {
+ if (os_file_mkdir(fname, 0755) < 0) {
+ retval = FAIL;
+ goto theend;
+ }
+ }
+
name_was_missing = curbuf->b_ffname == NULL;
retval = buf_write(curbuf, ffname, fname, eap->line1, eap->line2,
eap, eap->append, eap->forceit, true, false);
diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h
index 0015a82880..378271c107 100644
--- a/src/nvim/ex_cmds_defs.h
+++ b/src/nvim/ex_cmds_defs.h
@@ -202,6 +202,7 @@ struct exarg {
int regname; ///< register name (NUL if none)
int force_bin; ///< 0, FORCE_BIN or FORCE_NOBIN
int read_edit; ///< ++edit argument
+ int mkdir_p; ///< ++p argument
int force_ff; ///< ++ff= argument (first char of argument)
int force_enc; ///< ++enc= argument (index in cmd[])
int bad_char; ///< BAD_KEEP, BAD_DROP or replacement byte
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 0c2a92ef43..e19c5f27d7 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -4074,6 +4074,13 @@ static int getargopt(exarg_T *eap)
return OK;
}
+ // ":write ++p foo/bar/file
+ if (strncmp(arg, "p", 1) == 0) {
+ eap->mkdir_p = true;
+ eap->arg = skipwhite(arg + 1);
+ return OK;
+ }
+
if (STRNCMP(arg, "ff", 2) == 0) {
arg += 2;
pp = &eap->force_ff;
@@ -4224,7 +4231,7 @@ static void ex_autocmd(exarg_T *eap)
secure = 2;
eap->errmsg = _(e_curdir);
} else if (eap->cmdidx == CMD_autocmd) {
- do_autocmd(eap->arg, eap->forceit);
+ do_autocmd(eap, eap->arg, eap->forceit);
} else {
do_augroup(eap->arg, eap->forceit);
}
diff --git a/src/nvim/extmark_defs.h b/src/nvim/extmark_defs.h
index 9ef4ec23e5..51b60dcf6d 100644
--- a/src/nvim/extmark_defs.h
+++ b/src/nvim/extmark_defs.h
@@ -9,6 +9,8 @@ typedef struct {
int hl_id;
} VirtTextChunk;
+typedef kvec_t(VirtTextChunk) VirtText;
+
typedef struct undo_object ExtmarkUndoObject;
typedef kvec_t(ExtmarkUndoObject) extmark_undo_vec_t;
diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua
index 4e4dd83367..af21d44eaf 100644
--- a/src/nvim/generators/gen_options.lua
+++ b/src/nvim/generators/gen_options.lua
@@ -29,14 +29,14 @@ local type_flags={
}
local redraw_flags={
+ ui_option='P_UI_OPTION',
+ tabline='P_RTABL',
statuslines='P_RSTAT',
- tabline = 'P_RTABL',
current_window='P_RWIN',
current_window_only='P_RWINONLY',
current_buffer='P_RBUF',
all_windows='P_RALL',
curswant='P_CURSWANT',
- ui_option='P_UI_OPTION',
}
local list_flags={
@@ -78,6 +78,7 @@ local get_flags = function(o)
{'deny_in_modelines', 'P_NO_ML'},
{'deny_duplicates', 'P_NODUP'},
{'modelineexpr', 'P_MLE'},
+ {'func'}
}) do
local key_name = flag_desc[1]
local def_name = flag_desc[2] or ('P_' .. key_name:upper())
diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c
index ff59536e8d..b769f6215c 100644
--- a/src/nvim/hardcopy.c
+++ b/src/nvim/hardcopy.c
@@ -526,7 +526,6 @@ static void prt_header(prt_settings_T *const psettings, const int pagenum, const
if (*p_header != NUL) {
linenr_T tmp_lnum, tmp_topline, tmp_botline;
- int use_sandbox = false;
// Need to (temporarily) set current line number and first/last line
// number on the 'window'. Since we don't know how long the page is,
@@ -540,9 +539,8 @@ static void prt_header(prt_settings_T *const psettings, const int pagenum, const
curwin->w_botline = lnum + 63;
printer_page_num = pagenum;
- use_sandbox = was_set_insecurely(curwin, "printheader", 0);
build_stl_str_hl(curwin, (char *)tbuf, (size_t)width + IOSIZE,
- (char *)p_header, use_sandbox,
+ (char *)p_header, "printheader", 0,
' ', width, NULL, NULL);
// Reset line numbers
@@ -733,7 +731,7 @@ void ex_hardcopy(exarg_T *eap)
}
assert(prtpos.bytes_printed <= SIZE_MAX / 100);
- sprintf((char *)IObuff, _("Printing page %d (%zu%%)"),
+ sprintf((char *)IObuff, _("Printing page %d (%zu%%)"), // NOLINT(runtime/printf)
page_count + 1 + side,
prtpos.bytes_printed * 100 / bytes_to_print);
if (!mch_print_begin_page((char_u *)IObuff)) {
@@ -754,8 +752,7 @@ void ex_hardcopy(exarg_T *eap)
prtpos.file_line);
}
- for (page_line = 0; page_line < settings.lines_per_page;
- ++page_line) {
+ for (page_line = 0; page_line < settings.lines_per_page; page_line++) {
prtpos.column = hardcopy_line(&settings,
page_line, &prtpos);
if (prtpos.column == 0) {
@@ -2458,8 +2455,7 @@ bool mch_print_begin(prt_settings_T *psettings)
prt_dsc_font_resource("DocumentNeededResources", &prt_ps_courier_font);
}
if (prt_out_mbyte) {
- prt_dsc_font_resource((prt_use_courier ? NULL
- : "DocumentNeededResources"), &prt_ps_mb_font);
+ prt_dsc_font_resource((prt_use_courier ? NULL : "DocumentNeededResources"), &prt_ps_mb_font);
if (!prt_custom_cmap) {
prt_dsc_resources(NULL, "cmap", prt_cmap);
}
@@ -3008,7 +3004,7 @@ int mch_print_text_out(char_u *const textp, size_t len)
ga_append(&prt_ps_buffer, '\\'); break;
default:
- sprintf((char *)ch_buff, "%03o", (unsigned int)ch);
+ sprintf((char *)ch_buff, "%03o", (unsigned int)ch); // NOLINT(runtime/printf)
ga_append(&prt_ps_buffer, (char)ch_buff[0]);
ga_append(&prt_ps_buffer, (char)ch_buff[1]);
ga_append(&prt_ps_buffer, (char)ch_buff[2]);
diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h
index ffcb0f3f22..2557a248c3 100644
--- a/src/nvim/highlight_defs.h
+++ b/src/nvim/highlight_defs.h
@@ -114,6 +114,7 @@ typedef enum {
HLF_WBR, // Window bars
HLF_WBRNC, // Window bars of not-current windows
HLF_CU, // Cursor
+ HLF_BTITLE, // Float Border Title
HLF_COUNT, // MUST be the last one
} hlf_T;
@@ -178,6 +179,7 @@ EXTERN const char *hlf_names[] INIT(= {
[HLF_WBR] = "WinBar",
[HLF_WBRNC] = "WinBarNC",
[HLF_CU] = "Cursor",
+ [HLF_BTITLE] = "FloatTitle",
});
EXTERN int highlight_attr[HLF_COUNT + 1]; // Highl. attr for each context.
diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c
index a2f36424a3..3c3c834c6c 100644
--- a/src/nvim/highlight_group.c
+++ b/src/nvim/highlight_group.c
@@ -133,6 +133,7 @@ static const char *highlight_init_both[] = {
"default link MsgSeparator StatusLine",
"default link NormalFloat Pmenu",
"default link FloatBorder WinSeparator",
+ "default link FloatTitle Title",
"default FloatShadow blend=80 guibg=Black",
"default FloatShadowThrough blend=100 guibg=Black",
"RedrawDebugNormal cterm=reverse gui=reverse",
diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
index 0c57e4f885..2767d7939e 100644
--- a/src/nvim/insexpand.c
+++ b/src/nvim/insexpand.c
@@ -18,6 +18,7 @@
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
#include "nvim/ex_getln.h"
@@ -2226,6 +2227,91 @@ static buf_T *ins_compl_next_buf(buf_T *buf, int flag)
return buf;
}
+static Callback cfu_cb; ///< 'completefunc' callback function
+static Callback ofu_cb; ///< 'omnifunc' callback function
+static Callback tsrfu_cb; ///< 'thesaurusfunc' callback function
+
+/// Copy a global callback function to a buffer local callback.
+static void copy_global_to_buflocal_cb(Callback *globcb, Callback *bufcb)
+{
+ callback_free(bufcb);
+ if (globcb->type != kCallbackNone) {
+ callback_copy(bufcb, globcb);
+ }
+}
+
+/// Parse the 'completefunc' option value and set the callback function.
+/// Invoked when the 'completefunc' option is set. The option value can be a
+/// name of a function (string), or function(<name>) or funcref(<name>) or a
+/// lambda expression.
+int set_completefunc_option(void)
+{
+ int retval = option_set_callback_func(curbuf->b_p_cfu, &cfu_cb);
+ if (retval == OK) {
+ set_buflocal_cfu_callback(curbuf);
+ }
+
+ return retval;
+}
+
+/// Copy the global 'completefunc' callback function to the buffer-local
+/// 'completefunc' callback for "buf".
+void set_buflocal_cfu_callback(buf_T *buf)
+{
+ copy_global_to_buflocal_cb(&cfu_cb, &buf->b_cfu_cb);
+}
+
+/// Parse the 'omnifunc' option value and set the callback function.
+/// Invoked when the 'omnifunc' option is set. The option value can be a
+/// name of a function (string), or function(<name>) or funcref(<name>) or a
+/// lambda expression.
+int set_omnifunc_option(void)
+{
+ int retval = option_set_callback_func(curbuf->b_p_ofu, &ofu_cb);
+ if (retval == OK) {
+ set_buflocal_ofu_callback(curbuf);
+ }
+
+ return retval;
+}
+
+/// Copy the global 'omnifunc' callback function to the buffer-local 'omnifunc'
+/// callback for "buf".
+void set_buflocal_ofu_callback(buf_T *buf)
+{
+ copy_global_to_buflocal_cb(&ofu_cb, &buf->b_ofu_cb);
+}
+
+/// Parse the 'thesaurusfunc' option value and set the callback function.
+/// Invoked when the 'thesaurusfunc' option is set. The option value can be a
+/// name of a function (string), or function(<name>) or funcref(<name>) or a
+/// lambda expression.
+int set_thesaurusfunc_option(void)
+{
+ int retval;
+
+ if (*curbuf->b_p_tsrfu != NUL) {
+ // buffer-local option set
+ retval = option_set_callback_func(curbuf->b_p_tsrfu, &curbuf->b_tsrfu_cb);
+ } else {
+ // global option set
+ retval = option_set_callback_func(p_tsrfu, &tsrfu_cb);
+ }
+
+ return retval;
+}
+
+/// Mark the global 'completefunc' 'omnifunc' and 'thesaurusfunc' callbacks with
+/// "copyID" so that they are not garbage collected.
+bool set_ref_in_insexpand_funcs(int copyID)
+{
+ bool abort = set_ref_in_callback(&cfu_cb, copyID, NULL, NULL);
+ abort = abort || set_ref_in_callback(&ofu_cb, copyID, NULL, NULL);
+ abort = abort || set_ref_in_callback(&tsrfu_cb, copyID, NULL, NULL);
+
+ return abort;
+}
+
/// Get the user-defined completion function name for completion "type"
static char_u *get_complete_funcname(int type)
{
@@ -2241,10 +2327,23 @@ static char_u *get_complete_funcname(int type)
}
}
-/// Execute user defined complete function 'completefunc' or 'omnifunc', and
-/// get matches in "matches".
+/// Get the callback to use for insert mode completion.
+static Callback *get_insert_callback(int type)
+{
+ if (type == CTRL_X_FUNCTION) {
+ return &curbuf->b_cfu_cb;
+ }
+ if (type == CTRL_X_OMNI) {
+ return &curbuf->b_ofu_cb;
+ }
+ // CTRL_X_THESAURUS
+ return (*curbuf->b_p_tsrfu != NUL) ? &curbuf->b_tsrfu_cb : &tsrfu_cb;
+}
+
+/// Execute user defined complete function 'completefunc', 'omnifunc' or
+/// 'thesaurusfunc', and get matches in "matches".
///
-/// @param type CTRL_X_OMNI or CTRL_X_FUNCTION
+/// @param type either CTRL_X_OMNI or CTRL_X_FUNCTION or CTRL_X_THESAURUS
static void expand_by_function(int type, char_u *base)
{
list_T *matchlist = NULL;
@@ -2274,8 +2373,10 @@ static void expand_by_function(int type, char_u *base)
// Insert mode in another buffer.
textlock++;
+ Callback *cb = get_insert_callback(type);
+
// Call a function, which returns a list or dict.
- if (call_vim_function((char *)funcname, 2, args, &rettv) == OK) {
+ if (callback_call(cb, 2, args, &rettv)) {
switch (rettv.v_type) {
case VAR_LIST:
matchlist = rettv.vval.v_list;
@@ -3853,7 +3954,8 @@ static int get_userdefined_compl_info(colnr_T curs_col)
pos_T pos = curwin->w_cursor;
textlock++;
- colnr_T col = (colnr_T)call_func_retnr((char *)funcname, 2, args);
+ Callback *cb = get_insert_callback(ctrl_x_mode);
+ colnr_T col = (colnr_T)callback_call_retnr(cb, 2, args);
textlock--;
State = save_State;
@@ -4356,6 +4458,9 @@ static unsigned quote_meta(char_u *dest, char_u *src, int len)
void free_insexpand_stuff(void)
{
XFREE_CLEAR(compl_orig_text);
+ callback_free(&cfu_cb);
+ callback_free(&ofu_cb);
+ callback_free(&tsrfu_cb);
}
#endif
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index 9d63fe55f9..9cb42a81d3 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -1985,6 +1985,9 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview)
nlua_pushref(lstate, preview ? cmd->uc_preview_luaref : cmd->uc_luaref);
lua_newtable(lstate);
+ lua_pushstring(lstate, cmd->uc_name);
+ lua_setfield(lstate, -2, "name");
+
lua_pushboolean(lstate, eap->forceit == 1);
lua_setfield(lstate, -2, "bang");
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index b4b2c4c7eb..848b0f29d0 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -1549,7 +1549,7 @@ void show_utf8(void)
}
clen = utf_ptr2len((char *)line + i);
}
- sprintf((char *)IObuff + rlen, "%02x ",
+ sprintf((char *)IObuff + rlen, "%02x ", // NOLINT(runtime/printf)
(line[i] == NL) ? NUL : line[i]); // NUL is stored as NL
clen--;
rlen += (int)strlen(IObuff + rlen);
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index 615363e846..1826e5935e 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -2143,13 +2143,13 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b
buf->b_ml.ml_stack_top = stack_idx + 1; // truncate stack
if (lineadd) {
- --(buf->b_ml.ml_stack_top);
+ (buf->b_ml.ml_stack_top)--;
// fix line count for rest of blocks in the stack
ml_lineadd(buf, lineadd);
// fix stack itself
buf->b_ml.ml_stack[buf->b_ml.ml_stack_top].ip_high +=
lineadd;
- ++(buf->b_ml.ml_stack_top);
+ (buf->b_ml.ml_stack_top)++;
}
// We are finished, break the loop here.
@@ -2436,7 +2436,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message)
buf->b_ml.ml_stack[buf->b_ml.ml_stack_top].ip_high +=
buf->b_ml.ml_locked_lineadd;
}
- ++(buf->b_ml.ml_stack_top);
+ (buf->b_ml.ml_stack_top)++;
break;
}
@@ -2706,11 +2706,11 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action)
&& buf->b_ml.ml_locked_high >= lnum) {
// remember to update pointer blocks and stack later
if (action == ML_INSERT) {
- ++(buf->b_ml.ml_locked_lineadd);
- ++(buf->b_ml.ml_locked_high);
+ (buf->b_ml.ml_locked_lineadd)++;
+ (buf->b_ml.ml_locked_high)++;
} else if (action == ML_DELETE) {
- --(buf->b_ml.ml_locked_lineadd);
- --(buf->b_ml.ml_locked_high);
+ (buf->b_ml.ml_locked_lineadd)--;
+ (buf->b_ml.ml_locked_high)--;
}
return buf->b_ml.ml_locked;
}
diff --git a/src/nvim/msgpack_rpc/helpers.c b/src/nvim/msgpack_rpc/helpers.c
index ddca9afad0..86babd1c36 100644
--- a/src/nvim/msgpack_rpc/helpers.c
+++ b/src/nvim/msgpack_rpc/helpers.c
@@ -95,9 +95,8 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg)
dest = conv(((String) { \
.size = obj->via.attr.size, \
.data = (obj->via.attr.ptr == NULL || obj->via.attr.size == 0 \
- ? xmemdupz("", 0) \
- : xmemdupz(obj->via.attr.ptr, obj->via.attr.size)), \
- })); \
+ ? xmemdupz("", 0) \
+ : xmemdupz(obj->via.attr.ptr, obj->via.attr.size)), })); \
break; \
}
STR_CASE(MSGPACK_OBJECT_STR, str, cur.mobj, *cur.aobj, STRING_OBJ)
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index d558ec6c28..0999476ca4 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -2266,7 +2266,7 @@ bool find_decl(char_u *ptr, size_t len, bool locally, bool thisblock, int flags_
// Put "\V" before the pattern to avoid that the special meaning of "."
// and "~" causes trouble.
assert(len <= INT_MAX);
- sprintf((char *)pat, vim_iswordp(ptr) ? "\\V\\<%.*s\\>" : "\\V%.*s",
+ sprintf((char *)pat, vim_iswordp(ptr) ? "\\V\\<%.*s\\>" : "\\V%.*s", // NOLINT(runtime/printf)
(int)len, ptr);
old_pos = curwin->w_cursor;
save_p_ws = p_ws;
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 2d53918ded..65fc42bc08 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -5606,6 +5606,13 @@ void free_operatorfunc_option(void)
}
#endif
+/// Mark the global 'operatorfunc' callback with "copyID" so that it is not
+/// garbage collected.
+bool set_ref_in_opfunc(int copyID)
+{
+ return set_ref_in_callback(&opfunc_cb, copyID, NULL, NULL);
+}
+
/// Handle the "g@" operator: call 'operatorfunc'.
static void op_function(const oparg_T *oap)
FUNC_ATTR_NONNULL_ALL
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 628e1a6581..23c4a1ccf3 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -51,6 +51,7 @@
#include "nvim/highlight_group.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
+#include "nvim/insexpand.h"
#include "nvim/keycodes.h"
#include "nvim/locale.h"
#include "nvim/macros.h"
@@ -78,6 +79,7 @@
#include "nvim/spellsuggest.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
+#include "nvim/tag.h"
#include "nvim/ui.h"
#include "nvim/ui_compositor.h"
#include "nvim/undo.h"
@@ -577,6 +579,7 @@ void free_all_options(void)
}
}
free_operatorfunc_option();
+ free_tagfunc_option();
}
#endif
@@ -2839,6 +2842,7 @@ int findoption(const char *const arg)
/// Gets the value for an option.
///
/// @param stringval NULL when only checking existence
+/// @param flagsp set to the option flags (P_xxxx) (if not NULL)
///
/// @returns:
/// Number option: gov_number, *numval gets value.
@@ -2848,7 +2852,8 @@ int findoption(const char *const arg)
/// Hidden Toggle option: gov_hidden_bool.
/// Hidden String option: gov_hidden_string.
/// Unknown option: gov_unknown.
-getoption_T get_option_value(const char *name, long *numval, char **stringval, int opt_flags)
+getoption_T get_option_value(const char *name, long *numval, char **stringval, uint32_t *flagsp,
+ int scope)
{
if (get_tty_option(name, stringval)) {
return gov_string;
@@ -2859,7 +2864,12 @@ getoption_T get_option_value(const char *name, long *numval, char **stringval, i
return gov_unknown;
}
- char_u *varp = (char_u *)get_varp_scope(&(options[opt_idx]), opt_flags);
+ char_u *varp = (char_u *)get_varp_scope(&(options[opt_idx]), scope);
+
+ if (flagsp != NULL) {
+ // Return the P_xxxx option flags.
+ *flagsp = options[opt_idx].flags;
+ }
if (options[opt_idx].flags & P_STRING) {
if (varp == NULL) { // hidden option
@@ -3086,7 +3096,7 @@ char *set_option_value(const char *const name, const long number, const char *co
numval = -1;
} else {
char *s = NULL;
- (void)get_option_value(name, &numval, &s, OPT_GLOBAL);
+ (void)get_option_value(name, &numval, &s, NULL, OPT_GLOBAL);
}
}
if (flags & P_NUM) {
@@ -3694,15 +3704,17 @@ void unset_global_local_option(char *name, void *from)
}
/// Get pointer to option variable, depending on local or global scope.
-char *get_varp_scope(vimoption_T *p, int opt_flags)
+///
+/// @param scope can be OPT_LOCAL, OPT_GLOBAL or a combination.
+char *get_varp_scope(vimoption_T *p, int scope)
{
- if ((opt_flags & OPT_GLOBAL) && p->indir != PV_NONE) {
+ if ((scope & OPT_GLOBAL) && p->indir != PV_NONE) {
if (p->var == VAR_WIN) {
return GLOBAL_WO(get_varp(p));
}
return (char *)p->var;
}
- if ((opt_flags & OPT_LOCAL) && ((int)p->indir & PV_BOTH)) {
+ if ((scope & OPT_LOCAL) && ((int)p->indir & PV_BOTH)) {
switch ((int)p->indir) {
case PV_FP:
return (char *)&(curbuf->b_p_fp);
@@ -4375,10 +4387,13 @@ void buf_copy_options(buf_T *buf, int flags)
#endif
buf->b_p_cfu = xstrdup(p_cfu);
COPY_OPT_SCTX(buf, BV_CFU);
+ set_buflocal_cfu_callback(buf);
buf->b_p_ofu = xstrdup(p_ofu);
COPY_OPT_SCTX(buf, BV_OFU);
+ set_buflocal_ofu_callback(buf);
buf->b_p_tfu = xstrdup(p_tfu);
COPY_OPT_SCTX(buf, BV_TFU);
+ set_buflocal_tfu_callback(buf);
buf->b_p_sts = p_sts;
COPY_OPT_SCTX(buf, BV_STS);
buf->b_p_sts_nopaste = p_sts_nopaste;
@@ -4853,9 +4868,9 @@ void ExpandOldSetting(int *num_file, char ***file)
/// NameBuff[]. Must not be called with a hidden option!
///
/// @param opt_flags OPT_GLOBAL and/or OPT_LOCAL
-static void option_value2string(vimoption_T *opp, int opt_flags)
+static void option_value2string(vimoption_T *opp, int scope)
{
- char_u *varp = (char_u *)get_varp_scope(opp, opt_flags);
+ char_u *varp = (char_u *)get_varp_scope(opp, scope);
if (opp->flags & P_NUM) {
long wc = 0;
@@ -5174,7 +5189,7 @@ int option_set_callback_func(char *optval, Callback *optcb)
}
Callback cb;
- if (!callback_from_typval(&cb, tv)) {
+ if (!callback_from_typval(&cb, tv) || cb.type == kCallbackNone) {
tv_free(tv);
return FAIL;
}
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index 7c33773e41..d505e75be4 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -24,6 +24,7 @@
#define P_NO_MKRC 0x200U ///< don't include in :mkvimrc output
// when option changed, what to display:
+#define P_UI_OPTION 0x400U ///< send option to remote UI
#define P_RTABL 0x800U ///< redraw tabline
#define P_RSTAT 0x1000U ///< redraw status lines
#define P_RWIN 0x2000U ///< redraw current window and recompute text
@@ -50,9 +51,9 @@
#define P_NDNAME 0x8000000U ///< only normal dir name chars allowed
#define P_RWINONLY 0x10000000U ///< only redraw current window
#define P_MLE 0x20000000U ///< under control of 'modelineexpr'
+#define P_FUNC 0x40000000U ///< accept a function reference or a lambda
-#define P_NO_DEF_EXP 0x40000000U ///< Do not expand default value.
-#define P_UI_OPTION 0x80000000U ///< send option to remote ui
+#define P_NO_DEF_EXP 0x80000000U ///< Do not expand default value.
/// Flags for option-setting functions
///
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 3a59becb33..e938760e67 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -10,6 +10,7 @@
-- secure=nil, gettext=nil, noglob=nil, normal_fname_chars=nil,
-- pri_mkrc=nil, deny_in_modelines=nil, normal_dname_chars=nil,
-- modelineexpr=nil,
+-- func=nil,
-- expand=nil, nodefault=nil, no_mkrc=nil,
-- alloced=nil,
-- save_pv_indir=nil,
@@ -455,6 +456,7 @@ return {
type='string', scope={'buffer'},
secure=true,
alloced=true,
+ func=true,
varname='p_cfu',
defaults={if_true=""}
},
@@ -1638,6 +1640,7 @@ return {
type='string', scope={'buffer'},
secure=true,
alloced=true,
+ func=true,
varname='p_ofu',
defaults={if_true=""}
},
@@ -1653,6 +1656,7 @@ return {
short_desc=N_("function to be called for |g@| operator"),
type='string', scope={'global'},
secure=true,
+ func=true,
varname='p_opfunc',
defaults={if_true=""}
},
@@ -1835,6 +1839,8 @@ return {
full_name='quickfixtextfunc', abbreviation='qftf',
short_desc=N_("customize the quickfix window"),
type='string', scope={'global'},
+ secure=true,
+ func=true,
varname='p_qftf',
defaults={if_true=""}
},
@@ -2408,6 +2414,8 @@ return {
full_name='tagfunc', abbreviation='tfu',
short_desc=N_("function used to perform tag searches"),
type='string', scope={'buffer'},
+ secure=true,
+ func=true,
varname='p_tfu',
defaults={if_true=""}
},
@@ -2538,6 +2546,7 @@ return {
type='string', scope={'global', 'buffer'},
secure=true,
alloced=true,
+ func=true,
varname='p_tsrfu',
defaults={if_true=""}
},
diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c
index 65bc9f60df..b088a4c8c7 100644
--- a/src/nvim/optionstr.c
+++ b/src/nvim/optionstr.c
@@ -38,6 +38,7 @@
#include "nvim/spellfile.h"
#include "nvim/spellsuggest.h"
#include "nvim/strings.h"
+#include "nvim/tag.h"
#include "nvim/ui.h"
#include "nvim/vim.h"
#include "nvim/window.h"
@@ -1472,6 +1473,18 @@ char *did_set_string_option(int opt_idx, char **varp, char *oldval, char *errbuf
}
}
}
+ } else if (gvarp == &p_cfu) { // 'completefunc'
+ if (set_completefunc_option() == FAIL) {
+ errmsg = e_invarg;
+ }
+ } else if (gvarp == &p_ofu) { // 'omnifunc'
+ if (set_omnifunc_option() == FAIL) {
+ errmsg = e_invarg;
+ }
+ } else if (gvarp == &p_tsrfu) { // 'thesaurusfunc'
+ if (set_thesaurusfunc_option() == FAIL) {
+ errmsg = e_invarg;
+ }
} else if (varp == &p_opfunc) { // 'operatorfunc'
if (set_operatorfunc_option() == FAIL) {
errmsg = e_invarg;
@@ -1480,6 +1493,10 @@ char *did_set_string_option(int opt_idx, char **varp, char *oldval, char *errbuf
if (qf_process_qftf_option() == FAIL) {
errmsg = e_invarg;
}
+ } else if (gvarp == &p_tfu) { // 'tagfunc'
+ if (set_tagfunc_option() == FAIL) {
+ errmsg = e_invarg;
+ }
} else {
// Options that are a list of flags.
p = NULL;
diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c
index b1710737d0..280a9c2bee 100644
--- a/src/nvim/os/fileio.c
+++ b/src/nvim/os/fileio.c
@@ -71,6 +71,7 @@ int file_open(FileDescriptor *const ret_fp, const char *const fname, const int f
FLAG(flags, kFileReadOnly, O_RDONLY, kFalse, wr != kTrue);
#ifdef O_NOFOLLOW
FLAG(flags, kFileNoSymlink, O_NOFOLLOW, kNone, true);
+ FLAG(flags, kFileMkDir, O_CREAT|O_WRONLY, kTrue, !(flags & kFileCreateOnly));
#endif
#undef FLAG
// wr is used for kFileReadOnly flag, but on
@@ -78,6 +79,13 @@ int file_open(FileDescriptor *const ret_fp, const char *const fname, const int f
// `error: variable ‘wr’ set but not used [-Werror=unused-but-set-variable]`
(void)wr;
+ if (flags & kFileMkDir) {
+ int mkdir_ret = os_file_mkdir((char *)fname, 0755);
+ if (mkdir_ret < 0) {
+ return mkdir_ret;
+ }
+ }
+
const int fd = os_open(fname, os_open_flags, mode);
if (fd < 0) {
diff --git a/src/nvim/os/fileio.h b/src/nvim/os/fileio.h
index da23a54c4e..5e47bbf921 100644
--- a/src/nvim/os/fileio.h
+++ b/src/nvim/os/fileio.h
@@ -35,6 +35,7 @@ typedef enum {
///< be used with kFileCreateOnly.
kFileNonBlocking = 128, ///< Do not restart read() or write() syscall if
///< EAGAIN was encountered.
+ kFileMkDir = 256,
} FileOpenFlags;
static inline bool file_eof(const FileDescriptor *fp)
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
index 2a0018da9c..0cadabbb47 100644
--- a/src/nvim/os/fs.c
+++ b/src/nvim/os/fs.c
@@ -943,6 +943,37 @@ int os_mkdir_recurse(const char *const dir, int32_t mode, char **const failed_di
return 0;
}
+/// Create the parent directory of a file if it does not exist
+///
+/// @param[in] fname Full path of the file name whose parent directories
+/// we want to create
+/// @param[in] mode Permissions for the newly-created directory.
+///
+/// @return `0` for success, libuv error code for failure.
+int os_file_mkdir(char *fname, int32_t mode)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (!dir_of_file_exists((char_u *)fname)) {
+ char *tail = path_tail_with_sep(fname);
+ char *last_char = tail + strlen(tail) - 1;
+ if (vim_ispathsep(*last_char)) {
+ emsg(_(e_noname));
+ return -1;
+ }
+ char c = *tail;
+ *tail = NUL;
+ int r;
+ char *failed_dir;
+ if ((r = os_mkdir_recurse(fname, mode, &failed_dir) < 0)) {
+ semsg(_(e_mkdir), failed_dir, os_strerror(r));
+ xfree(failed_dir);
+ }
+ *tail = c;
+ return r;
+ }
+ return 0;
+}
+
/// Create a unique temporary directory.
///
/// @param[in] template Template of the path to the directory with XXXXXX
diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c
index 31d85ac2eb..2aaf776fc6 100644
--- a/src/nvim/os/stdpaths.c
+++ b/src/nvim/os/stdpaths.c
@@ -87,6 +87,9 @@ char *stdpaths_get_xdg_var(const XDGVarType idx)
} else if (idx == kXDGRuntimeDir) {
// Special-case: stdpath('run') is defined at startup.
ret = vim_gettempdir();
+ if (ret == NULL) {
+ ret = "/tmp/";
+ }
size_t len = strlen(ret);
ret = xstrndup(ret, len >= 2 ? len - 1 : 0); // Trim trailing slash.
}
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 82c1a50421..9fdf7b348c 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -6683,7 +6683,8 @@ int set_errorlist(win_T *wp, list_T *list, int action, char *title, dict_T *what
return retval;
}
-/// Mark the context as in use for all the lists in a quickfix stack.
+/// Mark the quickfix context and callback function as in use for all the lists
+/// in a quickfix stack.
static bool mark_quickfix_ctx(qf_info_T *qi, int copyID)
{
bool abort = false;
@@ -6692,8 +6693,11 @@ static bool mark_quickfix_ctx(qf_info_T *qi, int copyID)
typval_T *ctx = qi->qf_lists[i].qf_ctx;
if (ctx != NULL && ctx->v_type != VAR_NUMBER
&& ctx->v_type != VAR_STRING && ctx->v_type != VAR_FLOAT) {
- abort = set_ref_in_item(ctx, copyID, NULL, NULL);
+ abort = abort || set_ref_in_item(ctx, copyID, NULL, NULL);
}
+
+ Callback *cb = &qi->qf_lists[i].qf_qftf_cb;
+ abort = abort || set_ref_in_callback(cb, copyID, NULL, NULL);
}
return abort;
@@ -6708,6 +6712,11 @@ bool set_ref_in_quickfix(int copyID)
return abort;
}
+ abort = set_ref_in_callback(&qftf_cb, copyID, NULL, NULL);
+ if (abort) {
+ return abort;
+ }
+
FOR_ALL_TAB_WINDOWS(tp, win) {
if (win->w_llist != NULL) {
abort = mark_quickfix_ctx(win->w_llist, copyID);
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index 7a96889f22..27b5d198ac 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -1,9 +1,7 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-/*
- * Handling of regular expressions: vim_regcomp(), vim_regexec(), vim_regsub()
- */
+// Handling of regular expressions: vim_regcomp(), vim_regexec(), vim_regsub()
// By default: do not create debugging logs or files related to regular
// expressions, even when compiling with -DDEBUG.
@@ -41,21 +39,17 @@
# define BT_REGEXP_DEBUG_LOG_NAME "bt_regexp_debug.log"
#endif
-/*
- * Magic characters have a special meaning, they don't match literally.
- * Magic characters are negative. This separates them from literal characters
- * (possibly multi-byte). Only ASCII characters can be Magic.
- */
+// Magic characters have a special meaning, they don't match literally.
+// Magic characters are negative. This separates them from literal characters
+// (possibly multi-byte). Only ASCII characters can be Magic.
#define Magic(x) ((int)(x) - 256)
#define un_Magic(x) ((x) + 256)
#define is_Magic(x) ((x) < 0)
-/*
- * We should define ftpr as a pointer to a function returning a pointer to
- * a function returning a pointer to a function ...
- * This is impossible, so we declare a pointer to a function returning a
- * pointer to a function returning void. This should work for all compilers.
- */
+// We should define ftpr as a pointer to a function returning a pointer to
+// a function returning a pointer to a function ...
+// This is impossible, so we declare a pointer to a function returning a
+// pointer to a function returning void. This should work for all compilers.
typedef void (*(*fptr_T)(int *, int))(void);
static int no_Magic(int x)
@@ -143,28 +137,24 @@ static int re_multi_type(int c)
static char *reg_prev_sub = NULL;
-/*
- * REGEXP_INRANGE contains all characters which are always special in a []
- * range after '\'.
- * REGEXP_ABBR contains all characters which act as abbreviations after '\'.
- * These are:
- * \n - New line (NL).
- * \r - Carriage Return (CR).
- * \t - Tab (TAB).
- * \e - Escape (ESC).
- * \b - Backspace (Ctrl_H).
- * \d - Character code in decimal, eg \d123
- * \o - Character code in octal, eg \o80
- * \x - Character code in hex, eg \x4a
- * \u - Multibyte character code, eg \u20ac
- * \U - Long multibyte character code, eg \U12345678
- */
+// REGEXP_INRANGE contains all characters which are always special in a []
+// range after '\'.
+// REGEXP_ABBR contains all characters which act as abbreviations after '\'.
+// These are:
+// \n - New line (NL).
+// \r - Carriage Return (CR).
+// \t - Tab (TAB).
+// \e - Escape (ESC).
+// \b - Backspace (Ctrl_H).
+// \d - Character code in decimal, eg \d123
+// \o - Character code in octal, eg \o80
+// \x - Character code in hex, eg \x4a
+// \u - Multibyte character code, eg \u20ac
+// \U - Long multibyte character code, eg \U12345678
static char REGEXP_INRANGE[] = "]^-n\\";
static char REGEXP_ABBR[] = "nrtebdoxuU";
-/*
- * Translate '\x' to its control character, except "\n", which is Magic.
- */
+// Translate '\x' to its control character, except "\n", which is Magic.
static int backslash_trans(int c)
{
switch (c) {
@@ -239,10 +229,8 @@ static int get_char_class(char **pp)
return CLASS_NONE;
}
-/*
- * Specific version of character class functions.
- * Using a table to keep this fast.
- */
+// Specific version of character class functions.
+// Using a table to keep this fast.
static int16_t class_tab[256];
#define RI_DIGIT 0x01
@@ -325,9 +313,7 @@ static int reg_string; // matching with a string instead of a buffer
// line
static int reg_strict; // "[abc" is illegal
-/*
- * META contains all characters that may be magic, except '^' and '$'.
- */
+// META contains all characters that may be magic, except '^' and '$'.
// uncrustify:off
@@ -391,11 +377,9 @@ int re_multiline(const regprog_T *prog)
return prog->regflags & RF_HASNL;
}
-/*
- * Check for an equivalence class name "[=a=]". "pp" points to the '['.
- * Returns a character representing the class. Zero means that no item was
- * recognized. Otherwise "pp" is advanced to after the item.
- */
+// Check for an equivalence class name "[=a=]". "pp" points to the '['.
+// Returns a character representing the class. Zero means that no item was
+// recognized. Otherwise "pp" is advanced to after the item.
static int get_equi_class(char **pp)
{
int c;
@@ -413,12 +397,10 @@ static int get_equi_class(char **pp)
return 0;
}
-/*
- * Check for a collating element "[.a.]". "pp" points to the '['.
- * Returns a character. Zero means that no item was recognized. Otherwise
- * "pp" is advanced to after the item.
- * Currently only single characters are recognized!
- */
+// Check for a collating element "[.a.]". "pp" points to the '['.
+// Returns a character. Zero means that no item was recognized. Otherwise
+// "pp" is advanced to after the item.
+// Currently only single characters are recognized!
static int get_coll_element(char **pp)
{
int c;
@@ -562,9 +544,7 @@ static int prevchr_len; // byte length of previous char
static int at_start; // True when on the first character
static int prev_at_start; // True when on the second character
-/*
- * Start parsing at "str".
- */
+// Start parsing at "str".
static void initchr(char_u *str)
{
regparse = (char *)str;
@@ -574,10 +554,8 @@ static void initchr(char_u *str)
prev_at_start = false;
}
-/*
- * Save the current parse state, so that it can be restored and parsing
- * starts in the same state again.
- */
+// Save the current parse state, so that it can be restored and parsing
+// starts in the same state again.
static void save_parse_state(parse_state_T *ps)
{
ps->regparse = (char_u *)regparse;
@@ -591,9 +569,7 @@ static void save_parse_state(parse_state_T *ps)
ps->regnpar = regnpar;
}
-/*
- * Restore a previously saved parse state.
- */
+// Restore a previously saved parse state.
static void restore_parse_state(parse_state_T *ps)
{
regparse = (char *)ps->regparse;
@@ -607,9 +583,7 @@ static void restore_parse_state(parse_state_T *ps)
regnpar = ps->regnpar;
}
-/*
- * Get the next character without advancing.
- */
+// Get the next character without advancing.
static int peekchr(void)
{
static int after_slash = false;
@@ -736,9 +710,7 @@ static int peekchr(void)
after_slash--;
curchr = toggle_Magic(curchr);
} else if (vim_strchr(REGEXP_ABBR, c)) {
- /*
- * Handle abbreviations, like "\t" for TAB -- webb
- */
+ // Handle abbreviations, like "\t" for TAB -- webb
curchr = backslash_trans(c);
} else if (reg_magic == MAGIC_NONE && (c == '$' || c == '^')) {
curchr = toggle_Magic(c);
@@ -757,9 +729,7 @@ static int peekchr(void)
return curchr;
}
-/*
- * Eat one lexed character. Do this in a way that we can undo it.
- */
+// Eat one lexed character. Do this in a way that we can undo it.
static void skipchr(void)
{
// peekchr() eats a backslash, do the same here
@@ -781,10 +751,8 @@ static void skipchr(void)
nextchr = -1;
}
-/*
- * Skip a character while keeping the value of prev_at_start for at_start.
- * prevchr and prevprevchr are also kept.
- */
+// Skip a character while keeping the value of prev_at_start for at_start.
+// prevchr and prevprevchr are also kept.
static void skipchr_keepstart(void)
{
int as = prev_at_start;
@@ -797,10 +765,8 @@ static void skipchr_keepstart(void)
prevprevchr = prpr;
}
-/*
- * Get the next character from the pattern. We know about magic and such, so
- * therefore we need a lexical analyzer.
- */
+// Get the next character from the pattern. We know about magic and such, so
+// therefore we need a lexical analyzer.
static int getchr(void)
{
int chr = peekchr();
@@ -809,9 +775,7 @@ static int getchr(void)
return chr;
}
-/*
- * put character back. Works only once!
- */
+// put character back. Works only once!
static void ungetchr(void)
{
nextchr = curchr;
@@ -825,15 +789,13 @@ static void ungetchr(void)
regparse -= prevchr_len;
}
-/*
- * Get and return the value of the hex string at the current position.
- * Return -1 if there is no valid hex number.
- * The position is updated:
- * blahblah\%x20asdf
- * before-^ ^-after
- * The parameter controls the maximum number of input characters. This will be
- * 2 when reading a \%x20 sequence and 4 when reading a \%u20AC sequence.
- */
+// Get and return the value of the hex string at the current position.
+// Return -1 if there is no valid hex number.
+// The position is updated:
+// blahblah\%x20asdf
+// before-^ ^-after
+// The parameter controls the maximum number of input characters. This will be
+// 2 when reading a \%x20 sequence and 4 when reading a \%u20AC sequence.
static int64_t gethexchrs(int maxinputlen)
{
int64_t nr = 0;
@@ -856,10 +818,8 @@ static int64_t gethexchrs(int maxinputlen)
return nr;
}
-/*
- * Get and return the value of the decimal string immediately after the
- * current position. Return -1 for invalid. Consumes all digits.
- */
+// Get and return the value of the decimal string immediately after the
+// current position. Return -1 for invalid. Consumes all digits.
static int64_t getdecchrs(void)
{
int64_t nr = 0;
@@ -883,14 +843,12 @@ static int64_t getdecchrs(void)
return nr;
}
-/*
- * get and return the value of the octal string immediately after the current
- * position. Return -1 for invalid, or 0-255 for valid. Smart enough to handle
- * numbers > 377 correctly (for example, 400 is treated as 40) and doesn't
- * treat 8 or 9 as recognised characters. Position is updated:
- * blahblah\%o210asdf
- * before-^ ^-after
- */
+// get and return the value of the octal string immediately after the current
+// position. Return -1 for invalid, or 0-255 for valid. Smart enough to handle
+// numbers > 377 correctly (for example, 400 is treated as 40) and doesn't
+// treat 8 or 9 as recognised characters. Position is updated:
+// blahblah\%o210asdf
+// before-^ ^-after
static int64_t getoctchrs(void)
{
int64_t nr = 0;
@@ -913,12 +871,10 @@ static int64_t getoctchrs(void)
return nr;
}
-/*
- * read_limits - Read two integers to be taken as a minimum and maximum.
- * If the first character is '-', then the range is reversed.
- * Should end with 'end'. If minval is missing, zero is default, if maxval is
- * missing, a very big number is the default.
- */
+// read_limits - Read two integers to be taken as a minimum and maximum.
+// If the first character is '-', then the range is reversed.
+// Should end with 'end'. If minval is missing, zero is default, if maxval is
+// missing, a very big number is the default.
static int read_limits(long *minval, long *maxval)
{
int reverse = false;
@@ -950,10 +906,8 @@ static int read_limits(long *minval, long *maxval)
EMSG2_RET_FAIL(_("E554: Syntax error in %s{...}"), reg_magic == MAGIC_ALL);
}
- /*
- * Reverse the range if there was a '-', or make sure it is in the right
- * order otherwise.
- */
+ // Reverse the range if there was a '-', or make sure it is in the right
+ // order otherwise.
if ((!reverse && *minval > *maxval) || (reverse && *minval < *maxval)) {
tmp = *minval;
*minval = *maxval;
@@ -963,13 +917,9 @@ static int read_limits(long *minval, long *maxval)
return OK;
}
-/*
- * vim_regexec and friends
- */
+// vim_regexec and friends
-/*
- * Global work variables for vim_regexec().
- */
+// Global work variables for vim_regexec().
// Sometimes need to save a copy of a line. Since alloc()/free() is very
// slow, we keep one allocated piece of memory and only re-allocate it when
@@ -1052,9 +1002,7 @@ static bool reg_iswordc(int c)
return vim_iswordc_buf(c, rex.reg_buf);
}
-/*
- * Get pointer to the line "lnum", which is relative to "reg_firstlnum".
- */
+// Get pointer to the line "lnum", which is relative to "reg_firstlnum".
static char_u *reg_getline(linenr_T lnum)
{
// when looking behind for a match/no-match lnum is negative. But we
@@ -1077,9 +1025,7 @@ static lpos_T reg_endzpos[NSUBEXP]; // idem, end pos
// true if using multi-line regexp.
#define REG_MULTI (rex.reg_match == NULL)
-/*
- * Create a new extmatch and mark it as referenced once.
- */
+// Create a new extmatch and mark it as referenced once.
static reg_extmatch_T *make_extmatch(void)
FUNC_ATTR_NONNULL_RET
{
@@ -1088,9 +1034,7 @@ static reg_extmatch_T *make_extmatch(void)
return em;
}
-/*
- * Add a reference to an extmatch.
- */
+// Add a reference to an extmatch.
reg_extmatch_T *ref_extmatch(reg_extmatch_T *em)
{
if (em != NULL) {
@@ -1099,10 +1043,8 @@ reg_extmatch_T *ref_extmatch(reg_extmatch_T *em)
return em;
}
-/*
- * Remove a reference to an extmatch. If there are no references left, free
- * the info.
- */
+// Remove a reference to an extmatch. If there are no references left, free
+// the info.
void unref_extmatch(reg_extmatch_T *em)
{
int i;
@@ -1201,10 +1143,8 @@ static bool reg_match_visual(void)
return true;
}
-/*
- * Check the regexp program for its magic number.
- * Return true if it's wrong.
- */
+// Check the regexp program for its magic number.
+// Return true if it's wrong.
static int prog_magic_wrong(void)
{
regprog_T *prog;
@@ -1222,11 +1162,9 @@ static int prog_magic_wrong(void)
return false;
}
-/*
- * Cleanup the subexpressions, if this wasn't done yet.
- * This construction is used to clear the subexpressions only when they are
- * used (to increase speed).
- */
+// Cleanup the subexpressions, if this wasn't done yet.
+// This construction is used to clear the subexpressions only when they are
+// used (to increase speed).
static void cleanup_subexpr(void)
{
if (rex.need_clear_subexpr) {
@@ -1265,12 +1203,10 @@ static void reg_nextline(void)
fast_breakcheck();
}
-/*
- * Check whether a backreference matches.
- * Returns RA_FAIL, RA_NOMATCH or RA_MATCH.
- * If "bytelen" is not NULL, it is set to the byte length of the match in the
- * last line.
- */
+// Check whether a backreference matches.
+// Returns RA_FAIL, RA_NOMATCH or RA_MATCH.
+// If "bytelen" is not NULL, it is set to the byte length of the match in the
+// last line.
static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T end_lnum,
colnr_T end_col, int *bytelen)
{
@@ -1449,9 +1385,9 @@ static int cstrncmp(char *s1, char *s2, int *n)
c1 = mb_ptr2char_adv((const char_u **)&str1);
c2 = mb_ptr2char_adv((const char_u **)&str2);
- /* decompose the character if necessary, into 'base' characters
- * because I don't care about Arabic, I will hard-code the Hebrew
- * which I *do* care about! So sue me... */
+ // decompose the character if necessary, into 'base' characters
+ // because I don't care about Arabic, I will hard-code the Hebrew
+ // which I *do* care about! So sue me...
if (c1 != c2 && (!rex.reg_ic || utf_fold(c1) != utf_fold(c2))) {
// decomposition necessary?
mb_decompose(c1, &c11, &junk, &junk);
@@ -1566,7 +1502,7 @@ char *regtilde(char *source, int magic, bool preview)
int len;
int prevlen;
- for (p = newsub; *p; ++p) {
+ for (p = newsub; *p; p++) {
if ((*p == '~' && magic) || (*p == '\\' && *(p + 1) == '~' && !magic)) {
if (reg_prev_sub != NULL) {
// length = len(newsub) - 1 + len(prev_sub) + 1
@@ -1871,12 +1807,11 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des
*s = CAR;
} else if (*s == '\\' && s[1] != NUL) {
s++;
- /* Change NL to CR here too, so that this works:
- * :s/abc\\\ndef/\="aaa\\\nbbb"/ on text:
- * abc\
- * def
- * Not when called from vim_regexec_nl().
- */
+ // Change NL to CR here too, so that this works:
+ // :s/abc\\\ndef/\="aaa\\\nbbb"/ on text:
+ // abc{backslash}
+ // def
+ // Not when called from vim_regexec_nl().
if (*s == NL && !rsm.sm_line_lbr) {
*s = CAR;
}
@@ -2172,10 +2107,8 @@ char *reg_submatch(int no)
if (rsm.sm_match == NULL) {
ssize_t len;
- /*
- * First round: compute the length and allocate memory.
- * Second round: copy the text.
- */
+ // First round: compute the length and allocate memory.
+ // Second round: copy the text.
for (round = 1; round <= 2; round++) {
lnum = rsm.sm_mmatch->startpos[no].lnum;
if (lnum < 0 || rsm.sm_mmatch->endpos[no].lnum < 0) {
@@ -2216,7 +2149,7 @@ char *reg_submatch(int no)
len++;
}
if (round == 2) {
- STRNCPY(retval + len, reg_getline_submatch(lnum),
+ STRNCPY(retval + len, reg_getline_submatch(lnum), // NOLINT(runtime/printf)
rsm.sm_mmatch->endpos[no].col);
}
len += rsm.sm_mmatch->endpos[no].col;
@@ -2327,12 +2260,10 @@ static char_u regname[][30] = {
};
#endif
-/*
- * Compile a regular expression into internal code.
- * Returns the program in allocated memory.
- * Use vim_regfree() to free the memory.
- * Returns NULL for an error.
- */
+// Compile a regular expression into internal code.
+// Returns the program in allocated memory.
+// Use vim_regfree() to free the memory.
+// Returns NULL for an error.
regprog_T *vim_regcomp(char *expr_arg, int re_flags)
{
regprog_T *prog = NULL;
@@ -2413,9 +2344,7 @@ regprog_T *vim_regcomp(char *expr_arg, int re_flags)
return prog;
}
-/*
- * Free a compiled regexp program, returned by vim_regcomp().
- */
+// Free a compiled regexp program, returned by vim_regcomp().
void vim_regfree(regprog_T *prog)
{
if (prog != NULL) {
diff --git a/src/nvim/regexp_bt.c b/src/nvim/regexp_bt.c
index 6f63b38a90..7b5f4cd12a 100644
--- a/src/nvim/regexp_bt.c
+++ b/src/nvim/regexp_bt.c
@@ -1,137 +1,130 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-/*
- *
- * Backtracking regular expression implementation.
- *
- * This file is included in "regexp.c".
- *
- * NOTICE:
- *
- * This is NOT the original regular expression code as written by Henry
- * Spencer. This code has been modified specifically for use with the VIM
- * editor, and should not be used separately from Vim. If you want a good
- * regular expression library, get the original code. The copyright notice
- * that follows is from the original.
- *
- * END NOTICE
- *
- * Copyright (c) 1986 by University of Toronto.
- * Written by Henry Spencer. Not derived from licensed software.
- *
- * Permission is granted to anyone to use this software for any
- * purpose on any computer system, and to redistribute it freely,
- * subject to the following restrictions:
- *
- * 1. The author is not responsible for the consequences of use of
- * this software, no matter how awful, even if they arise
- * from defects in it.
- *
- * 2. The origin of this software must not be misrepresented, either
- * by explicit claim or by omission.
- *
- * 3. Altered versions must be plainly marked as such, and must not
- * be misrepresented as being the original software.
- *
- * Beware that some of this code is subtly aware of the way operator
- * precedence is structured in regular expressions. Serious changes in
- * regular-expression syntax might require a total rethink.
- *
- * Changes have been made by Tony Andrews, Olaf 'Rhialto' Seibert, Robert
- * Webb, Ciaran McCreesh and Bram Moolenaar.
- * Named character class support added by Walter Briscoe (1998 Jul 01)
- */
-
-/*
- * The "internal use only" fields in regexp_defs.h are present to pass info from
- * compile to execute that permits the execute phase to run lots faster on
- * simple cases. They are:
- *
- * regstart char that must begin a match; NUL if none obvious; Can be a
- * multi-byte character.
- * reganch is the match anchored (at beginning-of-line only)?
- * regmust string (pointer into program) that match must include, or NULL
- * regmlen length of regmust string
- * regflags RF_ values or'ed together
- *
- * Regstart and reganch permit very fast decisions on suitable starting points
- * for a match, cutting down the work a lot. Regmust permits fast rejection
- * of lines that cannot possibly match. The regmust tests are costly enough
- * that vim_regcomp() supplies a regmust only if the r.e. contains something
- * potentially expensive (at present, the only such thing detected is * or +
- * at the start of the r.e., which can involve a lot of backup). Regmlen is
- * supplied because the test in vim_regexec() needs it and vim_regcomp() is
- * computing it anyway.
- */
-
-/*
- * Structure for regexp "program". This is essentially a linear encoding
- * of a nondeterministic finite-state machine (aka syntax charts or
- * "railroad normal form" in parsing technology). Each node is an opcode
- * plus a "next" pointer, possibly plus an operand. "Next" pointers of
- * all nodes except BRANCH and BRACES_COMPLEX implement concatenation; a "next"
- * pointer with a BRANCH on both ends of it is connecting two alternatives.
- * (Here we have one of the subtle syntax dependencies: an individual BRANCH
- * (as opposed to a collection of them) is never concatenated with anything
- * because of operator precedence). The "next" pointer of a BRACES_COMPLEX
- * node points to the node after the stuff to be repeated.
- * The operand of some types of node is a literal string; for others, it is a
- * node leading into a sub-FSM. In particular, the operand of a BRANCH node
- * is the first node of the branch.
- * (NB this is *not* a tree structure: the tail of the branch connects to the
- * thing following the set of BRANCHes.)
- *
- * pattern is coded like:
- *
- * +-----------------+
- * | V
- * <aa>\|<bb> BRANCH <aa> BRANCH <bb> --> END
- * | ^ | ^
- * +------+ +----------+
- *
- *
- * +------------------+
- * V |
- * <aa>* BRANCH BRANCH <aa> --> BACK BRANCH --> NOTHING --> END
- * | | ^ ^
- * | +---------------+ |
- * +---------------------------------------------+
- *
- *
- * +----------------------+
- * V |
- * <aa>\+ BRANCH <aa> --> BRANCH --> BACK BRANCH --> NOTHING --> END
- * | | ^ ^
- * | +-----------+ |
- * +--------------------------------------------------+
- *
- *
- * +-------------------------+
- * V |
- * <aa>\{} BRANCH BRACE_LIMITS --> BRACE_COMPLEX <aa> --> BACK END
- * | | ^
- * | +----------------+
- * +-----------------------------------------------+
- *
- *
- * <aa>\@!<bb> BRANCH NOMATCH <aa> --> END <bb> --> END
- * | | ^ ^
- * | +----------------+ |
- * +--------------------------------+
- *
- * +---------+
- * | V
- * \z[abc] BRANCH BRANCH a BRANCH b BRANCH c BRANCH NOTHING --> END
- * | | | | ^ ^
- * | | | +-----+ |
- * | | +----------------+ |
- * | +---------------------------+ |
- * +------------------------------------------------------+
- *
- * They all start with a BRANCH for "\|" alternatives, even when there is only
- * one alternative.
- */
+// Backtracking regular expression implementation.
+//
+// This file is included in "regexp.c".
+//
+// NOTICE:
+//
+// This is NOT the original regular expression code as written by Henry
+// Spencer. This code has been modified specifically for use with the VIM
+// editor, and should not be used separately from Vim. If you want a good
+// regular expression library, get the original code. The copyright notice
+// that follows is from the original.
+//
+// END NOTICE
+//
+// Copyright (c) 1986 by University of Toronto.
+// Written by Henry Spencer. Not derived from licensed software.
+//
+// Permission is granted to anyone to use this software for any
+// purpose on any computer system, and to redistribute it freely,
+// subject to the following restrictions:
+//
+// 1. The author is not responsible for the consequences of use of
+// this software, no matter how awful, even if they arise
+// from defects in it.
+//
+// 2. The origin of this software must not be misrepresented, either
+// by explicit claim or by omission.
+//
+// 3. Altered versions must be plainly marked as such, and must not
+// be misrepresented as being the original software.
+//
+// Beware that some of this code is subtly aware of the way operator
+// precedence is structured in regular expressions. Serious changes in
+// regular-expression syntax might require a total rethink.
+//
+// Changes have been made by Tony Andrews, Olaf 'Rhialto' Seibert, Robert
+// Webb, Ciaran McCreesh and Bram Moolenaar.
+// Named character class support added by Walter Briscoe (1998 Jul 01)
+
+// The "internal use only" fields in regexp_defs.h are present to pass info from
+// compile to execute that permits the execute phase to run lots faster on
+// simple cases. They are:
+//
+// regstart char that must begin a match; NUL if none obvious; Can be a
+// multi-byte character.
+// reganch is the match anchored (at beginning-of-line only)?
+// regmust string (pointer into program) that match must include, or NULL
+// regmlen length of regmust string
+// regflags RF_ values or'ed together
+//
+// Regstart and reganch permit very fast decisions on suitable starting points
+// for a match, cutting down the work a lot. Regmust permits fast rejection
+// of lines that cannot possibly match. The regmust tests are costly enough
+// that vim_regcomp() supplies a regmust only if the r.e. contains something
+// potentially expensive (at present, the only such thing detected is * or +
+// at the start of the r.e., which can involve a lot of backup). Regmlen is
+// supplied because the test in vim_regexec() needs it and vim_regcomp() is
+// computing it anyway.
+
+// Structure for regexp "program". This is essentially a linear encoding
+// of a nondeterministic finite-state machine (aka syntax charts or
+// "railroad normal form" in parsing technology). Each node is an opcode
+// plus a "next" pointer, possibly plus an operand. "Next" pointers of
+// all nodes except BRANCH and BRACES_COMPLEX implement concatenation; a "next"
+// pointer with a BRANCH on both ends of it is connecting two alternatives.
+// (Here we have one of the subtle syntax dependencies: an individual BRANCH
+// (as opposed to a collection of them) is never concatenated with anything
+// because of operator precedence). The "next" pointer of a BRACES_COMPLEX
+// node points to the node after the stuff to be repeated.
+// The operand of some types of node is a literal string; for others, it is a
+// node leading into a sub-FSM. In particular, the operand of a BRANCH node
+// is the first node of the branch.
+// (NB this is *not* a tree structure: the tail of the branch connects to the
+// thing following the set of BRANCHes.)
+//
+// pattern is coded like:
+//
+// +-----------------+
+// | V
+// <aa>\|<bb> BRANCH <aa> BRANCH <bb> --> END
+// | ^ | ^
+// +------+ +----------+
+//
+//
+// +------------------+
+// V |
+// <aa>* BRANCH BRANCH <aa> --> BACK BRANCH --> NOTHING --> END
+// | | ^ ^
+// | +---------------+ |
+// +---------------------------------------------+
+//
+//
+// +----------------------+
+// V |
+// <aa>\+ BRANCH <aa> --> BRANCH --> BACK BRANCH --> NOTHING --> END
+// | | ^ ^
+// | +-----------+ |
+// +--------------------------------------------------+
+//
+//
+// +-------------------------+
+// V |
+// <aa>\{} BRANCH BRACE_LIMITS --> BRACE_COMPLEX <aa> --> BACK END
+// | | ^
+// | +----------------+
+// +-----------------------------------------------+
+//
+//
+// <aa>\@!<bb> BRANCH NOMATCH <aa> --> END <bb> --> END
+// | | ^ ^
+// | +----------------+ |
+// +--------------------------------+
+//
+// +---------+
+// | V
+// \z[abc] BRANCH BRANCH a BRANCH b BRANCH c BRANCH NOTHING --> END
+// | | | | ^ ^
+// | | | +-----+ |
+// | | +----------------+ |
+// | +---------------------------+ |
+// +------------------------------------------------------+
+//
+// They all start with a BRANCH for "\|" alternatives, even when there is only
+// one alternative.
#include <assert.h>
#include <inttypes.h>
@@ -141,9 +134,7 @@
#include "nvim/garray.h"
#include "nvim/regexp.h"
-/*
- * The opcodes are:
- */
+// The opcodes are:
// definition number opnd? meaning
#define END 0 // End of program or NOMATCH operand.
@@ -240,9 +231,7 @@
#define RE_VISUAL 208 // Match Visual area
#define RE_COMPOSING 209 // any composing characters
-/*
- * Flags to be passed up and down.
- */
+// Flags to be passed up and down.
#define HASWIDTH 0x1 // Known never to match null string.
#define SIMPLE 0x2 // Simple enough to be STAR/PLUS operand.
#define SPSTART 0x4 // Starts with * or +.
@@ -273,10 +262,8 @@ static int classcodes[] = {
UPPER, NUPPER
};
-/*
- * When regcode is set to this value, code is not emitted and size is computed
- * instead.
- */
+// When regcode is set to this value, code is not emitted and size is computed
+// instead.
#define JUST_CALC_SIZE ((char_u *)-1)
// Values for rs_state in regitem_T.
@@ -297,11 +284,9 @@ typedef enum regstate_E {
RS_STAR_SHORT, // STAR/PLUS/BRACE_SIMPLE shortest match
} regstate_T;
-/*
- * Structure used to save the current input state, when it needs to be
- * restored after trying a match. Used by reg_save() and reg_restore().
- * Also stores the length of "backpos".
- */
+// Structure used to save the current input state, when it needs to be
+// restored after trying a match. Used by reg_save() and reg_restore().
+// Also stores the length of "backpos".
typedef struct {
union {
char_u *ptr; // rex.input pointer, for single-line regexp
@@ -327,12 +312,10 @@ typedef struct regbehind_S {
save_se_T save_end[NSUBEXP];
} regbehind_T;
-/*
- * When there are alternatives a regstate_T is put on the regstack to remember
- * what we are doing.
- * Before it may be another type of item, depending on rs_state, to remember
- * more things.
- */
+// When there are alternatives a regstate_T is put on the regstack to remember
+// what we are doing.
+// Before it may be another type of item, depending on rs_state, to remember
+// more things.
typedef struct regitem_S {
regstate_T rs_state; // what we are doing, one of RS_ above
int16_t rs_no; // submatch nr or BEHIND/NOBEHIND
@@ -359,69 +342,63 @@ typedef struct backpos_S {
regsave_T bp_pos; // last input position
} backpos_T;
-/*
- * "regstack" and "backpos" are used by regmatch(). They are kept over calls
- * to avoid invoking malloc() and free() often.
- * "regstack" is a stack with regitem_T items, sometimes preceded by regstar_T
- * or regbehind_T.
- * "backpos_T" is a table with backpos_T for BACK
- */
+// "regstack" and "backpos" are used by regmatch(). They are kept over calls
+// to avoid invoking malloc() and free() often.
+// "regstack" is a stack with regitem_T items, sometimes preceded by regstar_T
+// or regbehind_T.
+// "backpos_T" is a table with backpos_T for BACK
static garray_T regstack = GA_EMPTY_INIT_VALUE;
static garray_T backpos = GA_EMPTY_INIT_VALUE;
static regsave_T behind_pos;
-/*
- * Both for regstack and backpos tables we use the following strategy of
- * allocation (to reduce malloc/free calls):
- * - Initial size is fairly small.
- * - When needed, the tables are grown bigger (8 times at first, double after
- * that).
- * - After executing the match we free the memory only if the array has grown.
- * Thus the memory is kept allocated when it's at the initial size.
- * This makes it fast while not keeping a lot of memory allocated.
- * A three times speed increase was observed when using many simple patterns.
- */
+// Both for regstack and backpos tables we use the following strategy of
+// allocation (to reduce malloc/free calls):
+// - Initial size is fairly small.
+// - When needed, the tables are grown bigger (8 times at first, double after
+// that).
+// - After executing the match we free the memory only if the array has grown.
+// Thus the memory is kept allocated when it's at the initial size.
+// This makes it fast while not keeping a lot of memory allocated.
+// A three times speed increase was observed when using many simple patterns.
#define REGSTACK_INITIAL 2048
#define BACKPOS_INITIAL 64
-/*
- * Opcode notes:
- *
- * BRANCH The set of branches constituting a single choice are hooked
- * together with their "next" pointers, since precedence prevents
- * anything being concatenated to any individual branch. The
- * "next" pointer of the last BRANCH in a choice points to the
- * thing following the whole choice. This is also where the
- * final "next" pointer of each individual branch points; each
- * branch starts with the operand node of a BRANCH node.
- *
- * BACK Normal "next" pointers all implicitly point forward; BACK
- * exists to make loop structures possible.
- *
- * STAR,PLUS '=', and complex '*' and '+', are implemented as circular
- * BRANCH structures using BACK. Simple cases (one character
- * per match) are implemented with STAR and PLUS for speed
- * and to minimize recursive plunges.
- *
- * BRACE_LIMITS This is always followed by a BRACE_SIMPLE or BRACE_COMPLEX
- * node, and defines the min and max limits to be used for that
- * node.
- *
- * MOPEN,MCLOSE ...are numbered at compile time.
- * ZOPEN,ZCLOSE ...ditto
- */
-
-/*
- * A node is one char of opcode followed by two chars of "next" pointer.
- * "Next" pointers are stored as two 8-bit bytes, high order first. The
- * value is a positive offset from the opcode of the node containing it.
- * An operand, if any, simply follows the node. (Note that much of the
- * code generation knows about this implicit relationship.)
- *
- * Using two bytes for the "next" pointer is vast overkill for most things,
- * but allows patterns to get big without disasters.
- */
+// Opcode notes:
+//
+// BRANCH The set of branches constituting a single choice are hooked
+// together with their "next" pointers, since precedence prevents
+// anything being concatenated to any individual branch. The
+// "next" pointer of the last BRANCH in a choice points to the
+// thing following the whole choice. This is also where the
+// final "next" pointer of each individual branch points; each
+// branch starts with the operand node of a BRANCH node.
+//
+// BACK Normal "next" pointers all implicitly point forward; BACK
+// exists to make loop structures possible.
+//
+// STAR,PLUS '=', and complex '*' and '+', are implemented as circular
+// BRANCH structures using BACK. Simple cases (one character
+// per match) are implemented with STAR and PLUS for speed
+// and to minimize recursive plunges.
+//
+// BRACE_LIMITS This is always followed by a BRACE_SIMPLE or BRACE_COMPLEX
+// node, and defines the min and max limits to be used for that
+// node.
+//
+// MOPEN,MCLOSE ...are numbered at compile time.
+// ZOPEN,ZCLOSE ...ditto
+///
+//
+//
+// A node is one char of opcode followed by two chars of "next" pointer.
+// "Next" pointers are stored as two 8-bit bytes, high order first. The
+// value is a positive offset from the opcode of the node containing it.
+// An operand, if any, simply follows the node. (Note that much of the
+// code generation knows about this implicit relationship.)
+//
+// Using two bytes for the "next" pointer is vast overkill for most things,
+// but allows patterns to get big without disasters.
#define OP(p) ((int)(*(p)))
#define NEXT(p) (((*((p) + 1) & 0377) << 8) + (*((p) + 2) & 0377))
#define OPERAND(p) ((p) + 3)
@@ -449,9 +426,7 @@ static int regnarrate = 0;
# include "regexp_bt.c.generated.h"
#endif
-/*
- * Setup to parse the regexp. Used once to get the length and once to do it.
- */
+// Setup to parse the regexp. Used once to get the length and once to do it.
static void regcomp_start(char_u *expr, int re_flags) // see vim_regcomp()
{
initchr(expr);
@@ -484,9 +459,7 @@ static bool use_multibytecode(int c)
|| utf_iscomposing(c));
}
-/*
- * Emit (if appropriate) a byte of code
- */
+// Emit (if appropriate) a byte of code
static void regc(int b)
{
if (regcode == JUST_CALC_SIZE) {
@@ -496,9 +469,7 @@ static void regc(int b)
}
}
-/*
- * Emit (if appropriate) a multi-byte character of code
- */
+// Emit (if appropriate) a multi-byte character of code
static void regmbc(int c)
{
if (regcode == JUST_CALC_SIZE) {
@@ -508,11 +479,9 @@ static void regmbc(int c)
}
}
-/*
- * Produce the bytes for equivalence class "c".
- * Currently only handles latin1, latin9 and utf-8.
- * NOTE: When changing this function, also change nfa_emit_equi_class()
- */
+// Produce the bytes for equivalence class "c".
+// Currently only handles latin1, latin9 and utf-8.
+// NOTE: When changing this function, also change nfa_emit_equi_class()
static void reg_equi_class(int c)
{
{
@@ -1481,10 +1450,8 @@ static void reg_equi_class(int c)
regmbc(c);
}
-/*
- * Emit a node.
- * Return pointer to generated code.
- */
+// Emit a node.
+// Return pointer to generated code.
static char_u *regnode(int op)
{
char_u *ret;
@@ -1500,9 +1467,7 @@ static char_u *regnode(int op)
return ret;
}
-/*
- * Write a four bytes number at "p" and return pointer to the next char.
- */
+// Write a four bytes number at "p" and return pointer to the next char.
static char_u *re_put_uint32(char_u *p, uint32_t val)
{
*p++ = (char_u)((val >> 24) & 0377);
@@ -1512,11 +1477,9 @@ static char_u *re_put_uint32(char_u *p, uint32_t val)
return p;
}
-/*
- * regnext - dig the "next" pointer out of a node
- * Returns NULL when calculating size, when there is no next item and when
- * there is an error.
- */
+// regnext - dig the "next" pointer out of a node
+// Returns NULL when calculating size, when there is no next item and when
+// there is an error.
static char_u *regnext(char_u *p)
FUNC_ATTR_NONNULL_ALL
{
@@ -1573,9 +1536,7 @@ static void regtail(char_u *p, char_u *val)
}
}
-/*
- * Like regtail, on item after a BRANCH; nop if none.
- */
+// Like regtail, on item after a BRANCH; nop if none.
static void regoptail(char_u *p, char_u *val)
{
// When op is neither BRANCH nor BRACE_COMPLEX0-9, it is "operandless"
@@ -1587,11 +1548,9 @@ static void regoptail(char_u *p, char_u *val)
regtail(OPERAND(p), val);
}
-/*
- * Insert an operator in front of already-emitted operand
- *
- * Means relocating the operand.
- */
+// Insert an operator in front of already-emitted operand
+//
+// Means relocating the operand.
static void reginsert(int op, char_u *opnd)
{
char_u *src;
@@ -1615,10 +1574,8 @@ static void reginsert(int op, char_u *opnd)
*place = NUL;
}
-/*
- * Insert an operator in front of already-emitted operand.
- * Add a number to the operator.
- */
+// Insert an operator in front of already-emitted operand.
+// Add a number to the operator.
static void reginsert_nr(int op, long val, char_u *opnd)
{
char_u *src;
@@ -1644,12 +1601,10 @@ static void reginsert_nr(int op, long val, char_u *opnd)
re_put_uint32(place, (uint32_t)val);
}
-/*
- * Insert an operator in front of already-emitted operand.
- * The operator has the given limit values as operands. Also set next pointer.
- *
- * Means relocating the operand.
- */
+// Insert an operator in front of already-emitted operand.
+// The operator has the given limit values as operands. Also set next pointer.
+//
+// Means relocating the operand.
static void reginsert_limits(int op, long minval, long maxval, char_u *opnd)
{
char_u *src;
@@ -1704,13 +1659,11 @@ static int seen_endbrace(int refnum)
return true;
}
-/*
- * Parse the lowest level.
- *
- * Optimization: gobbles an entire sequence of ordinary characters so that
- * it can turn them into a single node, which is smaller to store and
- * faster to run. Don't do this when one_exactly is set.
- */
+// Parse the lowest level.
+//
+// Optimization: gobbles an entire sequence of ordinary characters so that
+// it can turn them into a single node, which is smaller to store and
+// faster to run. Don't do this when one_exactly is set.
static char_u *regatom(int *flagp)
{
char_u *ret;
@@ -2289,8 +2242,7 @@ collection:
if (c_class != 0) {
// produce equivalence class
reg_equi_class(c_class);
- } else if ((c_class =
- get_coll_element(&regparse)) != 0) {
+ } else if ((c_class = get_coll_element(&regparse)) != 0) {
// produce a collating element
regmbc(c_class);
} else {
@@ -2466,7 +2418,7 @@ do_multibyte:
for (len = 0; c != NUL && (len == 0
|| (re_multi_type(peekchr()) == NOT_MULTI
&& !one_exactly
- && !is_Magic(c))); ++len) {
+ && !is_Magic(c))); len++) {
c = no_Magic(c);
{
regmbc(c);
@@ -2500,15 +2452,13 @@ do_multibyte:
return ret;
}
-/*
- * Parse something followed by possible [*+=].
- *
- * Note that the branching code sequences used for = and the general cases
- * of * and + are somewhat optimized: they use the same NOTHING node as
- * both the endmarker for their branch list and the body of the last branch.
- * It might seem that this node could be dispensed with entirely, but the
- * endmarker role is not redundant.
- */
+// Parse something followed by possible [*+=].
+//
+// Note that the branching code sequences used for = and the general cases
+// of * and + are somewhat optimized: they use the same NOTHING node as
+// both the endmarker for their branch list and the body of the last branch.
+// It might seem that this node could be dispensed with entirely, but the
+// endmarker role is not redundant.
static char_u *regpiece(int *flagp)
{
char_u *ret;
@@ -2644,10 +2594,8 @@ static char_u *regpiece(int *flagp)
return ret;
}
-/*
- * Parse one alternative of an | or & operator.
- * Implements the concatenation operator.
- */
+// Parse one alternative of an | or & operator.
+// Implements the concatenation operator.
static char_u *regconcat(int *flagp)
{
char_u *first = NULL;
@@ -2722,10 +2670,8 @@ static char_u *regconcat(int *flagp)
return first;
}
-/*
- * Parse one alternative of an | operator.
- * Implements the & operator.
- */
+// Parse one alternative of an | operator.
+// Implements the & operator.
static char_u *regbranch(int *flagp)
{
char_u *ret;
@@ -2874,27 +2820,25 @@ static char_u *reg(int paren, int *flagp)
return ret;
}
-/*
- * bt_regcomp() - compile a regular expression into internal code for the
- * traditional back track matcher.
- * Returns the program in allocated space. Returns NULL for an error.
- *
- * We can't allocate space until we know how big the compiled form will be,
- * but we can't compile it (and thus know how big it is) until we've got a
- * place to put the code. So we cheat: we compile it twice, once with code
- * generation turned off and size counting turned on, and once "for real".
- * This also means that we don't allocate space until we are sure that the
- * thing really will compile successfully, and we never have to move the
- * code and thus invalidate pointers into it. (Note that it has to be in
- * one piece because free() must be able to free it all.)
- *
- * Whether upper/lower case is to be ignored is decided when executing the
- * program, it does not matter here.
- *
- * Beware that the optimization-preparation code in here knows about some
- * of the structure of the compiled regexp.
- * "re_flags": RE_MAGIC and/or RE_STRING.
- */
+// bt_regcomp() - compile a regular expression into internal code for the
+// traditional back track matcher.
+// Returns the program in allocated space. Returns NULL for an error.
+//
+// We can't allocate space until we know how big the compiled form will be,
+// but we can't compile it (and thus know how big it is) until we've got a
+// place to put the code. So we cheat: we compile it twice, once with code
+// generation turned off and size counting turned on, and once "for real".
+// This also means that we don't allocate space until we are sure that the
+// thing really will compile successfully, and we never have to move the
+// code and thus invalidate pointers into it. (Note that it has to be in
+// one piece because free() must be able to free it all.)
+//
+// Whether upper/lower case is to be ignored is decided when executing the
+// program, it does not matter here.
+//
+// Beware that the optimization-preparation code in here knows about some
+// of the structure of the compiled regexp.
+// "re_flags": RE_MAGIC and/or RE_STRING.
static regprog_T *bt_regcomp(char_u *expr, int re_flags)
{
char_u *scan;
@@ -2999,19 +2943,15 @@ static regprog_T *bt_regcomp(char_u *expr, int re_flags)
return (regprog_T *)r;
}
-/*
- * Check if during the previous call to vim_regcomp the EOL item "$" has been
- * found. This is messy, but it works fine.
- */
+// Check if during the previous call to vim_regcomp the EOL item "$" has been
+// found. This is messy, but it works fine.
int vim_regcomp_had_eol(void)
{
return had_eol;
}
-/*
- * Get a number after a backslash that is inside [].
- * When nothing is recognized return a backslash.
- */
+// Get a number after a backslash that is inside [].
+// When nothing is recognized return a backslash.
static int coll_get_char(void)
{
int64_t nr = -1;
@@ -3037,9 +2977,7 @@ static int coll_get_char(void)
return (int)nr;
}
-/*
- * Free a compiled regexp program, returned by bt_regcomp().
- */
+// Free a compiled regexp program, returned by bt_regcomp().
static void bt_regfree(regprog_T *prog)
{
xfree(prog);
@@ -3047,11 +2985,9 @@ static void bt_regfree(regprog_T *prog)
#define ADVANCE_REGINPUT() MB_PTR_ADV(rex.input)
-/*
- * The arguments from BRACE_LIMITS are stored here. They are actually local
- * to regmatch(), but they are here to reduce the amount of stack space used
- * (it can be called recursively many times).
- */
+// The arguments from BRACE_LIMITS are stored here. They are actually local
+// to regmatch(), but they are here to reduce the amount of stack space used
+// (it can be called recursively many times).
static long bl_minval;
static long bl_maxval;
@@ -3108,13 +3044,11 @@ static bool reg_save_equal(const regsave_T *save)
else /* NOLINT */ \
*(pp) = (savep)->se_u.ptr; }
-/*
- * Tentatively set the sub-expression start to the current position (after
- * calling regmatch() they will have changed). Need to save the existing
- * values for when there is no match.
- * Use se_save() to use pointer (save_se_multi()) or position (save_se_one()),
- * depending on REG_MULTI.
- */
+// Tentatively set the sub-expression start to the current position (after
+// calling regmatch() they will have changed). Need to save the existing
+// values for when there is no match.
+// Use se_save() to use pointer (save_se_multi()) or position (save_se_one()),
+// depending on REG_MULTI.
static void save_se_multi(save_se_T *savep, lpos_T *posp)
{
savep->se_u.pos = *posp;
@@ -3494,10 +3428,8 @@ do_class:
return (int)count;
}
-/*
- * Push an item onto the regstack.
- * Returns pointer to new item. Returns NULL when out of memory.
- */
+// Push an item onto the regstack.
+// Returns pointer to new item. Returns NULL when out of memory.
static regitem_T *regstack_push(regstate_T state, char_u *scan)
{
regitem_T *rp;
@@ -3516,9 +3448,7 @@ static regitem_T *regstack_push(regstate_T state, char_u *scan)
return rp;
}
-/*
- * Pop an item from the regstack.
- */
+// Pop an item from the regstack.
static void regstack_pop(char_u **scan)
{
regitem_T *rp;
@@ -4643,7 +4573,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out)
// Pop the state. Restore pointers when there is no match.
if (status == RA_NOMATCH) {
reg_restore(&rp->rs_un.regsave, &backpos);
- --brace_count[rp->rs_no]; // decrement match count
+ brace_count[rp->rs_no]--; // decrement match count
}
regstack_pop(&scan);
break;
@@ -4653,7 +4583,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out)
if (status == RA_NOMATCH) {
// There was no match, but we did find enough matches.
reg_restore(&rp->rs_un.regsave, &backpos);
- --brace_count[rp->rs_no];
+ brace_count[rp->rs_no]--;
// continue with the items after "\{}"
status = RA_CONT;
}
@@ -5247,9 +5177,7 @@ static long bt_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_T
return bt_regexec_both(NULL, col, tm, timed_out);
}
-/*
- * Compare a number with the operand of RE_LNUM, RE_COL or RE_VCOL.
- */
+// Compare a number with the operand of RE_LNUM, RE_COL or RE_VCOL.
static int re_num_cmp(uint32_t val, char_u *scan)
{
uint32_t n = (uint32_t)OPERAND_MIN(scan);
@@ -5265,9 +5193,7 @@ static int re_num_cmp(uint32_t val, char_u *scan)
#ifdef BT_REGEXP_DUMP
-/*
- * regdump - dump a regexp onto stdout in vaguely comprehensible form
- */
+// regdump - dump a regexp onto stdout in vaguely comprehensible form
static void regdump(char_u *pattern, bt_regprog_T *r)
{
char_u *s;
@@ -5353,9 +5279,7 @@ static void regdump(char_u *pattern, bt_regprog_T *r)
#ifdef REGEXP_DEBUG
-/*
- * regprop - printable representation of opcode
- */
+// regprop - printable representation of opcode
static char_u *regprop(char_u *op)
{
char *p;
diff --git a/src/nvim/regexp_defs.h b/src/nvim/regexp_defs.h
index b24ed350e8..ee32b8d13a 100644
--- a/src/nvim/regexp_defs.h
+++ b/src/nvim/regexp_defs.h
@@ -1,13 +1,11 @@
-/*
- * NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE
- *
- * This is NOT the original regular expression code as written by Henry
- * Spencer. This code has been modified specifically for use with Vim, and
- * should not be used apart from compiling Vim. If you want a good regular
- * expression library, get the original code.
- *
- * NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE
- */
+// NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE
+//
+// This is NOT the original regular expression code as written by Henry
+// Spencer. This code has been modified specifically for use with Vim, and
+// should not be used apart from compiling Vim. If you want a good regular
+// expression library, get the original code.
+//
+// NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE
#ifndef NVIM_REGEXP_DEFS_H
#define NVIM_REGEXP_DEFS_H
@@ -17,18 +15,14 @@
#include "nvim/pos.h"
#include "nvim/types.h"
-/*
- * The number of sub-matches is limited to 10.
- * The first one (index 0) is the whole match, referenced with "\0".
- * The second one (index 1) is the first sub-match, referenced with "\1".
- * This goes up to the tenth (index 9), referenced with "\9".
- */
+// The number of sub-matches is limited to 10.
+// The first one (index 0) is the whole match, referenced with "\0".
+// The second one (index 1) is the first sub-match, referenced with "\1".
+// This goes up to the tenth (index 9), referenced with "\9".
#define NSUBEXP 10
-/*
- * In the NFA engine: how many braces are allowed.
- * TODO(RE): Use dynamic memory allocation instead of static, like here
- */
+// In the NFA engine: how many braces are allowed.
+// TODO(RE): Use dynamic memory allocation instead of static, like here
#define NFA_MAX_BRACES 20
// In the NFA engine: how many states are allowed.
@@ -61,11 +55,9 @@ typedef struct {
#include "nvim/buffer_defs.h"
-/*
- * Structure returned by vim_regcomp() to pass on to vim_regexec().
- * This is the general structure. For the actual matcher, two specific
- * structures are used. See code below.
- */
+// Structure returned by vim_regcomp() to pass on to vim_regexec().
+// This is the general structure. For the actual matcher, two specific
+// structures are used. See code below.
struct regprog {
regengine_T *engine;
unsigned regflags;
@@ -74,11 +66,9 @@ struct regprog {
bool re_in_use; ///< prog is being executed
};
-/*
- * Structure used by the back track matcher.
- * These fields are only to be used in regexp.c!
- * See regexp.c for an explanation.
- */
+// Structure used by the back track matcher.
+// These fields are only to be used in regexp.c!
+// See regexp.c for an explanation.
typedef struct {
// These four members implement regprog_T.
regengine_T *engine;
@@ -107,9 +97,7 @@ struct nfa_state {
int val;
};
-/*
- * Structure used by the NFA matcher.
- */
+// Structure used by the NFA matcher.
typedef struct {
// These four members implement regprog_T.
regengine_T *engine;
@@ -133,11 +121,9 @@ typedef struct {
nfa_state_T state[1]; // actually longer..
} nfa_regprog_T;
-/*
- * Structure to be used for single-line matching.
- * Sub-match "no" starts at "startp[no]" and ends just before "endp[no]".
- * When there is no match, the pointer is NULL.
- */
+// Structure to be used for single-line matching.
+// Sub-match "no" starts at "startp[no]" and ends just before "endp[no]".
+// When there is no match, the pointer is NULL.
typedef struct {
regprog_T *regprog;
char *startp[NSUBEXP];
@@ -145,11 +131,9 @@ typedef struct {
bool rm_ic;
} regmatch_T;
-/*
- * Structure used to store external references: "\z\(\)" to "\z\1".
- * Use a reference count to avoid the need to copy this around. When it goes
- * from 1 to zero the matches need to be freed.
- */
+// Structure used to store external references: "\z\(\)" to "\z\1".
+// Use a reference count to avoid the need to copy this around. When it goes
+// from 1 to zero the matches need to be freed.
struct reg_extmatch {
int16_t refcnt;
char_u *matches[NSUBEXP];
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
index d4d2ed28cc..c4102c40ec 100644
--- a/src/nvim/regexp_nfa.c
+++ b/src/nvim/regexp_nfa.c
@@ -1,11 +1,9 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-/*
- * NFA regular expression implementation.
- *
- * This file is included in "regexp.c".
- */
+// NFA regular expression implementation.
+//
+// This file is included in "regexp.c".
#include <assert.h>
#include <inttypes.h>
@@ -383,10 +381,8 @@ static void nfa_regcomp_start(char_u *expr, int re_flags)
regcomp_start(expr, re_flags);
}
-/*
- * Figure out if the NFA state list starts with an anchor, must match at start
- * of the line.
- */
+// Figure out if the NFA state list starts with an anchor, must match at start
+// of the line.
static int nfa_get_reganch(nfa_state_T *start, int depth)
{
nfa_state_T *p = start;
@@ -441,10 +437,8 @@ static int nfa_get_reganch(nfa_state_T *start, int depth)
return 0;
}
-/*
- * Figure out if the NFA state list starts with a character which must match
- * at start of the match.
- */
+// Figure out if the NFA state list starts with a character which must match
+// at start of the match.
static int nfa_get_regstart(nfa_state_T *start, int depth)
{
nfa_state_T *p = start;
@@ -521,11 +515,9 @@ static int nfa_get_regstart(nfa_state_T *start, int depth)
return 0;
}
-/*
- * Figure out if the NFA state list contains just literal text and nothing
- * else. If so return a string in allocated memory with what must match after
- * regstart. Otherwise return NULL.
- */
+// Figure out if the NFA state list contains just literal text and nothing
+// else. If so return a string in allocated memory with what must match after
+// regstart. Otherwise return NULL.
static char_u *nfa_get_match_text(nfa_state_T *start)
{
nfa_state_T *p = start;
@@ -557,10 +549,8 @@ static char_u *nfa_get_match_text(nfa_state_T *start)
return ret;
}
-/*
- * Allocate more space for post_start. Called when
- * running above the estimated number of states.
- */
+// Allocate more space for post_start. Called when
+// running above the estimated number of states.
static void realloc_post_list(void)
{
// For weird patterns the number of states can be very high. Increasing by
@@ -572,16 +562,14 @@ static void realloc_post_list(void)
post_start = new_start;
}
-/*
- * Search between "start" and "end" and try to recognize a
- * character class in expanded form. For example [0-9].
- * On success, return the id the character class to be emitted.
- * On failure, return 0 (=FAIL)
- * Start points to the first char of the range, while end should point
- * to the closing brace.
- * Keep in mind that 'ignorecase' applies at execution time, thus [a-z] may
- * need to be interpreted as [a-zA-Z].
- */
+// Search between "start" and "end" and try to recognize a
+// character class in expanded form. For example [0-9].
+// On success, return the id the character class to be emitted.
+// On failure, return 0 (=FAIL)
+// Start points to the first char of the range, while end should point
+// to the closing brace.
+// Keep in mind that 'ignorecase' applies at execution time, thus [a-z] may
+// need to be interpreted as [a-zA-Z].
static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl)
{
#define CLASS_not 0x80
@@ -700,14 +688,12 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl)
return FAIL;
}
-/*
- * Produce the bytes for equivalence class "c".
- * Currently only handles latin1, latin9 and utf-8.
- * Emits bytes in postfix notation: 'a,b,NFA_OR,c,NFA_OR' is
- * equivalent to 'a OR b OR c'
- *
- * NOTE! When changing this function, also update reg_equi_class()
- */
+// Produce the bytes for equivalence class "c".
+// Currently only handles latin1, latin9 and utf-8.
+// Emits bytes in postfix notation: 'a,b,NFA_OR,c,NFA_OR' is
+// equivalent to 'a OR b OR c'
+//
+// NOTE! When changing this function, also update reg_equi_class()
static void nfa_emit_equi_class(int c)
{
#define EMIT2(c) EMIT(c); EMIT(NFA_CONCAT);
@@ -1778,26 +1764,22 @@ static void nfa_emit_equi_class(int c)
#undef EMIT2
}
-/*
- * Code to parse regular expression.
- *
- * We try to reuse parsing functions in regexp.c to
- * minimize surprise and keep the syntax consistent.
- */
-
-/*
- * Parse the lowest level.
- *
- * An atom can be one of a long list of items. Many atoms match one character
- * in the text. It is often an ordinary character or a character class.
- * Braces can be used to make a pattern into an atom. The "\z(\)" construct
- * is only for syntax highlighting.
- *
- * atom ::= ordinary-atom
- * or \( pattern \)
- * or \%( pattern \)
- * or \z( pattern \)
- */
+// Code to parse regular expression.
+//
+// We try to reuse parsing functions in regexp.c to
+// minimize surprise and keep the syntax consistent.
+
+// Parse the lowest level.
+//
+// An atom can be one of a long list of items. Many atoms match one character
+// in the text. It is often an ordinary character or a character class.
+// Braces can be used to make a pattern into an atom. The "\z(\)" construct
+// is only for syntax highlighting.
+//
+// atom ::= ordinary-atom
+// or \( pattern \)
+// or \%( pattern \)
+// or \z( pattern \)
static int nfa_regatom(void)
{
int c;
@@ -1862,9 +1844,7 @@ static int nfa_regatom(void)
// "\_x" is character class plus newline
FALLTHROUGH;
- /*
- * Character classes.
- */
+ // Character classes.
case Magic('.'):
case Magic('i'):
case Magic('I'):
@@ -2228,24 +2208,20 @@ static int nfa_regatom(void)
case Magic('['):
collection:
- /*
- * [abc] uses NFA_START_COLL - NFA_END_COLL
- * [^abc] uses NFA_START_NEG_COLL - NFA_END_NEG_COLL
- * Each character is produced as a regular state, using
- * NFA_CONCAT to bind them together.
- * Besides normal characters there can be:
- * - character classes NFA_CLASS_*
- * - ranges, two characters followed by NFA_RANGE.
- */
+ // [abc] uses NFA_START_COLL - NFA_END_COLL
+ // [^abc] uses NFA_START_NEG_COLL - NFA_END_NEG_COLL
+ // Each character is produced as a regular state, using
+ // NFA_CONCAT to bind them together.
+ // Besides normal characters there can be:
+ // - character classes NFA_CLASS_*
+ // - ranges, two characters followed by NFA_RANGE.
p = (char_u *)regparse;
endp = skip_anyof((char *)p);
if (*endp == ']') {
- /*
- * Try to reverse engineer character classes. For example,
- * recognize that [0-9] stands for \d and [A-Za-z_] for \h,
- * and perform the necessary substitutions in the NFA.
- */
+ // Try to reverse engineer character classes. For example,
+ // recognize that [0-9] stands for \d and [A-Za-z_] for \h,
+ // and perform the necessary substitutions in the NFA.
int result = nfa_recognize_char_class((char_u *)regparse, endp, extra == NFA_ADD_NL);
if (result != FAIL) {
if (result >= NFA_FIRST_NL && result <= NFA_LAST_NL) {
@@ -2259,10 +2235,8 @@ collection:
MB_PTR_ADV(regparse);
return OK;
}
- /*
- * Failed to recognize a character class. Use the simple
- * version that turns [abc] into 'a' OR 'b' OR 'c'
- */
+ // Failed to recognize a character class. Use the simple
+ // version that turns [abc] into 'a' OR 'b' OR 'c'
startc = -1;
negated = false;
if (*regparse == '^') { // negated range
@@ -2554,16 +2528,14 @@ nfa_do_multibyte:
return OK;
}
-/*
- * Parse something followed by possible [*+=].
- *
- * A piece is an atom, possibly followed by a multi, an indication of how many
- * times the atom can be matched. Example: "a*" matches any sequence of "a"
- * characters: "", "a", "aa", etc.
- *
- * piece ::= atom
- * or atom multi
- */
+// Parse something followed by possible [*+=].
+//
+// A piece is an atom, possibly followed by a multi, an indication of how many
+// times the atom can be matched. Example: "a*" matches any sequence of "a"
+// characters: "", "a", "aa", etc.
+//
+// piece ::= atom
+// or atom multi
static int nfa_regpiece(void)
{
int i;
@@ -2601,17 +2573,15 @@ static int nfa_regpiece(void)
break;
case Magic('+'):
- /*
- * Trick: Normally, (a*)\+ would match the whole input "aaa". The
- * first and only submatch would be "aaa". But the backtracking
- * engine interprets the plus as "try matching one more time", and
- * a* matches a second time at the end of the input, the empty
- * string.
- * The submatch will be the empty string.
- *
- * In order to be consistent with the old engine, we replace
- * <atom>+ with <atom><atom>*
- */
+ // Trick: Normally, (a*)\+ would match the whole input "aaa". The
+ // first and only submatch would be "aaa". But the backtracking
+ // engine interprets the plus as "try matching one more time", and
+ // a* matches a second time at the end of the input, the empty
+ // string.
+ // The submatch will be the empty string.
+ //
+ // In order to be consistent with the old engine, we replace
+ // <atom>+ with <atom><atom>*
restore_parse_state(&old_state);
curchr = -1;
if (nfa_regatom() == FAIL) {
@@ -2770,16 +2740,14 @@ static int nfa_regpiece(void)
return OK;
}
-/*
- * Parse one or more pieces, concatenated. It matches a match for the
- * first piece, followed by a match for the second piece, etc. Example:
- * "f[0-9]b", first matches "f", then a digit and then "b".
- *
- * concat ::= piece
- * or piece piece
- * or piece piece piece
- * etc.
- */
+// Parse one or more pieces, concatenated. It matches a match for the
+// first piece, followed by a match for the second piece, etc. Example:
+// "f[0-9]b", first matches "f", then a digit and then "b".
+//
+// concat ::= piece
+// or piece piece
+// or piece piece piece
+// etc.
static int nfa_regconcat(void)
{
bool cont = true;
@@ -2843,18 +2811,16 @@ static int nfa_regconcat(void)
return OK;
}
-/*
- * Parse a branch, one or more concats, separated by "\&". It matches the
- * last concat, but only if all the preceding concats also match at the same
- * position. Examples:
- * "foobeep\&..." matches "foo" in "foobeep".
- * ".*Peter\&.*Bob" matches in a line containing both "Peter" and "Bob"
- *
- * branch ::= concat
- * or concat \& concat
- * or concat \& concat \& concat
- * etc.
- */
+// Parse a branch, one or more concats, separated by "\&". It matches the
+// last concat, but only if all the preceding concats also match at the same
+// position. Examples:
+// "foobeep\&..." matches "foo" in "foobeep".
+// ".*Peter\&.*Bob" matches in a line containing both "Peter" and "Bob"
+//
+// branch ::= concat
+// or concat \& concat
+// or concat \& concat \& concat
+// etc.
static int nfa_regbranch(void)
{
int old_post_pos;
@@ -3311,9 +3277,7 @@ static FILE *log_fd;
static char_u e_log_open_failed[] =
N_("Could not open temporary log file for writing, displaying on stderr... ");
-/*
- * Print the postfix notation of the current regexp.
- */
+// Print the postfix notation of the current regexp.
static void nfa_postfix_dump(char_u *expr, int retval)
{
int *p;
@@ -3341,9 +3305,7 @@ static void nfa_postfix_dump(char_u *expr, int retval)
}
}
-/*
- * Print the NFA starting with a root node "state".
- */
+// Print the NFA starting with a root node "state".
static void nfa_print_state(FILE *debugf, nfa_state_T *state)
{
garray_T indent;
@@ -3413,9 +3375,7 @@ static void nfa_print_state2(FILE *debugf, nfa_state_T *state, garray_T *indent)
ga_append(indent, NUL);
}
-/*
- * Print the NFA state machine.
- */
+// Print the NFA state machine.
static void nfa_dump(nfa_regprog_T *prog)
{
FILE *debugf = fopen(NFA_REGEXP_DUMP_LOG, "a");
@@ -3437,12 +3397,10 @@ static void nfa_dump(nfa_regprog_T *prog)
fclose(debugf);
}
}
-#endif /* REGEXP_DEBUG */
+#endif // REGEXP_DEBUG
-/*
- * Parse r.e. @expr and convert it into postfix form.
- * Return the postfix string on success, NULL otherwise.
- */
+// Parse r.e. @expr and convert it into postfix form.
+// Return the postfix string on success, NULL otherwise.
static int *re2post(void)
{
if (nfa_reg(REG_NOPAREN) == FAIL) {
@@ -3454,18 +3412,14 @@ static int *re2post(void)
// NB. Some of the code below is inspired by Russ's.
-/*
- * Represents an NFA state plus zero or one or two arrows exiting.
- * if c == MATCH, no arrows out; matching state.
- * If c == SPLIT, unlabeled arrows to out and out1 (if != NULL).
- * If c < 256, labeled arrow with character c to out.
- */
+// Represents an NFA state plus zero or one or two arrows exiting.
+// if c == MATCH, no arrows out; matching state.
+// If c == SPLIT, unlabeled arrows to out and out1 (if != NULL).
+// If c < 256, labeled arrow with character c to out.
static nfa_state_T *state_ptr; // points to nfa_prog->state
-/*
- * Allocate and initialize nfa_state_T.
- */
+// Allocate and initialize nfa_state_T.
static nfa_state_T *alloc_state(int c, nfa_state_T *out, nfa_state_T *out1)
{
nfa_state_T *s;
@@ -3488,16 +3442,12 @@ static nfa_state_T *alloc_state(int c, nfa_state_T *out, nfa_state_T *out1)
return s;
}
-/*
- * A partially built NFA without the matching state filled in.
- * Frag_T.start points at the start state.
- * Frag_T.out is a list of places that need to be set to the
- * next state for this fragment.
- */
+// A partially built NFA without the matching state filled in.
+// Frag_T.start points at the start state.
+// Frag_T.out is a list of places that need to be set to the
+// next state for this fragment.
-/*
- * Initialize a Frag_T struct and return it.
- */
+// Initialize a Frag_T struct and return it.
static Frag_T frag(nfa_state_T *start, Ptrlist *out)
{
Frag_T n;
@@ -3507,9 +3457,7 @@ static Frag_T frag(nfa_state_T *start, Ptrlist *out)
return n;
}
-/*
- * Create singleton list containing just outp.
- */
+// Create singleton list containing just outp.
static Ptrlist *list1(nfa_state_T **outp)
{
Ptrlist *l;
@@ -3519,9 +3467,7 @@ static Ptrlist *list1(nfa_state_T **outp)
return l;
}
-/*
- * Patch the list of states at out to point to start.
- */
+// Patch the list of states at out to point to start.
static void patch(Ptrlist *l, nfa_state_T *s)
{
Ptrlist *next;
@@ -3532,9 +3478,7 @@ static void patch(Ptrlist *l, nfa_state_T *s)
}
}
-/*
- * Join the two lists l1 and l2, returning the combination.
- */
+// Join the two lists l1 and l2, returning the combination.
static Ptrlist *append(Ptrlist *l1, Ptrlist *l2)
{
Ptrlist *oldl1;
@@ -3547,9 +3491,7 @@ static Ptrlist *append(Ptrlist *l1, Ptrlist *l2)
return oldl1;
}
-/*
- * Stack used for transforming postfix form into NFA.
- */
+// Stack used for transforming postfix form into NFA.
static Frag_T empty;
static void st_error(int *postfix, int *end, int *p)
@@ -3592,9 +3534,7 @@ static void st_error(int *postfix, int *end, int *p)
emsg(_("E874: (NFA) Could not pop the stack!"));
}
-/*
- * Push an item onto the stack.
- */
+// Push an item onto the stack.
static void st_push(Frag_T s, Frag_T **p, Frag_T *stack_end)
{
Frag_T *stackp = *p;
@@ -3606,9 +3546,7 @@ static void st_push(Frag_T s, Frag_T **p, Frag_T *stack_end)
*p = *p + 1;
}
-/*
- * Pop an item from the stack.
- */
+// Pop an item from the stack.
static Frag_T st_pop(Frag_T **p, Frag_T *stack)
{
Frag_T *stackp;
@@ -3621,10 +3559,8 @@ static Frag_T st_pop(Frag_T **p, Frag_T *stack)
return **p;
}
-/*
- * Estimate the maximum byte length of anything matching "state".
- * When unknown or unlimited return -1.
- */
+// Estimate the maximum byte length of anything matching "state".
+// When unknown or unlimited return -1.
static int nfa_max_width(nfa_state_T *startstate, int depth)
{
int l, r;
@@ -3827,10 +3763,8 @@ static int nfa_max_width(nfa_state_T *startstate, int depth)
return -1;
}
-/*
- * Convert a postfix form into its equivalent NFA.
- * Return the NFA start state on success, NULL otherwise.
- */
+// Convert a postfix form into its equivalent NFA.
+// Return the NFA start state on success, NULL otherwise.
static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
{
int *p;
@@ -3866,7 +3800,7 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
stack_end = stack + (nstate + 1);
}
- for (p = postfix; p < end; ++p) {
+ for (p = postfix; p < end; p++) {
switch (*p) {
case NFA_CONCAT:
// Concatenation.
@@ -4350,15 +4284,13 @@ theend:
#undef PUSH
}
-/*
- * After building the NFA program, inspect it to add optimization hints.
- */
+// After building the NFA program, inspect it to add optimization hints.
static void nfa_postprocess(nfa_regprog_T *prog)
{
int i;
int c;
- for (i = 0; i < prog->nstate; ++i) {
+ for (i = 0; i < prog->nstate; i++) {
c = prog->state[i].c;
if (c == NFA_START_INVISIBLE
|| c == NFA_START_INVISIBLE_NEG
@@ -4490,9 +4422,7 @@ static void clear_sub(regsub_T *sub)
sub->in_use = 0;
}
-/*
- * Copy the submatches from "from" to "to".
- */
+// Copy the submatches from "from" to "to".
static void copy_sub(regsub_T *to, regsub_T *from)
{
to->in_use = from->in_use;
@@ -4508,9 +4438,7 @@ static void copy_sub(regsub_T *to, regsub_T *from)
}
}
-/*
- * Like copy_sub() but exclude the main match.
- */
+// Like copy_sub() but exclude the main match.
static void copy_sub_off(regsub_T *to, regsub_T *from)
{
if (to->in_use < from->in_use) {
@@ -4528,9 +4456,7 @@ static void copy_sub_off(regsub_T *to, regsub_T *from)
}
}
-/*
- * Like copy_sub() but only do the end of the main match if \ze is present.
- */
+// Like copy_sub() but only do the end of the main match if \ze is present.
static void copy_ze_off(regsub_T *to, regsub_T *from)
{
if (rex.nfa_has_zend) {
@@ -4954,7 +4880,7 @@ static regsubs_T *addstate(nfa_list_T *l, nfa_state_T *state, regsubs_T *subs_ar
// When called from addstate_here() do insert before
// existing states.
if (add_here) {
- for (k = 0; k < l->n && k < listindex; ++k) {
+ for (k = 0; k < l->n && k < listindex; k++) {
if (l->t[k].state->id == state->id) {
found = true;
break;
@@ -5094,7 +5020,7 @@ skip_add:
save_in_use = -1;
} else {
save_in_use = sub->in_use;
- for (i = sub->in_use; i < subidx; ++i) {
+ for (i = sub->in_use; i < subidx; i++) {
sub->list.multi[i].start_lnum = -1;
sub->list.multi[i].end_lnum = -1;
}
@@ -5115,7 +5041,7 @@ skip_add:
save_in_use = -1;
} else {
save_in_use = sub->in_use;
- for (i = sub->in_use; i < subidx; ++i) {
+ for (i = sub->in_use; i < subidx; i++) {
sub->list.line[i].start = NULL;
sub->list.line[i].end = NULL;
}
@@ -5314,9 +5240,7 @@ static regsubs_T *addstate_here(nfa_list_T *l, nfa_state_T *state, regsubs_T *su
return r;
}
-/*
- * Check character class "class" against current character c.
- */
+// Check character class "class" against current character c.
static int check_char_class(int class, int c)
{
switch (class) {
@@ -5502,11 +5426,9 @@ static int match_zref(int subidx, int *bytelen)
return false;
}
-/*
- * Save list IDs for all NFA states of "prog" into "list".
- * Also reset the IDs to zero.
- * Only used for the recursive value lastlist[1].
- */
+// Save list IDs for all NFA states of "prog" into "list".
+// Also reset the IDs to zero.
+// Only used for the recursive value lastlist[1].
static void nfa_save_listids(nfa_regprog_T *prog, int *list)
{
int i;
@@ -5521,9 +5443,7 @@ static void nfa_save_listids(nfa_regprog_T *prog, int *list)
}
}
-/*
- * Restore list IDs from "list" to all NFA states.
- */
+// Restore list IDs from "list" to all NFA states.
static void nfa_restore_listids(nfa_regprog_T *prog, int *list)
{
int i;
@@ -5547,11 +5467,9 @@ static bool nfa_re_num_cmp(uintmax_t val, int op, uintmax_t pos)
return val == pos;
}
-/*
- * Recursively call nfa_regmatch()
- * "pim" is NULL or contains info about a Postponed Invisible Match (start
- * position).
- */
+// Recursively call nfa_regmatch()
+// "pim" is NULL or contains info about a Postponed Invisible Match (start
+// position).
static int recursive_regmatch(nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T *prog,
regsubs_T *submatch, regsubs_T *m, int **listids, int *listids_len)
FUNC_ATTR_NONNULL_ARG(1, 3, 5, 6, 7)
@@ -5691,12 +5609,10 @@ static int recursive_regmatch(nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T
return result;
}
-/*
- * Estimate the chance of a match with "state" failing.
- * empty match: 0
- * NFA_ANY: 1
- * specific character: 99
- */
+// Estimate the chance of a match with "state" failing.
+// empty match: 0
+// NFA_ANY: 1
+// specific character: 99
static int failure_chance(nfa_state_T *state, int depth)
{
int c = state->c;
@@ -5851,9 +5767,7 @@ static int failure_chance(nfa_state_T *state, int depth)
return 50;
}
-/*
- * Skip until the char "c" we know a match must start with.
- */
+// Skip until the char "c" we know a match must start with.
static int skip_to_start(int c, colnr_T *colp)
{
const char_u *const s = cstrchr(rex.line + *colp, c);
@@ -5864,11 +5778,9 @@ static int skip_to_start(int c, colnr_T *colp)
return OK;
}
-/*
- * Check for a match with match_text.
- * Called after skip_to_start() has found regstart.
- * Returns zero for no match, 1 for a match.
- */
+// Check for a match with match_text.
+// Called after skip_to_start() has found regstart.
+// Returns zero for no match, 1 for a match.
static long find_match_text(colnr_T startcol, int regstart, char_u *match_text)
{
#define PTR2LEN(x) utf_ptr2len(x)
@@ -6038,9 +5950,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm
add_off = clen; \
}
- /*
- * Run for each character.
- */
+ // Run for each character.
for (;;) {
int curc = utf_ptr2char((char *)rex.input);
int clen = utfc_ptr2len((char *)rex.input);
@@ -6086,9 +5996,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm
#ifdef NFA_REGEXP_DEBUG_LOG
fprintf(debug, "\n-------------------\n");
#endif
- /*
- * If the state lists are empty we can stop.
- */
+ // If the state lists are empty we can stop.
if (thislist->n == 0) {
break;
}
@@ -6131,10 +6039,8 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm
}
#endif
- /*
- * Handle the possible codes of the current state.
- * The most important is NFA_MATCH.
- */
+ // Handle the possible codes of the current state.
+ // The most important is NFA_MATCH.
add_state = NULL;
add_here = false;
add_count = 0;
@@ -7525,10 +7431,8 @@ theend:
return retval;
}
-/*
- * Compile a regular expression into internal code for the NFA matcher.
- * Returns the program in allocated space. Returns NULL for an error.
- */
+// Compile a regular expression into internal code for the NFA matcher.
+// Returns the program in allocated space. Returns NULL for an error.
static regprog_T *nfa_regcomp(char_u *expr, int re_flags)
{
nfa_regprog_T *prog = NULL;
@@ -7554,11 +7458,9 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags)
goto fail; // Cascaded (syntax?) error
}
- /*
- * In order to build the NFA, we parse the input regexp twice:
- * 1. first pass to count size (so we can allocate space)
- * 2. second to emit code
- */
+ // In order to build the NFA, we parse the input regexp twice:
+ // 1. first pass to count size (so we can allocate space)
+ // 2. second to emit code
#ifdef REGEXP_DEBUG
{
FILE *f = fopen(NFA_REGEXP_RUN_LOG, "a");
@@ -7573,10 +7475,8 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags)
}
#endif
- /*
- * PASS 1
- * Count number of NFA states in "nstate". Do not build the NFA.
- */
+ // PASS 1
+ // Count number of NFA states in "nstate". Do not build the NFA.
post2nfa(postfix, post_ptr, true);
// allocate the regprog with space for the compiled regexp
@@ -7585,10 +7485,8 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags)
state_ptr = prog->state;
prog->re_in_use = false;
- /*
- * PASS 2
- * Build the NFA
- */
+ // PASS 2
+ // Build the NFA
prog->start = post2nfa(postfix, post_ptr, false);
if (prog->start == NULL) {
goto fail;
@@ -7632,9 +7530,7 @@ fail:
goto out;
}
-/*
- * Free a compiled regexp program, returned by nfa_regcomp().
- */
+// Free a compiled regexp program, returned by nfa_regcomp().
static void nfa_regfree(regprog_T *prog)
{
if (prog != NULL) {
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index cd1d6553cb..39b3291404 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -598,6 +598,49 @@ void stl_clear_click_defs(StlClickDefinition *const click_defs, const long click
}
}
+/// Allocate or resize the click definitions array if needed.
+StlClickDefinition *stl_alloc_click_defs(StlClickDefinition *cdp, long width, size_t *size)
+{
+ if (*size < (size_t)width) {
+ xfree(cdp);
+ *size = (size_t)width;
+ cdp = xcalloc(*size, sizeof(StlClickDefinition));
+ }
+ return cdp;
+}
+
+/// Fill the click definitions array if needed.
+void stl_fill_click_defs(StlClickDefinition *click_defs, StlClickRecord *click_recs, char *buf,
+ int width, bool tabline)
+{
+ if (click_defs == NULL) {
+ return;
+ }
+
+ int col = 0;
+ int len = 0;
+
+ StlClickDefinition cur_click_def = {
+ .type = kStlClickDisabled,
+ };
+ for (int i = 0; click_recs[i].start != NULL; i++) {
+ len += vim_strnsize(buf, (int)(click_recs[i].start - buf));
+ while (col < len) {
+ click_defs[col++] = cur_click_def;
+ }
+ buf = (char *)click_recs[i].start;
+ cur_click_def = click_recs[i].def;
+ if (!tabline && !(cur_click_def.type == kStlClickDisabled
+ || cur_click_def.type == kStlClickFuncRun)) {
+ // window bar and status line only support click functions
+ cur_click_def.type = kStlClickDisabled;
+ }
+ }
+ while (col < width) {
+ click_defs[col++] = cur_click_def;
+ }
+}
+
/// Set cursor to its position in the current window.
void setcursor(void)
{
@@ -956,16 +999,7 @@ void draw_tabline(void)
// Use the 'tabline' option if it's set.
if (*p_tal != NUL) {
- int saved_did_emsg = did_emsg;
-
- // Check for an error. If there is one we would loop in redrawing the
- // screen. Avoid that by making 'tabline' empty.
- did_emsg = false;
win_redr_custom(NULL, false, false);
- if (did_emsg) {
- set_string_option_direct("tabline", -1, "", OPT_FREE, SID_ERROR);
- }
- did_emsg |= saved_did_emsg;
} else {
FOR_ALL_TABS(tp) {
tabcount++;
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index 11e468038e..64029e0799 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -3078,32 +3078,37 @@ shada_write_file_nomerge: {}
sd_reader.close(&sd_reader);
bool did_remove = false;
if (sw_ret == kSDWriteSuccessful) {
-#ifdef UNIX
- // For Unix we check the owner of the file. It's not very nice to
- // overwrite a user’s viminfo file after a "su root", with a
- // viminfo file that the user can't read.
FileInfo old_info;
- if (os_fileinfo(fname, &old_info)) {
- if (getuid() == ROOT_UID) {
- if (old_info.stat.st_uid != ROOT_UID
- || old_info.stat.st_gid != getgid()) {
- const uv_uid_t old_uid = (uv_uid_t)old_info.stat.st_uid;
- const uv_gid_t old_gid = (uv_gid_t)old_info.stat.st_gid;
- const int fchown_ret = os_fchown(file_fd(sd_writer.cookie),
- old_uid, old_gid);
- if (fchown_ret != 0) {
- semsg(_(RNERR "Failed setting uid and gid for file %s: %s"),
- tempname, os_strerror(fchown_ret));
- goto shada_write_file_did_not_remove;
- }
+ if (!os_fileinfo(fname, &old_info)
+ || S_ISDIR(old_info.stat.st_mode)
+#ifdef UNIX
+ // For Unix we check the owner of the file. It's not very nice
+ // to overwrite a user's viminfo file after a "su root", with a
+ // viminfo file that the user can't read.
+ || (getuid() != ROOT_UID
+ && !(old_info.stat.st_uid == getuid()
+ ? (old_info.stat.st_mode & 0200)
+ : (old_info.stat.st_gid == getgid()
+ ? (old_info.stat.st_mode & 0020)
+ : (old_info.stat.st_mode & 0002))))
+#endif
+ ) {
+ semsg(_("E137: ShaDa file is not writable: %s"), fname);
+ goto shada_write_file_did_not_remove;
+ }
+#ifdef UNIX
+ if (getuid() == ROOT_UID) {
+ if (old_info.stat.st_uid != ROOT_UID
+ || old_info.stat.st_gid != getgid()) {
+ const uv_uid_t old_uid = (uv_uid_t)old_info.stat.st_uid;
+ const uv_gid_t old_gid = (uv_gid_t)old_info.stat.st_gid;
+ const int fchown_ret = os_fchown(file_fd(sd_writer.cookie),
+ old_uid, old_gid);
+ if (fchown_ret != 0) {
+ semsg(_(RNERR "Failed setting uid and gid for file %s: %s"),
+ tempname, os_strerror(fchown_ret));
+ goto shada_write_file_did_not_remove;
}
- } else if (!(old_info.stat.st_uid == getuid()
- ? (old_info.stat.st_mode & 0200)
- : (old_info.stat.st_gid == getgid()
- ? (old_info.stat.st_mode & 0020)
- : (old_info.stat.st_mode & 0002)))) {
- semsg(_("E137: ShaDa file is not writable: %s"), fname);
- goto shada_write_file_did_not_remove;
}
}
#endif
@@ -3124,9 +3129,7 @@ shada_write_file_nomerge: {}
}
}
if (!did_remove) {
-#ifdef UNIX
shada_write_file_did_not_remove:
-#endif
semsg(_(RNERR "Do not forget to remove %s or rename it manually to %s."),
tempname, fname);
}
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index 3ca9693609..f24af5c3bf 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -3147,7 +3147,7 @@ void ex_spelldump(exarg_T *eap)
}
char *spl;
long dummy;
- (void)get_option_value("spl", &dummy, &spl, OPT_LOCAL);
+ (void)get_option_value("spl", &dummy, &spl, NULL, OPT_LOCAL);
// Create a new empty buffer in a new window.
do_cmdline_cmd("new");
diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c
index fe154a15f7..d483863fa8 100644
--- a/src/nvim/spellfile.c
+++ b/src/nvim/spellfile.c
@@ -2470,9 +2470,9 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname)
aff_entry->ae_cond = (char_u *)getroom_save(spin, (char_u *)items[4]);
if (*items[0] == 'P') {
- sprintf((char *)buf, "^%s", items[4]);
+ sprintf((char *)buf, "^%s", items[4]); // NOLINT(runtime/printf)
} else {
- sprintf((char *)buf, "%s$", items[4]);
+ sprintf((char *)buf, "%s$", items[4]); // NOLINT(runtime/printf)
}
aff_entry->ae_prog = vim_regcomp((char *)buf, RE_MAGIC + RE_STRING + RE_STRICT);
if (aff_entry->ae_prog == NULL) {
@@ -2519,8 +2519,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname)
onecap_copy((char_u *)items[4], buf, true);
aff_entry->ae_cond = (char_u *)getroom_save(spin, buf);
if (aff_entry->ae_cond != NULL) {
- sprintf((char *)buf, "^%s",
- aff_entry->ae_cond);
+ sprintf((char *)buf, "^%s", aff_entry->ae_cond); // NOLINT(runtime/printf)
vim_regfree(aff_entry->ae_prog);
aff_entry->ae_prog = vim_regcomp((char *)buf, RE_MAGIC + RE_STRING);
}
@@ -3619,7 +3618,7 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff
if (store_aff_word(spin, newword, ae->ae_flags,
affile, &affile->af_suff, xht,
use_condit & (xht == NULL
- ? ~0 : ~CONDIT_SUF),
+ ? ~0 : ~CONDIT_SUF),
use_flags, use_pfxlist, pfxlen) == FAIL) {
retval = FAIL;
}
diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c
index fb49a1b6a7..039fe5d586 100644
--- a/src/nvim/statusline.c
+++ b/src/nvim/statusline.c
@@ -172,19 +172,7 @@ void win_redr_winbar(win_T *wp)
if (wp->w_winbar_height == 0 || !redrawing()) {
// Do nothing.
} else if (*p_wbr != NUL || *wp->w_p_wbr != NUL) {
- int saved_did_emsg = did_emsg;
-
- did_emsg = false;
win_redr_custom(wp, true, false);
- if (did_emsg) {
- // When there is an error disable the winbar, otherwise the
- // display is messed up with errors and a redraw triggers the problem
- // again and again.
- set_string_option_direct("winbar", -1, "",
- OPT_FREE | (*wp->w_p_stl != NUL
- ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR);
- }
- did_emsg |= saved_did_emsg;
}
entered = false;
}
@@ -214,11 +202,7 @@ void win_redr_ruler(win_T *wp, bool always)
}
if (*p_ruf && p_ch > 0 && !ui_has(kUIMessages)) {
- const int called_emsg_before = called_emsg;
win_redr_custom(wp, false, true);
- if (called_emsg > called_emsg_before) {
- set_string_option_direct("rulerformat", -1, "", OPT_FREE, SID_ERROR);
- }
return;
}
@@ -389,7 +373,6 @@ int fillchar_status(int *attr, win_T *wp)
void redraw_custom_statusline(win_T *wp)
{
static bool entered = false;
- int saved_did_emsg = did_emsg;
// When called recursively return. This can happen when the statusline
// contains an expression that triggers a redraw.
@@ -398,17 +381,7 @@ void redraw_custom_statusline(win_T *wp)
}
entered = true;
- did_emsg = false;
win_redr_custom(wp, false, false);
- if (did_emsg) {
- // When there is an error disable the statusline, otherwise the
- // display is messed up with errors and a redraw triggers the problem
- // again and again.
- set_string_option_direct("statusline", -1, "",
- OPT_FREE | (*wp->w_p_stl != NUL
- ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR);
- }
- did_emsg |= saved_did_emsg;
entered = false;
}
@@ -429,9 +402,10 @@ void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
char buf[MAXPATHL];
char *stl;
char *p;
+ char *opt_name;
+ int opt_scope = 0;
stl_hlrec_t *hltab;
StlClickRecord *tabtab;
- int use_sandbox = false;
win_T *ewp;
int p_crb_save;
bool is_stl_global = global_stl_height() > 0;
@@ -454,9 +428,11 @@ void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
fillchar = ' ';
attr = HL_ATTR(HLF_TPF);
maxwidth = Columns;
- use_sandbox = was_set_insecurely(wp, "tabline", 0);
+ opt_name = "tabline";
} else if (draw_winbar) {
+ opt_name = "winbar";
stl = ((*wp->w_p_wbr != NUL) ? wp->w_p_wbr : p_wbr);
+ opt_scope = ((*wp->w_p_wbr != NUL) ? OPT_LOCAL : 0);
row = -1; // row zero is first row of text
col = 0;
grid = &wp->w_grid;
@@ -469,30 +445,20 @@ void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
fillchar = wp->w_p_fcs_chars.wbr;
attr = (wp == curwin) ? win_hl_attr(wp, HLF_WBR) : win_hl_attr(wp, HLF_WBRNC);
maxwidth = wp->w_width_inner;
- use_sandbox = was_set_insecurely(wp, "winbar", 0);
-
stl_clear_click_defs(wp->w_winbar_click_defs, (long)wp->w_winbar_click_defs_size);
- // Allocate / resize the click definitions array for winbar if needed.
- if (wp->w_winbar_height && wp->w_winbar_click_defs_size < (size_t)maxwidth) {
- xfree(wp->w_winbar_click_defs);
- wp->w_winbar_click_defs_size = (size_t)maxwidth;
- wp->w_winbar_click_defs = xcalloc(wp->w_winbar_click_defs_size, sizeof(StlClickRecord));
- }
+ wp->w_winbar_click_defs = stl_alloc_click_defs(wp->w_winbar_click_defs, maxwidth,
+ &wp->w_winbar_click_defs_size);
} else {
row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp);
fillchar = fillchar_status(&attr, wp);
maxwidth = is_stl_global ? Columns : wp->w_width;
-
stl_clear_click_defs(wp->w_status_click_defs, (long)wp->w_status_click_defs_size);
- // Allocate / resize the click definitions array for statusline if needed.
- if (wp->w_status_click_defs_size < (size_t)maxwidth) {
- xfree(wp->w_status_click_defs);
- wp->w_status_click_defs_size = (size_t)maxwidth;
- wp->w_status_click_defs = xcalloc(wp->w_status_click_defs_size, sizeof(StlClickRecord));
- }
+ wp->w_status_click_defs = stl_alloc_click_defs(wp->w_status_click_defs, maxwidth,
+ &wp->w_status_click_defs_size);
if (draw_ruler) {
stl = p_ruf;
+ opt_name = "rulerformat";
// advance past any leading group spec - implicit in ru_col
if (*stl == '%') {
if (*++stl == '-') {
@@ -519,15 +485,10 @@ void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
fillchar = ' ';
attr = HL_ATTR(HLF_MSG);
}
-
- use_sandbox = was_set_insecurely(wp, "rulerformat", 0);
} else {
- if (*wp->w_p_stl != NUL) {
- stl = wp->w_p_stl;
- } else {
- stl = p_stl;
- }
- use_sandbox = was_set_insecurely(wp, "statusline", *wp->w_p_stl == NUL ? 0 : OPT_LOCAL);
+ opt_name = "statusline";
+ stl = ((*wp->w_p_stl != NUL) ? wp->w_p_stl : p_stl);
+ opt_scope = ((*wp->w_p_stl != NUL) ? OPT_LOCAL : 0);
}
col += is_stl_global ? 0 : wp->w_wincol;
@@ -546,9 +507,8 @@ void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
// Make a copy, because the statusline may include a function call that
// might change the option value and free the memory.
stl = xstrdup(stl);
- width =
- build_stl_str_hl(ewp, buf, sizeof(buf), stl, use_sandbox,
- fillchar, maxwidth, &hltab, &tabtab);
+ width = build_stl_str_hl(ewp, buf, sizeof(buf), stl, opt_name,
+ opt_scope, fillchar, maxwidth, &hltab, &tabtab);
xfree(stl);
ewp->w_p_crb = p_crb_save;
@@ -597,32 +557,7 @@ void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
: draw_winbar ? wp->w_winbar_click_defs
: wp->w_status_click_defs;
- if (click_defs == NULL) {
- goto theend;
- }
-
- col = 0;
- len = 0;
- p = buf;
- StlClickDefinition cur_click_def = {
- .type = kStlClickDisabled,
- };
- for (n = 0; tabtab[n].start != NULL; n++) {
- len += vim_strnsize(p, (int)(tabtab[n].start - p));
- while (col < len) {
- click_defs[col++] = cur_click_def;
- }
- p = (char *)tabtab[n].start;
- cur_click_def = tabtab[n].def;
- if ((wp != NULL) && !(cur_click_def.type == kStlClickDisabled
- || cur_click_def.type == kStlClickFuncRun)) {
- // window bar and status line only support click functions
- cur_click_def.type = kStlClickDisabled;
- }
- }
- while (col < maxwidth) {
- click_defs[col++] = cur_click_def;
- }
+ stl_fill_click_defs(click_defs, tabtab, buf, maxwidth, wp == NULL);
theend:
entered = false;
@@ -646,15 +581,16 @@ theend:
/// Note: This should not be NameBuff
/// @param outlen The length of the output buffer
/// @param fmt The statusline format string
-/// @param use_sandbox Use a sandboxed environment when evaluating fmt
+/// @param opt_name The option name corresponding to "fmt"
+/// @param opt_scope The scope corresponding to "opt_name"
/// @param fillchar Character to use when filling empty space in the statusline
/// @param maxwidth The maximum width to make the statusline
/// @param hltab HL attributes (can be NULL)
/// @param tabtab Tab clicks definition (can be NULL).
///
/// @return The final width of the statusline
-int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_sandbox, int fillchar,
- int maxwidth, stl_hlrec_t **hltab, StlClickRecord **tabtab)
+int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_name, int opt_scope,
+ int fillchar, int maxwidth, stl_hlrec_t **hltab, StlClickRecord **tabtab)
{
static size_t stl_items_len = 20; // Initial value, grows as needed.
static stl_item_t *stl_items = NULL;
@@ -669,6 +605,10 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san
char *usefmt = fmt;
const int save_must_redraw = must_redraw;
const int save_redr_type = curwin->w_redr_type;
+ // TODO(Bram): find out why using called_emsg_before makes tests fail, does it
+ // matter?
+ // const int called_emsg_before = called_emsg;
+ const int did_emsg_before = did_emsg;
if (stl_items == NULL) {
stl_items = xmalloc(sizeof(stl_item_t) * stl_items_len);
@@ -682,6 +622,11 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san
stl_separator_locations = xmalloc(sizeof(int) * stl_items_len);
}
+ // if "fmt" was set insecurely it needs to be evaluated in the sandbox
+ // "opt_name" will be NULL when caller is nvim_eval_statusline()
+ const int use_sandbox = opt_name ? was_set_insecurely(wp, opt_name, opt_scope)
+ : false;
+
// When the format starts with "%!" then evaluate it as an expression and
// use the result as the actual format string.
if (fmt[0] == '%' && fmt[1] == '!') {
@@ -1804,5 +1749,15 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san
curwin->w_redr_type = save_redr_type;
}
+ // Check for an error. If there is one the display will be messed up and
+ // might loop redrawing. Avoid that by making the corresponding option
+ // empty.
+ // TODO(Bram): find out why using called_emsg_before makes tests fail, does it
+ // matter?
+ // if (called_emsg > called_emsg_before)
+ if (opt_name && did_emsg > did_emsg_before) {
+ set_string_option_direct(opt_name, -1, "", OPT_FREE | opt_scope, SID_ERROR);
+ }
+
return width;
}
diff --git a/src/nvim/strings.c b/src/nvim/strings.c
index 806e1eb5ec..dc48273c8e 100644
--- a/src/nvim/strings.c
+++ b/src/nvim/strings.c
@@ -1034,9 +1034,7 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
: va_arg(ap, long long)); // NOLINT (runtime/int)
break;
case 'z':
- arg = (tvs
- ? (ptrdiff_t)tv_nr(tvs, &arg_idx)
- : va_arg(ap, ptrdiff_t));
+ arg = (tvs ? (ptrdiff_t)tv_nr(tvs, &arg_idx) : va_arg(ap, ptrdiff_t));
break;
}
if (arg > 0) {
@@ -1048,19 +1046,13 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
// unsigned
switch (length_modifier) {
case '\0':
- uarg = (unsigned int)(tvs
- ? tv_nr(tvs, &arg_idx)
- : va_arg(ap, unsigned int));
+ uarg = (unsigned int)(tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, unsigned int));
break;
case 'h':
- uarg = (uint16_t)(tvs
- ? tv_nr(tvs, &arg_idx)
- : va_arg(ap, unsigned int));
+ uarg = (uint16_t)(tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, unsigned int));
break;
case 'l':
- uarg = (tvs
- ? (unsigned long)tv_nr(tvs, &arg_idx)
- : va_arg(ap, unsigned long));
+ uarg = (tvs ? (unsigned long)tv_nr(tvs, &arg_idx) : va_arg(ap, unsigned long));
break;
case '2':
uarg = (uintmax_t)(unsigned long long)( // NOLINT (runtime/int)
@@ -1070,9 +1062,7 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
: va_arg(ap, unsigned long long)); // NOLINT (runtime/int)
break;
case 'z':
- uarg = (tvs
- ? (size_t)tv_nr(tvs, &arg_idx)
- : va_arg(ap, size_t));
+ uarg = (tvs ? (size_t)tv_nr(tvs, &arg_idx) : va_arg(ap, size_t));
break;
}
arg_sign = (uarg != 0);
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index f49cbaa39c..fe1bdf12b1 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -1655,13 +1655,12 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con
&& (spp->sp_type == SPTYPE_MATCH
|| spp->sp_type == SPTYPE_START)
&& (current_next_list != NULL
- ? in_id_list(NULL, current_next_list,
- &spp->sp_syn, 0)
- : (cur_si == NULL
- ? !(spp->sp_flags & HL_CONTAINED)
- : in_id_list(cur_si,
- cur_si->si_cont_list, &spp->sp_syn,
- spp->sp_flags & HL_CONTAINED)))) {
+ ? in_id_list(NULL, current_next_list, &spp->sp_syn, 0)
+ : (cur_si == NULL
+ ? !(spp->sp_flags & HL_CONTAINED)
+ : in_id_list(cur_si,
+ cur_si->si_cont_list, &spp->sp_syn,
+ spp->sp_flags & HL_CONTAINED)))) {
// If we already tried matching in this line, and
// there isn't a match before next_match_col, skip
// this item.
@@ -2788,9 +2787,9 @@ static keyentry_T *match_keyword(char *keyword, hashtab_T *ht, stateitem_T *cur_
if (current_next_list != 0
? in_id_list(NULL, current_next_list, &kp->k_syn, 0)
: (cur_si == NULL
- ? !(kp->flags & HL_CONTAINED)
- : in_id_list(cur_si, cur_si->si_cont_list,
- &kp->k_syn, kp->flags & HL_CONTAINED))) {
+ ? !(kp->flags & HL_CONTAINED)
+ : in_id_list(cur_si, cur_si->si_cont_list,
+ &kp->k_syn, kp->flags & HL_CONTAINED))) {
return kp;
}
}
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index 607446a8a1..b6b8f5c959 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -108,6 +108,8 @@ static char_u *recurmsg
= (char_u *)N_("E986: cannot modify the tag stack within tagfunc");
static char_u *tfu_inv_ret_msg
= (char_u *)N_("E987: invalid return value from tagfunc");
+static char e_window_unexpectedly_close_while_searching_for_tags[]
+ = N_("E1299: Window unexpectedly closed while searching for tags");
static char *tagmatchname = NULL; // name of last used tag
@@ -116,10 +118,56 @@ static char *tagmatchname = NULL; // name of last used tag
static taggy_T ptag_entry = { NULL, INIT_FMARK, 0, 0, NULL };
static int tfu_in_use = false; // disallow recursive call of tagfunc
+static Callback tfu_cb; // 'tagfunc' callback function
// Used instead of NUL to separate tag fields in the growarrays.
#define TAG_SEP 0x02
+/// Reads the 'tagfunc' option value and convert that to a callback value.
+/// Invoked when the 'tagfunc' option is set. The option value can be a name of
+/// a function (string), or function(<name>) or funcref(<name>) or a lambda.
+int set_tagfunc_option(void)
+{
+ callback_free(&tfu_cb);
+ callback_free(&curbuf->b_tfu_cb);
+
+ if (*curbuf->b_p_tfu == NUL) {
+ return OK;
+ }
+
+ if (option_set_callback_func(curbuf->b_p_tfu, &tfu_cb) == FAIL) {
+ return FAIL;
+ }
+
+ callback_copy(&curbuf->b_tfu_cb, &tfu_cb);
+
+ return OK;
+}
+
+#if defined(EXITFREE)
+void free_tagfunc_option(void)
+{
+ callback_free(&tfu_cb);
+}
+#endif
+
+/// Mark the global 'tagfunc' callback with "copyID" so that it is not garbage
+/// collected.
+bool set_ref_in_tagfunc(int copyID)
+{
+ return set_ref_in_callback(&tfu_cb, copyID, NULL, NULL);
+}
+
+/// Copy the global 'tagfunc' callback function to the buffer-local 'tagfunc'
+/// callback for 'buf'.
+void set_buflocal_tfu_callback(buf_T *buf)
+{
+ callback_free(&buf->b_tfu_cb);
+ if (tfu_cb.type != kCallbackNone) {
+ callback_copy(&buf->b_tfu_cb, &tfu_cb);
+ }
+}
+
/// Jump to tag; handling of tag commands and tag stack
///
/// *tag != NUL: ":tag {tag}", jump to new tag, add to tag stack
@@ -162,6 +210,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
int skip_msg = false;
char_u *buf_ffname = (char_u *)curbuf->b_ffname; // name for priority computation
int use_tfu = 1;
+ char *tofree = NULL;
// remember the matches for the last used tag
static int num_matches = 0;
@@ -413,7 +462,10 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
// When desired match not found yet, try to find it (and others).
if (use_tagstack) {
- name = tagstack[tagstackidx].tagname;
+ // make a copy, the tagstack may change in 'tagfunc'
+ name = xstrdup(tagstack[tagstackidx].tagname);
+ xfree(tofree);
+ tofree = name;
} else if (g_do_tagpreview != 0) {
name = ptag_entry.tagname;
} else {
@@ -460,6 +512,15 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
// found: all matches found.
}
+ // A tag function may do anything, which may cause various
+ // information to become invalid. At least check for the tagstack
+ // to still be the same.
+ if (tagstack != curwin->w_tagstack) {
+ emsg(_(e_window_unexpectedly_close_while_searching_for_tags));
+ FreeWild(new_num_matches, new_matches);
+ break;
+ }
+
// If there already were some matches for the same name, move them
// to the start. Avoids that the order changes when using
// ":tnext" and jumping to another file.
@@ -644,6 +705,7 @@ end_do_tag:
}
postponed_split = 0; // don't split next time
g_do_tagpreview = 0; // don't do tag preview next time
+ xfree(tofree);
}
// List all the matching tags.
@@ -1100,7 +1162,7 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl
}
}
- if (*curbuf->b_p_tfu == NUL) {
+ if (*curbuf->b_p_tfu == NUL || curbuf->b_tfu_cb.type == kCallbackNone) {
return FAIL;
}
@@ -1131,7 +1193,7 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl
flags & TAG_REGEXP ? "r": "");
save_pos = curwin->w_cursor;
- result = call_vim_function(curbuf->b_p_tfu, 3, args, &rettv);
+ result = callback_call(&curbuf->b_tfu_cb, 3, args, &rettv);
curwin->w_cursor = save_pos; // restore the cursor position
d->dv_refcount--;
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index 5e221e13df..890b04a614 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -523,6 +523,18 @@ static int terminal_check(VimState *state)
if (must_redraw) {
update_screen();
+
+ // Make sure an invoked autocmd doesn't delete the buffer (and the
+ // terminal) under our fingers.
+ curbuf->b_locked++;
+
+ // save and restore curwin and curbuf, in case the autocmd changes them
+ aco_save_T aco;
+ aucmd_prepbuf(&aco, curbuf);
+ apply_autocmds(EVENT_TEXTCHANGEDT, NULL, NULL, false, curbuf);
+ aucmd_restbuf(&aco);
+
+ curbuf->b_locked--;
}
if (need_maketitle) { // Update title in terminal-mode. #7248
diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile
index a6d1cf1003..2d2853ead7 100644
--- a/src/nvim/testdir/Makefile
+++ b/src/nvim/testdir/Makefile
@@ -13,8 +13,7 @@ export SHELL := sh
export NVIM_PRG := $(NVIM_PRG)
export TMPDIR := $(abspath Xtest-tmpdir)
-SCRIPTS_DEFAULT = \
- test42.out \
+SCRIPTS_DEFAULT =
ifneq ($(OS),Windows_NT)
SCRIPTS_DEFAULTS := $(SCRIPTS_DEFAULT) \
diff --git a/src/nvim/testdir/check.vim b/src/nvim/testdir/check.vim
index 1459b70243..8a1080a2f3 100644
--- a/src/nvim/testdir/check.vim
+++ b/src/nvim/testdir/check.vim
@@ -90,15 +90,6 @@ func CheckUnix()
endif
endfunc
-" Command to check for not running on a BSD system.
-" TODO: using this checks should not be needed
-command CheckNotBSD call CheckNotBSD()
-func CheckNotBSD()
- if has('bsd')
- throw 'Skipped: does not work on BSD'
- endif
-endfunc
-
" Command to check that making screendumps is supported.
" Caller must source screendump.vim
command CheckScreendump call CheckScreendump()
diff --git a/src/nvim/testdir/test42.in b/src/nvim/testdir/test42.in
deleted file mode 100644
index 456f9ddb07..0000000000
--- a/src/nvim/testdir/test42.in
+++ /dev/null
Binary files differ
diff --git a/src/nvim/testdir/test42.ok b/src/nvim/testdir/test42.ok
deleted file mode 100644
index 183430d71c..0000000000
--- a/src/nvim/testdir/test42.ok
+++ /dev/null
Binary files differ
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index 1488fe8431..1e9406eb87 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -481,17 +481,20 @@ endfunc
func Test_early_bar()
" test that a bar is recognized before the {event}
call s:AddAnAutocmd()
- augroup vimBarTest | au! | augroup END
+ augroup vimBarTest | au! | let done = 77 | augroup END
call assert_equal(1, len(split(execute('au vimBarTest'), "\n")))
+ call assert_equal(77, done)
call s:AddAnAutocmd()
- augroup vimBarTest| au!| augroup END
+ augroup vimBarTest| au!| let done = 88 | augroup END
call assert_equal(1, len(split(execute('au vimBarTest'), "\n")))
+ call assert_equal(88, done)
" test that a bar is recognized after the {event}
call s:AddAnAutocmd()
- augroup vimBarTest| au!BufReadCmd| augroup END
+ augroup vimBarTest| au!BufReadCmd| let done = 99 | augroup END
call assert_equal(1, len(split(execute('au vimBarTest'), "\n")))
+ call assert_equal(99, done)
" test that a bar is recognized after the {group}
call s:AddAnAutocmd()
@@ -1975,9 +1978,7 @@ func Test_change_mark_in_autocmds()
endfunc
func Test_Filter_noshelltemp()
- if !executable('cat')
- return
- endif
+ CheckExecutable cat
enew!
call setline(1, ['a', 'b', 'c', 'd'])
diff --git a/src/nvim/testdir/test_backup.vim b/src/nvim/testdir/test_backup.vim
index 7eff818732..d304773da4 100644
--- a/src/nvim/testdir/test_backup.vim
+++ b/src/nvim/testdir/test_backup.vim
@@ -19,6 +19,22 @@ func Test_backup()
call delete('Xbackup.txt~')
endfunc
+func Test_backup_backupskip()
+ set backup backupdir=. backupskip=*.txt
+ new
+ call setline(1, ['line1', 'line2'])
+ :f Xbackup.txt
+ :w! Xbackup.txt
+ " backup file is only created after
+ " writing a second time (before overwriting)
+ :w! Xbackup.txt
+ call assert_false(filereadable('Xbackup.txt~'))
+ bw!
+ set backup&vim backupdir&vim backupskip&vim
+ call delete('Xbackup.txt')
+ call delete('Xbackup.txt~')
+endfunc
+
func Test_backup2()
set backup backupdir=.// backupskip=
new
@@ -30,7 +46,7 @@ func Test_backup2()
:w! Xbackup.txt
sp *Xbackup.txt~
call assert_equal(['line1', 'line2', 'line3'], getline(1,'$'))
- let f=expand('%')
+ let f = expand('%')
call assert_match('%testdir%Xbackup.txt\~', f)
bw!
bw!
@@ -50,7 +66,7 @@ func Test_backup2_backupcopy()
:w! Xbackup.txt
sp *Xbackup.txt~
call assert_equal(['line1', 'line2', 'line3'], getline(1,'$'))
- let f=expand('%')
+ let f = expand('%')
call assert_match('%testdir%Xbackup.txt\~', f)
bw!
bw!
@@ -62,14 +78,11 @@ endfunc
" Test for using a non-existing directory as a backup directory
func Test_non_existing_backupdir()
throw 'Skipped: Nvim auto-creates backup directory'
- CheckNotBSD
- let save_backup = &backupdir
- set backupdir=./non_existing_dir
+ set backupdir=./non_existing_dir backupskip=
call writefile(['line1'], 'Xfile')
new Xfile
- " TODO: write doesn't fail in Cirrus FreeBSD CI test
call assert_fails('write', 'E510:')
- let &backupdir = save_backup
+ set backupdir&vim backupskip&vim
call delete('Xfile')
endfunc
diff --git a/src/nvim/testdir/test_buffer.vim b/src/nvim/testdir/test_buffer.vim
index d8d22ff872..3300278802 100644
--- a/src/nvim/testdir/test_buffer.vim
+++ b/src/nvim/testdir/test_buffer.vim
@@ -434,4 +434,24 @@ func Test_buf_pattern_invalid()
bwipe!
endfunc
+" Test for the 'maxmem' and 'maxmemtot' options
+func Test_buffer_maxmem()
+ " use 1KB per buffer and 2KB for all the buffers
+ " set maxmem=1 maxmemtot=2
+ new
+ let v:errmsg = ''
+ " try opening some files
+ edit test_arglist.vim
+ call assert_equal('test_arglist.vim', bufname())
+ edit test_eval_stuff.vim
+ call assert_equal('test_eval_stuff.vim', bufname())
+ b test_arglist.vim
+ call assert_equal('test_arglist.vim', bufname())
+ b test_eval_stuff.vim
+ call assert_equal('test_eval_stuff.vim', bufname())
+ close
+ call assert_equal('', v:errmsg)
+ " set maxmem& maxmemtot&
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_bufline.vim b/src/nvim/testdir/test_bufline.vim
index 2867f13cbc..fae6b1dab7 100644
--- a/src/nvim/testdir/test_bufline.vim
+++ b/src/nvim/testdir/test_bufline.vim
@@ -239,4 +239,20 @@ func Test_setbufline_startup_nofile()
call delete('Xresult')
endfunc
+" Test that setbufline(), appendbufline() and deletebufline() should fail and
+" return 1 when "textlock" is active.
+func Test_change_bufline_with_textlock()
+ new
+ inoremap <buffer> <expr> <F2> setbufline('', 1, '')
+ call assert_fails("normal a\<F2>", 'E565:')
+ call assert_equal('1', getline(1))
+ inoremap <buffer> <expr> <F2> appendbufline('', 1, '')
+ call assert_fails("normal a\<F2>", 'E565:')
+ call assert_equal('11', getline(1))
+ inoremap <buffer> <expr> <F2> deletebufline('', 1)
+ call assert_fails("normal a\<F2>", 'E565:')
+ call assert_equal('111', getline(1))
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim
index 7cef3117c2..b0be7ab396 100644
--- a/src/nvim/testdir/test_cmdline.vim
+++ b/src/nvim/testdir/test_cmdline.vim
@@ -1513,6 +1513,7 @@ func Test_cmdline_expand_special()
call assert_fails('e <afile>', 'E495:')
call assert_fails('e <abuf>', 'E496:')
call assert_fails('e <amatch>', 'E497:')
+
call writefile([], 'Xfile.cpp')
call writefile([], 'Xfile.java')
new Xfile.cpp
@@ -2006,6 +2007,26 @@ func Test_recalling_cmdline()
cunmap <Plug>(save-cmdline)
endfunc
+" Test for the 'suffixes' option
+func Test_suffixes_opt()
+ call writefile([], 'Xfile')
+ call writefile([], 'Xfile.c')
+ call writefile([], 'Xfile.o')
+ set suffixes=
+ call feedkeys(":e Xfi*\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xfile Xfile.c Xfile.o', @:)
+ set suffixes=.c
+ call feedkeys(":e Xfi*\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xfile Xfile.o Xfile.c', @:)
+ set suffixes=,,
+ call feedkeys(":e Xfi*\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xfile.c Xfile.o Xfile', @:)
+ set suffixes&
+ call delete('Xfile')
+ call delete('Xfile.c')
+ call delete('Xfile.o')
+endfunc
+
" Test for using a popup menu for the command line completion matches
" (wildoptions=pum)
func Test_wildmenu_pum()
diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim
index 3dc8710d63..ec7d143030 100644
--- a/src/nvim/testdir/test_compiler.vim
+++ b/src/nvim/testdir/test_compiler.vim
@@ -1,9 +1,11 @@
" Test the :compiler command
+source check.vim
+source shared.vim
+
func Test_compiler()
- if !executable('perl')
- return
- endif
+ CheckExecutable perl
+ CheckFeature quickfix
" $LANG changes the output of Perl.
if $LANG != ''
diff --git a/src/nvim/testdir/test_delete.vim b/src/nvim/testdir/test_delete.vim
index b23a3bd025..6b49f153c6 100644
--- a/src/nvim/testdir/test_delete.vim
+++ b/src/nvim/testdir/test_delete.vim
@@ -1,5 +1,7 @@
" Test for delete().
+source check.vim
+
func Test_file_delete()
split Xfile
call setline(1, ['a', 'b'])
@@ -41,9 +43,7 @@ func Test_recursive_delete()
endfunc
func Test_symlink_delete()
- if !has('unix')
- return
- endif
+ CheckUnix
split Xfile
call setline(1, ['a', 'b'])
wq
@@ -56,9 +56,7 @@ func Test_symlink_delete()
endfunc
func Test_symlink_dir_delete()
- if !has('unix')
- return
- endif
+ CheckUnix
call mkdir('Xdir1')
silent !ln -s Xdir1 Xlink
call assert_true(isdirectory('Xdir1'))
@@ -70,9 +68,7 @@ func Test_symlink_dir_delete()
endfunc
func Test_symlink_recursive_delete()
- if !has('unix')
- return
- endif
+ CheckUnix
call mkdir('Xdir3')
call mkdir('Xdir3/subdir')
call mkdir('Xdir4')
diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim
index 831efdbfc2..0de5310735 100644
--- a/src/nvim/testdir/test_diffmode.vim
+++ b/src/nvim/testdir/test_diffmode.vim
@@ -621,9 +621,7 @@ func Test_diff_move_to()
endfunc
func Test_diffexpr()
- if !executable('diff')
- return
- endif
+ CheckExecutable diff
func DiffExpr()
" Prepend some text to check diff type detection
diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim
index 89fd73351d..dc850515b0 100644
--- a/src/nvim/testdir/test_edit.vim
+++ b/src/nvim/testdir/test_edit.vim
@@ -1836,7 +1836,8 @@ endfunc
" Test for editing a file without read permission
func Test_edit_file_no_read_perm()
CheckUnix
- CheckNotBSD
+ CheckNotRoot
+
call writefile(['one', 'two'], 'Xfile')
call setfperm('Xfile', '-w-------')
new
diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim
index 582dcaac2c..42b1f8ca48 100644
--- a/src/nvim/testdir/test_excmd.vim
+++ b/src/nvim/testdir/test_excmd.vim
@@ -476,6 +476,7 @@ func Test_winsize_cmd()
endfunc
" Test for the :redir command
+" NOTE: if you run tests as root this will fail. Don't run tests as root!
func Test_redir_cmd()
call assert_fails('redir @@', 'E475:')
call assert_fails('redir abc', 'E475:')
@@ -492,13 +493,6 @@ func Test_redir_cmd()
call assert_fails('redir > Xdir', 'E17:')
call delete('Xdir', 'd')
endif
- if !has('bsd')
- " Redirecting to a read-only file
- call writefile([], 'Xfile')
- call setfperm('Xfile', 'r--r--r--')
- call assert_fails('redir! > Xfile', 'E190:')
- call delete('Xfile')
- endif
" Test for redirecting to a register
redir @q> | echon 'clean ' | redir END
@@ -511,6 +505,16 @@ func Test_redir_cmd()
call assert_equal('blue sky', color)
endfunc
+func Test_redir_cmd_readonly()
+ CheckNotRoot
+
+ " Redirecting to a read-only file
+ call writefile([], 'Xfile')
+ call setfperm('Xfile', 'r--r--r--')
+ call assert_fails('redir! > Xfile', 'E190:')
+ call delete('Xfile')
+endfunc
+
" Test for the :filetype command
func Test_filetype_cmd()
call assert_fails('filetype abc', 'E475:')
diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim
index ea874cc398..9dbc923b96 100644
--- a/src/nvim/testdir/test_expr.vim
+++ b/src/nvim/testdir/test_expr.vim
@@ -496,6 +496,13 @@ func Test_function_with_funcref()
call assert_fails("call function('foo()')", 'E475:')
call assert_fails("call function('foo()')", 'foo()')
call assert_fails("function('')", 'E129:')
+
+ let Len = {s -> strlen(s)}
+ call assert_equal(6, Len('foobar'))
+ let name = string(Len)
+ " can evaluate "function('<lambda>99')"
+ call execute('let Ref = ' .. name)
+ call assert_equal(4, Ref('text'))
endfunc
func Test_funcref()
diff --git a/src/nvim/testdir/test_fileformat.vim b/src/nvim/testdir/test_fileformat.vim
index 81127ea59a..8d727a68c4 100644
--- a/src/nvim/testdir/test_fileformat.vim
+++ b/src/nvim/testdir/test_fileformat.vim
@@ -31,6 +31,15 @@ func Test_fileformat_autocommand()
bw!
endfunc
+func Test_fileformat_nomodifiable()
+ new
+ setlocal nomodifiable
+
+ call assert_fails('set fileformat=latin1', 'E21:')
+
+ bw
+endfunc
+
" Convert the contents of a file into a literal string
func s:file2str(fname)
let b = readfile(a:fname, 'B')
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index c6f90604e5..69508cb19e 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -612,6 +612,7 @@ let s:filename_checks = {
\ 'verilogams': ['file.va', 'file.vams'],
\ 'vgrindefs': ['vgrindefs'],
\ 'vhdl': ['file.hdl', 'file.vhd', 'file.vhdl', 'file.vbe', 'file.vst', 'file.vhdl_123', 'file.vho', 'some.vhdl_1', 'some.vhdl_1-file'],
+ \ 'vhs': ['file.tape'],
\ 'vim': ['file.vim', 'file.vba', '.exrc', '_exrc', 'some-vimrc', 'some-vimrc-file', 'vimrc', 'vimrc-file'],
\ 'viminfo': ['.viminfo', '_viminfo'],
\ 'vmasm': ['file.mar'],
diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim
index 0a9be310ff..832dd43b65 100644
--- a/src/nvim/testdir/test_fold.vim
+++ b/src/nvim/testdir/test_fold.vim
@@ -95,9 +95,7 @@ func Test_indent_fold2()
endfunc
func Test_manual_fold_with_filter()
- if !executable('cat')
- return
- endif
+ CheckExecutable cat
for type in ['manual', 'marker']
exe 'set foldmethod=' . type
new
diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
index b464f6591d..0f2066b7c1 100644
--- a/src/nvim/testdir/test_functions.vim
+++ b/src/nvim/testdir/test_functions.vim
@@ -2454,6 +2454,7 @@ endfunc
func Test_glob()
call assert_equal('', glob(v:_null_string))
call assert_equal('', globpath(v:_null_string, v:_null_string))
+ call assert_fails("let x = globpath(&rtp, 'syntax/c.vim', [])", 'E745:')
call writefile([], 'Xglob1')
call writefile([], 'XGLOB2')
diff --git a/src/nvim/testdir/test_help.vim b/src/nvim/testdir/test_help.vim
index 19c0fcd820..e30d305703 100644
--- a/src/nvim/testdir/test_help.vim
+++ b/src/nvim/testdir/test_help.vim
@@ -1,5 +1,7 @@
" Tests for :help
+source check.vim
+
func Test_help_restore_snapshot()
help
set buftype=
@@ -98,6 +100,7 @@ func Test_help_completion()
endfunc
" Test for the :helptags command
+" NOTE: if you run tests as root this will fail. Don't run tests as root!
func Test_helptag_cmd()
call mkdir('Xdir/a/doc', 'p')
@@ -111,29 +114,6 @@ func Test_helptag_cmd()
call assert_equal(["help-tags\ttags\t1"], readfile('Xdir/tags'))
call delete('Xdir/tags')
- " The following tests fail on FreeBSD for some reason
- if has('unix') && !has('bsd')
- " Read-only tags file
- call mkdir('Xdir/doc', 'p')
- call writefile([''], 'Xdir/doc/tags')
- call writefile([], 'Xdir/doc/sample.txt')
- call setfperm('Xdir/doc/tags', 'r-xr--r--')
- call assert_fails('helptags Xdir/doc', 'E152:', getfperm('Xdir/doc/tags'))
-
- let rtp = &rtp
- let &rtp = 'Xdir'
- helptags ALL
- let &rtp = rtp
-
- call delete('Xdir/doc/tags')
-
- " No permission to read the help file
- call setfperm('Xdir/a/doc/sample.txt', '-w-------')
- call assert_fails('helptags Xdir', 'E153:', getfperm('Xdir/a/doc/sample.txt'))
- call delete('Xdir/a/doc/sample.txt')
- call delete('Xdir/tags')
- endif
-
" Duplicate tags in the help file
call writefile(['*tag1*', '*tag1*', '*tag2*'], 'Xdir/a/doc/sample.txt')
call assert_fails('helptags Xdir', 'E154:')
@@ -141,9 +121,35 @@ func Test_helptag_cmd()
call delete('Xdir', 'rf')
endfunc
+func Test_helptag_cmd_readonly()
+ CheckUnix
+ CheckNotRoot
+
+ " Read-only tags file
+ call mkdir('Xdir/doc', 'p')
+ call writefile([''], 'Xdir/doc/tags')
+ call writefile([], 'Xdir/doc/sample.txt')
+ call setfperm('Xdir/doc/tags', 'r-xr--r--')
+ call assert_fails('helptags Xdir/doc', 'E152:', getfperm('Xdir/doc/tags'))
+
+ let rtp = &rtp
+ let &rtp = 'Xdir'
+ helptags ALL
+ let &rtp = rtp
+
+ call delete('Xdir/doc/tags')
+
+ " No permission to read the help file
+ call mkdir('Xdir/b/doc', 'p')
+ call writefile([], 'Xdir/b/doc/sample.txt')
+ call setfperm('Xdir/b/doc/sample.txt', '-w-------')
+ call assert_fails('helptags Xdir', 'E153:', getfperm('Xdir/b/doc/sample.txt'))
+ call delete('Xdir', 'rf')
+endfunc
+
" Test for setting the 'helpheight' option in the help window
func Test_help_window_height()
- let &cmdheight = &lines - 24
+ let &cmdheight = &lines - 23
set helpheight=10
help
set helpheight=14
diff --git a/src/nvim/testdir/test_help_tagjump.vim b/src/nvim/testdir/test_help_tagjump.vim
index e84726bbfc..f81e4fb1ef 100644
--- a/src/nvim/testdir/test_help_tagjump.vim
+++ b/src/nvim/testdir/test_help_tagjump.vim
@@ -38,18 +38,18 @@ func Test_help_tagjump()
call assert_true(getline('.') =~ '\*quotestar\*')
helpclose
- " The test result is different in vim. There ":help ??" will jump to the
- " falsy operator ??, which hasn't been ported to neovim yet. Instead, neovim
- " jumps to the tag "g??". This test result needs to be changed if neovim
- " ports the falsy operator.
- help ??
+ " help sm?le
+ help ch?ckhealth
call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*g??\*')
+ " call assert_true(getline('.') =~ '\*:smile\*')
+ call assert_true(getline('.') =~ '\*:checkhealth\*')
helpclose
- help ch?ckhealth
+ help ??
call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*:checkhealth\*')
+ " *??* tag needs patch 8.2.1794
+ " call assert_true(getline('.') =~ '\*??\*')
+ call assert_true(getline('.') =~ '\*g??\*')
helpclose
help :?
diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim
index f706322a85..d788841833 100644
--- a/src/nvim/testdir/test_ins_complete.vim
+++ b/src/nvim/testdir/test_ins_complete.vim
@@ -1,5 +1,8 @@
+" Test for insert completion
+
source screendump.vim
source check.vim
+source vim9.vim
" Test for insert expansion
func Test_ins_complete()
@@ -1285,6 +1288,675 @@ func Test_no_mapping_for_ctrl_x_key()
bwipe!
endfunc
+" Test for different ways of setting the 'completefunc' option
+func Test_completefunc_callback()
+ func CompleteFunc1(callnr, findstart, base)
+ call add(g:CompleteFunc1Args, [a:callnr, a:findstart, a:base])
+ return a:findstart ? 0 : []
+ endfunc
+ func CompleteFunc2(findstart, base)
+ call add(g:CompleteFunc2Args, [a:findstart, a:base])
+ return a:findstart ? 0 : []
+ endfunc
+
+ let lines =<< trim END
+ #" Test for using a function name
+ LET &completefunc = 'g:CompleteFunc2'
+ new
+ call setline(1, 'zero')
+ LET g:CompleteFunc2Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'zero']], g:CompleteFunc2Args)
+ bw!
+
+ #" Test for using a function()
+ set completefunc=function('g:CompleteFunc1',\ [10])
+ new
+ call setline(1, 'one')
+ LET g:CompleteFunc1Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[10, 1, ''], [10, 0, 'one']], g:CompleteFunc1Args)
+ bw!
+
+ #" Using a funcref variable to set 'completefunc'
+ VAR Fn = function('g:CompleteFunc1', [11])
+ LET &completefunc = Fn
+ new
+ call setline(1, 'two')
+ LET g:CompleteFunc1Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[11, 1, ''], [11, 0, 'two']], g:CompleteFunc1Args)
+ bw!
+
+ #" Using string(funcref_variable) to set 'completefunc'
+ LET Fn = function('g:CompleteFunc1', [12])
+ LET &completefunc = string(Fn)
+ new
+ call setline(1, 'two')
+ LET g:CompleteFunc1Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[12, 1, ''], [12, 0, 'two']], g:CompleteFunc1Args)
+ bw!
+
+ #" Test for using a funcref()
+ set completefunc=funcref('g:CompleteFunc1',\ [13])
+ new
+ call setline(1, 'three')
+ LET g:CompleteFunc1Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[13, 1, ''], [13, 0, 'three']], g:CompleteFunc1Args)
+ bw!
+
+ #" Using a funcref variable to set 'completefunc'
+ LET Fn = funcref('g:CompleteFunc1', [14])
+ LET &completefunc = Fn
+ new
+ call setline(1, 'four')
+ LET g:CompleteFunc1Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[14, 1, ''], [14, 0, 'four']], g:CompleteFunc1Args)
+ bw!
+
+ #" Using a string(funcref_variable) to set 'completefunc'
+ LET Fn = funcref('g:CompleteFunc1', [15])
+ LET &completefunc = string(Fn)
+ new
+ call setline(1, 'four')
+ LET g:CompleteFunc1Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[15, 1, ''], [15, 0, 'four']], g:CompleteFunc1Args)
+ bw!
+
+ #" Test for using a lambda function with set
+ VAR optval = "LSTART a, b LMIDDLE CompleteFunc1(16, a, b) LEND"
+ LET optval = substitute(optval, ' ', '\\ ', 'g')
+ exe "set completefunc=" .. optval
+ new
+ call setline(1, 'five')
+ LET g:CompleteFunc1Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[16, 1, ''], [16, 0, 'five']], g:CompleteFunc1Args)
+ bw!
+
+ #" Set 'completefunc' to a lambda expression
+ LET &completefunc = LSTART a, b LMIDDLE CompleteFunc1(17, a, b) LEND
+ new
+ call setline(1, 'six')
+ LET g:CompleteFunc1Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[17, 1, ''], [17, 0, 'six']], g:CompleteFunc1Args)
+ bw!
+
+ #" Set 'completefunc' to string(lambda_expression)
+ LET &completefunc = 'LSTART a, b LMIDDLE CompleteFunc1(18, a, b) LEND'
+ new
+ call setline(1, 'six')
+ LET g:CompleteFunc1Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[18, 1, ''], [18, 0, 'six']], g:CompleteFunc1Args)
+ bw!
+
+ #" Set 'completefunc' to a variable with a lambda expression
+ VAR Lambda = LSTART a, b LMIDDLE CompleteFunc1(19, a, b) LEND
+ LET &completefunc = Lambda
+ new
+ call setline(1, 'seven')
+ LET g:CompleteFunc1Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[19, 1, ''], [19, 0, 'seven']], g:CompleteFunc1Args)
+ bw!
+
+ #" Set 'completefunc' to a string(variable with a lambda expression)
+ LET Lambda = LSTART a, b LMIDDLE CompleteFunc1(20, a, b) LEND
+ LET &completefunc = string(Lambda)
+ new
+ call setline(1, 'seven')
+ LET g:CompleteFunc1Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[20, 1, ''], [20, 0, 'seven']], g:CompleteFunc1Args)
+ bw!
+
+ #" Test for using a lambda function with incorrect return value
+ LET Lambda = LSTART a, b LMIDDLE strlen(a) LEND
+ LET &completefunc = Lambda
+ new
+ call setline(1, 'eight')
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ bw!
+
+ #" Test for clearing the 'completefunc' option
+ set completefunc=''
+ set completefunc&
+ call assert_fails("set completefunc=function('abc')", "E700:")
+ call assert_fails("set completefunc=funcref('abc')", "E700:")
+
+ #" set 'completefunc' to a non-existing function
+ set completefunc=CompleteFunc2
+ call setline(1, 'five')
+ call assert_fails("set completefunc=function('NonExistingFunc')", 'E700:')
+ call assert_fails("LET &completefunc = function('NonExistingFunc')", 'E700:')
+ LET g:CompleteFunc2Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'five']], g:CompleteFunc2Args)
+ bw!
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ let &completefunc = {a -> 'abc'}
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+
+ " Using Vim9 lambda expression in legacy context should fail
+ " set completefunc=(a,\ b)\ =>\ CompleteFunc1(21,\ a,\ b)
+ new | only
+ let g:CompleteFunc1Args = []
+ " call assert_fails('call feedkeys("A\<C-X>\<C-U>\<Esc>", "x")', 'E117:')
+ call assert_equal([], g:CompleteFunc1Args)
+
+ " set 'completefunc' to a partial with dict. This used to cause a crash.
+ func SetCompleteFunc()
+ let params = {'complete': function('g:DictCompleteFunc')}
+ let &completefunc = params.complete
+ endfunc
+ func g:DictCompleteFunc(_) dict
+ endfunc
+ call SetCompleteFunc()
+ new
+ call SetCompleteFunc()
+ bw
+ call test_garbagecollect_now()
+ new
+ set completefunc=
+ wincmd w
+ set completefunc=
+ %bw!
+ delfunc g:DictCompleteFunc
+ delfunc SetCompleteFunc
+
+ " Vim9 tests
+ let lines =<< trim END
+ vim9script
+
+ # Test for using a def function with completefunc
+ def Vim9CompleteFunc(val: number, findstart: number, base: string): any
+ add(g:Vim9completeFuncArgs, [val, findstart, base])
+ return findstart ? 0 : []
+ enddef
+ set completefunc=function('Vim9CompleteFunc',\ [60])
+ new | only
+ setline(1, 'one')
+ g:Vim9completeFuncArgs = []
+ feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ assert_equal([[60, 1, ''], [60, 0, 'one']], g:Vim9completeFuncArgs)
+ bw!
+ END
+ " call CheckScriptSuccess(lines)
+
+ " cleanup
+ set completefunc&
+ delfunc CompleteFunc1
+ delfunc CompleteFunc2
+ unlet g:CompleteFunc1Args g:CompleteFunc2Args
+ %bw!
+endfunc
+
+" Test for different ways of setting the 'omnifunc' option
+func Test_omnifunc_callback()
+ func OmniFunc1(callnr, findstart, base)
+ call add(g:OmniFunc1Args, [a:callnr, a:findstart, a:base])
+ return a:findstart ? 0 : []
+ endfunc
+ func OmniFunc2(findstart, base)
+ call add(g:OmniFunc2Args, [a:findstart, a:base])
+ return a:findstart ? 0 : []
+ endfunc
+
+ let lines =<< trim END
+ #" Test for using a function name
+ LET &omnifunc = 'g:OmniFunc2'
+ new
+ call setline(1, 'zero')
+ LET g:OmniFunc2Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'zero']], g:OmniFunc2Args)
+ bw!
+
+ #" Test for using a function()
+ set omnifunc=function('g:OmniFunc1',\ [10])
+ new
+ call setline(1, 'one')
+ LET g:OmniFunc1Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[10, 1, ''], [10, 0, 'one']], g:OmniFunc1Args)
+ bw!
+
+ #" Using a funcref variable to set 'omnifunc'
+ VAR Fn = function('g:OmniFunc1', [11])
+ LET &omnifunc = Fn
+ new
+ call setline(1, 'two')
+ LET g:OmniFunc1Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[11, 1, ''], [11, 0, 'two']], g:OmniFunc1Args)
+ bw!
+
+ #" Using a string(funcref_variable) to set 'omnifunc'
+ LET Fn = function('g:OmniFunc1', [12])
+ LET &omnifunc = string(Fn)
+ new
+ call setline(1, 'two')
+ LET g:OmniFunc1Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[12, 1, ''], [12, 0, 'two']], g:OmniFunc1Args)
+ bw!
+
+ #" Test for using a funcref()
+ set omnifunc=funcref('g:OmniFunc1',\ [13])
+ new
+ call setline(1, 'three')
+ LET g:OmniFunc1Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[13, 1, ''], [13, 0, 'three']], g:OmniFunc1Args)
+ bw!
+
+ #" Use let to set 'omnifunc' to a funcref
+ LET Fn = funcref('g:OmniFunc1', [14])
+ LET &omnifunc = Fn
+ new
+ call setline(1, 'four')
+ LET g:OmniFunc1Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[14, 1, ''], [14, 0, 'four']], g:OmniFunc1Args)
+ bw!
+
+ #" Using a string(funcref) to set 'omnifunc'
+ LET Fn = funcref("g:OmniFunc1", [15])
+ LET &omnifunc = string(Fn)
+ new
+ call setline(1, 'four')
+ LET g:OmniFunc1Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[15, 1, ''], [15, 0, 'four']], g:OmniFunc1Args)
+ bw!
+
+ #" Test for using a lambda function with set
+ VAR optval = "LSTART a, b LMIDDLE OmniFunc1(16, a, b) LEND"
+ LET optval = substitute(optval, ' ', '\\ ', 'g')
+ exe "set omnifunc=" .. optval
+ new
+ call setline(1, 'five')
+ LET g:OmniFunc1Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[16, 1, ''], [16, 0, 'five']], g:OmniFunc1Args)
+ bw!
+
+ #" Set 'omnifunc' to a lambda expression
+ LET &omnifunc = LSTART a, b LMIDDLE OmniFunc1(17, a, b) LEND
+ new
+ call setline(1, 'six')
+ LET g:OmniFunc1Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[17, 1, ''], [17, 0, 'six']], g:OmniFunc1Args)
+ bw!
+
+ #" Set 'omnifunc' to a string(lambda_expression)
+ LET &omnifunc = 'LSTART a, b LMIDDLE OmniFunc1(18, a, b) LEND'
+ new
+ call setline(1, 'six')
+ LET g:OmniFunc1Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[18, 1, ''], [18, 0, 'six']], g:OmniFunc1Args)
+ bw!
+
+ #" Set 'omnifunc' to a variable with a lambda expression
+ VAR Lambda = LSTART a, b LMIDDLE OmniFunc1(19, a, b) LEND
+ LET &omnifunc = Lambda
+ new
+ call setline(1, 'seven')
+ LET g:OmniFunc1Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[19, 1, ''], [19, 0, 'seven']], g:OmniFunc1Args)
+ bw!
+
+ #" Set 'omnifunc' to a string(variable with a lambda expression)
+ LET Lambda = LSTART a, b LMIDDLE OmniFunc1(20, a, b) LEND
+ LET &omnifunc = string(Lambda)
+ new
+ call setline(1, 'seven')
+ LET g:OmniFunc1Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[20, 1, ''], [20, 0, 'seven']], g:OmniFunc1Args)
+ bw!
+
+ #" Test for using a lambda function with incorrect return value
+ LET Lambda = LSTART a, b LMIDDLE strlen(a) LEND
+ LET &omnifunc = Lambda
+ new
+ call setline(1, 'eight')
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ bw!
+
+ #" Test for clearing the 'omnifunc' option
+ set omnifunc=''
+ set omnifunc&
+ call assert_fails("set omnifunc=function('abc')", "E700:")
+ call assert_fails("set omnifunc=funcref('abc')", "E700:")
+
+ #" set 'omnifunc' to a non-existing function
+ set omnifunc=OmniFunc2
+ call setline(1, 'nine')
+ call assert_fails("set omnifunc=function('NonExistingFunc')", 'E700:')
+ call assert_fails("LET &omnifunc = function('NonExistingFunc')", 'E700:')
+ LET g:OmniFunc2Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'nine']], g:OmniFunc2Args)
+ bw!
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ let &omnifunc = {a -> 'abc'}
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+
+ " Using Vim9 lambda expression in legacy context should fail
+ " set omnifunc=(a,\ b)\ =>\ OmniFunc1(21,\ a,\ b)
+ new | only
+ let g:OmniFunc1Args = []
+ " call assert_fails('call feedkeys("A\<C-X>\<C-O>\<Esc>", "x")', 'E117:')
+ call assert_equal([], g:OmniFunc1Args)
+
+ " set 'omnifunc' to a partial with dict. This used to cause a crash.
+ func SetOmniFunc()
+ let params = {'omni': function('g:DictOmniFunc')}
+ let &omnifunc = params.omni
+ endfunc
+ func g:DictOmniFunc(_) dict
+ endfunc
+ call SetOmniFunc()
+ new
+ call SetOmniFunc()
+ bw
+ call test_garbagecollect_now()
+ new
+ set omnifunc=
+ wincmd w
+ set omnifunc=
+ %bw!
+ delfunc g:DictOmniFunc
+ delfunc SetOmniFunc
+
+ " Vim9 tests
+ let lines =<< trim END
+ vim9script
+
+ # Test for using a def function with omnifunc
+ def Vim9omniFunc(val: number, findstart: number, base: string): any
+ add(g:Vim9omniFunc_Args, [val, findstart, base])
+ return findstart ? 0 : []
+ enddef
+ set omnifunc=function('Vim9omniFunc',\ [60])
+ new | only
+ setline(1, 'one')
+ g:Vim9omniFunc_Args = []
+ feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ assert_equal([[60, 1, ''], [60, 0, 'one']], g:Vim9omniFunc_Args)
+ bw!
+ END
+ " call CheckScriptSuccess(lines)
+
+ " cleanup
+ set omnifunc&
+ delfunc OmniFunc1
+ delfunc OmniFunc2
+ unlet g:OmniFunc1Args g:OmniFunc2Args
+ %bw!
+endfunc
+
+" Test for different ways of setting the 'thesaurusfunc' option
+func Test_thesaurusfunc_callback()
+ func TsrFunc1(callnr, findstart, base)
+ call add(g:TsrFunc1Args, [a:callnr, a:findstart, a:base])
+ return a:findstart ? 0 : []
+ endfunc
+ func TsrFunc2(findstart, base)
+ call add(g:TsrFunc2Args, [a:findstart, a:base])
+ return a:findstart ? 0 : ['sunday']
+ endfunc
+
+ let lines =<< trim END
+ #" Test for using a function name
+ LET &thesaurusfunc = 'g:TsrFunc2'
+ new
+ call setline(1, 'zero')
+ LET g:TsrFunc2Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'zero']], g:TsrFunc2Args)
+ bw!
+
+ #" Test for using a function()
+ set thesaurusfunc=function('g:TsrFunc1',\ [10])
+ new
+ call setline(1, 'one')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[10, 1, ''], [10, 0, 'one']], g:TsrFunc1Args)
+ bw!
+
+ #" Using a funcref variable to set 'thesaurusfunc'
+ VAR Fn = function('g:TsrFunc1', [11])
+ LET &thesaurusfunc = Fn
+ new
+ call setline(1, 'two')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[11, 1, ''], [11, 0, 'two']], g:TsrFunc1Args)
+ bw!
+
+ #" Using a string(funcref_variable) to set 'thesaurusfunc'
+ LET Fn = function('g:TsrFunc1', [12])
+ LET &thesaurusfunc = string(Fn)
+ new
+ call setline(1, 'two')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[12, 1, ''], [12, 0, 'two']], g:TsrFunc1Args)
+ bw!
+
+ #" Test for using a funcref()
+ set thesaurusfunc=funcref('g:TsrFunc1',\ [13])
+ new
+ call setline(1, 'three')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[13, 1, ''], [13, 0, 'three']], g:TsrFunc1Args)
+ bw!
+
+ #" Using a funcref variable to set 'thesaurusfunc'
+ LET Fn = funcref('g:TsrFunc1', [14])
+ LET &thesaurusfunc = Fn
+ new
+ call setline(1, 'four')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[14, 1, ''], [14, 0, 'four']], g:TsrFunc1Args)
+ bw!
+
+ #" Using a string(funcref_variable) to set 'thesaurusfunc'
+ LET Fn = funcref('g:TsrFunc1', [15])
+ LET &thesaurusfunc = string(Fn)
+ new
+ call setline(1, 'four')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[15, 1, ''], [15, 0, 'four']], g:TsrFunc1Args)
+ bw!
+
+ #" Test for using a lambda function
+ VAR optval = "LSTART a, b LMIDDLE TsrFunc1(16, a, b) LEND"
+ LET optval = substitute(optval, ' ', '\\ ', 'g')
+ exe "set thesaurusfunc=" .. optval
+ new
+ call setline(1, 'five')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[16, 1, ''], [16, 0, 'five']], g:TsrFunc1Args)
+ bw!
+
+ #" Test for using a lambda function with set
+ LET &thesaurusfunc = LSTART a, b LMIDDLE TsrFunc1(17, a, b) LEND
+ new
+ call setline(1, 'six')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[17, 1, ''], [17, 0, 'six']], g:TsrFunc1Args)
+ bw!
+
+ #" Set 'thesaurusfunc' to a string(lambda expression)
+ LET &thesaurusfunc = 'LSTART a, b LMIDDLE TsrFunc1(18, a, b) LEND'
+ new
+ call setline(1, 'six')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[18, 1, ''], [18, 0, 'six']], g:TsrFunc1Args)
+ bw!
+
+ #" Set 'thesaurusfunc' to a variable with a lambda expression
+ VAR Lambda = LSTART a, b LMIDDLE TsrFunc1(19, a, b) LEND
+ LET &thesaurusfunc = Lambda
+ new
+ call setline(1, 'seven')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[19, 1, ''], [19, 0, 'seven']], g:TsrFunc1Args)
+ bw!
+
+ #" Set 'thesaurusfunc' to a string(variable with a lambda expression)
+ LET Lambda = LSTART a, b LMIDDLE TsrFunc1(20, a, b) LEND
+ LET &thesaurusfunc = string(Lambda)
+ new
+ call setline(1, 'seven')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[20, 1, ''], [20, 0, 'seven']], g:TsrFunc1Args)
+ bw!
+
+ #" Test for using a lambda function with incorrect return value
+ LET Lambda = LSTART a, b LMIDDLE strlen(a) LEND
+ LET &thesaurusfunc = Lambda
+ new
+ call setline(1, 'eight')
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ bw!
+
+ #" Test for clearing the 'thesaurusfunc' option
+ set thesaurusfunc=''
+ set thesaurusfunc&
+ call assert_fails("set thesaurusfunc=function('abc')", "E700:")
+ call assert_fails("set thesaurusfunc=funcref('abc')", "E700:")
+
+ #" set 'thesaurusfunc' to a non-existing function
+ set thesaurusfunc=TsrFunc2
+ call setline(1, 'ten')
+ call assert_fails("set thesaurusfunc=function('NonExistingFunc')", 'E700:')
+ call assert_fails("LET &thesaurusfunc = function('NonExistingFunc')", 'E700:')
+ LET g:TsrFunc2Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'ten']], g:TsrFunc2Args)
+ bw!
+
+ #" Use a buffer-local value and a global value
+ set thesaurusfunc&
+ setlocal thesaurusfunc=function('g:TsrFunc1',\ [22])
+ call setline(1, 'sun')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", "x")
+ call assert_equal('sun', getline(1))
+ call assert_equal([[22, 1, ''], [22, 0, 'sun']], g:TsrFunc1Args)
+ new
+ call setline(1, 'sun')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", "x")
+ call assert_equal('sun', getline(1))
+ call assert_equal([], g:TsrFunc1Args)
+ set thesaurusfunc=function('g:TsrFunc1',\ [23])
+ wincmd w
+ call setline(1, 'sun')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", "x")
+ call assert_equal('sun', getline(1))
+ call assert_equal([[22, 1, ''], [22, 0, 'sun']], g:TsrFunc1Args)
+ :%bw!
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ let &thesaurusfunc = {a -> 'abc'}
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+
+ " Using Vim9 lambda expression in legacy context should fail
+ " set thesaurusfunc=(a,\ b)\ =>\ TsrFunc1(21,\ a,\ b)
+ new | only
+ let g:TsrFunc1Args = []
+ " call assert_fails('call feedkeys("A\<C-X>\<C-T>\<Esc>", "x")', 'E117:')
+ call assert_equal([], g:TsrFunc1Args)
+ bw!
+
+ " set 'thesaurusfunc' to a partial with dict. This used to cause a crash.
+ func SetTsrFunc()
+ let params = {'thesaurus': function('g:DictTsrFunc')}
+ let &thesaurusfunc = params.thesaurus
+ endfunc
+ func g:DictTsrFunc(_) dict
+ endfunc
+ call SetTsrFunc()
+ new
+ call SetTsrFunc()
+ bw
+ call test_garbagecollect_now()
+ new
+ set thesaurusfunc=
+ wincmd w
+ %bw!
+ delfunc SetTsrFunc
+
+ " set buffer-local 'thesaurusfunc' to a partial with dict. This used to
+ " cause a crash.
+ func SetLocalTsrFunc()
+ let params = {'thesaurus': function('g:DictTsrFunc')}
+ let &l:thesaurusfunc = params.thesaurus
+ endfunc
+ call SetLocalTsrFunc()
+ call test_garbagecollect_now()
+ call SetLocalTsrFunc()
+ set thesaurusfunc=
+ bw!
+ delfunc g:DictTsrFunc
+ delfunc SetLocalTsrFunc
+
+ " Vim9 tests
+ let lines =<< trim END
+ vim9script
+
+ # Test for using a def function with thesaurusfunc
+ def Vim9tsrFunc(val: number, findstart: number, base: string): any
+ add(g:Vim9tsrFunc_Args, [val, findstart, base])
+ return findstart ? 0 : []
+ enddef
+ set thesaurusfunc=function('Vim9tsrFunc',\ [60])
+ new | only
+ setline(1, 'one')
+ g:Vim9tsrFunc_Args = []
+ feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ assert_equal([[60, 1, ''], [60, 0, 'one']], g:Vim9tsrFunc_Args)
+ bw!
+ END
+ " call CheckScriptSuccess(lines)
+
+ " cleanup
+ set thesaurusfunc&
+ delfunc TsrFunc1
+ delfunc TsrFunc2
+ unlet g:TsrFunc1Args g:TsrFunc2Args
+ %bw!
+endfunc
+
func FooBarComplete(findstart, base)
if a:findstart
return col('.') - 1
diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim
index 84929e2be3..0d75644920 100644
--- a/src/nvim/testdir/test_normal.vim
+++ b/src/nvim/testdir/test_normal.vim
@@ -3,6 +3,7 @@
source shared.vim
source check.vim
source view_util.vim
+source vim9.vim
source screendump.vim
func Setup_NewWindow()
@@ -388,70 +389,6 @@ func Test_normal09a_operatorfunc()
norm V10j,,
call assert_equal(22, g:a)
- " Use a lambda function for 'opfunc'
- unmap <buffer> ,,
- call cursor(1, 1)
- let g:a=0
- nmap <buffer><silent> ,, :set opfunc={type\ ->\ CountSpaces(type)}<CR>g@
- vmap <buffer><silent> ,, :<C-U>call CountSpaces(visualmode(), 1)<CR>
- 50
- norm V2j,,
- call assert_equal(6, g:a)
- norm V,,
- call assert_equal(2, g:a)
- norm ,,l
- call assert_equal(0, g:a)
- 50
- exe "norm 0\<c-v>10j2l,,"
- call assert_equal(11, g:a)
- 50
- norm V10j,,
- call assert_equal(22, g:a)
-
- " use a partial function for 'opfunc'
- let g:OpVal = 0
- func! Test_opfunc1(x, y, type)
- let g:OpVal = a:x + a:y
- endfunc
- set opfunc=function('Test_opfunc1',\ [5,\ 7])
- normal! g@l
- call assert_equal(12, g:OpVal)
- " delete the function and try to use g@
- delfunc Test_opfunc1
- call test_garbagecollect_now()
- call assert_fails('normal! g@l', 'E117:')
- set opfunc=
-
- " use a funcref for 'opfunc'
- let g:OpVal = 0
- func! Test_opfunc2(x, y, type)
- let g:OpVal = a:x + a:y
- endfunc
- set opfunc=funcref('Test_opfunc2',\ [4,\ 3])
- normal! g@l
- call assert_equal(7, g:OpVal)
- " delete the function and try to use g@
- delfunc Test_opfunc2
- call test_garbagecollect_now()
- call assert_fails('normal! g@l', 'E933:')
- set opfunc=
-
- " Try to use a function with two arguments for 'operatorfunc'
- let g:OpVal = 0
- func! Test_opfunc3(x, y)
- let g:OpVal = 4
- endfunc
- set opfunc=Test_opfunc3
- call assert_fails('normal! g@l', 'E119:')
- call assert_equal(0, g:OpVal)
- set opfunc=
- delfunc Test_opfunc3
- unlet g:OpVal
-
- " Try to use a lambda function with two arguments for 'operatorfunc'
- set opfunc={x,\ y\ ->\ 'done'}
- call assert_fails('normal! g@l', 'E119:')
-
" clean up
unmap <buffer> ,,
set opfunc=
@@ -520,6 +457,182 @@ func Test_normal09c_operatorfunc()
set operatorfunc=
endfunc
+" Test for different ways of setting the 'operatorfunc' option
+func Test_opfunc_callback()
+ new
+ func OpFunc1(callnr, type)
+ let g:OpFunc1Args = [a:callnr, a:type]
+ endfunc
+ func OpFunc2(type)
+ let g:OpFunc2Args = [a:type]
+ endfunc
+
+ let lines =<< trim END
+ #" Test for using a function name
+ LET &opfunc = 'g:OpFunc2'
+ LET g:OpFunc2Args = []
+ normal! g@l
+ call assert_equal(['char'], g:OpFunc2Args)
+
+ #" Test for using a function()
+ set opfunc=function('g:OpFunc1',\ [10])
+ LET g:OpFunc1Args = []
+ normal! g@l
+ call assert_equal([10, 'char'], g:OpFunc1Args)
+
+ #" Using a funcref variable to set 'operatorfunc'
+ VAR Fn = function('g:OpFunc1', [11])
+ LET &opfunc = Fn
+ LET g:OpFunc1Args = []
+ normal! g@l
+ call assert_equal([11, 'char'], g:OpFunc1Args)
+
+ #" Using a string(funcref_variable) to set 'operatorfunc'
+ LET Fn = function('g:OpFunc1', [12])
+ LET &operatorfunc = string(Fn)
+ LET g:OpFunc1Args = []
+ normal! g@l
+ call assert_equal([12, 'char'], g:OpFunc1Args)
+
+ #" Test for using a funcref()
+ set operatorfunc=funcref('g:OpFunc1',\ [13])
+ LET g:OpFunc1Args = []
+ normal! g@l
+ call assert_equal([13, 'char'], g:OpFunc1Args)
+
+ #" Using a funcref variable to set 'operatorfunc'
+ LET Fn = funcref('g:OpFunc1', [14])
+ LET &opfunc = Fn
+ LET g:OpFunc1Args = []
+ normal! g@l
+ call assert_equal([14, 'char'], g:OpFunc1Args)
+
+ #" Using a string(funcref_variable) to set 'operatorfunc'
+ LET Fn = funcref('g:OpFunc1', [15])
+ LET &opfunc = string(Fn)
+ LET g:OpFunc1Args = []
+ normal! g@l
+ call assert_equal([15, 'char'], g:OpFunc1Args)
+
+ #" Test for using a lambda function using set
+ VAR optval = "LSTART a LMIDDLE OpFunc1(16, a) LEND"
+ LET optval = substitute(optval, ' ', '\\ ', 'g')
+ exe "set opfunc=" .. optval
+ LET g:OpFunc1Args = []
+ normal! g@l
+ call assert_equal([16, 'char'], g:OpFunc1Args)
+
+ #" Test for using a lambda function using LET
+ LET &opfunc = LSTART a LMIDDLE OpFunc1(17, a) LEND
+ LET g:OpFunc1Args = []
+ normal! g@l
+ call assert_equal([17, 'char'], g:OpFunc1Args)
+
+ #" Set 'operatorfunc' to a string(lambda expression)
+ LET &opfunc = 'LSTART a LMIDDLE OpFunc1(18, a) LEND'
+ LET g:OpFunc1Args = []
+ normal! g@l
+ call assert_equal([18, 'char'], g:OpFunc1Args)
+
+ #" Set 'operatorfunc' to a variable with a lambda expression
+ VAR Lambda = LSTART a LMIDDLE OpFunc1(19, a) LEND
+ LET &opfunc = Lambda
+ LET g:OpFunc1Args = []
+ normal! g@l
+ call assert_equal([19, 'char'], g:OpFunc1Args)
+
+ #" Set 'operatorfunc' to a string(variable with a lambda expression)
+ LET Lambda = LSTART a LMIDDLE OpFunc1(20, a) LEND
+ LET &opfunc = string(Lambda)
+ LET g:OpFunc1Args = []
+ normal! g@l
+ call assert_equal([20, 'char'], g:OpFunc1Args)
+
+ #" Try to use 'operatorfunc' after the function is deleted
+ func g:TmpOpFunc1(type)
+ let g:TmpOpFunc1Args = [21, a:type]
+ endfunc
+ LET &opfunc = function('g:TmpOpFunc1')
+ delfunc g:TmpOpFunc1
+ call test_garbagecollect_now()
+ LET g:TmpOpFunc1Args = []
+ call assert_fails('normal! g@l', 'E117:')
+ call assert_equal([], g:TmpOpFunc1Args)
+
+ #" Try to use a function with two arguments for 'operatorfunc'
+ func g:TmpOpFunc2(x, y)
+ let g:TmpOpFunc2Args = [a:x, a:y]
+ endfunc
+ set opfunc=TmpOpFunc2
+ LET g:TmpOpFunc2Args = []
+ call assert_fails('normal! g@l', 'E119:')
+ call assert_equal([], g:TmpOpFunc2Args)
+ delfunc TmpOpFunc2
+
+ #" Try to use a lambda function with two arguments for 'operatorfunc'
+ LET &opfunc = LSTART a, b LMIDDLE OpFunc1(22, b) LEND
+ LET g:OpFunc1Args = []
+ call assert_fails('normal! g@l', 'E119:')
+ call assert_equal([], g:OpFunc1Args)
+
+ #" Test for clearing the 'operatorfunc' option
+ set opfunc=''
+ set opfunc&
+ call assert_fails("set opfunc=function('abc')", "E700:")
+ call assert_fails("set opfunc=funcref('abc')", "E700:")
+
+ #" set 'operatorfunc' to a non-existing function
+ LET &opfunc = function('g:OpFunc1', [23])
+ call assert_fails("set opfunc=function('NonExistingFunc')", 'E700:')
+ call assert_fails("LET &opfunc = function('NonExistingFunc')", 'E700:')
+ LET g:OpFunc1Args = []
+ normal! g@l
+ call assert_equal([23, 'char'], g:OpFunc1Args)
+ END
+ call CheckTransLegacySuccess(lines)
+
+ " Using Vim9 lambda expression in legacy context should fail
+ " set opfunc=(a)\ =>\ OpFunc1(24,\ a)
+ let g:OpFunc1Args = []
+ " call assert_fails('normal! g@l', 'E117:')
+ call assert_equal([], g:OpFunc1Args)
+
+ " set 'operatorfunc' to a partial with dict. This used to cause a crash.
+ func SetOpFunc()
+ let operator = {'execute': function('OperatorExecute')}
+ let &opfunc = operator.execute
+ endfunc
+ func OperatorExecute(_) dict
+ endfunc
+ call SetOpFunc()
+ call test_garbagecollect_now()
+ set operatorfunc=
+ delfunc SetOpFunc
+ delfunc OperatorExecute
+
+ " Vim9 tests
+ let lines =<< trim END
+ vim9script
+
+ # Test for using a def function with opfunc
+ def g:Vim9opFunc(val: number, type: string): void
+ g:OpFunc1Args = [val, type]
+ enddef
+ set opfunc=function('g:Vim9opFunc',\ [60])
+ g:OpFunc1Args = []
+ normal! g@l
+ assert_equal([60, 'char'], g:OpFunc1Args)
+ END
+ " call CheckScriptSuccess(lines)
+
+ " cleanup
+ set opfunc&
+ delfunc OpFunc1
+ delfunc OpFunc2
+ unlet g:OpFunc1Args g:OpFunc2Args
+ %bw!
+endfunc
+
func Test_normal10_expand()
" Test for expand()
10new
@@ -3399,6 +3512,13 @@ func Test_normal_delete_cmd()
" delete to a readonly register
call setline(1, ['abcd'])
call assert_beeps('normal ":d2l')
+
+ " D and d with 'nomodifiable'
+ call setline(1, ['abcd'])
+ setlocal nomodifiable
+ call assert_fails('normal D', 'E21:')
+ call assert_fails('normal d$', 'E21:')
+
close!
endfunc
diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index 9d9fc5e77b..42bdb7bad1 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -1,6 +1,7 @@
" Test for the quickfix feature.
source check.vim
+source vim9.vim
CheckFeature quickfix
source screendump.vim
@@ -5690,6 +5691,138 @@ func Test_qftextfunc()
call Xtest_qftextfunc('l')
endfunc
+func Test_qftextfunc_callback()
+ let lines =<< trim END
+ set efm=%f:%l:%c:%m
+
+ #" Test for using a function name
+ LET &qftf = 'g:Tqfexpr'
+ cexpr "F0:0:0:L0"
+ copen
+ call assert_equal('F0-L0C0-L0', getline(1))
+ cclose
+
+ #" Test for using a function()
+ set qftf=function('g:Tqfexpr')
+ cexpr "F1:1:1:L1"
+ copen
+ call assert_equal('F1-L1C1-L1', getline(1))
+ cclose
+
+ #" Using a funcref variable to set 'quickfixtextfunc'
+ VAR Fn = function('g:Tqfexpr')
+ LET &qftf = Fn
+ cexpr "F2:2:2:L2"
+ copen
+ call assert_equal('F2-L2C2-L2', getline(1))
+ cclose
+
+ #" Using string(funcref_variable) to set 'quickfixtextfunc'
+ LET Fn = function('g:Tqfexpr')
+ LET &qftf = string(Fn)
+ cexpr "F3:3:3:L3"
+ copen
+ call assert_equal('F3-L3C3-L3', getline(1))
+ cclose
+
+ #" Test for using a funcref()
+ set qftf=funcref('g:Tqfexpr')
+ cexpr "F4:4:4:L4"
+ copen
+ call assert_equal('F4-L4C4-L4', getline(1))
+ cclose
+
+ #" Using a funcref variable to set 'quickfixtextfunc'
+ LET Fn = funcref('g:Tqfexpr')
+ LET &qftf = Fn
+ cexpr "F5:5:5:L5"
+ copen
+ call assert_equal('F5-L5C5-L5', getline(1))
+ cclose
+
+ #" Using a string(funcref_variable) to set 'quickfixtextfunc'
+ LET Fn = funcref('g:Tqfexpr')
+ LET &qftf = string(Fn)
+ cexpr "F5:5:5:L5"
+ copen
+ call assert_equal('F5-L5C5-L5', getline(1))
+ cclose
+
+ #" Test for using a lambda function with set
+ VAR optval = "LSTART a LMIDDLE Tqfexpr(a) LEND"
+ LET optval = substitute(optval, ' ', '\\ ', 'g')
+ exe "set qftf=" .. optval
+ cexpr "F6:6:6:L6"
+ copen
+ call assert_equal('F6-L6C6-L6', getline(1))
+ cclose
+
+ #" Set 'quickfixtextfunc' to a lambda expression
+ LET &qftf = LSTART a LMIDDLE Tqfexpr(a) LEND
+ cexpr "F7:7:7:L7"
+ copen
+ call assert_equal('F7-L7C7-L7', getline(1))
+ cclose
+
+ #" Set 'quickfixtextfunc' to string(lambda_expression)
+ LET &qftf = "LSTART a LMIDDLE Tqfexpr(a) LEND"
+ cexpr "F8:8:8:L8"
+ copen
+ call assert_equal('F8-L8C8-L8', getline(1))
+ cclose
+
+ #" Set 'quickfixtextfunc' to a variable with a lambda expression
+ VAR Lambda = LSTART a LMIDDLE Tqfexpr(a) LEND
+ LET &qftf = Lambda
+ cexpr "F9:9:9:L9"
+ copen
+ call assert_equal('F9-L9C9-L9', getline(1))
+ cclose
+
+ #" Set 'quickfixtextfunc' to a string(variable with a lambda expression)
+ LET Lambda = LSTART a LMIDDLE Tqfexpr(a) LEND
+ LET &qftf = string(Lambda)
+ cexpr "F9:9:9:L9"
+ copen
+ call assert_equal('F9-L9C9-L9', getline(1))
+ cclose
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ " set 'quickfixtextfunc' to a partial with dict. This used to cause a crash.
+ func SetQftfFunc()
+ let params = {'qftf': function('g:DictQftfFunc')}
+ let &quickfixtextfunc = params.qftf
+ endfunc
+ func g:DictQftfFunc(_) dict
+ endfunc
+ call SetQftfFunc()
+ new
+ call SetQftfFunc()
+ bw
+ call test_garbagecollect_now()
+ new
+ set qftf=
+ wincmd w
+ set qftf=
+ :%bw!
+
+ " set per-quickfix list 'quickfixtextfunc' to a partial with dict. This used
+ " to cause a crash.
+ let &qftf = ''
+ func SetLocalQftfFunc()
+ let params = {'qftf': function('g:DictQftfFunc')}
+ call setqflist([], 'a', {'quickfixtextfunc' : params.qftf})
+ endfunc
+ call SetLocalQftfFunc()
+ call test_garbagecollect_now()
+ call setqflist([], 'a', {'quickfixtextfunc' : ''})
+ delfunc g:DictQftfFunc
+ delfunc SetQftfFunc
+ delfunc SetLocalQftfFunc
+ set efm&
+endfunc
+
" Test for updating a location list for some other window and check that
" 'qftextfunc' uses the correct location list.
func Test_qftextfunc_other_loclist()
diff --git a/src/nvim/testdir/test_sleep.vim b/src/nvim/testdir/test_sleep.vim
index f71855fd4b..a428f380b0 100644
--- a/src/nvim/testdir/test_sleep.vim
+++ b/src/nvim/testdir/test_sleep.vim
@@ -21,6 +21,7 @@ func! Test_sleep_bang()
call s:assert_takes_longer('sl 50m', 50)
call s:assert_takes_longer('sl! 50m', 50)
call s:assert_takes_longer('1sleep', 1000)
+ call s:assert_takes_longer('normal 1gs', 1000)
endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim
index 30bbd355df..f9f7c5b492 100644
--- a/src/nvim/testdir/test_startup.vim
+++ b/src/nvim/testdir/test_startup.vim
@@ -1279,4 +1279,19 @@ func Test_progname()
call delete('Xprogname', 'd')
endfunc
+" Test for doing a write from .vimrc
+func Test_write_in_vimrc()
+ call writefile(['silent! write'], 'Xvimrc')
+ let after =<< trim [CODE]
+ call assert_match('E32: ', v:errmsg)
+ call writefile(v:errors, 'Xtestout')
+ qall
+ [CODE]
+ if RunVim([], after, '-u Xvimrc')
+ call assert_equal([], readfile('Xtestout'))
+ call delete('Xtestout')
+ endif
+ call delete('Xvimrc')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_tagfunc.vim b/src/nvim/testdir/test_tagfunc.vim
index bdf5afa5b2..7a88723bc0 100644
--- a/src/nvim/testdir/test_tagfunc.vim
+++ b/src/nvim/testdir/test_tagfunc.vim
@@ -1,5 +1,7 @@
" Test 'tagfunc'
+source vim9.vim
+
func TagFunc(pat, flag, info)
let g:tagfunc_args = [a:pat, a:flag, a:info]
let tags = []
@@ -117,4 +119,252 @@ func Test_tagfunc_settagstack()
delfunc Mytagfunc2
endfunc
+" Script local tagfunc callback function
+func s:ScriptLocalTagFunc(pat, flags, info)
+ let g:ScriptLocalFuncArgs = [a:pat, a:flags, a:info]
+ return v:null
+endfunc
+
+" Test for different ways of setting the 'tagfunc' option
+func Test_tagfunc_callback()
+ func TagFunc1(callnr, pat, flags, info)
+ let g:TagFunc1Args = [a:callnr, a:pat, a:flags, a:info]
+ return v:null
+ endfunc
+ func TagFunc2(pat, flags, info)
+ let g:TagFunc2Args = [a:pat, a:flags, a:info]
+ return v:null
+ endfunc
+
+ let lines =<< trim END
+ #" Test for using a function name
+ LET &tagfunc = 'g:TagFunc2'
+ new
+ LET g:TagFunc2Args = []
+ call assert_fails('tag a10', 'E433:')
+ call assert_equal(['a10', '', {}], g:TagFunc2Args)
+ bw!
+
+ #" Test for using a function()
+ set tagfunc=function('g:TagFunc1',\ [10])
+ new
+ LET g:TagFunc1Args = []
+ call assert_fails('tag a11', 'E433:')
+ call assert_equal([10, 'a11', '', {}], g:TagFunc1Args)
+ bw!
+
+ #" Using a funcref variable to set 'tagfunc'
+ VAR Fn = function('g:TagFunc1', [11])
+ LET &tagfunc = Fn
+ new
+ LET g:TagFunc1Args = []
+ call assert_fails('tag a12', 'E433:')
+ call assert_equal([11, 'a12', '', {}], g:TagFunc1Args)
+ bw!
+
+ #" Using a string(funcref_variable) to set 'tagfunc'
+ LET Fn = function('g:TagFunc1', [12])
+ LET &tagfunc = string(Fn)
+ new
+ LET g:TagFunc1Args = []
+ call assert_fails('tag a12', 'E433:')
+ call assert_equal([12, 'a12', '', {}], g:TagFunc1Args)
+ bw!
+
+ #" Test for using a funcref()
+ set tagfunc=funcref('g:TagFunc1',\ [13])
+ new
+ LET g:TagFunc1Args = []
+ call assert_fails('tag a13', 'E433:')
+ call assert_equal([13, 'a13', '', {}], g:TagFunc1Args)
+ bw!
+
+ #" Using a funcref variable to set 'tagfunc'
+ LET Fn = funcref('g:TagFunc1', [14])
+ LET &tagfunc = Fn
+ new
+ LET g:TagFunc1Args = []
+ call assert_fails('tag a14', 'E433:')
+ call assert_equal([14, 'a14', '', {}], g:TagFunc1Args)
+ bw!
+
+ #" Using a string(funcref_variable) to set 'tagfunc'
+ LET Fn = funcref('g:TagFunc1', [15])
+ LET &tagfunc = string(Fn)
+ new
+ LET g:TagFunc1Args = []
+ call assert_fails('tag a14', 'E433:')
+ call assert_equal([15, 'a14', '', {}], g:TagFunc1Args)
+ bw!
+
+ #" Test for using a lambda function
+ VAR optval = "LSTART a, b, c LMIDDLE TagFunc1(16, a, b, c) LEND"
+ LET optval = substitute(optval, ' ', '\\ ', 'g')
+ exe "set tagfunc=" .. optval
+ new
+ LET g:TagFunc1Args = []
+ call assert_fails('tag a17', 'E433:')
+ call assert_equal([16, 'a17', '', {}], g:TagFunc1Args)
+ bw!
+
+ #" Set 'tagfunc' to a lambda expression
+ LET &tagfunc = LSTART a, b, c LMIDDLE TagFunc1(17, a, b, c) LEND
+ new
+ LET g:TagFunc1Args = []
+ call assert_fails('tag a18', 'E433:')
+ call assert_equal([17, 'a18', '', {}], g:TagFunc1Args)
+ bw!
+
+ #" Set 'tagfunc' to a string(lambda expression)
+ LET &tagfunc = 'LSTART a, b, c LMIDDLE TagFunc1(18, a, b, c) LEND'
+ new
+ LET g:TagFunc1Args = []
+ call assert_fails('tag a18', 'E433:')
+ call assert_equal([18, 'a18', '', {}], g:TagFunc1Args)
+ bw!
+
+ #" Set 'tagfunc' to a variable with a lambda expression
+ VAR Lambda = LSTART a, b, c LMIDDLE TagFunc1(19, a, b, c) LEND
+ LET &tagfunc = Lambda
+ new
+ LET g:TagFunc1Args = []
+ call assert_fails("tag a19", "E433:")
+ call assert_equal([19, 'a19', '', {}], g:TagFunc1Args)
+ bw!
+
+ #" Set 'tagfunc' to a string(variable with a lambda expression)
+ LET Lambda = LSTART a, b, c LMIDDLE TagFunc1(20, a, b, c) LEND
+ LET &tagfunc = string(Lambda)
+ new
+ LET g:TagFunc1Args = []
+ call assert_fails("tag a19", "E433:")
+ call assert_equal([20, 'a19', '', {}], g:TagFunc1Args)
+ bw!
+
+ #" Test for using a lambda function with incorrect return value
+ LET Lambda = LSTART a, b, c LMIDDLE strlen(a) LEND
+ LET &tagfunc = string(Lambda)
+ new
+ call assert_fails("tag a20", "E987:")
+ bw!
+
+ #" Test for clearing the 'tagfunc' option
+ set tagfunc=''
+ set tagfunc&
+ call assert_fails("set tagfunc=function('abc')", "E700:")
+ call assert_fails("set tagfunc=funcref('abc')", "E700:")
+
+ #" set 'tagfunc' to a non-existing function
+ LET &tagfunc = function('g:TagFunc2', [21])
+ LET g:TagFunc2Args = []
+ call assert_fails("set tagfunc=function('NonExistingFunc')", 'E700:')
+ call assert_fails("LET &tagfunc = function('NonExistingFunc')", 'E700:')
+ call assert_fails("tag axb123", 'E426:')
+ call assert_equal([], g:TagFunc2Args)
+ bw!
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ let &tagfunc = "{a -> 'abc'}"
+ call assert_fails("echo taglist('a')", "E987:")
+
+ " Using Vim9 lambda expression in legacy context should fail
+ " set tagfunc=(a,\ b,\ c)\ =>\ g:TagFunc1(21,\ a,\ b,\ c)
+ new | only
+ let g:TagFunc1Args = []
+ " call assert_fails("tag a17", "E117:")
+ call assert_equal([], g:TagFunc1Args)
+
+ " Test for using a script local function
+ set tagfunc=<SID>ScriptLocalTagFunc
+ new | only
+ let g:ScriptLocalFuncArgs = []
+ call assert_fails('tag a15', 'E433:')
+ call assert_equal(['a15', '', {}], g:ScriptLocalFuncArgs)
+
+ " Test for using a script local funcref variable
+ let Fn = function("s:ScriptLocalTagFunc")
+ let &tagfunc= Fn
+ new | only
+ let g:ScriptLocalFuncArgs = []
+ call assert_fails('tag a16', 'E433:')
+ call assert_equal(['a16', '', {}], g:ScriptLocalFuncArgs)
+
+ " Test for using a string(script local funcref variable)
+ let Fn = function("s:ScriptLocalTagFunc")
+ let &tagfunc= string(Fn)
+ new | only
+ let g:ScriptLocalFuncArgs = []
+ call assert_fails('tag a16', 'E433:')
+ call assert_equal(['a16', '', {}], g:ScriptLocalFuncArgs)
+
+ " set 'tagfunc' to a partial with dict. This used to cause a crash.
+ func SetTagFunc()
+ let params = {'tagfn': function('g:DictTagFunc')}
+ let &tagfunc = params.tagfn
+ endfunc
+ func g:DictTagFunc(_) dict
+ endfunc
+ call SetTagFunc()
+ new
+ call SetTagFunc()
+ bw
+ call test_garbagecollect_now()
+ new
+ set tagfunc=
+ wincmd w
+ set tagfunc=
+ :%bw!
+ delfunc g:DictTagFunc
+ delfunc SetTagFunc
+
+ " Vim9 tests
+ let lines =<< trim END
+ vim9script
+
+ # Test for using function()
+ def Vim9tagFunc(val: number, pat: string, flags: string, info: dict<any>): any
+ g:Vim9tagFuncArgs = [val, pat, flags, info]
+ return null
+ enddef
+ set tagfunc=function('Vim9tagFunc',\ [60])
+ new | only
+ g:Vim9tagFuncArgs = []
+ assert_fails('tag a10', 'E433:')
+ assert_equal([60, 'a10', '', {}], g:Vim9tagFuncArgs)
+ END
+ " call CheckScriptSuccess(lines)
+
+ " cleanup
+ delfunc TagFunc1
+ delfunc TagFunc2
+ set tagfunc&
+ %bw!
+endfunc
+
+func Test_tagfunc_wipes_buffer()
+ func g:Tag0unc0(t,f,o)
+ bwipe
+ endfunc
+ set tagfunc=g:Tag0unc0
+ new
+ cal assert_fails('tag 0', 'E987:')
+
+ delfunc g:Tag0unc0
+ set tagfunc=
+endfunc
+
+func Test_tagfunc_closes_window()
+ split any
+ func MytagfuncClose(pat, flags, info)
+ close
+ return [{'name' : 'mytag', 'filename' : 'Xtest', 'cmd' : '1'}]
+ endfunc
+ set tagfunc=MytagfuncClose
+ call assert_fails('tag xyz', 'E1299:')
+
+ set tagfunc=
+endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim
index 3bce2eb24d..7a13de12f4 100644
--- a/src/nvim/testdir/test_textformat.vim
+++ b/src/nvim/testdir/test_textformat.vim
@@ -1044,6 +1044,22 @@ func Test_empty_matchpairs()
bwipe!
endfunc
+func Test_mps_error()
+ let encoding_save = &encoding
+
+ " for e in ['utf-8', 'latin1']
+ for e in ['utf-8']
+ exe 'set encoding=' .. e
+
+ call assert_fails('set mps=<:', 'E474:', e)
+ call assert_fails('set mps=:>', 'E474:', e)
+ call assert_fails('set mps=<>', 'E474:', e)
+ call assert_fails('set mps=<:>_', 'E474:', e)
+ endfor
+
+ let &encoding = encoding_save
+endfunc
+
" Test for ra on multi-byte characters
func Test_ra_multibyte()
new
diff --git a/src/nvim/testdir/test_viminfo.vim b/src/nvim/testdir/test_viminfo.vim
index 2d6d598011..e792db90ab 100644
--- a/src/nvim/testdir/test_viminfo.vim
+++ b/src/nvim/testdir/test_viminfo.vim
@@ -18,4 +18,9 @@ func Test_viminfo_option_error()
call assert_fails('set viminfo=%10', 'E528:')
endfunc
+func Test_viminfo_oldfiles_newfile()
+ let v:oldfiles = v:_null_list
+ call assert_equal("\nNo old files", execute('oldfiles'))
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim
index 8fb4c8fa4c..81615b5963 100644
--- a/src/nvim/testdir/test_writefile.vim
+++ b/src/nvim/testdir/test_writefile.vim
@@ -57,7 +57,29 @@ func Test_writefile_fails_conversion()
call assert_fails('write ++enc=cp932', 'E513:')
call assert_equal(contents, readfile('Xfile'))
+ " With 'backupcopy' set, if there is a conversion error, the backup file is
+ " still created.
+ set backupcopy=yes writebackup& backup&
+ call delete('Xfile' .. &backupext)
+ call assert_fails('write ++enc=cp932', 'E513:')
+ call assert_equal(contents, readfile('Xfile'))
+ call assert_equal(contents, readfile('Xfile' .. &backupext))
+ set backupcopy&
+ %bw!
+
+ " Conversion error during write
+ new
+ call setline(1, ["\U10000000"])
+ let output = execute('write! ++enc=utf-16 Xfile')
+ call assert_match('CONVERSION ERROR', output)
+ let output = execute('write! ++enc=ucs-2 Xfile')
+ call assert_match('CONVERSION ERROR', output)
+ call delete('Xfilz~')
+ call delete('Xfily~')
+ %bw!
+
call delete('Xfile')
+ call delete('Xfile' .. &backupext)
bwipe!
set backup& writebackup& backupdir&vim backupskip&vim
endfunc
@@ -136,9 +158,7 @@ func Test_writefile_sync_arg()
endfunc
func Test_writefile_sync_dev_stdout()
- if !has('unix')
- return
- endif
+ CheckUnix
if filewritable('/dev/stdout')
" Just check that this doesn't cause an error.
call writefile(['one'], '/dev/stdout', 's')
@@ -272,6 +292,16 @@ func Test_write_errors()
let long_fname = repeat('n', 5000)
call assert_fails('exe "w " .. long_fname', 'E75:')
call assert_fails('call writefile([], long_fname)', 'E482:')
+
+ " Test for writing to a block device on Unix-like systems
+ if has('unix') && getfperm('/dev/loop0') != ''
+ \ && getftype('/dev/loop0') == 'bdev' && !IsRoot()
+ new
+ edit /dev/loop0
+ call assert_fails('write', 'E503: ')
+ call assert_fails('write!', 'E503: ')
+ close!
+ endif
endfunc
" Test for writing to a file which is modified after Vim read it
@@ -386,13 +416,10 @@ endfunc
" Test for writing to a readonly file
func Test_write_readonly()
- " In Cirrus-CI, the freebsd tests are run under a root account. So this test
- " doesn't fail.
- CheckNotBSD
call writefile([], 'Xfile')
call setfperm('Xfile', "r--------")
edit Xfile
- set noreadonly
+ set noreadonly backupskip=
call assert_fails('write', 'E505:')
let save_cpo = &cpo
set cpo+=W
@@ -401,38 +428,63 @@ func Test_write_readonly()
call setline(1, ['line1'])
write!
call assert_equal(['line1'], readfile('Xfile'))
+
+ " Auto-saving a readonly file should fail with 'autowriteall'
+ %bw!
+ e Xfile
+ set noreadonly autowriteall
+ call setline(1, ['aaaa'])
+ call assert_fails('n', 'E505:')
+ set cpo+=W
+ call assert_fails('n', 'E504:')
+ set cpo-=W
+ set autowriteall&
+
+ set backupskip&
call delete('Xfile')
+ %bw!
endfunc
" Test for 'patchmode'
func Test_patchmode()
- CheckNotBSD
call writefile(['one'], 'Xfile')
- set patchmode=.orig nobackup writebackup
+ set patchmode=.orig nobackup backupskip= writebackup
new Xfile
call setline(1, 'two')
" first write should create the .orig file
write
- " TODO: Xfile.orig is not created in Cirrus FreeBSD CI test
call assert_equal(['one'], readfile('Xfile.orig'))
call setline(1, 'three')
" subsequent writes should not create/modify the .orig file
write
call assert_equal(['one'], readfile('Xfile.orig'))
- set patchmode& backup& writebackup&
+
+ " use 'patchmode' with 'nobackup' and 'nowritebackup' to create an empty
+ " original file
+ call delete('Xfile')
+ call delete('Xfile.orig')
+ %bw!
+ set patchmode=.orig nobackup nowritebackup
+ edit Xfile
+ call setline(1, ['xxx'])
+ write
+ call assert_equal(['xxx'], readfile('Xfile'))
+ call assert_equal([], readfile('Xfile.orig'))
+
+ set patchmode& backup& backupskip& writebackup&
call delete('Xfile')
call delete('Xfile.orig')
endfunc
" Test for writing to a file in a readonly directory
+" NOTE: if you run tests as root this will fail. Don't run tests as root!
func Test_write_readonly_dir()
- if !has('unix') || has('bsd')
- " On MS-Windows, modifying files in a read-only directory is allowed.
- " In Cirrus-CI for Freebsd, tests are run under a root account where
- " modifying files in a read-only directory are allowed.
- return
- endif
- call mkdir('Xdir')
+ " On MS-Windows, modifying files in a read-only directory is allowed.
+ CheckUnix
+ " Root can do it too.
+ CheckNotRoot
+
+ call mkdir('Xdir/')
call writefile(['one'], 'Xdir/Xfile1')
call setfperm('Xdir', 'r-xr--r--')
" try to create a new file in the directory
@@ -441,12 +493,12 @@ func Test_write_readonly_dir()
call assert_fails('write', 'E212:')
" try to create a backup file in the directory
edit! Xdir/Xfile1
- set backupdir=./Xdir
+ set backupdir=./Xdir backupskip=
set patchmode=.orig
call assert_fails('write', 'E509:')
call setfperm('Xdir', 'rwxr--r--')
call delete('Xdir', 'rf')
- set backupdir& patchmode&
+ set backupdir& backupskip& patchmode&
endfunc
" Test for writing a file using invalid file encoding
@@ -716,10 +768,167 @@ func Test_read_write_bin()
call assert_equal(0z6E6F656F6C0A, readfile('XNoEolSetEol', 'B'))
call delete('XNoEolSetEol')
- set ff&
+ set ff& fixeol&
bwipe! XNoEolSetEol
endfunc
+" Test for the 'backupcopy' option when writing files
+func Test_backupcopy()
+ CheckUnix
+ set backupskip=
+ " With the default 'backupcopy' setting, saving a symbolic link file
+ " should not break the link.
+ set backupcopy&
+ call writefile(['1111'], 'Xfile1')
+ silent !ln -s Xfile1 Xfile2
+ new Xfile2
+ call setline(1, ['2222'])
+ write
+ close
+ call assert_equal(['2222'], readfile('Xfile1'))
+ call assert_equal('Xfile1', resolve('Xfile2'))
+ call assert_equal('link', getftype('Xfile2'))
+ call delete('Xfile1')
+ call delete('Xfile2')
+
+ " With the 'backupcopy' set to 'breaksymlink', saving a symbolic link file
+ " should break the link.
+ set backupcopy=yes,breaksymlink
+ call writefile(['1111'], 'Xfile1')
+ silent !ln -s Xfile1 Xfile2
+ new Xfile2
+ call setline(1, ['2222'])
+ write
+ close
+ call assert_equal(['1111'], readfile('Xfile1'))
+ call assert_equal(['2222'], readfile('Xfile2'))
+ call assert_equal('Xfile2', resolve('Xfile2'))
+ call assert_equal('file', getftype('Xfile2'))
+ call delete('Xfile1')
+ call delete('Xfile2')
+ set backupcopy&
+
+ " With the default 'backupcopy' setting, saving a hard link file
+ " should not break the link.
+ set backupcopy&
+ call writefile(['1111'], 'Xfile1')
+ silent !ln Xfile1 Xfile2
+ new Xfile2
+ call setline(1, ['2222'])
+ write
+ close
+ call assert_equal(['2222'], readfile('Xfile1'))
+ call delete('Xfile1')
+ call delete('Xfile2')
+
+ " With the 'backupcopy' set to 'breaksymlink', saving a hard link file
+ " should break the link.
+ set backupcopy=yes,breakhardlink
+ call writefile(['1111'], 'Xfile1')
+ silent !ln Xfile1 Xfile2
+ new Xfile2
+ call setline(1, ['2222'])
+ write
+ call assert_equal(['1111'], readfile('Xfile1'))
+ call assert_equal(['2222'], readfile('Xfile2'))
+ call delete('Xfile1')
+ call delete('Xfile2')
+
+ " If a backup file is already present, then a slightly modified filename
+ " should be used as the backup file. Try with 'backupcopy' set to 'yes' and
+ " 'no'.
+ %bw
+ call writefile(['aaaa'], 'Xfile')
+ call writefile(['bbbb'], 'Xfile.bak')
+ set backupcopy=yes backupext=.bak
+ new Xfile
+ call setline(1, ['cccc'])
+ write
+ close
+ call assert_equal(['cccc'], readfile('Xfile'))
+ call assert_equal(['bbbb'], readfile('Xfile.bak'))
+ set backupcopy=no backupext=.bak
+ new Xfile
+ call setline(1, ['dddd'])
+ write
+ close
+ call assert_equal(['dddd'], readfile('Xfile'))
+ call assert_equal(['bbbb'], readfile('Xfile.bak'))
+ call delete('Xfile')
+ call delete('Xfile.bak')
+
+ " Write to a device file (in Unix-like systems) which cannot be backed up.
+ if has('unix')
+ set writebackup backupcopy=yes nobackup
+ new
+ call setline(1, ['aaaa'])
+ let output = execute('write! /dev/null')
+ call assert_match('"/dev/null" \[Device]', output)
+ close
+ set writebackup backupcopy=no nobackup
+ new
+ call setline(1, ['aaaa'])
+ let output = execute('write! /dev/null')
+ call assert_match('"/dev/null" \[Device]', output)
+ close
+ set backup writebackup& backupcopy&
+ new
+ call setline(1, ['aaaa'])
+ let output = execute('write! /dev/null')
+ call assert_match('"/dev/null" \[Device]', output)
+ close
+ endif
+
+ set backupcopy& backupskip& backupext& backup&
+endfunc
+
+" Test for writing a file with 'encoding' set to 'utf-16'
+func Test_write_utf16()
+ new
+ call setline(1, ["\U00010001"])
+ write ++enc=utf-16 Xfile
+ bw!
+ call assert_equal(0zD800DC01, readfile('Xfile', 'B')[0:3])
+ call delete('Xfile')
+endfunc
+
+" Test for trying to save a backup file when the backup file is a symbolic
+" link to the original file. The backup file should not be modified.
+func Test_write_backup_symlink()
+ CheckUnix
+ call writefile(['1111'], 'Xfile')
+ silent !ln -s Xfile Xfile.bak
+
+ new Xfile
+ set backup backupcopy=yes backupext=.bak
+ write
+ call assert_equal('link', getftype('Xfile.bak'))
+ call assert_equal('Xfile', resolve('Xfile.bak'))
+ set backup& backupcopy& backupext&
+ close
+
+ call delete('Xfile')
+ call delete('Xfile.bak')
+endfunc
+
+" Test for ':write ++bin' and ':write ++nobin'
+func Test_write_binary_file()
+ " create a file without an eol/eof character
+ call writefile(0z616161, 'Xfile1', 'b')
+ new Xfile1
+ write ++bin Xfile2
+ write ++nobin Xfile3
+ call assert_equal(0z616161, readblob('Xfile2'))
+ if has('win32')
+ call assert_equal(0z6161610D.0A, readblob('Xfile3'))
+ else
+ call assert_equal(0z6161610A, readblob('Xfile3'))
+ endif
+ call delete('Xfile1')
+ call delete('Xfile2')
+ call delete('Xfile3')
+endfunc
+
" Check that buffer is written before triggering QuitPre
func Test_wq_quitpre_autocommand()
edit Xsomefile
diff --git a/src/nvim/testdir/vim9.vim b/src/nvim/testdir/vim9.vim
new file mode 100644
index 0000000000..db74ce3b11
--- /dev/null
+++ b/src/nvim/testdir/vim9.vim
@@ -0,0 +1,80 @@
+
+" Use a different file name for each run.
+let s:sequence = 1
+
+" Check that "lines" inside a legacy function has no error.
+func CheckLegacySuccess(lines)
+ let cwd = getcwd()
+ let fname = 'XlegacySuccess' .. s:sequence
+ let s:sequence += 1
+ call writefile(['func Func()'] + a:lines + ['endfunc'], fname)
+ try
+ exe 'so ' .. fname
+ call Func()
+ finally
+ delfunc! Func
+ call chdir(cwd)
+ call delete(fname)
+ endtry
+endfunc
+
+" Check that "lines" inside a legacy function results in the expected error
+func CheckLegacyFailure(lines, error)
+ let cwd = getcwd()
+ let fname = 'XlegacyFails' .. s:sequence
+ let s:sequence += 1
+ call writefile(['func Func()'] + a:lines + ['endfunc', 'call Func()'], fname)
+ try
+ call assert_fails('so ' .. fname, a:error)
+ finally
+ delfunc! Func
+ call chdir(cwd)
+ call delete(fname)
+ endtry
+endfunc
+
+" Execute "lines" in a legacy function, translated as in
+" CheckLegacyAndVim9Success()
+func CheckTransLegacySuccess(lines)
+ let legacylines = a:lines->deepcopy()->map({_, v ->
+ \ v->substitute('\<VAR\>', 'let', 'g')
+ \ ->substitute('\<LET\>', 'let', 'g')
+ \ ->substitute('\<LSTART\>', '{', 'g')
+ \ ->substitute('\<LMIDDLE\>', '->', 'g')
+ \ ->substitute('\<LEND\>', '}', 'g')
+ \ ->substitute('\<TRUE\>', '1', 'g')
+ \ ->substitute('\<FALSE\>', '0', 'g')
+ \ ->substitute('#"', ' "', 'g')
+ \ })
+ call CheckLegacySuccess(legacylines)
+endfunc
+
+" Execute "lines" in a legacy function
+" Use 'VAR' for a declaration.
+" Use 'LET' for an assignment
+" Use ' #"' for a comment
+" Use LSTART arg LMIDDLE expr LEND for lambda
+" Use 'TRUE' for 1
+" Use 'FALSE' for 0
+func CheckLegacyAndVim9Success(lines)
+ call CheckTransLegacySuccess(a:lines)
+endfunc
+
+" Execute "lines" in a legacy function
+" Use 'VAR' for a declaration.
+" Use 'LET' for an assignment
+" Use ' #"' for a comment
+func CheckLegacyAndVim9Failure(lines, error)
+ if type(a:error) == type('string')
+ let legacyError = error
+ else
+ let legacyError = error[0]
+ endif
+
+ let legacylines = a:lines->deepcopy()->map({_, v ->
+ \ v->substitute('\<VAR\>', 'let', 'g')
+ \ ->substitute('\<LET\>', 'let', 'g')
+ \ ->substitute('#"', ' "', 'g')
+ \ })
+ call CheckLegacyFailure(legacylines, legacyError)
+endfunc
diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c
index 8028950c07..77f85b5d2d 100644
--- a/src/nvim/viml/parser/expressions.c
+++ b/src/nvim/viml/parser/expressions.c
@@ -628,8 +628,8 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags)
GET_CCS(ret, pline);
ret.data.cmp.inv = (schar == '<');
ret.data.cmp.type = ((ret.data.cmp.inv ^ haseqsign)
- ? kExprCmpGreaterOrEqual
- : kExprCmpGreater);
+ ? kExprCmpGreaterOrEqual
+ : kExprCmpGreater);
break;
}
@@ -1963,8 +1963,8 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags)
|| ((*kv_Z(ast_stack, 1))->type != kExprNodeConcat
&& ((*kv_Z(ast_stack, 1))->type
!= kExprNodeConcatOrSubscript))))
- ? kELFlagAllowFloat
- : 0));
+ ? kELFlagAllowFloat
+ : 0));
LexExprToken cur_token = viml_pexpr_next_token(pstate,
want_node_to_lexer_flags[want_node] |
lexer_additional_flags);
@@ -2031,9 +2031,9 @@ viml_pexpr_parse_process_token:
const bool node_is_key = (
is_concat_or_subscript
&& (cur_token.type == kExprLexPlainIdentifier
- ? (!cur_token.data.var.autoload
- && cur_token.data.var.scope == kExprVarScopeMissing)
- : (cur_token.type == kExprLexNumber))
+ ? (!cur_token.data.var.autoload
+ && cur_token.data.var.scope == kExprVarScopeMissing)
+ : (cur_token.type == kExprLexNumber))
&& prev_token.type != kExprLexSpacing);
if (is_concat_or_subscript && !node_is_key) {
// Note: in Vim "d. a" (this is the reason behind `prev_token.type !=
@@ -2707,14 +2707,14 @@ viml_pexpr_parse_figure_brace_closing_error:
break;
case kExprLexPlainIdentifier: {
const ExprVarScope scope = (cur_token.type == kExprLexInvalid
- ? kExprVarScopeMissing
- : cur_token.data.var.scope);
+ ? kExprVarScopeMissing
+ : cur_token.data.var.scope);
if (want_node == kENodeValue) {
want_node = kENodeOperator;
NEW_NODE_WITH_CUR_POS(cur_node,
(node_is_key
- ? kExprNodePlainKey
- : kExprNodePlainIdentifier));
+ ? kExprNodePlainKey
+ : kExprNodePlainIdentifier));
cur_node->data.var.scope = scope;
const size_t scope_shift = (scope == kExprVarScopeMissing ? 0 : 2);
cur_node->data.var.ident = (pline.data + cur_token.start.col
@@ -2732,8 +2732,8 @@ viml_pexpr_parse_figure_brace_closing_error:
scope_shift),
cur_token.len - scope_shift,
(node_is_key
- ? HL(IdentifierKey)
- : HL(IdentifierName)));
+ ? HL(IdentifierKey)
+ : HL(IdentifierName)));
} else {
if (scope == kExprVarScopeMissing) {
// uncrustify:off
@@ -2902,15 +2902,15 @@ viml_pexpr_parse_no_paren_closing_error: {}
// different error numbers: "E114: Missing quote" and
// "E115: Missing quote".
ERROR_FROM_TOKEN_AND_MSG(cur_token, (is_double
- ? _("E114: Missing double quote: %.*s")
- : _("E115: Missing single quote: %.*s")));
+ ? _("E114: Missing double quote: %.*s")
+ : _("E115: Missing single quote: %.*s")));
}
if (want_node == kENodeOperator) {
OP_MISSING;
}
NEW_NODE_WITH_CUR_POS(cur_node, (is_double
- ? kExprNodeDoubleQuotedString
- : kExprNodeSingleQuotedString));
+ ? kExprNodeDoubleQuotedString
+ : kExprNodeSingleQuotedString));
*top_node_p = cur_node;
parse_quoted_string(pstate, cur_node, cur_token, &ast_stack, is_invalid);
want_node = kENodeOperator;
diff --git a/src/nvim/window.c b/src/nvim/window.c
index e1ed61ae93..3649e6bc16 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -4,6 +4,7 @@
#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
+#include <string.h>
#include "nvim/api/private/helpers.h"
#include "nvim/api/vim.h"
@@ -5063,6 +5064,9 @@ static void win_free(win_T *wp, tabpage_T *tp)
}
}
+ // free the border title text
+ clear_virttext(&wp->w_float_config.title_chunks);
+
clear_matches(wp);
free_jumplist(wp);
diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua
index f19d7a362b..d0fb26edc7 100644
--- a/test/functional/api/command_spec.lua
+++ b/test/functional/api/command_spec.lua
@@ -114,6 +114,7 @@ describe('nvim_create_user_command', function()
]]
eq({
+ name = "CommandWithLuaCallback",
args = [[this\ is a\ test]],
fargs = {"this ", "is", "a test"},
bang = false,
@@ -150,6 +151,7 @@ describe('nvim_create_user_command', function()
]=])
eq({
+ name = "CommandWithLuaCallback",
args = [[this includes\ a backslash: \\]],
fargs = {"this", "includes a", "backslash:", "\\"},
bang = false,
@@ -186,6 +188,7 @@ describe('nvim_create_user_command', function()
]=])
eq({
+ name = "CommandWithLuaCallback",
args = "a\\b",
fargs = {"a\\b"},
bang = false,
@@ -222,6 +225,7 @@ describe('nvim_create_user_command', function()
]=])
eq({
+ name = "CommandWithLuaCallback",
args = 'h\tey ',
fargs = {[[h]], [[ey]]},
bang = true,
@@ -258,6 +262,7 @@ describe('nvim_create_user_command', function()
]=])
eq({
+ name = "CommandWithLuaCallback",
args = "h",
fargs = {"h"},
bang = false,
@@ -294,6 +299,7 @@ describe('nvim_create_user_command', function()
]])
eq({
+ name = "CommandWithLuaCallback",
args = "",
fargs = {}, -- fargs works without args
bang = false,
@@ -342,6 +348,7 @@ describe('nvim_create_user_command', function()
]]
eq({
+ name = "CommandWithOneOrNoArg",
args = "hello I'm one argument",
fargs = {"hello I'm one argument"}, -- Doesn't split args
bang = false,
@@ -379,6 +386,7 @@ describe('nvim_create_user_command', function()
-- f-args is an empty table if no args were passed
eq({
+ name = "CommandWithOneOrNoArg",
args = "",
fargs = {},
bang = false,
@@ -427,6 +435,7 @@ describe('nvim_create_user_command', function()
})
]]
eq({
+ name = "CommandWithNoArgs",
args = "",
fargs = {},
bang = false,
@@ -463,6 +472,7 @@ describe('nvim_create_user_command', function()
]])
-- register can be specified
eq({
+ name = "CommandWithNoArgs",
args = "",
fargs = {},
bang = false,
diff --git a/test/functional/ex_cmds/write_spec.lua b/test/functional/ex_cmds/write_spec.lua
index 4045d13153..d6b5c54de9 100644
--- a/test/functional/ex_cmds/write_spec.lua
+++ b/test/functional/ex_cmds/write_spec.lua
@@ -20,6 +20,9 @@ describe(':write', function()
os.remove('test_bkc_file.txt')
os.remove('test_bkc_link.txt')
os.remove('test_fifo')
+ os.remove('test/write/p_opt.txt')
+ os.remove('test/write')
+ os.remove('test')
os.remove(fname)
os.remove(fname_bak)
os.remove(fname_broken)
@@ -94,6 +97,30 @@ describe(':write', function()
fifo:close()
end)
+ it("++p creates missing parent directories", function()
+ eq(0, eval("filereadable('p_opt.txt')"))
+ command("write ++p p_opt.txt")
+ eq(1, eval("filereadable('p_opt.txt')"))
+ os.remove("p_opt.txt")
+
+ eq(0, eval("filereadable('p_opt.txt')"))
+ command("write ++p ./p_opt.txt")
+ eq(1, eval("filereadable('p_opt.txt')"))
+ os.remove("p_opt.txt")
+
+ eq(0, eval("filereadable('test/write/p_opt.txt')"))
+ command("write ++p test/write/p_opt.txt")
+ eq(1, eval("filereadable('test/write/p_opt.txt')"))
+
+ eq(('Vim(write):E32: No file name'), pcall_err(command, 'write ++p test_write/'))
+ if not iswin() then
+ eq(('Vim(write):E17: "'..funcs.fnamemodify('.', ':p:h')..'" is a directory'),
+ pcall_err(command, 'write ++p .'))
+ eq(('Vim(write):E17: "'..funcs.fnamemodify('.', ':p:h')..'" is a directory'),
+ pcall_err(command, 'write ++p ./'))
+ end
+ end)
+
it('errors out correctly', function()
if isCI('cirrus') then
pending('FIXME: cirrus')
diff --git a/test/functional/lua/ffi_spec.lua b/test/functional/lua/ffi_spec.lua
index 80c01a2b8c..3969a7a478 100644
--- a/test/functional/lua/ffi_spec.lua
+++ b/test/functional/lua/ffi_spec.lua
@@ -35,11 +35,12 @@ describe('ffi.cdef', function()
int build_stl_str_hl(
win_T *wp,
- char_u *out,
+ char *out,
size_t outlen,
- char_u *fmt,
- int use_sandbox,
- char_u fillchar,
+ char *fmt,
+ char *opt_name,
+ int opt_scope,
+ int fillchar,
int maxwidth,
stl_hlrec_t **hltab,
StlClickRecord **tabtab
@@ -48,9 +49,10 @@ describe('ffi.cdef', function()
return ffi.C.build_stl_str_hl(
ffi.C.find_window_by_handle(0, ffi.new('Error')),
- ffi.new('char_u[1024]'),
+ ffi.new('char[1024]'),
1024,
- ffi.cast('char_u*', 'StatusLineOfLength20'),
+ ffi.cast('char*', 'StatusLineOfLength20'),
+ nil,
0,
0,
0,
diff --git a/test/functional/lua/ui_spec.lua b/test/functional/lua/ui_spec.lua
index 3fcb2dec8d..8f9d8e0f72 100644
--- a/test/functional/lua/ui_spec.lua
+++ b/test/functional/lua/ui_spec.lua
@@ -83,5 +83,51 @@ describe('vim.ui', function()
feed('abcdefg<cr>')
eq('abcdefg', exec_lua('return result'))
end)
+
+ it('can input empty text #18144', function()
+ feed(':lua vim.ui.input({}, function(input) result = input end)<cr>')
+ feed('<cr>')
+ eq('', exec_lua('return result'))
+ end)
+
+ it('can input empty text with cancelreturn opt #18144', function()
+ feed(':lua vim.ui.input({ cancelreturn = "CANCEL" }, function(input) result = input end)<cr>')
+ feed('<cr>')
+ eq('', exec_lua('return result'))
+ end)
+
+ it('can return nil when aborted with ESC #18144', function()
+ feed(':lua result = "on_confirm not called"<cr>')
+ feed(':lua vim.ui.input({}, function(input) result = input end)<cr>')
+ feed('Inputted Text<esc>')
+ -- Note: When `result == nil`, exec_lua('returns result') returns vim.NIL
+ eq(true, exec_lua('return (nil == result)'))
+ end)
+
+ it('can return opts.cacelreturn when aborted with ESC with cancelreturn opt #18144', function()
+ feed(':lua result = "on_confirm not called"<cr>')
+ feed(':lua vim.ui.input({ cancelreturn = "CANCEL" }, function(input) result = input end)<cr>')
+ feed('Inputted Text<esc>')
+ eq('CANCEL', exec_lua('return result'))
+ end)
+
+ it('does not call on_confirm when interrupted with Ctrl-C #18144', function()
+ feed(':lua result = "on_confirm not called"<cr>')
+ eq('on_confirm not called', exec_lua('return result'))
+ feed(':lua vim.ui.input({}, function(input) result = input end)<cr>')
+ feed('Inputted Text<c-c>')
+ -- Ctrl-C would make vim.ui.input() throw, so `result = input` won't be executed
+ eq('on_confirm not called', exec_lua('return result'))
+ end)
+
+ it('can return the identical object when an arbitrary opts.cancelreturn object is given', function()
+ feed(':lua fn = function() return 42 end<CR>')
+ eq(42, exec_lua('return fn()'))
+ feed(':lua vim.ui.input({ cancelreturn = fn }, function(input) result = input end)<cr>')
+ feed('cancel<esc>')
+ eq(true, exec_lua('return (result == fn)'))
+ eq(42, exec_lua('return result()'))
+ end)
+
end)
end)
diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua
index 9d10f43ec6..46d08897e0 100644
--- a/test/functional/terminal/buffer_spec.lua
+++ b/test/functional/terminal/buffer_spec.lua
@@ -410,6 +410,14 @@ describe('on_lines does not emit out-of-bounds line indexes when', function()
feed_command('bdelete!')
eq('', exec_lua([[return _G.cb_error]]))
end)
+
+ it('runs TextChangedT event', function()
+ meths.set_var('called', 0)
+ command('autocmd TextChangedT * ++once let g:called = 1')
+ feed_command('terminal')
+ feed('iaa')
+ eq(1, meths.get_var('called'))
+ end)
end)
it('terminal truncates number of composing characters to 5', function()
diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua
index 03cd4bfd06..e261f0dfab 100644
--- a/test/functional/ui/cursor_spec.lua
+++ b/test/functional/ui/cursor_spec.lua
@@ -215,7 +215,7 @@ describe('ui/cursor', function()
m.hl_id = 60
m.attr = {background = Screen.colors.DarkGray}
end
- if m.id_lm then m.id_lm = 61 end
+ if m.id_lm then m.id_lm = 62 end
end
-- Assert the new expectation.
diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua
index 9ef36cf5ae..cb90a902d7 100644
--- a/test/functional/ui/float_spec.lua
+++ b/test/functional/ui/float_spec.lua
@@ -1716,6 +1716,200 @@ describe('float window', function()
end
end)
+ it('validates title title_pos', function()
+ local buf = meths.create_buf(false,false)
+ eq("title requires border to be set",
+ pcall_err(meths.open_win,buf, false, {
+ relative='editor', width=9, height=2, row=2, col=5, title='Title',
+ }))
+ eq("title_pos requires title to be set",
+ pcall_err(meths.open_win,buf, false, {
+ relative='editor', width=9, height=2, row=2, col=5,
+ border='single', title_pos='left',
+ }))
+ end)
+
+ it('border with title', function()
+ local buf = meths.create_buf(false, false)
+ meths.buf_set_lines(buf, 0, -1, true, {' halloj! ',
+ ' BORDAA '})
+ local win = meths.open_win(buf, false, {
+ relative='editor', width=9, height=2, row=2, col=5, border="double",
+ title = "Left",title_pos = "left",
+ })
+
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 5
+ {5:╔}{11:Left}{5:═════╗}|
+ {5:║}{1: halloj! }{5:║}|
+ {5:║}{1: BORDAA }{5:║}|
+ {5:╚═════════╝}|
+ ]], float_pos={
+ [5] = { { id = 1002 }, "NW", 1, 2, 5, true }
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2};
+ }}
+ else
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }{5:╔}{11:Left}{5:═════╗}{0: }|
+ {0:~ }{5:║}{1: halloj! }{5:║}{0: }|
+ {0:~ }{5:║}{1: BORDAA }{5:║}{0: }|
+ {0:~ }{5:╚═════════╝}{0: }|
+ |
+ ]]}
+ end
+
+ meths.win_set_config(win, {title= "Center",title_pos="center"})
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 5
+ {5:╔═}{11:Center}{5:══╗}|
+ {5:║}{1: halloj! }{5:║}|
+ {5:║}{1: BORDAA }{5:║}|
+ {5:╚═════════╝}|
+ ]], float_pos={
+ [5] = { { id = 1002 }, "NW", 1, 2, 5, true }
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2};
+ }}
+ else
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }{5:╔═}{11:Center}{5:══╗}{0: }|
+ {0:~ }{5:║}{1: halloj! }{5:║}{0: }|
+ {0:~ }{5:║}{1: BORDAA }{5:║}{0: }|
+ {0:~ }{5:╚═════════╝}{0: }|
+ |
+ ]]}
+ end
+
+ meths.win_set_config(win, {title= "Right",title_pos="right"})
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 5
+ {5:╔════}{11:Right}{5:╗}|
+ {5:║}{1: halloj! }{5:║}|
+ {5:║}{1: BORDAA }{5:║}|
+ {5:╚═════════╝}|
+ ]], float_pos={
+ [5] = { { id = 1002 }, "NW", 1, 2, 5, true }
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2};
+ }}
+ else
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }{5:╔════}{11:Right}{5:╗}{0: }|
+ {0:~ }{5:║}{1: halloj! }{5:║}{0: }|
+ {0:~ }{5:║}{1: BORDAA }{5:║}{0: }|
+ {0:~ }{5:╚═════════╝}{0: }|
+ |
+ ]]}
+ end
+
+ meths.win_set_config(win, {title= { {"🦄"},{"BB"}},title_pos="right"})
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 5
+ {5:╔═════}🦄BB{5:╗}|
+ {5:║}{1: halloj! }{5:║}|
+ {5:║}{1: BORDAA }{5:║}|
+ {5:╚═════════╝}|
+ ]], float_pos={
+ [5] = { { id = 1002 }, "NW", 1, 2, 5, true }
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2};
+ }}
+ else
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }{5:╔═════}🦄BB{5:╗}{0: }|
+ {0:~ }{5:║}{1: halloj! }{5:║}{0: }|
+ {0:~ }{5:║}{1: BORDAA }{5:║}{0: }|
+ {0:~ }{5:╚═════════╝}{0: }|
+ |
+ ]]}
+ end
+ end)
+
it('terminates border on edge of viewport when window extends past viewport', function()
local buf = meths.create_buf(false, false)
meths.open_win(buf, false, {relative='editor', width=40, height=7, row=0, col=0, border="single"})
diff --git a/test/functional/vimscript/writefile_spec.lua b/test/functional/vimscript/writefile_spec.lua
index 5f693249a9..8c8da9dc88 100644
--- a/test/functional/vimscript/writefile_spec.lua
+++ b/test/functional/vimscript/writefile_spec.lua
@@ -111,6 +111,26 @@ describe('writefile()', function()
pcall_err(command, ('call writefile([42], %s)'):format(ddname_tail)))
end)
+ it('writefile(..., "p") creates missing parent directories', function()
+ os.remove(dname)
+ eq(nil, read_file(dfname))
+ eq(0, funcs.writefile({'abc', 'def', 'ghi'}, dfname, 'p'))
+ eq('abc\ndef\nghi\n', read_file(dfname))
+ os.remove(dfname)
+ os.remove(dname)
+ eq(nil, read_file(dfname))
+ eq(0, funcs.writefile({'\na\nb\n'}, dfname, 'pb'))
+ eq('\0a\0b\0', read_file(dfname))
+ os.remove(dfname)
+ os.remove(dname)
+ eq('Vim(call):E32: No file name',
+ pcall_err(command, ('call writefile([], "%s", "p")'):format(dfname .. '.d/')))
+ eq(('Vim(call):E482: Can\'t open file ./ for writing: illegal operation on a directory'),
+ pcall_err(command, 'call writefile([], "./", "p")'))
+ eq(('Vim(call):E482: Can\'t open file . for writing: illegal operation on a directory'),
+ pcall_err(command, 'call writefile([], ".", "p")'))
+ end)
+
it('errors out with invalid arguments', function()
write_file(fname, 'TEST')
eq('Vim(call):E119: Not enough arguments for function: writefile',
diff --git a/test/unit/buffer_spec.lua b/test/unit/buffer_spec.lua
index 5dccc2f5a2..2611e7ca7c 100644
--- a/test/unit/buffer_spec.lua
+++ b/test/unit/buffer_spec.lua
@@ -233,7 +233,8 @@ describe('buffer functions', function()
output_buffer,
buffer_byte_size,
to_cstr(pat),
- false,
+ NULL,
+ 0,
fillchar,
maximum_cell_count,
NULL,