diff options
Diffstat (limited to 'src/nvim/tui/tui.c')
-rw-r--r-- | src/nvim/tui/tui.c | 545 |
1 files changed, 338 insertions, 207 deletions
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index f41c715696..b6986b77e9 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -20,6 +20,7 @@ #include "nvim/vim.h" #include "nvim/log.h" #include "nvim/ui.h" +#include "nvim/highlight.h" #include "nvim/map.h" #include "nvim/main.h" #include "nvim/memory.h" @@ -31,13 +32,13 @@ #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/strings.h" +#include "nvim/syntax.h" #include "nvim/ui_bridge.h" #include "nvim/ugrid.h" #include "nvim/tui/input.h" #include "nvim/tui/tui.h" #include "nvim/tui/terminfo.h" #include "nvim/cursor_shape.h" -#include "nvim/syntax.h" #include "nvim/macros.h" // Space reserved in two output buffers to make the cursor normal or invisible @@ -87,17 +88,24 @@ 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; bool can_set_lr_margin; bool can_set_left_right_margin; + bool can_erase_chars; bool immediate_wrap_after_last_column; + bool bce; bool mouse_enabled; bool busy, is_invisible; bool cork, overflow; + bool cursor_color_changed; cursorentry_T cursor_shapes[SHAPE_IDX_COUNT]; - HlAttrs print_attrs; + HlAttrs clear_attrs; + kvec_t(HlAttrs) attrs; + int print_attr_id; + bool has_bg; bool default_attr; ModeShape showing_mode; struct { @@ -106,11 +114,14 @@ typedef struct { int enable_lr_margin, disable_lr_margin; int set_rgb_foreground, set_rgb_background; int set_cursor_color; + int reset_cursor_color; int enable_focus_reporting, disable_focus_reporting; int resize_screen; int reset_scroll_region; int set_cursor_style, reset_cursor_style; + int enter_undercurl_mode, exit_undercurl_mode, set_underline_color; } unibi_ext; + char *space_buf; } TUIData; static bool volatile got_winch = false; @@ -125,10 +136,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; @@ -136,10 +146,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; @@ -148,8 +156,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[kUILinegrid] = true; return ui_bridge_attach(ui, tui_main, tui_scheduler); } @@ -177,14 +187,17 @@ static void terminfo_start(UI *ui) data->scroll_region_is_full_screen = true; data->bufpos = 0; data->default_attr = false; + data->has_bg = false; data->is_invisible = true; data->busy = false; data->cork = false; data->overflow = false; + data->cursor_color_changed = false; data->showing_mode = SHAPE_IDX_N; data->unibi_ext.enable_mouse = -1; data->unibi_ext.disable_mouse = -1; data->unibi_ext.set_cursor_color = -1; + data->unibi_ext.reset_cursor_color = -1; data->unibi_ext.enable_bracketed_paste = -1; data->unibi_ext.disable_bracketed_paste = -1; data->unibi_ext.enable_lr_margin = -1; @@ -230,9 +243,11 @@ static void terminfo_start(UI *ui) data->can_set_left_right_margin = !!unibi_get_str(data->ut, unibi_set_left_margin_parm) && !!unibi_get_str(data->ut, unibi_set_right_margin_parm); + data->can_erase_chars = !!unibi_get_str(data->ut, unibi_erase_chars); data->immediate_wrap_after_last_column = terminfo_is_term_family(term, "cygwin") || terminfo_is_term_family(term, "interix"); + data->bce = unibi_get_bool(data->ut, unibi_back_color_erase); data->normlen = unibi_pre_fmt_str(data, unibi_cursor_normal, data->norm, sizeof data->norm); data->invislen = unibi_pre_fmt_str(data, unibi_cursor_invisible, @@ -272,6 +287,9 @@ static void terminfo_stop(UI *ui) unibi_out(ui, unibi_cursor_normal); unibi_out(ui, unibi_keypad_local); unibi_out(ui, unibi_exit_ca_mode); + if (data->cursor_color_changed) { + unibi_out_ext(ui, data->unibi_ext.reset_cursor_color); + } // Disable bracketed paste unibi_out_ext(ui, data->unibi_ext.disable_bracketed_paste); // Disable focus reporting @@ -289,7 +307,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_attr_id = -1; ugrid_init(&data->grid); terminfo_start(ui); update_size(ui); @@ -310,6 +328,12 @@ static void tui_terminal_after_startup(UI *ui) static void tui_terminal_stop(UI *ui) { TUIData *data = ui->data; + if (uv_is_closing(STRUCT_CAST(uv_handle_t, &data->output_handle))) { + // Race between SIGCONT (tui.c) and SIGHUP (os/signal.c)? #8075 + ELOG("TUI already stopped (race?)"); + ui->data = NULL; // Flag UI as "stopped". + return; + } term_input_stop(&data->input); signal_watcher_stop(&data->winch_handle); terminfo_stop(ui); @@ -319,8 +343,7 @@ static void tui_terminal_stop(UI *ui) static void tui_stop(UI *ui) { tui_terminal_stop(ui); - // Flag UI as "stopped". - ui->data = NULL; + ui->data = NULL; // Flag UI as "stopped". } /// Returns true if UI `ui` is stopped. @@ -345,6 +368,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 @@ -379,6 +405,8 @@ 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->space_buf); xfree(data); } @@ -413,55 +441,76 @@ static void sigwinch_cb(SignalWatcher *watcher, int signum, void *data) ui_schedule_refresh(); } -static bool attrs_differ(HlAttrs a1, HlAttrs a2, bool rgb) +static bool attrs_differ(UI *ui, int id1, int id2, bool rgb) { + TUIData *data = ui->data; + if (id1 == id2) { + return false; + } else if (id1 < 0 || id2 < 0) { + return true; + } + HlAttrs a1 = kv_A(data->attrs, (size_t)id1); + HlAttrs a2 = kv_A(data->attrs, (size_t)id2); + if (rgb) { - // TODO(bfredl): when we start to support special color, - // rgb_sp_color must be added here return a1.rgb_fg_color != a2.rgb_fg_color || a1.rgb_bg_color != a2.rgb_bg_color - || a1.rgb_ae_attr != a2.rgb_ae_attr; + || a1.rgb_ae_attr != a2.rgb_ae_attr + || a1.rgb_sp_color != a2.rgb_sp_color; } else { return a1.cterm_fg_color != a2.cterm_fg_color || a1.cterm_bg_color != a2.cterm_bg_color - || a1.cterm_ae_attr != a2.cterm_ae_attr; + || a1.cterm_ae_attr != a2.cterm_ae_attr + || (a1.cterm_ae_attr & (HL_UNDERLINE|HL_UNDERCURL) + && a1.rgb_sp_color != a2.rgb_sp_color); } } -static void update_attrs(UI *ui, HlAttrs attrs) +static void update_attrs(UI *ui, int attr_id) { TUIData *data = ui->data; - if (!attrs_differ(attrs, data->print_attrs, ui->rgb)) { + if (!attrs_differ(ui, attr_id, data->print_attr_id, ui->rgb)) { + data->print_attr_id = attr_id; return; } - - data->print_attrs = attrs; - UGrid *grid = &data->grid; + data->print_attr_id = attr_id; + HlAttrs attrs = kv_A(data->attrs, (size_t)attr_id); 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); } + data->has_bg = bg != -1; + int attr = ui->rgb ? attrs.rgb_ae_attr : attrs.cterm_ae_attr; bool bold = attr & HL_BOLD; bool italic = attr & HL_ITALIC; bool reverse = attr & HL_INVERSE; bool standout = attr & HL_STANDOUT; - bool underline = attr & (HL_UNDERLINE), undercurl = attr & (HL_UNDERCURL); + + bool underline; + bool undercurl; + if (data->unibi_ext.enter_undercurl_mode) { + underline = attr & HL_UNDERLINE; + undercurl = attr & HL_UNDERCURL; + } else { + underline = (attr & HL_UNDERLINE) || (attr & HL_UNDERCURL); + undercurl = false; + } if (unibi_get_str(data->ut, unibi_set_attributes)) { - if (bold || reverse || underline || undercurl || standout) { + if (bold || reverse || underline || standout) { UNIBI_SET_NUM_VAR(data->params[0], standout); - UNIBI_SET_NUM_VAR(data->params[1], underline || undercurl); + UNIBI_SET_NUM_VAR(data->params[1], underline); UNIBI_SET_NUM_VAR(data->params[2], reverse); UNIBI_SET_NUM_VAR(data->params[3], 0); // blink UNIBI_SET_NUM_VAR(data->params[4], 0); // dim @@ -480,7 +529,7 @@ static void update_attrs(UI *ui, HlAttrs attrs) if (bold) { unibi_out(ui, unibi_enter_bold_mode); } - if (underline || undercurl) { + if (underline) { unibi_out(ui, unibi_enter_underline_mode); } if (standout) { @@ -493,6 +542,18 @@ static void update_attrs(UI *ui, HlAttrs attrs) if (italic) { unibi_out(ui, unibi_enter_italics_mode); } + if (undercurl && data->unibi_ext.enter_undercurl_mode) { + unibi_out_ext(ui, data->unibi_ext.enter_undercurl_mode); + } + if ((undercurl || underline) && data->unibi_ext.set_underline_color) { + int color = attrs.rgb_sp_color; + if (color != -1) { + UNIBI_SET_NUM_VAR(data->params[0], (color >> 16) & 0xff); // red + UNIBI_SET_NUM_VAR(data->params[1], (color >> 8) & 0xff); // green + UNIBI_SET_NUM_VAR(data->params[2], color & 0xff); // blue + unibi_out_ext(ui, data->unibi_ext.set_underline_color); + } + } if (ui->rgb) { if (fg != -1) { UNIBI_SET_NUM_VAR(data->params[0], (fg >> 16) & 0xff); // red @@ -527,7 +588,7 @@ static void final_column_wrap(UI *ui) { TUIData *data = ui->data; UGrid *grid = &data->grid; - if (grid->col == ui->width) { + if (grid->row != -1 && grid->col == ui->width) { grid->col = 0; if (grid->row < MIN(ui->height, grid->height - 1)) { grid->row++; @@ -546,7 +607,7 @@ static void print_cell(UI *ui, UCell *ptr) // Printing the next character finally advances the cursor. final_column_wrap(ui); } - update_attrs(ui, ptr->attrs); + update_attrs(ui, ptr->attr); out(ui, ptr->data, strlen(ptr->data)); grid->col++; if (data->immediate_wrap_after_last_column) { @@ -562,7 +623,8 @@ static bool cheap_to_print(UI *ui, int row, int col, int next) UCell *cell = grid->cells[row] + col; while (next) { next--; - if (attrs_differ(cell->attrs, data->print_attrs, ui->rgb)) { + if (attrs_differ(ui, cell->attr, + data->print_attr_id, ui->rgb)) { if (data->default_attr) { return false; } @@ -596,6 +658,9 @@ static void cursor_goto(UI *ui, int row, int col) ugrid_goto(grid, row, col); return; } + if (grid->row == -1) { + goto safe_move; + } if (0 == col ? col != grid->col : row != grid->row ? false : 1 == col ? 2 < grid->col && cheap_to_print(ui, grid->row, 0, col) : @@ -609,7 +674,7 @@ static void cursor_goto(UI *ui, int row, int col) int n = col - grid->col; if (n <= (row == grid->row ? 4 : 2) && cheap_to_print(ui, grid->row, grid->col, n)) { - UGRID_FOREACH_CELL(grid, grid->row, grid->row, grid->col, col - 1, { + UGRID_FOREACH_CELL(grid, grid->row, grid->col, col, { print_cell(ui, cell); }); } @@ -674,96 +739,81 @@ static void cursor_goto(UI *ui, int row, int col) return; } } + +safe_move: unibi_goto(ui, row, 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, + int attr_id) { 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; - 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); - if (left == 0) { - if (bot == ui->height - 1) { - if (top == 0) { - unibi_out(ui, unibi_clear_screen); - ugrid_goto(&data->grid, top, left); - } else { - cursor_goto(ui, top, 0); - unibi_out(ui, unibi_clr_eos); - } - cleared = true; - } + + update_attrs(ui, attr_id); + + // non-BCE terminals can't clear with non-default background color + bool can_clear = data->bce || !data->has_bg; + + // 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. + if (can_clear && left == 0 && right == ui->width && bot == ui->height) { + if (top == 0) { + unibi_out(ui, unibi_clear_screen); + ugrid_goto(&data->grid, top, left); + } else { + cursor_goto(ui, top, 0); + unibi_out(ui, unibi_clr_eos); } + } else { + int width = right-left; - if (!cleared) { - // iterate through each line and clear with clr_eol - for (int row = top; row <= bot; row++) { - cursor_goto(ui, row, left); + // iterate through each line and clear + for (int row = top; row < bot; row++) { + cursor_goto(ui, row, left); + if (can_clear && right == ui->width) { unibi_out(ui, unibi_clr_eol); + } else if (data->can_erase_chars && can_clear && width >= 5) { + UNIBI_SET_NUM_VAR(data->params[0], width); + unibi_out(ui, unibi_erase_chars); + } else { + out(ui, data->space_buf, (size_t)width); + grid->col += width; + if (data->immediate_wrap_after_last_column) { + // Printing at the right margin immediately advances the cursor. + final_column_wrap(ui); + } } - cleared = true; } } - - if (!cleared) { - // could not clear using faster terminal codes, refresh the whole region - UGRID_FOREACH_CELL(grid, top, bot, left, right, { - cursor_goto(ui, row, col); - print_cell(ui, cell); - }); - } - - // restore cursor - cursor_goto(ui, saved_row, saved_col); -} - -static bool can_use_scroll(UI * ui) -{ - TUIData *data = ui->data; - UGrid *grid = &data->grid; - - return data->scroll_region_is_full_screen - || (data->can_change_scroll_region - && ((grid->left == 0 && grid->right == ui->width - 1) - || data->can_set_lr_margin - || data->can_set_left_right_margin)); } -static void set_scroll_region(UI *ui) +static void set_scroll_region(UI *ui, int top, int bot, int left, int right) { TUIData *data = ui->data; UGrid *grid = &data->grid; - UNIBI_SET_NUM_VAR(data->params[0], grid->top); - UNIBI_SET_NUM_VAR(data->params[1], grid->bot); + UNIBI_SET_NUM_VAR(data->params[0], top); + UNIBI_SET_NUM_VAR(data->params[1], bot); unibi_out(ui, unibi_change_scroll_region); - if (grid->left != 0 || grid->right != ui->width - 1) { + if (left != 0 || right != ui->width - 1) { unibi_out_ext(ui, data->unibi_ext.enable_lr_margin); if (data->can_set_lr_margin) { - UNIBI_SET_NUM_VAR(data->params[0], grid->left); - UNIBI_SET_NUM_VAR(data->params[1], grid->right); + UNIBI_SET_NUM_VAR(data->params[0], left); + UNIBI_SET_NUM_VAR(data->params[1], right); unibi_out(ui, unibi_set_lr_margin); } else { - UNIBI_SET_NUM_VAR(data->params[0], grid->left); + UNIBI_SET_NUM_VAR(data->params[0], left); unibi_out(ui, unibi_set_left_margin_parm); - UNIBI_SET_NUM_VAR(data->params[0], grid->right); + UNIBI_SET_NUM_VAR(data->params[0], right); unibi_out(ui, unibi_set_right_margin_parm); } } - unibi_goto(ui, grid->row, grid->col); + grid->row = -1; } -static void reset_scroll_region(UI *ui) +static void reset_scroll_region(UI *ui, bool fullwidth) { TUIData *data = ui->data; UGrid *grid = &data->grid; @@ -775,7 +825,7 @@ static void reset_scroll_region(UI *ui) UNIBI_SET_NUM_VAR(data->params[1], ui->height - 1); unibi_out(ui, unibi_change_scroll_region); } - if (grid->left != 0 || grid->right != ui->width - 1) { + if (!fullwidth) { if (data->can_set_lr_margin) { UNIBI_SET_NUM_VAR(data->params[0], 0); UNIBI_SET_NUM_VAR(data->params[1], ui->width - 1); @@ -788,13 +838,26 @@ static void reset_scroll_region(UI *ui) } unibi_out_ext(ui, data->unibi_ext.disable_lr_margin); } - unibi_goto(ui, grid->row, grid->col); + grid->row = -1; } -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); + UGrid *grid = &data->grid; + ugrid_resize(grid, (int)width, (int)height); + + xfree(data->space_buf); + data->space_buf = xmalloc((size_t)width * sizeof(*data->space_buf)); + memset(data->space_buf, ' ', (size_t)width); + + // resize might not always be followed by a clear before flush + // so clip the invalid region + for (size_t i = 0; i < kv_size(data->invalid_regions); i++) { + Rect *r = &kv_A(data->invalid_regions, i); + r->bot = MIN(r->bot, grid->height); + r->right = MIN(r->right, grid->width); + } if (!got_winch) { // Try to resize the terminal window. UNIBI_SET_NUM_VAR(data->params[0], (int)height); @@ -802,33 +865,30 @@ static void tui_resize(UI *ui, Integer width, Integer height) unibi_out_ext(ui, data->unibi_ext.resize_screen); // DECSLPP does not reset the scroll region. if (data->scroll_region_is_full_screen) { - reset_scroll_region(ui); + reset_scroll_region(ui, ui->width == grid->width); } } else { // Already handled the SIGWINCH signal; avoid double-resize. got_winch = false; + grid->row = -1; } } -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, 0, grid->height, 0, grid->width, 0); } -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) -{ - cursor_goto(ui, (int)row, (int)col); + // cursor position is validated in tui_flush + data->row = (int)row; + data->col = (int)col; } CursorShape tui_cursor_decode_shape(const char *shape_str) @@ -861,7 +921,7 @@ static cursorentry_T decode_cursor_entry(Dictionary args) r.blinkon = (int)value.data.integer; } else if (strequal(key, "blinkoff")) { r.blinkoff = (int)value.data.integer; - } else if (strequal(key, "hl_id")) { + } else if (strequal(key, "attr_id")) { r.id = (int)value.data.integer; } } @@ -929,13 +989,20 @@ static void tui_set_mode(UI *ui, ModeShape mode) TUIData *data = ui->data; cursorentry_T c = data->cursor_shapes[mode]; - if (c.id != 0 && ui->rgb) { - int attr = syn_id2attr(c.id); - if (attr > 0) { - HlAttrs *aep = syn_cterm_attr2entry(attr); - UNIBI_SET_NUM_VAR(data->params[0], aep->rgb_bg_color); + if (c.id != 0 && c.id < (int)kv_size(data->attrs) && ui->rgb) { + HlAttrs aep = kv_A(data->attrs, c.id); + if (aep.rgb_ae_attr & HL_INVERSE) { + // We interpret "inverse" as "default" (no termcode for "inverse"...). + // Hopefully the user's default cursor color is inverse. + unibi_out_ext(ui, data->unibi_ext.reset_cursor_color); + } else { + UNIBI_SET_NUM_VAR(data->params[0], aep.rgb_bg_color); unibi_out_ext(ui, data->unibi_ext.set_cursor_color); + data->cursor_color_changed = true; } + } else if (c.id == 0) { + // No cursor color for this mode; reset to default. + unibi_out_ext(ui, data->unibi_ext.reset_cursor_color); } int shape; @@ -957,90 +1024,70 @@ 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) -{ - TUIData *data = ui->data; - ugrid_set_scroll_region(&data->grid, (int)top, (int)bot, - (int)left, (int)right); - data->scroll_region_is_full_screen = - left == 0 && right == ui->width - 1 - && top == 0 && bot == ui->height - 1; -} - -static void tui_scroll(UI *ui, Integer count) +static void tui_grid_scroll(UI *ui, Integer g, Integer startrow, Integer endrow, + Integer startcol, Integer endcol, + Integer rows, Integer cols) { TUIData *data = ui->data; UGrid *grid = &data->grid; - int clear_top, clear_bot; - ugrid_scroll(grid, (int)count, &clear_top, &clear_bot); + int top = (int)startrow, bot = (int)endrow-1; + int left = (int)startcol, right = (int)endcol-1; + + bool fullwidth = left == 0 && right == ui->width-1; + data->scroll_region_is_full_screen = fullwidth + && top == 0 && bot == ui->height-1; + + ugrid_scroll(grid, top, bot, left, right, (int)rows); - 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); + bool can_scroll = data->scroll_region_is_full_screen + || (data->can_change_scroll_region + && ((left == 0 && right == ui->width - 1) + || data->can_set_lr_margin + || data->can_set_left_right_margin)); + if (can_scroll) { // Change terminal scroll region and move cursor to the top if (!data->scroll_region_is_full_screen) { - set_scroll_region(ui); - } - 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); + set_scroll_region(ui, top, bot, left, right); } + cursor_goto(ui, top, left); - 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); } } // Restore terminal scroll region and cursor if (!data->scroll_region_is_full_screen) { - reset_scroll_region(ui); - } - cursor_goto(ui, saved_row, saved_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); + reset_scroll_region(ui, fullwidth); } } else { - // Mark the entire scroll region as invalid for redrawing later - invalidate(ui, grid->top, grid->bot, grid->left, grid->right); + // Mark the moved region as invalid for redrawing later + if (rows > 0) { + endrow = endrow - rows; + } else { + startrow = startrow - rows; + } + invalidate(ui, (int)startrow, (int)endrow, (int)startcol, (int)endcol); } } -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) @@ -1057,12 +1104,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_attr_id = -1; + invalidate(ui, 0, data->grid.height, 0, data->grid.width); } static void tui_flush(UI *ui) @@ -1082,19 +1133,32 @@ 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); - UGRID_FOREACH_CELL(grid, r.top, r.bot, r.left, r.right, { - cursor_goto(ui, row, col); - print_cell(ui, cell); - }); + assert(r.bot <= grid->height && r.right <= grid->width); + + for (int row = r.top; row < r.bot; row++) { + int clear_attr = grid->cells[row][r.right-1].attr; + int clear_col; + for (clear_col = r.right; clear_col > 0; clear_col--) { + UCell *cell = &grid->cells[row][clear_col-1]; + if (!(cell->data[0] == ' ' && cell->data[1] == NUL + && cell->attr == clear_attr)) { + break; + } + } + + UGRID_FOREACH_CELL(grid, row, r.left, clear_col, { + cursor_goto(ui, row, col); + print_cell(ui, cell); + }); + if (clear_col < r.right) { + clear_region(ui, row, row+1, clear_col, r.right, clear_attr); + } + } } - cursor_goto(ui, saved_row, saved_col); + cursor_goto(ui, data->row, data->col); flush_buf(ui); } @@ -1175,7 +1239,49 @@ 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; - invalidate(ui, 0, data->grid.height-1, 0, data->grid.width-1); + + data->print_attr_id = -1; + invalidate(ui, 0, data->grid.height, 0, data->grid.width); + } +} + +static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol, + Integer endcol, Integer clearcol, Integer clearattr, + Boolean wrap, 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)); + assert((size_t)attrs[c-startcol] < kv_size(data->attrs)); + grid->cells[linerow][c].attr = attrs[c-startcol]; + } + UGRID_FOREACH_CELL(grid, (int)linerow, (int)startcol, (int)endcol, { + cursor_goto(ui, (int)linerow, col); + print_cell(ui, cell); + }); + + if (clearcol > endcol) { + ugrid_clear_chunk(grid, (int)linerow, (int)endcol, (int)clearcol, + (sattr_T)clearattr); + clear_region(ui, (int)linerow, (int)linerow+1, (int)endcol, (int)clearcol, + (int)clearattr); + } + + if (wrap && ui->width == grid->width && linerow + 1 < grid->height) { + // Only do line wrapping if the grid width is equal to the terminal + // width and the line continuation is within the grid. + + if (endcol != grid->width) { + // Print the last cell of the row, if we haven't already done so. + cursor_goto(ui, (int)linerow, grid->width - 1); + print_cell(ui, &grid->cells[linerow][grid->width - 1]); + } + + // Wrap the cursor over to the next line. The next line will be + // printed immediately without an intervening newline. + final_column_wrap(ui); } } @@ -1183,27 +1289,17 @@ static void invalidate(UI *ui, int top, int bot, int left, int right) { TUIData *data = ui->data; Rect *intersects = NULL; - // Increase dimensions before comparing to ensure adjacent regions are - // treated as intersecting - --top; - ++bot; - --left; - ++right; for (size_t i = 0; i < kv_size(data->invalid_regions); i++) { Rect *r = &kv_A(data->invalid_regions, i); - if (!(top > r->bot || bot < r->top - || left > r->right || right < r->left)) { + // adjacent regions are treated as overlapping + if (!(top > r->bot || bot < r->top) + && !(left > r->right || right < r->left)) { intersects = r; break; } } - ++top; - --bot; - ++left; - --right; - if (intersects) { // If top/bot/left/right intersects with a invalid rect, we replace it // by the union @@ -1346,6 +1442,18 @@ static int unibi_find_ext_str(unibi_term *ut, const char *name) return -1; } +static int unibi_find_ext_bool(unibi_term *ut, const char *name) +{ + size_t max = unibi_count_ext_bool(ut); + for (size_t i = 0; i < max; i++) { + const char * n = unibi_get_ext_bool_name(ut, i); + if (n && 0 == strcmp(n, name)) { + return (int)i; + } + } + return -1; +} + /// Patches the terminfo records after loading from system or built-in db. /// Several entries in terminfo are known to be deficient or outright wrong; /// and several terminal emulators falsely announce incorrect terminal types. @@ -1359,6 +1467,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, bool roxterm = !!os_getenv("ROXTERM_ID"); #endif bool xterm = terminfo_is_term_family(term, "xterm"); + bool kitty = terminfo_is_term_family(term, "xterm-kitty"); bool linuxvt = terminfo_is_term_family(term, "linux"); bool rxvt = terminfo_is_term_family(term, "rxvt"); bool teraterm = terminfo_is_term_family(term, "teraterm"); @@ -1372,6 +1481,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, || terminfo_is_term_family(term, "iterm2") || terminfo_is_term_family(term, "iTerm.app") || terminfo_is_term_family(term, "iTerm2.app"); + bool alacritty = terminfo_is_term_family(term, "alacritty"); // None of the following work over SSH; see :help TERM . bool iterm_pretending_xterm = xterm && iterm_env; bool konsole_pretending_xterm = xterm && konsole; @@ -1414,8 +1524,8 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, } } - if (!true_xterm) { - // Cannot trust terminfo; safer to disable BCE. #7624 + if (tmux || screen || kitty) { + // Disable BCE in some cases we know it is not working. #8806 unibi_set_bool(ut, unibi_back_color_erase, false); } @@ -1566,6 +1676,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, // per analysis of VT100Terminal.m || iterm || iterm_pretending_xterm || teraterm // per TeraTerm "Supported Control Functions" doco + || alacritty // https://github.com/jwilm/alacritty/pull/608 // Some linux-type terminals implement the xterm extension. // Example: console-terminal-emulator from the nosh toolset. || (linuxvt @@ -1713,11 +1824,15 @@ static void augment_terminfo(TUIData *data, const char *term, // would use a tmux control sequence and an extra if(screen) test. data->unibi_ext.set_cursor_color = (int)unibi_add_ext_str( ut, NULL, TMUX_WRAP(tmux, "\033]Pl%p1%06x\033\\")); - } else if (xterm || (vte_version != 0) || rxvt) { - // This seems to be supported for a long time in VTE - // urxvt also supports this + } else if ((xterm || rxvt) && (vte_version == 0 || vte_version >= 3900)) { + // Supported in urxvt, newer VTE. data->unibi_ext.set_cursor_color = (int)unibi_add_ext_str( - ut, NULL, "\033]12;#%p1%06x\007"); + ut, "ext.set_cursor_color", "\033]12;#%p1%06x\007"); + } + + if (-1 != data->unibi_ext.set_cursor_color) { + data->unibi_ext.reset_cursor_color = (int)unibi_add_ext_str( + ut, "ext.reset_cursor_color", "\x1b]112\x07"); } /// Terminals usually ignore unrecognized private modes, and there is no @@ -1741,6 +1856,22 @@ static void augment_terminfo(TUIData *data, const char *term, ut, "ext.enable_mouse", "\x1b[?1002h\x1b[?1006h"); data->unibi_ext.disable_mouse = (int)unibi_add_ext_str( ut, "ext.disable_mouse", "\x1b[?1002l\x1b[?1006l"); + + int ext_bool_Su = unibi_find_ext_bool(ut, "Su"); // used by kitty + if (vte_version >= 5102 + || (ext_bool_Su != -1 && unibi_get_ext_bool(ut, (size_t)ext_bool_Su))) { + data->unibi_ext.enter_undercurl_mode = (int)unibi_add_ext_str( + ut, "ext.enter_undercurl_mode", "\x1b[4:3m"); + data->unibi_ext.exit_undercurl_mode = (int)unibi_add_ext_str( + ut, "ext.exit_undercurl_mode", "\x1b[4:0m"); + if (has_colon_rgb) { + data->unibi_ext.set_underline_color = (int)unibi_add_ext_str( + ut, "ext.set_underline_color", "\x1b[58:2:%p1%d:%p2%d:%p3%dm"); + } else { + data->unibi_ext.set_underline_color = (int)unibi_add_ext_str( + ut, "ext.set_underline_color", "\x1b[58;2;%p1%d;%p2%d;%p3%dm"); + } + } } static void flush_buf(UI *ui) @@ -1795,7 +1926,7 @@ static void flush_buf(UI *ui) static const char *tui_get_stty_erase(void) { static char stty_erase[2] = { 0 }; -#if defined(ECHOE) && defined(ICANON) && defined(HAVE_TERMIOS_H) +#if defined(HAVE_TERMIOS_H) struct termios t; if (tcgetattr(input_global_fd(), &t) != -1) { stty_erase[0] = (char)t.c_cc[VERASE]; |