commit a0c60e819df1b4d4ae5ac631d48e874fa1cf9b92
parent 42aae632beb064ebb35da81f13a665c47c0a8dbe
Author: zeertzjq <zeertzjq@outlook.com>
Date: Fri, 26 Sep 2025 13:10:24 +0800
Merge pull request #35790 from janlazo/vim-8.1.1989
vim-patch:8.1.{2021,2077}
Diffstat:
21 files changed, 4123 insertions(+), 4047 deletions(-)
diff --git a/src/gen/gen_ex_cmds.lua b/src/gen/gen_ex_cmds.lua
@@ -74,6 +74,7 @@ defsfile:write(string.format(
#include "nvim/os/lang.h"
#include "nvim/profile.h"
#include "nvim/quickfix.h"
+#include "nvim/register.h"
#include "nvim/runtime.h"
#include "nvim/sign.h"
#include "nvim/spell.h"
diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c
@@ -28,9 +28,9 @@
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/memory_defs.h"
-#include "nvim/ops.h"
#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
+#include "nvim/register.h"
#include "nvim/strings.h"
#include "nvim/types_defs.h"
#include "nvim/usercmd.h"
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
@@ -65,7 +65,6 @@
#include "nvim/msgpack_rpc/channel.h"
#include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/msgpack_rpc/unpacker.h"
-#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/option_defs.h"
#include "nvim/option_vars.h"
@@ -75,6 +74,7 @@
#include "nvim/os/proc.h"
#include "nvim/popupmenu.h"
#include "nvim/pos_defs.h"
+#include "nvim/register.h"
#include "nvim/runtime.h"
#include "nvim/sign_defs.h"
#include "nvim/state.h"
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
@@ -65,6 +65,7 @@
#include "nvim/plines.h"
#include "nvim/popupmenu.h"
#include "nvim/pos_defs.h"
+#include "nvim/register.h"
#include "nvim/search.h"
#include "nvim/state.h"
#include "nvim/state_defs.h"
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
@@ -73,6 +73,7 @@
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
#include "nvim/regexp_defs.h"
+#include "nvim/register.h"
#include "nvim/runtime.h"
#include "nvim/runtime_defs.h"
#include "nvim/strings.h"
@@ -5865,7 +5866,7 @@ static list_T *string_to_list(const char *str, size_t len, const bool keepempty)
}
/// os_system wrapper. Handles 'verbose', :profile, and v:shell_error.
-void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, bool retlist)
+static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, bool retlist)
{
proftime_T wait_time;
bool profiling = do_profiling == PROF_YES;
@@ -5963,6 +5964,17 @@ void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, bool retlist
}
}
+/// f_system - the Vimscript system() function
+void f_system(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ get_system_output_as_rettv(argvars, rettv, false);
+}
+
+void f_systemlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ get_system_output_as_rettv(argvars, rettv, true);
+}
+
/// Get a callback from "arg". It can be a Funcref or a function name.
bool callback_from_typval(Callback *const callback, const typval_T *const arg)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
@@ -114,6 +114,7 @@
#include "nvim/profile.h"
#include "nvim/regexp.h"
#include "nvim/regexp_defs.h"
+#include "nvim/register.h"
#include "nvim/runtime.h"
#include "nvim/runtime_defs.h"
#include "nvim/search.h"
@@ -7950,17 +7951,6 @@ static void f_synstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
-/// f_system - the Vimscript system() function
-static void f_system(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- get_system_output_as_rettv(argvars, rettv, false);
-}
-
-static void f_systemlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- get_system_output_as_rettv(argvars, rettv, true);
-}
-
/// "tabpagebuflist()" function
static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c
@@ -36,10 +36,10 @@
#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/option_defs.h"
#include "nvim/os/os.h"
+#include "nvim/register.h"
#include "nvim/search.h"
#include "nvim/strings.h"
#include "nvim/types_defs.h"
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
@@ -72,7 +72,6 @@
#include "nvim/move.h"
#include "nvim/normal.h"
#include "nvim/normal_defs.h"
-#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/option_defs.h"
#include "nvim/option_vars.h"
@@ -90,6 +89,7 @@
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
#include "nvim/regexp_defs.h"
+#include "nvim/register.h"
#include "nvim/runtime.h"
#include "nvim/runtime_defs.h"
#include "nvim/search.h"
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
@@ -70,6 +70,7 @@
#include "nvim/profile.h"
#include "nvim/regexp.h"
#include "nvim/regexp_defs.h"
+#include "nvim/register.h"
#include "nvim/search.h"
#include "nvim/state.h"
#include "nvim/state_defs.h"
diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
@@ -55,7 +55,6 @@
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/move.h"
-#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/option_defs.h"
#include "nvim/option_vars.h"
@@ -67,6 +66,7 @@
#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
#include "nvim/regexp_defs.h"
+#include "nvim/register.h"
#include "nvim/search.h"
#include "nvim/spell.h"
#include "nvim/state.h"
diff --git a/src/nvim/main.c b/src/nvim/main.c
@@ -92,6 +92,7 @@
#include "nvim/popupmenu.h"
#include "nvim/profile.h"
#include "nvim/quickfix.h"
+#include "nvim/register.h"
#include "nvim/runtime.h"
#include "nvim/runtime_defs.h"
#include "nvim/shada.h"
diff --git a/src/nvim/memory.c b/src/nvim/memory.c
@@ -833,11 +833,11 @@ char *arena_memdupz(Arena *arena, const char *buf, size_t size)
# include "nvim/grid.h"
# include "nvim/mark.h"
# include "nvim/msgpack_rpc/channel.h"
-# include "nvim/ops.h"
# include "nvim/option.h"
# include "nvim/os/os.h"
# include "nvim/quickfix.h"
# include "nvim/regexp.h"
+# include "nvim/register.h"
# include "nvim/search.h"
# include "nvim/spell.h"
# include "nvim/tag.h"
diff --git a/src/nvim/message.c b/src/nvim/message.c
@@ -53,7 +53,6 @@
#include "nvim/message.h"
#include "nvim/message_defs.h"
#include "nvim/mouse.h"
-#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/option_vars.h"
#include "nvim/os/fs.h"
@@ -62,6 +61,7 @@
#include "nvim/os/time.h"
#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
+#include "nvim/register.h"
#include "nvim/runtime.h"
#include "nvim/runtime_defs.h"
#include "nvim/state_defs.h"
diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c
@@ -38,6 +38,7 @@
#include "nvim/plines.h"
#include "nvim/popupmenu.h"
#include "nvim/pos_defs.h"
+#include "nvim/register.h"
#include "nvim/search.h"
#include "nvim/state.h"
#include "nvim/state_defs.h"
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
@@ -69,6 +69,7 @@
#include "nvim/plines.h"
#include "nvim/profile.h"
#include "nvim/quickfix.h"
+#include "nvim/register.h"
#include "nvim/search.h"
#include "nvim/spell.h"
#include "nvim/spell_defs.h"
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
@@ -1,5 +1,5 @@
// ops.c: implementation of various operators: op_shift, op_delete, op_tilde,
-// op_change, op_yank, do_put, do_join
+// op_change, op_yank, do_join
#include <assert.h>
#include <ctype.h>
@@ -65,6 +65,7 @@
#include "nvim/os/input.h"
#include "nvim/os/time.h"
#include "nvim/plines.h"
+#include "nvim/register.h"
#include "nvim/search.h"
#include "nvim/state.h"
#include "nvim/state_defs.h"
@@ -77,21 +78,8 @@
#include "nvim/undo.h"
#include "nvim/vim_defs.h"
-static yankreg_T y_regs[NUM_REGISTERS] = { 0 };
-
-static yankreg_T *y_previous = NULL; // ptr to last written yankreg
-
-// for behavior between start_batch_changes() and end_batch_changes())
-static int batch_change_count = 0; // inside a script
-static bool clipboard_delay_update = false; // delay clipboard update
-static bool clipboard_needs_update = false; // clipboard was updated
-static bool clipboard_didwarn = false;
-
#include "ops.c.generated.h"
-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");
-
// Flags for third item in "opchars".
#define OPF_LINES 1 // operator always works on lines
#define OPF_CHANGE 2 // operator changes text
@@ -715,3126 +703,1097 @@ static void block_insert(oparg_T *oap, const char *s, size_t slen, bool b_insert
}
}
-// Keep the last expression line here, for repeating.
-static char *expr_line = NULL;
-
-/// 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.
+/// Handle a delete operation.
///
-/// @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)
+/// @return FAIL if undo failed, OK otherwise.
+int op_delete(oparg_T *oap)
{
- if (expr_line == NULL) {
- return NULL;
- }
- return xstrdup(expr_line);
-}
+ linenr_T lnum;
+ struct block_def bd = { 0 };
+ linenr_T old_lcount = curbuf->b_ml.ml_line_count;
-/// @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;
+ if (curbuf->b_ml.ml_flags & ML_EMPTY) { // nothing to do
+ return OK;
}
- return false;
-}
-/// @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;
+ // Nothing to delete, return here. Do prepare undo, for op_change().
+ if (oap->empty) {
+ return u_save_cursor();
}
- int i = op_reg_index(regname);
- // when not 0-9, a-z, A-Z or '-'/'+'/'*': use register 0
- if (i == -1) {
- i = 0;
+ if (!MODIFIABLE(curbuf)) {
+ emsg(_(e_modifiable));
+ return FAIL;
}
- reg = &y_regs[i];
- if (mode == YREG_YANK) {
- // remember the written register for unnamed paste
- y_previous = reg;
+ if (VIsual_select && oap->is_VIsual) {
+ // Use the register given with CTRL_R, defaults to zero
+ oap->regname = VIsual_select_reg;
}
- return reg;
-}
-
-static bool is_append_register(int regname)
-{
- return ASCII_ISUPPER(regname);
-}
-/// @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);
+ mb_adjust_opend(oap);
- 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);
+ // Imitate the strange Vi behaviour: If the delete spans more than one
+ // line and motion_type == kMTCharWise and the result is a blank line, make the
+ // delete linewise. Don't do this for the change command or Visual mode.
+ if (oap->motion_type == kMTCharWise
+ && !oap->is_VIsual
+ && oap->line_count > 1
+ && oap->motion_force == NUL
+ && oap->op_type == OP_DELETE) {
+ char *ptr = ml_get(oap->end.lnum) + oap->end.col;
+ if (*ptr != NUL) {
+ ptr += oap->inclusive;
+ }
+ ptr = skipwhite(ptr);
+ if (*ptr == NUL && inindent(0)) {
+ oap->motion_type = kMTLineWise;
}
}
- return copy;
-}
-
-/// 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;
-}
-/// 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);
+ // Check for trying to delete (e.g. "D") in an empty line.
+ // Note: For the change operator it is ok.
+ if (oap->motion_type != kMTLineWise
+ && oap->line_count == 1
+ && oap->op_type == OP_DELETE
+ && *ml_get(oap->start.lnum) == NUL) {
+ // It's an error to operate on an empty region, when 'E' included in
+ // 'cpoptions' (Vi compatible).
+ if (virtual_op) {
+ // Virtual editing: Nothing gets deleted, but we set the '[ and ']
+ // marks as if it happened.
+ goto setmarks;
}
- 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;
+ if (vim_strchr(p_cpo, CPO_EMPTYREGION) != NULL) {
+ beep_flush();
}
- }
- return retval;
-}
-
-/// 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;
-}
-
-static int execreg_lastc = 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 i = *idx;
- assert(i > 0);
- const size_t cmd_end = i;
-
- 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 (--i > 0) {
- char *p = skipwhite(lines[i].data);
- if (*p != '\\' && (p[0] != '"' || p[1] != '\\' || p[2] != ' ')) {
- break;
- }
- }
- const size_t cmd_start = i;
-
- // join all the lines
- ga_concat(&ga, lines[cmd_start].data);
- for (size_t j = cmd_start + 1; j <= cmd_end; j++) {
- char *p = skipwhite(lines[j].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));
+ // Do a yank of whatever we're about to delete.
+ // If a yank register was specified, put the deleted text into that
+ // register. For the black hole register '_' don't yank anything.
+ if (oap->regname != '_') {
+ yankreg_T *reg = NULL;
+ bool did_yank = false;
+ if (oap->regname != 0) {
+ // check for read-only register
+ if (!valid_yank_reg(oap->regname, true)) {
+ beep_flush();
+ return OK;
}
- ga_concat(&ga, p + 1);
+ reg = get_yank_register(oap->regname, YREG_YANK); // yank into specif'd reg
+ op_yank_reg(oap, false, reg, is_append_register(oap->regname)); // yank without message
+ did_yank = true;
}
- }
- ga_append(&ga, NUL);
- char *str = xmemdupz(ga.ga_data, (size_t)ga.ga_len);
- ga_clear(&ga);
- *idx = i;
- return str;
-}
+ // Put deleted text into register 1 and shift number registers if the
+ // delete contains a line break, or when using a specific operator (Vi
+ // compatible)
-/// 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 (oap->motion_type == kMTLineWise || oap->line_count > 1 || oap->use_reg_one) {
+ shift_delete_registers(is_append_register(oap->regname));
+ reg = get_y_register(1);
+ op_yank_reg(oap, false, reg, false);
+ did_yank = true;
+ }
- if (regname == '@') { // repeat previous one
- if (execreg_lastc == NUL) {
- emsg(_("E748: No previously used register"));
- return FAIL;
+ // Yank into small delete register when no named register specified
+ // and the delete is within one line.
+ if (oap->regname == 0 && oap->motion_type != kMTLineWise
+ && oap->line_count == 1) {
+ reg = get_yank_register('-', YREG_YANK);
+ op_yank_reg(oap, false, reg, false);
+ did_yank = true;
}
- 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 (did_yank || oap->regname == 0) {
+ if (reg == NULL) {
+ abort();
+ }
+ set_clipboard(oap->regname, reg);
+ do_autocmd_textyankpost(oap, reg);
+ }
}
- 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) {
+ // block mode delete
+ if (oap->motion_type == kMTBlockWise) {
+ if (u_save((linenr_T)(oap->start.lnum - 1),
+ (linenr_T)(oap->end.lnum + 1)) == FAIL) {
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;
- }
+ for (lnum = curwin->w_cursor.lnum; lnum <= oap->end.lnum; lnum++) {
+ block_prep(oap, &bd, lnum, true);
+ if (bd.textlen == 0) { // nothing to delete
+ continue;
}
- // 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;
- }
+ // Adjust cursor position for tab replaced by spaces and 'lbr'.
+ if (lnum == curwin->w_cursor.lnum) {
+ curwin->w_cursor.col = bd.textcol + bd.startspaces;
+ curwin->w_cursor.coladd = 0;
}
- char *escaped = vim_strsave_escape_ks(str);
- if (free_str) {
- xfree(str);
+
+ // "n" == number of chars deleted
+ // If we delete a TAB, it may be replaced by several characters.
+ // Thus the number of characters may increase!
+ int n = bd.textlen - bd.startspaces - bd.endspaces;
+ char *oldp = ml_get(lnum);
+ char *newp = xmalloc((size_t)ml_get_len(lnum) - (size_t)n + 1);
+ // copy up to deleted part
+ memmove(newp, oldp, (size_t)bd.textcol);
+ // insert spaces
+ memset(newp + bd.textcol, ' ', (size_t)bd.startspaces +
+ (size_t)bd.endspaces);
+ // copy the part after the deleted part
+ STRCPY(newp + bd.textcol + bd.startspaces + bd.endspaces,
+ oldp + bd.textcol + bd.textlen);
+ // replace the line
+ ml_replace(lnum, newp, false);
+
+ extmark_splice_cols(curbuf, (int)lnum - 1, bd.textcol,
+ bd.textlen, bd.startspaces + bd.endspaces,
+ kExtmarkUndo);
+ }
+
+ check_cursor_col(curwin);
+ changed_lines(curbuf, curwin->w_cursor.lnum, curwin->w_cursor.col,
+ oap->end.lnum + 1, 0, true);
+ oap->line_count = 0; // no lines deleted
+ } else if (oap->motion_type == kMTLineWise) {
+ if (oap->op_type == OP_CHANGE) {
+ // Delete the lines except the first one. Temporarily move the
+ // cursor to the next line. Save the current line number, if the
+ // last line is deleted it may be changed.
+
+ if (oap->line_count > 1) {
+ lnum = curwin->w_cursor.lnum;
+ curwin->w_cursor.lnum++;
+ del_lines(oap->line_count - 1, true);
+ curwin->w_cursor.lnum = lnum;
}
- retval = ins_typebuf(escaped, remap, 0, true, silent);
- xfree(escaped);
- if (retval == FAIL) {
+ if (u_save_cursor() == FAIL) {
return FAIL;
}
- if (colon
- && ins_typebuf(":", remap, 0, true, silent) == FAIL) {
- return FAIL;
+ if (curbuf->b_p_ai) { // don't delete indent
+ beginline(BL_WHITE); // cursor on first non-white
+ did_ai = true; // delete the indent when ESC hit
+ ai_col = curwin->w_cursor.col;
+ } else {
+ beginline(0); // cursor in column 0
+ }
+ truncate_line(false); // delete the rest of the line,
+ // leaving cursor past last char in line
+ if (oap->line_count > 1) {
+ u_clearline(curbuf); // "U" command not possible after "2cc"
}
+ } else {
+ del_lines(oap->line_count, true);
+ beginline(BL_WHITE | BL_FIX);
+ u_clearline(curbuf); // "U" command not possible after "dd"
}
- reg_executing = regname == 0 ? '"' : regname; // disable the 'q' command
- pending_end_reg_executing = false;
- }
- 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;
- }
-}
+ if (virtual_op) {
+ // For virtualedit: break the tabs that are partly included.
+ if (gchar_pos(&oap->start) == '\t') {
+ int endcol = 0;
+ if (u_save_cursor() == FAIL) { // save first line for undo
+ return FAIL;
+ }
+ if (oap->line_count == 1) {
+ endcol = getviscol2(oap->end.col, oap->end.coladd);
+ }
+ coladvance_force(getviscol2(oap->start.col, oap->start.coladd));
+ oap->start = curwin->w_cursor;
+ if (oap->line_count == 1) {
+ coladvance(curwin, endcol);
+ oap->end.col = curwin->w_cursor.col;
+ oap->end.coladd = curwin->w_cursor.coladd;
+ curwin->w_cursor = oap->start;
+ }
+ }
-/// 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;
+ // Break a tab only when it's included in the area.
+ if (gchar_pos(&oap->end) == '\t'
+ && oap->end.coladd == 0
+ && oap->inclusive) {
+ // save last line for undo
+ if (u_save((linenr_T)(oap->end.lnum - 1),
+ (linenr_T)(oap->end.lnum + 1)) == FAIL) {
+ return FAIL;
+ }
+ curwin->w_cursor = oap->end;
+ coladvance_force(getviscol2(oap->end.col, oap->end.coladd));
+ oap->end = curwin->w_cursor;
+ curwin->w_cursor = oap->start;
+ }
+ mb_adjust_opend(oap);
+ }
- put_reedit_in_typebuf(silent);
- if (colon) {
- retval = ins_typebuf("\n", REMAP_NONE, 0, true, silent);
- }
- if (retval == OK) {
- char *p;
+ if (oap->line_count == 1) { // delete characters within one line
+ if (u_save_cursor() == FAIL) { // save line for undo
+ return FAIL;
+ }
- 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 'cpoptions' contains '$', display '$' at end of change
+ if (vim_strchr(p_cpo, CPO_DOLLAR) != NULL
+ && oap->op_type == OP_CHANGE
+ && oap->end.lnum == curwin->w_cursor.lnum
+ && !oap->is_VIsual) {
+ display_dollar(oap->end.col - !oap->inclusive);
+ }
-/// 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;
- }
+ int n = oap->end.col - oap->start.col + 1 - !oap->inclusive;
- // check for valid regname
- if (regname != NUL && !valid_yank_reg(regname, false)) {
- return FAIL;
- }
+ if (virtual_op) {
+ // fix up things for virtualedit-delete:
+ // break the tabs which are going to get in our way
+ int len = get_cursor_line_len();
- 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;
- }
+ if (oap->end.coladd != 0
+ && (int)oap->end.col >= len - 1
+ && !(oap->start.coladd && (int)oap->end.col >= len - 1)) {
+ n++;
+ }
+ // Delete at least one char (e.g, when on a control char).
+ if (n == 0 && oap->start.coladd != oap->end.coladd) {
+ n = 1;
+ }
- 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');
- }
+ // When deleted a char in the line, reset coladd.
+ if (gchar_cursor() != NUL) {
+ curwin->w_cursor.coladd = 0;
}
}
- }
- }
-
- 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;
+ del_bytes((colnr_T)n, !virtual_op,
+ oap->op_type == OP_DELETE && !oap->is_VIsual);
+ } else {
+ // delete characters between lines
+ pos_T curpos;
- case '#': // alternate file name
- *argp = getaltfname(errmsg); // may give emsg if not set
- return true;
+ // save deleted and changed lines for undo
+ if (u_save(curwin->w_cursor.lnum - 1,
+ curwin->w_cursor.lnum + oap->line_count) == FAIL) {
+ return FAIL;
+ }
- case '=': // result of expression
- *argp = get_expr_line();
- *allocated = true;
- return true;
+ curbuf_splice_pending++;
+ pos_T startpos = curwin->w_cursor; // start position for delete
+ bcount_t deleted_bytes = get_region_bytecount(curbuf, startpos.lnum, oap->end.lnum,
+ startpos.col,
+ oap->end.col) + oap->inclusive;
+ truncate_line(true); // delete from cursor to end of line
- case ':': // last command line
- if (last_cmdline == NULL && errmsg) {
- emsg(_(e_nolastcmd));
- }
- *argp = last_cmdline;
- return true;
+ curpos = curwin->w_cursor; // remember curwin->w_cursor
+ curwin->w_cursor.lnum++;
- case '/': // last search-pattern
- if (last_search_pat() == NULL && errmsg) {
- emsg(_(e_noprevre));
- }
- *argp = last_search_pat();
- return true;
+ del_lines(oap->line_count - 2, false);
- case '.': // last inserted text
- *argp = get_last_insert_save();
- *allocated = true;
- if (*argp == NULL && errmsg) {
- emsg(_(e_noinstext));
+ // delete from start of line until op_end
+ int n = (oap->end.col + 1 - !oap->inclusive);
+ curwin->w_cursor.col = 0;
+ del_bytes((colnr_T)n, !virtual_op,
+ oap->op_type == OP_DELETE && !oap->is_VIsual);
+ curwin->w_cursor = curpos; // restore curwin->w_cursor
+ do_join(2, false, false, false, false);
+ curbuf_splice_pending--;
+ extmark_splice(curbuf, (int)startpos.lnum - 1, startpos.col,
+ (int)oap->line_count - 1, n, deleted_bytes,
+ 0, 0, 0, kExtmarkUndo);
}
- return true;
-
- case Ctrl_F: // Filename under cursor
- case Ctrl_P: // Path under cursor, expand via "path"
- if (!errmsg) {
- return false;
+ if (oap->op_type == OP_DELETE) {
+ auto_format(false, true);
}
- *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;
+ msgmore(curbuf->b_ml.ml_line_count - old_lcount);
- case Ctrl_L: // Line under cursor
- if (!errmsg) {
- return false;
+setmarks:
+ if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
+ if (oap->motion_type == kMTBlockWise) {
+ curbuf->b_op_end.lnum = oap->end.lnum;
+ curbuf->b_op_end.col = oap->start.col;
+ } else {
+ curbuf->b_op_end = oap->start;
}
-
- *argp = ml_get_buf(curwin->w_buffer, curwin->w_cursor.lnum);
- return true;
-
- case '_': // black hole: always empty
- *argp = "";
- return true;
+ curbuf->b_op_start = oap->start;
}
- return false;
+ return OK;
}
-/// 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)
+/// Adjust end of operating area for ending on a multi-byte character.
+/// Used for deletion.
+static void mb_adjust_opend(oparg_T *oap)
{
- 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;
+ if (!oap->inclusive) {
+ return;
}
- 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;
- }
+ const char *line = ml_get(oap->end.lnum);
+ const char *ptr = line + oap->end.col;
+ if (*ptr != NUL) {
+ ptr -= utf_head_off(line, ptr);
+ ptr += utfc_ptr2len(ptr) - 1;
+ oap->end.col = (colnr_T)(ptr - line);
}
- return OK;
}
-/// Shift the delete registers: "9 is cleared, "8 becomes "9, etc.
-static void shift_delete_registers(bool y_append)
+/// Put character 'c' at position 'lp'
+static inline void pbyte(pos_T lp, int c)
{
- 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];
+ assert(c <= UCHAR_MAX);
+ *(ml_get_buf_mut(curbuf, lp.lnum) + lp.col) = (char)c;
+ if (!curbuf_splice_pending) {
+ extmark_splice_cols(curbuf, (int)lp.lnum - 1, lp.col, 1, 1, kExtmarkUndo);
}
- y_regs[1].y_array = NULL; // set register "1 to empty
}
-/// Handle a delete operation.
-///
-/// @return FAIL if undo failed, OK otherwise.
-int op_delete(oparg_T *oap)
+/// Replace the character under the cursor with "c".
+/// This takes care of multi-byte characters.
+static void replace_character(int c)
{
- linenr_T lnum;
- struct block_def bd = { 0 };
- linenr_T old_lcount = curbuf->b_ml.ml_line_count;
+ const int n = State;
- if (curbuf->b_ml.ml_flags & ML_EMPTY) { // nothing to do
- return OK;
- }
+ State = MODE_REPLACE;
+ ins_char(c);
+ State = n;
+ // Backup to the replaced character.
+ dec_cursor();
+}
- // Nothing to delete, return here. Do prepare undo, for op_change().
- if (oap->empty) {
- return u_save_cursor();
- }
+/// Replace a whole area with one character.
+static int op_replace(oparg_T *oap, int c)
+{
+ int n;
+ struct block_def bd;
+ char *after_p = NULL;
+ bool had_ctrl_v_cr = false;
- if (!MODIFIABLE(curbuf)) {
- emsg(_(e_modifiable));
- return FAIL;
+ if ((curbuf->b_ml.ml_flags & ML_EMPTY) || oap->empty) {
+ return OK; // nothing to do
}
-
- if (VIsual_select && oap->is_VIsual) {
- // Use the register given with CTRL_R, defaults to zero
- oap->regname = VIsual_select_reg;
+ if (c == REPLACE_CR_NCHAR) {
+ had_ctrl_v_cr = true;
+ c = CAR;
+ } else if (c == REPLACE_NL_NCHAR) {
+ had_ctrl_v_cr = true;
+ c = NL;
}
mb_adjust_opend(oap);
- // Imitate the strange Vi behaviour: If the delete spans more than one
- // line and motion_type == kMTCharWise and the result is a blank line, make the
- // delete linewise. Don't do this for the change command or Visual mode.
- if (oap->motion_type == kMTCharWise
- && !oap->is_VIsual
- && oap->line_count > 1
- && oap->motion_force == NUL
- && oap->op_type == OP_DELETE) {
- char *ptr = ml_get(oap->end.lnum) + oap->end.col;
- if (*ptr != NUL) {
- ptr += oap->inclusive;
- }
- ptr = skipwhite(ptr);
- if (*ptr == NUL && inindent(0)) {
- oap->motion_type = kMTLineWise;
- }
- }
-
- // Check for trying to delete (e.g. "D") in an empty line.
- // Note: For the change operator it is ok.
- if (oap->motion_type != kMTLineWise
- && oap->line_count == 1
- && oap->op_type == OP_DELETE
- && *ml_get(oap->start.lnum) == NUL) {
- // It's an error to operate on an empty region, when 'E' included in
- // 'cpoptions' (Vi compatible).
- if (virtual_op) {
- // Virtual editing: Nothing gets deleted, but we set the '[ and ']
- // marks as if it happened.
- goto setmarks;
- }
- if (vim_strchr(p_cpo, CPO_EMPTYREGION) != NULL) {
- beep_flush();
- }
- return OK;
- }
-
- // Do a yank of whatever we're about to delete.
- // If a yank register was specified, put the deleted text into that
- // register. For the black hole register '_' don't yank anything.
- if (oap->regname != '_') {
- yankreg_T *reg = NULL;
- bool did_yank = false;
- if (oap->regname != 0) {
- // check for read-only register
- if (!valid_yank_reg(oap->regname, true)) {
- beep_flush();
- return OK;
- }
- reg = get_yank_register(oap->regname, YREG_YANK); // yank into specif'd reg
- op_yank_reg(oap, false, reg, is_append_register(oap->regname)); // yank without message
- did_yank = true;
- }
-
- // Put deleted text into register 1 and shift number registers if the
- // delete contains a line break, or when using a specific operator (Vi
- // compatible)
-
- if (oap->motion_type == kMTLineWise || oap->line_count > 1 || oap->use_reg_one) {
- shift_delete_registers(is_append_register(oap->regname));
- reg = &y_regs[1];
- op_yank_reg(oap, false, reg, false);
- did_yank = true;
- }
-
- // Yank into small delete register when no named register specified
- // and the delete is within one line.
- if (oap->regname == 0 && oap->motion_type != kMTLineWise
- && oap->line_count == 1) {
- reg = get_yank_register('-', YREG_YANK);
- op_yank_reg(oap, false, reg, false);
- did_yank = true;
- }
-
- if (did_yank || oap->regname == 0) {
- if (reg == NULL) {
- abort();
- }
- set_clipboard(oap->regname, reg);
- do_autocmd_textyankpost(oap, reg);
- }
- }
-
- // block mode delete
- if (oap->motion_type == kMTBlockWise) {
- if (u_save((linenr_T)(oap->start.lnum - 1),
- (linenr_T)(oap->end.lnum + 1)) == FAIL) {
- return FAIL;
- }
-
- for (lnum = curwin->w_cursor.lnum; lnum <= oap->end.lnum; lnum++) {
- block_prep(oap, &bd, lnum, true);
- if (bd.textlen == 0) { // nothing to delete
- continue;
- }
-
- // Adjust cursor position for tab replaced by spaces and 'lbr'.
- if (lnum == curwin->w_cursor.lnum) {
- curwin->w_cursor.col = bd.textcol + bd.startspaces;
- curwin->w_cursor.coladd = 0;
- }
-
- // "n" == number of chars deleted
- // If we delete a TAB, it may be replaced by several characters.
- // Thus the number of characters may increase!
- int n = bd.textlen - bd.startspaces - bd.endspaces;
- char *oldp = ml_get(lnum);
- char *newp = xmalloc((size_t)ml_get_len(lnum) - (size_t)n + 1);
- // copy up to deleted part
- memmove(newp, oldp, (size_t)bd.textcol);
- // insert spaces
- memset(newp + bd.textcol, ' ', (size_t)bd.startspaces +
- (size_t)bd.endspaces);
- // copy the part after the deleted part
- STRCPY(newp + bd.textcol + bd.startspaces + bd.endspaces,
- oldp + bd.textcol + bd.textlen);
- // replace the line
- ml_replace(lnum, newp, false);
-
- extmark_splice_cols(curbuf, (int)lnum - 1, bd.textcol,
- bd.textlen, bd.startspaces + bd.endspaces,
- kExtmarkUndo);
- }
-
- check_cursor_col(curwin);
- changed_lines(curbuf, curwin->w_cursor.lnum, curwin->w_cursor.col,
- oap->end.lnum + 1, 0, true);
- oap->line_count = 0; // no lines deleted
- } else if (oap->motion_type == kMTLineWise) {
- if (oap->op_type == OP_CHANGE) {
- // Delete the lines except the first one. Temporarily move the
- // cursor to the next line. Save the current line number, if the
- // last line is deleted it may be changed.
-
- if (oap->line_count > 1) {
- lnum = curwin->w_cursor.lnum;
- curwin->w_cursor.lnum++;
- del_lines(oap->line_count - 1, true);
- curwin->w_cursor.lnum = lnum;
- }
- if (u_save_cursor() == FAIL) {
- return FAIL;
- }
- if (curbuf->b_p_ai) { // don't delete indent
- beginline(BL_WHITE); // cursor on first non-white
- did_ai = true; // delete the indent when ESC hit
- ai_col = curwin->w_cursor.col;
- } else {
- beginline(0); // cursor in column 0
- }
- truncate_line(false); // delete the rest of the line,
- // leaving cursor past last char in line
- if (oap->line_count > 1) {
- u_clearline(curbuf); // "U" command not possible after "2cc"
- }
- } else {
- del_lines(oap->line_count, true);
- beginline(BL_WHITE | BL_FIX);
- u_clearline(curbuf); // "U" command not possible after "dd"
- }
- } else {
- if (virtual_op) {
- // For virtualedit: break the tabs that are partly included.
- if (gchar_pos(&oap->start) == '\t') {
- int endcol = 0;
- if (u_save_cursor() == FAIL) { // save first line for undo
- return FAIL;
- }
- if (oap->line_count == 1) {
- endcol = getviscol2(oap->end.col, oap->end.coladd);
- }
- coladvance_force(getviscol2(oap->start.col, oap->start.coladd));
- oap->start = curwin->w_cursor;
- if (oap->line_count == 1) {
- coladvance(curwin, endcol);
- oap->end.col = curwin->w_cursor.col;
- oap->end.coladd = curwin->w_cursor.coladd;
- curwin->w_cursor = oap->start;
- }
- }
-
- // Break a tab only when it's included in the area.
- if (gchar_pos(&oap->end) == '\t'
- && oap->end.coladd == 0
- && oap->inclusive) {
- // save last line for undo
- if (u_save((linenr_T)(oap->end.lnum - 1),
- (linenr_T)(oap->end.lnum + 1)) == FAIL) {
- return FAIL;
- }
- curwin->w_cursor = oap->end;
- coladvance_force(getviscol2(oap->end.col, oap->end.coladd));
- oap->end = curwin->w_cursor;
- curwin->w_cursor = oap->start;
- }
- mb_adjust_opend(oap);
- }
-
- if (oap->line_count == 1) { // delete characters within one line
- if (u_save_cursor() == FAIL) { // save line for undo
- return FAIL;
- }
-
- // if 'cpoptions' contains '$', display '$' at end of change
- if (vim_strchr(p_cpo, CPO_DOLLAR) != NULL
- && oap->op_type == OP_CHANGE
- && oap->end.lnum == curwin->w_cursor.lnum
- && !oap->is_VIsual) {
- display_dollar(oap->end.col - !oap->inclusive);
- }
-
- int n = oap->end.col - oap->start.col + 1 - !oap->inclusive;
-
- if (virtual_op) {
- // fix up things for virtualedit-delete:
- // break the tabs which are going to get in our way
- int len = get_cursor_line_len();
-
- if (oap->end.coladd != 0
- && (int)oap->end.col >= len - 1
- && !(oap->start.coladd && (int)oap->end.col >= len - 1)) {
- n++;
- }
- // Delete at least one char (e.g, when on a control char).
- if (n == 0 && oap->start.coladd != oap->end.coladd) {
- n = 1;
- }
-
- // When deleted a char in the line, reset coladd.
- if (gchar_cursor() != NUL) {
- curwin->w_cursor.coladd = 0;
- }
- }
-
- del_bytes((colnr_T)n, !virtual_op,
- oap->op_type == OP_DELETE && !oap->is_VIsual);
- } else {
- // delete characters between lines
- pos_T curpos;
-
- // save deleted and changed lines for undo
- if (u_save(curwin->w_cursor.lnum - 1,
- curwin->w_cursor.lnum + oap->line_count) == FAIL) {
- return FAIL;
- }
-
- curbuf_splice_pending++;
- pos_T startpos = curwin->w_cursor; // start position for delete
- bcount_t deleted_bytes = get_region_bytecount(curbuf, startpos.lnum, oap->end.lnum,
- startpos.col,
- oap->end.col) + oap->inclusive;
- truncate_line(true); // delete from cursor to end of line
-
- curpos = curwin->w_cursor; // remember curwin->w_cursor
- curwin->w_cursor.lnum++;
-
- del_lines(oap->line_count - 2, false);
-
- // delete from start of line until op_end
- int n = (oap->end.col + 1 - !oap->inclusive);
- curwin->w_cursor.col = 0;
- del_bytes((colnr_T)n, !virtual_op,
- oap->op_type == OP_DELETE && !oap->is_VIsual);
- curwin->w_cursor = curpos; // restore curwin->w_cursor
- do_join(2, false, false, false, false);
- curbuf_splice_pending--;
- extmark_splice(curbuf, (int)startpos.lnum - 1, startpos.col,
- (int)oap->line_count - 1, n, deleted_bytes,
- 0, 0, 0, kExtmarkUndo);
- }
- if (oap->op_type == OP_DELETE) {
- auto_format(false, true);
- }
- }
-
- msgmore(curbuf->b_ml.ml_line_count - old_lcount);
-
-setmarks:
- if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
- if (oap->motion_type == kMTBlockWise) {
- curbuf->b_op_end.lnum = oap->end.lnum;
- curbuf->b_op_end.col = oap->start.col;
- } else {
- curbuf->b_op_end = oap->start;
- }
- curbuf->b_op_start = oap->start;
- }
-
- return OK;
-}
-
-/// Adjust end of operating area for ending on a multi-byte character.
-/// Used for deletion.
-static void mb_adjust_opend(oparg_T *oap)
-{
- if (!oap->inclusive) {
- return;
- }
-
- const char *line = ml_get(oap->end.lnum);
- const char *ptr = line + oap->end.col;
- if (*ptr != NUL) {
- ptr -= utf_head_off(line, ptr);
- ptr += utfc_ptr2len(ptr) - 1;
- oap->end.col = (colnr_T)(ptr - line);
- }
-}
-
-/// Put character 'c' at position 'lp'
-static inline void pbyte(pos_T lp, int c)
-{
- assert(c <= UCHAR_MAX);
- *(ml_get_buf_mut(curbuf, lp.lnum) + lp.col) = (char)c;
- if (!curbuf_splice_pending) {
- extmark_splice_cols(curbuf, (int)lp.lnum - 1, lp.col, 1, 1, kExtmarkUndo);
- }
-}
-
-/// Replace the character under the cursor with "c".
-/// This takes care of multi-byte characters.
-static void replace_character(int c)
-{
- const int n = State;
-
- State = MODE_REPLACE;
- ins_char(c);
- State = n;
- // Backup to the replaced character.
- dec_cursor();
-}
-
-/// Replace a whole area with one character.
-static int op_replace(oparg_T *oap, int c)
-{
- int n;
- struct block_def bd;
- char *after_p = NULL;
- bool had_ctrl_v_cr = false;
-
- if ((curbuf->b_ml.ml_flags & ML_EMPTY) || oap->empty) {
- return OK; // nothing to do
- }
- if (c == REPLACE_CR_NCHAR) {
- had_ctrl_v_cr = true;
- c = CAR;
- } else if (c == REPLACE_NL_NCHAR) {
- had_ctrl_v_cr = true;
- c = NL;
- }
-
- mb_adjust_opend(oap);
-
- if (u_save((linenr_T)(oap->start.lnum - 1),
- (linenr_T)(oap->end.lnum + 1)) == FAIL) {
- return FAIL;
- }
-
- // block mode replace
- if (oap->motion_type == kMTBlockWise) {
- bd.is_MAX = (curwin->w_curswant == MAXCOL);
- for (; curwin->w_cursor.lnum <= oap->end.lnum; curwin->w_cursor.lnum++) {
- curwin->w_cursor.col = 0; // make sure cursor position is valid
- block_prep(oap, &bd, curwin->w_cursor.lnum, true);
- if (bd.textlen == 0 && (!virtual_op || bd.is_MAX)) {
- continue; // nothing to replace
- }
-
- // n == number of extra chars required
- // If we split a TAB, it may be replaced by several characters.
- // Thus the number of characters may increase!
- // If the range starts in virtual space, count the initial
- // coladd offset as part of "startspaces"
- if (virtual_op && bd.is_short && *bd.textstart == NUL) {
- pos_T vpos;
-
- vpos.lnum = curwin->w_cursor.lnum;
- getvpos(curwin, &vpos, oap->start_vcol);
- bd.startspaces += vpos.coladd;
- n = bd.startspaces;
- } else {
- // allow for pre spaces
- n = (bd.startspaces ? bd.start_char_vcols - 1 : 0);
- }
-
- // allow for post spp
- n += (bd.endspaces
- && !bd.is_oneChar
- && bd.end_char_vcols > 0) ? bd.end_char_vcols - 1 : 0;
- // Figure out how many characters to replace.
- int numc = oap->end_vcol - oap->start_vcol + 1;
- if (bd.is_short && (!virtual_op || bd.is_MAX)) {
- numc -= (oap->end_vcol - bd.end_vcol) + 1;
- }
-
- // A double-wide character can be replaced only up to half the
- // times.
- if (utf_char2cells(c) > 1) {
- if ((numc & 1) && !bd.is_short) {
- bd.endspaces++;
- n++;
- }
- numc = numc / 2;
- }
-
- // Compute bytes needed, move character count to num_chars.
- int num_chars = numc;
- numc *= utf_char2len(c);
-
- char *oldp = get_cursor_line_ptr();
- colnr_T oldlen = get_cursor_line_len();
-
- size_t newp_size = (size_t)bd.textcol + (size_t)bd.startspaces;
- if (had_ctrl_v_cr || (c != '\r' && c != '\n')) {
- newp_size += (size_t)numc;
- if (!bd.is_short) {
- newp_size += (size_t)(bd.endspaces + oldlen
- - bd.textcol - bd.textlen);
- }
- }
- char *newp = xmallocz(newp_size);
- // copy up to deleted part
- memmove(newp, oldp, (size_t)bd.textcol);
- oldp += bd.textcol + bd.textlen;
- // insert pre-spaces
- memset(newp + bd.textcol, ' ', (size_t)bd.startspaces);
- // insert replacement chars CHECK FOR ALLOCATED SPACE
- // REPLACE_CR_NCHAR/REPLACE_NL_NCHAR is used for entering CR literally.
- size_t after_p_len = 0;
- int col = oldlen - bd.textcol - bd.textlen + 1;
- assert(col >= 0);
- int newrows = 0;
- int newcols = 0;
- if (had_ctrl_v_cr || (c != '\r' && c != '\n')) {
- // strlen(newp) at this point
- int newp_len = bd.textcol + bd.startspaces;
- while (--num_chars >= 0) {
- newp_len += utf_char2bytes(c, newp + newp_len);
- }
- if (!bd.is_short) {
- // insert post-spaces
- memset(newp + newp_len, ' ', (size_t)bd.endspaces);
- newp_len += bd.endspaces;
- // copy the part after the changed part
- memmove(newp + newp_len, oldp, (size_t)col);
- }
- newcols = newp_len - bd.textcol;
- } else {
- // Replacing with \r or \n means splitting the line.
- after_p_len = (size_t)col;
- after_p = xmalloc(after_p_len);
- memmove(after_p, oldp, after_p_len);
- newrows = 1;
- }
- // replace the line
- ml_replace(curwin->w_cursor.lnum, newp, false);
- curbuf_splice_pending++;
- linenr_T baselnum = curwin->w_cursor.lnum;
- if (after_p != NULL) {
- ml_append(curwin->w_cursor.lnum++, after_p, (int)after_p_len, false);
- appended_lines_mark(curwin->w_cursor.lnum, 1);
- oap->end.lnum++;
- xfree(after_p);
- }
- curbuf_splice_pending--;
- extmark_splice(curbuf, (int)baselnum - 1, bd.textcol,
- 0, bd.textlen, bd.textlen,
- newrows, newcols, newrows + newcols, kExtmarkUndo);
- }
- } else {
- // Characterwise or linewise motion replace.
- if (oap->motion_type == kMTLineWise) {
- oap->start.col = 0;
- curwin->w_cursor.col = 0;
- oap->end.col = ml_get_len(oap->end.lnum);
- if (oap->end.col) {
- oap->end.col--;
- }
- } else if (!oap->inclusive) {
- dec(&(oap->end));
- }
-
- // TODO(bfredl): we could batch all the splicing
- // done on the same line, at least
- while (ltoreq(curwin->w_cursor, oap->end)) {
- bool done = false;
-
- n = gchar_cursor();
- if (n != NUL) {
- int new_byte_len = utf_char2len(c);
- int old_byte_len = utfc_ptr2len(get_cursor_pos_ptr());
-
- if (new_byte_len > 1 || old_byte_len > 1) {
- // This is slow, but it handles replacing a single-byte
- // with a multi-byte and the other way around.
- if (curwin->w_cursor.lnum == oap->end.lnum) {
- oap->end.col += new_byte_len - old_byte_len;
- }
- replace_character(c);
- done = true;
- } else {
- if (n == TAB) {
- int end_vcol = 0;
-
- if (curwin->w_cursor.lnum == oap->end.lnum) {
- // oap->end has to be recalculated when
- // the tab breaks
- end_vcol = getviscol2(oap->end.col,
- oap->end.coladd);
- }
- coladvance_force(getviscol());
- if (curwin->w_cursor.lnum == oap->end.lnum) {
- getvpos(curwin, &oap->end, end_vcol);
- }
- }
- // with "coladd" set may move to just after a TAB
- if (gchar_cursor() != NUL) {
- pbyte(curwin->w_cursor, c);
- done = true;
- }
- }
- }
- if (!done && virtual_op && curwin->w_cursor.lnum == oap->end.lnum) {
- int virtcols = oap->end.coladd;
-
- if (curwin->w_cursor.lnum == oap->start.lnum
- && oap->start.col == oap->end.col && oap->start.coladd) {
- virtcols -= oap->start.coladd;
- }
-
- // oap->end has been trimmed so it's effectively inclusive;
- // as a result an extra +1 must be counted so we don't
- // trample the NUL byte.
- coladvance_force(getviscol2(oap->end.col, oap->end.coladd) + 1);
- curwin->w_cursor.col -= (virtcols + 1);
- for (; virtcols >= 0; virtcols--) {
- if (utf_char2len(c) > 1) {
- replace_character(c);
- } else {
- pbyte(curwin->w_cursor, c);
- }
- if (inc(&curwin->w_cursor) == -1) {
- break;
- }
- }
- }
-
- // Advance to next character, stop at the end of the file.
- if (inc_cursor() == -1) {
- break;
- }
- }
- }
-
- curwin->w_cursor = oap->start;
- check_cursor(curwin);
- changed_lines(curbuf, oap->start.lnum, oap->start.col, oap->end.lnum + 1, 0, true);
-
- if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
- // Set "'[" and "']" marks.
- curbuf->b_op_start = oap->start;
- curbuf->b_op_end = oap->end;
- }
-
- return OK;
-}
-
-/// Handle the (non-standard vi) tilde operator. Also for "gu", "gU" and "g?".
-void op_tilde(oparg_T *oap)
-{
- struct block_def bd;
- bool did_change = false;
-
- if (u_save((linenr_T)(oap->start.lnum - 1),
- (linenr_T)(oap->end.lnum + 1)) == FAIL) {
- return;
- }
-
- pos_T pos = oap->start;
- if (oap->motion_type == kMTBlockWise) { // Visual block mode
- for (; pos.lnum <= oap->end.lnum; pos.lnum++) {
- block_prep(oap, &bd, pos.lnum, false);
- pos.col = bd.textcol;
- bool one_change = swapchars(oap->op_type, &pos, bd.textlen);
- did_change |= one_change;
- }
- if (did_change) {
- changed_lines(curbuf, oap->start.lnum, 0, oap->end.lnum + 1, 0, true);
- }
- } else { // not block mode
- if (oap->motion_type == kMTLineWise) {
- oap->start.col = 0;
- pos.col = 0;
- oap->end.col = ml_get_len(oap->end.lnum);
- if (oap->end.col) {
- oap->end.col--;
- }
- } else if (!oap->inclusive) {
- dec(&(oap->end));
- }
-
- if (pos.lnum == oap->end.lnum) {
- did_change = swapchars(oap->op_type, &pos,
- oap->end.col - pos.col + 1);
- } else {
- while (true) {
- did_change |= swapchars(oap->op_type, &pos,
- pos.lnum == oap->end.lnum ? oap->end.col + 1
- : ml_get_pos_len(&pos));
- if (ltoreq(oap->end, pos) || inc(&pos) == -1) {
- break;
- }
- }
- }
- if (did_change) {
- changed_lines(curbuf, oap->start.lnum, oap->start.col, oap->end.lnum + 1,
- 0, true);
- }
- }
-
- if (!did_change && oap->is_VIsual) {
- // No change: need to remove the Visual selection
- redraw_curbuf_later(UPD_INVERTED);
- }
-
- if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
- // Set '[ and '] marks.
- curbuf->b_op_start = oap->start;
- curbuf->b_op_end = oap->end;
- }
-
- if (oap->line_count > p_report) {
- smsg(0, NGETTEXT("%" PRId64 " line changed", "%" PRId64 " lines changed", oap->line_count),
- (int64_t)oap->line_count);
- }
-}
-
-/// Invoke swapchar() on "length" bytes at position "pos".
-///
-/// @param pos is advanced to just after the changed characters.
-/// @param length is rounded up to include the whole last multi-byte character.
-/// Also works correctly when the number of bytes changes.
-///
-/// @return true if some character was changed.
-static int swapchars(int op_type, pos_T *pos, int length)
- FUNC_ATTR_NONNULL_ALL
-{
- int did_change = 0;
-
- for (int todo = length; todo > 0; todo--) {
- const int len = utfc_ptr2len(ml_get_pos(pos));
-
- // we're counting bytes, not characters
- if (len > 0) {
- todo -= len - 1;
- }
- did_change |= swapchar(op_type, pos);
- if (inc(pos) == -1) { // at end of file
- break;
- }
- }
- return did_change;
-}
-
-/// @param op_type
-/// == OP_UPPER: make uppercase,
-/// == OP_LOWER: make lowercase,
-/// == OP_ROT13: do rot13 encoding,
-/// else swap case of character at 'pos'
-///
-/// @return true when something actually changed.
-bool swapchar(int op_type, pos_T *pos)
- FUNC_ATTR_NONNULL_ARG(2)
-{
- const int c = gchar_pos(pos);
-
- // Only do rot13 encoding for ASCII characters.
- if (c >= 0x80 && op_type == OP_ROT13) {
- return false;
- }
-
- int nc = c;
- if (mb_islower(c)) {
- if (op_type == OP_ROT13) {
- nc = ROT13(c, 'a');
- } else if (op_type != OP_LOWER) {
- nc = mb_toupper(c);
- }
- } else if (mb_isupper(c)) {
- if (op_type == OP_ROT13) {
- nc = ROT13(c, 'A');
- } else if (op_type != OP_UPPER) {
- nc = mb_tolower(c);
- }
- }
- if (nc != c) {
- if (c >= 0x80 || nc >= 0x80) {
- pos_T sp = curwin->w_cursor;
-
- curwin->w_cursor = *pos;
- // don't use del_char(), it also removes composing chars
- del_bytes(utf_ptr2len(get_cursor_pos_ptr()), false, false);
- ins_char(nc);
- curwin->w_cursor = sp;
- } else {
- pbyte(*pos, nc);
- }
- return true;
- }
- return false;
-}
-
-/// Insert and append operators for Visual mode.
-void op_insert(oparg_T *oap, int count1)
-{
- int pre_textlen = 0;
- colnr_T ind_pre_col = 0;
- int ind_pre_vcol = 0;
- struct block_def bd;
-
- // edit() changes this - record it for OP_APPEND
- bd.is_MAX = (curwin->w_curswant == MAXCOL);
-
- // vis block is still marked. Get rid of it now.
- curwin->w_cursor.lnum = oap->start.lnum;
- redraw_curbuf_later(UPD_INVERTED);
- update_screen();
-
- if (oap->motion_type == kMTBlockWise) {
- // When 'virtualedit' is used, need to insert the extra spaces before
- // doing block_prep(). When only "block" is used, virtual edit is
- // already disabled, but still need it when calling
- // coladvance_force().
- // coladvance_force() uses get_ve_flags() to get the 'virtualedit'
- // state for the current window. To override that state, we need to
- // set the window-local value of ve_flags rather than the global value.
- if (curwin->w_cursor.coladd > 0) {
- unsigned old_ve_flags = curwin->w_ve_flags;
-
- if (u_save_cursor() == FAIL) {
- return;
- }
- curwin->w_ve_flags = kOptVeFlagAll;
- coladvance_force(oap->op_type == OP_APPEND
- ? oap->end_vcol + 1 : getviscol());
- if (oap->op_type == OP_APPEND) {
- curwin->w_cursor.col--;
- }
- curwin->w_ve_flags = old_ve_flags;
- }
- // Get the info about the block before entering the text
- block_prep(oap, &bd, oap->start.lnum, true);
- // Get indent information
- ind_pre_col = (colnr_T)getwhitecols_curline();
- ind_pre_vcol = get_indent();
- pre_textlen = ml_get_len(oap->start.lnum) - bd.textcol;
- if (oap->op_type == OP_APPEND) {
- pre_textlen -= bd.textlen;
- }
- }
-
- if (oap->op_type == OP_APPEND) {
- if (oap->motion_type == kMTBlockWise
- && curwin->w_cursor.coladd == 0) {
- // Move the cursor to the character right of the block.
- curwin->w_set_curswant = true;
- while (*get_cursor_pos_ptr() != NUL
- && (curwin->w_cursor.col < bd.textcol + bd.textlen)) {
- curwin->w_cursor.col++;
- }
- if (bd.is_short && !bd.is_MAX) {
- // First line was too short, make it longer and adjust the
- // values in "bd".
- if (u_save_cursor() == FAIL) {
- return;
- }
- for (int i = 0; i < bd.endspaces; i++) {
- ins_char(' ');
- }
- bd.textlen += bd.endspaces;
- }
- } else {
- curwin->w_cursor = oap->end;
- check_cursor_col(curwin);
-
- // Works just like an 'i'nsert on the next character.
- if (!LINEEMPTY(curwin->w_cursor.lnum)
- && oap->start_vcol != oap->end_vcol) {
- inc_cursor();
- }
- }
- }
-
- pos_T t1 = oap->start;
- const pos_T start_insert = curwin->w_cursor;
- edit(NUL, false, (linenr_T)count1);
-
- // When a tab was inserted, and the characters in front of the tab
- // have been converted to a tab as well, the column of the cursor
- // might have actually been reduced, so need to adjust here.
- if (t1.lnum == curbuf->b_op_start_orig.lnum
- && lt(curbuf->b_op_start_orig, t1)) {
- oap->start = curbuf->b_op_start_orig;
- }
-
- // If user has moved off this line, we don't know what to do, so do
- // nothing.
- // Also don't repeat the insert when Insert mode ended with CTRL-C.
- if (curwin->w_cursor.lnum != oap->start.lnum || got_int) {
- return;
- }
-
- if (oap->motion_type == kMTBlockWise) {
- int ind_post_vcol = 0;
- struct block_def bd2;
- bool did_indent = false;
-
- // if indent kicked in, the firstline might have changed
- // but only do that, if the indent actually increased
- colnr_T ind_post_col = (colnr_T)getwhitecols_curline();
- if (curbuf->b_op_start.col > ind_pre_col && ind_post_col > ind_pre_col) {
- bd.textcol += ind_post_col - ind_pre_col;
- ind_post_vcol = get_indent();
- bd.start_vcol += ind_post_vcol - ind_pre_vcol;
- did_indent = true;
- }
-
- // The user may have moved the cursor before inserting something, try
- // to adjust the block for that. But only do it, if the difference
- // does not come from indent kicking in.
- if (oap->start.lnum == curbuf->b_op_start_orig.lnum && !bd.is_MAX && !did_indent) {
- const int t = getviscol2(curbuf->b_op_start_orig.col, curbuf->b_op_start_orig.coladd);
-
- if (oap->op_type == OP_INSERT
- && oap->start.col + oap->start.coladd
- != curbuf->b_op_start_orig.col + curbuf->b_op_start_orig.coladd) {
- oap->start.col = curbuf->b_op_start_orig.col;
- pre_textlen -= t - oap->start_vcol;
- oap->start_vcol = t;
- } else if (oap->op_type == OP_APPEND
- && oap->start.col + oap->start.coladd
- >= curbuf->b_op_start_orig.col + curbuf->b_op_start_orig.coladd) {
- oap->start.col = curbuf->b_op_start_orig.col;
- // reset pre_textlen to the value of OP_INSERT
- pre_textlen += bd.textlen;
- pre_textlen -= t - oap->start_vcol;
- oap->start_vcol = t;
- oap->op_type = OP_INSERT;
- }
- }
-
- // Spaces and tabs in the indent may have changed to other spaces and
- // tabs. Get the starting column again and correct the length.
- // Don't do this when "$" used, end-of-line will have changed.
- //
- // if indent was added and the inserted text was after the indent,
- // correct the selection for the new indent.
- if (did_indent && bd.textcol - ind_post_col > 0) {
- oap->start.col += ind_post_col - ind_pre_col;
- oap->start_vcol += ind_post_vcol - ind_pre_vcol;
- oap->end.col += ind_post_col - ind_pre_col;
- oap->end_vcol += ind_post_vcol - ind_pre_vcol;
- }
- block_prep(oap, &bd2, oap->start.lnum, true);
- if (did_indent && bd.textcol - ind_post_col > 0) {
- // undo for where "oap" is used below
- oap->start.col -= ind_post_col - ind_pre_col;
- oap->start_vcol -= ind_post_vcol - ind_pre_vcol;
- oap->end.col -= ind_post_col - ind_pre_col;
- oap->end_vcol -= ind_post_vcol - ind_pre_vcol;
- }
- if (!bd.is_MAX || bd2.textlen < bd.textlen) {
- if (oap->op_type == OP_APPEND) {
- pre_textlen += bd2.textlen - bd.textlen;
- if (bd2.endspaces) {
- bd2.textlen--;
- }
- }
- bd.textcol = bd2.textcol;
- bd.textlen = bd2.textlen;
- }
-
- // Subsequent calls to ml_get() flush the firstline data - take a
- // copy of the required string.
- char *firstline = ml_get(oap->start.lnum);
- colnr_T len = ml_get_len(oap->start.lnum);
- colnr_T add = bd.textcol;
- colnr_T offset = 0; // offset when cursor was moved in insert mode
- if (oap->op_type == OP_APPEND) {
- add += bd.textlen;
- // account for pressing cursor in insert mode when '$' was used
- if (bd.is_MAX && start_insert.lnum == Insstart.lnum && start_insert.col > Insstart.col) {
- offset = start_insert.col - Insstart.col;
- add -= offset;
- if (oap->end_vcol > offset) {
- oap->end_vcol -= offset + 1;
- } else {
- // moved outside of the visual block, what to do?
- return;
- }
- }
- }
- add = MIN(add, len); // short line, point to the NUL
- firstline += add;
- len -= add;
- int ins_len = len - pre_textlen - offset;
- if (pre_textlen >= 0 && ins_len > 0) {
- char *ins_text = xmemdupz(firstline, (size_t)ins_len);
- // block handled here
- if (u_save(oap->start.lnum, (linenr_T)(oap->end.lnum + 1)) == OK) {
- block_insert(oap, ins_text, (size_t)ins_len, (oap->op_type == OP_INSERT), &bd);
- }
-
- curwin->w_cursor.col = oap->start.col;
- check_cursor(curwin);
- xfree(ins_text);
- }
- }
-}
-
-/// handle a change operation
-///
-/// @return true if edit() returns because of a CTRL-O command
-int op_change(oparg_T *oap)
-{
- int pre_textlen = 0;
- int pre_indent = 0;
- char *firstline;
- struct block_def bd;
-
- colnr_T l = oap->start.col;
- if (oap->motion_type == kMTLineWise) {
- l = 0;
- can_si = may_do_si(); // Like opening a new line, do smart indent
- }
-
- // First delete the text in the region. In an empty buffer only need to
- // save for undo
- if (curbuf->b_ml.ml_flags & ML_EMPTY) {
- if (u_save_cursor() == FAIL) {
- return false;
- }
- } else if (op_delete(oap) == FAIL) {
- return false;
- }
-
- if ((l > curwin->w_cursor.col) && !LINEEMPTY(curwin->w_cursor.lnum)
- && !virtual_op) {
- inc_cursor();
+ if (u_save((linenr_T)(oap->start.lnum - 1),
+ (linenr_T)(oap->end.lnum + 1)) == FAIL) {
+ return FAIL;
}
- // check for still on same line (<CR> in inserted text meaningless)
- // skip blank lines too
+ // block mode replace
if (oap->motion_type == kMTBlockWise) {
- // Add spaces before getting the current line length.
- if (virtual_op && (curwin->w_cursor.coladd > 0
- || gchar_cursor() == NUL)) {
- coladvance_force(getviscol());
- }
- firstline = ml_get(oap->start.lnum);
- pre_textlen = ml_get_len(oap->start.lnum);
- pre_indent = (int)getwhitecols(firstline);
- bd.textcol = curwin->w_cursor.col;
- }
-
- if (oap->motion_type == kMTLineWise) {
- fix_indent();
- }
-
- // Reset finish_op now, don't want it set inside edit().
- const bool save_finish_op = finish_op;
- finish_op = false;
-
- int retval = edit(NUL, false, 1);
-
- finish_op = save_finish_op;
-
- // In Visual block mode, handle copying the new text to all lines of the
- // block.
- // Don't repeat the insert when Insert mode ended with CTRL-C.
- if (oap->motion_type == kMTBlockWise
- && oap->start.lnum != oap->end.lnum && !got_int) {
- // Auto-indenting may have changed the indent. If the cursor was past
- // the indent, exclude that indent change from the inserted text.
- firstline = ml_get(oap->start.lnum);
- if (bd.textcol > (colnr_T)pre_indent) {
- int new_indent = (int)getwhitecols(firstline);
-
- pre_textlen += new_indent - pre_indent;
- bd.textcol += (colnr_T)(new_indent - pre_indent);
- }
-
- int ins_len = ml_get_len(oap->start.lnum) - pre_textlen;
- if (ins_len > 0) {
- // Subsequent calls to ml_get() flush the firstline data - take a
- // copy of the inserted text.
- char *ins_text = xmalloc((size_t)ins_len + 1);
- xmemcpyz(ins_text, firstline + bd.textcol, (size_t)ins_len);
- for (linenr_T linenr = oap->start.lnum + 1; linenr <= oap->end.lnum;
- linenr++) {
- block_prep(oap, &bd, linenr, true);
- if (!bd.is_short || virtual_op) {
- pos_T vpos;
-
- // If the block starts in virtual space, count the
- // initial coladd offset as part of "startspaces"
- if (bd.is_short) {
- vpos.lnum = linenr;
- getvpos(curwin, &vpos, oap->start_vcol);
- } else {
- vpos.coladd = 0;
- }
- char *oldp = ml_get(linenr);
- char *newp = xmalloc((size_t)ml_get_len(linenr)
- + (size_t)vpos.coladd + (size_t)ins_len + 1);
- // copy up to block start
- memmove(newp, oldp, (size_t)bd.textcol);
- int newlen = bd.textcol;
- memset(newp + newlen, ' ', (size_t)vpos.coladd);
- newlen += vpos.coladd;
- memmove(newp + newlen, ins_text, (size_t)ins_len);
- newlen += ins_len;
- STRCPY(newp + newlen, oldp + bd.textcol);
- ml_replace(linenr, newp, false);
- extmark_splice_cols(curbuf, (int)linenr - 1, bd.textcol,
- 0, vpos.coladd + ins_len, kExtmarkUndo);
- }
- }
- check_cursor(curwin);
- changed_lines(curbuf, oap->start.lnum + 1, 0, oap->end.lnum + 1, 0, true);
- xfree(ins_text);
- }
- }
- auto_format(false, true);
-
- return retval;
-}
-
-#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);
-}
-
-/// 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;
-}
-
-static 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--;
- }
+ bd.is_MAX = (curwin->w_curswant == MAXCOL);
+ for (; curwin->w_cursor.lnum <= oap->end.lnum; curwin->w_cursor.lnum++) {
+ curwin->w_cursor.col = 0; // make sure cursor position is valid
+ block_prep(oap, &bd, curwin->w_cursor.lnum, true);
+ if (bd.textlen == 0 && (!virtual_op || bd.is_MAX)) {
+ continue; // nothing to replace
+ }
- 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();
+ // n == number of extra chars required
+ // If we split a TAB, it may be replaced by several characters.
+ // Thus the number of characters may increase!
+ // If the range starts in virtual space, count the initial
+ // coladd offset as part of "startspaces"
+ if (virtual_op && bd.is_short && *bd.textstart == NUL) {
+ pos_T vpos;
- size_t y_idx = 0; // index in y_array[]
- linenr_T lnum = oap->start.lnum; // current line number
+ vpos.lnum = curwin->w_cursor.lnum;
+ getvpos(curwin, &vpos, oap->start_vcol);
+ bd.startspaces += vpos.coladd;
+ n = bd.startspaces;
+ } else {
+ // allow for pre spaces
+ n = (bd.startspaces ? bd.start_char_vcols - 1 : 0);
+ }
- if (yank_type == kMTBlockWise) {
- // Visual block mode
- reg->y_width = oap->end_vcol - oap->start_vcol;
+ // allow for post spp
+ n += (bd.endspaces
+ && !bd.is_oneChar
+ && bd.end_char_vcols > 0) ? bd.end_char_vcols - 1 : 0;
+ // Figure out how many characters to replace.
+ int numc = oap->end_vcol - oap->start_vcol + 1;
+ if (bd.is_short && (!virtual_op || bd.is_MAX)) {
+ numc -= (oap->end_vcol - bd.end_vcol) + 1;
+ }
- if (curwin->w_curswant == MAXCOL && reg->y_width > 0) {
- reg->y_width--;
- }
- }
+ // A double-wide character can be replaced only up to half the
+ // times.
+ if (utf_char2cells(c) > 1) {
+ if ((numc & 1) && !bd.is_short) {
+ bd.endspaces++;
+ n++;
+ }
+ numc = numc / 2;
+ }
- 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;
+ // Compute bytes needed, move character count to num_chars.
+ int num_chars = numc;
+ numc *= utf_char2len(c);
- case kMTLineWise:
- reg->y_array[y_idx] = cbuf_to_string(ml_get(lnum), (size_t)ml_get_len(lnum));
- break;
+ char *oldp = get_cursor_line_ptr();
+ colnr_T oldlen = get_cursor_line_len();
- 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;
+ size_t newp_size = (size_t)bd.textcol + (size_t)bd.startspaces;
+ if (had_ctrl_v_cr || (c != '\r' && c != '\n')) {
+ newp_size += (size_t)numc;
+ if (!bd.is_short) {
+ newp_size += (size_t)(bd.endspaces + oldlen
+ - bd.textcol - bd.textlen);
+ }
}
- 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;
+ char *newp = xmallocz(newp_size);
+ // copy up to deleted part
+ memmove(newp, oldp, (size_t)bd.textcol);
+ oldp += bd.textcol + bd.textlen;
+ // insert pre-spaces
+ memset(newp + bd.textcol, ' ', (size_t)bd.startspaces);
+ // insert replacement chars CHECK FOR ALLOCATED SPACE
+ // REPLACE_CR_NCHAR/REPLACE_NL_NCHAR is used for entering CR literally.
+ size_t after_p_len = 0;
+ int col = oldlen - bd.textcol - bd.textlen + 1;
+ assert(col >= 0);
+ int newrows = 0;
+ int newcols = 0;
+ if (had_ctrl_v_cr || (c != '\r' && c != '\n')) {
+ // strlen(newp) at this point
+ int newp_len = bd.textcol + bd.startspaces;
+ while (--num_chars >= 0) {
+ newp_len += utf_char2bytes(c, newp + newp_len);
+ }
+ if (!bd.is_short) {
+ // insert post-spaces
+ memset(newp + newp_len, ' ', (size_t)bd.endspaces);
+ newp_len += bd.endspaces;
+ // copy the part after the changed part
+ memmove(newp + newp_len, oldp, (size_t)col);
+ }
+ newcols = newp_len - bd.textcol;
+ } else {
+ // Replacing with \r or \n means splitting the line.
+ after_p_len = (size_t)col;
+ after_p = xmalloc(after_p_len);
+ memmove(after_p, oldp, after_p_len);
+ newrows = 1;
+ }
+ // replace the line
+ ml_replace(curwin->w_cursor.lnum, newp, false);
+ curbuf_splice_pending++;
+ linenr_T baselnum = curwin->w_cursor.lnum;
+ if (after_p != NULL) {
+ ml_append(curwin->w_cursor.lnum++, after_p, (int)after_p_len, false);
+ appended_lines_mark(curwin->w_cursor.lnum, 1);
+ oap->end.lnum++;
+ xfree(after_p);
+ }
+ curbuf_splice_pending--;
+ extmark_splice(curbuf, (int)baselnum - 1, bd.textcol,
+ 0, bd.textlen, bd.textlen,
+ newrows, newcols, newrows + newcols, kExtmarkUndo);
}
- while (y_idx < reg->y_size) {
- curr->y_array[j++] = reg->y_array[y_idx++];
+ } else {
+ // Characterwise or linewise motion replace.
+ if (oap->motion_type == kMTLineWise) {
+ oap->start.col = 0;
+ curwin->w_cursor.col = 0;
+ oap->end.col = ml_get_len(oap->end.lnum);
+ if (oap->end.col) {
+ oap->end.col--;
+ }
+ } else if (!oap->inclusive) {
+ dec(&(oap->end));
}
- 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];
+ // TODO(bfredl): we could batch all the splicing
+ // done on the same line, at least
+ while (ltoreq(curwin->w_cursor, oap->end)) {
+ bool done = false;
- if (oap->regname == NUL) {
- *namebuf = NUL;
- } else {
- vim_snprintf(namebuf, sizeof(namebuf), _(" into \"%c"), oap->regname);
+ n = gchar_cursor();
+ if (n != NUL) {
+ int new_byte_len = utf_char2len(c);
+ int old_byte_len = utfc_ptr2len(get_cursor_pos_ptr());
+
+ if (new_byte_len > 1 || old_byte_len > 1) {
+ // This is slow, but it handles replacing a single-byte
+ // with a multi-byte and the other way around.
+ if (curwin->w_cursor.lnum == oap->end.lnum) {
+ oap->end.col += new_byte_len - old_byte_len;
+ }
+ replace_character(c);
+ done = true;
+ } else {
+ if (n == TAB) {
+ int end_vcol = 0;
+
+ if (curwin->w_cursor.lnum == oap->end.lnum) {
+ // oap->end has to be recalculated when
+ // the tab breaks
+ end_vcol = getviscol2(oap->end.col,
+ oap->end.coladd);
+ }
+ coladvance_force(getviscol());
+ if (curwin->w_cursor.lnum == oap->end.lnum) {
+ getvpos(curwin, &oap->end, end_vcol);
+ }
+ }
+ // with "coladd" set may move to just after a TAB
+ if (gchar_cursor() != NUL) {
+ pbyte(curwin->w_cursor, c);
+ done = true;
+ }
+ }
}
+ if (!done && virtual_op && curwin->w_cursor.lnum == oap->end.lnum) {
+ int virtcols = oap->end.coladd;
+
+ if (curwin->w_cursor.lnum == oap->start.lnum
+ && oap->start.col == oap->end.col && oap->start.coladd) {
+ virtcols -= oap->start.coladd;
+ }
- // redisplay now, so message is not deleted
- update_topline(curwin);
- if (must_redraw) {
- update_screen();
+ // oap->end has been trimmed so it's effectively inclusive;
+ // as a result an extra +1 must be counted so we don't
+ // trample the NUL byte.
+ coladvance_force(getviscol2(oap->end.col, oap->end.coladd) + 1);
+ curwin->w_cursor.col -= (virtcols + 1);
+ for (; virtcols >= 0; virtcols--) {
+ if (utf_char2len(c) > 1) {
+ replace_character(c);
+ } else {
+ pbyte(curwin->w_cursor, c);
+ }
+ if (inc(&curwin->w_cursor) == -1) {
+ break;
+ }
+ }
}
- 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);
+
+ // Advance to next character, stop at the end of the file.
+ if (inc_cursor() == -1) {
+ break;
}
}
}
+ curwin->w_cursor = oap->start;
+ check_cursor(curwin);
+ changed_lines(curbuf, oap->start.lnum, oap->start.col, oap->end.lnum + 1, 0, true);
+
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);
- }
}
-}
-/// 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);
+ return OK;
}
-/// Execute autocommands for TextYankPost.
-///
-/// @param oap Operator arguments.
-/// @param reg The yank register used.
-static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg)
- FUNC_ATTR_NONNULL_ALL
+/// Handle the (non-standard vi) tilde operator. Also for "gu", "gU" and "g?".
+void op_tilde(oparg_T *oap)
{
- static bool recursive = false;
+ struct block_def bd;
+ bool did_change = false;
- if (recursive || !has_event(EVENT_TEXTYANKPOST)) {
- // No autocommand was defined, or we yanked from this autocommand.
+ if (u_save((linenr_T)(oap->start.lnum - 1),
+ (linenr_T)(oap->end.lnum + 1)) == FAIL) {
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;
-}
-
-/// 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);
-
- 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;
- }
+ pos_T pos = oap->start;
+ if (oap->motion_type == kMTBlockWise) { // Visual block mode
+ for (; pos.lnum <= oap->end.lnum; pos.lnum++) {
+ block_prep(oap, &bd, pos.lnum, false);
+ pos.col = bd.textcol;
+ bool one_change = swapchars(oap->op_type, &pos, bd.textlen);
+ did_change |= one_change;
}
- 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 (did_change) {
+ changed_lines(curbuf, oap->start.lnum, 0, oap->end.lnum + 1, 0, true);
}
- }
-
- 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;
+ } else { // not block mode
+ if (oap->motion_type == kMTLineWise) {
+ oap->start.col = 0;
+ pos.col = 0;
+ oap->end.col = ml_get_len(oap->end.lnum);
+ if (oap->end.col) {
+ oap->end.col--;
+ }
+ } else if (!oap->inclusive) {
+ dec(&(oap->end));
}
- }
- 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.
+ if (pos.lnum == oap->end.lnum) {
+ did_change = swapchars(oap->op_type, &pos,
+ oap->end.col - pos.col + 1);
+ } else {
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) {
+ did_change |= swapchars(oap->op_type, &pos,
+ pos.lnum == oap->end.lnum ? oap->end.col + 1
+ : ml_get_pos_len(&pos));
+ if (ltoreq(oap->end, pos) || inc(&pos) == -1) {
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);
+ if (did_change) {
+ changed_lines(curbuf, oap->start.lnum, oap->start.col, oap->end.lnum + 1,
+ 0, true);
}
+ }
- y_type = reg->y_type;
- y_width = reg->y_width;
- y_size = reg->y_size;
- y_array = reg->y_array;
+ if (!did_change && oap->is_VIsual) {
+ // No change: need to remove the Visual selection
+ redraw_curbuf_later(UPD_INVERTED);
}
- if (curbuf->terminal) {
- terminal_paste(count, y_array, y_size);
- return;
+ if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
+ // Set '[ and '] marks.
+ curbuf->b_op_start = oap->start;
+ curbuf->b_op_end = oap->end;
}
- 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);
+ if (oap->line_count > p_report) {
+ smsg(0, NGETTEXT("%" PRId64 " line changed", "%" PRId64 " lines changed", oap->line_count),
+ (int64_t)oap->line_count);
+ }
+}
- char *ptr = xmemdupz(p, plen - (size_t)(p - p_orig));
- ml_append(curwin->w_cursor.lnum, ptr, 0, false);
- xfree(ptr);
+/// Invoke swapchar() on "length" bytes at position "pos".
+///
+/// @param pos is advanced to just after the changed characters.
+/// @param length is rounded up to include the whole last multi-byte character.
+/// Also works correctly when the number of bytes changes.
+///
+/// @return true if some character was changed.
+static int swapchars(int op_type, pos_T *pos, int length)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int did_change = 0;
- ptr = xmemdupz(get_cursor_line_ptr(), (size_t)split_pos);
- ml_replace(curwin->w_cursor.lnum, ptr, false);
- nr_lines++;
- dir = FORWARD;
+ for (int todo = length; todo > 0; todo--) {
+ const int len = utfc_ptr2len(ml_get_pos(pos));
- buf_updates_send_changes(curbuf, curwin->w_cursor.lnum, 1, 1);
+ // we're counting bytes, not characters
+ if (len > 0) {
+ todo -= len - 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;
+ did_change |= swapchar(op_type, pos);
+ if (inc(pos) == -1) { // at end of file
+ break;
}
- curbuf->b_op_start = curwin->w_cursor; // default for '[ mark
- curbuf->b_op_end = curwin->w_cursor; // default for '] mark
}
+ return did_change;
+}
- if (flags & PUT_LINE) { // :put command or "p" in Visual line mode.
- y_type = kMTLineWise;
- }
+/// @param op_type
+/// == OP_UPPER: make uppercase,
+/// == OP_LOWER: make lowercase,
+/// == OP_ROT13: do rot13 encoding,
+/// else swap case of character at 'pos'
+///
+/// @return true when something actually changed.
+bool swapchar(int op_type, pos_T *pos)
+ FUNC_ATTR_NONNULL_ARG(2)
+{
+ const int c = gchar_pos(pos);
- if (y_size == 0 || y_array == NULL) {
- semsg(_("E353: Nothing in register %s"),
- regname == 0 ? "\"" : transchar(regname));
- goto end;
+ // Only do rot13 encoding for ASCII characters.
+ if (c >= 0x80 && op_type == OP_ROT13) {
+ return false;
}
- 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;
+ int nc = c;
+ if (mb_islower(c)) {
+ if (op_type == OP_ROT13) {
+ nc = ROT13(c, 'a');
+ } else if (op_type != OP_LOWER) {
+ nc = mb_toupper(c);
}
- if (dir == FORWARD) {
- curwin->w_cursor.lnum = lnum - 1;
- } else {
- curwin->w_cursor.lnum = lnum;
+ } else if (mb_isupper(c)) {
+ if (op_type == OP_ROT13) {
+ nc = ROT13(c, 'A');
+ } else if (op_type != OP_UPPER) {
+ nc = mb_tolower(c);
}
- curbuf->b_op_start = curwin->w_cursor; // for mark_adjust()
- } else if (u_save_cursor() == FAIL) {
- goto end;
}
+ if (nc != c) {
+ if (c >= 0x80 || nc >= 0x80) {
+ pos_T sp = curwin->w_cursor;
- 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));
+ curwin->w_cursor = *pos;
+ // don't use del_char(), it also removes composing chars
+ del_bytes(utf_ptr2len(get_cursor_pos_ptr()), false, false);
+ ins_char(nc);
+ curwin->w_cursor = sp;
+ } else {
+ pbyte(*pos, nc);
}
+ return true;
}
+ return false;
+}
- lnum = curwin->w_cursor.lnum;
- colnr_T col = curwin->w_cursor.col;
+/// Insert and append operators for Visual mode.
+void op_insert(oparg_T *oap, int count1)
+{
+ int pre_textlen = 0;
+ colnr_T ind_pre_col = 0;
+ int ind_pre_vcol = 0;
+ struct block_def bd;
- // Block mode
- if (y_type == kMTBlockWise) {
- int incr = 0;
- struct block_def bd;
- int c = gchar_cursor();
- colnr_T endcol2 = 0;
+ // edit() changes this - record it for OP_APPEND
+ bd.is_MAX = (curwin->w_curswant == MAXCOL);
- 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);
- }
+ // vis block is still marked. Get rid of it now.
+ curwin->w_cursor.lnum = oap->start.lnum;
+ redraw_curbuf_later(UPD_INVERTED);
+ update_screen();
- // 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);
- }
+ if (oap->motion_type == kMTBlockWise) {
+ // When 'virtualedit' is used, need to insert the extra spaces before
+ // doing block_prep(). When only "block" is used, virtual edit is
+ // already disabled, but still need it when calling
+ // coladvance_force().
+ // coladvance_force() uses get_ve_flags() to get the 'virtualedit'
+ // state for the current window. To override that state, we need to
+ // set the window-local value of ve_flags rather than the global value.
+ if (curwin->w_cursor.coladd > 0) {
+ unsigned old_ve_flags = curwin->w_ve_flags;
- 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 (u_save_cursor() == FAIL) {
+ return;
}
- 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_ve_flags = kOptVeFlagAll;
+ coladvance_force(oap->op_type == OP_APPEND
+ ? oap->end_vcol + 1 : getviscol());
+ if (oap->op_type == OP_APPEND) {
+ curwin->w_cursor.col--;
}
+ curwin->w_ve_flags = old_ve_flags;
}
- 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;
- }
+ // Get the info about the block before entering the text
+ block_prep(oap, &bd, oap->start.lnum, true);
+ // Get indent information
+ ind_pre_col = (colnr_T)getwhitecols_curline();
+ ind_pre_vcol = get_indent();
+ pre_textlen = ml_get_len(oap->start.lnum) - bd.textcol;
+ if (oap->op_type == OP_APPEND) {
+ pre_textlen -= bd.textlen;
}
+ }
- 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 (oap->op_type == OP_APPEND) {
+ if (oap->motion_type == kMTBlockWise
+ && curwin->w_cursor.coladd == 0) {
+ // Move the cursor to the character right of the block.
+ curwin->w_set_curswant = true;
+ while (*get_cursor_pos_ptr() != NUL
+ && (curwin->w_cursor.col < bd.textcol + bd.textlen)) {
+ curwin->w_cursor.col++;
}
-
- if (count == 0 || yanklen == 0) {
- if (VIsual_active) {
- lnum = end_lnum;
+ if (bd.is_short && !bd.is_MAX) {
+ // First line was too short, make it longer and adjust the
+ // values in "bd".
+ if (u_save_cursor() == FAIL) {
+ return;
}
- } 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(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--;
+ for (int i = 0; i < bd.endspaces; i++) {
+ ins_char(' ');
}
- }
-
- // 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;
+ bd.textlen += bd.endspaces;
}
} 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();
+ curwin->w_cursor = oap->end;
+ check_cursor_col(curwin);
+
+ // Works just like an 'i'nsert on the next character.
+ if (!LINEEMPTY(curwin->w_cursor.lnum)
+ && oap->start_vcol != oap->end_vcol) {
+ inc_cursor();
}
+ }
+ }
- // 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;
- }
+ pos_T t1 = oap->start;
+ const pos_T start_insert = curwin->w_cursor;
+ edit(NUL, false, (linenr_T)count1);
- 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);
- }
- }
- }
+ // When a tab was inserted, and the characters in front of the tab
+ // have been converted to a tab as well, the column of the cursor
+ // might have actually been reduced, so need to adjust here.
+ if (t1.lnum == curbuf->b_op_start_orig.lnum
+ && lt(curbuf->b_op_start_orig, t1)) {
+ oap->start = curbuf->b_op_start_orig;
+ }
- 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 user has moved off this line, we don't know what to do, so do
+ // nothing.
+ // Also don't repeat the insert when Insert mode ended with CTRL-C.
+ if (curwin->w_cursor.lnum != oap->start.lnum || got_int) {
+ return;
+ }
- if (cnt == 1) {
- new_lnum = lnum;
- }
- }
+ if (oap->motion_type == kMTBlockWise) {
+ int ind_post_vcol = 0;
+ struct block_def bd2;
+ bool did_indent = false;
-error:
- // Adjust marks.
- if (y_type == kMTLineWise) {
- curbuf->b_op_start.col = 0;
- if (dir == FORWARD) {
- curbuf->b_op_start.lnum++;
- }
- }
+ // if indent kicked in, the firstline might have changed
+ // but only do that, if the indent actually increased
+ colnr_T ind_post_col = (colnr_T)getwhitecols_curline();
+ if (curbuf->b_op_start.col > ind_pre_col && ind_post_col > ind_pre_col) {
+ bd.textcol += ind_post_col - ind_pre_col;
+ ind_post_vcol = get_indent();
+ bd.start_vcol += ind_post_vcol - ind_pre_vcol;
+ did_indent = true;
+ }
- 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);
+ // The user may have moved the cursor before inserting something, try
+ // to adjust the block for that. But only do it, if the difference
+ // does not come from indent kicking in.
+ if (oap->start.lnum == curbuf->b_op_start_orig.lnum && !bd.is_MAX && !did_indent) {
+ const int t = getviscol2(curbuf->b_op_start_orig.col, curbuf->b_op_start_orig.coladd);
- // 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);
+ if (oap->op_type == OP_INSERT
+ && oap->start.col + oap->start.coladd
+ != curbuf->b_op_start_orig.col + curbuf->b_op_start_orig.coladd) {
+ oap->start.col = curbuf->b_op_start_orig.col;
+ pre_textlen -= t - oap->start_vcol;
+ oap->start_vcol = t;
+ } else if (oap->op_type == OP_APPEND
+ && oap->start.col + oap->start.coladd
+ >= curbuf->b_op_start_orig.col + curbuf->b_op_start_orig.coladd) {
+ oap->start.col = curbuf->b_op_start_orig.col;
+ // reset pre_textlen to the value of OP_INSERT
+ pre_textlen += bd.textlen;
+ pre_textlen -= t - oap->start_vcol;
+ oap->start_vcol = t;
+ oap->op_type = OP_INSERT;
}
+ }
- // 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);
+ // Spaces and tabs in the indent may have changed to other spaces and
+ // tabs. Get the starting column again and correct the length.
+ // Don't do this when "$" used, end-of-line will have changed.
+ //
+ // if indent was added and the inserted text was after the indent,
+ // correct the selection for the new indent.
+ if (did_indent && bd.textcol - ind_post_col > 0) {
+ oap->start.col += ind_post_col - ind_pre_col;
+ oap->start_vcol += ind_post_vcol - ind_pre_vcol;
+ oap->end.col += ind_post_col - ind_pre_col;
+ oap->end_vcol += ind_post_vcol - ind_pre_vcol;
+ }
+ block_prep(oap, &bd2, oap->start.lnum, true);
+ if (did_indent && bd.textcol - ind_post_col > 0) {
+ // undo for where "oap" is used below
+ oap->start.col -= ind_post_col - ind_pre_col;
+ oap->start_vcol -= ind_post_vcol - ind_pre_vcol;
+ oap->end.col -= ind_post_col - ind_pre_col;
+ oap->end_vcol -= ind_post_vcol - ind_pre_vcol;
+ }
+ if (!bd.is_MAX || bd2.textlen < bd.textlen) {
+ if (oap->op_type == OP_APPEND) {
+ pre_textlen += bd2.textlen - bd.textlen;
+ if (bd2.endspaces) {
+ bd2.textlen--;
}
- } else {
- curbuf->b_op_end.col = 0;
}
+ bd.textcol = bd2.textcol;
+ bd.textlen = bd2.textlen;
+ }
- 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;
+ // Subsequent calls to ml_get() flush the firstline data - take a
+ // copy of the required string.
+ char *firstline = ml_get(oap->start.lnum);
+ colnr_T len = ml_get_len(oap->start.lnum);
+ colnr_T add = bd.textcol;
+ colnr_T offset = 0; // offset when cursor was moved in insert mode
+ if (oap->op_type == OP_APPEND) {
+ add += bd.textlen;
+ // account for pressing cursor in insert mode when '$' was used
+ if (bd.is_MAX && start_insert.lnum == Insstart.lnum && start_insert.col > Insstart.col) {
+ offset = start_insert.col - Insstart.col;
+ add -= offset;
+ if (oap->end_vcol > offset) {
+ oap->end_vcol -= offset + 1;
} 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++;
+ // moved outside of the visual block, what to do?
+ return;
}
- 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;
+ add = MIN(add, len); // short line, point to the NUL
+ firstline += add;
+ len -= add;
+ int ins_len = len - pre_textlen - offset;
+ if (pre_textlen >= 0 && ins_len > 0) {
+ char *ins_text = xmemdupz(firstline, (size_t)ins_len);
+ // block handled here
+ if (u_save(oap->start.lnum, (linenr_T)(oap->end.lnum + 1)) == OK) {
+ block_insert(oap, ins_text, (size_t)ins_len, (oap->op_type == OP_INSERT), &bd);
+ }
- // 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 = oap->start.col;
+ check_cursor(curwin);
+ xfree(ins_text);
}
- 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();
}
-/// When the cursor is on the NUL past the end of the line and it should not be
-/// there move it left.
-void adjust_cursor_eol(void)
+/// handle a change operation
+///
+/// @return true if edit() returns because of a CTRL-O command
+int op_change(oparg_T *oap)
{
- unsigned cur_ve_flags = get_ve_flags(curwin);
+ int pre_textlen = 0;
+ int pre_indent = 0;
+ char *firstline;
+ struct block_def bd;
- const bool adj_cursor = (curwin->w_cursor.col > 0
- && gchar_cursor() == NUL
- && (cur_ve_flags & kOptVeFlagOnemore) == 0
- && !(restart_edit || (State & MODE_INSERT)));
- if (!adj_cursor) {
- return;
+ colnr_T l = oap->start.col;
+ if (oap->motion_type == kMTLineWise) {
+ l = 0;
+ can_si = may_do_si(); // Like opening a new line, do smart indent
}
- // Put the cursor on the last character in the line.
- dec_cursor();
-
- if (cur_ve_flags == kOptVeFlagAll) {
- colnr_T scol, ecol;
+ // First delete the text in the region. In an empty buffer only need to
+ // save for undo
+ if (curbuf->b_ml.ml_flags & ML_EMPTY) {
+ if (u_save_cursor() == FAIL) {
+ return false;
+ }
+ } else if (op_delete(oap) == FAIL) {
+ return false;
+ }
- // Coladd is set to the width of the last character.
- getvcol(curwin, &curwin->w_cursor, &scol, NULL, &ecol);
- curwin->w_cursor.coladd = ecol - scol + 1;
+ if ((l > curwin->w_cursor.col) && !LINEEMPTY(curwin->w_cursor.lnum)
+ && !virtual_op) {
+ inc_cursor();
}
-}
-/// @return the character name of the register with the given number
-int get_register_name(int num)
-{
- if (num == -1) {
- return '"';
- } else if (num < 10) {
- return num + '0';
- } else if (num == DELETION_REGISTER) {
- return '-';
- } else if (num == STAR_REGISTER) {
- return '*';
- } else if (num == PLUS_REGISTER) {
- return '+';
- } else {
- return num + 'a' - 10;
+ // check for still on same line (<CR> in inserted text meaningless)
+ // skip blank lines too
+ if (oap->motion_type == kMTBlockWise) {
+ // Add spaces before getting the current line length.
+ if (virtual_op && (curwin->w_cursor.coladd > 0
+ || gchar_cursor() == NUL)) {
+ coladvance_force(getviscol());
+ }
+ firstline = ml_get(oap->start.lnum);
+ pre_textlen = ml_get_len(oap->start.lnum);
+ pre_indent = (int)getwhitecols(firstline);
+ bd.textcol = curwin->w_cursor.col;
}
-}
-/// @return the index of the register "" points to.
-int get_unname_register(void)
-{
- return y_previous == NULL ? -1 : (int)(y_previous - &y_regs[0]);
-}
+ if (oap->motion_type == kMTLineWise) {
+ fix_indent();
+ }
-/// ":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;
+ // Reset finish_op now, don't want it set inside edit().
+ const bool save_finish_op = finish_op;
+ finish_op = false;
- 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;
- }
+ int retval = edit(NUL, false, 1);
- if (i == -1) {
- if (y_previous != NULL) {
- yb = y_previous;
- } else {
- yb = &(y_regs[0]);
- }
- } else {
- yb = &(y_regs[i]);
- }
+ finish_op = save_finish_op;
- get_clipboard(name, &yb, true);
+ // In Visual block mode, handle copying the new text to all lines of the
+ // block.
+ // Don't repeat the insert when Insert mode ended with CTRL-C.
+ if (oap->motion_type == kMTBlockWise
+ && oap->start.lnum != oap->end.lnum && !got_int) {
+ // Auto-indenting may have changed the indent. If the cursor was past
+ // the indent, exclude that indent change from the inserted text.
+ firstline = ml_get(oap->start.lnum);
+ if (bd.textcol > (colnr_T)pre_indent) {
+ int new_indent = (int)getwhitecols(firstline);
- if (name == mb_tolower(redir_reg)
- || (redir_reg == '"' && yb == y_previous)) {
- continue; // do not list register being written to, the
- // pointer can be freed
+ pre_textlen += new_indent - pre_indent;
+ bd.textcol += (colnr_T)(new_indent - pre_indent);
}
- 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);
- }
+ int ins_len = ml_get_len(oap->start.lnum) - pre_textlen;
+ if (ins_len > 0) {
+ // Subsequent calls to ml_get() flush the firstline data - take a
+ // copy of the inserted text.
+ char *ins_text = xmalloc((size_t)ins_len + 1);
+ xmemcpyz(ins_text, firstline + bd.textcol, (size_t)ins_len);
+ for (linenr_T linenr = oap->start.lnum + 1; linenr <= oap->end.lnum;
+ linenr++) {
+ block_prep(oap, &bd, linenr, true);
+ if (!bd.is_short || virtual_op) {
+ pos_T vpos;
- 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 the block starts in virtual space, count the
+ // initial coladd offset as part of "startspaces"
+ if (bd.is_short) {
+ vpos.lnum = linenr;
+ getvpos(curwin, &vpos, oap->start_vcol);
+ } else {
+ vpos.coladd = 0;
}
- }
- if (n > 1 && yb->y_type == kMTLineWise) {
- msg_puts_hl("^J", hl_id, false);
+ char *oldp = ml_get(linenr);
+ char *newp = xmalloc((size_t)ml_get_len(linenr)
+ + (size_t)vpos.coladd + (size_t)ins_len + 1);
+ // copy up to block start
+ memmove(newp, oldp, (size_t)bd.textcol);
+ int newlen = bd.textcol;
+ memset(newp + newlen, ' ', (size_t)vpos.coladd);
+ newlen += vpos.coladd;
+ memmove(newp + newlen, ins_text, (size_t)ins_len);
+ newlen += ins_len;
+ STRCPY(newp + newlen, oldp + bd.textcol);
+ ml_replace(linenr, newp, false);
+ extmark_splice_cols(curbuf, (int)linenr - 1, bd.textcol,
+ 0, vpos.coladd + ins_len, kExtmarkUndo);
}
}
- os_breakcheck();
+ check_cursor(curwin);
+ changed_lines(curbuf, oap->start.lnum + 1, 0, oap->end.lnum + 1, 0, true);
+ xfree(ins_text);
}
}
+ auto_format(false, true);
- // 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);
- }
+ return retval;
+}
- // display alternate file name
- if ((arg == NULL || vim_strchr(arg, '%') != NULL) && !got_int) {
- char *fname;
- linenr_T dummy;
+/// When the cursor is on the NUL past the end of the line and it should not be
+/// there move it left.
+void adjust_cursor_eol(void)
+{
+ unsigned cur_ve_flags = get_ve_flags(curwin);
- if (buflist_name_nr(0, &fname, &dummy) != FAIL && !message_filtered(fname)) {
- msg_puts("\n c \"# ");
- dis_msg(fname, false);
- }
+ const bool adj_cursor = (curwin->w_cursor.col > 0
+ && gchar_cursor() == NUL
+ && (cur_ve_flags & kOptVeFlagOnemore) == 0
+ && !(restart_edit || (State & MODE_INSERT)));
+ if (!adj_cursor) {
+ return;
}
- // 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);
- }
+ // Put the cursor on the last character in the line.
+ dec_cursor();
- // 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;
-}
+ if (cur_ve_flags == kOptVeFlagAll) {
+ colnr_T scol, ecol;
-/// 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);
- }
+ // Coladd is set to the width of the last character.
+ getvcol(curwin, &curwin->w_cursor, &scol, NULL, &ecol);
+ curwin->w_cursor.coladd = ecol - scol + 1;
}
- os_breakcheck();
}
/// If \p "process" is true and the line begins with a comment leader (possibly
@@ -4686,546 +2645,103 @@ bool do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
length--;
}
while (todel-- > 0) {
- if (c < 0x100 && isalpha(c)) {
- hexupper = isupper(c);
- }
- // del_char() will mark line needing displaying
- del_char(false);
- c = gchar_cursor();
- }
-
- // Prepare the leading characters in buf1[].
- // When there are many leading zeros it could be very long.
- // Allocate a bit too much.
- char *buf1 = xmalloc((size_t)length + NUMBUFLEN);
- ptr = buf1;
- if (negative && (!visual || was_positive)) {
- *ptr++ = '-';
- }
- if (pre) {
- *ptr++ = '0';
- length--;
- }
- if (pre == 'b' || pre == 'B' || pre == 'x' || pre == 'X') {
- *ptr++ = (char)pre;
- length--;
- }
-
- // Put the number characters in buf2[].
- char buf2[NUMBUFLEN];
- int buf2len = 0;
- if (pre == 'b' || pre == 'B') {
- size_t bits = 0;
-
- // leading zeros
- for (bits = 8 * sizeof(n); bits > 0; bits--) {
- if ((n >> (bits - 1)) & 0x1) {
- break;
- }
- }
-
- while (bits > 0 && buf2len < NUMBUFLEN - 1) {
- buf2[buf2len++] = ((n >> --bits) & 0x1) ? '1' : '0';
- }
-
- buf2[buf2len] = NUL;
- } else if (pre == 0) {
- buf2len = vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIu64, (uint64_t)n);
- } else if (pre == '0') {
- buf2len = vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIo64, (uint64_t)n);
- } else if (hexupper) {
- buf2len = vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIX64, (uint64_t)n);
- } else {
- buf2len = vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIx64, (uint64_t)n);
- }
- length -= buf2len;
-
- // Adjust number of zeros to the new number of digits, so the
- // total length of the number remains the same.
- // Don't do this when
- // the result may look like an octal number.
- if (firstdigit == '0' && !(do_oct && pre == 0)) {
- while (length-- > 0) {
- *ptr++ = '0';
- }
- }
- *ptr = NUL;
- int buf1len = (int)(ptr - buf1);
-
- STRCPY(buf1 + buf1len, buf2);
- buf1len += buf2len;
-
- ins_str(buf1, (size_t)buf1len); // insert the new number
- xfree(buf1);
-
- endpos = curwin->w_cursor;
- if (curwin->w_cursor.col) {
- curwin->w_cursor.col--;
- }
- }
-
- if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
- // set the '[ and '] marks
- curbuf->b_op_start = startpos;
- curbuf->b_op_end = endpos;
- if (curbuf->b_op_end.col > 0) {
- curbuf->b_op_end.col--;
- }
- }
-
-theend:
- if (visual) {
- curwin->w_cursor = save_cursor;
- } else if (did_change) {
- curwin->w_set_curswant = true;
- } else if (virtual_active(curwin)) {
- curwin->w_cursor.coladd = save_coladd;
- }
-
- return did_change;
-}
-
-/// 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;
-}
-
-/// 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;
- }
-}
-
-/// 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;
-}
-
-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);
-}
-
-/// 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
+ if (c < 0x100 && isalpha(c)) {
+ hexupper = isupper(c);
+ }
+ // del_char() will mark line needing displaying
+ del_char(false);
+ c = gchar_cursor();
+ }
- // Count the number of lines within the string
- if (str_list) {
- for (char **ss = (char **)str; *ss != NULL; ss++) {
- newlines++;
+ // Prepare the leading characters in buf1[].
+ // When there are many leading zeros it could be very long.
+ // Allocate a bit too much.
+ char *buf1 = xmalloc((size_t)length + NUMBUFLEN);
+ ptr = buf1;
+ if (negative && (!visual || was_positive)) {
+ *ptr++ = '-';
}
- } 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 (pre) {
+ *ptr++ = '0';
+ length--;
}
- if (y_ptr->y_size > 0 && y_ptr->y_type == kMTCharWise) {
- append = true;
- newlines--; // uncount newline when appending first line
+ if (pre == 'b' || pre == 'B' || pre == 'x' || pre == 'X') {
+ *ptr++ = (char)pre;
+ length--;
}
- }
- // Without any lines make the register empty.
- if (y_ptr->y_size + newlines == 0) {
- XFREE_CLEAR(y_ptr->y_array);
- return;
- }
+ // Put the number characters in buf2[].
+ char buf2[NUMBUFLEN];
+ int buf2len = 0;
+ if (pre == 'b' || pre == 'B') {
+ size_t bits = 0;
- // 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;
+ // leading zeros
+ for (bits = 8 * sizeof(n); bits > 0; bits--) {
+ if ((n >> (bits - 1)) & 0x1) {
+ break;
+ }
+ }
- size_t lnum = y_ptr->y_size; // The current line number.
+ while (bits > 0 && buf2len < NUMBUFLEN - 1) {
+ buf2[buf2len++] = ((n >> --bits) & 0x1) ? '1' : '0';
+ }
- // If called with `blocklen < 0`, we have to update the yank reg's width.
- size_t maxlen = 0;
+ buf2[buf2len] = NUL;
+ } else if (pre == 0) {
+ buf2len = vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIu64, (uint64_t)n);
+ } else if (pre == '0') {
+ buf2len = vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIo64, (uint64_t)n);
+ } else if (hexupper) {
+ buf2len = vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIX64, (uint64_t)n);
+ } else {
+ buf2len = vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIx64, (uint64_t)n);
+ }
+ length -= buf2len;
- // 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);
+ // Adjust number of zeros to the new number of digits, so the
+ // total length of the number remains the same.
+ // Don't do this when
+ // the result may look like an octal number.
+ if (firstdigit == '0' && !(do_oct && pre == 0)) {
+ while (length-- > 0) {
+ *ptr++ = '0';
}
}
- } 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));
- }
+ *ptr = NUL;
+ int buf1len = (int)(ptr - buf1);
- 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;
+ STRCPY(buf1 + buf1len, buf2);
+ buf1len += buf2len;
- if (append) {
- xfree(pp[lnum].data);
- append = false; // only first line is appended
- }
- pp[lnum] = cbuf_as_string(s, s_len);
+ ins_str(buf1, (size_t)buf1len); // insert the new number
+ xfree(buf1);
- // Convert NULs to '\n' to prevent truncation.
- memchrsub(pp[lnum].data, NUL, '\n', s_len);
+ endpos = curwin->w_cursor;
+ if (curwin->w_cursor.col) {
+ curwin->w_cursor.col--;
}
}
- 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;
+
+ if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
+ // set the '[ and '] marks
+ curbuf->b_op_start = startpos;
+ curbuf->b_op_end = endpos;
+ if (curbuf->b_op_end.col > 0) {
+ curbuf->b_op_end.col--;
+ }
+ }
+
+theend:
+ if (visual) {
+ curwin->w_cursor = save_cursor;
+ } else if (did_change) {
+ curwin->w_set_curswant = true;
+ } else if (virtual_active(curwin)) {
+ curwin->w_cursor.coladd = save_coladd;
}
+
+ return did_change;
}
void clear_oparg(oparg_T *oap)
@@ -6380,489 +3896,6 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
restore_lbr(lbr_saved);
}
-/// 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;
-}
-
-/// Determine if register `*name` should be used as a clipboard.
-/// In an unnamed operation, `*name` is `NUL` and will be adjusted to */+ if
-/// `clipboard=unnamed[plus]` is set.
-///
-/// @param name The name of register, or `NUL` if unnamed.
-/// @param quiet Suppress error messages
-/// @param writing if we're setting the contents of the clipboard
-///
-/// @returns the yankreg that should be written into, or `NULL`
-/// if the register isn't a clipboard or provider isn't available.
-static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing)
-{
-#define MSG_NO_CLIP "clipboard: No provider. " \
- "Try \":checkhealth\" or \":h clipboard\"."
-
- yankreg_T *target = NULL;
- bool explicit_cb_reg = (*name == '*' || *name == '+');
- bool implicit_cb_reg = (*name == NUL) && (cb_flags & (kOptCbFlagUnnamed | kOptCbFlagUnnamedplus));
- if (!explicit_cb_reg && !implicit_cb_reg) {
- goto end;
- }
-
- if (!eval_has_provider("clipboard", false)) {
- if (batch_change_count <= 1 && !quiet
- && (!clipboard_didwarn || (explicit_cb_reg && !redirecting()))) {
- clipboard_didwarn = true;
- // Do NOT error (emsg()) here--if it interrupts :redir we get into
- // a weird state, stuck in "redirect mode".
- msg(MSG_NO_CLIP, 0);
- }
- // ... else, be silent (don't flood during :while, :redir, etc.).
- goto end;
- }
-
- if (explicit_cb_reg) {
- target = &y_regs[*name == '*' ? STAR_REGISTER : PLUS_REGISTER];
- if (writing && (cb_flags & (*name == '*' ? kOptCbFlagUnnamed : kOptCbFlagUnnamedplus))) {
- clipboard_needs_update = false;
- }
- goto end;
- } else { // unnamed register: "implicit" clipboard
- if (writing && clipboard_delay_update) {
- // For "set" (copy), defer the clipboard call.
- clipboard_needs_update = true;
- goto end;
- } else if (!writing && clipboard_needs_update) {
- // For "get" (paste), use the internal value.
- goto end;
- }
-
- if (cb_flags & kOptCbFlagUnnamedplus) {
- *name = (cb_flags & kOptCbFlagUnnamed && writing) ? '"' : '+';
- target = &y_regs[PLUS_REGISTER];
- } else {
- *name = '*';
- target = &y_regs[STAR_REGISTER];
- }
- goto end;
- }
-
-end:
- return target;
-}
-
-/// @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);
-}
-
-/// Updates the "y_width" of a blockwise register based on its contents.
-/// Do nothing on a non-blockwise register.
-static 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);
- }
-}
-
-static bool get_clipboard(int name, yankreg_T **target, bool quiet)
-{
- // show message on error
- bool errmsg = true;
-
- yankreg_T *reg = adjust_clipboard_name(&name, quiet, false);
- if (reg == NULL) {
- return false;
- }
- free_register(reg);
-
- list_T *const args = tv_list_alloc(1);
- const char regname = (char)name;
- tv_list_append_string(args, ®name, 1);
-
- typval_T result = eval_call_provider("clipboard", "get", args, false);
-
- if (result.v_type != VAR_LIST) {
- if (result.v_type == VAR_NUMBER && result.vval.v_number == 0) {
- // failure has already been indicated by provider
- errmsg = false;
- }
- goto err;
- }
-
- list_T *res = result.vval.v_list;
- list_T *lines = NULL;
- if (tv_list_len(res) == 2
- && TV_LIST_ITEM_TV(tv_list_first(res))->v_type == VAR_LIST) {
- lines = TV_LIST_ITEM_TV(tv_list_first(res))->vval.v_list;
- if (TV_LIST_ITEM_TV(tv_list_last(res))->v_type != VAR_STRING) {
- goto err;
- }
- char *regtype = TV_LIST_ITEM_TV(tv_list_last(res))->vval.v_string;
- if (regtype == NULL || strlen(regtype) > 1) {
- goto err;
- }
- switch (regtype[0]) {
- 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:
- goto err;
- }
- } else {
- lines = res;
- // provider did not specify regtype, calculate it below
- reg->y_type = kMTUnknown;
- }
-
- reg->y_array = xcalloc((size_t)tv_list_len(lines), sizeof(String));
- reg->y_size = (size_t)tv_list_len(lines);
- reg->additional_data = NULL;
- reg->timestamp = 0;
- // Timestamp is not saved for clipboard registers because clipboard registers
- // are not saved in the ShaDa file.
-
- size_t tv_idx = 0;
- TV_LIST_ITER_CONST(lines, li, {
- if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING) {
- goto err;
- }
- const char *s = TV_LIST_ITEM_TV(li)->vval.v_string;
- reg->y_array[tv_idx++] = cstr_to_string(s != NULL ? s : "");
- });
-
- 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) {
- xfree(reg->y_array[reg->y_size - 1].data);
- 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);
-
- *target = reg;
- return true;
-
-err:
- if (reg->y_array) {
- for (size_t i = 0; i < reg->y_size; i++) {
- xfree(reg->y_array[i].data);
- }
- xfree(reg->y_array);
- }
- reg->y_array = NULL;
- reg->y_size = 0;
- reg->additional_data = NULL;
- reg->timestamp = 0;
- if (errmsg) {
- emsg("clipboard: provider returned invalid data");
- }
- *target = reg;
- return false;
-}
-
-static void set_clipboard(int name, yankreg_T *reg)
-{
- if (!adjust_clipboard_name(&name, false, true)) {
- return;
- }
-
- list_T *const lines = tv_list_alloc((ptrdiff_t)reg->y_size + (reg->y_type != kMTCharWise));
-
- for (size_t i = 0; i < reg->y_size; i++) {
- tv_list_append_string(lines, reg->y_array[i].data, -1);
- }
-
- char regtype;
- switch (reg->y_type) {
- case kMTLineWise:
- regtype = 'V';
- tv_list_append_string(lines, NULL, 0);
- break;
- case kMTCharWise:
- regtype = 'v';
- break;
- case kMTBlockWise:
- regtype = 'b';
- tv_list_append_string(lines, NULL, 0);
- break;
- case kMTUnknown:
- abort();
- }
-
- list_T *args = tv_list_alloc(3);
- tv_list_append_list(args, lines);
- tv_list_append_string(args, ®type, 1);
- tv_list_append_string(args, ((char[]) { (char)name }), 1);
-
- eval_call_provider("clipboard", "set", args, true);
-}
-
-/// Avoid slow things (clipboard) during batch operations (while/for-loops).
-void start_batch_changes(void)
-{
- if (++batch_change_count > 1) {
- return;
- }
- clipboard_delay_update = true;
-}
-
-/// Counterpart to start_batch_changes().
-void end_batch_changes(void)
-{
- if (--batch_change_count > 0) {
- // recursive
- return;
- }
- clipboard_delay_update = false;
- if (clipboard_needs_update) {
- // must be before, as set_clipboard will invoke
- // start/end_batch_changes recursively
- clipboard_needs_update = false;
- // unnamed ("implicit" clipboard)
- set_clipboard(NUL, y_previous);
- }
-}
-
-int save_batch_count(void)
-{
- int save_count = batch_change_count;
- batch_change_count = 0;
- clipboard_delay_update = false;
- if (clipboard_needs_update) {
- clipboard_needs_update = false;
- // unnamed ("implicit" clipboard)
- set_clipboard(NUL, y_previous);
- }
- return save_count;
-}
-
-void restore_batch_count(int save_count)
-{
- assert(batch_change_count == 0);
- batch_change_count = save_count;
- if (batch_change_count > 0) {
- clipboard_delay_update = true;
- }
-}
-
-/// Check whether register is empty
-static inline bool reg_empty(const yankreg_T *const reg)
- FUNC_ATTR_PURE
-{
- return (reg->y_array == NULL
- || reg->y_size == 0
- || (reg->y_size == 1
- && reg->y_type == kMTCharWise
- && reg->y_array[0].size == 0));
-}
-
-/// 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);
-}
-
-/// 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;
-}
-
-/// 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;
-}
-
/// Get the byte count of buffer region. End-exclusive.
///
/// @return number of bytes
diff --git a/src/nvim/ops.h b/src/nvim/ops.h
@@ -12,54 +12,9 @@
#include "nvim/option_defs.h" // IWYU pragma: keep
#include "nvim/os/time_defs.h"
#include "nvim/pos_defs.h"
+#include "nvim/register_defs.h"
#include "nvim/types_defs.h"
-/// structure used by block_prep, op_delete and op_yank for blockwise operators
-/// also op_change, op_shift, op_insert, op_replace - AKelly
-struct block_def {
- int startspaces; ///< 'extra' cols before first char
- int endspaces; ///< 'extra' cols after last char
- int textlen; ///< chars in block
- char *textstart; ///< pointer to 1st char (partially) in block
- colnr_T textcol; ///< index of chars (partially) in block
- colnr_T start_vcol; ///< start col of 1st char wholly inside block
- colnr_T end_vcol; ///< start col of 1st char wholly after block
- int is_short; ///< true if line is too short to fit in block
- int is_MAX; ///< true if curswant==MAXCOL when starting
- int is_oneChar; ///< true if block within one character
- int pre_whitesp; ///< screen cols of ws before block
- int pre_whitesp_c; ///< chars of ws before block
- colnr_T end_char_vcols; ///< number of vcols of post-block char
- colnr_T start_char_vcols; ///< number of vcols of pre-block char
-};
-
-/// flags for do_put()
-enum {
- PUT_FIXINDENT = 1, ///< make indent look nice
- PUT_CURSEND = 2, ///< leave cursor after end of new text
- PUT_CURSLINE = 4, ///< leave cursor on last line of new text
- PUT_LINE = 8, ///< put register as lines
- PUT_LINE_SPLIT = 16, ///< split line for linewise register
- PUT_LINE_FORWARD = 32, ///< put linewise register below Visual sel.
- PUT_BLOCK_INNER = 64, ///< in block mode, do not add trailing spaces
-};
-
-/// Registers:
-/// 0 = register for latest (unnamed) yank
-/// 1..9 = registers '1' to '9', for deletes
-/// 10..35 = registers 'a' to 'z'
-/// 36 = delete register '-'
-/// 37 = selection register '*'
-/// 38 = clipboard register '+'
-enum {
- DELETION_REGISTER = 36,
- NUM_SAVED_REGISTERS = 37,
- // The following registers should not be saved in ShaDa file:
- STAR_REGISTER = 37,
- PLUS_REGISTER = 38,
- NUM_REGISTERS = 39,
-};
-
/// Operator IDs; The order must correspond to opchars[] in ops.c!
enum {
OP_NOP = 0, ///< no pending operation
@@ -94,65 +49,7 @@ enum {
OP_NR_SUB = 29, ///< "<C-X>" Subtract from the number or alphabetic character
};
-/// Flags for get_reg_contents().
-enum GRegFlags {
- kGRegNoExpr = 1, ///< Do not allow expression register.
- kGRegExprSrc = 2, ///< Return expression itself for "=" register.
- kGRegList = 4, ///< Return list.
-};
-
-/// Definition of one register
-typedef struct {
- String *y_array; ///< Pointer to an array of Strings.
- size_t y_size; ///< Number of lines in y_array.
- MotionType y_type; ///< Register type
- colnr_T y_width; ///< Register width (only valid for y_type == kBlockWise).
- Timestamp timestamp; ///< Time when register was last modified.
- AdditionalData *additional_data; ///< Additional data from ShaDa file.
-} yankreg_T;
-
-/// Modes for get_yank_register()
-typedef enum {
- YREG_PASTE,
- YREG_YANK,
- YREG_PUT,
-} yreg_mode_t;
-
#include "ops.h.generated.h"
#include "ops.h.inline.generated.h"
-/// Convert register name into register index
-///
-/// @param[in] regname Register name.
-///
-/// @return Index in y_regs array or -1 if register name was not recognized.
-static inline int op_reg_index(const int regname)
- FUNC_ATTR_CONST
-{
- if (ascii_isdigit(regname)) {
- return regname - '0';
- } else if (ASCII_ISLOWER(regname)) {
- return CHAR_ORD_LOW(regname) + 10;
- } else if (ASCII_ISUPPER(regname)) {
- return CHAR_ORD_UP(regname) + 10;
- } else if (regname == '-') {
- return DELETION_REGISTER;
- } else if (regname == '*') {
- return STAR_REGISTER;
- } else if (regname == '+') {
- return PLUS_REGISTER;
- } else {
- return -1;
- }
-}
-
-/// @see get_yank_register
-/// @return true when register should be inserted literally
-/// (selection or clipboard)
-static inline bool is_literal_register(const int regname)
- FUNC_ATTR_CONST
-{
- return regname == '*' || regname == '+' || ASCII_ISALNUM(regname);
-}
-
EXTERN LuaRef repeat_luaref INIT( = LUA_NOREF); ///< LuaRef for "."
diff --git a/src/nvim/register.c b/src/nvim/register.c
@@ -0,0 +1,2984 @@
+// 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/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/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
+
+// for behavior between start_batch_changes() and end_batch_changes())
+static int batch_change_count = 0; // inside a script
+static bool clipboard_delay_update = false; // delay clipboard update
+static bool clipboard_needs_update = false; // clipboard was updated
+static bool clipboard_didwarn = false;
+
+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];
+}
+
+/// 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;
+}
+
+void set_clipboard(int name, yankreg_T *reg)
+{
+ if (!adjust_clipboard_name(&name, false, true)) {
+ return;
+ }
+
+ list_T *const lines = tv_list_alloc((ptrdiff_t)reg->y_size + (reg->y_type != kMTCharWise));
+
+ for (size_t i = 0; i < reg->y_size; i++) {
+ tv_list_append_string(lines, reg->y_array[i].data, -1);
+ }
+
+ char regtype;
+ switch (reg->y_type) {
+ case kMTLineWise:
+ regtype = 'V';
+ tv_list_append_string(lines, NULL, 0);
+ break;
+ case kMTCharWise:
+ regtype = 'v';
+ break;
+ case kMTBlockWise:
+ regtype = 'b';
+ tv_list_append_string(lines, NULL, 0);
+ break;
+ case kMTUnknown:
+ abort();
+ }
+
+ list_T *args = tv_list_alloc(3);
+ tv_list_append_list(args, lines);
+ tv_list_append_string(args, ®type, 1);
+ tv_list_append_string(args, ((char[]) { (char)name }), 1);
+
+ eval_call_provider("clipboard", "set", args, true);
+}
+
+/// Avoid slow things (clipboard) during batch operations (while/for-loops).
+void start_batch_changes(void)
+{
+ if (++batch_change_count > 1) {
+ return;
+ }
+ clipboard_delay_update = true;
+}
+
+/// Counterpart to start_batch_changes().
+void end_batch_changes(void)
+{
+ if (--batch_change_count > 0) {
+ // recursive
+ return;
+ }
+ clipboard_delay_update = false;
+ if (clipboard_needs_update) {
+ // must be before, as set_clipboard will invoke
+ // start/end_batch_changes recursively
+ clipboard_needs_update = false;
+ // unnamed ("implicit" clipboard)
+ set_clipboard(NUL, y_previous);
+ }
+}
+
+int save_batch_count(void)
+{
+ int save_count = batch_change_count;
+ batch_change_count = 0;
+ clipboard_delay_update = false;
+ if (clipboard_needs_update) {
+ clipboard_needs_update = false;
+ // unnamed ("implicit" clipboard)
+ set_clipboard(NUL, y_previous);
+ }
+ return save_count;
+}
+
+void restore_batch_count(int save_count)
+{
+ assert(batch_change_count == 0);
+ batch_change_count = save_count;
+ if (batch_change_count > 0) {
+ clipboard_delay_update = true;
+ }
+}
+
+/// 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;
+}
+
+/// Determine if register `*name` should be used as a clipboard.
+/// In an unnamed operation, `*name` is `NUL` and will be adjusted to */+ if
+/// `clipboard=unnamed[plus]` is set.
+///
+/// @param name The name of register, or `NUL` if unnamed.
+/// @param quiet Suppress error messages
+/// @param writing if we're setting the contents of the clipboard
+///
+/// @returns the yankreg that should be written into, or `NULL`
+/// if the register isn't a clipboard or provider isn't available.
+static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing)
+{
+#define MSG_NO_CLIP "clipboard: No provider. " \
+ "Try \":checkhealth\" or \":h clipboard\"."
+
+ yankreg_T *target = NULL;
+ bool explicit_cb_reg = (*name == '*' || *name == '+');
+ bool implicit_cb_reg = (*name == NUL) && (cb_flags & (kOptCbFlagUnnamed | kOptCbFlagUnnamedplus));
+ if (!explicit_cb_reg && !implicit_cb_reg) {
+ goto end;
+ }
+
+ if (!eval_has_provider("clipboard", false)) {
+ if (batch_change_count <= 1 && !quiet
+ && (!clipboard_didwarn || (explicit_cb_reg && !redirecting()))) {
+ clipboard_didwarn = true;
+ // Do NOT error (emsg()) here--if it interrupts :redir we get into
+ // a weird state, stuck in "redirect mode".
+ msg(MSG_NO_CLIP, 0);
+ }
+ // ... else, be silent (don't flood during :while, :redir, etc.).
+ goto end;
+ }
+
+ if (explicit_cb_reg) {
+ target = &y_regs[*name == '*' ? STAR_REGISTER : PLUS_REGISTER];
+ if (writing && (cb_flags & (*name == '*' ? kOptCbFlagUnnamed : kOptCbFlagUnnamedplus))) {
+ clipboard_needs_update = false;
+ }
+ goto end;
+ } else { // unnamed register: "implicit" clipboard
+ if (writing && clipboard_delay_update) {
+ // For "set" (copy), defer the clipboard call.
+ clipboard_needs_update = true;
+ goto end;
+ } else if (!writing && clipboard_needs_update) {
+ // For "get" (paste), use the internal value.
+ goto end;
+ }
+
+ if (cb_flags & kOptCbFlagUnnamedplus) {
+ *name = (cb_flags & kOptCbFlagUnnamed && writing) ? '"' : '+';
+ target = &y_regs[PLUS_REGISTER];
+ } else {
+ *name = '*';
+ target = &y_regs[STAR_REGISTER];
+ }
+ goto end;
+ }
+
+end:
+ return target;
+}
+
+/// Updates the "y_width" of a blockwise register based on its contents.
+/// Do nothing on a non-blockwise register.
+static 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);
+ }
+}
+
+static bool get_clipboard(int name, yankreg_T **target, bool quiet)
+{
+ // show message on error
+ bool errmsg = true;
+
+ yankreg_T *reg = adjust_clipboard_name(&name, quiet, false);
+ if (reg == NULL) {
+ return false;
+ }
+ free_register(reg);
+
+ list_T *const args = tv_list_alloc(1);
+ const char regname = (char)name;
+ tv_list_append_string(args, ®name, 1);
+
+ typval_T result = eval_call_provider("clipboard", "get", args, false);
+
+ if (result.v_type != VAR_LIST) {
+ if (result.v_type == VAR_NUMBER && result.vval.v_number == 0) {
+ // failure has already been indicated by provider
+ errmsg = false;
+ }
+ goto err;
+ }
+
+ list_T *res = result.vval.v_list;
+ list_T *lines = NULL;
+ if (tv_list_len(res) == 2
+ && TV_LIST_ITEM_TV(tv_list_first(res))->v_type == VAR_LIST) {
+ lines = TV_LIST_ITEM_TV(tv_list_first(res))->vval.v_list;
+ if (TV_LIST_ITEM_TV(tv_list_last(res))->v_type != VAR_STRING) {
+ goto err;
+ }
+ char *regtype = TV_LIST_ITEM_TV(tv_list_last(res))->vval.v_string;
+ if (regtype == NULL || strlen(regtype) > 1) {
+ goto err;
+ }
+ switch (regtype[0]) {
+ 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:
+ goto err;
+ }
+ } else {
+ lines = res;
+ // provider did not specify regtype, calculate it below
+ reg->y_type = kMTUnknown;
+ }
+
+ reg->y_array = xcalloc((size_t)tv_list_len(lines), sizeof(String));
+ reg->y_size = (size_t)tv_list_len(lines);
+ reg->additional_data = NULL;
+ reg->timestamp = 0;
+ // Timestamp is not saved for clipboard registers because clipboard registers
+ // are not saved in the ShaDa file.
+
+ size_t tv_idx = 0;
+ TV_LIST_ITER_CONST(lines, li, {
+ if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING) {
+ goto err;
+ }
+ const char *s = TV_LIST_ITEM_TV(li)->vval.v_string;
+ reg->y_array[tv_idx++] = cstr_to_string(s != NULL ? s : "");
+ });
+
+ 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) {
+ xfree(reg->y_array[reg->y_size - 1].data);
+ 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);
+
+ *target = reg;
+ return true;
+
+err:
+ if (reg->y_array) {
+ for (size_t i = 0; i < reg->y_size; i++) {
+ xfree(reg->y_array[i].data);
+ }
+ xfree(reg->y_array);
+ }
+ reg->y_array = NULL;
+ reg->y_size = 0;
+ reg->additional_data = NULL;
+ reg->timestamp = 0;
+ if (errmsg) {
+ emsg("clipboard: provider returned invalid data");
+ }
+ *target = reg;
+ return false;
+}
+
+/// @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 i = *idx;
+ assert(i > 0);
+ const size_t cmd_end = i;
+
+ 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 (--i > 0) {
+ char *p = skipwhite(lines[i].data);
+ if (*p != '\\' && (p[0] != '"' || p[1] != '\\' || p[2] != ' ')) {
+ break;
+ }
+ }
+ const size_t cmd_start = i;
+
+ // join all the lines
+ ga_concat(&ga, lines[cmd_start].data);
+ for (size_t j = cmd_start + 1; j <= cmd_end; j++) {
+ char *p = skipwhite(lines[j].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));
+ }
+ ga_concat(&ga, p + 1);
+ }
+ }
+ ga_append(&ga, NUL);
+ char *str = xmemdupz(ga.ga_data, (size_t)ga.ga_len);
+ ga_clear(&ga);
+
+ *idx = i;
+ 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);
+
+ 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(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);
+}
diff --git a/src/nvim/register.h b/src/nvim/register.h
@@ -0,0 +1,79 @@
+#pragma once
+
+#include "nvim/ascii_defs.h"
+#include "nvim/ex_cmds_defs.h"
+#include "nvim/macros_defs.h"
+#include "nvim/register_defs.h"
+
+#include "register.h.generated.h"
+#include "register.h.inline.generated.h"
+
+/// @see get_yank_register
+/// @return true when register should be inserted literally
+/// (selection or clipboard)
+static inline bool is_literal_register(const int regname)
+ FUNC_ATTR_CONST
+{
+ return regname == '*' || regname == '+' || ASCII_ISALNUM(regname);
+}
+
+/// Convert register name into register index
+///
+/// @param[in] regname Register name.
+///
+/// @return Index in y_regs array or -1 if register name was not recognized.
+static inline int op_reg_index(const int regname)
+ FUNC_ATTR_CONST
+{
+ if (ascii_isdigit(regname)) {
+ return regname - '0';
+ } else if (ASCII_ISLOWER(regname)) {
+ return CHAR_ORD_LOW(regname) + 10;
+ } else if (ASCII_ISUPPER(regname)) {
+ return CHAR_ORD_UP(regname) + 10;
+ } else if (regname == '-') {
+ return DELETION_REGISTER;
+ } else if (regname == '*') {
+ return STAR_REGISTER;
+ } else if (regname == '+') {
+ return PLUS_REGISTER;
+ } else {
+ return -1;
+ }
+}
+
+static inline bool is_append_register(int regname)
+ FUNC_ATTR_CONST
+{
+ return ASCII_ISUPPER(regname);
+}
+
+/// @return the character name of the register with the given number
+static inline int get_register_name(int num)
+ FUNC_ATTR_CONST
+{
+ if (num == -1) {
+ return '"';
+ } else if (num < 10) {
+ return num + '0';
+ } else if (num == DELETION_REGISTER) {
+ return '-';
+ } else if (num == STAR_REGISTER) {
+ return '*';
+ } else if (num == PLUS_REGISTER) {
+ return '+';
+ } else {
+ return num + 'a' - 10;
+ }
+}
+
+/// Check whether register is empty
+static inline bool reg_empty(const yankreg_T *const reg)
+ FUNC_ATTR_PURE
+{
+ return (reg->y_array == NULL
+ || reg->y_size == 0
+ || (reg->y_size == 1
+ && reg->y_type == kMTCharWise
+ && reg->y_array[0].size == 0));
+}
diff --git a/src/nvim/register_defs.h b/src/nvim/register_defs.h
@@ -0,0 +1,75 @@
+#pragma once
+
+#include "nvim/api/private/defs.h"
+#include "nvim/normal_defs.h"
+#include "nvim/os/time_defs.h"
+
+/// flags for do_put()
+enum {
+ PUT_FIXINDENT = 1, ///< make indent look nice
+ PUT_CURSEND = 2, ///< leave cursor after end of new text
+ PUT_CURSLINE = 4, ///< leave cursor on last line of new text
+ PUT_LINE = 8, ///< put register as lines
+ PUT_LINE_SPLIT = 16, ///< split line for linewise register
+ PUT_LINE_FORWARD = 32, ///< put linewise register below Visual sel.
+ PUT_BLOCK_INNER = 64, ///< in block mode, do not add trailing spaces
+};
+
+/// Registers:
+/// 0 = register for latest (unnamed) yank
+/// 1..9 = registers '1' to '9', for deletes
+/// 10..35 = registers 'a' to 'z'
+/// 36 = delete register '-'
+/// 37 = selection register '*'
+/// 38 = clipboard register '+'
+enum {
+ DELETION_REGISTER = 36,
+ NUM_SAVED_REGISTERS = 37,
+ // The following registers should not be saved in ShaDa file:
+ STAR_REGISTER = 37,
+ PLUS_REGISTER = 38,
+ NUM_REGISTERS = 39,
+};
+
+/// Flags for get_reg_contents().
+enum GRegFlags {
+ kGRegNoExpr = 1, ///< Do not allow expression register.
+ kGRegExprSrc = 2, ///< Return expression itself for "=" register.
+ kGRegList = 4, ///< Return list.
+};
+
+/// structure used by block_prep, op_delete and op_yank for blockwise operators
+/// also op_change, op_shift, op_insert, op_replace - AKelly
+struct block_def {
+ int startspaces; ///< 'extra' cols before first char
+ int endspaces; ///< 'extra' cols after last char
+ int textlen; ///< chars in block
+ char *textstart; ///< pointer to 1st char (partially) in block
+ colnr_T textcol; ///< index of chars (partially) in block
+ colnr_T start_vcol; ///< start col of 1st char wholly inside block
+ colnr_T end_vcol; ///< start col of 1st char wholly after block
+ int is_short; ///< true if line is too short to fit in block
+ int is_MAX; ///< true if curswant==MAXCOL when starting
+ int is_oneChar; ///< true if block within one character
+ int pre_whitesp; ///< screen cols of ws before block
+ int pre_whitesp_c; ///< chars of ws before block
+ colnr_T end_char_vcols; ///< number of vcols of post-block char
+ colnr_T start_char_vcols; ///< number of vcols of pre-block char
+};
+
+/// Definition of one register
+typedef struct {
+ String *y_array; ///< Pointer to an array of Strings.
+ size_t y_size; ///< Number of lines in y_array.
+ MotionType y_type; ///< Register type
+ colnr_T y_width; ///< Register width (only valid for y_type == kBlockWise).
+ Timestamp timestamp; ///< Time when register was last modified.
+ AdditionalData *additional_data; ///< Additional data from ShaDa file.
+} yankreg_T;
+
+/// Modes for get_yank_register()
+typedef enum {
+ YREG_PASTE,
+ YREG_YANK,
+ YREG_PUT,
+} yreg_mode_t;
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
@@ -43,7 +43,6 @@
#include "nvim/msgpack_rpc/packer_defs.h"
#include "nvim/msgpack_rpc/unpacker.h"
#include "nvim/normal_defs.h"
-#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/option_vars.h"
#include "nvim/os/fileio.h"
@@ -57,6 +56,7 @@
#include "nvim/path.h"
#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
+#include "nvim/register.h"
#include "nvim/search.h"
#include "nvim/shada.h"
#include "nvim/strings.h"