Draw message as one format, allowing prompts and messages to occupy only

a portion of the status bar, overlaying the normal status content rather
than replacing the entire line. A new message-format option now controls
the entire message (like status-format). From Conor Taylor in GitHub
issue 4861.
This commit is contained in:
nicm
2026-03-12 07:25:13 +00:00
parent 551e8fcd24
commit 19f3fb131b
5 changed files with 203 additions and 42 deletions

View File

@@ -724,13 +724,24 @@ const struct options_table_entry options_table[] = {
{ .name = "message-command-style", { .name = "message-command-style",
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SESSION, .scope = OPTIONS_TABLE_SESSION,
.default_str = "bg=black,fg=yellow", .default_str = "bg=black,fg=yellow,fill=black",
.flags = OPTIONS_TABLE_IS_STYLE, .flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",", .separator = ",",
.text = "Style of the command prompt when in command mode, if " .text = "Style of the command prompt when in command mode, if "
"'mode-keys' is set to 'vi'." "'mode-keys' is set to 'vi'."
}, },
{ .name = "message-format",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SESSION,
.default_str = "#[#{?#{command_prompt},"
"#{E:message-command-style},"
"#{E:message-style}}]"
"#{message}",
.text = "Format string for the prompt and message area. "
"The '#{message}' placeholder is replaced with the content."
},
{ .name = "message-line", { .name = "message-line",
.type = OPTIONS_TABLE_CHOICE, .type = OPTIONS_TABLE_CHOICE,
.scope = OPTIONS_TABLE_SESSION, .scope = OPTIONS_TABLE_SESSION,
@@ -742,10 +753,13 @@ const struct options_table_entry options_table[] = {
{ .name = "message-style", { .name = "message-style",
.type = OPTIONS_TABLE_STRING, .type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SESSION, .scope = OPTIONS_TABLE_SESSION,
.default_str = "bg=yellow,fg=black", .default_str = "bg=yellow,fg=black,fill=yellow",
.flags = OPTIONS_TABLE_IS_STYLE, .flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",", .separator = ",",
.text = "Style of messages and the command prompt. " .text = "Style of messages and the command prompt. "
"A 'fill' attribute controls background clearing and "
"a 'width' attribute (fixed or percentage) constrains "
"the prompt area width."
}, },
{ .name = "mouse", { .name = "mouse",

153
status.c
View File

@@ -551,6 +551,71 @@ status_message_callback(__unused int fd, __unused short event, void *data)
status_message_clear(c); status_message_clear(c);
} }
/*
* Calculate prompt/message area geometry from the style's width and align
* directives: x offset and available width within the status line.
*/
static void
status_prompt_area(struct client *c, u_int *area_x, u_int *area_w)
{
struct session *s = c->session;
struct style *sy;
u_int w;
/* Get width from message-style's width directive. */
sy = options_string_to_style(s->options, "message-style", NULL);
if (sy != NULL && sy->width >= 0) {
if (sy->width_percentage)
w = (c->tty.sx * (u_int)sy->width) / 100;
else
w = (u_int)sy->width;
} else
w = c->tty.sx;
if (w == 0 || w > c->tty.sx)
w = c->tty.sx;
/* Get horizontal position from message-style's align directive. */
if (sy != NULL) {
switch (sy->align) {
case STYLE_ALIGN_CENTRE:
case STYLE_ALIGN_ABSOLUTE_CENTRE:
*area_x = (c->tty.sx - w) / 2;
break;
case STYLE_ALIGN_RIGHT:
*area_x = c->tty.sx - w;
break;
default:
*area_x = 0;
break;
}
} else
*area_x = 0;
*area_w = w;
}
/* Escape # characters in a string so format_draw treats them as literal. */
static char *
status_prompt_escape(const char *s)
{
const char *cp;
char *out, *p;
size_t n = 0;
for (cp = s; *cp != '\0'; cp++) {
if (*cp == '#')
n++;
}
p = out = xmalloc(strlen(s) + n + 1);
for (cp = s; *cp != '\0'; cp++) {
if (*cp == '#')
*p++ = '#';
*p++ = *cp;
}
*p = '\0';
return (out);
}
/* Draw client message on status line of present else on last line. */ /* Draw client message on status line of present else on last line. */
int int
status_message_redraw(struct client *c) status_message_redraw(struct client *c)
@@ -559,10 +624,12 @@ status_message_redraw(struct client *c)
struct screen_write_ctx ctx; struct screen_write_ctx ctx;
struct session *s = c->session; struct session *s = c->session;
struct screen old_screen; struct screen old_screen;
size_t len; u_int lines, messageline;
u_int lines, offset, messageline; u_int ax, aw;
struct grid_cell gc; struct grid_cell gc;
struct format_tree *ft; struct format_tree *ft;
const char *msgfmt;
char *expanded, *msg;
if (c->tty.sx == 0 || c->tty.sy == 0) if (c->tty.sx == 0 || c->tty.sy == 0)
return (0); return (0);
@@ -577,26 +644,36 @@ status_message_redraw(struct client *c)
if (messageline > lines - 1) if (messageline > lines - 1)
messageline = lines - 1; messageline = lines - 1;
len = screen_write_strlen("%s", c->message_string); status_prompt_area(c, &ax, &aw);
if (len > c->tty.sx)
len = c->tty.sx;
ft = format_create_defaults(NULL, c, NULL, NULL, NULL); ft = format_create_defaults(NULL, c, NULL, NULL, NULL);
style_apply(&gc, s->options, "message-style", ft); memcpy(&gc, &grid_default_cell, sizeof gc);
/*
* Set #{message} in the format tree. If styles should be ignored in
* the message content, escape # characters so format_draw treats them
* as literal text.
*/
if (c->message_ignore_styles) {
msg = status_prompt_escape(c->message_string);
format_add(ft, "message", "%s", msg);
free(msg);
} else
format_add(ft, "message", "%s", c->message_string);
format_add(ft, "command_prompt", "%d", 0);
msgfmt = options_get_string(s->options, "message-format");
expanded = format_expand_time(ft, msgfmt);
format_free(ft); format_free(ft);
screen_write_start(&ctx, sl->active); screen_write_start(&ctx, sl->active);
screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines); screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines);
screen_write_cursormove(&ctx, 0, messageline, 0); screen_write_cursormove(&ctx, ax, messageline, 0);
for (offset = 0; offset < c->tty.sx; offset++) format_draw(&ctx, &gc, aw, expanded, NULL, 0);
screen_write_putc(&ctx, &gc, ' ');
screen_write_cursormove(&ctx, 0, messageline, 0);
if (c->message_ignore_styles)
screen_write_nputs(&ctx, len, &gc, "%s", c->message_string);
else
format_draw(&ctx, &gc, c->tty.sx, c->message_string, NULL, 0);
screen_write_stop(&ctx); screen_write_stop(&ctx);
free(expanded);
if (grid_compare(sl->active->grid, old_screen.grid) == 0) { if (grid_compare(sl->active->grid, old_screen.grid) == 0) {
screen_free(&old_screen); screen_free(&old_screen);
return (0); return (0);
@@ -789,9 +866,10 @@ status_prompt_redraw(struct client *c)
struct screen old_screen; struct screen old_screen;
u_int i, lines, offset, left, start, width, n; u_int i, lines, offset, left, start, width, n;
u_int pcursor, pwidth, promptline; u_int pcursor, pwidth, promptline;
u_int ax, aw;
struct grid_cell gc; struct grid_cell gc;
struct format_tree *ft = c->prompt_formats; const char *msgfmt;
char *prompt, *tmp; char *expanded, *prompt, *tmp;
if (c->tty.sx == 0 || c->tty.sy == 0) if (c->tty.sx == 0 || c->tty.sy == 0)
return (0); return (0);
@@ -816,29 +894,40 @@ status_prompt_redraw(struct client *c)
promptline = lines - 1; promptline = lines - 1;
if (c->prompt_mode == PROMPT_COMMAND) if (c->prompt_mode == PROMPT_COMMAND)
style_apply(&gc, s->options, "message-command-style", ft); style_apply(&gc, s->options, "message-command-style", NULL);
else else
style_apply(&gc, s->options, "message-style", ft); style_apply(&gc, s->options, "message-style", NULL);
status_prompt_area(c, &ax, &aw);
tmp = utf8_tocstr(c->prompt_buffer); tmp = utf8_tocstr(c->prompt_buffer);
format_add(c->prompt_formats, "prompt-input", "%s", tmp); format_add(c->prompt_formats, "prompt-input", "%s", tmp);
prompt = format_expand_time(c->prompt_formats, c->prompt_string); prompt = format_expand_time(c->prompt_formats, c->prompt_string);
free(tmp); free(tmp);
/*
* Set #{message} to the prompt string and expand message-format.
* format_draw handles fill, alignment, and decorations in one call.
*/
format_add(c->prompt_formats, "message", "%s", prompt);
format_add(c->prompt_formats, "command_prompt", "%d",
c->prompt_mode == PROMPT_COMMAND);
msgfmt = options_get_string(s->options, "message-format");
expanded = format_expand_time(c->prompt_formats, msgfmt);
start = format_width(prompt); start = format_width(prompt);
if (start > c->tty.sx) if (start > aw)
start = c->tty.sx; start = aw;
screen_write_start(&ctx, sl->active); screen_write_start(&ctx, sl->active);
screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines); screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines);
screen_write_cursormove(&ctx, 0, promptline, 0); screen_write_cursormove(&ctx, ax, promptline, 0);
for (offset = 0; offset < c->tty.sx; offset++) format_draw(&ctx, &gc, aw, expanded, NULL, 0);
screen_write_putc(&ctx, &gc, ' '); screen_write_cursormove(&ctx, ax + start, promptline, 0);
screen_write_cursormove(&ctx, 0, promptline, 0);
format_draw(&ctx, &gc, start, prompt, NULL, 0);
screen_write_cursormove(&ctx, start, promptline, 0);
left = c->tty.sx - start; free(expanded);
left = aw - start;
if (left == 0) if (left == 0)
goto finished; goto finished;
@@ -857,7 +946,7 @@ status_prompt_redraw(struct client *c)
offset = 0; offset = 0;
if (pwidth > left) if (pwidth > left)
pwidth = left; pwidth = left;
c->prompt_cursor = start + pcursor - offset; c->prompt_cursor = ax + start + pcursor - offset;
width = 0; width = 0;
for (i = 0; c->prompt_buffer[i].size != 0; i++) { for (i = 0; c->prompt_buffer[i].size != 0; i++) {
@@ -1831,7 +1920,7 @@ status_prompt_complete_list_menu(struct client *c, char **list, u_int size,
struct menu_item item; struct menu_item item;
struct status_prompt_menu *spm; struct status_prompt_menu *spm;
u_int lines = status_line_size(c), height, i; u_int lines = status_line_size(c), height, i;
u_int py; u_int py, ax, aw;
if (size <= 1) if (size <= 1)
return (0); return (0);
@@ -1859,11 +1948,13 @@ status_prompt_complete_list_menu(struct client *c, char **list, u_int size,
menu_add_item(menu, &item, NULL, c, NULL); menu_add_item(menu, &item, NULL, c, NULL);
} }
status_prompt_area(c, &ax, &aw);
if (options_get_number(c->session->options, "status-position") == 0) if (options_get_number(c->session->options, "status-position") == 0)
py = lines; py = lines;
else else
py = c->tty.sy - 3 - height; py = c->tty.sy - 3 - height;
offset += utf8_cstrwidth(c->prompt_string); offset += utf8_cstrwidth(c->prompt_string);
offset += ax;
if (offset > 2) if (offset > 2)
offset -= 2; offset -= 2;
else else
@@ -1890,7 +1981,7 @@ status_prompt_complete_window_menu(struct client *c, struct session *s,
struct winlink *wl; struct winlink *wl;
char **list = NULL, *tmp; char **list = NULL, *tmp;
u_int lines = status_line_size(c), height; u_int lines = status_line_size(c), height;
u_int py, size = 0, i; u_int py, size = 0, i, ax, aw;
if (c->tty.sy - lines < 3) if (c->tty.sy - lines < 3)
return (NULL); return (NULL);
@@ -1955,11 +2046,13 @@ status_prompt_complete_window_menu(struct client *c, struct session *s,
spm->size = size; spm->size = size;
spm->list = list; spm->list = list;
status_prompt_area(c, &ax, &aw);
if (options_get_number(c->session->options, "status-position") == 0) if (options_get_number(c->session->options, "status-position") == 0)
py = lines; py = lines;
else else
py = c->tty.sy - 3 - height; py = c->tty.sy - 3 - height;
offset += utf8_cstrwidth(c->prompt_string); offset += utf8_cstrwidth(c->prompt_string);
offset += ax;
if (offset > 2) if (offset > 2)
offset -= 2; offset -= 2;
else else

22
style.c
View File

@@ -39,7 +39,7 @@ static struct style style_default = {
STYLE_RANGE_NONE, 0, "", STYLE_RANGE_NONE, 0, "",
STYLE_WIDTH_DEFAULT, STYLE_PAD_DEFAULT, STYLE_WIDTH_DEFAULT, 0, STYLE_PAD_DEFAULT,
STYLE_DEFAULT_BASE STYLE_DEFAULT_BASE
}; };
@@ -226,10 +226,20 @@ style_parse(struct style *sy, const struct grid_cell *base, const char *in)
sy->gc.attr &= ~value; sy->gc.attr &= ~value;
} }
} else if (end > 6 && strncasecmp(tmp, "width=", 6) == 0) { } else if (end > 6 && strncasecmp(tmp, "width=", 6) == 0) {
if (end > 7 && tmp[end - 1] == '%') {
tmp[end - 1] = '\0';
n = strtonum(tmp + 6, 0, 100, &errstr);
if (errstr != NULL)
goto error;
sy->width = (int)n;
sy->width_percentage = 1;
} else {
n = strtonum(tmp + 6, 0, UINT_MAX, &errstr); n = strtonum(tmp + 6, 0, UINT_MAX, &errstr);
if (errstr != NULL) if (errstr != NULL)
goto error; goto error;
sy->width = (int)n; sy->width = (int)n;
sy->width_percentage = 0;
}
} else if (end > 4 && strncasecmp(tmp, "pad=", 4) == 0) { } else if (end > 4 && strncasecmp(tmp, "pad=", 4) == 0) {
n = strtonum(tmp + 4, 0, UINT_MAX, &errstr); n = strtonum(tmp + 4, 0, UINT_MAX, &errstr);
if (errstr != NULL) if (errstr != NULL)
@@ -343,13 +353,17 @@ style_tostring(struct style *sy)
comma = ","; comma = ",";
} }
if (gc->attr != 0) { if (gc->attr != 0) {
xsnprintf(s + off, sizeof s - off, "%s%s", comma, off += xsnprintf(s + off, sizeof s - off, "%s%s", comma,
attributes_tostring(gc->attr)); attributes_tostring(gc->attr));
comma = ","; comma = ",";
} }
if (sy->width >= 0) { if (sy->width >= 0) {
xsnprintf(s + off, sizeof s - off, "%swidth=%u", comma, if (sy->width_percentage)
sy->width); off += xsnprintf(s + off, sizeof s - off,
"%swidth=%u%%", comma, sy->width);
else
off += xsnprintf(s + off, sizeof s - off,
"%swidth=%u", comma, sy->width);
comma = ","; comma = ",";
} }
if (sy->pad >= 0) { if (sy->pad >= 0) {

39
tmux.1
View File

@@ -4745,6 +4745,23 @@ For how to specify
see the see the
.Sx STYLES .Sx STYLES
section. section.
.It Ic message-format Ar string
Set the format string for the prompt and message area.
The special placeholder
.Ql #{message}
expands to the interactive prompt or message text and
.Ql #{command_prompt}
is set to 1 when the prompt is in command mode (vi).
Style directives like
.Ic fill ,
.Ic align ,
and
.Ic width
may be used in the format string.
The default uses a conditional to select between
.Ic message-style
and
.Ic message-command-style .
.It Xo Ic message-line .It Xo Ic message-line
.Op Ic 0 | 1 | 2 | 3 | 4 .Op Ic 0 | 1 | 2 | 3 | 4
.Xc .Xc
@@ -4752,6 +4769,23 @@ Set line on which status line messages and the command prompt are shown.
.It Ic message-style Ar style .It Ic message-style Ar style
Set status line message style. Set status line message style.
This is used for messages and for the command prompt. This is used for messages and for the command prompt.
The message is drawn on top of the existing status line.
A
.Ic width
attribute (a fixed number of columns or a percentage such as
.Ql 50% )
constrains the prompt area width and an
.Ic align
attribute
.Pq Ic left , centre , right
sets its horizontal position.
When the width is less than the full terminal width, the normal status
bar content remains visible around the prompt area.
The
.Ic fill
attribute is used by the default
.Ic message-format
to clear the background.
For how to specify For how to specify
.Ar style , .Ar style ,
see the see the
@@ -6522,6 +6556,11 @@ is the terminal alternate character set.
Align text to the left, centre or right of the available space if appropriate. Align text to the left, centre or right of the available space if appropriate.
.It Ic fill=colour .It Ic fill=colour
Fill the available space with a background colour if appropriate. Fill the available space with a background colour if appropriate.
.It Ic width=N
Set the width of the styled area.
.Ar N
may be a column count or a percentage (for example
.Ql 50% ) .
.It Xo Ic list=on , .It Xo Ic list=on ,
.Ic list=focus , .Ic list=focus ,
.Ic list=left-marker , .Ic list=left-marker ,

1
tmux.h
View File

@@ -911,6 +911,7 @@ struct style {
char range_string[16]; char range_string[16];
int width; int width;
int width_percentage;
int pad; int pad;
enum style_default_type default_type; enum style_default_type default_type;