aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/tui/tui.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/tui/tui.c')
-rw-r--r--src/nvim/tui/tui.c545
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];