mirror of
https://github.com/neovim/neovim.git
synced 2026-07-09 10:59:38 +00:00
feat(api): nvim_set_option_value(operation=...) #39849
Problem: `nvim_set_option_value` cannot "update" options similar to `:set opt=`, `:set opt+=`, etc. The Lua impls of "vim.opt" / "vim.o" have incomplete, bespoke reimplementations of those operations. ref #38420 Solution: - Add `operation` param to `nvim_set_option_value`, which may be "set", "append", "prepend", or "remove". - Use this feature to implement `vim.opt` / `vim.o`.
This commit is contained in:
@@ -3596,6 +3596,11 @@ nvim_set_option_value({name}, {value}, {opts})
|
||||
• {value} (`any`) New option value
|
||||
• {opts} (`vim.api.keyset.option`) Optional parameters
|
||||
• buf: Buffer number. Used for setting buffer local option.
|
||||
• dry_run: (`boolean?`, default: false) If true, then the
|
||||
option value won't be set.
|
||||
• operation: One of "set", "append", "prepend", or "remove".
|
||||
Corresponds to |:set=|, |:set+=|, |:set^=|, and |:set-=|.
|
||||
Default is "set".
|
||||
• scope: One of "global" or "local". Analogous to
|
||||
|:setglobal| and |:setlocal|, respectively.
|
||||
• tab: |tab-ID| for tab-local options (currently only
|
||||
@@ -3604,6 +3609,9 @@ nvim_set_option_value({name}, {value}, {opts})
|
||||
it is switched-to.
|
||||
• win: |window-ID|. Used for setting window local option.
|
||||
|
||||
Return: ~
|
||||
(`any`) Option value
|
||||
|
||||
|
||||
==============================================================================
|
||||
Tabpage Functions *api-tabpage*
|
||||
|
||||
@@ -961,6 +961,12 @@ Vim options can be accessed through |vim.o|, which behaves like Vimscript
|
||||
To set a string value:
|
||||
Vimscript: `set wildignore=*.o,*.a,__pycache__`
|
||||
Lua: `vim.o.wildignore = '*.o,*.a,__pycache__'`
|
||||
Lua (alt): `vim.o.wildignore = { '*.o', '*.a', '__pycache__' }`
|
||||
|
||||
To set a key:value type option:
|
||||
Vimscript: `set listchars=eol:~,space:-`
|
||||
Lua: `vim.o.listchars = 'eol:~,space:-'`
|
||||
Lua (alt): `vim.o.listchars = { eol = '~', space = '-' }`
|
||||
|
||||
Similarly, there is |vim.bo| and |vim.wo| for setting buffer-scoped and
|
||||
window-scoped options. Note that this must NOT be confused with
|
||||
|
||||
@@ -69,6 +69,8 @@ API
|
||||
• |nvim_create_autocmd()|, |nvim_exec_autocmds()| and |nvim_clear_autocmds()|
|
||||
no longer treat an empty non-nil pattern as nil.
|
||||
• |nvim_clear_autocmds()| no longer treats an empty array event as nil.
|
||||
• |vim.o|, |vim.opt|, and |nvim_set_option_value()| expand `~` and environment
|
||||
variables (|expand-env|).
|
||||
|
||||
DIAGNOSTICS
|
||||
|
||||
@@ -105,6 +107,9 @@ LUA
|
||||
|
||||
• "standalone" Lua interpreter mode `nvim -ll` was removed. Use |-l| script
|
||||
mode instead.
|
||||
• |vim.opt| no longer supports chaining multiple infix operators (e.g.
|
||||
`vim.opt.wildignore + '*.o' + '*.obj'`). Instead, use tables:
|
||||
`vim.opt.wildignore + {'*.o', '*.obj'}`
|
||||
|
||||
OPTIONS
|
||||
|
||||
@@ -148,6 +153,9 @@ API
|
||||
• |nvim_buf_set_extmark()| `virt_lines_overflow` accepts "wrap" to enable
|
||||
wrapping onto extra rows and "auto" which enables horizontal scrolling when
|
||||
'nowrap' is set and wrapping when 'wrap' is set.
|
||||
• |nvim_set_option_value()| accepts a new `operation` field to modify the
|
||||
existing option value.
|
||||
• |nvim_set_option_value()| returns the new option value.
|
||||
|
||||
BUILD
|
||||
|
||||
@@ -260,6 +268,7 @@ LUA
|
||||
• |vim.log| provides a logging interface.
|
||||
• |vim.pack.get()| output includes revision of a pending update.
|
||||
• |vim.pack.get()| can fetch new updates before computing the output.
|
||||
• |vim.o| now accepts table style values for assignment.
|
||||
|
||||
OPTIONS
|
||||
|
||||
|
||||
@@ -92,6 +92,7 @@
|
||||
--- Invalid or unset key returns `nil`.
|
||||
--- </pre>
|
||||
|
||||
local M = {}
|
||||
local api = vim.api
|
||||
|
||||
-- TODO(tjdevries): Improve option metadata so that this doesn't have to be hardcoded.
|
||||
@@ -167,7 +168,7 @@ local function new_buf_opt_accessor(bufnr)
|
||||
end,
|
||||
|
||||
__newindex = function(_, k, v)
|
||||
return api.nvim_set_option_value(k, v, { buf = bufnr or 0 })
|
||||
api.nvim_set_option_value(k, v, { buf = bufnr or 0 })
|
||||
end,
|
||||
})
|
||||
end
|
||||
@@ -195,7 +196,7 @@ local function new_win_opt_accessor(winid, bufnr)
|
||||
end,
|
||||
|
||||
__newindex = function(_, k, v)
|
||||
return api.nvim_set_option_value(k, v, {
|
||||
api.nvim_set_option_value(k, v, {
|
||||
scope = bufnr and 'local' or nil,
|
||||
win = winid or 0,
|
||||
})
|
||||
@@ -221,6 +222,12 @@ end
|
||||
--- To set a string value:
|
||||
--- Vimscript: `set wildignore=*.o,*.a,__pycache__`
|
||||
--- Lua: `vim.o.wildignore = '*.o,*.a,__pycache__'`
|
||||
--- Lua (alt): `vim.o.wildignore = { '*.o', '*.a', '__pycache__' }`
|
||||
---
|
||||
--- To set a key:value type option:
|
||||
--- Vimscript: `set listchars=eol:~,space:-`
|
||||
--- Lua: `vim.o.listchars = 'eol:~,space:-'`
|
||||
--- Lua (alt): `vim.o.listchars = { eol = '~', space = '-' }`
|
||||
---
|
||||
--- Similarly, there is |vim.bo| and |vim.wo| for setting buffer-scoped and
|
||||
--- window-scoped options. Note that this must NOT be confused with
|
||||
@@ -243,7 +250,7 @@ vim.o = setmetatable({}, {
|
||||
return api.nvim_get_option_value(k, {})
|
||||
end,
|
||||
__newindex = function(_, k, v)
|
||||
return api.nvim_set_option_value(k, v, {})
|
||||
api.nvim_set_option_value(k, v, {})
|
||||
end,
|
||||
})
|
||||
|
||||
@@ -264,7 +271,7 @@ vim.go = setmetatable({}, {
|
||||
return api.nvim_get_option_value(k, { scope = 'global' })
|
||||
end,
|
||||
__newindex = function(_, k, v)
|
||||
return api.nvim_set_option_value(k, v, { scope = 'global' })
|
||||
api.nvim_set_option_value(k, v, { scope = 'global' })
|
||||
end,
|
||||
})
|
||||
|
||||
@@ -350,25 +357,7 @@ local function passthrough(_, x)
|
||||
return x
|
||||
end
|
||||
|
||||
local function tbl_merge(left, right)
|
||||
return vim.tbl_extend('force', left, right)
|
||||
end
|
||||
|
||||
--- @param t table<any,any>
|
||||
--- @param value any|any[]
|
||||
local function tbl_remove(t, value)
|
||||
if type(value) == 'string' then
|
||||
t[value] = nil
|
||||
else
|
||||
for _, v in ipairs(value) do
|
||||
t[v] = nil
|
||||
end
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
local valid_types = {
|
||||
local type_mappings = {
|
||||
boolean = { 'boolean' },
|
||||
number = { 'number' },
|
||||
string = { 'string' },
|
||||
@@ -377,6 +366,14 @@ local valid_types = {
|
||||
map = { 'string', 'table' },
|
||||
}
|
||||
|
||||
local function valid_types(name, metatype)
|
||||
-- Allow strings for special number options
|
||||
if name == 'wildchar' or name == 'wildcharm' then
|
||||
return { 'number', 'string' }
|
||||
end
|
||||
return type_mappings[metatype]
|
||||
end
|
||||
|
||||
-- Map of functions to take a Lua style value and convert to vimoption_T style value.
|
||||
-- Each function takes (info, lua_value) -> vim_value
|
||||
local to_vim_value = {
|
||||
@@ -445,14 +442,24 @@ local to_vim_value = {
|
||||
}
|
||||
|
||||
--- Convert a Lua value to a vimoption_T value
|
||||
local function convert_value_to_vim(name, info, value)
|
||||
if value == nil then
|
||||
function M.convert_value_to_vim(name, value, operation)
|
||||
if value == nil or value == vim.NIL then
|
||||
return vim.NIL
|
||||
end
|
||||
|
||||
assert_valid_value(name, value, valid_types[info.metatype])
|
||||
local info = get_options_info(name) or error('Not a valid option name: ' .. name)
|
||||
assert_valid_value(name, value, valid_types(name, info.metatype))
|
||||
|
||||
return to_vim_value[info.metatype](info, value)
|
||||
local vim_value = to_vim_value[info.metatype](info, value)
|
||||
-- In lua, we allow vim.opt.listchars = vim.opt.listchars - 'space'
|
||||
-- In vim, the key requires a colon on the end. See stropt_handle_keymatch.
|
||||
if operation == 'remove' and info.metatype == 'map' then
|
||||
if not vim_value:find(':') then
|
||||
vim_value = vim_value .. ':'
|
||||
end
|
||||
end
|
||||
|
||||
return vim_value
|
||||
end
|
||||
|
||||
-- Map of OptionType to functions that take vimoption_T values and convert to Lua values.
|
||||
@@ -462,15 +469,7 @@ local to_lua_value = {
|
||||
number = passthrough,
|
||||
string = passthrough,
|
||||
|
||||
array = function(info, value)
|
||||
if type(value) == 'table' then
|
||||
if not info.allows_duplicates then
|
||||
value = remove_duplicate_values(value)
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
array = function(_, value)
|
||||
-- Empty strings mean that there is nothing there,
|
||||
-- so empty table should be returned.
|
||||
if value == '' then
|
||||
@@ -561,136 +560,16 @@ local to_lua_value = {
|
||||
}
|
||||
|
||||
--- Converts a vimoption_T style value to a Lua value
|
||||
local function convert_value_to_lua(info, option_value)
|
||||
function M.convert_value_to_lua(name, option_value)
|
||||
local info = get_options_info(name) or error('Not a valid option name: ' .. name)
|
||||
return to_lua_value[info.metatype](info, option_value)
|
||||
end
|
||||
|
||||
local prepend_methods = {
|
||||
number = function()
|
||||
error("The '^' operator is not currently supported for")
|
||||
end,
|
||||
|
||||
string = function(left, right)
|
||||
return right .. left
|
||||
end,
|
||||
|
||||
array = function(left, right)
|
||||
for i = #right, 1, -1 do
|
||||
table.insert(left, 1, right[i])
|
||||
end
|
||||
|
||||
return left
|
||||
end,
|
||||
|
||||
map = tbl_merge,
|
||||
set = tbl_merge,
|
||||
}
|
||||
|
||||
--- Handles the '^' operator
|
||||
local function prepend_value(info, current, new)
|
||||
return prepend_methods[info.metatype](
|
||||
convert_value_to_lua(info, current),
|
||||
convert_value_to_lua(info, new)
|
||||
)
|
||||
end
|
||||
|
||||
local add_methods = {
|
||||
--- @param left integer
|
||||
--- @param right integer
|
||||
number = function(left, right)
|
||||
return left + right
|
||||
end,
|
||||
|
||||
--- @param left string
|
||||
--- @param right string
|
||||
string = function(left, right)
|
||||
return left .. right
|
||||
end,
|
||||
|
||||
--- @param left string[]
|
||||
--- @param right string[]
|
||||
--- @return string[]
|
||||
array = function(left, right)
|
||||
for _, v in ipairs(right) do
|
||||
table.insert(left, v)
|
||||
end
|
||||
|
||||
return left
|
||||
end,
|
||||
|
||||
map = tbl_merge,
|
||||
set = tbl_merge,
|
||||
}
|
||||
|
||||
--- Handles the '+' operator
|
||||
local function add_value(info, current, new)
|
||||
return add_methods[info.metatype](
|
||||
convert_value_to_lua(info, current),
|
||||
convert_value_to_lua(info, new)
|
||||
)
|
||||
end
|
||||
|
||||
--- @param t table<any,any>
|
||||
--- @param val any
|
||||
local function remove_one_item(t, val)
|
||||
if vim.islist(t) then
|
||||
local remove_index = nil
|
||||
for i, v in ipairs(t) do
|
||||
if v == val then
|
||||
remove_index = i
|
||||
end
|
||||
end
|
||||
|
||||
if remove_index then
|
||||
table.remove(t, remove_index)
|
||||
end
|
||||
else
|
||||
t[val] = nil
|
||||
end
|
||||
end
|
||||
|
||||
local remove_methods = {
|
||||
--- @param left integer
|
||||
--- @param right integer
|
||||
number = function(left, right)
|
||||
return left - right
|
||||
end,
|
||||
|
||||
string = function()
|
||||
error('Subtraction not supported for strings.')
|
||||
end,
|
||||
|
||||
--- @param left string[]
|
||||
--- @param right string[]
|
||||
--- @return string[]
|
||||
array = function(left, right)
|
||||
if type(right) == 'string' then
|
||||
remove_one_item(left, right)
|
||||
else
|
||||
for _, v in ipairs(right) do
|
||||
remove_one_item(left, v)
|
||||
end
|
||||
end
|
||||
|
||||
return left
|
||||
end,
|
||||
|
||||
map = tbl_remove,
|
||||
set = tbl_remove,
|
||||
}
|
||||
|
||||
--- Handles the '-' operator
|
||||
local function remove_value(info, current, new)
|
||||
return remove_methods[info.metatype](convert_value_to_lua(info, current), new)
|
||||
end
|
||||
|
||||
local function create_option_accessor(scope)
|
||||
--- @diagnostic disable-next-line: no-unknown
|
||||
local option_mt
|
||||
|
||||
local function make_option(name, value)
|
||||
local info = get_options_info(name) or error('Not a valid option name: ' .. name)
|
||||
|
||||
local function make_option(name, value, op_count)
|
||||
if type(value) == 'table' and getmetatable(value) == option_mt then
|
||||
assert(name == value._name, "must be the same value, otherwise that's weird.")
|
||||
|
||||
@@ -701,50 +580,63 @@ local function create_option_accessor(scope)
|
||||
return setmetatable({
|
||||
_name = name,
|
||||
_value = value,
|
||||
_info = info,
|
||||
_op_count = op_count,
|
||||
}, option_mt)
|
||||
end
|
||||
|
||||
option_mt = {
|
||||
-- To set a value, instead use:
|
||||
-- opt[my_option] = value
|
||||
_set = function(self)
|
||||
local value = convert_value_to_vim(self._name, self._info, self._value)
|
||||
api.nvim_set_option_value(self._name, value, { scope = scope })
|
||||
end,
|
||||
|
||||
get = function(self)
|
||||
return convert_value_to_lua(self._info, self._value)
|
||||
return M.convert_value_to_lua(self._name, self._value)
|
||||
end,
|
||||
|
||||
append = function(self, right)
|
||||
--- @diagnostic disable-next-line: no-unknown
|
||||
self._value = add_value(self._info, self._value, right)
|
||||
self:_set()
|
||||
vim.api.nvim_set_option_value(self._name, right, { operation = 'append', scope = scope })
|
||||
end,
|
||||
|
||||
__infix = function(self, right, operation)
|
||||
-- TODO(kylesower): support multiple infix operations. Right now this
|
||||
-- doesn't work because nvim_set_option_value uses get_option_newval to
|
||||
-- merge the values, and that always expects a varp pointer that points
|
||||
-- to the existing option value. Thus, when a new value is computed with
|
||||
-- an infix op, but the option isn't updated, subsequent infix ops still
|
||||
-- use the outdated option value.
|
||||
-- This could be resolved by updating the option value when computing
|
||||
-- the infix ops; however, this would then turn the infix ops into
|
||||
-- assignments. The full solution to the problem requires computing the
|
||||
-- infix op of two arbitrary values (not just one value compared to an
|
||||
-- existing option).
|
||||
if self._op_count > 0 then
|
||||
error('Multiple vim.opt infix operations unsupported')
|
||||
end
|
||||
return make_option(
|
||||
self._name,
|
||||
vim.api.nvim_set_option_value(
|
||||
self._name,
|
||||
right,
|
||||
{ operation = operation, scope = scope, dry_run = true }
|
||||
),
|
||||
self._op_count + 1
|
||||
)
|
||||
end,
|
||||
|
||||
__add = function(self, right)
|
||||
return make_option(self._name, add_value(self._info, self._value, right))
|
||||
return self:__infix(right, 'append')
|
||||
end,
|
||||
|
||||
prepend = function(self, right)
|
||||
--- @diagnostic disable-next-line: no-unknown
|
||||
self._value = prepend_value(self._info, self._value, right)
|
||||
self:_set()
|
||||
vim.api.nvim_set_option_value(self._name, right, { operation = 'prepend', scope = scope })
|
||||
end,
|
||||
|
||||
__pow = function(self, right)
|
||||
return make_option(self._name, prepend_value(self._info, self._value, right))
|
||||
return self:__infix(right, 'prepend')
|
||||
end,
|
||||
|
||||
remove = function(self, right)
|
||||
--- @diagnostic disable-next-line: no-unknown
|
||||
self._value = remove_value(self._info, self._value, right)
|
||||
self:_set()
|
||||
vim.api.nvim_set_option_value(self._name, right, { operation = 'remove', scope = scope })
|
||||
end,
|
||||
|
||||
__sub = function(self, right)
|
||||
return make_option(self._name, remove_value(self._info, self._value, right))
|
||||
return self:__infix(right, 'remove')
|
||||
end,
|
||||
}
|
||||
option_mt.__index = option_mt
|
||||
@@ -754,11 +646,12 @@ local function create_option_accessor(scope)
|
||||
-- vim.opt_global must get global value only
|
||||
-- vim.opt_local may fall back to global value like vim.opt
|
||||
local opts = { scope = scope == 'global' and 'global' or nil }
|
||||
return make_option(k, api.nvim_get_option_value(k, opts))
|
||||
return make_option(k, api.nvim_get_option_value(k, opts), 0)
|
||||
end,
|
||||
|
||||
__newindex = function(_, k, v)
|
||||
make_option(k, v):_set()
|
||||
local option = make_option(k, v, 0)
|
||||
api.nvim_set_option_value(option._name, option._value, { scope = scope })
|
||||
end,
|
||||
})
|
||||
end
|
||||
@@ -927,3 +820,5 @@ vim.opt_local = create_option_accessor('local')
|
||||
|
||||
--- @nodoc
|
||||
vim.opt_global = create_option_accessor('global')
|
||||
|
||||
return M
|
||||
|
||||
6
runtime/lua/vim/_meta/api.gen.lua
generated
6
runtime/lua/vim/_meta/api.gen.lua
generated
@@ -2305,12 +2305,18 @@ function vim.api.nvim_set_option(name, value) end
|
||||
--- @param value any New option value
|
||||
--- @param opts vim.api.keyset.option Optional parameters
|
||||
--- - buf: Buffer number. Used for setting buffer local option.
|
||||
--- - dry_run: (`boolean?`, default: false) If true, then the
|
||||
--- option value won't be set.
|
||||
--- - operation: One of "set", "append", "prepend", or "remove".
|
||||
--- Corresponds to `:set=`, `:set+=`, `:set^=`, and `:set-=`.
|
||||
--- Default is "set".
|
||||
--- - scope: One of "global" or "local". Analogous to
|
||||
--- `:setglobal` and `:setlocal`, respectively.
|
||||
--- - tab: `tab-ID` for tab-local options (currently only 'cmdheight'). Tabpage 0
|
||||
--- means the current tabpage. If a non-current tab is given, the value will take
|
||||
--- effect when it is switched-to.
|
||||
--- - win: `window-ID`. Used for setting window local option.
|
||||
--- @return any # Option value
|
||||
function vim.api.nvim_set_option_value(name, value, opts) end
|
||||
|
||||
--- Sets a global (g:) variable.
|
||||
|
||||
2
runtime/lua/vim/_meta/api_keysets.gen.lua
generated
2
runtime/lua/vim/_meta/api_keysets.gen.lua
generated
@@ -381,7 +381,9 @@ error('Cannot require a meta file')
|
||||
|
||||
--- @class vim.api.keyset.option
|
||||
--- @field buf? integer
|
||||
--- @field dry_run? boolean
|
||||
--- @field filetype? string
|
||||
--- @field operation? string
|
||||
--- @field scope? string
|
||||
--- @field tab? integer
|
||||
--- @field win? integer
|
||||
|
||||
@@ -170,6 +170,8 @@ typedef struct {
|
||||
Buffer buf;
|
||||
Tabpage tab;
|
||||
String filetype;
|
||||
String operation;
|
||||
Boolean dry_run;
|
||||
} Dict(option);
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -13,11 +13,14 @@
|
||||
#include "nvim/buffer.h"
|
||||
#include "nvim/buffer_defs.h"
|
||||
#include "nvim/globals.h"
|
||||
#include "nvim/lua/executor.h"
|
||||
#include "nvim/memline.h"
|
||||
#include "nvim/memory.h"
|
||||
#include "nvim/memory_defs.h"
|
||||
#include "nvim/option.h"
|
||||
#include "nvim/option_defs.h"
|
||||
#include "nvim/option_vars.h"
|
||||
#include "nvim/strings.h"
|
||||
#include "nvim/types_defs.h"
|
||||
#include "nvim/vim_defs.h"
|
||||
#include "nvim/window.h"
|
||||
@@ -26,7 +29,8 @@
|
||||
|
||||
static int validate_option_value_args(Dict(option) *opts, char *name, bool allow_tab,
|
||||
OptIndex *opt_idxp, int *opt_flags, OptScope *scope,
|
||||
void **from, char **filetype, Error *err)
|
||||
void **from, char **filetype, set_op_T *operation,
|
||||
bool *dry_run, Error *err)
|
||||
{
|
||||
#define HAS_KEY_X(d, v) HAS_KEY(d, option, v)
|
||||
// Validate incompatible argument combinations first, then resolve handles and scope.
|
||||
@@ -105,6 +109,34 @@ static int validate_option_value_args(Dict(option) *opts, char *name, bool allow
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
if (operation != NULL && HAS_KEY_X(opts, operation)) {
|
||||
if (strequal(opts->operation.data, "set")) {
|
||||
*operation = OP_NONE;
|
||||
} else if (strequal(opts->operation.data, "append")) {
|
||||
*operation = OP_ADDING;
|
||||
} else if (strequal(opts->operation.data, "prepend")) {
|
||||
*operation = OP_PREPENDING;
|
||||
} else if (strequal(opts->operation.data, "remove")) {
|
||||
*operation = OP_REMOVING;
|
||||
} else {
|
||||
VALIDATE_EXP(false, "operation", "'set', 'append', 'prepend', or 'remove'", NULL, {
|
||||
return FAIL;
|
||||
});
|
||||
}
|
||||
|
||||
VALIDATE_CON(*operation == OP_NONE || option_has_type(*opt_idxp,
|
||||
kOptValTypeString)
|
||||
|| option_has_type(*opt_idxp, kOptValTypeNumber),
|
||||
opts->operation.data,
|
||||
"boolean options", {
|
||||
return FAIL;
|
||||
});
|
||||
}
|
||||
|
||||
if (dry_run != NULL && HAS_KEY_X(opts, dry_run)) {
|
||||
*dry_run = opts->dry_run;
|
||||
}
|
||||
|
||||
// Reject keys whose scope the option doesn't support.
|
||||
VALIDATE_CON(!HAS_KEY_X(opts, tab) || option_has_scope(*opt_idxp, kOptScopeTab),
|
||||
"tab", name, { return FAIL; });
|
||||
@@ -235,7 +267,7 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
|
||||
char *filetype = NULL;
|
||||
|
||||
if (!validate_option_value_args(opts, name.data, true, &opt_idx, &opt_flags, &scope, &from,
|
||||
&filetype, err)) {
|
||||
&filetype, NULL, NULL, err)) {
|
||||
return (Object)OBJECT_INIT;
|
||||
}
|
||||
|
||||
@@ -288,6 +320,11 @@ err:
|
||||
/// @param value New option value
|
||||
/// @param opts Optional parameters
|
||||
/// - buf: Buffer number. Used for setting buffer local option.
|
||||
/// - dry_run: (`boolean?`, default: false) If true, then the
|
||||
/// option value won't be set.
|
||||
/// - operation: One of "set", "append", "prepend", or "remove".
|
||||
/// Corresponds to |:set=|, |:set+=|, |:set^=|, and |:set-=|.
|
||||
/// Default is "set".
|
||||
/// - scope: One of "global" or "local". Analogous to
|
||||
/// |:setglobal| and |:setlocal|, respectively.
|
||||
/// - tab: |tab-ID| for tab-local options (currently only 'cmdheight'). Tabpage 0
|
||||
@@ -295,17 +332,20 @@ err:
|
||||
/// effect when it is switched-to.
|
||||
/// - win: |window-ID|. Used for setting window local option.
|
||||
/// @param[out] err Error details, if any
|
||||
void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict(option) *opts,
|
||||
Error *err)
|
||||
/// @return Option value
|
||||
Object nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict(option) *opts,
|
||||
Arena *arena, Error *err)
|
||||
FUNC_API_SINCE(9)
|
||||
{
|
||||
OptIndex opt_idx = 0;
|
||||
int opt_flags = 0;
|
||||
OptScope scope = kOptScopeGlobal;
|
||||
set_op_T operation = OP_NONE;
|
||||
void *to = NULL;
|
||||
bool dry_run = false;
|
||||
if (!validate_option_value_args(opts, name.data, true, &opt_idx, &opt_flags, &scope, &to, NULL,
|
||||
err)) {
|
||||
return;
|
||||
&operation, &dry_run, err)) {
|
||||
return NIL;
|
||||
}
|
||||
|
||||
// If:
|
||||
@@ -320,20 +360,99 @@ void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict(
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the incoming Lua object (which could be a table) into the proper
|
||||
// OptVal type (string even for list/dict style options)
|
||||
Error lua_err = ERROR_INIT;
|
||||
MAXSIZE_TEMP_ARRAY(args, 3);
|
||||
ADD_C(args, STRING_OBJ(name));
|
||||
ADD_C(args, value);
|
||||
ADD_C(args, CSTR_AS_OBJ(set_op_get_name(operation)));
|
||||
Object vim_val =
|
||||
NLUA_EXEC_STATIC("return require('vim._core.options').convert_value_to_vim(...)",
|
||||
args, kRetObject, NULL, &lua_err);
|
||||
VALIDATE(!ERROR_SET(&lua_err), "%s", lua_err.msg, {
|
||||
api_clear_error(&lua_err);
|
||||
return NIL;
|
||||
});
|
||||
bool error = false;
|
||||
OptVal optval = object_as_optval(value, &error);
|
||||
OptVal optval_right = object_as_optval(vim_val, &error);
|
||||
|
||||
// Handle invalid option value type.
|
||||
// Don't use `name` in the error message here, because `name` can be any String.
|
||||
// No need to check if value type actually matches the types for the option, as set_option_value()
|
||||
// already handles that.
|
||||
VALIDATE_EXP(!error, "value", "valid option type", api_typename(value.type), {
|
||||
return;
|
||||
return NIL;
|
||||
});
|
||||
|
||||
WITH_SCRIPT_CONTEXT(channel_id, {
|
||||
set_option_value_for(name.data, opt_idx, optval, opt_flags, scope, to, err);
|
||||
});
|
||||
OptVal merged_val = NIL_OPTVAL;
|
||||
const char *errmsg = NULL;
|
||||
vimoption_T *option = get_option(opt_idx);
|
||||
|
||||
// Need to use varp specific to buf/win to ensure that merges are handled
|
||||
// correctly when the supplied buf/win are different than curbuf/curwin.
|
||||
buf_T *buf = scope == kOptScopeBuf ? to : curbuf;
|
||||
win_T *win = scope == kOptScopeWin ? to : curwin;
|
||||
void *varp = get_varp_from(option, buf, win);
|
||||
char *argp = NULL;
|
||||
|
||||
switch (optval_right.type) {
|
||||
case kOptValTypeNil:
|
||||
break;
|
||||
case kOptValTypeString: {
|
||||
char *optval_escaped = escape_option_str_cmdline(optval_right.data.string.data);
|
||||
// We need a leading equal sign because get_option_newval is used for
|
||||
// cmdline stuff and expects an =
|
||||
argp = arena_printf(arena, "=%s", optval_escaped).data;
|
||||
XFREE_CLEAR(optval_escaped);
|
||||
break;
|
||||
}
|
||||
case kOptValTypeNumber:
|
||||
argp = arena_printf(arena, "=%" PRId64, optval_right.data.number).data;
|
||||
break;
|
||||
case kOptValTypeBoolean:
|
||||
merged_val = optval_right;
|
||||
break;
|
||||
}
|
||||
|
||||
optval_free(optval_right);
|
||||
|
||||
if (optval_right.type == kOptValTypeNumber || optval_right.type == kOptValTypeString) {
|
||||
OptVal oldval = optval_from_varp(opt_idx, varp);
|
||||
merged_val = get_option_newval(opt_idx, opt_flags, PREFIX_NONE, &argp, 0, operation,
|
||||
option->flags, varp, &oldval, NULL, 0, &errmsg);
|
||||
VALIDATE(errmsg == NULL, "%s", errmsg, {
|
||||
return NIL;
|
||||
});
|
||||
}
|
||||
|
||||
if (!dry_run) {
|
||||
WITH_SCRIPT_CONTEXT(channel_id, {
|
||||
set_option_value_for(name.data, opt_idx, merged_val, opt_flags, scope, to, err);
|
||||
});
|
||||
}
|
||||
|
||||
if (merged_val.type == kOptValTypeString) {
|
||||
// Convert the return type to lua for string/list/map style option
|
||||
lua_err = ERROR_INIT;
|
||||
MAXSIZE_TEMP_ARRAY(lua_args, 2);
|
||||
ADD_C(lua_args, STRING_OBJ(name));
|
||||
ADD_C(lua_args, STRING_OBJ(merged_val.data.string));
|
||||
Object lua_val =
|
||||
NLUA_EXEC_STATIC("return require('vim._core.options').convert_value_to_lua(...)",
|
||||
lua_args, kRetObject, arena, &lua_err);
|
||||
|
||||
optval_free(merged_val);
|
||||
|
||||
VALIDATE(!ERROR_SET(&lua_err), "%s", lua_err.msg, {
|
||||
api_clear_error(&lua_err);
|
||||
return NIL;
|
||||
});
|
||||
|
||||
return lua_val;
|
||||
}
|
||||
|
||||
return optval_as_object(merged_val);
|
||||
}
|
||||
|
||||
/// Gets the option information for all options.
|
||||
@@ -393,7 +512,7 @@ DictAs(get_option_info) nvim_get_option_info2(String name, Dict(option) *opts, A
|
||||
void *from = NULL;
|
||||
// TODO(justinmk): support tab-local option.
|
||||
if (!validate_option_value_args(opts, name.data, false, &opt_idx, &opt_flags, &scope, &from, NULL,
|
||||
err)) {
|
||||
NULL, NULL, err)) {
|
||||
return (Dict)ARRAY_DICT_INIT;
|
||||
}
|
||||
|
||||
|
||||
@@ -151,13 +151,6 @@ static char *p_vsts_nopaste;
|
||||
|
||||
#define OPTION_COUNT ARRAY_SIZE(options)
|
||||
|
||||
/// :set boolean option prefix
|
||||
typedef enum {
|
||||
PREFIX_NO = 0, ///< "no" prefix
|
||||
PREFIX_NONE, ///< no prefix
|
||||
PREFIX_INV, ///< "inv" prefix
|
||||
} set_prefix_T;
|
||||
|
||||
#include "option.c.generated.h"
|
||||
|
||||
// options[] is initialized in options.generated.h.
|
||||
@@ -1100,14 +1093,15 @@ 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}={val}
|
||||
/// set {opt}:{val}
|
||||
static char *stropt_get_newval(int nextchar, OptIndex opt_idx, char **argp, void *varp,
|
||||
const char *origval, set_op_T *op_arg, uint32_t flags)
|
||||
static char *stropt_get_newval(OptIndex opt_idx, char **argp, void *varp, const char *origval,
|
||||
set_op_T *op_arg)
|
||||
{
|
||||
char *arg = *argp;
|
||||
set_op_T op = *op_arg;
|
||||
char *save_arg = NULL;
|
||||
char *newval;
|
||||
const char *s = NULL;
|
||||
uint32_t flags = options[opt_idx].flags;
|
||||
|
||||
arg++; // jump to after the '=' or ':'
|
||||
|
||||
@@ -1326,18 +1320,29 @@ 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)
|
||||
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,
|
||||
OptVal *oldval_override, char *errbuf, const size_t errbuflen,
|
||||
const char **errmsg)
|
||||
FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
assert(varp != NULL);
|
||||
|
||||
vimoption_T *opt = &options[opt_idx];
|
||||
char *arg = *argp;
|
||||
// When setting the local value of a global option, the old value may be the global value.
|
||||
const bool oldval_is_global = option_is_global_local(opt_idx) && (opt_flags & OPT_LOCAL);
|
||||
OptVal oldval = optval_from_varp(opt_idx, oldval_is_global ? get_varp(opt) : varp);
|
||||
|
||||
OptVal oldval;
|
||||
if (oldval_override != NULL) {
|
||||
// Allow overriding the oldval. This is needed to handle the case where
|
||||
// options for buffers/windows other than curbuf/curwin are updated. It can
|
||||
// also support merging arbitrary values if necessary down the road.
|
||||
oldval = *oldval_override;
|
||||
} else {
|
||||
// When setting the local value of a global option, the old value may be the global value.
|
||||
const bool oldval_is_global = option_is_global_local(opt_idx) && (opt_flags & OPT_LOCAL);
|
||||
oldval = optval_from_varp(opt_idx, oldval_is_global ? get_varp(opt) : varp);
|
||||
}
|
||||
|
||||
OptVal newval = NIL_OPTVAL;
|
||||
|
||||
if (nextchar == '&') {
|
||||
@@ -1434,8 +1439,7 @@ static OptVal get_option_newval(OptIndex opt_idx, int opt_flags, set_prefix_T pr
|
||||
case kOptValTypeString: {
|
||||
const char *oldval_str = oldval.data.string.data;
|
||||
// Get the new value for the option
|
||||
const char *newval_str = stropt_get_newval(nextchar, opt_idx, argp, varp, oldval_str, &op,
|
||||
flags);
|
||||
const char *newval_str = stropt_get_newval(opt_idx, argp, varp, oldval_str, &op);
|
||||
newval = CSTR_AS_OPTVAL(newval_str);
|
||||
break;
|
||||
}
|
||||
@@ -1555,7 +1559,7 @@ static void do_one_set_option(int opt_flags, char **argp, bool *did_show, char *
|
||||
}
|
||||
|
||||
OptVal newval = get_option_newval(opt_idx, opt_flags, prefix, argp, nextchar, op, flags, varp,
|
||||
errbuf, errbuflen, errmsg);
|
||||
NULL, errbuf, errbuflen, errmsg);
|
||||
|
||||
if (newval.type == kOptValTypeNil || *errmsg != NULL) {
|
||||
return;
|
||||
@@ -6072,7 +6076,7 @@ int ExpandSettings(expand_T *xp, regmatch_T *regmatch, char *fuzzystr, int *numM
|
||||
|
||||
/// Escape an option value that can be used on the command-line with :set.
|
||||
/// Caller needs to free the returned string, unless NULL is returned.
|
||||
static char *escape_option_str_cmdline(char *var)
|
||||
char *escape_option_str_cmdline(char *var)
|
||||
{
|
||||
// A backslash is required before some characters. This is the reverse of
|
||||
// what happens in do_set().
|
||||
|
||||
@@ -33,6 +33,13 @@ typedef enum {
|
||||
OPT_SKIPRTP = 0x80, ///< "skiprtp" in 'sessionoptions'
|
||||
} OptionSetFlags;
|
||||
|
||||
/// :set boolean option prefix
|
||||
typedef enum {
|
||||
PREFIX_NO = 0, ///< "no" prefix
|
||||
PREFIX_NONE, ///< no prefix
|
||||
PREFIX_INV, ///< "inv" prefix
|
||||
} set_prefix_T;
|
||||
|
||||
/// Get name of OptValType as a string.
|
||||
static inline const char *optval_type_get_name(const OptValType type)
|
||||
{
|
||||
@@ -49,6 +56,22 @@ static inline const char *optval_type_get_name(const OptValType type)
|
||||
UNREACHABLE;
|
||||
}
|
||||
|
||||
/// Get name of set_op_T as a string
|
||||
static inline const char *set_op_get_name(const set_op_T op)
|
||||
{
|
||||
switch (op) {
|
||||
case OP_NONE:
|
||||
return "set";
|
||||
case OP_ADDING:
|
||||
return "append";
|
||||
case OP_PREPENDING:
|
||||
return "prepend";
|
||||
case OP_REMOVING:
|
||||
return "remove";
|
||||
}
|
||||
UNREACHABLE;
|
||||
}
|
||||
|
||||
// OptVal helper macros.
|
||||
#define NIL_OPTVAL ((OptVal) { .type = kOptValTypeNil })
|
||||
#define BOOLEAN_OPTVAL(b) ((OptVal) { .type = kOptValTypeBoolean, .data.boolean = b })
|
||||
|
||||
@@ -1940,16 +1940,16 @@ describe('API', function()
|
||||
"Invalid 'scope': expected String, got Integer",
|
||||
pcall_err(api.nvim_get_option_value, 'scrolloff', { scope = 42 })
|
||||
)
|
||||
eq(
|
||||
"Invalid 'value': expected valid option type, got Array",
|
||||
matches(
|
||||
"Invalid option type 'table' for 'scrolloff', should be number",
|
||||
pcall_err(api.nvim_set_option_value, 'scrolloff', {}, {})
|
||||
)
|
||||
eq(
|
||||
"Invalid value for option 'scrolloff': expected number, got boolean true",
|
||||
matches(
|
||||
"Invalid option type 'boolean' for 'scrolloff', should be number",
|
||||
pcall_err(api.nvim_set_option_value, 'scrolloff', true, {})
|
||||
)
|
||||
eq(
|
||||
'Invalid value for option \'scrolloff\': expected number, got string "wrong"',
|
||||
matches(
|
||||
"Invalid option type 'string' for 'scrolloff', should be number",
|
||||
pcall_err(api.nvim_set_option_value, 'scrolloff', 'wrong', {})
|
||||
)
|
||||
local tab1 = api.nvim_get_current_tabpage()
|
||||
@@ -2243,6 +2243,161 @@ describe('API', function()
|
||||
eq(0, eval('g:modeline'))
|
||||
eq(1, eval('g:bufloaded'))
|
||||
end)
|
||||
|
||||
it('expands env vars', function()
|
||||
-- I don't know why setting vim.env.INCLUDE doesn't work here.
|
||||
clear { env = { INCLUDE = '/dev/null' } }
|
||||
api.nvim_set_option_value('path', '$INCLUDE', {})
|
||||
eq('/dev/null', api.nvim_get_option_value('path', {}))
|
||||
end)
|
||||
|
||||
it('expands ~', function()
|
||||
clear { env = { HOME = '/dev/null' } }
|
||||
api.nvim_set_option_value('rtp', '~', {})
|
||||
eq('/dev/null', api.nvim_get_option_value('rtp', {}))
|
||||
end)
|
||||
|
||||
it('allows setting, appending, prepending, and removing lists', function()
|
||||
api.nvim_set_option_value('wildignore', { '*.o', '*.obj' }, {})
|
||||
eq('*.o,*.obj', api.nvim_get_option_value('wildignore', {}))
|
||||
|
||||
api.nvim_set_option_value('wildignore', { '*.a', '*.b', '*.c' }, { operation = 'append' })
|
||||
eq('*.o,*.obj,*.a,*.b,*.c', api.nvim_get_option_value('wildignore', {}))
|
||||
|
||||
api.nvim_set_option_value('wildignore', { '1', '2', '3' }, { operation = 'prepend' })
|
||||
eq('1,2,3,*.o,*.obj,*.a,*.b,*.c', api.nvim_get_option_value('wildignore', {}))
|
||||
|
||||
-- NOTE: you can't remove something like { '1', '*.obj' } because lists
|
||||
-- only support removing items in order. Behavior matches :set-=
|
||||
api.nvim_set_option_value('wildignore', { '1', '2', '3' }, { operation = 'remove' })
|
||||
eq('*.o,*.obj,*.a,*.b,*.c', api.nvim_get_option_value('wildignore', {}))
|
||||
end)
|
||||
|
||||
it('allows setting, appending, prepending, removing tables', function()
|
||||
-- NOTE: order is dependent on lua's hash map implementation. I don't
|
||||
-- *think* order matters for the map style options
|
||||
api.nvim_set_option_value('listchars', { eol = '~', space = '-' }, {})
|
||||
eq('eol:~,space:-', api.nvim_get_option_value('listchars', {}))
|
||||
|
||||
api.nvim_set_option_value(
|
||||
'listchars',
|
||||
{ multispace = '---+', tab = '>-' },
|
||||
{ operation = 'append' }
|
||||
)
|
||||
eq('eol:~,space:-,multispace:---+,tab:>-', api.nvim_get_option_value('listchars', {}))
|
||||
|
||||
api.nvim_set_option_value('listchars', { lead = '.' }, { operation = 'prepend' })
|
||||
eq('lead:.,eol:~,space:-,multispace:---+,tab:>-', api.nvim_get_option_value('listchars', {}))
|
||||
|
||||
api.nvim_set_option_value(
|
||||
'listchars',
|
||||
{ lead = '', multispace = '' },
|
||||
{ operation = 'remove' }
|
||||
)
|
||||
eq('eol:~,space:-,tab:>-', api.nvim_get_option_value('listchars', {}))
|
||||
end)
|
||||
|
||||
it('returns the new option value', function()
|
||||
eq(
|
||||
{ eol = '~', space = '-' },
|
||||
api.nvim_set_option_value('listchars', { eol = '~', space = '-' }, {})
|
||||
)
|
||||
eq('eol:~,space:-', api.nvim_get_option_value('listchars', {}))
|
||||
eq(
|
||||
{ eol = '~', space = '-', tab = '>-' },
|
||||
api.nvim_set_option_value('listchars', { tab = '>-' }, { operation = 'append' })
|
||||
)
|
||||
eq('eol:~,space:-,tab:>-', api.nvim_get_option_value('listchars', {}))
|
||||
end)
|
||||
|
||||
it("doesn't update option value with dry_run flag", function()
|
||||
local listchars = api.nvim_get_option_value('listchars', {})
|
||||
eq(
|
||||
{ eol = '~', space = '-' },
|
||||
api.nvim_set_option_value('listchars', { eol = '~', space = '-' }, { dry_run = true })
|
||||
)
|
||||
eq(listchars, api.nvim_get_option_value('listchars', {}))
|
||||
end)
|
||||
|
||||
it('merges options against non-current window', function()
|
||||
api.nvim_set_option_value('listchars', {}, { operation = 'set' })
|
||||
|
||||
local oldwin = fn.win_getid()
|
||||
command('split')
|
||||
local curwin = fn.win_getid()
|
||||
neq(oldwin, curwin)
|
||||
|
||||
eq(
|
||||
{ eol = '~' },
|
||||
api.nvim_set_option_value(
|
||||
'listchars',
|
||||
{ eol = '~' },
|
||||
{ operation = 'append', win = oldwin }
|
||||
)
|
||||
)
|
||||
eq(
|
||||
{ tab = '>-' },
|
||||
api.nvim_set_option_value(
|
||||
'listchars',
|
||||
{ tab = '>-' },
|
||||
{ operation = 'append', win = curwin }
|
||||
)
|
||||
)
|
||||
eq(
|
||||
{ eol = '~', space = '-' },
|
||||
api.nvim_set_option_value(
|
||||
'listchars',
|
||||
{ space = '-' },
|
||||
{ operation = 'append', win = oldwin }
|
||||
)
|
||||
)
|
||||
eq(
|
||||
{ lead = '.', tab = '>-' },
|
||||
api.nvim_set_option_value(
|
||||
'listchars',
|
||||
{ lead = '.' },
|
||||
{ operation = 'append', win = curwin }
|
||||
)
|
||||
)
|
||||
end)
|
||||
|
||||
it('merges options against non-current buffer', function()
|
||||
eq({}, api.nvim_set_option_value('completeopt', {}, { operation = 'set' }))
|
||||
|
||||
local oldbuf = fn.bufnr()
|
||||
command('new')
|
||||
local curbuf = fn.bufnr()
|
||||
neq(oldbuf, curbuf)
|
||||
|
||||
eq(
|
||||
{ 'fuzzy' },
|
||||
api.nvim_set_option_value(
|
||||
'completeopt',
|
||||
{ 'fuzzy' },
|
||||
{ operation = 'append', buf = oldbuf }
|
||||
)
|
||||
)
|
||||
eq(
|
||||
{ 'longest' },
|
||||
api.nvim_set_option_value(
|
||||
'completeopt',
|
||||
{ 'longest' },
|
||||
{ operation = 'append', buf = curbuf }
|
||||
)
|
||||
)
|
||||
eq(
|
||||
{ 'fuzzy', 'menu' },
|
||||
api.nvim_set_option_value('completeopt', { 'menu' }, { operation = 'append', buf = oldbuf })
|
||||
)
|
||||
eq(
|
||||
{ 'longest', 'noinsert' },
|
||||
api.nvim_set_option_value(
|
||||
'completeopt',
|
||||
{ 'noinsert' },
|
||||
{ operation = 'append', buf = curbuf }
|
||||
)
|
||||
)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('nvim_{get,set}_current_buf, nvim_list_bufs', function()
|
||||
|
||||
@@ -526,6 +526,95 @@ describe('lua stdlib', function()
|
||||
end)
|
||||
|
||||
describe('options', function()
|
||||
describe('vim.o', function()
|
||||
it('works for boolean values', function()
|
||||
eq_exec_lua(false, function()
|
||||
vim.o.number = false
|
||||
return vim.o.number
|
||||
end)
|
||||
end)
|
||||
|
||||
it('works for number values', function()
|
||||
eq_exec_lua(10, function()
|
||||
vim.o.tabstop = 10
|
||||
return vim.o.tabstop
|
||||
end)
|
||||
end)
|
||||
|
||||
it('works for string values', function()
|
||||
eq_exec_lua('hello world', function()
|
||||
vim.o.makeprg = 'hello world'
|
||||
return vim.o.makeprg
|
||||
end)
|
||||
end)
|
||||
|
||||
it('works for array type options', function()
|
||||
eq_exec_lua('hello,world', function()
|
||||
vim.o.wildignore = { 'hello', 'world' }
|
||||
return vim.o.wildignore
|
||||
end)
|
||||
end)
|
||||
|
||||
it('works for key:value type options using string', function()
|
||||
eq_exec_lua('eol:~,space:-', function()
|
||||
vim.o.listchars = 'eol:~,space:-'
|
||||
return vim.o.listchars
|
||||
end)
|
||||
end)
|
||||
|
||||
it('works for key:value type options using list of strings', function()
|
||||
eq_exec_lua('eol:~,space:-', function()
|
||||
vim.o.listchars = { 'eol:~', 'space:-' }
|
||||
return vim.o.listchars
|
||||
end)
|
||||
end)
|
||||
|
||||
it('works for key:value type options using table', function()
|
||||
eq_exec_lua('eol:~,space:.', function()
|
||||
vim.o.listchars = { eol = '~', space = '.' }
|
||||
return vim.o.listchars
|
||||
end)
|
||||
end)
|
||||
|
||||
it('works for set type flaglists', function()
|
||||
eq_exec_lua('tcro', function()
|
||||
vim.o.formatoptions = 'tcro'
|
||||
return vim.o.formatoptions
|
||||
end)
|
||||
end)
|
||||
|
||||
it('works for set type flaglists using tables', function()
|
||||
eq_exec_lua({ t = true, c = true, r = true, o = true }, function()
|
||||
vim.o.formatoptions = { t = true, c = true, r = true, o = true }
|
||||
-- use vim.opt to convert to lua value
|
||||
return vim.opt.formatoptions:get()
|
||||
end)
|
||||
end)
|
||||
|
||||
it('works for weird number options', function()
|
||||
eq_exec_lua(9, function()
|
||||
vim.o.wildchar = '<Tab>'
|
||||
return vim.o.wildchar
|
||||
end)
|
||||
end)
|
||||
|
||||
it('expands env vars', function()
|
||||
eq_exec_lua('/dev/null', function()
|
||||
vim.fn.setenv('INCLUDE', '/dev/null')
|
||||
vim.o.path = '$INCLUDE'
|
||||
return vim.o.path
|
||||
end)
|
||||
end)
|
||||
|
||||
it('expands ~', function()
|
||||
eq_exec_lua('/dev/null', function()
|
||||
vim.fn.setenv('HOME', '/dev/null')
|
||||
vim.o.rtp = '~'
|
||||
return vim.o.rtp
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('vim.bo', function()
|
||||
it('can get and set options', function()
|
||||
eq('', fn.luaeval 'vim.bo.filetype')
|
||||
@@ -847,12 +936,17 @@ describe('lua stdlib', function()
|
||||
end)
|
||||
end)
|
||||
|
||||
it('allows adding dict style', function()
|
||||
eq_exec_lua('eol:~,space:_', function()
|
||||
vim.opt.listchars = { eol = '~', space = '.' }
|
||||
vim.opt.listchars = vim.opt.listchars + { space = '-' } + { space = '_' }
|
||||
return vim.o.listchars
|
||||
end)
|
||||
it('does not allow adding dict style multiple times', function()
|
||||
matches(
|
||||
'Multiple vim.opt infix operations unsupported',
|
||||
pcall_err(
|
||||
exec_lua,
|
||||
[[
|
||||
vim.opt.listchars = { eol = '~', space = '.' }
|
||||
vim.opt.listchars = vim.opt.listchars + { space = '-' } + { space = '_' }
|
||||
]]
|
||||
)
|
||||
)
|
||||
end)
|
||||
|
||||
it('allows completely new keys', function()
|
||||
@@ -871,20 +965,30 @@ describe('lua stdlib', function()
|
||||
end)
|
||||
end)
|
||||
|
||||
it('allows subtracting dict style', function()
|
||||
eq_exec_lua('', function()
|
||||
vim.opt.listchars = { eol = '~', space = '.' }
|
||||
vim.opt.listchars = vim.opt.listchars - 'space' - 'eol'
|
||||
return vim.o.listchars
|
||||
end)
|
||||
it('does not allow subtracting dict style multiple times', function()
|
||||
matches(
|
||||
'Multiple vim.opt infix operations unsupported',
|
||||
pcall_err(
|
||||
exec_lua,
|
||||
[[
|
||||
vim.opt.listchars = { eol = '~', space = '.' }
|
||||
vim.opt.listchars = vim.opt.listchars - 'space' - 'eol'
|
||||
]]
|
||||
)
|
||||
)
|
||||
end)
|
||||
|
||||
it('allows subtracting dict style multiple times', function()
|
||||
eq_exec_lua('eol:~', function()
|
||||
vim.opt.listchars = { eol = '~', space = '.' }
|
||||
vim.opt.listchars = vim.opt.listchars - 'space' - 'space'
|
||||
return vim.o.listchars
|
||||
end)
|
||||
it('does not allow subtracting dict style multiple times with the same key', function()
|
||||
matches(
|
||||
'Multiple vim.opt infix operations unsupported',
|
||||
pcall_err(
|
||||
exec_lua,
|
||||
[[
|
||||
vim.opt.listchars = { eol = '~', space = '.' }
|
||||
vim.opt.listchars = vim.opt.listchars - 'space' - 'space'
|
||||
]]
|
||||
)
|
||||
)
|
||||
end)
|
||||
|
||||
it('allows adding a key:value string to a listchars', function()
|
||||
@@ -898,6 +1002,7 @@ describe('lua stdlib', function()
|
||||
it('allows prepending a key:value string to a listchars', function()
|
||||
eq_exec_lua('eol:~,space:.,tab:>~', function()
|
||||
vim.opt.listchars = { eol = '~', space = '.' }
|
||||
-- Operator overloading is cursed.
|
||||
vim.opt.listchars = vim.opt.listchars ^ 'tab:>~'
|
||||
return vim.o.listchars
|
||||
end)
|
||||
@@ -965,12 +1070,17 @@ describe('lua stdlib', function()
|
||||
end)
|
||||
end)
|
||||
|
||||
it('allows adding multiple times', function()
|
||||
eq_exec_lua('foo,bar,baz', function()
|
||||
vim.opt.wildignore = 'foo'
|
||||
vim.opt.wildignore = vim.opt.wildignore + 'bar' + 'baz'
|
||||
return vim.o.wildignore
|
||||
end)
|
||||
it('does not allow adding multiple times', function()
|
||||
matches(
|
||||
'Multiple vim.opt infix operations unsupported',
|
||||
pcall_err(
|
||||
exec_lua,
|
||||
[[
|
||||
vim.opt.wildignore = 'foo'
|
||||
vim.opt.wildignore = vim.opt.wildignore + 'bar' + 'baz'
|
||||
]]
|
||||
)
|
||||
)
|
||||
end)
|
||||
|
||||
it('removes values when you use minus', function()
|
||||
@@ -1211,6 +1321,22 @@ describe('lua stdlib', function()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('expands env vars', function()
|
||||
eq_exec_lua({ '/dev/null' }, function()
|
||||
vim.env.INCLUDE = '/dev/null'
|
||||
vim.opt.path = '$INCLUDE'
|
||||
return vim.opt.path:get()
|
||||
end)
|
||||
end)
|
||||
|
||||
it('expands ~', function()
|
||||
eq_exec_lua({ '/dev/null' }, function()
|
||||
vim.env.HOME = '/dev/null'
|
||||
vim.opt.rtp = '~'
|
||||
return vim.opt.rtp:get()
|
||||
end)
|
||||
end)
|
||||
end) -- vim.opt
|
||||
|
||||
describe('vim.opt_local', function()
|
||||
|
||||
Reference in New Issue
Block a user