Skip to content

Commit 34d808b

Browse files
committed
feat(api): combined highlights in nvim_eval_statusline()
Problem: Combined highlighting was not applied to nvim_eval_statusline(), and 'statuscolumn' sign segment/numhl highlights. Solution: Add an additional `groups` element to the return value of `nvim_eval_statusline()->highlights`. This is an array of stacked highlight groups (highest priority last). Also resolve combined highlights for the 'statuscolumn' sign segment/numhl highlights. Expose/synchronize some drawline.c logic that is now mimicked in three different places.
1 parent 8634bd4 commit 34d808b

File tree

14 files changed

+244
-236
lines changed

14 files changed

+244
-236
lines changed

runtime/doc/api.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,10 @@ nvim_eval_statusline({str}, {opts}) *nvim_eval_statusline()*
672672
true. Each element of the array is a |Dict| with these keys:
673673
• start: (number) Byte index (0-based) of first character that uses
674674
the highlight.
675-
• group: (string) Name of highlight group.
675+
• group: (string) Name of highlight group. May be removed in the
676+
future, use `groups` instead.
677+
• groups: (array) Names of stacked highlight groups (highest
678+
priority last).
676679

677680
nvim_exec_lua({code}, {args}) *nvim_exec_lua()*
678681
Execute Lua code. Parameters (if any) are available as `...` inside the

runtime/doc/news.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -421,8 +421,10 @@ These existing features changed their behavior.
421421
using the upgraded implementation.
422422

423423
• Custom highlights in 'rulerformat', 'statuscolumn', 'statusline', 'tabline',
424-
'winbar' and the number column (through |:sign-define| `numhl`) now combine
425-
with their respective highlight groups, as opposed to |hl-Normal|.
424+
'winbar', and the sign/number column are stacked with their respective
425+
highlight groups, as opposed to |hl-Normal|.
426+
This is also reflected in the `highlights` from |nvim_eval_statusline()|,
427+
with a new `groups` field containing an array of stacked highlight groups.
426428

427429
|vim.on_key()| callbacks won't be invoked recursively when a callback itself
428430
consumes input.

runtime/lua/vim/_meta/api.lua

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/nvim/api/vim.c

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "nvim/context.h"
2929
#include "nvim/cursor.h"
3030
#include "nvim/decoration.h"
31+
#include "nvim/drawline.h"
3132
#include "nvim/drawscreen.h"
3233
#include "nvim/errors.h"
3334
#include "nvim/eval.h"
@@ -1983,7 +1984,9 @@ Array nvim_get_mark(String name, Dict(empty) *opts, Arena *arena, Error *err)
19831984
/// the "highlights" key in {opts} is true. Each element of the array is a
19841985
/// |Dict| with these keys:
19851986
/// - start: (number) Byte index (0-based) of first character that uses the highlight.
1986-
/// - group: (string) Name of highlight group.
1987+
/// - group: (string) Name of highlight group. May be removed in the future, use
1988+
/// `groups` instead.
1989+
/// - groups: (array) Names of stacked highlight groups (highest priority last).
19871990
Dict nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *arena, Error *err)
19881991
FUNC_API_SINCE(8) FUNC_API_FAST
19891992
{
@@ -2035,6 +2038,7 @@ Dict nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *arena,
20352038
});
20362039

20372040
int stc_hl_id = 0;
2041+
int scl_hl_id = 0;
20382042
statuscol_T statuscol = { 0 };
20392043
SignTextAttrs sattrs[SIGN_SHOW_MAX] = { 0 };
20402044

@@ -2043,23 +2047,18 @@ Dict nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *arena,
20432047
int cul_id = 0;
20442048
int num_id = 0;
20452049
linenr_T lnum = statuscol_lnum;
2050+
foldinfo_T cursorline_fi = { 0 };
20462051
decor_redraw_signs(wp, wp->w_buffer, lnum - 1, sattrs, &line_id, &cul_id, &num_id);
20472052

20482053
statuscol.sattrs = sattrs;
20492054
statuscol.foldinfo = fold_info(wp, lnum);
2050-
wp->w_cursorline = win_cursorline_standout(wp) ? wp->w_cursor.lnum : 0;
2055+
win_update_cursorline(wp, &cursorline_fi);
2056+
statuscol.sign_cul_id = use_cursor_line_highlight(wp, lnum) ? cul_id : 0;
2057+
scl_hl_id = use_cursor_line_highlight(wp, lnum) ? HLF_CLS : HLF_SC;
20512058

