aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/nvim/api/buffer.c253
-rw-r--r--src/nvim/api/private/helpers.c83
-rw-r--r--src/nvim/buffer.c431
-rw-r--r--src/nvim/buffer_defs.h23
-rw-r--r--src/nvim/buffer_updates.c48
-rw-r--r--src/nvim/bufhl_defs.h41
-rw-r--r--src/nvim/change.c39
-rw-r--r--src/nvim/diff.c2
-rw-r--r--src/nvim/edit.c18
-rw-r--r--src/nvim/ex_cmds.c315
-rw-r--r--src/nvim/fold.c12
-rw-r--r--src/nvim/indent.c12
-rw-r--r--src/nvim/indent.h9
-rw-r--r--src/nvim/map.c11
-rw-r--r--src/nvim/map.h14
-rw-r--r--src/nvim/mark.c22
-rw-r--r--src/nvim/mark.h1
-rw-r--r--src/nvim/mark_extended.c1593
-rw-r--r--src/nvim/mark_extended.h293
-rw-r--r--src/nvim/mark_extended_defs.h59
-rw-r--r--src/nvim/marktree.c10
-rw-r--r--src/nvim/ops.c161
-rw-r--r--src/nvim/screen.c59
-rw-r--r--src/nvim/undo.c2
-rw-r--r--test/functional/api/mark_extended_spec.lua309
-rw-r--r--test/functional/ui/bufhl_spec.lua198
26 files changed, 1646 insertions, 2372 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index e6f8f73b9d..3106011fe2 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -170,6 +170,14 @@ Boolean nvim_buf_attach(uint64_t channel_id,
}
cb.on_lines = v->data.luaref;
v->data.luaref = LUA_NOREF;
+ } else if (is_lua && strequal("_on_bytes", k.data)) {
+ // NB: undocumented, untested and incomplete interface!
+ if (v->type != kObjectTypeLuaRef) {
+ api_set_error(err, kErrorTypeValidation, "callback is not a function");
+ goto error;
+ }
+ cb.on_bytes = v->data.luaref;
+ v->data.luaref = LUA_NOREF;
} else if (is_lua && strequal("on_changedtick", k.data)) {
if (v->type != kObjectTypeLuaRef) {
api_set_error(err, kErrorTypeValidation, "callback is not a function");
@@ -201,6 +209,7 @@ Boolean nvim_buf_attach(uint64_t channel_id,
error:
// TODO(bfredl): ASAN build should check that the ref table is empty?
executor_free_luaref(cb.on_lines);
+ executor_free_luaref(cb.on_bytes);
executor_free_luaref(cb.on_changedtick);
executor_free_luaref(cb.on_detach);
return false;
@@ -639,7 +648,6 @@ void nvim_buf_set_lines(uint64_t channel_id,
(linenr_T)(end - 1),
MAXLNUM,
(long)extra,
- false,
kExtmarkUndo);
changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra, true);
@@ -1119,12 +1127,12 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
return rv;
}
- Extmark *extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id);
- if (!extmark) {
+ ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id);
+ if (extmark.row < 0) {
return rv;
}
- ADD(rv, INTEGER_OBJ((Integer)extmark->line->lnum-1));
- ADD(rv, INTEGER_OBJ((Integer)extmark->col-1));
+ ADD(rv, INTEGER_OBJ((Integer)extmark.row));
+ ADD(rv, INTEGER_OBJ((Integer)extmark.col));
return rv;
}
@@ -1204,43 +1212,39 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start,
if (limit == 0) {
return rv;
+ } else if (limit < 0) {
+ limit = INT64_MAX;
}
bool reverse = false;
- linenr_T l_lnum;
+ int l_row;
colnr_T l_col;
- if (!extmark_get_index_from_obj(buf, ns_id, start, &l_lnum, &l_col, err)) {
+ if (!extmark_get_index_from_obj(buf, ns_id, start, &l_row, &l_col, err)) {
return rv;
}
- linenr_T u_lnum;
+ int u_row;
colnr_T u_col;
- if (!extmark_get_index_from_obj(buf, ns_id, end, &u_lnum, &u_col, err)) {
+ if (!extmark_get_index_from_obj(buf, ns_id, end, &u_row, &u_col, err)) {
return rv;
}
- if (l_lnum > u_lnum || (l_lnum == u_lnum && l_col > u_col)) {
+ if (l_row > u_row || (l_row == u_row && 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,
+ ExtmarkArray marks = extmark_get(buf, (uint64_t)ns_id, l_row, l_col, u_row,
u_col, (int64_t)limit, 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));
+ ExtmarkInfo extmark = kv_A(marks, i);
+ ADD(mark, INTEGER_OBJ((Integer)extmark.mark_id));
+ ADD(mark, INTEGER_OBJ(extmark.row));
+ ADD(mark, INTEGER_OBJ(extmark.col));
ADD(rv, ARRAY_OBJ(mark));
}
@@ -1301,17 +1305,15 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id,
}
uint64_t id_num;
- if (id == 0) {
- id_num = extmark_free_id_get(buf, (uint64_t)ns_id);
- } else if (id > 0) {
+ 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);
+ id_num = extmark_set(buf, (uint64_t)ns_id, id_num,
+ (int)line, (colnr_T)col, kExtmarkUndo);
return (Integer)id_num;
}
@@ -1339,7 +1341,7 @@ Boolean nvim_buf_del_extmark(Buffer buffer,
return false;
}
- return extmark_del(buf, (uint64_t)ns_id, (uint64_t)id, kExtmarkUndo);
+ return extmark_del(buf, (uint64_t)ns_id, (uint64_t)id);
}
/// Adds a highlight to buffer.
@@ -1373,7 +1375,7 @@ Boolean nvim_buf_del_extmark(Buffer buffer,
/// @param[out] err Error details, if any
/// @return The ns_id that was used
Integer nvim_buf_add_highlight(Buffer buffer,
- Integer ns_id,
+ Integer src_id,
String hl_group,
Integer line,
Integer col_start,
@@ -1398,14 +1400,31 @@ Integer nvim_buf_add_highlight(Buffer buffer,
col_end = MAXCOL;
}
+ uint64_t ns_id = src2ns(&src_id);
+
+ if (!(0 <= line && line < buf->b_ml.ml_line_count)) {
+ // safety check, we can't add marks outside the range
+ return src_id;
+ }
+
int hlg_id = 0;
if (hl_group.size > 0) {
hlg_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size);
+ } else {
+ return src_id;
+ }
+
+ int end_line = (int)line;
+ if (col_end == MAXCOL) {
+ col_end = 0;
+ end_line++;
}
- ns_id = bufhl_add_hl(buf, (int)ns_id, hlg_id, (linenr_T)line+1,
- (colnr_T)col_start+1, (colnr_T)col_end);
- return ns_id;
+ ns_id = extmark_add_decoration(buf, ns_id, hlg_id,
+ (int)line, (colnr_T)col_start,
+ end_line, (colnr_T)col_end,
+ VIRTTEXT_EMPTY);
+ return src_id;
}
/// Clears namespaced objects (highlights, extmarks, virtual text) from
@@ -1439,12 +1458,9 @@ void nvim_buf_clear_namespace(Buffer buffer,
if (line_end < 0 || line_end > MAXLNUM) {
line_end = MAXLNUM;
}
-
- 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);
+ extmark_clear(buf, (ns_id < 0 ? 0 : (uint64_t)ns_id),
+ (int)line_start, 0,
+ (int)line_end-1, MAXCOL);
}
/// Clears highlights and virtual text from namespace and range of lines
@@ -1467,6 +1483,43 @@ void nvim_buf_clear_highlight(Buffer buffer,
nvim_buf_clear_namespace(buffer, ns_id, line_start, line_end, err);
}
+static VirtText parse_virt_text(Array chunks, Error *err)
+{
+ VirtText virt_text = KV_INITIAL_VALUE;
+ for (size_t i = 0; i < chunks.size; i++) {
+ if (chunks.items[i].type != kObjectTypeArray) {
+ api_set_error(err, kErrorTypeValidation, "Chunk is not an array");
+ goto free_exit;
+ }
+ Array chunk = chunks.items[i].data.array;
+ if (chunk.size == 0 || chunk.size > 2
+ || chunk.items[0].type != kObjectTypeString
+ || (chunk.size == 2 && chunk.items[1].type != kObjectTypeString)) {
+ api_set_error(err, kErrorTypeValidation,
+ "Chunk is not an array with one or two strings");
+ goto free_exit;
+ }
+
+ String str = chunk.items[0].data.string;
+ char *text = transstr(str.size > 0 ? str.data : ""); // allocates
+
+ int hl_id = 0;
+ if (chunk.size == 2) {
+ String hl = chunk.items[1].data.string;
+ if (hl.size > 0) {
+ hl_id = syn_check_group((char_u *)hl.data, (int)hl.size);
+ }
+ }
+ kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id }));
+ }
+
+ return virt_text;
+
+free_exit:
+ clear_virttext(&virt_text);
+ return virt_text;
+}
+
/// Set the virtual text (annotation) for a buffer line.
///
@@ -1496,7 +1549,7 @@ void nvim_buf_clear_highlight(Buffer buffer,
/// @param[out] err Error details, if any
/// @return The ns_id that was used
Integer nvim_buf_set_virtual_text(Buffer buffer,
- Integer ns_id,
+ Integer src_id,
Integer line,
Array chunks,
Dictionary opts,
@@ -1518,41 +1571,26 @@ Integer nvim_buf_set_virtual_text(Buffer buffer,
return 0;
}
- VirtText virt_text = KV_INITIAL_VALUE;
- for (size_t i = 0; i < chunks.size; i++) {
- if (chunks.items[i].type != kObjectTypeArray) {
- api_set_error(err, kErrorTypeValidation, "Chunk is not an array");
- goto free_exit;
- }
- Array chunk = chunks.items[i].data.array;
- if (chunk.size == 0 || chunk.size > 2
- || chunk.items[0].type != kObjectTypeString
- || (chunk.size == 2 && chunk.items[1].type != kObjectTypeString)) {
- api_set_error(err, kErrorTypeValidation,
- "Chunk is not an array with one or two strings");
- goto free_exit;
- }
-
- String str = chunk.items[0].data.string;
- char *text = transstr(str.size > 0 ? str.data : ""); // allocates
+ uint64_t ns_id = src2ns(&src_id);
- int hl_id = 0;
- if (chunk.size == 2) {
- String hl = chunk.items[1].data.string;
- if (hl.size > 0) {
- hl_id = syn_check_group((char_u *)hl.data, (int)hl.size);
- }
- }
- kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id }));
+ VirtText virt_text = parse_virt_text(chunks, err);
+ if (ERROR_SET(err)) {
+ return 0;
}
- ns_id = bufhl_add_virt_text(buf, (int)ns_id, (linenr_T)line+1,
- virt_text);
- return ns_id;
-free_exit:
- kv_destroy(virt_text);
- return 0;
+ VirtText *existing = extmark_find_virttext(buf, (int)line, ns_id);
+
+ if (existing) {
+ clear_virttext(existing);
+ *existing = virt_text;
+ return src_id;
+ }
+
+ extmark_add_decoration(buf, ns_id, 0,
+ (int)line, 0, -1, -1,
+ virt_text);
+ return src_id;
}
/// Get the virtual text (annotation) for a buffer line.
@@ -1570,7 +1608,7 @@ free_exit:
/// @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)
+Array nvim_buf_get_virtual_text(Buffer buffer, Integer line, Error *err)
FUNC_API_SINCE(7)
{
Array chunks = ARRAY_DICT_INIT;
@@ -1580,20 +1618,20 @@ Array nvim_buf_get_virtual_text(Buffer buffer, Integer lnum, Error *err)
return chunks;
}
- if (lnum < 0 || lnum >= MAXLNUM) {
+ if (line < 0 || line >= 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) {
+ VirtText *virt_text = extmark_find_virttext(buf, (int)line, 0);
+
+ if (!virt_text) {
return chunks;
}
- for (size_t i = 0; i < lineinfo->virt_text.size; i++) {
+ for (size_t i = 0; i < virt_text->size; i++) {
Array chunk = ARRAY_DICT_INIT;
- VirtTextChunk *vtc = &lineinfo->virt_text.items[i];
+ VirtTextChunk *vtc = &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(
@@ -1605,6 +1643,59 @@ Array nvim_buf_get_virtual_text(Buffer buffer, Integer lnum, Error *err)
return chunks;
}
+Integer nvim__buf_add_decoration(Buffer buffer, Integer ns_id, String hl_group,
+ Integer start_row, Integer start_col,
+ Integer end_row, Integer end_col,
+ Array virt_text,
+ Error *err)
+{
+ 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 (start_row < 0 || start_row >= MAXLNUM || end_row > MAXCOL) {
+ api_set_error(err, kErrorTypeValidation, "Line number outside range");
+ return 0;
+ }
+
+ if (start_col < 0 || start_col > MAXCOL || end_col > MAXCOL) {
+ api_set_error(err, kErrorTypeValidation, "Column value outside range");
+ return 0;
+ }
+ if (end_row < 0 || end_col < 0) {
+ end_row = -1;
+ end_col = -1;
+ }
+
+ if (start_row >= buf->b_ml.ml_line_count
+ || end_row >= buf->b_ml.ml_line_count) {
+ // safety check, we can't add marks outside the range
+ return 0;
+ }
+
+ int hlg_id = 0;
+ if (hl_group.size > 0) {
+ hlg_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size);
+ }
+
+ VirtText vt = parse_virt_text(virt_text, err);
+ if (ERROR_SET(err)) {
+ return 0;
+ }
+
+ uint64_t mark_id = extmark_add_decoration(buf, (uint64_t)ns_id, hlg_id,
+ (int)start_row, (colnr_T)start_col,
+ (int)end_row, (colnr_T)end_col, vt);
+ return (Integer)mark_id;
+}
+
Dictionary nvim__buf_stats(Buffer buffer, Error *err)
{
Dictionary rv = ARRAY_DICT_INIT;
@@ -1626,6 +1717,16 @@ Dictionary nvim__buf_stats(Buffer buffer, Error *err)
// this exists to debug issues
PUT(rv, "dirty_bytes", INTEGER_OBJ((Integer)buf->deleted_bytes));
+ 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;
+ }
+ if (uhp) {
+ PUT(rv, "uhp_extmark_size", INTEGER_OBJ((Integer)kv_size(uhp->uh_extmark)));
+ }
+
return rv;
}
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 36331eee6e..37e31e0807 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1511,61 +1511,6 @@ 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 ns, 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)ns, 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)ns,
- (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)
{
@@ -1584,29 +1529,29 @@ bool ns_initialized(uint64_t ns)
/// @param[out] colnr extmark column
///
/// @return true if the extmark was found, else false
-bool extmark_get_index_from_obj(buf_T *buf, Integer ns, Object obj, linenr_T
- *lnum, colnr_T *colnr, Error *err)
+bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int
+ *row, colnr_T *col, Error *err)
{
// Check if it is mark id
if (obj.type == kObjectTypeInteger) {
Integer id = obj.data.integer;
if (id == 0) {
- *lnum = 1;
- *colnr = 1;
+ *row = 0;
+ *col = 0;
return true;
} else if (id == -1) {
- *lnum = MAXLNUM;
- *colnr = MAXCOL;
+ *row = MAXLNUM;
+ *col = 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)ns, (uint64_t)id);
- if (extmark) {
- *lnum = extmark->line->lnum;
- *colnr = extmark->col;
+ ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id);
+ if (extmark.row >= 0) {
+ *row = extmark.row;
+ *col = extmark.col;
return true;
} else {
api_set_error(err, kErrorTypeValidation, _("No mark with requested id"));
@@ -1623,10 +1568,10 @@ bool extmark_get_index_from_obj(buf_T *buf, Integer ns, Object obj, linenr_T
_("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);
+ Integer pos_row = pos.items[0].data.integer;
+ Integer pos_col = pos.items[1].data.integer;
+ *row = (int)(pos_row >= 0 ? pos_row : MAXLNUM);
+ *col = (colnr_T)(pos_col >= 0 ? pos_col : MAXCOL);
return true;
} else {
api_set_error(err, kErrorTypeValidation,
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 33ffff39f6..837fcb5cc1 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -81,12 +81,6 @@
#include "nvim/os/input.h"
#include "nvim/buffer_updates.h"
-typedef enum {
- kBLSUnchanged = 0,
- kBLSChanged = 1,
- kBLSDeleted = 2,
-} BufhlLineStatus;
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "buffer.c.generated.h"
#endif
@@ -818,7 +812,6 @@ 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
XFREE_CLEAR(buf->b_start_fenc);
@@ -5342,430 +5335,6 @@ int buf_signcols(buf_T *buf)
return buf->b_signcols;
}
-// bufhl: plugin highlights associated with a buffer
-
-/// Get reference to line in kbtree_t
-///
-/// @param b the three
-/// @param line the linenumber to lookup
-/// @param put if true, put a new line when not found
-/// if false, return NULL when not found
-BufhlLine *bufhl_tree_ref(BufhlInfo *b, linenr_T line, bool put)
-{
- BufhlLine t = BUFHLLINE_INIT(line);
-
- // kp_put() only works if key is absent, try get first
- BufhlLine **pp = kb_get(bufhl, b, &t);
- if (pp) {
- return *pp;
- } else if (!put) {
- return NULL;
- }
-
- BufhlLine *p = xmalloc(sizeof(*p));
- *p = (BufhlLine)BUFHLLINE_INIT(line);
- kb_put(bufhl, b, p);
- return p;
-}
-
-/// Adds a highlight to buffer.
-///
-/// Unlike matchaddpos() highlights follow changes to line numbering (as lines
-/// are inserted/removed above the highlighted line), like signs and marks do.
-///
-/// When called with "src_id" set to 0, a unique source id is generated and
-/// returned. Succesive calls can pass it in as "src_id" to add new highlights
-/// to the same source group. All highlights in the same group can be cleared
-/// at once. If the highlight never will be manually deleted pass in -1 for
-/// "src_id"
-///
-/// if "hl_id" or "lnum" is invalid no highlight is added, but a new src_id
-/// is still returned.
-///
-/// @param buf The buffer to add highlights to
-/// @param src_id src_id to use or 0 to use a new src_id group,
-/// or -1 for ungrouped highlight.
-/// @param hl_id Id of the highlight group to use
-/// @param lnum The line to highlight
-/// @param col_start First column to highlight
-/// @param col_end The last column to highlight,
-/// or -1 to highlight to end of line
-/// @return The src_id that was used
-int bufhl_add_hl(buf_T *buf,
- int src_id,
- int hl_id,
- linenr_T lnum,
- colnr_T col_start,
- colnr_T col_end)
-{
- if (src_id == 0) {
- src_id = (int)nvim_create_namespace((String)STRING_INIT);
- }
- if (hl_id <= 0) {
- // no highlight group or invalid line, just return src_id
- return src_id;
- }
-
- BufhlLine *lineinfo = bufhl_tree_ref(&buf->b_bufhl_info, lnum, true);
-
- BufhlItem *hlentry = kv_pushp(lineinfo->items);
- hlentry->src_id = src_id;
- hlentry->hl_id = hl_id;
- hlentry->start = col_start;
- hlentry->stop = col_end;
-
- if (0 < lnum && lnum <= buf->b_ml.ml_line_count) {
- redraw_buf_line_later(buf, lnum);
- }
- return src_id;
-}
-
-/// Add highlighting to a buffer, bounded by two cursor positions,
-/// with an offset.
-///
-/// @param buf Buffer to add highlights to
-/// @param src_id src_id to use or 0 to use a new src_id group,
-/// or -1 for ungrouped highlight.
-/// @param hl_id Highlight group id
-/// @param pos_start Cursor position to start the hightlighting at
-/// @param pos_end Cursor position to end the highlighting at
-/// @param offset Move the whole highlighting this many columns to the right
-void bufhl_add_hl_pos_offset(buf_T *buf,
- int src_id,
- int hl_id,
- lpos_T pos_start,
- lpos_T pos_end,
- colnr_T offset)
-{
- colnr_T hl_start = 0;
- colnr_T hl_end = 0;
-
- for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum ++) {
- if (pos_start.lnum < lnum && lnum < pos_end.lnum) {
- hl_start = offset;
- hl_end = MAXCOL;
- } else if (lnum == pos_start.lnum && lnum < pos_end.lnum) {
- hl_start = pos_start.col + offset + 1;
- hl_end = MAXCOL;
- } else if (pos_start.lnum < lnum && lnum == pos_end.lnum) {
- hl_start = offset;
- hl_end = pos_end.col + offset;
- } else if (pos_start.lnum == lnum && pos_end.lnum == lnum) {
- hl_start = pos_start.col + offset + 1;
- hl_end = pos_end.col + offset;
- }
- (void)bufhl_add_hl(buf, src_id, hl_id, lnum, hl_start, hl_end);
- }
-}
-
-int bufhl_add_virt_text(buf_T *buf,
- int src_id,
- linenr_T lnum,
- VirtText virt_text)
-{
- if (src_id == 0) {
- src_id = (int)nvim_create_namespace((String)STRING_INIT);
- }
-
- BufhlLine *lineinfo = bufhl_tree_ref(&buf->b_bufhl_info, lnum, true);
-
- bufhl_clear_virttext(&lineinfo->virt_text);
- if (kv_size(virt_text) > 0) {
- lineinfo->virt_text_src = src_id;
- lineinfo->virt_text = virt_text;
- } else {
- lineinfo->virt_text_src = 0;
- // currently not needed, but allow a future caller with
- // 0 size and non-zero capacity
- kv_destroy(virt_text);
- }
-
- if (0 < lnum && lnum <= buf->b_ml.ml_line_count) {
- redraw_buf_line_later(buf, lnum);
- }
- return src_id;
-}
-
-static void bufhl_clear_virttext(VirtText *text)
-{
- for (size_t i = 0; i < kv_size(*text); i++) {
- xfree(kv_A(*text, i).text);
- }
- kv_destroy(*text);
- *text = (VirtText)KV_INITIAL_VALUE;
-}
-
-/// Clear bufhl highlights from a given source group and range of lines.
-///
-/// @param buf The buffer to remove highlights from
-/// @param src_id highlight source group to clear, or -1 to clear all groups.
-/// @param line_start first line to clear
-/// @param line_end last line to clear or MAXLNUM to clear to end of file.
-void bufhl_clear_line_range(buf_T *buf,
- int src_id,
- 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)) {
- kb_itr_next(bufhl, &buf->b_bufhl_info, &itr);
- }
- for (; kb_itr_valid(&itr); kb_itr_next(bufhl, &buf->b_bufhl_info, &itr)) {
- l = kb_itr_key(&itr);
- linenr_T line = l->line;
- if (line > line_end) {
- break;
- }
- if (line_start <= line) {
- BufhlLineStatus status = bufhl_clear_line(l, src_id, line);
- if (status != kBLSUnchanged) {
- redraw_buf_line_later(buf, line);
- }
- if (status == kBLSDeleted) {
- kb_del_itr(bufhl, &buf->b_bufhl_info, &itr);
- xfree(l);
- }
- }
- }
-}
-
-/// Clear bufhl highlights from a given source group and given line
-///
-/// @param bufhl_info The highlight info for the buffer
-/// @param src_id Highlight source group to clear, or -1 to clear all groups.
-/// @param lnum Linenr where the highlight should be cleared
-static BufhlLineStatus bufhl_clear_line(BufhlLine *lineinfo, int src_id,
- linenr_T lnum)
-{
- BufhlLineStatus changed = kBLSUnchanged;
- size_t oldsize = kv_size(lineinfo->items);
- if (src_id < 0) {
- kv_size(lineinfo->items) = 0;
- } else {
- size_t newidx = 0;
- for (size_t i = 0; i < kv_size(lineinfo->items); i++) {
- if (kv_A(lineinfo->items, i).src_id != src_id) {
- if (i != newidx) {
- kv_A(lineinfo->items, newidx) = kv_A(lineinfo->items, i);
- }
- newidx++;
- }
- }
- kv_size(lineinfo->items) = newidx;
- }
- if (kv_size(lineinfo->items) != oldsize) {
- changed = kBLSChanged;
- }
-
- if (kv_size(lineinfo->virt_text) != 0
- && (src_id < 0 || src_id == lineinfo->virt_text_src)) {
- bufhl_clear_virttext(&lineinfo->virt_text);
- lineinfo->virt_text_src = 0;
- changed = kBLSChanged;
- }
-
- if (kv_size(lineinfo->items) == 0 && kv_size(lineinfo->virt_text) == 0) {
- kv_destroy(lineinfo->items);
- return kBLSDeleted;
- }
- return changed;
-}
-
-
-/// Remove all highlights and free the highlight data
-void bufhl_clear_all(buf_T *buf)
-{
- bufhl_clear_line_range(buf, -1, 1, MAXLNUM);
- kb_destroy(bufhl, (&buf->b_bufhl_info));
- kb_init(&buf->b_bufhl_info);
- kv_destroy(buf->b_bufhl_move_space);
- kv_init(buf->b_bufhl_move_space);
-}
-
-/// Adjust a placed highlight for inserted/deleted lines.
-void bufhl_mark_adjust(buf_T* buf,
- linenr_T line1,
- linenr_T line2,
- long amount,
- long amount_after,
- bool end_temp)
-{
- kbitr_t(bufhl) itr;
- BufhlLine *l, t = BUFHLLINE_INIT(line1);
- if (end_temp && amount < 0) {
- // Move all items from b_bufhl_move_space to the btree.
- for (size_t i = 0; i < kv_size(buf->b_bufhl_move_space); i++) {
- l = kv_A(buf->b_bufhl_move_space, i);
- l->line += amount;
- kb_put(bufhl, &buf->b_bufhl_info, l);
- }
- kv_size(buf->b_bufhl_move_space) = 0;
- return;
- }
-
- if (!kb_itr_get(bufhl, &buf->b_bufhl_info, &t, &itr)) {
- kb_itr_next(bufhl, &buf->b_bufhl_info, &itr);
- }
- for (; kb_itr_valid(&itr); kb_itr_next(bufhl, &buf->b_bufhl_info, &itr)) {
- l = kb_itr_key(&itr);
- if (l->line >= line1 && l->line <= line2) {
- if (end_temp && amount > 0) {
- kb_del_itr(bufhl, &buf->b_bufhl_info, &itr);
- kv_push(buf->b_bufhl_move_space, l);
- }
- if (amount == MAXLNUM) {
- if (bufhl_clear_line(l, -1, l->line) == kBLSDeleted) {
- kb_del_itr(bufhl, &buf->b_bufhl_info, &itr);
- xfree(l);
- } else {
- assert(false);
- }
- } else {
- l->line += amount;
- }
- } else if (l->line > line2) {
- if (amount_after == 0) {
- break;
- }
- l->line += amount_after;
- }
- }
-}
-
-/// Adjust a placed highlight for column changes and joined/broken lines
-bool bufhl_mark_col_adjust(buf_T *buf,
- linenr_T lnum,
- colnr_T mincol,
- long lnum_amount,
- long col_amount)
-{
- bool moved = false;
- BufhlLine *lineinfo = bufhl_tree_ref(&buf->b_bufhl_info, lnum, false);
- if (!lineinfo) {
- // Old line empty, nothing to do
- return false;
- }
- // Create the new line below only if needed
- BufhlLine *lineinfo2 = NULL;
-
- colnr_T delcol = MAXCOL;
- if (lnum_amount == 0 && col_amount < 0) {
- delcol = mincol+(int)col_amount;
- }
-
- size_t newidx = 0;
- for (size_t i = 0; i < kv_size(lineinfo->items); i++) {
- BufhlItem *item = &kv_A(lineinfo->items, i);
- bool delete = false;
- if (item->start >= mincol) {
- moved = true;
- item->start += (int)col_amount;
- if (item->stop < MAXCOL) {
- item->stop += (int)col_amount;
- }
- if (lnum_amount != 0) {
- if (lineinfo2 == NULL) {
- lineinfo2 = bufhl_tree_ref(&buf->b_bufhl_info,
- lnum+lnum_amount, true);
- }
- kv_push(lineinfo2->items, *item);
- delete = true;
- }
- } else {
- if (item->start >= delcol) {
- moved = true;
- item->start = delcol;
- }
- if (item->stop == MAXCOL || item->stop+1 >= mincol) {
- if (item->stop == MAXCOL) {
- if (delcol < MAXCOL
- && delcol > (colnr_T)STRLEN(ml_get_buf(buf, lnum, false))) {
- delete = true;
- }
- } else {
- moved = true;
- item->stop += (int)col_amount;
- }
- assert(lnum_amount >= 0);
- if (lnum_amount > 0) {
- item->stop = MAXCOL;
- }
- } else if (item->stop+1 >= delcol) {
- moved = true;
- item->stop = delcol-1;
- }
- // we covered the entire range with a visual delete or something
- if (item->stop < item->start) {
- delete = true;
- }
- }
-
- if (!delete) {
- if (i != newidx) {
- kv_A(lineinfo->items, newidx) = kv_A(lineinfo->items, i);
- }
- newidx++;
- }
- }
- kv_size(lineinfo->items) = newidx;
-
- return moved;
-}
-
-
-/// Get highlights to display at a specific line
-///
-/// @param buf The buffer handle
-/// @param lnum The line number
-/// @param[out] info The highligts for the line
-/// @return true if there was highlights to display
-bool bufhl_start_line(buf_T *buf, linenr_T lnum, BufhlLineInfo *info)
-{
- BufhlLine *lineinfo = bufhl_tree_ref(&buf->b_bufhl_info, lnum, false);
- if (!lineinfo) {
- return false;
- }
- info->valid_to = -1;
- info->line = lineinfo;
- return true;
-}
-
-/// get highlighting at column col
-///
-/// It is is assumed this will be called with
-/// non-decreasing column nrs, so that it is
-/// possible to only recalculate highlights
-/// at endpoints.
-///
-/// @param info The info returned by bufhl_start_line
-/// @param col The column to get the attr for
-/// @return The highilight attr to display at the column
-int bufhl_get_attr(BufhlLineInfo *info, colnr_T col)
-{
- if (col <= info->valid_to) {
- return info->current;
- }
- int attr = 0;
- info->valid_to = MAXCOL;
- for (size_t i = 0; i < kv_size(info->line->items); i++) {
- BufhlItem entry = kv_A(info->line->items, i);
- if (entry.start <= col && col <= entry.stop) {
- int entry_attr = syn_id2attr(entry.hl_id);
- attr = hl_combine_attr(attr, entry_attr);
- if (entry.stop < info->valid_to) {
- info->valid_to = entry.stop;
- }
- } else if (col < entry.start && entry.start-1 < info->valid_to) {
- info->valid_to = entry.start-1;
- }
- }
- info->current = attr;
- return attr;
-}
-
-
/*
* Set 'buflisted' for curbuf to "on" and trigger autocommands if it changed.
*/
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 265fc05f67..a0379740b6 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -42,6 +42,8 @@ typedef struct {
#include "nvim/map.h"
// for kvec
#include "nvim/lib/kvec.h"
+// for marktree
+#include "nvim/marktree.h"
#define GETFILE_SUCCESS(x) ((x) <= 0)
#define MODIFIABLE(buf) (buf->b_p_ma)
@@ -109,15 +111,10 @@ typedef uint16_t disptick_T; // display tick type
#include "nvim/syntax_defs.h"
// for signlist_T
#include "nvim/sign_defs.h"
-// for bufhl_*_T
-#include "nvim/bufhl_defs.h"
#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.
*/
@@ -461,11 +458,15 @@ typedef TV_DICTITEM_STRUCT(sizeof("changedtick")) ChangedtickDictItem;
typedef struct {
LuaRef on_lines;
+ LuaRef on_bytes;
LuaRef on_changedtick;
LuaRef on_detach;
bool utf_sizes;
} BufUpdateCallbacks;
-#define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF, LUA_NOREF, false }
+#define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF, LUA_NOREF, \
+ LUA_NOREF, false }
+
+EXTERN int curbuf_splice_pending INIT(= 0);
#define BUF_HAS_QF_ENTRY 1
#define BUF_HAS_LL_ENTRY 2
@@ -804,13 +805,9 @@ struct file_buffer {
int b_mapped_ctrl_c; // modes where CTRL-C is mapped
- BufhlInfo b_bufhl_info; // buffer stored highlights
-
- 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
+ MarkTree b_marktree[1];
+ Map(uint64_t, ExtmarkItem) *b_extmark_index;
+ Map(uint64_t, ExtmarkNs) *b_extmark_ns; // extmark namespaces
// array of channel_id:s which have asked to receive updates for this
// buffer.
diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c
index d12527d6ac..80780a3aa3 100644
--- a/src/nvim/buffer_updates.c
+++ b/src/nvim/buffer_updates.c
@@ -281,6 +281,54 @@ void buf_updates_send_changes(buf_T *buf,
kv_size(buf->update_callbacks) = j;
}
+void buf_updates_send_splice(buf_T *buf,
+ linenr_T start_line, colnr_T start_col,
+ linenr_T oldextent_line, colnr_T oldextent_col,
+ linenr_T newextent_line, colnr_T newextent_col)
+{
+ if (!buf_updates_active(buf)) {
+ return;
+ }
+
+ // notify each of the active callbakcs
+ size_t j = 0;
+ for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) {
+ BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i);
+ bool keep = true;
+ if (cb.on_bytes != LUA_NOREF) {
+ Array args = ARRAY_DICT_INIT;
+ Object items[8];
+ args.size = 8;
+ args.items = items;
+
+ // the first argument is always the buffer handle
+ args.items[0] = BUFFER_OBJ(buf->handle);
+
+ // next argument is b:changedtick
+ args.items[1] = INTEGER_OBJ(buf_get_changedtick(buf));
+
+ args.items[2] = INTEGER_OBJ(start_line);
+ args.items[3] = INTEGER_OBJ(start_col);
+ args.items[4] = INTEGER_OBJ(oldextent_line);
+ args.items[5] = INTEGER_OBJ(oldextent_col);
+ args.items[6] = INTEGER_OBJ(newextent_line);
+ args.items[7] = INTEGER_OBJ(newextent_col);
+
+ textlock++;
+ Object res = executor_exec_lua_cb(cb.on_bytes, "bytes", args, true, NULL);
+ textlock--;
+
+ if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
+ free_update_callbacks(cb);
+ keep = false;
+ }
+ }
+ if (keep) {
+ kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i);
+ }
+ }
+ kv_size(buf->update_callbacks) = j;
+}
void buf_updates_changedtick(buf_T *buf)
{
// notify each of the active channels
diff --git a/src/nvim/bufhl_defs.h b/src/nvim/bufhl_defs.h
deleted file mode 100644
index d0fb40ab88..0000000000
--- a/src/nvim/bufhl_defs.h
+++ /dev/null
@@ -1,41 +0,0 @@
-#ifndef NVIM_BUFHL_DEFS_H
-#define NVIM_BUFHL_DEFS_H
-
-#include "nvim/pos.h"
-#include "nvim/lib/kvec.h"
-#include "nvim/lib/kbtree.h"
-
-// bufhl: buffer specific highlighting
-
-typedef struct {
- int src_id;
- int hl_id; // highlight group
- colnr_T start; // first column to highlight
- colnr_T stop; // last column to highlight
-} BufhlItem;
-
-typedef struct {
- char *text;
- int hl_id;
-} VirtTextChunk;
-
-typedef kvec_t(VirtTextChunk) VirtText;
-
-typedef struct {
- linenr_T line;
- kvec_t(BufhlItem) items;
- int virt_text_src;
- VirtText virt_text;
-} BufhlLine;
-#define BUFHLLINE_INIT(l) { l, KV_INITIAL_VALUE, 0, KV_INITIAL_VALUE }
-
-typedef struct {
- BufhlLine *line;
- int current;
- colnr_T valid_to;
-} BufhlLineInfo;
-
-#define BUFHL_CMP(a, b) ((int)(((a)->line - (b)->line)))
-KBTREE_INIT(bufhl, BufhlLine *, BUFHL_CMP, 10) // -V512
-typedef kbtree_t(bufhl) BufhlInfo;
-#endif // NVIM_BUFHL_DEFS_H
diff --git a/src/nvim/change.c b/src/nvim/change.c
index 05cacaf2c2..7eb6ea7328 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -363,15 +363,10 @@ void changed_bytes(linenr_T lnum, colnr_T col)
///
/// Like changed_bytes() but also adjust extmark for "added" bytes.
/// When "added" is negative text was deleted.
-static void inserted_bytes(linenr_T lnum, colnr_T col, int added)
+static void inserted_bytes(linenr_T lnum, colnr_T col, int old, int new)
{
- if (added > 0) {
- extmark_col_adjust(curbuf, lnum, col+1, 0, added, kExtmarkUndo);
- } else if (added < 0) {
- // TODO(bfredl): next revision of extmarks should handle both these
- // with the same entry point. Also with more sane params..
- extmark_col_adjust_delete(curbuf, lnum, col+2,
- col+(-added)+1, kExtmarkUndo, 0);
+ if (curbuf_splice_pending == 0) {
+ extmark_splice(curbuf, (int)lnum-1, col, 0, old, 0, new, kExtmarkUndo);
}
changed_bytes(lnum, col);
@@ -391,7 +386,10 @@ 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, kExtmarkUndo);
+ mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, kExtmarkUndo);
+ } else {
+ extmark_adjust(curbuf, lnum + 1, (linenr_T)MAXLNUM, count, 0L,
+ kExtmarkUndo);
}
changed_lines(lnum + 1, 0, lnum + 1, count, true);
}
@@ -409,7 +407,7 @@ 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,
kExtmarkUndo);
changed_lines(lnum, 0, lnum + count, -count, true);
}
@@ -648,7 +646,7 @@ void ins_char_bytes(char_u *buf, size_t charlen)
ml_replace(lnum, newp, false);
// mark the buffer as changed and prepare for displaying
- inserted_bytes(lnum, (colnr_T)col, (int)(newlen - oldlen));
+ inserted_bytes(lnum, (colnr_T)col, (int)oldlen, (int)newlen);
// If we're in Insert or Replace mode and 'showmatch' is set, then briefly
// show the match for right parens and braces.
@@ -694,7 +692,7 @@ void ins_str(char_u *s)
assert(bytes >= 0);
memmove(newp + col + newlen, oldp + col, (size_t)bytes);
ml_replace(lnum, newp, false);
- inserted_bytes(lnum, col, newlen);
+ inserted_bytes(lnum, col, 0, newlen);
curwin->w_cursor.col += newlen;
}
@@ -815,7 +813,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine)
}
// mark the buffer as changed and prepare for displaying
- inserted_bytes(lnum, col, -count);
+ inserted_bytes(lnum, col, count, 0);
return OK;
}
@@ -1583,6 +1581,7 @@ int open_line(
end_comment_pending = NUL; // turns out there was no leader
}
+ curbuf_splice_pending++;
old_cursor = curwin->w_cursor;
if (dir == BACKWARD) {
curwin->w_cursor.lnum--;
@@ -1597,7 +1596,7 @@ 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,
kExtmarkUndo);
}
did_append = true;
@@ -1638,7 +1637,7 @@ int open_line(
// it. It gets restored at the function end.
curbuf->b_p_pi = true;
} else {
- (void)set_indent(newindent, SIN_INSERT);
+ (void)set_indent(newindent, SIN_INSERT|SIN_NOMARK);
}
less_cols -= curwin->w_cursor.col;
@@ -1687,12 +1686,13 @@ 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, kExtmarkNOOP);
+ 1L, (long)-less_cols, 0);
}
// 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);
+ int cols_added = mincol-1+less_cols_off-less_cols;
+ extmark_splice(curbuf, (int)lnum-1, mincol-1, 0, less_cols_off,
+ 1, cols_added, kExtmarkUndo);
} else {
changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col);
}
@@ -1704,7 +1704,10 @@ int open_line(
}
if (did_append) {
changed_lines(curwin->w_cursor.lnum, 0, curwin->w_cursor.lnum, 1L, true);
+ extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1,
+ 0, 0, 0, 1, 0, kExtmarkUndo);
}
+ curbuf_splice_pending--;
curwin->w_cursor.col = newcol;
curwin->w_cursor.coladd = 0;
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index dccde01d29..c31adc01fd 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -2711,7 +2711,7 @@ 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,
kExtmarkUndo);
if (curwin->w_cursor.lnum >= lnum) {
// Adjust the cursor position if it's in/after the changed
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index d59924cd08..cdb4b127da 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -1826,11 +1826,14 @@ change_indent (
/* We only put back the new line up to the cursor */
new_line[curwin->w_cursor.col] = NUL;
+ int new_col = curwin->w_cursor.col;
// Put back original line
ml_replace(curwin->w_cursor.lnum, orig_line, false);
curwin->w_cursor.col = orig_col;
+ curbuf_splice_pending++;
+
/* Backspace from cursor to start of line */
backspace_until_column(0);
@@ -1838,13 +1841,16 @@ change_indent (
ins_bytes(new_line);
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);
+ curbuf_splice_pending--;
+
+ // TODO(bfredl): test for crazy edge cases, like we stand on a TAB or
+ // something? does this even do the right text change then?
+ int delta = orig_col - new_col;
+ extmark_splice(curbuf, curwin->w_cursor.lnum-1, new_col,
+ 0, delta < 0 ? -delta : 0,
+ 0, delta > 0 ? delta : 0,
+ kExtmarkUndo);
}
}
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 85048427b1..53caaa6a67 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -14,6 +14,7 @@
#include <math.h>
#include "nvim/api/private/defs.h"
+#include "nvim/api/vim.h"
#include "nvim/api/buffer.h"
#include "nvim/log.h"
#include "nvim/vim.h"
@@ -659,10 +660,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, kExtmarkUndo);
+ kExtmarkUndo);
msgmore(-deleted);
} else if (deleted < 0) {
- mark_adjust(eap->line2, MAXLNUM, -deleted, 0L, false, kExtmarkUndo);
+ mark_adjust(eap->line2, MAXLNUM, -deleted, 0L, kExtmarkUndo);
}
if (change_occurred || deleted != 0) {
changed_lines(eap->line1, 0, eap->line2 + 1, -deleted, true);
@@ -875,12 +876,10 @@ 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, kExtmarkNoUndo);
- extmark_adjust(curbuf, line1, line2, last_line - line2, 0L, kExtmarkNoUndo,
- true);
+ mark_adjust_nofold(line1, line2, last_line - line2, 0L, kExtmarkNOOP);
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, kExtmarkNoUndo);
+ mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, kExtmarkNOOP);
FOR_ALL_TAB_WINDOWS(tab, win) {
if (win->w_buffer == curbuf) {
foldMoveRange(&win->w_folds, line1, line2, dest);
@@ -889,8 +888,7 @@ 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,
- kExtmarkNoUndo);
+ mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, kExtmarkNOOP);
FOR_ALL_TAB_WINDOWS(tab, win) {
if (win->w_buffer == curbuf) {
foldMoveRange(&win->w_folds, dest + 1, line1 - 1, line2);
@@ -901,9 +899,15 @@ 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, kExtmarkNoUndo);
+ -(last_line - dest - extra), 0L, kExtmarkNOOP);
+
+ // extmarks are handled separately
+ int size = line2-line1+1;
+ int off = dest >= line2 ? -size : 0;
+ extmark_move_region(curbuf, line1-1, 0,
+ line2-line1+1, 0,
+ dest+off, 0, kExtmarkUndo);
- 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
@@ -1285,16 +1289,19 @@ static void do_filter(
if (do_in) {
if (cmdmod.keepmarks || vim_strchr(p_cpo, CPO_REMMARK) == NULL) {
+ // TODO(bfredl): Currently not active for extmarks. What would we
+ // do if columns don't match, assume added/deleted bytes at the
+ // end of each line?
if (read_linecount >= linecount) {
// move all marks from old lines to new lines
- mark_adjust(line1, line2, linecount, 0L, false, kExtmarkUndo);
+ mark_adjust(line1, line2, linecount, 0L, kExtmarkNOOP);
} 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,
- kExtmarkUndo);
- mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0L, false,
- kExtmarkUndo);
+ mark_adjust(line1, line1 + read_linecount - 1, linecount, 0L,
+ kExtmarkNOOP);
+ mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0L,
+ kExtmarkNOOP);
}
}
@@ -3222,186 +3229,6 @@ 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;
- 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;
- linenr_T 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;
-
- 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:
@@ -3449,11 +3276,6 @@ 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) {
@@ -4010,9 +3832,11 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
goto skip;
}
+
// 3. Substitute the string. During 'inccommand' preview only do this if
// there is a replace pattern.
if (!preview || has_second_delim) {
+ long lnum_start = lnum; // save the start lnum
save_ma = curbuf->b_p_ma;
if (subflags.do_count) {
// prevent accidentally changing the buffer by a function
@@ -4060,7 +3884,8 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
// Finally, at this point we can know where the match actually will
// start in the new text
- current_match.start.col = new_end - new_start;
+ int start_col = new_end - new_start;
+ current_match.start.col = start_col;
(void)vim_regsub_multi(&regmatch,
sub_firstlnum - regmatch.startpos[0].lnum,
@@ -4092,8 +3917,7 @@ 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,
- kExtmarkNOOP);
+ mark_adjust(lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, kExtmarkNOOP);
if (subflags.do_ask) {
appended_lines(lnum - 1, 1L);
@@ -4117,45 +3941,21 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
p1 += (*mb_ptr2len)(p1) - 1;
}
}
- current_match.end.col = STRLEN(new_start);
+ size_t new_endcol = STRLEN(new_start);
+ current_match.end.col = new_endcol;
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);
+ // TODO(bfredl): adjust in preview, because decorations?
+ // this has some robustness issues, will look into later.
+ if (!preview) {
+ lpos_T start = regmatch.startpos[0], end = regmatch.endpos[0];
+ int matchcols = end.col - ((end.lnum == start.lnum)
+ ? start.col : 0);
+ int subcols = new_endcol - ((lnum == lnum_start) ? start_col : 0);
+ extmark_splice(curbuf, lnum_start-1, start_col,
+ end.lnum-start.lnum, matchcols,
+ lnum-lnum_start, subcols, kExtmarkUndo);
}
- }
}
@@ -4225,7 +4025,7 @@ skip:
ml_delete(lnum, false);
}
mark_adjust(lnum, lnum + nmatch_tl - 1,
- (long)MAXLNUM, -nmatch_tl, false, kExtmarkNOOP);
+ (long)MAXLNUM, -nmatch_tl, kExtmarkNOOP);
if (subflags.do_ask) {
deleted_lines(lnum, nmatch_tl);
}
@@ -4387,7 +4187,7 @@ skip:
} else if (*p_icm != NUL && pat != NULL) {
if (pre_src_id == 0) {
// Get a unique new src_id, saved in a static
- pre_src_id = bufhl_add_hl(NULL, 0, -1, 0, 0, 0);
+ pre_src_id = (int)nvim_create_namespace((String)STRING_INIT);
}
if (pre_hl_id == 0) {
pre_hl_id = syn_check_group((char_u *)S_LEN("Substitute"));
@@ -4396,40 +4196,11 @@ skip:
preview_buf = show_sub(eap, old_cursor, &preview_lines,
pre_hl_id, pre_src_id);
if (subsize > 0) {
- bufhl_clear_line_range(orig_buf, pre_src_id, eap->line1,
- kv_last(preview_lines.subresults).end.lnum);
+ extmark_clear(orig_buf, pre_src_id, eap->line1-1, 0,
+ kv_last(preview_lines.subresults).end.lnum-1, MAXCOL);
}
}
}
- 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/fold.c b/src/nvim/fold.c
index b193b4005c..addfab8f08 100644
--- a/src/nvim/fold.c
+++ b/src/nvim/fold.c
@@ -22,6 +22,7 @@
#include "nvim/func_attr.h"
#include "nvim/indent.h"
#include "nvim/buffer_updates.h"
+#include "nvim/mark_extended.h"
#include "nvim/mark.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -1610,6 +1611,7 @@ static void foldAddMarker(linenr_T lnum, const char_u *marker, size_t markerlen)
// Allocate a new line: old-line + 'cms'-start + marker + 'cms'-end
line = ml_get(lnum);
size_t line_len = STRLEN(line);
+ size_t added = 0;
if (u_save(lnum - 1, lnum + 1) == OK) {
// Check if the line ends with an unclosed comment
@@ -1619,12 +1621,19 @@ static void foldAddMarker(linenr_T lnum, const char_u *marker, size_t markerlen)
// Append the marker to the end of the line
if (p == NULL || line_is_comment) {
STRLCPY(newline + line_len, marker, markerlen + 1);
+ added = markerlen;
} else {
STRCPY(newline + line_len, cms);
memcpy(newline + line_len + (p - cms), marker, markerlen);
STRCPY(newline + line_len + (p - cms) + markerlen, p + 2);
+ added = markerlen + STRLEN(cms)-2;
}
ml_replace(lnum, newline, false);
+ if (added) {
+ extmark_splice(curbuf, (int)lnum-1, (int)line_len,
+ 0, 0,
+ 0, (int)added, kExtmarkUndo);
+ }
}
}
@@ -1692,6 +1701,9 @@ static void foldDelMarker(linenr_T lnum, char_u *marker, size_t markerlen)
memcpy(newline, line, (size_t)(p - line));
STRCPY(newline + (p - line), p + len);
ml_replace(lnum, newline, false);
+ extmark_splice(curbuf, (int)lnum-1, (int)(p - line),
+ 0, (int)len,
+ 0, 0, kExtmarkUndo);
}
break;
}
diff --git a/src/nvim/indent.c b/src/nvim/indent.c
index efbfea33aa..2c5fdd8ea6 100644
--- a/src/nvim/indent.c
+++ b/src/nvim/indent.c
@@ -13,6 +13,7 @@
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/mark.h"
+#include "nvim/mark_extended.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/misc1.h"
@@ -88,6 +89,7 @@ int get_indent_str(char_u *ptr, int ts, int list)
// SIN_CHANGED: call changed_bytes() if the line was changed.
// SIN_INSERT: insert the indent in front of the line.
// SIN_UNDO: save line for undo before changing it.
+// SIN_NOMARK: don't move extmarks (because just after ml_append or something)
// @param size measured in spaces
// Returns true if the line was changed.
int set_indent(int size, int flags)
@@ -205,6 +207,7 @@ int set_indent(int size, int flags)
// If 'preserveindent' and 'expandtab' are both set keep the original
// characters and allocate accordingly. We will fill the rest with spaces
// after the if (!curbuf->b_p_et) below.
+ int skipcols = 0; // number of columns (in bytes) that were presved
if (orig_char_len != -1) {
int newline_size; // = orig_char_len + size - ind_done + line_len
STRICT_ADD(orig_char_len, size, &newline_size, int);
@@ -219,6 +222,7 @@ int set_indent(int size, int flags)
ind_len = orig_char_len + todo;
p = oldline;
s = newline;
+ skipcols = orig_char_len;
while (orig_char_len > 0) {
*s++ = *p++;
@@ -263,6 +267,7 @@ int set_indent(int size, int flags)
ind_done++;
}
*s++ = *p++;
+ skipcols++;
}
// Fill to next tabstop with a tab, if possible.
@@ -290,6 +295,13 @@ int set_indent(int size, int flags)
// Replace the line (unless undo fails).
if (!(flags & SIN_UNDO) || (u_savesub(curwin->w_cursor.lnum) == OK)) {
ml_replace(curwin->w_cursor.lnum, newline, false);
+ if (!(flags & SIN_NOMARK)) {
+ extmark_splice(curbuf,
+ (int)curwin->w_cursor.lnum-1, skipcols,
+ 0, (int)(p-oldline) - skipcols,
+ 0, (int)(s-newline) - skipcols,
+ kExtmarkUndo);
+ }
if (flags & SIN_CHANGED) {
changed_bytes(curwin->w_cursor.lnum, 0);
diff --git a/src/nvim/indent.h b/src/nvim/indent.h
index 6552157d20..f96732bf1c 100644
--- a/src/nvim/indent.h
+++ b/src/nvim/indent.h
@@ -3,10 +3,11 @@
#include "nvim/vim.h"
-/* flags for set_indent() */
-#define SIN_CHANGED 1 /* call changed_bytes() when line changed */
-#define SIN_INSERT 2 /* insert indent before existing text */
-#define SIN_UNDO 4 /* save line for undo before changing it */
+// flags for set_indent()
+#define SIN_CHANGED 1 // call changed_bytes() when line changed
+#define SIN_INSERT 2 // insert indent before existing text
+#define SIN_UNDO 4 // save line for undo before changing it
+#define SIN_NOMARK 8 // don't adjust extmarks
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "indent.h.generated.h"
diff --git a/src/nvim/map.c b/src/nvim/map.c
index cdade5ee71..cba39f24b3 100644
--- a/src/nvim/map.c
+++ b/src/nvim/map.c
@@ -44,7 +44,8 @@
#define INITIALIZER(T, U) T##_##U##_initializer
#define INITIALIZER_DECLARE(T, U, ...) const U INITIALIZER(T, U) = __VA_ARGS__
-#define DEFAULT_INITIALIZER {0}
+#define DEFAULT_INITIALIZER { 0 }
+#define SSIZE_INITIALIZER { -1 }
#define MAP_IMPL(T, U, ...) \
INITIALIZER_DECLARE(T, U, __VA_ARGS__); \
@@ -178,10 +179,16 @@ MAP_IMPL(int, int, DEFAULT_INITIALIZER)
MAP_IMPL(cstr_t, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER)
+MAP_IMPL(uint64_t, ssize_t, SSIZE_INITIALIZER)
+MAP_IMPL(uint64_t, uint64_t, DEFAULT_INITIALIZER)
+#define EXTMARK_NS_INITIALIZER { 0, 0 }
+MAP_IMPL(uint64_t, ExtmarkNs, EXTMARK_NS_INITIALIZER)
+#define KVEC_INITIALIZER { .size = 0, .capacity = 0, .items = NULL }
+#define EXTMARK_ITEM_INITIALIZER { 0, 0, 0, KVEC_INITIALIZER }
+MAP_IMPL(uint64_t, ExtmarkItem, EXTMARK_ITEM_INITIALIZER)
MAP_IMPL(handle_T, ptr_t, DEFAULT_INITIALIZER)
#define MSGPACK_HANDLER_INITIALIZER { .fn = NULL, .fast = false }
MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER)
-#define KVEC_INITIALIZER { .size = 0, .capacity = 0, .items = NULL }
MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER)
MAP_IMPL(String, handle_T, 0)
diff --git a/src/nvim/map.h b/src/nvim/map.h
index fec91ac0c2..761938776d 100644
--- a/src/nvim/map.h
+++ b/src/nvim/map.h
@@ -4,9 +4,9 @@
#include <stdbool.h>
#include "nvim/map_defs.h"
+#include "nvim/mark_extended_defs.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/dispatch.h"
-#include "nvim/bufhl_defs.h"
#include "nvim/highlight_defs.h"
#if defined(__NetBSD__)
@@ -38,6 +38,18 @@ MAP_DECLS(int, int)
MAP_DECLS(cstr_t, ptr_t)
MAP_DECLS(ptr_t, ptr_t)
MAP_DECLS(uint64_t, ptr_t)
+MAP_DECLS(uint64_t, ssize_t)
+MAP_DECLS(uint64_t, uint64_t)
+
+// NB: this is the only way to define a struct both containing and contained
+// in a map...
+typedef struct ExtmarkNs { // For namespacing extmarks
+ Map(uint64_t, uint64_t) *map; // For fast lookup
+ uint64_t free_id; // For automatically assigning id's
+} ExtmarkNs;
+
+MAP_DECLS(uint64_t, ExtmarkNs)
+MAP_DECLS(uint64_t, ExtmarkItem)
MAP_DECLS(handle_T, ptr_t)
MAP_DECLS(String, MsgpackRpcRequestHandler)
MAP_DECLS(HlEntry, int)
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index 8b2f342142..4a7452493a 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -20,6 +20,7 @@
#include "nvim/ex_cmds.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/mark_extended.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -915,10 +916,9 @@ void mark_adjust(linenr_T line1,
linenr_T line2,
long amount,
long amount_after,
- bool end_temp,
ExtmarkOp op)
{
- mark_adjust_internal(line1, line2, amount, amount_after, true, end_temp, op);
+ mark_adjust_internal(line1, line2, amount, amount_after, true, op);
}
// mark_adjust_nofold() does the same as mark_adjust() but without adjusting
@@ -927,15 +927,15 @@ 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,
ExtmarkOp op)
{
- mark_adjust_internal(line1, line2, amount, amount_after, false, end_temp, op);
+ mark_adjust_internal(line1, line2, amount, amount_after, false, 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,
ExtmarkOp op)
{
int i;
@@ -991,9 +991,8 @@ 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);
+ extmark_adjust(curbuf, line1, line2, amount, amount_after, op);
}
}
@@ -1106,7 +1105,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, ExtmarkOp op)
+ int spaces_removed)
{
int i;
int fnum = curbuf->b_fnum;
@@ -1126,13 +1125,6 @@ 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.h b/src/nvim/mark.h
index ed4e47907b..d8370c367a 100644
--- a/src/nvim/mark.h
+++ b/src/nvim/mark.h
@@ -6,6 +6,7 @@
#include "nvim/buffer_defs.h"
#include "nvim/func_attr.h"
#include "nvim/mark_defs.h"
+#include "nvim/mark_extended_defs.h"
#include "nvim/memory.h"
#include "nvim/pos.h"
#include "nvim/os/time.h"
diff --git a/src/nvim/mark_extended.c b/src/nvim/mark_extended.c
index 17776d438a..b60d847676 100644
--- a/src/nvim/mark_extended.c
+++ b/src/nvim/mark_extended.c
@@ -7,14 +7,13 @@
// The btree provides efficient 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
+// Marks are moved by calls to extmark_splice. Additionally mark_adjust
+// might adjust extmarks to line inserts/deletes.
//
// 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.
+// extmark_splice. 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.
@@ -30,9 +29,11 @@
// leave it in same position unless it is on the EOL of a line.
#include <assert.h>
+#include "nvim/api/vim.h"
#include "nvim/vim.h"
-#include "charset.h"
+#include "nvim/charset.h"
#include "nvim/mark_extended.h"
+#include "nvim/buffer_updates.h"
#include "nvim/memline.h"
#include "nvim/pos.h"
#include "nvim/globals.h"
@@ -40,93 +41,227 @@
#include "nvim/lib/kbtree.h"
#include "nvim/undo.h"
#include "nvim/buffer.h"
+#include "nvim/syntax.h"
+#include "nvim/highlight.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "mark_extended.c.generated.h"
#endif
+static ExtmarkNs *buf_ns_ref(buf_T *buf, uint64_t ns_id, bool put) {
+ if (!buf->b_extmark_ns) {
+ if (!put) {
+ return NULL;
+ }
+ buf->b_extmark_ns = map_new(uint64_t, ExtmarkNs)();
+ buf->b_extmark_index = map_new(uint64_t, ExtmarkItem)();
+ }
+
+ ExtmarkNs *ns = map_ref(uint64_t, ExtmarkNs)(buf->b_extmark_ns, ns_id, put);
+ if (put && ns->map == NULL) {
+ ns->map = map_new(uint64_t, uint64_t)();
+ ns->free_id = 1;
+ }
+ return ns;
+}
+
/// 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)
+/// @returns the mark id
+uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id,
+ int row, 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;
+ ExtmarkNs *ns = buf_ns_ref(buf, ns_id, true);
+ mtpos_t old_pos;
+ uint64_t mark = 0;
+
+ if (id == 0) {
+ id = ns->free_id++;
} 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);
+ uint64_t old_mark = map_get(uint64_t, uint64_t)(ns->map, id);
+ if (old_mark) {
+ if (old_mark & MARKTREE_PAIRED_FLAG) {
+ extmark_del(buf, ns_id, id);
+ } else {
+ // TODO(bfredl): we need to do more if "revising" a decoration mark.
+ MarkTreeIter itr[1];
+ old_pos = marktree_lookup(buf->b_marktree, old_mark, itr);
+ assert(itr->node);
+ if (old_pos.row == row && old_pos.col == col) {
+ map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, old_mark);
+ mark = marktree_revise(buf->b_marktree, itr);
+ goto revised;
+ }
+ marktree_del_itr(buf->b_marktree, itr, false);
+ }
+ } else {
+ ns->free_id = MAX(ns->free_id, id+1);
}
+ }
+
+ mark = marktree_put(buf->b_marktree, row, col, true);
+revised:
+ map_put(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark,
+ (ExtmarkItem){ ns_id, id, 0,
+ KV_INITIAL_VALUE });
+ map_put(uint64_t, uint64_t)(ns->map, id, mark);
+
+ if (op != kExtmarkNoUndo) {
+ // TODO(bfredl): this doesn't cover all the cases and probably shouldn't
+ // be done "prematurely". Any movement in undo history might necessitate
+ // adding new marks to old undo headers.
+ u_extmark_set(buf, mark, row, col);
+ }
+ return id;
+}
+
+static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col)
+{
+ MarkTreeIter itr[1];
+ mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr);
+ if (pos.row == -1) {
return false;
}
+
+ if (pos.row == row && pos.col == col) {
+ return true;
+ }
+
+ marktree_move(buf->b_marktree, itr, row, col);
+ return true;
}
// Remove an extmark
// Returns 0 on missing id
-int extmark_del(buf_T *buf, uint64_t ns, uint64_t id, ExtmarkOp op)
+bool extmark_del(buf_T *buf, uint64_t ns_id, uint64_t id)
{
- Extmark *extmark = extmark_from_id(buf, ns, id);
- if (!extmark) {
- return 0;
+ ExtmarkNs *ns = buf_ns_ref(buf, ns_id, false);
+ if (!ns) {
+ return false;
+ }
+
+ uint64_t mark = map_get(uint64_t, uint64_t)(ns->map, id);
+ if (!mark) {
+ return false;
}
- return extmark_delete(extmark, buf, ns, id, op);
+
+ MarkTreeIter itr[1];
+ mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr);
+ assert(pos.row >= 0);
+ marktree_del_itr(buf->b_marktree, itr, false);
+ ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark);
+
+ if (mark & MARKTREE_PAIRED_FLAG) {
+ mtpos_t pos2 = marktree_lookup(buf->b_marktree,
+ mark|MARKTREE_END_FLAG, itr);
+ assert(pos2.row >= 0);
+ marktree_del_itr(buf->b_marktree, itr, false);
+ if (item.hl_id && pos2.row >= pos.row) {
+ redraw_buf_range_later(buf, pos.row+1, pos2.row+1);
+ }
+ }
+
+ if (kv_size(item.virt_text)) {
+ redraw_buf_line_later(buf, pos.row+1);
+ }
+ clear_virttext(&item.virt_text);
+
+ map_del(uint64_t, uint64_t)(ns->map, id);
+ map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark);
+
+ // TODO(bfredl): delete it from current undo header, opportunistically?
+
+ return true;
}
// 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)
+bool extmark_clear(buf_T *buf, uint64_t ns_id,
+ int l_row, colnr_T l_col,
+ int u_row, colnr_T u_col)
{
if (!buf->b_extmark_ns) {
- return;
+ return false;
}
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;
+ bool all_ns = (ns_id == 0);
+ ExtmarkNs *ns = NULL;
if (!all_ns) {
- ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns);
- if (!ns_obj) {
+ ns = buf_ns_ref(buf, ns_id, false);
+ if (!ns) {
// nothing to do
- return;
+ return false;
}
+
+ // TODO(bfredl): if map_size(ns->map) << buf->b_marktree.n_nodes
+ // it could be faster to iterate over the map instead
}
- 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);
+ // the value is either zero or the lnum (row+1) if highlight was present.
+ static Map(uint64_t, uint64_t) *delete_set = NULL;
+ if (delete_set == NULL) {
+ delete_set = map_new(uint64_t, uint64_t)();
+ }
+
+ MarkTreeIter itr[1];
+ marktree_itr_get(buf->b_marktree, l_row, l_col, itr);
+ while (true) {
+ mtmark_t mark = marktree_itr_current(itr);
+ if (mark.row < 0
+ || mark.row > u_row
+ || (mark.row == u_row && mark.col > u_col)) {
+ break;
}
- });
+ uint64_t *del_status = map_ref(uint64_t, uint64_t)(delete_set, mark.id,
+ false);
+ if (del_status) {
+ marktree_del_itr(buf->b_marktree, itr, false);
+ map_del(uint64_t, uint64_t)(delete_set, mark.id);
+ if (*del_status > 0) {
+ redraw_buf_range_later(buf, (linenr_T)(*del_status), mark.row+1);
+ }
+ continue;
+ }
+
+ uint64_t start_id = mark.id & ~MARKTREE_END_FLAG;
+ ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index,
+ start_id);
- // Record the undo for the actual move
- if (marks_cleared && undo == kExtmarkUndo) {
- u_extmark_clear(buf, ns, l_lnum, u_lnum);
+ assert(item.ns_id > 0 && item.mark_id > 0);
+ if (item.mark_id > 0 && (item.ns_id == ns_id || all_ns)) {
+ if (kv_size(item.virt_text)) {
+ redraw_buf_line_later(buf, mark.row+1);
+ }
+ clear_virttext(&item.virt_text);
+ marks_cleared = true;
+ if (mark.id & MARKTREE_PAIRED_FLAG) {
+ uint64_t other = mark.id ^ MARKTREE_END_FLAG;
+ uint64_t status = item.hl_id ? ((uint64_t)mark.row+1) : 0;
+ map_put(uint64_t, uint64_t)(delete_set, other, status);
+ }
+ ExtmarkNs *my_ns = all_ns ? buf_ns_ref(buf, item.ns_id, false) : ns;
+ map_del(uint64_t, uint64_t)(my_ns->map, item.mark_id);
+ map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark.id);
+ marktree_del_itr(buf->b_marktree, itr, false);
+ } else {
+ marktree_itr_next(buf->b_marktree, itr);
+ }
}
+ uint64_t id, status;
+ map_foreach(delete_set, id, status, {
+ mtpos_t pos = marktree_lookup(buf->b_marktree, id, itr);
+ assert(itr->node);
+ marktree_del_itr(buf->b_marktree, itr, false);
+ if (status > 0) {
+ redraw_buf_range_later(buf, (linenr_T)status, pos.row+1);
+ }
+ });
+ map_clear(uint64_t, uint64_t)(delete_set);
+ return marks_cleared;
}
// Returns the position of marks between a range,
@@ -135,190 +270,65 @@ void extmark_clear(buf_T *buf, uint64_t ns,
// 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,
+ExtmarkArray extmark_get(buf_T *buf, uint64_t ns_id,
+ int l_row, colnr_T l_col,
+ int u_row, colnr_T u_col,
int64_t amount, bool reverse)
{
ExtmarkArray array = KV_INITIAL_VALUE;
+ MarkTreeIter itr[1];
// 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);
+ marktree_itr_get_ext(buf->b_marktree, (mtpos_t){ l_row, l_col },
+ itr, reverse, false, NULL);
+ int order = reverse ? -1 : 1;
+ while ((int64_t)kv_size(array) < amount) {
+ mtmark_t mark = marktree_itr_current(itr);
+ if (mark.row < 0
+ || (mark.row - u_row) * order > 0
+ || (mark.row == u_row && (mark.col - u_col) * order > 0)) {
+ break;
}
- // Just update the column
- } else {
- if (mitr != NULL) {
- // The btree stays organized during iteration with kbitr_t
- extmark->col = col;
+ ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index,
+ mark.id);
+ if (item.ns_id == ns_id) {
+ kv_push(array, ((ExtmarkInfo) { .ns_id = item.ns_id,
+ .mark_id = item.mark_id,
+ .row = mark.row, .col = mark.col }));
+ }
+ if (reverse) {
+ marktree_itr_prev(buf->b_marktree, itr);
} else {
- // Keep the btree in order
- kb_del(markitems, &old_line->items, *extmark);
- extmark_put(col, id, old_line, ns);
+ marktree_itr_next(buf->b_marktree, itr);
}
}
-}
-
-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;
+ return array;
}
// Lookup an extmark by id
-Extmark *extmark_from_id(buf_T *buf, uint64_t ns, uint64_t id)
+ExtmarkInfo extmark_from_id(buf_T *buf, uint64_t ns_id, 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;
+ ExtmarkNs *ns = buf_ns_ref(buf, ns_id, false);
+ ExtmarkInfo ret = { 0, 0, -1, -1 };
+ if (!ns) {
+ return ret;
}
- ExtmarkLine *extmarkline = pmap_get(uint64_t)(ns_obj->map, id);
- if (!extmarkline) {
- return NULL;
+
+ uint64_t mark = map_get(uint64_t, uint64_t)(ns->map, id);
+ if (!mark) {
+ return ret;
}
- FOR_ALL_EXTMARKS_IN_LINE(extmarkline->items, 0, MAXCOL, {
- if (extmark->ns_id == ns
- && extmark->mark_id == id) {
- return extmark;
- }
- })
- return NULL;
-}
+ mtpos_t pos = marktree_lookup(buf->b_marktree, mark, NULL);
+ assert(pos.row >= 0);
-// 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;
-}
+ ret.ns_id = ns_id;
+ ret.mark_id = id;
+ ret.row = pos.row;
+ ret.col = pos.col;
-// Returns an available 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;
+ return ret;
}
-// 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)
@@ -327,813 +337,574 @@ void extmark_free_all(buf_T *buf)
return;
}
- uint64_t ns;
- ExtmarkNs *ns_obj;
+ uint64_t id;
+ ExtmarkNs ns;
+ ExtmarkItem item;
- FOR_ALL_EXTMARKLINES(buf, 1, MAXLNUM, {
- kb_del_itr(extmarklines, &buf->b_extlines, &itr);
- extmarkline_free(extmarkline);
- })
+ marktree_clear(buf->b_marktree);
- map_foreach(buf->b_extmark_ns, ns, ns_obj, {
- (void)ns;
- pmap_free(uint64_t)(ns_obj->map);
- xfree(ns_obj);
+ map_foreach(buf->b_extmark_ns, id, ns, {
+ (void)id;
+ map_free(uint64_t, uint64_t)(ns.map);
});
-
- pmap_free(uint64_t)(buf->b_extmark_ns);
+ map_free(uint64_t, ExtmarkNs)(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);
+ map_foreach(buf->b_extmark_index, id, item, {
+ (void)id;
+ clear_virttext(&item.virt_text);
+ });
+ map_free(uint64_t, ExtmarkItem)(buf->b_extmark_index);
+ buf->b_extmark_index = NULL;
}
// 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)
+static void u_extmark_set(buf_T *buf, uint64_t mark,
+ int row, colnr_T col)
{
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;
+ ExtmarkSavePos pos;
+ pos.mark = mark;
+ pos.old_row = -1;
+ pos.old_col = -1;
+ pos.row = row;
+ pos.col = col;
- ExtmarkUndoObject undo = { .type = undo_type,
- .data.set = set };
+ ExtmarkUndoObject undo = { .type = kExtmarkSavePos,
+ .data.savepos = pos };
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)
+/// copy extmarks data between range
+///
+/// useful when we cannot simply reverse the operation. This will do nothing on
+/// redo, enforces correct position when undo.
+void u_extmark_copy(buf_T *buf,
+ int l_row, colnr_T l_col,
+ int u_row, colnr_T u_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;
- ExtmarkUndoObject undo = { .type = kExtmarkUpdate,
- .data.update = update };
- kv_push(uhp->uh_extmark, undo);
-}
+ MarkTreeIter itr[1];
+ marktree_itr_get(buf->b_marktree, l_row, l_col, itr);
+ while (true) {
+ mtmark_t mark = marktree_itr_current(itr);
+ if (mark.row < 0
+ || mark.row > u_row
+ || (mark.row == u_row && mark.col > u_col)) {
+ break;
+ }
+ ExtmarkSavePos pos;
+ pos.mark = mark.id;
+ pos.old_row = mark.row;
+ pos.old_col = mark.col;
+ pos.row = -1;
+ pos.col = -1;
+
+ undo.data.savepos = pos;
+ undo.type = kExtmarkSavePos;
+ 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;
+ marktree_itr_next(buf->b_marktree, itr);
}
+}
- if (kv_size(uhp->uh_extmark) < 1) {
- return false;
- }
- // Check the last action
- ExtmarkUndoObject object = kv_last(uhp->uh_extmark);
+/// undo or redo an extmark operation
+void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo)
+{
+ // splice: any text operation changing position (except :move)
+ if (undo_info.type == kExtmarkSplice) {
+ // Undo
+ ExtmarkSplice splice = undo_info.data.splice;
+ if (undo) {
+ extmark_splice(curbuf,
+ splice.start_row, splice.start_col,
+ splice.newextent_row, splice.newextent_col,
+ splice.oldextent_row, splice.oldextent_col,
+ kExtmarkNoUndo);
- if (object.type != kColAdjust) {
- return false;
+ } else {
+ extmark_splice(curbuf,
+ splice.start_row, splice.start_col,
+ splice.oldextent_row, splice.oldextent_col,
+ splice.newextent_row, splice.newextent_col,
+ kExtmarkNoUndo);
+ }
+ // kExtmarkSavePos
+ } else if (undo_info.type == kExtmarkSavePos) {
+ ExtmarkSavePos pos = undo_info.data.savepos;
+ if (undo) {
+ if (pos.old_row >= 0) {
+ extmark_setraw(curbuf, pos.mark, pos.old_row, pos.old_col);
+ }
+ // Redo
+ } else {
+ if (pos.row >= 0) {
+ extmark_setraw(curbuf, pos.mark, pos.row, pos.col);
+ }
+ }
+ } else if (undo_info.type == kExtmarkMove) {
+ ExtmarkMove move = undo_info.data.move;
+ if (undo) {
+ extmark_move_region(curbuf,
+ move.new_row, move.new_col,
+ move.extent_row, move.extent_col,
+ move.start_row, move.start_col,
+ kExtmarkNoUndo);
+ } else {
+ extmark_move_region(curbuf,
+ move.start_row, move.start_col,
+ move.extent_row, move.extent_col,
+ move.new_row, move.new_col,
+ kExtmarkNoUndo);
+ }
}
- 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;
+// 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)
+{
+ if (!curbuf_splice_pending) {
+ int old_extent, new_extent;
+ if (amount == MAXLNUM) {
+ old_extent = (int)(line2 - line1+1);
+ new_extent = (int)(amount_after + old_extent);
+ } else {
+ // A region is either deleted (amount == MAXLNUM) or
+ // added (line2 == MAXLNUM). The only other case is :move
+ // which is handled by a separate entry point extmark_move_region.
+ assert(line2 == MAXLNUM);
+ old_extent = 0;
+ new_extent = (int)amount;
+ }
+ extmark_splice(buf,
+ (int)line1-1, 0,
+ old_extent, 0,
+ new_extent, 0, undo);
}
-
- 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)
+void extmark_splice(buf_T *buf,
+ int start_row, colnr_T start_col,
+ int oldextent_row, colnr_T oldextent_col,
+ int newextent_row, colnr_T newextent_col,
+ ExtmarkOp undo)
{
- 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;
+ buf_updates_send_splice(buf, start_row, start_col,
+ oldextent_row, oldextent_col,
+ newextent_row, newextent_col);
- ExtmarkUndoObject undo = { .type = kColAdjust,
- .data.col_adjust = col_adjust };
-
- kv_push(uhp->uh_extmark, undo);
+ if (undo == kExtmarkUndo && (oldextent_row > 0 || oldextent_col > 0)) {
+ // Copy marks that would be effected by delete
+ // TODO(bfredl): Be "smart" about gravity here, left-gravity at the
+ // beginning and right-gravity at the end need not be preserved.
+ // Also be smart about marks that already have been saved (important for
+ // merge!)
+ int end_row = start_row + oldextent_row;
+ int end_col = (oldextent_row ? 0 : start_col) + oldextent_col;
+ u_extmark_copy(buf, start_row, start_col, end_row, end_col);
}
-}
-// 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;
+ marktree_splice(buf->b_marktree, start_row, start_col,
+ oldextent_row, oldextent_col,
+ newextent_row, newextent_col);
- ExtmarkUndoObject undo = { .type = kColAdjustDelete,
- .data.col_adjust_delete = col_adjust_delete };
+ if (undo == kExtmarkUndo) {
+ u_header_T *uhp = u_force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
- kv_push(uhp->uh_extmark, undo);
-}
+ bool merged = false;
+ // TODO(bfredl): this is quite rudimentary. We merge small (within line)
+ // inserts with each other and small deletes with each other. Add full
+ // merge algorithm later.
+ if (oldextent_row == 0 && newextent_row == 0 && kv_size(uhp->uh_extmark)) {
+ ExtmarkUndoObject *item = &kv_A(uhp->uh_extmark,
+ kv_size(uhp->uh_extmark)-1);
+ if (item->type == kExtmarkSplice) {
+ ExtmarkSplice *splice = &item->data.splice;
+ if (splice->start_row == start_row && splice->oldextent_row == 0
+ && splice->newextent_row == 0) {
+ if (oldextent_col == 0 && start_col >= splice->start_col
+ && start_col <= splice->start_col+splice->newextent_col) {
+ splice->newextent_col += newextent_col;
+ merged = true;
+ } else if (newextent_col == 0
+ && start_col == splice->start_col+splice->newextent_col) {
+ splice->oldextent_col += oldextent_col;
+ merged = true;
+ } else if (newextent_col == 0
+ && start_col + oldextent_col == splice->start_col) {
+ splice->start_col = start_col;
+ splice->oldextent_col += oldextent_col;
+ merged = true;
+ }
+ }
+ }
+ }
-// 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;
+ if (!merged) {
+ ExtmarkSplice splice;
+ splice.start_row = start_row;
+ splice.start_col = start_col;
+ splice.oldextent_row = oldextent_row;
+ splice.oldextent_col = oldextent_col;
+ splice.newextent_row = newextent_row;
+ splice.newextent_col = newextent_col;
+
+ kv_push(uhp->uh_extmark,
+ ((ExtmarkUndoObject){ .type = kExtmarkSplice,
+ .data.splice = splice }));
+ }
}
-
- 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)
+
+void extmark_move_region(buf_T *buf,
+ int start_row, colnr_T start_col,
+ int extent_row, colnr_T extent_col,
+ int new_row, colnr_T new_col,
+ ExtmarkOp undo)
{
- u_header_T *uhp = u_force_get_undo_header(buf);
- if (!uhp) {
- return;
- }
+ // TODO(bfredl): this is not synced to the buffer state inside the callback.
+ // But unless we make the undo implementation smarter, this is not ensured
+ // anyway.
+ buf_updates_send_splice(buf, start_row, start_col,
+ extent_row, extent_col,
+ 0, 0);
- AdjustMove move;
- move.line1 = line1;
- move.line2 = line2;
- move.last_line = last_line;
- move.dest = dest;
- move.num_lines = num_lines;
- move.extra = extra;
+ marktree_move_region(buf->b_marktree, start_row, start_col,
+ extent_row, extent_col,
+ new_row, new_col);
- ExtmarkUndoObject undo = { .type = kAdjustMove,
- .data.move = move };
+ buf_updates_send_splice(buf, new_row, new_col,
+ 0, 0,
+ extent_row, extent_col);
- 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;
- }
+ if (undo == kExtmarkUndo) {
+ u_header_T *uhp = u_force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
- bool all_ns = ns == 0 ? true : false;
+ ExtmarkMove move;
+ move.start_row = start_row;
+ move.start_col = start_col;
+ move.extent_row = extent_row;
+ move.extent_col = extent_col;
+ move.new_row = new_row;
+ move.new_col = new_col;
- 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);
- }
- });
+ kv_push(uhp->uh_extmark,
+ ((ExtmarkUndoObject){ .type = kExtmarkMove,
+ .data.move = move }));
+ }
}
-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)
+uint64_t src2ns(Integer *src_id)
{
- u_header_T *uhp = u_force_get_undo_header(buf);
- if (!uhp) {
- return;
+ if (*src_id == 0) {
+ *src_id = (Integer)nvim_create_namespace((String)STRING_INIT);
+ }
+ if (*src_id < 0) {
+ return UINT64_MAX;
+ } else {
+ return (uint64_t)(*src_id);
}
+}
- 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;
+/// Adds a decoration to a buffer.
+///
+/// Unlike matchaddpos() highlights, these follow changes to the the buffer
+/// texts. Decorations are represented internally and in the API as extmarks.
+///
+/// @param buf The buffer to add decorations to
+/// @param ns_id A valid namespace id.
+/// @param hl_id Id of the highlight group to use (or zero)
+/// @param start_row The line to highlight
+/// @param start_col First column to highlight
+/// @param end_row The line to highlight
+/// @param end_col The last column to highlight
+/// @param virt_text Virtual text (currently placed at the EOL of start_row)
+/// @return The extmark id inside the namespace
+uint64_t extmark_add_decoration(buf_T *buf, uint64_t ns_id, int hl_id,
+ int start_row, colnr_T start_col,
+ int end_row, colnr_T end_col,
+ VirtText virt_text)
+{
+ ExtmarkNs *ns = buf_ns_ref(buf, ns_id, true);
+ ExtmarkItem item;
+ item.ns_id = ns_id;
+ item.mark_id = ns->free_id++;
+ item.hl_id = hl_id;
+ item.virt_text = virt_text;
+
+ uint64_t mark;
+
+ if (end_row > -1) {
+ mark = marktree_put_pair(buf->b_marktree,
+ start_row, start_col, true,
+ end_row, end_col, false);
+ } else {
+ mark = marktree_put(buf->b_marktree, start_row, start_col, true);
+ }
- ExtmarkUndoObject undo = { .type = kExtmarkCopyPlace,
- .data.copy_place = copy_place };
+ map_put(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark, item);
+ map_put(uint64_t, uint64_t)(ns->map, item.mark_id, mark);
- kv_push(uhp->uh_extmark, undo);
+ redraw_buf_range_later(buf, start_row+1,
+ (end_row >= 0 ? end_row : start_row) + 1);
+ return item.mark_id;
}
-// 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;
+/// Add highlighting to a buffer, bounded by two cursor positions,
+/// with an offset.
+///
+/// @param buf Buffer to add highlights to
+/// @param src_id src_id to use or 0 to use a new src_id group,
+/// or -1 for ungrouped highlight.
+/// @param hl_id Highlight group id
+/// @param pos_start Cursor position to start the hightlighting at
+/// @param pos_end Cursor position to end the highlighting at
+/// @param offset Move the whole highlighting this many columns to the right
+void bufhl_add_hl_pos_offset(buf_T *buf,
+ int src_id,
+ int hl_id,
+ lpos_T pos_start,
+ lpos_T pos_end,
+ colnr_T offset)
+{
+ colnr_T hl_start = 0;
+ colnr_T hl_end = 0;
+
+ // TODO(bfredl): if decoration had blocky mode, we could avoid this loop
+ for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum ++) {
+ if (pos_start.lnum < lnum && lnum < pos_end.lnum) {
+ hl_start = offset-1;
+ hl_end = MAXCOL;
+ } else if (lnum == pos_start.lnum && lnum < pos_end.lnum) {
+ hl_start = pos_start.col + offset;
+ hl_end = MAXCOL;
+ } else if (pos_start.lnum < lnum && lnum == pos_end.lnum) {
+ hl_start = offset-1;
+ hl_end = pos_end.col + offset;
+ } else if (pos_start.lnum == lnum && pos_end.lnum == lnum) {
+ hl_start = pos_start.col + offset;
+ hl_end = pos_end.col + offset;
+ }
+ (void)extmark_add_decoration(buf, (uint64_t)src_id, hl_id,
+ (int)lnum-1, hl_start, (int)lnum-1, hl_end,
+ VIRTTEXT_EMPTY);
}
-
- 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)
+void clear_virttext(VirtText *text)
{
- 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);
- }
+ for (size_t i = 0; i < kv_size(*text); i++) {
+ xfree(kv_A(*text, i).text);
}
+ kv_destroy(*text);
+ *text = (VirtText)KV_INITIAL_VALUE;
}
-// undo/redo an kExtmarkMove operation
-static void apply_undo_move(ExtmarkUndoObject undo_info, bool undo)
+VirtText *extmark_find_virttext(buf_T *buf, int row, uint64_t ns_id)
{
- // 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);
+ MarkTreeIter itr[1];
+ marktree_itr_get(buf->b_marktree, row, 0, itr);
+ while (true) {
+ mtmark_t mark = marktree_itr_current(itr);
+ if (mark.row < 0 || mark.row > row) {
+ break;
}
- 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);
+ ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index,
+ mark.id, false);
+ if (item && (ns_id == 0 || ns_id == item->ns_id)
+ && kv_size(item->virt_text)) {
+ return &item->virt_text;
}
- extmark_adjust(curbuf, last_line - num_lines + 1, last_line,
- -(last_line - dest - extra), 0L, kExtmarkNoUndo, true);
+ marktree_itr_next(buf->b_marktree, itr);
}
+ return NULL;
}
-/// 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)
+bool extmark_decorations_reset(buf_T *buf, DecorationState *state)
{
- if (lnum > buf->b_ml.ml_line_count) {
- return 0;
- }
- return (colnr_T)STRLEN(ml_get_buf(buf, lnum, false)) + 1;
+ state->row = -1;
+ return buf->b_extmark_index;
}
-// 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 extmark_decorations_start(buf_T *buf, int top_row, DecorationState *state)
{
- bool marks_exist = false;
-
- ExtmarkLine *extmarkline = extmarkline_ref(buf, lnum, false);
- if (!extmarkline) {
+ kv_size(state->active) = 0;
+ state->top_row = top_row;
+ marktree_itr_get(buf->b_marktree, top_row, 0, state->itr);
+ if (!state->itr->node) {
return false;
}
+ marktree_itr_rewind(buf->b_marktree, state->itr);
+ while (true) {
+ mtmark_t mark = marktree_itr_current(state->itr);
+ if (mark.row < 0) { // || mark.row > end_row
+ break;
+ }
+ // TODO(bfredl): dedicated flag for being a decoration?
+ if ((mark.row < top_row && mark.id&MARKTREE_END_FLAG)) {
+ goto next_mark;
+ }
+ mtpos_t altpos = marktree_lookup(buf->b_marktree,
+ mark.id^MARKTREE_END_FLAG, NULL);
+
+ uint64_t start_id = mark.id & ~MARKTREE_END_FLAG;
+ ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index,
+ start_id, false);
+ if ((!(mark.id&MARKTREE_END_FLAG) && altpos.row < top_row
+ && !kv_size(item->virt_text))
+ || ((mark.id&MARKTREE_END_FLAG) && altpos.row >= top_row)) {
+ goto next_mark;
+ }
- 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);
+ if (item && (item->hl_id > 0 || kv_size(item->virt_text))) {
+ int attr_id = item->hl_id > 0 ? syn_id2attr(item->hl_id) : 0;
+ VirtText *vt = kv_size(item->virt_text) ? &item->virt_text : NULL;
+ HlRange range;
+ if (mark.id&MARKTREE_END_FLAG) {
+ range = (HlRange){ altpos.row, altpos.col, mark.row, mark.col,
+ attr_id, vt };
} else {
- // Mark outside of range
- // -1 because a delete of width 0 should still move marks
- col_amount = -(update_col - mincol) - 1;
+ range = (HlRange){ mark.row, mark.col, altpos.row,
+ altpos.col, attr_id, vt };
}
- } else {
- // for anything other than deletes
- col_amount = update_col;
+ kv_push(state->active, range);
}
-
- // 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);
+next_mark:
+ if (marktree_itr_node_done(state->itr)) {
+ break;
}
- })
-
- if (kb_size(&extmarkline->items) == 0) {
- kb_del(extmarklines, &buf->b_extlines, extmarkline);
- extmarkline_free(extmarkline);
+ marktree_itr_next(buf->b_marktree, state->itr);
}
- return marks_exist;
+ return true; // TODO(bfredl): check if available in the region
}
-// 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)
+bool extmark_decorations_line(buf_T *buf, int row, DecorationState *state)
{
- assert(col_amount > INT_MIN && col_amount <= INT_MAX);
-
- bool marks_moved = extmark_col_adjust_impl(buf, lnum, mincol, lnum_amount,
- false, col_amount);
-
- marks_moved |= bufhl_mark_col_adjust(buf, lnum, mincol,
- lnum_amount, col_amount);
-
- if (undo == kExtmarkUndo && marks_moved) {
- u_extmark_col_adjust(buf, lnum, mincol, lnum_amount, col_amount);
+ if (state->row == -1) {
+ extmark_decorations_start(buf, row, state);
}
+ state->row = row;
+ state->col_until = -1;
+ return true; // TODO(bfredl): be more precise
}
-// 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)
+int extmark_decorations_col(buf_T *buf, int col, DecorationState *state)
{
- 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);
-
- marks_moved |= bufhl_mark_col_adjust(buf, lnum, endcol, 0, mincol-(endcol+1));
- // 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);
+ if (col <= state->col_until) {
+ return state->current;
}
-}
+ state->col_until = MAXCOL;
+ while (true) {
+ mtmark_t mark = marktree_itr_current(state->itr);
+ if (mark.row < 0 || mark.row > state->row) {
+ break;
+ } else if (mark.row == state->row && mark.col > col) {
+ state->col_until = mark.col-1;
+ break;
+ }
-// 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);
+ if ((mark.id&MARKTREE_END_FLAG)) {
+ // TODO(bfredl): check decorations flag
+ goto next_mark;
}
- kv_size(buf->b_extmark_move_space) = 0;
- return;
- }
+ mtpos_t endpos = marktree_lookup(buf->b_marktree,
+ mark.id|MARKTREE_END_FLAG, NULL);
- 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);
- }
+ ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index,
+ mark.id, false);
- *lp += amount;
- } else if (amount_after && *lp > line2) {
- *lp += amount_after;
+ if (endpos.row < mark.row
+ || (endpos.row == mark.row && endpos.col <= mark.col)) {
+ if (!kv_size(item->virt_text)) {
+ goto next_mark;
+ }
}
- })
- 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)
+ if (item && (item->hl_id > 0 || kv_size(item->virt_text))) {
+ int attr_id = item->hl_id > 0 ? syn_id2attr(item->hl_id) : 0;
+ VirtText *vt = kv_size(item->virt_text) ? &item->virt_text : NULL;
+ kv_push(state->active, ((HlRange){ mark.row, mark.col,
+ endpos.row, endpos.col,
+ attr_id, vt }));
+ }
-{
- 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);
+next_mark:
+ marktree_itr_next(buf->b_marktree, state->itr);
}
- // 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;
+ int attr = 0;
+ size_t j = 0;
+ for (size_t i = 0; i < kv_size(state->active); i++) {
+ HlRange item = kv_A(state->active, i);
+ bool active = false, keep = true;
+ if (item.end_row < state->row
+ || (item.end_row == state->row && item.end_col <= col)) {
+ if (!(item.start_row >= state->row && item.virt_text)) {
+ keep = false;
}
- 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 {
+ if (item.start_row < state->row
+ || (item.start_row == state->row && item.start_col <= col)) {
+ active = true;
+ if (item.end_row == state->row) {
+ state->col_until = MIN(state->col_until, item.end_col-1);
+ }
} 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);
+ if (item.start_row == state->row) {
+ state->col_until = MIN(state->col_until, item.start_col-1);
+ }
}
- 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);
+ if (active && item.attr_id > 0) {
+ attr = hl_combine_attr(attr, item.attr_id);
+ }
+ if (keep) {
+ kv_A(state->active, j++) = kv_A(state->active, i);
+ }
}
-
- return marks_moved;
+ kv_size(state->active) = j;
+ state->current = attr;
+ return attr;
}
-// Get reference to line in kbtree_t, allocating it if neccessary.
-ExtmarkLine *extmarkline_ref(buf_T *buf, linenr_T lnum, bool put)
+VirtText *extmark_decorations_virt_text(buf_T *buf, DecorationState *state)
{
- 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;
+ extmark_decorations_col(buf, MAXCOL, state);
+ for (size_t i = 0; i < kv_size(state->active); i++) {
+ HlRange item = kv_A(state->active, i);
+ if (item.start_row == state->row && item.virt_text) {
+ return item.virt_text;
}
- 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);
+ return NULL;
}
-
-
diff --git a/src/nvim/mark_extended.h b/src/nvim/mark_extended.h
index ee1da26875..f809148d9b 100644
--- a/src/nvim/mark_extended.h
+++ b/src/nvim/mark_extended.h
@@ -1,236 +1,57 @@
#ifndef NVIM_MARK_EXTENDED_H
#define NVIM_MARK_EXTENDED_H
+#include "nvim/buffer_defs.h"
#include "nvim/mark_extended_defs.h"
-#include "nvim/buffer_defs.h" // for buf_T
+#include "nvim/marktree.h"
+EXTERN int extmark_splice_pending INIT(= 0);
-// 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;
-
+typedef struct
+{
+ uint64_t ns_id;
+ uint64_t mark_id;
+ int row;
+ colnr_T col;
+} ExtmarkInfo;
-// adjust line numbers only, corresponding to mark_adjust call
-typedef struct {
- linenr_T line1;
- linenr_T line2;
- long amount;
- long amount_after;
-} Adjust;
+typedef kvec_t(ExtmarkInfo) ExtmarkArray;
-// 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
+ int start_row;
+ colnr_T start_col;
+ int oldextent_row;
+ colnr_T oldextent_col;
+ int newextent_row;
+ colnr_T newextent_col;
+} ExtmarkSplice;
+
+// adjust marks 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;
+ int start_row;
+ int start_col;
+ int extent_row;
+ int extent_col;
+ int new_row;
+ int new_col;
+} ExtmarkMove;
// extmark was updated
typedef struct {
- uint64_t ns_id;
- uint64_t mark_id;
- linenr_T old_lnum;
+ uint64_t mark; // raw mark id of the marktree
+ int old_row;
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;
+ int row;
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;
-
+} ExtmarkSavePos;
typedef enum {
- kLineAdjust,
- kColAdjust,
- kColAdjustDelete,
- kAdjustMove,
- kExtmarkSet,
- kExtmarkDel,
+ kExtmarkSplice,
+ kExtmarkMove,
kExtmarkUpdate,
- kExtmarkCopy,
- kExtmarkCopyPlace,
+ kExtmarkSavePos,
kExtmarkClear,
} UndoObjectType;
@@ -238,42 +59,32 @@ typedef enum {
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;
+ ExtmarkSplice splice;
+ ExtmarkMove move;
+ ExtmarkSavePos savepos;
} data;
};
-// For doing move of extmarks in substitutions
typedef struct {
- lpos_T startpos;
- lpos_T endpos;
- linenr_T lnum;
- int sublen;
-} ExtmarkSubSingle;
+ int start_row;
+ int start_col;
+ int end_row;
+ int end_col;
+ int attr_id;
+ VirtText *virt_text;
+} HlRange;
-// 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;
+ MarkTreeIter itr[1];
+ kvec_t(HlRange) active;
+ int top_row;
+ int row;
+ int col_until;
+ int current;
+ VirtText *virt_text;
+} DecorationState;
-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"
diff --git a/src/nvim/mark_extended_defs.h b/src/nvim/mark_extended_defs.h
index 565c599d06..439f7f0b36 100644
--- a/src/nvim/mark_extended_defs.h
+++ b/src/nvim/mark_extended_defs.h
@@ -2,53 +2,36 @@
#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 {
+ char *text;
+ int hl_id;
+} VirtTextChunk;
-typedef struct Extmark
+typedef kvec_t(VirtTextChunk) VirtText;
+#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE)
+
+typedef struct
{
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)
-
+ int hl_id; // highlight group
+ // TODO(bfredl): virt_text is pretty larger than the rest,
+ // pointer indirection?
+ VirtText virt_text;
+} ExtmarkItem;
typedef struct undo_object ExtmarkUndoObject;
typedef kvec_t(ExtmarkUndoObject) extmark_undo_vec_t;
+// 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;
#endif // NVIM_MARK_EXTENDED_DEFS_H
diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c
index 6ad283f5dc..52e602cd94 100644
--- a/src/nvim/marktree.c
+++ b/src/nvim/marktree.c
@@ -1095,6 +1095,7 @@ static void marktree_itr_fix_pos(MarkTree *b, MarkTreeIter *itr)
void marktree_check(MarkTree *b)
{
+#ifndef NDEBUG
if (b->root == NULL) {
assert(b->n_keys == 0);
assert(b->n_nodes == 0);
@@ -1107,9 +1108,15 @@ void marktree_check(MarkTree *b)
size_t nkeys = check_node(b, b->root, &dummy, &last_right);
assert(b->n_keys == nkeys);
assert(b->n_keys == map_size(b->id2node));
+#else
+ // Do nothing, as assertions are required
+ (void)b;
+#endif
}
-size_t check_node(MarkTree *b, mtnode_t *x, mtpos_t *last, bool *last_right)
+#ifndef NDEBUG
+static size_t check_node(MarkTree *b, mtnode_t *x,
+ mtpos_t *last, bool *last_right)
{
assert(x->n <= 2 * T - 1);
// TODO(bfredl): too strict if checking "in repair" post-delete tree.
@@ -1153,6 +1160,7 @@ size_t check_node(MarkTree *b, mtnode_t *x, mtpos_t *last, bool *last_right)
}
return n_keys;
}
+#endif
char *mt_inspect_rec(MarkTree *b)
{
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 6a621cdaa6..5da81dbff6 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -31,6 +31,7 @@
#include "nvim/indent.h"
#include "nvim/log.h"
#include "nvim/mark.h"
+#include "nvim/mark_extended.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -307,15 +308,6 @@ 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);
}
}
@@ -352,6 +344,8 @@ static void shift_block(oparg_T *oap, int amount)
char_u *const oldp = get_cursor_line_ptr();
+ int startcol, oldlen, newlen;
+
if (!left) {
/*
* 1. Get start vcol
@@ -361,6 +355,7 @@ static void shift_block(oparg_T *oap, int amount)
*/
total += bd.pre_whitesp; // all virtual WS up to & incl a split TAB
colnr_T ws_vcol = bd.start_vcol - bd.pre_whitesp;
+ char_u * old_textstart = bd.textstart;
if (bd.startspaces) {
if (has_mbyte) {
if ((*mb_ptr2len)(bd.textstart) == 1) {
@@ -387,14 +382,19 @@ static void shift_block(oparg_T *oap, int amount)
j = ((ws_vcol % p_ts) + total) % p_ts; /* number of spp */
else
j = total;
- /* if we're splitting a TAB, allow for it */
- bd.textcol -= bd.pre_whitesp_c - (bd.startspaces != 0);
+
+ // if we're splitting a TAB, allow for it
+ int col_pre = bd.pre_whitesp_c - (bd.startspaces != 0);
+ bd.textcol -= col_pre;
const int len = (int)STRLEN(bd.textstart) + 1;
int col = bd.textcol + i +j + len;
assert(col >= 0);
newp = (char_u *)xmalloc((size_t)col);
memset(newp, NUL, (size_t)col);
memmove(newp, oldp, (size_t)bd.textcol);
+ startcol = bd.textcol;
+ oldlen = (int)(bd.textstart-old_textstart) + col_pre;
+ newlen = i+j;
memset(newp + bd.textcol, TAB, (size_t)i);
memset(newp + bd.textcol + i, ' ', (size_t)j);
/* the end */
@@ -478,7 +478,10 @@ static void shift_block(oparg_T *oap, int amount)
// - the rest of the line, pointed to by non_white.
new_line_len = verbatim_diff + fill + STRLEN(non_white) + 1;
- newp = (char_u *) xmalloc(new_line_len);
+ newp = (char_u *)xmalloc(new_line_len);
+ startcol = (int)verbatim_diff;
+ oldlen = bd.textcol + (int)(non_white - bd.textstart) - (int)verbatim_diff;
+ newlen = (int)fill;
memmove(newp, oldp, verbatim_diff);
memset(newp + verbatim_diff, ' ', fill);
STRMOVE(newp + verbatim_diff + fill, non_white);
@@ -486,13 +489,12 @@ static void shift_block(oparg_T *oap, int amount)
// replace the line
ml_replace(curwin->w_cursor.lnum, newp, false);
changed_bytes(curwin->w_cursor.lnum, (colnr_T)bd.textcol);
+ extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, startcol,
+ 0, oldlen, 0, newlen,
+ kExtmarkUndo);
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);
}
/*
@@ -561,6 +563,7 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def
// copy up to shifted part
memmove(newp, oldp, (size_t)offset);
oldp += offset;
+ int startcol = offset;
// insert pre-padding
memset(newp + offset, ' ', (size_t)spaces);
@@ -569,6 +572,7 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def
memmove(newp + offset + spaces, s, s_len);
offset += (int)s_len;
+ int skipped = 0;
if (spaces && !bdp->is_short) {
// insert post-padding
memset(newp + offset + spaces, ' ', (size_t)(p_ts - spaces));
@@ -576,6 +580,7 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def
oldp++;
// We allowed for that TAB, remember this now
count++;
+ skipped = 1;
}
if (spaces > 0)
@@ -583,6 +588,9 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def
STRMOVE(newp + offset, oldp);
ml_replace(lnum, newp, false);
+ extmark_splice(curbuf, (int)lnum-1, startcol,
+ 0, skipped,
+ 0, offset-startcol, kExtmarkUndo);
if (lnum == oap->end.lnum) {
/* Set "']" mark to the end of the block instead of the end of
@@ -642,14 +650,6 @@ void op_reindent(oparg_T *oap, Indenter how)
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;
@@ -1517,6 +1517,11 @@ int op_delete(oparg_T *oap)
STRMOVE(newp + bd.textcol + bd.startspaces + bd.endspaces, oldp);
// replace the line
ml_replace(lnum, newp, false);
+
+ extmark_splice(curbuf, (int)lnum-1, bd.textcol,
+ 0, bd.textlen,
+ 0, bd.startspaces+bd.endspaces,
+ kExtmarkUndo);
}
check_cursor_col();
@@ -1633,6 +1638,8 @@ int op_delete(oparg_T *oap)
(linenr_T)(curwin->w_cursor.lnum + oap->line_count)) == FAIL)
return FAIL;
+ curbuf_splice_pending++;
+ pos_T startpos = curwin->w_cursor; // start position for delete
truncate_line(true); // delete from cursor to end of line
curpos = curwin->w_cursor; // remember curwin->w_cursor
@@ -1646,6 +1653,9 @@ int op_delete(oparg_T *oap)
oap->op_type == OP_DELETE && !oap->is_VIsual);
curwin->w_cursor = curpos; // restore curwin->w_cursor
(void)do_join(2, false, false, false, false);
+ curbuf_splice_pending--;
+ extmark_splice(curbuf, (int)startpos.lnum-1, startpos.col,
+ (int)oap->line_count-1, n, 0, 0, kExtmarkUndo);
}
}
@@ -1660,19 +1670,6 @@ setmarks:
}
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);
- }
- }
return OK;
}
@@ -1695,8 +1692,11 @@ static void mb_adjust_opend(oparg_T *oap)
*/
static inline void pbyte(pos_T lp, int c)
{
- assert(c <= UCHAR_MAX);
- *(ml_get_buf(curbuf, lp.lnum, true) + lp.col) = (char_u)c;
+ assert(c <= UCHAR_MAX);
+ *(ml_get_buf(curbuf, lp.lnum, true) + lp.col) = (char_u)c;
+ if (!curbuf_splice_pending) {
+ extmark_splice(curbuf, (int)lp.lnum-1, lp.col, 0, 1, 0, 1, kExtmarkUndo);
+ }
}
// Replace the character under the cursor with "c".
@@ -1817,6 +1817,7 @@ int op_replace(oparg_T *oap, int c)
size_t after_p_len = 0;
int col = oldlen - bd.textcol - bd.textlen + 1;
assert(col >= 0);
+ int newrows = 0, newcols = 0;
if (had_ctrl_v_cr || (c != '\r' && c != '\n')) {
// strlen(newp) at this point
int newp_len = bd.textcol + bd.startspaces;
@@ -1829,21 +1830,27 @@ int op_replace(oparg_T *oap, int c)
newp_len += bd.endspaces;
// copy the part after the changed part
memmove(newp + newp_len, oldp, (size_t)col);
- }
+ }
+ newcols = newp_len - bd.textcol;
} else {
// Replacing with \r or \n means splitting the line.
after_p_len = (size_t)col;
after_p = (char_u *)xmalloc(after_p_len);
memmove(after_p, oldp, after_p_len);
+ newrows = 1;
}
// replace the line
ml_replace(curwin->w_cursor.lnum, newp, false);
+ linenr_T baselnum = curwin->w_cursor.lnum;
if (after_p != NULL) {
ml_append(curwin->w_cursor.lnum++, after_p, (int)after_p_len, false);
appended_lines_mark(curwin->w_cursor.lnum, 1L);
oap->end.lnum++;
xfree(after_p);
}
+ extmark_splice(curbuf, (int)baselnum-1, bd.textcol,
+ 0, bd.textlen,
+ newrows, newcols, kExtmarkUndo);
}
} else {
// Characterwise or linewise motion replace.
@@ -1856,6 +1863,8 @@ int op_replace(oparg_T *oap, int c)
} else if (!oap->inclusive)
dec(&(oap->end));
+ // TODO(bfredl): we could batch all the splicing
+ // done on the same line, at least
while (ltoreq(curwin->w_cursor, oap->end)) {
n = gchar_cursor();
if (n != NUL) {
@@ -2262,10 +2271,6 @@ 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);
- }
}
/*
@@ -2380,6 +2385,9 @@ int op_change(oparg_T *oap)
oldp += bd.textcol;
STRMOVE(newp + offset, oldp);
ml_replace(linenr, newp, false);
+ extmark_splice(curbuf, (int)linenr-1, bd.textcol,
+ 0, 0,
+ 0, vpos.coladd+(int)ins_len, kExtmarkUndo);
}
}
check_cursor();
@@ -2735,28 +2743,6 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg)
recursive = false;
}
-
-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!
@@ -3176,6 +3162,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
assert(columns >= 0);
memmove(ptr, oldp + bd.textcol + delcount, (size_t)columns);
ml_replace(curwin->w_cursor.lnum, newp, false);
+ extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, bd.textcol,
+ 0, delcount,
+ 0, (int)totlen,
+ kExtmarkUndo);
++curwin->w_cursor.lnum;
if (i == 0)
@@ -3277,6 +3267,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
if (totlen && (restart_edit != 0 || (flags & PUT_CURSEND)))
++curwin->w_cursor.col;
changed_bytes(lnum, col);
+ extmark_splice(curbuf, (int)lnum-1, col,
+ 0, 0,
+ 0, (int)totlen, kExtmarkUndo);
} else {
// Insert at least one line. When y_type is kMTCharWise, break the first
// line in two.
@@ -3332,13 +3325,22 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
first_indent = FALSE;
} else if ((indent = get_indent() + indent_diff) < 0)
indent = 0;
- (void)set_indent(indent, 0);
+ (void)set_indent(indent, SIN_NOMARK);
curwin->w_cursor = old_pos;
/* remember how many chars were removed */
if (cnt == count && i == y_size - 1)
lendiff -= (int)STRLEN(ml_get(lnum));
}
}
+
+ if (y_type == kMTCharWise) {
+ extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0,
+ (int)y_size-1, (int)STRLEN(y_array[y_size-1]),
+ kExtmarkUndo);
+ } else if (y_type == kMTLineWise && flags & PUT_LINE_SPLIT) {
+ extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0,
+ (int)y_size+1, 0, kExtmarkUndo);
+ }
}
error:
@@ -3352,8 +3354,10 @@ error:
// can't be marks there.
if (curbuf->b_op_start.lnum + (y_type == kMTCharWise) - 1 + nr_lines
< curbuf->b_ml.ml_line_count) {
+ ExtmarkOp kind = (y_type == kMTLineWise && !(flags & PUT_LINE_SPLIT))
+ ? kExtmarkUndo : kExtmarkNOOP;
mark_adjust(curbuf->b_op_start.lnum + (y_type == kMTCharWise),
- (linenr_T)MAXLNUM, nr_lines, 0L, false, kExtmarkUndo);
+ (linenr_T)MAXLNUM, nr_lines, 0L, kind);
}
// note changed text for displaying and folding
@@ -3415,9 +3419,7 @@ 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);
-}
+} // NOLINT(readability/fn_size)
/*
* When the cursor is on the NUL past the end of the line and it should not be
@@ -3779,6 +3781,13 @@ int do_join(size_t count,
}
}
}
+
+ if (t > 0 && curbuf_splice_pending == 0) {
+ extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, sumsize,
+ 1, (int)(curr- curr_start),
+ 0, spaces[t],
+ kExtmarkUndo);
+ }
currsize = (int)STRLEN(curr);
sumsize += currsize + spaces[t];
endcurr1 = endcurr2 = NUL;
@@ -3814,6 +3823,8 @@ int do_join(size_t count,
* should not really be a problem.
*/
+ curbuf_splice_pending++;
+
for (t = (linenr_T)count - 1;; t--) {
cend -= currsize;
memmove(cend, curr, (size_t)currsize);
@@ -3830,8 +3841,7 @@ int do_join(size_t count,
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(lnum, mincol, lnum_amount, col_amount, spaces_removed);
if (t == 0) {
break;
@@ -3867,6 +3877,7 @@ int do_join(size_t count,
curwin->w_cursor.lnum++;
del_lines((long)count - 1, false);
curwin->w_cursor.lnum = t;
+ curbuf_splice_pending--;
/*
* Set the cursor column:
@@ -4265,14 +4276,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, kExtmarkNOOP);
+ (long)-next_leader_len, 0);
} 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, kExtmarkNOOP);
+ (colnr_T)0, 0L, (long)-indent, 0);
}
}
curwin->w_cursor.lnum--;
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 0612575e67..24ad012201 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -87,6 +87,7 @@
#include "nvim/highlight.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"
@@ -622,6 +623,9 @@ bool win_cursorline_standout(const win_T *wp)
|| (wp->w_p_cole > 0 && (VIsual_active || !conceal_cursor_line(wp)));
}
+static DecorationState decorations;
+bool decorations_active = false;
+
/*
* Update a single window.
*
@@ -1221,6 +1225,7 @@ static void win_update(win_T *wp)
: (wp->w_topline + wp->w_height_inner));
args.items[0] = WINDOW_OBJ(wp->handle);
args.items[1] = BUFFER_OBJ(buf->handle);
+ // TODO(bfredl): we are not using this, but should be first drawn line?
args.items[2] = INTEGER_OBJ(wp->w_topline-1);
args.items[3] = INTEGER_OBJ(knownmax);
// TODO(bfredl): we could allow this callback to change mod_top, mod_bot.
@@ -1232,6 +1237,8 @@ static void win_update(win_T *wp)
}
}
+ decorations_active = extmark_decorations_reset(buf, &decorations);
+
for (;; ) {
/* stop updating when reached the end of the window (check for _past_
* the end of the window is at the end of the loop) */
@@ -2250,8 +2257,7 @@ win_line (
int prev_c1 = 0; // first composing char for prev_c
bool search_attr_from_match = false; // if search_attr is from :match
- BufhlLineInfo bufhl_info; // bufhl data for this line
- bool has_bufhl = false; // this buffer has highlight matches
+ bool has_decorations = false; // this buffer has decorations
bool do_virttext = false; // draw virtual text for this line
/* draw_state: items that are drawn in sequence: */
@@ -2315,14 +2321,12 @@ win_line (
}
}
- if (bufhl_start_line(wp->w_buffer, lnum, &bufhl_info)) {
- if (kv_size(bufhl_info.line->items)) {
- has_bufhl = true;
+ if (decorations_active) {
+ has_decorations = extmark_decorations_line(wp->w_buffer, lnum-1,
+ &decorations);
+ if (has_decorations) {
extra_check = true;
}
- if (kv_size(bufhl_info.line->virt_text)) {
- do_virttext = true;
- }
}
// Check for columns to display for 'colorcolumn'.
@@ -3515,19 +3519,25 @@ win_line (
char_attr = hl_combine_attr(spell_attr, char_attr);
}
- if (has_bufhl && v > 0) {
- int bufhl_attr = bufhl_get_attr(&bufhl_info, (colnr_T)v);
- if (bufhl_attr != 0) {
+ if (has_decorations && v > 0) {
+ int extmark_attr = extmark_decorations_col(wp->w_buffer, (colnr_T)v-1,
+ &decorations);
+ if (extmark_attr != 0) {
if (!attr_pri) {
- char_attr = hl_combine_attr(char_attr, bufhl_attr);
+ char_attr = hl_combine_attr(char_attr, extmark_attr);
} else {
- char_attr = hl_combine_attr(bufhl_attr, char_attr);
+ char_attr = hl_combine_attr(extmark_attr, char_attr);
}
}
}
+ // TODO(bfredl): luahl should reuse the "active decorations" buffer
if (buf->b_luahl && v > 0 && v < (long)lua_attr_bufsize+1) {
- char_attr = hl_combine_attr(char_attr, lua_attr_buf[v-1]);
+ if (!attr_pri) {
+ char_attr = hl_combine_attr(char_attr, lua_attr_buf[v-1]);
+ } else {
+ char_attr = hl_combine_attr(lua_attr_buf[v-1], char_attr);
+ }
}
if (wp->w_buffer->terminal) {
@@ -4008,6 +4018,19 @@ win_line (
if (draw_color_col)
draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
+ VirtText virt_text = KV_INITIAL_VALUE;
+ if (luatext) {
+ kv_push(virt_text, ((VirtTextChunk){ .text = luatext, .hl_id = 0 }));
+ do_virttext = true;
+ } else if (has_decorations) {
+ VirtText *vp = extmark_decorations_virt_text(wp->w_buffer,
+ &decorations);
+ if (vp) {
+ virt_text = *vp;
+ do_virttext = true;
+ }
+ }
+
if (((wp->w_p_cuc
&& (int)wp->w_virtcol >= VCOL_HLC - eol_hl_off
&& (int)wp->w_virtcol <
@@ -4018,14 +4041,6 @@ win_line (
int rightmost_vcol = 0;
int i;
- VirtText virt_text;
- if (luatext) {
- virt_text = (VirtText)KV_INITIAL_VALUE;
- kv_push(virt_text, ((VirtTextChunk){ .text = luatext, .hl_id = 0 }));
- } else {
- virt_text = do_virttext ? bufhl_info.line->virt_text
- : (VirtText)KV_INITIAL_VALUE;
- }
size_t virt_pos = 0;
LineState s = LINE_STATE((char_u *)"");
int virt_attr = 0;
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 980399a3ef..fda647106d 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -2244,7 +2244,7 @@ static void u_undoredo(int undo, bool do_buf_event)
// Adjust marks
if (oldsize != newsize) {
mark_adjust(top + 1, top + oldsize, (long)MAXLNUM,
- (long)newsize - (long)oldsize, false, kExtmarkNOOP);
+ (long)newsize - (long)oldsize, kExtmarkNOOP);
if (curbuf->b_op_start.lnum > top + oldsize) {
curbuf->b_op_start.lnum += newsize - oldsize;
}
diff --git a/test/functional/api/mark_extended_spec.lua b/test/functional/api/mark_extended_spec.lua
index bf910568b1..8aa8ed07c5 100644
--- a/test/functional/api/mark_extended_spec.lua
+++ b/test/functional/api/mark_extended_spec.lua
@@ -11,6 +11,11 @@ local insert = helpers.insert
local feed = helpers.feed
local clear = helpers.clear
local command = helpers.command
+local meths = helpers.meths
+
+local function expect(contents)
+ return eq(contents, helpers.curbuf_contents())
+end
local function check_undo_redo(ns, mark, sr, sc, er, ec) --s = start, e = end
local rv = curbufmeths.get_extmark_by_id(ns, mark)
@@ -37,9 +42,36 @@ local function get_extmarks(ns_id, start, end_, opts)
return curbufmeths.get_extmarks(ns_id, start, end_, opts)
end
+local function batch_set(ns_id, positions)
+ local ids = {}
+ for _, pos in ipairs(positions) do
+ table.insert(ids, set_extmark(ns_id, 0, pos[1], pos[2]))
+ end
+ return ids
+end
+
+local function batch_check(ns_id, ids, positions)
+ local actual, expected = {}, {}
+ for i,id in ipairs(ids) do
+ expected[id] = positions[i]
+ end
+ for _, mark in pairs(get_extmarks(ns_id, 0, -1, {})) do
+ actual[mark[1]] = {mark[2], mark[3]}
+ end
+ eq(expected, actual)
+end
+
+local function batch_check_undo_redo(ns_id, ids, before, after)
+ batch_check(ns_id, ids, after)
+ feed("u")
+ batch_check(ns_id, ids, before)
+ feed("<c-r>")
+ batch_check(ns_id, ids, after)
+end
+
describe('API/extmarks', function()
local screen
- local marks, positions, ns_string2, ns_string, init_text, row, col
+ local marks, positions, init_text, row, col
local ns, ns2
before_each(function()
@@ -47,22 +79,18 @@ describe('API/extmarks', function()
marks = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
positions = {{0, 0,}, {0, 2}, {0, 3}}
- ns_string = "my-fancy-plugin"
- ns_string2 = "my-fancy-plugin2"
init_text = "12345"
row = 0
col = 2
clear()
- screen = Screen.new(15, 10)
- screen:attach()
insert(init_text)
- ns = request('nvim_create_namespace', ns_string)
- ns2 = request('nvim_create_namespace', ns_string2)
+ ns = request('nvim_create_namespace', "my-fancy-plugin")
+ ns2 = request('nvim_create_namespace', "my-fancy-plugin2")
end)
- it('adds, updates and deletes marks #extmarks', function()
+ it('adds, updates and deletes marks', function()
local rv = set_extmark(ns, marks[1], positions[1][1], positions[1][2])
eq(marks[1], rv)
rv = curbufmeths.get_extmark_by_id(ns, marks[1])
@@ -92,7 +120,7 @@ describe('API/extmarks', function()
eq(false, curbufmeths.del_extmark(ns, 1000))
end)
- it('can clear a specific namespace range #extmarks', function()
+ it('can clear a specific namespace range', function()
set_extmark(ns, 1, 0, 1)
set_extmark(ns2, 1, 0, 1)
-- force a new undo buffer
@@ -102,13 +130,13 @@ describe('API/extmarks', function()
eq({}, get_extmarks(ns2, {0, 0}, {-1, -1}))
feed('u')
eq({{1, 0, 1}}, get_extmarks(ns, {0, 0}, {-1, -1}))
- eq({{1, 0, 1}}, get_extmarks(ns2, {0, 0}, {-1, -1}))
+ eq({}, get_extmarks(ns2, {0, 0}, {-1, -1}))
feed('<c-r>')
eq({{1, 0, 1}}, get_extmarks(ns, {0, 0}, {-1, -1}))
eq({}, get_extmarks(ns2, {0, 0}, {-1, -1}))
end)
- it('can clear a namespace range using 0,-1 #extmarks', function()
+ it('can clear a namespace range using 0,-1', function()
set_extmark(ns, 1, 0, 1)
set_extmark(ns2, 1, 0, 1)
-- force a new undo buffer
@@ -117,14 +145,16 @@ describe('API/extmarks', function()
eq({}, get_extmarks(ns, {0, 0}, {-1, -1}))
eq({}, get_extmarks(ns2, {0, 0}, {-1, -1}))
feed('u')
- eq({{1, 0, 1}}, get_extmarks(ns, {0, 0}, {-1, -1}))
- eq({{1, 0, 1}}, get_extmarks(ns2, {0, 0}, {-1, -1}))
+ eq({}, get_extmarks(ns, {0, 0}, {-1, -1}))
+ eq({}, get_extmarks(ns2, {0, 0}, {-1, -1}))
feed('<c-r>')
eq({}, get_extmarks(ns, {0, 0}, {-1, -1}))
eq({}, get_extmarks(ns2, {0, 0}, {-1, -1}))
end)
- it('querying for information and ranges #extmarks', function()
+ it('querying for information and ranges', function()
+ --marks = {1, 2, 3}
+ --positions = {{0, 0,}, {0, 2}, {0, 3}}
-- add some more marks
for i, m in ipairs(marks) do
if positions[i] ~= nil then
@@ -242,7 +272,7 @@ describe('API/extmarks', function()
eq({{marks[1], positions[1][1], positions[1][2]}}, rv)
end)
- it('querying for information with limit #extmarks', function()
+ it('querying for information with limit', function()
-- add some more marks
for i, m in ipairs(marks) do
if positions[i] ~= nil then
@@ -267,7 +297,7 @@ describe('API/extmarks', function()
eq(3, table.getn(rv))
end)
- it('get_marks works when mark col > upper col #extmarks', function()
+ it('get_marks works when mark col > upper col', function()
feed('A<cr>12345<esc>')
feed('A<cr>12345<esc>')
set_extmark(ns, 10, 0, 2) -- this shouldn't be found
@@ -281,7 +311,7 @@ describe('API/extmarks', function()
get_extmarks(ns, {0, 3}, {2, 0}))
end)
- it('get_marks works in reverse when mark col < lower col #extmarks', function()
+ it('get_marks works in reverse when mark col < lower col', function()
feed('A<cr>12345<esc>')
feed('A<cr>12345<esc>')
set_extmark(ns, 10, 0, 1) -- this shouldn't be found
@@ -296,27 +326,27 @@ describe('API/extmarks', function()
rv)
end)
- it('get_marks limit=0 returns nothing #extmarks', function()
+ it('get_marks limit=0 returns nothing', function()
set_extmark(ns, marks[1], positions[1][1], positions[1][2])
local rv = get_extmarks(ns, {-1, -1}, {-1, -1}, {limit=0})
eq({}, rv)
end)
- it('marks move with line insertations #extmarks', function()
+ it('marks move with line insertations', function()
set_extmark(ns, marks[1], 0, 0)
feed("yyP")
check_undo_redo(ns, marks[1], 0, 0, 1, 0)
end)
- it('marks move with multiline insertations #extmarks', function()
+ it('marks move with multiline insertations', function()
feed("a<cr>22<cr>33<esc>")
set_extmark(ns, marks[1], 1, 1)
feed('ggVGyP')
check_undo_redo(ns, marks[1], 1, 1, 4, 1)
end)
- it('marks move with line join #extmarks', function()
+ it('marks move with line join', function()
-- do_join in ops.c
feed("a<cr>222<esc>")
set_extmark(ns, marks[1], 1, 0)
@@ -324,7 +354,9 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[1], 1, 0, 0, 6)
end)
- it('join works when no marks are present #extmarks', function()
+ it('join works when no marks are present', function()
+ screen = Screen.new(15, 10)
+ screen:attach()
feed("a<cr>1<esc>")
feed('kJ')
-- This shouldn't seg fault
@@ -342,7 +374,7 @@ describe('API/extmarks', function()
]])
end)
- it('marks move with multiline join #extmarks', function()
+ it('marks move with multiline join', function()
-- do_join in ops.c
feed("a<cr>222<cr>333<cr>444<esc>")
set_extmark(ns, marks[1], 3, 0)
@@ -350,14 +382,14 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[1], 3, 0, 1, 8)
end)
- it('marks move with line deletes #extmarks', function()
+ it('marks move with line deletes', function()
feed("a<cr>222<cr>333<cr>444<esc>")
set_extmark(ns, marks[1], 2, 1)
feed('ggjdd')
check_undo_redo(ns, marks[1], 2, 1, 1, 1)
end)
- it('marks move with multiline deletes #extmarks', function()
+ it('marks move with multiline deletes', function()
feed("a<cr>222<cr>333<cr>444<esc>")
set_extmark(ns, marks[1], 3, 0)
feed('gg2dd')
@@ -367,7 +399,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[1], 3, 0, 0, 0)
end)
- it('marks move with open line #extmarks', function()
+ it('marks move with open line', function()
-- open_line in misc1.c
-- testing marks below are also moved
feed("yyP")
@@ -381,8 +413,10 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[2], 2, 4, 3, 4)
end)
- it('marks move with char inserts #extmarks', function()
+ it('marks move with char inserts', function()
-- insertchar in edit.c (the ins_str branch)
+ screen = Screen.new(15, 10)
+ screen:attach()
set_extmark(ns, marks[1], 0, 3)
feed('0')
insert('abc')
@@ -400,11 +434,11 @@ describe('API/extmarks', function()
]])
local rv = curbufmeths.get_extmark_by_id(ns, marks[1])
eq({0, 6}, rv)
- -- check_undo_redo(ns, marks[1], 0, 2, 0, 5)
+ check_undo_redo(ns, marks[1], 0, 3, 0, 6)
end)
-- gravity right as definted in tk library
- it('marks have gravity right #extmarks', function()
+ it('marks have gravity right', function()
-- insertchar in edit.c (the ins_str branch)
set_extmark(ns, marks[1], 0, 2)
feed('03l')
@@ -417,7 +451,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[1], 0, 2, 0, 2)
end)
- it('we can insert multibyte chars #extmarks', function()
+ it('we can insert multibyte chars', function()
-- insertchar in edit.c
feed('a<cr>12345<esc>')
set_extmark(ns, marks[1], 1, 2)
@@ -426,7 +460,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[1], 1, 2, 1, 5)
end)
- it('marks move with blockwise inserts #extmarks', function()
+ it('marks move with blockwise inserts', function()
-- op_insert in ops.c
feed('a<cr>12345<esc>')
set_extmark(ns, marks[1], 1, 2)
@@ -434,7 +468,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[1], 1, 2, 1, 3)
end)
- it('marks move with line splits (using enter) #extmarks', function()
+ it('marks move with line splits (using enter)', function()
-- open_line in misc1.c
-- testing marks below are also moved
feed("yyP")
@@ -445,14 +479,14 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[2], 1, 4, 2, 4)
end)
- it('marks at last line move on insert new line #extmarks', function()
+ it('marks at last line move on insert new line', function()
-- open_line in misc1.c
set_extmark(ns, marks[1], 0, 4)
feed('0i<cr><esc>')
check_undo_redo(ns, marks[1], 0, 4, 1, 4)
end)
- it('yet again marks move with line splits #extmarks', function()
+ it('yet again marks move with line splits', function()
-- the first test above wasn't catching all errors..
feed("A67890<esc>")
set_extmark(ns, marks[1], 0, 4)
@@ -460,7 +494,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[1], 0, 4, 1, 0)
end)
- it('and one last time line splits... #extmarks', function()
+ it('and one last time line splits...', function()
set_extmark(ns, marks[1], 0, 1)
set_extmark(ns, marks[2], 0, 2)
feed("02li<cr><esc>")
@@ -468,7 +502,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[2], 0, 2, 1, 0)
end)
- it('multiple marks move with mark splits #extmarks', function()
+ it('multiple marks move with mark splits', function()
set_extmark(ns, marks[1], 0, 1)
set_extmark(ns, marks[2], 0, 3)
feed("0li<cr><esc>")
@@ -476,21 +510,21 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[2], 0, 3, 1, 2)
end)
- it('deleting right before a mark works #extmarks', function()
+ it('deleting right before a mark works', function()
-- op_delete in ops.c
set_extmark(ns, marks[1], 0, 2)
feed('0lx')
check_undo_redo(ns, marks[1], 0, 2, 0, 1)
end)
- it('deleting on a mark works #extmarks', function()
+ it('deleting right after a mark works', function()
-- op_delete in ops.c
set_extmark(ns, marks[1], 0, 2)
feed('02lx')
check_undo_redo(ns, marks[1], 0, 2, 0, 2)
end)
- it('marks move with char deletes #extmarks', function()
+ it('marks move with char deletes', function()
-- op_delete in ops.c
set_extmark(ns, marks[1], 0, 2)
feed('02dl')
@@ -500,7 +534,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[1], 0, 0, 0, 0)
end)
- it('marks move with char deletes over a range #extmarks', function()
+ it('marks move with char deletes over a range', function()
-- op_delete in ops.c
set_extmark(ns, marks[1], 0, 2)
set_extmark(ns, marks[2], 0, 3)
@@ -513,7 +547,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[2], 0, 3, 0, 3)
end)
- it('deleting marks at end of line works #extmarks', function()
+ it('deleting marks at end of line works', function()
-- mark_extended.c/extmark_col_adjust_delete
set_extmark(ns, marks[1], 0, 4)
feed('$x')
@@ -525,7 +559,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[1], 0, 4, 0, 4)
end)
- it('marks move with blockwise deletes #extmarks', function()
+ it('marks move with blockwise deletes', function()
-- op_delete in ops.c
feed('a<cr>12345<esc>')
set_extmark(ns, marks[1], 1, 4)
@@ -533,7 +567,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[1], 1, 4, 1, 1)
end)
- it('marks move with blockwise deletes over a range #extmarks', function()
+ it('marks move with blockwise deletes over a range', function()
-- op_delete in ops.c
feed('a<cr>12345<esc>')
set_extmark(ns, marks[1], 0, 1)
@@ -550,7 +584,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[3], 1, 2, 1, 2)
end)
- it('works with char deletes over multilines #extmarks', function()
+ it('works with char deletes over multilines', function()
feed('a<cr>12345<cr>test-me<esc>')
set_extmark(ns, marks[1], 2, 5)
feed('gg')
@@ -558,7 +592,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[1], 2, 5, 0, 0)
end)
- it('marks outside of deleted range move with visual char deletes #extmarks', function()
+ it('marks outside of deleted range move with visual char deletes', function()
-- op_delete in ops.c
set_extmark(ns, marks[1], 0, 3)
feed('0vx<esc>')
@@ -577,7 +611,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[1], 0, 0, 0, 0)
end)
- it('marks outside of deleted range move with char deletes #extmarks', function()
+ it('marks outside of deleted range move with char deletes', function()
-- op_delete in ops.c
set_extmark(ns, marks[1], 0, 3)
feed('0x<esc>')
@@ -597,7 +631,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[1], 0, 3, 0, 3)
end)
- it('marks move with P(backward) paste #extmarks', function()
+ it('marks move with P(backward) paste', function()
-- do_put in ops.c
feed('0iabc<esc>')
set_extmark(ns, marks[1], 0, 7)
@@ -605,15 +639,15 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[1], 0, 7, 0, 15)
end)
- it('marks move with p(forward) paste #extmarks', function()
+ it('marks move with p(forward) paste', function()
-- do_put in ops.c
feed('0iabc<esc>')
set_extmark(ns, marks[1], 0, 7)
feed('0veyp')
- check_undo_redo(ns, marks[1], 0, 7, 0, 14)
+ check_undo_redo(ns, marks[1], 0, 7, 0, 15)
end)
- it('marks move with blockwise P(backward) paste #extmarks', function()
+ it('marks move with blockwise P(backward) paste', function()
-- do_put in ops.c
feed('a<cr>12345<esc>')
set_extmark(ns, marks[1], 1, 4)
@@ -621,42 +655,84 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[1], 1, 4, 1, 7)
end)
- it('marks move with blockwise p(forward) paste #extmarks', function()
+ it('marks move with blockwise p(forward) paste', function()
-- do_put in ops.c
feed('a<cr>12345<esc>')
set_extmark(ns, marks[1], 1, 4)
feed('<c-v>hhkyp<esc>')
- check_undo_redo(ns, marks[1], 1, 4, 1, 6)
+ check_undo_redo(ns, marks[1], 1, 4, 1, 7)
end)
- it('replace works #extmarks', function()
+ describe('multiline regions', function()
+ before_each(function()
+ feed('dd')
+ -- Achtung: code has been spiced with some unicode,
+ -- to make life more interesting.
+ -- luacheck whines about TABs inside strings for whatever reason.
+ -- luacheck: push ignore 621
+ insert([[
+ static int nlua_rpcrequest(lua_State *lstate)
+ {
+ Ïf (!nlua_is_deferred_safe(lstate)) {
+ // strictly not allowed
+ Яetörn luaL_error(lstate, e_luv_api_disabled, "rpcrequest");
+ }
+ return nlua_rpc(lstate, true);
+ }]])
+ -- luacheck: pop
+ end)
+
+ it('delete', function()
+ local pos1 = {
+ {2, 4}, {2, 12}, {2, 13}, {2, 14}, {2, 25},
+ {4, 8}, {4, 10}, {4, 20},
+ {5, 3}, {6, 10}
+ }
+ local ids = batch_set(ns, pos1)
+ batch_check(ns, ids, pos1)
+ feed('3Gfiv2+ftd')
+ batch_check_undo_redo(ns, ids, pos1, {
+ {2, 4}, {2, 12}, {2, 13}, {2, 13}, {2, 13},
+ {2, 13}, {2, 15}, {2, 25},
+ {3, 3}, {4, 10}
+ })
+ end)
+
+ -- TODO(bfredl): add more tests!
+ end)
+
+ it('replace works', function()
set_extmark(ns, marks[1], 0, 2)
feed('0r2')
check_undo_redo(ns, marks[1], 0, 2, 0, 2)
end)
- it('blockwise replace works #extmarks', function()
+ it('blockwise replace works', function()
feed('a<cr>12345<esc>')
set_extmark(ns, marks[1], 0, 2)
feed('0<c-v>llkr1<esc>')
- check_undo_redo(ns, marks[1], 0, 2, 0, 2)
+ check_undo_redo(ns, marks[1], 0, 2, 0, 3)
end)
- it('shift line #extmarks', function()
+ it('shift line', function()
-- shift_line in ops.c
feed(':set shiftwidth=4<cr><esc>')
set_extmark(ns, marks[1], 0, 2)
feed('0>>')
check_undo_redo(ns, marks[1], 0, 2, 0, 6)
+ expect(' 12345')
feed('>>')
- check_undo_redo(ns, marks[1], 0, 6, 0, 10)
+ -- this is counter-intuitive. But what happens
+ -- is that 4 spaces gets extended to one tab (== 8 spaces)
+ check_undo_redo(ns, marks[1], 0, 6, 0, 3)
+ expect('\t12345')
feed('<LT><LT>') -- have to escape, same as <<
- check_undo_redo(ns, marks[1], 0, 10, 0, 6)
+ check_undo_redo(ns, marks[1], 0, 3, 0, 6)
end)
- it('blockwise shift #extmarks', function()
+ it('blockwise shift', function()
-- shift_block in ops.c
feed(':set shiftwidth=4<cr><esc>')
feed('a<cr>12345<esc>')
@@ -664,13 +740,14 @@ describe('API/extmarks', function()
feed('0<c-v>k>')
check_undo_redo(ns, marks[1], 1, 2, 1, 6)
feed('<c-v>j>')
- check_undo_redo(ns, marks[1], 1, 6, 1, 10)
+ expect('\t12345\n\t12345')
+ check_undo_redo(ns, marks[1], 1, 6, 1, 3)
feed('<c-v>j<LT>')
- check_undo_redo(ns, marks[1], 1, 10, 1, 6)
+ check_undo_redo(ns, marks[1], 1, 3, 1, 6)
end)
- it('tab works with expandtab #extmarks', function()
+ it('tab works with expandtab', function()
-- ins_tab in edit.c
feed(':set expandtab<cr><esc>')
feed(':set shiftwidth=2<cr><esc>')
@@ -679,7 +756,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[1], 0, 2, 0, 6)
end)
- it('tabs work #extmarks', function()
+ it('tabs work', function()
-- ins_tab in edit.c
feed(':set noexpandtab<cr><esc>')
feed(':set shiftwidth=2<cr><esc>')
@@ -692,7 +769,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[1], 0, 4, 0, 6)
end)
- it('marks move when using :move #extmarks', function()
+ it('marks move when using :move', function()
set_extmark(ns, marks[1], 0, 0)
feed('A<cr>2<esc>:1move 2<cr><esc>')
check_undo_redo(ns, marks[1], 0, 0, 1, 0)
@@ -701,7 +778,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[1], 1, 0, 0, 0)
end)
- it('marks move when using :move part 2 #extmarks', function()
+ it('marks move when using :move part 2', function()
-- make sure we didn't get lucky with the math...
feed('A<cr>2<cr>3<cr>4<cr>5<cr>6<esc>')
set_extmark(ns, marks[1], 1, 0)
@@ -712,7 +789,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[1], 3, 0, 1, 0)
end)
- it('undo and redo of set and unset marks #extmarks', function()
+ it('undo and redo of set and unset marks', function()
-- Force a new undo head
feed('o<esc>')
set_extmark(ns, marks[1], 0, 1)
@@ -722,7 +799,7 @@ describe('API/extmarks', function()
feed("u")
local rv = get_extmarks(ns, {0, 0}, {-1, -1})
- eq(1, table.getn(rv))
+ eq(3, table.getn(rv))
feed("<c-r>")
rv = get_extmarks(ns, {0, 0}, {-1, -1})
@@ -735,20 +812,22 @@ describe('API/extmarks', function()
eq(1, table.getn(rv))
feed("u")
feed("<c-r>")
- check_undo_redo(ns, marks[1], 0, 1, positions[1][1], positions[1][2])
+ -- old value is NOT kept in history
+ check_undo_redo(ns, marks[1], positions[1][1], positions[1][2], positions[1][1], positions[1][2])
-- Test unset
feed('o<esc>')
curbufmeths.del_extmark(ns, marks[3])
feed("u")
rv = get_extmarks(ns, {0, 0}, {-1, -1})
- eq(3, table.getn(rv))
+ -- undo does NOT restore deleted marks
+ eq(2, table.getn(rv))
feed("<c-r>")
rv = get_extmarks(ns, {0, 0}, {-1, -1})
eq(2, table.getn(rv))
end)
- it('undo and redo of marks deleted during edits #extmarks', function()
+ it('undo and redo of marks deleted during edits', function()
-- test extmark_adjust
feed('A<cr>12345<esc>')
set_extmark(ns, marks[1], 1, 2)
@@ -756,7 +835,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[1], 1, 2, 1, 0)
end)
- it('namespaces work properly #extmarks', function()
+ it('namespaces work properly', function()
local rv = set_extmark(ns, marks[1], positions[1][1], positions[1][2])
eq(1, rv)
rv = set_extmark(ns2, marks[1], positions[1][1], positions[1][2])
@@ -802,7 +881,7 @@ describe('API/extmarks', function()
eq(2, table.getn(rv))
end)
- it('mark set can create unique identifiers #extmarks', function()
+ it('mark set can create unique identifiers', function()
-- create mark with id 1
eq(1, set_extmark(ns, 1, positions[1][1], positions[1][2]))
-- ask for unique id, it should be the next one, i e 2
@@ -817,7 +896,7 @@ describe('API/extmarks', function()
eq(8, set_extmark(ns, 0, positions[1][1], positions[1][2]))
end)
- it('auto indenting with enter works #extmarks', function()
+ it('auto indenting with enter works', function()
-- op_reindent in ops.c
feed(':set cindent<cr><esc>')
feed(':set autoindent<cr><esc>')
@@ -835,7 +914,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[1], 0, 12, 1, 3)
end)
- it('auto indenting entire line works #extmarks', function()
+ it('auto indenting entire line works', function()
feed(':set cindent<cr><esc>')
feed(':set autoindent<cr><esc>')
feed(':set shiftwidth=2<cr><esc>')
@@ -852,7 +931,7 @@ describe('API/extmarks', function()
eq({1, 3}, rv)
end)
- it('removing auto indenting with <C-D> works #extmarks', function()
+ it('removing auto indenting with <C-D> works', function()
feed(':set cindent<cr><esc>')
feed(':set autoindent<cr><esc>')
feed(':set shiftwidth=2<cr><esc>')
@@ -868,7 +947,7 @@ describe('API/extmarks', function()
eq({0, 1}, rv)
end)
- it('indenting multiple lines with = works #extmarks', function()
+ it('indenting multiple lines with = works', function()
feed(':set cindent<cr><esc>')
feed(':set autoindent<cr><esc>')
feed(':set shiftwidth=2<cr><esc>')
@@ -880,7 +959,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[2], 2, 1, 2, 5)
end)
- it('substitutes by deleting inside the replace matches #extmarks_sub', function()
+ it('substitutes by deleting inside the replace matches', function()
-- do_sub in ex_cmds.c
set_extmark(ns, marks[1], 0, 2)
set_extmark(ns, marks[2], 0, 3)
@@ -889,7 +968,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[2], 0, 3, 0, 4)
end)
- it('substitutes when insert text > deleted #extmarks_sub', function()
+ it('substitutes when insert text > deleted', function()
-- do_sub in ex_cmds.c
set_extmark(ns, marks[1], 0, 2)
set_extmark(ns, marks[2], 0, 3)
@@ -898,7 +977,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[2], 0, 3, 0, 5)
end)
- it('substitutes when marks around eol #extmarks_sub', function()
+ it('substitutes when marks around eol', function()
-- do_sub in ex_cmds.c
set_extmark(ns, marks[1], 0, 4)
set_extmark(ns, marks[2], 0, 5)
@@ -907,7 +986,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[2], 0, 5, 0, 7)
end)
- it('substitutes over range insert text > deleted #extmarks_sub', function()
+ it('substitutes over range insert text > deleted', function()
-- do_sub in ex_cmds.c
feed('A<cr>x34xx<esc>')
feed('A<cr>xxx34<esc>')
@@ -920,7 +999,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[3], 2, 4, 2, 6)
end)
- it('substitutes multiple matches in a line #extmarks_sub', function()
+ it('substitutes multiple matches in a line', function()
-- do_sub in ex_cmds.c
feed('ddi3x3x3<esc>')
set_extmark(ns, marks[1], 0, 0)
@@ -932,7 +1011,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[3], 0, 4, 0, 8)
end)
- it('substitions over multiple lines with newline in pattern #extmarks_sub', function()
+ it('substitions over multiple lines with newline in pattern', function()
feed('A<cr>67890<cr>xx<esc>')
set_extmark(ns, marks[1], 0, 3)
set_extmark(ns, marks[2], 0, 4)
@@ -947,7 +1026,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[5], 2, 0, 1, 0)
end)
- it('inserting #extmarks_sub', function()
+ it('inserting', function()
feed('A<cr>67890<cr>xx<esc>')
set_extmark(ns, marks[1], 0, 3)
set_extmark(ns, marks[2], 0, 4)
@@ -964,7 +1043,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[6], 1, 2, 0, 5)
end)
- it('substitions with multiple newlines in pattern #extmarks_sub', function()
+ it('substitions with multiple newlines in pattern', function()
feed('A<cr>67890<cr>xx<esc>')
set_extmark(ns, marks[1], 0, 4)
set_extmark(ns, marks[2], 0, 5)
@@ -979,7 +1058,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[5], 2, 0, 0, 6)
end)
- it('substitions over multiple lines with replace in substition #extmarks_sub', function()
+ it('substitions over multiple lines with replace in substition', function()
feed('A<cr>67890<cr>xx<esc>')
set_extmark(ns, marks[1], 0, 1)
set_extmark(ns, marks[2], 0, 2)
@@ -997,7 +1076,7 @@ describe('API/extmarks', function()
eq({1, 3}, curbufmeths.get_extmark_by_id(ns, marks[3]))
end)
- it('substitions over multiple lines with replace in substition #extmarks_sub', function()
+ it('substitions over multiple lines with replace in substition', function()
feed('A<cr>x3<cr>xx<esc>')
set_extmark(ns, marks[1], 1, 0)
set_extmark(ns, marks[2], 1, 1)
@@ -1008,7 +1087,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[3], 1, 2, 2, 0)
end)
- it('substitions over multiple lines with replace in substition #extmarks_sub', function()
+ it('substitions over multiple lines with replace in substition', function()
feed('A<cr>x3<cr>xx<esc>')
set_extmark(ns, marks[1], 0, 1)
set_extmark(ns, marks[2], 0, 2)
@@ -1026,7 +1105,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[3], 0, 4, 1, 3)
end)
- it('substitions with newline in match and sub, delta is 0 #extmarks_sub', function()
+ it('substitions with newline in match and sub, delta is 0', function()
feed('A<cr>67890<cr>xx<esc>')
set_extmark(ns, marks[1], 0, 3)
set_extmark(ns, marks[2], 0, 4)
@@ -1043,7 +1122,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[6], 2, 0, 2, 0)
end)
- it('substitions with newline in match and sub, delta > 0 #extmarks_sub', function()
+ it('substitions with newline in match and sub, delta > 0', function()
feed('A<cr>67890<cr>xx<esc>')
set_extmark(ns, marks[1], 0, 3)
set_extmark(ns, marks[2], 0, 4)
@@ -1060,7 +1139,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[6], 2, 0, 3, 0)
end)
- it('substitions with newline in match and sub, delta < 0 #extmarks_sub', function()
+ it('substitions with newline in match and sub, delta < 0', function()
feed('A<cr>67890<cr>xx<cr>xx<esc>')
set_extmark(ns, marks[1], 0, 3)
set_extmark(ns, marks[2], 0, 4)
@@ -1079,7 +1158,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[7], 3, 0, 2, 0)
end)
- it('substitions with backrefs, newline inserted into sub #extmarks_sub', function()
+ it('substitions with backrefs, newline inserted into sub', function()
feed('A<cr>67890<cr>xx<cr>xx<esc>')
set_extmark(ns, marks[1], 0, 3)
set_extmark(ns, marks[2], 0, 4)
@@ -1096,7 +1175,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[6], 2, 0, 3, 0)
end)
- it('substitions a ^ #extmarks_sub', function()
+ it('substitions a ^', function()
set_extmark(ns, marks[1], 0, 0)
set_extmark(ns, marks[2], 0, 1)
feed([[:s:^:x<cr>]])
@@ -1104,7 +1183,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[2], 0, 1, 0, 2)
end)
- it('using <c-a> without increase in order of magnitude #extmarks_inc_dec', function()
+ it('using <c-a> without increase in order of magnitude', function()
-- do_addsub in ops.c
feed('ddiabc998xxx<esc>Tc')
set_extmark(ns, marks[1], 0, 2)
@@ -1120,7 +1199,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[5], 0, 7, 0, 7)
end)
- it('using <c-a> when increase in order of magnitude #extmarks_inc_dec', function()
+ it('using <c-a> when increase in order of magnitude', function()
-- do_addsub in ops.c
feed('ddiabc999xxx<esc>Tc')
set_extmark(ns, marks[1], 0, 2)
@@ -1136,7 +1215,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[5], 0, 7, 0, 8)
end)
- it('using <c-a> when negative and without decrease in order of magnitude #extmarks_inc_dec', function()
+ it('using <c-a> when negative and without decrease in order of magnitude', function()
feed('ddiabc-999xxx<esc>T-')
set_extmark(ns, marks[1], 0, 2)
set_extmark(ns, marks[2], 0, 3)
@@ -1151,7 +1230,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[5], 0, 8, 0, 8)
end)
- it('using <c-a> when negative and decrease in order of magnitude #extmarks_inc_dec', function()
+ it('using <c-a> when negative and decrease in order of magnitude', function()
feed('ddiabc-1000xxx<esc>T-')
set_extmark(ns, marks[1], 0, 2)
set_extmark(ns, marks[2], 0, 3)
@@ -1166,7 +1245,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[5], 0, 9, 0, 8)
end)
- it('using <c-x> without decrease in order of magnitude #extmarks_inc_dec', function()
+ it('using <c-x> without decrease in order of magnitude', function()
-- do_addsub in ops.c
feed('ddiabc999xxx<esc>Tc')
set_extmark(ns, marks[1], 0, 2)
@@ -1182,7 +1261,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[5], 0, 7, 0, 7)
end)
- it('using <c-x> when decrease in order of magnitude #extmarks_inc_dec', function()
+ it('using <c-x> when decrease in order of magnitude', function()
-- do_addsub in ops.c
feed('ddiabc1000xxx<esc>Tc')
set_extmark(ns, marks[1], 0, 2)
@@ -1198,7 +1277,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[5], 0, 8, 0, 7)
end)
- it('using <c-x> when negative and without increase in order of magnitude #extmarks_inc_dec', function()
+ it('using <c-x> when negative and without increase in order of magnitude', function()
feed('ddiabc-998xxx<esc>T-')
set_extmark(ns, marks[1], 0, 2)
set_extmark(ns, marks[2], 0, 3)
@@ -1213,7 +1292,7 @@ describe('API/extmarks', function()
check_undo_redo(ns, marks[5], 0, 8, 0, 8)
end)
- it('using <c-x> when negative and increase in order of magnitude #extmarks_inc_dec', function()
+ it('using <c-x> when negative and increase in order of magnitude', function()
feed('ddiabc-999xxx<esc>T-')
set_extmark(ns, marks[1], 0, 2)
set_extmark(ns, marks[2], 0, 3)
@@ -1236,7 +1315,7 @@ describe('API/extmarks', function()
eq("Invalid ns_id", pcall_err(curbufmeths.get_extmark_by_id, ns_invalid, marks[1]))
end)
- it('when col = line-length, set the mark on eol #extmarks', function()
+ it('when col = line-length, set the mark on eol', function()
set_extmark(ns, marks[1], 0, -1)
local rv = curbufmeths.get_extmark_by_id(ns, marks[1])
eq({0, init_text:len()}, rv)
@@ -1246,19 +1325,19 @@ describe('API/extmarks', function()
eq({0, init_text:len()}, rv)
end)
- it('when col = line-length, set the mark on eol #extmarks', function()
+ it('when col = line-length, set the mark on eol', function()
local invalid_col = init_text:len() + 1
eq("col value outside range", pcall_err(set_extmark, ns, marks[1], 0, invalid_col))
end)
- it('fails when line > line_count #extmarks', function()
+ it('fails when line > line_count', function()
local invalid_col = init_text:len() + 1
local invalid_lnum = 3
eq('line value outside range', pcall_err(set_extmark, ns, marks[1], invalid_lnum, invalid_col))
eq({}, curbufmeths.get_extmark_by_id(ns, marks[1]))
end)
- it('bug from check_col in extmark_set #extmarks_sub', function()
+ it('bug from check_col in extmark_set', function()
-- This bug was caused by extmark_set always using check_col. check_col
-- always uses the current buffer. This wasn't working during undo so we
-- now use check_col and check_lnum only when they are required.
@@ -1282,6 +1361,16 @@ describe('API/extmarks', function()
local id = bufmeths.set_extmark(buf, ns, 0, 1, 0, {})
eq({{id, 1, 0}}, bufmeths.get_extmarks(buf, ns, 0, -1, {}))
end)
+
+ it('does not crash with append/delete/undo seqence', function()
+ meths.exec([[
+ let ns = nvim_create_namespace('myplugin')
+ call nvim_buf_set_extmark(0, ns, 0, 0, 0, {})
+ call append(0, '')
+ %delete
+ undo]],false)
+ eq(2, meths.eval('1+1')) -- did not crash
+ end)
end)
describe('Extmarks buffer api with many marks', function()
@@ -1326,12 +1415,12 @@ describe('Extmarks buffer api with many marks', function()
return marks
end
- it("can get marks #extmarks", function()
+ it("can get marks", function()
eq(ns_marks[ns1], get_marks(ns1))
eq(ns_marks[ns2], get_marks(ns2))
end)
- it("can clear all marks in ns #extmarks", function()
+ it("can clear all marks in ns", function()
curbufmeths.clear_namespace(ns1, 0, -1)
eq({}, get_marks(ns1))
eq(ns_marks[ns2], get_marks(ns2))
@@ -1340,7 +1429,7 @@ describe('Extmarks buffer api with many marks', function()
eq({}, get_marks(ns2))
end)
- it("can clear line range #extmarks", function()
+ it("can clear line range", function()
curbufmeths.clear_namespace(ns1, 10, 20)
for id, mark in pairs(ns_marks[ns1]) do
if 10 <= mark[1] and mark[1] < 20 then
@@ -1351,7 +1440,7 @@ describe('Extmarks buffer api with many marks', function()
eq(ns_marks[ns2], get_marks(ns2))
end)
- it("can delete line #extmarks", function()
+ it("can delete line", function()
feed('10Gdd')
for _, marks in pairs(ns_marks) do
for id, mark in pairs(marks) do
@@ -1366,7 +1455,7 @@ describe('Extmarks buffer api with many marks', function()
eq(ns_marks[ns2], get_marks(ns2))
end)
- it("can delete lines #extmarks", function()
+ it("can delete lines", function()
feed('10G10dd')
for _, marks in pairs(ns_marks) do
for id, mark in pairs(marks) do
@@ -1381,7 +1470,7 @@ describe('Extmarks buffer api with many marks', function()
eq(ns_marks[ns2], get_marks(ns2))
end)
- it("can wipe buffer #extmarks", function()
+ it("can wipe buffer", function()
command('bwipe!')
eq({}, get_marks(ns1))
eq({}, get_marks(ns2))
diff --git a/test/functional/ui/bufhl_spec.lua b/test/functional/ui/bufhl_spec.lua
index f589bb0e83..3cb592c714 100644
--- a/test/functional/ui/bufhl_spec.lua
+++ b/test/functional/ui/bufhl_spec.lua
@@ -5,6 +5,7 @@ local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert
local command, neq = helpers.command, helpers.neq
local meths = helpers.meths
local curbufmeths, eq = helpers.curbufmeths, helpers.eq
+local pcall_err = helpers.pcall_err
describe('Buffer highlighting', function()
local screen
@@ -34,6 +35,7 @@ describe('Buffer highlighting', function()
[17] = {foreground = Screen.colors.Magenta, background = Screen.colors.LightRed},
[18] = {background = Screen.colors.LightRed},
[19] = {foreground = Screen.colors.Blue1, background = Screen.colors.LightRed},
+ [20] = {underline = true, bold = true, foreground = Screen.colors.Cyan4},
})
end)
@@ -205,17 +207,116 @@ describe('Buffer highlighting', function()
|
]])
- command(':3move 4')
- screen:expect([[
+ -- TODO(bfedl): this behaves a bit weirdly due to the highlight on
+ -- the deleted line wrapping around. we should invalidate
+ -- highlights when they are completely inside deleted text
+ command('3move 4')
+ screen:expect{grid=[[
a {5:longer} example |
|
+ {8:from different sources} |
+ {8:^in }{20:order}{8: to demonstrate} |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ --screen:expect([[
+ -- a {5:longer} example |
+ -- |
+ -- {9:from }{8:diff}{7:erent} sources |
+ -- ^in {6:order} to {7:de}{5:monstr}{7:ate} |
+ -- {1:~ }|
+ -- {1:~ }|
+ -- {1:~ }|
+ -- |
+ --]])
+
+ command('undo')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ ^ |
+ in {6:order} to {7:de}{5:monstr}{7:ate} |
{9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 1 change; before #4 {MATCH:.*}|
+ ]]}
+
+ command('undo')
+ screen:expect{grid=[[
+ ^a {5:longer} example |
+ in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 1 line less; before #3 {MATCH:.*}|
+ ]]}
+
+ command('undo')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {7:^combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 1 more line; before #2 {MATCH:.*}|
+ ]]}
+ end)
+
+ it('and moving lines around', function()
+ command('2move 3')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ {7:combin}{8:ing}{9: hi}ghlights |
^in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {9:from }{8:diff}{7:erent} sources |
{1:~ }|
{1:~ }|
{1:~ }|
|
- ]])
+ ]]}
+
+ command('1,2move 4')
+ screen:expect{grid=[[
+ in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {9:from }{8:diff}{7:erent} sources |
+ a {5:longer} example |
+ {7:^combin}{8:ing}{9: hi}ghlights |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ command('undo')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ ^in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 2 change3; before #3 {MATCH:.*}|
+ ]]}
+
+ command('undo')
+ screen:expect{grid=[[
+ a {5:longer} example |
+ ^in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 1 change; before #2 {MATCH:.*}|
+ ]]}
end)
it('and adjusting columns', function()
@@ -272,7 +373,7 @@ describe('Buffer highlighting', function()
feed('u')
screen:expect{grid=[[
a {5:longer} example |
- in {6:ordAAAAr} to^ demonstrate |
+ in {6:ordAAAAr} to^ {7:de}{5:monstr}{7:ate} |
{7:combin}{8:ing}{9: hi}ghlights |
{9:from }{8:diff}{7:erent} sources |
{1:~ }|
@@ -284,7 +385,7 @@ describe('Buffer highlighting', function()
feed('u')
screen:expect{grid=[[
a {5:longer} example |
- in {6:ord^er} to demonstrate |
+ in {6:ord^er} to {7:de}{5:monstr}{7:ate} |
{7:combin}{8:ing}{9: hi}ghlights |
{9:from }{8:diff}{7:erent} sources |
{1:~ }|
@@ -292,14 +393,14 @@ describe('Buffer highlighting', function()
{1:~ }|
1 change; before #3 {MATCH:.*}|
]]}
- end)
+ end)
it('and joining lines', function()
feed('ggJJJ')
screen:expect{grid=[[
a {5:longer} example in {6:order} to {7:de}{5:monstr}{7:ate}|
- {7: combin}{8:ing hi}{7:ghlights^ }{8:from diff}{7:erent sou}|
- {7:rces} |
+ {7:combin}{8:ing}{9: hi}ghlights^ {9:from }{8:diff}{7:erent} sou|
+ rces |
{1:~ }|
{1:~ }|
{1:~ }|
@@ -307,13 +408,12 @@ describe('Buffer highlighting', function()
|
]]}
- -- TODO(bfredl): perhaps better undo
feed('uuu')
screen:expect{grid=[[
- ^a longer example |
- in order to demonstrate |
- combining highlights |
- from different sources |
+ ^a {5:longer} example |
+ in {6:order} to {7:de}{5:monstr}{7:ate} |
+ {7:combin}{8:ing}{9: hi}ghlights |
+ {9:from }{8:diff}{7:erent} sources |
{1:~ }|
{1:~ }|
{1:~ }|
@@ -334,25 +434,23 @@ describe('Buffer highlighting', function()
{7:-- INSERT --} |
]]}
- -- TODO(bfredl): keep both "parts" after split, requires proper extmark ranges
feed('<esc>tsi<cr>')
screen:expect{grid=[[
a {5:longer} example |
in {6:order} |
to {7:de}{5:mo} |
- ^nstrate |
+ {5:^nstr}{7:ate} |
{7:combin}{8:ing}{9: hi}ghlights |
{9:from }{8:diff}{7:erent} sources |
{1:~ }|
{7:-- INSERT --} |
]]}
- -- TODO(bfredl): perhaps better undo
feed('<esc>u')
screen:expect{grid=[[
a {5:longer} example |
in {6:order} |
- to demo{7:^nstrat}{8:e} |
+ to {7:de}{5:mo^nstr}{7:ate} |
{7:combin}{8:ing}{9: hi}ghlights |
{9:from }{8:diff}{7:erent} sources |
{1:~ }|
@@ -363,7 +461,7 @@ describe('Buffer highlighting', function()
feed('<esc>u')
screen:expect{grid=[[
a {5:longer} example |
- in order^ to demonstrate |
+ in {6:order}^ to {7:de}{5:monstr}{7:ate} |
{7:combin}{8:ing}{9: hi}ghlights |
{9:from }{8:diff}{7:erent} sources |
{1:~ }|
@@ -374,7 +472,7 @@ describe('Buffer highlighting', function()
end)
end)
- it('prioritizes latest added highlight', function()
+ pending('prioritizes latest added highlight', function()
insert([[
three overlapping colors]])
add_highlight(0, "Identifier", 0, 6, 17)
@@ -405,6 +503,37 @@ describe('Buffer highlighting', function()
]])
end)
+ it('prioritizes earlier highlight groups (TEMP)', function()
+ insert([[
+ three overlapping colors]])
+ add_highlight(0, "Identifier", 0, 6, 17)
+ add_highlight(0, "String", 0, 14, 23)
+ local id = add_highlight(0, "Special", 0, 0, 9)
+
+ screen:expect{grid=[[
+ {4:three }{6:overlapp}{2:ing color}^s |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ clear_namespace(id, 0, 1)
+ screen:expect{grid=[[
+ three {6:overlapp}{2:ing color}^s |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
it('works with multibyte text', function()
insert([[
Ta båten över sjön!]])
@@ -451,7 +580,7 @@ describe('Buffer highlighting', function()
]])
end)
- describe('virtual text annotations', function()
+ describe('virtual text decorations', function()
local set_virtual_text = curbufmeths.set_virtual_text
local id1, id2
before_each(function()
@@ -529,16 +658,35 @@ describe('Buffer highlighting', function()
]])
feed("2Gdd")
- screen:expect([[
+ -- TODO(bfredl): currently decorations get moved from a deleted line
+ -- to the next one. We might want to add "invalidation" when deleting
+ -- over a decoration.
+ screen:expect{grid=[[
1 + 2 |
^5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
- , 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s|
+ , 5, 5, 5, 5, 5, 5, {12:暗x事zz速野谷質結育}|
x = 4 |
{1:~ }|
{1:~ }|
{1:~ }|
|
- ]])
+ ]]}
+ --screen:expect([[
+ -- 1 + 2 |
+ -- ^5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
+ -- , 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s|
+ -- x = 4 |
+ -- {1:~ }|
+ -- {1:~ }|
+ -- {1:~ }|
+ -- |
+ --]])
+ end)
+
+ it('validates contents', function()
+ -- this used to leak memory
+ eq('Chunk is not an array', pcall_err(set_virtual_text, id1, 0, {"texty"}, {}))
+ eq('Chunk is not an array', pcall_err(set_virtual_text, id1, 0, {{"very"}, "texty"}, {}))
end)
it('can be retrieved', function()
@@ -548,7 +696,9 @@ describe('Buffer highlighting', function()
local s1 = {{'Köttbullar', 'Comment'}, {'Kräuterbutter'}}
local s2 = {{'こんにちは', 'Comment'}}
- set_virtual_text(-1, 0, s1, {})
+ -- TODO: only a virtual text from the same ns curretly overrides
+ -- an existing virtual text. We might add a prioritation system.
+ set_virtual_text(id1, 0, s1, {})
eq(s1, get_virtual_text(0))
set_virtual_text(-1, line_count(), s2, {})