patch 9.1.1108: 'smoothscroll' gets stuck with 'listchars' "eol"

Commit: 
https://github.com/vim/vim/commit/2c47ab8fcd7188fa87053c757ea86b0d846c06c1
Author: zeertzjq <zeert...@outlook.com>
Date:   Thu Feb 13 20:34:34 2025 +0100

    patch 9.1.1108: 'smoothscroll' gets stuck with 'listchars' "eol"
    
    Problem:  'smoothscroll' gets stuck with 'listchars' "eol".
    Solution: Count size of 'listchars' "eol" in line size when scrolling.
              (zeertzjq)
    
    related: neovim/neovim#32405
    closes: #16627
    
    Signed-off-by: zeertzjq <zeert...@outlook.com>
    Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/src/change.c b/src/change.c
index 72d01c51a..fb33971e1 100644
--- a/src/change.c
+++ b/src/change.c
@@ -572,8 +572,7 @@ changed_common(
                    && (last < wp->w_topline
                        || (wp->w_topline >= lnum
                            && wp->w_topline < lnume
-                           && win_linetabsize(wp, wp->w_topline,
-                                       ml_get(wp->w_topline), (colnr_T)MAXCOL)
+                           && linetabsize_eol(wp, wp->w_topline)
                                    <= wp->w_skipcol + sms_marker_overlap(wp, 
-1))))
                wp->w_skipcol = 0;
 
diff --git a/src/charset.c b/src/charset.c
index b4bc812fa..9c68d4af7 100644
--- a/src/charset.c
+++ b/src/charset.c
@@ -821,6 +821,7 @@ linetabsize_col(int startcol, char_u *s)
 
 /*
  * Like linetabsize_str(), but for a given window instead of the current one.
+ * Doesn't count the size of 'listchars' "eol".
  */
     int
 win_linetabsize(win_T *wp, linenr_T lnum, char_u *line, colnr_T len)
@@ -836,14 +837,25 @@ win_linetabsize(win_T *wp, linenr_T lnum, char_u *line, 
colnr_T len)
 /*
  * Return the number of cells line "lnum" of window "wp" will take on the
  * screen, taking into account the size of a tab and text properties.
+ * Doesn't count the size of 'listchars' "eol".
  */
-  int
+    int
 linetabsize(win_T *wp, linenr_T lnum)
 {
     return win_linetabsize(wp, lnum,
                       ml_get_buf(wp->w_buffer, lnum, FALSE), (colnr_T)MAXCOL);
 }
 
