From 8436383af96dc7afa3596fc22c012d68e76f47f8 Mon Sep 17 00:00:00 2001 From: Josh Rahm Date: Sun, 21 Aug 2022 18:49:46 -0600 Subject: feat(usermark); implement "user marks", i.e. marks whose behavior can be defined by the user. (Neo)vim has many different marks defined, but sometimes this may not be completely adequate. This change give the user the ability to define behavior for marks which are not built in to (Neo)vim directly. This is accomplished through a new option called the "usermarkfunc." The usermarkfunc points to a vimscript function that takes an "action" paramter (either "get" or "set") and a mark name. a basic implementation that re-implements global mark behavior for user marks would look something like: let s:marks = {} function UserMarkFunc(action, mark) if a:action == "set" let [n, lnum, col, off, curswant] = getcurpos() let s:marks[a:mark] = \ { "line": lnum, "col": col, "file": expand("%:p") } else return s:marks[a:mark] endif endfunction set usermarkfunc=UserMarkFunc of course the user could make the behavior be whatever. It should also be noted that any valid unicode character can now be a mark. It is not just limited to ASCII characters. --- src/nvim/buffer_defs.h | 1 + src/nvim/ex_docmd.c | 8 +-- src/nvim/map.c | 1 + src/nvim/map.h | 1 + src/nvim/mark.c | 159 ++++++++++++++++++++++++++++++++++++++++++++++++- src/nvim/option.c | 5 ++ src/nvim/option_defs.h | 1 + src/nvim/options.lua | 7 +++ 8 files changed, 178 insertions(+), 5 deletions(-) diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 3ef5ea7e02..9457051947 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -712,6 +712,7 @@ struct file_buffer { char_u *b_p_cfu; ///< 'completefunc' char_u *b_p_ofu; ///< 'omnifunc' char_u *b_p_tfu; ///< 'tagfunc' + char_u *b_p_umf; ///< 'usermarkfunc' int b_p_eol; ///< 'endofline' int b_p_fixeol; ///< 'fixendofline' int b_p_et; ///< 'expandtab' diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 4ac9847e53..0dde82c235 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -4017,7 +4017,7 @@ char *skip_range(const char *cmd, int *ctx) } } if (*cmd != NUL) { - ++cmd; + cmd += utf_ptr2len(cmd); } } @@ -4162,13 +4162,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 d3058a5d52..907c1d5996 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -163,6 +163,7 @@ static inline bool ColorKey_eq(ColorKey ae1, ColorKey ae2) return memcmp(&ae1, &ae2, sizeof(ae1)) == 0; } +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 845daac3f7..92c9ebcc34 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -34,6 +34,7 @@ // // 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(int, cstr_t) MAP_DECLS(cstr_t, ptr_t) diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 1fe3327b29..344d55a473 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -17,11 +17,13 @@ #include "nvim/diff.h" #include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" #include "nvim/ex_cmds.h" #include "nvim/extmark.h" #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/mark.h" +#include "nvim/mark_defs.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" @@ -39,6 +41,8 @@ #include "nvim/strings.h" #include "nvim/ui.h" #include "nvim/vim.h" +#include "nvim/map.h" +#include "nvim/eval/userfunc.h" /* * This file contains routines to maintain and manipulate marks. @@ -55,6 +59,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 @@ -164,7 +194,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); } /* @@ -349,6 +380,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; @@ -437,6 +477,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.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/option.c b/src/nvim/option.c index 12c5889703..4958c10220 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -142,6 +142,7 @@ static char_u *p_cpt; static char_u *p_cfu; static char_u *p_ofu; static char_u *p_tfu; +static char_u *p_umf; static int p_eol; static int p_fixeol; static int p_et; @@ -6273,6 +6274,8 @@ static char_u *get_varp(vimoption_T *p) return (char_u *)&(curbuf->b_p_sw); case PV_TFU: return (char_u *)&(curbuf->b_p_tfu); + case PV_UMF: + return (char_u *)&(curbuf->b_p_umf); case PV_TS: return (char_u *)&(curbuf->b_p_ts); case PV_TW: @@ -6596,6 +6599,8 @@ void buf_copy_options(buf_T *buf, int flags) COPY_OPT_SCTX(buf, BV_OFU); buf->b_p_tfu = vim_strsave(p_tfu); COPY_OPT_SCTX(buf, BV_TFU); + buf->b_p_umf = vim_strsave(p_umf); + COPY_OPT_SCTX(buf, BV_UMF); buf->b_p_sts = p_sts; COPY_OPT_SCTX(buf, BV_STS); buf->b_p_sts_nopaste = p_sts_nopaste; diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 237288fbad..92ab7489bb 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -838,6 +838,7 @@ enum { BV_SW, BV_SWF, BV_TFU, + BV_UMF, BV_TSRFU, BV_TAGS, BV_TC, diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 2f941f5d0c..5e4c73a5db 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2690,6 +2690,13 @@ return { varname='p_ut', 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='varsofttabstop', abbreviation='vsts', short_desc=N_("list of numbers of spaces that uses while editing"), -- cgit From 0eef922692e9061d130d72c00e51bf023b234142 Mon Sep 17 00:00:00 2001 From: Josh Rahm Date: Sun, 21 Aug 2022 23:29:47 -0600 Subject: feat(usermarks): add usermarks to f_has list --- src/nvim/eval/funcs.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 9c3c859771..9f851653eb 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -3660,6 +3660,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) "title", "user-commands", // was accidentally included in 5.4 "user_commands", + "usermarks", "vartabs", "vertsplit", "vimscript-1", -- cgit From 692ea0603a9fd20be5ce61293dd85ff8bb31ad26 Mon Sep 17 00:00:00 2001 From: Josh Rahm Date: Sun, 21 Aug 2022 23:43:52 -0600 Subject: feat(usermarks): add runtime files for usermarks --- runtime/autoload/usermark.vim | 7 +++++ runtime/lua/vim/usermark.lua | 65 +++++++++++++++++++++++++++++++++++++++++++ runtime/plugin/usermark.vim | 1 + 3 files changed, 73 insertions(+) create mode 100644 runtime/autoload/usermark.vim create mode 100644 runtime/lua/vim/usermark.lua create mode 100644 runtime/plugin/usermark.vim diff --git a/runtime/autoload/usermark.vim b/runtime/autoload/usermark.vim new file mode 100644 index 0000000000..4e0c56a9ea --- /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! userreg#func(action, mark) abort + return v:lua.vim.usermark.fn(a:action, a:mark) +endfunction diff --git a/runtime/lua/vim/usermark.lua b/runtime/lua/vim/usermark.lua new file mode 100644 index 0000000000..ca5b9a3a72 --- /dev/null +++ b/runtime/lua/vim/usermark.lua @@ -0,0 +1,65 @@ +-- 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, + file: file + } + 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[register] then + usermark._marktable[register] = usermark._default_handler() + end + + if action == "get" then + usermark._marktable[register]:get(mark) + return nil + else + return usermark._marktable[register]:set(mark) + 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(register, handler) + usermark._marktable[register] = handler +end + +return usermark 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 -- cgit From 323eef76ee869dab24a925f5df8a7aefac5e3597 Mon Sep 17 00:00:00 2001 From: Josh Rahm Date: Mon, 22 Aug 2022 00:08:04 -0600 Subject: feat(usermarks) fix bugs with usermarks runtime --- runtime/autoload/usermark.vim | 8 ++++++-- runtime/lua/vim/usermark.lua | 25 ++++++++++++++----------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/runtime/autoload/usermark.vim b/runtime/autoload/usermark.vim index 4e0c56a9ea..02c76d5ffd 100644 --- a/runtime/autoload/usermark.vim +++ b/runtime/autoload/usermark.vim @@ -2,6 +2,10 @@ lua vim.usermark = require('vim.usermark') -function! userreg#func(action, mark) abort - return v:lua.vim.usermark.fn(a:action, a:mark) +function! usermark#func(action, mark) abort + let ret = v:lua.vim.usermark.fn(a:action, a:mark) + + echo "Returning: " . string(ret) + + return ret endfunction diff --git a/runtime/lua/vim/usermark.lua b/runtime/lua/vim/usermark.lua index ca5b9a3a72..0d1ec0ae0f 100644 --- a/runtime/lua/vim/usermark.lua +++ b/runtime/lua/vim/usermark.lua @@ -4,7 +4,7 @@ -- -- The default handler behaves just as a normal register would. -local vim = assert("vim") +local vim = assert(vim) local usermark = {} -- Returns a "default handler" which behaves like normal global marks. When a @@ -26,10 +26,13 @@ function usermark._default_handler() local file = vim.fn.expand("%:p") self.content = { - line: r, - col: c, - file: file + line = r; + col = c; } + + if file ~= '' then + self.content.file = file + end end return d @@ -44,22 +47,22 @@ usermark._marktable = {} -- 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[register] then - usermark._marktable[register] = usermark._default_handler() + if not usermark._marktable[mark] then + usermark._marktable[mark] = usermark._default_handler() end if action == "get" then - usermark._marktable[register]:get(mark) - return nil + return usermark._marktable[mark]:get(mark) else - return usermark._marktable[register]:set(mark) + 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(register, handler) - usermark._marktable[register] = handler +function usermark.register_handler(mark, handler) + usermark._marktable[mark] = handler end return usermark -- cgit From 9e40b6e9e1bc67f2d856adb837ee64dd0e25b717 Mon Sep 17 00:00:00 2001 From: Josh Rahm Date: Mon, 22 Aug 2022 00:26:23 -0600 Subject: feat(usermarks) remove echo statement from usermark.vim --- runtime/autoload/usermark.vim | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/runtime/autoload/usermark.vim b/runtime/autoload/usermark.vim index 02c76d5ffd..b1b4113d1a 100644 --- a/runtime/autoload/usermark.vim +++ b/runtime/autoload/usermark.vim @@ -3,9 +3,5 @@ lua vim.usermark = require('vim.usermark') function! usermark#func(action, mark) abort - let ret = v:lua.vim.usermark.fn(a:action, a:mark) - - echo "Returning: " . string(ret) - - return ret + return v:lua.vim.usermark.fn(a:action, a:mark) endfunction -- cgit