diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2023-11-30 20:35:25 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2023-11-30 20:35:25 +0000 |
commit | 1b7b916b7631ddf73c38e3a0070d64e4636cb2f3 (patch) | |
tree | cd08258054db80bb9a11b1061bb091c70b76926a /src/nvim/move.c | |
parent | eaa89c11d0f8aefbb512de769c6c82f61a8baca3 (diff) | |
parent | 4a8bf24ac690004aedf5540fa440e788459e5e34 (diff) | |
download | rneovim-aucmd_textputpost.tar.gz rneovim-aucmd_textputpost.tar.bz2 rneovim-aucmd_textputpost.zip |
Merge remote-tracking branch 'upstream/master' into aucmd_textputpostaucmd_textputpost
Diffstat (limited to 'src/nvim/move.c')
-rw-r--r-- | src/nvim/move.c | 1049 |
1 files changed, 766 insertions, 283 deletions
diff --git a/src/nvim/move.c b/src/nvim/move.c index 3af26b910e..9ed3978490 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -1,6 +1,3 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - // move.c: Functions for moving the cursor and scrolling text. // // There are two ways to move the cursor: @@ -14,39 +11,42 @@ #include <limits.h> #include <stdbool.h> #include <stddef.h> +#include <stdint.h> -#include "nvim/ascii.h" +#include "nvim/ascii_defs.h" #include "nvim/buffer.h" -#include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval/typval.h" -#include "nvim/eval/typval_defs.h" #include "nvim/eval/window.h" #include "nvim/fold.h" +#include "nvim/func_attr.h" #include "nvim/getchar.h" #include "nvim/gettext.h" #include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" -#include "nvim/macros.h" +#include "nvim/macros_defs.h" +#include "nvim/mark.h" #include "nvim/mbyte.h" -#include "nvim/memline_defs.h" +#include "nvim/memline.h" #include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/option.h" +#include "nvim/option_vars.h" #include "nvim/plines.h" #include "nvim/popupmenu.h" -#include "nvim/pos.h" -#include "nvim/screen.h" +#include "nvim/pos_defs.h" #include "nvim/search.h" +#include "nvim/sign_defs.h" #include "nvim/strings.h" -#include "nvim/types.h" -#include "nvim/vim.h" +#include "nvim/types_defs.h" +#include "nvim/vim_defs.h" #include "nvim/window.h" +#include "nvim/winfloat.h" typedef struct { linenr_T lnum; // line number @@ -58,6 +58,37 @@ typedef struct { # include "move.c.generated.h" #endif +/// Get the number of screen lines skipped with "wp->w_skipcol". +int adjust_plines_for_skipcol(win_T *wp) +{ + if (wp->w_skipcol == 0) { + return 0; + } + + int width = wp->w_width_inner - win_col_off(wp); + int w2 = width + win_col_off2(wp); + if (wp->w_skipcol >= width && w2 > 0) { + return (wp->w_skipcol - width) / w2 + 1; + } + + return 0; +} + +/// 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); + } + 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) @@ -79,7 +110,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; @@ -104,18 +135,6 @@ static void comp_botline(win_T *wp) win_check_anchored_floats(wp); } -/// Redraw when w_cline_row changes and 'relativenumber' or 'cursorline' is set. -/// Also when concealing is on and 'concealcursor' is not active. -void redraw_for_cursorline(win_T *wp) - FUNC_ATTR_NONNULL_ALL -{ - if ((wp->w_valid & VALID_CROW) == 0 && !pum_visible() - && (wp->w_p_rnu || win_cursorline_standout(wp))) { - // win_line() will redraw the number column and cursorline only. - redraw_later(wp, UPD_VALID); - } -} - /// Redraw when w_virtcol changes and 'cursorcolumn' is set or 'cursorlineopt' /// contains "screenline" or when the "CurSearch" highlight is in use. /// Also when concealing is on and 'concealcursor' is active. @@ -123,7 +142,8 @@ static void redraw_for_cursorcolumn(win_T *wp) FUNC_ATTR_NONNULL_ALL { if ((wp->w_valid & VALID_VIRTCOL) == 0 && !pum_visible()) { - if (wp->w_p_cuc || ((HL_ATTR(HLF_LC) || win_hl_attr(wp, HLF_LC)) && using_hlsearch())) { + if (wp->w_p_cuc + || (win_hl_attr(wp, HLF_LC) != win_hl_attr(wp, HLF_L) && using_hlsearch())) { // When 'cursorcolumn' is set or "CurSearch" is in use // need to redraw with UPD_SOME_VALID. redraw_later(wp, UPD_SOME_VALID); @@ -140,15 +160,63 @@ 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. +/// 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) +{ + // There is no marker overlap when in showbreak mode, thus no need to + // account for it. See grid_put_linebuf(). + if (*get_showbreak_value(wp) != NUL) { + return 0; + } + + // Overlap when 'list' and 'listchars' "precedes" are set is 1. + if (wp->w_p_list && wp->w_p_lcs_chars.prec) { + return 1; + } + + 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->w_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) { - linenr_T old_topline; - int old_topfill; - bool check_topline = false; bool check_botline = false; - long *so_ptr = wp->w_p_so >= 0 ? &wp->w_p_so : &p_so; - long save_so = *so_ptr; + OptInt *so_ptr = wp->w_p_so >= 0 ? &wp->w_p_so : &p_so; + OptInt save_so = *so_ptr; // Cursor is updated instead when this is true for 'splitkeep'. if (skip_update_topline) { @@ -175,11 +243,11 @@ void update_topline(win_T *wp) *so_ptr = mouse_dragging - 1; } - old_topline = wp->w_topline; - old_topfill = wp->w_topfill; + linenr_T old_topline = wp->w_topline; + int old_topfill = wp->w_topfill; // If the buffer is empty, always set topline to 1. - if (buf_is_empty(curbuf)) { // special case - file is empty + if (buf_is_empty(wp->w_buffer)) { // special case - file is empty if (wp->w_topline != 1) { redraw_later(wp, UPD_NOT_VALID); } @@ -189,9 +257,10 @@ void update_topline(win_T *wp) wp->w_viewport_invalid = true; wp->w_scbind_pos = 1; } else { + 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. @@ -199,6 +268,16 @@ 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 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)); + if (wp->w_skipcol + overlap > vcol) { + check_topline = true; + } } } // Check if there are more filler lines than allowed. @@ -211,7 +290,7 @@ void update_topline(win_T *wp) if (halfheight < 2) { halfheight = 2; } - long n; + int64_t n; if (hasAnyFolding(wp)) { // Count the number of logical lines between the cursor and // topline + p_so (approximation of how much will be @@ -235,7 +314,7 @@ void update_topline(win_T *wp) // cursor in the middle of the window. Otherwise put the cursor // near the top of the window. if (n >= halfheight) { - scroll_cursor_halfway(false); + scroll_cursor_halfway(false, false); } else { scroll_cursor_top(scrolljump_value(), false); check_botline = true; @@ -261,9 +340,7 @@ void update_topline(win_T *wp) assert(wp->w_buffer != 0); if (wp->w_botline <= wp->w_buffer->b_ml.ml_line_count) { if (wp->w_cursor.lnum < wp->w_botline) { - if (((long)wp->w_cursor.lnum - >= (long)wp->w_botline - *so_ptr - || hasAnyFolding(wp))) { + if ((wp->w_cursor.lnum >= wp->w_botline - *so_ptr || hasAnyFolding(wp))) { lineoff_T loff; // Cursor is (a few lines) above botline, check if there are @@ -294,7 +371,7 @@ void update_topline(win_T *wp) } } if (check_botline) { - long line_count = 0; + int line_count = 0; if (hasAnyFolding(wp)) { // Count the number of logical lines between the cursor and // botline - p_so (approximation of how much will be @@ -309,12 +386,12 @@ void update_topline(win_T *wp) (void)hasFolding(lnum, &lnum, NULL); } } else { - line_count = wp->w_cursor.lnum - wp->w_botline + 1 + *so_ptr; + line_count = wp->w_cursor.lnum - wp->w_botline + 1 + (int)(*so_ptr); } if (line_count <= wp->w_height_inner + 1) { scroll_cursor_bot(scrolljump_value(), false); } else { - scroll_cursor_halfway(false); + scroll_cursor_halfway(false, false); } } } @@ -327,12 +404,15 @@ 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); + + // When 'smoothscroll' is not set, should reset w_skipcol. + if (!wp->w_p_sms) { + reset_skipcol(wp); + } else if (wp->w_skipcol != 0) { + redraw_later(wp, UPD_SOME_VALID); } + // May need to set w_skipcol when cursor in w_topline. if (wp->w_cursor.lnum == wp->w_topline) { validate_cursor(); @@ -347,16 +427,15 @@ void update_topline(win_T *wp) // When 'scrolljump' is negative use it as a percentage of the window height. static int scrolljump_value(void) { - long result = p_sj >= 0 ? p_sj : (curwin->w_height_inner * -p_sj) / 100; - assert(result <= INT_MAX); - return (int)result; + int result = p_sj >= 0 ? (int)p_sj : (curwin->w_height_inner * (int)(-p_sj)) / 100; + return result; } // Return true when there are not 'scrolloff' lines above the cursor for the // current window. static bool check_top_offset(void) { - long so = get_scrolloff_value(curwin); + int so = get_scrolloff_value(curwin); if (curwin->w_cursor.lnum < curwin->w_topline + so || hasAnyFolding(curwin)) { lineoff_T loff; @@ -405,7 +484,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 != @@ -454,18 +541,14 @@ void set_topline(win_T *wp, linenr_T lnum) redraw_later(wp, UPD_VALID); } -// Call this function when the length of the cursor line (in screen -// characters) has changed, and the change is before the cursor. -// Need to take care of w_botline separately! -void changed_cline_bef_curs(void) +/// Call this function when the length of the cursor line (in screen +/// characters) has changed, and the change is before the cursor. +/// If the line length changed the number of screen lines might change, +/// requiring updating w_topline. That may also invalidate w_crow. +/// Need to take care of w_botline separately! +void changed_cline_bef_curs(win_T *wp) { - curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL - |VALID_CHEIGHT|VALID_TOPLINE); -} - -void changed_cline_bef_curs_win(win_T *wp) -{ - wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL + wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL|VALID_CROW |VALID_CHEIGHT|VALID_TOPLINE); } @@ -484,6 +567,19 @@ void changed_line_abv_curs_win(win_T *wp) |VALID_CHEIGHT|VALID_TOPLINE); } +/// Display of line has changed for "buf", invalidate cursor position and +/// w_botline. +void changed_line_display_buf(buf_T *buf) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == buf) { + wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL + |VALID_CROW|VALID_CHEIGHT + |VALID_TOPLINE|VALID_BOTLINE|VALID_BOTLINE_AP); + } + } +} + // Make sure the value of curwin->w_botline is valid. void validate_botline(win_T *wp) { @@ -492,13 +588,8 @@ void validate_botline(win_T *wp) } } -// Mark curwin->w_botline as invalid (because of some change in the buffer). -void invalidate_botline(void) -{ - curwin->w_valid &= ~(VALID_BOTLINE|VALID_BOTLINE_AP); -} - -void invalidate_botline_win(win_T *wp) +// Mark wp->w_botline as invalid (because of some change in the buffer). +void invalidate_botline(win_T *wp) { wp->w_valid &= ~(VALID_BOTLINE|VALID_BOTLINE_AP); } @@ -512,14 +603,14 @@ void approximate_botline_win(win_T *wp) int cursor_valid(void) { check_cursor_moved(curwin); - return (curwin->w_valid & (VALID_WROW|VALID_WCOL)) == - (VALID_WROW|VALID_WCOL); + return (curwin->w_valid & (VALID_WROW|VALID_WCOL)) == (VALID_WROW|VALID_WCOL); } // Validate cursor position. Makes sure w_wrow and w_wcol are valid. // w_topline must be valid, you may need to call update_topline() first! void validate_cursor(void) { + check_cursor(); check_cursor_moved(curwin); if ((curwin->w_valid & (VALID_WCOL|VALID_WROW)) != (VALID_WCOL|VALID_WROW)) { curs_columns(curwin, true); @@ -555,7 +646,7 @@ static void curs_rows(win_T *wp) i--; // hold at inserted lines } } - if (valid && (lnum != wp->w_topline || !win_may_fill(wp))) { + if (valid && (lnum != wp->w_topline || (wp->w_skipcol == 0 && !win_may_fill(wp)))) { lnum = wp->w_lines[i].wl_lastlnum + 1; // Cursor inside folded lines, don't count this row if (lnum > wp->w_cursor.lnum) { @@ -565,7 +656,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; @@ -582,7 +673,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; @@ -629,7 +720,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; } @@ -667,11 +758,10 @@ void validate_cursor_col(void) // fold column and sign column (these don't move when scrolling horizontally). int win_col_off(win_T *wp) { - return ((wp->w_p_nu || wp->w_p_rnu || (*wp->w_p_stc != NUL)) ? - (number_width(wp) + (*wp->w_p_stc == NUL)) : 0) - + (cmdwin_type == 0 || wp != curwin ? 0 : 1) - + win_fdccol_count(wp) - + (win_signcol_count(wp) * win_signcol_width(wp)); + return ((wp->w_p_nu || wp->w_p_rnu || *wp->w_p_stc != NUL) + ? (number_width(wp) + (*wp->w_p_stc == NUL)) : 0) + + ((cmdwin_type == 0 || wp != curwin) ? 0 : 1) + + win_fdccol_count(wp) + (win_signcol_count(wp) * SIGN_WIDTH); } int curwin_col_off(void) @@ -680,12 +770,13 @@ 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) { - return number_width(wp) + 1; + if ((wp->w_p_nu || wp->w_p_rnu || *wp->w_p_stc != NUL) + && vim_strchr(p_cpo, CPO_NUMCOL) != NULL) { + return number_width(wp) + (*wp->w_p_stc == NUL); } return 0; } @@ -695,19 +786,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); @@ -737,8 +823,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; @@ -748,23 +837,29 @@ 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 - // mode, the 'showbreak' string isn't shown, backup to first - // column - char *const sbr = get_showbreak_value(wp); - if (*sbr && *get_cursor_pos_ptr() == NUL - && wp->w_wcol == vim_strsize(sbr)) { - wp->w_wcol = 0; - } } } else if (may_scroll && !wp->w_cline_folded) { @@ -776,18 +871,17 @@ 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 - assert(siso <= INT_MAX); - int off_left = startcol - wp->w_leftcol - (int)siso; - int off_right = - endcol - wp->w_leftcol - wp->w_width_inner + (int)siso + 1; + int siso = get_sidescrolloff_value(wp); + int off_left = startcol - wp->w_leftcol - siso; + int off_right = endcol - wp->w_leftcol - wp->w_width_inner + siso + 1; if (off_left < 0 || off_right > 0) { - int diff = (off_left < 0) ? -off_left: off_right; + int diff = (off_left < 0) ? -off_left : off_right; // 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); @@ -824,9 +918,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; + int 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) @@ -834,7 +928,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 @@ -843,7 +937,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 @@ -854,17 +948,17 @@ void curs_columns(win_T *wp, int may_scroll) plines--; if (plines > wp->w_wrow + so) { assert(so <= INT_MAX); - n = wp->w_wrow + (int)so; + n = wp->w_wrow + so; } 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 { @@ -874,51 +968,62 @@ 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; + if (n > 0) { + curwin->w_skipcol = width1 + (n - 1) * width2; + } else { + curwin->w_skipcol = 0; + } } 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 + 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; } @@ -935,64 +1040,67 @@ void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp, int *ccolp, { colnr_T scol = 0, ccol = 0, ecol = 0; int row = 0; - int rowoff = 0; colnr_T coloff = 0; bool visible_row = false; bool is_folded = false; - if (pos->lnum >= wp->w_topline && pos->lnum <= wp->w_botline) { - linenr_T lnum = pos->lnum; + linenr_T lnum = pos->lnum; + if (lnum >= wp->w_topline && lnum <= wp->w_botline) { is_folded = hasFoldingWin(wp, lnum, &lnum, NULL, true, NULL); - row = plines_m_win(wp, wp->w_topline, lnum - 1) + 1; + row = plines_m_win(wp, wp->w_topline, lnum - 1, false); + // "row" should be the screen line where line "lnum" begins, which can + // be negative if "lnum" is "w_topline" and "w_skipcol" is non-zero. + row -= adjust_plines_for_skipcol(wp); // Add filler lines above this buffer line. - row += win_get_fill(wp, lnum); + row += lnum == wp->w_topline ? wp->w_topfill : win_get_fill(wp, lnum); visible_row = true; - } else if (!local || pos->lnum < wp->w_topline) { + } else if (!local || lnum < wp->w_topline) { row = 0; } else { - row = wp->w_height_inner; + row = wp->w_height_inner - 1; } - bool existing_row = (pos->lnum > 0 - && pos->lnum <= wp->w_buffer->b_ml.ml_line_count); + bool existing_row = (lnum > 0 && lnum <= wp->w_buffer->b_ml.ml_line_count); if ((local || visible_row) && existing_row) { const colnr_T off = win_col_off(wp); if (is_folded) { - row += local ? 0 : wp->w_winrow + wp->w_winrow_off; + row += (local ? 0 : wp->w_winrow + wp->w_winrow_off) + 1; coloff = (local ? 0 : wp->w_wincol + wp->w_wincol_off) + 1 + off; } else { + assert(lnum == pos->lnum); getvcol(wp, pos, &scol, &ccol, &ecol); // 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; + int rowoff = visible_row ? ((col - wp->w_width_inner) / width + 1) : 0; col -= rowoff * width; + row += rowoff; } 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 >= 0 && row < 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; + row += (local ? 0 : wp->w_winrow + wp->w_winrow_off) + 1; } else { // character is left, right or below of the window scol = ccol = ecol = 0; if (local) { coloff = col < 0 ? -1 : wp->w_width_inner + 1; } else { - row = rowoff = 0; + row = 0; } } } } - *rowp = row + rowoff; + *rowp = row; *scolp = scol + coloff; *ccolp = ccol + coloff; *ecolp = ecol + coloff; @@ -1010,8 +1118,8 @@ void f_screenpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } pos_T pos = { - .lnum = (linenr_T)tv_get_number(&argvars[1]), - .col = (colnr_T)tv_get_number(&argvars[2]) - 1, + .lnum = (linenr_T)tv_get_number(&argvars[1]), + .col = (colnr_T)tv_get_number(&argvars[2]) - 1, .coladd = 0 }; if (pos.lnum > wp->w_buffer->b_ml.ml_line_count) { @@ -1028,6 +1136,25 @@ void f_screenpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) tv_dict_add_nr(dict, S_LEN("endcol"), ecol); } +/// Convert a virtual (screen) column to a character column. The first column +/// is one. For a multibyte character, the column number of the first byte is +/// returned. +static int virtcol2col(win_T *wp, linenr_T lnum, int vcol) +{ + int offset = vcol2col(wp, lnum, vcol - 1, NULL); + char *line = ml_get_buf(wp->w_buffer, lnum); + char *p = line + offset; + + if (*p == NUL) { + if (p == line) { // empty line + return 0; + } + // Move to the first byte of the last char. + MB_PTR_BACK(line, p); + } + return (int)(p - line + 1); +} + /// "virtcol2col({winid}, {lnum}, {col})" function void f_virtcol2col(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -1055,46 +1182,83 @@ void f_virtcol2col(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } - rettv->vval.v_number = vcol2col(wp, lnum, screencol); + rettv->vval.v_number = virtcol2col(wp, lnum, screencol); } /// Scroll the current window down by "line_count" logical lines. "CTRL-Y" /// /// @param line_count number of lines to scroll /// @param byfold if true, count a closed fold as one line -bool scrolldown(long line_count, int byfold) +bool scrolldown(linenr_T 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 (int 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 = win_linetabsize(curwin, curwin->w_topline, + ml_get(curwin->w_topline), 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 - invalidate_botline(); + invalidate_botline(curwin); } curwin->w_wrow += done; // keep w_wrow updated curwin->w_cline_row += done; // keep w_cline_row updated @@ -1135,6 +1299,27 @@ bool scrolldown(long line_count, int byfold) foldAdjustCursor(); coladvance(curwin->w_curswant); } + + if (curwin->w_cursor.lnum == curwin->w_topline && do_sms) { + int so = get_scrolloff_value(curwin); + colnr_T scrolloff_cols = so == 0 ? 0 : width1 + (so - 1) * width2; + + // make sure the cursor is in the visible text + validate_virtcol(); + colnr_T 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; + } + 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; } @@ -1142,35 +1327,76 @@ bool scrolldown(long line_count, int byfold) /// /// @param line_count number of lines to scroll /// @param byfold if true, count a closed fold as one line -bool scrollup(long line_count, int byfold) +bool scrollup(linenr_T 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 ((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--) { + if (do_sms || (byfold && hasAnyFolding(curwin)) || win_may_fill(curwin)) { + int width1 = curwin->w_width_inner - curwin_col_off(); + int width2 = width1 + curwin_col_off2(); + int size = 0; + const colnr_T prev_skipcol = curwin->w_skipcol; + + if (do_sms) { + size = linetabsize(curwin, curwin->w_topline); + } + + // 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 (int 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 (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 (prev_skipcol > 0 || curwin->w_skipcol > 0) { + // need to redraw more, because wl_size of the (new) topline may + // now be invalid + redraw_later(curwin, UPD_NOT_VALID); + } } else { - curwin->w_topline += (linenr_T)line_count; - curwin->w_botline += (linenr_T)line_count; // approximate w_botline + curwin->w_topline += line_count; + curwin->w_botline += line_count; // approximate w_botline } if (curwin->w_topline > curbuf->b_ml.ml_line_count) { @@ -1195,12 +1421,116 @@ 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; + int so = get_scrolloff_value(curwin); + colnr_T 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 overlap = scrolloff_cols != 0 ? 0 : sms_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 + overlap + scrolloff_cols) { + colnr_T col = curwin->w_virtcol; + + if (col < width1) { + col += width1; + } + while (col < curwin->w_skipcol + 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(); + int so = get_scrolloff_value(curwin); + colnr_T 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(); + int overlap = sms_marker_overlap(curwin, curwin_col_off() - curwin_col_off2()); + while (curwin->w_skipcol > 0 + && curwin->w_virtcol < curwin->w_skipcol + overlap + scrolloff_cols) { + // scroll a screen line down + if (curwin->w_skipcol >= width1 + width2) { + curwin->w_skipcol -= width2; + } else { + curwin->w_skipcol -= width1; + } + scrolled = true; + } + if (scrolled) { + validate_virtcol(); + redraw_later(curwin, UPD_NOT_VALID); + return; // don't scroll in the other direction now + } + colnr_T 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; + } + 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 @@ -1317,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 @@ -1332,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". @@ -1389,13 +1725,10 @@ 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); + int off = get_scrolloff_value(curwin); if (mouse_dragging > 0) { off = mouse_dragging - 1; @@ -1407,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++; @@ -1419,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. @@ -1432,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)) { @@ -1444,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; @@ -1464,10 +1800,10 @@ void scroll_cursor_top(int min_scroll, int always) // This makes sure we get the same position when using "k" and "j" // in a small window. if (used > curwin->w_height_inner) { - scroll_cursor_halfway(false); + 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; } @@ -1482,7 +1818,18 @@ void scroll_cursor_top(int min_scroll, int always) } } check_topfill(curwin, false); + if (curwin->w_topline != old_topline) { + reset_skipcol(curwin); + } else if (curwin->w_topline == curwin->w_cursor.lnum) { + validate_virtcol(); + if (curwin->w_skipcol >= curwin->w_virtcol) { + // TODO(vim): if the line doesn't fit may optimize w_skipcol instead + // of making it zero + 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,31 +1866,44 @@ void set_empty_rows(win_T *wp, int used) /// This is messy stuff!!! 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 + int do_sms = curwin->w_p_wrap && curwin->w_p_sms; if (set_topbot) { - used = 0; + bool set_skipcol = false; + + int used = 0; curwin->w_botline = cln + 1; loff.fill = 0; for (curwin->w_topline = curwin->w_botline; 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 (do_sms) { + // '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; @@ -1552,26 +1912,59 @@ 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); } // The lines of the cursor line itself are always used. - used = plines_win_nofill(curwin, cln, true); + int 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; + // 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; } + if (do_sms) { + // 'smoothscroll' and 'wrap' are set. + // 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(); + if (width1 > 0) { + 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); + } + } + } } + lineoff_T boff; // Stop counting lines to scroll when // - hitting start of the file // - scrolled nothing or at least 'sj' lines @@ -1583,9 +1976,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; + int 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. @@ -1669,15 +2063,19 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) // Scroll up if the cursor is off the bottom of the screen a bit. // Otherwise put it at 1/2 of the screen. if (line_count >= curwin->w_height_inner && line_count > min_scroll) { - scroll_cursor_halfway(false); - } else { - scrollup(line_count, true); + scroll_cursor_halfway(false, true); + } else if (line_count > 0) { + if (do_sms) { + 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; @@ -1690,55 +2088,121 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) /// /// @param atend if true, also put the cursor halfway to the end of the file. /// -void scroll_cursor_halfway(int atend) +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; - while (topline > 1) { - if (below <= above) { // add a line below the cursor first - if (boff.lnum < curbuf->b_ml.ml_line_count) { - botline_forw(curwin, &boff); - used += boff.height; - if (used > curwin->w_height_inner) { - break; - } - below += boff.height; - } else { - below++; // count a "~" line - if (atend) { - used++; - } - } + colnr_T skipcol = 0; + + int want_height; + bool do_sms = curwin->w_p_wrap && curwin->w_p_sms; + if (do_sms) { + // 'smoothscroll' and 'wrap' are set + if (atend) { + want_height = (curwin->w_height_inner - used) / 2; + used = 0; + } else { + want_height = curwin->w_height_inner; } + } - if (below > above) { // add a line above the cursor - topline_back(curwin, &loff); + 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 (do_sms) { + topline_back_winheight(curwin, &loff, false); if (loff.height == MAXCOL) { - used = MAXCOL; - } else { - used += loff.height; + break; } - if (used > curwin->w_height_inner) { + used += loff.height; + if (!atend && boff.lnum < curbuf->b_ml.ml_line_count) { + botline_forw(curwin, &boff); + used += boff.height; + } + if (used > want_height) { + if (used - loff.height < want_height) { + topline = loff.lnum; + topfill = loff.fill; + skipcol = skipcol_from_plines(curwin, used - want_height); + } break; } - above += loff.height; 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) + : (round == 1 && below <= above)) { + // add a line below the cursor + if (boff.lnum < curbuf->b_ml.ml_line_count) { + botline_forw(curwin, &boff); + used += boff.height; + if (used > curwin->w_height_inner) { + done = true; + break; + } + below += boff.height; + } else { + below++; // count a "~" line + if (atend) { + used++; + } + } + } + + if (prefer_above + ? (round == 1 && below >= above) + : (round == 1 && below > above)) { + // add a line above the cursor + topline_back(curwin, &loff); + if (loff.height == MAXCOL) { + used = MAXCOL; + } else { + used += loff.height; + } + if (used > curwin->w_height_inner) { + done = true; + break; + } + above += loff.height; + topline = loff.lnum; + topfill = loff.fill; + } + } + if (done) { + break; } } - if (!hasFolding(topline, &curwin->w_topline, NULL)) { + + if (!hasFolding(topline, &curwin->w_topline, NULL) + && (curwin->w_topline != topline || skipcol != 0 || curwin->w_skipcol != 0)) { curwin->w_topline = topline; + if (skipcol != 0) { + curwin->w_skipcol = skipcol; + redraw_later(curwin, UPD_NOT_VALID); + } else if (do_sms) { + reset_skipcol(curwin); + } } curwin->w_topfill = topfill; if (old_topline > curwin->w_topline + curwin->w_height_inner) { @@ -1757,8 +2221,8 @@ void cursor_correct(void) { // How many lines we would like to have above/below the cursor depends on // whether the first/last line of the file is on screen. - int above_wanted = (int)get_scrolloff_value(curwin); - int below_wanted = (int)get_scrolloff_value(curwin); + int above_wanted = get_scrolloff_value(curwin); + int below_wanted = get_scrolloff_value(curwin); if (mouse_dragging > 0) { above_wanted = mouse_dragging - 1; below_wanted = mouse_dragging - 1; @@ -1789,6 +2253,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 @@ -1841,16 +2315,16 @@ void cursor_correct(void) curwin->w_viewport_invalid = true; } -// move screen 'count' pages up or down and update screen -// -// return FAIL for failure, OK otherwise -int onepage(Direction dir, long count) +/// Move screen "count" pages up ("dir" is BACKWARD) or down ("dir" is FORWARD) +/// and update the screen. +/// +/// @return FAIL for failure, OK otherwise. +int onepage(Direction dir, int count) { - long n; int retval = OK; lineoff_T loff; linenr_T old_topline = curwin->w_topline; - long so = get_scrolloff_value(curwin); + int so = get_scrolloff_value(curwin); if (curbuf->b_ml.ml_line_count == 1) { // nothing to do beep_flush(); @@ -1945,7 +2419,7 @@ int onepage(Direction dir, long count) // Find the line just above the new topline to get the right line // at the bottom of the window. - n = 0; + int n = 0; while (n <= curwin->w_height_inner && loff.lnum >= 1) { topline_back(curwin, &loff); if (loff.height == MAXCOL) { @@ -1981,11 +2455,11 @@ int onepage(Direction dir, long count) if (curwin->w_topfill == loff.fill) { curwin->w_topline--; curwin->w_topfill = 0; + curwin->w_valid &= ~(VALID_WROW|VALID_CROW); } comp_botline(curwin); curwin->w_cursor.lnum = curwin->w_botline - 1; - curwin->w_valid &= - ~(VALID_WCOL | VALID_CHEIGHT | VALID_WROW | VALID_CROW); + curwin->w_valid &= ~(VALID_WCOL|VALID_CHEIGHT|VALID_WROW|VALID_CROW); } else { curwin->w_topline = loff.lnum; curwin->w_topfill = loff.fill; @@ -2087,7 +2561,7 @@ static void get_scroll_overlap(lineoff_T *lp, int dir) // Scroll 'scroll' lines up or down. void halfpage(bool flag, linenr_T Prenum) { - long scrolled = 0; + int scrolled = 0; int i; if (Prenum) { @@ -2157,7 +2631,7 @@ void halfpage(bool flag, linenr_T Prenum) } else { curwin->w_cursor.lnum += n; } - check_cursor_lnum(); + check_cursor_lnum(curwin); } } else { // scroll the text down @@ -2210,9 +2684,18 @@ void halfpage(bool flag, linenr_T Prenum) void do_check_cursorbind(void) { - linenr_T line = curwin->w_cursor.lnum; - colnr_T col = curwin->w_cursor.col; - colnr_T coladd = curwin->w_cursor.coladd; + static win_T *prev_curwin = NULL; + static pos_T prev_cursor = { 0, 0, 0 }; + + if (curwin == prev_curwin && equalpos(curwin->w_cursor, prev_cursor)) { + return; + } + prev_curwin = curwin; + prev_cursor = curwin->w_cursor; + + linenr_T line = curwin->w_cursor.lnum; + colnr_T col = curwin->w_cursor.col; + colnr_T coladd = curwin->w_cursor.coladd; colnr_T curswant = curwin->w_curswant; int set_curswant = curwin->w_set_curswant; win_T *old_curwin = curwin; @@ -2221,11 +2704,11 @@ void do_check_cursorbind(void) int old_VIsual_active = VIsual_active; // loop through the cursorbound windows - VIsual_select = VIsual_active = 0; + VIsual_select = VIsual_active = false; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { curwin = wp; curbuf = curwin->w_buffer; - // skip original window and windows with 'noscrollbind' + // skip original window and windows with 'nocursorbind' if (curwin != old_curwin && curwin->w_p_crb) { if (curwin->w_p_diff) { curwin->w_cursor.lnum = |