+/*
+ * Like linetabsize(), but counts the size of 'listchars' "eol".
+ */
+    int
+linetabsize_eol(win_T *wp, linenr_T lnum)
+{
+    return linetabsize(wp, lnum)
+       + ((wp->w_p_list && wp->w_lcs_chars.eol != NUL) ? 1 : 0);
+}
+
 /*
  * Like linetabsize(), but excludes 'above'/'after'/'right'/'below' aligned
  * virtual text, while keeping inline virtual text.
diff --git a/src/misc2.c b/src/misc2.c
index dc0d83f5a..a2978e558 100644
--- a/src/misc2.c
+++ b/src/misc2.c
@@ -168,7 +168,7 @@ coladvance2(
                && wcol >= (colnr_T)width
                && width > 0)
        {
-           csize = linetabsize(curwin, pos->lnum);
+           csize = linetabsize_eol(curwin, pos->lnum);
            if (csize > 0)
                csize--;
 
diff --git a/src/move.c b/src/move.c
index ed252d9db..5d34947a5 100644
--- a/src/move.c
+++ b/src/move.c
@@ -1621,7 +1621,7 @@ f_virtcol2col(typval_T *argvars UNUSED, typval_T *rettv)
  */
 static void cursor_correct_sms(void)
 {
-    if (!curwin->w_p_sms ||!curwin->w_p_wrap
+    if (!curwin->w_p_sms || !curwin->w_p_wrap
        || curwin->w_cursor.lnum != curwin->w_topline)
        return;
 
@@ -1631,8 +1631,7 @@ static void cursor_correct_sms(void)
     int            so_cols = so == 0 ? 0 : width1 + (so - 1) * width2;
     int            space_cols = (curwin->w_height - 1) * width2;
     int            overlap, top, bot;
-    int            size = so == 0 ? 0 : win_linetabsize(curwin, 
curwin->w_topline,
-                                   ml_get(curwin->w_topline), (colnr_T)MAXCOL);
+    int            size = so == 0 ? 0 : linetabsize_eol(curwin, 
curwin->w_topline);
 
     if (curwin->w_topline == 1 && curwin->w_skipcol == 0)
        so_cols = 0;               // Ignore 'scrolloff' at top of buffer.
@@ -1645,10 +1644,10 @@ static void cursor_correct_sms(void)
     if (so_cols >= width1 && so_cols > size)
        so_cols -= width1;
 
-    // If there is no marker or we have non-zero scrolloff, just ignore it.
-    overlap = (curwin->w_skipcol == 0 || so_cols != 0) ? 0
-                                           : sms_marker_overlap(curwin, -1);
-    top = curwin->w_skipcol + overlap + so_cols;
+    overlap = curwin->w_skipcol == 0 ? 0
+                       : sms_marker_overlap(curwin, curwin->w_width - width2);
+    // If we have non-zero scrolloff, ignore marker overlap.
+    top = curwin->w_skipcol + (so_cols != 0 ? so_cols : overlap);
     bot = curwin->w_skipcol + width1 + (curwin->w_height - 1) * width2
                                                                    - so_cols;
     validate_virtcol();
@@ -1667,12 +1666,28 @@ static void cursor_correct_sms(void)
 
     if (col != curwin->w_virtcol)
     {
+       int rc;
+
        curwin->w_curswant = col;
-       coladvance(curwin->w_curswant);
+       rc = coladvance(curwin->w_curswant);
        // validate_virtcol() marked various things as valid, but after
        // moving the cursor they need to be recomputed
        curwin->w_valid &=
            ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL);
+       if (rc == FAIL && curwin->w_skipcol > 0
+               && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count)
+       {
+           validate_virtcol();
+           if (curwin->w_virtcol < curwin->w_skipcol + overlap)
+           {
+               // Cursor still not visible: move it to the next line instead.
+               curwin->w_cursor.lnum++;
+               curwin->w_cursor.col = 0;
+               curwin->w_cursor.coladd = 0;
+               curwin->w_curswant = 0;
+               curwin->w_valid &= ~VALID_VIRTCOL;
+           }
+       }
     }
 }
 
