aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/api/buffer.c292
-rw-r--r--src/nvim/api/private/helpers.c126
-rw-r--r--src/nvim/api/vim.c1
-rw-r--r--src/nvim/buffer.c3
-rw-r--r--src/nvim/buffer_defs.h29
-rw-r--r--src/nvim/change.c18
-rw-r--r--src/nvim/diff.c32
-rw-r--r--src/nvim/diff.h7
-rw-r--r--src/nvim/edit.c42
-rw-r--r--src/nvim/eval.c123
-rw-r--r--src/nvim/eval.h1
-rw-r--r--src/nvim/ex_cmds.c295
-rw-r--r--src/nvim/ex_docmd.c4
-rw-r--r--src/nvim/fold.c5
-rw-r--r--src/nvim/getchar.c1
-rw-r--r--src/nvim/globals.h6
-rw-r--r--src/nvim/lib/kbtree.h6
-rw-r--r--src/nvim/lua/converter.c40
-rw-r--r--src/nvim/lua/executor.c197
-rw-r--r--src/nvim/lua/executor.h2
-rw-r--r--src/nvim/lua/vim.lua17
-rw-r--r--src/nvim/main.c1
-rw-r--r--src/nvim/mark.c25
-rw-r--r--src/nvim/mark_extended.c1135
-rw-r--r--src/nvim/mark_extended.h282
-rw-r--r--src/nvim/mark_extended_defs.h54
-rw-r--r--src/nvim/misc1.c1
-rw-r--r--src/nvim/ops.c143
-rw-r--r--src/nvim/options.lua2
-rw-r--r--src/nvim/os/tty.c1
-rw-r--r--src/nvim/po/check.vim11
-rw-r--r--src/nvim/pos.h4
-rw-r--r--src/nvim/quickfix.c54
-rw-r--r--src/nvim/screen.c5
-rw-r--r--src/nvim/spell.c11
-rw-r--r--src/nvim/testdir/test_diffmode.vim25
-rw-r--r--src/nvim/testdir/test_functions.vim7
-rw-r--r--src/nvim/testdir/test_join.vim21
-rw-r--r--src/nvim/testdir/test_quickfix.vim22
-rw-r--r--src/nvim/tui/input.c8
-rw-r--r--src/nvim/tui/tui.c2
-rw-r--r--src/nvim/undo.c77
-rw-r--r--src/nvim/undo_defs.h16
-rw-r--r--src/nvim/window.c17
44 files changed, 2965 insertions, 206 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index cb74c4227b..9ec96840d1 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,238 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
return rv;
}
+/// Returns position 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 ns_id,
+ Integer id, Error *err)
+ FUNC_API_SINCE(7)
+{
+ 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 ns_id"));
+ return rv;
+ }
+
+ Extmark *extmark = extmark_from_id(buf, (uint64_t)ns_id, (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 ns_id 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 opts additional options. Supports the keys:
+/// - amount: Maximum number of marks to return
+/// @param[out] err Details of an error that may have occurred
+/// @return [[extmark_id, row, col], ...]
+Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id,
+ Object start, Object end, Dictionary opts,
+ Error *err)
+ FUNC_API_SINCE(7)
+{
+ 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 ns_id"));
+ return rv;
+ }
+ Integer amount = -1;
+
+ for (size_t i = 0; i < opts.size; i++) {
+ String k = opts.items[i].key;
+ Object *v = &opts.items[i].value;
+ if (strequal("amount", k.data)) {
+ if (v->type != kObjectTypeInteger) {
+ api_set_error(err, kErrorTypeValidation, "amount is not an integer");
+ return rv;
+ }
+ amount = v->data.integer;
+ v->data.integer = LUA_NOREF;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ 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;
+ Extmark *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 an extmark at a position
+///
+/// If an invalid namespace is given, an error will be raised.
+///
+/// To create a new extmark, pass in id=0. The new extmark id will be
+/// returned. To move an existing mark, pass in its id.
+///
+/// It is also allowed to create a new mark by passing in a previously unused
+/// id, but the caller must then keep track of existing and unused ids itself.
+/// This is mainly useful over RPC, to avoid needing to wait for the return
+/// value.
+///
+/// @param buffer The buffer handle
+/// @param ns_id a identifier returned previously with nvim_create_namespace
+/// @param id The extmark's id or 0 to create a new mark.
+/// @param row The row to set the extmark to.
+/// @param col The column to set the extmark to.
+/// @param opts Optional parameters. Currently not used.
+/// @param[out] err Details of an error that may have occurred
+/// @return the id of the extmark.
+Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id,
+ Integer line, Integer col,
+ Dictionary opts, Error *err)
+ FUNC_API_SINCE(7)
+{
+ 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 ns_id"));
+ return 0;
+ }
+
+ if (opts.size > 0) {
+ api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ 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;
+ }
+
+ extmark_set(buf, (uint64_t)ns_id, id_num,
+ (linenr_T)line+1, (colnr_T)col+1, kExtmarkUndo);
+
+ return (Integer)id_num;
+}
+
+/// 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 the extmark was not found.
+Boolean nvim_buf_del_extmark(Buffer buffer,
+ Integer ns_id,
+ Integer id,
+ Error *err)
+ FUNC_API_SINCE(7)
+{
+ 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 ns_id"));
+ 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 +1333,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
@@ -1207,6 +1447,56 @@ free_exit:
return 0;
}
+/// Get the virtual text (annotation) for a buffer line.
+///
+/// The virtual text is returned as list of lists, whereas the inner lists have
+/// either one or two elements. The first element is the actual text, the
+/// optional second element is the highlight group.
+///
+/// The format is exactly the same as given to nvim_buf_set_virtual_text().
+///
+/// If there is no virtual text associated with the given line, an empty list
+/// is returned.
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param line Line to get the virtual text from (zero-indexed)
+/// @param[out] err Error details, if any
+/// @return List of virtual text chunks
+Array nvim_buf_get_virtual_text(Buffer buffer, Integer lnum, Error *err)
+ FUNC_API_SINCE(7)
+{
+ Array chunks = ARRAY_DICT_INIT;
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return chunks;
+ }
+
+ if (lnum < 0 || lnum >= MAXLNUM) {
+ api_set_error(err, kErrorTypeValidation, "Line number outside range");
+ return chunks;
+ }
+
+ BufhlLine *lineinfo = bufhl_tree_ref(&buf->b_bufhl_info, (linenr_T)(lnum + 1),
+ false);
+ if (!lineinfo) {
+ return chunks;
+ }
+
+ for (size_t i = 0; i < lineinfo->virt_text.size; i++) {
+ Array chunk = ARRAY_DICT_INIT;
+ VirtTextChunk *vtc = &lineinfo->virt_text.items[i];
+ ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text)));
+ if (vtc->hl_id > 0) {
+ ADD(chunk, STRING_OBJ(cstr_to_string(
+ (const char *)syn_id2name(vtc->hl_id))));
+ }
+ ADD(chunks, ARRAY_OBJ(chunk));
+ }
+
+ return chunks;
+}
+
Dictionary nvim__buf_stats(Buffer buffer, Error *err)
{
Dictionary rv = ARRAY_DICT_INIT;
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 2056cb07e3..fbfdb27827 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -10,6 +10,7 @@
#include "nvim/api/private/helpers.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/handle.h"
+#include "nvim/api/vim.h"
#include "nvim/msgpack_rpc/helpers.h"
#include "nvim/lua/executor.h"
#include "nvim/ascii.h"
@@ -23,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"
@@ -1505,3 +1507,127 @@ 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
+Extmark *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;
+ }
+
+ Extmark *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)
+{
+ if (ns < 1) {
+ return false;
+ }
+ return ns < (uint64_t)next_namespace_id;
+}
+
+/// Get line and column from extmark object
+///
+/// Extmarks may be queried from position or name or even special names
+/// in the future such as "cursor". This function sets the line and col
+/// to make the extmark functions recognize what's required
+///
+/// @param[out] lnum lnum to be set
+/// @param[out] colnr 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;
+ }
+
+ Extmark *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..700d8b82e6 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(extmarklines) 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;
@@ -911,19 +918,19 @@ typedef struct w_line {
* or row (FR_ROW) layout or is a leaf, which has a window.
*/
struct frame_S {
- char fr_layout; /* FR_LEAF, FR_COL or FR_ROW */
+ char fr_layout; // FR_LEAF, FR_COL or FR_ROW
int fr_width;
- int fr_newwidth; /* new width used in win_equal_rec() */
+ int fr_newwidth; // new width used in win_equal_rec()
int fr_height;
- int fr_newheight; /* new height used in win_equal_rec() */
- frame_T *fr_parent; /* containing frame or NULL */
- frame_T *fr_next; /* frame right or below in same parent, NULL
- for first */
- frame_T *fr_prev; /* frame left or above in same parent, NULL
- for last */
- /* fr_child and fr_win are mutually exclusive */
- frame_T *fr_child; /* first contained frame */
- win_T *fr_win; /* window that fills this frame */
+ int fr_newheight; // new height used in win_equal_rec()
+ frame_T *fr_parent; // containing frame or NULL
+ frame_T *fr_next; // frame right or below in same parent, NULL
+ // for last
+ frame_T *fr_prev; // frame left or above in same parent, NULL
+ // for first
+ // fr_child and fr_win are mutually exclusive
+ frame_T *fr_child; // first contained frame
+ win_T *fr_win; // window that fills this frame
};
#define FR_LEAF 0 /* frame is a leaf */
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..dccde01d29 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -44,7 +44,7 @@
#include "nvim/os/shell.h"
static int diff_busy = false; // using diff structs, don't change them
-static int diff_need_update = false; // ex_diffupdate needs to be called
+static bool diff_need_update = false; // ex_diffupdate needs to be called
// Flags obtained from the 'diffopt' option
#define DIFF_FILLER 0x001 // display filler lines
@@ -57,8 +57,9 @@ static int diff_need_update = false; // ex_diffupdate needs to be called
#define DIFF_VERTICAL 0x080 // vertical splits
#define DIFF_HIDDEN_OFF 0x100 // diffoff when hidden
#define DIFF_INTERNAL 0x200 // use internal xdiff algorithm
+#define DIFF_CLOSE_OFF 0x400 // diffoff when closing window
#define ALL_WHITE_DIFF (DIFF_IWHITE | DIFF_IWHITEALL | DIFF_IWHITEEOL)
-static int diff_flags = DIFF_INTERNAL | DIFF_FILLER;
+static int diff_flags = DIFF_INTERNAL | DIFF_FILLER | DIFF_CLOSE_OFF;
static long diff_algorithm = 0;
@@ -490,7 +491,8 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1,
}
if (tp == curtab) {
- diff_redraw(true);
+ // Don't redraw right away, this updates the diffs, which can be slow.
+ need_diff_redraw = true;
// Need to recompute the scroll binding, may remove or add filler
// lines (e.g., when adding lines above w_topline). But it's slow when
@@ -634,8 +636,9 @@ static int diff_check_sanity(tabpage_T *tp, diff_T *dp)
/// Mark all diff buffers in the current tab page for redraw.
///
/// @param dofold Also recompute the folds
-static void diff_redraw(int dofold)
+void diff_redraw(bool dofold)
{
+ need_diff_redraw = false;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (!wp->w_p_diff) {
continue;
@@ -1472,6 +1475,13 @@ void ex_diffoff(exarg_T *eap)
diff_buf_clear();
}
+ if (!diffwin) {
+ diff_need_update = false;
+ curtab->tp_diff_invalid = false;
+ curtab->tp_diff_update = false;
+ diff_clear(curtab);
+ }
+
// Remove "hor" from from 'scrollopt' if there are no diff windows left.
if (!diffwin && (vim_strchr(p_sbo, 'h') != NULL)) {
do_cmdline_cmd("set sbo-=hor");
@@ -1712,6 +1722,7 @@ static void diff_copy_entry(diff_T *dprev, diff_T *dp, int idx_orig,
///
/// @param tp
void diff_clear(tabpage_T *tp)
+ FUNC_ATTR_NONNULL_ALL
{
diff_T *p;
diff_T *next_p;
@@ -2141,6 +2152,9 @@ int diffopt_changed(void)
} else if (STRNCMP(p, "hiddenoff", 9) == 0) {
p += 9;
diff_flags_new |= DIFF_HIDDEN_OFF;
+ } else if (STRNCMP(p, "closeoff", 8) == 0) {
+ p += 8;
+ diff_flags_new |= DIFF_CLOSE_OFF;
} else if (STRNCMP(p, "indent-heuristic", 16) == 0) {
p += 16;
diff_indent_heuristic = XDF_INDENT_HEURISTIC;
@@ -2216,6 +2230,13 @@ bool diffopt_hiddenoff(void)
return (diff_flags & DIFF_HIDDEN_OFF) != 0;
}
+// Return true if 'diffopt' contains "closeoff".
+bool diffopt_closeoff(void)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return (diff_flags & DIFF_CLOSE_OFF) != 0;
+}
+
/// Find the difference within a changed line.
///
/// @param wp window whose current buffer to check
@@ -2690,7 +2711,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/diff.h b/src/nvim/diff.h
index 3624ce29bb..99a60381bd 100644
--- a/src/nvim/diff.h
+++ b/src/nvim/diff.h
@@ -4,6 +4,13 @@
#include "nvim/pos.h"
#include "nvim/ex_cmds_defs.h"
+// Value set from 'diffopt'.
+EXTERN int diff_context INIT(= 6); // context for folds
+EXTERN int diff_foldcolumn INIT(= 2); // 'foldcolumn' for diff mode
+EXTERN bool diff_need_scrollbind INIT(= false);
+
+EXTERN bool need_diff_redraw INIT(= false); // need to call diff_redraw()
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "diff.h.generated.h"
#endif
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/eval.c b/src/nvim/eval.c
index e08e129656..9fe92a92cc 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -422,6 +422,7 @@ static struct vimvar {
VV(VV_TYPE_BOOL, "t_bool", VAR_NUMBER, VV_RO),
VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO),
VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO),
+ VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO),
};
#undef VV
@@ -433,11 +434,14 @@ static struct vimvar {
#define vv_str vv_di.di_tv.vval.v_string
#define vv_list vv_di.di_tv.vval.v_list
#define vv_dict vv_di.di_tv.vval.v_dict
+#define vv_partial vv_di.di_tv.vval.v_partial
#define vv_tv vv_di.di_tv
/// Variable used for v:
static ScopeDictDictItem vimvars_var;
+static partial_T *vvlua_partial;
+
/// v: hashtab
#define vimvarht vimvardict.dv_hashtab
@@ -639,6 +643,13 @@ void eval_init(void)
set_vim_var_nr(VV_ECHOSPACE, sc_col - 1);
+ vimvars[VV_LUA].vv_type = VAR_PARTIAL;
+ vvlua_partial = xcalloc(1, sizeof(partial_T));
+ vimvars[VV_LUA].vv_partial = vvlua_partial;
+ // this value shouldn't be printed, but if it is, do not crash
+ vvlua_partial->pt_name = xmallocz(0);
+ vvlua_partial->pt_refcount++;
+
set_reg_var(0); // default for v:register is not 0 but '"'
}
@@ -1313,12 +1324,25 @@ int call_vim_function(
{
int doesrange;
int ret;
+ int len = (int)STRLEN(func);
+ partial_T *pt = NULL;
+
+ if (len >= 6 && !memcmp(func, "v:lua.", 6)) {
+ func += 6;
+ len = check_luafunc_name((const char *)func, false);
+ if (len == 0) {
+ ret = FAIL;
+ goto fail;
+ }
+ pt = vvlua_partial;
+ }
rettv->v_type = VAR_UNKNOWN; // tv_clear() uses this.
- ret = call_func(func, (int)STRLEN(func), rettv, argc, argv, NULL,
+ ret = call_func(func, len, rettv, argc, argv, NULL,
curwin->w_cursor.lnum, curwin->w_cursor.lnum,
- &doesrange, true, NULL, NULL);
+ &doesrange, true, pt, NULL);
+fail:
if (ret == FAIL) {
tv_clear(rettv);
}
@@ -2462,6 +2486,13 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,
}
}
+ if (lp->ll_di != NULL && tv_is_luafunc(&lp->ll_di->di_tv)
+ && len == -1 && rettv == NULL) {
+ tv_clear(&var1);
+ EMSG2(e_illvar, "v:['lua']");
+ return NULL;
+ }
+
if (lp->ll_di == NULL) {
// Can't add "v:" or "a:" variable.
if (lp->ll_dict == &vimvardict
@@ -4699,7 +4730,7 @@ eval_index(
if (evaluate) {
n1 = 0;
- if (!empty1 && rettv->v_type != VAR_DICT) {
+ if (!empty1 && rettv->v_type != VAR_DICT && !tv_is_luafunc(rettv)) {
n1 = tv_get_number(&var1);
tv_clear(&var1);
}
@@ -4823,7 +4854,7 @@ eval_index(
if (len == -1) {
tv_clear(&var1);
}
- if (item == NULL) {
+ if (item == NULL || tv_is_luafunc(&item->di_tv)) {
return FAIL;
}
@@ -6334,7 +6365,7 @@ static char_u *deref_func_name(const char *name, int *lenp,
*/
static int
get_func_tv(
- char_u *name, // name of the function
+ const char_u *name, // name of the function
int len, // length of "name"
typval_T *rettv,
char_u **arg, // argument, pointing to the '('
@@ -6590,7 +6621,15 @@ call_func(
rettv->vval.v_number = 0;
error = ERROR_UNKNOWN;
- if (!builtin_function((const char *)rfname, -1)) {
+ if (partial == vvlua_partial) {
+ if (len > 0) {
+ error = ERROR_NONE;
+ executor_call_lua((const char *)funcname, len,
+ argvars, argcount, rettv);
+ } else {
+ error = ERROR_UNKNOWN;
+ }
+ } else if (!builtin_function((const char *)rfname, -1)) {
// User defined function.
if (partial != NULL && partial->pt_func != NULL) {
fp = partial->pt_func;
@@ -6707,14 +6746,14 @@ call_func(
///
/// @param ermsg must be passed without translation (use N_() instead of _()).
/// @param name function name
-static void emsg_funcname(char *ermsg, char_u *name)
+static void emsg_funcname(char *ermsg, const char_u *name)
{
char_u *p;
if (*name == K_SPECIAL) {
p = concat_str((char_u *)"<SNR>", name + 3);
} else {
- p = name;
+ p = (char_u *)name;
}
EMSG2(_(ermsg), p);
@@ -8711,7 +8750,7 @@ static void f_getenv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (p == NULL) {
rettv->v_type = VAR_SPECIAL;
- rettv->vval.v_number = kSpecialVarNull;
+ rettv->vval.v_special = kSpecialVarNull;
return;
}
rettv->vval.v_string = p;
@@ -15669,7 +15708,7 @@ static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
const char *name = tv_get_string_buf(&argvars[0], namebuf);
if (argvars[1].v_type == VAR_SPECIAL
- && argvars[1].vval.v_number == kSpecialVarNull) {
+ && argvars[1].vval.v_special == kSpecialVarNull) {
os_unsetenv(name);
} else {
os_setenv(name, tv_get_string_buf(&argvars[1], valbuf), 1);
@@ -20168,6 +20207,26 @@ static void check_vars(const char *name, size_t len)
}
}
+/// check if special v:lua value for calling lua functions
+static bool tv_is_luafunc(typval_T *tv)
+{
+ return tv->v_type == VAR_PARTIAL && tv->vval.v_partial == vvlua_partial;
+}
+
+/// check the function name after "v:lua."
+static int check_luafunc_name(const char *str, bool paren)
+{
+ const char *p = str;
+ while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.') {
+ p++;
+ }
+ if (*p != (paren ? '(' : NUL)) {
+ return 0;
+ } else {
+ return (int)(p-str);
+ }
+}
+
/// Handle expr[expr], expr[expr:expr] subscript and .name lookup.
/// Also handle function call with Funcref variable: func(expr)
/// Can all be combined: dict.func(expr)[idx]['func'](expr)
@@ -20181,9 +20240,30 @@ handle_subscript(
{
int ret = OK;
dict_T *selfdict = NULL;
- char_u *s;
+ const char_u *s;
int len;
typval_T functv;
+ int slen = 0;
+ bool lua = false;
+
+ if (tv_is_luafunc(rettv)) {
+ if (**arg != '.') {
+ tv_clear(rettv);
+ ret = FAIL;
+ } else {
+ (*arg)++;
+
+ lua = true;
+ s = (char_u *)(*arg);
+ slen = check_luafunc_name(*arg, true);
+ if (slen == 0) {
+ tv_clear(rettv);
+ ret = FAIL;
+ }
+ (*arg) += slen;
+ }
+ }
+
while (ret == OK
&& (**arg == '['
@@ -20200,14 +20280,16 @@ handle_subscript(
// Invoke the function. Recursive!
if (functv.v_type == VAR_PARTIAL) {
pt = functv.vval.v_partial;
- s = partial_name(pt);
+ if (!lua) {
+ s = partial_name(pt);
+ }
} else {
s = functv.vval.v_string;
}
} else {
s = (char_u *)"";
}
- ret = get_func_tv(s, (int)STRLEN(s), rettv, (char_u **)arg,
+ ret = get_func_tv(s, lua ? slen : (int)STRLEN(s), rettv, (char_u **)arg,
curwin->w_cursor.lnum, curwin->w_cursor.lnum,
&len, evaluate, pt, selfdict);
@@ -22039,8 +22121,19 @@ trans_function_name(
*pp = (char_u *)end;
} else if (lv.ll_tv->v_type == VAR_PARTIAL
&& lv.ll_tv->vval.v_partial != NULL) {
- name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial));
- *pp = (char_u *)end;
+ if (lv.ll_tv->vval.v_partial == vvlua_partial && *end == '.') {
+ len = check_luafunc_name((const char *)end+1, true);
+ if (len == 0) {
+ EMSG2(e_invexpr2, "v:lua");
+ goto theend;
+ }
+ name = xmallocz(len);
+ memcpy(name, end+1, len);
+ *pp = (char_u *)end+1+len;
+ } else {
+ name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial));
+ *pp = (char_u *)end;
+ }
if (partial != NULL) {
*partial = lv.ll_tv->vval.v_partial;
}
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index e099de831a..2aa08e2074 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -117,6 +117,7 @@ typedef enum {
VV_TYPE_BOOL,
VV_ECHOSPACE,
VV_EXITING,
+ VV_LUA,
} VimVarIndex;
/// All recognized msgpack types
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 1b6d9b50e9..4725246764 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"
@@ -658,10 +659,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 +875,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 +889,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 +901,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 +1287,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 +3222,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 +3451,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;
+
+ // inccommand tests fail without this check
+ if (!preview) {
+ // Required for Undo to work for extmarks.
+ u_save_cursor();
+ }
if (!global_busy) {
sub_nsubs = 0;
@@ -3418,6 +3620,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 +4079,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 +4094,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 +4123,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 +4227,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 +4403,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/ex_docmd.c b/src/nvim/ex_docmd.c
index b826bb3262..641edf4610 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -8326,6 +8326,7 @@ static void ex_normal(exarg_T *eap)
int save_insertmode = p_im;
int save_finish_op = finish_op;
long save_opcount = opcount;
+ const int save_reg_executing = reg_executing;
char_u *arg = NULL;
int l;
char_u *p;
@@ -8420,7 +8421,8 @@ static void ex_normal(exarg_T *eap)
p_im = save_insertmode;
finish_op = save_finish_op;
opcount = save_opcount;
- msg_didout |= save_msg_didout; /* don't reset msg_didout now */
+ reg_executing = save_reg_executing;
+ msg_didout |= save_msg_didout; // don't reset msg_didout now
/* Restore the state (needed when called from a function executed for
* 'indentexpr'). Update the mouse and cursor, they may have changed. */
diff --git a/src/nvim/fold.c b/src/nvim/fold.c
index 5ce953e626..b193b4005c 100644
--- a/src/nvim/fold.c
+++ b/src/nvim/fold.c
@@ -771,6 +771,11 @@ void foldUpdate(win_T *wp, linenr_T top, linenr_T bot)
return;
}
+ if (need_diff_redraw) {
+ // will update later
+ return;
+ }
+
// Mark all folds from top to bot as maybe-small.
fold_T *fp;
(void)foldFind(&wp->w_folds, top, &fp);
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 399f0671b4..c038977127 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -2409,7 +2409,6 @@ int inchar(
did_outofmem_msg = FALSE; /* display out of memory message (again) */
did_swapwrite_msg = FALSE; /* display swap file write error again */
}
- undo_off = FALSE; /* restart undo now */
// Get a character from a script file if there is one.
// If interrupted: Stop reading script files, close them all.
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index c3d1a4d40b..15ad6d8767 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -400,11 +400,6 @@ EXTERN bool mouse_past_eol INIT(= false); /* mouse right of line */
EXTERN int mouse_dragging INIT(= 0); /* extending Visual area with
mouse dragging */
-/* Value set from 'diffopt'. */
-EXTERN int diff_context INIT(= 6); /* context for folds */
-EXTERN int diff_foldcolumn INIT(= 2); /* 'foldcolumn' for diff mode */
-EXTERN int diff_need_scrollbind INIT(= FALSE);
-
/* The root of the menu hierarchy. */
EXTERN vimmenu_T *root_menu INIT(= NULL);
/*
@@ -768,7 +763,6 @@ EXTERN int did_outofmem_msg INIT(= false);
// set after out of memory msg
EXTERN int did_swapwrite_msg INIT(= false);
// set after swap write error msg
-EXTERN int undo_off INIT(= false); // undo switched off for now
EXTERN int global_busy INIT(= 0); // set when :global is executing
EXTERN int listcmd_busy INIT(= false); // set when :argdo, :windo or
// :bufdo is executing
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/lua/converter.c b/src/nvim/lua/converter.c
index 844232c64a..44fe60e9c8 100644
--- a/src/nvim/lua/converter.c
+++ b/src/nvim/lua/converter.c
@@ -377,6 +377,19 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
nlua_pop_typval_table_processing_end:
break;
}
+ case LUA_TUSERDATA: {
+ nlua_pushref(lstate, nlua_nil_ref);
+ bool is_nil = lua_rawequal(lstate, -2, -1);
+ lua_pop(lstate, 1);
+ if (is_nil) {
+ cur.tv->v_type = VAR_SPECIAL;
+ cur.tv->vval.v_special = kSpecialVarNull;
+ } else {
+ EMSG(_("E5101: Cannot convert given lua type"));
+ ret = false;
+ }
+ break;
+ }
default: {
EMSG(_("E5101: Cannot convert given lua type"));
ret = false;
@@ -406,7 +419,13 @@ static bool typval_conv_special = false;
#define TYPVAL_ENCODE_ALLOW_SPECIALS true
#define TYPVAL_ENCODE_CONV_NIL(tv) \
- lua_pushnil(lstate)
+ do { \
+ if (typval_conv_special) { \
+ lua_pushnil(lstate); \
+ } else { \
+ nlua_pushref(lstate, nlua_nil_ref); \
+ } \
+ } while (0)
#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \
lua_pushboolean(lstate, (bool)(num))
@@ -718,7 +737,11 @@ void nlua_push_Object(lua_State *lstate, const Object obj, bool special)
{
switch (obj.type) {
case kObjectTypeNil: {
- lua_pushnil(lstate);
+ if (special) {
+ lua_pushnil(lstate);
+ } else {
+ nlua_pushref(lstate, nlua_nil_ref);
+ }
break;
}
case kObjectTypeLuaRef: {
@@ -1152,6 +1175,19 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err)
break;
}
+ case LUA_TUSERDATA: {
+ nlua_pushref(lstate, nlua_nil_ref);
+ bool is_nil = lua_rawequal(lstate, -2, -1);
+ lua_pop(lstate, 1);
+ if (is_nil) {
+ *cur.obj = NIL;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "Cannot convert userdata");
+ }
+ break;
+ }
+
default: {
type_error:
api_set_error(err, kErrorTypeValidation,
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index c7ff163f83..093c130c5f 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -12,6 +12,7 @@
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/vim.h"
+#include "nvim/msgpack_rpc/channel.h"
#include "nvim/vim.h"
#include "nvim/ex_getln.h"
#include "nvim/ex_cmds2.h"
@@ -47,9 +48,6 @@ typedef struct {
# include "lua/executor.c.generated.h"
#endif
-/// Name of the run code for use in messages
-#define NLUA_EVAL_NAME "<VimL compiled string>"
-
/// Convert lua error into a Vim error message
///
/// @param lstate Lua interpreter state.
@@ -299,6 +297,14 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
lua_pushcfunction(lstate, &nlua_call);
lua_setfield(lstate, -2, "call");
+ // rpcrequest
+ lua_pushcfunction(lstate, &nlua_rpcrequest);
+ lua_setfield(lstate, -2, "rpcrequest");
+
+ // rpcnotify
+ lua_pushcfunction(lstate, &nlua_rpcnotify);
+ lua_setfield(lstate, -2, "rpcnotify");
+
// vim.loop
luv_set_loop(lstate, &main_loop.uv);
luv_set_callback(lstate, nlua_luv_cfpcall);
@@ -314,6 +320,15 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
lua_setfield(lstate, -2, "luv");
lua_pop(lstate, 3);
+ // vim.NIL
+ lua_newuserdata(lstate, 0);
+ lua_createtable(lstate, 0, 0);
+ lua_pushcfunction(lstate, &nlua_nil_tostring);
+ lua_setfield(lstate, -2, "__tostring");
+ lua_setmetatable(lstate, -2);
+ nlua_nil_ref = nlua_ref(lstate, -1);
+ lua_setfield(lstate, -2, "NIL");
+
// internal vim._treesitter... API
nlua_add_treesitter(lstate);
@@ -379,29 +394,6 @@ static lua_State *nlua_enter(void)
return lstate;
}
-/// Execute lua string
-///
-/// @param[in] str String to execute.
-/// @param[out] ret_tv Location where result will be saved.
-///
-/// @return Result of the execution.
-void executor_exec_lua(const String str, typval_T *const ret_tv)
- FUNC_ATTR_NONNULL_ALL
-{
- lua_State *const lstate = nlua_enter();
-
- if (luaL_loadbuffer(lstate, str.data, str.size, NLUA_EVAL_NAME)) {
- nlua_error(lstate, _("E5104: Error while creating lua chunk: %.*s"));
- return;
- }
- if (lua_pcall(lstate, 0, 1, 0)) {
- nlua_error(lstate, _("E5105: Error while calling lua chunk: %.*s"));
- return;
- }
-
- nlua_pop_typval(lstate, ret_tv);
-}
-
static void nlua_print_event(void **argv)
{
char *str = argv[0];
@@ -547,6 +539,10 @@ int nlua_call(lua_State *lstate)
Error err = ERROR_INIT;
size_t name_len;
const char_u *name = (const char_u *)luaL_checklstring(lstate, 1, &name_len);
+ if (!nlua_is_deferred_safe(lstate)) {
+ return luaL_error(lstate, e_luv_api_disabled, "vimL function");
+ }
+
int nargs = lua_gettop(lstate)-1;
if (nargs > MAX_FUNC_ARGS) {
return luaL_error(lstate, "Function called with too many arguments");
@@ -596,6 +592,67 @@ free_vim_args:
return 1;
}
+static int nlua_rpcrequest(lua_State *lstate)
+{
+ if (!nlua_is_deferred_safe(lstate)) {
+ return luaL_error(lstate, e_luv_api_disabled, "rpcrequest");
+ }
+ return nlua_rpc(lstate, true);
+}
+
+static int nlua_rpcnotify(lua_State *lstate)
+{
+ return nlua_rpc(lstate, false);
+}
+
+static int nlua_rpc(lua_State *lstate, bool request)
+{
+ size_t name_len;
+ uint64_t chan_id = (uint64_t)luaL_checkinteger(lstate, 1);
+ const char *name = luaL_checklstring(lstate, 2, &name_len);
+ int nargs = lua_gettop(lstate)-2;
+ Error err = ERROR_INIT;
+ Array args = ARRAY_DICT_INIT;
+
+ for (int i = 0; i < nargs; i++) {
+ lua_pushvalue(lstate, (int)i+3);
+ ADD(args, nlua_pop_Object(lstate, false, &err));
+ if (ERROR_SET(&err)) {
+ api_free_array(args);
+ goto check_err;
+ }
+ }
+
+ if (request) {
+ Object result = rpc_send_call(chan_id, name, args, &err);
+ if (!ERROR_SET(&err)) {
+ nlua_push_Object(lstate, result, false);
+ api_free_object(result);
+ }
+ } else {
+ if (!rpc_send_event(chan_id, name, args)) {
+ api_set_error(&err, kErrorTypeValidation,
+ "Invalid channel: %"PRIu64, chan_id);
+ }
+ }
+
+check_err:
+ if (ERROR_SET(&err)) {
+ lua_pushstring(lstate, err.msg);
+ api_clear_error(&err);
+ return lua_error(lstate);
+ }
+
+ return request ? 1 : 0;
+}
+
+static int nlua_nil_tostring(lua_State *lstate)
+{
+ lua_pushstring(lstate, "vim.NIL");
+ return 1;
+}
+
+
#ifdef WIN32
/// os.getenv: override os.getenv to maintain coherency. #9681
///
@@ -649,10 +706,6 @@ void executor_eval_lua(const String str, typval_T *const arg,
typval_T *const ret_tv)
FUNC_ATTR_NONNULL_ALL
{
- lua_State *const lstate = nlua_enter();
-
- garray_T str_ga;
- ga_init(&str_ga, 1, 80);
#define EVALHEADER "local _A=select(1,...) return ("
const size_t lcmd_len = sizeof(EVALHEADER) - 1 + str.size + 1;
char *lcmd;
@@ -665,30 +718,71 @@ void executor_eval_lua(const String str, typval_T *const arg,
memcpy(lcmd + sizeof(EVALHEADER) - 1, str.data, str.size);
lcmd[lcmd_len - 1] = ')';
#undef EVALHEADER
- if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) {
- nlua_error(lstate,
- _("E5107: Error while creating lua chunk for luaeval(): %.*s"));
- if (lcmd != (char *)IObuff) {
- xfree(lcmd);
- }
- return;
- }
+ typval_exec_lua(lcmd, lcmd_len, "luaeval()", arg, 1, true, ret_tv);
+
if (lcmd != (char *)IObuff) {
xfree(lcmd);
}
+}
- if (arg->v_type == VAR_UNKNOWN) {
- lua_pushnil(lstate);
+void executor_call_lua(const char *str, size_t len, typval_T *const args,
+ int argcount, typval_T *ret_tv)
+ FUNC_ATTR_NONNULL_ALL
+{
+#define CALLHEADER "return "
+#define CALLSUFFIX "(...)"
+ const size_t lcmd_len = sizeof(CALLHEADER) - 1 + len + sizeof(CALLSUFFIX) - 1;
+ char *lcmd;
+ if (lcmd_len < IOSIZE) {
+ lcmd = (char *)IObuff;
} else {
- nlua_push_typval(lstate, arg, true);
+ lcmd = xmalloc(lcmd_len);
+ }
+ memcpy(lcmd, CALLHEADER, sizeof(CALLHEADER) - 1);
+ memcpy(lcmd + sizeof(CALLHEADER) - 1, str, len);
+ memcpy(lcmd + sizeof(CALLHEADER) - 1 + len, CALLSUFFIX,
+ sizeof(CALLSUFFIX) - 1);
+#undef CALLHEADER
+#undef CALLSUFFIX
+
+ typval_exec_lua(lcmd, lcmd_len, "v:lua", args, argcount, false, ret_tv);
+
+ if (lcmd != (char *)IObuff) {
+ xfree(lcmd);
+ }
+}
+
+static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name,
+ typval_T *const args, int argcount, bool special,
+ typval_T *ret_tv)
+{
+ if (check_restricted() || check_secure()) {
+ ret_tv->v_type = VAR_NUMBER;
+ ret_tv->vval.v_number = 0;
+ return;
}
- if (lua_pcall(lstate, 1, 1, 0)) {
- nlua_error(lstate,
- _("E5108: Error while calling lua chunk for luaeval(): %.*s"));
+
+ lua_State *const lstate = nlua_enter();
+ if (luaL_loadbuffer(lstate, lcmd, lcmd_len, name)) {
+ nlua_error(lstate, _("E5107: Error loading lua %.*s"));
return;
}
- nlua_pop_typval(lstate, ret_tv);
+ for (int i = 0; i < argcount; i++) {
+ if (args[i].v_type == VAR_UNKNOWN) {
+ lua_pushnil(lstate);
+ } else {
+ nlua_push_typval(lstate, &args[i], special);
+ }
+ }
+ if (lua_pcall(lstate, argcount, ret_tv ? 1 : 0, 0)) {
+ nlua_error(lstate, _("E5108: Error executing lua %.*s"));
+ return;
+ }
+
+ if (ret_tv) {
+ nlua_pop_typval(lstate, ret_tv);
+ }
}
/// Execute lua string
@@ -774,9 +868,8 @@ void ex_lua(exarg_T *const eap)
xfree(code);
return;
}
- typval_T tv = { .v_type = VAR_UNKNOWN };
- executor_exec_lua((String) { .data = code, .size = len }, &tv);
- tv_clear(&tv);
+ typval_exec_lua(code, len, ":lua", NULL, 0, false, NULL);
+
xfree(code);
}
@@ -814,8 +907,8 @@ void ex_luado(exarg_T *const eap)
#undef DOSTART
#undef DOEND
- if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) {
- nlua_error(lstate, _("E5109: Error while creating lua chunk: %.*s"));
+ if (luaL_loadbuffer(lstate, lcmd, lcmd_len, ":luado")) {
+ nlua_error(lstate, _("E5109: Error loading lua: %.*s"));
if (lcmd_len >= IOSIZE) {
xfree(lcmd);
}
@@ -825,7 +918,7 @@ void ex_luado(exarg_T *const eap)
xfree(lcmd);
}
if (lua_pcall(lstate, 0, 1, 0)) {
- nlua_error(lstate, _("E5110: Error while creating lua function: %.*s"));
+ nlua_error(lstate, _("E5110: Error executing lua: %.*s"));
return;
}
for (linenr_T l = eap->line1; l <= eap->line2; l++) {
@@ -836,7 +929,7 @@ void ex_luado(exarg_T *const eap)
lua_pushstring(lstate, (const char *)ml_get_buf(curbuf, l, false));
lua_pushnumber(lstate, (lua_Number)l);
if (lua_pcall(lstate, 2, 1, 0)) {
- nlua_error(lstate, _("E5111: Error while calling lua function: %.*s"));
+ nlua_error(lstate, _("E5111: Error calling lua: %.*s"));
break;
}
if (lua_isstring(lstate, -1)) {
diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h
index 8d356a5600..32f66b629c 100644
--- a/src/nvim/lua/executor.h
+++ b/src/nvim/lua/executor.h
@@ -12,6 +12,8 @@
// Generated by msgpack-gen.lua
void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL;
+EXTERN LuaRef nlua_nil_ref INIT(= LUA_NOREF);
+
#define set_api_error(s, err) \
do { \
Error *err_ = (err); \
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
index 1ebdde99d5..ce24d1716d 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -256,19 +256,26 @@ local function __index(t, key)
-- Expose all `vim.shared` functions on the `vim` module.
t[key] = require('vim.shared')[key]
return t[key]
+ elseif require('vim.uri')[key] ~= nil then
+ -- Expose all `vim.uri` functions on the `vim` module.
+ t[key] = require('vim.uri')[key]
+ return t[key]
+ elseif key == 'lsp' then
+ t.lsp = require('vim.lsp')
+ return t.lsp
end
end
-- vim.fn.{func}(...)
-local function fn_index(t, key)
- local function func(...)
+local function _fn_index(t, key)
+ local function _fn(...)
return vim.call(key, ...)
end
- t[key] = func
- return func
+ t[key] = _fn
+ return _fn
end
-local fn = setmetatable({}, {__index=fn_index})
+local fn = setmetatable({}, {__index=_fn_index})
local module = {
_update_package_paths = _update_package_paths,
diff --git a/src/nvim/main.c b/src/nvim/main.c
index e0a1e60fc0..e39eec4038 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -27,6 +27,7 @@
#include "nvim/highlight.h"
#include "nvim/iconv.h"
#include "nvim/if_cscope.h"
+#include "nvim/lua/executor.h"
#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index 432639d540..e5070f23ff 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..01745f484d
--- /dev/null
+++ b/src/nvim/mark_extended.c
@@ -0,0 +1,1135 @@
+// 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.
+//
+// For possible ideas for efficency improvements see:
+// http://blog.atom.io/2015/06/16/optimizing-an-important-atom-primitive.html
+// TODO(bfredl): These ideas could be used for an enhanced btree, which
+// wouldn't need separate line and column layers.
+// Other implementations exist in gtk and tk toolkits.
+//
+// Deleting marks only happens when explicitly calling extmark_del, deleteing
+// over a range of marks will only move the marks. Deleting on a mark will
+// leave it in same position unless it is on the EOL of a line.
+
+#include <assert.h>
+#include "nvim/vim.h"
+#include "charset.h"
+#include "nvim/mark_extended.h"
+#include "nvim/memline.h"
+#include "nvim/pos.h"
+#include "nvim/globals.h"
+#include "nvim/map.h"
+#include "nvim/lib/kbtree.h"
+#include "nvim/undo.h"
+#include "nvim/buffer.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "mark_extended.c.generated.h"
+#endif
+
+
+/// Create or update an extmark
+///
+/// must not be used during iteration!
+/// @returns whether a new mark was created
+int extmark_set(buf_T *buf, uint64_t ns, uint64_t id,
+ linenr_T lnum, colnr_T col, ExtmarkOp op)
+{
+ Extmark *extmark = extmark_from_id(buf, ns, id);
+ if (!extmark) {
+ extmark_create(buf, ns, id, lnum, col, op);
+ return true;
+ } else {
+ ExtmarkLine *extmarkline = extmark->line;
+ extmark_update(extmark, buf, ns, id, lnum, col, op, NULL);
+ if (kb_size(&extmarkline->items) == 0) {
+ kb_del(extmarklines, &buf->b_extlines, extmarkline);
+ extmarkline_free(extmarkline);
+ }
+ return false;
+ }
+}
+
+// Remove an extmark
+// Returns 0 on missing id
+int extmark_del(buf_T *buf, uint64_t ns, uint64_t id, ExtmarkOp op)
+{
+ Extmark *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(extmarkline->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, &extmarkline->items, &mitr);
+ }
+ });
+ if (kb_size(&extmarkline->items) == 0) {
+ kb_del_itr(extmarklines, &buf->b_extlines, &itr);
+ extmarkline_free(extmarkline);
+ }
+ });
+
+ // 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 *extmarkline = extmarkline_ref(buf, lnum, true);
+ // Create and put mark on the line
+ extmark_put(col, id, extmarkline, 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, extmarkline);
+ 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(Extmark *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 = extmarkline_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(Extmark *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 *extmarkline = extmark->line;
+ kb_del(markitems, &extmarkline->items, *extmark);
+ // Remove the line if there are no more marks in the line
+ if (kb_size(&extmarkline->items) == 0) {
+ kb_del(extmarklines, &buf->b_extlines, extmarkline);
+ extmarkline_free(extmarkline);
+ }
+ return true;
+}
+
+// Lookup an extmark by id
+Extmark *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 *extmarkline = pmap_get(uint64_t)(ns_obj->map, id);
+ if (!extmarkline) {
+ return NULL;
+ }
+
+ FOR_ALL_EXTMARKS_IN_LINE(extmarkline->items, 0, MAXCOL, {
+ if (extmark->ns_id == ns
+ && extmark->mark_id == id) {
+ return extmark;
+ }
+ })
+ return NULL;
+}
+
+// Lookup an extmark by position
+Extmark *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(extmarklines, &buf->b_extlines, &itr);
+ extmarkline_free(extmarkline);
+ })
+
+ 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);
+ buf->b_extmark_ns = NULL;
+
+ // k?_init called to set pointers to NULL
+ kb_destroy(extmarklines, (&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 = u_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 = u_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 = u_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 = u_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 = u_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 = u_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 = u_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 = u_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 = u_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 = u_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);
+ }
+}
+
+
+/// 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,
+ bool for_delete,
+ long update_col)
+{
+ bool marks_exist = false;
+
+ ExtmarkLine *extmarkline = extmarkline_ref(buf, lnum, false);
+ if (!extmarkline) {
+ return false;
+ }
+
+ FOR_ALL_EXTMARKS_IN_LINE(extmarkline->items, mincol, MAXCOL, {
+ marks_exist = true;
+
+ // Calculate desired col amount where the adjustment should take place
+ // (not taking) eol into account
+ long col_amount;
+ if (for_delete) {
+ if (extmark->col < update_col) {
+ // When mark inside range
+ colnr_T start_effected_range = mincol - 1;
+ col_amount = -(extmark->col - start_effected_range);
+ } else {
+ // Mark outside of range
+ // -1 because a delete of width 0 should still move marks
+ col_amount = -(update_col - mincol) - 1;
+ }
+ } else {
+ // for anything other than deletes
+ col_amount = update_col;
+ }
+
+ // No update required for this guy
+ if (col_amount == 0 && lnum_amount == 0) {
+ continue;
+ }
+
+ // Set mark to start of line
+ if (col_amount < 0
+ && extmark->col <= (colnr_T)-col_amount) {
+ extmark_update(extmark, buf, extmark->ns_id, extmark->mark_id,
+ extmarkline->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,
+ extmarkline->lnum + lnum_amount,
+ extmark->col + (colnr_T)col_amount, kExtmarkNoUndo, &mitr);
+ }
+ })
+
+ if (kb_size(&extmarkline->items) == 0) {
+ kb_del(extmarklines, &buf->b_extlines, extmarkline);
+ extmarkline_free(extmarkline);
+ }
+
+ 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,
+ false, 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;
+
+ 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,
+ true, (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,
+ extmarkline->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 = true 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(extmarklines, &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 extmarkline
+ // that is goinig to be adjusted to the target position.
+ linenr_T join_num = line1 - amount_after;
+ ExtmarkLine *joinline = (join_num > line2
+ ? extmarkline_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 = &(extmarkline->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_extmarklines(&buf->b_extlines, &itr);
+ kv_push(buf->b_extmark_move_space, extmarkline);
+ }
+
+ *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 extmarkline. Too keep it simple, first delete all
+ // items from the extmarkline and put them back in the right order.
+ FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, {
+ kvec_t(Extmark) temp_space = KV_INITIAL_VALUE;
+ bool same_line = extmarkline == destline;
+ FOR_ALL_EXTMARKS_IN_LINE(extmarkline->items,
+ (extmarkline->lnum > l_lnum) ? 0 : l_col,
+ (extmarkline->lnum < u_lnum) ? MAXCOL : u_col, {
+ if (!destline) {
+ destline = extmarkline_ref(buf, p_lnum, true);
+ same_line = extmarkline == 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, &extmarkline->items, &mitr);
+ })
+ if (same_line) {
+ for (size_t i = 0; i < kv_size(temp_space); i++) {
+ Extmark mark = kv_A(temp_space, i);
+ extmark_put(p_col, mark.mark_id, extmarkline, mark.ns_id);
+ }
+ kv_destroy(temp_space);
+ } else if (delete && kb_size(&extmarkline->items) == 0) {
+ kb_del_itr(extmarklines, &buf->b_extlines, &itr);
+ extmarkline_free(extmarkline);
+ }
+ })
+
+ // 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 *extmarkline_ref(buf_T *buf, linenr_T lnum, bool put)
+{
+ kbtree_t(extmarklines) *b = &buf->b_extlines;
+ ExtmarkLine t, **pp;
+ t.lnum = lnum;
+
+ pp = kb_get(extmarklines, b, &t);
+ if (!pp) {
+ if (!put) {
+ return NULL;
+ }
+ ExtmarkLine *p = xcalloc(sizeof(ExtmarkLine), 1);
+ p->lnum = lnum;
+ // p->items zero initialized
+ kb_put(extmarklines, b, p);
+ return p;
+ }
+ // Return existing
+ return *pp;
+}
+
+void extmarkline_free(ExtmarkLine *extmarkline)
+{
+ kb_destroy(markitems, (&extmarkline->items));
+ xfree(extmarkline);
+}
+
+/// 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 *extmarkline, uint64_t ns)
+{
+ Extmark t;
+ t.col = col;
+ t.mark_id = id;
+ t.line = extmarkline;
+ t.ns_id = ns;
+
+ kbtree_t(markitems) *b = &(extmarkline->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..ee1da26875
--- /dev/null
+++ b/src/nvim/mark_extended.h
@@ -0,0 +1,282 @@
+#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(extmarklines) itr;\
+ ExtmarkLine t;\
+ t.lnum = l_lnum;\
+ if (!kb_itr_get(extmarklines, &buf->b_extlines, &t, &itr)) { \
+ kb_itr_next(extmarklines, &buf->b_extlines, &itr);\
+ }\
+ ExtmarkLine *extmarkline;\
+ for (; kb_itr_valid(&itr); kb_itr_next(extmarklines, \
+ &buf->b_extlines, &itr)) { \
+ extmarkline = kb_itr_key(&itr);\
+ if (extmarkline->lnum > u_lnum) { \
+ break;\
+ }\
+ code;\
+ }
+
+// see FOR_ALL_? for documentation
+#define FOR_ALL_EXTMARKLINES_PREV(buf, l_lnum, u_lnum, code)\
+ kbitr_t(extmarklines) itr;\
+ ExtmarkLine t;\
+ t.lnum = u_lnum;\
+ if (!kb_itr_get(extmarklines, &buf->b_extlines, &t, &itr)) { \
+ kb_itr_prev(extmarklines, &buf->b_extlines, &itr);\
+ }\
+ ExtmarkLine *extmarkline;\
+ for (; kb_itr_valid(&itr); kb_itr_prev(extmarklines, \
+ &buf->b_extlines, &itr)) { \
+ extmarkline = kb_itr_key(&itr);\
+ if (extmarkline->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;\
+ Extmark mt;\
+ mt.ns_id = ns;\
+ mt.mark_id = 0;\
+ mt.line = NULL;\
+ FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, { \
+ mt.col = (extmarkline->lnum != l_lnum) ? MINCOL : l_col;\
+ if (!kb_itr_get(markitems, &extmarkline->items, mt, &mitr)) { \
+ kb_itr_next(markitems, &extmarkline->items, &mitr);\
+ } \
+ Extmark *extmark;\
+ for (; \
+ kb_itr_valid(&mitr); \
+ kb_itr_next(markitems, &extmarkline->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;\
+ Extmark mt;\
+ mt.mark_id = sizeof(uint64_t);\
+ mt.ns_id = ns;\
+ FOR_ALL_EXTMARKLINES_PREV(buf, l_lnum, u_lnum, { \
+ mt.col = (extmarkline->lnum != u_lnum) ? MAXCOL : u_col;\
+ if (!kb_itr_get(markitems, &extmarkline->items, mt, &mitr)) { \
+ kb_itr_prev(markitems, &extmarkline->items, &mitr);\
+ } \
+ Extmark *extmark;\
+ for (; \
+ kb_itr_valid(&mitr); \
+ kb_itr_prev(markitems, &extmarkline->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;\
+ Extmark mt;\
+ mt.ns_id = 0;\
+ mt.mark_id = 0;\
+ mt.line = NULL;\
+ mt.col = l_col;\
+ colnr_T extmarkline_u_col = u_col;\
+ if (!kb_itr_get(markitems, &items, mt, &mitr)) { \
+ kb_itr_next(markitems, &items, &mitr);\
+ } \
+ Extmark *extmark;\
+ for (; kb_itr_valid(&mitr); kb_itr_next(markitems, &items, &mitr)) { \
+ extmark = &kb_itr_key(&mitr);\
+ if (extmark->col > extmarkline_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(Extmark *) ExtmarkArray;
+
+
+// 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;
+
+
+// adjust line numbers only, corresponding to mark_adjust call
+typedef struct {
+ linenr_T line1;
+ linenr_T line2;
+ long amount;
+ long amount_after;
+} Adjust;
+
+// adjust columns after split/join line, like mark_col_adjust
+typedef struct {
+ linenr_T lnum;
+ colnr_T mincol;
+ long col_amount;
+ long lnum_amount;
+} ColAdjust;
+
+// delete the columns between mincol and endcol
+typedef struct {
+ linenr_T lnum;
+ colnr_T mincol;
+ colnr_T endcol;
+ int eol;
+} ColAdjustDelete;
+
+// adjust linenumbers after :move operation
+typedef struct {
+ linenr_T line1;
+ linenr_T line2;
+ linenr_T last_line;
+ linenr_T dest;
+ linenr_T num_lines;
+ linenr_T extra;
+} AdjustMove;
+
+// TODO(bfredl): reconsider if we really should track mark creation/updating
+// itself, these are not really "edit" operation.
+// extmark was created
+typedef struct {
+ uint64_t ns_id;
+ uint64_t mark_id;
+ linenr_T lnum;
+ colnr_T col;
+} ExtmarkSet;
+
+// extmark was updated
+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;
+
+// copied mark before deletion (as operation is destructive)
+typedef struct {
+ uint64_t ns_id;
+ uint64_t mark_id;
+ linenr_T lnum;
+ colnr_T col;
+} ExtmarkCopy;
+
+// also used as part of :move operation? probably can be simplified to one
+// event.
+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;
+
+// extmark was cleared.
+// TODO(bfredl): same reconsideration as for ExtmarkSet/ExtmarkUpdate
+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;
+
+// TODO(bfredl): reduce the number of undo action types
+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..565c599d06
--- /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 Extmark
+{
+ uint64_t ns_id;
+ uint64_t mark_id;
+ struct ExtmarkLine *line;
+ colnr_T col;
+} Extmark;
+
+
+// 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(Extmark a, Extmark 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, Extmark, markitems_cmp, 10)
+
+typedef struct ExtmarkLine
+{
+ linenr_T lnum;
+ kbtree_t(markitems) items;
+} ExtmarkLine;
+
+#define EXTMARKLINE_CMP(a, b) (kb_generic_cmp((a)->lnum, (b)->lnum))
+KBTREE_INIT(extmarklines, ExtmarkLine *, EXTMARKLINE_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..2301b2159f 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"
@@ -306,6 +307,15 @@ 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 mincol = (curwin->w_cursor.col + 1) -p_sw;
+ colnr_T col_amount = left ? -p_sw : p_sw;
+ extmark_col_adjust(curbuf,
+ curwin->w_cursor.lnum,
+ mincol,
+ 0,
+ col_amount,
+ kExtmarkUndo);
}
}
@@ -479,6 +489,10 @@ 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 = left ? -p_sw : p_sw;
+ extmark_col_adjust(curbuf, curwin->w_cursor.lnum,
+ curwin->w_cursor.col, 0, col_amount, kExtmarkUndo);
}
/*
@@ -623,10 +637,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;
@@ -1621,6 +1644,8 @@ int op_delete(oparg_T *oap)
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 +1657,36 @@ 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) {
+ endcol = MAX(endcol - 1, mincol);
+ }
+ extmark_col_adjust_delete(curbuf, lnum, mincol, endcol, kExtmarkUndo, 0);
+ }
return OK;
}
@@ -2031,8 +2082,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 +2156,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 +2276,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 +2750,27 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg)
}
+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 = (colnr_T)(dir == FORWARD ? totlen-1 : 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 +2785,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 +3363,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 +3429,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);
}
/*
@@ -3694,7 +3773,10 @@ int do_join(size_t count,
if (insert_space && t > 0) {
curr = skipwhite(curr);
- if (*curr != ')' && currsize != 0 && endcurr1 != TAB
+ if (*curr != NUL
+ && *curr != ')'
+ && currsize != 0
+ && endcurr1 != TAB
&& (!has_format_option(FO_MBYTE_JOIN)
|| (utf_ptr2char(curr) < 0x100 && endcurr1 < 0x100))
&& (!has_format_option(FO_MBYTE_JOIN2)
@@ -3745,6 +3827,7 @@ 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.
*/
+
for (t = (linenr_T)count - 1;; t--) {
cend -= currsize;
memmove(cend, curr, (size_t)currsize);
@@ -3756,12 +3839,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]);
+ linenr_T lnum = curwin->w_cursor.lnum + t;
+ colnr_T mincol = (colnr_T)0;
+ long lnum_amount = (linenr_T)-t;
+ long 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 +3858,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 +4279,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--;
@@ -4539,7 +4629,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd)
int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
{
int col;
- char_u *buf1;
+ char_u *buf1 = NULL;
char_u buf2[NUMBUFLEN];
int pre; // 'X' or 'x': hex; '0': octal; 'B' or 'b': bin
static bool hexupper = false; // 0xABC
@@ -4848,7 +4938,6 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
*ptr = NUL;
STRCAT(buf1, buf2);
ins_str(buf1); // insert the new number
- xfree(buf1);
endpos = curwin->w_cursor;
if (curwin->w_cursor.col) {
curwin->w_cursor.col--;
@@ -4862,7 +4951,25 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
curbuf->b_op_end.col--;
}
+ // if buf1 wasn't allocated, only a singe ASCII char was changed in-place.
+ if (did_change && buf1 != NULL) {
+ extmark_col_adjust_delete(curbuf,
+ pos->lnum,
+ startpos.col + 2,
+ endpos.col + 1 + length,
+ kExtmarkUndo,
+ 0);
+ long col_amount = (long)STRLEN(buf1);
+ extmark_col_adjust(curbuf,
+ pos->lnum,
+ startpos.col + 1,
+ 0,
+ col_amount,
+ kExtmarkUndo);
+ }
+
theend:
+ xfree(buf1);
if (visual) {
curwin->w_cursor = save_cursor;
} else if (did_change) {
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index e96b3f8e02..d20174466d 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -612,7 +612,7 @@ return {
alloced=true,
redraw={'current_window'},
varname='p_dip',
- defaults={if_true={vi="internal,filler"}}
+ defaults={if_true={vi="internal,filler,closeoff"}}
},
{
full_name='digraph', abbreviation='dg',
diff --git a/src/nvim/os/tty.c b/src/nvim/os/tty.c
index bd5b9b4506..4f525bed9a 100644
--- a/src/nvim/os/tty.c
+++ b/src/nvim/os/tty.c
@@ -6,6 +6,7 @@
//
#include "nvim/os/os.h"
+#include "nvim/os/tty.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/tty.c.generated.h"
diff --git a/src/nvim/po/check.vim b/src/nvim/po/check.vim
index eae27ef74d..650c6155e2 100644
--- a/src/nvim/po/check.vim
+++ b/src/nvim/po/check.vim
@@ -47,6 +47,17 @@ let wsv = winsaveview()
let error = 0
while 1
+ let lnum = line('.')
+ if getline(lnum) =~ 'msgid "Text;.*;"'
+ if getline(lnum + 1) !~ '^msgstr "\([^;]\+;\)\+"'
+ echomsg 'Mismatching ; in line ' . (lnum + 1)
+ echomsg 'Did you forget the trailing semicolon?'
+ if error == 0
+ let error = lnum + 1
+ endif
+ endif
+ endif
+
if getline(line('.') - 1) !~ "no-c-format"
" go over the "msgid" and "msgid_plural" lines
let prevfromline = 'foobar'
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/quickfix.c b/src/nvim/quickfix.c
index da315252b5..ed57b28029 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -4559,9 +4559,9 @@ static qfline_T *qf_find_closest_entry(qf_list_T *qfl,
/// Get the nth quickfix entry below the specified entry treating multiple
/// entries on a single line as one. Searches forward in the list.
-static qfline_T *qf_get_nth_below_entry(qfline_T *entry,
- int *errornr,
- linenr_T n)
+static void qf_get_nth_below_entry(qfline_T *entry,
+ int *errornr,
+ linenr_T n)
{
while (n-- > 0 && !got_int) {
qfline_T *first_entry = entry;
@@ -4582,15 +4582,13 @@ static qfline_T *qf_get_nth_below_entry(qfline_T *entry,
entry = entry->qf_next;
(*errornr)++;
}
-
- return entry;
}
/// Get the nth quickfix entry above the specified entry treating multiple
/// entries on a single line as one. Searches backwards in the list.
-static qfline_T *qf_get_nth_above_entry(qfline_T *entry,
- int *errornr,
- linenr_T n)
+static void qf_get_nth_above_entry(qfline_T *entry,
+ int *errornr,
+ linenr_T n)
{
while (n-- > 0 && !got_int) {
if (entry->qf_prev == NULL
@@ -4604,8 +4602,6 @@ static qfline_T *qf_get_nth_above_entry(qfline_T *entry,
// If multiple entries are on the same line, then use the first entry
entry = qf_find_first_entry_on_line(entry, errornr);
}
-
- return entry;
}
/// Find the n'th quickfix entry adjacent to line 'lnum' in buffer 'bnr' in the
@@ -4629,9 +4625,9 @@ static int qf_find_nth_adj_entry(qf_list_T *qfl,
if (--n > 0) {
// Go to the n'th entry in the current buffer
if (dir == FORWARD) {
- adj_entry = qf_get_nth_below_entry(adj_entry, &errornr, n);
+ qf_get_nth_below_entry(adj_entry, &errornr, n);
} else {
- adj_entry = qf_get_nth_above_entry(adj_entry, &errornr, n);
+ qf_get_nth_above_entry(adj_entry, &errornr, n);
}
}
@@ -5779,11 +5775,13 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
}
/// Add a new quickfix entry to list at 'qf_idx' in the stack 'qi' from the
-/// items in the dict 'd'.
+/// items in the dict 'd'. If it is a valid error entry, then set 'valid_entry'
+/// to true.
static int qf_add_entry_from_dict(
qf_list_T *qfl,
const dict_T *d,
- bool first_entry)
+ bool first_entry,
+ bool *valid_entry)
FUNC_ATTR_NONNULL_ALL
{
static bool did_bufnr_emsg;
@@ -5846,6 +5844,10 @@ static int qf_add_entry_from_dict(
xfree(pattern);
xfree(text);
+ if (valid) {
+ *valid_entry = true;
+ }
+
return status;
}
@@ -5857,6 +5859,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list,
qf_list_T *qfl = qf_get_list(qi, qf_idx);
qfline_T *old_last = NULL;
int retval = OK;
+ bool valid_entry = false;
if (action == ' ' || qf_idx == qi->qf_listcount) {
// make place for a new list
@@ -5881,23 +5884,30 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list,
continue;
}
- retval = qf_add_entry_from_dict(qfl, d, li == tv_list_first(list));
+ retval = qf_add_entry_from_dict(qfl, d, li == tv_list_first(list),
+ &valid_entry);
if (retval == QF_FAIL) {
break;
}
});
- if (qfl->qf_index == 0) {
- // no valid entry
- qfl->qf_nonevalid = true;
- } else {
+ // Check if any valid error entries are added to the list.
+ if (valid_entry) {
qfl->qf_nonevalid = false;
+ } else if (qfl->qf_index == 0) {
+ qfl->qf_nonevalid = true;
}
+
+ // If not appending to the list, set the current error to the first entry
if (action != 'a') {
qfl->qf_ptr = qfl->qf_start;
- if (!qf_list_empty(qfl)) {
- qfl->qf_index = 1;
- }
+ }
+
+ // Update the current error index if not appending to the list or if the
+ // list was empty before and it is not empty now.
+ if ((action != 'a' || qfl->qf_index == 0)
+ && !qf_list_empty(qfl)) {
+ qfl->qf_index = 1;
}
// Don't update the cursor in quickfix window when appending entries
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 1ce0b5217e..7b9601a5a6 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -286,6 +286,11 @@ int update_screen(int type)
return FAIL;
}
+ // May have postponed updating diffs.
+ if (need_diff_redraw) {
+ diff_redraw(true);
+ }
+
if (must_redraw) {
if (type < must_redraw) /* use maximal type */
type = must_redraw;
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index 687c86b4a8..5feb7efda9 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -1910,11 +1910,11 @@ int init_syl_tab(slang_T *slang)
// Count the number of syllables in "word".
// When "word" contains spaces the syllables after the last space are counted.
// Returns zero if syllables are not defines.
-static int count_syllables(slang_T *slang, char_u *word)
+static int count_syllables(slang_T *slang, const char_u *word)
+ FUNC_ATTR_NONNULL_ALL
{
int cnt = 0;
bool skip = false;
- char_u *p;
int len;
syl_item_T *syl;
int c;
@@ -1922,7 +1922,7 @@ static int count_syllables(slang_T *slang, char_u *word)
if (slang->sl_syllable == NULL)
return 0;
- for (p = word; *p != NUL; p += len) {
+ for (const char_u *p = word; *p != NUL; p += len) {
// When running into a space reset counter.
if (*p == ' ') {
len = 1;
@@ -2625,9 +2625,10 @@ static bool spell_mb_isword_class(int cl, const win_T *wp)
// Returns true if "p" points to a word character.
// Wide version of spell_iswordp().
-static bool spell_iswordp_w(int *p, win_T *wp)
+static bool spell_iswordp_w(const int *p, const win_T *wp)
+ FUNC_ATTR_NONNULL_ALL
{
- int *s;
+ const int *s;
if (*p < 256 ? wp->w_s->b_spell_ismw[*p]
: (wp->w_s->b_spell_ismw_mb != NULL
diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim
index 57b19aa817..21e0271bda 100644
--- a/src/nvim/testdir/test_diffmode.vim
+++ b/src/nvim/testdir/test_diffmode.vim
@@ -773,3 +773,28 @@ func Test_diff_of_diff()
call StopVimInTerminal(buf)
call delete('Xtest_diff_diff')
endfunc
+
+func CloseoffSetup()
+ enew
+ call setline(1, ['one', 'two', 'three'])
+ diffthis
+ new
+ call setline(1, ['one', 'tow', 'three'])
+ diffthis
+ call assert_equal(1, &diff)
+ only!
+endfunc
+
+func Test_diff_closeoff()
+ " "closeoff" included by default: last diff win gets 'diff' reset'
+ call CloseoffSetup()
+ call assert_equal(0, &diff)
+ enew!
+
+ " "closeoff" excluded: last diff win keeps 'diff' set'
+ set diffopt-=closeoff
+ call CloseoffSetup()
+ call assert_equal(1, &diff)
+ diffoff!
+ enew!
+endfunc
diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
index a36c51f56f..7822507f86 100644
--- a/src/nvim/testdir/test_functions.vim
+++ b/src/nvim/testdir/test_functions.vim
@@ -1132,6 +1132,13 @@ func Test_reg_executing_and_recording()
" :normal command saves and restores reg_executing
let s:reg_stat = ''
+ let @q = ":call TestFunc()\<CR>:call s:save_reg_stat()\<CR>"
+ func TestFunc() abort
+ normal! ia
+ endfunc
+ call feedkeys("@q", 'xt')
+ call assert_equal(':q', s:reg_stat)
+ delfunc TestFunc
" getchar() command saves and restores reg_executing
map W :call TestFunc()<CR>
diff --git a/src/nvim/testdir/test_join.vim b/src/nvim/testdir/test_join.vim
index 1c97414164..ecb55c9af6 100644
--- a/src/nvim/testdir/test_join.vim
+++ b/src/nvim/testdir/test_join.vim
@@ -9,6 +9,27 @@ func Test_join_with_count()
call setline(1, ['one', 'two', 'three', 'four'])
normal 10J
call assert_equal('one two three four', getline(1))
+
+ call setline(1, ['one', '', 'two'])
+ normal J
+ call assert_equal('one', getline(1))
+
+ call setline(1, ['one', ' ', 'two'])
+ normal J
+ call assert_equal('one', getline(1))
+
+ call setline(1, ['one', '', '', 'two'])
+ normal JJ
+ call assert_equal('one', getline(1))
+
+ call setline(1, ['one', ' ', ' ', 'two'])
+ normal JJ
+ call assert_equal('one', getline(1))
+
+ call setline(1, ['one', '', '', 'two'])
+ normal 2J
+ call assert_equal('one', getline(1))
+
quit!
endfunc
diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index 8949b3d968..15cbf52cb5 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -1320,6 +1320,28 @@ func SetXlistTests(cchar, bnum)
let l = g:Xgetlist()
call g:Xsetlist(l)
call assert_equal(0, g:Xgetlist()[0].valid)
+ " Adding a non-valid entry should not mark the list as having valid entries
+ call g:Xsetlist([{'bufnr':a:bnum, 'lnum':5, 'valid':0}], 'a')
+ Xwindow
+ call assert_equal(1, winnr('$'))
+
+ " :cnext/:cprev should still work even with invalid entries in the list
+ let l = [{'bufnr' : a:bnum, 'lnum' : 1, 'text' : '1', 'valid' : 0},
+ \ {'bufnr' : a:bnum, 'lnum' : 2, 'text' : '2', 'valid' : 0}]
+ call g:Xsetlist(l)
+ Xnext
+ call assert_equal(2, g:Xgetlist({'idx' : 0}).idx)
+ Xprev
+ call assert_equal(1, g:Xgetlist({'idx' : 0}).idx)
+ " :cnext/:cprev should still work after appending invalid entries to an
+ " empty list
+ call g:Xsetlist([])
+ call g:Xsetlist(l, 'a')
+ Xnext
+ call assert_equal(2, g:Xgetlist({'idx' : 0}).idx)
+ Xprev
+ call assert_equal(1, g:Xgetlist({'idx' : 0}).idx)
+
call g:Xsetlist([{'text':'Text1', 'valid':1}])
Xwindow
call assert_equal(2, winnr('$'))
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index 844bc0db40..c71378463f 100644
--- a/src/nvim/tui/input.c
+++ b/src/nvim/tui/input.c
@@ -26,7 +26,7 @@ void tinput_init(TermInput *input, Loop *loop)
{
input->loop = loop;
input->paste = 0;
- input->in_fd = 0;
+ input->in_fd = STDIN_FILENO;
input->waiting_for_bg_response = 0;
input->key_buffer = rbuffer_new(KEY_BUFFER_SIZE);
uv_mutex_init(&input->key_buffer_mutex);
@@ -36,7 +36,7 @@ void tinput_init(TermInput *input, Loop *loop)
// echo q | nvim -es
// ls *.md | xargs nvim
#ifdef WIN32
- if (!os_isatty(0)) {
+ if (!os_isatty(input->in_fd)) {
const HANDLE conin_handle = CreateFile("CONIN$",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
@@ -46,8 +46,8 @@ void tinput_init(TermInput *input, Loop *loop)
assert(input->in_fd != -1);
}
#else
- if (!os_isatty(0) && os_isatty(2)) {
- input->in_fd = 2;
+ if (!os_isatty(input->in_fd) && os_isatty(STDERR_FILENO)) {
+ input->in_fd = STDERR_FILENO;
}
#endif
input_global_fd_init(input->in_fd);
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index 11746441aa..60e1353000 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -220,7 +220,7 @@ static void terminfo_start(UI *ui)
data->unibi_ext.reset_cursor_style = -1;
data->unibi_ext.get_bg = -1;
data->unibi_ext.set_underline_color = -1;
- data->out_fd = 1;
+ data->out_fd = STDOUT_FILENO;
data->out_isatty = os_isatty(data->out_fd);
const char *term = os_getenv("TERM");
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 035613c7fd..539d42765d 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"
@@ -222,9 +225,6 @@ int u_save_cursor(void)
*/
int u_save(linenr_T top, linenr_T bot)
{
- if (undo_off)
- return OK;
-
if (top >= bot || bot > (curbuf->b_ml.ml_line_count + 1)) {
return FAIL; /* rely on caller to do error messages */
}
@@ -243,10 +243,7 @@ int u_save(linenr_T top, linenr_T bot)
*/
int u_savesub(linenr_T lnum)
{
- if (undo_off)
- return OK;
-
- return u_savecommon(lnum - 1, lnum + 1, lnum + 1, FALSE);
+ return u_savecommon(lnum - 1, lnum + 1, lnum + 1, false);
}
/*
@@ -257,10 +254,7 @@ int u_savesub(linenr_T lnum)
*/
int u_inssub(linenr_T lnum)
{
- if (undo_off)
- return OK;
-
- return u_savecommon(lnum - 1, lnum, lnum + 1, FALSE);
+ return u_savecommon(lnum - 1, lnum, lnum + 1, false);
}
/*
@@ -272,9 +266,6 @@ int u_inssub(linenr_T lnum)
*/
int u_savedel(linenr_T lnum, long nlines)
{
- if (undo_off)
- return OK;
-
return u_savecommon(lnum - 1, lnum + nlines,
nlines == curbuf->b_ml.ml_line_count ? 2 : lnum, FALSE);
}
@@ -384,6 +375,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 +2241,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 +2277,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 +2837,8 @@ u_freeentries(
u_freeentry(uep, uep->ue_size);
}
+ kv_destroy(uhp->uh_extmark);
+
#ifdef U_DEBUG
uhp->uh_magic = 0;
#endif
@@ -2902,9 +2913,6 @@ void u_undoline(void)
colnr_T t;
char_u *oldp;
- if (undo_off)
- return;
-
if (curbuf->b_u_line_ptr == NULL
|| curbuf->b_u_line_lnum > curbuf->b_ml.ml_line_count) {
beep_flush();
@@ -3022,3 +3030,34 @@ 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 *u_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) {
+ // Undo is normally invoked in change code, which already has swapped
+ // curbuf.
+ buf_T *save_curbuf = curbuf;
+ curbuf = buf;
+ // Args are tricky: this means replace empty range by empty range..
+ u_savecommon(0, 1, 1, true);
+ curbuf = save_curbuf;
+
+ uhp = buf->b_u_curhead;
+ if (!uhp) {
+ uhp = buf->b_u_newhead;
+ if (get_undolevel() > 0 && !uhp) {
+ abort();
+ }
+ }
+ }
+ return uhp;
+}
diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h
index 6c7e2bba41..0fa3b415ec 100644
--- a/src/nvim/undo_defs.h
+++ b/src/nvim/undo_defs.h
@@ -4,6 +4,7 @@
#include <time.h> // for time_t
#include "nvim/pos.h"
+#include "nvim/mark_extended_defs.h"
#include "nvim/mark_defs.h"
typedef struct u_header u_header_T;
@@ -56,14 +57,15 @@ struct u_header {
u_entry_T *uh_getbot_entry; /* pointer to where ue_bot must be set */
pos_T uh_cursor; /* cursor position before saving */
long uh_cursor_vcol;
- int uh_flags; /* see below */
- fmark_T uh_namedm[NMARKS]; /* marks before undo/after redo */
- visualinfo_T uh_visual; /* Visual areas before undo/after redo */
- time_t uh_time; /* timestamp when the change was made */
- long uh_save_nr; /* set when the file was saved after the
- changes in this block */
+ int uh_flags; // see below
+ fmark_T uh_namedm[NMARKS]; // marks before undo/after redo
+ extmark_undo_vec_t uh_extmark; // info to move extmarks
+ visualinfo_T uh_visual; // Visual areas before undo/after redo
+ time_t uh_time; // timestamp when the change was made
+ long uh_save_nr; // set when the file was saved after the
+ // changes in this block
#ifdef U_DEBUG
- int uh_magic; /* magic number to check allocation */
+ int uh_magic; // magic number to check allocation
#endif
};
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 0531ad1938..2a7578e33c 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -2418,6 +2418,7 @@ int win_close(win_T *win, bool free_buf)
bool help_window = false;
tabpage_T *prev_curtab = curtab;
frame_T *win_frame = win->w_floating ? NULL : win->w_frame->fr_parent;
+ const bool had_diffmode = win->w_p_diff;
if (last_window() && !win->w_floating) {
EMSG(_("E444: Cannot close last window"));
@@ -2642,6 +2643,22 @@ int win_close(win_T *win, bool free_buf)
if (help_window)
restore_snapshot(SNAP_HELP_IDX, close_curwin);
+ // If the window had 'diff' set and now there is only one window left in
+ // the tab page with 'diff' set, and "closeoff" is in 'diffopt', then
+ // execute ":diffoff!".
+ if (diffopt_closeoff() && had_diffmode && curtab == prev_curtab) {
+ int diffcount = 0;
+
+ FOR_ALL_WINDOWS_IN_TAB(dwin, curtab) {
+ if (dwin->w_p_diff) {
+ diffcount++;
+ }
+ }
+ if (diffcount == 1) {
+ do_cmdline_cmd("diffoff!");
+ }
+ }
+
curwin->w_pos_changed = true;
redraw_all_later(NOT_VALID);
return OK;