diff options
Diffstat (limited to 'src')
-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 | 16 | ||||
-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 | 1196 | ||||
-rw-r--r-- | src/nvim/marktree.h | 76 | ||||
-rw-r--r-- | src/nvim/ops.c | 161 | ||||
-rw-r--r-- | src/nvim/screen.c | 59 | ||||
-rw-r--r-- | src/nvim/undo.c | 2 |
25 files changed, 2538 insertions, 2237 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 75ab64cca4..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) @@ -53,6 +65,8 @@ MAP_DECLS(String, handle_T) #define map_del(T, U) map_##T##_##U##_del #define map_clear(T, U) map_##T##_##U##_clear +#define map_size(map) ((map)->table->size) + #define pmap_new(T) map_new(T, ptr_t) #define pmap_free(T) map_free(T, ptr_t) #define pmap_get(T) map_get(T, ptr_t) 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 new file mode 100644 index 0000000000..52e602cd94 --- /dev/null +++ b/src/nvim/marktree.c @@ -0,0 +1,1196 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +// Tree data structure for storing marks at (row, col) positions and updating +// them to arbitrary text changes. Derivative work of kbtree in klib, whose +// copyright notice is reproduced below. Also inspired by the design of the +// marker tree data structure of the Atom editor, regarding efficient updates +// to text changes. +// +// Marks are inserted using marktree_put. Text changes are processed using +// marktree_splice. All read and delete operations use the iterator. +// use marktree_itr_get to put an iterator at a given position or +// marktree_lookup to lookup a mark by its id (iterator optional in this case). +// Use marktree_itr_current and marktree_itr_next/prev to read marks in a loop. +// marktree_del_itr deletes the current mark of the iterator and implicitly +// moves the iterator to the next mark. +// +// Work is ongoing to fully support ranges (mark pairs). + +// Copyright notice for kbtree (included in heavily modified form): +// +// Copyright 1997-1999, 2001, John-Mark Gurney. +// 2008-2009, Attractive Chaos <attractor@live.co.uk> +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +// SUCH DAMAGE. +// +// Changes done by by the neovim project follow the Apache v2 license available +// at the repo root. + +#include <assert.h> + +#include "nvim/marktree.h" +#include "nvim/lib/kvec.h" +#include "nvim/garray.h" + +#define T MT_BRANCH_FACTOR +#define ILEN (sizeof(mtnode_t)+(2 * T) * sizeof(void *)) +#define key_t SKRAPET + +#define RIGHT_GRAVITY (((uint64_t)1) << 63) +#define ANTIGRAVITY(id) ((id)&(RIGHT_GRAVITY-1)) +#define IS_RIGHT(id) ((id)&RIGHT_GRAVITY) + +#define PAIRED MARKTREE_PAIRED_FLAG +#define END_FLAG MARKTREE_END_FLAG +#define ID_INCR (((uint64_t)1) << 2) + +#define PROP_MASK (RIGHT_GRAVITY|PAIRED|END_FLAG) + +#define rawkey(itr) (itr->node->key[itr->i]) + +static bool pos_leq(mtpos_t a, mtpos_t b) +{ + return a.row < b.row || (a.row == b.row && a.col <= b.col); +} + +static void relative(mtpos_t base, mtpos_t *val) +{ + assert(pos_leq(base, *val)); + if (val->row == base.row) { + val->row = 0; + val->col -= base.col; + } else { + val->row -= base.row; + } +} + +static void unrelative(mtpos_t base, mtpos_t *val) +{ + if (val->row == 0) { + val->row = base.row; + val->col += base.col; + } else { + val->row += base.row; + } +} + +static void compose(mtpos_t *base, mtpos_t val) +{ + if (val.row == 0) { + base->col += val.col; + } else { + base->row += val.row; + base->col = val.col; + } +} + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "marktree.c.generated.h" +#endif + +#define mt_generic_cmp(a, b) (((b) < (a)) - ((a) < (b))) +static int key_cmp(mtkey_t a, mtkey_t b) +{ + int cmp = mt_generic_cmp(a.pos.row, b.pos.row); + if (cmp != 0) { + return cmp; + } + cmp = mt_generic_cmp(a.pos.col, b.pos.col); + if (cmp != 0) { + return cmp; + } + // NB: keeping the events at the same pos sorted by id is actually not + // necessary only make sure that START is before END etc. + return mt_generic_cmp(a.id, b.id); +} + +static inline int marktree_getp_aux(const mtnode_t *x, mtkey_t k, int *r) +{ + int tr, *rr, begin = 0, end = x->n; + if (x->n == 0) { + return -1; + } + rr = r? r : &tr; + while (begin < end) { + int mid = (begin + end) >> 1; + if (key_cmp(x->key[mid], k) < 0) { + begin = mid + 1; + } else { + end = mid; + } + } + if (begin == x->n) { *rr = 1; return x->n - 1; } + if ((*rr = key_cmp(k, x->key[begin])) < 0) { + begin--; + } + return begin; +} + +static inline void refkey(MarkTree *b, mtnode_t *x, int i) +{ + pmap_put(uint64_t)(b->id2node, ANTIGRAVITY(x->key[i].id), x); +} + +// put functions + +// x must be an internal node, which is not full +// x->ptr[i] should be a full node, i e x->ptr[i]->n == 2*T-1 +static inline void split_node(MarkTree *b, mtnode_t *x, const int i) +{ + mtnode_t *y = x->ptr[i]; + mtnode_t *z; + z = (mtnode_t *)xcalloc(1, y->level ? ILEN : sizeof(mtnode_t)); + b->n_nodes++; + z->level = y->level; + z->n = T - 1; + memcpy(z->key, &y->key[T], sizeof(mtkey_t) * (T - 1)); + for (int j = 0; j < T-1; j++) { + refkey(b, z, j); + } + if (y->level) { + memcpy(z->ptr, &y->ptr[T], sizeof(mtnode_t *) * T); + for (int j = 0; j < T; j++) { + z->ptr[j]->parent = z; + } + } + y->n = T - 1; + memmove(&x->ptr[i + 2], &x->ptr[i + 1], + sizeof(mtnode_t *) * (size_t)(x->n - i)); + x->ptr[i + 1] = z; + z->parent = x; // == y->parent + memmove(&x->key[i + 1], &x->key[i], sizeof(mtkey_t) * (size_t)(x->n - i)); + + // move key to internal layer: + x->key[i] = y->key[T - 1]; + refkey(b, x, i); + x->n++; + + for (int j = 0; j < T-1; j++) { + relative(x->key[i].pos, &z->key[j].pos); + } + if (i > 0) { + unrelative(x->key[i-1].pos, &x->key[i].pos); + } +} + +// x must not be a full node (even if there might be internal space) +static inline void marktree_putp_aux(MarkTree *b, mtnode_t *x, mtkey_t k) +{ + int i = x->n - 1; + if (x->level == 0) { + i = marktree_getp_aux(x, k, 0); + if (i != x->n - 1) { + memmove(&x->key[i + 2], &x->key[i + 1], + (size_t)(x->n - i - 1) * sizeof(mtkey_t)); + } + x->key[i + 1] = k; + refkey(b, x, i+1); + x->n++; + } else { + i = marktree_getp_aux(x, k, 0) + 1; + if (x->ptr[i]->n == 2 * T - 1) { + split_node(b, x, i); + if (key_cmp(k, x->key[i]) > 0) { + i++; + } + } + if (i > 0) { + relative(x->key[i-1].pos, &k.pos); + } + marktree_putp_aux(b, x->ptr[i], k); + } +} + +uint64_t marktree_put(MarkTree *b, int row, int col, bool right_gravity) +{ + uint64_t id = (b->next_id+=ID_INCR); + uint64_t keyid = id; + if (right_gravity) { + // order all right gravity keys after the left ones, for effortless + // insertion (but not deletion!) + keyid |= RIGHT_GRAVITY; + } + marktree_put_key(b, row, col, keyid); + return id; +} + +uint64_t marktree_put_pair(MarkTree *b, + int start_row, int start_col, bool start_right, + int end_row, int end_col, bool end_right) +{ + uint64_t id = (b->next_id+=ID_INCR)|PAIRED; + uint64_t start_id = id|(start_right?RIGHT_GRAVITY:0); + uint64_t end_id = id|END_FLAG|(end_right?RIGHT_GRAVITY:0); + marktree_put_key(b, start_row, start_col, start_id); + marktree_put_key(b, end_row, end_col, end_id); + return id; +} + +void marktree_put_key(MarkTree *b, int row, int col, uint64_t id) +{ + mtkey_t k = { .pos = { .row = row, .col = col }, .id = id }; + + if (!b->root) { + b->root = (mtnode_t *)xcalloc(1, ILEN); + b->id2node = pmap_new(uint64_t)(); + b->n_nodes++; + } + mtnode_t *r, *s; + b->n_keys++; + r = b->root; + if (r->n == 2 * T - 1) { + b->n_nodes++; + s = (mtnode_t *)xcalloc(1, ILEN); + b->root = s; s->level = r->level+1; s->n = 0; + s->ptr[0] = r; + r->parent = s; + split_node(b, s, 0); + r = s; + } + marktree_putp_aux(b, r, k); +} + +/// INITIATING DELETION PROTOCOL: +/// +/// 1. Construct a valid iterator to the node to delete (argument) +/// 2. If an "internal" key. Iterate one step to the left or right, +/// which gives an internal key "auxiliary key". +/// 3. Now delete this internal key (intended or auxiliary). +/// The leaf node X might become undersized. +/// 4. If step two was done: now replace the key that _should_ be +/// deleted with the auxiliary key. Adjust relative +/// 5. Now "repair" the tree as needed. We always start at a leaf node X. +/// - if the node is big enough, terminate +/// - if we can steal from the left, steal +/// - if we can steal from the right, steal +/// - otherwise merge this node with a neighbour. This might make our +/// parent undersized. So repeat 5 for the parent. +/// 6. If 4 went all the way to the root node. The root node +/// might have ended up with size 0. Delete it then. +/// +/// NB: ideally keeps the iterator valid. Like point to the key after this +/// if present. +/// +/// @param rev should be true if we plan to iterate _backwards_ and delete +/// stuff before this key. Most of the time this is false (the +/// recommended strategy is to always iterate forward) +void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev) +{ + int adjustment = 0; + + mtnode_t *cur = itr->node; + int curi = itr->i; + uint64_t id = cur->key[curi].id; + // fprintf(stderr, "\nDELET %lu\n", id); + + if (itr->node->level) { + if (rev) { + abort(); + } else { + // fprintf(stderr, "INTERNAL %d\n", cur->level); + // steal previous node + marktree_itr_prev(b, itr); + adjustment = -1; + } + } + + // 3. + mtnode_t *x = itr->node; + assert(x->level == 0); + mtkey_t intkey = x->key[itr->i]; + if (x->n > itr->i+1) { + memmove(&x->key[itr->i], &x->key[itr->i+1], + sizeof(mtkey_t) * (size_t)(x->n - itr->i-1)); + } + x->n--; + + // 4. + if (adjustment) { + if (adjustment == 1) { + abort(); + } else { // adjustment == -1 + int ilvl = itr->lvl-1; + mtnode_t *lnode = x; + do { + mtnode_t *p = lnode->parent; + if (ilvl < 0) { + abort(); + } + int i = itr->s[ilvl].i; + assert(p->ptr[i] == lnode); + if (i > 0) { + unrelative(p->key[i-1].pos, &intkey.pos); + } + lnode = p; + ilvl--; + } while (lnode != cur); + + mtkey_t deleted = cur->key[curi]; + cur->key[curi] = intkey; + refkey(b, cur, curi); + relative(intkey.pos, &deleted.pos); + mtnode_t *y = cur->ptr[curi+1]; + if (deleted.pos.row || deleted.pos.col) { + while (y) { + for (int k = 0; k < y->n; k++) { + unrelative(deleted.pos, &y->key[k].pos); + } + y = y->level ? y->ptr[0] : NULL; + } + } + } + } + + b->n_keys--; + pmap_del(uint64_t)(b->id2node, ANTIGRAVITY(id)); + + // 5. + bool itr_dirty = false; + int rlvl = itr->lvl-1; + int *lasti = &itr->i; + while (x != b->root) { + assert(rlvl >= 0); + mtnode_t *p = x->parent; + if (x->n >= T-1) { + // we are done, if this node is fine the rest of the tree will be + break; + } + int pi = itr->s[rlvl].i; + assert(p->ptr[pi] == x); + if (pi > 0 && p->ptr[pi-1]->n > T-1) { + *lasti += 1; + itr_dirty = true; + // steal one key from the left neighbour + pivot_right(b, p, pi-1); + break; + } else if (pi < p->n && p->ptr[pi+1]->n > T-1) { + // steal one key from right neighbour + pivot_left(b, p, pi); + break; + } else if (pi > 0) { + // fprintf(stderr, "LEFT "); + assert(p->ptr[pi-1]->n == T-1); + // merge with left neighbour + *lasti += T; + x = merge_node(b, p, pi-1); + if (lasti == &itr->i) { + // TRICKY: we merged the node the iterator was on + itr->node = x; + } + itr->s[rlvl].i--; + itr_dirty = true; + } else { + // fprintf(stderr, "RIGHT "); + assert(pi < p->n && p->ptr[pi+1]->n == T-1); + merge_node(b, p, pi); + // no iter adjustment needed + } + lasti = &itr->s[rlvl].i; + rlvl--; + x = p; + } + + // 6. + if (b->root->n == 0) { + if (itr->lvl > 0) { + memmove(itr->s, itr->s+1, (size_t)(itr->lvl-1) * sizeof(*itr->s)); + itr->lvl--; + } + if (b->root->level) { + mtnode_t *oldroot = b->root; + b->root = b->root->ptr[0]; + b->root->parent = NULL; + xfree(oldroot); + } else { + // no items, nothing for iterator to point to + // not strictly needed, should handle delete right-most mark anyway + itr->node = NULL; + } + } + + if (itr->node && itr_dirty) { + marktree_itr_fix_pos(b, itr); + } + + // BONUS STEP: fix the iterator, so that it points to the key afterwards + // TODO(bfredl): with "rev" should point before + if (adjustment == 1) { + abort(); + } else if (adjustment == -1) { + // tricky: we stand at the deleted space in the previous leaf node. + // But the inner key is now the previous key we stole, so we need + // to skip that one as well. + marktree_itr_next(b, itr); + marktree_itr_next(b, itr); + } else { + if (itr->node && itr->i >= itr->node->n) { + // we deleted the last key of a leaf node + // go to the inner key after that. + assert(itr->node->level == 0); + marktree_itr_next(b, itr); + } + } +} + +static mtnode_t *merge_node(MarkTree *b, mtnode_t *p, int i) +{ + mtnode_t *x = p->ptr[i], *y = p->ptr[i+1]; + + x->key[x->n] = p->key[i]; + refkey(b, x, x->n); + if (i > 0) { + relative(p->key[i-1].pos, &x->key[x->n].pos); + } + + memmove(&x->key[x->n+1], y->key, (size_t)y->n * sizeof(mtkey_t)); + for (int k = 0; k < y->n; k++) { + refkey(b, x, x->n+1+k); + unrelative(x->key[x->n].pos, &x->key[x->n+1+k].pos); + } + if (x->level) { + memmove(&x->ptr[x->n+1], y->ptr, (size_t)(y->n + 1) * sizeof(mtnode_t *)); + for (int k = 0; k < y->n+1; k++) { + x->ptr[x->n+k+1]->parent = x; + } + } + x->n += y->n+1; + memmove(&p->key[i], &p->key[i + 1], (size_t)(p->n - i - 1) * sizeof(mtkey_t)); + memmove(&p->ptr[i + 1], &p->ptr[i + 2], + (size_t)(p->n - i - 1) * sizeof(mtkey_t *)); + p->n--; + xfree(y); + b->n_nodes--; + return x; +} + +// TODO(bfredl): as a potential "micro" optimization, pivoting should balance +// the two nodes instead of stealing just one key +static void pivot_right(MarkTree *b, mtnode_t *p, int i) +{ + mtnode_t *x = p->ptr[i], *y = p->ptr[i+1]; + memmove(&y->key[1], y->key, (size_t)y->n * sizeof(mtkey_t)); + if (y->level) { + memmove(&y->ptr[1], y->ptr, (size_t)(y->n + 1) * sizeof(mtnode_t *)); + } + y->key[0] = p->key[i]; + refkey(b, y, 0); + p->key[i] = x->key[x->n - 1]; + refkey(b, p, i); + if (x->level) { + y->ptr[0] = x->ptr[x->n]; + y->ptr[0]->parent = y; + } + x->n--; + y->n++; + if (i > 0) { + unrelative(p->key[i-1].pos, &p->key[i].pos); + } + relative(p->key[i].pos, &y->key[0].pos); + for (int k = 1; k < y->n; k++) { + unrelative(y->key[0].pos, &y->key[k].pos); + } +} + +static void pivot_left(MarkTree *b, mtnode_t *p, int i) +{ + mtnode_t *x = p->ptr[i], *y = p->ptr[i+1]; + + // reverse from how we "always" do it. but pivot_left + // is just the inverse of pivot_right, so reverse it literally. + for (int k = 1; k < y->n; k++) { + relative(y->key[0].pos, &y->key[k].pos); + } + unrelative(p->key[i].pos, &y->key[0].pos); + if (i > 0) { + relative(p->key[i-1].pos, &p->key[i].pos); + } + + x->key[x->n] = p->key[i]; + refkey(b, x, x->n); + p->key[i] = y->key[0]; + refkey(b, p, i); + if (x->level) { + x->ptr[x->n+1] = y->ptr[0]; + x->ptr[x->n+1]->parent = x; + } + memmove(y->key, &y->key[1], (size_t)(y->n-1) * sizeof(mtkey_t)); + if (y->level) { + memmove(y->ptr, &y->ptr[1], (size_t)y->n * sizeof(mtnode_t *)); + } + x->n++; + y->n--; +} + +/// frees all mem, resets tree to valid empty state +void marktree_clear(MarkTree *b) +{ + if (b->root) { + marktree_free_node(b->root); + b->root = NULL; + } + if (b->id2node) { + pmap_free(uint64_t)(b->id2node); + b->id2node = NULL; + } + b->n_keys = 0; + b->n_nodes = 0; +} + +void marktree_free_node(mtnode_t *x) +{ + if (x->level) { + for (int i = 0; i < x->n+1; i++) { + marktree_free_node(x->ptr[i]); + } + } + xfree(x); +} + +/// NB: caller must check not pair! +uint64_t marktree_revise(MarkTree *b, MarkTreeIter *itr) +{ + uint64_t old_id = rawkey(itr).id; + pmap_del(uint64_t)(b->id2node, ANTIGRAVITY(old_id)); + uint64_t new_id = (b->next_id += ID_INCR); + rawkey(itr).id = new_id + (RIGHT_GRAVITY&old_id); + refkey(b, itr->node, itr->i); + return new_id; +} + +void marktree_move(MarkTree *b, MarkTreeIter *itr, int row, int col) +{ + uint64_t old_id = rawkey(itr).id; + // TODO(bfredl): optimize when moving a mark within a leaf without moving it + // across neighbours! + marktree_del_itr(b, itr, false); + marktree_put_key(b, row, col, old_id); + itr->node = NULL; // itr might become invalid by put +} + +// itr functions + +// TODO(bfredl): static inline? +bool marktree_itr_get(MarkTree *b, int row, int col, MarkTreeIter *itr) +{ + return marktree_itr_get_ext(b, (mtpos_t){ row, col }, + itr, false, false, NULL); +} + +bool marktree_itr_get_ext(MarkTree *b, mtpos_t p, MarkTreeIter *itr, + bool last, bool gravity, mtpos_t *oldbase) +{ + mtkey_t k = { .pos = p, .id = gravity ? RIGHT_GRAVITY : 0 }; + if (last && !gravity) { + k.id = UINT64_MAX; + } + if (b->n_keys == 0) { + itr->node = NULL; + return false; + } + itr->pos = (mtpos_t){ 0, 0 }; + itr->node = b->root; + itr->lvl = 0; + if (oldbase) { + oldbase[itr->lvl] = itr->pos; + } + while (true) { + itr->i = marktree_getp_aux(itr->node, k, 0)+1; + + if (itr->node->level == 0) { + break; + } + + itr->s[itr->lvl].i = itr->i; + itr->s[itr->lvl].oldcol = itr->pos.col; + + if (itr->i > 0) { + compose(&itr->pos, itr->node->key[itr->i-1].pos); + relative(itr->node->key[itr->i-1].pos, &k.pos); + } + itr->node = itr->node->ptr[itr->i]; + itr->lvl++; + if (oldbase) { + oldbase[itr->lvl] = itr->pos; + } + } + + if (last) { + return marktree_itr_prev(b, itr); + } else if (itr->i >= itr->node->n) { + return marktree_itr_next(b, itr); + } + return true; +} + +bool marktree_itr_first(MarkTree *b, MarkTreeIter *itr) +{ + itr->node = b->root; + if (b->n_keys == 0) { + return false; + } + + itr->i = 0; + itr->lvl = 0; + itr->pos = (mtpos_t){ 0, 0 }; + while (itr->node->level > 0) { + itr->s[itr->lvl].i = 0; + itr->s[itr->lvl].oldcol = 0; + itr->lvl++; + itr->node = itr->node->ptr[0]; + } + return true; +} + +// gives the first key that is greater or equal to p +int marktree_itr_last(MarkTree *b, MarkTreeIter *itr) +{ + if (b->n_keys == 0) { + itr->node = NULL; + return false; + } + itr->pos = (mtpos_t){ 0, 0 }; + itr->node = b->root; + itr->lvl = 0; + while (true) { + itr->i = itr->node->n; + + if (itr->node->level == 0) { + break; + } + + itr->s[itr->lvl].i = itr->i; + itr->s[itr->lvl].oldcol = itr->pos.col; + + assert(itr->i > 0); + compose(&itr->pos, itr->node->key[itr->i-1].pos); + + itr->node = itr->node->ptr[itr->i]; + itr->lvl++; + } + itr->i--; + return true; +} + +// TODO(bfredl): static inline +bool marktree_itr_next(MarkTree *b, MarkTreeIter *itr) +{ + return marktree_itr_next_skip(b, itr, false, NULL); +} + +static bool marktree_itr_next_skip(MarkTree *b, MarkTreeIter *itr, bool skip, + mtpos_t oldbase[]) +{ + if (!itr->node) { + return false; + } + itr->i++; + if (itr->node->level == 0 || skip) { + if (itr->i < itr->node->n) { + // TODO(bfredl): this is the common case, + // and could be handled by inline wrapper + return true; + } + // we ran out of non-internal keys. Go up until we find an internal key + while (itr->i >= itr->node->n) { + itr->node = itr->node->parent; + if (itr->node == NULL) { + return false; + } + itr->lvl--; + itr->i = itr->s[itr->lvl].i; + if (itr->i > 0) { + itr->pos.row -= itr->node->key[itr->i-1].pos.row; + itr->pos.col = itr->s[itr->lvl].oldcol; + } + } + } else { + // we stood at an "internal" key. Go down to the first non-internal + // key after it. + while (itr->node->level > 0) { + // internal key, there is always a child after + if (itr->i > 0) { + itr->s[itr->lvl].oldcol = itr->pos.col; + compose(&itr->pos, itr->node->key[itr->i-1].pos); + } + if (oldbase && itr->i == 0) { + oldbase[itr->lvl+1] = oldbase[itr->lvl]; + } + itr->s[itr->lvl].i = itr->i; + assert(itr->node->ptr[itr->i]->parent == itr->node); + itr->node = itr->node->ptr[itr->i]; + itr->i = 0; + itr->lvl++; + } + } + return true; +} + +bool marktree_itr_prev(MarkTree *b, MarkTreeIter *itr) +{ + if (!itr->node) { + return false; + } + if (itr->node->level == 0) { + itr->i--; + if (itr->i >= 0) { + // TODO(bfredl): this is the common case, + // and could be handled by inline wrapper + return true; + } + // we ran out of non-internal keys. Go up until we find a non-internal key + while (itr->i < 0) { + itr->node = itr->node->parent; + if (itr->node == NULL) { + return false; + } + itr->lvl--; + itr->i = itr->s[itr->lvl].i-1; + if (itr->i >= 0) { + itr->pos.row -= itr->node->key[itr->i].pos.row; + itr->pos.col = itr->s[itr->lvl].oldcol; + } + } + } else { + // we stood at an "internal" key. Go down to the last non-internal + // key before it. + while (itr->node->level > 0) { + // internal key, there is always a child before + if (itr->i > 0) { + itr->s[itr->lvl].oldcol = itr->pos.col; + compose(&itr->pos, itr->node->key[itr->i-1].pos); + } + itr->s[itr->lvl].i = itr->i; + assert(itr->node->ptr[itr->i]->parent == itr->node); + itr->node = itr->node->ptr[itr->i]; + itr->i = itr->node->n; + itr->lvl++; + } + itr->i--; + } + return true; +} + +void marktree_itr_rewind(MarkTree *b, MarkTreeIter *itr) +{ + if (!itr->node) { + return; + } + if (itr->node->level) { + marktree_itr_prev(b, itr); + } + itr->i = 0; +} + +bool marktree_itr_node_done(MarkTreeIter *itr) +{ + return !itr->node || itr->i == itr->node->n-1; +} + + +mtpos_t marktree_itr_pos(MarkTreeIter *itr) +{ + mtpos_t pos = rawkey(itr).pos; + unrelative(itr->pos, &pos); + return pos; +} + +mtmark_t marktree_itr_current(MarkTreeIter *itr) +{ + if (itr->node) { + uint64_t keyid = rawkey(itr).id; + mtpos_t pos = marktree_itr_pos(itr); + mtmark_t mark = { .row = pos.row, + .col = pos.col, + .id = ANTIGRAVITY(keyid), + .right_gravity = keyid & RIGHT_GRAVITY }; + return mark; + } + return (mtmark_t){ -1, -1, 0, false }; +} + +static void swap_id(uint64_t *id1, uint64_t *id2) +{ + uint64_t temp = *id1; + *id1 = *id2; + *id2 = temp; +} + +bool marktree_splice(MarkTree *b, + int start_line, int start_col, + int old_extent_line, int old_extent_col, + int new_extent_line, int new_extent_col) +{ + mtpos_t start = { start_line, start_col }; + mtpos_t old_extent = { (int)old_extent_line, old_extent_col }; + mtpos_t new_extent = { (int)new_extent_line, new_extent_col }; + + bool may_delete = (old_extent.row != 0 || old_extent.col != 0); + bool same_line = old_extent.row == 0 && new_extent.row == 0; + unrelative(start, &old_extent); + unrelative(start, &new_extent); + MarkTreeIter itr[1], enditr[1]; + + mtpos_t oldbase[MT_MAX_DEPTH]; + + marktree_itr_get_ext(b, start, itr, false, true, oldbase); + if (!itr->node) { + // den e FĂ„RDIG + return false; + } + mtpos_t delta = { new_extent.row - old_extent.row, + new_extent.col-old_extent.col }; + + if (may_delete) { + mtpos_t ipos = marktree_itr_pos(itr); + if (!pos_leq(old_extent, ipos) + || (old_extent.row == ipos.row && old_extent.col == ipos.col + && !IS_RIGHT(rawkey(itr).id))) { + marktree_itr_get_ext(b, old_extent, enditr, true, true, NULL); + assert(enditr->node); + // "assert" (itr <= enditr) + } else { + may_delete = false; + } + } + + bool past_right = false; + bool moved = false; + + // Follow the general strategy of messing things up and fix them later + // "oldbase" carries the information needed to calculate old position of + // children. + if (may_delete) { + while (itr->node && !past_right) { + mtpos_t loc_start = start; + mtpos_t loc_old = old_extent; + relative(itr->pos, &loc_start); + + relative(oldbase[itr->lvl], &loc_old); + +continue_same_node: + // NB: strictly should be less than the right gravity of loc_old, but + // the iter comparison below will already break on that. + if (!pos_leq(rawkey(itr).pos, loc_old)) { + break; + } + + if (IS_RIGHT(rawkey(itr).id)) { + while (rawkey(itr).id != rawkey(enditr).id + && IS_RIGHT(rawkey(enditr).id)) { + marktree_itr_prev(b, enditr); + } + if (!IS_RIGHT(rawkey(enditr).id)) { + swap_id(&rawkey(itr).id, &rawkey(enditr).id); + refkey(b, itr->node, itr->i); + refkey(b, enditr->node, enditr->i); + } else { + past_right = true; + break; + } + } + + if (rawkey(itr).id == rawkey(enditr).id) { + // actually, will be past_right after this key + past_right = true; + } + + moved = true; + if (itr->node->level) { + oldbase[itr->lvl+1] = rawkey(itr).pos; + unrelative(oldbase[itr->lvl], &oldbase[itr->lvl+1]); + rawkey(itr).pos = loc_start; + marktree_itr_next_skip(b, itr, false, oldbase); + } else { + rawkey(itr).pos = loc_start; + if (itr->i < itr->node->n-1) { + itr->i++; + if (!past_right) { + goto continue_same_node; + } + } else { + marktree_itr_next(b, itr); + } + } + } + while (itr->node) { + mtpos_t loc_new = new_extent; + relative(itr->pos, &loc_new); + mtpos_t limit = old_extent; + + relative(oldbase[itr->lvl], &limit); + +past_continue_same_node: + + if (pos_leq(limit, rawkey(itr).pos)) { + break; + } + + mtpos_t oldpos = rawkey(itr).pos; + rawkey(itr).pos = loc_new; + moved = true; + if (itr->node->level) { + oldbase[itr->lvl+1] = oldpos; + unrelative(oldbase[itr->lvl], &oldbase[itr->lvl+1]); + + marktree_itr_next_skip(b, itr, false, oldbase); + } else { + if (itr->i < itr->node->n-1) { + itr->i++; + goto past_continue_same_node; + } else { + marktree_itr_next(b, itr); + } + } + } + } + + + while (itr->node) { + unrelative(oldbase[itr->lvl], &rawkey(itr).pos); + int realrow = rawkey(itr).pos.row; + assert(realrow >= old_extent.row); + bool done = false; + if (realrow == old_extent.row) { + if (delta.col) { + rawkey(itr).pos.col += delta.col; + moved = true; + } + } else { + if (same_line) { + // optimization: column only adjustment can skip remaining rows + done = true; + } + } + if (delta.row) { + rawkey(itr).pos.row += delta.row; + moved = true; + } + relative(itr->pos, &rawkey(itr).pos); + if (done) { + break; + } + marktree_itr_next_skip(b, itr, true, NULL); + } + return moved; +} + +void marktree_move_region(MarkTree *b, + int start_row, colnr_T start_col, + int extent_row, colnr_T extent_col, + int new_row, colnr_T new_col) +{ + mtpos_t start = { start_row, start_col }, size = { extent_row, extent_col }; + mtpos_t end = size; + unrelative(start, &end); + MarkTreeIter itr[1]; + marktree_itr_get_ext(b, start, itr, false, true, NULL); + kvec_t(mtkey_t) saved = KV_INITIAL_VALUE; + while (itr->node) { + mtpos_t pos = marktree_itr_pos(itr); + if (!pos_leq(pos, end) || (pos.row == end.row && pos.col == end.col + && rawkey(itr).id & RIGHT_GRAVITY)) { + break; + } + relative(start, &pos); + kv_push(saved, ((mtkey_t){ .pos = pos, .id = rawkey(itr).id })); + marktree_del_itr(b, itr, false); + } + + marktree_splice(b, start.row, start.col, size.row, size.col, 0, 0); + mtpos_t new = { new_row, new_col }; + marktree_splice(b, new.row, new.col, + 0, 0, size.row, size.col); + + for (size_t i = 0; i < kv_size(saved); i++) { + mtkey_t item = kv_A(saved, i); + unrelative(new, &item.pos); + marktree_put_key(b, item.pos.row, item.pos.col, item.id); + } + kv_destroy(saved); +} + +/// @param itr OPTIONAL. set itr to pos. +mtpos_t marktree_lookup(MarkTree *b, uint64_t id, MarkTreeIter *itr) +{ + mtnode_t *n = pmap_get(uint64_t)(b->id2node, id); + if (n == NULL) { + if (itr) { + itr->node = NULL; + } + return (mtpos_t){ -1, -1 }; + } + int i = 0; + for (i = 0; i < n->n; i++) { + if (ANTIGRAVITY(n->key[i].id) == id) { + goto found; + } + } + abort(); +found: {} + mtpos_t pos = n->key[i].pos; + if (itr) { + itr->i = i; + itr->node = n; + itr->lvl = b->root->level - n->level; + } + while (n->parent != NULL) { + mtnode_t *p = n->parent; + for (i = 0; i < p->n+1; i++) { + if (p->ptr[i] == n) { + goto found_node; + } + } + abort(); +found_node: + if (itr) { + itr->s[b->root->level-p->level].i = i; + } + if (i > 0) { + unrelative(p->key[i-1].pos, &pos); + } + n = p; + } + if (itr) { + marktree_itr_fix_pos(b, itr); + } + return pos; +} + +static void marktree_itr_fix_pos(MarkTree *b, MarkTreeIter *itr) +{ + itr->pos = (mtpos_t){ 0, 0 }; + mtnode_t *x = b->root; + for (int lvl = 0; lvl < itr->lvl; lvl++) { + itr->s[lvl].oldcol = itr->pos.col; + int i = itr->s[lvl].i; + if (i > 0) { + compose(&itr->pos, x->key[i-1].pos); + } + assert(x->level); + x = x->ptr[i]; + } + assert(x == itr->node); +} + +void marktree_check(MarkTree *b) +{ +#ifndef NDEBUG + if (b->root == NULL) { + assert(b->n_keys == 0); + assert(b->n_nodes == 0); + assert(b->id2node == NULL || map_size(b->id2node) == 0); + return; + } + + mtpos_t dummy; + bool last_right = false; + 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 +} + +#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. + assert(x->n >= (x != b->root ? T-1 : 0)); + size_t n_keys = (size_t)x->n; + + for (int i = 0; i < x->n; i++) { + if (x->level) { + n_keys += check_node(b, x->ptr[i], last, last_right); + } else { + *last = (mtpos_t) { 0, 0 }; + } + if (i > 0) { + unrelative(x->key[i-1].pos, last); + } + if (x->level) { + } + assert(pos_leq(*last, x->key[i].pos)); + if (last->row == x->key[i].pos.row && last->col == x->key[i].pos.col) { + assert(!*last_right || IS_RIGHT(x->key[i].id)); + } + *last_right = IS_RIGHT(x->key[i].id); + assert(x->key[i].pos.col >= 0); + assert(pmap_get(uint64_t)(b->id2node, ANTIGRAVITY(x->key[i].id)) == x); + } + + if (x->level) { + n_keys += check_node(b, x->ptr[x->n], last, last_right); + unrelative(x->key[x->n-1].pos, last); + + for (int i = 0; i < x->n+1; i++) { + assert(x->ptr[i]->parent == x); + assert(x->ptr[i]->level == x->level-1); + // PARANOIA: check no double node ref + for (int j = 0; j < i; j++) { + assert(x->ptr[i] != x->ptr[j]); + } + } + } else { + *last = x->key[x->n-1].pos; + } + return n_keys; +} +#endif + +char *mt_inspect_rec(MarkTree *b) +{ + garray_T ga; + ga_init(&ga, (int)sizeof(char), 80); + mtpos_t p = { 0, 0 }; + mt_inspect_node(b, &ga, b->root, p); + return ga.ga_data; +} + +void mt_inspect_node(MarkTree *b, garray_T *ga, mtnode_t *n, mtpos_t off) +{ + static char buf[1024]; +#define GA_PUT(x) ga_concat(ga, (char_u *)(x)) + GA_PUT("["); + if (n->level) { + mt_inspect_node(b, ga, n->ptr[0], off); + } + for (int i = 0; i < n->n; i++) { + mtpos_t p = n->key[i].pos; + unrelative(off, &p); + snprintf((char *)buf, sizeof(buf), "%d/%d", p.row, p.col); + GA_PUT(buf); + if (n->level) { + mt_inspect_node(b, ga, n->ptr[i+1], p); + } else { + GA_PUT(","); + } + } + GA_PUT("]"); +#undef GA_PUT +} + diff --git a/src/nvim/marktree.h b/src/nvim/marktree.h new file mode 100644 index 0000000000..0c73e75b2e --- /dev/null +++ b/src/nvim/marktree.h @@ -0,0 +1,76 @@ +#ifndef NVIM_MARKTREE_H +#define NVIM_MARKTREE_H + +#include <stdint.h> +#include "nvim/map.h" +#include "nvim/garray.h" + +#define MT_MAX_DEPTH 20 +#define MT_BRANCH_FACTOR 10 + +typedef struct { + int32_t row; + int32_t col; +} mtpos_t; + +typedef struct { + int32_t row; + int32_t col; + uint64_t id; + bool right_gravity; +} mtmark_t; + +typedef struct mtnode_s mtnode_t; +typedef struct { + int oldcol; + int i; +} iterstate_t; + +typedef struct { + mtpos_t pos; + int lvl; + mtnode_t *node; + int i; + iterstate_t s[MT_MAX_DEPTH]; +} MarkTreeIter; + + +// Internal storage +// +// NB: actual marks have id > 0, so we can use (row,col,0) pseudo-key for +// "space before (row,col)" +typedef struct { + mtpos_t pos; + uint64_t id; +} mtkey_t; + +struct mtnode_s { + int32_t n; + int32_t level; + // TODO(bfredl): we could consider having a only-sometimes-valid + // index into parent for faster "chached" lookup. + mtnode_t *parent; + mtkey_t key[2 * MT_BRANCH_FACTOR - 1]; + mtnode_t *ptr[]; +}; + +// TODO(bfredl): the iterator is pretty much everpresent, make it part of the +// tree struct itself? +typedef struct { + mtnode_t *root; + size_t n_keys, n_nodes; + uint64_t next_id; + // TODO(bfredl): the pointer to node could be part of the larger + // Map(uint64_t, ExtmarkItem) essentially; + PMap(uint64_t) *id2node; +} MarkTree; + + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "marktree.h.generated.h" +#endif + +#define MARKTREE_PAIRED_FLAG (((uint64_t)1) << 1) +#define MARKTREE_END_FLAG (((uint64_t)1) << 0) + +#endif // NVIM_MARKTREE_H 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; } |