diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index c55dc39605..e79a7a2de2 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1437,6 +1437,10 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, /// default /// - "combine": combine with background text color /// - "blend": blend with background text color. +/// - hl_eol : when true, for a multiline highlight covering the +/// EOL of a line, continue the highlight for the rest +/// of the screen line (just like for diff and +/// cursorline highlight). /// /// - ephemeral : for use with |nvim_set_decoration_provider| /// callbacks. The mark will only be used for the current @@ -1581,6 +1585,11 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, if (ERROR_SET(err)) { goto error; } + } else if (strequal("hl_eol", k.data)) { + decor.hl_eol = api_object_to_bool(*v, "hl_eol", false, err); + if (ERROR_SET(err)) { + goto error; + } } else if (strequal("hl_mode", k.data)) { if (v->type != kObjectTypeString) { api_set_error(err, kErrorTypeValidation, @@ -1669,7 +1678,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, if (ephemeral) { d = &decor; } else if (kv_size(decor.virt_text) - || decor.priority != DECOR_PRIORITY_BASE) { + || decor.priority != DECOR_PRIORITY_BASE + || decor.hl_eol) { // TODO(bfredl): this is a bit sketchy. eventually we should // have predefined decorations for both marks/ephemerals d = xcalloc(1, sizeof(*d)); @@ -1680,7 +1690,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, // TODO(bfredl): synergize these two branches even more if (ephemeral && decor_state.buf == buf) { - decor_add_ephemeral((int)line, (int)col, line2, col2, &decor, 0); + decor_add_ephemeral((int)line, (int)col, line2, col2, &decor); } else { if (ephemeral) { api_set_error(err, kErrorTypeException, "not yet implemented"); diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 52a48ae6fb..e39d2328f5 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -144,9 +144,9 @@ 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); + DecorRange item = kv_A(state->active, i); if (item.virt_text_owned) { - clear_virttext(&item.virt_text); + clear_virttext(&item.decor.virt_text); } } kv_size(state->active) = 0; @@ -190,14 +190,14 @@ bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state) if (mark.id&MARKTREE_END_FLAG) { decor_add(state, altpos.row, altpos.col, mark.row, mark.col, - decor, false, 0); + decor, false); } else { if (altpos.row == -1) { altpos.row = mark.row; altpos.col = mark.col; } decor_add(state, mark.row, mark.col, altpos.row, altpos.col, - decor, false, 0); + decor, false); } next_mark: @@ -222,22 +222,19 @@ bool decor_redraw_line(buf_T *buf, int row, DecorState *state) } static void decor_add(DecorState *state, int start_row, int start_col, - int end_row, int end_col, Decoration *decor, bool owned, - DecorPriority priority) + int end_row, int end_col, Decoration *decor, bool owned) { int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0; - HlRange range = { start_row, start_col, end_row, end_col, - attr_id, MAX(priority, decor->priority), - decor->virt_text, - decor->virt_text_pos, decor->virt_text_hide, decor->hl_mode, + DecorRange range = { start_row, start_col, end_row, end_col, + *decor, attr_id, kv_size(decor->virt_text) && owned, -1 }; kv_pushp(state->active); size_t index; for (index = kv_size(state->active)-1; index > 0; index--) { - HlRange item = kv_A(state->active, index-1); - if (item.priority <= range.priority) { + DecorRange item = kv_A(state->active, index-1); + if (item.decor.priority <= range.decor.priority) { break; } kv_A(state->active, index) = kv_A(state->active, index-1); @@ -291,7 +288,7 @@ int decor_redraw_col(buf_T *buf, int col, int virt_col, bool hidden, } decor_add(state, mark.row, mark.col, endpos.row, endpos.col, - decor, false, 0); + decor, false); next_mark: marktree_itr_next(buf->b_marktree, state->itr); @@ -300,11 +297,11 @@ next_mark: 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); + DecorRange 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 && kv_size(item.virt_text))) { + if (!(item.start_row >= state->row && kv_size(item.decor.virt_text))) { keep = false; } } else { @@ -324,13 +321,13 @@ next_mark: attr = hl_combine_attr(attr, item.attr_id); } if ((item.start_row == state->row && item.start_col <= col) - && kv_size(item.virt_text) && item.virt_col == -1) { - item.virt_col = (item.virt_text_hide && hidden) ? -2 : virt_col; + && kv_size(item.decor.virt_text) && item.virt_col == -1) { + item.virt_col = (item.decor.virt_text_hide && hidden) ? -2 : virt_col; } if (keep) { kv_A(state->active, j++) = item; } else if (item.virt_text_owned) { - clear_virttext(&item.virt_text); + clear_virttext(&item.decor.virt_text); } } kv_size(state->active) = j; @@ -343,28 +340,34 @@ void decor_redraw_end(DecorState *state) state->buf = NULL; } -VirtText decor_redraw_virt_text(buf_T *buf, DecorState *state) +VirtText decor_redraw_eol(buf_T *buf, DecorState *state, int *eol_attr) { decor_redraw_col(buf, MAXCOL, MAXCOL, false, state); + VirtText text = VIRTTEXT_EMPTY; for (size_t i = 0; i < kv_size(state->active); i++) { - HlRange item = kv_A(state->active, i); - if (item.start_row == state->row && kv_size(item.virt_text) - && item.virt_text_pos == kVTEndOfLine) { - return item.virt_text; + DecorRange item = kv_A(state->active, i); + if (!kv_size(text) + && item.start_row == state->row && kv_size(item.decor.virt_text) + && item.decor.virt_text_pos == kVTEndOfLine) { + text = item.decor.virt_text; + } + + if (item.decor.hl_eol && item.start_row <= state->row) { + *eol_attr = hl_combine_attr(*eol_attr, item.attr_id); } } - return VIRTTEXT_EMPTY; + + return text; } void decor_add_ephemeral(int start_row, int start_col, int end_row, int end_col, - Decoration *decor, DecorPriority priority) + Decoration *decor) { if (end_row == -1) { end_row = start_row; end_col = start_col; } - decor_add(&decor_state, start_row, start_col, end_row, end_col, decor, true, - priority); + decor_add(&decor_state, start_row, start_col, end_row, end_col, decor, true); } diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index c5424a1642..08d69060f0 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -37,33 +37,28 @@ struct Decoration VirtTextPos virt_text_pos; bool virt_text_hide; HlMode hl_mode; + bool hl_eol; // TODO(bfredl): style, signs, etc DecorPriority priority; bool shared; // shared decoration, don't free }; #define DECORATION_INIT { 0, KV_INITIAL_VALUE, kVTEndOfLine, false, \ - kHlModeUnknown, DECOR_PRIORITY_BASE, false } + kHlModeUnknown, false, DECOR_PRIORITY_BASE, false } typedef struct { int start_row; int start_col; int end_row; int end_col; - int attr_id; - // TODO(bfredl): embed decoration instead, perhaps using an arena - // for ephemerals? - DecorPriority priority; - VirtText virt_text; - VirtTextPos virt_text_pos; - bool virt_text_hide; - HlMode hl_mode; + Decoration decor; + int attr_id; // cached lookup of decor.hl_id bool virt_text_owned; int virt_col; -} HlRange; +} DecorRange; typedef struct { MarkTreeIter itr[1]; - kvec_t(HlRange) active; + kvec_t(DecorRange) active; buf_T *buf; int top_row; int row; diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 9fb2eb2772..9f11bae16c 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -3951,7 +3951,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, .hl_id = hl_err })); do_virttext = true; } else if (has_decor) { - virt_text = decor_redraw_virt_text(wp->w_buffer, &decor_state); + virt_text = decor_redraw_eol(wp->w_buffer, &decor_state, &line_attr); if (kv_size(virt_text)) { do_virttext = true; } @@ -4381,11 +4381,12 @@ void draw_virt_text(buf_T *buf, int *end_col, int max_col) { DecorState *state = &decor_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 && kv_size(item->virt_text) - && item->virt_text_pos == kVTOverlay + DecorRange *item = &kv_A(state->active, i); + if (item->start_row == state->row && kv_size(item->decor.virt_text) + && item->decor.virt_text_pos == kVTOverlay && item->virt_col >= 0) { - VirtText vt = item->virt_text; + VirtText vt = item->decor.virt_text; + HlMode hl_mode = item->decor.hl_mode; LineState s = LINE_STATE(""); int virt_attr = 0; int col = item->virt_col; @@ -4405,9 +4406,9 @@ void draw_virt_text(buf_T *buf, int *end_col, int max_col) } int attr; bool through = false; - if (item->hl_mode == kHlModeCombine) { + if (hl_mode == kHlModeCombine) { attr = hl_combine_attr(linebuf_attr[col], virt_attr); - } else if (item->hl_mode == kHlModeBlend) { + } else if (hl_mode == kHlModeBlend) { through = (*s.p == ' '); attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through); } else { diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index 295a54aec8..82d3075be2 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -29,6 +29,7 @@ describe('decorations providers', function() [10] = {italic = true, background = Screen.colors.Magenta}; [11] = {foreground = Screen.colors.Red, background = tonumber('0x005028')}; [12] = {foreground = tonumber('0x990000')}; + [13] = {background = Screen.colors.LightBlue}; } end) @@ -331,6 +332,37 @@ describe('decorations providers', function() | ]]} end) + + it('can highlight beyond EOL', function() + insert(mulholland) + setup_provider [[ + local test_ns = a.nvim_create_namespace "veberod" + function on_do(event, ...) + if event == "line" then + local win, buf, line = ... + if string.find(a.nvim_buf_get_lines(buf, line, line+1, true)[1], "buf") then + a.nvim_buf_set_extmark(buf, test_ns, line, 0, { + end_line = line+1; + hl_group = 'DiffAdd'; + hl_eol = true; + ephemeral = true; + }) + end + end + end + ]] + + screen:expect{grid=[[ + // just to see if there was an accident | + // on Mulholland Drive | + try_start(); | + {13:bufref_T save_buf; }| + {13:switch_buffer(&save_buf, buf); }| + posp = getmark(mark, false); | + {13:restore_buffer(&save_buf);^ }| + | + ]]} + end) end) describe('extmark decorations', function()