neovim

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

ops.c (128392B)


      1 // ops.c: implementation of various operators: op_shift, op_delete, op_tilde,
      2 //        op_change, op_yank, do_join
      3 
      4 #include <assert.h>
      5 #include <ctype.h>
      6 #include <inttypes.h>
      7 #include <limits.h>
      8 #include <stdbool.h>
      9 #include <stdio.h>
     10 #include <stdlib.h>
     11 #include <string.h>
     12 #include <uv.h>
     13 
     14 #include "nvim/api/private/defs.h"
     15 #include "nvim/api/private/helpers.h"
     16 #include "nvim/ascii_defs.h"
     17 #include "nvim/assert_defs.h"
     18 #include "nvim/autocmd.h"
     19 #include "nvim/autocmd_defs.h"
     20 #include "nvim/buffer.h"
     21 #include "nvim/buffer_defs.h"
     22 #include "nvim/buffer_updates.h"
     23 #include "nvim/change.h"
     24 #include "nvim/charset.h"
     25 #include "nvim/clipboard.h"
     26 #include "nvim/cursor.h"
     27 #include "nvim/drawscreen.h"
     28 #include "nvim/edit.h"
     29 #include "nvim/errors.h"
     30 #include "nvim/eval.h"
     31 #include "nvim/eval/typval.h"
     32 #include "nvim/eval/typval_defs.h"
     33 #include "nvim/ex_cmds2.h"
     34 #include "nvim/ex_cmds_defs.h"
     35 #include "nvim/ex_getln.h"
     36 #include "nvim/extmark.h"
     37 #include "nvim/file_search.h"
     38 #include "nvim/fold.h"
     39 #include "nvim/garray.h"
     40 #include "nvim/garray_defs.h"
     41 #include "nvim/getchar.h"
     42 #include "nvim/getchar_defs.h"
     43 #include "nvim/gettext_defs.h"
     44 #include "nvim/globals.h"
     45 #include "nvim/highlight_defs.h"
     46 #include "nvim/indent.h"
     47 #include "nvim/indent_c.h"
     48 #include "nvim/keycodes.h"
     49 #include "nvim/macros_defs.h"
     50 #include "nvim/mark.h"
     51 #include "nvim/mark_defs.h"
     52 #include "nvim/math.h"
     53 #include "nvim/mbyte.h"
     54 #include "nvim/mbyte_defs.h"
     55 #include "nvim/memline.h"
     56 #include "nvim/memline_defs.h"
     57 #include "nvim/memory.h"
     58 #include "nvim/message.h"
     59 #include "nvim/mouse.h"
     60 #include "nvim/move.h"
     61 #include "nvim/normal.h"
     62 #include "nvim/ops.h"
     63 #include "nvim/option.h"
     64 #include "nvim/option_defs.h"
     65 #include "nvim/option_vars.h"
     66 #include "nvim/os/input.h"
     67 #include "nvim/os/time.h"
     68 #include "nvim/plines.h"
     69 #include "nvim/register.h"
     70 #include "nvim/search.h"
     71 #include "nvim/state.h"
     72 #include "nvim/state_defs.h"
     73 #include "nvim/strings.h"
     74 #include "nvim/terminal.h"
     75 #include "nvim/textformat.h"
     76 #include "nvim/types_defs.h"
     77 #include "nvim/ui.h"
     78 #include "nvim/ui_defs.h"
     79 #include "nvim/undo.h"
     80 #include "nvim/vim_defs.h"
     81 
     82 #include "ops.c.generated.h"
     83 
     84 // Flags for third item in "opchars".
     85 #define OPF_LINES  1  // operator always works on lines
     86 #define OPF_CHANGE 2  // operator changes text
     87 
     88 /// The names of operators.
     89 /// IMPORTANT: Index must correspond with defines in ops.h!!!
     90 /// The third field indicates whether the operator always works on lines.
     91 static const char opchars[][3] = {
     92  { NUL, NUL, 0 },                       // OP_NOP
     93  { 'd', NUL, OPF_CHANGE },              // OP_DELETE
     94  { 'y', NUL, 0 },                       // OP_YANK
     95  { 'c', NUL, OPF_CHANGE },              // OP_CHANGE
     96  { '<', NUL, OPF_LINES | OPF_CHANGE },  // OP_LSHIFT
     97  { '>', NUL, OPF_LINES | OPF_CHANGE },  // OP_RSHIFT
     98  { '!', NUL, OPF_LINES | OPF_CHANGE },  // OP_FILTER
     99  { 'g', '~', OPF_CHANGE },              // OP_TILDE
    100  { '=', NUL, OPF_LINES | OPF_CHANGE },  // OP_INDENT
    101  { 'g', 'q', OPF_LINES | OPF_CHANGE },  // OP_FORMAT
    102  { ':', NUL, OPF_LINES },               // OP_COLON
    103  { 'g', 'U', OPF_CHANGE },              // OP_UPPER
    104  { 'g', 'u', OPF_CHANGE },              // OP_LOWER
    105  { 'J', NUL, OPF_LINES | OPF_CHANGE },  // DO_JOIN
    106  { 'g', 'J', OPF_LINES | OPF_CHANGE },  // DO_JOIN_NS
    107  { 'g', '?', OPF_CHANGE },              // OP_ROT13
    108  { 'r', NUL, OPF_CHANGE },              // OP_REPLACE
    109  { 'I', NUL, OPF_CHANGE },              // OP_INSERT
    110  { 'A', NUL, OPF_CHANGE },              // OP_APPEND
    111  { 'z', 'f', 0         },               // OP_FOLD
    112  { 'z', 'o', OPF_LINES },               // OP_FOLDOPEN
    113  { 'z', 'O', OPF_LINES },               // OP_FOLDOPENREC
    114  { 'z', 'c', OPF_LINES },               // OP_FOLDCLOSE
    115  { 'z', 'C', OPF_LINES },               // OP_FOLDCLOSEREC
    116  { 'z', 'd', OPF_LINES },               // OP_FOLDDEL
    117  { 'z', 'D', OPF_LINES },               // OP_FOLDDELREC
    118  { 'g', 'w', OPF_LINES | OPF_CHANGE },  // OP_FORMAT2
    119  { 'g', '@', OPF_CHANGE },              // OP_FUNCTION
    120  { Ctrl_A, NUL, OPF_CHANGE },           // OP_NR_ADD
    121  { Ctrl_X, NUL, OPF_CHANGE },           // OP_NR_SUB
    122 };
    123 
    124 /// Translate a command name into an operator type.
    125 /// Must only be called with a valid operator name!
    126 int get_op_type(int char1, int char2)
    127 {
    128  int i;
    129 
    130  if (char1 == 'r') {
    131    // ignore second character
    132    return OP_REPLACE;
    133  }
    134  if (char1 == '~') {
    135    // when tilde is an operator
    136    return OP_TILDE;
    137  }
    138  if (char1 == 'g' && char2 == Ctrl_A) {
    139    // add
    140    return OP_NR_ADD;
    141  }
    142  if (char1 == 'g' && char2 == Ctrl_X) {
    143    // subtract
    144    return OP_NR_SUB;
    145  }
    146  if (char1 == 'z' && char2 == 'y') {  // OP_YANK
    147    return OP_YANK;
    148  }
    149  for (i = 0;; i++) {
    150    if (opchars[i][0] == char1 && opchars[i][1] == char2) {
    151      break;
    152    }
    153    if (i == (int)(ARRAY_SIZE(opchars) - 1)) {
    154      internal_error("get_op_type()");
    155      break;
    156    }
    157  }
    158  return i;
    159 }
    160 
    161 /// @return  true if operator "op" always works on whole lines.
    162 int op_on_lines(int op)
    163 {
    164  return opchars[op][2] & OPF_LINES;
    165 }
    166 
    167 /// @return  true if operator "op" changes text.
    168 int op_is_change(int op)
    169 {
    170  return opchars[op][2] & OPF_CHANGE;
    171 }
    172 
    173 /// Get first operator command character.
    174 ///
    175 /// @return  'g' or 'z' if there is another command character.
    176 int get_op_char(int optype)
    177 {
    178  return opchars[optype][0];
    179 }
    180 
    181 /// Get second operator command character.
    182 int get_extra_op_char(int optype)
    183 {
    184  return opchars[optype][1];
    185 }
    186 
    187 /// handle a shift operation
    188 void op_shift(oparg_T *oap, bool curs_top, int amount)
    189 {
    190  int block_col = 0;
    191 
    192  if (u_save((linenr_T)(oap->start.lnum - 1),
    193             (linenr_T)(oap->end.lnum + 1)) == FAIL) {
    194    return;
    195  }
    196 
    197  if (oap->motion_type == kMTBlockWise) {
    198    block_col = curwin->w_cursor.col;
    199  }
    200 
    201  for (int i = oap->line_count - 1; i >= 0; i--) {
    202    int first_char = (uint8_t)(*get_cursor_line_ptr());
    203    if (first_char == NUL) {  // empty line
    204      curwin->w_cursor.col = 0;
    205    } else if (oap->motion_type == kMTBlockWise) {
    206      shift_block(oap, amount);
    207    } else if (first_char != '#' || !preprocs_left()) {
    208      // Move the line right if it doesn't start with '#', 'smartindent'
    209      // isn't set or 'cindent' isn't set or '#' isn't in 'cino'.
    210      shift_line(oap->op_type == OP_LSHIFT, p_sr, amount, false);
    211    }
    212    curwin->w_cursor.lnum++;
    213  }
    214 
    215  if (oap->motion_type == kMTBlockWise) {
    216    curwin->w_cursor.lnum = oap->start.lnum;
    217    curwin->w_cursor.col = block_col;
    218  } else if (curs_top) {  // put cursor on first line, for ">>"
    219    curwin->w_cursor.lnum = oap->start.lnum;
    220    beginline(BL_SOL | BL_FIX);       // shift_line() may have set cursor.col
    221  } else {
    222    curwin->w_cursor.lnum--;            // put cursor on last line, for ":>"
    223  }
    224  // The cursor line is not in a closed fold
    225  foldOpenCursor();
    226 
    227  if (oap->line_count > p_report) {
    228    char *op = oap->op_type == OP_RSHIFT ? ">" : "<";
    229 
    230    char *msg_line_single = NGETTEXT("%" PRId64 " line %sed %d time",
    231                                     "%" PRId64 " line %sed %d times", amount);
    232    char *msg_line_plural = NGETTEXT("%" PRId64 " lines %sed %d time",
    233                                     "%" PRId64 " lines %sed %d times", amount);
    234    vim_snprintf(IObuff, IOSIZE,
    235                 NGETTEXT(msg_line_single, msg_line_plural, oap->line_count),
    236                 (int64_t)oap->line_count, op, amount);
    237    msg_keep(IObuff, 0, true, false);
    238  }
    239 
    240  if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
    241    // Set "'[" and "']" marks.
    242    curbuf->b_op_start = oap->start;
    243    curbuf->b_op_end.lnum = oap->end.lnum;
    244    curbuf->b_op_end.col = ml_get_len(oap->end.lnum);
    245    if (curbuf->b_op_end.col > 0) {
    246      curbuf->b_op_end.col--;
    247    }
    248  }
    249 
    250  changed_lines(curbuf, oap->start.lnum, 0, oap->end.lnum + 1, 0, true);
    251 }
    252 
    253 /// Return the tabstop width at the index of the variable tabstop array.  If an
    254 /// index greater than the length of the array is given, the last tabstop width
    255 /// in the array is returned.
    256 static int get_vts(const int *vts_array, int index)
    257 {
    258  int ts;
    259 
    260  if (index < 1) {
    261    ts = 0;
    262  } else if (index <= vts_array[0]) {
    263    ts = vts_array[index];
    264  } else {
    265    ts = vts_array[vts_array[0]];
    266  }
    267 
    268  return ts;
    269 }
    270 
    271 /// Return the sum of all the tabstops through the index-th.
    272 static int get_vts_sum(const int *vts_array, int index)
    273 {
    274  int sum = 0;
    275  int i;
    276 
    277  // Perform the summation for indices within the actual array.
    278  for (i = 1; i <= index && i <= vts_array[0]; i++) {
    279    sum += vts_array[i];
    280  }
    281 
    282  // Add tabstops whose indices exceed the actual array.
    283  if (i <= index) {
    284    sum += vts_array[vts_array[0]] * (index - vts_array[0]);
    285  }
    286 
    287  return sum;
    288 }
    289 
    290 /// @param left    true if shift is to the left
    291 /// @param count   true if new indent is to be to a tabstop
    292 /// @param amount  number of shifts
    293 static int64_t get_new_sw_indent(bool left, bool round, int64_t amount, int64_t sw_val)
    294 {
    295  int64_t count = get_indent();  // get current indent
    296 
    297  if (round) {  // round off indent
    298    int64_t i = trim_to_int(count / sw_val);  // number of 'shiftwidth' rounded down
    299    int64_t j = trim_to_int(count % sw_val);  // extra spaces
    300    if (j && left) {  // first remove extra spaces
    301      amount--;
    302    }
    303    if (left) {
    304      i = MAX(i - amount, 0);
    305    } else {
    306      i += amount;
    307    }
    308    count = i * sw_val;
    309  } else {  // original vi indent
    310    if (left) {
    311      count = MAX(count - sw_val * amount, 0);
    312    } else {
    313      count += sw_val * amount;
    314    }
    315  }
    316 
    317  return count;
    318 }
    319 
    320 /// @param left    true if shift is to the left
    321 /// @param count   true if new indent is to be to a tabstop
    322 /// @param amount  number of shifts
    323 static int64_t get_new_vts_indent(bool left, bool round, int amount, int *vts_array)
    324 {
    325  int64_t indent = get_indent();
    326  int vtsi = 0;
    327  int vts_indent = 0;
    328  int ts = 0;         // Silence uninitialized variable warning.
    329 
    330  // Find the tabstop at or to the left of the current indent.
    331  while (vts_indent <= indent) {
    332    vtsi++;
    333    ts = get_vts(vts_array, vtsi);
    334    vts_indent += ts;
    335  }
    336  vts_indent -= ts;
    337  vtsi--;
    338 
    339  // Extra indent spaces to the right of the tabstop
    340  int64_t offset = indent - vts_indent;
    341 
    342  if (round) {
    343    if (left) {
    344      if (offset == 0) {
    345        indent = get_vts_sum(vts_array, vtsi - amount);
    346      } else {
    347        indent = get_vts_sum(vts_array, vtsi - (amount - 1));
    348      }
    349    } else {
    350      indent = get_vts_sum(vts_array, vtsi + amount);
    351    }
    352  } else {
    353    if (left) {
    354      if (amount > vtsi) {
    355        indent = 0;
    356      } else {
    357        indent = get_vts_sum(vts_array, vtsi - amount) + offset;
    358      }
    359    } else {
    360      indent = get_vts_sum(vts_array, vtsi + amount) + offset;
    361    }
    362  }
    363 
    364  return indent;
    365 }
    366 
    367 /// Shift the current line 'amount' shiftwidth(s) left (if 'left' is true) or
    368 /// right.
    369 ///
    370 /// The rules for choosing a shiftwidth are:  If 'shiftwidth' is non-zero, use
    371 /// 'shiftwidth'; else if 'vartabstop' is not empty, use 'vartabstop'; else use
    372 /// 'tabstop'.  The Vim documentation says nothing about 'softtabstop' or
    373 /// 'varsofttabstop' affecting the shiftwidth, and neither affects the
    374 /// shiftwidth in current versions of Vim, so they are not considered here.
    375 ///
    376 /// @param left                true if shift is to the left
    377 /// @param count               true if new indent is to be to a tabstop
    378 /// @param amount              number of shifts
    379 /// @param call_changed_bytes  call changed_bytes()
    380 void shift_line(bool left, bool round, int amount, int call_changed_bytes)
    381 {
    382  int64_t count;
    383  int64_t sw_val = curbuf->b_p_sw;
    384  int64_t ts_val = curbuf->b_p_ts;
    385  int *vts_array = curbuf->b_p_vts_array;
    386 
    387  if (sw_val != 0) {
    388    // 'shiftwidth' is not zero; use it as the shift size.
    389    count = get_new_sw_indent(left, round, amount, sw_val);
    390  } else if ((vts_array == NULL) || (vts_array[0] == 0)) {
    391    // 'shiftwidth' is zero and 'vartabstop' is empty; use 'tabstop' as the
    392    // shift size.
    393    count = get_new_sw_indent(left, round, amount, ts_val);
    394  } else {
    395    // 'shiftwidth' is zero and 'vartabstop' is defined; use 'vartabstop'
    396    // to determine the new indent.
    397    count = get_new_vts_indent(left, round, amount, vts_array);
    398  }
    399 
    400  // Set new indent
    401  if (State & VREPLACE_FLAG) {
    402    change_indent(INDENT_SET, trim_to_int(count), false, call_changed_bytes);
    403  } else {
    404    set_indent(trim_to_int(count), call_changed_bytes ? SIN_CHANGED : 0);
    405  }
    406 }
    407 
    408 /// Shift one line of the current block one shiftwidth right or left.
    409 /// Leaves cursor on first character in block.
    410 static void shift_block(oparg_T *oap, int amount)
    411 {
    412  const bool left = (oap->op_type == OP_LSHIFT);
    413  const int oldstate = State;
    414  char *newp;
    415  const int oldcol = curwin->w_cursor.col;
    416  const int sw_val = get_sw_value_indent(curbuf, left);
    417  const int ts_val = (int)curbuf->b_p_ts;
    418  struct block_def bd;
    419  int incr;
    420  const int old_p_ri = p_ri;
    421 
    422  p_ri = 0;                     // don't want revins in indent
    423 
    424  State = MODE_INSERT;          // don't want MODE_REPLACE for State
    425  block_prep(oap, &bd, curwin->w_cursor.lnum, true);
    426  if (bd.is_short) {
    427    return;
    428  }
    429 
    430  // total is number of screen columns to be inserted/removed
    431  int total = (int)((unsigned)amount * (unsigned)sw_val);
    432  if ((total / sw_val) != amount) {
    433    return;   // multiplication overflow
    434  }
    435 
    436  char *const oldp = get_cursor_line_ptr();
    437  const int old_line_len = get_cursor_line_len();
    438 
    439  int startcol, oldlen, newlen;
    440 
    441  if (!left) {
    442    //  1. Get start vcol
    443    //  2. Total ws vcols
    444    //  3. Divvy into TABs & spp
    445    //  4. Construct new string
    446    total += bd.pre_whitesp;    // all virtual WS up to & incl a split TAB
    447    colnr_T ws_vcol = bd.start_vcol - bd.pre_whitesp;
    448    char *old_textstart = bd.textstart;
    449    if (bd.startspaces) {
    450      if (utfc_ptr2len(bd.textstart) == 1) {
    451        bd.textstart++;
    452      } else {
    453        ws_vcol = 0;
    454        bd.startspaces = 0;
    455      }
    456    }
    457 
    458    // TODO(vim): is passing bd.textstart for start of the line OK?
    459    CharsizeArg csarg;
    460    CSType cstype = init_charsize_arg(&csarg, curwin, curwin->w_cursor.lnum, bd.textstart);
    461    StrCharInfo ci = utf_ptr2StrCharInfo(bd.textstart);
    462    int vcol = bd.start_vcol;
    463    while (ascii_iswhite(ci.chr.value)) {
    464      incr = win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &csarg).width;
    465      ci = utfc_next(ci);
    466      total += incr;
    467      vcol += incr;
    468    }
    469    bd.textstart = ci.ptr;
    470    bd.start_vcol = vcol;
    471 
    472    int tabs = 0;
    473    int spaces = 0;
    474    // OK, now total=all the VWS reqd, and textstart points at the 1st
    475    // non-ws char in the block.
    476    if (!curbuf->b_p_et) {
    477      tabstop_fromto(ws_vcol, ws_vcol + total,
    478                     ts_val, curbuf->b_p_vts_array, &tabs, &spaces);
    479    } else {
    480      spaces = total;
    481    }
    482 
    483    // if we're splitting a TAB, allow for it
    484    const int col_pre = bd.pre_whitesp_c - (bd.startspaces != 0);
    485    bd.textcol -= col_pre;
    486 
    487    const int new_line_len  // the length of the line after the block shift
    488      = bd.textcol + tabs + spaces + (old_line_len - (int)(bd.textstart - oldp));
    489    newp = xmalloc((size_t)new_line_len + 1);
    490    memmove(newp, oldp, (size_t)bd.textcol);
    491    startcol = bd.textcol;
    492    oldlen = (int)(bd.textstart - old_textstart) + col_pre;
    493    newlen = tabs + spaces;
    494    memset(newp + bd.textcol, TAB, (size_t)tabs);
    495    memset(newp + bd.textcol + tabs, ' ', (size_t)spaces);
    496    STRCPY(newp + bd.textcol + tabs + spaces, bd.textstart);
    497    assert(newlen - oldlen == new_line_len - old_line_len);
    498  } else {  // left
    499    char *verbatim_copy_end;      // end of the part of the line which is
    500                                  // copied verbatim
    501    colnr_T verbatim_copy_width;  // the (displayed) width of this part
    502                                  // of line
    503    char *non_white = bd.textstart;
    504 
    505    // Firstly, let's find the first non-whitespace character that is
    506    // displayed after the block's start column and the character's column
    507    // number. Also, let's calculate the width of all the whitespace
    508    // characters that are displayed in the block and precede the searched
    509    // non-whitespace character.
    510 
    511    // If "bd.startspaces" is set, "bd.textstart" points to the character,
    512    // the part of which is displayed at the block's beginning. Let's start
    513    // searching from the next character.
    514    if (bd.startspaces) {
    515      MB_PTR_ADV(non_white);
    516    }
    517 
    518    // The character's column is in "bd.start_vcol".
    519    colnr_T non_white_col = bd.start_vcol;
    520 
    521    CharsizeArg csarg;
    522    CSType cstype = init_charsize_arg(&csarg, curwin, curwin->w_cursor.lnum, bd.textstart);
    523    while (ascii_iswhite(*non_white)) {
    524      incr = win_charsize(cstype, non_white_col, non_white, (uint8_t)(*non_white), &csarg).width;
    525      non_white_col += incr;
    526      non_white++;
    527    }
    528 
    529    const colnr_T block_space_width = non_white_col - oap->start_vcol;
    530    // We will shift by "total" or "block_space_width", whichever is less.
    531    const colnr_T shift_amount = MIN(block_space_width, total);
    532    // The column to which we will shift the text.
    533    const colnr_T destination_col = non_white_col - shift_amount;
    534 
    535    // Now let's find out how much of the beginning of the line we can
    536    // reuse without modification.
    537    verbatim_copy_end = bd.textstart;
    538    verbatim_copy_width = bd.start_vcol;
    539 
    540    // If "bd.startspaces" is set, "bd.textstart" points to the character
    541    // preceding the block. We have to subtract its width to obtain its
    542    // column number.
    543    if (bd.startspaces) {
    544      verbatim_copy_width -= bd.start_char_vcols;
    545    }
    546    cstype = init_charsize_arg(&csarg, curwin, 0, bd.textstart);
    547    StrCharInfo ci = utf_ptr2StrCharInfo(verbatim_copy_end);
    548    while (verbatim_copy_width < destination_col) {
    549      incr = win_charsize(cstype, verbatim_copy_width, ci.ptr, ci.chr.value, &csarg).width;
    550      if (verbatim_copy_width + incr > destination_col) {
    551        break;
    552      }
    553      verbatim_copy_width += incr;
    554      ci = utfc_next(ci);
    555    }
    556    verbatim_copy_end = ci.ptr;
    557 
    558    // If "destination_col" is different from the width of the initial
    559    // part of the line that will be copied, it means we encountered a tab
    560    // character, which we will have to partly replace with spaces.
    561    assert(destination_col - verbatim_copy_width >= 0);
    562    const int fill  // nr of spaces that replace a TAB
    563      = destination_col - verbatim_copy_width;
    564 
    565    assert(verbatim_copy_end - oldp >= 0);
    566    // length of string left of the shift position (ie the string not being shifted)
    567    const int fixedlen = (int)(verbatim_copy_end - oldp);
    568    // The replacement line will consist of:
    569    // - the beginning of the original line up to "verbatim_copy_end",
    570    // - "fill" number of spaces,
    571    // - the rest of the line, pointed to by non_white.
    572    const int new_line_len  // the length of the line after the block shift
    573      = fixedlen + fill + (old_line_len - (int)(non_white - oldp));
    574 
    575    newp = xmalloc((size_t)new_line_len + 1);
    576    startcol = fixedlen;
    577    oldlen = bd.textcol + (int)(non_white - bd.textstart) - fixedlen;
    578    newlen = fill;
    579    memmove(newp, oldp, (size_t)fixedlen);
    580    memset(newp + fixedlen, ' ', (size_t)fill);
    581    STRCPY(newp + fixedlen + fill, non_white);
    582    assert(newlen - oldlen == new_line_len - old_line_len);
    583  }
    584  // replace the line
    585  ml_replace(curwin->w_cursor.lnum, newp, false);
    586  changed_bytes(curwin->w_cursor.lnum, bd.textcol);
    587  extmark_splice_cols(curbuf, (int)curwin->w_cursor.lnum - 1, startcol,
    588                      oldlen, newlen,
    589                      kExtmarkUndo);
    590  State = oldstate;
    591  curwin->w_cursor.col = oldcol;
    592  p_ri = old_p_ri;
    593 }
    594 
    595 /// Insert string "s" (b_insert ? before : after) block :AKelly
    596 /// Caller must prepare for undo.
    597 static void block_insert(oparg_T *oap, const char *s, size_t slen, bool b_insert,
    598                         struct block_def *bdp)
    599 {
    600  int ts_val;
    601  int count = 0;                // extra spaces to replace a cut TAB
    602  int spaces = 0;               // non-zero if cutting a TAB
    603  colnr_T offset;               // pointer along new line
    604  char *newp, *oldp;            // new, old lines
    605  int oldstate = State;
    606  State = MODE_INSERT;          // don't want MODE_REPLACE for State
    607 
    608  for (linenr_T lnum = oap->start.lnum + 1; lnum <= oap->end.lnum; lnum++) {
    609    block_prep(oap, bdp, lnum, true);
    610    if (bdp->is_short && b_insert) {
    611      continue;  // OP_INSERT, line ends before block start
    612    }
    613 
    614    oldp = ml_get(lnum);
    615 
    616    if (b_insert) {
    617      ts_val = bdp->start_char_vcols;
    618      spaces = bdp->startspaces;
    619      if (spaces != 0) {
    620        count = ts_val - 1;         // we're cutting a TAB
    621      }
    622      offset = bdp->textcol;
    623    } else {  // append
    624      ts_val = bdp->end_char_vcols;
    625      if (!bdp->is_short) {     // spaces = padding after block
    626        spaces = (bdp->endspaces ? ts_val - bdp->endspaces : 0);
    627        if (spaces != 0) {
    628          count = ts_val - 1;           // we're cutting a TAB
    629        }
    630        offset = bdp->textcol + bdp->textlen - (spaces != 0);
    631      } else {  // spaces = padding to block edge
    632                // if $ used, just append to EOL (ie spaces==0)
    633        if (!bdp->is_MAX) {
    634          spaces = (oap->end_vcol - bdp->end_vcol) + 1;
    635        }
    636        count = spaces;
    637        offset = bdp->textcol + bdp->textlen;
    638      }
    639    }
    640 
    641    if (spaces > 0) {
    642      // avoid copying part of a multi-byte character
    643      offset -= utf_head_off(oldp, oldp + offset);
    644    }
    645    spaces = MAX(spaces, 0);  // can happen when the cursor was moved
    646 
    647    assert(count >= 0);
    648    // Make sure the allocated size matches what is actually copied below.
    649    newp = xmalloc((size_t)ml_get_len(lnum) + (size_t)spaces + slen
    650                   + (spaces > 0 && !bdp->is_short ? (size_t)(ts_val - spaces) : 0)
    651                   + (size_t)count + 1);
    652 
    653    // copy up to shifted part
    654    memmove(newp, oldp, (size_t)offset);
    655    oldp += offset;
    656    int startcol = offset;
    657 
    658    // insert pre-padding
    659    memset(newp + offset, ' ', (size_t)spaces);
    660 
    661    // copy the new text
    662    memmove(newp + offset + spaces, s, slen);
    663    offset += (int)slen;
    664 
    665    int skipped = 0;
    666    if (spaces > 0 && !bdp->is_short) {
    667      if (*oldp == TAB) {
    668        // insert post-padding
    669        memset(newp + offset + spaces, ' ', (size_t)(ts_val - spaces));
    670        // We're splitting a TAB, don't copy it.
    671        oldp++;
    672        // We allowed for that TAB, remember this now
    673        count++;
    674        skipped = 1;
    675      } else {
    676        // Not a TAB, no extra spaces
    677        count = spaces;
    678      }
    679    }
    680 
    681    if (spaces > 0) {
    682      offset += count;
    683    }
    684    STRCPY(newp + offset, oldp);
    685 
    686    ml_replace(lnum, newp, false);
    687    extmark_splice_cols(curbuf, (int)lnum - 1, startcol,
    688                        skipped, offset - startcol, kExtmarkUndo);
    689 
    690    if (lnum == oap->end.lnum) {
    691      // Set "']" mark to the end of the block instead of the end of
    692      // the insert in the first line.
    693      curbuf->b_op_end.lnum = oap->end.lnum;
    694      curbuf->b_op_end.col = offset;
    695      if (curbuf->b_visual.vi_end.coladd) {
    696        curbuf->b_visual.vi_end.col += curbuf->b_visual.vi_end.coladd;
    697        curbuf->b_visual.vi_end.coladd = 0;
    698      }
    699    }
    700  }   // for all lnum
    701 
    702  State = oldstate;
    703 
    704  // Only call changed_lines if we actually modified additional lines beyond the first
    705  // This matches the condition for the for loop above: start + 1 <= end
    706  if (oap->start.lnum < oap->end.lnum) {
    707    changed_lines(curbuf, oap->start.lnum + 1, 0, oap->end.lnum + 1, 0, true);
    708  }
    709 }
    710 
    711 /// Handle a delete operation.
    712 ///
    713 /// @return  FAIL if undo failed, OK otherwise.
    714 int op_delete(oparg_T *oap)
    715 {
    716  linenr_T lnum;
    717  struct block_def bd = { 0 };
    718  linenr_T old_lcount = curbuf->b_ml.ml_line_count;
    719 
    720  if (curbuf->b_ml.ml_flags & ML_EMPTY) {  // nothing to do
    721    return OK;
    722  }
    723 
    724  // Nothing to delete, return here. Do prepare undo, for op_change().
    725  if (oap->empty) {
    726    return u_save_cursor();
    727  }
    728 
    729  if (!MODIFIABLE(curbuf)) {
    730    emsg(_(e_modifiable));
    731    return FAIL;
    732  }
    733 
    734  if (VIsual_select && oap->is_VIsual) {
    735    // Use the register given with CTRL_R, defaults to zero
    736    oap->regname = VIsual_select_reg;
    737  }
    738 
    739  mb_adjust_opend(oap);
    740 
    741  // Imitate the strange Vi behaviour: If the delete spans more than one
    742  // line and motion_type == kMTCharWise and the result is a blank line, make the
    743  // delete linewise.  Don't do this for the change command or Visual mode.
    744  if (oap->motion_type == kMTCharWise
    745      && !oap->is_VIsual
    746      && oap->line_count > 1
    747      && oap->motion_force == NUL
    748      && oap->op_type == OP_DELETE) {
    749    char *ptr = ml_get(oap->end.lnum) + oap->end.col;
    750    if (*ptr != NUL) {
    751      ptr += oap->inclusive;
    752    }
    753    ptr = skipwhite(ptr);
    754    if (*ptr == NUL && inindent(0)) {
    755      oap->motion_type = kMTLineWise;
    756    }
    757  }
    758 
    759  // Check for trying to delete (e.g. "D") in an empty line.
    760  // Note: For the change operator it is ok.
    761  if (oap->motion_type != kMTLineWise
    762      && oap->line_count == 1
    763      && oap->op_type == OP_DELETE
    764      && *ml_get(oap->start.lnum) == NUL) {
    765    // It's an error to operate on an empty region, when 'E' included in
    766    // 'cpoptions' (Vi compatible).
    767    if (virtual_op) {
    768      // Virtual editing: Nothing gets deleted, but we set the '[ and ']
    769      // marks as if it happened.
    770      goto setmarks;
    771    }
    772    if (vim_strchr(p_cpo, CPO_EMPTYREGION) != NULL) {
    773      beep_flush();
    774    }
    775    return OK;
    776  }
    777 
    778  // Do a yank of whatever we're about to delete.
    779  // If a yank register was specified, put the deleted text into that
    780  // register.  For the black hole register '_' don't yank anything.
    781  if (oap->regname != '_') {
    782    yankreg_T *reg = NULL;
    783    bool did_yank = false;
    784    if (oap->regname != 0) {
    785      // check for read-only register
    786      if (!valid_yank_reg(oap->regname, true)) {
    787        beep_flush();
    788        return OK;
    789      }
    790      reg = get_yank_register(oap->regname, YREG_YANK);  // yank into specif'd reg
    791      op_yank_reg(oap, false, reg, is_append_register(oap->regname));  // yank without message
    792      did_yank = true;
    793    }
    794 
    795    // Put deleted text into register 1 and shift number registers if the
    796    // delete contains a line break, or when using a specific operator (Vi
    797    // compatible)
    798 
    799    if (oap->motion_type == kMTLineWise || oap->line_count > 1 || oap->use_reg_one) {
    800      shift_delete_registers(is_append_register(oap->regname));
    801      reg = get_y_register(1);
    802      op_yank_reg(oap, false, reg, false);
    803      did_yank = true;
    804    }
    805 
    806    // Yank into small delete register when no named register specified
    807    // and the delete is within one line.
    808    if (oap->regname == 0 && oap->motion_type != kMTLineWise
    809        && oap->line_count == 1) {
    810      reg = get_yank_register('-', YREG_YANK);
    811      op_yank_reg(oap, false, reg, false);
    812      did_yank = true;
    813    }
    814 
    815    if (did_yank || oap->regname == 0) {
    816      if (reg == NULL) {
    817        abort();
    818      }
    819      set_clipboard(oap->regname, reg);
    820      do_autocmd_textyankpost(oap, reg);
    821    }
    822  }
    823 
    824  // block mode delete
    825  if (oap->motion_type == kMTBlockWise) {
    826    if (u_save((linenr_T)(oap->start.lnum - 1),
    827               (linenr_T)(oap->end.lnum + 1)) == FAIL) {
    828      return FAIL;
    829    }
    830 
    831    for (lnum = curwin->w_cursor.lnum; lnum <= oap->end.lnum; lnum++) {
    832      block_prep(oap, &bd, lnum, true);
    833      if (bd.textlen == 0) {            // nothing to delete
    834        continue;
    835      }
    836 
    837      // Adjust cursor position for tab replaced by spaces and 'lbr'.
    838      if (lnum == curwin->w_cursor.lnum) {
    839        curwin->w_cursor.col = bd.textcol + bd.startspaces;
    840        curwin->w_cursor.coladd = 0;
    841      }
    842 
    843      // "n" == number of chars deleted
    844      // If we delete a TAB, it may be replaced by several characters.
    845      // Thus the number of characters may increase!
    846      int n = bd.textlen - bd.startspaces - bd.endspaces;
    847      char *oldp = ml_get(lnum);
    848      char *newp = xmalloc((size_t)ml_get_len(lnum) - (size_t)n + 1);
    849      // copy up to deleted part
    850      memmove(newp, oldp, (size_t)bd.textcol);
    851      // insert spaces
    852      memset(newp + bd.textcol, ' ', (size_t)bd.startspaces +
    853             (size_t)bd.endspaces);
    854      // copy the part after the deleted part
    855      STRCPY(newp + bd.textcol + bd.startspaces + bd.endspaces,
    856             oldp + bd.textcol + bd.textlen);
    857      // replace the line
    858      ml_replace(lnum, newp, false);
    859 
    860      extmark_splice_cols(curbuf, (int)lnum - 1, bd.textcol,
    861                          bd.textlen, bd.startspaces + bd.endspaces,
    862                          kExtmarkUndo);
    863    }
    864 
    865    check_cursor_col(curwin);
    866    changed_lines(curbuf, curwin->w_cursor.lnum, curwin->w_cursor.col,
    867                  oap->end.lnum + 1, 0, true);
    868    oap->line_count = 0;  // no lines deleted
    869  } else if (oap->motion_type == kMTLineWise) {
    870    if (oap->op_type == OP_CHANGE) {
    871      // Delete the lines except the first one.  Temporarily move the
    872      // cursor to the next line.  Save the current line number, if the
    873      // last line is deleted it may be changed.
    874 
    875      if (oap->line_count > 1) {
    876        lnum = curwin->w_cursor.lnum;
    877        curwin->w_cursor.lnum++;
    878        del_lines(oap->line_count - 1, true);
    879        curwin->w_cursor.lnum = lnum;
    880      }
    881      if (u_save_cursor() == FAIL) {
    882        return FAIL;
    883      }
    884      if (curbuf->b_p_ai) {                 // don't delete indent
    885        beginline(BL_WHITE);                // cursor on first non-white
    886        did_ai = true;                      // delete the indent when ESC hit
    887        ai_col = curwin->w_cursor.col;
    888      } else {
    889        beginline(0);                       // cursor in column 0
    890      }
    891      truncate_line(false);         // delete the rest of the line,
    892                                    // leaving cursor past last char in line
    893      if (oap->line_count > 1) {
    894        u_clearline(curbuf);  // "U" command not possible after "2cc"
    895      }
    896    } else {
    897      del_lines(oap->line_count, true);
    898      beginline(BL_WHITE | BL_FIX);
    899      u_clearline(curbuf);  //  "U" command not possible after "dd"
    900    }
    901  } else {
    902    if (virtual_op) {
    903      // For virtualedit: break the tabs that are partly included.
    904      if (gchar_pos(&oap->start) == '\t') {
    905        int endcol = 0;
    906        if (u_save_cursor() == FAIL) {          // save first line for undo
    907          return FAIL;
    908        }
    909        if (oap->line_count == 1) {
    910          endcol = getviscol2(oap->end.col, oap->end.coladd);
    911        }
    912        coladvance_force(getviscol2(oap->start.col, oap->start.coladd));
    913        oap->start = curwin->w_cursor;
    914        if (oap->line_count == 1) {
    915          coladvance(curwin, endcol);
    916          oap->end.col = curwin->w_cursor.col;
    917          oap->end.coladd = curwin->w_cursor.coladd;
    918          curwin->w_cursor = oap->start;
    919        }
    920      }
    921 
    922      // Break a tab only when it's included in the area.
    923      if (gchar_pos(&oap->end) == '\t'
    924          && oap->end.coladd == 0
    925          && oap->inclusive) {
    926        // save last line for undo
    927        if (u_save((linenr_T)(oap->end.lnum - 1),
    928                   (linenr_T)(oap->end.lnum + 1)) == FAIL) {
    929          return FAIL;
    930        }
    931        curwin->w_cursor = oap->end;
    932        coladvance_force(getviscol2(oap->end.col, oap->end.coladd));
    933        oap->end = curwin->w_cursor;
    934        curwin->w_cursor = oap->start;
    935      }
    936      mb_adjust_opend(oap);
    937    }
    938 
    939    if (oap->line_count == 1) {         // delete characters within one line
    940      if (u_save_cursor() == FAIL) {            // save line for undo
    941        return FAIL;
    942      }
    943 
    944      // if 'cpoptions' contains '$', display '$' at end of change
    945      if (vim_strchr(p_cpo, CPO_DOLLAR) != NULL
    946          && oap->op_type == OP_CHANGE
    947          && oap->end.lnum == curwin->w_cursor.lnum
    948          && !oap->is_VIsual) {
    949        display_dollar(oap->end.col - !oap->inclusive);
    950      }
    951 
    952      int n = oap->end.col - oap->start.col + 1 - !oap->inclusive;
    953 
    954      if (virtual_op) {
    955        // fix up things for virtualedit-delete:
    956        // break the tabs which are going to get in our way
    957        int len = get_cursor_line_len();
    958 
    959        if (oap->end.coladd != 0
    960            && (int)oap->end.col >= len - 1
    961            && !(oap->start.coladd && (int)oap->end.col >= len - 1)) {
    962          n++;
    963        }
    964        // Delete at least one char (e.g, when on a control char).
    965        if (n == 0 && oap->start.coladd != oap->end.coladd) {
    966          n = 1;
    967        }
    968 
    969        // When deleted a char in the line, reset coladd.
    970        if (gchar_cursor() != NUL) {
    971          curwin->w_cursor.coladd = 0;
    972        }
    973      }
    974 
    975      del_bytes((colnr_T)n, !virtual_op,
    976                oap->op_type == OP_DELETE && !oap->is_VIsual);
    977    } else {
    978      // delete characters between lines
    979      pos_T curpos;
    980 
    981      // save deleted and changed lines for undo
    982      if (u_save(curwin->w_cursor.lnum - 1,
    983                 curwin->w_cursor.lnum + oap->line_count) == FAIL) {
    984        return FAIL;
    985      }
    986 
    987      curbuf_splice_pending++;
    988      pos_T startpos = curwin->w_cursor;  // start position for delete
    989      bcount_t deleted_bytes = get_region_bytecount(curbuf, startpos.lnum, oap->end.lnum,
    990                                                    startpos.col,
    991                                                    oap->end.col) + oap->inclusive;
    992      truncate_line(true);        // delete from cursor to end of line
    993 
    994      curpos = curwin->w_cursor;  // remember curwin->w_cursor
    995      curwin->w_cursor.lnum++;
    996 
    997      del_lines(oap->line_count - 2, false);
    998 
    999      // delete from start of line until op_end
   1000      int n = (oap->end.col + 1 - !oap->inclusive);
   1001      curwin->w_cursor.col = 0;
   1002      del_bytes((colnr_T)n, !virtual_op,
   1003                oap->op_type == OP_DELETE && !oap->is_VIsual);
   1004      curwin->w_cursor = curpos;  // restore curwin->w_cursor
   1005      do_join(2, false, false, false, false);
   1006      curbuf_splice_pending--;
   1007      extmark_splice(curbuf, (int)startpos.lnum - 1, startpos.col,
   1008                     (int)oap->line_count - 1, n, deleted_bytes,
   1009                     0, 0, 0, kExtmarkUndo);
   1010    }
   1011    if (oap->op_type == OP_DELETE) {
   1012      auto_format(false, true);
   1013    }
   1014  }
   1015 
   1016  msgmore(curbuf->b_ml.ml_line_count - old_lcount);
   1017 
   1018 setmarks:
   1019  if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
   1020    if (oap->motion_type == kMTBlockWise) {
   1021      curbuf->b_op_end.lnum = oap->end.lnum;
   1022      curbuf->b_op_end.col = oap->start.col;
   1023    } else {
   1024      curbuf->b_op_end = oap->start;
   1025    }
   1026    curbuf->b_op_start = oap->start;
   1027  }
   1028 
   1029  return OK;
   1030 }
   1031 
   1032 /// Adjust end of operating area for ending on a multi-byte character.
   1033 /// Used for deletion.
   1034 static void mb_adjust_opend(oparg_T *oap)
   1035 {
   1036  if (!oap->inclusive) {
   1037    return;
   1038  }
   1039 
   1040  const char *line = ml_get(oap->end.lnum);
   1041  const char *ptr = line + oap->end.col;
   1042  if (*ptr != NUL) {
   1043    ptr -= utf_head_off(line, ptr);
   1044    ptr += utfc_ptr2len(ptr) - 1;
   1045    oap->end.col = (colnr_T)(ptr - line);
   1046  }
   1047 }
   1048 
   1049 /// put byte 'c' at position 'lp', but
   1050 /// verify, that the position to place
   1051 /// is actually safe
   1052 static void pbyte(pos_T lp, int c)
   1053 {
   1054  assert(c <= UCHAR_MAX);
   1055  char *p = ml_get_buf_mut(curbuf, lp.lnum);
   1056  colnr_T len = curbuf->b_ml.ml_line_textlen;
   1057 
   1058  // safety check
   1059  if (lp.col >= len) {
   1060    lp.col = (len > 1 ? len - 2 : 0);
   1061  }
   1062  *(p + lp.col) = (char)c;
   1063  if (!curbuf_splice_pending) {
   1064    extmark_splice_cols(curbuf, (int)lp.lnum - 1, lp.col, 1, 1, kExtmarkUndo);
   1065  }
   1066 }
   1067 
   1068 /// Replace the character under the cursor with "c".
   1069 /// This takes care of multi-byte characters.
   1070 static void replace_character(int c)
   1071 {
   1072  const int n = State;
   1073 
   1074  State = MODE_REPLACE;
   1075  ins_char(c);
   1076  State = n;
   1077  // Backup to the replaced character.
   1078  dec_cursor();
   1079 }
   1080 
   1081 /// Replace a whole area with one character.
   1082 static int op_replace(oparg_T *oap, int c)
   1083 {
   1084  int n;
   1085  struct block_def bd;
   1086  char *after_p = NULL;
   1087  bool had_ctrl_v_cr = false;
   1088 
   1089  if ((curbuf->b_ml.ml_flags & ML_EMPTY) || oap->empty) {
   1090    return OK;              // nothing to do
   1091  }
   1092  if (c == REPLACE_CR_NCHAR) {
   1093    had_ctrl_v_cr = true;
   1094    c = CAR;
   1095  } else if (c == REPLACE_NL_NCHAR) {
   1096    had_ctrl_v_cr = true;
   1097    c = NL;
   1098  }
   1099 
   1100  mb_adjust_opend(oap);
   1101 
   1102  if (u_save((linenr_T)(oap->start.lnum - 1),
   1103             (linenr_T)(oap->end.lnum + 1)) == FAIL) {
   1104    return FAIL;
   1105  }
   1106 
   1107  // block mode replace
   1108  if (oap->motion_type == kMTBlockWise) {
   1109    bd.is_MAX = (curwin->w_curswant == MAXCOL);
   1110    for (; curwin->w_cursor.lnum <= oap->end.lnum; curwin->w_cursor.lnum++) {
   1111      curwin->w_cursor.col = 0;       // make sure cursor position is valid
   1112      block_prep(oap, &bd, curwin->w_cursor.lnum, true);
   1113      if (bd.textlen == 0 && (!virtual_op || bd.is_MAX)) {
   1114        continue;                     // nothing to replace
   1115      }
   1116 
   1117      // n == number of extra chars required
   1118      // If we split a TAB, it may be replaced by several characters.
   1119      // Thus the number of characters may increase!
   1120      // If the range starts in virtual space, count the initial
   1121      // coladd offset as part of "startspaces"
   1122      if (virtual_op && bd.is_short && *bd.textstart == NUL) {
   1123        pos_T vpos;
   1124 
   1125        vpos.lnum = curwin->w_cursor.lnum;
   1126        getvpos(curwin, &vpos, oap->start_vcol);
   1127        bd.startspaces += vpos.coladd;
   1128        n = bd.startspaces;
   1129      } else {
   1130        // allow for pre spaces
   1131        n = (bd.startspaces ? bd.start_char_vcols - 1 : 0);
   1132      }
   1133 
   1134      // allow for post spp
   1135      n += (bd.endspaces
   1136            && !bd.is_oneChar
   1137            && bd.end_char_vcols > 0) ? bd.end_char_vcols - 1 : 0;
   1138      // Figure out how many characters to replace.
   1139      int numc = oap->end_vcol - oap->start_vcol + 1;
   1140      if (bd.is_short && (!virtual_op || bd.is_MAX)) {
   1141        numc -= (oap->end_vcol - bd.end_vcol) + 1;
   1142      }
   1143 
   1144      // A double-wide character can be replaced only up to half the
   1145      // times.
   1146      if (utf_char2cells(c) > 1) {
   1147        if ((numc & 1) && !bd.is_short) {
   1148          bd.endspaces++;
   1149          n++;
   1150        }
   1151        numc = numc / 2;
   1152      }
   1153 
   1154      // Compute bytes needed, move character count to num_chars.
   1155      int num_chars = numc;
   1156      numc *= utf_char2len(c);
   1157 
   1158      char *oldp = get_cursor_line_ptr();
   1159      colnr_T oldlen = get_cursor_line_len();
   1160 
   1161      size_t newp_size = (size_t)bd.textcol + (size_t)bd.startspaces;
   1162      if (had_ctrl_v_cr || (c != '\r' && c != '\n')) {
   1163        newp_size += (size_t)numc;
   1164        if (!bd.is_short) {
   1165          newp_size += (size_t)(bd.endspaces + oldlen
   1166                                - bd.textcol - bd.textlen);
   1167        }
   1168      }
   1169      char *newp = xmallocz(newp_size);
   1170      // copy up to deleted part
   1171      memmove(newp, oldp, (size_t)bd.textcol);
   1172      oldp += bd.textcol + bd.textlen;
   1173      // insert pre-spaces
   1174      memset(newp + bd.textcol, ' ', (size_t)bd.startspaces);
   1175      // insert replacement chars CHECK FOR ALLOCATED SPACE
   1176      // REPLACE_CR_NCHAR/REPLACE_NL_NCHAR is used for entering CR literally.
   1177      size_t after_p_len = 0;
   1178      int col = oldlen - bd.textcol - bd.textlen + 1;
   1179      assert(col >= 0);
   1180      int newrows = 0;
   1181      int newcols = 0;
   1182      if (had_ctrl_v_cr || (c != '\r' && c != '\n')) {
   1183        // strlen(newp) at this point
   1184        int newp_len = bd.textcol + bd.startspaces;
   1185        while (--num_chars >= 0) {
   1186          newp_len += utf_char2bytes(c, newp + newp_len);
   1187        }
   1188        if (!bd.is_short) {
   1189          // insert post-spaces
   1190          memset(newp + newp_len, ' ', (size_t)bd.endspaces);
   1191          newp_len += bd.endspaces;
   1192          // copy the part after the changed part
   1193          memmove(newp + newp_len, oldp, (size_t)col);
   1194        }
   1195        newcols = newp_len - bd.textcol;
   1196      } else {
   1197        // Replacing with \r or \n means splitting the line.
   1198        after_p_len = (size_t)col;
   1199        after_p = xmalloc(after_p_len);
   1200        memmove(after_p, oldp, after_p_len);
   1201        newrows = 1;
   1202      }
   1203      // replace the line
   1204      ml_replace(curwin->w_cursor.lnum, newp, false);
   1205      curbuf_splice_pending++;
   1206      linenr_T baselnum = curwin->w_cursor.lnum;
   1207      if (after_p != NULL) {
   1208        ml_append(curwin->w_cursor.lnum++, after_p, (int)after_p_len, false);
   1209        appended_lines_mark(curwin->w_cursor.lnum, 1);
   1210        oap->end.lnum++;
   1211        xfree(after_p);
   1212      }
   1213      curbuf_splice_pending--;
   1214      extmark_splice(curbuf, (int)baselnum - 1, bd.textcol,
   1215                     0, bd.textlen, bd.textlen,
   1216                     newrows, newcols, newrows + newcols, kExtmarkUndo);
   1217    }
   1218  } else {
   1219    // Characterwise or linewise motion replace.
   1220    if (oap->motion_type == kMTLineWise) {
   1221      oap->start.col = 0;
   1222      curwin->w_cursor.col = 0;
   1223      oap->end.col = ml_get_len(oap->end.lnum);
   1224      if (oap->end.col) {
   1225        oap->end.col--;
   1226      }
   1227    } else if (!oap->inclusive) {
   1228      dec(&(oap->end));
   1229    }
   1230 
   1231    // TODO(bfredl): we could batch all the splicing
   1232    // done on the same line, at least
   1233    while (ltoreq(curwin->w_cursor, oap->end)) {
   1234      bool done = false;
   1235 
   1236      n = gchar_cursor();
   1237      if (n != NUL) {
   1238        int new_byte_len = utf_char2len(c);
   1239        int old_byte_len = utfc_ptr2len(get_cursor_pos_ptr());
   1240 
   1241        if (new_byte_len > 1 || old_byte_len > 1) {
   1242          // This is slow, but it handles replacing a single-byte
   1243          // with a multi-byte and the other way around.
   1244          if (curwin->w_cursor.lnum == oap->end.lnum) {
   1245            oap->end.col += new_byte_len - old_byte_len;
   1246          }
   1247          replace_character(c);
   1248          done = true;
   1249        } else {
   1250          if (n == TAB) {
   1251            int end_vcol = 0;
   1252 
   1253            if (curwin->w_cursor.lnum == oap->end.lnum) {
   1254              // oap->end has to be recalculated when
   1255              // the tab breaks
   1256              end_vcol = getviscol2(oap->end.col,
   1257                                    oap->end.coladd);
   1258            }
   1259            coladvance_force(getviscol());
   1260            if (curwin->w_cursor.lnum == oap->end.lnum) {
   1261              getvpos(curwin, &oap->end, end_vcol);
   1262            }
   1263          }
   1264          // with "coladd" set may move to just after a TAB
   1265          if (gchar_cursor() != NUL) {
   1266            pbyte(curwin->w_cursor, c);
   1267            done = true;
   1268          }
   1269        }
   1270      }
   1271      if (!done && virtual_op && curwin->w_cursor.lnum == oap->end.lnum) {
   1272        int virtcols = oap->end.coladd;
   1273 
   1274        if (curwin->w_cursor.lnum == oap->start.lnum
   1275            && oap->start.col == oap->end.col && oap->start.coladd) {
   1276          virtcols -= oap->start.coladd;
   1277        }
   1278 
   1279        // oap->end has been trimmed so it's effectively inclusive;
   1280        // as a result an extra +1 must be counted so we don't
   1281        // trample the NUL byte.
   1282        coladvance_force(getviscol2(oap->end.col, oap->end.coladd) + 1);
   1283        curwin->w_cursor.col -= (virtcols + 1);
   1284        for (; virtcols >= 0; virtcols--) {
   1285          if (utf_char2len(c) > 1) {
   1286            replace_character(c);
   1287          } else {
   1288            pbyte(curwin->w_cursor, c);
   1289          }
   1290          if (inc(&curwin->w_cursor) == -1) {
   1291            break;
   1292          }
   1293        }
   1294      }
   1295 
   1296      // Advance to next character, stop at the end of the file.
   1297      if (inc_cursor() == -1) {
   1298        break;
   1299      }
   1300    }
   1301  }
   1302 
   1303  curwin->w_cursor = oap->start;
   1304  check_cursor(curwin);
   1305  changed_lines(curbuf, oap->start.lnum, oap->start.col, oap->end.lnum + 1, 0, true);
   1306 
   1307  if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
   1308    // Set "'[" and "']" marks.
   1309    curbuf->b_op_start = oap->start;
   1310    curbuf->b_op_end = oap->end;
   1311  }
   1312 
   1313  return OK;
   1314 }
   1315 
   1316 /// Handle the (non-standard vi) tilde operator.  Also for "gu", "gU" and "g?".
   1317 void op_tilde(oparg_T *oap)
   1318 {
   1319  struct block_def bd;
   1320  bool did_change = false;
   1321 
   1322  if (u_save((linenr_T)(oap->start.lnum - 1),
   1323             (linenr_T)(oap->end.lnum + 1)) == FAIL) {
   1324    return;
   1325  }
   1326 
   1327  pos_T pos = oap->start;
   1328  if (oap->motion_type == kMTBlockWise) {  // Visual block mode
   1329    for (; pos.lnum <= oap->end.lnum; pos.lnum++) {
   1330      block_prep(oap, &bd, pos.lnum, false);
   1331      pos.col = bd.textcol;
   1332      bool one_change = swapchars(oap->op_type, &pos, bd.textlen);
   1333      did_change |= one_change;
   1334    }
   1335    if (did_change) {
   1336      changed_lines(curbuf, oap->start.lnum, 0, oap->end.lnum + 1, 0, true);
   1337    }
   1338  } else {  // not block mode
   1339    if (oap->motion_type == kMTLineWise) {
   1340      oap->start.col = 0;
   1341      pos.col = 0;
   1342      oap->end.col = ml_get_len(oap->end.lnum);
   1343      if (oap->end.col) {
   1344        oap->end.col--;
   1345      }
   1346    } else if (!oap->inclusive) {
   1347      dec(&(oap->end));
   1348    }
   1349 
   1350    if (pos.lnum == oap->end.lnum) {
   1351      did_change = swapchars(oap->op_type, &pos,
   1352                             oap->end.col - pos.col + 1);
   1353    } else {
   1354      while (true) {
   1355        did_change |= swapchars(oap->op_type, &pos,
   1356                                pos.lnum == oap->end.lnum ? oap->end.col + 1
   1357                                                          : ml_get_pos_len(&pos));
   1358        if (ltoreq(oap->end, pos) || inc(&pos) == -1) {
   1359          break;
   1360        }
   1361      }
   1362    }
   1363    if (did_change) {
   1364      changed_lines(curbuf, oap->start.lnum, oap->start.col, oap->end.lnum + 1,
   1365                    0, true);
   1366    }
   1367  }
   1368 
   1369  if (!did_change && oap->is_VIsual) {
   1370    // No change: need to remove the Visual selection
   1371    redraw_curbuf_later(UPD_INVERTED);
   1372  }
   1373 
   1374  if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
   1375    // Set '[ and '] marks.
   1376    curbuf->b_op_start = oap->start;
   1377    curbuf->b_op_end = oap->end;
   1378  }
   1379 
   1380  if (oap->line_count > p_report) {
   1381    smsg(0, NGETTEXT("%" PRId64 " line changed", "%" PRId64 " lines changed", oap->line_count),
   1382         (int64_t)oap->line_count);
   1383  }
   1384 }
   1385 
   1386 /// Invoke swapchar() on "length" bytes at position "pos".
   1387 ///
   1388 /// @param pos     is advanced to just after the changed characters.
   1389 /// @param length  is rounded up to include the whole last multi-byte character.
   1390 /// Also works correctly when the number of bytes changes.
   1391 ///
   1392 /// @return  true if some character was changed.
   1393 static int swapchars(int op_type, pos_T *pos, int length)
   1394  FUNC_ATTR_NONNULL_ALL
   1395 {
   1396  int did_change = 0;
   1397 
   1398  for (int todo = length; todo > 0; todo--) {
   1399    const int len = utfc_ptr2len(ml_get_pos(pos));
   1400 
   1401    // we're counting bytes, not characters
   1402    if (len > 0) {
   1403      todo -= len - 1;
   1404    }
   1405    did_change |= swapchar(op_type, pos);
   1406    if (inc(pos) == -1) {      // at end of file
   1407      break;
   1408    }
   1409  }
   1410  return did_change;
   1411 }
   1412 
   1413 /// @param op_type
   1414 ///                 == OP_UPPER: make uppercase,
   1415 ///                 == OP_LOWER: make lowercase,
   1416 ///                 == OP_ROT13: do rot13 encoding,
   1417 ///                 else swap case of character at 'pos'
   1418 ///
   1419 /// @return  true when something actually changed.
   1420 bool swapchar(int op_type, pos_T *pos)
   1421  FUNC_ATTR_NONNULL_ARG(2)
   1422 {
   1423  const int c = gchar_pos(pos);
   1424 
   1425  // Only do rot13 encoding for ASCII characters.
   1426  if (c >= 0x80 && op_type == OP_ROT13) {
   1427    return false;
   1428  }
   1429 
   1430  int nc = c;
   1431  if (mb_islower(c)) {
   1432    if (op_type == OP_ROT13) {
   1433      nc = ROT13(c, 'a');
   1434    } else if (op_type != OP_LOWER) {
   1435      nc = mb_toupper(c);
   1436    }
   1437  } else if (mb_isupper(c)) {
   1438    if (op_type == OP_ROT13) {
   1439      nc = ROT13(c, 'A');
   1440    } else if (op_type != OP_UPPER) {
   1441      nc = mb_tolower(c);
   1442    }
   1443  }
   1444  if (nc != c) {
   1445    if (c >= 0x80 || nc >= 0x80) {
   1446      pos_T sp = curwin->w_cursor;
   1447 
   1448      curwin->w_cursor = *pos;
   1449      // don't use del_char(), it also removes composing chars
   1450      del_bytes(utf_ptr2len(get_cursor_pos_ptr()), false, false);
   1451      ins_char(nc);
   1452      curwin->w_cursor = sp;
   1453    } else {
   1454      pbyte(*pos, nc);
   1455    }
   1456    return true;
   1457  }
   1458  return false;
   1459 }
   1460 
   1461 /// Insert and append operators for Visual mode.
   1462 void op_insert(oparg_T *oap, int count1)
   1463 {
   1464  int pre_textlen = 0;
   1465  colnr_T ind_pre_col = 0;
   1466  int ind_pre_vcol = 0;
   1467  struct block_def bd;
   1468 
   1469  // edit() changes this - record it for OP_APPEND
   1470  bd.is_MAX = (curwin->w_curswant == MAXCOL);
   1471 
   1472  // vis block is still marked. Get rid of it now.
   1473  curwin->w_cursor.lnum = oap->start.lnum;
   1474  redraw_curbuf_later(UPD_INVERTED);
   1475  update_screen();
   1476 
   1477  if (oap->motion_type == kMTBlockWise) {
   1478    // When 'virtualedit' is used, need to insert the extra spaces before
   1479    // doing block_prep().  When only "block" is used, virtual edit is
   1480    // already disabled, but still need it when calling
   1481    // coladvance_force().
   1482    // coladvance_force() uses get_ve_flags() to get the 'virtualedit'
   1483    // state for the current window.  To override that state, we need to
   1484    // set the window-local value of ve_flags rather than the global value.
   1485    if (curwin->w_cursor.coladd > 0) {
   1486      unsigned old_ve_flags = curwin->w_ve_flags;
   1487 
   1488      if (u_save_cursor() == FAIL) {
   1489        return;
   1490      }
   1491      curwin->w_ve_flags = kOptVeFlagAll;
   1492      coladvance_force(oap->op_type == OP_APPEND
   1493                       ? oap->end_vcol + 1 : getviscol());
   1494      if (oap->op_type == OP_APPEND) {
   1495        curwin->w_cursor.col--;
   1496      }
   1497      curwin->w_ve_flags = old_ve_flags;
   1498    }
   1499    // Get the info about the block before entering the text
   1500    block_prep(oap, &bd, oap->start.lnum, true);
   1501    // Get indent information
   1502    ind_pre_col = (colnr_T)getwhitecols_curline();
   1503    ind_pre_vcol = get_indent();
   1504    pre_textlen = ml_get_len(oap->start.lnum) - bd.textcol;
   1505    if (oap->op_type == OP_APPEND) {
   1506      pre_textlen -= bd.textlen;
   1507    }
   1508  }
   1509 
   1510  if (oap->op_type == OP_APPEND) {
   1511    if (oap->motion_type == kMTBlockWise
   1512        && curwin->w_cursor.coladd == 0) {
   1513      // Move the cursor to the character right of the block.
   1514      curwin->w_set_curswant = true;
   1515      while (*get_cursor_pos_ptr() != NUL
   1516             && (curwin->w_cursor.col < bd.textcol + bd.textlen)) {
   1517        curwin->w_cursor.col++;
   1518      }
   1519      if (bd.is_short && !bd.is_MAX) {
   1520        // First line was too short, make it longer and adjust the
   1521        // values in "bd".
   1522        if (u_save_cursor() == FAIL) {
   1523          return;
   1524        }
   1525        for (int i = 0; i < bd.endspaces; i++) {
   1526          ins_char(' ');
   1527        }
   1528        bd.textlen += bd.endspaces;
   1529      }
   1530    } else {
   1531      curwin->w_cursor = oap->end;
   1532      check_cursor_col(curwin);
   1533 
   1534      // Works just like an 'i'nsert on the next character.
   1535      if (!LINEEMPTY(curwin->w_cursor.lnum)
   1536          && oap->start_vcol != oap->end_vcol) {
   1537        inc_cursor();
   1538      }
   1539    }
   1540  }
   1541 
   1542  pos_T t1 = oap->start;
   1543  const pos_T start_insert = curwin->w_cursor;
   1544  edit(NUL, false, (linenr_T)count1);
   1545 
   1546  // When a tab was inserted, and the characters in front of the tab
   1547  // have been converted to a tab as well, the column of the cursor
   1548  // might have actually been reduced, so need to adjust here.
   1549  if (t1.lnum == curbuf->b_op_start_orig.lnum
   1550      && lt(curbuf->b_op_start_orig, t1)) {
   1551    oap->start = curbuf->b_op_start_orig;
   1552  }
   1553 
   1554  // If user has moved off this line, we don't know what to do, so do
   1555  // nothing.
   1556  // Also don't repeat the insert when Insert mode ended with CTRL-C.
   1557  if (curwin->w_cursor.lnum != oap->start.lnum || got_int) {
   1558    return;
   1559  }
   1560 
   1561  if (oap->motion_type == kMTBlockWise) {
   1562    int ind_post_vcol = 0;
   1563    struct block_def bd2;
   1564    bool did_indent = false;
   1565 
   1566    // if indent kicked in, the firstline might have changed
   1567    // but only do that, if the indent actually increased
   1568    colnr_T ind_post_col = (colnr_T)getwhitecols_curline();
   1569    if (curbuf->b_op_start.col > ind_pre_col && ind_post_col > ind_pre_col) {
   1570      bd.textcol += ind_post_col - ind_pre_col;
   1571      ind_post_vcol = get_indent();
   1572      bd.start_vcol += ind_post_vcol - ind_pre_vcol;
   1573      did_indent = true;
   1574    }
   1575 
   1576    // The user may have moved the cursor before inserting something, try
   1577    // to adjust the block for that.  But only do it, if the difference
   1578    // does not come from indent kicking in.
   1579    if (oap->start.lnum == curbuf->b_op_start_orig.lnum && !bd.is_MAX && !did_indent) {
   1580      const int t = getviscol2(curbuf->b_op_start_orig.col, curbuf->b_op_start_orig.coladd);
   1581 
   1582      if (oap->op_type == OP_INSERT
   1583          && oap->start.col + oap->start.coladd
   1584          != curbuf->b_op_start_orig.col + curbuf->b_op_start_orig.coladd) {
   1585        oap->start.col = curbuf->b_op_start_orig.col;
   1586        pre_textlen -= t - oap->start_vcol;
   1587        oap->start_vcol = t;
   1588      } else if (oap->op_type == OP_APPEND
   1589                 && oap->start.col + oap->start.coladd
   1590                 >= curbuf->b_op_start_orig.col + curbuf->b_op_start_orig.coladd) {
   1591        oap->start.col = curbuf->b_op_start_orig.col;
   1592        // reset pre_textlen to the value of OP_INSERT
   1593        pre_textlen += bd.textlen;
   1594        pre_textlen -= t - oap->start_vcol;
   1595        oap->start_vcol = t;
   1596        oap->op_type = OP_INSERT;
   1597      }
   1598    }
   1599 
   1600    // Spaces and tabs in the indent may have changed to other spaces and
   1601    // tabs.  Get the starting column again and correct the length.
   1602    // Don't do this when "$" used, end-of-line will have changed.
   1603    //
   1604    // if indent was added and the inserted text was after the indent,
   1605    // correct the selection for the new indent.
   1606    if (did_indent && bd.textcol - ind_post_col > 0) {
   1607      oap->start.col += ind_post_col - ind_pre_col;
   1608      oap->start_vcol += ind_post_vcol - ind_pre_vcol;
   1609      oap->end.col += ind_post_col - ind_pre_col;
   1610      oap->end_vcol += ind_post_vcol - ind_pre_vcol;
   1611    }
   1612    block_prep(oap, &bd2, oap->start.lnum, true);
   1613    if (did_indent && bd.textcol - ind_post_col > 0) {
   1614      // undo for where "oap" is used below
   1615      oap->start.col -= ind_post_col - ind_pre_col;
   1616      oap->start_vcol -= ind_post_vcol - ind_pre_vcol;
   1617      oap->end.col -= ind_post_col - ind_pre_col;
   1618      oap->end_vcol -= ind_post_vcol - ind_pre_vcol;
   1619    }
   1620    if (!bd.is_MAX || bd2.textlen < bd.textlen) {
   1621      if (oap->op_type == OP_APPEND) {
   1622        pre_textlen += bd2.textlen - bd.textlen;
   1623        if (bd2.endspaces) {
   1624          bd2.textlen--;
   1625        }
   1626      }
   1627      bd.textcol = bd2.textcol;
   1628      bd.textlen = bd2.textlen;
   1629    }
   1630 
   1631    // Subsequent calls to ml_get() flush the firstline data - take a
   1632    // copy of the required string.
   1633    char *firstline = ml_get(oap->start.lnum);
   1634    colnr_T len = ml_get_len(oap->start.lnum);
   1635    colnr_T add = bd.textcol;
   1636    colnr_T offset = 0;  // offset when cursor was moved in insert mode
   1637    if (oap->op_type == OP_APPEND) {
   1638      add += bd.textlen;
   1639      // account for pressing cursor in insert mode when '$' was used
   1640      if (bd.is_MAX && start_insert.lnum == Insstart.lnum && start_insert.col > Insstart.col) {
   1641        offset = start_insert.col - Insstart.col;
   1642        add -= offset;
   1643        if (oap->end_vcol > offset) {
   1644          oap->end_vcol -= offset + 1;
   1645        } else {
   1646          // moved outside of the visual block, what to do?
   1647          return;
   1648        }
   1649      }
   1650    }
   1651    add = MIN(add, len);  // short line, point to the NUL
   1652    firstline += add;
   1653    len -= add;
   1654    int ins_len = len - pre_textlen - offset;
   1655    if (pre_textlen >= 0 && ins_len > 0) {
   1656      char *ins_text = xmemdupz(firstline, (size_t)ins_len);
   1657      // block handled here
   1658      if (u_save(oap->start.lnum, (linenr_T)(oap->end.lnum + 1)) == OK) {
   1659        block_insert(oap, ins_text, (size_t)ins_len, (oap->op_type == OP_INSERT), &bd);
   1660      }
   1661 
   1662      curwin->w_cursor.col = oap->start.col;
   1663      check_cursor(curwin);
   1664      xfree(ins_text);
   1665    }
   1666  }
   1667 }
   1668 
   1669 /// handle a change operation
   1670 ///
   1671 /// @return  true if edit() returns because of a CTRL-O command
   1672 int op_change(oparg_T *oap)
   1673 {
   1674  int pre_textlen = 0;
   1675  int pre_indent = 0;
   1676  char *firstline;
   1677  struct block_def bd;
   1678 
   1679  colnr_T l = oap->start.col;
   1680  if (oap->motion_type == kMTLineWise) {
   1681    l = 0;
   1682    can_si = may_do_si();  // Like opening a new line, do smart indent
   1683  }
   1684 
   1685  // First delete the text in the region.  In an empty buffer only need to
   1686  // save for undo
   1687  if (curbuf->b_ml.ml_flags & ML_EMPTY) {
   1688    if (u_save_cursor() == FAIL) {
   1689      return false;
   1690    }
   1691  } else if (op_delete(oap) == FAIL) {
   1692    return false;
   1693  }
   1694 
   1695  if ((l > curwin->w_cursor.col) && !LINEEMPTY(curwin->w_cursor.lnum)
   1696      && !virtual_op) {
   1697    inc_cursor();
   1698  }
   1699 
   1700  // check for still on same line (<CR> in inserted text meaningless)
   1701  // skip blank lines too
   1702  if (oap->motion_type == kMTBlockWise) {
   1703    // Add spaces before getting the current line length.
   1704    if (virtual_op && (curwin->w_cursor.coladd > 0
   1705                       || gchar_cursor() == NUL)) {
   1706      coladvance_force(getviscol());
   1707    }
   1708    firstline = ml_get(oap->start.lnum);
   1709    pre_textlen = ml_get_len(oap->start.lnum);
   1710    pre_indent = (int)getwhitecols(firstline);
   1711    bd.textcol = curwin->w_cursor.col;
   1712  }
   1713 
   1714  if (oap->motion_type == kMTLineWise) {
   1715    fix_indent();
   1716  }
   1717 
   1718  // Reset finish_op now, don't want it set inside edit().
   1719  const bool save_finish_op = finish_op;
   1720  finish_op = false;
   1721 
   1722  int retval = edit(NUL, false, 1);
   1723 
   1724  finish_op = save_finish_op;
   1725 
   1726  // In Visual block mode, handle copying the new text to all lines of the
   1727  // block.
   1728  // Don't repeat the insert when Insert mode ended with CTRL-C.
   1729  if (oap->motion_type == kMTBlockWise
   1730      && oap->start.lnum != oap->end.lnum && !got_int) {
   1731    // Auto-indenting may have changed the indent.  If the cursor was past
   1732    // the indent, exclude that indent change from the inserted text.
   1733    firstline = ml_get(oap->start.lnum);
   1734    if (bd.textcol > (colnr_T)pre_indent) {
   1735      int new_indent = (int)getwhitecols(firstline);
   1736 
   1737      pre_textlen += new_indent - pre_indent;
   1738      bd.textcol += (colnr_T)(new_indent - pre_indent);
   1739    }
   1740 
   1741    int ins_len = ml_get_len(oap->start.lnum) - pre_textlen;
   1742    if (ins_len > 0) {
   1743      // Subsequent calls to ml_get() flush the firstline data - take a
   1744      // copy of the inserted text.
   1745      char *ins_text = xmalloc((size_t)ins_len + 1);
   1746      xmemcpyz(ins_text, firstline + bd.textcol, (size_t)ins_len);
   1747      for (linenr_T linenr = oap->start.lnum + 1; linenr <= oap->end.lnum;
   1748           linenr++) {
   1749        block_prep(oap, &bd, linenr, true);
   1750        if (!bd.is_short || virtual_op) {
   1751          pos_T vpos;
   1752 
   1753          // If the block starts in virtual space, count the
   1754          // initial coladd offset as part of "startspaces"
   1755          if (bd.is_short) {
   1756            vpos.lnum = linenr;
   1757            getvpos(curwin, &vpos, oap->start_vcol);
   1758          } else {
   1759            vpos.coladd = 0;
   1760          }
   1761          char *oldp = ml_get(linenr);
   1762          char *newp = xmalloc((size_t)ml_get_len(linenr)
   1763                               + (size_t)vpos.coladd + (size_t)ins_len + 1);
   1764          // copy up to block start
   1765          memmove(newp, oldp, (size_t)bd.textcol);
   1766          int newlen = bd.textcol;
   1767          memset(newp + newlen, ' ', (size_t)vpos.coladd);
   1768          newlen += vpos.coladd;
   1769          memmove(newp + newlen, ins_text, (size_t)ins_len);
   1770          newlen += ins_len;
   1771          STRCPY(newp + newlen, oldp + bd.textcol);
   1772          ml_replace(linenr, newp, false);
   1773          extmark_splice_cols(curbuf, (int)linenr - 1, bd.textcol,
   1774                              0, vpos.coladd + ins_len, kExtmarkUndo);
   1775        }
   1776      }
   1777      check_cursor(curwin);
   1778      changed_lines(curbuf, oap->start.lnum + 1, 0, oap->end.lnum + 1, 0, true);
   1779      xfree(ins_text);
   1780    }
   1781  }
   1782  auto_format(false, true);
   1783 
   1784  return retval;
   1785 }
   1786 
   1787 /// When the cursor is on the NUL past the end of the line and it should not be
   1788 /// there move it left.
   1789 void adjust_cursor_eol(void)
   1790 {
   1791  unsigned cur_ve_flags = get_ve_flags(curwin);
   1792 
   1793  const bool adj_cursor = (curwin->w_cursor.col > 0
   1794                           && gchar_cursor() == NUL
   1795                           && (cur_ve_flags & kOptVeFlagOnemore) == 0
   1796                           && (cur_ve_flags & kOptVeFlagAll) == 0
   1797                           && !(restart_edit || (State & MODE_INSERT)));
   1798  if (!adj_cursor) {
   1799    return;
   1800  }
   1801 
   1802  // Put the cursor on the last character in the line.
   1803  dec_cursor();
   1804 
   1805  if (cur_ve_flags == kOptVeFlagAll) {
   1806    colnr_T scol, ecol;
   1807 
   1808    // Coladd is set to the width of the last character.
   1809    getvcol(curwin, &curwin->w_cursor, &scol, NULL, &ecol);
   1810    curwin->w_cursor.coladd = ecol - scol + 1;
   1811  }
   1812 }
   1813 
   1814 /// If \p "process" is true and the line begins with a comment leader (possibly
   1815 /// after some white space), return a pointer to the text after it.
   1816 /// Put a boolean value indicating whether the line ends with an unclosed
   1817 /// comment in "is_comment".
   1818 ///
   1819 /// @param line - line to be processed
   1820 /// @param process - if false, will only check whether the line ends
   1821 ///         with an unclosed comment,
   1822 /// @param include_space - whether to skip space following the comment leader
   1823 /// @param[out] is_comment - whether the current line ends with an unclosed
   1824 ///  comment.
   1825 char *skip_comment(char *line, bool process, bool include_space, bool *is_comment)
   1826 {
   1827  char *comment_flags = NULL;
   1828  int leader_offset = get_last_leader_offset(line, &comment_flags);
   1829 
   1830  *is_comment = false;
   1831  if (leader_offset != -1) {
   1832    // Let's check whether the line ends with an unclosed comment.
   1833    // If the last comment leader has COM_END in flags, there's no comment.
   1834    while (*comment_flags) {
   1835      if (*comment_flags == COM_END
   1836          || *comment_flags == ':') {
   1837        break;
   1838      }
   1839      comment_flags++;
   1840    }
   1841    if (*comment_flags != COM_END) {
   1842      *is_comment = true;
   1843    }
   1844  }
   1845 
   1846  if (process == false) {
   1847    return line;
   1848  }
   1849 
   1850  int lead_len = get_leader_len(line, &comment_flags, false, include_space);
   1851 
   1852  if (lead_len == 0) {
   1853    return line;
   1854  }
   1855 
   1856  // Find:
   1857  // - COM_END,
   1858  // - colon,
   1859  // whichever comes first.
   1860  while (*comment_flags) {
   1861    if (*comment_flags == COM_END
   1862        || *comment_flags == ':') {
   1863      break;
   1864    }
   1865    comment_flags++;
   1866  }
   1867 
   1868  // If we found a colon, it means that we are not processing a line
   1869  // starting with a closing part of a three-part comment. That's good,
   1870  // because we don't want to remove those as this would be annoying.
   1871  if (*comment_flags == ':' || *comment_flags == NUL) {
   1872    line += lead_len;
   1873  }
   1874 
   1875  return line;
   1876 }
   1877 
   1878 /// @param count              number of lines (minimal 2) to join at the cursor position.
   1879 /// @param save_undo          when true, save lines for undo first.
   1880 /// @param use_formatoptions  set to false when e.g. processing backspace and comment
   1881 ///                           leaders should not be removed.
   1882 /// @param setmark            when true, sets the '[ and '] mark, else, the caller is expected
   1883 ///                           to set those marks.
   1884 ///
   1885 /// @return  FAIL for failure, OK otherwise
   1886 int do_join(size_t count, bool insert_space, bool save_undo, bool use_formatoptions, bool setmark)
   1887 {
   1888  char *curr = NULL;
   1889  char *curr_start = NULL;
   1890  char *cend;
   1891  int endcurr1 = NUL;
   1892  int endcurr2 = NUL;
   1893  int currsize = 0;             // size of the current line
   1894  int sumsize = 0;              // size of the long new line
   1895  int ret = OK;
   1896  int *comments = NULL;
   1897  bool remove_comments = use_formatoptions && has_format_option(FO_REMOVE_COMS);
   1898  bool prev_was_comment = false;
   1899  assert(count >= 1);
   1900 
   1901  if (save_undo && u_save(curwin->w_cursor.lnum - 1,
   1902                          curwin->w_cursor.lnum + (linenr_T)count) == FAIL) {
   1903    return FAIL;
   1904  }
   1905  // Allocate an array to store the number of spaces inserted before each
   1906  // line.  We will use it to pre-compute the length of the new line and the
   1907  // proper placement of each original line in the new one.
   1908  char *spaces = xcalloc(count, 1);  // number of spaces inserted before a line
   1909  if (remove_comments) {
   1910    comments = xcalloc(count, sizeof(*comments));
   1911  }
   1912 
   1913  // Don't move anything yet, just compute the final line length
   1914  // and setup the array of space strings lengths
   1915  // This loops forward over joined lines.
   1916  for (linenr_T t = 0; t < (linenr_T)count; t++) {
   1917    curr_start = ml_get(curwin->w_cursor.lnum + t);
   1918    curr = curr_start;
   1919    if (t == 0 && setmark && (cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
   1920      // Set the '[ mark.
   1921      curwin->w_buffer->b_op_start.lnum = curwin->w_cursor.lnum;
   1922      curwin->w_buffer->b_op_start.col = (colnr_T)strlen(curr);
   1923    }
   1924    if (remove_comments) {
   1925      // We don't want to remove the comment leader if the
   1926      // previous line is not a comment.
   1927      if (t > 0 && prev_was_comment) {
   1928        char *new_curr = skip_comment(curr, true, insert_space, &prev_was_comment);
   1929        comments[t] = (int)(new_curr - curr);
   1930        curr = new_curr;
   1931      } else {
   1932        curr = skip_comment(curr, false, insert_space, &prev_was_comment);
   1933      }
   1934    }
   1935 
   1936    if (insert_space && t > 0) {
   1937      curr = skipwhite(curr);
   1938      if (*curr != NUL
   1939          && *curr != ')'
   1940          && sumsize != 0
   1941          && endcurr1 != TAB
   1942          && (!has_format_option(FO_MBYTE_JOIN)
   1943              || (utf_ptr2char(curr) < 0x100 && endcurr1 < 0x100))
   1944          && (!has_format_option(FO_MBYTE_JOIN2)
   1945              || (utf_ptr2char(curr) < 0x100 && !utf_eat_space(endcurr1))
   1946              || (endcurr1 < 0x100
   1947                  && !utf_eat_space(utf_ptr2char(curr))))) {
   1948        // don't add a space if the line is ending in a space
   1949        if (endcurr1 == ' ') {
   1950          endcurr1 = endcurr2;
   1951        } else {
   1952          spaces[t]++;
   1953        }
   1954        // Extra space when 'joinspaces' set and line ends in '.', '?', or '!'.
   1955        if (p_js && (endcurr1 == '.' || endcurr1 == '?' || endcurr1 == '!')) {
   1956          spaces[t]++;
   1957        }
   1958      }
   1959    }
   1960 
   1961    if (t > 0 && curbuf_splice_pending == 0) {
   1962      colnr_T removed = (int)(curr - curr_start);
   1963      extmark_splice(curbuf, (int)curwin->w_cursor.lnum - 1, sumsize,
   1964                     1, removed, removed + 1,
   1965                     0, spaces[t], spaces[t],
   1966                     kExtmarkUndo);
   1967    }
   1968    currsize = (int)strlen(curr);
   1969    sumsize += currsize + spaces[t];
   1970    endcurr1 = endcurr2 = NUL;
   1971    if (insert_space && currsize > 0) {
   1972      cend = curr + currsize;
   1973      MB_PTR_BACK(curr, cend);
   1974      endcurr1 = utf_ptr2char(cend);
   1975      if (cend > curr) {
   1976        MB_PTR_BACK(curr, cend);
   1977        endcurr2 = utf_ptr2char(cend);
   1978      }
   1979    }
   1980    line_breakcheck();
   1981    if (got_int) {
   1982      ret = FAIL;
   1983      goto theend;
   1984    }
   1985  }
   1986 
   1987  // store the column position before last line
   1988  colnr_T col = sumsize - currsize - spaces[count - 1];
   1989 
   1990  // allocate the space for the new line
   1991  size_t newp_len = (size_t)sumsize;
   1992  char *newp = xmallocz(newp_len);
   1993  cend = newp + sumsize;
   1994 
   1995  // Move affected lines to the new long one.
   1996  // This loops backwards over the joined lines, including the original line.
   1997  //
   1998  // Move marks from each deleted line to the joined line, adjusting the
   1999  // column.  This is not Vi compatible, but Vi deletes the marks, thus that
   2000  // should not really be a problem.
   2001 
   2002  curbuf_splice_pending++;
   2003 
   2004  for (linenr_T t = (linenr_T)count - 1;; t--) {
   2005    cend -= currsize;
   2006    memmove(cend, curr, (size_t)currsize);
   2007 
   2008    if (spaces[t] > 0) {
   2009      cend -= spaces[t];
   2010      memset(cend, ' ', (size_t)(spaces[t]));
   2011    }
   2012 
   2013    // If deleting more spaces than adding, the cursor moves no more than
   2014    // what is added if it is inside these spaces.
   2015    const int spaces_removed = (int)((curr - curr_start) - spaces[t]);
   2016    linenr_T lnum = curwin->w_cursor.lnum + t;
   2017    colnr_T mincol = 0;
   2018    linenr_T lnum_amount = -t;
   2019    colnr_T col_amount = (colnr_T)(cend - newp - spaces_removed);
   2020 
   2021    mark_col_adjust(lnum, mincol, lnum_amount, col_amount, spaces_removed);
   2022 
   2023    if (t == 0) {
   2024      break;
   2025    }
   2026 
   2027    curr_start = ml_get((linenr_T)(curwin->w_cursor.lnum + t - 1));
   2028    curr = curr_start;
   2029    if (remove_comments) {
   2030      curr += comments[t - 1];
   2031    }
   2032    if (insert_space && t > 1) {
   2033      curr = skipwhite(curr);
   2034    }
   2035    currsize = (int)strlen(curr);
   2036  }
   2037 
   2038  ml_replace_len(curwin->w_cursor.lnum, newp, newp_len, false);
   2039 
   2040  if (setmark && (cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
   2041    // Set the '] mark.
   2042    curwin->w_buffer->b_op_end.lnum = curwin->w_cursor.lnum;
   2043    curwin->w_buffer->b_op_end.col = sumsize;
   2044  }
   2045 
   2046  // Only report the change in the first line here, del_lines() will report
   2047  // the deleted line.
   2048  changed_lines(curbuf, curwin->w_cursor.lnum, currsize,
   2049                curwin->w_cursor.lnum + 1, 0, true);
   2050 
   2051  // Delete following lines. To do this we move the cursor there
   2052  // briefly, and then move it back. After del_lines() the cursor may
   2053  // have moved up (last line deleted), so the current lnum is kept in t.
   2054  linenr_T t = curwin->w_cursor.lnum;
   2055  curwin->w_cursor.lnum++;
   2056  del_lines((int)count - 1, false);
   2057  curwin->w_cursor.lnum = t;
   2058  curbuf_splice_pending--;
   2059  curbuf->deleted_bytes2 = 0;
   2060 
   2061  // Set the cursor column:
   2062  // Vi compatible: use the column of the first join
   2063  // vim:             use the column of the last join
   2064  curwin->w_cursor.col =
   2065    (vim_strchr(p_cpo, CPO_JOINCOL) != NULL ? currsize : col);
   2066  check_cursor_col(curwin);
   2067 
   2068  curwin->w_cursor.coladd = 0;
   2069  curwin->w_set_curswant = true;
   2070 
   2071 theend:
   2072  xfree(spaces);
   2073  if (remove_comments) {
   2074    xfree(comments);
   2075  }
   2076  return ret;
   2077 }
   2078 
   2079 /// Reset 'linebreak' and take care of side effects.
   2080 /// @return  the previous value, to be passed to restore_lbr().
   2081 bool reset_lbr(void)
   2082 {
   2083  if (!curwin->w_p_lbr) {
   2084    return false;
   2085  }
   2086  // changing 'linebreak' may require w_virtcol to be updated
   2087  curwin->w_p_lbr = false;
   2088  curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL);
   2089  return true;
   2090 }
   2091 
   2092 /// Restore 'linebreak' and take care of side effects.
   2093 void restore_lbr(bool lbr_saved)
   2094 {
   2095  if (curwin->w_p_lbr || !lbr_saved) {
   2096    return;
   2097  }
   2098 
   2099  // changing 'linebreak' may require w_virtcol to be updated
   2100  curwin->w_p_lbr = true;
   2101  curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL);
   2102 }
   2103 
   2104 /// prepare a few things for block mode yank/delete/tilde
   2105 ///
   2106 /// for delete:
   2107 /// - textlen includes the first/last char to be (partly) deleted
   2108 /// - start/endspaces is the number of columns that are taken by the
   2109 ///   first/last deleted char minus the number of columns that have to be
   2110 ///   deleted.
   2111 /// for yank and tilde:
   2112 /// - textlen includes the first/last char to be wholly yanked
   2113 /// - start/endspaces is the number of columns of the first/last yanked char
   2114 ///   that are to be yanked.
   2115 void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool is_del)
   2116 {
   2117  int incr = 0;
   2118  // Avoid a problem with unwanted linebreaks in block mode.
   2119  const bool lbr_saved = reset_lbr();
   2120 
   2121  bdp->startspaces = 0;
   2122  bdp->endspaces = 0;
   2123  bdp->textlen = 0;
   2124  bdp->start_vcol = 0;
   2125  bdp->end_vcol = 0;
   2126  bdp->is_short = false;
   2127  bdp->is_oneChar = false;
   2128  bdp->pre_whitesp = 0;
   2129  bdp->pre_whitesp_c = 0;
   2130  bdp->end_char_vcols = 0;
   2131  bdp->start_char_vcols = 0;
   2132 
   2133  char *line = ml_get(lnum);
   2134  char *prev_pstart = line;
   2135 
   2136  CharsizeArg csarg;
   2137  CSType cstype = init_charsize_arg(&csarg, curwin, lnum, line);
   2138  StrCharInfo ci = utf_ptr2StrCharInfo(line);
   2139  int vcol = bdp->start_vcol;
   2140  while (vcol < oap->start_vcol && *ci.ptr != NUL) {
   2141    incr = win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &csarg).width;
   2142    vcol += incr;
   2143    if (ascii_iswhite(ci.chr.value)) {
   2144      bdp->pre_whitesp += incr;
   2145      bdp->pre_whitesp_c++;
   2146    } else {
   2147      bdp->pre_whitesp = 0;
   2148      bdp->pre_whitesp_c = 0;
   2149    }
   2150    prev_pstart = ci.ptr;
   2151    ci = utfc_next(ci);
   2152  }
   2153  bdp->start_vcol = vcol;
   2154  char *pstart = ci.ptr;
   2155 
   2156  bdp->start_char_vcols = incr;
   2157  if (bdp->start_vcol < oap->start_vcol) {      // line too short
   2158    bdp->end_vcol = bdp->start_vcol;
   2159    bdp->is_short = true;
   2160    if (!is_del || oap->op_type == OP_APPEND) {
   2161      bdp->endspaces = oap->end_vcol - oap->start_vcol + 1;
   2162    }
   2163  } else {
   2164    // notice: this converts partly selected Multibyte characters to
   2165    // spaces, too.
   2166    bdp->startspaces = bdp->start_vcol - oap->start_vcol;
   2167    if (is_del && bdp->startspaces) {
   2168      bdp->startspaces = bdp->start_char_vcols - bdp->startspaces;
   2169    }
   2170    char *pend = pstart;
   2171    bdp->end_vcol = bdp->start_vcol;
   2172    if (bdp->end_vcol > oap->end_vcol) {  // it's all in one character
   2173      bdp->is_oneChar = true;
   2174      if (oap->op_type == OP_INSERT) {
   2175        bdp->endspaces = bdp->start_char_vcols - bdp->startspaces;
   2176      } else if (oap->op_type == OP_APPEND) {
   2177        bdp->startspaces += oap->end_vcol - oap->start_vcol + 1;
   2178        bdp->endspaces = bdp->start_char_vcols - bdp->startspaces;
   2179      } else {
   2180        bdp->startspaces = oap->end_vcol - oap->start_vcol + 1;
   2181        if (is_del && oap->op_type != OP_LSHIFT) {
   2182          // just putting the sum of those two into
   2183          // bdp->startspaces doesn't work for Visual replace,
   2184          // so we have to split the tab in two
   2185          bdp->startspaces = bdp->start_char_vcols
   2186                             - (bdp->start_vcol - oap->start_vcol);
   2187          bdp->endspaces = bdp->end_vcol - oap->end_vcol - 1;
   2188        }
   2189      }
   2190    } else {
   2191      cstype = init_charsize_arg(&csarg, curwin, lnum, line);
   2192      ci = utf_ptr2StrCharInfo(pend);
   2193      vcol = bdp->end_vcol;
   2194      char *prev_pend = pend;
   2195      while (vcol <= oap->end_vcol && *ci.ptr != NUL) {
   2196        prev_pend = ci.ptr;
   2197        incr = win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &csarg).width;
   2198        vcol += incr;
   2199        ci = utfc_next(ci);
   2200      }
   2201      bdp->end_vcol = vcol;
   2202      pend = ci.ptr;
   2203 
   2204      if (bdp->end_vcol <= oap->end_vcol
   2205          && (!is_del
   2206              || oap->op_type == OP_APPEND
   2207              || oap->op_type == OP_REPLACE)) {  // line too short
   2208        bdp->is_short = true;
   2209        // Alternative: include spaces to fill up the block.
   2210        // Disadvantage: can lead to trailing spaces when the line is
   2211        // short where the text is put
   2212        // if (!is_del || oap->op_type == OP_APPEND)
   2213        if (oap->op_type == OP_APPEND || virtual_op) {
   2214          bdp->endspaces = oap->end_vcol - bdp->end_vcol
   2215                           + oap->inclusive;
   2216        }
   2217      } else if (bdp->end_vcol > oap->end_vcol) {
   2218        bdp->endspaces = bdp->end_vcol - oap->end_vcol - 1;
   2219        if (!is_del && bdp->endspaces) {
   2220          bdp->endspaces = incr - bdp->endspaces;
   2221          if (pend != pstart) {
   2222            pend = prev_pend;
   2223          }
   2224        }
   2225      }
   2226    }
   2227    bdp->end_char_vcols = incr;
   2228    if (is_del && bdp->startspaces) {
   2229      pstart = prev_pstart;
   2230    }
   2231    bdp->textlen = (int)(pend - pstart);
   2232  }
   2233  bdp->textcol = (colnr_T)(pstart - line);
   2234  bdp->textstart = pstart;
   2235  restore_lbr(lbr_saved);
   2236 }
   2237 
   2238 /// Get block text from "start" to "end"
   2239 void charwise_block_prep(pos_T start, pos_T end, struct block_def *bdp, linenr_T lnum,
   2240                         bool inclusive)
   2241 {
   2242  colnr_T startcol = 0;
   2243  colnr_T endcol = MAXCOL;
   2244  colnr_T cs, ce;
   2245  char *p = ml_get(lnum);
   2246  int plen = ml_get_len(lnum);
   2247 
   2248  bdp->startspaces = 0;
   2249  bdp->endspaces = 0;
   2250  bdp->is_oneChar = false;
   2251  bdp->start_char_vcols = 0;
   2252 
   2253  if (lnum == start.lnum) {
   2254    startcol = start.col;
   2255    if (virtual_op) {
   2256      getvcol(curwin, &start, &cs, NULL, &ce);
   2257      if (ce != cs && start.coladd > 0) {
   2258        // Part of a tab selected -- but don't double-count it.
   2259        bdp->start_char_vcols = ce - cs + 1;
   2260        bdp->startspaces = MAX(bdp->start_char_vcols - start.coladd, 0);
   2261        startcol++;
   2262      }
   2263    }
   2264  }
   2265 
   2266  if (lnum == end.lnum) {
   2267    endcol = end.col;
   2268    if (virtual_op) {
   2269      getvcol(curwin, &end, &cs, NULL, &ce);
   2270      if (p[endcol] == NUL || (cs + end.coladd < ce
   2271                               // Don't add space for double-wide
   2272                               // char; endcol will be on last byte
   2273                               // of multi-byte char.
   2274                               && utf_head_off(p, p + endcol) == 0)) {
   2275        if (start.lnum == end.lnum && start.col == end.col) {
   2276          // Special case: inside a single char
   2277          bdp->is_oneChar = true;
   2278          bdp->startspaces = end.coladd - start.coladd + inclusive;
   2279          endcol = startcol;
   2280        } else {
   2281          bdp->endspaces = end.coladd + inclusive;
   2282          endcol -= inclusive;
   2283        }
   2284      }
   2285    }
   2286  }
   2287  if (endcol == MAXCOL) {
   2288    endcol = ml_get_len(lnum);
   2289  }
   2290  if (startcol > endcol || bdp->is_oneChar) {
   2291    bdp->textlen = 0;
   2292  } else {
   2293    bdp->textlen = endcol - startcol + inclusive;
   2294  }
   2295  bdp->textcol = startcol;
   2296  bdp->textstart = startcol <= plen ? p + startcol : p;
   2297 }
   2298 
   2299 /// Handle the add/subtract operator.
   2300 ///
   2301 /// @param[in]  oap      Arguments of operator.
   2302 /// @param[in]  Prenum1  Amount of addition or subtraction.
   2303 /// @param[in]  g_cmd    Prefixed with `g`.
   2304 void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd)
   2305 {
   2306  struct block_def bd;
   2307  ssize_t change_cnt = 0;
   2308  linenr_T amount = Prenum1;
   2309 
   2310  // do_addsub() might trigger re-evaluation of 'foldexpr' halfway, when the
   2311  // buffer is not completely updated yet. Postpone updating folds until before
   2312  // the call to changed_lines().
   2313  disable_fold_update++;
   2314 
   2315  if (!VIsual_active) {
   2316    pos_T pos = curwin->w_cursor;
   2317    if (u_save_cursor() == FAIL) {
   2318      disable_fold_update--;
   2319      return;
   2320    }
   2321    change_cnt = do_addsub(oap->op_type, &pos, 0, amount);
   2322    disable_fold_update--;
   2323    if (change_cnt) {
   2324      changed_lines(curbuf, pos.lnum, 0, pos.lnum + 1, 0, true);
   2325    }
   2326  } else {
   2327    int length;
   2328    pos_T startpos;
   2329 
   2330    if (u_save((linenr_T)(oap->start.lnum - 1),
   2331               (linenr_T)(oap->end.lnum + 1)) == FAIL) {
   2332      disable_fold_update--;
   2333      return;
   2334    }
   2335 
   2336    pos_T pos = oap->start;
   2337    for (; pos.lnum <= oap->end.lnum; pos.lnum++) {
   2338      if (oap->motion_type == kMTBlockWise) {
   2339        // Visual block mode
   2340        block_prep(oap, &bd, pos.lnum, false);
   2341        pos.col = bd.textcol;
   2342        length = bd.textlen;
   2343      } else if (oap->motion_type == kMTLineWise) {
   2344        curwin->w_cursor.col = 0;
   2345        pos.col = 0;
   2346        length = ml_get_len(pos.lnum);
   2347      } else {
   2348        // oap->motion_type == kMTCharWise
   2349        if (pos.lnum == oap->start.lnum && !oap->inclusive) {
   2350          dec(&(oap->end));
   2351        }
   2352        length = ml_get_len(pos.lnum);
   2353        pos.col = 0;
   2354        if (pos.lnum == oap->start.lnum) {
   2355          pos.col += oap->start.col;
   2356          length -= oap->start.col;
   2357        }
   2358        if (pos.lnum == oap->end.lnum) {
   2359          length = ml_get_len(oap->end.lnum);
   2360          oap->end.col = MIN(oap->end.col, length - 1);
   2361          length = oap->end.col - pos.col + 1;
   2362        }
   2363      }
   2364      bool one_change = do_addsub(oap->op_type, &pos, length, amount);
   2365      if (one_change) {
   2366        // Remember the start position of the first change.
   2367        if (change_cnt == 0) {
   2368          startpos = curbuf->b_op_start;
   2369        }
   2370        change_cnt++;
   2371      }
   2372 
   2373      if (g_cmd && one_change) {
   2374        amount += Prenum1;
   2375      }
   2376    }
   2377 
   2378    disable_fold_update--;
   2379    if (change_cnt) {
   2380      changed_lines(curbuf, oap->start.lnum, 0, oap->end.lnum + 1, 0, true);
   2381    }
   2382 
   2383    if (!change_cnt && oap->is_VIsual) {
   2384      // No change: need to remove the Visual selection
   2385      redraw_curbuf_later(UPD_INVERTED);
   2386    }
   2387 
   2388    // Set '[ mark if something changed. Keep the last end
   2389    // position from do_addsub().
   2390    if (change_cnt > 0 && (cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
   2391      curbuf->b_op_start = startpos;
   2392    }
   2393 
   2394    if (change_cnt > p_report) {
   2395      smsg(0, NGETTEXT("%" PRId64 " lines changed", "%" PRId64 " lines changed", change_cnt),
   2396           (int64_t)change_cnt);
   2397    }
   2398  }
   2399 }
   2400 
   2401 /// Add or subtract from a number in a line.
   2402 ///
   2403 /// @param op_type OP_NR_ADD or OP_NR_SUB.
   2404 /// @param pos     Cursor position.
   2405 /// @param length  Target number length.
   2406 /// @param Prenum1 Amount of addition or subtraction.
   2407 ///
   2408 /// @return true if some character was changed.
   2409 bool do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
   2410 {
   2411  int pre;  // 'X' or 'x': hex; '0': octal; 'B' or 'b': bin
   2412  static bool hexupper = false;  // 0xABC
   2413  uvarnumber_T n;
   2414  bool blank_unsigned = false;  // blank: treat as unsigned?
   2415  bool negative = false;
   2416  bool was_positive = true;
   2417  bool visual = VIsual_active;
   2418  bool did_change = false;
   2419  pos_T save_cursor = curwin->w_cursor;
   2420  int maxlen = 0;
   2421  pos_T startpos;
   2422  pos_T endpos;
   2423  colnr_T save_coladd = 0;
   2424 
   2425  const bool do_hex = vim_strchr(curbuf->b_p_nf, 'x') != NULL;       // "heX"
   2426  const bool do_oct = vim_strchr(curbuf->b_p_nf, 'o') != NULL;       // "Octal"
   2427  const bool do_bin = vim_strchr(curbuf->b_p_nf, 'b') != NULL;       // "Bin"
   2428  const bool do_alpha = vim_strchr(curbuf->b_p_nf, 'p') != NULL;     // "alPha"
   2429  const bool do_unsigned = vim_strchr(curbuf->b_p_nf, 'u') != NULL;  // "Unsigned"
   2430  const bool do_blank = vim_strchr(curbuf->b_p_nf, 'k') != NULL;     // "blanK"
   2431 
   2432  if (virtual_active(curwin)) {
   2433    save_coladd = pos->coladd;
   2434    pos->coladd = 0;
   2435  }
   2436 
   2437  curwin->w_cursor = *pos;
   2438  char *ptr = ml_get(pos->lnum);
   2439  int linelen = ml_get_len(pos->lnum);
   2440  int col = pos->col;
   2441 
   2442  if (col + !!save_coladd >= linelen) {
   2443    goto theend;
   2444  }
   2445 
   2446  // First check if we are on a hexadecimal number, after the "0x".
   2447  if (!VIsual_active) {
   2448    if (do_bin) {
   2449      while (col > 0 && ascii_isbdigit(ptr[col])) {
   2450        col--;
   2451        col -= utf_head_off(ptr, ptr + col);
   2452      }
   2453    }
   2454 
   2455    if (do_hex) {
   2456      while (col > 0 && ascii_isxdigit(ptr[col])) {
   2457        col--;
   2458        col -= utf_head_off(ptr, ptr + col);
   2459      }
   2460    }
   2461    if (do_bin
   2462        && do_hex
   2463        && !((col > 0
   2464              && (ptr[col] == 'X' || ptr[col] == 'x')
   2465              && ptr[col - 1] == '0'
   2466              && !utf_head_off(ptr, ptr + col - 1)
   2467              && ascii_isxdigit(ptr[col + 1])))) {
   2468      // In case of binary/hexadecimal pattern overlap match, rescan
   2469 
   2470      col = curwin->w_cursor.col;
   2471 
   2472      while (col > 0 && ascii_isdigit(ptr[col])) {
   2473        col--;
   2474        col -= utf_head_off(ptr, ptr + col);
   2475      }
   2476    }
   2477 
   2478    if ((do_hex
   2479         && col > 0
   2480         && (ptr[col] == 'X' || ptr[col] == 'x')
   2481         && ptr[col - 1] == '0'
   2482         && !utf_head_off(ptr, ptr + col - 1)
   2483         && ascii_isxdigit(ptr[col + 1]))
   2484        || (do_bin
   2485            && col > 0
   2486            && (ptr[col] == 'B' || ptr[col] == 'b')
   2487            && ptr[col - 1] == '0'
   2488            && !utf_head_off(ptr, ptr + col - 1)
   2489            && ascii_isbdigit(ptr[col + 1]))) {
   2490      // Found hexadecimal or binary number, move to its start.
   2491      col--;
   2492      col -= utf_head_off(ptr, ptr + col);
   2493    } else {
   2494      // Search forward and then backward to find the start of number.
   2495      col = pos->col;
   2496 
   2497      while (ptr[col] != NUL
   2498             && !ascii_isdigit(ptr[col])
   2499             && !(do_alpha && ASCII_ISALPHA(ptr[col]))) {
   2500        col++;
   2501      }
   2502 
   2503      while (col > 0
   2504             && ascii_isdigit(ptr[col - 1])
   2505             && !(do_alpha && ASCII_ISALPHA(ptr[col]))) {
   2506        col--;
   2507      }
   2508    }
   2509  }
   2510 
   2511  if (visual) {
   2512    while (ptr[col] != NUL && length > 0 && !ascii_isdigit(ptr[col])
   2513           && !(do_alpha && ASCII_ISALPHA(ptr[col]))) {
   2514      int mb_len = utfc_ptr2len(ptr + col);
   2515 
   2516      col += mb_len;
   2517      length -= mb_len;
   2518    }
   2519 
   2520    if (length == 0) {
   2521      goto theend;
   2522    }
   2523 
   2524    if (col > pos->col && ptr[col - 1] == '-'
   2525        && !utf_head_off(ptr, ptr + col - 1)
   2526        && !do_unsigned) {
   2527      if (do_blank && col >= 2 && !ascii_iswhite(ptr[col - 2])) {
   2528        blank_unsigned = true;
   2529      } else {
   2530        negative = true;
   2531        was_positive = false;
   2532      }
   2533    }
   2534  }
   2535 
   2536  // If a number was found, and saving for undo works, replace the number.
   2537  int firstdigit = (uint8_t)ptr[col];
   2538  if (!ascii_isdigit(firstdigit) && !(do_alpha && ASCII_ISALPHA(firstdigit))) {
   2539    beep_flush();
   2540    goto theend;
   2541  }
   2542 
   2543  if (do_alpha && ASCII_ISALPHA(firstdigit)) {
   2544    // decrement or increment alphabetic character
   2545    if (op_type == OP_NR_SUB) {
   2546      if (CHAR_ORD(firstdigit) < Prenum1) {
   2547        firstdigit = isupper(firstdigit) ? 'A' : 'a';
   2548      } else {
   2549        firstdigit -= (int)Prenum1;
   2550      }
   2551    } else {
   2552      if (26 - CHAR_ORD(firstdigit) - 1 < Prenum1) {
   2553        firstdigit = isupper(firstdigit) ? 'Z' : 'z';
   2554      } else {
   2555        firstdigit += (int)Prenum1;
   2556      }
   2557    }
   2558    curwin->w_cursor.col = col;
   2559    startpos = curwin->w_cursor;
   2560    did_change = true;
   2561    del_char(false);
   2562    ins_char(firstdigit);
   2563    endpos = curwin->w_cursor;
   2564    curwin->w_cursor.col = col;
   2565  } else {
   2566    if (col > 0 && ptr[col - 1] == '-'
   2567        && !utf_head_off(ptr, ptr + col - 1)
   2568        && !visual
   2569        && !do_unsigned) {
   2570      if (do_blank && col >= 2 && !ascii_iswhite(ptr[col - 2])) {
   2571        blank_unsigned = true;
   2572      } else {
   2573        // negative number
   2574        col--;
   2575        negative = true;
   2576      }
   2577    }
   2578 
   2579    // get the number value (unsigned)
   2580    if (visual && VIsual_mode != 'V') {
   2581      maxlen = curbuf->b_visual.vi_curswant == MAXCOL ? linelen - col : length;
   2582    }
   2583 
   2584    bool overflow = false;
   2585    vim_str2nr(ptr + col, &pre, &length,
   2586               0 + (do_bin ? STR2NR_BIN : 0)
   2587               + (do_oct ? STR2NR_OCT : 0)
   2588               + (do_hex ? STR2NR_HEX : 0),
   2589               NULL, &n, maxlen, false, &overflow);
   2590 
   2591    // ignore leading '-' for hex, octal and bin numbers
   2592    if (pre && negative) {
   2593      col++;
   2594      length--;
   2595      negative = false;
   2596    }
   2597 
   2598    // add or subtract
   2599    bool subtract = false;
   2600    if (op_type == OP_NR_SUB) {
   2601      subtract ^= true;
   2602    }
   2603    if (negative) {
   2604      subtract ^= true;
   2605    }
   2606 
   2607    uvarnumber_T oldn = n;
   2608 
   2609    if (!overflow) {  // if number is too big don't add/subtract
   2610      n = subtract ? n - (uvarnumber_T)Prenum1
   2611                   : n + (uvarnumber_T)Prenum1;
   2612    }
   2613 
   2614    // handle wraparound for decimal numbers
   2615    if (!pre) {
   2616      if (subtract) {
   2617        if (n > oldn) {
   2618          n = 1 + (n ^ (uvarnumber_T)(-1));
   2619          negative ^= true;
   2620        }
   2621      } else {
   2622        // add
   2623        if (n < oldn) {
   2624          n = (n ^ (uvarnumber_T)(-1));
   2625          negative ^= true;
   2626        }
   2627      }
   2628      if (n == 0) {
   2629        negative = false;
   2630      }
   2631    }
   2632 
   2633    if ((do_unsigned || blank_unsigned) && negative) {
   2634      if (subtract) {
   2635        // sticking at zero.
   2636        n = 0;
   2637      } else {
   2638        // sticking at 2^64 - 1.
   2639        n = (uvarnumber_T)(-1);
   2640      }
   2641      negative = false;
   2642    }
   2643 
   2644    if (visual && !was_positive && !negative && col > 0) {
   2645      // need to remove the '-'
   2646      col--;
   2647      length++;
   2648    }
   2649 
   2650    // Delete the old number.
   2651    curwin->w_cursor.col = col;
   2652    startpos = curwin->w_cursor;
   2653    did_change = true;
   2654    int todel = length;
   2655    int c = gchar_cursor();
   2656 
   2657    // Don't include the '-' in the length, only the length of the part
   2658    // after it is kept the same.
   2659    if (c == '-') {
   2660      length--;
   2661    }
   2662    while (todel-- > 0) {
   2663      if (c < 0x100 && isalpha(c)) {
   2664        hexupper = isupper(c);
   2665      }
   2666      // del_char() will mark line needing displaying
   2667      del_char(false);
   2668      c = gchar_cursor();
   2669    }
   2670 
   2671    // Prepare the leading characters in buf1[].
   2672    // When there are many leading zeros it could be very long.
   2673    // Allocate a bit too much.
   2674    char *buf1 = xmalloc((size_t)length + NUMBUFLEN);
   2675    ptr = buf1;
   2676    if (negative && (!visual || was_positive)) {
   2677      *ptr++ = '-';
   2678    }
   2679    if (pre) {
   2680      *ptr++ = '0';
   2681      length--;
   2682    }
   2683    if (pre == 'b' || pre == 'B' || pre == 'x' || pre == 'X') {
   2684      *ptr++ = (char)pre;
   2685      length--;
   2686    }
   2687 
   2688    // Put the number characters in buf2[].
   2689    char buf2[NUMBUFLEN];
   2690    int buf2len = 0;
   2691    if (pre == 'b' || pre == 'B') {
   2692      size_t bits = 0;
   2693 
   2694      // leading zeros
   2695      for (bits = 8 * sizeof(n); bits > 0; bits--) {
   2696        if ((n >> (bits - 1)) & 0x1) {
   2697          break;
   2698        }
   2699      }
   2700 
   2701      while (bits > 0 && buf2len < NUMBUFLEN - 1) {
   2702        buf2[buf2len++] = ((n >> --bits) & 0x1) ? '1' : '0';
   2703      }
   2704 
   2705      buf2[buf2len] = NUL;
   2706    } else if (pre == 0) {
   2707      buf2len = vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIu64, (uint64_t)n);
   2708    } else if (pre == '0') {
   2709      buf2len = vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIo64, (uint64_t)n);
   2710    } else if (hexupper) {
   2711      buf2len = vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIX64, (uint64_t)n);
   2712    } else {
   2713      buf2len = vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIx64, (uint64_t)n);
   2714    }
   2715    length -= buf2len;
   2716 
   2717    // Adjust number of zeros to the new number of digits, so the
   2718    // total length of the number remains the same.
   2719    // Don't do this when
   2720    // the result may look like an octal number.
   2721    if (firstdigit == '0' && !(do_oct && pre == 0)) {
   2722      while (length-- > 0) {
   2723        *ptr++ = '0';
   2724      }
   2725    }
   2726    *ptr = NUL;
   2727    int buf1len = (int)(ptr - buf1);
   2728 
   2729    STRCPY(buf1 + buf1len, buf2);
   2730    buf1len += buf2len;
   2731 
   2732    ins_str(buf1, (size_t)buf1len);  // insert the new number
   2733    xfree(buf1);
   2734 
   2735    endpos = curwin->w_cursor;
   2736    if (curwin->w_cursor.col) {
   2737      curwin->w_cursor.col--;
   2738    }
   2739  }
   2740 
   2741  if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
   2742    // set the '[ and '] marks
   2743    curbuf->b_op_start = startpos;
   2744    curbuf->b_op_end = endpos;
   2745    if (curbuf->b_op_end.col > 0) {
   2746      curbuf->b_op_end.col--;
   2747    }
   2748  }
   2749 
   2750 theend:
   2751  if (visual) {
   2752    curwin->w_cursor = save_cursor;
   2753  } else if (did_change) {
   2754    curwin->w_set_curswant = true;
   2755  } else if (virtual_active(curwin)) {
   2756    curwin->w_cursor.coladd = save_coladd;
   2757  }
   2758 
   2759  return did_change;
   2760 }
   2761 
   2762 void clear_oparg(oparg_T *oap)
   2763 {
   2764  CLEAR_POINTER(oap);
   2765 }
   2766 
   2767 ///  Count the number of bytes, characters and "words" in a line.
   2768 ///
   2769 ///  "Words" are counted by looking for boundaries between non-space and
   2770 ///  space characters.  (it seems to produce results that match 'wc'.)
   2771 ///
   2772 ///  Return value is byte count; word count for the line is added to "*wc".
   2773 ///  Char count is added to "*cc".
   2774 ///
   2775 ///  The function will only examine the first "limit" characters in the
   2776 ///  line, stopping if it encounters an end-of-line (NUL byte).  In that
   2777 ///  case, eol_size will be added to the character count to account for
   2778 ///  the size of the EOL character.
   2779 static varnumber_T line_count_info(char *line, varnumber_T *wc, varnumber_T *cc, varnumber_T limit,
   2780                                   int eol_size)
   2781 {
   2782  varnumber_T i;
   2783  varnumber_T words = 0;
   2784  varnumber_T chars = 0;
   2785  bool is_word = false;
   2786 
   2787  for (i = 0; i < limit && line[i] != NUL;) {
   2788    if (is_word) {
   2789      if (ascii_isspace(line[i])) {
   2790        words++;
   2791        is_word = false;
   2792      }
   2793    } else if (!ascii_isspace(line[i])) {
   2794      is_word = true;
   2795    }
   2796    chars++;
   2797    i += utfc_ptr2len(line + i);
   2798  }
   2799 
   2800  if (is_word) {
   2801    words++;
   2802  }
   2803  *wc += words;
   2804 
   2805  // Add eol_size if the end of line was reached before hitting limit.
   2806  if (i < limit && line[i] == NUL) {
   2807    i += eol_size;
   2808    chars += eol_size;
   2809  }
   2810  *cc += chars;
   2811  return i;
   2812 }
   2813 
   2814 /// Give some info about the position of the cursor (for "g CTRL-G").
   2815 /// In Visual mode, give some info about the selected region.  (In this case,
   2816 /// the *_count_cursor variables store running totals for the selection.)
   2817 ///
   2818 /// @param dict  when not NULL, store the info there instead of showing it.
   2819 void cursor_pos_info(dict_T *dict)
   2820 {
   2821  char buf1[50];
   2822  char buf2[40];
   2823  varnumber_T byte_count = 0;
   2824  varnumber_T bom_count = 0;
   2825  varnumber_T byte_count_cursor = 0;
   2826  varnumber_T char_count = 0;
   2827  varnumber_T char_count_cursor = 0;
   2828  varnumber_T word_count = 0;
   2829  varnumber_T word_count_cursor = 0;
   2830  pos_T min_pos, max_pos;
   2831  oparg_T oparg;
   2832  struct block_def bd;
   2833  const int l_VIsual_active = VIsual_active;
   2834  const int l_VIsual_mode = VIsual_mode;
   2835 
   2836  // Compute the length of the file in characters.
   2837  if (curbuf->b_ml.ml_flags & ML_EMPTY) {
   2838    if (dict == NULL) {
   2839      msg(_(no_lines_msg), 0);
   2840      return;
   2841    }
   2842  } else {
   2843    int eol_size;
   2844    varnumber_T last_check = 100000;
   2845    int line_count_selected = 0;
   2846    if (get_fileformat(curbuf) == EOL_DOS) {
   2847      eol_size = 2;
   2848    } else {
   2849      eol_size = 1;
   2850    }
   2851 
   2852    if (l_VIsual_active) {
   2853      if (lt(VIsual, curwin->w_cursor)) {
   2854        min_pos = VIsual;
   2855        max_pos = curwin->w_cursor;
   2856      } else {
   2857        min_pos = curwin->w_cursor;
   2858        max_pos = VIsual;
   2859      }
   2860      if (*p_sel == 'e' && max_pos.col > 0) {
   2861        max_pos.col--;
   2862      }
   2863 
   2864      if (l_VIsual_mode == Ctrl_V) {
   2865        char *const saved_sbr = p_sbr;
   2866        char *const saved_w_sbr = curwin->w_p_sbr;
   2867 
   2868        // Make 'sbr' empty for a moment to get the correct size.
   2869        p_sbr = empty_string_option;
   2870        curwin->w_p_sbr = empty_string_option;
   2871        oparg.is_VIsual = true;
   2872        oparg.motion_type = kMTBlockWise;
   2873        oparg.op_type = OP_NOP;
   2874        getvcols(curwin, &min_pos, &max_pos, &oparg.start_vcol, &oparg.end_vcol);
   2875        p_sbr = saved_sbr;
   2876        curwin->w_p_sbr = saved_w_sbr;
   2877        if (curwin->w_curswant == MAXCOL) {
   2878          oparg.end_vcol = MAXCOL;
   2879        }
   2880        // Swap the start, end vcol if needed
   2881        if (oparg.end_vcol < oparg.start_vcol) {
   2882          oparg.end_vcol += oparg.start_vcol;
   2883          oparg.start_vcol = oparg.end_vcol - oparg.start_vcol;
   2884          oparg.end_vcol -= oparg.start_vcol;
   2885        }
   2886      }
   2887      line_count_selected = max_pos.lnum - min_pos.lnum + 1;
   2888    }
   2889 
   2890    for (linenr_T lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) {
   2891      // Check for a CTRL-C every 100000 characters.
   2892      if (byte_count > last_check) {
   2893        os_breakcheck();
   2894        if (got_int) {
   2895          return;
   2896        }
   2897        last_check = byte_count + 100000;
   2898      }
   2899 
   2900      // Do extra processing for VIsual mode.
   2901      if (l_VIsual_active
   2902          && lnum >= min_pos.lnum && lnum <= max_pos.lnum) {
   2903        char *s = NULL;
   2904        int len = 0;
   2905 
   2906        switch (l_VIsual_mode) {
   2907        case Ctrl_V:
   2908          virtual_op = virtual_active(curwin);
   2909          block_prep(&oparg, &bd, lnum, false);
   2910          virtual_op = kNone;
   2911          s = bd.textstart;
   2912          len = bd.textlen;
   2913          break;
   2914        case 'V':
   2915          s = ml_get(lnum);
   2916          len = MAXCOL;
   2917          break;
   2918        case 'v': {
   2919          colnr_T start_col = (lnum == min_pos.lnum)
   2920                              ? min_pos.col : 0;
   2921          colnr_T end_col = (lnum == max_pos.lnum)
   2922                            ? max_pos.col - start_col + 1 : MAXCOL;
   2923 
   2924          s = ml_get(lnum) + start_col;
   2925          len = end_col;
   2926        }
   2927        break;
   2928        }
   2929        if (s != NULL) {
   2930          byte_count_cursor += line_count_info(s, &word_count_cursor,
   2931                                               &char_count_cursor, len, eol_size);
   2932          if (lnum == curbuf->b_ml.ml_line_count
   2933              && !curbuf->b_p_eol
   2934              && (curbuf->b_p_bin || !curbuf->b_p_fixeol)
   2935              && (int)strlen(s) < len) {
   2936            byte_count_cursor -= eol_size;
   2937          }
   2938        }
   2939      } else {
   2940        // In non-visual mode, check for the line the cursor is on
   2941        if (lnum == curwin->w_cursor.lnum) {
   2942          word_count_cursor += word_count;
   2943          char_count_cursor += char_count;
   2944          byte_count_cursor = byte_count
   2945                              + line_count_info(ml_get(lnum), &word_count_cursor,
   2946                                                &char_count_cursor,
   2947                                                (varnumber_T)curwin->w_cursor.col + 1,
   2948                                                eol_size);
   2949        }
   2950      }
   2951      // Add to the running totals
   2952      byte_count += line_count_info(ml_get(lnum), &word_count, &char_count,
   2953                                    (varnumber_T)MAXCOL, eol_size);
   2954    }
   2955 
   2956    // Correction for when last line doesn't have an EOL.
   2957    if (!curbuf->b_p_eol && (curbuf->b_p_bin || !curbuf->b_p_fixeol)) {
   2958      byte_count -= eol_size;
   2959    }
   2960 
   2961    if (dict == NULL) {
   2962      if (l_VIsual_active) {
   2963        if (l_VIsual_mode == Ctrl_V && curwin->w_curswant < MAXCOL) {
   2964          getvcols(curwin, &min_pos, &max_pos, &min_pos.col, &max_pos.col);
   2965          int64_t cols;
   2966          STRICT_SUB(oparg.end_vcol + 1, oparg.start_vcol, &cols, int64_t);
   2967          vim_snprintf(buf1, sizeof(buf1), _("%" PRId64 " Cols; "),
   2968                       cols);
   2969        } else {
   2970          buf1[0] = NUL;
   2971        }
   2972 
   2973        if (char_count_cursor == byte_count_cursor
   2974            && char_count == byte_count) {
   2975          vim_snprintf(IObuff, IOSIZE,
   2976                       _("Selected %s%" PRId64 " of %" PRId64 " Lines;"
   2977                         " %" PRId64 " of %" PRId64 " Words;"
   2978                         " %" PRId64 " of %" PRId64 " Bytes"),
   2979                       buf1, (int64_t)line_count_selected,
   2980                       (int64_t)curbuf->b_ml.ml_line_count,
   2981                       (int64_t)word_count_cursor, (int64_t)word_count,
   2982                       (int64_t)byte_count_cursor, (int64_t)byte_count);
   2983        } else {
   2984          vim_snprintf(IObuff, IOSIZE,
   2985                       _("Selected %s%" PRId64 " of %" PRId64 " Lines;"
   2986                         " %" PRId64 " of %" PRId64 " Words;"
   2987                         " %" PRId64 " of %" PRId64 " Chars;"
   2988                         " %" PRId64 " of %" PRId64 " Bytes"),
   2989                       buf1, (int64_t)line_count_selected,
   2990                       (int64_t)curbuf->b_ml.ml_line_count,
   2991                       (int64_t)word_count_cursor, (int64_t)word_count,
   2992                       (int64_t)char_count_cursor, (int64_t)char_count,
   2993                       (int64_t)byte_count_cursor, (int64_t)byte_count);
   2994        }
   2995      } else {
   2996        char *p = get_cursor_line_ptr();
   2997        validate_virtcol(curwin);
   2998        col_print(buf1, sizeof(buf1), (int)curwin->w_cursor.col + 1,
   2999                  (int)curwin->w_virtcol + 1);
   3000        col_print(buf2, sizeof(buf2), get_cursor_line_len(), linetabsize_str(p));
   3001 
   3002        if (char_count_cursor == byte_count_cursor
   3003            && char_count == byte_count) {
   3004          vim_snprintf(IObuff, IOSIZE,
   3005                       _("Col %s of %s; Line %" PRId64 " of %" PRId64 ";"
   3006                         " Word %" PRId64 " of %" PRId64 ";"
   3007                         " Byte %" PRId64 " of %" PRId64 ""),
   3008                       buf1, buf2,
   3009                       (int64_t)curwin->w_cursor.lnum,
   3010                       (int64_t)curbuf->b_ml.ml_line_count,
   3011                       (int64_t)word_count_cursor, (int64_t)word_count,
   3012                       (int64_t)byte_count_cursor, (int64_t)byte_count);
   3013        } else {
   3014          vim_snprintf(IObuff, IOSIZE,
   3015                       _("Col %s of %s; Line %" PRId64 " of %" PRId64 ";"
   3016                         " Word %" PRId64 " of %" PRId64 ";"
   3017                         " Char %" PRId64 " of %" PRId64 ";"
   3018                         " Byte %" PRId64 " of %" PRId64 ""),
   3019                       buf1, buf2,
   3020                       (int64_t)curwin->w_cursor.lnum,
   3021                       (int64_t)curbuf->b_ml.ml_line_count,
   3022                       (int64_t)word_count_cursor, (int64_t)word_count,
   3023                       (int64_t)char_count_cursor, (int64_t)char_count,
   3024                       (int64_t)byte_count_cursor, (int64_t)byte_count);
   3025        }
   3026      }
   3027    }
   3028 
   3029    bom_count = bomb_size();
   3030    if (dict == NULL && bom_count > 0) {
   3031      const size_t len = strlen(IObuff);
   3032      vim_snprintf(IObuff + len, IOSIZE - len,
   3033                   _("(+%" PRId64 " for BOM)"), (int64_t)bom_count);
   3034    }
   3035    if (dict == NULL) {
   3036      // Don't shorten this message, the user asked for it.
   3037      char *p = p_shm;
   3038      p_shm = "";
   3039      if (p_ch < 1) {
   3040        msg_start();
   3041        msg_scroll = true;
   3042      }
   3043      msg(IObuff, 0);
   3044      p_shm = p;
   3045    }
   3046  }
   3047 
   3048  if (dict != NULL) {
   3049    // Don't shorten this message, the user asked for it.
   3050    tv_dict_add_nr(dict, S_LEN("words"), word_count);
   3051    tv_dict_add_nr(dict, S_LEN("chars"), char_count);
   3052    tv_dict_add_nr(dict, S_LEN("bytes"), byte_count + bom_count);
   3053 
   3054    STATIC_ASSERT(sizeof("visual") == sizeof("cursor"),
   3055                  "key_len argument in tv_dict_add_nr is wrong");
   3056    tv_dict_add_nr(dict, l_VIsual_active ? "visual_bytes" : "cursor_bytes",
   3057                   sizeof("visual_bytes") - 1, byte_count_cursor);
   3058    tv_dict_add_nr(dict, l_VIsual_active ? "visual_chars" : "cursor_chars",
   3059                   sizeof("visual_chars") - 1, char_count_cursor);
   3060    tv_dict_add_nr(dict, l_VIsual_active ? "visual_words" : "cursor_words",
   3061                   sizeof("visual_words") - 1, word_count_cursor);
   3062  }
   3063 }
   3064 
   3065 /// Handle indent and format operators and visual mode ":".
   3066 static void op_colon(oparg_T *oap)
   3067 {
   3068  stuffcharReadbuff(':');
   3069  if (oap->is_VIsual) {
   3070    stuffReadbuff("'<,'>");
   3071  } else {
   3072    // Make the range look nice, so it can be repeated.
   3073    if (oap->start.lnum == curwin->w_cursor.lnum) {
   3074      stuffcharReadbuff('.');
   3075    } else {
   3076      stuffnumReadbuff(oap->start.lnum);
   3077    }
   3078 
   3079    // When using !! on a closed fold the range ".!" works best to operate
   3080    // on, it will be made the whole closed fold later.
   3081    linenr_T endOfStartFold = oap->start.lnum;
   3082    hasFolding(curwin, oap->start.lnum, NULL, &endOfStartFold);
   3083    if (oap->end.lnum != oap->start.lnum && oap->end.lnum != endOfStartFold) {
   3084      // Make it a range with the end line.
   3085      stuffcharReadbuff(',');
   3086      if (oap->end.lnum == curwin->w_cursor.lnum) {
   3087        stuffcharReadbuff('.');
   3088      } else if (oap->end.lnum == curbuf->b_ml.ml_line_count) {
   3089        stuffcharReadbuff('$');
   3090      } else if (oap->start.lnum == curwin->w_cursor.lnum
   3091                 // do not use ".+number" for a closed fold, it would count
   3092                 // folded lines twice
   3093                 && !hasFolding(curwin, oap->end.lnum, NULL, NULL)) {
   3094        stuffReadbuff(".+");
   3095        stuffnumReadbuff(oap->line_count - 1);
   3096      } else {
   3097        stuffnumReadbuff(oap->end.lnum);
   3098      }
   3099    }
   3100  }
   3101  if (oap->op_type != OP_COLON) {
   3102    stuffReadbuff("!");
   3103  }
   3104  if (oap->op_type == OP_INDENT) {
   3105    stuffReadbuff(get_equalprg());
   3106    stuffReadbuff("\n");
   3107  } else if (oap->op_type == OP_FORMAT) {
   3108    if (*curbuf->b_p_fp != NUL) {
   3109      stuffReadbuff(curbuf->b_p_fp);
   3110    } else if (*p_fp != NUL) {
   3111      stuffReadbuff(p_fp);
   3112    } else {
   3113      stuffReadbuff("fmt");
   3114    }
   3115    stuffReadbuff("\n']");
   3116  }
   3117 
   3118  // do_cmdline() does the rest
   3119 }
   3120 
   3121 /// callback function for 'operatorfunc'
   3122 static Callback opfunc_cb;
   3123 
   3124 /// Process the 'operatorfunc' option value.
   3125 const char *did_set_operatorfunc(optset_T *args FUNC_ATTR_UNUSED)
   3126 {
   3127  if (option_set_callback_func(p_opfunc, &opfunc_cb) == FAIL) {
   3128    return e_invarg;
   3129  }
   3130  return NULL;
   3131 }
   3132 
   3133 #if defined(EXITFREE)
   3134 void free_operatorfunc_option(void)
   3135 {
   3136  callback_free(&opfunc_cb);
   3137 }
   3138 #endif
   3139 
   3140 /// Mark the global 'operatorfunc' callback with "copyID" so that it is not
   3141 /// garbage collected.
   3142 bool set_ref_in_opfunc(int copyID)
   3143 {
   3144  return set_ref_in_callback(&opfunc_cb, copyID, NULL, NULL);
   3145 }
   3146 
   3147 /// Handle the "g@" operator: call 'operatorfunc'.
   3148 static void op_function(const oparg_T *oap)
   3149  FUNC_ATTR_NONNULL_ALL
   3150 {
   3151  const pos_T orig_start = curbuf->b_op_start;
   3152  const pos_T orig_end = curbuf->b_op_end;
   3153 
   3154  if (*p_opfunc == NUL) {
   3155    emsg(_("E774: 'operatorfunc' is empty"));
   3156  } else {
   3157    // Set '[ and '] marks to text to be operated on.
   3158    curbuf->b_op_start = oap->start;
   3159    curbuf->b_op_end = oap->end;
   3160    if (oap->motion_type != kMTLineWise && !oap->inclusive) {
   3161      // Exclude the end position.
   3162      decl(&curbuf->b_op_end);
   3163    }
   3164 
   3165    typval_T argv[2];
   3166    argv[0].v_type = VAR_STRING;
   3167    argv[1].v_type = VAR_UNKNOWN;
   3168    argv[0].vval.v_string =
   3169      (char *)(((const char *const[]) {
   3170      [kMTBlockWise] = "block",
   3171      [kMTLineWise] = "line",
   3172      [kMTCharWise] = "char",
   3173    })[oap->motion_type]);
   3174 
   3175    // Reset virtual_op so that 'virtualedit' can be changed in the
   3176    // function.
   3177    const TriState save_virtual_op = virtual_op;
   3178    virtual_op = kNone;
   3179 
   3180    // Reset finish_op so that mode() returns the right value.
   3181    const bool save_finish_op = finish_op;
   3182    finish_op = false;
   3183 
   3184    typval_T rettv;
   3185    if (callback_call(&opfunc_cb, 1, argv, &rettv)) {
   3186      tv_clear(&rettv);
   3187    }
   3188 
   3189    virtual_op = save_virtual_op;
   3190    finish_op = save_finish_op;
   3191    if (cmdmod.cmod_flags & CMOD_LOCKMARKS) {
   3192      curbuf->b_op_start = orig_start;
   3193      curbuf->b_op_end = orig_end;
   3194    }
   3195  }
   3196 }
   3197 
   3198 /// Calculate start/end virtual columns for operating in block mode.
   3199 ///
   3200 /// @param initial  when true: adjust position for 'selectmode'
   3201 static void get_op_vcol(oparg_T *oap, colnr_T redo_VIsual_vcol, bool initial)
   3202 {
   3203  colnr_T start;
   3204  colnr_T end;
   3205 
   3206  if (VIsual_mode != Ctrl_V
   3207      || (!initial && oap->end.col < curwin->w_view_width)) {
   3208    return;
   3209  }
   3210 
   3211  oap->motion_type = kMTBlockWise;
   3212 
   3213  // prevent from moving onto a trail byte
   3214  mark_mb_adjustpos(curwin->w_buffer, &oap->end);
   3215 
   3216  getvvcol(curwin, &(oap->start), &oap->start_vcol, NULL, &oap->end_vcol);
   3217  if (!redo_VIsual_busy) {
   3218    getvvcol(curwin, &(oap->end), &start, NULL, &end);
   3219 
   3220    oap->start_vcol = MIN(oap->start_vcol, start);
   3221    if (end > oap->end_vcol) {
   3222      if (initial && *p_sel == 'e'
   3223          && start >= 1
   3224          && start - 1 >= oap->end_vcol) {
   3225        oap->end_vcol = start - 1;
   3226      } else {
   3227        oap->end_vcol = end;
   3228      }
   3229    }
   3230  }
   3231 
   3232  // if '$' was used, get oap->end_vcol from longest line
   3233  if (curwin->w_curswant == MAXCOL) {
   3234    curwin->w_cursor.col = MAXCOL;
   3235    oap->end_vcol = 0;
   3236    for (curwin->w_cursor.lnum = oap->start.lnum;
   3237         curwin->w_cursor.lnum <= oap->end.lnum; curwin->w_cursor.lnum++) {
   3238      getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &end);
   3239      oap->end_vcol = MAX(oap->end_vcol, end);
   3240    }
   3241  } else if (redo_VIsual_busy) {
   3242    oap->end_vcol = oap->start_vcol + redo_VIsual_vcol - 1;
   3243  }
   3244 
   3245  // Correct oap->end.col and oap->start.col to be the
   3246  // upper-left and lower-right corner of the block area.
   3247  //
   3248  // (Actually, this does convert column positions into character
   3249  // positions)
   3250  curwin->w_cursor.lnum = oap->end.lnum;
   3251  coladvance(curwin, oap->end_vcol);
   3252  oap->end = curwin->w_cursor;
   3253 
   3254  curwin->w_cursor = oap->start;
   3255  coladvance(curwin, oap->start_vcol);
   3256  oap->start = curwin->w_cursor;
   3257 }
   3258 
   3259 /// Information for redoing the previous Visual selection.
   3260 typedef struct {
   3261  int rv_mode;             ///< 'v', 'V', or Ctrl-V
   3262  linenr_T rv_line_count;  ///< number of lines
   3263  colnr_T rv_vcol;         ///< number of cols or end column
   3264  int rv_count;            ///< count for Visual operator
   3265  int rv_arg;              ///< extra argument
   3266 } redo_VIsual_T;
   3267 
   3268 static bool is_ex_cmdchar(cmdarg_T *cap)
   3269 {
   3270  return cap->cmdchar == ':' || cap->cmdchar == K_COMMAND;
   3271 }
   3272 
   3273 /// Handle an operator after Visual mode or when the movement is finished.
   3274 /// "gui_yank" is true when yanking text for the clipboard.
   3275 void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
   3276 {
   3277  oparg_T *oap = cap->oap;
   3278  int lbr_saved = curwin->w_p_lbr;
   3279 
   3280  // The visual area is remembered for redo
   3281  static redo_VIsual_T redo_VIsual = { NUL, 0, 0, 0, 0 };
   3282 
   3283  pos_T old_cursor = curwin->w_cursor;
   3284 
   3285  // If an operation is pending, handle it...
   3286  if ((finish_op
   3287       || VIsual_active)
   3288      && oap->op_type != OP_NOP) {
   3289    bool empty_region_error;
   3290    int restart_edit_save;
   3291    bool include_line_break = false;
   3292    // Yank can be redone when 'y' is in 'cpoptions', but not when yanking
   3293    // for the clipboard.
   3294    const bool redo_yank = vim_strchr(p_cpo, CPO_YANK) != NULL && !gui_yank;
   3295 
   3296    // Avoid a problem with unwanted linebreaks in block mode
   3297    reset_lbr();
   3298    oap->is_VIsual = VIsual_active;
   3299    if (oap->motion_force == 'V') {
   3300      oap->motion_type = kMTLineWise;
   3301    } else if (oap->motion_force == 'v') {
   3302      // If the motion was linewise, "inclusive" will not have been set.
   3303      // Use "exclusive" to be consistent.  Makes "dvj" work nice.
   3304      if (oap->motion_type == kMTLineWise) {
   3305        oap->inclusive = false;
   3306      } else if (oap->motion_type == kMTCharWise) {
   3307        // If the motion already was charwise, toggle "inclusive"
   3308        oap->inclusive = !oap->inclusive;
   3309      }
   3310      oap->motion_type = kMTCharWise;
   3311    } else if (oap->motion_force == Ctrl_V) {
   3312      // Change line- or charwise motion into Visual block mode.
   3313      if (!VIsual_active) {
   3314        VIsual_active = true;
   3315        VIsual = oap->start;
   3316      }
   3317      VIsual_mode = Ctrl_V;
   3318      VIsual_select = false;
   3319      VIsual_reselect = false;
   3320    }
   3321 
   3322    // Only redo yank when 'y' flag is in 'cpoptions'.
   3323    // Never redo "zf" (define fold).
   3324    if ((redo_yank || oap->op_type != OP_YANK)
   3325        && ((!VIsual_active || oap->motion_force)
   3326            // Also redo Operator-pending Visual mode mappings.
   3327            || ((is_ex_cmdchar(cap) || cap->cmdchar == K_LUA)
   3328                && oap->op_type != OP_COLON))
   3329        && cap->cmdchar != 'D'
   3330        && oap->op_type != OP_FOLD
   3331        && oap->op_type != OP_FOLDOPEN
   3332        && oap->op_type != OP_FOLDOPENREC
   3333        && oap->op_type != OP_FOLDCLOSE
   3334        && oap->op_type != OP_FOLDCLOSEREC
   3335        && oap->op_type != OP_FOLDDEL
   3336        && oap->op_type != OP_FOLDDELREC) {
   3337      prep_redo(oap->regname, cap->count0,
   3338                get_op_char(oap->op_type), get_extra_op_char(oap->op_type),
   3339                oap->motion_force, cap->cmdchar, cap->nchar);
   3340      if (cap->cmdchar == '/' || cap->cmdchar == '?') {     // was a search
   3341        // If 'cpoptions' does not contain 'r', insert the search
   3342        // pattern to really repeat the same command.
   3343        if (vim_strchr(p_cpo, CPO_REDO) == NULL) {
   3344          AppendToRedobuffLit(cap->searchbuf, -1);
   3345        }
   3346        AppendToRedobuff(NL_STR);
   3347      } else if (is_ex_cmdchar(cap)) {
   3348        // do_cmdline() has stored the first typed line in
   3349        // "repeat_cmdline".  When several lines are typed repeating
   3350        // won't be possible.
   3351        if (repeat_cmdline == NULL) {
   3352          ResetRedobuff();
   3353        } else {
   3354          if (cap->cmdchar == ':') {
   3355            AppendToRedobuffLit(repeat_cmdline, -1);
   3356          } else {
   3357            AppendToRedobuffSpec(repeat_cmdline);
   3358          }
   3359          AppendToRedobuff(NL_STR);
   3360          XFREE_CLEAR(repeat_cmdline);
   3361        }
   3362      } else if (cap->cmdchar == K_LUA) {
   3363        AppendNumberToRedobuff(repeat_luaref);
   3364        AppendToRedobuff(NL_STR);
   3365      }
   3366    }
   3367 
   3368    if (redo_VIsual_busy) {
   3369      // Redo of an operation on a Visual area. Use the same size from
   3370      // redo_VIsual.rv_line_count and redo_VIsual.rv_vcol.
   3371      oap->start = curwin->w_cursor;
   3372      curwin->w_cursor.lnum += redo_VIsual.rv_line_count - 1;
   3373      curwin->w_cursor.lnum = MIN(curwin->w_cursor.lnum, curbuf->b_ml.ml_line_count);
   3374      VIsual_mode = redo_VIsual.rv_mode;
   3375      if (redo_VIsual.rv_vcol == MAXCOL || VIsual_mode == 'v') {
   3376        if (VIsual_mode == 'v') {
   3377          if (redo_VIsual.rv_line_count <= 1) {
   3378            validate_virtcol(curwin);
   3379            curwin->w_curswant = curwin->w_virtcol + redo_VIsual.rv_vcol - 1;
   3380          } else {
   3381            curwin->w_curswant = redo_VIsual.rv_vcol;
   3382          }
   3383        } else {
   3384          curwin->w_curswant = MAXCOL;
   3385        }
   3386        coladvance(curwin, curwin->w_curswant);
   3387      }
   3388      cap->count0 = redo_VIsual.rv_count;
   3389      cap->count1 = (cap->count0 == 0 ? 1 : cap->count0);
   3390    } else if (VIsual_active) {
   3391      if (!gui_yank) {
   3392        // Save the current VIsual area for '< and '> marks, and "gv"
   3393        curbuf->b_visual.vi_start = VIsual;
   3394        curbuf->b_visual.vi_end = curwin->w_cursor;
   3395        curbuf->b_visual.vi_mode = VIsual_mode;
   3396        restore_visual_mode();
   3397        curbuf->b_visual.vi_curswant = curwin->w_curswant;
   3398        curbuf->b_visual_mode_eval = VIsual_mode;
   3399      }
   3400 
   3401      // In Select mode, a linewise selection is operated upon like a
   3402      // charwise selection.
   3403      // Special case: gH<Del> deletes the last line.
   3404      if (VIsual_select && VIsual_mode == 'V'
   3405          && cap->oap->op_type != OP_DELETE) {
   3406        if (lt(VIsual, curwin->w_cursor)) {
   3407          VIsual.col = 0;
   3408          curwin->w_cursor.col = ml_get_len(curwin->w_cursor.lnum);
   3409        } else {
   3410          curwin->w_cursor.col = 0;
   3411          VIsual.col = ml_get_len(VIsual.lnum);
   3412        }
   3413        VIsual_mode = 'v';
   3414      } else if (VIsual_mode == 'v') {
   3415        // If 'selection' is "exclusive", backup one character for
   3416        // charwise selections.
   3417        include_line_break = unadjust_for_sel();
   3418      }
   3419 
   3420      oap->start = VIsual;
   3421      if (VIsual_mode == 'V') {
   3422        oap->start.col = 0;
   3423        oap->start.coladd = 0;
   3424      }
   3425    }
   3426 
   3427    // Set oap->start to the first position of the operated text, oap->end
   3428    // to the end of the operated text.  w_cursor is equal to oap->start.
   3429    if (lt(oap->start, curwin->w_cursor)) {
   3430      // Include folded lines completely.
   3431      if (!VIsual_active) {
   3432        if (hasFolding(curwin, oap->start.lnum, &oap->start.lnum, NULL)) {
   3433          oap->start.col = 0;
   3434        }
   3435        if ((curwin->w_cursor.col > 0
   3436             || oap->inclusive
   3437             || oap->motion_type == kMTLineWise)
   3438            && hasFolding(curwin, curwin->w_cursor.lnum, NULL,
   3439                          &curwin->w_cursor.lnum)) {
   3440          curwin->w_cursor.col = get_cursor_line_len();
   3441        }
   3442      }
   3443      oap->end = curwin->w_cursor;
   3444      curwin->w_cursor = oap->start;
   3445 
   3446      // w_virtcol may have been updated; if the cursor goes back to its
   3447      // previous position w_virtcol becomes invalid and isn't updated
   3448      // automatically.
   3449      curwin->w_valid &= ~VALID_VIRTCOL;
   3450    } else {
   3451      // Include folded lines completely.
   3452      if (!VIsual_active && oap->motion_type == kMTLineWise) {
   3453        if (hasFolding(curwin, curwin->w_cursor.lnum, &curwin->w_cursor.lnum,
   3454                       NULL)) {
   3455          curwin->w_cursor.col = 0;
   3456        }
   3457        if (hasFolding(curwin, oap->start.lnum, NULL, &oap->start.lnum)) {
   3458          oap->start.col = ml_get_len(oap->start.lnum);
   3459        }
   3460      }
   3461      oap->end = oap->start;
   3462      oap->start = curwin->w_cursor;
   3463    }
   3464 
   3465    // Just in case lines were deleted that make the position invalid.
   3466    check_pos(curwin->w_buffer, &oap->end);
   3467    oap->line_count = oap->end.lnum - oap->start.lnum + 1;
   3468 
   3469    // Set "virtual_op" before resetting VIsual_active.
   3470    virtual_op = virtual_active(curwin);
   3471 
   3472    if (VIsual_active || redo_VIsual_busy) {
   3473      get_op_vcol(oap, redo_VIsual.rv_vcol, true);
   3474 
   3475      if (!redo_VIsual_busy && !gui_yank) {
   3476        // Prepare to reselect and redo Visual: this is based on the
   3477        // size of the Visual text
   3478        resel_VIsual_mode = VIsual_mode;
   3479        if (curwin->w_curswant == MAXCOL) {
   3480          resel_VIsual_vcol = MAXCOL;
   3481        } else {
   3482          if (VIsual_mode != Ctrl_V) {
   3483            getvvcol(curwin, &(oap->end),
   3484                     NULL, NULL, &oap->end_vcol);
   3485          }
   3486          if (VIsual_mode == Ctrl_V || oap->line_count <= 1) {
   3487            if (VIsual_mode != Ctrl_V) {
   3488              getvvcol(curwin, &(oap->start),
   3489                       &oap->start_vcol, NULL, NULL);
   3490            }
   3491            resel_VIsual_vcol = oap->end_vcol - oap->start_vcol + 1;
   3492          } else {
   3493            resel_VIsual_vcol = oap->end_vcol;
   3494          }
   3495        }
   3496        resel_VIsual_line_count = oap->line_count;
   3497      }
   3498 
   3499      // can't redo yank (unless 'y' is in 'cpoptions') and ":"
   3500      if ((redo_yank || oap->op_type != OP_YANK)
   3501          && oap->op_type != OP_COLON
   3502          && oap->op_type != OP_FOLD
   3503          && oap->op_type != OP_FOLDOPEN
   3504          && oap->op_type != OP_FOLDOPENREC
   3505          && oap->op_type != OP_FOLDCLOSE
   3506          && oap->op_type != OP_FOLDCLOSEREC
   3507          && oap->op_type != OP_FOLDDEL
   3508          && oap->op_type != OP_FOLDDELREC
   3509          && oap->motion_force == NUL) {
   3510        // Prepare for redoing.  Only use the nchar field for "r",
   3511        // otherwise it might be the second char of the operator.
   3512        if (cap->cmdchar == 'g' && (cap->nchar == 'n'
   3513                                    || cap->nchar == 'N')) {
   3514          prep_redo(oap->regname, cap->count0,
   3515                    get_op_char(oap->op_type), get_extra_op_char(oap->op_type),
   3516                    oap->motion_force, cap->cmdchar, cap->nchar);
   3517        } else if (!is_ex_cmdchar(cap) && cap->cmdchar != K_LUA) {
   3518          int opchar = get_op_char(oap->op_type);
   3519          int extra_opchar = get_extra_op_char(oap->op_type);
   3520          int nchar = oap->op_type == OP_REPLACE ? cap->nchar : NUL;
   3521 
   3522          // reverse what nv_replace() did
   3523          if (nchar == REPLACE_CR_NCHAR) {
   3524            nchar = CAR;
   3525          } else if (nchar == REPLACE_NL_NCHAR) {
   3526            nchar = NL;
   3527          }
   3528 
   3529          if (opchar == 'g' && extra_opchar == '@') {
   3530            // also repeat the count for 'operatorfunc'
   3531            prep_redo_num2(oap->regname, 0, NUL, 'v', cap->count0, opchar, extra_opchar, nchar);
   3532          } else {
   3533            prep_redo(oap->regname, 0, NUL, 'v', opchar, extra_opchar, nchar);
   3534          }
   3535        }
   3536        if (!redo_VIsual_busy) {
   3537          redo_VIsual.rv_mode = resel_VIsual_mode;
   3538          redo_VIsual.rv_vcol = resel_VIsual_vcol;
   3539          redo_VIsual.rv_line_count = resel_VIsual_line_count;
   3540          redo_VIsual.rv_count = cap->count0;
   3541          redo_VIsual.rv_arg = cap->arg;
   3542        }
   3543      }
   3544 
   3545      // oap->inclusive defaults to true.
   3546      // If oap->end is on a NUL (empty line) oap->inclusive becomes
   3547      // false.  This makes "d}P" and "v}dP" work the same.
   3548      if (oap->motion_force == NUL || oap->motion_type == kMTLineWise) {
   3549        oap->inclusive = true;
   3550      }
   3551      if (VIsual_mode == 'V') {
   3552        oap->motion_type = kMTLineWise;
   3553      } else if (VIsual_mode == 'v') {
   3554        oap->motion_type = kMTCharWise;
   3555        if (*ml_get_pos(&(oap->end)) == NUL
   3556            && (include_line_break || !virtual_op)) {
   3557          oap->inclusive = false;
   3558          // Try to include the newline, unless it's an operator
   3559          // that works on lines only.
   3560          if (*p_sel != 'o'
   3561              && !op_on_lines(oap->op_type)
   3562              && oap->end.lnum < curbuf->b_ml.ml_line_count) {
   3563            oap->end.lnum++;
   3564            oap->end.col = 0;
   3565            oap->end.coladd = 0;
   3566            oap->line_count++;
   3567          }
   3568        }
   3569      }
   3570 
   3571      redo_VIsual_busy = false;
   3572 
   3573      // Switch Visual off now, so screen updating does
   3574      // not show inverted text when the screen is redrawn.
   3575      // With OP_YANK and sometimes with OP_COLON and OP_FILTER there is
   3576      // no screen redraw, so it is done here to remove the inverted
   3577      // part.
   3578      if (!gui_yank) {
   3579        VIsual_active = false;
   3580        setmouse();
   3581        mouse_dragging = 0;
   3582        may_clear_cmdline();
   3583        if ((oap->op_type == OP_YANK
   3584             || oap->op_type == OP_COLON
   3585             || oap->op_type == OP_FUNCTION
   3586             || oap->op_type == OP_FILTER)
   3587            && oap->motion_force == NUL) {
   3588          // Make sure redrawing is correct.
   3589          restore_lbr(lbr_saved);
   3590          redraw_curbuf_later(UPD_INVERTED);
   3591        }
   3592      }
   3593    }
   3594 
   3595    // Include the trailing byte of a multi-byte char.
   3596    if (oap->inclusive) {
   3597      const int l = utfc_ptr2len(ml_get_pos(&oap->end));
   3598      if (l > 1) {
   3599        oap->end.col += l - 1;
   3600      }
   3601    }
   3602    curwin->w_set_curswant = true;
   3603 
   3604    // oap->empty is set when start and end are the same.  The inclusive
   3605    // flag affects this too, unless yanking and the end is on a NUL.
   3606    oap->empty = (oap->motion_type != kMTLineWise
   3607                  && (!oap->inclusive
   3608                      || (oap->op_type == OP_YANK
   3609                          && gchar_pos(&oap->end) == NUL))
   3610                  && equalpos(oap->start, oap->end)
   3611                  && !(virtual_op && oap->start.coladd != oap->end.coladd));
   3612    // For delete, change and yank, it's an error to operate on an
   3613    // empty region, when 'E' included in 'cpoptions' (Vi compatible).
   3614    empty_region_error = (oap->empty
   3615                          && vim_strchr(p_cpo, CPO_EMPTYREGION) != NULL);
   3616 
   3617    // Force a redraw when operating on an empty Visual region, when
   3618    // 'modifiable is off or creating a fold.
   3619    if (oap->is_VIsual && (oap->empty || !MODIFIABLE(curbuf)
   3620                           || oap->op_type == OP_FOLD)) {
   3621      restore_lbr(lbr_saved);
   3622      redraw_curbuf_later(UPD_INVERTED);
   3623    }
   3624 
   3625    // If the end of an operator is in column one while oap->motion_type
   3626    // is kMTCharWise and oap->inclusive is false, we put op_end after the last
   3627    // character in the previous line. If op_start is on or before the
   3628    // first non-blank in the line, the operator becomes linewise
   3629    // (strange, but that's the way vi does it).
   3630    if (oap->motion_type == kMTCharWise
   3631        && oap->inclusive == false
   3632        && !(cap->retval & CA_NO_ADJ_OP_END)
   3633        && oap->end.col == 0
   3634        && (!oap->is_VIsual || *p_sel == 'o')
   3635        && oap->line_count > 1) {
   3636      oap->end_adjusted = true;  // remember that we did this
   3637      oap->line_count--;
   3638      oap->end.lnum--;
   3639      if (inindent(0)) {
   3640        oap->motion_type = kMTLineWise;
   3641      } else {
   3642        oap->end.col = ml_get_len(oap->end.lnum);
   3643        if (oap->end.col) {
   3644          oap->end.col--;
   3645          oap->inclusive = true;
   3646        }
   3647      }
   3648    } else {
   3649      oap->end_adjusted = false;
   3650    }
   3651 
   3652    switch (oap->op_type) {
   3653    case OP_LSHIFT:
   3654    case OP_RSHIFT:
   3655      op_shift(oap, true, oap->is_VIsual ? cap->count1 : 1);
   3656      auto_format(false, true);
   3657      break;
   3658 
   3659    case OP_JOIN_NS:
   3660    case OP_JOIN:
   3661      oap->line_count = MAX(oap->line_count, 2);
   3662      if (curwin->w_cursor.lnum + oap->line_count - 1 >
   3663          curbuf->b_ml.ml_line_count) {
   3664        beep_flush();
   3665      } else {
   3666        do_join((size_t)oap->line_count, oap->op_type == OP_JOIN,
   3667                true, true, true);
   3668        auto_format(false, true);
   3669      }
   3670      break;
   3671 
   3672    case OP_DELETE:
   3673      VIsual_reselect = false;              // don't reselect now
   3674      if (empty_region_error) {
   3675        vim_beep(kOptBoFlagOperator);
   3676        CancelRedo();
   3677      } else {
   3678        op_delete(oap);
   3679        // save cursor line for undo if it wasn't saved yet
   3680        if (oap->motion_type == kMTLineWise
   3681            && has_format_option(FO_AUTO)
   3682            && u_save_cursor() == OK) {
   3683          auto_format(false, true);
   3684        }
   3685      }
   3686      break;
   3687 
   3688    case OP_YANK:
   3689      if (empty_region_error) {
   3690        if (!gui_yank) {
   3691          vim_beep(kOptBoFlagOperator);
   3692          CancelRedo();
   3693        }
   3694      } else {
   3695        restore_lbr(lbr_saved);
   3696        oap->excl_tr_ws = cap->cmdchar == 'z';
   3697        op_yank(oap, !gui_yank);
   3698      }
   3699      check_cursor_col(curwin);
   3700      break;
   3701 
   3702    case OP_CHANGE:
   3703      VIsual_reselect = false;              // don't reselect now
   3704      if (empty_region_error) {
   3705        vim_beep(kOptBoFlagOperator);
   3706        CancelRedo();
   3707      } else {
   3708        // This is a new edit command, not a restart.  Need to
   3709        // remember it to make i_CTRL-O work with mappings for
   3710        // Visual mode.  But do this only once and not when typed.
   3711        if (!KeyTyped) {
   3712          restart_edit_save = restart_edit;
   3713        } else {
   3714          restart_edit_save = 0;
   3715        }
   3716        restart_edit = 0;
   3717 
   3718        // Restore linebreak, so that when the user edits it looks as before.
   3719        restore_lbr(lbr_saved);
   3720 
   3721        // trigger TextChangedI
   3722        curbuf->b_last_changedtick_i = buf_get_changedtick(curbuf);
   3723 
   3724        if (op_change(oap)) {           // will call edit()
   3725          cap->retval |= CA_COMMAND_BUSY;
   3726        }
   3727        if (restart_edit == 0) {
   3728          restart_edit = restart_edit_save;
   3729        }
   3730      }
   3731      break;
   3732 
   3733    case OP_FILTER:
   3734      if (vim_strchr(p_cpo, CPO_FILTER) != NULL) {
   3735        AppendToRedobuff("!\r");  // Use any last used !cmd.
   3736      } else {
   3737        bangredo = true;  // do_bang() will put cmd in redo buffer.
   3738      }
   3739      FALLTHROUGH;
   3740 
   3741    case OP_INDENT:
   3742    case OP_COLON:
   3743 
   3744      // If 'equalprg' is empty, do the indenting internally.
   3745      if (oap->op_type == OP_INDENT && *get_equalprg() == NUL) {
   3746        if (curbuf->b_p_lisp) {
   3747          if (use_indentexpr_for_lisp()) {
   3748            op_reindent(oap, get_expr_indent);
   3749          } else {
   3750            op_reindent(oap, get_lisp_indent);
   3751          }
   3752          break;
   3753        }
   3754        op_reindent(oap,
   3755                    *curbuf->b_p_inde != NUL ? get_expr_indent
   3756                                             : get_c_indent);
   3757        break;
   3758      }
   3759 
   3760      op_colon(oap);
   3761      break;
   3762 
   3763    case OP_TILDE:
   3764    case OP_UPPER:
   3765    case OP_LOWER:
   3766    case OP_ROT13:
   3767      if (empty_region_error) {
   3768        vim_beep(kOptBoFlagOperator);
   3769        CancelRedo();
   3770      } else {
   3771        op_tilde(oap);
   3772      }
   3773      check_cursor_col(curwin);
   3774      break;
   3775 
   3776    case OP_FORMAT:
   3777      if (*curbuf->b_p_fex != NUL) {
   3778        op_formatexpr(oap);             // use expression
   3779      } else {
   3780        if (*p_fp != NUL || *curbuf->b_p_fp != NUL) {
   3781          op_colon(oap);                // use external command
   3782        } else {
   3783          op_format(oap, false);        // use internal function
   3784        }
   3785      }
   3786      break;
   3787 
   3788    case OP_FORMAT2:
   3789      op_format(oap, true);             // use internal function
   3790      break;
   3791 
   3792    case OP_FUNCTION: {
   3793      redo_VIsual_T save_redo_VIsual = redo_VIsual;
   3794 
   3795      // Restore linebreak, so that when the user edits it looks as before.
   3796      restore_lbr(lbr_saved);
   3797      // call 'operatorfunc'
   3798      op_function(oap);
   3799 
   3800      // Restore the info for redoing Visual mode, the function may
   3801      // invoke another operator and unintentionally change it.
   3802      redo_VIsual = save_redo_VIsual;
   3803      break;
   3804    }
   3805 
   3806    case OP_INSERT:
   3807    case OP_APPEND:
   3808      VIsual_reselect = false;          // don't reselect now
   3809      if (empty_region_error) {
   3810        vim_beep(kOptBoFlagOperator);
   3811        CancelRedo();
   3812      } else {
   3813        // This is a new edit command, not a restart.  Need to
   3814        // remember it to make i_CTRL-O work with mappings for
   3815        // Visual mode.  But do this only once.
   3816        restart_edit_save = restart_edit;
   3817        restart_edit = 0;
   3818 
   3819        // Restore linebreak, so that when the user edits it looks as before.
   3820        restore_lbr(lbr_saved);
   3821 
   3822        // trigger TextChangedI
   3823        curbuf->b_last_changedtick_i = buf_get_changedtick(curbuf);
   3824 
   3825        op_insert(oap, cap->count1);
   3826 
   3827        // Reset linebreak, so that formatting works correctly.
   3828        reset_lbr();
   3829 
   3830        // TODO(brammool): when inserting in several lines, should format all
   3831        // the lines.
   3832        auto_format(false, true);
   3833 
   3834        if (restart_edit == 0) {
   3835          restart_edit = restart_edit_save;
   3836        } else {
   3837          cap->retval |= CA_COMMAND_BUSY;
   3838        }
   3839      }
   3840      break;
   3841 
   3842    case OP_REPLACE:
   3843      VIsual_reselect = false;          // don't reselect now
   3844      if (empty_region_error) {
   3845        vim_beep(kOptBoFlagOperator);
   3846        CancelRedo();
   3847      } else {
   3848        // Restore linebreak, so that when the user edits it looks as before.
   3849        restore_lbr(lbr_saved);
   3850 
   3851        op_replace(oap, cap->nchar);
   3852      }
   3853      break;
   3854 
   3855    case OP_FOLD:
   3856      VIsual_reselect = false;          // don't reselect now
   3857      foldCreate(curwin, oap->start, oap->end);
   3858      break;
   3859 
   3860    case OP_FOLDOPEN:
   3861    case OP_FOLDOPENREC:
   3862    case OP_FOLDCLOSE:
   3863    case OP_FOLDCLOSEREC:
   3864      VIsual_reselect = false;          // don't reselect now
   3865      opFoldRange(oap->start, oap->end,
   3866                  oap->op_type == OP_FOLDOPEN
   3867                  || oap->op_type == OP_FOLDOPENREC,
   3868                  oap->op_type == OP_FOLDOPENREC
   3869                  || oap->op_type == OP_FOLDCLOSEREC,
   3870                  oap->is_VIsual);
   3871      break;
   3872 
   3873    case OP_FOLDDEL:
   3874    case OP_FOLDDELREC:
   3875      VIsual_reselect = false;          // don't reselect now
   3876      deleteFold(curwin, oap->start.lnum, oap->end.lnum,
   3877                 oap->op_type == OP_FOLDDELREC, oap->is_VIsual);
   3878      break;
   3879 
   3880    case OP_NR_ADD:
   3881    case OP_NR_SUB:
   3882      if (empty_region_error) {
   3883        vim_beep(kOptBoFlagOperator);
   3884        CancelRedo();
   3885      } else {
   3886        VIsual_active = true;
   3887        restore_lbr(lbr_saved);
   3888        op_addsub(oap, (linenr_T)cap->count1, redo_VIsual.rv_arg);
   3889        VIsual_active = false;
   3890      }
   3891      check_cursor_col(curwin);
   3892      break;
   3893    default:
   3894      clearopbeep(oap);
   3895    }
   3896    virtual_op = kNone;
   3897    if (!gui_yank) {
   3898      // if 'sol' not set, go back to old column for some commands
   3899      if (!p_sol && oap->motion_type == kMTLineWise && !oap->end_adjusted
   3900          && (oap->op_type == OP_LSHIFT || oap->op_type == OP_RSHIFT
   3901              || oap->op_type == OP_DELETE)) {
   3902        reset_lbr();
   3903        coladvance(curwin, curwin->w_curswant = old_col);
   3904      }
   3905    } else {
   3906      curwin->w_cursor = old_cursor;
   3907    }
   3908    clearop(oap);
   3909    motion_force = NUL;
   3910  }
   3911  restore_lbr(lbr_saved);
   3912 }
   3913 
   3914 /// Get the byte count of buffer region. End-exclusive.
   3915 ///
   3916 /// @return number of bytes
   3917 bcount_t get_region_bytecount(buf_T *buf, linenr_T start_lnum, linenr_T end_lnum, colnr_T start_col,
   3918                              colnr_T end_col)
   3919 {
   3920  linenr_T max_lnum = buf->b_ml.ml_line_count;
   3921  if (start_lnum > max_lnum) {
   3922    return 0;
   3923  }
   3924  if (start_lnum == end_lnum) {
   3925    return end_col - start_col;
   3926  }
   3927  bcount_t deleted_bytes = ml_get_buf_len(buf, start_lnum) - start_col + 1;
   3928 
   3929  for (linenr_T i = 1; i <= end_lnum - start_lnum - 1; i++) {
   3930    if (start_lnum + i > max_lnum) {
   3931      return deleted_bytes;
   3932    }
   3933    deleted_bytes += ml_get_buf_len(buf, start_lnum + i) + 1;
   3934  }
   3935  if (end_lnum > max_lnum) {
   3936    return deleted_bytes;
   3937  }
   3938  return deleted_bytes + end_col;
   3939 }