aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/ops.c
diff options
context:
space:
mode:
authorEliseo Martínez <eliseomarmol@gmail.com>2014-05-12 02:25:17 +0200
committerEliseo Martínez <eliseomarmol@gmail.com>2014-05-15 20:46:01 +0200
commitda51dc9cf202772f60bd2da975dbef257bd9237c (patch)
tree5c16b93238a153f55634e9323077f30c8133970c /src/nvim/ops.c
parentffe61e5ba1721340ca51d56bae3ddaca415fb5bc (diff)
downloadrneovim-da51dc9cf202772f60bd2da975dbef257bd9237c.tar.gz
rneovim-da51dc9cf202772f60bd2da975dbef257bd9237c.tar.bz2
rneovim-da51dc9cf202772f60bd2da975dbef257bd9237c.zip
Introduce nvim namespace: Move files.
Move files from src/ to src/nvim/. - src/nvim/ becomes the new root dir for nvim executable sources. - src/libnvim/ is planned to become root dir of the neovim library.
Diffstat (limited to 'src/nvim/ops.c')
-rw-r--r--src/nvim/ops.c5174
1 files changed, 5174 insertions, 0 deletions
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
new file mode 100644
index 0000000000..30606b077e
--- /dev/null
+++ b/src/nvim/ops.c
@@ -0,0 +1,5174 @@
+/*
+ * VIM - Vi IMproved by Bram Moolenaar
+ *
+ * Do ":help uganda" in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+/*
+ * ops.c: implementation of various operators: op_shift, op_delete, op_tilde,
+ * op_change, op_yank, do_put, do_join
+ */
+
+#include <string.h>
+
+#include "vim.h"
+#include "ops.h"
+#include "buffer.h"
+#include "charset.h"
+#include "edit.h"
+#include "eval.h"
+#include "ex_cmds.h"
+#include "ex_cmds2.h"
+#include "ex_getln.h"
+#include "fold.h"
+#include "getchar.h"
+#include "indent.h"
+#include "mark.h"
+#include "mbyte.h"
+#include "memline.h"
+#include "memory.h"
+#include "message.h"
+#include "misc1.h"
+#include "misc2.h"
+#include "move.h"
+#include "normal.h"
+#include "option.h"
+#include "path.h"
+#include "screen.h"
+#include "search.h"
+#include "term.h"
+#include "ui.h"
+#include "undo.h"
+#include "window.h"
+
+/*
+ * Registers:
+ * 0 = unnamed register, for normal yanks and puts
+ * 1..9 = registers '1' to '9', for deletes
+ * 10..35 = registers 'a' to 'z'
+ * 36 = delete register '-'
+ */
+#define NUM_REGISTERS 37
+#define DELETION_REGISTER 36
+
+/*
+ * Each yank register is an array of pointers to lines.
+ */
+static struct yankreg {
+ char_u **y_array; /* pointer to array of line pointers */
+ linenr_T y_size; /* number of lines in y_array */
+ char_u y_type; /* MLINE, MCHAR or MBLOCK */
+ colnr_T y_width; /* only set if y_type == MBLOCK */
+} y_regs[NUM_REGISTERS];
+
+static struct yankreg *y_current; /* ptr to current yankreg */
+static int y_append; /* TRUE when appending */
+static struct yankreg *y_previous = NULL; /* ptr to last written yankreg */
+
+/*
+ * structure used by block_prep, op_delete and op_yank for blockwise operators
+ * also op_change, op_shift, op_insert, op_replace - AKelly
+ */
+struct block_def {
+ int startspaces; /* 'extra' cols before first char */
+ int endspaces; /* 'extra' cols after last char */
+ int textlen; /* chars in block */
+ char_u *textstart; /* pointer to 1st char (partially) in block */
+ colnr_T textcol; /* index of chars (partially) in block */
+ colnr_T start_vcol; /* start col of 1st char wholly inside block */
+ colnr_T end_vcol; /* start col of 1st char wholly after block */
+ int is_short; /* TRUE if line is too short to fit in block */
+ int is_MAX; /* TRUE if curswant==MAXCOL when starting */
+ int is_oneChar; /* TRUE if block within one character */
+ int pre_whitesp; /* screen cols of ws before block */
+ int pre_whitesp_c; /* chars of ws before block */
+ colnr_T end_char_vcols; /* number of vcols of post-block char */
+ colnr_T start_char_vcols; /* number of vcols of pre-block char */
+};
+
+static void shift_block(oparg_T *oap, int amount);
+static void block_insert(oparg_T *oap, char_u *s, int b_insert,
+ struct block_def*bdp);
+static int stuff_yank(int, char_u *);
+static void put_reedit_in_typebuf(int silent);
+static int put_in_typebuf(char_u *s, int esc, int colon,
+ int silent);
+static void stuffescaped(char_u *arg, int literally);
+static void mb_adjust_opend(oparg_T *oap);
+static void free_yank(long);
+static void free_yank_all(void);
+static void yank_copy_line(struct block_def *bd, long y_idx);
+static void dis_msg(char_u *p, int skip_esc);
+static char_u *skip_comment(char_u *line, int process,
+ int include_space,
+ int *is_comment);
+static void block_prep(oparg_T *oap, struct block_def *, linenr_T, int);
+static void str_to_reg(struct yankreg *y_ptr, int type, char_u *str,
+ long len,
+ long blocklen);
+static int ends_in_white(linenr_T lnum);
+static int same_leader(linenr_T lnum, int, char_u *, int, char_u *);
+static int fmt_check_par(linenr_T, int *, char_u **, int do_comments);
+
+/*
+ * The names of operators.
+ * IMPORTANT: Index must correspond with defines in vim.h!!!
+ * The third field indicates whether the operator always works on lines.
+ */
+static char opchars[][3] =
+{
+ {NUL, NUL, FALSE}, /* OP_NOP */
+ {'d', NUL, FALSE}, /* OP_DELETE */
+ {'y', NUL, FALSE}, /* OP_YANK */
+ {'c', NUL, FALSE}, /* OP_CHANGE */
+ {'<', NUL, TRUE}, /* OP_LSHIFT */
+ {'>', NUL, TRUE}, /* OP_RSHIFT */
+ {'!', NUL, TRUE}, /* OP_FILTER */
+ {'g', '~', FALSE}, /* OP_TILDE */
+ {'=', NUL, TRUE}, /* OP_INDENT */
+ {'g', 'q', TRUE}, /* OP_FORMAT */
+ {':', NUL, TRUE}, /* OP_COLON */
+ {'g', 'U', FALSE}, /* OP_UPPER */
+ {'g', 'u', FALSE}, /* OP_LOWER */
+ {'J', NUL, TRUE}, /* DO_JOIN */
+ {'g', 'J', TRUE}, /* DO_JOIN_NS */
+ {'g', '?', FALSE}, /* OP_ROT13 */
+ {'r', NUL, FALSE}, /* OP_REPLACE */
+ {'I', NUL, FALSE}, /* OP_INSERT */
+ {'A', NUL, FALSE}, /* OP_APPEND */
+ {'z', 'f', TRUE}, /* OP_FOLD */
+ {'z', 'o', TRUE}, /* OP_FOLDOPEN */
+ {'z', 'O', TRUE}, /* OP_FOLDOPENREC */
+ {'z', 'c', TRUE}, /* OP_FOLDCLOSE */
+ {'z', 'C', TRUE}, /* OP_FOLDCLOSEREC */
+ {'z', 'd', TRUE}, /* OP_FOLDDEL */
+ {'z', 'D', TRUE}, /* OP_FOLDDELREC */
+ {'g', 'w', TRUE}, /* OP_FORMAT2 */
+ {'g', '@', FALSE}, /* OP_FUNCTION */
+};
+
+/*
+ * Translate a command name into an operator type.
+ * Must only be called with a valid operator name!
+ */
+int get_op_type(int char1, int char2)
+{
+ int i;
+
+ if (char1 == 'r') /* ignore second character */
+ return OP_REPLACE;
+ if (char1 == '~') /* when tilde is an operator */
+ return OP_TILDE;
+ for (i = 0;; ++i)
+ if (opchars[i][0] == char1 && opchars[i][1] == char2)
+ break;
+ return i;
+}
+
+/*
+ * Return TRUE if operator "op" always works on whole lines.
+ */
+int op_on_lines(int op)
+{
+ return opchars[op][2];
+}
+
+/*
+ * Get first operator command character.
+ * Returns 'g' or 'z' if there is another command character.
+ */
+int get_op_char(int optype)
+{
+ return opchars[optype][0];
+}
+
+/*
+ * Get second operator command character.
+ */
+int get_extra_op_char(int optype)
+{
+ return opchars[optype][1];
+}
+
+/*
+ * op_shift - handle a shift operation
+ */
+void op_shift(oparg_T *oap, int curs_top, int amount)
+{
+ long i;
+ int first_char;
+ char_u *s;
+ int block_col = 0;
+
+ if (u_save((linenr_T)(oap->start.lnum - 1),
+ (linenr_T)(oap->end.lnum + 1)) == FAIL)
+ return;
+
+ if (oap->block_mode)
+ block_col = curwin->w_cursor.col;
+
+ for (i = oap->line_count; --i >= 0; ) {
+ first_char = *ml_get_curline();
+ if (first_char == NUL) /* empty line */
+ curwin->w_cursor.col = 0;
+ else if (oap->block_mode)
+ shift_block(oap, amount);
+ else
+ /* Move the line right if it doesn't start with '#', 'smartindent'
+ * isn't set or 'cindent' isn't set or '#' isn't in 'cino'. */
+ if (first_char != '#' || !preprocs_left()) {
+ shift_line(oap->op_type == OP_LSHIFT, p_sr, amount, FALSE);
+ }
+ ++curwin->w_cursor.lnum;
+ }
+
+ changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L);
+ /* The cursor line is not in a closed fold */
+ foldOpenCursor();
+
+ if (oap->block_mode) {
+ curwin->w_cursor.lnum = oap->start.lnum;
+ curwin->w_cursor.col = block_col;
+ } else if (curs_top) { /* put cursor on first line, for ">>" */
+ curwin->w_cursor.lnum = oap->start.lnum;
+ beginline(BL_SOL | BL_FIX); /* shift_line() may have set cursor.col */
+ } else
+ --curwin->w_cursor.lnum; /* put cursor on last line, for ":>" */
+
+ if (oap->line_count > p_report) {
+ if (oap->op_type == OP_RSHIFT)
+ s = (char_u *)">";
+ else
+ s = (char_u *)"<";
+ if (oap->line_count == 1) {
+ if (amount == 1)
+ sprintf((char *)IObuff, _("1 line %sed 1 time"), s);
+ else
+ sprintf((char *)IObuff, _("1 line %sed %d times"), s, amount);
+ } else {
+ if (amount == 1)
+ sprintf((char *)IObuff, _("%" PRId64 " lines %sed 1 time"),
+ (int64_t)oap->line_count, s);
+ else
+ sprintf((char *)IObuff, _("%" PRId64 " lines %sed %d times"),
+ (int64_t)oap->line_count, s, amount);
+ }
+ msg(IObuff);
+ }
+
+ /*
+ * Set "'[" and "']" marks.
+ */
+ curbuf->b_op_start = oap->start;
+ curbuf->b_op_end.lnum = oap->end.lnum;
+ curbuf->b_op_end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum));
+ if (curbuf->b_op_end.col > 0)
+ --curbuf->b_op_end.col;
+}
+
+/*
+ * shift the current line one shiftwidth left (if left != 0) or right
+ * leaves cursor on first blank in the line
+ */
+void
+shift_line (
+ int left,
+ int round,
+ int amount,
+ int call_changed_bytes /* call changed_bytes() */
+)
+{
+ int count;
+ int i, j;
+ int p_sw = (int)get_sw_value(curbuf);
+
+ count = get_indent(); /* get current indent */
+
+ if (round) { /* round off indent */
+ i = count / p_sw; /* number of p_sw rounded down */
+ j = count % p_sw; /* extra spaces */
+ if (j && left) /* first remove extra spaces */
+ --amount;
+ if (left) {
+ i -= amount;
+ if (i < 0)
+ i = 0;
+ } else
+ i += amount;
+ count = i * p_sw;
+ } else { /* original vi indent */
+ if (left) {
+ count -= p_sw * amount;
+ if (count < 0)
+ count = 0;
+ } else
+ count += p_sw * amount;
+ }
+
+ /* Set new indent */
+ if (State & VREPLACE_FLAG)
+ change_indent(INDENT_SET, count, FALSE, NUL, call_changed_bytes);
+ else
+ (void)set_indent(count, call_changed_bytes ? SIN_CHANGED : 0);
+}
+
+/*
+ * Shift one line of the current block one shiftwidth right or left.
+ * Leaves cursor on first character in block.
+ */
+static void shift_block(oparg_T *oap, int amount)
+{
+ int left = (oap->op_type == OP_LSHIFT);
+ int oldstate = State;
+ int total;
+ char_u *newp, *oldp;
+ int oldcol = curwin->w_cursor.col;
+ int p_sw = (int)get_sw_value(curbuf);
+ int p_ts = (int)curbuf->b_p_ts;
+ struct block_def bd;
+ int incr;
+ colnr_T ws_vcol;
+ int i = 0, j = 0;
+ int len;
+ int old_p_ri = p_ri;
+
+ p_ri = 0; /* don't want revins in indent */
+
+ State = INSERT; /* don't want REPLACE for State */
+ block_prep(oap, &bd, curwin->w_cursor.lnum, TRUE);
+ if (bd.is_short)
+ return;
+
+ /* total is number of screen columns to be inserted/removed */
+ total = amount * p_sw;
+ oldp = ml_get_curline();
+
+ if (!left) {
+ /*
+ * 1. Get start vcol
+ * 2. Total ws vcols
+ * 3. Divvy into TABs & spp
+ * 4. Construct new string
+ */
+ total += bd.pre_whitesp; /* all virtual WS upto & incl a split TAB */
+ ws_vcol = bd.start_vcol - bd.pre_whitesp;
+ if (bd.startspaces) {
+ if (has_mbyte)
+ bd.textstart += (*mb_ptr2len)(bd.textstart);
+ else
+ ++bd.textstart;
+ }
+ for (; vim_iswhite(*bd.textstart); ) {
+ incr = lbr_chartabsize_adv(&bd.textstart, (colnr_T)(bd.start_vcol));
+ total += incr;
+ bd.start_vcol += incr;
+ }
+ /* OK, now total=all the VWS reqd, and textstart points at the 1st
+ * non-ws char in the block. */
+ if (!curbuf->b_p_et)
+ i = ((ws_vcol % p_ts) + total) / p_ts; /* number of tabs */
+ if (i)
+ j = ((ws_vcol % p_ts) + total) % p_ts; /* number of spp */
+ else
+ j = total;
+ /* if we're splitting a TAB, allow for it */
+ bd.textcol -= bd.pre_whitesp_c - (bd.startspaces != 0);
+ len = (int)STRLEN(bd.textstart) + 1;
+ newp = (char_u *) xmalloc((size_t)(bd.textcol + i + j + len));
+ memset(newp, NUL, (size_t)(bd.textcol + i + j + len));
+ memmove(newp, oldp, (size_t)bd.textcol);
+ copy_chars(newp + bd.textcol, (size_t)i, TAB);
+ copy_spaces(newp + bd.textcol + i, (size_t)j);
+ /* the end */
+ memmove(newp + bd.textcol + i + j, bd.textstart, (size_t)len);
+ } else { /* left */
+ colnr_T destination_col; /* column to which text in block will
+ be shifted */
+ char_u *verbatim_copy_end; /* end of the part of the line which is
+ copied verbatim */
+ colnr_T verbatim_copy_width; /* the (displayed) width of this part
+ of line */
+ unsigned fill; /* nr of spaces that replace a TAB */
+ unsigned new_line_len; /* the length of the line after the
+ block shift */
+ size_t block_space_width;
+ size_t shift_amount;
+ char_u *non_white = bd.textstart;
+ colnr_T non_white_col;
+
+ /*
+ * Firstly, let's find the first non-whitespace character that is
+ * displayed after the block's start column and the character's column
+ * number. Also, let's calculate the width of all the whitespace
+ * characters that are displayed in the block and precede the searched
+ * non-whitespace character.
+ */
+
+ /* If "bd.startspaces" is set, "bd.textstart" points to the character,
+ * the part of which is displayed at the block's beginning. Let's start
+ * searching from the next character. */
+ if (bd.startspaces)
+ mb_ptr_adv(non_white);
+
+ /* The character's column is in "bd.start_vcol". */
+ non_white_col = bd.start_vcol;
+
+ while (vim_iswhite(*non_white)) {
+ incr = lbr_chartabsize_adv(&non_white, non_white_col);
+ non_white_col += incr;
+ }
+
+ block_space_width = non_white_col - oap->start_vcol;
+ /* We will shift by "total" or "block_space_width", whichever is less.
+ */
+ shift_amount = (block_space_width < (size_t)total
+ ? block_space_width : (size_t)total);
+
+ /* The column to which we will shift the text. */
+ destination_col = (colnr_T)(non_white_col - shift_amount);
+
+ /* Now let's find out how much of the beginning of the line we can
+ * reuse without modification. */
+ verbatim_copy_end = bd.textstart;
+ verbatim_copy_width = bd.start_vcol;
+
+ /* If "bd.startspaces" is set, "bd.textstart" points to the character
+ * preceding the block. We have to subtract its width to obtain its
+ * column number. */
+ if (bd.startspaces)
+ verbatim_copy_width -= bd.start_char_vcols;
+ while (verbatim_copy_width < destination_col) {
+ incr = lbr_chartabsize(verbatim_copy_end, verbatim_copy_width);
+ if (verbatim_copy_width + incr > destination_col)
+ break;
+ verbatim_copy_width += incr;
+ mb_ptr_adv(verbatim_copy_end);
+ }
+
+ /* If "destination_col" is different from the width of the initial
+ * part of the line that will be copied, it means we encountered a tab
+ * character, which we will have to partly replace with spaces. */
+ fill = destination_col - verbatim_copy_width;
+
+ /* The replacement line will consist of:
+ * - the beginning of the original line up to "verbatim_copy_end",
+ * - "fill" number of spaces,
+ * - the rest of the line, pointed to by non_white. */
+ new_line_len = (unsigned)(verbatim_copy_end - oldp)
+ + fill
+ + (unsigned)STRLEN(non_white) + 1;
+
+ newp = (char_u *) xmalloc((size_t)(new_line_len));
+ memmove(newp, oldp, (size_t)(verbatim_copy_end - oldp));
+ copy_spaces(newp + (verbatim_copy_end - oldp), (size_t)fill);
+ STRMOVE(newp + (verbatim_copy_end - oldp) + fill, non_white);
+ }
+ /* replace the line */
+ ml_replace(curwin->w_cursor.lnum, newp, FALSE);
+ changed_bytes(curwin->w_cursor.lnum, (colnr_T)bd.textcol);
+ State = oldstate;
+ curwin->w_cursor.col = oldcol;
+ p_ri = old_p_ri;
+}
+
+/*
+ * Insert string "s" (b_insert ? before : after) block :AKelly
+ * Caller must prepare for undo.
+ */
+static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def *bdp)
+{
+ int p_ts;
+ int count = 0; /* extra spaces to replace a cut TAB */
+ int spaces = 0; /* non-zero if cutting a TAB */
+ colnr_T offset; /* pointer along new line */
+ unsigned s_len; /* STRLEN(s) */
+ char_u *newp, *oldp; /* new, old lines */
+ linenr_T lnum; /* loop var */
+ int oldstate = State;
+
+ State = INSERT; /* don't want REPLACE for State */
+ s_len = (unsigned)STRLEN(s);
+
+ for (lnum = oap->start.lnum + 1; lnum <= oap->end.lnum; lnum++) {
+ block_prep(oap, bdp, lnum, TRUE);
+ if (bdp->is_short && b_insert)
+ continue; /* OP_INSERT, line ends before block start */
+
+ oldp = ml_get(lnum);
+
+ if (b_insert) {
+ p_ts = bdp->start_char_vcols;
+ spaces = bdp->startspaces;
+ if (spaces != 0)
+ count = p_ts - 1; /* we're cutting a TAB */
+ offset = bdp->textcol;
+ } else { /* append */
+ p_ts = bdp->end_char_vcols;
+ if (!bdp->is_short) { /* spaces = padding after block */
+ spaces = (bdp->endspaces ? p_ts - bdp->endspaces : 0);
+ if (spaces != 0)
+ count = p_ts - 1; /* we're cutting a TAB */
+ offset = bdp->textcol + bdp->textlen - (spaces != 0);
+ } else { /* spaces = padding to block edge */
+ /* if $ used, just append to EOL (ie spaces==0) */
+ if (!bdp->is_MAX)
+ spaces = (oap->end_vcol - bdp->end_vcol) + 1;
+ count = spaces;
+ offset = bdp->textcol + bdp->textlen;
+ }
+ }
+
+ newp = (char_u *) xmalloc((size_t)(STRLEN(oldp) + s_len + count + 1));
+
+ /* copy up to shifted part */
+ memmove(newp, oldp, (size_t)(offset));
+ oldp += offset;
+
+ /* insert pre-padding */
+ copy_spaces(newp + offset, (size_t)spaces);
+
+ /* copy the new text */
+ memmove(newp + offset + spaces, s, (size_t)s_len);
+ offset += s_len;
+
+ if (spaces && !bdp->is_short) {
+ /* insert post-padding */
+ copy_spaces(newp + offset + spaces, (size_t)(p_ts - spaces));
+ /* We're splitting a TAB, don't copy it. */
+ oldp++;
+ /* We allowed for that TAB, remember this now */
+ count++;
+ }
+
+ if (spaces > 0)
+ offset += count;
+ STRMOVE(newp + offset, oldp);
+
+ ml_replace(lnum, newp, FALSE);
+
+ if (lnum == oap->end.lnum) {
+ /* Set "']" mark to the end of the block instead of the end of
+ * the insert in the first line. */
+ curbuf->b_op_end.lnum = oap->end.lnum;
+ curbuf->b_op_end.col = offset;
+ }
+ } /* for all lnum */
+
+ changed_lines(oap->start.lnum + 1, 0, oap->end.lnum + 1, 0L);
+
+ State = oldstate;
+}
+
+/*
+ * op_reindent - handle reindenting a block of lines.
+ */
+void op_reindent(oap, how)
+oparg_T *oap;
+int (*how)(void);
+{
+ long i;
+ char_u *l;
+ int count;
+ linenr_T first_changed = 0;
+ linenr_T last_changed = 0;
+ linenr_T start_lnum = curwin->w_cursor.lnum;
+
+ /* Don't even try when 'modifiable' is off. */
+ if (!curbuf->b_p_ma) {
+ EMSG(_(e_modifiable));
+ return;
+ }
+
+ for (i = oap->line_count; --i >= 0 && !got_int; ) {
+ /* it's a slow thing to do, so give feedback so there's no worry that
+ * the computer's just hung. */
+
+ if (i > 1
+ && (i % 50 == 0 || i == oap->line_count - 1)
+ && oap->line_count > p_report)
+ smsg((char_u *)_("%" PRId64 " lines to indent... "), (int64_t)i);
+
+ /*
+ * Be vi-compatible: For lisp indenting the first line is not
+ * indented, unless there is only one line.
+ */
+ if (i != oap->line_count - 1 || oap->line_count == 1
+ || how != get_lisp_indent) {
+ l = skipwhite(ml_get_curline());
+ if (*l == NUL) /* empty or blank line */
+ count = 0;
+ else
+ count = how(); /* get the indent for this line */
+
+ if (set_indent(count, SIN_UNDO)) {
+ /* did change the indent, call changed_lines() later */
+ if (first_changed == 0)
+ first_changed = curwin->w_cursor.lnum;
+ last_changed = curwin->w_cursor.lnum;
+ }
+ }
+ ++curwin->w_cursor.lnum;
+ curwin->w_cursor.col = 0; /* make sure it's valid */
+ }
+
+ /* put cursor on first non-blank of indented line */
+ curwin->w_cursor.lnum = start_lnum;
+ beginline(BL_SOL | BL_FIX);
+
+ /* Mark changed lines so that they will be redrawn. When Visual
+ * highlighting was present, need to continue until the last line. When
+ * there is no change still need to remove the Visual highlighting. */
+ if (last_changed != 0)
+ changed_lines(first_changed, 0,
+ oap->is_VIsual ? start_lnum + oap->line_count :
+ last_changed + 1, 0L);
+ else if (oap->is_VIsual)
+ redraw_curbuf_later(INVERTED);
+
+ if (oap->line_count > p_report) {
+ i = oap->line_count - (i + 1);
+ if (i == 1)
+ MSG(_("1 line indented "));
+ else
+ smsg((char_u *)_("%" PRId64 " lines indented "), (int64_t)i);
+ }
+ /* set '[ and '] marks */
+ curbuf->b_op_start = oap->start;
+ curbuf->b_op_end = oap->end;
+}
+
+/*
+ * Keep the last expression line here, for repeating.
+ */
+static char_u *expr_line = NULL;
+
+/*
+ * Get an expression for the "\"=expr1" or "CTRL-R =expr1"
+ * Returns '=' when OK, NUL otherwise.
+ */
+int get_expr_register(void)
+{
+ char_u *new_line;
+
+ new_line = getcmdline('=', 0L, 0);
+ if (new_line == NULL)
+ return NUL;
+ if (*new_line == NUL) /* use previous line */
+ free(new_line);
+ else
+ set_expr_line(new_line);
+ return '=';
+}
+
+/*
+ * Set the expression for the '=' register.
+ * Argument must be an allocated string.
+ */
+void set_expr_line(char_u *new_line)
+{
+ free(expr_line);
+ expr_line = new_line;
+}
+
+/*
+ * Get the result of the '=' register expression.
+ * Returns a pointer to allocated memory, or NULL for failure.
+ */
+char_u *get_expr_line(void)
+{
+ char_u *expr_copy;
+ char_u *rv;
+ static int nested = 0;
+
+ if (expr_line == NULL)
+ return NULL;
+
+ /* Make a copy of the expression, because evaluating it may cause it to be
+ * changed. */
+ expr_copy = vim_strsave(expr_line);
+ if (expr_copy == NULL)
+ return NULL;
+
+ /* When we are invoked recursively limit the evaluation to 10 levels.
+ * Then return the string as-is. */
+ if (nested >= 10)
+ return expr_copy;
+
+ ++nested;
+ rv = eval_to_string(expr_copy, NULL, TRUE);
+ --nested;
+ free(expr_copy);
+ return rv;
+}
+
+/*
+ * Get the '=' register expression itself, without evaluating it.
+ */
+char_u *get_expr_line_src(void)
+{
+ if (expr_line == NULL)
+ return NULL;
+ return vim_strsave(expr_line);
+}
+
+/*
+ * Check if 'regname' is a valid name of a yank register.
+ * Note: There is no check for 0 (default register), caller should do this
+ */
+int
+valid_yank_reg (
+ int regname,
+ int writing /* if TRUE check for writable registers */
+)
+{
+ if ( (regname > 0 && ASCII_ISALNUM(regname))
+ || (!writing && vim_strchr((char_u *)
+ "/.%#:="
+ , regname) != NULL)
+ || regname == '"'
+ || regname == '-'
+ || regname == '_'
+ )
+ return TRUE;
+ return FALSE;
+}
+
+/*
+ * Set y_current and y_append, according to the value of "regname".
+ * Cannot handle the '_' register.
+ * Must only be called with a valid register name!
+ *
+ * If regname is 0 and writing, use register 0
+ * If regname is 0 and reading, use previous register
+ */
+void get_yank_register(int regname, int writing)
+{
+ int i;
+
+ y_append = FALSE;
+ if ((regname == 0 || regname == '"') && !writing && y_previous != NULL) {
+ y_current = y_previous;
+ return;
+ }
+ i = regname;
+ if (VIM_ISDIGIT(i))
+ i -= '0';
+ else if (ASCII_ISLOWER(i))
+ i = CharOrdLow(i) + 10;
+ else if (ASCII_ISUPPER(i)) {
+ i = CharOrdUp(i) + 10;
+ y_append = TRUE;
+ } else if (regname == '-')
+ i = DELETION_REGISTER;
+ else /* not 0-9, a-z, A-Z or '-': use register 0 */
+ i = 0;
+ y_current = &(y_regs[i]);
+ if (writing) /* remember the register we write into for do_put() */
+ y_previous = y_current;
+}
+
+
+/*
+ * Obtain the contents of a "normal" register. The register is made empty.
+ * The returned pointer has allocated memory, use put_register() later.
+ */
+void *
+get_register (
+ int name,
+ int copy /* make a copy, if FALSE make register empty. */
+)
+{
+ get_yank_register(name, 0);
+
+ struct yankreg *reg = xmalloc(sizeof(struct yankreg));
+ *reg = *y_current;
+ if (copy) {
+ if (reg->y_size == 0) {
+ reg->y_array = NULL;
+ } else {
+ reg->y_array = xmalloc(reg->y_size * sizeof(char_u *));
+ for (linenr_T i = 0; i < reg->y_size; ++i) {
+ reg->y_array[i] = vim_strsave(y_current->y_array[i]);
+ }
+ }
+ } else
+ y_current->y_array = NULL;
+
+ return (void *)reg;
+}
+
+/*
+ * Put "reg" into register "name". Free any previous contents and "reg".
+ */
+void put_register(int name, void *reg)
+{
+ get_yank_register(name, 0);
+ free_yank_all();
+ *y_current = *(struct yankreg *)reg;
+ free(reg);
+
+}
+
+/*
+ * return TRUE if the current yank register has type MLINE
+ */
+int yank_register_mline(int regname)
+{
+ if (regname != 0 && !valid_yank_reg(regname, FALSE))
+ return FALSE;
+ if (regname == '_') /* black hole is always empty */
+ return FALSE;
+ get_yank_register(regname, FALSE);
+ return y_current->y_type == MLINE;
+}
+
+/*
+ * Start or stop recording into a yank register.
+ *
+ * Return FAIL for failure, OK otherwise.
+ */
+int do_record(int c)
+{
+ char_u *p;
+ static int regname;
+ struct yankreg *old_y_previous, *old_y_current;
+ int retval;
+
+ if (Recording == FALSE) { /* start recording */
+ /* registers 0-9, a-z and " are allowed */
+ if (c < 0 || (!ASCII_ISALNUM(c) && c != '"'))
+ retval = FAIL;
+ else {
+ Recording = TRUE;
+ showmode();
+ regname = c;
+ retval = OK;
+ }
+ } else { /* stop recording */
+ /*
+ * Get the recorded key hits. K_SPECIAL and CSI will be escaped, this
+ * needs to be removed again to put it in a register. exec_reg then
+ * adds the escaping back later.
+ */
+ Recording = FALSE;
+ MSG("");
+ p = get_recorded();
+ if (p == NULL)
+ retval = FAIL;
+ else {
+ /* Remove escaping for CSI and K_SPECIAL in multi-byte chars. */
+ vim_unescape_csi(p);
+
+ /*
+ * We don't want to change the default register here, so save and
+ * restore the current register name.
+ */
+ old_y_previous = y_previous;
+ old_y_current = y_current;
+
+ retval = stuff_yank(regname, p);
+
+ y_previous = old_y_previous;
+ y_current = old_y_current;
+ }
+ }
+ return retval;
+}
+
+/*
+ * Stuff string "p" into yank register "regname" as a single line (append if
+ * uppercase). "p" must have been alloced.
+ *
+ * return FAIL for failure, OK otherwise
+ */
+static int stuff_yank(int regname, char_u *p)
+{
+ /* check for read-only register */
+ if (regname != 0 && !valid_yank_reg(regname, TRUE)) {
+ free(p);
+ return FAIL;
+ }
+ if (regname == '_') { /* black hole: don't do anything */
+ free(p);
+ return OK;
+ }
+ get_yank_register(regname, TRUE);
+ if (y_append && y_current->y_array != NULL) {
+ char_u **pp = &(y_current->y_array[y_current->y_size - 1]);
+ char_u *lp = xmalloc(STRLEN(*pp) + STRLEN(p) + 1);
+ STRCPY(lp, *pp);
+ // TODO(philix): use xstpcpy() in stuff_yank()
+ STRCAT(lp, p);
+ free(p);
+ free(*pp);
+ *pp = lp;
+ } else {
+ free_yank_all();
+ y_current->y_array = (char_u **)alloc((unsigned)sizeof(char_u *));
+ y_current->y_array[0] = p;
+ y_current->y_size = 1;
+ y_current->y_type = MCHAR; /* used to be MLINE, why? */
+ }
+ return OK;
+}
+
+static int execreg_lastc = NUL;
+
+/*
+ * execute a yank register: copy it into the stuff buffer
+ *
+ * return FAIL for failure, OK otherwise
+ */
+int
+do_execreg (
+ int regname,
+ int colon, /* insert ':' before each line */
+ int addcr, /* always add '\n' to end of line */
+ int silent /* set "silent" flag in typeahead buffer */
+)
+{
+ long i;
+ char_u *p;
+ int retval = OK;
+ int remap;
+
+ if (regname == '@') { /* repeat previous one */
+ if (execreg_lastc == NUL) {
+ EMSG(_("E748: No previously used register"));
+ return FAIL;
+ }
+ regname = execreg_lastc;
+ }
+ /* check for valid regname */
+ if (regname == '%' || regname == '#' || !valid_yank_reg(regname, FALSE)) {
+ emsg_invreg(regname);
+ return FAIL;
+ }
+ execreg_lastc = regname;
+
+
+ if (regname == '_') /* black hole: don't stuff anything */
+ return OK;
+
+ if (regname == ':') { /* use last command line */
+ if (last_cmdline == NULL) {
+ EMSG(_(e_nolastcmd));
+ return FAIL;
+ }
+ free(new_last_cmdline); /* don't keep the cmdline containing @: */
+ new_last_cmdline = NULL;
+ /* Escape all control characters with a CTRL-V */
+ p = vim_strsave_escaped_ext(
+ last_cmdline,
+ (char_u *)
+ "\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037",
+ Ctrl_V, FALSE);
+ if (p != NULL) {
+ /* When in Visual mode "'<,'>" will be prepended to the command.
+ * Remove it when it's already there. */
+ if (VIsual_active && STRNCMP(p, "'<,'>", 5) == 0)
+ retval = put_in_typebuf(p + 5, TRUE, TRUE, silent);
+ else
+ retval = put_in_typebuf(p, TRUE, TRUE, silent);
+ }
+ free(p);
+ } else if (regname == '=') {
+ p = get_expr_line();
+ if (p == NULL)
+ return FAIL;
+ retval = put_in_typebuf(p, TRUE, colon, silent);
+ free(p);
+ } else if (regname == '.') { /* use last inserted text */
+ p = get_last_insert_save();
+ if (p == NULL) {
+ EMSG(_(e_noinstext));
+ return FAIL;
+ }
+ retval = put_in_typebuf(p, FALSE, colon, silent);
+ free(p);
+ } else {
+ get_yank_register(regname, FALSE);
+ if (y_current->y_array == NULL)
+ return FAIL;
+
+ /* Disallow remaping for ":@r". */
+ remap = colon ? REMAP_NONE : REMAP_YES;
+
+ /*
+ * Insert lines into typeahead buffer, from last one to first one.
+ */
+ put_reedit_in_typebuf(silent);
+ for (i = y_current->y_size; --i >= 0; ) {
+ char_u *escaped;
+
+ /* insert NL between lines and after last line if type is MLINE */
+ if (y_current->y_type == MLINE || i < y_current->y_size - 1
+ || addcr) {
+ if (ins_typebuf((char_u *)"\n", remap, 0, TRUE, silent) == FAIL)
+ return FAIL;
+ }
+ escaped = vim_strsave_escape_csi(y_current->y_array[i]);
+ if (escaped == NULL)
+ return FAIL;
+ retval = ins_typebuf(escaped, remap, 0, TRUE, silent);
+ free(escaped);
+ if (retval == FAIL)
+ return FAIL;
+ if (colon && ins_typebuf((char_u *)":", remap, 0, TRUE, silent)
+ == FAIL)
+ return FAIL;
+ }
+ Exec_reg = TRUE; /* disable the 'q' command */
+ }
+ return retval;
+}
+
+/*
+ * If "restart_edit" is not zero, put it in the typeahead buffer, so that it's
+ * used only after other typeahead has been processed.
+ */
+static void put_reedit_in_typebuf(int silent)
+{
+ char_u buf[3];
+
+ if (restart_edit != NUL) {
+ if (restart_edit == 'V') {
+ buf[0] = 'g';
+ buf[1] = 'R';
+ buf[2] = NUL;
+ } else {
+ buf[0] = restart_edit == 'I' ? 'i' : restart_edit;
+ buf[1] = NUL;
+ }
+ if (ins_typebuf(buf, REMAP_NONE, 0, TRUE, silent) == OK)
+ restart_edit = NUL;
+ }
+}
+
+/*
+ * Insert register contents "s" into the typeahead buffer, so that it will be
+ * executed again.
+ * When "esc" is TRUE it is to be taken literally: Escape CSI characters and
+ * no remapping.
+ */
+static int
+put_in_typebuf (
+ char_u *s,
+ int esc,
+ int colon, /* add ':' before the line */
+ int silent
+)
+{
+ int retval = OK;
+
+ put_reedit_in_typebuf(silent);
+ if (colon)
+ retval = ins_typebuf((char_u *)"\n", REMAP_NONE, 0, TRUE, silent);
+ if (retval == OK) {
+ char_u *p;
+
+ if (esc)
+ p = vim_strsave_escape_csi(s);