Merge pull request #8180 from bfredl/eol_text

support "virtual text" annotations (currently after EOL only)
This commit is contained in:
Björn Linse 2018-09-17 15:36:10 +02:00 committed by GitHub
commit 8de87c7b1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 429 additions and 78 deletions

View File

@ -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).

View File

@ -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->items) == 0) {
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 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);

View File

@ -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;

View File

@ -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)) {
LineState s = LINE_STATE(text);
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;
}
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;
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;
}
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 (VCOL_HLC >= rightmost_vcol)
if (do_virttext) {
attr = hl_combine_attr(attr, virt_attr);
}
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;
}

View File

@ -23,7 +23,10 @@ describe('Buffer highlighting', function()
[7] = {bold = true},
[8] = {underline = true, bold = true, foreground = Screen.colors.SlateBlue},
[9] = {foreground = Screen.colors.SlateBlue, underline = true},
[10] = {foreground = Screen.colors.Red}
[10] = {foreground = Screen.colors.Red},
[11] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
[12] = {foreground = Screen.colors.Blue1},
[13] = {background = Screen.colors.LightGrey},
})
end)
@ -77,7 +80,7 @@ describe('Buffer highlighting', function()
|
]])
clear_hl(-1, 0 , -1)
clear_hl(-1, 0, -1)
screen:expect([[
these are some lines |
^ |
@ -275,4 +278,140 @@ describe('Buffer highlighting', function()
|
]])
end)
it('supports virtual text annotations', function()
local set_virtual_text = curbufmeths.set_virtual_text
insert([[
1 + 2
3 +
x = 4]])
feed('O<esc>20A5, <esc>gg')
screen:expect([[
^1 + 2 |
3 + |
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
, 5, 5, 5, 5, 5, 5, |
x = 4 |
{1:~ }|
{1:~ }|
|
]])
local id1 = set_virtual_text(0, 0, {{"=", "Statement"}, {" 3", "Number"}}, {})
set_virtual_text(id1, 1, {{"ERROR:", "ErrorMsg"}, {" invalid syntax"}}, {})
local id2 = set_virtual_text(0, 2, {{"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."}}, {})
neq(id2, id1)
screen:expect([[
^1 + 2 {3:=}{2: 3} |
3 + {11:ERROR:} invalid syntax |
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
, 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s|
x = 4 |
{1:~ }|
{1:~ }|
|
]])
clear_hl(id1, 0, -1)
screen:expect([[
^1 + 2 |
3 + |
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
, 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s|
x = 4 |
{1:~ }|
{1:~ }|
|
]])
-- Handles doublewidth chars, leaving a space if truncating
-- in the middle of a char
set_virtual_text(id1, 1, {{"暗x事zz速野谷質結育副住新覚丸活解終事", "Comment"}}, {})
screen:expect([[
^1 + 2 |
3 + {12:x事zz速野谷質結育副住新覚丸活解終 }|
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
, 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s|
x = 4 |
{1:~ }|
{1:~ }|
|
]])
feed("2Gx")
screen:expect([[
1 + 2 |
^ + {12:x事zz速野谷質結育副住新覚丸活解終事}|
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
, 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s|
x = 4 |
{1:~ }|
{1:~ }|
|
]])
-- visual selection doesn't highlight virtual text
feed("ggVG")
screen:expect([[
{13:1 + 2} |
{13: +} {12:x事zz速野谷質結育副住新覚丸活解終事}|
{13:5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5}|
{13:, 5, 5, 5, 5, 5, 5, } Lorem ipsum dolor s|
^x{13: = 4} |
{1:~ }|
{1:~ }|
{7:-- VISUAL LINE --} |
]])
feed("<esc>")
screen:expect([[
1 + 2 |
+ {12:x事zz速野谷質結育副住新覚丸活解終事}|
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
, 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s|
^x = 4 |
{1:~ }|
{1:~ }|
|
]])
feed("2Gdd")
screen:expect([[
1 + 2 |
^5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
, 5, 5, 5, 5, 5, 5, Lorem ipsum dolor s|
x = 4 |
{1:~ }|
{1:~ }|
{1:~ }|
|
]])
-- listchars=eol:- works, and doesn't shift virtual text
command("set list")
screen:expect([[
1 + 2 |
^5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
, 5, 5, 5, 5, 5, 5,{1:-} Lorem ipsum dolor s|
x = 4 |
{1:~ }|
{1:~ }|
{1:~ }|
|
]])
clear_hl(-1, 0, -1)
screen:expect([[
1 + 2 |
^5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
, 5, 5, 5, 5, 5, 5,{1:-} |
x = 4 |
{1:~ }|
{1:~ }|
{1:~ }|
|
]])
end)
end)