fix(ui): forward 'rulerformat' to msg_ruler event #35707

Problem:  A 'rulerformat' not part of the statusline is not emitted through
          msg_ruler events.
Solution: Build the message chunks to emit as a msg_ruler event.
This commit is contained in:
luukvbaal
2025-09-13 22:34:58 +02:00
committed by GitHub
parent a41703d107
commit c1648cf820
9 changed files with 123 additions and 90 deletions

View File

@@ -201,6 +201,7 @@ EVENTS
• |CmdlineLeavePre| triggered before preparing to leave the command line.
• New `append` paremeter for |ui-messages| `msg_show` event.
• New `msg_id` paremeter for |ui-messages| `msg_show` event.
• 'rulerformat' is emitted as `msg_ruler` when not part of the statusline.
• Creating or updating a progress message with |nvim_echo()| triggers a |Progress| event.
HIGHLIGHTS

View File

@@ -2141,8 +2141,8 @@ DictAs(eval_statusline_ret) nvim_eval_statusline(String str, Dict(eval_statuslin
if (opts->use_winbar) {
fillchar = wp->w_p_fcs_chars.wbr;
} else {
int attr;
fillchar = fillchar_status(&attr, wp);
hlf_T group;
fillchar = fillchar_status(&group, wp);
}
}

View File

@@ -513,17 +513,13 @@ static int wildmenu_match_len(expand_T *xp, char *s)
/// @param matches list of matches
static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int match, bool showtail)
{
int len;
int clen; // length in screen cells
int attr;
int i;
bool highlight = true;
char *selstart = NULL;
int selstart_col = 0;
char *selend = NULL;
static int first_match = 0;
bool add_left = false;
int l;
int i, l;
if (matches == NULL) { // interrupted completion?
return;
@@ -536,7 +532,7 @@ static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int m
highlight = false;
}
// count 1 for the ending ">"
clen = wildmenu_match_len(xp, SHOW_MATCH(match)) + 3;
int clen = wildmenu_match_len(xp, SHOW_MATCH(match)) + 3; // length in screen cells
if (match == 0) {
first_match = 0;
} else if (match < first_match) {
@@ -577,7 +573,10 @@ static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int m
}
}
schar_T fillchar = fillchar_status(&attr, curwin);
int len;
hlf_T group;
schar_T fillchar = fillchar_status(&group, curwin);
int attr = win_hl_attr(curwin, (int)group);
if (first_match == 0) {
*buf = NUL;

View File

@@ -2094,6 +2094,7 @@ static const char *did_set_laststatus(optset_T *args)
win_comp_pos();
}
status_redraw_curbuf();
last_status(false); // (re)set last window status line.
return NULL;
}

View File

