diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 17f7e6112a..bcdb12fafd 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -3177,16 +3177,15 @@ nvim_buf_get_extmarks({buf}, {ns_id}, {start}, {end}, {opts}) If `end` is less than `start`, marks are returned in reverse order. (Useful with `limit`, to get the first marks prior to a given position.) - Note: For a reverse range, `limit` does not actually affect the traversed - range, just how many marks are returned - - Note: when using extmark ranges (marks with a end_row/end_col position) - the `overlap` option might be useful. Otherwise only the start position of - an extmark will be considered. - - Note: legacy signs placed through the |:sign| commands are implemented as - extmarks and will show up here. Their details array will contain a - `sign_name` field. + Note: + • For a reverse range, `limit` does not actually affect the traversed + range, just how many marks are returned + • When using extmark ranges (marks with a end_row/end_col position) the + `overlap` option might be useful. Otherwise only the start position of + an extmark will be considered. + • The |:marks| command can list extmarks. + • Legacy signs placed through the |:sign| commands are implemented as + extmarks. Their details array will contain a `sign_name` field. Example: >lua local api = vim.api diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index b3a3088d6f..a72a8212a6 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -1575,6 +1575,8 @@ vim.show_pos({buf}, {row}, {col}, {filter}) *vim.show_pos()* Can also be shown with `:Inspect`. *:Inspect* + See also |:marks| to list all extmarks. + Example: To bind this function to the vim-scriptease inspired `zS` in Normal mode: >lua vim.keymap.set('n', 'zS', vim.show_pos) diff --git a/runtime/doc/motion.txt b/runtime/doc/motion.txt index 147399bcbc..1b4daa36e2 100644 --- a/runtime/doc/motion.txt +++ b/runtime/doc/motion.txt @@ -877,7 +877,9 @@ g'{mark} g`{mark} See also |:keepjumps|. *:marks* -:marks List all the current marks (not a motion command). +:marks List all marks, and |extmarks| namespaces (use ":marks + {ns}" to list them). + The |'(|, |')|, |'{| and |'}| marks are not listed. The first column has number zero. *E283* @@ -885,7 +887,10 @@ g'{mark} g`{mark} motion command). For example: > :marks aB < to list marks 'a' and 'B'. - + If {arg} is |extmark| |namespace|, the extmarks for + current buffer are listed: > + :marks nvim.multicursor +< *:delm* *:delmarks* :delm[arks] {marks} Delete the specified marks. Marks that can be deleted include A-Z and 0-9. You cannot delete the ' mark. diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 4ee40a9218..eed1f8deca 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -213,6 +213,8 @@ EDITOR • The |cmdwin-char| is shown via 'statuscolumn'. • |gf| and || support `file://…` URIs. • |:log| opens log files. +• |:marks| can list |extmarks| for a given |namespace|. +• |:marks| gained tab-completion. • |:restart| saves/restores the current session (window layout, buffers, …). • |:restart!| (with a bang "!") does not save/restore the session. • |ZR| restarts Nvim (|:restart|). diff --git a/runtime/doc/vimfn.txt b/runtime/doc/vimfn.txt index 538cac7dac..c0915ee20d 100644 --- a/runtime/doc/vimfn.txt +++ b/runtime/doc/vimfn.txt @@ -3954,7 +3954,8 @@ getmarklist([{buf}]) *getmarklist()* If the optional {buf} argument is specified, returns the local marks defined in buffer {buf}. For the use of {buf}, see |bufname()|. If {buf} is invalid, an empty list is - returned. + returned. For a |prompt-buffer| the result includes the + |':| mark. Each item in the returned List is a |Dict| with the following: mark name of the mark prefixed by "'" diff --git a/runtime/lua/vim/_core/marks.lua b/runtime/lua/vim/_core/marks.lua new file mode 100644 index 0000000000..63acbad83f --- /dev/null +++ b/runtime/lua/vim/_core/marks.lua @@ -0,0 +1,172 @@ +--- Marks functionality +--- - |:marks| command/completion + +local api = vim.api +local util = require('vim._core.util') +local N_ = vim.fn.gettext + +local M = {} + +--- Names of the namespaces that have extmarks in the current buffer, sorted. +--- @return string[] +local function buf_namespaces() + local names = {} ---@type string[] + for name, id in pairs(api.nvim_get_namespaces()) do + if #api.nvim_buf_get_extmarks(0, id, 0, -1, { limit = 1 }) > 0 then + names[#names + 1] = name + end + end + table.sort(names) + return names +end + +--- Completion for ":marks {arg}": +--- - set marks (buffer-local and global) +--- - extmark namespaces (buffer-local) +--- @return string[] +function M.complete() + local names = {} ---@type string[] + for _, list in ipairs({ vim.fn.getmarklist(api.nvim_get_current_buf()), vim.fn.getmarklist() }) do + for _, m in ipairs(list) do + names[#names + 1] = m.mark:sub(2) -- "'a" => "a" + end + end + table.sort(names) + return vim.list_extend(names, buf_namespaces()) +end + +--- ":marks {ns}": lists the extmarks of namespace {ns} in the current buffer. +--- @param ns string +--- @return boolean handled false: {ns} is not a namespace (it is a set of mark names) +function M.show(ns) + local id = api.nvim_get_namespaces()[ns] + if not id then + return false + end + local extmarks = api.nvim_buf_get_extmarks(0, id, 0, -1, {}) + if #extmarks == 0 then + api.nvim_echo({ { ('No extmarks in this buffer for namespace "%s"'):format(ns) } }, false, {}) + return true + end + local chunks = { { ('%6s %5s %4s %s'):format('id', 'line', 'col', 'text'), 'Title' } } + for _, m in ipairs(extmarks) do + local text = api.nvim_buf_get_lines(0, m[2], m[2] + 1, false)[1] or '' + chunks[#chunks + 1] = { ('\n%6d %5d %4d %s'):format(m[1], m[2] + 1, m[3], vim.trim(text)) } + end + api.nvim_echo(chunks, false, { kind = 'list_cmd' }) + return true +end + +--- The line at mark line {lnum}, sans leading whitespace, truncated to fit in the window +--- (cells), like C mark_line(). +--- @param lnum integer +--- @return string +local function mark_line(lnum) + if lnum > api.nvim_buf_line_count(0) then + return '-invalid-' + end + local text = api.nvim_buf_get_lines(0, lnum - 1, lnum, false)[1]:gsub('^%s+', '') + local limit = vim.o.columns - 15 + if vim.fn.strdisplaywidth(text) < limit then + return text + end + local keep = {} ---@type string[] + local width = 0 + for _, c in ipairs(vim.fn.split(text, [[\zs]])) do + width = width + vim.fn.strdisplaywidth(c) + if width >= limit then + break + end + keep[#keep + 1] = c + end + return table.concat(keep) +end + +--- ":marks [arg]" (|ex_cmds.lua| "marks" => C ex_marks() => this). +--- @param args vim._core.ExCmdArgs +function M.ex_marks(args) + local arg = args.args ~= '' and args.args or nil + + -- ":marks {ns}": list namespace's extmarks, or fallthrough if unknown. + if arg and #arg > 1 and M.show(arg) then + return + end + + local curbuf = api.nvim_get_current_buf() + local marks = {} ---@type table + for _, list in ipairs({ vim.fn.getmarklist(curbuf), vim.fn.getmarklist() }) do + for _, m in ipairs(list) do + marks[m.mark:sub(2)] = m + end + end + + -- "<" and ">" are shown as where they will jump to: start before end. + local vs, ve = marks['<'], marks['>'] + if vs and ve then + local s, e = vs.pos, ve.pos + if s[2] > e[2] or (s[2] == e[2] and s[3] > e[3]) then + vs.pos, ve.pos = e, s + end + elseif ve and not vs then + marks['<'], marks['>'] = ve, nil + end + + --- The "file/text" column: the text at the mark if it is in the current buffer (highlighted + --- as "Directory", like C show_one_mark()), else the file name. + --- @param m {pos:integer[], file?:string} + --- @return string text, string? hl + local function displayname(m) + if m.pos[1] == curbuf then + return mark_line(m.pos[2]), 'Directory' + elseif m.pos[1] == 0 then + return m.file or '', nil + end + return vim.fn.fnamemodify(vim.fn.bufname(m.pos[1]), ':~'), nil + end + + -- Fixed :marks order. + local order = { "'" } + for _, range in ipairs({ { 'a', 'z' }, { 'A', 'Z' }, { '0', '9' } }) do + for b = range[1]:byte(), range[2]:byte() do + order[#order + 1] = string.char(b) + end + end + vim.list_extend(order, { '"', '[', ']', '^', '.', ':', '<', '>' }) + + local filtered = require('vim._core.ex_cmd').filter + local chunks = {} ---@type [string, string?][] + for _, name in ipairs(order) do + local m = marks[name] + if m and (not arg or arg:find(name, 1, true)) then + local text, hl = displayname(m) + if not filtered(args.smods.filter, text) then + chunks[#chunks + 1] = { ('\n %s %6d %4d '):format(name, m.pos[2], m.pos[3] - 1) } + if text ~= '' then + chunks[#chunks + 1] = { text, hl } + end + end + end + end + + if #chunks == 0 then + if arg then + util.echo_err(N_('E283: No marks matching "%s"'):format(arg)) + return + end + api.nvim_echo({ { N_('No marks set') } }, false, {}) + else + table.insert(chunks, 1, { N_('\nmark line col file/text'), 'Title' }) + api.nvim_echo(chunks, false, { kind = 'list_cmd' }) + end + + if not arg then + -- By default, list extmarks namespaces (but not their extmarks). + local names = buf_namespaces() + if #names > 0 then + local msg = ('Extmark namespaces (use ":marks {ns}"): %s'):format(table.concat(names, ', ')) + api.nvim_echo({ { msg } }, false, {}) + end + end +end + +return M diff --git a/runtime/lua/vim/_inspector.lua b/runtime/lua/vim/_inspector.lua index 5a39006e9c..2ae357a700 100644 --- a/runtime/lua/vim/_inspector.lua +++ b/runtime/lua/vim/_inspector.lua @@ -151,6 +151,8 @@ end --- ---Can also be shown with `:Inspect`. [:Inspect]() --- +---See also |:marks| to list all extmarks. +--- ---Example: To bind this function to the vim-scriptease ---inspired `zS` in Normal mode: --- diff --git a/runtime/lua/vim/_meta/api.gen.lua b/runtime/lua/vim/_meta/api.gen.lua index d5208ce596..88ffde519a 100644 --- a/runtime/lua/vim/_meta/api.gen.lua +++ b/runtime/lua/vim/_meta/api.gen.lua @@ -409,8 +409,8 @@ function vim.api.nvim_buf_get_commands(buf, opts) end --- text span range is deleted. See also the key `invalidate` in |nvim_buf_set_extmark()|. function vim.api.nvim_buf_get_extmark_by_id(buf, ns_id, id, opts) end ---- Gets `extmarks` in "traversal order" from a `charwise` region defined by ---- buffer positions (inclusive, 0-indexed `api-indexing`). +--- Gets `extmarks` in "traversal order" from a `charwise` region defined by buffer positions +--- (inclusive, 0-indexed `api-indexing`). --- --- Region can be given as (row,col) tuples, or valid extmark ids (whose --- positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1) @@ -424,16 +424,14 @@ function vim.api.nvim_buf_get_extmark_by_id(buf, ns_id, id, opts) end --- If `end` is less than `start`, marks are returned in reverse order. --- (Useful with `limit`, to get the first marks prior to a given position.) --- ---- Note: For a reverse range, `limit` does not actually affect the traversed ---- range, just how many marks are returned ---- ---- Note: when using extmark ranges (marks with a end_row/end_col position) ---- the `overlap` option might be useful. Otherwise only the start position ---- of an extmark will be considered. ---- ---- Note: legacy signs placed through the `:sign` commands are implemented ---- as extmarks and will show up here. Their details array will contain a ---- `sign_name` field. +--- Note: +--- - For a reverse range, `limit` does not actually affect the traversed range, just how many marks +--- are returned +--- - When using extmark ranges (marks with a end_row/end_col position) the `overlap` option might +--- be useful. Otherwise only the start position of an extmark will be considered. +--- - The `:marks` command can list extmarks. +--- - Legacy signs placed through the `:sign` commands are implemented as extmarks. Their details +--- array will contain a `sign_name` field. --- --- Example: --- diff --git a/runtime/lua/vim/_meta/vimfn.gen.lua b/runtime/lua/vim/_meta/vimfn.gen.lua index 5fa3910c0a..252425d2fe 100644 --- a/runtime/lua/vim/_meta/vimfn.gen.lua +++ b/runtime/lua/vim/_meta/vimfn.gen.lua @@ -3539,7 +3539,8 @@ function vim.fn.getloclist(nr, what) end --- If the optional {buf} argument is specified, returns the --- local marks defined in buffer {buf}. For the use of {buf}, --- see |bufname()|. If {buf} is invalid, an empty list is ---- returned. +--- returned. For a |prompt-buffer| the result includes the +--- |':| mark. --- --- Each item in the returned List is a |Dict| with the following: --- mark name of the mark prefixed by "'" diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 246eb9fd88..be16ea3470 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -232,8 +232,8 @@ nvim_buf_get_extmark_by_id(Buffer buf, Integer ns_id, Integer id, Dict(get_extma return extmark_to_array(extmark, false, details, hl_name, arena); } -/// Gets |extmarks| in "traversal order" from a |charwise| region defined by -/// buffer positions (inclusive, 0-indexed |api-indexing|). +/// Gets |extmarks| in "traversal order" from a |charwise| region defined by buffer positions +/// (inclusive, 0-indexed |api-indexing|). /// /// Region can be given as (row,col) tuples, or valid extmark ids (whose /// positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1) @@ -247,16 +247,14 @@ nvim_buf_get_extmark_by_id(Buffer buf, Integer ns_id, Integer id, Dict(get_extma /// If `end` is less than `start`, marks are returned in reverse order. /// (Useful with `limit`, to get the first marks prior to a given position.) /// -/// Note: For a reverse range, `limit` does not actually affect the traversed -/// range, just how many marks are returned -/// -/// Note: when using extmark ranges (marks with a end_row/end_col position) -/// the `overlap` option might be useful. Otherwise only the start position -/// of an extmark will be considered. -/// -/// Note: legacy signs placed through the |:sign| commands are implemented -/// as extmarks and will show up here. Their details array will contain a -/// `sign_name` field. +/// Note: +/// - For a reverse range, `limit` does not actually affect the traversed range, just how many marks +/// are returned +/// - When using extmark ranges (marks with a end_row/end_col position) the `overlap` option might +/// be useful. Otherwise only the start position of an extmark will be considered. +/// - The |:marks| command can list extmarks. +/// - Legacy signs placed through the |:sign| commands are implemented as extmarks. Their details +/// array will contain a `sign_name` field. /// /// Example: /// diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index 01e2eb6d9e..b37c40155d 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -1337,6 +1337,7 @@ char *addstar(char *fname, size_t len, int context) || context == EXPAND_LOG || context == EXPAND_PACKDEL || context == EXPAND_PACKUPDATE + || context == EXPAND_MARKS || context == EXPAND_LUA) { retval = xstrnsave(fname, len); } else { @@ -2352,6 +2353,10 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa xp->xp_context = EXPAND_CHECKHEALTH; break; + case CMD_marks: + xp->xp_context = EXPAND_MARKS; + break; + case CMD_log: xp->xp_context = EXPAND_LOG; break; @@ -2940,9 +2945,15 @@ static char *get_arg1_from_lua(char *lua, expand_T *xp, int idx) return NULL; } -/// Completion for |:log| command. -/// -/// Given to ExpandGeneric() to obtain `:log` completion. +/// Completion for the |:marks| command. Given to ExpandGeneric(). +/// @param[in] xp Expandy thing 🤷 +/// @param[in] idx Index of the item. +static char *get_marks_arg(expand_T *xp, int idx) +{ + return get_arg1_from_lua("return require'vim._core.marks'.complete(...)", xp, idx); +} + +/// Completion for |:log| command. Given to ExpandGeneric(). /// @param[in] xp Expandy thing 🤷 /// @param[in] idx Index of the item. static char *get_log_arg(expand_T *xp, int idx) @@ -2950,9 +2961,7 @@ static char *get_log_arg(expand_T *xp, int idx) return get_arg1_from_lua("return require'vim._core.ex_cmd'.log_complete(...)", xp, idx); } -/// Completion for |:lsp| command. -/// -/// Given to ExpandGeneric() to obtain `:lsp` completion. +/// Completion for |:lsp| command. Given to ExpandGeneric(). /// @param[in] xp Expandy thing 🤷 /// @param[in] idx Index of the item. static char *get_lsp_arg(expand_T *xp, int idx) @@ -2960,9 +2969,7 @@ static char *get_lsp_arg(expand_T *xp, int idx) return get_arg1_from_lua("return require'vim._core.ex_cmd'.lsp_complete(...)", xp, idx); } -/// Completion for |:packdel| command. -/// -/// Given to ExpandGeneric() to obtain `:packdel` completion. +/// Completion for |:packdel| command. Given to ExpandGeneric(). /// @param[in] xp Expandy thing. /// @param[in] idx Index of the item. static char *get_packdel_arg(expand_T *xp, int idx) @@ -2970,9 +2977,7 @@ static char *get_packdel_arg(expand_T *xp, int idx) return get_arg1_from_lua("return require'vim._core.ex_cmd'.packdel_complete(...)", xp, idx); } -/// Completion for |:packupdate| command. -/// -/// Given to ExpandGeneric() to obtain `:packupdate` completion. +/// Completion for |:packupdate| command. Given to ExpandGeneric(). /// @param[in] xp Expandy thing. /// @param[in] idx Index of the item. static char *get_packupdate_arg(expand_T *xp, int idx) @@ -3022,6 +3027,7 @@ static int ExpandOther(char *pat, expand_T *xp, regmatch_T *rmp, char ***matches { EXPAND_SCRIPTNAMES, get_scriptnames_arg, true, false }, { EXPAND_RETAB, get_retab_arg, true, true }, { EXPAND_CHECKHEALTH, get_healthcheck_names, true, false }, + { EXPAND_MARKS, get_marks_arg, true, false }, { EXPAND_LOG, get_log_arg, true, false }, { EXPAND_LSP, get_lsp_arg, true, false }, { EXPAND_PACKDEL, get_packdel_arg, true, false }, diff --git a/src/nvim/cmdexpand_defs.h b/src/nvim/cmdexpand_defs.h index cc7e9dd595..9fee7fd06f 100644 --- a/src/nvim/cmdexpand_defs.h +++ b/src/nvim/cmdexpand_defs.h @@ -133,6 +133,7 @@ enum { EXPAND_LOG, EXPAND_PACKDEL, EXPAND_PACKUPDATE, + EXPAND_MARKS, }; /// Type used by ExpandGeneric() diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index c423f3b91d..a07e3be0c3 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -4365,7 +4365,8 @@ M.funcs = { If the optional {buf} argument is specified, returns the local marks defined in buffer {buf}. For the use of {buf}, see |bufname()|. If {buf} is invalid, an empty list is - returned. + returned. For a |prompt-buffer| the result includes the + |':| mark. Each item in the returned List is a |Dict| with the following: mark name of the mark prefixed by "'" diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 95139c58e7..99d24be8f5 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -19,6 +19,7 @@ #include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds_defs.h" +#include "nvim/ex_docmd.h" #include "nvim/extmark.h" #include "nvim/extmark_defs.h" #include "nvim/fold.h" @@ -26,6 +27,7 @@ #include "nvim/globals.h" #include "nvim/highlight_defs.h" #include "nvim/insert.h" +#include "nvim/lua/executor.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" @@ -909,104 +911,10 @@ static char *mark_line(pos_T *mp, int lead_len) return s; } -// print the marks +/// ":marks {arg}": list marks, or namespace-extmarks. void ex_marks(exarg_T *eap) { - char *arg = eap->arg; - char *name; - pos_T *posp; - - if (arg != NULL && *arg == NUL) { - arg = NULL; - } - - msg_ext_set_kind("list_cmd"); - show_one_mark('\'', arg, &curwin->w_pcmark, NULL, true); - for (int i = 0; i < NMARKS; i++) { - show_one_mark(i + 'a', arg, &curbuf->b_namedm[i].mark, NULL, true); - } - for (int i = 0; i < NGLOBALMARKS; i++) { - if (namedfm[i].fmark.fnum != 0) { - name = fm_getname(&namedfm[i].fmark, 15); - } else { - name = namedfm[i].fname; - } - if (name != NULL) { - show_one_mark(i >= NMARKS ? i - NMARKS + '0' : i + 'A', - arg, &namedfm[i].fmark.mark, name, - namedfm[i].fmark.fnum == curbuf->b_fnum); - if (namedfm[i].fmark.fnum != 0) { - xfree(name); - } - } - } - show_one_mark('"', arg, &curbuf->b_last_cursor.mark, NULL, true); - show_one_mark('[', arg, &curbuf->b_op_start, NULL, true); - show_one_mark(']', arg, &curbuf->b_op_end, NULL, true); - show_one_mark('^', arg, &curbuf->b_last_insert.mark, NULL, true); - show_one_mark('.', arg, &curbuf->b_last_change.mark, NULL, true); - if (bt_prompt(curbuf)) { - show_one_mark(':', arg, &curbuf->b_prompt_start.mark, NULL, true); - } - - // Show the marks as where they will jump to. - pos_T *startp = &curbuf->b_visual.vi_start; - pos_T *endp = &curbuf->b_visual.vi_end; - if ((lt(*startp, *endp) || endp->lnum == 0) && startp->lnum != 0) { - posp = startp; - } else { - posp = endp; - } - show_one_mark('<', arg, posp, NULL, true); - show_one_mark('>', arg, posp == startp ? endp : startp, NULL, true); - - show_one_mark(-1, arg, NULL, NULL, false); -} - -/// @param current in current file -static void show_one_mark(int c, char *arg, pos_T *p, char *name_arg, int current) -{ - static bool did_title = false; - bool mustfree = false; - char *name = name_arg; - - if (c == -1) { // finish up - if (did_title) { - did_title = false; - } else { - if (arg == NULL) { - msg(_("No marks set"), 0); - } else { - semsg(_("E283: No marks matching \"%s\""), arg); - } - } - } else if (!got_int - && (arg == NULL || vim_strchr(arg, c) != NULL) - && p->lnum != 0) { - // don't output anything if 'q' typed at --more-- prompt - if (name == NULL && current) { - name = mark_line(p, 15); - mustfree = true; - } - if (!message_filtered(name)) { - if (!did_title) { - // Highlight title - msg_puts_title(_("\nmark line col file/text")); - did_title = true; - } - msg_putchar('\n'); - if (!got_int) { - snprintf(IObuff, IOSIZE, " %c %6" PRIdLINENR " %4d ", c, p->lnum, p->col); - msg_outtrans(IObuff, 0, false); - if (name != NULL) { - msg_outtrans(name, current ? HLF_D : 0, false); - } - } - } - if (mustfree) { - xfree(name); - } - } + nlua_call_excmd("vim._core.marks", "ex_marks", eap, &cmdmod, NULL); } // ":delmarks[!] [marks]" @@ -1963,6 +1871,9 @@ void get_buf_local_marks(const buf_T *buf, list_T *l) add_mark(l, S_LEN("']"), &buf->b_op_end, buf->b_fnum, NULL); add_mark(l, S_LEN("'^"), &buf->b_last_insert.mark, buf->b_fnum, NULL); add_mark(l, S_LEN("'."), &buf->b_last_change.mark, buf->b_fnum, NULL); + if (bt_prompt((buf_T *)buf)) { + add_mark(l, S_LEN("':"), &buf->b_prompt_start.mark, buf->b_fnum, NULL); + } add_mark(l, S_LEN("'<"), &buf->b_visual.vi_start, buf->b_fnum, NULL); add_mark(l, S_LEN("'>"), &buf->b_visual.vi_end, buf->b_fnum, NULL); } diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua index 3041b82abb..d185156d7d 100644 --- a/test/functional/core/main_spec.lua +++ b/test/functional/core/main_spec.lua @@ -205,6 +205,7 @@ describe('vim._core', function() 'vim._core.exrc', 'vim._core.help', 'vim._core.log', + 'vim._core.marks', 'vim._core.options', 'vim._core.proc', 'vim._core.server', diff --git a/test/functional/ex_cmds/marks_spec.lua b/test/functional/ex_cmds/marks_spec.lua new file mode 100644 index 0000000000..b620c2f038 --- /dev/null +++ b/test/functional/ex_cmds/marks_spec.lua @@ -0,0 +1,62 @@ +local t = require('test.testutil') +local n = require('test.functional.testnvim')() + +local command = n.command +local eq = t.eq +local exec_capture = n.exec_capture +local exec_lua = n.exec_lua +local feed = n.feed +local fn = n.fn +local matches = t.matches + +describe(':marks', function() + before_each(function() + n.clear {} + fn.setline(1, { 'one', 'two', 'three' }) + feed('ma') + exec_lua(function() + local ns = vim.api.nvim_create_namespace('my.plugin') + vim.api.nvim_buf_set_extmark(0, ns, 1, 0, {}) + vim.api.nvim_buf_set_extmark(0, ns, 2, 1, {}) + vim.api.nvim_create_namespace('empty.ns') -- no extmarks: not completed, not mentioned + end) + end) + + it('completes names, lists extmarks', function() + -- Completion offers the set marks and the buffer's extmark namespaces. + eq({ '"', "'", '.', 'a', 'my.plugin' }, fn.getcompletion('marks ', 'cmdline')) + -- :marks (no args) only mentions the namespaces instead of listing their extmarks. + local out = exec_capture('marks') + matches('mark line col file/text', out) + matches('Extmark namespaces %(use ":marks {ns}"%): my%.plugin', out) + eq(nil, out:find('empty%.ns')) + -- ":marks {ns}" lists that namespace's extmarks. + out = exec_capture('marks my.plugin') + matches('id%s+line%s+col text', out) + matches('1%s+2%s+0 two', out) + matches('2%s+3%s+1 three', out) + -- Not a namespace: the usual mark-names filter. + matches('mark line col file/text', exec_capture('marks aB')) + end) + + it(':filter on file/text column; E283; "No marks set"', function() + fn.setpos("'b", { 0, 2, 1, 0 }) + local out = exec_capture('filter /two/ marks') + matches(' b%s+2%s+0 two', out) + eq(nil, out:find(' a ', 1, true)) -- mark a is on "one": filtered out + matches('E283: No marks matching "z"', t.pcall_err(command, 'marks z')) + -- Bare :marks with every row filtered out. + matches('No marks set', exec_capture('filter /xxx-nomatch/ marks')) + end) + + it("prompt buffer: ':' mark in getmarklist() and :marks", function() + command('enew | set buftype=prompt') + feed('ifoo') + local found = false + for _, m in ipairs(fn.getmarklist(fn.bufnr(''))) do + found = found or m.mark == "':" + end + eq(true, found) + matches(' :%s+%d+%s+%d+ ', exec_capture('marks')) + end) +end)