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 + * + * 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 0d1351ad..ccf3fe38 100644 --- a/format.c +++ b/format.c @@ -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, 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 7b7720e4..a233a91d 100644 --- a/server-client.c +++ b/server-client.c @@ -409,12 +409,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; @@ -501,17 +502,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 de25d227..4df558eb 100644 --- a/tmux.1 +++ b/tmux.1 @@ -255,6 +255,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. @@ -2925,9 +2926,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 @@ -3771,6 +3785,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 @@ -3977,8 +3992,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 , @@ -3995,6 +4011,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 16727536..37befeb8 100644 --- a/tmux.h +++ b/tmux.h @@ -637,9 +637,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. */ @@ -871,10 +912,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 @@ -956,6 +993,7 @@ struct session { struct winlinks windows; int statusat; + u_int statuslines; struct hooks *hooks; struct options *options; @@ -1000,7 +1038,9 @@ struct mouse_event { int valid; key_code key; + int statusat; + u_int statuslines; u_int x; u_int y; @@ -1315,17 +1355,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. */ @@ -1584,6 +1627,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); @@ -1606,6 +1650,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 *); @@ -1981,7 +2033,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 *); @@ -2081,9 +2133,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 *, ...); @@ -2420,8 +2469,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); /* osdep-*.c */ diff --git a/utf8.c b/utf8.c index a91da360..94879665 100644 --- a/utf8.c +++ b/utf8.c @@ -408,66 +408,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 abe0b333..da5b1131 100644 --- a/window.c +++ b/window.c @@ -215,7 +215,6 @@ winlink_remove(struct winlinks *wwl, struct winlink *wl) } RB_REMOVE(winlinks, wwl, wl); - free(wl->status_text); free(wl); }