2052-
if (wp->w_p_cul) {
2053-
if (statuscol.foldinfo.fi_level != 0 && statuscol.foldinfo.fi_lines > 0) {
2054-
wp->w_cursorline = statuscol.foldinfo.fi_lnum;
2055-
}
2056-
statuscol.use_cul = lnum == wp->w_cursorline && (wp->w_p_culopt_flags & kOptCuloptFlagNumber);
2057-
}
2058-
2059-
statuscol.sign_cul_id = statuscol.use_cul ? cul_id : 0;
20602059
if (num_id) {
20612060
stc_hl_id = num_id;
2062-
} else if (statuscol.use_cul) {
2061+
} else if (use_cursor_line_highlight(wp, lnum)) {
20632062
stc_hl_id = HLF_CLN;
20642063
} else if (wp->w_p_rnu) {
20652064
stc_hl_id = (lnum < wp->w_cursor.lnum ? HLF_LNA : HLF_LNB);
@@ -2112,22 +2111,19 @@ Dict nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *arena,
21122111

21132112
// If first character doesn't have a defined highlight,
21142113
// add the default highlight at the beginning of the highlight list
2114+
const char *dfltname = get_default_stl_hl(opts->use_tabline ? NULL : wp,
2115+
opts->use_winbar, stc_hl_id);
21152116
if (hltab->start == NULL || (hltab->start - buf) != 0) {
2116-
Dict hl_info = arena_dict(arena, 2);
2117-
const char *grpname = get_default_stl_hl(opts->use_tabline ? NULL : wp,
2118-
opts->use_winbar, stc_hl_id);
2119-
2117+
Dict hl_info = arena_dict(arena, 3);
21202118
PUT_C(hl_info, "start", INTEGER_OBJ(0));
2121-
PUT_C(hl_info, "group", CSTR_AS_OBJ(grpname));
2122-
2119+
PUT_C(hl_info, "group", CSTR_AS_OBJ(dfltname));
2120+
Array groups = arena_array(arena, 1);
2121+
ADD_C(groups, CSTR_AS_OBJ(dfltname));
2122+
PUT_C(hl_info, "groups", ARRAY_OBJ(groups));
21232123
ADD_C(hl_values, DICT_OBJ(hl_info));
21242124
}
21252125

21262126
for (stl_hlrec_t *sp = hltab; sp->start != NULL; sp++) {
2127-
Dict hl_info = arena_dict(arena, 2);
2128-
2129-
PUT_C(hl_info, "start", INTEGER_OBJ(sp->start - buf));
2130-
21312127
const char *grpname;
21322128
if (sp->userhl == 0) {
21332129
grpname = get_default_stl_hl(opts->use_tabline ? NULL : wp, opts->use_winbar, stc_hl_id);
@@ -2137,7 +2133,18 @@ Dict nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *arena,
21372133
snprintf(user_group, sizeof(user_group), "User%d", sp->userhl);
21382134
grpname = arena_memdupz(arena, user_group, strlen(user_group));
21392135
}
2136+
2137+
const char *combine = sp->item == STL_SIGNCOL ? syn_id2name(scl_hl_id)
2138+
: sp->item == STL_FOLDCOL ? grpname : dfltname;
2139+
Dict hl_info = arena_dict(arena, 3);
2140+
PUT_C(hl_info, "start", INTEGER_OBJ(sp->start - buf));
21402141
PUT_C(hl_info, "group", CSTR_AS_OBJ(grpname));
2142+
Array groups = arena_array(arena, 1 + (combine != grpname));
2143+
if (combine != grpname) {
2144+
ADD_C(groups, CSTR_AS_OBJ(combine));
2145+
}
2146+
ADD_C(groups, CSTR_AS_OBJ(grpname));
2147+
PUT_C(hl_info, "groups", ARRAY_OBJ(groups));
21412148
ADD_C(hl_values, DICT_OBJ(hl_info));
21422149
}
21432150
PUT_C(result, "highlights", ARRAY_OBJ(hl_values));

src/nvim/decoration.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -856,9 +856,9 @@ static const uint32_t sign_filter[4] = {[kMTMetaSignText] = kMTFilterSelect,
856856
/// Return the sign attributes on the currently refreshed row.
857857
///
858858
/// @param[out] sattrs Output array for sign text and texthl id
859-
/// @param[out] line_attr Highest priority linehl id
860-
/// @param[out] cul_attr Highest priority culhl id
861-
/// @param[out] num_attr Highest priority numhl id
859+
/// @param[out] line_id Highest priority linehl id
860+
/// @param[out] cul_id Highest priority culhl id
861+
/// @param[out] num_id Highest priority numhl id
862862
void decor_redraw_signs(win_T *wp, buf_T *buf, int row, SignTextAttrs sattrs[], int *line_id,
863863
int *cul_id, int *num_id)
864864
{

src/nvim/drawline.c

Lines changed: 31 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ typedef struct {
8181
int cul_attr; ///< set when 'cursorline' active
8282
int line_attr; ///< attribute for the whole line
8383
int line_attr_lowprio; ///< low-priority attribute for the line
84+
int sign_num_attr; ///< line number attribute (sign numhl)
85+
int sign_cul_attr; ///< cursorline sign attribute (sign culhl)
8486

8587
int fromcol; ///< start of inverting
8688
int tocol; ///< end of inverting
@@ -397,7 +399,7 @@ static void draw_col_fill(winlinevars_T *wlv, schar_T fillchar, int width, int a
397399
}
398400

399401
/// Return true if CursorLineSign highlight is to be used.
400-
static bool use_cursor_line_highlight(win_T *wp, linenr_T lnum)
402+
bool use_cursor_line_highlight(win_T *wp, linenr_T lnum)
401403
{
402404
return wp->w_p_cul
403405
&& lnum == wp->w_cursorline
@@ -460,16 +462,15 @@ void fill_foldcolumn(win_T *wp, foldinfo_T foldinfo, linenr_T lnum, int attr, in
460462
/// If "nrcol" is true, the sign is going to be displayed in the number column.
461463
/// Otherwise the sign is going to be displayed in the sign column. If there is no
462464
/// sign, draw blank cells instead.
463-
static void draw_sign(bool nrcol, win_T *wp, winlinevars_T *wlv, int sign_idx, int sign_cul_attr)
465+
static void draw_sign(bool nrcol, win_T *wp, winlinevars_T *wlv, int sign_idx)
464466
{
465467
SignTextAttrs sattr = wlv->sattrs[sign_idx];
466468
int scl_attr = win_hl_attr(wp, use_cursor_line_highlight(wp, wlv->lnum) ? HLF_CLS : HLF_SC);
467469

468470
if (sattr.text[0] && wlv->row == wlv->startrow + wlv->filler_lines && wlv->filler_todo <= 0) {
469-
int attr = (use_cursor_line_highlight(wp, wlv->lnum) && sign_cul_attr)
470-
? sign_cul_attr : sattr.hl_id ? syn_id2attr(sattr.hl_id) : 0;
471-
attr = hl_combine_attr(scl_attr, attr);
472471
int fill = nrcol ? number_width(wp) + 1 : SIGN_WIDTH;
472+
int attr = wlv->sign_cul_attr ? wlv->sign_cul_attr : sattr.hl_id ? syn_id2attr(sattr.hl_id) : 0;
473+
attr = hl_combine_attr(scl_attr, attr);
473474
draw_col_fill(wlv, schar_from_ascii(' '), fill, attr);
474475
int sign_pos = wlv->off - SIGN_WIDTH - (int)nrcol;
475476
assert(sign_pos >= 0);
@@ -544,7 +545,7 @@ static int get_line_number_attr(win_T *wp, winlinevars_T *wlv)
544545

545546
/// Display the absolute or relative line number. After the first row fill with
546547
/// blanks when the 'n' flag isn't in 'cpo'.
547-
static void draw_lnum_col(win_T *wp, winlinevars_T *wlv, int sign_num_attr, int sign_cul_attr)
548+
static void draw_lnum_col(win_T *wp, winlinevars_T *wlv)
548549
{
549550
bool has_cpo_n = vim_strchr(p_cpo, CPO_NUMCOL) != NULL;
550551

@@ -557,12 +558,12 @@ static void draw_lnum_col(win_T *wp, winlinevars_T *wlv, int sign_num_attr, int
557558
// then display the sign instead of the line number.
558559
if (wp->w_minscwidth == SCL_NUM && wlv->sattrs[0].text[0]
559560
&& wlv->row == wlv->startrow + wlv->filler_lines && wlv->filler_todo <= 0) {
560-
draw_sign(true, wp, wlv, 0, sign_cul_attr);
561+
draw_sign(true, wp, wlv, 0);
561562
} else {
562563
// Draw the line number (empty space after wrapping).
563564
int width = number_width(wp) + 1;
564565
int attr = hl_combine_attr(get_line_number_attr(wp, wlv),
565-
wlv->filler_todo <= 0 ? sign_num_attr : 0);
566+
wlv->filler_todo <= 0 ? wlv->sign_num_attr : 0);
566567
if (wlv->row == wlv->startrow + wlv->filler_lines
567568
&& (wp->w_skipcol == 0 || wlv->row > 0 || (wp->w_p_nu && wp->w_p_rnu))) {
568569
char buf[32];
@@ -631,22 +632,25 @@ static void draw_statuscol(win_T *wp, winlinevars_T *wlv, linenr_T lnum, int vir
631632

632633
char *p = buf;
633634
char transbuf[MAXPATHL];
634-
int attr = stcp->num_attr;
635635
size_t len = strlen(buf);
636+
int scl_attr = win_hl_attr(wp, use_cursor_line_highlight(wp, wlv->lnum) ? HLF_CLS : HLF_SC);
637+
int num_attr = hl_combine_attr(get_line_number_attr(wp, wlv),
638+
wlv->filler_todo <= 0 ? wlv->sign_num_attr : 0);
639+
int cur_attr = num_attr;
636640

637641
// Draw each segment with the specified highlighting.
638642
for (stl_hlrec_t *sp = stcp->hlrec; sp->start != NULL; sp++) {
639643
ptrdiff_t textlen = sp->start - p;
640644
// Make all characters printable.
641645
size_t translen = transstr_buf(p, textlen, transbuf, MAXPATHL, true);
642-
draw_col_buf(wp, wlv, transbuf, translen, attr, false);
646+
draw_col_buf(wp, wlv, transbuf, translen, cur_attr, false);
647+
int attr = sp->item == STL_SIGNCOL ? scl_attr : sp->item == STL_FOLDCOL ? 0 : num_attr;
648+
cur_attr = hl_combine_attr(attr, sp->userhl < 0 ? syn_id2attr(-sp->userhl) : 0);
643649
p = sp->start;
644-
int hl = sp->userhl;
645-
attr = hl < 0 ? hl_combine_attr(stcp->num_attr, syn_id2attr(-hl)) : stcp->num_attr;
646650
}
647651
size_t translen = transstr_buf(p, buf + len - p, transbuf, MAXPATHL, true);
648-
draw_col_buf(wp, wlv, transbuf, translen, attr, false);
649-
draw_col_fill(wlv, schar_from_ascii(' '), stcp->width - width, stcp->num_attr);
652+
draw_col_buf(wp, wlv, transbuf, translen, num_attr, false);
653+
draw_col_fill(wlv, schar_from_ascii(' '), stcp->width - width, num_attr);
650654
}
651655

652656
static void handle_breakindent(win_T *wp, winlinevars_T *wlv)
@@ -1201,11 +1205,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
12011205
area_highlighting = true;
12021206
}
12031207

1204-
int line_attr = 0;
1205-
int sign_cul_attr = 0;
1206-
int sign_num_attr = 0;
1208+
int sign_line_attr = 0;
12071209
// TODO(bfredl, vigoux): line_attr should not take priority over decoration!
1208-
decor_redraw_signs(wp, buf, wlv.lnum - 1, wlv.sattrs, &line_attr, &sign_cul_attr, &sign_num_attr);
1210+
decor_redraw_signs(wp, buf, wlv.lnum - 1, wlv.sattrs,
1211+
&sign_line_attr, &wlv.sign_cul_attr, &wlv.sign_num_attr);
12091212

12101213
statuscol_T statuscol = { 0 };
12111214
if (*wp->w_p_stc != NUL) {
@@ -1214,19 +1217,15 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
12141217
statuscol.sattrs = wlv.sattrs;
12151218
statuscol.foldinfo = foldinfo;
12161219
statuscol.width = win_col_off(wp) - (wp == cmdwin_win);
1217-
statuscol.use_cul = use_cursor_line_highlight(wp, lnum);
1218-
statuscol.sign_cul_id = statuscol.use_cul ? sign_cul_attr : 0;
1219-
statuscol.num_attr = sign_num_attr > 0 ? syn_id2attr(sign_num_attr) : 0;
1220-
} else {
1221-
if (sign_cul_attr > 0) {
1222-
sign_cul_attr = syn_id2attr(sign_cul_attr);
1223-
}
1224-
if (sign_num_attr > 0) {
1225-
sign_num_attr = syn_id2attr(sign_num_attr);
1226-
}
1220+
statuscol.sign_cul_id = use_cursor_line_highlight(wp, lnum) ? wlv.sign_cul_attr : 0;
1221+
} else if (wlv.sign_cul_attr > 0) {
1222+
wlv.sign_cul_attr = use_cursor_line_highlight(wp, lnum) ? syn_id2attr(wlv.sign_cul_attr) : 0;
12271223
}
1228-
if (line_attr > 0) {
1229-
wlv.line_attr = syn_id2attr(line_attr);
1224+
if (wlv.sign_num_attr > 0) {
1225+
wlv.sign_num_attr = syn_id2attr(wlv.sign_num_attr);
1226+
}
1227+
if (sign_line_attr > 0) {
1228+
wlv.line_attr = syn_id2attr(sign_line_attr);
12301229
}
12311230

12321231
// Highlight the current line in the quickfix window.
@@ -1549,9 +1548,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
15491548
// skip columns
15501549
} else if (statuscol.draw) {
15511550
// Draw 'statuscolumn' if it is set.
1552-
if (sign_num_attr == 0) {
1553-
statuscol.num_attr = get_line_number_attr(wp, &wlv);
1554-
}
15551551
const int v = (int)(ptr - line);
15561552
draw_statuscol(wp, &wlv, lnum, wlv.row - startrow - wlv.filler_lines, col_rows, &statuscol);
15571553
if (wp->w_redr_statuscol) {
@@ -1568,10 +1564,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
15681564

15691565
// wp->w_scwidth is zero if signcol=number is used
15701566
for (int sign_idx = 0; sign_idx < wp->w_scwidth; sign_idx++) {
1571-
draw_sign(false, wp, &wlv, sign_idx, sign_cul_attr);
1567+
draw_sign(false, wp, &wlv, sign_idx);
15721568
}
15731569

1574-
draw_lnum_col(wp, &wlv, sign_num_attr, sign_cul_attr);
1570+
draw_lnum_col(wp, &wlv);
15751571
}
15761572

15771573
win_col_offset = wlv.off;

src/nvim/drawscreen.c

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2029,14 +2029,7 @@ static void win_update(win_T *wp)
20292029
}
20302030

20312031
foldinfo_T cursorline_fi = { 0 };
2032-
wp->w_cursorline = win_cursorline_standout(wp) ? wp->w_cursor.lnum : 0;
2033-
if (wp->w_p_cul) {
2034-
// Make sure that the cursorline on a closed fold is redrawn
2035-
cursorline_fi = fold_info(wp, wp->w_cursor.lnum);
2036-
if (cursorline_fi.fi_level != 0 && cursorline_fi.fi_lines > 0) {
2037-
wp->w_cursorline = cursorline_fi.fi_lnum;
2038-
}
2039-
}
2032+
win_update_cursorline(wp, &cursorline_fi);
20402033

20412034
win_check_ns_hl(wp);
20422035

@@ -2862,3 +2855,18 @@ bool win_cursorline_standout(const win_T *wp)
28622855
{
28632856
return wp->w_p_cul || (wp->w_p_cole > 0 && !conceal_cursor_line(wp));
28642857
}
2858+
2859+
/// Update w_cursorline, taking care to set it to the to the start of a closed fold.
2860+
///
2861+
/// @param[out] foldinfo foldinfo for the cursor line
2862+
void win_update_cursorline(win_T *wp, foldinfo_T *foldinfo)
2863+
{
2864+
wp->w_cursorline = win_cursorline_standout(wp) ? wp->w_cursor.lnum : 0;
2865+
if (wp->w_p_cul) {
2866+
// Make sure that the cursorline on a closed fold is redrawn
2867+
*foldinfo = fold_info(wp, wp->w_cursor.lnum);
2868+
if (foldinfo->fi_level != 0 && foldinfo->fi_lines > 0) {
2869+
wp->w_cursorline = foldinfo->fi_lnum;
2870+
}
2871+
}
2872+
}

0 commit comments

Comments
 (0)