From bd4ac0299484b0930c0644e48680f3a63ad6a0c7 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 22 Jul 2026 08:19:14 +0000 Subject: [PATCH] Add A modifier to cycle through a series of values, GitHub issue 5412 from Fernando Daciuk. --- format.c | 101 +++++++++++++++++++++++++++++++++++++++++++++--- server-client.c | 4 +- tmux.1 | 24 +++++++++++- tmux.h | 3 +- 4 files changed, 123 insertions(+), 9 deletions(-) diff --git a/format.c b/format.c index a3b1acfa7..f703022b5 100644 --- a/format.c +++ b/format.c @@ -1,4 +1,4 @@ -/* $OpenBSD: format.c,v 1.407 2026/07/20 07:42:13 nicm Exp $ */ +/* $OpenBSD: format.c,v 1.408 2026/07/22 08:19:14 nicm Exp $ */ /* * Copyright (c) 2011 Nicholas Marriott @@ -128,6 +128,7 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2) #define FORMAT_OPTIONS 0x40000000 #define FORMAT_ENVIRON 0x80000000ULL #define FORMAT_DIFFERENCE 0x100000000ULL +#define FORMAT_CYCLE 0x200000000ULL /* Limit on recursion. */ #define FORMAT_LOOP_LIMIT 100 @@ -138,9 +139,13 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2) /* How often to check the time in long loops. */ #define FORMAT_TIME_LOOP_CHECK 10000 +/* Fixed animation period (ms): redraw interval and shortest frame step. */ +#define FORMAT_CYCLE_PERIOD 100 + /* Format expand flags. */ #define FORMAT_EXPAND_TIME 0x1 #define FORMAT_EXPAND_NOJOBS 0x2 +#define FORMAT_EXPAND_NOCYCLE 0x4 /* Entry in format tree. */ struct format_entry { @@ -413,7 +418,8 @@ format_job_get(struct format_expand_state *es, const char *cmd) RB_INSERT(format_job_tree, jobs, fj); } - format_copy_state(&next, es, FORMAT_EXPAND_NOJOBS); + format_copy_state(&next, es, FORMAT_EXPAND_NOJOBS| + FORMAT_EXPAND_NOCYCLE); next.flags &= ~FORMAT_EXPAND_TIME; expanded = format_expand1(&next, cmd); @@ -4779,7 +4785,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s, /* * Modifiers are a ; separated list of the forms: - * l,m,C,a,b,c,d,I,n,t,w,q,E,T,S,W,P,O,V,R,<,> + * l,m,C,a,b,c,d,I,n,t,w,q,E,T,S,W,P,O,V,R,A,<,> * =a * =/a * =/a/ @@ -4798,7 +4804,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s, break; /* Check single character modifiers with no arguments. */ - if (strchr("labdnwETSWPOVL!<>", cp[0]) != NULL && + if (strchr("labdnwETSWPOVL!<>A", cp[0]) != NULL && format_is_end(cp[1])) { format_add_modifier(&list, count, cp, 1, NULL, 0); cp++; @@ -4820,7 +4826,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s, } /* Now try single character with arguments. */ - if (strchr("ImCLNPSOVst=pReqWc", cp[0]) == NULL) + if (strchr("ImCLNPSOVst=pReqWcA", cp[0]) == NULL) break; c = cp[0]; @@ -5779,6 +5785,74 @@ fail: return (NULL); } +/* Callback for the cycle timer; redraw the status line. */ +static void +format_cycle_callback(__unused int fd, __unused short events, void *arg) +{ + struct client *c = arg; + + if (c->message_string == NULL && c->prompt == NULL) + c->flags |= CLIENT_REDRAWSTATUS; +} + +/* Arm the cycle timer to redraw the status line if it is not already. */ +static void +format_cycle_start_timer(struct client *c) +{ + struct timeval tv; + + tv.tv_sec = FORMAT_CYCLE_PERIOD / 1000; + tv.tv_usec = (FORMAT_CYCLE_PERIOD % 1000) * 1000L; + + if (!event_initialized(&c->cycle_timer)) + evtimer_set(&c->cycle_timer, format_cycle_callback, c); + if (!evtimer_pending(&c->cycle_timer, NULL)) + evtimer_add(&c->cycle_timer, &tv); +} + +/* Expand the "A" animation modifier; see the manual for the syntax. */ +static char * +format_cycle(struct format_expand_state *es, const char *frames, u_int count) +{ + struct format_tree *ft = es->ft; + const char *start, *end, *cp; + u_int n, index, i; + + /* + * A cycle is only expanded in a status format, and never in the + * command or output of #() where it would change on every frame and + * make the job run again. + */ + if (!(ft->flags & FORMAT_STATUS) || (es->flags & FORMAT_EXPAND_NOCYCLE)) + return (xstrdup("")); + if (*frames == '\0') + return (xstrdup("")); + + /* Count the comma-separated frames (there is at least one). */ + n = 1; + for (cp = frames; *cp != '\0'; cp++) { + if (*cp == ',') + n++; + } + index = (es->start_time / (count * FORMAT_CYCLE_PERIOD)) % n; + + /* + * Redraw the status line so the frames advance on their own; a + * single frame never changes so there is nothing to redraw for. + */ + if (n > 1 && ft->client != NULL) + format_cycle_start_timer(ft->client); + + /* Walk to the chosen frame and return a copy of it. */ + start = frames; + for (i = 0; i < index; i++) + start = strchr(start, ',') + 1; + end = strchr(start, ','); + if (end == NULL) + end = start + strlen(start); + return (xstrndup(start, end - start)); +} + /* Replace a key. */ static int format_replace(struct format_expand_state *es, const char *key, size_t keylen, @@ -5799,6 +5873,7 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, struct format_modifier *list, *cmp = NULL, *search = NULL; struct format_modifier **sub = NULL, *mexp = NULL, *fm; struct format_modifier *bool_op_n = NULL; + u_int cycle_count = 1; u_int i, count, nsub = 0, nrep, check = 0; const char *loop_flags = ""; struct format_expand_state next; @@ -5859,6 +5934,15 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, if (errstr != NULL) width = 0; break; + case 'A': + modifiers |= FORMAT_CYCLE; + if (fm->argc < 1) + break; + cycle_count = strtonum(fm->argv[0], 1, 100, + &errstr); + if (errstr != NULL) + cycle_count = 1; + break; case 'w': modifiers |= FORMAT_WIDTH; break; @@ -6080,6 +6164,13 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, goto done; } + /* Is this an animation cycle? */ + if (modifiers & FORMAT_CYCLE) { + value = format_cycle(es, copy, cycle_count); + format_log(es, "cycle '%s' is: %s", copy, value); + goto done; + } + /* Is this a literal string? */ if (modifiers & FORMAT_LITERAL) { format_log(es, "literal string is '%s'", copy); diff --git a/server-client.c b/server-client.c index a010c9f16..081862e43 100644 --- a/server-client.c +++ b/server-client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server-client.c,v 1.498 2026/07/21 12:28:43 nicm Exp $ */ +/* $OpenBSD: server-client.c,v 1.499 2026/07/22 08:19:14 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -530,6 +530,8 @@ server_client_lost(struct client *c) evtimer_del(&c->repeat_timer); evtimer_del(&c->click_timer); + if (event_initialized(&c->cycle_timer)) + evtimer_del(&c->cycle_timer); key_bindings_unref_table(c->keytable); diff --git a/tmux.1 b/tmux.1 index 5fb22d413..c3d66e73f 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: tmux.1,v 1.1146 2026/07/21 12:28:43 nicm Exp $ +.\" $OpenBSD: tmux.1,v 1.1147 2026/07/22 08:19:14 nicm Exp $ .\" .\" Copyright (c) 2007 Nicholas Marriott .\" @@ -14,7 +14,7 @@ .\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING .\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: July 21 2026 $ +.Dd $Mdocdate: July 22 2026 $ .Dt TMUX 1 .Os .Sh NAME @@ -7183,6 +7183,26 @@ For example will be replaced by .Ql #{?pane_in_mode,yes,no} . .Pp +The +.Ql A +modifier shows one of a list of frames in turn: +.Ql #{A/count:frame,frame,...} +or +.Ql #{A:frame,frame,...} . +Each frame is shown for +.Ar count +periods of 100 milliseconds before the next, wrapping back to the first after +the last. +If no count is given, one is used. +The +.Ql A +modifier is expanded only in the status line and +.Ic pane\-border\-format ; +if present, the status line or pane borders are redrawn every 100 milliseconds. +For example, +.Ql #{A/2:|,/,\-,\e} +shows the four frames in turn, changing every 200 milliseconds. +.Pp The following variables are available, where appropriate: .Bl -column "XXXXXXXXXXXXXXXXXXX" "XXXXX" .It Sy "Variable name" Ta Sy "Alias" Ta Sy "Replaced with" diff --git a/tmux.h b/tmux.h index 217e5e55e..04ad25b82 100644 --- a/tmux.h +++ b/tmux.h @@ -1,4 +1,4 @@ -/* $OpenBSD: tmux.h,v 1.1412 2026/07/21 12:28:43 nicm Exp $ */ +/* $OpenBSD: tmux.h,v 1.1413 2026/07/22 08:19:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -2203,6 +2203,7 @@ struct client { struct mouse_event click_event; struct status_line status; + struct event cycle_timer; enum client_theme theme; struct input_requests input_requests;