diff options
-rw-r--r-- | runtime/doc/msgpack_rpc.txt | 23 | ||||
-rw-r--r-- | src/nvim/api/ui.c | 39 | ||||
-rw-r--r-- | src/nvim/popupmnu.c | 9 | ||||
-rw-r--r-- | src/nvim/screen.c | 25 | ||||
-rw-r--r-- | src/nvim/tui/tui.c | 4 | ||||
-rw-r--r-- | src/nvim/ui.c | 27 | ||||
-rw-r--r-- | src/nvim/ui.h | 11 | ||||
-rw-r--r-- | src/nvim/ui_bridge.c | 5 | ||||
-rw-r--r-- | src/nvim/window.c | 4 | ||||
-rw-r--r-- | test/functional/ui/screen_basic_spec.lua | 20 | ||||
-rw-r--r-- | test/functional/ui/tabline_spec.lua | 57 | ||||
-rw-r--r-- | test/functional/viml/completion_spec.lua | 4 | ||||
-rw-r--r-- | third-party/cmake/BuildLuarocks.cmake | 2 |
13 files changed, 194 insertions, 36 deletions
diff --git a/runtime/doc/msgpack_rpc.txt b/runtime/doc/msgpack_rpc.txt index 77cbc2f3d8..261e68cfb1 100644 --- a/runtime/doc/msgpack_rpc.txt +++ b/runtime/doc/msgpack_rpc.txt @@ -251,9 +251,9 @@ connect to another with different type codes. 6. Remote UIs *rpc-remote-ui* GUIs can be implemented as external processes communicating with Nvim over the -RPC API. Currently the UI model consists of a terminal-like grid with one -single, monospace font size. Some elements (UI "widgets") can be drawn -separately from the grid. +RPC API. The UI model consists of a terminal-like grid with a single, +monospace font size. Some elements (UI "widgets") can be drawn separately from +the grid ("externalized"). After connecting to Nvim (usually a spawned, embedded instance) use the |nvim_ui_attach| API method to tell Nvim that your program wants to draw the @@ -264,10 +264,11 @@ a dictionary with these (optional) keys: colors. Set to false to use terminal color codes (at most 256 different colors). - `popupmenu_external` Instead of drawing the completion popupmenu on - the grid, Nvim will send higher-level events to - the ui and let it draw the popupmenu. - Defaults to false. + `ext_popupmenu` Externalize the popupmenu. |ui-ext-popupmenu| + `ext_tabline` Externalize the tabline. |ui-ext-tabline| + Externalized widgets will not be drawn by + Nvim; only high-level data will be published + in new UI event kinds. Nvim will then send msgpack-rpc notifications, with the method name "redraw" and a single argument, an array of screen updates (described below). These @@ -417,6 +418,7 @@ properties specified in the corresponding item. The set of modes reported will change in new versions of Nvim, for instance more submodes and temporary states might be represented as separate modes. + *ui-ext-popupmenu* ["popupmenu_show", items, selected, row, col] When `popupmenu_external` is set to true, nvim will not draw the popupmenu on the grid, instead when the popupmenu is to be displayed @@ -436,5 +438,12 @@ states might be represented as separate modes. ["popupmenu_hide"] The popupmenu is hidden. + *ui-ext-tabline* +["tabline_update", curtab, tabs] + Tabline was updated. UIs should present this data in a custom tabline + widget. + curtab: Current Tabpage + tabs: List of Dicts [{ "tab": Tabpage, "name": String }, ...] + ============================================================================== vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index f0da0d1812..3c0e8bc049 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -68,7 +68,6 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, ui->width = (int)width; ui->height = (int)height; ui->rgb = true; - ui->pum_external = false; ui->resize = remote_ui_resize; ui->clear = remote_ui_clear; ui->eol_clear = remote_ui_eol_clear; @@ -95,6 +94,8 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, ui->set_icon = remote_ui_set_icon; ui->event = remote_ui_event; + memset(ui->ui_ext, 0, sizeof(ui->ui_ext)); + for (size_t i = 0; i < options.size; i++) { ui_set_option(ui, options.items[i].key, options.items[i].value, err); if (ERROR_SET(err)) { @@ -170,23 +171,47 @@ void nvim_ui_set_option(uint64_t channel_id, String name, } } -static void ui_set_option(UI *ui, String name, Object value, Error *error) { - if (strcmp(name.data, "rgb") == 0) { +static void ui_set_option(UI *ui, String name, Object value, Error *error) +{ +#define UI_EXT_OPTION(o, e) \ + do { \ + if (strequal(name.data, #o)) { \ + if (value.type != kObjectTypeBoolean) { \ + api_set_error(error, kErrorTypeValidation, #o " must be a Boolean"); \ + return; \ + } \ + ui->ui_ext[(e)] = value.data.boolean; \ + return; \ + } \ + } while (0) + + if (strequal(name.data, "rgb")) { if (value.type != kObjectTypeBoolean) { api_set_error(error, kErrorTypeValidation, "rgb must be a Boolean"); return; } ui->rgb = value.data.boolean; - } else if (strcmp(name.data, "popupmenu_external") == 0) { + return; + } + + UI_EXT_OPTION(ext_cmdline, kUICmdline); + UI_EXT_OPTION(ext_popupmenu, kUIPopupmenu); + UI_EXT_OPTION(ext_tabline, kUITabline); + UI_EXT_OPTION(ext_wildmenu, kUIWildmenu); + + if (strequal(name.data, "popupmenu_external")) { + // LEGACY: Deprecated option, use `ui_ext` instead. if (value.type != kObjectTypeBoolean) { api_set_error(error, kErrorTypeValidation, "popupmenu_external must be a Boolean"); return; } - ui->pum_external = value.data.boolean; - } else { - api_set_error(error, kErrorTypeValidation, "No such ui option"); + ui->ui_ext[kUIPopupmenu] = value.data.boolean; + return; } + + api_set_error(error, kErrorTypeValidation, "No such ui option"); +#undef UI_EXT_OPTION } static void push_call(UI *ui, char *name, Array args) diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index 6e81c5a171..b8650d8c62 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -41,9 +41,7 @@ static int pum_row; // top row of pum static int pum_col; // left column of pum static bool pum_is_visible = false; - static bool pum_external = false; -static bool pum_wants_external = false; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "popupmnu.c.generated.h" @@ -80,7 +78,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed) if (!pum_is_visible) { // To keep the code simple, we only allow changing the // draw mode when the popup menu is not being displayed - pum_external = pum_wants_external; + pum_external = ui_is_external(kUIPopupmenu); } redo: @@ -751,8 +749,3 @@ int pum_get_height(void) { return pum_height; } - -void pum_set_external(bool external) -{ - pum_wants_external = external; -} diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 10dc86d5fa..de24156579 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -132,6 +132,7 @@ #include "nvim/version.h" #include "nvim/window.h" #include "nvim/os/time.h" +#include "nvim/api/private/helpers.h" #define MB_FILLER_CHAR '<' /* character used when a double-width character * doesn't fit. */ @@ -6887,6 +6888,10 @@ static void draw_tabline(void) } redraw_tabline = false; + if (ui_is_external(kUITabline)) { + ui_ext_tabline_update(); + return; + } if (tabline_height() < 1) return; @@ -7027,6 +7032,26 @@ static void draw_tabline(void) redraw_tabline = FALSE; } +void ui_ext_tabline_update(void) +{ + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(curtab->handle)); + Array tabs = ARRAY_DICT_INIT; + FOR_ALL_TABS(tp) { + Dictionary tab_info = ARRAY_DICT_INIT; + PUT(tab_info, "tab", TABPAGE_OBJ(tp->handle)); + + win_T *cwp = (tp == curtab) ? curwin : tp->tp_curwin; + get_trans_bufname(cwp->w_buffer); + PUT(tab_info, "name", STRING_OBJ(cstr_to_string((char *)NameBuff))); + + ADD(tabs, DICTIONARY_OBJ(tab_info)); + } + ADD(args, ARRAY_OBJ(tabs)); + + ui_event("tabline_update", args); +} + /* * Get buffer name for "buf" into NameBuff[]. * Takes care of special buffer names and translates special characters. diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index e1b97f5306..21abc19c47 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -109,7 +109,6 @@ UI *tui_start(void) UI *ui = xcalloc(1, sizeof(UI)); ui->stop = tui_stop; ui->rgb = p_tgc; - ui->pum_external = false; ui->resize = tui_resize; ui->clear = tui_clear; ui->eol_clear = tui_eol_clear; @@ -135,6 +134,9 @@ UI *tui_start(void) ui->set_title = tui_set_title; ui->set_icon = tui_set_icon; ui->event = tui_event; + + memset(ui->ui_ext, 0, sizeof(ui->ui_ext)); + return ui_bridge_attach(ui, tui_main, tui_scheduler); } diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 69916fa4cd..713dffb46c 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -47,6 +47,7 @@ #define MAX_UI_COUNT 16 static UI *uis[MAX_UI_COUNT]; +static bool ui_ext[UI_WIDGETS] = { 0 }; static size_t ui_count = 0; static int row = 0, col = 0; static struct { @@ -166,18 +167,25 @@ void ui_refresh(void) } int width = INT_MAX, height = INT_MAX; - bool pum_external = true; + bool ext_widgets[UI_WIDGETS]; + for (UIWidget i = 0; (int)i < UI_WIDGETS; i++) { + ext_widgets[i] = true; + } for (size_t i = 0; i < ui_count; i++) { UI *ui = uis[i]; width = MIN(ui->width, width); height = MIN(ui->height, height); - pum_external &= ui->pum_external; + for (UIWidget i = 0; (int)i < UI_WIDGETS; i++) { + ext_widgets[i] &= ui->ui_ext[i]; + } } row = col = 0; screen_resize(width, height); - pum_set_external(pum_external); + for (UIWidget i = 0; (int)i < UI_WIDGETS; i++) { + ui_set_external(i, ext_widgets[i]); + } ui_mode_info_set(); old_mode_idx = -1; ui_cursor_shape(); @@ -557,3 +565,16 @@ void ui_cursor_shape(void) conceal_check_cursur_line(); } +/// Returns true if `widget` is externalized. +bool ui_is_external(UIWidget widget) +{ + return ui_ext[widget]; +} + +/// Sets `widget` as "external". +/// Such widgets are not drawn by Nvim; external UIs are expected to handle +/// higher-level UI events and present the data. +void ui_set_external(UIWidget widget, bool external) +{ + ui_ext[widget] = external; +} diff --git a/src/nvim/ui.h b/src/nvim/ui.h index fcf52ac9e1..9338ab3ea3 100644 --- a/src/nvim/ui.h +++ b/src/nvim/ui.h @@ -8,6 +8,14 @@ #include "api/private/defs.h" #include "nvim/buffer_defs.h" +typedef enum { + kUICmdline = 0, + kUIPopupmenu, + kUITabline, + kUIWildmenu, +} UIWidget; +#define UI_WIDGETS (kUIWildmenu + 1) + typedef struct { bool bold, underline, undercurl, italic, reverse; int foreground, background, special; @@ -16,7 +24,8 @@ typedef struct { typedef struct ui_t UI; struct ui_t { - bool rgb, pum_external; + bool rgb; + bool ui_ext[UI_WIDGETS]; ///< Externalized widgets int width, height; void *data; void (*resize)(UI *ui, int rows, int columns); diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c index 5697c765ba..b7b12ae39e 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -57,7 +57,6 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler) UIBridgeData *rv = xcalloc(1, sizeof(UIBridgeData)); rv->ui = ui; rv->bridge.rgb = ui->rgb; - rv->bridge.pum_external = ui->pum_external; rv->bridge.stop = ui_bridge_stop; rv->bridge.resize = ui_bridge_resize; rv->bridge.clear = ui_bridge_clear; @@ -85,6 +84,10 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler) rv->bridge.set_icon = ui_bridge_set_icon; rv->scheduler = scheduler; + for (UIWidget i = 0; (int)i < UI_WIDGETS; i++) { + rv->bridge.ui_ext[i] = ui->ui_ext[i]; + } + rv->ui_main = ui_main; uv_mutex_init(&rv->mutex); uv_cond_init(&rv->cond); diff --git a/src/nvim/window.c b/src/nvim/window.c index 60bba19b07..69c0a838ea 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -48,6 +48,7 @@ #include "nvim/syntax.h" #include "nvim/terminal.h" #include "nvim/undo.h" +#include "nvim/ui.h" #include "nvim/os/os.h" @@ -5223,6 +5224,9 @@ static void last_status_rec(frame_T *fr, int statusline) */ int tabline_height(void) { + if (ui_is_external(kUITabline)) { + return 0; + } assert(first_tabpage); switch (p_stal) { case 0: return 0; diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index d9cb3d7b6f..5d89416e4a 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -6,7 +6,7 @@ local insert = helpers.insert local eq = helpers.eq local eval = helpers.eval -describe('Initial screen', function() +describe('screen', function() local screen local nvim_argv = {helpers.nvim_prog, '-u', 'NONE', '-i', 'NONE', '-N', '--cmd', 'set shortmess+=I background=light noswapfile belloff= noshowcmd noruler', @@ -27,7 +27,7 @@ describe('Initial screen', function() screen:detach() end) - it('is the default initial screen', function() + it('default initial screen', function() screen:expect([[ ^ | {0:~ }| @@ -565,12 +565,22 @@ describe('Screen', function() ]]) end) end) +end) - it('nvim_ui_attach() handles very large width/height #2180', function() - screen:detach() - screen = Screen.new(999, 999) +describe('nvim_ui_attach()', function() + before_each(function() + clear() + end) + it('handles very large width/height #2180', function() + local screen = Screen.new(999, 999) screen:attach() eq(999, eval('&lines')) eq(999, eval('&columns')) end) + it('invalid option returns error', function() + local screen = Screen.new() + local status, rv = pcall(function() screen:attach({foo={'foo'}}) end) + eq(false, status) + eq('No such ui option', rv:match("No such .*")) + end) end) diff --git a/test/functional/ui/tabline_spec.lua b/test/functional/ui/tabline_spec.lua new file mode 100644 index 0000000000..2d5faf394b --- /dev/null +++ b/test/functional/ui/tabline_spec.lua @@ -0,0 +1,57 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear, command, eq = helpers.clear, helpers.command, helpers.eq + +describe('ui/tabline', function() + local screen + local event_tabs, event_curtab + + before_each(function() + clear() + screen = Screen.new(25, 5) + screen:attach({rgb=true, ext_tabline=true}) + screen:set_on_event_handler(function(name, data) + if name == "tabline_update" then + event_curtab, event_tabs = unpack(data) + end + end) + end) + + after_each(function() + screen:detach() + end) + + describe('externalized', function() + it('publishes UI events', function() + command("tabedit another-tab") + + local expected_tabs = { + {tab = { id = 1 }, name = '[No Name]'}, + {tab = { id = 2 }, name = 'another-tab'}, + } + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]], nil, nil, function() + eq(2, event_curtab) + eq(expected_tabs, event_tabs) + end) + + command("tabNext") + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]], nil, nil, function() + eq(1, event_curtab) + eq(expected_tabs, event_tabs) + end) + + end) + end) +end) diff --git a/test/functional/viml/completion_spec.lua b/test/functional/viml/completion_spec.lua index b35e8d4f94..0e5278345c 100644 --- a/test/functional/viml/completion_spec.lua +++ b/test/functional/viml/completion_spec.lua @@ -868,13 +868,13 @@ describe('completion', function() end) end) -describe('External completion popupmenu', function() +describe('ui/externalized/popupmenu', function() local screen local items, selected, anchor before_each(function() clear() screen = Screen.new(60, 8) - screen:attach({rgb=true, popupmenu_external=true}) + screen:attach({rgb=true, ext_popupmenu=true}) screen:set_default_attr_ids({ [1] = {bold=true, foreground=Screen.colors.Blue}, [2] = {bold = true}, diff --git a/third-party/cmake/BuildLuarocks.cmake b/third-party/cmake/BuildLuarocks.cmake index 7312b6f91b..88ddaf44ce 100644 --- a/third-party/cmake/BuildLuarocks.cmake +++ b/third-party/cmake/BuildLuarocks.cmake @@ -167,7 +167,7 @@ if(USE_BUNDLED_BUSTED) add_custom_command(OUTPUT ${HOSTDEPS_LIB_DIR}/luarocks/rocks/nvim-client COMMAND ${LUAROCKS_BINARY} - ARGS build https://raw.githubusercontent.com/neovim/lua-client/0.0.1-25/nvim-client-0.0.1-25.rockspec ${LUAROCKS_BUILDARGS} + ARGS build https://raw.githubusercontent.com/neovim/lua-client/0.0.1-26/nvim-client-0.0.1-26.rockspec ${LUAROCKS_BUILDARGS} DEPENDS luv) add_custom_target(nvim-client DEPENDS ${HOSTDEPS_LIB_DIR}/luarocks/rocks/nvim-client) |