patch 8.0.0885: terminal window scrollback is stored inefficiently
Problem: Terminal window scrollback is stored inefficiently.
Solution: Store the text in the Vim buffer.
diff --git a/src/terminal.c b/src/terminal.c
index 0f70cc6..99b82b2 100644
--- a/src/terminal.c
+++ b/src/terminal.c
@@ -36,8 +36,6 @@
* that buffer, attributes come from the scrollback buffer tl_scrollback.
*
* TODO:
- * - For the scrollback buffer store lines in the buffer, only attributes in
- * tl_scrollback.
* - When the job ends:
* - Need an option or argument to drop the window+buffer right away, to be
* used for a shell or Vim. 'termfinish'; "close", "open" (open window when
@@ -97,9 +95,16 @@
#include "libvterm/include/vterm.h"
+/* This is VTermScreenCell without the characters, thus much smaller. */
+typedef struct {
+ VTermScreenCellAttrs attrs;
+ char width;
+ VTermColor fg, bg;
+} cellattr_T;
+
typedef struct sb_line_S {
- int sb_cols; /* can differ per line */
- VTermScreenCell *sb_cells; /* allocated */
+ int sb_cols; /* can differ per line */
+ cellattr_T *sb_cells; /* allocated */
} sb_line_T;
/* typedef term_T in structs.h */
@@ -688,29 +693,11 @@
* Add the last line of the scrollback buffer to the buffer in the window.
*/
static void
-add_scrollback_line_to_buffer(term_T *term)
+add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
{
linenr_T lnum = term->tl_scrollback.ga_len - 1;
- sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
- garray_T ga;
- int c;
- int col;
- int i;
- ga_init2(&ga, 1, 100);
- for (col = 0; col < line->sb_cols; col += line->sb_cells[col].width)
- {
- if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
- goto failed;
- for (i = 0; (c = line->sb_cells[col].chars[i]) > 0 || i == 0; ++i)
- ga.ga_len += mb_char2bytes(c == NUL ? ' ' : c,
- (char_u *)ga.ga_data + ga.ga_len);
- }
- if (ga_grow(&ga, 1) == FAIL)
- goto failed;
- *((char_u *)ga.ga_data + ga.ga_len) = NUL;
- ml_append_buf(term->tl_buffer, lnum, ga.ga_data, ga.ga_len + 1, FALSE);
-
+ ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE);
if (lnum == 0)
{
/* Delete the empty line that was in the empty buffer. */
@@ -718,14 +705,11 @@
ml_delete(2, FALSE);
curbuf = curwin->w_buffer;
}
-
-failed:
- ga_clear(&ga);
}
/*
* Add the current lines of the terminal to scrollback and to the buffer.
- * Called after the job has ended and when switching to Terminal mode.
+ * Called after the job has ended and when switching to Terminal-Normal mode.
*/
static void
move_terminal_to_buffer(term_T *term)
@@ -735,7 +719,7 @@
int lines_skipped = 0;
VTermPos pos;
VTermScreenCell cell;
- VTermScreenCell *p;
+ cellattr_T *p;
VTermScreen *screen;
if (term->tl_vterm == NULL)
@@ -766,28 +750,61 @@
line->sb_cells = NULL;
++term->tl_scrollback.ga_len;
- add_scrollback_line_to_buffer(term);
+ add_scrollback_line_to_buffer(term, (char_u *)"", 0);
}
}
- p = (VTermScreenCell *)alloc((int)sizeof(VTermScreenCell) * len);
+ p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
if (p != NULL && ga_grow(&term->tl_scrollback, 1) == OK)
{
- sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
+ garray_T ga;
+ int width;
+ sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
+ term->tl_scrollback.ga_len;
- for (pos.col = 0; pos.col < len; ++pos.col)
+ ga_init2(&ga, 1, 100);
+ for (pos.col = 0; pos.col < len; pos.col += width)
{
if (vterm_screen_get_cell(screen, pos, &cell) == 0)
- vim_memset(p + pos.col, 0, sizeof(cell));
+ {
+ width = 1;
+ vim_memset(p + pos.col, 0, sizeof(cellattr_T));
+ if (ga_grow(&ga, 1) == OK)
+ ga.ga_len += mb_char2bytes(' ',
+ (char_u *)ga.ga_data + ga.ga_len);
+ }
else
- p[pos.col] = cell;
+ {
+ width = cell.width;
+
+ p[pos.col].width = cell.width;
+ p[pos.col].attrs = cell.attrs;
+ p[pos.col].fg = cell.fg;
+ p[pos.col].bg = cell.bg;
+
+ if (ga_grow(&ga, MB_MAXBYTES) == OK)
+ {
+ int i;
+ int c;
+
+ for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i)
+ ga.ga_len += mb_char2bytes(c == NUL ? ' ' : c,
+ (char_u *)ga.ga_data + ga.ga_len);
+ }
+ }
}
line->sb_cols = len;
line->sb_cells = p;
++term->tl_scrollback.ga_len;
- add_scrollback_line_to_buffer(term);
+ if (ga_grow(&ga, 1) == FAIL)
+ add_scrollback_line_to_buffer(term, (char_u *)"", 0);
+ else
+ {
+ *((char_u *)ga.ga_data + ga.ga_len) = NUL;
+ add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
+ }
+ ga_clear(&ga);
}
else
vim_free(p);
@@ -1312,13 +1329,15 @@
term_T *term = (term_T *)user;
/* TODO: Limit the number of lines that are stored. */
- /* TODO: put the text in the buffer. */
if (ga_grow(&term->tl_scrollback, 1) == OK)
{
- VTermScreenCell *p = NULL;
+ cellattr_T *p = NULL;
int len = 0;
int i;
+ int c;
+ int col;
sb_line_T *line;
+ garray_T ga;
/* do not store empty cells at the end */
for (i = 0; i < cols; ++i)
@@ -1326,9 +1345,34 @@
len = i + 1;
if (len > 0)
- p = (VTermScreenCell *)alloc((int)sizeof(VTermScreenCell) * len);
+ p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
if (p != NULL)
- mch_memmove(p, cells, sizeof(VTermScreenCell) * len);
+ {
+ ga_init2(&ga, 1, 100);
+ for (col = 0; col < len; col += cells[col].width)
+ {
+ if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
+ {
+ ga.ga_len = 0;
+ break;
+ }
+ for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
+ ga.ga_len += mb_char2bytes(c == NUL ? ' ' : c,
+ (char_u *)ga.ga_data + ga.ga_len);
+ p[col].width = cells[col].width;
+ p[col].attrs = cells[col].attrs;
+ p[col].fg = cells[col].fg;
+ p[col].bg = cells[col].bg;
+ }
+ }
+ if (ga_grow(&ga, 1) == FAIL)
+ add_scrollback_line_to_buffer(term, (char_u *)"", 0);
+ else
+ {
+ *((char_u *)ga.ga_data + ga.ga_len) = NUL;
+ add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
+ }
+ ga_clear(&ga);
line = (sb_line_T *)term->tl_scrollback.ga_data
+ term->tl_scrollback.ga_len;
@@ -1336,8 +1380,6 @@
line->sb_cells = p;
++term->tl_scrollback.ga_len;
++term->tl_scrollback_scrolled;
-
- add_scrollback_line_to_buffer(term);
}
return 0; /* ignored */
}
@@ -1510,19 +1552,19 @@
* Convert the attributes of a vterm cell into an attribute index.
*/
static int
-cell2attr(VTermScreenCell *cell)
+cell2attr(VTermScreenCellAttrs cellattrs, VTermColor cellfg, VTermColor cellbg)
{
int attr = 0;
- if (cell->attrs.bold)
+ if (cellattrs.bold)
attr |= HL_BOLD;
- if (cell->attrs.underline)
+ if (cellattrs.underline)
attr |= HL_UNDERLINE;
- if (cell->attrs.italic)
+ if (cellattrs.italic)
attr |= HL_ITALIC;
- if (cell->attrs.strike)
+ if (cellattrs.strike)
attr |= HL_STANDOUT;
- if (cell->attrs.reverse)
+ if (cellattrs.reverse)
attr |= HL_INVERSE;
#ifdef FEAT_GUI
@@ -1530,8 +1572,8 @@
{
guicolor_T fg, bg;
- fg = gui_mch_get_rgb_color(cell->fg.red, cell->fg.green, cell->fg.blue);
- bg = gui_mch_get_rgb_color(cell->bg.red, cell->bg.green, cell->bg.blue);
+ fg = gui_mch_get_rgb_color(cellfg.red, cellfg.green, cellfg.blue);
+ bg = gui_mch_get_rgb_color(cellbg.red, cellbg.green, cellbg.blue);
return get_gui_attr_idx(attr, fg, bg);
}
else
@@ -1541,8 +1583,8 @@
{
guicolor_T fg, bg;
- fg = gui_get_rgb_color_cmn(cell->fg.red, cell->fg.green, cell->fg.blue);
- bg = gui_get_rgb_color_cmn(cell->bg.red, cell->bg.green, cell->bg.blue);
+ fg = gui_get_rgb_color_cmn(cellfg.red, cellfg.green, cellfg.blue);
+ bg = gui_get_rgb_color_cmn(cellbg.red, cellbg.green, cellbg.blue);
return get_tgc_attr_idx(attr, fg, bg);
}
@@ -1550,8 +1592,8 @@
#endif
{
int bold = MAYBE;
- int fg = color2index(&cell->fg, TRUE, &bold);
- int bg = color2index(&cell->bg, FALSE, &bold);
+ int fg = color2index(&cellfg, TRUE, &bold);
+ int bg = color2index(&cellbg, FALSE, &bold);
/* with 8 colors set the bold attribute to get a bright foreground */
if (bold == TRUE)
@@ -1660,7 +1702,7 @@
ScreenLines[off] = c;
#endif
}
- ScreenAttrs[off] = cell2attr(&cell);
+ ScreenAttrs[off] = cell2attr(cell.attrs, cell.fg, cell.bg);
++pos.col;
++off;
@@ -1734,15 +1776,17 @@
int
term_get_attr(buf_T *buf, linenr_T lnum, int col)
{
- term_T *term = buf->b_term;
- sb_line_T *line;
+ term_T *term = buf->b_term;
+ sb_line_T *line;
+ cellattr_T *cellattr;
if (lnum > term->tl_scrollback.ga_len)
return 0;
line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
if (col >= line->sb_cols)
return 0;
- return cell2attr(line->sb_cells + col);
+ cellattr = line->sb_cells + col;
+ return cell2attr(cellattr->attrs, cellattr->fg, cellattr->bg);
}
/*
@@ -2086,21 +2130,36 @@
VTermPos pos;
list_T *l;
term_T *term;
+ char_u *p;
+ sb_line_T *line;
if (rettv_list_alloc(rettv) == FAIL)
return;
if (buf == NULL)
return;
term = buf->b_term;
- if (term->tl_vterm != NULL)
- screen = vterm_obtain_screen(term->tl_vterm);
l = rettv->vval.v_list;
pos.row = get_row_number(&argvars[1], term);
+
+ if (term->tl_vterm != NULL)
+ screen = vterm_obtain_screen(term->tl_vterm);
+ else
+ {
+ linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
+
+ if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
+ return;
+ p = ml_get_buf(buf, lnum + 1, FALSE);
+ line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
+ }
+
for (pos.col = 0; pos.col < term->tl_cols; )
{
dict_T *dcell;
- VTermScreenCell cell;
+ int width;
+ VTermScreenCellAttrs attrs;
+ VTermColor fg, bg;
char_u rgb[8];
char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
int off = 0;
@@ -2108,43 +2167,57 @@
if (screen == NULL)
{
- linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
- sb_line_T *line;
+ cellattr_T *cellattr;
+ int len;
/* vterm has finished, get the cell from scrollback */
- if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
- break;
- line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
if (pos.col >= line->sb_cols)
break;
- cell = line->sb_cells[pos.col];
+ cellattr = line->sb_cells + pos.col;
+ width = cellattr->width;
+ attrs = cellattr->attrs;
+ fg = cellattr->fg;
+ bg = cellattr->bg;
+ len = MB_PTR2LEN(p);
+ mch_memmove(mbs, p, len);
+ mbs[len] = NUL;
+ p += len;
}
- else if (vterm_screen_get_cell(screen, pos, &cell) == 0)
- break;
+ else
+ {
+ VTermScreenCell cell;
+ if (vterm_screen_get_cell(screen, pos, &cell) == 0)
+ break;
+ for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
+ {
+ if (cell.chars[i] == 0)
+ break;
+ off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
+ }
+ mbs[off] = NUL;
+ width = cell.width;
+ attrs = cell.attrs;
+ fg = cell.fg;
+ bg = cell.bg;
+ }
dcell = dict_alloc();
list_append_dict(l, dcell);
- for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
- {
- if (cell.chars[i] == 0)
- break;
- off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
- }
- mbs[off] = NUL;
dict_add_nr_str(dcell, "chars", 0, mbs);
vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
- cell.fg.red, cell.fg.green, cell.fg.blue);
+ fg.red, fg.green, fg.blue);
dict_add_nr_str(dcell, "fg", 0, rgb);
vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
- cell.bg.red, cell.bg.green, cell.bg.blue);
+ bg.red, bg.green, bg.blue);
dict_add_nr_str(dcell, "bg", 0, rgb);
- dict_add_nr_str(dcell, "attr", cell2attr(&cell), NULL);
- dict_add_nr_str(dcell, "width", cell.width, NULL);
+ dict_add_nr_str(dcell, "attr",
+ cell2attr(attrs, fg, bg), NULL);
+ dict_add_nr_str(dcell, "width", width, NULL);
++pos.col;
- if (cell.width == 2)
+ if (width == 2)
++pos.col;
}
}
diff --git a/src/testdir/test_terminal.vim b/src/testdir/test_terminal.vim
index 863a784..845f06b 100644
--- a/src/testdir/test_terminal.vim
+++ b/src/testdir/test_terminal.vim
@@ -97,7 +97,7 @@
endfunc
func Test_terminal_nasty_cb()
- let cmd = Get_cat_cmd()
+ let cmd = Get_cat_123_cmd()
let g:buf = term_start(cmd, {'exit_cb': function('s:Nasty_exit_cb')})
let g:job = term_getjob(g:buf)
@@ -135,7 +135,7 @@
call assert_equal('123', l)
endfunc
-func Get_cat_cmd()
+func Get_cat_123_cmd()
if has('win32')
return 'cmd /c "cls && color 2 && echo 123"'
else
@@ -144,8 +144,8 @@
endif
endfunc
-func Test_terminal_scrape()
- let cmd = Get_cat_cmd()
+func Test_terminal_scrape_123()
+ let cmd = Get_cat_123_cmd()
let buf = term_start(cmd)
let termlist = term_list()
@@ -172,8 +172,46 @@
call delete('Xtext')
endfunc
+func Test_terminal_scrape_multibyte()
+ if !has('multi_byte')
+ return
+ endif
+ call writefile(["léttまrs"], 'Xtext')
+ if has('win32')
+ let cmd = 'cmd /c "type Xtext"'
+ else
+ let cmd = "cat Xtext"
+ endif
+ let buf = term_start(cmd)
+
+ call term_wait(buf)
+ if has('win32')
+ " TODO: this should not be needed
+ sleep 100m
+ endif
+
+ let l = term_scrape(buf, 1)
+ call assert_true(len(l) >= 7)
+ call assert_equal('l', l[0].chars)
+ call assert_equal('é', l[1].chars)
+ call assert_equal(1, l[1].width)
+ call assert_equal('t', l[2].chars)
+ call assert_equal('t', l[3].chars)
+ call assert_equal('ま', l[4].chars)
+ call assert_equal(2, l[4].width)
+ call assert_equal('r', l[5].chars)
+ call assert_equal('s', l[6].chars)
+
+ let g:job = term_getjob(buf)
+ call WaitFor('job_status(g:job) == "dead"')
+ call term_wait(buf)
+
+ exe buf . 'bwipe'
+ call delete('Xtext')
+endfunc
+
func Test_terminal_size()
- let cmd = Get_cat_cmd()
+ let cmd = Get_cat_123_cmd()
exe '5terminal ' . cmd
let size = term_getsize('')
diff --git a/src/version.c b/src/version.c
index e318114..2b769ea 100644
--- a/src/version.c
+++ b/src/version.c
@@ -770,6 +770,8 @@
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 885,
+/**/
884,
/**/
883,