diff options
-rw-r--r-- | src/nvim/api/buffer.c | 253 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.c | 83 | ||||
-rw-r--r-- | src/nvim/buffer.c | 431 | ||||
-rw-r--r-- | src/nvim/buffer_defs.h | 23 | ||||
-rw-r--r-- | src/nvim/buffer_updates.c | 48 | ||||
-rw-r--r-- | src/nvim/bufhl_defs.h | 41 | ||||
-rw-r--r-- | src/nvim/change.c | 39 | ||||
-rw-r--r-- | src/nvim/diff.c | 2 | ||||
-rw-r--r-- | src/nvim/edit.c | 18 | ||||
-rw-r--r-- | src/nvim/ex_cmds.c | 315 | ||||
-rw-r--r-- | src/nvim/fold.c | 12 | ||||
-rw-r--r-- | src/nvim/indent.c | 12 | ||||
-rw-r--r-- | src/nvim/indent.h | 9 | ||||
-rw-r--r-- | src/nvim/map.c | 11 | ||||
-rw-r--r-- | src/nvim/map.h | 14 | ||||
-rw-r--r-- | src/nvim/mark.c | 22 | ||||
-rw-r--r-- | src/nvim/mark.h | 1 | ||||
-rw-r--r-- | src/nvim/mark_extended.c | 1593 | ||||
-rw-r--r-- | src/nvim/mark_extended.h | 293 | ||||
-rw-r--r-- | src/nvim/mark_extended_defs.h | 59 | ||||
-rw-r--r-- | src/nvim/marktree.c | 10 | ||||
-rw-r--r-- | src/nvim/ops.c | 161 | ||||
-rw-r--r-- | src/nvim/screen.c | 59 | ||||
-rw-r--r-- | src/nvim/undo.c | 2 | ||||
-rw-r--r-- | test/functional/api/mark_extended_spec.lua | 309 | ||||
-rw-r--r-- | test/functional/ui/bufhl_spec.lua | 198 |
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(®match, 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, {}) |