aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/msgpack_rpc.txt25
-rw-r--r--src/nvim/api/ui.c40
-rw-r--r--src/nvim/popupmnu.c9
-rw-r--r--src/nvim/screen.c18
-rw-r--r--src/nvim/tui/tui.c5
-rw-r--r--src/nvim/ui.c51
-rw-r--r--src/nvim/ui.h10
-rw-r--r--src/nvim/ui_bridge.c6
-rw-r--r--src/nvim/window.c4
-rw-r--r--test/functional/ui/screen_basic_spec.lua38
-rw-r--r--test/functional/ui/tabline_spec.lua10
-rw-r--r--test/functional/viml/completion_spec.lua4
12 files changed, 124 insertions, 96 deletions
diff --git a/runtime/doc/msgpack_rpc.txt b/runtime/doc/msgpack_rpc.txt
index c550631a32..7adc4ca2e9 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,14 +264,13 @@ 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.
- `tabline_external` Instead of drawing the tabline on the grid,
- Nvim will send higher-level events to
- the UI and let it draw the tabline.
- Defaults to false.
+ `ui_ext` String array of "externalized" widgets.
+ Widgets in this list will not be drawn by
+ Nvim; only high-level data will be published
+ in new UI event kinds. Valid names:
+ popupmenu |ui-ext-popupmenu|
+ tabline |ui-ext-tabline|
+ Defaults to empty.
Nvim will then send msgpack-rpc notifications, with the method name "redraw"
and a single argument, an array of screen updates (described below). These
@@ -421,6 +420,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
@@ -440,9 +440,10 @@ states might be represented as separate modes.
["popupmenu_hide"]
The popupmenu is hidden.
+ *ui-ext-tabline*
["tabline_update", curtab, tabs]
Nvim will send this event when drawing tabline. curtab is the tab id
- of the current tab. tabs is an arrays of the form:
+ of the current tab. tabs is an array of the form:
[tabid, { "name": name }]
==============================================================================
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index 28fc641dec..08d285eedc 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -68,8 +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->tabline_external = false;
ui->resize = remote_ui_resize;
ui->clear = remote_ui_clear;
ui->eol_clear = remote_ui_eol_clear;
@@ -96,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)) {
@@ -171,7 +171,8 @@ void nvim_ui_set_option(uint64_t channel_id, String name,
}
}
-static void ui_set_option(UI *ui, String name, Object value, Error *error) {
+static void ui_set_option(UI *ui, String name, Object value, Error *error)
+{
if (strequal(name.data, "rgb")) {
if (value.type != kObjectTypeBoolean) {
api_set_error(error, kErrorTypeValidation, "rgb must be a Boolean");
@@ -179,19 +180,42 @@ static void ui_set_option(UI *ui, String name, Object value, Error *error) {
}
ui->rgb = value.data.boolean;
} else 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 if (strequal(name.data, "tabline_external")) {
- if (value.type != kObjectTypeBoolean) {
+ ui->ui_ext[kUIPopupmenu] = value.data.boolean;
+ } else if (strequal(name.data, "ui_ext")) {
+ if (value.type != kObjectTypeArray) {
api_set_error(error, kErrorTypeValidation,
- "tabline_external must be a Boolean");
+ "ui_ext must be an Array");
return;
}
- ui->tabline_external = value.data.boolean;
+
+ for (size_t i = 0; i < value.data.array.size; i++) {
+ Object item = value.data.array.items[i];
+ if (item.type != kObjectTypeString) {
+ api_set_error(error, kErrorTypeValidation,
+ "ui_ext: item must be a String");
+ return;
+ }
+ char *name = item.data.string.data;
+ if (strequal(name, "cmdline")) {
+ ui->ui_ext[kUICmdline] = true;
+ } else if (strequal(name, "popupmenu")) {
+ ui->ui_ext[kUIPopupmenu] = true;
+ } else if (strequal(name, "tabline")) {
+ ui->ui_ext[kUITabline] = true;
+ } else if (strequal(name, "wildmenu")) {
+ ui->ui_ext[kUIWildmenu] = true;
+ } else {
+ api_set_error(error, kErrorTypeValidation,
+ "ui_ext: unknown widget: %s", name);
+ return;
+ }
+ }
} else {
api_set_error(error, kErrorTypeValidation, "No such ui option");
}
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 ed85b6e8b8..a6563534aa 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -6886,14 +6886,13 @@ static void draw_tabline(void)
if (ScreenLines == NULL) {
return;
}
+ redraw_tabline = false;
- if (ui_is_widget_external(kUITabline)) {
- draw_tabline_ext();
+ if (ui_is_external(kUITabline)) {
+ ui_ext_tabline_update();
return;
}
- redraw_tabline = false;
-
if (tabline_height() < 1)
return;
@@ -7033,20 +7032,13 @@ static void draw_tabline(void)
redraw_tabline = FALSE;
}
-// send tabline update to external ui
-void draw_tabline_ext(void)
+void ui_ext_tabline_update(void)
{
- win_T *cwp;
-
Array args = ARRAY_DICT_INIT;
ADD(args, INTEGER_OBJ(curtab->handle));
Array tabs = ARRAY_DICT_INIT;
FOR_ALL_TABS(tp) {
- if (tp == curtab) {
- cwp = curwin;
- } else {
- cwp = tp->tp_curwin;
- }
+ win_T *cwp = (tp == curtab) ? curwin : tp->tp_curwin;
get_trans_bufname(cwp->w_buffer);
Array tab = ARRAY_DICT_INIT;
ADD(tab, INTEGER_OBJ(tp->handle));
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index cd94fe9d49..21abc19c47 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -109,8 +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->tabline_external = false;
ui->resize = tui_resize;
ui->clear = tui_clear;
ui->eol_clear = tui_eol_clear;
@@ -136,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 6dcc3de1b0..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 {
@@ -58,10 +59,6 @@ static int busy = 0;
static int height, width;
static int old_mode_idx = -1;
-static bool tabline_external = false;
-static bool cmdline_external = false;
-static bool wildmenu_external = false;
-
// UI_CALL invokes a function on all registered UI instances. The functions can
// have 0-5 arguments (configurable by SELECT_NTH).
//
@@ -170,21 +167,25 @@ void ui_refresh(void)
}
int width = INT_MAX, height = INT_MAX;
- bool pum_external = true;
- bool tabline_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;
- tabline_external &= ui->tabline_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);
- ui_set_widget_external(kUITabline, tabline_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();
@@ -564,30 +565,16 @@ void ui_cursor_shape(void)
conceal_check_cursur_line();
}
-bool ui_is_widget_external(UIWidget widget)
+/// Returns true if `widget` is externalized.
+bool ui_is_external(UIWidget widget)
{
- switch (widget) {
- case kUITabline:
- return tabline_external;
- case kUICmdline:
- return cmdline_external;
- case kUIWildmenu:
- return wildmenu_external;
- }
- return false;
+ return ui_ext[widget];
}
-void ui_set_widget_external(UIWidget widget, bool external)
+/// 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)
{
- switch (widget) {
- case kUITabline:
- tabline_external = external;
- break;
- case kUICmdline:
- cmdline_external = external;
- break;
- case kUIWildmenu:
- wildmenu_external = external;
- break;
- }
+ ui_ext[widget] = external;
}
diff --git a/src/nvim/ui.h b/src/nvim/ui.h
index a1ff449eaf..9338ab3ea3 100644
--- a/src/nvim/ui.h
+++ b/src/nvim/ui.h
@@ -8,12 +8,13 @@
#include "api/private/defs.h"
#include "nvim/buffer_defs.h"
-// values for externalized widgets
typedef enum {
+ kUICmdline = 0,
+ kUIPopupmenu,
kUITabline,
- kUICmdline,
- kUIWildmenu
+ kUIWildmenu,
} UIWidget;
+#define UI_WIDGETS (kUIWildmenu + 1)
typedef struct {
bool bold, underline, undercurl, italic, reverse;
@@ -23,7 +24,8 @@ typedef struct {
typedef struct ui_t UI;
struct ui_t {
- bool rgb, pum_external, tabline_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 9a3fcab13c..b7b12ae39e 100644
--- a/src/nvim/ui_bridge.c
+++ b/src/nvim/ui_bridge.c
@@ -57,8 +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.tabline_external = ui->tabline_external;
rv->bridge.stop = ui_bridge_stop;
rv->bridge.resize = ui_bridge_resize;
rv->bridge.clear = ui_bridge_clear;
@@ -86,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 effce8b413..69c0a838ea 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -5224,8 +5224,8 @@ static void last_status_rec(frame_T *fr, int statusline)
*/
int tabline_height(void)
{
- if (ui_is_widget_external(kUITabline)) {
- return 0;
+ if (ui_is_external(kUITabline)) {
+ return 0;
}
assert(first_tabpage);
switch (p_stal) {
diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua
index d9cb3d7b6f..d6aa1aa993 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,40 @@ 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('"ui_ext" widgets', function()
+ local screen = Screen.new()
+ screen:attach({ui_ext={
+ 'cmdline',
+ 'popupmenu',
+ 'tabline',
+ 'wildmenu',
+ }})
+ end)
+ it('invalid "ui_ext" returns error', function()
+ local screen = Screen.new()
+
+ local status, rv = pcall(function() screen:attach({ui_ext={'foo'}}) end)
+ eq(false, status)
+ eq('ui_ext: unknown widget: foo', rv:match("ui_ext:.*"))
+
+ status, rv = pcall(function() screen:attach({ui_ext={'cmdline','foo'}}) end)
+ eq(false, status)
+ eq('ui_ext: unknown widget: foo', rv:match("ui_ext:.*"))
+
+ status, rv = pcall(function() screen:attach({ui_ext={'cmdline',1}}) end)
+ eq(false, status)
+ eq('ui_ext: item must be a String', rv:match("ui_ext:.*"))
+ end)
end)
diff --git a/test/functional/ui/tabline_spec.lua b/test/functional/ui/tabline_spec.lua
index 018e008e4a..c62dd0c94b 100644
--- a/test/functional/ui/tabline_spec.lua
+++ b/test/functional/ui/tabline_spec.lua
@@ -2,16 +2,14 @@ local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear, feed, eq = helpers.clear, helpers.feed, helpers.eq
-if helpers.pending_win32(pending) then return end
-
-describe('External tab line', function()
+describe('ui/tabline', function()
local screen
local tabs, curtab
before_each(function()
clear()
screen = Screen.new(25, 5)
- screen:attach({rgb=true, tabline_external=true})
+ screen:attach({rgb=true, ui_ext={'tabline'}})
screen:set_on_event_handler(function(name, data)
if name == "tabline_update" then
curtab, tabs = unpack(data)
@@ -23,8 +21,8 @@ describe('External tab line', function()
screen:detach()
end)
- describe("'tabline'", function()
- it('tabline', function()
+ describe('externalized', function()
+ it('publishes UI events', function()
local expected = {
{1, {['name'] = '[No Name]'}},
{2, {['name'] = '[No Name]'}},
diff --git a/test/functional/viml/completion_spec.lua b/test/functional/viml/completion_spec.lua
index b35e8d4f94..5f5e9437dc 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, ui_ext={'popupmenu'}})
screen:set_default_attr_ids({
[1] = {bold=true, foreground=Screen.colors.Blue},
[2] = {bold = true},