From a9065a50518ef59351f9d0d32041a991a751653f Mon Sep 17 00:00:00 2001 From: timeyyy Date: Wed, 18 Jan 2017 13:20:07 +0100 Subject: [PATCH] nsmarks: initial commit --- README.md | 11 + runtime/doc/api.txt | 46 + src/nvim/api/buffer.c | 217 ++- src/nvim/api/private/helpers.c | 117 ++ src/nvim/api/vim.c | 1 + src/nvim/buffer.c | 3 + src/nvim/buffer_defs.h | 7 + src/nvim/change.c | 18 +- src/nvim/diff.c | 3 +- src/nvim/edit.c | 42 +- src/nvim/ex_cmds.c | 297 +++- src/nvim/lib/kbtree.h | 6 + src/nvim/map.c | 1 + src/nvim/map.h | 1 + src/nvim/mark.c | 25 +- src/nvim/mark_extended.c | 1262 +++++++++++++++ src/nvim/mark_extended.h | 267 ++++ src/nvim/mark_extended_defs.h | 54 + src/nvim/misc1.c | 1 - src/nvim/ops.c | 185 ++- src/nvim/pos.h | 4 + src/nvim/undo.c | 53 +- src/nvim/undo_defs.h | 16 +- test/functional/api/mark_extended_spec.lua | 1365 +++++++++++++++++ .../legacy/046_multi_line_regexps_spec.lua | 30 +- test/functional/ui/inccommand_spec.lua | 16 +- test/functional/ui/mouse_spec.lua | 1 - 27 files changed, 3968 insertions(+), 81 deletions(-) create mode 100644 src/nvim/mark_extended.c create mode 100644 src/nvim/mark_extended.h create mode 100644 src/nvim/mark_extended_defs.h create mode 100644 test/functional/api/mark_extended_spec.lua 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 + + +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 @@ -438,6 +438,36 @@ Example: create a float with scratch buffer: > call nvim_win_set_option(win, 'winhl', 'Normal:MyHighlight') > +============================================================================== +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* @@ -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 +#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 // 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("") + 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') + 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('') + 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') + 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('') + 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('A12345') + feed('A12345') + 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('A12345') + feed('A12345') + 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("a2233") + 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("a222") + 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("a1") + 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("a222333444") + 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("a222333444") + 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("a222333444") + 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') + check_undo_redo(ns, marks[1], 0, 4, 1, 4) + check_undo_redo(ns, marks[2], 1, 4, 2, 4) + feed('2Go') + 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') + 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('a12345') + curbufmeths.set_extmark(ns, marks[1], 1, 2) + -- Insert a fullwidth (two col) tilde, NICE + feed('0i~') + 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('a12345') + curbufmeths.set_extmark(ns, marks[1], 1, 2) + feed('0lkI9') + 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') + 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') + 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") + curbufmeths.set_extmark(ns, marks[1], 0, 4) + feed("04li") + 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") + 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") + 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') + 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('a12345') + curbufmeths.set_extmark(ns, marks[1], 1, 4) + feed('hhhkd') + 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('a12345') + 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('0k3lx') + 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('$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('a12345test-me') + curbufmeths.set_extmark(ns, marks[1], 2, 5) + feed('gg') + feed('dv?-m?') + 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') + check_undo_redo(ns, marks[1], 0, 3, 0, 2) + + feed("u") + feed('0vlx') + check_undo_redo(ns, marks[1], 0, 3, 0, 1) + + feed("u") + feed('0v2lx') + 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') + check_undo_redo(ns, marks[1], 0, 3, 0, 2) + + feed("u") + feed('02x') + check_undo_redo(ns, marks[1], 0, 3, 0, 1) + + feed("u") + feed('0v3lx') + 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') + 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') + 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('a12345') + curbufmeths.set_extmark(ns, marks[1], 1, 4) + feed('hhkyP') + 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('a12345') + curbufmeths.set_extmark(ns, marks[1], 1, 4) + feed('hhkyp') + 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('a12345') + curbufmeths.set_extmark(ns, marks[1], 0, 2) + feed('0llkr1') + 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') + 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('') -- 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') + feed('a12345') + curbufmeths.set_extmark(ns, marks[1], 1, 2) + feed('0k>') + check_undo_redo(ns, marks[1], 1, 2, 1, 6) + feed('j>') + check_undo_redo(ns, marks[1], 1, 6, 1, 10) + + feed('j') + 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') + feed(':set shiftwidth=2') + curbufmeths.set_extmark(ns, marks[1], 0, 2) + feed('0i') + check_undo_redo(ns, marks[1], 0, 2, 0, 6) + end) + + it('tabs work #extmarks', function() + -- ins_tab in edit.c + feed(':set noexpandtab') + feed(':set shiftwidth=2') + feed(':set softtabstop=2') + feed(':set tabstop=8') + curbufmeths.set_extmark(ns, marks[1], 0, 2) + feed('0i') + check_undo_redo(ns, marks[1], 0, 2, 0, 4) + feed('0iX') + 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('A2:1move 2') + check_undo_redo(ns, marks[1], 0, 0, 1, 0) + -- test codepath when moving lines up + feed(':2move 0') + 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('A23456') + curbufmeths.set_extmark(ns, marks[1], 1, 0) + feed(':2,3move 5') + check_undo_redo(ns, marks[1], 1, 0, 3, 0) + -- test codepath when moving lines up + feed(':4,5move 1') + 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') + curbufmeths.set_extmark(ns, marks[1], 0, 1) + feed('o') + 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("") + rv = curbufmeths.get_extmarks(ns, {0, 0}, {-1, -1}, ALL) + eq(3, table.getn(rv)) + + -- Test updates + feed('o') + 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("") + check_undo_redo(ns, marks[1], 0, 1, positions[1][1], positions[1][2]) + + -- Test unset + feed('o') + curbufmeths.del_extmark(ns, marks[3]) + feed("u") + rv = curbufmeths.get_extmarks(ns, {0, 0}, {-1, -1}, ALL) + eq(3, table.getn(rv)) + feed("") + 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('A12345') + 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') + feed(':set autoindent') + feed(':set shiftwidth=2') + feed("0iint A {1M1b") + -- 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") + 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') + feed(':set autoindent') + feed(':set shiftwidth=2') + -- will force an indent of 2 + feed("0iint A {0i1M1") + curbufmeths.set_extmark(ns, marks[1], 1, 1) + feed("0i") + 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") + rv = curbufmeths.get_extmark_by_id(ns, marks[1]) + eq({1, 3}, rv) + end) + + it('removing auto indenting with works #extmarks', function() + feed(':set cindent') + feed(':set autoindent') + feed(':set shiftwidth=2') + feed("0i") + curbufmeths.set_extmark(ns, marks[1], 0, 3) + feed("bi") + 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") + 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') + feed(':set autoindent') + feed(':set shiftwidth=2') + feed("0iint A {1M12M2") + 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') + 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') + 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') + 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('Ax34xx') + feed('Axxx34') + 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') + 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') + 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') + 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('A67890xx') + 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 ]]) + 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('A67890xx') + 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]]) + 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('A67890xx') + 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]]) + 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('A67890xx') + 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]]) + 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]]) + 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('Ax3xx') + 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]]) + 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('Ax3xx') + 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]]) + 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]]) + 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('A67890xx') + 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]]) + 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('A67890xx') + 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]]) + 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('A67890xxxx') + 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]]) + 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('A67890xxxx') + 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]]) + 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]]) + check_undo_redo(ns, marks[1], 0, 0, 0, 1) + check_undo_redo(ns, marks[2], 0, 1, 0, 2) + end) + + it('using without increase in order of magnitude #extmarks_inc_dec', function() + -- do_addsub in ops.c + feed('ddiabc998xxxTc') + 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('') + 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 when increase in order of magnitude #extmarks_inc_dec', function() + -- do_addsub in ops.c + feed('ddiabc999xxxTc') + 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('') + 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 when negative and without decrease in order of magnitude #extmarks_inc_dec', function() + feed('ddiabc-999xxxT-') + 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('') + 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 when negative and decrease in order of magnitude #extmarks_inc_dec', function() + feed('ddiabc-1000xxxT-') + 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('') + 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 without decrease in order of magnitude #extmarks_inc_dec', function() + -- do_addsub in ops.c + feed('ddiabc999xxxTc') + 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('') + 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 when decrease in order of magnitude #extmarks_inc_dec', function() + -- do_addsub in ops.c + feed('ddiabc1000xxxTc') + 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('') + 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 when negative and without increase in order of magnitude #extmarks_inc_dec', function() + feed('ddiabc-998xxxT-') + 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('') + 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 when negative and increase in order of magnitude #extmarks_inc_dec', function() + feed('ddiabc-999xxxT-') + 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('') + 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('A67890xx') + feed('A1234567890xx') + curbufmeths.set_extmark(ns, marks[1], 3, 4) + feed([[:1,5s:5\n:5 ]]) + 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/]]) 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*