neovim

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

ex_cmds.c (161731B)


      1 // ex_cmds.c: some functions for command line commands
      2 
      3 #include <assert.h>
      4 #include <ctype.h>
      5 #include <float.h>
      6 #include <inttypes.h>
      7 #include <limits.h>
      8 #include <math.h>
      9 #include <stdbool.h>
     10 #include <stddef.h>
     11 #include <stdio.h>
     12 #include <stdlib.h>
     13 #include <string.h>
     14 #include <time.h>
     15 
     16 #include "auto/config.h"
     17 #include "klib/kvec.h"
     18 #include "nvim/api/private/helpers.h"
     19 #include "nvim/arglist.h"
     20 #include "nvim/ascii_defs.h"
     21 #include "nvim/autocmd.h"
     22 #include "nvim/autocmd_defs.h"
     23 #include "nvim/buffer.h"
     24 #include "nvim/buffer_defs.h"
     25 #include "nvim/buffer_updates.h"
     26 #include "nvim/bufwrite.h"
     27 #include "nvim/change.h"
     28 #include "nvim/channel.h"
     29 #include "nvim/charset.h"
     30 #include "nvim/cmdexpand_defs.h"
     31 #include "nvim/cmdhist.h"
     32 #include "nvim/cursor.h"
     33 #include "nvim/decoration.h"
     34 #include "nvim/diff.h"
     35 #include "nvim/digraph.h"
     36 #include "nvim/drawscreen.h"
     37 #include "nvim/edit.h"
     38 #include "nvim/errors.h"
     39 #include "nvim/eval/typval.h"
     40 #include "nvim/eval/typval_defs.h"
     41 #include "nvim/eval/vars.h"
     42 #include "nvim/ex_cmds.h"
     43 #include "nvim/ex_cmds2.h"
     44 #include "nvim/ex_cmds_defs.h"
     45 #include "nvim/ex_docmd.h"
     46 #include "nvim/ex_eval.h"
     47 #include "nvim/ex_getln.h"
     48 #include "nvim/extmark.h"
     49 #include "nvim/extmark_defs.h"
     50 #include "nvim/fileio.h"
     51 #include "nvim/fold.h"
     52 #include "nvim/getchar.h"
     53 #include "nvim/gettext_defs.h"
     54 #include "nvim/globals.h"
     55 #include "nvim/help.h"
     56 #include "nvim/highlight_defs.h"
     57 #include "nvim/highlight_group.h"
     58 #include "nvim/indent.h"
     59 #include "nvim/input.h"
     60 #include "nvim/macros_defs.h"
     61 #include "nvim/main.h"
     62 #include "nvim/mark.h"
     63 #include "nvim/mark_defs.h"
     64 #include "nvim/mbyte.h"
     65 #include "nvim/memline.h"
     66 #include "nvim/memline_defs.h"
     67 #include "nvim/memory.h"
     68 #include "nvim/message.h"
     69 #include "nvim/mouse.h"
     70 #include "nvim/move.h"
     71 #include "nvim/normal.h"
     72 #include "nvim/ops.h"
     73 #include "nvim/option.h"
     74 #include "nvim/option_defs.h"
     75 #include "nvim/option_vars.h"
     76 #include "nvim/os/fs.h"
     77 #include "nvim/os/fs_defs.h"
     78 #include "nvim/os/input.h"
     79 #include "nvim/os/os.h"
     80 #include "nvim/os/os_defs.h"
     81 #include "nvim/os/shell.h"
     82 #include "nvim/os/time.h"
     83 #include "nvim/path.h"
     84 #include "nvim/plines.h"
     85 #include "nvim/pos_defs.h"
     86 #include "nvim/profile.h"
     87 #include "nvim/quickfix.h"
     88 #include "nvim/regexp.h"
     89 #include "nvim/regexp_defs.h"
     90 #include "nvim/search.h"
     91 #include "nvim/spell.h"
     92 #include "nvim/state_defs.h"
     93 #include "nvim/strings.h"
     94 #include "nvim/terminal.h"
     95 #include "nvim/types_defs.h"
     96 #include "nvim/ui.h"
     97 #include "nvim/ui_defs.h"
     98 #include "nvim/undo.h"
     99 #include "nvim/vim_defs.h"
    100 #include "nvim/window.h"
    101 
    102 /// Case matching style to use for :substitute
    103 typedef enum {
    104  kSubHonorOptions = 0,  ///< Honor the user's 'ignorecase'/'smartcase' options
    105  kSubIgnoreCase,        ///< Ignore case of the search
    106  kSubMatchCase,         ///< Match case of the search
    107 } SubIgnoreType;
    108 
    109 /// Flags kept between calls to :substitute.
    110 typedef struct {
    111  bool do_all;          ///< do multiple substitutions per line
    112  bool do_ask;          ///< ask for confirmation
    113  bool do_count;        ///< count only
    114  bool do_error;        ///< if false, ignore errors
    115  bool do_print;        ///< print last line with subs
    116  bool do_list;         ///< list last line with subs
    117  bool do_number;       ///< list last line with line nr
    118  SubIgnoreType do_ic;  ///< ignore case flag
    119 } subflags_T;
    120 
    121 /// Partial result of a substitution during :substitute.
    122 /// Numbers refer to the buffer _after_ substitution
    123 typedef struct {
    124  lpos_T start;  // start of the match
    125  lpos_T end;    // end of the match
    126  linenr_T pre_match;  // where to begin showing lines before the match
    127 } SubResult;
    128 
    129 // Collected results of a substitution for showing them in
    130 // the preview window
    131 typedef struct {
    132  kvec_t(SubResult) subresults;
    133  linenr_T lines_needed;  // lines needed in the preview window
    134 } PreviewLines;
    135 
    136 #include "ex_cmds.c.generated.h"
    137 
    138 static const char e_non_numeric_argument_to_z[]
    139  = N_("E144: Non-numeric argument to :z");
    140 
    141 /// ":ascii" and "ga" implementation
    142 void do_ascii(exarg_T *eap)
    143 {
    144  char *data = get_cursor_pos_ptr();
    145  size_t len = (size_t)utfc_ptr2len(data);
    146 
    147  if (len == 0) {
    148    msg("NUL", 0);
    149    return;
    150  }
    151 
    152  bool need_clear = true;
    153  msg_sb_eol();
    154  msg_start();
    155 
    156  int c = utf_ptr2char(data);
    157  size_t off = 0;
    158 
    159  // TODO(bfredl): merge this with the main loop
    160  if (c < 0x80) {
    161    if (c == NL) {  // NUL is stored as NL.
    162      c = NUL;
    163    }
    164    const int cval = (c == CAR && get_fileformat(curbuf) == EOL_MAC
    165                      ? NL  // NL is stored as CR.
    166                      : c);
    167    char buf1[20];
    168    if (vim_isprintc(c) && (c < ' ' || c > '~')) {
    169      char buf3[7];
    170      transchar_nonprint(curbuf, buf3, c);
    171      vim_snprintf(buf1, sizeof(buf1), "  <%s>", buf3);
    172    } else {
    173      buf1[0] = NUL;
    174    }
    175    char buf2[20];
    176    buf2[0] = NUL;
    177 
    178    char *dig = get_digraph_for_char(cval);
    179    if (dig != NULL) {
    180      vim_snprintf(IObuff, sizeof(IObuff),
    181                   _("<%s>%s%s  %d,  Hex %02x,  Oct %03o, Digr %s"),
    182                   transchar(c), buf1, buf2, cval, cval, cval, dig);
    183    } else {
    184      vim_snprintf(IObuff, sizeof(IObuff),
    185                   _("<%s>%s%s  %d,  Hex %02x,  Octal %03o"),
    186                   transchar(c), buf1, buf2, cval, cval, cval);
    187    }
    188 
    189    msg_multiline(cstr_as_string(IObuff), 0, true, false, &need_clear);
    190 
    191    off += (size_t)utf_ptr2len(data);  // needed for overlong ascii?
    192  }
    193 
    194  // Repeat for combining characters, also handle multiby here.
    195  while (off < len) {
    196    c = utf_ptr2char(data + off);
    197 
    198    size_t iobuff_len = 0;
    199    // This assumes every multi-byte char is printable...
    200    if (off > 0) {
    201      IObuff[iobuff_len++] = ' ';
    202    }
    203    IObuff[iobuff_len++] = '<';
    204    if (utf_iscomposing_first(c)) {
    205      IObuff[iobuff_len++] = ' ';  // Draw composing char on top of a space.
    206    }
    207    iobuff_len += (size_t)utf_char2bytes(c, IObuff + iobuff_len);
    208 
    209    char *dig = get_digraph_for_char(c);
    210    if (dig != NULL) {
    211      vim_snprintf(IObuff + iobuff_len, sizeof(IObuff) - iobuff_len,
    212                   (c < 0x10000
    213                    ? _("> %d, Hex %04x, Oct %o, Digr %s")
    214                    : _("> %d, Hex %08x, Oct %o, Digr %s")),
    215                   c, c, c, dig);
    216    } else {
    217      vim_snprintf(IObuff + iobuff_len, sizeof(IObuff) - iobuff_len,
    218                   (c < 0x10000
    219                    ? _("> %d, Hex %04x, Octal %o")
    220                    : _("> %d, Hex %08x, Octal %o")),
    221                   c, c, c);
    222    }
    223 
    224    msg_multiline(cstr_as_string(IObuff), 0, true, false, &need_clear);
    225 
    226    off += (size_t)utf_ptr2len(data + off);  // needed for overlong ascii?
    227  }
    228 
    229  if (need_clear) {
    230    msg_clr_eos();
    231  }
    232  msg_end();
    233 }
    234 
    235 /// ":left", ":center" and ":right": align text.
    236 void ex_align(exarg_T *eap)
    237 {
    238  int indent = 0;
    239  int new_indent;
    240 
    241  if (curwin->w_p_rl) {
    242    // switch left and right aligning
    243    if (eap->cmdidx == CMD_right) {
    244      eap->cmdidx = CMD_left;
    245    } else if (eap->cmdidx == CMD_left) {
    246      eap->cmdidx = CMD_right;
    247    }
    248  }
    249 
    250  int width = atoi(eap->arg);
    251  pos_T save_curpos = curwin->w_cursor;
    252  if (eap->cmdidx == CMD_left) {    // width is used for new indent
    253    if (width >= 0) {
    254      indent = width;
    255    }
    256  } else {
    257    // if 'textwidth' set, use it
    258    // else if 'wrapmargin' set, use it
    259    // if invalid value, use 80
    260    if (width <= 0) {
    261      width = (int)curbuf->b_p_tw;
    262    }
    263    if (width == 0 && curbuf->b_p_wm > 0) {
    264      width = curwin->w_view_width - (int)curbuf->b_p_wm;
    265    }
    266    if (width <= 0) {
    267      width = 80;
    268    }
    269  }
    270 
    271  if (u_save((linenr_T)(eap->line1 - 1), (linenr_T)(eap->line2 + 1)) == FAIL) {
    272    return;
    273  }
    274 
    275  for (curwin->w_cursor.lnum = eap->line1;
    276       curwin->w_cursor.lnum <= eap->line2; curwin->w_cursor.lnum++) {
    277    if (eap->cmdidx == CMD_left) {              // left align
    278      new_indent = indent;
    279    } else {
    280      int has_tab = false;          // avoid uninit warnings
    281      int len = linelen(eap->cmdidx == CMD_right ? &has_tab : NULL) - get_indent();
    282 
    283      if (len <= 0) {                           // skip blank lines
    284        continue;
    285      }
    286 
    287      if (eap->cmdidx == CMD_center) {
    288        new_indent = (width - len) / 2;
    289      } else {
    290        new_indent = width - len;               // right align
    291 
    292        // Make sure that embedded TABs don't make the text go too far
    293        // to the right.
    294        if (has_tab) {
    295          while (new_indent > 0) {
    296            set_indent(new_indent, 0);
    297            if (linelen(NULL) <= width) {
    298              // Now try to move the line as much as possible to
    299              // the right.  Stop when it moves too far.
    300              do {
    301                set_indent(++new_indent, 0);
    302              } while (linelen(NULL) <= width);
    303              new_indent--;
    304              break;
    305            }
    306            new_indent--;
    307          }
    308        }
    309      }
    310    }
    311    new_indent = MAX(new_indent, 0);
    312    set_indent(new_indent, 0);                    // set indent
    313  }
    314  changed_lines(curbuf, eap->line1, 0, eap->line2 + 1, 0, true);
    315  curwin->w_cursor = save_curpos;
    316  beginline(BL_WHITE | BL_FIX);
    317 }
    318 
    319 /// @return  the length of the current line, excluding trailing white space.
    320 static int linelen(int *has_tab)
    321 {
    322  char *last;
    323 
    324  // Get the line.  If it's empty bail out early (could be the empty string
    325  // for an unloaded buffer).
    326  char *line = get_cursor_line_ptr();
    327  if (*line == NUL) {
    328    return 0;
    329  }
    330  // find the first non-blank character
    331  char *first = skipwhite(line);
    332 
    333  // find the character after the last non-blank character
    334  for (last = first + strlen(first);
    335       last > first && ascii_iswhite(last[-1]); last--) {}
    336  char save = *last;
    337  *last = NUL;
    338  int len = linetabsize_str(line);  // Get line length.
    339  if (has_tab != NULL) {        // Check for embedded TAB.
    340    *has_tab = vim_strchr(first, TAB) != NULL;
    341  }
    342  *last = save;
    343 
    344  return len;
    345 }
    346 
    347 // Buffer for two lines used during sorting.  They are allocated to
    348 // contain the longest line being sorted.
    349 static char *sortbuf1;
    350 static char *sortbuf2;
    351 
    352 static bool sort_lc;      ///< sort using locale
    353 static bool sort_ic;      ///< ignore case
    354 static bool sort_nr;      ///< sort on number
    355 static bool sort_rx;      ///< sort on regex instead of skipping it
    356 static bool sort_flt;     ///< sort on floating number
    357 
    358 static bool sort_abort;   ///< flag to indicate if sorting has been interrupted
    359 
    360 /// Struct to store info to be sorted.
    361 typedef struct {
    362  linenr_T lnum;          ///< line number
    363  union {
    364    struct {
    365      varnumber_T start_col_nr;  ///< starting column number
    366      varnumber_T end_col_nr;    ///< ending column number
    367    } line;
    368    struct {
    369      varnumber_T value;         ///< value if sorting by integer
    370      bool is_number;            ///< true when line contains a number
    371    } num;
    372    float_T value_flt;    ///< value if sorting by float
    373  } st_u;
    374 } sorti_T;
    375 
    376 static int string_compare(const void *s1, const void *s2) FUNC_ATTR_NONNULL_ALL
    377 {
    378  if (sort_lc) {
    379    return strcoll((const char *)s1, (const char *)s2);
    380  }
    381  return sort_ic ? STRICMP(s1, s2) : strcmp(s1, s2);
    382 }
    383 
    384 static int sort_compare(const void *s1, const void *s2)
    385 {
    386  sorti_T l1 = *(sorti_T *)s1;
    387  sorti_T l2 = *(sorti_T *)s2;
    388  int result = 0;
    389 
    390  // If the user interrupts, there's no way to stop qsort() immediately, but
    391  // if we return 0 every time, qsort will assume it's done sorting and
    392  // exit.
    393  if (sort_abort) {
    394    return 0;
    395  }
    396  fast_breakcheck();
    397  if (got_int) {
    398    sort_abort = true;
    399  }
    400 
    401  // When sorting numbers "start_col_nr" is the number, not the column
    402  // number.
    403  if (sort_nr) {
    404    if (l1.st_u.num.is_number != l2.st_u.num.is_number) {
    405      result = l1.st_u.num.is_number > l2.st_u.num.is_number ? 1 : -1;
    406    } else {
    407      result = l1.st_u.num.value == l2.st_u.num.value
    408               ? 0
    409               : l1.st_u.num.value > l2.st_u.num.value ? 1 : -1;
    410    }
    411  } else if (sort_flt) {
    412    result = l1.st_u.value_flt == l2.st_u.value_flt
    413             ? 0
    414             : l1.st_u.value_flt > l2.st_u.value_flt ? 1 : -1;
    415  } else {
    416    // We need to copy one line into "sortbuf1", because there is no
    417    // guarantee that the first pointer becomes invalid when obtaining the
    418    // second one.
    419    memcpy(sortbuf1, ml_get(l1.lnum) + l1.st_u.line.start_col_nr,
    420           (size_t)(l1.st_u.line.end_col_nr - l1.st_u.line.start_col_nr + 1));
    421    sortbuf1[l1.st_u.line.end_col_nr - l1.st_u.line.start_col_nr] = NUL;
    422    memcpy(sortbuf2, ml_get(l2.lnum) + l2.st_u.line.start_col_nr,
    423           (size_t)(l2.st_u.line.end_col_nr - l2.st_u.line.start_col_nr + 1));
    424    sortbuf2[l2.st_u.line.end_col_nr - l2.st_u.line.start_col_nr] = NUL;
    425 
    426    result = string_compare(sortbuf1, sortbuf2);
    427  }
    428 
    429  // If two lines have the same value, preserve the original line order.
    430  if (result == 0) {
    431    return l1.lnum - l2.lnum;
    432  }
    433  return result;
    434 }
    435 
    436 /// ":sort".
    437 void ex_sort(exarg_T *eap)
    438 {
    439  regmatch_T regmatch;
    440  int maxlen = 0;
    441  size_t count = (size_t)(eap->line2 - eap->line1) + 1;
    442  size_t i;
    443  bool unique = false;
    444  int sort_what = 0;
    445 
    446  // Sorting one line is really quick!
    447  if (count <= 1) {
    448    return;
    449  }
    450 
    451  if (u_save((linenr_T)(eap->line1 - 1), (linenr_T)(eap->line2 + 1)) == FAIL) {
    452    return;
    453  }
    454  sortbuf1 = NULL;
    455  sortbuf2 = NULL;
    456  regmatch.regprog = NULL;
    457  sorti_T *nrs = xmalloc(count * sizeof(sorti_T));
    458 
    459  sort_abort = sort_ic = sort_lc = sort_rx = sort_nr = sort_flt = false;
    460  size_t format_found = 0;
    461  bool change_occurred = false;   // Buffer contents changed.
    462 
    463  for (char *p = eap->arg; *p != NUL; p++) {
    464    if (ascii_iswhite(*p)) {
    465      // Skip
    466    } else if (*p == 'i') {
    467      sort_ic = true;
    468    } else if (*p == 'l') {
    469      sort_lc = true;
    470    } else if (*p == 'r') {
    471      sort_rx = true;
    472    } else if (*p == 'n') {
    473      sort_nr = true;
    474      format_found++;
    475    } else if (*p == 'f') {
    476      sort_flt = true;
    477      format_found++;
    478    } else if (*p == 'b') {
    479      sort_what = STR2NR_BIN + STR2NR_FORCE;
    480      format_found++;
    481    } else if (*p == 'o') {
    482      sort_what = STR2NR_OCT + STR2NR_FORCE;
    483      format_found++;
    484    } else if (*p == 'x') {
    485      sort_what = STR2NR_HEX + STR2NR_FORCE;
    486      format_found++;
    487    } else if (*p == 'u') {
    488      unique = true;
    489    } else if (*p == '"') {  // comment start
    490      break;
    491    } else if (check_nextcmd(p) != NULL) {
    492      eap->nextcmd = check_nextcmd(p);
    493      break;
    494    } else if (!ASCII_ISALPHA(*p) && regmatch.regprog == NULL) {
    495      char *s = skip_regexp_err(p + 1, *p, true);
    496      if (s == NULL) {
    497        goto sortend;
    498      }
    499      *s = NUL;
    500      // Use last search pattern if sort pattern is empty.
    501      if (s == p + 1) {
    502        if (last_search_pat() == NULL) {
    503          emsg(_(e_noprevre));
    504          goto sortend;
    505        }
    506        regmatch.regprog = vim_regcomp(last_search_pat(), RE_MAGIC);
    507      } else {
    508        regmatch.regprog = vim_regcomp(p + 1, RE_MAGIC);
    509      }
    510      if (regmatch.regprog == NULL) {
    511        goto sortend;
    512      }
    513      p = s;                    // continue after the regexp
    514      regmatch.rm_ic = p_ic;
    515    } else {
    516      semsg(_(e_invarg2), p);
    517      goto sortend;
    518    }
    519  }
    520 
    521  // Can only have one of 'n', 'b', 'o' and 'x'.
    522  if (format_found > 1) {
    523    emsg(_(e_invarg));
    524    goto sortend;
    525  }
    526 
    527  // From here on "sort_nr" is used as a flag for any integer number
    528  // sorting.
    529  sort_nr |= sort_what;
    530 
    531  // Make an array with all line numbers.  This avoids having to copy all
    532  // the lines into allocated memory.
    533  // When sorting on strings "start_col_nr" is the offset in the line, for
    534  // numbers sorting it's the number to sort on.  This means the pattern
    535  // matching and number conversion only has to be done once per line.
    536  // Also get the longest line length for allocating "sortbuf".
    537  for (linenr_T lnum = eap->line1; lnum <= eap->line2; lnum++) {
    538    char *s = ml_get(lnum);
    539    int len = ml_get_len(lnum);
    540    maxlen = MAX(maxlen, len);
    541 
    542    colnr_T start_col = 0;
    543    colnr_T end_col = len;
    544    if (regmatch.regprog != NULL && vim_regexec(&regmatch, s, 0)) {
    545      if (sort_rx) {
    546        start_col = (colnr_T)(regmatch.startp[0] - s);
    547        end_col = (colnr_T)(regmatch.endp[0] - s);
    548      } else {
    549        start_col = (colnr_T)(regmatch.endp[0] - s);
    550      }
    551    } else if (regmatch.regprog != NULL) {
    552      end_col = 0;
    553    }
    554 
    555    if (sort_nr || sort_flt) {
    556      // Make sure vim_str2nr() doesn't read any digits past the end
    557      // of the match, by temporarily terminating the string there
    558      char *s2 = s + end_col;
    559      char c = *s2;  // temporary character storage
    560      *s2 = NUL;
    561      // Sorting on number: Store the number itself.
    562      char *p = s + start_col;
    563      if (sort_nr) {
    564        if (sort_what & STR2NR_HEX) {
    565          s = skiptohex(p);
    566        } else if (sort_what & STR2NR_BIN) {
    567          s = (char *)skiptobin(p);
    568        } else {
    569          s = skiptodigit(p);
    570        }
    571        if (s > p && s[-1] == '-') {
    572          s--;  // include preceding negative sign
    573        }
    574        if (*s == NUL) {
    575          // line without number should sort before any number
    576          nrs[lnum - eap->line1].st_u.num.is_number = false;
    577          nrs[lnum - eap->line1].st_u.num.value = 0;
    578        } else {
    579          nrs[lnum - eap->line1].st_u.num.is_number = true;
    580          vim_str2nr(s, NULL, NULL, sort_what,
    581                     &nrs[lnum - eap->line1].st_u.num.value, NULL, 0, false, NULL);
    582        }
    583      } else {
    584        s = skipwhite(p);
    585        if (*s == '+') {
    586          s = skipwhite(s + 1);
    587        }
    588 
    589        if (*s == NUL) {
    590          // empty line should sort before any number
    591          nrs[lnum - eap->line1].st_u.value_flt = -DBL_MAX;
    592        } else {
    593          nrs[lnum - eap->line1].st_u.value_flt = strtod(s, NULL);
    594        }
    595      }
    596      *s2 = c;
    597    } else {
    598      // Store the column to sort at.
    599      nrs[lnum - eap->line1].st_u.line.start_col_nr = start_col;
    600      nrs[lnum - eap->line1].st_u.line.end_col_nr = end_col;
    601    }
    602 
    603    nrs[lnum - eap->line1].lnum = lnum;
    604 
    605    if (regmatch.regprog != NULL) {
    606      fast_breakcheck();
    607    }
    608    if (got_int) {
    609      goto sortend;
    610    }
    611  }
    612 
    613  // Allocate a buffer that can hold the longest line.
    614  sortbuf1 = xmalloc((size_t)maxlen + 1);
    615  sortbuf2 = xmalloc((size_t)maxlen + 1);
    616 
    617  // Sort the array of line numbers.  Note: can't be interrupted!
    618  qsort((void *)nrs, count, sizeof(sorti_T), sort_compare);
    619 
    620  if (sort_abort) {
    621    goto sortend;
    622  }
    623 
    624  bcount_t old_count = 0;
    625  bcount_t new_count = 0;
    626 
    627  // Insert the lines in the sorted order below the last one.
    628  linenr_T lnum = eap->line2;
    629  for (i = 0; i < count; i++) {
    630    const linenr_T get_lnum = nrs[eap->forceit ? count - i - 1 : i].lnum;
    631 
    632    // If the original line number of the line being placed is not the same
    633    // as "lnum" (accounting for offset), we know that the buffer changed.
    634    if (get_lnum + ((linenr_T)count - 1) != lnum) {
    635      change_occurred = true;
    636    }
    637 
    638    char *s = ml_get(get_lnum);
    639    colnr_T bytelen = ml_get_len(get_lnum) + 1;  // include EOL in bytelen
    640    old_count += bytelen;
    641    if (!unique || i == 0 || string_compare(s, sortbuf1) != 0) {
    642      // Copy the line into a buffer, it may become invalid in
    643      // ml_append(). And it's needed for "unique".
    644      STRCPY(sortbuf1, s);
    645      if (ml_append(lnum++, sortbuf1, 0, false) == FAIL) {
    646        break;
    647      }
    648      new_count += bytelen;
    649    }
    650    fast_breakcheck();
    651    if (got_int) {
    652      goto sortend;
    653    }
    654  }
    655 
    656  // delete the original lines if appending worked
    657  if (i == count) {
    658    for (i = 0; i < count; i++) {
    659      ml_delete(eap->line1);
    660    }
    661  } else {
    662    count = 0;
    663  }
    664 
    665  // Adjust marks for deleted (or added) lines and prepare for displaying.
    666  linenr_T deleted = (linenr_T)count - (lnum - eap->line2);
    667  if (deleted > 0) {
    668    mark_adjust(eap->line2 - deleted, eap->line2, MAXLNUM, -deleted, kExtmarkNOOP);
    669    msgmore(-deleted);
    670  } else if (deleted < 0) {
    671    mark_adjust(eap->line2, MAXLNUM, -deleted, 0, kExtmarkNOOP);
    672  }
    673 
    674  if (change_occurred || deleted != 0) {
    675    extmark_splice(curbuf, eap->line1 - 1, 0,
    676                   (int)count, 0, old_count,
    677                   lnum - eap->line2, 0, new_count, kExtmarkUndo);
    678    changed_lines(curbuf, eap->line1, 0, eap->line2 + 1, -deleted, true);
    679  }
    680 
    681  curwin->w_cursor.lnum = eap->line1;
    682  beginline(BL_WHITE | BL_FIX);
    683 
    684 sortend:
    685  xfree(nrs);
    686  xfree(sortbuf1);
    687  xfree(sortbuf2);
    688  vim_regfree(regmatch.regprog);
    689  if (got_int) {
    690    emsg(_(e_interr));
    691  }
    692 }
    693 
    694 /// ":uniq".
    695 void ex_uniq(exarg_T *eap)
    696 {
    697  regmatch_T regmatch;
    698  int maxlen = 0;
    699  linenr_T count = eap->line2 - eap->line1 + 1;
    700  bool keep_only_unique = false;
    701  bool keep_only_not_unique = eap->forceit;
    702  linenr_T deleted = 0;
    703 
    704  // Uniq one line is really quick!
    705  if (count <= 1) {
    706    return;
    707  }
    708 
    709  if (u_save((linenr_T)(eap->line1 - 1), (linenr_T)(eap->line2 + 1)) == FAIL) {
    710    return;
    711  }
    712  sortbuf1 = NULL;
    713  regmatch.regprog = NULL;
    714 
    715  sort_abort = sort_ic = sort_lc = sort_rx = sort_nr = sort_flt = false;
    716  bool change_occurred = false;    // Buffer contents changed.
    717 
    718  for (char *p = eap->arg; *p != NUL; p++) {
    719    if (ascii_iswhite(*p)) {
    720      // Skip
    721    } else if (*p == 'i') {
    722      sort_ic = true;
    723    } else if (*p == 'l') {
    724      sort_lc = true;
    725    } else if (*p == 'r') {
    726      sort_rx = true;
    727    } else if (*p == 'u') {
    728      // 'u' is only valid when '!' is not given.
    729      if (!keep_only_not_unique) {
    730        keep_only_unique = true;
    731      }
    732    } else if (*p == '"') {  // comment start
    733      break;
    734    } else if (eap->nextcmd == NULL && check_nextcmd(p) != NULL) {
    735      eap->nextcmd = check_nextcmd(p);
    736      break;
    737    } else if (!ASCII_ISALPHA(*p) && regmatch.regprog == NULL) {
    738      char *s = skip_regexp_err(p + 1, *p, true);
    739      if (s == NULL) {
    740        goto uniqend;
    741      }
    742      *s = NUL;
    743      // Use last search pattern if uniq pattern is empty.
    744      if (s == p + 1) {
    745        if (last_search_pat() == NULL) {
    746          emsg(_(e_noprevre));
    747          goto uniqend;
    748        }
    749        regmatch.regprog = vim_regcomp(last_search_pat(), RE_MAGIC);
    750      } else {
    751        regmatch.regprog = vim_regcomp(p + 1, RE_MAGIC);
    752      }
    753      if (regmatch.regprog == NULL) {
    754        goto uniqend;
    755      }
    756      p = s;              // continue after the regexp
    757      regmatch.rm_ic = p_ic;
    758    } else {
    759      semsg(_(e_invarg2), p);
    760      goto uniqend;
    761    }
    762  }
    763 
    764  // Find the length of the longest line.
    765  for (linenr_T lnum = eap->line1; lnum <= eap->line2; lnum++) {
    766    int len = ml_get_len(lnum);
    767    if (maxlen < len) {
    768      maxlen = len;
    769    }
    770 
    771    if (got_int) {
    772      goto uniqend;
    773    }
    774  }
    775 
    776  // Allocate a buffer that can hold the longest line.
    777  sortbuf1 = xmalloc((size_t)maxlen + 1);
    778 
    779  // Delete lines according to options.
    780  bool match_continue = false;
    781  bool next_is_unmatch = false;
    782  linenr_T done_lnum = eap->line1 - 1;
    783  linenr_T delete_lnum = 0;
    784  for (linenr_T i = 0; i < count; i++) {
    785    linenr_T get_lnum = eap->line1 + i;
    786 
    787    char *s = ml_get(get_lnum);
    788    int len = ml_get_len(get_lnum);
    789 
    790    colnr_T start_col = 0;
    791    colnr_T end_col = len;
    792    if (regmatch.regprog != NULL && vim_regexec(&regmatch, s, 0)) {
    793      if (sort_rx) {
    794        start_col = (colnr_T)(regmatch.startp[0] - s);
    795        end_col = (colnr_T)(regmatch.endp[0] - s);
    796      } else {
    797        start_col = (colnr_T)(regmatch.endp[0] - s);
    798      }
    799    } else if (regmatch.regprog != NULL) {
    800      end_col = 0;
    801    }
    802    char save_c = NUL;  // temporary character storage
    803    if (end_col > 0) {
    804      save_c = s[end_col];
    805      s[end_col] = NUL;
    806    }
    807 
    808    bool is_match = i > 0 ? !string_compare(&s[start_col], sortbuf1) : false;
    809    delete_lnum = 0;
    810    if (next_is_unmatch) {
    811      is_match = false;
    812      next_is_unmatch = false;
    813    }
    814 
    815    if (!keep_only_unique && !keep_only_not_unique) {
    816      if (is_match) {
    817        delete_lnum = get_lnum;
    818      } else {
    819        STRCPY(sortbuf1, &s[start_col]);
    820      }
    821    } else if (keep_only_not_unique) {
    822      if (is_match) {
    823        done_lnum = get_lnum - 1;
    824        delete_lnum = get_lnum;
    825        match_continue = true;
    826      } else {
    827        if (i > 0 && !match_continue && get_lnum - 1 > done_lnum) {
    828          delete_lnum = get_lnum - 1;
    829          next_is_unmatch = true;
    830        } else if (i >= count - 1) {
    831          delete_lnum = get_lnum;
    832        }
    833        match_continue = false;
    834        STRCPY(sortbuf1, &s[start_col]);
    835      }
    836    } else {  // keep_only_unique
    837      if (is_match) {
    838        if (!match_continue) {
    839          delete_lnum = get_lnum - 1;
    840        } else {
    841          delete_lnum = get_lnum;
    842        }
    843        match_continue = true;
    844      } else {
    845        if (i == 0 && match_continue) {
    846          delete_lnum = get_lnum;
    847        }
    848        match_continue = false;
    849        STRCPY(sortbuf1, &s[start_col]);
    850      }
    851    }
    852 
    853    if (end_col > 0) {
    854      s[end_col] = save_c;
    855    }
    856 
    857    if (delete_lnum > 0) {
    858      ml_delete(delete_lnum);
    859      i -= get_lnum - delete_lnum + 1;
    860      count--;
    861      deleted++;
    862      change_occurred = true;
    863    }
    864 
    865    fast_breakcheck();
    866    if (got_int) {
    867      goto uniqend;
    868    }
    869  }
    870 
    871  // Adjust marks for deleted lines and prepare for displaying.
    872  mark_adjust(eap->line2 - deleted, eap->line2, MAXLNUM, -deleted,
    873              change_occurred ? kExtmarkUndo : kExtmarkNOOP);
    874  msgmore(-deleted);
    875 
    876  if (change_occurred) {
    877    changed_lines(curbuf, eap->line1, 0, eap->line2 + 1, -deleted, true);
    878  }
    879 
    880  curwin->w_cursor.lnum = eap->line1;
    881  beginline(BL_WHITE | BL_FIX);
    882 
    883 uniqend:
    884  xfree(sortbuf1);
    885  vim_regfree(regmatch.regprog);
    886  if (got_int) {
    887    emsg(_(e_interr));
    888  }
    889 }
    890 
    891 /// :move command - move lines line1-line2 to line dest
    892 ///
    893 /// @return  FAIL for failure, OK otherwise
    894 int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
    895 {
    896  if (dest >= line1 && dest < line2) {
    897    emsg(_("E134: Cannot move a range of lines into itself"));
    898    return FAIL;
    899  }
    900 
    901  // Do nothing if we are not actually moving any lines.  This will prevent
    902  // the 'modified' flag from being set without cause.
    903  if (dest == line1 - 1 || dest == line2) {
    904    // Move the cursor as if lines were moved (see below) to be backwards
    905    // compatible.
    906    curwin->w_cursor.lnum = dest >= line1
    907                            ? dest
    908                            : dest + (line2 - line1) + 1;
    909    return OK;
    910  }
    911 
    912  bcount_t start_byte = ml_find_line_or_offset(curbuf, line1, NULL, true);
    913  bcount_t end_byte = ml_find_line_or_offset(curbuf, line2 + 1, NULL, true);
    914  bcount_t extent_byte = end_byte - start_byte;
    915  bcount_t dest_byte = ml_find_line_or_offset(curbuf, dest + 1, NULL, true);
    916 
    917  linenr_T num_lines = line2 - line1 + 1;  // Num lines moved
    918 
    919  // First we copy the old text to its new location -- webb
    920  // Also copy the flag that ":global" command uses.
    921  if (u_save(dest, dest + 1) == FAIL) {
    922    return FAIL;
    923  }
    924 
    925  linenr_T l;
    926  linenr_T extra;      // Num lines added before line1
    927  for (extra = 0, l = line1; l <= line2; l++) {
    928    char *str = xstrnsave(ml_get(l + extra), (size_t)ml_get_len(l + extra));
    929    ml_append(dest + l - line1, str, 0, false);
    930    xfree(str);
    931    if (dest < line1) {
    932      extra++;
    933    }
    934  }
    935 
    936  // Now we must be careful adjusting our marks so that we don't overlap our
    937  // mark_adjust() calls.
    938  //
    939  // We adjust the marks within the old text so that they refer to the
    940  // last lines of the file (temporarily), because we know no other marks
    941  // will be set there since these line numbers did not exist until we added
    942  // our new lines.
    943  //
    944  // Then we adjust the marks on lines between the old and new text positions
    945  // (either forwards or backwards).
    946  //
    947  // And Finally we adjust the marks we put at the end of the file back to
    948  // their final destination at the new text position -- webb
    949  linenr_T last_line = curbuf->b_ml.ml_line_count;  // Last line in file after adding new text
    950  mark_adjust_nofold(line1, line2, last_line - line2, 0, kExtmarkNOOP);
    951 
    952  disable_fold_update++;
    953  changed_lines(curbuf, last_line - num_lines + 1, 0, last_line + 1, num_lines, false);
    954  disable_fold_update--;
    955 
    956  int line_off = 0;
    957  bcount_t byte_off = 0;
    958  if (dest >= line2) {
    959    mark_adjust_nofold(line2 + 1, dest, -num_lines, 0, kExtmarkNOOP);
    960    FOR_ALL_TAB_WINDOWS(tab, win) {
    961      if (win->w_buffer == curbuf) {
    962        foldMoveRange(win, &win->w_folds, line1, line2, dest);
    963      }
    964    }
    965    if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
    966      curbuf->b_op_start.lnum = dest - num_lines + 1;
    967      curbuf->b_op_end.lnum = dest;
    968    }
    969    line_off = -num_lines;
    970    byte_off = -extent_byte;
    971  } else {
    972    mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0, kExtmarkNOOP);
    973    FOR_ALL_TAB_WINDOWS(tab, win) {
    974      if (win->w_buffer == curbuf) {
    975        foldMoveRange(win, &win->w_folds, dest + 1, line1 - 1, line2);
    976      }
    977    }
    978    if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
    979      curbuf->b_op_start.lnum = dest + 1;
    980      curbuf->b_op_end.lnum = dest + num_lines;
    981    }
    982  }
    983  if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
    984    curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
    985  }
    986  mark_adjust_nofold(last_line - num_lines + 1, last_line,
    987                     -(last_line - dest - extra), 0, kExtmarkNOOP);
    988 
    989  disable_fold_update++;
    990  changed_lines(curbuf, last_line - num_lines + 1, 0, last_line + 1, -extra, false);
    991  disable_fold_update--;
    992 
    993  // send update regarding the new lines that were added
    994  buf_updates_send_changes(curbuf, dest + 1, num_lines, 0);
    995 
    996  // Now we delete the original text -- webb
    997  if (u_save(line1 + extra - 1, line2 + extra + 1) == FAIL) {
    998    return FAIL;
    999  }
   1000 
   1001  for (l = line1; l <= line2; l++) {
   1002    ml_delete_flags(line1 + extra, ML_DEL_MESSAGE);
   1003  }
   1004  if (!global_busy && num_lines > p_report) {
   1005    smsg(0, NGETTEXT("%" PRId64 " line moved",
   1006                     "%" PRId64 " lines moved", num_lines),
   1007         (int64_t)num_lines);
   1008  }
   1009 
   1010  extmark_move_region(curbuf, line1 - 1, 0, start_byte,
   1011                      line2 - line1 + 1, 0, extent_byte,
   1012                      dest + line_off, 0, dest_byte + byte_off,
   1013                      kExtmarkUndo);
   1014 
   1015  // Leave the cursor on the last of the moved lines.
   1016  if (dest >= line1) {
   1017    curwin->w_cursor.lnum = dest;
   1018  } else {
   1019    curwin->w_cursor.lnum = dest + (line2 - line1) + 1;
   1020  }
   1021 
   1022  if (line1 < dest) {
   1023    dest += num_lines + 1;
   1024    last_line = curbuf->b_ml.ml_line_count;
   1025    dest = MIN(dest, last_line + 1);
   1026    changed_lines(curbuf, line1, 0, dest, 0, false);
   1027  } else {
   1028    changed_lines(curbuf, dest + 1, 0, line1 + num_lines, 0, false);
   1029  }
   1030 
   1031  // send nvim_buf_lines_event regarding lines that were deleted
   1032  buf_updates_send_changes(curbuf, line1 + extra, 0, num_lines);
   1033 
   1034  return OK;
   1035 }
   1036 
   1037 /// ":copy"
   1038 void ex_copy(linenr_T line1, linenr_T line2, linenr_T n)
   1039 {
   1040  linenr_T count = line2 - line1 + 1;
   1041  if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
   1042    curbuf->b_op_start.lnum = n + 1;
   1043    curbuf->b_op_end.lnum = n + count;
   1044    curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
   1045  }
   1046 
   1047  // there are three situations:
   1048  // 1. destination is above line1
   1049  // 2. destination is between line1 and line2
   1050  // 3. destination is below line2
   1051  //
   1052  // n = destination (when starting)
   1053  // curwin->w_cursor.lnum = destination (while copying)
   1054  // line1 = start of source (while copying)
   1055  // line2 = end of source (while copying)
   1056  if (u_save(n, n + 1) == FAIL) {
   1057    return;
   1058  }
   1059 
   1060  curwin->w_cursor.lnum = n;
   1061  while (line1 <= line2) {
   1062    // need to make a copy because the line will be unlocked within ml_append()
   1063    char *p = xstrnsave(ml_get(line1), (size_t)ml_get_len(line1));
   1064    ml_append(curwin->w_cursor.lnum, p, 0, false);
   1065    xfree(p);
   1066 
   1067    // situation 2: skip already copied lines
   1068    if (line1 == n) {
   1069      line1 = curwin->w_cursor.lnum;
   1070    }
   1071    line1++;
   1072    if (curwin->w_cursor.lnum < line1) {
   1073      line1++;
   1074    }
   1075    if (curwin->w_cursor.lnum < line2) {
   1076      line2++;
   1077    }
   1078    curwin->w_cursor.lnum++;
   1079  }
   1080 
   1081  appended_lines_mark(n, count);
   1082  if (VIsual_active) {
   1083    check_pos(curbuf, &VIsual);
   1084  }
   1085 
   1086  msgmore(count);
   1087 }
   1088 
   1089 static char *prevcmd = NULL;        // the previous command
   1090 
   1091 #if defined(EXITFREE)
   1092 void free_prev_shellcmd(void)
   1093 {
   1094  xfree(prevcmd);
   1095 }
   1096 
   1097 #endif
   1098 
   1099 /// Check that "prevcmd" is not NULL.  If it is NULL then give an error message
   1100 /// and return false.
   1101 static int prevcmd_is_set(void)
   1102 {
   1103  if (prevcmd == NULL) {
   1104    emsg(_(e_noprev));
   1105    return false;
   1106  }
   1107  return true;
   1108 }
   1109 
   1110 /// Handle the ":!cmd" command.  Also for ":r !cmd" and ":w !cmd"
   1111 /// Bangs in the argument are replaced with the previously entered command.
   1112 /// Remember the argument.
   1113 void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out)
   1114  FUNC_ATTR_NONNULL_ALL
   1115 {
   1116  char *arg = eap->arg;             // command
   1117  linenr_T line1 = eap->line1;        // start of range
   1118  linenr_T line2 = eap->line2;        // end of range
   1119  char *newcmd = NULL;              // the new command
   1120  bool free_newcmd = false;           // need to free() newcmd
   1121  int scroll_save = msg_scroll;
   1122 
   1123  // Disallow shell commands in secure mode
   1124  if (check_secure()) {
   1125    return;
   1126  }
   1127 
   1128  if (addr_count == 0) {                // :!
   1129    msg_scroll = false;             // don't scroll here
   1130    autowrite_all();
   1131    msg_scroll = scroll_save;
   1132  }
   1133 
   1134  // Try to find an embedded bang, like in ":!<cmd> ! [args]"
   1135  // ":!!" is indicated by the 'forceit' variable.
   1136  bool ins_prevcmd = forceit;
   1137 
   1138  // Skip leading white space to avoid a strange error with some shells.
   1139  char *trailarg = skipwhite(arg);
   1140  do {
   1141    size_t len = strlen(trailarg) + 1;
   1142    if (newcmd != NULL) {
   1143      len += strlen(newcmd);
   1144    }
   1145    if (ins_prevcmd) {
   1146      if (!prevcmd_is_set()) {
   1147        xfree(newcmd);
   1148        return;
   1149      }
   1150      len += strlen(prevcmd);
   1151    }
   1152    char *t = xmalloc(len);
   1153    *t = NUL;
   1154    if (newcmd != NULL) {
   1155      strcat(t, newcmd);
   1156    }
   1157    if (ins_prevcmd) {
   1158      strcat(t, prevcmd);
   1159    }
   1160    char *p = t + strlen(t);
   1161    strcat(t, trailarg);
   1162    xfree(newcmd);
   1163    newcmd = t;
   1164 
   1165    // Scan the rest of the argument for '!', which is replaced by the
   1166    // previous command.  "\!" is replaced by "!" (this is vi compatible).
   1167    trailarg = NULL;
   1168    while (*p) {
   1169      if (*p == '!') {
   1170        if (p > newcmd && p[-1] == '\\') {
   1171          STRMOVE(p - 1, p);
   1172        } else {
   1173          trailarg = p;
   1174          *trailarg++ = NUL;
   1175          ins_prevcmd = true;
   1176          break;
   1177        }
   1178      }
   1179      p++;
   1180    }
   1181  } while (trailarg != NULL);
   1182 
   1183  // Only set "prevcmd" if there is a command to run, otherwise keep te one
   1184  // we have.
   1185  if (strlen(newcmd) > 0) {
   1186    xfree(prevcmd);
   1187    prevcmd = newcmd;
   1188  } else {
   1189    free_newcmd = true;
   1190  }
   1191 
   1192  if (bangredo) {  // put cmd in redo buffer for ! command
   1193    if (!prevcmd_is_set()) {
   1194      goto theend;
   1195    }
   1196 
   1197    // If % or # appears in the command, it must have been escaped.
   1198    // Reescape them, so that redoing them does not substitute them by the
   1199    // buffername.
   1200    char *cmd = vim_strsave_escaped(prevcmd, "%#");
   1201 
   1202    AppendToRedobuffLit(cmd, -1);
   1203    xfree(cmd);
   1204    AppendToRedobuff("\n");
   1205    bangredo = false;
   1206  }
   1207  // Add quotes around the command, for shells that need them.
   1208  if (*p_shq != NUL) {
   1209    if (free_newcmd) {
   1210      xfree(newcmd);
   1211    }
   1212    newcmd = xmalloc(strlen(prevcmd) + 2 * strlen(p_shq) + 1);
   1213    STRCPY(newcmd, p_shq);
   1214    strcat(newcmd, prevcmd);
   1215    strcat(newcmd, p_shq);
   1216    free_newcmd = true;
   1217  }
   1218  if (addr_count == 0) {                // :!
   1219    // echo the command
   1220    msg_start();
   1221    msg_ext_set_kind("shell_cmd");
   1222    msg_putchar(':');
   1223    msg_putchar('!');
   1224    msg_outtrans(newcmd, 0, false);
   1225    msg_clr_eos();
   1226    ui_cursor_goto(msg_row, msg_col);
   1227 
   1228    do_shell(newcmd, 0);
   1229  } else {                            // :range!
   1230    // Careful: This may recursively call do_bang() again! (because of
   1231    // autocommands)
   1232    do_filter(line1, line2, eap, newcmd, do_in, do_out);
   1233    apply_autocmds(EVENT_SHELLFILTERPOST, NULL, NULL, false, curbuf);
   1234  }
   1235 
   1236 theend:
   1237  if (free_newcmd) {
   1238    xfree(newcmd);
   1239  }
   1240 }
   1241 
   1242 /// do_filter: filter lines through a command given by the user
   1243 ///
   1244 /// We mostly use temp files and the call_shell() routine here. This would
   1245 /// normally be done using pipes on a Unix system, but this is more portable
   1246 /// to non-Unix systems. The call_shell() routine needs to be able
   1247 /// to deal with redirection somehow, and should handle things like looking
   1248 /// at the PATH env. variable, and adding reasonable extensions to the
   1249 /// command name given by the user. All reasonable versions of call_shell()
   1250 /// do this.
   1251 /// Alternatively, if on Unix and redirecting input or output, but not both,
   1252 /// and the 'shelltemp' option isn't set, use pipes.
   1253 /// We use input redirection if do_in is true.
   1254 /// We use output redirection if do_out is true.
   1255 ///
   1256 /// @param eap  for forced 'ff' and 'fenc'
   1257 static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, bool do_in,
   1258                      bool do_out)
   1259 {
   1260  char *itmp = NULL;
   1261  char *otmp = NULL;
   1262  buf_T *old_curbuf = curbuf;
   1263  int shell_flags = 0;
   1264  const pos_T orig_start = curbuf->b_op_start;
   1265  const pos_T orig_end = curbuf->b_op_end;
   1266  const int stmp = p_stmp;
   1267 
   1268  if (*cmd == NUL) {        // no filter command
   1269    return;
   1270  }
   1271 
   1272  const int save_cmod_flags = cmdmod.cmod_flags;
   1273  // Temporarily disable lockmarks since that's needed to propagate changed
   1274  // regions of the buffer for foldUpdate(), linecount, etc.
   1275  cmdmod.cmod_flags &= ~CMOD_LOCKMARKS;
   1276 
   1277  pos_T cursor_save = curwin->w_cursor;
   1278  linenr_T linecount = line2 - line1 + 1;
   1279  curwin->w_cursor.lnum = line1;
   1280  curwin->w_cursor.col = 0;
   1281  changed_line_abv_curs();
   1282  invalidate_botline_win(curwin);
   1283 
   1284  // When using temp files:
   1285  // 1. * Form temp file names
   1286  // 2. * Write the lines to a temp file
   1287  // 3.   Run the filter command on the temp file
   1288  // 4. * Read the output of the command into the buffer
   1289  // 5. * Delete the original lines to be filtered
   1290  // 6. * Remove the temp files
   1291  //
   1292  // When writing the input with a pipe or when catching the output with a
   1293  // pipe only need to do 3.
   1294 
   1295  if (do_out) {
   1296    shell_flags |= kShellOptDoOut;
   1297  }
   1298 
   1299  if (!do_in && do_out && !stmp) {
   1300    // Use a pipe to fetch stdout of the command, do not use a temp file.
   1301    shell_flags |= kShellOptRead;
   1302    curwin->w_cursor.lnum = line2;
   1303  } else if (do_in && !do_out && !stmp) {
   1304    // Use a pipe to write stdin of the command, do not use a temp file.
   1305    shell_flags |= kShellOptWrite;
   1306    curbuf->b_op_start.lnum = line1;
   1307    curbuf->b_op_end.lnum = line2;
   1308  } else if (do_in && do_out && !stmp) {
   1309    // Use a pipe to write stdin and fetch stdout of the command, do not
   1310    // use a temp file.
   1311    shell_flags |= kShellOptRead | kShellOptWrite;
   1312    curbuf->b_op_start.lnum = line1;
   1313    curbuf->b_op_end.lnum = line2;
   1314    curwin->w_cursor.lnum = line2;
   1315  } else if ((do_in && (itmp = vim_tempname()) == NULL)
   1316             || (do_out && (otmp = vim_tempname()) == NULL)) {
   1317    emsg(_(e_notmp));
   1318    goto filterend;
   1319  }
   1320 
   1321  // The writing and reading of temp files will not be shown.
   1322  // Vi also doesn't do this and the messages are not very informative.
   1323  no_wait_return++;             // don't call wait_return() while busy
   1324  if (itmp != NULL && buf_write(curbuf, itmp, NULL, line1, line2, eap,
   1325                                false, false, false, true) == FAIL) {
   1326    if (!ui_has(kUIMessages)) {
   1327      msg_putchar('\n');  // Keep message from buf_write().
   1328    }
   1329    no_wait_return--;
   1330    if (!aborting()) {
   1331      // will call wait_return()
   1332      semsg(_("E482: Can't create file %s"), itmp);
   1333    }
   1334    goto filterend;
   1335  }
   1336  if (curbuf != old_curbuf) {
   1337    goto filterend;
   1338  }
   1339 
   1340  if (!do_out) {
   1341    msg_putchar('\n');
   1342  }
   1343 
   1344  // Create the shell command in allocated memory.
   1345  char *cmd_buf = make_filter_cmd(cmd, itmp, otmp, do_in);
   1346  ui_cursor_goto(Rows - 1, 0);
   1347 
   1348  if (do_out) {
   1349    if (u_save(line2, (linenr_T)(line2 + 1)) == FAIL) {
   1350      xfree(cmd_buf);
   1351      goto error;
   1352    }
   1353    redraw_curbuf_later(UPD_VALID);
   1354  }
   1355  linenr_T read_linecount = curbuf->b_ml.ml_line_count;
   1356 
   1357  // Pass on the kShellOptDoOut flag when the output is being redirected.
   1358  call_shell(cmd_buf, kShellOptFilter | shell_flags, NULL);
   1359  xfree(cmd_buf);
   1360 
   1361  did_check_timestamps = false;
   1362  need_check_timestamps = true;
   1363 
   1364  // When interrupting the shell command, it may still have produced some
   1365  // useful output.  Reset got_int here, so that readfile() won't cancel
   1366  // reading.
   1367  os_breakcheck();
   1368  got_int = false;
   1369 
   1370  if (do_out) {
   1371    if (otmp != NULL) {
   1372      if (readfile(otmp, NULL, line2, 0, (linenr_T)MAXLNUM, eap,
   1373                   READ_FILTER, false) != OK) {
   1374        if (!aborting()) {
   1375          msg_putchar('\n');
   1376          semsg(_(e_cant_read_file_str), otmp);
   1377        }
   1378        goto error;
   1379      }
   1380      if (curbuf != old_curbuf) {
   1381        goto filterend;
   1382      }
   1383    }
   1384 
   1385    read_linecount = curbuf->b_ml.ml_line_count - read_linecount;
   1386 
   1387    if (shell_flags & kShellOptRead) {
   1388      curbuf->b_op_start.lnum = line2 + 1;
   1389      curbuf->b_op_end.lnum = curwin->w_cursor.lnum;
   1390      appended_lines_mark(line2, read_linecount);
   1391    }
   1392 
   1393    if (do_in) {
   1394      if ((cmdmod.cmod_flags & CMOD_KEEPMARKS)
   1395          || vim_strchr(p_cpo, CPO_REMMARK) == NULL) {
   1396        // TODO(bfredl): Currently not active for extmarks. What would we
   1397        // do if columns don't match, assume added/deleted bytes at the
   1398        // end of each line?
   1399        if (read_linecount >= linecount) {
   1400          // move all marks from old lines to new lines
   1401          mark_adjust(line1, line2, linecount, 0, kExtmarkNOOP);
   1402        } else {
   1403          // move marks from old lines to new lines, delete marks
   1404          // that are in deleted lines
   1405          mark_adjust(line1, line1 + read_linecount - 1, linecount, 0,
   1406                      kExtmarkNOOP);
   1407          mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0,
   1408                      kExtmarkNOOP);
   1409        }
   1410      }
   1411 
   1412      // Put cursor on first filtered line for ":range!cmd".
   1413      // Adjust '[ and '] (set by buf_write()).
   1414      curwin->w_cursor.lnum = line1;
   1415      del_lines(linecount, true);
   1416      curbuf->b_op_start.lnum -= linecount;             // adjust '[
   1417      curbuf->b_op_end.lnum -= linecount;               // adjust ']
   1418      write_lnum_adjust(-linecount);                    // adjust last line
   1419                                                        // for next write
   1420      foldUpdate(curwin, curbuf->b_op_start.lnum, curbuf->b_op_end.lnum);
   1421    } else {
   1422      // Put cursor on last new line for ":r !cmd".
   1423      linecount = curbuf->b_op_end.lnum - curbuf->b_op_start.lnum + 1;
   1424      curwin->w_cursor.lnum = curbuf->b_op_end.lnum;
   1425    }
   1426 
   1427    beginline(BL_WHITE | BL_FIX);           // cursor on first non-blank
   1428    no_wait_return--;
   1429 
   1430    if (linecount > p_report) {
   1431      if (do_in) {
   1432        vim_snprintf(msg_buf, sizeof(msg_buf),
   1433                     _("%" PRId64 " lines filtered"), (int64_t)linecount);
   1434        if (msg(msg_buf, 0) && !msg_scroll) {
   1435          // save message to display it after redraw
   1436          set_keep_msg(msg_buf, 0);
   1437        }
   1438      } else {
   1439        msgmore(linecount);
   1440      }
   1441    }
   1442  } else {
   1443 error:
   1444    // put cursor back in same position for ":w !cmd"
   1445    curwin->w_cursor = cursor_save;
   1446    no_wait_return--;
   1447    wait_return(false);
   1448  }
   1449 
   1450 filterend:
   1451 
   1452  cmdmod.cmod_flags = save_cmod_flags;
   1453  if (curbuf != old_curbuf) {
   1454    no_wait_return--;
   1455    emsg(_("E135: *Filter* Autocommands must not change current buffer"));
   1456  } else if (cmdmod.cmod_flags & CMOD_LOCKMARKS) {
   1457    curbuf->b_op_start = orig_start;
   1458    curbuf->b_op_end = orig_end;
   1459  }
   1460 
   1461  if (itmp != NULL) {
   1462    os_remove(itmp);
   1463  }
   1464  if (otmp != NULL) {
   1465    os_remove(otmp);
   1466  }
   1467  xfree(itmp);
   1468  xfree(otmp);
   1469 }
   1470 
   1471 /// Call a shell to execute a command.
   1472 /// When "cmd" is NULL start an interactive shell.
   1473 ///
   1474 /// @param flags  may be SHELL_DOOUT when output is redirected
   1475 void do_shell(char *cmd, int flags)
   1476 {
   1477  // Disallow shell commands in secure mode
   1478  if (check_secure()) {
   1479    msg_end();
   1480    return;
   1481  }
   1482 
   1483  // For autocommands we want to get the output on the current screen, to
   1484  // avoid having to type return below.
   1485  msg_putchar('\r');                    // put cursor at start of line
   1486  msg_putchar('\n');                    // may shift screen one line up
   1487 
   1488  // warning message before calling the shell
   1489  if (p_warn
   1490      && !autocmd_busy
   1491      && msg_silent == 0) {
   1492    FOR_ALL_BUFFERS(buf) {
   1493      if (bufIsChanged(buf)) {
   1494        msg_puts(_("[No write since last change]\n"));
   1495        break;
   1496      }
   1497    }
   1498  }
   1499 
   1500  // This ui_cursor_goto is required for when the '\n' resulted in a "delete line
   1501  // 1" command to the terminal.
   1502  ui_cursor_goto(msg_row, msg_col);
   1503  call_shell(cmd, flags, NULL);
   1504  if (msg_silent == 0) {
   1505    msg_didout = true;
   1506  }
   1507  did_check_timestamps = false;
   1508  need_check_timestamps = true;
   1509 
   1510  // put the message cursor at the end of the screen, avoids wait_return()
   1511  // to overwrite the text that the external command showed
   1512  msg_row = Rows - 1;
   1513  msg_col = 0;
   1514 
   1515  apply_autocmds(EVENT_SHELLCMDPOST, NULL, NULL, false, curbuf);
   1516 }
   1517 
   1518 #if !defined(UNIX)
   1519 static char *find_pipe(const char *cmd)
   1520 {
   1521  bool inquote = false;
   1522 
   1523  for (const char *p = cmd; *p != NUL; p++) {
   1524    if (!inquote && *p == '|') {
   1525      return (char *)p;
   1526    }
   1527    if (*p == '"') {
   1528      inquote = !inquote;
   1529    } else if (rem_backslash(p)) {
   1530      p++;
   1531    }
   1532  }
   1533  return NULL;
   1534 }
   1535 #endif
   1536 
   1537 /// Create a shell command from a command string, input redirection file and
   1538 /// output redirection file.
   1539 ///
   1540 /// @param cmd  Command to execute.
   1541 /// @param itmp NULL or the input file.
   1542 /// @param otmp NULL or the output file.
   1543 /// @param do_in true if stdin is needed.
   1544 /// @returns an allocated string with the shell command.
   1545 char *make_filter_cmd(char *cmd, char *itmp, char *otmp, bool do_in)
   1546 {
   1547  bool is_fish_shell =
   1548 #if defined(UNIX)
   1549    strncmp(invocation_path_tail(p_sh, NULL), "fish", 4) == 0;
   1550 #else
   1551    false;
   1552 #endif
   1553  bool is_pwsh = strncmp(invocation_path_tail(p_sh, NULL), "pwsh", 4) == 0
   1554                 || strncmp(invocation_path_tail(p_sh, NULL), "powershell",
   1555                            10) == 0;
   1556 
   1557  size_t len = strlen(cmd) + 1;  // At least enough space for cmd + NULL.
   1558 
   1559  len += is_fish_shell ? sizeof("begin; " "; end") - 1
   1560                       : !is_pwsh ? sizeof("(" ")") - 1
   1561                                  : 0;
   1562 
   1563  if (itmp != NULL) {
   1564    len += is_pwsh ? strlen(itmp) + sizeof("& { Get-Content " " | & " " }") - 1 + 6  // +6: #20530
   1565                   : strlen(itmp) + sizeof(" { " " < " " } ") - 1;
   1566  }
   1567 
   1568  if (do_in && is_pwsh) {
   1569    len += sizeof(" $input | ");
   1570  }
   1571 
   1572  if (otmp != NULL) {
   1573    len += strlen(otmp) + strlen(p_srr) + 2;  // two extra spaces ("  "),
   1574  }
   1575 
   1576  char *const buf = xmalloc(len);
   1577 
   1578  if (is_pwsh) {
   1579    if (itmp != NULL) {
   1580      xstrlcpy(buf, "& { Get-Content ", len - 1);  // FIXME: should we add "-Encoding utf8"?
   1581      xstrlcat(buf, itmp, len - 1);
   1582      xstrlcat(buf, " | & ", len - 1);  // FIXME: add `&` ourself or leave to user?
   1583      xstrlcat(buf, cmd, len - 1);
   1584      xstrlcat(buf, " }", len - 1);
   1585    } else if (do_in) {
   1586      xstrlcpy(buf, " $input | ", len - 1);
   1587      xstrlcat(buf, cmd, len);
   1588    } else {
   1589      xstrlcpy(buf, cmd, len);
   1590    }
   1591  } else {
   1592 #if defined(UNIX)
   1593    // Put delimiters around the command (for concatenated commands) when
   1594    // redirecting input and/or output.
   1595    if (itmp != NULL || otmp != NULL) {
   1596      char *fmt = is_fish_shell ? "begin; %s; end"
   1597                                : "(%s)";
   1598      vim_snprintf(buf, len, fmt, cmd);
   1599    } else {
   1600      xstrlcpy(buf, cmd, len);
   1601    }
   1602 
   1603    if (itmp != NULL) {
   1604      xstrlcat(buf, " < ", len - 1);
   1605      xstrlcat(buf, itmp, len - 1);
   1606    }
   1607 #else
   1608    // For shells that don't understand braces around commands, at least allow
   1609    // the use of commands in a pipe.
   1610    xstrlcpy(buf, cmd, len);
   1611    if (itmp != NULL) {
   1612      // If there is a pipe, we have to put the '<' in front of it.
   1613      // Don't do this when 'shellquote' is not empty, otherwise the
   1614      // redirection would be inside the quotes.
   1615      if (*p_shq == NUL) {
   1616        char *const p = find_pipe(buf);
   1617        if (p != NULL) {
   1618          *p = NUL;
   1619        }
   1620      }
   1621      xstrlcat(buf, " < ", len);
   1622      xstrlcat(buf, itmp, len);
   1623      if (*p_shq == NUL) {
   1624        const char *const p = find_pipe(cmd);
   1625        if (p != NULL) {
   1626          xstrlcat(buf, " ", len - 1);  // Insert a space before the '|' for DOS
   1627          xstrlcat(buf, p, len - 1);
   1628        }
   1629      }
   1630    }
   1631 #endif
   1632  }
   1633  if (otmp != NULL) {
   1634    append_redir(buf, len, p_srr, otmp);
   1635  }
   1636  return buf;
   1637 }
   1638 
   1639 /// Append output redirection for the given file to the end of the buffer
   1640 ///
   1641 /// @param[out]  buf  Buffer to append to.
   1642 /// @param[in]  buflen  Buffer length.
   1643 /// @param[in]  opt  Separator or format string to append: will append
   1644 ///                  `printf(' ' . opt, fname)` if `%s` is found in `opt` or
   1645 ///                  a space, opt, a space and then fname if `%s` is not found
   1646 ///                  there.
   1647 /// @param[in]  fname  File name to append.
   1648 void append_redir(char *const buf, const size_t buflen, const char *const opt,
   1649                  const char *const fname)
   1650 {
   1651  char *const end = buf + strlen(buf);
   1652  // find "%s"
   1653  const char *p = opt;
   1654  for (; (p = strchr(p, '%')) != NULL; p++) {
   1655    if (p[1] == 's') {  // found %s
   1656      break;
   1657    } else if (p[1] == '%') {  // skip %%
   1658      p++;
   1659    }
   1660  }
   1661  if (p != NULL) {
   1662    *end = ' ';  // not really needed? Not with sh, ksh or bash
   1663    vim_snprintf(end + 1, (size_t)((ptrdiff_t)buflen - (end + 1 - buf)), opt, fname);
   1664  } else {
   1665    vim_snprintf(end, (size_t)((ptrdiff_t)buflen - (end - buf)), " %s %s", opt, fname);
   1666  }
   1667 }
   1668 
   1669 void print_line_no_prefix(linenr_T lnum, bool use_number, bool list)
   1670 {
   1671  char numbuf[30];
   1672 
   1673  if (curwin->w_p_nu || use_number) {
   1674    vim_snprintf(numbuf, sizeof(numbuf), "%*" PRIdLINENR " ",
   1675                 number_width(curwin), lnum);
   1676    msg_puts_hl(numbuf, HLF_N + 1, false);  // Highlight line nrs.
   1677  }
   1678  msg_prt_line(ml_get(lnum), list);
   1679 }
   1680 
   1681 static bool global_need_msg_kind = false;  // Start new message only once during :global.
   1682 
   1683 /// Print a text line.  Also in silent mode ("ex -s").
   1684 void print_line(linenr_T lnum, bool use_number, bool list, bool first)
   1685 {
   1686  bool save_silent = silent_mode;
   1687 
   1688  // apply :filter /pat/
   1689  if (message_filtered(ml_get(lnum))) {
   1690    return;
   1691  }
   1692 
   1693  silent_mode = false;
   1694  info_message = true;  // use stdout, not stderr
   1695  if ((!global_busy || global_need_msg_kind) && first) {
   1696    msg_start();
   1697    msg_ext_set_kind("list_cmd");
   1698    global_need_msg_kind = false;
   1699  } else if (!save_silent) {
   1700    msg_putchar('\n');  // don't want trailing newline with regular messaging
   1701  }
   1702  print_line_no_prefix(lnum, use_number, list);
   1703  if (save_silent) {
   1704    msg_putchar('\n');  // batch mode message should always end in newline
   1705    silent_mode = save_silent;
   1706  }
   1707  info_message = false;
   1708 }
   1709 
   1710 int rename_buffer(char *new_fname)
   1711 {
   1712  buf_T *buf = curbuf;
   1713  apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, false, curbuf);
   1714  // buffer changed, don't change name now
   1715  if (buf != curbuf) {
   1716    return FAIL;
   1717  }
   1718  if (aborting()) {         // autocmds may abort script processing
   1719    return FAIL;
   1720  }
   1721  // The name of the current buffer will be changed.
   1722  // A new (unlisted) buffer entry needs to be made to hold the old file
   1723  // name, which will become the alternate file name.
   1724  // But don't set the alternate file name if the buffer didn't have a
   1725  // name.
   1726  char *fname = curbuf->b_ffname;
   1727  char *sfname = curbuf->b_sfname;
   1728  char *xfname = curbuf->b_fname;
   1729  curbuf->b_ffname = NULL;
   1730  curbuf->b_sfname = NULL;
   1731  if (setfname(curbuf, new_fname, NULL, true) == FAIL) {
   1732    curbuf->b_ffname = fname;
   1733    curbuf->b_sfname = sfname;
   1734    return FAIL;
   1735  }
   1736  curbuf->b_flags |= BF_NOTEDITED;
   1737  if (xfname != NULL && *xfname != NUL) {
   1738    buf = buflist_new(fname, xfname, curwin->w_cursor.lnum, 0);
   1739    if (buf != NULL && (cmdmod.cmod_flags & CMOD_KEEPALT) == 0) {
   1740      curwin->w_alt_fnum = buf->b_fnum;
   1741    }
   1742  }
   1743  xfree(fname);
   1744  xfree(sfname);
   1745  apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, curbuf);
   1746  // Change directories when the 'acd' option is set.
   1747  do_autochdir();
   1748  return OK;
   1749 }
   1750 
   1751 /// ":file[!] [fname]".
   1752 void ex_file(exarg_T *eap)
   1753 {
   1754  // ":0file" removes the file name.  Check for illegal uses ":3file",
   1755  // "0file name", etc.
   1756  if (eap->addr_count > 0
   1757      && (*eap->arg != NUL
   1758          || eap->line2 > 0
   1759          || eap->addr_count > 1)) {
   1760    emsg(_(e_invarg));
   1761    return;
   1762  }
   1763 
   1764  if (*eap->arg != NUL || eap->addr_count == 1) {
   1765    if (rename_buffer(eap->arg) == FAIL) {
   1766      return;
   1767    }
   1768    redraw_tabline = true;
   1769  }
   1770 
   1771  // print file name if no argument or 'F' is not in 'shortmess'
   1772  if (*eap->arg == NUL || !shortmess(SHM_FILEINFO)) {
   1773    fileinfo(false, false, eap->forceit);
   1774  }
   1775 }
   1776 
   1777 /// ":update".
   1778 void ex_update(exarg_T *eap)
   1779 {
   1780  if (curbufIsChanged()
   1781      || (!bt_nofilename(curbuf) && curbuf->b_ffname != NULL
   1782          && !os_path_exists(curbuf->b_ffname))) {
   1783    do_write(eap);
   1784  }
   1785 }
   1786 
   1787 /// ":write" and ":saveas".
   1788 void ex_write(exarg_T *eap)
   1789 {
   1790  if (eap->cmdidx == CMD_saveas) {
   1791    // :saveas does not take a range, uses all lines.
   1792    eap->line1 = 1;
   1793    eap->line2 = curbuf->b_ml.ml_line_count;
   1794  }
   1795 
   1796  if (eap->usefilter) {  // input lines to shell command
   1797    do_bang(1, eap, false, true, false);
   1798  } else {
   1799    do_write(eap);
   1800  }
   1801 }
   1802 
   1803 #ifdef UNIX
   1804 static int check_writable(const char *fname)
   1805 {
   1806  if (os_nodetype(fname) == NODE_OTHER) {
   1807    semsg(_("E503: \"%s\" is not a file or writable device"), fname);
   1808    return FAIL;
   1809  }
   1810  return OK;
   1811 }
   1812 #endif
   1813 
   1814 static int handle_mkdir_p_arg(exarg_T *eap, char *fname)
   1815 {
   1816  if (eap->mkdir_p && os_file_mkdir(fname, 0755) < 0) {
   1817    return FAIL;
   1818  }
   1819 
   1820  return OK;
   1821 }
   1822 
   1823 /// Write current buffer to file "eap->arg".
   1824 /// If "eap->append" is true, append to the file.
   1825 ///
   1826 /// If "*eap->arg == NUL" write to current file.
   1827 ///
   1828 /// @return  FAIL for failure, OK otherwise.
   1829 int do_write(exarg_T *eap)
   1830 {
   1831  bool other;
   1832  char *fname = NULL;            // init to shut up gcc
   1833  int retval = FAIL;
   1834  char *free_fname = NULL;
   1835  buf_T *alt_buf = NULL;
   1836 
   1837  if (not_writing()) {          // check 'write' option
   1838    return FAIL;
   1839  }
   1840 
   1841  char *ffname = eap->arg;
   1842  if (*ffname == NUL) {
   1843    if (eap->cmdidx == CMD_saveas) {
   1844      emsg(_(e_argreq));
   1845      goto theend;
   1846    }
   1847    other = false;
   1848  } else {
   1849    fname = ffname;
   1850    free_fname = fix_fname(ffname);
   1851    // When out-of-memory, keep unexpanded file name, because we MUST be
   1852    // able to write the file in this situation.
   1853    if (free_fname != NULL) {
   1854      ffname = free_fname;
   1855    }
   1856    other = otherfile(ffname);
   1857  }
   1858 
   1859  // If we have a new file, put its name in the list of alternate file names.
   1860  if (other) {
   1861    if (vim_strchr(p_cpo, CPO_ALTWRITE) != NULL
   1862        || eap->cmdidx == CMD_saveas) {
   1863      alt_buf = setaltfname(ffname, fname, 1);
   1864    } else {
   1865      alt_buf = buflist_findname(ffname);
   1866    }
   1867    if (alt_buf != NULL && alt_buf->b_ml.ml_mfp != NULL) {
   1868      // Overwriting a file that is loaded in another buffer is not a
   1869      // good idea.
   1870      emsg(_(e_bufloaded));
   1871      goto theend;
   1872    }
   1873  }
   1874 
   1875  // Writing to the current file is not allowed in readonly mode
   1876  // and a file name is required.
   1877  // "nofile" and "nowrite" buffers cannot be written implicitly either.
   1878  if (!other && (bt_dontwrite_msg(curbuf)
   1879                 || check_fname() == FAIL
   1880 #ifdef UNIX
   1881                 || check_writable(curbuf->b_ffname) == FAIL
   1882 #endif
   1883                 || check_readonly(&eap->forceit, curbuf))) {
   1884    goto theend;
   1885  }
   1886 
   1887  if (!other) {
   1888    ffname = curbuf->b_ffname;
   1889    fname = curbuf->b_fname;
   1890    // Not writing the whole file is only allowed with '!'.
   1891    if ((eap->line1 != 1
   1892         || eap->line2 != curbuf->b_ml.ml_line_count)
   1893        && !eap->forceit
   1894        && !eap->append
   1895        && !p_wa) {
   1896      if (p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) {
   1897        if (vim_dialog_yesno(VIM_QUESTION, NULL,
   1898                             _("Write partial file?"), 2) != VIM_YES) {
   1899          goto theend;
   1900        }
   1901        eap->forceit = true;
   1902      } else {
   1903        emsg(_("E140: Use ! to write partial buffer"));
   1904        goto theend;
   1905      }
   1906    }
   1907  }
   1908 
   1909  if (check_overwrite(eap, curbuf, fname, ffname, other) == OK) {
   1910    if (eap->cmdidx == CMD_saveas && alt_buf != NULL) {
   1911      buf_T *was_curbuf = curbuf;
   1912 
   1913      apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, false, curbuf);
   1914      apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, false, alt_buf);
   1915      if (curbuf != was_curbuf || aborting()) {
   1916        // buffer changed, don't change name now
   1917        retval = FAIL;
   1918        goto theend;
   1919      }
   1920      // Exchange the file names for the current and the alternate
   1921      // buffer.  This makes it look like we are now editing the buffer
   1922      // under the new name.  Must be done before buf_write(), because
   1923      // if there is no file name and 'cpo' contains 'F', it will set
   1924      // the file name.
   1925      fname = alt_buf->b_fname;
   1926      alt_buf->b_fname = curbuf->b_fname;
   1927      curbuf->b_fname = fname;
   1928      fname = alt_buf->b_ffname;
   1929      alt_buf->b_ffname = curbuf->b_ffname;
   1930      curbuf->b_ffname = fname;
   1931      fname = alt_buf->b_sfname;
   1932      alt_buf->b_sfname = curbuf->b_sfname;
   1933      curbuf->b_sfname = fname;
   1934      buf_name_changed(curbuf);
   1935      apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, curbuf);
   1936      apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, alt_buf);
   1937      if (!alt_buf->b_p_bl) {
   1938        alt_buf->b_p_bl = true;
   1939        apply_autocmds(EVENT_BUFADD, NULL, NULL, false, alt_buf);
   1940      }
   1941      if (curbuf != was_curbuf || aborting()) {
   1942        // buffer changed, don't write the file
   1943        retval = FAIL;
   1944        goto theend;
   1945      }
   1946 
   1947      // If 'filetype' was empty try detecting it now.
   1948      if (*curbuf->b_p_ft == NUL) {
   1949        if (augroup_exists("filetypedetect")) {
   1950          do_doautocmd("filetypedetect BufRead", true, NULL);
   1951        }
   1952        do_modelines(0);
   1953      }
   1954 
   1955      // Autocommands may have changed buffer names, esp. when
   1956      // 'autochdir' is set.
   1957      fname = curbuf->b_sfname;
   1958    }
   1959 
   1960    if (handle_mkdir_p_arg(eap, fname) == FAIL) {
   1961      retval = FAIL;
   1962      goto theend;
   1963    }
   1964 
   1965    int name_was_missing = curbuf->b_ffname == NULL;
   1966    retval = buf_write(curbuf, ffname, fname, eap->line1, eap->line2,
   1967                       eap, eap->append, eap->forceit, true, false);
   1968 
   1969    // After ":saveas fname" reset 'readonly'.
   1970    if (eap->cmdidx == CMD_saveas) {
   1971      if (retval == OK) {
   1972        curbuf->b_p_ro = false;
   1973        redraw_tabline = true;
   1974      }
   1975    }
   1976 
   1977    // Change directories when the 'acd' option is set and the file name
   1978    // got changed or set.
   1979    if (eap->cmdidx == CMD_saveas || name_was_missing) {
   1980      do_autochdir();
   1981    }
   1982  }
   1983 
   1984 theend:
   1985  xfree(free_fname);
   1986  return retval;
   1987 }
   1988 
   1989 /// Check if it is allowed to overwrite a file.  If b_flags has BF_NOTEDITED,
   1990 /// BF_NEW or BF_READERR, check for overwriting current file.
   1991 /// May set eap->forceit if a dialog says it's OK to overwrite.
   1992 ///
   1993 /// @param fname   file name to be used (can differ from buf->ffname)
   1994 /// @param ffname  full path version of fname
   1995 /// @param other   writing under other name
   1996 ///
   1997 /// @return  OK if it's OK, FAIL if it is not.
   1998 int check_overwrite(exarg_T *eap, buf_T *buf, char *fname, char *ffname, bool other)
   1999 {
   2000  // Write to another file or b_flags set or not writing the whole file:
   2001  // overwriting only allowed with '!'
   2002  // If "other" is false and bt_nofilename(buf) is true, this must be
   2003  // writing an "acwrite" buffer to the same file as its b_ffname, and
   2004  // buf_write() will only allow writing with BufWriteCmd autocommands,
   2005  // so there is no need for an overwrite check.
   2006  if ((other
   2007       || (!bt_nofilename(buf)
   2008           && ((buf->b_flags & BF_NOTEDITED)
   2009               || ((buf->b_flags & BF_NEW)
   2010                   && vim_strchr(p_cpo, CPO_OVERNEW) == NULL)
   2011               || (buf->b_flags & BF_READERR))))
   2012      && !p_wa
   2013      && os_path_exists(ffname)) {
   2014    if (!eap->forceit && !eap->append) {
   2015 #ifdef UNIX
   2016      // It is possible to open a directory on Unix.
   2017      if (os_isdir(ffname)) {
   2018        semsg(_(e_isadir2), ffname);
   2019        return FAIL;
   2020      }
   2021 #endif
   2022      if (p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) {
   2023        char buff[DIALOG_MSG_SIZE];
   2024 
   2025        dialog_msg(buff, _("Overwrite existing file \"%s\"?"), fname);
   2026        if (vim_dialog_yesno(VIM_QUESTION, NULL, buff, 2) != VIM_YES) {
   2027          return FAIL;
   2028        }
   2029        eap->forceit = true;
   2030      } else {
   2031        emsg(_(e_exists));
   2032        return FAIL;
   2033      }
   2034    }
   2035 
   2036    // For ":w! filename" check that no swap file exists for "filename".
   2037    if (other && !emsg_silent) {
   2038      char *dir;
   2039 
   2040      // We only try the first entry in 'directory', without checking if
   2041      // it's writable.  If the "." directory is not writable the write
   2042      // will probably fail anyway.
   2043      // Use 'shortname' of the current buffer, since there is no buffer
   2044      // for the written file.
   2045      if (*p_dir == NUL) {
   2046        dir = xmalloc(5);
   2047        STRCPY(dir, ".");
   2048      } else {
   2049        dir = xmalloc(MAXPATHL);
   2050        char *p = p_dir;
   2051        copy_option_part(&p, dir, MAXPATHL, ",");
   2052      }
   2053      char *swapname = makeswapname(fname, ffname, curbuf, dir);
   2054      xfree(dir);
   2055      if (os_path_exists(swapname)) {
   2056        if (p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) {
   2057          char buff[DIALOG_MSG_SIZE];
   2058 
   2059          dialog_msg(buff,
   2060                     _("Swap file \"%s\" exists, overwrite anyway?"),
   2061                     swapname);
   2062          if (vim_dialog_yesno(VIM_QUESTION, NULL, buff, 2)
   2063              != VIM_YES) {
   2064            xfree(swapname);
   2065            return FAIL;
   2066          }
   2067          eap->forceit = true;
   2068        } else {
   2069          semsg(_("E768: Swap file exists: %s (:silent! overrides)"),
   2070                swapname);
   2071          xfree(swapname);
   2072          return FAIL;
   2073        }
   2074      }
   2075      xfree(swapname);
   2076    }
   2077  }
   2078  return OK;
   2079 }
   2080 
   2081 /// Handle ":wnext", ":wNext" and ":wprevious" commands.
   2082 void ex_wnext(exarg_T *eap)
   2083 {
   2084  int i;
   2085 
   2086  if (eap->cmd[1] == 'n') {
   2087    i = curwin->w_arg_idx + (int)eap->line2;
   2088  } else {
   2089    i = curwin->w_arg_idx - (int)eap->line2;
   2090  }
   2091  eap->line1 = 1;
   2092  eap->line2 = curbuf->b_ml.ml_line_count;
   2093  if (do_write(eap) != FAIL) {
   2094    do_argfile(eap, i);
   2095  }
   2096 }
   2097 
   2098 /// ":wall", ":wqall" and ":xall": Write all changed files (and exit).
   2099 void do_wqall(exarg_T *eap)
   2100 {
   2101  int error = 0;
   2102  int save_forceit = eap->forceit;
   2103  bool save_exiting = exiting;
   2104 
   2105  if (eap->cmdidx == CMD_xall || eap->cmdidx == CMD_wqall) {
   2106    if (before_quit_all(eap) == FAIL) {
   2107      return;
   2108    }
   2109    exiting = true;
   2110  }
   2111 
   2112  FOR_ALL_BUFFERS(buf) {
   2113    if (exiting && !eap->forceit
   2114        && buf->terminal
   2115        // TODO(zeertzjq): this always returns false for nvim_open_term() terminals.
   2116        // Use terminal_running() instead?
   2117        && channel_job_running((uint64_t)buf->b_p_channel)) {
   2118      no_write_message_buf(buf);
   2119      error++;
   2120    } else if (!bufIsChanged(buf) || bt_dontwrite(buf)) {
   2121      continue;
   2122    }
   2123    // Check if there is a reason the buffer cannot be written:
   2124    // 1. if the 'write' option is set
   2125    // 2. if there is no file name (even after browsing)
   2126    // 3. if the 'readonly' is set (even after a dialog)
   2127    // 4. if overwriting is allowed (even after a dialog)
   2128    if (not_writing()) {
   2129      error++;
   2130      break;
   2131    }
   2132    if (buf->b_ffname == NULL) {
   2133      semsg(_("E141: No file name for buffer %" PRId64), (int64_t)buf->b_fnum);
   2134      error++;
   2135    } else if (check_readonly(&eap->forceit, buf)
   2136               || check_overwrite(eap, buf, buf->b_fname, buf->b_ffname, false) == FAIL) {
   2137      error++;
   2138    } else {
   2139      bufref_T bufref;
   2140      set_bufref(&bufref, buf);
   2141      if (handle_mkdir_p_arg(eap, buf->b_fname) == FAIL
   2142          || buf_write_all(buf, eap->forceit) == FAIL) {
   2143        error++;
   2144      }
   2145      // An autocommand may have deleted the buffer.
   2146      if (!bufref_valid(&bufref)) {
   2147        buf = firstbuf;
   2148      }
   2149    }
   2150    eap->forceit = save_forceit;          // check_overwrite() may set it
   2151  }
   2152  if (exiting) {
   2153    if (!error) {
   2154      getout(0);                // exit Vim
   2155    }
   2156    not_exiting(save_exiting);
   2157  }
   2158 }
   2159 
   2160 /// Check the 'write' option.
   2161 ///
   2162 /// @return  true and give a message when it's not st.
   2163 static bool not_writing(void)
   2164 {
   2165  if (p_write) {
   2166    return false;
   2167  }
   2168  emsg(_("E142: File not written: Writing is disabled by 'write' option"));
   2169  return true;
   2170 }
   2171 
   2172 /// Check if a buffer is read-only (either 'readonly' option is set or file is
   2173 /// read-only). Ask for overruling in a dialog. Return true and give an error
   2174 /// message when the buffer is readonly.
   2175 static int check_readonly(int *forceit, buf_T *buf)
   2176 {
   2177  // Handle a file being readonly when the 'readonly' option is set or when
   2178  // the file exists and permissions are read-only.
   2179  if (!*forceit && (buf->b_p_ro
   2180                    || (os_path_exists(buf->b_ffname)
   2181                        && !os_file_is_writable(buf->b_ffname)))) {
   2182    if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && buf->b_fname != NULL) {
   2183      char buff[DIALOG_MSG_SIZE];
   2184 
   2185      if (buf->b_p_ro) {
   2186        dialog_msg(buff,
   2187                   _("'readonly' option is set for \"%s\".\nDo you wish to write anyway?"),
   2188                   buf->b_fname);
   2189      } else {
   2190        dialog_msg(buff,
   2191                   _("File permissions of \"%s\" are read-only.\nIt may still be possible to "
   2192                     "write it.\nDo you wish to try?"),
   2193                   buf->b_fname);
   2194      }
   2195 
   2196      if (vim_dialog_yesno(VIM_QUESTION, NULL, buff, 2) == VIM_YES) {
   2197        // Set forceit, to force the writing of a readonly file
   2198        *forceit = true;
   2199        return false;
   2200      }
   2201      return true;
   2202    } else if (buf->b_p_ro) {
   2203      emsg(_(e_readonly));
   2204    } else {
   2205      semsg(_("E505: \"%s\" is read-only (add ! to override)"),
   2206            buf->b_fname);
   2207    }
   2208    return true;
   2209  }
   2210 
   2211  return false;
   2212 }
   2213 
   2214 /// Try to abandon the current file and edit a new or existing file.
   2215 ///
   2216 /// @param fnum  the number of the file, if zero use "ffname_arg"/"sfname_arg".
   2217 /// @param lnum  the line number for the cursor in the new file (if non-zero).
   2218 ///
   2219 /// @return:
   2220 ///           GETFILE_ERROR for "normal" error,
   2221 ///           GETFILE_NOT_WRITTEN for "not written" error,
   2222 ///           GETFILE_SAME_FILE for success
   2223 ///           GETFILE_OPEN_OTHER for successfully opening another file.
   2224 int getfile(int fnum, char *ffname_arg, char *sfname_arg, bool setpm, linenr_T lnum, bool forceit)
   2225 {
   2226  if (!check_can_set_curbuf_forceit(forceit)) {
   2227    return GETFILE_ERROR;
   2228  }
   2229 
   2230  char *ffname = ffname_arg;
   2231  char *sfname = sfname_arg;
   2232  bool other;
   2233  int retval;
   2234  char *free_me = NULL;
   2235 
   2236  if (text_locked()) {
   2237    return GETFILE_ERROR;
   2238  }
   2239  if (curbuf_locked()) {
   2240    return GETFILE_ERROR;
   2241  }
   2242 
   2243  if (fnum == 0) {
   2244    // make ffname full path, set sfname
   2245    fname_expand(curbuf, &ffname, &sfname);
   2246    other = otherfile(ffname);
   2247    free_me = ffname;                   // has been allocated, free() later
   2248  } else {
   2249    other = (fnum != curbuf->b_fnum);
   2250  }
   2251 
   2252  if (other) {
   2253    no_wait_return++;               // don't wait for autowrite message
   2254  }
   2255  if (other && !forceit && curbuf->b_nwindows == 1 && !buf_hide(curbuf)
   2256      && curbufIsChanged() && autowrite(curbuf, forceit) == FAIL) {
   2257    if (p_confirm && p_write) {
   2258      dialog_changed(curbuf, false);
   2259    }
   2260    if (curbufIsChanged()) {
   2261      no_wait_return--;
   2262      no_write_message();
   2263      retval = GETFILE_NOT_WRITTEN;     // File has been changed.
   2264      goto theend;
   2265    }
   2266  }
   2267  if (other) {
   2268    no_wait_return--;
   2269  }
   2270  if (setpm) {
   2271    setpcmark();
   2272  }
   2273  if (!other) {
   2274    if (lnum != 0) {
   2275      curwin->w_cursor.lnum = lnum;
   2276    }
   2277    check_cursor_lnum(curwin);
   2278    beginline(BL_SOL | BL_FIX);
   2279    retval = GETFILE_SAME_FILE;     // it's in the same file
   2280  } else if (do_ecmd(fnum, ffname, sfname, NULL, lnum,
   2281                     (buf_hide(curbuf) ? ECMD_HIDE : 0)
   2282                     + (forceit ? ECMD_FORCEIT : 0), curwin) == OK) {
   2283    retval = GETFILE_OPEN_OTHER;    // opened another file
   2284  } else {
   2285    retval = GETFILE_ERROR;         // error encountered
   2286  }
   2287 
   2288 theend:
   2289  xfree(free_me);
   2290  return retval;
   2291 }
   2292 
   2293 /// set v:swapcommand for the SwapExists autocommands.
   2294 ///
   2295 /// @param command  [+cmd] to be executed (e.g. +10).
   2296 /// @param newlnum  if > 0: put cursor on this line number (if possible)
   2297 //
   2298 /// @return 1 if swapcommand was actually set, 0 otherwise
   2299 bool set_swapcommand(char *command, linenr_T newlnum)
   2300 {
   2301  if ((command == NULL && newlnum <= 0) || *get_vim_var_str(VV_SWAPCOMMAND) != NUL) {
   2302    return false;
   2303  }
   2304  const size_t len = (command != NULL) ? strlen(command) + 3 : 30;
   2305  char *const p = xmalloc(len);
   2306  if (command != NULL) {
   2307    vim_snprintf(p, len, ":%s\r", command);
   2308  } else {
   2309    vim_snprintf(p, len, "%" PRId64 "G", (int64_t)newlnum);
   2310  }
   2311  set_vim_var_string(VV_SWAPCOMMAND, p, -1);
   2312  xfree(p);
   2313  return true;
   2314 }
   2315 
   2316 /// start editing a new file
   2317 ///
   2318 /// @param fnum     file number; if zero use ffname/sfname
   2319 /// @param ffname   the file name
   2320 ///                 - full path if sfname used,
   2321 ///                 - any file name if sfname is NULL
   2322 ///                 - empty string to re-edit with the same file name (but may
   2323 ///                   be in a different directory)
   2324 ///                 - NULL to start an empty buffer
   2325 /// @param sfname   the short file name (or NULL)
   2326 /// @param eap      contains the command to be executed after loading the file
   2327 ///                 and forced 'ff' and 'fenc'. Can be NULL!
   2328 /// @param newlnum  if > 0: put cursor on this line number (if possible)
   2329 ///                 ECMD_LASTL: use last position in loaded file
   2330 ///                 ECMD_LAST: use last position in all files
   2331 ///                 ECMD_ONE: use first line
   2332 /// @param flags    ECMD_HIDE: if true don't free the current buffer
   2333 ///                 ECMD_SET_HELP: set b_help flag of (new) buffer before
   2334 ///                 opening file
   2335 ///                 ECMD_OLDBUF: use existing buffer if it exists
   2336 ///                 ECMD_FORCEIT: ! used for Ex command
   2337 ///                 ECMD_ADDBUF: don't edit, just add to buffer list
   2338 ///                 ECMD_ALTBUF: like ECMD_ADDBUF and also set the alternate
   2339 ///                 file
   2340 ///                 ECMD_NOWINENTER: Do not trigger BufWinEnter
   2341 /// @param oldwin   Should be "curwin" when editing a new buffer in the current
   2342 ///                 window, NULL when splitting the window first.  When not NULL
   2343 ///                 info of the previous buffer for "oldwin" is stored.
   2344 ///
   2345 /// @return FAIL for failure, OK otherwise
   2346 int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum, int flags,
   2347            win_T *oldwin)
   2348 {
   2349  bool other_file;                      // true if editing another file
   2350  int oldbuf;                           // true if using existing buffer
   2351  bool auto_buf = false;                // true if autocommands brought us
   2352                                        // into the buffer unexpectedly
   2353  char *new_name = NULL;
   2354  bool did_set_swapcommand = false;
   2355  buf_T *buf;
   2356  bufref_T bufref;
   2357  bufref_T old_curbuf;
   2358  char *free_fname = NULL;
   2359  int retval = FAIL;
   2360  linenr_T topline = 0;
   2361  int newcol = -1;
   2362  int solcol = -1;
   2363  char *command = NULL;
   2364  bool did_get_winopts = false;
   2365  int readfile_flags = 0;
   2366  bool did_inc_redrawing_disabled = false;
   2367  OptInt *so_ptr = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so;
   2368 
   2369  if (eap != NULL) {
   2370    command = eap->do_ecmd_cmd;
   2371  }
   2372 
   2373  set_bufref(&old_curbuf, curbuf);
   2374 
   2375  if (fnum != 0) {
   2376    if (fnum == curbuf->b_fnum) {       // file is already being edited
   2377      return OK;                        // nothing to do
   2378    }
   2379    other_file = true;
   2380  } else {
   2381    // if no short name given, use ffname for short name
   2382    if (sfname == NULL) {
   2383      sfname = ffname;
   2384    }
   2385 #ifdef CASE_INSENSITIVE_FILENAME
   2386    if (sfname != NULL) {
   2387      path_fix_case(sfname);             // set correct case for sfname
   2388    }
   2389 #endif
   2390 
   2391    if ((flags & (ECMD_ADDBUF | ECMD_ALTBUF))
   2392        && (ffname == NULL || *ffname == NUL)) {
   2393      goto theend;
   2394    }
   2395 
   2396    if (ffname == NULL) {
   2397      other_file = true;
   2398    } else if (*ffname == NUL && curbuf->b_ffname == NULL) {  // there is no file name
   2399      other_file = false;
   2400    } else {
   2401      if (*ffname == NUL) {                 // re-edit with same file name
   2402        ffname = curbuf->b_ffname;
   2403        sfname = curbuf->b_fname;
   2404      }
   2405      free_fname = fix_fname(ffname);       // may expand to full path name
   2406      if (free_fname != NULL) {
   2407        ffname = free_fname;
   2408      }
   2409      other_file = otherfile(ffname);
   2410    }
   2411  }
   2412 
   2413  // Re-editing a terminal buffer: skip most buffer re-initialization.
   2414  if (!other_file && curbuf->terminal) {
   2415    check_arg_idx(curwin);  // Needed when called from do_argfile().
   2416    maketitle();            // Title may show the arg index, e.g. "(2 of 5)".
   2417    retval = OK;
   2418    goto theend;
   2419  }
   2420 
   2421  // If the file was changed we may not be allowed to abandon it:
   2422  // - if we are going to re-edit the same file
   2423  // - or if we are the only window on this file and if ECMD_HIDE is false
   2424  if (((!other_file && !(flags & ECMD_OLDBUF))
   2425       || (curbuf->b_nwindows == 1
   2426           && !(flags & (ECMD_HIDE | ECMD_ADDBUF | ECMD_ALTBUF))))
   2427      && check_changed(curbuf, (p_awa ? CCGD_AW : 0)
   2428                       | (other_file ? 0 : CCGD_MULTWIN)
   2429                       | ((flags & ECMD_FORCEIT) ? CCGD_FORCEIT : 0)
   2430                       | (eap == NULL ? 0 : CCGD_EXCMD))) {
   2431    if (fnum == 0 && other_file && ffname != NULL) {
   2432      setaltfname(ffname, sfname, newlnum < 0 ? 0 : newlnum);
   2433    }
   2434    goto theend;
   2435  }
   2436 
   2437  // End Visual mode before switching to another buffer, so the text can be
   2438  // copied into the GUI selection buffer.
   2439  // Careful: may trigger ModeChanged() autocommand
   2440 
   2441  // Should we block autocommands here?
   2442  reset_VIsual();
   2443 
   2444  // autocommands freed window :(
   2445  if (oldwin != NULL && !win_valid(oldwin)) {
   2446    oldwin = NULL;
   2447  }
   2448 
   2449  did_set_swapcommand = set_swapcommand(command, newlnum);
   2450 
   2451  // If we are starting to edit another file, open a (new) buffer.
   2452  // Otherwise we re-use the current buffer.
   2453  if (other_file) {
   2454    const int prev_alt_fnum = curwin->w_alt_fnum;
   2455 
   2456    if (!(flags & (ECMD_ADDBUF | ECMD_ALTBUF))) {
   2457      if ((cmdmod.cmod_flags & CMOD_KEEPALT) == 0) {
   2458        curwin->w_alt_fnum = curbuf->b_fnum;
   2459      }
   2460      if (oldwin != NULL) {
   2461        buflist_altfpos(oldwin);
   2462      }
   2463    }
   2464 
   2465    if (fnum) {
   2466      buf = buflist_findnr(fnum);
   2467    } else {
   2468      if (flags & (ECMD_ADDBUF | ECMD_ALTBUF)) {
   2469        // Default the line number to zero to avoid that a wininfo item
   2470        // is added for the current window.
   2471        linenr_T tlnum = 0;
   2472 
   2473        if (command != NULL) {
   2474          tlnum = (linenr_T)atol(command);
   2475          if (tlnum <= 0) {
   2476            tlnum = 1;
   2477          }
   2478        }
   2479        // Add BLN_NOCURWIN to avoid a new wininfo items are associated
   2480        // with the current window.
   2481        const buf_T *const newbuf
   2482          = buflist_new(ffname, sfname, tlnum, BLN_LISTED | BLN_NOCURWIN);
   2483        if (newbuf != NULL && (flags & ECMD_ALTBUF)) {
   2484          curwin->w_alt_fnum = newbuf->b_fnum;
   2485        }
   2486        goto theend;
   2487      }
   2488      buf = buflist_new(ffname, sfname, 0,
   2489                        BLN_CURBUF | (flags & ECMD_SET_HELP ? 0 : BLN_LISTED));
   2490      // Autocmds may change curwin and curbuf.
   2491      if (oldwin != NULL) {
   2492        oldwin = curwin;
   2493      }
   2494      set_bufref(&old_curbuf, curbuf);
   2495    }
   2496    if (buf == NULL) {
   2497      goto theend;
   2498    }
   2499    // autocommands try to edit a closing buffer, which like splitting, can
   2500    // result in more windows displaying it; abort
   2501    if (buf->b_locked_split) {
   2502      // window was split, but not editing the new buffer, reset b_nwindows again
   2503      if (oldwin == NULL
   2504          && curwin->w_buffer != NULL
   2505          && curwin->w_buffer->b_nwindows > 1) {
   2506        curwin->w_buffer->b_nwindows--;
   2507      }
   2508      emsg(_(e_cannot_switch_to_a_closing_buffer));
   2509      goto theend;
   2510    }
   2511    if (curwin->w_alt_fnum == buf->b_fnum && prev_alt_fnum != 0) {
   2512      // reusing the buffer, keep the old alternate file
   2513      curwin->w_alt_fnum = prev_alt_fnum;
   2514    }
   2515    if (buf->b_ml.ml_mfp == NULL) {
   2516      // No memfile yet.
   2517      oldbuf = false;
   2518    } else {
   2519      // Existing memfile.
   2520      oldbuf = true;
   2521      set_bufref(&bufref, buf);
   2522      buf_check_timestamp(buf);
   2523      // Check if autocommands made buffer invalid or changed the current
   2524      // buffer.
   2525      if (!bufref_valid(&bufref) || curbuf != old_curbuf.br_buf) {
   2526        goto theend;
   2527      }
   2528      if (aborting()) {
   2529        // Autocmds may abort script processing.
   2530        goto theend;
   2531      }
   2532    }
   2533 
   2534    // May jump to last used line number for a loaded buffer or when asked
   2535    // for explicitly
   2536    if ((oldbuf && newlnum == ECMD_LASTL) || newlnum == ECMD_LAST) {
   2537      pos_T *pos = &buflist_findfmark(buf)->mark;
   2538      newlnum = pos->lnum;
   2539      solcol = pos->col;
   2540    }
   2541 
   2542    // Make the (new) buffer the one used by the current window.
   2543    // If the old buffer becomes unused, free it if ECMD_HIDE is false.
   2544    // If the current buffer was empty and has no file name, curbuf
   2545    // is returned by buflist_new(), nothing to do here.
   2546    if (buf != curbuf) {
   2547      // Should only be possible to get here if the cmdwin is closed, or
   2548      // if it's opening and its buffer hasn't been set yet (the new
   2549      // buffer is for it).
   2550      assert(cmdwin_buf == NULL);
   2551 
   2552      const int save_cmdwin_type = cmdwin_type;
   2553      win_T *const save_cmdwin_win = cmdwin_win;
   2554      win_T *const save_cmdwin_old_curwin = cmdwin_old_curwin;
   2555 
   2556      // BufLeave applies to the old buffer.
   2557      cmdwin_type = 0;
   2558      cmdwin_win = NULL;
   2559      cmdwin_old_curwin = NULL;
   2560 
   2561      // Be careful: The autocommands may delete any buffer and change
   2562      // the current buffer.
   2563      // - If the buffer we are going to edit is deleted, give up.
   2564      // - If the current buffer is deleted, prefer to load the new
   2565      //   buffer when loading a buffer is required.  This avoids
   2566      //   loading another buffer which then must be closed again.
   2567      // - If we ended up in the new buffer already, need to skip a few
   2568      //         things, set auto_buf.
   2569      if (buf->b_fname != NULL) {
   2570        new_name = xstrdup(buf->b_fname);
   2571      }
   2572      const bufref_T save_au_new_curbuf = au_new_curbuf;
   2573      set_bufref(&au_new_curbuf, buf);
   2574      apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, false, curbuf);
   2575 
   2576      cmdwin_type = save_cmdwin_type;
   2577      cmdwin_win = save_cmdwin_win;
   2578      cmdwin_old_curwin = save_cmdwin_old_curwin;
   2579 
   2580      if (!bufref_valid(&au_new_curbuf)) {
   2581        // New buffer has been deleted.
   2582        delbuf_msg(new_name);  // Frees new_name.
   2583        au_new_curbuf = save_au_new_curbuf;
   2584        goto theend;
   2585      }
   2586      if (aborting()) {             // autocmds may abort script processing
   2587        xfree(new_name);
   2588        au_new_curbuf = save_au_new_curbuf;
   2589        goto theend;
   2590      }
   2591      if (buf == curbuf) {  // already in new buffer
   2592        auto_buf = true;
   2593      } else {
   2594        win_T *the_curwin = curwin;
   2595        buf_T *was_curbuf = curbuf;
   2596 
   2597        // Set w_locked to avoid that autocommands close the window.
   2598        // Set b_locked for the same reason.
   2599        the_curwin->w_locked = true;
   2600        buf->b_locked++;
   2601 
   2602        if (curbuf == old_curbuf.br_buf) {
   2603          buf_copy_options(buf, BCO_ENTER);
   2604        }
   2605 
   2606        // Close the link to the current buffer. This will set
   2607        // oldwin->w_buffer to NULL.
   2608        u_sync(false);
   2609        const bool did_decrement
   2610          = close_buffer(oldwin, curbuf,
   2611                         (flags & ECMD_HIDE)
   2612                         || (curbuf->terminal && terminal_running(curbuf->terminal))
   2613                         ? 0 : DOBUF_UNLOAD,
   2614                         false, false);
   2615 
   2616        // Autocommands may have closed the window.
   2617        if (win_valid(the_curwin)) {
   2618          the_curwin->w_locked = false;
   2619        }
   2620        buf->b_locked--;
   2621 
   2622        // autocmds may abort script processing
   2623        if (aborting() && curwin->w_buffer != NULL) {
   2624          xfree(new_name);
   2625          au_new_curbuf = save_au_new_curbuf;
   2626          goto theend;
   2627        }
   2628        // Be careful again, like above.
   2629        if (!bufref_valid(&au_new_curbuf)) {
   2630          // New buffer has been deleted.
   2631          delbuf_msg(new_name);  // Frees new_name.
   2632          au_new_curbuf = save_au_new_curbuf;
   2633          goto theend;
   2634        }
   2635        if (buf == curbuf) {  // already in new buffer
   2636          // close_buffer() has decremented the window count,
   2637          // increment it again here and restore w_buffer.
   2638          if (did_decrement && buf_valid(was_curbuf)) {
   2639            was_curbuf->b_nwindows++;
   2640          }
   2641          if (win_valid_any_tab(oldwin) && oldwin->w_buffer == NULL) {
   2642            oldwin->w_buffer = was_curbuf;
   2643          }
   2644          auto_buf = true;
   2645        } else {
   2646          // <VN> We could instead free the synblock
   2647          // and re-attach to buffer, perhaps.
   2648          if (curwin->w_buffer == NULL
   2649              || curwin->w_s == &(curwin->w_buffer->b_s)) {
   2650            curwin->w_s = &(buf->b_s);
   2651          }
   2652 
   2653          curwin->w_buffer = buf;
   2654          curbuf = buf;
   2655          curbuf->b_nwindows++;
   2656 
   2657          // Set 'fileformat', 'binary' and 'fenc' when forced.
   2658          if (!oldbuf && eap != NULL) {
   2659            set_file_options(true, eap);
   2660            set_forced_fenc(eap);
   2661          }
   2662        }
   2663 
   2664        // May get the window options from the last time this buffer
   2665        // was in this window (or another window).  If not used
   2666        // before, reset the local window options to the global
   2667        // values.  Also restores old folding stuff.
   2668        get_winopts(curbuf);
   2669        did_get_winopts = true;
   2670      }
   2671      xfree(new_name);
   2672      au_new_curbuf = save_au_new_curbuf;
   2673    }
   2674 
   2675    curwin->w_pcmark.lnum = 1;
   2676    curwin->w_pcmark.col = 0;
   2677  } else {  // !other_file
   2678    if ((flags & (ECMD_ADDBUF | ECMD_ALTBUF)) || check_fname() == FAIL) {
   2679      goto theend;
   2680    }
   2681    oldbuf = (flags & ECMD_OLDBUF);
   2682  }
   2683 
   2684  // Don't redraw until the cursor is in the right line, otherwise
   2685  // autocommands may cause ml_get errors.
   2686  RedrawingDisabled++;
   2687  did_inc_redrawing_disabled = true;
   2688 
   2689  buf = curbuf;
   2690  if ((flags & ECMD_SET_HELP) || keep_help_flag) {
   2691    prepare_help_buffer();
   2692  } else if (!curbuf->b_help) {
   2693    // Don't make a buffer listed if it's a help buffer.  Useful when using
   2694    // CTRL-O to go back to a help file.
   2695    set_buflisted(true);
   2696  }
   2697 
   2698  // If autocommands change buffers under our fingers, forget about
   2699  // editing the file.
   2700  if (buf != curbuf) {
   2701    goto theend;
   2702  }
   2703  if (aborting()) {         // autocmds may abort script processing
   2704    goto theend;
   2705  }
   2706 
   2707  // Since we are starting to edit a file, consider the filetype to be
   2708  // unset.  Helps for when an autocommand changes files and expects syntax
   2709  // highlighting to work in the other file.
   2710  curbuf->b_did_filetype = false;
   2711 
   2712  // other_file oldbuf
   2713  //  false     false       re-edit same file, buffer is re-used
   2714  //  false     true        re-edit same file, nothing changes
   2715  //  true      false       start editing new file, new buffer
   2716  //  true      true        start editing in existing buffer (nothing to do)
   2717  if (!other_file && !oldbuf) {         // re-use the buffer
   2718    set_last_cursor(curwin);            // may set b_last_cursor
   2719    if (newlnum == ECMD_LAST || newlnum == ECMD_LASTL) {
   2720      newlnum = curwin->w_cursor.lnum;
   2721      solcol = curwin->w_cursor.col;
   2722    }
   2723    buf = curbuf;
   2724    if (buf->b_fname != NULL) {
   2725      new_name = xstrdup(buf->b_fname);
   2726    } else {
   2727      new_name = NULL;
   2728    }
   2729    set_bufref(&bufref, buf);
   2730 
   2731    // If the buffer was used before, store the current contents so that
   2732    // the reload can be undone.  Do not do this if the (empty) buffer is
   2733    // being re-used for another file.
   2734    if (!(curbuf->b_flags & BF_NEVERLOADED)
   2735        && (p_ur < 0 || curbuf->b_ml.ml_line_count <= p_ur)) {
   2736      // Sync first so that this is a separate undo-able action.
   2737      u_sync(false);
   2738      if (u_savecommon(curbuf, 0, curbuf->b_ml.ml_line_count + 1, 0, true)
   2739          == FAIL) {
   2740        xfree(new_name);
   2741        goto theend;
   2742      }
   2743      u_unchanged(curbuf);
   2744      buf_freeall(curbuf, BFA_KEEP_UNDO);
   2745 
   2746      // Tell readfile() not to clear or reload undo info.
   2747      readfile_flags = READ_KEEP_UNDO;
   2748    } else {
   2749      buf_freeall(curbuf, 0);  // Free all things for buffer.
   2750    }
   2751    // If autocommands deleted the buffer we were going to re-edit, give
   2752    // up and jump to the end.
   2753    if (!bufref_valid(&bufref)) {
   2754      delbuf_msg(new_name);  // Frees new_name.
   2755      goto theend;
   2756    }
   2757    xfree(new_name);
   2758 
   2759    // If autocommands change buffers under our fingers, forget about
   2760    // re-editing the file.  Should do the buf_clear_file(), but perhaps
   2761    // the autocommands changed the buffer...
   2762    if (buf != curbuf) {
   2763      goto theend;
   2764    }
   2765    if (aborting()) {       // autocmds may abort script processing
   2766      goto theend;
   2767    }
   2768    buf_clear_file(curbuf);
   2769    curbuf->b_op_start.lnum = 0;        // clear '[ and '] marks
   2770    curbuf->b_op_end.lnum = 0;
   2771  }
   2772 
   2773  // If we get here we are sure to start editing
   2774 
   2775  // Assume success now
   2776  retval = OK;
   2777 
   2778  // If the file name was changed, reset the not-edit flag so that ":write"
   2779  // works.
   2780  if (!other_file) {
   2781    curbuf->b_flags &= ~BF_NOTEDITED;
   2782  }
   2783 
   2784  // Check if we are editing the w_arg_idx file in the argument list.
   2785  check_arg_idx(curwin);
   2786 
   2787  if (!auto_buf) {
   2788    // Set cursor and init window before reading the file and executing
   2789    // autocommands.  This allows for the autocommands to position the
   2790    // cursor.
   2791    curwin_init();
   2792 
   2793    // It's possible that all lines in the buffer changed.  Need to update
   2794    // automatic folding for all windows where it's used.
   2795    FOR_ALL_TAB_WINDOWS(tp, win) {
   2796      if (win->w_buffer == curbuf) {
   2797        foldUpdateAll(win);
   2798      }
   2799    }
   2800 
   2801    // Change directories when the 'acd' option is set.
   2802    do_autochdir();
   2803 
   2804    // Careful: open_buffer() and apply_autocmds() may change the current
   2805    // buffer and window.
   2806    pos_T orig_pos = curwin->w_cursor;
   2807    topline = curwin->w_topline;
   2808    if (!oldbuf) {                          // need to read the file
   2809      swap_exists_action = SEA_DIALOG;
   2810      curbuf->b_flags |= BF_CHECK_RO;       // set/reset 'ro' flag
   2811 
   2812      // Open the buffer and read the file.
   2813      if (flags & ECMD_NOWINENTER) {
   2814        readfile_flags |= READ_NOWINENTER;
   2815      }
   2816      if (should_abort(open_buffer(false, eap, readfile_flags))) {
   2817        retval = FAIL;
   2818      }
   2819 
   2820      if (swap_exists_action == SEA_QUIT) {
   2821        retval = FAIL;
   2822      }
   2823      handle_swap_exists(&old_curbuf);
   2824    } else {
   2825      // Read the modelines, but only to set window-local options.  Any
   2826      // buffer-local options have already been set and may have been
   2827      // changed by the user.
   2828      do_modelines(OPT_WINONLY);
   2829 
   2830      apply_autocmds_retval(EVENT_BUFENTER, NULL, NULL, false, curbuf,
   2831                            &retval);
   2832      if ((flags & ECMD_NOWINENTER) == 0) {
   2833        apply_autocmds_retval(EVENT_BUFWINENTER, NULL, NULL, false, curbuf,
   2834                              &retval);
   2835      }
   2836    }
   2837    check_arg_idx(curwin);
   2838 
   2839    // If autocommands change the cursor position or topline, we should
   2840    // keep it.  Also when it moves within a line. But not when it moves
   2841    // to the first non-blank.
   2842    if (!equalpos(curwin->w_cursor, orig_pos)) {
   2843      const char *text = get_cursor_line_ptr();
   2844 
   2845      if (curwin->w_cursor.lnum != orig_pos.lnum
   2846          || curwin->w_cursor.col != (int)(skipwhite(text) - text)) {
   2847        newlnum = curwin->w_cursor.lnum;
   2848        newcol = curwin->w_cursor.col;
   2849      }
   2850    }
   2851    if (curwin->w_topline == topline) {
   2852      topline = 0;
   2853    }
   2854 
   2855    // Even when cursor didn't move we need to recompute topline.
   2856    changed_line_abv_curs();
   2857 
   2858    maketitle();
   2859  }
   2860 
   2861  // Tell the diff stuff that this buffer is new and/or needs updating.
   2862  // Also needed when re-editing the same buffer, because unloading will
   2863  // have removed it as a diff buffer.
   2864  if (curwin->w_p_diff) {
   2865    diff_buf_add(curbuf);
   2866    diff_invalidate(curbuf);
   2867  }
   2868 
   2869  // If the window options were changed may need to set the spell language.
   2870  // Can only do this after the buffer has been properly setup.
   2871  if (did_get_winopts && curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) {
   2872    parse_spelllang(curwin);
   2873  }
   2874 
   2875  if (command == NULL) {
   2876    if (newcol >= 0) {          // position set by autocommands
   2877      curwin->w_cursor.lnum = newlnum;
   2878      curwin->w_cursor.col = newcol;
   2879      check_cursor(curwin);
   2880    } else if (newlnum > 0) {  // line number from caller or old position
   2881      curwin->w_cursor.lnum = newlnum;
   2882      check_cursor_lnum(curwin);
   2883      if (solcol >= 0 && !p_sol) {
   2884        // 'sol' is off: Use last known column.
   2885        curwin->w_cursor.col = solcol;
   2886        check_cursor_col(curwin);
   2887        curwin->w_cursor.coladd = 0;
   2888        curwin->w_set_curswant = true;
   2889      } else {
   2890        beginline(BL_SOL | BL_FIX);
   2891      }
   2892    } else {                  // no line number, go to last line in Ex mode
   2893      if (exmode_active) {
   2894        curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
   2895      }
   2896      beginline(BL_WHITE | BL_FIX);
   2897    }
   2898  }
   2899 
   2900  // Check if cursors in other windows on the same buffer are still valid
   2901  check_lnums(false);
   2902 
   2903  // Did not read the file, need to show some info about the file.
   2904  // Do this after setting the cursor.
   2905  if (oldbuf
   2906      && !auto_buf) {
   2907    int msg_scroll_save = msg_scroll;
   2908 
   2909    // Obey the 'O' flag in 'cpoptions': overwrite any previous file
   2910    // message.
   2911    if (shortmess(SHM_OVERALL) && !msg_listdo_overwrite && !exiting && p_verbose == 0) {
   2912      msg_scroll = false;
   2913    }
   2914    if (!msg_scroll) {          // wait a bit when overwriting an error msg
   2915      msg_check_for_delay(false);
   2916    }
   2917    msg_start();
   2918    msg_scroll = msg_scroll_save;
   2919    msg_scrolled_ign = true;
   2920 
   2921    if (!shortmess(SHM_FILEINFO)) {
   2922      fileinfo(false, true, false);
   2923    }
   2924 
   2925    msg_scrolled_ign = false;
   2926  }
   2927 
   2928  curbuf->b_last_used = time(NULL);
   2929 
   2930  if (command != NULL) {
   2931    do_cmdline(command, NULL, NULL, DOCMD_VERBOSE);
   2932  }
   2933 
   2934  if (curbuf->b_kmap_state & KEYMAP_INIT) {
   2935    keymap_init();
   2936  }
   2937 
   2938  RedrawingDisabled--;
   2939  did_inc_redrawing_disabled = false;
   2940  if (!skip_redraw) {
   2941    OptInt n = *so_ptr;
   2942    if (topline == 0 && command == NULL) {
   2943      *so_ptr = 999;    // force cursor to be vertically centered in the window
   2944    }
   2945    update_topline(curwin);
   2946    curwin->w_scbind_pos = plines_m_win_fill(curwin, 1, curwin->w_topline);
   2947    *so_ptr = n;
   2948    redraw_curbuf_later(UPD_NOT_VALID);  // redraw this buffer later
   2949  }
   2950 
   2951  // Change directories when the 'acd' option is set.
   2952  do_autochdir();
   2953 
   2954 theend:
   2955  if (bufref_valid(&old_curbuf) && old_curbuf.br_buf->terminal != NULL) {
   2956    terminal_check_size(old_curbuf.br_buf->terminal);
   2957  }
   2958  if ((!bufref_valid(&old_curbuf) || curbuf != old_curbuf.br_buf) && curbuf->terminal != NULL) {
   2959    terminal_check_size(curbuf->terminal);
   2960  }
   2961 
   2962  if (did_inc_redrawing_disabled) {
   2963    RedrawingDisabled--;
   2964  }
   2965  if (did_set_swapcommand) {
   2966    set_vim_var_string(VV_SWAPCOMMAND, NULL, -1);
   2967  }
   2968  xfree(free_fname);
   2969  return retval;
   2970 }
   2971 
   2972 static void delbuf_msg(char *name)
   2973 {
   2974  semsg(_("E143: Autocommands unexpectedly deleted new buffer %s"),
   2975        name == NULL ? "" : name);
   2976  xfree(name);
   2977  au_new_curbuf.br_buf = NULL;
   2978  au_new_curbuf.br_buf_free_count = 0;
   2979 }
   2980 
   2981 static int append_indent = 0;       // autoindent for first line
   2982 
   2983 /// ":insert" and ":append", also used by ":change"
   2984 void ex_append(exarg_T *eap)
   2985 {
   2986  char *theline;
   2987  bool did_undo = false;
   2988  linenr_T lnum = eap->line2;
   2989  int indent = 0;
   2990  char *p;
   2991  bool empty = (curbuf->b_ml.ml_flags & ML_EMPTY);
   2992 
   2993  // the ! flag toggles autoindent
   2994  if (eap->forceit) {
   2995    curbuf->b_p_ai = !curbuf->b_p_ai;
   2996  }
   2997 
   2998  // First autoindent comes from the line we start on
   2999  if (eap->cmdidx != CMD_change && curbuf->b_p_ai && lnum > 0) {
   3000    append_indent = get_indent_lnum(lnum);
   3001  }
   3002 
   3003  if (eap->cmdidx != CMD_append) {
   3004    lnum--;
   3005  }
   3006 
   3007  // when the buffer is empty need to delete the dummy line
   3008  if (empty && lnum == 1) {
   3009    lnum = 0;
   3010  }
   3011 
   3012  State = MODE_INSERT;                   // behave like in Insert mode
   3013  if (curbuf->b_p_iminsert == B_IMODE_LMAP) {
   3014    State |= MODE_LANGMAP;
   3015  }
   3016 
   3017  while (true) {
   3018    msg_scroll = true;
   3019    need_wait_return = false;
   3020    if (curbuf->b_p_ai) {
   3021      if (append_indent >= 0) {
   3022        indent = append_indent;
   3023        append_indent = -1;
   3024      } else if (lnum > 0) {
   3025        indent = get_indent_lnum(lnum);
   3026      }
   3027    }
   3028    if (*eap->arg == '|') {
   3029      // Get the text after the trailing bar.
   3030      theline = xstrdup(eap->arg + 1);
   3031      *eap->arg = NUL;
   3032    } else if (eap->ea_getline == NULL) {
   3033      // No getline() function, use the lines that follow. This ends
   3034      // when there is no more.
   3035      if (eap->nextcmd == NULL) {
   3036        break;
   3037      }
   3038      p = vim_strchr(eap->nextcmd, NL);
   3039      if (p == NULL) {
   3040        p = eap->nextcmd + strlen(eap->nextcmd);
   3041      }
   3042      theline = xmemdupz(eap->nextcmd, (size_t)(p - eap->nextcmd));
   3043      if (*p != NUL) {
   3044        p++;
   3045      } else {
   3046        p = NULL;
   3047      }
   3048      eap->nextcmd = p;
   3049    } else {
   3050      int save_State = State;
   3051      // Set State to avoid the cursor shape to be set to MODE_INSERT
   3052      // state when getline() returns.
   3053      State = MODE_CMDLINE;
   3054      theline = eap->ea_getline(eap->cstack->cs_looplevel > 0 ? -1 : NUL,
   3055                                eap->cookie, indent, true);
   3056      State = save_State;
   3057    }
   3058    lines_left = Rows - 1;
   3059    if (theline == NULL) {
   3060      break;
   3061    }
   3062 
   3063    // Look for the "." after automatic indent.
   3064    int vcol = 0;
   3065    for (p = theline; indent > vcol; p++) {
   3066      if (*p == ' ') {
   3067        vcol++;
   3068      } else if (*p == TAB) {
   3069        vcol += 8 - vcol % 8;
   3070      } else {
   3071        break;
   3072      }
   3073    }
   3074    if ((p[0] == '.' && p[1] == NUL)
   3075        || (!did_undo && u_save(lnum, lnum + 1 + (empty ? 1 : 0))
   3076            == FAIL)) {
   3077      xfree(theline);
   3078      break;
   3079    }
   3080 
   3081    // don't use autoindent if nothing was typed.
   3082    if (p[0] == NUL) {
   3083      theline[0] = NUL;
   3084    }
   3085 
   3086    did_undo = true;
   3087    ml_append(lnum, theline, 0, false);
   3088    if (empty) {
   3089      // there are no marks below the inserted lines
   3090      appended_lines(lnum, 1);
   3091    } else {
   3092      appended_lines_mark(lnum, 1);
   3093    }
   3094 
   3095    xfree(theline);
   3096    lnum++;
   3097 
   3098    if (empty) {
   3099      ml_delete(2);
   3100      empty = false;
   3101    }
   3102  }
   3103  State = MODE_NORMAL;
   3104  ui_cursor_shape();
   3105 
   3106  if (eap->forceit) {
   3107    curbuf->b_p_ai = !curbuf->b_p_ai;
   3108  }
   3109 
   3110  // "start" is set to eap->line2+1 unless that position is invalid (when
   3111  // eap->line2 pointed to the end of the buffer and nothing was appended)
   3112  // "end" is set to lnum when something has been appended, otherwise
   3113  // it is the same as "start"  -- Acevedo
   3114  if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
   3115    curbuf->b_op_start.lnum
   3116      = (eap->line2 < curbuf->b_ml.ml_line_count) ? eap->line2 + 1 : curbuf->b_ml.ml_line_count;
   3117    if (eap->cmdidx != CMD_append) {
   3118      curbuf->b_op_start.lnum--;
   3119    }
   3120    curbuf->b_op_end.lnum = (eap->line2 < lnum) ? lnum : curbuf->b_op_start.lnum;
   3121    curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
   3122  }
   3123  curwin->w_cursor.lnum = lnum;
   3124  check_cursor_lnum(curwin);
   3125  beginline(BL_SOL | BL_FIX);
   3126 
   3127  need_wait_return = false;     // don't use wait_return() now
   3128  ex_no_reprint = true;
   3129 }
   3130 
   3131 /// ":change"
   3132 void ex_change(exarg_T *eap)
   3133 {
   3134  linenr_T lnum;
   3135 
   3136  if (eap->line2 >= eap->line1
   3137      && u_save(eap->line1 - 1, eap->line2 + 1) == FAIL) {
   3138    return;
   3139  }
   3140 
   3141  // the ! flag toggles autoindent
   3142  if (eap->forceit ? !curbuf->b_p_ai : curbuf->b_p_ai) {
   3143    append_indent = get_indent_lnum(eap->line1);
   3144  }
   3145 
   3146  for (lnum = eap->line2; lnum >= eap->line1; lnum--) {
   3147    if (curbuf->b_ml.ml_flags & ML_EMPTY) {         // nothing to delete
   3148      break;
   3149    }
   3150    ml_delete(eap->line1);
   3151  }
   3152 
   3153  // make sure the cursor is not beyond the end of the file now
   3154  check_cursor_lnum(curwin);
   3155  deleted_lines_mark(eap->line1, (eap->line2 - lnum));
   3156 
   3157  // ":append" on the line above the deleted lines.
   3158  eap->line2 = eap->line1;
   3159  ex_append(eap);
   3160 }
   3161 
   3162 void ex_z(exarg_T *eap)
   3163 {
   3164  int64_t bigness;
   3165  int minus = 0;
   3166  linenr_T start, end, curs;
   3167  linenr_T lnum = eap->line2;
   3168 
   3169  // Vi compatible: ":z!" uses display height, without a count uses
   3170  // 'scroll'
   3171  if (eap->forceit) {
   3172    bigness = Rows - 1;
   3173  } else if (ONE_WINDOW) {
   3174    bigness = curwin->w_p_scr * 2;
   3175  } else {
   3176    bigness = curwin->w_view_height - 3;
   3177  }
   3178  bigness = MAX(bigness, 1);
   3179 
   3180  char *x = eap->arg;
   3181  char *kind = x;
   3182  if (*kind == '-' || *kind == '+' || *kind == '='
   3183      || *kind == '^' || *kind == '.') {
   3184    x++;
   3185  }
   3186  while (*x == '-' || *x == '+') {
   3187    x++;
   3188  }
   3189 
   3190  if (*x != 0) {
   3191    if (!ascii_isdigit(*x)) {
   3192      emsg(_(e_non_numeric_argument_to_z));
   3193      return;
   3194    }
   3195    bigness = atol(x);
   3196 
   3197    // bigness could be < 0 if atol(x) overflows.
   3198    if (bigness > 2 * curbuf->b_ml.ml_line_count || bigness < 0) {
   3199      bigness = 2 * curbuf->b_ml.ml_line_count;
   3200    }
   3201 
   3202    p_window = (int)bigness;
   3203    if (*kind == '=') {
   3204      bigness += 2;
   3205    }
   3206  }
   3207 
   3208  // the number of '-' and '+' multiplies the distance
   3209  if (*kind == '-' || *kind == '+') {
   3210    for (x = kind + 1; *x == *kind; x++) {}
   3211  }
   3212 
   3213  switch (*kind) {
   3214  case '-':
   3215    start = lnum - (linenr_T)bigness * (linenr_T)(x - kind) + 1;
   3216    end = start + (linenr_T)bigness - 1;
   3217    curs = end;
   3218    break;
   3219 
   3220  case '=':
   3221    start = lnum - ((linenr_T)bigness + 1) / 2 + 1;
   3222    end = lnum + ((linenr_T)bigness + 1) / 2 - 1;
   3223    curs = lnum;
   3224    minus = 1;
   3225    break;
   3226 
   3227  case '^':
   3228    start = lnum - (linenr_T)bigness * 2;
   3229    end = lnum - (linenr_T)bigness;
   3230    curs = lnum - (linenr_T)bigness;
   3231    break;
   3232 
   3233  case '.':
   3234    start = lnum - ((linenr_T)bigness + 1) / 2 + 1;
   3235    end = lnum + ((linenr_T)bigness + 1) / 2 - 1;
   3236    curs = end;
   3237    break;
   3238 
   3239  default:        // '+'
   3240    start = lnum;
   3241    if (*kind == '+') {
   3242      start += (linenr_T)bigness * (linenr_T)(x - kind - 1) + 1;
   3243    } else if (eap->addr_count == 0) {
   3244      start++;
   3245    }
   3246    end = start + (linenr_T)bigness - 1;
   3247    curs = end;
   3248    break;
   3249  }
   3250 
   3251  start = MAX(start, 1);
   3252  end = MIN(end, curbuf->b_ml.ml_line_count);
   3253  curs = MIN(MAX(curs, 1), curbuf->b_ml.ml_line_count);
   3254 
   3255  for (linenr_T i = start; i <= end; i++) {
   3256    if (minus && i == lnum) {
   3257      msg_putchar('\n');
   3258 
   3259      for (int j = 1; j < Columns; j++) {
   3260        msg_putchar('-');
   3261      }
   3262    }
   3263 
   3264    print_line(i, eap->flags & EXFLAG_NR, eap->flags & EXFLAG_LIST, i == start);
   3265 
   3266    if (minus && i == lnum) {
   3267      msg_putchar('\n');
   3268 
   3269      for (int j = 1; j < Columns; j++) {
   3270        msg_putchar('-');
   3271      }
   3272    }
   3273  }
   3274 
   3275  if (curwin->w_cursor.lnum != curs) {
   3276    curwin->w_cursor.lnum = curs;
   3277    curwin->w_cursor.col = 0;
   3278  }
   3279  ex_no_reprint = true;
   3280 }
   3281 
   3282 /// @return  true if the secure flag is set and also give an error message.
   3283 ///          Otherwise, return false.
   3284 bool check_secure(void)
   3285 {
   3286  if (secure) {
   3287    secure = 2;
   3288    emsg(_(e_curdir));
   3289    return true;
   3290  }
   3291 
   3292  // In the sandbox more things are not allowed, including the things
   3293  // disallowed in secure mode.
   3294  if (sandbox != 0) {
   3295    emsg(_(e_sandbox));
   3296    return true;
   3297  }
   3298  return false;
   3299 }
   3300 
   3301 /// Previous substitute replacement string
   3302 static SubReplacementString old_sub = { NULL, 0, NULL };
   3303 
   3304 static int global_need_beginline;       // call beginline() after ":g"
   3305 
   3306 /// Get old substitute replacement string
   3307 ///
   3308 /// @param[out]  ret_sub    Location where old string will be saved.
   3309 void sub_get_replacement(SubReplacementString *const ret_sub)
   3310  FUNC_ATTR_NONNULL_ALL
   3311 {
   3312  *ret_sub = old_sub;
   3313 }
   3314 
   3315 /// Set substitute string and timestamp
   3316 ///
   3317 /// @warning `sub` must be in allocated memory. It is not copied.
   3318 ///
   3319 /// @param[in]  sub  New replacement string.
   3320 void sub_set_replacement(SubReplacementString sub)
   3321 {
   3322  xfree(old_sub.sub);
   3323  if (sub.additional_data != old_sub.additional_data) {
   3324    xfree(old_sub.additional_data);
   3325  }
   3326  old_sub = sub;
   3327 }
   3328 
   3329 /// Recognize ":%s/\n//" and turn it into a join command, which is much
   3330 /// more efficient.
   3331 ///
   3332 /// @param[in]  eap  Ex arguments
   3333 /// @param[in]  pat  Search pattern
   3334 /// @param[in]  sub  Replacement string
   3335 /// @param[in]  cmd  Command from :s_flags
   3336 /// @param[in]  save Save pattern to options, history
   3337 ///
   3338 /// @returns true if :substitute can be replaced with a join command
   3339 static bool sub_joining_lines(exarg_T *eap, char *pat, size_t patlen, const char *sub,
   3340                              const char *cmd, bool save, bool keeppatterns)
   3341  FUNC_ATTR_NONNULL_ARG(1, 4, 5)
   3342 {
   3343  // TODO(vim): find a generic solution to make line-joining operations more
   3344  // efficient, avoid allocating a string that grows in size.
   3345  if (pat != NULL
   3346      && strcmp(pat, "\\n") == 0
   3347      && *sub == NUL
   3348      && (*cmd == NUL || (cmd[1] == NUL
   3349                          && (*cmd == 'g'
   3350                              || *cmd == 'l'
   3351                              || *cmd == 'p'
   3352                              || *cmd == '#')))) {
   3353    if (eap->skip) {
   3354      return true;
   3355    }
   3356    curwin->w_cursor.lnum = eap->line1;
   3357    if (*cmd == 'l') {
   3358      eap->flags = EXFLAG_LIST;
   3359    } else if (*cmd == '#') {
   3360      eap->flags = EXFLAG_NR;
   3361    } else if (*cmd == 'p') {
   3362      eap->flags = EXFLAG_PRINT;
   3363    }
   3364 
   3365    // The number of lines joined is the number of lines in the range
   3366    linenr_T joined_lines_count = eap->line2 - eap->line1 + 1
   3367                                  // plus one extra line if not at the end of file.
   3368                                  + (eap->line2 < curbuf->b_ml.ml_line_count ? 1 : 0);
   3369    if (joined_lines_count > 1) {
   3370      do_join((size_t)joined_lines_count, false, true, false, true);
   3371      sub_nsubs = joined_lines_count - 1;
   3372      sub_nlines = 1;
   3373      do_sub_msg(false);
   3374      ex_may_print(eap);
   3375    }
   3376 
   3377    if (save) {
   3378      if (!keeppatterns) {
   3379        save_re_pat(RE_SUBST, pat, patlen, magic_isset());
   3380      }
   3381      // put pattern in history
   3382      add_to_history(HIST_SEARCH, pat, patlen, true, NUL);
   3383    }
   3384 
   3385    return true;
   3386  }
   3387 
   3388  return false;
   3389 }
   3390 
   3391 /// Allocate memory to store the replacement text for :substitute.
   3392 ///
   3393 /// Slightly more memory that is strictly necessary is allocated to reduce the
   3394 /// frequency of memory (re)allocation.
   3395 ///
   3396 /// @param[in,out]  new_start      pointer to the memory for the replacement text
   3397 /// @param[in,out]  new_start_len  pointer to length of new_start
   3398 /// @param[in]      needed_len     amount of memory needed
   3399 ///
   3400 /// @returns pointer to the end of the allocated memory
   3401 static char *sub_grow_buf(char **new_start, int *new_start_len, int needed_len)
   3402  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
   3403 {
   3404  char *new_end;
   3405  if (*new_start == NULL) {
   3406    // Get some space for a temporary buffer to do the
   3407    // substitution into (and some extra space to avoid
   3408    // too many calls to xmalloc()/free()).
   3409    *new_start_len = needed_len + 50;
   3410    *new_start = xcalloc(1, (size_t)(*new_start_len));
   3411    **new_start = NUL;
   3412    new_end = *new_start;
   3413  } else {
   3414    // Check if the temporary buffer is long enough to do the
   3415    // substitution into.  If not, make it larger (with a bit
   3416    // extra to avoid too many calls to xmalloc()/free()).
   3417    size_t len = strlen(*new_start);
   3418    needed_len += (int)len;
   3419    if (needed_len > *new_start_len) {
   3420      size_t prev_new_start_len = (size_t)(*new_start_len);
   3421      *new_start_len = needed_len + 50;
   3422      size_t added_len = (size_t)(*new_start_len) - prev_new_start_len;
   3423      *new_start = xrealloc(*new_start, (size_t)(*new_start_len));
   3424      memset(*new_start + prev_new_start_len, 0, added_len);
   3425    }
   3426    new_end = *new_start + len;
   3427  }
   3428 
   3429  return new_end;
   3430 }
   3431 
   3432 /// Parse cmd string for :substitute's {flags} and update subflags accordingly
   3433 ///
   3434 /// @param[in]      cmd  command string
   3435 /// @param[in,out]  subflags  current flags defined for the :substitute command
   3436 /// @param[in,out]  which_pat  pattern type from which to get default search
   3437 ///
   3438 /// @returns pointer to the end of the flags, which may be the end of the string
   3439 static char *sub_parse_flags(char *cmd, subflags_T *subflags, int *which_pat)
   3440  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
   3441 {
   3442  // Find trailing options.  When '&' is used, keep old options.
   3443  if (*cmd == '&') {
   3444    cmd++;
   3445  } else {
   3446    subflags->do_all = p_gd;
   3447    subflags->do_ask = false;
   3448    subflags->do_error = true;
   3449    subflags->do_print = false;
   3450    subflags->do_list = false;
   3451    subflags->do_count = false;
   3452    subflags->do_number = false;
   3453    subflags->do_ic = kSubHonorOptions;
   3454  }
   3455  while (*cmd) {
   3456    // Note that 'g' and 'c' are always inverted.
   3457    // 'r' is never inverted.
   3458    if (*cmd == 'g') {
   3459      subflags->do_all = !subflags->do_all;
   3460    } else if (*cmd == 'c') {
   3461      subflags->do_ask = !subflags->do_ask;
   3462    } else if (*cmd == 'n') {
   3463      subflags->do_count = true;
   3464    } else if (*cmd == 'e') {
   3465      subflags->do_error = !subflags->do_error;
   3466    } else if (*cmd == 'r') {  // use last used regexp
   3467      *which_pat = RE_LAST;
   3468    } else if (*cmd == 'p') {
   3469      subflags->do_print = true;
   3470    } else if (*cmd == '#') {
   3471      subflags->do_print = true;
   3472      subflags->do_number = true;
   3473    } else if (*cmd == 'l') {
   3474      subflags->do_print = true;
   3475      subflags->do_list = true;
   3476    } else if (*cmd == 'i') {  // ignore case
   3477      subflags->do_ic = kSubIgnoreCase;
   3478    } else if (*cmd == 'I') {  // don't ignore case
   3479      subflags->do_ic = kSubMatchCase;
   3480    } else {
   3481      break;
   3482    }
   3483    cmd++;
   3484  }
   3485  if (subflags->do_count) {
   3486    subflags->do_ask = false;
   3487  }
   3488 
   3489  return cmd;
   3490 }
   3491 
   3492 /// Skip over the "sub" part in :s/pat/sub/ where "delimiter" is the separating
   3493 /// character.
   3494 static char *skip_substitute(char *start, int delimiter)
   3495 {
   3496  char *p = start;
   3497 
   3498  while (p[0]) {
   3499    if (p[0] == delimiter) {  // end delimiter found
   3500      *p++ = NUL;  // replace it with a NUL
   3501      break;
   3502    }
   3503    if (p[0] == '\\' && p[1] != 0) {  // skip escaped characters
   3504      p++;
   3505    }
   3506    MB_PTR_ADV(p);
   3507  }
   3508  return p;
   3509 }
   3510 
   3511 static int check_regexp_delim(int c)
   3512  FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
   3513 {
   3514  if (isalpha(c)) {
   3515    emsg(_("E146: Regular expressions can't be delimited by letters"));
   3516    return FAIL;
   3517  }
   3518  return OK;
   3519 }
   3520 
   3521 /// Perform a substitution from line eap->line1 to line eap->line2 using the
   3522 /// command pointed to by eap->arg which should be of the form:
   3523 ///
   3524 /// /pattern/substitution/{flags}
   3525 ///
   3526 /// The usual escapes are supported as described in the regexp docs.
   3527 ///
   3528 /// @param cmdpreview_ns  The namespace to show 'inccommand' preview highlights.
   3529 ///                       If <= 0, preview shouldn't be shown.
   3530 /// @return  0, 1 or 2. See cmdpreview_may_show() for more information on the meaning.
   3531 static int do_sub(exarg_T *eap, const proftime_T timeout, const int cmdpreview_ns,
   3532                  const handle_T cmdpreview_bufnr)
   3533 {
   3534 #define ADJUST_SUB_FIRSTLNUM() \
   3535  do { \
   3536    /* For a multi-line match, make a copy of the last matched */ \
   3537    /* line and continue in that one. */ \
   3538    if (nmatch > 1) { \
   3539      sub_firstlnum += (linenr_T)nmatch - 1; \
   3540      xfree(sub_firstline); \
   3541      sub_firstline = xstrnsave(ml_get(sub_firstlnum), \
   3542                                (size_t)ml_get_len(sub_firstlnum)); \
   3543      /* When going beyond the last line, stop substituting. */ \
   3544      if (sub_firstlnum <= line2) { \
   3545        do_again = true; \
   3546      } else { \
   3547        subflags.do_all = false; \
   3548      } \
   3549    } \
   3550    if (skip_match) { \
   3551      /* Already hit end of the buffer, sub_firstlnum is one */ \
   3552      /* less than what it ought to be. */ \
   3553      xfree(sub_firstline); \
   3554      sub_firstline = xstrdup(""); \
   3555      copycol = 0; \
   3556    } \
   3557  } while (0)
   3558 
   3559  int i = 0;
   3560  regmmatch_T regmatch;
   3561  static subflags_T subflags = {
   3562    .do_all = false,
   3563    .do_ask = false,
   3564    .do_count = false,
   3565    .do_error = true,
   3566    .do_print = false,
   3567    .do_list = false,
   3568    .do_number = false,
   3569    .do_ic = kSubHonorOptions
   3570  };
   3571  char *pat = NULL;
   3572  char *sub = NULL;  // init for GCC
   3573  size_t patlen = 0;
   3574  int delimiter;
   3575  bool has_second_delim = false;
   3576  int sublen;
   3577  bool got_quit = false;
   3578  bool got_match = false;
   3579  int which_pat;
   3580  char *cmd = eap->arg;
   3581  linenr_T first_line = 0;  // first changed line
   3582  linenr_T last_line = 0;    // below last changed line AFTER the change
   3583  linenr_T old_line_count = curbuf->b_ml.ml_line_count;
   3584  char *sub_firstline;    // allocated copy of first sub line
   3585  bool endcolumn = false;   // cursor in last column when done
   3586  const bool keeppatterns = cmdmod.cmod_flags & CMOD_KEEPPATTERNS;
   3587  PreviewLines preview_lines = { KV_INITIAL_VALUE, 0 };
   3588  static int pre_hl_id = 0;
   3589  pos_T old_cursor = curwin->w_cursor;
   3590  int start_nsubs;
   3591 
   3592  bool did_save = false;
   3593 
   3594  if (!global_busy) {
   3595    sub_nsubs = 0;
   3596    sub_nlines = 0;
   3597  }
   3598  start_nsubs = sub_nsubs;
   3599 
   3600  if (eap->cmdidx == CMD_tilde) {
   3601    which_pat = RE_LAST;        // use last used regexp
   3602  } else {
   3603    which_pat = RE_SUBST;       // use last substitute regexp
   3604  }
   3605  // new pattern and substitution
   3606  if (eap->cmd[0] == 's' && *cmd != NUL && !ascii_iswhite(*cmd)
   3607      && vim_strchr("0123456789cegriIp|\"", (uint8_t)(*cmd)) == NULL) {
   3608    // don't accept alphanumeric for separator
   3609    if (check_regexp_delim(*cmd) == FAIL) {
   3610      return 0;
   3611    }
   3612 
   3613    // undocumented vi feature:
   3614    //  "\/sub/" and "\?sub?" use last used search pattern (almost like
   3615    //  //sub/r).  "\&sub&" use last substitute pattern (like //sub/).
   3616    if (*cmd == '\\') {
   3617      cmd++;
   3618      if (vim_strchr("/?&", (uint8_t)(*cmd)) == NULL) {
   3619        emsg(_(e_backslash));
   3620        return 0;
   3621      }
   3622      if (*cmd != '&') {
   3623        which_pat = RE_SEARCH;              // use last '/' pattern
   3624      }
   3625      pat = "";                   // empty search pattern
   3626      patlen = 0;
   3627      delimiter = (uint8_t)(*cmd++);                   // remember delimiter character
   3628      has_second_delim = true;
   3629    } else {          // find the end of the regexp
   3630      which_pat = RE_LAST;                  // use last used regexp
   3631      delimiter = (uint8_t)(*cmd++);                   // remember delimiter character
   3632      pat = cmd;                            // remember start of search pat
   3633      cmd = skip_regexp_ex(cmd, delimiter, magic_isset(), &eap->arg, NULL, NULL);
   3634      if (cmd[0] == delimiter) {            // end delimiter found
   3635        *cmd++ = NUL;                       // replace it with a NUL
   3636        has_second_delim = true;
   3637      }
   3638      patlen = strlen(pat);
   3639    }
   3640 
   3641    // Small incompatibility: vi sees '\n' as end of the command, but in
   3642    // Vim we want to use '\n' to find/substitute a NUL.
   3643    char *p = cmd;  // remember the start of the substitution
   3644    cmd = skip_substitute(cmd, delimiter);
   3645    sub = xstrdup(p);
   3646 
   3647    if (!eap->skip && !keeppatterns && cmdpreview_ns <= 0) {
   3648      sub_set_replacement((SubReplacementString) {
   3649        .sub = xstrdup(sub),
   3650        .timestamp = os_time(),
   3651        .additional_data = NULL,
   3652      });
   3653    }
   3654  } else if (!eap->skip) {    // use previous pattern and substitution
   3655    if (old_sub.sub == NULL) {      // there is no previous command
   3656      emsg(_(e_nopresub));
   3657      return 0;
   3658    }
   3659    pat = NULL;                 // search_regcomp() will use previous pattern
   3660    patlen = 0;
   3661    sub = xstrdup(old_sub.sub);
   3662 
   3663    // Vi compatibility quirk: repeating with ":s" keeps the cursor in the
   3664    // last column after using "$".
   3665    endcolumn = (curwin->w_curswant == MAXCOL);
   3666  }
   3667 
   3668  if (sub != NULL && sub_joining_lines(eap, pat, patlen, sub, cmd, cmdpreview_ns <= 0,
   3669                                       keeppatterns)) {
   3670    xfree(sub);
   3671    return 0;
   3672  }
   3673 
   3674  cmd = sub_parse_flags(cmd, &subflags, &which_pat);
   3675 
   3676  bool save_do_all = subflags.do_all;  // remember user specified 'g' flag
   3677  bool save_do_ask = subflags.do_ask;  // remember user specified 'c' flag
   3678 
   3679  // check for a trailing count
   3680  cmd = skipwhite(cmd);
   3681  if (ascii_isdigit(*cmd)) {
   3682    i = getdigits_int(&cmd, true, INT_MAX);
   3683    if (i <= 0 && !eap->skip && subflags.do_error) {
   3684      emsg(_(e_zerocount));
   3685      xfree(sub);
   3686      return 0;
   3687    } else if (i >= INT_MAX) {
   3688      char buf[20];
   3689      vim_snprintf(buf, sizeof(buf), "%d", i);
   3690      semsg(_(e_val_too_large), buf);
   3691      xfree(sub);
   3692      return 0;
   3693    }
   3694    eap->line1 = eap->line2;
   3695    eap->line2 += (linenr_T)i - 1;
   3696    eap->line2 = MIN(eap->line2, curbuf->b_ml.ml_line_count);
   3697  }
   3698 
   3699  // check for trailing command or garbage
   3700  cmd = skipwhite(cmd);
   3701  if (*cmd && *cmd != '"') {        // if not end-of-line or comment
   3702    eap->nextcmd = check_nextcmd(cmd);
   3703    if (eap->nextcmd == NULL) {
   3704      semsg(_(e_trailing_arg), cmd);
   3705      xfree(sub);
   3706      return 0;
   3707    }
   3708  }
   3709 
   3710  if (eap->skip) {          // not executing commands, only parsing
   3711    xfree(sub);
   3712    return 0;
   3713  }
   3714 
   3715  if (!subflags.do_count && !MODIFIABLE(curbuf)) {
   3716    // Substitution is not allowed in non-'modifiable' buffer
   3717    emsg(_(e_modifiable));
   3718    xfree(sub);
   3719    return 0;
   3720  }
   3721 
   3722  if (search_regcomp(pat, patlen, NULL, RE_SUBST, which_pat,
   3723                     (cmdpreview_ns > 0 ? 0 : SEARCH_HIS), &regmatch) == FAIL) {
   3724    if (subflags.do_error) {
   3725      emsg(_(e_invcmd));
   3726    }
   3727    xfree(sub);
   3728    return 0;
   3729  }
   3730 
   3731  // the 'i' or 'I' flag overrules 'ignorecase' and 'smartcase'
   3732  if (subflags.do_ic == kSubIgnoreCase) {
   3733    regmatch.rmm_ic = true;
   3734  } else if (subflags.do_ic == kSubMatchCase) {
   3735    regmatch.rmm_ic = false;
   3736  }
   3737 
   3738  sub_firstline = NULL;
   3739 
   3740  assert(sub != NULL);
   3741 
   3742  // If the substitute pattern starts with "\=" then it's an expression.
   3743  // Make a copy, a recursive function may free it.
   3744  // Otherwise, '~' in the substitute pattern is replaced with the old
   3745  // pattern.  We do it here once to avoid it to be replaced over and over
   3746  // again.
   3747  if (sub[0] == '\\' && sub[1] == '=') {
   3748    char *p = xstrdup(sub);
   3749    xfree(sub);
   3750    sub = p;
   3751  } else {
   3752    char *p = regtilde(sub, magic_isset(), cmdpreview_ns > 0);
   3753    if (p != sub) {
   3754      xfree(sub);
   3755      sub = p;
   3756    }
   3757  }
   3758 
   3759  // Check for a match on each line.
   3760  // If preview: limit to max('cmdwinheight', viewport).
   3761  linenr_T line2 = eap->line2;
   3762 
   3763  for (linenr_T lnum = eap->line1;
   3764       lnum <= line2 && !got_quit && !aborting()
   3765       && (cmdpreview_ns <= 0 || preview_lines.lines_needed <= (linenr_T)p_cwh
   3766           || lnum <= curwin->w_botline);
   3767       lnum++) {
   3768    int nmatch = vim_regexec_multi(&regmatch, curwin, curbuf, lnum,
   3769                                   0, NULL, NULL);
   3770    if (nmatch) {
   3771      colnr_T copycol;
   3772      colnr_T matchcol;
   3773      colnr_T prev_matchcol = MAXCOL;
   3774      char *new_end;
   3775      char *new_start = NULL;
   3776      int new_start_len = 0;
   3777      char *p1;
   3778      bool did_sub = false;
   3779      int lastone;
   3780      linenr_T nmatch_tl = 0;               // nr of lines matched below lnum
   3781      int do_again;                     // do it again after joining lines
   3782      bool skip_match = false;
   3783      linenr_T sub_firstlnum;           // nr of first sub line
   3784 
   3785      // Track where substitutions started (set once per line).
   3786      linenr_T lnum_start = 0;
   3787 
   3788      // Track per-line data for each match.
   3789      // Will be sent as a batch to `extmark_splice` after the substitution is done.
   3790      typedef struct {
   3791        int start_col;         // Position in new text where replacement goes
   3792        lpos_T start;          // Match start position in original text
   3793        lpos_T end;            // Match end position in original text
   3794        int matchcols;         // Columns deleted from original text
   3795        bcount_t matchbytes;   // Bytes deleted from original text
   3796        int subcols;           // Columns in replacement text
   3797        bcount_t subbytes;     // Bytes in replacement text
   3798        linenr_T lnum_before;  // Line number before this substitution
   3799        linenr_T lnum_after;   // Line number after this substitution
   3800      } LineData;
   3801 
   3802      kvec_t(LineData) line_matches = KV_INITIAL_VALUE;
   3803 
   3804      // The new text is build up step by step, to avoid too much
   3805      // copying.  There are these pieces:
   3806      // sub_firstline  The old text, unmodified.
   3807      // copycol                Column in the old text where we started
   3808      //                        looking for a match; from here old text still
   3809      //                        needs to be copied to the new text.
   3810      // matchcol               Column number of the old text where to look
   3811      //                        for the next match.  It's just after the
   3812      //                        previous match or one further.
   3813      // prev_matchcol  Column just after the previous match (if any).
   3814      //                        Mostly equal to matchcol, except for the first
   3815      //                        match and after skipping an empty match.
   3816      // regmatch.*pos  Where the pattern matched in the old text.
   3817      // new_start      The new text, all that has been produced so
   3818      //                        far.
   3819      // new_end                The new text, where to append new text.
   3820      //
   3821      // lnum           The line number where we found the start of
   3822      //                        the match.  Can be below the line we searched
   3823      //                        when there is a \n before a \zs in the
   3824      //                        pattern.
   3825      // sub_firstlnum  The line number in the buffer where to look
   3826      //                        for a match.  Can be different from "lnum"
   3827      //                        when the pattern or substitute string contains
   3828      //                        line breaks.
   3829      //
   3830      // Special situations:
   3831      // - When the substitute string contains a line break, the part up
   3832      //   to the line break is inserted in the text, but the copy of
   3833      //   the original line is kept.  "sub_firstlnum" is adjusted for
   3834      //   the inserted lines.
   3835      // - When the matched pattern contains a line break, the old line
   3836      //   is taken from the line at the end of the pattern.  The lines
   3837      //   in the match are deleted later, "sub_firstlnum" is adjusted
   3838      //   accordingly.
   3839      //
   3840      // The new text is built up in new_start[].  It has some extra
   3841      // room to avoid using xmalloc()/free() too often.  new_start_len is
   3842      // the length of the allocated memory at new_start.
   3843      //
   3844      // Make a copy of the old line, so it won't be taken away when
   3845      // updating the screen or handling a multi-line match.  The "old_"
   3846      // pointers point into this copy.
   3847      sub_firstlnum = lnum;
   3848      copycol = 0;
   3849      matchcol = 0;
   3850 
   3851      // At first match, remember current cursor position.
   3852      if (!got_match) {
   3853        setpcmark();
   3854        got_match = true;
   3855      }
   3856 
   3857      // Loop until nothing more to replace in this line.
   3858      // 1. Handle match with empty string.
   3859      // 2. If subflags.do_ask is set, ask for confirmation.
   3860      // 3. substitute the string.
   3861      // 4. if subflags.do_all is set, find next match
   3862      // 5. break if there isn't another match in this line
   3863      while (true) {
   3864        SubResult current_match = {
   3865          .start = { 0, 0 },
   3866          .end = { 0, 0 },
   3867          .pre_match = 0,
   3868        };
   3869        // lnum is where the match start, but maybe not the pattern match,
   3870        // since we can have \n before \zs in the pattern
   3871 
   3872        // Advance "lnum" to the line where the match starts.  The
   3873        // match does not start in the first line when there is a line
   3874        // break before \zs.
   3875        if (regmatch.startpos[0].lnum > 0) {
   3876          current_match.pre_match = lnum;
   3877          lnum += regmatch.startpos[0].lnum;
   3878          sub_firstlnum += regmatch.startpos[0].lnum;
   3879          nmatch -= regmatch.startpos[0].lnum;
   3880          XFREE_CLEAR(sub_firstline);
   3881        }
   3882 
   3883        // Now we're at the line where the pattern match starts
   3884        // Note: If not first match on a line, column can't be known here
   3885        current_match.start.lnum = sub_firstlnum;
   3886 
   3887        // Match might be after the last line for "\n\zs" matching at
   3888        // the end of the last line.
   3889        if (lnum > curbuf->b_ml.ml_line_count) {
   3890          break;
   3891        }
   3892        if (sub_firstline == NULL) {
   3893          sub_firstline = xstrnsave(ml_get(sub_firstlnum),
   3894                                    (size_t)ml_get_len(sub_firstlnum));
   3895        }
   3896 
   3897        // Save the line number of the last change for the final
   3898        // cursor position (just like Vi).
   3899        curwin->w_cursor.lnum = lnum;
   3900        do_again = false;
   3901 
   3902        // 1. Match empty string does not count, except for first
   3903        // match.  This reproduces the strange vi behaviour.
   3904        // This also catches endless loops.
   3905        if (matchcol == prev_matchcol
   3906            && regmatch.endpos[0].lnum == 0
   3907            && matchcol == regmatch.endpos[0].col) {
   3908          if (sub_firstline[matchcol] == NUL) {
   3909            // We already were at the end of the line.  Don't look
   3910            // for a match in this line again.
   3911            skip_match = true;
   3912          } else {
   3913            // search for a match at next column
   3914            matchcol += utfc_ptr2len(sub_firstline + matchcol);
   3915          }
   3916          // match will be pushed to preview_lines, bring it into a proper state
   3917          current_match.start.col = matchcol;
   3918          current_match.end.lnum = sub_firstlnum;
   3919          current_match.end.col = matchcol;
   3920          goto skip;
   3921        }
   3922 
   3923        // Normally we continue searching for a match just after the
   3924        // previous match.
   3925        matchcol = regmatch.endpos[0].col;
   3926        prev_matchcol = matchcol;
   3927 
   3928        // 2. If subflags.do_count is set only increase the counter.
   3929        //    If do_ask is set, ask for confirmation.
   3930        if (subflags.do_count) {
   3931          // For a multi-line match, put matchcol at the NUL at
   3932          // the end of the line and set nmatch to one, so that
   3933          // we continue looking for a match on the next line.
   3934          // Avoids that ":s/\nB\@=//gc" get stuck.
   3935          if (nmatch > 1) {
   3936            matchcol = (colnr_T)strlen(sub_firstline);
   3937            nmatch = 1;
   3938            skip_match = true;
   3939          }
   3940          sub_nsubs++;
   3941          did_sub = true;
   3942          // Skip the substitution, unless an expression is used,
   3943          // then it is evaluated in the sandbox.
   3944          if (!(sub[0] == '\\' && sub[1] == '=')) {
   3945            goto skip;
   3946          }
   3947        }
   3948 
   3949        if (subflags.do_ask && cmdpreview_ns <= 0) {
   3950          int typed = 0;
   3951          int save_State = State;
   3952          curwin->w_cursor.col = regmatch.startpos[0].col;
   3953 
   3954          if (curwin->w_p_crb) {
   3955            do_check_cursorbind();
   3956          }
   3957 
   3958          // When 'cpoptions' contains "u" don't sync undo when
   3959          // asking for confirmation.
   3960          if (vim_strchr(p_cpo, CPO_UNDO) != NULL) {
   3961            no_u_sync++;
   3962          }
   3963 
   3964          // Loop until 'y', 'n', 'q', CTRL-E or CTRL-Y typed.
   3965          while (subflags.do_ask) {
   3966            if (exmode_active) {
   3967              print_line_no_prefix(lnum, subflags.do_number, subflags.do_list);
   3968 
   3969              colnr_T sc, ec;
   3970              getvcol(curwin, &curwin->w_cursor, &sc, NULL, NULL);
   3971              curwin->w_cursor.col = MAX(regmatch.endpos[0].col - 1, 0);
   3972 
   3973              getvcol(curwin, &curwin->w_cursor, NULL, NULL, &ec);
   3974              curwin->w_cursor.col = regmatch.startpos[0].col;
   3975              if (subflags.do_number || curwin->w_p_nu) {
   3976                int numw = number_width(curwin) + 1;
   3977                sc += numw;
   3978                ec += numw;
   3979              }
   3980 
   3981              char *prompt = xmallocz((size_t)ec + 1);
   3982              memset(prompt, ' ', (size_t)sc);
   3983              memset(prompt + sc, '^', (size_t)(ec - sc) + 1);
   3984              char *resp = getcmdline_prompt(-1, prompt, 0, EXPAND_NOTHING, NULL,
   3985                                             CALLBACK_NONE, false, NULL);
   3986              if (!ui_has(kUIMessages)) {
   3987                msg_putchar('\n');
   3988              }
   3989              xfree(prompt);
   3990              if (resp != NULL) {
   3991                typed = (uint8_t)(*resp);
   3992                xfree(resp);
   3993              } else {
   3994                // getcmdline_prompt() returns NULL if there is no command line to return.
   3995                typed = NUL;
   3996              }
   3997              // When ":normal" runs out of characters we get
   3998              // an empty line.  Use "q" to get out of the
   3999              // loop.
   4000              if (ex_normal_busy && typed == NUL) {
   4001                typed = 'q';
   4002              }
   4003            } else {
   4004              char *orig_line = NULL;
   4005              int len_change = 0;
   4006              const bool save_p_lz = p_lz;
   4007              int save_p_fen = curwin->w_p_fen;
   4008 
   4009              curwin->w_p_fen = false;
   4010              // Invert the matched string.
   4011              // Remove the inversion afterwards.
   4012              int temp = RedrawingDisabled;
   4013              RedrawingDisabled = 0;
   4014 
   4015              // avoid calling update_screen() in vgetorpeek()
   4016              p_lz = false;
   4017 
   4018              if (new_start != NULL) {
   4019                // There already was a substitution, we would
   4020                // like to show this to the user.  We cannot
   4021                // really update the line, it would change
   4022                // what matches.  Temporarily replace the line
   4023                // and change it back afterwards.
   4024                orig_line = xstrnsave(ml_get(lnum), (size_t)ml_get_len(lnum));
   4025                char *new_line = concat_str(new_start, sub_firstline + copycol);
   4026 
   4027                // Position the cursor relative to the end of the line, the
   4028                // previous substitute may have inserted or deleted characters
   4029                // before the cursor.
   4030                len_change = (int)strlen(new_line) - (int)strlen(orig_line);
   4031                curwin->w_cursor.col += len_change;
   4032                ml_replace(lnum, new_line, false);
   4033              }
   4034 
   4035              search_match_lines = regmatch.endpos[0].lnum
   4036                                   - regmatch.startpos[0].lnum;
   4037              search_match_endcol = regmatch.endpos[0].col
   4038                                    + len_change;
   4039              if (search_match_lines == 0 && search_match_endcol == 0) {
   4040                // highlight at least one character for /^/
   4041                search_match_endcol = 1;
   4042              }
   4043              highlight_match = true;
   4044 
   4045              update_topline(curwin);
   4046              validate_cursor(curwin);
   4047              redraw_later(curwin, UPD_SOME_VALID);
   4048              show_cursor_info_later(true);
   4049              update_screen();
   4050              redraw_later(curwin, UPD_SOME_VALID);
   4051 
   4052              curwin->w_p_fen = save_p_fen;
   4053 
   4054              char *p = _("replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)");
   4055              snprintf(IObuff, IOSIZE, p, sub);
   4056              p = xstrdup(IObuff);
   4057              typed = prompt_for_input(p, HLF_R, true, NULL);
   4058              highlight_match = false;
   4059              xfree(p);
   4060 
   4061              msg_didout = false;                 // don't scroll up
   4062              gotocmdline(true);
   4063              p_lz = save_p_lz;
   4064              RedrawingDisabled = temp;
   4065 
   4066              // restore the line
   4067              if (orig_line != NULL) {
   4068                ml_replace(lnum, orig_line, false);
   4069              }
   4070            }
   4071 
   4072            need_wait_return = false;             // no hit-return prompt
   4073            if (typed == 'q' || typed == ESC || typed == Ctrl_C) {
   4074              got_quit = true;
   4075              break;
   4076            }
   4077            if (typed == 'n') {
   4078              break;
   4079            }
   4080            if (typed == 'y') {
   4081              break;
   4082            }
   4083            if (typed == 'l') {
   4084              // last: replace and then stop
   4085              subflags.do_all = false;
   4086              line2 = lnum;
   4087              break;
   4088            }
   4089            if (typed == 'a') {
   4090              subflags.do_ask = false;
   4091              break;
   4092            }
   4093            if (typed == Ctrl_E) {
   4094              scrollup_clamp();
   4095            } else if (typed == Ctrl_Y) {
   4096              scrolldown_clamp();
   4097            }
   4098          }
   4099          State = save_State;
   4100          setmouse();
   4101          if (vim_strchr(p_cpo, CPO_UNDO) != NULL) {
   4102            no_u_sync--;
   4103          }
   4104 
   4105          if (typed == 'n') {
   4106            // For a multi-line match, put matchcol at the NUL at
   4107            // the end of the line and set nmatch to one, so that
   4108            // we continue looking for a match on the next line.
   4109            // Avoids that ":%s/\nB\@=//gc" and ":%s/\n/,\r/gc"
   4110            // get stuck when pressing 'n'.
   4111            if (nmatch > 1) {
   4112              matchcol = (colnr_T)strlen(sub_firstline);
   4113              skip_match = true;
   4114            }
   4115            goto skip;
   4116          }
   4117          if (got_quit) {
   4118            goto skip;
   4119          }
   4120        }
   4121 
   4122        // Move the cursor to the start of the match, so that we can
   4123        // use "\=col(".").
   4124        curwin->w_cursor.col = regmatch.startpos[0].col;
   4125 
   4126        // When the match included the "$" of the last line it may
   4127        // go beyond the last line of the buffer.
   4128        if (nmatch > curbuf->b_ml.ml_line_count - sub_firstlnum + 1) {
   4129          nmatch = curbuf->b_ml.ml_line_count - sub_firstlnum + 1;
   4130          current_match.end.lnum = sub_firstlnum + (linenr_T)nmatch;
   4131          skip_match = true;
   4132          // safety check
   4133          if (nmatch < 0) {
   4134            goto skip;
   4135          }
   4136        }
   4137 
   4138        // Save the line numbers for the preview buffer
   4139        // NOTE: If the pattern matches a final newline, the next line will
   4140        // be shown also, but should not be highlighted. Intentional for now.
   4141        if (cmdpreview_ns > 0 && !has_second_delim) {
   4142          current_match.start.col = regmatch.startpos[0].col;
   4143          if (current_match.end.lnum == 0) {
   4144            current_match.end.lnum = sub_firstlnum + (linenr_T)nmatch - 1;
   4145          }
   4146          current_match.end.col = regmatch.endpos[0].col;
   4147 
   4148          ADJUST_SUB_FIRSTLNUM();
   4149          lnum += (linenr_T)nmatch - 1;
   4150 
   4151          goto skip;
   4152        }
   4153 
   4154        // 3. Substitute the string. During 'inccommand' preview only do this if
   4155        //    there is a replace pattern.
   4156        if (cmdpreview_ns <= 0 || has_second_delim) {
   4157          lnum_start = lnum;  // save the start lnum
   4158          int save_ma = curbuf->b_p_ma;
   4159          int save_sandbox = sandbox;
   4160          if (subflags.do_count) {
   4161            // prevent accidentally changing the buffer by a function
   4162            curbuf->b_p_ma = false;
   4163            sandbox++;
   4164          }
   4165          // Save flags for recursion.  They can change for e.g.
   4166          // :s/^/\=execute("s#^##gn")
   4167          subflags_T subflags_save = subflags;
   4168 
   4169          // Disallow changing text or switching window in an expression.
   4170          textlock++;
   4171          // Get length of substitution part, including the NUL.
   4172          // When it fails sublen is zero.
   4173          sublen = vim_regsub_multi(&regmatch,
   4174                                    sub_firstlnum - regmatch.startpos[0].lnum,
   4175                                    sub, sub_firstline, 0,
   4176                                    REGSUB_BACKSLASH
   4177                                    | (magic_isset() ? REGSUB_MAGIC : 0));
   4178          textlock--;
   4179 
   4180          // If getting the substitute string caused an error, don't do
   4181          // the replacement.
   4182          // Don't keep flags set by a recursive call
   4183          subflags = subflags_save;
   4184          if (sublen == 0 || aborting() || subflags.do_count) {
   4185            curbuf->b_p_ma = save_ma;
   4186            sandbox = save_sandbox;
   4187            goto skip;
   4188          }
   4189 
   4190          // Need room for:
   4191          // - result so far in new_start (not for first sub in line)
   4192          // - original text up to match
   4193          // - length of substituted part
   4194          // - original text after match
   4195          if (nmatch == 1) {
   4196            p1 = sub_firstline;
   4197          } else {
   4198            linenr_T lastlnum = sub_firstlnum + (linenr_T)nmatch - 1;
   4199            p1 = ml_get(lastlnum);
   4200            nmatch_tl += nmatch - 1;
   4201          }
   4202          int copy_len = regmatch.startpos[0].col - copycol;
   4203          new_end = sub_grow_buf(&new_start, &new_start_len,
   4204                                 (colnr_T)strlen(p1) - regmatch.endpos[0].col
   4205                                 + copy_len + sublen + 1);
   4206 
   4207          // copy the text up to the part that matched
   4208          memmove(new_end, sub_firstline + copycol, (size_t)copy_len);
   4209          new_end += copy_len;
   4210 
   4211          if (new_start_len - copy_len < sublen) {
   4212            sublen = new_start_len - copy_len - 1;
   4213          }
   4214 
   4215          // Finally, at this point we can know where the match actually will
   4216          // start in the new text
   4217          int start_col = (int)(new_end - new_start);
   4218          current_match.start.col = start_col;
   4219 
   4220          textlock++;
   4221          vim_regsub_multi(&regmatch,
   4222                           sub_firstlnum - regmatch.startpos[0].lnum,
   4223                           sub, new_end, sublen,
   4224                           REGSUB_COPY | REGSUB_BACKSLASH
   4225                           | (magic_isset() ? REGSUB_MAGIC : 0));
   4226          textlock--;
   4227          sub_nsubs++;
   4228          did_sub = true;
   4229 
   4230          // Move the cursor to the start of the line, to avoid that it
   4231          // is beyond the end of the line after the substitution.
   4232          curwin->w_cursor.col = 0;
   4233 
   4234          // Remember next character to be copied.
   4235          copycol = regmatch.endpos[0].col;
   4236 
   4237          ADJUST_SUB_FIRSTLNUM();
   4238 
   4239          // TODO(bfredl): this has some robustness issues, look into later.
   4240          bcount_t replaced_bytes = 0;
   4241          lpos_T start = regmatch.startpos[0];
   4242          lpos_T end = regmatch.endpos[0];
   4243          for (i = 0; i < nmatch - 1; i++) {
   4244            replaced_bytes += (bcount_t)strlen(ml_get((linenr_T)(lnum_start + i))) + 1;
   4245          }
   4246          replaced_bytes += end.col - start.col;
   4247 
   4248          // Save the line number before processing newlines.
   4249          linenr_T lnum_before_newlines = lnum;
   4250 
   4251          // Now the trick is to replace CTRL-M chars with a real line
   4252          // break.  This would make it impossible to insert a CTRL-M in
   4253          // the text.  The line break can be avoided by preceding the
   4254          // CTRL-M with a backslash.  To be able to insert a backslash,
   4255          // they must be doubled in the string and are halved here.
   4256          // That is Vi compatible.
   4257          for (p1 = new_end; *p1; p1++) {
   4258            if (p1[0] == '\\' && p1[1] != NUL) {            // remove backslash
   4259              sublen--;  // correct the byte counts for extmark_splice()
   4260              STRMOVE(p1, p1 + 1);
   4261            } else if (*p1 == CAR) {
   4262              if (u_inssub(lnum) == OK) {             // prepare for undo
   4263                *p1 = NUL;                            // truncate up to the CR
   4264                ml_append(lnum - 1, new_start,
   4265                          (colnr_T)(p1 - new_start + 1), false);
   4266                mark_adjust(lnum + 1, (linenr_T)MAXLNUM, 1, 0, kExtmarkNOOP);
   4267 
   4268                if (subflags.do_ask) {
   4269                  appended_lines(lnum - 1, 1);
   4270                } else {
   4271                  if (first_line == 0) {
   4272                    first_line = lnum;
   4273                  }
   4274                  last_line = lnum + 1;
   4275                }
   4276                // All line numbers increase.
   4277                sub_firstlnum++;
   4278                lnum++;
   4279                line2++;
   4280                // move the cursor to the new line, like Vi
   4281                curwin->w_cursor.lnum++;
   4282                // copy the rest
   4283                STRMOVE(new_start, p1 + 1);
   4284                p1 = new_start - 1;
   4285              }
   4286            } else {
   4287              p1 += utfc_ptr2len(p1) - 1;
   4288            }
   4289          }
   4290          colnr_T new_endcol = (colnr_T)strlen(new_start);
   4291          current_match.end.col = new_endcol;
   4292          current_match.end.lnum = lnum;
   4293 
   4294          int matchcols = end.col - ((end.lnum == start.lnum)
   4295                                     ? start.col : 0);
   4296          int subcols = new_endcol - ((lnum == lnum_start) ? start_col : 0);
   4297          if (!did_save) {
   4298            // Required for Undo to work for extmarks.
   4299            u_save_cursor();
   4300            did_save = true;
   4301          }
   4302 
   4303          // Store extmark data for this match.
   4304          LineData *data = kv_pushp(line_matches);
   4305          data->start_col = start_col;
   4306          data->start = start;
   4307          data->end = end;
   4308          data->matchcols = matchcols;
   4309          data->matchbytes = replaced_bytes;
   4310          data->subcols = subcols;
   4311          data->subbytes = sublen - 1;
   4312          data->lnum_before = lnum_before_newlines;
   4313          data->lnum_after = lnum;
   4314        }
   4315 
   4316        // 4. If subflags.do_all is set, find next match.
   4317        // Prevent endless loop with patterns that match empty
   4318        // strings, e.g. :s/$/pat/g or :s/[a-z]* /(&)/g.
   4319        // But ":s/\n/#/" is OK.
   4320 skip:
   4321        // We already know that we did the last subst when we are at
   4322        // the end of the line, except that a pattern like
   4323        // "bar\|\nfoo" may match at the NUL.  "lnum" can be below
   4324        // "line2" when there is a \zs in the pattern after a line
   4325        // break.
   4326        lastone = (skip_match
   4327                   || got_int
   4328                   || got_quit
   4329                   || lnum > line2
   4330                   || !(subflags.do_all || do_again)
   4331                   || (sub_firstline[matchcol] == NUL && nmatch <= 1
   4332                       && !re_multiline(regmatch.regprog)));
   4333        nmatch = -1;
   4334 
   4335        // Replace the line in the buffer when needed.  This is
   4336        // skipped when there are more matches.
   4337        // The check for nmatch_tl is needed for when multi-line
   4338        // matching must replace the lines before trying to do another
   4339        // match, otherwise "\@<=" won't work.
   4340        // When the match starts below where we start searching also
   4341        // need to replace the line first (using \zs after \n).
   4342        if (lastone
   4343            || nmatch_tl > 0
   4344            || (nmatch = vim_regexec_multi(&regmatch, curwin,
   4345                                           curbuf, sub_firstlnum,
   4346                                           matchcol, NULL, NULL)) == 0
   4347            || regmatch.startpos[0].lnum > 0) {
   4348          if (new_start != NULL) {
   4349            // Copy the rest of the line, that didn't match.
   4350            // "matchcol" has to be adjusted, we use the end of
   4351            // the line as reference, because the substitute may
   4352            // have changed the number of characters.  Same for
   4353            // "prev_matchcol".
   4354            strcat(new_start, sub_firstline + copycol);
   4355            matchcol = (colnr_T)strlen(sub_firstline) - matchcol;
   4356            prev_matchcol = (colnr_T)strlen(sub_firstline)
   4357                            - prev_matchcol;
   4358 
   4359            if (u_savesub(lnum) != OK) {
   4360              break;
   4361            }
   4362            ml_replace(lnum, new_start, true);
   4363 
   4364            // Call extmark_splice for each match on this line.
   4365            for (size_t match_idx = 0; match_idx < kv_size(line_matches); match_idx++) {
   4366              LineData *match = &kv_A(line_matches, match_idx);
   4367 
   4368              extmark_splice(curbuf, (int)match->lnum_before - 1, match->start_col,
   4369                             match->end.lnum - match->start.lnum, match->matchcols,
   4370                             match->matchbytes,
   4371                             match->lnum_after - match->lnum_before,
   4372                             match->subcols,
   4373                             match->subbytes, kExtmarkUndo);
   4374            }
   4375 
   4376            // Reset the match data for the next line.
   4377            kv_size(line_matches) = 0;
   4378 
   4379            if (nmatch_tl > 0) {
   4380              // Matched lines have now been substituted and are
   4381              // useless, delete them.  The part after the match
   4382              // has been appended to new_start, we don't need
   4383              // it in the buffer.
   4384              lnum++;
   4385              if (u_savedel(lnum, nmatch_tl) != OK) {
   4386                break;
   4387              }
   4388              for (i = 0; i < nmatch_tl; i++) {
   4389                ml_delete(lnum);
   4390              }
   4391              mark_adjust(lnum, lnum + nmatch_tl - 1, MAXLNUM, -nmatch_tl, kExtmarkNOOP);
   4392              if (subflags.do_ask) {
   4393                deleted_lines(lnum, nmatch_tl);
   4394              }
   4395              lnum--;
   4396              line2 -= nmatch_tl;  // nr of lines decreases
   4397              nmatch_tl = 0;
   4398            }
   4399 
   4400            // When asking, undo is saved each time, must also set
   4401            // changed flag each time.
   4402            if (subflags.do_ask) {
   4403              changed_bytes(lnum, 0);
   4404            } else {
   4405              if (first_line == 0) {
   4406                first_line = lnum;
   4407              }
   4408              last_line = lnum + 1;
   4409            }
   4410 
   4411            sub_firstlnum = lnum;
   4412            xfree(sub_firstline);                // free the temp buffer
   4413            sub_firstline = new_start;
   4414            new_start = NULL;
   4415            matchcol = (colnr_T)strlen(sub_firstline) - matchcol;
   4416            prev_matchcol = (colnr_T)strlen(sub_firstline)
   4417                            - prev_matchcol;
   4418            copycol = 0;
   4419          }
   4420          if (nmatch == -1 && !lastone) {
   4421            nmatch = vim_regexec_multi(&regmatch, curwin, curbuf,
   4422                                       sub_firstlnum, matchcol, NULL, NULL);
   4423          }
   4424 
   4425          // 5. break if there isn't another match in this line
   4426          if (nmatch <= 0) {
   4427            // If the match found didn't start where we were
   4428            // searching, do the next search in the line where we
   4429            // found the match.
   4430            if (nmatch == -1) {
   4431              lnum -= regmatch.startpos[0].lnum;
   4432            }
   4433 
   4434            // uncrustify:off
   4435 
   4436 #define PUSH_PREVIEW_LINES() \
   4437  do { \
   4438    if (cmdpreview_ns > 0) { \
   4439      linenr_T match_lines = current_match.end.lnum \
   4440                             - current_match.start.lnum +1; \
   4441      if (preview_lines.subresults.size > 0) { \
   4442        linenr_T last = kv_last(preview_lines.subresults).end.lnum; \
   4443        if (last == current_match.start.lnum) { \
   4444          preview_lines.lines_needed += match_lines - 1; \
   4445        } else { \
   4446          preview_lines.lines_needed += match_lines; \
   4447        } \
   4448      } else { \
   4449        preview_lines.lines_needed += match_lines; \
   4450      } \
   4451      kv_push(preview_lines.subresults, current_match); \
   4452    } \
   4453  } while (0)
   4454 
   4455            // uncrustify:on
   4456 
   4457            // Push the match to preview_lines.
   4458            PUSH_PREVIEW_LINES();
   4459 
   4460            break;
   4461          }
   4462        }
   4463        // Push the match to preview_lines.
   4464        PUSH_PREVIEW_LINES();
   4465 
   4466        line_breakcheck();
   4467      }
   4468 
   4469      if (did_sub) {
   4470        sub_nlines++;
   4471      }
   4472      xfree(new_start);              // for when substitute was cancelled
   4473      XFREE_CLEAR(sub_firstline);    // free the copy of the original line
   4474      kv_destroy(line_matches);      // clean up match data
   4475    }
   4476 
   4477    line_breakcheck();
   4478 
   4479    if (profile_passed_limit(timeout)) {
   4480      got_quit = true;
   4481    }
   4482  }
   4483 
   4484  curbuf->deleted_bytes2 = 0;
   4485 
   4486  if (first_line != 0) {
   4487    // Need to subtract the number of added lines from "last_line" to get
   4488    // the line number before the change (same as adding the number of
   4489    // deleted lines).
   4490    i = curbuf->b_ml.ml_line_count - old_line_count;
   4491    changed_lines(curbuf, first_line, 0, last_line - (linenr_T)i, (linenr_T)i, false);
   4492 
   4493    int64_t num_added = last_line - first_line;
   4494    int64_t num_removed = num_added - i;
   4495    buf_updates_send_changes(curbuf, first_line, num_added, num_removed);
   4496  }
   4497 
   4498  xfree(sub_firstline);   // may have to free allocated copy of the line
   4499 
   4500  // ":s/pat//n" doesn't move the cursor
   4501  if (subflags.do_count) {
   4502    curwin->w_cursor = old_cursor;
   4503  }
   4504 
   4505  if (sub_nsubs > start_nsubs) {
   4506    if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
   4507      // Set the '[ and '] marks.
   4508      curbuf->b_op_start.lnum = eap->line1;
   4509      curbuf->b_op_end.lnum = line2;
   4510      curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
   4511    }
   4512 
   4513    if (!global_busy) {
   4514      // when interactive leave cursor on the match
   4515      if (!subflags.do_ask) {
   4516        if (endcolumn) {
   4517          coladvance(curwin, MAXCOL);
   4518        } else {
   4519          beginline(BL_WHITE | BL_FIX);
   4520        }
   4521      }
   4522      if (cmdpreview_ns <= 0 && !do_sub_msg(subflags.do_count) && subflags.do_ask && p_ch > 0) {
   4523        msg("", 0);
   4524      }
   4525    } else {
   4526      global_need_beginline = true;
   4527    }
   4528    if (subflags.do_print) {
   4529      print_line(curwin->w_cursor.lnum, subflags.do_number, subflags.do_list, true);
   4530    }
   4531  } else if (!global_busy) {
   4532    if (got_int) {
   4533      // interrupted
   4534      emsg(_(e_interr));
   4535    } else if (got_match) {
   4536      // did find something but nothing substituted
   4537      if (p_ch > 0 && !ui_has(kUIMessages)) {
   4538        msg("", 0);
   4539      }
   4540    } else if (subflags.do_error) {
   4541      // nothing found
   4542      semsg(_(e_patnotf2), get_search_pat());
   4543    }
   4544  }
   4545 
   4546  if (subflags.do_ask && hasAnyFolding(curwin)) {
   4547    // Cursor position may require updating
   4548    changed_window_setting(curwin);
   4549  }
   4550 
   4551  vim_regfree(regmatch.regprog);
   4552  xfree(sub);
   4553 
   4554  // Restore the flag values, they can be used for ":&&".
   4555  subflags.do_all = save_do_all;
   4556  subflags.do_ask = save_do_ask;
   4557 
   4558  int retv = 0;
   4559 
   4560  // Show 'inccommand' preview if there are matched lines.
   4561  if (cmdpreview_ns > 0 && !aborting()) {
   4562    if (got_quit || profile_passed_limit(timeout)) {  // Too slow, disable.
   4563      set_option_direct(kOptInccommand, STATIC_CSTR_AS_OPTVAL(""), 0, SID_NONE);
   4564    } else if (*p_icm != NUL && pat != NULL) {
   4565      if (pre_hl_id == 0) {
   4566        pre_hl_id = syn_check_group(S_LEN("Substitute"));
   4567      }
   4568      retv = show_sub(eap, old_cursor, &preview_lines, pre_hl_id, cmdpreview_ns, cmdpreview_bufnr);
   4569    }
   4570  }
   4571 
   4572  kv_destroy(preview_lines.subresults);
   4573  return retv;
   4574 #undef ADJUST_SUB_FIRSTLNUM
   4575 #undef PUSH_PREVIEW_LINES
   4576 }
   4577 
   4578 /// Give message for number of substitutions.
   4579 /// Can also be used after a ":global" command.
   4580 ///
   4581 /// @param count_only  used 'n' flag for ":s"
   4582 ///
   4583 /// @return            true if a message was given.
   4584 bool do_sub_msg(bool count_only)
   4585 {
   4586  // Only report substitutions when:
   4587  // - more than 'report' substitutions
   4588  // - command was typed by user, or number of changed lines > 'report'
   4589  // - giving messages is not disabled by 'lazyredraw'
   4590  if (((sub_nsubs > p_report && (KeyTyped || sub_nlines > 1 || p_report < 1))
   4591       || count_only)
   4592      && messaging()) {
   4593    if (got_int) {
   4594      STRCPY(msg_buf, _("(Interrupted) "));
   4595    } else {
   4596      *msg_buf = NUL;
   4597    }
   4598 
   4599    char *msg_single = count_only
   4600                       ? NGETTEXT("%" PRId64 " match on %" PRId64 " line",
   4601                                  "%" PRId64 " matches on %" PRId64 " line", sub_nsubs)
   4602                       : NGETTEXT("%" PRId64 " substitution on %" PRId64 " line",
   4603                                  "%" PRId64 " substitutions on %" PRId64 " line", sub_nsubs);
   4604    char *msg_plural = count_only
   4605                       ? NGETTEXT("%" PRId64 " match on %" PRId64 " lines",
   4606                                  "%" PRId64 " matches on %" PRId64 " lines", sub_nsubs)
   4607                       : NGETTEXT("%" PRId64 " substitution on %" PRId64 " lines",
   4608                                  "%" PRId64 " substitutions on %" PRId64 " lines", sub_nsubs);
   4609    vim_snprintf_add(msg_buf, sizeof(msg_buf),
   4610                     NGETTEXT(msg_single, msg_plural, sub_nlines),
   4611                     (int64_t)sub_nsubs, (int64_t)sub_nlines);
   4612    if (msg(msg_buf, 0)) {
   4613      // save message to display it after redraw
   4614      set_keep_msg(msg_buf, 0);
   4615    }
   4616    return true;
   4617  }
   4618  if (got_int) {
   4619    emsg(_(e_interr));
   4620    return true;
   4621  }
   4622  return false;
   4623 }
   4624 
   4625 static void global_exe_one(char *const cmd, const linenr_T lnum)
   4626 {
   4627  curwin->w_cursor.lnum = lnum;
   4628  curwin->w_cursor.col = 0;
   4629  if (*cmd == NUL || *cmd == '\n') {
   4630    do_cmdline("p", NULL, NULL, DOCMD_NOWAIT);
   4631  } else {
   4632    do_cmdline(cmd, NULL, NULL, DOCMD_NOWAIT);
   4633  }
   4634 }
   4635 
   4636 /// Execute a global command of the form:
   4637 ///
   4638 /// g/pattern/X : execute X on all lines where pattern matches
   4639 /// v/pattern/X : execute X on all lines where pattern does not match
   4640 ///
   4641 /// where 'X' is an EX command
   4642 ///
   4643 /// The command character (as well as the trailing slash) is optional, and
   4644 /// is assumed to be 'p' if missing.
   4645 ///
   4646 /// This is implemented in two passes: first we scan the file for the pattern and
   4647 /// set a mark for each line that (not) matches. Secondly we execute the command
   4648 /// for each line that has a mark. This is required because after deleting
   4649 /// lines we do not know where to search for the next match.
   4650 void ex_global(exarg_T *eap)
   4651 {
   4652  linenr_T lnum;                // line number according to old situation
   4653  int type;                     // first char of cmd: 'v' or 'g'
   4654  char *cmd;                    // command argument
   4655 
   4656  char delim;                 // delimiter, normally '/'
   4657  char *pat;
   4658  size_t patlen;
   4659  regmmatch_T regmatch;
   4660 
   4661  // When nesting the command works on one line.  This allows for
   4662  // ":g/found/v/notfound/command".
   4663  if (global_busy && (eap->line1 != 1
   4664                      || eap->line2 != curbuf->b_ml.ml_line_count)) {
   4665    // will increment global_busy to break out of the loop
   4666    emsg(_("E147: Cannot do :global recursive with a range"));
   4667    return;
   4668  }
   4669 
   4670  if (eap->forceit) {               // ":global!" is like ":vglobal"
   4671    type = 'v';
   4672  } else {
   4673    type = (uint8_t)(*eap->cmd);
   4674  }
   4675  cmd = eap->arg;
   4676  int which_pat = RE_LAST;              // default: use last used regexp
   4677 
   4678  // undocumented vi feature:
   4679  //    "\/" and "\?": use previous search pattern.
   4680  //             "\&": use previous substitute pattern.
   4681  if (*cmd == '\\') {
   4682    cmd++;
   4683    if (vim_strchr("/?&", (uint8_t)(*cmd)) == NULL) {
   4684      emsg(_(e_backslash));
   4685      return;
   4686    }
   4687    if (*cmd == '&') {
   4688      which_pat = RE_SUBST;             // use previous substitute pattern
   4689    } else {
   4690      which_pat = RE_SEARCH;            // use previous search pattern
   4691    }
   4692    cmd++;
   4693    pat = "";
   4694    patlen = 0;
   4695  } else if (*cmd == NUL) {
   4696    emsg(_("E148: Regular expression missing from global"));
   4697    return;
   4698  } else if (check_regexp_delim(*cmd) == FAIL) {
   4699    return;
   4700  } else {
   4701    delim = *cmd;               // get the delimiter
   4702    cmd++;                      // skip delimiter if there is one
   4703    pat = cmd;                  // remember start of pattern
   4704    cmd = skip_regexp_ex(cmd, delim, magic_isset(), &eap->arg, NULL, NULL);
   4705    if (cmd[0] == delim) {                  // end delimiter found
   4706      *cmd++ = NUL;                         // replace it with a NUL
   4707    }
   4708    patlen = strlen(pat);
   4709  }
   4710 
   4711  char *used_pat;
   4712  if (search_regcomp(pat, patlen, &used_pat, RE_BOTH, which_pat,
   4713                     SEARCH_HIS, &regmatch) == FAIL) {
   4714    emsg(_(e_invcmd));
   4715    return;
   4716  }
   4717 
   4718  if (global_busy) {
   4719    lnum = curwin->w_cursor.lnum;
   4720    int match = vim_regexec_multi(&regmatch, curwin, curbuf, lnum, 0, NULL, NULL);
   4721    if ((type == 'g' && match) || (type == 'v' && !match)) {
   4722      global_exe_one(cmd, lnum);
   4723    }
   4724  } else {
   4725    int ndone = 0;
   4726    // pass 1: set marks for each (not) matching line
   4727    for (lnum = eap->line1; lnum <= eap->line2 && !got_int; lnum++) {
   4728      // a match on this line?
   4729      int match = vim_regexec_multi(&regmatch, curwin, curbuf, lnum, 0, NULL, NULL);
   4730      if (regmatch.regprog == NULL) {
   4731        break;  // re-compiling regprog failed
   4732      }
   4733      if ((type == 'g' && match) || (type == 'v' && !match)) {
   4734        ml_setmarked(lnum);
   4735        ndone++;
   4736      }
   4737      line_breakcheck();
   4738    }
   4739 
   4740    // pass 2: execute the command for each line that has been marked
   4741    if (got_int) {
   4742      msg(_(e_interr), 0);
   4743    } else if (ndone == 0) {
   4744      if (type == 'v') {
   4745        smsg(0, _("Pattern found in every line: %s"), used_pat);
   4746      } else {
   4747        smsg(0, _("Pattern not found: %s"), used_pat);
   4748      }
   4749    } else {
   4750      global_exe(cmd);
   4751    }
   4752    ml_clearmarked();         // clear rest of the marks
   4753  }
   4754  vim_regfree(regmatch.regprog);
   4755 }
   4756 
   4757 /// Execute `cmd` on lines marked with ml_setmarked().
   4758 void global_exe(char *cmd)
   4759 {
   4760  linenr_T old_lcount;      // b_ml.ml_line_count before the command
   4761  buf_T *old_buf = curbuf;  // remember what buffer we started in
   4762  linenr_T lnum;            // line number according to old situation
   4763 
   4764  // Set current position only once for a global command.
   4765  // If global_busy is set, setpcmark() will not do anything.
   4766  // If there is an error, global_busy will be incremented.
   4767  setpcmark();
   4768 
   4769  // When the command writes a message, don't overwrite the command.
   4770  msg_didout = true;
   4771 
   4772  sub_nsubs = 0;
   4773  sub_nlines = 0;
   4774  global_need_msg_kind = true;
   4775  global_need_beginline = false;
   4776  global_busy = 1;
   4777  old_lcount = curbuf->b_ml.ml_line_count;
   4778 
   4779  while (!got_int && (lnum = ml_firstmarked()) != 0 && global_busy == 1) {
   4780    global_exe_one(cmd, lnum);
   4781    os_breakcheck();
   4782  }
   4783 
   4784  global_busy = 0;
   4785  if (global_need_beginline) {
   4786    beginline(BL_WHITE | BL_FIX);
   4787  } else {
   4788    check_cursor(curwin);  // cursor may be beyond the end of the line
   4789  }
   4790 
   4791  // the cursor may not have moved in the text but a change in a previous
   4792  // line may move it on the screen
   4793  changed_line_abv_curs();
   4794 
   4795  // If it looks like no message was written, allow overwriting the
   4796  // command with the report for number of changes.
   4797  if (msg_col == 0 && msg_scrolled == 0) {
   4798    msg_didout = false;
   4799  }
   4800 
   4801  // If substitutes done, report number of substitutes, otherwise report
   4802  // number of extra or deleted lines.
   4803  // Don't report extra or deleted lines in the edge case where the buffer
   4804  // we are in after execution is different from the buffer we started in.
   4805  if (!do_sub_msg(false) && curbuf == old_buf) {
   4806    msgmore(curbuf->b_ml.ml_line_count - old_lcount);
   4807  }
   4808 }
   4809 
   4810 #if defined(EXITFREE)
   4811 void free_old_sub(void)
   4812 {
   4813  sub_set_replacement((SubReplacementString) { NULL, 0, NULL });
   4814 }
   4815 
   4816 #endif
   4817 
   4818 /// Set up for a tagpreview.
   4819 ///
   4820 /// @param undo_sync  sync undo when leaving the window
   4821 ///
   4822 /// @return           true when it was created.
   4823 bool prepare_tagpreview(bool undo_sync)
   4824 {
   4825  if (curwin->w_p_pvw) {
   4826    return false;
   4827  }
   4828 
   4829  // If there is already a preview window open, use that one.
   4830  FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
   4831    if (wp->w_p_pvw) {
   4832      win_enter(wp, undo_sync);
   4833      return false;
   4834    }
   4835  }
   4836 
   4837  // There is no preview window open yet.  Create one.
   4838  if (win_split(g_do_tagpreview > 0 ? g_do_tagpreview : 0, 0)
   4839      == FAIL) {
   4840    return false;
   4841  }
   4842  curwin->w_p_pvw = true;
   4843  curwin->w_p_wfh = true;
   4844  RESET_BINDING(curwin);                // don't take over 'scrollbind' and 'cursorbind'
   4845  curwin->w_p_diff = false;             // no 'diff'
   4846 
   4847  set_option_direct(kOptFoldcolumn, STATIC_CSTR_AS_OPTVAL("0"), 0, SID_NONE);  // no 'foldcolumn'
   4848  return true;
   4849 }
   4850 
   4851 /// Shows the effects of the :substitute command being typed ('inccommand').
   4852 /// If inccommand=split, shows a preview window and later restores the layout.
   4853 ///
   4854 /// @return 1 if preview window isn't needed, 2 if preview window is needed.
   4855 static int show_sub(exarg_T *eap, pos_T old_cusr, PreviewLines *preview_lines, int hl_id,
   4856                    int cmdpreview_ns, handle_T cmdpreview_bufnr)
   4857  FUNC_ATTR_NONNULL_ALL
   4858 {
   4859  char *save_shm_p = xstrdup(p_shm);
   4860  PreviewLines lines = *preview_lines;
   4861  buf_T *orig_buf = curbuf;
   4862  // We keep a special-purpose buffer around, but don't assume it exists.
   4863  buf_T *cmdpreview_buf = NULL;
   4864 
   4865  // disable file info message
   4866  set_option_direct(kOptShortmess, STATIC_CSTR_AS_OPTVAL("F"), 0, SID_NONE);
   4867 
   4868  // Place cursor on nearest matching line, to undo do_sub() cursor placement.
   4869  for (size_t i = 0; i < lines.subresults.size; i++) {
   4870    SubResult curres = lines.subresults.items[i];
   4871    if (curres.start.lnum >= old_cusr.lnum) {
   4872      curwin->w_cursor.lnum = curres.start.lnum;
   4873      curwin->w_cursor.col = curres.start.col;
   4874      break;
   4875    }  // Else: All matches are above, do_sub() already placed cursor.
   4876  }
   4877 
   4878  // Update the topline to ensure that main window is on the correct line
   4879  update_topline(curwin);
   4880 
   4881  // Width of the "| lnum|..." column which displays the line numbers.
   4882  int col_width = 0;
   4883  // Use preview window only when inccommand=split and range is not just the current line
   4884  bool preview = (*p_icm == 's') && (eap->line1 != old_cusr.lnum || eap->line2 != old_cusr.lnum);
   4885 
   4886  if (preview) {
   4887    cmdpreview_buf = buflist_findnr(cmdpreview_bufnr);
   4888    assert(cmdpreview_buf != NULL);
   4889 
   4890    if (lines.subresults.size > 0) {
   4891      SubResult last_match = kv_last(lines.subresults);
   4892      // `last_match.end.lnum` may be 0 when using 'n' flag.
   4893      linenr_T highest_lnum = MAX(last_match.start.lnum, last_match.end.lnum);
   4894      assert(highest_lnum > 0);
   4895      col_width = (int)log10(highest_lnum) + 1 + 3;
   4896    }
   4897  }
   4898 
   4899  char *str = NULL;  // construct the line to show in here
   4900  colnr_T old_line_size = 0;
   4901  colnr_T line_size = 0;
   4902  linenr_T linenr_preview = 0;  // last line added to preview buffer
   4903  linenr_T linenr_origbuf = 0;  // last line added to original buffer
   4904  linenr_T next_linenr = 0;     // next line to show for the match
   4905 
   4906  for (size_t matchidx = 0; matchidx < lines.subresults.size; matchidx++) {
   4907    SubResult match = lines.subresults.items[matchidx];
   4908 
   4909    if (cmdpreview_buf) {
   4910      lpos_T p_start = { 0, match.start.col };  // match starts here in preview
   4911      lpos_T p_end = { 0, match.end.col };    // ... and ends here
   4912 
   4913      // You Might Gonna Need It
   4914      buf_ensure_loaded(cmdpreview_buf);
   4915 
   4916      if (match.pre_match == 0) {
   4917        next_linenr = match.start.lnum;
   4918      } else {
   4919        next_linenr = match.pre_match;
   4920      }
   4921      // Don't add a line twice
   4922      if (next_linenr == linenr_origbuf) {
   4923        next_linenr++;
   4924        p_start.lnum = linenr_preview;  // might be redefined below
   4925        p_end.lnum = linenr_preview;  // might be redefined below
   4926      }
   4927 
   4928      for (; next_linenr <= match.end.lnum; next_linenr++) {
   4929        if (next_linenr == match.start.lnum) {
   4930          p_start.lnum = linenr_preview + 1;
   4931        }
   4932        if (next_linenr == match.end.lnum) {
   4933          p_end.lnum = linenr_preview + 1;
   4934        }
   4935        char *line;
   4936        if (next_linenr == orig_buf->b_ml.ml_line_count + 1) {
   4937          line = "";
   4938        } else {
   4939          line = ml_get_buf(orig_buf, next_linenr);
   4940          line_size = ml_get_buf_len(orig_buf, next_linenr) + col_width + 1;
   4941 
   4942          // Reallocate if line not long enough
   4943          if (line_size > old_line_size) {
   4944            str = xrealloc(str, (size_t)line_size * sizeof(char));
   4945            old_line_size = line_size;
   4946          }
   4947        }
   4948        // Put "|lnum| line" into `str` and append it to the preview buffer.
   4949        snprintf(str, (size_t)line_size, "|%*" PRIdLINENR "| %s", col_width - 3,
   4950                 next_linenr, line);
   4951        if (linenr_preview == 0) {
   4952          ml_replace_buf(cmdpreview_buf, 1, str, true, false);
   4953        } else {
   4954          ml_append_buf(cmdpreview_buf, linenr_preview, str, line_size, false);
   4955        }
   4956        linenr_preview += 1;
   4957      }
   4958      linenr_origbuf = match.end.lnum;
   4959 
   4960      bufhl_add_hl_pos_offset(cmdpreview_buf, cmdpreview_ns, hl_id, p_start, p_end, col_width);
   4961    }
   4962    bufhl_add_hl_pos_offset(orig_buf, cmdpreview_ns, hl_id, match.start, match.end, 0);
   4963  }
   4964 
   4965  xfree(str);
   4966 
   4967  set_option_direct(kOptShortmess, CSTR_AS_OPTVAL(save_shm_p), 0, SID_NONE);
   4968  xfree(save_shm_p);
   4969 
   4970  return preview ? 2 : 1;
   4971 }
   4972 
   4973 /// :substitute command.
   4974 void ex_substitute(exarg_T *eap)
   4975 {
   4976  do_sub(eap, profile_zero(), 0, 0);
   4977 }
   4978 
   4979 /// :substitute command preview callback.
   4980 int ex_substitute_preview(exarg_T *eap, int cmdpreview_ns, handle_T cmdpreview_bufnr)
   4981 {
   4982  // Only preview once the pattern delimiter has been typed
   4983  if (*eap->arg && !ASCII_ISALNUM(*eap->arg)) {
   4984    char *save_eap = eap->arg;
   4985    int retv = do_sub(eap, profile_setlimit(p_rdt), cmdpreview_ns, cmdpreview_bufnr);
   4986    eap->arg = save_eap;
   4987    return retv;
   4988  }
   4989 
   4990  return 0;
   4991 }
   4992 
   4993 /// Skip over the pattern argument of ":vimgrep /pat/[g][j]".
   4994 /// Put the start of the pattern in "*s", unless "s" is NULL.
   4995 ///
   4996 /// @param flags  if not NULL, put the flags in it: VGR_GLOBAL, VGR_NOJUMP.
   4997 /// @param s      if not NULL, terminate the pattern with a NUL.
   4998 ///
   4999 /// @return  a pointer to the char just past the pattern plus flags.
   5000 char *skip_vimgrep_pat(char *p, char **s, int *flags)
   5001 {
   5002  if (vim_isIDc((uint8_t)(*p))) {
   5003    // ":vimgrep pattern fname"
   5004    if (s != NULL) {
   5005      *s = p;
   5006    }
   5007    p = skiptowhite(p);
   5008    if (s != NULL && *p != NUL) {
   5009      *p++ = NUL;
   5010    }
   5011  } else {
   5012    // ":vimgrep /pattern/[g][j] fname"
   5013    if (s != NULL) {
   5014      *s = p + 1;
   5015    }
   5016    int c = (uint8_t)(*p);
   5017    p = skip_regexp(p + 1, c, true);
   5018    if (*p != c) {
   5019      return NULL;
   5020    }
   5021 
   5022    // Truncate the pattern.
   5023    if (s != NULL) {
   5024      *p = NUL;
   5025    }
   5026    p++;
   5027 
   5028    // Find the flags
   5029    while (*p == 'g' || *p == 'j' || *p == 'f') {
   5030      if (flags != NULL) {
   5031        if (*p == 'g') {
   5032          *flags |= VGR_GLOBAL;
   5033        } else if (*p == 'j') {
   5034          *flags |= VGR_NOJUMP;
   5035        } else {
   5036          *flags |= VGR_FUZZY;
   5037        }
   5038      }
   5039      p++;
   5040    }
   5041  }
   5042  return p;
   5043 }
   5044 
   5045 /// List v:oldfiles in a nice way.
   5046 void ex_oldfiles(exarg_T *eap)
   5047 {
   5048  list_T *l = get_vim_var_list(VV_OLDFILES);
   5049  int nr = 0;
   5050 
   5051  if (l == NULL) {
   5052    msg(_("No old files"), 0);
   5053    return;
   5054  }
   5055 
   5056  msg_start();
   5057  msg_scroll = true;
   5058  TV_LIST_ITER(l, li, {
   5059    if (got_int) {
   5060      break;
   5061    }
   5062    nr++;
   5063    const char *fname = tv_get_string(TV_LIST_ITEM_TV(li));
   5064    if (!message_filtered(fname)) {
   5065      msg_outnum(nr);
   5066      msg_puts(": ");
   5067      msg_outtrans(tv_get_string(TV_LIST_ITEM_TV(li)), 0, false);
   5068      msg_clr_eos();
   5069      msg_putchar('\n');
   5070      os_breakcheck();
   5071    }
   5072  });
   5073 
   5074  // Assume "got_int" was set to truncate the listing.
   5075  got_int = false;
   5076 
   5077  // File selection prompt on ":browse oldfiles"
   5078  if (cmdmod.cmod_flags & CMOD_BROWSE) {
   5079    quit_more = false;
   5080    nr = prompt_for_input(NULL, 0, false, NULL);
   5081    msg_starthere();
   5082    if (nr > 0 && nr <= tv_list_len(l)) {
   5083      const char *const p = tv_list_find_str(l, nr - 1);
   5084      if (p == NULL) {
   5085        return;
   5086      }
   5087      char *const s = expand_env_save((char *)p);
   5088      eap->arg = s;
   5089      eap->cmdidx = CMD_edit;
   5090      cmdmod.cmod_flags &= ~CMOD_BROWSE;
   5091      do_exedit(eap, NULL);
   5092      xfree(s);
   5093    }
   5094  }
   5095 }