neovim

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

change.c (75715B)


      1 /// change.c: functions related to changing text
      2 
      3 #include <assert.h>
      4 #include <stdbool.h>
      5 #include <stdint.h>
      6 #include <string.h>
      7 
      8 #include "nvim/ascii_defs.h"
      9 #include "nvim/assert_defs.h"
     10 #include "nvim/autocmd.h"
     11 #include "nvim/autocmd_defs.h"
     12 #include "nvim/buffer.h"
     13 #include "nvim/buffer_defs.h"
     14 #include "nvim/buffer_updates.h"
     15 #include "nvim/change.h"
     16 #include "nvim/charset.h"
     17 #include "nvim/cursor.h"
     18 #include "nvim/diff.h"
     19 #include "nvim/drawscreen.h"
     20 #include "nvim/edit.h"
     21 #include "nvim/eval/vars.h"
     22 #include "nvim/ex_cmds_defs.h"
     23 #include "nvim/extmark.h"
     24 #include "nvim/extmark_defs.h"
     25 #include "nvim/fold.h"
     26 #include "nvim/gettext_defs.h"
     27 #include "nvim/globals.h"
     28 #include "nvim/highlight_defs.h"
     29 #include "nvim/indent.h"
     30 #include "nvim/indent_c.h"
     31 #include "nvim/insexpand.h"
     32 #include "nvim/macros_defs.h"
     33 #include "nvim/mark.h"
     34 #include "nvim/mark_defs.h"
     35 #include "nvim/marktree_defs.h"
     36 #include "nvim/mbyte.h"
     37 #include "nvim/mbyte_defs.h"
     38 #include "nvim/memline.h"
     39 #include "nvim/memline_defs.h"
     40 #include "nvim/memory.h"
     41 #include "nvim/message.h"
     42 #include "nvim/move.h"
     43 #include "nvim/option.h"
     44 #include "nvim/option_vars.h"
     45 #include "nvim/os/time.h"
     46 #include "nvim/plines.h"
     47 #include "nvim/pos_defs.h"
     48 #include "nvim/search.h"
     49 #include "nvim/spell.h"
     50 #include "nvim/state.h"
     51 #include "nvim/state_defs.h"
     52 #include "nvim/strings.h"
     53 #include "nvim/textformat.h"
     54 #include "nvim/types_defs.h"
     55 #include "nvim/ui.h"
     56 #include "nvim/undo.h"
     57 #include "nvim/vim_defs.h"
     58 
     59 #include "change.c.generated.h"
     60 
     61 /// If the file is readonly, give a warning message with the first change.
     62 /// Don't do this for autocommands.
     63 /// Doesn't use emsg(), because it flushes the macro buffer.
     64 /// If we have undone all changes b_changed will be false, but "b_did_warn"
     65 /// will be true.
     66 /// "col" is the column for the message; non-zero when in insert mode and
     67 /// 'showmode' is on.
     68 /// Careful: may trigger autocommands that reload the buffer.
     69 void change_warning(buf_T *buf, int col)
     70 {
     71  static const char *w_readonly = N_("W10: Warning: Changing a readonly file");
     72 
     73  if (buf->b_did_warn == false
     74      && curbufIsChanged() == 0
     75      && !autocmd_busy
     76      && buf->b_p_ro) {
     77    buf->b_ro_locked++;
     78    apply_autocmds(EVENT_FILECHANGEDRO, NULL, NULL, false, buf);
     79    buf->b_ro_locked--;
     80    if (!buf->b_p_ro) {
     81      return;
     82    }
     83    // Do what msg() does, but with a column offset if the warning should
     84    // be after the mode message.
     85    msg_start();
     86    if (msg_row == Rows - 1) {
     87      msg_col = col;
     88    }
     89    msg_source(HLF_W);
     90    msg_ext_set_kind("wmsg");
     91    msg_puts_hl(_(w_readonly), HLF_W, true);
     92    set_vim_var_string(VV_WARNINGMSG, _(w_readonly), -1);
     93    msg_clr_eos();
     94    msg_end();
     95    if (msg_silent == 0 && !silent_mode && ui_active()) {
     96      msg_delay(1002, true);  // give the user time to think about it
     97    }
     98    buf->b_did_warn = true;
     99    redraw_cmdline = false;  // don't redraw and erase the message
    100    if (msg_row < Rows - 1) {
    101      showmode();
    102    }
    103  }
    104 }
    105 
    106 /// Call this function when something in a buffer is changed.
    107 ///
    108 /// Most often called through changed_bytes() and changed_lines(), which also
    109 /// mark the area of the display to be redrawn.
    110 ///
    111 /// Careful: may trigger autocommands that reload the buffer.
    112 void changed(buf_T *buf)
    113 {
    114  if (!buf->b_changed) {
    115    int save_msg_scroll = msg_scroll;
    116 
    117    // Give a warning about changing a read-only file.  This may also
    118    // check-out the file, thus change "curbuf"!
    119    change_warning(buf, 0);
    120 
    121    // Create a swap file if that is wanted.
    122    // Don't do this for "nofile" and "nowrite" buffer types.
    123    if (buf->b_may_swap && !bt_dontwrite(buf)) {
    124      bool save_need_wait_return = need_wait_return;
    125 
    126      need_wait_return = false;
    127      ml_open_file(buf);
    128 
    129      // The ml_open_file() can cause an ATTENTION message.
    130      // Wait two seconds, to make sure the user reads this unexpected
    131      // message.  Since we could be anywhere, call wait_return() now,
    132      // and don't let the emsg() set msg_scroll.
    133      if (need_wait_return && emsg_silent == 0 && !in_assert_fails && !ui_has(kUIMessages)) {
    134        msg_delay(2002, true);
    135        wait_return(true);
    136        msg_scroll = save_msg_scroll;
    137      } else {
    138        need_wait_return = save_need_wait_return;
    139      }
    140    }
    141    changed_internal(buf);
    142  }
    143  buf_inc_changedtick(buf);
    144 
    145  // If a pattern is highlighted, the position may now be invalid.
    146  highlight_match = false;
    147 }
    148 
    149 /// Internal part of changed(), no user interaction.
    150 /// Also used for recovery.
    151 void changed_internal(buf_T *buf)
    152 {
    153  buf->b_changed = true;
    154  buf->b_changed_invalid = true;
    155  ml_setflags(buf);
    156  redraw_buf_status_later(buf);
    157  redraw_tabline = true;
    158  need_maketitle = true;  // set window title later
    159 }
    160 
    161 /// Invalidate a window's w_valid flags and w_lines[] entries after changing lines.
    162 static void changed_lines_invalidate_win(win_T *wp, linenr_T lnum, colnr_T col, linenr_T lnume,
    163                                         linenr_T xtra)
    164 {
    165  // If the changed line is in a range of previously folded lines,
    166  // compare with the first line in that range.
    167  if (wp->w_cursor.lnum <= lnum) {
    168    int i = find_wl_entry(wp, lnum);
    169    if (i >= 0 && wp->w_cursor.lnum > wp->w_lines[i].wl_lnum) {
    170      changed_line_abv_curs_win(wp);
    171    }
    172  }
    173 
    174  if (wp->w_cursor.lnum > lnum) {
    175    changed_line_abv_curs_win(wp);
    176  } else if (wp->w_cursor.lnum == lnum && wp->w_cursor.col >= col) {
    177    changed_cline_bef_curs(wp);
    178  }
    179  if (wp->w_botline >= lnum) {
    180    if (xtra < 0) {
    181      invalidate_botline_win(wp);
    182    } else {
    183      // Assume that botline doesn't change (inserted lines make
    184      // other lines scroll down below botline).
    185      approximate_botline_win(wp);
    186    }
    187  }
    188 
    189  // If lines have been inserted/deleted and the buffer has virt_lines, or
    190  // inline virt_text with 'wrap' enabled, invalidate the line after the changed
    191  // lines. virt_lines may now be drawn above that line, and inline virt_text
    192  // may cause that line to wrap.
    193  if ((xtra < 0 && wp->w_p_wrap && buf_meta_total(wp->w_buffer, kMTMetaInline))
    194      || (xtra != 0 && buf_meta_total(wp->w_buffer, kMTMetaLines))) {
    195    lnume++;
    196  }
    197  // Check if any w_lines[] entries have become invalid.
    198  // For entries below the change: Correct the lnums for inserted/deleted lines.
    199  // Makes it possible to stop displaying after the change.
    200  for (int i = 0; i < wp->w_lines_valid; i++) {
    201    if (wp->w_lines[i].wl_valid) {
    202      if (wp->w_lines[i].wl_lnum >= lnum) {
    203        // Do not change wl_lnum at index zero, it is used to compare with w_topline.
    204        // Invalidate it instead.
    205        if (i == 0 || wp->w_lines[i].wl_lnum < lnume) {
    206          // line included in change
    207          wp->w_lines[i].wl_valid = false;
    208        } else if (xtra != 0) {
    209          // line below change
    210          wp->w_lines[i].wl_lnum += xtra;
    211          wp->w_lines[i].wl_foldend += xtra;
    212          wp->w_lines[i].wl_lastlnum += xtra;
    213        }
    214      } else if (wp->w_lines[i].wl_lastlnum >= lnum) {
    215        // change somewhere inside this range of folded or concealed lines,
    216        // may need to be redrawn
    217        wp->w_lines[i].wl_valid = false;
    218      }
    219    }
    220  }
    221 }
    222 
    223 /// Line changed_lines_invalidate_win(), but for all windows displaying a buffer.
    224 void changed_lines_invalidate_buf(buf_T *buf, linenr_T lnum, colnr_T col, linenr_T lnume,
    225                                  linenr_T xtra)
    226 {
    227  FOR_ALL_TAB_WINDOWS(tp, wp) {
    228    if (wp->w_buffer == buf) {
    229      changed_lines_invalidate_win(wp, lnum, col, lnume, xtra);
    230    }
    231  }
    232 }
    233 
    234 /// Common code for when a change was made.
    235 /// See changed_lines() for the arguments.
    236 /// Careful: may trigger autocommands that reload the buffer.
    237 static void changed_common(buf_T *buf, linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T xtra)
    238 {
    239  // mark the buffer as modified
    240  changed(buf);
    241 
    242  FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
    243    if (win->w_buffer == buf && win->w_p_diff && diff_internal()) {
    244      curtab->tp_diff_update = true;
    245      diff_update_line(lnum);
    246    }
    247  }
    248 
    249  // set the '. mark
    250  if ((cmdmod.cmod_flags & CMOD_KEEPJUMPS) == 0) {
    251    fmarkv_T view = INIT_FMARKV;
    252    // Set the markview only if lnum is visible, as changes might be done
    253    // outside of the current window view.
    254 
    255    if (curwin->w_buffer == buf) {
    256      if (lnum >= curwin->w_topline && lnum <= curwin->w_botline) {
    257        view = mark_view_make(curwin->w_topline, curwin->w_cursor);
    258      }
    259    }
    260    RESET_FMARK(&buf->b_last_change, ((pos_T) { lnum, col, 0 }), buf->handle, view);
    261 
    262    // Create a new entry if a new undo-able change was started or we
    263    // don't have an entry yet.
    264    if (buf->b_new_change || buf->b_changelistlen == 0) {
    265      bool add;
    266      if (buf->b_changelistlen == 0) {
    267        add = true;
    268      } else {
    269        // Don't create a new entry when the line number is the same
    270        // as the last one and the column is not too far away.  Avoids
    271        // creating many entries for typing "xxxxx".
    272        pos_T *p = &buf->b_changelist[buf->b_changelistlen - 1].mark;
    273        if (p->lnum != lnum) {
    274          add = true;
    275        } else {
    276          int cols = comp_textwidth(false);
    277          if (cols == 0) {
    278            cols = 79;
    279          }
    280          add = (p->col + cols < col || col + cols < p->col);
    281        }
    282      }
    283      if (add) {
    284        // This is the first of a new sequence of undo-able changes
    285        // and it's at some distance of the last change.  Use a new
    286        // position in the changelist.
    287        buf->b_new_change = false;
    288 
    289        if (buf->b_changelistlen == JUMPLISTSIZE) {
    290          // changelist is full: remove oldest entry
    291          buf->b_changelistlen = JUMPLISTSIZE - 1;
    292          memmove(buf->b_changelist, buf->b_changelist + 1,
    293                  sizeof(buf->b_changelist[0]) * (JUMPLISTSIZE - 1));
    294          FOR_ALL_TAB_WINDOWS(tp, wp) {
    295            // Correct position in changelist for other windows on
    296            // this buffer.
    297            if (wp->w_buffer == buf && wp->w_changelistidx > 0) {
    298              wp->w_changelistidx--;
    299            }
    300          }
    301        }
    302        FOR_ALL_TAB_WINDOWS(tp, wp) {
    303          // For other windows, if the position in the changelist is
    304          // at the end it stays at the end.
    305          if (wp->w_buffer == buf
    306              && wp->w_changelistidx == buf->b_changelistlen) {
    307            wp->w_changelistidx++;
    308          }
    309        }
    310        buf->b_changelistlen++;
    311      }
    312    }
    313    buf->b_changelist[buf->b_changelistlen - 1] =
    314      buf->b_last_change;
    315    // The current window is always after the last change, so that "g,"
    316    // takes you back to it.
    317    if (curwin->w_buffer == buf) {
    318      curwin->w_changelistidx = buf->b_changelistlen;
    319    }
    320  }
    321 
    322  if (curwin->w_buffer == buf && VIsual_active) {
    323    check_visual_pos();
    324  }
    325 
    326  FOR_ALL_TAB_WINDOWS(tp, wp) {
    327    if (wp->w_buffer == buf) {
    328      // Mark this window to be redrawn later.
    329      if (!redraw_not_allowed && wp->w_redr_type < UPD_VALID) {
    330        wp->w_redr_type = UPD_VALID;
    331      }
    332 
    333      // When inserting/deleting lines and the window has specific lines
    334      // to be redrawn, w_redraw_top and w_redraw_bot may now be invalid,
    335      // so just redraw everything.
    336      if (xtra != 0 && wp->w_redraw_top != 0) {
    337        redraw_later(wp, UPD_NOT_VALID);
    338      }
    339 
    340      linenr_T last = lnume + xtra - 1;  // last line after the change
    341 
    342      // Reset "w_skipcol" if the topline length has become smaller to
    343      // such a degree that nothing will be visible anymore, accounting
    344      // for 'smoothscroll' <<< or 'listchars' "precedes" marker.
    345      if (wp->w_skipcol > 0
    346          && (last < wp->w_topline
    347              || (wp->w_topline >= lnum
    348                  && wp->w_topline < lnume
    349                  && (linetabsize_eol(wp, wp->w_topline)
    350                      <= wp->w_skipcol + sms_marker_overlap(wp, -1))))) {
    351        wp->w_skipcol = 0;
    352      }
    353 
    354      // Check if a change in the buffer has invalidated the cached
    355      // values for the cursor.
    356      // Update the folds for this window.  Can't postpone this, because
    357      // a following operator might work on the whole fold: ">>dd".
    358      foldUpdate(wp, lnum, last);
    359 
    360      // The change may cause lines above or below the change to become
    361      // included in a fold.  Set lnum/lnume to the first/last line that
    362      // might be displayed differently.
    363      // Set w_cline_folded here as an efficient way to update it when
    364      // inserting lines just above a closed fold.
    365      bool folded = hasFoldingWin(wp, lnum, &lnum, NULL, false, NULL);
    366      if (wp->w_cursor.lnum == lnum) {
    367        wp->w_cline_folded = folded;
    368      }
    369      folded = hasFoldingWin(wp, last, NULL, &last, false, NULL);
    370      if (wp->w_cursor.lnum == last) {
    371        wp->w_cline_folded = folded;
    372      }
    373 
    374      changed_lines_invalidate_win(wp, lnum, col, lnume, xtra);
    375 
    376      // Take care of side effects for setting w_topline when folds have
    377      // changed.  Esp. when the buffer was changed in another window.
    378      if (hasAnyFolding(wp)) {
    379        set_topline(wp, wp->w_topline);
    380      }
    381 
    382      // If lines have been added or removed, relative numbering always
    383      // requires an update even if cursor didn't move.
    384      if (wp->w_p_rnu && xtra != 0) {
    385        wp->w_last_cursor_lnum_rnu = 0;
    386      }
    387 
    388      if (wp->w_p_cul && wp->w_last_cursorline >= lnum) {
    389        if (wp->w_last_cursorline < lnume) {
    390          // If 'cursorline' was inside the change, it has already
    391          // been invalidated in w_lines[] by the loop above.
    392          wp->w_last_cursorline = 0;
    393        } else {
    394          // If 'cursorline' was below the change, adjust its lnum.
    395          wp->w_last_cursorline += xtra;
    396        }
    397      }
    398    }
    399 
    400    if (wp == curwin && xtra != 0 && search_hl_has_cursor_lnum >= lnum) {
    401      search_hl_has_cursor_lnum += xtra;
    402    }
    403  }
    404 
    405  // Call update_screen() later, which checks out what needs to be redrawn,
    406  // since it notices b_mod_set and then uses b_mod_*.
    407  set_must_redraw(UPD_VALID);
    408 
    409  // when the cursor line is changed always trigger CursorMoved
    410  if (last_cursormoved_win == curwin && curwin->w_buffer == buf
    411      && lnum <= curwin->w_cursor.lnum
    412      && lnume + (xtra < 0 ? -xtra : xtra) > curwin->w_cursor.lnum) {
    413    last_cursormoved.lnum = 0;
    414  }
    415 }
    416 
    417 /// Changed bytes within a single line for the current buffer.
    418 /// - marks the windows on this buffer to be redisplayed
    419 /// - marks the buffer changed by calling changed()
    420 /// - invalidates cached values
    421 /// Careful: may trigger autocommands that reload the buffer.
    422 void changed_bytes(linenr_T lnum, colnr_T col)
    423 {
    424  changed_lines_redraw_buf(curbuf, lnum, lnum + 1, 0);
    425  changed_common(curbuf, lnum, col, lnum + 1, 0);
    426  // When text has been changed at the end of the line, possibly the start of
    427  // the next line may have SpellCap that should be removed or it needs to be
    428  // displayed.  Schedule the next line for redrawing just in case.
    429  // Don't do this when displaying '$' at the end of changed text.
    430  if (spell_check_window(curwin)
    431      && lnum < curbuf->b_ml.ml_line_count
    432      && vim_strchr(p_cpo, CPO_DOLLAR) == NULL) {
    433    redrawWinline(curwin, lnum + 1);
    434  }
    435  // notify any channels that are watching
    436  buf_updates_send_changes(curbuf, lnum, 1, 1);
    437 
    438  // Diff highlighting in other diff windows may need to be updated too.
    439  if (curwin->w_p_diff) {
    440    FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
    441      if (wp->w_p_diff && wp != curwin) {
    442        redraw_later(wp, UPD_VALID);
    443        linenr_T wlnum = diff_lnum_win(lnum, wp);
    444        if (wlnum > 0) {
    445          changed_lines_redraw_buf(wp->w_buffer, wlnum, wlnum + 1, 0);
    446        }
    447      }
    448    }
    449  }
    450 }
    451 
    452 /// insert/delete bytes at column
    453 ///
    454 /// Like changed_bytes() but also adjust extmark for "new" bytes.
    455 void inserted_bytes(linenr_T lnum, colnr_T start_col, int old_col, int new_col)
    456 {
    457  if (curbuf_splice_pending == 0) {
    458    extmark_splice_cols(curbuf, (int)lnum - 1, start_col, old_col, new_col, kExtmarkUndo);
    459  }
    460 
    461  changed_bytes(lnum, start_col);
    462 }
    463 
    464 /// Appended "count" lines below line "lnum" in the given buffer.
    465 /// Must be called AFTER the change and after mark_adjust().
    466 /// Takes care of marking the buffer to be redrawn and sets the changed flag.
    467 void appended_lines_buf(buf_T *buf, linenr_T lnum, linenr_T count)
    468 {
    469  changed_lines(buf, lnum + 1, 0, lnum + 1, count, true);
    470 }
    471 
    472 /// Appended "count" lines below line "lnum" in the current buffer.
    473 /// Must be called AFTER the change and after mark_adjust().
    474 /// Takes care of marking the buffer to be redrawn and sets the changed flag.
    475 void appended_lines(linenr_T lnum, linenr_T count)
    476 {
    477  appended_lines_buf(curbuf, lnum, count);
    478 }
    479 
    480 /// Like appended_lines(), but adjust marks first.
    481 void appended_lines_mark(linenr_T lnum, int count)
    482 {
    483  mark_adjust(lnum + 1, (linenr_T)MAXLNUM, (linenr_T)count, 0, kExtmarkUndo);
    484  changed_lines(curbuf, lnum + 1, 0, lnum + 1, (linenr_T)count, true);
    485 }
    486 
    487 /// Deleted "count" lines at line "lnum" in the given buffer.
    488 /// Must be called AFTER the change and after mark_adjust().
    489 /// Takes care of marking the buffer to be redrawn and sets the changed flag.
    490 void deleted_lines_buf(buf_T *buf, linenr_T lnum, linenr_T count)
    491 {
    492  changed_lines(buf, lnum, 0, lnum + count, -count, true);
    493 }
    494 
    495 /// Deleted "count" lines at line "lnum" in the current buffer.
    496 /// Must be called AFTER the change and after mark_adjust().
    497 /// Takes care of marking the buffer to be redrawn and sets the changed flag.
    498 void deleted_lines(linenr_T lnum, linenr_T count)
    499 {
    500  deleted_lines_buf(curbuf, lnum, count);
    501 }
    502 
    503 /// Like deleted_lines(), but adjust marks first.
    504 /// Make sure the cursor is on a valid line before calling, a GUI callback may
    505 /// be triggered to display the cursor.
    506 void deleted_lines_mark(linenr_T lnum, int count)
    507 {
    508  bool made_empty = (count > 0) && curbuf->b_ml.ml_flags & ML_EMPTY;
    509 
    510  mark_adjust(lnum, (linenr_T)(lnum + count - 1), MAXLNUM, -(linenr_T)count, kExtmarkNOOP);
    511  // if we deleted the entire buffer, we need to implicitly add a new empty line
    512  extmark_adjust(curbuf, lnum, (linenr_T)(lnum + count - 1), MAXLNUM,
    513                 -(linenr_T)count + (made_empty ? 1 : 0), kExtmarkUndo);
    514  changed_lines(curbuf, lnum, 0, lnum + (linenr_T)count, (linenr_T)(-count), true);
    515 }
    516 
    517 /// Marks the area to be redrawn after a change.
    518 /// Consider also calling changed_lines_invalidate_buf().
    519 ///
    520 /// @param buf the buffer where lines were changed
    521 /// @param lnum first line with change
    522 /// @param lnume line below last changed line
    523 /// @param xtra number of extra lines (negative when deleting)
    524 void changed_lines_redraw_buf(buf_T *buf, linenr_T lnum, linenr_T lnume, linenr_T xtra)
    525 {
    526  // If lines have been deleted and there may be decorations in the buffer, ensure
    527  // win_update() calculates the height of, and redraws the line to which or whence
    528  // from its mark may have moved. When lines are deleted, a virt_line mark may
    529  // have moved be drawn two lines below so increase by one more.
    530  if (xtra != 0 && buf->b_marktree->n_keys > 0) {
    531    lnume += 1 + (xtra < 0 && buf_meta_total(buf, kMTMetaLines));
    532  }
    533 
    534  if (buf->b_mod_set) {
    535    // find the maximum area that must be redisplayed
    536    buf->b_mod_top = MIN(buf->b_mod_top, lnum);
    537    if (lnum < buf->b_mod_bot) {
    538      // adjust old bot position for xtra lines
    539      buf->b_mod_bot += xtra;
    540      buf->b_mod_bot = MAX(buf->b_mod_bot, lnum);
    541    }
    542    buf->b_mod_bot = MAX(buf->b_mod_bot, lnume + xtra);
    543    buf->b_mod_xlines += xtra;
    544  } else {
    545    // set the area that must be redisplayed
    546    buf->b_mod_set = true;
    547    buf->b_mod_top = lnum;
    548    buf->b_mod_bot = lnume + xtra;
    549    buf->b_mod_xlines = xtra;
    550  }
    551 }
    552 
    553 /// Changed lines for a buffer.
    554 /// Must be called AFTER the change and after mark_adjust().
    555 /// - mark the buffer changed by calling changed()
    556 /// - mark the windows on this buffer to be redisplayed
    557 /// - invalidate cached values
    558 /// "lnum" is the first line that needs displaying, "lnume" the first line
    559 /// below the changed lines (BEFORE the change).
    560 /// When only inserting lines, "lnum" and "lnume" are equal.
    561 /// Takes care of calling changed() and updating b_mod_*.
    562 /// Careful: may trigger autocommands that reload the buffer.
    563 ///
    564 /// @param lnum  first line with change
    565 /// @param col  column in first line with change
    566 /// @param lnume  line below last changed line
    567 /// @param xtra  number of extra lines (negative when deleting)
    568 /// @param do_buf_event  some callers like undo/redo call changed_lines() and
    569 /// then increment changedtick *again*. This flag allows these callers to send
    570 /// the nvim_buf_lines_event events after they're done modifying changedtick.
    571 void changed_lines(buf_T *buf, linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T xtra,
    572                   bool do_buf_event)
    573 {
    574  changed_lines_redraw_buf(buf, lnum, lnume, xtra);
    575 
    576  if (xtra == 0 && curwin->w_p_diff && curwin->w_buffer == buf && !diff_internal()) {
    577    // When the number of lines doesn't change then mark_adjust() isn't
    578    // called and other diff buffers still need to be marked for
    579    // displaying.
    580    linenr_T wlnum;
    581 
    582    FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
    583      if (wp->w_p_diff && wp != curwin) {
    584        redraw_later(wp, UPD_VALID);
    585        wlnum = diff_lnum_win(lnum, wp);
    586        if (wlnum > 0) {
    587          changed_lines_redraw_buf(wp->w_buffer, wlnum, lnume - lnum + wlnum, 0);
    588        }
    589      }
    590    }
    591  }
    592 
    593  changed_common(buf, lnum, col, lnume, xtra);
    594 
    595  if (do_buf_event) {
    596    int64_t num_added = (int64_t)(lnume + xtra - lnum);
    597    int64_t num_removed = lnume - lnum;
    598    buf_updates_send_changes(buf, lnum, num_added, num_removed);
    599  }
    600 }
    601 
    602 /// Called when the changed flag must be reset for buffer `buf`.
    603 /// When `ff` is true also reset 'fileformat'.
    604 /// When `always_inc_changedtick` is true b:changedtick is incremented even
    605 /// when the changed flag was off.
    606 void unchanged(buf_T *buf, bool ff, bool always_inc_changedtick)
    607 {
    608  if (buf->b_changed || (ff && file_ff_differs(buf, false))) {
    609    buf->b_changed = false;
    610    buf->b_changed_invalid = true;
    611    ml_setflags(buf);
    612    if (ff) {
    613      save_file_ff(buf);
    614    }
    615    redraw_buf_status_later(buf);
    616    redraw_tabline = true;
    617    need_maketitle = true;  // set window title later
    618    buf_inc_changedtick(buf);
    619  } else if (always_inc_changedtick) {
    620    buf_inc_changedtick(buf);
    621  }
    622 }
    623 
    624 /// Save the current values of 'fileformat' and 'fileencoding', so that we know
    625 /// the file must be considered changed when the value is different.
    626 void save_file_ff(buf_T *buf)
    627 {
    628  buf->b_start_ffc = (unsigned char)(*buf->b_p_ff);
    629  buf->b_start_eof = buf->b_p_eof;
    630  buf->b_start_eol = buf->b_p_eol;
    631  buf->b_start_bomb = buf->b_p_bomb;
    632 
    633  // Only use free/alloc when necessary, they take time.
    634  if (buf->b_start_fenc == NULL
    635      || strcmp(buf->b_start_fenc, buf->b_p_fenc) != 0) {
    636    xfree(buf->b_start_fenc);
    637    buf->b_start_fenc = xstrdup(buf->b_p_fenc);
    638  }
    639 }
    640 
    641 /// Return true if 'fileformat' and/or 'fileencoding' has a different value
    642 /// from when editing started (save_file_ff() called).
    643 /// Also when 'endofline' was changed and 'binary' is set, or when 'bomb' was
    644 /// changed and 'binary' is not set.
    645 /// Also when 'endofline' was changed and 'fixeol' is not set.
    646 /// When "ignore_empty" is true don't consider a new, empty buffer to be
    647 /// changed.
    648 bool file_ff_differs(buf_T *buf, bool ignore_empty)
    649  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
    650 {
    651  // In a buffer that was never loaded the options are not valid.
    652  if (buf->b_flags & BF_NEVERLOADED) {
    653    return false;
    654  }
    655  if (ignore_empty
    656      && (buf->b_flags & BF_NEW)
    657      && buf->b_ml.ml_line_count == 1
    658      && *ml_get_buf(buf, 1) == NUL) {
    659    return false;
    660  }
    661  if (buf->b_start_ffc != *buf->b_p_ff) {
    662    return true;
    663  }
    664  if ((buf->b_p_bin || !buf->b_p_fixeol)
    665      && (buf->b_start_eof != buf->b_p_eof || buf->b_start_eol != buf->b_p_eol)) {
    666    return true;
    667  }
    668  if (!buf->b_p_bin && buf->b_start_bomb != buf->b_p_bomb) {
    669    return true;
    670  }
    671  if (buf->b_start_fenc == NULL) {
    672    return *buf->b_p_fenc != NUL;
    673  }
    674  return strcmp(buf->b_start_fenc, buf->b_p_fenc) != 0;
    675 }
    676 
    677 /// Insert string "p" at the cursor position.  Stops at a NUL byte.
    678 /// Handles Replace mode and multi-byte characters.
    679 void ins_bytes(char *p)
    680 {
    681  ins_bytes_len(p, strlen(p));
    682 }
    683 
    684 /// Insert string "p" with length "len" at the cursor position.
    685 /// Handles Replace mode and multi-byte characters.
    686 void ins_bytes_len(char *p, size_t len)
    687 {
    688  size_t n;
    689  for (size_t i = 0; i < len; i += n) {
    690    // avoid reading past p[len]
    691    n = (size_t)utfc_ptr2len_len(p + i, (int)(len - i));
    692    ins_char_bytes(p + i, n);
    693  }
    694 }
    695 
    696 /// Insert or replace a single character at the cursor position.
    697 /// When in MODE_REPLACE or MODE_VREPLACE state, replace any existing character.
    698 /// Caller must have prepared for undo.
    699 /// For multi-byte characters we get the whole character, the caller must
    700 /// convert bytes to a character.
    701 void ins_char(int c)
    702 {
    703  char buf[MB_MAXCHAR + 1];
    704  size_t n = (size_t)utf_char2bytes(c, buf);
    705 
    706  // When "c" is 0x100, 0x200, etc. we don't want to insert a NUL byte.
    707  // Happens for CTRL-Vu9900.
    708  if (buf[0] == 0) {
    709    buf[0] = '\n';
    710  }
    711  ins_char_bytes(buf, n);
    712 }
    713 
    714 void ins_char_bytes(char *buf, size_t charlen)
    715 {
    716  // Break tabs if needed.
    717  if (virtual_active(curwin) && curwin->w_cursor.coladd > 0) {
    718    coladvance_force(getviscol());
    719  }
    720 
    721  size_t col = (size_t)curwin->w_cursor.col;
    722  linenr_T lnum = curwin->w_cursor.lnum;
    723  char *oldp = ml_get(lnum);
    724  size_t linelen = (size_t)ml_get_len(lnum) + 1;  // length of old line including NUL
    725 
    726  // The lengths default to the values for when not replacing.
    727  size_t oldlen = 0;        // nr of bytes inserted
    728  size_t newlen = charlen;  // nr of bytes deleted (0 when not replacing)
    729 
    730  if (State & REPLACE_FLAG) {
    731    if (State & VREPLACE_FLAG) {
    732      // Disable 'list' temporarily, unless 'cpo' contains the 'L' flag.
    733      // Returns the old value of list, so when finished,
    734      // curwin->w_p_list should be set back to this.
    735      int old_list = curwin->w_p_list;
    736      if (old_list && vim_strchr(p_cpo, CPO_LISTWM) == NULL) {
    737        curwin->w_p_list = false;
    738      }
    739      // In virtual replace mode each character may replace one or more
    740      // characters (zero if it's a TAB).  Count the number of bytes to
    741      // be deleted to make room for the new character, counting screen
    742      // cells.  May result in adding spaces to fill a gap.
    743      colnr_T vcol;
    744      getvcol(curwin, &curwin->w_cursor, NULL, &vcol, NULL);
    745      colnr_T new_vcol = vcol + win_chartabsize(curwin, buf, vcol);
    746      while (oldp[col + oldlen] != NUL && vcol < new_vcol) {
    747        vcol += win_chartabsize(curwin, oldp + col + oldlen, vcol);
    748        // Don't need to remove a TAB that takes us to the right
    749        // position.
    750        if (vcol > new_vcol && oldp[col + oldlen] == TAB) {
    751          break;
    752        }
    753        oldlen += (size_t)utfc_ptr2len(oldp + col + oldlen);
    754        // Deleted a bit too much, insert spaces.
    755        if (vcol > new_vcol) {
    756          newlen += (size_t)(vcol - new_vcol);
    757        }
    758      }
    759      curwin->w_p_list = old_list;
    760    } else if (oldp[col] != NUL) {
    761      // normal replace
    762      oldlen = (size_t)utfc_ptr2len(oldp + col);
    763    }
    764 
    765    // Push the replaced bytes onto the replace stack, so that they can be
    766    // put back when BS is used.  The bytes of a multi-byte character are
    767    // done the other way around, so that the first byte is popped off
    768    // first (it tells the byte length of the character).
    769    replace_push_nul();
    770    replace_push(oldp + col, oldlen);
    771  }
    772 
    773  char *newp = xmalloc(linelen + newlen - oldlen);
    774 
    775  // Copy bytes before the cursor.
    776  if (col > 0) {
    777    memmove(newp, oldp, col);
    778  }
    779 
    780  // Copy bytes after the changed character(s).
    781  char *p = newp + col;
    782  if (linelen > col + oldlen) {
    783    memmove(p + newlen, oldp + col + oldlen, linelen - col - oldlen);
    784  }
    785 
    786  // Insert or overwrite the new character.
    787  memmove(p, buf, charlen);
    788 
    789  // Fill with spaces when necessary.
    790  for (size_t i = charlen; i < newlen; i++) {
    791    p[i] = ' ';
    792  }
    793 
    794  // Replace the line in the buffer.
    795  ml_replace(lnum, newp, false);
    796 
    797  // mark the buffer as changed and prepare for displaying
    798  inserted_bytes(lnum, (colnr_T)col, (int)oldlen, (int)newlen);
    799 
    800  // If we're in Insert or Replace mode and 'showmatch' is set, then briefly
    801  // show the match for right parens and braces.
    802  if (p_sm && (State & MODE_INSERT)
    803      && msg_silent == 0
    804      && !ins_compl_active()) {
    805    showmatch(utf_ptr2char(buf));
    806  }
    807 
    808  if (!p_ri || (State & REPLACE_FLAG)) {
    809    // Normal insert: move cursor right
    810    curwin->w_cursor.col += (colnr_T)charlen;
    811  }
    812  // TODO(Bram): should try to update w_row here, to avoid recomputing it later.
    813 }
    814 
    815 /// Insert a string at the cursor position.
    816 /// Note: Does NOT handle Replace mode.
    817 /// Caller must have prepared for undo.
    818 void ins_str(char *s, size_t slen)
    819 {
    820  linenr_T lnum = curwin->w_cursor.lnum;
    821 
    822  if (virtual_active(curwin) && curwin->w_cursor.coladd > 0) {
    823    coladvance_force(getviscol());
    824  }
    825 
    826  colnr_T col = curwin->w_cursor.col;
    827  char *oldp = ml_get(lnum);
    828  int oldlen = ml_get_len(lnum);
    829 
    830  char *newp = xmalloc((size_t)oldlen + slen + 1);
    831  if (col > 0) {
    832    memmove(newp, oldp, (size_t)col);
    833  }
    834  memmove(newp + col, s, slen);
    835  int bytes = oldlen - col + 1;
    836  assert(bytes >= 0);
    837  memmove(newp + col + slen, oldp + col, (size_t)bytes);
    838  ml_replace(lnum, newp, false);
    839  inserted_bytes(lnum, col, 0, (int)slen);
    840  curwin->w_cursor.col += (colnr_T)slen;
    841 }
    842 
    843 // Delete one character under the cursor.
    844 // If "fixpos" is true, don't leave the cursor on the NUL after the line.
    845 // Caller must have prepared for undo.
    846 //
    847 // return FAIL for failure, OK otherwise
    848 int del_char(bool fixpos)
    849 {
    850  // Make sure the cursor is at the start of a character.
    851  mb_adjust_cursor();
    852  if (*get_cursor_pos_ptr() == NUL) {
    853    return FAIL;
    854  }
    855  return del_chars(1, fixpos);
    856 }
    857 
    858 /// Like del_bytes(), but delete characters instead of bytes.
    859 int del_chars(int count, int fixpos)
    860 {
    861  int bytes = 0;
    862  char *p = get_cursor_pos_ptr();
    863  for (int i = 0; i < count && *p != NUL; i++) {
    864    int l = utfc_ptr2len(p);
    865    bytes += l;
    866    p += l;
    867  }
    868  return del_bytes(bytes, fixpos, true);
    869 }
    870 
    871 /// Delete "count" bytes under the cursor.
    872 /// If "fixpos" is true, don't leave the cursor on the NUL after the line.
    873 /// Caller must have prepared for undo.
    874 ///
    875 /// @param  count           number of bytes to be deleted
    876 /// @param  fixpos_arg      leave the cursor on the NUL after the line
    877 /// @param  use_delcombine  'delcombine' option applies
    878 ///
    879 /// @return FAIL for failure, OK otherwise
    880 int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine)
    881 {
    882  linenr_T lnum = curwin->w_cursor.lnum;
    883  colnr_T col = curwin->w_cursor.col;
    884  bool fixpos = fixpos_arg;
    885  char *oldp = ml_get(lnum);
    886  colnr_T oldlen = ml_get_len(lnum);
    887 
    888  // Can't do anything when the cursor is on the NUL after the line.
    889  if (col >= oldlen) {
    890    return FAIL;
    891  }
    892  // If "count" is zero there is nothing to do.
    893  if (count == 0) {
    894    return OK;
    895  }
    896  // If "count" is negative the caller must be doing something wrong.
    897  if (count < 1) {
    898    siemsg("E292: Invalid count for del_bytes(): %ld", (int64_t)count);
    899    return FAIL;
    900  }
    901 
    902  // If 'delcombine' is set and deleting (less than) one character, only
    903  // delete the last combining character.
    904  if (p_deco && use_delcombine && utfc_ptr2len(oldp + col) >= count) {
    905    char *p0 = oldp + col;
    906    GraphemeState state = GRAPHEME_STATE_INIT;
    907    if (utf_composinglike(p0, p0 + utf_ptr2len(p0), &state)) {
    908      // Find the last composing char, there can be several.
    909      int n = col;
    910      do {
    911        col = n;
    912        count = utf_ptr2len(oldp + n);
    913        n += count;
    914      } while (utf_composinglike(oldp + col, oldp + n, &state));
    915      fixpos = false;
    916    }
    917  }
    918 
    919  // When count is too big, reduce it.
    920  int movelen = oldlen - col - count + 1;  // includes trailing NUL
    921  if (movelen <= 1) {
    922    // If we just took off the last character of a non-blank line, and
    923    // fixpos is true, we don't want to end up positioned at the NUL,
    924    // unless "restart_edit" is set or 'virtualedit' contains "onemore".
    925    if (col > 0 && fixpos && restart_edit == 0
    926        && (get_ve_flags(curwin) & kOptVeFlagOnemore) == 0) {
    927      curwin->w_cursor.col--;
    928      curwin->w_cursor.coladd = 0;
    929      curwin->w_cursor.col -= utf_head_off(oldp, oldp + curwin->w_cursor.col);
    930    }
    931    count = oldlen - col;
    932    movelen = 1;
    933  }
    934  colnr_T newlen = oldlen - count;
    935 
    936  // If the old line has been allocated the deletion can be done in the
    937  // existing line. Otherwise a new line has to be allocated.
    938  bool alloc_newp = !ml_line_alloced();     // check if oldp was allocated
    939  char *newp;
    940  if (!alloc_newp) {
    941    ml_add_deleted_len(curbuf->b_ml.ml_line_ptr, oldlen);
    942    newp = oldp;                            // use same allocated memory
    943  } else {                                  // need to allocate a new line
    944    newp = xmallocz((size_t)newlen);
    945    memmove(newp, oldp, (size_t)col);
    946  }
    947  memmove(newp + col, oldp + col + count, (size_t)movelen);
    948  if (alloc_newp) {
    949    ml_replace(lnum, newp, false);
    950  } else {
    951    curbuf->b_ml.ml_line_textlen = newlen + 1;
    952  }
    953 
    954  // mark the buffer as changed and prepare for displaying
    955  inserted_bytes(lnum, col, count, 0);
    956 
    957  return OK;
    958 }
    959 
    960 /// open_line: Add a new line below or above the current line.
    961 ///
    962 /// For MODE_VREPLACE state, we only add a new line when we get to the end of
    963 /// the file, otherwise we just start replacing the next line.
    964 ///
    965 /// Caller must take care of undo.  Since MODE_VREPLACE may affect any number of
    966 /// lines however, it may call u_save_cursor() again when starting to change a
    967 /// new line.
    968 /// "flags": OPENLINE_DELSPACES delete spaces after cursor
    969 ///          OPENLINE_DO_COM    format comments
    970 ///          OPENLINE_KEEPTRAIL keep trailing spaces
    971 ///          OPENLINE_MARKFIX   adjust mark positions after the line break
    972 ///          OPENLINE_COM_LIST  format comments with list or 2nd line indent
    973 ///          OPENLINE_FORCE_INDENT  set indent from second_line_indent, ignore 'autoindent'
    974 ///
    975 /// "second_line_indent": indent for after ^^D in Insert mode or if flag
    976 ///                       OPENLINE_COM_LIST
    977 /// "did_do_comment" is set to true when intentionally putting the comment
    978 /// leader in front of the new line.
    979 ///
    980 /// @param dir  FORWARD or BACKWARD
    981 ///
    982 /// @return true on success, false on failure
    983 bool open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
    984 {
    985  char *next_line = NULL;         // copy of the next line
    986  char *p_extra = NULL;           // what goes to next line
    987  colnr_T less_cols = 0;          // less columns for mark in new line
    988  colnr_T less_cols_off = 0;      // columns to skip for mark adjust
    989  pos_T old_cursor;               // old cursor position
    990  colnr_T newcol = 0;             // new cursor column
    991  int newindent = 0;              // auto-indent of the new line
    992  bool trunc_line = false;        // truncate current line afterwards
    993  bool retval = false;            // return value
    994  int extra_len = 0;              // length of p_extra string
    995  int lead_len;                   // length of comment leader
    996  int comment_start = 0;          // start index of the comment leader
    997  char *lead_flags;               // position in 'comments' for comment leader
    998  char *leader = NULL;            // copy of comment leader
    999  char *allocated = NULL;         // allocated memory
   1000  char *p;
   1001  char saved_char = NUL;          // init for GCC
   1002  pos_T *pos;
   1003  bool do_si = may_do_si();
   1004  bool no_si = false;             // reset did_si afterwards
   1005  int first_char = NUL;           // init for GCC
   1006  int vreplace_mode;
   1007  bool did_append;                // appended a new line
   1008  int saved_pi = curbuf->b_p_pi;  // copy of preserveindent setting
   1009 
   1010  linenr_T lnum = curwin->w_cursor.lnum;
   1011  colnr_T mincol = curwin->w_cursor.col + 1;
   1012 
   1013  // make a copy of the current line so we can mess with it
   1014  char *saved_line = xstrnsave(get_cursor_line_ptr(), (size_t)get_cursor_line_len());
   1015 
   1016  if (State & VREPLACE_FLAG) {
   1017    // With MODE_VREPLACE we make a copy of the next line, which we will be
   1018    // starting to replace.  First make the new line empty and let vim play
   1019    // with the indenting and comment leader to its heart's content.  Then
   1020    // we grab what it ended up putting on the new line, put back the
   1021    // original line, and call ins_char() to put each new character onto
   1022    // the line, replacing what was there before and pushing the right
   1023    // stuff onto the replace stack.  -- webb.
   1024    if (curwin->w_cursor.lnum < orig_line_count) {
   1025      next_line = xstrnsave(ml_get(curwin->w_cursor.lnum + 1),
   1026                            (size_t)ml_get_len(curwin->w_cursor.lnum + 1));
   1027    } else {
   1028      next_line = xstrdup("");
   1029    }
   1030 
   1031    // In MODE_VREPLACE state, a NL replaces the rest of the line, and
   1032    // starts replacing the next line, so push all of the characters left
   1033    // on the line onto the replace stack.  We'll push any other characters
   1034    // that might be replaced at the start of the next line (due to
   1035    // autoindent etc) a bit later.
   1036    replace_push_nul();      // Call twice because BS over NL expects it
   1037    replace_push_nul();
   1038    p = saved_line + curwin->w_cursor.col;
   1039    replace_push(p, strlen(p));
   1040    saved_line[curwin->w_cursor.col] = NUL;
   1041  }
   1042 
   1043  if ((State & MODE_INSERT) && (State & VREPLACE_FLAG) == 0) {
   1044    p_extra = saved_line + curwin->w_cursor.col;
   1045    if (do_si) {  // need first char after new line break
   1046      p = skipwhite(p_extra);
   1047      first_char = (unsigned char)(*p);
   1048    }
   1049    extra_len = (int)strlen(p_extra);
   1050    saved_char = *p_extra;
   1051    *p_extra = NUL;
   1052  }
   1053 
   1054  u_clearline(curbuf);  // cannot do "U" command when adding lines
   1055  did_si = false;
   1056  ai_col = 0;
   1057 
   1058  // If we just did an auto-indent, then we didn't type anything on
   1059  // the prior line, and it should be truncated.  Do this even if 'ai' is not
   1060  // set because automatically inserting a comment leader also sets did_ai.
   1061  if (dir == FORWARD && did_ai) {
   1062    trunc_line = true;
   1063  }
   1064 
   1065  if ((flags & OPENLINE_FORCE_INDENT)) {
   1066    newindent = second_line_indent;
   1067  } else if (curbuf->b_p_ai || do_si) {
   1068    // If 'autoindent' and/or 'smartindent' is set, try to figure out what
   1069    // indent to use for the new line.
   1070    // count white space on current line
   1071    newindent = indent_size_ts(saved_line, curbuf->b_p_ts, curbuf->b_p_vts_array);
   1072    if (newindent == 0 && !(flags & OPENLINE_COM_LIST)) {
   1073      newindent = second_line_indent;  // for ^^D command in insert mode
   1074    }
   1075 
   1076    // Do smart indenting.
   1077    // In insert/replace mode (only when dir == FORWARD)
   1078    // we may move some text to the next line. If it starts with '{'
   1079    // don't add an indent. Fixes inserting a NL before '{' in line
   1080    //   "if (condition) {"
   1081    if (!trunc_line && do_si && *saved_line != NUL
   1082        && (p_extra == NULL || first_char != '{')) {
   1083      old_cursor = curwin->w_cursor;
   1084      char *ptr = saved_line;
   1085      if (flags & OPENLINE_DO_COM) {
   1086        lead_len = get_leader_len(ptr, NULL, false, true);
   1087      } else {
   1088        lead_len = 0;
   1089      }
   1090      if (dir == FORWARD) {
   1091        // Skip preprocessor directives, unless they are recognised as comments.
   1092        if (lead_len == 0 && ptr[0] == '#') {
   1093          while (ptr[0] == '#' && curwin->w_cursor.lnum > 1) {
   1094            ptr = ml_get(--curwin->w_cursor.lnum);
   1095          }
   1096          newindent = get_indent();
   1097        }
   1098        if (flags & OPENLINE_DO_COM) {
   1099          lead_len = get_leader_len(ptr, NULL, false, true);
   1100        } else {
   1101          lead_len = 0;
   1102        }
   1103        if (lead_len > 0) {
   1104          // This case gets the following right:
   1105          //     \*
   1106          //      * A comment (read '\' as '/').
   1107          //      */
   1108          //     #define IN_THE_WAY
   1109          //     This should line up here;
   1110          p = skipwhite(ptr);
   1111          if (p[0] == '/' && p[1] == '*') {
   1112            p++;
   1113          }
   1114          if (p[0] == '*') {
   1115            for (p++; *p; p++) {
   1116              if (p[0] == '/' && p[-1] == '*') {
   1117                // End of C comment, indent should line up
   1118                // with the line containing the start of
   1119                // the comment.
   1120                curwin->w_cursor.col = (colnr_T)(p - ptr);
   1121                if ((pos = findmatch(NULL, NUL)) != NULL) {
   1122                  curwin->w_cursor.lnum = pos->lnum;
   1123                  newindent = get_indent();
   1124                  break;
   1125                }
   1126                // this may make "ptr" invalid, get it again
   1127                ptr = ml_get(curwin->w_cursor.lnum);
   1128                p = ptr + curwin->w_cursor.col;
   1129              }
   1130            }
   1131          }
   1132        } else {      // Not a comment line
   1133          // Find last non-blank in line
   1134          p = ptr + strlen(ptr) - 1;
   1135          while (p > ptr && ascii_iswhite(*p)) {
   1136            p--;
   1137          }
   1138          char last_char = *p;
   1139 
   1140          // find the character just before the '{' or ';'
   1141          if (last_char == '{' || last_char == ';') {
   1142            if (p > ptr) {
   1143              p--;
   1144            }
   1145            while (p > ptr && ascii_iswhite(*p)) {
   1146              p--;
   1147            }
   1148          }
   1149          // Try to catch lines that are split over multiple
   1150          // lines.  eg:
   1151          //     if (condition &&
   1152          //             condition) {
   1153          //         Should line up here!
   1154          //     }
   1155          if (*p == ')') {
   1156            curwin->w_cursor.col = (colnr_T)(p - ptr);
   1157            if ((pos = findmatch(NULL, '(')) != NULL) {
   1158              curwin->w_cursor.lnum = pos->lnum;
   1159              newindent = get_indent();
   1160              ptr = get_cursor_line_ptr();
   1161            }
   1162          }
   1163          // If last character is '{' do indent, without
   1164          // checking for "if" and the like.
   1165          if (last_char == '{') {
   1166            did_si = true;              // do indent
   1167            no_si = true;               // don't delete it when '{' typed
   1168            // Look for "if" and the like, use 'cinwords'.
   1169            // Don't do this if the previous line ended in ';' or
   1170            // '}'.
   1171          } else if (last_char != ';' && last_char != '}'
   1172                     && cin_is_cinword(ptr)) {
   1173            did_si = true;
   1174          }
   1175        }
   1176      } else {  // dir == BACKWARD
   1177        // Skip preprocessor directives, unless they are
   1178        // recognised as comments.
   1179        if (lead_len == 0 && ptr[0] == '#') {
   1180          bool was_backslashed = false;
   1181 
   1182          while ((ptr[0] == '#' || was_backslashed)
   1183                 && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) {
   1184            if (*ptr && ptr[strlen(ptr) - 1] == '\\') {
   1185              was_backslashed = true;
   1186            } else {
   1187              was_backslashed = false;
   1188            }
   1189            ptr = ml_get(++curwin->w_cursor.lnum);
   1190          }
   1191          if (was_backslashed) {
   1192            newindent = 0;  // Got to end of file
   1193          } else {
   1194            newindent = get_indent();
   1195          }
   1196        }
   1197        p = skipwhite(ptr);
   1198        if (*p == '}') {            // if line starts with '}': do indent
   1199          did_si = true;
   1200        } else {                    // can delete indent when '{' typed
   1201          can_si_back = true;
   1202        }
   1203      }
   1204      curwin->w_cursor = old_cursor;
   1205    }
   1206    if (do_si) {
   1207      can_si = true;
   1208    }
   1209 
   1210    did_ai = true;
   1211  }
   1212 
   1213  // May do indenting after opening a new line.
   1214  bool do_cindent = !p_paste && (curbuf->b_p_cin || *curbuf->b_p_inde != NUL)
   1215                    && in_cinkeys(dir == FORWARD ? KEY_OPEN_FORW : KEY_OPEN_BACK,
   1216                                  ' ', linewhite(curwin->w_cursor.lnum))
   1217                    && !(flags & OPENLINE_FORCE_INDENT);
   1218 
   1219  // Find out if the current line starts with a comment leader.
   1220  // This may then be inserted in front of the new line.
   1221  end_comment_pending = NUL;
   1222  if (flags & OPENLINE_DO_COM) {
   1223    lead_len = get_leader_len(saved_line, &lead_flags, dir == BACKWARD, true);
   1224    if (lead_len == 0 && curbuf->b_p_cin && do_cindent && dir == FORWARD
   1225        && (!has_format_option(FO_NO_OPEN_COMS) || (flags & OPENLINE_FORMAT))) {
   1226      // Check for a line comment after code.
   1227      comment_start = check_linecomment(saved_line);
   1228      if (comment_start != MAXCOL) {
   1229        lead_len = get_leader_len(saved_line + comment_start, &lead_flags, false, true);
   1230        if (lead_len != 0) {
   1231          lead_len += comment_start;
   1232          if (did_do_comment != NULL) {
   1233            *did_do_comment = true;
   1234          }
   1235        }
   1236      }
   1237    }
   1238  } else {
   1239    lead_len = 0;
   1240  }
   1241  if (lead_len > 0) {
   1242    char *lead_repl = NULL;                 // replaces comment leader
   1243    int lead_repl_len = 0;                  // length of *lead_repl
   1244    char lead_middle[COM_MAX_LEN];          // middle-comment string
   1245    char lead_end[COM_MAX_LEN];             // end-comment string
   1246    char *comment_end = NULL;               // where lead_end has been found
   1247    int extra_space = false;                // append extra space
   1248    bool require_blank = false;             // requires blank after middle
   1249    char *p2;
   1250 
   1251    // If the comment leader has the start, middle or end flag, it may not
   1252    // be used or may be replaced with the middle leader.
   1253    for (p = lead_flags; *p && *p != ':'; p++) {
   1254      if (*p == COM_BLANK) {
   1255        require_blank = true;
   1256        continue;
   1257      }
   1258      if (*p == COM_START || *p == COM_MIDDLE) {
   1259        int current_flag = (unsigned char)(*p);
   1260        if (*p == COM_START) {
   1261          // Doing "O" on a start of comment does not insert leader.
   1262          if (dir == BACKWARD) {
   1263            lead_len = 0;
   1264            break;
   1265          }
   1266 
   1267          // find start of middle part
   1268          copy_option_part(&p, lead_middle, COM_MAX_LEN, ",");
   1269          require_blank = false;
   1270        }
   1271 
   1272        // Isolate the strings of the middle and end leader.
   1273        while (*p && p[-1] != ':') {  // find end of middle flags
   1274          if (*p == COM_BLANK) {
   1275            require_blank = true;
   1276          }
   1277          p++;
   1278        }
   1279        copy_option_part(&p, lead_middle, COM_MAX_LEN, ",");
   1280 
   1281        while (*p && p[-1] != ':') {  // find end of end flags
   1282          // Check whether we allow automatic ending of comments
   1283          if (*p == COM_AUTO_END) {
   1284            end_comment_pending = -1;  // means we want to set it
   1285          }
   1286          p++;
   1287        }
   1288        size_t n = copy_option_part(&p, lead_end, COM_MAX_LEN, ",");
   1289 
   1290        if (end_comment_pending == -1) {  // we can set it now
   1291          end_comment_pending = (unsigned char)lead_end[n - 1];
   1292        }
   1293 
   1294        // If the end of the comment is in the same line, don't use
   1295        // the comment leader.
   1296        if (dir == FORWARD) {
   1297          for (p = saved_line + lead_len; *p; p++) {
   1298            if (strncmp(p, lead_end, n) == 0) {
   1299              comment_end = p;
   1300              lead_len = 0;
   1301              break;
   1302            }
   1303          }
   1304        }
   1305 
   1306        // Doing "o" on a start of comment inserts the middle leader.
   1307        if (lead_len > 0) {
   1308          if (current_flag == COM_START) {
   1309            lead_repl = lead_middle;
   1310            lead_repl_len = (int)strlen(lead_middle);
   1311          }
   1312 
   1313          // If we have hit RETURN immediately after the start
   1314          // comment leader, then put a space after the middle
   1315          // comment leader on the next line.
   1316          if (!ascii_iswhite(saved_line[lead_len - 1])
   1317              && ((p_extra != NULL
   1318                   && (int)curwin->w_cursor.col == lead_len)
   1319                  || (p_extra == NULL
   1320                      && saved_line[lead_len] == NUL)
   1321                  || require_blank)) {
   1322            extra_space = true;
   1323          }
   1324        }
   1325        break;
   1326      }
   1327      if (*p == COM_END) {
   1328        // Doing "o" on the end of a comment does not insert leader.
   1329        // Remember where the end is, might want to use it to find the
   1330        // start (for C-comments).
   1331        if (dir == FORWARD) {
   1332          comment_end = skipwhite(saved_line);
   1333          lead_len = 0;
   1334          break;
   1335        }
   1336 
   1337        // Doing "O" on the end of a comment inserts the middle leader.
   1338        // Find the string for the middle leader, searching backwards.
   1339        while (p > curbuf->b_p_com && *p != ',') {
   1340          p--;
   1341        }
   1342        for (lead_repl = p; lead_repl > curbuf->b_p_com
   1343             && lead_repl[-1] != ':'; lead_repl--) {}
   1344        lead_repl_len = (int)(p - lead_repl);
   1345 
   1346        // We can probably always add an extra space when doing "O" on
   1347        // the comment-end
   1348        extra_space = true;
   1349 
   1350        // Check whether we allow automatic ending of comments
   1351        for (p2 = p; *p2 && *p2 != ':'; p2++) {
   1352          if (*p2 == COM_AUTO_END) {
   1353            end_comment_pending = -1;  // means we want to set it
   1354          }
   1355        }
   1356        if (end_comment_pending == -1) {
   1357          // Find last character in end-comment string
   1358          while (*p2 && *p2 != ',') {
   1359            p2++;
   1360          }
   1361          end_comment_pending = (unsigned char)p2[-1];
   1362        }
   1363        break;
   1364      }
   1365      if (*p == COM_FIRST) {
   1366        // Comment leader for first line only: Don't repeat leader
   1367        // when using "O", blank out leader when using "o".
   1368        if (dir == BACKWARD) {
   1369          lead_len = 0;
   1370        } else {
   1371          lead_repl = "";
   1372          lead_repl_len = 0;
   1373        }
   1374        break;
   1375      }
   1376    }
   1377    if (lead_len > 0) {
   1378      // allocate buffer (may concatenate p_extra later)
   1379      int bytes = lead_len
   1380                  + lead_repl_len
   1381                  + extra_space
   1382                  + extra_len
   1383                  + (second_line_indent > 0 ? second_line_indent : 0)
   1384                  + 1;
   1385      assert(bytes >= 0);
   1386      leader = xmalloc((size_t)bytes);
   1387      allocated = leader;  // remember to free it later
   1388 
   1389      xmemcpyz(leader, saved_line, (size_t)lead_len);
   1390 
   1391      // TODO(vim): handle multi-byte and double width chars
   1392      for (int li = 0; li < comment_start; li++) {
   1393        if (!ascii_iswhite(leader[li])) {
   1394          leader[li] = ' ';
   1395        }
   1396      }
   1397 
   1398      // Replace leader with lead_repl, right or left adjusted
   1399      if (lead_repl != NULL) {
   1400        int c = 0;
   1401        int off = 0;
   1402 
   1403        for (p = lead_flags; *p != NUL && *p != ':';) {
   1404          if (*p == COM_RIGHT || *p == COM_LEFT) {
   1405            c = (unsigned char)(*p++);
   1406          } else if (ascii_isdigit(*p) || *p == '-') {
   1407            off = getdigits_int(&p, true, 0);
   1408          } else {
   1409            p++;
   1410          }
   1411        }
   1412        if (c == COM_RIGHT) {  // right adjusted leader
   1413          // find last non-white in the leader to line up with
   1414          for (p = leader + lead_len - 1; p > leader
   1415               && ascii_iswhite(*p); p--) {}
   1416          p++;
   1417 
   1418          // Compute the length of the replaced characters in
   1419          // screen characters, not bytes.
   1420          {
   1421            int repl_size = vim_strnsize(lead_repl, lead_repl_len);
   1422            int old_size = 0;
   1423            char *endp = p;
   1424 
   1425            while (old_size < repl_size && p > leader) {
   1426              MB_PTR_BACK(leader, p);
   1427              old_size += ptr2cells(p);
   1428            }
   1429            int l = lead_repl_len - (int)(endp - p);
   1430            if (l != 0) {
   1431              memmove(endp + l, endp,
   1432                      (size_t)((leader + lead_len) - endp));
   1433            }
   1434            lead_len += l;
   1435          }
   1436          memmove(p, lead_repl, (size_t)lead_repl_len);
   1437          if (p + lead_repl_len > leader + lead_len) {
   1438            p[lead_repl_len] = NUL;
   1439          }
   1440 
   1441          // blank-out any other chars from the old leader.
   1442          while (--p >= leader) {
   1443            int l = utf_head_off(leader, p);
   1444 
   1445            if (l > 1) {
   1446              p -= l;
   1447              if (ptr2cells(p) > 1) {
   1448                p[1] = ' ';
   1449                l--;
   1450              }
   1451              memmove(p + 1, p + l + 1,
   1452                      (size_t)((leader + lead_len) - (p + l + 1)));
   1453              lead_len -= l;
   1454              *p = ' ';
   1455            } else if (!ascii_iswhite(*p)) {
   1456              *p = ' ';
   1457            }
   1458          }
   1459        } else {  // left adjusted leader
   1460          p = skipwhite(leader);
   1461          // Compute the length of the replaced characters in
   1462          // screen characters, not bytes. Move the part that is
   1463          // not to be overwritten.
   1464          {
   1465            int repl_size = vim_strnsize(lead_repl, lead_repl_len);
   1466            int i;
   1467            int l;
   1468 
   1469            for (i = 0; i < lead_len && p[i] != NUL; i += l) {
   1470              l = utfc_ptr2len(p + i);
   1471              if (vim_strnsize(p, i + l) > repl_size) {
   1472                break;
   1473              }
   1474            }
   1475            if (i != lead_repl_len) {
   1476              memmove(p + lead_repl_len, p + i,
   1477                      (size_t)(lead_len - i - (p - leader)));
   1478              lead_len += lead_repl_len - i;
   1479            }
   1480          }
   1481          memmove(p, lead_repl, (size_t)lead_repl_len);
   1482 
   1483          // Replace any remaining non-white chars in the old
   1484          // leader by spaces.  Keep Tabs, the indent must
   1485          // remain the same.
   1486          for (p += lead_repl_len; p < leader + lead_len; p++) {
   1487            if (!ascii_iswhite(*p)) {
   1488              // Don't put a space before a TAB.
   1489              if (p + 1 < leader + lead_len && p[1] == TAB) {
   1490                lead_len--;
   1491                memmove(p, p + 1, (size_t)(leader + lead_len - p));
   1492              } else {
   1493                int l = utfc_ptr2len(p);
   1494 
   1495                if (l > 1) {
   1496                  if (ptr2cells(p) > 1) {
   1497                    // Replace a double-wide char with
   1498                    // two spaces
   1499                    l--;
   1500                    *p++ = ' ';
   1501                  }
   1502                  memmove(p + 1, p + l, (size_t)(leader + lead_len - p));
   1503                  lead_len -= l - 1;
   1504                }
   1505                *p = ' ';
   1506              }
   1507            }
   1508          }
   1509          *p = NUL;
   1510        }
   1511 
   1512        // Recompute the indent, it may have changed.
   1513        if (curbuf->b_p_ai || do_si) {
   1514          newindent = indent_size_ts(leader, curbuf->b_p_ts, curbuf->b_p_vts_array);
   1515        }
   1516 
   1517        // Add the indent offset
   1518        if (newindent + off < 0) {
   1519          off = -newindent;
   1520          newindent = 0;
   1521        } else {
   1522          newindent += off;
   1523        }
   1524 
   1525        // Correct trailing spaces for the shift, so that
   1526        // alignment remains equal.
   1527        while (off > 0 && lead_len > 0
   1528               && leader[lead_len - 1] == ' ') {
   1529          // Don't do it when there is a tab before the space
   1530          if (vim_strchr(skipwhite(leader), '\t') != NULL) {
   1531            break;
   1532          }
   1533          lead_len--;
   1534          off--;
   1535        }
   1536 
   1537        // If the leader ends in white space, don't add an
   1538        // extra space
   1539        if (lead_len > 0 && ascii_iswhite(leader[lead_len - 1])) {
   1540          extra_space = false;
   1541        }
   1542        leader[lead_len] = NUL;
   1543      }
   1544 
   1545      if (extra_space) {
   1546        leader[lead_len++] = ' ';
   1547        leader[lead_len] = NUL;
   1548      }
   1549 
   1550      newcol = lead_len;
   1551 
   1552      // if a new indent will be set below, remove the indent that
   1553      // is in the comment leader
   1554      if (newindent || did_si) {
   1555        while (lead_len && ascii_iswhite(*leader)) {
   1556          lead_len--;
   1557          newcol--;
   1558          leader++;
   1559        }
   1560      }
   1561      did_si = can_si = false;
   1562    } else if (comment_end != NULL) {
   1563      // We have finished a comment, so we don't use the leader.
   1564      // If this was a C-comment and 'ai' or 'si' is set do a normal
   1565      // indent to align with the line containing the start of the
   1566      // comment.
   1567      if (comment_end[0] == '*' && comment_end[1] == '/'
   1568          && (curbuf->b_p_ai || do_si)) {
   1569        old_cursor = curwin->w_cursor;
   1570        curwin->w_cursor.col = (colnr_T)(comment_end - saved_line);
   1571        if ((pos = findmatch(NULL, NUL)) != NULL) {
   1572          curwin->w_cursor.lnum = pos->lnum;
   1573          newindent = get_indent();
   1574        }
   1575        curwin->w_cursor = old_cursor;
   1576      }
   1577    }
   1578  }
   1579 
   1580  // (State == MODE_INSERT || State == MODE_REPLACE), only when dir == FORWARD
   1581  if (p_extra != NULL) {
   1582    *p_extra = saved_char;              // restore char that NUL replaced
   1583 
   1584    // When 'ai' set or "flags" has OPENLINE_DELSPACES, skip to the first
   1585    // non-blank.
   1586    //
   1587    // When in MODE_REPLACE state, put the deleted blanks on the replace
   1588    // stack, preceded by a NUL, so they can be put back when a BS is
   1589    // entered.
   1590    if (REPLACE_NORMAL(State)) {
   1591      replace_push_nul();            // end of extra blanks
   1592    }
   1593    if (curbuf->b_p_ai || (flags & OPENLINE_DELSPACES)) {
   1594      while ((*p_extra == ' ' || *p_extra == '\t')
   1595             && !utf_iscomposing_first(utf_ptr2char(p_extra + 1))) {
   1596        if (REPLACE_NORMAL(State)) {
   1597          replace_push(p_extra, 1);  // always ascii, len = 1
   1598        }
   1599        p_extra++;
   1600        less_cols_off++;
   1601      }
   1602    }
   1603 
   1604    // columns for marks adjusted for removed columns
   1605    less_cols = (int)(p_extra - saved_line);
   1606  }
   1607 
   1608  if (p_extra == NULL) {
   1609    p_extra = "";                 // append empty line
   1610  }
   1611 
   1612  // concatenate leader and p_extra, if there is a leader
   1613  if (lead_len > 0) {
   1614    if (flags & OPENLINE_COM_LIST && second_line_indent > 0) {
   1615      int padding = second_line_indent
   1616                    - (newindent + (int)strlen(leader));
   1617 
   1618      // Here whitespace is inserted after the comment char.
   1619      // Below, set_indent(newindent, SIN_INSERT) will insert the
   1620      // whitespace needed before the comment char.
   1621      for (int i = 0; i < padding; i++) {
   1622        strcat(leader, " ");
   1623        less_cols--;
   1624        newcol++;
   1625      }
   1626    }
   1627    strcat(leader, p_extra);
   1628    p_extra = leader;
   1629    did_ai = true;          // So truncating blanks works with comments
   1630    less_cols -= lead_len;
   1631  } else {
   1632    end_comment_pending = NUL;  // turns out there was no leader
   1633  }
   1634 
   1635  curbuf_splice_pending++;
   1636  old_cursor = curwin->w_cursor;
   1637  int old_cmod_flags = cmdmod.cmod_flags;
   1638  char *prompt_moved = NULL;
   1639  if (dir == BACKWARD) {
   1640    // In case of prompt buffer, when we are applying 'normal O' operation on line of prompt,
   1641    // we can't add a new line before the prompt. In this case, we move the prompt text one
   1642    // line below and create a new prompt line as current line.
   1643    if (bt_prompt(curbuf) && curwin->w_cursor.lnum == curbuf->b_prompt_start.mark.lnum) {
   1644      char *prompt_line = ml_get(curwin->w_cursor.lnum);
   1645      char *prompt = prompt_text();
   1646      size_t prompt_len = strlen(prompt);
   1647 
   1648      if (strncmp(prompt_line, prompt, prompt_len) == 0) {
   1649        STRMOVE(prompt_line, prompt_line + prompt_len);
   1650        // We are moving the lines but the b_prompt_start mark needs to stay in
   1651        // place so freezing marks before making the move.
   1652        cmdmod.cmod_flags = cmdmod.cmod_flags | CMOD_LOCKMARKS;
   1653        ml_replace(curwin->w_cursor.lnum, prompt_line, true);
   1654        prompt_moved = concat_str(prompt, p_extra);
   1655        p_extra = prompt_moved;
   1656      }
   1657    }
   1658    curwin->w_cursor.lnum--;
   1659  }
   1660  if ((State & VREPLACE_FLAG) == 0 || old_cursor.lnum >= orig_line_count) {
   1661    if (ml_append(curwin->w_cursor.lnum, p_extra, 0, false) == FAIL) {
   1662      goto theend;
   1663    }
   1664    // Postpone calling changed_lines(), because it would mess up folding
   1665    // with markers.
   1666    mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1, 0, kExtmarkNOOP);
   1667    did_append = true;
   1668  } else {
   1669    // In MODE_VREPLACE state we are starting to replace the next line.
   1670    curwin->w_cursor.lnum++;
   1671    if (curwin->w_cursor.lnum >= Insstart.lnum + vr_lines_changed) {
   1672      // In case we NL to a new line, BS to the previous one, and NL
   1673      // again, we don't want to save the new line for undo twice.
   1674      u_save_cursor();  // errors are ignored!
   1675      vr_lines_changed++;
   1676    }
   1677    ml_replace(curwin->w_cursor.lnum, p_extra, true);
   1678    changed_bytes(curwin->w_cursor.lnum, 0);
   1679    // TODO(vigoux): extmark_splice_cols here??
   1680    curwin->w_cursor.lnum--;
   1681    did_append = false;
   1682  }
   1683 
   1684  inhibit_delete_count++;
   1685  if (newindent || did_si) {
   1686    curwin->w_cursor.lnum++;
   1687    if (did_si) {
   1688      int sw = get_sw_value(curbuf);
   1689 
   1690      if (p_sr) {
   1691        newindent -= newindent % sw;
   1692      }
   1693      newindent += sw;
   1694    }
   1695    // Copy the indent
   1696    if (curbuf->b_p_ci) {
   1697      copy_indent(newindent, saved_line);
   1698 
   1699      // Set the 'preserveindent' option so that any further screwing
   1700      // with the line doesn't entirely destroy our efforts to preserve
   1701      // it.  It gets restored at the function end.
   1702      curbuf->b_p_pi = true;
   1703    } else {
   1704      set_indent(newindent, SIN_INSERT|SIN_NOMARK);
   1705    }
   1706    less_cols -= curwin->w_cursor.col;
   1707 
   1708    ai_col = curwin->w_cursor.col;
   1709 
   1710    // In MODE_REPLACE state, for each character in the new indent, there
   1711    // must be a NUL on the replace stack, for when it is deleted with BS
   1712    if (REPLACE_NORMAL(State)) {
   1713      for (colnr_T n = 0; n < curwin->w_cursor.col; n++) {
   1714        replace_push_nul();
   1715      }
   1716    }
   1717    newcol += curwin->w_cursor.col;
   1718    if (no_si) {
   1719      did_si = false;
   1720    }
   1721  }
   1722  inhibit_delete_count--;
   1723 
   1724  // In MODE_REPLACE state, for each character in the extra leader, there
   1725  // must be a NUL on the replace stack, for when it is deleted with BS.
   1726  if (REPLACE_NORMAL(State)) {
   1727    while (lead_len-- > 0) {
   1728      replace_push_nul();
   1729    }
   1730  }
   1731 
   1732  curwin->w_cursor = old_cursor;
   1733 
   1734  if (dir == FORWARD) {
   1735    if (trunc_line || (State & MODE_INSERT)) {
   1736      // truncate current line at cursor
   1737      saved_line[curwin->w_cursor.col] = NUL;
   1738      // Remove trailing white space, unless OPENLINE_KEEPTRAIL used.
   1739      if (trunc_line && !(flags & OPENLINE_KEEPTRAIL)) {
   1740        truncate_spaces(saved_line, (size_t)curwin->w_cursor.col);
   1741      }
   1742      ml_replace(curwin->w_cursor.lnum, saved_line, false);
   1743 
   1744      int new_len = (int)strlen(saved_line);
   1745 
   1746      // TODO(vigoux): maybe there is issues there with expandtabs ?
   1747      int cols_spliced = 0;
   1748      if (new_len < curwin->w_cursor.col) {
   1749        extmark_splice_cols(curbuf, (int)curwin->w_cursor.lnum - 1,
   1750                            new_len, curwin->w_cursor.col - new_len, 0, kExtmarkUndo);
   1751        cols_spliced = curwin->w_cursor.col - new_len;
   1752      }
   1753 
   1754      saved_line = NULL;
   1755      if (did_append) {
   1756        // Always move extmarks - Here we move only the line where the cursor is,
   1757        // the previous mark_adjust() took care of the lines after.
   1758        int cols_added = mincol - 1 + less_cols_off - less_cols;
   1759        extmark_splice(curbuf, (int)lnum - 1, mincol - 1 - cols_spliced,
   1760                       0, less_cols_off, less_cols_off,
   1761                       1, cols_added, 1 + cols_added, kExtmarkUndo);
   1762 
   1763        changed_lines(curbuf, curwin->w_cursor.lnum, curwin->w_cursor.col,
   1764                      curwin->w_cursor.lnum + 1, 1, true);
   1765        did_append = false;
   1766 
   1767        // Move marks after the line break to the new line.
   1768        if (flags & OPENLINE_MARKFIX) {
   1769          mark_col_adjust(curwin->w_cursor.lnum,
   1770                          curwin->w_cursor.col + less_cols_off,
   1771                          1, -less_cols, 0);
   1772        }
   1773      } else {
   1774        changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col);
   1775      }
   1776    }
   1777 
   1778    // Put the cursor on the new line.  Careful: the scrollup() above may
   1779    // have moved w_cursor, we must use old_cursor.
   1780    curwin->w_cursor.lnum = old_cursor.lnum + 1;
   1781  }
   1782  if (did_append) {
   1783    // bail out and just get the final length of the line we just manipulated
   1784    bcount_t extra = ml_get_len(curwin->w_cursor.lnum);
   1785    extmark_splice(curbuf, (int)curwin->w_cursor.lnum - 1, 0,
   1786                   0, 0, 0, 1, 0, 1 + extra, kExtmarkUndo);
   1787    changed_lines(curbuf, curwin->w_cursor.lnum, 0, curwin->w_cursor.lnum, 1, true);
   1788  }
   1789  curbuf_splice_pending--;
   1790 
   1791  curwin->w_cursor.col = newcol;
   1792  curwin->w_cursor.coladd = 0;
   1793 
   1794  // In MODE_VREPLACE state, we are handling the replace stack ourselves, so
   1795  // stop fixthisline() from doing it (via change_indent()) by telling it
   1796  // we're in normal MODE_INSERT state.
   1797  if (State & VREPLACE_FLAG) {
   1798    vreplace_mode = State;  // So we know to put things right later
   1799    State = MODE_INSERT;
   1800  } else {
   1801    vreplace_mode = 0;
   1802  }
   1803 
   1804  if (!p_paste) {
   1805    if (leader == NULL
   1806        && !use_indentexpr_for_lisp()
   1807        && curbuf->b_p_lisp
   1808        && curbuf->b_p_ai) {
   1809      // do lisp indenting
   1810      fixthisline(get_lisp_indent);
   1811      ai_col = (colnr_T)getwhitecols_curline();
   1812    } else if (do_cindent || (curbuf->b_p_ai && use_indentexpr_for_lisp())) {
   1813      // do 'cindent' or 'indentexpr' indenting
   1814      do_c_expr_indent();
   1815      ai_col = (colnr_T)getwhitecols_curline();
   1816    }
   1817  }
   1818 
   1819  if (vreplace_mode != 0) {
   1820    State = vreplace_mode;
   1821  }
   1822 
   1823  // Finally, MODE_VREPLACE gets the stuff on the new line, then puts back
   1824  // the original line, and inserts the new stuff char by char, pushing old
   1825  // stuff onto the replace stack (via ins_char()).
   1826  if (State & VREPLACE_FLAG) {
   1827    // Put new line in p_extra
   1828    p_extra = xstrnsave(get_cursor_line_ptr(), (size_t)get_cursor_line_len());
   1829 
   1830    // Put back original line
   1831    ml_replace(curwin->w_cursor.lnum, next_line, false);
   1832 
   1833    // Insert new stuff into line again
   1834    curwin->w_cursor.col = 0;
   1835    curwin->w_cursor.coladd = 0;
   1836    ins_bytes(p_extra);         // will call changed_bytes()
   1837    xfree(p_extra);
   1838    next_line = NULL;
   1839  }
   1840 
   1841  retval = true;                // success!
   1842 theend:
   1843  curbuf->b_p_pi = saved_pi;
   1844  xfree(saved_line);
   1845  xfree(next_line);
   1846  xfree(allocated);
   1847  xfree(prompt_moved);
   1848  cmdmod.cmod_flags = old_cmod_flags;
   1849  return retval;
   1850 }
   1851 
   1852 /// Delete from cursor to end of line.
   1853 /// Caller must have prepared for undo.
   1854 /// If "fixpos" is true fix the cursor position when done.
   1855 void truncate_line(int fixpos)
   1856 {
   1857  linenr_T lnum = curwin->w_cursor.lnum;
   1858  colnr_T col = curwin->w_cursor.col;
   1859  char *old_line = ml_get(lnum);
   1860  char *newp = col == 0 ? xstrdup("") : xstrnsave(old_line, (size_t)col);
   1861  int deleted = ml_get_len(lnum) - col;
   1862 
   1863  ml_replace(lnum, newp, false);
   1864 
   1865  // mark the buffer as changed and prepare for displaying
   1866  inserted_bytes(lnum, curwin->w_cursor.col, deleted, 0);
   1867 
   1868  // If "fixpos" is true we don't want to end up positioned at the NUL.
   1869  if (fixpos && curwin->w_cursor.col > 0) {
   1870    curwin->w_cursor.col--;
   1871  }
   1872 }
   1873 
   1874 /// Delete "nlines" lines at the cursor.
   1875 /// Saves the lines for undo first if "undo" is true.
   1876 void del_lines(linenr_T nlines, bool undo)
   1877 {
   1878  int n;
   1879  linenr_T first = curwin->w_cursor.lnum;
   1880 
   1881  if (nlines <= 0) {
   1882    return;
   1883  }
   1884 
   1885  // save the deleted lines for undo
   1886  if (undo && u_savedel(first, nlines) == FAIL) {
   1887    return;
   1888  }
   1889 
   1890  for (n = 0; n < nlines;) {
   1891    if (curbuf->b_ml.ml_flags & ML_EMPTY) {  // nothing to delete
   1892      break;
   1893    }
   1894 
   1895    ml_delete_flags(first, ML_DEL_MESSAGE);
   1896    n++;
   1897 
   1898    // If we delete the last line in the file, stop
   1899    if (first > curbuf->b_ml.ml_line_count) {
   1900      break;
   1901    }
   1902  }
   1903 
   1904  // Correct the cursor position before calling deleted_lines_mark(), it may
   1905  // trigger a callback to display the cursor.
   1906  curwin->w_cursor.col = 0;
   1907  check_cursor_lnum(curwin);
   1908 
   1909  // adjust marks, mark the buffer as changed and prepare for displaying
   1910  deleted_lines_mark(first, n);
   1911 }
   1912 
   1913 /// Returns the length in bytes of the prefix of the given string which introduces a comment.
   1914 ///
   1915 /// If this string is not a comment then 0 is returned.
   1916 /// When "flags" is not NULL, it is set to point to the flags of the recognized comment leader.
   1917 /// "backward" must be true for the "O" command.
   1918 /// If "include_space" is set, include trailing whitespace while calculating the length.
   1919 int get_leader_len(char *line, char **flags, bool backward, bool include_space)
   1920 {
   1921  int j;
   1922  bool got_com = false;
   1923  char part_buf[COM_MAX_LEN];         // buffer for one option part
   1924  char *string;                  // pointer to comment string
   1925  int middle_match_len = 0;
   1926  char *saved_flags = NULL;
   1927 
   1928  int result = 0;
   1929  int i = 0;
   1930  while (ascii_iswhite(line[i])) {  // leading white space is ignored
   1931    i++;
   1932  }
   1933 
   1934  // Repeat to match several nested comment strings.
   1935  while (line[i] != NUL) {
   1936    // scan through the 'comments' option for a match
   1937    bool found_one = false;
   1938    for (char *list = curbuf->b_p_com; *list;) {
   1939      // Get one option part into part_buf[].  Advance "list" to next
   1940      // one.  Put "string" at start of string.
   1941      if (!got_com && flags != NULL) {
   1942        *flags = list;              // remember where flags started
   1943      }
   1944      char *prev_list = list;
   1945      copy_option_part(&list, part_buf, COM_MAX_LEN, ",");
   1946      string = vim_strchr(part_buf, ':');
   1947      if (string == NULL) {         // missing ':', ignore this part
   1948        continue;
   1949      }
   1950      *string++ = NUL;              // isolate flags from string
   1951 
   1952      // If we found a middle match previously, use that match when this
   1953      // is not a middle or end.
   1954      if (middle_match_len != 0
   1955          && vim_strchr(part_buf, COM_MIDDLE) == NULL
   1956          && vim_strchr(part_buf, COM_END) == NULL) {
   1957        break;
   1958      }
   1959 
   1960      // When we already found a nested comment, only accept further
   1961      // nested comments.
   1962      if (got_com && vim_strchr(part_buf, COM_NEST) == NULL) {
   1963        continue;
   1964      }
   1965 
   1966      // When 'O' flag present and using "O" command skip this one.
   1967      if (backward && vim_strchr(part_buf, COM_NOBACK) != NULL) {
   1968        continue;
   1969      }
   1970 
   1971      // Line contents and string must match.
   1972      // When string starts with white space, must have some white space
   1973      // (but the amount does not need to match, there might be a mix of
   1974      // TABs and spaces).
   1975      if (ascii_iswhite(string[0])) {
   1976        if (i == 0 || !ascii_iswhite(line[i - 1])) {
   1977          continue;            // missing white space
   1978        }
   1979        while (ascii_iswhite(string[0])) {
   1980          string++;
   1981        }
   1982      }
   1983      for (j = 0; string[j] != NUL && string[j] == line[i + j]; j++) {}
   1984      if (string[j] != NUL) {
   1985        continue;          // string doesn't match
   1986      }
   1987      // When 'b' flag used, there must be white space or an
   1988      // end-of-line after the string in the line.
   1989      if (vim_strchr(part_buf, COM_BLANK) != NULL
   1990          && !ascii_iswhite(line[i + j]) && line[i + j] != NUL) {
   1991        continue;
   1992      }
   1993 
   1994      // We have found a match, stop searching unless this is a middle
   1995      // comment. The middle comment can be a substring of the end
   1996      // comment in which case it's better to return the length of the
   1997      // end comment and its flags.  Thus we keep searching with middle
   1998      // and end matches and use an end match if it matches better.
   1999      if (vim_strchr(part_buf, COM_MIDDLE) != NULL) {
   2000        if (middle_match_len == 0) {
   2001          middle_match_len = j;
   2002          saved_flags = prev_list;
   2003        }
   2004        continue;
   2005      }
   2006      if (middle_match_len != 0 && j > middle_match_len) {
   2007        // Use this match instead of the middle match, since it's a
   2008        // longer thus better match.
   2009        middle_match_len = 0;
   2010      }
   2011 
   2012      if (middle_match_len == 0) {
   2013        i += j;
   2014      }
   2015      found_one = true;
   2016      break;
   2017    }
   2018 
   2019    if (middle_match_len != 0) {
   2020      // Use the previously found middle match after failing to find a
   2021      // match with an end.
   2022      if (!got_com && flags != NULL) {
   2023        *flags = saved_flags;
   2024      }
   2025      i += middle_match_len;
   2026      found_one = true;
   2027    }
   2028 
   2029    // No match found, stop scanning.
   2030    if (!found_one) {
   2031      break;
   2032    }
   2033 
   2034    result = i;
   2035 
   2036    // Include any trailing white space.
   2037    while (ascii_iswhite(line[i])) {
   2038      i++;
   2039    }
   2040 
   2041    if (include_space) {
   2042      result = i;
   2043    }
   2044 
   2045    // If this comment doesn't nest, stop here.
   2046    got_com = true;
   2047    if (vim_strchr(part_buf, COM_NEST) == NULL) {
   2048      break;
   2049    }
   2050  }
   2051  return result;
   2052 }
   2053 
   2054 /// Return the offset at which the last comment in line starts. If there is no
   2055 /// comment in the whole line, -1 is returned.
   2056 ///
   2057 /// When "flags" is not null, it is set to point to the flags describing the
   2058 /// recognized comment leader.
   2059 int get_last_leader_offset(char *line, char **flags)
   2060 {
   2061  int result = -1;
   2062  int j;
   2063  int lower_check_bound = 0;
   2064  char *com_leader;
   2065  char *com_flags;
   2066  char part_buf[COM_MAX_LEN];         // buffer for one option part
   2067 
   2068  // Repeat to match several nested comment strings.
   2069  int i = (int)strlen(line);
   2070  while (--i >= lower_check_bound) {
   2071    // scan through the 'comments' option for a match
   2072    bool found_one = false;
   2073    for (char *list = curbuf->b_p_com; *list;) {
   2074      char *flags_save = list;
   2075 
   2076      // Get one option part into part_buf[].  Advance list to next one.
   2077      // put string at start of string.
   2078      copy_option_part(&list, part_buf, COM_MAX_LEN, ",");
   2079      char *string = vim_strchr(part_buf, ':');
   2080      if (string == NULL) {  // If everything is fine, this cannot actually
   2081                             // happen.
   2082        continue;
   2083      }
   2084      *string++ = NUL;          // Isolate flags from string.
   2085      com_leader = string;
   2086 
   2087      // Line contents and string must match.
   2088      // When string starts with white space, must have some white space
   2089      // (but the amount does not need to match, there might be a mix of
   2090      // TABs and spaces).
   2091      if (ascii_iswhite(string[0])) {
   2092        if (i == 0 || !ascii_iswhite(line[i - 1])) {
   2093          continue;
   2094        }
   2095        while (ascii_iswhite(*string)) {
   2096          string++;
   2097        }
   2098      }
   2099      for (j = 0; string[j] != NUL && string[j] == line[i + j]; j++) {
   2100        // do nothing
   2101      }
   2102      if (string[j] != NUL) {
   2103        continue;
   2104      }
   2105 
   2106      // When 'b' flag used, there must be white space or an
   2107      // end-of-line after the string in the line.
   2108      if (vim_strchr(part_buf, COM_BLANK) != NULL
   2109          && !ascii_iswhite(line[i + j]) && line[i + j] != NUL) {
   2110        continue;
   2111      }
   2112 
   2113      if (vim_strchr(part_buf, COM_MIDDLE) != NULL) {
   2114        // For a middlepart comment, only consider it to match if
   2115        // everything before the current position in the line is
   2116        // whitespace.  Otherwise we would think we are inside a
   2117        // comment if the middle part appears somewhere in the middle
   2118        // of the line.  E.g. for C the "*" appears often.
   2119        for (j = 0; j <= i && ascii_iswhite(line[j]); j++) {}
   2120        if (j < i) {
   2121          continue;
   2122        }
   2123      }
   2124 
   2125      // We have found a match, stop searching.
   2126      found_one = true;
   2127 
   2128      if (flags) {
   2129        *flags = flags_save;
   2130      }
   2131      com_flags = flags_save;
   2132 
   2133      break;
   2134    }
   2135 
   2136    if (found_one) {
   2137      char part_buf2[COM_MAX_LEN];            // buffer for one option part
   2138 
   2139      result = i;
   2140      // If this comment nests, continue searching.
   2141      if (vim_strchr(part_buf, COM_NEST) != NULL) {
   2142        continue;
   2143      }
   2144 
   2145      lower_check_bound = i;
   2146 
   2147      // Let's verify whether the comment leader found is a substring
   2148      // of other comment leaders. If it is, let's adjust the
   2149      // lower_check_bound so that we make sure that we have determined
   2150      // the comment leader correctly.
   2151 
   2152      while (ascii_iswhite(*com_leader)) {
   2153        com_leader++;
   2154      }
   2155      int len1 = (int)strlen(com_leader);
   2156 
   2157      for (char *list = curbuf->b_p_com; *list;) {
   2158        char *flags_save = list;
   2159 
   2160        copy_option_part(&list, part_buf2, COM_MAX_LEN, ",");
   2161        if (flags_save == com_flags) {
   2162          continue;
   2163        }
   2164        char *string = vim_strchr(part_buf2, ':');
   2165        string++;
   2166        while (ascii_iswhite(*string)) {
   2167          string++;
   2168        }
   2169        int len2 = (int)strlen(string);
   2170        if (len2 == 0) {
   2171          continue;
   2172        }
   2173 
   2174        // Now we have to verify whether string ends with a substring
   2175        // beginning the com_leader.
   2176        for (int off = (len2 > i ? i : len2); off > 0 && off + len1 > len2;) {
   2177          off--;
   2178          if (!strncmp(string + off, com_leader, (size_t)(len2 - off))) {
   2179            lower_check_bound = MIN(lower_check_bound, i - off);
   2180          }
   2181        }
   2182      }
   2183    }
   2184  }
   2185  return result;
   2186 }