diff options
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(®parse)) != 0) { + } else if ((c_class = get_coll_element(®parse)) != 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 Binary files differdeleted file mode 100644 index 456f9ddb07..0000000000 --- a/src/nvim/testdir/test42.in +++ /dev/null diff --git a/src/nvim/testdir/test42.ok b/src/nvim/testdir/test42.ok Binary files differdeleted file mode 100644 index 183430d71c..0000000000 --- a/src/nvim/testdir/test42.ok +++ /dev/null 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, |