summaryrefslogtreecommitdiffstats
path: root/format-draw.c
diff options
context:
space:
mode:
authornicm <nicm>2019-03-18 20:53:33 +0000
committernicm <nicm>2019-03-18 20:53:33 +0000
commit979313832ce1d5f6cdc2c512e8524d6c517422e0 (patch)
tree13768e951adacd7c96b493a84fb5e65838ef94ed /format-draw.c
parentd738d5168804405240a3f36b6f410baee39cd9e0 (diff)
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.
Diffstat (limited to 'format-draw.c')
-rw-r--r--format-draw.c871
1 files changed, 871 insertions, 0 deletions
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);
+}