Merge PR #1810 'abstract_ui fixes and improvements(continuation)'

This commit is contained in:
Thiago de Arruda 2015-01-15 09:19:27 -03:00
commit 5f24549ab1
18 changed files with 714 additions and 189 deletions

View File

@ -76,6 +76,7 @@
#include "nvim/tag.h"
#include "nvim/tempfile.h"
#include "nvim/term.h"
#include "nvim/ui.h"
#include "nvim/mouse.h"
#include "nvim/undo.h"
#include "nvim/version.h"
@ -10023,6 +10024,8 @@ static void f_has(typval_T *argvars, typval_T *rettv)
#endif
} else if (STRICMP(name, "syntax_items") == 0) {
n = syntax_present(curwin);
} else if (STRICMP(name, "gui_running") == 0) {
n = ui_rgb_attached();
}
}
@ -14440,7 +14443,7 @@ static void f_synIDattr(typval_T *argvars, typval_T *rettv)
if (modec != 't' && modec != 'c' && modec != 'g')
modec = 0; /* replace invalid with current */
} else {
if (t_colors > 1)
if (abstract_ui || t_colors > 1)
modec = 'c';
else
modec = 't';

View File

@ -261,10 +261,7 @@ int main(int argc, char **argv)
if (params.want_full_screen && !silent_mode) {
if (embedded_mode) {
// In embedded mode don't do terminal-related initializations, assume an
// initial screen size of 80x20
full_screen = true;
screen_resize(80, 20, false);
// embedded mode implies abstract_ui
termcapinit((uint8_t *)"abstract_ui");
} else {
// set terminal name and get terminal capabilities (will set full_screen)
@ -278,7 +275,9 @@ int main(int argc, char **argv)
event_init();
if (abstract_ui) {
full_screen = true;
t_colors = 256;
T_CCO = (uint8_t *)"256";
} else {
// Print a warning if stdout is not a terminal TODO(tarruda): Remove this
// check once the new terminal UI is implemented

View File

@ -45,6 +45,7 @@ typedef struct {
typedef struct {
uint64_t id;
size_t pending_requests;
PMap(cstr_t) *subscribed_events;
bool is_job, closed;
msgpack_unpacker *unpacker;
@ -83,7 +84,6 @@ static uint64_t next_id = 1;
static PMap(uint64_t) *channels = NULL;
static PMap(cstr_t) *event_strings = NULL;
static msgpack_sbuffer out_buffer;
static size_t pending_requests = 0;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "msgpack_rpc/channel.c.generated.h"
@ -103,14 +103,7 @@ void channel_init(void)
}
if (abstract_ui) {
// Add handler for "attach_ui"
remote_ui_init();
String method = cstr_as_string("attach_ui");
MsgpackRpcRequestHandler handler = {.fn = remote_ui_attach, .defer = true};
msgpack_rpc_add_method_handler(method, handler);
method = cstr_as_string("detach_ui");
handler.fn = remote_ui_detach;
msgpack_rpc_add_method_handler(method, handler);
}
}
@ -200,7 +193,8 @@ bool channel_send_event(uint64_t id, char *name, Array args)
return false;
}
if (pending_requests) {
if (channel) {
if (channel->pending_requests) {
DelayedNotification p = {
.channel = channel,
.method = cstr_to_string(name),
@ -209,11 +203,11 @@ bool channel_send_event(uint64_t id, char *name, Array args)
// Pending request, queue the notification for sending later
*kl_pushp(DelayedNotification, delayed_notifications) = p;
} else {
if (channel) {
send_event(channel, name, args);
} else {
broadcast_event(name, args);
}
} else {
// TODO(tarruda): Implement event broadcasting in vimscript
broadcast_event(name, args);
}
return true;
@ -246,10 +240,10 @@ Object channel_send_call(uint64_t id,
// Push the frame
ChannelCallFrame frame = {request_id, false, false, NIL};
kv_push(ChannelCallFrame *, channel->call_stack, &frame);
pending_requests++;
channel->pending_requests++;
event_poll_until(-1, frame.returned);
(void)kv_pop(channel->call_stack);
pending_requests--;
channel->pending_requests--;
if (frame.errored) {
api_set_error(err, Exception, "%s", frame.result.data.string.data);
@ -261,7 +255,7 @@ Object channel_send_call(uint64_t id,
free_channel(channel);
}
if (!pending_requests) {
if (!channel->pending_requests) {
send_delayed_notifications();
}
@ -644,6 +638,7 @@ static void close_channel(Channel *channel)
uv_handle_t *handle = (uv_handle_t *)channel->data.streams.uv;
if (handle) {
uv_close(handle, close_cb);
free_channel(channel);
} else {
event_push((Event) { .handler = on_stdio_close }, false);
}
@ -687,6 +682,7 @@ static Channel *register_channel(void)
rv->closed = false;
rv->unpacker = msgpack_unpacker_new(MSGPACK_UNPACKER_INIT_BUFFER_SIZE);
rv->id = next_id++;
rv->pending_requests = 0;
rv->subscribed_events = pmap_new(cstr_t)();
rv->next_request_id = 1;
kv_init(rv->call_stack);

View File

@ -26,18 +26,44 @@ static PMap(uint64_t) *connected_uis = NULL;
void remote_ui_init(void)
{
connected_uis = pmap_new(uint64_t)();
// Add handler for "attach_ui"
String method = cstr_as_string("ui_attach");
MsgpackRpcRequestHandler handler = {.fn = remote_ui_attach, .defer = false};
msgpack_rpc_add_method_handler(method, handler);
method = cstr_as_string("ui_detach");
handler.fn = remote_ui_detach;
msgpack_rpc_add_method_handler(method, handler);
method = cstr_as_string("ui_try_resize");
handler.fn = remote_ui_try_resize;
msgpack_rpc_add_method_handler(method, handler);
}
Object remote_ui_attach(uint64_t channel_id, uint64_t request_id, Array args,
Error *error)
void remote_ui_disconnect(uint64_t channel_id)
{
UI *ui = pmap_get(uint64_t)(connected_uis, channel_id);
if (!ui) {
return;
}
UIData *data = ui->data;
// destroy pending screen updates
api_free_array(data->buffer);
pmap_del(uint64_t)(connected_uis, channel_id);
free(ui->data);
ui_detach(ui);
free(ui);
}
static Object remote_ui_attach(uint64_t channel_id, uint64_t request_id,
Array args, Error *error)
{
if (pmap_has(uint64_t)(connected_uis, channel_id)) {
api_set_error(error, Exception, _("UI already attached for channel"));
return NIL;
}
if (args.size != 2 || args.items[0].type != kObjectTypeInteger
if (args.size != 3 || args.items[0].type != kObjectTypeInteger
|| args.items[1].type != kObjectTypeInteger
|| args.items[2].type != kObjectTypeBoolean
|| args.items[0].data.integer <= 0 || args.items[1].data.integer <= 0) {
api_set_error(error, Validation,
_("Arguments must be a pair of positive integers "
@ -50,6 +76,7 @@ Object remote_ui_attach(uint64_t channel_id, uint64_t request_id, Array args,
UI *ui = xcalloc(1, sizeof(UI));
ui->width = (int)args.items[0].data.integer;
ui->height = (int)args.items[1].data.integer;
ui->rgb = args.items[2].data.boolean;
ui->data = data;
ui->resize = remote_ui_resize;
ui->clear = remote_ui_clear;
@ -67,16 +94,19 @@ Object remote_ui_attach(uint64_t channel_id, uint64_t request_id, Array args,
ui->put = remote_ui_put;
ui->bell = remote_ui_bell;
ui->visual_bell = remote_ui_visual_bell;
ui->update_fg = remote_ui_update_fg;
ui->update_bg = remote_ui_update_bg;
ui->flush = remote_ui_flush;
ui->suspend = remote_ui_suspend;
ui->set_title = remote_ui_set_title;
ui->set_icon = remote_ui_set_icon;
pmap_put(uint64_t)(connected_uis, channel_id, ui);
ui_attach(ui);
return NIL;
}
Object remote_ui_detach(uint64_t channel_id, uint64_t request_id, Array args,
Error *error)
static Object remote_ui_detach(uint64_t channel_id, uint64_t request_id,
Array args, Error *error)
{
if (!pmap_has(uint64_t)(connected_uis, channel_id)) {
api_set_error(error, Exception, _("UI is not attached for channel"));
@ -86,21 +116,30 @@ Object remote_ui_detach(uint64_t channel_id, uint64_t request_id, Array args,
return NIL;
}
void remote_ui_disconnect(uint64_t channel_id)
static Object remote_ui_try_resize(uint64_t channel_id, uint64_t request_id,
Array args, Error *error)
{
UI *ui = pmap_get(uint64_t)(connected_uis, channel_id);
if (!ui) {
return;
if (!pmap_has(uint64_t)(connected_uis, channel_id)) {
api_set_error(error, Exception, _("UI is not attached for channel"));
}
UIData *data = ui->data;
// destroy pending screen updates
api_free_array(data->buffer);
pmap_del(uint64_t)(connected_uis, channel_id);
free(ui->data);
ui_detach(ui);
free(ui);
if (args.size != 2 || args.items[0].type != kObjectTypeInteger
|| args.items[1].type != kObjectTypeInteger
|| args.items[0].data.integer <= 0 || args.items[1].data.integer <= 0) {
api_set_error(error, Validation,
_("Arguments must be a pair of positive integers "
"representing the remote screen width/height"));
return NIL;
}
UI *ui = pmap_get(uint64_t)(connected_uis, channel_id);
ui->width = (int)args.items[0].data.integer;
ui->height = (int)args.items[1].data.integer;
ui_refresh();
return NIL;
}
static void push_call(UI *ui, char *name, Array args)
{
Array call = ARRAY_DICT_INIT;
@ -214,10 +253,6 @@ static void remote_ui_highlight_set(UI *ui, HlAttrs attrs)
PUT(hl, "bold", BOOLEAN_OBJ(true));
}
if (attrs.standout) {
PUT(hl, "standout", BOOLEAN_OBJ(true));
}
if (attrs.underline) {
PUT(hl, "underline", BOOLEAN_OBJ(true));
}
@ -266,6 +301,20 @@ static void remote_ui_visual_bell(UI *ui)
push_call(ui, "visual_bell", args);
}
static void remote_ui_update_fg(UI *ui, int fg)
{
Array args = ARRAY_DICT_INIT;
ADD(args, INTEGER_OBJ(fg));
push_call(ui, "update_fg", args);
}
static void remote_ui_update_bg(UI *ui, int bg)
{
Array args = ARRAY_DICT_INIT;
ADD(args, INTEGER_OBJ(bg));
push_call(ui, "update_bg", args);
}
static void remote_ui_flush(UI *ui)
{
UIData *data = ui->data;
@ -275,6 +324,20 @@ static void remote_ui_flush(UI *ui)
static void remote_ui_suspend(UI *ui)
{
UIData *data = ui->data;
remote_ui_disconnect(data->channel_id);
Array args = ARRAY_DICT_INIT;
push_call(ui, "suspend", args);
}
static void remote_ui_set_title(UI *ui, char *title)
{
Array args = ARRAY_DICT_INIT;
ADD(args, STRING_OBJ(cstr_to_string(title)));
push_call(ui, "set_title", args);
}
static void remote_ui_set_icon(UI *ui, char *icon)
{
Array args = ARRAY_DICT_INIT;
ADD(args, STRING_OBJ(cstr_to_string(icon)));
push_call(ui, "set_icon", args);
}

View File

@ -187,14 +187,20 @@ size_t input_enqueue(String keys)
unsigned int new_size = trans_special((uint8_t **)&ptr, buf, false);
if (!new_size) {
if (*ptr == '<') {
// Invalid key sequence, skip until the next '>' or until *end
do {
ptr++;
} while (ptr < end && *ptr != '>');
ptr++;
continue;
}
// copy the character unmodified
*buf = (uint8_t)*ptr++;
new_size = 1;
}
new_size = handle_mouse_event(&ptr, buf, new_size);
// TODO(tarruda): Don't produce past unclosed '<' characters, except if
// there's a lot of characters after the '<'
rbuffer_write(input_buffer, (char *)buf, new_size);
}
@ -217,7 +223,8 @@ static unsigned int handle_mouse_event(char **ptr, uint8_t *buf,
mouse_code = buf[5];
}
if (mouse_code < KE_LEFTMOUSE || mouse_code > KE_RIGHTRELEASE) {
if (!((mouse_code >= KE_LEFTMOUSE && mouse_code <= KE_RIGHTRELEASE)
|| (mouse_code >= KE_MOUSEDOWN && mouse_code <= KE_MOUSERIGHT))) {
return bufsize;
}
@ -226,7 +233,7 @@ static unsigned int handle_mouse_event(char **ptr, uint8_t *buf,
// find mouse coordinates, and it would be too expensive to refactor this
// now.
int col, row, advance;
if (sscanf(*ptr, "<%d,%d>%n", &col, &row, &advance)) {
if (sscanf(*ptr, "<%d,%d>%n", &col, &row, &advance) != EOF && advance) {
if (col >= 0 && row >= 0) {
mouse_row = row;
mouse_col = col;

View File

@ -24,7 +24,6 @@
#include "nvim/option_defs.h"
#include "nvim/charset.h"
#include "nvim/strings.h"
#include "nvim/ui.h"
#define DYNAMIC_BUFFER_INIT {NULL, 0, 0}
@ -414,6 +413,7 @@ static size_t write_output(char *output, size_t remaining, bool to_buffer,
char *start = output;
size_t off = 0;
int lastrow = (int)Rows - 1;
while (off < remaining) {
if (output[off] == NL) {
// Insert the line
@ -421,10 +421,8 @@ static size_t write_output(char *output, size_t remaining, bool to_buffer,
if (to_buffer) {
ml_append(curwin->w_cursor.lnum++, (char_u *)output, 0, false);
} else {
// pending data from the output buffer has been flushed to the screen,
// safe to call ui_write directly
ui_write((char_u *)output, (int)off);
ui_write((char_u *)"\r\n", 2);
screen_del_lines(0, 0, 1, (int)Rows, true, NULL);
screen_puts_len((char_u *)output, (int)off, lastrow, 0, 0);
}
size_t skip = off + 1;
output += skip;
@ -448,8 +446,8 @@ static size_t write_output(char *output, size_t remaining, bool to_buffer,
// remember that the NL was missing
curbuf->b_no_eol_lnum = curwin->w_cursor.lnum;
} else {
ui_write((char_u *)output, (int)remaining);
ui_write((char_u *)"\r\n", 2);
screen_del_lines(0, 0, 1, (int)Rows, true, NULL);
screen_puts_len((char_u *)output, (int)remaining, lastrow, 0, 0);
}
output += remaining;
} else if (to_buffer) {

View File

@ -43,6 +43,7 @@
#include "nvim/syntax.h"
#include "nvim/tempfile.h"
#include "nvim/term.h"
#include "nvim/ui.h"
#include "nvim/types.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
@ -181,22 +182,26 @@ void mch_settitle(char_u *title, char_u *icon)
* Note: if "t_ts" is set, title is set with escape sequence rather
* than x11 calls, because the x11 calls don't always work
*/
if ((type || *T_TS != NUL) && title != NULL) {
if ((type || *T_TS != NUL || abstract_ui) && title != NULL) {
if (oldtitle == NULL
) /* first call but not in GUI, save title */
(void)get_x11_title(FALSE);
if (*T_TS != NUL) /* it's OK if t_fs is empty */
if (abstract_ui) {
ui_set_title((char *)title);
} else if (*T_TS != NUL) /* it's OK if t_fs is empty */
term_settitle(title);
did_set_title = TRUE;
}
if ((type || *T_CIS != NUL) && icon != NULL) {
if ((type || *T_CIS != NUL || abstract_ui) && icon != NULL) {
if (oldicon == NULL
) /* first call, save icon */
get_x11_icon(FALSE);
if (*T_CIS != NUL) {
if (abstract_ui) {
ui_set_icon((char *)icon);
} else if (*T_CIS != NUL) {
out_str(T_CIS); /* set icon start */
out_str_nf(icon);
out_str(T_CIE); /* set icon end */

View File

@ -5971,7 +5971,7 @@ void screen_stop_highlight(void)
*/
void reset_cterm_colors(void)
{
if (t_colors > 1) {
if (!abstract_ui && t_colors > 1) {
/* set Normal cterm colors */
if (cterm_normal_fg_color > 0 || cterm_normal_bg_color > 0) {
out_str(T_OP);
@ -6150,8 +6150,7 @@ void screen_fill(int start_row, int end_row, int start_col, int end_col, int c1,
return;
/* it's a "normal" terminal when not in a GUI or cterm */
norm_term = (
t_colors <= 1);
norm_term = (!abstract_ui && t_colors <= 1);
for (row = start_row; row < end_row; ++row) {
if (has_mbyte
) {
@ -6675,8 +6674,8 @@ static void linecopy(int to, int from, win_T *wp)
*/
int can_clear(char_u *p)
{
return *p != NUL && (t_colors <= 1
|| cterm_normal_bg_color == 0 || *T_UT != NUL);
return abstract_ui || (*p != NUL && (t_colors <= 1
|| cterm_normal_bg_color == 0 || *T_UT != NUL));
}
/*
@ -8186,8 +8185,13 @@ void screen_resize(int width, int height, int mustset)
Columns = width;
}
check_shellsize();
height = Rows;
width = Columns;
if (abstract_ui) {
// Clear the output buffer to ensure UIs don't receive redraw command meant
// for invalid screen sizes.
out_buf_clear();
ui_resize(width, height);
} else {
mch_set_shellsize();

View File

@ -45,6 +45,7 @@
#include "nvim/strings.h"
#include "nvim/syntax_defs.h"
#include "nvim/term.h"
#include "nvim/ui.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
@ -6036,6 +6037,7 @@ int load_colors(char_u *name)
apply_autocmds(EVENT_COLORSCHEME, name, curbuf->b_fname, FALSE, curbuf);
recursive = FALSE;
ui_refresh();
return retval;
}
@ -6438,8 +6440,7 @@ do_highlight (
HL_TABLE()[idx].sg_cterm &= ~HL_BOLD;
}
color &= 7; /* truncate to 8 colors */
} else if (t_colors == 16 || t_colors == 88
|| t_colors == 256) {
} else if (t_colors == 16 || t_colors == 88 || t_colors == 256) {
/*
* Guess: if the termcap entry ends in 'm', it is
* probably an xterm-like terminal. Use the changed
@ -6449,7 +6450,7 @@ do_highlight (
p = T_CAF;
else
p = T_CSF;
if (*p != NUL && *(p + STRLEN(p) - 1) == 'm')
if (abstract_ui || (*p != NUL && *(p + STRLEN(p) - 1) == 'm'))
switch (t_colors) {
case 16:
color = color_numbers_8[i];
@ -6632,6 +6633,10 @@ do_highlight (
if (is_normal_group) {
HL_TABLE()[idx].sg_term_attr = 0;
HL_TABLE()[idx].sg_cterm_attr = 0;
if (abstract_ui) {
// If the normal group has changed, it is simpler to refresh every UI
ui_refresh();
}
} else
set_hl_attr(idx);
HL_TABLE()[idx].sg_scriptID = current_SID;
@ -6860,7 +6865,7 @@ int hl_combine_attr(int char_attr, int prim_attr)
if (char_attr <= HL_ALL && prim_attr <= HL_ALL)
return char_attr | prim_attr;
if (t_colors > 1) {
if (abstract_ui || t_colors > 1) {
if (char_attr > HL_ALL)
char_aep = syn_cterm_attr2entry(char_attr);
if (char_aep != NULL)
@ -6924,7 +6929,7 @@ int syn_attr2attr(int attr)
{
attrentry_T *aep;
if (t_colors > 1)
if (abstract_ui || t_colors > 1)
aep = syn_cterm_attr2entry(attr);
else
aep = syn_term_attr2entry(attr);
@ -7194,9 +7199,10 @@ set_hl_attr (
* For the color term mode: If there are other than "normal"
* highlighting attributes, need to allocate an attr number.
*/
if (sgp->sg_cterm_fg == 0 && sgp->sg_cterm_bg == 0)
if (sgp->sg_cterm_fg == 0 && sgp->sg_cterm_bg == 0
&& sgp->sg_rgb_fg == -1 && sgp->sg_rgb_bg == -1) {
sgp->sg_cterm_attr = sgp->sg_cterm;
else {
} else {
at_en.ae_attr = abstract_ui ? sgp->sg_gui : sgp->sg_cterm;
at_en.ae_u.cterm.fg_color = sgp->sg_cterm_fg;
at_en.ae_u.cterm.bg_color = sgp->sg_cterm_bg;
@ -7351,7 +7357,7 @@ int syn_id2attr(int hl_id)
hl_id = syn_get_final_id(hl_id);
sgp = &HL_TABLE()[hl_id - 1]; /* index is ID minus one */
if (t_colors > 1)
if (abstract_ui || t_colors > 1)
attr = sgp->sg_cterm_attr;
else
attr = sgp->sg_term_attr;

View File

@ -170,6 +170,7 @@ static struct builtin_term builtin_termcaps[] =
{(int)KS_DL, "\033|d"},
{(int)KS_CDL, "\033|%p1%dD"},
{(int)KS_CS, "\033|%p1%d;%p2%dR"},
{(int)KS_CSV, "\033|%p1%d;%p2%dV"},
{(int)KS_CL, "\033|C"},
// attributes switched on with 'h', off with * 'H'
{(int)KS_ME, "\033|31H"}, // HL_ALL
@ -1816,17 +1817,20 @@ void term_write(char_u *s, size_t len)
static char_u out_buf[OUT_SIZE + 1];
static int out_pos = 0; /* number of chars in out_buf */
// Clear the output buffer
void out_buf_clear(void)
{
out_pos = 0;
}
/*
* out_flush(): flush the output buffer
*/
void out_flush(void)
{
if (out_pos != 0) {
/* set out_pos to 0 before ui_write, to avoid recursiveness */
int len = out_pos;
out_pos = 0;
ui_write(out_buf, len);
}
}
/*
@ -2202,7 +2206,11 @@ void win_new_shellsize(void)
*/
void shell_resized(void)
{
if (abstract_ui) {
ui_refresh();
} else {
screen_resize(0, 0, FALSE);
}
}
/*

View File

@ -60,11 +60,8 @@ static struct {
int top, bot, left, right;
} sr;
static int current_highlight_mask = 0;
static HlAttrs current_attrs = {
false, false, false, false, false, false, -1, -1
};
static bool cursor_enabled = true;
static int height = INT_MAX, width = INT_MAX;
static int height, width;
// This set of macros allow us to use UI_CALL to invoke any function on
// registered UI instances. The functions can have 0-5 arguments(configurable
@ -98,6 +95,10 @@ void ui_write(uint8_t *s, int len)
return;
}
if (!len) {
return;
}
char_u *tofree = NULL;
if (output_conv.vc_type != CONV_NONE) {
@ -113,6 +114,16 @@ void ui_write(uint8_t *s, int len)
free(tofree);
}
bool ui_rgb_attached(void)
{
for (size_t i = 0; i < ui_count; i++) {
if (uis[i]->rgb) {
return true;
}
}
return false;
}
/*
* If the machine has job control, use it to suspend the program,
* otherwise fake it by starting a new shell.
@ -122,11 +133,24 @@ void ui_suspend(void)
{
if (abstract_ui) {
UI_CALL(suspend);
UI_CALL(flush);
} else {
mch_suspend();
}
}
void ui_set_title(char *title)
{
UI_CALL(set_title, title);
UI_CALL(flush);
}
void ui_set_icon(char *icon)
{
UI_CALL(set_icon, icon);
UI_CALL(flush);
}
/*
* Try to get the current Vim shell size. Put the result in Rows and Columns.
* Use the new sizes as defaults for 'columns' and 'lines'.
@ -165,8 +189,31 @@ void ui_cursor_shape(void)
}
}
void ui_resize(int width, int height)
void ui_refresh(void)
{
if (!ui_count) {
return;
}
int width = INT_MAX, height = INT_MAX;
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;
}
screen_resize(width, height, true);
}
void ui_resize(int new_width, int new_height)
{
width = new_width;
height = new_height;
UI_CALL(update_fg, (ui->rgb ? normal_fg : cterm_normal_fg_color - 1));
UI_CALL(update_bg, (ui->rgb ? normal_bg : cterm_normal_bg_color - 1));
sr.top = 0;
sr.bot = height - 1;
sr.left = 0;
@ -240,7 +287,7 @@ void ui_attach(UI *ui)
}
uis[ui_count++] = ui;
resized(ui);
ui_refresh();
}
void ui_detach(UI *ui)
@ -267,17 +314,8 @@ void ui_detach(UI *ui)
ui_count--;
if (ui->width == width || ui->height == height) {
// It is possible that the UI being detached had the smallest screen,
// so check for the new minimum dimensions
width = height = INT_MAX;
for (size_t i = 0; i < ui_count; i++) {
check_dimensions(uis[i]);
}
}
if (ui_count) {
screen_resize(width, height, true);
ui_refresh();
}
}
@ -295,8 +333,7 @@ static void highlight_start(int mask)
return;
}
set_highlight_args(current_highlight_mask, &current_attrs);
UI_CALL(highlight_set, current_attrs);
set_highlight_args(current_highlight_mask);
}
static void highlight_stop(int mask)
@ -309,12 +346,12 @@ static void highlight_stop(int mask)
current_highlight_mask &= ~mask;
}
set_highlight_args(current_highlight_mask, &current_attrs);
UI_CALL(highlight_set, current_attrs);
set_highlight_args(current_highlight_mask);
}
static void set_highlight_args(int mask, HlAttrs *attrs)
static void set_highlight_args(int mask)
{
HlAttrs rgb_attrs = { false, false, false, false, false, -1, -1 };
attrentry_T *aep = NULL;
if (mask > HL_ALL) {
@ -322,18 +359,40 @@ static void set_highlight_args(int mask, HlAttrs *attrs)
mask = aep ? aep->ae_attr : 0;
}
attrs->bold = mask & HL_BOLD;
attrs->standout = mask & HL_STANDOUT;
attrs->underline = mask & HL_UNDERLINE;
attrs->undercurl = mask & HL_UNDERCURL;
attrs->italic = mask & HL_ITALIC;
attrs->reverse = mask & HL_INVERSE;
attrs->foreground = aep && aep->fg_color >= 0 ? aep->fg_color : normal_fg;
attrs->background = aep && aep->bg_color >= 0 ? aep->bg_color : normal_bg;
rgb_attrs.bold = mask & HL_BOLD;
rgb_attrs.underline = mask & HL_UNDERLINE;
rgb_attrs.undercurl = mask & HL_UNDERCURL;
rgb_attrs.italic = mask & HL_ITALIC;
rgb_attrs.reverse = mask & (HL_INVERSE | HL_STANDOUT);
HlAttrs cterm_attrs = rgb_attrs;
if (aep) {
if (aep->fg_color != normal_fg) {
rgb_attrs.foreground = aep->fg_color;
}
if (aep->bg_color != normal_bg) {
rgb_attrs.background = aep->bg_color;
}
if (cterm_normal_fg_color != aep->ae_u.cterm.fg_color) {
cterm_attrs.foreground = aep->ae_u.cterm.fg_color - 1;
}
if (cterm_normal_bg_color != aep->ae_u.cterm.bg_color) {
cterm_attrs.background = aep->ae_u.cterm.bg_color - 1;
}
}
UI_CALL(highlight_set, (ui->rgb ? rgb_attrs : cterm_attrs));
}
static void parse_abstract_ui_codes(uint8_t *ptr, int len)
{
if (!ui_count) {
return;
}
int arg1 = 0, arg2 = 0;
uint8_t *end = ptr + len, *p, c;
bool update_cursor = false;
@ -444,6 +503,9 @@ static void parse_abstract_ui_codes(uint8_t *ptr, int len)
UI_CALL(put, NULL, 0);
col++;
}
if (col >= width) {
ui_linefeed();
}
p += clen;
}
ptr = p;
@ -457,25 +519,6 @@ static void parse_abstract_ui_codes(uint8_t *ptr, int len)
UI_CALL(flush);
}
static void resized(UI *ui)
{
check_dimensions(ui);
screen_resize(width, height, true);
}
static void check_dimensions(UI *ui)
{
// The internal screen dimensions are always the minimum required to fit on
// all connected screens
if (ui->width < width) {
width = ui->width;
}
if (ui->height < height) {
height = ui->height;
}
}
static void ui_linefeed(void)
{
int new_col = 0;

View File

@ -6,13 +6,14 @@
#include <stdint.h>
typedef struct {
bool bold, standout, underline, undercurl, italic, reverse;
bool bold, underline, undercurl, italic, reverse;
int foreground, background;
} HlAttrs;
typedef struct ui_t UI;
struct ui_t {
bool rgb;
int width, height;
void *data;
void (*resize)(UI *ui, int rows, int columns);
@ -32,7 +33,11 @@ struct ui_t {
void (*bell)(UI *ui);
void (*visual_bell)(UI *ui);
void (*flush)(UI *ui);
void (*update_fg)(UI *ui, int fg);
void (*update_bg)(UI *ui, int bg);
void (*suspend)(UI *ui);
void (*set_title)(UI *ui, char *title);
void (*set_icon)(UI *ui, char *icon);
};
#ifdef INCLUDE_GENERATED_DECLARATIONS

View File

@ -23,18 +23,18 @@ describe('visual block shift and tab characters', function()
abcdefghijklmnopqrstuvwxyz]])
feed('gg')
feed([[fe<C-v>4jR<esc>ugvr1:'<,'>yank A<cr>]])
feed([[fe<C-v>4jR<esc>ugvr1:'<lt>,'>yank A<cr>]])
execute('/^abcdefgh')
feed('<C-v>4jI <esc>j<<11|D')
feed('<C-v>4jI <esc>j<lt><lt>11|D')
feed('j7|a <esc>')
feed('j7|a <esc>')
feed('j7|a <esc>4k13|<C-v>4j<')
feed('j7|a <esc>4k13|<C-v>4j<lt>')
execute('$-5,$yank A')
execute([[$-4,$s/\s\+//g]])
feed('<C-v>4kI <esc>j<<')
feed('<C-v>4kI <esc>j<lt><lt>')
feed('j7|a <esc>')
feed('j7|a <esc>')
feed('j7|a <esc>4k13|<C-v>4j3<')
feed('j7|a <esc>4k13|<C-v>4j3<lt>')
execute('$-4,$yank A')
-- Put @a and clean empty lines

View File

@ -1,7 +1,30 @@
local helpers = require('test.functional.helpers')
local Screen = require('test.functional.ui.screen')
local clear, feed, nvim = helpers.clear, helpers.feed, helpers.nvim
local execute = helpers.execute
local execute, request, eq = helpers.execute, helpers.request, helpers.eq
describe('color scheme compatibility', function()
before_each(function()
clear()
end)
it('t_Co is set to 256 by default', function()
eq('256', request('vim_eval', '&t_Co'))
request('vim_set_option', 't_Co', '88')
eq('88', request('vim_eval', '&t_Co'))
end)
it('emulates gui_running when a rgb UI is attached', function()
eq(0, request('vim_eval', 'has("gui_running")'))
local screen = Screen.new()
screen:attach()
eq(1, request('vim_eval', 'has("gui_running")'))
screen:detach()
eq(0, request('vim_eval', 'has("gui_running")'))
end)
end)
describe('Default highlight groups', function()
-- Test the default attributes for highlight groups shown by the :highlight
@ -24,7 +47,6 @@ describe('Default highlight groups', function()
after_each(function()
screen:detach()
end)
it('window status bar', function()
screen:set_default_attr_ids({
[1] = {reverse = true, bold = true}, -- StatusLine
@ -142,7 +164,6 @@ describe('Default highlight groups', function()
end)
it('end of file markers', function()
nvim('command', 'hi Normal guibg=black')
screen:expect([[
^ |
{1:~ }|

View File

@ -1,6 +1,7 @@
local helpers = require('test.functional.helpers')
local Screen = require('test.functional.ui.screen')
local clear, feed, nvim = helpers.clear, helpers.feed, helpers.nvim
local insert, execute = helpers.insert, helpers.execute
describe('Mouse input', function()
local screen, hlgroup_colors
@ -154,4 +155,87 @@ describe('Mouse input', function()
]])
feed('<cr>')
end)
it('mouse whell will target the hovered window', function()
feed('ggdG')
insert([[
Inserting
text
with
many
lines
to
test
mouse scrolling
]])
screen:try_resize(53, 14)
execute('sp', 'vsp')
screen:expect([[
lines |lines |
to |to |
test |test |
mouse scrolling |mouse scrolling |
^ | |
~ |~ |
[No Name] [+] [No Name] [+] |
to |
test |
mouse scrolling |
|
~ |
[No Name] [+] |
:vsp |
]])
feed('<MouseUp><0,0>')
screen:expect([[
mouse scrolling |lines |
^ |to |
~ |test |
~ |mouse scrolling |
~ | |
~ |~ |
[No Name] [+] [No Name] [+] |
to |
test |
mouse scrolling |
|
~ |
[No Name] [+] |
|
]])
feed('<MouseDown><27,0>')
screen:expect([[
mouse scrolling |text |
^ |with |
~ |many |
~ |lines |
~ |to |
~ |test |
[No Name] [+] [No Name] [+] |
to |
test |
mouse scrolling |
|
~ |
[No Name] [+] |
|
]])
feed('<MouseDown><27,7><MouseDown>')
screen:expect([[
mouse scrolling |text |
^ |with |
~ |many |
~ |lines |
~ |to |
~ |test |
[No Name] [+] [No Name] [+] |
Inserting |
text |
with |
many |
lines |
[No Name] [+] |
|
]])
end)
end)

View File

@ -83,6 +83,21 @@ local eq, dedent = helpers.eq, helpers.dedent
local Screen = {}
Screen.__index = Screen
local debug_screen
function Screen.debug(command)
if not command then
command = 'pynvim -n -g -c '
end
command = command .. request('vim_eval', '$NVIM_LISTEN_ADDRESS')
if debug_screen then
debug_screen:close()
end
debug_screen = io.popen(command, 'r')
debug_screen:read()
end
function Screen.new(width, height)
if not width then
width = 53
@ -90,24 +105,22 @@ function Screen.new(width, height)
if not height then
height = 14
end
return setmetatable({
local self = setmetatable({
title = '',
icon = '',
bell = false,
visual_bell = false,
suspended = false,
_default_attr_ids = nil,
_width = width,
_height = height,
_rows = new_cell_grid(width, height),
_mode = 'normal',
_mouse_enabled = true,
_bell = false,
_visual_bell = false,
_suspended = true,
_attrs = {},
_cursor = {
enabled = true, row = 1, col = 1
},
_scroll_region = {
top = 1, bot = height, left = 1, right = width
}
}, Screen)
self:_handle_resize(width, height)
return self
end
function Screen:set_default_attr_ids(attr_ids)
@ -115,13 +128,15 @@ function Screen:set_default_attr_ids(attr_ids)
end
function Screen:attach()
request('attach_ui', self._width, self._height)
self._suspended = false
request('ui_attach', self._width, self._height, true)
end
function Screen:detach()
request('detach_ui')
self._suspended = true
request('ui_detach')
end
function Screen:try_resize(columns, rows)
request('ui_try_resize', columns, rows)
end
function Screen:expect(expected, attr_ids)
@ -134,7 +149,7 @@ function Screen:expect(expected, attr_ids)
table.insert(expected_rows, row)
end
local ids = attr_ids or self._default_attr_ids
self:_wait(function()
self:wait(function()
for i = 1, self._height do
local expected_row = expected_rows[i]
local actual_row = self:_row_repr(self._rows[i], ids)
@ -146,7 +161,7 @@ function Screen:expect(expected, attr_ids)
end)
end
function Screen:_wait(check, timeout)
function Screen:wait(check, timeout)
local err, checked = false
local function notification_cb(method, args)
assert(method == 'redraw')
@ -181,16 +196,30 @@ function Screen:_redraw(updates)
end
function Screen:_handle_resize(width, height)
self._rows = new_cell_grid(width, height)
local rows = {}
for i = 1, height do
local cols = {}
for j = 1, width do
table.insert(cols, {text = ' ', attrs = {}})
end
table.insert(rows, cols)
end
self._rows = rows
self._width = width
self._height = height
self._scroll_region = {
top = 1, bot = height, left = 1, right = width
}
end
function Screen:_handle_clear()
self:_clear_block(1, self._height, 1, self._width)
self:_clear_block(self._scroll_region.top, self._scroll_region.bot,
self._scroll_region.left, self._scroll_region.right)
end
function Screen:_handle_eol_clear()
local row, col = self._cursor.row, self._cursor.col
self:_clear_block(row, 1, col, self._width - col)
self:_clear_block(row, 1, col, self._scroll_region.right - col)
end
function Screen:_handle_cursor_goto(row, col)
@ -250,11 +279,14 @@ function Screen:_handle_scroll(count)
for i = start, stop, step do
local target = self._rows[i]
local source = self._rows[i + count]
self:_copy_row_section(target, source, left, right)
for j = left, right do
target[j].text = source[j].text
target[j].attrs = source[j].attrs
end
end
-- clear invalid rows
for i = stop + 1, stop + count, step do
for i = stop + step, stop + count, step do
self:_clear_row_section(i, left, right)
end
end
@ -271,15 +303,31 @@ function Screen:_handle_put(str)
end
function Screen:_handle_bell()
self._bell = true
self.bell = true
end
function Screen:_handle_visual_bell()
self._visual_bell = true
self.visual_bell = true
end
function Screen:_handle_update_fg(fg)
self._fg = fg
end
function Screen:_handle_update_bg(bg)
self._bg = bg
end
function Screen:_handle_suspend()
self._suspended = true
self.suspended = true
end
function Screen:_handle_set_title(title)
self.title = title
end
function Screen:_handle_set_icon(icon)
self.icon = icon
end
function Screen:_clear_block(top, lines, left, columns)
@ -296,13 +344,6 @@ function Screen:_clear_row_section(rownum, startcol, stopcol)
end
end
function Screen:_copy_row_section(target, source, startcol, stopcol)
for i = startcol, stopcol do
target[i].text = source[i].text
target[i].attrs = source[i].attrs
end
end
function Screen:_row_repr(row, attr_ids)
local rv = {}
local current_attr_id
@ -353,18 +394,6 @@ function backward_find_meaningful(tbl, from)
return from
end
function new_cell_grid(width, height)
local rows = {}
for i = 1, height do
local cols = {}
for j = 1, width do
table.insert(cols, {text = ' ', attrs = {}})
end
table.insert(rows, cols)
end
return rows
end
function get_attr_id(attr_ids, attrs)
if not attr_ids then
return

View File

@ -16,6 +16,69 @@ describe('Screen', function()
screen:detach()
end)
describe(':suspend', function()
it('is forwarded to the UI', function()
local function check()
if not screen.suspended then
return 'Screen was not suspended'
end
end
execute('suspend')
screen:wait(check)
screen.suspended = false
feed('<c-z>')
screen:wait(check)
end)
end)
describe('bell/visual bell', function()
it('is forwarded to the UI', function()
feed('<left>')
screen:wait(function()
if not screen.bell or screen.visual_bell then
return 'Bell was not sent'
end
end)
screen.bell = false
execute('set visualbell')
feed('<left>')
screen:wait(function()
if not screen.visual_bell or screen.bell then
return 'Visual bell was not sent'
end
end)
end)
end)
describe(':set title', function()
it('is forwarded to the UI', function()
local expected = 'test-title'
execute('set titlestring='..expected)
execute('set title')
screen:wait(function()
local actual = screen.title
if actual ~= expected then
return 'Expected title to be "'..expected..'" but was "'..actual..'"'
end
end)
end)
end)
describe(':set icon', function()
it('is forwarded to the UI', function()
local expected = 'test-icon'
execute('set iconstring='..expected)
execute('set icon')
screen:wait(function()
local actual = screen.icon
if actual ~= expected then
return 'Expected title to be "'..expected..'" but was "'..actual..'"'
end
end)
end)
end)
describe('window', function()
describe('split', function()
it('horizontal', function()
@ -95,6 +158,8 @@ describe('Screen', function()
|
]])
end)
end)
end)
@ -221,4 +286,193 @@ describe('Screen', function()
feed('<cr>') -- skip the "Press ENTER..." state or tests will hang
end)
end)
describe('scrolling and clearing', function()
before_each(function()
insert([[
Inserting
text
with
many
lines
to
test
scrolling
and
clearing
in
split
windows
]])
execute('sp', 'vsp', 'vsp')
screen:expect([[
and |and |and |
clearing |clearing |clearing |
in |in |in |
split |split |split |
windows |windows |windows |
^ | | |
[No Name] [+] [No Name] [+] [No Name] [+] |
clearing |
in |
split |
windows |
|
[No Name] [+] |
|
]])
end)
it('only affects the current scroll region', function()
feed('6k')
screen:expect([[
^crolling |and |and |
and |clearing |clearing |
clearing |in |in |
in |split |split |
split |windows |windows |
windows | | |
[No Name] [+] [No Name] [+] [No Name] [+] |
clearing |
in |
split |
windows |
|
[No Name] [+] |
|
]])
feed('<c-w>l')
screen:expect([[
scrolling |and |and |
and |clearing |clearing |
clearing |in |in |
in |split |split |
split |windows |windows |
windows |^ | |
[No Name] [+] [No Name] [+] <Name] [+] |
clearing |
in |
split |
windows |
|
[No Name] [+] |
|
]])
feed('gg')
screen:expect([[
scrolling |^nserting |and |
and |text |clearing |
clearing |with |in |
in |many |split |
split |lines |windows |
windows |to | |
[No Name] [+] [No Name] [+] <Name] [+] |
clearing |
in |
split |
windows |
|
[No Name] [+] |
|
]])
feed('7j')
screen:expect([[
scrolling |with |and |
and |many |clearing |
clearing |lines |in |
in |to |split |
split |test |windows |
windows |^crolling | |
[No Name] [+] [No Name] [+] <Name] [+] |
clearing |
in |
split |
windows |
|
[No Name] [+] |
|
]])
feed('2j')
screen:expect([[
scrolling |lines |and |
and |to |clearing |
clearing |test |in |
in |scrolling |split |
split |and |windows |
windows |^learing | |
[No Name] [+] [No Name] [+] <Name] [+] |
clearing |
in |
split |
windows |
|
[No Name] [+] |
|
]])
feed('5k')
screen:expect([[
scrolling |^ines |and |
and |to |clearing |
clearing |test |in |
in |scrolling |split |
split |and |windows |
windows |clearing | |
[No Name] [+] [No Name] [+] <Name] [+] |
clearing |
in |
split |
windows |
|
[No Name] [+] |
|
]])
feed('k')
screen:expect([[
scrolling |^any |and |
and |lines |clearing |
clearing |to |in |
in |test |split |
split |scrolling |windows |
windows |and | |
[No Name] [+] [No Name] [+] <Name] [+] |
clearing |
in |
split |
windows |
|
[No Name] [+] |
|
]])
end)
end)
describe('resize', function()
before_each(function()
screen:try_resize(25, 5)
feed('iresize')
end)
it('rebuilds the whole screen', function()
screen:expect([[
resize^ |
~ |
~ |
~ |
-- INSERT -- |
]])
end)
it('has minimum width/height values', function()
screen:try_resize(1, 1)
screen:expect([[
-- INS^RT --|
|
]])
feed('<esc>:ls')
screen:expect([[
resize |
:ls^ |
]])
end)
end)
end)

View File

@ -73,9 +73,9 @@ set(LIBTERMKEY_URL https://github.com/neovim/libtermkey/archive/7b3bdafdf589d084
set(LIBTERMKEY_SHA1 28bfe54dfd9269910a132b51dee7725a2121578d)
set(LIBTERMKEY_MD5 f0bac9c2467cc80c821be937ea5c13bc)
set(LIBTICKIT_URL https://github.com/neovim/libtickit/archive/0430ba2f43fdf1c31bca66def52a2537c581ade5.tar.gz)
set(LIBTICKIT_SHA1 732b145a4dab06e6f1b40a352424f808730726bf)
set(LIBTICKIT_MD5 3fcb635e572851472fc5009709d980fe)
set(LIBTICKIT_URL https://github.com/neovim/libtickit/archive/33f4afb3891df05955429acbf5b406dfe87ec22b.tar.gz)
set(LIBTICKIT_SHA1 3aab459b9fb3cd83e85ac2e08f05e5f162c8c9d2)
set(LIBTICKIT_MD5 19ee9271c16716620d0906db74158ec6)
if(USE_BUNDLED_LIBUNIBILIUM)
ExternalProject_Add(libunibilium