diff options
62 files changed, 1649 insertions, 1028 deletions
diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt index c768f30da2..9c973b20bd 100644 --- a/runtime/doc/deprecated.txt +++ b/runtime/doc/deprecated.txt @@ -44,7 +44,8 @@ TREESITTER return the descendant itself. LSP -• *vim.lsp.util.jump_to_location* +• *vim.lsp.util.jump_to_location* Use |vim.lsp.util.show_document()| with + `{focus=true}` instead. • *vim.lsp.buf.execute_command* Use |Client:exec_cmd()| instead. • *vim.lsp.buf.completion* Use |vim.lsp.completion.trigger()| instead. • vim.lsp.buf_request_all The `error` key has been renamed to `err` inside diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt index 342947595e..9ccc3102b6 100644 --- a/runtime/doc/diagnostic.txt +++ b/runtime/doc/diagnostic.txt @@ -445,10 +445,10 @@ Lua module: vim.diagnostic *diagnostic-api* updated on |InsertLeave|) • {severity_sort}? (`boolean|{reverse?:boolean}`, default: `false`) Sort diagnostics by severity. This affects the - order in which signs and virtual text are - displayed. When true, higher severities are - displayed before lower severities (e.g. ERROR is - displayed before WARN). Options: + order in which signs, virtual text, and + highlights are displayed. When true, higher + severities are displayed before lower severities + (e.g. ERROR is displayed before WARN). Options: • {reverse}? (boolean) Reverse sort order • {jump}? (`vim.diagnostic.Opts.Jump`) Default values for |vim.diagnostic.jump()|. See diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 300a58b681..751a66771c 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -77,6 +77,7 @@ Some keymaps are created unconditionally when Nvim starts: - "gra" is mapped in Normal and Visual mode to |vim.lsp.buf.code_action()| - "grr" is mapped in Normal mode to |vim.lsp.buf.references()| - "gri" is mapped in Normal mode to |vim.lsp.buf.implementation()| +- "gO" is mapped in Normal mode to |vim.lsp.buf.document_symbol()| - CTRL-S is mapped in Insert mode to |vim.lsp.buf.signature_help()| If not wanted, these keymaps can be removed at any time using diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 508d47e59f..91d36accc7 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -55,6 +55,8 @@ DIAGNOSTICS • |vim.diagnostic.config()| accepts a "jump" table to specify defaults for |vim.diagnostic.jump()|. +• The "underline" diagnostics handler sorts diagnostics by severity when using + the "severity_sort" option. EDITOR @@ -101,6 +103,10 @@ OPTIONS changes according to related options. It takes care of alignment, 'number', 'relativenumber' and 'signcolumn' set to "number". The now redundant `%r` item is no longer treated specially for 'statuscolumn'. +• `:set {option}<` removes the local value for all |global-local| options instead + of just string |global-local| options. +• `:setlocal {option}<` copies the global value to the local value for number + and boolean |global-local| options instead of removing the local value. PLUGINS @@ -151,6 +157,7 @@ DEFAULTS • |grn| in Normal mode maps to |vim.lsp.buf.rename()| • |grr| in Normal mode maps to |vim.lsp.buf.references()| • |gri| in Normal mode maps to |vim.lsp.buf.implementation()| + • |gO| in Normal mode maps to |vim.lsp.buf.document_symbol()| • |gra| in Normal and Visual mode maps to |vim.lsp.buf.code_action()| • CTRL-S in Insert mode maps to |vim.lsp.buf.signature_help()| • Mouse |popup-menu| includes an "Open in web browser" item when you right-click @@ -201,6 +208,8 @@ LUA • |vim.fs.rm()| can delete files and directories. • |vim.validate()| now has a new signature which uses less tables, is more peformant and easier to read. +• |vim.str_byteindex()| and |vim.str_utfindex()| gained overload signatures + supporting two new parameters, `encoding` and `strict_indexing`. OPTIONS diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 2b81c408ed..7f95a19918 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -306,19 +306,13 @@ created, thus they behave slightly differently: :se[t] {option}< Set the effective value of {option} to its global value. - For string |global-local| options, the local value is - removed, so that the global value will be used. + For |global-local| options, the local value is removed, + so that the global value will be used. For all other options, the global value is copied to the local value. :setl[ocal] {option}< Set the effective value of {option} to its global - value. - For number and boolean |global-local| options, the - local value is removed, so that the global value will - be used. - For all other options, including string |global-local| - options, the global value is copied to the local - value. + value by copying the global value to the local value. Note that the behaviour for |global-local| options is slightly different between string and number-based options. @@ -2604,6 +2598,55 @@ A jump table for the options with a short description can be found at |Q_op|. eob EndOfBuffer |hl-EndOfBuffer| lastline NonText |hl-NonText| + *'findexpr'* *'fexpr'* *E1514* +'findexpr' 'fexpr' string (default "") + global or local to buffer |global-local| + Expression that is evaluated to obtain the filename(s) for the |:find| + command. When this option is empty, the internal |file-searching| + mechanism is used. + + While evaluating the expression, the |v:fname| variable is set to the + argument of the |:find| command. + + The expression is evaluated only once per |:find| command invocation. + The expression can process all the directories specified in 'path'. + + The expression may be evaluated for command-line completion as well, + in which case the |v:cmdcomplete| variable will be set to |v:true|, + otherwise it will be set to |v:false|. + + If a match is found, the expression should return a |List| containing + one or more file names. If a match is not found, the expression + should return an empty List. + + If any errors are encountered during the expression evaluation, an + empty List is used as the return value. + + Using a function call without arguments is faster |expr-option-function| + + It is not allowed to change text or jump to another window while + evaluating 'findexpr' |textlock|. + + This option cannot be set from a |modeline| or in the |sandbox|, for + security reasons. + + Examples: + >vim + " Use glob() + func FindExprGlob() + let pat = v:cmdcomplete ? $'{v:fname}*' : v:fname + return glob(pat, v:false, v:true) + endfunc + set findexpr=FindExprGlob() + + " Use the 'git ls-files' output + func FindGitFiles() + let fnames = systemlist('git ls-files') + return fnames->filter('v:val =~? v:fname') + endfunc + set findexpr=FindGitFiles() +< + *'fixendofline'* *'fixeol'* *'nofixendofline'* *'nofixeol'* 'fixendofline' 'fixeol' boolean (default on) local to buffer diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt index d77750b485..f64865a031 100644 --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -705,6 +705,7 @@ Short explanation of each option: *option-list* 'fileignorecase' 'fic' ignore case when using file names 'filetype' 'ft' type of file, used for autocommands 'fillchars' 'fcs' characters to use for displaying special items +'findexpr' 'fexpr' expression to evaluate for |:find| 'fixendofline' 'fixeol' make sure last line in file has <EOL> 'foldclose' 'fcl' close a fold when the cursor leaves it 'foldcolumn' 'fdc' width of the column used to indicate folds diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index f9a98250ea..e439e8fccb 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -1211,7 +1211,7 @@ add_predicate({name}, {handler}, {opts}) Parameters: ~ • {name} (`string`) Name of the predicate, without leading # - • {handler} (`fun(match: table<integer,TSNode[]>, pattern: integer, source: integer|string, predicate: any[], metadata: vim.treesitter.query.TSMetadata)`) + • {handler} (`fun(match: table<integer,TSNode[]>, pattern: integer, source: integer|string, predicate: any[], metadata: vim.treesitter.query.TSMetadata): boolean?`) • see |vim.treesitter.query.add_directive()| for argument meanings • {opts} (`table?`) A table with the following fields: diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 94c0578872..10816ec358 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -151,6 +151,7 @@ of these in your config by simply removing the mapping, e.g. ":unmap Y". - |grr| - |gra| - |gri| + - |gO| - <C-S> |i_CTRL-S| - ]d |]d-default| - [d |[d-default| @@ -339,10 +340,8 @@ Normal commands: Options: -Local values for global-local number/boolean options are unset when the option -is set without a scope (e.g. by using |:set|), similarly to how global-local -string options work. - +- `:set {option}<` removes local value for all |global-local| options. +- `:setlocal {option}<` copies global value to local value for all options. - 'autoread' works in the terminal (if it supports "focus" events) - 'cpoptions' flags: |cpo-_| - 'diffopt' "linematch" feature diff --git a/runtime/doc/vvars.txt b/runtime/doc/vvars.txt index 15d836a83d..1c1d88c29c 100644 --- a/runtime/doc/vvars.txt +++ b/runtime/doc/vvars.txt @@ -48,6 +48,11 @@ v:cmdbang can only be used in autocommands. For user commands |<bang>| can be used. + *v:cmdcomplete* *cmdcomplete-variable* +v:cmdcomplete + When evaluating 'findexpr': if 'findexpr' is used for cmdline + completion the value is |v:true|, otherwise it is |v:false|. + *v:collate* *collate-variable* v:collate The current locale setting for collation order of the runtime @@ -254,7 +259,8 @@ v:fcs_reason *v:fname* *fname-variable* v:fname When evaluating 'includeexpr': the file name that was - detected. Empty otherwise. + detected. When evaluating 'findexpr': the argument passed to + the |:find| command. Empty otherwise. *v:fname_diff* *fname_diff-variable* v:fname_diff diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua index 6f60bf1cef..114c94f9e5 100644 --- a/runtime/lua/man.lua +++ b/runtime/lua/man.lua @@ -305,7 +305,7 @@ local function matchstr(text, pat_or_re) return end - return text:sub(vim.str_utfindex(text, s) + 1, vim.str_utfindex(text, e)) + return text:sub(vim.str_utfindex(text, 'utf-32', s) + 1, vim.str_utfindex(text, 'utf-32', e)) end -- attempt to extract the name and sect out of 'name(sect)' diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 0f4cd08da0..03b2803f3e 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -157,7 +157,7 @@ do --- client is attached. If no client is attached, or if a server does not support a capability, an --- error message is displayed rather than exhibiting different behavior. --- - --- See |grr|, |grn|, |gra|, |gri|, |i_CTRL-S|. + --- See |grr|, |grn|, |gra|, |gri|, |gO|, |i_CTRL-S|. do vim.keymap.set('n', 'grn', function() vim.lsp.buf.rename() @@ -175,6 +175,10 @@ do vim.lsp.buf.implementation() end, { desc = 'vim.lsp.buf.implementation()' }) + vim.keymap.set('n', 'gO', function() + vim.lsp.buf.document_symbol() + end, { desc = 'vim.lsp.buf.document_symbol()' }) + vim.keymap.set('i', '<C-S>', function() vim.lsp.buf.signature_help() end, { desc = 'vim.lsp.buf.signature_help()' }) @@ -234,6 +238,12 @@ do create_unimpaired_mapping(']q', function() vim.cmd.cnext({ count = vim.v.count1 }) end, ':cnext') + create_unimpaired_mapping('[Q', function() + vim.cmd.crewind({ count = vim.v.count ~= 0 and vim.v.count or nil }) + end, ':crewind') + create_unimpaired_mapping(']Q', function() + vim.cmd.clast({ count = vim.v.count ~= 0 and vim.v.count or nil }) + end, ':clast') create_unimpaired_mapping('[<C-Q>', function() vim.cmd.cpfile({ count = vim.v.count1 }) end, ':cpfile') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 496bbf747c..c6aa303124 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -545,7 +545,7 @@ function vim.region(bufnr, pos1, pos2, regtype, inclusive) -- TODO: handle double-width characters if regtype:byte() == 22 then local bufline = vim.api.nvim_buf_get_lines(bufnr, pos1[1], pos1[1] + 1, true)[1] - pos1[2] = vim.str_utfindex(bufline, pos1[2]) + pos1[2] = vim.str_utfindex(bufline, 'utf-32', pos1[2]) end local region = {} @@ -557,14 +557,14 @@ function vim.region(bufnr, pos1, pos2, regtype, inclusive) c2 = c1 + tonumber(regtype:sub(2)) -- and adjust for non-ASCII characters local bufline = vim.api.nvim_buf_get_lines(bufnr, l, l + 1, true)[1] - local utflen = vim.str_utfindex(bufline, #bufline) + local utflen = vim.str_utfindex(bufline, 'utf-32', #bufline) if c1 <= utflen then - c1 = assert(tonumber(vim.str_byteindex(bufline, c1))) + c1 = assert(tonumber(vim.str_byteindex(bufline, 'utf-32', c1))) else c1 = #bufline + 1 end if c2 <= utflen then - c2 = assert(tonumber(vim.str_byteindex(bufline, c2))) + c2 = assert(tonumber(vim.str_byteindex(bufline, 'utf-32', c2))) else c2 = #bufline + 1 end @@ -740,9 +740,14 @@ function vim.str_byteindex(s, encoding, index, strict_indexing) -- • {str} (`string`) -- • {index} (`integer`) -- • {use_utf16} (`boolean?`) + vim.deprecate( + 'vim.str_byteindex', + 'vim.str_byteindex(s, encoding, index, strict_indexing)', + '1.0' + ) local old_index = encoding local use_utf16 = index or false - return vim.__str_byteindex(s, old_index, use_utf16) or error('index out of range') + return vim._str_byteindex(s, old_index, use_utf16) or error('index out of range') end vim.validate('s', s, 'string') @@ -769,7 +774,7 @@ function vim.str_byteindex(s, encoding, index, strict_indexing) end return index end - return vim.__str_byteindex(s, index, encoding == 'utf-16') + return vim._str_byteindex(s, index, encoding == 'utf-16') or strict_indexing and error('index out of range') or len end @@ -793,8 +798,13 @@ function vim.str_utfindex(s, encoding, index, strict_indexing) -- Parameters: ~ -- • {str} (`string`) -- • {index} (`integer?`) + vim.deprecate( + 'vim.str_utfindex', + 'vim.str_utfindex(s, encoding, index, strict_indexing)', + '1.0' + ) local old_index = encoding - local col32, col16 = vim.__str_utfindex(s, old_index) --[[@as integer,integer]] + local col32, col16 = vim._str_utfindex(s, old_index) --[[@as integer,integer]] if not col32 or not col16 then error('index out of range') end @@ -828,7 +838,7 @@ function vim.str_utfindex(s, encoding, index, strict_indexing) local len = #s return index <= len and index or (strict_indexing and error('index out of range') or len) end - local col32, col16 = vim.__str_utfindex(s, index) --[[@as integer?,integer?]] + local col32, col16 = vim._str_utfindex(s, index) --[[@as integer?,integer?]] local col = encoding == 'utf-16' and col16 or col32 if col then return col @@ -836,7 +846,7 @@ function vim.str_utfindex(s, encoding, index, strict_indexing) if strict_indexing then error('index out of range') end - local max32, max16 = vim.__str_utfindex(s)--[[@as integer integer]] + local max32, max16 = vim._str_utfindex(s)--[[@as integer integer]] return encoding == 'utf-16' and max16 or max32 end diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 00f7554832..710f82bf21 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -2294,6 +2294,62 @@ vim.wo.fcs = vim.wo.fillchars vim.go.fillchars = vim.o.fillchars vim.go.fcs = vim.go.fillchars +--- Expression that is evaluated to obtain the filename(s) for the `:find` +--- command. When this option is empty, the internal `file-searching` +--- mechanism is used. +--- +--- While evaluating the expression, the `v:fname` variable is set to the +--- argument of the `:find` command. +--- +--- The expression is evaluated only once per `:find` command invocation. +--- The expression can process all the directories specified in 'path'. +--- +--- The expression may be evaluated for command-line completion as well, +--- in which case the `v:cmdcomplete` variable will be set to `v:true`, +--- otherwise it will be set to `v:false`. +--- +--- If a match is found, the expression should return a `List` containing +--- one or more file names. If a match is not found, the expression +--- should return an empty List. +--- +--- If any errors are encountered during the expression evaluation, an +--- empty List is used as the return value. +--- +--- Using a function call without arguments is faster `expr-option-function` +--- +--- It is not allowed to change text or jump to another window while +--- evaluating 'findexpr' `textlock`. +--- +--- This option cannot be set from a `modeline` or in the `sandbox`, for +--- security reasons. +--- +--- Examples: +--- +--- ```vim +--- " Use glob() +--- func FindExprGlob() +--- let pat = v:cmdcomplete ? $'{v:fname}*' : v:fname +--- return glob(pat, v:false, v:true) +--- endfunc +--- set findexpr=FindExprGlob() +--- +--- " Use the 'git ls-files' output +--- func FindGitFiles() +--- let fnames = systemlist('git ls-files') +--- return fnames->filter('v:val =~? v:fname') +--- endfunc +--- set findexpr=FindGitFiles() +--- ``` +--- +--- +--- @type string +vim.o.findexpr = "" +vim.o.fexpr = vim.o.findexpr +vim.bo.findexpr = vim.o.findexpr +vim.bo.fexpr = vim.bo.findexpr +vim.go.findexpr = vim.o.findexpr +vim.go.fexpr = vim.go.findexpr + --- When writing a file and this option is on, <EOL> at the end of file --- will be restored if missing. Turn this option off if you want to --- preserve the situation from the original file. diff --git a/runtime/lua/vim/_meta/vvars.lua b/runtime/lua/vim/_meta/vvars.lua index e00402ab3f..b104356334 100644 --- a/runtime/lua/vim/_meta/vvars.lua +++ b/runtime/lua/vim/_meta/vvars.lua @@ -44,6 +44,11 @@ vim.v.cmdarg = ... --- @type integer vim.v.cmdbang = ... +--- When evaluating 'findexpr': if 'findexpr' is used for cmdline +--- completion the value is `v:true`, otherwise it is `v:false`. +--- @type boolean +vim.v.cmdcomplete = ... + --- The current locale setting for collation order of the runtime --- environment. This allows Vim scripts to be aware of the --- current locale encoding. Technical: it's the value of @@ -267,7 +272,8 @@ vim.v.fcs_choice = ... vim.v.fcs_reason = ... --- When evaluating 'includeexpr': the file name that was ---- detected. Empty otherwise. +--- detected. When evaluating 'findexpr': the argument passed to +--- the `:find` command. Empty otherwise. --- @type string vim.v.fname = ... diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 392db5b800..299f34c921 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -71,9 +71,9 @@ local M = {} --- (default: `false`) --- @field update_in_insert? boolean --- ---- Sort diagnostics by severity. This affects the order in which signs and ---- virtual text are displayed. When true, higher severities are displayed ---- before lower severities (e.g. ERROR is displayed before WARN). +--- Sort diagnostics by severity. This affects the order in which signs, +--- virtual text, and highlights are displayed. When true, higher severities are +--- displayed before lower severities (e.g. ERROR is displayed before WARN). --- Options: --- - {reverse}? (boolean) Reverse sort order --- (default: `false`) @@ -657,6 +657,28 @@ local function save_extmarks(namespace, bufnr) api.nvim_buf_get_extmarks(bufnr, namespace, 0, -1, { details = true }) end +--- Create a function that converts a diagnostic severity to an extmark priority. +--- @param priority integer Base priority +--- @param opts vim.diagnostic.OptsResolved +--- @return fun(severity: vim.diagnostic.Severity): integer +local function severity_to_extmark_priority(priority, opts) + if opts.severity_sort then + if type(opts.severity_sort) == 'table' and opts.severity_sort.reverse then + return function(severity) + return priority + (severity - vim.diagnostic.severity.ERROR) + end + end + + return function(severity) + return priority + (vim.diagnostic.severity.HINT - severity) + end + end + + return function() + return priority + end +end + --- @type table<string,true> local registered_autocmds = {} @@ -1352,22 +1374,7 @@ M.handlers.signs = { -- 10 is the default sign priority when none is explicitly specified local priority = opts.signs and opts.signs.priority or 10 - local get_priority --- @type function - if opts.severity_sort then - if type(opts.severity_sort) == 'table' and opts.severity_sort.reverse then - get_priority = function(severity) - return priority + (severity - vim.diagnostic.severity.ERROR) - end - else - get_priority = function(severity) - return priority + (vim.diagnostic.severity.HINT - severity) - end - end - else - get_priority = function() - return priority - end - end + local get_priority = severity_to_extmark_priority(priority, opts) local ns = M.get_namespace(namespace) if not ns.user_data.sign_ns then @@ -1478,6 +1485,8 @@ M.handlers.underline = { end local underline_ns = ns.user_data.underline_ns + local get_priority = severity_to_extmark_priority(vim.hl.priorities.diagnostics, opts) + for _, diagnostic in ipairs(diagnostics) do --- @type string? local higroup = underline_highlight_map[assert(diagnostic.severity)] @@ -1504,7 +1513,7 @@ M.handlers.underline = { higroup, { diagnostic.lnum, diagnostic.col }, { diagnostic.end_lnum, diagnostic.end_col }, - { priority = vim.hl.priorities.diagnostics } + { priority = get_priority(diagnostic.severity) } ) end save_extmarks(underline_ns, bufnr) diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 685dcae851..a8ae283f64 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -189,9 +189,10 @@ local function reuse_client_default(client, config) end if config.root_dir then + local root = vim.uri_from_fname(config.root_dir) for _, dir in ipairs(client.workspace_folders or {}) do -- note: do not need to check client.root_dir since that should be client.workspace_folders[1] - if config.root_dir == dir.name then + if root == dir.uri then return true end end @@ -1045,7 +1046,7 @@ function lsp.formatexpr(opts) if client.supports_method(ms.textDocument_rangeFormatting) then local params = util.make_formatting_params() local end_line = vim.fn.getline(end_lnum) --[[@as string]] - local end_col = util._str_utfindex_enc(end_line, nil, client.offset_encoding) + local end_col = vim.str_utfindex(end_line, client.offset_encoding) --- @cast params +lsp.DocumentRangeFormattingParams params.range = { start = { diff --git a/runtime/lua/vim/lsp/completion.lua b/runtime/lua/vim/lsp/completion.lua index e36d329dc5..10086fa49e 100644 --- a/runtime/lua/vim/lsp/completion.lua +++ b/runtime/lua/vim/lsp/completion.lua @@ -315,7 +315,7 @@ local function adjust_start_col(lnum, line, items, encoding) end end if min_start_char then - return lsp.util._str_byteindex_enc(line, min_start_char, encoding) + return vim.str_byteindex(line, encoding, min_start_char, false) else return nil end diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index bf72222536..c59e2db901 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -33,25 +33,6 @@ local function severity_vim_to_lsp(severity) return severity end ----@param lines string[]? ----@param lnum integer ----@param col integer ----@param offset_encoding string ----@return integer -local function line_byte_from_position(lines, lnum, col, offset_encoding) - if not lines or offset_encoding == 'utf-8' then - return col - end - - local line = lines[lnum + 1] - local ok, result = pcall(vim.str_byteindex, line, col, offset_encoding == 'utf-16') - if ok then - return result --- @type integer - end - - return col -end - ---@param bufnr integer ---@return string[]? local function get_buf_lines(bufnr) @@ -118,12 +99,13 @@ local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id) ) message = diagnostic.message.value end + local line = buf_lines and buf_lines[start.line + 1] or '' --- @type vim.Diagnostic return { lnum = start.line, - col = line_byte_from_position(buf_lines, start.line, start.character, offset_encoding), + col = vim.str_byteindex(line, offset_encoding, start.character, false), end_lnum = _end.line, - end_col = line_byte_from_position(buf_lines, _end.line, _end.character, offset_encoding), + end_col = vim.str_byteindex(line, offset_encoding, _end.character, false), severity = severity_lsp_to_vim(diagnostic.severity), message = message, source = diagnostic.source, diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua index 18066a84db..0d314108fe 100644 --- a/runtime/lua/vim/lsp/health.lua +++ b/runtime/lua/vim/lsp/health.lua @@ -39,12 +39,27 @@ local function check_active_clients() elseif type(client.config.cmd) == 'function' then cmd = tostring(client.config.cmd) end + local dirs_info ---@type string + if client.workspace_folders and #client.workspace_folders > 1 then + dirs_info = string.format( + ' Workspace folders:\n %s', + vim + .iter(client.workspace_folders) + ---@param folder lsp.WorkspaceFolder + :map(function(folder) + return folder.name + end) + :join('\n ') + ) + else + dirs_info = string.format( + ' Root directory: %s', + client.root_dir and vim.fn.fnamemodify(client.root_dir, ':~') + ) or nil + end report_info(table.concat({ string.format('%s (id: %d)', client.name, client.id), - string.format( - ' Root directory: %s', - client.root_dir and vim.fn.fnamemodify(client.root_dir, ':~') or nil - ), + dirs_info, string.format(' Command: %s', cmd), string.format(' Settings: %s', vim.inspect(client.settings, { newline = '\n ' })), string.format( diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index 61d119e653..e5892928cf 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -70,20 +70,12 @@ function M.on_inlayhint(err, result, ctx, _) end local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false) - ---@param position lsp.Position - ---@return integer - local function pos_to_byte(position) - local col = position.character - if col > 0 then - local line = lines[position.line + 1] or '' - return util._str_byteindex_enc(line, col, client.offset_encoding) - end - return col - end for _, hint in ipairs(result) do local lnum = hint.position.line - hint.position.character = pos_to_byte(hint.position) + local line = lines and lines[lnum + 1] or '' + hint.position.character = + vim.str_byteindex(line, client.offset_encoding, hint.position.character, false) table.insert(new_lnum_hints[lnum], hint) end diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua index 0f6e45c330..d680522592 100644 --- a/runtime/lua/vim/lsp/semantic_tokens.lua +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -137,16 +137,10 @@ local function tokens_to_ranges(data, bufnr, client, request) local token_type = token_types[data[i + 3] + 1] local modifiers = modifiers_from_number(data[i + 4], token_modifiers) - local function _get_byte_pos(col) - if col > 0 then - local buf_line = lines[line + 1] or '' - return util._str_byteindex_enc(buf_line, col, client.offset_encoding) - end - return col - end - - local start_col = _get_byte_pos(start_char) - local end_col = _get_byte_pos(start_char + data[i + 2]) + local end_char = start_char + data[i + 2] + local buf_line = lines and lines[line + 1] or '' + local start_col = vim.str_byteindex(buf_line, client.offset_encoding, start_char, false) + local end_col = vim.str_byteindex(buf_line, client.offset_encoding, end_char, false) if token_type then ranges[#ranges + 1] = { diff --git a/runtime/lua/vim/lsp/sync.lua b/runtime/lua/vim/lsp/sync.lua index bdfe8d51b8..3df45ebff0 100644 --- a/runtime/lua/vim/lsp/sync.lua +++ b/runtime/lua/vim/lsp/sync.lua @@ -48,45 +48,6 @@ local str_utfindex = vim.str_utfindex local str_utf_start = vim.str_utf_start local str_utf_end = vim.str_utf_end --- Given a line, byte idx, and offset_encoding convert to the --- utf-8, utf-16, or utf-32 index. ----@param line string the line to index into ----@param byte integer the byte idx ----@param offset_encoding string utf-8|utf-16|utf-32|nil (default: utf-8) ----@return integer utf_idx for the given encoding -local function byte_to_utf(line, byte, offset_encoding) - -- convert to 0 based indexing for str_utfindex - byte = byte - 1 - - local utf_idx, _ --- @type integer, integer - -- Convert the byte range to utf-{8,16,32} and convert 1-based (lua) indexing to 0-based - if offset_encoding == 'utf-16' then - _, utf_idx = str_utfindex(line, byte) - elseif offset_encoding == 'utf-32' then - utf_idx, _ = str_utfindex(line, byte) - else - utf_idx = byte - end - - -- convert to 1 based indexing - return utf_idx + 1 -end - ----@param line string ----@param offset_encoding string ----@return integer -local function compute_line_length(line, offset_encoding) - local length, _ --- @type integer, integer - if offset_encoding == 'utf-16' then - _, length = str_utfindex(line) - elseif offset_encoding == 'utf-32' then - length, _ = str_utfindex(line) - else - length = #line - end - return length -end - -- Given a line, byte idx, alignment, and offset_encoding convert to the aligned -- utf-8 index and either the utf-16, or utf-32 index. ---@param line string the line to index into @@ -101,7 +62,7 @@ local function align_end_position(line, byte, offset_encoding) char = byte -- Called in the case of extending an empty line "" -> "a" elseif byte == #line + 1 then - char = compute_line_length(line, offset_encoding) + 1 + char = str_utfindex(line, offset_encoding) + 1 else -- Modifying line, find the nearest utf codepoint local offset = str_utf_start(line, byte) @@ -111,9 +72,10 @@ local function align_end_position(line, byte, offset_encoding) byte = byte + str_utf_end(line, byte) + 1 end if byte <= #line then - char = byte_to_utf(line, byte, offset_encoding) + --- Convert to 0 based for input, and from 0 based for output + char = str_utfindex(line, offset_encoding, byte - 1) + 1 else - char = compute_line_length(line, offset_encoding) + 1 + char = str_utfindex(line, offset_encoding) + 1 end -- Extending line, find the nearest utf codepoint for the last valid character end @@ -153,7 +115,7 @@ local function compute_start_range( if line then line_idx = firstline - 1 byte_idx = #line + 1 - char_idx = compute_line_length(line, offset_encoding) + 1 + char_idx = str_utfindex(line, offset_encoding) + 1 else line_idx = firstline byte_idx = 1 @@ -190,10 +152,11 @@ local function compute_start_range( char_idx = 1 elseif start_byte_idx == #prev_line + 1 then byte_idx = start_byte_idx - char_idx = compute_line_length(prev_line, offset_encoding) + 1 + char_idx = str_utfindex(prev_line, offset_encoding) + 1 else byte_idx = start_byte_idx + str_utf_start(prev_line, start_byte_idx) - char_idx = byte_to_utf(prev_line, byte_idx, offset_encoding) + --- Convert to 0 based for input, and from 0 based for output + char_idx = vim.str_utfindex(prev_line, offset_encoding, byte_idx - 1) + 1 end -- Return the start difference (shared for new and prev lines) @@ -230,7 +193,7 @@ local function compute_end_range( return { line_idx = lastline - 1, byte_idx = #prev_line + 1, - char_idx = compute_line_length(prev_line, offset_encoding) + 1, + char_idx = str_utfindex(prev_line, offset_encoding) + 1, }, { line_idx = 1, byte_idx = 1, char_idx = 1 } end -- If firstline == new_lastline, the first change occurred on a line that was deleted. @@ -376,7 +339,7 @@ local function compute_range_length(lines, start_range, end_range, offset_encodi local start_line = lines[start_range.line_idx] local range_length --- @type integer if start_line and #start_line > 0 then - range_length = compute_line_length(start_line, offset_encoding) + range_length = str_utfindex(start_line, offset_encoding) - start_range.char_idx + 1 + line_ending_length @@ -389,7 +352,7 @@ local function compute_range_length(lines, start_range, end_range, offset_encodi for idx = start_range.line_idx + 1, end_range.line_idx - 1 do -- Length full line plus newline character if #lines[idx] > 0 then - range_length = range_length + compute_line_length(lines[idx], offset_encoding) + #line_ending + range_length = range_length + str_utfindex(lines[idx], offset_encoding) + #line_ending else range_length = range_length + line_ending_length end diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 2e9c71cf38..41f93e5374 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -116,71 +116,6 @@ local function create_window_without_focus() return new end ---- Convert byte index to `encoding` index. ---- Convenience wrapper around vim.str_utfindex ----@param line string line to be indexed ----@param index integer? byte index (utf-8), or `nil` for length ----@param encoding 'utf-8'|'utf-16'|'utf-32'? defaults to utf-16 ----@return integer `encoding` index of `index` in `line` -function M._str_utfindex_enc(line, index, encoding) - local len32, len16 = vim.str_utfindex(line) - if not encoding then - encoding = 'utf-16' - end - if encoding == 'utf-8' then - if index then - return index - else - return #line - end - elseif encoding == 'utf-16' then - if not index or index > len16 then - return len16 - end - local _, col16 = vim.str_utfindex(line, index) - return col16 - elseif encoding == 'utf-32' then - if not index or index > len32 then - return len32 - end - local col32, _ = vim.str_utfindex(line, index) - return col32 - else - error('Invalid encoding: ' .. vim.inspect(encoding)) - end -end - ---- Convert UTF index to `encoding` index. ---- Convenience wrapper around vim.str_byteindex ----Alternative to vim.str_byteindex that takes an encoding. ----@param line string line to be indexed ----@param index integer UTF index ----@param encoding string utf-8|utf-16|utf-32| defaults to utf-16 ----@return integer byte (utf-8) index of `encoding` index `index` in `line` -function M._str_byteindex_enc(line, index, encoding) - -- LSP spec: if character > line length, default to the line length. - -- https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#position - local len8 = #line - if not encoding then - encoding = 'utf-16' - end - if encoding == 'utf-8' then - if index and index <= len8 then - return index - else - return len8 - end - end - local len32, len16 = vim.str_utfindex(line) - if encoding == 'utf-16' then - return index <= len16 and vim.str_byteindex(line, index, true) or len8 - elseif encoding == 'utf-32' then - return index <= len32 and vim.str_byteindex(line, index) or len8 - else - error('Invalid encoding: ' .. vim.inspect(encoding)) - end -end - --- Replaces text in a range with new text. --- --- CAUTION: Changes in-place! @@ -352,7 +287,7 @@ local function get_line_byte_from_position(bufnr, position, offset_encoding) -- character if col > 0 then local line = get_line(bufnr, position.line) or '' - return M._str_byteindex_enc(line, col, offset_encoding) + return vim.str_byteindex(line, offset_encoding, col, false) end return col end @@ -1022,7 +957,7 @@ end --- Jumps to a location. --- ----@deprecated +---@deprecated use `vim.lsp.util.show_document` with `{focus=true}` instead ---@param location lsp.Location|lsp.LocationLink ---@param offset_encoding 'utf-8'|'utf-16'|'utf-32'? ---@param reuse_win boolean? Jump to existing window if buffer is already open. @@ -1787,8 +1722,8 @@ function M.locations_to_items(locations, offset_encoding) local end_row = end_pos.line local line = lines[row] or '' local end_line = lines[end_row] or '' - local col = M._str_byteindex_enc(line, pos.character, offset_encoding) - local end_col = M._str_byteindex_enc(end_line, end_pos.character, offset_encoding) + local col = vim.str_byteindex(line, offset_encoding, pos.character, false) + local end_col = vim.str_byteindex(end_line, offset_encoding, end_pos.character, false) items[#items + 1] = { filename = filename, @@ -1911,7 +1846,7 @@ local function make_position_param(window, offset_encoding) return { line = 0, character = 0 } end - col = M._str_utfindex_enc(line, col, offset_encoding) + col = vim.str_utfindex(line, offset_encoding, col, false) return { line = row, character = col } end @@ -2092,7 +2027,7 @@ function M.character_offset(buf, row, col, offset_encoding) ) offset_encoding = vim.lsp.get_clients({ bufnr = buf })[1].offset_encoding end - return M._str_utfindex_enc(line, col, offset_encoding) + return vim.str_utfindex(line, offset_encoding, col, false) end --- Helper function to return nested values in language server settings diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 4614967799..1677e8d364 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -626,7 +626,7 @@ local directive_handlers = { --- Adds a new predicate to be used in queries --- ---@param name string Name of the predicate, without leading # ----@param handler fun(match: table<integer,TSNode[]>, pattern: integer, source: integer|string, predicate: any[], metadata: vim.treesitter.query.TSMetadata) +---@param handler fun(match: table<integer,TSNode[]>, pattern: integer, source: integer|string, predicate: any[], metadata: vim.treesitter.query.TSMetadata): boolean? --- - see |vim.treesitter.query.add_directive()| for argument meanings ---@param opts? vim.treesitter.query.add_predicate.Opts function M.add_predicate(name, handler, opts) diff --git a/runtime/syntax/awk.vim b/runtime/syntax/awk.vim index 3082c1cb5c..4e7c0d1e72 100644 --- a/runtime/syntax/awk.vim +++ b/runtime/syntax/awk.vim @@ -2,7 +2,7 @@ " Language: awk, nawk, gawk, mawk " Maintainer: Doug Kearns <dougkearns@gmail.com> " Previous Maintainer: Antonio Colombo <azc100@gmail.com> -" Last Change: 2020 Aug 18 +" Last Change: 2024 Oct 28 " AWK ref. is: Alfred V. Aho, Brian W. Kernighan, Peter J. Weinberger " The AWK Programming Language, Addison-Wesley, 1988 @@ -92,7 +92,7 @@ syn match awkSpecialCharacter display contained "\\x[0-9A-Fa-f]\+" syn match awkFieldVars "\$\d\+" " catch errors caused by wrong parenthesis -syn region awkParen transparent start="(" end=")" contains=ALLBUT,awkParenError,awkSpecialCharacter,awkArrayElement,awkArrayArray,awkTodo,awkRegExp,awkBrktRegExp,awkBrackets,awkCharClass,awkComment +syn region awkParen transparent start="(" end=")" contains=ALLBUT,awkParenError,awkSpecialCharacter,awkArrayElement,awkArrayArray,awkTodo,awkRegExp,awkBrktRegExp,awkBrackets,awkCharClass syn match awkParenError display ")" "syn match awkInParen display contained "[{}]" diff --git a/runtime/tutor/en/vim-01-beginner.tutor b/runtime/tutor/en/vim-01-beginner.tutor index e6b81d63b9..95d4f4eafd 100644 --- a/runtime/tutor/en/vim-01-beginner.tutor +++ b/runtime/tutor/en/vim-01-beginner.tutor @@ -302,7 +302,7 @@ it would be easier to simply type two d's to delete a line. 3. Now move to the fourth line. - 4. Type `2dd`{normal} to delete two lines. + 4. Type `2dd`{normal} to delete two lines, then press `u`{normal} twice to undo all three lines. 1) Roses are red, 2) Mud is fun, @@ -689,7 +689,7 @@ NOTE: Pressing [v](v) starts [Visual selection](visual-mode). You can move the c 1. Place the cursor just above this line. NOTE: After executing Step 2 you will see text from Lesson 5.3. Then move - DOWN to see this lesson again. + DOWN to see this lesson again. Press `u`{normal} to undo after you are done. 2. Now retrieve your TEST file using the command @@ -736,12 +736,12 @@ NOTE: You can also read the output of an external command. For example, 2. Type the lowercase letter `o`{normal} to [open](o) up a line BELOW the cursor and place you in Insert mode. - 3. Now type some text and press `<Esc>`{normal} to exit Insert mode. + 3. Now type some text and press `<Esc>`{normal} to exit Insert mode. Remove your opened lines after you're done. After typing `o`{normal} the cursor is placed on the open line in Insert mode. 4. To open up a line ABOVE the cursor, simply type a [capital O](O), rather - than a lowercase `o`{normal}. Try this on the line below. + than a lowercase `o`{normal}. Try this on the line below. Remove your opened lines after you're done. Open up a line above this by typing O while the cursor is on this line. diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index e61f1a8ce2..84735c293a 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -73,10 +73,10 @@ return { 'InsertLeavePre', -- just before leaving Insert mode 'LspAttach', -- after an LSP client attaches to a buffer 'LspDetach', -- after an LSP client detaches from a buffer - 'LspRequest', -- after an LSP request is started, canceled, or completed 'LspNotify', -- after an LSP notice has been sent to the server - 'LspTokenUpdate', -- after a visible LSP token is updated 'LspProgress', -- after a LSP progress update + 'LspRequest', -- after an LSP request is started, canceled, or completed + 'LspTokenUpdate', -- after a visible LSP token is updated 'MenuPopup', -- just before popup menu is displayed 'ModeChanged', -- after changing the mode 'OptionSet', -- after setting any option @@ -160,8 +160,8 @@ return { LspAttach = true, LspDetach = true, LspNotify = true, - LspRequest = true, LspProgress = true, + LspRequest = true, LspTokenUpdate = true, RecordingEnter = true, RecordingLeave = true, diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 2142b5b298..42cc745fe6 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -2049,6 +2049,7 @@ void free_buf_options(buf_T *buf, bool free_p_ff) clear_string_option(&buf->b_p_indk); clear_string_option(&buf->b_p_fp); clear_string_option(&buf->b_p_fex); + clear_string_option(&buf->b_p_fexpr); clear_string_option(&buf->b_p_kp); clear_string_option(&buf->b_p_mps); clear_string_option(&buf->b_p_fo); diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 1fe5512708..88e8d59faa 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -608,6 +608,7 @@ struct file_buffer { char *b_p_mp; ///< 'makeprg' local value char *b_p_efm; ///< 'errorformat' local value char *b_p_ep; ///< 'equalprg' local value + char *b_p_fexpr; ///< 'findexpr' local value char *b_p_path; ///< 'path' local value int b_p_ar; ///< 'autoread' local value char *b_p_tags; ///< 'tags' local value @@ -1249,7 +1250,7 @@ struct window_S { // transform a pointer to a "onebuf" option into a "allbuf" option #define GLOBAL_WO(p) ((char *)(p) + sizeof(winopt_T)) - // A few options have local flags for P_INSECURE. + // A few options have local flags for kOptFlagInsecure. uint32_t w_p_stl_flags; // flags for 'statusline' uint32_t w_p_wbr_flags; // flags for 'winbar' uint32_t w_p_fde_flags; // flags for 'foldexpr' diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index 250d705ee6..aeaed536fc 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -109,6 +109,7 @@ static bool cmdline_fuzzy_completion_supported(const expand_T *const xp) && xp->xp_context != EXPAND_FILES && xp->xp_context != EXPAND_FILES_IN_PATH && xp->xp_context != EXPAND_FILETYPE + && xp->xp_context != EXPAND_FINDEXPR && xp->xp_context != EXPAND_HELP && xp->xp_context != EXPAND_KEYMAP && xp->xp_context != EXPAND_LUA @@ -1228,7 +1229,8 @@ char *addstar(char *fname, size_t len, int context) // For help tags the translation is done in find_help_tags(). // For a tag pattern starting with "/" no translation is needed. - if (context == EXPAND_HELP + if (context == EXPAND_FINDEXPR + || context == EXPAND_HELP || context == EXPAND_COLORS || context == EXPAND_COMPILER || context == EXPAND_OWNSYNTAX @@ -1350,7 +1352,7 @@ char *addstar(char *fname, size_t len, int context) /// it. /// EXPAND_BUFFERS Complete file names for :buf and :sbuf commands. /// EXPAND_FILES After command with EX_XFILE set, or after setting -/// with P_EXPAND set. eg :e ^I, :w>>^I +/// with kOptFlagExpand set. eg :e ^I, :w>>^I /// EXPAND_DIRECTORIES In some cases this is used instead of the latter /// when we know only directories are of interest. /// E.g. :set dir=^I and :cd ^I @@ -1827,7 +1829,7 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa case CMD_sfind: case CMD_tabfind: if (xp->xp_context == EXPAND_FILES) { - xp->xp_context = EXPAND_FILES_IN_PATH; + xp->xp_context = *get_findexpr() != NUL ? EXPAND_FINDEXPR : EXPAND_FILES_IN_PATH; } break; case CMD_cd: @@ -2497,21 +2499,25 @@ static int expand_files_and_dirs(expand_T *xp, char *pat, char ***matches, int * } } - if (xp->xp_context == EXPAND_FILES) { - flags |= EW_FILE; - } else if (xp->xp_context == EXPAND_FILES_IN_PATH) { - flags |= (EW_FILE | EW_PATH); - } else if (xp->xp_context == EXPAND_DIRS_IN_CDPATH) { - flags = (flags | EW_DIR | EW_CDPATH) & ~EW_FILE; + int ret = FAIL; + if (xp->xp_context == EXPAND_FINDEXPR) { + ret = expand_findexpr(pat, matches, numMatches); } else { - flags = (flags | EW_DIR) & ~EW_FILE; - } - if (options & WILD_ICASE) { - flags |= EW_ICASE; + if (xp->xp_context == EXPAND_FILES) { + flags |= EW_FILE; + } else if (xp->xp_context == EXPAND_FILES_IN_PATH) { + flags |= (EW_FILE | EW_PATH); + } else if (xp->xp_context == EXPAND_DIRS_IN_CDPATH) { + flags = (flags | EW_DIR | EW_CDPATH) & ~EW_FILE; + } else { + flags = (flags | EW_DIR) & ~EW_FILE; + } + if (options & WILD_ICASE) { + flags |= EW_ICASE; + } + // Expand wildcards, supporting %:h and the like. + ret = expand_wildcards_eval(&pat, numMatches, matches, flags); } - - // Expand wildcards, supporting %:h and the like. - int ret = expand_wildcards_eval(&pat, numMatches, matches, flags); if (free_pat) { xfree(pat); } @@ -2716,6 +2722,7 @@ static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numM if (xp->xp_context == EXPAND_FILES || xp->xp_context == EXPAND_DIRECTORIES || xp->xp_context == EXPAND_FILES_IN_PATH + || xp->xp_context == EXPAND_FINDEXPR || xp->xp_context == EXPAND_DIRS_IN_CDPATH) { return expand_files_and_dirs(xp, pat, matches, numMatches, flags, options); } diff --git a/src/nvim/cmdexpand_defs.h b/src/nvim/cmdexpand_defs.h index 86725eafd6..ce51f30bec 100644 --- a/src/nvim/cmdexpand_defs.h +++ b/src/nvim/cmdexpand_defs.h @@ -107,6 +107,7 @@ enum { EXPAND_KEYMAP, EXPAND_DIRS_IN_CDPATH, EXPAND_SHELLCMDLINE, + EXPAND_FINDEXPR, EXPAND_CHECKHEALTH, EXPAND_LUA, }; diff --git a/src/nvim/errors.h b/src/nvim/errors.h index 39095db952..6682a42d61 100644 --- a/src/nvim/errors.h +++ b/src/nvim/errors.h @@ -156,6 +156,11 @@ EXTERN const char e_luv_api_disabled[] INIT(= N_("E5560: %s must not be called i EXTERN const char e_floatonly[] INIT(= N_("E5601: Cannot close window, only floating window would remain")); EXTERN const char e_floatexchange[] INIT(= N_("E5602: Cannot exchange or rotate float")); +EXTERN const char e_cant_find_directory_str_in_cdpath[] INIT(= N_("E344: Can't find directory \"%s\" in cdpath")); +EXTERN const char e_cant_find_file_str_in_path[] INIT(= N_("E345: Can't find file \"%s\" in path")); +EXTERN const char e_no_more_directory_str_found_in_cdpath[] INIT(= N_("E346: No more directory \"%s\" found in cdpath")); +EXTERN const char e_no_more_file_str_found_in_path[] INIT(= N_("E347: No more file \"%s\" found in path")); + EXTERN const char e_cannot_define_autocommands_for_all_events[] INIT(= N_("E1155: Cannot define autocommands for ALL events")); EXTERN const char e_resulting_text_too_long[] INIT(= N_("E1240: Resulting text too long")); @@ -181,6 +186,7 @@ INIT(= N_("E5767: Cannot use :undo! to redo or move to a different undo branch") EXTERN const char e_winfixbuf_cannot_go_to_buffer[] INIT(= N_("E1513: Cannot switch buffer. 'winfixbuf' is enabled")); +EXTERN const char e_invalid_return_type_from_findexpr[] INIT( = N_("E1514: 'findexpr' did not return a List type")); EXTERN const char e_trustfile[] INIT(= N_("E5570: Cannot update trust file: %s")); diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 93cff80bd4..bf85ed1646 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -270,6 +270,7 @@ static struct vimvar { VV(VV_COLLATE, "collate", VAR_STRING, VV_RO), VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO), VV(VV_MAXCOL, "maxcol", VAR_NUMBER, VV_RO), + VV(VV_CMDCOMPLETE, "cmdcomplete", VAR_BOOL, VV_RO), // Neovim VV(VV_STDERR, "stderr", VAR_NUMBER, VV_RO), VV(VV_MSGPACK_TYPES, "msgpack_types", VAR_DICT, VV_RO), @@ -460,6 +461,9 @@ void eval_init(void) set_vim_var_nr(VV_SEARCHFORWARD, 1); set_vim_var_nr(VV_HLSEARCH, 1); set_vim_var_nr(VV_COUNT1, 1); + set_vim_var_special(VV_EXITING, kSpecialVarNull); + set_vim_var_bool(VV_CMDCOMPLETE, kBoolVarFalse); + set_vim_var_nr(VV_TYPE_NUMBER, VAR_TYPE_NUMBER); set_vim_var_nr(VV_TYPE_STRING, VAR_TYPE_STRING); set_vim_var_nr(VV_TYPE_FUNC, VAR_TYPE_FUNC); @@ -475,7 +479,6 @@ void eval_init(void) set_vim_var_nr(VV_NUMBERMAX, VARNUMBER_MAX); set_vim_var_nr(VV_NUMBERMIN, VARNUMBER_MIN); set_vim_var_nr(VV_NUMBERSIZE, sizeof(varnumber_T) * 8); - set_vim_var_special(VV_EXITING, kSpecialVarNull); set_vim_var_nr(VV_MAXCOL, MAXCOL); set_vim_var_nr(VV_ECHOSPACE, sc_col - 1); @@ -2631,7 +2634,7 @@ static int may_call_simple_func(const char *arg, typval_T *rettv) /// Handle zero level expression with optimization for a simple function call. /// Same arguments and return value as eval0(). -static int eval0_simple_funccal(char *arg, typval_T *rettv, exarg_T *eap, evalarg_T *const evalarg) +int eval0_simple_funccal(char *arg, typval_T *rettv, exarg_T *eap, evalarg_T *const evalarg) { int r = may_call_simple_func(arg, rettv); diff --git a/src/nvim/eval.h b/src/nvim/eval.h index bb9b00abc7..b5605bb644 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -167,6 +167,7 @@ typedef enum { VV_COLLATE, VV_EXITING, VV_MAXCOL, + VV_CMDCOMPLETE, // Nvim VV_STDERR, VV_MSGPACK_TYPES, diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 5125bd0b88..34045c7c9d 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -11015,6 +11015,44 @@ M.funcs = { params = { { 'expr', 'number' } }, signature = 'srand([{expr}])', }, + state = { + args = { 0, 1 }, + base = 1, + desc = [=[ + Return a string which contains characters indicating the + current state. Mostly useful in callbacks that want to do + work that may not always be safe. Roughly this works like: + - callback uses state() to check if work is safe to do. + Yes: then do it right away. + No: add to work queue and add a |SafeState| autocommand. + - When SafeState is triggered and executes your autocommand, + check with `state()` if the work can be done now, and if yes + remove it from the queue and execute. + Remove the autocommand if the queue is now empty. + Also see |mode()|. + + When {what} is given only characters in this string will be + added. E.g, this checks if the screen has scrolled: >vim + if state('s') == '' + " screen has not scrolled + < + These characters indicate the state, generally indicating that + something is busy: + m halfway a mapping, :normal command, feedkeys() or + stuffed command + o operator pending, e.g. after |d| + a Insert mode autocomplete active + x executing an autocommand + S not triggering SafeState, e.g. after |f| or a count + c callback invoked, including timer (repeats for + recursiveness up to "ccc") + s screen has scrolled for messages + ]=], + fast = true, + name = 'state', + params = { { 'what', 'string' } }, + signature = 'state([{what}])', + }, stdioopen = { args = 1, desc = [=[ @@ -11073,44 +11111,6 @@ M.funcs = { returns = 'string|string[]', signature = 'stdpath({what})', }, - state = { - args = { 0, 1 }, - base = 1, - desc = [=[ - Return a string which contains characters indicating the - current state. Mostly useful in callbacks that want to do - work that may not always be safe. Roughly this works like: - - callback uses state() to check if work is safe to do. - Yes: then do it right away. - No: add to work queue and add a |SafeState| autocommand. - - When SafeState is triggered and executes your autocommand, - check with `state()` if the work can be done now, and if yes - remove it from the queue and execute. - Remove the autocommand if the queue is now empty. - Also see |mode()|. - - When {what} is given only characters in this string will be - added. E.g, this checks if the screen has scrolled: >vim - if state('s') == '' - " screen has not scrolled - < - These characters indicate the state, generally indicating that - something is busy: - m halfway a mapping, :normal command, feedkeys() or - stuffed command - o operator pending, e.g. after |d| - a Insert mode autocomplete active - x executing an autocommand - S not triggering SafeState, e.g. after |f| or a count - c callback invoked, including timer (repeats for - recursiveness up to "ccc") - s screen has scrolled for messages - ]=], - fast = true, - name = 'state', - params = { { 'what', 'string' } }, - signature = 'state([{what}])', - }, str2float = { args = 1, base = 1, diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index d002bff321..d3f836f7f4 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -1908,7 +1908,7 @@ static OptVal tv_to_optval(typval_T *tv, OptIndex opt_idx, const char *option, b const bool option_has_num = !is_tty_opt && option_has_type(opt_idx, kOptValTypeNumber); const bool option_has_str = is_tty_opt || option_has_type(opt_idx, kOptValTypeString); - if (!is_tty_opt && (get_option(opt_idx)->flags & P_FUNC) && tv_is_func(*tv)) { + if (!is_tty_opt && (get_option(opt_idx)->flags & kOptFlagFunc) && 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. diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 293aaac036..74ba19b30a 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -5165,6 +5165,115 @@ static void ex_wrongmodifier(exarg_T *eap) eap->errmsg = _(e_invcmd); } +/// Evaluate the 'findexpr' expression and return the result. When evaluating +/// the expression, v:fname is set to the ":find" command argument. +static list_T *eval_findexpr(const char *pat, bool cmdcomplete) +{ + const sctx_T saved_sctx = current_sctx; + + char *findexpr = get_findexpr(); + + set_vim_var_string(VV_FNAME, pat, -1); + set_vim_var_bool(VV_CMDCOMPLETE, cmdcomplete ? kBoolVarTrue : kBoolVarFalse); + current_sctx = curbuf->b_p_script_ctx[BV_FEXPR].script_ctx; + + char *arg = skipwhite(findexpr); + + textlock++; + + // Evaluate the expression. If the expression is "FuncName()" call the + // function directly. + typval_T tv; + list_T *retlist = NULL; + if (eval0_simple_funccal(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL) { + retlist = NULL; + } else { + if (tv.v_type == VAR_LIST) { + retlist = tv_list_copy(NULL, tv.vval.v_list, true, get_copyID()); + } else { + emsg(_(e_invalid_return_type_from_findexpr)); + } + tv_clear(&tv); + } + textlock--; + clear_evalarg(&EVALARG_EVALUATE, NULL); + + set_vim_var_string(VV_FNAME, NULL, 0); + set_vim_var_bool(VV_CMDCOMPLETE, kBoolVarFalse); + current_sctx = saved_sctx; + + return retlist; +} + +/// Find file names matching "pat" using 'findexpr' and return it in "files". +/// Used for expanding the :find, :sfind and :tabfind command argument. +/// Returns OK on success and FAIL otherwise. +int expand_findexpr(const char *pat, char ***files, int *numMatches) +{ + *numMatches = 0; + *files = NULL; + + list_T *l = eval_findexpr(pat, true); + if (l == NULL) { + return FAIL; + } + + int len = tv_list_len(l); + if (len == 0) { // empty List + return FAIL; + } + + *files = xmalloc(sizeof(char *) * (size_t)len); + + // Copy all the List items + int idx = 0; + TV_LIST_ITER_CONST(l, li, { + if (TV_LIST_ITEM_TV(li)->v_type == VAR_STRING) { + (*files)[idx] = xstrdup(TV_LIST_ITEM_TV(li)->vval.v_string); + idx++; + } + }); + + *numMatches = idx; + tv_list_free(l); + + return OK; +} + +/// Use 'findexpr' to find file 'findarg'. The 'count' argument is used to find +/// the n'th matching file. +static char *findexpr_find_file(char *findarg, size_t findarg_len, int count) +{ + char *ret_fname = NULL; + + const char cc = findarg[findarg_len]; + findarg[findarg_len] = NUL; + + list_T *fname_list = eval_findexpr(findarg, false); + int fname_count = tv_list_len(fname_list); + + if (fname_count == 0) { + semsg(_(e_cant_find_file_str_in_path), findarg); + } else { + if (count > fname_count) { + semsg(_(e_no_more_file_str_found_in_path), findarg); + } else { + listitem_T *li = tv_list_find(fname_list, count - 1); + if (li != NULL && TV_LIST_ITEM_TV(li)->v_type == VAR_STRING) { + ret_fname = xstrdup(TV_LIST_ITEM_TV(li)->vval.v_string); + } + } + } + + if (fname_list != NULL) { + tv_list_free(fname_list); + } + + findarg[findarg_len] = cc; + + return ret_fname; +} + /// :sview [+command] file split window with new file, read-only /// :split [[+command] file] split window with current or new file /// :vsplit [[+command] file] split window vertically with current or new file @@ -5196,13 +5305,17 @@ void ex_splitview(exarg_T *eap) } if (eap->cmdidx == CMD_sfind || eap->cmdidx == CMD_tabfind) { - char *file_to_find = NULL; - char *search_ctx = NULL; - fname = find_file_in_path(eap->arg, strlen(eap->arg), - FNAME_MESS, true, curbuf->b_ffname, - &file_to_find, &search_ctx); - xfree(file_to_find); - vim_findfile_cleanup(search_ctx); + if (*get_findexpr() != NUL) { + fname = findexpr_find_file(eap->arg, strlen(eap->arg), + eap->addr_count > 0 ? eap->line2 : 1); + } else { + char *file_to_find = NULL; + char *search_ctx = NULL; + fname = find_file_in_path(eap->arg, strlen(eap->arg), FNAME_MESS, true, + curbuf->b_ffname, &file_to_find, &search_ctx); + xfree(file_to_find); + vim_findfile_cleanup(search_ctx); + } if (fname == NULL) { goto theend; } @@ -5398,23 +5511,28 @@ static void ex_find(exarg_T *eap) return; } - char *file_to_find = NULL; - char *search_ctx = NULL; - char *fname = find_file_in_path(eap->arg, strlen(eap->arg), - FNAME_MESS, true, curbuf->b_ffname, - &file_to_find, &search_ctx); - if (eap->addr_count > 0) { - // Repeat finding the file "count" times. This matters when it appears - // several times in the path. - linenr_T count = eap->line2; - while (fname != NULL && --count > 0) { - xfree(fname); - fname = find_file_in_path(NULL, 0, FNAME_MESS, false, curbuf->b_ffname, - &file_to_find, &search_ctx); + char *fname = NULL; + if (*get_findexpr() != NUL) { + fname = findexpr_find_file(eap->arg, strlen(eap->arg), + eap->addr_count > 0 ? eap->line2 : 1); + } else { + char *file_to_find = NULL; + char *search_ctx = NULL; + fname = find_file_in_path(eap->arg, strlen(eap->arg), FNAME_MESS, true, + curbuf->b_ffname, &file_to_find, &search_ctx); + if (eap->addr_count > 0) { + // Repeat finding the file "count" times. This matters when it appears + // several times in the path. + linenr_T count = eap->line2; + while (fname != NULL && --count > 0) { + xfree(fname); + fname = find_file_in_path(NULL, 0, FNAME_MESS, false, + curbuf->b_ffname, &file_to_find, &search_ctx); + } } + xfree(file_to_find); + vim_findfile_cleanup(search_ctx); } - xfree(file_to_find); - vim_findfile_cleanup(search_ctx); if (fname == NULL) { return; diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index cdfd281718..aeaf448a05 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -1489,15 +1489,15 @@ char *find_file_in_path_option(char *ptr, size_t len, int options, int first, ch if (file_name == NULL && (options & FNAME_MESS)) { if (first == true) { if (find_what == FINDFILE_DIR) { - semsg(_("E344: Can't find directory \"%s\" in cdpath"), *file_to_find); + semsg(_(e_cant_find_directory_str_in_cdpath), *file_to_find); } else { - semsg(_("E345: Can't find file \"%s\" in path"), *file_to_find); + semsg(_(e_cant_find_file_str_in_path), *file_to_find); } } else { if (find_what == FINDFILE_DIR) { - semsg(_("E346: No more directory \"%s\" found in cdpath"), *file_to_find); + semsg(_(e_no_more_directory_str_found_in_cdpath), *file_to_find); } else { - semsg(_("E347: No more file \"%s\" found in path"), *file_to_find); + semsg(_(e_no_more_file_str_found_in_path), *file_to_find); } } } diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index 591a6b93df..0cb5fa8e95 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -16,23 +16,23 @@ local options = require('options') local cstr = options.cstr local redraw_flags = { - ui_option = 'P_UI_OPTION', - tabline = 'P_RTABL', - statuslines = 'P_RSTAT', - current_window = 'P_RWIN', - current_buffer = 'P_RBUF', - all_windows = 'P_RALL', - curswant = 'P_CURSWANT', - highlight_only = 'P_HLONLY', + ui_option = 'kOptFlagUIOption', + tabline = 'kOptFlagRedrTabl', + statuslines = 'kOptFlagRedrStat', + current_window = 'kOptFlagRedrWin', + current_buffer = 'kOptFlagRedrBuf', + all_windows = 'kOptFlagRedrAll', + curswant = 'kOptFlagCurswant', + highlight_only = 'kOptFlagHLOnly', } local list_flags = { - comma = 'P_COMMA', - onecomma = 'P_ONECOMMA', - commacolon = 'P_COMMA|P_COLON', - onecommacolon = 'P_ONECOMMA|P_COLON', - flags = 'P_FLAGLIST', - flagscomma = 'P_COMMA|P_FLAGLIST', + comma = 'kOptFlagComma', + onecomma = 'kOptFlagOneComma', + commacolon = 'kOptFlagComma|kOptFlagColon', + onecommacolon = 'kOptFlagOneComma|kOptFlagColon', + flags = 'kOptFlagFlagList', + flagscomma = 'kOptFlagComma|kOptFlagFlagList', } --- @param s string @@ -61,28 +61,27 @@ local function get_flags(o) end end if o.expand then - add_flag('P_EXPAND') + add_flag('kOptFlagExpand') if o.expand == 'nodefault' then - add_flag('P_NO_DEF_EXP') + add_flag('kOptFlagNoDefExp') end end for _, flag_desc in ipairs({ - { 'alloced' }, - { 'nodefault' }, - { 'no_mkrc' }, + { 'nodefault', 'NoDefault' }, + { 'no_mkrc', 'NoMkrc' }, { 'secure' }, { 'gettext' }, - { 'noglob' }, - { 'normal_fname_chars', 'P_NFNAME' }, - { 'normal_dname_chars', 'P_NDNAME' }, - { 'pri_mkrc' }, - { 'deny_in_modelines', 'P_NO_ML' }, - { 'deny_duplicates', 'P_NODUP' }, - { 'modelineexpr', 'P_MLE' }, + { 'noglob', 'NoGlob' }, + { 'normal_fname_chars', 'NFname' }, + { 'normal_dname_chars', 'NDname' }, + { 'pri_mkrc', 'PriMkrc' }, + { 'deny_in_modelines', 'NoML' }, + { 'deny_duplicates', 'NoDup' }, + { 'modelineexpr', 'MLE' }, { 'func' }, }) do local key_name = flag_desc[1] - local def_name = flag_desc[2] or ('P_' .. key_name:upper()) + local def_name = 'kOptFlag' .. (flag_desc[2] or lowercase_to_titlecase(key_name)) if o[key_name] then add_flag(def_name) end @@ -90,6 +89,12 @@ local function get_flags(o) return flags end +--- @param opt_type vim.option_type +--- @return string +local function opt_type_enum(opt_type) + return ('kOptValType%s'):format(lowercase_to_titlecase(opt_type)) +end + --- @param o vim.option_meta --- @return string local function get_type_flags(o) @@ -99,7 +104,7 @@ local function get_type_flags(o) for _, opt_type in ipairs(opt_types) do assert(type(opt_type) == 'string') - type_flags = ('%s | (1 << kOptValType%s)'):format(type_flags, lowercase_to_titlecase(opt_type)) + type_flags = ('%s | (1 << %s)'):format(type_flags, opt_type_enum(opt_type)) end return type_flags @@ -125,27 +130,48 @@ local function get_cond(c, base_string) return cond_string end +--- @param s string +--- @return string +local static_cstr_as_string = function(s) + return ('{ .data = %s, .size = sizeof(%s) - 1 }'):format(s, s) +end + +--- @param v vim.option_value|function +--- @return string +local get_opt_val = function(v) + --- @type vim.option_type + local v_type + + if type(v) == 'function' then + v, v_type = v() --[[ @as string, vim.option_type ]] + + if v_type == 'string' then + v = static_cstr_as_string(v) + end + else + v_type = type(v) --[[ @as vim.option_type ]] + + if v_type == 'boolean' then + v = v and 'true' or 'false' + elseif v_type == 'number' then + v = ('%iL'):format(v) + elseif v_type == 'string' then + v = static_cstr_as_string(cstr(v)) + end + end + + return ('{ .type = %s, .data.%s = %s }'):format(opt_type_enum(v_type), v_type, v) +end + +--- @param d vim.option_value|function +--- @param n string +--- @return string + local get_defaults = function(d, n) if d == nil then error("option '" .. n .. "' should have a default value") end - - local value_dumpers = { - ['function'] = function(v) - return v() - end, - string = function(v) - return '.string=' .. cstr(v) - end, - boolean = function(v) - return '.boolean=' .. (v and 'true' or 'false') - end, - number = function(v) - return ('.number=%iL'):format(v) - end, - } - - return value_dumpers[type(d)](d) + return get_opt_val(d) end --- @type [string,string][] @@ -173,7 +199,7 @@ local function dump_option(i, o) w(' .var=&' .. o.varname) elseif o.hidden or o.immutable then -- Hidden and immutable options can directly point to the default value. - w((' .var=&options[%u].def_val'):format(i - 1)) + w((' .var=&options[%u].def_val.data'):format(i - 1)) elseif #o.scope == 1 and o.scope[1] == 'window' then w(' .var=VAR_WIN') else @@ -219,14 +245,16 @@ local function dump_option(i, o) if o.defaults.condition then w(get_cond(o.defaults.condition)) end - w(' .def_val' .. get_defaults(o.defaults.if_true, o.full_name)) + w(' .def_val=' .. get_defaults(o.defaults.if_true, o.full_name)) if o.defaults.condition then if o.defaults.if_false then w('#else') - w(' .def_val' .. get_defaults(o.defaults.if_false, o.full_name)) + w(' .def_val=' .. get_defaults(o.defaults.if_false, o.full_name)) end w('#endif') end + else + w(' .def_val=NIL_OPTVAL') end w(' },') end diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 45a31ce6b0..1cdb38f5f8 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -713,12 +713,6 @@ EXTERN char *escape_chars INIT( = " \t\\\"|"); // need backslash in cmd line EXTERN bool keep_help_flag INIT( = false); // doing :ta from help file -// When a string option is NULL (which only happens in out-of-memory situations), it is set to -// empty_string_option, to avoid having to check for NULL everywhere. -// -// TODO(famiu): Remove this when refcounted strings are used for string options. -EXTERN char *empty_string_option INIT( = ""); - EXTERN bool redir_off INIT( = false); // no redirection for a moment EXTERN FILE *redir_fd INIT( = NULL); // message redirection file EXTERN int redir_reg INIT( = 0); // message redirection register diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c index bf8b085458..e719d99640 100644 --- a/src/nvim/lua/stdlib.c +++ b/src/nvim/lua/stdlib.c @@ -699,10 +699,10 @@ void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread) lua_setfield(lstate, -2, "stricmp"); // str_utfindex lua_pushcfunction(lstate, &nlua_str_utfindex); - lua_setfield(lstate, -2, "__str_utfindex"); + lua_setfield(lstate, -2, "_str_utfindex"); // str_byteindex lua_pushcfunction(lstate, &nlua_str_byteindex); - lua_setfield(lstate, -2, "__str_byteindex"); + lua_setfield(lstate, -2, "_str_byteindex"); // str_utf_pos lua_pushcfunction(lstate, &nlua_str_utf_pos); lua_setfield(lstate, -2, "str_utf_pos"); diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 1ebf835eb5..9ea55dbd0c 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -1151,7 +1151,7 @@ static int __has_ancestor(lua_State *L) int const pred_len = (int)lua_objlen(L, 2); TSNode node = ts_tree_root_node(descendant.tree); - while (node.id != descendant.id) { + while (node.id != descendant.id && !ts_node_is_null(node)) { char const *node_type = ts_node_type(node); size_t node_type_len = strlen(node_type); diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c index e055ebc2fa..23efd2a841 100644 --- a/src/nvim/mapping.c +++ b/src/nvim/mapping.c @@ -568,6 +568,12 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, mapblock_T **abbr_table = args->buffer ? &buf->b_first_abbr : &first_abbr; mapblock_T *mp_result[2] = { NULL, NULL }; + bool unmap_lhs_only = false; + if (maptype == MAPTYPE_UNMAP_LHS) { + unmap_lhs_only = true; + maptype = MAPTYPE_UNMAP; + } + // For ":noremap" don't remap, otherwise do remap. int noremap = args->script ? REMAP_SCRIPT : maptype == MAPTYPE_NOREMAP ? REMAP_NONE : REMAP_YES; @@ -720,8 +726,8 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, // entry with a matching 'to' part. This was done to allow ":ab foo bar" // to be unmapped by typing ":unab foo", where "foo" will be replaced by // "bar" because of the abbreviation. - for (int round = 0; (round == 0 || maptype == MAPTYPE_UNMAP) && round <= 1 - && !did_it && !got_int; round++) { + const int num_rounds = maptype == MAPTYPE_UNMAP && !unmap_lhs_only ? 2 : 1; + for (int round = 0; round < num_rounds && !did_it && !got_int; round++) { int hash_start, hash_end; if ((round == 0 && has_lhs) || is_abbrev) { // just use one hash @@ -935,9 +941,11 @@ theend: /// for :cabbr mode is MODE_CMDLINE /// ``` /// -/// @param maptype MAPTYPE_MAP for |:map| -/// MAPTYPE_UNMAP for |:unmap| -/// MAPTYPE_NOREMAP for |:noremap|. +/// @param maptype MAPTYPE_MAP for |:map| or |:abbr| +/// MAPTYPE_UNMAP for |:unmap| or |:unabbr| +/// MAPTYPE_NOREMAP for |:noremap| or |:noreabbr| +/// MAPTYPE_UNMAP_LHS is like MAPTYPE_UNMAP, but doesn't try to match +/// with {rhs} if there is no match with {lhs}. /// @param arg C-string containing the arguments of the map/abbrev /// command, i.e. everything except the initial `:[X][nore]map`. /// - Cannot be a read-only string; it will be modified. @@ -2348,7 +2356,7 @@ void f_mapset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) MapArguments unmap_args = MAP_ARGUMENTS_INIT; set_maparg_lhs_rhs(lhs, strlen(lhs), "", 0, LUA_NOREF, p_cpo, &unmap_args); unmap_args.buffer = buffer; - buf_do_map(MAPTYPE_UNMAP, &unmap_args, mode, is_abbr, curbuf); + buf_do_map(MAPTYPE_UNMAP_LHS, &unmap_args, mode, is_abbr, curbuf); xfree(unmap_args.rhs); xfree(unmap_args.orig_rhs); diff --git a/src/nvim/mapping.h b/src/nvim/mapping.h index b82117ea86..fc120b683c 100644 --- a/src/nvim/mapping.h +++ b/src/nvim/mapping.h @@ -19,9 +19,10 @@ /// Used for the first argument of do_map() enum { - MAPTYPE_MAP = 0, - MAPTYPE_UNMAP = 1, - MAPTYPE_NOREMAP = 2, + MAPTYPE_MAP = 0, + MAPTYPE_UNMAP = 1, + MAPTYPE_NOREMAP = 2, + MAPTYPE_UNMAP_LHS = 3, }; /// Adjust chars in a language according to 'langmap' option. diff --git a/src/nvim/option.c b/src/nvim/option.c index 5f0115b46c..65f03ca77f 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -176,7 +176,7 @@ static int p_paste_dep_opts[] = { void set_init_tablocal(void) { // susy baka: cmdheight calls itself OPT_GLOBAL but is really tablocal! - p_ch = options[kOptCmdheight].def_val.number; + p_ch = options[kOptCmdheight].def_val.data.number; } /// Initialize the 'shell' option to a default value. @@ -291,8 +291,8 @@ static void set_init_default_cdpath(void) } } buf[j] = NUL; - options[kOptCdpath].def_val.string = buf; - options[kOptCdpath].flags |= P_DEF_ALLOCED; + change_option_default(kOptCdpath, CSTR_AS_OPTVAL(buf)); + xfree(cdpath); } @@ -301,29 +301,22 @@ static void set_init_default_cdpath(void) /// only happen for non-indirect options. /// Also set the default to the expanded value, so ":set" does not list /// them. -/// Don't set the P_ALLOCED flag, because we don't want to free the -/// default. static void set_init_expand_env(void) { for (OptIndex opt_idx = 0; opt_idx < kOptIndexCount; opt_idx++) { vimoption_T *opt = &options[opt_idx]; - if (opt->flags & P_NO_DEF_EXP) { + if (opt->flags & kOptFlagNoDefExp) { continue; } char *p; - if ((opt->flags & P_GETTEXT) && opt->var != NULL) { + if ((opt->flags & kOptFlagGettext) && opt->var != NULL) { p = _(*(char **)opt->var); } else { p = option_expand(opt_idx, NULL); } if (p != NULL) { - p = xstrdup(p); - *(char **)opt->var = p; - if (opt->flags & P_DEF_ALLOCED) { - xfree(opt->def_val.string); - } - opt->def_val.string = p; - opt->flags |= P_DEF_ALLOCED; + set_option_varp(opt_idx, opt->var, CSTR_TO_OPTVAL(p), true); + change_option_default(opt_idx, CSTR_TO_OPTVAL(p)); } } } @@ -354,6 +347,9 @@ void set_init_1(bool clean_arg) { langmap_init(); + // Allocate the default option values. + alloc_options_default(); + set_init_default_shell(); set_init_default_backupskip(); set_init_default_cdpath(); @@ -430,71 +426,73 @@ void set_init_1(bool clean_arg) set_helplang_default(get_mess_lang()); } -/// Set an option to its default value. -/// This does not take care of side effects! +/// Get default value for option, based on the option's type and scope. /// -/// @param opt_flags OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL. +/// @param opt_idx Option index in options[] table. +/// @param opt_flags Option flags. /// -/// TODO(famiu): Refactor this when def_val uses OptVal. -static void set_option_default(const OptIndex opt_idx, int opt_flags) +/// @return Default value of option for the scope specified in opt_flags. +static OptVal get_option_default(const OptIndex opt_idx, int opt_flags) { - bool both = (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0; - - // pointer to variable for current option vimoption_T *opt = &options[opt_idx]; - void *varp = get_varp_scope(opt, both ? OPT_LOCAL : opt_flags); - uint32_t flags = opt->flags; - if (varp != NULL) { // skip hidden option, nothing to do for it - if (option_has_type(opt_idx, kOptValTypeString)) { - // Use set_option_direct() for local options to handle freeing and allocating the value. - if (opt->indir != PV_NONE) { - set_option_direct(opt_idx, CSTR_AS_OPTVAL(opt->def_val.string), opt_flags, 0); - } else { - if (flags & P_ALLOCED) { - free_string_option(*(char **)(varp)); - } - *(char **)varp = opt->def_val.string; - opt->flags &= ~P_ALLOCED; - } - } else if (option_has_type(opt_idx, kOptValTypeNumber)) { - if (opt->indir == PV_SCROLL) { - win_comp_scroll(curwin); - } else { - OptInt def_val = opt->def_val.number; - if ((OptInt *)varp == &curwin->w_p_so - || (OptInt *)varp == &curwin->w_p_siso) { - // 'scrolloff' and 'sidescrolloff' local values have a - // different default value than the global default. - *(OptInt *)varp = -1; - } else { - *(OptInt *)varp = def_val; - } - // May also set global value for local option. - if (both) { - *(OptInt *)get_varp_scope(opt, OPT_GLOBAL) = def_val; - } - } - } else { // boolean - *(int *)varp = opt->def_val.boolean; + bool is_global_local_option = opt->indir & PV_BOTH; + #ifdef UNIX - // 'modeline' defaults to off for root - if (opt->indir == PV_ML && getuid() == ROOT_UID) { - *(int *)varp = false; - } + if (opt_idx == kOptModeline && getuid() == ROOT_UID) { + // 'modeline' defaults to off for root. + return BOOLEAN_OPTVAL(false); + } #endif - // May also set global value for local option. - if (both) { - *(int *)get_varp_scope(opt, OPT_GLOBAL) = - *(int *)varp; - } - } - // The default value is not insecure. - uint32_t *flagsp = insecure_flag(curwin, opt_idx, opt_flags); - *flagsp = *flagsp & ~P_INSECURE; + if ((opt_flags & OPT_LOCAL) && is_global_local_option) { + // Use unset local value instead of default value for local scope of global-local options. + return get_option_unset_value(opt_idx); + } else if (option_has_type(opt_idx, kOptValTypeString) && !(opt->flags & kOptFlagNoDefExp)) { + // For string options, expand environment variables and ~ since the default value was already + // expanded, only required when an environment variable was set later. + char *s = option_expand(opt_idx, opt->def_val.data.string.data); + return s == NULL ? opt->def_val : CSTR_AS_OPTVAL(s); + } else { + return opt->def_val; } +} - set_option_sctx(opt_idx, opt_flags, current_sctx); +/// Allocate the default values for all options by copying them from the stack. +/// This ensures that we don't need to always check if the option default is allocated or not. +static void alloc_options_default(void) +{ + for (OptIndex opt_idx = 0; opt_idx < kOptIndexCount; opt_idx++) { + options[opt_idx].def_val = optval_copy(options[opt_idx].def_val); + } +} + +/// Change the default value for an option. +/// +/// @param opt_idx Option index in options[] table. +/// @param value New default value. Must be allocated. +static void change_option_default(const OptIndex opt_idx, OptVal value) +{ + optval_free(options[opt_idx].def_val); + options[opt_idx].def_val = value; +} + +/// Set an option to its default value. +/// This does not take care of side effects! +/// +/// @param opt_idx Option index in options[] table. +/// @param opt_flags Option flags. +static void set_option_default(const OptIndex opt_idx, int opt_flags) +{ + OptVal def_val = get_option_default(opt_idx, opt_flags); + set_option_direct(opt_idx, def_val, opt_flags, current_sctx.sc_sid); + + if (opt_idx == kOptScroll) { + win_comp_scroll(curwin); + } + + // The default value is not insecure. + uint32_t *flagsp = insecure_flag(curwin, opt_idx, opt_flags); + *flagsp = *flagsp & ~(unsigned)kOptFlagInsecure; } /// Set all options (except terminal options) to their default value. @@ -503,7 +501,7 @@ static void set_option_default(const OptIndex opt_idx, int opt_flags) static void set_options_default(int opt_flags) { for (OptIndex opt_idx = 0; opt_idx < kOptIndexCount; opt_idx++) { - if (!(options[opt_idx].flags & P_NODEFAULT)) { + if (!(options[opt_idx].flags & kOptFlagNoDefault)) { set_option_default(opt_idx, opt_flags); } } @@ -522,20 +520,13 @@ static void set_options_default(int opt_flags) /// @param opt_idx Option index in options[] table. /// @param val The value of the option. /// @param allocated If true, do not copy default as it was already allocated. +/// +/// TODO(famiu): Remove this. static void set_string_default(OptIndex opt_idx, char *val, bool allocated) FUNC_ATTR_NONNULL_ALL { - if (opt_idx == kOptInvalid) { - return; - } - - vimoption_T *opt = &options[opt_idx]; - if (opt->flags & P_DEF_ALLOCED) { - xfree(opt->def_val.string); - } - - opt->def_val.string = allocated ? val : xstrdup(val); - opt->flags |= P_DEF_ALLOCED; + assert(opt_idx != kOptInvalid); + change_option_default(opt_idx, CSTR_AS_OPTVAL(allocated ? val : xstrdup(val))); } /// For an option value that contains comma separated items, find "newval" in @@ -551,9 +542,9 @@ static char *find_dup_item(char *origval, const char *newval, const size_t newva int bs = 0; for (char *s = origval; *s != NUL; s++) { - if ((!(flags & P_COMMA) || s == origval || (s[-1] == ',' && !(bs & 1))) + if ((!(flags & kOptFlagComma) || s == origval || (s[-1] == ',' && !(bs & 1))) && strncmp(s, newval, newvallen) == 0 - && (!(flags & P_COMMA) || s[newvallen] == ',' || s[newvallen] == NUL)) { + && (!(flags & kOptFlagComma) || s[newvallen] == ',' || s[newvallen] == NUL)) { return s; } // Count backslashes. Only a comma with an even number of backslashes @@ -569,15 +560,6 @@ static char *find_dup_item(char *origval, const char *newval, const size_t newva return NULL; } -/// Set the Vi-default value of a number option. -/// Used for 'lines' and 'columns'. -void set_number_default(OptIndex opt_idx, OptInt val) -{ - if (opt_idx != kOptInvalid) { - options[opt_idx].def_val.number = val; - } -} - #if defined(EXITFREE) /// Free all options. void free_all_options(void) @@ -585,16 +567,14 @@ void free_all_options(void) for (OptIndex opt_idx = 0; opt_idx < kOptIndexCount; opt_idx++) { if (options[opt_idx].indir == PV_NONE) { // global option: free value and default value. - if ((options[opt_idx].flags & P_ALLOCED) && options[opt_idx].var != NULL) { + if (options[opt_idx].var != NULL) { optval_free(optval_from_varp(opt_idx, options[opt_idx].var)); } - if (options[opt_idx].flags & P_DEF_ALLOCED) { - optval_free(optval_from_varp(opt_idx, &options[opt_idx].def_val)); - } } else if (options[opt_idx].var != VAR_WIN) { // buffer-local option: free global value optval_free(optval_from_varp(opt_idx, options[opt_idx].var)); } + optval_free(options[opt_idx].def_val); } free_operatorfunc_option(); free_tagfunc_option(); @@ -612,7 +592,7 @@ void set_init_2(bool headless) // 'scroll' defaults to half the window height. The stored default is zero, // which results in the actual value computed from the window height. - if (!(options[kOptScroll].flags & P_WAS_SET)) { + if (!(options[kOptScroll].flags & kOptFlagWasSet)) { set_option_default(kOptScroll, OPT_LOCAL); } comp_col(); @@ -622,7 +602,7 @@ void set_init_2(bool headless) if (!option_was_set(kOptWindow)) { p_window = Rows - 1; } - set_number_default(kOptWindow, Rows - 1); + change_option_default(kOptWindow, NUMBER_OPTVAL(Rows - 1)); } /// Initialize the options, part three: After reading the .vimrc @@ -633,56 +613,42 @@ void set_init_3(void) // Set 'shellpipe' and 'shellredir', depending on the 'shell' option. // This is done after other initializations, where 'shell' might have been // set, but only if they have not been set before. - bool do_srr = !(options[kOptShellredir].flags & P_WAS_SET); - bool do_sp = !(options[kOptShellpipe].flags & P_WAS_SET); + bool do_srr = !(options[kOptShellredir].flags & kOptFlagWasSet); + bool do_sp = !(options[kOptShellpipe].flags & kOptFlagWasSet); size_t len = 0; char *p = (char *)invocation_path_tail(p_sh, &len); p = xmemdupz(p, len); - { - // - // Default for p_sp is "| tee", for p_srr is ">". - // For known shells it is changed here to include stderr. - // - if (path_fnamecmp(p, "csh") == 0 - || path_fnamecmp(p, "tcsh") == 0) { - if (do_sp) { - p_sp = "|& tee"; - options[kOptShellpipe].def_val.string = p_sp; - } - if (do_srr) { - p_srr = ">&"; - options[kOptShellredir].def_val.string = p_srr; - } - } else if (path_fnamecmp(p, "sh") == 0 - || path_fnamecmp(p, "ksh") == 0 - || path_fnamecmp(p, "mksh") == 0 - || path_fnamecmp(p, "pdksh") == 0 - || path_fnamecmp(p, "zsh") == 0 - || path_fnamecmp(p, "zsh-beta") == 0 - || path_fnamecmp(p, "bash") == 0 - || path_fnamecmp(p, "fish") == 0 - || path_fnamecmp(p, "ash") == 0 - || path_fnamecmp(p, "dash") == 0) { - // Always use POSIX shell style redirection if we reach this - if (do_sp) { - p_sp = "2>&1| tee"; - options[kOptShellpipe].def_val.string = p_sp; - } - if (do_srr) { - p_srr = ">%s 2>&1"; - options[kOptShellredir].def_val.string = p_srr; - } + bool is_csh = path_fnamecmp(p, "csh") == 0 || path_fnamecmp(p, "tcsh") == 0; + bool is_known_shell = path_fnamecmp(p, "sh") == 0 || path_fnamecmp(p, "ksh") == 0 + || path_fnamecmp(p, "mksh") == 0 || path_fnamecmp(p, "pdksh") == 0 + || path_fnamecmp(p, "zsh") == 0 || path_fnamecmp(p, "zsh-beta") == 0 + || path_fnamecmp(p, "bash") == 0 || path_fnamecmp(p, "fish") == 0 + || path_fnamecmp(p, "ash") == 0 || path_fnamecmp(p, "dash") == 0; + + // Default for p_sp is "| tee", for p_srr is ">". + // For known shells it is changed here to include stderr. + if (is_csh || is_known_shell) { + if (do_sp) { + const OptVal sp = + is_csh ? STATIC_CSTR_AS_OPTVAL("|& tee") : STATIC_CSTR_AS_OPTVAL("2>&1| tee"); + set_option_direct(kOptShellpipe, sp, 0, SID_NONE); + change_option_default(kOptShellpipe, optval_copy(sp)); + } + if (do_srr) { + const OptVal srr = is_csh ? STATIC_CSTR_AS_OPTVAL(">&") : STATIC_CSTR_AS_OPTVAL(">%s 2>&1"); + set_option_direct(kOptShellredir, srr, 0, SID_NONE); + change_option_default(kOptShellredir, optval_copy(srr)); } - xfree(p); } + xfree(p); if (buf_is_empty(curbuf)) { int idx_ffs = find_option("ffs"); // Apply the first entry of 'fileformats' to the initial buffer. - if (idx_ffs >= 0 && (options[idx_ffs].flags & P_WAS_SET)) { + if (idx_ffs >= 0 && (options[idx_ffs].flags & kOptFlagWasSet)) { set_fileformat(default_fileformat(), OPT_LOCAL); } } @@ -702,13 +668,11 @@ void set_helplang_default(const char *lang) if (lang_len < 2) { // safety check return; } - if (options[kOptHelplang].flags & P_WAS_SET) { + if (options[kOptHelplang].flags & kOptFlagWasSet) { return; } - if (options[kOptHelplang].flags & P_ALLOCED) { - free_string_option(p_hlg); - } + free_string_option(p_hlg); p_hlg = xmemdupz(lang, lang_len); // zh_CN becomes "cn", zh_TW becomes "tw". if (STRNICMP(p_hlg, "zh_", 3) == 0 && lang_len >= 5) { @@ -720,7 +684,6 @@ void set_helplang_default(const char *lang) p_hlg[1] = 'n'; } p_hlg[2] = NUL; - options[kOptHelplang].flags |= P_ALLOCED; } /// 'title' and 'icon' only default to true if they have not been set or reset @@ -733,13 +696,13 @@ void set_title_defaults(void) // If GUI is (going to be) used, we can always set the window title and // icon name. Saves a bit of time, because the X11 display server does // not need to be contacted. - if (!(options[kOptTitle].flags & P_WAS_SET)) { - options[kOptTitle].def_val.boolean = false; - p_title = false; + if (!(options[kOptTitle].flags & kOptFlagWasSet)) { + change_option_default(kOptTitle, BOOLEAN_OPTVAL(false)); + p_title = 0; } - if (!(options[kOptIcon].flags & P_WAS_SET)) { - options[kOptIcon].def_val.boolean = false; - p_icon = false; + if (!(options[kOptIcon].flags & kOptFlagWasSet)) { + change_option_default(kOptIcon, BOOLEAN_OPTVAL(false)); + p_icon = 0; } } @@ -758,27 +721,6 @@ void ex_set(exarg_T *eap) do_set(eap->arg, flags); } -/// Get the default value for a string option. -static char *stropt_get_default_val(OptIndex opt_idx, uint64_t flags) -{ - char *newval = options[opt_idx].def_val.string; - // expand environment variables and ~ since the default value was - // already expanded, only required when an environment variable was set - // later - if (newval == NULL) { - newval = empty_string_option; - } else if (!(options[opt_idx].flags & P_NO_DEF_EXP)) { - char *s = option_expand(opt_idx, newval); - if (s == NULL) { - s = newval; - } - newval = xstrdup(s); - } else { - newval = xstrdup(newval); - } - return newval; -} - /// Copy the new string value into allocated memory for the option. /// Can't use set_option_direct(), because we need to remove the backslashes. static char *stropt_copy_value(char *origval, char **argp, set_op_T op, @@ -802,7 +744,7 @@ static char *stropt_copy_value(char *origval, char **argp, set_op_T op, while (*arg != NUL && !ascii_iswhite(*arg)) { if (*arg == '\\' && arg[1] != NUL #ifdef BACKSLASH_IN_FILENAME - && !((flags & P_EXPAND) + && !((flags & kOptFlagExpand) && vim_isfilec((uint8_t)arg[1]) && !ascii_iswhite(arg[1]) && (arg[1] != '\\' @@ -851,12 +793,12 @@ static char *stropt_expand_envvar(OptIndex opt_idx, char *origval, char *newval, static void stropt_concat_with_comma(char *origval, char *newval, set_op_T op, uint32_t flags) { int len = 0; - int comma = ((flags & P_COMMA) && *origval != NUL && *newval != NUL); + int comma = ((flags & kOptFlagComma) && *origval != NUL && *newval != NUL); if (op == OP_ADDING) { len = (int)strlen(origval); // Strip a trailing comma, would get 2. if (comma && len > 1 - && (flags & P_ONECOMMA) == P_ONECOMMA + && (flags & kOptFlagOneComma) == kOptFlagOneComma && origval[len - 1] == ',' && origval[len - 2] != '\\') { len--; @@ -881,7 +823,7 @@ static void stropt_remove_val(char *origval, char *newval, uint32_t flags, char STRCPY(newval, origval); if (*strval) { // may need to remove a comma - if (flags & P_COMMA) { + if (flags & kOptFlagComma) { if (strval == origval) { // include comma after string if (strval[len] == ',') { @@ -903,8 +845,8 @@ static void stropt_remove_dupflags(char *newval, uint32_t flags) char *s = newval; // Remove flags that appear twice. for (s = newval; *s;) { - // if options have P_FLAGLIST and P_ONECOMMA such as 'whichwrap' - if (flags & P_ONECOMMA) { + // if options have kOptFlagFlagList and kOptFlagOneComma such as 'whichwrap' + if (flags & kOptFlagOneComma) { if (*s != ',' && *(s + 1) == ',' && vim_strchr(s + 2, (uint8_t)(*s)) != NULL) { // Remove the duplicated value and the next comma. @@ -912,7 +854,7 @@ static void stropt_remove_dupflags(char *newval, uint32_t flags) continue; } } else { - if ((!(flags & P_COMMA) || *s != ',') + if ((!(flags & kOptFlagComma) || *s != ',') && vim_strchr(s + 1, (uint8_t)(*s)) != NULL) { STRMOVE(s, s + 1); continue; @@ -922,10 +864,7 @@ static void stropt_remove_dupflags(char *newval, uint32_t flags) } } -/// Get the string value specified for a ":set" command. The following set -/// options are supported: -/// set {opt}& -/// set {opt}< +/// Get the string value specified for a ":set" command. The following set options are supported: /// set {opt}={val} /// set {opt}:{val} static char *stropt_get_newval(int nextchar, OptIndex opt_idx, char **argp, void *varp, @@ -936,61 +875,56 @@ static char *stropt_get_newval(int nextchar, OptIndex opt_idx, char **argp, void char *save_arg = NULL; char *newval; char *s = NULL; - if (nextchar == '&') { // set to default val - newval = stropt_get_default_val(opt_idx, flags); - } else if (nextchar == '<') { // set to global val - newval = xstrdup(*(char **)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL)); - } else { - arg++; // jump to after the '=' or ':' - // Set 'keywordprg' to ":help" if an empty - // value was passed to :set by the user. - if (varp == &p_kp && (*arg == NUL || *arg == ' ')) { - save_arg = arg; - arg = ":help"; - } + arg++; // jump to after the '=' or ':' - // Copy the new string into allocated memory. - newval = stropt_copy_value(origval, &arg, op, flags); + // Set 'keywordprg' to ":help" if an empty + // value was passed to :set by the user. + if (varp == &p_kp && (*arg == NUL || *arg == ' ')) { + save_arg = arg; + arg = ":help"; + } - // Expand environment variables and ~. - // Don't do it when adding without inserting a comma. - if (op == OP_NONE || (flags & P_COMMA)) { - newval = stropt_expand_envvar(opt_idx, origval, newval, op); - } + // Copy the new string into allocated memory. + newval = stropt_copy_value(origval, &arg, op, flags); - // locate newval[] in origval[] when removing it - // and when adding to avoid duplicates - int len = 0; - if (op == OP_REMOVING || (flags & P_NODUP)) { - len = (int)strlen(newval); - s = find_dup_item(origval, newval, (size_t)len, flags); + // Expand environment variables and ~. + // Don't do it when adding without inserting a comma. + if (op == OP_NONE || (flags & kOptFlagComma)) { + newval = stropt_expand_envvar(opt_idx, origval, newval, op); + } - // do not add if already there - if ((op == OP_ADDING || op == OP_PREPENDING) && s != NULL) { - op = OP_NONE; - STRCPY(newval, origval); - } + // locate newval[] in origval[] when removing it + // and when adding to avoid duplicates + int len = 0; + if (op == OP_REMOVING || (flags & kOptFlagNoDup)) { + len = (int)strlen(newval); + s = find_dup_item(origval, newval, (size_t)len, flags); - // if no duplicate, move pointer to end of original value - if (s == NULL) { - s = origval + (int)strlen(origval); - } + // do not add if already there + if ((op == OP_ADDING || op == OP_PREPENDING) && s != NULL) { + op = OP_NONE; + STRCPY(newval, origval); } - // concatenate the two strings; add a ',' if needed - if (op == OP_ADDING || op == OP_PREPENDING) { - stropt_concat_with_comma(origval, newval, op, flags); - } else if (op == OP_REMOVING) { - // Remove newval[] from origval[]. (Note: "len" has been set above - // and is used here). - stropt_remove_val(origval, newval, flags, s, len); + // if no duplicate, move pointer to end of original value + if (s == NULL) { + s = origval + (int)strlen(origval); } + } - if (flags & P_FLAGLIST) { - // Remove flags that appear twice. - stropt_remove_dupflags(newval, flags); - } + // concatenate the two strings; add a ',' if needed + if (op == OP_ADDING || op == OP_PREPENDING) { + stropt_concat_with_comma(origval, newval, op, flags); + } else if (op == OP_REMOVING) { + // Remove newval[] from origval[]. (Note: "len" has been set above + // and is used here). + stropt_remove_val(origval, newval, flags, s, len); + } + + if (flags & kOptFlagFlagList) { + // Remove flags that appear twice. + stropt_remove_dupflags(newval, flags); } if (save_arg != NULL) { @@ -1052,11 +986,11 @@ static int validate_opt_idx(win_T *win, OptIndex opt_idx, int opt_flags, uint32_ // Disallow changing some options from modelines. if (opt_flags & OPT_MODELINE) { - if (flags & (P_SECURE | P_NO_ML)) { + if (flags & (kOptFlagSecure | kOptFlagNoML)) { *errmsg = e_not_allowed_in_modeline; return FAIL; } - if ((flags & P_MLE) && !p_mle) { + if ((flags & kOptFlagMLE) && !p_mle) { *errmsg = e_not_allowed_in_modeline_when_modelineexpr_is_off; return FAIL; } @@ -1072,7 +1006,7 @@ static int validate_opt_idx(win_T *win, OptIndex opt_idx, int opt_flags, uint32_ } // Disallow changing some options in the sandbox - if (sandbox != 0 && (flags & P_SECURE)) { + if (sandbox != 0 && (flags & kOptFlagSecure)) { *errmsg = e_sandbox; return FAIL; } @@ -1152,6 +1086,7 @@ const char *find_option_end(const char *arg, OptIndex *opt_idxp) } /// Get new option value from argp. Allocated OptVal must be freed by caller. +/// Can unset local value of an option when ":set {option}<" is used. static OptVal get_option_newval(OptIndex opt_idx, int opt_flags, set_prefix_T prefix, char **argp, int nextchar, set_op_T op, uint32_t flags, void *varp, char *errbuf, const size_t errbuflen, const char **errmsg) @@ -1166,6 +1101,20 @@ static OptVal get_option_newval(OptIndex opt_idx, int opt_flags, set_prefix_T pr OptVal oldval = optval_from_varp(opt_idx, oldval_is_global ? get_varp(opt) : varp); OptVal newval = NIL_OPTVAL; + if (nextchar == '&') { + // ":set opt&": Reset to default value. + // NOTE: Use OPT_GLOBAL instead of opt_flags to ensure we don't use the unset local value for + // global-local options when OPT_LOCAL is used. + return optval_copy(get_option_default(opt_idx, OPT_GLOBAL)); + } else if (nextchar == '<') { + // ":set opt<": Reset to global value. + // ":setlocal opt<": Copy global value to local value. + if (option_is_global_local(opt_idx) && !(opt_flags & OPT_LOCAL)) { + unset_option_local_value(opt_idx); + } + return get_option_value(opt_idx, OPT_GLOBAL); + } + switch (oldval.type) { case kOptValTypeNil: abort(); @@ -1173,8 +1122,6 @@ static OptVal get_option_newval(OptIndex opt_idx, int opt_flags, set_prefix_T pr TriState newval_bool; // ":set opt!": invert - // ":set opt&": reset to default value - // ":set opt<": reset to global value if (nextchar == '!') { switch (oldval.data.boolean) { case kNone: @@ -1187,15 +1134,6 @@ static OptVal get_option_newval(OptIndex opt_idx, int opt_flags, set_prefix_T pr newval_bool = kTrue; break; } - } else if (nextchar == '&') { - newval_bool = TRISTATE_FROM_INT(options[opt_idx].def_val.boolean); - } else if (nextchar == '<') { - // For 'autoread', kNone means to use global value. - if ((int *)varp == &curbuf->b_p_ar && opt_flags == OPT_LOCAL) { - newval_bool = kNone; - } else { - newval_bool = TRISTATE_FROM_INT(*(int *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL)); - } } else { // ":set invopt": invert // ":set opt" or ":set noopt": set or reset @@ -1214,31 +1152,15 @@ static OptVal get_option_newval(OptIndex opt_idx, int opt_flags, set_prefix_T pr OptInt newval_num; // Different ways to set a number option: - // & set to default value - // < set to global value // <xx> accept special key codes for 'wildchar' or 'wildcharm' // ^x accept ctrl key codes for 'wildchar' or 'wildcharm' // c accept any non-digit for 'wildchar' or 'wildcharm' // [-]0-9 set number // other error arg++; - if (nextchar == '&') { - newval_num = options[opt_idx].def_val.number; - } else if (nextchar == '<') { - if ((OptInt *)varp == &curbuf->b_p_ul && opt_flags == OPT_LOCAL) { - // for 'undolevels' NO_LOCAL_UNDOLEVEL means using the global newval_num - newval_num = NO_LOCAL_UNDOLEVEL; - } else if (opt_flags == OPT_LOCAL - && ((OptInt *)varp == &curwin->w_p_siso || (OptInt *)varp == &curwin->w_p_so)) { - // for 'scrolloff'/'sidescrolloff' -1 means using the global newval_num - newval_num = -1; - } else { - newval_num = *(OptInt *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); - } - } else if (((OptInt *)varp == &p_wc || (OptInt *)varp == &p_wcm) - && (*arg == '<' || *arg == '^' - || (*arg != NUL && (!arg[1] || ascii_iswhite(arg[1])) - && !ascii_isdigit(*arg)))) { + if (((OptInt *)varp == &p_wc || (OptInt *)varp == &p_wcm) + && (*arg == '<' || *arg == '^' + || (*arg != NUL && (!arg[1] || ascii_iswhite(arg[1])) && !ascii_isdigit(*arg)))) { newval_num = string_to_key(arg); if (newval_num == 0) { *errmsg = e_invarg; @@ -1664,7 +1586,7 @@ char *find_shada_parameter(int type) static char *option_expand(OptIndex opt_idx, char *val) { // if option doesn't need expansion nothing to do - if (!(options[opt_idx].flags & P_EXPAND) || options[opt_idx].var == NULL) { + if (!(options[opt_idx].flags & kOptFlagExpand) || options[opt_idx].var == NULL) { return NULL; } @@ -1755,10 +1677,10 @@ int was_set_insecurely(win_T *const wp, OptIndex opt_idx, int opt_flags) assert(opt_idx != kOptInvalid); uint32_t *flagp = insecure_flag(wp, opt_idx, opt_flags); - return (*flagp & P_INSECURE) != 0; + return (*flagp & kOptFlagInsecure) != 0; } -/// Get a pointer to the flags used for the P_INSECURE flag of option +/// Get a pointer to the flags used for the kOptFlagInsecure flag of option /// "opt_idx". For some local options a local flags field is used. /// NOTE: Caller must make sure that "wp" is set to the window from which /// the option is used. @@ -3054,25 +2976,25 @@ static const char *validate_num_option(OptIndex opt_idx, void *varp, OptInt *new /// Called after an option changed: check if something needs to be redrawn. void check_redraw_for(buf_T *buf, win_T *win, uint32_t flags) { - // Careful: P_RALL is a combination of other P_ flags - bool all = (flags & P_RALL) == P_RALL; + // Careful: kOptFlagRedrAll is a combination of other redraw flags + bool all = (flags & kOptFlagRedrAll) == kOptFlagRedrAll; - if ((flags & P_RSTAT) || all) { // mark all status lines and window bars dirty + if ((flags & kOptFlagRedrStat) || all) { // mark all status lines and window bars dirty status_redraw_all(); } - if ((flags & P_RTABL) || all) { // mark tablines dirty + if ((flags & kOptFlagRedrTabl) || all) { // mark tablines dirty redraw_tabline = true; } - if ((flags & P_RBUF) || (flags & P_RWIN) || all) { - if (flags & P_HLONLY) { + if ((flags & kOptFlagRedrBuf) || (flags & kOptFlagRedrWin) || all) { + if (flags & kOptFlagHLOnly) { redraw_later(win, UPD_NOT_VALID); } else { changed_window_setting(win); } } - if (flags & P_RBUF) { + if (flags & kOptFlagRedrBuf) { redraw_buf_later(buf, UPD_NOT_VALID); } if (all) { @@ -3218,6 +3140,19 @@ bool optval_equal(OptVal o1, OptVal o2) UNREACHABLE; } +/// Get type of option. Does not support multitype options. +static OptValType option_get_type(const OptIndex opt_idx) +{ + assert(!option_is_multitype(opt_idx)); + + // If the option only supports a single type, it means that the index of the option's type flag + // corresponds to the value of the type enum. So get the index of the type flag using xctz() and + // use that as the option's type. + OptValType type = xctz(options[opt_idx].type_flags); + assert(type > kOptValTypeNil && type < kOptValTypeSize); + return type; +} + /// Create OptVal from var pointer. /// /// @param opt_idx Option index in options[] table. @@ -3237,11 +3172,7 @@ OptVal optval_from_varp(OptIndex opt_idx, void *varp) return varp == NULL ? NIL_OPTVAL : *(OptVal *)varp; } - // If the option only supports a single type, it means that the index of the option's type flag - // corresponds to the value of the type enum. So get the index of the type flag using xctz() and - // use that as the option's type. - OptValType type = xctz(options[opt_idx].type_flags); - assert(type > kOptValTypeNil && type < kOptValTypeSize); + OptValType type = option_get_type(opt_idx); switch (type) { case kOptValTypeNil: @@ -3390,6 +3321,11 @@ bool is_option_hidden(OptIndex opt_idx) return opt_idx == kOptInvalid ? false : get_varp(&options[opt_idx]) == NULL; } +static inline bool option_is_global_local(OptIndex opt_idx) +{ + return opt_idx == kOptInvalid ? false : (options[opt_idx].indir & PV_BOTH); +} + /// Get option flags. /// /// @param opt_idx Option index in options[] table. @@ -3504,7 +3440,6 @@ static const char *did_set_option(OptIndex opt_idx, void *varp, OptVal old_value vimoption_T *opt = &options[opt_idx]; const char *errmsg = NULL; bool restore_chartab = false; - bool free_oldval = (opt->flags & P_ALLOCED); bool value_changed = false; bool value_checked = false; @@ -3531,7 +3466,7 @@ static const char *did_set_option(OptIndex opt_idx, void *varp, OptVal old_value errmsg = e_unsupportedoption; } // Disallow changing some options from secure mode. - else if ((secure || sandbox != 0) && (opt->flags & P_SECURE)) { + else if ((secure || sandbox != 0) && (opt->flags & kOptFlagSecure)) { errmsg = e_secure; } // Check for a "normal" directory or file name in some string options. @@ -3580,18 +3515,12 @@ static const char *did_set_option(OptIndex opt_idx, void *varp, OptVal old_value set_option_sctx(opt_idx, opt_flags, script_ctx); } - // Free options that are in allocated memory. - // Use "free_oldval", because recursiveness may change the flags (esp. init_highlight()). - if (free_oldval) { - optval_free(old_value); - } - opt->flags |= P_ALLOCED; + optval_free(old_value); const bool scope_both = (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0; - const bool opt_is_global_local = opt->indir & PV_BOTH; if (scope_both) { - if (opt_is_global_local) { + if (option_is_global_local(opt_idx)) { // Global option with local value set to use global value. // Free the local value and clear it. void *varp_local = get_varp_scope(opt, OPT_LOCAL); @@ -3638,7 +3567,8 @@ static const char *did_set_option(OptIndex opt_idx, void *varp, OptVal old_value } if (curwin->w_curswant != MAXCOL - && (opt->flags & (P_CURSWANT | P_RALL)) != 0 && (opt->flags & P_HLONLY) == 0) { + && (opt->flags & (kOptFlagCurswant | kOptFlagRedrAll)) != 0 + && (opt->flags & kOptFlagHLOnly) == 0) { curwin->w_set_curswant = true; } @@ -3646,14 +3576,14 @@ static const char *did_set_option(OptIndex opt_idx, void *varp, OptVal old_value if (errmsg == NULL) { uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags); - opt->flags |= P_WAS_SET; + opt->flags |= kOptFlagWasSet; - // When an option is set in the sandbox, from a modeline or in secure mode set the P_INSECURE + // When an option is set in the sandbox, from a modeline or in secure mode set the kOptFlagInsecure // flag. Otherwise, if a new value is stored reset the flag. if (!value_checked && (secure || sandbox != 0 || (opt_flags & OPT_MODELINE))) { - *p |= P_INSECURE; + *p |= kOptFlagInsecure; } else if (value_replaced) { - *p &= ~P_INSECURE; + *p &= ~(unsigned)kOptFlagInsecure; } } @@ -3678,7 +3608,6 @@ static const char *validate_option_value(const OptIndex opt_idx, void *varp, Opt if (opt_flags == OPT_GLOBAL) { errmsg = _("Cannot unset global option value"); } else { - optval_free(*newval); *newval = optval_copy(get_option_unset_value(opt_idx)); } } else if (!option_has_type(opt_idx, newval->type)) { @@ -3719,25 +3648,28 @@ static const char *set_option(const OptIndex opt_idx, void *varp, OptVal value, { assert(opt_idx != kOptInvalid); - const char *errmsg = validate_option_value(opt_idx, varp, &value, opt_flags, errbuf, errbuflen); + const char *errmsg = NULL; - if (errmsg != NULL) { - optval_free(value); - return errmsg; + if (!direct) { + errmsg = validate_option_value(opt_idx, varp, &value, opt_flags, errbuf, errbuflen); + + if (errmsg != NULL) { + optval_free(value); + return errmsg; + } } vimoption_T *opt = &options[opt_idx]; const bool scope_local = opt_flags & OPT_LOCAL; const bool scope_global = opt_flags & OPT_GLOBAL; const bool scope_both = !scope_local && !scope_global; - const bool opt_is_global_local = opt->indir & PV_BOTH; // Whether local value of global-local option is unset. - // NOTE: When this is true, it also implies that opt_is_global_local is true. + // NOTE: When this is true, it also implies that the option is global-local. const bool is_opt_local_unset = is_option_local_value_unset(opt_idx); // When using ":set opt=val" for a global option with a local value the local value will be reset, // use the global value here. - if (scope_both && opt_is_global_local) { + if (scope_both && option_is_global_local(opt_idx)) { varp = opt->var; } @@ -3769,9 +3701,9 @@ static const char *set_option(const OptIndex opt_idx, void *varp, OptVal value, const int secure_saved = secure; // When an option is set in the sandbox, from a modeline or in secure mode, then deal with side - // effects in secure mode. Also when the value was set with the P_INSECURE flag and is not + // effects in secure mode. Also when the value was set with the kOptFlagInsecure flag and is not // completely replaced. - if ((opt_flags & OPT_MODELINE) || sandbox != 0 || (!value_replaced && (*p & P_INSECURE))) { + if ((opt_flags & OPT_MODELINE) || sandbox != 0 || (!value_replaced && (*p & kOptFlagInsecure))) { secure = 1; } @@ -3788,7 +3720,7 @@ static const char *set_option(const OptIndex opt_idx, void *varp, OptVal value, apply_optionset_autocmd(opt_idx, opt_flags, saved_used_value, saved_old_global_value, saved_old_local_value, saved_new_value, errmsg); } - if (opt->flags & P_UI_OPTION) { + if (opt->flags & kOptFlagUIOption) { ui_call_option_set(cstr_as_string(opt->fullname), optval_as_object(saved_new_value)); } } @@ -3823,8 +3755,10 @@ void set_option_direct(OptIndex opt_idx, OptVal value, int opt_flags, scid_T set const bool scope_both = (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0; void *varp = get_varp_scope(opt, scope_both ? OPT_LOCAL : opt_flags); - set_option(opt_idx, varp, optval_copy(value), opt_flags, set_sid, true, true, errbuf, - sizeof(errbuf)); + const char *errmsg = set_option(opt_idx, varp, optval_copy(value), opt_flags, set_sid, true, true, + errbuf, sizeof(errbuf)); + assert(errmsg == NULL); + (void)errmsg; // ignore unused warning } /// Set option value directly for buffer / window, without processing any side effects. @@ -3879,7 +3813,7 @@ const char *set_option_value(const OptIndex opt_idx, const OptVal value, int opt uint32_t flags = options[opt_idx].flags; // Disallow changing some options in the sandbox - if (sandbox > 0 && (flags & P_SECURE)) { + if (sandbox > 0 && (flags & kOptFlagSecure)) { return _(e_sandbox); } @@ -3893,6 +3827,17 @@ const char *set_option_value(const OptIndex opt_idx, const OptVal value, int opt sizeof(errbuf)); } +/// Unset the local value of a global-local option. +/// +/// @param opt_idx Index in options[] table. Must not be kOptInvalid. +/// +/// @return NULL on success, an untranslated error message on error. +static inline const char *unset_option_local_value(const OptIndex opt_idx) +{ + assert(option_is_global_local(opt_idx)); + return set_option_value(opt_idx, get_option_unset_value(opt_idx), OPT_LOCAL); +} + /// Set the value of an option. Supports TTY options, unlike set_option_value(). /// /// @param name Option name. Used for error messages and for setting TTY options. @@ -4106,7 +4051,7 @@ OptVal get_option_value_strict(OptIndex opt_idx, OptReqScope req_scope, void *fr /// Get option value for buffer / window. /// /// @param opt_idx Option index in options[] table. -/// @param[out] flagsp Set to the option flags (P_xxxx) (if not NULL). +/// @param[out] flagsp Set to the option flags (see OptFlags) (if not NULL). /// @param[in] scope Option scope (can be OPT_LOCAL, OPT_GLOBAL or a combination). /// @param[out] hidden Whether option is hidden. /// @param req_scope Requested option scope. See OptReqScope in option.h. @@ -4273,7 +4218,7 @@ static int optval_default(OptIndex opt_idx, void *varp) } OptVal current_val = optval_from_varp(opt_idx, varp); - OptVal default_val = optval_from_varp(opt_idx, &opt->def_val); + OptVal default_val = opt->def_val; return optval_equal(current_val, default_val); } @@ -4283,7 +4228,7 @@ void ui_refresh_options(void) { for (OptIndex opt_idx = 0; opt_idx < kOptIndexCount; opt_idx++) { uint32_t flags = options[opt_idx].flags; - if (!(flags & P_UI_OPTION)) { + if (!(flags & kOptFlagUIOption)) { continue; } String name = cstr_as_string(options[opt_idx].fullname); @@ -4358,14 +4303,14 @@ int makeset(FILE *fd, int opt_flags, int local_only) // - Hidden options. // // Do the loop over "options[]" twice: once for options with the - // P_PRI_MKRC flag and once without. + // kOptFlagPriMkrc flag and once without. for (int pri = 1; pri >= 0; pri--) { vimoption_T *opt; for (OptIndex opt_idx = 0; opt_idx < kOptIndexCount; opt_idx++) { opt = &options[opt_idx]; - if (!(opt->flags & P_NO_MKRC) - && ((pri == 1) == ((opt->flags & P_PRI_MKRC) != 0))) { + if (!(opt->flags & kOptFlagNoMkrc) + && ((pri == 1) == ((opt->flags & kOptFlagPriMkrc) != 0))) { // skip global option when only doing locals if (opt->indir == PV_NONE && !(opt_flags & OPT_GLOBAL)) { continue; @@ -4373,7 +4318,7 @@ int makeset(FILE *fd, int opt_flags, int local_only) // Do not store options like 'bufhidden' and 'syntax' in a vimrc // file, they are always buffer-specific. - if ((opt_flags & OPT_GLOBAL) && (opt->flags & P_NOGLOB)) { + if ((opt_flags & OPT_GLOBAL) && (opt->flags & kOptFlagNoGlob)) { continue; } @@ -4488,7 +4433,7 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char **valuep, uint64_ char *part = NULL; if (*valuep != NULL) { - if ((flags & P_EXPAND) != 0) { + if ((flags & kOptFlagExpand) != 0) { size_t size = (size_t)strlen(*valuep) + 1; // replace home directory in the whole option value into "buf" @@ -4498,7 +4443,7 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char **valuep, uint64_ // If the option value is longer than MAXPATHL, we need to append // each comma separated part of the option separately, so that it // can be expanded when read back. - if (size >= MAXPATHL && (flags & P_COMMA) != 0 + if (size >= MAXPATHL && (flags & kOptFlagComma) != 0 && vim_strchr(*valuep, ',') != NULL) { part = xmalloc(size); @@ -4585,6 +4530,8 @@ void *get_varp_scope_from(vimoption_T *p, int scope, buf_T *buf, win_T *win) switch ((int)p->indir) { case PV_FP: return &(buf->b_p_fp); + case PV_FEXPR: + return &(buf->b_p_fexpr); case PV_EFM: return &(buf->b_p_efm); case PV_GP: @@ -4706,6 +4653,8 @@ void *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win) return *buf->b_p_tsrfu != NUL ? &(buf->b_p_tsrfu) : p->var; case PV_FP: return *buf->b_p_fp != NUL ? &(buf->b_p_fp) : p->var; + case PV_FEXPR: + return *buf->b_p_fexpr != NUL ? &(buf->b_p_fexpr) : p->var; case PV_EFM: return *buf->b_p_efm != NUL ? &(buf->b_p_efm) : p->var; case PV_GP: @@ -4977,6 +4926,15 @@ char *get_equalprg(void) return curbuf->b_p_ep; } +/// Get the value of 'findexpr', either the buffer-local one or the global one. +char *get_findexpr(void) +{ + if (*curbuf->b_p_fexpr == NUL) { + return p_fexpr; + } + return curbuf->b_p_fexpr; +} + /// Copy options from one window to another. /// Used when splitting a window. void win_copy_options(win_T *wp_from, win_T *wp_to) @@ -5375,6 +5333,8 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_mp = empty_string_option; buf->b_p_efm = empty_string_option; buf->b_p_ep = empty_string_option; + buf->b_p_fexpr = xstrdup(p_fexpr); + COPY_OPT_SCTX(buf, BV_FEXPR); buf->b_p_kp = empty_string_option; buf->b_p_path = empty_string_option; buf->b_p_tags = empty_string_option; @@ -5447,7 +5407,7 @@ void reset_modifiable(void) { curbuf->b_p_ma = false; p_ma = false; - options[kOptModifiable].def_val.boolean = false; + change_option_default(kOptModifiable, BOOLEAN_OPTVAL(false)); } /// Set the global value for 'iminsert' to the local value. @@ -5630,8 +5590,8 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags) // Only string options below - // Options that have P_EXPAND are considered to all use file/dir expansion. - if (flags & P_EXPAND) { + // Options that have kOptFlagExpand are considered to all use file/dir expansion. + if (flags & kOptFlagExpand) { p = options[opt_idx].var; if (p == (char *)&p_bdir || p == (char *)&p_dir @@ -5655,7 +5615,7 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags) xp->xp_backslash = XP_BS_ONE; } } - if (flags & P_COMMA) { + if (flags & kOptFlagComma) { xp->xp_backslash |= XP_BS_COMMA; } } @@ -5665,21 +5625,21 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags) // pattern, while accounting for backslash-escaped space/commas/colons. // Triple-backslashed escaped file names (e.g. 'path') can also be // delimited by space. - if ((flags & P_EXPAND) || (flags & P_COMMA) || (flags & P_COLON)) { + if ((flags & kOptFlagExpand) || (flags & kOptFlagComma) || (flags & kOptFlagColon)) { for (p = argend - 1; p > xp->xp_pattern; p--) { // count number of backslashes before ' ' or ',' - if (*p == ' ' || *p == ',' || (*p == ':' && (flags & P_COLON))) { + if (*p == ' ' || *p == ',' || (*p == ':' && (flags & kOptFlagColon))) { char *s = p; while (s > xp->xp_pattern && *(s - 1) == '\\') { s--; } if ((*p == ' ' && ((xp->xp_backslash & XP_BS_THREE) && (p - s) < 3)) #if defined(BACKSLASH_IN_FILENAME) - || (*p == ',' && (flags & P_COMMA) && (p - s) < 1) + || (*p == ',' && (flags & kOptFlagComma) && (p - s) < 1) #else - || (*p == ',' && (flags & P_COMMA) && (p - s) < 2) + || (*p == ',' && (flags & kOptFlagComma) && (p - s) < 2) #endif - || (*p == ':' && (flags & P_COLON))) { + || (*p == ':' && (flags & kOptFlagColon))) { xp->xp_pattern = p + 1; break; } @@ -5689,7 +5649,7 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags) // An option that is a list of single-character flags should always start // at the end as we don't complete words. - if (flags & P_FLAGLIST) { + if (flags & kOptFlagFlagList) { xp->xp_pattern = argend; } @@ -5842,7 +5802,7 @@ static char *escape_option_str_cmdline(char *var) for (var = buf; *var != NUL; MB_PTR_ADV(var)) { if (var[0] == '\\' && var[1] == '\\' && expand_option_idx != kOptInvalid - && (options[expand_option_idx].flags & P_EXPAND) + && (options[expand_option_idx].flags & kOptFlagExpand) && vim_isfilec((uint8_t)var[2]) && (var[2] != '\\' || (var == buf && var[4] != '\\'))) { STRMOVE(var, var + 1); @@ -5928,11 +5888,11 @@ int ExpandSettingSubtract(expand_T *xp, regmatch_T *regmatch, int *numMatches, c if (option_has_type(expand_option_idx, kOptValTypeNumber)) { return ExpandOldSetting(numMatches, matches); - } else if (option_flags & P_COMMA) { + } else if (option_flags & kOptFlagComma) { // Split the option by comma, then present each option to the user if // it matches the pattern. // This condition needs to go first, because 'whichwrap' has both - // P_COMMA and P_FLAGLIST. + // kOptFlagComma and kOptFlagFlagList. if (*option_val == NUL) { return FAIL; @@ -5979,7 +5939,7 @@ int ExpandSettingSubtract(expand_T *xp, regmatch_T *regmatch, int *numMatches, c *matches = ga.ga_data; *numMatches = ga.ga_len; return OK; - } else if (option_flags & P_FLAGLIST) { + } else if (option_flags & kOptFlagFlagList) { // Only present the flags that are set on the option as the other flags // are not meaningful to do set-= on. @@ -6044,7 +6004,7 @@ static void option_value2string(vimoption_T *opt, int scope) varp = *(char **)(varp); if (varp == NULL) { // Just in case. NameBuff[0] = NUL; - } else if (opt->flags & P_EXPAND) { + } else if (opt->flags & kOptFlagExpand) { home_replace(NULL, varp, NameBuff, MAXPATHL, false); } else { xstrlcpy(NameBuff, varp, MAXPATHL); @@ -6105,7 +6065,7 @@ void vimrc_found(char *fname, char *envname) bool option_was_set(OptIndex opt_idx) { assert(opt_idx != kOptInvalid); - return options[opt_idx].flags & P_WAS_SET; + return options[opt_idx].flags & kOptFlagWasSet; } /// Reset the flag indicating option "name" was set. @@ -6114,7 +6074,7 @@ bool option_was_set(OptIndex opt_idx) void reset_option_was_set(OptIndex opt_idx) { assert(opt_idx != kOptInvalid); - options[opt_idx].flags &= ~P_WAS_SET; + options[opt_idx].flags &= ~(unsigned)kOptFlagWasSet; } /// fill_culopt_flags() -- called when 'culopt' changes value @@ -6512,10 +6472,10 @@ static Dict vimoption2dict(vimoption_T *opt, int req_scope, buf_T *buf, win_T *w // welcome to the jungle PUT_C(dict, "global_local", BOOLEAN_OBJ(opt->indir & PV_BOTH)); - PUT_C(dict, "commalist", BOOLEAN_OBJ(opt->flags & P_COMMA)); - PUT_C(dict, "flaglist", BOOLEAN_OBJ(opt->flags & P_FLAGLIST)); + PUT_C(dict, "commalist", BOOLEAN_OBJ(opt->flags & kOptFlagComma)); + PUT_C(dict, "flaglist", BOOLEAN_OBJ(opt->flags & kOptFlagFlagList)); - PUT_C(dict, "was_set", BOOLEAN_OBJ(opt->flags & P_WAS_SET)); + PUT_C(dict, "was_set", BOOLEAN_OBJ(opt->flags & kOptFlagWasSet)); LastSet last_set = { .channel_id = 0 }; if (req_scope == OPT_GLOBAL) { @@ -6537,12 +6497,9 @@ static Dict vimoption2dict(vimoption_T *opt, int req_scope, buf_T *buf, win_T *w PUT_C(dict, "last_set_linenr", INTEGER_OBJ(last_set.script_ctx.sc_lnum)); PUT_C(dict, "last_set_chan", INTEGER_OBJ((int64_t)last_set.channel_id)); - // TODO(bfredl): do you even nocp? - OptVal def = optval_from_varp(get_opt_idx(opt), &opt->def_val); - - PUT_C(dict, "type", CSTR_AS_OBJ(optval_type_get_name(def.type))); - PUT_C(dict, "default", optval_as_object(def)); - PUT_C(dict, "allows_duplicates", BOOLEAN_OBJ(!(opt->flags & P_NODUP))); + PUT_C(dict, "type", CSTR_AS_OBJ(optval_type_get_name(option_get_type(get_opt_idx(opt))))); + PUT_C(dict, "default", optval_as_object(opt->def_val)); + PUT_C(dict, "allows_duplicates", BOOLEAN_OBJ(!(opt->flags & kOptFlagNoDup))); return dict; } diff --git a/src/nvim/option.h b/src/nvim/option.h index 19764c0121..9b74429467 100644 --- a/src/nvim/option.h +++ b/src/nvim/option.h @@ -60,13 +60,7 @@ typedef struct { /// cmdline. Only useful for string options. opt_expand_cb_T opt_expand_cb; - // TODO(famiu): Use OptVal for def_val. - union { - int boolean; - OptInt number; - char *string; - } def_val; ///< default value for variable - + OptVal def_val; ///< default value LastSet last_set; ///< script in which the option was last set } vimoption_T; diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index ae9ccd371c..e32edbf727 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -11,6 +11,40 @@ # include "options_enum.generated.h" #endif +/// Option flags. +typedef enum { + kOptFlagExpand = 1 << 0, ///< Environment expansion. + ///< NOTE: kOptFlagExpand can never be used for local or hidden options. + kOptFlagNoDefExp = 1 << 1, ///< Don't expand default value. + kOptFlagNoDefault = 1 << 2, ///< Don't set to default value. + kOptFlagWasSet = 1 << 3, ///< Option has been set/reset. + kOptFlagNoMkrc = 1 << 4, ///< Don't include in :mkvimrc output. + kOptFlagUIOption = 1 << 5, ///< Send option to remote UI. + kOptFlagRedrTabl = 1 << 6, ///< Redraw tabline. + kOptFlagRedrStat = 1 << 7, ///< Redraw status lines. + kOptFlagRedrWin = 1 << 8, ///< Redraw current window and recompute text. + kOptFlagRedrBuf = 1 << 9, ///< Redraw current buffer and recompute text. + kOptFlagRedrAll = kOptFlagRedrBuf | kOptFlagRedrWin, ///< Redraw all windows and recompute text. + kOptFlagRedrClear = kOptFlagRedrAll | kOptFlagRedrStat, ///< Clear and redraw all and recompute text. + kOptFlagComma = 1 << 10, ///< Comma-separated list. + kOptFlagOneComma = (1 << 11) | kOptFlagComma, ///< Comma-separated list that cannot have two consecutive commas. + kOptFlagNoDup = 1 << 12, ///< Don't allow duplicate strings. + kOptFlagFlagList = 1 << 13, ///< List of single-char flags. + kOptFlagSecure = 1 << 14, ///< Cannot change in modeline or secure mode. + kOptFlagGettext = 1 << 15, ///< Expand default value with _(). + kOptFlagNoGlob = 1 << 16, ///< Do not use local value for global vimrc. + kOptFlagNFname = 1 << 17, ///< Only normal file name chars allowed. + kOptFlagInsecure = 1 << 18, ///< Option was set from a modeline. + kOptFlagPriMkrc = 1 << 19, ///< Priority for :mkvimrc (setting option has side effects). + kOptFlagNoML = 1 << 20, ///< Not allowed in modeline. + kOptFlagCurswant = 1 << 21, ///< Update curswant required; not needed when there is a redraw flag. + kOptFlagNDname = 1 << 22, ///< Only normal directory name chars allowed. + kOptFlagHLOnly = 1 << 23, ///< Option only changes highlight, not text. + kOptFlagMLE = 1 << 24, ///< Under control of 'modelineexpr'. + kOptFlagFunc = 1 << 25, ///< Accept a function reference or a lambda. + kOptFlagColon = 1 << 26, ///< Values use colons to create sublists. +} OptFlags; + /// Option value type. /// These types are also used as type flags by using the type value as an index for the type_flags /// bit field (@see option_has_type()). @@ -62,7 +96,7 @@ typedef struct { /// New value of the option. OptValData os_newval; - /// Option value was checked to be safe, no need to set P_INSECURE + /// Option value was checked to be safe, no need to set kOptFlagInsecure /// Used for the 'keymap', 'filetype' and 'syntax' options. bool os_value_checked; /// Option value changed. Used for the 'filetype' and 'syntax' options. diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h index bfdaa11ed9..a88b51dae7 100644 --- a/src/nvim/option_vars.h +++ b/src/nvim/option_vars.h @@ -7,51 +7,6 @@ // option_vars.h: definition of global variables for settable options -// Option Flags -#define P_ALLOCED 0x01U ///< the option is in allocated memory, - ///< must use free_string_option() when - ///< assigning new value. Not set if default is - ///< the same. -#define P_EXPAND 0x02U ///< environment expansion. NOTE: P_EXPAND can - ///< never be used for local or hidden options -#define P_NO_DEF_EXP 0x04U ///< do not expand default value -#define P_NODEFAULT 0x08U ///< don't set to default value -#define P_DEF_ALLOCED 0x10U ///< default value is in allocated memory, must - ///< use free() when assigning new value -#define P_WAS_SET 0x20U ///< option has been set/reset -#define P_NO_MKRC 0x40U ///< don't include in :mkvimrc output - -// when option changed, what to display: -#define P_UI_OPTION 0x80U ///< send option to remote UI -#define P_RTABL 0x100U ///< redraw tabline -#define P_RSTAT 0x200U ///< redraw status lines -#define P_RWIN 0x400U ///< redraw current window and recompute text -#define P_RBUF 0x800U ///< redraw current buffer and recompute text -#define P_RALL 0xC00U ///< redraw all windows and recompute text -#define P_RCLR 0xE00U ///< clear and redraw all and recompute text - -#define P_COMMA 0x1000U ///< comma separated list -#define P_ONECOMMA 0x3000U ///< P_COMMA and cannot have two consecutive - ///< commas -#define P_NODUP 0x4000U ///< don't allow duplicate strings -#define P_FLAGLIST 0x8000U ///< list of single-char flags - -#define P_SECURE 0x10000U ///< cannot change in modeline or secure mode -#define P_GETTEXT 0x20000U ///< expand default value with _() -#define P_NOGLOB 0x40000U ///< do not use local value for global vimrc -#define P_NFNAME 0x80000U ///< only normal file name chars allowed -#define P_INSECURE 0x100000U ///< option was set from a modeline -#define P_PRI_MKRC 0x200000U ///< priority for :mkvimrc (setting option - ///< has side effects) -#define P_NO_ML 0x400000U ///< not allowed in modeline -#define P_CURSWANT 0x800000U ///< update curswant required; not needed - ///< when there is a redraw flag -#define P_NDNAME 0x1000000U ///< only normal dir name chars allowed -#define P_HLONLY 0x2000000U ///< option only changes highlight, not text -#define P_MLE 0x4000000U ///< under control of 'modelineexpr' -#define P_FUNC 0x8000000U ///< accept a function reference or a lambda -#define P_COLON 0x10000000U ///< values use colons to create sublists - #define HIGHLIGHT_INIT \ "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText,d:Directory,e:ErrorMsg," \ "i:IncSearch,l:Search,y:CurSearch,m:MoreMsg,M:ModeMsg,n:LineNr,a:LineNrAbove,b:LineNrBelow," \ @@ -348,6 +303,12 @@ enum { #define LISPWORD_VALUE \ "defun,define,defmacro,set!,lambda,if,case,let,flet,let*,letrec,do,do*,define-syntax,let-syntax,letrec-syntax,destructuring-bind,defpackage,defparameter,defstruct,deftype,defvar,do-all-symbols,do-external-symbols,do-symbols,dolist,dotimes,ecase,etypecase,eval-when,labels,macrolet,multiple-value-bind,multiple-value-call,multiple-value-prog1,multiple-value-setq,prog1,progv,typecase,unless,unwind-protect,when,with-input-from-string,with-open-file,with-open-stream,with-output-to-string,with-package-iterator,define-condition,handler-bind,handler-case,restart-bind,restart-case,with-simple-restart,store-value,use-value,muffle-warning,abort,continue,with-slots,with-slots*,with-accessors,with-accessors*,defclass,defmethod,print-unreadable-object" +// When a string option is NULL, it is set to empty_string_option, +// to avoid having to check for NULL everywhere. +// +// TODO(famiu): Remove this when refcounted strings are used for string options. +EXTERN char empty_string_option[] INIT( = ""); + // The following are actual variables for the options EXTERN char *p_ambw; ///< 'ambiwidth' @@ -490,6 +451,7 @@ EXTERN char *p_ffs; ///< 'fileformats' EXTERN int p_fic; ///< 'fileignorecase' EXTERN char *p_ft; ///< 'filetype' EXTERN char *p_fcs; ///< 'fillchar' +EXTERN char *p_fexpr; ///< 'findexpr' EXTERN int p_fixeol; ///< 'fixendofline' EXTERN char *p_fcl; ///< 'foldclose' EXTERN OptInt p_fdls; ///< 'foldlevelstart' @@ -770,7 +732,7 @@ EXTERN unsigned ve_flags; #define VE_NONEU 32U // "NONE" EXTERN OptInt p_verbose; ///< 'verbose' #ifdef IN_OPTION_C -char *p_vfile = ""; ///< used before options are initialized +char *p_vfile = empty_string_option; ///< used before options are initialized #else extern char *p_vfile; ///< 'verbosefile' #endif diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 734d9a4a02..d59958eaf2 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -8,8 +8,7 @@ --- @field short_desc? string|fun(): string --- @field varname? string --- @field pv_name? string ---- @field type 'boolean'|'number'|'string' ---- @field hidden? boolean +--- @field type vim.option_type|vim.option_type[] --- @field immutable? boolean --- @field list? 'comma'|'onecomma'|'commacolon'|'onecommacolon'|'flags'|'flagscomma' --- @field scope vim.option_scope[] @@ -43,6 +42,8 @@ --- @field meta? integer|boolean|string Default to use in Lua meta files --- @alias vim.option_scope 'global'|'buffer'|'window' +--- @alias vim.option_type 'boolean'|'number'|'string' +--- @alias vim.option_value boolean|number|string --- @alias vim.option_redraw --- |'statuslines' @@ -61,18 +62,11 @@ local function cstr(s) end --- @param s string ---- @return fun(): string -local function macros(s) - return function() - return '.string=' .. s - end -end - ---- @param s string ---- @return fun(): string -local function imacros(s) +--- @param t vim.option_type +--- @return fun(): string, vim.option_type +local function macros(s, t) return function() - return '.number=' .. s + return s, t end end @@ -741,7 +735,6 @@ return { }, { abbreviation = 'briopt', - alloced = true, cb = 'did_set_breakindentopt', defaults = { if_true = '' }, deny_duplicates = true, @@ -804,7 +797,6 @@ return { }, { abbreviation = 'bh', - alloced = true, cb = 'did_set_bufhidden', defaults = { if_true = '' }, desc = [=[ @@ -857,7 +849,6 @@ return { }, { abbreviation = 'bt', - alloced = true, cb = 'did_set_buftype', defaults = { if_true = '' }, desc = [=[ @@ -994,7 +985,7 @@ return { { cb = 'did_set_cedit', defaults = { - if_true = macros('CTRL_F_STR'), + if_true = macros('CTRL_F_STR', 'string'), doc = 'CTRL-F', }, desc = [=[ @@ -1109,7 +1100,6 @@ return { }, { abbreviation = 'cink', - alloced = true, defaults = { if_true = '0{,0},0),0],:,0#,!^F,o,O,e' }, deny_duplicates = true, desc = [=[ @@ -1128,7 +1118,6 @@ return { }, { abbreviation = 'cino', - alloced = true, cb = 'did_set_cinoptions', defaults = { if_true = '' }, deny_duplicates = true, @@ -1146,7 +1135,6 @@ return { }, { abbreviation = 'cinsd', - alloced = true, defaults = { if_true = 'public,protected,private' }, deny_duplicates = true, desc = [=[ @@ -1165,7 +1153,6 @@ return { }, { abbreviation = 'cinw', - alloced = true, defaults = { if_true = 'if,else,while,do,for,switch' }, deny_duplicates = true, desc = [=[ @@ -1288,7 +1275,7 @@ return { abbreviation = 'co', cb = 'did_set_lines_or_columns', defaults = { - if_true = imacros('DFLT_COLS'), + if_true = macros('DFLT_COLS', 'number'), doc = '80 or terminal width', }, desc = [=[ @@ -1315,7 +1302,6 @@ return { }, { abbreviation = 'com', - alloced = true, cb = 'did_set_comments', defaults = { if_true = 's1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-,fb:•' }, deny_duplicates = true, @@ -1334,7 +1320,6 @@ return { }, { abbreviation = 'cms', - alloced = true, cb = 'did_set_commentstring', defaults = { if_true = '' }, desc = [=[ @@ -1360,7 +1345,6 @@ return { }, { abbreviation = 'cpt', - alloced = true, cb = 'did_set_complete', defaults = { if_true = '.,w,b,u,t' }, deny_duplicates = true, @@ -1409,7 +1393,6 @@ return { }, { abbreviation = 'cfu', - alloced = true, cb = 'did_set_completefunc', defaults = { if_true = '' }, desc = [=[ @@ -1531,7 +1514,6 @@ return { }, { abbreviation = 'cocu', - alloced = true, cb = 'did_set_concealcursor', defaults = { if_true = '' }, desc = [=[ @@ -1630,7 +1612,7 @@ return { { abbreviation = 'cpo', cb = 'did_set_cpoptions', - defaults = { if_true = macros('CPO_VIM') }, + defaults = { if_true = macros('CPO_VIM', 'string') }, desc = [=[ A sequence of single character flags. When a character is present this indicates Vi-compatible behavior. This is used for things where @@ -1974,7 +1956,6 @@ return { }, { abbreviation = 'def', - alloced = true, defaults = { if_true = '' }, desc = [=[ Pattern to be used to find a macro definition. It is a search @@ -2094,7 +2075,6 @@ return { }, { abbreviation = 'dip', - alloced = true, cb = 'did_set_diffopt', defaults = { if_true = 'internal,filler,closeoff' }, deny_duplicates = true, @@ -2368,7 +2348,7 @@ return { { abbreviation = 'enc', cb = 'did_set_encoding', - defaults = { if_true = macros('ENC_DFLT') }, + defaults = { if_true = macros('ENC_DFLT', 'string') }, deny_in_modelines = true, desc = [=[ String-encoding used internally and for |RPC| communication. @@ -2492,7 +2472,7 @@ return { }, { abbreviation = 'ef', - defaults = { if_true = macros('DFLT_ERRORFILE') }, + defaults = { if_true = macros('DFLT_ERRORFILE', 'string') }, desc = [=[ Name of the errorfile for the QuickFix mode (see |:cf|). When the "-q" command-line argument is used, 'errorfile' is set to the @@ -2514,7 +2494,7 @@ return { { abbreviation = 'efm', defaults = { - if_true = macros('DFLT_EFM'), + if_true = macros('DFLT_EFM', 'string'), doc = 'is very long', }, deny_duplicates = true, @@ -2589,7 +2569,6 @@ return { }, { abbreviation = 'fenc', - alloced = true, cb = 'did_set_encoding', defaults = { if_true = '' }, desc = [=[ @@ -2703,10 +2682,9 @@ return { }, { abbreviation = 'ff', - alloced = true, cb = 'did_set_fileformat', defaults = { - if_true = macros('DFLT_FF'), + if_true = macros('DFLT_FF', 'string'), doc = 'Windows: "dos", Unix: "unix"', }, desc = [=[ @@ -2739,7 +2717,7 @@ return { abbreviation = 'ffs', cb = 'did_set_fileformats', defaults = { - if_true = macros('DFLT_FFS_VIM'), + if_true = macros('DFLT_FFS_VIM', 'string'), doc = 'Windows: "dos,unix", Unix: "unix,dos"', }, deny_duplicates = true, @@ -2819,7 +2797,6 @@ return { }, { abbreviation = 'ft', - alloced = true, cb = 'did_set_filetype_or_syntax', defaults = { if_true = '' }, desc = [=[ @@ -2855,7 +2832,6 @@ return { }, { abbreviation = 'fcs', - alloced = true, cb = 'did_set_chars_option', defaults = { if_true = '' }, deny_duplicates = true, @@ -2930,6 +2906,65 @@ return { varname = 'p_fcs', }, { + abbreviation = 'fexpr', + cb = 'did_set_optexpr', + defaults = { if_true = '' }, + desc = [=[ + Expression that is evaluated to obtain the filename(s) for the |:find| + command. When this option is empty, the internal |file-searching| + mechanism is used. + + While evaluating the expression, the |v:fname| variable is set to the + argument of the |:find| command. + + The expression is evaluated only once per |:find| command invocation. + The expression can process all the directories specified in 'path'. + + The expression may be evaluated for command-line completion as well, + in which case the |v:cmdcomplete| variable will be set to |v:true|, + otherwise it will be set to |v:false|. + + If a match is found, the expression should return a |List| containing + one or more file names. If a match is not found, the expression + should return an empty List. + + If any errors are encountered during the expression evaluation, an + empty List is used as the return value. + + Using a function call without arguments is faster |expr-option-function| + + It is not allowed to change text or jump to another window while + evaluating 'findexpr' |textlock|. + + This option cannot be set from a |modeline| or in the |sandbox|, for + security reasons. + + Examples: + >vim + " Use glob() + func FindExprGlob() + let pat = v:cmdcomplete ? $'{v:fname}*' : v:fname + return glob(pat, v:false, v:true) + endfunc + set findexpr=FindExprGlob() + + " Use the 'git ls-files' output + func FindGitFiles() + let fnames = systemlist('git ls-files') + return fnames->filter('v:val =~? v:fname') + endfunc + set findexpr=FindGitFiles() + < + ]=], + full_name = 'findexpr', + scope = { 'global', 'buffer' }, + secure = true, + short_desc = N_('expression used for :find'), + tags = { 'E1514' }, + type = 'string', + varname = 'p_fexpr', + }, + { abbreviation = 'fixeol', cb = 'did_set_eof_eol_fixeol_bomb', defaults = { if_true = true }, @@ -2970,7 +3005,6 @@ return { }, { abbreviation = 'fdc', - alloced = true, cb = 'did_set_foldcolumn', defaults = { if_true = '0' }, desc = [=[ @@ -3009,7 +3043,6 @@ return { }, { abbreviation = 'fde', - alloced = true, cb = 'did_set_foldexpr', defaults = { if_true = '0' }, desc = [=[ @@ -3035,7 +3068,6 @@ return { }, { abbreviation = 'fdi', - alloced = true, cb = 'did_set_foldignore', defaults = { if_true = '#' }, desc = [=[ @@ -3090,7 +3122,6 @@ return { }, { abbreviation = 'fmr', - alloced = true, cb = 'did_set_foldmarker', defaults = { if_true = '{{{,}}}' }, deny_duplicates = true, @@ -3110,7 +3141,6 @@ return { }, { abbreviation = 'fdm', - alloced = true, cb = 'did_set_foldmethod', defaults = { if_true = 'manual' }, desc = [=[ @@ -3211,7 +3241,6 @@ return { }, { abbreviation = 'fdt', - alloced = true, cb = 'did_set_optexpr', defaults = { if_true = 'foldtext()' }, desc = [=[ @@ -3239,7 +3268,6 @@ return { }, { abbreviation = 'fex', - alloced = true, cb = 'did_set_optexpr', defaults = { if_true = '' }, desc = [=[ @@ -3293,7 +3321,6 @@ return { }, { abbreviation = 'flp', - alloced = true, defaults = { if_true = '^\\s*\\d\\+[\\]:.)}\\t ]\\s*' }, desc = [=[ A pattern that is used to recognize a list header. This is used for @@ -3314,9 +3341,8 @@ return { }, { abbreviation = 'fo', - alloced = true, cb = 'did_set_formatoptions', - defaults = { if_true = macros('DFLT_FO_VIM') }, + defaults = { if_true = macros('DFLT_FO_VIM', 'string') }, desc = [=[ This is a sequence of letters which describes how automatic formatting is to be done. @@ -3409,7 +3435,7 @@ return { }, { abbreviation = 'gfm', - defaults = { if_true = macros('DFLT_GREPFORMAT') }, + defaults = { if_true = macros('DFLT_GREPFORMAT', 'string') }, deny_duplicates = true, desc = [=[ Format to recognize for the ":grep" command output. @@ -3773,6 +3799,7 @@ return { }, { abbreviation = 'gtl', + defaults = { if_true = '' }, desc = [=[ When non-empty describes the text to use in a label of the GUI tab pages line. When empty and when the result is empty Vim will use a @@ -3798,6 +3825,7 @@ return { }, { abbreviation = 'gtt', + defaults = { if_true = '' }, desc = [=[ When non-empty describes the text to use in a tooltip for the GUI tab pages line. When empty Vim will use a default tooltip. @@ -3817,7 +3845,7 @@ return { abbreviation = 'hf', cb = 'did_set_helpfile', defaults = { - if_true = macros('DFLT_HELPFILE'), + if_true = macros('DFLT_HELPFILE', 'string'), doc = [[(MS-Windows) "$VIMRUNTIME\doc\help.txt" (others) "$VIMRUNTIME/doc/help.txt"]], }, @@ -3914,7 +3942,7 @@ return { { abbreviation = 'hl', cb = 'did_set_highlight', - defaults = { if_true = macros('HIGHLIGHT_INIT') }, + defaults = { if_true = macros('HIGHLIGHT_INIT', 'string') }, deny_duplicates = true, full_name = 'highlight', list = 'onecomma', @@ -4080,7 +4108,7 @@ return { { abbreviation = 'imi', cb = 'did_set_iminsert', - defaults = { if_true = imacros('B_IMODE_NONE') }, + defaults = { if_true = macros('B_IMODE_NONE', 'number') }, desc = [=[ Specifies whether :lmap or an Input Method (IM) is to be used in Insert mode. Valid values: @@ -4106,7 +4134,7 @@ return { }, { abbreviation = 'ims', - defaults = { if_true = imacros('B_IMODE_USE_INSERT') }, + defaults = { if_true = macros('B_IMODE_USE_INSERT', 'number') }, desc = [=[ Specifies whether :lmap or an Input Method (IM) is to be used when entering a search pattern. Valid values: @@ -4155,7 +4183,6 @@ return { }, { abbreviation = 'inc', - alloced = true, defaults = { if_true = '' }, desc = [=[ Pattern to be used to find an include command. It is a search @@ -4177,7 +4204,6 @@ return { }, { abbreviation = 'inex', - alloced = true, cb = 'did_set_optexpr', defaults = { if_true = '' }, desc = [=[ @@ -4262,7 +4288,6 @@ return { }, { abbreviation = 'inde', - alloced = true, cb = 'did_set_optexpr', defaults = { if_true = '' }, desc = [=[ @@ -4316,7 +4341,6 @@ return { }, { abbreviation = 'indk', - alloced = true, defaults = { if_true = '0{,0},0),0],:,0#,!^F,o,O,e' }, deny_duplicates = true, desc = [=[ @@ -4458,7 +4482,6 @@ return { }, { abbreviation = 'isk', - alloced = true, cb = 'did_set_iskeyword', defaults = { if_true = '@,48-57,_,192-255' }, deny_duplicates = true, @@ -4567,7 +4590,6 @@ return { }, { abbreviation = 'kmp', - alloced = true, cb = 'did_set_keymap', defaults = { if_true = '' }, desc = [=[ @@ -4812,7 +4834,7 @@ return { { cb = 'did_set_lines_or_columns', defaults = { - if_true = imacros('DFLT_ROWS'), + if_true = macros('DFLT_ROWS', 'number'), doc = '24 or terminal height', }, desc = [=[ @@ -4900,7 +4922,7 @@ return { { abbreviation = 'lw', defaults = { - if_true = macros('LISPWORD_VALUE'), + if_true = macros('LISPWORD_VALUE', 'string'), doc = 'is very long', }, deny_duplicates = true, @@ -4944,7 +4966,6 @@ return { }, { abbreviation = 'lcs', - alloced = true, cb = 'did_set_chars_option', defaults = { if_true = 'tab:> ,trail:-,nbsp:+' }, deny_duplicates = true, @@ -5166,7 +5187,6 @@ return { }, { abbreviation = 'mps', - alloced = true, cb = 'did_set_matchpairs', defaults = { if_true = '(:),{:},[:]' }, deny_duplicates = true, @@ -5210,7 +5230,7 @@ return { }, { abbreviation = 'mco', - defaults = { if_true = imacros('MAX_MCO') }, + defaults = { if_true = macros('MAX_MCO', 'number') }, full_name = 'maxcombine', scope = { 'global' }, short_desc = N_('maximum nr of combining characters displayed'), @@ -5734,7 +5754,6 @@ return { }, { abbreviation = 'nf', - alloced = true, cb = 'did_set_nrformats', defaults = { if_true = 'bin,hex' }, deny_duplicates = true, @@ -5845,7 +5864,6 @@ return { }, { abbreviation = 'ofu', - alloced = true, cb = 'did_set_omnifunc', defaults = { if_true = '' }, desc = [=[ @@ -6229,7 +6247,6 @@ return { }, { abbreviation = 'qe', - alloced = true, defaults = { if_true = '\\' }, desc = [=[ The characters that are used to escape quotes in a string. Used for @@ -6440,7 +6457,6 @@ return { }, { abbreviation = 'rlc', - alloced = true, cb = 'did_set_rightleftcmd', defaults = { if_true = 'search' }, desc = [=[ @@ -6495,7 +6511,6 @@ return { }, { abbreviation = 'ruf', - alloced = true, cb = 'did_set_rulerformat', defaults = { if_true = '' }, desc = [=[ @@ -7644,7 +7659,6 @@ return { }, { abbreviation = 'scl', - alloced = true, cb = 'did_set_signcolumn', defaults = { if_true = 'auto' }, desc = [=[ @@ -7801,7 +7815,6 @@ return { }, { abbreviation = 'spc', - alloced = true, cb = 'did_set_spellcapcheck', defaults = { if_true = '[.?!]\\_[\\])\'"\\t ]\\+' }, desc = [=[ @@ -7824,7 +7837,6 @@ return { }, { abbreviation = 'spf', - alloced = true, cb = 'did_set_spellfile', defaults = { if_true = '' }, deny_duplicates = true, @@ -7862,7 +7874,6 @@ return { }, { abbreviation = 'spl', - alloced = true, cb = 'did_set_spelllang', defaults = { if_true = 'en' }, deny_duplicates = true, @@ -8096,7 +8107,6 @@ return { }, { abbreviation = 'stc', - alloced = true, cb = 'did_set_statuscolumn', defaults = { if_true = '' }, desc = [=[ @@ -8162,7 +8172,6 @@ return { }, { abbreviation = 'stl', - alloced = true, cb = 'did_set_statusline', defaults = { if_true = '' }, desc = [=[ @@ -8411,7 +8420,6 @@ return { }, { abbreviation = 'sua', - alloced = true, defaults = { if_true = '' }, deny_duplicates = true, desc = [=[ @@ -8520,7 +8528,6 @@ return { }, { abbreviation = 'syn', - alloced = true, cb = 'did_set_filetype_or_syntax', defaults = { if_true = '' }, desc = [=[ @@ -9007,7 +9014,6 @@ return { }, { abbreviation = 'tsrfu', - alloced = true, cb = 'did_set_thesaurusfunc', defaults = { if_true = '' }, desc = [=[ @@ -9613,7 +9619,7 @@ return { abbreviation = 'wc', cb = 'did_set_wildchar', defaults = { - if_true = imacros('TAB'), + if_true = macros('TAB', 'number'), doc = '<Tab>', }, desc = [=[ @@ -9867,7 +9873,6 @@ return { }, { abbreviation = 'wbr', - alloced = true, cb = 'did_set_winbar', defaults = { if_true = '' }, desc = [=[ @@ -10010,7 +10015,6 @@ return { }, { abbreviation = 'winhl', - alloced = true, cb = 'did_set_winhighlight', defaults = { if_true = '' }, deny_duplicates = true, diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 21a3a3877b..c66849800c 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -233,6 +233,7 @@ void check_buf_options(buf_T *buf) check_string_option(&buf->b_p_mp); check_string_option(&buf->b_p_efm); check_string_option(&buf->b_p_ep); + check_string_option(&buf->b_p_fexpr); check_string_option(&buf->b_p_path); check_string_option(&buf->b_p_tags); check_string_option(&buf->b_p_tfu); @@ -250,7 +251,6 @@ void check_buf_options(buf_T *buf) /// Free the string allocated for an option. /// Checks for the string being empty_string_option. This may happen if we're out of memory, /// xstrdup() returned NULL, which was replaced by empty_string_option by check_options(). -/// Does NOT check for P_ALLOCED flag! void free_string_option(char *p) { if (p != empty_string_option) { @@ -420,9 +420,9 @@ const char *check_stl_option(char *s) /// often illegal in a file name. Be more permissive if "secure" is off. bool check_illegal_path_names(char *val, uint32_t flags) { - return (((flags & P_NFNAME) + return (((flags & kOptFlagNFname) && strpbrk(val, (secure ? "/\\*?[|;&<>\r\n" : "/\\*?[<>\r\n")) != NULL) - || ((flags & P_NDNAME) + || ((flags & kOptFlagNDname) && strpbrk(val, "*?[|;&<>\r\n") != NULL)); } @@ -1378,7 +1378,7 @@ const char *did_set_filetype_or_syntax(optset_T *args) args->os_value_changed = strcmp(args->os_oldval.string.data, *varp) != 0; - // Since we check the value, there is no need to set P_INSECURE, + // Since we check the value, there is no need to set kOptFlagInsecure, // even when the value comes from a modeline. args->os_value_checked = true; @@ -1659,7 +1659,7 @@ const char *did_set_keymap(optset_T *args) secure = secure_save; - // Since we check the value, there is no need to set P_INSECURE, + // Since we check the value, there is no need to set kOptFlagInsecure, // even when the value comes from a modeline. args->os_value_checked = true; @@ -1886,8 +1886,9 @@ int expand_set_nrformats(optexpand_T *args, int *numMatches, char ***matches) matches); } -/// One of the '*expr' options is changed:, 'diffexpr', 'foldexpr', 'foldtext', -/// 'formatexpr', 'includeexpr', 'indentexpr', 'patchexpr' and 'charconvert'. +/// One of the '*expr' options is changed:, 'diffexpr', 'findexpr', +/// 'foldexpr', 'foldtext', 'formatexpr', 'includeexpr', 'indentexpr', +/// 'patchexpr' and 'charconvert'. const char *did_set_optexpr(optset_T *args) { char **varp = (char **)args->os_varp; diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index 87ef6a27ad..529d65c5dc 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -1358,14 +1358,15 @@ static void pum_select_mouse_pos(void) if (mouse_grid == pum_grid.handle) { pum_selected = mouse_row; return; - } else if (mouse_grid != pum_anchor_grid) { + } else if (mouse_grid != pum_anchor_grid || mouse_col < pum_grid.comp_col + || mouse_col >= pum_grid.comp_col + pum_grid.comp_width) { pum_selected = -1; return; } - int idx = mouse_row - pum_row; + int idx = mouse_row - pum_grid.comp_row; - if (idx < 0 || idx >= pum_height) { + if (idx < 0 || idx >= pum_grid.comp_height) { pum_selected = -1; } else if (*pum_array[idx].pum_text != NUL) { pum_selected = idx; diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 5bd81ce469..f037d5d924 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -7348,7 +7348,6 @@ void ex_helpgrep(exarg_T *eap) bool updated = false; // Make 'cpoptions' empty, the 'l' flag should not be used here. char *const save_cpo = p_cpo; - const bool save_cpo_allocated = (get_option(kOptCpoptions)->flags & P_ALLOCED); p_cpo = empty_string_option; bool new_qi = false; @@ -7388,9 +7387,7 @@ void ex_helpgrep(exarg_T *eap) if (*p_cpo == NUL) { set_option_value_give_err(kOptCpoptions, CSTR_AS_OPTVAL(save_cpo), 0); } - if (save_cpo_allocated) { - free_string_option(save_cpo); - } + free_string_option(save_cpo); } if (updated) { diff --git a/src/nvim/vvars.lua b/src/nvim/vvars.lua index 2f43f8b32b..6c6edd4ee2 100644 --- a/src/nvim/vvars.lua +++ b/src/nvim/vvars.lua @@ -41,6 +41,22 @@ M.vars = { included here, because it will be executed anyway. ]=], }, + cmdbang = { + type = 'integer', + desc = [=[ + Set like v:cmdarg for a file read/write command. When a "!" + was used the value is 1, otherwise it is 0. Note that this + can only be used in autocommands. For user commands |<bang>| + can be used. + ]=], + }, + cmdcomplete = { + type = 'boolean', + desc = [=[ + When evaluating 'findexpr': if 'findexpr' is used for cmdline + completion the value is |v:true|, otherwise it is |v:false|. + ]=], + }, collate = { type = 'string', desc = [=[ @@ -53,15 +69,6 @@ M.vars = { See |multi-lang|. ]=], }, - cmdbang = { - type = 'integer', - desc = [=[ - Set like v:cmdarg for a file read/write command. When a "!" - was used the value is 1, otherwise it is 0. Note that this - can only be used in autocommands. For user commands |<bang>| - can be used. - ]=], - }, completed_item = { desc = [=[ Dictionary containing the |complete-items| for the most @@ -118,15 +125,6 @@ M.vars = { VimLeave autocommands will not be executed. ]=], }, - exiting = { - desc = [=[ - Exit code, or |v:null| before invoking the |VimLeavePre| - and |VimLeave| autocmds. See |:q|, |:x| and |:cquit|. - Example: >vim - :au VimLeave * echo "Exit value is " .. v:exiting - < - ]=], - }, echospace = { type = 'integer', desc = [=[ @@ -243,18 +241,13 @@ M.vars = { or |expr7| when used with numeric operators). Read-only. ]=], }, - fcs_reason = { - type = 'string', + exiting = { desc = [=[ - The reason why the |FileChangedShell| event was triggered. - Can be used in an autocommand to decide what to do and/or what - to set v:fcs_choice to. Possible values: - deleted file no longer exists - conflict file contents, mode or timestamp was - changed and buffer is modified - changed file contents has changed - mode mode of file changed - time only file timestamp changed + Exit code, or |v:null| before invoking the |VimLeavePre| + and |VimLeave| autocmds. See |:q|, |:x| and |:cquit|. + Example: >vim + :au VimLeave * echo "Exit value is " .. v:exiting + < ]=], }, fcs_choice = { @@ -280,11 +273,33 @@ M.vars = { Vim behaves like it is empty, there is no warning message. ]=], }, + fcs_reason = { + type = 'string', + desc = [=[ + The reason why the |FileChangedShell| event was triggered. + Can be used in an autocommand to decide what to do and/or what + to set v:fcs_choice to. Possible values: + deleted file no longer exists + conflict file contents, mode or timestamp was + changed and buffer is modified + changed file contents has changed + mode mode of file changed + time only file timestamp changed + ]=], + }, fname = { type = 'string', desc = [=[ When evaluating 'includeexpr': the file name that was - detected. Empty otherwise. + detected. When evaluating 'findexpr': the argument passed to + the |:find| command. Empty otherwise. + ]=], + }, + fname_diff = { + type = 'string', + desc = [=[ + The name of the diff (patch) file. Only valid while + evaluating 'patchexpr'. ]=], }, fname_in = { @@ -298,6 +313,13 @@ M.vars = { And set to the swap file name for |SwapExists|. ]=], }, + fname_new = { + type = 'string', + desc = [=[ + The name of the new version of the file. Only valid while + evaluating 'diffexpr'. + ]=], + }, fname_out = { type = 'string', desc = [=[ @@ -313,20 +335,6 @@ M.vars = { file and different from v:fname_in. ]=], }, - fname_new = { - type = 'string', - desc = [=[ - The name of the new version of the file. Only valid while - evaluating 'diffexpr'. - ]=], - }, - fname_diff = { - type = 'string', - desc = [=[ - The name of the diff (patch) file. Only valid while - evaluating 'patchexpr'. - ]=], - }, folddashes = { type = 'string', desc = [=[ @@ -335,17 +343,17 @@ M.vars = { Read-only in the |sandbox|. |fold-foldtext| ]=], }, - foldlevel = { + foldend = { type = 'integer', desc = [=[ - Used for 'foldtext': foldlevel of closed fold. + Used for 'foldtext': last line of closed fold. Read-only in the |sandbox|. |fold-foldtext| ]=], }, - foldend = { + foldlevel = { type = 'integer', desc = [=[ - Used for 'foldtext': last line of closed fold. + Used for 'foldtext': foldlevel of closed fold. Read-only in the |sandbox|. |fold-foldtext| ]=], }, @@ -435,19 +443,12 @@ M.vars = { 2147483647 on all systems. ]=], }, - mouse_win = { - type = 'integer', - desc = [=[ - Window number for a mouse click obtained with |getchar()|. - First window has number 1, like with |winnr()|. The value is - zero when there was no mouse button click. - ]=], - }, - mouse_winid = { + mouse_col = { type = 'integer', desc = [=[ - |window-ID| for a mouse click obtained with |getchar()|. - The value is zero when there was no mouse button click. + Column number for a mouse click obtained with |getchar()|. + This is the screen column number, like with |virtcol()|. The + value is zero when there was no mouse button click. ]=], }, mouse_lnum = { @@ -458,12 +459,19 @@ M.vars = { value is zero when there was no mouse button click. ]=], }, - mouse_col = { + mouse_win = { type = 'integer', desc = [=[ - Column number for a mouse click obtained with |getchar()|. - This is the screen column number, like with |virtcol()|. The - value is zero when there was no mouse button click. + Window number for a mouse click obtained with |getchar()|. + First window has number 1, like with |winnr()|. The value is + zero when there was no mouse button click. + ]=], + }, + mouse_winid = { + type = 'integer', + desc = [=[ + |window-ID| for a mouse click obtained with |getchar()|. + The value is zero when there was no mouse button click. ]=], }, msgpack_types = { @@ -516,6 +524,35 @@ M.vars = { than String this will cause trouble. ]=], }, + operator = { + type = 'string', + desc = [=[ + The last operator given in Normal mode. This is a single + character except for commands starting with <g> or <z>, + in which case it is two characters. Best used alongside + |v:prevcount| and |v:register|. Useful if you want to cancel + Operator-pending mode and then use the operator, e.g.: >vim + :omap O <Esc>:call MyMotion(v:operator)<CR> + < + The value remains set until another operator is entered, thus + don't expect it to be empty. + v:operator is not set for |:delete|, |:yank| or other Ex + commands. + Read-only. + ]=], + }, + option_command = { + type = 'string', + desc = [=[ + Command used to set the option. Valid while executing an + |OptionSet| autocommand. + value option was set via ~ + "setlocal" |:setlocal| or `:let l:xxx` + "setglobal" |:setglobal| or `:let g:xxx` + "set" |:set| or |:let| + "modeline" |modeline| + ]=], + }, option_new = { desc = [=[ New value of the option. Valid while executing an |OptionSet| @@ -530,15 +567,15 @@ M.vars = { global old value. ]=], }, - option_oldlocal = { + option_oldglobal = { desc = [=[ - Old local value of the option. Valid while executing an + Old global value of the option. Valid while executing an |OptionSet| autocommand. ]=], }, - option_oldglobal = { + option_oldlocal = { desc = [=[ - Old global value of the option. Valid while executing an + Old local value of the option. Valid while executing an |OptionSet| autocommand. ]=], }, @@ -549,35 +586,6 @@ M.vars = { |OptionSet| autocommand. Can be either "global" or "local" ]=], }, - option_command = { - type = 'string', - desc = [=[ - Command used to set the option. Valid while executing an - |OptionSet| autocommand. - value option was set via ~ - "setlocal" |:setlocal| or `:let l:xxx` - "setglobal" |:setglobal| or `:let g:xxx` - "set" |:set| or |:let| - "modeline" |modeline| - ]=], - }, - operator = { - type = 'string', - desc = [=[ - The last operator given in Normal mode. This is a single - character except for commands starting with <g> or <z>, - in which case it is two characters. Best used alongside - |v:prevcount| and |v:register|. Useful if you want to cancel - Operator-pending mode and then use the operator, e.g.: >vim - :omap O <Esc>:call MyMotion(v:operator)<CR> - < - The value remains set until another operator is entered, thus - don't expect it to be empty. - v:operator is not set for |:delete|, |:yank| or other Ex - commands. - Read-only. - ]=], - }, prevcount = { type = 'integer', desc = [=[ @@ -641,6 +649,17 @@ M.vars = { hit-enter prompt. ]=], }, + searchforward = { + type = 'integer', + desc = [=[ + Search direction: 1 after a forward search, 0 after a + backward search. It is reset to forward when directly setting + the last search pattern, see |quote/|. + Note that the value is restored when returning from a + function. |function-search-undo|. + Read-write. + ]=], + }, servername = { type = 'string', desc = [=[ @@ -664,17 +683,6 @@ M.vars = { Note the contents of $NVIM may change in the future. ]=], }, - searchforward = { - type = 'integer', - desc = [=[ - Search direction: 1 after a forward search, 0 after a - backward search. It is reset to forward when directly setting - the last search pattern, see |quote/|. - Note that the value is restored when returning from a - function. |function-search-undo|. - Read-write. - ]=], - }, shell_error = { type = 'integer', desc = [=[ @@ -709,14 +717,6 @@ M.vars = { < ]=], }, - swapname = { - type = 'string', - desc = [=[ - Name of the swapfile found. - Only valid during |SwapExists| event. - Read-only. - ]=], - }, swapchoice = { type = 'string', desc = [=[ @@ -743,6 +743,14 @@ M.vars = { For ":edit +cmd file" the value is ":cmd\r". ]=], }, + swapname = { + type = 'string', + desc = [=[ + Name of the swapfile found. + Only valid during |SwapExists| event. + Read-only. + ]=], + }, t_blob = { type = 'integer', tags = { 'v:t_TYPE' }, @@ -776,22 +784,22 @@ M.vars = { type = 'integer', desc = 'Value of |String| type. Read-only. See: |type()|', }, - termresponse = { + termrequest = { type = 'string', desc = [=[ The value of the most recent OSC or DCS control sequence - received by Nvim from the terminal. This can be read in a - |TermResponse| event handler after querying the terminal using - another escape sequence. + sent from a process running in the embedded |terminal|. + This can be read in a |TermRequest| event handler to respond + to queries from embedded applications. ]=], }, - termrequest = { + termresponse = { type = 'string', desc = [=[ The value of the most recent OSC or DCS control sequence - sent from a process running in the embedded |terminal|. - This can be read in a |TermRequest| event handler to respond - to queries from embedded applications. + received by Nvim from the terminal. This can be read in a + |TermResponse| event handler after querying the terminal using + another escape sequence. ]=], }, testing = { @@ -849,20 +857,20 @@ M.vars = { < ]=], }, - virtnum = { + vim_did_enter = { type = 'integer', desc = [=[ - Virtual line number for the 'statuscolumn' expression. - Negative when drawing the status column for virtual lines, zero - when drawing an actual buffer line, and positive when drawing - the wrapped part of a buffer line. + 0 during startup, 1 just before |VimEnter|. Read-only. ]=], }, - vim_did_enter = { + virtnum = { type = 'integer', desc = [=[ - 0 during startup, 1 just before |VimEnter|. + Virtual line number for the 'statuscolumn' expression. + Negative when drawing the status column for virtual lines, zero + when drawing an actual buffer line, and positive when drawing + the wrapped part of a buffer line. Read-only. ]=], }, diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua index 4ae1146703..eb1ac3e6a1 100644 --- a/test/functional/lua/diagnostic_spec.lua +++ b/test/functional/lua/diagnostic_spec.lua @@ -111,6 +111,19 @@ describe('vim.diagnostic', function() { details = true } ) end + + ---@param ns integer + function _G.get_underline_extmarks(ns) + ---@type integer + local underline_ns = vim.diagnostic.get_namespace(ns).user_data.underline_ns + return vim.api.nvim_buf_get_extmarks( + _G.diagnostic_bufnr, + underline_ns, + 0, + -1, + { details = true } + ) + end end) exec_lua(function() @@ -1813,6 +1826,21 @@ describe('vim.diagnostic', function() _G.make_info('Info', 4, 4, 4, 4), }) + function _G.get_highest_underline_hl(severity_sort) + vim.diagnostic.config({ + underline = true, + severity_sort = severity_sort, + }) + + local extmarks = _G.get_underline_extmarks(_G.diagnostic_ns) + + table.sort(extmarks, function(a, b) + return a[4].priority > b[4].priority + end) + + return extmarks[1][4].hl_group + end + function _G.get_virt_text_and_signs(severity_sort) vim.diagnostic.config({ severity_sort = severity_sort, @@ -1864,6 +1892,12 @@ describe('vim.diagnostic', function() result = exec_lua [[return _G.get_virt_text_and_signs({ reverse = true })]] eq({ 'Error', 'Warn', 'Info' }, result[1]) eq({ 'Info', 'Warn', 'Error' }, result[2]) + + local underline_hl = exec_lua [[return _G.get_highest_underline_hl(true)]] + eq('DiagnosticUnderlineError', underline_hl) + + underline_hl = exec_lua [[return _G.get_highest_underline_hl({ reverse = true })]] + eq('DiagnosticUnderlineInfo', underline_hl) end) it('can show diagnostic sources in virtual text', function() diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua index 78c684083b..b7e292cad0 100644 --- a/test/functional/plugin/lsp/diagnostic_spec.lua +++ b/test/functional/plugin/lsp/diagnostic_spec.lua @@ -219,13 +219,13 @@ describe('vim.lsp.diagnostic', function() eq(1, #result) eq( exec_lua(function() - return vim.str_byteindex(line, 7, true) + return vim.str_byteindex(line, 'utf-16', 7) end), result[1].col ) eq( exec_lua(function() - return vim.str_byteindex(line, 8, true) + return vim.str_byteindex(line, 'utf-16', 8) end), result[1].end_col ) diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index f128bdc961..295d677aa0 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -4506,9 +4506,10 @@ describe('builtin popupmenu', function() :let g:menustr = 'foo' | ]]) end + local no_menu_screen ---@type string|test.function.ui.screen.Expect if multigrid then api.nvim_input_mouse('left', 'press', '', 4, 1, 2) - screen:expect({ + no_menu_screen = { grid = [[ ## grid 1 [2:--------------------------------]|*2 @@ -4527,19 +4528,189 @@ describe('builtin popupmenu', function() {2:WINBAR }| ^popup menu test | ]], - }) + } else feed('<LeftMouse><31,2>') - screen:expect([[ + no_menu_screen = { + grid = [[ popup menu test | {1:~ }| {3:[No Name] [+] }| popup menu test│{2:WINBAR }| {1:~ }│^popup menu test | :let g:menustr = 'bar' | - ]]) + ]], + } end + screen:expect(no_menu_screen) eq('bar', api.nvim_get_var('menustr')) + + local no_sel_screen ---@type string|test.function.ui.screen.Expect + if multigrid then + no_sel_screen = { + grid = [[ + ## grid 1 + [2:--------------------------------]|*2 + {3:[No Name] [+] }| + [5:---------------]│[6:----------------]|*2 + [3:--------------------------------]| + ## grid 2 + popup menu test | + {1:~ }| + ## grid 3 + :let g:menustr = 'bar' | + ## grid 4 + {n: foo }| + {n: bar }| + {n: baz }| + ## grid 5 + popup menu test| + {1:~ }| + ## grid 6 + {2:WINBAR }| + ^popup menu test | + ]], + float_pos = { [4] = { -1, 'NW', 1, 1, 19, false, 250 } }, + } + else + no_sel_screen = { + grid = [[ + popup menu test | + {1:~ }{n: foo }{1: }| + {3:[No Name] [+] }{n: bar }{3: }| + popup menu test│{2:WIN}{n: baz }{2: }| + {1:~ }│^popup menu test | + :let g:menustr = 'bar' | + ]], + } + end + local sel_screens = {} ---@type (string|test.function.ui.screen.Expect)[] + for i, s in ipairs({ 'foo', 'bar', 'baz' }) do + local sel_screen = vim.deepcopy(no_sel_screen) + local grid = assert(sel_screen.grid) + grid = grid:gsub(vim.pesc(('{n: %s }'):format(s)), ('{s: %s }'):format(s)) + sel_screen.grid = grid + sel_screens[i] = sel_screen + end + + command([[let g:menustr = '']]) + local g = multigrid and 1 or 0 + + api.nvim_input_mouse('right', 'press', '', g, 0, 20) + screen:expect(no_sel_screen) + api.nvim_input_mouse('move', '', '', g, 1, 19) + screen:expect(sel_screens[1]) + api.nvim_input_mouse('move', '', '', g, 1, 18) + screen:expect(no_sel_screen) + api.nvim_input_mouse('move', '', '', g, 2, 23) + screen:expect(sel_screens[2]) + api.nvim_input_mouse('move', '', '', g, 2, 24) + screen:expect(no_sel_screen) + api.nvim_input_mouse('move', '', '', g, 3, 19) + screen:expect(sel_screens[3]) + api.nvim_input_mouse('left', 'press', '', g, 3, 18) + screen:expect(no_menu_screen) + eq('', api.nvim_get_var('menustr')) + + command('wincmd t | set rightleft') + if multigrid then + no_menu_screen = { + grid = [[ + ## grid 1 + [2:--------------------------------]|*2 + {4:[No Name] [+] }| + [5:---------------]│[6:----------------]|*2 + [3:--------------------------------]| + ## grid 2 + tset unem pupo^p| + {1: ~}| + ## grid 3 + :let g:menustr = 'bar' | + ## grid 5 + popup menu test| + {1:~ }| + ## grid 6 + {2:WINBAR }| + popup menu test | + ]], + } + else + no_menu_screen = { + grid = [[ + tset unem pupo^p| + {1: ~}| + {4:[No Name] [+] }| + popup menu test│{2:WINBAR }| + {1:~ }│popup menu test | + :let g:menustr = 'bar' | + ]], + } + end + screen:expect(no_menu_screen) + + if multigrid then + no_sel_screen = { + grid = [[ + ## grid 1 + [2:--------------------------------]|*2 + {4:[No Name] [+] }| + [5:---------------]│[6:----------------]|*2 + [3:--------------------------------]| + ## grid 2 + tset unem pupo^p| + {1: ~}| + ## grid 3 + :let g:menustr = 'bar' | + ## grid 4 + {n: oof }| + {n: rab }| + {n: zab }| + ## grid 5 + popup menu test| + {1:~ }| + ## grid 6 + {2:WINBAR }| + popup menu test | + ]], + float_pos = { [4] = { -1, 'NW', 1, 1, 17, false, 250 } }, + } + else + no_sel_screen = { + grid = [[ + tset unem pupo^p| + {1: }{n: oof }{1: ~}| + {4:[No Name] [+] }{n: rab }{4: }| + popup menu test│{2:W}{n: zab }{2: }| + {1:~ }│popup menu test | + :let g:menustr = 'bar' | + ]], + } + end + for i, s in ipairs({ 'oof', 'rab', 'zab' }) do + local sel_screen = vim.deepcopy(no_sel_screen) + local grid = assert(sel_screen.grid) + grid = grid:gsub(vim.pesc(('{n: %s }'):format(s)), ('{s: %s }'):format(s)) + sel_screen.grid = grid + sel_screens[i] = sel_screen + end + + api.nvim_input_mouse('right', 'press', '', g, 0, 20) + screen:expect(no_sel_screen) + api.nvim_input_mouse('move', '', '', g, 1, 21) + screen:expect(sel_screens[1]) + api.nvim_input_mouse('move', '', '', g, 1, 22) + screen:expect(no_sel_screen) + api.nvim_input_mouse('move', '', '', g, 2, 17) + screen:expect(sel_screens[2]) + api.nvim_input_mouse('move', '', '', g, 2, 16) + screen:expect(no_sel_screen) + api.nvim_input_mouse('move', '', '', g, 3, 21) + screen:expect(sel_screens[3]) + api.nvim_input_mouse('left', 'press', '', g, 3, 22) + screen:expect(no_menu_screen) + eq('', api.nvim_get_var('menustr')) + + command('set norightleft') end) if not multigrid then diff --git a/test/old/testdir/runtest.vim b/test/old/testdir/runtest.vim index e05a78e9ca..058635c332 100644 --- a/test/old/testdir/runtest.vim +++ b/test/old/testdir/runtest.vim @@ -174,10 +174,6 @@ func GetAllocId(name) return lnum - top - 1 endfunc -if has('reltime') - let g:func_start = reltime() -endif - " Get the list of swap files in the current directory. func s:GetSwapFileList() let save_dir = &directory @@ -567,6 +563,16 @@ for g:testfunc in sort(s:tests) " A test can set g:test_is_flaky to retry running the test. let g:test_is_flaky = 0 + " A test can set g:max_run_nr to change the max retry count. + let g:max_run_nr = 5 + if has('mac') + let g:max_run_nr = 10 + endif + + " By default, give up if the same error occurs. A test can set + " g:giveup_same_error to 0 to not give up on the same error and keep trying. + let g:giveup_same_error = 1 + let starttime = strftime("%H:%M:%S") call RunTheTest(g:testfunc) @@ -582,10 +588,15 @@ for g:testfunc in sort(s:tests) call extend(s:messages, v:errors) let endtime = strftime("%H:%M:%S") - call add(total_errors, $'Run {g:run_nr}, {starttime} - {endtime}:') + if has('reltime') + let suffix = $' in{reltimestr(reltime(g:func_start))} seconds' + else + let suffix = '' + endif + call add(total_errors, $'Run {g:run_nr}, {starttime} - {endtime}{suffix}:') call extend(total_errors, v:errors) - if g:run_nr >= 5 || prev_error == v:errors[0] + if g:run_nr >= g:max_run_nr || g:giveup_same_error && prev_error == v:errors[0] call add(total_errors, 'Flaky test failed too often, giving up') let v:errors = total_errors break @@ -596,7 +607,8 @@ for g:testfunc in sort(s:tests) " Flakiness is often caused by the system being very busy. Sleep a " couple of seconds to have a higher chance of succeeding the second " time. - sleep 2 + let delay = g:run_nr * 2 + exe 'sleep' delay let prev_error = v:errors[0] let v:errors = [] diff --git a/test/old/testdir/test_findfile.vim b/test/old/testdir/test_findfile.vim index 06d781ed69..d3fdcad045 100644 --- a/test/old/testdir/test_findfile.vim +++ b/test/old/testdir/test_findfile.vim @@ -1,5 +1,7 @@ " Test findfile() and finddir() +source check.vim + let s:files = [ 'Xfinddir1/foo', \ 'Xfinddir1/bar', \ 'Xfinddir1/Xdir2/foo', @@ -286,4 +288,223 @@ func Test_find_non_existing_path() let &path = save_path endfunc +" Test for 'findexpr' +func Test_findexpr() + CheckUnix + call assert_equal('', &findexpr) + call writefile(['aFile'], 'Xfindexpr1.c', 'D') + call writefile(['bFile'], 'Xfindexpr2.c', 'D') + call writefile(['cFile'], 'Xfindexpr3.c', 'D') + + " basic tests + func FindExpr1() + let fnames = ['Xfindexpr1.c', 'Xfindexpr2.c', 'Xfindexpr3.c'] + return fnames->copy()->filter('v:val =~? v:fname') + endfunc + + set findexpr=FindExpr1() + find Xfindexpr3 + call assert_match('Xfindexpr3.c', @%) + bw! + 2find Xfind + call assert_match('Xfindexpr2.c', @%) + bw! + call assert_fails('4find Xfind', 'E347: No more file "Xfind" found in path') + call assert_fails('find foobar', 'E345: Can''t find file "foobar" in path') + + sfind Xfindexpr2.c + call assert_match('Xfindexpr2.c', @%) + call assert_equal(2, winnr('$')) + %bw! + call assert_fails('sfind foobar', 'E345: Can''t find file "foobar" in path') + + tabfind Xfindexpr3.c + call assert_match('Xfindexpr3.c', @%) + call assert_equal(2, tabpagenr()) + %bw! + call assert_fails('tabfind foobar', 'E345: Can''t find file "foobar" in path') + + " Buffer-local option + set findexpr=['abc'] + new + setlocal findexpr=['def'] + find xxxx + call assert_equal('def', @%) + wincmd w + find xxxx + call assert_equal('abc', @%) + aboveleft new + call assert_equal("['abc']", &findexpr) + wincmd k + aboveleft new + call assert_equal("['abc']", &findexpr) + %bw! + + " Empty list + set findexpr=[] + call assert_fails('find xxxx', 'E345: Can''t find file "xxxx" in path') + + " Error cases + + " Syntax error in the expression + set findexpr=FindExpr1{} + call assert_fails('find Xfindexpr1.c', 'E15: Invalid expression') + + " Find expression throws an error + func FindExpr2() + throw 'find error' + endfunc + set findexpr=FindExpr2() + call assert_fails('find Xfindexpr1.c', 'find error') + + " Try using a null List as the expression + set findexpr=v:_null_list + call assert_fails('find Xfindexpr1.c', 'E345: Can''t find file "Xfindexpr1.c" in path') + + " Try to create a new window from the find expression + func FindExpr3() + new + return ["foo"] + endfunc + set findexpr=FindExpr3() + call assert_fails('find Xfindexpr1.c', 'E565: Not allowed to change text or change window') + + " Try to modify the current buffer from the find expression + func FindExpr4() + call setline(1, ['abc']) + return ["foo"] + endfunc + set findexpr=FindExpr4() + call assert_fails('find Xfindexpr1.c', 'E565: Not allowed to change text or change window') + + " Expression returning a string + set findexpr='abc' + call assert_fails('find Xfindexpr1.c', "E1514: 'findexpr' did not return a List type") + + set findexpr& + delfunc! FindExpr1 + delfunc! FindExpr2 + delfunc! FindExpr3 + delfunc! FindExpr4 +endfunc + +" Test for using a script-local function for 'findexpr' +func Test_findexpr_scriptlocal_func() + func! s:FindExprScript() + let g:FindExprArg = v:fname + return ['xxx'] + endfunc + + set findexpr=s:FindExprScript() + call assert_equal(expand('<SID>') .. 'FindExprScript()', &findexpr) + call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr) + new | only + let g:FindExprArg = '' + find abc + call assert_equal('abc', g:FindExprArg) + bw! + + set findexpr=<SID>FindExprScript() + call assert_equal(expand('<SID>') .. 'FindExprScript()', &findexpr) + call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr) + new | only + let g:FindExprArg = '' + find abc + call assert_equal('abc', g:FindExprArg) + bw! + + let &findexpr = 's:FindExprScript()' + call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr) + new | only + let g:FindExprArg = '' + find abc + call assert_equal('abc', g:FindExprArg) + bw! + + let &findexpr = '<SID>FindExprScript()' + call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr) + new | only + let g:FindExprArg = '' + find abc + call assert_equal('abc', g:FindExprArg) + bw! + + set findexpr= + setglobal findexpr=s:FindExprScript() + setlocal findexpr= + call assert_equal(expand('<SID>') .. 'FindExprScript()', &findexpr) + call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr) + call assert_equal('', &l:findexpr) + new | only + let g:FindExprArg = '' + find abc + call assert_equal('abc', g:FindExprArg) + bw! + + new | only + set findexpr= + setglobal findexpr= + setlocal findexpr=s:FindExprScript() + call assert_equal(expand('<SID>') .. 'FindExprScript()', &findexpr) + call assert_equal(expand('<SID>') .. 'FindExprScript()', &l:findexpr) + call assert_equal('', &g:findexpr) + let g:FindExprArg = '' + find abc + call assert_equal('abc', g:FindExprArg) + bw! + + set findexpr= + delfunc s:FindExprScript +endfunc + +" Test for expanding the argument to the :find command using 'findexpr' +func Test_findexpr_expand_arg() + let s:fnames = ['Xfindexpr1.c', 'Xfindexpr2.c', 'Xfindexpr3.c'] + + " 'findexpr' that accepts a regular expression + func FindExprRegexp() + return s:fnames->copy()->filter('v:val =~? v:fname') + endfunc + + " 'findexpr' that accepts a glob + func FindExprGlob() + let pat = glob2regpat(v:cmdcomplete ? $'*{v:fname}*' : v:fname) + return s:fnames->copy()->filter('v:val =~? pat') + endfunc + + for regexp in [v:true, v:false] + let &findexpr = regexp ? 'FindExprRegexp()' : 'FindExprGlob()' + + call feedkeys(":find \<Tab>\<C-B>\"\<CR>", "xt") + call assert_equal('"find Xfindexpr1.c', @:) + + call feedkeys(":find Xfind\<Tab>\<Tab>\<C-B>\"\<CR>", "xt") + call assert_equal('"find Xfindexpr2.c', @:) + + call assert_equal(s:fnames, getcompletion('find ', 'cmdline')) + call assert_equal(s:fnames, getcompletion('find Xfind', 'cmdline')) + + let pat = regexp ? 'X.*1\.c' : 'X*1.c' + call feedkeys($":find {pat}\<Tab>\<C-B>\"\<CR>", "xt") + call assert_equal('"find Xfindexpr1.c', @:) + call assert_equal(['Xfindexpr1.c'], getcompletion($'find {pat}', 'cmdline')) + + call feedkeys(":find 3\<Tab>\<C-B>\"\<CR>", "xt") + call assert_equal('"find Xfindexpr3.c', @:) + call assert_equal(['Xfindexpr3.c'], getcompletion($'find 3', 'cmdline')) + + call feedkeys(":find Xfind\<C-A>\<C-B>\"\<CR>", "xt") + call assert_equal('"find Xfindexpr1.c Xfindexpr2.c Xfindexpr3.c', @:) + + call feedkeys(":find abc\<Tab>\<C-B>\"\<CR>", "xt") + call assert_equal('"find abc', @:) + call assert_equal([], getcompletion('find abc', 'cmdline')) + endfor + + set findexpr& + delfunc! FindExprRegexp + delfunc! FindExprGlob + unlet s:fnames +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_map_functions.vim b/test/old/testdir/test_map_functions.vim index 8f7c8bae76..5118063b36 100644 --- a/test/old/testdir/test_map_functions.vim +++ b/test/old/testdir/test_map_functions.vim @@ -527,6 +527,25 @@ func Test_map_restore_negative_sid() call delete('Xresult') endfunc +" Check that restoring a mapping doesn't remove a mapping whose {rhs} matches +" the restored mapping's {lhs}. +func Test_map_restore_with_rhs_match_lhs() + nnoremap <F2> <F3> + nnoremap <F3> <F4> + call assert_equal('<F3>', maparg('<F2>', 'n')) + call assert_equal('<F4>', maparg('<F3>', 'n')) + let d = maparg('<F3>', 'n', v:false, v:true) + nunmap <F3> + call assert_equal('<F3>', maparg('<F2>', 'n')) + call assert_equal('', maparg('<F3>', 'n')) + call mapset(d) + call assert_equal('<F3>', maparg('<F2>', 'n')) + call assert_equal('<F4>', maparg('<F3>', 'n')) + + nunmap <F2> + nunmap <F3> +endfunc + func Test_maplist() new func s:ClearMappingsAbbreviations() diff --git a/test/old/testdir/test_modeline.vim b/test/old/testdir/test_modeline.vim index 487a89e038..7b7163d372 100644 --- a/test/old/testdir/test_modeline.vim +++ b/test/old/testdir/test_modeline.vim @@ -217,6 +217,7 @@ func Test_modeline_fails_always() call s:modeline_fails('equalprg', 'equalprg=Something()', 'E520:') call s:modeline_fails('errorfile', 'errorfile=Something()', 'E520:') call s:modeline_fails('exrc', 'exrc=Something()', 'E520:') + call s:modeline_fails('findexpr', 'findexpr=Something()', 'E520:') call s:modeline_fails('formatprg', 'formatprg=Something()', 'E520:') call s:modeline_fails('fsync', 'fsync=Something()', 'E520:') call s:modeline_fails('grepprg', 'grepprg=Something()', 'E520:') diff --git a/test/old/testdir/test_options.vim b/test/old/testdir/test_options.vim index ccc7abe2d8..6902560518 100644 --- a/test/old/testdir/test_options.vim +++ b/test/old/testdir/test_options.vim @@ -1386,7 +1386,8 @@ func Test_local_scrolloff() call assert_equal(5, &so) wincmd w call assert_equal(3, &so) - setlocal so< + "setlocal so< + set so< call assert_equal(5, &so) setglob so=8 call assert_equal(8, &so) @@ -1403,7 +1404,8 @@ func Test_local_scrolloff() call assert_equal(7, &siso) wincmd w call assert_equal(3, &siso) - setlocal siso< + "setlocal siso< + set siso< call assert_equal(7, &siso) setglob siso=4 call assert_equal(4, &siso) @@ -1557,7 +1559,7 @@ endfunc " Test for changing options in a sandbox func Test_opt_sandbox() - for opt in ['backupdir', 'cdpath', 'exrc'] + for opt in ['backupdir', 'cdpath', 'exrc', 'findexpr'] call assert_fails('sandbox set ' .. opt .. '?', 'E48:') call assert_fails('sandbox let &' .. opt .. ' = 1', 'E48:') endfor @@ -1595,17 +1597,17 @@ func Test_set_number_global_local_option() call assert_equal(12, &l:scrolloff) call assert_equal(12, &scrolloff) - " :set {option}< set the effective value of {option} to its global value. - set scrolloff< - " Nvim: local value is removed - " call assert_equal(10, &l:scrolloff) - call assert_equal(-1, &l:scrolloff) + " :setlocal {option}< set the effective value of {option} to its global value. + "set scrolloff< + setlocal scrolloff< + call assert_equal(10, &l:scrolloff) call assert_equal(10, &scrolloff) - " :setlocal {option}< removes the local value, so that the global value will be used. + " :set {option}< removes the local value, so that the global value will be used. setglobal scrolloff=15 setlocal scrolloff=18 - setlocal scrolloff< + "setlocal scrolloff< + set scrolloff< call assert_equal(-1, &l:scrolloff) call assert_equal(15, &scrolloff) @@ -1620,17 +1622,17 @@ func Test_set_boolean_global_local_option() call assert_equal(0, &l:autoread) call assert_equal(0, &autoread) - " :set {option}< set the effective value of {option} to its global value. - set autoread< - " Nvim: local value is removed - " call assert_equal(1, &l:autoread) - call assert_equal(-1, &l:autoread) + " :setlocal {option}< set the effective value of {option} to its global value. + "set autoread< + setlocal autoread< + call assert_equal(1, &l:autoread) call assert_equal(1, &autoread) - " :setlocal {option}< removes the local value, so that the global value will be used. + " :set {option}< removes the local value, so that the global value will be used. setglobal noautoread setlocal autoread - setlocal autoread< + "setlocal autoread< + set autoread< call assert_equal(-1, &l:autoread) call assert_equal(0, &autoread) diff --git a/test/old/testdir/test_undo.vim b/test/old/testdir/test_undo.vim index a207f4f4e0..d876277850 100644 --- a/test/old/testdir/test_undo.vim +++ b/test/old/testdir/test_undo.vim @@ -187,7 +187,8 @@ func Test_global_local_undolevels() " Resetting the local 'undolevels' value to use the global value setlocal undolevels=5 - setlocal undolevels< + "setlocal undolevels< + set undolevels< call assert_equal(-123456, &l:undolevels) " Drop created windows |