diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 4569ebc713..4fc0ee4fdf 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -28,6 +28,7 @@ #include "nvim/map.h" #include "nvim/mark.h" #include "nvim/extmark.h" +#include "nvim/decoration.h" #include "nvim/fileio.h" #include "nvim/move.h" #include "nvim/syntax.h" @@ -1470,15 +1471,15 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, } // TODO(bfredl): synergize these two branches even more - if (ephemeral && redrawn_win && redrawn_win->w_buffer == buf) { + if (ephemeral && decor_state.buf == buf) { int attr_id = hl_id > 0 ? syn_id2attr(hl_id) : 0; VirtText *vt_allocated = NULL; if (kv_size(virt_text)) { vt_allocated = xmalloc(sizeof *vt_allocated); *vt_allocated = virt_text; } - decorations_add_ephemeral(attr_id, (int)line, (colnr_T)col, - (int)line2, (colnr_T)col2, vt_allocated); + decor_add_ephemeral(attr_id, (int)line, (colnr_T)col, + (int)line2, (colnr_T)col2, vt_allocated); } else { if (ephemeral) { api_set_error(err, kErrorTypeException, "not yet implemented"); @@ -1490,7 +1491,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, decor->hl_id = hl_id; decor->virt_text = virt_text; } else if (hl_id) { - decor = decoration_hl(hl_id); + decor = decor_hl(hl_id); } id = extmark_set(buf, (uint64_t)ns_id, id, (int)line, (colnr_T)col, @@ -1609,7 +1610,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, extmark_set(buf, ns_id, 0, (int)line, (colnr_T)col_start, end_line, (colnr_T)col_end, - decoration_hl(hl_id), kExtmarkNoUndo); + decor_hl(hl_id), kExtmarkNoUndo); return src_id; } @@ -1728,7 +1729,7 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, } - VirtText *existing = extmark_find_virttext(buf, (int)line, ns_id); + VirtText *existing = decor_find_virttext(buf, (int)line, ns_id); if (existing) { clear_virttext(existing); diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index a9b1676879..2c99d3426c 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -27,6 +27,7 @@ #include "nvim/map_defs.h" #include "nvim/map.h" #include "nvim/extmark.h" +#include "nvim/decoration.h" #include "nvim/option.h" #include "nvim/option_defs.h" #include "nvim/version.h" @@ -1652,11 +1653,11 @@ const char *describe_ns(NS ns_id) return "(UNKNOWN PLUGIN)"; } -DecorationProvider *get_provider(NS ns_id, bool force) +DecorProvider *get_provider(NS ns_id, bool force) { ssize_t i; - for (i = 0; i < (ssize_t)kv_size(decoration_providers); i++) { - DecorationProvider *item = &kv_A(decoration_providers, i); + for (i = 0; i < (ssize_t)kv_size(decor_providers); i++) { + DecorProvider *item = &kv_A(decor_providers, i); if (item->ns_id == ns_id) { return item; } else if (item->ns_id > ns_id) { @@ -1668,12 +1669,12 @@ DecorationProvider *get_provider(NS ns_id, bool force) return NULL; } - for (ssize_t j = (ssize_t)kv_size(decoration_providers)-1; j >= i; j++) { + for (ssize_t j = (ssize_t)kv_size(decor_providers)-1; j >= i; j++) { // allocates if needed: - (void)kv_a(decoration_providers, (size_t)j+1); - kv_A(decoration_providers, (size_t)j+1) = kv_A(decoration_providers, j); + (void)kv_a(decor_providers, (size_t)j+1); + kv_A(decor_providers, (size_t)j+1) = kv_A(decor_providers, j); } - DecorationProvider *item = &kv_a(decoration_providers, (size_t)i); + DecorProvider *item = &kv_a(decor_providers, (size_t)i); *item = DECORATION_PROVIDER_INIT(ns_id); return item; diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 7c6f07402b..271fd5b485 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -7,6 +7,7 @@ #include "nvim/vim.h" #include "nvim/getchar.h" #include "nvim/memory.h" +#include "nvim/decoration.h" #include "nvim/ex_eval.h" #include "nvim/lib/kvec.h" diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 717713b948..51f1af4eb5 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -355,7 +355,7 @@ void nvim_ui_pum_set_height(uint64_t channel_id, Integer height, Error *err) /// Note that this method is not to be confused with |nvim_ui_pum_set_height()|, /// which sets the number of visible items in the popup menu, while this /// function sets the bounding box of the popup menu, including visual -/// decorations such as boarders and sliders. Floats need not use the same font +/// elements such as borders and sliders. Floats need not use the same font /// size, nor be anchored to exact grid corners, so one can set floating-point /// numbers to the popup menu geometry. /// diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 63ca0f0e76..5050f1842d 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -41,7 +41,7 @@ #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/state.h" -#include "nvim/extmark.h" +#include "nvim/decoration.h" #include "nvim/syntax.h" #include "nvim/getchar.h" #include "nvim/os/input.h" @@ -2685,7 +2685,7 @@ void nvim__screenshot(String path) ui_call_screenshot(path); } -static void clear_provider(DecorationProvider *p) +static void clear_provider(DecorProvider *p) { NLUA_CLEAR_REF(p->redraw_start); NLUA_CLEAR_REF(p->redraw_buf); @@ -2711,7 +2711,7 @@ static void clear_provider(DecorationProvider *p) /// callback can return `false` to disable the provider until the next redraw. /// Similarily, return `false` in `on_win` will skip the `on_lines` calls /// for that window (but any extmarks set in `on_win` will still be used). -/// A plugin managing multiple sources of decorations should ideally only set +/// A plugin managing multiple sources of decoration should ideally only set /// one provider, and merge the sources internally. You can use multiple `ns_id` /// for the extmarks set/modified inside the callback anyway. /// @@ -2739,7 +2739,7 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, Error *err) FUNC_API_SINCE(7) FUNC_API_LUA_ONLY { - DecorationProvider *p = get_provider((NS)ns_id, true); + DecorProvider *p = get_provider((NS)ns_id, true); clear_provider(p); // regardless of what happens, it seems good idea to redraw diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index b72ca51517..0b89dff92f 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -568,7 +568,7 @@ struct file_buffer { // negative when lines were deleted wininfo_T *b_wininfo; // list of last used info for each window int b_mod_tick_syn; // last display tick syntax was updated - int b_mod_tick_deco; // last display tick decoration providers + int b_mod_tick_decor; // last display tick decoration providers // where invoked long b_mtime; // last change time of original file diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c new file mode 100644 index 0000000000..d411f22e7a --- /dev/null +++ b/src/nvim/decoration.c @@ -0,0 +1,331 @@ +// 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 +// +// +#include "nvim/vim.h" +#include "nvim/extmark.h" +#include "nvim/decoration.h" +#include "nvim/screen.h" +#include "nvim/syntax.h" +#include "nvim/highlight.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "decoration.c.generated.h" +#endif + +static PMap(uint64_t) *hl_decors; + +void decor_init(void) +{ + hl_decors = pmap_new(uint64_t)(); +} + +/// Add highlighting to a buffer, bounded by two cursor positions, +/// with an offset. +/// +/// TODO(bfredl): make decoration powerful enough so that this +/// can be done with a single ephemeral decoration. +/// +/// @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; + Decoration *decor = decor_hl(hl_id); + + // TODO(bfredl): if decoration had blocky mode, we could avoid this loop + for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum ++) { + int end_off = 0; + if (pos_start.lnum < lnum && lnum < pos_end.lnum) { + // TODO(bfredl): This is quite ad-hoc, but the space between |num| and + // text being highlighted is the indication of \n being part of the + // substituted text. But it would be more consistent to highlight + // a space _after_ the previous line instead (like highlight EOL list + // char) + hl_start = MAX(offset-1, 0); + end_off = 1; + hl_end = 0; + } else if (lnum == pos_start.lnum && lnum < pos_end.lnum) { + hl_start = pos_start.col + offset; + end_off = 1; + hl_end = 0; + } else if (pos_start.lnum < lnum && lnum == pos_end.lnum) { + hl_start = MAX(offset-1, 0); + 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_set(buf, (uint64_t)src_id, 0, + (int)lnum-1, hl_start, (int)lnum-1+end_off, hl_end, + decor, kExtmarkNoUndo); + } +} + +Decoration *decor_hl(int hl_id) +{ + assert(hl_id > 0); + Decoration **dp = (Decoration **)pmap_ref(uint64_t)(hl_decors, + (uint64_t)hl_id, true); + if (*dp) { + return *dp; + } + + Decoration *decor = xcalloc(1, sizeof(*decor)); + decor->hl_id = hl_id; + decor->shared = true; + *dp = decor; + return decor; +} + +void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor) +{ + if (decor->hl_id && row2 >= row1) { + redraw_buf_range_later(buf, row1+1, row2+1); + } + + if (kv_size(decor->virt_text)) { + redraw_buf_line_later(buf, row1+1); + } +} + +void decor_free(Decoration *decor) +{ + if (decor && !decor->shared) { + clear_virttext(&decor->virt_text); + xfree(decor); + } +} + +void 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; +} + +VirtText *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id) +{ + MarkTreeIter itr[1] = { 0 }; + 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; + } + ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, + mark.id, false); + if (item && (ns_id == 0 || ns_id == item->ns_id) + && item->decor && kv_size(item->decor->virt_text)) { + return &item->decor->virt_text; + } + marktree_itr_next(buf->b_marktree, itr); + } + return NULL; +} + +bool decor_redraw_reset(buf_T *buf, DecorState *state) +{ + state->row = -1; + state->buf = buf; + for (size_t i = 0; i < kv_size(state->active); i++) { + HlRange item = kv_A(state->active, i); + if (item.virt_text_owned) { + clear_virttext(item.virt_text); + xfree(item.virt_text); + } + } + kv_size(state->active) = 0; + return buf->b_extmark_index; +} + + +bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state) +{ + 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; + } + 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 (!item || !item->decor) { + // TODO(bfredl): dedicated flag for being a decoration? + goto next_mark; + } + Decoration *decor = item->decor; + + if ((!(mark.id&MARKTREE_END_FLAG) && altpos.row < top_row + && item && !kv_size(decor->virt_text)) + || ((mark.id&MARKTREE_END_FLAG) && altpos.row >= top_row)) { + goto next_mark; + } + + int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0; + VirtText *vt = kv_size(decor->virt_text) ? &decor->virt_text : NULL; + HlRange range; + if (mark.id&MARKTREE_END_FLAG) { + range = (HlRange){ altpos.row, altpos.col, mark.row, mark.col, + attr_id, vt, false }; + } else { + range = (HlRange){ mark.row, mark.col, altpos.row, + altpos.col, attr_id, vt, false }; + } + kv_push(state->active, range); + +next_mark: + if (marktree_itr_node_done(state->itr)) { + break; + } + marktree_itr_next(buf->b_marktree, state->itr); + } + + return true; // TODO(bfredl): check if available in the region +} + +bool decor_redraw_line(buf_T *buf, int row, DecorState *state) +{ + if (state->row == -1) { + decor_redraw_start(buf, row, state); + } + state->row = row; + state->col_until = -1; + return true; // TODO(bfredl): be more precise +} + +int decor_redraw_col(buf_T *buf, int col, DecorState *state) +{ + 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; + } + + if ((mark.id&MARKTREE_END_FLAG)) { + // TODO(bfredl): check decoration flag + goto next_mark; + } + mtpos_t endpos = marktree_lookup(buf->b_marktree, + mark.id|MARKTREE_END_FLAG, NULL); + + ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, + mark.id, false); + if (!item || !item->decor) { + // TODO(bfredl): dedicated flag for being a decoration? + goto next_mark; + } + Decoration *decor = item->decor; + + if (endpos.row < mark.row + || (endpos.row == mark.row && endpos.col <= mark.col)) { + if (item && !kv_size(decor->virt_text)) { + goto next_mark; + } + } + + int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0; + VirtText *vt = kv_size(decor->virt_text) ? &decor->virt_text : NULL; + kv_push(state->active, ((HlRange){ mark.row, mark.col, + endpos.row, endpos.col, + attr_id, vt, false })); + +next_mark: + marktree_itr_next(buf->b_marktree, state->itr); + } + + 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; + } + } 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 { + if (item.start_row == state->row) { + state->col_until = MIN(state->col_until, item.start_col-1); + } + } + } + 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); + } else if (item.virt_text_owned) { + clear_virttext(item.virt_text); + xfree(item.virt_text); + } + } + kv_size(state->active) = j; + state->current = attr; + return attr; +} + +void decor_redraw_end(DecorState *state) +{ + state->buf = NULL; +} + +VirtText *decor_redraw_virt_text(buf_T *buf, DecorState *state) +{ + decor_redraw_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; + } + } + return NULL; +} + +void decor_add_ephemeral(int attr_id, int start_row, int start_col, + int end_row, int end_col, VirtText *virt_text) +{ + kv_push(decor_state.active, + ((HlRange){ start_row, start_col, + end_row, end_col, + attr_id, virt_text, virt_text != NULL })); +} diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h new file mode 100644 index 0000000000..90fdc3dc43 --- /dev/null +++ b/src/nvim/decoration.h @@ -0,0 +1,71 @@ +#ifndef NVIM_DECORATION_H +#define NVIM_DECORATION_H + +#include "nvim/pos.h" +#include "nvim/buffer_defs.h" +#include "nvim/extmark_defs.h" + +// actual Decoration data is in extmark_defs.h + +typedef struct { + char *text; + int hl_id; +} VirtTextChunk; + +typedef kvec_t(VirtTextChunk) VirtText; +#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE) + +struct Decoration +{ + int hl_id; // highlight group + VirtText virt_text; + // TODO(bfredl): style, signs, etc + bool shared; // shared decoration, don't free +}; + +typedef struct { + int start_row; + int start_col; + int end_row; + int end_col; + int attr_id; + VirtText *virt_text; + bool virt_text_owned; +} HlRange; + +typedef struct { + MarkTreeIter itr[1]; + kvec_t(HlRange) active; + buf_T *buf; + int top_row; + int row; + int col_until; + int current; + VirtText *virt_text; +} DecorState; + +typedef struct { + NS ns_id; + bool active; + LuaRef redraw_start; + LuaRef redraw_buf; + LuaRef redraw_win; + LuaRef redraw_line; + LuaRef redraw_end; + LuaRef hl_def; + int hl_valid; +} DecorProvider; + +EXTERN kvec_t(DecorProvider) decor_providers INIT(= KV_INITIAL_VALUE); +EXTERN DecorState decor_state INIT(= { 0 }); + +#define DECORATION_PROVIDER_INIT(ns_id) (DecorProvider) \ + { ns_id, false, LUA_NOREF, LUA_NOREF, \ + LUA_NOREF, LUA_NOREF, LUA_NOREF, \ + LUA_NOREF, -1 } + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "decoration.h.generated.h" +#endif + +#endif // NVIM_DECORATION_H diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 41137bf2db..3e49d7845d 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -41,6 +41,7 @@ #include "nvim/main.h" #include "nvim/mark.h" #include "nvim/extmark.h" +#include "nvim/decoration.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/message.h" diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index 0de396fd1f..413ea4116a 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -22,12 +22,18 @@ // Deleting marks only happens when explicitly calling extmark_del, deleteing // over a range of marks will only move the marks. Deleting on a mark will // leave it in same position unless it is on the EOL of a line. +// +// Extmarks are used to implement buffer decoration. Decoration is mostly +// regarded as an application of extmarks, however for practical reasons code +// that deletes an extmark with decoration will call back into the decoration +// code for redrawing the line with the deleted decoration. #include #include "nvim/api/vim.h" #include "nvim/vim.h" #include "nvim/charset.h" #include "nvim/extmark.h" +#include "nvim/decoration.h" #include "nvim/buffer_updates.h" #include "nvim/memline.h" #include "nvim/pos.h" @@ -36,20 +42,11 @@ #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 "extmark.c.generated.h" #endif -static PMap(uint64_t) *hl_decors; - -void extmark_init(void) -{ - hl_decors = pmap_new(uint64_t)(); -} - static ExtmarkNs *buf_ns_ref(buf_T *buf, uint64_t ns_id, bool put) { if (!buf->b_extmark_ns) { if (!put) { @@ -96,8 +93,8 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id, ExtmarkItem it = map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, old_mark); if (it.decor) { - decoration_redraw(buf, row, row, it.decor); - free_decoration(it.decor); + decor_redraw(buf, row, row, it.decor); + decor_free(it.decor); } mark = marktree_revise(buf->b_marktree, itr); goto revised; @@ -130,7 +127,7 @@ revised: } if (decor) { - decoration_redraw(buf, row, end_row > -1 ? end_row : row, decor); + decor_redraw(buf, row, end_row > -1 ? end_row : row, decor); } return id; } @@ -179,8 +176,8 @@ bool extmark_del(buf_T *buf, uint64_t ns_id, uint64_t id) } if (item.decor) { - decoration_redraw(buf, pos.row, pos2.row, item.decor); - free_decoration(item.decor); + decor_redraw(buf, pos.row, pos2.row, item.decor); + decor_free(item.decor); } map_del(uint64_t, uint64_t)(ns->map, id); @@ -238,8 +235,8 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, marktree_del_itr(buf->b_marktree, itr, false); if (*del_status >= 0) { // we had a decor_id DecorItem it = kv_A(decors, *del_status); - decoration_redraw(buf, it.row1, mark.row, it.decor); - free_decoration(it.decor); + decor_redraw(buf, it.row1, mark.row, it.decor); + decor_free(it.decor); } map_del(uint64_t, ssize_t)(delete_set, mark.id); continue; @@ -264,8 +261,8 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, } map_put(uint64_t, ssize_t)(delete_set, other, decor_id); } else if (item.decor) { - decoration_redraw(buf, mark.row, mark.row, item.decor); - free_decoration(item.decor); + decor_redraw(buf, mark.row, mark.row, item.decor); + decor_free(item.decor); } 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); @@ -283,8 +280,8 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, marktree_del_itr(buf->b_marktree, itr, false); if (decor_id >= 0) { DecorItem it = kv_A(decors, decor_id); - decoration_redraw(buf, it.row1, pos.row, it.decor); - free_decoration(it.decor); + decor_redraw(buf, it.row1, pos.row, it.decor); + decor_free(it.decor); } }); map_clear(uint64_t, ssize_t)(delete_set); @@ -403,7 +400,7 @@ void extmark_free_all(buf_T *buf) map_foreach(buf->b_extmark_index, id, item, { (void)id; - free_decoration(item.decor); + decor_free(item.decor); }); map_free(uint64_t, ExtmarkItem)(buf->b_extmark_index); buf->b_extmark_index = NULL; @@ -737,295 +734,3 @@ uint64_t src2ns(Integer *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; - Decoration *decor = decoration_hl(hl_id); - - // TODO(bfredl): if decoration had blocky mode, we could avoid this loop - for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum ++) { - int end_off = 0; - if (pos_start.lnum < lnum && lnum < pos_end.lnum) { - // TODO(bfredl): This is quite ad-hoc, but the space between |num| and - // text being highlighted is the indication of \n being part of the - // substituted text. But it would be more consistent to highlight - // a space _after_ the previous line instead (like highlight EOL list - // char) - hl_start = MAX(offset-1, 0); - end_off = 1; - hl_end = 0; - } else if (lnum == pos_start.lnum && lnum < pos_end.lnum) { - hl_start = pos_start.col + offset; - end_off = 1; - hl_end = 0; - } else if (pos_start.lnum < lnum && lnum == pos_end.lnum) { - hl_start = MAX(offset-1, 0); - 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_set(buf, (uint64_t)src_id, 0, - (int)lnum-1, hl_start, (int)lnum-1+end_off, hl_end, - decor, kExtmarkNoUndo); - } -} - -Decoration *decoration_hl(int hl_id) -{ - assert(hl_id > 0); - Decoration **dp = (Decoration **)pmap_ref(uint64_t)(hl_decors, - (uint64_t)hl_id, true); - if (*dp) { - return *dp; - } - - Decoration *decor = xcalloc(1, sizeof(*decor)); - decor->hl_id = hl_id; - decor->shared = true; - *dp = decor; - return decor; -} - -void decoration_redraw(buf_T *buf, int row1, int row2, Decoration *decor) -{ - if (decor->hl_id && row2 >= row1) { - redraw_buf_range_later(buf, row1+1, row2+1); - } - - if (kv_size(decor->virt_text)) { - redraw_buf_line_later(buf, row1+1); - } -} - -void free_decoration(Decoration *decor) -{ - if (decor && !decor->shared) { - clear_virttext(&decor->virt_text); - xfree(decor); - } -} - -void 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; -} - -VirtText *extmark_find_virttext(buf_T *buf, int row, uint64_t ns_id) -{ - MarkTreeIter itr[1] = { 0 }; - 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; - } - ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, - mark.id, false); - if (item && (ns_id == 0 || ns_id == item->ns_id) - && item->decor && kv_size(item->decor->virt_text)) { - return &item->decor->virt_text; - } - marktree_itr_next(buf->b_marktree, itr); - } - return NULL; -} - -bool decorations_redraw_reset(buf_T *buf, DecorationRedrawState *state) -{ - state->row = -1; - for (size_t i = 0; i < kv_size(state->active); i++) { - HlRange item = kv_A(state->active, i); - if (item.virt_text_owned) { - clear_virttext(item.virt_text); - xfree(item.virt_text); - } - } - kv_size(state->active) = 0; - return buf->b_extmark_index; -} - - -bool decorations_redraw_start(buf_T *buf, int top_row, - DecorationRedrawState *state) -{ - 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; - } - 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 (!item || !item->decor) { - // TODO(bfredl): dedicated flag for being a decoration? - goto next_mark; - } - Decoration *decor = item->decor; - - if ((!(mark.id&MARKTREE_END_FLAG) && altpos.row < top_row - && item && !kv_size(decor->virt_text)) - || ((mark.id&MARKTREE_END_FLAG) && altpos.row >= top_row)) { - goto next_mark; - } - - int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0; - VirtText *vt = kv_size(decor->virt_text) ? &decor->virt_text : NULL; - HlRange range; - if (mark.id&MARKTREE_END_FLAG) { - range = (HlRange){ altpos.row, altpos.col, mark.row, mark.col, - attr_id, vt, false }; - } else { - range = (HlRange){ mark.row, mark.col, altpos.row, - altpos.col, attr_id, vt, false }; - } - kv_push(state->active, range); - -next_mark: - if (marktree_itr_node_done(state->itr)) { - break; - } - marktree_itr_next(buf->b_marktree, state->itr); - } - - return true; // TODO(bfredl): check if available in the region -} - -bool decorations_redraw_line(buf_T *buf, int row, DecorationRedrawState *state) -{ - if (state->row == -1) { - decorations_redraw_start(buf, row, state); - } - state->row = row; - state->col_until = -1; - return true; // TODO(bfredl): be more precise -} - -int decorations_redraw_col(buf_T *buf, int col, DecorationRedrawState *state) -{ - 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; - } - - if ((mark.id&MARKTREE_END_FLAG)) { - // TODO(bfredl): check decorations flag - goto next_mark; - } - mtpos_t endpos = marktree_lookup(buf->b_marktree, - mark.id|MARKTREE_END_FLAG, NULL); - - ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, - mark.id, false); - if (!item || !item->decor) { - // TODO(bfredl): dedicated flag for being a decoration? - goto next_mark; - } - Decoration *decor = item->decor; - - if (endpos.row < mark.row - || (endpos.row == mark.row && endpos.col <= mark.col)) { - if (item && !kv_size(decor->virt_text)) { - goto next_mark; - } - } - - int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0; - VirtText *vt = kv_size(decor->virt_text) ? &decor->virt_text : NULL; - kv_push(state->active, ((HlRange){ mark.row, mark.col, - endpos.row, endpos.col, - attr_id, vt, false })); - -next_mark: - marktree_itr_next(buf->b_marktree, state->itr); - } - - 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; - } - } 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 { - if (item.start_row == state->row) { - state->col_until = MIN(state->col_until, item.start_col-1); - } - } - } - 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); - } else if (item.virt_text_owned) { - clear_virttext(item.virt_text); - xfree(item.virt_text); - } - } - kv_size(state->active) = j; - state->current = attr; - return attr; -} - -VirtText *decorations_redraw_virt_text(buf_T *buf, DecorationRedrawState *state) -{ - decorations_redraw_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; - } - } - return NULL; -} diff --git a/src/nvim/extmark.h b/src/nvim/extmark.h index 101527ab4f..1bc42322a3 100644 --- a/src/nvim/extmark.h +++ b/src/nvim/extmark.h @@ -79,29 +79,6 @@ struct undo_object { }; -typedef struct { - int start_row; - int start_col; - int end_row; - int end_col; - int attr_id; - VirtText *virt_text; - bool virt_text_owned; -} HlRange; - -typedef struct { - MarkTreeIter itr[1]; - kvec_t(HlRange) active; - int top_row; - int row; - int col_until; - int current; - VirtText *virt_text; -} DecorationRedrawState; - -EXTERN kvec_t(DecorationProvider) decoration_providers INIT(= KV_INITIAL_VALUE); -EXTERN win_T *redrawn_win INIT(= NULL); // used for ephemeral extmarks - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "extmark.h.generated.h" #endif diff --git a/src/nvim/extmark_defs.h b/src/nvim/extmark_defs.h index 382aaf6ed3..784280dace 100644 --- a/src/nvim/extmark_defs.h +++ b/src/nvim/extmark_defs.h @@ -4,21 +4,7 @@ #include "nvim/types.h" #include "nvim/lib/kvec.h" -typedef struct { - char *text; - int hl_id; -} VirtTextChunk; - -typedef kvec_t(VirtTextChunk) VirtText; -#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE) - -typedef struct -{ - int hl_id; // highlight group - VirtText virt_text; - // TODO(bfredl): style, signs, etc - bool shared; // shared decoration, don't free -} Decoration; +typedef struct Decoration Decoration; typedef struct { @@ -42,21 +28,4 @@ typedef enum { kExtmarkUndoNoRedo, // Operation should be undoable, but not redoable } ExtmarkOp; -typedef struct { - NS ns_id; - bool active; - LuaRef redraw_start; - LuaRef redraw_buf; - LuaRef redraw_win; - LuaRef redraw_line; - LuaRef redraw_end; - LuaRef hl_def; - int hl_valid; -} DecorationProvider; - -#define DECORATION_PROVIDER_INIT(ns_id) (DecorationProvider) \ - { ns_id, false, LUA_NOREF, LUA_NOREF, \ - LUA_NOREF, LUA_NOREF, LUA_NOREF, \ - LUA_NOREF, -1 } - #endif // NVIM_EXTMARK_DEFS_H diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 8403615166..898ff4ebfe 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -156,7 +156,7 @@ static ColorKey colored_key(NS ns_id, int syn_id) void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id) { - DecorationProvider *p = get_provider(ns_id, true); + DecorProvider *p = get_provider(ns_id, true); int attr_id = link_id > 0 ? -1 : hl_get_syn_attr(ns_id, hl_id, attrs); ColorItem it = { .attr_id = attr_id, .link_id = link_id, @@ -175,7 +175,7 @@ int ns_get_hl(NS ns_id, int hl_id, bool link) ns_id = ns_hl_active; } - DecorationProvider *p = get_provider(ns_id, true); + DecorProvider *p = get_provider(ns_id, true); ColorItem it = map_get(ColorKey, ColorItem)(ns_hl, colored_key(ns_id, hl_id)); // TODO(bfredl): map_ref true even this? bool valid_cache = it.version >= p->hl_valid; diff --git a/src/nvim/main.c b/src/nvim/main.c index 27bf5c45c6..99a3657bf9 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -23,7 +23,7 @@ #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" -#include "nvim/extmark.h" +#include "nvim/decoration.h" #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/getchar.h" @@ -163,7 +163,7 @@ void early_init(mparm_T *paramp) env_init(); fs_init(); handle_init(); - extmark_init(); + decor_init(); eval_init(); // init global variables init_path(argv0 ? argv0 : "nvim"); init_normal_cmds(); // Init the table of Normal mode commands. diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 36a6ae59f8..8998f9037e 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -88,6 +88,7 @@ #include "nvim/main.h" #include "nvim/mark.h" #include "nvim/extmark.h" +#include "nvim/decoration.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" @@ -124,7 +125,7 @@ #define MB_FILLER_CHAR '<' /* character used when a double-width character * doesn't fit. */ -typedef kvec_withinit_t(DecorationProvider *, 4) Providers; +typedef kvec_withinit_t(DecorProvider *, 4) Providers; // temporary buffer for rendering a single screenline, so it can be // compared with previous contents to calculate smallest delta. @@ -473,8 +474,8 @@ int update_screen(int type) Providers providers; kvi_init(providers); - for (size_t i = 0; i < kv_size(decoration_providers); i++) { - DecorationProvider *p = &kv_A(decoration_providers, i); + for (size_t i = 0; i < kv_size(decor_providers); i++) { + DecorProvider *p = &kv_A(decor_providers, i); if (!p->active) { continue; } @@ -556,16 +557,16 @@ int update_screen(int type) buf->b_mod_tick_syn = display_tick; } - if (buf->b_mod_tick_deco < display_tick) { + if (buf->b_mod_tick_decor < display_tick) { for (size_t i = 0; i < kv_size(providers); i++) { - DecorationProvider *p = kv_A(providers, i); + DecorProvider *p = kv_A(providers, i); if (p && p->redraw_buf != LUA_NOREF) { FIXED_TEMP_ARRAY(args, 1); args.items[0] = BUFFER_OBJ(buf->handle); provider_invoke(p->ns_id, "buf", p->redraw_buf, args, true); } } - buf->b_mod_tick_deco = display_tick; + buf->b_mod_tick_decor = display_tick; } } } @@ -579,8 +580,6 @@ int update_screen(int type) FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - redrawn_win = wp; - if (wp->w_redr_type == CLEAR && wp->w_floating && wp->w_grid.chars) { grid_invalidate(&wp->w_grid); wp->w_redr_type = NOT_VALID; @@ -598,8 +597,6 @@ int update_screen(int type) if (wp->w_redr_status) { win_redr_status(wp); } - - redrawn_win = NULL; } end_search_hl(); @@ -631,7 +628,7 @@ int update_screen(int type) did_intro = TRUE; for (size_t i = 0; i < kv_size(providers); i++) { - DecorationProvider *p = kv_A(providers, i); + DecorProvider *p = kv_A(providers, i); if (!p->active) { continue; } @@ -701,18 +698,6 @@ bool win_cursorline_standout(const win_T *wp) || (wp->w_p_cole > 0 && (VIsual_active || !conceal_cursor_line(wp))); } -static DecorationRedrawState decorations; - -void decorations_add_ephemeral(int attr_id, - int start_row, int start_col, - int end_row, int end_col, VirtText *virt_text) -{ - kv_push(decorations.active, - ((HlRange){ start_row, start_col, - end_row, end_col, - attr_id, virt_text, virt_text != NULL })); -} - /* * Update a single window. * @@ -1306,7 +1291,7 @@ static void win_update(win_T *wp, Providers *providers) srow = 0; lnum = wp->w_topline; // first line shown in window - decorations_redraw_reset(buf, &decorations); + decor_redraw_reset(buf, &decor_state); Providers line_providers; kvi_init(line_providers); @@ -1316,7 +1301,7 @@ static void win_update(win_T *wp, Providers *providers) : (wp->w_topline + wp->w_height_inner)); for (size_t k = 0; k < kv_size(*providers); k++) { - DecorationProvider *p = kv_A(*providers, k); + DecorProvider *p = kv_A(*providers, k); if (p && p->redraw_win != LUA_NOREF) { FIXED_TEMP_ARRAY(args, 4); args.items[0] = WINDOW_OBJ(wp->handle); @@ -1755,6 +1740,7 @@ static void win_update(win_T *wp, Providers *providers) } } + /* restore got_int, unless CTRL-C was hit while redrawing */ if (!got_int) got_int = save_got_int; @@ -2102,7 +2088,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int prev_c1 = 0; // first composing char for prev_c bool search_attr_from_match = false; // if search_attr is from :match - bool has_decorations = false; // this buffer has decorations + bool has_decor = false; // this buffer has decoration bool do_virttext = false; // draw virtual text for this line char_u buf_fold[FOLD_TEXT_LEN + 1]; // Hold value returned by get_foldtext @@ -2170,18 +2156,18 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, } } - has_decorations = decorations_redraw_line(wp->w_buffer, lnum-1, - &decorations); + has_decor = decor_redraw_line(wp->w_buffer, lnum-1, + &decor_state); for (size_t k = 0; k < kv_size(*providers); k++) { - DecorationProvider *p = kv_A(*providers, k); + DecorProvider *p = kv_A(*providers, k); if (p && p->redraw_line != LUA_NOREF) { FIXED_TEMP_ARRAY(args, 3); args.items[0] = WINDOW_OBJ(wp->handle); args.items[1] = BUFFER_OBJ(buf->handle); args.items[2] = INTEGER_OBJ(lnum-1); if (provider_invoke(p->ns_id, "line", p->redraw_line, args, true)) { - has_decorations = true; + has_decor = true; } else { // return 'false' or error: skip rest of this window kv_A(*providers, k) = NULL; @@ -2191,7 +2177,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, } } - if (has_decorations) { + if (has_decor) { extra_check = true; } @@ -2377,7 +2363,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, } // If this line has a sign with line highlighting set line_attr. - // TODO(bfredl, vigoux): this should not take priority over decorations! + // TODO(bfredl, vigoux): this should not take priority over decoration! v = buf_getsigntype(wp->w_buffer, lnum, SIGN_LINEHL, 0, 1); if (v != 0) { line_attr = sign_get_attr((int)v, SIGN_LINEHL); @@ -3402,9 +3388,9 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, char_attr = hl_combine_attr(spell_attr, char_attr); } - if (has_decorations && v > 0) { - int extmark_attr = decorations_redraw_col(wp->w_buffer, (colnr_T)v-1, - &decorations); + if (has_decor && v > 0) { + int extmark_attr = decor_redraw_col(wp->w_buffer, (colnr_T)v-1, + &decor_state); if (extmark_attr != 0) { if (!attr_pri) { char_attr = hl_combine_attr(char_attr, extmark_attr); @@ -3906,8 +3892,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, kv_push(virt_text, ((VirtTextChunk){ .text = err_text, .hl_id = hl_err })); do_virttext = true; - } else if (has_decorations) { - VirtText *vp = decorations_redraw_virt_text(wp->w_buffer, &decorations); + } else if (has_decor) { + VirtText *vp = decor_redraw_virt_text(wp->w_buffer, &decor_state); if (vp) { virt_text = *vp; do_virttext = true;