diff options
author | Thiago de Arruda <tpadilha84@gmail.com> | 2014-12-06 11:27:36 -0300 |
---|---|---|
committer | Thiago de Arruda <tpadilha84@gmail.com> | 2014-12-08 23:44:23 -0300 |
commit | 07e569a25dba4bf6d9743102a34666964efb45cb (patch) | |
tree | f0a1e6bc8546327365cae045505a6d059a87d64f /src/nvim/ui.c | |
parent | 8b6cfff6a18d839d11900cd1fade5938dc9a02d5 (diff) | |
download | rneovim-07e569a25dba4bf6d9743102a34666964efb45cb.tar.gz rneovim-07e569a25dba4bf6d9743102a34666964efb45cb.tar.bz2 rneovim-07e569a25dba4bf6d9743102a34666964efb45cb.zip |
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.
Diffstat (limited to 'src/nvim/ui.c')
-rw-r--r-- | src/nvim/ui.c | 455 |
1 files changed, 440 insertions, 15 deletions
diff --git a/src/nvim/ui.c b/src/nvim/ui.c index eab6251288..fb3325f163 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -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 (silent_mode && !p_verbose) { + // Don't output anything in silent mode ("ex -s") unless 'verbose' set + return; + } - 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; - } + if (abstract_ui) { + parse_abstract_ui_codes(s, len); + return; + } - term_write(s, len); + char_u *tofree = NULL; - if (output_conv.vc_type != CONV_NONE) - free(tofree); + 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, ¤t_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, ¤t_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); +} |