diff options
author | Javier Lopez <graulopezjavier@gmail.com> | 2021-10-05 10:49:20 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-05 08:49:20 -0700 |
commit | 49fdc62114a5f37def3ff60ca0c4d8638903ac24 (patch) | |
tree | 8d75845ca18d9dd9c6da261473673445ab0bef27 | |
parent | 912a6e5a9c58fce74134f9f8c2801373928e8289 (diff) | |
download | rneovim-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.txt | 92 | ||||
-rw-r--r-- | src/nvim/api/buffer.c | 87 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.c | 40 | ||||
-rw-r--r-- | src/nvim/api/vim.c | 103 | ||||
-rw-r--r-- | src/nvim/mark.c | 9 | ||||
-rw-r--r-- | test/functional/api/buffer_spec.lua | 55 | ||||
-rw-r--r-- | test/functional/api/vim_spec.lua | 55 |
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) |