Merge pull request #4432 from bfredl/pum_ui

allow external UI:s to render the popupmenu
This commit is contained in:
Björn Linse 2016-08-29 11:00:24 +02:00 committed by GitHub
commit 0b5a7e4ad5
19 changed files with 535 additions and 82 deletions

View File

@ -11,6 +11,7 @@ RPC API for Nvim *RPC* *rpc* *msgpack-rpc*
3. Connecting |rpc-connecting|
4. Clients |rpc-api-client|
5. Types |rpc-types|
6. Remote UIs |rpc-remote-ui|
==============================================================================
1. Introduction *rpc-intro*
@ -237,5 +238,170 @@ Even for statically compiled clients it is good practice to avoid hardcoding
the type codes, because a client may be built against one Nvim version but
connect to another with different type codes.
==============================================================================
6. Remote UIs *rpc-remote-ui*
Nvim allows Graphical user interfaces to be implemented by separate processes
communicating with Nvim over the RPC API. Currently the ui model conists of a
terminal-like grid with one single, monospace font size, with a few elements
that could be drawn separately from the grid (for the momemnt only the popup
menu)
After connecting to a nvim instance (typically a spawned, embedded instance)
use the |nvim_ui_attach|(width, height, options) API method to tell nvim that your
program wants to draw the nvim screen on a grid with "width" times
"height" cells. "options" should be a dictionary with the following (all
optional) keys:
`rgb`: Controls what color format to use.
Set to true (default) to use 24-bit rgb
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.
Nvim will then send msgpack-rpc notifications, with the method name "redraw"
and a single argument, an array of screen updates (described below).
These should be processed in order. Preferably the user should only be able to
see the screen state after all updates are processed (not any intermediate
state after processing only a part of the array).
Screen updates are arrays. The first element a string describing the kind
of update.
["resize", width, height]
The grid is resized to `width` and `height` cells.
["clear"]
Clear the screen.
["eol_clear"]
Clear from the cursor position to the end of the current line.
["cursor_goto", row, col]
Move the cursor to position (row, col). Currently, the same cursor is
used to define the position for text insertion and the visible cursor.
However, only the last cursor position, after processing the entire
array in the "redraw" event, is intended to be a visible cursor
position.
["update_fg", color]
["update_bg", color]
["update_sp", color]
Set the default foreground, background and special colors
respectively.
["highlight_set", attrs]
Set the attributes that the next text put on the screen will have.
`attrs` is a dict with the keys below. Any absent key is reset
to its default value. Color defaults are set by the `update_fg` etc
updates. All boolean keys default to false.
`foreground`: foreground color.
`background`: backround color.
`special`: color to use for underline and undercurl, when present.
`reverse`: reverse video. Foreground and background colors are
switched.
`italic`: italic text.
`bold`: bold text.
`underline`: underlined text. The line has `special` color.
`undercurl`: undercurled text. The curl has `special` color.
["put", text]
The (utf-8 encoded) string `text` is put at the cursor position
(and the cursor is advanced), with the highlights as set by the
last `highlight_set` update.
["set_scroll_region", top, bot, left, right]
Define the scroll region used by `scroll` below.
["scroll", count]
Scroll the text in the scroll region. The diagrams below illustrate
what will happen, depending on the scroll direction. "=" is used to
represent the SR(scroll region) boundaries and "-" the moved rectangles.
Note that dst and src share a common region.
If count is bigger than 0, move a rectangle in the SR up, this can
happen while scrolling down.
>
+-------------------------+
| (clipped above SR) | ^
|=========================| dst_top |
| dst (still in SR) | |
+-------------------------+ src_top |
| src (moved up) and dst | |
|-------------------------| dst_bot |
| src (cleared) | |
+=========================+ src_bot
<
If count is less than zero, move a rectangle in the SR down, this can
happen while scrolling up.
>
+=========================+ src_top
| src (cleared) | |
|------------------------ | dst_top |
| src (moved down) and dst| |
+-------------------------+ src_bot |
| dst (still in SR) | |
|=========================| dst_bot |
| (clipped below SR) | v
+-------------------------+
<
["set_title", title]
["set_icon", icon]
Set the window title, and icon (minimized) window title, respectively.
In windowing systems not distinguishing between the two, "set_icon"
can be ignored.
["mouse_on"]
["mouse_off"]
Tells the client whether mouse support, as determined by |'mouse'|
option, is considered to be active in the current mode. This is mostly
useful for a terminal frontend, or other situations where nvim mouse
would conflict with other usages of the mouse. It is safe for a client
to ignore this and always send mouse events.
["busy_on"]
["busy_off"]
Nvim started or stopped being busy, and possibly not responsible to user
input. This could be indicated to the user by hiding the cursor.
["suspend"]
|:suspend| command or |Ctrl-Z| mapping is used. A terminal client (or other
client where it makes sense) could suspend itself. Other clients can
safely ignore it.
["bell"]
["visual_bell"]
Notify the user with an audible or visual bell, respectively.
["update_menu"]
The menu mappings changed.
["mode_change", mode]
The mode changed. Currently sent when "insert", "replace" and "normal"
modes are entered. A client could for instance change the cursor shape.
["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
this update is sent. `items` is an array of the items to show, the
items are themselves arrays of the form [word, kind, menu, info]
as defined at |complete-items|, except that `word` is replaced by
`abbr` if present. `selected` is the initially selected item, either a
zero-based index into the array of items, or -1 if no item is
selected. `row` and `col` is the anchor position, where the first
character of the completed word will be.
["popupmenu_select", selected]
An item in the currently displayed popupmenu is selected. `selected`
is either a zero-based index into the array of items from the last
`popupmenu_show` event, or -1 if no item is selected.
["popupmenu_hide"]
The popupmenu is hidden.
==============================================================================
vim:tw=78:ts=8:noet:ft=help:norl:

View File

@ -8,8 +8,10 @@
#include "nvim/memory.h"
#include "nvim/map.h"
#include "nvim/msgpack_rpc/channel.h"
#include "nvim/api/ui.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/popupmnu.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/ui.c.generated.h"
@ -44,8 +46,8 @@ void remote_ui_disconnect(uint64_t channel_id)
xfree(ui);
}
void ui_attach(uint64_t channel_id, Integer width, Integer height,
Boolean enable_rgb, Error *err)
void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
Dictionary options, Error *err)
{
if (pmap_has(uint64_t)(connected_uis, channel_id)) {
api_set_error(err, Exception, _("UI already attached for channel"));
@ -57,14 +59,11 @@ void ui_attach(uint64_t channel_id, Integer width, Integer height,
_("Expected width > 0 and height > 0"));
return;
}
UIData *data = xmalloc(sizeof(UIData));
data->channel_id = channel_id;
data->buffer = (Array)ARRAY_DICT_INIT;
UI *ui = xcalloc(1, sizeof(UI));
ui->width = (int)width;
ui->height = (int)height;
ui->rgb = enable_rgb;
ui->data = data;
ui->rgb = true;
ui->pum_external = false;
ui->resize = remote_ui_resize;
ui->clear = remote_ui_clear;
ui->eol_clear = remote_ui_eol_clear;
@ -88,37 +87,108 @@ void 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->event = remote_ui_event;
for (size_t i = 0; i < options.size; i++) {
ui_set_option(ui, options.items[i].key, options.items[i].value, err);
if (err->set) {
xfree(ui);
return;
}
}
UIData *data = xmalloc(sizeof(UIData));
data->channel_id = channel_id;
data->buffer = (Array)ARRAY_DICT_INIT;
ui->data = data;
pmap_put(uint64_t)(connected_uis, channel_id, ui);
ui_attach_impl(ui);
return;
}
void ui_detach(uint64_t channel_id, Error *err)
/// @deprecated
void ui_attach(uint64_t channel_id, Integer width, Integer height,
Boolean enable_rgb, Error *err)
{
Dictionary opts = ARRAY_DICT_INIT;
PUT(opts, "rgb", BOOLEAN_OBJ(enable_rgb));
nvim_ui_attach(channel_id, width, height, opts, err);
api_free_dictionary(opts);
}
void nvim_ui_detach(uint64_t channel_id, Error *err)
{
if (!pmap_has(uint64_t)(connected_uis, channel_id)) {
api_set_error(err, Exception, _("UI is not attached for channel"));
return;
}
remote_ui_disconnect(channel_id);
}
Object ui_try_resize(uint64_t channel_id, Integer width,
Integer height, Error *err)
/// @deprecated
void ui_detach(uint64_t channel_id, Error *err)
{
nvim_ui_detach(channel_id, err);
}
void nvim_ui_try_resize(uint64_t channel_id, Integer width,
Integer height, Error *err)
{
if (!pmap_has(uint64_t)(connected_uis, channel_id)) {
api_set_error(err, Exception, _("UI is not attached for channel"));
return;
}
if (width <= 0 || height <= 0) {
api_set_error(err, Validation,
_("Expected width > 0 and height > 0"));
return NIL;
return;
}
UI *ui = pmap_get(uint64_t)(connected_uis, channel_id);
ui->width = (int)width;
ui->height = (int)height;
ui_refresh();
return NIL;
}
/// @deprecated
void ui_try_resize(uint64_t channel_id, Integer width,
Integer height, Error *err)
{
nvim_ui_try_resize(channel_id, width, height, err);
}
void nvim_ui_set_option(uint64_t channel_id, String name,
Object value, Error *error) {
if (!pmap_has(uint64_t)(connected_uis, channel_id)) {
api_set_error(error, Exception, _("UI is not attached for channel"));
return;
}
UI *ui = pmap_get(uint64_t)(connected_uis, channel_id);
ui_set_option(ui, name, value, error);
if (!error->set) {
ui_refresh();
}
}
static void ui_set_option(UI *ui, String name, Object value, Error *error) {
if (strcmp(name.data, "rgb") == 0) {
if (value.type != kObjectTypeBoolean) {
api_set_error(error, Validation, _("rgb must be a Boolean"));
return;
}
ui->rgb = value.data.boolean;
} else if (strcmp(name.data, "popupmenu_external") == 0) {
if (value.type != kObjectTypeBoolean) {
api_set_error(error, Validation,
_("popupmenu_external must be a Boolean"));
return;
}
ui->pum_external = value.data.boolean;
} else {
api_set_error(error, Validation, _("No such ui option"));
}
}
static void push_call(UI *ui, char *name, Array args)
@ -341,3 +411,19 @@ static void remote_ui_set_icon(UI *ui, char *icon)
ADD(args, STRING_OBJ(cstr_to_string(icon)));
push_call(ui, "set_icon", args);
}
static void remote_ui_event(UI *ui, char *name, Array args, bool *args_consumed)
{
Array my_args = ARRAY_DICT_INIT;
// Objects are currently single-reference
// make a copy, but only if necessary
if (*args_consumed) {
for (size_t i = 0; i < args.size; i++) {
ADD(my_args, copy_object(args.items[i]));
}
} else {
my_args = args;
*args_consumed = true;
}
push_call(ui, name, my_args);
}

