From 979313832ce1d5f6cdc2c512e8524d6c517422e0 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 18 Mar 2019 20:53:33 +0000 Subject: [PATCH] Extend the #[] style syntax and use that together with previous format changes to allow the status line to be entirely configured with a single option. Now that it is possible to configure their content, enable the existing code that lets the status line be multiple lines in height. The status option can now take a value of 2, 3, 4 or 5 (as well as the previous on or off) to configure more than one line. The new status-format array option configures the format of each line, the default just references the existing status-* options, although some of the more obscure status options may be eliminated in time. Additions to the #[] syntax are: "align" to specify alignment (left, centre, right), "list" for the window list and "range" to configure ranges of text for the mouse bindings. The "align" keyword can also be used to specify alignment of entries in tree mode and the pane status lines. --- Makefile | 1 + format-draw.c | 871 ++++++++++++++++++++++++++++++++++++++++++++++++ format.c | 6 +- mode-tree.c | 13 +- options-table.c | 52 ++- screen-redraw.c | 28 +- screen-write.c | 107 ------ server-client.c | 33 +- status.c | 433 ++++++------------------ style.c | 111 +++++- tmux.1 | 72 +++- tmux.h | 85 +++-- utf8.c | 60 ---- window-client.c | 26 +- window.c | 1 - 15 files changed, 1338 insertions(+), 561 deletions(-) create mode 100644 format-draw.c diff --git a/Makefile b/Makefile index 8947cd53..99417ba2 100644 --- a/Makefile +++ b/Makefile @@ -75,6 +75,7 @@ SRCS= alerts.c \ control.c \ environ.c \ format.c \ + format-draw.c \ grid-view.c \ grid.c \ hooks.c \ diff --git a/format-draw.c b/format-draw.c new file mode 100644 index 00000000..52292c98 --- /dev/null +++ b/format-draw.c @@ -0,0 +1,871 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2019 Nicholas Marriott + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include + +#include "tmux.h" + +/* Format range. */ +struct format_range { + u_int index; + struct screen *s; + + u_int start; + u_int end; + + enum style_range_type type; + u_int argument; + + TAILQ_ENTRY(format_range) entry; +}; +TAILQ_HEAD(format_ranges, format_range); + +/* Does this range match this style? */ +static int +format_is_type(struct format_range *fr, struct style *sy) +{ + if (fr->type != sy->range_type) + return (0); + if (fr->type == STYLE_RANGE_WINDOW && + fr->argument != sy->range_argument) + return (0); + return (1); +} + +/* Free a range. */ +static void +format_free_range(struct format_ranges *frs, struct format_range *fr) +{ + TAILQ_REMOVE(frs, fr, entry); + free(fr); +} + +/* Fix range positions. */ +static void +format_update_ranges(struct format_ranges *frs, struct screen *s, u_int offset, + u_int start, u_int width) +{ + struct format_range *fr, *fr1; + + if (frs == NULL) + return; + + TAILQ_FOREACH_SAFE(fr, frs, entry, fr1) { + if (fr->s != s) + continue; + + if (fr->end <= start || fr->start >= start + width) { + format_free_range(frs, fr); + continue; + } + + if (fr->start < start) + fr->start = start; + if (fr->end > start + width) + fr->end = start + width; + if (fr->start == fr->end) { + format_free_range(frs, fr); + continue; + } + + fr->start += offset; + fr->end += offset; + } +} + +/* Draw a part of the format. */ +static void +format_draw_put(struct screen_write_ctx *octx, u_int ocx, u_int ocy, + struct screen *s, struct format_ranges *frs, u_int offset, u_int start, + u_int width) +{ + /* + * The offset is how far from the cursor on the target screen; start + * and width how much to copy from the source screen. + */ + screen_write_cursormove(octx, ocx + offset, ocy, 0); + screen_write_fast_copy(octx, s, start, 0, width, 1); + format_update_ranges(frs, s, offset, start, width); +} + +/* Draw list part of format. */ +static void +format_draw_put_list(struct screen_write_ctx *octx, + u_int ocx, u_int ocy, u_int offset, u_int width, struct screen *list, + struct screen *list_left, struct screen *list_right, int focus_start, + int focus_end, struct format_ranges *frs) +{ + u_int start, focus_centre; + + /* If there is enough space for the list, draw it entirely. */ + if (width >= list->cx) { + format_draw_put(octx, ocx, ocy, list, frs, offset, 0, width); + return; + } + + /* The list needs to be trimmed. Try to keep the focus visible. */ + focus_centre = focus_start + (focus_end - focus_start) / 2; + if (focus_centre < width / 2) + start = 0; + else + start = focus_centre - width / 2; + if (start + width > list->cx) + start = list->cx - width; + + /* Draw <> markers at either side if needed. */ + if (start != 0 && width > list_left->cx) { + screen_write_cursormove(octx, ocx + offset, ocy, 0); + screen_write_fast_copy(octx, list_left, 0, 0, list_left->cx, 1); + offset += list_left->cx; + start += list_left->cx; + width -= list_left->cx; + } + if (start + width < list->cx && width > list_right->cx) { + screen_write_cursormove(octx, ocx + offset + width - 1, ocy, 0); + screen_write_fast_copy(octx, list_right, 0, 0, list_right->cx, + 1); + width -= list_right->cx; + } + + /* Draw the list screen itself. */ + format_draw_put(octx, ocx, ocy, list, frs, offset, start, width); +} + +/* Draw format with no list. */ +static void +format_draw_none(struct screen_write_ctx *octx, u_int available, u_int ocx, + u_int ocy, struct screen *left, struct screen *centre, struct screen *right, + struct format_ranges *frs) +{ + u_int width_left, width_centre, width_right; + + width_left = left->cx; + width_centre = centre->cx; + width_right = right->cx; + + /* + * Try to keep as much of the left and right as possible at the expense + * of the centre. + */ + while (width_left + width_centre + width_right > available) { + if (width_centre > 0) + width_centre--; + else if (width_right > 0) + width_right--; + else + width_left--; + } + + /* Write left. */ + format_draw_put(octx, ocx, ocy, left, frs, 0, 0, width_left); + + /* Write right at available - width_right. */ + format_draw_put(octx, ocx, ocy, right, frs, + available - width_right, + right->cx - width_right, + width_right); + + /* + * Write centre halfway between + * width_left + * and + * available - width_right. + */ + format_draw_put(octx, ocx, ocy, centre, frs, + width_left + + ((available - width_right) - width_left) / 2 + - width_centre / 2, + centre->cx / 2 - width_centre / 2, + width_centre); +} + +/* Draw format with list on the left. */ +static void +format_draw_left(struct screen_write_ctx *octx, u_int available, u_int ocx, + u_int ocy, struct screen *left, struct screen *centre, struct screen *right, + struct screen *list, struct screen *list_left, struct screen *list_right, + struct screen *after, int focus_start, int focus_end, + struct format_ranges *frs) +{ + u_int width_left, width_centre, width_right; + u_int width_list, width_after; + struct screen_write_ctx ctx; + + width_left = left->cx; + width_centre = centre->cx; + width_right = right->cx; + width_list = list->cx; + width_after = after->cx; + + /* + * Trim first the centre, then the list, then the right, then after the + * list, then the left. + */ + while (width_left + + width_centre + + width_right + + width_list + + width_after > available) { + if (width_centre > 0) + width_centre--; + else if (width_list > 0) + width_list--; + else if (width_right > 0) + width_right--; + else if (width_after > 0) + width_after--; + else + width_left--; + } + + /* If there is no list left, pass off to the no list function. */ + if (width_list == 0) { + screen_write_start(&ctx, NULL, left); + screen_write_fast_copy(&ctx, after, 0, 0, width_after, 1); + screen_write_stop(&ctx); + + format_draw_none(octx, available, ocx, ocy, left, centre, + right, frs); + return; + } + + /* Write left at 0. */ + format_draw_put(octx, ocx, ocy, left, frs, 0, 0, width_left); + + /* Write right at available - width_right. */ + format_draw_put(octx, ocx, ocy, right, frs, + available - width_right, + right->cx - width_right, + width_right); + + /* Write after at width_left + width_list. */ + format_draw_put(octx, ocx, ocy, after, frs, + width_left + width_list, + 0, + width_after); + + /* + * Write centre halfway between + * width_left + width_list + width_after + * and + * available - width_right. + */ + format_draw_put(octx, ocx, ocy, centre, frs, + (width_left + width_list + width_after) + + ((available - width_right) + - (width_left + width_list + width_after)) / 2 + - width_centre / 2, + centre->cx / 2 - width_centre / 2, + width_centre); + + /* + * The list now goes from + * width_left + * to + * width_left + width_list. + * If there is no focus given, keep the left in focus. + */ + if (focus_start == -1 || focus_end == -1) + focus_start = focus_end = 0; + format_draw_put_list(octx, ocx, ocy, width_left, width_list, list, + list_left, list_right, focus_start, focus_end, frs); +} + +/* Draw format with list in the centre. */ +static void +format_draw_centre(struct screen_write_ctx *octx, u_int available, u_int ocx, + u_int ocy, struct screen *left, struct screen *centre, struct screen *right, + struct screen *list, struct screen *list_left, struct screen *list_right, + struct screen *after, int focus_start, int focus_end, + struct format_ranges *frs) +{ + u_int width_left, width_centre, width_right; + u_int width_list, width_after, middle; + struct screen_write_ctx ctx; + + width_left = left->cx; + width_centre = centre->cx; + width_right = right->cx; + width_list = list->cx; + width_after = after->cx; + + /* + * Trim first the list, then after the list, then the centre, then the + * right, then the left. + */ + while (width_left + + width_centre + + width_right + + width_list + + width_after > available) { + if (width_list > 0) + width_list--; + else if (width_after > 0) + width_after--; + else if (width_centre > 0) + width_centre--; + else if (width_right > 0) + width_right--; + else + width_left--; + } + + /* If there is no list left, pass off to the no list function. */ + if (width_list == 0) { + screen_write_start(&ctx, NULL, centre); + screen_write_fast_copy(&ctx, after, 0, 0, width_after, 1); + screen_write_stop(&ctx); + + format_draw_none(octx, available, ocx, ocy, left, centre, + right, frs); + return; + } + + /* Write left at 0. */ + format_draw_put(octx, ocx, ocy, left, frs, 0, 0, width_left); + + /* Write after at available - width_after. */ + format_draw_put(octx, ocx, ocy, after, frs, + available - width_after, + after->cx - width_after, + width_after); + + /* Write right at available - width_right. */ + format_draw_put(octx, ocx, ocy, right, frs, + available - width_right, + right->cx - width_right, + width_right); + + /* + * All three centre sections are offset from the middle of the + * available space. + */ + middle = (width_left + ((available - width_right) - width_left) / 2); + + /* + * Write centre at + * middle - width_list / 2 - width_centre. + */ + format_draw_put(octx, ocx, ocy, centre, frs, + middle - width_list / 2 - width_centre, + 0, + width_centre); + + /* + * Write after at + * middle + width_list / 2 - width_centre. + */ + format_draw_put(octx, ocx, ocy, after, frs, + middle + width_list / 2, + 0, + width_after); + + /* + * The list now goes from + * middle - width_list / 2 + * to + * middle + width_list / 2 + * If there is no focus given, keep the centre in focus. + */ + if (focus_start == -1 || focus_end == -1) + focus_start = focus_end = list->cx / 2; + format_draw_put_list(octx, ocx, ocy, middle - width_list / 2, + width_list, list, list_left, list_right, focus_start, focus_end, + frs); +} + +/* Draw format with list on the right. */ +static void +format_draw_right(struct screen_write_ctx *octx, u_int available, u_int ocx, + u_int ocy, struct screen *left, struct screen *centre, struct screen *right, + struct screen *list, struct screen *list_left, struct screen *list_right, + struct screen *after, int focus_start, int focus_end, + struct format_ranges *frs) +{ + u_int width_left, width_centre, width_right; + u_int width_list, width_after; + struct screen_write_ctx ctx; + + width_left = left->cx; + width_centre = centre->cx; + width_right = right->cx; + width_list = list->cx; + width_after = after->cx; + + /* + * Trim first the centre, then the list, then the right, then + * after the list, then the left. + */ + while (width_left + + width_centre + + width_right + + width_list + + width_after > available) { + if (width_centre > 0) + width_centre--; + else if (width_list > 0) + width_list--; + else if (width_right > 0) + width_right--; + else if (width_after > 0) + width_after--; + else + width_left--; + } + + /* If there is no list left, pass off to the no list function. */ + if (width_list == 0) { + screen_write_start(&ctx, NULL, right); + screen_write_fast_copy(&ctx, after, 0, 0, width_after, 1); + screen_write_stop(&ctx); + + format_draw_none(octx, available, ocx, ocy, left, centre, + right, frs); + return; + } + + /* Write left at 0. */ + format_draw_put(octx, ocx, ocy, left, frs, 0, 0, width_left); + + /* Write after at available - width_after. */ + format_draw_put(octx, ocx, ocy, after, frs, + available - width_after, + after->cx - width_after, + width_after); + + /* + * Write right at + * available - width_right - width_list - width_after. + */ + format_draw_put(octx, ocx, ocy, right, frs, + available - width_right - width_list - width_after, + 0, + width_right); + + /* + * Write centre halfway between + * width_left + * and + * available - width_right - width_list - width_after. + */ + format_draw_put(octx, ocx, ocy, centre, frs, + width_left + + ((available - width_right - width_list - width_after) + - width_left) / 2 + - width_centre / 2, + centre->cx / 2 - width_centre / 2, + width_centre); + + /* + * The list now goes from + * available - width_list - width_after + * to + * available - width_after + * If there is no focus given, keep the right in focus. + */ + if (focus_start == -1 || focus_end == -1) + focus_start = focus_end = 0; + format_draw_put_list(octx, ocx, ocy, available - width_list - + width_after, width_list, list, list_left, list_right, focus_start, + focus_end, frs); +} + +/* Draw a format to a screen. */ +void +format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, + u_int available, const char *expanded, struct style_ranges *srs) +{ + enum { LEFT, + CENTRE, + RIGHT, + LIST, + LIST_LEFT, + LIST_RIGHT, + AFTER, + TOTAL } current = LEFT, last = LEFT; + const char *names[] = { "LEFT", + "CENTRE", + "RIGHT", + "LIST", + "LIST_LEFT", + "LIST_RIGHT", + "AFTER" }; + size_t size = strlen(expanded); + struct screen *os = octx->s, s[TOTAL]; + struct screen_write_ctx ctx[TOTAL]; + u_int ocx = os->cx, ocy = os->cy, i, width[TOTAL]; + u_int map[] = { LEFT, LEFT, CENTRE, RIGHT }; + int focus_start = -1, focus_end = -1; + int list_state = -1; + enum style_align list_align = STYLE_ALIGN_DEFAULT; + struct style sy; + struct utf8_data *ud = &sy.gc.data; + const char *cp, *end; + enum utf8_state more; + char *tmp; + struct format_range *fr = NULL, *fr1; + struct format_ranges frs; + struct style_range *sr; + + style_set(&sy, base); + TAILQ_INIT(&frs); + + /* + * We build three screens for left, right, centre alignment, one for + * the list, one for anything after the list and two for the list left + * and right markers. + */ + for (i = 0; i < TOTAL; i++) { + screen_init(&s[i], size, 1, 0); + screen_write_start(&ctx[i], NULL, &s[i]); + screen_write_clearendofline(&ctx[i], base->bg); + width[i] = 0; + } + + /* + * Walk the string and add to the corresponding screens, + * parsing styles as we go. + */ + cp = expanded; + while (*cp != '\0') { + if (cp[0] != '#' || cp[1] != '[') { + /* See if this is a UTF-8 character. */ + if ((more = utf8_open(ud, *cp)) == UTF8_MORE) { + while (*++cp != '\0' && more == UTF8_MORE) + more = utf8_append(ud, *cp); + if (more != UTF8_DONE) + cp -= ud->have; + } + + /* Not a UTF-8 character - ASCII or not valid. */ + if (more != UTF8_DONE) { + if (*cp < 0x20 || *cp > 0x7e) { + /* Ignore nonprintable characters. */ + cp++; + continue; + } + utf8_set(ud, *cp); + cp++; + } + + /* Draw the cell to th current screen. */ + screen_write_cell(&ctx[current], &sy.gc); + width[current] += ud->width; + continue; + } + + /* This is a style. Work out where the end is and parse it. */ + end = format_skip(cp + 2, "]"); + if (end == NULL) + return; + tmp = xstrndup(cp + 2, end - (cp + 2)); + if (style_parse(&sy, base, tmp) != 0) { + free(tmp); + return; + } + log_debug("style '%s' -> '%s'", tmp, style_tostring(&sy)); + free(tmp); + + /* Check the list state. */ + switch (sy.list) { + case STYLE_LIST_ON: + /* + * Entering the list, exiting a marker, or exiting the + * focus. + */ + if (list_state != 0) { + if (fr != NULL) { /* abort any region */ + free(fr); + fr = NULL; + } + list_state = 0; + list_align = sy.align; + } + + /* End the focus if started. */ + if (focus_start != -1 && focus_end == -1) + focus_end = s[LIST].cx; + + current = LIST; + break; + case STYLE_LIST_FOCUS: + /* Entering the focus. */ + if (list_state != 0) /* not inside the list */ + break; + if (focus_start == -1) /* focus already started */ + focus_start = s[LIST].cx; + break; + case STYLE_LIST_OFF: + /* Exiting or outside the list. */ + if (list_state == 0) { + if (fr != NULL) { /* abort any region */ + free(fr); + fr = NULL; + } + if (focus_start != -1 && focus_end == -1) + focus_end = s[LIST].cx; + + map[list_align] = AFTER; + if (list_align == STYLE_ALIGN_LEFT) + map[STYLE_ALIGN_DEFAULT] = AFTER; + list_state = 1; + } + current = map[sy.align]; + break; + case STYLE_LIST_LEFT_MARKER: + /* Entering left marker. */ + if (list_state != 0) /* not inside the list */ + break; + if (s[LIST_LEFT].cx != 0) /* already have marker */ + break; + if (fr != NULL) { /* abort any region */ + free(fr); + fr = NULL; + } + if (focus_start != -1 && focus_end == -1) + focus_start = focus_end = -1; + current = LIST_LEFT; + break; + case STYLE_LIST_RIGHT_MARKER: + /* Entering right marker. */ + if (list_state != 0) /* not inside the list */ + break; + if (s[LIST_RIGHT].cx != 0) /* already have marker */ + break; + if (fr != NULL) { /* abort any region */ + free(fr); + fr = NULL; + } + if (focus_start != -1 && focus_end == -1) + focus_start = focus_end = -1; + current = LIST_RIGHT; + break; + } + if (current != last) { + log_debug("%s: change %s -> %s", __func__, + names[last], names[current]); + last = current; + } + + /* + * Check if the range style has changed and if so end the + * current range and start a new one if needed. + */ + if (srs != NULL) { + if (fr != NULL && !format_is_type(fr, &sy)) { + if (s[current].cx != fr->start) { + fr->end = s[current].cx + 1; + TAILQ_INSERT_TAIL(&frs, fr, entry); + } else + free(fr); + fr = NULL; + } + if (fr == NULL && sy.range_type != STYLE_RANGE_NONE) { + fr = xcalloc(1, sizeof *fr); + fr->index = current; + + fr->s = &s[current]; + fr->start = s[current].cx; + + fr->type = sy.range_type; + fr->argument = sy.range_argument; + } + } + + cp = end + 1; + } + free(fr); + + for (i = 0; i < TOTAL; i++) + log_debug("%s: width %s is %u", __func__, names[i], width[i]); + if (focus_start != -1 && focus_end != -1) + log_debug("focus is %d-%d", focus_start, focus_end); + TAILQ_FOREACH(fr, &frs, entry) { + log_debug("%s: range %d|%u is %s %u-%u", __func__, fr->type, + fr->argument, names[fr->index], fr->start, fr->end); + } + + /* + * Draw the screens. How they are arranged depends on where the list + * appearsq. + */ + switch (list_align) { + case STYLE_ALIGN_DEFAULT: + /* No list. */ + format_draw_none(octx, available, ocx, ocy, &s[LEFT], + &s[CENTRE], &s[RIGHT], &frs); + break; + case STYLE_ALIGN_LEFT: + /* List is part of the left. */ + format_draw_left(octx, available, ocx, ocy, &s[LEFT], + &s[CENTRE], &s[RIGHT], &s[LIST], &s[LIST_LEFT], + &s[LIST_RIGHT], &s[AFTER], focus_start, focus_end, &frs); + break; + case STYLE_ALIGN_CENTRE: + /* List is part of the centre. */ + format_draw_centre(octx, available, ocx, ocy, &s[LEFT], + &s[CENTRE], &s[RIGHT], &s[LIST], &s[LIST_LEFT], + &s[LIST_RIGHT], &s[AFTER], focus_start, focus_end, &frs); + break; + case STYLE_ALIGN_RIGHT: + /* List is part of the right. */ + format_draw_right(octx, available, ocx, ocy, &s[LEFT], + &s[CENTRE], &s[RIGHT], &s[LIST], &s[LIST_LEFT], + &s[LIST_RIGHT], &s[AFTER], focus_start, focus_end, &frs); + break; + } + + /* Create ranges to return. */ + TAILQ_FOREACH_SAFE(fr, &frs, entry, fr1) { + sr = xcalloc(1, sizeof *sr); + sr->type = fr->type; + sr->argument = fr->argument; + sr->start = fr->start; + sr->end = fr->end; + TAILQ_INSERT_TAIL(srs, sr, entry); + + log_debug("%s: range %d|%u at %u-%u", __func__, sr->type, + sr->argument, sr->start, sr->end); + + format_free_range(&frs, fr); + } + + /* Restore the original cursor position. */ + screen_write_cursormove(octx, ocx, ocy, 0); +} + +/* Get width, taking #[] into account. */ +u_int +format_width(const char *expanded) +{ + const char *cp, *end; + u_int width = 0; + struct utf8_data ud; + enum utf8_state more; + + cp = expanded; + while (*cp != '\0') { + if (cp[0] == '#' && cp[1] == '[') { + end = format_skip(cp + 2, "]"); + if (end == NULL) + return 0; + cp = end + 1; + } else if ((more = utf8_open(&ud, *cp)) == UTF8_MORE) { + while (*++cp != '\0' && more == UTF8_MORE) + more = utf8_append(&ud, *cp); + if (more == UTF8_DONE) + width += ud.width; + else + cp -= ud.have; + } else if (*cp > 0x1f && *cp < 0x7f) { + width++; + cp++; + } + } + return (width); +} + +/* Trim on the left, taking #[] into account. */ +char * +format_trim_left(const char *expanded, u_int limit) +{ + char *copy, *out; + const char *cp = expanded, *end; + u_int width = 0; + struct utf8_data ud; + enum utf8_state more; + + out = copy = xmalloc(strlen(expanded) + 1); + while (*cp != '\0') { + if (cp[0] == '#' && cp[1] == '[') { + end = format_skip(cp + 2, "]"); + if (end == NULL) + break; + memcpy(out, cp, end + 1 - cp); + out += (end + 1 - cp); + cp = end + 1; + } else if ((more = utf8_open(&ud, *cp)) == UTF8_MORE) { + while (*++cp != '\0' && more == UTF8_MORE) + more = utf8_append(&ud, *cp); + if (more == UTF8_DONE) { + if (width + ud.width <= limit) { + memcpy(out, ud.data, ud.size); + out += ud.size; + } + width += ud.width; + } else + cp -= ud.have; + } else if (*cp > 0x1f && *cp < 0x7f) { + if (width + 1 <= limit) + *out++ = *cp; + width++; + cp++; + } + } + *out = '\0'; + return (copy); +} + +/* Trim on the right, taking #[] into account. */ +char * +format_trim_right(const char *expanded, u_int limit) +{ + char *copy, *out; + const char *cp = expanded, *end; + u_int width = 0, total_width, skip; + struct utf8_data ud; + enum utf8_state more; + + total_width = format_width(expanded); + if (total_width <= limit) + return (xstrdup(expanded)); + skip = total_width - limit; + + out = copy = xmalloc(strlen(expanded) + 1); + while (*cp != '\0') { + if (cp[0] == '#' && cp[1] == '[') { + end = format_skip(cp + 2, "]"); + if (end == NULL) + break; + memcpy(out, cp, end + 1 - cp); + out += (end + 1 - cp); + cp = end + 1; + } else if ((more = utf8_open(&ud, *cp)) == UTF8_MORE) { + while (*++cp != '\0' && more == UTF8_MORE) + more = utf8_append(&ud, *cp); + if (more == UTF8_DONE) { + if (width >= skip) { + memcpy(out, ud.data, ud.size); + out += ud.size; + } + width += ud.width; + } else + cp -= ud.have; + } else if (*cp > 0x1f && *cp < 0x7f) { + if (width >= skip) + *out++ = *cp; + width++; + cp++; + } + } + *out = '\0'; + return (copy); +} diff --git a/format.c b/format.c index 501b0a34..30391da1 100644 --- a/format.c +++ b/format.c @@ -972,7 +972,7 @@ found: } /* Skip until end. */ -static const char * +const char * format_skip(const char *s, const char *end) { int brackets = 0; @@ -1580,12 +1580,12 @@ done: /* Truncate the value if needed. */ if (limit > 0) { - new = utf8_trimcstr(value, limit); + new = format_trim_left(value, limit); format_log(ft, "applied length limit %d: %s", limit, new); free(value); value = new; } else if (limit < 0) { - new = utf8_rtrimcstr(value, -limit); + new = format_trim_right(value, -limit); format_log(ft, "applied length limit %d: %s", limit, new); free(value); value = new; diff --git a/mode-tree.c b/mode-tree.c index 112969ea..60e23534 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -497,7 +497,7 @@ mode_tree_draw(struct mode_tree_data *mtd) struct options *oo = wp->window->options; struct screen_write_ctx ctx; struct grid_cell gc0, gc; - u_int w, h, i, j, sy, box_x, box_y; + u_int w, h, i, j, sy, box_x, box_y, width; char *text, *start, key[7]; const char *tag, *symbol; size_t size, n; @@ -572,8 +572,9 @@ mode_tree_draw(struct mode_tree_data *mtd) tag = "*"; else tag = ""; - xasprintf(&text, "%-*s%s%s%s: %s", keylen, key, start, - mti->name, tag, mti->text); + xasprintf(&text, "%-*s%s%s%s: ", keylen, key, start, mti->name, + tag); + width = utf8_cstrwidth(text); free(start); if (mti->tagged) { @@ -582,11 +583,13 @@ mode_tree_draw(struct mode_tree_data *mtd) } if (i != mtd->current) { - screen_write_cnputs(&ctx, w, &gc0, "%s", text); screen_write_clearendofline(&ctx, 8); + screen_write_puts(&ctx, &gc0, "%s", text); + format_draw(&ctx, &gc0, w - width, mti->text, NULL); } else { - screen_write_cnputs(&ctx, w, &gc, "%s", text); screen_write_clearendofline(&ctx, gc.bg); + screen_write_puts(&ctx, &gc, "%s", text); + format_draw(&ctx, &gc, w - width, mti->text, NULL); } free(text); diff --git a/options-table.c b/options-table.c index 73d22b0a..a2a3ffe6 100644 --- a/options-table.c +++ b/options-table.c @@ -39,6 +39,9 @@ static const char *options_table_mode_keys_list[] = { static const char *options_table_clock_mode_style_list[] = { "12", "24", NULL }; +static const char *options_table_status_list[] = { + "off", "on", "2", "3", "4", "5", NULL +}; static const char *options_table_status_keys_list[] = { "emacs", "vi", NULL }; @@ -64,6 +67,46 @@ static const char *options_table_window_size_list[] = { "largest", "smallest", "manual", NULL }; +/* Status line format. */ +#define OPTIONS_TABLE_STATUS_FORMAT1 \ + "#[align=left range=left #{status-left-style}]" \ + "#{T;=/#{status-left-length}:status-left}#[norange default]" \ + "#[list=on align=#{status-justify}]" \ + "#[list=left-marker]<#[list=right-marker]>#[list=on]" \ + "#{W:" \ + "#[range=window|#{window_index}" \ + "#{?window_last_flag, #{window-status-last-style},}" \ + "#{?window_bell_flag," \ + " #{window-status-bell-style}," \ + "#{?window_activity_flag," \ + " #{window-status-activity-style},}" \ + "}" \ + "]" \ + "#{T:window-status-format}" \ + "#[norange default]" \ + "#{?window_end_flag,,#{window-status-separator}}" \ + "," \ + "#[range=window|#{window_index} list=focus" \ + "#{?window_last_flag, #{window-status-last-style},}" \ + "#{?window_bell_flag," \ + " #{window-status-bell-style}," \ + "#{?window_activity_flag," \ + " #{window-status-activity-style},}" \ + "}" \ + "]" \ + "#{T:window-status-current-format}" \ + "#[norange list=on default]" \ + "#{?window_end_flag,,#{window-status-separator}}" \ + "}" \ + "#[nolist align=right range=right #{status-right-style}]" \ + "#{T;=/#{status-right-length}:status-right}#[norange default]" +#define OPTIONS_TABLE_STATUS_FORMAT2 \ + "#[align=centre]#{P:#{?pane_active,#[reverse],}" \ + "#{pane_index}[#{pane_width}x#{pane_height}]#[default] }" +static const char *options_table_status_format_default[] = { + OPTIONS_TABLE_STATUS_FORMAT1, OPTIONS_TABLE_STATUS_FORMAT2, NULL +}; + /* Top-level options. */ const struct options_table_entry options_table[] = { { .name = "buffer-limit", @@ -378,8 +421,9 @@ const struct options_table_entry options_table[] = { }, { .name = "status", - .type = OPTIONS_TABLE_FLAG, + .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, + .choices = options_table_status_list, .default_num = 1 }, @@ -404,6 +448,12 @@ const struct options_table_entry options_table[] = { .style = "status-style" }, + { .name = "status-format", + .type = OPTIONS_TABLE_ARRAY, + .scope = OPTIONS_TABLE_SESSION, + .default_arr = options_table_status_format_default, + }, + { .name = "status-interval", .type = OPTIONS_TABLE_NUMBER, .scope = OPTIONS_TABLE_SESSION, diff --git a/screen-redraw.c b/screen-redraw.c index 9880433d..691b2194 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -274,8 +274,8 @@ screen_redraw_make_pane_status(struct client *c, struct window *w, struct grid_cell gc; const char *fmt; struct format_tree *ft; - char *out; - size_t outlen; + char *expanded; + u_int width, i; struct screen_write_ctx ctx; struct screen old; @@ -289,27 +289,27 @@ screen_redraw_make_pane_status(struct client *c, struct window *w, ft = format_create(c, NULL, FORMAT_PANE|wp->id, 0); format_defaults(ft, c, NULL, NULL, wp); + expanded = format_expand_time(ft, fmt); + wp->status_size = width = wp->sx - 4; + memcpy(&old, &wp->status_screen, sizeof old); - screen_init(&wp->status_screen, wp->sx, 1, 0); + screen_init(&wp->status_screen, width, 1, 0); wp->status_screen.mode = 0; - out = format_expand(ft, fmt); - outlen = screen_write_cstrlen("%s", out); - if (outlen > wp->sx - 4) - outlen = wp->sx - 4; - screen_resize(&wp->status_screen, outlen, 1, 0); - screen_write_start(&ctx, NULL, &wp->status_screen); + + gc.attr |= GRID_ATTR_CHARSET; + for (i = 0; i < width; i++) + screen_write_putc(&ctx, &gc, 'q'); + gc.attr &= ~GRID_ATTR_CHARSET; + screen_write_cursormove(&ctx, 0, 0, 0); - screen_write_clearline(&ctx, 8); - screen_write_cnputs(&ctx, outlen, &gc, "%s", out); + format_draw(&ctx, &gc, width, expanded, NULL); screen_write_stop(&ctx); - free(out); + free(expanded); format_free(ft); - wp->status_size = outlen; - if (grid_compare(wp->status_screen.grid, old.grid) == 0) { screen_free(&old); return (0); diff --git a/screen-write.c b/screen-write.c index eb12474d..0d57b818 100644 --- a/screen-write.c +++ b/screen-write.c @@ -169,41 +169,6 @@ screen_write_putc(struct screen_write_ctx *ctx, const struct grid_cell *gcp, screen_write_cell(ctx, &gc); } -/* Calculate string length, with embedded formatting. */ -size_t -screen_write_cstrlen(const char *fmt, ...) -{ - va_list ap; - char *msg, *msg2, *ptr, *ptr2; - size_t size; - - va_start(ap, fmt); - xvasprintf(&msg, fmt, ap); - va_end(ap); - msg2 = xmalloc(strlen(msg) + 1); - - ptr = msg; - ptr2 = msg2; - while (*ptr != '\0') { - if (ptr[0] == '#' && ptr[1] == '[') { - while (*ptr != ']' && *ptr != '\0') - ptr++; - if (*ptr == ']') - ptr++; - continue; - } - *ptr2++ = *ptr++; - } - *ptr2 = '\0'; - - size = screen_write_strlen("%s", msg2); - - free(msg); - free(msg2); - - return (size); -} - /* Calculate string length. */ size_t screen_write_strlen(const char *fmt, ...) @@ -322,78 +287,6 @@ screen_write_vnputs(struct screen_write_ctx *ctx, ssize_t maxlen, free(msg); } -/* Write string, similar to nputs, but with embedded formatting (#[]). */ -void -screen_write_cnputs(struct screen_write_ctx *ctx, ssize_t maxlen, - const struct grid_cell *gcp, const char *fmt, ...) -{ - struct style sy; - struct utf8_data *ud = &sy.gc.data; - va_list ap; - char *msg; - u_char *ptr, *last; - size_t left, size = 0; - enum utf8_state more; - - style_set(&sy, gcp); - - va_start(ap, fmt); - xvasprintf(&msg, fmt, ap); - va_end(ap); - - ptr = msg; - while (*ptr != '\0') { - if (ptr[0] == '#' && ptr[1] == '[') { - ptr += 2; - last = ptr + strcspn(ptr, "]"); - if (*last == '\0') { - /* No ]. Not much point in doing anything. */ - break; - } - *last = '\0'; - - style_parse(&sy, gcp, ptr); - ptr = last + 1; - continue; - } - if (*ptr > 0x7f && utf8_open(ud, *ptr) == UTF8_MORE) { - ptr++; - - left = strlen(ptr); - if (left < (size_t)ud->size - 1) - break; - while ((more = utf8_append(ud, *ptr)) == UTF8_MORE) - ptr++; - ptr++; - - if (more != UTF8_DONE) - continue; - if (maxlen > 0 && size + ud->width > (size_t)maxlen) { - while (size < (size_t)maxlen) { - screen_write_putc(ctx, &sy.gc, ' '); - size++; - } - break; - } - size += ud->width; - screen_write_cell(ctx, &sy.gc); - } else { - if (maxlen > 0 && size + 1 > (size_t)maxlen) - break; - - if (*ptr == '\001') - sy.gc.attr ^= GRID_ATTR_CHARSET; - else if (*ptr > 0x1f && *ptr < 0x7f) { - size++; - screen_write_putc(ctx, &sy.gc, *ptr); - } - ptr++; - } - } - - free(msg); -} - /* Copy from another screen. Assumes target region is big enough. */ void screen_write_copy(struct screen_write_ctx *ctx, struct screen *src, u_int px, diff --git a/server-client.c b/server-client.c index fe79163f..eee5dff5 100644 --- a/server-client.c +++ b/server-client.c @@ -411,12 +411,13 @@ server_client_check_mouse(struct client *c) { struct session *s = c->session; struct mouse_event *m = &c->tty.mouse; - struct window *w; + struct winlink *wl; struct window_pane *wp; u_int x, y, b, sx, sy, px, py; int flag; key_code key; struct timeval tv; + struct style_range *sr; enum { NOTYPE, MOVE, DOWN, UP, DRAG, WHEEL, DOUBLE, TRIPLE } type; enum { NOWHERE, PANE, STATUS, STATUS_LEFT, STATUS_RIGHT, BORDER } where; @@ -503,17 +504,29 @@ have_event: /* Is this on the status line? */ m->statusat = status_at_line(c); - if (m->statusat != -1 && y == (u_int)m->statusat) { - if (x < c->status.left_size) + if (m->statusat != -1 && + y >= (u_int)m->statusat && + y < m->statusat + status_line_size(c)) + sr = status_get_range(c, x, y - m->statusat); + else + sr = NULL; + if (sr != NULL) { + switch (sr->type) { + case STYLE_RANGE_NONE: + break; + case STYLE_RANGE_LEFT: where = STATUS_LEFT; - else if (x > c->tty.sx - c->status.right_size) + break; + case STYLE_RANGE_RIGHT: where = STATUS_RIGHT; - else { - w = status_get_window_at(c, x); - if (w == NULL) - return (KEYC_UNKNOWN); - m->w = w->id; - where = STATUS; + break; + case STYLE_RANGE_WINDOW: + wl = winlink_find_by_index(&s->windows, sr->argument); + if (wl != NULL) { + m->w = wl->window->id; + where = STATUS; + } + break; } } diff --git a/status.c b/status.c index de6d2716..467db6ad 100644 --- a/status.c +++ b/status.c @@ -29,14 +29,6 @@ #include "tmux.h" -static char *status_redraw_get_left(struct client *, struct grid_cell *, - size_t *); -static char *status_redraw_get_right(struct client *, struct grid_cell *, - size_t *); -static char *status_print(struct client *, struct winlink *, - struct grid_cell *); -static char *status_replace(struct client *, struct winlink *, - const char *); static void status_message_callback(int, short, void *); static void status_timer_callback(int, short, void *); @@ -196,7 +188,8 @@ status_timer_start_all(void) void status_update_cache(struct session *s) { - if (!options_get_number(s->options, "status")) + s->statuslines = options_get_number(s->options, "status"); + if (s->statuslines == 0) s->statusat = -1; else if (options_get_number(s->options, "status-position") == 0) s->statusat = 0; @@ -225,77 +218,37 @@ status_line_size(struct client *c) if (c->flags & CLIENT_STATUSOFF) return (0); - if (s->statusat == -1) - return (0); - return (1); -} - -/* Retrieve options for left string. */ -static char * -status_redraw_get_left(struct client *c, struct grid_cell *gc, size_t *size) -{ - struct session *s = c->session; - const char *template; - char *left; - size_t leftlen; - - style_apply_update(gc, s->options, "status-left-style"); - - template = options_get_string(s->options, "status-left"); - left = status_replace(c, NULL, template); - - *size = options_get_number(s->options, "status-left-length"); - leftlen = screen_write_cstrlen("%s", left); - if (leftlen < *size) - *size = leftlen; - return (left); -} - -/* Retrieve options for right string. */ -static char * -status_redraw_get_right(struct client *c, struct grid_cell *gc, size_t *size) -{ - struct session *s = c->session; - const char *template; - char *right; - size_t rightlen; - - style_apply_update(gc, s->options, "status-right-style"); - - template = options_get_string(s->options, "status-right"); - right = status_replace(c, NULL, template); - - *size = options_get_number(s->options, "status-right-length"); - rightlen = screen_write_cstrlen("%s", right); - if (rightlen < *size) - *size = rightlen; - return (right); + return (s->statuslines); } /* Get window at window list position. */ -struct window * -status_get_window_at(struct client *c, u_int x) +struct style_range * +status_get_range(struct client *c, u_int x, u_int y) { - struct session *s = c->session; - struct winlink *wl; - struct options *oo; - const char *sep; - size_t seplen; + struct status_line *sl = &c->status; + struct style_range *sr; - x += c->status.window_list_offset; - RB_FOREACH(wl, winlinks, &s->windows) { - oo = wl->window->options; - - sep = options_get_string(oo, "window-status-separator"); - seplen = screen_write_cstrlen("%s", sep); - - if (x < wl->status_width) - return (wl->window); - x -= wl->status_width + seplen; + if (y >= nitems(sl->entries)) + return (NULL); + TAILQ_FOREACH(sr, &sl->entries[y].ranges, entry) { + if (x >= sr->start && x < sr->end) + return (sr); } return (NULL); } +/* Free all ranges. */ +static void +status_free_ranges(struct style_ranges *srs) +{ + struct style_range *sr, *sr1; + + TAILQ_FOREACH_SAFE(sr, srs, entry, sr1) { + TAILQ_REMOVE(srs, sr, entry); + free(sr); + } +} + /* Save old status line. */ static void status_push_screen(struct client *c) @@ -327,6 +280,10 @@ void status_init(struct client *c) { struct status_line *sl = &c->status; + u_int i; + + for (i = 0; i < nitems(sl->entries); i++) + TAILQ_INIT(&sl->entries[i].ranges); screen_init(&sl->screen, c->tty.sx, 1, 0); sl->active = &sl->screen; @@ -337,6 +294,12 @@ void status_free(struct client *c) { struct status_line *sl = &c->status; + u_int i; + + for (i = 0; i < nitems(sl->entries); i++) { + status_free_ranges(&sl->entries[i].ranges); + free((void *)sl->entries[i].expanded); + } if (event_initialized(&sl->timer)) evtimer_del(&sl->timer); @@ -352,19 +315,19 @@ status_free(struct client *c) int status_redraw(struct client *c) { - struct status_line *sl = &c->status; - struct screen_write_ctx ctx; - struct session *s = c->session; - struct winlink *wl; - struct screen old_screen, window_list; - struct grid_cell stdgc, lgc, rgc, gc; - struct options *oo; - char *left, *right; - const char *sep; - u_int offset, needed, lines; - u_int wlstart, wlwidth, wlavailable, wloffset, wlsize; - size_t llen, rlen, seplen; - int larrow, rarrow; + struct status_line *sl = &c->status; + struct status_line_entry *sle; + struct session *s = c->session; + struct screen_write_ctx ctx; + struct grid_cell gc; + u_int lines, i, width = c->tty.sx; + int flags, force = 0, changed = 0; + struct options_entry *o; + struct format_tree *ft; + const char *fmt; + char *expanded; + + log_debug("%s enter", __func__); /* Shouldn't get here if not the active screen. */ if (sl->active != &sl->screen) @@ -374,257 +337,71 @@ status_redraw(struct client *c) lines = status_line_size(c); if (c->tty.sy == 0 || lines == 0) return (1); - left = right = NULL; - larrow = rarrow = 0; /* Set up default colour. */ - style_apply(&stdgc, s->options, "status-style"); - - /* Create the target screen. */ - memcpy(&old_screen, sl->active, sizeof old_screen); - screen_init(sl->active, c->tty.sx, lines, 0); - screen_write_start(&ctx, NULL, sl->active); - for (offset = 0; offset < lines * c->tty.sx; offset++) - screen_write_putc(&ctx, &stdgc, ' '); - screen_write_stop(&ctx); - - /* If the height is too small, blank status line. */ - if (c->tty.sy < lines) - goto out; - - /* Work out left and right strings. */ - memcpy(&lgc, &stdgc, sizeof lgc); - left = status_redraw_get_left(c, &lgc, &llen); - memcpy(&rgc, &stdgc, sizeof rgc); - right = status_redraw_get_right(c, &rgc, &rlen); - - /* - * Figure out how much space we have for the window list. If there - * isn't enough space, just show a blank status line. - */ - needed = 0; - if (llen != 0) - needed += llen; - if (rlen != 0) - needed += rlen; - if (c->tty.sx == 0 || c->tty.sx <= needed) - goto out; - wlavailable = c->tty.sx - needed; - - /* Calculate the total size needed for the window list. */ - wlstart = wloffset = wlwidth = 0; - RB_FOREACH(wl, winlinks, &s->windows) { - free(wl->status_text); - memcpy(&wl->status_cell, &stdgc, sizeof wl->status_cell); - wl->status_text = status_print(c, wl, &wl->status_cell); - wl->status_width = screen_write_cstrlen("%s", wl->status_text); - - if (wl == s->curw) - wloffset = wlwidth; - - oo = wl->window->options; - sep = options_get_string(oo, "window-status-separator"); - seplen = screen_write_cstrlen("%s", sep); - wlwidth += wl->status_width + seplen; + style_apply(&gc, s->options, "status-style"); + if (!grid_cells_equal(&gc, &sl->style)) { + force = 1; + memcpy(&sl->style, &gc, sizeof sl->style); } - /* Create a new screen for the window list. */ - screen_init(&window_list, wlwidth, 1, 0); - - /* And draw the window list into it. */ - screen_write_start(&ctx, NULL, &window_list); - RB_FOREACH(wl, winlinks, &s->windows) { - screen_write_cnputs(&ctx, -1, &wl->status_cell, "%s", - wl->status_text); - - oo = wl->window->options; - sep = options_get_string(oo, "window-status-separator"); - screen_write_cnputs(&ctx, -1, &stdgc, "%s", sep); + /* Resize the target screen. */ + if (screen_size_x(&sl->screen) != width || + screen_size_y(&sl->screen) != lines) { + if (screen_size_x(&sl->screen) != width) + force = 1; + screen_resize(&sl->screen, width, lines, 0); + changed = 1; } - screen_write_stop(&ctx); + screen_write_start(&ctx, NULL, &sl->screen); - /* If there is enough space for the total width, skip to draw now. */ - if (wlwidth <= wlavailable) - goto draw; - - /* Find size of current window text. */ - wlsize = s->curw->status_width; - - /* - * If the current window is already on screen, good to draw from the - * start and just leave off the end. - */ - if (wloffset + wlsize < wlavailable) { - if (wlavailable > 0) { - rarrow = 1; - wlavailable--; - } - wlwidth = wlavailable; - } else { - /* - * Work out how many characters we need to omit from the - * start. There are wlavailable characters to fill, and - * wloffset + wlsize must be the last. So, the start character - * is wloffset + wlsize - wlavailable. - */ - if (wlavailable > 0) { - larrow = 1; - wlavailable--; - } - - wlstart = wloffset + wlsize - wlavailable; - if (wlavailable > 0 && wlwidth > wlstart + wlavailable + 1) { - rarrow = 1; - wlstart++; - wlavailable--; - } - wlwidth = wlavailable; - } - - /* Bail if anything is now too small too. */ - if (wlwidth == 0 || wlavailable == 0) { - screen_free(&window_list); - goto out; - } - - /* - * Now the start position is known, work out the state of the left and - * right arrows. - */ - offset = 0; - RB_FOREACH(wl, winlinks, &s->windows) { - if (wl->flags & WINLINK_ALERTFLAGS && - larrow == 1 && offset < wlstart) - larrow = -1; - - offset += wl->status_width; - - if (wl->flags & WINLINK_ALERTFLAGS && - rarrow == 1 && offset > wlstart + wlwidth) - rarrow = -1; - } - -draw: - /* Begin drawing. */ - screen_write_start(&ctx, NULL, sl->active); - - /* Draw the left string and arrow. */ - screen_write_cursormove(&ctx, 0, 0, 0); - if (llen != 0) - screen_write_cnputs(&ctx, llen, &lgc, "%s", left); - if (larrow != 0) { - memcpy(&gc, &stdgc, sizeof gc); - if (larrow == -1) - gc.attr ^= GRID_ATTR_REVERSE; - screen_write_putc(&ctx, &gc, '<'); - } - - /* Draw the right string and arrow. */ - if (rarrow != 0) { - screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, 0, 0); - memcpy(&gc, &stdgc, sizeof gc); - if (rarrow == -1) - gc.attr ^= GRID_ATTR_REVERSE; - screen_write_putc(&ctx, &gc, '>'); - } else - screen_write_cursormove(&ctx, c->tty.sx - rlen, 0, 0); - if (rlen != 0) - screen_write_cnputs(&ctx, rlen, &rgc, "%s", right); - - /* Figure out the offset for the window list. */ - if (llen != 0) - wloffset = llen; - else - wloffset = 0; - if (wlwidth < wlavailable) { - switch (options_get_number(s->options, "status-justify")) { - case 1: /* centred */ - wloffset += (wlavailable - wlwidth) / 2; - break; - case 2: /* right */ - wloffset += (wlavailable - wlwidth); - break; - } - } - if (larrow != 0) - wloffset++; - - /* Copy the window list. */ - sl->window_list_offset = -wloffset + wlstart; - screen_write_cursormove(&ctx, wloffset, 0, 0); - screen_write_fast_copy(&ctx, &window_list, wlstart, 0, wlwidth, 1); - screen_free(&window_list); - - /* Save left and right size. */ - sl->left_size = llen; - sl->right_size = rlen; - - screen_write_stop(&ctx); - -out: - free(left); - free(right); - - if (grid_compare(sl->active->grid, old_screen.grid) == 0) { - screen_free(&old_screen); - return (0); - } - screen_free(&old_screen); - return (1); -} - -/* Replace special sequences in fmt. */ -static char * -status_replace(struct client *c, struct winlink *wl, const char *fmt) -{ - struct format_tree *ft; - char *expanded; - u_int tag; - - if (fmt == NULL) - return (xstrdup("")); - - if (wl != NULL) - tag = FORMAT_WINDOW|wl->window->id; - else - tag = FORMAT_NONE; + /* Create format tree. */ + flags = FORMAT_STATUS; if (c->flags & CLIENT_STATUSFORCE) - ft = format_create(c, NULL, tag, FORMAT_STATUS|FORMAT_FORCE); - else - ft = format_create(c, NULL, tag, FORMAT_STATUS); - format_defaults(ft, c, NULL, wl, NULL); + flags |= FORMAT_FORCE; + ft = format_create(c, NULL, FORMAT_NONE, flags); + format_defaults(ft, c, NULL, NULL, NULL); - expanded = format_expand_time(ft, fmt); + /* Write the status lines. */ + o = options_get(s->options, "status-format"); + if (o == NULL) + screen_write_clearscreen(&ctx, gc.bg); + else { + for (i = 0; i < lines; i++) { + screen_write_cursormove(&ctx, 0, i, 0); - format_free(ft); - return (expanded); -} + fmt = options_array_get(o, i); + if (fmt == NULL) { + screen_write_clearline(&ctx, gc.bg); + continue; + } + sle = &sl->entries[i]; -/* Return winlink status line entry and adjust gc as necessary. */ -static char * -status_print(struct client *c, struct winlink *wl, struct grid_cell *gc) -{ - struct options *oo = wl->window->options; - struct session *s = c->session; - const char *fmt; - char *text; + expanded = format_expand_time(ft, fmt); + if (!force && + sle->expanded != NULL && + strcmp(expanded, sle->expanded) == 0) { + free(expanded); + continue; + } + changed = 1; - style_apply_update(gc, oo, "window-status-style"); - fmt = options_get_string(oo, "window-status-format"); - if (wl == s->curw) { - style_apply_update(gc, oo, "window-status-current-style"); - fmt = options_get_string(oo, "window-status-current-format"); + screen_write_clearline(&ctx, gc.bg); + status_free_ranges(&sle->ranges); + format_draw(&ctx, &gc, width, expanded, &sle->ranges); + + free(sle->expanded); + sle->expanded = expanded; + } } - if (wl == TAILQ_FIRST(&s->lastw)) - style_apply_update(gc, oo, "window-status-last-style"); + screen_write_stop(&ctx); - if (wl->flags & WINLINK_BELL) - style_apply_update(gc, oo, "window-status-bell-style"); - else if (wl->flags & (WINLINK_ACTIVITY|WINLINK_SILENCE)) - style_apply_update(gc, oo, "window-status-activity-style"); + /* Free the format tree. */ + format_free(ft); - text = status_replace(c, wl, fmt); - return (text); + /* Return if the status line has changed. */ + log_debug("%s exit: force=%d, changed=%d", __func__, force, changed); + return (force || changed); } /* Set a status line message. */ @@ -713,8 +490,9 @@ status_message_redraw(struct client *c) style_apply(&gc, s->options, "message-style"); screen_write_start(&ctx, NULL, sl->active); - screen_write_cursormove(&ctx, 0, 0, 0); - for (offset = 0; offset < lines * c->tty.sx; offset++) + screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1); + screen_write_cursormove(&ctx, 0, lines - 1, 0); + for (offset = 0; offset < c->tty.sx; offset++) screen_write_putc(&ctx, &gc, ' '); screen_write_cursormove(&ctx, 0, lines - 1, 0); screen_write_nputs(&ctx, len, &gc, "%s", c->message_string); @@ -864,12 +642,13 @@ status_prompt_redraw(struct client *c) start = c->tty.sx; screen_write_start(&ctx, NULL, sl->active); - screen_write_cursormove(&ctx, 0, 0, 0); - for (offset = 0; offset < lines * c->tty.sx; offset++) + screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1); + screen_write_cursormove(&ctx, 0, lines - 1, 0); + for (offset = 0; offset < c->tty.sx; offset++) screen_write_putc(&ctx, &gc, ' '); - screen_write_cursormove(&ctx, 0, 0, 0); + screen_write_cursormove(&ctx, 0, lines - 1, 0); screen_write_nputs(&ctx, start, &gc, "%s", c->prompt_string); - screen_write_cursormove(&ctx, start, 0, 0); + screen_write_cursormove(&ctx, start, lines - 1, 0); left = c->tty.sx - start; if (left == 0) diff --git a/style.c b/style.c index 293c70f1..c3d2960d 100644 --- a/style.c +++ b/style.c @@ -19,6 +19,8 @@ #include +#include +#include #include #include "tmux.h" @@ -28,7 +30,12 @@ /* Default style. */ static struct style style_default = { - { 0, 0, 8, 8, { { ' ' }, 0, 1, 1 } } + { 0, 0, 8, 8, { { ' ' }, 0, 1, 1 } }, + + STYLE_ALIGN_DEFAULT, + STYLE_LIST_OFF, + + STYLE_RANGE_NONE, 0 }; /* @@ -40,8 +47,8 @@ int style_parse(struct style *sy, const struct grid_cell *base, const char *in) { struct style saved; - const char delimiters[] = " ,"; - char tmp[32]; + const char delimiters[] = " ,", *cp; + char tmp[256], *found; int value; size_t end; @@ -68,6 +75,60 @@ style_parse(struct style *sy, const struct grid_cell *base, const char *in) sy->gc.bg = base->bg; sy->gc.attr = base->attr; sy->gc.flags = base->flags; + } else if (strcasecmp(tmp, "nolist") == 0) + sy->list = STYLE_LIST_OFF; + else if (strncasecmp(tmp, "list=", 5) == 0) { + if (strcasecmp(tmp + 5, "on") == 0) + sy->list = STYLE_LIST_ON; + else if (strcasecmp(tmp + 5, "focus") == 0) + sy->list = STYLE_LIST_FOCUS; + else if (strcasecmp(tmp + 5, "left-marker") == 0) + sy->list = STYLE_LIST_LEFT_MARKER; + else if (strcasecmp(tmp + 5, "right-marker") == 0) + sy->list = STYLE_LIST_RIGHT_MARKER; + else + goto error; + } else if (strcasecmp(tmp, "norange") == 0) { + sy->range_type = style_default.range_type; + sy->range_argument = style_default.range_type; + } else if (end > 6 && strncasecmp(tmp, "range=", 6) == 0) { + found = strchr(tmp + 6, '|'); + if (found != NULL) { + *found++ = '\0'; + if (*found == '\0') + goto error; + for (cp = found; *cp != '\0'; cp++) { + if (!isdigit((u_char)*cp)) + goto error; + } + } + if (strcasecmp(tmp + 6, "left") == 0) { + if (found != NULL) + goto error; + sy->range_type = STYLE_RANGE_LEFT; + sy->range_argument = 0; + } else if (strcasecmp(tmp + 6, "right") == 0) { + if (found != NULL) + goto error; + sy->range_type = STYLE_RANGE_RIGHT; + sy->range_argument = 0; + } else if (strcasecmp(tmp + 6, "window") == 0) { + if (found == NULL) + goto error; + sy->range_type = STYLE_RANGE_WINDOW; + sy->range_argument = atoi(found); + } + } else if (strcasecmp(tmp, "noalign") == 0) + sy->align = style_default.align; + else if (end > 6 && strncasecmp(tmp, "align=", 6) == 0) { + if (strcasecmp(tmp + 6, "left") == 0) + sy->align = STYLE_ALIGN_LEFT; + else if (strcasecmp(tmp + 6, "centre") == 0) + sy->align = STYLE_ALIGN_CENTRE; + else if (strcasecmp(tmp + 6, "right") == 0) + sy->align = STYLE_ALIGN_RIGHT; + else + goto error; } else if (end > 3 && strncasecmp(tmp + 1, "g=", 2) == 0) { if ((value = colour_fromstring(tmp + 3)) == -1) goto error; @@ -111,11 +172,49 @@ style_tostring(struct style *sy) { struct grid_cell *gc = &sy->gc; int off = 0; - const char *comma = ""; + const char *comma = "", *tmp; static char s[256]; + char b[16]; *s = '\0'; + if (sy->list != STYLE_LIST_OFF) { + if (sy->list == STYLE_LIST_ON) + tmp = "on"; + else if (sy->list == STYLE_LIST_FOCUS) + tmp = "focus"; + else if (sy->list == STYLE_LIST_LEFT_MARKER) + tmp = "left-marker"; + else if (sy->list == STYLE_LIST_RIGHT_MARKER) + tmp = "right-marker"; + off += xsnprintf(s + off, sizeof s - off, "%slist=%s", comma, + tmp); + comma = ","; + } + if (sy->range_type != STYLE_RANGE_NONE) { + if (sy->range_type == STYLE_RANGE_LEFT) + tmp = "left"; + else if (sy->range_type == STYLE_RANGE_RIGHT) + tmp = "right"; + else if (sy->range_type == STYLE_RANGE_WINDOW) { + snprintf(b, sizeof b, "window|%u", sy->range_argument); + tmp = b; + } + off += xsnprintf(s + off, sizeof s - off, "%srange=%s", comma, + tmp); + comma = ","; + } + if (sy->align != STYLE_ALIGN_DEFAULT) { + if (sy->align == STYLE_ALIGN_LEFT) + tmp = "left"; + else if (sy->align == STYLE_ALIGN_CENTRE) + tmp = "centre"; + else if (sy->align == STYLE_ALIGN_RIGHT) + tmp = "right"; + off += xsnprintf(s + off, sizeof s - off, "%salign=%s", comma, + tmp); + comma = ","; + } if (gc->fg != 8) { off += xsnprintf(s + off, sizeof s - off, "%sfg=%s", comma, colour_tostring(gc->fg)); @@ -180,7 +279,7 @@ style_copy(struct style *dst, struct style *src) memcpy(dst, src, sizeof *dst); } -/* Check if two styles are the same. */ +/* Check if two styles are (visibly) the same. */ int style_equal(struct style *sy1, struct style *sy2) { @@ -193,6 +292,8 @@ style_equal(struct style *sy1, struct style *sy2) return (0); if ((gc1->attr & STYLE_ATTR_MASK) != (gc2->attr & STYLE_ATTR_MASK)) return (0); + if (sy1->align != sy2->align) + return (0); return (1); } diff --git a/tmux.1 b/tmux.1 index c7b7d812..2399f289 100644 --- a/tmux.1 +++ b/tmux.1 @@ -251,6 +251,7 @@ client. .It ! Break the current pane out of the window. .It \&" +.\" " Split the current pane into two, top and bottom. .It # List all paste buffers. @@ -2838,9 +2839,22 @@ is on. The values are the same as those for .Ic activity-action . .It Xo Ic status -.Op Ic on | off +.Op Ic off | on | 2 | 3 | 4 | 5 .Xc -Show or hide the status line. +Show or hide the status line or specify its size. +Using +.Ic on +gives a status line one row in height; +.Ic 2 , +.Ic 3 , +.Ic 4 +or +.Ic 5 +more rows. +.It Ic status-format[] Ar format +Specify the format to be used for each line of the status line. +The default builds the top status line from the various individual status +options below. .It Ic status-interval Ar interval Update the status line every .Ar interval @@ -3669,6 +3683,7 @@ For example, to get a list of windows formatted like the status line: .Bd -literal -offset indent #{W:#{E:window-status-format} ,#{E:window-status-current-format} } .Ed +.Pp A prefix of the form .Ql s/foo/bar/: will substitute @@ -3872,8 +3887,9 @@ for the terminal default colour; or a hexadecimal RGB string such as Set the background colour. .It Ic none Set no attributes (turn off any active attributes). -.It Xo Ic bright (or -.Ic bold ) +.It Xo Ic bright +(or +.Ic bold ) , .Ic dim , .Ic underscore , .Ic blink , @@ -3890,6 +3906,54 @@ Set an attribute. Any of the attributes may be prefixed with .Ql no to unset. +.It Xo Ic align=left +(or +.Ic noalign ) , +.Ic align=centre , +.Ic align=right +.Xc +Align text to the left, centre or right of the available space if appropriate. +.It Xo Ic list=on , +.Ic list=focus , +.Ic list=left-marker , +.Ic list=right=marker , +.Ic nolist +.Xc +Mark the position of the various window list components in the +.Ic status-format +option: +.Ic list=on +marks the start of the list; +.Ic list=focus +is the part of the list that should be kept in focus if the entire list won't fit +in the available space (typically the current window); +.Ic list=left-marker +and +.Ic list=right-marker +mark the text to be used to mark that text has been trimmed from the left or +right of the list if there is not enough space. +.It Xo Ic range=left , +.Ic range=right , +.Ic range=window|X , +.Ic norange +.Xc +Mark a range in the +. Ic status-format +option. +.Ic range=left +and +.Ic range=right +are the text used for the +.Ql StatusLeft +and +.Ql StatusRight +mouse keys. +.Ic range=window|X +is the range for a window passed to the +.Ql Status +mouse key, where +.Ql X +is a window index. .El .Pp Examples are: diff --git a/tmux.h b/tmux.h index 9adef27f..e6ed430b 100644 --- a/tmux.h +++ b/tmux.h @@ -635,9 +635,50 @@ struct grid { struct grid_line *linedata; }; +/* Style alignment. */ +enum style_align { + STYLE_ALIGN_DEFAULT, + STYLE_ALIGN_LEFT, + STYLE_ALIGN_CENTRE, + STYLE_ALIGN_RIGHT +}; + +/* Style list. */ +enum style_list { + STYLE_LIST_OFF, + STYLE_LIST_ON, + STYLE_LIST_FOCUS, + STYLE_LIST_LEFT_MARKER, + STYLE_LIST_RIGHT_MARKER, +}; + +/* Style range. */ +enum style_range_type { + STYLE_RANGE_NONE, + STYLE_RANGE_LEFT, + STYLE_RANGE_RIGHT, + STYLE_RANGE_WINDOW +}; +struct style_range { + enum style_range_type type; + u_int argument; + + u_int start; + u_int end; /* not included */ + + TAILQ_ENTRY(style_range) entry; +}; +TAILQ_HEAD(style_ranges, style_range); + /* Style option. */ struct style { - struct grid_cell gc; + struct grid_cell gc; + + enum style_align align; + enum style_list list; + + enum style_range_type range_type; + u_int range_argument; }; /* Hook data structures. */ @@ -869,10 +910,6 @@ struct winlink { struct session *session; struct window *window; - size_t status_width; - struct grid_cell status_cell; - char *status_text; - int flags; #define WINLINK_BELL 0x1 #define WINLINK_ACTIVITY 0x2 @@ -954,6 +991,7 @@ struct session { struct winlinks windows; int statusat; + u_int statuslines; struct hooks *hooks; struct options *options; @@ -998,7 +1036,9 @@ struct mouse_event { int valid; key_code key; + int statusat; + u_int statuslines; u_int x; u_int y; @@ -1313,17 +1353,20 @@ struct cmd_entry { }; /* Status line. */ +#define STATUS_LINES_LIMIT 5 +struct status_line_entry { + char *expanded; + struct style_ranges ranges; +}; struct status_line { - struct event timer; + struct event timer; - struct screen screen; - struct screen *active; - int references; + struct screen screen; + struct screen *active; + int references; - int window_list_offset; - - u_int left_size; - u_int right_size; + struct grid_cell style; + struct status_line_entry entries[STATUS_LINES_LIMIT]; }; /* Client connection. */ @@ -1582,6 +1625,7 @@ char *paste_make_sample(struct paste_buffer *); #define FORMAT_PANE 0x80000000U #define FORMAT_WINDOW 0x40000000U struct format_tree; +const char *format_skip(const char *s, const char *end); int format_true(const char *); struct format_tree *format_create(struct client *, struct cmdq_item *, int, int); @@ -1604,6 +1648,14 @@ void format_defaults_paste_buffer(struct format_tree *, struct paste_buffer *); void format_lost_client(struct client *); +/* format-draw.c */ +void format_draw(struct screen_write_ctx *, + const struct grid_cell *, u_int, const char *, + struct style_ranges *); +u_int format_width(const char *); +char *format_trim_left(const char *, u_int); +char *format_trim_right(const char *, u_int); + /* hooks.c */ struct hook; struct hooks *hooks_get(struct session *); @@ -1979,7 +2031,7 @@ void status_timer_start_all(void); void status_update_cache(struct session *); int status_at_line(struct client *); u_int status_line_size(struct client *); -struct window *status_get_window_at(struct client *, u_int); +struct style_range *status_get_range(struct client *, u_int, u_int); void status_init(struct client *); void status_free(struct client *); int status_redraw(struct client *); @@ -2079,9 +2131,6 @@ void screen_write_start(struct screen_write_ctx *, struct window_pane *, struct screen *); void screen_write_stop(struct screen_write_ctx *); void screen_write_reset(struct screen_write_ctx *); -size_t printflike(1, 2) screen_write_cstrlen(const char *, ...); -void printflike(4, 5) screen_write_cnputs(struct screen_write_ctx *, - ssize_t, const struct grid_cell *, const char *, ...); size_t printflike(1, 2) screen_write_strlen(const char *, ...); void printflike(3, 4) screen_write_puts(struct screen_write_ctx *, const struct grid_cell *, const char *, ...); @@ -2418,8 +2467,6 @@ u_int utf8_strwidth(const struct utf8_data *, ssize_t); struct utf8_data *utf8_fromcstr(const char *); char *utf8_tocstr(struct utf8_data *); u_int utf8_cstrwidth(const char *); -char *utf8_rtrimcstr(const char *, u_int); -char *utf8_trimcstr(const char *, u_int); char *utf8_padcstr(const char *, u_int); /* procname.c */ diff --git a/utf8.c b/utf8.c index b25ac06b..08990634 100644 --- a/utf8.c +++ b/utf8.c @@ -383,66 +383,6 @@ utf8_cstrwidth(const char *s) return (width); } -/* Trim UTF-8 string to width. Caller frees. */ -char * -utf8_trimcstr(const char *s, u_int width) -{ - struct utf8_data *tmp, *next; - char *out; - u_int at; - - tmp = utf8_fromcstr(s); - - at = 0; - for (next = tmp; next->size != 0; next++) { - if (at + next->width > width) { - next->size = 0; - break; - } - at += next->width; - } - - out = utf8_tocstr(tmp); - free(tmp); - return (out); -} - -/* Trim UTF-8 string to width. Caller frees. */ -char * -utf8_rtrimcstr(const char *s, u_int width) -{ - struct utf8_data *tmp, *next, *end; - char *out; - u_int at; - - tmp = utf8_fromcstr(s); - - for (end = tmp; end->size != 0; end++) - /* nothing */; - if (end == tmp) { - free(tmp); - return (xstrdup("")); - } - next = end - 1; - - at = 0; - for (;;) { - if (at + next->width > width) { - next++; - break; - } - at += next->width; - - if (next == tmp) - break; - next--; - } - - out = utf8_tocstr(next); - free(tmp); - return (out); -} - /* Pad UTF-8 string to width. Caller frees. */ char * utf8_padcstr(const char *s, u_int width) diff --git a/window-client.c b/window-client.c index ec98984d..e0637a06 100644 --- a/window-client.c +++ b/window-client.c @@ -217,20 +217,36 @@ window_client_draw(__unused void *modedata, void *itemdata, { struct window_client_itemdata *item = itemdata; struct client *c = item->c; + struct screen *s = ctx->s; struct window_pane *wp; - u_int cx = ctx->s->cx, cy = ctx->s->cy; + u_int cx = s->cx, cy = s->cy, lines, at; if (c->session == NULL || (c->flags & (CLIENT_DEAD|CLIENT_DETACHING))) return; wp = c->session->curw->window->active; - screen_write_preview(ctx, &wp->base, sx, sy - 3); + lines = status_line_size(c); + if (lines >= sy) + lines = 0; + if (status_at_line(c) == 0) + at = lines; + else + at = 0; - screen_write_cursormove(ctx, cx, cy + sy - 2, 0); + screen_write_cursormove(ctx, cx, cy + at, 0); + screen_write_preview(ctx, &wp->base, sx, sy - 2 - lines); + + if (at != 0) + screen_write_cursormove(ctx, cx, cy + 2, 0); + else + screen_write_cursormove(ctx, cx, cy + sy - 1 - lines, 0); screen_write_hline(ctx, sx, 0, 0); - screen_write_cursormove(ctx, cx, cy + sy - 1, 0); - screen_write_fast_copy(ctx, &c->status.screen, 0, 0, sx, 1); + if (at != 0) + screen_write_cursormove(ctx, cx, cy, 0); + else + screen_write_cursormove(ctx, cx, cy + sy - lines, 0); + screen_write_fast_copy(ctx, &c->status.screen, 0, 0, sx, lines); } static struct screen * diff --git a/window.c b/window.c index a74d01d7..8539c9ca 100644 --- a/window.c +++ b/window.c @@ -217,7 +217,6 @@ winlink_remove(struct winlinks *wwl, struct winlink *wl) } RB_REMOVE(winlinks, wwl, wl); - free(wl->status_text); free(wl); }