mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
Merge #6583 from justinmk/ui-tabline
This commit is contained in:
commit
0b59f988f4
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
57
test/functional/ui/tabline_spec.lua
Normal file
57
test/functional/ui/tabline_spec.lua
Normal file
@ -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)
|
@ -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},
|
||||
|
2
third-party/cmake/BuildLuarocks.cmake
vendored
2
third-party/cmake/BuildLuarocks.cmake
vendored
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user