aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/api/buffer.c79
-rw-r--r--src/nvim/buffer.c63
-rw-r--r--src/nvim/bufhl_defs.h15
-rw-r--r--src/nvim/screen.c207
4 files changed, 288 insertions, 76 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 67a4b70c9e..16196ec910 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -967,6 +967,85 @@ void nvim_buf_clear_highlight(Buffer buffer,
bufhl_clear_line_range(buf, (int)src_id, (int)line_start+1, (int)line_end);
}
+
+/// Set the virtual text (annotation) for a buffer line.
+///
+/// By default (and currently the only option) the text will be placed after
+/// the buffer text. Virtual text will never cause reflow, rather virtual
+/// text will be truncated at the end of the screen line. The virtual text will
+/// begin after one cell to the right of the ordinary text, this will contain
+/// the |lcs-eol| char if set, otherwise just be a space.
+///
+/// @param buffer Buffer handle
+/// @param src_id Source group to use or 0 to use a new group,
+/// or -1 for a ungrouped annotation
+/// @param line Line to annotate with virtual text (zero-indexed)
+/// @param chunks A list of [text, hl_group] arrays, each representing a
+/// text chunk with specified highlight. `hl_group` element
+/// can be omitted for no highlight.
+/// @param opts Optional parameters. Currently not used.
+/// @param[out] err Error details, if any
+/// @return The src_id that was used
+Integer nvim_buf_set_virtual_text(Buffer buffer,
+ Integer src_id,
+ Integer line,
+ Array chunks,
+ Dictionary opts,
+ Error *err)
+ FUNC_API_SINCE(5)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return 0;
+ }
+
+ if (line < 0 || line >= MAXLNUM) {
+ api_set_error(err, kErrorTypeValidation, "Line number outside range");
+ return 0;
+ }
+
+ if (opts.size > 0) {
+ api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ 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 = xstrdup(str.size > 0 ? str.data : "");
+
+ 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 }));
+ }
+
+ src_id = bufhl_add_virt_text(buf, (int)src_id, (linenr_T)line+1,
+ virt_text);
+ return src_id;
+
+free_exit:
+ kv_destroy(virt_text);
+ return 0;
+}
+
// Check if deleting lines made the cursor position invalid.
// Changed the lines from "lo" to "hi" and added "extra" lines (negative if
// deleted).
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 384c7f77b2..a0ac8d3201 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -5375,6 +5375,45 @@ void bufhl_add_hl_pos_offset(buf_T *buf,
}
}
+int bufhl_add_virt_text(buf_T *buf,
+ int src_id,
+ linenr_T lnum,
+ VirtText virt_text)
+{
+ static int next_src_id = 1;
+ if (src_id == 0) {
+ src_id = next_src_id++;
+ }
+
+ 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) {
+ changed_lines_buf(buf, lnum, lnum+1, 0);
+ redraw_buf_later(buf, VALID);
+ }
+ 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
@@ -5430,6 +5469,7 @@ void bufhl_clear_line_range(buf_T *buf,
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;
@@ -5445,14 +5485,25 @@ static BufhlLineStatus bufhl_clear_line(BufhlLine *lineinfo, int src_id,
}
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) {
+ if (kv_size(lineinfo->items) == 0 && kv_size(lineinfo->virt_text) == 0) {
kv_destroy(lineinfo->items);
return kBLSDeleted;
}
- return kv_size(lineinfo->items) != oldsize ? kBLSChanged : kBLSUnchanged;
+ return changed;
}
+
/// Remove all highlights and free the highlight data
void bufhl_clear_all(buf_T *buf)
{
@@ -5527,8 +5578,8 @@ bool bufhl_start_line(buf_T *buf, linenr_T lnum, BufhlLineInfo *info)
return false;
}
info->valid_to = -1;
- info->entries = lineinfo->items;
- return kv_size(info->entries) > 0;
+ info->line = lineinfo;
+ return true;
}
/// get highlighting at column col
@@ -5548,8 +5599,8 @@ int bufhl_get_attr(BufhlLineInfo *info, colnr_T col)
}
int attr = 0;
info->valid_to = MAXCOL;
- for (size_t i = 0; i < kv_size(info->entries); i++) {
- BufhlItem entry = kv_A(info->entries, i);
+ 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);
diff --git a/src/nvim/bufhl_defs.h b/src/nvim/bufhl_defs.h
index 14b1afa7d9..d0fb40ab88 100644
--- a/src/nvim/bufhl_defs.h
+++ b/src/nvim/bufhl_defs.h
@@ -14,16 +14,23 @@ typedef struct {
colnr_T stop; // last column to highlight
} BufhlItem;
-typedef kvec_t(BufhlItem) BufhlItemVec;
+typedef struct {
+ char *text;
+ int hl_id;
+} VirtTextChunk;
+
+typedef kvec_t(VirtTextChunk) VirtText;
typedef struct {
linenr_T line;
- BufhlItemVec items;
+ kvec_t(BufhlItem) items;
+ int virt_text_src;
+ VirtText virt_text;
} BufhlLine;
-#define BUFHLLINE_INIT(l) { l, KV_INITIAL_VALUE }
+#define BUFHLLINE_INIT(l) { l, KV_INITIAL_VALUE, 0, KV_INITIAL_VALUE }
typedef struct {
- BufhlItemVec entries;
+ BufhlLine *line;
int current;
colnr_T valid_to;
} BufhlLineInfo;
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 2e27bef3de..fe6a15c5fc 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -132,6 +132,15 @@ static schar_T *current_ScreenLine;
StlClickDefinition *tab_page_click_defs = NULL;
long tab_page_click_defs_size = 0;
+// for line_putchar. Contains the state that needs to be remembered from
+// putting one character to the next.
+typedef struct {
+ const char_u *p;
+ int prev_c; // previous Arabic character
+ int prev_c1; // first composing char for prev_c
+} LineState;
+#define LINE_STATE(p) { p, 0, 0 }
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "screen.c.generated.h"
#endif
@@ -1731,6 +1740,56 @@ static int compute_foldcolumn(win_T *wp, int col)
return fdc;
}
+/// Put a single char from an UTF-8 buffer into a line buffer.
+///
+/// Handles composing chars and arabic shaping state.
+static int line_putchar(LineState *s, schar_T *dest, int maxcells, bool rl)
+{
+ const char_u *p = s->p;
+ int cells = utf_ptr2cells(p);
+ int c_len = utfc_ptr2len(p);
+ int u8c, u8cc[MAX_MCO];
+ if (cells > maxcells) {
+ return -1;
+ }
+ u8c = utfc_ptr2char(p, u8cc);
+ if (*p < 0x80 && u8cc[0] == 0) {
+ schar_from_ascii(dest[0], *p);
+ s->prev_c = u8c;
+ } else {
+ if (p_arshape && !p_tbidi && arabic_char(u8c)) {
+ // Do Arabic shaping.
+ int pc, pc1, nc;
+ int pcc[MAX_MCO];
+ int firstbyte = *p;
+
+ // The idea of what is the previous and next
+ // character depends on 'rightleft'.
+ if (rl) {
+ pc = s->prev_c;
+ pc1 = s->prev_c1;
+ nc = utf_ptr2char(p + c_len);
+ s->prev_c1 = u8cc[0];
+ } else {
+ pc = utfc_ptr2char(p + c_len, pcc);
+ nc = s->prev_c;
+ pc1 = pcc[0];
+ }
+ s->prev_c = u8c;
+
+ u8c = arabic_shape(u8c, &firstbyte, &u8cc[0], pc, pc1, nc);
+ } else {
+ s->prev_c = u8c;
+ }
+ schar_from_cc(dest[0], u8c, u8cc);
+ }
+ if (cells > 1) {
+ dest[1][0] = 0;
+ }
+ s->p += c_len;
+ return cells;
+}
+
/*
* Display one folded line.
*/
@@ -1863,13 +1922,7 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T
* Right-left text is put in columns 0 - number-col, normal text is put
* in columns number-col - window-width.
*/
- int cells;
- int u8c, u8cc[MAX_MCO];
int idx;
- int c_len;
- char_u *p;
- int prev_c = 0; // previous Arabic character
- int prev_c1 = 0; // first composing char for prev_c
if (wp->w_p_rl) {
idx = off;
@@ -1877,50 +1930,20 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T
idx = off + col;
}
- // Store multibyte characters in ScreenLines[] et al. correctly.
- for (p = text; *p != NUL; ) {
- cells = utf_ptr2cells(p);
- c_len = utfc_ptr2len(p);
- if (col + cells > wp->w_width - (wp->w_p_rl ? col : 0)) {
- break;
- }
- u8c = utfc_ptr2char(p, u8cc);
- if (*p < 0x80 && u8cc[0] == 0) {
- schar_from_ascii(ScreenLines[idx], *p);
- prev_c = u8c;
- } else {
- if (p_arshape && !p_tbidi && arabic_char(u8c)) {
- // Do Arabic shaping.
- int pc, pc1, nc;
- int pcc[MAX_MCO];
- int firstbyte = *p;
-
- // The idea of what is the previous and next
- // character depends on 'rightleft'.
- if (wp->w_p_rl) {
- pc = prev_c;
- pc1 = prev_c1;
- nc = utf_ptr2char(p + c_len);
- prev_c1 = u8cc[0];
- } else {
- pc = utfc_ptr2char(p + c_len, pcc);
- nc = prev_c;
- pc1 = pcc[0];
- }
- prev_c = u8c;
+ LineState s = LINE_STATE(text);
- u8c = arabic_shape(u8c, &firstbyte, &u8cc[0], pc, pc1, nc);
- } else {
- prev_c = u8c;
- }
- schar_from_cc(ScreenLines[idx], u8c, u8cc);
- }
- if (cells > 1) {
- ScreenLines[idx + 1][0] = 0;
+ while (*s.p != NUL) {
+ // TODO(bfredl): cargo-culted from the old Vim code:
+ // if(col + cells > wp->w_width - (wp->w_p_rl ? col : 0)) { break; }
+ // This is obvious wrong. If Vim ever fixes this, solve for "cells" again
+ // in the correct condition.
+ int maxcells = wp->w_width - col - (wp->w_p_rl ? col : 0);
+ int cells = line_putchar(&s, &ScreenLines[idx], maxcells, wp->w_p_rl);
+ if (cells == -1) {
+ break;
}
col += cells;
idx += cells;
- p += c_len;
}
/* Fill the rest of the line with the fold filler */
@@ -2215,9 +2238,9 @@ win_line (
int did_line_attr = 0;
bool search_attr_from_match = false; // if search_attr is from :match
- bool has_bufhl = false; // this buffer has highlight matches
- int bufhl_attr = 0; // attributes desired by bufhl
BufhlLineInfo bufhl_info; // bufhl data for this line
+ bool has_bufhl = false; // this buffer has highlight matches
+ bool do_virttext = false; // draw virtual text for this line
/* draw_state: items that are drawn in sequence: */
#define WL_START 0 /* nothing done yet */
@@ -2279,8 +2302,13 @@ win_line (
}
if (bufhl_start_line(wp->w_buffer, lnum, &bufhl_info)) {
- has_bufhl = true;
- extra_check = true;
+ if (kv_size(bufhl_info.line->items)) {
+ has_bufhl = true;
+ extra_check = true;
+ }
+ if (kv_size(bufhl_info.line->virt_text)) {
+ do_virttext = true;
+ }
}
// Check for columns to display for 'colorcolumn'.
@@ -3429,7 +3457,7 @@ win_line (
}
if (has_bufhl && v > 0) {
- bufhl_attr = bufhl_get_attr(&bufhl_info, (colnr_T)v);
+ int bufhl_attr = bufhl_get_attr(&bufhl_info, (colnr_T)v);
if (bufhl_attr != 0) {
if (!attr_pri) {
char_attr = hl_combine_attr(char_attr, bufhl_attr);
@@ -3949,40 +3977,87 @@ win_line (
&& (int)wp->w_virtcol <
wp->w_width * (row - startrow + 1) + v
&& lnum != wp->w_cursor.lnum)
- || draw_color_col)
- && !wp->w_p_rl
- ) {
+ || draw_color_col || do_virttext)
+ && !wp->w_p_rl) {
int rightmost_vcol = 0;
int i;
- if (wp->w_p_cuc)
+ VirtText 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;
+
+ // Make sure alignment is the same regardless
+ // if listchars=eol:X is used or not.
+ bool delay_virttext = lcs_eol <= 0;
+
+ if (wp->w_p_cuc) {
rightmost_vcol = wp->w_virtcol;
- if (draw_color_col)
- /* determine rightmost colorcolumn to possibly draw */
- for (i = 0; color_cols[i] >= 0; ++i)
- if (rightmost_vcol < color_cols[i])
+ }
+
+ if (draw_color_col) {
+ // determine rightmost colorcolumn to possibly draw
+ for (i = 0; color_cols[i] >= 0; i++) {
+ if (rightmost_vcol < color_cols[i]) {
rightmost_vcol = color_cols[i];
+ }
+ }
+ }
int cuc_attr = win_hl_attr(wp, HLF_CUC);
int mc_attr = win_hl_attr(wp, HLF_MC);
while (col < wp->w_width) {
- schar_from_ascii(ScreenLines[off], ' ');
- col++;
+ int cells = -1;
+ if (do_virttext && !delay_virttext) {
+ if (*s.p == NUL) {
+ if (virt_pos < virt_text.size) {
+ s.p = (char_u *)kv_A(virt_text, virt_pos).text;
+ int hl_id = kv_A(virt_text, virt_pos).hl_id;
+ virt_attr = hl_id > 0 ? syn_id2attr(hl_id) : 0;
+ virt_pos++;
+ } else {
+ do_virttext = false;
+ }
+ }
+ if (*s.p != NUL) {
+ cells = line_putchar(&s, &ScreenLines[off], wp->w_width - col,
+ false);
+ }
+ }
+ delay_virttext = false;
+
+ if (cells == -1) {
+ schar_from_ascii(ScreenLines[off], ' ');
+ cells = 1;
+ }
+ col += cells;
if (draw_color_col) {
draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
}
+ int attr = 0;
if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol) {
- ScreenAttrs[off++] = cuc_attr;
+ attr = cuc_attr;
} else if (draw_color_col && VCOL_HLC == *color_cols) {
- ScreenAttrs[off++] = mc_attr;
- } else {
- ScreenAttrs[off++] = wp->w_hl_attr_normal;
+ attr = mc_attr;
+ }
+
+ if (do_virttext) {
+ attr = hl_combine_attr(attr, virt_attr);
}
- if (VCOL_HLC >= rightmost_vcol)
+ ScreenAttrs[off] = attr;
+ if (cells == 2) {
+ ScreenAttrs[off+1] = attr;
+ }
+ off += cells;
+
+ if (VCOL_HLC >= rightmost_vcol && *s.p == NUL
+ && virt_pos >= virt_text.size) {
break;
+ }
++vcol;
}