mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
Merge pull request #15996 from gpanders/nvim_get_option_value
feat(api): add nvim_{get,set}_option_value
This commit is contained in:
commit
1e6eeca9d1
@ -141,9 +141,9 @@ set(NVIM_VERSION_PATCH 0)
|
||||
set(NVIM_VERSION_PRERELEASE "-dev") # for package maintainers
|
||||
|
||||
# API level
|
||||
set(NVIM_API_LEVEL 8) # Bump this after any API change.
|
||||
set(NVIM_API_LEVEL 9) # Bump this after any API change.
|
||||
set(NVIM_API_LEVEL_COMPAT 0) # Adjust this after a _breaking_ API change.
|
||||
set(NVIM_API_PRERELEASE false)
|
||||
set(NVIM_API_PRERELEASE true)
|
||||
|
||||
set(NVIM_VERSION_BUILD_TYPE "${CMAKE_BUILD_TYPE}")
|
||||
# NVIM_VERSION_CFLAGS set further below.
|
||||
|
@ -133,107 +133,18 @@ do -- window option accessor
|
||||
vim.wo = new_win_opt_accessor(nil)
|
||||
end
|
||||
|
||||
--[[
|
||||
Local window setter
|
||||
|
||||
buffer options: does not get copied when split
|
||||
nvim_set_option(buf_opt, value) -> sets the default for NEW buffers
|
||||
this sets the hidden global default for buffer options
|
||||
|
||||
nvim_buf_set_option(...) -> sets the local value for the buffer
|
||||
|
||||
set opt=value, does BOTH global default AND buffer local value
|
||||
setlocal opt=value, does ONLY buffer local value
|
||||
|
||||
window options: gets copied
|
||||
does not need to call nvim_set_option because nobody knows what the heck this does⸮
|
||||
We call it anyway for more readable code.
|
||||
|
||||
|
||||
Command global value local value
|
||||
:set option=value set set
|
||||
:setlocal option=value - set
|
||||
:setglobal option=value set -
|
||||
--]]
|
||||
local function set_scoped_option(k, v, set_type)
|
||||
local info = options_info[k]
|
||||
|
||||
-- Don't let people do setlocal with global options.
|
||||
-- That is a feature that doesn't make sense.
|
||||
if set_type == SET_TYPES.LOCAL and is_global_option(info) then
|
||||
error(string.format("Unable to setlocal option: '%s', which is a global option.", k))
|
||||
end
|
||||
|
||||
-- Only `setlocal` skips setting the default/global value
|
||||
-- This will more-or-less noop for window options, but that's OK
|
||||
if set_type ~= SET_TYPES.LOCAL then
|
||||
a.nvim_set_option(k, v)
|
||||
end
|
||||
|
||||
if is_window_option(info) then
|
||||
if set_type ~= SET_TYPES.GLOBAL then
|
||||
a.nvim_win_set_option(0, k, v)
|
||||
end
|
||||
elseif is_buffer_option(info) then
|
||||
if set_type == SET_TYPES.LOCAL
|
||||
or (set_type == SET_TYPES.SET and not info.global_local) then
|
||||
a.nvim_buf_set_option(0, k, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
Local window getter
|
||||
|
||||
Command global value local value
|
||||
:set option? - display
|
||||
:setlocal option? - display
|
||||
:setglobal option? display -
|
||||
--]]
|
||||
local function get_scoped_option(k, set_type)
|
||||
local info = assert(options_info[k], "Must be a valid option: " .. tostring(k))
|
||||
|
||||
if set_type == SET_TYPES.GLOBAL or is_global_option(info) then
|
||||
return a.nvim_get_option(k)
|
||||
end
|
||||
|
||||
if is_buffer_option(info) then
|
||||
local was_set, value = pcall(a.nvim_buf_get_option, 0, k)
|
||||
if was_set then return value end
|
||||
|
||||
if info.global_local then
|
||||
return a.nvim_get_option(k)
|
||||
end
|
||||
|
||||
error("buf_get: This should not be able to happen, given my understanding of options // " .. k)
|
||||
end
|
||||
|
||||
if is_window_option(info) then
|
||||
local ok, value = pcall(a.nvim_win_get_option, 0, k)
|
||||
if ok then
|
||||
return value
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
-- vim global option
|
||||
-- this ONLY sets the global option. like `setglobal`
|
||||
vim.go = make_meta_accessor(a.nvim_get_option, a.nvim_set_option)
|
||||
vim.go = make_meta_accessor(
|
||||
function(k) return a.nvim_get_option_value(k, {scope = "global"}) end,
|
||||
function(k, v) return a.nvim_set_option_value(k, v, {scope = "global"}) end
|
||||
)
|
||||
|
||||
-- vim `set` style options.
|
||||
-- it has no additional metamethod magic.
|
||||
vim.o = make_meta_accessor(
|
||||
function(k) return get_scoped_option(k, SET_TYPES.SET) end,
|
||||
function(k, v) return set_scoped_option(k, v, SET_TYPES.SET) end
|
||||
function(k) return a.nvim_get_option_value(k, {}) end,
|
||||
function(k, v) return a.nvim_set_option_value(k, v, {}) end
|
||||
)
|
||||
|
||||
---@brief [[
|
||||
@ -389,6 +300,10 @@ local convert_value_to_vim = (function()
|
||||
}
|
||||
|
||||
return function(name, info, value)
|
||||
if value == nil then
|
||||
return vim.NIL
|
||||
end
|
||||
|
||||
local option_type = get_option_type(name, info)
|
||||
assert_valid_value(name, value, valid_types[option_type])
|
||||
|
||||
@ -671,15 +586,19 @@ local create_option_metatable = function(set_type)
|
||||
}, option_mt)
|
||||
end
|
||||
|
||||
-- TODO(tjdevries): consider supporting `nil` for set to remove the local option.
|
||||
-- vim.cmd [[set option<]]
|
||||
local scope
|
||||
if set_type == SET_TYPES.GLOBAL then
|
||||
scope = "global"
|
||||
elseif set_type == SET_TYPES.LOCAL then
|
||||
scope = "local"
|
||||
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)
|
||||
set_scoped_option(self._name, value, set_type)
|
||||
a.nvim_set_option_value(self._name, value, {scope = scope})
|
||||
|
||||
return self
|
||||
end,
|
||||
@ -716,7 +635,7 @@ local create_option_metatable = function(set_type)
|
||||
|
||||
set_mt = {
|
||||
__index = function(_, k)
|
||||
return make_option(k, get_scoped_option(k, set_type))
|
||||
return make_option(k, a.nvim_get_option_value(k, {scope = scope}))
|
||||
end,
|
||||
|
||||
__newindex = function(_, k, v)
|
||||
|
@ -58,5 +58,8 @@ return {
|
||||
"highlights";
|
||||
"use_tabline";
|
||||
};
|
||||
option = {
|
||||
"scope";
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -642,7 +642,7 @@ void nvim_set_vvar(String name, Object value, Error *err)
|
||||
dict_set_var(&vimvardict, name, value, false, false, err);
|
||||
}
|
||||
|
||||
/// Gets an option value string.
|
||||
/// Gets the global value of an option.
|
||||
///
|
||||
/// @param name Option name
|
||||
/// @param[out] err Error details, if any
|
||||
@ -653,6 +653,115 @@ Object nvim_get_option(String name, Error *err)
|
||||
return get_option_from(NULL, SREQ_GLOBAL, name, err);
|
||||
}
|
||||
|
||||
/// Gets the value of an option. The behavior of this function matches that of
|
||||
/// |:set|: the local value of an option is returned if it exists; otherwise,
|
||||
/// the global value is returned. Local values always correspond to the current
|
||||
/// buffer or window. To get a buffer-local or window-local option for a
|
||||
/// specific buffer or window, use |nvim_buf_get_option()| or
|
||||
/// |nvim_win_get_option()|.
|
||||
///
|
||||
/// @param name Option name
|
||||
/// @param opts Optional parameters
|
||||
/// - scope: One of 'global' or 'local'. Analagous to
|
||||
/// |:setglobal| and |:setlocal|, respectively.
|
||||
/// @param[out] err Error details, if any
|
||||
/// @return Option value
|
||||
Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
|
||||
FUNC_API_SINCE(9)
|
||||
{
|
||||
Object rv = OBJECT_INIT;
|
||||
|
||||
int scope = 0;
|
||||
if (opts->scope.type == kObjectTypeString) {
|
||||
if (!strcmp(opts->scope.data.string.data, "local")) {
|
||||
scope = OPT_LOCAL;
|
||||
} else if (!strcmp(opts->scope.data.string.data, "global")) {
|
||||
scope = OPT_GLOBAL;
|
||||
} else {
|
||||
api_set_error(err, kErrorTypeValidation, "invalid scope: must be 'local' or 'global'");
|
||||
goto end;
|
||||
}
|
||||
} else if (HAS_KEY(opts->scope)) {
|
||||
api_set_error(err, kErrorTypeValidation, "invalid value for key: scope");
|
||||
goto end;
|
||||
}
|
||||
|
||||
long numval = 0;
|
||||
char *stringval = NULL;
|
||||
switch (get_option_value(name.data, &numval, (char_u **)&stringval, scope)) {
|
||||
case 0:
|
||||
rv = STRING_OBJ(cstr_as_string(stringval));
|
||||
break;
|
||||
case 1:
|
||||
rv = INTEGER_OBJ(numval);
|
||||
break;
|
||||
case 2:
|
||||
rv = BOOLEAN_OBJ(!!numval);
|
||||
break;
|
||||
default:
|
||||
api_set_error(err, kErrorTypeValidation, "unknown option '%s'", name.data);
|
||||
goto end;
|
||||
}
|
||||
|
||||
end:
|
||||
return rv;
|
||||
}
|
||||
|
||||
/// Sets the value of an option. The behavior of this function matches that of
|
||||
/// |:set|: for global-local options, both the global and local value are set
|
||||
/// unless otherwise specified with {scope}.
|
||||
///
|
||||
/// @param name Option name
|
||||
/// @param value New option value
|
||||
/// @param opts Optional parameters
|
||||
/// - scope: One of 'global' or 'local'. Analagous to
|
||||
/// |:setglobal| and |:setlocal|, respectively.
|
||||
/// @param[out] err Error details, if any
|
||||
void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error *err)
|
||||
FUNC_API_SINCE(9)
|
||||
{
|
||||
int scope = 0;
|
||||
if (opts->scope.type == kObjectTypeString) {
|
||||
if (!strcmp(opts->scope.data.string.data, "local")) {
|
||||
scope = OPT_LOCAL;
|
||||
} else if (!strcmp(opts->scope.data.string.data, "global")) {
|
||||
scope = OPT_GLOBAL;
|
||||
} else {
|
||||
api_set_error(err, kErrorTypeValidation, "invalid scope: must be 'local' or 'global'");
|
||||
return;
|
||||
}
|
||||
} else if (HAS_KEY(opts->scope)) {
|
||||
api_set_error(err, kErrorTypeValidation, "invalid value for key: scope");
|
||||
return;
|
||||
}
|
||||
|
||||
long numval = 0;
|
||||
char *stringval = NULL;
|
||||
|
||||
switch (value.type) {
|
||||
case kObjectTypeInteger:
|
||||
numval = value.data.integer;
|
||||
break;
|
||||
case kObjectTypeBoolean:
|
||||
numval = value.data.boolean ? 1 : 0;
|
||||
break;
|
||||
case kObjectTypeString:
|
||||
stringval = value.data.string.data;
|
||||
break;
|
||||
case kObjectTypeNil:
|
||||
// Do nothing
|
||||
break;
|
||||
default:
|
||||
api_set_error(err, kErrorTypeValidation, "invalid value for option");
|
||||
return;
|
||||
}
|
||||
|
||||
char *e = set_option_value(name.data, numval, stringval, scope);
|
||||
if (e) {
|
||||
api_set_error(err, kErrorTypeException, "%s", e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the option information for all options.
|
||||
///
|
||||
/// The dictionary has the full option names as keys and option metadata
|
||||
@ -694,7 +803,7 @@ Dictionary nvim_get_option_info(String name, Error *err)
|
||||
return get_vimoption(name, err);
|
||||
}
|
||||
|
||||
/// Sets an option value.
|
||||
/// Sets the global value of an option.
|
||||
///
|
||||
/// @param channel_id
|
||||
/// @param name Option name
|
||||
|
@ -4828,7 +4828,7 @@ int get_option_tv(const char **const arg, typval_T *const rettv, const bool eval
|
||||
} else if (opt_type == -1) { // hidden number option
|
||||
rettv->v_type = VAR_NUMBER;
|
||||
rettv->vval.v_number = 0;
|
||||
} else if (opt_type == 1) { // number option
|
||||
} else if (opt_type == 1 || opt_type == 2) { // number or boolean option
|
||||
rettv->v_type = VAR_NUMBER;
|
||||
rettv->vval.v_number = numval;
|
||||
} else { // string option
|
||||
|
@ -4771,7 +4771,8 @@ static int findoption(const char *const arg)
|
||||
/// @param stringval NULL when only checking existence
|
||||
///
|
||||
/// @returns:
|
||||
/// Number or Toggle option: 1, *numval gets value.
|
||||
/// Toggle option: 2, *numval gets value.
|
||||
/// Number option: 1, *numval gets value.
|
||||
/// String option: 0, *stringval gets allocated string.
|
||||
/// Hidden Number or Toggle option: -1.
|
||||
/// hidden String option: -2.
|
||||
@ -4804,16 +4805,18 @@ int get_option_value(const char *name, long *numval, char_u **stringval, int opt
|
||||
}
|
||||
if (options[opt_idx].flags & P_NUM) {
|
||||
*numval = *(long *)varp;
|
||||
} else {
|
||||
// Special case: 'modified' is b_changed, but we also want to consider
|
||||
// it set when 'ff' or 'fenc' changed.
|
||||
if ((int *)varp == &curbuf->b_changed) {
|
||||
*numval = curbufIsChanged();
|
||||
} else {
|
||||
*numval = (long)*(int *)varp; // NOLINT(whitespace/cast)
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
|
||||
// Special case: 'modified' is b_changed, but we also want to consider
|
||||
// it set when 'ff' or 'fenc' changed.
|
||||
if ((int *)varp == &curbuf->b_changed) {
|
||||
*numval = curbufIsChanged();
|
||||
} else {
|
||||
*numval = (long)*(int *)varp; // NOLINT(whitespace/cast)
|
||||
}
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Returns the option attributes and its value. Unlike the above function it
|
||||
@ -4909,7 +4912,7 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o
|
||||
// only getting a pointer, no need to use aucmd_prepbuf()
|
||||
curbuf = (buf_T *)from;
|
||||
curwin->w_buffer = curbuf;
|
||||
varp = get_varp(p);
|
||||
varp = get_varp_scope(p, OPT_LOCAL);
|
||||
curbuf = save_curbuf;
|
||||
curwin->w_buffer = curbuf;
|
||||
}
|
||||
@ -4917,7 +4920,7 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o
|
||||
win_T *save_curwin = curwin;
|
||||
curwin = (win_T *)from;
|
||||
curbuf = curwin->w_buffer;
|
||||
varp = get_varp(p);
|
||||
varp = get_varp_scope(p, OPT_LOCAL);
|
||||
curwin = save_curwin;
|
||||
curbuf = curwin->w_buffer;
|
||||
}
|
||||
|
@ -629,6 +629,13 @@ describe('api/buf', function()
|
||||
-- Doesn't change the global value
|
||||
eq([[^\s*#\s*define]], nvim('get_option', 'define'))
|
||||
end)
|
||||
|
||||
it('returns values for unset local options', function()
|
||||
-- 'undolevels' is only set to its "unset" value when a new buffer is
|
||||
-- created
|
||||
command('enew')
|
||||
eq(-123456, curbuf('get_option', 'undolevels'))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('nvim_buf_get_name, nvim_buf_set_name', function()
|
||||
|
@ -949,6 +949,33 @@ describe('API', function()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('nvim_get_option_value, nvim_set_option_value', function()
|
||||
it('works', function()
|
||||
ok(nvim('get_option_value', 'equalalways', {}))
|
||||
nvim('set_option_value', 'equalalways', false, {})
|
||||
ok(not nvim('get_option_value', 'equalalways', {}))
|
||||
end)
|
||||
|
||||
it('can get local values when global value is set', function()
|
||||
eq(0, nvim('get_option_value', 'scrolloff', {}))
|
||||
eq(-1, nvim('get_option_value', 'scrolloff', {scope = 'local'}))
|
||||
end)
|
||||
|
||||
it('can set global and local values', function()
|
||||
nvim('set_option_value', 'makeprg', 'hello', {})
|
||||
eq('hello', nvim('get_option_value', 'makeprg', {}))
|
||||
eq('', nvim('get_option_value', 'makeprg', {scope = 'local'}))
|
||||
nvim('set_option_value', 'makeprg', 'world', {scope = 'local'})
|
||||
eq('world', nvim('get_option_value', 'makeprg', {scope = 'local'}))
|
||||
nvim('set_option_value', 'makeprg', 'goodbye', {scope = 'global'})
|
||||
eq('goodbye', nvim('get_option_value', 'makeprg', {scope = 'global'}))
|
||||
nvim('set_option_value', 'makeprg', 'hello', {})
|
||||
eq('hello', nvim('get_option_value', 'makeprg', {scope = 'global'}))
|
||||
eq('hello', nvim('get_option_value', 'makeprg', {}))
|
||||
eq('', nvim('get_option_value', 'makeprg', {scope = 'local'}))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('nvim_{get,set}_current_buf, nvim_list_bufs', function()
|
||||
it('works', function()
|
||||
eq(1, #nvim('list_bufs'))
|
||||
|
@ -222,9 +222,9 @@ describe('API/win', function()
|
||||
eq('', nvim('get_option', 'statusline'))
|
||||
command("set modified")
|
||||
command("enew") -- global-local: not preserved in new buffer
|
||||
eq("Failed to get value for option 'statusline'",
|
||||
pcall_err(curwin, 'get_option', 'statusline'))
|
||||
eq('', eval('&l:statusline')) -- confirm local value was not copied
|
||||
-- confirm local value was not copied
|
||||
eq('', curwin('get_option', 'statusline'))
|
||||
eq('', eval('&l:statusline'))
|
||||
end)
|
||||
|
||||
it('after switching windows #15390', function()
|
||||
@ -238,6 +238,10 @@ describe('API/win', function()
|
||||
eq('window-status', window('get_option', win1, 'statusline'))
|
||||
assert_alive()
|
||||
end)
|
||||
|
||||
it('returns values for unset local options', function()
|
||||
eq(-1, curwin('get_option', 'scrolloff'))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('get_position', function()
|
||||
|
@ -1240,7 +1240,7 @@ describe('lua stdlib', function()
|
||||
|
||||
vim.opt.makeprg = "global-local"
|
||||
table.insert(result, vim.api.nvim_get_option('makeprg'))
|
||||
table.insert(result, (pcall(vim.api.nvim_buf_get_option, 0, 'makeprg')))
|
||||
table.insert(result, vim.api.nvim_buf_get_option(0, 'makeprg'))
|
||||
|
||||
vim.opt_local.mp = "only-local"
|
||||
table.insert(result, vim.api.nvim_get_option('makeprg'))
|
||||
@ -1258,7 +1258,7 @@ describe('lua stdlib', function()
|
||||
|
||||
-- Set -> global & local
|
||||
eq("global-local", result[1])
|
||||
eq(false, result[2])
|
||||
eq("", result[2])
|
||||
|
||||
-- Setlocal -> only local
|
||||
eq("global-local", result[3])
|
||||
@ -1268,9 +1268,9 @@ describe('lua stdlib', function()
|
||||
eq("only-global", result[5])
|
||||
eq("only-local", result[6])
|
||||
|
||||
-- set -> doesn't override previously set value
|
||||
-- Set -> sets global value and resets local value
|
||||
eq("global-local", result[7])
|
||||
eq("only-local", result[8])
|
||||
eq("", result[8])
|
||||
end)
|
||||
|
||||
it('should allow you to retrieve window opts even if they have not been set', function()
|
||||
|
Loading…
Reference in New Issue
Block a user