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. • |CmdlineLeavePre| triggered before preparing to leave the command line.
• New `append` paremeter for |ui-messages| `msg_show` event. • New `append` paremeter for |ui-messages| `msg_show` event.
• New `msg_id` 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. • Creating or updating a progress message with |nvim_echo()| triggers a |Progress| event.
HIGHLIGHTS HIGHLIGHTS

View File

@@ -2141,8 +2141,8 @@ DictAs(eval_statusline_ret) nvim_eval_statusline(String str, Dict(eval_statuslin
if (opts->use_winbar) { if (opts->use_winbar) {
fillchar = wp->w_p_fcs_chars.wbr; fillchar = wp->w_p_fcs_chars.wbr;
} else { } else {
int attr; hlf_T group;
fillchar = fillchar_status(&attr, wp); 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 /// @param matches list of matches
static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int match, bool showtail) 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; bool highlight = true;
char *selstart = NULL; char *selstart = NULL;
int selstart_col = 0; int selstart_col = 0;
char *selend = NULL; char *selend = NULL;
static int first_match = 0; static int first_match = 0;
bool add_left = false; bool add_left = false;
int l; int i, l;
if (matches == NULL) { // interrupted completion? if (matches == NULL) { // interrupted completion?
return; return;
@@ -536,7 +532,7 @@ static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int m
highlight = false; highlight = false;
} }
// count 1 for the ending ">" // 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) { if (match == 0) {
first_match = 0; first_match = 0;
} else if (match < first_match) { } 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) { if (first_match == 0) {
*buf = NUL; *buf = NUL;

View File

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

View File

@@ -66,7 +66,6 @@ typedef enum {
/// If inversion is possible we use it. Else '=' characters are used. /// If inversion is possible we use it. Else '=' characters are used.
void win_redr_status(win_T *wp) void win_redr_status(win_T *wp)
{ {
int attr;
bool is_stl_global = global_stl_height() > 0; bool is_stl_global = global_stl_height() > 0;
static bool busy = false; static bool busy = false;
@@ -92,15 +91,16 @@ void win_redr_status(win_T *wp)
redraw_custom_statusline(wp); redraw_custom_statusline(wp);
} }
hlf_T group = HLF_C;
// May need to draw the character below the vertical separator. // May need to draw the character below the vertical separator.
if (wp->w_vsep_width != 0 && wp->w_status_height != 0 && redrawing()) { if (wp->w_vsep_width != 0 && wp->w_status_height != 0 && redrawing()) {
schar_T fillchar; schar_T fillchar;
if (stl_connected(wp)) { if (stl_connected(wp)) {
fillchar = fillchar_status(&attr, wp); fillchar = fillchar_status(&group, wp);
} else { } else {
attr = win_hl_attr(wp, HLF_C);
fillchar = wp->w_p_fcs_chars.vert; 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_start(&default_gridview, W_ENDROW(wp));
grid_line_put_schar(W_ENDCOL(wp), fillchar, attr); grid_line_put_schar(W_ENDCOL(wp), fillchar, attr);
grid_line_flush(); 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". /// Redraw the status line, window bar, ruler or tabline.
/// When "wp" is NULL redraw the tab pages line from 'tabline'. /// @param wp target window, NULL for 'tabline'
static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) /// @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; static bool entered = false;
int attr;
int row;
int col = 0; int col = 0;
int maxwidth; int attr, row, maxwidth;
hlf_T group;
schar_T fillchar; schar_T fillchar;
char buf[MAXPATHL]; char buf[MAXPATHL];
char transbuf[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; stl = p_tal;
row = 0; row = 0;
fillchar = schar_from_ascii(' '); fillchar = schar_from_ascii(' ');
attr = HL_ATTR(HLF_TPF); group = HLF_TPF;
attr = HL_ATTR(group);
maxwidth = Columns; maxwidth = Columns;
opt_idx = kOptTabline; opt_idx = kOptTabline;
} else if (draw_winbar) { } 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; 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; maxwidth = wp->w_view_width;
stl_clear_click_defs(wp->w_winbar_click_defs, wp->w_winbar_click_defs_size); 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 = stl_alloc_click_defs(wp->w_winbar_click_defs, maxwidth,
&wp->w_winbar_click_defs_size); &wp->w_winbar_click_defs_size);
} else { } else {
row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp); 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; const bool in_status_line = wp->w_status_height != 0 || is_stl_global;
maxwidth = in_status_line && !is_stl_global ? wp->w_width : Columns; 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); 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); grid = grid_adjust(&msg_grid_adj, &row, &col);
maxwidth--; // writing in last column may cause scrolling maxwidth--; // writing in last column may cause scrolling
fillchar = schar_from_ascii(' '); fillchar = schar_from_ascii(' ');
attr = HL_ATTR(HLF_MSG); group = HLF_MSG;
} }
} else { } else {
opt_idx = kOptStatusline; 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); opt_scope = ((*wp->w_p_stl != NUL) ? OPT_LOCAL : 0);
} }
attr = win_hl_attr(wp, (int)group);
if (in_status_line && !is_stl_global) { if (in_status_line && !is_stl_global) {
col += wp->w_wincol; 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 len = (int)strlen(buf);
int start_col = col; int start_col = col;
// Draw each snippet with the specified highlighting. if (!ui_event) {
screengrid_line_start(grid, row, 0); // Draw each snippet with the specified highlighting.
screengrid_line_start(grid, row, 0);
}
int curattr = attr;
char *p = buf; char *p = buf;
for (int n = 0; hltab[n].start != NULL; n++) { int curattr = attr;
int textlen = (int)(hltab[n].start - p); int curgroup = (int)group;
// Make all characters printable. Array content = ARRAY_DICT_INIT;
size_t tsize = transstr_buf(p, textlen, transbuf, sizeof transbuf, true); for (stl_hlrec_t *sp = hltab;; sp++) {
col += grid_line_puts(col, transbuf, (int)tsize, curattr); int textlen = (int)(sp->start ? sp->start - p : buf + len - p);
p = hltab[n].start; // 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 (hltab[n].userhl == 0) { if (!ui_event) {
curattr = attr; col += grid_line_puts(col, transbuf, (int)tsize, curattr);
} 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];
} else { } 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); if (ui_event) {
col += grid_line_puts(col, transbuf, (int)tsize, curattr); ui_call_msg_ruler(content);
api_free_array(content);
goto theend;
}
int maxcol = start_col + maxwidth; int maxcol = start_col + maxwidth;
// fill up with "fillchar" // fill up with "fillchar"
@@ -389,7 +420,7 @@ void win_redr_winbar(win_T *wp)
if (wp->w_winbar_height == 0 || !redrawing()) { if (wp->w_winbar_height == 0 || !redrawing()) {
// Do nothing. // Do nothing.
} else if (*p_wbr != NUL || *wp->w_p_wbr != NUL) { } 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; entered = false;
} }
@@ -423,44 +454,22 @@ void redraw_ruler(void)
// Don't draw the ruler while doing insert-completion, it might overwrite // Don't draw the ruler while doing insert-completion, it might overwrite
// the (long) mode message. // the (long) mode message.
if (wp->w_status_height == 0 && !is_stl_global) { if (wp->w_status_height == 0 && !is_stl_global && edit_submode != NULL) {
if (edit_submode != NULL) {
return;
}
}
if (*p_ruf && p_ch > 0 && !ui_has(kUIMessages)) {
win_redr_custom(wp, false, true);
return; return;
} }
// Check if not in Insert mode and the line is empty (will show "0-1"). bool part_of_status = wp->w_status_height || is_stl_global;
int empty_line = (State & MODE_INSERT) == 0 if (*p_ruf && (p_ch > 0 || (ui_has(kUIMessages) && !part_of_status))) {
&& *ml_get_buf(wp->w_buffer, wp->w_cursor.lnum) == NUL; win_redr_custom(wp, false, true, ui_has(kUIMessages));
return;
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 // In list mode virtcol needs to be recomputed
colnr_T virtcol = wp->w_virtcol; colnr_T virtcol = wp->w_virtcol;
if (wp->w_p_list && wp->w_p_lcs_chars.tab1 == NUL) { 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; 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 #define RULER_BUF_LEN 70
char buffer[RULER_BUF_LEN]; 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". /// 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) { if (wp == curwin) {
*attr = win_hl_attr(wp, HLF_S); *group = HLF_S;
return wp->w_p_fcs_chars.stl; return wp->w_p_fcs_chars.stl;
} else { } else {
*attr = win_hl_attr(wp, HLF_SNC); *group = HLF_SNC;
return wp->w_p_fcs_chars.stlnc; return wp->w_p_fcs_chars.stlnc;
} }
} }
@@ -566,7 +579,7 @@ void redraw_custom_statusline(win_T *wp)
} }
entered = true; entered = true;
win_redr_custom(wp, false, false); win_redr_custom(wp, false, false, false);
entered = false; entered = false;
} }
@@ -644,7 +657,7 @@ void draw_tabline(void)
// Use the 'tabline' option if it's set. // Use the 'tabline' option if it's set.
if (*p_tal != NUL) { if (*p_tal != NUL) {
win_redr_custom(NULL, false, false); win_redr_custom(NULL, false, false, false);
} else { } else {
int tabcount = 0; int tabcount = 0;
int col = 0; int col = 0;

View File

@@ -2,6 +2,7 @@
#include <stddef.h> #include <stddef.h>
#include "nvim/highlight_defs.h"
#include "nvim/macros_defs.h" #include "nvim/macros_defs.h"
#include "nvim/option_defs.h" // IWYU pragma: keep #include "nvim/option_defs.h" // IWYU pragma: keep
#include "nvim/statusline_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() before_each(function()
screen = Screen.new(50, 7) 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) end)
it('is updated in cmdline mode when using window-local statusline vim-patch:8.2.2737', function() 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([[ screen:expect([[
^ | ^ |
{1:~ }|*4 {1:~ }|*4
{9:<F}{18:GHI }| {100:<F}{101:GHI }|
| |
]]) ]])
end) end)

