diff options
| author | Eliseo Martínez <eliseomarmol@gmail.com> | 2014-05-12 02:25:17 +0200 |
|---|---|---|
| committer | Eliseo Martínez <eliseomarmol@gmail.com> | 2014-05-15 20:46:01 +0200 |
| commit | da51dc9cf202772f60bd2da975dbef257bd9237c (patch) | |
| tree | 5c16b93238a153f55634e9323077f30c8133970c /src/nvim/ops.c | |
| parent | ffe61e5ba1721340ca51d56bae3ddaca415fb5bc (diff) | |
| download | rneovim-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.c | 5174 |
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); |