aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/auevents.lua2
-rw-r--r--src/nvim/buffer_defs.h1
-rw-r--r--src/nvim/eval.c10
-rw-r--r--src/nvim/eval/funcs.c1
-rw-r--r--src/nvim/eval/userfunc.c6
-rw-r--r--src/nvim/eval/vars.c16
-rw-r--r--src/nvim/map.c3
-rw-r--r--src/nvim/map_defs.h1
-rw-r--r--src/nvim/ops.c477
-rw-r--r--src/nvim/ops.h10
-rw-r--r--src/nvim/option.c4
-rw-r--r--src/nvim/option_vars.h3
-rw-r--r--src/nvim/options.lua59
-rw-r--r--src/nvim/po/da.po2
-rw-r--r--src/nvim/po/fr.po2
-rw-r--r--src/nvim/po/tr.po2
-rw-r--r--src/nvim/po/uk.po2
-rw-r--r--src/nvim/shada.c4
-rw-r--r--src/nvim/yankmap.c45
-rw-r--r--src/nvim/yankmap.h25
20 files changed, 605 insertions, 70 deletions
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index 84735c293a..3243822c94 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -118,6 +118,8 @@ return {
'TextChangedP', -- text was modified in Insert mode(popup)
'TextChangedT', -- text was modified in Terminal mode
'TextYankPost', -- after a yank or delete was done (y, d, c)
+ 'TextPutPre', -- before a put was done (p, P)
+ 'TextPutPost', -- after a put was done (p, P)
'UIEnter', -- after UI attaches
'UILeave', -- after UI detaches
'User', -- user defined autocommand
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index bffb29578f..3b84a4420f 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -541,6 +541,7 @@ struct file_buffer {
Callback b_ofu_cb; ///< 'omnifunc' callback
char *b_p_tfu; ///< 'tagfunc' option value
Callback b_tfu_cb; ///< 'tagfunc' callback
+ char *b_p_urf; ///< 'userregfunc'
char *b_p_ffu; ///< 'findfunc' option value
Callback b_ffu_cb; ///< 'findfunc' callback
int b_p_eof; ///< 'endoffile'
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 5b91f1248f..97d1a3c75d 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -3446,12 +3446,10 @@ static int eval7(char **arg, typval_T *rettv, evalarg_T *const evalarg, bool wan
// Register contents: @r.
case '@':
(*arg)++;
+ int regname = mb_cptr2char_adv((const char**) arg);
if (evaluate) {
rettv->v_type = VAR_STRING;
- rettv->vval.v_string = get_reg_contents(**arg, kGRegExprSrc);
- }
- if (**arg != NUL) {
- (*arg)++;
+ rettv->vval.v_string = get_reg_contents(regname, kGRegExprSrc);
}
break;
@@ -4819,13 +4817,13 @@ bool garbage_collect(bool testing)
// registers (ShaDa additional data)
{
- const void *reg_iter = NULL;
+ iter_register_T reg_iter = ITER_REGISTER_NULL;
do {
yankreg_T reg;
char name = NUL;
bool is_unnamed = false;
reg_iter = op_global_reg_iter(reg_iter, &name, &reg, &is_unnamed);
- } while (reg_iter != NULL);
+ } while (reg_iter != ITER_REGISTER_NULL);
}
// global marks (ShaDa additional data)
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index c125bd8893..8db50a53b3 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -3197,6 +3197,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
"xattr",
#endif
"nvim",
+ "rneovim",
};
// XXX: eval_has_provider() may shell out :(
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index f386dd28b9..84ee27493c 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -1020,6 +1020,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
bool started_profiling = false;
bool did_save_redo = false;
save_redo_T save_redo;
+ char* saved_repeat_cmdline = NULL;
// If depth of calling is getting too high, don't execute the function
if (depth >= p_mfd) {
@@ -1032,6 +1033,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
// Save search patterns and redo buffer.
save_search_patterns();
if (!ins_compl_active()) {
+ if (repeat_cmdline) {
+ saved_repeat_cmdline = xstrdup(repeat_cmdline);
+ }
saveRedobuff(&save_redo);
did_save_redo = true;
}
@@ -1376,6 +1380,8 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
// restore search patterns and redo buffer
if (did_save_redo) {
restoreRedobuff(&save_redo);
+ xfree(repeat_cmdline);
+ repeat_cmdline = saved_repeat_cmdline;
}
restore_search_patterns();
}
diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c
index 012d23b567..c64477741d 100644
--- a/src/nvim/eval/vars.c
+++ b/src/nvim/eval/vars.c
@@ -34,6 +34,7 @@
#include "nvim/globals.h"
#include "nvim/hashtab.h"
#include "nvim/macros_defs.h"
+#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/ops.h"
@@ -599,7 +600,7 @@ const char *skip_var_list(const char *arg, int *var_count, int *semicolon, bool
static const char *skip_var_one(const char *arg)
{
if (*arg == '@' && arg[1] != NUL) {
- return arg + 2;
+ return arg + 1 + utfc_ptr2len(arg + 1);
}
return find_name_end(*arg == '$' || *arg == '&' ? arg + 1 : arg,
NULL, NULL, FNE_INCL_BR | FNE_CHECK_START);
@@ -908,16 +909,20 @@ static char *ex_let_register(char *arg, typval_T *const tv, const bool is_const,
char *arg_end = NULL;
arg++;
+
+ int regname = utf_ptr2char(arg);
+ int mblen = utf_ptr2len(arg);
+
if (op != NULL && vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) {
semsg(_(e_letwrong), op);
} else if (endchars != NULL
- && vim_strchr(endchars, (uint8_t)(*skipwhite(arg + 1))) == NULL) {
+ && vim_strchr(endchars, (uint8_t)(*skipwhite(arg + mblen))) == NULL) {
emsg(_(e_letunexp));
} else {
char *ptofree = NULL;
const char *p = tv_get_string_chk(tv);
if (p != NULL && op != NULL && *op == '.') {
- char *s = get_reg_contents(*arg == '@' ? '"' : *arg, kGRegExprSrc);
+ char *s = get_reg_contents(*arg == '@' ? '"' : regname, kGRegExprSrc);
if (s != NULL) {
ptofree = concat_str(s, p);
p = ptofree;
@@ -925,8 +930,9 @@ static char *ex_let_register(char *arg, typval_T *const tv, const bool is_const,
}
}
if (p != NULL) {
- write_reg_contents(*arg == '@' ? '"' : *arg, p, (ssize_t)strlen(p), false);
- arg_end = arg + 1;
+ write_reg_contents(*arg == '@' ? '"' : regname,
+ p, (ssize_t)strlen(p), false);
+ arg_end = arg + mblen;
}
xfree(ptofree);
}
diff --git a/src/nvim/map.c b/src/nvim/map.c
index be6bf58daa..d7d1a00158 100644
--- a/src/nvim/map.c
+++ b/src/nvim/map.c
@@ -118,6 +118,9 @@ void mh_clear(MapHash *h)
#define VAL_NAME(x) quasiquote(x, ptr_t)
#include "nvim/map_value_impl.c.h"
#undef VAL_NAME
+#define VAL_NAME(x) quasiquote(x, int)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
#undef KEY_NAME
#define KEY_NAME(x) x##cstr_t
diff --git a/src/nvim/map_defs.h b/src/nvim/map_defs.h
index 836b1447c2..36c851497d 100644
--- a/src/nvim/map_defs.h
+++ b/src/nvim/map_defs.h
@@ -153,6 +153,7 @@ KEY_DECLS(HlEntry)
KEY_DECLS(ColorKey)
MAP_DECLS(int, int)
+MAP_DECLS(ptr_t, int)
MAP_DECLS(int, ptr_t)
MAP_DECLS(cstr_t, ptr_t)
MAP_DECLS(cstr_t, int)
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index d51b4cc88b..2f45d862c3 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -28,6 +28,7 @@
#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_cmds_defs.h"
@@ -76,8 +77,26 @@
#include "nvim/ui_defs.h"
#include "nvim/undo.h"
#include "nvim/vim_defs.h"
+#include "nvim/window.h"
+#include "nvim/yankmap.h"
+#include "nvim/window.h"
-static yankreg_T y_regs[NUM_REGISTERS] = { 0 };
+struct yank_registers {
+ yankmap_T inner;
+};
+
+yank_registers_T y_regs;
+
+static yankreg_T *get_reg(yank_registers_T *regs, int idx)
+{
+ return yankmap_get(&regs->inner, idx);
+
+}
+
+static yankreg_T *get_global_reg(int idx)
+{
+ return get_reg(&y_regs, idx);
+}
static yankreg_T *y_previous = NULL; // ptr to last written yankreg
@@ -866,6 +885,24 @@ char *get_expr_line_src(void)
return xstrdup(expr_line);
}
+
+int get_userreg(int regname)
+{
+ if ((regname >= 'a' && regname <= 'z')
+ || (regname >= 'A' && regname <= 'Z')
+ || (regname >= '0' && regname <= '9')
+ || (regname <= 127 && strchr("\"-:.%#=*+_/", regname))
+ || regname == Ctrl_F
+ || regname == Ctrl_P
+ || regname == Ctrl_W
+ || regname == Ctrl_A
+ || (regname + USER_REGISTERS_START) < regname) {
+ return -1;
+ }
+
+ return regname + USER_REGISTERS_START;
+}
+
/// @return whether `regname` is a valid name of a yank register.
///
/// @note: There is no check for 0 (default register), caller should do this.
@@ -882,12 +919,164 @@ bool valid_yank_reg(int regname, bool writing)
|| regname == '-'
|| regname == '_'
|| regname == '*'
- || regname == '+') {
+ || regname == '+'
+ || get_userreg(regname) != -1) {
return true;
}
return false;
}
+static int call_userreg_put(const char* urf, int regname, typval_T* out)
+{
+ char regname_str[5];
+ int len;
+
+ len = utf_char2len(regname);
+ regname_str[len] = 0;
+ utf_char2bytes(regname, regname_str);
+
+ typval_T args[3];
+ args[0].v_type = VAR_STRING;
+ args[1].v_type = VAR_STRING;
+ args[2].v_type = VAR_NUMBER;
+
+ args[0].vval.v_string = "put";
+ args[1].vval.v_string = regname_str;
+ args[2].vval.v_number = 0;
+
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.fe_evaluate = true;
+
+ return call_func(
+ urf,
+ -1,
+ out,
+ /* argcount_in = */ 3,
+ args,
+ &funcexe);
+}
+
+// Converts a typval returned from the userregfunction to a register.
+static void typval_to_yankreg(yankreg_T* yankreg, typval_T* val)
+{
+ if (!yankreg || !val) {
+ return;
+ }
+
+ char* type;
+ dict_T* dict;
+ typval_T tv;
+ size_t i;
+ size_t sz;
+
+ free_register(yankreg);
+ memset(yankreg, 0, sizeof(*yankreg));
+
+ switch (val->v_type) {
+
+ case VAR_DICT:
+ dict = val->vval.v_dict;
+ type = tv_dict_get_string(dict, "type", false);
+
+ if (!strcmp(type, "block")) {
+ yankreg->y_width = (int) tv_dict_get_number(dict, "width");
+ yankreg->y_type = kMTBlockWise;
+ } else if (!strcmp(type, "line")) {
+ yankreg->y_type = kMTLineWise;
+ } else {
+ yankreg->y_type = kMTCharWise;
+ }
+
+ if (tv_dict_get_tv(dict, "lines", &tv) == OK) {
+ if (tv.v_type == VAR_STRING) {
+ yankreg->y_array = (String*) xcalloc(1, sizeof(String));
+ yankreg->y_array[0].data = strdup(tv.vval.v_string);
+ yankreg->y_array[0].size = strlen(tv.vval.v_string);
+ } else if (tv.v_type == VAR_LIST) {
+ yankreg->y_array =
+ (String*) xcalloc((size_t) tv_list_len(tv.vval.v_list), sizeof(String));
+
+ i = 0;
+ TV_LIST_ITER_CONST(tv.vval.v_list, li, {
+ if (li->li_tv.v_type == VAR_STRING) {
+ char* tmp = strdup(tv_get_string(&li->li_tv));
+ yankreg->y_array[i].data = tmp;
+ yankreg->y_array[i].size = strlen(tmp);
+ } else {
+ yankreg->y_array[i].data = NULL;
+ yankreg->y_array[i].size = 0;
+ }
+ ++ i;
+ });
+
+ yankreg->y_size = i;
+ }
+ } else {
+ yankreg->y_array = NULL;
+ }
+
+ if (tv_dict_get_tv(dict, "additional_data", &tv) == OK) {
+ if (tv.v_type == VAR_DICT) {
+ yankreg->additional_data = NULL; // tv.vval.v_dict;
+ }
+ }
+ break;
+
+ case VAR_LIST:
+ yankreg->y_type = kMTLineWise;
+ sz = (size_t) tv_list_len(val->vval.v_list);
+ yankreg->y_array = (String*) xcalloc(sz, sizeof(String));
+ yankreg->y_size = sz;
+ i = 0;
+ TV_LIST_ITER_CONST(val->vval.v_list, li, {
+ char* tmp = strdup(tv_get_string(&li->li_tv));
+ yankreg->y_array[i].data = tmp;
+ yankreg->y_array[i].size = strlen(tmp);
+ i ++;
+ });
+ break;
+
+ default:
+ yankreg->y_type = kMTCharWise;
+ yankreg->y_size = 1;
+
+ if (val->vval.v_string) {
+ yankreg->y_array = (String*) xcalloc(1, sizeof(String));
+ char* tmp = strdup(tv_get_string(val));
+ yankreg->y_array[0].data = tmp;
+ yankreg->y_array[0].size = strlen(tmp);
+ } else {
+ yankreg->y_array = NULL;
+ }
+
+ break;
+
+ }
+
+ yankreg->timestamp = os_time();
+}
+
+static void copy_userreg(yankreg_T* into, int regname)
+{
+ if (!into) {
+ return;
+ }
+
+ if (!curbuf->b_p_urf || strlen(curbuf->b_p_urf) == 0) {
+ return;
+ }
+
+ typval_T* ret = xmalloc(sizeof(typval_T));
+
+ if (call_userreg_put(curbuf->b_p_urf, regname, ret) == FAIL) {
+ return;
+ }
+
+ typval_to_yankreg(into, ret);
+
+ tv_free(ret);
+}
+
/// @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!
@@ -931,7 +1120,11 @@ yankreg_T *get_yank_register(int regname, int mode)
if (i == -1) {
i = 0;
}
- reg = &y_regs[i];
+ reg = get_global_reg(i);
+ if (get_userreg(regname) != -1 && mode != YREG_YANK) {
+ // If the mode is not yank, copy the userreg data to the reg.
+ copy_userreg(reg, regname);
+ }
if (mode == YREG_YANK) {
// remember the written register for unnamed paste
@@ -988,8 +1181,7 @@ int do_record(int c)
if (reg_recording == 0) {
// start recording
- // registers 0-9, a-z and " are allowed
- if (c < 0 || (!ASCII_ISALNUM(c) && c != '"')) {
+ if (c < 0) {
retval = FAIL;
} else {
reg_recording = c;
@@ -1014,9 +1206,10 @@ int do_record(int c)
}
// Name of requested register, or empty string for unnamed operation.
- char buf[NUMBUFLEN + 2];
- buf[0] = (char)regname;
- buf[1] = NUL;
+ char buf[NUMBUFLEN + 5];
+ int len = utf_char2len(regname);
+ utf_char2bytes(regname, buf);
+ buf[len] = NUL;
tv_dict_add_str(dict, S_LEN("regname"), buf);
tv_dict_set_keys_readonly(dict);
@@ -1084,6 +1277,9 @@ static int stuff_yank(int regname, char *p)
reg->y_type = kMTCharWise;
}
reg->timestamp = os_time();
+ if (get_userreg(regname) != -1) {
+ return eval_yank_userreg(curbuf->b_p_urf, regname, reg);
+ }
return OK;
}
@@ -1392,6 +1588,90 @@ int insert_reg(int regname, bool literally_arg)
return retval;
}
+/// Converts a yankreg to a dict which can be used as an argument to the
+// userregfunc.
+static dict_T* yankreg_to_dict(yankreg_T* yankreg) {
+ dict_T *const dict = tv_dict_alloc();
+ dict->dv_refcount = 1;
+ tv_dict_add_nr(dict, S_LEN("width"), yankreg->y_width);
+
+ const char* type;
+
+ switch(yankreg->y_type) {
+ case kMTLineWise:
+ type = "line";
+ break;
+ case kMTCharWise:
+ type = "char";
+ break;
+ case kMTBlockWise:
+ type = "block";
+ break;
+ default:
+ type = "unknown";
+ }
+
+ tv_dict_add_str(dict, S_LEN("type"), type);
+ // if (yankreg->additional_data) {
+ // tv_dict_add_dict(dict, S_LEN("additional_data"), yankreg->additional_data);
+ // }
+
+ list_T *const lines = tv_list_alloc((long)yankreg->y_size);
+
+ size_t i;
+ for (i = 0; i < yankreg->y_size; ++ i) {
+ tv_list_append_string(
+ lines, yankreg->y_array[i].data, (long)yankreg->y_array[i].size);
+ }
+
+ tv_dict_add_list(dict, S_LEN("lines"), lines);
+
+ return dict;
+}
+
+/*
+ * Executes the yank() function on a user-defined register to set the contents
+ * of that register.
+ */
+static int eval_yank_userreg(const char *ufn, int regname, yankreg_T *reg)
+{
+ if (!reg)
+ return -1;
+
+ int ret, len;
+ char regname_str[5];
+
+ len = (*utf_char2len)(regname);
+ regname_str[len] = 0;
+ utf_char2bytes(regname, regname_str);
+
+ typval_T args[4];
+ args[0].v_type = VAR_STRING;
+ args[1].v_type = VAR_STRING;
+ args[2].v_type = VAR_DICT;
+ args[3].v_type = VAR_UNKNOWN;
+
+ args[0].vval.v_string = "yank";
+ args[1].vval.v_string = regname_str;
+ args[2].vval.v_dict = yankreg_to_dict(reg);
+
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.fe_evaluate = true;
+
+ typval_T* out = xmalloc(sizeof(typval_T));
+ return call_func(
+ ufn,
+ -1,
+ out,
+ /* argcount_in = */ 3,
+ args,
+ &funcexe
+ );
+
+ tv_free(out);
+ return ret;
+}
+
/// If "regname" is a special register, return true and store a pointer to its
/// value in "argp".
///
@@ -1475,6 +1755,9 @@ bool get_spec_reg(int regname, char **argp, bool *allocated, bool errmsg)
case '_': // black hole: always empty
*argp = "";
return true;
+
+ default:
+ break;
}
return false;
@@ -1521,14 +1804,14 @@ bool cmdline_paste_reg(int regname, bool literally_arg, bool remcr)
/// Shift the delete registers: "9 is cleared, "8 becomes "9, etc.
static void shift_delete_registers(bool y_append)
{
- free_register(&y_regs[9]); // free register "9
+ free_register(get_global_reg(9)); // free register "9
for (int n = 9; n > 1; n--) {
- y_regs[n] = y_regs[n - 1];
+ *get_global_reg(n) = *get_global_reg(n - 1);
}
if (!y_append) {
- y_previous = &y_regs[1];
+ y_previous = get_global_reg(1);
}
- y_regs[1].y_array = NULL; // set register "1 to empty
+ get_global_reg(1)->y_array = NULL; // set register "1 to empty
}
/// Handle a delete operation.
@@ -1621,7 +1904,7 @@ int op_delete(oparg_T *oap)
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];
+ reg = get_global_reg(1);
op_yank_reg(oap, false, reg, false);
did_yank = true;
}
@@ -2598,11 +2881,20 @@ int op_change(oparg_T *oap)
return retval;
}
+
+/*
+ * set all the yank registers to empty (called from main())
+ */
+void init_yank(void)
+{
+ init_yankmap(&y_regs.inner);
+}
+
#if defined(EXITFREE)
void clear_registers(void)
{
for (int i = 0; i < NUM_REGISTERS; i++) {
- free_register(&y_regs[i]);
+ free_register(get_global_reg(i));
}
}
@@ -2648,6 +2940,14 @@ bool op_yank(oparg_T *oap, bool message)
yankreg_T *reg = get_yank_register(oap->regname, YREG_YANK);
op_yank_reg(oap, message, reg, is_append_register(oap->regname));
+
+ if (get_userreg(oap->regname) != -1) {
+ if (eval_yank_userreg(curbuf->b_p_urf, oap->regname, reg) == -1) {
+ beep_flush();
+ return false;
+ }
+ }
+
set_clipboard(oap->regname, reg);
do_autocmd_textyankpost(oap, reg);
return true;
@@ -2778,7 +3078,11 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
if (oap->regname == NUL) {
*namebuf = NUL;
} else {
- vim_snprintf(namebuf, sizeof(namebuf), _(" into \"%c"), oap->regname);
+ char buf[5];
+ int len = (*utf_char2len) (oap->regname);
+ utf_char2bytes(oap->regname, buf);
+ buf[len] = 0;
+ vim_snprintf(namebuf, sizeof(namebuf), _(" into \"%s"), buf);
}
// redisplay now, so message is not deleted
@@ -2849,6 +3153,7 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg)
FUNC_ATTR_NONNULL_ALL
{
static bool recursive = false;
+ int len;
if (recursive || !has_event(EVENT_TEXTYANKPOST)) {
// No autocommand was defined, or we yanked from this autocommand.
@@ -2870,13 +3175,14 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg)
tv_dict_add_list(dict, S_LEN("regcontents"), list);
// Register type.
- char buf[NUMBUFLEN + 2];
+ char buf[NUMBUFLEN + 6];
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;
+ len = utf_char2len(oap->regname);
+ buf[len] = 0;
+ utf_char2bytes(oap->regname, buf);
tv_dict_add_str(dict, S_LEN("regname"), buf);
// Motion type: inclusive or exclusive.
@@ -2901,6 +3207,56 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg)
recursive = false;
}
+/// Execute autocommands for TextPutPost.
+///
+/// @param oap Operator arguments.
+/// @param reg The yank register used.
+static void do_autocmd_textput(int regname, yankreg_T *reg, enum auto_event evt)
+ FUNC_ATTR_NONNULL_ALL
+{
+ static bool recursive = false;
+ int len;
+
+ if (recursive || !has_event(evt)) {
+ // 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, reg->y_array[i].size);
+ }
+ tv_list_set_lock(list, VAR_FIXED);
+ (void)tv_dict_add_list(dict, S_LEN("regcontents"), list);
+
+ // Register type.
+ char buf[NUMBUFLEN + 6];
+ format_reg_type(reg->y_type, reg->y_width, buf, ARRAY_SIZE(buf));
+ (void)tv_dict_add_str(dict, S_LEN("regtype"), buf);
+
+ // Name of requested register, or empty string for unnamed operation.
+ len = utf_char2len(regname);
+ buf[len] = 0;
+ utf_char2bytes(regname, buf);
+ recursive = true;
+ (void)tv_dict_add_str(dict, S_LEN("regname"), buf);
+
+ tv_dict_set_keys_readonly(dict);
+ textlock++;
+ apply_autocmds(evt, 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!
///
@@ -2924,6 +3280,10 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags)
const pos_T orig_end = curbuf->b_op_end;
unsigned cur_ve_flags = get_ve_flags(curwin);
+ if (reg) {
+ do_autocmd_textput(regname, reg, EVENT_TEXTPUTPRE);
+ }
+
curbuf->b_op_start = curwin->w_cursor; // default for '[ mark
curbuf->b_op_end = curwin->w_cursor; // default for '] mark
@@ -3079,6 +3439,10 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags)
reg = get_yank_register(regname, YREG_PASTE);
}
+ if (get_userreg(regname) != -1) {
+ copy_userreg(reg, regname);
+ }
+
y_type = reg->y_type;
y_width = reg->y_width;
y_size = reg->y_size;
@@ -3682,6 +4046,10 @@ error:
}
end:
+ if (reg) {
+ do_autocmd_textput(regname, reg, EVENT_TEXTPUTPOST);
+ }
+
if (cmdmod.cmod_flags & CMOD_LOCKMARKS) {
curbuf->b_op_start = orig_start;
curbuf->b_op_end = orig_end;
@@ -3754,7 +4122,7 @@ int get_register_name(int num)
/// @return the index of the register "" points to.
int get_unname_register(void)
{
- return y_previous == NULL ? -1 : (int)(y_previous - &y_regs[0]);
+ return yankmap_find(&y_regs.inner, y_previous);
}
/// ":dis" and ":registers": Display the contents of the yank registers.
@@ -3791,10 +4159,10 @@ void ex_display(exarg_T *eap)
if (y_previous != NULL) {
yb = y_previous;
} else {
- yb = &(y_regs[0]);
+ yb = get_global_reg(0);
}
} else {
- yb = &(y_regs[i]);
+ yb = get_global_reg(i);
}
get_clipboard(name, &yb, true);
@@ -5040,6 +5408,10 @@ static yankreg_T *init_write_reg(int name, yankreg_T **old_y_previous, bool must
static void finish_write_reg(int name, yankreg_T *reg, yankreg_T *old_y_previous)
{
+ if (get_userreg(name) != -1) {
+ eval_yank_userreg(curbuf->b_p_urf, name, reg);
+ }
+
// Send text of clipboard register to the clipboard.
set_clipboard(name, reg);
@@ -6478,7 +6850,7 @@ static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing)
}
if (explicit_cb_reg) {
- target = &y_regs[*name == '*' ? STAR_REGISTER : PLUS_REGISTER];
+ target = get_global_reg(*name == '*' ? STAR_REGISTER : PLUS_REGISTER);
if (writing && (cb_flags & (*name == '*' ? kOptCbFlagUnnamed : kOptCbFlagUnnamedplus))) {
clipboard_needs_update = false;
}
@@ -6495,10 +6867,10 @@ static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing)
if (cb_flags & kOptCbFlagUnnamedplus) {
*name = (cb_flags & kOptCbFlagUnnamed && writing) ? '"' : '+';
- target = &y_regs[PLUS_REGISTER];
+ target = get_global_reg(PLUS_REGISTER);
} else {
*name = '*';
- target = &y_regs[STAR_REGISTER];
+ target = get_global_reg(STAR_REGISTER);
}
goto end;
}
@@ -6808,11 +7180,11 @@ static inline bool reg_empty(const yankreg_T *const reg)
/// 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)
+iter_register_T op_global_reg_iter(iter_register_T 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);
+ return op_reg_iter(iter, &y_regs, name, reg, is_unnamed);
}
/// Iterate over registers `regs`.
@@ -6824,30 +7196,33 @@ const void *op_global_reg_iter(const void *const iter, char *const name, yankreg
///
/// @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)
+iter_register_T op_reg_iter(iter_register_T iter, yank_registers_T *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++;
+ int iter_idx = (int)(iter == ITER_REGISTER_NULL ? 0 : iter - 1);
+
+ while (iter_idx < NUM_SAVED_REGISTERS && reg_empty(get_reg(regs, iter_idx))) {
+ ++iter_idx;
}
- if (iter_reg - &(regs[0]) == NUM_SAVED_REGISTERS || reg_empty(iter_reg)) {
- return NULL;
+
+ if (iter_idx >= NUM_SAVED_REGISTERS || reg_empty(get_reg(regs, iter_idx))) {
+ return ITER_REGISTER_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;
+
+ *reg = *get_reg(regs, iter_idx);
+ *name = (char) get_register_name(iter_idx);
+ *is_unnamed = (get_reg(regs, iter_idx) == y_previous);
+
+ while (++iter_idx < NUM_SAVED_REGISTERS) {
+ if (!reg_empty(get_reg(regs, iter_idx))) {
+ return iter_idx + 1;
}
}
- return NULL;
+
+ return ITER_REGISTER_NULL;
}
/// Get a number of non-empty registers
@@ -6855,8 +7230,8 @@ 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)) {
+ for (int i = 0; i < NUM_SAVED_REGISTERS; i++) {
+ if (!reg_empty(get_global_reg(i))) {
ret++;
}
}
@@ -6876,11 +7251,11 @@ bool op_reg_set(const char name, const yankreg_T reg, bool is_unnamed)
if (i == -1) {
return false;
}
- free_register(&y_regs[i]);
- y_regs[i] = reg;
+ free_register(get_global_reg(i));
+ *get_global_reg(i) = reg;
if (is_unnamed) {
- y_previous = &y_regs[i];
+ y_previous = get_global_reg(i);
}
return true;
}
@@ -6896,7 +7271,7 @@ const yankreg_T *op_reg_get(const char name)
if (i == -1) {
return NULL;
}
- return &y_regs[i];
+ return get_global_reg(i);
}
/// Set the previous yank register
@@ -6911,7 +7286,7 @@ bool op_reg_set_previous(const char name)
return false;
}
- y_previous = &y_regs[i];
+ y_previous = get_global_reg(i);
return true;
}
diff --git a/src/nvim/ops.h b/src/nvim/ops.h
index 99b9b6182d..05dd0454dc 100644
--- a/src/nvim/ops.h
+++ b/src/nvim/ops.h
@@ -37,6 +37,7 @@ typedef int (*Indenter)(void);
/// flags for do_put()
enum {
+ ITER_REGISTER_NULL = 0,
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
@@ -60,6 +61,7 @@ enum {
STAR_REGISTER = 37,
PLUS_REGISTER = 38,
NUM_REGISTERS = 39,
+ USER_REGISTERS_START = 39
};
/// Operator IDs; The order must correspond to opchars[] in ops.c!
@@ -96,6 +98,10 @@ enum {
OP_NR_SUB = 29, ///< "<C-X>" Subtract from the number or alphabetic character
};
+struct yank_registers;
+typedef struct yank_registers yank_registers_T;
+typedef size_t iter_register_T;
+
/// Flags for get_reg_contents().
enum GRegFlags {
kGRegNoExpr = 1, ///< Do not allow expression register.
@@ -119,6 +125,8 @@ typedef enum {
YREG_YANK,
YREG_PUT,
} yreg_mode_t;
+/// Returns a reference to a user-defined register.
+int get_userreg(int regname);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ops.h.generated.h"
@@ -146,7 +154,7 @@ static inline int op_reg_index(const int regname)
} else if (regname == '+') {
return PLUS_REGISTER;
} else {
- return -1;
+ return get_userreg(regname);
}
}
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 796165453c..3d805dc319 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -4679,6 +4679,8 @@ void *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win)
return &(buf->b_p_cfu);
case kOptOmnifunc:
return &(buf->b_p_ofu);
+ case kOptUserregfunc:
+ return &(buf->b_p_urf);
case kOptEndoffile:
return &(buf->b_p_eof);
case kOptEndofline:
@@ -5102,7 +5104,9 @@ void buf_copy_options(buf_T *buf, int flags)
set_buflocal_cfu_callback(buf);
buf->b_p_ofu = xstrdup(p_ofu);
COPY_OPT_SCTX(buf, kBufOptOmnifunc);
+ buf->b_p_urf = xstrdup(p_urf);
set_buflocal_ofu_callback(buf);
+ COPY_OPT_SCTX(buf, kBufOptUserregfunc);
buf->b_p_tfu = xstrdup(p_tfu);
COPY_OPT_SCTX(buf, kBufOptTagfunc);
set_buflocal_tfu_callback(buf);
diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h
index 340a12a32f..d31e76aff8 100644
--- a/src/nvim/option_vars.h
+++ b/src/nvim/option_vars.h
@@ -535,6 +535,7 @@ EXTERN char *p_udir; ///< 'undodir'
EXTERN int p_udf; ///< 'undofile'
EXTERN OptInt p_ul; ///< 'undolevels'
EXTERN OptInt p_ur; ///< 'undoreload'
+EXTERN char* p_urf; ///< 'userregfunction'
EXTERN OptInt p_uc; ///< 'updatecount'
EXTERN OptInt p_ut; ///< 'updatetime'
EXTERN char *p_shada; ///< 'shada'
@@ -577,7 +578,7 @@ EXTERN int p_wa; ///< 'writeany'
EXTERN int p_wb; ///< 'writebackup'
EXTERN OptInt p_wd; ///< 'writedelay'
EXTERN int p_cdh; ///< 'cdhome'
-
+ ///
// Value for b_p_ul indicating the global value must be used.
#define NO_LOCAL_UNDOLEVEL (-123456)
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index c61c6032c4..dcd6e0a58b 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -9588,6 +9588,65 @@ local options = {
varname = 'p_ut',
},
{
+ abbreviation='urf',
+ full_name='userregfunc',
+ desc= [=[
+ This option specifies a function to be used to handle any registers
+ that Neovim does not natively handle. This option unlocks all
+ characters to be used as registers by the user.
+
+ The 'userregfunc' function is called each time a user register is read
+ from or written to.
+
+ The 'userregfunc' function must take the following parameters:
+
+ {action} The action being done on this register (either 'yank'
+ or 'put'
+
+ {register} The string holding the name of the register. This
+ is always a single character, though multi-byte
+ characters are allowed.
+
+ {content} If the action is 'yank' this is the content being
+ yanked into the register. The content is a dictionary
+ with the following items:
+
+ {lines} The lines being yanked, as a list.
+
+ {type} The type of yank, either "line", "char", or
+ "block"
+
+ {width} The width in case of "block" mode.
+
+ {additional_data} Additional data. (can be returned in
+ put mode).
+
+ In case the action is 'put', the 'userregfunc' function should return
+ the content to place in that location. The content can either be a
+ string, in which case "char" mode is inferred, or it can return a
+ dictionary of the same template that populates 'content'.
+
+ A very simple example of a 'userregfunc' function that behaves exactly
+ like traditional registers would look like: >
+
+ let s:contents = {}
+ function! MyUserregFunction(action, register, content) abort
+ if a:action == "put"
+ return get(s:contents, a:register, "")
+ else
+ let s:contents[a:register] = a:content
+ endif
+ endfunction
+ set userregfunc=MyUserregFunction
+<
+ ]=],
+ short_desc=N_("Function used to define behavior of user-defined registers."),
+ type='string',
+ scope={'buf'},
+ varname='p_urf',
+ defaults={if_true=""}
+ },
+ {
abbreviation = 'vsts',
cb = 'did_set_varsofttabstop',
defaults = '',
diff --git a/src/nvim/po/da.po b/src/nvim/po/da.po
index 8a7bffdd53..ba7ffa9f7b 100644
--- a/src/nvim/po/da.po
+++ b/src/nvim/po/da.po
@@ -4049,7 +4049,7 @@ msgid "freeing %ld lines"
msgstr "frigør %ld linjer"
#, c-format
-msgid " into \"%c"
+msgid " into \"%s"
msgstr " i \"%c"
#, c-format
diff --git a/src/nvim/po/fr.po b/src/nvim/po/fr.po
index 900f5bde03..0dae00f3a3 100644
--- a/src/nvim/po/fr.po
+++ b/src/nvim/po/fr.po
@@ -4313,7 +4313,7 @@ msgid "freeing %ld lines"
msgstr "libration de %ld lignes"
#, c-format
-msgid " into \"%c"
+msgid " into \"%s"
msgstr " dans \"%c"
#, c-format
diff --git a/src/nvim/po/tr.po b/src/nvim/po/tr.po
index e73c033a4e..ea41cf56b3 100644
--- a/src/nvim/po/tr.po
+++ b/src/nvim/po/tr.po
@@ -4208,7 +4208,7 @@ msgstr[0] "%<PRId64> satır değiştirildi"
msgstr[1] "%<PRId64> satır değiştirildi"
#, c-format
-msgid " into \"%c"
+msgid " into \"%s"
msgstr " \"%c"
#, c-format
diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po
index 25b31dac9b..cb422cc2b6 100644
--- a/src/nvim/po/uk.po
+++ b/src/nvim/po/uk.po
@@ -5375,7 +5375,7 @@ msgstr[1] "Змінено %<PRId64> рядки"
msgstr[2] "Змінено %<PRId64> рядків"
#, c-format
-msgid " into \"%c"
+msgid " into \"%s"
msgstr " у \"%c"
#, c-format
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index d60f2368ce..a5e98af7ac 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -2109,7 +2109,7 @@ static inline void add_search_pattern(PossiblyFreedShadaEntry *const ret_pse,
static inline void shada_initialize_registers(WriteMergerState *const wms, int max_reg_lines)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE
{
- const void *reg_iter = NULL;
+ iter_register_T reg_iter = ITER_REGISTER_NULL;
const bool limit_reg_lines = max_reg_lines >= 0;
do {
yankreg_T reg;
@@ -2140,7 +2140,7 @@ static inline void shada_initialize_registers(WriteMergerState *const wms, int m
.additional_data = reg.additional_data,
}
};
- } while (reg_iter != NULL);
+ } while (reg_iter != ITER_REGISTER_NULL);
}
/// Replace numbered mark in WriteMergerState
diff --git a/src/nvim/yankmap.c b/src/nvim/yankmap.c
new file mode 100644
index 0000000000..12b70fed65
--- /dev/null
+++ b/src/nvim/yankmap.c
@@ -0,0 +1,45 @@
+#include "nvim/yankmap.h"
+
+#include "nvim/memory.h"
+
+void init_yankmap(yankmap_T* map)
+{
+ memset(map, 0, sizeof(yankmap_T));
+
+ map->reg_to_yankreg = (Map(int, ptr_t))MAP_INIT;
+ map->yankreg_to_reg = (Map(ptr_t, int))MAP_INIT;
+ // map_init(int, ptr_t, &map->reg_to_yankreg);
+ // map_init(ptr_t, int, &map->yankreg_to_reg);
+}
+
+yankreg_T* yankmap_get(yankmap_T* yankmap, int reg)
+{
+ bool is_new = false;
+ yankreg_T** ret
+ = (yankreg_T**)map_put_ref(int, ptr_t)(&yankmap->reg_to_yankreg, reg, NULL, &is_new);
+
+ if (ret) {
+ if (is_new) {
+ *ret = xcalloc(1, sizeof(yankreg_T));
+ }
+
+ /* Add the back-reference */
+ int* ref = map_put_ref(ptr_t, int)(&yankmap->yankreg_to_reg, *ret, NULL, NULL);
+ *ref = reg;
+
+ return *ret;
+ }
+
+ return NULL;
+}
+
+int yankmap_find(yankmap_T* yankmap, yankreg_T* yankreg)
+{
+ int* ref = map_ref(ptr_t, int)(&yankmap->yankreg_to_reg, yankreg, NULL);
+
+ if (ref) {
+ return *ref;
+ }
+
+ return -1;
+}
diff --git a/src/nvim/yankmap.h b/src/nvim/yankmap.h
new file mode 100644
index 0000000000..4468f3a016
--- /dev/null
+++ b/src/nvim/yankmap.h
@@ -0,0 +1,25 @@
+#ifndef YANK_TRIE_H_
+#define YANK_TRIE_H_
+
+#include <stdbool.h>
+
+#include "nvim/map_defs.h"
+#include "nvim/ops.h"
+
+typedef struct {
+ /* Register name to yank register. */
+ Map(int, ptr_t) reg_to_yankreg;
+
+ /* Yank register to register name. */
+ Map(ptr_t, int) yankreg_to_reg;
+} yankmap_T;
+
+void init_yankmap(yankmap_T* yankmap);
+
+yankreg_T* yankmap_get(yankmap_T* yankmap, int reg);
+
+yankreg_T* yankmap_put(yankmap_T* yankmap, int index);
+
+int yankmap_find(yankmap_T* yankmap, yankreg_T* yankreg);
+
+#endif