diff options
Diffstat (limited to 'src/nvim/ops.c')
-rw-r--r-- | src/nvim/ops.c | 322 |
1 files changed, 299 insertions, 23 deletions
diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 977d26890e..e89b38296c 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -29,6 +29,7 @@ #include "nvim/eval.h" #include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" +#include "nvim/eval/userfunc.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_cmds_defs.h" #include "nvim/ex_getln.h" @@ -80,6 +81,7 @@ static yankreg_T y_regs[NUM_REGISTERS] = { 0 }; static yankreg_T *y_previous = NULL; // ptr to last written yankreg +static int last_userreg_name = -1; // for behavior between start_batch_changes() and end_batch_changes()) static int batch_change_count = 0; // inside a script @@ -134,6 +136,15 @@ static char opchars[][3] = { { Ctrl_X, NUL, OPF_CHANGE }, // OP_NR_SUB }; +static char regstr_mb[MB_MAXBYTES + 1]; +/// Converts a register to a multibyte-character "string". Returns a pointer to a static buffer, so +/// this should be used quickly. +const char* reg_to_mb(int reg) +{ + regstr_mb[utf_char2bytes(reg, regstr_mb)] = 0; + return regstr_mb; +} + yankreg_T *get_y_previous(void) { return y_previous; @@ -875,17 +886,11 @@ char *get_expr_line_src(void) /// @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 (writing && vim_strchr("/.%:=", regname) != NULL) { + return false; } - return false; + + return true; } /// @return yankreg_T to use, according to the value of `regname`. @@ -909,6 +914,8 @@ bool valid_yank_reg(int regname, bool writing) yankreg_T *get_yank_register(int regname, int mode) { yankreg_T *reg; + int do_eval = !(mode & YREG_NOEVAL); + mode &= ~YREG_NOEVAL; if ((mode == YREG_PASTE || mode == YREG_PUT) && get_clipboard(regname, ®, false)) { @@ -923,16 +930,26 @@ yankreg_T *get_yank_register(int regname, int mode) && (regname == 0 || regname == '"' || regname == '*' || regname == '+') && y_previous != NULL) { // in case clipboard not available, paste from previous used register + if (do_eval && is_yankreg_user_register(y_previous)) { + read_userregister(last_userreg_name, y_previous); + } 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 (i == USER_REGISTER) { + last_userreg_name = regname; + if (do_eval && (mode == YREG_PUT || mode == YREG_PASTE)) { + read_userregister(regname, reg); + } + } + if (mode == YREG_YANK) { // remember the written register for unnamed paste y_previous = reg; @@ -945,6 +962,14 @@ static bool is_append_register(int regname) return ASCII_ISUPPER(regname); } +static bool is_user_register(int regname) { + return op_reg_index(regname) == USER_REGISTER; +} + +static bool is_yankreg_user_register(yankreg_T* yankreg) { + return yankreg == &y_regs[USER_REGISTER]; +} + /// @return a copy of contents in register `name` for use in do_put. Should be /// freed by caller. yankreg_T *copy_register(int name) @@ -988,8 +1013,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 (!valid_yank_reg(c, true)) { retval = FAIL; } else { reg_recording = c; @@ -1038,10 +1062,12 @@ int do_record(int c) // 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; + int old_last_userreg_name = last_userreg_name; retval = stuff_yank(regname, p); y_previous = old_y_previous; + last_userreg_name = old_last_userreg_name; } } return retval; @@ -1082,6 +1108,10 @@ static int stuff_yank(int regname, char *p) reg->y_array[0] = cbuf_as_string(p, plen); reg->y_size = 1; reg->y_type = kMTCharWise; + + if (is_user_register(regname)) { + write_userregister(regname, reg); + } } reg->timestamp = os_time(); return OK; @@ -2767,6 +2797,10 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) xfree(reg->y_array); } + if (is_user_register(oap->regname)) { + write_userregister(oap->regname, curr); + } + if (message) { // Display message about yank? if (yank_type == kMTCharWise && yanklines == 1) { yanklines = 0; @@ -2778,7 +2812,7 @@ 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); + vim_snprintf(namebuf, sizeof(namebuf), _(" into \"%s"), reg_to_mb(oap->regname)); } // redisplay now, so message is not deleted @@ -5033,7 +5067,8 @@ void *get_reg_contents(int regname, int flags) return retval; } -static yankreg_T *init_write_reg(int name, yankreg_T **old_y_previous, bool must_append) +static yankreg_T *init_write_reg(int name, yankreg_T **old_y_previous, int *old_last_userreg_name, + bool must_append) { if (!valid_yank_reg(name, true)) { // check for valid reg name emsg_invreg(name); @@ -5042,6 +5077,7 @@ static yankreg_T *init_write_reg(int name, yankreg_T **old_y_previous, bool must // Don't want to change the current (unnamed) register. *old_y_previous = y_previous; + *old_last_userreg_name = last_userreg_name; yankreg_T *reg = get_yank_register(name, YREG_YANK); if (!is_append_register(name) && !must_append) { @@ -5050,7 +5086,7 @@ static yankreg_T *init_write_reg(int name, yankreg_T **old_y_previous, bool must return reg; } -static void finish_write_reg(int name, yankreg_T *reg, yankreg_T *old_y_previous) +static void finish_write_reg(int name, yankreg_T *reg, yankreg_T *old_y_previous, int old_last_userreg_name) { // Send text of clipboard register to the clipboard. set_clipboard(name, reg); @@ -5058,6 +5094,11 @@ static void finish_write_reg(int name, yankreg_T *reg, yankreg_T *old_y_previous // ':let @" = "val"' should change the meaning of the "" register if (name != '"') { y_previous = old_y_previous; + last_userreg_name = old_last_userreg_name; + } + + if (is_user_register(name)) { + write_userregister(name, reg); } } @@ -5090,13 +5131,14 @@ void write_reg_contents_lst(int name, char **strings, bool must_append, MotionTy } yankreg_T *old_y_previous, *reg; - if (!(reg = init_write_reg(name, &old_y_previous, must_append))) { + int old_last_userreg_name; + if (!(reg = init_write_reg(name, &old_y_previous, &old_last_userreg_name, 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); + finish_write_reg(name, reg, old_y_previous, old_last_userreg_name); } /// write_reg_contents_ex - store `str` in register `name` @@ -5178,11 +5220,12 @@ void write_reg_contents_ex(int name, const char *str, ssize_t len, bool must_app } yankreg_T *old_y_previous, *reg; - if (!(reg = init_write_reg(name, &old_y_previous, must_append))) { + int old_last_userreg_name; + if (!(reg = init_write_reg(name, &old_y_previous, &old_last_userreg_name, must_append))) { return; } str_to_reg(reg, yank_type, str, (size_t)len, block_len, false); - finish_write_reg(name, reg, old_y_previous); + finish_write_reg(name, reg, old_y_previous, old_last_userreg_name); } /// str_to_reg - Put a string into a register. @@ -5683,6 +5726,15 @@ const char *did_set_operatorfunc(optset_T *args FUNC_ATTR_UNUSED) return NULL; } +static Callback urf_cb; +const char *did_set_userregfunc(optset_T *args FUNC_ATTR_UNUSED) +{ + if (option_set_callback_func(p_urf, &urf_cb) == FAIL) { + return e_invarg; + } + return NULL; +} + #if defined(EXITFREE) void free_operatorfunc_option(void) { @@ -6779,6 +6831,221 @@ static void set_clipboard(int name, yankreg_T *reg) eval_call_provider("clipboard", "set", args, true); } +dict_T *yankreg_to_dict(const yankreg_T *reg) +{ + if (reg == NULL) { + return NULL; + } + + dict_T *d = tv_dict_alloc(); + + // Add "lines" field + list_T *l = tv_list_alloc(reg->y_size); + for (size_t i = 0; i < reg->y_size; ++i) { + String str = reg->y_array[i]; + tv_list_append_string(l, str.data, str.size); + } + tv_dict_add_list(d, S_LEN("lines"), l); + + // Add "type" field + const char *type_str = NULL; + switch (reg->y_type) { + case kMTCharWise: + type_str = "v"; + break; + case kMTLineWise: + type_str = "V"; + break; + case kMTBlockWise: + type_str = "b"; + break; + default: + type_str = "v"; + break; + } + tv_dict_add_str(d, S_LEN("type"), xstrdup(type_str)); + + // Add "width" field for blockwise (optional) + if (reg->y_type == kMTBlockWise) { + tv_dict_add_nr(d, S_LEN("width"), reg->y_width); + } + + // Add "additional_data" fvield (empty by default) + tv_dict_add_dict(d, S_LEN("additional_data"), tv_dict_alloc()); + + return d; +} + +int dict_to_yankreg(dict_T *dict, yankreg_T *out) +{ + if (dict == NULL || out == NULL) { + return 1; + } + + dictitem_T *di_lines = tv_dict_find(dict, "lines", -1); + if (di_lines == NULL || di_lines->di_tv.v_type != VAR_LIST) { + return 1; + } + + list_T *lines = di_lines->di_tv.vval.v_list; + if (lines == NULL) { + return 1; + } + + free_register(out); + out->y_size = tv_list_len(lines); + out->y_array = xcalloc(out->y_size, sizeof(String)); + + int i = 0; + TV_LIST_ITER_CONST(lines, li, { + const typval_T *tv = TV_LIST_ITEM_TV(li); + const char *s = tv_get_string(tv); + if (s == NULL) { + s = ""; + } + + size_t len = strlen(s); + char* p = xstrnsave(s, len); + out->y_array[i++] = cbuf_to_string(p, len); + }); + + // Get "type" field + dictitem_T *di_type = tv_dict_find(dict, "type", -1); + if (di_type == NULL || di_type->di_tv.v_type != VAR_STRING) { + return 1; + } + + char *type = di_type->di_tv.vval.v_string; + if (type == NULL || strcmp(type, "v") == 0) { + out->y_type = kMTCharWise; + } else if (strcmp(type, "V") == 0) { + out->y_type = kMTLineWise; + } else if (strcmp(type, "b") == 0) { + out->y_type = kMTBlockWise; + } else { + return 1; + } + + // Get optional "width" field + dictitem_T *di_width = tv_dict_find(dict, "width", -1); + if (di_width != NULL && di_width->di_tv.v_type == VAR_NUMBER) { + out->y_width = (int)di_width->di_tv.vval.v_number; + } else { + out->y_width = 0; + } + + // Additional data is optional, no-op for now + out->additional_data = NULL; + + return true; +} + +int read_userregister(int regname, yankreg_T *out) +{ + if (out == NULL || p_urf == NULL || *p_urf == NUL) { + return 1; + } + + // Clear output + memset(out, 0, sizeof(*out)); + + typval_T rettv; + typval_T argv[3]; + + // argv[0] = "put" + argv[0].v_type = VAR_STRING; + argv[0].vval.v_string = "put"; + + // argv[1] = register name (UTF-8 string) + char regstr[MB_MAXBYTES + 1]; + int len = utf_char2bytes(regname, (char *)regstr); + regstr[len] = '\0'; + + argv[1].v_type = VAR_STRING; + argv[1].vval.v_string = regstr; + + // argv[2] = v:null + argv[2].v_type = VAR_SPECIAL; + argv[2].vval.v_special = kSpecialVarNull; + + // Call user function + if (callback_call(&urf_cb, 3, argv, &rettv) == FAIL) { + tv_clear(&rettv); + return 1; + } + + // Return is a string. Make the register a "charwise" register. + if (rettv.v_type == VAR_STRING) { + out->y_type = kMTCharWise; + out->y_size = 1; + out->y_array = xmalloc(sizeof(String)); + const char* ret = rettv.vval.v_string != NULL ? rettv.vval.v_string : ""; + size_t slen = strlen(ret); + char* saved = xstrnsave(ret, slen); + out->y_array[0] = cbuf_to_string(saved, slen); + + tv_clear(&rettv); + return 0; + } + + // Return is a dictionary. It should look like a yankreg_T. + if (rettv.v_type == VAR_DICT && rettv.vval.v_dict != NULL) { + int ec = dict_to_yankreg(rettv.vval.v_dict, out); + tv_clear(&rettv); + return ec; + } + + // Unexpected type + tv_clear(&rettv); + return 1; +} + +int write_userregister(int regname, const yankreg_T *in) +{ + int ec = 0; + typval_T argv[3] = { 0 }; + typval_T rettv = { 0 }; + dict_T* dict = NULL; + + if (in == NULL || p_urf == NULL || *p_urf == NUL) { + ec = 1; + goto end; + } + + argv[0].v_type = VAR_STRING; + argv[0].vval.v_string = "yank"; + + // Register name + char regstr[MB_MAXBYTES + 1]; + int len = utf_char2bytes(regname, regstr); + regstr[len] = '\0'; + + argv[1].v_type = VAR_STRING; + argv[1].vval.v_string = regstr; + + // Convert yankreg_T -> dict + dict = yankreg_to_dict(in); + if (dict == NULL) { + ec = 1; + goto end; + } + + argv[2].v_type = VAR_DICT; + argv[2].vval.v_dict = tv_dict_copy(NULL, dict, false, 0); + + if (callback_call(&urf_cb, 3, argv, &rettv)) { + ec = 1; + tv_clear(&rettv); + tv_clear(&argv[2]); + goto end; + } + tv_clear(&argv[2]); + + // We're not expecting anything specific from the return +end: + return ec; +} + /// Avoid slow things (clipboard) during batch operations (while/for-loops). void start_batch_changes(void) { @@ -6903,7 +7170,7 @@ size_t op_reg_amount(void) /// @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) +bool op_reg_set(int name, const yankreg_T reg, bool is_unnamed) { int i = op_reg_index(name); if (i == -1) { @@ -6912,6 +7179,10 @@ bool op_reg_set(const char name, const yankreg_T reg, bool is_unnamed) free_register(&y_regs[i]); y_regs[i] = reg; + if (i == USER_REGISTER) { + write_userregister(name, ®); + } + if (is_unnamed) { y_previous = &y_regs[i]; } @@ -6923,12 +7194,17 @@ bool op_reg_set(const char name, const yankreg_T reg, bool is_unnamed) /// @param[in] name Register name. /// /// @return Pointer to the register contents or NULL. -const yankreg_T *op_reg_get(const char name) +const yankreg_T *op_reg_get(int name) { int i = op_reg_index(name); if (i == -1) { return NULL; } + + if (i == USER_REGISTER) { + read_userregister(name, &y_regs[i]); + } + return &y_regs[i]; } @@ -6937,7 +7213,7 @@ const yankreg_T *op_reg_get(const char name) /// @param[in] name Register name. /// /// @return true on success, false on failure. -bool op_reg_set_previous(const char name) +bool op_reg_set_previous(int name) { int i = op_reg_index(name); if (i == -1) { |