View File

@@ -852,7 +852,7 @@ describe('ui/ext_messages', function()
} }
end) end)
it('supports &showcmd and &ruler', function() it("supports 'showcmd' and 'ruler(format)'", function()
command('set showcmd ruler') command('set showcmd ruler')
command('hi link MsgArea ErrorMsg') command('hi link MsgArea ErrorMsg')
screen:expect({ screen:expect({
@@ -940,19 +940,26 @@ describe('ui/ext_messages', function()
]], ]],
ruler = { { '2,0-1 All', 'MsgArea' } }, ruler = { { '2,0-1 All', 'MsgArea' } },
} }
command('set rulerformat=Foo%#ErrorMsg#Bar')
-- 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({ screen:expect({
grid = [[ grid = [[
abcde | abcde |
^ | ^ |
{1:~ }|*2 {1:~ }|*3
{3:<] [+] 2,0-1 All}|
]], ]],
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) end)
it('keeps history of message of different kinds', function() 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)) call assert_equal(sa3, screenattr(&lines - 1, 7))
" %*: Set highlight group to User{N} " %*: 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 set statusline=a%1*b%0*c
call assert_match('^abc\s*$', s:get_statusline()) call assert_match('^abc\s*$', s:get_statusline())
let sa1=screenattr(&lines - 1, 1) let sa1=screenattr(&lines - 1, 1)