diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index ea57db22e08a64..b51da8d56f14d4 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -1912,7 +1912,7 @@ nvim_get_all_options_info() *nvim_get_all_options_info()* Gets the option information for all options. The dictionary has the full option names as keys and option metadata - dictionaries as detailed at |nvim_get_option_info()|. + dictionaries as detailed at |nvim_get_option_info2()|. Return: ~ dictionary of all options @@ -1926,8 +1926,8 @@ nvim_get_option({name}) *nvim_get_option()* Return: ~ Option value (global) -nvim_get_option_info({name}) *nvim_get_option_info()* - Gets the option information for one option +nvim_get_option_info2({name}, {*opts}) *nvim_get_option_info2()* + Gets the option information for one option from arbitrary buffer or window Resulting dictionary has keys: • name: Name of the option (like 'filetype') @@ -1943,8 +1943,19 @@ nvim_get_option_info({name}) *nvim_get_option_info()* • commalist: List of comma separated values • flaglist: List of single char flags + When {scope} is not provided, the last set information applies to the + local value in the current buffer or window if it is available, otherwise + the global value information is returned. This behavior can be disabled by + explicitly specifying {scope} in the {opts} table. + Parameters: ~ • {name} Option name + • {opts} Optional parameters + • scope: One of "global" or "local". Analogous to |:setglobal| + and |:setlocal|, respectively. + • win: |window-ID|. Used for getting window local options. + • buf: Buffer number. Used for getting buffer local options. + Implies {scope} is "local". Return: ~ Option Information diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt index 69cec8da53d3fe..3eb2017bed6ad0 100644 --- a/runtime/doc/deprecated.txt +++ b/runtime/doc/deprecated.txt @@ -20,6 +20,7 @@ API - *nvim_get_hl_by_name()* Use |nvim_get_hl()| instead. - *nvim_get_hl_by_id()* Use |nvim_get_hl()| instead. - *nvim_exec()* Use |nvim_exec2()| instead. +- *nvim_get_option_info()* Use |nvim_get_option_info2()| instead. COMMANDS - *:rv* *:rviminfo* Deprecated alias to |:rshada| command. diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index 6a12cfe2da04eb..5937b2f635386c 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -20,6 +20,7 @@ #include "nvim/highlight_group.h" #include "nvim/lua/executor.h" #include "nvim/memory.h" +#include "nvim/option.h" #include "nvim/pos.h" #include "nvim/types.h" @@ -508,3 +509,16 @@ static int64_t convert_index(int64_t index) { return index < 0 ? index - 1 : index; } + +/// Gets the option information for one option +/// +/// @deprecated Use @ref nvim_get_option_info2 instead. +/// +/// @param name Option name +/// @param[out] err Error details, if any +/// @return Option Information +Dictionary nvim_get_option_info(String name, Error *err) + FUNC_API_SINCE(7) +{ + return get_vimoption(name, OPT_GLOBAL, curbuf, curwin, err); +} diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c index 7a453c01b439ea..31a2fb3b80be2a 100644 --- a/src/nvim/api/options.c +++ b/src/nvim/api/options.c @@ -283,7 +283,7 @@ void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict( /// Gets the option information for all options. /// /// The dictionary has the full option names as keys and option metadata -/// dictionaries as detailed at |nvim_get_option_info()|. +/// dictionaries as detailed at |nvim_get_option_info2()|. /// /// @return dictionary of all options Dictionary nvim_get_all_options_info(Error *err) @@ -292,7 +292,7 @@ Dictionary nvim_get_all_options_info(Error *err) return get_all_vimoptions(); } -/// Gets the option information for one option +/// Gets the option information for one option from arbitrary buffer or window /// /// Resulting dictionary has keys: /// - name: Name of the option (like 'filetype') @@ -311,15 +311,36 @@ Dictionary nvim_get_all_options_info(Error *err) /// - commalist: List of comma separated values /// - flaglist: List of single char flags /// +/// When {scope} is not provided, the last set information applies to the local +/// value in the current buffer or window if it is available, otherwise the +/// global value information is returned. This behavior can be disabled by +/// explicitly specifying {scope} in the {opts} table. /// -/// @param name Option name +/// @param name Option name +/// @param opts Optional parameters +/// - scope: One of "global" or "local". Analogous to +/// |:setglobal| and |:setlocal|, respectively. +/// - win: |window-ID|. Used for getting window local options. +/// - buf: Buffer number. Used for getting buffer local options. +/// Implies {scope} is "local". /// @param[out] err Error details, if any /// @return Option Information -Dictionary nvim_get_option_info(String name, Error *err) - FUNC_API_SINCE(7) +Dictionary nvim_get_option_info2(String name, Dict(option) *opts, Error *err) + FUNC_API_SINCE(11) { - return get_vimoption(name, err); + int scope = 0; + int opt_type = SREQ_GLOBAL; + void *from = NULL; + if (!validate_option_value_args(opts, &scope, &opt_type, &from, NULL, err)) { + return (Dictionary)ARRAY_DICT_INIT; + } + + buf_T *buf = (opt_type == SREQ_BUF) ? (buf_T *)from : curbuf; + win_T *win = (opt_type == SREQ_WIN) ? (win_T *)from : curwin; + + return get_vimoption(name, scope, buf, win, err); } + /// Sets the global value of an option. /// /// @param channel_id diff --git a/src/nvim/option.c b/src/nvim/option.c index 0aa2f8ab0452dc..f672b3ab6ff13e 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -5605,26 +5605,27 @@ long get_sidescrolloff_value(win_T *wp) return wp->w_p_siso < 0 ? p_siso : wp->w_p_siso; } -Dictionary get_vimoption(String name, Error *err) +Dictionary get_vimoption(String name, int scope, buf_T *buf, win_T *win, Error *err) { int opt_idx = findoption_len((const char *)name.data, name.size); VALIDATE_S(opt_idx >= 0, "option (not found)", name.data, { return (Dictionary)ARRAY_DICT_INIT; }); - return vimoption2dict(&options[opt_idx]); + + return vimoption2dict(&options[opt_idx], scope, buf, win); } Dictionary get_all_vimoptions(void) { Dictionary retval = ARRAY_DICT_INIT; for (size_t i = 0; options[i].fullname != NULL; i++) { - Dictionary opt_dict = vimoption2dict(&options[i]); + Dictionary opt_dict = vimoption2dict(&options[i], OPT_GLOBAL, curbuf, curwin); PUT(retval, options[i].fullname, DICTIONARY_OBJ(opt_dict)); } return retval; } -static Dictionary vimoption2dict(vimoption_T *opt) +static Dictionary vimoption2dict(vimoption_T *opt, int req_scope, buf_T *buf, win_T *win) { Dictionary dict = ARRAY_DICT_INIT; @@ -5649,9 +5650,25 @@ static Dictionary vimoption2dict(vimoption_T *opt) PUT(dict, "was_set", BOOL(opt->flags & P_WAS_SET)); - PUT(dict, "last_set_sid", INTEGER_OBJ(opt->last_set.script_ctx.sc_sid)); - PUT(dict, "last_set_linenr", INTEGER_OBJ(opt->last_set.script_ctx.sc_lnum)); - PUT(dict, "last_set_chan", INTEGER_OBJ((int64_t)opt->last_set.channel_id)); + LastSet last_set = { .channel_id = 0 }; + if (req_scope == OPT_GLOBAL) { + last_set = opt->last_set; + } else { + // Scope is either OPT_LOCAL or a fallback mode was requested. + if (opt->indir & PV_BUF) { + last_set = buf->b_p_script_ctx[opt->indir & PV_MASK]; + } + if (opt->indir & PV_WIN) { + last_set = win->w_p_script_ctx[opt->indir & PV_MASK]; + } + if (req_scope != OPT_LOCAL && last_set.script_ctx.sc_sid == 0) { + last_set = opt->last_set; + } + } + + PUT(dict, "last_set_sid", INTEGER_OBJ(last_set.script_ctx.sc_sid)); + PUT(dict, "last_set_linenr", INTEGER_OBJ(last_set.script_ctx.sc_lnum)); + PUT(dict, "last_set_chan", INTEGER_OBJ((int64_t)last_set.channel_id)); const char *type; Object def; diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index d8fb1bc62305a7..9d5a0c4b4e52d8 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -2892,6 +2892,99 @@ describe('API', function() end) end) + describe('nvim_get_option_info2', function() + local fname + local bufs + local wins + + before_each(function() + fname = tmpname() + write_file(fname, [[ + setglobal dictionary=mydict " 1, global-local (buffer) + setlocal formatprg=myprg " 2, global-local (buffer) + setglobal equalprg=prg1 " 3, global-local (buffer) + setlocal equalprg=prg2 " 4, global-local (buffer) + setglobal fillchars=stl:x " 5, global-local (window) + setlocal listchars=eol:c " 6, global-local (window) + setglobal showbreak=aaa " 7, global-local (window) + setlocal showbreak=bbb " 8, global-local (window) + setglobal completeopt=menu " 9, global + ]]) + + exec_lua 'vim.cmd.vsplit()' + meths.create_buf(false, false) + + bufs = meths.list_bufs() + wins = meths.list_wins() + + meths.win_set_buf(wins[1].id, bufs[1].id) + meths.win_set_buf(wins[2].id, bufs[2].id) + + meths.set_current_win(wins[2].id) + meths.exec('source ' .. fname, false) + + meths.set_current_win(wins[1].id) + end) + + after_each(function() + os.remove(fname) + end) + + it('should return option information', function() + eq(meths.get_option_info('dictionary'), meths.get_option_info2('dictionary', {})) -- buffer + eq(meths.get_option_info('fillchars'), meths.get_option_info2('fillchars', {})) -- window + eq(meths.get_option_info('completeopt'), meths.get_option_info2('completeopt', {})) -- global + end) + + describe('last set', function() + local tests = { + {desc="(buf option, global requested, global set) points to global", linenr=1, sid=1, args={'dictionary', {scope='global'}}}, + {desc="(buf option, global requested, local set) is not set", linenr=0, sid=0, args={'formatprg', {scope='global'}}}, + {desc="(buf option, global requested, both set) points to global", linenr=3, sid=1, args={'equalprg', {scope='global'}}}, + {desc="(buf option, local requested, global set) is not set", linenr=0, sid=0, args={'dictionary', {scope='local'}}}, + {desc="(buf option, local requested, local set) points to local", linenr=2, sid=1, args={'formatprg', {scope='local'}}}, + {desc="(buf option, local requested, both set) points to local", linenr=4, sid=1, args={'equalprg', {scope='local'}}}, + {desc="(buf option, fallback requested, global set) points to global", linenr=1, sid=1, args={'dictionary', {}}}, + {desc="(buf option, fallback requested, local set) points to local", linenr=2, sid=1, args={'formatprg', {}}}, + {desc="(buf option, fallback requested, both set) points to local", linenr=4, sid=1, args={'equalprg', {}}}, + {desc="(win option, global requested, global set) points to global", linenr=5, sid=1, args={'fillchars', {scope='global'}}}, + {desc="(win option, global requested, local set) is not set", linenr=0, sid=0, args={'listchars', {scope='global'}}}, + {desc="(win option, global requested, both set) points to global", linenr=7, sid=1, args={'showbreak', {scope='global'}}}, + {desc="(win option, local requested, global set) is not set", linenr=0, sid=0, args={'fillchars', {scope='local'}}}, + {desc="(win option, local requested, local set) points to local", linenr=6, sid=1, args={'listchars', {scope='local'}}}, + {desc="(win option, local requested, both set) points to local", linenr=8, sid=1, args={'showbreak', {scope='local'}}}, + {desc="(win option, fallback requested, global set) points to global", linenr=5, sid=1, args={'fillchars', {}}}, + {desc="(win option, fallback requested, local set) points to local", linenr=6, sid=1, args={'listchars', {}}}, + {desc="(win option, fallback requested, both set) points to local", linenr=8, sid=1, args={'showbreak', {}}}, + {desc="(global option, global requested) points to global", linenr=9, sid=1, args={'completeopt', {scope='global'}}}, + {desc="(global option, local requested) is not set", linenr=0, sid=0, args={'completeopt', {scope='local'}}}, + {desc="(global option, fallback requested) points to global", linenr=9, sid=1, args={'completeopt', {}}}, + } + + for _, t in pairs(tests) do + it(t.desc, function() + -- Switch to the target buffer/window so that curbuf/curwin are used. + meths.set_current_win(wins[2].id) + local info = meths.get_option_info2(unpack(t.args)) + eq(t.linenr, info.last_set_linenr) + eq(t.sid, info.last_set_sid) + end) + end + + it('is provided for cross-buffer requests', function() + local info = meths.get_option_info2('formatprg', {buf=bufs[2].id}) + eq(2, info.last_set_linenr) + eq(1, info.last_set_sid) + end) + + it('is provided for cross-window requests', function() + local info = meths.get_option_info2('listchars', {win=wins[2].id}) + eq(6, info.last_set_linenr) + eq(1, info.last_set_sid) + end) + end) + end) + describe('nvim_echo', function() local screen