Files
neovim/src/nvim/register.c
zeertzjq 3f4ef487da vim-patch:9.2.0040: completion: preinsert wrong with register completion (#38003)
Problem:  completion: preinsert wrong with register completion
Solution: Remove preinserted text during register content insertion
          (Girish Palya).

fixes:  vim/vim#19329
closes: vim/vim#19474

3684ad83d1

Co-authored-by: Girish Palya <girishji@gmail.com>
2026-02-22 08:13:15 +08:00

2728 lines
80 KiB
C

// 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, &reg, 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 :@<register>
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));
*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
}
#if defined(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, -1);
}
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);
} else {
getvcol(curwin, &curwin->w_cursor, NULL, NULL, &col);
}
// 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);
}
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);
}
}
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, -1);
}
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);
}