From: Cimbali Date: Fri, 10 Apr 2026 22:02:09 +0000 (+0000) Subject: patch 9.2.0336: libvterm: no terminal reflow support X-Git-Tag: v9.2.0336^0 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;ds=inline;p=thirdparty%2Fvim.git patch 9.2.0336: libvterm: no terminal reflow support Problem: libvterm: no terminal reflow support Solution: Support for reflowing, sync libvterm to revision 843 (Cimbali) fixes: #2865 closes: #8365 closes: #19863 Co-authored-by: Paul "LeoNerd" Evans Signed-off-by: Cimbali Signed-off-by: Christian Brabandt --- diff --git a/Filelist b/Filelist index 6a21157647..1522e33740 100644 --- a/Filelist +++ b/Filelist @@ -440,6 +440,7 @@ SRC_ALL = \ src/libvterm/t/66screen_extent.test \ src/libvterm/t/67screen_dbl_wh.test \ src/libvterm/t/68screen_termprops.test \ + src/libvterm/t/69screen_pushline.test \ src/libvterm/t/69screen_reflow.test \ src/libvterm/t/90vttest_01-movement-1.test \ src/libvterm/t/90vttest_01-movement-2.test \ diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt index e0976453d9..2b3decac78 100644 --- a/runtime/doc/version9.txt +++ b/runtime/doc/version9.txt @@ -1,4 +1,4 @@ -*version9.txt* For Vim version 9.2. Last change: 2026 Apr 09 +*version9.txt* For Vim version 9.2. Last change: 2026 Apr 10 VIM REFERENCE MANUAL by Bram Moolenaar @@ -52615,6 +52615,7 @@ Other ~ bypassing the shell completely. - Allow mouse clickable regions in the |status-line| using the |stl-%[FuncName]| atom. +- Enable reflow support in the |:terminal|. Platform specific ~ ----------------- diff --git a/src/libvterm/README b/src/libvterm/README index 56c6e222be..875c448cbb 100644 --- a/src/libvterm/README +++ b/src/libvterm/README @@ -7,7 +7,7 @@ The original can be found: https://github.com/neovim/libvterm Modifications: -- revisions up to 839 have been included +- revisions up to 843 have been included - Added a .gitignore file. - use TRUE and FALSE instead of true and false - use int or unsigned int instead of bool diff --git a/src/libvterm/include/vterm.h b/src/libvterm/include/vterm.h index 48deebe25e..54e9e49242 100644 --- a/src/libvterm/include/vterm.h +++ b/src/libvterm/include/vterm.h @@ -452,6 +452,8 @@ typedef struct { int (*resize)(int rows, int cols, VTermStateFields *fields, void *user); int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user); int (*sb_clear)(void *user); + // ABI-compat only enabled if vterm_state_callbacks_has_premove() is invoked + int (*premove)(VTermRect dest, void *user); } VTermStateCallbacks; // VIM: added @@ -488,6 +490,8 @@ VTermState *vterm_obtain_state(VTerm *vt); void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user); void *vterm_state_get_cbdata(VTermState *state); +void vterm_state_callbacks_has_premove(VTermState *state); + void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermStateFallbacks *fallbacks, void *user); void *vterm_state_get_unrecognised_fbdata(VTermState *state); @@ -578,6 +582,8 @@ typedef struct { int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user); int (*sb_popline)(int cols, VTermScreenCell *cells, void *user); int (*sb_clear)(void* user); + /* ABI-compat this is only used if vterm_screen_callbacks_has_pushline4() is called */ + int (*sb_pushline4)(int cols, const VTermScreenCell *cells, int continuation, void *user); } VTermScreenCallbacks; // Return the screen of the vterm. @@ -590,6 +596,8 @@ VTermScreen *vterm_obtain_screen(VTerm *vt); void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user); void *vterm_screen_get_cbdata(VTermScreen *screen); +void vterm_screen_callbacks_has_pushline4(VTermScreen *screen); + void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermStateFallbacks *fallbacks, void *user); void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen); diff --git a/src/libvterm/src/screen.c b/src/libvterm/src/screen.c index fd76777c41..5ed8dd48ae 100644 --- a/src/libvterm/src/screen.c +++ b/src/libvterm/src/screen.c @@ -49,6 +49,7 @@ struct VTermScreen const VTermScreenCallbacks *callbacks; void *cbdata; + int callbacks_has_pushline4; VTermDamageSize damage_merge; /* start_row == -1 => no damage */ @@ -209,28 +210,41 @@ static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user) return 1; } -static void sb_pushline_from_row(VTermScreen *screen, int row) +static void sb_pushline_from_row(VTermScreen *screen, int row, int continuation) { VTermPos pos; 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); + if(screen->callbacks_has_pushline4 && screen->callbacks->sb_pushline4) + (screen->callbacks->sb_pushline4)(screen->cols, screen->sb_buffer, continuation, screen->cbdata); + else + (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata); } -static int moverect_internal(VTermRect dest, VTermRect src, void *user) +static int premove(VTermRect rect, void *user) { VTermScreen *screen = user; - if(screen->callbacks && screen->callbacks->sb_pushline && - dest.start_row == 0 && dest.start_col == 0 && // starts top-left corner - dest.end_col == screen->cols && // full width + if(((screen->callbacks && screen->callbacks->sb_pushline) || + (screen->callbacks_has_pushline4 && screen->callbacks && screen->callbacks->sb_pushline4)) && + rect.start_row == 0 && rect.start_col == 0 && // starts top-left corner + rect.end_col == screen->cols && // full width screen->buffer == screen->buffers[BUFIDX_PRIMARY]) { // not altscreen - for(int row = 0; row < src.start_row; row++) - sb_pushline_from_row(screen, row); + for(int row = 0; row < rect.end_row; row++) { + const VTermLineInfo *lineinfo = vterm_state_get_lineinfo(screen->state, row); + sb_pushline_from_row(screen, row, lineinfo->continuation); + } } + return 1; +} + +static int moverect_internal(VTermRect dest, VTermRect src, void *user) +{ + VTermScreen *screen = user; + int cols = src.end_col - src.start_col; int downward = src.start_row - dest.start_row; @@ -685,9 +699,12 @@ static void resize_buffer(VTermScreen *screen, int bufidx, int new_rows, int new if(old_row >= 0 && bufidx == BUFIDX_PRIMARY) { /* Push spare lines to scrollback buffer */ - if(screen->callbacks && screen->callbacks->sb_pushline) - for(int row = 0; row <= old_row; row++) - sb_pushline_from_row(screen, row); + if((screen->callbacks && screen->callbacks->sb_pushline) || + (screen->callbacks_has_pushline4 && screen->callbacks && screen->callbacks->sb_pushline4)) + for(int row = 0; row <= old_row; row++) { + const VTermLineInfo *lineinfo = old_lineinfo + row; + sb_pushline_from_row(screen, row, lineinfo->continuation); + } if(active) statefields->pos.row -= (old_row + 1); } @@ -880,6 +897,7 @@ static VTermStateCallbacks state_cbs = { &resize, // resize &setlineinfo, // setlineinfo &sb_clear, //sb_clear + &premove, // premove }; /* @@ -914,6 +932,7 @@ static VTermScreen *screen_new(VTerm *vt) screen->callbacks = NULL; screen->cbdata = NULL; + screen->callbacks_has_pushline4 = 0; screen->buffers[BUFIDX_PRIMARY] = alloc_buffer(screen, rows, cols); @@ -927,6 +946,7 @@ static VTermScreen *screen_new(VTerm *vt) } vterm_state_set_callbacks(screen->state, &state_cbs, screen); + vterm_state_callbacks_has_premove(screen->state); return screen; } @@ -1127,6 +1147,11 @@ void *vterm_screen_get_cbdata(VTermScreen *screen) return screen->cbdata; } +void vterm_screen_callbacks_has_pushline4(VTermScreen *screen) +{ + screen->callbacks_has_pushline4 = 1; +} + void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermStateFallbacks *fallbacks, void *user) { vterm_state_set_unrecognised_fallbacks(screen->state, fallbacks, user); diff --git a/src/libvterm/src/state.c b/src/libvterm/src/state.c index fca833326e..34b37445c2 100644 --- a/src/libvterm/src/state.c +++ b/src/libvterm/src/state.c @@ -78,6 +78,7 @@ static VTermState *vterm_state_new(VTerm *vt) state->callbacks = NULL; state->cbdata = NULL; + state->callbacks_has_premove = 0; state->selection.callbacks = NULL; state->selection.user = NULL; @@ -133,6 +134,33 @@ static void scroll(VTermState *state, VTermRect rect, int downward, int rightwar else if(rightward < -cols) rightward = -cols; + if(state->callbacks_has_premove && state->callbacks && state->callbacks->premove) { + // TODO: technically this logic is wrong if both downward != 0 and rightward != 0 + + /* Work out what subsection of the destination area is about to be destroyed */ + if(downward > 0) + /* about to destroy the top */ + (*state->callbacks->premove)((VTermRect){ + .start_row = rect.start_row, .end_row = rect.start_row + downward, + .start_col = rect.start_col, .end_col = rect.end_col}, state->cbdata); + else if(downward < 0) + /* about to destroy the bottom */ + (*state->callbacks->premove)((VTermRect){ + .start_row = rect.end_row + downward, .end_row = rect.end_row, + .start_col = rect.start_col, .end_col = rect.end_col}, state->cbdata); + + if(rightward > 0) + /* about to destroy the left */ + (*state->callbacks->premove)((VTermRect){ + .start_row = rect.start_row, .end_row = rect.end_row, + .start_col = rect.start_col, .end_col = rect.start_col + rightward}, state->cbdata); + else if(rightward < 0) + /* about to destroy the right */ + (*state->callbacks->premove)((VTermRect){ + .start_row = rect.start_row, .end_row = rect.end_row, + .start_col = rect.end_col + rightward, .end_col = rect.end_col}, 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); @@ -2319,6 +2347,11 @@ void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *cal } } +void vterm_state_callbacks_has_premove(VTermState *state) +{ + state->callbacks_has_premove = 1; +} + void *vterm_state_get_cbdata(VTermState *state) { return state->cbdata; diff --git a/src/libvterm/src/vterm_internal.h b/src/libvterm/src/vterm_internal.h index 0f80fa4315..b9830b9db3 100644 --- a/src/libvterm/src/vterm_internal.h +++ b/src/libvterm/src/vterm_internal.h @@ -70,6 +70,7 @@ struct VTermState const VTermStateCallbacks *callbacks; void *cbdata; + int callbacks_has_premove; const VTermStateFallbacks *fallbacks; void *fbdata; diff --git a/src/libvterm/t/12state_scroll.test b/src/libvterm/t/12state_scroll.test index c1f2791d00..5605131bc7 100644 --- a/src/libvterm/t/12state_scroll.test +++ b/src/libvterm/t/12state_scroll.test @@ -127,24 +127,28 @@ PUSH "\e[100;105r\eD" PUSH "\e[5;2r\eD" RESET -WANTSTATE -s+me +WANTSTATE -s+Pme !Scroll Down move+erase emulation PUSH "\e[S" + premove 0..1,0..80 moverect 1..25,0..80 -> 0..24,0..80 erase 24..25,0..80 ?cursor = 0,0 PUSH "\e[2S" + premove 0..2,0..80 moverect 2..25,0..80 -> 0..23,0..80 erase 23..25,0..80 ?cursor = 0,0 !Scroll Up move+erase emulation PUSH "\e[T" + premove 24..25,0..80 moverect 0..24,0..80 -> 1..25,0..80 erase 0..1,0..80 ?cursor = 0,0 PUSH "\e[2T" + premove 23..25,0..80 moverect 0..23,0..80 -> 2..25,0..80 erase 0..2,0..80 ?cursor = 0,0 diff --git a/src/libvterm/t/13state_edit.test b/src/libvterm/t/13state_edit.test index d3f3e9e40b..84d22cc9ac 100644 --- a/src/libvterm/t/13state_edit.test +++ b/src/libvterm/t/13state_edit.test @@ -268,7 +268,7 @@ PUSH "\e[2\"q" PUSH "\eP\$q\"q\e\\" output "\eP1\$r2\"q\e\\" -WANTSTATE -s+m +WANTSTATE -s+Pm !ICH move+erase emuation RESET @@ -278,12 +278,14 @@ PUSH "ACD" PUSH "\e[2D" ?cursor = 0,1 PUSH "\e[@" + premove 0..1,79..80 moverect 0..1,1..79 -> 0..1,2..80 erase 0..1,1..2 ?cursor = 0,1 PUSH "B" ?cursor = 0,2 PUSH "\e[3@" + premove 0..1,77..80 moverect 0..1,2..77 -> 0..1,5..80 erase 0..1,2..5 @@ -295,10 +297,12 @@ PUSH "ABBC" PUSH "\e[3D" ?cursor = 0,1 PUSH "\e[P" + premove 0..1,1..2 moverect 0..1,2..80 -> 0..1,1..79 erase 0..1,79..80 ?cursor = 0,1 PUSH "\e[3P" + premove 0..1,1..4 moverect 0..1,4..80 -> 0..1,1..77 erase 0..1,77..80 ?cursor = 0,1 diff --git a/src/libvterm/t/62screen_damage.test b/src/libvterm/t/62screen_damage.test index 3b1b238527..2f11c759dd 100644 --- a/src/libvterm/t/62screen_damage.test +++ b/src/libvterm/t/62screen_damage.test @@ -146,9 +146,9 @@ PUSH "\e[25H\r\nABCDE\b\b\b\e[2P\r\n" sb_pushline 80 = moverect 1..25,0..80 -> 0..24,0..80 damage 24..25,0..80 = 24<41 42 43 44 45> + sb_pushline 80 = moverect 24..25,4..80 -> 24..25,2..78 damage 24..25,78..80 - sb_pushline 80 = DAMAGEFLUSH moverect 1..25,0..80 -> 0..24,0..80 damage 24..25,0..80 diff --git a/src/libvterm/t/69screen_pushline.test b/src/libvterm/t/69screen_pushline.test new file mode 100644 index 0000000000..735ea29479 --- /dev/null +++ b/src/libvterm/t/69screen_pushline.test @@ -0,0 +1,17 @@ +INIT +WANTSTATE +WANTSCREEN b + +RESET + +!Spillover text marks continuation on second line +PUSH "A"x85 +PUSH "\r\n" + ?lineinfo 0 = + ?lineinfo 1 = cont + +!Continuation mark sent to sb_pushline +PUSH "\n"x23 + sb_pushline 80 = 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 +PUSH "\n" + sb_pushline 80 cont = 41 41 41 41 41 diff --git a/src/libvterm/t/harness.c b/src/libvterm/t/harness.c index 5285d94ec0..6fc34fe043 100644 --- a/src/libvterm/t/harness.c +++ b/src/libvterm/t/harness.c @@ -326,6 +326,18 @@ static int movecursor(VTermPos pos, VTermPos oldpos UNUSED, int visible UNUSED, return 1; } +static int want_premove = 0; +static int premove(VTermRect rect, void *user UNUSED) +{ + if(!want_premove) + return 0; + + printf("premove %d..%d,%d..%d\n", + rect.start_row, rect.end_row, rect.start_col, rect.end_col); + + return 1; +} + static int want_scrollrect = 0; static int scrollrect(VTermRect rect, int downward, int rightward, void *user UNUSED) { @@ -509,6 +521,7 @@ VTermStateCallbacks state_cbs = { NULL, // resize state_setlineinfo, // setlineinfo state_sb_clear, // sb_clear + premove, // premove }; static int selection_set(VTermSelectionMask mask, VTermStringFragment frag, void *user UNUSED) @@ -590,7 +603,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_pushline4(int cols, const VTermScreenCell *cells, int continuation, void *user UNUSED) { int eol; int c; @@ -602,7 +615,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, continuation ? " cont" : ""); for(c = 0; c < eol; c++) printf(" %02X", cells[c].chars[0]); printf("\n"); @@ -647,9 +660,10 @@ VTermScreenCallbacks screen_cbs = { settermprop, // settermprop NULL, // bell NULL, // resize - screen_sb_pushline, // sb_pushline + NULL, // sb_pushline screen_sb_popline, // sb_popline screen_sb_clear, // sb_clear + screen_sb_pushline4, // sb_pushline4 }; int main(int argc UNUSED, char **argv UNUSED) @@ -689,6 +703,7 @@ int main(int argc UNUSED, char **argv UNUSED) if(!state) { state = vterm_obtain_state(vt); vterm_state_set_callbacks(state, &state_cbs, NULL); + vterm_state_callbacks_has_premove(state); /* In some tests we want to check the behaviour of overflowing the * buffer, so make it nicely small */ @@ -713,6 +728,9 @@ int main(int argc UNUSED, char **argv UNUSED) case 's': want_scrollrect = sense; break; + case 'P': + want_premove = sense; + break; case 'm': want_moverect = sense; break; @@ -740,6 +758,7 @@ int main(int argc UNUSED, char **argv UNUSED) if(!screen) screen = vterm_obtain_screen(vt); vterm_screen_set_callbacks(screen, &screen_cbs, NULL); + vterm_screen_callbacks_has_pushline4(screen); while(line[i] == ' ') i++; diff --git a/src/libvterm/t/run-test.pl b/src/libvterm/t/run-test.pl index 3440465cda..819b5b4d4e 100644 --- a/src/libvterm/t/run-test.pl +++ b/src/libvterm/t/run-test.pl @@ -124,7 +124,7 @@ sub do_line elsif( $line =~ m/^putglyph (\S+) (.*)$/ ) { $line = "putglyph " . join( ",", map sprintf("%x", $_), eval($1) ) . " $2"; } - elsif( $line =~ m/^(?:movecursor|scrollrect|moverect|erase|damage|sb_pushline|sb_popline|sb_clear|settermprop|setmousefunc|selection-query) ?/ ) { + elsif( $line =~ m/^(?:movecursor|scrollrect|premove|moverect|erase|damage|sb_pushline|sb_popline|sb_clear|settermprop|setmousefunc|selection-query) ?/ ) { # no conversion } elsif( $line =~ m/^(selection-set) (.*?) (\[?)(.*?)(\]?)$/ ) { diff --git a/src/terminal.c b/src/terminal.c index 0310ae9aa2..6a9c286e29 100644 --- a/src/terminal.c +++ b/src/terminal.c @@ -61,9 +61,11 @@ 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 + char_u continuation; } sb_line_T; #ifdef MSWIN @@ -148,6 +150,8 @@ struct terminal_S { garray_T tl_scrollback; 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 @@ -1386,6 +1390,112 @@ update_cursor(term_T *term, int redraw) } } +/* + * Find the location of a scrollbackline in the buffer + */ + static void +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; + size_t calc_pos = 0; + int calc_col = 0; + int i; + + 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; + // Do not count this line's bytes/cols twice + if (lines[i].continuation) + ++i; + } + 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 + */ + static 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". @@ -1908,13 +2018,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; @@ -1927,13 +2038,32 @@ 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); } } +#endif + + if (append) + { + char_u *prev_text = ml_get_buf(buf, lnum, FALSE); + size_t prev_len = STRLEN(prev_text); + + char_u *both = alloc(len + prev_len + 2); + if (both == NULL) + return; + 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 - ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE); if (empty) { // Delete the empty line that was in the empty buffer. @@ -1989,6 +2119,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; @@ -2005,16 +2136,33 @@ 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; + if (line->sb_bytes < 0 || (size_t)line->sb_bytes > bufline_length) + break; + bufline_length -= line->sb_bytes; + 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) @@ -2028,6 +2176,7 @@ cleanup_scrollback(term_T *term) update_snapshot(term_T *term) { VTermScreen *screen; + VTermState *state; int len; int lines_skipped = 0; VTermPos pos; @@ -2043,6 +2192,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) { @@ -2067,7 +2217,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); + { + add_scrollback_line_to_buffer(term, (char_u *)"", 0, 0); + ++term->tl_scrollback_snapshot; + } } if (len == 0) @@ -2079,9 +2232,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) { @@ -2116,17 +2272,21 @@ 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 = (char_u)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); + 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); } @@ -2141,7 +2301,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); + { + add_scrollback_line_to_buffer(term, (char_u *)"", 0, 0); + ++term->tl_scrollback_snapshot; + } } term->tl_dirty_snapshot = FALSE; @@ -2186,7 +2349,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); if (redraw) @@ -2282,7 +2445,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); @@ -2291,15 +2456,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 + term->tl_scrollback_scrolled, &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; } /* @@ -3483,20 +3649,27 @@ limit_scrollback(term_T *term, garray_T *gap, int update_buffer) int todo = MAX(term->tl_buffer->b_p_twsl / 10, gap->ga_len - term->tl_buffer->b_p_twsl); 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); + --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); 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) { win_T *curwin_save = curwin; @@ -3521,7 +3694,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, int continuation, void *user) { term_T *term = (term_T *)user; garray_T *gap; @@ -3600,16 +3773,20 @@ 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, continuation); 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 = (char_u)continuation; if (update_buffer) { line->sb_text = NULL; ++term->tl_scrollback_scrolled; + if (!continuation) + ++term->tl_buffer_scrolled; ga_clear(&ga); // free the text } else @@ -3651,17 +3828,20 @@ 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), pp_line->continuation); vim_free(pp_line->sb_text); 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); @@ -3685,9 +3865,10 @@ static VTermScreenCallbacks screen_callbacks = { handle_settermprop, // settermprop handle_bell, // bell handle_resize, // resize - handle_pushline, // sb_pushline + NULL, // sb_pushline NULL, // sb_popline - NULL // sb_clear + NULL, // sb_clear + handle_pushline // sb_pushline4 }; /* @@ -4243,16 +4424,21 @@ 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 = col; - if (lnum > term->tl_scrollback.ga_len) + 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; 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); } @@ -4964,6 +5150,7 @@ create_vterm(term_T *term, int rows, int cols) vterm_screen_set_callbacks(screen, &screen_callbacks, term); vterm_screen_set_damage_merge(screen, VTERM_DAMAGE_SCROLL); + vterm_screen_callbacks_has_pushline4(screen); // TODO: depends on 'encoding'. vterm_set_utf8(vterm, 1); @@ -5478,8 +5665,10 @@ 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; + line->continuation = 0; ++term->tl_scrollback.ga_len; ga_init(&ga_cell); @@ -6300,11 +6489,24 @@ 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; + size_t offset = 0; + int sb_row = term->tl_scrollback_scrolled + row; + sb_line_T *line; + + if (sb_row < 0 || sb_row >= term->tl_scrollback.ga_len) + return; + line = (sb_line_T *)term->tl_scrollback.ga_data + sb_row; + + scrollbackline_pos_in_buf(term, sb_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 { @@ -6343,7 +6545,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; } /* @@ -6557,12 +6759,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; + size_t offset = 0; - if (lnum < 0 || lnum >= term->tl_scrollback.ga_len) + 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; - 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; ) diff --git a/src/testdir/test_terminal.vim b/src/testdir/test_terminal.vim index 6f8fb36626..c6521d3efe 100644 --- a/src/testdir/test_terminal.vim +++ b/src/testdir/test_terminal.vim @@ -2451,4 +2451,29 @@ func Test_terminal_disable_kitty_keyboard() bwipe! endfunc +func Test_terminal_unwraps() + CheckNotMSWindows + + 30vnew + + redraw + let buf = term_start("echo 1+2+3+4+5+6+7+8+9+10+11+12+13+14+15") + " Wait until both wrapped lines have appeared in the terminal + call WaitForAssert({-> assert_equal('14+15', term_getline(buf, 2))}) + + " 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_equal('14+15', l) + + call TermWait(buf) + " 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) + + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/version.c b/src/version.c index 0de4e4f14c..0b3a06d204 100644 --- a/src/version.c +++ b/src/version.c @@ -734,6 +734,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 336, /**/ 335, /**/