ui: forward relevant option updates to UIs (#7520)

also make termguicolors mutable after startup
This commit is contained in:
Björn Linse 2017-12-12 18:23:19 +01:00 committed by GitHub
parent f976826690
commit 34057045be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 255 additions and 15 deletions

View File

@ -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)

View File

@ -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

View File

@ -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));

View File

@ -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;

View File

@ -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

View File

@ -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={

View File

@ -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!

View File

@ -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'

View File

@ -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}}
},

View File

@ -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)

View File

@ -339,6 +339,7 @@ void ui_attach_impl(UI *ui)
}
uis[ui_count++] = ui;
ui_refresh_options();
ui_refresh();
}

View File

@ -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;

View File

@ -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()

View 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)

View File

@ -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)