neovim

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

plines.c (31961B)


      1 // plines.c: calculate the vertical and horizontal size of text in a window
      2 
      3 #include <limits.h>
      4 #include <stdbool.h>
      5 #include <stdint.h>
      6 #include <string.h>
      7 
      8 #include "nvim/api/extmark.h"
      9 #include "nvim/ascii_defs.h"
     10 #include "nvim/buffer.h"
     11 #include "nvim/buffer_defs.h"
     12 #include "nvim/charset.h"
     13 #include "nvim/decoration.h"
     14 #include "nvim/decoration_defs.h"
     15 #include "nvim/diff.h"
     16 #include "nvim/fold.h"
     17 #include "nvim/globals.h"
     18 #include "nvim/indent.h"
     19 #include "nvim/macros_defs.h"
     20 #include "nvim/mark_defs.h"
     21 #include "nvim/marktree.h"
     22 #include "nvim/mbyte.h"
     23 #include "nvim/mbyte_defs.h"
     24 #include "nvim/memline.h"
     25 #include "nvim/move.h"
     26 #include "nvim/option.h"
     27 #include "nvim/option_vars.h"
     28 #include "nvim/plines.h"
     29 #include "nvim/pos_defs.h"
     30 #include "nvim/state.h"
     31 #include "nvim/state_defs.h"
     32 #include "nvim/types_defs.h"
     33 
     34 #include "plines.c.generated.h"
     35 
     36 /// Functions calculating horizontal size of text, when displayed in a window.
     37 
     38 /// Return the number of cells the first char in "p" will take on the screen,
     39 /// taking into account the size of a tab.
     40 /// Also see getvcol()
     41 ///
     42 /// @param p
     43 /// @param col
     44 ///
     45 /// @return Number of cells.
     46 ///
     47 /// @see charsize_nowrap()
     48 int win_chartabsize(win_T *wp, char *p, colnr_T col)
     49 {
     50  buf_T *buf = wp->w_buffer;
     51  if (*p == TAB && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) {
     52    return tabstop_padding(col, buf->b_p_ts, buf->b_p_vts_array);
     53  }
     54  return ptr2cells(p);
     55 }
     56 
     57 /// Like linetabsize_str(), but "s" starts at virtual column "startvcol".
     58 ///
     59 /// @param startvcol
     60 /// @param s
     61 ///
     62 /// @return Number of cells the string will take on the screen.
     63 int linetabsize_col(int startvcol, char *s)
     64 {
     65  CharsizeArg csarg;
     66  CSType const cstype = init_charsize_arg(&csarg, curwin, 0, s);
     67  if (cstype == kCharsizeFast) {
     68    return linesize_fast(&csarg, startvcol, MAXCOL);
     69  } else {
     70    return linesize_regular(&csarg, startvcol, MAXCOL);
     71  }
     72 }
     73 
     74 /// Return the number of cells line "lnum" of window "wp" will take on the
     75 /// screen, taking into account the size of a tab and inline virtual text.
     76 /// Doesn't count the size of 'listchars' "eol".
     77 int linetabsize(win_T *wp, linenr_T lnum)
     78 {
     79  return win_linetabsize(wp, lnum, ml_get_buf(wp->w_buffer, lnum), MAXCOL);
     80 }
     81 
     82 /// Like linetabsize(), but counts the size of 'listchars' "eol".
     83 int linetabsize_eol(win_T *wp, linenr_T lnum)
     84 {
     85  return linetabsize(wp, lnum)
     86         + ((wp->w_p_list && wp->w_p_lcs_chars.eol != NUL) ? 1 : 0);
     87 }
     88 
     89 static const uint32_t inline_filter[kMTMetaCount] = {[kMTMetaInline] = kMTFilterSelect };
     90 
     91 /// Prepare the structure passed to charsize functions.
     92 ///
     93 /// "line" is the start of the line.
     94 /// When "lnum" is zero do not use inline virtual text.
     95 CSType init_charsize_arg(CharsizeArg *csarg, win_T *wp, linenr_T lnum, char *line)
     96 {
     97  csarg->win = wp;
     98  csarg->line = line;
     99  csarg->max_head_vcol = 0;
    100  csarg->cur_text_width_left = 0;
    101  csarg->cur_text_width_right = 0;
    102  csarg->virt_row = -1;
    103  csarg->indent_width = INT_MIN;
    104  csarg->use_tabstop = !wp->w_p_list || wp->w_p_lcs_chars.tab1;
    105 
    106  if (lnum > 0) {
    107    if (marktree_itr_get_filter(wp->w_buffer->b_marktree, lnum - 1, 0, lnum, 0,
    108                                inline_filter, csarg->iter)) {
    109      csarg->virt_row = lnum - 1;
    110    }
    111  }
    112 
    113  if (csarg->virt_row >= 0
    114      || (wp->w_p_wrap && (wp->w_p_lbr || wp->w_p_bri || *get_showbreak_value(wp) != NUL))) {
    115    return kCharsizeRegular;
    116  } else {
    117    return kCharsizeFast;
    118  }
    119 }
    120 
    121 /// Get the number of cells taken up on the screen for the given arguments.
    122 /// "csarg->cur_text_width_left" and "csarg->cur_text_width_right" are set
    123 /// to the extra size for inline virtual text.
    124 ///
    125 /// When "csarg->max_head_vcol" is positive, only count in "head" the size
    126 /// of 'showbreak'/'breakindent' before "csarg->max_head_vcol".
    127 /// When "csarg->max_head_vcol" is negative, only count in "head" the size
    128 /// of 'showbreak'/'breakindent' before where cursor should be placed.
    129 CharSize charsize_regular(CharsizeArg *csarg, char *const cur, colnr_T const vcol,
    130                          int32_t const cur_char)
    131 {
    132  csarg->cur_text_width_left = 0;
    133  csarg->cur_text_width_right = 0;
    134 
    135  win_T *wp = csarg->win;
    136  buf_T *buf = wp->w_buffer;
    137  char *line = csarg->line;
    138  bool const use_tabstop = cur_char == TAB && csarg->use_tabstop;
    139  int mb_added = 0;
    140 
    141  bool has_lcs_eol = wp->w_p_list && wp->w_p_lcs_chars.eol != NUL;
    142 
    143  // First get normal size, without 'linebreak' or inline virtual text
    144  int size;
    145  int is_doublewidth = false;
    146  if (use_tabstop) {
    147    size = tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array);
    148  } else if (*cur == NUL) {
    149    // 1 cell for EOL list char (if present), as opposed to the two cell ^@
    150    // for a NUL character in the text.
    151    size = has_lcs_eol ? 1 : 0;
    152  } else if (cur_char < 0) {
    153    size = kInvalidByteCells;
    154  } else {
    155    size = ptr2cells(cur);
    156    is_doublewidth = size == 2 && cur_char >= 0x80;
    157  }
    158 
    159  if (csarg->virt_row >= 0) {
    160    int tab_size = size;
    161    int col = (int)(cur - line);
    162    while (true) {
    163      MTKey mark = marktree_itr_current(csarg->iter);
    164      if (mark.pos.row != csarg->virt_row || mark.pos.col > col) {
    165        break;
    166      } else if (mark.pos.col == col) {
    167        if (!mt_invalid(mark) && ns_in_win(mark.ns, wp)) {
    168          DecorInline decor = mt_decor(mark);
    169          DecorVirtText *vt = decor.ext ? decor.data.ext.vt : NULL;
    170          while (vt) {
    171            if (!(vt->flags & kVTIsLines) && vt->pos == kVPosInline) {
    172              if (mt_right(mark)) {
    173                csarg->cur_text_width_right += vt->width;
    174              } else {
    175                csarg->cur_text_width_left += vt->width;
    176              }
    177              size += vt->width;
    178              if (use_tabstop) {
    179                // tab size changes because of the inserted text
    180                size -= tab_size;
    181                tab_size = tabstop_padding(vcol + size, buf->b_p_ts, buf->b_p_vts_array);
    182                size += tab_size;
    183              }
    184            }
    185            vt = vt->next;
    186          }
    187        }
    188      }
    189      marktree_itr_next_filter(wp->w_buffer->b_marktree, csarg->iter, csarg->virt_row + 1, 0,
    190                               inline_filter);
    191    }
    192  }
    193 
    194  if (is_doublewidth && wp->w_p_wrap && in_win_border(wp, vcol + size - 2)) {
    195    // Count the ">" in the last column.
    196    size++;
    197    mb_added = 1;
    198  }
    199 
    200  char *const sbr = get_showbreak_value(wp);
    201 
    202  // May have to add something for 'breakindent' and/or 'showbreak'
    203  // string at the start of a screen line.
    204  int head = mb_added;
    205  // When "size" is 0, no new screen line is started.
    206  if (size > 0 && wp->w_p_wrap && (*sbr != NUL || wp->w_p_bri)) {
    207    int col_off_prev = win_col_off(wp);
    208    int width2 = wp->w_view_width - col_off_prev + win_col_off2(wp);
    209    colnr_T wcol = vcol + col_off_prev;
    210    colnr_T max_head_vcol = csarg->max_head_vcol;
    211    int added = 0;
    212 
    213    // cells taken by 'showbreak'/'breakindent' before current char
    214    int head_prev = 0;
    215    if (wcol >= wp->w_view_width) {
    216      wcol -= wp->w_view_width;
    217      col_off_prev = wp->w_view_width - width2;
    218      if (wcol >= width2 && width2 > 0) {
    219        wcol %= width2;
    220      }
    221      head_prev = csarg->indent_width;
    222      if (head_prev == INT_MIN) {
    223        head_prev = 0;
    224        if (*sbr != NUL) {
    225          head_prev += vim_strsize(sbr);
    226        }
    227        if (wp->w_p_bri) {
    228          head_prev += get_breakindent_win(wp, line);
    229        }
    230        csarg->indent_width = head_prev;
    231      }
    232      if (wcol < head_prev) {
    233        head_prev -= wcol;
    234        wcol += head_prev;
    235        added += head_prev;
    236        if (max_head_vcol <= 0 || vcol < max_head_vcol) {
    237          head += head_prev;
    238        }
    239      } else {
    240        head_prev = 0;
    241      }
    242      wcol += col_off_prev;
    243    }
    244 
    245    if (wcol + size > wp->w_view_width) {
    246      // cells taken by 'showbreak'/'breakindent' halfway current char
    247      int head_mid = csarg->indent_width;
    248      if (head_mid == INT_MIN) {
    249        head_mid = 0;
    250        if (*sbr != NUL) {
    251          head_mid += vim_strsize(sbr);
    252        }
    253        if (wp->w_p_bri) {
    254          head_mid += get_breakindent_win(wp, line);
    255        }
    256        csarg->indent_width = head_mid;
    257      }
    258      if (head_mid > 0) {
    259        // Calculate effective window width.
    260        int prev_rem = wp->w_view_width - wcol;
    261        int width = width2 - head_mid;
    262 
    263        if (width <= 0) {
    264          width = 1;
    265        }
    266        // Divide "size - prev_rem" by "width", rounding up.
    267        int cnt = (size - prev_rem + width - 1) / width;
    268        added += cnt * head_mid;
    269 
    270        if (max_head_vcol == 0 || vcol + size + added < max_head_vcol) {
    271          head += cnt * head_mid;
    272        } else if (max_head_vcol > vcol + head_prev + prev_rem) {
    273          head += (max_head_vcol - (vcol + head_prev + prev_rem)
    274                   + width2 - 1) / width2 * head_mid;
    275        } else if (max_head_vcol < 0) {
    276          int off = mb_added + virt_text_cursor_off(csarg, *cur == NUL);
    277          if (off >= prev_rem) {
    278            if (size > off) {
    279              head += (1 + (off - prev_rem) / width) * head_mid;
    280            } else {
    281              head += (off - prev_rem + width - 1) / width * head_mid;
    282            }
    283          }
    284        }
    285      }
    286    }
    287 
    288    size += added;
    289  }
    290 
    291  bool need_lbr = false;
    292  // If 'linebreak' set check at a blank before a non-blank if the line
    293  // needs a break here.
    294  if (wp->w_p_lbr && wp->w_p_wrap && wp->w_view_width != 0
    295      && vim_isbreak((uint8_t)cur[0]) && !vim_isbreak((uint8_t)cur[1])) {
    296    char *t = csarg->line;
    297    while (vim_isbreak((uint8_t)t[0])) {
    298      t++;
    299    }
    300    // 'linebreak' is only needed when not in leading whitespace.
    301    need_lbr = cur >= t;
    302  }
    303  if (need_lbr) {
    304    char *s = cur;
    305    // Count all characters from first non-blank after a blank up to next
    306    // non-blank after a blank.
    307    int numberextra = win_col_off(wp);
    308    colnr_T col_adj = size - 1;
    309    colnr_T colmax = (colnr_T)(wp->w_view_width - numberextra - col_adj);
    310    if (vcol >= colmax) {
    311      colmax += col_adj;
    312      int n = colmax + win_col_off2(wp);
    313      if (n > 0) {
    314        colmax += (((vcol - colmax) / n) + 1) * n - col_adj;
    315      }
    316    }
    317 
    318    colnr_T vcol2 = vcol;
    319    while (true) {
    320      char *ps = s;
    321      MB_PTR_ADV(s);
    322      int c = (uint8_t)(*s);
    323      if (!(c != NUL
    324            && (vim_isbreak(c) || vcol2 == vcol || !vim_isbreak((uint8_t)(*ps))))) {
    325        break;
    326      }
    327 
    328      vcol2 += win_chartabsize(wp, s, vcol2);
    329      if (vcol2 >= colmax) {  // doesn't fit
    330        size = colmax - vcol + col_adj;
    331        break;
    332      }
    333    }
    334  }
    335 
    336  return (CharSize){ .width = size, .head = head };
    337 }
    338 
    339 /// Like charsize_regular(), except it doesn't handle inline virtual text,
    340 /// 'linebreak', 'breakindent' or 'showbreak'.
    341 /// Handles normal characters, tabs and wrapping.
    342 /// This function is always inlined.
    343 ///
    344 /// @see charsize_regular
    345 /// @see charsize_fast
    346 static inline CharSize charsize_fast_impl(win_T *const wp, const char *cur, bool use_tabstop,
    347                                          colnr_T const vcol, int32_t const cur_char)
    348  FUNC_ATTR_PURE FUNC_ATTR_ALWAYS_INLINE
    349 {
    350  // A tab gets expanded, depending on the current column
    351  if (cur_char == TAB && use_tabstop) {
    352    return (CharSize){
    353      .width = tabstop_padding(vcol, wp->w_buffer->b_p_ts,
    354                               wp->w_buffer->b_p_vts_array)
    355    };
    356  } else {
    357    int width;
    358    if (cur_char < 0) {
    359      width = kInvalidByteCells;
    360    } else {
    361      // TODO(bfredl): perf: often cur_char is enough at this point to determine width.
    362      // we likely want a specialized version of utf_ptr2StrCharInfo also determining
    363      // the ptr2cells width at the same time without any extra decoding. (also applies
    364      // to charsize_regular and charsize_nowrap)
    365      width = ptr2cells(cur);
    366    }
    367 
    368    // If a double-width char doesn't fit at the end of a line, it wraps to the next line,
    369    // and the last column displays a '>'.
    370    if (width == 2 && cur_char >= 0x80 && wp->w_p_wrap && in_win_border(wp, vcol)) {
    371      return (CharSize){ .width = 3, .head = 1 };
    372    } else {
    373      return (CharSize){ .width = width };
    374    }
    375  }
    376 }
    377 
    378 /// Like charsize_regular(), except it doesn't handle inline virtual text,
    379 /// 'linebreak', 'breakindent' or 'showbreak'.
    380 /// Handles normal characters, tabs and wrapping.
    381 /// Can be used if CSType is kCharsizeFast.
    382 ///
    383 /// @see charsize_regular
    384 CharSize charsize_fast(CharsizeArg *csarg, const char *cur, colnr_T vcol, int32_t cur_char)
    385  FUNC_ATTR_PURE
    386 {
    387  return charsize_fast_impl(csarg->win, cur, csarg->use_tabstop, vcol, cur_char);
    388 }
    389 
    390 /// Get the number of cells taken up on the screen at given virtual column.
    391 ///
    392 /// @see win_chartabsize()
    393 int charsize_nowrap(buf_T *buf, const char *cur, bool use_tabstop, colnr_T vcol, int32_t cur_char)
    394 {
    395  if (cur_char == TAB && use_tabstop) {
    396    return tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array);
    397  } else if (cur_char < 0) {
    398    return kInvalidByteCells;
    399  } else {
    400    return ptr2cells(cur);
    401  }
    402 }
    403 
    404 /// Check that virtual column "vcol" is in the rightmost column of window "wp".
    405 ///
    406 /// @param  wp    window
    407 /// @param  vcol  column number
    408 static bool in_win_border(win_T *wp, colnr_T vcol)
    409  FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1)
    410 {
    411  if (wp->w_view_width == 0) {
    412    // there is no border
    413    return false;
    414  }
    415  int width1 = wp->w_view_width - win_col_off(wp);  // width of first line (after line number)
    416 
    417  if ((int)vcol < width1 - 1) {
    418    return false;
    419  }
    420 
    421  if ((int)vcol == width1 - 1) {
    422    return true;
    423  }
    424  int width2 = width1 + win_col_off2(wp);  // width of further lines
    425 
    426  if (width2 <= 0) {
    427    return false;
    428  }
    429  return (vcol - width1) % width2 == width2 - 1;
    430 }
    431 
    432 /// Calculate virtual column until the given "len".
    433 ///
    434 /// @param csarg    Argument to charsize functions.
    435 /// @param vcol_arg Starting virtual column.
    436 /// @param len      First byte of the end character, or MAXCOL.
    437 ///
    438 /// @return virtual column before the character at "len",
    439 ///         or full size of the line if "len" is MAXCOL.
    440 int linesize_regular(CharsizeArg *const csarg, int vcol_arg, colnr_T const len)
    441 {
    442  char *const line = csarg->line;
    443  int64_t vcol = vcol_arg;
    444 
    445  StrCharInfo ci = utf_ptr2StrCharInfo(line);
    446  while (ci.ptr - line < len && *ci.ptr != NUL) {
    447    vcol += charsize_regular(csarg, ci.ptr, vcol_arg, ci.chr.value).width;
    448    ci = utfc_next(ci);
    449    if (vcol > MAXCOL) {
    450      vcol_arg = MAXCOL;
    451      break;
    452    } else {
    453      vcol_arg = (int)vcol;
    454    }
    455  }
    456 
    457  // Check for inline virtual text after the end of the line.
    458  if (len == MAXCOL && csarg->virt_row >= 0 && *ci.ptr == NUL) {
    459    int head = charsize_regular(csarg, ci.ptr, vcol_arg, ci.chr.value).head;
    460    vcol += csarg->cur_text_width_left + csarg->cur_text_width_right + head;
    461    vcol_arg = vcol > MAXCOL ? MAXCOL : (int)vcol;
    462  }
    463 
    464  return vcol_arg;
    465 }
    466 
    467 /// Like linesize_regular(), but can be used when CSType is kCharsizeFast.
    468 ///
    469 /// @see linesize_regular
    470 int linesize_fast(CharsizeArg const *const csarg, int vcol_arg, colnr_T const len)
    471 {
    472  win_T *const wp = csarg->win;
    473  bool const use_tabstop = csarg->use_tabstop;
    474 
    475  char *const line = csarg->line;
    476  int64_t vcol = vcol_arg;
    477 
    478  StrCharInfo ci = utf_ptr2StrCharInfo(line);
    479  while (ci.ptr - line < len && *ci.ptr != NUL) {
    480    vcol += charsize_fast_impl(wp, ci.ptr, use_tabstop, vcol_arg, ci.chr.value).width;
    481    ci = utfc_next(ci);
    482    if (vcol > MAXCOL) {
    483      vcol_arg = MAXCOL;
    484      break;
    485    } else {
    486      vcol_arg = (int)vcol;
    487    }
    488  }
    489 
    490  return vcol_arg;
    491 }
    492 
    493 /// Get how many virtual columns inline virtual text should offset the cursor.
    494 ///
    495 /// @param csarg   should contain information stored by charsize_regular()
    496 ///                about widths of left and right gravity virtual text
    497 /// @param on_NUL  whether this is the end of the line
    498 static int virt_text_cursor_off(const CharsizeArg *csarg, bool on_NUL)
    499 {
    500  int off = 0;
    501  if (!on_NUL || !(State & MODE_NORMAL)) {
    502    off += csarg->cur_text_width_left;
    503  }
    504  if (!on_NUL && (State & MODE_NORMAL)) {
    505    off += csarg->cur_text_width_right;
    506  }
    507  return off;
    508 }
    509 
    510 /// Get virtual column number of pos.
    511 ///  start: on the first position of this character (TAB, ctrl)
    512 /// cursor: where the cursor is on this character (first char, except for TAB)
    513 ///    end: on the last position of this character (TAB, ctrl)
    514 ///
    515 /// This is used very often, keep it fast!
    516 ///
    517 /// @param wp
    518 /// @param pos
    519 /// @param start
    520 /// @param cursor
    521 /// @param end
    522 void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end)
    523 {
    524  char *const line = ml_get_buf(wp->w_buffer, pos->lnum);  // start of the line
    525  colnr_T const end_col = pos->col;
    526 
    527  CharsizeArg csarg;
    528  bool on_NUL = false;
    529  CSType const cstype = init_charsize_arg(&csarg, wp, pos->lnum, line);
    530  csarg.max_head_vcol = -1;
    531 
    532  colnr_T vcol = 0;
    533  CharSize char_size;
    534  StrCharInfo ci = utf_ptr2StrCharInfo(line);
    535  if (cstype == kCharsizeFast) {
    536    bool const use_tabstop = csarg.use_tabstop;
    537    while (true) {
    538      if (*ci.ptr == NUL) {
    539        // if cursor is at NUL, it is treated like 1 cell char
    540        char_size = (CharSize){ .width = 1 };
    541        break;
    542      }
    543      char_size = charsize_fast_impl(wp, ci.ptr, use_tabstop, vcol, ci.chr.value);
    544      StrCharInfo const next = utfc_next(ci);
    545      if (next.ptr - line > end_col) {
    546        break;
    547      }
    548      ci = next;
    549      vcol += char_size.width;
    550    }
    551  } else {
    552    while (true) {
    553      char_size = charsize_regular(&csarg, ci.ptr, vcol, ci.chr.value);
    554      // make sure we don't go past the end of the line
    555      if (*ci.ptr == NUL) {
    556        // NUL at end of line only takes one column unless there is virtual text
    557        char_size.width = 1 + csarg.cur_text_width_left + csarg.cur_text_width_right;
    558        on_NUL = true;
    559        break;
    560      }
    561      StrCharInfo const next = utfc_next(ci);
    562      if (next.ptr - line > end_col) {
    563        break;
    564      }
    565      ci = next;
    566      vcol += char_size.width;
    567    }
    568  }
    569 
    570  if (*ci.ptr == NUL && end_col < MAXCOL && end_col > ci.ptr - line) {
    571    pos->col = (colnr_T)(ci.ptr - line);
    572  }
    573 
    574  int head = char_size.head;
    575  int incr = char_size.width;
    576 
    577  if (start != NULL) {
    578    *start = vcol + head;
    579  }
    580 
    581  if (end != NULL) {
    582    *end = vcol + incr - 1;
    583  }
    584 
    585  if (cursor != NULL) {
    586    if (ci.chr.value == TAB
    587        && (State & MODE_NORMAL)
    588        && !wp->w_p_list
    589        && !virtual_active(wp)
    590        && !(VIsual_active && ((*p_sel == 'e') || ltoreq(*pos, VIsual)))) {
    591      // cursor at end
    592      *cursor = vcol + incr - 1;
    593    } else {
    594      vcol += virt_text_cursor_off(&csarg, on_NUL);
    595      // cursor at start
    596      *cursor = vcol + head;
    597    }
    598  }
    599 }
    600 
    601 /// Get virtual cursor column in the current window, pretending 'list' is off.
    602 ///
    603 /// @param posp
    604 ///
    605 /// @return The virtual cursor column.
    606 colnr_T getvcol_nolist(pos_T *posp)
    607 {
    608  int list_save = curwin->w_p_list;
    609  colnr_T vcol;
    610 
    611  curwin->w_p_list = false;
    612  if (posp->coladd) {
    613    getvvcol(curwin, posp, NULL, &vcol, NULL);
    614  } else {
    615    getvcol(curwin, posp, NULL, &vcol, NULL);
    616  }
    617  curwin->w_p_list = list_save;
    618  return vcol;
    619 }
    620 
    621 /// Get virtual column in virtual mode.
    622 ///
    623 /// @param wp
    624 /// @param pos
    625 /// @param start
    626 /// @param cursor
    627 /// @param end
    628 void getvvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end)
    629 {
    630  colnr_T col;
    631 
    632  if (virtual_active(wp)) {
    633    // For virtual mode, only want one value
    634    getvcol(wp, pos, &col, NULL, NULL);
    635 
    636    colnr_T coladd = pos->coladd;
    637    colnr_T endadd = 0;
    638 
    639    // Cannot put the cursor on part of a wide character.
    640    char *ptr = ml_get_buf(wp->w_buffer, pos->lnum);
    641 
    642    if (pos->col < ml_get_buf_len(wp->w_buffer, pos->lnum)) {
    643      int c = utf_ptr2char(ptr + pos->col);
    644      if ((c != TAB) && vim_isprintc(c)) {
    645        endadd = (colnr_T)(ptr2cells(ptr + pos->col) - 1);
    646        if (coladd > endadd) {
    647          // past end of line
    648          endadd = 0;
    649        } else {
    650          coladd = 0;
    651        }
    652      }
    653    }
    654    col += coladd;
    655 
    656    if (start != NULL) {
    657      *start = col;
    658    }
    659 
    660    if (cursor != NULL) {
    661      *cursor = col;
    662    }
    663 
    664    if (end != NULL) {
    665      *end = col + endadd;
    666    }
    667  } else {
    668    getvcol(wp, pos, start, cursor, end);
    669  }
    670 }
    671 
    672 /// Get the leftmost and rightmost virtual column of pos1 and pos2.
    673 /// Used for Visual block mode.
    674 ///
    675 /// @param wp
    676 /// @param pos1
    677 /// @param pos2
    678 /// @param left
    679 /// @param right
    680 void getvcols(win_T *wp, pos_T *pos1, pos_T *pos2, colnr_T *left, colnr_T *right)
    681 {
    682  colnr_T from1;
    683  colnr_T from2;
    684  colnr_T to1;
    685  colnr_T to2;
    686 
    687  if (lt(*pos1, *pos2)) {
    688    getvvcol(wp, pos1, &from1, NULL, &to1);
    689    getvvcol(wp, pos2, &from2, NULL, &to2);
    690  } else {
    691    getvvcol(wp, pos2, &from1, NULL, &to1);
    692    getvvcol(wp, pos1, &from2, NULL, &to2);
    693  }
    694 
    695  if (from2 < from1) {
    696    *left = from2;
    697  } else {
    698    *left = from1;
    699  }
    700 
    701  if (to2 > to1) {
    702    if ((*p_sel == 'e') && (from2 - 1 >= to1)) {
    703      *right = from2 - 1;
    704    } else {
    705      *right = to2;
    706    }
    707  } else {
    708    *right = to1;
    709  }
    710 }
    711 
    712 /// Functions calculating vertical size of text when displayed inside a window.
    713 /// Calls horizontal size functions defined above.
    714 
    715 /// Check if there may be filler lines anywhere in window "wp".
    716 bool win_may_fill(win_T *wp)
    717 {
    718  return ((wp->w_p_diff && diffopt_filler())
    719          || buf_meta_total(wp->w_buffer, kMTMetaLines));
    720 }
    721 
    722 /// Return the number of filler lines above "lnum".
    723 ///
    724 /// @param wp
    725 /// @param lnum
    726 ///
    727 /// @return Number of filler lines above lnum
    728 int win_get_fill(win_T *wp, linenr_T lnum)
    729 {
    730  int virt_lines = decor_virt_lines(wp, lnum - 1, lnum, NULL, NULL, true);
    731 
    732  // be quick when there are no filler lines
    733  if (diffopt_filler()) {
    734    int n = diff_check_fill(wp, lnum);
    735 
    736    if (n > 0) {
    737      return virt_lines + n;
    738    }
    739  }
    740  return virt_lines;
    741 }
    742 
    743 /// Return the number of window lines occupied by buffer line "lnum".
    744 /// Includes any filler lines.
    745 ///
    746 /// @param limit_winheight  when true limit to window height
    747 int plines_win(win_T *wp, linenr_T lnum, bool limit_winheight)
    748 {
    749  // Check for filler lines above this buffer line.
    750  return plines_win_nofill(wp, lnum, limit_winheight) + win_get_fill(wp, lnum);
    751 }
    752 
    753 /// Return the number of window lines occupied by buffer line "lnum".
    754 /// Does not include filler lines.
    755 ///
    756 /// @param limit_winheight  when true limit to window height
    757 int plines_win_nofill(win_T *wp, linenr_T lnum, bool limit_winheight)
    758 {
    759  if (decor_conceal_line(wp, lnum - 1, false)) {
    760    return 0;
    761  }
    762 
    763  if (!wp->w_p_wrap) {
    764    return 1;
    765  }
    766 
    767  if (wp->w_view_width == 0) {
    768    return 1;
    769  }
    770 
    771  // Folded lines are handled just like an empty line.
    772  if (lineFolded(wp, lnum)) {
    773    return 1;
    774  }
    775 
    776  const int lines = plines_win_nofold(wp, lnum);
    777  if (limit_winheight && lines > wp->w_view_height) {
    778    return wp->w_view_height;
    779  }
    780  return lines;
    781 }
    782 
    783 /// Get number of window lines physical line "lnum" will occupy in window "wp".
    784 /// Does not care about folding, 'wrap' or filler lines.
    785 int plines_win_nofold(win_T *wp, linenr_T lnum)
    786 {
    787  char *s = ml_get_buf(wp->w_buffer, lnum);
    788  CharsizeArg csarg;
    789  CSType const cstype = init_charsize_arg(&csarg, wp, lnum, s);
    790  if (*s == NUL && csarg.virt_row < 0) {
    791    return 1;  // be quick for an empty line
    792  }
    793 
    794  int64_t col;
    795  if (cstype == kCharsizeFast) {
    796    col = linesize_fast(&csarg, 0, MAXCOL);
    797  } else {
    798    col = linesize_regular(&csarg, 0, MAXCOL);
    799  }
    800 
    801  // If list mode is on, then the '$' at the end of the line may take up one
    802  // extra column.
    803  if (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL) {
    804    col += 1;
    805  }
    806 
    807  // Add column offset for 'number', 'relativenumber' and 'foldcolumn'.
    808  int width = wp->w_view_width - win_col_off(wp);
    809  if (width <= 0) {
    810    return 32000;  // bigger than the number of screen lines
    811  }
    812  if (col <= width) {
    813    return 1;
    814  }
    815  col -= width;
    816  width += win_col_off2(wp);
    817  const int64_t lines = (col + (width - 1)) / width + 1;
    818  return (lines > 0 && lines <= INT_MAX) ? (int)lines : INT_MAX;
    819 }
    820 
    821 /// Like plines_win(), but only reports the number of physical screen lines
    822 /// used from the start of the line to the given column number.
    823 int plines_win_col(win_T *wp, linenr_T lnum, long column)
    824 {
    825  // Check for filler lines above this buffer line.
    826  int lines = win_get_fill(wp, lnum);
    827 
    828  if (!wp->w_p_wrap) {
    829    return lines + 1;
    830  }
    831 
    832  if (wp->w_view_width == 0) {
    833    return lines + 1;
    834  }
    835 
    836  char *line = ml_get_buf(wp->w_buffer, lnum);
    837 
    838  CharsizeArg csarg;
    839  CSType const cstype = init_charsize_arg(&csarg, wp, lnum, line);
    840 
    841  colnr_T vcol = 0;
    842  StrCharInfo ci = utf_ptr2StrCharInfo(line);
    843  if (cstype == kCharsizeFast) {
    844    bool const use_tabstop = csarg.use_tabstop;
    845    while (*ci.ptr != NUL && --column >= 0) {
    846      vcol += charsize_fast_impl(wp, ci.ptr, use_tabstop, vcol, ci.chr.value).width;
    847      ci = utfc_next(ci);
    848    }
    849  } else {
    850    while (*ci.ptr != NUL && --column >= 0) {
    851      vcol += charsize_regular(&csarg, ci.ptr, vcol, ci.chr.value).width;
    852      ci = utfc_next(ci);
    853    }
    854  }
    855 
    856  // If current char is a TAB, and the TAB is not displayed as ^I, and we're not
    857  // in MODE_INSERT state, then col must be adjusted so that it represents the
    858  // last screen position of the TAB.  This only fixes an error when the TAB
    859  // wraps from one screen line to the next (when 'columns' is not a multiple
    860  // of 'ts') -- webb.
    861  colnr_T col = vcol;
    862  if (ci.chr.value == TAB && (State & MODE_NORMAL) && csarg.use_tabstop) {
    863    col += win_charsize(cstype, col, ci.ptr, ci.chr.value, &csarg).width - 1;
    864  }
    865 
    866  // Add column offset for 'number', 'relativenumber', 'foldcolumn', etc.
    867  int width = wp->w_view_width - win_col_off(wp);
    868  if (width <= 0) {
    869    return 9999;
    870  }
    871 
    872  lines += 1;
    873  if (col > width) {
    874    lines += (col - width) / (width + win_col_off2(wp)) + 1;
    875  }
    876  return lines;
    877 }
    878 
    879 /// Get the number of screen lines buffer line "lnum" will take in window "wp".
    880 /// This takes care of both folds and topfill.
    881 ///
    882 /// XXX: Because of topfill, this only makes sense when lnum >= wp->w_topline.
    883 ///
    884 /// @param[in]  wp               window the line is in
    885 /// @param[in]  lnum             line number
    886 /// @param[out] nextp            if not NULL, the last line of a fold
    887 /// @param[out] foldedp          if not NULL, whether lnum is on a fold
    888 /// @param[in]  cache            whether to use the window's cache for folds
    889 /// @param[in]  limit_winheight  when true limit to window height
    890 ///
    891 /// @return the total number of screen lines
    892 int plines_win_full(win_T *wp, linenr_T lnum, linenr_T *const nextp, bool *const foldedp,
    893                    const bool cache, const bool limit_winheight)
    894 {
    895  bool folded = hasFoldingWin(wp, lnum, &lnum, nextp, cache, NULL);
    896  if (foldedp != NULL) {
    897    *foldedp = folded;
    898  }
    899 
    900  int filler_lines = lnum == wp->w_topline ? wp->w_topfill : win_get_fill(wp, lnum);
    901 
    902  if (decor_conceal_line(wp, lnum - 1, false)) {
    903    return filler_lines;
    904  }
    905 
    906  return (folded ? 1 : plines_win_nofill(wp, lnum, limit_winheight)) + filler_lines;
    907 }
    908 
    909 /// Return number of window lines a physical line range will occupy in window "wp".
    910 /// Takes into account folding, 'wrap', topfill and filler lines beyond the end of the buffer.
    911 ///
    912 /// XXX: Because of topfill, this only makes sense when first >= wp->w_topline.
    913 ///
    914 /// @param first  first line number
    915 /// @param last   last line number
    916 /// @param max    number of lines to limit the height to
    917 ///
    918 /// @see win_text_height
    919 int plines_m_win(win_T *wp, linenr_T first, linenr_T last, int max)
    920 {
    921  int count = 0;
    922 
    923  while (first <= last && count < max) {
    924    linenr_T next = first;
    925    count += plines_win_full(wp, first, &next, NULL, false, false);
    926    first = next + 1;
    927  }
    928  if (first == wp->w_buffer->b_ml.ml_line_count + 1) {
    929    count += win_get_fill(wp, first);
    930  }
    931  return MIN(max, count);
    932 }
    933 
    934 /// Return total number of physical and filler lines in a physical line range.
    935 /// Doesn't treat a fold as a single line or consider a wrapped line multiple lines,
    936 /// unlike plines_m_win() or win_text_height().
    937 ///
    938 /// Mainly used for calculating scrolling offsets.
    939 int plines_m_win_fill(win_T *wp, linenr_T first, linenr_T last)
    940 {
    941  int count = last - first + 1 + decor_virt_lines(wp, first - 1, last, NULL, NULL, false);
    942 
    943  if (diffopt_filler()) {
    944    for (int lnum = first; lnum <= last; lnum++) {
    945      // Note: this also considers folds (no filler lines inside folds).
    946      int n = diff_check_fill(wp, lnum);
    947      count += MAX(n, 0);
    948    }
    949  }
    950 
    951  return MAX(count, 0);
    952 }
    953 
    954 /// Get the number of screen lines a range of text will take in window "wp".
    955 ///
    956 /// @param[in] start_lnum    Starting line number, 1-based inclusive.
    957 /// @param[in] start_vcol    >= 0: Starting virtual column index on "start_lnum",
    958 ///                                0-based inclusive, rounded down to full screen lines.
    959 ///                          < 0:  Count a full "start_lnum", including filler lines above.
    960 /// @param[in,out] end_lnum  Ending line number, 1-based inclusive. Set to last line for
    961 ///                          which the height is calculated (smaller if "max" is reached).
    962 /// @param[in,out] end_vcol  >= 0: Ending virtual column index on "end_lnum",
    963 ///                                0-based exclusive, rounded up to full screen lines.
    964 ///                          < 0:  Count a full "end_lnum", not including filler lines below.
    965 ///                          Set to the number of columns in "end_lnum" to reach "max".
    966 /// @param[in] max           Don't calculate the height for lines beyond the line where "max"
    967 ///                          height is reached.
    968 /// @param[out] fill         If not NULL, set to the number of filler lines in the range.
    969 int64_t win_text_height(win_T *const wp, const linenr_T start_lnum, const int64_t start_vcol,
    970                        linenr_T *const end_lnum, int64_t *const end_vcol, int64_t *const fill,
    971                        int64_t const max)
    972 {
    973  int width1 = wp->w_view_width - win_col_off(wp);
    974  int width2 = width1 + win_col_off2(wp);
    975  width1 = MAX(width1, 0);
    976  width2 = MAX(width2, 0);
    977  int64_t height_sum_fill = 0;
    978  int64_t height_cur_nofill = 0;
    979  int64_t height_sum_nofill = 0;
    980  linenr_T lnum = start_lnum;
    981  linenr_T cur_lnum = lnum;
    982  bool cur_folded = false;
    983 
    984  if (start_vcol >= 0) {
    985    linenr_T lnum_next = lnum;
    986    cur_folded = hasFolding(wp, lnum, &lnum, &lnum_next);
    987    height_cur_nofill = plines_win_nofill(wp, lnum, false);
    988    height_sum_nofill += height_cur_nofill;
    989    const int64_t row_off = (start_vcol < width1 || width2 <= 0)
    990                            ? 0
    991                            : 1 + (start_vcol - width1) / width2;
    992    height_sum_nofill -= MIN(row_off, height_cur_nofill);
    993    lnum = lnum_next + 1;
    994  }
    995 
    996  while (lnum <= *end_lnum && height_sum_nofill + height_sum_fill < max) {
    997    linenr_T lnum_next = lnum;
    998    cur_folded = hasFolding(wp, lnum, &lnum, &lnum_next);
    999    height_sum_fill += win_get_fill(wp, lnum);
   1000    height_cur_nofill = plines_win_nofill(wp, lnum, false);
   1001    height_sum_nofill += height_cur_nofill;
   1002    cur_lnum = lnum;
   1003    lnum = lnum_next + 1;
   1004  }
   1005 
   1006  int64_t vcol_end = *end_vcol;
   1007  bool use_vcol = vcol_end >= 0 && lnum > *end_lnum;
   1008  if (use_vcol) {
   1009    height_sum_nofill -= height_cur_nofill;
   1010    const int64_t row_off = vcol_end == 0
   1011                            ? 0
   1012                            : (vcol_end <= width1 || width2 <= 0)
   1013                            ? 1
   1014                            : 1 + (vcol_end - width1 + width2 - 1) / width2;
   1015    height_sum_nofill += MIN(row_off, height_cur_nofill);
   1016  }
   1017 
   1018  if (cur_folded) {
   1019    vcol_end = 0;
   1020  } else {
   1021    int linesize = linetabsize_eol(wp, cur_lnum);
   1022    vcol_end = MIN(use_vcol ? vcol_end : INT64_MAX, linesize);
   1023  }
   1024 
   1025  int64_t overflow = height_sum_nofill + height_sum_fill - max;
   1026  if (overflow > 0 && width2 > 0 && vcol_end > width2) {
   1027    vcol_end -= (vcol_end - width1) % width2 + (overflow - 1) * width2;
   1028  }
   1029 
   1030  *end_lnum = cur_lnum;
   1031  *end_vcol = vcol_end;
   1032  if (fill != NULL) {
   1033    *fill = height_sum_fill;
   1034  }
   1035  return height_sum_fill + height_sum_nofill;
   1036 }