aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortimeyyy <timeyyy_da_man@hotmail.com>2017-01-18 13:20:07 +0100
committerBjörn Linse <bjorn.linse@gmail.com>2019-11-11 19:43:15 +0100
commita9065a50518ef59351f9d0d32041a991a751653f (patch)
tree4448b4895b8977fcb09b8d87060cfa20fb8058b9
parente757f4d5363125e242f1e680d1f5bb53e8014b06 (diff)
downloadrneovim-a9065a50518ef59351f9d0d32041a991a751653f.tar.gz
rneovim-a9065a50518ef59351f9d0d32041a991a751653f.tar.bz2
rneovim-a9065a50518ef59351f9d0d32041a991a751653f.zip
nsmarks: initial commit
-rw-r--r--README.md11
-rw-r--r--runtime/doc/api.txt46
-rw-r--r--src/nvim/api/buffer.c217
-rw-r--r--src/nvim/api/private/helpers.c117
-rw-r--r--src/nvim/api/vim.c1
-rw-r--r--src/nvim/buffer.c3
-rw-r--r--src/nvim/buffer_defs.h7
-rw-r--r--src/nvim/change.c18
-rw-r--r--src/nvim/diff.c3
-rw-r--r--src/nvim/edit.c42
-rw-r--r--src/nvim/ex_cmds.c297
-rw-r--r--src/nvim/lib/kbtree.h6
-rw-r--r--src/nvim/map.c1
-rw-r--r--src/nvim/map.h1
-rw-r--r--src/nvim/mark.c25
-rw-r--r--src/nvim/mark_extended.c1262
-rw-r--r--src/nvim/mark_extended.h267
-rw-r--r--src/nvim/mark_extended_defs.h54
-rw-r--r--src/nvim/misc1.c1
-rw-r--r--src/nvim/ops.c185
-rw-r--r--src/nvim/pos.h4
-rw-r--r--src/nvim/undo.c53
-rw-r--r--src/nvim/undo_defs.h16
-rw-r--r--test/functional/api/mark_extended_spec.lua1365
-rw-r--r--test/functional/legacy/046_multi_line_regexps_spec.lua30
-rw-r--r--test/functional/ui/inccommand_spec.lua16
-rw-r--r--test/functional/ui/mouse_spec.lua1
27 files changed, 3968 insertions, 81 deletions
diff --git a/README.md b/README.md
index 338c8df70e..e09b6a2e85 100644
--- a/README.md
+++ b/README.md
@@ -148,3 +148,14 @@ See `LICENSE` for details.
[Gentoo]: https://packages.gentoo.org/packages/app-editors/neovim
<!-- vim: set tw=80: -->
+
+
+CC=clang make functionaltest CMAKE_EXTRA_FLAGS="-DCLANG_ASAN_UBSAN=ON" TEST_FILE=test/functional/api TEST_TAG=extmarks
+
+CC=clang make functionaltest CMAKE_EXTRA_FLAGS="-DCLANG_ASAN_UBSAN=ON" TEST_FILE=test/functional/ui/mouse_spec.lua TEST_TAG=blah
+CC=clang make functionaltest CMAKE_EXTRA_FLAGS="-DCLANG_ASAN_UBSAN=ON"
+
+CC=clang make functionaltest CMAKE_EXTRA_FLAGS="-DCLANG_ASAN_UBSAN=OFF" TEST_FILE=test/functional/api/mark_extended_spec.lua TEST_TAG=broken
+
+# TODO debug in container with clion...
+https://github.com/shuhaoliu/docker-clion-dev
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index 93440f307c..d6b7938633 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -439,6 +439,36 @@ Example: create a float with scratch buffer: >
>
==============================================================================
+Buffer extended marks *api-extended-marks*
+
+An extended mark represents a buffer annotation that remains logically
+stationary even as the buffer changes. They could be used to represent cursors,
+folds, misspelled words, and anything else that needs to track a logical
+location in the buffer over time.
+
+Example:
+
+We will set an extmark at row 1, column three.
+
+`let g:mark_ns = nvim_create_namespace('myplugin')`
+`let g:mark_id = nvim_buf_set_mark(0, :g:mark_ns, 0, 1, 3)`
+
+Note: the mark id was randomly generated because we used an inital value of 0
+
+`echo nvim_buf_lookup_mark(0, g:mark_ns, g:mark_id)`
+=> [1, 1, 3]
+`echo nvim_buf_get_marks(0, g:mark_ns, [1, 1], [1, 3], -1, 0)`
+=> [[1, 1, 3]]
+
+Deleting the text all around an extended mark does not remove it. If you want
+to remove an extended mark, use the |nvim_buf_unset_mark()| function.
+
+The namepsace ensures you only ever work with the extended marks you mean to.
+
+Calling set and unset of marks will not automatically prop a new undo.
+
+
+==============================================================================
Global Functions *api-global*
nvim_command({command}) *nvim_command()*
@@ -1449,6 +1479,22 @@ nvim_select_popupmenu_item({item}, {insert}, {finish}, {opts})
nvim__inspect_cell({grid}, {row}, {col}) *nvim__inspect_cell()*
TODO: Documentation
+ *nvim_init_mark_ns()*
+nvim_init_mark_ns({namespace})
+ Create a new namepsace for holding extended marks. The id
+ of the namespace is returned, this namespace id is required
+ for using the rest of the extended marks api.
+
+ Parameters:~
+ {namespace} String name to be assigned to the namespace
+
+
+ *nvim_mark_get_ns_ids()*
+nvim_mark_get_ns_ids()
+ Returns a list of extended mark namespaces.
+ Pairs of ids and string names are returned.
+ An empty list will be returned if no namespaces are set.
+
==============================================================================
Buffer Functions *api-buffer*
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 10250be044..a77c33e891 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -23,7 +23,10 @@
#include "nvim/memory.h"
#include "nvim/misc1.h"
#include "nvim/ex_cmds.h"
+#include "nvim/map_defs.h"
+#include "nvim/map.h"
#include "nvim/mark.h"
+#include "nvim/mark_extended.h"
#include "nvim/fileio.h"
#include "nvim/move.h"
#include "nvim/syntax.h"
@@ -544,7 +547,8 @@ void nvim_buf_set_lines(uint64_t channel_id,
(linenr_T)(end - 1),
MAXLNUM,
(long)extra,
- false);
+ false,
+ kExtmarkUndo);
changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra, true);
fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra);
@@ -999,6 +1003,213 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
return rv;
}
+/// Returns position info for a given extmark id
+///
+/// @param buffer The buffer handle
+/// @param namespace a identifier returned previously with nvim_create_namespace
+/// @param id the extmark id
+/// @param[out] err Details of an error that may have occurred
+/// @return (row, col) tuple or empty list () if extmark id was absent
+ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer namespace,
+ Integer id, Error *err)
+ FUNC_API_SINCE(6)
+{
+ Array rv = ARRAY_DICT_INIT;
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return rv;
+ }
+
+ if (!ns_initialized((uint64_t)namespace)) {
+ api_set_error(err, kErrorTypeValidation, _("Invalid mark namespace"));
+ return rv;
+ }
+
+ ExtendedMark *extmark = extmark_from_id(buf,
+ (uint64_t)namespace,
+ (uint64_t)id);
+ if (!extmark) {
+ return rv;
+ }
+ ADD(rv, INTEGER_OBJ((Integer)extmark->line->lnum-1));
+ ADD(rv, INTEGER_OBJ((Integer)extmark->col-1));
+ return rv;
+}
+
+/// List extmarks in a range (inclusive)
+///
+/// range ends can be specified as (row, col) tuples, as well as extmark
+/// ids in the same namespace. In addition, 0 and -1 works as shorthands
+/// for (0,0) and (-1,-1) respectively, so that all marks in the buffer can be
+/// quieried as:
+///
+/// all_marks = nvim_buf_get_extmarks(0, my_ns, 0, -1, -1)
+///
+/// If end is a lower position than start, then the range will be traversed
+/// backwards. This is mostly used with limited amount, to be able to get the
+/// first marks prior to a given position.
+///
+/// @param buffer The buffer handle
+/// @param namespace An id returned previously from nvim_create_namespace
+/// @param lower One of: extmark id, (row, col) or 0, -1 for buffer ends
+/// @param upper One of: extmark id, (row, col) or 0, -1 for buffer ends
+/// @param amount Maximum number of marks to return or -1 for all marks found
+/// /// @param[out] err Details of an error that may have occurred
+/// @return [[nsmark_id, row, col], ...]
+Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id,
+ Object start, Object end, Integer amount,
+ Error *err)
+ FUNC_API_SINCE(6)
+{
+ Array rv = ARRAY_DICT_INIT;
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return rv;
+ }
+
+ if (!ns_initialized((uint64_t)ns_id)) {
+ api_set_error(err, kErrorTypeValidation, _("Invalid mark namespace"));
+ return rv;
+ }
+
+ if (amount == 0) {
+ return rv;
+ }
+
+
+ bool reverse = false;
+
+ linenr_T l_lnum;
+ colnr_T l_col;
+ if (!set_extmark_index_from_obj(buf, ns_id, start, &l_lnum, &l_col, err)) {
+ return rv;
+ }
+
+ linenr_T u_lnum;
+ colnr_T u_col;
+ if (!set_extmark_index_from_obj(buf, ns_id, end, &u_lnum, &u_col, err)) {
+ return rv;
+ }
+
+ if (l_lnum > u_lnum || (l_lnum == u_lnum && l_col > u_col)) {
+ reverse = true;
+ linenr_T tmp_lnum = l_lnum;
+ l_lnum = u_lnum;
+ u_lnum = tmp_lnum;
+ colnr_T tmp_col = l_col;
+ l_col = u_col;
+ u_col = tmp_col;
+ }
+
+
+ ExtmarkArray marks = extmark_get(buf, (uint64_t)ns_id, l_lnum, l_col,
+ u_lnum, u_col, (int64_t)amount,
+ reverse);
+
+ for (size_t i = 0; i < kv_size(marks); i++) {
+ Array mark = ARRAY_DICT_INIT;
+ ExtendedMark *extmark = kv_A(marks, i);
+ ADD(mark, INTEGER_OBJ((Integer)extmark->mark_id));
+ ADD(mark, INTEGER_OBJ(extmark->line->lnum-1));
+ ADD(mark, INTEGER_OBJ(extmark->col-1));
+ ADD(rv, ARRAY_OBJ(mark));
+ }
+
+ kv_destroy(marks);
+ return rv;
+}
+
+/// Create or update a namespaced mark at a position
+///
+/// If an invalid namespace is given, an error will be raised.
+///
+/// @param buffer The buffer handle
+/// @param ns_id a identifier returned previously with nvim_create_namespace
+/// @param id The extmark's id or 0 for next free id
+/// @param row The row to set the extmark to.
+/// @param col The column to set the extmark to.
+/// @param[out] err Details of an error that may have occurred
+/// @return the nsmark_id for a new mark, or 0 for an update
+Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id,
+ Integer line, Integer col, Error *err)
+ FUNC_API_SINCE(6)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return 0;
+ }
+
+ if (!ns_initialized((uint64_t)ns_id)) {
+ api_set_error(err, kErrorTypeValidation, _("Invalid mark namespace"));
+ return 0;
+ }
+
+ size_t len = 0;
+ if (line < 0 || line > buf->b_ml.ml_line_count) {
+ api_set_error(err, kErrorTypeValidation, "line value outside range");
+ return 0;
+ } else if (line < buf->b_ml.ml_line_count) {
+ len = STRLEN(ml_get_buf(curbuf, (linenr_T)line+1, false));
+ }
+
+ if (col == -1) {
+ col = (Integer)len;
+ } else if (col < -1 || col > (Integer)len) {
+ api_set_error(err, kErrorTypeValidation, "col value outside range");
+ return 0;
+ }
+
+ uint64_t id_num;
+ if (id == 0) {
+ id_num = extmark_free_id_get(buf, (uint64_t)ns_id);
+ } else if (id > 0) {
+ id_num = (uint64_t)id;
+ } else {
+ api_set_error(err, kErrorTypeValidation, _("Invalid mark id"));
+ return 0;
+ }
+
+ bool new = extmark_set(buf, (uint64_t)ns_id, id_num,
+ (linenr_T)line+1,
+ (colnr_T)col+1,
+ kExtmarkUndo);
+
+ if (new) {
+ return (Integer)id_num;
+ } else {
+ return 0;
+ }
+}
+
+/// Remove an extmark
+///
+/// @param buffer The buffer handle
+/// @param ns_id a identifier returned previously with nvim_create_namespace
+/// @param id The extmarks's id
+/// @param[out] err Details of an error that may have occurred
+/// @return true on success, false if no extmarks found
+Boolean nvim_buf_del_extmark(Buffer buffer,
+ Integer ns_id,
+ Integer id,
+ Error *err)
+ FUNC_API_SINCE(6)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return false;
+ }
+ if (!ns_initialized((uint64_t)ns_id)) {
+ api_set_error(err, kErrorTypeValidation, _("Invalid mark namespace"));
+ return false;
+ }
+
+ return extmark_del(buf, (uint64_t)ns_id, (uint64_t)id, kExtmarkUndo);
+}
+
/// Adds a highlight to buffer.
///
/// Useful for plugins that dynamically generate highlights to a buffer
@@ -1097,6 +1308,10 @@ void nvim_buf_clear_namespace(Buffer buffer,
}
bufhl_clear_line_range(buf, (int)ns_id, (int)line_start+1, (int)line_end);
+ extmark_clear(buf, ns_id == -1 ? 0 : (uint64_t)ns_id,
+ (linenr_T)line_start+1,
+ (linenr_T)line_end,
+ kExtmarkUndo);
}
/// Clears highlights and virtual text from namespace and range of lines
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index b54fc9207e..5e9a572a78 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -24,6 +24,7 @@
#include "nvim/eval/typval.h"
#include "nvim/map_defs.h"
#include "nvim/map.h"
+#include "nvim/mark_extended.h"
#include "nvim/option.h"
#include "nvim/option_defs.h"
#include "nvim/version.h"
@@ -1507,6 +1508,63 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf)
return mappings;
}
+// Returns an extmark given an id or a positional index
+// If throw == true then an error will be raised if nothing
+// was found
+// Returns NULL if something went wrong
+ExtendedMark *extmark_from_id_or_pos(Buffer buffer,
+ Integer namespace,
+ Object id,
+ Error *err,
+ bool throw)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return NULL;
+ }
+
+ ExtendedMark *extmark = NULL;
+ if (id.type == kObjectTypeArray) {
+ if (id.data.array.size != 2) {
+ api_set_error(err, kErrorTypeValidation,
+ _("Position must have 2 elements"));
+ return NULL;
+ }
+ linenr_T row = (linenr_T)id.data.array.items[0].data.integer;
+ colnr_T col = (colnr_T)id.data.array.items[1].data.integer;
+ if (row < 1 || col < 1) {
+ if (throw) {
+ api_set_error(err, kErrorTypeValidation, _("Row and column MUST be > 0"));
+ }
+ return NULL;
+ }
+ extmark = extmark_from_pos(buf, (uint64_t)namespace, row, col);
+ } else if (id.type != kObjectTypeInteger) {
+ if (throw) {
+ api_set_error(err, kErrorTypeValidation,
+ _("Mark id must be an int or [row, col]"));
+ }
+ return NULL;
+ } else if (id.data.integer < 0) {
+ if (throw) {
+ api_set_error(err, kErrorTypeValidation, _("Mark id must be positive"));
+ }
+ return NULL;
+ } else {
+ extmark = extmark_from_id(buf,
+ (uint64_t)namespace,
+ (uint64_t)id.data.integer);
+ }
+
+ if (!extmark) {
+ if (throw) {
+ api_set_error(err, kErrorTypeValidation, _("Mark doesn't exist"));
+ }
+ return NULL;
+ }
+ return extmark;
+}
// Is the Namespace in use?
bool ns_initialized(uint64_t ns)
@@ -1516,3 +1574,62 @@ bool ns_initialized(uint64_t ns)
}
return ns < (uint64_t)next_namespace_id;
}
+
+// Extmarks may be queried from position or name or even special names
+// in the future such as "cursor". This macro sets the line and col
+// to make the extmark functions recognize what's required
+//
+// *lnum: linenr_T, lnum to be set
+// *col: colnr_T, col to be set
+bool set_extmark_index_from_obj(buf_T *buf, Integer namespace,
+ Object obj, linenr_T *lnum, colnr_T *colnr,
+ Error *err)
+{
+ // Check if it is mark id
+ if (obj.type == kObjectTypeInteger) {
+ Integer id = obj.data.integer;
+ if (id == 0) {
+ *lnum = 1;
+ *colnr = 1;
+ return true;
+ } else if (id == -1) {
+ *lnum = MAXLNUM;
+ *colnr = MAXCOL;
+ return true;
+ } else if (id < 0) {
+ api_set_error(err, kErrorTypeValidation, _("Mark id must be positive"));
+ return false;
+ }
+
+ ExtendedMark *extmark = extmark_from_id(buf, (uint64_t)namespace,
+ (uint64_t)id);
+ if (extmark) {
+ *lnum = extmark->line->lnum;
+ *colnr = extmark->col;
+ return true;
+ } else {
+ api_set_error(err, kErrorTypeValidation, _("No mark with requested id"));
+ return false;
+ }
+
+ // Check if it is a position
+ } else if (obj.type == kObjectTypeArray) {
+ Array pos = obj.data.array;
+ if (pos.size != 2
+ || pos.items[0].type != kObjectTypeInteger
+ || pos.items[1].type != kObjectTypeInteger) {
+ api_set_error(err, kErrorTypeValidation,
+ _("Position must have 2 integer elements"));
+ return false;
+ }
+ Integer line = pos.items[0].data.integer;
+ Integer col = pos.items[1].data.integer;
+ *lnum = (linenr_T)(line >= 0 ? line + 1 : MAXLNUM);
+ *colnr = (colnr_T)(col >= 0 ? col + 1 : MAXCOL);
+ return true;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ _("Position must be a mark id Integer or position Array"));
+ return false;
+ }
+}
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index e587df5384..10f7dd1a7b 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -39,6 +39,7 @@
#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/state.h"
+#include "nvim/mark_extended.h"
#include "nvim/syntax.h"
#include "nvim/getchar.h"
#include "nvim/os/input.h"
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 1d5aa8ba9b..79f339b3aa 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -53,6 +53,7 @@
#include "nvim/indent_c.h"
#include "nvim/main.h"
#include "nvim/mark.h"
+#include "nvim/mark_extended.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -816,6 +817,7 @@ static void free_buffer_stuff(buf_T *buf, int free_flags)
}
uc_clear(&buf->b_ucmds); // clear local user commands
buf_delete_signs(buf, (char_u *)"*"); // delete any signs
+ extmark_free_all(buf); // delete any extmarks
bufhl_clear_all(buf); // delete any highligts
map_clear_int(buf, MAP_ALL_MODES, true, false); // clear local mappings
map_clear_int(buf, MAP_ALL_MODES, true, true); // clear local abbrevs
@@ -5496,6 +5498,7 @@ void bufhl_clear_line_range(buf_T *buf,
linenr_T line_start,
linenr_T line_end)
{
+ // TODO(bfredl): implement kb_itr_interval to jump directly to the first line
kbitr_t(bufhl) itr;
BufhlLine *l, t = BUFHLLINE_INIT(line_start);
if (!kb_itr_get(bufhl, &buf->b_bufhl_info, &t, &itr)) {
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index ca740dea21..2d65d0e160 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -115,6 +115,9 @@ typedef uint16_t disptick_T; // display tick type
#include "nvim/os/fs_defs.h" // for FileID
#include "nvim/terminal.h" // for Terminal
+#include "nvim/lib/kbtree.h"
+#include "nvim/mark_extended.h"
+
/*
* The taggy struct is used to store the information about a :tag command.
*/
@@ -805,6 +808,10 @@ struct file_buffer {
kvec_t(BufhlLine *) b_bufhl_move_space; // temporary space for highlights
+ PMap(uint64_t) *b_extmark_ns; // extmark namespaces
+ kbtree_t(extlines) b_extlines; // extmarks
+ kvec_t(ExtMarkLine *) b_extmark_move_space; // temp space for extmarks
+
// array of channel_id:s which have asked to receive updates for this
// buffer.
kvec_t(uint64_t) update_channels;
diff --git a/src/nvim/change.c b/src/nvim/change.c
index ba80e71ae6..7558055696 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -17,6 +17,7 @@
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/mark.h"
+#include "nvim/mark_extended.h"
#include "nvim/memline.h"
#include "nvim/misc1.h"
#include "nvim/move.h"
@@ -372,7 +373,7 @@ void appended_lines_mark(linenr_T lnum, long count)
// Skip mark_adjust when adding a line after the last one, there can't
// be marks there. But it's still needed in diff mode.
if (lnum + count < curbuf->b_ml.ml_line_count || curwin->w_p_diff) {
- mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, false);
+ mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, false, kExtmarkUndo);
}
changed_lines(lnum + 1, 0, lnum + 1, count, true);
}
@@ -390,7 +391,8 @@ void deleted_lines(linenr_T lnum, long count)
/// be triggered to display the cursor.
void deleted_lines_mark(linenr_T lnum, long count)
{
- mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, -count, false);
+ mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, -count, false,
+ kExtmarkUndo);
changed_lines(lnum, 0, lnum + count, -count, true);
}
@@ -951,6 +953,9 @@ int open_line(
bool did_append; // appended a new line
int saved_pi = curbuf->b_p_pi; // copy of preserveindent setting
+ linenr_T lnum = curwin->w_cursor.lnum;
+ colnr_T mincol = curwin->w_cursor.col + 1;
+
// make a copy of the current line so we can mess with it
char_u *saved_line = vim_strsave(get_cursor_line_ptr());
@@ -1574,7 +1579,8 @@ int open_line(
// be marks there. But still needed in diff mode.
if (curwin->w_cursor.lnum + 1 < curbuf->b_ml.ml_line_count
|| curwin->w_p_diff) {
- mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false);
+ mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false,
+ kExtmarkUndo);
}
did_append = true;
} else {
@@ -1663,8 +1669,12 @@ int open_line(
if (flags & OPENLINE_MARKFIX) {
mark_col_adjust(curwin->w_cursor.lnum,
curwin->w_cursor.col + less_cols_off,
- 1L, (long)-less_cols, 0);
+ 1L, (long)-less_cols, 0, kExtmarkNOOP);
}
+ // Always move extmarks - Here we move only the line where the
+ // cursor is, the previous mark_adjust takes care of the lines after
+ extmark_col_adjust(curbuf, lnum, mincol, 1L, (long)-less_cols,
+ kExtmarkUndo);
} else {
changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col);
}
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index 31552929dc..313f77474d 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -2690,7 +2690,8 @@ void ex_diffgetput(exarg_T *eap)
// Adjust marks. This will change the following entries!
if (added != 0) {
- mark_adjust(lnum, lnum + count - 1, (long)MAXLNUM, (long)added, false);
+ mark_adjust(lnum, lnum + count - 1, (long)MAXLNUM, (long)added, false,
+ kExtmarkUndo);
if (curwin->w_cursor.lnum >= lnum) {
// Adjust the cursor position if it's in/after the changed
// lines.
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 93f13c8d3f..25b6502b19 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -28,6 +28,7 @@
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/main.h"
+#include "nvim/mark_extended.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -1837,6 +1838,13 @@ change_indent (
xfree(new_line);
}
+
+ // change_indent seems to bec called twice, this combination only triggers
+ // once for both calls
+ if (new_cursor_col - vcol != 0) {
+ extmark_col_adjust(curbuf, curwin->w_cursor.lnum, 0, 0, amount,
+ kExtmarkUndo);
+ }
}
/*
@@ -5587,6 +5595,9 @@ insertchar (
do_digraph(buf[i-1]); /* may be the start of a digraph */
buf[i] = NUL;
ins_str(buf);
+ extmark_col_adjust(curbuf, curwin->w_cursor.lnum,
+ (colnr_T)(curwin->w_cursor.col + 1), 0,
+ (long)STRLEN(buf), kExtmarkUndo);
if (flags & INSCHAR_CTRLV) {
redo_literal(*buf);
i = 1;
@@ -5597,6 +5608,9 @@ insertchar (
} else {
int cc;
+ extmark_col_adjust(curbuf, curwin->w_cursor.lnum,
+ (colnr_T)(curwin->w_cursor.col + 1), 0,
+ 1, kExtmarkUndo);
if ((cc = utf_char2len(c)) > 1) {
char_u buf[MB_MAXBYTES + 1];
@@ -5606,10 +5620,11 @@ insertchar (
AppendCharToRedobuff(c);
} else {
ins_char(c);
- if (flags & INSCHAR_CTRLV)
+ if (flags & INSCHAR_CTRLV) {
redo_literal(c);
- else
+ } else {
AppendCharToRedobuff(c);
+ }
}
}
}
@@ -6891,8 +6906,9 @@ static void mb_replace_pop_ins(int cc)
for (i = 1; i < n; ++i)
buf[i] = replace_pop();
ins_bytes_len(buf, n);
- } else
+ } else {
ins_char(cc);
+ }
if (enc_utf8)
/* Handle composing chars. */
@@ -8002,9 +8018,9 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
Insstart_orig.col = curwin->w_cursor.col;
}
- if (State & VREPLACE_FLAG)
+ if (State & VREPLACE_FLAG) {
ins_char(' ');
- else {
+ } else {
ins_str((char_u *)" ");
if ((State & REPLACE_FLAG))
replace_push(NUL);
@@ -8482,8 +8498,17 @@ static bool ins_tab(void)
} else { // otherwise use "tabstop"
temp = (int)curbuf->b_p_ts;
}
+
temp -= get_nolist_virtcol() % temp;
+ // Move extmarks
+ extmark_col_adjust(curbuf,
+ curwin->w_cursor.lnum,
+ curwin->w_cursor.col,
+ 0,
+ temp,
+ kExtmarkUndo);
+
/*
* Insert the first space with ins_char(). It will delete one char in
* replace mode. Insert the rest with ins_str(); it will not delete any
@@ -8491,12 +8516,13 @@ static bool ins_tab(void)
*/
ins_char(' ');
while (--temp > 0) {
- if (State & VREPLACE_FLAG)
+ if (State & VREPLACE_FLAG) {
ins_char(' ');
- else {
+ } else {
ins_str((char_u *)" ");
- if (State & REPLACE_FLAG) /* no char replaced */
+ if (State & REPLACE_FLAG) { // no char replaced
replace_push(NUL);
+ }
}
}
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 1b6d9b50e9..fb97c46117 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -39,6 +39,7 @@
#include "nvim/buffer_updates.h"
#include "nvim/main.h"
#include "nvim/mark.h"
+#include "nvim/mark_extended.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/message.h"
@@ -105,6 +106,8 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_cmds.c.generated.h"
+#include "mark_extended.h"
+
#endif
/// ":ascii" and "ga" implementation
@@ -658,10 +661,10 @@ void ex_sort(exarg_T *eap)
deleted = (long)(count - (lnum - eap->line2));
if (deleted > 0) {
mark_adjust(eap->line2 - deleted, eap->line2, (long)MAXLNUM, -deleted,
- false);
+ false, kExtmarkUndo);
msgmore(-deleted);
} else if (deleted < 0) {
- mark_adjust(eap->line2, MAXLNUM, -deleted, 0L, false);
+ mark_adjust(eap->line2, MAXLNUM, -deleted, 0L, false, kExtmarkUndo);
}
if (change_occurred || deleted != 0) {
changed_lines(eap->line1, 0, eap->line2 + 1, -deleted, true);
@@ -874,10 +877,12 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
* their final destination at the new text position -- webb
*/
last_line = curbuf->b_ml.ml_line_count;
- mark_adjust_nofold(line1, line2, last_line - line2, 0L, true);
+ mark_adjust_nofold(line1, line2, last_line - line2, 0L, true, kExtmarkNoUndo);
+ extmark_adjust(curbuf, line1, line2, last_line - line2, 0L, kExtmarkNoUndo,
+ true);
changed_lines(last_line - num_lines + 1, 0, last_line + 1, num_lines, false);
if (dest >= line2) {
- mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, false);
+ mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, false, kExtmarkNoUndo);
FOR_ALL_TAB_WINDOWS(tab, win) {
if (win->w_buffer == curbuf) {
foldMoveRange(&win->w_folds, line1, line2, dest);
@@ -886,7 +891,8 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
curbuf->b_op_start.lnum = dest - num_lines + 1;
curbuf->b_op_end.lnum = dest;
} else {
- mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, false);
+ mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, false,
+ kExtmarkNoUndo);
FOR_ALL_TAB_WINDOWS(tab, win) {
if (win->w_buffer == curbuf) {
foldMoveRange(&win->w_folds, dest + 1, line1 - 1, line2);
@@ -897,7 +903,9 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
}
curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
mark_adjust_nofold(last_line - num_lines + 1, last_line,
- -(last_line - dest - extra), 0L, true);
+ -(last_line - dest - extra), 0L, true, kExtmarkNoUndo);
+
+ u_extmark_move(curbuf, line1, line2, last_line, dest, num_lines, extra);
changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra, false);
// send update regarding the new lines that were added
@@ -1281,12 +1289,14 @@ static void do_filter(
if (cmdmod.keepmarks || vim_strchr(p_cpo, CPO_REMMARK) == NULL) {
if (read_linecount >= linecount) {
// move all marks from old lines to new lines
- mark_adjust(line1, line2, linecount, 0L, false);
+ mark_adjust(line1, line2, linecount, 0L, false, kExtmarkUndo);
} else {
// move marks from old lines to new lines, delete marks
// that are in deleted lines
- mark_adjust(line1, line1 + read_linecount - 1, linecount, 0L, false);
- mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0L, false);
+ mark_adjust(line1, line1 + read_linecount - 1, linecount, 0L, false,
+ kExtmarkUndo);
+ mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0L, false,
+ kExtmarkUndo);
}
}
@@ -3214,6 +3224,189 @@ static char_u *sub_parse_flags(char_u *cmd, subflags_T *subflags,
return cmd;
}
+static void extmark_move_regmatch_single(lpos_T startpos,
+ lpos_T endpos,
+ linenr_T lnum,
+ int sublen)
+{
+ colnr_T mincol;
+ colnr_T endcol;
+ colnr_T col_amount;
+
+ mincol = startpos.col + 1;
+ endcol = endpos.col + 1;
+
+ // There are cases such as :s/^/x/ where this happens
+ // a delete is simply not required.
+ if (mincol + 1 <= endcol) {
+ extmark_col_adjust_delete(curbuf,
+ lnum, mincol + 1, endcol, kExtmarkUndo, 0);
+ }
+
+ // Insert, sublen seems to be the value we need but + 1...
+ col_amount = sublen - 1;
+ extmark_col_adjust(curbuf, lnum, mincol, 0, col_amount, kExtmarkUndo);
+}
+
+static void extmark_move_regmatch_multi(ExtmarkSubMulti s, long i)
+{
+ colnr_T mincol;
+ linenr_T u_lnum;
+ mincol = s.startpos.col + 1;
+
+ linenr_T n_u_lnum = s.lnum + s.endpos.lnum - s.startpos.lnum;
+ colnr_T n_after_newline_in_pat = s.endpos.col;
+ colnr_T n_before_newline_in_pat = mincol - s.cm_start.col;
+ long n_after_newline_in_sub;
+ if (!s.newline_in_sub) {
+ n_after_newline_in_sub = s.cm_end.col - s.cm_start.col;
+ } else {
+ n_after_newline_in_sub = s.cm_end.col;
+ }
+
+ if (s.newline_in_pat && !s.newline_in_sub) {
+ // -- Delete Pattern --
+ // 1. Move marks in the pattern
+ mincol = s.startpos.col + 1;
+ u_lnum = n_u_lnum;
+ assert(n_u_lnum == u_lnum);
+ extmark_copy_and_place(curbuf,
+ s.lnum, mincol,
+ u_lnum, n_after_newline_in_pat,
+ s.lnum, mincol,
+ kExtmarkUndo, true, NULL);
+ // 2. Move marks on last newline
+ mincol = mincol - n_before_newline_in_pat;
+ extmark_col_adjust(curbuf,
+ u_lnum,
+ n_after_newline_in_pat + 1,
+ -s.newline_in_pat,
+ mincol - n_after_newline_in_pat,
+ kExtmarkUndo);
+ // Take care of the lines after
+ extmark_adjust(curbuf,
+ u_lnum,
+ u_lnum,
+ MAXLNUM,
+ -s.newline_in_pat,
+ kExtmarkUndo,
+ false);
+ // 1. first insert the text in the substitutaion
+ extmark_col_adjust(curbuf,
+ s.lnum,
+ mincol + 1,
+ s.newline_in_sub,
+ n_after_newline_in_sub,
+ kExtmarkUndo);
+
+ } else {
+ // The data in sub_obj is as if the substituons above had already taken
+ // place. For our extmarks they haven't as we work from the bottom of the
+ // buffer up. Readjust the data.
+ n_u_lnum = s.lnum + s.endpos.lnum - s.startpos.lnum;
+ n_u_lnum = n_u_lnum - s.lnum_added;
+
+ // adjusted = L - (i-1)N
+ // where L = lnum value, N= lnum_added and i = iteration
+ linenr_T a_l_lnum = s.cm_start.lnum - ((i -1) * s.lnum_added);
+ linenr_T a_u_lnum = a_l_lnum + s.endpos.lnum;
+ assert(s.startpos.lnum == 0);
+
+ mincol = s.startpos.col + 1;
+ u_lnum = n_u_lnum;
+
+ if (!s.newline_in_pat && s.newline_in_sub) {
+ // -- Delete Pattern --
+ // 1. Move marks in the pattern
+ extmark_col_adjust_delete(curbuf,
+ a_l_lnum,
+ mincol + 1,
+ s.endpos.col + 1,
+ kExtmarkUndo,
+ s.eol);
+
+ extmark_adjust(curbuf,
+ a_u_lnum + 1,
+ MAXLNUM,
+ (long)s.newline_in_sub,
+ 0,
+ kExtmarkUndo,
+ false);
+ // 3. Insert
+ extmark_col_adjust(curbuf,
+ a_l_lnum,
+ mincol,
+ s.newline_in_sub,
+ (long)-mincol + 1 + n_after_newline_in_sub,
+ kExtmarkUndo);
+ } else if (s.newline_in_pat && s.newline_in_sub) {
+ if (s.lnum_added >= 0) {
+ linenr_T u_col = n_after_newline_in_pat == 0
+ ? 1 : n_after_newline_in_pat;
+ extmark_copy_and_place(curbuf,
+ a_l_lnum, mincol,
+ a_u_lnum, u_col,
+ a_l_lnum, mincol,
+ kExtmarkUndo, true, NULL);
+ // 2. Move marks on last newline
+ mincol = mincol - (colnr_T)n_before_newline_in_pat;
+ extmark_col_adjust(curbuf,
+ a_u_lnum,
+ (colnr_T)(n_after_newline_in_pat + 1),
+ -s.newline_in_pat,
+ mincol - n_after_newline_in_pat,
+ kExtmarkUndo);
+ // TODO(timeyyy): nothing to do here if lnum_added = 0
+ extmark_adjust(curbuf,
+ a_u_lnum + 1,
+ MAXLNUM,
+ (long)s.lnum_added,
+ 0,
+ kExtmarkUndo,
+ false);
+
+ extmark_col_adjust(curbuf,
+ a_l_lnum,
+ mincol + 1,
+ s.newline_in_sub,
+ (long)-mincol + n_after_newline_in_sub,
+ kExtmarkUndo);
+ } else {
+ mincol = s.startpos.col + 1;
+ a_l_lnum = s.startpos.lnum + 1;
+ a_u_lnum = s.endpos.lnum + 1;
+ extmark_copy_and_place(curbuf,
+ a_l_lnum, mincol,
+ a_u_lnum, n_after_newline_in_pat,
+ a_l_lnum, mincol,
+ kExtmarkUndo, true, NULL);
+ // 2. Move marks on last newline
+ mincol = mincol - (colnr_T)n_before_newline_in_pat;
+ extmark_col_adjust(curbuf,
+ a_u_lnum,
+ (colnr_T)(n_after_newline_in_pat + 1),
+ -s.newline_in_pat,
+ mincol - n_after_newline_in_pat,
+ kExtmarkUndo);
+ extmark_adjust(curbuf,
+ a_u_lnum,
+ a_u_lnum,
+ MAXLNUM,
+ s.lnum_added,
+ kExtmarkUndo,
+ false);
+ // 3. Insert
+ extmark_col_adjust(curbuf,
+ a_l_lnum,
+ mincol + 1,
+ s.newline_in_sub,
+ (long)-mincol + n_after_newline_in_sub,
+ kExtmarkUndo);
+ }
+ }
+ }
+}
+
/// Perform a substitution from line eap->line1 to line eap->line2 using the
/// command pointed to by eap->arg which should be of the form:
///
@@ -3260,6 +3453,17 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
int save_ma = 0;
int save_b_changed = curbuf->b_changed;
bool preview = (State & CMDPREVIEW);
+ extmark_sub_multi_vec_t extmark_sub_multi = KV_INITIAL_VALUE;
+ extmark_sub_single_vec_t extmark_sub_single = KV_INITIAL_VALUE;
+ linenr_T no_of_lines_changed = 0;
+ linenr_T newline_in_pat = 0;
+ linenr_T newline_in_sub = 0;
+
+ // inccomand tests fail without this check
+ if (!preview) {
+ // Requried for Undo to work for nsmarks,
+ u_save_cursor();
+ }
if (!global_busy) {
sub_nsubs = 0;
@@ -3418,6 +3622,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
// Check for a match on each line.
// If preview: limit to max('cmdwinheight', viewport).
linenr_T line2 = eap->line2;
+
for (linenr_T lnum = eap->line1;
lnum <= line2 && !got_quit && !aborting()
&& (!preview || preview_lines.lines_needed <= (linenr_T)p_cwh
@@ -3876,6 +4081,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
ADJUST_SUB_FIRSTLNUM();
+
// Now the trick is to replace CTRL-M chars with a real line
// break. This would make it impossible to insert a CTRL-M in
// the text. The line break can be avoided by preceding the
@@ -3890,7 +4096,9 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
*p1 = NUL; // truncate up to the CR
ml_append(lnum - 1, new_start,
(colnr_T)(p1 - new_start + 1), false);
- mark_adjust(lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false);
+ mark_adjust(lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false,
+ kExtmarkNOOP);
+
if (subflags.do_ask) {
appended_lines(lnum - 1, 1L);
} else {
@@ -3917,6 +4125,44 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
current_match.end.lnum = lnum;
}
+ // Adjust extmarks, by delete and then insert
+ if (!preview) {
+ newline_in_pat = (regmatch.endpos[0].lnum
+ - regmatch.startpos[0].lnum);
+ newline_in_sub = current_match.end.lnum - current_match.start.lnum;
+ if (newline_in_pat || newline_in_sub) {
+ ExtmarkSubMulti sub_multi;
+ no_of_lines_changed = newline_in_sub - newline_in_pat;
+
+ sub_multi.newline_in_pat = newline_in_pat;
+ sub_multi.newline_in_sub = newline_in_sub;
+ sub_multi.lnum = lnum;
+ sub_multi.lnum_added = no_of_lines_changed;
+ sub_multi.cm_start = current_match.start;
+ sub_multi.cm_end = current_match.end;
+
+ sub_multi.startpos = regmatch.startpos[0];
+ sub_multi.endpos = regmatch.endpos[0];
+ sub_multi.eol = extmark_eol_col(curbuf, lnum);
+
+ kv_push(extmark_sub_multi, sub_multi);
+ // Collect information required for moving extmarks WITHOUT \n, \r
+ } else {
+ no_of_lines_changed = 0;
+
+ if (regmatch.startpos[0].col != -1) {
+ ExtmarkSubSingle sub_single;
+ sub_single.sublen = sublen;
+ sub_single.lnum = lnum;
+ sub_single.startpos = regmatch.startpos[0];
+ sub_single.endpos = regmatch.endpos[0];
+
+ kv_push(extmark_sub_single, sub_single);
+ }
+ }
+ }
+
+
// 4. If subflags.do_all is set, find next match.
// Prevent endless loop with patterns that match empty
// strings, e.g. :s/$/pat/g or :s/[a-z]* /(&)/g.
@@ -3983,7 +4229,7 @@ skip:
ml_delete(lnum, false);
}
mark_adjust(lnum, lnum + nmatch_tl - 1,
- (long)MAXLNUM, -nmatch_tl, false);
+ (long)MAXLNUM, -nmatch_tl, false, kExtmarkNOOP);
if (subflags.do_ask) {
deleted_lines(lnum, nmatch_tl);
}
@@ -4159,6 +4405,35 @@ skip:
}
}
}
+ if (newline_in_pat || newline_in_sub) {
+ long n = (long)kv_size(extmark_sub_multi);
+ ExtmarkSubMulti sub_multi;
+ if (no_of_lines_changed < 0) {
+ for (i = 0; i < n; i++) {
+ sub_multi = kv_A(extmark_sub_multi, i);
+ extmark_move_regmatch_multi(sub_multi, i);
+ }
+ } else {
+ // Move extmarks in reverse order to avoid moving marks we just moved...
+ for (i = 0; i < n; i++) {
+ sub_multi = kv_Z(extmark_sub_multi, i);
+ extmark_move_regmatch_multi(sub_multi, n - i);
+ }
+ }
+ kv_destroy(extmark_sub_multi);
+ } else {
+ long n = (long)kv_size(extmark_sub_single);
+ ExtmarkSubSingle sub_single;
+ for (i = 0; i < n; i++) {
+ sub_single = kv_Z(extmark_sub_single, i);
+ extmark_move_regmatch_single(sub_single.startpos,
+ sub_single.endpos,
+ sub_single.lnum,
+ sub_single.sublen);
+ }
+
+ kv_destroy(extmark_sub_single);
+ }
kv_destroy(preview_lines.subresults);
diff --git a/src/nvim/lib/kbtree.h b/src/nvim/lib/kbtree.h
index 33aeff1d89..bef37f8ba9 100644
--- a/src/nvim/lib/kbtree.h
+++ b/src/nvim/lib/kbtree.h
@@ -25,6 +25,12 @@
* SUCH DAMAGE.
*/
+// Gotchas
+// -------
+//
+// if you delete from a kbtree while iterating over it you must use
+// kb_del_itr and not kb_del otherwise the iterator might point to freed memory.
+
#ifndef NVIM_LIB_KBTREE_H
#define NVIM_LIB_KBTREE_H
diff --git a/src/nvim/map.c b/src/nvim/map.c
index cdade5ee71..a4cf5304e0 100644
--- a/src/nvim/map.c
+++ b/src/nvim/map.c
@@ -184,6 +184,7 @@ MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER)
#define KVEC_INITIALIZER { .size = 0, .capacity = 0, .items = NULL }
MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER)
MAP_IMPL(String, handle_T, 0)
+MAP_IMPL(uint64_t, cstr_t, DEFAULT_INITIALIZER)
/// Deletes a key:value pair from a string:pointer map, and frees the
diff --git a/src/nvim/map.h b/src/nvim/map.h
index 75ab64cca4..073be6822a 100644
--- a/src/nvim/map.h
+++ b/src/nvim/map.h
@@ -42,6 +42,7 @@ MAP_DECLS(handle_T, ptr_t)
MAP_DECLS(String, MsgpackRpcRequestHandler)
MAP_DECLS(HlEntry, int)
MAP_DECLS(String, handle_T)
+MAP_DECLS(uint64_t, cstr_t)
#define map_new(T, U) map_##T##_##U##_new
#define map_free(T, U) map_##T##_##U##_free
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index e8f1651a6e..454e9e46f4 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -905,9 +905,10 @@ void mark_adjust(linenr_T line1,
linenr_T line2,
long amount,
long amount_after,
- bool end_temp)
+ bool end_temp,
+ ExtmarkOp op)
{
- mark_adjust_internal(line1, line2, amount, amount_after, true, end_temp);
+ mark_adjust_internal(line1, line2, amount, amount_after, true, end_temp, op);
}
// mark_adjust_nofold() does the same as mark_adjust() but without adjusting
@@ -916,14 +917,16 @@ void mark_adjust(linenr_T line1,
// calling foldMarkAdjust() with arguments line1, line2, amount, amount_after,
// for an example of why this may be necessary, see do_move().
void mark_adjust_nofold(linenr_T line1, linenr_T line2, long amount,
- long amount_after, bool end_temp)
+ long amount_after, bool end_temp,
+ ExtmarkOp op)
{
- mark_adjust_internal(line1, line2, amount, amount_after, false, end_temp);
+ mark_adjust_internal(line1, line2, amount, amount_after, false, end_temp, op);
}
static void mark_adjust_internal(linenr_T line1, linenr_T line2,
long amount, long amount_after,
- bool adjust_folds, bool end_temp)
+ bool adjust_folds, bool end_temp,
+ ExtmarkOp op)
{
int i;
int fnum = curbuf->b_fnum;
@@ -979,6 +982,9 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2,
sign_mark_adjust(line1, line2, amount, amount_after);
bufhl_mark_adjust(curbuf, line1, line2, amount, amount_after, end_temp);
+ if (op != kExtmarkNOOP) {
+ extmark_adjust(curbuf, line1, line2, amount, amount_after, op, end_temp);
+ }
}
/* previous context mark */
@@ -1090,7 +1096,7 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2,
// cursor is inside them.
void mark_col_adjust(
linenr_T lnum, colnr_T mincol, long lnum_amount, long col_amount,
- int spaces_removed)
+ int spaces_removed, ExtmarkOp op)
{
int i;
int fnum = curbuf->b_fnum;
@@ -1110,6 +1116,13 @@ void mark_col_adjust(
col_adjust(&(namedfm[i].fmark.mark));
}
+ // Extmarks
+ if (op != kExtmarkNOOP) {
+ // TODO(timeyyy): consider spaces_removed? (behave like a delete)
+ extmark_col_adjust(curbuf, lnum, mincol, lnum_amount, col_amount,
+ kExtmarkUndo);
+ }
+
/* last Insert position */
col_adjust(&(curbuf->b_last_insert.mark));
diff --git a/src/nvim/mark_extended.c b/src/nvim/mark_extended.c
new file mode 100644
index 0000000000..cb5f45d88c
--- /dev/null
+++ b/src/nvim/mark_extended.c
@@ -0,0 +1,1262 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+// Implements extended marks for plugins
+// Each Mark exists in a btree of lines containing btrees of columns.
+//
+// The btree provides efficent range lookups.
+// A map of pointers to the marks is used for fast lookup by mark id.
+//
+// Marks are moved by calls to: extmark_col_adjust, extmark_adjust, or
+// extmark_col_adjust_delete which are based on col_adjust and mark_adjust from
+// mark.c
+//
+// Undo/Redo of marks is implemented by storing the call arguments to
+// extmark_col_adjust or extmark_adjust. The list of arguments
+// is applied in extmark_apply_undo. The only case where we have to
+// copy extmarks is for the area being effected by a delete.
+//
+// Marks live in namespaces that allow plugins/users to segregate marks
+// from other users, namespaces have to be initialized before usage
+//
+// For possible ideas for efficency improvements see:
+// http://blog.atom.io/2015/06/16/optimizing-an-important-atom-primitive.html
+// Other implementations exist in gtk and tk toolkits.
+//
+// Deleting marks only happens explicitly extmark_del, deleteing over a
+// range of marks will only move the marks.
+//
+// deleting on a mark will leave it in that same position unless it is on
+// the eol of the line.
+
+#include <assert.h>
+#include "nvim/vim.h"
+#include "charset.h"
+#include "nvim/mark_extended.h"
+#include "nvim/memline.h"
+#include "nvim/pos.h"
+#include "nvim/globals.h"
+#include "nvim/map.h"
+#include "nvim/lib/kbtree.h"
+#include "nvim/undo.h"
+#include "nvim/buffer.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "mark_extended.c.generated.h"
+#endif
+
+
+/// Create or update an extmark
+///
+/// must not be used during iteration!
+/// @returns whether a new mark was created
+int extmark_set(buf_T *buf,
+ uint64_t ns,
+ uint64_t id,
+ linenr_T lnum,
+ colnr_T col,
+ ExtmarkOp op)
+{
+ ExtendedMark *extmark = extmark_from_id(buf, ns, id);
+ if (!extmark) {
+ extmark_create(buf, ns, id, lnum, col, op);
+ return true;
+ } else {
+ ExtMarkLine *extline = extmark->line;
+ extmark_update(extmark, buf, ns, id, lnum, col, op, NULL);
+ if (kb_size(&extline->items) == 0) {
+ kb_del(extlines, &buf->b_extlines, extline);
+ extline_free(extline);
+ }
+ return false;
+ }
+}
+
+// Remove an extmark
+// Returns 0 on missing id
+int extmark_del(buf_T *buf,
+ uint64_t ns,
+ uint64_t id,
+ ExtmarkOp op)
+{
+ ExtendedMark *extmark = extmark_from_id(buf, ns, id);
+ if (!extmark) {
+ return 0;
+ }
+ return extmark_delete(extmark, buf, ns, id, op);
+}
+
+// Free extmarks in a ns between lines
+// if ns = 0, it means clear all namespaces
+void extmark_clear(buf_T *buf,
+ uint64_t ns,
+ linenr_T l_lnum,
+ linenr_T u_lnum,
+ ExtmarkOp undo)
+{
+ if (!buf->b_extmark_ns) {
+ return;
+ }
+
+ bool marks_cleared = false;
+ if (undo == kExtmarkUndo) {
+ // Copy marks that would be effected by clear
+ u_extmark_copy(buf, ns, l_lnum, 0, u_lnum, MAXCOL);
+ }
+
+ bool all_ns = ns == 0 ? true : false;
+ ExtmarkNs *ns_obj;
+ if (!all_ns) {
+ ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns);
+ if (!ns_obj) {
+ // nothing to do
+ return;
+ }
+ }
+
+ FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, {
+ FOR_ALL_EXTMARKS_IN_LINE(extline->items, 0, MAXCOL, {
+ if (extmark->ns_id == ns || all_ns) {
+ marks_cleared = true;
+ if (all_ns) {
+ ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, extmark->ns_id);
+ } else {
+ ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns);
+ }
+ pmap_del(uint64_t)(ns_obj->map, extmark->mark_id);
+ kb_del_itr(markitems, &extline->items, &mitr);
+ }
+ });
+ if (kb_size(&extline->items) == 0) {
+ kb_del_itr(extlines, &buf->b_extlines, &itr);
+ extline_free(extline);
+ }
+ });
+
+ // Record the undo for the actual move
+ if (marks_cleared && undo == kExtmarkUndo) {
+ u_extmark_clear(buf, ns, l_lnum, u_lnum);
+ }
+}
+
+// Returns the position of marks between a range,
+// marks found at the start or end index will be included,
+// if upper_lnum or upper_col are negative the buffer
+// will be searched to the start, or end
+// dir can be set to control the order of the array
+// amount = amount of marks to find or -1 for all
+ExtmarkArray extmark_get(buf_T *buf,
+ uint64_t ns,
+ linenr_T l_lnum,
+ colnr_T l_col,
+ linenr_T u_lnum,
+ colnr_T u_col,
+ int64_t amount,
+ bool reverse)
+{
+ ExtmarkArray array = KV_INITIAL_VALUE;
+ // Find all the marks
+ if (!reverse) {
+ FOR_ALL_EXTMARKS(buf, ns, l_lnum, l_col, u_lnum, u_col, {
+ if (extmark->ns_id == ns) {
+ kv_push(array, extmark);
+ if (kv_size(array) == (size_t)amount) {
+ return array;
+ }
+ }
+ })
+ } else {
+ FOR_ALL_EXTMARKS_PREV(buf, ns, l_lnum, l_col, u_lnum, u_col, {
+ if (extmark->ns_id == ns) {
+ kv_push(array, extmark);
+ if (kv_size(array) == (size_t)amount) {
+ return array;
+ }
+ }
+ })
+ }
+ return array;
+}
+
+static void extmark_create(buf_T *buf,
+ uint64_t ns,
+ uint64_t id,
+ linenr_T lnum,
+ colnr_T col,
+ ExtmarkOp op)
+{
+ if (!buf->b_extmark_ns) {
+ buf->b_extmark_ns = pmap_new(uint64_t)();
+ }
+ ExtmarkNs *ns_obj = NULL;
+ ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns);
+ // Initialize a new namespace for this buffer
+ if (!ns_obj) {
+ ns_obj = xmalloc(sizeof(ExtmarkNs));
+ ns_obj->map = pmap_new(uint64_t)();
+ pmap_put(uint64_t)(buf->b_extmark_ns, ns, ns_obj);
+ }
+
+ // Create or get a line
+ ExtMarkLine *extline = extline_ref(buf, lnum, true);
+ // Create and put mark on the line
+ extmark_put(col, id, extline, ns);
+
+ // Marks do not have stable address so we have to look them up
+ // by using the line instead of the mark
+ pmap_put(uint64_t)(ns_obj->map, id, extline);
+ if (op != kExtmarkNoUndo) {
+ u_extmark_set(buf, ns, id, lnum, col, kExtmarkSet);
+ }
+
+ // Set a free id so extmark_free_id_get works
+ extmark_free_id_set(ns_obj, id);
+}
+
+// update the position of an extmark
+// to update while iterating pass the markitems itr
+static void extmark_update(ExtendedMark *extmark,
+ buf_T *buf,
+ uint64_t ns,
+ uint64_t id,
+ linenr_T lnum,
+ colnr_T col,
+ ExtmarkOp op,
+ kbitr_t(markitems) *mitr)
+{
+ assert(op != kExtmarkNOOP);
+ if (op != kExtmarkNoUndo) {
+ u_extmark_update(buf, ns, id, extmark->line->lnum, extmark->col,
+ lnum, col);
+ }
+ ExtMarkLine *old_line = extmark->line;
+ // Move the mark to a new line and update column
+ if (old_line->lnum != lnum) {
+ ExtMarkLine *ref_line = extline_ref(buf, lnum, true);
+ extmark_put(col, id, ref_line, ns);
+ // Update the hashmap
+ ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns);
+ pmap_put(uint64_t)(ns_obj->map, id, ref_line);
+ // Delete old mark
+ if (mitr != NULL) {
+ kb_del_itr(markitems, &(old_line->items), mitr);
+ } else {
+ kb_del(markitems, &old_line->items, *extmark);
+ }
+ // Just update the column
+ } else {
+ if (mitr != NULL) {
+ // The btree stays organized during iteration with kbitr_t
+ extmark->col = col;
+ } else {
+ // Keep the btree in order
+ kb_del(markitems, &old_line->items, *extmark);
+ extmark_put(col, id, old_line, ns);
+ }
+ }
+}
+
+static int extmark_delete(ExtendedMark *extmark,
+ buf_T *buf,
+ uint64_t ns,
+ uint64_t id,
+ ExtmarkOp op)
+{
+ if (op != kExtmarkNoUndo) {
+ u_extmark_set(buf, ns, id, extmark->line->lnum, extmark->col,
+ kExtmarkDel);
+ }
+
+ // Remove our key from the namespace
+ ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns);
+ pmap_del(uint64_t)(ns_obj->map, id);
+
+ // Remove the mark mark from the line
+ ExtMarkLine *extline = extmark->line;
+ kb_del(markitems, &extline->items, *extmark);
+ // Remove the line if there are no more marks in the line
+ if (kb_size(&extline->items) == 0) {
+ kb_del(extlines, &buf->b_extlines, extline);
+ extline_free(extline);
+ }
+ return true;
+}
+
+// Lookup an extmark by id
+ExtendedMark *extmark_from_id(buf_T *buf, uint64_t ns, uint64_t id)
+{
+ if (!buf->b_extmark_ns) {
+ return NULL;
+ }
+ ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns);
+ if (!ns_obj || !kh_size(ns_obj->map->table)) {
+ return NULL;
+ }
+ ExtMarkLine *extline = pmap_get(uint64_t)(ns_obj->map, id);
+ if (!extline) {
+ return NULL;
+ }
+
+ FOR_ALL_EXTMARKS_IN_LINE(extline->items, 0, MAXCOL, {
+ if (extmark->ns_id == ns
+ && extmark->mark_id == id) {
+ return extmark;
+ }
+ })
+ return NULL;
+}
+
+// Lookup an extmark by position
+ExtendedMark *extmark_from_pos(buf_T *buf,
+ uint64_t ns, linenr_T lnum, colnr_T col)
+{
+ if (!buf->b_extmark_ns) {
+ return NULL;
+ }
+ FOR_ALL_EXTMARKS(buf, ns, lnum, col, lnum, col, {
+ if (extmark->ns_id == ns) {
+ if (extmark->col == col) {
+ return extmark;
+ }
+ }
+ })
+ return NULL;
+}
+
+// Returns an avaliable id in a namespace
+uint64_t extmark_free_id_get(buf_T *buf, uint64_t ns)
+{
+ if (!buf->b_extmark_ns) {
+ return 1;
+ }
+ ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns);
+ if (!ns_obj) {
+ return 1;
+ }
+ return ns_obj->free_id;
+}
+
+// Set the next free id in a namesapce
+static void extmark_free_id_set(ExtmarkNs *ns_obj, uint64_t id)
+{
+ // Simply Heurstic, the largest id + 1
+ ns_obj->free_id = id + 1;
+}
+
+// free extmarks from the buffer
+void extmark_free_all(buf_T *buf)
+{
+ if (!buf->b_extmark_ns) {
+ return;
+ }
+
+ uint64_t ns;
+ ExtmarkNs *ns_obj;
+
+ FOR_ALL_EXTMARKLINES(buf, 1, MAXLNUM, {
+ kb_del_itr(extlines, &buf->b_extlines, &itr);
+ extline_free(extline);
+ })
+
+ map_foreach(buf->b_extmark_ns, ns, ns_obj, {
+ (void)ns;
+ pmap_free(uint64_t)(ns_obj->map);
+ xfree(ns_obj);
+ });
+
+ pmap_free(uint64_t)(buf->b_extmark_ns);
+
+ // k?_init called to set pointers to NULL
+ kb_destroy(extlines, (&buf->b_extlines));
+ kb_init(&buf->b_extlines);
+
+ kv_destroy(buf->b_extmark_move_space);
+ kv_init(buf->b_extmark_move_space);
+}
+
+
+// Save info for undo/redo of set marks
+static void u_extmark_set(buf_T *buf,
+ uint64_t ns,
+ uint64_t id,
+ linenr_T lnum,
+ colnr_T col,
+ UndoObjectType undo_type)
+{
+ u_header_T *uhp = force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ ExtmarkSet set;
+ set.ns_id = ns;
+ set.mark_id = id;
+ set.lnum = lnum;
+ set.col = col;
+
+ ExtmarkUndoObject undo = { .type = undo_type,
+ .data.set = set };
+
+ kv_push(uhp->uh_extmark, undo);
+}
+
+// Save info for undo/redo of deleted marks
+static void u_extmark_update(buf_T *buf,
+ uint64_t ns,
+ uint64_t id,
+ linenr_T old_lnum,
+ colnr_T old_col,
+ linenr_T lnum,
+ colnr_T col)
+{
+ u_header_T *uhp = force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ ExtmarkUpdate update;
+ update.ns_id = ns;
+ update.mark_id = id;
+ update.old_lnum = old_lnum;
+ update.old_col = old_col;
+ update.lnum = lnum;
+ update.col = col;
+
+ ExtmarkUndoObject undo = { .type = kExtmarkUpdate,
+ .data.update = update };
+ kv_push(uhp->uh_extmark, undo);
+}
+
+// Hueristic works only for when the user is typing in insert mode
+// - Instead of 1 undo object for each char inserted,
+// we create 1 undo objet for all text inserted before the user hits esc
+// Return True if we compacted else False
+static bool u_compact_col_adjust(buf_T *buf,
+ linenr_T lnum,
+ colnr_T mincol,
+ long lnum_amount,
+ long col_amount)
+{
+ u_header_T *uhp = force_get_undo_header(buf);
+ if (!uhp) {
+ return false;
+ }
+
+ if (kv_size(uhp->uh_extmark) < 1) {
+ return false;
+ }
+ // Check the last action
+ ExtmarkUndoObject object = kv_last(uhp->uh_extmark);
+
+ if (object.type != kColAdjust) {
+ return false;
+ }
+ ColAdjust undo = object.data.col_adjust;
+ bool compactable = false;
+
+ if (!undo.lnum_amount && !lnum_amount) {
+ if (undo.lnum == lnum) {
+ if ((undo.mincol + undo.col_amount) >= mincol) {
+ compactable = true;
+ } } }
+
+ if (!compactable) {
+ return false;
+ }
+
+ undo.col_amount = undo.col_amount + col_amount;
+ ExtmarkUndoObject new_undo = { .type = kColAdjust,
+ .data.col_adjust = undo };
+ kv_last(uhp->uh_extmark) = new_undo;
+ return true;
+}
+
+// Save col_adjust info so we can undo/redo
+void u_extmark_col_adjust(buf_T *buf,
+ linenr_T lnum,
+ colnr_T mincol,
+ long lnum_amount,
+ long col_amount)
+{
+ u_header_T *uhp = force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ if (!u_compact_col_adjust(buf, lnum, mincol, lnum_amount, col_amount)) {
+ ColAdjust col_adjust;
+ col_adjust.lnum = lnum;
+ col_adjust.mincol = mincol;
+ col_adjust.lnum_amount = lnum_amount;
+ col_adjust.col_amount = col_amount;
+
+ ExtmarkUndoObject undo = { .type = kColAdjust,
+ .data.col_adjust = col_adjust };
+
+ kv_push(uhp->uh_extmark, undo);
+ }
+}
+
+// Save col_adjust_delete info so we can undo/redo
+void u_extmark_col_adjust_delete(buf_T *buf,
+ linenr_T lnum,
+ colnr_T mincol,
+ colnr_T endcol,
+ int eol)
+{
+ u_header_T *uhp = force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ ColAdjustDelete col_adjust_delete;
+ col_adjust_delete.lnum = lnum;
+ col_adjust_delete.mincol = mincol;
+ col_adjust_delete.endcol = endcol;
+ col_adjust_delete.eol = eol;
+
+ ExtmarkUndoObject undo = { .type = kColAdjustDelete,
+ .data.col_adjust_delete = col_adjust_delete };
+
+ kv_push(uhp->uh_extmark, undo);
+}
+
+// Save adjust info so we can undo/redo
+static void u_extmark_adjust(buf_T * buf,
+ linenr_T line1,
+ linenr_T line2,
+ long amount,
+ long amount_after)
+{
+ u_header_T *uhp = force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ Adjust adjust;
+ adjust.line1 = line1;
+ adjust.line2 = line2;
+ adjust.amount = amount;
+ adjust.amount_after = amount_after;
+
+ ExtmarkUndoObject undo = { .type = kLineAdjust,
+ .data.adjust = adjust };
+
+ kv_push(uhp->uh_extmark, undo);
+}
+
+// save info to undo/redo a :move
+void u_extmark_move(buf_T *buf,
+ linenr_T line1,
+ linenr_T line2,
+ linenr_T last_line,
+ linenr_T dest,
+ linenr_T num_lines,
+ linenr_T extra)
+{
+ u_header_T *uhp = force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ AdjustMove move;
+ move.line1 = line1;
+ move.line2 = line2;
+ move.last_line = last_line;
+ move.dest = dest;
+ move.num_lines = num_lines;
+ move.extra = extra;
+
+ ExtmarkUndoObject undo = { .type = kAdjustMove,
+ .data.move = move };
+
+ kv_push(uhp->uh_extmark, undo);
+}
+
+// copy extmarks data between range, useful when we cannot simply reverse
+// the operation. This will do nothing on redo, enforces correct position when
+// undo.
+// if ns = 0, it means copy all namespaces
+void u_extmark_copy(buf_T *buf,
+ uint64_t ns,
+ linenr_T l_lnum,
+ colnr_T l_col,
+ linenr_T u_lnum,
+ colnr_T u_col)
+{
+ u_header_T *uhp = force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ bool all_ns = ns == 0 ? true : false;
+
+ ExtmarkCopy copy;
+ ExtmarkUndoObject undo;
+ FOR_ALL_EXTMARKS(buf, 1, l_lnum, l_col, u_lnum, u_col, {
+ if (all_ns || extmark->ns_id == ns) {
+ copy.ns_id = extmark->ns_id;
+ copy.mark_id = extmark->mark_id;
+ copy.lnum = extmark->line->lnum;
+ copy.col = extmark->col;
+
+ undo.data.copy = copy;
+ undo.type = kExtmarkCopy;
+ kv_push(uhp->uh_extmark, undo);
+ }
+ });
+}
+
+void u_extmark_copy_place(buf_T *buf,
+ linenr_T l_lnum,
+ colnr_T l_col,
+ linenr_T u_lnum,
+ colnr_T u_col,
+ linenr_T p_lnum,
+ colnr_T p_col)
+{
+ u_header_T *uhp = force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ ExtmarkCopyPlace copy_place;
+ copy_place.l_lnum = l_lnum;
+ copy_place.l_col = l_col;
+ copy_place.u_lnum = u_lnum;
+ copy_place.u_col = u_col;
+ copy_place.p_lnum = p_lnum;
+ copy_place.p_col = p_col;
+
+ ExtmarkUndoObject undo = { .type = kExtmarkCopyPlace,
+ .data.copy_place = copy_place };
+
+ kv_push(uhp->uh_extmark, undo);
+}
+
+// Save info for undo/redo of extmark_clear
+static void u_extmark_clear(buf_T *buf,
+ uint64_t ns,
+ linenr_T l_lnum,
+ linenr_T u_lnum)
+{
+ u_header_T *uhp = force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ ExtmarkClear clear;
+ clear.ns_id = ns;
+ clear.l_lnum = l_lnum;
+ clear.u_lnum = u_lnum;
+
+ ExtmarkUndoObject undo = { .type = kExtmarkClear,
+ .data.clear = clear };
+ kv_push(uhp->uh_extmark, undo);
+}
+
+// undo or redo an extmark operation
+void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo)
+{
+ linenr_T lnum;
+ colnr_T mincol;
+ long lnum_amount;
+ long col_amount;
+ linenr_T line1;
+ linenr_T line2;
+ long amount;
+ long amount_after;
+
+ // use extmark_col_adjust
+ if (undo_info.type == kColAdjust) {
+ // Undo
+ if (undo) {
+ lnum = (undo_info.data.col_adjust.lnum
+ + undo_info.data.col_adjust.lnum_amount);
+ lnum_amount = -undo_info.data.col_adjust.lnum_amount;
+ col_amount = -undo_info.data.col_adjust.col_amount;
+ mincol = (undo_info.data.col_adjust.mincol
+ + (colnr_T)undo_info.data.col_adjust.col_amount);
+ // Redo
+ } else {
+ lnum = undo_info.data.col_adjust.lnum;
+ col_amount = undo_info.data.col_adjust.col_amount;
+ lnum_amount = undo_info.data.col_adjust.lnum_amount;
+ mincol = undo_info.data.col_adjust.mincol;
+ }
+ extmark_col_adjust(curbuf,
+ lnum, mincol, lnum_amount, col_amount, kExtmarkNoUndo);
+ // use extmark_col_adjust_delete
+ } else if (undo_info.type == kColAdjustDelete) {
+ if (undo) {
+ mincol = undo_info.data.col_adjust_delete.mincol;
+ col_amount = (undo_info.data.col_adjust_delete.endcol
+ - undo_info.data.col_adjust_delete.mincol) + 1;
+ extmark_col_adjust(curbuf,
+ undo_info.data.col_adjust_delete.lnum,
+ mincol,
+ 0,
+ col_amount,
+ kExtmarkNoUndo);
+ // Redo
+ } else {
+ extmark_col_adjust_delete(curbuf,
+ undo_info.data.col_adjust_delete.lnum,
+ undo_info.data.col_adjust_delete.mincol,
+ undo_info.data.col_adjust_delete.endcol,
+ kExtmarkNoUndo,
+ undo_info.data.col_adjust_delete.eol);
+ }
+ // use extmark_adjust
+ } else if (undo_info.type == kLineAdjust) {
+ if (undo) {
+ // Undo - call signature type one - insert now
+ if (undo_info.data.adjust.amount == MAXLNUM) {
+ line1 = undo_info.data.adjust.line1;
+ line2 = MAXLNUM;
+ amount = -undo_info.data.adjust.amount_after;
+ amount_after = 0;
+ // Undo - call singature type two - delete now
+ } else if (undo_info.data.adjust.line2 == MAXLNUM) {
+ line1 = undo_info.data.adjust.line1;
+ line2 = undo_info.data.adjust.line2;
+ amount = -undo_info.data.adjust.amount;
+ amount_after = undo_info.data.adjust.amount_after;
+ // Undo - call signature three - move lines
+ } else {
+ line1 = (undo_info.data.adjust.line1
+ + undo_info.data.adjust.amount);
+ line2 = (undo_info.data.adjust.line2
+ + undo_info.data.adjust.amount);
+ amount = -undo_info.data.adjust.amount;
+ amount_after = -undo_info.data.adjust.amount_after;
+ }
+ // redo
+ } else {
+ line1 = undo_info.data.adjust.line1;
+ line2 = undo_info.data.adjust.line2;
+ amount = undo_info.data.adjust.amount;
+ amount_after = undo_info.data.adjust.amount_after;
+ }
+ extmark_adjust(curbuf,
+ line1, line2, amount, amount_after, kExtmarkNoUndo, false);
+ // kExtmarkCopy
+ } else if (undo_info.type == kExtmarkCopy) {
+ // Redo should be handled by kColAdjustDelete or kExtmarkCopyPlace
+ if (undo) {
+ extmark_set(curbuf,
+ undo_info.data.copy.ns_id,
+ undo_info.data.copy.mark_id,
+ undo_info.data.copy.lnum,
+ undo_info.data.copy.col,
+ kExtmarkNoUndo);
+ }
+ // uses extmark_copy_and_place
+ } else if (undo_info.type == kExtmarkCopyPlace) {
+ // Redo, undo is handle by kExtmarkCopy
+ if (!undo) {
+ extmark_copy_and_place(curbuf,
+ undo_info.data.copy_place.l_lnum,
+ undo_info.data.copy_place.l_col,
+ undo_info.data.copy_place.u_lnum,
+ undo_info.data.copy_place.u_col,
+ undo_info.data.copy_place.p_lnum,
+ undo_info.data.copy_place.p_col,
+ kExtmarkNoUndo, true, NULL);
+ }
+ // kExtmarkClear
+ } else if (undo_info.type == kExtmarkClear) {
+ // Redo, undo is handle by kExtmarkCopy
+ if (!undo) {
+ extmark_clear(curbuf,
+ undo_info.data.clear.ns_id,
+ undo_info.data.clear.l_lnum,
+ undo_info.data.clear.u_lnum,
+ kExtmarkNoUndo);
+ }
+ // kAdjustMove
+ } else if (undo_info.type == kAdjustMove) {
+ apply_undo_move(undo_info, undo);
+ // extmark_set
+ } else if (undo_info.type == kExtmarkSet) {
+ if (undo) {
+ extmark_del(curbuf,
+ undo_info.data.set.ns_id,
+ undo_info.data.set.mark_id,
+ kExtmarkNoUndo);
+ // Redo
+ } else {
+ extmark_set(curbuf,
+ undo_info.data.set.ns_id,
+ undo_info.data.set.mark_id,
+ undo_info.data.set.lnum,
+ undo_info.data.set.col,
+ kExtmarkNoUndo);
+ }
+ // extmark_set into update
+ } else if (undo_info.type == kExtmarkUpdate) {
+ if (undo) {
+ extmark_set(curbuf,
+ undo_info.data.update.ns_id,
+ undo_info.data.update.mark_id,
+ undo_info.data.update.old_lnum,
+ undo_info.data.update.old_col,
+ kExtmarkNoUndo);
+ // Redo
+ } else {
+ extmark_set(curbuf,
+ undo_info.data.update.ns_id,
+ undo_info.data.update.mark_id,
+ undo_info.data.update.lnum,
+ undo_info.data.update.col,
+ kExtmarkNoUndo);
+ }
+ // extmark_del
+ } else if (undo_info.type == kExtmarkDel) {
+ if (undo) {
+ extmark_set(curbuf,
+ undo_info.data.set.ns_id,
+ undo_info.data.set.mark_id,
+ undo_info.data.set.lnum,
+ undo_info.data.set.col,
+ kExtmarkNoUndo);
+ // Redo
+ } else {
+ extmark_del(curbuf,
+ undo_info.data.set.ns_id,
+ undo_info.data.set.mark_id,
+ kExtmarkNoUndo);
+ }
+ }
+}
+
+// undo/redo an kExtmarkMove operation
+static void apply_undo_move(ExtmarkUndoObject undo_info, bool undo)
+{
+ // 3 calls are required , see comment in function do_move (ex_cmds.c)
+ linenr_T line1 = undo_info.data.move.line1;
+ linenr_T line2 = undo_info.data.move.line2;
+ linenr_T last_line = undo_info.data.move.last_line;
+ linenr_T dest = undo_info.data.move.dest;
+ linenr_T num_lines = undo_info.data.move.num_lines;
+ linenr_T extra = undo_info.data.move.extra;
+
+ if (undo) {
+ if (dest >= line2) {
+ extmark_adjust(curbuf,
+ dest - num_lines + 1,
+ dest,
+ last_line - dest + num_lines - 1,
+ 0L,
+ kExtmarkNoUndo,
+ true);
+ extmark_adjust(curbuf,
+ dest - line2,
+ dest - line1,
+ dest - line2,
+ 0L,
+ kExtmarkNoUndo,
+ false);
+ } else {
+ extmark_adjust(curbuf,
+ line1-num_lines,
+ line2-num_lines,
+ last_line - (line1-num_lines),
+ 0L,
+ kExtmarkNoUndo,
+ true);
+ extmark_adjust(curbuf,
+ (line1-num_lines) + 1,
+ (line2-num_lines) + 1,
+ -num_lines,
+ 0L,
+ kExtmarkNoUndo,
+ false);
+ }
+ extmark_adjust(curbuf,
+ last_line,
+ last_line + num_lines - 1,
+ line1 - last_line,
+ 0L,
+ kExtmarkNoUndo,
+ true);
+ // redo
+ } else {
+ extmark_adjust(curbuf,
+ line1,
+ line2,
+ last_line - line2,
+ 0L,
+ kExtmarkNoUndo,
+ true);
+ if (dest >= line2) {
+ extmark_adjust(curbuf,
+ line2 + 1,
+ dest,
+ -num_lines,
+ 0L,
+ kExtmarkNoUndo,
+ false);
+ } else {
+ extmark_adjust(curbuf,
+ dest + 1,
+ line1 - 1,
+ num_lines,
+ 0L,
+ kExtmarkNoUndo,
+ false);
+ }
+ extmark_adjust(curbuf,
+ last_line - num_lines + 1,
+ last_line,
+ -(last_line - dest - extra),
+ 0L,
+ kExtmarkNoUndo,
+ true);
+ }
+}
+
+// for anything other than deletes
+// Return, desired col amount where the adjustment should take place
+// (not taking) eol into account
+static long update_constantly(colnr_T _, colnr_T __, long col_amount)
+{
+ return col_amount;
+}
+
+// for deletes,
+// Return, desired col amount where the adjustment should take place
+// (not taking) eol into account
+static long update_variably(colnr_T mincol, colnr_T current, long endcol)
+{
+ colnr_T start_effected_range = mincol - 1;
+ long col_amount;
+ // When mark inside range
+ if (current < endcol) {
+ col_amount = -(current - start_effected_range);
+ // Mark outside of range
+ } else {
+ // -1 because a delete of width 0 should still move marks
+ col_amount = -(endcol - mincol) - 1;
+ }
+ return col_amount;
+}
+
+
+/// Get the column position for EOL on a line
+///
+/// If the lnum doesn't exist, returns 0
+colnr_T extmark_eol_col(buf_T *buf, linenr_T lnum)
+{
+ if (lnum > buf->b_ml.ml_line_count) {
+ return 0;
+ }
+ return (colnr_T)STRLEN(ml_get_buf(buf, lnum, false)) + 1;
+}
+
+
+// Adjust columns and rows for extmarks
+// based off mark_col_adjust in mark.c
+// returns true if something was moved otherwise false
+static bool extmark_col_adjust_impl(buf_T *buf, linenr_T lnum,
+ colnr_T mincol, long lnum_amount,
+ long (*calc_amount)(colnr_T, colnr_T, long),
+ long func_arg)
+{
+ bool marks_exist = false;
+ colnr_T *cp;
+ long col_amount;
+
+ ExtMarkLine *extline = extline_ref(buf, lnum, false);
+ if (!extline) {
+ return false;
+ }
+
+ FOR_ALL_EXTMARKS_IN_LINE(extline->items, mincol, MAXCOL, {
+ marks_exist = true;
+ cp = &(extmark->col);
+
+ col_amount = (*calc_amount)(mincol, *cp, func_arg);
+ // No update required for this guy
+ if (col_amount == 0 && lnum_amount == 0) {
+ continue;
+ }
+
+ // Set mark to start of line
+ if (col_amount < 0
+ && *cp <= (colnr_T)-col_amount) { // TODO(timeyyy): does mark.c
+ // need this line?
+ extmark_update(extmark, buf, extmark->ns_id, extmark->mark_id,
+ extline->lnum + lnum_amount,
+ 1, kExtmarkNoUndo, &mitr);
+ // Update the mark
+ } else {
+ // Note: The undo is handled by u_extmark_col_adjust, NoUndo here
+ extmark_update(extmark, buf, extmark->ns_id, extmark->mark_id,
+ extline->lnum + lnum_amount,
+ *cp + (colnr_T)col_amount, kExtmarkNoUndo, &mitr);
+ }
+ })
+
+ if (kb_size(&extline->items) == 0) {
+ kb_del(extlines, &buf->b_extlines, extline);
+ extline_free(extline);
+ }
+
+ return marks_exist;
+}
+
+// Adjust columns and rows for extmarks
+//
+// based off mark_col_adjust in mark.c
+// use extmark_col_adjust_impl to move columns by inserting
+// Doesn't take the eol into consideration (possible to put marks in invalid
+// positions)
+void extmark_col_adjust(buf_T *buf, linenr_T lnum,
+ colnr_T mincol, long lnum_amount,
+ long col_amount, ExtmarkOp undo)
+{
+ assert(col_amount > INT_MIN && col_amount <= INT_MAX);
+
+ bool marks_moved = extmark_col_adjust_impl(buf, lnum, mincol, lnum_amount,
+ &update_constantly, col_amount);
+
+ if (undo == kExtmarkUndo && marks_moved) {
+ u_extmark_col_adjust(buf, lnum, mincol, lnum_amount, col_amount);
+ }
+}
+
+// Adjust marks after a delete on a line
+//
+// Automatically readjusts to take the eol into account
+// TODO(timeyyy): change mincol to be for the mark to be copied, not moved
+//
+// @param mincol First column that needs to be moved (start of delete range) + 1
+// @param endcol Last column which needs to be copied (end of delete range + 1)
+void extmark_col_adjust_delete(buf_T *buf, linenr_T lnum,
+ colnr_T mincol, colnr_T endcol,
+ ExtmarkOp undo, int _eol)
+{
+ colnr_T start_effected_range = mincol;
+ // TODO(timeyyy): understand why this has to be uncommented out for the
+ // tests to pass.. shouldn't this be required?
+ // why is this even being called if it's not neeed?
+
+ // Some tests in the vim suite were hitting the assertion that was here.
+ // functional/ui/mouse_spec/
+ // We don't know what to do in this case so just bail!
+
+ // if (start_effected_range <= endcol) {
+ // return;
+ // }
+
+ bool marks_moved;
+ if (undo == kExtmarkUndo) {
+ // Copy marks that would be effected by delete
+ // -1 because we need to restore if a mark existed at the start pos
+ u_extmark_copy(buf, 0, lnum, start_effected_range, lnum, endcol);
+ }
+
+ marks_moved = extmark_col_adjust_impl(buf, lnum, mincol, 0,
+ &update_variably, (long)endcol);
+
+ // Deletes at the end of the line have different behaviour than the normal
+ // case when deleted.
+ // Cleanup any marks that are floating beyond the end of line.
+ // we allow this to be passed in as well because the buffer may have already
+ // been mutated.
+ int eol = _eol;
+ if (!eol) {
+ eol = extmark_eol_col(buf, lnum);
+ }
+ FOR_ALL_EXTMARKS(buf, 1, lnum, eol, lnum, -1, {
+ extmark_update(extmark, buf, extmark->ns_id, extmark->mark_id,
+ extline->lnum, (colnr_T)eol, kExtmarkNoUndo, &mitr);
+ })
+
+ // Record the undo for the actual move
+ if (marks_moved && undo == kExtmarkUndo) {
+ u_extmark_col_adjust_delete(buf, lnum, mincol, endcol, eol);
+ }
+}
+
+// Adjust extmark row for inserted/deleted rows (columns stay fixed).
+void extmark_adjust(buf_T *buf,
+ linenr_T line1,
+ linenr_T line2,
+ long amount,
+ long amount_after,
+ ExtmarkOp undo,
+ bool end_temp)
+{
+ ExtMarkLine *_extline;
+
+ // btree needs to be kept ordered to work, so far only :move requires this
+ // 2nd call with end_temp = unpack the lines from the temp position
+ if (end_temp && amount < 0) {
+ for (size_t i = 0; i < kv_size(buf->b_extmark_move_space); i++) {
+ _extline = kv_A(buf->b_extmark_move_space, i);
+ _extline->lnum += amount;
+ kb_put(extlines, &buf->b_extlines, _extline);
+ }
+ kv_size(buf->b_extmark_move_space) = 0;
+ return;
+ }
+
+ bool marks_exist = false;
+ linenr_T *lp;
+
+ linenr_T adj_start = line1;
+ if (amount == MAXLNUM) {
+ // Careful! marks from deleted region can end up on en extisting extline
+ // that is goinig to be adjusted to the target position.
+ linenr_T join_num = line1 - amount_after;
+ ExtMarkLine *joinline = join_num > line2 \
+ ? extline_ref(buf, join_num, false) : NULL;
+ // extmark_adjust is already redoable, the copy should only be for undo
+ marks_exist = extmark_copy_and_place(curbuf,
+ line1, 1,
+ line2, MAXCOL,
+ line1, 1,
+ kExtmarkUndoNoRedo, true, joinline);
+ adj_start = line2+1;
+ }
+ FOR_ALL_EXTMARKLINES(buf, adj_start, MAXLNUM, {
+ marks_exist = true;
+ lp = &(extline->lnum);
+ if (*lp <= line2) {
+ // 1st call with end_temp = true, store the lines in a temp position
+ if (end_temp && amount > 0) {
+ kb_del_itr_extlines(&buf->b_extlines, &itr);
+ kv_push(buf->b_extmark_move_space, extline);
+ }
+
+ *lp += amount;
+ } else if (amount_after && *lp > line2) {
+ *lp += amount_after;
+ }
+ })
+
+ if (undo == kExtmarkUndo && marks_exist) {
+ u_extmark_adjust(buf, line1, line2, amount, amount_after);
+ }
+}
+
+/// Range points to copy
+///
+/// if part of a larger iteration we can't delete, then the caller
+/// must check for empty lines.
+bool extmark_copy_and_place(buf_T *buf,
+ linenr_T l_lnum,
+ colnr_T l_col,
+ linenr_T u_lnum,
+ colnr_T u_col,
+ linenr_T p_lnum,
+ colnr_T p_col,
+ ExtmarkOp undo,
+ bool delete,
+ ExtMarkLine *destline)
+
+{
+ bool marks_moved = false;
+ if (undo == kExtmarkUndo || undo == kExtmarkUndoNoRedo) {
+ // Copy marks that would be effected by delete
+ u_extmark_copy(buf, 0, l_lnum, l_col, u_lnum, u_col);
+ }
+
+ // Move extmarks to their final position
+ // Careful: if we move items within the same line, we might change order of
+ // marks within the same extline. Too keep it simple, first delete all items
+ // from the extline and put them back in the right order.
+ FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, {
+ kvec_t(ExtendedMark) temp_space = KV_INITIAL_VALUE;
+ bool same_line = extline == destline;
+ FOR_ALL_EXTMARKS_IN_LINE(extline->items,
+ (extline->lnum > l_lnum) ? 0 : l_col,
+ (extline->lnum < u_lnum) ? MAXCOL : u_col, {
+ if (!destline) {
+ destline = extline_ref(buf, p_lnum, true);
+ same_line = extline == destline;
+ }
+ marks_moved = true;
+ if (!same_line) {
+ extmark_put(p_col, extmark->mark_id, destline, extmark->ns_id);
+ ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns,
+ extmark->ns_id);
+ pmap_put(uint64_t)(ns_obj->map, extmark->mark_id, destline);
+ } else {
+ kv_push(temp_space, *extmark);
+ }
+ // Delete old mark
+ kb_del_itr(markitems, &extline->items, &mitr);
+ })
+ if (same_line) {
+ for (size_t i = 0; i < kv_size(temp_space); i++) {
+ ExtendedMark mark = kv_A(temp_space, i);
+ extmark_put(p_col, mark.mark_id, extline, mark.ns_id);
+ }
+ kv_destroy(temp_space);
+ } else if (delete && kb_size(&extline->items) == 0) {
+ kb_del_itr(extlines, &buf->b_extlines, &itr);
+ extline_free(extline);
+ }
+ })
+
+ // Record the undo for the actual move
+ if (marks_moved && undo == kExtmarkUndo) {
+ u_extmark_copy_place(buf, l_lnum, l_col, u_lnum, u_col, p_lnum, p_col);
+ }
+
+ return marks_moved;
+}
+
+// Get reference to line in kbtree_t, allocating it if neccessary.
+ExtMarkLine *extline_ref(buf_T *buf, linenr_T lnum, bool put)
+{
+ kbtree_t(extlines) *b = &buf->b_extlines;
+ ExtMarkLine t, **pp;
+ t.lnum = lnum;
+
+ pp = kb_get(extlines, b, &t);
+ if (!pp) {
+ if (!put) {
+ return NULL;
+ }
+ ExtMarkLine *p = xcalloc(sizeof(ExtMarkLine), 1);
+ p->lnum = lnum;
+ // p->items zero initialized
+ kb_put(extlines, b, p);
+ return p;
+ }
+ // Return existing
+ return *pp;
+}
+
+void extline_free(ExtMarkLine *extline)
+{
+ kb_destroy(markitems, (&extline->items));
+ xfree(extline);
+}
+
+/// Put an extmark into a line,
+///
+/// caller must ensure combination of id and ns_id isn't in use.
+void extmark_put(colnr_T col,
+ uint64_t id,
+ ExtMarkLine *extline,
+ uint64_t ns)
+{
+ ExtendedMark t;
+ t.col = col;
+ t.mark_id = id;
+ t.line = extline;
+ t.ns_id = ns;
+
+ kbtree_t(markitems) *b = &(extline->items);
+ // kb_put requries the key to not be there
+ assert(!kb_getp(markitems, b, &t));
+
+ kb_put(markitems, b, t);
+}
+
+
diff --git a/src/nvim/mark_extended.h b/src/nvim/mark_extended.h
new file mode 100644
index 0000000000..7f407a683c
--- /dev/null
+++ b/src/nvim/mark_extended.h
@@ -0,0 +1,267 @@
+#ifndef NVIM_MARK_EXTENDED_H
+#define NVIM_MARK_EXTENDED_H
+
+#include "nvim/mark_extended_defs.h"
+#include "nvim/buffer_defs.h" // for buf_T
+
+
+// Macro Documentation: FOR_ALL_?
+// Search exclusively using the range values given.
+// Use MAXCOL/MAXLNUM for the start and end of the line/col.
+// The ns parameter: Unless otherwise stated, this is only a starting point
+// for the btree to searched in, the results being itterated over will
+// still contain extmarks from other namespaces.
+
+// see FOR_ALL_? for documentation
+#define FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, code)\
+ kbitr_t(extlines) itr;\
+ ExtMarkLine t;\
+ t.lnum = l_lnum;\
+ if (!kb_itr_get(extlines, &buf->b_extlines, &t, &itr)) { \
+ kb_itr_next(extlines, &buf->b_extlines, &itr);\
+ }\
+ ExtMarkLine *extline;\
+ for (; kb_itr_valid(&itr); kb_itr_next(extlines, &buf->b_extlines, &itr)) { \
+ extline = kb_itr_key(&itr);\
+ if (extline->lnum > u_lnum) { \
+ break;\
+ }\
+ code;\
+ }
+
+// see FOR_ALL_? for documentation
+#define FOR_ALL_EXTMARKLINES_PREV(buf, l_lnum, u_lnum, code)\
+ kbitr_t(extlines) itr;\
+ ExtMarkLine t;\
+ t.lnum = u_lnum;\
+ if (!kb_itr_get(extlines, &buf->b_extlines, &t, &itr)) { \
+ kb_itr_prev(extlines, &buf->b_extlines, &itr);\
+ }\
+ ExtMarkLine *extline;\
+ for (; kb_itr_valid(&itr); kb_itr_prev(extlines, &buf->b_extlines, &itr)) { \
+ extline = kb_itr_key(&itr);\
+ if (extline->lnum < l_lnum) { \
+ break;\
+ }\
+ code;\
+ }
+
+// see FOR_ALL_? for documentation
+#define FOR_ALL_EXTMARKS(buf, ns, l_lnum, l_col, u_lnum, u_col, code)\
+ kbitr_t(markitems) mitr;\
+ ExtendedMark mt;\
+ mt.ns_id = ns;\
+ mt.mark_id = 0;\
+ mt.line = NULL;\
+ FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, { \
+ mt.col = (extline->lnum != l_lnum) ? MINCOL : l_col;\
+ if (!kb_itr_get(markitems, &extline->items, mt, &mitr)) { \
+ kb_itr_next(markitems, &extline->items, &mitr);\
+ } \
+ ExtendedMark *extmark;\
+ for (; \
+ kb_itr_valid(&mitr); \
+ kb_itr_next(markitems, &extline->items, &mitr)) { \
+ extmark = &kb_itr_key(&mitr);\
+ if (extmark->line->lnum == u_lnum \
+ && extmark->col > u_col) { \
+ break;\
+ }\
+ code;\
+ }\
+ })
+
+
+// see FOR_ALL_? for documentation
+#define FOR_ALL_EXTMARKS_PREV(buf, ns, l_lnum, l_col, u_lnum, u_col, code)\
+ kbitr_t(markitems) mitr;\
+ ExtendedMark mt;\
+ mt.mark_id = sizeof(uint64_t);\
+ mt.ns_id = ns;\
+ FOR_ALL_EXTMARKLINES_PREV(buf, l_lnum, u_lnum, { \
+ mt.col = (extline->lnum != u_lnum) ? MAXCOL : u_col;\
+ if (!kb_itr_get(markitems, &extline->items, mt, &mitr)) { \
+ kb_itr_prev(markitems, &extline->items, &mitr);\
+ } \
+ ExtendedMark *extmark;\
+ for (; \
+ kb_itr_valid(&mitr); \
+ kb_itr_prev(markitems, &extline->items, &mitr)) { \
+ extmark = &kb_itr_key(&mitr);\
+ if (extmark->line->lnum == l_lnum \
+ && extmark->col < l_col) { \
+ break;\
+ }\
+ code;\
+ }\
+ })
+
+
+#define FOR_ALL_EXTMARKS_IN_LINE(items, l_col, u_col, code)\
+ kbitr_t(markitems) mitr;\
+ ExtendedMark mt;\
+ mt.ns_id = 0;\
+ mt.mark_id = 0;\
+ mt.line = NULL;\
+ mt.col = l_col;\
+ colnr_T extline_u_col = u_col;\
+ if (!kb_itr_get(markitems, &items, mt, &mitr)) { \
+ kb_itr_next(markitems, &items, &mitr);\
+ } \
+ ExtendedMark *extmark;\
+ for (; kb_itr_valid(&mitr); kb_itr_next(markitems, &items, &mitr)) { \
+ extmark = &kb_itr_key(&mitr);\
+ if (extmark->col > extline_u_col) { \
+ break;\
+ }\
+ code;\
+ }
+
+
+typedef struct ExtmarkNs { // For namespacing extmarks
+ PMap(uint64_t) *map; // For fast lookup
+ uint64_t free_id; // For automatically assigning id's
+} ExtmarkNs;
+
+
+typedef kvec_t(ExtendedMark *) ExtmarkArray;
+typedef kvec_t(ExtMarkLine *) ExtlineArray;
+
+
+// Undo/redo extmarks
+
+typedef enum {
+ kExtmarkNOOP, // Extmarks shouldn't be moved
+ kExtmarkUndo, // Operation should be reversable/undoable
+ kExtmarkNoUndo, // Operation should not be reversable
+ kExtmarkUndoNoRedo, // Operation should be undoable, but not redoable
+} ExtmarkOp;
+
+
+typedef struct {
+ linenr_T line1;
+ linenr_T line2;
+ long amount;
+ long amount_after;
+} Adjust;
+
+typedef struct {
+ linenr_T lnum;
+ colnr_T mincol;
+ long col_amount;
+ long lnum_amount;
+} ColAdjust;
+
+typedef struct {
+ linenr_T lnum;
+ colnr_T mincol;
+ colnr_T endcol;
+ int eol;
+} ColAdjustDelete;
+
+typedef struct {
+ linenr_T line1;
+ linenr_T line2;
+ linenr_T last_line;
+ linenr_T dest;
+ linenr_T num_lines;
+ linenr_T extra;
+} AdjustMove;
+
+typedef struct {
+ uint64_t ns_id;
+ uint64_t mark_id;
+ linenr_T lnum;
+ colnr_T col;
+} ExtmarkSet;
+
+typedef struct {
+ uint64_t ns_id;
+ uint64_t mark_id;
+ linenr_T old_lnum;
+ colnr_T old_col;
+ linenr_T lnum;
+ colnr_T col;
+} ExtmarkUpdate;
+
+typedef struct {
+ uint64_t ns_id;
+ uint64_t mark_id;
+ linenr_T lnum;
+ colnr_T col;
+} ExtmarkCopy;
+
+typedef struct {
+ linenr_T l_lnum;
+ colnr_T l_col;
+ linenr_T u_lnum;
+ colnr_T u_col;
+ linenr_T p_lnum;
+ colnr_T p_col;
+} ExtmarkCopyPlace;
+
+typedef struct {
+ uint64_t ns_id;
+ linenr_T l_lnum;
+ linenr_T u_lnum;
+} ExtmarkClear;
+
+
+typedef enum {
+ kLineAdjust,
+ kColAdjust,
+ kColAdjustDelete,
+ kAdjustMove,
+ kExtmarkSet,
+ kExtmarkDel,
+ kExtmarkUpdate,
+ kExtmarkCopy,
+ kExtmarkCopyPlace,
+ kExtmarkClear,
+} UndoObjectType;
+
+struct undo_object {
+ UndoObjectType type;
+ union {
+ Adjust adjust;
+ ColAdjust col_adjust;
+ ColAdjustDelete col_adjust_delete;
+ AdjustMove move;
+ ExtmarkSet set;
+ ExtmarkUpdate update;
+ ExtmarkCopy copy;
+ ExtmarkCopyPlace copy_place;
+ ExtmarkClear clear;
+ } data;
+};
+
+
+// For doing move of extmarks in substitutions
+typedef struct {
+ lpos_T startpos;
+ lpos_T endpos;
+ linenr_T lnum;
+ int sublen;
+} ExtmarkSubSingle;
+
+// For doing move of extmarks in substitutions
+typedef struct {
+ lpos_T startpos;
+ lpos_T endpos;
+ linenr_T lnum;
+ linenr_T newline_in_pat;
+ linenr_T newline_in_sub;
+ linenr_T lnum_added;
+ lpos_T cm_start; // start of the match
+ lpos_T cm_end; // end of the match
+ int eol; // end of the match
+} ExtmarkSubMulti;
+
+typedef kvec_t(ExtmarkSubSingle) extmark_sub_single_vec_t;
+typedef kvec_t(ExtmarkSubMulti) extmark_sub_multi_vec_t;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "mark_extended.h.generated.h"
+#endif
+
+#endif // NVIM_MARK_EXTENDED_H
diff --git a/src/nvim/mark_extended_defs.h b/src/nvim/mark_extended_defs.h
new file mode 100644
index 0000000000..4da35a05c8
--- /dev/null
+++ b/src/nvim/mark_extended_defs.h
@@ -0,0 +1,54 @@
+#ifndef NVIM_MARK_EXTENDED_DEFS_H
+#define NVIM_MARK_EXTENDED_DEFS_H
+
+#include "nvim/pos.h" // for colnr_T
+#include "nvim/map.h" // for uint64_t
+#include "nvim/lib/kbtree.h"
+#include "nvim/lib/kvec.h"
+
+struct ExtMarkLine;
+
+typedef struct ExtendedMark
+{
+ uint64_t ns_id;
+ uint64_t mark_id;
+ struct ExtMarkLine *line;
+ colnr_T col;
+} ExtendedMark;
+
+
+// We only need to compare columns as rows are stored in a different tree.
+// Marks are ordered by: position, namespace, mark_id
+// This improves moving marks but slows down all other use cases (searches)
+static inline int extmark_cmp(ExtendedMark a, ExtendedMark b)
+{
+ int cmp = kb_generic_cmp(a.col, b.col);
+ if (cmp != 0) {
+ return cmp;
+ }
+ cmp = kb_generic_cmp(a.ns_id, b.ns_id);
+ if (cmp != 0) {
+ return cmp;
+ }
+ return kb_generic_cmp(a.mark_id, b.mark_id);
+}
+
+
+#define markitems_cmp(a, b) (extmark_cmp((a), (b)))
+KBTREE_INIT(markitems, ExtendedMark, markitems_cmp, 10)
+
+typedef struct ExtMarkLine
+{
+ linenr_T lnum;
+ kbtree_t(markitems) items;
+} ExtMarkLine;
+
+#define extline_cmp(a, b) (kb_generic_cmp((a)->lnum, (b)->lnum))
+KBTREE_INIT(extlines, ExtMarkLine *, extline_cmp, 10)
+
+
+typedef struct undo_object ExtmarkUndoObject;
+typedef kvec_t(ExtmarkUndoObject) extmark_undo_vec_t;
+
+
+#endif // NVIM_MARK_EXTENDED_DEFS_H
diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c
index c1de7ab9a4..a871d424c6 100644
--- a/src/nvim/misc1.c
+++ b/src/nvim/misc1.c
@@ -30,7 +30,6 @@
#include "nvim/indent_c.h"
#include "nvim/buffer_updates.h"
#include "nvim/main.h"
-#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index fbbdfdcd82..1631204840 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -49,6 +49,7 @@
#include "nvim/undo.h"
#include "nvim/macros.h"
#include "nvim/window.h"
+#include "nvim/lib/kvec.h"
#include "nvim/os/input.h"
#include "nvim/os/time.h"
@@ -126,6 +127,30 @@ static char opchars[][3] =
{ Ctrl_X, NUL, false }, // OP_NR_SUB
};
+char *nvim_lltoa(int64_t val, int base)
+{
+ static char buf[64] = { 0 };
+
+ int i = 62;
+ int sign = (val < 0);
+ if (sign) {
+ val = -val;
+ }
+
+ if (val == 0) {
+ return "0";
+ }
+
+ for (; val && i ; i--, val /= base) {
+ buf[i] = "0123456789abcdef"[val % base];
+ }
+
+ if (sign) {
+ buf[i--] = '-';
+ }
+ return &buf[i+1];
+}
+
/*
* Translate a command name into an operator type.
* Must only be called with a valid operator name!
@@ -306,6 +331,20 @@ void shift_line(
change_indent(INDENT_SET, count, false, NUL, call_changed_bytes);
} else {
(void)set_indent(count, call_changed_bytes ? SIN_CHANGED : 0);
+
+ colnr_T col_amount;
+ colnr_T mincol = (curwin->w_cursor.col + 1) -p_sw;
+ if (left) {
+ col_amount = -p_sw;
+ } else {
+ col_amount = p_sw;
+ }
+ extmark_col_adjust(curbuf,
+ curwin->w_cursor.lnum,
+ mincol,
+ 0,
+ col_amount,
+ kExtmarkUndo);
}
}
@@ -479,6 +518,13 @@ static void shift_block(oparg_T *oap, int amount)
State = oldstate;
curwin->w_cursor.col = oldcol;
p_ri = old_p_ri;
+
+ colnr_T col_amount = p_sw;
+ if (left) {
+ col_amount = -col_amount;
+ }
+ extmark_col_adjust(curbuf, curwin->w_cursor.lnum,
+ curwin->w_cursor.col, 0, col_amount, kExtmarkUndo);
}
/*
@@ -623,10 +669,19 @@ void op_reindent(oparg_T *oap, Indenter how)
amount = how(); /* get the indent for this line */
if (amount >= 0 && set_indent(amount, SIN_UNDO)) {
- /* did change the indent, call changed_lines() later */
- if (first_changed == 0)
+ // did change the indent, call changed_lines() later
+ if (first_changed == 0) {
first_changed = curwin->w_cursor.lnum;
+ }
last_changed = curwin->w_cursor.lnum;
+
+ // Adjust extmarks
+ extmark_col_adjust(curbuf,
+ curwin->w_cursor.lnum,
+ 0, // mincol
+ 0, // lnum_amount
+ amount, // col_amount
+ kExtmarkUndo);
}
}
++curwin->w_cursor.lnum;
@@ -1614,13 +1669,15 @@ int op_delete(oparg_T *oap)
curpos = curwin->w_cursor; // remember curwin->w_cursor
curwin->w_cursor.lnum++;
- del_lines(oap->line_count - 2, false);
+ del_lines(oap->line_count - 2, true);
// delete from start of line until op_end
n = (oap->end.col + 1 - !oap->inclusive);
curwin->w_cursor.col = 0;
(void)del_bytes((colnr_T)n, !virtual_op,
oap->op_type == OP_DELETE && !oap->is_VIsual);
+ extmark_col_adjust(curbuf, curwin->w_cursor.lnum,
+ (colnr_T)0, 0L, (long)-n, kExtmarkUndo);
curwin->w_cursor = curpos; // restore curwin->w_cursor
(void)do_join(2, false, false, false, false);
}
@@ -1632,10 +1689,41 @@ setmarks:
if (oap->motion_type == kMTBlockWise) {
curbuf->b_op_end.lnum = oap->end.lnum;
curbuf->b_op_end.col = oap->start.col;
- } else
+ } else {
curbuf->b_op_end = oap->start;
+ }
curbuf->b_op_start = oap->start;
+ // TODO(timeyyy): refactor: Move extended marks
+ // + 1 to change to buf mode,
+ // and + 1 because we only move marks after the deleted col
+ colnr_T mincol = oap->start.col + 1 + 1;
+ colnr_T endcol;
+ if (oap->motion_type == kMTBlockWise) {
+ // TODO(timeyyy): refactor extmark_col_adjust to take lnumstart, lnum_end ?
+ endcol = bd.end_vcol + 1;
+ for (lnum = curwin->w_cursor.lnum; lnum <= oap->end.lnum; lnum++) {
+ extmark_col_adjust_delete(curbuf, lnum, mincol, endcol,
+ kExtmarkUndo, 0);
+ }
+
+ // Delete characters within one line,
+ // The case with multiple lines is handled by do_join
+ } else if (oap->motion_type == kMTCharWise && oap->line_count == 1) {
+ // + 1 to change to buf mode, then plus 1 to fit function requirements
+ endcol = oap->end.col + 1 + 1;
+
+ lnum = curwin->w_cursor.lnum;
+ if (oap->is_VIsual == false) {
+ // for some reason we required this :/
+ endcol = endcol - 1;
+ // for some reason we required this :/
+ if (endcol < mincol) {
+ endcol = mincol;
+ }
+ }
+ extmark_col_adjust_delete(curbuf, lnum, mincol, endcol, kExtmarkUndo, 0);
+ }
return OK;
}
@@ -2031,8 +2119,8 @@ bool swapchar(int op_type, pos_T *pos)
pos_T sp = curwin->w_cursor;
curwin->w_cursor = *pos;
- /* don't use del_char(), it also removes composing chars */
- del_bytes(utf_ptr2len(get_cursor_pos_ptr()), FALSE, FALSE);
+ // don't use del_char(), it also removes composing chars
+ del_bytes(utf_ptr2len(get_cursor_pos_ptr()), false, false);
ins_char(nc);
curwin->w_cursor = sp;
} else {
@@ -2105,8 +2193,9 @@ void op_insert(oparg_T *oap, long count1)
* values in "bd". */
if (u_save_cursor() == FAIL)
return;
- for (i = 0; i < bd.endspaces; i++)
+ for (i = 0; i < bd.endspaces; i++) {
ins_char(' ');
+ }
bd.textlen += bd.endspaces;
}
} else {
@@ -2224,6 +2313,10 @@ void op_insert(oparg_T *oap, long count1)
xfree(ins_text);
}
}
+ colnr_T col = oap->start.col;
+ for (linenr_T lnum = oap->start.lnum; lnum <= oap->end.lnum; lnum++) {
+ extmark_col_adjust(curbuf, lnum, col, 0, 1, kExtmarkUndo);
+ }
}
/*
@@ -2694,6 +2787,34 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg)
}
+
+// Function length couldn't be over 500 lines..
+static void extmarks_do_put(int dir,
+ size_t totlen,
+ MotionType y_type,
+ linenr_T lnum,
+ colnr_T col)
+{
+ // adjust extmarks
+ colnr_T col_amount;
+ if (dir == FORWARD) {
+ col_amount = (colnr_T)(totlen-1);
+ } else {
+ col_amount = (colnr_T)totlen;
+ }
+ // Move extmark with char put
+ if (y_type == kMTCharWise) {
+ extmark_col_adjust(curbuf, lnum, col, 0, col_amount, kExtmarkUndo);
+ // Move extmark with blockwise put
+ } else if (y_type == kMTBlockWise) {
+ for (lnum = curbuf->b_op_start.lnum;
+ lnum <= curbuf->b_op_end.lnum;
+ lnum++) {
+ extmark_col_adjust(curbuf, lnum, col, 0, col_amount, kExtmarkUndo);
+ }
+ }
+}
+
/*
* Put contents of register "regname" into the text.
* Caller must check "regname" to be valid!
@@ -2708,8 +2829,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
char_u *oldp;
int yanklen;
size_t totlen = 0; // init for gcc
- linenr_T lnum;
- colnr_T col;
+ linenr_T lnum = 0;
+ colnr_T col = 0;
size_t i; // index in y_array[]
MotionType y_type;
size_t y_size;
@@ -3286,11 +3407,11 @@ error:
curbuf->b_op_start.lnum++;
}
// Skip mark_adjust when adding lines after the last one, there
- // can't be marks there. But still needed in diff mode.
+ // can't be marks there.
if (curbuf->b_op_start.lnum + (y_type == kMTCharWise) - 1 + nr_lines
- < curbuf->b_ml.ml_line_count || curwin->w_p_diff) {
+ < curbuf->b_ml.ml_line_count) {
mark_adjust(curbuf->b_op_start.lnum + (y_type == kMTCharWise),
- (linenr_T)MAXLNUM, nr_lines, 0L, false);
+ (linenr_T)MAXLNUM, nr_lines, 0L, false, kExtmarkUndo);
}
// note changed text for displaying and folding
@@ -3352,6 +3473,8 @@ end:
/* If the cursor is past the end of the line put it at the end. */
adjust_cursor_eol();
+
+ extmarks_do_put(dir, totlen, y_type, lnum, col);
}
/*
@@ -3745,6 +3868,12 @@ int do_join(size_t count,
* column. This is not Vi compatible, but Vi deletes the marks, thus that
* should not really be a problem.
*/
+
+ linenr_T lnum;
+ colnr_T mincol;
+ long lnum_amount;
+ long col_amount;
+
for (t = (linenr_T)count - 1;; t--) {
cend -= currsize;
memmove(cend, curr, (size_t)currsize);
@@ -3756,12 +3885,18 @@ int do_join(size_t count,
// If deleting more spaces than adding, the cursor moves no more than
// what is added if it is inside these spaces.
const int spaces_removed = (int)((curr - curr_start) - spaces[t]);
+ lnum = curwin->w_cursor.lnum + t;
+ mincol = (colnr_T)0;
+ lnum_amount = (linenr_T)-t;
+ col_amount = (long)(cend - newp - spaces_removed);
+
+ mark_col_adjust(lnum, mincol, lnum_amount, col_amount, spaces_removed,
+ kExtmarkUndo);
- mark_col_adjust(curwin->w_cursor.lnum + t, (colnr_T)0, (linenr_T)-t,
- (long)(cend - newp - spaces_removed), spaces_removed);
if (t == 0) {
break;
}
+
curr = curr_start = ml_get((linenr_T)(curwin->w_cursor.lnum + t - 1));
if (remove_comments)
curr += comments[t - 1];
@@ -3769,6 +3904,7 @@ int do_join(size_t count,
curr = skipwhite(curr);
currsize = (int)STRLEN(curr);
}
+
ml_replace(curwin->w_cursor.lnum, newp, false);
if (setmark) {
@@ -4189,14 +4325,14 @@ format_lines(
if (next_leader_len > 0) {
(void)del_bytes(next_leader_len, false, false);
mark_col_adjust(curwin->w_cursor.lnum, (colnr_T)0, 0L,
- (long)-next_leader_len, 0);
+ (long)-next_leader_len, 0, kExtmarkUndo);
} else if (second_indent > 0) { // the "leader" for FO_Q_SECOND
int indent = (int)getwhitecols_curline();
if (indent > 0) {
(void)del_bytes(indent, FALSE, FALSE);
mark_col_adjust(curwin->w_cursor.lnum,
- (colnr_T)0, 0L, (long)-indent, 0);
+ (colnr_T)0, 0L, (long)-indent, 0, kExtmarkUndo);
}
}
curwin->w_cursor.lnum--;
@@ -4862,6 +4998,23 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
curbuf->b_op_end.col--;
}
+ if (did_change) {
+ extmark_col_adjust_delete(curbuf,
+ pos->lnum,
+ startpos.col + 2,
+ endpos.col + 1 + length,
+ kExtmarkUndo,
+ 0);
+ long col_amount = (long)strlen(nvim_lltoa((int64_t)n, 10));
+ col_amount = negative ? col_amount + 1 : col_amount;
+ extmark_col_adjust(curbuf,
+ pos->lnum,
+ startpos.col + 1,
+ 0,
+ col_amount,
+ kExtmarkUndo);
+ }
+
theend:
if (visual) {
curwin->w_cursor = save_cursor;
diff --git a/src/nvim/pos.h b/src/nvim/pos.h
index 47d253e083..8e86ea08c5 100644
--- a/src/nvim/pos.h
+++ b/src/nvim/pos.h
@@ -14,6 +14,10 @@ typedef int colnr_T;
enum { MAXLNUM = 0x7fffffff };
/// Maximal column number, 31 bits
enum { MAXCOL = 0x7fffffff };
+// Minimum line number
+enum { MINLNUM = 1 };
+// minimum column number
+enum { MINCOL = 1 };
/*
* position in file or buffer
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 035613c7fd..91a142214b 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -91,7 +91,9 @@
#include "nvim/fileio.h"
#include "nvim/fold.h"
#include "nvim/buffer_updates.h"
+#include "nvim/pos.h" // MAXLNUM
#include "nvim/mark.h"
+#include "nvim/mark_extended.h"
#include "nvim/memline.h"
#include "nvim/message.h"
#include "nvim/misc1.h"
@@ -106,6 +108,7 @@
#include "nvim/types.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
+#include "nvim/lib/kvec.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "undo.c.generated.h"
@@ -384,6 +387,7 @@ int u_savecommon(linenr_T top, linenr_T bot, linenr_T newbot, int reload)
* up the undo info when out of memory.
*/
uhp = xmalloc(sizeof(u_header_T));
+ kv_init(uhp->uh_extmark);
#ifdef U_DEBUG
uhp->uh_magic = UH_MAGIC;
#endif
@@ -2249,10 +2253,10 @@ static void u_undoredo(int undo, bool do_buf_event)
xfree((char_u *)uep->ue_array);
}
- /* adjust marks */
+ // Adjust marks
if (oldsize != newsize) {
mark_adjust(top + 1, top + oldsize, (long)MAXLNUM,
- (long)newsize - (long)oldsize, false);
+ (long)newsize - (long)oldsize, false, kExtmarkNOOP);
if (curbuf->b_op_start.lnum > top + oldsize) {
curbuf->b_op_start.lnum += newsize - oldsize;
}
@@ -2285,6 +2289,23 @@ static void u_undoredo(int undo, bool do_buf_event)
newlist = uep;
}
+ // Adjust Extmarks
+ ExtmarkUndoObject undo_info;
+ if (undo) {
+ for (i = (int)kv_size(curhead->uh_extmark) - 1; i > -1; i--) {
+ undo_info = kv_A(curhead->uh_extmark, i);
+ extmark_apply_undo(undo_info, undo);
+ }
+ // redo
+ } else {
+ for (i = 0; i < (int)kv_size(curhead->uh_extmark); i++) {
+ undo_info = kv_A(curhead->uh_extmark, i);
+ extmark_apply_undo(undo_info, undo);
+ }
+ }
+ // finish Adjusting extmarks
+
+
curhead->uh_entry = newlist;
curhead->uh_flags = new_flags;
if ((old_flags & UH_EMPTYBUF) && BUFEMPTY()) {
@@ -2828,6 +2849,9 @@ u_freeentries(
u_freeentry(uep, uep->ue_size);
}
+ // TODO(timeyyy): is this the correct place? ...
+ kv_destroy(uhp->uh_extmark);
+
#ifdef U_DEBUG
uhp->uh_magic = 0;
#endif
@@ -3022,3 +3046,28 @@ list_T *u_eval_tree(const u_header_T *const first_uhp)
return list;
}
+
+// Given the buffer, Return the undo header. If none is set, set one first.
+// NULL will be returned if e.g undolevels = -1 (undo disabled)
+u_header_T *force_get_undo_header(buf_T *buf)
+{
+ u_header_T *uhp = NULL;
+ if (buf->b_u_curhead != NULL) {
+ uhp = buf->b_u_curhead;
+ } else if (buf->b_u_newhead) {
+ uhp = buf->b_u_newhead;
+ }
+ // Create the first undo header for the buffer
+ if (!uhp) {
+ // TODO(timeyyy): there would be a better way to do this!
+ u_save_cursor();
+ uhp = buf->b_u_curhead;
+ if (!uhp) {
+ uhp = buf->b_u_newhead;
+ if (get_undolevel() > 0) {
+ assert(uhp);
+ }
+ }
+ }
+ return uhp;
+}
diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h
index 6c7e2bba41..0fa3b415ec 100644
--- a/src/nvim/undo_defs.h
+++ b/src/nvim/undo_defs.h
@@ -4,6 +4,7 @@
#include <time.h> // for time_t
#include "nvim/pos.h"
+#include "nvim/mark_extended_defs.h"
#include "nvim/mark_defs.h"
typedef struct u_header u_header_T;
@@ -56,14 +57,15 @@ struct u_header {
u_entry_T *uh_getbot_entry; /* pointer to where ue_bot must be set */
pos_T uh_cursor; /* cursor position before saving */
long uh_cursor_vcol;
- int uh_flags; /* see below */
- fmark_T uh_namedm[NMARKS]; /* marks before undo/after redo */
- visualinfo_T uh_visual; /* Visual areas before undo/after redo */
- time_t uh_time; /* timestamp when the change was made */
- long uh_save_nr; /* set when the file was saved after the
- changes in this block */
+ int uh_flags; // see below
+ fmark_T uh_namedm[NMARKS]; // marks before undo/after redo
+ extmark_undo_vec_t uh_extmark; // info to move extmarks
+ visualinfo_T uh_visual; // Visual areas before undo/after redo
+ time_t uh_time; // timestamp when the change was made
+ long uh_save_nr; // set when the file was saved after the
+ // changes in this block
#ifdef U_DEBUG
- int uh_magic; /* magic number to check allocation */
+ int uh_magic; // magic number to check allocation
#endif
};
diff --git a/test/functional/api/mark_extended_spec.lua b/test/functional/api/mark_extended_spec.lua
new file mode 100644
index 0000000000..6bf0e59133
--- /dev/null
+++ b/test/functional/api/mark_extended_spec.lua
@@ -0,0 +1,1365 @@
+-- TODO(timeyyy): go through todo's lol
+-- change representation of stored marks to have location start at 0
+-- check with memsan, asan etc
+
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+
+local request = helpers.request
+local eq = helpers.eq
+local ok = helpers.ok
+local curbufmeths = helpers.curbufmeths
+local meth_pcall = helpers.meth_pcall
+local insert = helpers.insert
+local feed = helpers.feed
+local clear = helpers.clear
+
+local ALL = -1
+
+local rv = nil
+
+local function check_undo_redo(ns, mark, sr, sc, er, ec) --s = start, e = end
+ rv = curbufmeths.get_extmark_by_id(ns, mark)
+ eq({er, ec}, rv)
+ feed("u")
+ rv = curbufmeths.get_extmark_by_id(ns, mark)
+ eq({sr, sc}, rv)
+ feed("<c-r>")
+ rv = curbufmeths.get_extmark_by_id(ns, mark)
+ eq({er, ec}, rv)
+end
+
+describe('Extmarks buffer api', function()
+ local screen
+ local marks, positions, ns_string2, ns_string, init_text, row, col
+ local ns, ns2
+
+ before_each(function()
+ -- Initialize some namespaces and insert 12345 into a buffer
+ marks = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
+ positions = {{0, 0,}, {0, 2}, {0, 3}}
+
+ ns_string = "my-fancy-plugin"
+ ns_string2 = "my-fancy-plugin2"
+ init_text = "12345"
+ row = 0
+ col = 2
+
+ clear()
+ screen = Screen.new(15, 10)
+ screen:attach()
+
+ insert(init_text)
+ ns = request('nvim_create_namespace', ns_string)
+ ns2 = request('nvim_create_namespace', ns_string2)
+ end)
+
+ it('adds, updates and deletes marks #extmarks', function()
+ rv = curbufmeths.set_extmark(ns, marks[1], positions[1][1], positions[1][2])
+ eq(marks[1], rv)
+ rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({positions[1][1], positions[1][2]}, rv)
+ -- Test adding a second mark on same row works
+ rv = curbufmeths.set_extmark(ns, marks[2], positions[2][1], positions[2][2])
+ eq(marks[2], rv)
+
+ -- Test an update, (same pos)
+ rv = curbufmeths.set_extmark(ns, marks[1], positions[1][1], positions[1][2])
+ eq(0, rv)
+ rv = curbufmeths.get_extmark_by_id(ns, marks[2])
+ eq({positions[2][1], positions[2][2]}, rv)
+ -- Test an update, (new pos)
+ row = positions[1][1]
+ col = positions[1][2] + 1
+ rv = curbufmeths.set_extmark(ns, marks[1], row, col)
+ eq(0, rv)
+ rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({row, col}, rv)
+
+ -- remove the test marks
+ eq(true, curbufmeths.del_extmark(ns, marks[1]))
+ eq(false, curbufmeths.del_extmark(ns, marks[1]))
+ eq(true, curbufmeths.del_extmark(ns, marks[2]))
+ eq(false, curbufmeths.del_extmark(ns, marks[3]))
+ eq(false, curbufmeths.del_extmark(ns, 1000))
+ end)
+
+ it('can clear a specific namespace range #extmarks', function()
+ curbufmeths.set_extmark(ns, 1, 0, 1)
+ curbufmeths.set_extmark(ns2, 1, 0, 1)
+ -- force a new undo buffer
+ feed('o<esc>')
+ curbufmeths.clear_namespace(ns2, 0, -1)
+ eq({{1, 0, 1}}, curbufmeths.get_extmarks(ns, {0, 0}, {-1, -1}, ALL))
+ eq({}, curbufmeths.get_extmarks(ns2, {0, 0}, {-1, -1}, ALL))
+ feed('u')
+ eq({{1, 0, 1}}, curbufmeths.get_extmarks(ns, {0, 0}, {-1, -1}, ALL))
+ eq({{1, 0, 1}}, curbufmeths.get_extmarks(ns2, {0, 0}, {-1, -1}, ALL))
+ feed('<c-r>')
+ eq({{1, 0, 1}}, curbufmeths.get_extmarks(ns, {0, 0}, {-1, -1}, ALL))
+ eq({}, curbufmeths.get_extmarks(ns2, {0, 0}, {-1, -1}, ALL))
+ end)
+
+ it('can clear a namespace range using ALL #extmarks', function()
+ curbufmeths.set_extmark(ns, 1, 0, 1)
+ curbufmeths.set_extmark(ns2, 1, 0, 1)
+ -- force a new undo buffer
+ feed('o<esc>')
+ curbufmeths.clear_namespace(-1, 0, -1)
+ eq({}, curbufmeths.get_extmarks(ns, {0, 0}, {-1, -1}, ALL))
+ eq({}, curbufmeths.get_extmarks(ns2, {0, 0}, {-1, -1}, ALL))
+ feed('u')
+ eq({{1, 0, 1}}, curbufmeths.get_extmarks(ns, {0, 0}, {-1, -1}, ALL))
+ eq({{1, 0, 1}}, curbufmeths.get_extmarks(ns2, {0, 0}, {-1, -1}, ALL))
+ feed('<c-r>')
+ eq({}, curbufmeths.get_extmarks(ns, {0, 0}, {-1, -1}, ALL))
+ eq({}, curbufmeths.get_extmarks(ns2, {0, 0}, {-1, -1}, ALL))
+ end)
+
+ it('querying for information and ranges #extmarks', function()
+ -- add some more marks
+ for i, m in ipairs(marks) do
+ if positions[i] ~= nil then
+ rv = curbufmeths.set_extmark(ns, m, positions[i][1], positions[i][2])
+ eq(m, rv)
+ end
+ end
+
+ -- {0, 0} and {-1, -1} work as extreme values
+ eq({{1, 0, 0}}, curbufmeths.get_extmarks(ns, {0, 0}, {0, 0}, ALL))
+ eq({}, curbufmeths.get_extmarks(ns, {-1, -1}, {-1, -1}, ALL))
+ rv = curbufmeths.get_extmarks(ns, {0, 0}, {-1, -1}, ALL)
+ for i, m in ipairs(marks) do
+ if positions[i] ~= nil then
+ eq({m, positions[i][1], positions[i][2]}, rv[i])
+ end
+ end
+
+ -- 0 and -1 works as short hand extreme values
+ eq({{1, 0, 0}}, curbufmeths.get_extmarks(ns, 0, 0, ALL))
+ eq({}, curbufmeths.get_extmarks(ns, -1, -1, ALL))
+ rv = curbufmeths.get_extmarks(ns, 0, -1, ALL)
+ for i, m in ipairs(marks) do
+ if positions[i] ~= nil then
+ eq({m, positions[i][1], positions[i][2]}, rv[i])
+ end
+ end
+
+ -- next with mark id
+ rv = curbufmeths.get_extmarks(ns, marks[1], {-1, -1}, 1)
+ eq({{marks[1], positions[1][1], positions[1][2]}}, rv)
+ rv = curbufmeths.get_extmarks(ns, marks[2], {-1, -1}, 1)
+ eq({{marks[2], positions[2][1], positions[2][2]}}, rv)
+ -- next with positional when mark exists at position
+ rv = curbufmeths.get_extmarks(ns, positions[1], {-1, -1}, 1)
+ eq({{marks[1], positions[1][1], positions[1][2]}}, rv)
+ -- next with positional index (no mark at position)
+ rv = curbufmeths.get_extmarks(ns, {positions[1][1], positions[1][2] +1}, {-1, -1}, 1)
+ eq({{marks[2], positions[2][1], positions[2][2]}}, rv)
+ -- next with Extremity index
+ rv = curbufmeths.get_extmarks(ns, {0,0}, {-1, -1}, 1)
+ eq({{marks[1], positions[1][1], positions[1][2]}}, rv)
+
+ -- nextrange with mark id
+ rv = curbufmeths.get_extmarks(ns, marks[1], marks[3], ALL)
+ eq({marks[1], positions[1][1], positions[1][2]}, rv[1])
+ eq({marks[2], positions[2][1], positions[2][2]}, rv[2])
+ -- nextrange with amount
+ rv = curbufmeths.get_extmarks(ns, marks[1], marks[3], 2)
+ eq(2, table.getn(rv))
+ -- nextrange with positional when mark exists at position
+ rv = curbufmeths.get_extmarks(ns, positions[1], positions[3], ALL)
+ eq({marks[1], positions[1][1], positions[1][2]}, rv[1])
+ eq({marks[2], positions[2][1], positions[2][2]}, rv[2])
+ rv = curbufmeths.get_extmarks(ns, positions[2], positions[3], ALL)
+ eq(2, table.getn(rv))
+ -- nextrange with positional index (no mark at position)
+ local lower = {positions[1][1], positions[2][2] -1}
+ local upper = {positions[2][1], positions[3][2] - 1}
+ rv = curbufmeths.get_extmarks(ns, lower, upper, ALL)
+ eq({{marks[2], positions[2][1], positions[2][2]}}, rv)
+ lower = {positions[3][1], positions[3][2] + 1}
+ upper = {positions[3][1], positions[3][2] + 2}
+ rv = curbufmeths.get_extmarks(ns, lower, upper, ALL)
+ eq({}, rv)
+ -- nextrange with extremity index
+ lower = {positions[2][1], positions[2][2]+1}
+ upper = {-1, -1}
+ rv = curbufmeths.get_extmarks(ns, lower, upper, ALL)
+ eq({{marks[3], positions[3][1], positions[3][2]}}, rv)
+
+ -- prev with mark id
+ rv = curbufmeths.get_extmarks(ns, marks[3], {0, 0}, 1)
+ eq({{marks[3], positions[3][1], positions[3][2]}}, rv)
+ rv = curbufmeths.get_extmarks(ns, marks[2], {0, 0}, 1)
+ eq({{marks[2], positions[2][1], positions[2][2]}}, rv)
+ -- prev with positional when mark exists at position
+ rv = curbufmeths.get_extmarks(ns, positions[3], {0, 0}, 1)
+ eq({{marks[3], positions[3][1], positions[3][2]}}, rv)
+ -- prev with positional index (no mark at position)
+ rv = curbufmeths.get_extmarks(ns, {positions[1][1], positions[1][2] +1}, {0, 0}, 1)
+ eq({{marks[1], positions[1][1], positions[1][2]}}, rv)
+ -- prev with Extremity index
+ rv = curbufmeths.get_extmarks(ns, {-1,-1}, {0,0}, 1)
+ eq({{marks[3], positions[3][1], positions[3][2]}}, rv)
+
+ -- prevrange with mark id
+ rv = curbufmeths.get_extmarks(ns, marks[3], marks[1], ALL)
+ eq({marks[3], positions[3][1], positions[3][2]}, rv[1])
+ eq({marks[2], positions[2][1], positions[2][2]}, rv[2])
+ eq({marks[1], positions[1][1], positions[1][2]}, rv[3])
+ -- prevrange with amount
+ rv = curbufmeths.get_extmarks(ns, marks[3], marks[1], 2)
+ eq(2, table.getn(rv))
+ -- prevrange with positional when mark exists at position
+ rv = curbufmeths.get_extmarks(ns, positions[3], positions[1], ALL)
+ eq({{marks[3], positions[3][1], positions[3][2]},
+ {marks[2], positions[2][1], positions[2][2]},
+ {marks[1], positions[1][1], positions[1][2]}}, rv)
+ rv = curbufmeths.get_extmarks(ns, positions[2], positions[1], ALL)
+ eq(2, table.getn(rv))
+ -- prevrange with positional index (no mark at position)
+ lower = {positions[2][1], positions[2][2] + 1}
+ upper = {positions[3][1], positions[3][2] + 1}
+ rv = curbufmeths.get_extmarks(ns, upper, lower, ALL)
+ eq({{marks[3], positions[3][1], positions[3][2]}}, rv)
+ lower = {positions[3][1], positions[3][2] + 1}
+ upper = {positions[3][1], positions[3][2] + 2}
+ rv = curbufmeths.get_extmarks(ns, upper, lower, ALL)
+ eq({}, rv)
+ -- prevrange with extremity index
+ lower = {0,0}
+ upper = {positions[2][1], positions[2][2] - 1}
+ rv = curbufmeths.get_extmarks(ns, upper, lower, ALL)
+ eq({{marks[1], positions[1][1], positions[1][2]}}, rv)
+ end)
+
+ it('querying for information with amount #extmarks', function()
+ -- add some more marks
+ for i, m in ipairs(marks) do
+ if positions[i] ~= nil then
+ rv = curbufmeths.set_extmark(ns, m, positions[i][1], positions[i][2])
+ eq(m, rv)
+ end
+ end
+
+ rv = curbufmeths.get_extmarks(ns, {0, 0}, {-1, -1}, 1)
+ eq(1, table.getn(rv))
+ rv = curbufmeths.get_extmarks(ns, {0, 0}, {-1, -1}, 2)
+ eq(2, table.getn(rv))
+ rv = curbufmeths.get_extmarks(ns, {0, 0}, {-1, -1}, 3)
+ eq(3, table.getn(rv))
+
+ -- now in reverse
+ rv = curbufmeths.get_extmarks(ns, {0, 0}, {-1, -1}, 1)
+ eq(1, table.getn(rv))
+ rv = curbufmeths.get_extmarks(ns, {0, 0}, {-1, -1}, 2)
+ eq(2, table.getn(rv))
+ rv = curbufmeths.get_extmarks(ns, {0, 0}, {-1, -1}, 3)
+ eq(3, table.getn(rv))
+ end)
+
+ it('get_marks works when mark col > upper col #extmarks', function()
+ feed('A<cr>12345<esc>')
+ feed('A<cr>12345<esc>')
+ curbufmeths.set_extmark(ns, 10, 0, 2) -- this shouldn't be found
+ curbufmeths.set_extmark(ns, 11, 2, 1) -- this shouldn't be found
+ curbufmeths.set_extmark(ns, marks[1], 0, 4) -- check col > our upper bound
+ curbufmeths.set_extmark(ns, marks[2], 1, 1) -- check col < lower bound
+ curbufmeths.set_extmark(ns, marks[3], 2, 0) -- check is inclusive
+ eq({{marks[1], 0, 4},
+ {marks[2], 1, 1},
+ {marks[3], 2, 0}},
+ curbufmeths.get_extmarks(ns, {0, 3}, {2, 0}, -1))
+ end)
+
+ it('get_marks works in reverse when mark col < lower col #extmarks', function()
+ feed('A<cr>12345<esc>')
+ feed('A<cr>12345<esc>')
+ curbufmeths.set_extmark(ns, 10, 0, 1) -- this shouldn't be found
+ curbufmeths.set_extmark(ns, 11, 2, 4) -- this shouldn't be found
+ curbufmeths.set_extmark(ns, marks[1], 2, 1) -- check col < our lower bound
+ curbufmeths.set_extmark(ns, marks[2], 1, 4) -- check col > upper bound
+ curbufmeths.set_extmark(ns, marks[3], 0, 2) -- check is inclusive
+ rv = curbufmeths.get_extmarks(ns, {2, 3}, {0, 2}, -1)
+ eq({{marks[1], 2, 1},
+ {marks[2], 1, 4},
+ {marks[3], 0, 2}},
+ rv)
+ end)
+
+ it('get_marks amount 0 returns nothing #extmarks', function()
+ curbufmeths.set_extmark(ns, marks[1], positions[1][1], positions[1][2])
+ rv = curbufmeths.get_extmarks(ns, {-1, -1}, {-1, -1}, 0)
+ eq({}, rv)
+ end)
+
+
+ it('marks move with line insertations #extmarks', function()
+ curbufmeths.set_extmark(ns, marks[1], 0, 0)
+ feed("yyP")
+ check_undo_redo(ns, marks[1], 0, 0, 1, 0)
+ end)
+
+ it('marks move with multiline insertations #extmarks', function()
+ feed("a<cr>22<cr>33<esc>")
+ curbufmeths.set_extmark(ns, marks[1], 1, 1)
+ feed('ggVGyP')
+ check_undo_redo(ns, marks[1], 1, 1, 4, 1)
+ end)
+
+ it('marks move with line join #extmarks', function()
+ -- do_join in ops.c
+ feed("a<cr>222<esc>")
+ curbufmeths.set_extmark(ns, marks[1], 1, 0)
+ feed('ggJ')
+ check_undo_redo(ns, marks[1], 1, 0, 0, 6)
+ end)
+
+ it('join works when no marks are present #extmarks', function()
+ feed("a<cr>1<esc>")
+ feed('kJ')
+ -- This shouldn't seg fault
+ screen:expect([[
+ 12345^ 1 |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ end)
+
+ it('marks move with multiline join #extmarks', function()
+ -- do_join in ops.c
+ feed("a<cr>222<cr>333<cr>444<esc>")
+ curbufmeths.set_extmark(ns, marks[1], 3, 0)
+ feed('2GVGJ')
+ check_undo_redo(ns, marks[1], 3, 0, 1, 8)
+ end)
+
+ it('marks move with line deletes #extmarks', function()
+ feed("a<cr>222<cr>333<cr>444<esc>")
+ curbufmeths.set_extmark(ns, marks[1], 2, 1)
+ feed('ggjdd')
+ check_undo_redo(ns, marks[1], 2, 1, 1, 1)
+ end)
+
+ it('marks move with multiline deletes #extmarks', function()
+ feed("a<cr>222<cr>333<cr>444<esc>")
+ curbufmeths.set_extmark(ns, marks[1], 3, 0)
+ feed('gg2dd')
+ check_undo_redo(ns, marks[1], 3, 0, 1, 0)
+ -- regression test, undoing multiline delete when mark is on row 1
+ feed('ugg3dd')
+ check_undo_redo(ns, marks[1], 3, 0, 0, 0)
+ end)
+
+ it('marks move with open line #extmarks', function()
+ -- open_line in misc1.c
+ -- testing marks below are also moved
+ feed("yyP")
+ curbufmeths.set_extmark(ns, marks[1], 0, 4)
+ curbufmeths.set_extmark(ns, marks[2], 1, 4)
+ feed('1G<s-o><esc>')
+ check_undo_redo(ns, marks[1], 0, 4, 1, 4)
+ check_undo_redo(ns, marks[2], 1, 4, 2, 4)
+ feed('2Go<esc>')
+ check_undo_redo(ns, marks[1], 1, 4, 1, 4)
+ check_undo_redo(ns, marks[2], 2, 4, 3, 4)
+ end)
+
+ -- NO idea why this doesn't work... works in program.
+ pending('marks move with char inserts #extmarks', function()
+ -- insertchar in edit.c (the ins_str branch)
+ curbufmeths.set_extmark(ns, marks[1], 1, 3)
+ feed('0')
+ insert('abc')
+ screen:expect([[
+ ab^c12345 |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq(1, rv[2])
+ eq(6, rv[3])
+ -- check_undo_redo(ns, marks[1], 0, 2, 0, 5)
+ end)
+
+ -- gravity right as definted in tk library
+ it('marks have gravity right #extmarks', function()
+ -- insertchar in edit.c (the ins_str branch)
+ curbufmeths.set_extmark(ns, marks[1], 0, 2)
+ feed('03l')
+ insert("X")
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+
+ -- check multibyte chars
+ feed('03l<esc>')
+ insert("~~")
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ end)
+
+ it('we can insert multibyte chars #extmarks', function()
+ -- insertchar in edit.c
+ feed('a<cr>12345<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 1, 2)
+ -- Insert a fullwidth (two col) tilde, NICE
+ feed('0i~<esc>')
+ check_undo_redo(ns, marks[1], 1, 2, 1, 3)
+ end)
+
+ it('marks move with blockwise inserts #extmarks', function()
+ -- op_insert in ops.c
+ feed('a<cr>12345<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 1, 2)
+ feed('0<c-v>lkI9<esc>')
+ check_undo_redo(ns, marks[1], 1, 2, 1, 3)
+ end)
+
+ it('marks move with line splits (using enter) #extmarks', function()
+ -- open_line in misc1.c
+ -- testing marks below are also moved
+ feed("yyP")
+ curbufmeths.set_extmark(ns, marks[1], 0, 4)
+ curbufmeths.set_extmark(ns, marks[2], 1, 4)
+ feed('1Gla<cr><esc>')
+ check_undo_redo(ns, marks[1], 0, 4, 1, 2)
+ check_undo_redo(ns, marks[2], 1, 4, 2, 4)
+ end)
+
+ -- TODO mark_col_adjust for normal marks fails in vim/neovim
+ -- because flags is 9 in: if (flags & OPENLINE_MARKFIX) {
+ it('marks at last line move on insert new line #extmarks', function()
+ -- open_line in misc1.c
+ curbufmeths.set_extmark(ns, marks[1], 0, 4)
+ feed('0i<cr><esc>')
+ check_undo_redo(ns, marks[1], 0, 4, 1, 4)
+ end)
+
+ it('yet again marks move with line splits #extmarks', function()
+ -- the first test above wasn't catching all errors..
+ feed("A67890<esc>")
+ curbufmeths.set_extmark(ns, marks[1], 0, 4)
+ feed("04li<cr><esc>")
+ check_undo_redo(ns, marks[1], 0, 4, 1, 0)
+ end)
+
+ it('and one last time line splits... #extmarks', function()
+ curbufmeths.set_extmark(ns, marks[1], 0, 1)
+ curbufmeths.set_extmark(ns, marks[2], 0, 2)
+ feed("02li<cr><esc>")
+ check_undo_redo(ns, marks[1], 0, 1, 0, 1)
+ check_undo_redo(ns, marks[2], 0, 2, 1, 0)
+ end)
+
+ it('multiple marks move with mark splits #extmarks', function()
+ curbufmeths.set_extmark(ns, marks[1], 0, 1)
+ curbufmeths.set_extmark(ns, marks[2], 0, 3)
+ feed("0li<cr><esc>")
+ check_undo_redo(ns, marks[1], 0, 1, 1, 0)
+ check_undo_redo(ns, marks[2], 0, 3, 1, 2)
+ end)
+
+ it('deleting on a mark works #extmarks', function()
+ -- op_delete in ops.c
+ curbufmeths.set_extmark(ns, marks[1], 0, 2)
+ feed('02lx')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ end)
+
+ it('marks move with char deletes #extmarks', function()
+ -- op_delete in ops.c
+ curbufmeths.set_extmark(ns, marks[1], 0, 2)
+ feed('02dl')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 0)
+ -- from the other side (nothing should happen)
+ feed('$x')
+ check_undo_redo(ns, marks[1], 0, 0, 0, 0)
+ end)
+
+ it('marks move with char deletes over a range #extmarks', function()
+ -- op_delete in ops.c
+ curbufmeths.set_extmark(ns, marks[1], 0, 2)
+ curbufmeths.set_extmark(ns, marks[2], 0, 3)
+ feed('0l3dl<esc>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 1)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 1)
+ -- delete 1, nothing should happend to our marks
+ feed('u')
+ feed('$x')
+ -- TODO do we need to test marks[1] ???
+ check_undo_redo(ns, marks[2], 0, 3, 0, 3)
+ end)
+
+ it('deleting marks at end of line works #extmarks', function()
+ -- mark_extended.c/extmark_col_adjust_delete
+ curbufmeths.set_extmark(ns, marks[1], 0, 4)
+ feed('$x')
+ check_undo_redo(ns, marks[1], 0, 4, 0, 4)
+ -- check the copy happened correctly on delete at eol
+ feed('$x')
+ check_undo_redo(ns, marks[1], 0, 4, 0, 3)
+ feed('u')
+ check_undo_redo(ns, marks[1], 0, 4, 0, 4)
+ end)
+
+ it('marks move with blockwise deletes #extmarks', function()
+ -- op_delete in ops.c
+ feed('a<cr>12345<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 1, 4)
+ feed('h<c-v>hhkd')
+ check_undo_redo(ns, marks[1], 1, 4, 1, 1)
+ end)
+
+ it('marks move with blockwise deletes over a range #extmarks', function()
+ -- op_delete in ops.c
+ feed('a<cr>12345<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 0, 1)
+ curbufmeths.set_extmark(ns, marks[2], 0, 3)
+ curbufmeths.set_extmark(ns, marks[3], 1, 2)
+ feed('0<c-v>k3lx')
+ check_undo_redo(ns, marks[1], 0, 1, 0, 0)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 0)
+ check_undo_redo(ns, marks[3], 1, 2, 1, 0)
+ -- delete 1, nothing should happend to our marks
+ feed('u')
+ feed('$<c-v>jx')
+ -- TODO do we need to test marks[1] ???
+ check_undo_redo(ns, marks[2], 0, 3, 0, 3)
+ check_undo_redo(ns, marks[3], 1, 2, 1, 2)
+ end)
+
+ it('works with char deletes over multilines #extmarks', function()
+ feed('a<cr>12345<cr>test-me<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 2, 5)
+ feed('gg')
+ feed('dv?-m?<cr>')
+ check_undo_redo(ns, marks[1], 2, 5, 0, 0)
+ end)
+
+ it('marks outside of deleted range move with visual char deletes #extmarks', function()
+ -- op_delete in ops.c
+ curbufmeths.set_extmark(ns, marks[1], 0, 3)
+ feed('0vx<esc>')
+ check_undo_redo(ns, marks[1], 0, 3, 0, 2)
+
+ feed("u")
+ feed('0vlx<esc>')
+ check_undo_redo(ns, marks[1], 0, 3, 0, 1)
+
+ feed("u")
+ feed('0v2lx<esc>')
+ check_undo_redo(ns, marks[1], 0, 3, 0, 0)
+
+ -- from the other side (nothing should happen)
+ feed('$vx')
+ check_undo_redo(ns, marks[1], 0, 0, 0, 0)
+ end)
+
+ it('marks outside of deleted range move with char deletes #extmarks', function()
+ -- op_delete in ops.c
+ curbufmeths.set_extmark(ns, marks[1], 0, 3)
+ feed('0x<esc>')
+ check_undo_redo(ns, marks[1], 0, 3, 0, 2)
+
+ feed("u")
+ feed('02x<esc>')
+ check_undo_redo(ns, marks[1], 0, 3, 0, 1)
+
+ feed("u")
+ feed('0v3lx<esc>')
+ check_undo_redo(ns, marks[1], 0, 3, 0, 0)
+
+ -- from the other side (nothing should happen)
+ feed("u")
+ feed('$vx')
+ check_undo_redo(ns, marks[1], 0, 3, 0, 3)
+ end)
+
+ it('marks move with P(backward) paste #extmarks', function()
+ -- do_put in ops.c
+ feed('0iabc<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 0, 7)
+ feed('0veyP')
+ check_undo_redo(ns, marks[1], 0, 7, 0, 15)
+ end)
+
+ it('marks move with p(forward) paste #extmarks', function()
+ -- do_put in ops.c
+ feed('0iabc<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 0, 7)
+ feed('0veyp')
+ check_undo_redo(ns, marks[1], 0, 7, 0, 14)
+ end)
+
+ it('marks move with blockwise P(backward) paste #extmarks', function()
+ -- do_put in ops.c
+ feed('a<cr>12345<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 1, 4)
+ feed('<c-v>hhkyP<esc>')
+ check_undo_redo(ns, marks[1], 1, 4, 1, 7)
+ end)
+
+ it('marks move with blockwise p(forward) paste #extmarks', function()
+ -- do_put in ops.c
+ feed('a<cr>12345<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 1, 4)
+ feed('<c-v>hhkyp<esc>')
+ check_undo_redo(ns, marks[1], 1, 4, 1, 6)
+ end)
+
+ it('replace works #extmarks', function()
+ curbufmeths.set_extmark(ns, marks[1], 0, 2)
+ feed('0r2')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ end)
+
+ it('blockwise replace works #extmarks', function()
+ feed('a<cr>12345<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 0, 2)
+ feed('0<c-v>llkr1<esc>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ end)
+
+ it('shift line #extmarks', function()
+ -- shift_line in ops.c
+ feed(':set shiftwidth=4<cr><esc>')
+ curbufmeths.set_extmark(ns, marks[1], 0, 2)
+ feed('0>>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 6)
+
+ feed('>>')
+ check_undo_redo(ns, marks[1], 0, 6, 0, 10)
+
+ feed('<LT><LT>') -- have to escape, same as <<
+ check_undo_redo(ns, marks[1], 0, 10, 0, 6)
+ end)
+
+ it('blockwise shift #extmarks', function()
+ -- shift_block in ops.c
+ feed(':set shiftwidth=4<cr><esc>')
+ feed('a<cr>12345<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 1, 2)
+ feed('0<c-v>k>')
+ check_undo_redo(ns, marks[1], 1, 2, 1, 6)
+ feed('<c-v>j>')
+ check_undo_redo(ns, marks[1], 1, 6, 1, 10)
+
+ feed('<c-v>j<LT>')
+ check_undo_redo(ns, marks[1], 1, 10, 1, 6)
+ end)
+
+ it('tab works with expandtab #extmarks', function()
+ -- ins_tab in edit.c
+ feed(':set expandtab<cr><esc>')
+ feed(':set shiftwidth=2<cr><esc>')
+ curbufmeths.set_extmark(ns, marks[1], 0, 2)
+ feed('0i<tab><tab><esc>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 6)
+ end)
+
+ it('tabs work #extmarks', function()
+ -- ins_tab in edit.c
+ feed(':set noexpandtab<cr><esc>')
+ feed(':set shiftwidth=2<cr><esc>')
+ feed(':set softtabstop=2<cr><esc>')
+ feed(':set tabstop=8<cr><esc>')
+ curbufmeths.set_extmark(ns, marks[1], 0, 2)
+ feed('0i<tab><esc>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 4)
+ feed('0iX<tab><esc>')
+ check_undo_redo(ns, marks[1], 0, 4, 0, 6)
+ end)
+
+ it('marks move when using :move #extmarks', function()
+ curbufmeths.set_extmark(ns, marks[1], 0, 0)
+ feed('A<cr>2<esc>:1move 2<cr><esc>')
+ check_undo_redo(ns, marks[1], 0, 0, 1, 0)
+ -- test codepath when moving lines up
+ feed(':2move 0<cr><esc>')
+ check_undo_redo(ns, marks[1], 1, 0, 0, 0)
+ end)
+
+ it('marks move when using :move part 2 #extmarks', function()
+ -- make sure we didn't get lucky with the math...
+ feed('A<cr>2<cr>3<cr>4<cr>5<cr>6<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 1, 0)
+ feed(':2,3move 5<cr><esc>')
+ check_undo_redo(ns, marks[1], 1, 0, 3, 0)
+ -- test codepath when moving lines up
+ feed(':4,5move 1<cr><esc>')
+ check_undo_redo(ns, marks[1], 3, 0, 1, 0)
+ end)
+
+ it('undo and redo of set and unset marks #extmarks', function()
+ -- Force a new undo head
+ feed('o<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 0, 1)
+ feed('o<esc>')
+ curbufmeths.set_extmark(ns, marks[2], 0, -1)
+ curbufmeths.set_extmark(ns, marks[3], 0, -1)
+
+ feed("u")
+ rv = curbufmeths.get_extmarks(ns, {0, 0}, {-1, -1}, ALL)
+ eq(1, table.getn(rv))
+
+ feed("<c-r>")
+ rv = curbufmeths.get_extmarks(ns, {0, 0}, {-1, -1}, ALL)
+ eq(3, table.getn(rv))
+
+ -- Test updates
+ feed('o<esc>')
+ curbufmeths.set_extmark(ns, marks[1], positions[1][1], positions[1][2])
+ rv = curbufmeths.get_extmarks(ns, marks[1], marks[1], 1)
+ feed("u")
+ feed("<c-r>")
+ check_undo_redo(ns, marks[1], 0, 1, positions[1][1], positions[1][2])
+
+ -- Test unset
+ feed('o<esc>')
+ curbufmeths.del_extmark(ns, marks[3])
+ feed("u")
+ rv = curbufmeths.get_extmarks(ns, {0, 0}, {-1, -1}, ALL)
+ eq(3, table.getn(rv))
+ feed("<c-r>")
+ rv = curbufmeths.get_extmarks(ns, {0, 0}, {-1, -1}, ALL)
+ eq(2, table.getn(rv))
+ end)
+
+ it('undo and redo of marks deleted during edits #extmarks', function()
+ -- test extmark_adjust
+ feed('A<cr>12345<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 1, 2)
+ feed('dd')
+ check_undo_redo(ns, marks[1], 1, 2, 1, 0)
+ end)
+
+ it('namespaces work properly #extmarks', function()
+ rv = curbufmeths.set_extmark(ns, marks[1], positions[1][1], positions[1][2])
+ eq(1, rv)
+ rv = curbufmeths.set_extmark(ns2, marks[1], positions[1][1], positions[1][2])
+ eq(1, rv)
+ rv = curbufmeths.get_extmarks(ns, {0, 0}, {-1, -1}, ALL)
+ eq(1, table.getn(rv))
+ rv = curbufmeths.get_extmarks(ns2, {0, 0}, {-1, -1}, ALL)
+ eq(1, table.getn(rv))
+
+ -- Set more marks for testing the ranges
+ rv = curbufmeths.set_extmark(ns, marks[2], positions[2][1], positions[2][2])
+ rv = curbufmeths.set_extmark(ns, marks[3], positions[3][1], positions[3][2])
+ rv = curbufmeths.set_extmark(ns2, marks[2], positions[2][1], positions[2][2])
+ rv = curbufmeths.set_extmark(ns2, marks[3], positions[3][1], positions[3][2])
+
+ -- get_next (amount set)
+ rv = curbufmeths.get_extmarks(ns, {0, 0}, positions[2], 1)
+ eq(1, table.getn(rv))
+ rv = curbufmeths.get_extmarks(ns2, {0, 0}, positions[2], 1)
+ eq(1, table.getn(rv))
+ -- get_prev (amount set)
+ rv = curbufmeths.get_extmarks(ns, positions[1], {0, 0}, 1)
+ eq(1, table.getn(rv))
+ rv = curbufmeths.get_extmarks(ns2, positions[1], {0, 0}, 1)
+ eq(1, table.getn(rv))
+
+ -- get_next (amount not set)
+ rv = curbufmeths.get_extmarks(ns, positions[1], positions[2], ALL)
+ eq(2, table.getn(rv))
+ rv = curbufmeths.get_extmarks(ns2, positions[1], positions[2], ALL)
+ eq(2, table.getn(rv))
+ -- get_prev (amount not set)
+ rv = curbufmeths.get_extmarks(ns, positions[2], positions[1], ALL)
+ eq(2, table.getn(rv))
+ rv = curbufmeths.get_extmarks(ns2, positions[2], positions[1], ALL)
+ eq(2, table.getn(rv))
+
+ curbufmeths.del_extmark(ns, marks[1])
+ rv = curbufmeths.get_extmarks(ns, {0, 0}, {-1, -1}, ALL)
+ eq(2, table.getn(rv))
+ curbufmeths.del_extmark(ns2, marks[1])
+ rv = curbufmeths.get_extmarks(ns2, {0, 0}, {-1, -1}, ALL)
+ eq(2, table.getn(rv))
+ end)
+
+ it('mark set can create unique identifiers #extmarks', function()
+ -- create mark with id 1
+ eq(1, curbufmeths.set_extmark(ns, 1, positions[1][1], positions[1][2]))
+ -- ask for unique id, it should be the next one, i e 2
+ eq(2, curbufmeths.set_extmark(ns, 0, positions[1][1], positions[1][2]))
+ eq(3, curbufmeths.set_extmark(ns, 3, positions[2][1], positions[2][2]))
+ eq(4, curbufmeths.set_extmark(ns, 0, positions[1][1], positions[1][2]))
+
+ -- mixing manual and allocated id:s are not recommened, but it should
+ -- do something reasonable
+ eq(6, curbufmeths.set_extmark(ns, 6, positions[2][1], positions[2][2]))
+ eq(7, curbufmeths.set_extmark(ns, 0, positions[1][1], positions[1][2]))
+ eq(8, curbufmeths.set_extmark(ns, 0, positions[1][1], positions[1][2]))
+ end)
+
+ it('auto indenting with enter works #extmarks', function()
+ -- op_reindent in ops.c
+ feed(':set cindent<cr><esc>')
+ feed(':set autoindent<cr><esc>')
+ feed(':set shiftwidth=2<cr><esc>')
+ feed("0iint <esc>A {1M1<esc>b<esc>")
+ -- Set the mark on the M, should move..
+ curbufmeths.set_extmark(ns, marks[1], 0, 12)
+ -- Set the mark before the cursor, should stay there
+ curbufmeths.set_extmark(ns, marks[2], 0, 10)
+ feed("i<cr><esc>")
+ rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({1, 3}, rv)
+ rv = curbufmeths.get_extmark_by_id(ns, marks[2])
+ eq({0, 10}, rv)
+ check_undo_redo(ns, marks[1], 0, 12, 1, 3)
+ end)
+
+ it('auto indenting entire line works #extmarks', function()
+ feed(':set cindent<cr><esc>')
+ feed(':set autoindent<cr><esc>')
+ feed(':set shiftwidth=2<cr><esc>')
+ -- <c-f> will force an indent of 2
+ feed("0iint <esc>A {<cr><esc>0i1M1<esc>")
+ curbufmeths.set_extmark(ns, marks[1], 1, 1)
+ feed("0i<c-f><esc>")
+ rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({1, 3}, rv)
+ check_undo_redo(ns, marks[1], 1, 1, 1, 3)
+ -- now check when cursor at eol
+ feed("uA<c-f><esc>")
+ rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({1, 3}, rv)
+ end)
+
+ it('removing auto indenting with <C-D> works #extmarks', function()
+ feed(':set cindent<cr><esc>')
+ feed(':set autoindent<cr><esc>')
+ feed(':set shiftwidth=2<cr><esc>')
+ feed("0i<tab><esc>")
+ curbufmeths.set_extmark(ns, marks[1], 0, 3)
+ feed("bi<c-d><esc>")
+ rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({0, 1}, rv)
+ check_undo_redo(ns, marks[1], 0, 3, 0, 1)
+ -- check when cursor at eol
+ feed("uA<c-d><esc>")
+ rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({0, 1}, rv)
+ end)
+
+ it('indenting multiple lines with = works #extmarks', function()
+ feed(':set cindent<cr><esc>')
+ feed(':set autoindent<cr><esc>')
+ feed(':set shiftwidth=2<cr><esc>')
+ feed("0iint <esc>A {<cr><bs>1M1<cr><bs>2M2<esc>")
+ curbufmeths.set_extmark(ns, marks[1], 1, 1)
+ curbufmeths.set_extmark(ns, marks[2], 2, 1)
+ feed('=gg')
+ check_undo_redo(ns, marks[1], 1, 1, 1, 3)
+ check_undo_redo(ns, marks[2], 2, 1, 2, 5)
+ end)
+
+ it('substitutes by deleting inside the replace matches #extmarks_sub', function()
+ -- do_sub in ex_cmds.c
+ curbufmeths.set_extmark(ns, marks[1], 0, 2)
+ curbufmeths.set_extmark(ns, marks[2], 0, 3)
+ feed(':s/34/xx<cr>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 4)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 4)
+ end)
+
+ it('substitutes when insert text > deleted #extmarks_sub', function()
+ -- do_sub in ex_cmds.c
+ curbufmeths.set_extmark(ns, marks[1], 0, 2)
+ curbufmeths.set_extmark(ns, marks[2], 0, 3)
+ feed(':s/34/xxx<cr>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 5)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 5)
+ end)
+
+ it('substitutes when marks around eol #extmarks_sub', function()
+ -- do_sub in ex_cmds.c
+ curbufmeths.set_extmark(ns, marks[1], 0, 4)
+ curbufmeths.set_extmark(ns, marks[2], 0, 5)
+ feed(':s/5/xxx<cr>')
+ check_undo_redo(ns, marks[1], 0, 4, 0, 7)
+ check_undo_redo(ns, marks[2], 0, 5, 0, 7)
+ end)
+
+ it('substitutes over range insert text > deleted #extmarks_sub', function()
+ -- do_sub in ex_cmds.c
+ feed('A<cr>x34xx<esc>')
+ feed('A<cr>xxx34<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 0, 2)
+ curbufmeths.set_extmark(ns, marks[2], 1, 1)
+ curbufmeths.set_extmark(ns, marks[3], 2, 4)
+ feed(':1,3s/34/xxx<cr><esc>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 5)
+ check_undo_redo(ns, marks[2], 1, 1, 1, 4)
+ check_undo_redo(ns, marks[3], 2, 4, 2, 6)
+ end)
+
+ it('substitutes multiple matches in a line #extmarks_sub', function()
+ -- do_sub in ex_cmds.c
+ feed('ddi3x3x3<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 0, 0)
+ curbufmeths.set_extmark(ns, marks[2], 0, 2)
+ curbufmeths.set_extmark(ns, marks[3], 0, 4)
+ feed(':s/3/yy/g<cr><esc>')
+ check_undo_redo(ns, marks[1], 0, 0, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 2, 0, 5)
+ check_undo_redo(ns, marks[3], 0, 4, 0, 8)
+ end)
+
+ it('substitions over multiple lines with newline in pattern #extmarks_sub', function()
+ feed('A<cr>67890<cr>xx<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 0, 3)
+ curbufmeths.set_extmark(ns, marks[2], 0, 4)
+ curbufmeths.set_extmark(ns, marks[3], 1, 0)
+ curbufmeths.set_extmark(ns, marks[4], 1, 5)
+ curbufmeths.set_extmark(ns, marks[5], 2, 0)
+ feed([[:1,2s:5\n:5 <cr>]])
+ check_undo_redo(ns, marks[1], 0, 3, 0, 3)
+ check_undo_redo(ns, marks[2], 0, 4, 0, 6)
+ check_undo_redo(ns, marks[3], 1, 0, 0, 6)
+ check_undo_redo(ns, marks[4], 1, 5, 0, 11)
+ check_undo_redo(ns, marks[5], 2, 0, 1, 0)
+ end)
+
+ it('inserting #extmarks_sub', function()
+ feed('A<cr>67890<cr>xx<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 0, 3)
+ curbufmeths.set_extmark(ns, marks[2], 0, 4)
+ curbufmeths.set_extmark(ns, marks[3], 1, 0)
+ curbufmeths.set_extmark(ns, marks[4], 1, 5)
+ curbufmeths.set_extmark(ns, marks[5], 2, 0)
+ curbufmeths.set_extmark(ns, marks[6], 1, 2)
+ feed([[:1,2s:5\n67:X<cr>]])
+ check_undo_redo(ns, marks[1], 0, 3, 0, 3)
+ check_undo_redo(ns, marks[2], 0, 4, 0, 5)
+ check_undo_redo(ns, marks[3], 1, 0, 0, 5)
+ check_undo_redo(ns, marks[4], 1, 5, 0, 8)
+ check_undo_redo(ns, marks[5], 2, 0, 1, 0)
+ check_undo_redo(ns, marks[6], 1, 2, 0, 5)
+ end)
+
+ it('substitions with multiple newlines in pattern #extmarks_sub', function()
+ feed('A<cr>67890<cr>xx<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 0, 4)
+ curbufmeths.set_extmark(ns, marks[2], 0, 5)
+ curbufmeths.set_extmark(ns, marks[3], 1, 0)
+ curbufmeths.set_extmark(ns, marks[4], 1, 5)
+ curbufmeths.set_extmark(ns, marks[5], 2, 0)
+ feed([[:1,2s:\n.*\n:X<cr>]])
+ check_undo_redo(ns, marks[1], 0, 4, 0, 4)
+ check_undo_redo(ns, marks[2], 0, 5, 0, 6)
+ check_undo_redo(ns, marks[3], 1, 0, 0, 6)
+ check_undo_redo(ns, marks[4], 1, 5, 0, 6)
+ check_undo_redo(ns, marks[5], 2, 0, 0, 6)
+ end)
+
+ it('substitions over multiple lines with replace in substition #extmarks_sub', function()
+ feed('A<cr>67890<cr>xx<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 0, 1)
+ curbufmeths.set_extmark(ns, marks[2], 0, 2)
+ curbufmeths.set_extmark(ns, marks[3], 0, 4)
+ curbufmeths.set_extmark(ns, marks[4], 1, 0)
+ curbufmeths.set_extmark(ns, marks[5], 2, 0)
+ feed([[:1,2s:3:\r<cr>]])
+ check_undo_redo(ns, marks[1], 0, 1, 0, 1)
+ check_undo_redo(ns, marks[2], 0, 2, 1, 0)
+ check_undo_redo(ns, marks[3], 0, 4, 1, 1)
+ check_undo_redo(ns, marks[4], 1, 0, 2, 0)
+ check_undo_redo(ns, marks[5], 2, 0, 3, 0)
+ feed('u')
+ feed([[:1,2s:3:\rxx<cr>]])
+ eq({1, 3}, curbufmeths.get_extmark_by_id(ns, marks[3]))
+ end)
+
+ it('substitions over multiple lines with replace in substition #extmarks_sub', function()
+ feed('A<cr>x3<cr>xx<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 1, 0)
+ curbufmeths.set_extmark(ns, marks[2], 1, 1)
+ curbufmeths.set_extmark(ns, marks[3], 1, 2)
+ feed([[:2,2s:3:\r<cr>]])
+ check_undo_redo(ns, marks[1], 1, 0, 1, 0)
+ check_undo_redo(ns, marks[2], 1, 1, 2, 0)
+ check_undo_redo(ns, marks[3], 1, 2, 2, 0)
+ end)
+
+ it('substitions over multiple lines with replace in substition #extmarks_sub', function()
+ feed('A<cr>x3<cr>xx<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 0, 1)
+ curbufmeths.set_extmark(ns, marks[2], 0, 2)
+ curbufmeths.set_extmark(ns, marks[3], 0, 4)
+ curbufmeths.set_extmark(ns, marks[4], 1, 1)
+ curbufmeths.set_extmark(ns, marks[5], 2, 0)
+ feed([[:1,2s:3:\r<cr>]])
+ check_undo_redo(ns, marks[1], 0, 1, 0, 1)
+ check_undo_redo(ns, marks[2], 0, 2, 1, 0)
+ check_undo_redo(ns, marks[3], 0, 4, 1, 1)
+ check_undo_redo(ns, marks[4], 1, 1, 3, 0)
+ check_undo_redo(ns, marks[5], 2, 0, 4, 0)
+ feed('u')
+ feed([[:1,2s:3:\rxx<cr>]])
+ check_undo_redo(ns, marks[3], 0, 4, 1, 3)
+ end)
+
+ it('substitions with newline in match and sub, delta is 0 #extmarks_sub', function()
+ feed('A<cr>67890<cr>xx<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 0, 3)
+ curbufmeths.set_extmark(ns, marks[2], 0, 4)
+ curbufmeths.set_extmark(ns, marks[3], 0, 5)
+ curbufmeths.set_extmark(ns, marks[4], 1, 0)
+ curbufmeths.set_extmark(ns, marks[5], 1, 5)
+ curbufmeths.set_extmark(ns, marks[6], 2, 0)
+ feed([[:1,1s:5\n:\r<cr>]])
+ check_undo_redo(ns, marks[1], 0, 3, 0, 3)
+ check_undo_redo(ns, marks[2], 0, 4, 1, 0)
+ check_undo_redo(ns, marks[3], 0, 5, 1, 0)
+ check_undo_redo(ns, marks[4], 1, 0, 1, 0)
+ check_undo_redo(ns, marks[5], 1, 5, 1, 5)
+ check_undo_redo(ns, marks[6], 2, 0, 2, 0)
+ end)
+
+ it('substitions with newline in match and sub, delta > 0 #extmarks_sub', function()
+ feed('A<cr>67890<cr>xx<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 0, 3)
+ curbufmeths.set_extmark(ns, marks[2], 0, 4)
+ curbufmeths.set_extmark(ns, marks[3], 0, 5)
+ curbufmeths.set_extmark(ns, marks[4], 1, 0)
+ curbufmeths.set_extmark(ns, marks[5], 1, 5)
+ curbufmeths.set_extmark(ns, marks[6], 2, 0)
+ feed([[:1,1s:5\n:\r\r<cr>]])
+ check_undo_redo(ns, marks[1], 0, 3, 0, 3)
+ check_undo_redo(ns, marks[2], 0, 4, 2, 0)
+ check_undo_redo(ns, marks[3], 0, 5, 2, 0)
+ check_undo_redo(ns, marks[4], 1, 0, 2, 0)
+ check_undo_redo(ns, marks[5], 1, 5, 2, 5)
+ check_undo_redo(ns, marks[6], 2, 0, 3, 0)
+ end)
+
+ it('substitions with newline in match and sub, delta < 0 #extmarks_sub', function()
+ feed('A<cr>67890<cr>xx<cr>xx<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 0, 3)
+ curbufmeths.set_extmark(ns, marks[2], 0, 4)
+ curbufmeths.set_extmark(ns, marks[3], 0, 5)
+ curbufmeths.set_extmark(ns, marks[4], 1, 0)
+ curbufmeths.set_extmark(ns, marks[5], 1, 5)
+ curbufmeths.set_extmark(ns, marks[6], 2, 1)
+ curbufmeths.set_extmark(ns, marks[7], 3, 0)
+ feed([[:1,2s:5\n.*\n:\r<cr>]])
+ check_undo_redo(ns, marks[1], 0, 3, 0, 3)
+ check_undo_redo(ns, marks[2], 0, 4, 1, 0)
+ check_undo_redo(ns, marks[3], 0, 5, 1, 0)
+ check_undo_redo(ns, marks[4], 1, 0, 1, 0)
+ check_undo_redo(ns, marks[5], 1, 5, 1, 0)
+ check_undo_redo(ns, marks[6], 2, 1, 1, 1)
+ check_undo_redo(ns, marks[7], 3, 0, 2, 0)
+ end)
+
+ it('substitions with backrefs, newline inserted into sub #extmarks_sub', function()
+ feed('A<cr>67890<cr>xx<cr>xx<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 0, 3)
+ curbufmeths.set_extmark(ns, marks[2], 0, 4)
+ curbufmeths.set_extmark(ns, marks[3], 0, 5)
+ curbufmeths.set_extmark(ns, marks[4], 1, 0)
+ curbufmeths.set_extmark(ns, marks[5], 1, 5)
+ curbufmeths.set_extmark(ns, marks[6], 2, 0)
+ feed([[:1,1s:5\(\n\):\0\1<cr>]])
+ check_undo_redo(ns, marks[1], 0, 3, 0, 3)
+ check_undo_redo(ns, marks[2], 0, 4, 2, 0)
+ check_undo_redo(ns, marks[3], 0, 5, 2, 0)
+ check_undo_redo(ns, marks[4], 1, 0, 2, 0)
+ check_undo_redo(ns, marks[5], 1, 5, 2, 5)
+ check_undo_redo(ns, marks[6], 2, 0, 3, 0)
+ end)
+
+ it('substitions a ^ #extmarks_sub', function()
+ curbufmeths.set_extmark(ns, marks[1], 0, 0)
+ curbufmeths.set_extmark(ns, marks[2], 0, 1)
+ feed([[:s:^:x<cr>]])
+ check_undo_redo(ns, marks[1], 0, 0, 0, 1)
+ check_undo_redo(ns, marks[2], 0, 1, 0, 2)
+ end)
+
+ it('using <c-a> without increase in order of magnitude #extmarks_inc_dec', function()
+ -- do_addsub in ops.c
+ feed('ddiabc998xxx<esc>Tc')
+ curbufmeths.set_extmark(ns, marks[1], 0, 2)
+ curbufmeths.set_extmark(ns, marks[2], 0, 3)
+ curbufmeths.set_extmark(ns, marks[3], 0, 5)
+ curbufmeths.set_extmark(ns, marks[4], 0, 6)
+ curbufmeths.set_extmark(ns, marks[5], 0, 7)
+ feed('<c-a>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 6)
+ check_undo_redo(ns, marks[3], 0, 5, 0, 6)
+ check_undo_redo(ns, marks[4], 0, 6, 0, 6)
+ check_undo_redo(ns, marks[5], 0, 7, 0, 7)
+ end)
+
+ it('using <c-a> when increase in order of magnitude #extmarks_inc_dec', function()
+ -- do_addsub in ops.c
+ feed('ddiabc999xxx<esc>Tc')
+ curbufmeths.set_extmark(ns, marks[1], 0, 2)
+ curbufmeths.set_extmark(ns, marks[2], 0, 3)
+ curbufmeths.set_extmark(ns, marks[3], 0, 5)
+ curbufmeths.set_extmark(ns, marks[4], 0, 6)
+ curbufmeths.set_extmark(ns, marks[5], 0, 7)
+ feed('<c-a>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 7)
+ check_undo_redo(ns, marks[3], 0, 5, 0, 7)
+ check_undo_redo(ns, marks[4], 0, 6, 0, 7)
+ check_undo_redo(ns, marks[5], 0, 7, 0, 8)
+ end)
+
+ it('using <c-a> when negative and without decrease in order of magnitude #extmarks_inc_dec', function()
+ feed('ddiabc-999xxx<esc>T-')
+ curbufmeths.set_extmark(ns, marks[1], 0, 2)
+ curbufmeths.set_extmark(ns, marks[2], 0, 3)
+ curbufmeths.set_extmark(ns, marks[3], 0, 6)
+ curbufmeths.set_extmark(ns, marks[4], 0, 7)
+ curbufmeths.set_extmark(ns, marks[5], 0, 8)
+ feed('<c-a>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 7)
+ check_undo_redo(ns, marks[3], 0, 6, 0, 7)
+ check_undo_redo(ns, marks[4], 0, 7, 0, 7)
+ check_undo_redo(ns, marks[5], 0, 8, 0, 8)
+ end)
+
+ it('using <c-a> when negative and decrease in order of magnitude #extmarks_inc_dec', function()
+ feed('ddiabc-1000xxx<esc>T-')
+ curbufmeths.set_extmark(ns, marks[1], 0, 2)
+ curbufmeths.set_extmark(ns, marks[2], 0, 3)
+ curbufmeths.set_extmark(ns, marks[3], 0, 7)
+ curbufmeths.set_extmark(ns, marks[4], 0, 8)
+ curbufmeths.set_extmark(ns, marks[5], 0, 9)
+ feed('<c-a>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 7)
+ check_undo_redo(ns, marks[3], 0, 7, 0, 7)
+ check_undo_redo(ns, marks[4], 0, 8, 0, 7)
+ check_undo_redo(ns, marks[5], 0, 9, 0, 8)
+ end)
+
+ it('using <c-x> without decrease in order of magnitude #extmarks_inc_dec', function()
+ -- do_addsub in ops.c
+ feed('ddiabc999xxx<esc>Tc')
+ curbufmeths.set_extmark(ns, marks[1], 0, 2)
+ curbufmeths.set_extmark(ns, marks[2], 0, 3)
+ curbufmeths.set_extmark(ns, marks[3], 0, 5)
+ curbufmeths.set_extmark(ns, marks[4], 0, 6)
+ curbufmeths.set_extmark(ns, marks[5], 0, 7)
+ feed('<c-x>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 6)
+ check_undo_redo(ns, marks[3], 0, 5, 0, 6)
+ check_undo_redo(ns, marks[4], 0, 6, 0, 6)
+ check_undo_redo(ns, marks[5], 0, 7, 0, 7)
+ end)
+
+ it('using <c-x> when decrease in order of magnitude #extmarks_inc_dec', function()
+ -- do_addsub in ops.c
+ feed('ddiabc1000xxx<esc>Tc')
+ curbufmeths.set_extmark(ns, marks[1], 0, 2)
+ curbufmeths.set_extmark(ns, marks[2], 0, 3)
+ curbufmeths.set_extmark(ns, marks[3], 0, 6)
+ curbufmeths.set_extmark(ns, marks[4], 0, 7)
+ curbufmeths.set_extmark(ns, marks[5], 0, 8)
+ feed('<c-x>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 6)
+ check_undo_redo(ns, marks[3], 0, 6, 0, 6)
+ check_undo_redo(ns, marks[4], 0, 7, 0, 6)
+ check_undo_redo(ns, marks[5], 0, 8, 0, 7)
+ end)
+
+ it('using <c-x> when negative and without increase in order of magnitude #extmarks_inc_dec', function()
+ feed('ddiabc-998xxx<esc>T-')
+ curbufmeths.set_extmark(ns, marks[1], 0, 2)
+ curbufmeths.set_extmark(ns, marks[2], 0, 3)
+ curbufmeths.set_extmark(ns, marks[3], 0, 6)
+ curbufmeths.set_extmark(ns, marks[4], 0, 7)
+ curbufmeths.set_extmark(ns, marks[5], 0, 8)
+ feed('<c-x>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 7)
+ check_undo_redo(ns, marks[3], 0, 6, 0, 7)
+ check_undo_redo(ns, marks[4], 0, 7, 0, 7)
+ check_undo_redo(ns, marks[5], 0, 8, 0, 8)
+ end)
+
+ it('using <c-x> when negative and increase in order of magnitude #extmarks_inc_dec', function()
+ feed('ddiabc-999xxx<esc>T-')
+ curbufmeths.set_extmark(ns, marks[1], 0, 2)
+ curbufmeths.set_extmark(ns, marks[2], 0, 3)
+ curbufmeths.set_extmark(ns, marks[3], 0, 6)
+ curbufmeths.set_extmark(ns, marks[4], 0, 7)
+ curbufmeths.set_extmark(ns, marks[5], 0, 8)
+ feed('<c-x>')
+ check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ check_undo_redo(ns, marks[2], 0, 3, 0, 8)
+ check_undo_redo(ns, marks[3], 0, 6, 0, 8)
+ check_undo_redo(ns, marks[4], 0, 7, 0, 8)
+ check_undo_redo(ns, marks[5], 0, 8, 0, 9)
+ end)
+
+ -- TODO catch exceptions
+ pending('throws consistent error codes #todo', function()
+ local ns_invalid = ns2 + 1
+ rv = curbufmeths.set_extmark(ns_invalid, marks[1], positions[1][1], positions[1][2])
+ rv = curbufmeths.del_extmark(ns_invalid, marks[1])
+ rv = curbufmeths.get_extmarks(ns_invalid, positions[1], positions[2], ALL)
+ rv = curbufmeths.get_extmark_by_id(ns_invalid, marks[1])
+
+ end)
+
+ it('when col = line-length, set the mark on eol #extmarks', function()
+ curbufmeths.set_extmark(ns, marks[1], 0, -1)
+ rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({0, init_text:len()}, rv)
+ -- Test another
+ curbufmeths.set_extmark(ns, marks[1], 0, -1)
+ rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({0, init_text:len()}, rv)
+ end)
+
+ it('when col = line-length, set the mark on eol #extmarks', function()
+ local invalid_col = init_text:len() + 1
+ eq({false, "col value outside range"}, meth_pcall(curbufmeths.set_extmark, ns, marks[1], 0, invalid_col))
+ end)
+
+ -- TODO(bfredl): decide what to do with this
+ pending('when line > line, set the mark on end of buffer #extmarks', function()
+ local invalid_col = init_text:len() + 1
+ local invalid_lnum = 3 -- line1 ends in an eol. so line 2 contains a valid position (eol)?
+ curbufmeths.set_extmark(ns, marks[1], invalid_lnum, invalid_col)
+ rv = curbufmeths.get_extmark_by_id(ns, marks[1])
+ eq({2, 1}, rv)
+ end)
+
+ it('bug from check_col in extmark_set #extmarks_sub', function()
+ -- This bug was caused by extmark_set always using
+ -- check_col. check_col always uses the current buffer.
+ -- This wasn't working during undo so we now use
+ -- check_col and check_lnum only when they are required.
+ feed('A<cr>67890<cr>xx<esc>')
+ feed('A<cr>12345<cr>67890<cr>xx<esc>')
+ curbufmeths.set_extmark(ns, marks[1], 3, 4)
+ feed([[:1,5s:5\n:5 <cr>]])
+ check_undo_redo(ns, marks[1], 3, 4, 2, 6)
+ end)
+
+end)
+
+describe('Extmarks buffer api with many marks', function()
+ local ns1
+ local ns2
+ local ns_marks = {}
+ before_each(function()
+ clear()
+ ns1 = request('nvim_create_namespace', "ns1")
+ ns2 = request('nvim_create_namespace', "ns2")
+ ns_marks = {[ns1]={}, [ns2]={}}
+ local lines = {}
+ for i = 1,30 do
+ lines[#lines+1] = string.rep("x ",i)
+ end
+ curbufmeths.set_lines(0, -1, true, lines)
+ local ns = ns1
+ local q = 0
+ for i = 0,29 do
+ for j = 0,i do
+ local id = curbufmeths.set_extmark(ns,0, i,j)
+ eq(nil, ns_marks[ns][id])
+ ok(id > 0)
+ ns_marks[ns][id] = {i,j}
+ ns = ns1+ns2-ns
+ q = q + 1
+ end
+ end
+ eq(233, #ns_marks[ns1])
+ eq(232, #ns_marks[ns2])
+
+ end)
+
+ local function get_marks(ns)
+ local mark_list = curbufmeths.get_extmarks(ns, 0, -1, -1)
+ local marks = {}
+ for _, mark in ipairs(mark_list) do
+ local id, row, col = unpack(mark)
+ eq(nil, marks[id], "duplicate mark")
+ marks[id] = {row,col}
+ end
+ return marks
+ end
+
+ it("can get marks #extmarks", function()
+ eq(ns_marks[ns1], get_marks(ns1))
+ eq(ns_marks[ns2], get_marks(ns2))
+ end)
+
+ it("can clear all marks in ns #extmarks", function()
+ curbufmeths.clear_namespace(ns1, 0, -1)
+ eq({}, get_marks(ns1))
+ eq(ns_marks[ns2], get_marks(ns2))
+ curbufmeths.clear_namespace(ns2, 0, -1)
+ eq({}, get_marks(ns1))
+ eq({}, get_marks(ns2))
+ end)
+
+ it("can clear line range #extmarks", function()
+ curbufmeths.clear_namespace(ns1, 10, 20)
+ for id, mark in pairs(ns_marks[ns1]) do
+ if 10 <= mark[1] and mark[1] < 20 then
+ ns_marks[ns1][id] = nil
+ end
+ end
+ eq(ns_marks[ns1], get_marks(ns1))
+ eq(ns_marks[ns2], get_marks(ns2))
+ end)
+
+ it("can delete line #extmarks", function()
+ feed('10Gdd')
+ for _, marks in pairs(ns_marks) do
+ for id, mark in pairs(marks) do
+ if mark[1] == 9 then
+ marks[id] = {9,0}
+ elseif mark[1] >= 10 then
+ mark[1] = mark[1] - 1
+ end
+ end
+ end
+ eq(ns_marks[ns1], get_marks(ns1))
+ eq(ns_marks[ns2], get_marks(ns2))
+ end)
+
+ it("can delete lines #extmarks", function()
+ feed('10G10dd')
+ for _, marks in pairs(ns_marks) do
+ for id, mark in pairs(marks) do
+ if 9 <= mark[1] and mark[1] < 19 then
+ marks[id] = {9,0}
+ elseif mark[1] >= 19 then
+ mark[1] = mark[1] - 10
+ end
+ end
+ end
+ eq(ns_marks[ns1], get_marks(ns1))
+ eq(ns_marks[ns2], get_marks(ns2))
+ end)
+end)
diff --git a/test/functional/legacy/046_multi_line_regexps_spec.lua b/test/functional/legacy/046_multi_line_regexps_spec.lua
index 30ec76ea3e..9d3ee57b46 100644
--- a/test/functional/legacy/046_multi_line_regexps_spec.lua
+++ b/test/functional/legacy/046_multi_line_regexps_spec.lua
@@ -8,22 +8,22 @@ local expect = helpers.expect
describe('multi-line regexp', function()
setup(clear)
- it('is working', function()
+ it('is working #fail', function()
insert([[
- 1 aa
- bb
- cc
- 2 dd
- ee
- 3 ef
- gh
- 4 ij
- 5 a8
- 8b c9
- 9d
- 6 e7
- 77f
- xxxxx]])
+1 aa
+bb
+cc
+2 dd
+ee
+3 ef
+gh
+4 ij
+5 a8
+8b c9
+9d
+6 e7
+77f
+xxxxx]])
-- Test if replacing a line break works with a back reference
feed([[:/^1/,/^2/s/\n\(.\)/ \1/<cr>]])
diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua
index b841574643..0136e4e7e0 100644
--- a/test/functional/ui/inccommand_spec.lua
+++ b/test/functional/ui/inccommand_spec.lua
@@ -131,7 +131,7 @@ describe(":substitute, inccommand=split interactivity", function()
end)
end)
-describe(":substitute, 'inccommand' preserves", function()
+describe(":substitute, 'inccommand' preserves #inc", function()
before_each(clear)
it('listed buffers (:ls)', function()
@@ -293,7 +293,7 @@ describe(":substitute, 'inccommand' preserves", function()
end)
-describe(":substitute, 'inccommand' preserves undo", function()
+describe(":substitute, 'inccommand' preserves undo #inc", function()
local cases = { "", "split", "nosplit" }
local substrings = {
@@ -1962,7 +1962,7 @@ describe(":substitute", function()
clear()
end)
- it("inccommand=split, highlights multiline substitutions", function()
+ it("inccommand=split, highlights multiline substitutions #inc2", function()
common_setup(screen, "split", multiline_text)
feed("gg")
@@ -2024,7 +2024,7 @@ describe(":substitute", function()
]])
end)
- it("inccommand=nosplit, highlights multiline substitutions", function()
+ it("inccommand=nosplit, highlights multiline substitutions #inc2", function()
common_setup(screen, "nosplit", multiline_text)
feed("gg")
@@ -2117,7 +2117,7 @@ describe(":substitute", function()
]])
end)
- it("inccommand=split, with \\zs", function()
+ it("inccommand=split, with \\zs #inc", function()
common_setup(screen, "split", multiline_text)
feed("gg")
@@ -2141,7 +2141,7 @@ describe(":substitute", function()
]])
end)
- it("inccommand=nosplit, with \\zs", function()
+ it("inccommand=nosplit, with \\zs #inc", function()
common_setup(screen, "nosplit", multiline_text)
feed("gg")
@@ -2212,7 +2212,7 @@ describe(":substitute", function()
]])
end)
- it("inccommand=split, contraction of lines", function()
+ it("inccommand=split, contraction of lines #inc2", function()
local text = [[
T T123 T T123 T2T TT T23423424
x
@@ -2261,7 +2261,7 @@ describe(":substitute", function()
]])
end)
- it("inccommand=nosplit, contraction of lines", function()
+ it("inccommand=nosplit, contraction of lines #inc2", function()
local text = [[
T T123 T T123 T2T TT T23423424
x
diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua
index 7840ba9167..2c50bcb97b 100644
--- a/test/functional/ui/mouse_spec.lua
+++ b/test/functional/ui/mouse_spec.lua
@@ -817,7 +817,6 @@ describe('ui/mouse/input', function()
feed_command('syntax match NonText "cats" conceal cchar=X')
feed_command('syntax match NonText "x" conceal cchar=>')
- -- First column is there to retain the tabs.
insert([[
|Section *t1*
| *t2* *t3* *t4*