ui: Add abstract_ui termcap and split UI layer

This is how Nvim behaves when the "abstract_ui" termcap is activated:

- No data is written/read to stdout/stdin by default.
- Instead of sending data to stdout, ui_write will parse the termcap codes
  and invoke dispatch functions in the ui.c module.
- The dispatch functions will forward the calls to all attached UI
  instances(each UI instance is an implementation of the UI layer and is
  registered with ui_attach).
- Like with the "builtin_gui" termcap, "abstract_ui" does not contain any key
  sequences. Instead, vim key strings(<cr>, <esc>, etc) are parsed directly by
  input_enqueue and the translated strings are pushed to the input buffer.

With this new input model, its not possible to send mouse events yet. Thats
because mouse sequence parsing happens in term.c/check_termcodes which must
return early when "abstract_ui" is activated.
This commit is contained in:
Thiago de Arruda
2014-12-06 11:27:36 -03:00
parent 8b6cfff6a1
commit 07e569a25d
11 changed files with 615 additions and 81 deletions

View File

@@ -15,20 +15,24 @@
* 3. Input buffer stuff.
*/
#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
#include <string.h>
#include "nvim/vim.h"
#include "nvim/ui.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
#include "nvim/ex_cmds2.h"
#include "nvim/fold.h"
#include "nvim/main.h"
#include "nvim/mbyte.h"
#include "nvim/ascii.h"
#include "nvim/misc1.h"
#include "nvim/misc2.h"
#include "nvim/mbyte.h"
#include "nvim/garray.h"
#include "nvim/memory.h"
#include "nvim/move.h"
@@ -39,27 +43,74 @@
#include "nvim/os/input.h"
#include "nvim/os/signal.h"
#include "nvim/screen.h"
#include "nvim/syntax.h"
#include "nvim/term.h"
#include "nvim/window.h"
void ui_write(char_u *s, int len)
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ui.c.generated.h"
#endif
#define MAX_UI_COUNT 16
static UI *uis[MAX_UI_COUNT];
static size_t ui_count = 0;
static int row, col;
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;
// 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
// by SELECT_NTH)
//
// See http://stackoverflow.com/a/11172679 for a better explanation of how it
// works.
#define UI_CALL(...) \
do { \
for (size_t i = 0; i < ui_count; i++) { \
UI *ui = uis[i]; \
UI_CALL_HELPER(CNT(__VA_ARGS__), __VA_ARGS__); \
} \
} while (0)
#define CNT(...) SELECT_NTH(__VA_ARGS__, MORE, MORE, MORE, MORE, ZERO, ignore)
#define SELECT_NTH(a1, a2, a3, a4, a5, a6, ...) a6
#define UI_CALL_HELPER(c, ...) UI_CALL_HELPER2(c, __VA_ARGS__)
#define UI_CALL_HELPER2(c, ...) UI_CALL_##c(__VA_ARGS__)
#define UI_CALL_MORE(method, ...) ui->method(ui, __VA_ARGS__)
#define UI_CALL_ZERO(method) ui->method(ui)
void ui_write(uint8_t *s, int len)
{
/* Don't output anything in silent mode ("ex -s") unless 'verbose' set */
if (!(silent_mode && p_verbose == 0)) {
char_u *tofree = NULL;
if (output_conv.vc_type != CONV_NONE) {
/* Convert characters from 'encoding' to 'termencoding'. */
tofree = string_convert(&output_conv, s, &len);
if (tofree != NULL)
s = tofree;
}
term_write(s, len);
if (output_conv.vc_type != CONV_NONE)
free(tofree);
if (silent_mode && !p_verbose) {
// Don't output anything in silent mode ("ex -s") unless 'verbose' set
return;
}
if (abstract_ui) {
parse_abstract_ui_codes(s, len);
return;
}
char_u *tofree = NULL;
if (output_conv.vc_type != CONV_NONE) {
/* Convert characters from 'encoding' to 'termencoding'. */
tofree = string_convert(&output_conv, s, &len);
if (tofree != NULL)
s = tofree;
}
term_write(s, len);
if (output_conv.vc_type != CONV_NONE)
free(tofree);
}
/*
@@ -69,7 +120,11 @@ void ui_write(char_u *s, int len)
*/
void ui_suspend(void)
{
mch_suspend();
if (abstract_ui) {
UI_CALL(suspend);
} else {
mch_suspend();
}
}
/*
@@ -79,6 +134,10 @@ void ui_suspend(void)
*/
int ui_get_shellsize(void)
{
if (abstract_ui) {
return FAIL;
}
int retval;
retval = mch_get_shellsize();
@@ -98,7 +157,373 @@ int ui_get_shellsize(void)
*/
void ui_cursor_shape(void)
{
term_cursor_shape();
if (abstract_ui) {
ui_change_mode();
} else {
term_cursor_shape();
conceal_check_cursur_line();
}
}
void ui_resize(int width, int height)
{
sr.top = 0;
sr.bot = height - 1;
sr.left = 0;
sr.right = width - 1;
UI_CALL(resize, width, height);
}
void ui_cursor_on(void)
{
if (!cursor_enabled) {
UI_CALL(cursor_on);
cursor_enabled = true;
}
}
void ui_cursor_off(void)
{
if (full_screen) {
if (cursor_enabled) {
UI_CALL(cursor_off);
}
cursor_enabled = false;
}
}
void ui_mouse_on(void)
{
if (abstract_ui) {
UI_CALL(mouse_on);
} else {
mch_setmouse(true);
}
}
void ui_mouse_off(void)
{
if (abstract_ui) {
UI_CALL(mouse_off);
} else {
mch_setmouse(false);
}
}
// Notify that the current mode has changed. Can be used to change cursor
// shape, for example.
void ui_change_mode(void)
{
static int showing_insert_mode = MAYBE;
if (!full_screen)
return;
if (State & INSERT) {
if (showing_insert_mode != TRUE) {
UI_CALL(insert_mode);
}
showing_insert_mode = TRUE;
} else {
if (showing_insert_mode != FALSE) {
UI_CALL(normal_mode);
}
showing_insert_mode = FALSE;
}
conceal_check_cursur_line();
}
void ui_attach(UI *ui)
{
if (ui_count == MAX_UI_COUNT) {
abort();
}
uis[ui_count++] = ui;
resized(ui);
}
void ui_detach(UI *ui)
{
size_t shift_index = MAX_UI_COUNT;
// Find the index that will be removed
for (size_t i = 0; i < ui_count; i++) {
if (uis[i] == ui) {
shift_index = i;
break;
}
}
if (shift_index == MAX_UI_COUNT) {
abort();
}
// Shift UIs at "shift_index"
while (shift_index < ui_count - 1) {
uis[shift_index] = uis[shift_index + 1];
shift_index++;
}
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);
}
}
static void highlight_start(int mask)
{
if (mask > HL_ALL) {
// attribute code
current_highlight_mask = mask;
} else {
// attribute mask
current_highlight_mask |= mask;
}
if (!ui_count) {
return;
}
set_highlight_args(current_highlight_mask, &current_attrs);
UI_CALL(highlight_set, current_attrs);
}
static void highlight_stop(int mask)
{
if (mask > HL_ALL) {
// attribute code
current_highlight_mask = HL_NORMAL;
} else {
// attribute mask
current_highlight_mask &= ~mask;
}
set_highlight_args(current_highlight_mask, &current_attrs);
UI_CALL(highlight_set, current_attrs);
}
static void set_highlight_args(int mask, HlAttrs *attrs)
{
attrentry_T *aep = NULL;
attrs->foreground = -1;
attrs->background = -1;
if (mask > HL_ALL) {
aep = syn_cterm_attr2entry(mask);
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;
if (aep && aep->ae_u.cterm.fg_color
&& (cterm_normal_fg_color != aep->ae_u.cterm.fg_color)) {
attrs->foreground = aep->ae_u.cterm.fg_color - 1;
}
if (aep && aep->ae_u.cterm.bg_color
&& (cterm_normal_bg_color != aep->ae_u.cterm.bg_color)) {
attrs->background = aep->ae_u.cterm.bg_color - 1;
}
}
static void parse_abstract_ui_codes(uint8_t *ptr, int len)
{
int arg1 = 0, arg2 = 0;
uint8_t *end = ptr + len, *p, c;
bool update_cursor = false;
while (ptr < end) {
if (ptr < end - 1 && ptr[0] == ESC && ptr[1] == '|') {
p = ptr + 2;
assert(p != end);
if (VIM_ISDIGIT(*p)) {
arg1 = (int)getdigits(&p);
if (p >= end) {
break;
}
if (*p == ';') {
p++;
arg2 = (int)getdigits(&p);
if (p >= end)
break;
}
}
switch (*p) {
case 'C':
UI_CALL(clear);
break;
case 'M':
ui_cursor_goto(arg1, arg2);
break;
case 's':
update_cursor = true;
break;
case 'R':
if (arg1 < arg2) {
sr.top = arg1;
sr.bot = arg2;
UI_CALL(set_scroll_region, sr.top, sr.bot, sr.left, sr.right);
} else {
sr.top = arg2;
sr.bot = arg1;
UI_CALL(set_scroll_region, sr.top, sr.bot, sr.left, sr.right);
}
break;
case 'V':
if (arg1 < arg2) {
sr.left = arg1;
sr.right = arg2;
UI_CALL(set_scroll_region, sr.top, sr.bot, sr.left, sr.right);
} else {
sr.left = arg2;
sr.right = arg1;
UI_CALL(set_scroll_region, sr.top, sr.bot, sr.left, sr.right);
}
break;
case 'd':
UI_CALL(scroll, 1);
break;
case 'D':
UI_CALL(scroll, arg1);
break;
case 'i':
UI_CALL(scroll, -1);
break;
case 'I':
UI_CALL(scroll, -arg1);
break;
case '$':
UI_CALL(eol_clear);
break;
case 'h':
highlight_start(arg1);
break;
case 'H':
highlight_stop(arg1);
break;
case 'f':
UI_CALL(visual_bell);
break;
default:
// Skip the ESC
p = ptr + 1;
break;
}
ptr = ++p;
} else if ((c = *ptr) < 0x20) {
// Ctrl character
if (c == '\n') {
ui_linefeed();
} else if (c == '\r') {
ui_carriage_return();
} else if (c == '\b') {
ui_cursor_left();
} else if (c == Ctrl_L) { // cursor right
ui_cursor_right();
} else if (c == Ctrl_G) {
UI_CALL(bell);
}
ptr++;
} else {
p = ptr;
while (p < end && (*p >= 0x20)) {
size_t clen = (size_t)mb_ptr2len(p);
UI_CALL(put, p, (size_t)clen);
col++;
if (mb_ptr2cells(p) > 1) {
// double cell character, blank the next cell
UI_CALL(put, NULL, 0);
col++;
}
p += clen;
}
ptr = p;
}
}
if (update_cursor) {
ui_cursor_shape();
}
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;
int new_row = row;
if (new_row < sr.bot) {
new_row++;
} else {
UI_CALL(scroll, 1);
}
ui_cursor_goto(new_row, new_col);
}
static void ui_carriage_return(void)
{
int new_col = 0;
ui_cursor_goto(row, new_col);
}
static void ui_cursor_left(void)
{
int new_col = col - 1;
assert(new_col >= 0);
ui_cursor_goto(row, new_col);
}
static void ui_cursor_right(void)
{
int new_col = col + 1;
assert(new_col < width);
ui_cursor_goto(row, new_col);
}
static void ui_cursor_goto(int new_row, int new_col)
{
if (new_row == row && new_col == col) {
return;
}
row = new_row;
col = new_col;
UI_CALL(cursor_goto, row, col);
}