mirror of
https://github.com/neovim/neovim.git
synced 2026-07-15 22:00:40 +00:00
feat(api): tab-local option scope #39811
Problem:
2d795face6 added support for tab-local options ('cmdheight')
to `nvim_get_option_value`, but not to:
nvim_get_option_info2()
nvim_set_option_value(…, { tab = … })
gettabwinvar()
Solution:
- Update `options.lua` to model tab-local options. Introduce `kOptScopeTab`.
- Handle tab scope in the options layer so it works for all options APIs.
Note:
- No change to `gettabvar()`. Not sure if needed/wanted.
fix https://github.com/neovim/neovim/issues/31140
This commit is contained in:
@@ -3514,7 +3514,7 @@ nvim_get_option_info2({name}, {opts}) *nvim_get_option_info2()*
|
||||
• last_set_sid: Last set script id (if any)
|
||||
• last_set_linenr: line number where option was set
|
||||
• last_set_chan: Channel where option was set (0 for local)
|
||||
• scope: one of "global", "win", or "buf"
|
||||
• scope: one of "global", "win", "buf", or "tab"
|
||||
• global_local: whether win or buf option has a global value
|
||||
• commalist: List of comma separated values
|
||||
• flaglist: List of single char flags
|
||||
@@ -3584,6 +3584,10 @@ nvim_set_option_value({name}, {value}, {opts})
|
||||
• buf: Buffer number. Used for setting buffer local option.
|
||||
• 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.
|
||||
|
||||
|
||||
|
||||
@@ -117,6 +117,12 @@ API
|
||||
• |nvim_echo()| distinguishes zero percent from omitted percent for Progress
|
||||
events.
|
||||
• |nvim_create_user_command()| accepts `desc` for Vimscript commands.
|
||||
• Support for tabpage-local options ('cmdheight'):
|
||||
• |gettabvar()|
|
||||
• |gettabwinvar()|
|
||||
• |nvim_get_option_info2()|
|
||||
• |nvim_get_option_value()|
|
||||
• |nvim_set_option_value()|
|
||||
|
||||
BUILD
|
||||
|
||||
|
||||
@@ -3246,22 +3246,25 @@ getbufvar({buf}, {varname} [, {def}]) *getbufvar()*
|
||||
{buf} to a bufnr; option names use |nvim_get_option_value()|
|
||||
or |vim.bo|.
|
||||
|
||||
The result is the value of option or local buffer variable
|
||||
{varname} in buffer {buf}. Note that the name without "b:"
|
||||
must be used.
|
||||
The {varname} argument is a string.
|
||||
When {varname} is empty returns a |Dictionary| with all the
|
||||
buffer-local variables.
|
||||
When {varname} is equal to "&" returns a |Dictionary| with all
|
||||
the buffer-local options.
|
||||
Otherwise, when {varname} starts with "&" returns the value of
|
||||
a buffer-local option.
|
||||
This also works for a global or buffer-local option, but it
|
||||
doesn't work for a global variable, window-local variable or
|
||||
window-local option.
|
||||
For the use of {buf}, see |bufname()| above.
|
||||
When the buffer or variable doesn't exist {def} or an empty
|
||||
string is returned, there is no error message.
|
||||
Gets the value of a buffer-local variable or option {varname}
|
||||
in buffer {buf}.
|
||||
|
||||
{varname} is a string:
|
||||
- Name of the variable (without "b:").
|
||||
- If empty, gets a |Dictionary| of all buffer-local variables.
|
||||
- If "&", gets a |Dictionary| of all buffer-local options.
|
||||
- If it starts with "&", gets the value of a buffer-local
|
||||
option.
|
||||
|
||||
{buf} has the same form as in |bufname()|.
|
||||
|
||||
Also works for a global or buffer-local option. But not for
|
||||
a global variable, window-local variable or window-local
|
||||
option.
|
||||
|
||||
When the buffer or variable doesn't exist, {def} or an empty
|
||||
string is returned; there is no error.
|
||||
|
||||
Examples: >vim
|
||||
let bufmodified = getbufvar(1, "&mod")
|
||||
echo "todo myvar = " .. getbufvar("todo", "myvar")
|
||||
@@ -4555,29 +4558,34 @@ gettabwinvar({tabnr}, {winnr}, {varname} [, {def}]) *gettabwinvar()*
|
||||
{tabnr} and {winnr} to a winid; option names use
|
||||
|nvim_get_option_value()| or |vim.wo|.
|
||||
|
||||
Get the value of window-local variable {varname} in window
|
||||
{winnr} in tabpage {tabnr}.
|
||||
The {varname} argument is a string. When {varname} is empty a
|
||||
dictionary with all window-local variables is returned.
|
||||
When {varname} is equal to "&" get the values of all
|
||||
window-local options in a |Dictionary|.
|
||||
Otherwise, when {varname} starts with "&" get the value of a
|
||||
window-local option.
|
||||
Note that {varname} must be the name without "w:".
|
||||
Tabs are numbered starting with one. For the current tabpage
|
||||
use |getwinvar()|.
|
||||
{winnr} is a |window-number| or |window-ID|.
|
||||
Gets the value of window-local variable {varname} in {winnr}
|
||||
(|window-number| or |window-ID|) in |tabpage-number| {tabnr}.
|
||||
|
||||
{varname} is a string:
|
||||
- Name of the variable (without "w:").
|
||||
- If empty, gets a dictionary with all window-local variables.
|
||||
- If "&", gets the values of all window-local options in
|
||||
a |Dictionary|.
|
||||
- If it starts with "&", gets the value of a window-local
|
||||
option.
|
||||
|
||||
To get window-local variables in the current tabpage use
|
||||
|getwinvar()|.
|
||||
|
||||
When {winnr} is zero the current window is used.
|
||||
This also works for a global option, buffer-local option and
|
||||
window-local option, but it doesn't work for a global variable
|
||||
or buffer-local variable.
|
||||
When the tab, window or variable doesn't exist {def} or an
|
||||
empty string is returned, there is no error message.
|
||||
|
||||
Also works for a global option, buffer-local option,
|
||||
window-local option, and tab-local option ('cmdheight').
|
||||
But not for a global variable or buffer-local variable.
|
||||
|
||||
When the tab, window or variable doesn't exist, {def} or an
|
||||
empty string is returned; there is no error.
|
||||
|
||||
Examples: >vim
|
||||
let list_is_on = gettabwinvar(1, 2, '&list')
|
||||
echo "myvar = " .. gettabwinvar(3, 1, 'myvar')
|
||||
<
|
||||
To obtain all window-local variables use: >vim
|
||||
To get all window-local variables: >vim
|
||||
gettabwinvar({tabnr}, {winnr}, '&')
|
||||
<
|
||||
|
||||
|
||||
5
runtime/lua/vim/_meta/api.gen.lua
generated
5
runtime/lua/vim/_meta/api.gen.lua
generated
@@ -1516,7 +1516,7 @@ function vim.api.nvim_get_option_info(name) end
|
||||
--- - last_set_linenr: line number where option was set
|
||||
--- - last_set_chan: Channel where option was set (0 for local)
|
||||
---
|
||||
--- - scope: one of "global", "win", or "buf"
|
||||
--- - scope: one of "global", "win", "buf", or "tab"
|
||||
--- - global_local: whether win or buf option has a global value
|
||||
---
|
||||
--- - commalist: List of comma separated values
|
||||
@@ -2336,6 +2336,9 @@ function vim.api.nvim_set_option(name, value) end
|
||||
--- - buf: Buffer number. Used for setting buffer local option.
|
||||
--- - 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.
|
||||
function vim.api.nvim_set_option_value(name, value, opts) end
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ error('Cannot require a meta file')
|
||||
--- @class vim.api.keyset.get_option_info
|
||||
--- @field name string
|
||||
--- @field shortname string
|
||||
--- @field scope 'buf'|'win'|'global'
|
||||
--- @field scope 'buf'|'win'|'global'|'tab'
|
||||
--- @field global_local boolean
|
||||
--- @field commalist boolean
|
||||
--- @field flaglist boolean
|
||||
|
||||
76
runtime/lua/vim/_meta/vimfn.gen.lua
generated
76
runtime/lua/vim/_meta/vimfn.gen.lua
generated
@@ -2884,22 +2884,25 @@ function vim.fn.getbufoneline(buf, lnum) end
|
||||
|
||||
--- Lua: Prefer |nvim_buf_get_var()| or |vim.b| after resolving {buf} to a bufnr; option names use |nvim_get_option_value()| or |vim.bo|.
|
||||
---
|
||||
--- The result is the value of option or local buffer variable
|
||||
--- {varname} in buffer {buf}. Note that the name without "b:"
|
||||
--- must be used.
|
||||
--- The {varname} argument is a string.
|
||||
--- When {varname} is empty returns a |Dictionary| with all the
|
||||
--- buffer-local variables.
|
||||
--- When {varname} is equal to "&" returns a |Dictionary| with all
|
||||
--- the buffer-local options.
|
||||
--- Otherwise, when {varname} starts with "&" returns the value of
|
||||
--- a buffer-local option.
|
||||
--- This also works for a global or buffer-local option, but it
|
||||
--- doesn't work for a global variable, window-local variable or
|
||||
--- window-local option.
|
||||
--- For the use of {buf}, see |bufname()| above.
|
||||
--- When the buffer or variable doesn't exist {def} or an empty
|
||||
--- string is returned, there is no error message.
|
||||
--- Gets the value of a buffer-local variable or option {varname}
|
||||
--- in buffer {buf}.
|
||||
---
|
||||
--- {varname} is a string:
|
||||
--- - Name of the variable (without "b:").
|
||||
--- - If empty, gets a |Dictionary| of all buffer-local variables.
|
||||
--- - If "&", gets a |Dictionary| of all buffer-local options.
|
||||
--- - If it starts with "&", gets the value of a buffer-local
|
||||
--- option.
|
||||
---
|
||||
--- {buf} has the same form as in |bufname()|.
|
||||
---
|
||||
--- Also works for a global or buffer-local option. But not for
|
||||
--- a global variable, window-local variable or window-local
|
||||
--- option.
|
||||
---
|
||||
--- When the buffer or variable doesn't exist, {def} or an empty
|
||||
--- string is returned; there is no error.
|
||||
---
|
||||
--- Examples: >vim
|
||||
--- let bufmodified = getbufvar(1, "&mod")
|
||||
--- echo "todo myvar = " .. getbufvar("todo", "myvar")
|
||||
@@ -4090,29 +4093,34 @@ function vim.fn.gettabvar(tabnr, varname, def) end
|
||||
|
||||
--- Lua: Prefer |nvim_win_get_var()| or |vim.w| after resolving {tabnr} and {winnr} to a winid; option names use |nvim_get_option_value()| or |vim.wo|.
|
||||
---
|
||||
--- Get the value of window-local variable {varname} in window
|
||||
--- {winnr} in tabpage {tabnr}.
|
||||
--- The {varname} argument is a string. When {varname} is empty a
|
||||
--- dictionary with all window-local variables is returned.
|
||||
--- When {varname} is equal to "&" get the values of all
|
||||
--- window-local options in a |Dictionary|.
|
||||
--- Otherwise, when {varname} starts with "&" get the value of a
|
||||
--- window-local option.
|
||||
--- Note that {varname} must be the name without "w:".
|
||||
--- Tabs are numbered starting with one. For the current tabpage
|
||||
--- use |getwinvar()|.
|
||||
--- {winnr} is a |window-number| or |window-ID|.
|
||||
--- Gets the value of window-local variable {varname} in {winnr}
|
||||
--- (|window-number| or |window-ID|) in |tabpage-number| {tabnr}.
|
||||
---
|
||||
--- {varname} is a string:
|
||||
--- - Name of the variable (without "w:").
|
||||
--- - If empty, gets a dictionary with all window-local variables.
|
||||
--- - If "&", gets the values of all window-local options in
|
||||
--- a |Dictionary|.
|
||||
--- - If it starts with "&", gets the value of a window-local
|
||||
--- option.
|
||||
---
|
||||
--- To get window-local variables in the current tabpage use
|
||||
--- |getwinvar()|.
|
||||
---
|
||||
--- When {winnr} is zero the current window is used.
|
||||
--- This also works for a global option, buffer-local option and
|
||||
--- window-local option, but it doesn't work for a global variable
|
||||
--- or buffer-local variable.
|
||||
--- When the tab, window or variable doesn't exist {def} or an
|
||||
--- empty string is returned, there is no error message.
|
||||
---
|
||||
--- Also works for a global option, buffer-local option,
|
||||
--- window-local option, and tab-local option ('cmdheight').
|
||||
--- But not for a global variable or buffer-local variable.
|
||||
---
|
||||
--- When the tab, window or variable doesn't exist, {def} or an
|
||||
--- empty string is returned; there is no error.
|
||||
---
|
||||
--- Examples: >vim
|
||||
--- let list_is_on = gettabwinvar(1, 2, '&list')
|
||||
--- echo "myvar = " .. gettabwinvar(3, 1, 'myvar')
|
||||
--- <
|
||||
--- To obtain all window-local variables use: >vim
|
||||
--- To get all window-local variables: >vim
|
||||
--- gettabwinvar({tabnr}, {winnr}, '&')
|
||||
--- <
|
||||
---
|
||||
|
||||
@@ -90,6 +90,14 @@ static int validate_option_value_args(Dict(option) *opts, char *name, bool allow
|
||||
}
|
||||
}
|
||||
|
||||
if (HAS_KEY_X(opts, tab)) {
|
||||
*scope = kOptScopeTab;
|
||||
*from = find_tab_by_handle(opts->tab, err);
|
||||
if (ERROR_SET(err)) {
|
||||
return FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
*opt_idxp = find_option(name);
|
||||
if (*opt_idxp == kOptInvalid) {
|
||||
// unknown option
|
||||
@@ -97,11 +105,9 @@ static int validate_option_value_args(Dict(option) *opts, char *name, bool allow
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
// The only tab-local option is 'cmdheight'.
|
||||
// TODO(justinmk): introduce kOptScopeTab into option metadata, then use option_has_scope below.
|
||||
VALIDATE_CON(!HAS_KEY_X(opts, tab) || (*opt_idxp == kOptCmdheight), "tab", name, {
|
||||
return FAIL;
|
||||
});
|
||||
// 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; });
|
||||
|
||||
// If 'buf' or 'win' is passed, make sure the option supports it.
|
||||
if (*scope == kOptScopeBuf || *scope == kOptScopeWin) {
|
||||
@@ -235,15 +241,6 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
|
||||
return (Object)OBJECT_INIT;
|
||||
}
|
||||
|
||||
if (HAS_KEY(opts, option, tab)) {
|
||||
tabpage_T *tab = find_tab_by_handle(opts->tab, err);
|
||||
if (ERROR_SET(err)) {
|
||||
return (Object)OBJECT_INIT;
|
||||
}
|
||||
const OptInt ch = (tab == curtab) ? p_ch : tab->tp_ch_used;
|
||||
return optval_as_object(NUMBER_OPTVAL(ch));
|
||||
}
|
||||
|
||||
aco_save_T aco;
|
||||
bool aco_used;
|
||||
|
||||
@@ -300,6 +297,9 @@ err:
|
||||
/// - buf: Buffer number. Used for setting buffer local option.
|
||||
/// - 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.
|
||||
/// @param[out] err Error details, if any
|
||||
void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict(option) *opts,
|
||||
@@ -310,8 +310,7 @@ void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict(
|
||||
int opt_flags = 0;
|
||||
OptScope scope = kOptScopeGlobal;
|
||||
void *to = NULL;
|
||||
// TODO(justinmk): support tab-local option.
|
||||
if (!validate_option_value_args(opts, name.data, false, &opt_idx, &opt_flags, &scope, &to, NULL,
|
||||
if (!validate_option_value_args(opts, name.data, true, &opt_idx, &opt_flags, &scope, &to, NULL,
|
||||
err)) {
|
||||
return;
|
||||
}
|
||||
@@ -371,7 +370,7 @@ Dict nvim_get_all_options_info(Arena *arena, Error *err)
|
||||
/// - last_set_linenr: line number where option was set
|
||||
/// - last_set_chan: Channel where option was set (0 for local)
|
||||
///
|
||||
/// - scope: one of "global", "win", or "buf"
|
||||
/// - scope: one of "global", "win", "buf", or "tab"
|
||||
/// - global_local: whether win or buf option has a global value
|
||||
///
|
||||
/// - commalist: List of comma separated values
|
||||
|
||||
@@ -3593,22 +3593,25 @@ M.funcs = {
|
||||
args = { 2, 3 },
|
||||
base = 1,
|
||||
desc = [=[
|
||||
The result is the value of option or local buffer variable
|
||||
{varname} in buffer {buf}. Note that the name without "b:"
|
||||
must be used.
|
||||
The {varname} argument is a string.
|
||||
When {varname} is empty returns a |Dictionary| with all the
|
||||
buffer-local variables.
|
||||
When {varname} is equal to "&" returns a |Dictionary| with all
|
||||
the buffer-local options.
|
||||
Otherwise, when {varname} starts with "&" returns the value of
|
||||
a buffer-local option.
|
||||
This also works for a global or buffer-local option, but it
|
||||
doesn't work for a global variable, window-local variable or
|
||||
window-local option.
|
||||
For the use of {buf}, see |bufname()| above.
|
||||
When the buffer or variable doesn't exist {def} or an empty
|
||||
string is returned, there is no error message.
|
||||
Gets the value of a buffer-local variable or option {varname}
|
||||
in buffer {buf}.
|
||||
|
||||
{varname} is a string:
|
||||
- Name of the variable (without "b:").
|
||||
- If empty, gets a |Dictionary| of all buffer-local variables.
|
||||
- If "&", gets a |Dictionary| of all buffer-local options.
|
||||
- If it starts with "&", gets the value of a buffer-local
|
||||
option.
|
||||
|
||||
{buf} has the same form as in |bufname()|.
|
||||
|
||||
Also works for a global or buffer-local option. But not for
|
||||
a global variable, window-local variable or window-local
|
||||
option.
|
||||
|
||||
When the buffer or variable doesn't exist, {def} or an empty
|
||||
string is returned; there is no error.
|
||||
|
||||
Examples: >vim
|
||||
let bufmodified = getbufvar(1, "&mod")
|
||||
echo "todo myvar = " .. getbufvar("todo", "myvar")
|
||||
@@ -5006,29 +5009,34 @@ M.funcs = {
|
||||
args = { 3, 4 },
|
||||
base = 1,
|
||||
desc = [=[
|
||||
Get the value of window-local variable {varname} in window
|
||||
{winnr} in tabpage {tabnr}.
|
||||
The {varname} argument is a string. When {varname} is empty a
|
||||
dictionary with all window-local variables is returned.
|
||||
When {varname} is equal to "&" get the values of all
|
||||
window-local options in a |Dictionary|.
|
||||
Otherwise, when {varname} starts with "&" get the value of a
|
||||
window-local option.
|
||||
Note that {varname} must be the name without "w:".
|
||||
Tabs are numbered starting with one. For the current tabpage
|
||||
use |getwinvar()|.
|
||||
{winnr} is a |window-number| or |window-ID|.
|
||||
Gets the value of window-local variable {varname} in {winnr}
|
||||
(|window-number| or |window-ID|) in |tabpage-number| {tabnr}.
|
||||
|
||||
{varname} is a string:
|
||||
- Name of the variable (without "w:").
|
||||
- If empty, gets a dictionary with all window-local variables.
|
||||
- If "&", gets the values of all window-local options in
|
||||
a |Dictionary|.
|
||||
- If it starts with "&", gets the value of a window-local
|
||||
option.
|
||||
|
||||
To get window-local variables in the current tabpage use
|
||||
|getwinvar()|.
|
||||
|
||||
When {winnr} is zero the current window is used.
|
||||
This also works for a global option, buffer-local option and
|
||||
window-local option, but it doesn't work for a global variable
|
||||
or buffer-local variable.
|
||||
When the tab, window or variable doesn't exist {def} or an
|
||||
empty string is returned, there is no error message.
|
||||
|
||||
Also works for a global option, buffer-local option,
|
||||
window-local option, and tab-local option ('cmdheight').
|
||||
But not for a global variable or buffer-local variable.
|
||||
|
||||
When the tab, window or variable doesn't exist, {def} or an
|
||||
empty string is returned; there is no error.
|
||||
|
||||
Examples: >vim
|
||||
let list_is_on = gettabwinvar(1, 2, '&list')
|
||||
echo "myvar = " .. gettabwinvar(3, 1, 'myvar')
|
||||
<
|
||||
To obtain all window-local variables use: >vim
|
||||
To get all window-local variables: >vim
|
||||
gettabwinvar({tabnr}, {winnr}, '&')
|
||||
<
|
||||
]=],
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/// Check if number is a power of two
|
||||
static inline bool is_power_of_two(uint64_t x)
|
||||
{
|
||||
return x != 0 && ((x & (x - 1)) == 0);
|
||||
}
|
||||
|
||||
#include "math.h.generated.h"
|
||||
|
||||
@@ -74,7 +74,6 @@
|
||||
#include "nvim/lua/executor.h"
|
||||
#include "nvim/macros_defs.h"
|
||||
#include "nvim/mapping.h"
|
||||
#include "nvim/math.h"
|
||||
#include "nvim/mbyte.h"
|
||||
#include "nvim/memfile.h"
|
||||
#include "nvim/memline.h"
|
||||
@@ -3559,28 +3558,37 @@ bool option_has_scope(OptIndex opt_idx, OptScope scope)
|
||||
return get_option(opt_idx)->scope_flags & (1 << scope);
|
||||
}
|
||||
|
||||
/// Check if option is global-local.
|
||||
/// Check if option is global-local (has global AND buffer/window scope).
|
||||
/// Tab scope is independent and does not make an option "global-local".
|
||||
static inline bool option_is_global_local(OptIndex opt_idx)
|
||||
{
|
||||
// Global-local options have at least two types, so their type flag cannot be a power of two.
|
||||
return opt_idx != kOptInvalid && !is_power_of_two(options[opt_idx].scope_flags);
|
||||
}
|
||||
|
||||
/// Check if option only supports global scope.
|
||||
static inline bool option_is_global_only(OptIndex opt_idx)
|
||||
{
|
||||
// For an option to be global-only, it has to only have a single scope, which means the scope
|
||||
// flags must be a power of two, and it must have the global scope.
|
||||
return opt_idx != kOptInvalid && is_power_of_two(options[opt_idx].scope_flags)
|
||||
if (opt_idx == kOptInvalid) {
|
||||
return false;
|
||||
}
|
||||
const OptScopeFlags bw = (1 << kOptScopeBuf) | (1 << kOptScopeWin);
|
||||
return (options[opt_idx].scope_flags & bw) != 0
|
||||
&& option_has_scope(opt_idx, kOptScopeGlobal);
|
||||
}
|
||||
|
||||
/// Check if option only supports window scope.
|
||||
/// Check if option only supports global scope (ignoring tab scope, which is independent).
|
||||
static inline bool option_is_global_only(OptIndex opt_idx)
|
||||
{
|
||||
if (opt_idx == kOptInvalid) {
|
||||
return false;
|
||||
}
|
||||
const OptScopeFlags bw = (1 << kOptScopeBuf) | (1 << kOptScopeWin);
|
||||
return (options[opt_idx].scope_flags & bw) == 0
|
||||
&& option_has_scope(opt_idx, kOptScopeGlobal);
|
||||
}
|
||||
|
||||
/// Check if option only supports window scope (ignoring tab scope, which is independent).
|
||||
static inline bool option_is_window_local(OptIndex opt_idx)
|
||||
{
|
||||
// For an option to be window-local it has to only have a single scope, which means the scope
|
||||
// flags must be a power of two, and it must have the window scope.
|
||||
return opt_idx != kOptInvalid && is_power_of_two(options[opt_idx].scope_flags)
|
||||
if (opt_idx == kOptInvalid) {
|
||||
return false;
|
||||
}
|
||||
const OptScopeFlags exclude = (1 << kOptScopeGlobal) | (1 << kOptScopeBuf);
|
||||
return (options[opt_idx].scope_flags & exclude) == 0
|
||||
&& option_has_scope(opt_idx, kOptScopeWin);
|
||||
}
|
||||
|
||||
@@ -4073,6 +4081,10 @@ void set_option_direct_for(OptIndex opt_idx, OptVal value, int opt_flags, scid_T
|
||||
case kOptScopeBuf:
|
||||
curbuf = (buf_T *)from;
|
||||
break;
|
||||
case kOptScopeTab:
|
||||
// No context to switch; tab-scoped direct-set isn't used. Caller should route through
|
||||
// set_option_value_for() instead.
|
||||
abort();
|
||||
}
|
||||
|
||||
set_option_direct(opt_idx, value, opt_flags, set_sid);
|
||||
@@ -4156,12 +4168,18 @@ void set_option_value_give_err(const OptIndex opt_idx, OptVal value, int opt_fla
|
||||
}
|
||||
}
|
||||
|
||||
/// Switch current context to get/set option value for window/buffer.
|
||||
/// Switch current context to get/set option value for the target window/buffer/tabpage.
|
||||
///
|
||||
/// @param[out] ctx Current context. switchwin_T for window and aco_save_T for buffer.
|
||||
/// @param scope Option scope. See OptScope in option.h.
|
||||
/// @param[in] from Target buffer/window.
|
||||
/// @param[out] err Error message, if any.
|
||||
/// Tab-scope switch is GET-only (SET requires side-effects, see set_option_value_for()).
|
||||
///
|
||||
/// @param[out] ctx Switched-from context, restored by restore_option_context():
|
||||
/// - kOptScopeWin: switchwin_T
|
||||
/// - kOptScopeBuf: aco_save_T
|
||||
/// - kOptScopeTab: tabpage_T * (saved curtab)
|
||||
/// - kOptScopeGlobal: unused
|
||||
/// @param scope Option scope. See OptScope in option.h.
|
||||
/// @param[in] from Target win_T/buf_T/tabpage_T.
|
||||
/// @param[out] err Error message, if any.
|
||||
///
|
||||
/// @return true if context was switched, false otherwise.
|
||||
static bool switch_option_context(void *const ctx, OptScope scope, void *const from, Error *err)
|
||||
@@ -4177,8 +4195,7 @@ static bool switch_option_context(void *const ctx, OptScope scope, void *const f
|
||||
return false;
|
||||
}
|
||||
|
||||
if (switch_win_noblock(switchwin, win, win_find_tabpage(win), true)
|
||||
== FAIL) {
|
||||
if (FAIL == switch_win_noblock(switchwin, win, win_find_tabpage(win), true)) {
|
||||
restore_win_noblock(switchwin, true);
|
||||
|
||||
if (ERROR_SET(err)) {
|
||||
@@ -4199,6 +4216,20 @@ static bool switch_option_context(void *const ctx, OptScope scope, void *const f
|
||||
aucmd_prepbuf(aco, buf);
|
||||
return true;
|
||||
}
|
||||
case kOptScopeTab: {
|
||||
// GET-only: swap curtab so get_option_value() reads the target tab's stored value (p_ch).
|
||||
// SET on a non-current tab cannot use this path, see comment in set_option_value_for().
|
||||
tabpage_T *const tab = (tabpage_T *)from;
|
||||
tabpage_T **const saved_curtab = (tabpage_T **)ctx;
|
||||
|
||||
if (tab == curtab) {
|
||||
return false;
|
||||
}
|
||||
*saved_curtab = curtab;
|
||||
unuse_tabpage(curtab);
|
||||
use_tabpage(tab);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
UNREACHABLE;
|
||||
}
|
||||
@@ -4216,6 +4247,12 @@ static void restore_option_context(void *const ctx, OptScope scope)
|
||||
case kOptScopeBuf:
|
||||
aucmd_restbuf((aco_save_T *)ctx);
|
||||
break;
|
||||
case kOptScopeTab: {
|
||||
tabpage_T *const saved_curtab = *(tabpage_T **)ctx;
|
||||
unuse_tabpage(curtab);
|
||||
use_tabpage(saved_curtab);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4235,8 +4272,11 @@ OptVal get_option_value_for(OptIndex opt_idx, int opt_flags, const OptScope scop
|
||||
{
|
||||
switchwin_T switchwin;
|
||||
aco_save_T aco;
|
||||
tabpage_T *swtab = NULL;
|
||||
void *ctx = scope == kOptScopeWin ? (void *)&switchwin
|
||||
: (scope == kOptScopeBuf ? (void *)&aco : NULL);
|
||||
: scope == kOptScopeBuf ? (void *)&aco
|
||||
: scope == kOptScopeTab ? (void *)&swtab
|
||||
: NULL;
|
||||
|
||||
bool switched = switch_option_context(ctx, scope, from, err);
|
||||
if (ERROR_SET(err)) {
|
||||
@@ -4265,10 +4305,29 @@ void set_option_value_for(const char *name, OptIndex opt_idx, OptVal value, cons
|
||||
const OptScope scope, void *const from, Error *err)
|
||||
FUNC_ATTR_NONNULL_ARG(1)
|
||||
{
|
||||
// Special case: Tab scope (for NON-CURRENT tab) can't go through the normal "set" path:
|
||||
// did_set_cmdheight() mutates globals not managed by use_tabpage()/unuse_tabpage(), which would
|
||||
// leak across switch_option_context().
|
||||
//
|
||||
// Workaround: set `tp_ch_used` directly; the cmdline will resize when the tab is switched-to.
|
||||
if (scope == kOptScopeTab && (tabpage_T *)from != curtab) {
|
||||
tabpage_T *const tab = (tabpage_T *)from;
|
||||
assert(opt_idx == kOptCmdheight);
|
||||
if (value.type != kOptValTypeNumber) {
|
||||
api_set_error(err, kErrorTypeValidation, "'cmdheight' requires a Number");
|
||||
return;
|
||||
}
|
||||
tab->tp_ch_used = value.data.number;
|
||||
return;
|
||||
}
|
||||
|
||||
switchwin_T switchwin;
|
||||
aco_save_T aco;
|
||||
tabpage_T *swtab = NULL;
|
||||
void *ctx = scope == kOptScopeWin ? (void *)&switchwin
|
||||
: (scope == kOptScopeBuf ? (void *)&aco : NULL);
|
||||
: scope == kOptScopeBuf ? (void *)&aco
|
||||
: scope == kOptScopeTab ? (void *)&swtab
|
||||
: NULL;
|
||||
|
||||
bool switched = switch_option_context(ctx, scope, from, err);
|
||||
if (ERROR_SET(err)) {
|
||||
@@ -6686,6 +6745,8 @@ static Dict vimoption2dict(vimoption_T *opt, int opt_flags, buf_T *buf, win_T *w
|
||||
scope = "buf";
|
||||
} else if (option_has_scope(opt_idx, kOptScopeWin)) {
|
||||
scope = "win";
|
||||
} else if (option_has_scope(opt_idx, kOptScopeTab)) {
|
||||
scope = "tab";
|
||||
} else {
|
||||
scope = "global";
|
||||
}
|
||||
|
||||
@@ -57,9 +57,10 @@ typedef enum {
|
||||
kOptScopeGlobal = 0, ///< Request global option value
|
||||
kOptScopeWin, ///< Request window-local option value
|
||||
kOptScopeBuf, ///< Request buffer-local option value
|
||||
kOptScopeTab, ///< Request tabpage-local option value
|
||||
} OptScope;
|
||||
/// Always update this whenever a new option scope is added.
|
||||
#define kOptScopeSize (kOptScopeBuf + 1)
|
||||
#define kOptScopeSize (kOptScopeTab + 1)
|
||||
typedef uint8_t OptScopeFlags;
|
||||
|
||||
typedef union {
|
||||
|
||||
@@ -88,7 +88,7 @@ end
|
||||
local options = {
|
||||
cstr = cstr,
|
||||
--- @type string[]
|
||||
valid_scopes = { 'global', 'buf', 'win' },
|
||||
valid_scopes = { 'global', 'buf', 'win', 'tab' },
|
||||
--- @type vim.option_meta[]
|
||||
--- The order of the options MUST be alphabetic for ":set all".
|
||||
options = {
|
||||
@@ -1350,7 +1350,7 @@ local options = {
|
||||
]=],
|
||||
full_name = 'cmdheight',
|
||||
redraw = { 'all_windows' },
|
||||
scope = { 'global' },
|
||||
scope = { 'global', 'tab' },
|
||||
short_desc = N_('number of lines to use for the command-line'),
|
||||
type = 'number',
|
||||
varname = 'p_ch',
|
||||
|
||||
@@ -4337,6 +4337,10 @@ void unuse_tabpage(tabpage_T *tp)
|
||||
tp->tp_firstwin = firstwin;
|
||||
tp->tp_lastwin = lastwin;
|
||||
tp->tp_curwin = curwin;
|
||||
// Set this tab's stored cmdheight so use_tabpage() can restore it later.
|
||||
// command_height() and win_new_screen_rows() also keep tp_ch_used in sync for the current tab
|
||||
// between tab switches; this catches the no-display switch_win() path which bypasses them.
|
||||
tp->tp_ch_used = p_ch;
|
||||
}
|
||||
|
||||
// When switching tabpage, handle other side-effects in command_height(), but
|
||||
@@ -4352,6 +4356,9 @@ void use_tabpage(tabpage_T *tp)
|
||||
firstwin = curtab->tp_firstwin;
|
||||
lastwin = curtab->tp_lastwin;
|
||||
curwin = curtab->tp_curwin;
|
||||
// Restore this tab's cmdheight. Layout adjustment (OptionSet, frame resize) is the caller's
|
||||
// responsibility, see enter_tabpage().
|
||||
p_ch = curtab->tp_ch_used;
|
||||
}
|
||||
|
||||
// Allocate the first window and put an empty buffer in it.
|
||||
@@ -4789,20 +4796,21 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, bool trigger_enter_a
|
||||
int old_off = tp->tp_firstwin->w_winrow;
|
||||
win_T *next_prevwin = tp->tp_prevwin;
|
||||
tabpage_T *old_curtab = curtab;
|
||||
OptInt prev_p_ch = p_ch;
|
||||
|
||||
use_tabpage(tp);
|
||||
|
||||
if (old_curtab != curtab) {
|
||||
if (old_curtab != curtab && p_ch != prev_p_ch) {
|
||||
tabpage_check_windows(old_curtab);
|
||||
// use_tabpage() loaded a different cmdheight for the new tab. Fire OptionSet and adjust
|
||||
// the cmdline row without touching frame sizes (the new tab's frames are already correct).
|
||||
OptInt new_ch = p_ch;
|
||||
p_ch = prev_p_ch;
|
||||
command_frame_height = false;
|
||||
set_option_value(kOptCmdheight, NUMBER_OPTVAL(new_ch), 0);
|
||||
command_frame_height = true;
|
||||
} else if (old_curtab != curtab) {
|
||||
tabpage_check_windows(old_curtab);
|
||||
if (p_ch != curtab->tp_ch_used) {
|
||||
// Use the stored value of p_ch, so that it can be different for each tab page.
|
||||
// Handle other side-effects but avoid setting frame sizes, which are still correct.
|
||||
OptInt new_ch = curtab->tp_ch_used;
|
||||
curtab->tp_ch_used = p_ch;
|
||||
command_frame_height = false;
|
||||
set_option_value(kOptCmdheight, NUMBER_OPTVAL(new_ch), 0);
|
||||
command_frame_height = true;
|
||||
}
|
||||
}
|
||||
|
||||
// We would like doing the TabEnter event first, but we don't have a
|
||||
|
||||
@@ -1973,8 +1973,8 @@ describe('API', function()
|
||||
pcall_err(api.nvim_get_option_value, 'shiftwidth', { tab = tab1 })
|
||||
)
|
||||
eq(
|
||||
"Conflict: 'tab' not allowed with this function",
|
||||
pcall_err(api.nvim_set_option_value, 'cmdheight', 2, { tab = tab1 })
|
||||
"Conflict: 'tab' not allowed with 'shiftwidth'",
|
||||
pcall_err(api.nvim_set_option_value, 'shiftwidth', 2, { tab = tab1 })
|
||||
)
|
||||
eq(
|
||||
"Conflict: 'tab' not allowed with this function",
|
||||
@@ -1986,7 +1986,7 @@ describe('API', function()
|
||||
)
|
||||
end)
|
||||
|
||||
it("tabpage-local 'cmdheight' #31140", function()
|
||||
it("tabpage-local option ('cmdheight') #31140", function()
|
||||
api.nvim_set_option_value('cmdheight', 1, {})
|
||||
local tab1 = api.nvim_get_current_tabpage()
|
||||
eq(1, api.nvim_get_option_value('cmdheight', { tab = 0 }))
|
||||
@@ -1998,6 +1998,14 @@ describe('API', function()
|
||||
eq(4, api.nvim_get_option_value('cmdheight', { tab = tab2 }))
|
||||
eq(1, api.nvim_get_option_value('cmdheight', { tab = tab1 }))
|
||||
eq(4, api.nvim_get_option_value('cmdheight', {}))
|
||||
|
||||
-- Set non-current tab option.
|
||||
api.nvim_set_option_value('cmdheight', 3, { tab = tab1 })
|
||||
eq(3, api.nvim_get_option_value('cmdheight', { tab = tab1 }))
|
||||
eq(4, api.nvim_get_option_value('cmdheight', { tab = tab2 }))
|
||||
eq(4, api.nvim_get_option_value('cmdheight', {}))
|
||||
command('tabnext')
|
||||
eq(3, api.nvim_get_option_value('cmdheight', {}))
|
||||
end)
|
||||
|
||||
it('can get local values when global value is set', function()
|
||||
@@ -3891,10 +3899,11 @@ describe('API', function()
|
||||
os.remove(fname)
|
||||
end)
|
||||
|
||||
it('should return option information', function()
|
||||
it('gets option info', function()
|
||||
eq(api.nvim_get_option_info('dictionary'), api.nvim_get_option_info2('dictionary', {})) -- buffer
|
||||
eq(api.nvim_get_option_info('fillchars'), api.nvim_get_option_info2('fillchars', {})) -- window
|
||||
eq(api.nvim_get_option_info('completeopt'), api.nvim_get_option_info2('completeopt', {})) -- global
|
||||
eq('tab', api.nvim_get_option_info2('cmdheight', {}).scope) -- tab #31140
|
||||
end)
|
||||
|
||||
describe('last set', function()
|
||||
@@ -3933,13 +3942,13 @@ describe('API', function()
|
||||
end)
|
||||
end
|
||||
|
||||
it('is provided for cross-buffer requests', function()
|
||||
it('cross-buffer', function()
|
||||
local info = api.nvim_get_option_info2('formatprg', { buf = bufs[2] })
|
||||
eq(2, info.last_set_linenr)
|
||||
eq(1, info.last_set_sid)
|
||||
end)
|
||||
|
||||
it('is provided for cross-window requests', function()
|
||||
it('cross-window', function()
|
||||
local info = api.nvim_get_option_info2('listchars', { win = wins[2] })
|
||||
eq(6, info.last_set_linenr)
|
||||
eq(1, info.last_set_sid)
|
||||
|
||||
@@ -142,6 +142,22 @@ describe('tabpage', function()
|
||||
eq(42, fn.gettabvar(0, 'tabvar'))
|
||||
end)
|
||||
|
||||
it("gettabwinvar() returns tab-local 'cmdheight' #31140", function()
|
||||
command('set cmdheight=5')
|
||||
local tab1 = api.nvim_get_current_tabpage()
|
||||
command('tabnew')
|
||||
command('set cmdheight=2')
|
||||
local tab2 = api.nvim_get_current_tabpage()
|
||||
local tnr1 = fn.nvim_tabpage_get_number(tab1)
|
||||
local tnr2 = fn.nvim_tabpage_get_number(tab2)
|
||||
|
||||
-- Reading the *other* tab's cmdheight does not change the current tab.
|
||||
eq(5, fn.gettabwinvar(tnr1, 1, '&cmdheight'))
|
||||
eq(2, fn.gettabwinvar(tnr2, 1, '&cmdheight'))
|
||||
eq(tab2, api.nvim_get_current_tabpage())
|
||||
eq(2, api.nvim_get_option_value('cmdheight', {}))
|
||||
end)
|
||||
|
||||
it(':tabs does not overflow IObuff with long path with comma #20850', function()
|
||||
api.nvim_buf_set_name(0, ('x'):rep(1024) .. ',' .. ('x'):rep(1024))
|
||||
command('tabs')
|
||||
|
||||
Reference in New Issue
Block a user