// register.c: functions for managing registers #include "nvim/api/private/helpers.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/buffer_defs.h" #include "nvim/buffer_updates.h" #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/clipboard.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/errors.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_getln.h" #include "nvim/extmark.h" #include "nvim/file_search.h" #include "nvim/fold.h" #include "nvim/garray.h" #include "nvim/getchar.h" #include "nvim/globals.h" #include "nvim/indent.h" #include "nvim/insexpand.h" #include "nvim/keycodes.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/message.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/option_vars.h" #include "nvim/os/input.h" #include "nvim/os/time.h" #include "nvim/plines.h" #include "nvim/register.h" #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/terminal.h" #include "nvim/types_defs.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "register.c.generated.h" // Keep the last expression line here, for repeating. static char *expr_line = NULL; static int execreg_lastc = NUL; static yankreg_T y_regs[NUM_REGISTERS] = { 0 }; static yankreg_T *y_previous = NULL; // ptr to last written yankreg static const char e_search_pattern_and_expression_register_may_not_contain_two_or_more_lines[] = N_("E883: Search pattern and expression register may not contain two or more lines"); /// @return the index of the register "" points to. int get_unname_register(void) { return y_previous == NULL ? -1 : (int)(y_previous - &y_regs[0]); } yankreg_T *get_y_register(int reg) { return &y_regs[reg]; } yankreg_T *get_y_previous(void) FUNC_ATTR_PURE { return y_previous; } /// Get an expression for the "\"=expr1" or "CTRL-R =expr1" /// /// @return '=' when OK, NUL otherwise. int get_expr_register(void) { char *new_line = getcmdline('=', 0, 0, true); if (new_line == NULL) { return NUL; } if (*new_line == NUL) { // use previous line xfree(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 *new_line) { xfree(expr_line); expr_line = new_line; } /// Get the result of the '=' register expression. /// /// @return a pointer to allocated memory, or NULL for failure. char *get_expr_line(void) { 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. char *expr_copy = xstrdup(expr_line); // When we are invoked recursively limit the evaluation to 10 levels. // Then return the string as-is. if (nested >= 10) { return expr_copy; } nested++; char *rv = eval_to_string(expr_copy, true, false); nested--; xfree(expr_copy); return rv; } /// Get the '=' register expression itself, without evaluating it. char *get_expr_line_src(void) { if (expr_line == NULL) { return NULL; } return xstrdup(expr_line); } /// @return whether `regname` is a valid name of a yank register. /// /// @note: There is no check for 0 (default register), caller should do this. /// The black hole register '_' is regarded as valid. /// /// @param regname name of register /// @param writing allow only writable registers bool valid_yank_reg(int regname, bool writing) { if ((regname > 0 && ASCII_ISALNUM(regname)) || (!writing && vim_strchr("/.%:=", regname) != NULL) || regname == '#' || regname == '"' || regname == '-' || regname == '_' || regname == '*' || regname == '+') { return true; } return false; } /// Check if the default register (used in an unnamed paste) should be a /// clipboard register. This happens when `clipboard=unnamed[plus]` is set /// and a provider is available. /// /// @returns the name of of a clipboard register that should be used, or `NUL` if none. int get_default_register_name(void) { int name = NUL; adjust_clipboard_name(&name, true, false); return name; } /// Iterate over registers `regs`. /// /// @param[in] iter Iterator. Pass NULL to start iteration. /// @param[in] regs Registers list to be iterated. /// @param[out] name Register name. /// @param[out] reg Register contents. /// /// @return Pointer that must be passed to next `op_register_iter` call or /// NULL if iteration is over. const void *op_reg_iter(const void *const iter, const yankreg_T *const regs, char *const name, yankreg_T *const reg, bool *is_unnamed) FUNC_ATTR_NONNULL_ARG(3, 4, 5) FUNC_ATTR_WARN_UNUSED_RESULT { *name = NUL; const yankreg_T *iter_reg = (iter == NULL ? &(regs[0]) : (const yankreg_T *const)iter); while (iter_reg - &(regs[0]) < NUM_SAVED_REGISTERS && reg_empty(iter_reg)) { iter_reg++; } if (iter_reg - &(regs[0]) == NUM_SAVED_REGISTERS || reg_empty(iter_reg)) { return NULL; } int iter_off = (int)(iter_reg - &(regs[0])); *name = (char)get_register_name(iter_off); *reg = *iter_reg; *is_unnamed = (iter_reg == y_previous); while (++iter_reg - &(regs[0]) < NUM_SAVED_REGISTERS) { if (!reg_empty(iter_reg)) { return (void *)iter_reg; } } return NULL; } /// Iterate over global registers. /// /// @see op_register_iter const void *op_global_reg_iter(const void *const iter, char *const name, yankreg_T *const reg, bool *is_unnamed) FUNC_ATTR_NONNULL_ARG(2, 3, 4) FUNC_ATTR_WARN_UNUSED_RESULT { return op_reg_iter(iter, y_regs, name, reg, is_unnamed); } /// Get a number of non-empty registers size_t op_reg_amount(void) FUNC_ATTR_WARN_UNUSED_RESULT { size_t ret = 0; for (size_t i = 0; i < NUM_SAVED_REGISTERS; i++) { if (!reg_empty(y_regs + i)) { ret++; } } return ret; } /// Set register to a given value /// /// @param[in] name Register name. /// @param[in] reg Register value. /// @param[in] is_unnamed Whether to set the unnamed regiseter to reg /// /// @return true on success, false on failure. bool op_reg_set(const char name, const yankreg_T reg, bool is_unnamed) { int i = op_reg_index(name); if (i == -1) { return false; } free_register(&y_regs[i]); y_regs[i] = reg; if (is_unnamed) { y_previous = &y_regs[i]; } return true; } /// Get register with the given name /// /// @param[in] name Register name. /// /// @return Pointer to the register contents or NULL. const yankreg_T *op_reg_get(const char name) { int i = op_reg_index(name); if (i == -1) { return NULL; } return &y_regs[i]; } /// Set the previous yank register /// /// @param[in] name Register name. /// /// @return true on success, false on failure. bool op_reg_set_previous(const char name) { int i = op_reg_index(name); if (i == -1) { return false; } y_previous = &y_regs[i]; return true; } /// Updates the "y_width" of a blockwise register based on its contents. /// Do nothing on a non-blockwise register. void update_yankreg_width(yankreg_T *reg) { if (reg->y_type == kMTBlockWise) { size_t maxlen = 0; for (size_t i = 0; i < reg->y_size; i++) { size_t rowlen = mb_string2cells_len(reg->y_array[i].data, reg->y_array[i].size); maxlen = MAX(maxlen, rowlen); } assert(maxlen <= INT_MAX); reg->y_width = MAX(reg->y_width, (int)maxlen - 1); } } /// @return yankreg_T to use, according to the value of `regname`. /// Cannot handle the '_' (black hole) register. /// Must only be called with a valid register name! /// /// @param regname The name of the register used or 0 for the unnamed register /// @param mode One of the following three flags: /// /// `YREG_PASTE`: /// Prepare for pasting the register `regname`. With no regname specified, /// read from last written register, or from unnamed clipboard (depending on the /// `clipboard=unnamed` option). Queries the clipboard provider if necessary. /// /// `YREG_YANK`: /// Preparare for yanking into `regname`. With no regname specified, /// yank into `"0` register. Update `y_previous` for next unnamed paste. /// /// `YREG_PUT`: /// Obtain the location that would be read when pasting `regname`. yankreg_T *get_yank_register(int regname, int mode) { yankreg_T *reg; if ((mode == YREG_PASTE || mode == YREG_PUT) && get_clipboard(regname, ®, false)) { // reg is set to clipboard contents. return reg; } else if (mode == YREG_PUT && (regname == '*' || regname == '+')) { // in case clipboard not available and we aren't actually pasting, // return an empty register static yankreg_T empty_reg = { .y_array = NULL }; return &empty_reg; } else if (mode != YREG_YANK && (regname == 0 || regname == '"' || regname == '*' || regname == '+') && y_previous != NULL) { // in case clipboard not available, paste from previous used register return y_previous; } int i = op_reg_index(regname); // when not 0-9, a-z, A-Z or '-'/'+'/'*': use register 0 if (i == -1) { i = 0; } reg = &y_regs[i]; if (mode == YREG_YANK) { // remember the written register for unnamed paste y_previous = reg; } return reg; } /// Check if the current yank register has kMTLineWise register type /// For valid, non-blackhole registers also provides pointer to the register /// structure prepared for pasting. /// /// @param regname The name of the register used or 0 for the unnamed register /// @param reg Pointer to store yankreg_T* for the requested register. Will be /// set to NULL for invalid or blackhole registers. bool yank_register_mline(int regname, yankreg_T **reg) { *reg = NULL; if (regname != 0 && !valid_yank_reg(regname, false)) { return false; } if (regname == '_') { // black hole is always empty return false; } *reg = get_yank_register(regname, YREG_PASTE); return (*reg)->y_type == kMTLineWise; } /// @return a copy of contents in register `name` for use in do_put. Should be /// freed by caller. yankreg_T *copy_register(int name) FUNC_ATTR_NONNULL_RET { yankreg_T *reg = get_yank_register(name, YREG_PASTE); yankreg_T *copy = xmalloc(sizeof(yankreg_T)); *copy = *reg; if (copy->y_size == 0) { copy->y_array = NULL; } else { copy->y_array = xcalloc(copy->y_size, sizeof(String)); for (size_t i = 0; i < copy->y_size; i++) { copy->y_array[i] = copy_string(reg->y_array[i], NULL); } } return copy; } /// Stuff string "p" into yank register "regname" as a single line (append if /// uppercase). "p" must have been allocated. /// /// @return FAIL for failure, OK otherwise static int stuff_yank(int regname, char *p) { // check for read-only register if (regname != 0 && !valid_yank_reg(regname, true)) { xfree(p); return FAIL; } if (regname == '_') { // black hole: don't do anything xfree(p); return OK; } const size_t plen = strlen(p); yankreg_T *reg = get_yank_register(regname, YREG_YANK); if (is_append_register(regname) && reg->y_array != NULL) { String *pp = &(reg->y_array[reg->y_size - 1]); const size_t tmplen = pp->size + plen; char *tmp = xmalloc(tmplen + 1); memcpy(tmp, pp->data, pp->size); memcpy(tmp + pp->size, p, plen); *(tmp + tmplen) = NUL; xfree(p); xfree(pp->data); *pp = cbuf_as_string(tmp, tmplen); } else { free_register(reg); reg->additional_data = NULL; reg->y_array = xmalloc(sizeof(String)); reg->y_array[0] = cbuf_as_string(p, plen); reg->y_size = 1; reg->y_type = kMTCharWise; } reg->timestamp = os_time(); return OK; } /// Start or stop recording into a yank register. /// /// @return FAIL for failure, OK otherwise. int do_record(int c) { static int regname; int retval; if (reg_recording == 0) { // start recording // registers 0-9, a-z and " are allowed if (c < 0 || (!ASCII_ISALNUM(c) && c != '"')) { retval = FAIL; } else { reg_recording = c; // TODO(bfredl): showmode based messaging is currently missing with cmdheight=0 showmode(); regname = c; retval = OK; apply_autocmds(EVENT_RECORDINGENTER, NULL, NULL, false, curbuf); } } else { // stop recording save_v_event_T save_v_event; // Set the v:event dictionary with information about the recording. dict_T *dict = get_v_event(&save_v_event); // The recorded text contents. char *p = get_recorded(); if (p != NULL) { // Remove escaping for K_SPECIAL in multi-byte chars. vim_unescape_ks(p); tv_dict_add_str(dict, S_LEN("regcontents"), p); } // Name of requested register, or empty string for unnamed operation. char buf[NUMBUFLEN + 2]; buf[0] = (char)regname; buf[1] = NUL; tv_dict_add_str(dict, S_LEN("regname"), buf); tv_dict_set_keys_readonly(dict); // Get the recorded key hits. K_SPECIAL will be escaped, this // needs to be removed again to put it in a register. exec_reg then // adds the escaping back later. apply_autocmds(EVENT_RECORDINGLEAVE, NULL, NULL, false, curbuf); restore_v_event(dict, &save_v_event); reg_recorded = reg_recording; reg_recording = 0; if (p_ch == 0 || ui_has(kUIMessages)) { showmode(); } else { msg("", 0); } if (p == NULL) { retval = FAIL; } else { // We don't want to change the default register here, so save and // restore the current register name. yankreg_T *old_y_previous = y_previous; retval = stuff_yank(regname, p); y_previous = old_y_previous; } } return retval; } /// Insert register contents "s" into the typeahead buffer, so that it will be /// executed again. /// /// @param esc when true then it is to be taken literally: Escape K_SPECIAL /// characters and no remapping. /// @param colon add ':' before the line static int put_in_typebuf(char *s, bool esc, bool colon, int silent) { int retval = OK; put_reedit_in_typebuf(silent); if (colon) { retval = ins_typebuf("\n", REMAP_NONE, 0, true, silent); } if (retval == OK) { char *p; if (esc) { p = vim_strsave_escape_ks(s); } else { p = s; } if (p == NULL) { retval = FAIL; } else { retval = ins_typebuf(p, esc ? REMAP_NONE : REMAP_YES, 0, true, silent); } if (esc) { xfree(p); } } if (colon && retval == OK) { retval = ins_typebuf(":", REMAP_NONE, 0, true, silent); } 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) { uint8_t buf[3]; if (restart_edit == NUL) { return; } if (restart_edit == 'V') { buf[0] = 'g'; buf[1] = 'R'; buf[2] = NUL; } else { buf[0] = (uint8_t)(restart_edit == 'I' ? 'i' : restart_edit); buf[1] = NUL; } if (ins_typebuf((char *)buf, REMAP_NONE, 0, true, silent) == OK) { restart_edit = NUL; } } /// When executing a register as a series of ex-commands, if the /// line-continuation character is used for a line, then join it with one or /// more previous lines. Note that lines are processed backwards starting from /// the last line in the register. /// /// @param lines list of lines in the register /// @param idx index of the line starting with \ or "\. Join this line with all the immediate /// predecessor lines that start with a \ and the first line that doesn't start /// with a \. Lines that start with a comment "\ character are ignored. /// @returns the concatenated line. The index of the line that should be /// processed next is returned in idx. static char *execreg_line_continuation(String *lines, size_t *idx) { size_t cmd_start = *idx; assert(cmd_start > 0); const size_t cmd_end = cmd_start; garray_T ga; ga_init(&ga, (int)sizeof(char), 400); // search backwards to find the first line of this command. // Any line not starting with \ or "\ is the start of the // command. while (--cmd_start > 0) { char *p = skipwhite(lines[cmd_start].data); if (*p != '\\' && (p[0] != '"' || p[1] != '\\' || p[2] != ' ')) { break; } } // join all the lines String *tmp = &lines[cmd_start]; ga_concat_len(&ga, tmp->data, tmp->size); for (size_t j = cmd_start + 1; j <= cmd_end; j++) { tmp = &lines[j]; char *p = skipwhite(tmp->data); if (*p == '\\') { // Adjust the growsize to the current length to // speed up concatenating many lines. if (ga.ga_len > 400) { ga_set_growsize(&ga, MIN(ga.ga_len, 8000)); } p++; ga_concat_len(&ga, p, (size_t)(tmp->data + tmp->size - p)); } } ga_append(&ga, NUL); char *str = xmemdupz(ga.ga_data, (size_t)ga.ga_len); ga_clear(&ga); *idx = cmd_start; return str; } /// Execute a yank register: copy it into the stuff buffer /// /// @param colon insert ':' before each line /// @param addcr always add '\n' to end of line /// @param silent set "silent" flag in typeahead buffer /// /// @return FAIL for failure, OK otherwise int do_execreg(int regname, int colon, int addcr, int silent) { int retval = OK; 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; } // don't keep the cmdline containing @: XFREE_CLEAR(new_last_cmdline); // Escape all control characters with a CTRL-V char *p = vim_strsave_escaped_ext(last_cmdline, "\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); // 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); } xfree(p); } else if (regname == '=') { char *p = get_expr_line(); if (p == NULL) { return FAIL; } retval = put_in_typebuf(p, true, colon, silent); xfree(p); } else if (regname == '.') { // use last inserted text char *p = get_last_insert_save(); if (p == NULL) { emsg(_(e_noinstext)); return FAIL; } retval = put_in_typebuf(p, false, colon, silent); xfree(p); } else { yankreg_T *reg = get_yank_register(regname, YREG_PASTE); if (reg->y_array == NULL) { return FAIL; } // Disallow remapping for ":@r". int remap = colon ? REMAP_NONE : REMAP_YES; // Insert lines into typeahead buffer, from last one to first one. put_reedit_in_typebuf(silent); for (size_t i = reg->y_size; i-- > 0;) { // from y_size - 1 to 0 included // insert NL between lines and after last line if type is kMTLineWise if (reg->y_type == kMTLineWise || i < reg->y_size - 1 || addcr) { if (ins_typebuf("\n", remap, 0, true, silent) == FAIL) { return FAIL; } } // Handle line-continuation for :@ char *str = reg->y_array[i].data; bool free_str = false; if (colon && i > 0) { char *p = skipwhite(str); if (*p == '\\' || (p[0] == '"' && p[1] == '\\' && p[2] == ' ')) { str = execreg_line_continuation(reg->y_array, &i); free_str = true; } } char *escaped = vim_strsave_escape_ks(str); if (free_str) { xfree(str); } retval = ins_typebuf(escaped, remap, 0, true, silent); xfree(escaped); if (retval == FAIL) { return FAIL; } if (colon && ins_typebuf(":", remap, 0, true, silent) == FAIL) { return FAIL; } } reg_executing = regname == 0 ? '"' : regname; // disable the 'q' command pending_end_reg_executing = false; } return retval; } /// Insert a yank register: copy it into the Read buffer. /// Used by CTRL-R command and middle mouse button in insert mode. /// /// @param literally_arg insert literally, not as if typed /// /// @return FAIL for failure, OK otherwise int insert_reg(int regname, yankreg_T *reg, bool literally_arg) { int retval = OK; bool allocated; const bool literally = literally_arg || is_literal_register(regname); // It is possible to get into an endless loop by having CTRL-R a in // register a and then, in insert mode, doing CTRL-R a. // If you hit CTRL-C, the loop will be broken here. os_breakcheck(); if (got_int) { return FAIL; } // check for valid regname if (regname != NUL && !valid_yank_reg(regname, false)) { return FAIL; } char *arg; if (regname == '.') { // Insert last inserted text. retval = stuff_inserted(NUL, 1, true); } else if (get_spec_reg(regname, &arg, &allocated, true)) { if (arg == NULL) { return FAIL; } stuffescaped(arg, literally); if (allocated) { xfree(arg); } } else { // Name or number register. if (reg == NULL) { reg = get_yank_register(regname, YREG_PASTE); } if (reg->y_array == NULL) { retval = FAIL; } else { for (size_t i = 0; i < reg->y_size; i++) { if (regname == '-' && reg->y_type == kMTCharWise) { Direction dir = BACKWARD; if ((State & REPLACE_FLAG) != 0) { pos_T curpos; if (u_save_cursor() == FAIL) { return FAIL; } del_chars(mb_charlen(reg->y_array[0].data), true); curpos = curwin->w_cursor; if (oneright() == FAIL) { // hit end of line, need to put forward (after the current position) dir = FORWARD; } curwin->w_cursor = curpos; } AppendCharToRedobuff(Ctrl_R); AppendCharToRedobuff(regname); do_put(regname, NULL, dir, 1, PUT_CURSEND); } else { stuffescaped(reg->y_array[i].data, literally); // Insert a newline between lines and after last line if // y_type is kMTLineWise. if (reg->y_type == kMTLineWise || i < reg->y_size - 1) { stuffcharReadbuff('\n'); } } } } } return retval; } /// If "regname" is a special register, return true and store a pointer to its /// value in "argp". /// /// @param allocated return: true when value was allocated /// @param errmsg give error message when failing /// /// @return true if "regname" is a special register, bool get_spec_reg(int regname, char **argp, bool *allocated, bool errmsg) { *argp = NULL; *allocated = false; switch (regname) { case '%': // file name if (errmsg) { check_fname(); // will give emsg if not set } *argp = curbuf->b_fname; return true; case '#': // alternate file name *argp = getaltfname(errmsg); // may give emsg if not set return true; case '=': // result of expression *argp = get_expr_line(); *allocated = true; return true; case ':': // last command line if (last_cmdline == NULL && errmsg) { emsg(_(e_nolastcmd)); } *argp = last_cmdline; return true; case '/': // last search-pattern if (last_search_pat() == NULL && errmsg) { emsg(_(e_noprevre)); } *argp = last_search_pat(); return true; case '.': // last inserted text *argp = get_last_insert_save(); *allocated = true; if (*argp == NULL && errmsg) { emsg(_(e_noinstext)); } return true; case Ctrl_F: // Filename under cursor case Ctrl_P: // Path under cursor, expand via "path" if (!errmsg) { return false; } *argp = file_name_at_cursor(FNAME_MESS | FNAME_HYP | (regname == Ctrl_P ? FNAME_EXP : 0), 1, NULL); *allocated = true; return true; case Ctrl_W: // word under cursor case Ctrl_A: // WORD (mnemonic All) under cursor if (!errmsg) { return false; } size_t cnt = find_ident_under_cursor(argp, (regname == Ctrl_W ? (FIND_IDENT|FIND_STRING) : FIND_STRING), NULL); *argp = cnt ? xmemdupz(*argp, cnt) : NULL; *allocated = true; return true; case Ctrl_L: // Line under cursor if (!errmsg) { return false; } *argp = ml_get_buf(curwin->w_buffer, curwin->w_cursor.lnum); return true; case '_': // black hole: always empty *argp = ""; return true; } return false; } /// Paste a yank register into the command line. /// Only for non-special registers. /// Used by CTRL-R in command-line mode. /// insert_reg() can't be used here, because special characters from the /// register contents will be interpreted as commands. /// /// @param regname Register name. /// @param literally_arg Insert text literally instead of "as typed". /// @param remcr When true, don't add CR characters. /// /// @returns FAIL for failure, OK otherwise bool cmdline_paste_reg(int regname, bool literally_arg, bool remcr) { const bool literally = literally_arg || is_literal_register(regname); yankreg_T *reg = get_yank_register(regname, YREG_PASTE); if (reg->y_array == NULL) { return FAIL; } for (size_t i = 0; i < reg->y_size; i++) { cmdline_paste_str(reg->y_array[i].data, literally); // Insert ^M between lines, unless `remcr` is true. if (i < reg->y_size - 1 && !remcr) { cmdline_paste_str("\r", literally); } // Check for CTRL-C, in case someone tries to paste a few thousand // lines and gets bored. os_breakcheck(); if (got_int) { return FAIL; } } return OK; } /// Shift the delete registers: "9 is cleared, "8 becomes "9, etc. void shift_delete_registers(bool y_append) { free_register(&y_regs[9]); // free register "9 for (int n = 9; n > 1; n--) { y_regs[n] = y_regs[n - 1]; } if (!y_append) { y_previous = &y_regs[1]; } y_regs[1].y_array = NULL; // set register "1 to empty } #ifdef EXITFREE void clear_registers(void) { for (int i = 0; i < NUM_REGISTERS; i++) { free_register(&y_regs[i]); } } #endif /// Free contents of yankreg `reg`. /// Called for normal freeing and in case of error. /// /// @param reg must not be NULL (but `reg->y_array` might be) void free_register(yankreg_T *reg) FUNC_ATTR_NONNULL_ALL { XFREE_CLEAR(reg->additional_data); if (reg->y_array == NULL) { return; } for (size_t i = reg->y_size; i-- > 0;) { // from y_size - 1 to 0 included API_CLEAR_STRING(reg->y_array[i]); } XFREE_CLEAR(reg->y_array); } /// Copy a block range into a register. /// /// @param exclude_trailing_space if true, do not copy trailing whitespaces. static void yank_copy_line(yankreg_T *reg, struct block_def *bd, size_t y_idx, bool exclude_trailing_space) FUNC_ATTR_NONNULL_ALL { if (exclude_trailing_space) { bd->endspaces = 0; } int size = bd->startspaces + bd->endspaces + bd->textlen; assert(size >= 0); char *pnew = xmallocz((size_t)size); reg->y_array[y_idx].data = pnew; memset(pnew, ' ', (size_t)bd->startspaces); pnew += bd->startspaces; memmove(pnew, bd->textstart, (size_t)bd->textlen); pnew += bd->textlen; memset(pnew, ' ', (size_t)bd->endspaces); pnew += bd->endspaces; if (exclude_trailing_space) { int s = bd->textlen + bd->endspaces; while (s > 0 && ascii_iswhite(*(bd->textstart + s - 1))) { s = s - utf_head_off(bd->textstart, bd->textstart + s - 1) - 1; pnew--; } } *pnew = NUL; reg->y_array[y_idx].size = (size_t)(pnew - reg->y_array[y_idx].data); } void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) { yankreg_T newreg; // new yank register when appending MotionType yank_type = oap->motion_type; size_t yanklines = (size_t)oap->line_count; linenr_T yankendlnum = oap->end.lnum; struct block_def bd; yankreg_T *curr = reg; // copy of current register // append to existing contents if (append && reg->y_array != NULL) { reg = &newreg; } else { free_register(reg); // free previously yanked lines } // If the cursor was in column 1 before and after the movement, and the // operator is not inclusive, the yank is always linewise. if (oap->motion_type == kMTCharWise && oap->start.col == 0 && !oap->inclusive && (!oap->is_VIsual || *p_sel == 'o') && oap->end.col == 0 && yanklines > 1) { yank_type = kMTLineWise; yankendlnum--; yanklines--; } reg->y_size = yanklines; reg->y_type = yank_type; // set the yank register type reg->y_width = 0; reg->y_array = xcalloc(yanklines, sizeof(String)); reg->additional_data = NULL; reg->timestamp = os_time(); size_t y_idx = 0; // index in y_array[] linenr_T lnum = oap->start.lnum; // current line number if (yank_type == kMTBlockWise) { // Visual block mode reg->y_width = oap->end_vcol - oap->start_vcol; if (curwin->w_curswant == MAXCOL && reg->y_width > 0) { reg->y_width--; } } for (; lnum <= yankendlnum; lnum++, y_idx++) { switch (reg->y_type) { case kMTBlockWise: block_prep(oap, &bd, lnum, false); yank_copy_line(reg, &bd, y_idx, oap->excl_tr_ws); break; case kMTLineWise: reg->y_array[y_idx] = cbuf_to_string(ml_get(lnum), (size_t)ml_get_len(lnum)); break; case kMTCharWise: charwise_block_prep(oap->start, oap->end, &bd, lnum, oap->inclusive); // make sure bd.textlen is not longer than the text int tmp = (int)strlen(bd.textstart); if (tmp < bd.textlen) { bd.textlen = tmp; } yank_copy_line(reg, &bd, y_idx, false); break; // NOTREACHED case kMTUnknown: abort(); } } if (curr != reg) { // append the new block to the old block size_t j; String *new_ptr = xmalloc(sizeof(String) * (curr->y_size + reg->y_size)); for (j = 0; j < curr->y_size; j++) { new_ptr[j] = curr->y_array[j]; } xfree(curr->y_array); curr->y_array = new_ptr; if (yank_type == kMTLineWise) { // kMTLineWise overrides kMTCharWise and kMTBlockWise curr->y_type = kMTLineWise; } // Concatenate the last line of the old block with the first line of // the new block, unless being Vi compatible. if (curr->y_type == kMTCharWise && vim_strchr(p_cpo, CPO_REGAPPEND) == NULL) { char *pnew = xmalloc(curr->y_array[curr->y_size - 1].size + reg->y_array[0].size + 1); j--; STRCPY(pnew, curr->y_array[j].data); STRCPY(pnew + curr->y_array[j].size, reg->y_array[0].data); xfree(curr->y_array[j].data); curr->y_array[j] = cbuf_as_string(pnew, curr->y_array[j].size + reg->y_array[0].size); j++; API_CLEAR_STRING(reg->y_array[0]); y_idx = 1; } else { y_idx = 0; } while (y_idx < reg->y_size) { curr->y_array[j++] = reg->y_array[y_idx++]; } curr->y_size = j; xfree(reg->y_array); } if (message) { // Display message about yank? if (yank_type == kMTCharWise && yanklines == 1) { yanklines = 0; } // Some versions of Vi use ">=" here, some don't... if (yanklines > (size_t)p_report) { char namebuf[100]; if (oap->regname == NUL) { *namebuf = NUL; } else { vim_snprintf(namebuf, sizeof(namebuf), _(" into \"%c"), oap->regname); } // redisplay now, so message is not deleted update_topline(curwin); if (must_redraw) { update_screen(); } if (yank_type == kMTBlockWise) { smsg(0, NGETTEXT("block of %" PRId64 " line yanked%s", "block of %" PRId64 " lines yanked%s", yanklines), (int64_t)yanklines, namebuf); } else { smsg(0, NGETTEXT("%" PRId64 " line yanked%s", "%" PRId64 " lines yanked%s", yanklines), (int64_t)yanklines, namebuf); } } } if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { // Set "'[" and "']" marks. curbuf->b_op_start = oap->start; curbuf->b_op_end = oap->end; if (yank_type == kMTLineWise) { curbuf->b_op_start.col = 0; curbuf->b_op_end.col = MAXCOL; } if (yank_type != kMTLineWise && !oap->inclusive) { // Exclude the end position. decl(&curbuf->b_op_end); } } } /// Format the register type as a string. /// /// @param reg_type The register type. /// @param reg_width The width, only used if "reg_type" is kMTBlockWise. /// @param[out] buf Buffer to store formatted string. The allocated size should /// be at least NUMBUFLEN+2 to always fit the value. /// @param buf_len The allocated size of the buffer. void format_reg_type(MotionType reg_type, colnr_T reg_width, char *buf, size_t buf_len) FUNC_ATTR_NONNULL_ALL { assert(buf_len > 1); switch (reg_type) { case kMTLineWise: buf[0] = 'V'; buf[1] = NUL; break; case kMTCharWise: buf[0] = 'v'; buf[1] = NUL; break; case kMTBlockWise: snprintf(buf, buf_len, CTRL_V_STR "%" PRIdCOLNR, reg_width + 1); break; case kMTUnknown: buf[0] = NUL; break; } } /// Execute autocommands for TextYankPost. /// /// @param oap Operator arguments. /// @param reg The yank register used. void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) FUNC_ATTR_NONNULL_ALL { static bool recursive = false; if (recursive || !has_event(EVENT_TEXTYANKPOST)) { // No autocommand was defined, or we yanked from this autocommand. return; } recursive = true; save_v_event_T save_v_event; // Set the v:event dictionary with information about the yank. dict_T *dict = get_v_event(&save_v_event); // The yanked text contents. list_T *const list = tv_list_alloc((ptrdiff_t)reg->y_size); for (size_t i = 0; i < reg->y_size; i++) { tv_list_append_string(list, reg->y_array[i].data, (int)reg->y_array[i].size); } tv_list_set_lock(list, VAR_FIXED); tv_dict_add_list(dict, S_LEN("regcontents"), list); // Register type. char buf[NUMBUFLEN + 2]; format_reg_type(reg->y_type, reg->y_width, buf, ARRAY_SIZE(buf)); tv_dict_add_str(dict, S_LEN("regtype"), buf); // Name of requested register, or empty string for unnamed operation. buf[0] = (char)oap->regname; buf[1] = NUL; tv_dict_add_str(dict, S_LEN("regname"), buf); // Motion type: inclusive or exclusive. tv_dict_add_bool(dict, S_LEN("inclusive"), oap->inclusive ? kBoolVarTrue : kBoolVarFalse); // Kind of operation: yank, delete, change). buf[0] = (char)get_op_char(oap->op_type); buf[1] = NUL; tv_dict_add_str(dict, S_LEN("operator"), buf); // Selection type: visual or not. tv_dict_add_bool(dict, S_LEN("visual"), oap->is_VIsual ? kBoolVarTrue : kBoolVarFalse); tv_dict_set_keys_readonly(dict); textlock++; apply_autocmds(EVENT_TEXTYANKPOST, NULL, NULL, false, curbuf); textlock--; restore_v_event(dict, &save_v_event); recursive = false; } /// Yanks the text between "oap->start" and "oap->end" into a yank register. /// If we are to append (uppercase register), we first yank into a new yank /// register and then concatenate the old and the new one. /// Do not call this from a delete operation. Use op_yank_reg() instead. /// /// @param oap operator arguments /// @param message show message when more than `&report` lines are yanked. /// @returns whether the operation register was writable. bool op_yank(oparg_T *oap, bool message) FUNC_ATTR_NONNULL_ALL { // check for read-only register if (oap->regname != 0 && !valid_yank_reg(oap->regname, true)) { beep_flush(); return false; } if (oap->regname == '_') { return true; // black hole: nothing to do } yankreg_T *reg = get_yank_register(oap->regname, YREG_YANK); op_yank_reg(oap, message, reg, is_append_register(oap->regname)); set_clipboard(oap->regname, reg); do_autocmd_textyankpost(oap, reg); return true; } /// Put contents of register "regname" into the text. /// Caller must check "regname" to be valid! /// /// @param flags PUT_FIXINDENT make indent look nice /// PUT_CURSEND leave cursor after end of new text /// PUT_LINE force linewise put (":put") /// PUT_BLOCK_INNER in block mode, do not add trailing spaces /// @param dir BACKWARD for 'P', FORWARD for 'p' void do_put(int regname, yankreg_T *reg, int dir, int count, int flags) { size_t totlen = 0; // init for gcc linenr_T lnum = 0; MotionType y_type; size_t y_size; int y_width = 0; colnr_T vcol = 0; String *y_array = NULL; linenr_T nr_lines = 0; bool allocated = false; const pos_T orig_start = curbuf->b_op_start; const pos_T orig_end = curbuf->b_op_end; unsigned cur_ve_flags = get_ve_flags(curwin); // Remove any preinserted text (issue vim/vim#19329) if (ins_compl_preinsert_effect()) { ins_compl_delete(false); } curbuf->b_op_start = curwin->w_cursor; // default for '[ mark curbuf->b_op_end = curwin->w_cursor; // default for '] mark // Using inserted text works differently, because the register includes // special characters (newlines, etc.). if (regname == '.' && !reg) { bool non_linewise_vis = (VIsual_active && VIsual_mode != 'V'); // PUT_LINE has special handling below which means we use 'i' to start. char command_start_char = non_linewise_vis ? 'c' : (flags & PUT_LINE ? 'i' : (dir == FORWARD ? 'a' : 'i')); // To avoid 'autoindent' on linewise puts, create a new line with `:put _`. if (flags & PUT_LINE) { do_put('_', NULL, dir, 1, PUT_LINE); } // If given a count when putting linewise, we stuff the readbuf with the // dot register 'count' times split by newlines. if (flags & PUT_LINE) { stuffcharReadbuff(command_start_char); for (; count > 0; count--) { stuff_inserted(NUL, 1, count != 1); if (count != 1) { // To avoid 'autoindent' affecting the text, use Ctrl_U to remove any // whitespace. Can't just insert Ctrl_U into readbuf1, this would go // back to the previous line in the case of 'noautoindent' and // 'backspace' includes "eol". So we insert a dummy space for Ctrl_U // to consume. stuffReadbuff("\n "); stuffcharReadbuff(Ctrl_U); } } } else { stuff_inserted(command_start_char, count, false); } // Putting the text is done later, so can't move the cursor to the next // character. Simulate it with motion commands after the insert. if (flags & PUT_CURSEND) { if (flags & PUT_LINE) { stuffReadbuff("j0"); } else { // Avoid ringing the bell from attempting to move into the space after // the current line. We can stuff the readbuffer with "l" if: // 1) 'virtualedit' is "all" or "onemore" // 2) We are not at the end of the line // 3) We are not (one past the end of the line && on the last line) // This allows a visual put over a selection one past the end of the // line joining the current line with the one below. // curwin->w_cursor.col marks the byte position of the cursor in the // currunt line. It increases up to a max of // strlen(ml_get(curwin->w_cursor.lnum)). With 'virtualedit' and the // cursor past the end of the line, curwin->w_cursor.coladd is // incremented instead of curwin->w_cursor.col. char *cursor_pos = get_cursor_pos_ptr(); bool one_past_line = (*cursor_pos == NUL); bool eol = false; if (!one_past_line) { eol = (*(cursor_pos + utfc_ptr2len(cursor_pos)) == NUL); } bool ve_allows = (cur_ve_flags == kOptVeFlagAll || cur_ve_flags == kOptVeFlagOnemore); bool eof = curbuf->b_ml.ml_line_count == curwin->w_cursor.lnum && one_past_line; if (ve_allows || !(eol || eof)) { stuffcharReadbuff('l'); } } } else if (flags & PUT_LINE) { stuffReadbuff("g'["); } // So the 'u' command restores cursor position after ".p, save the cursor // position now (though not saving any text). if (command_start_char == 'a') { if (u_save(curwin->w_cursor.lnum, curwin->w_cursor.lnum + 1) == FAIL) { return; } } return; } // For special registers '%' (file name), '#' (alternate file name) and // ':' (last command line), etc. we have to create a fake yank register. String insert_string = STRING_INIT; if (!reg && get_spec_reg(regname, &insert_string.data, &allocated, true)) { if (insert_string.data == NULL) { return; } } if (!curbuf->terminal) { // Autocommands may be executed when saving lines for undo. This might // make y_array invalid, so we start undo now to avoid that. if (u_save(curwin->w_cursor.lnum, curwin->w_cursor.lnum + 1) == FAIL) { return; } } if (insert_string.data != NULL) { insert_string.size = strlen(insert_string.data); y_type = kMTCharWise; if (regname == '=') { // For the = register we need to split the string at NL // characters. // Loop twice: count the number of lines and save them. while (true) { y_size = 0; char *ptr = insert_string.data; size_t ptrlen = insert_string.size; while (ptr != NULL) { if (y_array != NULL) { y_array[y_size].data = ptr; } y_size++; char *tmp = vim_strchr(ptr, '\n'); if (tmp == NULL) { if (y_array != NULL) { y_array[y_size - 1].size = ptrlen; } } else { if (y_array != NULL) { *tmp = NUL; y_array[y_size - 1].size = (size_t)(tmp - ptr); ptrlen -= y_array[y_size - 1].size + 1; } tmp++; // A trailing '\n' makes the register linewise. if (*tmp == NUL) { y_type = kMTLineWise; break; } } ptr = tmp; } if (y_array != NULL) { break; } y_array = xmalloc(y_size * sizeof(String)); } } else { y_size = 1; // use fake one-line yank register y_array = &insert_string; } } else { // in case of replacing visually selected text // the yankreg might already have been saved to avoid // just restoring the deleted text. if (reg == NULL) { reg = get_yank_register(regname, YREG_PASTE); } y_type = reg->y_type; y_width = reg->y_width; y_size = reg->y_size; y_array = reg->y_array; } if (curbuf->terminal) { terminal_paste(count, y_array, y_size); return; } colnr_T split_pos = 0; if (y_type == kMTLineWise) { if (flags & PUT_LINE_SPLIT) { // "p" or "P" in Visual mode: split the lines to put the text in // between. if (u_save_cursor() == FAIL) { goto end; } char *curline = get_cursor_line_ptr(); char *p = get_cursor_pos_ptr(); char *const p_orig = p; const size_t plen = (size_t)get_cursor_pos_len(); if (dir == FORWARD && *p != NUL) { MB_PTR_ADV(p); } // we need this later for the correct extmark_splice() event split_pos = (colnr_T)(p - curline); char *ptr = xmemdupz(p, plen - (size_t)(p - p_orig)); ml_append(curwin->w_cursor.lnum, ptr, 0, false); xfree(ptr); ptr = xmemdupz(get_cursor_line_ptr(), (size_t)split_pos); ml_replace(curwin->w_cursor.lnum, ptr, false); nr_lines++; dir = FORWARD; buf_updates_send_changes(curbuf, curwin->w_cursor.lnum, 1, 1); } if (flags & PUT_LINE_FORWARD) { // Must be "p" for a Visual block, put lines below the block. curwin->w_cursor = curbuf->b_visual.vi_end; dir = FORWARD; } curbuf->b_op_start = curwin->w_cursor; // default for '[ mark curbuf->b_op_end = curwin->w_cursor; // default for '] mark } if (flags & PUT_LINE) { // :put command or "p" in Visual line mode. y_type = kMTLineWise; } if (y_size == 0 || y_array == NULL) { semsg(_("E353: Nothing in register %s"), regname == 0 ? "\"" : transchar(regname)); goto end; } if (y_type == kMTBlockWise) { lnum = curwin->w_cursor.lnum + (linenr_T)y_size + 1; lnum = MIN(lnum, curbuf->b_ml.ml_line_count + 1); if (u_save(curwin->w_cursor.lnum - 1, lnum) == FAIL) { goto end; } } else if (y_type == kMTLineWise) { lnum = curwin->w_cursor.lnum; // Correct line number for closed fold. Don't move the cursor yet, // u_save() uses it. if (dir == BACKWARD) { hasFolding(curwin, lnum, &lnum, NULL); } else { hasFolding(curwin, lnum, NULL, &lnum); } if (dir == FORWARD) { lnum++; } // In an empty buffer the empty line is going to be replaced, include // it in the saved lines. if ((buf_is_empty(curbuf) ? u_save(0, 2) : u_save(lnum - 1, lnum)) == FAIL) { goto end; } if (dir == FORWARD) { curwin->w_cursor.lnum = lnum - 1; } else { curwin->w_cursor.lnum = lnum; } curbuf->b_op_start = curwin->w_cursor; // for mark_adjust() } else if (u_save_cursor() == FAIL) { goto end; } if (cur_ve_flags == kOptVeFlagAll && y_type == kMTCharWise) { if (gchar_cursor() == TAB) { int viscol = getviscol(); OptInt ts = curbuf->b_p_ts; // Don't need to insert spaces when "p" on the last position of a // tab or "P" on the first position. if (dir == FORWARD ? tabstop_padding(viscol, ts, curbuf->b_p_vts_array) != 1 : curwin->w_cursor.coladd > 0) { coladvance_force(viscol); } else { curwin->w_cursor.coladd = 0; } } else if (curwin->w_cursor.coladd > 0 || gchar_cursor() == NUL) { coladvance_force(getviscol() + (dir == FORWARD)); } } lnum = curwin->w_cursor.lnum; colnr_T col = curwin->w_cursor.col; // Block mode if (y_type == kMTBlockWise) { int incr = 0; struct block_def bd; int c = gchar_cursor(); colnr_T endcol2 = 0; if (dir == FORWARD && c != NUL) { if (cur_ve_flags == kOptVeFlagAll) { getvcol(curwin, &curwin->w_cursor, &col, NULL, &endcol2, 0); } else { getvcol(curwin, &curwin->w_cursor, NULL, NULL, &col, 0); } // move to start of next multi-byte character curwin->w_cursor.col += utfc_ptr2len(get_cursor_pos_ptr()); col++; } else { getvcol(curwin, &curwin->w_cursor, &col, NULL, &endcol2, 0); } col += curwin->w_cursor.coladd; if (cur_ve_flags == kOptVeFlagAll && (curwin->w_cursor.coladd > 0 || endcol2 == curwin->w_cursor.col)) { if (dir == FORWARD && c == NUL) { col++; } if (dir != FORWARD && c != NUL && curwin->w_cursor.coladd > 0) { curwin->w_cursor.col++; } if (c == TAB) { if (dir == BACKWARD && curwin->w_cursor.col) { curwin->w_cursor.col--; } if (dir == FORWARD && col - 1 == endcol2) { curwin->w_cursor.col++; } } } curwin->w_cursor.coladd = 0; bd.textcol = 0; for (size_t i = 0; i < y_size; i++) { int spaces = 0; char shortline; // can just be 0 or 1, needed for blockwise paste beyond the current // buffer end int lines_appended = 0; bd.startspaces = 0; bd.endspaces = 0; vcol = 0; int delcount = 0; // add a new line if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { if (ml_append(curbuf->b_ml.ml_line_count, "", 1, false) == FAIL) { break; } nr_lines++; lines_appended = 1; } // get the old line and advance to the position to insert at char *oldp = get_cursor_line_ptr(); colnr_T oldlen = get_cursor_line_len(); CharsizeArg csarg; CSType cstype = init_charsize_arg(&csarg, curwin, curwin->w_cursor.lnum, oldp); StrCharInfo ci = utf_ptr2StrCharInfo(oldp); vcol = 0; while (vcol < col && *ci.ptr != NUL) { incr = win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &csarg).width; vcol += incr; ci = utfc_next(ci); } char *ptr = ci.ptr; bd.textcol = (colnr_T)(ptr - oldp); shortline = (vcol < col) || (vcol == col && !*ptr); if (vcol < col) { // line too short, pad with spaces bd.startspaces = col - vcol; } else if (vcol > col) { bd.endspaces = vcol - col; bd.startspaces = incr - bd.endspaces; bd.textcol--; delcount = 1; bd.textcol -= utf_head_off(oldp, oldp + bd.textcol); if (oldp[bd.textcol] != TAB) { // Only a Tab can be split into spaces. Other // characters will have to be moved to after the // block, causing misalignment. delcount = 0; bd.endspaces = 0; } } const int yanklen = (int)y_array[i].size; if ((flags & PUT_BLOCK_INNER) == 0) { // calculate number of spaces required to fill right side of block spaces = y_width + 1; cstype = init_charsize_arg(&csarg, curwin, 0, y_array[i].data); ci = utf_ptr2StrCharInfo(y_array[i].data); while (*ci.ptr != NUL) { spaces -= win_charsize(cstype, 0, ci.ptr, ci.chr.value, &csarg).width; ci = utfc_next(ci); } spaces = MAX(spaces, 0); } // Insert the new text. // First check for multiplication overflow. if (yanklen + spaces != 0 && count > ((INT_MAX - (bd.startspaces + bd.endspaces)) / (yanklen + spaces))) { emsg(_(e_resulting_text_too_long)); break; } totlen = (size_t)count * (size_t)(yanklen + spaces) + (size_t)bd.startspaces + (size_t)bd.endspaces; char *newp = xmalloc(totlen + (size_t)oldlen + 1); // copy part up to cursor to new line ptr = newp; memmove(ptr, oldp, (size_t)bd.textcol); ptr += bd.textcol; // may insert some spaces before the new text memset(ptr, ' ', (size_t)bd.startspaces); ptr += bd.startspaces; // insert the new text for (int j = 0; j < count; j++) { memmove(ptr, y_array[i].data, (size_t)yanklen); ptr += yanklen; // insert block's trailing spaces only if there's text behind if ((j < count - 1 || !shortline) && spaces > 0) { memset(ptr, ' ', (size_t)spaces); ptr += spaces; } else { totlen -= (size_t)spaces; // didn't use these spaces } } // may insert some spaces after the new text memset(ptr, ' ', (size_t)bd.endspaces); ptr += bd.endspaces; // move the text after the cursor to the end of the line. int columns = oldlen - bd.textcol - delcount + 1; assert(columns >= 0); memmove(ptr, oldp + bd.textcol + delcount, (size_t)columns); ml_replace(curwin->w_cursor.lnum, newp, false); extmark_splice_cols(curbuf, (int)curwin->w_cursor.lnum - 1, bd.textcol, delcount, (int)totlen + lines_appended, kExtmarkUndo); curwin->w_cursor.lnum++; if (i == 0) { curwin->w_cursor.col += bd.startspaces; } } changed_lines(curbuf, lnum, 0, curbuf->b_op_start.lnum + (linenr_T)y_size - nr_lines, nr_lines, true); // Set '[ mark. curbuf->b_op_start = curwin->w_cursor; curbuf->b_op_start.lnum = lnum; // adjust '] mark curbuf->b_op_end.lnum = curwin->w_cursor.lnum - 1; curbuf->b_op_end.col = MAX(bd.textcol + (colnr_T)totlen - 1, 0); curbuf->b_op_end.coladd = 0; if (flags & PUT_CURSEND) { curwin->w_cursor = curbuf->b_op_end; curwin->w_cursor.col++; // in Insert mode we might be after the NUL, correct for that colnr_T len = get_cursor_line_len(); curwin->w_cursor.col = MIN(curwin->w_cursor.col, len); } else { curwin->w_cursor.lnum = lnum; } } else { const int yanklen = (int)y_array[0].size; // Character or Line mode if (y_type == kMTCharWise) { // if type is kMTCharWise, FORWARD is the same as BACKWARD on the next // char if (dir == FORWARD && gchar_cursor() != NUL) { int bytelen = utfc_ptr2len(get_cursor_pos_ptr()); // put it on the next of the multi-byte character. col += bytelen; if (yanklen) { curwin->w_cursor.col += bytelen; curbuf->b_op_end.col += bytelen; } } curbuf->b_op_start = curwin->w_cursor; } else if (dir == BACKWARD) { // Line mode: BACKWARD is the same as FORWARD on the previous line lnum--; } pos_T new_cursor = curwin->w_cursor; // simple case: insert into one line at a time if (y_type == kMTCharWise && y_size == 1) { linenr_T end_lnum = 0; // init for gcc linenr_T start_lnum = lnum; int first_byte_off = 0; if (VIsual_active) { end_lnum = MAX(curbuf->b_visual.vi_end.lnum, curbuf->b_visual.vi_start.lnum); if (end_lnum > start_lnum) { // "col" is valid for the first line, in following lines // the virtual column needs to be used. Matters for // multi-byte characters. pos_T pos = { .lnum = lnum, .col = col, .coladd = 0, }; getvcol(curwin, &pos, NULL, &vcol, NULL, 0); } } if (count == 0 || yanklen == 0) { if (VIsual_active) { lnum = end_lnum; } } else if (count > INT_MAX / yanklen) { // multiplication overflow emsg(_(e_resulting_text_too_long)); } else { totlen = (size_t)count * (size_t)yanklen; do { char *oldp = ml_get(lnum); colnr_T oldlen = ml_get_len(lnum); if (lnum > start_lnum) { pos_T pos = { .lnum = lnum, }; if (getvpos(curwin, &pos, vcol) == OK) { col = pos.col; } else { col = MAXCOL; } } if (VIsual_active && col > oldlen) { lnum++; continue; } char *newp = xmalloc(totlen + (size_t)oldlen + 1); memmove(newp, oldp, (size_t)col); char *ptr = newp + col; for (size_t i = 0; i < (size_t)count; i++) { memmove(ptr, y_array[0].data, (size_t)yanklen); ptr += yanklen; } memmove(ptr, oldp + col, (size_t)(oldlen - col) + 1); // +1 for NUL ml_replace(lnum, newp, false); // compute the byte offset for the last character first_byte_off = utf_head_off(newp, ptr - 1); // Place cursor on last putted char. if (lnum == curwin->w_cursor.lnum) { // make sure curwin->w_virtcol is updated changed_cline_bef_curs(curwin); invalidate_botline_win(curwin); curwin->w_cursor.col += (colnr_T)(totlen - 1); } changed_bytes(lnum, col); extmark_splice_cols(curbuf, (int)lnum - 1, col, 0, (int)totlen, kExtmarkUndo); if (VIsual_active) { lnum++; } } while (VIsual_active && lnum <= end_lnum); if (VIsual_active) { // reset lnum to the last visual line lnum--; } } // put '] at the first byte of the last character curbuf->b_op_end = curwin->w_cursor; curbuf->b_op_end.col -= first_byte_off; // For "CTRL-O p" in Insert mode, put cursor after last char if (totlen && (restart_edit != 0 || (flags & PUT_CURSEND))) { curwin->w_cursor.col++; } else { curwin->w_cursor.col -= first_byte_off; } } else { linenr_T new_lnum = new_cursor.lnum; int indent; int orig_indent = 0; int indent_diff = 0; // init for gcc bool first_indent = true; int lendiff = 0; if (flags & PUT_FIXINDENT) { orig_indent = get_indent(); } // Insert at least one line. When y_type is kMTCharWise, break the first // line in two. for (int cnt = 1; cnt <= count; cnt++) { size_t i = 0; if (y_type == kMTCharWise) { // Split the current line in two at the insert position. // First insert y_array[size - 1] in front of second line. // Then append y_array[0] to first line. lnum = new_cursor.lnum; char *ptr = ml_get(lnum) + col; size_t ptrlen = (size_t)ml_get_len(lnum) - (size_t)col; totlen = y_array[y_size - 1].size; char *newp = xmalloc(ptrlen + totlen + 1); STRCPY(newp, y_array[y_size - 1].data); STRCPY(newp + totlen, ptr); // insert second line ml_append(lnum, newp, 0, false); new_lnum++; xfree(newp); char *oldp = ml_get(lnum); newp = xmalloc((size_t)col + (size_t)yanklen + 1); // copy first part of line memmove(newp, oldp, (size_t)col); // append to first line memmove(newp + col, y_array[0].data, (size_t)yanklen + 1); ml_replace(lnum, newp, false); curwin->w_cursor.lnum = lnum; i = 1; } for (; i < y_size; i++) { if ((y_type != kMTCharWise || i < y_size - 1)) { if (ml_append(lnum, y_array[i].data, 0, false) == FAIL) { goto error; } new_lnum++; } lnum++; nr_lines++; if (flags & PUT_FIXINDENT) { pos_T old_pos = curwin->w_cursor; curwin->w_cursor.lnum = lnum; char *ptr = ml_get(lnum); if (cnt == count && i == y_size - 1) { lendiff = ml_get_len(lnum); } if (*ptr == '#' && preprocs_left()) { indent = 0; // Leave # lines at start } else if (*ptr == NUL) { indent = 0; // Ignore empty lines } else if (first_indent) { indent_diff = orig_indent - get_indent(); indent = orig_indent; first_indent = false; } else if ((indent = get_indent() + indent_diff) < 0) { indent = 0; } set_indent(indent, SIN_NOMARK); curwin->w_cursor = old_pos; // remember how many chars were removed if (cnt == count && i == y_size - 1) { lendiff -= ml_get_len(lnum); } } } bcount_t totsize = 0; int lastsize = 0; if (y_type == kMTCharWise || (y_type == kMTLineWise && (flags & PUT_LINE_SPLIT))) { for (i = 0; i < y_size - 1; i++) { totsize += (bcount_t)y_array[i].size + 1; } lastsize = (int)y_array[y_size - 1].size; totsize += lastsize; } if (y_type == kMTCharWise) { extmark_splice(curbuf, (int)new_cursor.lnum - 1, col, 0, 0, 0, (int)y_size - 1, lastsize, totsize, kExtmarkUndo); } else if (y_type == kMTLineWise && (flags & PUT_LINE_SPLIT)) { // Account for last pasted NL + last NL extmark_splice(curbuf, (int)new_cursor.lnum - 1, split_pos, 0, 0, 0, (int)y_size + 1, 0, totsize + 2, kExtmarkUndo); } if (cnt == 1) { new_lnum = lnum; } } error: // Adjust marks. if (y_type == kMTLineWise) { curbuf->b_op_start.col = 0; if (dir == FORWARD) { curbuf->b_op_start.lnum++; } } ExtmarkOp kind = (y_type == kMTLineWise && !(flags & PUT_LINE_SPLIT)) ? kExtmarkUndo : kExtmarkNOOP; mark_adjust(curbuf->b_op_start.lnum + (y_type == kMTCharWise), (linenr_T)MAXLNUM, nr_lines, 0, kind); // note changed text for displaying and folding if (y_type == kMTCharWise) { changed_lines(curbuf, curwin->w_cursor.lnum, col, curwin->w_cursor.lnum + 1, nr_lines, true); } else { changed_lines(curbuf, curbuf->b_op_start.lnum, 0, curbuf->b_op_start.lnum, nr_lines, true); } // Put the '] mark on the first byte of the last inserted character. // Correct the length for change in indent. curbuf->b_op_end.lnum = new_lnum; col = MAX(0, (colnr_T)y_array[y_size - 1].size - lendiff); if (col > 1) { curbuf->b_op_end.col = col - 1; if (y_array[y_size - 1].size > 0) { curbuf->b_op_end.col -= utf_head_off(y_array[y_size - 1].data, y_array[y_size - 1].data + y_array[y_size - 1].size - 1); } } else { curbuf->b_op_end.col = 0; } if (flags & PUT_CURSLINE) { // ":put": put cursor on last inserted line curwin->w_cursor.lnum = lnum; beginline(BL_WHITE | BL_FIX); } else if (flags & PUT_CURSEND) { // put cursor after inserted text if (y_type == kMTLineWise) { if (lnum >= curbuf->b_ml.ml_line_count) { curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; } else { curwin->w_cursor.lnum = lnum + 1; } curwin->w_cursor.col = 0; } else { curwin->w_cursor.lnum = new_lnum; curwin->w_cursor.col = col; curbuf->b_op_end = curwin->w_cursor; if (col > 1) { curbuf->b_op_end.col = col - 1; } } } else if (y_type == kMTLineWise) { // put cursor on first non-blank in first inserted line curwin->w_cursor.col = 0; if (dir == FORWARD) { curwin->w_cursor.lnum++; } beginline(BL_WHITE | BL_FIX); } else { // put cursor on first inserted character curwin->w_cursor = new_cursor; } } } msgmore(nr_lines); curwin->w_set_curswant = true; // Make sure the cursor is not after the NUL. int len = get_cursor_line_len(); if (curwin->w_cursor.col > len) { if (cur_ve_flags == kOptVeFlagAll) { curwin->w_cursor.coladd = curwin->w_cursor.col - len; } curwin->w_cursor.col = len; } end: if (cmdmod.cmod_flags & CMOD_LOCKMARKS) { curbuf->b_op_start = orig_start; curbuf->b_op_end = orig_end; } if (allocated) { xfree(insert_string.data); } if (regname == '=') { xfree(y_array); } VIsual_active = false; // If the cursor is past the end of the line put it at the end. adjust_cursor_eol(); } /// display a string for do_dis() /// truncate at end of screen line /// /// @param skip_esc if true, ignore trailing ESC static void dis_msg(const char *p, bool skip_esc) FUNC_ATTR_NONNULL_ALL { int n = Columns - 6; while (*p != NUL && !(*p == ESC && skip_esc && *(p + 1) == NUL) && (n -= ptr2cells(p)) >= 0) { int l; if ((l = utfc_ptr2len(p)) > 1) { msg_outtrans_len(p, l, 0, false); p += l; } else { msg_outtrans_len(p++, 1, 0, false); } } os_breakcheck(); } /// ":dis" and ":registers": Display the contents of the yank registers. void ex_display(exarg_T *eap) { char *p; yankreg_T *yb; char *arg = eap->arg; int type; if (arg != NULL && *arg == NUL) { arg = NULL; } int hl_id = HLF_8; msg_ext_set_kind("list_cmd"); msg_ext_skip_flush = true; // Highlight title msg_puts_title(_("\nType Name Content")); for (int i = -1; i < NUM_REGISTERS && !got_int; i++) { int name = get_register_name(i); if (arg != NULL && vim_strchr(arg, name) == NULL) { continue; // did not ask for this register } switch (get_reg_type(name, NULL)) { case kMTLineWise: type = 'l'; break; case kMTCharWise: type = 'c'; break; default: type = 'b'; break; } if (i == -1) { if (y_previous != NULL) { yb = y_previous; } else { yb = &(y_regs[0]); } } else { yb = &(y_regs[i]); } get_clipboard(name, &yb, true); if (name == mb_tolower(redir_reg) || (redir_reg == '"' && yb == y_previous)) { continue; // do not list register being written to, the // pointer can be freed } if (yb->y_array != NULL) { bool do_show = false; for (size_t j = 0; !do_show && j < yb->y_size; j++) { do_show = !message_filtered(yb->y_array[j].data); } if (do_show || yb->y_size == 0) { msg_putchar('\n'); msg_puts(" "); msg_putchar(type); msg_puts(" "); msg_putchar('"'); msg_putchar(name); msg_puts(" "); int n = Columns - 11; for (size_t j = 0; j < yb->y_size && n > 1; j++) { if (j) { msg_puts_hl("^J", hl_id, false); n -= 2; } for (p = yb->y_array[j].data; *p != NUL && (n -= ptr2cells(p)) >= 0; p++) { int clen = utfc_ptr2len(p); msg_outtrans_len(p, clen, 0, false); p += clen - 1; } } if (n > 1 && yb->y_type == kMTLineWise) { msg_puts_hl("^J", hl_id, false); } } os_breakcheck(); } } // display last inserted text String insert = get_last_insert(); if ((p = insert.data) != NULL && (arg == NULL || vim_strchr(arg, '.') != NULL) && !got_int && !message_filtered(p)) { msg_puts("\n c \". "); dis_msg(p, true); } // display last command line if (last_cmdline != NULL && (arg == NULL || vim_strchr(arg, ':') != NULL) && !got_int && !message_filtered(last_cmdline)) { msg_puts("\n c \": "); dis_msg(last_cmdline, false); } // display current file name if (curbuf->b_fname != NULL && (arg == NULL || vim_strchr(arg, '%') != NULL) && !got_int && !message_filtered(curbuf->b_fname)) { msg_puts("\n c \"% "); dis_msg(curbuf->b_fname, false); } // display alternate file name if ((arg == NULL || vim_strchr(arg, '%') != NULL) && !got_int) { char *fname; linenr_T dummy; if (buflist_name_nr(0, &fname, &dummy) != FAIL && !message_filtered(fname)) { msg_puts("\n c \"# "); dis_msg(fname, false); } } // display last search pattern if (last_search_pat() != NULL && (arg == NULL || vim_strchr(arg, '/') != NULL) && !got_int && !message_filtered(last_search_pat())) { msg_puts("\n c \"/ "); dis_msg(last_search_pat(), false); } // display last used expression if (expr_line != NULL && (arg == NULL || vim_strchr(arg, '=') != NULL) && !got_int && !message_filtered(expr_line)) { msg_puts("\n c \"= "); dis_msg(expr_line, false); } msg_ext_skip_flush = false; } /// Used for getregtype() /// /// @return the type of a register or /// kMTUnknown for error. MotionType get_reg_type(int regname, colnr_T *reg_width) { switch (regname) { case '%': // file name case '#': // alternate file name case '=': // expression case ':': // last command line case '/': // last search-pattern case '.': // last inserted text case Ctrl_F: // Filename under cursor case Ctrl_P: // Path under cursor, expand via "path" case Ctrl_W: // word under cursor case Ctrl_A: // WORD (mnemonic All) under cursor case '_': // black hole: always empty return kMTCharWise; } if (regname != NUL && !valid_yank_reg(regname, false)) { return kMTUnknown; } yankreg_T *reg = get_yank_register(regname, YREG_PASTE); if (reg->y_array != NULL) { if (reg_width != NULL && reg->y_type == kMTBlockWise) { *reg_width = reg->y_width; } return reg->y_type; } return kMTUnknown; } /// When `flags` has `kGRegList` return a list with text `s`. /// Otherwise just return `s`. /// /// @return a void * for use in get_reg_contents(). static void *get_reg_wrap_one_line(char *s, int flags) { if (!(flags & kGRegList)) { return s; } list_T *const list = tv_list_alloc(1); tv_list_append_allocated_string(list, s); return list; } /// Gets the contents of a register. /// @remark Used for `@r` in expressions and for `getreg()`. /// /// @param regname The register. /// @param flags see @ref GRegFlags /// /// @returns The contents of the register as an allocated string. /// @returns A linked list when `flags` contains @ref kGRegList. /// @returns NULL for error. void *get_reg_contents(int regname, int flags) { // Don't allow using an expression register inside an expression. if (regname == '=') { if (flags & kGRegNoExpr) { return NULL; } if (flags & kGRegExprSrc) { return get_reg_wrap_one_line(get_expr_line_src(), flags); } return get_reg_wrap_one_line(get_expr_line(), flags); } if (regname == '@') { // "@@" is used for unnamed register regname = '"'; } // check for valid regname if (regname != NUL && !valid_yank_reg(regname, false)) { return NULL; } char *retval; bool allocated; if (get_spec_reg(regname, &retval, &allocated, false)) { if (retval == NULL) { return NULL; } if (allocated) { return get_reg_wrap_one_line(retval, flags); } return get_reg_wrap_one_line(xstrdup(retval), flags); } yankreg_T *reg = get_yank_register(regname, YREG_PUT); if (reg->y_array == NULL) { return NULL; } if (flags & kGRegList) { list_T *const list = tv_list_alloc((ptrdiff_t)reg->y_size); for (size_t i = 0; i < reg->y_size; i++) { tv_list_append_string(list, reg->y_array[i].data, (int)reg->y_array[i].size); } return list; } // Compute length of resulting string. size_t len = 0; for (size_t i = 0; i < reg->y_size; i++) { len += reg->y_array[i].size; // Insert a newline between lines and after last line if y_type is kMTLineWise. if (reg->y_type == kMTLineWise || i < reg->y_size - 1) { len++; } } retval = xmalloc(len + 1); // Copy the lines of the yank register into the string. len = 0; for (size_t i = 0; i < reg->y_size; i++) { STRCPY(retval + len, reg->y_array[i].data); len += reg->y_array[i].size; // Insert a newline between lines and after the last line if y_type is kMTLineWise. if (reg->y_type == kMTLineWise || i < reg->y_size - 1) { retval[len++] = '\n'; } } retval[len] = NUL; return retval; } static yankreg_T *init_write_reg(int name, yankreg_T **old_y_previous, bool must_append) { if (!valid_yank_reg(name, true)) { // check for valid reg name emsg_invreg(name); return NULL; } // Don't want to change the current (unnamed) register. *old_y_previous = y_previous; yankreg_T *reg = get_yank_register(name, YREG_YANK); if (!is_append_register(name) && !must_append) { free_register(reg); } return reg; } /// str_to_reg - Put a string into a register. /// /// When the register is not empty, the string is appended. /// /// @param y_ptr pointer to yank register /// @param yank_type The motion type (kMTUnknown to auto detect) /// @param str string or list of strings to put in register /// @param len length of the string (Ignored when str_list=true.) /// @param blocklen width of visual block, or -1 for "I don't know." /// @param str_list True if str is `char **`. static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char *str, size_t len, colnr_T blocklen, bool str_list) FUNC_ATTR_NONNULL_ALL { if (y_ptr->y_array == NULL) { // NULL means empty register y_ptr->y_size = 0; } if (yank_type == kMTUnknown) { yank_type = ((str_list || (len > 0 && (str[len - 1] == NL || str[len - 1] == CAR))) ? kMTLineWise : kMTCharWise); } size_t newlines = 0; bool extraline = false; // extra line at the end bool append = false; // append to last line in register // Count the number of lines within the string if (str_list) { for (char **ss = (char **)str; *ss != NULL; ss++) { newlines++; } } else { newlines = memcnt(str, '\n', len); if (yank_type == kMTCharWise || len == 0 || str[len - 1] != '\n') { extraline = 1; newlines++; // count extra newline at the end } if (y_ptr->y_size > 0 && y_ptr->y_type == kMTCharWise) { append = true; newlines--; // uncount newline when appending first line } } // Without any lines make the register empty. if (y_ptr->y_size + newlines == 0) { XFREE_CLEAR(y_ptr->y_array); return; } // Grow the register array to hold the pointers to the new lines. String *pp = xrealloc(y_ptr->y_array, (y_ptr->y_size + newlines) * sizeof(String)); y_ptr->y_array = pp; size_t lnum = y_ptr->y_size; // The current line number. // If called with `blocklen < 0`, we have to update the yank reg's width. size_t maxlen = 0; // Find the end of each line and save it into the array. if (str_list) { for (char **ss = (char **)str; *ss != NULL; ss++, lnum++) { pp[lnum] = cstr_to_string(*ss); if (yank_type == kMTBlockWise) { size_t charlen = mb_string2cells(*ss); maxlen = MAX(maxlen, charlen); } } } else { size_t line_len; for (const char *start = str, *end = str + len; start < end + extraline; start += line_len + 1, lnum++) { int charlen = 0; const char *line_end = start; while (line_end < end) { // find the end of the line if (*line_end == '\n') { break; } if (yank_type == kMTBlockWise) { charlen += utf_ptr2cells_len(line_end, (int)(end - line_end)); } if (*line_end == NUL) { line_end++; // registers can have NUL chars } else { line_end += utf_ptr2len_len(line_end, (int)(end - line_end)); } } assert(line_end - start >= 0); line_len = (size_t)(line_end - start); maxlen = MAX(maxlen, (size_t)charlen); // When appending, copy the previous line and free it after. size_t extra = append ? pp[--lnum].size : 0; char *s = xmallocz(line_len + extra); if (extra > 0) { memcpy(s, pp[lnum].data, extra); } if (line_len > 0) { memcpy(s + extra, start, line_len); } size_t s_len = extra + line_len; if (append) { xfree(pp[lnum].data); append = false; // only first line is appended } pp[lnum] = cbuf_as_string(s, s_len); // Convert NULs to '\n' to prevent truncation. memchrsub(pp[lnum].data, NUL, '\n', s_len); } } y_ptr->y_type = yank_type; y_ptr->y_size = lnum; XFREE_CLEAR(y_ptr->additional_data); y_ptr->timestamp = os_time(); if (yank_type == kMTBlockWise) { y_ptr->y_width = (blocklen == -1 ? (colnr_T)maxlen - 1 : blocklen); } else { y_ptr->y_width = 0; } } static void finish_write_reg(int name, yankreg_T *reg, yankreg_T *old_y_previous) { // Send text of clipboard register to the clipboard. set_clipboard(name, reg); // ':let @" = "val"' should change the meaning of the "" register if (name != '"') { y_previous = old_y_previous; } } /// store `str` in register `name` /// /// @see write_reg_contents_ex void write_reg_contents(int name, const char *str, ssize_t len, int must_append) { write_reg_contents_ex(name, str, len, must_append, kMTUnknown, 0); } void write_reg_contents_lst(int name, char **strings, bool must_append, MotionType yank_type, colnr_T block_len) { if (name == '/' || name == '=') { char *s = strings[0]; if (strings[0] == NULL) { s = ""; } else if (strings[1] != NULL) { emsg(_(e_search_pattern_and_expression_register_may_not_contain_two_or_more_lines)); return; } write_reg_contents_ex(name, s, -1, must_append, yank_type, block_len); return; } // black hole: nothing to do if (name == '_') { return; } yankreg_T *old_y_previous, *reg; if (!(reg = init_write_reg(name, &old_y_previous, must_append))) { return; } str_to_reg(reg, yank_type, (char *)strings, strlen((char *)strings), block_len, true); finish_write_reg(name, reg, old_y_previous); } /// write_reg_contents_ex - store `str` in register `name` /// /// If `str` ends in '\n' or '\r', use linewise, otherwise use charwise. /// /// @warning when `name` is '/', `len` and `must_append` are ignored. This /// means that `str` MUST be NUL-terminated. /// /// @param name The name of the register /// @param str The contents to write /// @param len If >= 0, write `len` bytes of `str`. Otherwise, write /// `strlen(str)` bytes. If `len` is larger than the /// allocated size of `src`, the behaviour is undefined. /// @param must_append If true, append the contents of `str` to the current /// contents of the register. Note that regardless of /// `must_append`, this function will append when `name` /// is an uppercase letter. /// @param yank_type The motion type (kMTUnknown to auto detect) /// @param block_len width of visual block void write_reg_contents_ex(int name, const char *str, ssize_t len, bool must_append, MotionType yank_type, colnr_T block_len) { if (len < 0) { len = (ssize_t)strlen(str); } // Special case: '/' search pattern if (name == '/') { set_last_search_pat(str, RE_SEARCH, true, true); return; } if (name == '#') { buf_T *buf; if (ascii_isdigit(*str)) { int num = atoi(str); buf = buflist_findnr(num); if (buf == NULL) { semsg(_(e_nobufnr), (int64_t)num); } } else { buf = buflist_findnr(buflist_findpat(str, str + len, true, false, false)); } if (buf == NULL) { return; } curwin->w_alt_fnum = buf->b_fnum; return; } if (name == '=') { size_t offset = 0; size_t totlen = (size_t)len; if (must_append && expr_line) { // append has been specified and expr_line already exists, so we'll // append the new string to expr_line. size_t exprlen = strlen(expr_line); totlen += exprlen; offset = exprlen; } // modify the global expr_line, extend/shrink it if necessary (realloc). // Copy the input string into the adjusted memory at the specified // offset. expr_line = xrealloc(expr_line, totlen + 1); memcpy(expr_line + offset, str, (size_t)len); expr_line[totlen] = NUL; return; } if (name == '_') { // black hole: nothing to do return; } yankreg_T *old_y_previous, *reg; if (!(reg = init_write_reg(name, &old_y_previous, must_append))) { return; } str_to_reg(reg, yank_type, str, (size_t)len, block_len, false); finish_write_reg(name, reg, old_y_previous); } /// @param[out] reg Expected to be empty bool prepare_yankreg_from_object(yankreg_T *reg, String regtype, size_t lines) { char type = regtype.data ? regtype.data[0] : NUL; switch (type) { case 0: reg->y_type = kMTUnknown; break; case 'v': case 'c': reg->y_type = kMTCharWise; break; case 'V': case 'l': reg->y_type = kMTLineWise; break; case 'b': case Ctrl_V: reg->y_type = kMTBlockWise; break; default: return false; } reg->y_width = 0; if (regtype.size > 1) { if (reg->y_type != kMTBlockWise) { return false; } // allow "b7" for a block at least 7 spaces wide if (!ascii_isdigit(regtype.data[1])) { return false; } const char *p = regtype.data + 1; reg->y_width = getdigits_int((char **)&p, false, 1) - 1; if (regtype.size > (size_t)(p - regtype.data)) { return false; } } reg->additional_data = NULL; reg->timestamp = 0; return true; } void finish_yankreg_from_object(yankreg_T *reg, bool clipboard_adjust) { if (reg->y_size > 0 && reg->y_array[reg->y_size - 1].size == 0) { // a known-to-be charwise yank might have a final linebreak // but otherwise there is no line after the final newline if (reg->y_type != kMTCharWise) { if (reg->y_type == kMTUnknown || clipboard_adjust) { reg->y_size--; } if (reg->y_type == kMTUnknown) { reg->y_type = kMTLineWise; } } } else { if (reg->y_type == kMTUnknown) { reg->y_type = kMTCharWise; } } update_yankreg_width(reg); }