diff options
-rw-r--r-- | runtime/doc/options.txt | 1 | ||||
-rw-r--r-- | runtime/doc/ui.txt | 21 | ||||
-rw-r--r-- | src/nvim/api/ui.c | 1 | ||||
-rw-r--r-- | src/nvim/api/ui_events.in.h | 2 | ||||
-rw-r--r-- | src/nvim/generators/gen_api_ui_events.lua | 15 | ||||
-rw-r--r-- | src/nvim/generators/gen_options.lua | 1 | ||||
-rw-r--r-- | src/nvim/option.c | 42 | ||||
-rw-r--r-- | src/nvim/option_defs.h | 3 | ||||
-rw-r--r-- | src/nvim/options.lua | 26 | ||||
-rw-r--r-- | src/nvim/tui/tui.c | 9 | ||||
-rw-r--r-- | src/nvim/ui.c | 1 | ||||
-rw-r--r-- | src/nvim/ui_bridge.c | 24 | ||||
-rw-r--r-- | test/functional/terminal/tui_spec.lua | 53 | ||||
-rw-r--r-- | test/functional/ui/options_spec.lua | 66 | ||||
-rw-r--r-- | test/functional/ui/screen.lua | 5 |
15 files changed, 255 insertions, 15 deletions
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 4180ca21f2..d3072d83e2 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -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) diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index abbd063483..e1c5523202 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -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 diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index a9eaccfac5..35508fde6b 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -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)); diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 847b21072a..3d2253e918 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -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; diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua index d2b90db707..2666ca6e6f 100644 --- a/src/nvim/generators/gen_api_ui_events.lua +++ b/src/nvim/generators/gen_api_ui_events.lua @@ -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 diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index 36562c0be9..fdc00d5dc0 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -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={ diff --git a/src/nvim/option.c b/src/nvim/option.c index f8a05f133d..499cf79836 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -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! diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index b16f222705..1b978137ae 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -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' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 16f472230a..45efd49391 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -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}} }, diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 9ff1acf64a..49aa41b9b0 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -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) diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 3b8b3ac9a7..81da88c54a 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -339,6 +339,7 @@ void ui_attach_impl(UI *ui) } uis[ui_count++] = ui; + ui_refresh_options(); ui_refresh(); } diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c index 7573fa1653..0a69cf0ecb 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -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; diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index bf3c6bdb3a..745bfeecf9 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -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() diff --git a/test/functional/ui/options_spec.lua b/test/functional/ui/options_spec.lua new file mode 100644 index 0000000000..14f40b3ec1 --- /dev/null +++ b/test/functional/ui/options_spec.lua @@ -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) diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 075d8c40d7..696feabeed 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -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) |