diff options
-rw-r--r-- | Makefile.am | 1 | ||||
-rw-r--r-- | format-draw.c | 871 | ||||
-rw-r--r-- | format.c | 6 | ||||
-rw-r--r-- | mode-tree.c | 13 | ||||
-rw-r--r-- | options-table.c | 52 | ||||
-rw-r--r-- | screen-redraw.c | 28 | ||||
-rw-r--r-- | screen-write.c | 107 | ||||
-rw-r--r-- | server-client.c | 33 | ||||
-rw-r--r-- | status.c | 425 | ||||
-rw-r--r-- | style.c | 111 | ||||
-rw-r--r-- | tmux.1 | 72 | ||||
-rw-r--r-- | tmux.h | 85 | ||||
-rw-r--r-- | utf8.c | 60 | ||||
-rw-r--r-- | window-client.c | 26 | ||||
-rw-r--r-- | window.c | 1 |
15 files changed, 1334 insertions, 557 deletions
diff --git a/Makefile.am b/Makefile.am index 8519c47c..16c842c8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -132,6 +132,7 @@ dist_tmux_SOURCES = \ 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 <nicholas.marriott@gmail.com> + * + * 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 <sys/types.h> + +#include <stdlib.h> +#include <string.h> + +#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); +} @@ -988,7 +988,7 @@ found: } /* Skip until end. */ -static const char * +const char * format_skip(const char *s, const char *end) { int brackets = 0; @@ -1596,12 +1596,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 2f760b74..76e75722 100644 --- a/options-table.c +++ b/options-table.c @@ -38,6 +38,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 }; @@ -63,6 +66,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", @@ -377,8 +420,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 }, @@ -403,6 +447,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, NU |