@@ -1813,8 +1828,7 @@ scrolldown(
 #endif
                if (do_sms)
                {
-                   int size = win_linetabsize(curwin, curwin->w_topline,
-                                  ml_get(curwin->w_topline), (colnr_T)MAXCOL);
+                   int size = linetabsize_eol(curwin, curwin->w_topline);
                    if (size > width1)
                    {
                        curwin->w_skipcol = width1;
@@ -1911,7 +1925,7 @@ scrollup(
        colnr_T     prev_skipcol = curwin->w_skipcol;
 
        if (do_sms)
-           size = linetabsize(curwin, curwin->w_topline);
+           size = linetabsize_eol(curwin, curwin->w_topline);
 
        // diff mode: first consume "topfill"
        // 'smoothscroll': increase "w_skipcol" until it goes over the end of
@@ -1966,7 +1980,7 @@ scrollup(
 # endif
                    curwin->w_skipcol = 0;
                    if (todo > 1 && do_sms)
-                       size = linetabsize(curwin, curwin->w_topline);
+                       size = linetabsize_eol(curwin, curwin->w_topline);
                }
            }
        }
@@ -2043,7 +2057,7 @@ adjust_skipcol(void)
     }
 
     validate_virtcol();
-    overlap = sms_marker_overlap(curwin, -1);
+    overlap = sms_marker_overlap(curwin, curwin->w_width - width2);
     while (curwin->w_skipcol > 0
            && curwin->w_virtcol < curwin->w_skipcol + overlap + scrolloff_cols)
     {
@@ -2066,8 +2080,7 @@ adjust_skipcol(void)
     // Avoid adjusting for 'scrolloff' beyond the text line height.
     if (scrolloff_cols > 0)
     {
-       int size = win_linetabsize(curwin, curwin->w_topline,
-                                   ml_get(curwin->w_topline), (colnr_T)MAXCOL);
+       int size = linetabsize_eol(curwin, curwin->w_topline);
        size = width1 + width2 * ((size - width1 + width2 - 1) / width2);
        while (col > size)
            col -= width2;
diff --git a/src/normal.c b/src/normal.c
index 6cf2b9d2f..1189737c3 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -5764,7 +5764,7 @@ nv_g_home_m_cmd(cmdarg_T *cap)
        // that skipcol is not adjusted later.
        if (curwin->w_skipcol > 0 && curwin->w_cursor.lnum == curwin->w_topline)
        {
-           int overlap = sms_marker_overlap(curwin, -1);
+           int overlap = sms_marker_overlap(curwin, curwin->w_width - width2);
            if (overlap > 0 && i == curwin->w_skipcol)
                i += overlap;
        }
diff --git a/src/proto/charset.pro b/src/proto/charset.pro
index 54af280e9..bf1fc1026 100644
--- a/src/proto/charset.pro
+++ b/src/proto/charset.pro
@@ -22,6 +22,7 @@ int linetabsize_str(char_u *s);
 int linetabsize_col(int startcol, char_u *s);
 int win_linetabsize(win_T *wp, linenr_T lnum, char_u *line, colnr_T len);
 int linetabsize(win_T *wp, linenr_T lnum);
+int linetabsize_eol(win_T *wp, linenr_T lnum);
 int linetabsize_no_outer(win_T *wp, linenr_T lnum);
 void win_linetabsize_cts(chartabsize_T *cts, colnr_T len);
 int vim_isIDc(int c);
diff --git a/src/testdir/test_scroll_opt.vim b/src/testdir/test_scroll_opt.vim
index 9c4e29ed2..7573db9ac 100644
--- a/src/testdir/test_scroll_opt.vim
+++ b/src/testdir/test_scroll_opt.vim
@@ -1216,4 +1216,59 @@ func Test_smooth_long_scrolloff()
   call StopVimInTerminal(buf)
 endfunc
 
+func Test_smoothscroll_listchars_eol()
+  call NewWindow(10, 40)
+  setlocal list listchars=eol:$ scrolloff=0 smoothscroll
+  call setline(1, repeat('-', 40))
+  call append(1, repeat(['foobar'], 10))
+
+  normal! G
+  call assert_equal(2, line('w0'))
+  call assert_equal(0, winsaveview().skipcol)
+
+  exe "normal! \<C-Y>"
+  call assert_equal(1, line('w0'))
+  call assert_equal(40, winsaveview().skipcol)
+
+  exe "normal! \<C-Y>"
+  call assert_equal(1, line('w0'))
+  call assert_equal(0, winsaveview().skipcol)
+
+  exe "normal! \<C-Y>"
+  call assert_equal(1, line('w0'))
+  call assert_equal(0, winsaveview().skipcol)
+
+  exe "normal! \<C-E>"
+  call assert_equal(1, line('w0'))
+  call assert_equal(40, winsaveview().skipcol)
+
+  exe "normal! \<C-E>"
+  call assert_equal(2, line('w0'))
+  call assert_equal(0, winsaveview().skipcol)
+
+  for ve in ['', 'all', 'onemore']
+    let &virtualedit = ve
+    normal! gg
+    call assert_equal(1, line('w0'))
+    call assert_equal(0, winsaveview().skipcol)
+
+    exe "normal! \<C-E>"
+    redraw  " redrawing should not cause another scroll
+    call assert_equal(1, line('w0'))
+    call assert_equal(40, winsaveview().skipcol)
+
+    exe "normal! \<C-E>"
+    redraw
+    call assert_equal(2, line('w0'))
+    call assert_equal(0, winsaveview().skipcol)
+
+    if ve != 'all'
+      call assert_equal([0, 2, 1, 0], getpos('.'))
+    endif
+  endfor
+
+  set virtualedit&
+  bwipe!
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index 418fcb7ec..8854292e1 100644
--- a/src/version.c
+++ b/src/version.c
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1108,
 /**/
     1107,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to vim_dev+unsubscr...@googlegroups.com.
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1tif8z-0070Qu-Bs%40256bit.org.

Raspunde prin e-mail lui