From 36d1fd0602d440e4c20cee852bcc43bfa83cb80f Mon Sep 17 00:00:00 2001 From: Jonathan de Boyne Pollard Date: Wed, 3 May 2017 22:54:09 +0100 Subject: [PATCH 1/4] tui: Only use dtterm's extension where supported. This limits the use of dtterm's extension to DECSLPP to only those terminal types where it is known to be supported. Because it can be potentially understood as genuine DECSLPP sequence, setting the number of lines to a number larger than 25, which of course can cause confusion (especially if it is the width parameter that results in this) only use it on terminals that are known to support the dtterm extension. rxvt (Unicode) also understands dtterm's extension. --- src/nvim/tui/tui.c | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 8d240c91ae3681..5d9db9494822d2 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -53,6 +53,9 @@ typedef enum TermType { kTermiTerm, kTermKonsole, kTermRxvt, + kTermDTTerm, + kTermXTerm, + kTermTeraTerm, } TermType; typedef struct { @@ -425,9 +428,16 @@ static void tui_resize(UI *ui, Integer width, Integer height) ugrid_resize(&data->grid, (int)width, (int)height); if (!got_winch) { // Try to resize the terminal window. - char r[16]; // enough for 9999x9999 - snprintf(r, sizeof(r), "\x1b[8;%d;%dt", (int)height, (int)width); - out(ui, r, strlen(r)); + // Only send this extension to terminal types that we know understand it. + if (data->term == kTermDTTerm // originated this extension + || data->term == kTermXTerm // per xterm ctlseqs doco + || data->term == kTermKonsole // per commentary in VT102Emulation.cpp + || data->term == kTermTeraTerm // per TeraTerm "Supported Control Functions" doco + || data->term == kTermRxvt) { // per command.C + char r[16]; // enough for 9999x9999 + snprintf(r, sizeof(r), "\x1b[8;%d;%dt", height, width); + out(ui, r, strlen(r)); + } } else { // Already handled the SIGWINCH signal; avoid double-resize. got_winch = false; } @@ -957,6 +967,15 @@ static TermType detect_term(const char *term, const char *colorterm) if (colorterm && strstr(colorterm, "gnome-terminal")) { return kTermGnome; } + if (STARTS_WITH(term, "xterm")) { + return kTermXTerm; + } + if (STARTS_WITH(term, "dtterm")) { + return kTermDTTerm; + } + if (STARTS_WITH(term, "teraterm")) { + return kTermTeraTerm; + } return kTermUnknown; } @@ -977,14 +996,14 @@ static void fix_terminfo(TUIData *data) unibi_set_if_empty(ut, unibi_flash_screen, "\x1b[?5h$<20/>\x1b[?5l"); unibi_set_if_empty(ut, unibi_enter_italics_mode, "\x1b[3m"); unibi_set_if_empty(ut, unibi_to_status_line, "\x1b]2"); - } else if (STARTS_WITH(term, "xterm")) { + } else if (data->term == kTermXTerm) { unibi_set_if_empty(ut, unibi_to_status_line, "\x1b]0;"); } else if (STARTS_WITH(term, "screen") || STARTS_WITH(term, "tmux")) { unibi_set_if_empty(ut, unibi_to_status_line, "\x1b_"); unibi_set_if_empty(ut, unibi_from_status_line, "\x1b\\"); } - if (STARTS_WITH(term, "xterm") || data->term == kTermRxvt) { + if (data->term == kTermXTerm || data->term == kTermRxvt) { const char *normal = unibi_get_str(ut, unibi_cursor_normal); if (!normal) { unibi_set_str(ut, unibi_cursor_normal, "\x1b[?25h"); From 593af64943033de8b638e338cbb1d45ced7b04ae Mon Sep 17 00:00:00 2001 From: Jonathan de Boyne Pollard Date: Fri, 5 May 2017 15:17:32 +0100 Subject: [PATCH 2/4] tui: resize: use an extended terminal capability ... rather than hardwiring the string and testing the terminal type every time the screen is re-sized. --- src/nvim/tui/tui.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 5d9db9494822d2..4c22495d8ea243 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -95,6 +95,7 @@ typedef struct { int set_rgb_foreground, set_rgb_background; int set_cursor_color; int enable_focus_reporting, disable_focus_reporting; + int resize_screen; } unibi_ext; } TUIData; @@ -157,6 +158,7 @@ static void terminfo_start(UI *ui) data->unibi_ext.disable_bracketed_paste = -1; data->unibi_ext.enable_focus_reporting = -1; data->unibi_ext.disable_focus_reporting = -1; + data->unibi_ext.resize_screen = -1; data->out_fd = 1; data->out_isatty = os_isatty(data->out_fd); // setup unibilium @@ -428,16 +430,9 @@ static void tui_resize(UI *ui, Integer width, Integer height) ugrid_resize(&data->grid, (int)width, (int)height); if (!got_winch) { // Try to resize the terminal window. - // Only send this extension to terminal types that we know understand it. - if (data->term == kTermDTTerm // originated this extension - || data->term == kTermXTerm // per xterm ctlseqs doco - || data->term == kTermKonsole // per commentary in VT102Emulation.cpp - || data->term == kTermTeraTerm // per TeraTerm "Supported Control Functions" doco - || data->term == kTermRxvt) { // per command.C - char r[16]; // enough for 9999x9999 - snprintf(r, sizeof(r), "\x1b[8;%d;%dt", height, width); - out(ui, r, strlen(r)); - } + data->params[0].i = (int)height; + data->params[1].i = (int)width; + unibi_out(ui, data->unibi_ext.resize_screen); } else { // Already handled the SIGWINCH signal; avoid double-resize. got_winch = false; } @@ -1048,6 +1043,16 @@ static void fix_terminfo(TUIData *data) unibi_set_str(ut, unibi_set_a_background, XTERM_SETAB); } + // Only define this capability for terminal types that we know understand it. + if (data->term == kTermDTTerm // originated this extension + || data->term == kTermXTerm // per xterm ctlseqs doco + || data->term == kTermKonsole // per commentary in VT102Emulation.cpp + || data->term == kTermTeraTerm // per TeraTerm "Supported Control Functions" doco + || data->term == kTermRxvt) { // per command.C + data->unibi_ext.resize_screen = (int)unibi_add_ext_str(ut, NULL, + "\x1b[8;%p1%d;%p2%dt"); + } + end: // Fill some empty slots with common terminal strings if (data->term == kTermiTerm) { From 0de7b17d03e2de18adcc9e7db27483160c4ac052 Mon Sep 17 00:00:00 2001 From: Jonathan de Boyne Pollard Date: Sat, 20 May 2017 11:21:42 +0100 Subject: [PATCH 3/4] tui: Reset the scroll region when resizing. DECSLPP is explicitly documented as not affecting the scroll region. The dtterm extension is not as well documented, but it is safer than not to assume that it operates similarly. This also eliminates a pointlessly repeated test from tui_scroll(). It additionally uses a non-parameterized DECSTBM sequence when attempting to reset back to whole-screen scrolling. --- src/nvim/tui/tui.c | 69 ++++++++++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 27 deletions(-) diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 4c22495d8ea243..5614a1738f3ca0 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -82,7 +82,7 @@ typedef struct { UGrid grid; kvec_t(Rect) invalid_regions; int out_fd; - bool can_use_terminal_scroll; + bool scroll_region_is_full_screen; bool mouse_enabled; bool busy; cursorentry_T cursor_shapes[SHAPE_IDX_COUNT]; @@ -96,6 +96,7 @@ typedef struct { int set_cursor_color; int enable_focus_reporting, disable_focus_reporting; int resize_screen; + int reset_scroll_region; } unibi_ext; } TUIData; @@ -147,7 +148,7 @@ UI *tui_start(void) static void terminfo_start(UI *ui) { TUIData *data = ui->data; - data->can_use_terminal_scroll = true; + data->scroll_region_is_full_screen = true; data->bufpos = 0; data->bufsize = sizeof(data->buf) - CNORM_COMMAND_MAX_SIZE; data->showing_mode = SHAPE_IDX_N; @@ -159,6 +160,7 @@ static void terminfo_start(UI *ui) data->unibi_ext.enable_focus_reporting = -1; data->unibi_ext.disable_focus_reporting = -1; data->unibi_ext.resize_screen = -1; + data->unibi_ext.reset_scroll_region = -1; data->out_fd = 1; data->out_isatty = os_isatty(data->out_fd); // setup unibilium @@ -424,6 +426,19 @@ static void clear_region(UI *ui, int top, int bot, int left, int right) unibi_goto(ui, grid->row, grid->col); } +static void reset_scroll_region(UI *ui) +{ + TUIData *data = ui->data; + + if (0 <= data->unibi_ext.reset_scroll_region) { + unibi_out(ui, data->unibi_ext.reset_scroll_region); + } else { + data->params[0].i = 0; + data->params[1].i = ui->height - 1; + unibi_out(ui, unibi_change_scroll_region); + } +} + static void tui_resize(UI *ui, Integer width, Integer height) { TUIData *data = ui->data; @@ -433,6 +448,10 @@ static void tui_resize(UI *ui, Integer width, Integer height) data->params[0].i = (int)height; data->params[1].i = (int)width; unibi_out(ui, data->unibi_ext.resize_screen); + // DECSLPP does not reset the scroll region. + if (data->scroll_region_is_full_screen) { + reset_scroll_region(ui); + } } else { // Already handled the SIGWINCH signal; avoid double-resize. got_winch = false; } @@ -619,10 +638,9 @@ static void tui_set_scroll_region(UI *ui, Integer top, Integer bot, TUIData *data = ui->data; ugrid_set_scroll_region(&data->grid, (int)top, (int)bot, (int)left, (int)right); - data->can_use_terminal_scroll = + data->scroll_region_is_full_screen = left == 0 && right == ui->width - 1 - && ((top == 0 && bot == ui->height - 1) - || unibi_get_str(data->ut, unibi_change_scroll_region)); + && top == 0 && bot == ui->height - 1; } static void tui_scroll(UI *ui, Integer count) @@ -632,31 +650,28 @@ static void tui_scroll(UI *ui, Integer count) int clear_top, clear_bot; ugrid_scroll(grid, (int)count, &clear_top, &clear_bot); - if (data->can_use_terminal_scroll) { + if (data->scroll_region_is_full_screen || 0 <= unibi_change_scroll_region) { // Change terminal scroll region and move cursor to the top - data->params[0].i = grid->top; - data->params[1].i = grid->bot; - unibi_out(ui, unibi_change_scroll_region); + if (!data->scroll_region_is_full_screen) { + data->params[0].i = grid->top; + data->params[1].i = grid->bot; + unibi_out(ui, unibi_change_scroll_region); + } unibi_goto(ui, grid->top, grid->left); // also set default color attributes or some terminals can become funny HlAttrs clear_attrs = EMPTY_ATTRS; clear_attrs.foreground = grid->fg; clear_attrs.background = grid->bg; update_attrs(ui, clear_attrs); - } - if (count > 0) { - if (data->can_use_terminal_scroll) { + if (count > 0) { if (count == 1) { unibi_out(ui, unibi_delete_line); } else { data->params[0].i = (int)count; unibi_out(ui, unibi_parm_delete_line); } - } - - } else { - if (data->can_use_terminal_scroll) { + } else { if (count == -1) { unibi_out(ui, unibi_insert_line); } else { @@ -664,13 +679,11 @@ static void tui_scroll(UI *ui, Integer count) unibi_out(ui, unibi_parm_insert_line); } } - } - if (data->can_use_terminal_scroll) { // Restore terminal scroll region and cursor - data->params[0].i = 0; - data->params[1].i = ui->height - 1; - unibi_out(ui, unibi_change_scroll_region); + if (!data->scroll_region_is_full_screen) { + reset_scroll_region(ui); + } unibi_goto(ui, grid->row, grid->col); if (grid->bg != -1) { @@ -1015,6 +1028,8 @@ static void fix_terminfo(TUIData *data) unibi_set_if_empty(ut, unibi_change_scroll_region, "\x1b[%i%p1%d;%p2%dr"); unibi_set_if_empty(ut, unibi_clear_screen, "\x1b[H\x1b[2J"); unibi_set_if_empty(ut, unibi_from_status_line, "\x07"); + data->unibi_ext.reset_scroll_region = (int)unibi_add_ext_str(ut, NULL, + "\x1b[r"); } data->unibi_ext.enable_bracketed_paste = (int)unibi_add_ext_str(ut, NULL, @@ -1044,13 +1059,13 @@ static void fix_terminfo(TUIData *data) } // Only define this capability for terminal types that we know understand it. - if (data->term == kTermDTTerm // originated this extension - || data->term == kTermXTerm // per xterm ctlseqs doco - || data->term == kTermKonsole // per commentary in VT102Emulation.cpp - || data->term == kTermTeraTerm // per TeraTerm "Supported Control Functions" doco - || data->term == kTermRxvt) { // per command.C + if (data->term == kTermDTTerm // originated this extension + || data->term == kTermXTerm // per xterm ctlseqs doc + || data->term == kTermKonsole // per commentary in VT102Emulation.cpp + || data->term == kTermTeraTerm // per "Supported Control Functions" doc + || data->term == kTermRxvt) { // per command.C data->unibi_ext.resize_screen = (int)unibi_add_ext_str(ut, NULL, - "\x1b[8;%p1%d;%p2%dt"); + "\x1b[8;%p1%d;%p2%dt"); } end: From 133ae5eeeff3b83e1a3e6da35b57dcdfb92287b0 Mon Sep 17 00:00:00 2001 From: Jonathan de Boyne Pollard Date: Mon, 22 May 2017 21:41:27 +0100 Subject: [PATCH 4/4] tui: Improve scrolling mechanism. Respect the BGE flag from terminfo rather than guessing that it is always off. Emit DECLRMM and DECSLRM (or equivalent) to properly define the scroll rectangle. --- src/nvim/tui/tui.c | 108 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 93 insertions(+), 15 deletions(-) diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 5614a1738f3ca0..736d50ee8b416a 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -83,6 +83,9 @@ typedef struct { kvec_t(Rect) invalid_regions; 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 mouse_enabled; bool busy; cursorentry_T cursor_shapes[SHAPE_IDX_COUNT]; @@ -92,6 +95,7 @@ typedef struct { struct { int enable_mouse, disable_mouse; int enable_bracketed_paste, disable_bracketed_paste; + int enable_lr_margin, disable_lr_margin; int set_rgb_foreground, set_rgb_background; int set_cursor_color; int enable_focus_reporting, disable_focus_reporting; @@ -157,6 +161,8 @@ static void terminfo_start(UI *ui) data->unibi_ext.set_cursor_color = -1; data->unibi_ext.enable_bracketed_paste = -1; data->unibi_ext.disable_bracketed_paste = -1; + data->unibi_ext.enable_lr_margin = -1; + data->unibi_ext.disable_lr_margin = -1; data->unibi_ext.enable_focus_reporting = -1; data->unibi_ext.disable_focus_reporting = -1; data->unibi_ext.resize_screen = -1; @@ -171,6 +177,13 @@ static void terminfo_start(UI *ui) data->ut = unibi_dummy(); } fix_terminfo(data); + data->can_change_scroll_region = + !!unibi_get_str(data->ut, unibi_change_scroll_region); + data->can_set_lr_margin = + !!unibi_get_str(data->ut, unibi_set_lr_margin); + 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); // Set 't_Co' from the result of unibilium & fix_terminfo. t_colors = unibi_get_num(data->ut, unibi_max_colors); // Enter alternate screen and clear @@ -426,9 +439,46 @@ static void clear_region(UI *ui, int top, int bot, int left, int right) unibi_goto(ui, grid->row, grid->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) +{ + TUIData *data = ui->data; + UGrid *grid = &data->grid; + + data->params[0].i = grid->top; + data->params[1].i = grid->bot; + unibi_out(ui, unibi_change_scroll_region); + if (grid->left != 0 || grid->right != ui->width - 1) { + unibi_out(ui, data->unibi_ext.enable_lr_margin); + if (data->can_set_lr_margin) { + data->params[0].i = grid->left; + data->params[1].i = grid->right; + unibi_out(ui, unibi_set_lr_margin); + } else { + data->params[0].i = grid->left; + unibi_out(ui, unibi_set_left_margin_parm); + data->params[0].i = grid->right; + unibi_out(ui, unibi_set_right_margin_parm); + } + } + unibi_goto(ui, grid->row, grid->col); +} + static void reset_scroll_region(UI *ui) { TUIData *data = ui->data; + UGrid *grid = &data->grid; if (0 <= data->unibi_ext.reset_scroll_region) { unibi_out(ui, data->unibi_ext.reset_scroll_region); @@ -437,6 +487,20 @@ static void reset_scroll_region(UI *ui) data->params[1].i = ui->height - 1; unibi_out(ui, unibi_change_scroll_region); } + if (grid->left != 0 || grid->right != ui->width - 1) { + if (data->can_set_lr_margin) { + data->params[0].i = 0; + data->params[1].i = ui->width - 1; + unibi_out(ui, unibi_set_lr_margin); + } else { + data->params[0].i = 0; + unibi_out(ui, unibi_set_left_margin_parm); + data->params[0].i = ui->width - 1; + unibi_out(ui, unibi_set_right_margin_parm); + } + unibi_out(ui, data->unibi_ext.disable_lr_margin); + } + unibi_goto(ui, grid->row, grid->col); } static void tui_resize(UI *ui, Integer width, Integer height) @@ -650,19 +714,22 @@ static void tui_scroll(UI *ui, Integer count) int clear_top, clear_bot; ugrid_scroll(grid, (int)count, &clear_top, &clear_bot); - if (data->scroll_region_is_full_screen || 0 <= unibi_change_scroll_region) { + if (can_use_scroll(ui)) { + bool scroll_clears_to_current_colour = + unibi_get_bool(data->ut, unibi_back_color_erase); + // Change terminal scroll region and move cursor to the top if (!data->scroll_region_is_full_screen) { - data->params[0].i = grid->top; - data->params[1].i = grid->bot; - unibi_out(ui, unibi_change_scroll_region); + set_scroll_region(ui); } unibi_goto(ui, grid->top, grid->left); // also set default color attributes or some terminals can become funny - HlAttrs clear_attrs = EMPTY_ATTRS; - clear_attrs.foreground = grid->fg; - clear_attrs.background = grid->bg; - update_attrs(ui, clear_attrs); + if (scroll_clears_to_current_colour) { + HlAttrs clear_attrs = EMPTY_ATTRS; + clear_attrs.foreground = grid->fg; + clear_attrs.background = grid->bg; + update_attrs(ui, clear_attrs); + } if (count > 0) { if (count == 1) { @@ -686,11 +753,9 @@ static void tui_scroll(UI *ui, Integer count) } unibi_goto(ui, grid->row, grid->col); - if (grid->bg != -1) { - // Update the cleared area of the terminal if its builtin scrolling - // facility was used and the background color is not the default. This is - // required because scrolling may leave wrong background in the cleared - // area. + if (!scroll_clears_to_current_colour) { + // This is required because scrolling will leave wrong background in the + // cleared area on non-bge terminals. clear_region(ui, clear_top, clear_bot, grid->left, grid->right); } } else { @@ -1025,13 +1090,21 @@ static void fix_terminfo(TUIData *data) unibi_set_if_empty(ut, unibi_cursor_invisible, "\x1b[?25l"); unibi_set_if_empty(ut, unibi_flash_screen, "\x1b[?5h$<100/>\x1b[?5l"); unibi_set_if_empty(ut, unibi_exit_attribute_mode, "\x1b(B\x1b[m"); + unibi_set_if_empty(ut, unibi_set_tb_margin, "\x1b[%i%p1%d;%p2%dr"); + unibi_set_if_empty(ut, unibi_set_lr_margin, "\x1b[%i%p1%d;%p2%ds"); + unibi_set_if_empty(ut, unibi_set_left_margin_parm, "\x1b[%i%p1%ds"); + unibi_set_if_empty(ut, unibi_set_right_margin_parm, "\x1b[%i;%p2%ds"); unibi_set_if_empty(ut, unibi_change_scroll_region, "\x1b[%i%p1%d;%p2%dr"); unibi_set_if_empty(ut, unibi_clear_screen, "\x1b[H\x1b[2J"); unibi_set_if_empty(ut, unibi_from_status_line, "\x07"); - data->unibi_ext.reset_scroll_region = (int)unibi_add_ext_str(ut, NULL, - "\x1b[r"); + unibi_set_bool(ut, unibi_back_color_erase, true); } + data->unibi_ext.enable_lr_margin = (int)unibi_add_ext_str(ut, NULL, + "\x1b[?69h"); + data->unibi_ext.disable_lr_margin = (int)unibi_add_ext_str(ut, NULL, + "\x1b[?69l"); + data->unibi_ext.enable_bracketed_paste = (int)unibi_add_ext_str(ut, NULL, "\x1b[?2004h"); data->unibi_ext.disable_bracketed_paste = (int)unibi_add_ext_str(ut, NULL, @@ -1068,6 +1141,11 @@ static void fix_terminfo(TUIData *data) "\x1b[8;%p1%d;%p2%dt"); } + if (data->term == kTermXTerm || data->term == kTermRxvt) { + data->unibi_ext.reset_scroll_region = (int)unibi_add_ext_str(ut, NULL, + "\x1b[r"); + } + end: // Fill some empty slots with common terminal strings if (data->term == kTermiTerm) {