aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/decoration.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/decoration.c')
-rw-r--r--src/nvim/decoration.c331
1 files changed, 331 insertions, 0 deletions
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 }));
+}