commit c1648cf820ed379f6abb7cb802680a02a1def755
parent a41703d107da4479635f8c9ebcd14409e6a7d648
Author: luukvbaal <luukvbaal@gmail.com>
Date: Sat, 13 Sep 2025 22:34:58 +0200
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.
Diffstat:
9 files changed, 121 insertions(+), 88 deletions(-)
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
@@ -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
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
@@ -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);
}
}
diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c
@@ -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;
diff --git a/src/nvim/option.c b/src/nvim/option.c
@@ -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;
}
diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c
@@ -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) {
+ 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 {
+ 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;
- } 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];
+ curgroup = (int)group;
+ } else if (sp->userhl < 0) {
+ curattr = syn_id2attr(-sp->userhl);
+ curgroup = -sp->userhl;
} else {
- curattr = highlight_user[hltab[n].userhl - 1];
+ 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,43 +454,21 @@ 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 (wp->w_status_height == 0 && !is_stl_global && edit_submode != NULL) {
+ return;
}
- if (*p_ruf && p_ch > 0 && !ui_has(kUIMessages)) {
- win_redr_custom(wp, false, true);
+ 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;
}
- // 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;
- }
+ 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;
@@ -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;
diff --git a/src/nvim/statusline.h b/src/nvim/statusline.h
@@ -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
diff --git a/test/functional/legacy/statusline_spec.lua b/test/functional/legacy/statusline_spec.lua
@@ -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)
diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua
@@ -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()
diff --git a/test/old/testdir/test_statusline.vim b/test/old/testdir/test_statusline.vim
@@ -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)