patch 9.2.0336: libvterm: no terminal reflow support
Commit:
https://github.com/vim/vim/commit/e29f33ef5115dbb66370ce18f46b3e01674e2180
Author: Cimbali <
m...@cimba.li>
Date: Fri Apr 10 22:02:09 2026 +0000
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 <
leo...@leonerd.org.uk>
Signed-off-by: Cimbali <
m...@cimba.li>
Signed-off-by: Christian Brabandt <
c...@256bit.org>
diff --git a/Filelist b/Filelist
index 6a2115764..1522e3374 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 e0976453d..2b3decac7 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 56c6e222b..875c448cb 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 48deebe25..54e9e4924 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 fd76777c4..5ed8dd48a 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 fca833326..34b37445c 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 0f80fa431..b9830b9db 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 c1f2791d0..5605131bc 100644
--- a/src/libvterm/t/12state_scroll.test
+++ b/src/libvterm/t/12state_scroll.test
@@ -127,24 +127,28 @@ PUSH " [100;105r D"
PUSH " [5;2r D"
RESET
-WANTSTATE -s+me
+WANTSTATE -s+Pme
!Scroll Down move+erase emulation
PUSH " [S"
+ premove 0..1,0..80
moverect 1..25,0..80 -> 0..24,0..80
erase 24..25,0..80
?cursor = 0,0
PUSH " [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 " [T"
+ premove 24..25,0..80
moverect 0..24,0..80 -> 1..25,0..80
erase 0..1,0..80
?cursor = 0,0
PUSH " [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 d3f3e9e40..84d22cc9a 100644
--- a/src/libvterm/t/13state_edit.test
+++ b/src/libvterm/t/13state_edit.test
@@ -268,7 +268,7 @@ PUSH " [2\"q"
PUSH " P\$q\"q \"
output " P1\$r2\"q \"
-WANTSTATE -s+m
+WANTSTATE -s+Pm
!ICH move+erase emuation
RESET
@@ -278,12 +278,14 @@ PUSH "ACD"
PUSH " [2D"
?cursor = 0,1
PUSH " [@"
+ 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 " [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 " [3D"
?cursor = 0,1
PUSH " [P"
+ premove 0..1,1..2
moverect 0..1,2..80 -> 0..1,1..79
erase 0..1,79..80
?cursor = 0,1
PUSH " [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 3b1b23852..2f11c759d 100644
--- a/src/libvterm/t/62screen_damage.test
+++ b/src/libvterm/t/62screen_damage.test
@@ -146,9 +146,9 @@ PUSH " [25H
ABCDE [2P
"
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 000000000..735ea2947
--- /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 "
"
+ ?lineinfo 0 =
+ ?lineinfo 1 = cont
+
+!Continuation mark sent to sb_pushline
+PUSH "
"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 "
"
+ sb_pushline 80 cont = 41 41 41 41 41
diff --git a/src/libvterm/t/harness.c b/src/libvterm/t/harness.c
index 5285d94ec..6fc34fe04 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
",
+ 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("
");
@@ -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 3440465cd..819b5b4d4 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 0310ae9aa..6a9c286e2 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 6f8fb3662..c6521d3ef 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 0de4e4f14..0b3a06d20 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,
/**/