]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0336: libvterm: no terminal reflow support master v9.2.0336
authorCimbali <me@cimba.li>
Fri, 10 Apr 2026 22:02:09 +0000 (22:02 +0000)
committerChristian Brabandt <cb@256bit.org>
Fri, 10 Apr 2026 22:15:36 +0000 (22:15 +0000)
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 <leonerd@leonerd.org.uk>
Signed-off-by: Cimbali <me@cimba.li>
Signed-off-by: Christian Brabandt <cb@256bit.org>
16 files changed:
Filelist
runtime/doc/version9.txt
src/libvterm/README
src/libvterm/include/vterm.h
src/libvterm/src/screen.c
src/libvterm/src/state.c
src/libvterm/src/vterm_internal.h
src/libvterm/t/12state_scroll.test
src/libvterm/t/13state_edit.test
src/libvterm/t/62screen_damage.test
src/libvterm/t/69screen_pushline.test [new file with mode: 0644]
src/libvterm/t/harness.c
src/libvterm/t/run-test.pl
src/terminal.c
src/testdir/test_terminal.vim
src/version.c

index 6a21157647c1cbd58dd142066691727d8a3b7f4e..1522e33740d4cc472c376f886449e8913385bf02 100644 (file)
--- 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 \
index e0976453d935c6dda9f701e889d6b34c9652c431..2b3decac78bdd78b57bf0ecd421f3eb25eaa9961 100644 (file)
@@ -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 ~
 -----------------
index 56c6e222be2e39d8e9d562218fdee8958701f34f..875c448cbbe783056986d1b020b1152b853a649c 100644 (file)
@@ -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
index 48deebe25ea5e9c011d70e40b456adce4ab62b1b..54e9e49242c7438f328b4913714a47c87273a6e4 100644 (file)
@@ -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);
 
index fd76777c410fe90082574351572f75bd2c766ffd..5ed8dd48ae4c8ff2c425d793137044db68441a30 100644 (file)
@@ -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);
index fca833326ef39242a592e02ed428e589e878a7d6..34b37445c254f8f44b756bee1049cdc0bc9c9c77 100644 (file)
@@ -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;
index 0f80fa431558efeecd048e9cb31429ed5c6761c1..b9830b9db3a9047a1aeeae48659f853aeebb09c6 100644 (file)
@@ -70,6 +70,7 @@ struct VTermState
 
   const VTermStateCallbacks *callbacks;
   void *cbdata;
+  int callbacks_has_premove;
 
   const VTermStateFallbacks *fallbacks;
   void *fbdata;
index c1f2791d00b034a0db7073a3b17839c29b761bd6..5605131bc7b6951b7f94e07cc324d6bafad45235 100644 (file)
@@ -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
index d3f3e9e40bb72c025babe622c7e86e29c055da51..84d22cc9acc8e78f276837824c2c5704e319afe2 100644 (file)
@@ -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
index 3b1b2385272f29238411c794b04b27ad71bf0e26..2f11c759dd1f32b7155080d56b473929a008b2ed 100644 (file)
@@ -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 (file)
index 0000000..735ea29
--- /dev/null
@@ -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
index 5285d94ec0ba005047146a555178fa07c973785c..6fc34fe0433bbaa15fd53d358d0736c800e340ec 100644 (file)
@@ -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++;
index 3440465cda7c06bfe3d2cdce1c0c28b77a93a466..819b5b4d4ec5e7520b1559411bf0c8c620166b6e 100644 (file)
@@ -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) (.*?) (\[?)(.*?)(\]?)$/ ) {
index 0310ae9aa2e20f018cd046364b4d3ff7da97c04d..6a9c286e29ed9f176947ae98918b3dcf94d3c8b9 100644 (file)
@@ -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; )
index 6f8fb36626f60c2ed669d0a71137a8b9bcd5b736..c6521d3efe0f725375f428a6f03e905dde72d428 100644 (file)
@@ -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
index 0de4e4f14c3b1f9562a2e1844500ebc4d89439bc..0b3a06d204d3a3f0765042cb6f23fc19bc28f5b6 100644 (file)
@@ -734,6 +734,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    336,
 /**/
     335,
 /**/