aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJavier Lopez <graulopezjavier@gmail.com>2021-10-05 10:49:20 -0500
committerGitHub <noreply@github.com>2021-10-05 08:49:20 -0700
commit49fdc62114a5f37def3ff60ca0c4d8638903ac24 (patch)
tree8d75845ca18d9dd9c6da261473673445ab0bef27
parent912a6e5a9c58fce74134f9f8c2801373928e8289 (diff)
downloadrneovim-49fdc62114a5f37def3ff60ca0c4d8638903ac24.tar.gz
rneovim-49fdc62114a5f37def3ff60ca0c4d8638903ac24.tar.bz2
rneovim-49fdc62114a5f37def3ff60ca0c4d8638903ac24.zip
feat(api): named marks set, get, delete #15346
Adds the following API functions. - nvim_buf_set_mark(buf, name, line, col) * Set marks in a buffer. - nvim_buf_del_mark(buf, name) * Delete a mark that belongs to buffer. - nvim_del_mark(name) * Delete a global mark. - nvim_get_mark(name) * Get a global mark. Tests: - Adds test to all the new api functions, and adds more for the existing nvim_buf_get_mark. * Tests include failure cases. Documentation: - Adds documentation for all the new functions, and improves the existing fucntion docs.
-rw-r--r--runtime/doc/api.txt92
-rw-r--r--src/nvim/api/buffer.c87
-rw-r--r--src/nvim/api/private/helpers.c40
-rw-r--r--src/nvim/api/vim.c103
-rw-r--r--src/nvim/mark.c9
-rw-r--r--test/functional/api/buffer_spec.lua55
-rw-r--r--test/functional/api/vim_spec.lua55
7 files changed, 436 insertions, 5 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index a8b4bc7dd2..9178f4978b 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -130,7 +130,9 @@ end of a range, -1 denotes the last line/column.
Exception: the following API functions use "mark-like" indexing (1-based
lines, 0-based columns):
+ |nvim_get_mark()|
|nvim_buf_get_mark()|
+ |nvim_buf_set_mark()|
|nvim_win_get_cursor()|
|nvim_win_set_cursor()|
@@ -735,6 +737,23 @@ nvim_del_keymap({mode}, {lhs}) *nvim_del_keymap()*
See also: ~
|nvim_set_keymap()|
+nvim_del_mark({name}) *nvim_del_mark()*
+ Deletes a uppercase/file named mark. See |mark-motions|.
+
+ Note:
+ fails with error if a lowercase or buffer local named mark
+ is used.
+
+ Parameters: ~
+ {name} Mark name
+
+ Return: ~
+ true if the mark was deleted, else false.
+
+ See also: ~
+ |nvim_buf_del_mark()|
+ |nvim_get_mark()|
+
nvim_del_var({name}) *nvim_del_var()*
Removes a global (g:) variable.
@@ -1008,6 +1027,27 @@ nvim_get_keymap({mode}) *nvim_get_keymap()*
Array of maparg()-like dictionaries describing mappings.
The "buffer" key is always zero.
+nvim_get_mark({name}) *nvim_get_mark()*
+ Return a tuple (row, col, buffer, buffername) representing the
+ position of the uppercase/file named mark. See |mark-motions|.
+
+ Marks are (1,0)-indexed. |api-indexing|
+
+ Note:
+ fails with error if a lowercase or buffer local named mark
+ is used.
+
+ Parameters: ~
+ {name} Mark name
+
+ Return: ~
+ 4-tuple (row, col, buffer, buffername), (0, 0, 0, '') if
+ the mark is not set.
+
+ See also: ~
+ |nvim_buf_set_mark()|
+ |nvim_del_mark()|
+
nvim_get_mode() *nvim_get_mode()*
Gets the current mode. |mode()| "blocking" is true if Nvim is
waiting for input.
@@ -2063,6 +2103,24 @@ nvim_buf_del_keymap({buffer}, {mode}, {lhs}) *nvim_buf_del_keymap()*
See also: ~
|nvim_del_keymap()|
+nvim_buf_del_mark({buffer}, {name}) *nvim_buf_del_mark()*
+ Deletes a named mark in the buffer. See |mark-motions|.
+
+ Note:
+ only deletes marks set in the buffer, if the mark is not
+ set in the buffer it will return false.
+
+ Parameters: ~
+ {buffer} Buffer to set the mark on
+ {name} Mark name
+
+ Return: ~
+ true if the mark was deleted, else false.
+
+ See also: ~
+ |nvim_buf_set_mark()|
+ |nvim_del_mark()|
+
nvim_buf_del_var({buffer}, {name}) *nvim_buf_del_var()*
Removes a buffer-scoped (b:) variable
@@ -2217,8 +2275,8 @@ nvim_buf_get_lines({buffer}, {start}, {end}, {strict_indexing})
Array of lines, or empty array for unloaded buffer.
nvim_buf_get_mark({buffer}, {name}) *nvim_buf_get_mark()*
- Return a tuple (row,col) representing the position of the
- named mark.
+ Returns a tuple (row,col) representing the position of the
+ named mark. See |mark-motions|.
Marks are (1,0)-indexed. |api-indexing|
@@ -2227,7 +2285,12 @@ nvim_buf_get_mark({buffer}, {name}) *nvim_buf_get_mark()*
{name} Mark name
Return: ~
- (row, col) tuple
+ (row, col) tuple, (0, 0) if the mark is not set, or is an
+ uppercase/file mark set in another buffer.
+
+ See also: ~
+ |nvim_buf_set_mark()|
+ |nvim_buf_del_mark()|
nvim_buf_get_name({buffer}) *nvim_buf_get_name()*
Gets the full file name for the buffer
@@ -2435,6 +2498,29 @@ nvim_buf_set_lines({buffer}, {start}, {end}, {strict_indexing}, {replacement})
error.
{replacement} Array of lines to use as replacement
+ *nvim_buf_set_mark()*
+nvim_buf_set_mark({buffer}, {name}, {line}, {col})
+ Sets a named mark in the given buffer, all marks are allowed
+ file/uppercase, visual, last change, etc. See |mark-motions|.
+
+ Marks are (1,0)-indexed. |api-indexing|
+
+ Note:
+ Passing 0 as line deletes the mark
+
+ Parameters: ~
+ {buffer} Buffer to set the mark on
+ {name} Mark name
+ {line} Line number
+ {col} Column/row number
+
+ Return: ~
+ true if the mark was set, else false.
+
+ See also: ~
+ |nvim_buf_del_mark()|
+ |nvim_buf_get_mark()|
+
nvim_buf_set_name({buffer}, {name}) *nvim_buf_set_name()*
Sets the full file name for a buffer
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 34d7f6e32d..3ab7e6b778 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -1108,14 +1108,97 @@ Boolean nvim_buf_is_valid(Buffer buffer)
return ret;
}
-/// Return a tuple (row,col) representing the position of the named mark.
+/// Deletes a named mark in the buffer. See |mark-motions|.
+///
+/// @note only deletes marks set in the buffer, if the mark is not set
+/// in the buffer it will return false.
+/// @param buffer Buffer to set the mark on
+/// @param name Mark name
+/// @return true if the mark was deleted, else false.
+/// @see |nvim_buf_set_mark()|
+/// @see |nvim_del_mark()|
+Boolean nvim_buf_del_mark(Buffer buffer, String name, Error *err)
+ FUNC_API_SINCE(8)
+{
+ bool res = false;
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return res;
+ }
+
+ if (name.size != 1) {
+ api_set_error(err, kErrorTypeValidation,
+ "Mark name must be a single character");
+ return res;
+ }
+
+ pos_T *pos = getmark_buf(buf, *name.data, false);
+
+ // pos point to NULL when there's no mark with name
+ if (pos == NULL) {
+ api_set_error(err, kErrorTypeValidation, "Invalid mark name: '%c'",
+ *name.data);
+ return res;
+ }
+
+ // pos->lnum is 0 when the mark is not valid in the buffer, or is not set.
+ if (pos->lnum != 0) {
+ // since the mark belongs to the buffer delete it.
+ res = set_mark(buf, name, 0, 0, err);
+ }
+
+ return res;
+}
+
+/// Sets a named mark in the given buffer, all marks are allowed
+/// file/uppercase, visual, last change, etc. See |mark-motions|.
+///
+/// Marks are (1,0)-indexed. |api-indexing|
+///
+/// @note Passing 0 as line deletes the mark
+///
+/// @param buffer Buffer to set the mark on
+/// @param name Mark name
+/// @param line Line number
+/// @param col Column/row number
+/// @return true if the mark was set, else false.
+/// @see |nvim_buf_del_mark()|
+/// @see |nvim_buf_get_mark()|
+Boolean nvim_buf_set_mark(Buffer buffer, String name,
+ Integer line, Integer col, Error *err)
+ FUNC_API_SINCE(8)
+{
+ bool res = false;
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return res;
+ }
+
+ if (name.size != 1) {
+ api_set_error(err, kErrorTypeValidation,
+ "Mark name must be a single character");
+ return res;
+ }
+
+ res = set_mark(buf, name, line, col, err);
+
+ return res;
+}
+
+/// Returns a tuple (row,col) representing the position of the named mark. See
+/// |mark-motions|.
///
/// Marks are (1,0)-indexed. |api-indexing|
///
/// @param buffer Buffer handle, or 0 for current buffer
/// @param name Mark name
/// @param[out] err Error details, if any
-/// @return (row, col) tuple
+/// @return (row, col) tuple, (0, 0) if the mark is not set, or is an
+/// uppercase/file mark set in another buffer.
+/// @see |nvim_buf_set_mark()|
+/// @see |nvim_buf_del_mark()|
ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
FUNC_API_SINCE(1)
{
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 7b70c183c3..46ef41cc38 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -25,6 +25,7 @@
#include "nvim/lua/executor.h"
#include "nvim/map.h"
#include "nvim/map_defs.h"
+#include "nvim/mark.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/helpers.h"
@@ -1671,3 +1672,42 @@ void api_free_keydict(void *dict, KeySetLink *table)
}
}
+/// Set a named mark
+/// buffer and mark name must be validated already
+/// @param buffer Buffer to set the mark on
+/// @param name Mark name
+/// @param line Line number
+/// @param col Column/row number
+/// @return true if the mark was set, else false
+bool set_mark(buf_T *buf, String name, Integer line, Integer col, Error *err)
+{
+ buf = buf == NULL ? curbuf : buf;
+ // If line == 0 the marks is being deleted
+ bool res = false;
+ bool deleting = false;
+ if (line == 0) {
+ col = 0;
+ deleting = true;
+ } else {
+ if (col > MAXCOL) {
+ api_set_error(err, kErrorTypeValidation, "Column value outside range");
+ return res;
+ }
+ if (line < 1 || line > buf->b_ml.ml_line_count) {
+ api_set_error(err, kErrorTypeValidation, "Line value outside range");
+ return res;
+ }
+ }
+ pos_T pos = { line, (int)col, (int)col };
+ res = setmark_pos(*name.data, &pos, buf->handle);
+ if (!res) {
+ if (deleting) {
+ api_set_error(err, kErrorTypeException,
+ "Failed to delete named mark: %c", *name.data);
+ } else {
+ api_set_error(err, kErrorTypeException,
+ "Failed to set named mark: %c", *name.data);
+ }
+ }
+ return res;
+}
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 45fca8b1c5..ce6e36c6f4 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -2779,3 +2779,106 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, Erro
error:
decor_provider_clear(p);
}
+
+/// Deletes a uppercase/file named mark. See |mark-motions|.
+///
+/// @note fails with error if a lowercase or buffer local named mark is used.
+/// @param name Mark name
+/// @return true if the mark was deleted, else false.
+/// @see |nvim_buf_del_mark()|
+/// @see |nvim_get_mark()|
+Boolean nvim_del_mark(String name, Error *err)
+ FUNC_API_SINCE(8)
+{
+ bool res = false;
+ if (name.size != 1) {
+ api_set_error(err, kErrorTypeValidation,
+ "Mark name must be a single character");
+ return res;
+ }
+ // Only allow file/uppercase marks
+ // TODO(muniter): Refactor this ASCII_ISUPPER macro to a proper function
+ if (ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data)) {
+ res = set_mark(NULL, name, 0, 0, err);
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "Only file/uppercase marks allowed, invalid mark name: '%c'",
+ *name.data);
+ }
+ return res;
+}
+
+/// Return a tuple (row, col, buffer, buffername) representing the position of
+/// the uppercase/file named mark. See |mark-motions|.
+///
+/// Marks are (1,0)-indexed. |api-indexing|
+///
+/// @note fails with error if a lowercase or buffer local named mark is used.
+/// @param name Mark name
+/// @return 4-tuple (row, col, buffer, buffername), (0, 0, 0, '') if the mark is
+/// not set.
+/// @see |nvim_buf_set_mark()|
+/// @see |nvim_del_mark()|
+Array nvim_get_mark(String name, Error *err)
+ FUNC_API_SINCE(8)
+{
+ Array rv = ARRAY_DICT_INIT;
+
+ if (name.size != 1) {
+ api_set_error(err, kErrorTypeValidation,
+ "Mark name must be a single character");
+ return rv;
+ } else if (!(ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data))) {
+ api_set_error(err, kErrorTypeValidation,
+ "Only file/uppercase marks allowed, invalid mark name: '%c'",
+ *name.data);
+ return rv;
+ }
+
+ xfmark_T mark = get_global_mark(*name.data);
+ pos_T pos = mark.fmark.mark;
+ bool allocated = false;
+ int bufnr;
+ char *filename;
+
+ // Marks are from an open buffer it fnum is non zero
+ if (mark.fmark.fnum != 0) {
+ bufnr = mark.fmark.fnum;
+ filename = (char *)buflist_nr2name(bufnr, true, true);
+ allocated = true;
+ // Marks comes from shada
+ } else {
+ filename = (char *)mark.fname;
+ bufnr = 0;
+ }
+
+ bool exists = filename != NULL;
+ Integer row;
+ Integer col;
+
+ if (!exists || pos.lnum <= 0) {
+ if (allocated) {
+ xfree(filename);
+ allocated = false;
+ }
+ filename = "";
+ bufnr = 0;
+ row = 0;
+ col = 0;
+ } else {
+ row = pos.lnum;
+ col = pos.col;
+ }
+
+ ADD(rv, INTEGER_OBJ(row));
+ ADD(rv, INTEGER_OBJ(col));
+ ADD(rv, INTEGER_OBJ(bufnr));
+ ADD(rv, STRING_OBJ(cstr_to_string(filename)));
+
+ if (allocated) {
+ xfree(filename);
+ }
+
+ return rv;
+}
+
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index b296bf39cf..edec85c0b2 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -1637,6 +1637,15 @@ void get_buf_local_marks(const buf_T *buf, list_T *l)
add_mark(l, "'>", &buf->b_visual.vi_end, buf->b_fnum, NULL);
}
+/// Get a global mark
+///
+/// @param[in] Name of named mark
+/// @param[out] Global/file mark
+xfmark_T get_global_mark(char name)
+{
+ return namedfm[mark_global_index(name)];
+}
+
/// Get information about global marks ('A' to 'Z' and '0' to '9')
///
/// @param[out] l List to store global marks
diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua
index 81fad206da..01fcfab543 100644
--- a/test/functional/api/buffer_spec.lua
+++ b/test/functional/api/buffer_spec.lua
@@ -707,4 +707,59 @@ describe('api/buf', function()
eq({3, 0}, curbuf('get_mark', 'v'))
end)
end)
+
+ describe('nvim_buf_set_mark', function()
+ it('works with buffer local marks', function()
+ curbufmeths.set_lines(-1, -1, true, {'a', 'bit of', 'text'})
+ eq(true, curbufmeths.set_mark('z', 1, 1))
+ eq({1, 1}, curbufmeths.get_mark('z'))
+ end)
+ it('works with file/uppercase marks', function()
+ curbufmeths.set_lines(-1, -1, true, {'a', 'bit of', 'text'})
+ eq(true, curbufmeths.set_mark('Z', 3, 1))
+ eq({3, 1}, curbufmeths.get_mark('Z'))
+ end)
+ it('fails when invalid marks names are used', function()
+ eq(false, pcall(curbufmeths.set_mark, '!', 1, 0))
+ eq(false, pcall(curbufmeths.set_mark, 'fail', 1, 0))
+ end)
+ it('fails when invalid buffer number is used', function()
+ eq(false, pcall(meths.buf_set_mark, 99, 'a', 1, 1))
+ end)
+ end)
+
+ describe('nvim_buf_del_mark', function()
+ it('works with buffer local marks', function()
+ curbufmeths.set_lines(-1, -1, true, {'a', 'bit of', 'text'})
+ curbufmeths.set_mark('z', 3, 1)
+ eq(true, curbufmeths.del_mark('z'))
+ eq({0, 0}, curbufmeths.get_mark('z'))
+ end)
+ it('works with file/uppercase marks', function()
+ curbufmeths.set_lines(-1, -1, true, {'a', 'bit of', 'text'})
+ curbufmeths.set_mark('Z', 3, 3)
+ eq(true, curbufmeths.del_mark('Z'))
+ eq({0, 0}, curbufmeths.get_mark('Z'))
+ end)
+ it('returns false in marks not set in this buffer', function()
+ local abuf = meths.create_buf(false,true)
+ bufmeths.set_lines(abuf, -1, -1, true, {'a', 'bit of', 'text'})
+ bufmeths.set_mark(abuf, 'A', 2, 2)
+ eq(false, curbufmeths.del_mark('A'))
+ eq({2, 2}, bufmeths.get_mark(abuf, 'A'))
+ end)
+ it('returns false if mark was not deleted', function()
+ curbufmeths.set_lines(-1, -1, true, {'a', 'bit of', 'text'})
+ curbufmeths.set_mark('z', 3, 1)
+ eq(true, curbufmeths.del_mark('z'))
+ eq(false, curbufmeths.del_mark('z')) -- Mark was already deleted
+ end)
+ it('fails when invalid marks names are used', function()
+ eq(false, pcall(curbufmeths.del_mark, '!'))
+ eq(false, pcall(curbufmeths.del_mark, 'fail'))
+ end)
+ it('fails when invalid buffer number is used', function()
+ eq(false, pcall(meths.buf_del_mark, 99, 'a'))
+ end)
+ end)
end)
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index 6bcf8dd91f..6b644ed519 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -2267,4 +2267,59 @@ describe('API', function()
]]}
end)
end)
+
+ describe('nvim_del_mark', function()
+ it('works', function()
+ local buf = meths.create_buf(false,true)
+ meths.buf_set_lines(buf, -1, -1, true, {'a', 'bit of', 'text'})
+ eq(true, meths.buf_set_mark(buf, 'F', 2, 2))
+ eq(true, meths.del_mark('F'))
+ eq({0, 0}, meths.buf_get_mark(buf, 'F'))
+ end)
+ it('fails when invalid marks are used', function()
+ eq(false, pcall(meths.del_mark, 'f'))
+ eq(false, pcall(meths.del_mark, '!'))
+ eq(false, pcall(meths.del_mark, 'fail'))
+ end)
+ end)
+ describe('nvim_get_mark', function()
+ it('works', function()
+ local buf = meths.create_buf(false,true)
+ meths.buf_set_lines(buf, -1, -1, true, {'a', 'bit of', 'text'})
+ meths.buf_set_mark(buf, 'F', 2, 2)
+ meths.buf_set_name(buf, "mybuf")
+ local mark = meths.get_mark('F')
+ -- Compare the path tail ony
+ assert(string.find(mark[4], "mybuf$"))
+ eq({2, 2, buf.id, mark[4]}, mark)
+ end)
+ it('fails when invalid marks are used', function()
+ eq(false, pcall(meths.del_mark, 'f'))
+ eq(false, pcall(meths.del_mark, '!'))
+ eq(false, pcall(meths.del_mark, 'fail'))
+ end)
+ it('returns the expected when mark is not set', function()
+ eq(true, meths.del_mark('A'))
+ eq({0, 0, 0, ''}, meths.get_mark('A'))
+ end)
+ it('works with deleted buffers', function()
+ local fname = tmpname()
+ write_file(fname, 'a\nbit of\text')
+ nvim("command", "edit " .. fname)
+ local buf = meths.get_current_buf()
+
+ meths.buf_set_mark(buf, 'F', 2, 2)
+ nvim("command", "new") -- Create new buf to avoid :bd failing
+ nvim("command", "bd! " .. buf.id)
+ os.remove(fname)
+
+ local mark = meths.get_mark('F')
+ -- To avoid comparing relative vs absolute path
+ local mfname = mark[4]
+ local tail_patt = [[[\/][^\/]*$]]
+ -- tail of paths should be equals
+ eq(fname:match(tail_patt), mfname:match(tail_patt))
+ eq({2, 2, buf.id, mark[4]}, mark)
+ end)
+ end)
end)