mirror of
https://github.com/neovim/neovim.git
synced 2024-09-17 20:58:20 -04:00
ui: forward relevant option updates to UIs (#7520)
also make termguicolors mutable after startup
This commit is contained in:
parent
f976826690
commit
34057045be
@ -6192,7 +6192,6 @@ A jump table for the options with a short description can be found at |Q_op|.
|
||||
When on, uses |highlight-guifg| and |highlight-guibg| attributes in
|
||||
the terminal (thus using 24-bit color). Requires a ISO-8613-3
|
||||
compatible terminal.
|
||||
Must be set at startup (in your |init.vim| or |--cmd|).
|
||||
|
||||
*'terse'* *'noterse'*
|
||||
'terse' boolean (default off)
|
||||
|
@ -80,6 +80,27 @@ Global Events *ui-global*
|
||||
|
||||
Some keys are missing in some modes.
|
||||
|
||||
["option_set", name, value]
|
||||
The value of ui related option `name` changed. The sent options are
|
||||
listed below:
|
||||
|
||||
'arabicshape'
|
||||
'ambiwith'
|
||||
'emoji'
|
||||
'guifont'
|
||||
'guifontset'
|
||||
'guifontwide'
|
||||
'showtabline'
|
||||
'termguicolors'
|
||||
|
||||
Options are not added to the list if their effects are already taken
|
||||
care of. For instance, instead of forwarding the raw 'mouse' option
|
||||
value, `mouse_on` and `mouse_off` directly indicate if mouse support
|
||||
is active right now. Some options like 'ambiwith' have already taken
|
||||
effect on the grid, where appropriate empty cells are added, however
|
||||
an ui might still use these options when rendering raw text sent from
|
||||
Nvim, like the text of the cmdline when |ui-ext-cmdline| is set.
|
||||
|
||||
["mode_change", mode, mode_idx]
|
||||
The mode changed. The first parameter `mode` is a string representing
|
||||
the current mode. `mode_idx` is an index into the array received in
|
||||
|
@ -93,6 +93,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
|
||||
ui->suspend = remote_ui_suspend;
|
||||
ui->set_title = remote_ui_set_title;
|
||||
ui->set_icon = remote_ui_set_icon;
|
||||
ui->option_set = remote_ui_option_set;
|
||||
ui->event = remote_ui_event;
|
||||
|
||||
memset(ui->ui_ext, 0, sizeof(ui->ui_ext));
|
||||
|
@ -58,6 +58,8 @@ void set_title(String title)
|
||||
FUNC_API_SINCE(3);
|
||||
void set_icon(String icon)
|
||||
FUNC_API_SINCE(3);
|
||||
void option_set(String name, Object value)
|
||||
FUNC_API_SINCE(4) FUNC_API_BRIDGE_IMPL;
|
||||
|
||||
void popupmenu_show(Array items, Integer selected, Integer row, Integer col)
|
||||
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
|
||||
|
@ -37,7 +37,7 @@ function write_arglist(output, ev, need_copy)
|
||||
for j = 1, #ev.parameters do
|
||||
local param = ev.parameters[j]
|
||||
local kind = string.upper(param[1])
|
||||
local do_copy = need_copy and (kind == "ARRAY" or kind == "DICTIONARY" or kind == "STRING")
|
||||
local do_copy = need_copy and (kind == "ARRAY" or kind == "DICTIONARY" or kind == "STRING" or kind == "OBJECT")
|
||||
output:write(' ADD(args, ')
|
||||
if do_copy then
|
||||
output:write('copy_object(')
|
||||
@ -91,7 +91,7 @@ for i = 1, #events do
|
||||
recv_cleanup = recv_cleanup..' api_free_string('..param[2]..');\n'
|
||||
argc = argc+2
|
||||
elseif param[1] == 'Array' then
|
||||
send = send..' Array copy_'..param[2]..' = copy_array('..param[2]..');\n'
|
||||
send = send..' Array '..copy..' = copy_array('..param[2]..');\n'
|
||||
argv = argv..', '..copy..'.items, INT2PTR('..copy..'.size)'
|
||||
recv = (recv..' Array '..param[2]..
|
||||
' = (Array){.items = argv['..argc..'],'..
|
||||
@ -99,6 +99,15 @@ for i = 1, #events do
|
||||
recv_argv = recv_argv..', '..param[2]
|
||||
recv_cleanup = recv_cleanup..' api_free_array('..param[2]..');\n'
|
||||
argc = argc+2
|
||||
elseif param[1] == 'Object' then
|
||||
send = send..' Object *'..copy..' = xmalloc(sizeof(Object));\n'
|
||||
send = send..' *'..copy..' = copy_object('..param[2]..');\n'
|
||||
argv = argv..', '..copy
|
||||
recv = recv..' Object '..param[2]..' = *(Object *)argv['..argc..'];\n'
|
||||
recv_argv = recv_argv..', '..param[2]
|
||||
recv_cleanup = (recv_cleanup..' api_free_object('..param[2]..');\n'..
|
||||
' xfree(argv['..argc..']);\n')
|
||||
argc = argc+1
|
||||
elseif param[1] == 'Integer' or param[1] == 'Boolean' then
|
||||
argv = argv..', INT2PTR('..param[2]..')'
|
||||
recv_argv = recv_argv..', PTR2INT(argv['..argc..'])'
|
||||
@ -119,7 +128,7 @@ for i = 1, #events do
|
||||
write_signature(bridge_output, ev, 'UI *ui')
|
||||
bridge_output:write('\n{\n')
|
||||
bridge_output:write(send)
|
||||
bridge_output:write(' UI_BRIDGE_CALL(ui, '..ev.name..', '..argc..', ui'..argv..');\n}\n')
|
||||
bridge_output:write(' UI_BRIDGE_CALL(ui, '..ev.name..', '..argc..', ui'..argv..');\n}\n\n')
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -36,6 +36,7 @@ local redraw_flags={
|
||||
all_windows='P_RALL',
|
||||
everything='P_RCLR',
|
||||
curswant='P_CURSWANT',
|
||||
ui_option='P_UI_OPTION',
|
||||
}
|
||||
|
||||
local list_flags={
|
||||
|
@ -74,6 +74,7 @@
|
||||
#include "nvim/undo.h"
|
||||
#include "nvim/window.h"
|
||||
#include "nvim/os/os.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
#include "nvim/os/input.h"
|
||||
#include "nvim/os/lang.h"
|
||||
|
||||
@ -248,6 +249,7 @@ typedef struct vimoption {
|
||||
|
||||
#define P_RWINONLY 0x10000000U ///< only redraw current window
|
||||
#define P_NDNAME 0x20000000U ///< only normal dir name chars allowed
|
||||
#define P_UI_OPTION 0x40000000U ///< send option to remote ui
|
||||
|
||||
#define HIGHLIGHT_INIT \
|
||||
"8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText," \
|
||||
@ -1188,6 +1190,7 @@ do_set (
|
||||
set_options_default(OPT_FREE | opt_flags);
|
||||
didset_options();
|
||||
didset_options2();
|
||||
ui_refresh_options();
|
||||
redraw_all_later(CLEAR);
|
||||
} else {
|
||||
showoptions(1, opt_flags);
|
||||
@ -1815,6 +1818,10 @@ do_set (
|
||||
NULL, false, NULL);
|
||||
reset_v_option_vars();
|
||||
xfree(saved_origval);
|
||||
if (options[opt_idx].flags & P_UI_OPTION) {
|
||||
ui_call_option_set(cstr_as_string(options[opt_idx].fullname),
|
||||
STRING_OBJ(cstr_as_string(*(char **)varp)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// key code option(FIXME(tarruda): Show a warning or something
|
||||
@ -2417,6 +2424,10 @@ static char *set_string_option(const int opt_idx, const char *const value,
|
||||
NULL, false, NULL);
|
||||
reset_v_option_vars();
|
||||
xfree(saved_oldval);
|
||||
if (options[opt_idx].flags & P_UI_OPTION) {
|
||||
ui_call_option_set(cstr_as_string(options[opt_idx].fullname),
|
||||
STRING_OBJ(cstr_as_string((char *)(*varp))));
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
@ -4024,6 +4035,10 @@ static char *set_bool_option(const int opt_idx, char_u *const varp,
|
||||
(char_u *) options[opt_idx].fullname,
|
||||
NULL, false, NULL);
|
||||
reset_v_option_vars();
|
||||
if (options[opt_idx].flags & P_UI_OPTION) {
|
||||
ui_call_option_set(cstr_as_string(options[opt_idx].fullname),
|
||||
BOOLEAN_OBJ(value));
|
||||
}
|
||||
}
|
||||
|
||||
comp_col(); /* in case 'ruler' or 'showcmd' changed */
|
||||
@ -4429,6 +4444,10 @@ static char *set_num_option(int opt_idx, char_u *varp, long value,
|
||||
(char_u *) options[opt_idx].fullname,
|
||||
NULL, false, NULL);
|
||||
reset_v_option_vars();
|
||||
if (options[opt_idx].flags & P_UI_OPTION) {
|
||||
ui_call_option_set(cstr_as_string(options[opt_idx].fullname),
|
||||
INTEGER_OBJ(value));
|
||||
}
|
||||
}
|
||||
|
||||
comp_col(); /* in case 'columns' or 'ls' changed */
|
||||
@ -4999,6 +5018,29 @@ static int optval_default(vimoption_T *p, char_u *varp)
|
||||
return STRCMP(*(char_u **)varp, p->def_val[dvi]) == 0;
|
||||
}
|
||||
|
||||
/// Send update to UIs with values of UI relevant options
|
||||
void ui_refresh_options(void)
|
||||
{
|
||||
for (int opt_idx = 0; options[opt_idx].fullname; opt_idx++) {
|
||||
uint32_t flags = options[opt_idx].flags;
|
||||
if (!(flags & P_UI_OPTION)) {
|
||||
continue;
|
||||
}
|
||||
String name = cstr_as_string(options[opt_idx].fullname);
|
||||
void *varp = options[opt_idx].var;
|
||||
Object value = OBJECT_INIT;
|
||||
if (flags & P_BOOL) {
|
||||
value = BOOLEAN_OBJ(*(int *)varp);
|
||||
} else if (flags & P_NUM) {
|
||||
value = INTEGER_OBJ(*(long *)varp);
|
||||
} else if (flags & P_STRING) {
|
||||
// cstr_as_string handles NULL string
|
||||
value = STRING_OBJ(cstr_as_string(*(char **)varp));
|
||||
}
|
||||
ui_call_option_set(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* showoneopt: show the value of one option
|
||||
* must not be called with a hidden option!
|
||||
|
@ -447,6 +447,9 @@ EXTERN char_u *p_popt; // 'printoptions'
|
||||
EXTERN char_u *p_header; // 'printheader'
|
||||
EXTERN int p_prompt; // 'prompt'
|
||||
EXTERN char_u *p_guicursor; // 'guicursor'
|
||||
EXTERN char_u *p_guifont; // 'guifont'
|
||||
EXTERN char_u *p_guifontset; // 'guifontset'
|
||||
EXTERN char_u *p_guifontwide; // 'guifontwide'
|
||||
EXTERN char_u *p_hf; // 'helpfile'
|
||||
EXTERN long p_hh; // 'helpheight'
|
||||
EXTERN char_u *p_hlg; // 'helplang'
|
||||
|
@ -68,7 +68,8 @@ return {
|
||||
type='bool', scope={'global'},
|
||||
vi_def=true,
|
||||
vim=true,
|
||||
redraw={'everything'},
|
||||
redraw={'everything', 'ui_option'},
|
||||
|
||||
varname='p_arshape',
|
||||
defaults={if_true={vi=true}}
|
||||
},
|
||||
@ -91,7 +92,7 @@ return {
|
||||
full_name='ambiwidth', abbreviation='ambw',
|
||||
type='string', scope={'global'},
|
||||
vi_def=true,
|
||||
redraw={'everything'},
|
||||
redraw={'everything', 'ui_option'},
|
||||
varname='p_ambw',
|
||||
defaults={if_true={vi="single"}}
|
||||
},
|
||||
@ -661,7 +662,7 @@ return {
|
||||
full_name='emoji', abbreviation='emo',
|
||||
type='bool', scope={'global'},
|
||||
vi_def=true,
|
||||
redraw={'everything'},
|
||||
redraw={'everything', 'ui_option'},
|
||||
varname='p_emoji',
|
||||
defaults={if_true={vi=true}}
|
||||
},
|
||||
@ -1021,23 +1022,26 @@ return {
|
||||
type='string', list='onecomma', scope={'global'},
|
||||
deny_duplicates=true,
|
||||
vi_def=true,
|
||||
redraw={'everything'},
|
||||
enable_if=false,
|
||||
varname='p_guifont',
|
||||
redraw={'everything', 'ui_option'},
|
||||
defaults={if_true={vi=""}}
|
||||
},
|
||||
{
|
||||
full_name='guifontset', abbreviation='gfs',
|
||||
type='string', list='onecomma', scope={'global'},
|
||||
vi_def=true,
|
||||
redraw={'everything'},
|
||||
enable_if=false,
|
||||
varname='p_guifontset',
|
||||
redraw={'everything', 'ui_option'},
|
||||
defaults={if_true={vi=""}}
|
||||
},
|
||||
{
|
||||
full_name='guifontwide', abbreviation='gfw',
|
||||
type='string', list='onecomma', scope={'global'},
|
||||
deny_duplicates=true,
|
||||
vi_def=true,
|
||||
redraw={'everything'},
|
||||
enable_if=false,
|
||||
redraw={'everything', 'ui_option'},
|
||||
varname='p_guifontwide',
|
||||
defaults={if_true={vi=""}}
|
||||
},
|
||||
{
|
||||
full_name='guioptions', abbreviation='go',
|
||||
@ -2164,7 +2168,7 @@ return {
|
||||
full_name='showtabline', abbreviation='stal',
|
||||
type='number', scope={'global'},
|
||||
vi_def=true,
|
||||
redraw={'all_windows'},
|
||||
redraw={'all_windows', 'ui_option'},
|
||||
varname='p_stal',
|
||||
defaults={if_true={vi=1}}
|
||||
},
|
||||
@ -2435,7 +2439,7 @@ return {
|
||||
full_name='termguicolors', abbreviation='tgc',
|
||||
type='bool', scope={'global'},
|
||||
vi_def=false,
|
||||
redraw={'everything'},
|
||||
redraw={'everything', 'ui_option'},
|
||||
varname='p_tgc',
|
||||
defaults={if_true={vi=false}}
|
||||
},
|
||||
|
@ -151,6 +151,7 @@ UI *tui_start(void)
|
||||
ui->suspend = tui_suspend;
|
||||
ui->set_title = tui_set_title;
|
||||
ui->set_icon = tui_set_icon;
|
||||
ui->option_set= tui_option_set;
|
||||
ui->event = tui_event;
|
||||
|
||||
memset(ui->ui_ext, 0, sizeof(ui->ui_ext));
|
||||
@ -1136,6 +1137,14 @@ static void tui_set_icon(UI *ui, String icon)
|
||||
{
|
||||
}
|
||||
|
||||
static void tui_option_set(UI *ui, String name, Object value)
|
||||
{
|
||||
if (strequal(name.data, "termguicolors")) {
|
||||
// NB: value for bridge is set in ui_bridge.c
|
||||
ui->rgb = value.data.boolean;
|
||||
}
|
||||
}
|
||||
|
||||
// NB: if we start to use this, the ui_bridge must be updated
|
||||
// to make a copy for the tui thread
|
||||
static void tui_event(UI *ui, char *name, Array args, bool *args_consumed)
|
||||
|
@ -339,6 +339,7 @@ void ui_attach_impl(UI *ui)
|
||||
}
|
||||
|
||||
uis[ui_count++] = ui;
|
||||
ui_refresh_options();
|
||||
ui_refresh();
|
||||
}
|
||||
|
||||
|
@ -66,6 +66,7 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler)
|
||||
rv->bridge.suspend = ui_bridge_suspend;
|
||||
rv->bridge.set_title = ui_bridge_set_title;
|
||||
rv->bridge.set_icon = ui_bridge_set_icon;
|
||||
rv->bridge.option_set = ui_bridge_option_set;
|
||||
rv->scheduler = scheduler;
|
||||
|
||||
for (UIWidget i = 0; (int)i < UI_WIDGETS; i++) {
|
||||
@ -144,6 +145,29 @@ static void ui_bridge_highlight_set_event(void **argv)
|
||||
xfree(argv[1]);
|
||||
}
|
||||
|
||||
static void ui_bridge_option_set(UI *ui, String name, Object value)
|
||||
{
|
||||
// Assumes bridge is only used by TUI
|
||||
if (strequal(name.data, "termguicolors")) {
|
||||
ui->rgb = value.data.boolean;
|
||||
}
|
||||
String copy_name = copy_string(name);
|
||||
Object *copy_value = xmalloc(sizeof(Object));
|
||||
*copy_value = copy_object(value);
|
||||
UI_BRIDGE_CALL(ui, option_set, 4, ui,
|
||||
copy_name.data, INT2PTR(copy_name.size), copy_value);
|
||||
}
|
||||
static void ui_bridge_option_set_event(void **argv)
|
||||
{
|
||||
UI *ui = UI(argv[0]);
|
||||
String name = (String){ .data = argv[1], .size = (size_t)argv[2] };
|
||||
Object value = *(Object *)argv[3];
|
||||
ui->option_set(ui, name, value);
|
||||
api_free_string(name);
|
||||
api_free_object(value);
|
||||
xfree(argv[3]);
|
||||
}
|
||||
|
||||
static void ui_bridge_suspend(UI *b)
|
||||
{
|
||||
UIBridgeData *data = (UIBridgeData *)b;
|
||||
|
@ -4,6 +4,7 @@ local global_helpers = require('test.helpers')
|
||||
local uname = global_helpers.uname
|
||||
local helpers = require('test.functional.helpers')(after_each)
|
||||
local thelpers = require('test.functional.terminal.helpers')
|
||||
local Screen = require('test.functional.ui.screen')
|
||||
local eq = helpers.eq
|
||||
local feed_data = thelpers.feed_data
|
||||
local feed_command = helpers.feed_command
|
||||
@ -179,6 +180,58 @@ describe('tui', function()
|
||||
{3:-- TERMINAL --} |
|
||||
]])
|
||||
end)
|
||||
|
||||
it('allows termguicolors to be set at runtime', function()
|
||||
screen:set_option('rgb', true)
|
||||
screen:set_default_attr_ids({
|
||||
[1] = {reverse = true},
|
||||
[2] = {foreground = 13, special = Screen.colors.Grey0},
|
||||
[3] = {special = Screen.colors.Grey0, bold = true, reverse = true},
|
||||
[4] = {bold = true},
|
||||
[5] = {special = Screen.colors.Grey0, reverse = true, foreground = 4},
|
||||
[6] = {foreground = 4, special = Screen.colors.Grey0},
|
||||
[7] = {special = Screen.colors.Grey0, reverse = true, foreground = Screen.colors.SeaGreen4},
|
||||
[8] = {foreground = Screen.colors.SeaGreen4, special = Screen.colors.Grey0},
|
||||
[9] = {special = Screen.colors.Grey0, bold = true, foreground = Screen.colors.Blue1},
|
||||
})
|
||||
|
||||
feed_data(':hi SpecialKey ctermfg=3 guifg=SeaGreen\n')
|
||||
feed_data('i')
|
||||
feed_data('\022\007') -- ctrl+g
|
||||
feed_data('\028\014') -- crtl+\ ctrl+N
|
||||
feed_data(':set termguicolors?\n')
|
||||
screen:expect([[
|
||||
{5:^}{6:G} |
|
||||
{2:~ }|
|
||||
{2:~ }|
|
||||
{2:~ }|
|
||||
{3:[No Name] [+] }|
|
||||
notermguicolors |
|
||||
{4:-- TERMINAL --} |
|
||||
]])
|
||||
|
||||
feed_data(':set termguicolors\n')
|
||||
screen:expect([[
|
||||
{7:^}{8:G} |
|
||||
{9:~ }|
|
||||
{9:~ }|
|
||||
{9:~ }|
|
||||
{3:[No Name] [+] }|
|
||||
|
|
||||
{4:-- TERMINAL --} |
|
||||
]])
|
||||
|
||||
feed_data(':set notermguicolors\n')
|
||||
screen:expect([[
|
||||
{5:^}{6:G} |
|
||||
{2:~ }|
|
||||
{2:~ }|
|
||||
{2:~ }|
|
||||
{3:[No Name] [+] }|
|
||||
|
|
||||
{4:-- TERMINAL --} |
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('tui with non-tty file descriptors', function()
|
||||
|
66
test/functional/ui/options_spec.lua
Normal file
66
test/functional/ui/options_spec.lua
Normal file
@ -0,0 +1,66 @@
|
||||
local helpers = require('test.functional.helpers')(after_each)
|
||||
local Screen = require('test.functional.ui.screen')
|
||||
local clear = helpers.clear
|
||||
local command = helpers.command
|
||||
local eq = helpers.eq
|
||||
|
||||
describe('ui receives option updates', function()
|
||||
local screen
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
screen = Screen.new(20,5)
|
||||
screen:attach()
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
screen:detach()
|
||||
end)
|
||||
|
||||
local defaults = {
|
||||
ambiwidth='single',
|
||||
arabicshape=true,
|
||||
emoji=true,
|
||||
guifont='',
|
||||
guifontset='',
|
||||
guifontwide='',
|
||||
showtabline=1,
|
||||
termguicolors=false,
|
||||
}
|
||||
|
||||
it("for defaults", function()
|
||||
screen:expect(function()
|
||||
eq(defaults, screen.options)
|
||||
end)
|
||||
end)
|
||||
|
||||
it("when setting options", function()
|
||||
local changed = {}
|
||||
for k,v in pairs(defaults) do
|
||||
changed[k] = v
|
||||
end
|
||||
|
||||
command("set termguicolors")
|
||||
changed.termguicolors = true
|
||||
screen:expect(function()
|
||||
eq(changed, screen.options)
|
||||
end)
|
||||
|
||||
command("set guifont=Comic\\ Sans")
|
||||
changed.guifont = "Comic Sans"
|
||||
screen:expect(function()
|
||||
eq(changed, screen.options)
|
||||
end)
|
||||
|
||||
command("set showtabline=0")
|
||||
changed.showtabline = 0
|
||||
screen:expect(function()
|
||||
eq(changed, screen.options)
|
||||
end)
|
||||
|
||||
command("set all&")
|
||||
screen:expect(function()
|
||||
eq(defaults, screen.options)
|
||||
end)
|
||||
end)
|
||||
end)
|
@ -137,6 +137,7 @@ function Screen.new(width, height)
|
||||
visual_bell = false,
|
||||
suspended = false,
|
||||
mode = 'normal',
|
||||
options = {},
|
||||
_default_attr_ids = nil,
|
||||
_default_attr_ignore = nil,
|
||||
_mouse_enabled = true,
|
||||
@ -482,6 +483,10 @@ function Screen:_handle_set_icon(icon)
|
||||
self.icon = icon
|
||||
end
|
||||
|
||||
function Screen:_handle_option_set(name, value)
|
||||
self.options[name] = value
|
||||
end
|
||||
|
||||
function Screen:_clear_block(top, bot, left, right)
|
||||
for i = top, bot do
|
||||
self:_clear_row_section(i, left, right)
|
||||
|
Loading…
Reference in New Issue
Block a user