From 61e55fa50dc7119a8c88bb385e615a8e6c8d6a85 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 9 Nov 2020 09:10:10 +0000 Subject: Change how escaping is processed for formats so that ## and # can be used in styles. Also add a 'w' format modifier for the width. From Chas J Owens IV in GitHub issue 2389. --- format-draw.c | 174 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---- format.c | 40 ++++++++++++-- tmux.1 | 4 +- 3 files changed, 201 insertions(+), 17 deletions(-) diff --git a/format-draw.c b/format-draw.c index ec98ba95..e73c5df4 100644 --- a/format-draw.c +++ b/format-draw.c @@ -486,6 +486,18 @@ format_draw_right(struct screen_write_ctx *octx, u_int available, u_int ocx, focus_end, frs); } +/* Draw multiple characters. */ +static void +format_draw_many(struct screen_write_ctx *ctx, struct style *sy, char ch, + u_int n) +{ + u_int i; + + utf8_set(&sy->gc.data, ch); + for (i = 0; i < n; i++) + screen_write_cell(ctx, &sy->gc); +} + /* Draw a format to a screen. */ void format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, @@ -509,10 +521,10 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, 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 ocx = os->cx, ocy = os->cy, n, i, width[TOTAL]; u_int map[] = { LEFT, LEFT, CENTRE, RIGHT }; int focus_start = -1, focus_end = -1; - int list_state = -1, fill = -1; + int list_state = -1, fill = -1, even; enum style_align list_align = STYLE_ALIGN_DEFAULT; struct grid_cell gc, current_default; struct style sy, saved_sy; @@ -547,6 +559,34 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, */ cp = expanded; while (*cp != '\0') { + /* Handle sequences of #. */ + if (cp[0] == '#' && cp[1] != '[' && cp[1] != '\0') { + for (n = 1; cp[n] == '#'; n++) + /* nothing */; + if (cp[n] != '[') { + width[current] += n; + cp += n; + format_draw_many(&ctx[current], &sy, '#', n); + continue; + } + even = ((n % 2) == 0); + if (even) + cp += (n + 1); + else + cp += (n - 1); + if (sy.ignore) + continue; + format_draw_many(&ctx[current], &sy, '#', n / 2); + width[current] += (n / 2); + if (even) { + utf8_set(ud, '['); + screen_write_cell(&ctx[current], &sy.gc); + width[current]++; + } + continue; + } + + /* Is this not a style? */ if (cp[0] != '#' || cp[1] != '[' || sy.ignore) { /* See if this is a UTF-8 character. */ if ((more = utf8_open(ud, *cp)) == UTF8_MORE) { @@ -796,13 +836,33 @@ u_int format_width(const char *expanded) { const char *cp, *end; - u_int width = 0; + u_int n, width = 0; struct utf8_data ud; enum utf8_state more; cp = expanded; while (*cp != '\0') { - if (cp[0] == '#' && cp[1] == '[') { + if (*cp == '#') { + for (n = 1; cp[n] == '#'; n++) + /* nothing */; + if (cp[n] != '[') { + width += n; + cp += n; + continue; + } + width += (n / 2); /* one for each ## */ + + if ((n % 2) == 0) { + /* + * An even number of #s means that all #s are + * escaped, so not a style. + */ + width++; /* one for the [ */ + cp += (n + 1); + continue; + } + cp += (n - 1); /* point to the [ */ + end = format_skip(cp + 2, "]"); if (end == NULL) return (0); @@ -823,19 +883,57 @@ format_width(const char *expanded) return (width); } -/* Trim on the left, taking #[] into account. */ +/* + * Trim on the left, taking #[] into account. Note, we copy the whole set of + * unescaped #s, but only add their escaped size to width. This is because the + * format_draw function will actually do the escaping when it runs + */ char * format_trim_left(const char *expanded, u_int limit) { char *copy, *out; const char *cp = expanded, *end; - u_int width = 0; + u_int even, n, width = 0; struct utf8_data ud; enum utf8_state more; - out = copy = xmalloc(strlen(expanded) + 1); + out = copy = xcalloc(1, strlen(expanded) + 1); while (*cp != '\0') { - if (cp[0] == '#' && cp[1] == '[') { + if (width >= limit) + break; + if (*cp == '#') { + for (end = cp + 1; *end == '#'; end++) + /* nothing */; + n = end - cp; + if (*end != '[') { + if (n > limit - width) + n = limit - width; + memcpy(out, cp, n); + out += n; + width += n; + cp = end; + continue; + } + even = ((n % 2) == 0); + + n /= 2; + if (n > limit - width) + n = limit - width; + width += n; + n *= 2; + memcpy(out, cp, n); + out += n; + + if (even) { + if (width + 1 <= limit) { + *out++ = '['; + width++; + } + cp = end + 1; + continue; + } + cp = end - 1; + end = format_skip(cp + 2, "]"); if (end == NULL) break; @@ -873,7 +971,7 @@ format_trim_right(const char *expanded, u_int limit) { char *copy, *out; const char *cp = expanded, *end; - u_int width = 0, total_width, skip; + u_int width = 0, total_width, skip, old_n, even, n; struct utf8_data ud; enum utf8_state more; @@ -882,12 +980,64 @@ format_trim_right(const char *expanded, u_int limit) return (xstrdup(expanded)); skip = total_width - limit; - out = copy = xmalloc(strlen(expanded) + 1); + out = copy = xcalloc(1, strlen(expanded) + 1); while (*cp != '\0') { - if (cp[0] == '#' && cp[1] == '[') { + if (*cp == '#') { + for (end = cp + 1; *end == '#'; end++) + /* nothing */; + old_n = n = end - cp; + if (*end != '[') { + if (width <= skip) { + if (skip - width >= n) + n = 0; + else + n -= (skip - width); + } + if (n != 0) { + memcpy(out, cp, n); + out += n; + } + + /* + * The width always increases by the full + * amount even if we can't copy anything yet. + */ + width += old_n; + cp = end; + continue; + } + even = ((n % 2) == 0); + + n /= 2; + if (width <= skip) { + if (skip - width >= n) + n = 0; + else + n -= (skip - width); + } + if (n != 0) { + /* + * Copy the full amount because it hasn't been + * escaped yet. + */ + memcpy(out, cp, old_n); + out += old_n; + } + cp += old_n; + width += (old_n / 2) - even; + + if (even) { + if (width > skip) + *out++ = '['; + width++; + continue; + } + cp = end - 1; + end = format_skip(cp + 2, "]"); - if (end == NULL) + if (end == NULL) { break; + } memcpy(out, cp, end + 1 - cp); out += (end + 1 - cp); cp = end + 1; diff --git a/format.c b/format.c index 99efbc1c..91955259 100644 --- a/format.c +++ b/format.c @@ -98,6 +98,7 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2) #define FORMAT_PANES 0x200 #define FORMAT_PRETTY 0x400 #define FORMAT_LENGTH 0x800 +#define FORMAT_WIDTH 0x1000 /* Limit on recursion. */ #define FORMAT_LOOP_LIMIT 10 @@ -1671,7 +1672,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s, /* * Modifiers are a ; separated list of the forms: - * l,m,C,b,d,n,t,q,E,T,S,W,P,<,> + * l,m,C,b,d,n,t,w,q,E,T,S,W,P,<,> * =a * =/a * =/a/ @@ -1688,7 +1689,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s, cp++; /* Check single character modifiers with no arguments. */ - if (strchr("lbdnqETSWP<>", cp[0]) != NULL && + if (strchr("lbdnqwETSWP<>", cp[0]) != NULL && format_is_end(cp[1])) { format_add_modifier(&list, count, cp, 1, NULL, 0); cp++; @@ -2184,6 +2185,9 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, if (errptr != NULL) width = 0; break; + case 'w': + modifiers |= FORMAT_WIDTH; + break; case 'e': if (fm->argc < 1 || fm->argc > 3) break; @@ -2456,13 +2460,19 @@ done: format_log(es, "applied padding width %d: %s", width, value); } - /* Replace with the length if needed. */ + /* Replace with the length or width if needed. */ if (modifiers & FORMAT_LENGTH) { xasprintf(&new, "%zu", strlen(value)); free(value); value = new; format_log(es, "replacing with length: %s", new); } + if (modifiers & FORMAT_WIDTH) { + xasprintf(&new, "%u", format_width(value)); + free(value); + value = new; + format_log(es, "replacing with width: %s", new); + } /* Expand the buffer and copy in the value. */ valuelen = strlen(value); @@ -2589,8 +2599,30 @@ format_expand1(struct format_expand_state *es, const char *fmt) break; fmt += n + 1; continue; - case '}': case '#': + /* + * If ##[ (with two or more #s), then it is a style and + * can be left for format_draw to handle. + */ + ptr = fmt; + n = 2; + while (*ptr == '#') { + ptr++; + n++; + } + if (*ptr == '[') { + format_log(es, "found #*%zu[", n); + while (len - off < n + 2) { + buf = xreallocarray(buf, 2, len); + len *= 2; + } + memcpy(buf + off, fmt - 2, n + 1); + off += n + 1; + fmt = ptr + 1; + continue; + } + /* FALLTHROUGH */ + case '}': case ',': format_log(es, "found #%c", ch); while (len - off < 2) { diff --git a/tmux.1 b/tmux.1 index 0a71cc37..37f6f165 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4591,7 +4591,9 @@ pads the string to a given width, for example will result in a width of at least 10 characters. A positive width pads on the left, a negative on the right. .Ql n -expands to the length of the variable, for example +expands to the length of the variable and +.Ql w +to its width when displayed, for example .Ql #{n:window_name} . .Pp Prefixing a time variable with -- cgit v1.2.3