diff options
author | Björn Linse <bjorn.linse@gmail.com> | 2018-07-06 14:39:50 +0200 |
---|---|---|
committer | Björn Linse <bjorn.linse@gmail.com> | 2018-07-21 13:21:58 +0200 |
commit | 1adb01c120d04bdbf25cd4ea6151ecd5f2de3a72 (patch) | |
tree | 1f906f5f77b77bc0beecb33320ceb4c15ff5c58e | |
parent | 2134396074d86c344aaf43c3b839fd38c499fb69 (diff) | |
download | rneovim-1adb01c120d04bdbf25cd4ea6151ecd5f2de3a72.tar.gz rneovim-1adb01c120d04bdbf25cd4ea6151ecd5f2de3a72.tar.bz2 rneovim-1adb01c120d04bdbf25cd4ea6151ecd5f2de3a72.zip |
ui: use line-based rather than char-based updates in screen.c
Add ext_newgrid and ext_hlstate extensions. These use predefined
highlights and line-segment based updates, for efficiency and
simplicity.. The ext_hlstate extension in addition allows semantic
identification of builtin and syntax highlights.
Reimplement the old char-based updates in the remote UI layer, for
compatibility. For the moment, this is still the default. The bulitin
TUI uses the new line-based protocol.
cmdline uses curwin cursor position when ext_cmdline is active.
28 files changed, 967 insertions, 564 deletions
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 01e6e0ca03..63c2c4a1b9 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -26,6 +26,12 @@ typedef struct { uint64_t channel_id; Array buffer; + + int hl_id; // current higlight for legacy put event + Integer cursor_row, cursor_col; // Intended visibule cursor position + + // Position of legacy cursor, used both for drawing and visible user cursor. + Integer client_row, client_col; } UIData; static PMap(uint64_t) *connected_uis = NULL; @@ -71,10 +77,9 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, ui->width = (int)width; ui->height = (int)height; ui->rgb = true; - ui->resize = remote_ui_resize; - ui->clear = remote_ui_clear; - ui->eol_clear = remote_ui_eol_clear; - ui->cursor_goto = remote_ui_cursor_goto; + ui->grid_resize = remote_ui_grid_resize; + ui->grid_clear = remote_ui_grid_clear; + ui->grid_cursor_goto = remote_ui_grid_cursor_goto; ui->mode_info_set = remote_ui_mode_info_set; ui->update_menu = remote_ui_update_menu; ui->busy_start = remote_ui_busy_start; @@ -82,16 +87,12 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, ui->mouse_on = remote_ui_mouse_on; ui->mouse_off = remote_ui_mouse_off; ui->mode_change = remote_ui_mode_change; - ui->set_scroll_region = remote_ui_set_scroll_region; - ui->scroll = remote_ui_scroll; - ui->highlight_set = remote_ui_highlight_set; - ui->put = remote_ui_put; + ui->grid_scroll = remote_ui_grid_scroll; + ui->hl_attr_define = remote_ui_hl_attr_define; + ui->raw_line = remote_ui_raw_line; ui->bell = remote_ui_bell; ui->visual_bell = remote_ui_visual_bell; ui->default_colors_set = remote_ui_default_colors_set; - ui->update_fg = remote_ui_update_fg; - ui->update_bg = remote_ui_update_bg; - ui->update_sp = remote_ui_update_sp; ui->flush = remote_ui_flush; ui->suspend = remote_ui_suspend; ui->set_title = remote_ui_set_title; @@ -103,16 +104,22 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, 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); + ui_set_option(ui, true, options.items[i].key, options.items[i].value, err); if (ERROR_SET(err)) { xfree(ui); return; } } + if (ui->ui_ext[kUIHlState]) { + ui->ui_ext[kUINewgrid] = true; + } + UIData *data = xmalloc(sizeof(UIData)); data->channel_id = channel_id; data->buffer = (Array)ARRAY_DICT_INIT; + data->hl_id = 0; + data->client_col = -1; ui->data = data; pmap_put(uint64_t)(connected_uis, channel_id, ui); @@ -174,13 +181,11 @@ void nvim_ui_set_option(uint64_t channel_id, String name, } UI *ui = pmap_get(uint64_t)(connected_uis, channel_id); - ui_set_option(ui, name, value, error); - if (!ERROR_SET(error)) { - ui_refresh(); - } + ui_set_option(ui, false, name, value, error); } -static void ui_set_option(UI *ui, String name, Object value, Error *error) +static void ui_set_option(UI *ui, bool init, String name, Object value, + Error *error) { if (strequal(name.data, "rgb")) { if (value.type != kObjectTypeBoolean) { @@ -188,40 +193,46 @@ static void ui_set_option(UI *ui, String name, Object value, Error *error) return; } ui->rgb = value.data.boolean; + // A little drastic, but only legacy uis need to use this option + if (!init) { + ui_refresh(); + } return; } + // LEGACY: Deprecated option, use `ext_cmdline` instead. + bool is_popupmenu = strequal(name.data, "popupmenu_external"); + for (UIExtension i = 0; i < kUIExtCount; i++) { - if (strequal(name.data, ui_ext_names[i])) { + if (strequal(name.data, ui_ext_names[i]) + || (i == kUIPopupmenu && is_popupmenu)) { if (value.type != kObjectTypeBoolean) { snprintf((char *)IObuff, IOSIZE, "%s must be a Boolean", - ui_ext_names[i]); + name.data); api_set_error(error, kErrorTypeValidation, (char *)IObuff); return; } - ui->ui_ext[i] = value.data.boolean; - return; - } - } - - if (strequal(name.data, "popupmenu_external")) { - // LEGACY: Deprecated option, use `ext_cmdline` instead. - if (value.type != kObjectTypeBoolean) { - api_set_error(error, kErrorTypeValidation, - "popupmenu_external must be a Boolean"); + bool boolval = value.data.boolean; + if (!init && i == kUINewgrid && boolval != ui->ui_ext[i]) { + // There shouldn't be a reason for an UI to do this ever + // so explicitly don't support this. + api_set_error(error, kErrorTypeValidation, + "ext_newgrid option cannot be changed"); + } + ui->ui_ext[i] = boolval; + if (!init) { + ui_set_ext_option(ui, i, boolval); + } return; } - ui->ui_ext[kUIPopupmenu] = value.data.boolean; - return; } api_set_error(error, kErrorTypeValidation, "No such UI option: %s", name.data); -#undef UI_EXT_OPTION } /// Pushes data into UI.UIData, to be consumed later by remote_ui_flush(). -static void push_call(UI *ui, char *name, Array args) +static void push_call(UI *ui, const char *name, Array args) { Array call = ARRAY_DICT_INIT; UIData *data = ui->data; @@ -243,27 +254,293 @@ static void push_call(UI *ui, char *name, Array args) kv_A(data->buffer, kv_size(data->buffer) - 1).data.array = call; } +static void remote_ui_grid_clear(UI *ui, Integer grid) +{ + Array args = ARRAY_DICT_INIT; + if (ui->ui_ext[kUINewgrid]) { + ADD(args, INTEGER_OBJ(grid)); + } + const char *name = ui->ui_ext[kUINewgrid] ? "grid_clear" : "clear"; + push_call(ui, name, args); +} -static void remote_ui_highlight_set(UI *ui, HlAttrs attrs) +static void remote_ui_grid_resize(UI *ui, Integer grid, + Integer width, Integer height) { Array args = ARRAY_DICT_INIT; + if (ui->ui_ext[kUINewgrid]) { + ADD(args, INTEGER_OBJ(grid)); + } + ADD(args, INTEGER_OBJ(width)); + ADD(args, INTEGER_OBJ(height)); + const char *name = ui->ui_ext[kUINewgrid] ? "grid_resize" : "resize"; + push_call(ui, name, args); +} + +static void remote_ui_grid_scroll(UI *ui, Integer grid, Integer top, + Integer bot, Integer left, Integer right, + Integer rows, Integer cols) +{ + if (ui->ui_ext[kUINewgrid]) { + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(grid)); + ADD(args, INTEGER_OBJ(top)); + ADD(args, INTEGER_OBJ(bot)); + ADD(args, INTEGER_OBJ(left)); + ADD(args, INTEGER_OBJ(right)); + ADD(args, INTEGER_OBJ(rows)); + ADD(args, INTEGER_OBJ(cols)); + push_call(ui, "grid_scroll", args); + } else { + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(top)); + ADD(args, INTEGER_OBJ(bot-1)); + ADD(args, INTEGER_OBJ(left)); + ADD(args, INTEGER_OBJ(right-1)); + push_call(ui, "set_scroll_region", args); + + args = (Array)ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(rows)); + push_call(ui, "scroll", args); + } +} + +static void remote_ui_default_colors_set(UI *ui, Integer rgb_fg, + Integer rgb_bg, Integer rgb_sp, + Integer cterm_fg, Integer cterm_bg) +{ + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(rgb_fg)); + ADD(args, INTEGER_OBJ(rgb_bg)); + ADD(args, INTEGER_OBJ(rgb_sp)); + ADD(args, INTEGER_OBJ(cterm_fg)); + ADD(args, INTEGER_OBJ(cterm_bg)); + push_call(ui, "default_colors_set", args); + + // Deprecated + if (!ui->ui_ext[kUINewgrid]) { + args = (Array)ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(ui->rgb ? rgb_fg : cterm_fg - 1)); + push_call(ui, "update_fg", args); + + args = (Array)ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(ui->rgb ? rgb_bg : cterm_bg - 1)); + push_call(ui, "update_bg", args); + + args = (Array)ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(ui->rgb ? rgb_sp : -1)); + push_call(ui, "update_sp", args); + } +} + +static void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, + HlAttrs cterm_attrs, Array info) +{ + if (!ui->ui_ext[kUINewgrid]) { + return; + } + Array args = ARRAY_DICT_INIT; + + ADD(args, INTEGER_OBJ(id)); + + Dictionary rgb_hl = hlattrs2dict(&rgb_attrs, true); + ADD(args, DICTIONARY_OBJ(rgb_hl)); + + Dictionary cterm_hl = hlattrs2dict(&cterm_attrs, false); + ADD(args, DICTIONARY_OBJ(cterm_hl)); + + if (ui->ui_ext[kUIHlState]) { + ADD(args, ARRAY_OBJ(copy_array(info))); + } else { + ADD(args, ARRAY_OBJ((Array)ARRAY_DICT_INIT)); + } + + push_call(ui, "hl_attr_define", args); +} + +static void remote_ui_highlight_set(UI *ui, int id) +{ + Array args = ARRAY_DICT_INIT; + UIData *data = ui->data; + + HlAttrs attrs = HLATTRS_INIT; + + if (data->hl_id == id) { + return; + } + data->hl_id = id; + + if (id != 0) { + HlAttrs *aep = syn_attr2entry(id); + if (aep) { + attrs = *aep; + } + } + Dictionary hl = hlattrs2dict(&attrs, ui->rgb); ADD(args, DICTIONARY_OBJ(hl)); push_call(ui, "highlight_set", args); } +/// "true" cursor used only for input focus +static void remote_ui_grid_cursor_goto(UI *ui, Integer grid, Integer row, + Integer col) +{ + if (ui->ui_ext[kUINewgrid]) { + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(grid)); + ADD(args, INTEGER_OBJ(row)); + ADD(args, INTEGER_OBJ(col)); + push_call(ui, "grid_cursor_goto", args); + } else { + UIData *data = ui->data; + data->cursor_row = row; + data->cursor_col = col; + remote_ui_cursor_goto(ui, row, col); + } +} + +/// emulated cursor used both for drawing and for input focus +static void remote_ui_cursor_goto(UI *ui, Integer row, Integer col) +{ + UIData *data = ui->data; + if (data->client_row == row && data->client_col == col) { + return; + } + data->client_row = row; + data->client_col = col; + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(row)); + ADD(args, INTEGER_OBJ(col)); + push_call(ui, "cursor_goto", args); +} + +static void remote_ui_put(UI *ui, const char *cell) +{ + UIData *data = ui->data; + data->client_col++; + Array args = ARRAY_DICT_INIT; + ADD(args, STRING_OBJ(cstr_to_string(cell))); + push_call(ui, "put", args); +} + +static void remote_ui_raw_line(UI *ui, Integer grid, Integer row, + Integer startcol, Integer endcol, + Integer clearcol, Integer clearattr, + const schar_T *chunk, const sattr_T *attrs) +{ + UIData *data = ui->data; + if (ui->ui_ext[kUINewgrid]) { + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(grid)); + ADD(args, INTEGER_OBJ(row)); + ADD(args, INTEGER_OBJ(startcol)); + Array cells = ARRAY_DICT_INIT; + int repeat = 0; + size_t ncells = (size_t)(endcol-startcol); + int last_hl = -1; + for (size_t i = 0; i < ncells; i++) { + repeat++; + if (i == ncells-1 || attrs[i] != attrs[i+1] + || STRCMP(chunk[i], chunk[i+1])) { + Array cell = ARRAY_DICT_INIT; + ADD(cell, STRING_OBJ(cstr_to_string((const char *)chunk[i]))); + if (attrs[i] != last_hl || repeat > 1) { + ADD(cell, INTEGER_OBJ(attrs[i])); + last_hl = attrs[i]; + } + if (repeat > 1) { + ADD(cell, INTEGER_OBJ(repeat)); + } + ADD(cells, ARRAY_OBJ(cell)); + repeat = 0; + } + } + if (endcol < clearcol) { + Array cell = ARRAY_DICT_INIT; + ADD(cell, STRING_OBJ(cstr_to_string(" "))); + ADD(cell, INTEGER_OBJ(clearattr)); + ADD(cell, INTEGER_OBJ(clearcol-endcol)); + ADD(cells, ARRAY_OBJ(cell)); + } + ADD(args, ARRAY_OBJ(cells)); + + push_call(ui, "grid_line", args); + } else { + for (int i = 0; i < endcol-startcol; i++) { + remote_ui_cursor_goto(ui, row, startcol+i); + remote_ui_highlight_set(ui, attrs[i]); + remote_ui_put(ui, (const char *)chunk[i]); + if (utf_ambiguous_width(utf_ptr2char(chunk[i]))) { + data->client_col = -1; // force cursor update + } + } + if (endcol < clearcol) { + remote_ui_cursor_goto(ui, row, endcol); + remote_ui_highlight_set(ui, (int)clearattr); + // legacy eol_clear was only ever used with cleared attributes + // so be on the safe side + if (clearattr == 0 && clearcol == Columns) { + Array args = ARRAY_DICT_INIT; + push_call(ui, "eol_clear", args); + } else { + for (Integer c = endcol; c < clearcol; c++) { + remote_ui_put(ui, " "); + } + } + } + } +} + static void remote_ui_flush(UI *ui) { UIData *data = ui->data; if (data->buffer.size > 0) { + if (!ui->ui_ext[kUINewgrid]) { + remote_ui_cursor_goto(ui, data->cursor_row, data->cursor_col); + } rpc_send_event(data->channel_id, "redraw", data->buffer); data->buffer = (Array)ARRAY_DICT_INIT; } } +static void remote_ui_cmdline_show(UI *ui, Array args) +{ + Array new_args = ARRAY_DICT_INIT; + Array contents = args.items[0].data.array; + Array new_contents = ARRAY_DICT_INIT; + for (size_t i = 0; i < contents.size; i++) { + Array item = contents.items[i].data.array; + Array new_item = ARRAY_DICT_INIT; + int attr = (int)item.items[0].data.integer; + if (attr) { + HlAttrs *aep = syn_attr2entry(attr); + Dictionary rgb_attrs = hlattrs2dict(aep, ui->rgb ? kTrue : kFalse); + ADD(new_item, DICTIONARY_OBJ(rgb_attrs)); + } else { + ADD(new_item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT)); + } + ADD(new_item, copy_object(item.items[1])); + ADD(new_contents, ARRAY_OBJ(new_item)); + } + ADD(new_args, ARRAY_OBJ(new_contents)); + for (size_t i = 1; i < args.size; i++) { + ADD(new_args, copy_object(args.items[i])); + } + push_call(ui, "cmdline_show", new_args); +} + static void remote_ui_event(UI *ui, char *name, Array args, bool *args_consumed) { + if (!ui->ui_ext[kUINewgrid]) { + // the representation of cmdline_show changed, translate back + if (strequal(name, "cmdline_show")) { + remote_ui_cmdline_show(ui, args); + // never consumes args + return; + } + } Array my_args = ARRAY_DICT_INIT; // Objects are currently single-reference // make a copy, but only if necessary diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 3ef16a7ac3..456ad0c8cc 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -10,14 +10,6 @@ #include "nvim/func_attr.h" #include "nvim/ui.h" -void resize(Integer width, Integer height) - FUNC_API_SINCE(3); -void clear(void) - FUNC_API_SINCE(3); -void eol_clear(void) - FUNC_API_SINCE(3); -void cursor_goto(Integer row, Integer col) - FUNC_API_SINCE(3); void mode_info_set(Boolean enabled, Array cursor_styles) FUNC_API_SINCE(3); void update_menu(void) @@ -32,29 +24,12 @@ void mouse_off(void) FUNC_API_SINCE(3); void mode_change(String mode, Integer mode_idx) FUNC_API_SINCE(3); -void set_scroll_region(Integer top, Integer bot, Integer left, Integer right) - FUNC_API_SINCE(3); -void scroll(Integer count) - FUNC_API_SINCE(3); -void highlight_set(HlAttrs attrs) - FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; -void put(String str) - FUNC_API_SINCE(3); void bell(void) FUNC_API_SINCE(3); void visual_bell(void) FUNC_API_SINCE(3); void flush(void) FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL; -void update_fg(Integer fg) - FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; -void update_bg(Integer bg) - FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; -void update_sp(Integer sp) - FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; -void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, - Integer cterm_fg, Integer cterm_bg) - FUNC_API_SINCE(4); void suspend(void) FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; void set_title(String title) @@ -64,6 +39,49 @@ void set_icon(String icon) void option_set(String name, Object value) FUNC_API_SINCE(4) FUNC_API_BRIDGE_IMPL; +// First revison of the grid protocol, used by default +void update_fg(Integer fg) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void update_bg(Integer bg) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void update_sp(Integer sp) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void resize(Integer width, Integer height) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void clear(void) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void eol_clear(void) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void cursor_goto(Integer row, Integer col) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void highlight_set(HlAttrs attrs) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY FUNC_API_REMOTE_IMPL; +void put(String str) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void set_scroll_region(Integer top, Integer bot, Integer left, Integer right) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void scroll(Integer count) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; + +// Second revison of the grid protocol, used with ext_newgrid ui option +void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, + Integer cterm_fg, Integer cterm_bg) + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; +void hl_attr_define(Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs, + Array info) + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; +void grid_resize(Integer grid, Integer width, Integer height) + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; +void grid_clear(Integer grid) + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; +void grid_cursor_goto(Integer grid, Integer row, Integer col) + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; +void grid_line(Integer grid, Integer row, Integer col_start, Array data) + FUNC_API_SINCE(5) FUNC_API_REMOTE_ONLY; +void grid_scroll(Integer grid, Integer top, Integer bot, + Integer left, Integer right, Integer rows, Integer cols) + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; + void popupmenu_show(Array items, Integer selected, Integer row, Integer col) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void popupmenu_hide(void) diff --git a/src/nvim/event/defs.h b/src/nvim/event/defs.h index cc875d74b9..55b2d277bb 100644 --- a/src/nvim/event/defs.h +++ b/src/nvim/event/defs.h @@ -4,7 +4,7 @@ #include <assert.h> #include <stdarg.h> -#define EVENT_HANDLER_MAX_ARGC 6 +#define EVENT_HANDLER_MAX_ARGC 9 typedef void (*argv_callback)(void **argv); typedef struct message { diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index c1b9eff697..b077aefa1e 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -6320,8 +6320,10 @@ static void ex_stop(exarg_T *eap) autowrite_all(); } apply_autocmds(EVENT_VIMSUSPEND, NULL, NULL, false, NULL); + + // TODO(bfredl): the TUI should do this on suspend ui_cursor_goto((int)Rows - 1, 0); - ui_linefeed(); + ui_call_grid_scroll(1, 0, Rows, 0, Columns, 1, 0); ui_flush(); ui_call_suspend(); // call machine specific function diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 6b52ce7c80..775d002e58 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -215,6 +215,8 @@ static int hislen = 0; /* actual length of history tables */ /// user interrupting highlight function to not interrupt command-line. static bool getln_interrupted_highlight = false; +static bool need_cursor_update = false; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_getln.c.generated.h" @@ -2944,30 +2946,22 @@ static void ui_ext_cmdline_show(CmdlineInfo *line) char *buf = xmallocz(len); memset(buf, '*', len); Array item = ARRAY_DICT_INIT; - ADD(item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT)); + ADD(item, INTEGER_OBJ(0)); ADD(item, STRING_OBJ(((String) { .data = buf, .size = len }))); ADD(content, ARRAY_OBJ(item)); } else if (kv_size(line->last_colors.colors)) { for (size_t i = 0; i < kv_size(line->last_colors.colors); i++) { CmdlineColorChunk chunk = kv_A(line->last_colors.colors, i); Array item = ARRAY_DICT_INIT; + ADD(item, INTEGER_OBJ(chunk.attr)); - if (chunk.attr) { - HlAttrs *aep = syn_attr2entry(chunk.attr); - // TODO(bfredl): this desicion could be delayed by making attr_code a - // recognized type - Dictionary rgb_attrs = hlattrs2dict(aep, true); - ADD(item, DICTIONARY_OBJ(rgb_attrs)); - } else { - ADD(item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT)); - } ADD(item, STRING_OBJ(cbuf_to_string((char *)line->cmdbuff + chunk.start, chunk.end-chunk.start))); ADD(content, ARRAY_OBJ(item)); } } else { Array item = ARRAY_DICT_INIT; - ADD(item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT)); + ADD(item, INTEGER_OBJ(0)); ADD(item, STRING_OBJ(cstr_to_string((char *)(line->cmdbuff)))); ADD(content, ARRAY_OBJ(item)); } @@ -3033,6 +3027,8 @@ void cmdline_screen_cleared(void) } prev_ccline = prev_ccline->prev_ccline; } + + need_cursor_update = true; } /// called by ui_flush, do what redraws neccessary to keep cmdline updated. @@ -3501,6 +3497,10 @@ static void cursorcmd(void) if (ccline.redraw_state < kCmdRedrawPos) { ccline.redraw_state = kCmdRedrawPos; } + if (need_cursor_update) { + need_cursor_update = false; + setcursor(); + } return; } diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua index 2666ca6e6f..e76b601d8a 100644 --- a/src/nvim/generators/gen_api_ui_events.lua +++ b/src/nvim/generators/gen_api_ui_events.lua @@ -132,19 +132,21 @@ for i = 1, #events do end end - call_output:write('void ui_call_'..ev.name) - write_signature(call_output, ev, '') - call_output:write('\n{\n') - if ev.remote_only then - write_arglist(call_output, ev, false) - call_output:write(' UI_LOG('..ev.name..', 0);\n') - call_output:write(' ui_event("'..ev.name..'", args);\n') - else - call_output:write(' UI_CALL') - write_signature(call_output, ev, ev.name, true) - call_output:write(";\n") + if not (ev.remote_only and ev.remote_impl) then + call_output:write('void ui_call_'..ev.name) + write_signature(call_output, ev, '') + call_output:write('\n{\n') + if ev.remote_only then + write_arglist(call_output, ev, false) + call_output:write(' UI_LOG('..ev.name..', 0);\n') + call_output:write(' ui_event("'..ev.name..'", args);\n') + else + call_output:write(' UI_CALL') + write_signature(call_output, ev, ev.name, true) + call_output:write(";\n") + end + call_output:write("}\n\n") end - call_output:write("}\n\n") end diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 5efbafa128..0b39ba442e 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -38,7 +38,7 @@ void highlight_init(void) bool highlight_use_hlstate(void) { if (hlstate_active) { - return false; + return false; } hlstate_active = true; // hl tables must now be rebuilt. @@ -89,9 +89,26 @@ static int get_attr_entry(HlEntry entry) map_put(HlEntry, int)(attr_entry_ids, entry, id); + Array inspect = hl_inspect(id); + + // Note: internally we don't distinguish between cterm and rgb attributes, + // remote_ui_hl_attr_define will however. + ui_call_hl_attr_define(id, entry.attr, entry.attr, inspect); + api_free_array(inspect); return id; } +/// When a UI connects, we need to send it the table of higlights used so far. +void ui_send_all_hls(UI *ui) +{ + for (size_t i = 1; i < kv_size(attr_entries); i++) { + Array inspect = hl_inspect((int)i); + ui->hl_attr_define(ui, (Integer)i, kv_A(attr_entries, i).attr, + kv_A(attr_entries, i).attr, inspect); + api_free_array(inspect); + } +} + /// Get attribute code for a syntax group. int hl_get_syn_attr(int idx, HlAttrs at_en) { diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index 5f637228ba..09d20c75ea 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -8,6 +8,8 @@ typedef int32_t RgbValue; /// Highlighting attribute bits. +/// +/// sign bit should not be used here, as it identifies invalid highlight typedef enum { HL_INVERSE = 0x01, HL_BOLD = 0x02, @@ -35,6 +37,17 @@ typedef struct attr_entry { .cterm_bg_color = 0, \ } +// sentinel value that compares unequal to any valid highlight +#define HLATTRS_INVALID (HlAttrs) { \ + .rgb_ae_attr = -1, \ + .cterm_ae_attr = -1, \ + .rgb_fg_color = -1, \ + .rgb_bg_color = -1, \ + .rgb_sp_color = -1, \ + .cterm_fg_color = 0, \ + .cterm_bg_color = 0, \ +} + /// Values for index in highlight_attr[]. /// When making changes, also update hlf_names below! typedef enum { diff --git a/src/nvim/lib/kvec.h b/src/nvim/lib/kvec.h index 6d54c7f78d..93b2f053bc 100644 --- a/src/nvim/lib/kvec.h +++ b/src/nvim/lib/kvec.h @@ -98,14 +98,14 @@ (*kv_pushp(v) = (x)) #define kv_a(v, i) \ - (((v).capacity <= (size_t) (i) \ + (*(((v).capacity <= (size_t) (i) \ ? ((v).capacity = (v).size = (i) + 1, \ kv_roundup32((v).capacity), \ - kv_resize((v), (v).capacity), 0) \ + kv_resize((v), (v).capacity), 0UL) \ : ((v).size <= (size_t) (i) \ ? (v).size = (i) + 1 \ - : 0)), \ - (v).items[(i)]) + : 0UL)), \ + &(v).items[(i)])) /// Type of a vector with a few first members allocated on stack /// diff --git a/src/nvim/main.c b/src/nvim/main.c index 528fd921bc..96c2168bca 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -454,7 +454,6 @@ int main(int argc, char **argv) } setmouse(); // may start using the mouse - ui_reset_scroll_region(); // In case Rows changed if (exmode_active) { must_redraw = CLEAR; // Don't clear the screen when starting in Ex mode. @@ -1374,7 +1373,7 @@ static void handle_quickfix(mparm_T *paramp) paramp->use_ef, OPT_FREE, SID_CARG); vim_snprintf((char *)IObuff, IOSIZE, "cfile %s", p_ef); if (qf_init(NULL, p_ef, p_efm, true, IObuff, p_menc) < 0) { - ui_linefeed(); + msg_putchar('\n'); mch_exit(3); } TIME_MSG("reading errorfile"); diff --git a/src/nvim/message.c b/src/nvim/message.c index 9d4d421941..46fc9115b4 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -1888,11 +1888,9 @@ static void msg_scroll_up(void) fill_msgsep, fill_msgsep, HL_ATTR(HLF_MSGSEP)); } int nscroll = MIN(msg_scrollsize()+1, Rows); - ui_call_set_scroll_region(Rows-nscroll, Rows-1, 0, Columns-1); - screen_del_lines(Rows-nscroll, 0, 1, nscroll, NULL); - ui_reset_scroll_region(); + screen_del_lines(Rows-nscroll, 1, Rows, 0, Columns); } else { - screen_del_lines(0, 0, 1, (int)Rows, NULL); + screen_del_lines(0, 1, (int)Rows, 0, Columns); } } @@ -2307,9 +2305,9 @@ static int do_more_prompt(int typed_char) mp_last = msg_sb_start(mp_last->sb_prev); } - if (toscroll == -1 && screen_ins_lines(0, 0, 1, - (int)Rows, NULL) == OK) { - /* display line at top */ + if (toscroll == -1 + && screen_ins_lines(0, 1, (int)Rows, 0, (int)Columns) == OK) { + // display line at top (void)disp_sb_line(0, mp); } else { /* redisplay all lines */ diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index 3d7399f151..684f486c04 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -2714,7 +2714,7 @@ int call_shell(char_u *cmd, ShellOpts opts, char_u *extra_shell_arg) if (p_verbose > 3) { verbose_enter(); smsg(_("Calling shell to execute: \"%s\""), cmd == NULL ? p_sh : cmd); - ui_linefeed(); + msg_putchar('\n'); verbose_leave(); } diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index 1182d3d902..5bd4b4ddff 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -341,6 +341,8 @@ void pum_redraw(void) idx = i + pum_first; attr = (idx == pum_selected) ? attr_select : attr_norm; + screen_puts_line_start(row); + // prepend a space if there is room if (curwin->w_p_rl) { if (pum_col < curwin->w_wincol + curwin->w_width - 1) { @@ -488,6 +490,7 @@ void pum_redraw(void) ? attr_thumb : attr_scroll); } } + screen_puts_line_flush(false); row++; } } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 41a430bb8b..65a3c17286 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -300,7 +300,8 @@ void update_screen(int type) type = CLEAR; } else if (type != CLEAR) { check_for_delay(false); - if (screen_ins_lines(0, 0, msg_scrolled, (int)Rows, NULL) == FAIL) { + if (screen_ins_lines(0, msg_scrolled, (int)Rows, 0, (int)Columns) + == FAIL) { type = CLEAR; } FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { @@ -4314,13 +4315,14 @@ static void screen_line(int row, int coloff, int endcol, /* 2: occupies two display cells */ # define CHAR_CELLS char_cells + int start_dirty = -1, end_dirty = 0; + /* Check for illegal row and col, just in case. */ if (row >= Rows) row = Rows - 1; if (endcol > Columns) endcol = Columns; - off_from = (unsigned)(current_ScreenLine - ScreenLines); off_to = LineOffset[row] + coloff; max_off_from = off_from + screen_Columns; @@ -4368,6 +4370,10 @@ static void screen_line(int row, int coloff, int endcol, if (redraw_this) { + if (start_dirty == -1) { + start_dirty = col; + } + end_dirty = col + char_cells; // When writing a single-width character over a double-width // character and at the end of the redrawn text, need to clear out // the right halve of the old character. @@ -4388,12 +4394,11 @@ static void screen_line(int row, int coloff, int endcol, } ScreenAttrs[off_to] = ScreenAttrs[off_from]; - /* For simplicity set the attributes of second half of a - * double-wide character equal to the first half. */ - if (char_cells == 2) + // For simplicity set the attributes of second half of a + // double-wide character equal to the first half. + if (char_cells == 2) { ScreenAttrs[off_to + 1] = ScreenAttrs[off_from]; - - screen_char(off_to, row, col + coloff); + } } off_to += CHAR_CELLS; @@ -4405,23 +4410,29 @@ static void screen_line(int row, int coloff, int endcol, /* Clear the second half of a double-wide character of which the left * half was overwritten with a single-wide character. */ schar_from_ascii(ScreenLines[off_to], ' '); - screen_char(off_to, row, col + coloff); + end_dirty++; } + int clear_end = -1; if (clear_width > 0 && !rlflag) { // blank out the rest of the line - while (col < clear_width && ScreenLines[off_to][0] == ' ' - && ScreenLines[off_to][1] == NUL - && ScreenAttrs[off_to] == bg_attr - ) { - ++off_to; - ++col; - } - if (col < clear_width) { - screen_fill(row, row + 1, col + coloff, clear_width + coloff, ' ', ' ', - bg_attr); - off_to += clear_width - col; - col = clear_width; + // TODO(bfredl): we could cache winline widths + while (col < clear_width) { + if (ScreenLines[off_to][0] != ' ' || ScreenLines[off_to][1] != NUL + || ScreenAttrs[off_to] != bg_attr) { + ScreenLines[off_to][0] = ' '; + ScreenLines[off_to][1] = NUL; + ScreenAttrs[off_to] = bg_attr; + if (start_dirty == -1) { + start_dirty = col; + end_dirty = col; + } else if (clear_end == -1) { + end_dirty = endcol; + } + clear_end = col+1; + } + col++; + off_to++; } } @@ -4436,11 +4447,25 @@ static void screen_line(int row, int coloff, int endcol, || ScreenAttrs[off_to] != hl) { schar_copy(ScreenLines[off_to], sc); ScreenAttrs[off_to] = hl; - screen_char(off_to, row, col + coloff); + if (start_dirty == -1) { + start_dirty = col; + } + end_dirty = col+1; } } else LineWraps[row] = FALSE; } + + if (clear_end < end_dirty) { + clear_end = end_dirty; + } + if (start_dirty == -1) { + start_dirty = end_dirty; + } + if (clear_end > start_dirty) { + ui_line(row, coloff+start_dirty, coloff+end_dirty, coloff+clear_end, + bg_attr); + } } /* @@ -4722,11 +4747,11 @@ win_redr_status_matches ( /* Put the wildmenu just above the command line. If there is * no room, scroll the screen one line up. */ if (cmdline_row == Rows - 1) { - screen_del_lines(0, 0, 1, (int)Rows, NULL); - ++msg_scrolled; + screen_del_lines(0, 1, (int)Rows, 0, (int)Columns); + msg_scrolled++; } else { - ++cmdline_row; - ++row; + cmdline_row++; + row++; } wild_menu_showing = WM_SCROLLED; } else { @@ -5090,6 +5115,8 @@ win_redr_custom ( /* * Draw each snippet with the specified highlighting. */ + screen_puts_line_start(row); + curattr = attr; p = buf; for (n = 0; hltab[n].start != NULL; n++) { @@ -5110,6 +5137,8 @@ win_redr_custom ( // Make sure to use an empty string instead of p, if p is beyond buf + len. screen_puts(p >= buf + len ? (char_u *)"" : p, row, col, curattr); + screen_puts_line_flush(false); + if (wp == NULL) { // Fill the tab_page_click_defs array for clicking in the tab pages line. col = 0; @@ -5207,7 +5236,6 @@ void screen_getbytes(int row, int col, char_u *bytes, int *attrp) } } - /* * Put string '*text' on the screen at position 'row' and 'col', with * attributes 'attr', and update ScreenLines[] and ScreenAttrs[]. @@ -5219,6 +5247,20 @@ void screen_puts(char_u *text, int row, int col, int attr) screen_puts_len(text, -1, row, col, attr); } +static int put_dirty_row = -1; +static int put_dirty_first = -1; +static int put_dirty_last = 0; + +/// Start a group of screen_puts_len calls that builds a single screen line. +/// +/// Must be matched with a screen_puts_line_flush call before moving to +/// another line. +void screen_puts_line_start(int row) +{ + assert(put_dirty_row == -1); + put_dirty_row = row; +} + /* * Like screen_puts(), but output "text[len]". When "len" is -1 output up to * a NUL. @@ -5242,6 +5284,16 @@ void screen_puts_len(char_u *text, int textlen, int row, int col, int attr) int force_redraw_next = FALSE; int need_redraw; + bool do_flush = false; + if (put_dirty_row == -1) { + screen_puts_line_start(row); + do_flush = true; + } else { + if (row != put_dirty_row) { + abort(); + } + } + if (ScreenLines == NULL || row >= screen_Rows) /* safety check */ return; off = LineOffset[row] + col; @@ -5252,9 +5304,12 @@ void screen_puts_len(char_u *text, int textlen, int row, int col, int attr) schar_from_ascii(ScreenLines[off - 1], ' '); ScreenAttrs[off - 1] = 0; // redraw the previous cell, make it empty - screen_char(off - 1, row, col - 1); - /* force the cell at "col" to be redrawn */ - force_redraw_next = TRUE; + if (put_dirty_first == -1) { + put_dirty_first = col-1; + } + put_dirty_last = col+1; + // force the cell at "col" to be redrawn + force_redraw_next = true; } max_off = LineOffset[row] + screen_Columns; @@ -5333,8 +5388,12 @@ void screen_puts_len(char_u *text, int textlen, int row, int col, int attr) ScreenLines[off + 1][0] = 0; ScreenAttrs[off + 1] = attr; } - screen_char(off, row, col); + if (put_dirty_first == -1) { + put_dirty_first = col; + } + put_dirty_last = col+mbyte_cells; } + off += mbyte_cells; col += mbyte_cells; ptr += mbyte_blen; @@ -5345,11 +5404,29 @@ void screen_puts_len(char_u *text, int textlen, int row, int col, int attr) } } - /* If we detected the next character needs to be redrawn, but the text - * doesn't extend up to there, update the character here. */ - if (force_redraw_next && col < screen_Columns) { - screen_char(off, row, col); + if (do_flush) { + screen_puts_line_flush(true); + } +} + +/// End a group of screen_puts_len calls and send the screen buffer to the UI +/// layer. +/// +/// @param set_cursor Move the visible cursor to the end of the changed region. +/// This is a workaround for not yet refactored code paths +/// and shouldn't be used in new code. +void screen_puts_line_flush(bool set_cursor) +{ + assert(put_dirty_row != -1); + if (put_dirty_first != -1) { + if (set_cursor) { + ui_cursor_goto(put_dirty_row, put_dirty_last); + } + ui_line(put_dirty_row, put_dirty_first, put_dirty_last, put_dirty_last, 0); + put_dirty_first = -1; + put_dirty_last = 0; } + put_dirty_row = -1; } /* @@ -5641,32 +5718,6 @@ next_search_hl_pos( return 0; } -/* - * Put character ScreenLines["off"] on the screen at position "row" and "col", - * using the attributes from ScreenAttrs["off"]. - */ -static void screen_char(unsigned off, int row, int col) -{ - // Check for illegal values, just in case (could happen just after resizing). - if (row >= screen_Rows || col >= screen_Columns) { - return; - } - - // Outputting the last character on the screen may scrollup the screen. - // Don't to it! Mark the character invalid (update it when scrolled up) - // FIXME: The premise here is not actually true (cf. deferred wrap). - if (row == screen_Rows - 1 && col == screen_Columns - 1 - // account for first command-line character in rightleft mode - && !cmdmsg_rl) { - ScreenAttrs[off] = (sattr_T)-1; - return; - } - - ui_cursor_goto(row, col); - ui_set_highlight(ScreenAttrs[off]); - - ui_puts(ScreenLines[off]); -} /* * Fill the screen from 'start_row' to 'end_row', from 'start_col' to 'end_col' @@ -5675,12 +5726,6 @@ static void screen_char(unsigned off, int row, int col) */ void screen_fill(int start_row, int end_row, int start_col, int end_col, int c1, int c2, int attr) { - int row; - int col; - int off; - int end_off; - int did_delete; - int c; schar_T sc; if (end_row > screen_Rows) /* safety check */ @@ -5692,8 +5737,7 @@ void screen_fill(int start_row, int end_row, int start_col, int end_col, int c1, || start_col >= end_col) /* nothing to do */ return; - /* it's a "normal" terminal when not in a GUI or cterm */ - for (row = start_row; row < end_row; ++row) { + for (int row = start_row; row < end_row; row++) { if (has_mbyte) { // When drawing over the right halve of a double-wide char clear // out the left halve. When drawing over the left halve of a @@ -5706,71 +5750,52 @@ void screen_fill(int start_row, int end_row, int start_col, int end_col, int c1, screen_puts_len((char_u *)" ", 1, row, end_col, 0); } } - /* - * Try to use delete-line termcap code, when no attributes or in a - * "normal" terminal, where a bold/italic space is just a - * space. - */ - did_delete = FALSE; - if (c2 == ' ' - && end_col == Columns - && attr == 0) { - /* - * check if we really need to clear something - */ - col = start_col; - if (c1 != ' ') /* don't clear first char */ - ++col; - - off = LineOffset[row] + col; - end_off = LineOffset[row] + end_col; - - // skip blanks (used often, keep it fast!) - while (off < end_off && ScreenLines[off][0] == ' ' - && ScreenLines[off][1] == 0 && ScreenAttrs[off] == 0) { - off++; - } - if (off < end_off) { // something to be cleared - col = off - LineOffset[row]; - ui_clear_highlight(); - ui_cursor_goto(row, col); // clear rest of this screen line - ui_call_eol_clear(); - col = end_col - col; - while (col--) { // clear chars in ScreenLines - schar_from_ascii(ScreenLines[off], ' '); - ScreenAttrs[off] = 0; - ++off; - } - } - did_delete = TRUE; /* the chars are cleared now */ - } - off = LineOffset[row] + start_col; - c = c1; - schar_from_char(sc, c); + int dirty_first = INT_MAX; + int dirty_last = 0; + int col = start_col; + schar_from_char(sc, c1); + int lineoff = LineOffset[row]; for (col = start_col; col < end_col; col++) { + int off = lineoff + col; if (schar_cmp(ScreenLines[off], sc) || ScreenAttrs[off] != attr) { schar_copy(ScreenLines[off], sc); ScreenAttrs[off] = attr; - if (!did_delete || c != ' ') - screen_char(off, row, col); + if (dirty_first == INT_MAX) { + dirty_first = col; + } + dirty_last = col+1; } - ++off; if (col == start_col) { - if (did_delete) - break; - c = c2; - schar_from_char(sc, c); + schar_from_char(sc, c2); } } - if (end_col == Columns) - LineWraps[row] = FALSE; - if (row == Rows - 1) { /* overwritten the command line */ - redraw_cmdline = TRUE; - if (c1 == ' ' && c2 == ' ') - clear_cmdline = FALSE; /* command line has been cleared */ - if (start_col == 0) - mode_displayed = FALSE; /* mode cleared or overwritten */ + if (dirty_last > dirty_first) { + // TODO(bfredl): support a cleared suffix even with a batched line? + if (put_dirty_row == row) { + if (put_dirty_first == -1) { + put_dirty_first = dirty_first; + } + put_dirty_last = MAX(put_dirty_last, dirty_last); + } else { + int last = c2 != ' ' ? dirty_last : dirty_first + (c1 != ' '); + ui_line(row, dirty_first, last, dirty_last, attr); + } + } + + if (end_col == Columns) { + LineWraps[row] = false; + } + + // TODO(bfredl): The relevant caller should do this + if (row == Rows - 1) { // overwritten the command line + redraw_cmdline = true; + if (c1 == ' ' && c2 == ' ') { + clear_cmdline = false; // command line has been cleared + } + if (start_col == 0) { + mode_displayed = false; // mode cleared or overwritten + } } } } @@ -6027,15 +6052,13 @@ static void screenclear2(void) return; } - ui_clear_highlight(); // don't want highlighting here - /* blank out ScreenLines */ for (i = 0; i < Rows; ++i) { lineclear(LineOffset[i], (int)Columns); LineWraps[i] = FALSE; } - ui_call_clear(); // clear the display + ui_call_grid_clear(1); // clear the display clear_cmdline = false; mode_displayed = false; screen_cleared = true; // can use contents of ScreenLines now @@ -6064,18 +6087,16 @@ static void lineclear(unsigned off, int width) (void)memset(ScreenAttrs + off, 0, (size_t)width * sizeof(sattr_T)); } -/* - * Copy part of a Screenline for vertically split window "wp". - */ -static void linecopy(int to, int from, win_T *wp) +/// Copy part of a Screenline for vertically split window. +static void linecopy(int to, int from, int col, int width) { - const unsigned off_to = LineOffset[to] + wp->w_wincol; - const unsigned off_from = LineOffset[from] + wp->w_wincol; + unsigned off_to = LineOffset[to] + col; + unsigned off_from = LineOffset[from] + col; memmove(ScreenLines + off_to, ScreenLines + off_from, - wp->w_width * sizeof(schar_T)); + width * sizeof(schar_T)); memmove(ScreenAttrs + off_to, ScreenAttrs + off_from, - wp->w_width * sizeof(ScreenAttrs[0])); + width * sizeof(sattr_T)); } /* @@ -6153,15 +6174,16 @@ static int win_do_lines(win_T *wp, int row, int line_count, // otherwise it will stay there forever. clear_cmdline = TRUE; int retval; - ui_set_scroll_region(wp, row); + if (del) { - retval = screen_del_lines(wp->w_winrow + row, 0, line_count, - wp->w_height - row, wp); + retval = screen_del_lines(wp->w_winrow + row, line_count, + wp->w_winrow + wp->w_height, + wp->w_wincol, wp->w_width); } else { - retval = screen_ins_lines(wp->w_winrow + row, 0, line_count, - wp->w_height - row, wp); + retval = screen_ins_lines(wp->w_winrow + row, line_count, + wp->w_winrow + wp->w_height, + wp->w_wincol, wp->w_width); } - ui_reset_scroll_region(); return retval; } @@ -6189,19 +6211,13 @@ static void win_rest_invalid(win_T *wp) */ -// insert lines on the screen and update ScreenLines[] -// 'end' is the line after the scrolled part. Normally it is Rows. -// When scrolling region used 'off' is the offset from the top for the region. -// 'row' and 'end' are relative to the start of the region. -// -// return FAIL for failure, OK for success. -int screen_ins_lines ( - int off, - int row, - int line_count, - int end, - win_T *wp /* NULL or window to use width from */ -) +/// insert lines on the screen and update ScreenLines[] +/// 'end' is the line after the scrolled part. Normally it is Rows. +/// When scrolling region used 'off' is the offset from the top for the region. +/// 'row' and 'end' are relative to the start of the region. +/// +/// @return FAIL for failure, OK for success. +int screen_ins_lines(int row, int line_count, int end, int col, int width) { int i; int j; @@ -6213,18 +6229,16 @@ int screen_ins_lines ( // Shift LineOffset[] line_count down to reflect the inserted lines. // Clear the inserted lines in ScreenLines[]. - row += off; - end += off; - for (i = 0; i < line_count; ++i) { - if (wp != NULL && wp->w_width != Columns) { + for (i = 0; i < line_count; i++) { + if (width != Columns) { // need to copy part of a line j = end - 1 - i; while ((j -= line_count) >= row) { - linecopy(j + line_count, j, wp); + linecopy(j + line_count, j, col, width); } j += line_count; - lineclear(LineOffset[j] + wp->w_wincol, wp->w_width); - LineWraps[j] = FALSE; + lineclear(LineOffset[j] + col, width); + LineWraps[j] = false; } else { j = end - 1 - i; temp = LineOffset[j]; @@ -6233,29 +6247,23 @@ int screen_ins_lines ( LineWraps[j + line_count] = LineWraps[j]; } LineOffset[j + line_count] = temp; - LineWraps[j + line_count] = FALSE; + LineWraps[j + line_count] = false; lineclear(temp, (int)Columns); } } - ui_call_scroll(-line_count); + ui_call_grid_scroll(1, row, end, col, col+width, -line_count, 0); return OK; } -// delete lines on the screen and update ScreenLines[] -// 'end' is the line after the scrolled part. Normally it is Rows. -// When scrolling region used 'off' is the offset from the top for the region. -// 'row' and 'end' are relative to the start of the region. -// -// Return OK for success, FAIL if the lines are not deleted. -int screen_del_lines ( - int off, - int row, - int line_count, - int end, - win_T *wp /* NULL or window to use width from */ -) +/// delete lines on the screen and update ScreenLines[] +/// 'end' is the line after the scrolled part. Normally it is Rows. +/// When scrolling region used 'off' is the offset from the top for the region. +/// 'row' and 'end' are relative to the start of the region. +/// +/// Return OK for success, FAIL if the lines are not deleted. +int screen_del_lines(int row, int line_count, int end, int col, int width) { int j; int i; @@ -6267,18 +6275,16 @@ int screen_del_lines ( // Now shift LineOffset[] line_count up to reflect the deleted lines. // Clear the inserted lines in ScreenLines[]. - row += off; - end += off; - for (i = 0; i < line_count; ++i) { - if (wp != NULL && wp->w_width != Columns) { + for (i = 0; i < line_count; i++) { + if (width != Columns) { // need to copy part of a line j = row + i; while ((j += line_count) <= end - 1) { - linecopy(j - line_count, j, wp); + linecopy(j - line_count, j, col, width); } j -= line_count; - lineclear(LineOffset[j] + wp->w_wincol, wp->w_width); - LineWraps[j] = FALSE; + lineclear(LineOffset[j] + col, width); + LineWraps[j] = false; } else { // whole width, moving the line pointers is faster j = row + i; @@ -6288,16 +6294,17 @@ int screen_del_lines ( LineWraps[j - line_count] = LineWraps[j]; } LineOffset[j - line_count] = temp; - LineWraps[j - line_count] = FALSE; + LineWraps[j - line_count] = false; lineclear(temp, (int)Columns); } } - ui_call_scroll(line_count); + ui_call_grid_scroll(1, row, end, col, col+width, line_count, 0); return OK; } + /* * show the current mode and ruler * diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index ff9619574b..ff47e443f9 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -6798,14 +6798,12 @@ void do_highlight(const char *line, const bool forceit, const bool init) HL_TABLE()[idx].sg_cterm_fg = color + 1; if (is_normal_group) { cterm_normal_fg_color = color + 1; - must_redraw = CLEAR; } } else { HL_TABLE()[idx].sg_cterm_bg = color + 1; if (is_normal_group) { cterm_normal_bg_color = color + 1; if (!ui_rgb_attached()) { - must_redraw = CLEAR; if (color >= 0) { int dark = -1; @@ -6909,8 +6907,16 @@ void do_highlight(const char *line, const bool forceit, const bool init) // Need to update all groups, because they might be using "bg" and/or // "fg", which have been changed now. highlight_attr_set_all(); - // If the normal group has changed, it is simpler to refresh every UI - ui_refresh(); + + if (!ui_is_external(kUINewgrid)) { + // Older UIs assume that we clear the screen after normal group is + // changed + ui_refresh(); + } else { + // TUI and newer UIs will repaint the screen themselves. NOT_VALID + // redraw below will still handle usages of guibg=fg etc. + ui_default_colors_set(); + } } else { set_hl_attr(idx); } diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 03288a3325..56c47ed6cc 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -88,6 +88,7 @@ typedef struct { bool cont_received; UGrid grid; kvec_t(Rect) invalid_regions; + int row, col; int out_fd; bool scroll_region_is_full_screen; bool can_change_scroll_region; @@ -98,6 +99,8 @@ typedef struct { bool busy, is_invisible; bool cork, overflow; cursorentry_T cursor_shapes[SHAPE_IDX_COUNT]; + HlAttrs clear_attrs; + kvec_t(HlAttrs) attrs; HlAttrs print_attrs; bool default_attr; ModeShape showing_mode; @@ -126,10 +129,9 @@ UI *tui_start(void) { UI *ui = xcalloc(1, sizeof(UI)); // Freed by ui_bridge_stop(). ui->stop = tui_stop; - ui->resize = tui_resize; - ui->clear = tui_clear; - ui->eol_clear = tui_eol_clear; - ui->cursor_goto = tui_cursor_goto; + ui->grid_resize = tui_grid_resize; + ui->grid_clear = tui_grid_clear; + ui->grid_cursor_goto = tui_grid_cursor_goto; ui->mode_info_set = tui_mode_info_set; ui->update_menu = tui_update_menu; ui->busy_start = tui_busy_start; @@ -137,10 +139,8 @@ UI *tui_start(void) ui->mouse_on = tui_mouse_on; ui->mouse_off = tui_mouse_off; ui->mode_change = tui_mode_change; - ui->set_scroll_region = tui_set_scroll_region; - ui->scroll = tui_scroll; - ui->highlight_set = tui_highlight_set; - ui->put = tui_put; + ui->grid_scroll = tui_grid_scroll; + ui->hl_attr_define = tui_hl_attr_define; ui->bell = tui_bell; ui->visual_bell = tui_visual_bell; ui->default_colors_set = tui_default_colors_set; @@ -149,8 +149,10 @@ UI *tui_start(void) ui->set_title = tui_set_title; ui->set_icon = tui_set_icon; ui->option_set= tui_option_set; + ui->raw_line = tui_raw_line; memset(ui->ui_ext, 0, sizeof(ui->ui_ext)); + ui->ui_ext[kUINewgrid] = true; return ui_bridge_attach(ui, tui_main, tui_scheduler); } @@ -290,7 +292,7 @@ static void terminfo_stop(UI *ui) static void tui_terminal_start(UI *ui) { TUIData *data = ui->data; - data->print_attrs = HLATTRS_INIT; + data->print_attrs = HLATTRS_INVALID; ugrid_init(&data->grid); terminfo_start(ui); update_size(ui); @@ -346,6 +348,9 @@ static void tui_main(UIBridgeData *bridge, UI *ui) signal_watcher_start(&data->cont_handle, sigcont_cb, SIGCONT); #endif + // TODO(bfredl): zero hl is empty, send this explicitly? + kv_push(data->attrs, HLATTRS_INIT); + #if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18 data->input.tk_ti_hook_fn = tui_tk_ti_getstr; #endif @@ -380,6 +385,7 @@ static void tui_main(UIBridgeData *bridge, UI *ui) signal_watcher_close(&data->winch_handle, NULL); loop_close(&tui_loop, false); kv_destroy(data->invalid_regions); + kv_destroy(data->attrs); xfree(data); } @@ -438,18 +444,17 @@ static void update_attrs(UI *ui, HlAttrs attrs) } data->print_attrs = attrs; - UGrid *grid = &data->grid; int fg = ui->rgb ? attrs.rgb_fg_color : (attrs.cterm_fg_color - 1); if (fg == -1) { - fg = ui->rgb ? grid->clear_attrs.rgb_fg_color - : (grid->clear_attrs.cterm_fg_color - 1); + fg = ui->rgb ? data->clear_attrs.rgb_fg_color + : (data->clear_attrs.cterm_fg_color - 1); } int bg = ui->rgb ? attrs.rgb_bg_color : (attrs.cterm_bg_color - 1); if (bg == -1) { - bg = ui->rgb ? grid->clear_attrs.rgb_bg_color - : (grid->clear_attrs.cterm_bg_color - 1); + bg = ui->rgb ? data->clear_attrs.rgb_bg_color + : (data->clear_attrs.cterm_bg_color - 1); } int attr = ui->rgb ? attrs.rgb_ae_attr : attrs.cterm_ae_attr; @@ -592,6 +597,8 @@ static void cursor_goto(UI *ui, int row, int col) if (row == grid->row && col == grid->col) { return; } + grid->row = row; + grid->col = col; if (0 == row && 0 == col) { unibi_out(ui, unibi_cursor_home); ugrid_goto(grid, row, col); @@ -679,20 +686,20 @@ static void cursor_goto(UI *ui, int row, int col) ugrid_goto(grid, row, col); } -static void clear_region(UI *ui, int top, int bot, int left, int right) +static void clear_region(UI *ui, int top, int bot, int left, int right, + HlAttrs attrs) { TUIData *data = ui->data; UGrid *grid = &data->grid; - int saved_row = grid->row; - int saved_col = grid->col; bool cleared = false; - bool nobg = ui->rgb ? grid->clear_attrs.rgb_bg_color == -1 - : grid->clear_attrs.cterm_bg_color == 0; + // TODO(bfredl): support BCE for non-default background + bool nobg = ui->rgb ? attrs.rgb_bg_color == -1 + : attrs.cterm_bg_color == 0; if (nobg && right == ui->width -1) { // Background is set to the default color and the right edge matches the // screen end, try to use terminal codes for clearing the requested area. - update_attrs(ui, grid->clear_attrs); + update_attrs(ui, attrs); if (left == 0) { if (bot == ui->height - 1) { if (top == 0) { @@ -725,7 +732,7 @@ static void clear_region(UI *ui, int top, int bot, int left, int right) } // restore cursor - cursor_goto(ui, saved_row, saved_col); + cursor_goto(ui, data->row, data->col); } static bool can_use_scroll(UI * ui) @@ -792,7 +799,7 @@ static void reset_scroll_region(UI *ui) unibi_goto(ui, grid->row, grid->col); } -static void tui_resize(UI *ui, Integer width, Integer height) +static void tui_grid_resize(UI *ui, Integer g, Integer width, Integer height) { TUIData *data = ui->data; ugrid_resize(&data->grid, (int)width, (int)height); @@ -810,25 +817,21 @@ static void tui_resize(UI *ui, Integer width, Integer height) } } -static void tui_clear(UI *ui) +static void tui_grid_clear(UI *ui, Integer g) { TUIData *data = ui->data; UGrid *grid = &data->grid; ugrid_clear(grid); kv_size(data->invalid_regions) = 0; - clear_region(ui, grid->top, grid->bot, grid->left, grid->right); + clear_region(ui, grid->top, grid->bot, grid->left, grid->right, + data->clear_attrs); } -static void tui_eol_clear(UI *ui) +static void tui_grid_cursor_goto(UI *ui, Integer grid, Integer row, Integer col) { TUIData *data = ui->data; - UGrid *grid = &data->grid; - ugrid_eol_clear(grid); - clear_region(ui, grid->row, grid->row, grid->col, grid->right); -} - -static void tui_cursor_goto(UI *ui, Integer row, Integer col) -{ + data->row = (int)row; + data->col = (int)col; cursor_goto(ui, (int)row, (int)col); } @@ -958,27 +961,23 @@ static void tui_mode_change(UI *ui, String mode, Integer mode_idx) data->showing_mode = (ModeShape)mode_idx; } -static void tui_set_scroll_region(UI *ui, Integer top, Integer bot, - Integer left, Integer right) +static void tui_grid_scroll(UI *ui, Integer g, Integer top, Integer bot, + Integer left, Integer right, + Integer rows, Integer cols) { TUIData *data = ui->data; - ugrid_set_scroll_region(&data->grid, (int)top, (int)bot, - (int)left, (int)right); + UGrid *grid = &data->grid; + ugrid_set_scroll_region(&data->grid, (int)top, (int)bot-1, + (int)left, (int)right-1); + data->scroll_region_is_full_screen = - left == 0 && right == ui->width - 1 - && top == 0 && bot == ui->height - 1; -} + left == 0 && right == ui->width + && top == 0 && bot == ui->height; -static void tui_scroll(UI *ui, Integer count) -{ - TUIData *data = ui->data; - UGrid *grid = &data->grid; int clear_top, clear_bot; - ugrid_scroll(grid, (int)count, &clear_top, &clear_bot); + ugrid_scroll(grid, (int)rows, &clear_top, &clear_bot); if (can_use_scroll(ui)) { - int saved_row = grid->row; - int saved_col = grid->col; bool scroll_clears_to_current_colour = unibi_get_bool(data->ut, unibi_back_color_erase); @@ -989,21 +988,21 @@ static void tui_scroll(UI *ui, Integer count) cursor_goto(ui, grid->top, grid->left); // also set default color attributes or some terminals can become funny if (scroll_clears_to_current_colour) { - update_attrs(ui, grid->clear_attrs); + update_attrs(ui, data->clear_attrs); } - if (count > 0) { - if (count == 1) { + if (rows > 0) { + if (rows == 1) { unibi_out(ui, unibi_delete_line); } else { - UNIBI_SET_NUM_VAR(data->params[0], (int)count); + UNIBI_SET_NUM_VAR(data->params[0], (int)rows); unibi_out(ui, unibi_parm_delete_line); } } else { - if (count == -1) { + if (rows == -1) { unibi_out(ui, unibi_insert_line); } else { - UNIBI_SET_NUM_VAR(data->params[0], -(int)count); + UNIBI_SET_NUM_VAR(data->params[0], -(int)rows); unibi_out(ui, unibi_parm_insert_line); } } @@ -1012,12 +1011,13 @@ static void tui_scroll(UI *ui, Integer count) if (!data->scroll_region_is_full_screen) { reset_scroll_region(ui); } - cursor_goto(ui, saved_row, saved_col); + cursor_goto(ui, data->row, data->col); if (!scroll_clears_to_current_colour) { // Scrolling will leave wrong background in the cleared area on non-BCE // terminals. Update the cleared area. - clear_region(ui, clear_top, clear_bot, grid->left, grid->right); + clear_region(ui, clear_top, clear_bot, grid->left, grid->right, + data->clear_attrs); } } else { // Mark the entire scroll region as invalid for redrawing later @@ -1025,23 +1025,11 @@ static void tui_scroll(UI *ui, Integer count) } } -static void tui_highlight_set(UI *ui, HlAttrs attrs) -{ - ((TUIData *)ui->data)->grid.attrs = attrs; -} - -static void tui_put(UI *ui, String text) +static void tui_hl_attr_define(UI *ui, Integer id, HlAttrs attrs, + HlAttrs cterm_attrs, Array info) { TUIData *data = ui->data; - UGrid *grid = &data->grid; - UCell *cell; - - cell = ugrid_put(&data->grid, (uint8_t *)text.data, text.size); - // ugrid_put does not advance the cursor correctly, as the actual terminal - // will when we print. Its cursor motion model is simplistic and wrong. So - // we have to undo what it has just done before doing it right. - grid->col--; - print_cell(ui, cell); + kv_a(data->attrs, (size_t)id) = attrs; } static void tui_bell(UI *ui) @@ -1058,12 +1046,16 @@ static void tui_default_colors_set(UI *ui, Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, Integer cterm_fg, Integer cterm_bg) { - UGrid *grid = &((TUIData *)ui->data)->grid; - grid->clear_attrs.rgb_fg_color = (int)rgb_fg; - grid->clear_attrs.rgb_bg_color = (int)rgb_bg; - grid->clear_attrs.rgb_sp_color = (int)rgb_sp; - grid->clear_attrs.cterm_fg_color = (int)cterm_fg; - grid->clear_attrs.cterm_bg_color = (int)cterm_bg; + TUIData *data = ui->data; + + data->clear_attrs.rgb_fg_color = (int)rgb_fg; + data->clear_attrs.rgb_bg_color = (int)rgb_bg; + data->clear_attrs.rgb_sp_color = (int)rgb_sp; + data->clear_attrs.cterm_fg_color = (int)cterm_fg; + data->clear_attrs.cterm_bg_color = (int)cterm_bg; + + data->print_attrs = HLATTRS_INVALID; + invalidate(ui, 0, data->grid.height-1, 0, data->grid.width-1); } static void tui_flush(UI *ui) @@ -1083,9 +1075,6 @@ static void tui_flush(UI *ui) tui_busy_stop(ui); // avoid hidden cursor } - int saved_row = grid->row; - int saved_col = grid->col; - while (kv_size(data->invalid_regions)) { Rect r = kv_pop(data->invalid_regions); assert(r.bot < grid->height && r.right < grid->width); @@ -1095,7 +1084,7 @@ static void tui_flush(UI *ui) }); } - cursor_goto(ui, saved_row, saved_col); + cursor_goto(ui, data->row, data->col); flush_buf(ui); } @@ -1176,10 +1165,37 @@ static void tui_option_set(UI *ui, String name, Object value) TUIData *data = ui->data; if (strequal(name.data, "termguicolors")) { ui->rgb = value.data.boolean; + + data->print_attrs = HLATTRS_INVALID; invalidate(ui, 0, data->grid.height-1, 0, data->grid.width-1); } } +static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol, + Integer endcol, Integer clearcol, Integer clearattr, + const schar_T *chunk, const sattr_T *attrs) +{ + TUIData *data = ui->data; + UGrid *grid = &data->grid; + for (Integer c = startcol; c < endcol; c++) { + memcpy(grid->cells[linerow][c].data, chunk[c-startcol], sizeof(schar_T)); + grid->cells[linerow][c].attrs = kv_A(data->attrs, attrs[c-startcol]); + } + UGRID_FOREACH_CELL(grid, (int)linerow, (int)linerow, (int)startcol, + (int)endcol-1, { + cursor_goto(ui, row, col); + print_cell(ui, cell); + }); + + if (clearcol > endcol) { + HlAttrs cl_attrs = kv_A(data->attrs, (size_t)clearattr); + ugrid_clear_chunk(grid, (int)linerow, (int)endcol, (int)clearcol, + cl_attrs); + clear_region(ui, (int)linerow, (int)linerow, (int)endcol, (int)clearcol-1, + cl_attrs); + } +} + static void invalidate(UI *ui, int top, int bot, int left, int right) { TUIData *data = ui->data; diff --git a/src/nvim/ugrid.c b/src/nvim/ugrid.c index 6d420ef2f8..48f3cff2d7 100644 --- a/src/nvim/ugrid.c +++ b/src/nvim/ugrid.c @@ -17,7 +17,6 @@ void ugrid_init(UGrid *grid) { grid->attrs = HLATTRS_INIT; - grid->clear_attrs = HLATTRS_INIT; grid->cells = NULL; } @@ -45,12 +44,13 @@ void ugrid_resize(UGrid *grid, int width, int height) void ugrid_clear(UGrid *grid) { - clear_region(grid, grid->top, grid->bot, grid->left, grid->right); + clear_region(grid, grid->top, grid->bot, grid->left, grid->right, + HLATTRS_INIT); } -void ugrid_eol_clear(UGrid *grid) +void ugrid_clear_chunk(UGrid *grid, int row, int col, int endcol, HlAttrs attrs) { - clear_region(grid, grid->row, grid->row, grid->col, grid->right); + clear_region(grid, row, row, col, endcol-1, attrs); } void ugrid_goto(UGrid *grid, int row, int col) @@ -99,7 +99,8 @@ void ugrid_scroll(UGrid *grid, int count, int *clear_top, int *clear_bot) *clear_bot = stop; *clear_top = stop + count + 1; } - clear_region(grid, *clear_top, *clear_bot, grid->left, grid->right); + clear_region(grid, *clear_top, *clear_bot, grid->left, grid->right, + HLATTRS_INIT); } UCell *ugrid_put(UGrid *grid, uint8_t *text, size_t size) @@ -117,13 +118,13 @@ UCell *ugrid_put(UGrid *grid, uint8_t *text, size_t size) return cell; } -static void clear_region(UGrid *grid, int top, int bot, int left, int right) +static void clear_region(UGrid *grid, int top, int bot, int left, int right, + HlAttrs attrs) { - HlAttrs clear_attrs = grid->clear_attrs; UGRID_FOREACH_CELL(grid, top, bot, left, right, { cell->data[0] = ' '; cell->data[1] = 0; - cell->attrs = clear_attrs; + cell->attrs = attrs; }); } diff --git a/src/nvim/ugrid.h b/src/nvim/ugrid.h index 035074846e..04e027bd46 100644 --- a/src/nvim/ugrid.h +++ b/src/nvim/ugrid.h @@ -7,7 +7,7 @@ typedef struct ucell UCell; typedef struct ugrid UGrid; -#define CELLBYTES (4 * (MAX_MCO+1)) +#define CELLBYTES (sizeof(schar_T)) struct ucell { char data[CELLBYTES + 1]; @@ -17,7 +17,6 @@ struct ucell { struct ugrid { int top, bot, left, right; int row, col; - HlAttrs clear_attrs; int width, height; HlAttrs attrs; UCell **cells; diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 8419951079..ef68b804ba 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -52,14 +52,10 @@ static UI *uis[MAX_UI_COUNT]; static bool ui_ext[kUIExtCount] = { 0 }; static size_t ui_count = 0; static int row = 0, col = 0; -static struct { - int top, bot, left, right; -} sr; -static int current_attr_code = -1; static bool pending_cursor_update = false; static int busy = 0; -static int height, width; -static int old_mode_idx = -1; +static int mode_idx = SHAPE_IDX_N; +static bool pending_mode_update = false; #if MIN_LOG_LEVEL > DEBUG_LOG_LEVEL # define UI_LOG(funname, ...) @@ -89,7 +85,6 @@ static char uilog_last_event[1024] = { 0 }; #ifdef _MSC_VER # define UI_CALL(funname, ...) \ do { \ - flush_cursor_update(); \ UI_LOG(funname, 0); \ for (size_t i = 0; i < ui_count; i++) { \ UI *ui = uis[i]; \ @@ -99,7 +94,6 @@ static char uilog_last_event[1024] = { 0 }; #else # define UI_CALL(...) \ do { \ - flush_cursor_update(); \ UI_LOG(__VA_ARGS__, 0); \ for (size_t i = 0; i < ui_count; i++) { \ UI *ui = uis[i]; \ @@ -108,8 +102,8 @@ static char uilog_last_event[1024] = { 0 }; } while (0) #endif #define CNT(...) SELECT_NTH(__VA_ARGS__, MORE, MORE, MORE, \ - MORE, MORE, ZERO, ignore) -#define SELECT_NTH(a1, a2, a3, a4, a5, a6, a7, ...) a7 + MORE, MORE, MORE, MORE, MORE, ZERO, ignore) +#define SELECT_NTH(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, ...) a10 #define UI_CALL_HELPER(c, ...) UI_CALL_HELPER2(c, __VA_ARGS__) // Resolves to UI_CALL_MORE or UI_CALL_ZERO. #define UI_CALL_HELPER2(c, ...) UI_CALL_##c(__VA_ARGS__) @@ -199,6 +193,9 @@ void ui_refresh(void) } row = col = 0; + pending_cursor_update = true; + + ui_default_colors_set(); int save_p_lz = p_lz; p_lz = false; // convince redrawing() to return true ... @@ -207,13 +204,14 @@ void ui_refresh(void) for (UIExtension i = 0; (int)i < kUIExtCount; i++) { ui_ext[i] = ext_widgets[i]; - ui_call_option_set(cstr_as_string((char *)ui_ext_names[i]), - BOOLEAN_OBJ(ext_widgets[i])); + if (i < kUIGlobalCount) { + ui_call_option_set(cstr_as_string((char *)ui_ext_names[i]), + BOOLEAN_OBJ(ext_widgets[i])); + } } ui_mode_info_set(); - old_mode_idx = -1; + pending_mode_update = true; ui_cursor_shape(); - current_attr_code = -1; } static void ui_refresh_event(void **argv) @@ -226,25 +224,15 @@ void ui_schedule_refresh(void) loop_schedule(&main_loop, event_create(ui_refresh_event, 0)); } -void ui_resize(int new_width, int new_height) +void ui_resize(int width, int height) { - width = new_width; - height = new_height; + ui_call_grid_resize(1, width, height); +} - // TODO(bfredl): update default colors when they changed, NOT on resize. +void ui_default_colors_set(void) +{ ui_call_default_colors_set(normal_fg, normal_bg, normal_sp, cterm_normal_fg_color, cterm_normal_bg_color); - - // Deprecated: - 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)); - UI_CALL(update_sp, (ui->rgb ? normal_sp : -1)); - - sr.top = 0; - sr.bot = height - 1; - sr.left = 0; - sr.right = width - 1; - ui_call_resize(width, height); } void ui_busy_start(void) @@ -269,6 +257,18 @@ void ui_attach_impl(UI *ui) uis[ui_count++] = ui; ui_refresh_options(); + + for (UIExtension i = kUIGlobalCount; (int)i < kUIExtCount; i++) { + ui_set_ext_option(ui, i, ui->ui_ext[i]); + } + + bool sent = false; + if (ui->ui_ext[kUIHlState]) { + sent = highlight_use_hlstate(); + } + if (!sent) { + ui_send_all_hls(ui); + } ui_refresh(); } @@ -302,97 +302,34 @@ void ui_detach_impl(UI *ui) } } -// Set scrolling region for window 'wp'. -// The region starts 'off' lines from the start of the window. -// Also set the vertical scroll region for a vertically split window. Always -// the full width of the window, excluding the vertical separator. -void ui_set_scroll_region(win_T *wp, int off) -{ - sr.top = wp->w_winrow + off; - sr.bot = wp->w_winrow + wp->w_height - 1; - - if (wp->w_width != Columns) { - sr.left = wp->w_wincol; - sr.right = wp->w_wincol + wp->w_width - 1; - } - - ui_call_set_scroll_region(sr.top, sr.bot, sr.left, sr.right); -} - -// Reset scrolling region to the whole screen. -void ui_reset_scroll_region(void) -{ - sr.top = 0; - sr.bot = (int)Rows - 1; - sr.left = 0; - sr.right = (int)Columns - 1; - ui_call_set_scroll_region(sr.top, sr.bot, sr.left, sr.right); -} - -void ui_set_highlight(int attr_code) +void ui_set_ext_option(UI *ui, UIExtension ext, bool active) { - if (current_attr_code == attr_code) { + if (ext < kUIGlobalCount) { + ui_refresh(); return; } - current_attr_code = attr_code; - - HlAttrs attrs = HLATTRS_INIT; - - if (attr_code != 0) { - HlAttrs *aep = syn_attr2entry(attr_code); - if (aep) { - attrs = *aep; - } + if (ui->option_set) { + ui->option_set(ui, cstr_as_string((char *)ui_ext_names[ext]), + BOOLEAN_OBJ(active)); } - - UI_CALL(highlight_set, attrs); -} - -void ui_clear_highlight(void) -{ - ui_set_highlight(0); } -void ui_puts(uint8_t *str) +void ui_line(int row, int startcol, int endcol, int clearcol, int clearattr) { - uint8_t *p = str; - uint8_t c; - - while ((c = *p)) { - if (c < 0x20) { - abort(); - } - - size_t clen = (size_t)mb_ptr2len(p); - ui_call_put((String){ .data = (char *)p, .size = clen }); - col++; - if (mb_ptr2cells(p) > 1) { - // double cell character, blank the next cell - ui_call_put((String)STRING_INIT); - col++; - } - if (utf_ambiguous_width(utf_ptr2char(p))) { - pending_cursor_update = true; - } - if (col >= width) { - ui_linefeed(); - } - p += clen; - - if (p_wd) { // 'writedelay': flush & delay each time. - ui_flush(); - uint64_t wd = (uint64_t)labs(p_wd); - os_microdelay(wd * 1000u, true); - } + size_t off = LineOffset[row]+(size_t)startcol; + UI_CALL(raw_line, 1, row, startcol, endcol, clearcol, clearattr, + (const schar_T *)ScreenLines+off, (const sattr_T *)ScreenAttrs+off); + if (p_wd) { // 'writedelay': flush & delay each time. + int old_row = row, old_col = col; + // If'writedelay is active, we set the cursor to highlight what was drawn + ui_cursor_goto(row, MIN(clearcol, (int)Columns-1)); + ui_flush(); + uint64_t wd = (uint64_t)labs(p_wd); + os_microdelay(wd * 1000u, true); + ui_cursor_goto(old_row, old_col); } } -void ui_putc(uint8_t c) -{ - uint8_t buf[2] = {c, 0}; - ui_puts(buf); -} - void ui_cursor_goto(int new_row, int new_col) { if (new_row == row && new_col == col) { @@ -450,30 +387,19 @@ int ui_current_col(void) void ui_flush(void) { cmdline_ui_flush(); - ui_call_flush(); -} - - -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 flush_cursor_update(void) -{ if (pending_cursor_update) { + ui_call_grid_cursor_goto(1, row, col); pending_cursor_update = false; - ui_call_cursor_goto(row, col); } + if (pending_mode_update) { + char *full_name = shape_table[mode_idx].full_name; + ui_call_mode_change(cstr_as_string(full_name), mode_idx); + pending_mode_update = false; + } + ui_call_flush(); } + /// Check if current mode has changed. /// May update the shape of the cursor. void ui_cursor_shape(void) @@ -481,12 +407,11 @@ void ui_cursor_shape(void) if (!full_screen) { return; } - int mode_idx = cursor_get_mode_idx(); + int new_mode_idx = cursor_get_mode_idx(); - if (old_mode_idx != mode_idx) { - old_mode_idx = mode_idx; - char *full_name = shape_table[mode_idx].full_name; - ui_call_mode_change(cstr_as_string(full_name), mode_idx); + if (new_mode_idx != mode_idx) { + mode_idx = new_mode_idx; + pending_mode_update = true; } conceal_check_cursur_line(); } diff --git a/src/nvim/ui.h b/src/nvim/ui.h index 9e98ff9f15..584d8a77c6 100644 --- a/src/nvim/ui.h +++ b/src/nvim/ui.h @@ -5,13 +5,18 @@ #include <stdbool.h> #include <stdint.h> -#include "api/private/defs.h" +#include "nvim/globals.h" +#include "nvim/api/private/defs.h" +#include "nvim/highlight_defs.h" typedef enum { kUICmdline = 0, kUIPopupmenu, kUITabline, kUIWildmenu, +#define kUIGlobalCount (kUIWildmenu+1) + kUINewgrid, + kUIHlState, kUIExtCount, } UIExtension; @@ -19,7 +24,9 @@ EXTERN const char *ui_ext_names[] INIT(= { "ext_cmdline", "ext_popupmenu", "ext_tabline", - "ext_wildmenu" + "ext_wildmenu", + "ext_newgrid", + "ext_hlstate", }); @@ -30,9 +37,17 @@ struct ui_t { bool ui_ext[kUIExtCount]; ///< Externalized widgets int width, height; void *data; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ui_events.generated.h" #endif + + // For perfomance and simplicity, we use the dense screen representation + // in the bridge and the TUI. The remote_ui module will translate this + // in to the public grid_line format. + void (*raw_line)(UI *ui, Integer grid, Integer row, Integer startcol, + Integer endcol, Integer clearcol, Integer clearattr, + const schar_T *chunk, const sattr_T *attrs); void (*event)(UI *ui, char *name, Array args, bool *args_consumed); void (*stop)(UI *ui); void (*inspect)(UI *ui, Dictionary *info); diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c index 56db124a46..a96a24bde7 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -42,10 +42,9 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler) rv->ui = ui; rv->bridge.rgb = ui->rgb; rv->bridge.stop = ui_bridge_stop; - rv->bridge.resize = ui_bridge_resize; - rv->bridge.clear = ui_bridge_clear; - rv->bridge.eol_clear = ui_bridge_eol_clear; - rv->bridge.cursor_goto = ui_bridge_cursor_goto; + rv->bridge.grid_resize = ui_bridge_grid_resize; + rv->bridge.grid_clear = ui_bridge_grid_clear; + rv->bridge.grid_cursor_goto = ui_bridge_grid_cursor_goto; rv->bridge.mode_info_set = ui_bridge_mode_info_set; rv->bridge.update_menu = ui_bridge_update_menu; rv->bridge.busy_start = ui_bridge_busy_start; @@ -53,10 +52,8 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler) rv->bridge.mouse_on = ui_bridge_mouse_on; rv->bridge.mouse_off = ui_bridge_mouse_off; rv->bridge.mode_change = ui_bridge_mode_change; - rv->bridge.set_scroll_region = ui_bridge_set_scroll_region; - rv->bridge.scroll = ui_bridge_scroll; - rv->bridge.highlight_set = ui_bridge_highlight_set; - rv->bridge.put = ui_bridge_put; + rv->bridge.grid_scroll = ui_bridge_grid_scroll; + rv->bridge.hl_attr_define = ui_bridge_hl_attr_define; rv->bridge.bell = ui_bridge_bell; rv->bridge.visual_bell = ui_bridge_visual_bell; rv->bridge.default_colors_set = ui_bridge_default_colors_set; @@ -65,6 +62,7 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler) rv->bridge.set_title = ui_bridge_set_title; rv->bridge.set_icon = ui_bridge_set_icon; rv->bridge.option_set = ui_bridge_option_set; + rv->bridge.raw_line = ui_bridge_raw_line; rv->scheduler = scheduler; for (UIExtension i = 0; (int)i < kUIExtCount; i++) { @@ -133,19 +131,45 @@ static void ui_bridge_stop_event(void **argv) ui->stop(ui); } -static void ui_bridge_highlight_set(UI *b, HlAttrs attrs) +static void ui_bridge_hl_attr_define(UI *ui, Integer id, HlAttrs attrs, + HlAttrs cterm_attrs, Array info) { HlAttrs *a = xmalloc(sizeof(HlAttrs)); *a = attrs; - UI_BRIDGE_CALL(b, highlight_set, 2, b, a); + UI_BRIDGE_CALL(ui, hl_attr_define, 3, ui, INT2PTR(id), a); } -static void ui_bridge_highlight_set_event(void **argv) +static void ui_bridge_hl_attr_define_event(void **argv) { UI *ui = UI(argv[0]); - ui->highlight_set(ui, *((HlAttrs *)argv[1])); - xfree(argv[1]); + Array info = ARRAY_DICT_INIT; + ui->hl_attr_define(ui, PTR2INT(argv[1]), *((HlAttrs *)argv[2]), + *((HlAttrs *)argv[2]), info); + xfree(argv[2]); } +static void ui_bridge_raw_line_event(void **argv) +{ + UI *ui = UI(argv[0]); + ui->raw_line(ui, PTR2INT(argv[1]), PTR2INT(argv[2]), PTR2INT(argv[3]), + PTR2INT(argv[4]), PTR2INT(argv[5]), PTR2INT(argv[6]), + argv[7], argv[8]); + xfree(argv[7]); + xfree(argv[8]); +} +static void ui_bridge_raw_line(UI *ui, Integer grid, Integer row, + Integer startcol, Integer endcol, + Integer clearcol, Integer clearattr, + const schar_T *chunk, const sattr_T *attrs) +{ + size_t ncol = (size_t)(endcol-startcol); + schar_T *c = xmemdup(chunk, ncol * sizeof(schar_T)); + sattr_T *hl = xmemdup(attrs, ncol * sizeof(sattr_T)); + UI_BRIDGE_CALL(ui, raw_line, 9, ui, INT2PTR(grid), INT2PTR(row), + INT2PTR(startcol), INT2PTR(endcol), INT2PTR(clearcol), + INT2PTR(clearattr), c, hl); +} + + static void ui_bridge_suspend(UI *b) { UIBridgeData *data = (UIBridgeData *)b; diff --git a/test/functional/api/version_spec.lua b/test/functional/api/version_spec.lua index 7bf54c0d1e..e0472977cc 100644 --- a/test/functional/api/version_spec.lua +++ b/test/functional/api/version_spec.lua @@ -156,6 +156,6 @@ describe("ui_options in metadata", function() local api = helpers.call('api_info') local options = api.ui_options eq({'rgb', 'ext_cmdline', 'ext_popupmenu', - 'ext_tabline', 'ext_wildmenu'}, options) + 'ext_tabline', 'ext_wildmenu', 'ext_newgrid', 'ext_hlstate'}, options) end) end) diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index e4b343c123..ae8a8488d4 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -1242,6 +1242,8 @@ describe('API', function() ext_popupmenu = false, ext_tabline = false, ext_wildmenu = false, + ext_newgrid = screen._options.ext_newgrid or false, + ext_hlstate=false, height = 4, rgb = true, width = 20, @@ -1252,18 +1254,9 @@ describe('API', function() screen:detach() screen = Screen.new(44, 99) screen:attach({ rgb = false }) - expected = { - { - chan = 1, - ext_cmdline = false, - ext_popupmenu = false, - ext_tabline = false, - ext_wildmenu = false, - height = 99, - rgb = false, - width = 44, - } - } + expected[1].rgb = false + expected[1].width = 44 + expected[1].height = 99 eq(expected, nvim("list_uis")) end) end) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 5603224975..c0404ff463 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -207,7 +207,7 @@ describe('tui', function() screen:set_default_attr_ids({ [1] = {reverse = true}, [2] = {foreground = 13, special = Screen.colors.Grey0}, - [3] = {special = Screen.colors.Grey0, bold = true, reverse = true}, + [3] = {bold = true, reverse = true, special = Screen.colors.Grey0}, [4] = {bold = true}, [5] = {special = Screen.colors.Grey0, reverse = true, foreground = 4}, [6] = {foreground = 4, special = Screen.colors.Grey0}, @@ -257,11 +257,11 @@ describe('tui', function() it('shows up in nvim_list_uis', function() feed_data(':echo map(nvim_list_uis(), {k,v -> sort(items(v))})\013') screen:expect([=[ - {5: }| - [[['ext_cmdline', v:false], ['ext_popupmenu', v:fa| - lse], ['ext_tabline', v:false], ['ext_wildmenu', v| - :false], ['height', 6], ['rgb', v:false], ['width'| - , 50]]] | + [[['ext_cmdline', v:false], ['ext_hlstate', v:fals| + e], ['ext_newgrid', v:true], ['ext_popupmenu', v:f| + alse], ['ext_tabline', v:false], ['ext_wildmenu', | + v:false], ['height', 6], ['rgb', v:false], ['width| + ', 50]]] | {10:Press ENTER or type command to continue}{1: } | {3:-- TERMINAL --} | ]=]) diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index 5ce49822e5..b2fc008dba 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -29,6 +29,9 @@ describe('external cmdline', function() if name == "cmdline_show" then local content, pos, firstc, prompt, indent, level = unpack(data) ok(level > 0) + for _,item in ipairs(content) do + item[1] = screen:get_hl(item[1]) + end cmdline[level] = {content=content, pos=pos, firstc=firstc, prompt=prompt, indent=indent} last_level = level @@ -87,6 +90,7 @@ describe('external cmdline', function() | ]], nil, nil, function() eq(1, last_level) + --print(require('inspect')(cmdline)) eq({{ content = { { {}, "" } }, firstc = ":", @@ -168,10 +172,10 @@ describe('external cmdline', function() it('from normal mode', function() feed(':') screen:expect([[ - | + ^ | {1:~ }| {1:~ }| - {3:c^ }| + {3:c }| | ]], nil, nil, function() eq({{ @@ -351,11 +355,11 @@ describe('external cmdline', function() -- redraw! forgets cursor position. Be OK with that, as UI should indicate -- focus is at external cmdline anyway. screen:expect([[ - | + ^ | {1:~ }| {1:~ }| {1:~ }| - ^ | + | ]], nil, nil, function() eq(expectation, cmdline) end) @@ -363,11 +367,11 @@ describe('external cmdline', function() feed('<cr>') screen:expect([[ - | + ^ | {1:~ }| {1:~ }| {1:~ }| - ^ | + | ]], nil, nil, function() eq({{ content = { { {}, "xx3" } }, @@ -424,11 +428,11 @@ describe('external cmdline', function() block = {} command("redraw!") screen:expect([[ - | + ^ | {1:~ }| {1:~ }| {1:~ }| - ^ | + | ]], nil, nil, function() eq({ { { {}, 'function Foo()'} }, { { {}, ' line1'} } }, block) @@ -528,9 +532,9 @@ describe('external cmdline', function() screen:expect([[ | {2:[No Name] }| - {1::}make | + {1::}make^ | {3:[Command Line] }| - ^ | + | ]], nil, nil, function() eq({nil, { content = { { {}, "yank" } }, @@ -572,11 +576,11 @@ describe('external cmdline', function() cmdline = {} command("redraw!") screen:expect([[ - | + ^ | {1:~ }| {1:~ }| {1:~ }| - ^ | + | ]], nil, nil, function() eq({{ content = { { {}, "make" } }, diff --git a/test/functional/ui/options_spec.lua b/test/functional/ui/options_spec.lua index 62b08c0967..322a94763f 100644 --- a/test/functional/ui/options_spec.lua +++ b/test/functional/ui/options_spec.lua @@ -30,10 +30,15 @@ describe('ui receives option updates', function() ext_popupmenu=false, ext_tabline=false, ext_wildmenu=false, + ext_newgrid=false, + ext_hlstate=false, } it("for defaults", function() screen:attach() + -- NB: UI test suite can be run in both "newgrid" and legacy grid mode. + -- In both cases check that the received value is the one requested. + defaults.ext_newgrid = screen._options.ext_newgrid or false screen:expect(function() eq(defaults, screen.options) end) @@ -41,6 +46,7 @@ describe('ui receives option updates', function() it("when setting options", function() screen:attach() + defaults.ext_newgrid = screen._options.ext_newgrid or false local changed = {} for k,v in pairs(defaults) do changed[k] = v @@ -89,6 +95,7 @@ describe('ui receives option updates', function() end screen:attach({ext_cmdline=true, ext_wildmenu=true}) + defaults.ext_newgrid = screen._options.ext_newgrid or false changed.ext_cmdline = true changed.ext_wildmenu = true screen:expect(function() diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 7607131e9b..d96aae1aa7 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -142,6 +142,8 @@ function Screen.new(width, height) _default_attr_ignore = nil, _mouse_enabled = true, _attrs = {}, + _attr_table = {[0]={{},{}}}, + _clear_attrs = {}, _cursor = { row = 1, col = 1 }, @@ -163,6 +165,11 @@ function Screen:attach(options) if options == nil then options = {rgb=true} end + if options.ext_newgrid == nil then + options.ext_newgrid = true + end + self._options = options + self._clear_attrs = (options.ext_newgrid and {{},{}}) or {} uimeths.attach(self._width, self._height, options) end @@ -176,6 +183,7 @@ end function Screen:set_option(option, value) uimeths.set_option(option, value) + self._options[option] = value end -- Asserts that `expected` eventually matches the screen state. @@ -339,7 +347,7 @@ function Screen:_handle_resize(width, height) for _ = 1, height do local cols = {} for _ = 1, width do - table.insert(cols, {text = ' ', attrs = {}}) + table.insert(cols, {text = ' ', attrs = self._clear_attrs}) end table.insert(rows, cols) end @@ -353,14 +361,24 @@ function Screen:_handle_resize(width, height) } end +function Screen:_handle_grid_resize(grid, width, height) + assert(grid == 1) + self:_handle_resize(width, height) +end + + function Screen:_handle_mode_info_set(cursor_style_enabled, mode_info) self._cursor_style_enabled = cursor_style_enabled self._mode_info = mode_info end function Screen:_handle_clear() - self:_clear_block(self._scroll_region.top, self._scroll_region.bot, - self._scroll_region.left, self._scroll_region.right) + self:_clear_block(1, self._height, 1, self._width) +end + +function Screen:_handle_grid_clear(grid) + assert(grid == 1) + self:_handle_clear() end function Screen:_handle_eol_clear() @@ -373,6 +391,12 @@ function Screen:_handle_cursor_goto(row, col) self._cursor.col = col + 1 end +function Screen:_handle_grid_cursor_goto(grid, row, col) + assert(grid == 1) + self._cursor.row = row + 1 + self._cursor.col = col + 1 +end + function Screen:_handle_busy_start() self._busy = true end @@ -434,6 +458,27 @@ function Screen:_handle_scroll(count) end end +function Screen:_handle_grid_scroll(grid, top, bot, left, right, rows, cols) + assert(grid == 1) + assert(cols == 0) + -- TODO: if we truly believe we should translate the other way + self:_handle_set_scroll_region(top,bot-1,left,right-1) + self:_handle_scroll(rows) +end + +function Screen:_handle_hl_attr_define(id, rgb_attrs, cterm_attrs, info) + self._attr_table[id] = {rgb_attrs, cterm_attrs} + self._new_attrs = true +end + +function Screen:get_hl(val) + if self._options.ext_newgrid then + return self._attr_table[val][1] + else + return val + end +end + function Screen:_handle_highlight_set(attrs) self._attrs = attrs end @@ -445,6 +490,25 @@ function Screen:_handle_put(str) self._cursor.col = self._cursor.col + 1 end +function Screen:_handle_grid_line(grid, row, col, items) + assert(grid == 1) + local line = self._rows[row+1] + local colpos = col+1 + local hl = self._clear_attrs + for _,item in ipairs(items) do + local text, hlid, count = unpack(item) + if hlid ~= nil then + hl = self._attr_table[hlid] + end + for _ = 1, (count or 1) do + local cell = line[colpos] + cell.text = text + cell.attrs = hl + colpos = colpos+1 + end + end +end + function Screen:_handle_bell() self.bell = true end @@ -498,7 +562,7 @@ function Screen:_clear_row_section(rownum, startcol, stopcol) local row = self._rows[rownum] for i = startcol, stopcol do row[i].text = ' ' - row[i].attrs = {} + row[i].attrs = self._clear_attrs end end @@ -506,7 +570,11 @@ function Screen:_row_repr(row, attr_ids, attr_ignore) local rv = {} local current_attr_id for i = 1, self._width do - local attr_id = self:_get_attr_id(attr_ids, attr_ignore, row[i].attrs) + local attrs = row[i].attrs + if self._options.ext_newgrid then + attrs = attrs[(self._options.rgb and 1) or 2] + end + local attr_id = self:_get_attr_id(attr_ids, attr_ignore, attrs, row[i].hl_id) if current_attr_id and attr_id ~= current_attr_id then -- close current attribute bracket, add it before any whitespace -- up to the current cell @@ -647,6 +715,7 @@ function Screen:_get_attr_id(attr_ids, ignore, attrs) if not attr_ids then return end + for id, a in pairs(attr_ids) do if self:_equal_attrs(a, attrs) then return id diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index 6f04cde4d4..75a2d4978d 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -48,13 +48,13 @@ describe('screen', function() end) end) -describe('Screen', function() +local function screen_tests(newgrid) local screen before_each(function() clear() screen = Screen.new() - screen:attach() + screen:attach({rgb=true,ext_newgrid=newgrid}) screen:set_default_attr_ids( { [0] = {bold=true, foreground=255}, [1] = {bold=true, reverse=true}, @@ -741,4 +741,12 @@ describe('Screen', function() | ]]) end) +end + +describe("Screen (char-based)", function() + screen_tests(false) +end) + +describe("Screen (line-based)", function() + screen_tests(true) end) |