aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTJ DeVries <devries.timothyj@gmail.com>2021-06-29 10:24:41 -0400
committerGitHub <noreply@github.com>2021-06-29 10:24:41 -0400
commit2bcf18deaa32ad371b6de349b353d42e9b1c9ea0 (patch)
treeec954d3f87ae1cea2dccc73fb4213118fbf27d7f
parentf8990870ffd86ff36cc86e23b82b74b1a764b525 (diff)
parent19b7cef0a7dc52f3ec016e0dd0ae82f0f6ecd4b1 (diff)
downloadrneovim-2bcf18deaa32ad371b6de349b353d42e9b1c9ea0.tar.gz
rneovim-2bcf18deaa32ad371b6de349b353d42e9b1c9ea0.tar.bz2
rneovim-2bcf18deaa32ad371b6de349b353d42e9b1c9ea0.zip
Merge pull request #14779 from tjdevries/tjdevries/vim_opt_fixes
An assortment of various vim.opt fixups.
-rw-r--r--runtime/lua/vim/_meta.lua147
-rw-r--r--src/nvim/option.c1
-rw-r--r--src/nvim/options.lua2
-rw-r--r--test/functional/api/vim_spec.lua3
-rw-r--r--test/functional/lua/vim_spec.lua248
5 files changed, 377 insertions, 24 deletions
diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua
index 02d1154df4..b1f935541c 100644
--- a/runtime/lua/vim/_meta.lua
+++ b/runtime/lua/vim/_meta.lua
@@ -209,12 +209,17 @@ local function get_scoped_option(k, set_type)
end
if is_window_option(info) then
- if vim.api.nvim_get_option_info(k).was_set then
- local was_set, value = pcall(a.nvim_win_get_option, 0, k)
- if was_set then return value end
+ local ok, value = pcall(a.nvim_win_get_option, 0, k)
+ if ok then
+ return value
end
- return a.nvim_get_option(k)
+ local global_ok, global_val = pcall(a.nvim_get_option, k)
+ if global_ok then
+ return global_val
+ end
+
+ error("win_get: This should never happen. File an issue and tag @tjdevries")
end
error("This fallback case should not be possible. " .. k)
@@ -262,7 +267,7 @@ local key_value_options = {
winhl = true,
}
----@class OptionType
+---@class OptionTypes
--- Option Type Enum
local OptionTypes = setmetatable({
BOOLEAN = 0,
@@ -306,6 +311,28 @@ local get_option_type = function(name, info)
end
+-- Check whether the OptionTypes is allowed for vim.opt
+-- If it does not match, throw an error which indicates which option causes the error.
+local function assert_valid_value(name, value, types)
+ local type_of_value = type(value)
+ for _, valid_type in ipairs(types) do
+ if valid_type == type_of_value then
+ return
+ end
+ end
+
+ error(string.format("Invalid option type '%s' for '%s', should be %s", type_of_value, name, table.concat(types, " or ")))
+end
+
+local valid_types = {
+ [OptionTypes.BOOLEAN] = { "boolean" },
+ [OptionTypes.NUMBER] = { "number" },
+ [OptionTypes.STRING] = { "string" },
+ [OptionTypes.SET] = { "string", "table" },
+ [OptionTypes.ARRAY] = { "string", "table" },
+ [OptionTypes.MAP] = { "string", "table" },
+}
+
--- Convert a lua value to a vimoption_T value
local convert_value_to_vim = (function()
-- Map of functions to take a Lua style value and convert to vimoption_T style value.
@@ -315,24 +342,41 @@ local convert_value_to_vim = (function()
[OptionTypes.NUMBER] = function(_, value) return value end,
[OptionTypes.STRING] = function(_, value) return value end,
- [OptionTypes.SET] = function(_, value)
+ [OptionTypes.SET] = function(info, value)
if type(value) == "string" then return value end
- local result = ''
- for k in pairs(value) do
- result = result .. k
- end
- return result
+ if info.flaglist and info.commalist then
+ local keys = {}
+ for k, v in pairs(value) do
+ if v then
+ table.insert(keys, k)
+ end
+ end
+
+ table.sort(keys)
+ return table.concat(keys, ",")
+ else
+ local result = ''
+ for k, v in pairs(value) do
+ if v then
+ result = result .. k
+ end
+ end
+
+ return result
+ end
end,
- [OptionTypes.ARRAY] = function(_, value)
+ [OptionTypes.ARRAY] = function(info, value)
if type(value) == "string" then return value end
- return table.concat(remove_duplicate_values(value), ",")
+ if not info.allows_duplicates then
+ value = remove_duplicate_values(value)
+ end
+ return table.concat(value, ",")
end,
[OptionTypes.MAP] = function(_, value)
if type(value) == "string" then return value end
- if type(value) == "function" then error(debug.traceback("asdf")) end
local result = {}
for opt_key, opt_value in pairs(value) do
@@ -345,7 +389,10 @@ local convert_value_to_vim = (function()
}
return function(name, info, value)
- return to_vim_value[get_option_type(name, info)](info, value)
+ local option_type = get_option_type(name, info)
+ assert_valid_value(name, value, valid_types[option_type])
+
+ return to_vim_value[option_type](info, value)
end
end)()
@@ -358,26 +405,82 @@ local convert_value_to_lua = (function()
[OptionTypes.NUMBER] = function(_, value) return value end,
[OptionTypes.STRING] = function(_, value) return value end,
- [OptionTypes.ARRAY] = function(_, value)
+ [OptionTypes.ARRAY] = function(info, value)
if type(value) == "table" then
- value = remove_duplicate_values(value)
+ if not info.allows_duplicates then
+ value = remove_duplicate_values(value)
+ end
+
return value
end
+ -- Empty strings mean that there is nothing there,
+ -- so empty table should be returned.
+ if value == '' then
+ return {}
+ end
+
+ -- Handles unescaped commas in a list.
+ if string.find(value, ",,,") then
+ local comma_split = vim.split(value, ",,,")
+ local left = comma_split[1]
+ local right = comma_split[2]
+
+ local result = {}
+ vim.list_extend(result, vim.split(left, ","))
+ table.insert(result, ",")
+ vim.list_extend(result, vim.split(right, ","))
+
+ table.sort(result)
+
+ return result
+ end
+
+ if string.find(value, ",^,,", 1, true) then
+ local comma_split = vim.split(value, ",^,,", true)
+ local left = comma_split[1]
+ local right = comma_split[2]
+
+ local result = {}
+ vim.list_extend(result, vim.split(left, ","))
+ table.insert(result, "^,")
+ vim.list_extend(result, vim.split(right, ","))
+
+ table.sort(result)
+
+ return result
+ end
+
return vim.split(value, ",")
end,
[OptionTypes.SET] = function(info, value)
if type(value) == "table" then return value end
+ -- Empty strings mean that there is nothing there,
+ -- so empty table should be returned.
+ if value == '' then
+ return {}
+ end
+
assert(info.flaglist, "That is the only one I know how to handle")
- local result = {}
- for i = 1, #value do
- result[value:sub(i, i)] = true
- end
+ if info.flaglist and info.commalist then
+ local split_value = vim.split(value, ",")
+ local result = {}
+ for _, v in ipairs(split_value) do
+ result[v] = true
+ end
- return result
+ return result
+ else
+ local result = {}
+ for i = 1, #value do
+ result[value:sub(i, i)] = true
+ end
+
+ return result
+ end
end,
[OptionTypes.MAP] = function(info, raw_value)
diff --git a/src/nvim/option.c b/src/nvim/option.c
index f57abe89cc..388bedc043 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -7765,6 +7765,7 @@ static Dictionary vimoption2dict(vimoption_T *opt)
}
PUT(dict, "type", CSTR_TO_OBJ(type));
PUT(dict, "default", def);
+ PUT(dict, "allows_duplicates", BOOL(!(opt->flags & P_NODUP)));
return dict;
}
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 86dec74f56..0b09686675 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -3152,7 +3152,7 @@ return {
full_name='wildmode', abbreviation='wim',
short_desc=N_("mode for 'wildchar' command-line expansion"),
type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
+ deny_duplicates=false,
vim=true,
varname='p_wim',
defaults={if_true={vi="", vim="full"}}
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index 0c0f610401..91d2745130 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -2003,6 +2003,7 @@ describe('API', function()
it('should have information about window options', function()
eq({
+ allows_duplicates = true,
commalist = false;
default = "";
flaglist = false;
@@ -2020,6 +2021,7 @@ describe('API', function()
it('should have information about buffer options', function()
eq({
+ allows_duplicates = true,
commalist = false,
default = "",
flaglist = false,
@@ -2041,6 +2043,7 @@ describe('API', function()
eq(false, meths.get_option'showcmd')
eq({
+ allows_duplicates = true,
commalist = false,
default = true,
flaglist = false,
diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua
index 836f514433..eff838aea3 100644
--- a/test/functional/lua/vim_spec.lua
+++ b/test/functional/lua/vim_spec.lua
@@ -1228,6 +1228,21 @@ describe('lua stdlib', function()
eq("only-local", result[8])
end)
+ it('should allow you to retrieve window opts even if they have not been set', function()
+ local result = exec_lua [[
+ local result = {}
+ table.insert(result, vim.opt.number:get())
+ table.insert(result, vim.opt_local.number:get())
+
+ vim.opt_local.number = true
+ table.insert(result, vim.opt.number:get())
+ table.insert(result, vim.opt_local.number:get())
+
+ return result
+ ]]
+ eq({false, false, true, true}, result)
+ end)
+
it('should allow all sorts of string manipulation', function()
eq({'hello', 'hello world', 'start hello world'}, exec_lua [[
local results = {}
@@ -1299,6 +1314,22 @@ describe('lua stdlib', function()
eq("*.c", wildignore[1])
end)
+ it('should work for options that are both commalist and flaglist', function()
+ local result = exec_lua [[
+ vim.opt.whichwrap = "b,s"
+ return vim.opt.whichwrap:get()
+ ]]
+
+ eq({b = true, s = true}, result)
+
+ result = exec_lua [[
+ vim.opt.whichwrap = { b = true, s = false, h = true }
+ return vim.opt.whichwrap:get()
+ ]]
+
+ eq({b = true, h = true}, result)
+ end)
+
it('should work for key-value pair options', function()
local listchars = exec_lua [[
vim.opt.listchars = "tab:>~,space:_"
@@ -1569,7 +1600,222 @@ describe('lua stdlib', function()
eq(wildignore, 'super_first,first,foo')
end)
- end)
+ it('should not remove duplicates from wildmode: #14708', function()
+ local wildmode = exec_lua [[
+ vim.opt.wildmode = {"full", "list", "full"}
+ return vim.o.wildmode
+ ]]
+
+ eq(wildmode, 'full,list,full')
+ end)
+
+ describe('option types', function()
+ it('should allow to set option with numeric value', function()
+ eq(4, exec_lua [[
+ vim.opt.tabstop = 4
+ return vim.bo.tabstop
+ ]])
+
+ matches("Invalid option type 'string' for 'tabstop'", pcall_err(exec_lua, [[
+ vim.opt.tabstop = '4'
+ ]]))
+ matches("Invalid option type 'boolean' for 'tabstop'", pcall_err(exec_lua, [[
+ vim.opt.tabstop = true
+ ]]))
+ matches("Invalid option type 'table' for 'tabstop'", pcall_err(exec_lua, [[
+ vim.opt.tabstop = {4, 2}
+ ]]))
+ matches("Invalid option type 'function' for 'tabstop'", pcall_err(exec_lua, [[
+ vim.opt.tabstop = function()
+ return 4
+ end
+ ]]))
+ end)
+
+ it('should allow to set option with boolean value', function()
+ eq(true, exec_lua [[
+ vim.opt.undofile = true
+ return vim.bo.undofile
+ ]])
+
+ matches("Invalid option type 'number' for 'undofile'", pcall_err(exec_lua, [[
+ vim.opt.undofile = 0
+ ]]))
+ matches("Invalid option type 'table' for 'undofile'", pcall_err(exec_lua, [[
+ vim.opt.undofile = {true}
+ ]]))
+ matches("Invalid option type 'string' for 'undofile'", pcall_err(exec_lua, [[
+ vim.opt.undofile = 'true'
+ ]]))
+ matches("Invalid option type 'function' for 'undofile'", pcall_err(exec_lua, [[
+ vim.opt.undofile = function()
+ return true
+ end
+ ]]))
+ end)
+
+ it('should allow to set option with array or string value', function()
+ eq('indent,eol,start', exec_lua [[
+ vim.opt.backspace = {'indent','eol','start'}
+ return vim.go.backspace
+ ]])
+ eq('indent,eol,start', exec_lua [[
+ vim.opt.backspace = 'indent,eol,start'
+ return vim.go.backspace
+ ]])
+
+ matches("Invalid option type 'boolean' for 'backspace'", pcall_err(exec_lua, [[
+ vim.opt.backspace = true
+ ]]))
+ matches("Invalid option type 'number' for 'backspace'", pcall_err(exec_lua, [[
+ vim.opt.backspace = 2
+ ]]))
+ matches("Invalid option type 'function' for 'backspace'", pcall_err(exec_lua, [[
+ vim.opt.backspace = function()
+ return 'indent,eol,start'
+ end
+ ]]))
+ end)
+
+ it('should allow set option with map or string value', function()
+ eq("eol:~,space:.", exec_lua [[
+ vim.opt.listchars = {
+ eol = "~",
+ space = ".",
+ }
+ return vim.o.listchars
+ ]])
+ eq("eol:~,space:.,tab:>~", exec_lua [[
+ vim.opt.listchars = "eol:~,space:.,tab:>~"
+ return vim.o.listchars
+ ]])
+
+ matches("Invalid option type 'boolean' for 'listchars'", pcall_err(exec_lua, [[
+ vim.opt.listchars = true
+ ]]))
+ matches("Invalid option type 'number' for 'listchars'", pcall_err(exec_lua, [[
+ vim.opt.listchars = 2
+ ]]))
+ matches("Invalid option type 'function' for 'listchars'", pcall_err(exec_lua, [[
+ vim.opt.listchars = function()
+ return "eol:~,space:.,tab:>~"
+ end
+ ]]))
+ end)
+
+ it('should allow set option with set or string value', function()
+ local ww = exec_lua [[
+ vim.opt.whichwrap = {
+ b = true,
+ s = 1,
+ }
+ return vim.go.whichwrap
+ ]]
+
+ eq(ww, "b,s")
+ eq("b,s,<,>,[,]", exec_lua [[
+ vim.opt.whichwrap = "b,s,<,>,[,]"
+ return vim.go.whichwrap
+ ]])
+
+ matches("Invalid option type 'boolean' for 'whichwrap'", pcall_err(exec_lua, [[
+ vim.opt.whichwrap = true
+ ]]))
+ matches("Invalid option type 'number' for 'whichwrap'", pcall_err(exec_lua, [[
+ vim.opt.whichwrap = 2
+ ]]))
+ matches("Invalid option type 'function' for 'whichwrap'", pcall_err(exec_lua, [[
+ vim.opt.whichwrap = function()
+ return "b,s,<,>,[,]"
+ end
+ ]]))
+ end)
+ end)
+
+ -- isfname=a,b,c,,,d,e,f
+ it('can handle isfname ,,,', function()
+ local result = exec_lua [[
+ vim.opt.isfname = "a,b,,,c"
+ return { vim.opt.isfname:get(), vim.api.nvim_get_option('isfname') }
+ ]]
+
+ eq({{",", "a", "b", "c"}, "a,b,,,c"}, result)
+ end)
+
+ -- isfname=a,b,c,^,,def
+ it('can handle isfname ,^,,', function()
+ local result = exec_lua [[
+ vim.opt.isfname = "a,b,^,,c"
+ return { vim.opt.isfname:get(), vim.api.nvim_get_option('isfname') }
+ ]]
+
+ eq({{"^,", "a", "b", "c"}, "a,b,^,,c"}, result)
+ end)
+
+
+
+ describe('https://github.com/neovim/neovim/issues/14828', function()
+ it('gives empty list when item is empty:array', function()
+ eq({}, exec_lua [[
+ vim.cmd("set wildignore=")
+ return vim.opt.wildignore:get()
+ ]])
+
+ eq({}, exec_lua [[
+ vim.opt.wildignore = {}
+ return vim.opt.wildignore:get()
+ ]])
+ end)
+
+ it('gives empty list when item is empty:set', function()
+ eq({}, exec_lua [[
+ vim.cmd("set formatoptions=")
+ return vim.opt.formatoptions:get()
+ ]])
+
+ eq({}, exec_lua [[
+ vim.opt.formatoptions = {}
+ return vim.opt.formatoptions:get()
+ ]])
+ end)
+
+ it('does not append to empty item', function()
+ eq({"*.foo", "*.bar"}, exec_lua [[
+ vim.opt.wildignore = {}
+ vim.opt.wildignore:append { "*.foo", "*.bar" }
+
+ return vim.opt.wildignore:get()
+ ]])
+ end)
+
+ it('does not prepend to empty item', function()
+ eq({"*.foo", "*.bar"}, exec_lua [[
+ vim.opt.wildignore = {}
+ vim.opt.wildignore:prepend { "*.foo", "*.bar" }
+
+ return vim.opt.wildignore:get()
+ ]])
+ end)
+
+ it('append to empty set', function()
+ eq({ t = true }, exec_lua [[
+ vim.opt.formatoptions = {}
+ vim.opt.formatoptions:append("t")
+
+ return vim.opt.formatoptions:get()
+ ]])
+ end)
+
+ it('prepend to empty set', function()
+ eq({ t = true }, exec_lua [[
+ vim.opt.formatoptions = {}
+ vim.opt.formatoptions:prepend("t")
+
+ return vim.opt.formatoptions:get()
+ ]])
+ end)
+ end)
+ end) -- vim.opt
it('vim.cmd', function()
exec_lua [[