diff options
author | Lewis Russell <lewis6991@gmail.com> | 2025-01-10 10:20:43 +0000 |
---|---|---|
committer | Lewis Russell <me@lewisr.dev> | 2025-01-13 16:58:25 +0000 |
commit | 34e2185022ab698827b72751d77e218a1b6b6afe (patch) | |
tree | 9b8c0fe0a24b77a60e1e6511cfb3e2135b7789af | |
parent | cb7b4e296238b46025de05203c886d67da401728 (diff) | |
download | rneovim-34e2185022ab698827b72751d77e218a1b6b6afe.tar.gz rneovim-34e2185022ab698827b72751d77e218a1b6b6afe.tar.bz2 rneovim-34e2185022ab698827b72751d77e218a1b6b6afe.zip |
fix(options): better handling of empty values
Problem:
Whether an option is allowed to be empty isn't well defined and
isn't properly checked.
Solution:
- For non-list string options, explicitly check the option value
if it is empty.
- Annotate non-list string options that can accept an empty value.
- Adjust command completion to ignore the empty value.
- Render values in Lua meta files
-rw-r--r-- | runtime/doc/vim_diff.txt | 7 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/options.lua | 36 | ||||
-rwxr-xr-x | scripts/gen_eval_files.lua | 11 | ||||
-rw-r--r-- | src/nvim/options.lua | 17 | ||||
-rw-r--r-- | src/nvim/optionstr.c | 16 | ||||
-rw-r--r-- | test/old/testdir/gen_opt_test.vim | 14 | ||||
-rw-r--r-- | test/old/testdir/test_options.vim | 3 | ||||
-rw-r--r-- | test/old/testdir/test_termdebug.vim | 6 | ||||
-rw-r--r-- | test/unit/optionstr_spec.lua | 4 |
9 files changed, 72 insertions, 42 deletions
diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index d690460f77..a59312208a 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -347,11 +347,15 @@ Options: - `:set {option}<` removes local value for all |global-local| options. - `:setlocal {option}<` copies global value to local value for all options. +- 'ambiwidth' cannot be set to empty. - 'autoread' works in the terminal (if it supports "focus" events) +- 'background' cannot be set to empty. - 'cpoptions' flags: |cpo-_| - 'diffopt' "linematch" feature +- 'eadirection' cannot be set to empty. - 'exrc' searches for ".nvim.lua", ".nvimrc", or ".exrc" files. The user is prompted whether to trust the file. +- 'fileformat' cannot be set to empty. - 'fillchars' flags: "msgsep", "horiz", "horizup", "horizdown", "vertleft", "vertright", "verthoriz" - 'foldcolumn' supports up to 9 dynamic/fixed columns @@ -363,14 +367,17 @@ Options: - "clean" removes unloaded buffers from the jumplist. - the |jumplist|, |changelist|, |alternate-file| or using |mark-motions|. - 'laststatus' global statusline support +- 'mousemodel' cannot be set to empty. - 'mousescroll' amount to scroll by when scrolling with a mouse - 'pumblend' pseudo-transparent popupmenu - 'scrollback' - 'shortmess' - "F" flag does not affect output from autocommands. - "q" flag fully hides macro recording message. +- 'showcmdloc' cannot be set to empty. - 'signcolumn' can show multiple signs (dynamic or fixed columns) - 'statuscolumn' full control of columns using 'statusline' format +- 'splitkeep' cannot be set to empty. - 'tabline' middle-click on tabpage label closes tabpage, and %@Func@foo%X can call any function on mouse-click - 'termpastefilter' diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index c9871c8660..14f252516a 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -52,7 +52,7 @@ vim.go.ari = vim.go.allowrevins --- set to one of CJK locales. See Unicode Standard Annex #11 --- (https://www.unicode.org/reports/tr11). --- ---- @type string +--- @type 'single'|'double' vim.o.ambiwidth = "single" vim.o.ambw = vim.o.ambiwidth vim.go.ambiwidth = vim.o.ambiwidth @@ -208,7 +208,7 @@ vim.go.awa = vim.go.autowriteall --- will change. To use other settings, place ":highlight" commands AFTER --- the setting of the 'background' option. --- ---- @type string +--- @type 'light'|'dark' vim.o.background = "dark" vim.o.bg = vim.o.background vim.go.background = vim.o.background @@ -595,7 +595,7 @@ vim.wo.briopt = vim.wo.breakindentopt --- This option is used together with 'buftype' and 'swapfile' to specify --- special kinds of buffers. See `special-buffers`. --- ---- @type string +--- @type ''|'hide'|'unload'|'delete'|'wipe' vim.o.bufhidden = "" vim.o.bh = vim.o.bufhidden vim.bo.bufhidden = vim.o.bufhidden @@ -658,7 +658,7 @@ vim.bo.bl = vim.bo.buflisted --- without saving. For writing there must be matching `BufWriteCmd|, --- |FileWriteCmd` or `FileAppendCmd` autocommands. --- ---- @type string +--- @type ''|'acwrite'|'help'|'nofile'|'nowrite'|'quickfix'|'terminal'|'prompt' vim.o.buftype = "" vim.o.bt = vim.o.buftype vim.bo.buftype = vim.o.buftype @@ -1118,7 +1118,7 @@ vim.go.cot = vim.go.completeopt --- For Insert mode completion the buffer-local value is used. For --- command line completion the global value is used. --- ---- @type string +--- @type ''|'slash'|'backslash' vim.o.completeslash = "" vim.o.csl = vim.o.completeslash vim.bo.completeslash = vim.o.completeslash @@ -1824,7 +1824,7 @@ vim.go.dy = vim.go.display --- hor horizontally, height of windows is not affected --- both width and height of windows is affected --- ---- @type string +--- @type 'both'|'ver'|'hor' vim.o.eadirection = "both" vim.o.ead = vim.o.eadirection vim.go.eadirection = vim.o.eadirection @@ -2126,7 +2126,7 @@ vim.go.fencs = vim.go.fileencodings --- option is set, because the file would be different when written. --- This option cannot be changed when 'modifiable' is off. --- ---- @type string +--- @type 'unix'|'dos'|'mac' vim.o.fileformat = "unix" vim.o.ff = vim.o.fileformat vim.bo.fileformat = vim.o.fileformat @@ -2382,7 +2382,7 @@ vim.go.fcl = vim.go.foldclose --- "[1-9]": to display a fixed number of columns --- See `folding`. --- ---- @type string +--- @type 'auto'|'auto:1'|'auto:2'|'auto:3'|'auto:4'|'auto:5'|'auto:6'|'auto:7'|'auto:8'|'auto:9'|'0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9' vim.o.foldcolumn = "0" vim.o.fdc = vim.o.foldcolumn vim.wo.foldcolumn = vim.o.foldcolumn @@ -2479,7 +2479,7 @@ vim.wo.fmr = vim.wo.foldmarker --- `fold-syntax` syntax Syntax highlighting items specify folds. --- `fold-diff` diff Fold text that is not changed. --- ---- @type string +--- @type 'manual'|'expr'|'marker'|'indent'|'syntax'|'diff' vim.o.foldmethod = "manual" vim.o.fdm = vim.o.foldmethod vim.wo.foldmethod = vim.o.foldmethod @@ -3144,7 +3144,7 @@ vim.bo.ims = vim.bo.imsearch --- 'redrawtime') then 'inccommand' is automatically disabled until --- `Command-line-mode` is done. --- ---- @type string +--- @type 'nosplit'|'split'|'' vim.o.inccommand = "nosplit" vim.o.icm = vim.o.inccommand vim.go.inccommand = vim.o.inccommand @@ -4354,7 +4354,7 @@ vim.go.mh = vim.go.mousehide --- "g<LeftMouse>" is "<C-LeftMouse> (jump to tag under mouse click) --- "g<RightMouse>" is "<C-RightMouse> ("CTRL-T") --- ---- @type string +--- @type 'extend'|'popup'|'popup_setpos' vim.o.mousemodel = "popup_setpos" vim.o.mousem = vim.o.mousemodel vim.go.mousemodel = vim.o.mousemodel @@ -4947,7 +4947,7 @@ vim.wo.rl = vim.wo.rightleft --- This is useful for languages such as Hebrew, Arabic and Farsi. --- The 'rightleft' option must be set for 'rightleftcmd' to take effect. --- ---- @type string +--- @type 'search' vim.o.rightleftcmd = "search" vim.o.rlc = vim.o.rightleftcmd vim.wo.rightleftcmd = vim.o.rightleftcmd @@ -5222,7 +5222,7 @@ vim.go.sect = vim.go.sections --- backwards, you cannot include the last character of a line, when --- starting in Normal mode and 'virtualedit' empty. --- ---- @type string +--- @type 'inclusive'|'exclusive'|'old' vim.o.selection = "inclusive" vim.o.sel = vim.o.selection vim.go.selection = vim.o.selection @@ -5788,7 +5788,7 @@ vim.go.sc = vim.go.showcmd --- place the text. Without a custom 'statusline' or 'tabline' it will be --- displayed in a convenient location. --- ---- @type string +--- @type 'last'|'statusline'|'tabline' vim.o.showcmdloc = "last" vim.o.sloc = vim.o.showcmdloc vim.go.showcmdloc = vim.o.showcmdloc @@ -5920,7 +5920,7 @@ vim.go.siso = vim.go.sidescrolloff --- "number" display signs in the 'number' column. If the number --- column is not present, then behaves like "auto". --- ---- @type string +--- @type 'yes'|'no'|'auto'|'auto:1'|'auto:2'|'auto:3'|'auto:4'|'auto:5'|'auto:6'|'auto:7'|'auto:8'|'auto:9'|'yes:1'|'yes:2'|'yes:3'|'yes:4'|'yes:5'|'yes:6'|'yes:7'|'yes:8'|'yes:9'|'number' vim.o.signcolumn = "auto" vim.o.scl = vim.o.signcolumn vim.wo.signcolumn = vim.o.signcolumn @@ -6228,7 +6228,7 @@ vim.go.sb = vim.go.splitbelow --- with the previous cursor position. For "screen", the text cannot always --- be kept on the same screen line when 'wrap' is enabled. --- ---- @type string +--- @type 'cursor'|'screen'|'topline' vim.o.splitkeep = "cursor" vim.o.spk = vim.o.splitkeep vim.go.splitkeep = vim.o.splitkeep @@ -6876,7 +6876,7 @@ vim.go.tbs = vim.go.tagbsearch --- match Match case --- smart Ignore case unless an upper case letter is used --- ---- @type string +--- @type 'followic'|'ignore'|'match'|'followscs'|'smart' vim.o.tagcase = "followic" vim.o.tc = vim.o.tagcase vim.bo.tagcase = vim.o.tagcase @@ -7758,7 +7758,7 @@ vim.go.wop = vim.go.wildoptions --- key is never used for the menu. --- This option is not used for <F10>; on Win32. --- ---- @type string +--- @type 'yes'|'menu'|'no' vim.o.winaltkeys = "menu" vim.o.wak = vim.o.winaltkeys vim.go.winaltkeys = vim.o.winaltkeys diff --git a/scripts/gen_eval_files.lua b/scripts/gen_eval_files.lua index f888972f0d..58d3eeeadc 100755 --- a/scripts/gen_eval_files.lua +++ b/scripts/gen_eval_files.lua @@ -666,7 +666,16 @@ local function render_option_meta(_f, opt, write) write('--- ' .. l) end - write('--- @type ' .. OPTION_TYPES[opt.type]) + if opt.type == 'string' and not opt.list and opt.values then + local values = {} --- @type string[] + for _, e in ipairs(opt.values) do + values[#values + 1] = fmt("'%s'", e) + end + write('--- @type ' .. table.concat(values, '|')) + else + write('--- @type ' .. OPTION_TYPES[opt.type]) + end + write('vim.o.' .. opt.full_name .. ' = ' .. render_option_default(opt.defaults)) if opt.abbreviation then write('vim.o.' .. opt.abbreviation .. ' = vim.o.' .. opt.full_name) diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 15a4e8ddc2..2425dcb93e 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -7,7 +7,7 @@ --- @field alias? string|string[] --- @field short_desc? string|fun(): string --- @field varname? string ---- @field type vim.option_type|vim.option_type[] +--- @field type vim.option_type --- @field immutable? boolean --- @field list? 'comma'|'onecomma'|'commacolon'|'onecommacolon'|'flags'|'flagscomma' --- @field scope vim.option_scope[] @@ -834,7 +834,7 @@ return { abbreviation = 'bh', cb = 'did_set_bufhidden', defaults = { if_true = '' }, - values = { 'hide', 'unload', 'delete', 'wipe' }, + values = { '', 'hide', 'unload', 'delete', 'wipe' }, desc = [=[ This option specifies what happens when a buffer is no longer displayed in a window: @@ -888,11 +888,12 @@ return { cb = 'did_set_buftype', defaults = { if_true = '' }, values = { + '', + 'acwrite', + 'help', 'nofile', 'nowrite', 'quickfix', - 'help', - 'acwrite', 'terminal', 'prompt', }, @@ -1554,7 +1555,7 @@ return { abbreviation = 'csl', cb = 'did_set_completeslash', defaults = { if_true = '' }, - values = { 'slash', 'backslash' }, + values = { '', 'slash', 'backslash' }, desc = [=[ only modifiable in MS-Windows When this option is set it overrules 'shellslash' for completion: @@ -2017,8 +2018,10 @@ return { "msg" and "throw" are useful for debugging 'foldexpr', 'formatexpr' or 'indentexpr'. ]=], + -- TODO(lewis6991): bug, values currently cannot be combined expand_cb = 'expand_set_debug', full_name = 'debug', + list = 'comma', scope = { 'global' }, short_desc = N_('to "msg" to see all error messages'), type = 'string', @@ -4299,7 +4302,7 @@ return { abbreviation = 'icm', cb = 'did_set_inccommand', defaults = { if_true = 'nosplit' }, - values = { 'nosplit', 'split' }, + values = { 'nosplit', 'split', '' }, desc = [=[ When nonempty, shows the effects of |:substitute|, |:smagic|, |:snomagic| and user commands with the |:command-preview| flag as you @@ -5735,7 +5738,7 @@ return { abbreviation = 'mousem', cb = 'did_set_mousemodel', defaults = { if_true = 'popup_setpos' }, - values = { 'extend', 'popup', 'popup_setpos', 'mac' }, + values = { 'extend', 'popup', 'popup_setpos' }, desc = [=[ Sets the model to use for the mouse. The name mostly specifies what the right mouse button is used for: diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 9b7b50ae04..eac9ea02e0 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -395,7 +395,9 @@ static int expand_set_opt_string(optexpand_T *args, const char **values, size_t } for (const char **val = values; *val != NULL; val++) { - if (include_orig_val && *option_val != NUL) { + if (**val == NUL) { + continue; // Ignore empty + } else if (include_orig_val && *option_val != NUL) { if (strcmp(*val, option_val) == 0) { continue; } @@ -1091,7 +1093,7 @@ int expand_set_cursorlineopt(optexpand_T *args, int *numMatches, char ***matches /// The 'debug' option is changed. const char *did_set_debug(optset_T *args FUNC_ATTR_UNUSED) { - return did_set_opt_strings(p_debug, opt_debug_values, false); + return did_set_opt_strings(p_debug, opt_debug_values, true); } int expand_set_debug(optexpand_T *args, int *numMatches, char ***matches) @@ -2545,7 +2547,7 @@ int expand_set_winhighlight(optexpand_T *args, int *numMatches, char ***matches) /// @param list when true: accept a list of values /// /// @return OK for correct value, FAIL otherwise. Empty is always OK. -static int check_opt_strings(char *val, const char **values, int list) +static int check_opt_strings(char *val, const char **values, bool list) { return opt_strings_flags(val, values, NULL, list); } @@ -2562,7 +2564,10 @@ static int opt_strings_flags(const char *val, const char **values, unsigned *fla { unsigned new_flags = 0; - while (*val) { + // If not list and val is empty, then force one iteration of the while loop + bool iter_one = (*val == NUL) && !list; + + while (*val || iter_one) { for (unsigned i = 0;; i++) { if (values[i] == NULL) { // val not found in values[] return FAIL; @@ -2577,6 +2582,9 @@ static int opt_strings_flags(const char *val, const char **values, unsigned *fla break; // check next item in val list } } + if (iter_one) { + break; + } } if (flagp != NULL) { *flagp = new_flags; diff --git a/test/old/testdir/gen_opt_test.vim b/test/old/testdir/gen_opt_test.vim index 3802ee3fc9..bcb0f3d4c4 100644 --- a/test/old/testdir/gen_opt_test.vim +++ b/test/old/testdir/gen_opt_test.vim @@ -145,8 +145,8 @@ let test_values = { \ 'winwidth': [[1, 10, 999], [-1, 0]], \ "\ string options - \ 'ambiwidth': [['', 'single', 'double'], ['xxx']], - \ 'background': [['', 'light', 'dark'], ['xxx']], + \ 'ambiwidth': [['single', 'double'], ['xxx']], + \ 'background': [['light', 'dark'], ['xxx']], "\ 'backspace': [[0, 1, 2, 3, '', 'indent', 'eol', 'start', 'nostop', "\ " 'eol,start', 'indent,eol,nostop'], "\ " [-1, 4, 'xxx']], @@ -214,12 +214,12 @@ let test_values = { \ ['xxx', 'foldcolumn:xxx', 'algorithm:xxx', 'algorithm:']], \ 'display': [['', 'lastline', 'truncate', 'uhex', 'lastline,uhex'], \ ['xxx']], - \ 'eadirection': [['', 'both', 'ver', 'hor'], ['xxx', 'ver,hor']], + \ 'eadirection': [['both', 'ver', 'hor'], ['xxx', 'ver,hor']], "\ 'encoding': [['latin1'], ['xxx', '']], \ 'eventignore': [['', 'WinEnter', 'WinLeave,winenter', 'all,WinEnter'], \ ['xxx']], \ 'fileencoding': [['', 'latin1', 'xxx'], []], - \ 'fileformat': [['', 'dos', 'unix', 'mac'], ['xxx']], + \ 'fileformat': [['dos', 'unix', 'mac'], ['xxx']], \ 'fileformats': [['', 'dos', 'dos,unix'], ['xxx']], \ 'fillchars': [['', 'stl:x', 'stlnc:x', 'vert:x', 'fold:x', 'foldopen:x', \ 'foldclose:x', 'foldsep:x', 'diff:x', 'eob:x', 'lastline:x', @@ -274,7 +274,7 @@ let test_values = { \ 'mkspellmem': [['10000,100,12'], ['', 'xxx', '10000,100']], \ 'mouse': [['', 'n', 'v', 'i', 'c', 'h', 'a', 'r', 'nvi'], \ ['xxx', 'n,v,i']], - \ 'mousemodel': [['', 'extend', 'popup', 'popup_setpos'], ['xxx']], + \ 'mousemodel': [['extend', 'popup', 'popup_setpos'], ['xxx']], \ 'mouseshape': [['', 'n:arrow'], ['xxx']], \ 'nrformats': [['', 'alpha', 'octal', 'hex', 'bin', 'unsigned', 'blank', \ 'alpha,hex,bin'], @@ -299,7 +299,7 @@ let test_values = { \ 'sessionoptions': [['', 'blank', 'curdir', 'sesdir', \ 'help,options,slash'], \ ['xxx', 'curdir,sesdir']], - \ 'showcmdloc': [['', 'last', 'statusline', 'tabline'], ['xxx']], + \ 'showcmdloc': [['last', 'statusline', 'tabline'], ['xxx']], "\ 'signcolumn': [['', 'auto', 'no', 'yes', 'number'], ['xxx', 'no,yes']], \ 'spellfile': [['', 'file.en.add', 'xxx.en.add,yyy.gb.add,zzz.ja.add', \ '/tmp/dir\ with\ space/en.utf-8.add', @@ -311,7 +311,7 @@ let test_values = { \ 'spellsuggest': [['', 'best', 'double', 'fast', '100', 'timeout:100', \ 'timeout:-1', 'file:/tmp/file', 'expr:Func()', 'double,33'], \ ['xxx', '-1', 'timeout:', 'best,double', 'double,fast']], - \ 'splitkeep': [['', 'cursor', 'screen', 'topline'], ['xxx']], + \ 'splitkeep': [['cursor', 'screen', 'topline'], ['xxx']], \ 'statusline': [['', 'xxx'], ['%$', '%{', '%{%', '%{%}', '%(', '%)']], "\ 'swapsync': [['', 'sync', 'fsync'], ['xxx']], \ 'switchbuf': [['', 'useopen', 'usetab', 'split', 'vsplit', 'newtab', diff --git a/test/old/testdir/test_options.vim b/test/old/testdir/test_options.vim index d090186b62..dfa140b163 100644 --- a/test/old/testdir/test_options.vim +++ b/test/old/testdir/test_options.vim @@ -504,7 +504,8 @@ func Test_set_completion_string_values() call assert_equal('current', getcompletion('set browsedir=', 'cmdline')[1]) endif call assert_equal('unload', getcompletion('set bufhidden=', 'cmdline')[1]) - call assert_equal('nowrite', getcompletion('set buftype=', 'cmdline')[1]) + "call assert_equal('nowrite', getcompletion('set buftype=', 'cmdline')[1]) + call assert_equal('help', getcompletion('set buftype=', 'cmdline')[1]) call assert_equal('internal', getcompletion('set casemap=', 'cmdline')[1]) if exists('+clipboard') " call assert_match('unnamed', getcompletion('set clipboard=', 'cmdline')[1]) diff --git a/test/old/testdir/test_termdebug.vim b/test/old/testdir/test_termdebug.vim index eb88ea6f5f..6ff233fff0 100644 --- a/test/old/testdir/test_termdebug.vim +++ b/test/old/testdir/test_termdebug.vim @@ -391,7 +391,8 @@ endfunc function Test_termdebug_save_restore_variables() " saved mousemodel - let &mousemodel='' + "let &mousemodel='' + let &mousemodel='extend' " saved keys nnoremap K :echo "hello world!"<cr> @@ -414,7 +415,8 @@ function Test_termdebug_save_restore_variables() quit! call WaitForAssert({-> assert_equal(1, winnr('$'))}) - call assert_true(empty(&mousemodel)) + "call assert_true(empty(&mousemodel)) + call assert_equal(&mousemodel, 'extend') call assert_true(empty(expected_map_minus)) call assert_equal(expected_map_K.rhs, maparg('K', 'n', 0, 1).rhs) diff --git a/test/unit/optionstr_spec.lua b/test/unit/optionstr_spec.lua index b9c9ceaa85..1f5b42485f 100644 --- a/test/unit/optionstr_spec.lua +++ b/test/unit/optionstr_spec.lua @@ -11,8 +11,8 @@ local check_ff_value = function(ff) end describe('check_ff_value', function() - itp('views empty string as valid', function() - eq(1, check_ff_value('')) + itp('views empty string as invalid', function() + eq(0, check_ff_value('')) end) itp('views "unix", "dos" and "mac" as valid', function() |