diff options
Diffstat (limited to 'src/nvim/move.c')
-rw-r--r-- | src/nvim/move.c | 725 |
1 files changed, 596 insertions, 129 deletions
diff --git a/src/nvim/move.c b/src/nvim/move.c index b749d07d15..447926ceb8 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -57,6 +57,43 @@ typedef struct { # include "move.c.generated.h" #endif +/// Reduce "n" for the screen lines skipped with "wp->w_skipcol". +static int adjust_plines_for_skipcol(win_T *wp, int n) +{ + if (wp->w_skipcol == 0) { + return n; + } + + int off = 0; + int width = wp->w_width_inner - win_col_off(wp); + if (wp->w_skipcol >= width) { + off++; + int skip = wp->w_skipcol - width; + width -= win_col_off2(wp); + while (skip >= width) { + off++; + skip -= width; + } + } + wp->w_valid &= ~VALID_WROW; + return n - off; +} + +/// 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) +{ + int n = plines_win_full(wp, lnum, nextp, foldedp, true, false); + if (lnum == wp->w_topline) { + n = adjust_plines_for_skipcol(wp, n); + } + if (n > wp->w_height_inner) { + return wp->w_height_inner; + } + return n; +} + // Compute wp->w_botline for the current wp->w_topline. Can be called after // wp->w_topline changed. static void comp_botline(win_T *wp) @@ -78,7 +115,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_win_full(wp, lnum, &last, &folded, true); + int n = plines_correct_topline(wp, lnum, &last, &folded); if (lnum <= wp->w_cursor.lnum && last >= wp->w_cursor.lnum) { wp->w_cline_row = done; wp->w_cline_height = n; @@ -127,6 +164,51 @@ static void redraw_for_cursorcolumn(win_T *wp) } } +/// Calculates how much overlap the smoothscroll marker "<<<" overlaps with +/// buffer text for curwin. +/// Parameter "extra2" should be the padding on the 2nd line, not the first +/// line. +/// Returns the number of columns of overlap with buffer text, excluding the +/// extra padding on the ledge. +static int smoothscroll_marker_overlap(win_T *wp, int extra2) +{ + // We don't draw the <<< marker when in showbreak mode, thus no need to + // account for it. See grid_put_linebuf(). + if (*get_showbreak_value(wp) != NUL) { + return 0; + } + return extra2 > 3 ? 0 : 3 - extra2; +} + +/// Calculates the skipcol offset for window "wp" given how many +/// physical lines we want to scroll down. +static int skipcol_from_plines(win_T *wp, int plines_off) +{ + int width1 = wp->w_width_inner - win_col_off(wp); + + int skipcol = 0; + if (plines_off > 0) { + skipcol += width1; + } + if (plines_off > 1) { + skipcol += (width1 + win_col_off2(wp)) * (plines_off - 1); + } + return skipcol; +} + +/// Set wp->s_skipcol to zero and redraw later if needed. +static void reset_skipcol(win_T *wp) +{ + if (wp->w_skipcol != 0) { + wp->w_skipcol = 0; + + // Should use the least expensive way that displays all that changed. + // UPD_NOT_VALID is too expensive, UPD_REDRAW_TOP does not redraw + // enough when the top line gets another screen line. + redraw_later(wp, UPD_SOME_VALID); + } +} + // Update curwin->w_topline to move the cursor onto the screen. void update_topline(win_T *wp) { @@ -178,7 +260,7 @@ void update_topline(win_T *wp) bool check_topline = false; // If the cursor is above or near the top of the window, scroll the window // to show the line the cursor is in, with 'scrolloff' context. - if (wp->w_topline > 1) { + if (wp->w_topline > 1 || wp->w_skipcol > 0) { // If the cursor is above topline, scrolling is always needed. // If the cursor is far below topline and there is no folding, // scrolling down is never needed. @@ -186,6 +268,17 @@ void update_topline(win_T *wp) check_topline = true; } else if (check_top_offset()) { check_topline = true; + } else if (wp->w_skipcol > 0 && wp->w_cursor.lnum == wp->w_topline) { + colnr_T vcol; + + // Check that the cursor position is visible. Add columns for the + // smoothscroll marker "<<<" displayed in the top-left if needed. + getvvcol(wp, &wp->w_cursor, &vcol, NULL, NULL); + int smoothscroll_overlap = smoothscroll_marker_overlap(wp, + win_col_off(wp) - win_col_off2(wp)); + if (wp->w_skipcol + smoothscroll_overlap > vcol) { + check_topline = true; + } } } // Check if there are more filler lines than allowed. @@ -314,12 +407,9 @@ void update_topline(win_T *wp) if (wp->w_topline != old_topline || wp->w_topfill != old_topfill) { dollar_vcol = -1; - if (wp->w_skipcol != 0) { - wp->w_skipcol = 0; - redraw_later(wp, UPD_NOT_VALID); - } else { - redraw_later(wp, UPD_VALID); - } + redraw_later(wp, UPD_VALID); + reset_skipcol(wp); + // May need to set w_skipcol when cursor in w_topline. if (wp->w_cursor.lnum == wp->w_topline) { validate_cursor(); @@ -392,7 +482,15 @@ void check_cursor_moved(win_T *wp) |VALID_CHEIGHT|VALID_CROW|VALID_TOPLINE); wp->w_valid_cursor = wp->w_cursor; wp->w_valid_leftcol = wp->w_leftcol; + wp->w_valid_skipcol = wp->w_skipcol; wp->w_viewport_invalid = true; + } else if (wp->w_skipcol != wp->w_valid_skipcol) { + wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL + |VALID_CHEIGHT|VALID_CROW + |VALID_BOTLINE|VALID_BOTLINE_AP); + wp->w_valid_cursor = wp->w_cursor; + wp->w_valid_leftcol = wp->w_leftcol; + wp->w_valid_skipcol = wp->w_skipcol; } else if (wp->w_cursor.col != wp->w_valid_cursor.col || wp->w_leftcol != wp->w_valid_leftcol || wp->w_cursor.coladd != @@ -564,7 +662,7 @@ static void curs_rows(win_T *wp) } else { linenr_T last = lnum; bool folded; - int n = plines_win_full(wp, lnum, &last, &folded, false); + int n = plines_correct_topline(wp, lnum, &last, &folded); lnum = last + 1; if (folded && lnum > wp->w_cursor.lnum) { break; @@ -581,7 +679,7 @@ static void curs_rows(win_T *wp) && (!wp->w_lines[i].wl_valid || wp->w_lines[i].wl_lnum != wp->w_cursor.lnum))) { wp->w_cline_height = plines_win_full(wp, wp->w_cursor.lnum, NULL, - &wp->w_cline_folded, true); + &wp->w_cline_folded, true, true); } else if (i > wp->w_lines_valid) { // a line that is too long to fit on the last screen line wp->w_cline_height = 0; @@ -628,7 +726,7 @@ void validate_cheight(void) curwin->w_cline_height = plines_win_full(curwin, curwin->w_cursor.lnum, NULL, &curwin->w_cline_folded, - true); + true, true); curwin->w_valid |= VALID_CHEIGHT; } @@ -679,8 +777,8 @@ int curwin_col_off(void) } // Return the difference in column offset for the second screen line of a -// wrapped line. It's 8 if 'number' or 'relativenumber' is on and 'n' is in -// 'cpoptions'. +// wrapped line. It's positive if 'number' or 'relativenumber' is on and 'n' +// is in 'cpoptions'. int win_col_off2(win_T *wp) { if ((wp->w_p_nu || wp->w_p_rnu) && vim_strchr(p_cpo, CPO_NUMCOL) != NULL) { @@ -694,19 +792,14 @@ int curwin_col_off2(void) return win_col_off2(curwin); } -// Compute curwin->w_wcol and curwin->w_virtcol. -// Also updates curwin->w_wrow and curwin->w_cline_row. -// Also updates curwin->w_leftcol. +// Compute wp->w_wcol and wp->w_virtcol. +// Also updates wp->w_wrow and wp->w_cline_row. +// Also updates wp->w_leftcol. // @param may_scroll when true, may scroll horizontally void curs_columns(win_T *wp, int may_scroll) { - int n; - int width = 0; colnr_T startcol; colnr_T endcol; - colnr_T prev_skipcol; - long so = get_scrolloff_value(wp); - long siso = get_sidescrolloff_value(wp); // First make sure that w_topline is valid (after moving the cursor). update_topline(wp); @@ -736,8 +829,11 @@ void curs_columns(win_T *wp, int may_scroll) // Now compute w_wrow, counting screen lines from w_cline_row. wp->w_wrow = wp->w_cline_row; - int textwidth = wp->w_width_inner - extra; - if (textwidth <= 0) { + int n; + int width1 = wp->w_width_inner - extra; // text width for first screen line + int width2 = 0; // text width for second and later screen line + bool did_sub_skipcol = false; + if (width1 <= 0) { // No room for text, put cursor in last char of window. // If not wrapping, the last non-empty line. wp->w_wcol = wp->w_width_inner - 1; @@ -747,13 +843,28 @@ void curs_columns(win_T *wp, int may_scroll) wp->w_wrow = wp->w_height_inner - 1 - wp->w_empty_rows; } } else if (wp->w_p_wrap && wp->w_width_inner != 0) { - width = textwidth + win_col_off2(wp); + width2 = width1 + win_col_off2(wp); + + // skip columns that are not visible + if (wp->w_cursor.lnum == wp->w_topline + && wp->w_skipcol > 0 + && wp->w_wcol >= wp->w_skipcol) { + // Deduct by multiples of width2. This allows the long line wrapping + // formula below to correctly calculate the w_wcol value when wrapping. + if (wp->w_skipcol <= width1) { + wp->w_wcol -= width2; + } else { + wp->w_wcol -= width2 * (((wp->w_skipcol - width1) / width2) + 1); + } + + did_sub_skipcol = true; + } // long line wrapping, adjust wp->w_wrow if (wp->w_wcol >= wp->w_width_inner) { // this same formula is used in validate_cursor_col() - n = (wp->w_wcol - wp->w_width_inner) / width + 1; - wp->w_wcol -= n * width; + n = (wp->w_wcol - wp->w_width_inner) / width2 + 1; + wp->w_wcol -= n * width2; wp->w_wrow += n; // When cursor wraps to first char of next line in Insert @@ -775,6 +886,7 @@ void curs_columns(win_T *wp, int may_scroll) // If Cursor is right of the screen, scroll leftwards // If we get closer to the edge than 'sidescrolloff', scroll a little // extra + long siso = get_sidescrolloff_value(wp); assert(siso <= INT_MAX); int off_left = startcol - wp->w_leftcol - (int)siso; int off_right = @@ -785,8 +897,8 @@ void curs_columns(win_T *wp, int may_scroll) // When far off or not enough room on either side, put cursor in // middle of window. int new_leftcol; - if (p_ss == 0 || diff >= textwidth / 2 || off_right >= off_left) { - new_leftcol = wp->w_wcol - extra - textwidth / 2; + if (p_ss == 0 || diff >= width1 / 2 || off_right >= off_left) { + new_leftcol = curwin->w_wcol - extra - width1 / 2; } else { if (diff < p_ss) { assert(p_ss <= INT_MAX); @@ -823,9 +935,9 @@ void curs_columns(win_T *wp, int may_scroll) wp->w_wrow += win_get_fill(wp, wp->w_cursor.lnum); } - prev_skipcol = wp->w_skipcol; - int plines = 0; + long so = get_scrolloff_value(wp); + colnr_T prev_skipcol = wp->w_skipcol; if ((wp->w_wrow >= wp->w_height_inner || ((prev_skipcol > 0 || wp->w_wrow + so >= wp->w_height_inner) @@ -833,7 +945,7 @@ void curs_columns(win_T *wp, int may_scroll) >= wp->w_height_inner)) && wp->w_height_inner != 0 && wp->w_cursor.lnum == wp->w_topline - && width > 0 + && width2 > 0 && wp->w_width_inner != 0) { // Cursor past end of screen. Happens with a single line that does // not fit on screen. Find a skipcol to show the text around the @@ -842,7 +954,7 @@ void curs_columns(win_T *wp, int may_scroll) // 2: Less than "p_so" lines below // 3: both of them extra = 0; - if (wp->w_skipcol + so * width > wp->w_virtcol) { + if (wp->w_skipcol + so * width2 > wp->w_virtcol) { extra = 1; } // Compute last display line of the buffer line that we want at the @@ -857,13 +969,13 @@ void curs_columns(win_T *wp, int may_scroll) } else { n = plines; } - if ((colnr_T)n >= wp->w_height_inner + wp->w_skipcol / width - so) { + if ((colnr_T)n >= wp->w_height_inner + wp->w_skipcol / width2 - so) { extra += 2; } - if (extra == 3 || plines <= so * 2) { + if (extra == 3 || wp->w_height_inner <= so * 2) { // not enough room for 'scrolloff', put cursor in the middle - n = wp->w_virtcol / width; + n = wp->w_virtcol / width2; if (n > wp->w_height_inner / 2) { n -= wp->w_height_inner / 2; } else { @@ -873,51 +985,58 @@ void curs_columns(win_T *wp, int may_scroll) if (n > plines - wp->w_height_inner + 1) { n = plines - wp->w_height_inner + 1; } - wp->w_skipcol = n * width; + wp->w_skipcol = n * width2; } else if (extra == 1) { // less than 'scrolloff' lines above, decrease skipcol assert(so <= INT_MAX); - extra = (wp->w_skipcol + (int)so * width - wp->w_virtcol - + width - 1) / width; + extra = (wp->w_skipcol + (int)so * width2 - wp->w_virtcol + width2 - 1) / width2; if (extra > 0) { - if ((colnr_T)(extra * width) > wp->w_skipcol) { - extra = wp->w_skipcol / width; + if ((colnr_T)(extra * width2) > wp->w_skipcol) { + extra = wp->w_skipcol / width2; } - wp->w_skipcol -= extra * width; + wp->w_skipcol -= extra * width2; } } else if (extra == 2) { // less than 'scrolloff' lines below, increase skipcol - endcol = (n - wp->w_height_inner + 1) * width; + endcol = (n - wp->w_height_inner + 1) * width2; while (endcol > wp->w_virtcol) { - endcol -= width; + endcol -= width2; } if (endcol > wp->w_skipcol) { wp->w_skipcol = endcol; } } - wp->w_wrow -= wp->w_skipcol / width; + // adjust w_wrow for the changed w_skipcol + if (did_sub_skipcol) { + wp->w_wrow -= (wp->w_skipcol - prev_skipcol) / width2; + } else { + wp->w_wrow -= wp->w_skipcol / width2; + } + if (wp->w_wrow >= wp->w_height_inner) { // small window, make sure cursor is in it extra = wp->w_wrow - wp->w_height_inner + 1; - wp->w_skipcol += extra * width; + wp->w_skipcol += extra * width2; wp->w_wrow -= extra; } // extra could be either positive or negative - extra = ((int)prev_skipcol - (int)wp->w_skipcol) / width; + extra = (prev_skipcol - wp->w_skipcol) / width2; win_scroll_lines(wp, 0, extra); - } else { + } else if (!wp->w_p_sms) { wp->w_skipcol = 0; } if (prev_skipcol != wp->w_skipcol) { - redraw_later(wp, UPD_NOT_VALID); + redraw_later(wp, UPD_SOME_VALID); } - redraw_for_cursorcolumn(curwin); + redraw_for_cursorcolumn(wp); - // now w_leftcol is valid, avoid check_cursor_moved() thinking otherwise + // now w_leftcol and w_skipcol are valid, avoid check_cursor_moved() + // thinking otherwise wp->w_valid_leftcol = wp->w_leftcol; + wp->w_valid_skipcol = wp->w_skipcol; wp->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL; } @@ -966,18 +1085,18 @@ void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp, int *ccolp, // similar to what is done in validate_cursor_col() colnr_T col = scol; col += off; - int width = wp->w_width - off + win_col_off2(wp); + int width = wp->w_width_inner - off + win_col_off2(wp); // long line wrapping, adjust row - if (wp->w_p_wrap && col >= (colnr_T)wp->w_width && width > 0) { + if (wp->w_p_wrap && col >= (colnr_T)wp->w_width_inner && width > 0) { // use same formula as what is used in curs_columns() - rowoff = visible_row ? ((col - wp->w_width) / width + 1) : 0; + rowoff = visible_row ? ((col - wp->w_width_inner) / width + 1) : 0; col -= rowoff * width; } col -= wp->w_leftcol; - if (col >= 0 && col < wp->w_width && row + rowoff <= wp->w_height) { + if (col >= 0 && col < wp->w_width_inner && row + rowoff <= wp->w_height_inner) { coloff = col - scol + (local ? 0 : wp->w_wincol + wp->w_wincol_off) + 1; row += local ? 0 : wp->w_winrow + wp->w_winrow_off; } else { @@ -1064,32 +1183,69 @@ void f_virtcol2col(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) bool scrolldown(long line_count, int byfold) { int done = 0; // total # of physical lines done + int width1 = 0; + int width2 = 0; + bool do_sms = curwin->w_p_wrap && curwin->w_p_sms; + + if (do_sms) { + width1 = curwin->w_width_inner - curwin_col_off(); + width2 = width1 + curwin_col_off2(); + } // Make sure w_topline is at the first of a sequence of folded lines. (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL); validate_cursor(); // w_wrow needs to be valid - while (line_count-- > 0) { + for (long todo = line_count; todo > 0; todo--) { if (curwin->w_topfill < win_get_fill(curwin, curwin->w_topline) && curwin->w_topfill < curwin->w_height_inner - 1) { curwin->w_topfill++; done++; } else { - if (curwin->w_topline == 1) { + // break when at the very top + if (curwin->w_topline == 1 && (!do_sms || curwin->w_skipcol < width1)) { break; } - curwin->w_topline--; - curwin->w_topfill = 0; - // A sequence of folded lines only counts for one logical line - linenr_T first; - if (hasFolding(curwin->w_topline, &first, NULL)) { - done++; - if (!byfold) { - line_count -= curwin->w_topline - first - 1; + if (do_sms && curwin->w_skipcol >= width1) { + // scroll a screen line down + if (curwin->w_skipcol >= width1 + width2) { + curwin->w_skipcol -= width2; + } else { + curwin->w_skipcol -= width1; } - curwin->w_botline -= curwin->w_topline - first; - curwin->w_topline = first; + redraw_later(curwin, UPD_NOT_VALID); + done++; } else { - done += plines_win_nofill(curwin, curwin->w_topline, true); + // scroll a text line down + curwin->w_topline--; + curwin->w_skipcol = 0; + curwin->w_topfill = 0; + // A sequence of folded lines only counts for one logical line + linenr_T first; + if (hasFolding(curwin->w_topline, &first, NULL)) { + done++; + if (!byfold) { + todo -= curwin->w_topline - first - 1; + } + curwin->w_botline -= curwin->w_topline - first; + curwin->w_topline = first; + } else { + if (do_sms) { + int size = (int)win_linetabsize(curwin, curwin->w_topline, + ml_get(curwin->w_topline), (colnr_T)MAXCOL); + if (size > width1) { + curwin->w_skipcol = width1; + size -= width1; + redraw_later(curwin, UPD_NOT_VALID); + } + while (size > width2) { + curwin->w_skipcol += width2; + size -= width2; + } + done++; + } else { + done += plines_win_nofill(curwin, curwin->w_topline, true); + } + } } } curwin->w_botline--; // approximate w_botline @@ -1134,9 +1290,39 @@ bool scrolldown(long line_count, int byfold) foldAdjustCursor(); coladvance(curwin->w_curswant); } + + if (curwin->w_cursor.lnum == curwin->w_topline && do_sms) { + long so = get_scrolloff_value(curwin); + long scrolloff_cols = so == 0 ? 0 : width1 + (so - 1) * width2; + + // make sure the cursor is in the visible text + validate_virtcol(); + long col = curwin->w_virtcol - curwin->w_skipcol + scrolloff_cols; + int row = 0; + if (col >= width1) { + col -= width1; + row++; + } + if (col > width2 && width2 > 0) { + row += (int)col / width2; + col = col % width2; + } + if (row >= curwin->w_height_inner) { + curwin->w_curswant = curwin->w_virtcol - (row - curwin->w_height_inner + 1) * width2; + coladvance(curwin->w_curswant); + } + } return moved; } +/// Return TRUE if scrollup() will scroll by screen line rather than text line. +static int scrolling_screenlines(bool byfold) +{ + return (curwin->w_p_wrap && curwin->w_p_sms) + || (byfold && hasAnyFolding(curwin)) + || (curwin->w_p_diff && !curwin->w_p_wrap); +} + /// Scroll the current window up by "line_count" logical lines. "CTRL-E" /// /// @param line_count number of lines to scroll @@ -1145,28 +1331,68 @@ bool scrollup(long line_count, int byfold) { linenr_T topline = curwin->w_topline; linenr_T botline = curwin->w_botline; + int do_sms = curwin->w_p_wrap && curwin->w_p_sms; + + if (scrolling_screenlines(byfold) || win_may_fill(curwin)) { + int width1 = curwin->w_width_inner - curwin_col_off(); + int width2 = width1 + curwin_col_off2(); + unsigned size = 0; + linenr_T prev_topline = curwin->w_topline; + + if (do_sms) { + size = linetabsize(curwin, curwin->w_topline); + } - if ((byfold && hasAnyFolding(curwin)) - || win_may_fill(curwin)) { - // count each sequence of folded lines as one logical line - linenr_T lnum = curwin->w_topline; - while (line_count--) { + // diff mode: first consume "topfill" + // 'smoothscroll': increase "w_skipcol" until it goes over the end of + // the line, then advance to the next line. + // folding: count each sequence of folded lines as one logical line. + for (long todo = line_count; todo > 0; todo--) { if (curwin->w_topfill > 0) { curwin->w_topfill--; } else { + linenr_T lnum = curwin->w_topline; if (byfold) { + // for a closed fold: go to the last line in the fold (void)hasFolding(lnum, NULL, &lnum); } - if (lnum >= curbuf->b_ml.ml_line_count) { - break; + if (lnum == curwin->w_topline && do_sms) { + // 'smoothscroll': increase "w_skipcol" until it goes over + // the end of the line, then advance to the next line. + int add = curwin->w_skipcol > 0 ? width2 : width1; + curwin->w_skipcol += add; + if ((unsigned)curwin->w_skipcol >= size) { + if (lnum == curbuf->b_ml.ml_line_count) { + // at the last screen line, can't scroll further + curwin->w_skipcol -= add; + break; + } + lnum++; + } + } else { + if (lnum >= curbuf->b_ml.ml_line_count) { + break; + } + lnum++; + } + + if (lnum > curwin->w_topline) { + // approximate w_botline + curwin->w_botline += lnum - curwin->w_topline; + curwin->w_topline = lnum; + curwin->w_topfill = win_get_fill(curwin, lnum); + curwin->w_skipcol = 0; + if (todo > 1 && do_sms) { + size = linetabsize(curwin, curwin->w_topline); + } } - lnum++; - curwin->w_topfill = win_get_fill(curwin, lnum); } } - // approximate w_botline - curwin->w_botline += lnum - curwin->w_topline; - curwin->w_topline = lnum; + + if (curwin->w_topline == prev_topline) { + // need to redraw even though w_topline didn't change + redraw_later(curwin, UPD_NOT_VALID); + } } else { curwin->w_topline += (linenr_T)line_count; curwin->w_botline += (linenr_T)line_count; // approximate w_botline @@ -1194,12 +1420,117 @@ bool scrollup(long line_count, int byfold) coladvance(curwin->w_curswant); } - bool moved = topline != curwin->w_topline - || botline != curwin->w_botline; + if (curwin->w_cursor.lnum == curwin->w_topline && do_sms && curwin->w_skipcol > 0) { + int col_off = curwin_col_off(); + int col_off2 = curwin_col_off2(); + + int width1 = curwin->w_width_inner - col_off; + int width2 = width1 + col_off2; + int extra2 = col_off - col_off2; + long so = get_scrolloff_value(curwin); + long scrolloff_cols = so == 0 ? 0 : width1 + (so - 1) * width2; + int space_cols = (curwin->w_height_inner - 1) * width2; + + // If we have non-zero scrolloff, just ignore the <<< marker as we are + // going past it anyway. + int smoothscroll_overlap = scrolloff_cols != 0 ? 0 : + smoothscroll_marker_overlap(curwin, 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(); + if (curwin->w_virtcol < curwin->w_skipcol + smoothscroll_overlap + scrolloff_cols) { + colnr_T col = curwin->w_virtcol; + + if (col < width1) { + col += width1; + } + while (col < curwin->w_skipcol + smoothscroll_overlap + scrolloff_cols) { + col += width2; + } + curwin->w_curswant = col; + 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); + } + } + + bool moved = topline != curwin->w_topline || botline != curwin->w_botline; return moved; } +/// Called after changing the cursor column: make sure that curwin->w_skipcol is +/// valid for 'smoothscroll'. +void adjust_skipcol(void) +{ + if (!curwin->w_p_wrap || !curwin->w_p_sms || curwin->w_cursor.lnum != curwin->w_topline) { + return; + } + + int width1 = curwin->w_width_inner - curwin_col_off(); + if (width1 <= 0) { + return; // no text will be displayed + } + int width2 = width1 + curwin_col_off2(); + long so = get_scrolloff_value(curwin); + long scrolloff_cols = so == 0 ? 0 : width1 + (so - 1) * width2; + bool scrolled = false; + + validate_cheight(); + if (curwin->w_cline_height == curwin->w_height_inner + // w_cline_height may be capped at w_height_inner, check there aren't + // actually more lines. + && plines_win(curwin, curwin->w_cursor.lnum, false) <= curwin->w_height_inner) { + // the line just fits in the window, don't scroll + reset_skipcol(curwin); + return; + } + + validate_virtcol(); + while (curwin->w_skipcol > 0 + && curwin->w_virtcol < curwin->w_skipcol + 3 + scrolloff_cols) { + // scroll a screen line down + if (curwin->w_skipcol >= width1 + width2) { + curwin->w_skipcol -= width2; + } else { + curwin->w_skipcol -= width1; + } + redraw_later(curwin, UPD_NOT_VALID); + scrolled = true; + validate_virtcol(); + } + if (scrolled) { + return; // don't scroll in the other direction now + } + long col = curwin->w_virtcol - curwin->w_skipcol + scrolloff_cols; + int row = 0; + if (col >= width1) { + col -= width1; + row++; + } + if (col > width2) { + row += (int)col / width2; + col = col % width2; + } + if (row >= curwin->w_height_inner) { + if (curwin->w_skipcol == 0) { + curwin->w_skipcol += width1; + row--; + } + if (row >= curwin->w_height_inner) { + curwin->w_skipcol += (row - curwin->w_height_inner) * width2; + } + redraw_later(curwin, UPD_NOT_VALID); + } +} + /// Don't end up with too many filler lines in the window. /// /// @param down when true scroll down when not enough space @@ -1316,7 +1647,8 @@ void scrollup_clamp(void) // a (wrapped) text line. Uses and sets "lp->fill". // Returns the height of the added line in "lp->height". // Lines above the first one are incredibly high: MAXCOL. -static void topline_back(win_T *wp, lineoff_T *lp) +// When "winheight" is true limit to window height. +static void topline_back_winheight(win_T *wp, lineoff_T *lp, int winheight) { if (lp->fill < win_get_fill(wp, lp->lnum)) { // Add a filler line @@ -1331,11 +1663,16 @@ static void topline_back(win_T *wp, lineoff_T *lp) // Add a closed fold lp->height = 1; } else { - lp->height = plines_win_nofill(wp, lp->lnum, true); + lp->height = plines_win_nofill(wp, lp->lnum, winheight); } } } +static void topline_back(win_T *wp, lineoff_T *lp) +{ + topline_back_winheight(wp, lp, true); +} + // Add one line below "lp->lnum". This can be a filler line, a closed fold or // a (wrapped) text line. Uses and sets "lp->fill". // Returns the height of the added line in "lp->height". @@ -1388,12 +1725,9 @@ static void topline_botline(lineoff_T *lp) // If "always" is true, always set topline (for "zt"). void scroll_cursor_top(int min_scroll, int always) { - int scrolled = 0; - linenr_T top; // just above displayed lines - linenr_T bot; // just below displayed lines linenr_T old_topline = curwin->w_topline; + int old_skipcol = curwin->w_skipcol; linenr_T old_topfill = curwin->w_topfill; - linenr_T new_topline; int off = (int)get_scrolloff_value(curwin); if (mouse_dragging > 0) { @@ -1406,11 +1740,14 @@ void scroll_cursor_top(int min_scroll, int always) // - moved at least 'scrolljump' lines and // - at least 'scrolloff' lines above and below the cursor validate_cheight(); + int scrolled = 0; int used = curwin->w_cline_height; // includes filler lines above if (curwin->w_cursor.lnum < curwin->w_topline) { scrolled = used; } + linenr_T top; // just above displayed lines + linenr_T bot; // just below displayed lines if (hasFolding(curwin->w_cursor.lnum, &top, &bot)) { top--; bot++; @@ -1418,7 +1755,7 @@ void scroll_cursor_top(int min_scroll, int always) top = curwin->w_cursor.lnum - 1; bot = curwin->w_cursor.lnum + 1; } - new_topline = top + 1; + linenr_T new_topline = top + 1; // "used" already contains the number of filler lines above, don't add it // again. @@ -1431,6 +1768,15 @@ void scroll_cursor_top(int min_scroll, int always) int i = hasFolding(top, &top, NULL) ? 1 // count one logical line for a sequence of folded lines : plines_win_nofill(curwin, top, true); + if (top < curwin->w_topline) { + scrolled += i; + } + + // If scrolling is needed, scroll at least 'sj' lines. + if ((new_topline >= curwin->w_topline || scrolled > min_scroll) && extra >= off) { + break; + } + used += i; if (extra + i <= off && bot < curbuf->b_ml.ml_line_count) { if (hasFolding(bot, NULL, &bot)) { @@ -1443,15 +1789,6 @@ void scroll_cursor_top(int min_scroll, int always) if (used > curwin->w_height_inner) { break; } - if (top < curwin->w_topline) { - scrolled += i; - } - - // If scrolling is needed, scroll at least 'sj' lines. - if ((new_topline >= curwin->w_topline || scrolled > min_scroll) - && extra >= off) { - break; - } extra += i; new_topline = top; @@ -1466,7 +1803,7 @@ void scroll_cursor_top(int min_scroll, int always) scroll_cursor_halfway(false, false); } else { // If "always" is false, only adjust topline to a lower value, higher - // value may happen with wrapping lines + // value may happen with wrapping lines. if (new_topline < curwin->w_topline || always) { curwin->w_topline = new_topline; } @@ -1481,7 +1818,13 @@ void scroll_cursor_top(int min_scroll, int always) } } check_topfill(curwin, false); + // TODO(vim): if the line doesn't fit may optimize w_skipcol + if (curwin->w_topline == curwin->w_cursor.lnum + && curwin->w_skipcol >= curwin->w_cursor.col) { + reset_skipcol(curwin); + } if (curwin->w_topline != old_topline + || curwin->w_skipcol != old_skipcol || curwin->w_topfill != old_topfill) { curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); @@ -1519,20 +1862,18 @@ void set_empty_rows(win_T *wp, int used) void scroll_cursor_bot(int min_scroll, int set_topbot) { int used; - int scrolled = 0; - int extra = 0; lineoff_T loff; - lineoff_T boff; - int fill_below_window; - linenr_T old_topline = curwin->w_topline; - int old_topfill = curwin->w_topfill; - linenr_T old_botline = curwin->w_botline; - int old_valid = curwin->w_valid; + linenr_T old_topline = curwin->w_topline; + int old_skipcol = curwin->w_skipcol; + int old_topfill = curwin->w_topfill; + linenr_T old_botline = curwin->w_botline; + int old_valid = curwin->w_valid; int old_empty_rows = curwin->w_empty_rows; - linenr_T cln = curwin->w_cursor.lnum; // Cursor Line Number - long so = get_scrolloff_value(curwin); + linenr_T cln = curwin->w_cursor.lnum; // Cursor Line Number if (set_topbot) { + bool set_skipcol = false; + used = 0; curwin->w_botline = cln + 1; loff.fill = 0; @@ -1540,9 +1881,24 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) curwin->w_topline > 1; curwin->w_topline = loff.lnum) { loff.lnum = curwin->w_topline; - topline_back(curwin, &loff); - if (loff.height == MAXCOL - || used + loff.height > curwin->w_height_inner) { + topline_back_winheight(curwin, &loff, false); + if (loff.height == MAXCOL) { + break; + } + if (used + loff.height > curwin->w_height_inner) { + if (curwin->w_p_sms && curwin->w_p_wrap) { + // 'smoothscroll' and 'wrap' are set. The above line is + // too long to show in its entirety, so we show just a part + // of it. + if (used < curwin->w_height_inner) { + int plines_offset = used + loff.height - curwin->w_height_inner; + used = curwin->w_height_inner; + curwin->w_topfill = loff.fill; + curwin->w_topline = loff.lnum; + curwin->w_skipcol = skipcol_from_plines(curwin, plines_offset); + set_skipcol = true; + } + } break; } used += loff.height; @@ -1551,8 +1907,15 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) set_empty_rows(curwin, used); curwin->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP; if (curwin->w_topline != old_topline - || curwin->w_topfill != old_topfill) { + || curwin->w_topfill != old_topfill + || set_skipcol + || curwin->w_skipcol != 0) { curwin->w_valid &= ~(VALID_WROW|VALID_CROW); + if (set_skipcol) { + redraw_later(curwin, UPD_NOT_VALID); + } else { + reset_skipcol(curwin); + } } } else { validate_botline(curwin); @@ -1561,16 +1924,50 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) // The lines of the cursor line itself are always used. used = plines_win_nofill(curwin, cln, true); - // If the cursor is below botline, we will at least scroll by the height - // of the cursor line. Correct for empty lines, which are really part of - // botline. + int scrolled = 0; + int min_scrolled = 1; + // If the cursor is on or below botline, we will at least scroll by the + // height of the cursor line, which is "used". Correct for empty lines, + // which are really part of botline. if (cln >= curwin->w_botline) { scrolled = used; if (cln == curwin->w_botline) { scrolled -= curwin->w_empty_rows; } + min_scrolled = scrolled; + if (curwin->w_p_sms && curwin->w_p_wrap) { + // 'smoothscroll' and 'wrap' are set + if (cln > curwin->w_botline) { + // add screen lines below w_botline + for (linenr_T lnum = curwin->w_botline + 1; lnum <= cln; lnum++) { + min_scrolled += plines_win_nofill(curwin, lnum, true); + } + } + + // Calculate how many screen lines the current top line of window + // occupies. If it is occupying more than the entire window, we + // need to scroll the additional clipped lines to scroll past the + // top line before we can move on to the other lines. + int top_plines = plines_win_nofill(curwin, curwin->w_topline, false); + int skip_lines = 0; + int width1 = curwin->w_width_inner - curwin_col_off(); + int width2 = width1 + curwin_col_off2(); + // similar formula is used in curs_columns() + if (curwin->w_skipcol > width1) { + skip_lines += (curwin->w_skipcol - width1) / width2 + 1; + } else if (curwin->w_skipcol > 0) { + skip_lines = 1; + } + + top_plines -= skip_lines; + if (top_plines > curwin->w_height_inner) { + scrolled += (top_plines - curwin->w_height_inner); + min_scrolled += (top_plines - curwin->w_height_inner); + } + } } + lineoff_T boff; // Stop counting lines to scroll when // - hitting start of the file // - scrolled nothing or at least 'sj' lines @@ -1582,9 +1979,10 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) } loff.fill = 0; boff.fill = 0; - fill_below_window = win_get_fill(curwin, curwin->w_botline) - - curwin->w_filler_rows; + int fill_below_window = win_get_fill(curwin, curwin->w_botline) - curwin->w_filler_rows; + int extra = 0; + long so = get_scrolloff_value(curwin); while (loff.lnum > 1) { // Stop when scrolled nothing or at least "min_scroll", found "extra" // context for 'scrolloff' and counted all lines below the window. @@ -1670,13 +2068,27 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) if (line_count >= curwin->w_height_inner && line_count > min_scroll) { scroll_cursor_halfway(false, true); } else { - scrollup(line_count, true); + // With 'smoothscroll' scroll at least the height of the cursor line, + // unless it would move the cursor. + if (curwin->w_p_wrap && curwin->w_p_sms && line_count < min_scrolled + && (curwin->w_cursor.lnum < curwin->w_topline + || (curwin->w_virtcol - curwin->w_skipcol >= + curwin->w_width_inner - curwin_col_off()))) { + line_count = min_scrolled; + } + if (line_count > 0) { + if (scrolling_screenlines(true)) { + scrollup(scrolled, true); // TODO(vim): + } else { + scrollup(line_count, true); + } + } } // If topline didn't change we need to restore w_botline and w_empty_rows // (we changed them). // If topline did change, update_screen() will set botline. - if (curwin->w_topline == old_topline && set_topbot) { + if (curwin->w_topline == old_topline && curwin->w_skipcol == old_skipcol && set_topbot) { curwin->w_botline = old_botline; curwin->w_empty_rows = old_empty_rows; curwin->w_valid = old_valid; @@ -1691,27 +2103,65 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) /// void scroll_cursor_halfway(bool atend, bool prefer_above) { - int above = 0; - int topfill = 0; - int below = 0; - lineoff_T loff; - lineoff_T boff; linenr_T old_topline = curwin->w_topline; - - loff.lnum = boff.lnum = curwin->w_cursor.lnum; + lineoff_T loff = { .lnum = curwin->w_cursor.lnum }; + lineoff_T boff = { .lnum = curwin->w_cursor.lnum }; (void)hasFolding(loff.lnum, &loff.lnum, &boff.lnum); int used = plines_win_nofill(curwin, loff.lnum, true); loff.fill = 0; boff.fill = 0; linenr_T topline = loff.lnum; + colnr_T skipcol = 0; + bool set_skipcol = false; + + int half_height = 0; + bool smooth_scroll = false; + if (curwin->w_p_sms && curwin->w_p_wrap) { + // 'smoothscroll' and 'wrap' are set + smooth_scroll = true; + half_height = (curwin->w_height_inner - used) / 2; + used = 0; + } + int topfill = 0; while (topline > 1) { + // If using smoothscroll, we can precisely scroll to the + // exact point where the cursor is halfway down the screen. + if (smooth_scroll) { + topline_back_winheight(curwin, &loff, false); + if (loff.height == MAXCOL) { + break; + } else { + used += loff.height; + } + if (used > half_height) { + if (used - loff.height < half_height) { + int plines_offset = used - half_height; + loff.height -= plines_offset; + used = half_height; + + topline = loff.lnum; + topfill = loff.fill; + skipcol = skipcol_from_plines(curwin, plines_offset); + set_skipcol = true; + } + break; + } + topline = loff.lnum; + topfill = loff.fill; + continue; + } + + // If not using smoothscroll, we have to iteratively find how many + // lines to scroll down to roughly fit the cursor. // This may not be right in the middle if the lines' // physical height > 1 (e.g. 'wrap' is on). // Depending on "prefer_above" we add a line above or below first. // Loop twice to avoid duplicating code. bool done = false; + int above = 0; + int below = 0; for (int round = 1; round <= 2; round++) { if (prefer_above ? (round == 2 && below < above) @@ -1757,8 +2207,15 @@ void scroll_cursor_halfway(bool atend, bool prefer_above) } } - if (!hasFolding(topline, &curwin->w_topline, NULL)) { + if (!hasFolding(topline, &curwin->w_topline, NULL) + && (curwin->w_topline != topline || set_skipcol || curwin->w_skipcol != 0)) { curwin->w_topline = topline; + if (set_skipcol) { + curwin->w_skipcol = skipcol; + redraw_later(curwin, UPD_NOT_VALID); + } else { + reset_skipcol(curwin); + } } curwin->w_topfill = topfill; if (old_topline > curwin->w_topline + curwin->w_height_inner) { @@ -1809,6 +2266,16 @@ void cursor_correct(void) return; } + if (curwin->w_p_sms && !curwin->w_p_wrap) { + // 'smoothscroll is active + if (curwin->w_cline_height == curwin->w_height_inner) { + // The cursor line just fits in the window, don't scroll. + reset_skipcol(curwin); + return; + } + // TODO(vim): If the cursor line doesn't fit in the window then only adjust w_skipcol. + } + // Narrow down the area where the cursor can be put by taking lines from // the top and the bottom until: // - the desired context lines are found @@ -1861,9 +2328,9 @@ void cursor_correct(void) curwin->w_viewport_invalid = true; } -// move screen 'count' pages up or down and update screen +// Move screen "count" pages up or down and update screen. // -// return FAIL for failure, OK otherwise +// Return FAIL for failure, OK otherwise. int onepage(Direction dir, long count) { long n; |