neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

move.c (83661B)


      1 // move.c: Functions for moving the cursor and scrolling text.
      2 //
      3 // There are two ways to move the cursor:
      4 // 1. Move the cursor directly, the text is scrolled to keep the cursor in the
      5 //    window.
      6 // 2. Scroll the text, the cursor is moved into the text visible in the
      7 //    window.
      8 // The 'scrolloff' option makes this a bit complicated.
      9 
     10 #include <assert.h>
     11 #include <limits.h>
     12 #include <stdbool.h>
     13 #include <stdint.h>
     14 #include <stdlib.h>
     15 
     16 #include "nvim/ascii_defs.h"
     17 #include "nvim/buffer.h"
     18 #include "nvim/buffer_defs.h"
     19 #include "nvim/cursor.h"
     20 #include "nvim/decoration.h"
     21 #include "nvim/diff.h"
     22 #include "nvim/drawscreen.h"
     23 #include "nvim/edit.h"
     24 #include "nvim/errors.h"
     25 #include "nvim/eval/typval.h"
     26 #include "nvim/eval/window.h"
     27 #include "nvim/fold.h"
     28 #include "nvim/getchar.h"
     29 #include "nvim/gettext_defs.h"
     30 #include "nvim/globals.h"
     31 #include "nvim/grid.h"
     32 #include "nvim/grid_defs.h"
     33 #include "nvim/macros_defs.h"
     34 #include "nvim/mark_defs.h"
     35 #include "nvim/mbyte.h"
     36 #include "nvim/memline.h"
     37 #include "nvim/message.h"
     38 #include "nvim/mouse.h"
     39 #include "nvim/move.h"
     40 #include "nvim/normal.h"
     41 #include "nvim/normal_defs.h"
     42 #include "nvim/option.h"
     43 #include "nvim/option_vars.h"
     44 #include "nvim/plines.h"
     45 #include "nvim/popupmenu.h"
     46 #include "nvim/pos_defs.h"
     47 #include "nvim/strings.h"
     48 #include "nvim/types_defs.h"
     49 #include "nvim/vim_defs.h"
     50 #include "nvim/window.h"
     51 #include "nvim/winfloat.h"
     52 
     53 typedef struct {
     54  linenr_T lnum;                // line number
     55  int fill;                     // filler lines
     56  int height;                   // height of added line
     57 } lineoff_T;
     58 
     59 #include "move.c.generated.h"
     60 
     61 /// Get the number of screen lines skipped with "wp->w_skipcol".
     62 static int adjust_plines_for_skipcol(win_T *wp)
     63 {
     64  if (wp->w_skipcol == 0) {
     65    return 0;
     66  }
     67 
     68  int width = wp->w_view_width - win_col_off(wp);
     69  int w2 = width + win_col_off2(wp);
     70  if (wp->w_skipcol >= width && w2 > 0) {
     71    return (wp->w_skipcol - width) / w2 + 1;
     72  }
     73 
     74  return 0;
     75 }
     76 
     77 /// Return how many lines "lnum" will take on the screen, taking into account
     78 /// whether it is the first line, whether w_skipcol is non-zero and limiting to
     79 /// the window height.
     80 int plines_correct_topline(win_T *wp, linenr_T lnum, linenr_T *nextp, bool limit_winheight,
     81                           bool *foldedp)
     82 {
     83  int n = plines_win_full(wp, lnum, nextp, foldedp, true, false);
     84  if (lnum == wp->w_topline) {
     85    n -= adjust_plines_for_skipcol(wp);
     86  }
     87  if (limit_winheight && n > wp->w_view_height) {
     88    return wp->w_view_height;
     89  }
     90  return n;
     91 }
     92 
     93 // Compute wp->w_botline for the current wp->w_topline.  Can be called after
     94 // wp->w_topline changed.
     95 static void comp_botline(win_T *wp)
     96 {
     97  linenr_T lnum;
     98  int done;
     99 
    100  // If w_cline_row is valid, start there.
    101  // Otherwise have to start at w_topline.
    102  check_cursor_moved(wp);
    103  if (wp->w_valid & VALID_CROW) {
    104    lnum = wp->w_cursor.lnum;
    105    done = wp->w_cline_row;
    106  } else {
    107    lnum = wp->w_topline;
    108    done = 0;
    109  }
    110 
    111  for (; lnum <= wp->w_buffer->b_ml.ml_line_count; lnum++) {
    112    linenr_T last = lnum;
    113    bool folded;
    114    int n = plines_correct_topline(wp, lnum, &last, true, &folded);
    115    if (lnum <= wp->w_cursor.lnum && last >= wp->w_cursor.lnum) {
    116      wp->w_cline_row = done;
    117      wp->w_cline_height = n;
    118      wp->w_cline_folded = folded;
    119      redraw_for_cursorline(wp);
    120      wp->w_valid |= (VALID_CROW|VALID_CHEIGHT);
    121    }
    122    if (done + n > wp->w_view_height) {
    123      break;
    124    }
    125    done += n;
    126    lnum = last;
    127  }
    128 
    129  // wp->w_botline is the line that is just below the window
    130  wp->w_botline = lnum;
    131  wp->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP;
    132  wp->w_viewport_invalid = true;
    133 
    134  set_empty_rows(wp, done);
    135 
    136  win_check_anchored_floats(wp);
    137 }
    138 
    139 /// Redraw when w_cline_row changes and 'relativenumber' or 'cursorline' is set.
    140 /// Also when concealing is on and 'concealcursor' is not active.
    141 static void redraw_for_cursorline(win_T *wp)
    142  FUNC_ATTR_NONNULL_ALL
    143 {
    144  if ((wp->w_valid & VALID_CROW) == 0 && !pum_visible()
    145      && (wp->w_p_rnu || win_cursorline_standout(wp))) {
    146    // win_line() will redraw the number column and cursorline only.
    147    redraw_later(wp, UPD_VALID);
    148  }
    149 }
    150 
    151 /// Redraw when 'concealcursor' is active, or when w_virtcol changes and:
    152 /// - 'cursorcolumn' is set, or
    153 /// - 'cursorlineopt' contains "screenline", or
    154 /// - Visual mode is active.
    155 static void redraw_for_cursorcolumn(win_T *wp)
    156  FUNC_ATTR_NONNULL_ALL
    157 {
    158  // If the cursor moves horizontally when 'concealcursor' is active, then the
    159  // current line needs to be redrawn to calculate the correct cursor position.
    160  if (wp == curwin && wp->w_p_cole > 0 && conceal_cursor_line(wp)) {
    161    redrawWinline(wp, wp->w_cursor.lnum);
    162  }
    163 
    164  if ((wp->w_valid & VALID_VIRTCOL) || pum_visible()) {
    165    return;
    166  }
    167 
    168  if (wp->w_p_cuc) {
    169    // When 'cursorcolumn' is set need to redraw with UPD_SOME_VALID.
    170    redraw_later(wp, UPD_SOME_VALID);
    171  } else if (wp->w_p_cul && (wp->w_p_culopt_flags & kOptCuloptFlagScreenline)) {
    172    // When 'cursorlineopt' contains "screenline" need to redraw with UPD_VALID.
    173    redraw_later(wp, UPD_VALID);
    174  }
    175 
    176  // When current buffer's cursor moves in Visual mode, redraw it with UPD_INVERTED.
    177  if (VIsual_active && wp->w_buffer == curbuf) {
    178    redraw_buf_later(curbuf, UPD_INVERTED);
    179  }
    180 }
    181 
    182 /// Set wp->w_virtcol to a value ("vcol") that is already valid.
    183 /// Handles redrawing if wp->w_virtcol was previously invalid.
    184 void set_valid_virtcol(win_T *wp, colnr_T vcol)
    185 {
    186  wp->w_virtcol = vcol;
    187  redraw_for_cursorcolumn(wp);
    188  wp->w_valid |= VALID_VIRTCOL;
    189 }
    190 
    191 /// Calculates how much the 'listchars' "precedes" or 'smoothscroll' "<<<"
    192 /// marker overlaps with buffer text for window "wp".
    193 /// Parameter "extra2" should be the padding on the 2nd line, not the first
    194 /// line. When "extra2" is -1 calculate the padding.
    195 /// Returns the number of columns of overlap with buffer text, excluding the
    196 /// extra padding on the ledge.
    197 int sms_marker_overlap(win_T *wp, int extra2)
    198 {
    199  if (extra2 == -1) {
    200    extra2 = win_col_off(wp) - win_col_off2(wp);
    201  }
    202  // There is no marker overlap when in showbreak mode, thus no need to
    203  // account for it.  See wlv_put_linebuf().
    204  if (*get_showbreak_value(wp) != NUL) {
    205    return 0;
    206  }
    207 
    208  // Overlap when 'list' and 'listchars' "precedes" are set is 1.
    209  if (wp->w_p_list && wp->w_p_lcs_chars.prec) {
    210    return 1;
    211  }
    212 
    213  return extra2 > 3 ? 0 : 3 - extra2;
    214 }
    215 
    216 /// Calculates the skipcol offset for window "wp" given how many
    217 /// physical lines we want to scroll down.
    218 static int skipcol_from_plines(win_T *wp, int plines_off)
    219 {
    220  int width1 = wp->w_view_width - win_col_off(wp);
    221 
    222  int skipcol = 0;
    223  if (plines_off > 0) {
    224    skipcol += width1;
    225  }
    226  if (plines_off > 1) {
    227    skipcol += (width1 + win_col_off2(wp)) * (plines_off - 1);
    228  }
    229  return skipcol;
    230 }
    231 
    232 /// Set wp->w_skipcol to zero and redraw later if needed.
    233 static void reset_skipcol(win_T *wp)
    234 {
    235  if (wp->w_skipcol == 0) {
    236    return;
    237  }
    238 
    239  wp->w_skipcol = 0;
    240 
    241  // Should use the least expensive way that displays all that changed.
    242  // UPD_NOT_VALID is too expensive, UPD_REDRAW_TOP does not redraw
    243  // enough when the top line gets another screen line.
    244  redraw_later(wp, UPD_SOME_VALID);
    245 }
    246 
    247 // Update wp->w_topline to move the cursor onto the screen.
    248 void update_topline(win_T *wp)
    249 {
    250  bool check_botline = false;
    251  OptInt *so_ptr = wp->w_p_so >= 0 ? &wp->w_p_so : &p_so;
    252  OptInt save_so = *so_ptr;
    253 
    254  // Cursor is updated instead when this is true for 'splitkeep'.
    255  if (skip_update_topline) {
    256    return;
    257  }
    258 
    259  // If there is no valid screen and when the window height is zero just use
    260  // the cursor line.
    261  if (!default_grid.chars || wp->w_view_height == 0) {
    262    check_cursor_lnum(wp);
    263    wp->w_topline = wp->w_cursor.lnum;
    264    wp->w_botline = wp->w_topline;
    265    wp->w_viewport_invalid = true;
    266    wp->w_scbind_pos = 1;
    267    return;
    268  }
    269 
    270  check_cursor_moved(wp);
    271  if (wp->w_valid & VALID_TOPLINE) {
    272    return;
    273  }
    274 
    275  // When dragging with the mouse, don't scroll that quickly
    276  if (mouse_dragging > 0) {
    277    *so_ptr = mouse_dragging - 1;
    278  }
    279 
    280  linenr_T old_topline = wp->w_topline;
    281  int old_topfill = wp->w_topfill;
    282 
    283  // If the buffer is empty, always set topline to 1.
    284  if (buf_is_empty(wp->w_buffer)) {  // special case - file is empty
    285    if (wp->w_topline != 1) {
    286      redraw_later(wp, UPD_NOT_VALID);
    287    }
    288    wp->w_topline = 1;
    289    wp->w_botline = 2;
    290    wp->w_skipcol = 0;
    291    wp->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP;
    292    wp->w_viewport_invalid = true;
    293    wp->w_scbind_pos = 1;
    294  } else {
    295    bool check_topline = false;
    296    // If the cursor is above or near the top of the window, scroll the window
    297    // to show the line the cursor is in, with 'scrolloff' context.
    298    if (wp->w_topline > 1 || wp->w_skipcol > 0) {
    299      // If the cursor is above topline, scrolling is always needed.
    300      // If the cursor is far below topline and there is no folding,
    301      // scrolling down is never needed.
    302      if (wp->w_cursor.lnum < wp->w_topline) {
    303        check_topline = true;
    304      } else if (check_top_offset(wp)) {
    305        check_topline = true;
    306      } else if (wp->w_skipcol > 0 && wp->w_cursor.lnum == wp->w_topline) {
    307        colnr_T vcol;
    308 
    309        // Check that the cursor position is visible.  Add columns for
    310        // the marker displayed in the top-left if needed.
    311        getvvcol(wp, &wp->w_cursor, &vcol, NULL, NULL);
    312        int overlap = sms_marker_overlap(wp, -1);
    313        if (wp->w_skipcol + overlap > vcol) {
    314          check_topline = true;
    315        }
    316      }
    317    }
    318    // Check if there are more filler lines than allowed.
    319    if (!check_topline && wp->w_topfill > win_get_fill(wp, wp->w_topline)) {
    320      check_topline = true;
    321    }
    322 
    323    if (check_topline) {
    324      int halfheight = wp->w_view_height / 2 - 1;
    325      if (halfheight < 2) {
    326        halfheight = 2;
    327      }
    328      int64_t n;
    329      if (win_lines_concealed(wp)) {
    330        // Count the number of logical lines between the cursor and
    331        // topline + p_so (approximation of how much will be
    332        // scrolled).
    333        n = 0;
    334        for (linenr_T lnum = wp->w_cursor.lnum; lnum < wp->w_topline + *so_ptr; lnum++) {
    335          // stop at end of file or when we know we are far off
    336          assert(wp->w_buffer != 0);
    337          if (lnum >= wp->w_buffer->b_ml.ml_line_count
    338              || (n += !decor_conceal_line(wp, lnum, false)) >= halfheight) {
    339            break;
    340          }
    341          hasFolding(wp, lnum, NULL, &lnum);
    342        }
    343      } else {
    344        n = wp->w_topline + *so_ptr - wp->w_cursor.lnum;
    345      }
    346 
    347      // If we weren't very close to begin with, we scroll to put the
    348      // cursor in the middle of the window.  Otherwise put the cursor
    349      // near the top of the window.
    350      if (n >= halfheight) {
    351        scroll_cursor_halfway(wp, false, false);
    352      } else {
    353        scroll_cursor_top(wp, scrolljump_value(wp), false);
    354        check_botline = true;
    355      }
    356    } else {
    357      // Make sure topline is the first line of a fold.
    358      hasFolding(wp, wp->w_topline, &wp->w_topline, NULL);
    359      check_botline = true;
    360    }
    361  }
    362 
    363  // If the cursor is below the bottom of the window, scroll the window
    364  // to put the cursor on the window.
    365  // When w_botline is invalid, recompute it first, to avoid a redraw later.
    366  // If w_botline was approximated, we might need a redraw later in a few
    367  // cases, but we don't want to spend (a lot of) time recomputing w_botline
    368  // for every small change.
    369  if (check_botline) {
    370    if (!(wp->w_valid & VALID_BOTLINE_AP)) {
    371      validate_botline_win(wp);
    372    }
    373 
    374    assert(wp->w_buffer != 0);
    375    if (wp->w_botline <= wp->w_buffer->b_ml.ml_line_count) {
    376      if (wp->w_cursor.lnum < wp->w_botline) {
    377        if ((wp->w_cursor.lnum >= wp->w_botline - *so_ptr || win_lines_concealed(wp))) {
    378          lineoff_T loff;
    379 
    380          // Cursor is (a few lines) above botline, check if there are
    381          // 'scrolloff' window lines below the cursor.  If not, need to
    382          // scroll.
    383          int n = wp->w_empty_rows;
    384          loff.lnum = wp->w_cursor.lnum;
    385          // In a fold go to its last line.
    386          hasFolding(wp, loff.lnum, NULL, &loff.lnum);
    387          loff.fill = 0;
    388          n += wp->w_filler_rows;
    389          loff.height = 0;
    390          while (loff.lnum < wp->w_botline
    391                 && (loff.lnum + 1 < wp->w_botline || loff.fill == 0)) {
    392            n += loff.height;
    393            if (n >= *so_ptr) {
    394              break;
    395            }
    396            botline_forw(wp, &loff);
    397          }
    398          if (n >= *so_ptr) {
    399            // sufficient context, no need to scroll
    400            check_botline = false;
    401          }
    402        } else {
    403          // sufficient context, no need to scroll
    404          check_botline = false;
    405        }
    406      }
    407      if (check_botline) {
    408        int n = 0;
    409        if (win_lines_concealed(wp)) {
    410          // Count the number of logical lines between the cursor and
    411          // botline - p_so (approximation of how much will be
    412          // scrolled).
    413          for (linenr_T lnum = wp->w_cursor.lnum; lnum >= wp->w_botline - *so_ptr; lnum--) {
    414            // stop at end of file or when we know we are far off
    415            if (lnum <= 0
    416                || (n += !decor_conceal_line(wp, lnum - 1, false)) > wp->w_view_height + 1) {
    417              break;
    418            }
    419            hasFolding(wp, lnum, &lnum, NULL);
    420          }
    421        } else {
    422          n = wp->w_cursor.lnum - wp->w_botline + 1 + (int)(*so_ptr);
    423        }
    424        if (n <= wp->w_view_height + 1) {
    425          scroll_cursor_bot(wp, scrolljump_value(wp), false);
    426        } else {
    427          scroll_cursor_halfway(wp, false, false);
    428        }
    429      }
    430    }
    431  }
    432  wp->w_valid |= VALID_TOPLINE;
    433  wp->w_viewport_invalid = true;
    434  win_check_anchored_floats(wp);
    435 
    436  // Need to redraw when topline changed.
    437  if (wp->w_topline != old_topline
    438      || wp->w_topfill != old_topfill) {
    439    dollar_vcol = -1;
    440    redraw_later(wp, UPD_VALID);
    441 
    442    // When 'smoothscroll' is not set, should reset w_skipcol.
    443    if (!wp->w_p_sms) {
    444      reset_skipcol(wp);
    445    } else if (wp->w_skipcol != 0) {
    446      redraw_later(wp, UPD_SOME_VALID);
    447    }
    448 
    449    // May need to set w_skipcol when cursor in w_topline.
    450    if (wp->w_cursor.lnum == wp->w_topline) {
    451      validate_cursor(wp);
    452    }
    453  }
    454 
    455  *so_ptr = save_so;
    456 }
    457 
    458 /// Return the scrolljump value to use for the window "wp".
    459 /// When 'scrolljump' is positive use it as-is.
    460 /// When 'scrolljump' is negative use it as a percentage of the window height.
    461 static int scrolljump_value(win_T *wp)
    462 {
    463  int result = p_sj >= 0 ? (int)p_sj : (wp->w_view_height * (int)(-p_sj)) / 100;
    464  return result;
    465 }
    466 
    467 /// Return true when there are not 'scrolloff' lines above the cursor for window "wp".
    468 static bool check_top_offset(win_T *wp)
    469 {
    470  int so = get_scrolloff_value(wp);
    471  if (wp->w_cursor.lnum < wp->w_topline + so || win_lines_concealed(wp)) {
    472    lineoff_T loff;
    473    loff.lnum = wp->w_cursor.lnum;
    474    loff.fill = 0;
    475    int n = wp->w_topfill;  // always have this context
    476    // Count the visible screen lines above the cursor line.
    477    while (n < so) {
    478      topline_back(wp, &loff);
    479      // Stop when included a line above the window.
    480      if (loff.lnum < wp->w_topline
    481          || (loff.lnum == wp->w_topline && loff.fill > 0)) {
    482        break;
    483      }
    484      n += loff.height;
    485    }
    486    if (n < so) {
    487      return true;
    488    }
    489  }
    490  return false;
    491 }
    492 
    493 /// Update w_curswant.
    494 void update_curswant_force(void)
    495 {
    496  validate_virtcol(curwin);
    497  curwin->w_curswant = curwin->w_virtcol;
    498  curwin->w_set_curswant = false;
    499 }
    500 
    501 /// Update w_curswant if w_set_curswant is set.
    502 void update_curswant(void)
    503 {
    504  if (curwin->w_set_curswant) {
    505    update_curswant_force();
    506  }
    507 }
    508 
    509 // Check if the cursor has moved.  Set the w_valid flag accordingly.
    510 void check_cursor_moved(win_T *wp)
    511 {
    512  if (wp->w_cursor.lnum != wp->w_valid_cursor.lnum) {
    513    wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL
    514                     |VALID_CHEIGHT|VALID_CROW|VALID_TOPLINE);
    515 
    516    // Concealed line visibility toggled.
    517    if (wp == curwin && wp->w_valid_cursor.lnum > 0 && wp->w_p_cole >= 2
    518        && !conceal_cursor_line(wp)
    519        && (decor_conceal_line(wp, wp->w_cursor.lnum - 1, true)
    520            || decor_conceal_line(wp, wp->w_valid_cursor.lnum - 1, true))) {
    521      changed_window_setting(wp);
    522    }
    523    wp->w_valid_cursor = wp->w_cursor;
    524    wp->w_valid_leftcol = wp->w_leftcol;
    525    wp->w_valid_skipcol = wp->w_skipcol;
    526    wp->w_viewport_invalid = true;
    527  } else if (wp->w_skipcol != wp->w_valid_skipcol) {
    528    wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL
    529                     |VALID_CHEIGHT|VALID_CROW
    530                     |VALID_BOTLINE|VALID_BOTLINE_AP);
    531    wp->w_valid_cursor = wp->w_cursor;
    532    wp->w_valid_leftcol = wp->w_leftcol;
    533    wp->w_valid_skipcol = wp->w_skipcol;
    534  } else if (wp->w_cursor.col != wp->w_valid_cursor.col
    535             || wp->w_leftcol != wp->w_valid_leftcol
    536             || wp->w_cursor.coladd !=
    537             wp->w_valid_cursor.coladd) {
    538    wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL);
    539    wp->w_valid_cursor.col = wp->w_cursor.col;
    540    wp->w_valid_leftcol = wp->w_leftcol;
    541    wp->w_valid_cursor.coladd = wp->w_cursor.coladd;
    542    wp->w_viewport_invalid = true;
    543  }
    544 }
    545 
    546 // Call this function when some window settings have changed, which require
    547 // the cursor position, botline and topline to be recomputed and the window to
    548 // be redrawn.  E.g, when changing the 'wrap' option or folding.
    549 void changed_window_setting(win_T *wp)
    550 {
    551  wp->w_lines_valid = 0;
    552  changed_line_abv_curs_win(wp);
    553  wp->w_valid &= ~(VALID_BOTLINE|VALID_BOTLINE_AP|VALID_TOPLINE);
    554  redraw_later(wp, UPD_NOT_VALID);
    555 }
    556 
    557 /// Call changed_window_setting() for every window.
    558 void changed_window_setting_all(void)
    559 {
    560  FOR_ALL_TAB_WINDOWS(tp, wp) {
    561    changed_window_setting(wp);
    562  }
    563 }
    564 
    565 // Set wp->w_topline to a certain number.
    566 void set_topline(win_T *wp, linenr_T lnum)
    567 {
    568  linenr_T prev_topline = wp->w_topline;
    569 
    570  // go to first of folded lines
    571  hasFolding(wp, lnum, &lnum, NULL);
    572  // Approximate the value of w_botline
    573  wp->w_botline += lnum - wp->w_topline;
    574  if (wp->w_botline > wp->w_buffer->b_ml.ml_line_count + 1) {
    575    wp->w_botline = wp->w_buffer->b_ml.ml_line_count + 1;
    576  }
    577  wp->w_topline = lnum;
    578  wp->w_topline_was_set = true;
    579  if (lnum != prev_topline) {
    580    // Keep the filler lines when the topline didn't change.
    581    wp->w_topfill = 0;
    582  }
    583  wp->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_TOPLINE);
    584  // Don't set VALID_TOPLINE here, 'scrolloff' needs to be checked.
    585  redraw_later(wp, UPD_VALID);
    586 }
    587 
    588 /// Call this function when the length of the cursor line (in screen
    589 /// characters) has changed, and the change is before the cursor.
    590 /// If the line length changed the number of screen lines might change,
    591 /// requiring updating w_topline.  That may also invalidate w_crow.
    592 /// Need to take care of w_botline separately!
    593 void changed_cline_bef_curs(win_T *wp)
    594 {
    595  wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL|VALID_CROW
    596                   |VALID_CHEIGHT|VALID_TOPLINE);
    597 }
    598 
    599 // Call this function when the length of a line (in screen characters) above
    600 // the cursor have changed.
    601 // Need to take care of w_botline separately!
    602 void changed_line_abv_curs(void)
    603 {
    604  curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL|VALID_CROW
    605                       |VALID_CHEIGHT|VALID_TOPLINE);
    606 }
    607 
    608 void changed_line_abv_curs_win(win_T *wp)
    609 {
    610  wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL|VALID_CROW
    611                   |VALID_CHEIGHT|VALID_TOPLINE);
    612 }
    613 
    614 /// Make sure the value of wp->w_botline is valid.
    615 void validate_botline_win(win_T *wp)
    616  FUNC_ATTR_NONNULL_ALL
    617 {
    618  if (!(wp->w_valid & VALID_BOTLINE)) {
    619    comp_botline(wp);
    620  }
    621 }
    622 
    623 /// Mark wp->w_botline as invalid (because of some change in the buffer).
    624 void invalidate_botline_win(win_T *wp)
    625  FUNC_ATTR_NONNULL_ALL
    626 {
    627  wp->w_valid &= ~(VALID_BOTLINE|VALID_BOTLINE_AP);
    628 }
    629 
    630 void approximate_botline_win(win_T *wp)
    631 {
    632  wp->w_valid &= ~VALID_BOTLINE;
    633 }
    634 
    635 // Return true if wp->w_wrow and wp->w_wcol are valid.
    636 int cursor_valid(win_T *wp)
    637 {
    638  check_cursor_moved(wp);
    639  return (wp->w_valid & (VALID_WROW|VALID_WCOL)) == (VALID_WROW|VALID_WCOL);
    640 }
    641 
    642 // Validate cursor position.  Makes sure w_wrow and w_wcol are valid.
    643 // w_topline must be valid, you may need to call update_topline() first!
    644 void validate_cursor(win_T *wp)
    645 {
    646  check_cursor_lnum(wp);
    647  check_cursor_moved(wp);
    648  if ((wp->w_valid & (VALID_WCOL|VALID_WROW)) != (VALID_WCOL|VALID_WROW)) {
    649    curs_columns(wp, true);
    650  }
    651 }
    652 
    653 // Compute wp->w_cline_row and wp->w_cline_height, based on the current value
    654 // of wp->w_topline.
    655 static void curs_rows(win_T *wp)
    656 {
    657  // Check if wp->w_lines[].wl_size is invalid
    658  bool all_invalid = (!redrawing()
    659                      || wp->w_lines_valid == 0
    660                      || wp->w_lines[0].wl_lnum > wp->w_topline);
    661  int i = 0;
    662  wp->w_cline_row = 0;
    663  for (linenr_T lnum = wp->w_topline; lnum < wp->w_cursor.lnum; i++) {
    664    bool valid = false;
    665    if (!all_invalid && i < wp->w_lines_valid) {
    666      if (wp->w_lines[i].wl_lnum < lnum || !wp->w_lines[i].wl_valid) {
    667        continue;                       // skip changed or deleted lines
    668      }
    669      if (wp->w_lines[i].wl_lnum == lnum) {
    670        // Check for newly inserted lines below this row, in which
    671        // case we need to check for folded lines.
    672        if (!wp->w_buffer->b_mod_set
    673            || wp->w_lines[i].wl_lastlnum < wp->w_cursor.lnum
    674            || wp->w_buffer->b_mod_top
    675            > wp->w_lines[i].wl_lastlnum + 1) {
    676          valid = true;
    677        }
    678      } else if (wp->w_lines[i].wl_lnum > lnum) {
    679        i--;                            // hold at inserted lines
    680      }
    681    }
    682    if (valid && (lnum != wp->w_topline || (wp->w_skipcol == 0 && !win_may_fill(wp)))) {
    683      lnum = wp->w_lines[i].wl_lastlnum + 1;
    684      // Cursor inside folded or concealed lines, don't count this row
    685      if (lnum > wp->w_cursor.lnum) {
    686        break;
    687      }
    688      wp->w_cline_row += wp->w_lines[i].wl_size;
    689    } else {
    690      linenr_T last = lnum;
    691      bool folded;
    692      int n = plines_correct_topline(wp, lnum, &last, true, &folded);
    693      lnum = last + 1;
    694      if (lnum + decor_conceal_line(wp, lnum - 1, false) > wp->w_cursor.lnum) {
    695        break;
    696      }
    697      wp->w_cline_row += n;
    698    }
    699  }
    700 
    701  check_cursor_moved(wp);
    702  if (!(wp->w_valid & VALID_CHEIGHT)) {
    703    if (all_invalid
    704        || i == wp->w_lines_valid
    705        || (i < wp->w_lines_valid
    706            && (!wp->w_lines[i].wl_valid
    707                || wp->w_lines[i].wl_lnum != wp->w_cursor.lnum))) {
    708      wp->w_cline_height = plines_win_full(wp, wp->w_cursor.lnum, NULL,
    709                                           &wp->w_cline_folded, true, true);
    710    } else if (i > wp->w_lines_valid) {
    711      // a line that is too long to fit on the last screen line
    712      wp->w_cline_height = 0;
    713      wp->w_cline_folded = hasFolding(wp, wp->w_cursor.lnum, NULL, NULL);
    714    } else {
    715      wp->w_cline_height = wp->w_lines[i].wl_size;
    716      wp->w_cline_folded = wp->w_lines[i].wl_folded;
    717    }
    718  }
    719 
    720  redraw_for_cursorline(wp);
    721  wp->w_valid |= VALID_CROW|VALID_CHEIGHT;
    722 }
    723 
    724 // Validate wp->w_virtcol only.
    725 void validate_virtcol(win_T *wp)
    726 {
    727  check_cursor_moved(wp);
    728 
    729  if (wp->w_valid & VALID_VIRTCOL) {
    730    return;
    731  }
    732 
    733  getvvcol(wp, &wp->w_cursor, NULL, &(wp->w_virtcol), NULL);
    734  redraw_for_cursorcolumn(wp);
    735  wp->w_valid |= VALID_VIRTCOL;
    736 }
    737 
    738 // Validate wp->w_cline_height only.
    739 void validate_cheight(win_T *wp)
    740 {
    741  check_cursor_moved(wp);
    742 
    743  if (wp->w_valid & VALID_CHEIGHT) {
    744    return;
    745  }
    746 
    747  wp->w_cline_height = plines_win_full(wp, wp->w_cursor.lnum,
    748                                       NULL, &wp->w_cline_folded,
    749                                       true, true);
    750  wp->w_valid |= VALID_CHEIGHT;
    751 }
    752 
    753 // Validate w_wcol and w_virtcol only.
    754 void validate_cursor_col(win_T *wp)
    755 {
    756  validate_virtcol(wp);
    757 
    758  if (wp->w_valid & VALID_WCOL) {
    759    return;
    760  }
    761 
    762  colnr_T col = wp->w_virtcol;
    763  colnr_T off = win_col_off(wp);
    764  col += off;
    765  int width = wp->w_view_width - off + win_col_off2(wp);
    766 
    767  // long line wrapping, adjust wp->w_wrow
    768  if (wp->w_p_wrap && col >= (colnr_T)wp->w_view_width && width > 0) {
    769    // use same formula as what is used in curs_columns()
    770    col -= ((col - wp->w_view_width) / width + 1) * width;
    771  }
    772  if (col > (int)wp->w_leftcol) {
    773    col -= wp->w_leftcol;
    774  } else {
    775    col = 0;
    776  }
    777  wp->w_wcol = col;
    778 
    779  wp->w_valid |= VALID_WCOL;
    780 }
    781 
    782 // Compute offset of a window, occupied by absolute or relative line number,
    783 // fold column and sign column (these don't move when scrolling horizontally).
    784 int win_col_off(win_T *wp)
    785 {
    786  return ((wp->w_p_nu || wp->w_p_rnu || *wp->w_p_stc != NUL)
    787          ? (number_width(wp) + (*wp->w_p_stc == NUL)) : 0)
    788         + ((wp != cmdwin_win) ? 0 : 1)
    789         + win_fdccol_count(wp) + (wp->w_scwidth * SIGN_WIDTH);
    790 }
    791 
    792 // Return the difference in column offset for the second screen line of a
    793 // wrapped line.  It's positive if 'number' or 'relativenumber' is on and 'n'
    794 // is in 'cpoptions'.
    795 int win_col_off2(win_T *wp)
    796 {
    797  if ((wp->w_p_nu || wp->w_p_rnu || *wp->w_p_stc != NUL)
    798      && vim_strchr(p_cpo, CPO_NUMCOL) != NULL) {
    799    return number_width(wp) + (*wp->w_p_stc == NUL);
    800  }
    801  return 0;
    802 }
    803 
    804 // Compute wp->w_wcol and wp->w_virtcol.
    805 // Also updates wp->w_wrow and wp->w_cline_row.
    806 // Also updates wp->w_leftcol.
    807 // @param may_scroll when true, may scroll horizontally
    808 void curs_columns(win_T *wp, int may_scroll)
    809 {
    810  colnr_T startcol;
    811  colnr_T endcol;
    812 
    813  // First make sure that w_topline is valid (after moving the cursor).
    814  update_topline(wp);
    815 
    816  // Next make sure that w_cline_row is valid.
    817  if (!(wp->w_valid & VALID_CROW)) {
    818    curs_rows(wp);
    819  }
    820 
    821  // Compute the number of virtual columns.
    822  if (wp->w_cline_folded) {
    823    // In a folded line the cursor is always in the first column
    824    startcol = wp->w_virtcol = endcol = wp->w_leftcol;
    825  } else {
    826    getvvcol(wp, &wp->w_cursor, &startcol, &(wp->w_virtcol), &endcol);
    827  }
    828 
    829  // remove '$' from change command when cursor moves onto it
    830  if (startcol > dollar_vcol) {
    831    dollar_vcol = -1;
    832  }
    833 
    834  int extra = win_col_off(wp);
    835  wp->w_wcol = wp->w_virtcol + extra;
    836  endcol += extra;
    837 
    838  // Now compute w_wrow, counting screen lines from w_cline_row.
    839  wp->w_wrow = wp->w_cline_row;
    840 
    841  int n;
    842  int width1 = wp->w_view_width - extra;  // text width for first screen line
    843  int width2 = 0;                          // text width for second and later screen line
    844  bool did_sub_skipcol = false;
    845  if (width1 <= 0) {
    846    // No room for text, put cursor in last char of window.
    847    // If not wrapping, the last non-empty line.
    848    wp->w_wcol = wp->w_view_width - 1;
    849    if (wp->w_p_wrap) {
    850      wp->w_wrow = wp->w_view_height - 1;
    851    } else {
    852      wp->w_wrow = wp->w_view_height - 1 - wp->w_empty_rows;
    853    }
    854  } else if (wp->w_p_wrap && wp->w_view_width != 0) {
    855    width2 = width1 + win_col_off2(wp);
    856 
    857    // skip columns that are not visible
    858    if (wp->w_cursor.lnum == wp->w_topline
    859        && wp->w_skipcol > 0
    860        && wp->w_wcol >= wp->w_skipcol) {
    861      // Deduct by multiples of width2.  This allows the long line wrapping
    862      // formula below to correctly calculate the w_wcol value when wrapping.
    863      if (wp->w_skipcol <= width1) {
    864        wp->w_wcol -= width2;
    865      } else {
    866        wp->w_wcol -= width2 * (((wp->w_skipcol - width1) / width2) + 1);
    867      }
    868 
    869      did_sub_skipcol = true;
    870    }
    871 
    872    // long line wrapping, adjust wp->w_wrow
    873    if (wp->w_wcol >= wp->w_view_width) {
    874      // this same formula is used in validate_cursor_col()
    875      n = (wp->w_wcol - wp->w_view_width) / width2 + 1;
    876      wp->w_wcol -= n * width2;
    877      wp->w_wrow += n;
    878    }
    879  } else if (may_scroll
    880             && !wp->w_cline_folded) {
    881    // No line wrapping: compute wp->w_leftcol if scrolling is on and line
    882    // is not folded.
    883    // If scrolling is off, wp->w_leftcol is assumed to be 0
    884 
    885    // If Cursor is left of the screen, scroll rightwards.
    886    // If Cursor is right of the screen, scroll leftwards
    887    // If we get closer to the edge than 'sidescrolloff', scroll a little
    888    // extra
    889    int siso = get_sidescrolloff_value(wp);
    890    int off_left = startcol - wp->w_leftcol - siso;
    891    int off_right = endcol - wp->w_leftcol - wp->w_view_width + siso + 1;
    892    if (off_left < 0 || off_right > 0) {
    893      int diff = (off_left < 0) ? -off_left : off_right;
    894 
    895      // When far off or not enough room on either side, put cursor in
    896      // middle of window.
    897      int new_leftcol;
    898      if (p_ss == 0 || diff >= width1 / 2 || off_right >= off_left) {
    899        new_leftcol = wp->w_wcol - extra - width1 / 2;
    900      } else {
    901        if (diff < p_ss) {
    902          assert(p_ss <= INT_MAX);
    903          diff = (int)p_ss;
    904        }
    905        if (off_left < 0) {
    906          new_leftcol = wp->w_leftcol - diff;
    907        } else {
    908          new_leftcol = wp->w_leftcol + diff;
    909        }
    910      }
    911      new_leftcol = MAX(new_leftcol, 0);
    912      if (new_leftcol != (int)wp->w_leftcol) {
    913        wp->w_leftcol = new_leftcol;
    914        win_check_anchored_floats(wp);
    915        // screen has to be redrawn with new wp->w_leftcol
    916        redraw_later(wp, UPD_NOT_VALID);
    917      }
    918    }
    919    wp->w_wcol -= wp->w_leftcol;
    920  } else if (wp->w_wcol > (int)wp->w_leftcol) {
    921    wp->w_wcol -= wp->w_leftcol;
    922  } else {
    923    wp->w_wcol = 0;
    924  }
    925 
    926  // Skip over filler lines.  At the top use w_topfill, there
    927  // may be some filler lines above the window.
    928  if (wp->w_cursor.lnum == wp->w_topline) {
    929    wp->w_wrow += wp->w_topfill;
    930  } else {
    931    wp->w_wrow += win_get_fill(wp, wp->w_cursor.lnum);
    932  }
    933 
    934  int plines = 0;
    935  int so = get_scrolloff_value(wp);
    936  colnr_T prev_skipcol = wp->w_skipcol;
    937  if ((wp->w_wrow >= wp->w_view_height
    938       || ((prev_skipcol > 0
    939            || wp->w_wrow + so >= wp->w_view_height)
    940           && (plines = plines_win_nofill(wp, wp->w_cursor.lnum, false)) - 1
    941           >= wp->w_view_height))
    942      && wp->w_view_height != 0
    943      && wp->w_cursor.lnum == wp->w_topline
    944      && width2 > 0
    945      && wp->w_view_width != 0) {
    946    // Cursor past end of screen.  Happens with a single line that does
    947    // not fit on screen.  Find a skipcol to show the text around the
    948    // cursor.  Avoid scrolling all the time. compute value of "extra":
    949    // 1: Less than "p_so" lines above
    950    // 2: Less than "p_so" lines below
    951    // 3: both of them
    952    extra = 0;
    953    if (wp->w_skipcol + so * width2 > wp->w_virtcol) {
    954      extra = 1;
    955    }
    956    // Compute last display line of the buffer line that we want at the
    957    // bottom of the window.
    958    if (plines == 0) {
    959      plines = plines_win(wp, wp->w_cursor.lnum, false);
    960    }
    961    plines--;
    962    if (plines > wp->w_wrow + so) {
    963      assert(so <= INT_MAX);
    964      n = wp->w_wrow + so;
    965    } else {
    966      n = plines;
    967    }
    968    if ((colnr_T)n >= wp->w_view_height + wp->w_skipcol / width2 - so) {
    969      extra += 2;
    970    }
    971 
    972    if (extra == 3 || wp->w_view_height <= so * 2) {
    973      // not enough room for 'scrolloff', put cursor in the middle
    974      n = wp->w_virtcol / width2;
    975      if (n > wp->w_view_height / 2) {
    976        n -= wp->w_view_height / 2;
    977      } else {
    978        n = 0;
    979      }
    980      // don't skip more than necessary
    981      if (n > plines - wp->w_view_height + 1) {
    982        n = plines - wp->w_view_height + 1;
    983      }
    984      wp->w_skipcol = n > 0 ? width1 + (n - 1) * width2
    985                            : 0;
    986    } else if (extra == 1) {
    987      // less than 'scrolloff' lines above, decrease skipcol
    988      assert(so <= INT_MAX);
    989      extra = (wp->w_skipcol + so * width2 - wp->w_virtcol + width2 - 1) / width2;
    990      if (extra > 0) {
    991        if ((colnr_T)(extra * width2) > wp->w_skipcol) {
    992          extra = wp->w_skipcol / width2;
    993        }
    994        wp->w_skipcol -= extra * width2;
    995      }
    996    } else if (extra == 2) {
    997      // less than 'scrolloff' lines below, increase skipcol
    998      endcol = (n - wp->w_view_height + 1) * width2;
    999      while (endcol > wp->w_virtcol) {
   1000        endcol -= width2;
   1001      }
   1002      wp->w_skipcol = MAX(wp->w_skipcol, endcol);
   1003    }
   1004 
   1005    // adjust w_wrow for the changed w_skipcol
   1006    if (did_sub_skipcol) {
   1007      wp->w_wrow -= (wp->w_skipcol - prev_skipcol) / width2;
   1008    } else {
   1009      wp->w_wrow -= wp->w_skipcol / width2;
   1010    }
   1011 
   1012    if (wp->w_wrow >= wp->w_view_height) {
   1013      // small window, make sure cursor is in it
   1014      extra = wp->w_wrow - wp->w_view_height + 1;
   1015      wp->w_skipcol += extra * width2;
   1016      wp->w_wrow -= extra;
   1017    }
   1018 
   1019    // extra could be either positive or negative
   1020    extra = (prev_skipcol - wp->w_skipcol) / width2;
   1021    // TODO(bfredl): this is very suspicious when not called by win_update()
   1022    // We should not randomly alter screen state outside of update_screen() :(
   1023    if (wp->w_grid.target) {
   1024      win_scroll_lines(wp, 0, extra);
   1025    }
   1026  } else if (!wp->w_p_sms) {
   1027    wp->w_skipcol = 0;
   1028  }
   1029  if (prev_skipcol != wp->w_skipcol) {
   1030    redraw_later(wp, UPD_SOME_VALID);
   1031  }
   1032 
   1033  redraw_for_cursorcolumn(wp);
   1034 
   1035  // now w_leftcol and w_skipcol are valid, avoid check_cursor_moved()
   1036  // thinking otherwise
   1037  wp->w_valid_leftcol = wp->w_leftcol;
   1038  wp->w_valid_skipcol = wp->w_skipcol;
   1039 
   1040  wp->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL;
   1041 }
   1042 
   1043 /// Compute the screen position of text character at "pos" in window "wp"
   1044 /// The resulting values are one-based, zero when character is not visible.
   1045 ///
   1046 /// @param[out] rowp screen row
   1047 /// @param[out] scolp start screen column
   1048 /// @param[out] ccolp cursor screen column
   1049 /// @param[out] ecolp end screen column
   1050 void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp, int *ccolp, int *ecolp,
   1051                       bool local)
   1052 {
   1053  colnr_T scol = 0;
   1054  colnr_T ccol = 0;
   1055  colnr_T ecol = 0;
   1056  int row = 0;
   1057  colnr_T coloff = 0;
   1058  bool visible_row = false;
   1059  bool is_folded = false;
   1060 
   1061  linenr_T lnum = pos->lnum;
   1062  if (lnum >= wp->w_topline && lnum <= wp->w_botline) {
   1063    is_folded = hasFolding(wp, lnum, &lnum, NULL);
   1064    row = plines_m_win(wp, wp->w_topline, lnum - 1, INT_MAX);
   1065    // "row" should be the screen line where line "lnum" begins, which can
   1066    // be negative if "lnum" is "w_topline" and "w_skipcol" is non-zero.
   1067    row -= adjust_plines_for_skipcol(wp);
   1068    // Add filler lines above this buffer line.
   1069    row += lnum == wp->w_topline ? wp->w_topfill : win_get_fill(wp, lnum);
   1070    visible_row = true;
   1071  } else if (!local || lnum < wp->w_topline) {
   1072    row = 0;
   1073  } else {
   1074    row = wp->w_view_height - 1;
   1075  }
   1076 
   1077  bool existing_row = (lnum > 0 && lnum <= wp->w_buffer->b_ml.ml_line_count);
   1078 
   1079  if ((local || visible_row) && existing_row) {
   1080    const colnr_T off = win_col_off(wp);
   1081    if (is_folded) {
   1082      row += (local ? 0 : wp->w_winrow + wp->w_winrow_off) + 1;
   1083      coloff = (local ? 0 : wp->w_wincol + wp->w_wincol_off) + 1 + off;
   1084    } else {
   1085      assert(lnum == pos->lnum);
   1086      getvcol(wp, pos, &scol, &ccol, &ecol);
   1087 
   1088      // similar to what is done in validate_cursor_col()
   1089      colnr_T col = scol;
   1090      col += off;
   1091      int width = wp->w_view_width - off + win_col_off2(wp);
   1092 
   1093      // long line wrapping, adjust row
   1094      if (wp->w_p_wrap && col >= (colnr_T)wp->w_view_width && width > 0) {
   1095        // use same formula as what is used in curs_columns()
   1096        int rowoff = visible_row ? ((col - wp->w_view_width) / width + 1) : 0;
   1097        col -= rowoff * width;
   1098        row += rowoff;
   1099      }
   1100 
   1101      col -= wp->w_leftcol;
   1102 
   1103      if (col >= 0 && col < wp->w_view_width && row >= 0 && row < wp->w_view_height) {
   1104        coloff = col - scol + (local ? 0 : wp->w_wincol + wp->w_wincol_off) + 1;
   1105        row += (local ? 0 : wp->w_winrow + wp->w_winrow_off) + 1;
   1106      } else {
   1107        // character is left, right or below of the window
   1108        scol = ccol = ecol = 0;
   1109        if (local) {
   1110          coloff = col < 0 ? -1 : wp->w_view_width + 1;
   1111        } else {
   1112          row = 0;
   1113        }
   1114      }
   1115    }
   1116  }
   1117  *rowp = row;
   1118  *scolp = scol + coloff;
   1119  *ccolp = ccol + coloff;
   1120  *ecolp = ecol + coloff;
   1121 }
   1122 
   1123 /// "screenpos({winid}, {lnum}, {col})" function
   1124 void f_screenpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
   1125 {
   1126  tv_dict_alloc_ret(rettv);
   1127  dict_T *dict = rettv->vval.v_dict;
   1128 
   1129  win_T *wp = find_win_by_nr_or_id(&argvars[0]);
   1130  if (wp == NULL) {
   1131    return;
   1132  }
   1133 
   1134  pos_T pos = {
   1135    .lnum = (linenr_T)tv_get_number(&argvars[1]),
   1136    .col = (colnr_T)tv_get_number(&argvars[2]) - 1,
   1137    .coladd = 0
   1138  };
   1139  if (pos.lnum > wp->w_buffer->b_ml.ml_line_count) {
   1140    semsg(_(e_invalid_line_number_nr), pos.lnum);
   1141    return;
   1142  }
   1143  pos.col = MAX(pos.col, 0);
   1144  int row = 0;
   1145  int scol = 0;
   1146  int ccol = 0;
   1147  int ecol = 0;
   1148  textpos2screenpos(wp, &pos, &row, &scol, &ccol, &ecol, false);
   1149 
   1150  tv_dict_add_nr(dict, S_LEN("row"), row);
   1151  tv_dict_add_nr(dict, S_LEN("col"), scol);
   1152  tv_dict_add_nr(dict, S_LEN("curscol"), ccol);
   1153  tv_dict_add_nr(dict, S_LEN("endcol"), ecol);
   1154 }
   1155 
   1156 /// Convert a virtual (screen) column to a character column.  The first column
   1157 /// is one.  For a multibyte character, the column number of the first byte is
   1158 /// returned.
   1159 static int virtcol2col(win_T *wp, linenr_T lnum, int vcol)
   1160 {
   1161  int offset = vcol2col(wp, lnum, vcol - 1, NULL);
   1162  char *line = ml_get_buf(wp->w_buffer, lnum);
   1163  char *p = line + offset;
   1164 
   1165  if (*p == NUL) {
   1166    if (p == line) {  // empty line
   1167      return 0;
   1168    }
   1169    // Move to the first byte of the last char.
   1170    MB_PTR_BACK(line, p);
   1171  }
   1172  return (int)(p - line + 1);
   1173 }
   1174 
   1175 /// "virtcol2col({winid}, {lnum}, {col})" function
   1176 void f_virtcol2col(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
   1177 {
   1178  rettv->vval.v_number = -1;
   1179 
   1180  if (tv_check_for_number_arg(argvars, 0) == FAIL
   1181      || tv_check_for_number_arg(argvars, 1) == FAIL
   1182      || tv_check_for_number_arg(argvars, 2) == FAIL) {
   1183    return;
   1184  }
   1185 
   1186  win_T *wp = find_win_by_nr_or_id(&argvars[0]);
   1187  if (wp == NULL) {
   1188    return;
   1189  }
   1190 
   1191  bool error = false;
   1192  linenr_T lnum = (linenr_T)tv_get_number_chk(&argvars[1], &error);
   1193  if (error || lnum < 0 || lnum > wp->w_buffer->b_ml.ml_line_count) {
   1194    return;
   1195  }
   1196 
   1197  int screencol = (int)tv_get_number_chk(&argvars[2], &error);
   1198  if (error || screencol < 0) {
   1199    return;
   1200  }
   1201 
   1202  rettv->vval.v_number = virtcol2col(wp, lnum, screencol);
   1203 }
   1204 
   1205 /// Make sure the cursor is in the visible part of the topline after scrolling
   1206 /// the screen with 'smoothscroll'.
   1207 static void cursor_correct_sms(win_T *wp)
   1208 {
   1209  if (!wp->w_p_sms || !wp->w_p_wrap || wp->w_cursor.lnum != wp->w_topline) {
   1210    return;
   1211  }
   1212 
   1213  int so = get_scrolloff_value(wp);
   1214  int width1 = wp->w_view_width - win_col_off(wp);
   1215  int width2 = width1 + win_col_off2(wp);
   1216  int so_cols = so == 0 ? 0 : width1 + (so - 1) * width2;
   1217  int space_cols = (wp->w_view_height - 1) * width2;
   1218  int size = so == 0 ? 0 : linetabsize_eol(wp, wp->w_topline);
   1219 
   1220  if (wp->w_topline == 1 && wp->w_skipcol == 0) {
   1221    so_cols = 0;               // Ignore 'scrolloff' at top of buffer.
   1222  } else if (so_cols > space_cols / 2) {
   1223    so_cols = space_cols / 2;  // Not enough room: put cursor in the middle.
   1224  }
   1225 
   1226  // Not enough screen lines in topline: ignore 'scrolloff'.
   1227  while (so_cols > size && so_cols - width2 >= width1 && width1 > 0) {
   1228    so_cols -= width2;
   1229  }
   1230  if (so_cols >= width1 && so_cols > size) {
   1231    so_cols -= width1;
   1232  }
   1233 
   1234  int overlap = wp->w_skipcol == 0
   1235                ? 0 : sms_marker_overlap(wp, wp->w_view_width - width2);
   1236  // If we have non-zero scrolloff, ignore marker overlap.
   1237  int top = wp->w_skipcol + (so_cols != 0 ? so_cols : overlap);
   1238  int bot = wp->w_skipcol + width1 + (wp->w_view_height - 1) * width2 - so_cols;
   1239 
   1240  validate_virtcol(wp);
   1241  colnr_T col = wp->w_virtcol;
   1242 
   1243  if (col < top) {
   1244    if (col < width1) {
   1245      col += width1;
   1246    }
   1247    while (width2 > 0 && col < top) {
   1248      col += width2;
   1249    }
   1250  } else {
   1251    while (width2 > 0 && col >= bot) {
   1252      col -= width2;
   1253    }
   1254  }
   1255 
   1256  if (col != wp->w_virtcol) {
   1257    wp->w_curswant = col;
   1258    int rc = coladvance(wp, wp->w_curswant);
   1259    // validate_virtcol() marked various things as valid, but after
   1260    // moving the cursor they need to be recomputed
   1261    wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL);
   1262    if (rc == FAIL && wp->w_skipcol > 0
   1263        && wp->w_cursor.lnum < wp->w_buffer->b_ml.ml_line_count) {
   1264      validate_virtcol(wp);
   1265      if (wp->w_virtcol < wp->w_skipcol + overlap) {
   1266        // Cursor still not visible: move it to the next line instead.
   1267        wp->w_cursor.lnum++;
   1268        wp->w_cursor.col = 0;
   1269        wp->w_cursor.coladd = 0;
   1270        wp->w_curswant = 0;
   1271        wp->w_valid &= ~VALID_VIRTCOL;
   1272      }
   1273    }
   1274  }
   1275 }
   1276 
   1277 /// Scroll "count" lines up or down, and redraw.
   1278 void scroll_redraw(int up, linenr_T count)
   1279 {
   1280  linenr_T prev_topline = curwin->w_topline;
   1281  int prev_skipcol = curwin->w_skipcol;
   1282  int prev_topfill = curwin->w_topfill;
   1283  linenr_T prev_lnum = curwin->w_cursor.lnum;
   1284 
   1285  bool moved = up
   1286               ? scrollup(curwin, count, true)
   1287               : scrolldown(curwin, count, true);
   1288 
   1289  if (get_scrolloff_value(curwin) > 0) {
   1290    // Adjust the cursor position for 'scrolloff'.  Mark w_topline as
   1291    // valid, otherwise the screen jumps back at the end of the file.
   1292    cursor_correct(curwin);
   1293    check_cursor_moved(curwin);
   1294    curwin->w_valid |= VALID_TOPLINE;
   1295 
   1296    // If moved back to where we were, at least move the cursor, otherwise
   1297    // we get stuck at one position.  Don't move the cursor up if the
   1298    // first line of the buffer is already on the screen
   1299    while (curwin->w_topline == prev_topline
   1300           && curwin->w_skipcol == prev_skipcol
   1301           && curwin->w_topfill == prev_topfill) {
   1302      if (up) {
   1303        if (curwin->w_cursor.lnum > prev_lnum
   1304            || cursor_down(1L, false) == FAIL) {
   1305          break;
   1306        }
   1307      } else {
   1308        if (curwin->w_cursor.lnum < prev_lnum
   1309            || prev_topline == 1L
   1310            || cursor_up(1L, false) == FAIL) {
   1311          break;
   1312        }
   1313      }
   1314      // Mark w_topline as valid, otherwise the screen jumps back at the
   1315      // end of the file.
   1316      check_cursor_moved(curwin);
   1317      curwin->w_valid |= VALID_TOPLINE;
   1318    }
   1319  }
   1320 
   1321  if (moved) {
   1322    curwin->w_viewport_invalid = true;
   1323  }
   1324 
   1325  cursor_correct_sms(curwin);
   1326  if (curwin->w_cursor.lnum != prev_lnum) {
   1327    coladvance(curwin, curwin->w_curswant);
   1328  }
   1329  redraw_later(curwin, UPD_VALID);
   1330 }
   1331 
   1332 /// Scroll a window down by "line_count" logical lines.  "CTRL-Y"
   1333 ///
   1334 /// @param line_count number of lines to scroll
   1335 /// @param byfold if true, count a closed fold as one line
   1336 bool scrolldown(win_T *wp, linenr_T line_count, int byfold)
   1337 {
   1338  int done = 0;                // total # of physical lines done
   1339  int width1 = 0;
   1340  int width2 = 0;
   1341  bool do_sms = wp->w_p_wrap && wp->w_p_sms;
   1342 
   1343  if (do_sms) {
   1344    width1 = wp->w_view_width - win_col_off(wp);
   1345    width2 = width1 + win_col_off2(wp);
   1346  }
   1347 
   1348  // Make sure w_topline is at the first of a sequence of folded lines.
   1349  hasFolding(wp, wp->w_topline, &wp->w_topline, NULL);
   1350  validate_cursor(wp);            // w_wrow needs to be valid
   1351  for (int todo = line_count; todo > 0; todo--) {
   1352    bool can_fill = wp->w_topfill < wp->w_view_height - 1
   1353                    && wp->w_topfill < win_get_fill(wp, wp->w_topline);
   1354    // break when at the very top
   1355    if (wp->w_topline == 1 && !can_fill && (!do_sms || wp->w_skipcol < width1)) {
   1356      break;
   1357    }
   1358    if (do_sms && wp->w_skipcol >= width1) {
   1359      // scroll a screen line down
   1360      if (wp->w_skipcol >= width1 + width2) {
   1361        wp->w_skipcol -= width2;
   1362      } else {
   1363        wp->w_skipcol -= width1;
   1364      }
   1365      redraw_later(wp, UPD_NOT_VALID);
   1366      done++;
   1367    } else if (can_fill) {
   1368      wp->w_topfill++;
   1369      done++;
   1370    } else {
   1371      // scroll a text line down
   1372      wp->w_topline--;
   1373      wp->w_skipcol = 0;
   1374      wp->w_topfill = 0;
   1375      // A sequence of folded lines only counts for one logical line
   1376      linenr_T first;
   1377      if (hasFolding(wp, wp->w_topline, &first, NULL)) {
   1378        done += !decor_conceal_line(wp, first - 1, false);
   1379        if (!byfold) {
   1380          todo -= wp->w_topline - first - 1;
   1381        }
   1382        wp->w_botline -= wp->w_topline - first;
   1383        wp->w_topline = first;
   1384      } else if (decor_conceal_line(wp, wp->w_topline - 1, false)) {
   1385        todo++;
   1386      } else {
   1387        if (do_sms) {
   1388          int size = linetabsize_eol(wp, wp->w_topline);
   1389          if (size > width1) {
   1390            wp->w_skipcol = width1;
   1391            size -= width1;
   1392            redraw_later(wp, UPD_NOT_VALID);
   1393          }
   1394          while (size > width2) {
   1395            wp->w_skipcol += width2;
   1396            size -= width2;
   1397          }
   1398          done++;
   1399        } else {
   1400          done += plines_win_nofill(wp, wp->w_topline, true);
   1401        }
   1402      }
   1403    }
   1404    wp->w_botline--;                // approximate w_botline
   1405    invalidate_botline_win(wp);
   1406  }
   1407 
   1408  // Adjust for concealed lines above w_topline
   1409  while (wp->w_topline > 1 && decor_conceal_line(wp, wp->w_topline - 2, false)) {
   1410    wp->w_topline--;
   1411    hasFolding(wp, wp->w_topline, &wp->w_topline, NULL);
   1412  }
   1413 
   1414  wp->w_wrow += done;               // keep w_wrow updated
   1415  wp->w_cline_row += done;          // keep w_cline_row updated
   1416 
   1417  if (wp->w_cursor.lnum == wp->w_topline) {
   1418    wp->w_cline_row = 0;
   1419  }
   1420  check_topfill(wp, true);
   1421 
   1422  // Compute the row number of the last row of the cursor line
   1423  // and move the cursor onto the displayed part of the window.
   1424  int wrow = wp->w_wrow;
   1425  if (wp->w_p_wrap && wp->w_view_width != 0) {
   1426    validate_virtcol(wp);
   1427    validate_cheight(wp);
   1428    wrow += wp->w_cline_height - 1 -
   1429            wp->w_virtcol / wp->w_view_width;
   1430  }
   1431  bool moved = false;
   1432  while (wrow >= wp->w_view_height && wp->w_cursor.lnum > 1) {
   1433    linenr_T first;
   1434    if (hasFolding(wp, wp->w_cursor.lnum, &first, NULL)) {
   1435      wrow -= !decor_conceal_line(wp, wp->w_cursor.lnum - 1, false);
   1436      wp->w_cursor.lnum = MAX(first - 1, 1);
   1437    } else {
   1438      wrow -= plines_win(wp, wp->w_cursor.lnum--, true);
   1439    }
   1440    wp->w_valid &=
   1441      ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL);
   1442    moved = true;
   1443  }
   1444  if (moved) {
   1445    // Move cursor to first line of closed fold.
   1446    foldAdjustCursor(wp);
   1447    coladvance(wp, wp->w_curswant);
   1448  }
   1449  wp->w_cursor.lnum = MAX(wp->w_cursor.lnum, wp->w_topline);
   1450 
   1451  return moved;
   1452 }
   1453 
   1454 /// Scroll a window up by "line_count" logical lines.  "CTRL-E"
   1455 ///
   1456 /// @param line_count number of lines to scroll
   1457 /// @param byfold if true, count a closed fold as one line
   1458 bool scrollup(win_T *wp, linenr_T line_count, bool byfold)
   1459 {
   1460  linenr_T topline = wp->w_topline;
   1461  linenr_T botline = wp->w_botline;
   1462  bool do_sms = wp->w_p_wrap && wp->w_p_sms;
   1463 
   1464  if (do_sms || (byfold && win_lines_concealed(wp)) || win_may_fill(wp)) {
   1465    int width1 = wp->w_view_width - win_col_off(wp);
   1466    int width2 = width1 + win_col_off2(wp);
   1467    int size = 0;
   1468    const colnr_T prev_skipcol = wp->w_skipcol;
   1469 
   1470    if (do_sms) {
   1471      size = linetabsize_eol(wp, wp->w_topline);
   1472    }
   1473 
   1474    // diff mode: first consume "topfill"
   1475    // 'smoothscroll': increase "w_skipcol" until it goes over the end of
   1476    // the line, then advance to the next line.
   1477    // folding: count each sequence of folded lines as one logical line.
   1478    for (int todo = line_count; todo > 0; todo--) {
   1479      todo += decor_conceal_line(wp, wp->w_topline - 1, false);
   1480      if (wp->w_topfill > 0) {
   1481        wp->w_topfill--;
   1482      } else {
   1483        linenr_T lnum = wp->w_topline;
   1484        if (byfold) {
   1485          // for a closed fold: go to the last line in the fold
   1486          hasFolding(wp, lnum, NULL, &lnum);
   1487        }
   1488        if (lnum == wp->w_topline && do_sms) {
   1489          // 'smoothscroll': increase "w_skipcol" until it goes over
   1490          // the end of the line, then advance to the next line.
   1491          int add = wp->w_skipcol > 0 ? width2 : width1;
   1492          wp->w_skipcol += add;
   1493          if (wp->w_skipcol >= size) {
   1494            if (lnum == wp->w_buffer->b_ml.ml_line_count) {
   1495              // at the last screen line, can't scroll further
   1496              wp->w_skipcol -= add;
   1497              break;
   1498            }
   1499            lnum++;
   1500          }
   1501        } else {
   1502          if (lnum >= wp->w_buffer->b_ml.ml_line_count) {
   1503            break;
   1504          }
   1505          lnum++;
   1506        }
   1507 
   1508        if (lnum > wp->w_topline) {
   1509          // approximate w_botline
   1510          wp->w_botline += lnum - wp->w_topline;
   1511          wp->w_topline = lnum;
   1512          wp->w_topfill = win_get_fill(wp, lnum);
   1513          wp->w_skipcol = 0;
   1514          if (todo > 1 && do_sms) {
   1515            size = linetabsize_eol(wp, wp->w_topline);
   1516          }
   1517        }
   1518      }
   1519    }
   1520 
   1521    if (prev_skipcol > 0 || wp->w_skipcol > 0) {
   1522      // need to redraw more, because wl_size of the (new) topline may
   1523      // now be invalid
   1524      redraw_later(wp, UPD_NOT_VALID);
   1525    }
   1526  } else {
   1527    wp->w_topline += line_count;
   1528    wp->w_botline += line_count;            // approximate w_botline
   1529  }
   1530 
   1531  wp->w_topline = MIN(wp->w_topline, wp->w_buffer->b_ml.ml_line_count);
   1532  wp->w_botline = MIN(wp->w_botline, wp->w_buffer->b_ml.ml_line_count + 1);
   1533 
   1534  check_topfill(wp, false);
   1535 
   1536  // Make sure w_topline is at the first of a sequence of folded lines.
   1537  hasFolding(wp, wp->w_topline, &wp->w_topline, NULL);
   1538 
   1539  wp->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE);
   1540  if (wp->w_cursor.lnum < wp->w_topline) {
   1541    wp->w_cursor.lnum = wp->w_topline;
   1542    wp->w_valid &=
   1543      ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL);
   1544    coladvance(wp, wp->w_curswant);
   1545  }
   1546 
   1547  bool moved = topline != wp->w_topline || botline != wp->w_botline;
   1548 
   1549  return moved;
   1550 }
   1551 
   1552 /// Called after changing the cursor column: make sure that curwin->w_skipcol is
   1553 /// valid for 'smoothscroll'.
   1554 void adjust_skipcol(void)
   1555 {
   1556  if (!curwin->w_p_wrap || !curwin->w_p_sms || curwin->w_cursor.lnum != curwin->w_topline) {
   1557    return;
   1558  }
   1559 
   1560  int width1 = curwin->w_view_width - win_col_off(curwin);
   1561  if (width1 <= 0) {
   1562    return;  // no text will be displayed
   1563  }
   1564  int width2 = width1 + win_col_off2(curwin);
   1565  int so = get_scrolloff_value(curwin);
   1566  colnr_T scrolloff_cols = so == 0 ? 0 : width1 + (so - 1) * width2;
   1567  bool scrolled = false;
   1568 
   1569  validate_cheight(curwin);
   1570  if (curwin->w_cline_height == curwin->w_view_height
   1571      // w_cline_height may be capped at w_view_height, check there aren't
   1572      // actually more lines.
   1573      && plines_win(curwin, curwin->w_cursor.lnum, false) <= curwin->w_view_height) {
   1574    // the line just fits in the window, don't scroll
   1575    reset_skipcol(curwin);
   1576    return;
   1577  }
   1578 
   1579  validate_virtcol(curwin);
   1580  int overlap = sms_marker_overlap(curwin, curwin->w_view_width - width2);
   1581  while (curwin->w_skipcol > 0
   1582         && curwin->w_virtcol < curwin->w_skipcol + overlap + scrolloff_cols) {
   1583    // scroll a screen line down
   1584    if (curwin->w_skipcol >= width1 + width2) {
   1585      curwin->w_skipcol -= width2;
   1586    } else {
   1587      curwin->w_skipcol -= width1;
   1588    }
   1589    scrolled = true;
   1590  }
   1591  if (scrolled) {
   1592    validate_virtcol(curwin);
   1593    redraw_later(curwin, UPD_NOT_VALID);
   1594    return;  // don't scroll in the other direction now
   1595  }
   1596  int row = 0;
   1597  colnr_T col = curwin->w_virtcol + scrolloff_cols;
   1598 
   1599  // Avoid adjusting for 'scrolloff' beyond the text line height.
   1600  if (scrolloff_cols > 0) {
   1601    int size = linetabsize_eol(curwin, curwin->w_topline);
   1602    size = width1 + width2 * ((size - width1 + width2 - 1) / width2);
   1603    while (col > size) {
   1604      col -= width2;
   1605    }
   1606  }
   1607  col -= curwin->w_skipcol;
   1608 
   1609  if (col >= width1) {
   1610    col -= width1;
   1611    row++;
   1612  }
   1613  if (col > width2) {
   1614    row += (int)col / width2;
   1615  }
   1616  if (row >= curwin->w_view_height) {
   1617    if (curwin->w_skipcol == 0) {
   1618      curwin->w_skipcol += width1;
   1619      row--;
   1620    }
   1621    if (row >= curwin->w_view_height) {
   1622      curwin->w_skipcol += (row - curwin->w_view_height) * width2;
   1623    }
   1624    redraw_later(curwin, UPD_NOT_VALID);
   1625  }
   1626 }
   1627 
   1628 /// Don't end up with too many filler lines in the window.
   1629 ///
   1630 /// @param down  when true scroll down when not enough space
   1631 void check_topfill(win_T *wp, bool down)
   1632 {
   1633  if (wp->w_topfill > 0) {
   1634    int n = plines_win_nofill(wp, wp->w_topline, true);
   1635    if (wp->w_topfill + n > wp->w_view_height) {
   1636      if (down && wp->w_topline > 1) {
   1637        wp->w_topline--;
   1638        wp->w_topfill = 0;
   1639      } else {
   1640        wp->w_topfill = wp->w_view_height - n;
   1641        wp->w_topfill = MAX(wp->w_topfill, 0);
   1642      }
   1643    }
   1644  }
   1645  win_check_anchored_floats(wp);
   1646 }
   1647 
   1648 // Scroll the screen one line down, but don't do it if it would move the
   1649 // cursor off the screen.
   1650 void scrolldown_clamp(void)
   1651 {
   1652  bool can_fill = (curwin->w_topfill < win_get_fill(curwin, curwin->w_topline));
   1653 
   1654  if (curwin->w_topline <= 1
   1655      && !can_fill) {
   1656    return;
   1657  }
   1658 
   1659  validate_cursor(curwin);        // w_wrow needs to be valid
   1660 
   1661  // Compute the row number of the last row of the cursor line
   1662  // and make sure it doesn't go off the screen. Make sure the cursor
   1663  // doesn't go past 'scrolloff' lines from the screen end.
   1664  int end_row = curwin->w_wrow;
   1665  if (can_fill) {
   1666    end_row++;
   1667  } else {
   1668    end_row += plines_win_nofill(curwin, curwin->w_topline - 1, true);
   1669  }
   1670  if (curwin->w_p_wrap && curwin->w_view_width != 0) {
   1671    validate_cheight(curwin);
   1672    validate_virtcol(curwin);
   1673    end_row += curwin->w_cline_height - 1 -
   1674               curwin->w_virtcol / curwin->w_view_width;
   1675  }
   1676  if (end_row < curwin->w_view_height - get_scrolloff_value(curwin)) {
   1677    if (can_fill) {
   1678      curwin->w_topfill++;
   1679      check_topfill(curwin, true);
   1680    } else {
   1681      curwin->w_topline--;
   1682      curwin->w_topfill = 0;
   1683    }
   1684    hasFolding(curwin, curwin->w_topline, &curwin->w_topline, NULL);
   1685    curwin->w_botline--;            // approximate w_botline
   1686    curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE);
   1687  }
   1688 }
   1689 
   1690 // Scroll the screen one line up, but don't do it if it would move the cursor
   1691 // off the screen.
   1692 void scrollup_clamp(void)
   1693 {
   1694  if (curwin->w_topline == curbuf->b_ml.ml_line_count
   1695      && curwin->w_topfill == 0) {
   1696    return;
   1697  }
   1698 
   1699  validate_cursor(curwin);        // w_wrow needs to be valid
   1700 
   1701  // Compute the row number of the first row of the cursor line
   1702  // and make sure it doesn't go off the screen. Make sure the cursor
   1703  // doesn't go before 'scrolloff' lines from the screen start.
   1704  int start_row = (curwin->w_wrow
   1705                   - plines_win_nofill(curwin, curwin->w_topline, true)
   1706                   - curwin->w_topfill);
   1707  if (curwin->w_p_wrap && curwin->w_view_width != 0) {
   1708    validate_virtcol(curwin);
   1709    start_row -= curwin->w_virtcol / curwin->w_view_width;
   1710  }
   1711  if (start_row >= get_scrolloff_value(curwin)) {
   1712    if (curwin->w_topfill > 0) {
   1713      curwin->w_topfill--;
   1714    } else {
   1715      hasFolding(curwin, curwin->w_topline, NULL, &curwin->w_topline);
   1716      curwin->w_topline++;
   1717    }
   1718    curwin->w_botline++;                // approximate w_botline
   1719    curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE);
   1720  }
   1721 }
   1722 
   1723 // Add one line above "lp->lnum".  This can be a filler line, a closed fold or
   1724 // a (wrapped) text line.  Uses and sets "lp->fill".
   1725 // Returns the height of the added line in "lp->height".
   1726 // Lines above the first one are incredibly high: MAXCOL.
   1727 // When "winheight" is true limit to window height.
   1728 static void topline_back_winheight(win_T *wp, lineoff_T *lp, int winheight)
   1729 {
   1730  if (lp->fill < win_get_fill(wp, lp->lnum)) {
   1731    // Add a filler line
   1732    lp->fill++;
   1733    lp->height = 1;
   1734  } else {
   1735    lp->lnum--;
   1736    lp->fill = 0;
   1737    if (lp->lnum < 1) {
   1738      lp->height = MAXCOL;
   1739    } else if (hasFolding(wp, lp->lnum, &lp->lnum, NULL)) {
   1740      // Add a closed fold unless concealed.
   1741      lp->height = !decor_conceal_line(wp, lp->lnum - 1, false);
   1742    } else {
   1743      lp->height = plines_win_nofill(wp, lp->lnum, winheight);
   1744    }
   1745  }
   1746 }
   1747 
   1748 static void topline_back(win_T *wp, lineoff_T *lp)
   1749 {
   1750  topline_back_winheight(wp, lp, true);
   1751 }
   1752 
   1753 // Add one line below "lp->lnum".  This can be a filler line, a closed fold or
   1754 // a (wrapped) text line.  Uses and sets "lp->fill".
   1755 // Returns the height of the added line in "lp->height".
   1756 // Lines below the last one are incredibly high.
   1757 static void botline_forw(win_T *wp, lineoff_T *lp)
   1758 {
   1759  if (lp->fill < win_get_fill(wp, lp->lnum + 1)) {
   1760    // Add a filler line.
   1761    lp->fill++;
   1762    lp->height = 1;
   1763  } else {
   1764    lp->lnum++;
   1765    lp->fill = 0;
   1766    assert(wp->w_buffer != 0);
   1767    if (lp->lnum > wp->w_buffer->b_ml.ml_line_count) {
   1768      lp->height = MAXCOL;
   1769    } else if (hasFolding(wp, lp->lnum, NULL, &lp->lnum)) {
   1770      // Add a closed fold unless concealed.
   1771      lp->height = !decor_conceal_line(wp, lp->lnum - 1, false);
   1772    } else {
   1773      lp->height = plines_win_nofill(wp, lp->lnum, true);
   1774    }
   1775  }
   1776 }
   1777 
   1778 // Recompute topline to put the cursor at the top of the window.
   1779 // Scroll at least "min_scroll" lines.
   1780 // If "always" is true, always set topline (for "zt").
   1781 void scroll_cursor_top(win_T *wp, int min_scroll, int always)
   1782 {
   1783  linenr_T old_topline = wp->w_topline;
   1784  int old_skipcol = wp->w_skipcol;
   1785  linenr_T old_topfill = wp->w_topfill;
   1786  int off = get_scrolloff_value(wp);
   1787 
   1788  if (mouse_dragging > 0) {
   1789    off = mouse_dragging - 1;
   1790  }
   1791 
   1792  // Decrease topline until:
   1793  // - it has become 1
   1794  // - (part of) the cursor line is moved off the screen or
   1795  // - moved at least 'scrolljump' lines and
   1796  // - at least 'scrolloff' lines above and below the cursor
   1797  validate_cheight(wp);
   1798  int scrolled = 0;
   1799  int used = wp->w_cline_height;  // includes filler lines above
   1800  if (wp->w_cursor.lnum < wp->w_topline) {
   1801    scrolled = used;
   1802  }
   1803 
   1804  linenr_T top;  // just above displayed lines
   1805  linenr_T bot;  // just below displayed lines
   1806  if (hasFolding(wp, wp->w_cursor.lnum, &top, &bot)) {
   1807    top--;
   1808    bot++;
   1809  } else {
   1810    top = wp->w_cursor.lnum - 1;
   1811    bot = wp->w_cursor.lnum + 1;
   1812  }
   1813  linenr_T new_topline = top + 1;
   1814 
   1815  // "used" already contains the number of filler lines above, don't add it
   1816  // again.
   1817  // Hide filler lines above cursor line by adding them to "extra".
   1818  int extra = win_get_fill(wp, wp->w_cursor.lnum);
   1819 
   1820  // Check if the lines from "top" to "bot" fit in the window.  If they do,
   1821  // set new_topline and advance "top" and "bot" to include more lines.
   1822  while (top > 0) {
   1823    int i = plines_win_nofill(wp, top, true);
   1824    hasFolding(wp, top, &top, NULL);
   1825    if (top < wp->w_topline) {
   1826      scrolled += i;
   1827    }
   1828 
   1829    // If scrolling is needed, scroll at least 'sj' lines.
   1830    if ((new_topline >= wp->w_topline || scrolled > min_scroll) && extra >= off) {
   1831      break;
   1832    }
   1833 
   1834    used += i;
   1835    if (extra + i <= off && bot < wp->w_buffer->b_ml.ml_line_count) {
   1836      used += plines_win_full(wp, bot, &bot, NULL, true, true);
   1837    }
   1838    if (used > wp->w_view_height) {
   1839      break;
   1840    }
   1841 
   1842    extra += i;
   1843    new_topline = top;
   1844    top--;
   1845    bot++;
   1846  }
   1847 
   1848  // If we don't have enough space, put cursor in the middle.
   1849  // This makes sure we get the same position when using "k" and "j"
   1850  // in a small window.
   1851  if (used > wp->w_view_height) {
   1852    scroll_cursor_halfway(wp, false, false);
   1853  } else {
   1854    // If "always" is false, only adjust topline to a lower value, higher
   1855    // value may happen with wrapping lines.
   1856    if (new_topline < wp->w_topline || always) {
   1857      wp->w_topline = new_topline;
   1858    }
   1859    wp->w_topline = MIN(wp->w_topline, wp->w_cursor.lnum);
   1860    wp->w_topfill = win_get_fill(wp, wp->w_topline);
   1861    if (wp->w_topfill > 0 && extra > off) {
   1862      wp->w_topfill -= extra - off;
   1863      wp->w_topfill = MAX(wp->w_topfill, 0);
   1864    }
   1865    check_topfill(wp, false);
   1866    if (wp->w_topline != old_topline) {
   1867      reset_skipcol(wp);
   1868    } else if (wp->w_topline == wp->w_cursor.lnum) {
   1869      validate_virtcol(wp);
   1870      if (wp->w_skipcol >= wp->w_virtcol) {
   1871        // TODO(vim): if the line doesn't fit may optimize w_skipcol instead
   1872        // of making it zero
   1873        reset_skipcol(wp);
   1874      }
   1875    }
   1876    if (wp->w_topline != old_topline
   1877        || wp->w_skipcol != old_skipcol
   1878        || wp->w_topfill != old_topfill) {
   1879      wp->w_valid &=
   1880        ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP);
   1881    }
   1882    wp->w_valid |= VALID_TOPLINE;
   1883    wp->w_viewport_invalid = true;
   1884  }
   1885 }
   1886 
   1887 // Set w_empty_rows and w_filler_rows for window "wp", having used up "used"
   1888 // screen lines for text lines.
   1889 void set_empty_rows(win_T *wp, int used)
   1890 {
   1891  wp->w_filler_rows = 0;
   1892  if (used == 0) {
   1893    wp->w_empty_rows = 0;  // single line that doesn't fit
   1894  } else {
   1895    wp->w_empty_rows = wp->w_view_height - used;
   1896    if (wp->w_botline <= wp->w_buffer->b_ml.ml_line_count) {
   1897      wp->w_filler_rows = win_get_fill(wp, wp->w_botline);
   1898      if (wp->w_empty_rows > wp->w_filler_rows) {
   1899        wp->w_empty_rows -= wp->w_filler_rows;
   1900      } else {
   1901        wp->w_filler_rows = wp->w_empty_rows;
   1902        wp->w_empty_rows = 0;
   1903      }
   1904    }
   1905  }
   1906 }
   1907 
   1908 /// Recompute topline to put the cursor at the bottom of the window.
   1909 /// When scrolling scroll at least "min_scroll" lines.
   1910 /// If "set_topbot" is true, set topline and botline first (for "zb").
   1911 /// This is messy stuff!!!
   1912 void scroll_cursor_bot(win_T *wp, int min_scroll, bool set_topbot)
   1913 {
   1914  lineoff_T loff;
   1915  linenr_T old_topline = wp->w_topline;
   1916  int old_skipcol = wp->w_skipcol;
   1917  int old_topfill = wp->w_topfill;
   1918  linenr_T old_botline = wp->w_botline;
   1919  int old_valid = wp->w_valid;
   1920  int old_empty_rows = wp->w_empty_rows;
   1921  linenr_T cln = wp->w_cursor.lnum;  // Cursor Line Number
   1922  bool do_sms = wp->w_p_wrap && wp->w_p_sms;
   1923 
   1924  if (set_topbot) {
   1925    int used = 0;
   1926    wp->w_botline = cln + 1;
   1927    loff.lnum = cln + 1;
   1928    loff.fill = 0;
   1929    while (true) {
   1930      topline_back_winheight(wp, &loff, false);
   1931      if (loff.height == MAXCOL) {
   1932        break;
   1933      }
   1934      if (used + loff.height > wp->w_view_height) {
   1935        if (do_sms) {
   1936          // 'smoothscroll' and 'wrap' are set.  The above line is
   1937          // too long to show in its entirety, so we show just a part
   1938          // of it.
   1939          if (used < wp->w_view_height) {
   1940            int plines_offset = used + loff.height - wp->w_view_height;
   1941            used = wp->w_view_height;
   1942            wp->w_topfill = loff.fill;
   1943            wp->w_topline = loff.lnum;
   1944            wp->w_skipcol = skipcol_from_plines(wp, plines_offset);
   1945          }
   1946        }
   1947        break;
   1948      }
   1949      wp->w_topfill = loff.fill;
   1950      wp->w_topline = loff.lnum;
   1951      used += loff.height;
   1952    }
   1953 
   1954    set_empty_rows(wp, used);
   1955    wp->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP;
   1956    if (wp->w_topline != old_topline
   1957        || wp->w_topfill != old_topfill
   1958        || wp->w_skipcol != old_skipcol
   1959        || wp->w_skipcol != 0) {
   1960      wp->w_valid &= ~(VALID_WROW|VALID_CROW);
   1961      if (wp->w_skipcol != old_skipcol) {
   1962        redraw_later(wp, UPD_NOT_VALID);
   1963      } else {
   1964        reset_skipcol(wp);
   1965      }
   1966    }
   1967  } else {
   1968    validate_botline_win(wp);
   1969  }
   1970 
   1971  // The lines of the cursor line itself are always used.
   1972  int used = plines_win_nofill(wp, cln, true);
   1973 
   1974  int scrolled = 0;
   1975  // If the cursor is on or below botline, we will at least scroll by the
   1976  // height of the cursor line, which is "used".  Correct for empty lines,
   1977  // which are really part of botline.
   1978  if (cln >= wp->w_botline) {
   1979    scrolled = used;
   1980    if (cln == wp->w_botline) {
   1981      scrolled -= wp->w_empty_rows;
   1982    }
   1983    if (do_sms) {
   1984      // 'smoothscroll' and 'wrap' are set.
   1985      // Calculate how many screen lines the current top line of window
   1986      // occupies. If it is occupying more than the entire window, we
   1987      // need to scroll the additional clipped lines to scroll past the
   1988      // top line before we can move on to the other lines.
   1989      int top_plines = plines_win_nofill(wp, wp->w_topline, false);
   1990      int width1 = wp->w_view_width - win_col_off(wp);
   1991 
   1992      if (width1 > 0) {
   1993        int width2 = width1 + win_col_off2(wp);
   1994        int skip_lines = 0;
   1995 
   1996        // A similar formula is used in curs_columns().
   1997        if (wp->w_skipcol > width1) {
   1998          skip_lines += (wp->w_skipcol - width1) / width2 + 1;
   1999        } else if (wp->w_skipcol > 0) {
   2000          skip_lines = 1;
   2001        }
   2002 
   2003        top_plines -= skip_lines;
   2004        if (top_plines > wp->w_view_height) {
   2005          scrolled += (top_plines - wp->w_view_height);
   2006        }
   2007      }
   2008    }
   2009  }
   2010 
   2011  lineoff_T boff;
   2012  // Stop counting lines to scroll when
   2013  // - hitting start of the file
   2014  // - scrolled nothing or at least 'sj' lines
   2015  // - at least 'so' lines below the cursor
   2016  // - lines between botline and cursor have been counted
   2017  if (!hasFolding(wp, wp->w_cursor.lnum, &loff.lnum, &boff.lnum)) {
   2018    loff.lnum = cln;
   2019    boff.lnum = cln;
   2020  }
   2021  loff.fill = 0;
   2022  boff.fill = 0;
   2023  int fill_below_window = win_get_fill(wp, wp->w_botline) - wp->w_filler_rows;
   2024 
   2025  int extra = 0;
   2026  int so = get_scrolloff_value(wp);
   2027  while (loff.lnum > 1) {
   2028    // Stop when scrolled nothing or at least "min_scroll", found "extra"
   2029    // context for 'scrolloff' and counted all lines below the window.
   2030    if ((((scrolled <= 0 || scrolled >= min_scroll)
   2031          && extra >= (mouse_dragging > 0 ? mouse_dragging - 1 : so))
   2032         || boff.lnum + 1 > wp->w_buffer->b_ml.ml_line_count)
   2033        && loff.lnum <= wp->w_botline
   2034        && (loff.lnum < wp->w_botline
   2035            || loff.fill >= fill_below_window)) {
   2036      break;
   2037    }
   2038 
   2039    // Add one line above
   2040    topline_back(wp, &loff);
   2041    if (loff.height == MAXCOL) {
   2042      used = MAXCOL;
   2043    } else {
   2044      used += loff.height;
   2045    }
   2046    if (used > wp->w_view_height) {
   2047      break;
   2048    }
   2049    if (loff.lnum >= wp->w_botline
   2050        && (loff.lnum > wp->w_botline
   2051            || loff.fill <= fill_below_window)) {
   2052      // Count screen lines that are below the window.
   2053      scrolled += loff.height;
   2054      if (loff.lnum == wp->w_botline
   2055          && loff.fill == 0) {
   2056        scrolled -= wp->w_empty_rows;
   2057      }
   2058    }
   2059 
   2060    if (boff.lnum < wp->w_buffer->b_ml.ml_line_count) {
   2061      // Add one line below
   2062      botline_forw(wp, &boff);
   2063      used += boff.height;
   2064      if (used > wp->w_view_height) {
   2065        break;
   2066      }
   2067      if (extra < (mouse_dragging > 0 ? mouse_dragging - 1 : so)
   2068          || scrolled < min_scroll) {
   2069        extra += boff.height;
   2070        if (boff.lnum >= wp->w_botline
   2071            || (boff.lnum + 1 == wp->w_botline
   2072                && boff.fill > wp->w_filler_rows)) {
   2073          // Count screen lines that are below the window.
   2074          scrolled += boff.height;
   2075          if (boff.lnum == wp->w_botline
   2076              && boff.fill == 0) {
   2077            scrolled -= wp->w_empty_rows;
   2078          }
   2079        }
   2080      }
   2081    }
   2082  }
   2083 
   2084  linenr_T line_count;
   2085  // wp->w_empty_rows is larger, no need to scroll
   2086  if (scrolled <= 0) {
   2087    line_count = 0;
   2088    // more than a screenfull, don't scroll but redraw
   2089  } else if (used > wp->w_view_height) {
   2090    line_count = used;
   2091    // scroll minimal number of lines
   2092  } else {
   2093    line_count = 0;
   2094    boff.fill = wp->w_topfill;
   2095    boff.lnum = wp->w_topline - 1;
   2096    int i;
   2097    for (i = 0; i < scrolled && boff.lnum < wp->w_botline;) {
   2098      botline_forw(wp, &boff);
   2099      i += boff.height;
   2100      line_count++;
   2101    }
   2102    if (i < scrolled) {         // below wp->w_botline, don't scroll
   2103      line_count = 9999;
   2104    }
   2105  }
   2106 
   2107  // Scroll up if the cursor is off the bottom of the screen a bit.
   2108  // Otherwise put it at 1/2 of the screen.
   2109  if (line_count >= wp->w_view_height && line_count > min_scroll) {
   2110    scroll_cursor_halfway(wp, false, true);
   2111  } else if (line_count > 0) {
   2112    if (do_sms) {
   2113      scrollup(wp, scrolled, true);  // TODO(vim):
   2114    } else {
   2115      scrollup(wp, line_count, true);
   2116    }
   2117  }
   2118 
   2119  // If topline didn't change we need to restore w_botline and w_empty_rows
   2120  // (we changed them).
   2121  // If topline did change, update_screen() will set botline.
   2122  if (wp->w_topline == old_topline && wp->w_skipcol == old_skipcol && set_topbot) {
   2123    wp->w_botline = old_botline;
   2124    wp->w_empty_rows = old_empty_rows;
   2125    wp->w_valid = old_valid;
   2126  }
   2127  wp->w_valid |= VALID_TOPLINE;
   2128  wp->w_viewport_invalid = true;
   2129 
   2130  // Make sure cursor is still visible after adjusting skipcol for "zb".
   2131  if (set_topbot) {
   2132    cursor_correct_sms(wp);
   2133  }
   2134 }
   2135 
   2136 /// Recompute topline to put the cursor halfway across the window
   2137 ///
   2138 /// @param atend if true, also put the cursor halfway to the end of the file.
   2139 ///
   2140 void scroll_cursor_halfway(win_T *wp, bool atend, bool prefer_above)
   2141 {
   2142  linenr_T old_topline = wp->w_topline;
   2143  lineoff_T loff = { .lnum = wp->w_cursor.lnum };
   2144  lineoff_T boff = { .lnum = wp->w_cursor.lnum };
   2145  hasFolding(wp, loff.lnum, &loff.lnum, &boff.lnum);
   2146  int used = plines_win_nofill(wp, loff.lnum, true);
   2147  loff.fill = 0;
   2148  boff.fill = 0;
   2149  linenr_T topline = loff.lnum;
   2150  colnr_T skipcol = 0;
   2151 
   2152  int want_height;
   2153  bool do_sms = wp->w_p_wrap && wp->w_p_sms;
   2154  if (do_sms) {
   2155    // 'smoothscroll' and 'wrap' are set
   2156    if (atend) {
   2157      want_height = (wp->w_view_height - used) / 2;
   2158      used = 0;
   2159    } else {
   2160      want_height = wp->w_view_height;
   2161    }
   2162  }
   2163 
   2164  int topfill = 0;
   2165  while (topline > 1) {
   2166    // If using smoothscroll, we can precisely scroll to the
   2167    // exact point where the cursor is halfway down the screen.
   2168    if (do_sms) {
   2169      topline_back_winheight(wp, &loff, false);
   2170      if (loff.height == MAXCOL) {
   2171        break;
   2172      }
   2173      used += loff.height;
   2174      if (!atend && boff.lnum < wp->w_buffer->b_ml.ml_line_count) {
   2175        botline_forw(wp, &boff);
   2176        used += boff.height;
   2177      }
   2178      if (used > want_height) {
   2179        if (used - loff.height < want_height) {
   2180          topline = loff.lnum;
   2181          topfill = loff.fill;
   2182          skipcol = skipcol_from_plines(wp, used - want_height);
   2183        }
   2184        break;
   2185      }
   2186      topline = loff.lnum;
   2187      topfill = loff.fill;
   2188      continue;
   2189    }
   2190 
   2191    // If not using smoothscroll, we have to iteratively find how many
   2192    // lines to scroll down to roughly fit the cursor.
   2193    // This may not be right in the middle if the lines'
   2194    // physical height > 1 (e.g. 'wrap' is on).
   2195 
   2196    // Depending on "prefer_above" we add a line above or below first.
   2197    // Loop twice to avoid duplicating code.
   2198    bool done = false;
   2199    int above = 0;
   2200    int below = 0;
   2201    for (int round = 1; round <= 2; round++) {
   2202      if (prefer_above
   2203          ? (round == 2 && below < above)
   2204          : (round == 1 && below <= above)) {
   2205        // add a line below the cursor
   2206        if (boff.lnum < wp->w_buffer->b_ml.ml_line_count) {
   2207          botline_forw(wp, &boff);
   2208          used += boff.height;
   2209          if (used > wp->w_view_height) {
   2210            done = true;
   2211            break;
   2212          }
   2213          below += boff.height;
   2214        } else {
   2215          below++;                    // count a "~" line
   2216          if (atend) {
   2217            used++;
   2218          }
   2219        }
   2220      }
   2221 
   2222      if (prefer_above
   2223          ? (round == 1 && below >= above)
   2224          : (round == 1 && below > above)) {
   2225        // add a line above the cursor
   2226        topline_back(wp, &loff);
   2227        if (loff.height == MAXCOL) {
   2228          used = MAXCOL;
   2229        } else {
   2230          used += loff.height;
   2231        }
   2232        if (used > wp->w_view_height) {
   2233          done = true;
   2234          break;
   2235        }
   2236        above += loff.height;
   2237        topline = loff.lnum;
   2238        topfill = loff.fill;
   2239      }
   2240    }
   2241    if (done) {
   2242      break;
   2243    }
   2244  }
   2245 
   2246  if (!hasFolding(wp, topline, &wp->w_topline, NULL)
   2247      && (wp->w_topline != topline || skipcol != 0 || wp->w_skipcol != 0)) {
   2248    wp->w_topline = topline;
   2249    if (skipcol != 0) {
   2250      wp->w_skipcol = skipcol;
   2251      redraw_later(wp, UPD_NOT_VALID);
   2252    } else if (do_sms) {
   2253      reset_skipcol(wp);
   2254    }
   2255  }
   2256  wp->w_topfill = topfill;
   2257  if (old_topline > wp->w_topline + wp->w_view_height) {
   2258    wp->w_botfill = false;
   2259  }
   2260  check_topfill(wp, false);
   2261  wp->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP);
   2262  wp->w_valid |= VALID_TOPLINE;
   2263 }
   2264 
   2265 // Correct the cursor position so that it is in a part of the screen at least
   2266 // 'so' lines from the top and bottom, if possible.
   2267 // If not possible, put it at the same position as scroll_cursor_halfway().
   2268 // When called topline must be valid!
   2269 void cursor_correct(win_T *wp)
   2270 {
   2271  // How many lines we would like to have above/below the cursor depends on
   2272  // whether the first/last line of the file is on screen.
   2273  int above_wanted = get_scrolloff_value(wp);
   2274  int below_wanted = get_scrolloff_value(wp);
   2275  if (mouse_dragging > 0) {
   2276    above_wanted = mouse_dragging - 1;
   2277    below_wanted = mouse_dragging - 1;
   2278  }
   2279  if (wp->w_topline == 1) {
   2280    above_wanted = 0;
   2281    int max_off = wp->w_view_height / 2;
   2282    below_wanted = MIN(below_wanted, max_off);
   2283  }
   2284  validate_botline_win(wp);
   2285  if (wp->w_botline == wp->w_buffer->b_ml.ml_line_count + 1
   2286      && mouse_dragging == 0) {
   2287    below_wanted = 0;
   2288    int max_off = (wp->w_view_height - 1) / 2;
   2289    above_wanted = MIN(above_wanted, max_off);
   2290  }
   2291 
   2292  // If there are sufficient file-lines above and below the cursor, we can
   2293  // return now.
   2294  linenr_T cln = wp->w_cursor.lnum;  // Cursor Line Number
   2295  if (cln >= wp->w_topline + above_wanted
   2296      && cln < wp->w_botline - below_wanted
   2297      && !win_lines_concealed(wp)) {
   2298    return;
   2299  }
   2300 
   2301  if (wp->w_p_sms && !wp->w_p_wrap) {
   2302    // 'smoothscroll' is active
   2303    if (wp->w_cline_height == wp->w_view_height) {
   2304      // The cursor line just fits in the window, don't scroll.
   2305      reset_skipcol(wp);
   2306      return;
   2307    }
   2308    // TODO(vim): If the cursor line doesn't fit in the window then only adjust w_skipcol.
   2309  }
   2310 
   2311  // Narrow down the area where the cursor can be put by taking lines from
   2312  // the top and the bottom until:
   2313  // - the desired context lines are found
   2314  // - the lines from the top is past the lines from the bottom
   2315  linenr_T topline = wp->w_topline;
   2316  linenr_T botline = wp->w_botline - 1;
   2317  // count filler lines as context
   2318  int above = wp->w_topfill;  // screen lines above topline
   2319  int below = wp->w_filler_rows;  // screen lines below botline
   2320  while ((above < above_wanted || below < below_wanted) && topline < botline) {
   2321    if (below < below_wanted && (below <= above || above >= above_wanted)) {
   2322      below += plines_win_full(wp, botline, NULL, NULL, true, true);
   2323      hasFolding(wp, botline, &botline, NULL);
   2324      botline--;
   2325    }
   2326    if (above < above_wanted && (above < below || below >= below_wanted)) {
   2327      above += plines_win_nofill(wp, topline, true);
   2328      hasFolding(wp, topline, NULL, &topline);
   2329 
   2330      // Count filler lines below this line as context.
   2331      if (topline < botline) {
   2332        above += win_get_fill(wp, topline + 1);
   2333      }
   2334      topline++;
   2335    }
   2336  }
   2337  if (topline == botline || botline == 0) {
   2338    wp->w_cursor.lnum = topline;
   2339  } else if (topline > botline) {
   2340    wp->w_cursor.lnum = botline;
   2341  } else {
   2342    if (cln < topline && wp->w_topline > 1) {
   2343      wp->w_cursor.lnum = topline;
   2344      wp->w_valid &=
   2345        ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW);
   2346    }
   2347    if (cln > botline && wp->w_botline <= wp->w_buffer->b_ml.ml_line_count) {
   2348      wp->w_cursor.lnum = botline;
   2349      wp->w_valid &=
   2350        ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW);
   2351    }
   2352  }
   2353  check_cursor_moved(wp);
   2354  wp->w_valid |= VALID_TOPLINE;
   2355  wp->w_viewport_invalid = true;
   2356 }
   2357 
   2358 /// Decide how much overlap to use for page-up or page-down scrolling.
   2359 /// This is symmetric, so that doing both keeps the same lines displayed.
   2360 /// Three lines are examined:
   2361 ///
   2362 ///  before CTRL-F          after CTRL-F / before CTRL-B
   2363 ///     etc.                    l1
   2364 ///  l1 last but one line       ------------
   2365 ///  l2 last text line          l2 top text line
   2366 ///  -------------              l3 second text line
   2367 ///  l3                            etc.
   2368 static int get_scroll_overlap(Direction dir)
   2369 {
   2370  lineoff_T loff;
   2371  int min_height = curwin->w_view_height - 2;
   2372 
   2373  validate_botline_win(curwin);
   2374  if ((dir == BACKWARD && curwin->w_topline == 1)
   2375      || (dir == FORWARD && curwin->w_botline > curbuf->b_ml.ml_line_count)) {
   2376    return min_height + 2;  // no overlap, still handle 'smoothscroll'
   2377  }
   2378 
   2379  loff.lnum = dir == FORWARD ? curwin->w_botline : curwin->w_topline - 1;
   2380  loff.fill = win_get_fill(curwin, loff.lnum + (dir == BACKWARD))
   2381              - (dir == FORWARD ? curwin->w_filler_rows : curwin->w_topfill);
   2382  loff.height = loff.fill > 0 ? 1 : plines_win_nofill(curwin, loff.lnum, true);
   2383 
   2384  int h1 = loff.height;
   2385  if (h1 > min_height) {
   2386    return min_height + 2;  // no overlap
   2387  }
   2388  if (dir == FORWARD) {
   2389    topline_back(curwin, &loff);
   2390  } else {
   2391    botline_forw(curwin, &loff);
   2392  }
   2393 
   2394  int h2 = loff.height;
   2395  if (h2 == MAXCOL || h2 + h1 > min_height) {
   2396    return min_height + 2;  // no overlap
   2397  }
   2398  if (dir == FORWARD) {
   2399    topline_back(curwin, &loff);
   2400  } else {
   2401    botline_forw(curwin, &loff);
   2402  }
   2403 
   2404  int h3 = loff.height;
   2405  if (h3 == MAXCOL || h3 + h2 > min_height) {
   2406    return min_height + 2;  // no overlap
   2407  }
   2408  if (dir == FORWARD) {
   2409    topline_back(curwin, &loff);
   2410  } else {
   2411    botline_forw(curwin, &loff);
   2412  }
   2413 
   2414  int h4 = loff.height;
   2415  if (h4 == MAXCOL || h4 + h3 + h2 > min_height || h3 + h2 + h1 > min_height) {
   2416    return min_height + 1;  // 1 line overlap
   2417  } else {
   2418    return min_height;      // 2 lines overlap
   2419  }
   2420 }
   2421 
   2422 /// Scroll "count" lines with 'smoothscroll' in direction "dir". Return true
   2423 /// when scrolling happened. Adjust "curscount" for scrolling different amount
   2424 /// of lines when 'smoothscroll' is disabled.
   2425 static bool scroll_with_sms(Direction dir, int count, int *curscount)
   2426 {
   2427  int prev_sms = curwin->w_p_sms;
   2428  colnr_T prev_skipcol = curwin->w_skipcol;
   2429  linenr_T prev_topline = curwin->w_topline;
   2430  int prev_topfill = curwin->w_topfill;
   2431 
   2432  curwin->w_p_sms = true;
   2433  scroll_redraw(dir == FORWARD, count);
   2434 
   2435  // Not actually smoothscrolling but ended up with partially visible line.
   2436  // Continue scrolling until skipcol is zero.
   2437  if (!prev_sms && curwin->w_skipcol > 0) {
   2438    int fixdir = dir;
   2439    // Reverse the scroll direction when topline already changed. One line
   2440    // extra for scrolling backward so that consuming skipcol is symmetric.
   2441    if (labs(curwin->w_topline - prev_topline) > (dir == BACKWARD)) {
   2442      fixdir = dir * -1;
   2443    }
   2444 
   2445    int width1 = curwin->w_view_width - win_col_off(curwin);
   2446    int width2 = width1 + win_col_off2(curwin);
   2447    count = 1 + (curwin->w_skipcol - width1 - 1) / width2;
   2448    if (fixdir == FORWARD) {
   2449      count = 1 + (linetabsize_eol(curwin, curwin->w_topline)
   2450                   - curwin->w_skipcol - width1 + width2 - 1) / width2;
   2451    }
   2452    scroll_redraw(fixdir == FORWARD, count);
   2453    *curscount += count * (fixdir == dir ? 1 : -1);
   2454  }
   2455  curwin->w_p_sms = prev_sms;
   2456 
   2457  return curwin->w_topline != prev_topline
   2458         || curwin->w_topfill != prev_topfill
   2459         || curwin->w_skipcol != prev_skipcol;
   2460 }
   2461 
   2462 /// Move screen "count" (half) pages up ("dir" is BACKWARD) or down ("dir" is
   2463 /// FORWARD) and update the screen. Handle moving the cursor and not scrolling
   2464 /// to reveal end of buffer lines for half-page scrolling with CTRL-D and CTRL-U.
   2465 ///
   2466 /// @return  FAIL for failure, OK otherwise.
   2467 int pagescroll(Direction dir, int count, bool half)
   2468 {
   2469  bool did_move = false;
   2470  int buflen = curbuf->b_ml.ml_line_count;
   2471  colnr_T prev_col = curwin->w_cursor.col;
   2472  colnr_T prev_curswant = curwin->w_curswant;
   2473  linenr_T prev_lnum = curwin->w_cursor.lnum;
   2474  oparg_T oa = { 0 };
   2475  cmdarg_T ca = { 0 };
   2476  ca.oap = &oa;
   2477 
   2478  if (half) {
   2479    // Scroll [count], 'scroll' or current window height lines.
   2480    if (count) {
   2481      curwin->w_p_scr = MIN(curwin->w_view_height, count);
   2482    }
   2483    count = MIN(curwin->w_view_height, (int)curwin->w_p_scr);
   2484 
   2485    int curscount = count;
   2486    // Adjust count so as to not reveal end of buffer lines.
   2487    if (dir == FORWARD
   2488        && (curwin->w_topline + curwin->w_view_height + count > buflen
   2489            || win_lines_concealed(curwin))) {
   2490      int n = plines_correct_topline(curwin, curwin->w_topline, NULL, false, NULL);
   2491      if (n - count < curwin->w_view_height && curwin->w_topline < buflen) {
   2492        n += plines_m_win(curwin, curwin->w_topline + 1, buflen, curwin->w_view_height + count);
   2493      }
   2494      if (n < curwin->w_view_height + count) {
   2495        count = n - curwin->w_view_height;
   2496      }
   2497    }
   2498 
   2499    // (Try to) scroll the window unless already at the end of the buffer.
   2500    if (count > 0) {
   2501      did_move = scroll_with_sms(dir, count, &curscount);
   2502      curwin->w_cursor.lnum = prev_lnum;
   2503      curwin->w_cursor.col = prev_col;
   2504      curwin->w_curswant = prev_curswant;
   2505    }
   2506 
   2507    // Move the cursor the same amount of screen lines, skipping over
   2508    // concealed lines as those were not included in "curscount".
   2509    if (curwin->w_p_wrap) {
   2510      nv_screengo(&oa, dir, curscount, true);
   2511    } else if (dir == FORWARD) {
   2512      cursor_down_inner(curwin, curscount, true);
   2513    } else {
   2514      cursor_up_inner(curwin, curscount, true);
   2515    }
   2516  } else {
   2517    // Scroll [count] times 'window' or current window height lines.
   2518    count *= ((ONE_WINDOW && p_window > 0 && p_window < Rows - 1)
   2519              ? MAX(1, (int)p_window - 2) : get_scroll_overlap(dir));
   2520    did_move = scroll_with_sms(dir, count, &count);
   2521 
   2522    if (did_move) {
   2523      // Place cursor at top or bottom of window.
   2524      validate_botline_win(curwin);
   2525      linenr_T lnum = (dir == FORWARD ? curwin->w_topline : curwin->w_botline - 1);
   2526      // In silent Ex mode the value of w_botline - 1 may be 0,
   2527      // but cursor lnum needs to be at least 1.
   2528      curwin->w_cursor.lnum = MAX(lnum, 1);
   2529    }
   2530  }
   2531 
   2532  if (get_scrolloff_value(curwin) > 0) {
   2533    cursor_correct(curwin);
   2534  }
   2535  // Move cursor to first line of closed fold.
   2536  foldAdjustCursor(curwin);
   2537 
   2538  did_move = did_move
   2539             || prev_col != curwin->w_cursor.col
   2540             || prev_lnum != curwin->w_cursor.lnum;
   2541 
   2542  // Error if both the viewport and cursor did not change.
   2543  if (!did_move) {
   2544    beep_flush();
   2545  } else if (!curwin->w_p_sms) {
   2546    beginline(BL_SOL | BL_FIX);
   2547  } else if (p_sol) {
   2548    nv_g_home_m_cmd(&ca);
   2549  }
   2550 
   2551  return did_move ? OK : FAIL;
   2552 }
   2553 
   2554 void do_check_cursorbind(void)
   2555 {
   2556  static win_T *prev_curwin = NULL;
   2557  static pos_T prev_cursor = { 0, 0, 0 };
   2558 
   2559  if (curwin == prev_curwin && equalpos(curwin->w_cursor, prev_cursor)) {
   2560    return;
   2561  }
   2562  prev_curwin = curwin;
   2563  prev_cursor = curwin->w_cursor;
   2564 
   2565  linenr_T line = curwin->w_cursor.lnum;
   2566  colnr_T col = curwin->w_cursor.col;
   2567  colnr_T coladd = curwin->w_cursor.coladd;
   2568  colnr_T curswant = curwin->w_curswant;
   2569  bool set_curswant = curwin->w_set_curswant;
   2570  win_T *old_curwin = curwin;
   2571  buf_T *old_curbuf = curbuf;
   2572  int old_VIsual_select = VIsual_select;
   2573  int old_VIsual_active = VIsual_active;
   2574 
   2575  // loop through the cursorbound windows
   2576  VIsual_select = VIsual_active = false;
   2577  FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
   2578    curwin = wp;
   2579    curbuf = curwin->w_buffer;
   2580    // skip original window and windows with 'nocursorbind'
   2581    if (curwin != old_curwin && curwin->w_p_crb) {
   2582      if (curwin->w_p_diff) {
   2583        curwin->w_cursor.lnum =
   2584          diff_get_corresponding_line(old_curbuf, line);
   2585      } else {
   2586        curwin->w_cursor.lnum = line;
   2587      }
   2588      curwin->w_cursor.col = col;
   2589      curwin->w_cursor.coladd = coladd;
   2590      curwin->w_curswant = curswant;
   2591      curwin->w_set_curswant = set_curswant;
   2592 
   2593      // Make sure the cursor is in a valid position.  Temporarily set
   2594      // "restart_edit" to allow the cursor to be beyond the EOL.
   2595      {
   2596        int restart_edit_save = restart_edit;
   2597        restart_edit = true;
   2598        check_cursor(curwin);
   2599 
   2600        // Avoid a scroll here for the cursor position, 'scrollbind' is
   2601        // more important.
   2602        if (!curwin->w_p_scb) {
   2603          validate_cursor(curwin);
   2604        }
   2605 
   2606        restart_edit = restart_edit_save;
   2607      }
   2608      // Correct cursor for multi-byte character.
   2609      mb_adjust_cursor();
   2610      redraw_later(curwin, UPD_VALID);
   2611 
   2612      // Only scroll when 'scrollbind' hasn't done this.
   2613      if (!curwin->w_p_scb) {
   2614        update_topline(curwin);
   2615      }
   2616      curwin->w_redr_status = true;
   2617    }
   2618  }
   2619 
   2620  // reset current-window
   2621  VIsual_select = old_VIsual_select;
   2622  VIsual_active = old_VIsual_active;
   2623  curwin = old_curwin;
   2624  curbuf = old_curbuf;
   2625 }