diff options
35 files changed, 1011 insertions, 115 deletions
diff --git a/runtime/autoload/usermark.vim b/runtime/autoload/usermark.vim new file mode 100644 index 0000000000..b1b4113d1a --- /dev/null +++ b/runtime/autoload/usermark.vim @@ -0,0 +1,7 @@ +" This is used for the default userreg function. + +lua vim.usermark = require('vim.usermark') + +function! usermark#func(action, mark) abort + return v:lua.vim.usermark.fn(a:action, a:mark) +endfunction diff --git a/runtime/autoload/userreg.vim b/runtime/autoload/userreg.vim new file mode 100644 index 0000000000..fd026a12e6 --- /dev/null +++ b/runtime/autoload/userreg.vim @@ -0,0 +1,7 @@ +" This is used for the default userreg function. + +lua vim.userreg = require('vim.userreg') + +function! userreg#func(action, register, content) abort + return v:lua.vim.userreg.fn(a:action, a:register, a:content) +endfunction diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index b1af90a604..2bd976fa43 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1371,7 +1371,8 @@ A jump table for the options with a short description can be found at |Q_op|. 'colorcolumn' 'cc' string (default "") local to window 'colorcolumn' is a comma-separated list of screen columns that are - highlighted with ColorColumn |hl-ColorColumn|. Useful to align + highlighted with ColorColumn |hl-ColorColumn| and drawn using the + "colorcol" option from 'fillchars'. Useful to align text. Will make screen redrawing slower. The screen column can be an absolute number, or a number preceded with '+' or '-', which is added to or subtracted from 'textwidth'. > @@ -2501,6 +2502,7 @@ A jump table for the options with a short description can be found at |Q_op|. diff '-' deleted lines of the 'diff' option msgsep ' ' message separator 'display' eob '~' empty lines at the end of a buffer + colorcol ' ' character to display in the colorcolumn lastline '@' 'display' contains lastline/truncate Any one that is omitted will fall back to the default. For "stl" and @@ -2539,6 +2541,7 @@ A jump table for the options with a short description can be found at |Q_op|. fold Folded |hl-Folded| diff DiffDelete |hl-DiffDelete| eob EndOfBuffer |hl-EndOfBuffer| + colorcol:c ColorColumn |hl-ColorColumn| lastline NonText |hl-NonText| *'fixendofline'* *'fixeol'* *'nofixendofline'* *'nofixeol'* @@ -6828,6 +6831,57 @@ A jump table for the options with a short description can be found at |Q_op|. written to disk (see |crash-recovery|). Also used for the |CursorHold| autocommand event. + *'userregfunc'* *'urf'* +'userregfunc' 'urf' string (default "") + global + The 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 +< *'varsofttabstop'* *'vsts'* 'varsofttabstop' 'vsts' string (default "") local to buffer diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua index b1bc48dac6..a9d3d0cbd6 100644 --- a/runtime/lua/vim/lsp/semantic_tokens.lua +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -330,7 +330,7 @@ function STHighlighter:process_response(response, client, version) end vim.list_extend(tokens, old_tokens, idx) else - tokens = response.data + tokens = response.data or {} end -- Update the state with the new results @@ -378,7 +378,7 @@ function STHighlighter:on_win(topline, botline) -- -- Instead, we have to use normal extmarks that can attach to locations -- in the buffer and are persisted between redraws. - local highlights = current_result.highlights + local highlights = current_result.highlights or {} local idx = binary_search(highlights, topline) for i = idx, #highlights do @@ -612,7 +612,7 @@ function M.get_at_pos(bufnr, row, col) local tokens = {} for client_id, client in pairs(highlighter.client_state) do - local highlights = client.current_result.highlights + local highlights = client.current_result.highlights or {} if highlights then local idx = binary_search(highlights, row) for i = idx, #highlights do diff --git a/runtime/lua/vim/usermark.lua b/runtime/lua/vim/usermark.lua new file mode 100644 index 0000000000..0d1ec0ae0f --- /dev/null +++ b/runtime/lua/vim/usermark.lua @@ -0,0 +1,68 @@ +-- Defualt implementation of the usermarkfunc. This default implementation is +-- extensible and allows other plugins to register handlers for different +-- registers. +-- +-- The default handler behaves just as a normal register would. + +local vim = assert(vim) +local usermark = {} + +-- Returns a "default handler" which behaves like normal global marks. When a +-- call to set() is made, it stores the current line and col of the cursor and +-- the filename of the current file. +function usermark._default_handler() + local d = {} + + -- Called when a mark is recalled using the "'" command. Just returns what was + -- stored before or nothing if it was never set before. + function d.get(self, mark) + return self.content or {} + end + + -- Called when a mark is set using the "m" command. Stores the current cursor + -- position to be recalled at a later time. + function d.set(self, mark) + local r,c = unpack(vim.api.nvim_win_get_cursor(0)) + local file = vim.fn.expand("%:p") + + self.content = { + line = r; + col = c; + } + + if file ~= '' then + self.content.file = file + end + end + + return d +end + +-- The store for register default handler +usermark._marktable = {} + +-- Function for the 'usermarkfunc'. Will defer to the handler associated with +-- the provided mark. +-- +-- If not handler is registered to a given mark, the default handler is used, +-- which is a re-implementation of standard mark behavior. +function usermark.fn(action, mark) + if not usermark._marktable[mark] then + usermark._marktable[mark] = usermark._default_handler() + end + + if action == "get" then + return usermark._marktable[mark]:get(mark) + else + usermark._marktable[mark]:set(mark) + return nil + end +end + +-- Registers a handler with a mark. Gets and sets will then defer to this +-- handler when determining the mark's behavior. +function usermark.register_handler(mark, handler) + usermark._marktable[mark] = handler +end + +return usermark diff --git a/runtime/lua/vim/userreg.lua b/runtime/lua/vim/userreg.lua new file mode 100644 index 0000000000..5abcff0407 --- /dev/null +++ b/runtime/lua/vim/userreg.lua @@ -0,0 +1,51 @@ +-- Defualt implementation of the userregfunc. This default implementation is +-- extensible and allows other plugins to register handlers for different +-- registers. +-- +-- The default handler behaves just as a normal register would. + +local userreg = {} + +-- Returns a "default handler" which behaves exactly like the builtin registers +-- in Vim. Simply stores whatever was yanked and returns the last thing that was +-- yanked. +function userreg._default_handler() + local d = {} + + function d.do_yank(self, content) + self.content = content + end + + function d.do_put(self) + return self.content or {} + end + + return d +end + +-- The store for registers default handler +userreg._regtable = {} + +-- Function for the userreg. This function will defer to the handler registered +-- to the given register. If no handler is registered to the given register, the +-- default handler is used. +function userreg.fn(action, register, content) + if not userreg._regtable[register] then + userreg._regtable[register] = userreg._default_handler() + end + + if action == "yank" then + userreg._regtable[register]:do_yank(content) + return nil + else + return userreg._regtable[register]:do_put() + end +end + +-- Registers a handler with a register. Future yanks and puts will defer to the +-- handler when determining the content to put/yank. +function userreg.register_handler(register, handler) + userreg._regtable[register] = handler +end + +return userreg diff --git a/runtime/plugin/usermark.vim b/runtime/plugin/usermark.vim new file mode 100644 index 0000000000..917e7510f1 --- /dev/null +++ b/runtime/plugin/usermark.vim @@ -0,0 +1 @@ +set usermarkfunc=usermark#func diff --git a/runtime/plugin/userreg.vim b/runtime/plugin/userreg.vim new file mode 100644 index 0000000000..099e7c65cb --- /dev/null +++ b/runtime/plugin/userreg.vim @@ -0,0 +1 @@ +set userregfunc=userreg#func diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 4c99191170..28c3374c0c 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -93,6 +93,22 @@ typedef uint64_t disptick_T; // display tick type #include "nvim/syntax_defs.h" #include "nvim/terminal.h" +typedef enum { + kColorcolBehind = 1, + kColorcolForeground = 2, +} colorcol_flags_T; + +// Structure to define data associated with a colorcolumn. +typedef struct { + int col; // The column number to highlight. + int ch; // The character to draw in the column. + + char* syn_name; // The highlight group name. Must be free'd. + int syn_attr; // The attribute. Will be set before a redraw. + + int flags; // Additional flags +} colorcol_T; + // The taggy struct is used to store the information about a :tag command. typedef struct taggy { char *tagname; // tag name @@ -657,12 +673,14 @@ struct file_buffer { #ifdef BACKSLASH_IN_FILENAME char *b_p_csl; ///< 'completeslash' #endif + char *b_p_umf; ///< 'usermarkfunc' char *b_p_cfu; ///< 'completefunc' Callback b_cfu_cb; ///< 'completefunc' callback char *b_p_ofu; ///< 'omnifunc' Callback b_ofu_cb; ///< 'omnifunc' callback char *b_p_tfu; ///< 'tagfunc' Callback b_tfu_cb; ///< 'tagfunc' callback + char *b_p_urf; ///< 'userregfunc' int b_p_eof; ///< 'endoffile' int b_p_eol; ///< 'endofline' int b_p_fixeol; ///< 'fixendofline' @@ -1335,7 +1353,7 @@ struct window_S { uint32_t w_p_wbr_flags; // flags for 'winbar' uint32_t w_p_fde_flags; // flags for 'foldexpr' uint32_t w_p_fdt_flags; // flags for 'foldtext' - int *w_p_cc_cols; // array of columns to highlight or NULL + colorcol_T *w_p_cc_cols; // array of columns to highlight or NULL uint8_t w_p_culopt_flags; // flags for cursorline highlighting long w_p_siso; // 'sidescrolloff' local value long w_p_so; // 'scrolloff' local value diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 01ff207c2b..83aa68194c 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -84,12 +84,12 @@ typedef struct { /// Advance **color_cols /// /// @return true when there are columns to draw. -static bool advance_color_col(int vcol, int **color_cols) +static bool advance_color_col(int vcol, colorcol_T **color_cols) { - while (**color_cols >= 0 && vcol > **color_cols) { + while ((*color_cols)->col >= 0 && vcol > (*color_cols)->col) { (*color_cols)++; } - return **color_cols >= 0; + return (*color_cols)->col >= 0; } /// Used when 'cursorlineopt' contains "screenline": compute the margins between @@ -671,7 +671,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, int save_did_emsg; int eol_hl_off = 0; // 1 if highlighted char after EOL bool draw_color_col = false; // highlight colorcolumn - int *color_cols = NULL; // pointer to according columns array + colorcol_T *color_cols = NULL; // pointer to according columns array bool has_spell = false; // this buffer has spell checking #define SPWORDLEN 150 char nextline[SPWORDLEN * 2]; // text with start of the next line @@ -2494,15 +2494,14 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, if (draw_color_col) { // determine rightmost colorcolumn to possibly draw - for (i = 0; color_cols[i] >= 0; i++) { - if (rightmost_vcol < color_cols[i]) { - rightmost_vcol = color_cols[i]; + for (i = 0; color_cols[i].col >= 0; i++) { + if (rightmost_vcol < color_cols[i].col) { + rightmost_vcol = color_cols[i].col; } } } int cuc_attr = win_hl_attr(wp, HLF_CUC); - int mc_attr = win_hl_attr(wp, HLF_MC); int diff_attr = 0; if (diff_hlf == HLF_TXD) { @@ -2530,8 +2529,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol) { col_attr = cuc_attr; - } else if (draw_color_col && VCOL_HLC == *color_cols) { - col_attr = mc_attr; + } else if (draw_color_col && VCOL_HLC == color_cols->col) { + col_attr = color_cols->syn_attr; + c = color_cols->ch; + schar_from_char(linebuf_char[off], c); } col_attr = hl_combine_attr(col_attr, line_attr); @@ -2618,9 +2619,14 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, && lnum != wp->w_cursor.lnum) { vcol_save_attr = char_attr; char_attr = hl_combine_attr(win_hl_attr(wp, HLF_CUC), char_attr); - } else if (draw_color_col && VCOL_HLC == *color_cols) { + } else if (draw_color_col && VCOL_HLC == color_cols->col) { vcol_save_attr = char_attr; - char_attr = hl_combine_attr(win_hl_attr(wp, HLF_MC), char_attr); + + if (color_cols->flags & kColorcolForeground) { + char_attr = hl_combine_attr(char_attr, color_cols->syn_attr); + } else if (!(color_cols->flags & kColorcolBehind)) { + char_attr = hl_combine_attr(color_cols->syn_attr, char_attr); + } } } diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index 04c342e068..f5f72b711f 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -1016,6 +1016,19 @@ static void win_update(win_T *wp, DecorProviders *providers) return; } + // Link colorcolumn syn_attrs to syn_names. Needs to be done at a redraw + // as the syn names are volitile and can change. + if (wp->w_p_cc_cols) { + for (int i = 0; wp->w_p_cc_cols[i].col >= 0; ++ i) { + const char* syn_name = wp->w_p_cc_cols[i].syn_name; + if (syn_name == NULL) { + wp->w_p_cc_cols[i].syn_attr = win_hl_attr(wp, HLF_MC); + } else { + wp->w_p_cc_cols[i].syn_attr = syn_name2attr(syn_name); + } + } + } + buf_T *buf = wp->w_buffer; // reset got_int, otherwise regexp won't work diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 4392ea306f..ffab9a02b5 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2976,12 +2976,10 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) // 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; @@ -4175,7 +4173,7 @@ 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; @@ -4184,7 +4182,7 @@ bool garbage_collect(bool testing) if (name != NUL) { ABORTING(set_ref_dict)(reg.additional_data, copyID); } - } 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 48f3cd4293..569c1462d1 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -3134,6 +3134,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) "title", "user-commands", // was accidentally included in 5.4 "user_commands", + "usermarks", "vartabs", "vertsplit", "vimscript-1", @@ -3147,6 +3148,8 @@ static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) "winaltkeys", "writebackup", "nvim", + "rneovim", + "fancycolorcol", }; // XXX: eval_has_provider() may shell out :( diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 6c6dc3fa43..ab81c190b0 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -854,6 +854,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett int started_profiling = false; bool did_save_redo = false; save_redo_T save_redo; + char_u* saved_repeat_cmdline = NULL; // If depth of calling is getting too high, don't execute the function if (depth >= p_mfd) { @@ -866,6 +867,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; } @@ -1215,6 +1219,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 9ed245d6c4..f38dc90e9d 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -408,7 +408,7 @@ const char *skip_var_list(const char *arg, int *var_count, int *semicolon) 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 (char *)find_name_end(*arg == '$' || *arg == '&' ? arg + 1 : arg, NULL, NULL, FNE_INCL_BR | FNE_CHECK_START); @@ -716,10 +716,14 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo return 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 *s; @@ -727,7 +731,7 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo char *ptofree = NULL; const char *p = tv_get_string_chk(tv); if (p != NULL && op != NULL && *op == '.') { - s = get_reg_contents(*arg == '@' ? '"' : *arg, kGRegExprSrc); + s = get_reg_contents(*arg == '@' ? '"' : regname, kGRegExprSrc); if (s != NULL) { ptofree = concat_str(s, p); p = (const char *)ptofree; @@ -735,8 +739,9 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo } } 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/ex_docmd.c b/src/nvim/ex_docmd.c index a24e8458a6..5d271e4ef6 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -3195,7 +3195,7 @@ char *skip_range(const char *cmd, int *ctx) } } if (*cmd != NUL) { - cmd++; + cmd += utf_ptr2len(cmd); } } @@ -3338,13 +3338,13 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int goto error; } if (skip) { - cmd++; + cmd += utfc_ptr2len(cmd); } else { // Only accept a mark in another file when it is // used by itself: ":'M". MarkGet flag = to_other_file && cmd[1] == NUL ? kMarkAll : kMarkBufLocal; - fmark_T *fm = mark_get(curbuf, curwin, NULL, flag, *cmd); - cmd++; + fmark_T *fm = mark_get(curbuf, curwin, NULL, flag, utf_ptr2char(cmd)); + cmd += utf_ptr2len(cmd); if (fm != NULL && fm->fnum != curbuf->handle) { // Jumped to another file. lnum = curwin->w_cursor.lnum; diff --git a/src/nvim/map.c b/src/nvim/map.c index 191a459863..b94e0dd8c6 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -162,6 +162,8 @@ static inline bool ColorKey_eq(ColorKey ae1, ColorKey ae2) return memcmp(&ae1, &ae2, sizeof(ae1)) == 0; } +MAP_IMPL(ptr_t, int, DEFAULT_INITIALIZER) +MAP_IMPL(int, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(int, int, DEFAULT_INITIALIZER) MAP_IMPL(int, cstr_t, DEFAULT_INITIALIZER) MAP_IMPL(cstr_t, ptr_t, DEFAULT_INITIALIZER) diff --git a/src/nvim/map.h b/src/nvim/map.h index 92f0b32255..9b91d3c7a9 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -39,7 +39,9 @@ // // NOTE: Keys AND values must be allocated! khash.h does not make a copy. // +MAP_DECLS(int, ptr_t) MAP_DECLS(int, int) +MAP_DECLS(ptr_t, int) MAP_DECLS(int, cstr_t) MAP_DECLS(cstr_t, ptr_t) MAP_DECLS(cstr_t, int) diff --git a/src/nvim/mark.c b/src/nvim/mark.c index f1a1f25e6c..0c7f5efd89 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -26,6 +26,7 @@ #include "nvim/globals.h" #include "nvim/highlight_defs.h" #include "nvim/mark.h" +#include "nvim/mark_defs.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" @@ -42,6 +43,8 @@ #include "nvim/textobject.h" #include "nvim/undo_defs.h" #include "nvim/vim.h" +#include "nvim/map.h" +#include "nvim/eval/userfunc.h" // This file contains routines to maintain and manipulate marks. @@ -54,6 +57,32 @@ /// Global marks (marks with file number or name) static xfmark_T namedfm[NGLOBALMARKS]; +static struct { + Map(int, ptr_t) named; +} usermarks; +bool usermarks_init; + +static xfmark_T* lookup_user_mark(int mark) +{ + if (!usermarks_init) { + map_init(int, ptr_t, &usermarks.named); + usermarks_init = 1; + } + + xfmark_T **ret = + (xfmark_T**) map_ref(int, ptr_t)(&usermarks.named, mark, true); + + if (ret) { + if (! (*ret)) { + *ret = xcalloc(sizeof(xfmark_T), 1); + } + + return *ret; + } + + return NULL; +} + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "mark.c.generated.h" #endif @@ -160,7 +189,8 @@ int setmark_pos(int c, pos_T *pos, int fnum, fmarkv_T *view_pt) RESET_XFMARK(namedfm + i, *pos, fnum, view, NULL); return OK; } - return FAIL; + + return mark_set_user(buf, c); } // Set the previous context mark to the current position and add it to the @@ -341,6 +371,15 @@ fmark_T *mark_get(buf_T *buf, win_T *win, fmark_T *fmp, MarkGet flag, int name) // Local Marks fm = mark_get_local(buf, win, name); } + + if (!fm) { + // Get usermark. + xfmark_T* xm = mark_get_user(buf, name); + if (xm) { + fm = &xm->fmark; + } + } + if (fmp != NULL && fm != NULL) { *fmp = *fm; return fmp; @@ -429,6 +468,123 @@ fmark_T *mark_get_local(buf_T *buf, win_T *win, int name) return mark; } +/// Loads the mark 'out' with the results from calling the usermarkfunc. +/// +/// @param umf String for the usermarkfunc +/// @param name name for the mark +/// @param[out] out the mark to write the results to. +static int call_umf( + const char* umf, int name, typval_T* out, const char* get_or_set_a) +{ + char markname_str[5]; + char get_or_set[4]; + int len; + + strncpy(get_or_set, get_or_set_a, sizeof(get_or_set)); + get_or_set[3] = 0; + + len = (*utf_char2len)(name); + markname_str[len] = 0; + utf_char2bytes(name, markname_str); + + typval_T args[3]; + args[0].v_type = VAR_STRING; + args[1].v_type = VAR_STRING; + args[2].v_type = VAR_UNKNOWN; + + args[0].vval.v_string = get_or_set; + args[1].vval.v_string = markname_str; + + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.fe_evaluate = true; + + return call_func(umf, -1, out, 2, args, &funcexe); +} + +static int typval_to_xfmark(buf_T* buf, xfmark_T* out, typval_T* in) +{ + varnumber_T line; + varnumber_T col; + char* filename = NULL; + + switch (in->v_type) { + case VAR_DICT: + line = tv_dict_get_number(in->vval.v_dict, "line"); + col = tv_dict_get_number(in->vval.v_dict, "col"); + filename = tv_dict_get_string(in->vval.v_dict, "file", true); + break; + + case VAR_NUMBER: + line = in->vval.v_number; + col = 1; + break; + + default: + return -1; + } + + free_xfmark(*out); + memset(out, 0, sizeof(*out)); + + out->fname = filename; + out->fmark.mark.col = (int) col; + out->fmark.mark.lnum = (int) line; + out->fmark.fnum = 0; + out->fmark.timestamp = os_time(); + + return 0; +} + +/// Gets marks that are defined by the user. +/// +/// @param buf the buffer +/// @param name name fo the mark +xfmark_T *mark_get_user(buf_T* buf, int name) +{ + const char* umf = (const char*) buf->b_p_umf; + + if (!umf) { + return NULL; + } + + xfmark_T* mark = lookup_user_mark(name); + if (mark) { + typval_T* typval = xcalloc(sizeof(typval_T), 1); + call_umf(umf, name, typval, "get"); + typval_to_xfmark(buf, mark, typval); + tv_free(typval); + + if (mark->fname) { + buf_T* buffer = + buflist_new( + mark->fname, NULL, mark->fmark.mark.lnum, BLN_CURBUF | BLN_LISTED); + + if (buffer) { + mark->fmark.fnum = buffer->b_fnum; + } + } else { + mark->fmark.fnum = buf->b_fnum; + } + } + + return mark; +} + +int mark_set_user(buf_T* buf, int name) +{ + const char* umf = (const char*) buf->b_p_umf; + + if (!umf) { + return FAIL; + } + + typval_T* out = xcalloc(sizeof(typval_T), 1); + call_umf(umf, name, out, "set"); + tv_free(out); + + return OK; +} + /// Get marks that are actually motions but return them as marks /// /// Gets the following motions as marks: '{', '}', '(', ')' diff --git a/src/nvim/ops.c b/src/nvim/ops.c index cb56cb7027..86e317b7f6 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -25,6 +25,7 @@ #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" +#include "nvim/eval/userfunc.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_cmds_defs.h" #include "nvim/ex_getln.h" @@ -63,8 +64,24 @@ #include "nvim/undo.h" #include "nvim/vim.h" #include "nvim/window.h" +#include "nvim/yankmap.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(®s->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 @@ -782,6 +799,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. @@ -798,12 +833,152 @@ 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 = xcalloc(sizeof(char*), 1); + yankreg->y_array[0] = strdup(tv.vval.v_string); + } else if (tv.v_type == VAR_LIST) { + yankreg->y_array = + xcalloc(sizeof(char*), (size_t) tv_list_len(tv.vval.v_list)); + + i = 0; + TV_LIST_ITER_CONST(tv.vval.v_list, li, { + if (li->li_tv.v_type == VAR_STRING) { + yankreg->y_array[i] = strdup(tv_get_string(&li->li_tv)); + } else { + yankreg->y_array[i] = NULL; + } + ++ 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 = 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 = xcalloc(sizeof(char*), sz); + yankreg->y_size = sz; + i = 0; + TV_LIST_ITER_CONST(val->vval.v_list, li, { + yankreg->y_array[i] = strdup(tv_get_string(&li->li_tv)); + i ++; + }); + break; + + default: + yankreg->y_type = kMTCharWise; + yankreg->y_size = 1; + + if (val->vval.v_string) { + yankreg->y_array = xcalloc(sizeof(char*), 1); + yankreg->y_array[0] = strdup(tv_get_string(val)); + } 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! @@ -841,7 +1016,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 @@ -909,8 +1088,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; @@ -935,9 +1113,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; (void)tv_dict_add_str(dict, S_LEN("regname"), buf); tv_dict_set_keys_readonly(dict); @@ -1012,6 +1191,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; } @@ -1308,6 +1490,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], (long)strlen(yankreg->y_array[i])); + } + + 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". /// @@ -1393,6 +1659,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; @@ -1439,14 +1708,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. @@ -1542,7 +1811,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; } @@ -2556,13 +2825,22 @@ 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) { int i; for (i = 0; i < NUM_REGISTERS; i++) { - free_register(&y_regs[i]); + free_register(get_global_reg(i)); } } @@ -2608,6 +2886,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); @@ -2788,7 +3074,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 @@ -2858,6 +3148,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. @@ -2879,13 +3170,15 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) (void)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)); (void)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); + recursive = true; (void)tv_dict_add_str(dict, S_LEN("regname"), buf); // Motion type: inclusive or exclusive. @@ -3150,6 +3443,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long 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; @@ -3817,7 +4114,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. @@ -3856,10 +4153,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); @@ -5068,6 +5365,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); @@ -6507,7 +6808,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 == '*' ? CB_UNNAMED : CB_UNNAMEDPLUS))) { clipboard_needs_update = false; } @@ -6524,10 +6825,10 @@ static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing) if (cb_flags & CB_UNNAMEDPLUS) { *name = (cb_flags & CB_UNNAMED && 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; } @@ -6843,11 +7144,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`. @@ -6859,30 +7160,31 @@ 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++; - } - 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; + 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_idx >= NUM_SAVED_REGISTERS || reg_empty(get_reg(regs, iter_idx))) + return ITER_REGISTER_NULL; + + *reg = *get_reg(regs, iter_idx); + *name = (char)get_register_name((int)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_register_T)(iter_idx + 1); } } - return NULL; + + return ITER_REGISTER_NULL; } /// Get a number of non-empty registers @@ -6890,8 +7192,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++; } } @@ -6911,11 +7213,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; } @@ -6931,7 +7233,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 @@ -6946,7 +7248,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 75ea1853a0..bffb692d00 100644 --- a/src/nvim/ops.h +++ b/src/nvim/ops.h @@ -25,6 +25,7 @@ typedef int (*Indenter)(void); #define PUT_LINE_SPLIT 16 // split line for linewise register #define PUT_LINE_FORWARD 32 // put linewise register below Visual sel. #define PUT_BLOCK_INNER 64 // in block mode, do not add trailing spaces +#define ITER_REGISTER_NULL 0 // Registers: // 0 = register for latest (unnamed) yank @@ -38,7 +39,8 @@ typedef int (*Indenter)(void); // The following registers should not be saved in ShaDa file: #define STAR_REGISTER 37 #define PLUS_REGISTER 38 -#define NUM_REGISTERS 39 +#define USER_REGISTERS_START 39 +#define NUM_REGISTERS USER_REGISTERS_START // Operator IDs; The order must correspond to opchars[] in ops.c! #define OP_NOP 0 // no pending operation @@ -97,6 +99,8 @@ typedef enum { YREG_YANK, YREG_PUT, } yreg_mode_t; +/// Returns a reference to a user-defined register. +int get_userreg(const int regname); /// Convert register name into register index /// @@ -119,10 +123,15 @@ static inline int op_reg_index(const int regname) } else if (regname == '+') { return PLUS_REGISTER; } else { - return -1; + return get_userreg(regname); } } +struct yank_registers; +typedef struct yank_registers yank_registers_T; + +typedef size_t iter_register_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ops.h.generated.h" #endif diff --git a/src/nvim/option.c b/src/nvim/option.c index 387b94533c..86840faa43 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -3997,6 +3997,8 @@ static char_u *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win) return (char_u *)&(buf->b_p_cfu); case PV_OFU: return (char_u *)&(buf->b_p_ofu); + case PV_URF: + return (char_u *)&(buf->b_p_urf); case PV_EOF: return (char_u *)&(buf->b_p_eof); case PV_EOL: @@ -4077,6 +4079,8 @@ static char_u *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win) return (char_u *)&(buf->b_p_sw); case PV_TFU: return (char_u *)&(buf->b_p_tfu); + case PV_UMF: + return (char_u *)&(buf->b_p_umf); case PV_TS: return (char_u *)&(buf->b_p_ts); case PV_TW: @@ -4413,9 +4417,13 @@ 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, BV_OFU); + buf->b_p_urf = xstrdup(p_urf); + COPY_OPT_SCTX(buf, BV_URF); set_buflocal_ofu_callback(buf); buf->b_p_tfu = xstrdup(p_tfu); COPY_OPT_SCTX(buf, BV_TFU); + buf->b_p_umf = xstrdup(p_umf); + COPY_OPT_SCTX(buf, BV_UMF); set_buflocal_tfu_callback(buf); buf->b_p_sts = p_sts; COPY_OPT_SCTX(buf, BV_STS); diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index d190fc5999..6fb4621033 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -398,6 +398,7 @@ EXTERN long p_channel; ///< 'channel' EXTERN char *p_cink; ///< 'cinkeys' EXTERN char *p_cinsd; ///< 'cinscopedecls' EXTERN char *p_cinw; ///< 'cinwords' +EXTERN char *p_umf; ///< 'usermarkfunc' EXTERN char *p_cfu; ///< 'completefunc' EXTERN char *p_ofu; ///< 'omnifunc' EXTERN char *p_tsrfu; ///< 'thesaurusfunc' @@ -816,6 +817,7 @@ EXTERN int p_wa; // 'writeany' EXTERN int p_wb; // 'writebackup' EXTERN long p_wd; // 'writedelay' EXTERN int p_cdh; // 'cdhome' +EXTERN char *p_urf; // 'userregister' EXTERN int p_force_on; ///< options that cannot be turned off. EXTERN int p_force_off; ///< options that cannot be turned on. @@ -903,6 +905,7 @@ enum { BV_SW, BV_SWF, BV_TFU, + BV_UMF, BV_TSRFU, BV_TAGS, BV_TC, @@ -914,6 +917,7 @@ enum { BV_WM, BV_VSTS, BV_VTS, + BV_URF, BV_COUNT, // must be the last one }; diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 387ccd0888..11d0a7e92c 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2629,6 +2629,20 @@ return { defaults={if_true=4000} }, { + full_name='usermarkfunc', abbreviation='umf', + short_desc=N_("function used to perform jumps/sets to user marks."), + type='string', scope={'buffer'}, + varname='p_umf', + defaults={if_true=""}, + }, + { + full_name='userregfunc', abbreviation='urf', + short_desc=N_("Function used to define behavior of user-defined registers."), + type='string', scope={'buffer'}, + varname='p_urf', + defaults={if_true=""} + }, + { full_name='varsofttabstop', abbreviation='vsts', short_desc=N_("list of numbers of spaces that <Tab> uses while editing"), type='string', list='comma', scope={'buffer'}, diff --git a/src/nvim/po/da.po b/src/nvim/po/da.po index c55918abc9..80f558aba3 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 f4fce68ac5..b8c61a627e 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 eb7879efc4..5e049127ba 100644 --- a/src/nvim/po/tr.po +++ b/src/nvim/po/tr.po @@ -3960,7 +3960,7 @@ msgid "E748: No previously used register" msgstr "E748: Daha önce kullanılan bir yazmaç yok" #, 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 06f845f113..785b3909bb 100644 --- a/src/nvim/po/uk.po +++ b/src/nvim/po/uk.po @@ -4039,7 +4039,7 @@ msgid "E748: No previously used register" msgstr "E748: Регістри перед цим не вживались" #, c-format -msgid " into \"%c" +msgid " into \"%s" msgstr " у \"%c" #, c-format diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 05da6e0ef1..f35912849d 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -677,9 +677,9 @@ static void recording_mode(int attr) return; } - char s[4]; - snprintf(s, ARRAY_SIZE(s), " @%c", reg_recording); - msg_puts_attr(s, attr); + char s[7] = { ' ', '@', 0, 0, 0, 0, 0 }; + utf_char2bytes(reg_recording, s + 2); + msg_puts_attr(s, attr); } void get_trans_bufname(buf_T *buf) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 90a01aaf97..c16f3debf9 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -2395,7 +2395,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; @@ -2426,7 +2426,7 @@ static inline void shada_initialize_registers(WriteMergerState *const wms, int m } } }; - } while (reg_iter != NULL); + } while (reg_iter != ITER_REGISTER_NULL); } /// Replace numbered mark in WriteMergerState diff --git a/src/nvim/state.c b/src/nvim/state.c index 9ba5f81776..a25cb5b76e 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -35,6 +35,8 @@ # include "state.c.generated.h" // IWYU pragma: export #endif +size_t hack_keyfix = 0; + void state_enter(VimState *s) { for (;;) { @@ -85,6 +87,18 @@ getkey: } } + // Hacky "fix" for https://github.com/neovim/neovim/issues/20726 + if (key == 3) { + hack_keyfix ++; + if (hack_keyfix == 100) { + got_int = 0; + hack_keyfix = 0; + goto getkey; + } + } else { + hack_keyfix = 0; + } + if (key == K_EVENT) { // An event handler may use the value of reg_executing. // Clear it if it should be cleared when getting the next character. diff --git a/src/nvim/window.c b/src/nvim/window.c index 6b40ccdb84..4448b72ac0 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -5064,6 +5064,17 @@ void free_wininfo(wininfo_T *wip, buf_T *bp) xfree(wip); } +// Free colorcolumns. Has syntax names. +static void free_wp_cc_cols(colorcol_T* cc) +{ + if (cc) { + for (int i = 0; cc[i].col >= 0; i ++) { + xfree(cc[i].syn_name); + } + } + xfree(cc); +} + /// Remove window 'wp' from the window list and free the structure. /// /// @param tp tab page "win" is in, NULL for current @@ -5157,7 +5168,7 @@ static void win_free(win_T *wp, tabpage_T *tp) qf_free_all(wp); - xfree(wp->w_p_cc_cols); + free_wp_cc_cols(wp->w_p_cc_cols); win_free_grid(wp, false); @@ -7399,10 +7410,21 @@ static bool frame_check_width(const frame_T *topfrp, int width) return true; } -/// Simple int comparison function for use with qsort() -static int int_cmp(const void *a, const void *b) +/// Simple colorcol_T comparison function for use with qsort(). +/// Compares the column numbers +static int colorcol_cmp(const void *a, const void *b) { - return *(const int *)a - *(const int *)b; + colorcol_T *ac = (colorcol_T*) a; + colorcol_T *bc = (colorcol_T*) b; + int ret = ac->col - bc->col; + if (ret == 0) { + // qsort() is not inherently stable, so to make it stable, + // the syn_attr field temporarily contains the original index. + // Comparing these will enforce stability. + return ac->syn_attr - bc->syn_attr; + } else { + return ret; + } } /// Handle setting 'colorcolumn' or 'textwidth' in window "wp". @@ -7415,9 +7437,16 @@ char *check_colorcolumn(win_T *wp) } unsigned int count = 0; - int color_cols[256]; + colorcol_T color_cols[256]; + bool do_skip = false; + for (char *s = wp->w_p_cc; *s != NUL && count < 255;) { int col; + int ch = ' '; + char syn_name[256]; + int flags = 0; + syn_name[0] = 0; + if (*s == '-' || *s == '+') { // -N and +N: add to 'textwidth' col = (*s == '-') ? -1 : 1; @@ -7427,7 +7456,7 @@ char *check_colorcolumn(win_T *wp) } col = col * getdigits_int(&s, true, 0); if (wp->w_buffer->b_p_tw == 0) { - goto skip; // 'textwidth' not set, skip this item + do_skip = true; // 'textwidth' not set, skip this item } assert((col >= 0 && wp->w_buffer->b_p_tw <= INT_MAX - col @@ -7437,15 +7466,63 @@ char *check_colorcolumn(win_T *wp) && wp->w_buffer->b_p_tw + col <= INT_MAX)); col += (int)wp->w_buffer->b_p_tw; if (col < 0) { - goto skip; + do_skip = true; } } else if (ascii_isdigit(*s)) { col = getdigits_int(&s, true, 0); } else { return e_invarg; } - color_cols[count++] = col - 1; // 1-based to 0-based -skip: + + // Parse the character. + if (*s == '/') { + s ++; + ch = mb_ptr2char_adv((const char**) &s); + if (!ch) { + return e_invarg; + } + } + + // Parse the highlight group. + if (*s == '/') { + s ++; + size_t i = 0; + while(i < sizeof(syn_name) && *s && *s != '/' && *s != ',') { + syn_name[i ++] = *(s++); + } + syn_name[i] = 0; + } + + // Parse extra flags + if (*s == '/') { + s ++; + while (*s != ',' && *s) { + switch (*(s ++)) { + case 'b': + flags |= kColorcolBehind; + break; + + case 'f': + flags |= kColorcolForeground; + break; + + default: + return e_invarg; + } + } + } + + if (!do_skip) { + color_cols[count] = (colorcol_T) { + .col = col - 1, // 1-based to 0-based + .syn_attr = (int) count, // Temporarily use this for stable sorting. + .ch = ch, + .syn_name = syn_name[0] == 0 ? NULL : xstrdup(syn_name), + .flags = flags, + }; + count ++; + } + if (*s == NUL) { break; } @@ -7457,23 +7534,26 @@ skip: } } - xfree(wp->w_p_cc_cols); + free_wp_cc_cols(wp->w_p_cc_cols); if (count == 0) { wp->w_p_cc_cols = NULL; } else { - wp->w_p_cc_cols = xmalloc(sizeof(int) * (count + 1)); + wp->w_p_cc_cols = xmalloc(sizeof(colorcol_T) * (count + 1)); // sort the columns for faster usage on screen redraw inside // win_line() - qsort(color_cols, count, sizeof(int), int_cmp); + qsort(color_cols, count, sizeof(colorcol_T), colorcol_cmp); int j = 0; for (unsigned int i = 0; i < count; i++) { // skip duplicates - if (j == 0 || wp->w_p_cc_cols[j - 1] != color_cols[i]) { - wp->w_p_cc_cols[j++] = color_cols[i]; + if (j == 0 || wp->w_p_cc_cols[j - 1].col != color_cols[i].col) { + wp->w_p_cc_cols[j] = color_cols[i]; + // Clear syn_attr, which was used for stable sorting. + wp->w_p_cc_cols[j ++].syn_attr = 0; } } - wp->w_p_cc_cols[j] = -1; // end marker + memset(&wp->w_p_cc_cols[j], 0, sizeof(wp->w_p_cc_cols[j])); + wp->w_p_cc_cols[j].col = -1; // end marker } return NULL; // no error diff --git a/src/nvim/yankmap.c b/src/nvim/yankmap.c new file mode 100644 index 0000000000..d9229e015d --- /dev/null +++ b/src/nvim/yankmap.c @@ -0,0 +1,42 @@ +#include "nvim/yankmap.h" + +#include "nvim/memory.h" + +void init_yankmap(yankmap_T* map) +{ + memset(map, 0, sizeof(yankmap_T)); + + 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) +{ + yankreg_T** ret = + (yankreg_T**) map_ref(int, ptr_t)(&yankmap->reg_to_yankreg, reg, true); + + if (ret) { + if (! (*ret)) { + *ret = xcalloc(sizeof(yankreg_T), 1); + } + + /* Add the back-reference */ + int* ref = map_ref(ptr_t, int)(&yankmap->yankreg_to_reg, *ret, true); + *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, false); + + if (ref) { + return *ref; + } + + return -1; +} diff --git a/src/nvim/yankmap.h b/src/nvim/yankmap.h new file mode 100644 index 0000000000..da7c4dcf13 --- /dev/null +++ b/src/nvim/yankmap.h @@ -0,0 +1,24 @@ +#ifndef YANK_TRIE_H_ +#define YANK_TRIE_H_ + +#include <stdbool.h> +#include "nvim/ops.h" +#include "nvim/map.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 index); + +yankreg_T* yankmap_put(yankmap_T* yankmap, int index); + +int yankmap_find(yankmap_T* yankmap, yankreg_T* yankreg); + +#endif diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index 288c2a214f..61abe341c7 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -859,13 +859,14 @@ describe('CursorLine and CursorLineNr highlights', function() | ]]) + command('set fillchars=colorcol:.') command('set colorcolumn=3') feed('i <esc>') screen:expect([[ - {1:{} {7: } | + {1:{} {7:.} | "{2:a}{7:"} : {3:abc} {3:// 10;} | - {1:}} {7: } | - {5: ^ }{7: }{5: }| + {1:}} {7:.} | + {5: ^ }{7:.}{5: }| | ]]) end) |