mirror of
https://github.com/neovim/neovim.git
synced 2026-07-15 22:00:40 +00:00
feat(marks): :marks completion, list extmarks #40743
Problem: - No UI for listing extmarks. - `:marks` doesn't support cmdline-completion. Solution: - Add completion and extmarks support to `:marks`. - Update `get_buf_local_marks` to include the ":" (prompt-buffer) mark.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -213,6 +213,8 @@ EDITOR
|
||||
• The |cmdwin-char| is shown via 'statuscolumn'.
|
||||
• |gf| and |<cfile>| 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|).
|
||||
|
||||
@@ -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 "'"
|
||||
|
||||
172
runtime/lua/vim/_core/marks.lua
Normal file
172
runtime/lua/vim/_core/marks.lua
Normal file
@@ -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<string,{mark:string, pos:integer[], file?:string}>
|
||||
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
|
||||
@@ -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:
|
||||
---
|
||||
|
||||
22
runtime/lua/vim/_meta/api.gen.lua
generated
22
runtime/lua/vim/_meta/api.gen.lua
generated
@@ -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:
|
||||
---
|
||||
|
||||
3
runtime/lua/vim/_meta/vimfn.gen.lua
generated
3
runtime/lua/vim/_meta/vimfn.gen.lua
generated
@@ -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 "'"
|
||||
|
||||
@@ -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:
|
||||
///
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -133,6 +133,7 @@ enum {
|
||||
EXPAND_LOG,
|
||||
EXPAND_PACKDEL,
|
||||
EXPAND_PACKUPDATE,
|
||||
EXPAND_MARKS,
|
||||
};
|
||||
|
||||
/// Type used by ExpandGeneric()
|
||||
|
||||
@@ -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 "'"
|
||||
|
||||
103
src/nvim/mark.c
103
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);
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
62
test/functional/ex_cmds/marks_spec.lua
Normal file
62
test/functional/ex_cmds/marks_spec.lua
Normal file
@@ -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<Esc>')
|
||||
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)
|
||||
Reference in New Issue
Block a user