Compare commits

..

3 Commits

Author SHA1 Message Date
Michael Grant
2f7429c556
Merge 76b8ee08ef244537c4050bb99346a550c7d5222d into 7e439539377e272f37d18bb10dbff374b87acee6 2025-07-02 22:29:03 +02:00
Thomas Adam
7e43953937 Merge branch 'obsd-master' 2025-07-02 12:01:08 +01:00
nicm
28481e984b Add sorting to W, P, L operators as well, and add some new session
format variables. From Michael Grant in GitHub issue 4516.
2025-07-02 08:13:09 +00:00
4 changed files with 363 additions and 31 deletions

371
format.c
View File

@ -140,8 +140,8 @@ enum format_loop_sort_type {
};
static struct format_loop_sort_criteria {
u_int field;
int reversed;
enum format_loop_sort_type field;
int reversed;
} format_loop_sort_criteria;
struct format_tree {
@ -554,6 +554,38 @@ format_cb_session_attached_list(struct format_tree *ft)
return (value);
}
/* Callback for session_alert. */
static void *
format_cb_session_alert(struct format_tree *ft)
{
struct session *s = ft->s;
struct winlink *wl;
char alerts[1024];
int alerted = 0;
if (s == NULL)
return (NULL);
*alerts = '\0';
RB_FOREACH(wl, winlinks, &s->windows) {
if ((wl->flags & WINLINK_ALERTFLAGS) == 0)
continue;
if (~alerted & wl->flags & WINLINK_ACTIVITY) {
strlcat(alerts, "#", sizeof alerts);
alerted |= WINLINK_ACTIVITY;
}
if (~alerted & wl->flags & WINLINK_BELL) {
strlcat(alerts, "!", sizeof alerts);
alerted |= WINLINK_BELL;
}
if (~alerted & wl->flags & WINLINK_SILENCE) {
strlcat(alerts, "~", sizeof alerts);
alerted |= WINLINK_SILENCE;
}
}
return (xstrdup(alerts));
}
/* Callback for session_alerts. */
static void *
format_cb_session_alerts(struct format_tree *ft)
@ -1743,6 +1775,15 @@ format_cb_keypad_flag(struct format_tree *ft)
return (NULL);
}
/* Callback for loop_last_flag. */
static void *
format_cb_loop_last_flag(struct format_tree *ft)
{
if (ft->flags & FORMAT_LAST)
return (xstrdup("1"));
return (xstrdup("0"));
}
/* Callback for mouse_all_flag. */
static void *
format_cb_mouse_all_flag(struct format_tree *ft)
@ -2264,6 +2305,66 @@ format_cb_server_sessions(__unused struct format_tree *ft)
return (format_printf("%u", n));
}
/* Callback for session_active. */
static void *
format_cb_session_active(struct format_tree *ft)
{
if (ft->s == NULL || ft->c == NULL)
return (NULL);
if (ft->c->session == ft->s)
return (xstrdup("1"));
return (xstrdup("0"));
}
/* Callback for session_activity_flag. */
static void *
format_cb_session_activity_flag(struct format_tree *ft)
{
struct winlink *wl;
if (ft->s != NULL) {
RB_FOREACH(wl, winlinks, &ft->s->windows) {
if (ft->wl->flags & WINLINK_ACTIVITY)
return (xstrdup("1"));
return (xstrdup("0"));
}
}
return (NULL);
}
/* Callback for session_bell_flag. */
static void *
format_cb_session_bell_flag(struct format_tree *ft)
{
struct winlink *wl;
if (ft->s != NULL) {
RB_FOREACH(wl, winlinks, &ft->s->windows) {
if (wl->flags & WINLINK_BELL)
return (xstrdup("1"));
return (xstrdup("0"));
}
}
return (NULL);
}
/* Callback for session_silence_flag. */
static void *
format_cb_session_silence_flag(struct format_tree *ft)
{
struct winlink *wl;
if (ft->s != NULL) {
RB_FOREACH(wl, winlinks, &ft->s->windows) {
if (ft->wl->flags & WINLINK_SILENCE)
return (xstrdup("1"));
return (xstrdup("0"));
}
}
return (NULL);
}
/* Callback for session_attached. */
static void *
format_cb_session_attached(struct format_tree *ft)
@ -3054,6 +3155,9 @@ static const struct format_table_entry format_table[] = {
{ "last_window_index", FORMAT_TABLE_STRING,
format_cb_last_window_index
},
{ "loop_last_flag", FORMAT_TABLE_STRING,
format_cb_loop_last_flag
},
{ "mouse_all_flag", FORMAT_TABLE_STRING,
format_cb_mouse_all_flag
},
@ -3234,9 +3338,18 @@ static const struct format_table_entry format_table[] = {
{ "server_sessions", FORMAT_TABLE_STRING,
format_cb_server_sessions
},
{ "session_active", FORMAT_TABLE_STRING,
format_cb_session_active
},
{ "session_activity", FORMAT_TABLE_TIME,
format_cb_session_activity
},
{ "session_activity_flag", FORMAT_TABLE_STRING,
format_cb_session_activity_flag
},
{ "session_alert", FORMAT_TABLE_STRING,
format_cb_session_alert
},
{ "session_alerts", FORMAT_TABLE_STRING,
format_cb_session_alerts
},
@ -3246,6 +3359,9 @@ static const struct format_table_entry format_table[] = {
{ "session_attached_list", FORMAT_TABLE_STRING,
format_cb_session_attached_list
},
{ "session_bell_flag", FORMAT_TABLE_STRING,
format_cb_session_bell_flag
},
{ "session_created", FORMAT_TABLE_TIME,
format_cb_session_created
},
@ -3291,6 +3407,9 @@ static const struct format_table_entry format_table[] = {
{ "session_path", FORMAT_TABLE_STRING,
format_cb_session_path
},
{ "session_silence_flag", FORMAT_TABLE_STRING,
format_cb_session_silence_flag
},
{ "session_stack", FORMAT_TABLE_STRING,
format_cb_session_stack
},
@ -4044,7 +4163,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s,
}
/* Now try single character with arguments. */
if (strchr("mCNSst=pReq", cp[0]) == NULL)
if (strchr("mCLNPSst=pReqW", cp[0]) == NULL)
break;
c = cp[0];
@ -4243,13 +4362,14 @@ format_session_name(struct format_expand_state *es, const char *fmt)
static int
format_cmp_session(const void *a0, const void *b0)
{
const struct session *const *a = a0;
const struct session *const *b = b0;
const struct session *sa = *a;
const struct session *sb = *b;
int result = 0;
struct format_loop_sort_criteria *sc = &format_loop_sort_criteria;
const struct session *const *a = a0;
const struct session *const *b = b0;
const struct session *sa = *a;
const struct session *sb = *b;
int result = 0;
switch (format_loop_sort_criteria.field) {
switch (sc->field) {
case FORMAT_LOOP_BY_INDEX:
result = sa->id - sb->id;
break;
@ -4268,7 +4388,7 @@ format_cmp_session(const void *a0, const void *b0)
break;
}
if (format_loop_sort_criteria.reversed)
if (sc->reversed)
result = -result;
return (result);
}
@ -4285,7 +4405,7 @@ format_loop_sessions(struct format_expand_state *es, const char *fmt)
char *all, *active, *use, *expanded, *value;
size_t valuelen;
struct session *s;
int i, n;
int i, n, last = 0;
static struct session **l = NULL;
static int lsz = 0;
@ -4315,7 +4435,9 @@ format_loop_sessions(struct format_expand_state *es, const char *fmt)
use = active;
else
use = all;
nft = format_create(c, item, FORMAT_NONE, ft->flags);
if (i == n - 1)
last = FORMAT_LAST;
nft = format_create(c, item, FORMAT_NONE, ft->flags|last);
format_defaults(nft, ft->c, s, NULL, NULL);
format_copy_state(&next, es, 0);
next.ft = nft;
@ -4356,6 +4478,40 @@ format_window_name(struct format_expand_state *es, const char *fmt)
return (xstrdup("0"));
}
static int
format_cmp_window(const void *a0, const void *b0)
{
struct format_loop_sort_criteria *sc = &format_loop_sort_criteria;
const struct winlink *const *a = a0;
const struct winlink *const *b = b0;
const struct window *wa = (*a)->window;
const struct window *wb = (*b)->window;
int result = 0;
switch (sc->field) {
case FORMAT_LOOP_BY_INDEX:
result = wa->id - wb->id;
break;
case FORMAT_LOOP_BY_TIME:
if (timercmp(&wa->activity_time, &wb->activity_time, >)) {
result = -1;
break;
}
if (timercmp(&wa->activity_time, &wb->activity_time, <)) {
result = 1;
break;
}
/* FALLTHROUGH */
case FORMAT_LOOP_BY_NAME:
result = strcmp(wa->name, wb->name);
break;
}
if (sc->reversed)
result = -result;
return (result);
}
/* Loop over windows. */
static char *
format_loop_windows(struct format_expand_state *es, const char *fmt)
@ -4369,6 +4525,9 @@ format_loop_windows(struct format_expand_state *es, const char *fmt)
size_t valuelen;
struct winlink *wl;
struct window *w;
int i, n, last = 0;
static struct winlink **l = NULL;
static int lsz = 0;
if (ft->s == NULL) {
format_log(es, "window loop but no session");
@ -4380,17 +4539,32 @@ format_loop_windows(struct format_expand_state *es, const char *fmt)
active = NULL;
}
n = 0;
RB_FOREACH(wl, winlinks, &ft->s->windows) {
if (lsz <= n) {
lsz += 100;
l = xreallocarray(l, lsz, sizeof *l);
}
l[n++] = wl;
}
qsort(l, n, sizeof *l, format_cmp_window);
value = xcalloc(1, 1);
valuelen = 1;
RB_FOREACH(wl, winlinks, &ft->s->windows) {
for (i = 0; i < n; i++) {
wl = l[i];
w = wl->window;
format_log(es, "window loop: %u @%u", wl->idx, w->id);
if (active != NULL && wl == ft->s->curw)
use = active;
else
use = all;
nft = format_create(c, item, FORMAT_WINDOW|w->id, ft->flags);
if (i == n - 1)
last = FORMAT_LAST;
nft = format_create(c, item, FORMAT_WINDOW|w->id,
ft->flags|last);
format_defaults(nft, ft->c, ft->s, wl, NULL);
format_copy_state(&next, es, 0);
next.ft = nft;
@ -4410,6 +4584,23 @@ format_loop_windows(struct format_expand_state *es, const char *fmt)
return (value);
}
static int
format_cmp_pane(const void *a0, const void *b0)
{
struct format_loop_sort_criteria *sc = &format_loop_sort_criteria;
const struct window_pane *const *a = a0;
const struct window_pane *const *b = b0;
const struct window_pane *wpa = *a;
const struct window_pane *wpb = *b;
int result = 0;
if (sc->reversed)
result = wpb->id - wpa->id;
else
result = wpa->id - wpb->id;
return (result);
}
/* Loop over panes. */
static char *
format_loop_panes(struct format_expand_state *es, const char *fmt)
@ -4422,6 +4613,9 @@ format_loop_panes(struct format_expand_state *es, const char *fmt)
char *all, *active, *use, *expanded, *value;
size_t valuelen;
struct window_pane *wp;
int i, n, last = 0;
static struct window_pane **l = NULL;
static int lsz = 0;
if (ft->w == NULL) {
format_log(es, "pane loop but no window");
@ -4433,16 +4627,31 @@ format_loop_panes(struct format_expand_state *es, const char *fmt)
active = NULL;
}
n = 0;
TAILQ_FOREACH(wp, &ft->w->panes, entry) {
if (lsz <= n) {
lsz += 100;
l = xreallocarray(l, lsz, sizeof *l);
}
l[n++] = wp;
}
qsort(l, n, sizeof *l, format_cmp_pane);
value = xcalloc(1, 1);
valuelen = 1;
TAILQ_FOREACH(wp, &ft->w->panes, entry) {
for (i = 0; i < n; i++) {
wp = l[i];
format_log(es, "pane loop: %%%u", wp->id);
if (active != NULL && wp == ft->w->active)
use = active;
else
use = all;
nft = format_create(c, item, FORMAT_PANE|wp->id, ft->flags);
if (i == n - 1)
last = FORMAT_LAST;
nft = format_create(c, item, FORMAT_PANE|wp->id,
ft->flags|last);
format_defaults(nft, ft->c, ft->s, ft->wl, wp);
format_copy_state(&next, es, 0);
next.ft = nft;
@ -4462,24 +4671,86 @@ format_loop_panes(struct format_expand_state *es, const char *fmt)
return (value);
}
static int
format_cmp_client(const void *a0, const void *b0)
{
struct format_loop_sort_criteria *sc = &format_loop_sort_criteria;
const struct client *const *a = a0;
const struct client *const *b = b0;
const struct client *ca = *a;
const struct client *cb = *b;
int result = 0;
switch (sc->field) {
case FORMAT_LOOP_BY_INDEX:
break;
case FORMAT_LOOP_BY_TIME:
if (timercmp(&ca->activity_time, &cb->activity_time, >)) {
result = -1;
break;
}
if (timercmp(&ca->activity_time, &cb->activity_time, <)) {
result = 1;
break;
}
/* FALLTHROUGH */
case FORMAT_LOOP_BY_NAME:
result = strcmp(ca->name, cb->name);
break;
}
if (sc->reversed)
result = -result;
return (result);
}
/* Loop over clients. */
static char *
format_loop_clients(struct format_expand_state *es, const char *fmt)
{
struct format_tree *ft = es->ft;
struct client *c;
struct cmdq_item *item = ft->item;
struct format_tree *nft;
struct format_expand_state next;
char *expanded, *value;
size_t valuelen;
struct format_loop_sort_criteria *sc = &format_loop_sort_criteria;
struct format_tree *ft = es->ft;
struct client *c;
struct cmdq_item *item = ft->item;
struct format_tree *nft;
struct format_expand_state next;
char *expanded, *value;
size_t valuelen;
int i, n, last = 0;
static struct client **l = NULL;
static int lsz = 0;
value = xcalloc(1, 1);
valuelen = 1;
n = 0;
TAILQ_FOREACH(c, &clients, entry) {
if (lsz <= n) {
lsz += 100;
l = xreallocarray(l, lsz, sizeof *l);
}
l[n++] = c;
}
if (sc->field != FORMAT_LOOP_BY_INDEX)
qsort(l, n, sizeof *l, format_cmp_client);
else {
/* Use order in the TAILQ as "index" order. */
if (sc->reversed) {
for (i = 0; i < n / 2; i++) {
c = l[i];
l[i] = l[n - 1 - i];
l[n - 1 - i] = c;
}
}
}
for (i = 0; i < n; i++) {
c = l[i];
format_log(es, "client loop: %s", c->name);
nft = format_create(c, item, 0, ft->flags);
if (i == n - 1)
last = FORMAT_LAST;
nft = format_create(c, item, 0, ft->flags|last);
format_defaults(nft, c, ft->s, ft->wl, ft->wp);
format_copy_state(&next, es, 0);
next.ft = nft;
@ -4643,6 +4914,7 @@ static int
format_replace(struct format_expand_state *es, const char *key, size_t keylen,
char **buf, size_t *len, size_t *off)
{
struct format_loop_sort_criteria *sc = &format_loop_sort_criteria;
struct format_tree *ft = es->ft;
struct window_pane *wp = ft->wp;
const char *errstr, *copy, *cp, *cp2;
@ -4658,7 +4930,6 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
struct format_modifier *bool_op_n = NULL;
u_int i, count, nsub = 0, nrep;
struct format_expand_state next;
struct format_loop_sort_criteria *sc = &format_loop_sort_criteria;
/* Make a copy of the key. */
copy = copy0 = xstrndup(key, keylen);
@ -4769,15 +5040,19 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
break;
case 'S':
modifiers |= FORMAT_SESSIONS;
if (fm->argc < 1)
if (fm->argc < 1) {
sc->field = FORMAT_LOOP_BY_INDEX;
sc->reversed = 0;
break;
}
if (strchr(fm->argv[0], 'i') != NULL)
sc->field = FORMAT_LOOP_BY_INDEX;
else if (strchr(fm->argv[0], 'n') != NULL)
sc->field = FORMAT_LOOP_BY_NAME;
else if (strchr(fm->argv[0], 't') != NULL)
sc->field = FORMAT_LOOP_BY_TIME;
else sc->field = FORMAT_LOOP_BY_INDEX;
else
sc->field = FORMAT_LOOP_BY_INDEX;
if (strchr(fm->argv[0], 'r') != NULL)
sc->reversed = 1;
else
@ -4785,12 +5060,54 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
break;
case 'W':
modifiers |= FORMAT_WINDOWS;
if (fm->argc < 1) {
sc->field = FORMAT_LOOP_BY_INDEX;
sc->reversed = 0;
break;
}
if (strchr(fm->argv[0], 'i') != NULL)
sc->field = FORMAT_LOOP_BY_INDEX;
else if (strchr(fm->argv[0], 'n') != NULL)
sc->field = FORMAT_LOOP_BY_NAME;
else if (strchr(fm->argv[0], 't') != NULL)
sc->field = FORMAT_LOOP_BY_TIME;
else
sc->field = FORMAT_LOOP_BY_INDEX;
if (strchr(fm->argv[0], 'r') != NULL)
sc->reversed = 1;
else
sc->reversed = 0;
break;
case 'P':
modifiers |= FORMAT_PANES;
if (fm->argc < 1) {
sc->reversed = 0;
break;
}
if (strchr(fm->argv[0], 'r') != NULL)
sc->reversed = 1;
else
sc->reversed = 0;
break;
case 'L':
modifiers |= FORMAT_CLIENTS;
if (fm->argc < 1) {
sc->field = FORMAT_LOOP_BY_INDEX;
sc->reversed = 0;
break;
}
if (strchr(fm->argv[0], 'i') != NULL)
sc->field = FORMAT_LOOP_BY_INDEX;
else if (strchr(fm->argv[0], 'n') != NULL)
sc->field = FORMAT_LOOP_BY_NAME;
else if (strchr(fm->argv[0], 't') != NULL)
sc->field = FORMAT_LOOP_BY_TIME;
else
sc->field = FORMAT_LOOP_BY_INDEX;
if (strchr(fm->argv[0], 'r') != NULL)
sc->reversed = 1;
else
sc->reversed = 0;
break;
case 'R':
modifiers |= FORMAT_REPEAT;

View File

@ -138,7 +138,7 @@ static const char *options_table_allow_passthrough_list[] = {
"#{T:window-status-format}" \
"#[pop-default]" \
"#[norange default]" \
"#{?window_end_flag,,#{window-status-separator}}" \
"#{?loop_last_flag,,#{window-status-separator}}" \
"," \
"#[range=window|#{window_index} list=focus " \
"#{?#{!=:#{E:window-status-current-style},default}," \
@ -165,7 +165,7 @@ static const char *options_table_allow_passthrough_list[] = {
"#{T:window-status-current-format}" \
"#[pop-default]" \
"#[norange list=on default]" \
"#{?window_end_flag,,#{window-status-separator}}" \
"#{?loop_last_flag,,#{window-status-separator}}" \
"}" \
"#[nolist align=right range=right #{E:status-right-style}]" \
"#[push-default]" \

18
tmux.1
View File

@ -5911,14 +5911,21 @@ or
.Ql L:\&
will loop over each session, window, pane or client and insert the format once
for each.
.Ql S:\& ,
.Ql L:\& ,
.Ql S:\&
and
.Ql W:\&
can take an optional sort argument
.Ql /i\& ,
.Ql /n\& ,
.Ql /t\&
to sort by index, name, or time; or
to sort by index, name, or last activity time; additionally
.Ql /r\&
to sort in reverse order.
.Ql /r\&
can also be used with
.Ql P:\&
to reverse the sort order by pane index.
For example,
.Ql S/nr:\&
to sort sessions by name in reverse order.
@ -6070,8 +6077,10 @@ The following variables are available, where appropriate:
.It Li "insert_flag" Ta "" Ta "Pane insert flag"
.It Li "keypad_cursor_flag" Ta "" Ta "Pane keypad cursor flag"
.It Li "keypad_flag" Ta "" Ta "Pane keypad flag"
.It Li "last_session_index" Ta "" Ta "Index of last session"
.It Li "last_window_index" Ta "" Ta "Index of last window in session"
.It Li "line" Ta "" Ta "Line number in the list"
.It Li "loop_last_flag" Ta "" Ta "1 if last window, pane, session, client in the W:, P:, S:, or L: loop"
.It Li "mouse_all_flag" Ta "" Ta "Pane mouse all flag"
.It Li "mouse_any_flag" Ta "" Ta "Pane mouse any flag"
.It Li "mouse_button_flag" Ta "" Ta "Pane mouse button flag"
@ -6143,10 +6152,13 @@ The following variables are available, where appropriate:
.It Li "selection_start_x" Ta "" Ta "X position of the start of the selection"
.It Li "selection_start_y" Ta "" Ta "Y position of the start of the selection"
.It Li "server_sessions" Ta "" Ta "Number of sessions"
.It Li "session_active" Ta "" Ta "1 if session active"
.It Li "session_activity" Ta "" Ta "Time of session last activity"
.It Li "session_activity_flag" Ta "" Ta "1 if any window in session has activity"
.It Li "session_alerts" Ta "" Ta "List of window indexes with alerts"
.It Li "session_attached" Ta "" Ta "Number of clients session is attached to"
.It Li "session_attached_list" Ta "" Ta "List of clients session is attached to"
.It Li "session_bell_flag" Ta "" Ta "1 if any window in session has bell"
.It Li "session_created" Ta "" Ta "Time session created"
.It Li "session_format" Ta "" Ta "1 if format is for a session"
.It Li "session_group" Ta "" Ta "Name of session group"
@ -6157,11 +6169,13 @@ The following variables are available, where appropriate:
.It Li "session_group_size" Ta "" Ta "Size of session group"
.It Li "session_grouped" Ta "" Ta "1 if session in a group"
.It Li "session_id" Ta "" Ta "Unique session ID"
.It Li "session_index" Ta "" Ta "Index of session"
.It Li "session_last_attached" Ta "" Ta "Time session last attached"
.It Li "session_many_attached" Ta "" Ta "1 if multiple clients attached"
.It Li "session_marked" Ta "" Ta "1 if this session contains the marked pane"
.It Li "session_name" Ta "#S" Ta "Name of session"
.It Li "session_path" Ta "" Ta "Working directory of session"
.It Li "session_silence_flag" Ta "" Ta "1 if any window in session has silence alert"
.It Li "session_stack" Ta "" Ta "Window indexes in most recent order"
.It Li "session_windows" Ta "" Ta "Number of windows in session"
.It Li "socket_path" Ta "" Ta "Server socket path"

1
tmux.h
View File

@ -2304,6 +2304,7 @@ char *paste_make_sample(struct paste_buffer *);
#define FORMAT_FORCE 0x2
#define FORMAT_NOJOBS 0x4
#define FORMAT_VERBOSE 0x8
#define FORMAT_LAST 0x10
#define FORMAT_NONE 0
#define FORMAT_PANE 0x80000000U
#define FORMAT_WINDOW 0x40000000U