@@ -66,7 +66,6 @@ typedef enum {
/// If inversion is possible we use it. Else '=' characters are used.
void win_redr_status(win_T *wp)
{
int attr;
bool is_stl_global = global_stl_height() > 0;
static bool busy = false;
@@ -92,15 +91,16 @@ void win_redr_status(win_T *wp)
redraw_custom_statusline(wp);
}
hlf_T group = HLF_C;
// May need to draw the character below the vertical separator.
if (wp->w_vsep_width != 0 && wp->w_status_height != 0 && redrawing()) {
schar_T fillchar;
if (stl_connected(wp)) {
fillchar = fillchar_status(&attr, wp);
fillchar = fillchar_status(&group, wp);
} else {
attr = win_hl_attr(wp, HLF_C);
fillchar = wp->w_p_fcs_chars.vert;
}
int attr = win_hl_attr(wp, (int)group);
grid_line_start(&default_gridview, W_ENDROW(wp));
grid_line_put_schar(W_ENDCOL(wp), fillchar, attr);
grid_line_flush();
@@ -208,15 +208,17 @@ void stl_fill_click_defs(StlClickDefinition *click_defs, StlClickRecord *click_r
}
}
/// Redraw the status line, window bar or ruler of window "wp".
/// When "wp" is NULL redraw the tab pages line from 'tabline'.
static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
/// Redraw the status line, window bar, ruler or tabline.
/// @param wp target window, NULL for 'tabline'
/// @param draw_winbar redraw 'winbar'
/// @param draw_ruler redraw 'rulerformat'
/// @param ui_event emit UI-event instead of drawing
static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler, bool ui_event)
{
static bool entered = false;
int attr;
int row;
int col = 0;
int maxwidth;
int attr, row, maxwidth;
hlf_T group;
schar_T fillchar;
char buf[MAXPATHL];
char transbuf[MAXPATHL];
@@ -243,7 +245,8 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
stl = p_tal;
row = 0;
fillchar = schar_from_ascii(' ');
attr = HL_ATTR(HLF_TPF);
group = HLF_TPF;
attr = HL_ATTR(group);
maxwidth = Columns;
opt_idx = kOptTabline;
} else if (draw_winbar) {
@@ -259,14 +262,15 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
}
fillchar = wp->w_p_fcs_chars.wbr;
attr = (wp == curwin) ? win_hl_attr(wp, HLF_WBR) : win_hl_attr(wp, HLF_WBRNC);
group = (wp == curwin) ? HLF_WBR : HLF_WBRNC;
attr = win_hl_attr(wp, (int)group);
maxwidth = wp->w_view_width;
stl_clear_click_defs(wp->w_winbar_click_defs, wp->w_winbar_click_defs_size);
wp->w_winbar_click_defs = stl_alloc_click_defs(wp->w_winbar_click_defs, maxwidth,
&wp->w_winbar_click_defs_size);
} else {
row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp);
fillchar = fillchar_status(&attr, wp);
fillchar = fillchar_status(&group, wp);
const bool in_status_line = wp->w_status_height != 0 || is_stl_global;
maxwidth = in_status_line && !is_stl_global ? wp->w_width : Columns;
stl_clear_click_defs(wp->w_status_click_defs, wp->w_status_click_defs_size);
@@ -297,7 +301,7 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
grid = grid_adjust(&msg_grid_adj, &row, &col);
maxwidth--; // writing in last column may cause scrolling
fillchar = schar_from_ascii(' ');
attr = HL_ATTR(HLF_MSG);
group = HLF_MSG;
}
} else {
opt_idx = kOptStatusline;
@@ -305,6 +309,7 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
opt_scope = ((*wp->w_p_stl != NUL) ? OPT_LOCAL : 0);
}
attr = win_hl_attr(wp, (int)group);
if (in_status_line && !is_stl_global) {
col += wp->w_wincol;
}
@@ -332,31 +337,57 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
int len = (int)strlen(buf);
int start_col = col;
// Draw each snippet with the specified highlighting.
screengrid_line_start(grid, row, 0);
if (!ui_event) {
// Draw each snippet with the specified highlighting.
screengrid_line_start(grid, row, 0);
}
int curattr = attr;
char *p = buf;
for (int n = 0; hltab[n].start != NULL; n++) {
int textlen = (int)(hltab[n].start - p);
// Make all characters printable.
size_t tsize = transstr_buf(p, textlen, transbuf, sizeof transbuf, true);
col += grid_line_puts(col, transbuf, (int)tsize, curattr);
p = hltab[n].start;
if (hltab[n].userhl == 0) {
curattr = attr;
} else if (hltab[n].userhl < 0) {
curattr = hl_combine_attr(attr, syn_id2attr(-hltab[n].userhl));
} else if (wp != NULL && wp != curwin && wp->w_status_height != 0) {
curattr = highlight_stlnc[hltab[n].userhl - 1];
int curattr = attr;
int curgroup = (int)group;
Array content = ARRAY_DICT_INIT;
for (stl_hlrec_t *sp = hltab;; sp++) {
int textlen = (int)(sp->start ? sp->start - p : buf + len - p);
// Make all characters printable. Use an empty string instead of p, if p is beyond buf + len.
size_t tsize = transstr_buf(p >= buf + len ? "" : p, textlen, transbuf, sizeof transbuf, true);
if (!ui_event) {
col += grid_line_puts(col, transbuf, (int)tsize, curattr);
} else {
curattr = highlight_user[hltab[n].userhl - 1];
Array chunk = ARRAY_DICT_INIT;
ADD(chunk, INTEGER_OBJ(curattr));
ADD(chunk, STRING_OBJ(cbuf_as_string(xmemdupz(transbuf, tsize), tsize)));
ADD(chunk, INTEGER_OBJ(curgroup));
ADD(content, ARRAY_OBJ(chunk));
}
p = sp->start;
if (p == NULL) {
break;
} else if (sp->userhl == 0) {
curattr = attr;
curgroup = (int)group;
} else if (sp->userhl < 0) {
curattr = syn_id2attr(-sp->userhl);
curgroup = -sp->userhl;
} else {
int *userhl = (wp != NULL && wp != curwin && wp->w_status_height != 0)
? highlight_stlnc : highlight_user;
char userbuf[5] = "User";
userbuf[4] = (char)sp->userhl + '0';
curattr = userhl[sp->userhl - 1];
curgroup = syn_name2id_len(userbuf, 5);
}
if (curattr != attr) {
curattr = hl_combine_attr(attr, curattr);
}
}
// Make sure to use an empty string instead of p, if p is beyond buf + len.
size_t tsize = transstr_buf(p >= buf + len ? "" : p, -1, transbuf, sizeof transbuf, true);
col += grid_line_puts(col, transbuf, (int)tsize, curattr);
if (ui_event) {
ui_call_msg_ruler(content);
api_free_array(content);
goto theend;
}
int maxcol = start_col + maxwidth;
// fill up with "fillchar"
@@ -389,7 +420,7 @@ void win_redr_winbar(win_T *wp)
if (wp->w_winbar_height == 0 || !redrawing()) {
// Do nothing.
} else if (*p_wbr != NUL || *wp->w_p_wbr != NUL) {
win_redr_custom(wp, true, false);
win_redr_custom(wp, true, false, false);
}
entered = false;
}
@@ -423,44 +454,22 @@ void redraw_ruler(void)
// Don't draw the ruler while doing insert-completion, it might overwrite
// the (long) mode message.
if (wp->w_status_height == 0 && !is_stl_global) {
if (edit_submode != NULL) {
return;
}
}
if (*p_ruf && p_ch > 0 && !ui_has(kUIMessages)) {
win_redr_custom(wp, false, true);
if (wp->w_status_height == 0 && !is_stl_global && edit_submode != NULL) {
return;
}
// Check if not in Insert mode and the line is empty (will show "0-1").
int empty_line = (State & MODE_INSERT) == 0
&& *ml_get_buf(wp->w_buffer, wp->w_cursor.lnum) == NUL;
int width;
schar_T fillchar;
int attr;
int off;
bool part_of_status = false;
if (wp->w_status_height) {
fillchar = fillchar_status(&attr, wp);
off = wp->w_wincol;
width = wp->w_width;
part_of_status = true;
} else if (is_stl_global) {
fillchar = fillchar_status(&attr, wp);
off = 0;
width = Columns;
part_of_status = true;
} else {
fillchar = schar_from_ascii(' ');
attr = HL_ATTR(HLF_MSG);
width = Columns;
off = 0;
bool part_of_status = wp->w_status_height || is_stl_global;
if (*p_ruf && (p_ch > 0 || (ui_has(kUIMessages) && !part_of_status))) {
win_redr_custom(wp, false, true, ui_has(kUIMessages));
return;
}
hlf_T group = HLF_MSG;
int off = wp->w_status_height ? wp->w_wincol : 0;
int width = wp->w_status_height ? wp->w_width : Columns;
schar_T fillchar = part_of_status ? fillchar_status(&group, wp) : schar_from_ascii(' ');
int attr = win_hl_attr(wp, (int)group);
// In list mode virtcol needs to be recomputed
colnr_T virtcol = wp->w_virtcol;
if (wp->w_p_list && wp->w_p_lcs_chars.tab1 == NUL) {
@@ -469,6 +478,10 @@ void redraw_ruler(void)
wp->w_p_list = true;
}
// Check if not in Insert mode and the line is empty (will show "0-1").
int empty_line = (State & MODE_INSERT) == 0
&& *ml_get_buf(wp->w_buffer, wp->w_cursor.lnum) == NUL;
#define RULER_BUF_LEN 70
char buffer[RULER_BUF_LEN];
@@ -542,13 +555,13 @@ void redraw_ruler(void)
}
/// Get the character to use in a status line. Get its attributes in "*attr".
schar_T fillchar_status(int *attr, win_T *wp)
schar_T fillchar_status(hlf_T *group, win_T *wp)
{
if (wp == curwin) {
*attr = win_hl_attr(wp, HLF_S);
*group = HLF_S;
return wp->w_p_fcs_chars.stl;
} else {
*attr = win_hl_attr(wp, HLF_SNC);
*group = HLF_SNC;
return wp->w_p_fcs_chars.stlnc;
}
}
@@ -566,7 +579,7 @@ void redraw_custom_statusline(win_T *wp)
}
entered = true;
win_redr_custom(wp, false, false);
win_redr_custom(wp, false, false, false);
entered = false;
}
@@ -644,7 +657,7 @@ void draw_tabline(void)
// Use the 'tabline' option if it's set.
if (*p_tal != NUL) {
win_redr_custom(NULL, false, false);
win_redr_custom(NULL, false, false, false);
} else {
int tabcount = 0;
int col = 0;

View File

@@ -2,6 +2,7 @@
#include <stddef.h>
#include "nvim/highlight_defs.h"
#include "nvim/macros_defs.h"
#include "nvim/option_defs.h" // IWYU pragma: keep
#include "nvim/statusline_defs.h" // IWYU pragma: keep

View File

@@ -12,6 +12,15 @@ describe('statusline', function()
before_each(function()
screen = Screen.new(50, 7)
screen:add_extra_attr_ids({
[100] = {
background = Screen.colors.Red,
foreground = Screen.colors.Gray100,
reverse = true,
bold = true,
},
[101] = { foreground = Screen.colors.Blue, reverse = true, bold = true },
})
end)
it('is updated in cmdline mode when using window-local statusline vim-patch:8.2.2737', function()
@@ -51,7 +60,7 @@ describe('statusline', function()
screen:expect([[
^ |
{1:~ }|*4
{9:<F}{18:GHI }|
{100:<F}{101:GHI }|
|
]])
end)

View File

@@ -852,7 +852,7 @@ describe('ui/ext_messages', function()
}
end)
it('supports &showcmd and &ruler', function()
it("supports 'showcmd' and 'ruler(format)'", function()
command('set showcmd ruler')
command('hi link MsgArea ErrorMsg')
screen:expect({
@@ -940,19 +940,26 @@ describe('ui/ext_messages', function()
]],
ruler = { { '2,0-1 All', 'MsgArea' } },
}
-- when ruler is part of statusline it is not externalized.
-- this will be added as part of future ext_statusline support
command('set laststatus=2')
command('set rulerformat=Foo%#ErrorMsg#Bar')
screen:expect({
grid = [[
abcde |
^ |
{1:~ }|*2
{3:<] [+] 2,0-1 All}|
{1:~ }|*3
]],
ruler = { { '2,0-1 All', 'MsgArea' } },
ruler = { { 'Foo', 'MsgArea' }, { 'Bar', 9, 'ErrorMsg' } },
})
command('set rulerformat=')
-- when ruler is part of statusline it is not externalized.
-- this will be added as part of future ext_statusline support
command('set laststatus=2')
screen:expect([[
abcde |
^ |
{1:~ }|*2
{3:<] [+] 2,0-1 All}|
]])
end)
it('keeps history of message of different kinds', function()

View File

@@ -315,6 +315,8 @@ func Test_statusline()
call assert_equal(sa3, screenattr(&lines - 1, 7))
" %*: Set highlight group to User{N}
" Nvim: Combined with hl-StatusLine so needs to be set.
hi link User1 ErrorMsg
set statusline=a%1*b%0*c
call assert_match('^abc\s*$', s:get_statusline())
let sa1=screenattr(&lines - 1, 1)