aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/nvim/change.c5
-rw-r--r--src/nvim/move.c353
-rw-r--r--src/nvim/normal.c67
-rw-r--r--src/nvim/plines.c3
-rw-r--r--test/functional/legacy/matchparen_spec.lua2
-rw-r--r--test/functional/legacy/normal_spec.lua3
-rw-r--r--test/old/testdir/test_diffmode.vim18
-rw-r--r--test/old/testdir/test_edit.vim8
-rw-r--r--test/old/testdir/test_normal.vim31
-rw-r--r--test/old/testdir/test_scroll_opt.vim89
10 files changed, 376 insertions, 203 deletions
diff --git a/src/nvim/change.c b/src/nvim/change.c
index fce0de49bb..d76baaff2c 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -342,9 +342,8 @@ static void changed_common(buf_T *buf, linenr_T lnum, colnr_T col, linenr_T lnum
&& (last < wp->w_topline
|| (wp->w_topline >= lnum
&& wp->w_topline < lnume
- && win_linetabsize(wp, wp->w_topline, ml_get(wp->w_topline), MAXCOL)
- <= (wp->w_skipcol
- + sms_marker_overlap(wp, win_col_off(wp) - win_col_off2(wp)))))) {
+ && win_linetabsize(wp, wp->w_topline, ml_get_buf(buf, wp->w_topline), MAXCOL)
+ <= (wp->w_skipcol + sms_marker_overlap(wp, -1))))) {
wp->w_skipcol = 0;
}
diff --git a/src/nvim/move.c b/src/nvim/move.c
index e7416549f5..675f048d09 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -78,13 +78,14 @@ int adjust_plines_for_skipcol(win_T *wp)
/// Return how many lines "lnum" will take on the screen, taking into account
/// whether it is the first line, whether w_skipcol is non-zero and limiting to
/// the window height.
-static int plines_correct_topline(win_T *wp, linenr_T lnum, linenr_T *nextp, bool *foldedp)
+static int plines_correct_topline(win_T *wp, linenr_T lnum, linenr_T *nextp, bool limit_winheight,
+ bool *foldedp)
{
int n = plines_win_full(wp, lnum, nextp, foldedp, true, false);
if (lnum == wp->w_topline) {
n -= adjust_plines_for_skipcol(wp);
}
- if (n > wp->w_height_inner) {
+ if (limit_winheight && n > wp->w_height_inner) {
return wp->w_height_inner;
}
return n;
@@ -111,7 +112,7 @@ static void comp_botline(win_T *wp)
for (; lnum <= wp->w_buffer->b_ml.ml_line_count; lnum++) {
linenr_T last = lnum;
bool folded;
- int n = plines_correct_topline(wp, lnum, &last, &folded);
+ int n = plines_correct_topline(wp, lnum, &last, true, &folded);
if (lnum <= wp->w_cursor.lnum && last >= wp->w_cursor.lnum) {
wp->w_cline_row = done;
wp->w_cline_height = n;
@@ -190,11 +191,14 @@ static void redraw_for_cursorcolumn(win_T *wp)
/// Calculates how much the 'listchars' "precedes" or 'smoothscroll' "<<<"
/// marker overlaps with buffer text for window "wp".
/// Parameter "extra2" should be the padding on the 2nd line, not the first
-/// line.
+/// line. When "extra2" is -1 calculate the padding.
/// Returns the number of columns of overlap with buffer text, excluding the
/// extra padding on the ledge.
int sms_marker_overlap(win_T *wp, int extra2)
{
+ if (extra2 == -1) {
+ extra2 = win_col_off(wp) - win_col_off2(wp);
+ }
// There is no marker overlap when in showbreak mode, thus no need to
// account for it. See wlv_put_linebuf().
if (*get_showbreak_value(wp) != NUL) {
@@ -303,7 +307,7 @@ void update_topline(win_T *wp)
// Check that the cursor position is visible. Add columns for
// the marker displayed in the top-left if needed.
getvvcol(wp, &wp->w_cursor, &vcol, NULL, NULL);
- int overlap = sms_marker_overlap(wp, win_col_off(wp) - win_col_off2(wp));
+ int overlap = sms_marker_overlap(wp, -1);
if (wp->w_skipcol + overlap > vcol) {
check_topline = true;
}
@@ -667,7 +671,7 @@ static void curs_rows(win_T *wp)
} else {
linenr_T last = lnum;
bool folded;
- int n = plines_correct_topline(wp, lnum, &last, &folded);
+ int n = plines_correct_topline(wp, lnum, &last, true, &folded);
lnum = last + 1;
if (folded && lnum > wp->w_cursor.lnum) {
break;
@@ -1185,6 +1189,122 @@ void f_virtcol2col(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->vval.v_number = virtcol2col(wp, lnum, screencol);
}
+/// Make sure the cursor is in the visible part of the topline after scrolling
+/// the screen with 'smoothscroll'.
+static void cursor_correct_sms(win_T *wp)
+{
+ if (!wp->w_p_sms || !wp->w_p_wrap || wp->w_cursor.lnum != wp->w_topline) {
+ return;
+ }
+
+ int so = get_scrolloff_value(wp);
+ int width1 = wp->w_width_inner - win_col_off(wp);
+ int width2 = width1 + win_col_off2(wp);
+ int so_cols = so == 0 ? 0 : width1 + (so - 1) * width2;
+ int space_cols = (wp->w_height_inner - 1) * width2;
+ int size = so == 0 ? 0 : win_linetabsize(wp, wp->w_topline,
+ ml_get_buf(wp->w_buffer, wp->w_topline),
+ (colnr_T)MAXCOL);
+
+ if (wp->w_topline == 1 && wp->w_skipcol == 0) {
+ so_cols = 0; // Ignore 'scrolloff' at top of buffer.
+ } else if (so_cols > space_cols / 2) {
+ so_cols = space_cols / 2; // Not enough room: put cursor in the middle.
+ }
+
+ // Not enough screen lines in topline: ignore 'scrolloff'.
+ while (so_cols > size && so_cols - width2 >= width1) {
+ so_cols -= width2;
+ }
+ if (so_cols >= width1 && so_cols > size) {
+ so_cols -= width1;
+ }
+
+ // If there is no marker or we have non-zero scrolloff, just ignore it.
+ int overlap = (wp->w_skipcol == 0 || so_cols != 0) ? 0 : sms_marker_overlap(wp, -1);
+ int top = wp->w_skipcol + overlap + so_cols;
+ int bot = wp->w_skipcol + width1 + (wp->w_height_inner - 1) * width2 - so_cols;
+
+ validate_virtcol(wp);
+ colnr_T col = wp->w_virtcol;
+
+ if (col < top) {
+ if (col < width1) {
+ col += width1;
+ }
+ while (width2 > 0 && col < top) {
+ col += width2;
+ }
+ } else {
+ while (width2 > 0 && col >= bot) {
+ col -= width2;
+ }
+ }
+
+ if (col != wp->w_virtcol) {
+ wp->w_curswant = col;
+ coladvance(wp, wp->w_curswant);
+ // validate_virtcol() marked various things as valid, but after
+ // moving the cursor they need to be recomputed
+ wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL);
+ }
+}
+
+/// Scroll "count" lines up or down, and redraw.
+void scroll_redraw(int up, linenr_T count)
+{
+ linenr_T prev_topline = curwin->w_topline;
+ int prev_skipcol = curwin->w_skipcol;
+ int prev_topfill = curwin->w_topfill;
+ linenr_T prev_lnum = curwin->w_cursor.lnum;
+
+ bool moved = up
+ ? scrollup(curwin, count, true)
+ : scrolldown(curwin, count, true);
+
+ if (get_scrolloff_value(curwin) > 0) {
+ // Adjust the cursor position for 'scrolloff'. Mark w_topline as
+ // valid, otherwise the screen jumps back at the end of the file.
+ cursor_correct(curwin);
+ check_cursor_moved(curwin);
+ curwin->w_valid |= VALID_TOPLINE;
+
+ // If moved back to where we were, at least move the cursor, otherwise
+ // we get stuck at one position. Don't move the cursor up if the
+ // first line of the buffer is already on the screen
+ while (curwin->w_topline == prev_topline
+ && curwin->w_skipcol == prev_skipcol
+ && curwin->w_topfill == prev_topfill) {
+ if (up) {
+ if (curwin->w_cursor.lnum > prev_lnum
+ || cursor_down(1L, false) == FAIL) {
+ break;
+ }
+ } else {
+ if (curwin->w_cursor.lnum < prev_lnum
+ || prev_topline == 1L
+ || cursor_up(1L, false) == FAIL) {
+ break;
+ }
+ }
+ // Mark w_topline as valid, otherwise the screen jumps back at the
+ // end of the file.
+ check_cursor_moved(curwin);
+ curwin->w_valid |= VALID_TOPLINE;
+ }
+ }
+
+ if (moved) {
+ curwin->w_viewport_invalid = true;
+ }
+
+ cursor_correct_sms(curwin);
+ if (curwin->w_cursor.lnum != prev_lnum) {
+ coladvance(curwin, curwin->w_curswant);
+ }
+ redraw_later(curwin, UPD_VALID);
+}
+
/// Scroll a window down by "line_count" logical lines. "CTRL-Y"
///
/// @param line_count number of lines to scroll
@@ -1240,7 +1360,7 @@ bool scrolldown(win_T *wp, linenr_T line_count, int byfold)
} else {
if (do_sms) {
int size = win_linetabsize(wp, wp->w_topline,
- ml_get(wp->w_topline), MAXCOL);
+ ml_get_buf(wp->w_buffer, wp->w_topline), MAXCOL);
if (size > width1) {
wp->w_skipcol = width1;
size -= width1;
@@ -1299,27 +1419,10 @@ bool scrolldown(win_T *wp, linenr_T line_count, int byfold)
foldAdjustCursor(wp);
coladvance(wp, wp->w_curswant);
}
-
- if (wp->w_cursor.lnum == wp->w_topline && do_sms) {
- int so = get_scrolloff_value(wp);
- colnr_T scrolloff_cols = so == 0 ? 0 : width1 + (so - 1) * width2;
-
- // make sure the cursor is in the visible text
- validate_virtcol(wp);
- colnr_T col = wp->w_virtcol - wp->w_skipcol + scrolloff_cols;
- int row = 0;
- if (col >= width1) {
- col -= width1;
- row++;
- }
- if (col > width2 && width2 > 0) {
- row += (int)col / width2;
- }
- if (row >= wp->w_height_inner) {
- wp->w_curswant = wp->w_virtcol - (row - wp->w_height_inner + 1) * width2;
- coladvance(wp, wp->w_curswant);
- }
+ if (wp->w_cursor.lnum < wp->w_topline) {
+ wp->w_cursor.lnum = wp->w_topline;
}
+
return moved;
}
@@ -1421,46 +1524,6 @@ bool scrollup(win_T *wp, linenr_T line_count, bool byfold)
coladvance(wp, wp->w_curswant);
}
- if (wp->w_cursor.lnum == wp->w_topline && do_sms && wp->w_skipcol > 0) {
- int col_off = win_col_off(wp);
- int col_off2 = win_col_off2(wp);
-
- int width1 = wp->w_width_inner - col_off;
- int width2 = width1 + col_off2;
- int extra2 = col_off - col_off2;
- int so = get_scrolloff_value(wp);
- colnr_T scrolloff_cols = so == 0 ? 0 : width1 + (so - 1) * width2;
- int space_cols = (wp->w_height_inner - 1) * width2;
-
- // If we have non-zero scrolloff, just ignore the marker as we are
- // going past it anyway.
- int overlap = scrolloff_cols != 0 ? 0 : sms_marker_overlap(wp, extra2);
-
- // Make sure the cursor is in a visible part of the line, taking
- // 'scrolloff' into account, but using screen lines.
- // If there are not enough screen lines put the cursor in the middle.
- if (scrolloff_cols > space_cols / 2) {
- scrolloff_cols = space_cols / 2;
- }
- validate_virtcol(wp);
- if (wp->w_virtcol < wp->w_skipcol + overlap + scrolloff_cols) {
- colnr_T col = wp->w_virtcol;
-
- if (col < width1) {
- col += width1;
- }
- while (col < wp->w_skipcol + overlap + scrolloff_cols) {
- col += width2;
- }
- wp->w_curswant = col;
- coladvance(wp, wp->w_curswant);
-
- // validate_virtcol() marked various things as valid, but after
- // moving the cursor they need to be recomputed
- wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL);
- }
- }
-
bool moved = topline != wp->w_topline || botline != wp->w_botline;
return moved;
@@ -1494,7 +1557,7 @@ void adjust_skipcol(void)
}
validate_virtcol(curwin);
- int overlap = sms_marker_overlap(curwin, win_col_off(curwin) - win_col_off2(curwin));
+ int overlap = sms_marker_overlap(curwin, -1);
while (curwin->w_skipcol > 0
&& curwin->w_virtcol < curwin->w_skipcol + overlap + scrolloff_cols) {
// scroll a screen line down
@@ -1840,15 +1903,11 @@ void scroll_cursor_bot(win_T *wp, int min_scroll, bool set_topbot)
bool do_sms = wp->w_p_wrap && wp->w_p_sms;
if (set_topbot) {
- bool set_skipcol = false;
-
int used = 0;
wp->w_botline = cln + 1;
+ loff.lnum = cln + 1;
loff.fill = 0;
- for (wp->w_topline = wp->w_botline;
- wp->w_topline > 1;
- wp->w_topline = loff.lnum) {
- loff.lnum = wp->w_topline;
+ while (true) {
topline_back_winheight(wp, &loff, false);
if (loff.height == MAXCOL) {
break;
@@ -1864,22 +1923,23 @@ void scroll_cursor_bot(win_T *wp, int min_scroll, bool set_topbot)
wp->w_topfill = loff.fill;
wp->w_topline = loff.lnum;
wp->w_skipcol = skipcol_from_plines(wp, plines_offset);
- set_skipcol = true;
}
}
break;
}
- used += loff.height;
wp->w_topfill = loff.fill;
+ wp->w_topline = loff.lnum;
+ used += loff.height;
}
+
set_empty_rows(wp, used);
wp->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP;
if (wp->w_topline != old_topline
|| wp->w_topfill != old_topfill
- || set_skipcol
+ || wp->w_skipcol != old_skipcol
|| wp->w_skipcol != 0) {
wp->w_valid &= ~(VALID_WROW|VALID_CROW);
- if (set_skipcol) {
+ if (wp->w_skipcol != old_skipcol) {
redraw_later(wp, UPD_NOT_VALID);
} else {
reset_skipcol(wp);
@@ -2045,6 +2105,8 @@ void scroll_cursor_bot(win_T *wp, int min_scroll, bool set_topbot)
}
wp->w_valid |= VALID_TOPLINE;
wp->w_viewport_invalid = true;
+
+ cursor_correct_sms(wp);
}
/// Recompute topline to put the cursor halfway across the window
@@ -2294,7 +2356,8 @@ static int get_scroll_overlap(Direction dir)
int min_height = curwin->w_height_inner - 2;
validate_botline(curwin);
- if (dir == FORWARD && curwin->w_botline > curbuf->b_ml.ml_line_count) {
+ if ((dir == BACKWARD && curwin->w_topline == 1)
+ || (dir == FORWARD && curwin->w_botline > curbuf->b_ml.ml_line_count)) {
return min_height + 2; // no overlap, still handle 'smoothscroll'
}
@@ -2341,15 +2404,53 @@ static int get_scroll_overlap(Direction dir)
}
}
-/// Move screen "count" pages up ("dir" is BACKWARD) or down ("dir" is FORWARD)
-/// and update the screen.
+/// Scroll "count" lines with 'smoothscroll' in direction "dir".
+static bool scroll_with_sms(Direction dir, int count)
+{
+ int prev_sms = curwin->w_p_sms;
+ colnr_T prev_skipcol = curwin->w_skipcol;
+ linenr_T prev_topline = curwin->w_topline;
+ int prev_topfill = curwin->w_topfill;
+
+ curwin->w_p_sms = true;
+ scroll_redraw(dir == FORWARD, count);
+
+ // Not actually smoothscrolling but ended up with partially visible line.
+ // Continue scrolling until skipcol is zero.
+ if (!prev_sms && curwin->w_skipcol > 0) {
+ int fixdir = dir;
+ // Reverse the scroll direction when topline already changed. One line
+ // extra for scrolling backward so that consuming skipcol is symmetric.
+ if (labs(curwin->w_topline - prev_topline) > (dir == BACKWARD)) {
+ fixdir = dir * -1;
+ }
+ validate_cursor(curwin);
+ while (curwin->w_skipcol > 0
+ && curwin->w_topline < curbuf->b_ml.ml_line_count) {
+ scroll_redraw(fixdir == FORWARD, 1);
+ }
+ }
+ curwin->w_p_sms = prev_sms;
+
+ return curwin->w_topline == prev_topline
+ && curwin->w_topfill == prev_topfill
+ && curwin->w_skipcol == prev_skipcol;
+}
+
+/// Move screen "count" (half) pages up ("dir" is BACKWARD) or down ("dir" is
+/// FORWARD) and update the screen. Handle moving the cursor and not scrolling
+/// to reveal end of buffer lines for half-page scrolling with CTRL-D and CTRL-U.
///
/// @return FAIL for failure, OK otherwise.
int pagescroll(Direction dir, int count, bool half)
{
- int prev_topfill = curwin->w_topfill;
- linenr_T prev_topline = curwin->w_topline;
- colnr_T prev_skipcol = curwin->w_skipcol;
+ int nochange = true;
+ int buflen = curbuf->b_ml.ml_line_count;
+ colnr_T prev_col = curwin->w_cursor.col;
+ linenr_T prev_lnum = curwin->w_cursor.lnum;
+ oparg_T oa = { 0 };
+ cmdarg_T ca = { 0 };
+ ca.oap = &oa;
if (half) {
// Scroll [count], 'scroll' or current window height lines.
@@ -2357,45 +2458,67 @@ int pagescroll(Direction dir, int count, bool half)
curwin->w_p_scr = MIN(curwin->w_height_inner, count);
}
count = MIN(curwin->w_height_inner, (int)curwin->w_p_scr);
- } else {
- // Scroll 'window' or current window height lines.
- count *= ((ONE_WINDOW && p_window > 0 && p_window < Rows - 1)
- ? (int)p_window - 2 : get_scroll_overlap(dir));
- }
- if (curwin->w_p_sms) {
- scroll_redraw(dir == FORWARD, count);
- } else {
- // Scroll at least one full line without 'smoothscroll'.
- count -= plines_win_nofill(curwin, curwin->w_topline, false);
- scroll_redraw(dir == FORWARD, 1);
+ int curscount = count;
+ // Adjust count so as to not reveal end of buffer lines.
+ if (dir == FORWARD) {
+ int n = plines_correct_topline(curwin, curwin->w_topline, NULL, false, NULL);
+ if (n - count < curwin->w_height_inner && curwin->w_topline < buflen) {
+ n += plines_m_win(curwin, curwin->w_topline + 1, buflen, true);
+ }
+ if (n - count < curwin->w_height_inner) {
+ count = n - curwin->w_height_inner;
+ }
+ }
- // Temporarily set 'smoothscroll' so that scrolling count lines
- // does not skip over parts of the buffer with wrapped lines.
- curwin->w_p_sms = true;
+ // Scroll the window and determine number of lines to move the cursor.
if (count > 0) {
- scroll_redraw(dir == FORWARD, count);
+ validate_cursor(curwin);
+ int prev_wrow = curwin->w_wrow;
+ nochange = scroll_with_sms(dir, count);
+ if (!nochange) {
+ validate_cursor(curwin);
+ curscount = abs(prev_wrow - curwin->w_wrow);
+ dir = prev_wrow > curwin->w_wrow ? FORWARD : BACKWARD;
+ }
}
- curwin->w_p_sms = false;
- }
- // Move cursor to first line of closed fold.
- foldAdjustCursor(curwin);
- int nochange = curwin->w_topline == prev_topline
- && curwin->w_topfill == prev_topfill
- && curwin->w_skipcol == prev_skipcol;
+ int so = get_scrolloff_value(curwin);
+ // Move the cursor the same amount of screen lines except if
+ // 'scrolloff' is set and cursor was at start or end of buffer.
+ if (so == 0 || (prev_lnum != 1 && prev_lnum != buflen)) {
+ if (curwin->w_p_wrap) {
+ nv_screengo(&oa, dir, curscount);
+ } else if (dir == FORWARD) {
+ cursor_down_inner(curwin, curscount);
+ } else {
+ cursor_up_inner(curwin, curscount);
+ }
+ }
- // Error if the viewport did not change and the cursor is already
- // at the boundary.
- if (nochange) {
- int prev_cursor = curwin->w_cursor.lnum;
- curwin->w_cursor.lnum += (count + 1) * (dir == FORWARD ? 1 : -1);
- check_cursor(curwin);
- if (curwin->w_cursor.lnum == prev_cursor) {
- beep_flush();
+ if (so > 0) {
+ cursor_correct(curwin);
}
- } else if (!curwin->w_p_sms || curwin->w_skipcol == prev_skipcol) {
+ // Move cursor to first line of closed fold.
+ foldAdjustCursor(curwin);
+
+ nochange = nochange
+ && prev_col == curwin->w_cursor.col
+ && prev_lnum == curwin->w_cursor.lnum;
+ } else {
+ // Scroll [count] times 'window' or current window height lines.
+ count *= ((ONE_WINDOW && p_window > 0 && p_window < Rows - 1)
+ ? MAX(1, (int)p_window - 2) : get_scroll_overlap(dir));
+ nochange = scroll_with_sms(dir, count);
+ }
+
+ // Error if both the viewport and cursor did not change.
+ if (nochange) {
+ beep_flush();
+ } else if (!curwin->w_p_sms) {
beginline(BL_SOL | BL_FIX);
+ } else if (p_sol) {
+ nv_g_home_m_cmd(&ca);
}
return nochange;
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 2eb247a47e..a95965ad6a 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -2460,7 +2460,7 @@ bool find_decl(char *ptr, size_t len, bool locally, bool thisblock, int flags_ar
/// 'dist' must be positive.
///
/// @return true if able to move cursor, false otherwise.
-static bool nv_screengo(oparg_T *oap, int dir, int dist)
+bool nv_screengo(oparg_T *oap, int dir, int dist)
{
int linelen = linetabsize(curwin, curwin->w_cursor.lnum);
bool retval = true;
@@ -2614,58 +2614,6 @@ void nv_scroll_line(cmdarg_T *cap)
}
}
-/// Scroll "count" lines up or down, and redraw.
-void scroll_redraw(bool up, linenr_T count)
-{
- linenr_T prev_topline = curwin->w_topline;
- int prev_skipcol = curwin->w_skipcol;
- int prev_topfill = curwin->w_topfill;
- linenr_T prev_lnum = curwin->w_cursor.lnum;
-
- bool moved = up
- ? scrollup(curwin, count, true)
- : scrolldown(curwin, count, true);
-
- if (get_scrolloff_value(curwin) > 0) {
- // Adjust the cursor position for 'scrolloff'. Mark w_topline as
- // valid, otherwise the screen jumps back at the end of the file.
- cursor_correct(curwin);
- check_cursor_moved(curwin);
- curwin->w_valid |= VALID_TOPLINE;
-
- // If moved back to where we were, at least move the cursor, otherwise
- // we get stuck at one position. Don't move the cursor up if the
- // first line of the buffer is already on the screen
- while (curwin->w_topline == prev_topline
- && curwin->w_skipcol == prev_skipcol
- && curwin->w_topfill == prev_topfill) {
- if (up) {
- if (curwin->w_cursor.lnum > prev_lnum
- || cursor_down(1, false) == false) {
- break;
- }
- } else {
- if (curwin->w_cursor.lnum < prev_lnum
- || prev_topline == 1
- || cursor_up(1, false) == false) {
- break;
- }
- }
- // Mark w_topline as valid, otherwise the screen jumps back at the
- // end of the file.
- check_cursor_moved(curwin);
- curwin->w_valid |= VALID_TOPLINE;
- }
- }
- if (curwin->w_cursor.lnum != prev_lnum) {
- coladvance(curwin, curwin->w_curswant);
- }
- if (moved) {
- curwin->w_viewport_invalid = true;
- }
- redraw_later(curwin, UPD_VALID);
-}
-
/// Get the count specified after a 'z' command. Only the 'z<CR>', 'zl', 'zh',
/// 'z<Left>', and 'z<Right>' commands accept a count after 'z'.
/// @return true to process the 'z' command and false to skip it.
@@ -5223,7 +5171,7 @@ static void nv_gv_cmd(cmdarg_T *cap)
/// "g0", "g^" : Like "0" and "^" but for screen lines.
/// "gm": middle of "g0" and "g$".
-static void nv_g_home_m_cmd(cmdarg_T *cap)
+void nv_g_home_m_cmd(cmdarg_T *cap)
{
int i;
const bool flag = cap->nchar == '^';
@@ -5239,6 +5187,15 @@ static void nv_g_home_m_cmd(cmdarg_T *cap)
if (curwin->w_virtcol >= (colnr_T)width1 && width2 > 0) {
i = (curwin->w_virtcol - width1) / width2 * width2 + width1;
}
+
+ // When ending up below 'smoothscroll' marker, move just beyond it so
+ // 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);
+ if (overlap > 0 && i == curwin->w_skipcol) {
+ i += overlap;
+ }
+ }
} else {
i = curwin->w_leftcol;
}
@@ -6402,7 +6359,7 @@ static void nv_at(cmdarg_T *cap)
static void nv_halfpage(cmdarg_T *cap)
{
if (!checkclearop(cap->oap)) {
- pagescroll(cap->cmdchar == Ctrl_D, cap->count0, true);
+ pagescroll(cap->cmdchar == Ctrl_D ? FORWARD : BACKWARD, cap->count0, true);
}
}
diff --git a/src/nvim/plines.c b/src/nvim/plines.c
index d90ee9c1ba..8734780415 100644
--- a/src/nvim/plines.c
+++ b/src/nvim/plines.c
@@ -885,6 +885,9 @@ int plines_m_win(win_T *wp, linenr_T first, linenr_T last, bool limit_winheight)
count += plines_win_full(wp, first, &next, NULL, false, limit_winheight);
first = next + 1;
}
+ if (first == wp->w_buffer->b_ml.ml_line_count + 1) {
+ count += win_get_fill(wp, first);
+ }
return count;
}
diff --git a/test/functional/legacy/matchparen_spec.lua b/test/functional/legacy/matchparen_spec.lua
index db5a24e2e0..0a21efeb7c 100644
--- a/test/functional/legacy/matchparen_spec.lua
+++ b/test/functional/legacy/matchparen_spec.lua
@@ -19,7 +19,7 @@ describe('matchparen', function()
call cursor(5, 1)
]])
- feed('V<c-d><c-d>3j')
+ feed('V<c-d><c-d>')
screen:expect([[
{17:{} |
{17:}} |
diff --git a/test/functional/legacy/normal_spec.lua b/test/functional/legacy/normal_spec.lua
index 113f7c668a..c57e51c18e 100644
--- a/test/functional/legacy/normal_spec.lua
+++ b/test/functional/legacy/normal_spec.lua
@@ -94,10 +94,9 @@ describe('normal', function()
feed('ggG<C-D>')
screen:expect({
grid = [[
- foobar one two three |*8
+ foobar one two three |*16
^foobar one two three |
{2:---} |
- {1:~ }|*8
|
]],
})
diff --git a/test/old/testdir/test_diffmode.vim b/test/old/testdir/test_diffmode.vim
index 3e7eabf8f5..334bb3ee32 100644
--- a/test/old/testdir/test_diffmode.vim
+++ b/test/old/testdir/test_diffmode.vim
@@ -1664,8 +1664,7 @@ func Test_diff_scroll_many_filler()
endfor
set smoothscroll&
- bwipe!
- bwipe!
+ %bwipe!
endfunc
" This was trying to update diffs for a buffer being closed
@@ -1773,4 +1772,19 @@ func Test_diff_toggle_wrap_skipcol_leftcol()
bwipe!
endfunc
+" Ctrl-D reveals filler lines below the last line in the buffer.
+func Test_diff_eob_halfpage()
+ 5new
+ call setline(1, ['']->repeat(10) + ['a'])
+ diffthis
+ 5new
+ call setline(1, ['']->repeat(3) + ['a', 'b'])
+ diffthis
+ wincmd j
+ exe "norm! G\<C-D>"
+ call assert_equal(6, line('w0'))
+
+ %bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/test/old/testdir/test_edit.vim b/test/old/testdir/test_edit.vim
index 1bfe46c4a9..abd30074d5 100644
--- a/test/old/testdir/test_edit.vim
+++ b/test/old/testdir/test_edit.vim
@@ -1320,7 +1320,7 @@ func Test_edit_PAGEUP_PAGEDOWN()
call feedkeys("A\<PageUp>\<esc>", 'tnix')
call assert_equal([0, 10, 1, 0], getpos('.'))
call feedkeys("A\<PageUp>\<esc>", 'tnix')
- call assert_equal([0, 2, 11, 0], getpos('.'))
+ call assert_equal([0, 10, 11, 0], getpos('.'))
" <S-Up> is the same as <PageUp>
" <S-Down> is the same as <PageDown>
call cursor(1, 1)
@@ -1343,7 +1343,7 @@ func Test_edit_PAGEUP_PAGEDOWN()
call feedkeys("A\<S-Up>\<esc>", 'tnix')
call assert_equal([0, 10, 1, 0], getpos('.'))
call feedkeys("A\<S-Up>\<esc>", 'tnix')
- call assert_equal([0, 2, 11, 0], getpos('.'))
+ call assert_equal([0, 10, 11, 0], getpos('.'))
set nostartofline
call cursor(30, 11)
norm! zt
@@ -1356,7 +1356,7 @@ func Test_edit_PAGEUP_PAGEDOWN()
call feedkeys("A\<PageUp>\<esc>", 'tnix')
call assert_equal([0, 10, 11, 0], getpos('.'))
call feedkeys("A\<PageUp>\<esc>", 'tnix')
- call assert_equal([0, 2, 11, 0], getpos('.'))
+ call assert_equal([0, 10, 11, 0], getpos('.'))
call cursor(1, 1)
call feedkeys("A\<PageDown>\<esc>", 'tnix')
call assert_equal([0, 9, 11, 0], getpos('.'))
@@ -1381,7 +1381,7 @@ func Test_edit_PAGEUP_PAGEDOWN()
call feedkeys("A\<S-Up>\<esc>", 'tnix')
call assert_equal([0, 10, 11, 0], getpos('.'))
call feedkeys("A\<S-Up>\<esc>", 'tnix')
- call assert_equal([0, 2, 11, 0], getpos('.'))
+ call assert_equal([0, 10, 11, 0], getpos('.'))
call cursor(1, 1)
call feedkeys("A\<S-Down>\<esc>", 'tnix')
call assert_equal([0, 9, 11, 0], getpos('.'))
diff --git a/test/old/testdir/test_normal.vim b/test/old/testdir/test_normal.vim
index 26270fddd2..10fbf4125a 100644
--- a/test/old/testdir/test_normal.vim
+++ b/test/old/testdir/test_normal.vim
@@ -1283,13 +1283,9 @@ func Test_vert_scroll_cmds()
exe "normal \<C-D>"
call assert_equal(46, line('.'))
exe "normal \<C-U>"
- call assert_equal(36, line('w0'))
- call assert_equal(46, line('.'))
- exe "normal \<C-U>"
- call assert_equal(1, line('w0'))
- call assert_equal(40, line('.'))
+ call assert_equal(36, line('.'))
exe "normal \<C-U>"
- call assert_equal(30, line('.'))
+ call assert_equal(1, line('.'))
exe "normal \<C-U>"
call assert_equal(1, line('.'))
set scroll&
@@ -1310,8 +1306,9 @@ func Test_vert_scroll_cmds()
call assert_equal(50, line('.'))
call assert_equal(100, line('w$'))
normal z.
+ let lnum = winline()
exe "normal \<C-D>"
- call assert_equal(1, winline())
+ call assert_equal(lnum, winline())
call assert_equal(50, line('.'))
normal zt
exe "normal \<C-D>"
@@ -3080,8 +3077,7 @@ func Test_normal42_halfpage()
call assert_equal(2, &scroll)
set scroll=5
exe "norm! \<c-u>"
- call assert_equal('3', getline('w0'))
- call assert_equal('8', getline('.'))
+ call assert_equal('3', getline('.'))
1
set scrolloff=5
exe "norm! \<c-d>"
@@ -3828,7 +3824,7 @@ func Test_normal_vert_scroll_longline()
call assert_equal(1, winline())
exe "normal \<C-B>"
call assert_equal(10, line('.'))
- call assert_equal(10, winline())
+ call assert_equal(4, winline())
exe "normal \<C-B>\<C-B>"
call assert_equal(5, line('.'))
call assert_equal(5, winline())
@@ -4224,4 +4220,19 @@ func Test_single_line_scroll()
call prop_type_delete(vt)
endfunc
+" Test for zb in buffer with a single line and filler lines
+func Test_single_line_filler_zb()
+ call setline(1, ['', 'foobar one two three'])
+ diffthis
+ new
+ call setline(1, ['foobar one two three'])
+ diffthis
+
+ " zb scrolls to reveal filler lines at the start of the buffer.
+ exe "normal \<C-E>zb"
+ call assert_equal(1, winsaveview().topfill)
+
+ bw!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab nofoldenable
diff --git a/test/old/testdir/test_scroll_opt.vim b/test/old/testdir/test_scroll_opt.vim
index 575ce0c6b5..cb37b09707 100644
--- a/test/old/testdir/test_scroll_opt.vim
+++ b/test/old/testdir/test_scroll_opt.vim
@@ -552,14 +552,14 @@ func Test_smoothscroll_cursor_position()
exe "normal \<C-Y>"
call s:check_col_calc(1, 3, 41)
- " Test "g0/g<Home>"
+ " Test "g0/g<Home>"
exe "normal gg\<C-E>"
norm $gkg0
- call s:check_col_calc(1, 2, 21)
+ call s:check_col_calc(4, 1, 24)
" Test moving the cursor behind the <<< display with 'virtualedit'
set virtualedit=all
- exe "normal \<C-E>3lgkh"
+ exe "normal \<C-E>gkh"
call s:check_col_calc(3, 2, 23)
set virtualedit&
@@ -1020,26 +1020,93 @@ func Test_smoothscroll_page()
exe "norm! \<C-B>"
call assert_equal(0, winsaveview().skipcol)
- exe "norm! \<C-D>"
+ " Half-page scrolling does not go beyond end of buffer and moves the cursor.
+ " Even with 'nostartofline', the correct amount of lines is scrolled.
+ setl nostartofline
+ exe "norm! 15|\<C-D>"
call assert_equal(200, winsaveview().skipcol)
+ call assert_equal(215, col('.'))
exe "norm! \<C-D>"
call assert_equal(400, winsaveview().skipcol)
+ call assert_equal(415, col('.'))
exe "norm! \<C-D>"
- call assert_equal(600, winsaveview().skipcol)
+ call assert_equal(520, winsaveview().skipcol)
+ call assert_equal(535, col('.'))
exe "norm! \<C-D>"
- call assert_equal(800, winsaveview().skipcol)
+ call assert_equal(520, winsaveview().skipcol)
+ call assert_equal(735, col('.'))
exe "norm! \<C-D>"
- call assert_equal(880, winsaveview().skipcol)
+ call assert_equal(520, winsaveview().skipcol)
+ call assert_equal(895, col('.'))
exe "norm! \<C-U>"
- call assert_equal(680, winsaveview().skipcol)
+ call assert_equal(320, winsaveview().skipcol)
+ call assert_equal(695, col('.'))
exe "norm! \<C-U>"
- call assert_equal(480, winsaveview().skipcol)
+ call assert_equal(120, winsaveview().skipcol)
+ call assert_equal(495, col('.'))
exe "norm! \<C-U>"
- call assert_equal(280, winsaveview().skipcol)
+ call assert_equal(0, winsaveview().skipcol)
+ call assert_equal(375, col('.'))
exe "norm! \<C-U>"
- call assert_equal(80, winsaveview().skipcol)
+ call assert_equal(0, winsaveview().skipcol)
+ call assert_equal(175, col('.'))
exe "norm! \<C-U>"
call assert_equal(0, winsaveview().skipcol)
+ call assert_equal(15, col('.'))
+
+ bwipe!
+endfunc
+
+func Test_smoothscroll_next_topline()
+ call NewWindow(10, 40)
+ setlocal smoothscroll
+ call setline(1, ['abcde '->repeat(150)]->repeat(2))
+
+ " Scrolling a screenline that causes the cursor to move to the next buffer
+ " line should not skip part of that line to bring the cursor into view.
+ exe "norm! 22\<C-E>"
+ call assert_equal(880, winsaveview().skipcol)
+ exe "norm! \<C-E>"
+ redraw
+ call assert_equal(0, winsaveview().skipcol)
+
+ " Also when scrolling back.
+ exe "norm! G\<C-Y>"
+ redraw
+ call assert_equal(880, winsaveview().skipcol)
+
+ " Cursor in correct place when not in the first screenline of a buffer line.
+ exe "norm! gg4gj20\<C-D>\<C-D>"
+ redraw
+ call assert_equal(2, line('w0'))
+
+ " Cursor does not end up above topline, adjusting topline later.
+ setlocal nu cpo+=n
+ exe "norm! G$g013\<C-Y>"
+ redraw
+ call assert_equal(2, line('.'))
+ call assert_equal(0, winsaveview().skipcol)
+
+ set cpo-=n
+ bwipe!
+endfunc
+
+func Test_smoothscroll_long_line_zb()
+ call NewWindow(10, 40)
+ call setline(1, 'abcde '->repeat(150))
+
+ " Also works without 'smoothscroll' when last line of buffer doesn't fit.
+ " Used to set topline to buffer line count plus one, causing an empty screen.
+ norm zb
+ redraw
+ call assert_equal(1, winsaveview().topline)
+
+ " Moving cursor to bottom works on line that doesn't fit with 'smoothscroll'.
+ " Skipcol was adjusted later for cursor being on not visible part of line.
+ setlocal smoothscroll
+ norm zb
+ redraw
+ call assert_equal(520, winsaveview().skipcol)
bwipe!
endfunc