From 6be93f375849bcc70293fe97fa416879f6002b3e Mon Sep 17 00:00:00 2001 From: Cimbali Date: Sat, 3 Sep 2022 23:40:19 +0100 Subject: [PATCH 01/12] Add a test for terminal unwrapping The output of seq -s + 30 on 30 columns is: ``` 1+2+3+4+5+6+7+8+9+10+11+12+13+ 14+15+16+17+18+19-20+21+22+23+ 24+25+26+27+28+29+30 ``` This test checks there are at least 2 lines for the terminal and only one in the buffer. --- src/testdir/test_terminal.vim | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/testdir/test_terminal.vim b/src/testdir/test_terminal.vim index bb6fca3acf..ed2cf58e25 100644 --- a/src/testdir/test_terminal.vim +++ b/src/testdir/test_terminal.vim @@ -2358,4 +2358,19 @@ func Test_term_TextChangedT_close() augroup END endfunc +func Test_terminal_unwraps() + let width = &columns + let buf = term_start("seq -s + " .. width) + call WaitForAssert({-> assert_equal('finished', term_getstatus(buf))}) + + let l = term_getline(buf, 2) + call assert_notequal('', l) + + call TermWait(buf) + let lines = line('$') + call assert_equal(1, lines) + + exe buf . 'bwipe!' +endfunc + " vim: shiftwidth=2 sts=2 expandtab From 47a77dadc4c5ddd1e66b7397b416c6b2e28a9fbf Mon Sep 17 00:00:00 2001 From: Cimbali Date: Wed, 12 Jul 2023 14:51:05 +0100 Subject: [PATCH 02/12] Add VTermLineInfo* to libvterm sb_pushline callback --- src/libvterm/include/vterm.h | 2 +- src/libvterm/src/screen.c | 4 +++- src/libvterm/src/state.c | 16 ++++++++-------- src/libvterm/t/70screen_pushline.test | 18 ++++++++++++++++++ src/libvterm/t/harness.c | 4 ++-- src/terminal.c | 6 +++--- 6 files changed, 35 insertions(+), 15 deletions(-) create mode 100644 src/libvterm/t/70screen_pushline.test diff --git a/src/libvterm/include/vterm.h b/src/libvterm/include/vterm.h index 48deebe25e..b9bd1acc94 100644 --- a/src/libvterm/include/vterm.h +++ b/src/libvterm/include/vterm.h @@ -575,7 +575,7 @@ typedef struct { // A line was pushed off the top of the window. // "cells[cols]" contains the cells of that line. // Return value is unused. - int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user); + int (*sb_pushline)(int cols, const VTermScreenCell *cells, const VTermLineInfo *newinfo, void *user); int (*sb_popline)(int cols, VTermScreenCell *cells, void *user); int (*sb_clear)(void* user); } VTermScreenCallbacks; diff --git a/src/libvterm/src/screen.c b/src/libvterm/src/screen.c index fd76777c41..07d58280a1 100644 --- a/src/libvterm/src/screen.c +++ b/src/libvterm/src/screen.c @@ -212,11 +212,13 @@ static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user) static void sb_pushline_from_row(VTermScreen *screen, int row) { VTermPos pos; + const VTermLineInfo *info; pos.row = row; for(pos.col = 0; pos.col < screen->cols; pos.col++) vterm_screen_get_cell(screen, pos, screen->sb_buffer + pos.col); - (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata); + info = vterm_state_get_lineinfo(screen->state, row); + (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, info, screen->cbdata); } static int moverect_internal(VTermRect dest, VTermRect src, void *user) diff --git a/src/libvterm/src/state.c b/src/libvterm/src/state.c index ee36ad4c2d..94d2f2a71b 100644 --- a/src/libvterm/src/state.c +++ b/src/libvterm/src/state.c @@ -118,6 +118,7 @@ static void scroll(VTermState *state, VTermRect rect, int downward, int rightwar { int rows; int cols; + int state_scrollrect_success; if(!downward && !rightward) return; @@ -133,6 +134,13 @@ static void scroll(VTermState *state, VTermRect rect, int downward, int rightwar else if(rightward < -cols) rightward = -cols; + if(state->callbacks && state->callbacks->scrollrect) + state_scrollrect_success = (*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata); + + if(state->callbacks && !state_scrollrect_success) + vterm_scroll_rect(rect, downward, rightward, + state->callbacks->moverect, state->callbacks->erase, state->cbdata); + // Update lineinfo if full line if(rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) { int height = rect.end_row - rect.start_row - abs(downward); @@ -154,14 +162,6 @@ static void scroll(VTermState *state, VTermRect rect, int downward, int rightwar state->lineinfo[row] = zeroLineInfo; } } - - if(state->callbacks && state->callbacks->scrollrect) - if((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata)) - return; - - if(state->callbacks) - vterm_scroll_rect(rect, downward, rightward, - state->callbacks->moverect, state->callbacks->erase, state->cbdata); } static void linefeed(VTermState *state) diff --git a/src/libvterm/t/70screen_pushline.test b/src/libvterm/t/70screen_pushline.test new file mode 100644 index 0000000000..3bb26b2482 --- /dev/null +++ b/src/libvterm/t/70screen_pushline.test @@ -0,0 +1,18 @@ +INIT +RESIZE 5,10 +WANTSTATE +WANTSCREEN b + +RESET + +!Spillover text marks continuation on second line +PUSH "A"x15 +PUSH "\r\n" + ?lineinfo 0 = + ?lineinfo 1 = cont + +!Continuation mark sent to sb_pushline +PUSH "\n"x3 + sb_pushline 10 = 41 41 41 41 41 41 41 41 41 41 +PUSH "\n" + sb_pushline 10 cont = 41 41 41 41 41 diff --git a/src/libvterm/t/harness.c b/src/libvterm/t/harness.c index 5285d94ec0..61a5bdfbd0 100644 --- a/src/libvterm/t/harness.c +++ b/src/libvterm/t/harness.c @@ -590,7 +590,7 @@ static int screen_damage(VTermRect rect, void *user UNUSED) } static int want_screen_scrollback = 0; -static int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user UNUSED) +static int screen_sb_pushline(int cols, const VTermScreenCell *cells, const VTermLineInfo *lineinfo, void *user UNUSED) { int eol; int c; @@ -602,7 +602,7 @@ static int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user while(eol && !cells[eol-1].chars[0]) eol--; - printf("sb_pushline %d =", cols); + printf("sb_pushline %d %s=", cols, lineinfo->continuation ? "cont " : ""); for(c = 0; c < eol; c++) printf(" %02X", cells[c].chars[0]); printf("\n"); diff --git a/src/terminal.c b/src/terminal.c index 648fc78728..f48c9f5427 100644 --- a/src/terminal.c +++ b/src/terminal.c @@ -3457,7 +3457,7 @@ limit_scrollback(term_T *term, garray_T *gap, int update_buffer) * Handle a line that is pushed off the top of the screen. */ static int -handle_pushline(int cols, const VTermScreenCell *cells, void *user) +handle_pushline(int cols, const VTermScreenCell *cells, const VTermLineInfo *info, void *user) { term_T *term = (term_T *)user; garray_T *gap; @@ -3535,7 +3535,7 @@ handle_pushline(int cols, const VTermScreenCell *cells, void *user) *(text + text_len) = NUL; } if (update_buffer) - add_scrollback_line_to_buffer(term, text, text_len); + add_scrollback_line_to_buffer(term, text, text_len, 0); line = (sb_line_T *)gap->ga_data + gap->ga_len; line->sb_cols = len; @@ -3586,7 +3586,7 @@ handle_postponed_scrollback(term_T *term) text = pp_line->sb_text; if (text == NULL) text = (char_u *)""; - add_scrollback_line_to_buffer(term, text, (int)STRLEN(text)); + add_scrollback_line_to_buffer(term, text, (int)STRLEN(text), 0); vim_free(pp_line->sb_text); line = (sb_line_T *)term->tl_scrollback.ga_data From 708176875cd6693c488571266abbac6e2355c079 Mon Sep 17 00:00:00 2001 From: Cimbali Date: Fri, 9 Sep 2022 12:07:17 +0100 Subject: [PATCH 03/12] Unwrap terminal lines in buffer Pass the info to add_scrollback_line_to_buffer() to correctly append terminal lines to the last line or to a new line. Handle postponed callback, which requires storing the continuation info in the line struct. --- src/libvterm/src/state.c | 10 ++++++-- src/terminal.c | 49 +++++++++++++++++++++++++++++++--------- 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/src/libvterm/src/state.c b/src/libvterm/src/state.c index 94d2f2a71b..25627abf40 100644 --- a/src/libvterm/src/state.c +++ b/src/libvterm/src/state.c @@ -151,15 +151,21 @@ static void scroll(VTermState *state, VTermRect rect, int downward, int rightwar memmove(state->lineinfo + rect.start_row, state->lineinfo + rect.start_row + downward, height * sizeof(state->lineinfo[0])); - for(row = rect.end_row - downward; row < rect.end_row; row++) + for(row = rect.end_row - downward; row < rect.end_row; row++) { + if(state->callbacks && state->callbacks->setlineinfo) + (*state->callbacks->setlineinfo)(row, &zeroLineInfo, state->lineinfo + row, state->cbdata); state->lineinfo[row] = zeroLineInfo; + } } else { memmove(state->lineinfo + rect.start_row - downward, state->lineinfo + rect.start_row, height * sizeof(state->lineinfo[0])); - for(row = rect.start_row; row < rect.start_row - downward; row++) + for(row = rect.start_row; row < rect.start_row - downward; row++) { + if(state->callbacks && state->callbacks->setlineinfo) + (*state->callbacks->setlineinfo)(row, &zeroLineInfo, state->lineinfo + row, state->cbdata); state->lineinfo[row] = zeroLineInfo; + } } } } diff --git a/src/terminal.c b/src/terminal.c index f48c9f5427..1a5a95deb6 100644 --- a/src/terminal.c +++ b/src/terminal.c @@ -64,6 +64,7 @@ typedef struct sb_line_S { cellattr_T *sb_cells; // allocated cellattr_T sb_fill_attr; // for short line char_u *sb_text; // for tl_scrollback_postponed + char_u continuation; } sb_line_T; #ifdef MSWIN @@ -1872,13 +1873,14 @@ term_try_stop_job(buf_T *buf) * Add the last line of the scrollback buffer to the buffer in the window. */ static void -add_scrollback_line_to_buffer(term_T *term, char_u *text, int len) +add_scrollback_line_to_buffer(term_T *term, char_u *text, int len, int append) { buf_T *buf = term->tl_buffer; int empty = (buf->b_ml.ml_flags & ML_EMPTY); linenr_T lnum = buf->b_ml.ml_line_count; #ifdef MSWIN + char_u *tmp = text; if (!enc_utf8 && enc_codepage > 0) { WCHAR *ret = NULL; @@ -1891,13 +1893,30 @@ add_scrollback_line_to_buffer(term_T *term, char_u *text, int len) WideCharToMultiByte_alloc(enc_codepage, 0, ret, length, (char **)&text, &len, 0, 0); vim_free(ret); - ml_append_buf(term->tl_buffer, lnum, text, len, FALSE); - vim_free(text); } } - else #endif - ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE); + + if (append) + { + char_u *prev_text = ml_get_buf(buf, lnum, FALSE); + int prev_len = STRLEN(prev_text); + + char_u *both = alloc(len + prev_len + 2); + vim_strncpy(both, prev_text, prev_len + 1); + vim_strncpy(both + prev_len, text, len + 1); + + curbuf = buf; + ml_replace(lnum, both, FALSE); + curbuf = curwin->w_buffer; + } + else + ml_append_buf(buf, lnum, text, len + 1, FALSE); + +#ifdef MSWIN + if (tmp != text) + vim_free(text); +#endif if (empty) { // Delete the empty line that was in the empty buffer. @@ -1992,6 +2011,7 @@ cleanup_scrollback(term_T *term) update_snapshot(term_T *term) { VTermScreen *screen; + VTermState *state; int len; int lines_skipped = 0; VTermPos pos; @@ -2007,6 +2027,7 @@ update_snapshot(term_T *term) cleanup_scrollback(term); screen = vterm_obtain_screen(term->tl_vterm); + state = vterm_obtain_state(term->tl_vterm); fill_attr = new_fill_attr = term->tl_default_color; for (pos.row = 0; pos.row < term->tl_rows; ++pos.row) { @@ -2031,7 +2052,7 @@ update_snapshot(term_T *term) // Line was skipped, add an empty line. --lines_skipped; if (add_empty_scrollback(term, &fill_attr, 0) == OK) - add_scrollback_line_to_buffer(term, (char_u *)"", 0); + add_scrollback_line_to_buffer(term, (char_u *)"", 0, 0); } if (len == 0) @@ -2043,9 +2064,12 @@ update_snapshot(term_T *term) { garray_T ga; int width; + const VTermLineInfo *lineinfo; sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data + term->tl_scrollback.ga_len; + lineinfo = vterm_state_get_lineinfo(state, pos.row); + ga_init2(&ga, 1, 100); for (pos.col = 0; pos.col < len; pos.col += width) { @@ -2082,15 +2106,17 @@ update_snapshot(term_T *term) line->sb_cols = len; line->sb_cells = p; line->sb_fill_attr = new_fill_attr; + line->continuation = lineinfo->continuation; fill_attr = new_fill_attr; ++term->tl_scrollback.ga_len; if (ga_grow(&ga, 1) == FAIL) - add_scrollback_line_to_buffer(term, (char_u *)"", 0); + add_scrollback_line_to_buffer(term, (char_u *)"", 0, 0); else { *((char_u *)ga.ga_data + ga.ga_len) = NUL; - add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len); + add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len, + lineinfo->continuation); } ga_clear(&ga); } @@ -2105,7 +2131,7 @@ update_snapshot(term_T *term) ++pos.row) { if (add_empty_scrollback(term, &fill_attr, 0) == OK) - add_scrollback_line_to_buffer(term, (char_u *)"", 0); + add_scrollback_line_to_buffer(term, (char_u *)"", 0, 0); } term->tl_dirty_snapshot = FALSE; @@ -3535,12 +3561,13 @@ handle_pushline(int cols, const VTermScreenCell *cells, const VTermLineInfo *inf *(text + text_len) = NUL; } if (update_buffer) - add_scrollback_line_to_buffer(term, text, text_len, 0); + add_scrollback_line_to_buffer(term, text, text_len, info->continuation); line = (sb_line_T *)gap->ga_data + gap->ga_len; line->sb_cols = len; line->sb_cells = p; line->sb_fill_attr = fill_attr; + line->continuation = info->continuation; if (update_buffer) { line->sb_text = NULL; @@ -3586,7 +3613,7 @@ handle_postponed_scrollback(term_T *term) text = pp_line->sb_text; if (text == NULL) text = (char_u *)""; - add_scrollback_line_to_buffer(term, text, (int)STRLEN(text), 0); + add_scrollback_line_to_buffer(term, text, (int)STRLEN(text), pp_line->continuation); vim_free(pp_line->sb_text); line = (sb_line_T *)term->tl_scrollback.ga_data From 1c6cc35baab962a96f30fd150ef1cc9d695e1a95 Mon Sep 17 00:00:00 2001 From: Cimbali Date: Mon, 8 Aug 2022 17:00:44 +0100 Subject: [PATCH 04/12] Only un-scroll from buffer the lines that were added for snapshot Due to continuation, a buffer line can correspond to several VTerm lines. Make sure we remove the correct amount. --- src/terminal.c | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/terminal.c b/src/terminal.c index 1a5a95deb6..efa4ff4929 100644 --- a/src/terminal.c +++ b/src/terminal.c @@ -149,6 +149,7 @@ struct terminal_S { garray_T tl_scrollback; int tl_scrollback_scrolled; garray_T tl_scrollback_postponed; + int tl_scrollback_snapshot; char_u *tl_highlight_name; // replaces "Terminal"; allocated @@ -1988,16 +1989,31 @@ cleanup_scrollback(term_T *term) { sb_line_T *line; garray_T *gap; + char_u *bufline; + size_t bufline_length; curbuf = term->tl_buffer; gap = &term->tl_scrollback; - while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled - && gap->ga_len > 0) + bufline = ml_get_buf(curbuf, curbuf->b_ml.ml_line_count, FALSE); + bufline_length = STRLEN(bufline); + while (term->tl_scrollback_snapshot && gap->ga_len > 0) { - ml_delete(curbuf->b_ml.ml_line_count); line = (sb_line_T *)gap->ga_data + gap->ga_len - 1; + bufline_length -= line->sb_cols; + if (!bufline_length) + { + ml_delete(curbuf->b_ml.ml_line_count); + bufline = ml_get_buf(curbuf, curbuf->b_ml.ml_line_count, FALSE); + bufline_length = STRLEN(bufline); + } vim_free(line->sb_cells); --gap->ga_len; + --term->tl_scrollback_snapshot; + } + if (bufline_length < STRLEN(bufline)) + { + char_u *shortened = vim_strnsave(bufline, bufline_length); + ml_replace(curbuf->b_ml.ml_line_count, shortened, FALSE); } curbuf = curwin->w_buffer; if (curbuf == term->tl_buffer) @@ -2052,7 +2068,10 @@ update_snapshot(term_T *term) // Line was skipped, add an empty line. --lines_skipped; if (add_empty_scrollback(term, &fill_attr, 0) == OK) + { add_scrollback_line_to_buffer(term, (char_u *)"", 0, 0); + ++term->tl_scrollback_snapshot; + } } if (len == 0) @@ -2109,6 +2128,7 @@ update_snapshot(term_T *term) line->continuation = lineinfo->continuation; fill_attr = new_fill_attr; ++term->tl_scrollback.ga_len; + ++term->tl_scrollback_snapshot; if (ga_grow(&ga, 1) == FAIL) add_scrollback_line_to_buffer(term, (char_u *)"", 0, 0); @@ -2131,7 +2151,10 @@ update_snapshot(term_T *term) ++pos.row) { if (add_empty_scrollback(term, &fill_attr, 0) == OK) + { add_scrollback_line_to_buffer(term, (char_u *)"", 0, 0); + ++term->tl_scrollback_snapshot; + } } term->tl_dirty_snapshot = FALSE; From 151ae49edcb65ece4c2a551139c8d2ed9b69a403 Mon Sep 17 00:00:00 2001 From: Cimbali Date: Wed, 12 Jul 2023 15:26:22 +0100 Subject: [PATCH 05/12] Update limit_scrollback to take line continuations into account --- src/terminal.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/terminal.c b/src/terminal.c index efa4ff4929..52924431ce 100644 --- a/src/terminal.c +++ b/src/terminal.c @@ -3484,13 +3484,18 @@ limit_scrollback(term_T *term, garray_T *gap, int update_buffer) int todo = term->tl_buffer->b_p_twsl / 10; int i; + sb_line_T **sb_lines = (sb_line_T **)gap->ga_data; curbuf = term->tl_buffer; for (i = 0; i < todo; ++i) { - vim_free(((sb_line_T *)gap->ga_data + i)->sb_cells); - if (update_buffer) + if (update_buffer && (!sb_lines[i]->continuation || !i)) ml_delete(1); + vim_free(sb_lines[i]->sb_cells); + } + // Continue until end of wrapped line + for (; todo < gap->ga_len && sb_lines[todo]->continuation; ++todo) { + vim_free(sb_lines[todo]->sb_cells); } curbuf = curwin->w_buffer; From 8654ba0bdc49b7700dfa926639c398bca5f350bb Mon Sep 17 00:00:00 2001 From: Cimbali Date: Wed, 10 Aug 2022 15:25:36 +0100 Subject: [PATCH 06/12] Parallel tracking of buffer lines and scrollback lines --- src/terminal.c | 199 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 171 insertions(+), 28 deletions(-) diff --git a/src/terminal.c b/src/terminal.c index 52924431ce..a61f192354 100644 --- a/src/terminal.c +++ b/src/terminal.c @@ -61,6 +61,7 @@ typedef struct { typedef struct sb_line_S { int sb_cols; // can differ per line + int sb_bytes; // length in bytes of text cellattr_T *sb_cells; // allocated cellattr_T sb_fill_attr; // for short line char_u *sb_text; // for tl_scrollback_postponed @@ -150,6 +151,7 @@ struct terminal_S { int tl_scrollback_scrolled; garray_T tl_scrollback_postponed; int tl_scrollback_snapshot; + int tl_buffer_scrolled; char_u *tl_highlight_name; // replaces "Terminal"; allocated @@ -1379,6 +1381,111 @@ update_cursor(term_T *term, int redraw) } } +/* + * Find the location of a scrollbackline in the buffer + */ + void +scrollbackline_pos_in_buf(term_T *term, int row, linenr_T *lnum, int *start_col, int *start_pos) +{ + sb_line_T *lines = (sb_line_T *)term->tl_scrollback.ga_data; + linenr_T calc_lnum = term->tl_buffer_scrolled; + int calc_pos = 0; + int calc_col = 0; + int i; + + row = row + term->tl_scrollback_scrolled; + if (row < 0 || row >= term->tl_scrollback.ga_len) + return; + + if (row > term->tl_scrollback_scrolled) + { + // Lookback how far along in the top line we are + for (i = term->tl_scrollback_scrolled + 1; i > 0 && lines[i].continuation; --i) + { + calc_pos += lines[i - 1].sb_bytes; + calc_col += lines[i - 1].sb_cols; + } + i = term->tl_scrollback_scrolled + 1; + calc_lnum = term->tl_buffer_scrolled + 1; + } + else + { + i = 1; + calc_lnum = 1; + } + + for (; i <= row; ++i) + { + if (!lines[i].continuation) + { + ++calc_lnum; + calc_pos = 0; + calc_col = 0; + } + else + { + calc_pos += lines[i - 1].sb_bytes; + calc_col += lines[i - 1].sb_cols; + } + } + + *lnum = calc_lnum; + if (start_col) + *start_col = calc_col; + if (start_pos) + *start_pos = calc_pos; +} + +/* + * Find the location of a buffer line in the scrollback + */ + void +bufline_pos_in_scrollback(term_T *term, linenr_T lnum, int col, int *row, int *wrapped_col) +{ + buf_T *buf = term->tl_buffer; + sb_line_T *lines = (sb_line_T *)term->tl_scrollback.ga_data; + linenr_T calc_row = term->tl_scrollback_scrolled; + int calc_col = col; + linenr_T l; + + if (lnum > buf->b_ml.ml_line_count) + return; + + if (lnum > term->tl_buffer_scrolled) + { + calc_row = term->tl_scrollback_scrolled; + l = term->tl_buffer_scrolled + 1; + + while (calc_row < term->tl_scrollback.ga_len && lines[calc_row].continuation) + ++calc_row; + } + else + { + calc_row = 0; + l = 1; + } + + while (calc_row < term->tl_scrollback.ga_len && l < lnum) + { + ++calc_row; + if (!lines[calc_row].continuation) + ++l; + } + + while (calc_row + 1 < term->tl_scrollback.ga_len && lines[calc_row + 1].continuation + && calc_col >= lines[calc_row].sb_cols) + { + calc_col -= lines[calc_row].sb_cols; + ++calc_row; + } + + if (row) + *row = calc_row; + if (wrapped_col) + *wrapped_col = calc_col; +} + + /* * Invoked when "msg" output from a job was received. Write it to the terminal * of "buffer". @@ -1973,6 +2080,7 @@ add_empty_scrollback(term_T *term, cellattr_T *fill_attr, int lnum) } } line->sb_cols = 0; + line->sb_bytes = 0; line->sb_cells = NULL; line->sb_fill_attr = *fill_attr; ++term->tl_scrollback.ga_len; @@ -1999,7 +2107,7 @@ cleanup_scrollback(term_T *term) while (term->tl_scrollback_snapshot && gap->ga_len > 0) { line = (sb_line_T *)gap->ga_data + gap->ga_len - 1; - bufline_length -= line->sb_cols; + bufline_length -= line->sb_bytes; if (!bufline_length) { ml_delete(curbuf->b_ml.ml_line_count); @@ -2123,6 +2231,7 @@ update_snapshot(term_T *term) } } line->sb_cols = len; + line->sb_bytes = ga.ga_len; line->sb_cells = p; line->sb_fill_attr = new_fill_attr; line->continuation = lineinfo->continuation; @@ -2199,7 +2308,7 @@ may_move_terminal_to_buffer(term_T *term, int redraw) // Update the snapshot only if something changes or the buffer does not // have all the lines. if (term->tl_dirty_snapshot || term->tl_buffer->b_ml.ml_line_count - <= term->tl_scrollback_scrolled) + <= term->tl_buffer_scrolled) update_snapshot(term); // Obtain the current background color. @@ -2299,7 +2408,9 @@ cleanup_vterm(term_T *term) static void term_enter_normal_mode(void) { - term_T *term = curbuf->b_term; + term_T *term = curbuf->b_term; + linenr_T lnum; + int col; set_terminal_mode(term, TRUE); @@ -2308,15 +2419,16 @@ term_enter_normal_mode(void) // Move the window cursor to the position of the cursor in the // terminal. - curwin->w_cursor.lnum = term->tl_scrollback_scrolled - + term->tl_cursor_pos.row + 1; + lnum = term->tl_buffer_scrolled + 1 + term->tl_cursor_pos.row; + col = term->tl_cursor_pos.col; + scrollbackline_pos_in_buf(term, term->tl_cursor_pos.row, &lnum, &col, NULL); + + curwin->w_cursor.lnum = lnum; check_cursor(); - if (coladvance(term->tl_cursor_pos.col) == FAIL) + if (coladvance(col) == FAIL) coladvance(MAXCOL); curwin->w_set_curswant = TRUE; - - // Display the same lines as in the terminal. - curwin->w_topline = term->tl_scrollback_scrolled + 1; + curwin->w_topline = term->tl_buffer_scrolled + 1; } /* @@ -3484,25 +3596,27 @@ limit_scrollback(term_T *term, garray_T *gap, int update_buffer) int todo = term->tl_buffer->b_p_twsl / 10; int i; - sb_line_T **sb_lines = (sb_line_T **)gap->ga_data; + sb_line_T *sb_lines = (sb_line_T *)gap->ga_data; curbuf = term->tl_buffer; for (i = 0; i < todo; ++i) { - if (update_buffer && (!sb_lines[i]->continuation || !i)) + if (update_buffer && (!sb_lines[i].continuation || !i)) + { ml_delete(1); - vim_free(sb_lines[i]->sb_cells); + --term->tl_buffer_scrolled; + } + vim_free(sb_lines[i].sb_cells); } // Continue until end of wrapped line - for (; todo < gap->ga_len && sb_lines[todo]->continuation; ++todo) { - vim_free(sb_lines[todo]->sb_cells); - } + for (; todo < gap->ga_len && sb_lines[todo].continuation; ++todo) + vim_free(sb_lines[todo].sb_cells); curbuf = curwin->w_buffer; gap->ga_len -= todo; mch_memmove(gap->ga_data, - (sb_line_T *)gap->ga_data + todo, - sizeof(sb_line_T) * gap->ga_len); + (sb_line_T *)gap->ga_data + todo, + sizeof(sb_line_T) * gap->ga_len); if (update_buffer) term->tl_scrollback_scrolled -= todo; } @@ -3593,6 +3707,7 @@ handle_pushline(int cols, const VTermScreenCell *cells, const VTermLineInfo *inf line = (sb_line_T *)gap->ga_data + gap->ga_len; line->sb_cols = len; + line->sb_bytes = text_len; line->sb_cells = p; line->sb_fill_attr = fill_attr; line->continuation = info->continuation; @@ -3600,6 +3715,8 @@ handle_pushline(int cols, const VTermScreenCell *cells, const VTermLineInfo *inf { line->sb_text = NULL; ++term->tl_scrollback_scrolled; + if (!info->continuation) + ++term->tl_buffer_scrolled; ga_clear(&ga); // free the text } else @@ -3647,11 +3764,14 @@ handle_postponed_scrollback(term_T *term) line = (sb_line_T *)term->tl_scrollback.ga_data + term->tl_scrollback.ga_len; line->sb_cols = pp_line->sb_cols; + line->sb_bytes = pp_line->sb_bytes; line->sb_cells = pp_line->sb_cells; line->sb_fill_attr = pp_line->sb_fill_attr; line->sb_text = NULL; ++term->tl_scrollback_scrolled; ++term->tl_scrollback.ga_len; + if (!pp_line->continuation) + ++term->tl_buffer_scrolled; } ga_clear(&term->tl_scrollback_postponed); @@ -4228,16 +4348,20 @@ term_get_attr(win_T *wp, linenr_T lnum, int col) term_T *term = buf->b_term; sb_line_T *line; cellattr_T *cellattr; + int sb_line = -1; + int sb_col; - if (lnum > term->tl_scrollback.ga_len) + bufline_pos_in_scrollback(term, lnum, col, &sb_line, &sb_col); + + if (sb_line < 0) cellattr = &term->tl_default_color; else { - line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1; - if (col < 0 || col >= line->sb_cols) + line = (sb_line_T *)term->tl_scrollback.ga_data + sb_line; + if (sb_col < 0 || sb_col >= line->sb_cols) cellattr = &line->sb_fill_attr; else - cellattr = line->sb_cells + col; + cellattr = line->sb_cells + sb_col; } return cell2attr(term, wp, &cellattr->attrs, &cellattr->fg, &cellattr->bg); } @@ -5464,6 +5588,7 @@ read_dump_file(FILE *fd, VTermPos *cursor_pos) if (max_cells < ga_cell.ga_len) max_cells = ga_cell.ga_len; line->sb_cols = ga_cell.ga_len; + line->sb_bytes = ga_text.ga_len; line->sb_cells = ga_cell.ga_data; line->sb_fill_attr = term->tl_default_color; ++term->tl_scrollback.ga_len; @@ -6280,11 +6405,19 @@ f_term_getline(typval_T *argvars, typval_T *rettv) if (term->tl_vterm == NULL) { - linenr_T lnum = row + term->tl_scrollback_scrolled + 1; + linenr_T lnum = 0; + int offset = 0; + sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data + term->tl_scrollback_scrolled + row; + + scrollbackline_pos_in_buf(term, row, &lnum, NULL, &offset); // vterm is finished, get the text from the buffer if (lnum > 0 && lnum <= buf->b_ml.ml_line_count) - rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE)); + { + char_u *p = ml_get_buf(buf, lnum, FALSE); + if (STRLEN(p) >= offset + line->sb_bytes) + rettv->vval.v_string = vim_strnsave(p + offset, line->sb_bytes); + } } else { @@ -6323,7 +6456,7 @@ f_term_getscrolled(typval_T *argvars, typval_T *rettv) buf = term_get_buf(argvars, "term_getscrolled()"); if (buf == NULL) return; - rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled; + rettv->vval.v_number = buf->b_term->tl_buffer_scrolled; } /* @@ -6537,12 +6670,22 @@ f_term_scrape(typval_T *argvars, typval_T *rettv) } else { - linenr_T lnum = pos.row + term->tl_scrollback_scrolled; + int sb_row = term->tl_scrollback_scrolled + pos.row; + linenr_T lnum = 0; + int offset = 0; - if (lnum < 0 || lnum >= term->tl_scrollback.ga_len) + scrollbackline_pos_in_buf(term, pos.row, &lnum, NULL, &offset); + + if (sb_row >= term->tl_scrollback.ga_len || lnum <= 0 || lnum > buf->b_ml.ml_line_count) return; - p = ml_get_buf(buf, lnum + 1, FALSE); - line = (sb_line_T *)term->tl_scrollback.ga_data + lnum; + + line = (sb_line_T *)term->tl_scrollback.ga_data + sb_row; + p = ml_get_buf(buf, lnum, FALSE); + + if (STRLEN(p) < offset + line->sb_bytes) + return; + + p += offset; } for (pos.col = 0; pos.col < term->tl_cols; ) From 6d4c90f1ec3bf33a0a01db0f4bffec3a8a4fd325 Mon Sep 17 00:00:00 2001 From: Cimbali Date: Thu, 1 Sep 2022 17:04:08 +0100 Subject: [PATCH 07/12] Call scrollbackline_pos_in_buf() with absolute row number Instead of row number shifted by number of scrolled back rows. --- src/terminal.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/terminal.c b/src/terminal.c index a61f192354..8d68caf49d 100644 --- a/src/terminal.c +++ b/src/terminal.c @@ -1393,7 +1393,6 @@ scrollbackline_pos_in_buf(term_T *term, int row, linenr_T *lnum, int *start_col, int calc_col = 0; int i; - row = row + term->tl_scrollback_scrolled; if (row < 0 || row >= term->tl_scrollback.ga_len) return; @@ -2421,7 +2420,7 @@ term_enter_normal_mode(void) // terminal. lnum = term->tl_buffer_scrolled + 1 + term->tl_cursor_pos.row; col = term->tl_cursor_pos.col; - scrollbackline_pos_in_buf(term, term->tl_cursor_pos.row, &lnum, &col, NULL); + scrollbackline_pos_in_buf(term, term->tl_cursor_pos.row + term->tl_scrollback_scrolled, &lnum, &col, NULL); curwin->w_cursor.lnum = lnum; check_cursor(); @@ -6409,7 +6408,7 @@ f_term_getline(typval_T *argvars, typval_T *rettv) int offset = 0; sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data + term->tl_scrollback_scrolled + row; - scrollbackline_pos_in_buf(term, row, &lnum, NULL, &offset); + scrollbackline_pos_in_buf(term, row + term->tl_scrollback_scrolled, &lnum, NULL, &offset); // vterm is finished, get the text from the buffer if (lnum > 0 && lnum <= buf->b_ml.ml_line_count) @@ -6674,7 +6673,7 @@ f_term_scrape(typval_T *argvars, typval_T *rettv) linenr_T lnum = 0; int offset = 0; - scrollbackline_pos_in_buf(term, pos.row, &lnum, NULL, &offset); + scrollbackline_pos_in_buf(term, sb_row, &lnum, NULL, &offset); if (sb_row >= term->tl_scrollback.ga_len || lnum <= 0 || lnum > buf->b_ml.ml_line_count) return; From 027c26c6526338d0ba846b6733d7b3ccfdd2a442 Mon Sep 17 00:00:00 2001 From: Cimbali Date: Thu, 25 Aug 2022 17:20:45 +0200 Subject: [PATCH 08/12] Ensure the first line is not continuation on clear cmd --- src/libvterm/src/state.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libvterm/src/state.c b/src/libvterm/src/state.c index 25627abf40..89360a6863 100644 --- a/src/libvterm/src/state.c +++ b/src/libvterm/src/state.c @@ -48,10 +48,12 @@ static void erase(VTermState *state, VTermRect rect, int selective) if(rect.end_col == state->cols) { int row; /* If we're erasing the final cells of any lines, cancel the continuation - * marker on the subsequent line + * marker on the subsequent line, and similarly the marker of a fully erased line */ for(row = rect.start_row + 1; row < rect.end_row + 1 && row < state->rows; row++) state->lineinfo[row].continuation = 0; + if (rect.start_col == 0) + state->lineinfo[rect.start_row].continuation = 0; } if(state->callbacks && state->callbacks->erase) From 9e33745d471fef1a74be1d078e183d2201455a1f Mon Sep 17 00:00:00 2001 From: Cimbali Date: Tue, 6 Sep 2022 17:57:53 +0100 Subject: [PATCH 09/12] Do not lookup bufline if no scrollback For example after changes in a terminal buffer --- src/terminal.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/terminal.c b/src/terminal.c index 8d68caf49d..11ff178698 100644 --- a/src/terminal.c +++ b/src/terminal.c @@ -4350,7 +4350,8 @@ term_get_attr(win_T *wp, linenr_T lnum, int col) int sb_line = -1; int sb_col; - bufline_pos_in_scrollback(term, lnum, col, &sb_line, &sb_col); + if (term->tl_scrollback.ga_len) + bufline_pos_in_scrollback(term, lnum, col, &sb_line, &sb_col); if (sb_line < 0) cellattr = &term->tl_default_color; From c634e58e0600aa6a61e1ceb3657bf5a56ff7b80f Mon Sep 17 00:00:00 2001 From: Cimbali Date: Wed, 14 Sep 2022 10:00:17 +0100 Subject: [PATCH 10/12] Tweaks to make bytes unsigned And thus comaprable to STRLEN which returns size_t --- src/terminal.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/terminal.c b/src/terminal.c index 11ff178698..d3d0b743df 100644 --- a/src/terminal.c +++ b/src/terminal.c @@ -1385,11 +1385,11 @@ update_cursor(term_T *term, int redraw) * Find the location of a scrollbackline in the buffer */ void -scrollbackline_pos_in_buf(term_T *term, int row, linenr_T *lnum, int *start_col, int *start_pos) +scrollbackline_pos_in_buf(term_T *term, int row, linenr_T *lnum, int *start_col, size_t *start_pos) { sb_line_T *lines = (sb_line_T *)term->tl_scrollback.ga_data; linenr_T calc_lnum = term->tl_buffer_scrolled; - int calc_pos = 0; + size_t calc_pos = 0; int calc_col = 0; int i; @@ -6406,7 +6406,7 @@ f_term_getline(typval_T *argvars, typval_T *rettv) if (term->tl_vterm == NULL) { linenr_T lnum = 0; - int offset = 0; + size_t offset = 0; sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data + term->tl_scrollback_scrolled + row; scrollbackline_pos_in_buf(term, row + term->tl_scrollback_scrolled, &lnum, NULL, &offset); @@ -6672,7 +6672,7 @@ f_term_scrape(typval_T *argvars, typval_T *rettv) { int sb_row = term->tl_scrollback_scrolled + pos.row; linenr_T lnum = 0; - int offset = 0; + size_t offset = 0; scrollbackline_pos_in_buf(term, sb_row, &lnum, NULL, &offset); From 01b99194f60941e14f1f39c69c95f51f45d0fd5b Mon Sep 17 00:00:00 2001 From: Cimbali Date: Wed, 12 Jul 2023 15:48:33 +0100 Subject: [PATCH 11/12] Ensure state_scrollrect_success is initialized --- src/libvterm/src/state.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libvterm/src/state.c b/src/libvterm/src/state.c index 89360a6863..eb0ca8fd11 100644 --- a/src/libvterm/src/state.c +++ b/src/libvterm/src/state.c @@ -138,6 +138,8 @@ static void scroll(VTermState *state, VTermRect rect, int downward, int rightwar if(state->callbacks && state->callbacks->scrollrect) state_scrollrect_success = (*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata); + else if (state->callbacks) + state_scrollrect_success = 0; if(state->callbacks && !state_scrollrect_success) vterm_scroll_rect(rect, downward, rightward, From 6d96c4da987694c72f924cbdee7a3fdf5eec9162 Mon Sep 17 00:00:00 2001 From: Cimbali Date: Wed, 14 Sep 2022 14:11:57 +0100 Subject: [PATCH 12/12] Try to make test less flaky for CIs - comment - fix width during test - do not rely on seq options being available everywhere - use a vertical split rather than columns as suggested - wait for non-empty first line as other tests do, instead of finished status - compare line contents instead of line numbers in case of echoing --- src/testdir/test_terminal.vim | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/testdir/test_terminal.vim b/src/testdir/test_terminal.vim index ed2cf58e25..ec3a1e7cd4 100644 --- a/src/testdir/test_terminal.vim +++ b/src/testdir/test_terminal.vim @@ -2359,18 +2359,25 @@ func Test_term_TextChangedT_close() endfunc func Test_terminal_unwraps() - let width = &columns - let buf = term_start("seq -s + " .. width) - call WaitForAssert({-> assert_equal('finished', term_getstatus(buf))}) + 30vnew + + redraw + let buf = term_start("echo 1+2+3+4+5+6+7+8+9+10+11+12+13+14+15") + call WaitForAssert({-> assert_notequal('', term_getline(buf, 1))}) + + " A long wrapped line appears as 2 lines in libvterm + let l = term_getline(buf, 1) + call assert_equal('1+2+3+4+5+6+7+8+9+10+11+12+13+', l) let l = term_getline(buf, 2) - call assert_notequal('', l) + call assert_equal('14+15', l) call TermWait(buf) - let lines = line('$') - call assert_equal(1, lines) + " It should appear as a single buffer line in vim + let lastline = getline('$') + call assert_equal('1+2+3+4+5+6+7+8+9+10+11+12+13+14+15', lastline) - exe buf . 'bwipe!' + bwipe! endfunc " vim: shiftwidth=2 sts=2 expandtab