diff --git a/options-table.c b/options-table.c index 2eb4f6d5..8101a92c 100644 --- a/options-table.c +++ b/options-table.c @@ -724,13 +724,24 @@ const struct options_table_entry options_table[] = { { .name = "message-command-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "bg=black,fg=yellow", + .default_str = "bg=black,fg=yellow,fill=black", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of the command prompt when in command mode, if " "'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", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, @@ -742,10 +753,13 @@ const struct options_table_entry options_table[] = { { .name = "message-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "bg=yellow,fg=black", + .default_str = "bg=yellow,fg=black,fill=yellow", .flags = OPTIONS_TABLE_IS_STYLE, .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", diff --git a/status.c b/status.c index 87d5e017..cd8c62be 100644 --- a/status.c +++ b/status.c @@ -551,6 +551,71 @@ status_message_callback(__unused int fd, __unused short event, void *data) 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. */ int status_message_redraw(struct client *c) @@ -559,10 +624,12 @@ status_message_redraw(struct client *c) struct screen_write_ctx ctx; struct session *s = c->session; struct screen old_screen; - size_t len; - u_int lines, offset, messageline; + u_int lines, messageline; + u_int ax, aw; struct grid_cell gc; struct format_tree *ft; + const char *msgfmt; + char *expanded, *msg; if (c->tty.sx == 0 || c->tty.sy == 0) return (0); @@ -577,26 +644,36 @@ status_message_redraw(struct client *c) if (messageline > lines - 1) messageline = lines - 1; - len = screen_write_strlen("%s", c->message_string); - if (len > c->tty.sx) - len = c->tty.sx; + status_prompt_area(c, &ax, &aw); 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); screen_write_start(&ctx, sl->active); screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines); - screen_write_cursormove(&ctx, 0, messageline, 0); - for (offset = 0; offset < c->tty.sx; offset++) - 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_cursormove(&ctx, ax, messageline, 0); + format_draw(&ctx, &gc, aw, expanded, NULL, 0); screen_write_stop(&ctx); + free(expanded); + if (grid_compare(sl->active->grid, old_screen.grid) == 0) { screen_free(&old_screen); return (0); @@ -789,9 +866,10 @@ status_prompt_redraw(struct client *c) struct screen old_screen; u_int i, lines, offset, left, start, width, n; u_int pcursor, pwidth, promptline; + u_int ax, aw; struct grid_cell gc; - struct format_tree *ft = c->prompt_formats; - char *prompt, *tmp; + const char *msgfmt; + char *expanded, *prompt, *tmp; if (c->tty.sx == 0 || c->tty.sy == 0) return (0); @@ -816,29 +894,40 @@ status_prompt_redraw(struct client *c) promptline = lines - 1; 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 - 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); format_add(c->prompt_formats, "prompt-input", "%s", tmp); 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); - if (start > c->tty.sx) - start = c->tty.sx; + if (start > aw) + start = aw; screen_write_start(&ctx, sl->active); screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines); - screen_write_cursormove(&ctx, 0, promptline, 0); - for (offset = 0; offset < c->tty.sx; offset++) - screen_write_putc(&ctx, &gc, ' '); - screen_write_cursormove(&ctx, 0, promptline, 0); - format_draw(&ctx, &gc, start, prompt, NULL, 0); - screen_write_cursormove(&ctx, start, promptline, 0); + screen_write_cursormove(&ctx, ax, promptline, 0); + format_draw(&ctx, &gc, aw, expanded, NULL, 0); + screen_write_cursormove(&ctx, ax + start, promptline, 0); - left = c->tty.sx - start; + free(expanded); + + left = aw - start; if (left == 0) goto finished; @@ -857,7 +946,7 @@ status_prompt_redraw(struct client *c) offset = 0; if (pwidth > left) pwidth = left; - c->prompt_cursor = start + pcursor - offset; + c->prompt_cursor = ax + start + pcursor - offset; width = 0; 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 status_prompt_menu *spm; u_int lines = status_line_size(c), height, i; - u_int py; + u_int py, ax, aw; if (size <= 1) 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); } + status_prompt_area(c, &ax, &aw); if (options_get_number(c->session->options, "status-position") == 0) py = lines; else py = c->tty.sy - 3 - height; offset += utf8_cstrwidth(c->prompt_string); + offset += ax; if (offset > 2) offset -= 2; else @@ -1890,7 +1981,7 @@ status_prompt_complete_window_menu(struct client *c, struct session *s, struct winlink *wl; char **list = NULL, *tmp; 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) return (NULL); @@ -1955,11 +2046,13 @@ status_prompt_complete_window_menu(struct client *c, struct session *s, spm->size = size; spm->list = list; + status_prompt_area(c, &ax, &aw); if (options_get_number(c->session->options, "status-position") == 0) py = lines; else py = c->tty.sy - 3 - height; offset += utf8_cstrwidth(c->prompt_string); + offset += ax; if (offset > 2) offset -= 2; else diff --git a/style.c b/style.c index 4acc17dd..394eaf32 100644 --- a/style.c +++ b/style.c @@ -39,7 +39,7 @@ static struct style style_default = { STYLE_RANGE_NONE, 0, "", - STYLE_WIDTH_DEFAULT, STYLE_PAD_DEFAULT, + STYLE_WIDTH_DEFAULT, 0, STYLE_PAD_DEFAULT, STYLE_DEFAULT_BASE }; @@ -226,10 +226,20 @@ style_parse(struct style *sy, const struct grid_cell *base, const char *in) sy->gc.attr &= ~value; } } else if (end > 6 && strncasecmp(tmp, "width=", 6) == 0) { - n = strtonum(tmp + 6, 0, UINT_MAX, &errstr); - if (errstr != NULL) - goto error; - sy->width = (int)n; + 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); + if (errstr != NULL) + goto error; + sy->width = (int)n; + sy->width_percentage = 0; + } } else if (end > 4 && strncasecmp(tmp, "pad=", 4) == 0) { n = strtonum(tmp + 4, 0, UINT_MAX, &errstr); if (errstr != NULL) @@ -343,13 +353,17 @@ style_tostring(struct style *sy) comma = ","; } 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)); comma = ","; } if (sy->width >= 0) { - xsnprintf(s + off, sizeof s - off, "%swidth=%u", comma, - sy->width); + if (sy->width_percentage) + 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 = ","; } if (sy->pad >= 0) { diff --git a/tmux.1 b/tmux.1 index c404abab..9c15ab30 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4745,6 +4745,23 @@ For how to specify see the .Sx STYLES 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 .Op Ic 0 | 1 | 2 | 3 | 4 .Xc @@ -4752,6 +4769,23 @@ Set line on which status line messages and the command prompt are shown. .It Ic message-style Ar style Set status line message style. 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 .Ar style , 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. .It Ic fill=colour 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 , .Ic list=focus , .Ic list=left-marker , diff --git a/tmux.h b/tmux.h index 2a9eb46f..d7d2d100 100644 --- a/tmux.h +++ b/tmux.h @@ -911,6 +911,7 @@ struct style { char range_string[16]; int width; + int width_percentage; int pad; enum style_default_type default_type;