aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorzeertzjq <zeertzjq@outlook.com>2025-02-20 21:47:12 +0800
committerGitHub <noreply@github.com>2025-02-20 21:47:12 +0800
commit51cf84daf9612574978731e66db45a52136b8899 (patch)
tree5ac6f72c40e5219356ac198e1c20ca4df55354cf /src
parent574ea6a1911b740bb611f0b658a83f606b6837bc (diff)
downloadrneovim-51cf84daf9612574978731e66db45a52136b8899.tar.gz
rneovim-51cf84daf9612574978731e66db45a52136b8899.tar.bz2
rneovim-51cf84daf9612574978731e66db45a52136b8899.zip
feat(marks): virtual lines support horizontal scrolling (#32497)
Add a new field `virt_lines_overflow` that enables horizontal scrolling for virtual lines when set to "scroll".
Diffstat (limited to 'src')
-rw-r--r--src/nvim/api/extmark.c20
-rw-r--r--src/nvim/api/keysets_defs.h1
-rw-r--r--src/nvim/api/private/helpers.h4
-rw-r--r--src/nvim/decoration.c8
-rw-r--r--src/nvim/decoration_defs.h9
-rw-r--r--src/nvim/drawline.c67
6 files changed, 79 insertions, 30 deletions
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index 2cf9221e52..ef09dbb0aa 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -448,7 +448,11 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// - virt_lines_leftcol: Place virtual lines in the leftmost
/// column of the window, bypassing
/// sign and number columns.
-///
+/// - virt_lines_overflow: controls how to handle virtual lines wider
+/// than the window. Currently takes the one of the following values:
+/// - "trunc": truncate virtual lines on the right (default).
+/// - "scroll": virtual lines can scroll horizontally with 'nowrap',
+/// otherwise the same as "trunc".
/// - ephemeral : for use with |nvim_set_decoration_provider()|
/// callbacks. The mark will only be used for the current
/// redraw cycle, and not be permantently stored in the
@@ -669,7 +673,17 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
}
}
- bool virt_lines_leftcol = opts->virt_lines_leftcol;
+ int virt_lines_flags = opts->virt_lines_leftcol ? kVLLeftcol : 0;
+ if (HAS_KEY(opts, set_extmark, virt_lines_overflow)) {
+ String str = opts->virt_lines_overflow;
+ if (strequal("scroll", str.data)) {
+ virt_lines_flags |= kVLScroll;
+ } else if (!strequal("trunc", str.data)) {
+ VALIDATE_S(false, "virt_lines_overflow", str.data, {
+ goto error;
+ });
+ }
+ }
if (HAS_KEY(opts, set_extmark, virt_lines)) {
Array a = opts->virt_lines;
@@ -679,7 +693,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
});
int dummig;
VirtText jtem = parse_virt_text(a.items[j].data.array, err, &dummig);
- kv_push(virt_lines.data.virt_lines, ((struct virt_line){ jtem, virt_lines_leftcol }));
+ kv_push(virt_lines.data.virt_lines, ((struct virt_line){ jtem, virt_lines_flags }));
if (ERROR_SET(err)) {
goto error;
}
diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h
index 6625908cda..b3015911f9 100644
--- a/src/nvim/api/keysets_defs.h
+++ b/src/nvim/api/keysets_defs.h
@@ -45,6 +45,7 @@ typedef struct {
Array virt_lines;
Boolean virt_lines_above;
Boolean virt_lines_leftcol;
+ String virt_lines_overflow;
Boolean strict;
String sign_text;
HLGroupID sign_hl_group;
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index d581c6bc10..5cf1ca4d34 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -64,7 +64,7 @@
#define NIL ((Object)OBJECT_INIT)
#define NULL_STRING ((String)STRING_INIT)
-#define HAS_KEY(d, typ, key) (((d)->is_set__##typ##_ & (1 << KEYSET_OPTIDX_##typ##__##key)) != 0)
+#define HAS_KEY(d, typ, key) (((d)->is_set__##typ##_ & (1ULL << KEYSET_OPTIDX_##typ##__##key)) != 0)
#define GET_BOOL_OR_TRUE(d, typ, key) (HAS_KEY(d, typ, key) ? (d)->key : true)
@@ -75,7 +75,7 @@
kv_push_c(dict, ((KeyValuePair) { .key = cstr_as_string(k), .value = v }))
#define PUT_KEY(d, typ, key, v) \
- do { (d).is_set__##typ##_ |= (1 << KEYSET_OPTIDX_##typ##__##key); (d).key = v; } while (0)
+ do { (d).is_set__##typ##_ |= (1ULL << KEYSET_OPTIDX_##typ##__##key); (d).key = v; } while (0)
#define ADD(array, item) \
kv_push(array, item)
diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c
index 3ca3f2904b..d41e7e32be 100644
--- a/src/nvim/decoration.c
+++ b/src/nvim/decoration.c
@@ -1169,15 +1169,17 @@ void decor_to_dict_legacy(Dict *dict, DecorInline decor, bool hl_name, Arena *ar
if (virt_lines) {
Array all_chunks = arena_array(arena, kv_size(virt_lines->data.virt_lines));
- bool virt_lines_leftcol = false;
+ int virt_lines_flags = 0;
for (size_t i = 0; i < kv_size(virt_lines->data.virt_lines); i++) {
- virt_lines_leftcol = kv_A(virt_lines->data.virt_lines, i).left_col;
+ virt_lines_flags = kv_A(virt_lines->data.virt_lines, i).flags;
Array chunks = virt_text_to_array(kv_A(virt_lines->data.virt_lines, i).line, hl_name, arena);
ADD(all_chunks, ARRAY_OBJ(chunks));
}
PUT_C(*dict, "virt_lines", ARRAY_OBJ(all_chunks));
PUT_C(*dict, "virt_lines_above", BOOLEAN_OBJ(virt_lines->flags & kVTLinesAbove));
- PUT_C(*dict, "virt_lines_leftcol", BOOLEAN_OBJ(virt_lines_leftcol));
+ PUT_C(*dict, "virt_lines_leftcol", BOOLEAN_OBJ(virt_lines_flags & kVLLeftcol));
+ PUT_C(*dict, "virt_lines_overflow",
+ CSTR_AS_OBJ(virt_lines_flags & kVLScroll ? "scroll" : "trunc"));
priority = virt_lines->priority;
}
diff --git a/src/nvim/decoration_defs.h b/src/nvim/decoration_defs.h
index 36ad6df7a0..8be988cd82 100644
--- a/src/nvim/decoration_defs.h
+++ b/src/nvim/decoration_defs.h
@@ -26,7 +26,14 @@ typedef enum {
kVPosWinCol,
} VirtTextPos;
-typedef kvec_t(struct virt_line { VirtText line; bool left_col; }) VirtLines;
+/// Flags for virtual lines
+enum {
+ kVLLeftcol = 1, ///< Start at left window edge, ignoring number column, etc.
+ kVLScroll = 2, ///< Can scroll horizontally with 'nowrap'
+ // kVLWrap = 4,
+};
+
+typedef kvec_t(struct virt_line { VirtText line; int flags; }) VirtLines;
typedef uint16_t DecorPriority;
#define DECOR_PRIORITY_BASE 0x1000
diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c
index 0988f668d5..c2a14bdfd5 100644
--- a/src/nvim/drawline.c
+++ b/src/nvim/drawline.c
@@ -231,7 +231,8 @@ static int line_putchar(buf_T *buf, const char **pp, schar_T *dest, int maxcells
}
if (*p == TAB) {
- cells = MIN(tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array), maxcells);
+ cells = tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array);
+ cells = MIN(cells, maxcells);
}
// When overwriting the left half of a double-width char, clear the right half.
@@ -345,7 +346,7 @@ static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int
if (vt) {
int vcol = item->draw_col - col_off;
int col = draw_virt_text_item(buf, item->draw_col, vt->data.virt_text,
- vt->hl_mode, max_col, vcol);
+ vt->hl_mode, max_col, vcol, 0);
if (do_eol && ((vt->pos == kVPosEndOfLine) || (vt->pos == kVPosEndOfLineRightAlign))) {
state->eol_col = col + 1;
}
@@ -358,32 +359,45 @@ static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int
}
static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, int max_col,
- int vcol)
+ int vcol, int skip_cells)
{
- const char *p = "";
+ const char *virt_str = "";
int virt_attr = 0;
size_t virt_pos = 0;
while (col < max_col) {
- if (!*p) {
+ if (skip_cells >= 0 && *virt_str == NUL) {
if (virt_pos >= kv_size(vt)) {
break;
}
virt_attr = 0;
- p = next_virt_text_chunk(vt, &virt_pos, &virt_attr);
- if (p == NULL) {
+ virt_str = next_virt_text_chunk(vt, &virt_pos, &virt_attr);
+ if (virt_str == NULL) {
break;
}
}
- if (*p == NUL) {
+ // Skip cells in the text.
+ while (skip_cells > 0 && *virt_str != NUL) {
+ int c_len = utfc_ptr2len(virt_str);
+ int cells = *virt_str == TAB
+ ? tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array)
+ : utf_ptr2cells(virt_str);
+ skip_cells -= cells;
+ vcol += cells;
+ virt_str += c_len;
+ }
+ // If a double-width char or TAB doesn't fit, pad with spaces.
+ const char *draw_str = skip_cells < 0 ? " " : virt_str;
+ if (*draw_str == NUL) {
continue;
}
+ assert(skip_cells <= 0);
int attr;
bool through = false;
if (hl_mode == kHlModeCombine) {
attr = hl_combine_attr(linebuf_attr[col], virt_attr);
} else if (hl_mode == kHlModeBlend) {
- through = (*p == ' ');
+ through = (*draw_str == ' ');
attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through);
} else {
attr = virt_attr;
@@ -397,13 +411,18 @@ static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode,
// Clear the right half as well for the assertion in line_putchar().
linebuf_char[col] = schar_from_ascii(' ');
}
- int cells = line_putchar(buf, &p, through ? dummy : &linebuf_char[col],
+ int cells = line_putchar(buf, &draw_str, through ? dummy : &linebuf_char[col],
maxcells, vcol);
for (int c = 0; c < cells; c++) {
linebuf_attr[col] = attr;
col++;
}
- vcol += cells;
+ if (skip_cells < 0) {
+ skip_cells++;
+ } else {
+ vcol += cells;
+ virt_str = draw_str;
+ }
}
return col;
}
@@ -1587,8 +1606,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
const bool may_have_inline_virt
= !has_foldtext && buf_meta_total(wp->w_buffer, kMTMetaInline) > 0;
- int virt_line_index;
- int virt_line_offset = -1;
+ int virt_line_index = -1;
+ int virt_line_flags = 0;
// Repeat for the whole displayed line.
while (true) {
int has_match_conc = 0; ///< match wants to conceal
@@ -1616,11 +1635,11 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
if (index > 0) {
virt_line_index = (int)kv_size(virt_lines) - index;
assert(virt_line_index >= 0);
- virt_line_offset = kv_A(virt_lines, virt_line_index).left_col ? 0 : win_col_off(wp);
+ virt_line_flags = kv_A(virt_lines, virt_line_index).flags;
}
}
- if (virt_line_offset == 0) {
+ if (virt_line_index >= 0 && (virt_line_flags & kVLLeftcol)) {
// skip columns
} else if (statuscol.draw) {
// Draw 'statuscolumn' if it is set.
@@ -2715,7 +2734,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
}
if (kv_size(fold_vt) > 0) {
- draw_virt_text_item(buf, win_col_offset, fold_vt, kHlModeCombine, grid->cols, 0);
+ draw_virt_text_item(buf, win_col_offset, fold_vt, kHlModeCombine, grid->cols, 0, 0);
}
draw_virt_text(wp, buf, win_col_offset, &wlv.col, wlv.row);
// Set increasing virtual columns in grid->vcols[] to set correct curswant
@@ -2923,7 +2942,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
end_check:
// At end of screen line and there is more to come: Display the line
// so far. If there is no more to display it is caught above.
- if (wlv.col >= grid->cols && (!has_foldtext || virt_line_offset >= 0)
+ if (wlv.col >= grid->cols && (!has_foldtext || virt_line_index >= 0)
&& (wlv.col <= leftcols_width
|| *ptr != NUL
|| wlv.filler_todo > 0
@@ -2956,9 +2975,14 @@ end_check:
}
}
- if (virt_line_offset >= 0) {
- draw_virt_text_item(buf, virt_line_offset, kv_A(virt_lines, virt_line_index).line,
- kHlModeReplace, grid->cols, 0);
+ if (virt_line_index >= 0) {
+ draw_virt_text_item(buf,
+ virt_line_flags & kVLLeftcol ? 0 : win_col_offset,
+ kv_A(virt_lines, virt_line_index).line,
+ kHlModeReplace,
+ grid->cols,
+ 0,
+ virt_line_flags & kVLScroll ? wp->w_leftcol : 0);
} else if (wlv.filler_todo <= 0) {
draw_virt_text(wp, buf, win_col_offset, &draw_col, wlv.row);
}
@@ -3008,7 +3032,8 @@ end_check:
statuscol.draw = false; // don't draw status column if "n" is in 'cpo'
}
wlv.filler_todo--;
- virt_line_offset = -1;
+ virt_line_index = -1;
+ virt_line_flags = 0;
// When the filler lines are actually below the last line of the
// file, don't draw the line itself, break here.
if (wlv.filler_todo == 0 && (wp->w_botfill || end_fill)) {