View File

@ -2472,6 +2472,7 @@ void ins_compl_show_pum(void)
int cur = -1;
colnr_T col;
int lead_len = 0;
bool array_changed = false;
if (!pum_wanted() || !pum_enough_matches())
return;
@ -2483,7 +2484,8 @@ void ins_compl_show_pum(void)
update_screen(0);
if (compl_match_array == NULL) {
/* Need to build the popup menu list. */
array_changed = true;
// Need to build the popup menu list.
compl_match_arraysize = 0;
compl = compl_first_match;
/*
@ -2586,7 +2588,7 @@ void ins_compl_show_pum(void)
// Use the cursor to get all wrapping and other settings right.
col = curwin->w_cursor.col;
curwin->w_cursor.col = compl_col;
pum_display(compl_match_array, compl_match_arraysize, cur);
pum_display(compl_match_array, compl_match_arraysize, cur, array_changed);
curwin->w_cursor.col = col;
}

View File

@ -7,6 +7,7 @@
#include <stdbool.h>
#include "nvim/vim.h"
#include "nvim/api/private/helpers.h"
#include "nvim/ascii.h"
#include "nvim/popupmnu.h"
#include "nvim/charset.h"
@ -21,6 +22,7 @@
#include "nvim/memory.h"
#include "nvim/window.h"
#include "nvim/edit.h"
#include "nvim/ui.h"
static pumitem_T *pum_array = NULL; // items of displayed pum
static int pum_size; // nr of items in "pum_array"
@ -36,8 +38,10 @@ static int pum_scrollbar; // TRUE when scrollbar present
static int pum_row; // top row of pum
static int pum_col; // left column of pum
static int pum_do_redraw = FALSE; // do redraw anyway
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"
@ -53,7 +57,10 @@ static int pum_do_redraw = FALSE; // do redraw anyway
/// @param array
/// @param size
/// @param selected index of initially selected item, none if out of range
void pum_display(pumitem_T *array, int size, int selected)
/// @param array_changed if true, array contains different items since last call
/// if false, a new item is selected, but the array
/// is the same
void pum_display(pumitem_T *array, int size, int selected, bool array_changed)
{
int w;
int def_width;
@ -68,20 +75,55 @@ void pum_display(pumitem_T *array, int size, int selected)
int above_row = cmdline_row;
int redo_count = 0;
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;
}
redo:
// Mark the pum as visible already here,
// to avoid that must_redraw is set when 'cursorcolumn' is on.
pum_is_visible = true;
validate_cursor_col();
// anchor position: the start of the completed word
row = curwin->w_wrow + curwin->w_winrow;
if (curwin->w_p_rl) {
col = curwin->w_wincol + curwin->w_width - curwin->w_wcol - 1;
} else {
col = curwin->w_wincol + curwin->w_wcol;
}
if (pum_external) {
Array args = ARRAY_DICT_INIT;
if (array_changed) {
Array arr = ARRAY_DICT_INIT;
for (i = 0; i < size; i++) {
Array item = ARRAY_DICT_INIT;
ADD(item, STRING_OBJ(cstr_to_string((char *)array[i].pum_text)));
ADD(item, STRING_OBJ(cstr_to_string((char *)array[i].pum_kind)));
ADD(item, STRING_OBJ(cstr_to_string((char *)array[i].pum_extra)));
ADD(item, STRING_OBJ(cstr_to_string((char *)array[i].pum_info)));
ADD(arr, ARRAY_OBJ(item));
}
ADD(args, ARRAY_OBJ(arr));
ADD(args, INTEGER_OBJ(selected));
ADD(args, INTEGER_OBJ(row));
ADD(args, INTEGER_OBJ(col));
ui_event("popupmenu_show", args);
} else {
ADD(args, INTEGER_OBJ(selected));
ui_event("popupmenu_select", args);
}
return;
}
def_width = PUM_DEF_WIDTH;
max_width = 0;
kind_width = 0;
extra_width = 0;
// Pretend the pum is already there to avoid that must_redraw is set when
// 'cuc' is on.
pum_array = (pumitem_T *)1;
validate_cursor_col();
pum_array = NULL;
row = curwin->w_wrow + curwin->w_winrow;
if (firstwin->w_p_pvw) {
top_clear = firstwin->w_height;
} else {
@ -194,13 +236,6 @@ redo:
pum_base_width = max_width;
pum_kind_width = kind_width;
// Calculate column
if (curwin->w_p_rl) {
col = curwin->w_wincol + curwin->w_width - curwin->w_wcol - 1;
} else {
col = curwin->w_wincol + curwin->w_wcol;
}
// if there are more items than room we need a scrollbar
if (pum_height < size) {
pum_scrollbar = 1;
@ -641,9 +676,9 @@ static int pum_set_selected(int n, int repeat)
// Update the screen before drawing the popup menu.
// Enable updating the status lines.
pum_do_redraw = TRUE;
pum_is_visible = false;
update_screen(0);
pum_do_redraw = FALSE;
pum_is_visible = true;
if (!resized && win_valid(curwin_save)) {
no_u_sync++;
@ -653,9 +688,9 @@ static int pum_set_selected(int n, int repeat)
// May need to update the screen again when there are
// autocommands involved.
pum_do_redraw = TRUE;
pum_is_visible = false;
update_screen(0);
pum_do_redraw = FALSE;
pum_is_visible = true;
}
}
}
@ -672,10 +707,17 @@ static int pum_set_selected(int n, int repeat)
/// Undisplay the popup menu (later).
void pum_undisplay(void)
{
pum_is_visible = false;
pum_array = NULL;
redraw_all_later(SOME_VALID);
redraw_tabline = TRUE;
status_redraw_all();
if (pum_external) {
Array args = ARRAY_DICT_INIT;
ui_event("popupmenu_hide", args);
} else {
redraw_all_later(SOME_VALID);
redraw_tabline = true;
status_redraw_all();
}
}
/// Clear the popup menu. Currently only resets the offset to the first
@ -685,12 +727,16 @@ void pum_clear(void)
pum_first = 0;
}
/// Overruled when "pum_do_redraw" is set, used to redraw the status lines.
///
/// @return TRUE if the popup menu is displayed.
int pum_visible(void)
/// @return true if the popup menu is displayed.
bool pum_visible(void)
{
return !pum_do_redraw && pum_array != NULL;
return pum_is_visible;
}
/// @return true if the popup menu is displayed and drawn on the grid.
bool pum_drawn(void)
{
return pum_visible() && !pum_external;
}
/// Gets the height of the menu.
@ -701,3 +747,8 @@ int pum_get_height(void)
{
return pum_height;
}
void pum_set_external(bool external)
{
pum_wants_external = external;
}

View File

@ -420,9 +420,10 @@ void update_screen(int type)
}
}
end_search_hl();
/* May need to redraw the popup menu. */
if (pum_visible())
// May need to redraw the popup menu.
if (pum_drawn()) {
pum_redraw();
}
/* Reset b_mod_set flags. Going through all windows is probably faster
* than going through all buffers (there could be many buffers). */
@ -4827,15 +4828,12 @@ void win_redr_status(win_T *wp)
wp->w_redr_status = FALSE;
if (wp->w_status_height == 0) {
/* no status line, can only be last window */
redraw_cmdline = TRUE;
} else if (!redrawing()
/* don't update status line when popup menu is visible and may be
* drawn over it */
|| pum_visible()
) {
/* Don't redraw right now, do it later. */
wp->w_redr_status = TRUE;
// no status line, can only be last window
redraw_cmdline = true;
} else if (!redrawing() || pum_drawn()) {
// Don't redraw right now, do it later. Don't update status line when
// popup menu is visible and may be drawn over it
wp->w_redr_status = true;
} else if (*p_stl != NUL || *wp->w_p_stl != NUL) {
/* redraw custom status line */
redraw_custom_statusline(wp);
@ -7081,9 +7079,9 @@ void showruler(int always)
{
if (!always && !redrawing())
return;
if (pum_visible()) {
/* Don't redraw right now, do it later. */
curwin->w_redr_status = TRUE;
if (pum_drawn()) {
// Don't redraw right now, do it later.
curwin->w_redr_status = true;
return;
}
if ((*p_stl != NUL || *curwin->w_p_stl != NUL) && curwin->w_status_height) {
@ -7119,9 +7117,10 @@ static void win_redr_ruler(win_T *wp, int always)
if (wp == lastwin && lastwin->w_status_height == 0)
if (edit_submode != NULL)
return;
/* Don't draw the ruler when the popup menu is visible, it may overlap. */
if (pum_visible())
// Don't draw the ruler when the popup menu is visible, it may overlap.
if (pum_drawn()) {
return;
}
if (*p_ruf) {
int save_called_emsg = called_emsg;
@ -7371,7 +7370,7 @@ void screen_resize(int width, int height)
redrawcmdline();
} else {
update_topline();
if (pum_visible()) {
if (pum_drawn()) {
redraw_later(NOT_VALID);
ins_compl_show_pum(); /* This includes the redraw. */
} else

View File

@ -83,6 +83,7 @@ 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;
@ -106,6 +107,7 @@ UI *tui_start(void)
ui->suspend = tui_suspend;
ui->set_title = tui_set_title;
ui->set_icon = tui_set_icon;
ui->event = tui_event;
return ui_bridge_attach(ui, tui_main, tui_scheduler);
}
@ -650,6 +652,12 @@ static void tui_set_icon(UI *ui, char *icon)
{
}
// 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)
{
}
static void invalidate(UI *ui, int top, int bot, int left, int right)
{
TUIData *data = ui->data;

View File

@ -27,6 +27,7 @@
#include "nvim/os/time.h"
#include "nvim/os/input.h"
#include "nvim/os/signal.h"
#include "nvim/popupmnu.h"
#include "nvim/screen.h"
#include "nvim/syntax.h"
#include "nvim/window.h"
@ -35,6 +36,7 @@
#else
# include "nvim/msgpack_rpc/server.h"
#endif
#include "nvim/api/private/helpers.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ui.c.generated.h"
@ -143,6 +145,15 @@ void ui_set_icon(char *icon)
UI_CALL(flush);
}
void ui_event(char *name, Array args)
{
bool args_consumed = false;
UI_CALL(event, name, args, &args_consumed);
if (!args_consumed) {
api_free_array(args);
}
}
// May update the shape of the cursor.
void ui_cursor_shape(void)
{
@ -156,15 +167,18 @@ void ui_refresh(void)
}
int width = INT_MAX, height = INT_MAX;
bool pum_external = true;
for (size_t i = 0; i < ui_count; i++) {
UI *ui = uis[i];
width = ui->width < width ? ui->width : width;
height = ui->height < height ? ui->height : height;
pum_external &= ui->pum_external;
}
row = col = 0;
screen_resize(width, height);
pum_set_external(pum_external);
}
void ui_resize(int new_width, int new_height)
@ -519,3 +533,4 @@ static void ui_mode_change(void)
UI_CALL(mode_change, mode);
conceal_check_cursur_line();
}

View File

@ -5,6 +5,8 @@
#include <stdbool.h>
#include <stdint.h>
#include "api/private/defs.h"
typedef struct {
bool bold, underline, undercurl, italic, reverse;
int foreground, background, special;
@ -13,7 +15,7 @@ typedef struct {
typedef struct ui_t UI;
struct ui_t {
bool rgb;
bool rgb, pum_external;
int width, height;
void *data;
void (*resize)(UI *ui, int rows, int columns);
@ -39,6 +41,7 @@ struct ui_t {
void (*suspend)(UI *ui);
void (*set_title)(UI *ui, char *title);
void (*set_icon)(UI *ui, char *icon);
void (*event)(UI *ui, char *name, Array args, bool *args_consumed);
void (*stop)(UI *ui);
};

View File

@ -31,6 +31,7 @@ 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;

View File

@ -12,7 +12,7 @@ describe('TermClose event', function()
nvim('set_option', 'shell', nvim_dir .. '/shell-test')
nvim('set_option', 'shellcmdflag', 'EXE')
screen = Screen.new(20, 4)
screen:attach(false)
screen:attach({rgb=false})
end)
it('works as expected', function()

View File

@ -306,6 +306,10 @@ local function nvim(method, ...)
return request('vim_'..method, ...)
end
local function ui(method, ...)
return request('nvim_ui_'..method, ...)
end
local function nvim_async(method, ...)
session:notify('vim_'..method, ...)
end
@ -432,6 +436,7 @@ end
local funcs = create_callindex(nvim_call)
local meths = create_callindex(nvim)
local uimeths = create_callindex(ui)
local bufmeths = create_callindex(buffer)
local winmeths = create_callindex(window)
local tabmeths = create_callindex(tabpage)
@ -490,6 +495,7 @@ return function(after_each)
bufmeths = bufmeths,
winmeths = winmeths,
tabmeths = tabmeths,
uimeths = uimeths,
curbufmeths = curbufmeths,
curwinmeths = curwinmeths,
curtabmeths = curtabmeths,

View File

@ -135,7 +135,7 @@ describe('cursor with customized highlighting', function()
[2] = {foreground = 55, background = 56},
[3] = {bold = true},
})
screen:attach(false)
screen:attach({rgb=false})
execute('call termopen(["'..nvim_dir..'/tty-test"]) | startinsert')
end)

View File

@ -12,7 +12,7 @@ local eq = helpers.eq
describe(':edit term://*', function()
local get_screen = function(columns, lines)
local scr = screen.new(columns, lines)
scr:attach(false)
scr:attach({rgb=false})
return scr
end

View File

@ -10,7 +10,7 @@ describe(':terminal', function()
before_each(function()
clear()
screen = Screen.new(50, 4)
screen:attach(false)
screen:attach({rgb=false})
nvim('set_option', 'shell', nvim_dir..'/shell-test')
nvim('set_option', 'shellcmdflag', 'EXE')

View File

@ -53,7 +53,7 @@ local function screen_setup(extra_height, command)
[9] = {foreground = 4},
})
screen:attach(false)
screen:attach({rgb=false})
-- tty-test puts the terminal into raw mode and echoes all input. tests are
-- done by feeding it with terminfo codes to control the display and
-- verifying output with screen:expect.

View File

@ -25,7 +25,7 @@ describe('terminal window highlighting', function()
[10] = {reverse = true},
[11] = {background = 11},
})
screen:attach(false)
screen:attach({rgb=false})
execute('enew | call termopen(["'..nvim_dir..'/tty-test"]) | startinsert')
screen:expect([[
tty ready |
@ -127,7 +127,7 @@ describe('terminal window highlighting with custom palette', function()
[8] = {background = 11},
[9] = {bold = true},
})
screen:attach(true)
screen:attach({rgb=true})
nvim('set_var', 'terminal_color_3', '#123456')
execute('enew | call termopen(["'..nvim_dir..'/tty-test"]) | startinsert')
screen:expect([[
@ -185,7 +185,7 @@ describe('synIDattr()', function()
end)
it('returns gui-color if RGB-capable UI is attached', function()
screen:attach(true)
screen:attach({rgb=true})
eq('#ff0000', eval('synIDattr(hlID("Normal"), "fg")'))
eq('Black', eval('synIDattr(hlID("Normal"), "bg")'))
eq('Salmon', eval('synIDattr(hlID("Keyword"), "fg")'))
@ -193,7 +193,7 @@ describe('synIDattr()', function()
end)
it('returns #RRGGBB value for fg#/bg#/sp#', function()
screen:attach(true)
screen:attach({rgb=true})
eq('#ff0000', eval('synIDattr(hlID("Normal"), "fg#")'))
eq('#000000', eval('synIDattr(hlID("Normal"), "bg#")'))
eq('#fa8072', eval('synIDattr(hlID("Keyword"), "fg#")'))
@ -201,7 +201,7 @@ describe('synIDattr()', function()
end)
it('returns color number if non-GUI', function()
screen:attach(false)
screen:attach({rgb=false})
eq('252', eval('synIDattr(hlID("Normal"), "fg")'))
eq('79', eval('synIDattr(hlID("Keyword"), "fg")'))
end)

View File

@ -331,7 +331,7 @@ describe('terminal prints more lines than the screen height and exits', function
it('will push extra lines to scrollback', function()
clear()
local screen = Screen.new(50, 7)
screen:attach(false)
screen:attach({rgb=false})
execute('call termopen(["'..nvim_dir..'/tty-test", "10"]) | startinsert')
wait()
screen:expect([[

View File

@ -106,7 +106,7 @@
-- use `screen:snapshot_util({},true)`
local helpers = require('test.functional.helpers')(nil)
local request, run = helpers.request, helpers.run
local request, run, uimeths = helpers.request, helpers.run, helpers.uimeths
local dedent = helpers.dedent
local Screen = {}
@ -192,22 +192,22 @@ function Screen:set_default_attr_ignore(attr_ignore)
self._default_attr_ignore = attr_ignore
end
function Screen:attach(rgb)
if rgb == nil then
rgb = true
function Screen:attach(options)
if options == nil then
options = {rgb=true}
end
request('ui_attach', self._width, self._height, rgb)
uimeths.attach(self._width, self._height, options)
end
function Screen:detach()
request('ui_detach')
uimeths.detach()
end
function Screen:try_resize(columns, rows)
request('ui_try_resize', columns, rows)
uimeths.try_resize(columns, rows)
end
function Screen:expect(expected, attr_ids, attr_ignore)
function Screen:expect(expected, attr_ids, attr_ignore, condition)
-- remove the last line and dedent
expected = dedent(expected:gsub('\n[ ]+$', ''))
local expected_rows = {}
@ -219,6 +219,12 @@ function Screen:expect(expected, attr_ids, attr_ignore)
local ids = attr_ids or self._default_attr_ids
local ignore = attr_ignore or self._default_attr_ignore
self:wait(function()
if condition ~= nil then
local status, res = pcall(condition)
if not status then
return tostring(res)
end
end
local actual_rows = {}
for i = 1, self._height do
actual_rows[i] = self:_row_repr(self._rows[i], ids, ignore)
@ -303,12 +309,20 @@ function Screen:_redraw(updates)
local method = update[1]
for i = 2, #update do
local handler = self['_handle_'..method]
handler(self, unpack(update[i]))
if handler ~= nil then
handler(self, unpack(update[i]))
else
self._on_event(method, update[i])
end
end
-- print(self:_current_screen())
end
end
function Screen:set_on_event_handler(callback)
self._on_event = callback
end
function Screen:_handle_resize(width, height)
local rows = {}
for _ = 1, height do

View File

@ -755,4 +755,106 @@ describe('completion', function()
]])
end)
end)
end)
describe('External completion 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:set_default_attr_ids({
[1] = {bold=true, foreground=Screen.colors.Blue},
[2] = {bold = true},
})
screen:set_on_event_handler(function(name, data)
if name == "popupmenu_show" then
local row, col
items, selected, row, col = unpack(data)
anchor = {row, col}
elseif name == "popupmenu_select" then
selected = data[1]
elseif name == "popupmenu_hide" then
items = nil
end
end)
end)
it('works', function()
source([[
function! TestComplete() abort
call complete(1, ['foo', 'bar', 'spam'])
return ''
endfunction
]])
local expected = {
{'foo', '', '', ''},
{'bar', '', '', ''},
{'spam', '', '', ''},
}
feed('o<C-r>=TestComplete()<CR>')
screen:expect([[
|
foo^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2:-- INSERT --} |
]], nil, nil, function()
eq(expected, items)
eq(0, selected)
eq({1,0}, anchor)
end)
feed('<c-p>')
screen:expect([[
|
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2:-- INSERT --} |
]], nil, nil, function()
eq(expected, items)
eq(-1, selected)
eq({1,0}, anchor)
end)
-- down moves the selection in the menu, but does not insert anything
feed('<down><down>')
screen:expect([[
|
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2:-- INSERT --} |
]], nil, nil, function()
eq(expected, items)
eq(1, selected)
eq({1,0}, anchor)
end)
feed('<cr>')
screen:expect([[
|
bar^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2:-- INSERT --} |
]], nil, nil, function()
eq(nil, items) -- popupmenu was hidden
end)
end)
end)