root/usr.bin/tmux/format.c
/* $OpenBSD: format.c,v 1.409 2026/07/26 15:21:53 nicm Exp $ */

/*
 * Copyright (c) 2011 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 <sys/wait.h>

#include <ctype.h>
#include <errno.h>
#include <fnmatch.h>
#include <libgen.h>
#include <math.h>
#include <pwd.h>
#include <regex.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include "tmux.h"

/*
 * Build a list of key-value pairs and use them to expand #{key} entries in a
 * string.
 */

struct format_expand_state;

static char     *format_job_get(struct format_expand_state *, const char *);
static char     *format_quote_shell_single(const char *);
static char     *format_expand1(struct format_expand_state *, const char *);
static int       format_replace(struct format_expand_state *, const char *,
                     size_t, char **, size_t *, size_t *);
static void      format_defaults_session(struct format_tree *,
                     struct session *);
static void      format_defaults_client(struct format_tree *, struct client *);
static void      format_defaults_winlink(struct format_tree *,
                     struct winlink *);

/* Entry in format job tree. */
struct format_job {
        struct client           *client;
        u_int                    tag;
        const char              *cmd;
        const char              *expanded;

        time_t                   last;
        char                    *out;
        int                      updated;

        struct job              *job;
        int                      status;

        RB_ENTRY(format_job)     entry;
};

/* Format job tree. */
static int format_job_cmp(struct format_job *, struct format_job *);
static RB_HEAD(format_job_tree, format_job) format_jobs = RB_INITIALIZER();
RB_GENERATE_STATIC(format_job_tree, format_job, entry, format_job_cmp);

/* Format job tree comparison function. */
static int
format_job_cmp(struct format_job *fj1, struct format_job *fj2)
{
        if (fj1->tag < fj2->tag)
                return (-1);
        if (fj1->tag > fj2->tag)
                return (1);
        return (strcmp(fj1->cmd, fj2->cmd));
}

/* Maximum pad and trim width. */
#define FORMAT_MAX_WIDTH 10000

/* Maximum repeat size. */
#define FORMAT_MAX_REPEAT 10000

/* Maximum precision. */
#define FORMAT_MAX_PRECISION 100

/* Format modifiers. */
#define FORMAT_TIMESTRING 0x1
#define FORMAT_BASENAME 0x2
#define FORMAT_DIRNAME 0x4
#define FORMAT_QUOTE_SHELL 0x8
#define FORMAT_LITERAL 0x10
#define FORMAT_EXPAND 0x20
#define FORMAT_EXPANDTIME 0x40
#define FORMAT_SESSIONS 0x80
#define FORMAT_WINDOWS 0x100
#define FORMAT_PANES 0x200
#define FORMAT_PRETTY 0x400
#define FORMAT_LENGTH 0x800
#define FORMAT_WIDTH 0x1000
#define FORMAT_QUOTE_STYLE 0x2000
#define FORMAT_WINDOW_NAME 0x4000
#define FORMAT_SESSION_NAME 0x8000
#define FORMAT_CHARACTER 0x10000
#define FORMAT_COLOUR 0x20000
#define FORMAT_CLIENTS 0x40000
#define FORMAT_NOT 0x80000
#define FORMAT_NOT_NOT 0x100000
#define FORMAT_REPEAT 0x200000
#define FORMAT_QUOTE_ARGUMENTS 0x400000
#define FORMAT_RELATIVE 0x800000
#define FORMAT_CLIENT_TERMCAP 0x1000000
#define FORMAT_CLIENT_TERMFEAT 0x2000000
#define FORMAT_CLIENT_ENVIRON 0x4000000
#define FORMAT_COLOUR_ESC_FG 0x8000000
#define FORMAT_COLOUR_ESC_BG 0x10000000
#define FORMAT_QUOTE_SHELL_SQ 0x20000000
#define FORMAT_OPTIONS 0x40000000
#define FORMAT_ENVIRON 0x80000000ULL
#define FORMAT_DIFFERENCE 0x100000000ULL
#define FORMAT_CYCLE 0x200000000ULL

/* Limit on recursion. */
#define FORMAT_LOOP_LIMIT 100

/* Limit on time taken (milliseconds). */
#define FORMAT_TIME_LIMIT 100

/* 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 {
        char                    *key;
        char                    *value;
        time_t                   time;
        format_cb                cb;
        RB_ENTRY(format_entry)   entry;
};

/* Format type. */
enum format_type {
        FORMAT_TYPE_UNKNOWN,
        FORMAT_TYPE_SESSION,
        FORMAT_TYPE_WINDOW,
        FORMAT_TYPE_PANE
};

static struct sort_criteria sort_crit;

struct format_tree {
        enum format_type         type;

        struct client           *c;
        struct session          *s;
        struct winlink          *wl;
        struct window           *w;
        struct window_pane      *wp;
        struct paste_buffer     *pb;

        struct cmdq_item        *item;
        struct client           *client;
        int                      flags;
        u_int                    tag;

        struct mouse_event       m;

        RB_HEAD(format_entry_tree, format_entry) tree;
};
static int format_entry_cmp(struct format_entry *, struct format_entry *);
RB_GENERATE_STATIC(format_entry_tree, format_entry, entry, format_entry_cmp);

/* Format expand state. */
struct format_expand_state {
        struct format_tree      *ft;
        u_int                    loop;
        uint64_t                 start_time;
        int                      flags;

        time_t                   time;
        struct tm                tm;
};

/* Format modifier. */
struct format_modifier {
        char      modifier[3];
        u_int     size;

        char    **argv;
        int       argc;
};

/* Format entry tree comparison function. */
static int
format_entry_cmp(struct format_entry *fe1, struct format_entry *fe2)
{
        return (strcmp(fe1->key, fe2->key));
}

/* Single-character uppercase aliases. */
static const char *format_upper[] = {
        NULL,           /* A */
        NULL,           /* B */
        NULL,           /* C */
        "pane_id",      /* D */
        NULL,           /* E */
        "window_flags", /* F */
        NULL,           /* G */
        "host",         /* H */
        "window_index", /* I */
        NULL,           /* J */
        NULL,           /* K */
        NULL,           /* L */
        NULL,           /* M */
        NULL,           /* N */
        NULL,           /* O */
        "pane_index",   /* P */
        NULL,           /* Q */
        NULL,           /* R */
        "session_name", /* S */
        "pane_title",   /* T */
        NULL,           /* U */
        NULL,           /* V */
        "window_name",  /* W */
        NULL,           /* X */
        NULL,           /* Y */
        NULL            /* Z */
};

/* Single-character lowercase aliases. */
static const char *format_lower[] = {
        NULL,           /* a */
        NULL,           /* b */
        NULL,           /* c */
        NULL,           /* d */
        NULL,           /* e */
        NULL,           /* f */
        NULL,           /* g */
        "host_short",   /* h */
        NULL,           /* i */
        NULL,           /* j */
        NULL,           /* k */
        NULL,           /* l */
        NULL,           /* m */
        NULL,           /* n */
        NULL,           /* o */
        NULL,           /* p */
        NULL,           /* q */
        NULL,           /* r */
        NULL,           /* s */
        NULL,           /* t */
        NULL,           /* u */
        NULL,           /* v */
        NULL,           /* w */
        NULL,           /* x */
        NULL,           /* y */
        NULL            /* z */
};

/* Is logging enabled? */
static inline int
format_logging(struct format_tree *ft)
{
        return (log_get_level() != 0 || (ft->flags & FORMAT_VERBOSE));
}

/* Log a message if verbose. */
static void printflike(3, 4)
format_log1(struct format_expand_state *es, const char *from, const char *fmt,
    ...)
{
        struct format_tree      *ft = es->ft;
        va_list                  ap;
        char                    *s;
        static const char        spaces[] = "          ";

        if (!format_logging(ft))
                return;

        va_start(ap, fmt);
        xvasprintf(&s, fmt, ap);
        va_end(ap);

        log_debug("%s: %s", from, s);
        if (ft->item != NULL && (ft->flags & FORMAT_VERBOSE))
                cmdq_print(ft->item, "#%.*s%s", es->loop, spaces, s);

        free(s);
}
#define format_log(es, fmt, ...) format_log1(es, __func__, fmt, ##__VA_ARGS__)

/* Copy expand state. */
static void
format_copy_state(struct format_expand_state *to,
    struct format_expand_state *from, int flags)
{
        to->ft = from->ft;
        to->loop = from->loop;
        to->time = from->time;
        memcpy(&to->tm, &from->tm, sizeof to->tm);
        to->flags = from->flags|flags;
        to->start_time = from->start_time;
}

/* Format job update callback. */
static void
format_job_update(struct job *job)
{
        struct format_job       *fj = job_get_data(job);
        struct evbuffer         *evb = job_get_event(job)->input;
        char                    *line = NULL, *next;
        time_t                   t;

        while ((next = evbuffer_readline(evb)) != NULL) {
                free(line);
                line = next;
        }
        if (line == NULL)
                return;
        fj->updated = 1;

        free(fj->out);
        fj->out = line;

        log_debug("%s: %p %s: %s", __func__, fj, fj->cmd, fj->out);

        t = time(NULL);
        if (fj->status && fj->last != t) {
                if (fj->client != NULL)
                        server_status_client(fj->client);
                fj->last = t;
        }
}

/* Format job complete callback. */
static void
format_job_complete(struct job *job)
{
        struct format_job       *fj = job_get_data(job);
        struct evbuffer         *evb = job_get_event(job)->input;
        char                    *line, *buf;
        size_t                   len;

        fj->job = NULL;

        buf = NULL;
        if ((line = evbuffer_readline(evb)) == NULL) {
                len = EVBUFFER_LENGTH(evb);
                buf = xmalloc(len + 1);
                if (len != 0)
                        memcpy(buf, EVBUFFER_DATA(evb), len);
                buf[len] = '\0';
        } else
                buf = line;

        log_debug("%s: %p %s: %s", __func__, fj, fj->cmd, buf);

        if (*buf != '\0' || !fj->updated) {
                free(fj->out);
                fj->out = buf;
        } else
                free(buf);

        if (fj->status) {
                if (fj->client != NULL)
                        server_status_client(fj->client);
                fj->status = 0;
        }
}

/* Find a job. */
static char *
format_job_get(struct format_expand_state *es, const char *cmd)
{
        struct format_tree              *ft = es->ft;
        struct format_job_tree          *jobs;
        struct format_job                fj0, *fj;
        time_t                           t;
        char                            *expanded;
        int                              force;
        struct format_expand_state       next;

        if (ft->client == NULL)
                jobs = &format_jobs;
        else if (ft->client->jobs != NULL)
                jobs = ft->client->jobs;
        else {
                jobs = ft->client->jobs = xmalloc(sizeof *ft->client->jobs);
                RB_INIT(jobs);
        }

        fj0.tag = ft->tag;
        fj0.cmd = cmd;
        if ((fj = RB_FIND(format_job_tree, jobs, &fj0)) == NULL) {
                fj = xcalloc(1, sizeof *fj);
                fj->client = ft->client;
                fj->tag = ft->tag;
                fj->cmd = xstrdup(cmd);

                RB_INSERT(format_job_tree, jobs, fj);
        }

        format_copy_state(&next, es, FORMAT_EXPAND_NOJOBS|
            FORMAT_EXPAND_NOCYCLE);
        next.flags &= ~FORMAT_EXPAND_TIME;

        expanded = format_expand1(&next, cmd);
        if (fj->expanded == NULL || strcmp(expanded, fj->expanded) != 0) {
                free((void *)fj->expanded);
                fj->expanded = xstrdup(expanded);
                force = 1;
        } else
                force = (ft->flags & FORMAT_FORCE);

        t = time(NULL);
        if (force && fj->job != NULL)
               job_free(fj->job);
        if (force || (fj->job == NULL && fj->last != t)) {
                fj->job = job_run(expanded, 0, NULL, NULL, NULL,
                    server_client_get_cwd(ft->client, NULL), format_job_update,
                    format_job_complete, NULL, fj, JOB_NOWAIT, -1, -1);
                if (fj->job == NULL) {
                        free(fj->out);
                        xasprintf(&fj->out, "<'%s' didn't start>", fj->cmd);
                }
                fj->last = t;
                fj->updated = 0;
        } else if (fj->job != NULL && (t - fj->last) > 1 && fj->out == NULL)
                xasprintf(&fj->out, "<'%s' not ready>", fj->cmd);
        free(expanded);

        if (ft->flags & FORMAT_STATUS)
                fj->status = 1;
        if (fj->out == NULL)
                return (xstrdup(""));
        return (format_expand1(&next, fj->out));
}

/* Remove old jobs. */
static void
format_job_tidy(struct format_job_tree *jobs, int force)
{
        struct format_job       *fj, *fj1;
        time_t                   now;

        now = time(NULL);
        RB_FOREACH_SAFE(fj, format_job_tree, jobs, fj1) {
                if (!force && (fj->last > now || now - fj->last < 3600))
                        continue;
                RB_REMOVE(format_job_tree, jobs, fj);

                log_debug("%s: %s", __func__, fj->cmd);

                if (fj->job != NULL)
                        job_free(fj->job);

                free((void *)fj->expanded);
                free((void *)fj->cmd);
                free(fj->out);

                free(fj);
        }
}

/* Tidy old jobs for all clients. */
void
format_tidy_jobs(void)
{
        struct client   *c;

        format_job_tidy(&format_jobs, 0);
        TAILQ_FOREACH(c, &clients, entry) {
                if (c->jobs != NULL)
                        format_job_tidy(c->jobs, 0);
        }
}

/* Remove old jobs for client. */
void
format_lost_client(struct client *c)
{
        if (c->jobs != NULL)
                format_job_tidy(c->jobs, 1);
        free(c->jobs);
}

/* Wrapper for asprintf. */
static char * printflike(1, 2)
format_printf(const char *fmt, ...)
{
        va_list  ap;
        char    *s;

        va_start(ap, fmt);
        xvasprintf(&s, fmt, ap);
        va_end(ap);
        return (s);
}

/* Callback for host. */
static void *
format_cb_host(__unused struct format_tree *ft)
{
        char host[HOST_NAME_MAX + 1];

        if (gethostname(host, sizeof host) != 0)
                return (xstrdup(""));
        return (xstrdup(host));
}

/* Callback for host_short. */
static void *
format_cb_host_short(__unused struct format_tree *ft)
{
        char host[HOST_NAME_MAX + 1], *cp;

        if (gethostname(host, sizeof host) != 0)
                return (xstrdup(""));
        if ((cp = strchr(host, '.')) != NULL)
                *cp = '\0';
        return (xstrdup(host));
}

/* Callback for pid. */
static void *
format_cb_pid(__unused struct format_tree *ft)
{
        char    *value;

        xasprintf(&value, "%ld", (long)getpid());
        return (value);
}

/* Callback for session_attached_list. */
static void *
format_cb_session_attached_list(struct format_tree *ft)
{
        struct session  *s = ft->s;
        struct client   *loop;
        struct evbuffer *buffer;
        int              size;
        char            *value = NULL;

        if (s == NULL)
                return (NULL);

        buffer = evbuffer_new();
        if (buffer == NULL)
                fatalx("out of memory");

        TAILQ_FOREACH(loop, &clients, entry) {
                if (loop->session == s) {
                        if (EVBUFFER_LENGTH(buffer) > 0)
                                evbuffer_add(buffer, ",", 1);
                        evbuffer_add_printf(buffer, "%s", loop->name);
                }
        }

        if ((size = EVBUFFER_LENGTH(buffer)) != 0)
                value = xmemdup(EVBUFFER_DATA(buffer), size);
        evbuffer_free(buffer);
        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)
{
        struct session  *s = ft->s;
        struct winlink  *wl;
        char             alerts[1024], tmp[16];

        if (s == NULL)
                return (NULL);

        *alerts = '\0';
        RB_FOREACH(wl, winlinks, &s->windows) {
                if ((wl->flags & WINLINK_ALERTFLAGS) == 0)
                        continue;
                xsnprintf(tmp, sizeof tmp, "%u", wl->idx);

                if (*alerts != '\0')
                        strlcat(alerts, ",", sizeof alerts);
                strlcat(alerts, tmp, sizeof alerts);
                if (wl->flags & WINLINK_ACTIVITY)
                        strlcat(alerts, "#", sizeof alerts);
                if (wl->flags & WINLINK_BELL)
                        strlcat(alerts, "!", sizeof alerts);
                if (wl->flags & WINLINK_SILENCE)
                        strlcat(alerts, "~", sizeof alerts);
        }
        return (xstrdup(alerts));
}

/* Callback for session_stack. */
static void *
format_cb_session_stack(struct format_tree *ft)
{
        struct session  *s = ft->s;
        struct winlink  *wl;
        char             result[1024], tmp[16];

        if (s == NULL)
                return (NULL);

        xsnprintf(result, sizeof result, "%u", s->curw->idx);
        TAILQ_FOREACH(wl, &s->lastw, sentry) {
                xsnprintf(tmp, sizeof tmp, "%u", wl->idx);

                if (*result != '\0')
                        strlcat(result, ",", sizeof result);
                strlcat(result, tmp, sizeof result);
        }
        return (xstrdup(result));
}

/* Callback for window_stack_index. */
static void *
format_cb_window_stack_index(struct format_tree *ft)
{
        struct session  *s;
        struct winlink  *wl;
        u_int            idx;
        char            *value = NULL;

        if (ft->wl == NULL)
                return (NULL);
        s = ft->wl->session;

        idx = 0;
        TAILQ_FOREACH(wl, &s->lastw, sentry) {
                idx++;
                if (wl == ft->wl)
                        break;
        }
        if (wl == NULL)
                return (xstrdup("0"));
        xasprintf(&value, "%u", idx);
        return (value);
}

/* Callback for window_linked_sessions_list. */
static void *
format_cb_window_linked_sessions_list(struct format_tree *ft)
{
        struct window   *w;
        struct winlink  *wl;
        struct evbuffer *buffer;
        int              size;
        char            *value = NULL;

        if (ft->wl == NULL)
                return (NULL);
        w = ft->wl->window;

        buffer = evbuffer_new();
        if (buffer == NULL)
                fatalx("out of memory");

        TAILQ_FOREACH(wl, &w->winlinks, wentry) {
                if (EVBUFFER_LENGTH(buffer) > 0)
                        evbuffer_add(buffer, ",", 1);
                evbuffer_add_printf(buffer, "%s", wl->session->name);
        }

        if ((size = EVBUFFER_LENGTH(buffer)) != 0)
                value = xmemdup(EVBUFFER_DATA(buffer), size);
        evbuffer_free(buffer);
        return (value);
}

/* Callback for window_active_sessions. */
static void *
format_cb_window_active_sessions(struct format_tree *ft)
{
        struct window   *w;
        struct winlink  *wl;
        u_int            n = 0;
        char            *value;

        if (ft->wl == NULL)
                return (NULL);
        w = ft->wl->window;

        TAILQ_FOREACH(wl, &w->winlinks, wentry) {
                if (wl->session->curw == wl)
                        n++;
        }

        xasprintf(&value, "%u", n);
        return (value);
}

/* Callback for window_active_sessions_list. */
static void *
format_cb_window_active_sessions_list(struct format_tree *ft)
{
        struct window   *w;
        struct winlink  *wl;
        struct evbuffer *buffer;
        int              size;
        char            *value = NULL;

        if (ft->wl == NULL)
                return (NULL);
        w = ft->wl->window;

        buffer = evbuffer_new();
        if (buffer == NULL)
                fatalx("out of memory");

        TAILQ_FOREACH(wl, &w->winlinks, wentry) {
                if (wl->session->curw == wl) {
                        if (EVBUFFER_LENGTH(buffer) > 0)
                                evbuffer_add(buffer, ",", 1);
                        evbuffer_add_printf(buffer, "%s", wl->session->name);
                }
        }

        if ((size = EVBUFFER_LENGTH(buffer)) != 0)
                value = xmemdup(EVBUFFER_DATA(buffer), size);
        evbuffer_free(buffer);
        return (value);
}

/* Callback for window_active_clients. */
static void *
format_cb_window_active_clients(struct format_tree *ft)
{
        struct window   *w;
        struct client   *loop;
        struct session  *client_session;
        u_int            n = 0;
        char            *value;

        if (ft->wl == NULL)
                return (NULL);
        w = ft->wl->window;

        TAILQ_FOREACH(loop, &clients, entry) {
                client_session = loop->session;
                if (client_session == NULL)
                        continue;

                if (w == client_session->curw->window)
                        n++;
        }

        xasprintf(&value, "%u", n);
        return (value);
}

/* Callback for window_active_clients_list. */
static void *
format_cb_window_active_clients_list(struct format_tree *ft)
{
        struct window   *w;
        struct client   *loop;
        struct session  *client_session;
        struct evbuffer *buffer;
        int              size;
        char            *value = NULL;

        if (ft->wl == NULL)
                return (NULL);
        w = ft->wl->window;

        buffer = evbuffer_new();
        if (buffer == NULL)
                fatalx("out of memory");

        TAILQ_FOREACH(loop, &clients, entry) {
                client_session = loop->session;
                if (client_session == NULL)
                        continue;

                if (w == client_session->curw->window) {
                        if (EVBUFFER_LENGTH(buffer) > 0)
                                evbuffer_add(buffer, ",", 1);
                        evbuffer_add_printf(buffer, "%s", loop->name);
                }
        }

        if ((size = EVBUFFER_LENGTH(buffer)) != 0)
                value = xmemdup(EVBUFFER_DATA(buffer), size);
        evbuffer_free(buffer);
        return (value);
}

/* Callback for window_layout. */
static void *
format_cb_window_layout(struct format_tree *ft)
{
        struct window   *w = ft->w;

        if (w == NULL)
                return (NULL);

        if (w->saved_layout_root != NULL)
                return (layout_dump(w, w->saved_layout_root));
        return (layout_dump(w, w->layout_root));
}

/* Callback for window_visible_layout. */
static void *
format_cb_window_visible_layout(struct format_tree *ft)
{
        struct window   *w = ft->w;

        if (w == NULL)
                return (NULL);

        return (layout_dump(w, w->layout_root));
}

/* Callback for pane_start_command. */
static void *
format_cb_start_command(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;

        if (wp == NULL)
                return (NULL);

        return (cmd_stringify_argv(wp->argc, wp->argv));
}

/* Callback for pane_start_command_list. */
static void *
format_cb_start_command_list(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;
        char                    *buf = NULL, *s;
        size_t                   len = 0;
        int                      i;

        if (wp == NULL)
                return (NULL);
        if (wp->argc == 0)
                return (xstrdup(""));

        for (i = 0; i < wp->argc; i++) {
                s = format_quote_shell_single(wp->argv[i]);

                len += strlen(s) + 1;
                buf = xrealloc(buf, len);

                if (i == 0)
                        *buf = '\0';
                else
                        strlcat(buf, " ", len);
                strlcat(buf, s, len);

                free(s);
        }
        return (buf);
}

/* Callback for pane_start_path. */
static void *
format_cb_start_path(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;

        if (wp == NULL)
                return (NULL);

        if (wp->cwd == NULL)
                return (xstrdup(""));
        return (xstrdup(wp->cwd));
}

/* Callback for pane_current_command. */
static void *
format_cb_current_command(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;
        char                    *cmd, *value;

        if (wp == NULL || wp->shell == NULL)
                return (NULL);

        cmd = get_proc_name(wp->fd, wp->tty);
        if (cmd == NULL || *cmd == '\0') {
                free(cmd);
                cmd = cmd_stringify_argv(wp->argc, wp->argv);
                if (cmd == NULL || *cmd == '\0') {
                        free(cmd);
                        cmd = xstrdup(wp->shell);
                }
        }
        value = parse_window_name(cmd);
        free(cmd);
        return (value);
}

/* Callback for pane_current_path. */
static void *
format_cb_current_path(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;
        char                    *cwd;

        if (wp == NULL)
                return (NULL);

        cwd = get_proc_cwd(wp->fd);
        if (cwd == NULL)
                return (NULL);
        return (xstrdup(cwd));
}

/* Callback for history_bytes. */
static void *
format_cb_history_bytes(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;
        struct grid             *gd;
        struct grid_line        *gl;
        size_t                   size = 0;
        u_int                    i;
        char                    *value;

        if (wp == NULL)
                return (NULL);
        gd = wp->base.grid;

        for (i = 0; i < gd->hsize + gd->sy; i++) {
                gl = grid_get_line(gd, i);
                size += gl->cellsize * sizeof *gl->celldata;
                size += gl->extdsize * sizeof *gl->extddata;
        }
        size += (gd->hsize + gd->sy) * sizeof *gl;

        xasprintf(&value, "%zu", size);
        return (value);
}

/* Callback for history_all_bytes. */
static void *
format_cb_history_all_bytes(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;
        struct grid             *gd;
        struct grid_line        *gl;
        u_int                    i, lines, cells = 0, extended_cells = 0;
        char                    *value;

        if (wp == NULL)
                return (NULL);
        gd = wp->base.grid;

        lines = gd->hsize + gd->sy;
        for (i = 0; i < lines; i++) {
                gl = grid_get_line(gd, i);
                cells += gl->cellsize;
                extended_cells += gl->extdsize;
        }

        xasprintf(&value, "%u,%zu,%u,%zu,%u,%zu", lines,
            lines * sizeof *gl, cells, cells * sizeof *gl->celldata,
            extended_cells, extended_cells * sizeof *gl->extddata);
        return (value);
}

/* Callback for pane_tabs. */
static void *
format_cb_pane_tabs(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;
        struct evbuffer         *buffer;
        u_int                    i;
        int                      size;
        char                    *value = NULL;

        if (wp == NULL)
                return (NULL);

        buffer = evbuffer_new();
        if (buffer == NULL)
                fatalx("out of memory");
        for (i = 0; i < wp->base.grid->sx; i++) {
                if (!bit_test(wp->base.tabs, i))
                        continue;

                if (EVBUFFER_LENGTH(buffer) > 0)
                        evbuffer_add(buffer, ",", 1);
                evbuffer_add_printf(buffer, "%u", i);
        }
        if ((size = EVBUFFER_LENGTH(buffer)) != 0)
                value = xmemdup(EVBUFFER_DATA(buffer), size);
        evbuffer_free(buffer);
        return (value);
}

/* Callback for pane_fg. */
static void *
format_cb_pane_fg(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;
        struct grid_cell         gc;

        if (wp == NULL)
                return (NULL);

        tty_default_colours(&gc, wp, NULL);
        return (xstrdup(colour_tostring(gc.fg)));
}

/* Callback for pane_flags. */
static void *
format_cb_pane_flags(struct format_tree *ft)
{
        if (ft->wp != NULL)
                return (xstrdup(window_pane_printable_flags(ft->wp)));
        return (NULL);
}

/* Callback for pane_floating_flag. */
static void *
format_cb_pane_floating_flag(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;

        if (wp != NULL) {
                if (window_pane_is_floating(wp))
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for pane_modal_flag. */
static void *
format_cb_pane_modal_flag(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;

        if (wp != NULL) {
                if (wp == wp->window->modal)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for pane_bg. */
static void *
format_cb_pane_bg(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;
        struct grid_cell         gc;

        if (wp == NULL)
                return (NULL);

        tty_default_colours(&gc, wp, NULL);
        return (xstrdup(colour_tostring(gc.bg)));
}

/* Callback for session_group_list. */
static void *
format_cb_session_group_list(struct format_tree *ft)
{
        struct session          *s = ft->s;
        struct session_group    *sg;
        struct session          *loop;
        struct evbuffer         *buffer;
        int                      size;
        char                    *value = NULL;

        if (s == NULL)
                return (NULL);
        sg = session_group_contains(s);
        if (sg == NULL)
                return (NULL);

        buffer = evbuffer_new();
        if (buffer == NULL)
                fatalx("out of memory");

        TAILQ_FOREACH(loop, &sg->sessions, gentry) {
                if (EVBUFFER_LENGTH(buffer) > 0)
                        evbuffer_add(buffer, ",", 1);
                evbuffer_add_printf(buffer, "%s", loop->name);
        }

        if ((size = EVBUFFER_LENGTH(buffer)) != 0)
                value = xmemdup(EVBUFFER_DATA(buffer), size);
        evbuffer_free(buffer);
        return (value);
}

/* Callback for session_group_attached_list. */
static void *
format_cb_session_group_attached_list(struct format_tree *ft)
{
        struct session          *s = ft->s, *client_session, *session_loop;
        struct session_group    *sg;
        struct client           *loop;
        struct evbuffer         *buffer;
        int                      size;
        char                    *value = NULL;

        if (s == NULL)
                return (NULL);
        sg = session_group_contains(s);
        if (sg == NULL)
                return (NULL);

        buffer = evbuffer_new();
        if (buffer == NULL)
                fatalx("out of memory");

        TAILQ_FOREACH(loop, &clients, entry) {
                client_session = loop->session;
                if (client_session == NULL)
                        continue;
                TAILQ_FOREACH(session_loop, &sg->sessions, gentry) {
                        if (session_loop == client_session){
                                if (EVBUFFER_LENGTH(buffer) > 0)
                                        evbuffer_add(buffer, ",", 1);
                                evbuffer_add_printf(buffer, "%s", loop->name);
                        }
                }
        }

        if ((size = EVBUFFER_LENGTH(buffer)) != 0)
                value = xmemdup(EVBUFFER_DATA(buffer), size);
        evbuffer_free(buffer);
        return (value);
}

/* Callback for pane_in_mode. */
static void *
format_cb_pane_in_mode(struct format_tree *ft)
{
        struct window_pane              *wp = ft->wp;
        u_int                            n = 0;
        struct window_mode_entry        *wme;
        char                            *value;

        if (wp == NULL)
                return (NULL);

        TAILQ_FOREACH(wme, &wp->modes, entry)
                n++;
        xasprintf(&value, "%u", n);
        return (value);
}

/* Callback for pane_at_top. */
static void *
format_cb_pane_at_top(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;
        int                      status, flag;
        char                    *value;

        if (wp == NULL)
                return (NULL);

        status = window_pane_get_pane_status(wp);
        if (status == PANE_STATUS_TOP)
                flag = (wp->yoff == 1);
        else
                flag = (wp->yoff == 0);
        xasprintf(&value, "%d", flag);
        return (value);
}

/* Callback for pane_at_bottom. */
static void *
format_cb_pane_at_bottom(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;
        struct window           *w;
        int                      status, flag;
        char                    *value;

        if (wp == NULL)
                return (NULL);
        w = wp->window;

        status = window_pane_get_pane_status(wp);
        if (status == PANE_STATUS_BOTTOM)
                flag = (wp->yoff + (int)wp->sy == (int)w->sy - 1);
        else
                flag = (wp->yoff + (int)wp->sy == (int)w->sy);
        xasprintf(&value, "%d", flag);
        return (value);
}

/* Callback for cursor_character. */
static void *
format_cb_cursor_character(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;
        struct grid_cell         gc;
        char                    *value = NULL;

        if (wp == NULL)
                return (NULL);

        grid_view_get_cell(wp->base.grid, wp->base.cx, wp->base.cy, &gc);
        if (~gc.flags & GRID_FLAG_PADDING)
                xasprintf(&value, "%.*s", (int)gc.data.size, gc.data.data);
        return (value);
}

/* Callback for cursor_colour. */
static void *
format_cb_cursor_colour(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;

        if (wp == NULL || wp->screen == NULL)
                return (NULL);

        if (wp->screen->ccolour != -1)
                return (xstrdup(colour_tostring(wp->screen->ccolour)));
        return (xstrdup(colour_tostring(wp->screen->default_ccolour)));
}

/* Callback for mouse_word. */
static void *
format_cb_mouse_word(struct format_tree *ft)
{
        struct window_pane      *wp;
        struct grid             *gd;
        u_int                    x, y;

        if (!ft->m.valid)
                return (NULL);
        wp = cmd_mouse_pane(&ft->m, NULL, NULL);
        if (wp == NULL)
                return (NULL);
        if (cmd_mouse_at(wp, &ft->m, &x, &y, 0) != 0)
                return (NULL);

        if (!TAILQ_EMPTY(&wp->modes)) {
                if (window_pane_mode(wp) != WINDOW_PANE_NO_MODE)
                        return (window_copy_get_word(wp, x, y));
                return (NULL);
        }
        gd = wp->base.grid;
        return (format_grid_word(gd, x, gd->hsize + y));
}

/* Callback for mouse_hyperlink. */
static void *
format_cb_mouse_hyperlink(struct format_tree *ft)
{
        struct window_pane      *wp;
        struct grid             *gd;
        u_int                    x, y;

        if (!ft->m.valid)
                return (NULL);
        wp = cmd_mouse_pane(&ft->m, NULL, NULL);
        if (wp == NULL)
                return (NULL);
        if (cmd_mouse_at(wp, &ft->m, &x, &y, 0) != 0)
                return (NULL);

        if (!TAILQ_EMPTY(&wp->modes)) {
                if (window_pane_mode(wp) != WINDOW_PANE_NO_MODE)
                        return (window_copy_get_hyperlink(wp, x, y));
                return (NULL);
        }
        gd = wp->base.grid;
        return (format_grid_hyperlink(gd, x, gd->hsize + y, wp->screen));
}

/* Callback for mouse_line. */
static void *
format_cb_mouse_line(struct format_tree *ft)
{
        struct window_pane      *wp;
        struct grid             *gd;
        u_int                    x, y;

        if (!ft->m.valid)
                return (NULL);
        wp = cmd_mouse_pane(&ft->m, NULL, NULL);
        if (wp == NULL)
                return (NULL);
        if (cmd_mouse_at(wp, &ft->m, &x, &y, 0) != 0)
                return (NULL);

        if (!TAILQ_EMPTY(&wp->modes)) {
                if (window_pane_mode(wp) != WINDOW_PANE_NO_MODE)
                        return (window_copy_get_line(wp, y));
                return (NULL);
        }
        gd = wp->base.grid;
        return (format_grid_line(gd, gd->hsize + y));
}

/* Callback for mouse_status_line. */
static void *
format_cb_mouse_status_line(struct format_tree *ft)
{
        char    *value;
        u_int    y;

        if (!ft->m.valid)
                return (NULL);
        if (ft->c == NULL || (~ft->c->tty.flags & TTY_STARTED))
                return (NULL);

        if (ft->m.statusat == 0 && ft->m.y < ft->m.statuslines) {
                y = ft->m.y;
        } else if (ft->m.statusat > 0 && ft->m.y >= (u_int)ft->m.statusat) {
                y = ft->m.y - ft->m.statusat;
        } else
                return (NULL);
        xasprintf(&value, "%u", y);
        return (value);

}

/* Callback for mouse_status_range. */
static void *
format_cb_mouse_status_range(struct format_tree *ft)
{
        struct style_range      *sr;
        u_int                    x, y;

        if (!ft->m.valid)
                return (NULL);
        if (ft->c == NULL || (~ft->c->tty.flags & TTY_STARTED))
                return (NULL);

        if (ft->m.statusat == 0 && ft->m.y < ft->m.statuslines) {
                x = ft->m.x;
                y = ft->m.y;
        } else if (ft->m.statusat > 0 && ft->m.y >= (u_int)ft->m.statusat) {
                x = ft->m.x;
                y = ft->m.y - ft->m.statusat;
        } else
                return (NULL);

        sr = status_get_range(ft->c, x, y);
        if (sr == NULL)
                return (NULL);
        switch (sr->type) {
        case STYLE_RANGE_NONE:
                return (NULL);
        case STYLE_RANGE_LEFT:
                return (xstrdup("left"));
        case STYLE_RANGE_RIGHT:
                return (xstrdup("right"));
        case STYLE_RANGE_PANE:
                return (xstrdup("pane"));
        case STYLE_RANGE_WINDOW:
                return (xstrdup("window"));
        case STYLE_RANGE_SESSION:
                return (xstrdup("session"));
        case STYLE_RANGE_USER:
                return (xstrdup(sr->string));
        case STYLE_RANGE_CONTROL:
                return (xstrdup("control"));
        }
        return (NULL);
}

/* Callback for alternate_on. */
static void *
format_cb_alternate_on(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (ft->wp->base.saved_grid != NULL)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for alternate_saved_x. */
static void *
format_cb_alternate_saved_x(struct format_tree *ft)
{
        if (ft->wp != NULL)
                return (format_printf("%u", ft->wp->base.saved_cx));
        return (NULL);
}

/* Callback for alternate_saved_y. */
static void *
format_cb_alternate_saved_y(struct format_tree *ft)
{
        if (ft->wp != NULL)
                return (format_printf("%u", ft->wp->base.saved_cy));
        return (NULL);
}

/* Callback for bracket_paste_flag. */
static void *
format_cb_bracket_paste_flag(struct format_tree *ft)
{
        if (ft->wp != NULL && ft->wp->screen != NULL) {
                if (ft->wp->screen->mode & MODE_BRACKETPASTE)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for buffer_name. */
static void *
format_cb_buffer_name(struct format_tree *ft)
{
        if (ft->pb != NULL)
                return (xstrdup(paste_buffer_name(ft->pb)));
        return (NULL);
}

/* Callback for buffer_sample. */
static void *
format_cb_buffer_sample(struct format_tree *ft)
{
        if (ft->pb != NULL)
                return (paste_make_sample(ft->pb));
        return (NULL);
}

/* Callback for buffer_full. */
static void *
format_cb_buffer_full(struct format_tree *ft)
{
        size_t           size;
        const char      *s;

        if (ft->pb != NULL) {
                s = paste_buffer_data(ft->pb, &size);
                if (s != NULL)
                        return (xstrndup(s, size));
        }
        return (NULL);
}

/* Callback for buffer_size. */
static void *
format_cb_buffer_size(struct format_tree *ft)
{
        size_t  size;

        if (ft->pb != NULL) {
                paste_buffer_data(ft->pb, &size);
                return (format_printf("%zu", size));
        }
        return (NULL);
}

/* Callback for client_cell_height. */
static void *
format_cb_client_cell_height(struct format_tree *ft)
{
        if (ft->c != NULL && (ft->c->tty.flags & TTY_STARTED))
                return (format_printf("%u", ft->c->tty.ypixel));
        return (NULL);
}

/* Callback for client_cell_width. */
static void *
format_cb_client_cell_width(struct format_tree *ft)
{
        if (ft->c != NULL && (ft->c->tty.flags & TTY_STARTED))
                return (format_printf("%u", ft->c->tty.xpixel));
        return (NULL);
}

/* Callback for client_colours. */
static void *
format_cb_client_colours(struct format_tree *ft)
{
        struct tty_term *term;
        u_int            colours;

        if (ft->c == NULL || (~ft->c->tty.flags & TTY_STARTED))
                return (NULL);
        term = ft->c->tty.term;

        if (term->flags & TERM_RGBCOLOURS)
                colours = 16777216;
        else if (term->flags & TERM_256COLOURS)
                colours = 256;
        else {
                colours = tty_term_number(term, TTYC_COLORS);
                if (colours < 8)
                        colours = 2;
                else if (colours < 16)
                        colours = 8;
                else
                        colours = 16;
        }
        return (format_printf("%u", colours));
}

/* Callback for client_control_mode. */
static void *
format_cb_client_control_mode(struct format_tree *ft)
{
        if (ft->c != NULL) {
                if (ft->c->flags & CLIENT_CONTROL)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for client_discarded. */
static void *
format_cb_client_discarded(struct format_tree *ft)
{
        if (ft->c != NULL)
                return (format_printf("%zu", ft->c->discarded));
        return (NULL);
}

/* Callback for client_flags. */
static void *
format_cb_client_flags(struct format_tree *ft)
{
        if (ft->c != NULL)
                return (xstrdup(server_client_get_flags(ft->c)));
        return (NULL);
}

/* Callback for client_height. */
static void *
format_cb_client_height(struct format_tree *ft)
{
        if (ft->c != NULL && (ft->c->tty.flags & TTY_STARTED))
                return (format_printf("%u", ft->c->tty.sy));
        return (NULL);
}

/* Callback for client_key_table. */
static void *
format_cb_client_key_table(struct format_tree *ft)
{
        if (ft->c != NULL)
                return (xstrdup(ft->c->keytable->name));
        return (NULL);
}

/* Callback for client_last_session. */
static void *
format_cb_client_last_session(struct format_tree *ft)
{
        if (ft->c != NULL &&
            ft->c->last_session != NULL &&
            session_alive(ft->c->last_session))
                return (xstrdup(ft->c->last_session->name));
        return (NULL);
}

/* Callback for client_name. */
static void *
format_cb_client_name(struct format_tree *ft)
{
        if (ft->c != NULL)
                return (xstrdup(ft->c->name));
        return (NULL);
}

/* Callback for client_pid. */
static void *
format_cb_client_pid(struct format_tree *ft)
{
        if (ft->c != NULL)
                return (format_printf("%ld", (long)ft->c->pid));
        return (NULL);
}

/* Callback for client_prefix. */
static void *
format_cb_client_prefix(struct format_tree *ft)
{
        const char      *name;

        if (ft->c != NULL) {
                name = server_client_get_key_table(ft->c);
                if (strcmp(ft->c->keytable->name, name) == 0)
                        return (xstrdup("0"));
                return (xstrdup("1"));
        }
        return (NULL);
}

/* Callback for client_readonly. */
static void *
format_cb_client_readonly(struct format_tree *ft)
{
        if (ft->c != NULL) {
                if (ft->c->flags & CLIENT_READONLY)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for client_session. */
static void *
format_cb_client_session(struct format_tree *ft)
{
        if (ft->c != NULL && ft->c->session != NULL)
                return (xstrdup(ft->c->session->name));
        return (NULL);
}

/* Callback for client_termfeatures. */
static void *
format_cb_client_termfeatures(struct format_tree *ft)
{
        if (ft->c != NULL)
                return (xstrdup(tty_get_features(ft->c->term_features)));
        return (NULL);
}

/* Callback for client_termname. */
static void *
format_cb_client_termname(struct format_tree *ft)
{
        if (ft->c != NULL)
                return (xstrdup(ft->c->term_name));
        return (NULL);
}

/* Callback for client_termtype. */
static void *
format_cb_client_termtype(struct format_tree *ft)
{
        if (ft->c != NULL) {
                if (ft->c->term_type == NULL)
                        return (xstrdup(""));
                return (xstrdup(ft->c->term_type));
        }
        return (NULL);
}

/* Callback for client_tty. */
static void *
format_cb_client_tty(struct format_tree *ft)
{
        if (ft->c != NULL)
                return (xstrdup(ft->c->ttyname));
        return (NULL);
}

/* Callback for client_uid. */
static void *
format_cb_client_uid(struct format_tree *ft)
{
        uid_t   uid;

        if (ft->c != NULL) {
                uid = proc_get_peer_uid(ft->c->peer);
                if (uid != (uid_t)-1)
                        return (format_printf("%ld", (long)uid));
        }
        return (NULL);
}

/* Callback for client_user. */
static void *
format_cb_client_user(struct format_tree *ft)
{
        uid_t            uid;
        struct passwd   *pw;

        if (ft->c != NULL) {
                if (ft->c->user != NULL)
                        return (xstrdup(ft->c->user));
                uid = proc_get_peer_uid(ft->c->peer);
                if (uid != (uid_t)-1 && (pw = getpwuid(uid)) != NULL) {
                        ft->c->user = xstrdup(pw->pw_name);
                        return (xstrdup(ft->c->user));
                }
        }
        return (NULL);
}

/* Callback for client_utf8. */
static void *
format_cb_client_utf8(struct format_tree *ft)
{
        if (ft->c != NULL) {
                if (ft->c->flags & CLIENT_UTF8)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for client_width. */
static void *
format_cb_client_width(struct format_tree *ft)
{
        if (ft->c != NULL)
                return (format_printf("%u", ft->c->tty.sx));
        return (NULL);
}

/* Callback for client_written. */
static void *
format_cb_client_written(struct format_tree *ft)
{
        if (ft->c != NULL)
                return (format_printf("%zu", ft->c->written));
        return (NULL);
}

/* Callback for client_theme. */
static void *
format_cb_client_theme(struct format_tree *ft)
{
        if (ft->c != NULL) {
                switch (ft->c->theme) {
                case THEME_DARK:
                        return (xstrdup("dark"));
                case THEME_LIGHT:
                        return (xstrdup("light"));
                case THEME_UNKNOWN:
                        return (NULL);
                }
        }
        return (NULL);
}

/* Callback for config_files. */
static void *
format_cb_config_files(__unused struct format_tree *ft)
{
        char    *s = NULL;
        size_t   slen = 0;
        u_int    i;
        size_t   n;

        for (i = 0; i < cfg_nfiles; i++) {
                n = strlen(cfg_files[i]) + 1;
                s = xrealloc(s, slen + n + 1);
                slen += xsnprintf(s + slen, n + 1, "%s,", cfg_files[i]);
        }
        if (s == NULL)
                return (xstrdup(""));
        s[slen - 1] = '\0';
        return (s);
}

/* Callback for cursor_flag. */
static void *
format_cb_cursor_flag(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (ft->wp->base.mode & MODE_CURSOR)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for cursor_shape. */
static void *
format_cb_cursor_shape(struct format_tree *ft)
{
        if (ft->wp != NULL && ft->wp->screen != NULL) {
                switch (ft->wp->screen->cstyle) {
                case SCREEN_CURSOR_BLOCK:
                        return (xstrdup("block"));
                case SCREEN_CURSOR_UNDERLINE:
                        return (xstrdup("underline"));
                case SCREEN_CURSOR_BAR:
                        return (xstrdup("bar"));
                default:
                        return (xstrdup("default"));
                }
        }
        return (NULL);
}

/* Callback for cursor_very_visible. */
static void *
format_cb_cursor_very_visible(struct format_tree *ft)
{
        if (ft->wp != NULL && ft->wp->screen != NULL) {
                if (ft->wp->screen->mode & MODE_CURSOR_VERY_VISIBLE)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for cursor_x. */
static void *
format_cb_cursor_x(struct format_tree *ft)
{
        if (ft->wp != NULL)
                return (format_printf("%u", ft->wp->base.cx));
        return (NULL);
}

/* Callback for cursor_y. */
static void *
format_cb_cursor_y(struct format_tree *ft)
{
        if (ft->wp != NULL)
                return (format_printf("%u", ft->wp->base.cy));
        return (NULL);
}

/* Callback for cursor_blinking. */
static void *
format_cb_cursor_blinking(struct format_tree *ft)
{
        if (ft->wp != NULL && ft->wp->screen != NULL) {
                if (ft->wp->screen->mode & MODE_CURSOR_BLINKING)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for history_limit. */
static void *
format_cb_history_limit(struct format_tree *ft)
{
        if (ft->wp != NULL)
                return (format_printf("%u", ft->wp->base.grid->hlimit));
        return (NULL);
}

/* Callback for history_size. */
static void *
format_cb_history_size(struct format_tree *ft)
{
        if (ft->wp != NULL)
                return (format_printf("%u", ft->wp->base.grid->hsize));
        return (NULL);
}

/* Callback for insert_flag. */
static void *
format_cb_insert_flag(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (ft->wp->base.mode & MODE_INSERT)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for keypad_cursor_flag. */
static void *
format_cb_keypad_cursor_flag(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (ft->wp->base.mode & MODE_KCURSOR)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for keypad_flag. */
static void *
format_cb_keypad_flag(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (ft->wp->base.mode & MODE_KKEYPAD)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for mouse_all_flag. */
static void *
format_cb_mouse_all_flag(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (ft->wp->base.mode & MODE_MOUSE_ALL)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for mouse_any_flag. */
static void *
format_cb_mouse_any_flag(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (ft->wp->base.mode & ALL_MOUSE_MODES)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for mouse_button_flag. */
static void *
format_cb_mouse_button_flag(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (ft->wp->base.mode & MODE_MOUSE_BUTTON)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for mouse_pane. */
static void *
format_cb_mouse_pane(struct format_tree *ft)
{
        struct window_pane      *wp;

        if (ft->m.valid) {
                wp = cmd_mouse_pane(&ft->m, NULL, NULL);
                if (wp != NULL)
                        return (format_printf("%%%u", wp->id));
                return (NULL);
        }
        return (NULL);
}

/* Callback for mouse_sgr_flag. */
static void *
format_cb_mouse_sgr_flag(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (ft->wp->base.mode & MODE_MOUSE_SGR)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for mouse_standard_flag. */
static void *
format_cb_mouse_standard_flag(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (ft->wp->base.mode & MODE_MOUSE_STANDARD)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for mouse_utf8_flag. */
static void *
format_cb_mouse_utf8_flag(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (ft->wp->base.mode & MODE_MOUSE_UTF8)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for mouse_x. */
static void *
format_cb_mouse_x(struct format_tree *ft)
{
        struct window_pane      *wp;
        u_int                    x, y;

        if (!ft->m.valid)
                return (NULL);
        wp = cmd_mouse_pane(&ft->m, NULL, NULL);
        if (wp != NULL && cmd_mouse_at(wp, &ft->m, &x, &y, 0) == 0)
                return (format_printf("%u", x));
        if (ft->c != NULL && (ft->c->tty.flags & TTY_STARTED)) {
                if (ft->m.statusat == 0 && ft->m.y < ft->m.statuslines)
                        return (format_printf("%u", ft->m.x));
                if (ft->m.statusat > 0 && ft->m.y >= (u_int)ft->m.statusat)
                        return (format_printf("%u", ft->m.x));
        }
        return (NULL);
}

/* Callback for mouse_y. */
static void *
format_cb_mouse_y(struct format_tree *ft)
{
        struct window_pane      *wp;
        u_int                    x, y;

        if (!ft->m.valid)
                return (NULL);
        wp = cmd_mouse_pane(&ft->m, NULL, NULL);
        if (wp != NULL && cmd_mouse_at(wp, &ft->m, &x, &y, 0) == 0)
                return (format_printf("%u", y));
        if (ft->c != NULL && (ft->c->tty.flags & TTY_STARTED)) {
                if (ft->m.statusat == 0 && ft->m.y < ft->m.statuslines)
                        return (format_printf("%u", ft->m.y));
                if (ft->m.statusat > 0 && ft->m.y >= (u_int)ft->m.statusat)
                        return (format_printf("%u", ft->m.y - ft->m.statusat));
        }
        return (NULL);
}

/* Callback for next_session_id. */
static void *
format_cb_next_session_id(__unused struct format_tree *ft)
{
        return (format_printf("$%u", next_session_id));
}

/* Callback for origin_flag. */
static void *
format_cb_origin_flag(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (ft->wp->base.mode & MODE_ORIGIN)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for synchronized_output_flag. */
static void *
format_cb_synchronized_output_flag(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (ft->wp->base.mode & MODE_SYNC)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for pane_active. */
static void *
format_cb_pane_active(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (ft->wp == ft->wp->window->active)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for pane_at_left. */
static void *
format_cb_pane_at_left(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (ft->wp->xoff == 0)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for pane_at_right. */
static void *
format_cb_pane_at_right(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (ft->wp->xoff + (int)ft->wp->sx == (int)ft->wp->window->sx)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for pane_bottom. */
static void *
format_cb_pane_bottom(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;

        if (wp != NULL)
                return (format_printf("%d", wp->yoff + (int)wp->sy - 1));
        return (NULL);
}

/* Callback for pane_dead. */
static void *
format_cb_pane_dead(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;

        if (wp != NULL) {
                if (wp->fd == -1 && (wp->flags & PANE_STATUSREADY))
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for pane_dead_signal. */
static void *
format_cb_pane_dead_signal(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;
        const char              *name;

        if (wp != NULL) {
                if ((wp->flags & PANE_STATUSREADY) && WIFSIGNALED(wp->status)) {
                        name = sig2name(WTERMSIG(wp->status));
                        return (format_printf("%s", name));
                }
                return (NULL);
        }
        return (NULL);
}

/* Callback for pane_dead_status. */
static void *
format_cb_pane_dead_status(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;

        if (wp != NULL) {
                if ((wp->flags & PANE_STATUSREADY) && WIFEXITED(wp->status))
                        return (format_printf("%d", WEXITSTATUS(wp->status)));
                return (NULL);
        }
        return (NULL);
}

/* Callback for pane_dead_time. */
static void *
format_cb_pane_dead_time(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;

        if (wp != NULL) {
                if (wp->flags & PANE_STATUSDRAWN)
                        return (&wp->dead_time);
                return (NULL);
        }
        return (NULL);
}

/* Callback for pane_last_output_time. */
static void *
format_cb_pane_last_output_time(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;
        static struct timeval    tv;

        if (wp != NULL && wp->last_output_time != 0) {
                tv.tv_sec = wp->last_output_time;
                tv.tv_usec = 0;
                return (&tv);
        }
        return (NULL);
}

/* Callback for pane_last_prompt_time. */
static void *
format_cb_pane_last_prompt_time(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;
        static struct timeval    tv;

        if (wp != NULL && wp->last_prompt_time != 0) {
                tv.tv_sec = wp->last_prompt_time;
                tv.tv_usec = 0;
                return (&tv);
        }
        return (NULL);
}

/* Callback for pane_command_start_time. */
static void *
format_cb_pane_command_start_time(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;
        static struct timeval    tv;

        if (wp != NULL && wp->cmd_start_time != 0) {
                tv.tv_sec = wp->cmd_start_time;
                tv.tv_usec = 0;
                return (&tv);
        }
        return (NULL);
}

/* Callback for pane_command_end_time. */
static void *
format_cb_pane_command_end_time(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;
        static struct timeval    tv;

        if (wp != NULL && wp->cmd_end_time != 0) {
                tv.tv_sec = wp->cmd_end_time;
                tv.tv_usec = 0;
                return (&tv);
        }
        return (NULL);
}

/* Callback for pane_command_running. */
static void *
format_cb_pane_command_running(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;

        if (wp != NULL)
                return (format_printf("%d", !!(wp->flags & PANE_CMDRUNNING)));
        return (NULL);
}

/* Callback for pane_command_duration. */
static void *
format_cb_pane_command_duration(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;
        time_t                   end;

        if (wp == NULL || wp->cmd_start_time == 0)
                return (NULL);
        if (wp->flags & PANE_CMDRUNNING)
                end = time(NULL);
        else
                end = wp->cmd_end_time;
        if (end < wp->cmd_start_time)
                end = wp->cmd_start_time;
        return (format_printf("%lld", (long long)(end - wp->cmd_start_time)));
}

/* Callback for pane_command_status. */
static void *
format_cb_pane_command_status(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;

        if (wp != NULL && wp->cmd_status != -1)
                return (format_printf("%d", wp->cmd_status));
        return (NULL);
}

/* Callback for pane_format. */
static void *
format_cb_pane_format(struct format_tree *ft)
{
        if (ft->type == FORMAT_TYPE_PANE)
                return (xstrdup("1"));
        return (xstrdup("0"));
}

/* Callback for pane_height. */
static void *
format_cb_pane_height(struct format_tree *ft)
{
        if (ft->wp != NULL)
                return (format_printf("%u", ft->wp->sy));
        return (NULL);
}

/* Callback for pane_id. */
static void *
format_cb_pane_id(struct format_tree *ft)
{
        if (ft->wp != NULL)
                return (format_printf("%%%u", ft->wp->id));
        return (NULL);
}

/* Callback for pane_index. */
static void *
format_cb_pane_index(struct format_tree *ft)
{
        u_int   idx;

        if (ft->wp != NULL && window_pane_index(ft->wp, &idx) == 0)
                return (format_printf("%u", idx));
        return (NULL);
}

/* Callback for pane_input_off. */
static void *
format_cb_pane_input_off(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (ft->wp->flags & PANE_INPUTOFF)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for pane_unseen_changes. */
static void *
format_cb_pane_unseen_changes(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (ft->wp->flags & PANE_UNSEENCHANGES)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for pane_key_mode. */
static void *
format_cb_pane_key_mode(struct format_tree *ft)
{
        if (ft->wp != NULL && ft->wp->screen != NULL) {
                switch (ft->wp->screen->mode & EXTENDED_KEY_MODES) {
                case MODE_KEYS_EXTENDED:
                        return (xstrdup("Ext 1"));
                case MODE_KEYS_EXTENDED_2:
                        return (xstrdup("Ext 2"));
                default:
                        return (xstrdup("VT10x"));
                }
        }
        return (NULL);
}

/* Callback for pane_last. */
static void *
format_cb_pane_last(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (ft->wp == TAILQ_FIRST(&ft->wp->window->last_panes))
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for pane_left. */
static void *
format_cb_pane_left(struct format_tree *ft)
{
        if (ft->wp != NULL)
                return (format_printf("%d", ft->wp->xoff));
        return (NULL);
}

/* Callback for pane_marked. */
static void *
format_cb_pane_marked(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (server_check_marked() && marked_pane.wp == ft->wp)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for pane_marked_set. */
static void *
format_cb_pane_marked_set(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (server_check_marked())
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for pane_mode. */
static void *
format_cb_pane_mode(struct format_tree *ft)
{
        struct window_mode_entry        *wme;

        if (ft->wp != NULL) {
                wme = TAILQ_FIRST(&ft->wp->modes);
                if (wme != NULL)
                        return (xstrdup(wme->mode->name));
                return (NULL);
        }
        return (NULL);
}

/* Callback for pane_path. */
static void *
format_cb_pane_path(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (ft->wp->base.path == NULL)
                        return (xstrdup(""));
                return (xstrdup(ft->wp->base.path));
        }
        return (NULL);
}

/* Callback for pane_pid. */
static void *
format_cb_pane_pid(struct format_tree *ft)
{
        if (ft->wp != NULL && ft->wp->fd != -1)
                return (format_printf("%ld", (long)ft->wp->pid));
        return (NULL);
}

/* Callback for pane_pipe. */
static void *
format_cb_pane_pipe(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (ft->wp->pipe_fd != -1)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for pane_pipe_pid. */
static void *
format_cb_pane_pipe_pid(struct format_tree *ft)
{
        char    *value = NULL;

        if (ft->wp != NULL && ft->wp->pipe_fd != -1)
                xasprintf(&value, "%ld", (long)ft->wp->pipe_pid);
        return (value);
}

/* Callback for pane_pb_progress. */
static void *
format_cb_pane_pb_progress(struct format_tree *ft)
{
        char    *value = NULL;

        if (ft->wp != NULL)
                xasprintf(&value, "%d", ft->wp->base.progress_bar.progress);
        return (value);
}

/* Callback for pane_pb_state. */
static void *
format_cb_pane_pb_state(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                switch (ft->wp->base.progress_bar.state) {
                case PROGRESS_BAR_HIDDEN:
                        return xstrdup("hidden");
                case PROGRESS_BAR_NORMAL:
                        return xstrdup("normal");
                case PROGRESS_BAR_ERROR:
                        return xstrdup("error");
                case PROGRESS_BAR_INDETERMINATE:
                        return xstrdup("indeterminate");
                case PROGRESS_BAR_PAUSED:
                        return xstrdup("paused");
                }
        }
        return (NULL);
}

/* Callback for pane_right. */
static void *
format_cb_pane_right(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;

        if (wp != NULL)
                return (format_printf("%d", wp->xoff + (int)wp->sx - 1));
        return (NULL);
}

/* Callback for pane_search_string. */
static void *
format_cb_pane_search_string(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (ft->wp->searchstr == NULL)
                        return (xstrdup(""));
                return (xstrdup(ft->wp->searchstr));
        }
        return (NULL);
}

/* Callback for pane_synchronized. */
static void *
format_cb_pane_synchronized(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (options_get_number(ft->wp->options, "synchronize-panes"))
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for pane_title. */
static void *
format_cb_pane_title(struct format_tree *ft)
{
        if (ft->wp != NULL)
                return (xstrdup(ft->wp->base.title));
        return (NULL);
}

/* Callback for pane_top. */
static void *
format_cb_pane_top(struct format_tree *ft)
{
        if (ft->wp != NULL)
                return (format_printf("%d", ft->wp->yoff));
        return (NULL);
}

/* Callback for pane_tty. */
static void *
format_cb_pane_tty(struct format_tree *ft)
{
        if (ft->wp != NULL)
                return (xstrdup(ft->wp->tty));
        return (NULL);
}

/* Callback for pane_unzoomed_height. */
static void *
format_cb_pane_unzoomed_height(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;
        struct window           *w;
        struct layout_cell      *lc, *root;
        int                      status, floating;
        u_int                    sy;

        if (wp == NULL)
                return (NULL);
        w = wp->window;

        lc = wp->saved_layout_cell;
        if (lc == NULL)
                lc = wp->layout_cell;
        if (lc == NULL)
                return (NULL);
        sy = lc->g.sy;
        floating = (lc->flags & LAYOUT_CELL_FLOATING);

        root = w->saved_layout_root;
        if (root == NULL)
                root = w->layout_root;
        if (lc == wp->saved_layout_cell && !floating)
                status = window_get_pane_status(w);
        else
                status = window_pane_get_pane_status(wp);
        if (!floating &&
            root != NULL &&
            layout_add_horizontal_border(root, lc, status) &&
            sy > 1)
                sy--;

        return (format_printf("%u", sy));
}

/* Callback for pane_unzoomed_width. */
static void *
format_cb_pane_unzoomed_width(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;
        struct layout_cell      *lc;
        int                      sb_w, sb_pad;
        u_int                    sx;

        if (wp == NULL)
                return (NULL);

        lc = wp->saved_layout_cell;
        if (lc == NULL)
                lc = wp->layout_cell;
        if (lc == NULL)
                return (NULL);
        sx = lc->g.sx;

        if (window_pane_scrollbar_reserve(wp)) {
                sb_w = wp->scrollbar_style.width;
                sb_pad = wp->scrollbar_style.pad;
                if (sb_w < 1)
                        sb_w = 1;
                if (sb_pad < 0)
                        sb_pad = 0;
                if ((int)sx - sb_w - sb_pad < PANE_MINIMUM)
                        sx = PANE_MINIMUM;
                else
                        sx -= sb_w + sb_pad;
        }

        return (format_printf("%u", sx));
}

/* Callback for pane_width. */
static void *
format_cb_pane_width(struct format_tree *ft)
{
        if (ft->wp != NULL)
                return (format_printf("%u", ft->wp->sx));
        return (NULL);
}

/* Callback for pane_x. */
static void *
format_cb_pane_x(struct format_tree *ft)
{
        if (ft->wp != NULL)
                return (format_printf("%d", ft->wp->xoff));
        return (NULL);
}

/* Callback for pane_y. */
static void *
format_cb_pane_y(struct format_tree *ft)
{
        if (ft->wp != NULL)
                return (format_printf("%d", ft->wp->yoff));
        return (NULL);
}

/* Callback for pane_z. */
static void *
format_cb_pane_z(struct format_tree *ft)
{
        u_int   idx;

        if (ft->wp != NULL && window_pane_zindex(ft->wp, &idx) == 0)
                return (format_printf("%u", idx));
        return (NULL);
}

/* Callback for pane_zoomed_flag. */
static void *
format_cb_pane_zoomed_flag(struct format_tree *ft)
{
        struct window_pane      *wp = ft->wp;

        if (wp != NULL) {
                if (wp->flags & PANE_ZOOMED)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for scroll_region_lower. */
static void *
format_cb_scroll_region_lower(struct format_tree *ft)
{
        if (ft->wp != NULL)
                return (format_printf("%u", ft->wp->base.rlower));
        return (NULL);
}

/* Callback for scroll_region_upper. */
static void *
format_cb_scroll_region_upper(struct format_tree *ft)
{
        if (ft->wp != NULL)
                return (format_printf("%u", ft->wp->base.rupper));
        return (NULL);
}

/* Callback for server_sessions. */
static void *
format_cb_server_sessions(__unused struct format_tree *ft)
{
        struct session  *s;
        u_int            n = 0;

        RB_FOREACH(s, sessions, &sessions)
                n++;
        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)
{
        if (ft->s != NULL)
                return (format_printf("%u", ft->s->attached));
        return (NULL);
}

/* Callback for session_format. */
static void *
format_cb_session_format(struct format_tree *ft)
{
        if (ft->type == FORMAT_TYPE_SESSION)
                return (xstrdup("1"));
        return (xstrdup("0"));
}

/* Callback for session_group. */
static void *
format_cb_session_group(struct format_tree *ft)
{
        struct session_group    *sg;

        if (ft->s != NULL && (sg = session_group_contains(ft->s)) != NULL)
                return (xstrdup(sg->name));
        return (NULL);
}

/* Callback for session_group_attached. */
static void *
format_cb_session_group_attached(struct format_tree *ft)
{
        struct session_group    *sg;

        if (ft->s != NULL && (sg = session_group_contains(ft->s)) != NULL)
                return (format_printf("%u", session_group_attached_count (sg)));
        return (NULL);
}

/* Callback for session_group_many_attached. */
static void *
format_cb_session_group_many_attached(struct format_tree *ft)
{
        struct session_group    *sg;

        if (ft->s != NULL && (sg = session_group_contains(ft->s)) != NULL) {
                if (session_group_attached_count (sg) > 1)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for session_group_size. */
static void *
format_cb_session_group_size(struct format_tree *ft)
{
        struct session_group    *sg;

        if (ft->s != NULL && (sg = session_group_contains(ft->s)) != NULL)
                return (format_printf("%u", session_group_count (sg)));
        return (NULL);
}

/* Callback for session_grouped. */
static void *
format_cb_session_grouped(struct format_tree *ft)
{
        if (ft->s != NULL) {
                if (session_group_contains(ft->s) != NULL)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for session_id. */
static void *
format_cb_session_id(struct format_tree *ft)
{
        if (ft->s != NULL)
                return (format_printf("$%u", ft->s->id));
        return (NULL);
}

/* Callback for session_many_attached. */
static void *
format_cb_session_many_attached(struct format_tree *ft)
{
        if (ft->s != NULL) {
                if (ft->s->attached > 1)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for session_marked. */
static void *
format_cb_session_marked(struct format_tree *ft)
{
        if (ft->s != NULL) {
                if (server_check_marked() && marked_pane.s == ft->s)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for session_name. */
static void *
format_cb_session_name(struct format_tree *ft)
{
        if (ft->s != NULL)
                return (xstrdup(ft->s->name));
        return (NULL);
}

/* Callback for session_path. */
static void *
format_cb_session_path(struct format_tree *ft)
{
        if (ft->s != NULL)
                return (xstrdup(ft->s->cwd));
        return (NULL);
}

/* Callback for session_windows. */
static void *
format_cb_session_windows(struct format_tree *ft)
{
        if (ft->s != NULL)
                return (format_printf("%u", winlink_count(&ft->s->windows)));
        return (NULL);
}

/* Callback for socket_path. */
static void *
format_cb_socket_path(__unused struct format_tree *ft)
{
        return (xstrdup(socket_path));
}

/* Callback for version. */
static void *
format_cb_version(__unused struct format_tree *ft)
{
        return (xstrdup(getversion()));
}

/* Callback for sixel_support. */
static void *
format_cb_sixel_support(__unused struct format_tree *ft)
{
        return (xstrdup("0"));
}

/* Callback for active_window_index. */
static void *
format_cb_active_window_index(struct format_tree *ft)
{
        if (ft->s != NULL)
                return (format_printf("%u", ft->s->curw->idx));
        return (NULL);
}

/* Callback for last_window_index. */
static void *
format_cb_last_window_index(struct format_tree *ft)
{
        struct winlink  *wl;

        if (ft->s != NULL) {
                wl = RB_MAX(winlinks, &ft->s->windows);
                return (format_printf("%u", wl->idx));
        }
        return (NULL);
}

/* Callback for window_active. */
static void *
format_cb_window_active(struct format_tree *ft)
{
        if (ft->wl != NULL) {
                if (ft->wl == ft->wl->session->curw)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for window_activity_flag. */
static void *
format_cb_window_activity_flag(struct format_tree *ft)
{
        if (ft->wl != NULL) {
                if (ft->wl->flags & WINLINK_ACTIVITY)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for window_bell_flag. */
static void *
format_cb_window_bell_flag(struct format_tree *ft)
{
        if (ft->wl != NULL) {
                if (ft->wl->flags & WINLINK_BELL)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for window_bigger. */
static void *
format_cb_window_bigger(struct format_tree *ft)
{
        u_int   ox, oy, sx, sy;

        if (ft->c != NULL) {
                if (tty_window_offset(&ft->c->tty, &ox, &oy, &sx, &sy))
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for window_cell_height. */
static void *
format_cb_window_cell_height(struct format_tree *ft)
{
        if (ft->w != NULL)
                return (format_printf("%u", ft->w->ypixel));
        return (NULL);
}

/* Callback for window_cell_width. */
static void *
format_cb_window_cell_width(struct format_tree *ft)
{
        if (ft->w != NULL)
                return (format_printf("%u", ft->w->xpixel));
        return (NULL);
}

/* Callback for window_end_flag. */
static void *
format_cb_window_end_flag(struct format_tree *ft)
{
        if (ft->wl != NULL) {
                if (ft->wl == RB_MAX(winlinks, &ft->wl->session->windows))
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for window_flags. */
static void *
format_cb_window_flags(struct format_tree *ft)
{
        if (ft->wl != NULL)
                return (xstrdup(window_printable_flags(ft->wl, 1)));
        return (NULL);
}

/* Callback for window_format. */
static void *
format_cb_window_format(struct format_tree *ft)
{
        if (ft->type == FORMAT_TYPE_WINDOW)
                return (xstrdup("1"));
        return (xstrdup("0"));
}

/* Callback for window_height. */
static void *
format_cb_window_height(struct format_tree *ft)
{
        if (ft->w != NULL)
                return (format_printf("%u", ft->w->sy));
        return (NULL);
}

/* Callback for window_manual_height. */
static void *
format_cb_window_manual_height(struct format_tree *ft)
{
        struct window   *w = ft->w;

        if (w == NULL)
                return (NULL);
        if (options_get_number(w->options, "window-size") != WINDOW_SIZE_MANUAL)
                return (xstrdup(""));
        return (format_printf("%u", w->manual_sy));
}

/* Callback for window_id. */
static void *
format_cb_window_id(struct format_tree *ft)
{
        if (ft->w != NULL)
                return (format_printf("@%u", ft->w->id));
        return (NULL);
}

/* Callback for window_index. */
static void *
format_cb_window_index(struct format_tree *ft)
{
        if (ft->wl != NULL)
                return (format_printf("%d", ft->wl->idx));
        return (NULL);
}

/* Callback for window_last_flag. */
static void *
format_cb_window_last_flag(struct format_tree *ft)
{
        if (ft->wl != NULL) {
                if (ft->wl == TAILQ_FIRST(&ft->wl->session->lastw))
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for window_linked. */
static void *
format_cb_window_linked(struct format_tree *ft)
{
        struct winlink  *wl;
        struct session  *s;
        int              found = 0;

        if (ft->wl != NULL) {
                RB_FOREACH(s, sessions, &sessions) {
                        RB_FOREACH(wl, winlinks, &s->windows) {
                                if (wl->window == ft->wl->window) {
                                        if (found)
                                                return (xstrdup("1"));
                                        found = 1;
                                }
                        }
                }
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for window_linked_sessions. */
static void *
format_cb_window_linked_sessions(struct format_tree *ft)
{
        struct window           *w;
        struct session_group    *sg;
        struct session          *s;
        u_int                    n = 0;

        if (ft->wl == NULL)
                return (NULL);
        w = ft->wl->window;

        RB_FOREACH(sg, session_groups, &session_groups) {
                s = TAILQ_FIRST(&sg->sessions);
                if (winlink_find_by_window(&s->windows, w) != NULL)
                        n++;
        }
        RB_FOREACH(s, sessions, &sessions) {
                if (session_group_contains(s) != NULL)
                        continue;
                if (winlink_find_by_window(&s->windows, w) != NULL)
                        n++;
        }
        return (format_printf("%u", n));
}

/* Callback for window_marked_flag. */
static void *
format_cb_window_marked_flag(struct format_tree *ft)
{
        if (ft->wl != NULL) {
                if (server_check_marked() && marked_pane.wl == ft->wl)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for window_modal_pane. */
static void *
format_cb_window_modal_pane(struct format_tree *ft)
{
        if (ft->w != NULL && ft->w->modal != NULL)
                return (format_printf("%%%u", ft->w->modal->id));
        return (NULL);
}

/* Callback for window_name. */
static void *
format_cb_window_name(struct format_tree *ft)
{
        if (ft->w != NULL)
                return (format_printf("%s", ft->w->name));
        return (NULL);
}

/* Callback for window_offset_x. */
static void *
format_cb_window_offset_x(struct format_tree *ft)
{
        u_int   ox, oy, sx, sy;

        if (ft->c != NULL) {
                if (tty_window_offset(&ft->c->tty, &ox, &oy, &sx, &sy))
                        return (format_printf("%u", ox));
                return (NULL);
        }
        return (NULL);
}

/* Callback for window_offset_y. */
static void *
format_cb_window_offset_y(struct format_tree *ft)
{
        u_int   ox, oy, sx, sy;

        if (ft->c != NULL) {
                if (tty_window_offset(&ft->c->tty, &ox, &oy, &sx, &sy))
                        return (format_printf("%u", oy));
                return (NULL);
        }
        return (NULL);
}

/* Callback for window_panes. */
static void *
format_cb_window_panes(struct format_tree *ft)
{
        if (ft->w != NULL)
                return (format_printf("%u", window_count_panes(ft->w, 1)));
        return (NULL);
}

/* Callback for window_raw_flags. */
static void *
format_cb_window_raw_flags(struct format_tree *ft)
{
        if (ft->wl != NULL)
                return (xstrdup(window_printable_flags(ft->wl, 0)));
        return (NULL);
}

/* Callback for window_silence_flag. */
static void *
format_cb_window_silence_flag(struct format_tree *ft)
{
        if (ft->wl != NULL) {
                if (ft->wl->flags & WINLINK_SILENCE)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for window_start_flag. */
static void *
format_cb_window_start_flag(struct format_tree *ft)
{
        if (ft->wl != NULL) {
                if (ft->wl == RB_MIN(winlinks, &ft->wl->session->windows))
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for window_width. */
static void *
format_cb_window_width(struct format_tree *ft)
{
        if (ft->w != NULL)
                return (format_printf("%u", ft->w->sx));
        return (NULL);
}

/* Callback for window_manual_width. */
static void *
format_cb_window_manual_width(struct format_tree *ft)
{
        struct window   *w = ft->w;

        if (w == NULL)
                return (NULL);
        if (options_get_number(w->options, "window-size") != WINDOW_SIZE_MANUAL)
                return (xstrdup(""));
        return (format_printf("%u", w->manual_sx));
}

/* Callback for window_zoomed_flag. */
static void *
format_cb_window_zoomed_flag(struct format_tree *ft)
{
        if (ft->w != NULL) {
                if (ft->w->flags & WINDOW_ZOOMED)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for wrap_flag. */
static void *
format_cb_wrap_flag(struct format_tree *ft)
{
        if (ft->wp != NULL) {
                if (ft->wp->base.mode & MODE_WRAP)
                        return (xstrdup("1"));
                return (xstrdup("0"));
        }
        return (NULL);
}

/* Callback for buffer_created. */
static void *
format_cb_buffer_created(struct format_tree *ft)
{
        static struct timeval    tv;

        if (ft->pb != NULL) {
                timerclear(&tv);
                tv.tv_sec = paste_buffer_created(ft->pb);
                return (&tv);
        }
        return (NULL);
}

/* Callback for client_activity. */
static void *
format_cb_client_activity(struct format_tree *ft)
{
        if (ft->c != NULL)
                return (&ft->c->activity_time);
        return (NULL);
}

/* Callback for client_created. */
static void *
format_cb_client_created(struct format_tree *ft)
{
        if (ft->c != NULL)
                return (&ft->c->creation_time);
        return (NULL);
}

/* Callback for session_activity. */
static void *
format_cb_session_activity(struct format_tree *ft)
{
        if (ft->s != NULL)
                return (&ft->s->activity_time);
        return (NULL);
}

/* Callback for session_created. */
static void *
format_cb_session_created(struct format_tree *ft)
{
        if (ft->s != NULL)
                return (&ft->s->creation_time);
        return (NULL);
}

/* Callback for session_last_attached. */
static void *
format_cb_session_last_attached(struct format_tree *ft)
{
        if (ft->s != NULL)
                return (&ft->s->last_attached_time);
        return (NULL);
}

/* Callback for start_time. */
static void *
format_cb_start_time(__unused struct format_tree *ft)
{
        return (&start_time);
}

/* Callback for window_activity. */
static void *
format_cb_window_activity(struct format_tree *ft)
{
        if (ft->w != NULL)
                return (&ft->w->activity_time);
        return (NULL);
}

/* Callback for buffer_mode_format, */
static void *
format_cb_buffer_mode_format(__unused struct format_tree *ft)
{
        return (xstrdup(window_buffer_mode.default_format));
}

/* Callback for client_mode_format, */
static void *
format_cb_client_mode_format(__unused struct format_tree *ft)
{
        return (xstrdup(window_client_mode.default_format));
}

/* Callback for tree_mode_format, */
static void *
format_cb_tree_mode_format(__unused struct format_tree *ft)
{
        return (xstrdup(window_tree_mode.default_format));
}

/* Callback for uid. */
static void *
format_cb_uid(__unused struct format_tree *ft)
{
        return (format_printf("%ld", (long)getuid()));
}

/* Callback for user. */
static void *
format_cb_user(__unused struct format_tree *ft)
{
        static char     *cached;
        struct passwd   *pw;

        if (cached == NULL && (pw = getpwuid(getuid())) != NULL)
                cached = xstrdup(pw->pw_name);
        if (cached != NULL)
                return (xstrdup(cached));
        return (NULL);
}

/* Format table type. */
enum format_table_type {
        FORMAT_TABLE_STRING,
        FORMAT_TABLE_TIME
};

/* Format table entry. */
struct format_table_entry {
        const char              *key;
        enum format_table_type   type;
        format_cb                cb;
};

/*
 * Format table. Default format variables (that are almost always in the tree
 * and where the value is expanded by a callback in this file) are listed here.
 * Only variables which are added by the caller go into the tree.
 */
static const struct format_table_entry format_table[] = {
        { "active_window_index", FORMAT_TABLE_STRING,
          format_cb_active_window_index
        },
        { "alternate_on", FORMAT_TABLE_STRING,
          format_cb_alternate_on
        },
        { "alternate_saved_x", FORMAT_TABLE_STRING,
          format_cb_alternate_saved_x
        },
        { "alternate_saved_y", FORMAT_TABLE_STRING,
          format_cb_alternate_saved_y
        },
        { "bracket_paste_flag", FORMAT_TABLE_STRING,
          format_cb_bracket_paste_flag
        },
        { "buffer_created", FORMAT_TABLE_TIME,
          format_cb_buffer_created
        },
        { "buffer_full", FORMAT_TABLE_STRING,
          format_cb_buffer_full
        },
        { "buffer_mode_format", FORMAT_TABLE_STRING,
          format_cb_buffer_mode_format
        },
        { "buffer_name", FORMAT_TABLE_STRING,
          format_cb_buffer_name
        },
        { "buffer_sample", FORMAT_TABLE_STRING,
          format_cb_buffer_sample
        },
        { "buffer_size", FORMAT_TABLE_STRING,
          format_cb_buffer_size
        },
        { "client_activity", FORMAT_TABLE_TIME,
          format_cb_client_activity
        },
        { "client_cell_height", FORMAT_TABLE_STRING,
          format_cb_client_cell_height
        },
        { "client_cell_width", FORMAT_TABLE_STRING,
          format_cb_client_cell_width
        },
        { "client_colours", FORMAT_TABLE_STRING,
          format_cb_client_colours
        },
        { "client_control_mode", FORMAT_TABLE_STRING,
          format_cb_client_control_mode
        },
        { "client_created", FORMAT_TABLE_TIME,
          format_cb_client_created
        },
        { "client_discarded", FORMAT_TABLE_STRING,
          format_cb_client_discarded
        },
        { "client_flags", FORMAT_TABLE_STRING,
          format_cb_client_flags
        },
        { "client_height", FORMAT_TABLE_STRING,
          format_cb_client_height
        },
        { "client_key_table", FORMAT_TABLE_STRING,
          format_cb_client_key_table
        },
        { "client_last_session", FORMAT_TABLE_STRING,
          format_cb_client_last_session
        },
        { "client_mode_format", FORMAT_TABLE_STRING,
          format_cb_client_mode_format
        },
        { "client_name", FORMAT_TABLE_STRING,
          format_cb_client_name
        },
        { "client_pid", FORMAT_TABLE_STRING,
          format_cb_client_pid
        },
        { "client_prefix", FORMAT_TABLE_STRING,
          format_cb_client_prefix
        },
        { "client_readonly", FORMAT_TABLE_STRING,
          format_cb_client_readonly
        },
        { "client_session", FORMAT_TABLE_STRING,
          format_cb_client_session
        },
        { "client_termfeatures", FORMAT_TABLE_STRING,
          format_cb_client_termfeatures
        },
        { "client_termname", FORMAT_TABLE_STRING,
          format_cb_client_termname
        },
        { "client_termtype", FORMAT_TABLE_STRING,
          format_cb_client_termtype
        },
        { "client_theme", FORMAT_TABLE_STRING,
          format_cb_client_theme
        },
        { "client_tty", FORMAT_TABLE_STRING,
          format_cb_client_tty
        },
        { "client_uid", FORMAT_TABLE_STRING,
          format_cb_client_uid
        },
        { "client_user", FORMAT_TABLE_STRING,
          format_cb_client_user
        },
        { "client_utf8", FORMAT_TABLE_STRING,
          format_cb_client_utf8
        },
        { "client_width", FORMAT_TABLE_STRING,
          format_cb_client_width
        },
        { "client_written", FORMAT_TABLE_STRING,
          format_cb_client_written
        },
        { "config_files", FORMAT_TABLE_STRING,
          format_cb_config_files
        },
        { "cursor_blinking", FORMAT_TABLE_STRING,
          format_cb_cursor_blinking
        },
        { "cursor_character", FORMAT_TABLE_STRING,
          format_cb_cursor_character
        },
        { "cursor_colour", FORMAT_TABLE_STRING,
          format_cb_cursor_colour
        },
        { "cursor_flag", FORMAT_TABLE_STRING,
          format_cb_cursor_flag
        },
        { "cursor_shape", FORMAT_TABLE_STRING,
          format_cb_cursor_shape
        },
        { "cursor_very_visible", FORMAT_TABLE_STRING,
          format_cb_cursor_very_visible
        },
        { "cursor_x", FORMAT_TABLE_STRING,
          format_cb_cursor_x
        },
        { "cursor_y", FORMAT_TABLE_STRING,
          format_cb_cursor_y
        },
        { "history_all_bytes", FORMAT_TABLE_STRING,
          format_cb_history_all_bytes
        },
        { "history_bytes", FORMAT_TABLE_STRING,
          format_cb_history_bytes
        },
        { "history_limit", FORMAT_TABLE_STRING,
          format_cb_history_limit
        },
        { "history_size", FORMAT_TABLE_STRING,
          format_cb_history_size
        },
        { "host", FORMAT_TABLE_STRING,
          format_cb_host
        },
        { "host_short", FORMAT_TABLE_STRING,
          format_cb_host_short
        },
        { "insert_flag", FORMAT_TABLE_STRING,
          format_cb_insert_flag
        },
        { "keypad_cursor_flag", FORMAT_TABLE_STRING,
          format_cb_keypad_cursor_flag
        },
        { "keypad_flag", FORMAT_TABLE_STRING,
          format_cb_keypad_flag
        },
        { "last_window_index", FORMAT_TABLE_STRING,
          format_cb_last_window_index
        },
        { "mouse_all_flag", FORMAT_TABLE_STRING,
          format_cb_mouse_all_flag
        },
        { "mouse_any_flag", FORMAT_TABLE_STRING,
          format_cb_mouse_any_flag
        },
        { "mouse_button_flag", FORMAT_TABLE_STRING,
          format_cb_mouse_button_flag
        },
        { "mouse_hyperlink", FORMAT_TABLE_STRING,
          format_cb_mouse_hyperlink
        },
        { "mouse_line", FORMAT_TABLE_STRING,
          format_cb_mouse_line
        },
        { "mouse_pane", FORMAT_TABLE_STRING,
          format_cb_mouse_pane
        },
        { "mouse_sgr_flag", FORMAT_TABLE_STRING,
          format_cb_mouse_sgr_flag
        },
        { "mouse_standard_flag", FORMAT_TABLE_STRING,
          format_cb_mouse_standard_flag
        },
        { "mouse_status_line", FORMAT_TABLE_STRING,
          format_cb_mouse_status_line
        },
        { "mouse_status_range", FORMAT_TABLE_STRING,
          format_cb_mouse_status_range
        },
        { "mouse_utf8_flag", FORMAT_TABLE_STRING,
          format_cb_mouse_utf8_flag
        },
        { "mouse_word", FORMAT_TABLE_STRING,
          format_cb_mouse_word
        },
        { "mouse_x", FORMAT_TABLE_STRING,
          format_cb_mouse_x
        },
        { "mouse_y", FORMAT_TABLE_STRING,
          format_cb_mouse_y
        },
        { "next_session_id", FORMAT_TABLE_STRING,
          format_cb_next_session_id
        },
        { "origin_flag", FORMAT_TABLE_STRING,
          format_cb_origin_flag
        },
        { "pane_active", FORMAT_TABLE_STRING,
          format_cb_pane_active
        },
        { "pane_at_bottom", FORMAT_TABLE_STRING,
          format_cb_pane_at_bottom
        },
        { "pane_at_left", FORMAT_TABLE_STRING,
          format_cb_pane_at_left
        },
        { "pane_at_right", FORMAT_TABLE_STRING,
          format_cb_pane_at_right
        },
        { "pane_at_top", FORMAT_TABLE_STRING,
          format_cb_pane_at_top
        },
        { "pane_bg", FORMAT_TABLE_STRING,
          format_cb_pane_bg
        },
        { "pane_bottom", FORMAT_TABLE_STRING,
          format_cb_pane_bottom
        },
        { "pane_command_duration", FORMAT_TABLE_STRING,
          format_cb_pane_command_duration
        },
        { "pane_command_end_time", FORMAT_TABLE_TIME,
          format_cb_pane_command_end_time
        },
        { "pane_command_running", FORMAT_TABLE_STRING,
          format_cb_pane_command_running
        },
        { "pane_command_start_time", FORMAT_TABLE_TIME,
          format_cb_pane_command_start_time
        },
        { "pane_command_status", FORMAT_TABLE_STRING,
          format_cb_pane_command_status
        },
        { "pane_current_command", FORMAT_TABLE_STRING,
          format_cb_current_command
        },
        { "pane_current_path", FORMAT_TABLE_STRING,
          format_cb_current_path
        },
        { "pane_dead", FORMAT_TABLE_STRING,
          format_cb_pane_dead
        },
        { "pane_dead_signal", FORMAT_TABLE_STRING,
          format_cb_pane_dead_signal
        },
        { "pane_dead_status", FORMAT_TABLE_STRING,
          format_cb_pane_dead_status
        },
        { "pane_dead_time", FORMAT_TABLE_TIME,
          format_cb_pane_dead_time
        },
        { "pane_fg", FORMAT_TABLE_STRING,
          format_cb_pane_fg
        },
        { "pane_flags", FORMAT_TABLE_STRING,
          format_cb_pane_flags
        },
        { "pane_floating_flag", FORMAT_TABLE_STRING,
          format_cb_pane_floating_flag
        },
        { "pane_format", FORMAT_TABLE_STRING,
          format_cb_pane_format
        },
        { "pane_height", FORMAT_TABLE_STRING,
          format_cb_pane_height
        },
        { "pane_id", FORMAT_TABLE_STRING,
          format_cb_pane_id
        },
        { "pane_in_mode", FORMAT_TABLE_STRING,
          format_cb_pane_in_mode
        },
        { "pane_index", FORMAT_TABLE_STRING,
          format_cb_pane_index
        },
        { "pane_input_off", FORMAT_TABLE_STRING,
          format_cb_pane_input_off
        },
        { "pane_key_mode", FORMAT_TABLE_STRING,
          format_cb_pane_key_mode
        },
        { "pane_last", FORMAT_TABLE_STRING,
          format_cb_pane_last
        },
        { "pane_last_output_time", FORMAT_TABLE_TIME,
          format_cb_pane_last_output_time
        },
        { "pane_last_prompt_time", FORMAT_TABLE_TIME,
          format_cb_pane_last_prompt_time
        },
        { "pane_left", FORMAT_TABLE_STRING,
          format_cb_pane_left
        },
        { "pane_marked", FORMAT_TABLE_STRING,
          format_cb_pane_marked
        },
        { "pane_marked_set", FORMAT_TABLE_STRING,
          format_cb_pane_marked_set
        },
        { "pane_modal_flag", FORMAT_TABLE_STRING,
          format_cb_pane_modal_flag
        },
        { "pane_mode", FORMAT_TABLE_STRING,
          format_cb_pane_mode
        },
        { "pane_path", FORMAT_TABLE_STRING,
          format_cb_pane_path
        },
        { "pane_pb_progress", FORMAT_TABLE_STRING,
          format_cb_pane_pb_progress
        },
        { "pane_pb_state", FORMAT_TABLE_STRING,
          format_cb_pane_pb_state
        },
        { "pane_pid", FORMAT_TABLE_STRING,
          format_cb_pane_pid
        },
        { "pane_pipe", FORMAT_TABLE_STRING,
          format_cb_pane_pipe
        },
        { "pane_pipe_pid", FORMAT_TABLE_STRING,
          format_cb_pane_pipe_pid
        },
        { "pane_right", FORMAT_TABLE_STRING,
          format_cb_pane_right
        },
        { "pane_search_string", FORMAT_TABLE_STRING,
          format_cb_pane_search_string
        },
        { "pane_start_command", FORMAT_TABLE_STRING,
          format_cb_start_command
        },
        { "pane_start_command_list", FORMAT_TABLE_STRING,
          format_cb_start_command_list
        },
        { "pane_start_path", FORMAT_TABLE_STRING,
          format_cb_start_path
        },
        { "pane_synchronized", FORMAT_TABLE_STRING,
          format_cb_pane_synchronized
        },
        { "pane_tabs", FORMAT_TABLE_STRING,
          format_cb_pane_tabs
        },
        { "pane_title", FORMAT_TABLE_STRING,
          format_cb_pane_title
        },
        { "pane_top", FORMAT_TABLE_STRING,
          format_cb_pane_top
        },
        { "pane_tty", FORMAT_TABLE_STRING,
          format_cb_pane_tty
        },
        { "pane_unseen_changes", FORMAT_TABLE_STRING,
          format_cb_pane_unseen_changes
        },
        { "pane_unzoomed_height", FORMAT_TABLE_STRING,
          format_cb_pane_unzoomed_height
        },
        { "pane_unzoomed_width", FORMAT_TABLE_STRING,
          format_cb_pane_unzoomed_width
        },
        { "pane_width", FORMAT_TABLE_STRING,
          format_cb_pane_width
        },
        { "pane_x", FORMAT_TABLE_STRING,
          format_cb_pane_x
        },
        { "pane_y", FORMAT_TABLE_STRING,
          format_cb_pane_y
        },
        { "pane_z", FORMAT_TABLE_STRING,
          format_cb_pane_z
        },
        { "pane_zoomed_flag", FORMAT_TABLE_STRING,
          format_cb_pane_zoomed_flag
        },
        { "pid", FORMAT_TABLE_STRING,
          format_cb_pid
        },
        { "scroll_region_lower", FORMAT_TABLE_STRING,
          format_cb_scroll_region_lower
        },
        { "scroll_region_upper", FORMAT_TABLE_STRING,
          format_cb_scroll_region_upper
        },
        { "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
        },
        { "session_attached", FORMAT_TABLE_STRING,
          format_cb_session_attached
        },
        { "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
        },
        { "session_format", FORMAT_TABLE_STRING,
          format_cb_session_format
        },
        { "session_group", FORMAT_TABLE_STRING,
          format_cb_session_group
        },
        { "session_group_attached", FORMAT_TABLE_STRING,
          format_cb_session_group_attached
        },
        { "session_group_attached_list", FORMAT_TABLE_STRING,
          format_cb_session_group_attached_list
        },
        { "session_group_list", FORMAT_TABLE_STRING,
          format_cb_session_group_list
        },
        { "session_group_many_attached", FORMAT_TABLE_STRING,
          format_cb_session_group_many_attached
        },
        { "session_group_size", FORMAT_TABLE_STRING,
          format_cb_session_group_size
        },
        { "session_grouped", FORMAT_TABLE_STRING,
          format_cb_session_grouped
        },
        { "session_id", FORMAT_TABLE_STRING,
          format_cb_session_id
        },
        { "session_last_attached", FORMAT_TABLE_TIME,
          format_cb_session_last_attached
        },
        { "session_many_attached", FORMAT_TABLE_STRING,
          format_cb_session_many_attached
        },
        { "session_marked", FORMAT_TABLE_STRING,
          format_cb_session_marked,
        },
        { "session_name", FORMAT_TABLE_STRING,
          format_cb_session_name
        },
        { "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
        },
        { "session_windows", FORMAT_TABLE_STRING,
          format_cb_session_windows
        },
        { "sixel_support", FORMAT_TABLE_STRING,
          format_cb_sixel_support
        },
        { "socket_path", FORMAT_TABLE_STRING,
          format_cb_socket_path
        },
        { "start_time", FORMAT_TABLE_TIME,
          format_cb_start_time
        },
        { "synchronized_output_flag", FORMAT_TABLE_STRING,
          format_cb_synchronized_output_flag
        },
        { "tree_mode_format", FORMAT_TABLE_STRING,
          format_cb_tree_mode_format
        },
        { "uid", FORMAT_TABLE_STRING,
          format_cb_uid
        },
        { "user", FORMAT_TABLE_STRING,
          format_cb_user
        },
        { "version", FORMAT_TABLE_STRING,
          format_cb_version
        },
        { "window_active", FORMAT_TABLE_STRING,
          format_cb_window_active
        },
        { "window_active_clients", FORMAT_TABLE_STRING,
          format_cb_window_active_clients
        },
        { "window_active_clients_list", FORMAT_TABLE_STRING,
          format_cb_window_active_clients_list
        },
        { "window_active_sessions", FORMAT_TABLE_STRING,
          format_cb_window_active_sessions
        },
        { "window_active_sessions_list", FORMAT_TABLE_STRING,
          format_cb_window_active_sessions_list
        },
        { "window_activity", FORMAT_TABLE_TIME,
          format_cb_window_activity
        },
        { "window_activity_flag", FORMAT_TABLE_STRING,
          format_cb_window_activity_flag
        },
        { "window_bell_flag", FORMAT_TABLE_STRING,
          format_cb_window_bell_flag
        },
        { "window_bigger", FORMAT_TABLE_STRING,
          format_cb_window_bigger
        },
        { "window_cell_height", FORMAT_TABLE_STRING,
          format_cb_window_cell_height
        },
        { "window_cell_width", FORMAT_TABLE_STRING,
          format_cb_window_cell_width
        },
        { "window_end_flag", FORMAT_TABLE_STRING,
          format_cb_window_end_flag
        },
        { "window_flags", FORMAT_TABLE_STRING,
          format_cb_window_flags
        },
        { "window_format", FORMAT_TABLE_STRING,
          format_cb_window_format
        },
        { "window_height", FORMAT_TABLE_STRING,
          format_cb_window_height
        },
        { "window_id", FORMAT_TABLE_STRING,
          format_cb_window_id
        },
        { "window_index", FORMAT_TABLE_STRING,
          format_cb_window_index
        },
        { "window_last_flag", FORMAT_TABLE_STRING,
          format_cb_window_last_flag
        },
        { "window_layout", FORMAT_TABLE_STRING,
          format_cb_window_layout
        },
        { "window_linked", FORMAT_TABLE_STRING,
          format_cb_window_linked
        },
        { "window_linked_sessions", FORMAT_TABLE_STRING,
          format_cb_window_linked_sessions
        },
        { "window_linked_sessions_list", FORMAT_TABLE_STRING,
          format_cb_window_linked_sessions_list
        },
        { "window_manual_height", FORMAT_TABLE_STRING,
          format_cb_window_manual_height
        },
        { "window_manual_width", FORMAT_TABLE_STRING,
          format_cb_window_manual_width
        },
        { "window_marked_flag", FORMAT_TABLE_STRING,
          format_cb_window_marked_flag
        },
        { "window_modal_pane", FORMAT_TABLE_STRING,
          format_cb_window_modal_pane
        },
        { "window_name", FORMAT_TABLE_STRING,
          format_cb_window_name
        },
        { "window_offset_x", FORMAT_TABLE_STRING,
          format_cb_window_offset_x
        },
        { "window_offset_y", FORMAT_TABLE_STRING,
          format_cb_window_offset_y
        },
        { "window_panes", FORMAT_TABLE_STRING,
          format_cb_window_panes
        },
        { "window_raw_flags", FORMAT_TABLE_STRING,
          format_cb_window_raw_flags
        },
        { "window_silence_flag", FORMAT_TABLE_STRING,
          format_cb_window_silence_flag
        },
        { "window_stack_index", FORMAT_TABLE_STRING,
          format_cb_window_stack_index
        },
        { "window_start_flag", FORMAT_TABLE_STRING,
          format_cb_window_start_flag
        },
        { "window_visible_layout", FORMAT_TABLE_STRING,
          format_cb_window_visible_layout
        },
        { "window_width", FORMAT_TABLE_STRING,
          format_cb_window_width
        },
        { "window_zoomed_flag", FORMAT_TABLE_STRING,
          format_cb_window_zoomed_flag
        },
        { "wrap_flag", FORMAT_TABLE_STRING,
          format_cb_wrap_flag
        }
};

/* Compare format table entries. */
static int
format_table_compare(const void *key0, const void *entry0)
{
        const char                      *key = key0;
        const struct format_table_entry *entry = entry0;

        return (strcmp(key, entry->key));
}

/* Get a format callback. */
static const struct format_table_entry *
format_table_get(const char *key)
{
        return (bsearch(key, format_table, nitems(format_table),
            sizeof *format_table, format_table_compare));
}

/* Merge one format tree into another. */
void
format_merge(struct format_tree *ft, struct format_tree *from)
{
        struct format_entry     *fe;

        RB_FOREACH(fe, format_entry_tree, &from->tree) {
                if (fe->value != NULL)
                        format_add(ft, fe->key, "%s", fe->value);
        }
}

/* Get format pane. */
struct window_pane *
format_get_pane(struct format_tree *ft)
{
        return (ft->wp);
}

/* Add item bits to tree. */
static void
format_create_add_item(struct format_tree *ft, struct cmdq_item *item)
{
        struct key_event        *event = cmdq_get_event(item);
        struct mouse_event      *m = &event->m;

        cmdq_merge_formats(item, ft);
        memcpy(&ft->m, m, sizeof ft->m);
}

/* Create a new tree. */
struct format_tree *
format_create(struct client *c, struct cmdq_item *item, int tag, int flags)
{
        struct format_tree      *ft;

        ft = xcalloc(1, sizeof *ft);
        RB_INIT(&ft->tree);

        if (c != NULL) {
                ft->client = c;
                ft->client->references++;
        }
        ft->item = item;

        ft->tag = tag;
        ft->flags = flags;

        if (item != NULL)
                format_create_add_item(ft, item);

        return (ft);
}

/* Free a tree. */
void
format_free(struct format_tree *ft)
{
        struct format_entry     *fe, *fe1;

        RB_FOREACH_SAFE(fe, format_entry_tree, &ft->tree, fe1) {
                RB_REMOVE(format_entry_tree, &ft->tree, fe);
                free(fe->value);
                free(fe->key);
                free(fe);
        }

        if (ft->client != NULL)
                server_client_unref(ft->client);
        free(ft);
}

/* Log each format. */
static void
format_log_debug_cb(const char *key, const char *value, void *arg)
{
        const char      *prefix = arg;

        log_debug("%s: %s=%s", prefix, key, value);
}

/* Log a format tree. */
void
format_log_debug(struct format_tree *ft, const char *prefix)
{
        format_each(ft, format_log_debug_cb, (void *)prefix);
}

/* Walk each format. */
void
format_each(struct format_tree *ft, void (*cb)(const char *, const char *,
    void *), void *arg)
{
        const struct format_table_entry *fte;
        struct format_entry             *fe;
        u_int                            i;
        char                             s[64];
        void                            *value;
        struct timeval                  *tv;

        for (i = 0; i < nitems(format_table); i++) {
                fte = &format_table[i];

                value = fte->cb(ft);
                if (value == NULL)
                        continue;
                if (fte->type == FORMAT_TABLE_TIME) {
                        tv = value;
                        xsnprintf(s, sizeof s, "%lld", (long long)tv->tv_sec);
                        cb(fte->key, s, arg);
                } else {
                        cb(fte->key, value, arg);
                        free(value);
                }
        }
        RB_FOREACH(fe, format_entry_tree, &ft->tree) {
                if (fe->time != 0) {
                        xsnprintf(s, sizeof s, "%lld", (long long)fe->time);
                        cb(fe->key, s, arg);
                } else {
                        if (fe->value == NULL && fe->cb != NULL) {
                                fe->value = fe->cb(ft);
                                if (fe->value == NULL)
                                        fe->value = xstrdup("");
                        }
                        cb(fe->key, fe->value, arg);
                }
        }
}

/* Add a key-value pair. */
void
format_add(struct format_tree *ft, const char *key, const char *fmt, ...)
{
        struct format_entry     *fe;
        struct format_entry     *fe_now;
        va_list                  ap;

        fe = xmalloc(sizeof *fe);
        fe->key = xstrdup(key);

        fe_now = RB_INSERT(format_entry_tree, &ft->tree, fe);
        if (fe_now != NULL) {
                free(fe->key);
                free(fe);
                free(fe_now->value);
                fe = fe_now;
        }

        fe->cb = NULL;
        fe->time = 0;

        va_start(ap, fmt);
        xvasprintf(&fe->value, fmt, ap);
        va_end(ap);
}

/* Add a key and time. */
void
format_add_tv(struct format_tree *ft, const char *key, struct timeval *tv)
{
        struct format_entry     *fe, *fe_now;

        fe = xmalloc(sizeof *fe);
        fe->key = xstrdup(key);

        fe_now = RB_INSERT(format_entry_tree, &ft->tree, fe);
        if (fe_now != NULL) {
                free(fe->key);
                free(fe);
                free(fe_now->value);
                fe = fe_now;
        }

        fe->cb = NULL;
        fe->time = tv->tv_sec;

        fe->value = NULL;
}

/* Add a key and function. */
void
format_add_cb(struct format_tree *ft, const char *key, format_cb cb)
{
        struct format_entry     *fe;
        struct format_entry     *fe_now;

        fe = xmalloc(sizeof *fe);
        fe->key = xstrdup(key);

        fe_now = RB_INSERT(format_entry_tree, &ft->tree, fe);
        if (fe_now != NULL) {
                free(fe->key);
                free(fe);
                free(fe_now->value);
                fe = fe_now;
        }

        fe->cb = cb;
        fe->time = 0;

        fe->value = NULL;
}

/* Quote shell special characters in string. */
static char *
format_quote_shell(const char *s)
{
        const char      *cp;
        char            *out, *at;

        at = out = xmalloc(strlen(s) * 2 + 1);
        for (cp = s; *cp != '\0'; cp++) {
                if (strchr("|&;<>()$`\\\"'*?[# =%\n\t", *cp) != NULL)
                        *at++ = '\\';
                *at++ = *cp;
        }
        *at = '\0';
        return (out);
}

/* Quote string with POSIX shell single quotes. */
static char *
format_quote_shell_single(const char *s)
{
        const char      *cp;
        char            *out, *at;

        at = out = xmalloc(strlen(s) * 4 + 3);
        *at++ = '\'';
        for (cp = s; *cp != '\0'; cp++) {
                if (*cp == '\'') {
                        *at++ = '\'';
                        *at++ = '\\';
                        *at++ = '\'';
                        *at++ = '\'';
                } else
                        *at++ = *cp;
        }
        *at++ = '\'';
        *at = '\0';
        return (out);
}

/* Quote #s in string. */
static char *
format_quote_style(const char *s)
{
        const char      *cp;
        char            *out, *at;

        at = out = xmalloc(strlen(s) * 2 + 1);
        for (cp = s; *cp != '\0'; cp++) {
                if (*cp == '#')
                        *at++ = '#';
                *at++ = *cp;
        }
        *at = '\0';
        return (out);
}

/* Make a prettier time. */
char *
format_pretty_time(time_t t, int seconds)
{
        struct tm       now_tm, tm;
        time_t          now, age;
        char            s[9];

        time(&now);
        if (now < t)
                now = t;
        age = now - t;

        localtime_r(&now, &now_tm);
        localtime_r(&t, &tm);

        /* Last 24 hours. */
        if (age < 24 * 3600) {
                if (seconds)
                        strftime(s, sizeof s, "%H:%M:%S", &tm);
                else
                        strftime(s, sizeof s, "%H:%M", &tm);
                return (xstrdup(s));
        }

        /* This month or last 28 days. */
        if ((tm.tm_year == now_tm.tm_year && tm.tm_mon == now_tm.tm_mon) ||
            age < 28 * 24 * 3600) {
                strftime(s, sizeof s, "%a%d", &tm);
                return (xstrdup(s));
        }

        /* Last 12 months. */
        if ((tm.tm_year == now_tm.tm_year && tm.tm_mon < now_tm.tm_mon) ||
            (tm.tm_year == now_tm.tm_year - 1 && tm.tm_mon > now_tm.tm_mon)) {
                strftime(s, sizeof s, "%d%b", &tm);
                return (xstrdup(s));
        }

        /* Older than that. */
        strftime(s, sizeof s, "%h%y", &tm);
        return (xstrdup(s));
}

/* Make a relative time. */
static char *
format_relative_time(time_t t)
{
        time_t  now, age;
        u_int   d, h, m, s;
        char    out[32];

        time(&now);
        if (t > now)
                return (NULL);
        if (t == now)
                return (xstrdup("0s"));
        age = now - t;

        d = age / 86400;
        h = (age % 86400) / 3600;
        m = (age % 3600) / 60;
        s = age % 60;

        if (d != 0) {
                if (h != 0)
                        xsnprintf(out, sizeof out, "%ud%uh", d, h);
                else
                        xsnprintf(out, sizeof out, "%ud", d);
        } else if (h != 0) {
                if (m != 0)
                        xsnprintf(out, sizeof out, "%uh%um", h, m);
                else
                        xsnprintf(out, sizeof out, "%uh", h);
        } else if (m != 0) {
                if (s != 0)
                        xsnprintf(out, sizeof out, "%um%us", m, s);
                else
                        xsnprintf(out, sizeof out, "%um", m);
        } else
                xsnprintf(out, sizeof out, "%us", s);
        return (xstrdup(out));
}

/* Make a time difference in seconds. */
static char *
format_time_difference(time_t t)
{
        time_t   now = time(NULL);
        char    *out;

        xasprintf(&out, "%ld", (long)now - (long)t);
        return (out);
}

/* Find a format entry. */
static char *
format_find(struct format_tree *ft, const char *key, uint64_t modifiers,
    const char *time_format)
{
        const struct format_table_entry *fte;
        void                            *value;
        struct format_entry             *fe, fe_find;
        struct environ_entry            *envent;
        struct options_entry            *o;
        char                            *found = NULL, *saved, s[512];
        char                            *array_key = NULL;
        const char                      *errstr;
        time_t                           t = 0;
        struct tm                        tm;

        o = options_parse_get(global_options, key, &array_key, 0);
        if (o == NULL && ft->wp != NULL)
                o = options_parse_get(ft->wp->options, key, &array_key, 0);
        if (o == NULL && ft->w != NULL)
                o = options_parse_get(ft->w->options, key, &array_key, 0);
        if (o == NULL)
                o = options_parse_get(global_w_options, key, &array_key, 0);
        if (o == NULL && ft->s != NULL)
                o = options_parse_get(ft->s->options, key, &array_key, 0);
        if (o == NULL)
                o = options_parse_get(global_s_options, key, &array_key, 0);
        if (o != NULL) {
                found = options_to_string(o, array_key, 1);
                free(array_key);
                goto found;
        }

        fte = format_table_get(key);
        if (fte != NULL) {
                value = fte->cb(ft);
                if (fte->type == FORMAT_TABLE_TIME && value != NULL)
                        t = ((struct timeval *)value)->tv_sec;
                else
                        found = value;
                goto found;
        }
        fe_find.key = (char *)key;
        fe = RB_FIND(format_entry_tree, &ft->tree, &fe_find);
        if (fe != NULL) {
                if (fe->time != 0) {
                        t = fe->time;
                        goto found;
                }
                if (fe->value == NULL && fe->cb != NULL) {
                        fe->value = fe->cb(ft);
                        if (fe->value == NULL)
                                fe->value = xstrdup("");
                }
                found = xstrdup(fe->value);
                goto found;
        }

        if (~modifiers & FORMAT_TIMESTRING) {
                envent = NULL;
                if (ft->s != NULL)
                        envent = environ_find(ft->s->environ, key);
                if (envent == NULL)
                        envent = environ_find(global_environ, key);
                if (envent != NULL && envent->value != NULL) {
                        found = xstrdup(envent->value);
                        goto found;
                }
        }

        return (NULL);

found:
        if (modifiers & FORMAT_TIMESTRING) {
                if (t == 0 && found != NULL) {
                        t = strtonum(found, 0, INT64_MAX, &errstr);
                        if (errstr != NULL)
                                t = 0;
                        free(found);
                }
                if (t == 0)
                        return (NULL);
                if (modifiers & FORMAT_RELATIVE)
                        found = format_relative_time(t);
                else if (modifiers & FORMAT_DIFFERENCE)
                        found = format_time_difference(t);
                else if (modifiers & FORMAT_PRETTY)
                        found = format_pretty_time(t, 0);
                else {
                        if (time_format != NULL) {
                                localtime_r(&t, &tm);
                                strftime(s, sizeof s, time_format, &tm);
                        } else {
                                ctime_r(&t, s);
                                s[strcspn(s, "\n")] = '\0';
                        }
                        found = xstrdup(s);
                }
                return (found);
        }

        if (t != 0)
                xasprintf(&found, "%lld", (long long)t);
        else if (found == NULL)
                return (NULL);
        if (modifiers & FORMAT_BASENAME) {
                saved = found;
                found = xstrdup(basename(saved));
                free(saved);
        }
        if (modifiers & FORMAT_DIRNAME) {
                saved = found;
                found = xstrdup(dirname(saved));
                free(saved);
        }
        if (modifiers & FORMAT_QUOTE_SHELL) {
                saved = found;
                found = format_quote_shell(saved);
                free(saved);
        }
        if (modifiers & FORMAT_QUOTE_SHELL_SQ) {
                saved = found;
                found = format_quote_shell_single(saved);
                free(saved);
        }
        if (modifiers & FORMAT_QUOTE_STYLE) {
                saved = found;
                found = format_quote_style(saved);
                free(saved);
        }
        if (modifiers & FORMAT_QUOTE_ARGUMENTS) {
                saved = found;
                found = args_escape(saved);
                free(saved);
        }
        return (found);
}

/* Check if format has not taken too long. */
static int
format_check_time(struct format_expand_state *es, u_int *check)
{
        uint64_t t;

        if (check != NULL && ++*check % FORMAT_TIME_LOOP_CHECK != 0)
                return (1);

        t = get_timer();
        if (t - es->start_time < FORMAT_TIME_LIMIT)
                return (1);
        t -= es->start_time;

        format_log(es, "reached time limit (%llu)", (unsigned long long)t);
        return (0);
}

/* Unescape escaped characters. */
static char *
format_unescape(struct format_expand_state *es, const char *s, size_t n)
{
        const char      *end = s + n;
        char            *out, *cp;
        int              brackets = 0;
        u_int            check = 0;

        cp = out = xmalloc(n + 1);
        for (; s != end; s++) {
                if (!format_check_time(es, &check)) {
                        free(out);
                        return (xstrdup(""));
                }
                if (*s == '#' && s + 1 != end && s[1] == '{')
                        brackets++;
                if (brackets == 0 &&
                    *s == '#' &&
                    s + 1 != end &&
                    strchr(",#{}:", s[1]) != NULL) {
                        *cp++ = *++s;
                        continue;
                }
                if (*s == '}')
                        brackets--;
                *cp++ = *s;
        }
        *cp = '\0';
        return (out);
}

/* Remove escaped characters. */
static char *
format_strip(struct format_expand_state *es, const char *s)
{
        char    *out, *cp;
        int      brackets = 0;
        u_int    check = 0;

        cp = out = xmalloc(strlen(s) + 1);
        for (; *s != '\0'; s++) {
                if (!format_check_time(es, &check)) {
                        free(out);
                        return (xstrdup(""));
                }
                if (*s == '#' && s[1] == '{')
                        brackets++;
                if (*s == '#' && strchr(",#{}:", s[1]) != NULL) {
                        if (brackets != 0)
                                *cp++ = *s;
                        continue;
                }
                if (*s == '}')
                        brackets--;
                *cp++ = *s;
        }
        *cp = '\0';
        return (out);
}

/* Skip until end. */
static const char *
format_skip1(struct format_expand_state *es, const char *s, const char *end)
{
        int     brackets = 0;
        u_int   check = 0;

        for (; *s != '\0'; s++) {
                if (es != NULL && !format_check_time(es, &check))
                        return (NULL);
                if (*s == '#' && s[1] == '{')
                        brackets++;
                if (*s == '#' &&
                    s[1] != '\0' &&
                    strchr(",#{}:", s[1]) != NULL) {
                        s++;
                        continue;
                }
                if (*s == '}')
                        brackets--;
                if (strchr(end, *s) != NULL && brackets == 0)
                        break;
        }
        if (*s == '\0')
                return (NULL);
        return (s);
}

/* Skip until end. */
const char *
format_skip(const char *s, const char *end)
{
    return (format_skip1(NULL, s, end));
}

/* Return left and right alternatives separated by commas. */
static int
format_choose(struct format_expand_state *es, const char *s, char **left,
    char **right, int expand)
{
        const char      *cp;
        char            *left0, *right0;

        cp = format_skip1(es, s, ",");
        if (cp == NULL)
                return (-1);
        left0 = xstrndup(s, cp - s);
        right0 = xstrdup(cp + 1);

        if (expand) {
                *left = format_expand1(es, left0);
                free(left0);
                *right = format_expand1(es, right0);
                free(right0);
        } else {
                *left = left0;
                *right = right0;
        }
        return (0);
}

/* Is this true? */
int
format_true(const char *s)
{
        if (s != NULL && *s != '\0' && (s[0] != '0' || s[1] != '\0'))
                return (1);
        return (0);
}

/* Check if modifier end. */
static int
format_is_end(char c)
{
        return (c == ';' || c == ':');
}

/* Add to modifier list. */
static void
format_add_modifier(struct format_modifier **list, u_int *count,
    const char *c, size_t n, char **argv, int argc)
{
        struct format_modifier *fm;

        *list = xreallocarray(*list, (*count) + 1, sizeof **list);
        fm = &(*list)[(*count)++];

        memcpy(fm->modifier, c, n);
        fm->modifier[n] = '\0';
        fm->size = n;

        fm->argv = argv;
        fm->argc = argc;
}

/* Free modifier list. */
static void
format_free_modifiers(struct format_modifier *list, u_int count)
{
        u_int   i;

        for (i = 0; i < count; i++)
                cmd_free_argv(list[i].argc, list[i].argv);
        free(list);
}

/* Build modifier list. */
static struct format_modifier *
format_build_modifiers(struct format_expand_state *es, const char **s,
    u_int *count)
{
        const char              *cp = *s, *end;
        struct format_modifier  *list = NULL;
        char                     c, last[] = "X;:", **argv, *value;
        int                      argc;

        /*
         * 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,A,<,>
         *      =a
         *      =/a
         *      =/a/
         *      s/a/b/
         *      s/a/b
         *      ||,&&,!=,==,<=,>=
         */

        *count = 0;

        while (*cp != '\0' && *cp != ':') {
                /* Skip any separator character. */
                if (*cp == ';')
                        cp++;
                if (*cp == '\0')
                        break;

                /* Check single character modifiers with no arguments. */
                if (strchr("labdnwETSWPOVL!<>A", cp[0]) != NULL &&
                    format_is_end(cp[1])) {
                        format_add_modifier(&list, count, cp, 1, NULL, 0);
                        cp++;
                        continue;
                }

                /* Then try double character with no arguments. */
                if ((memcmp("||", cp, 2) == 0 ||
                    memcmp("&&", cp, 2) == 0 ||
                    memcmp("!!", cp, 2) == 0 ||
                    memcmp("!=", cp, 2) == 0 ||
                    memcmp("==", cp, 2) == 0 ||
                    memcmp("<=", cp, 2) == 0 ||
                    memcmp(">=", cp, 2) == 0) &&
                    format_is_end(cp[2])) {
                        format_add_modifier(&list, count, cp, 2, NULL, 0);
                        cp += 2;
                        continue;
                }

                /* Now try single character with arguments. */
                if (strchr("ImCLNPSOVst=pReqWcA", cp[0]) == NULL)
                        break;
                c = cp[0];

                /* No arguments provided. */
                if (format_is_end(cp[1])) {
                        format_add_modifier(&list, count, cp, 1, NULL, 0);
                        cp++;
                        continue;
                }
                argv = NULL;
                argc = 0;

                /* Single argument with no wrapper character. */
                if (!ispunct((u_char)cp[1]) || cp[1] == '-') {
                        end = format_skip1(es, cp + 1, ":;");
                        if (end == NULL)
                                break;

                        argv = xcalloc(1, sizeof *argv);
                        value = format_unescape(es, cp + 1, end - (cp + 1));
                        argv[0] = format_expand1(es, value);
                        free(value);
                        argc = 1;

                        format_add_modifier(&list, count, &c, 1, argv, argc);
                        cp = end;
                        continue;
                }

                /* Multiple arguments with a wrapper character. */
                last[0] = cp[1];
                cp++;
                do {
                        if (cp[0] == last[0] && format_is_end(cp[1])) {
                                cp++;
                                break;
                        }
                        end = format_skip1(es, cp + 1, last);
                        if (end == NULL)
                                break;
                        cp++;

                        argv = xreallocarray(argv, argc + 1, sizeof *argv);
                        value = format_unescape(es, cp, end - cp);
                        argv[argc++] = format_expand1(es, value);
                        free(value);

                        cp = end;
                } while (!format_is_end(cp[0]));
                format_add_modifier(&list, count, &c, 1, argv, argc);
        }
        if (*cp != ':') {
                format_free_modifiers(list, *count);
                *count = 0;
                return (NULL);
        }
        *s = cp + 1;
        return (list);
}

/* Match using the fuzzy matcher. */
static char *
format_match_fuzzy(const char *pattern, const char *text, int positions)
{
        struct evbuffer *buffer;
        bitstr_t        *bs;
        char            *value;
        size_t           size;
        u_int            i, width;

        width = format_width(text);
        if (width == 0)
                width = 1;
        bs = fuzzy_match(pattern, text, width, NULL);
        if (bs == NULL)
                return (xstrdup(positions ? "" : "0"));

        if (!positions) {
                free(bs);
                return (xstrdup("1"));
        }

        buffer = evbuffer_new();
        if (buffer == NULL)
                fatalx("out of memory");
        for (i = 0; i < width; i++) {
                if (!bit_test(bs, i))
                        continue;
                if (EVBUFFER_LENGTH(buffer) != 0)
                        evbuffer_add(buffer, ",", 1);
                evbuffer_add_printf(buffer, "%u", i);
        }
        if ((size = EVBUFFER_LENGTH(buffer)) != 0)
                value = xmemdup(EVBUFFER_DATA(buffer), size);
        else
                value = xstrdup("");
        evbuffer_free(buffer);
        free(bs);
        return (value);
}

/* Match against an fnmatch(3) pattern or regular expression. */
static char *
format_match(struct format_modifier *fm, const char *pattern, const char *text)
{
        const char      *s = "";
        regex_t          r;
        int              flags = 0;

        if (fm->argc >= 1)
                s = fm->argv[0];
        if (strchr(s, 'p') != NULL)
                return (format_match_fuzzy(pattern, text, 1));
        if (strchr(s, 'z') != NULL)
                return (format_match_fuzzy(pattern, text, 0));
        if (strchr(s, 'r') == NULL) {
                if (strchr(s, 'i') != NULL)
                        flags |= FNM_CASEFOLD;
                if (fnmatch(pattern, text, flags) != 0)
                        return (xstrdup("0"));
        } else {
                flags = REG_EXTENDED|REG_NOSUB;
                if (strchr(s, 'i') != NULL)
                        flags |= REG_ICASE;
                if (regcomp(&r, pattern, flags) != 0)
                        return (xstrdup("0"));
                if (regexec(&r, text, 0, NULL, 0) != 0) {
                        regfree(&r);
                        return (xstrdup("0"));
                }
                regfree(&r);
        }
        return (xstrdup("1"));
}

/* Perform substitution in string. */
static char *
format_sub(struct format_modifier *fm, const char *text, const char *pattern,
    const char *with)
{
        char    *value;
        int      flags = REG_EXTENDED;

        if (fm->argc >= 3 && strchr(fm->argv[2], 'i') != NULL)
                flags |= REG_ICASE;
        value = regsub(pattern, with, text, flags);
        if (value == NULL)
                return (xstrdup(text));
        return (value);
}

/* Search inside pane. */
static char *
format_search(struct format_modifier *fm, struct window_pane *wp, const char *s)
{
        int      ignore = 0, regex = 0;
        char    *value;

        if (fm->argc >= 1) {
                if (strchr(fm->argv[0], 'i') != NULL)
                        ignore = 1;
                if (strchr(fm->argv[0], 'r') != NULL)
                        regex = 1;
        }
        xasprintf(&value, "%u", window_pane_search(wp, s, regex, ignore));
        return (value);
}

/* Handle unary boolean operators, "!" and "!!". */
static char *
format_bool_op_1(struct format_expand_state *es, const char *fmt, int not)
{
        int      result;
        char    *expanded;

        expanded = format_expand1(es, fmt);
        result = format_true(expanded);
        if (not)
                result = !result;
        free(expanded);

        return (xstrdup(result ? "1" : "0"));
}

/* Handle n-ary boolean operators, "&&" and "||". */
static char *
format_bool_op_n(struct format_expand_state *es, const char *fmt, int and)
{
        int              result;
        const char      *cp1, *cp2;
        char            *raw, *expanded;

        result = and ? 1 : 0;
        cp1 = fmt;

        while (and ? result : !result) {
                cp2 = format_skip1(es, cp1, ",");

                if (cp2 == NULL)
                        raw = xstrdup(cp1);
                else
                        raw = xstrndup(cp1, cp2 - cp1);
                expanded = format_expand1(es, raw);
                free(raw);
                format_log(es, "operator %s has operand: %s",
                    and ? "&&" : "||", expanded);

                if (and)
                        result = result && format_true(expanded);
                else
                        result = result || format_true(expanded);
                free(expanded);

                if (cp2 == NULL)
                        break;
                else
                        cp1 = cp2 + 1;
        }

        return (xstrdup(result ? "1" : "0"));
}

/* Does session name exist? */
static char *
format_session_name(struct format_expand_state *es, const char *fmt)
{
        char            *name;
        struct session  *s;

        name = format_expand1(es, fmt);
        RB_FOREACH(s, sessions, &sessions) {
                if (strcmp(s->name, name) == 0) {
                        free(name);
                        return (xstrdup("1"));
                }
        }
        free(name);
        return (xstrdup("0"));
}

/* Loop over sessions. */
static char *
format_loop_sessions(struct format_expand_state *es, const char *fmt)
{
        struct sort_criteria             *sc = &sort_crit;
        struct format_tree               *ft = es->ft;
        struct client                    *c = ft->client;
        struct cmdq_item                 *item = ft->item;
        struct format_tree               *nft;
        struct format_expand_state        next;
        char                             *all, *active, *use, *expanded, *value;
        struct evbuffer                  *buffer;
        size_t                            size;
        struct session                   *s, **l;
        int                               i, n;

        if (format_choose(es, fmt, &all, &active, 0) != 0) {
                all = xstrdup(fmt);
                active = NULL;
        }

        buffer = evbuffer_new();
        if (buffer == NULL)
                fatalx("out of memory");

        l = sort_get_sessions(&n, sc);
        for (i = 0; i < n; i++) {
                s = l[i];
                format_log(es, "session loop: $%u", s->id);
                if (active != NULL &&
                    ft->c != NULL &&
                    s->id == ft->c->session->id)
                        use = active;
                else
                        use = all;
                nft = format_create(c, item, FORMAT_NONE, ft->flags);

                format_add(nft, "loop_index", "%d", i);
                format_add(nft, "loop_last_flag", "%d", i == n - 1);

                format_defaults(nft, ft->c, s, NULL, NULL);
                format_copy_state(&next, es, 0);
                next.ft = nft;

                expanded = format_expand1(&next, use);
                format_free(next.ft);

                evbuffer_add(buffer, expanded, strlen(expanded));
                free(expanded);
        }

        free(active);
        free(all);

        if ((size = EVBUFFER_LENGTH(buffer)) != 0)
                value = xmemdup(EVBUFFER_DATA(buffer), size);
        else
                value = xstrdup("");
        evbuffer_free(buffer);
        return (value);
}

/* Does window name exist? */
static char *
format_window_name(struct format_expand_state *es, const char *fmt)
{
        struct format_tree      *ft = es->ft;
        char                    *name;
        struct winlink          *wl;

        if (ft->s == NULL) {
                format_log(es, "window name but no session");
                return (NULL);
        }

        name = format_expand1(es, fmt);
        RB_FOREACH(wl, winlinks, &ft->s->windows) {
                if (strcmp(wl->window->name, name) == 0) {
                        free(name);
                        return (xstrdup("1"));
                }
        }
        free(name);
        return (xstrdup("0"));
}

/* Add neighbour window variables to the format tree. */
static void
format_add_window_neighbour(struct format_tree *nft, struct winlink *wl,
    struct session *s, const char *prefix)
{
        struct options_entry    *o;
        const char              *oname;
        char                    *key, *prefixed, *oval;

        xasprintf(&key, "%s_window_index", prefix);
        format_add(nft, key, "%u", wl->idx);
        free(key);

        xasprintf(&key, "%s_window_active", prefix);
        format_add(nft, key, "%d", wl == s->curw);
        free(key);

        o = options_first(wl->window->options);
        while (o != NULL) {
                oname = options_name(o);
                if (*oname == '@') {
                        xasprintf(&prefixed, "%s_%s", prefix, oname);
                        oval = options_to_string(o, NULL, 1);
                        format_add(nft, prefixed, "%s", oval);
                        free(oval);
                        free(prefixed);
                }
                o = options_next(o);
        }
}

/* Loop over windows. */
static char *
format_loop_windows(struct format_expand_state *es, const char *fmt)
{
        struct sort_criteria            *sc = &sort_crit;
        struct format_tree              *ft = es->ft;
        struct client                   *c = ft->client;
        struct session                  *s = ft->s;
        struct cmdq_item                *item = ft->item;
        struct format_tree              *nft;
        struct format_expand_state       next;
        char                            *all, *active, *use, *expanded, *value;
        struct evbuffer                 *buffer;
        size_t                           size;
        struct winlink                  *wl, **l;
        struct window                   *w;
        int                              i, n;

        if (s == NULL) {
                format_log(es, "window loop but no session");
                return (NULL);
        }

        if (format_choose(es, fmt, &all, &active, 0) != 0) {
                all = xstrdup(fmt);
                active = NULL;
        }

        buffer = evbuffer_new();
        if (buffer == NULL)
                fatalx("out of memory");

        l = sort_get_winlinks_session(s, &n, sc);
        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 == s->curw)
                        use = active;
                else
                        use = all;
                nft = format_create(c, item, FORMAT_WINDOW|w->id, ft->flags);

                format_add(nft, "loop_index", "%d", i);
                format_add(nft, "loop_last_flag", "%d", i == n - 1);

                /* Add neighbour window data to the format tree. */
                if (i > 0 && l[i - 1] == s->curw)
                        format_add(nft, "window_after_active", "1");
                else
                        format_add(nft, "window_after_active", "0");
                if (i + 1 < n && l[i + 1] == s->curw)
                        format_add(nft, "window_before_active", "1");
                else
                        format_add(nft, "window_before_active", "0");
                if (i + 1 < n)
                        format_add_window_neighbour(nft, l[i + 1], s, "next");
                if (i > 0)
                        format_add_window_neighbour(nft, l[i - 1], s, "prev");

                format_defaults(nft, ft->c, s, wl, NULL);
                format_copy_state(&next, es, 0);
                next.ft = nft;

                expanded = format_expand1(&next, use);
                format_free(nft);

                evbuffer_add(buffer, expanded, strlen(expanded));
                free(expanded);
        }

        free(active);
        free(all);

        if ((size = EVBUFFER_LENGTH(buffer)) != 0)
                value = xmemdup(EVBUFFER_DATA(buffer), size);
        else
                value = xstrdup("");
        evbuffer_free(buffer);
        return (value);
}

/* Loop over panes. */
static char *
format_loop_panes(struct format_expand_state *es, const char *fmt)
{
        struct sort_criteria            *sc = &sort_crit;
        struct format_tree              *ft = es->ft;
        struct client                   *c = ft->client;
        struct cmdq_item                *item = ft->item;
        struct format_tree              *nft;
        struct format_expand_state       next;
        char                            *all, *active, *use, *expanded, *value;
        struct evbuffer                 *buffer;
        size_t                           size;
        struct window_pane              *wp, **l;
        int                               i, n;

        if (ft->w == NULL) {
                format_log(es, "pane loop but no window");
                return (NULL);
        }

        if (format_choose(es, fmt, &all, &active, 0) != 0) {
                all = xstrdup(fmt);
                active = NULL;
        }

        buffer = evbuffer_new();
        if (buffer == NULL)
                fatalx("out of memory");

        l = sort_get_panes_window(ft->w, &n, sc);
        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);

                format_add(nft, "loop_index", "%d", i);
                format_add(nft, "loop_last_flag", "%d", i == n - 1);

                format_defaults(nft, ft->c, ft->s, ft->wl, wp);
                format_copy_state(&next, es, 0);
                next.ft = nft;

                expanded = format_expand1(&next, use);
                format_free(nft);

                evbuffer_add(buffer, expanded, strlen(expanded));
                free(expanded);
        }

        free(active);
        free(all);

        if ((size = EVBUFFER_LENGTH(buffer)) != 0)
                value = xmemdup(EVBUFFER_DATA(buffer), size);
        else
                value = xstrdup("");
        evbuffer_free(buffer);
        return (value);
}

/* Add an option to an options loop. */
static void
format_loop_add_option(struct format_expand_state *es, const char *fmt,
    struct evbuffer *buffer, struct options_entry *o, u_int n, u_int i)
{
        struct format_tree                      *ft = es->ft, *nft;
        struct format_expand_state               next;
        const struct options_table_entry        *oe = options_table_entry(o);
        const char                              *name = options_name(o);
        char                                    *expanded, *s;
        int                                      is_array = options_is_array(o);

        format_log(es, "option loop: %s", name);
        nft = format_create(ft->client, ft->item, FORMAT_NONE, ft->flags);

        format_add(nft, "option_name", "%s", name);
        s = options_to_string(o, NULL, 0);
        format_add(nft, "option_value", "%s", s);
        free(s);

        format_add(nft, "option_is_array", "%d", is_array);
        format_add(nft, "option_array_key", "%s", "");
        format_add(nft, "option_array_index", "%s", "");
        format_add(nft, "option_array_first", "%d", is_array);
        format_add(nft, "option_array_last", "%d", is_array);
        format_add(nft, "option_array_count", "%u", n);

        if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_HOOK))
                format_add(nft, "option_is_hook", "1");
        else
                format_add(nft, "option_is_hook", "0");
        format_add(nft, "option_is_user", "%d", oe == NULL);

        if (options_next(o) == NULL)
                format_add(nft, "loop_last_flag", "1");
        else
                format_add(nft, "loop_last_flag", "0");
        format_add(nft, "loop_index", "%u", i);

        format_defaults(nft, ft->c, ft->s, ft->wl, ft->wp);
        format_copy_state(&next, es, 0);
        next.ft = nft;

        expanded = format_expand1(&next, fmt);
        format_free(nft);
        evbuffer_add(buffer, expanded, strlen(expanded));
        free(expanded);
}

/* Add an array option item to an options loop. */
static void
format_loop_add_array_item(struct format_expand_state *es, const char *fmt,
    struct evbuffer *buffer, struct options_entry *o,
    struct options_array_item *a, int n, u_int i)
{
        struct format_tree                      *ft = es->ft, *nft;
        struct format_expand_state               next;
        const struct options_table_entry        *oe = options_table_entry(o);
        const char                              *name = options_name(o);
        const char                              *array_key;
        char                                    *expanded, *s;

        array_key = options_array_item_key(a);
        format_log(es, "option loop: %s[%s]", name, array_key);
        nft = format_create(ft->client, ft->item, FORMAT_NONE, ft->flags);

        format_add(nft, "option_name", "%s", name);
        s = options_to_string(o, array_key, 0);
        format_add(nft, "option_value", "%s", s);
        free(s);

        format_add(nft, "option_is_array", "1");
        format_add(nft, "option_array_key", "%s", array_key);
        format_add(nft, "option_array_index", "%s", array_key);
        if (a == options_array_first(o))
                format_add(nft, "option_array_first", "1");
        else
                format_add(nft, "option_array_first", "0");
        if (options_array_next(a) == NULL)
                format_add(nft, "option_array_last", "1");
        else
                format_add(nft, "option_array_last", "0");
        format_add(nft, "option_array_count", "%u", n);

        if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_HOOK))
                format_add(nft, "option_is_hook", "1");
        else
                format_add(nft, "option_is_hook", "0");
        format_add(nft, "option_is_user", "%d", oe == NULL);

        if (options_array_next(a) == NULL && options_next(o) == NULL)
                format_add(nft, "loop_last_flag", "1");
        else
                format_add(nft, "loop_last_flag", "0");
        format_add(nft, "loop_index", "%u", i);

        format_defaults(nft, ft->c, ft->s, ft->wl, ft->wp);
        format_copy_state(&next, es, 0);
        next.ft = nft;

        expanded = format_expand1(&next, fmt);
        format_free(nft);
        evbuffer_add(buffer, expanded, strlen(expanded));
        free(expanded);
}

/* Loop over options. */
static char *
format_loop_options(struct format_expand_state *es, const char *fmt,
    const char *flags)
{
        struct format_tree              *ft = es->ft;
        struct options                  *oo = NULL;
        struct options_entry            *o;
        struct options_array_item       *a;
        char                            *value;
        struct evbuffer                 *buffer;
        size_t                           size;
        u_int                            i = 0, n;
        int                              global = 0;

        if (flags == NULL || *flags == '\0')
                flags = "s";
        if (strchr(flags, 'v') != NULL)
                oo = global_options;
        else {
                if (strchr(flags, 'g') != NULL)
                        global = 1;
                if (strchr(flags, 'w') != NULL) {
                        if (global)
                                oo = global_w_options;
                        else if (ft->w != NULL)
                                oo = ft->w->options;
                } else if (strchr(flags, 's') != NULL) {
                        if (global)
                                oo = global_s_options;
                        else if (ft->s != NULL)
                                oo = ft->s->options;
                } else if (strchr(flags, 'p') != NULL) {
                        if (global)
                                /* invalid */;
                        else if (ft->wp != NULL)
                                oo = ft->wp->options;
                } else if (global)
                        oo = global_s_options;
        }
        if (oo == NULL)
                return (xstrdup(""));

        buffer = evbuffer_new();
        if (buffer == NULL)
                fatalx("out of memory");

        o = options_first(oo);
        while (o != NULL) {

                n = 0;
                if (options_is_array(o)) {
                        a = options_array_first(o);
                        while (a != NULL) {
                                n++;
                                a = options_array_next(a);
                        }
                }

                if (!options_is_array(o) || n == 0) {
                        format_loop_add_option(es, fmt, buffer, o, n, i);
                        i++;
                        o = options_next(o);
                        continue;
                }

                a = options_array_first(o);
                while (a != NULL) {
                        format_loop_add_array_item(es, fmt, buffer, o, a, n, i);
                        i++;
                        a = options_array_next(a);
                }
                o = options_next(o);
        }

        if ((size = EVBUFFER_LENGTH(buffer)) != 0)
                value = xmemdup(EVBUFFER_DATA(buffer), size);
        else
                value = xstrdup("");
        evbuffer_free(buffer);
        return (value);
}

/* Loop over an environment. */
static char *
format_loop_environ(struct format_expand_state *es, const char *fmt,
    const char *flags)
{
        struct format_tree              *ft = es->ft, *nft;
        struct client                   *c = ft->client;
        struct cmdq_item                *item = ft->item;
        struct format_expand_state       next;
        struct environ                  *env = NULL;
        struct environ_entry            *envent;
        char                            *expanded, *value;
        struct evbuffer                 *buffer;
        size_t                           size;
        u_int                            i = 0;

        if (flags == NULL || *flags == '\0' || strcmp(flags, "s") == 0) {
                if (ft->s != NULL)
                        env = ft->s->environ;
        } else if (strcmp(flags, "g") == 0)
                env = global_environ;
        else if (strcmp(flags, "c") == 0) {
                if (ft->client != NULL)
                        env = ft->client->environ;
        }
        if (env == NULL)
                return (xstrdup(""));

        buffer = evbuffer_new();
        if (buffer == NULL)
                fatalx("out of memory");

        envent = environ_first(env);
        while (envent != NULL) {
                format_log(es, "environment loop: %s", envent->name);
                nft = format_create(c, item, FORMAT_NONE, ft->flags);

                format_add(nft, "environ_name", "%s", envent->name);
                if (envent->value == NULL)
                        format_add(nft, "environ_value", "%s", "");
                else
                        format_add(nft, "environ_value", "%s", envent->value);

                if (envent->flags & ENVIRON_HIDDEN)
                        format_add(nft, "environ_hidden", "1");
                else
                        format_add(nft, "environ_hidden", "0");
                format_add(nft, "environ_removed", "%d", envent->value == NULL);

                if (environ_next(envent) == NULL)
                        format_add(nft, "loop_last_flag", "1");
                else
                        format_add(nft, "loop_last_flag", "0");
                format_add(nft, "loop_index", "%u", i);

                format_defaults(nft, ft->c, ft->s, ft->wl, ft->wp);
                format_copy_state(&next, es, 0);
                next.ft = nft;

                expanded = format_expand1(&next, fmt);
                format_free(nft);
                evbuffer_add(buffer, expanded, strlen(expanded));
                free(expanded);

                i++;
                envent = environ_next(envent);
        }

        if ((size = EVBUFFER_LENGTH(buffer)) != 0)
                value = xmemdup(EVBUFFER_DATA(buffer), size);
        else
                value = xstrdup("");
        evbuffer_free(buffer);
        return (value);
}

/* Loop over clients. */
static char *
format_loop_clients(struct format_expand_state *es, const char *fmt)
{
        struct sort_criteria             *sc = &sort_crit;
        struct format_tree               *ft = es->ft;
        struct client                    *c, **l;
        struct cmdq_item                 *item = ft->item;
        struct format_tree               *nft;
        struct format_expand_state        next;
        char                             *expanded, *value;
        struct evbuffer                  *buffer;
        size_t                            size;
        int                               i, n;

        buffer = evbuffer_new();
        if (buffer == NULL)
                fatalx("out of memory");

        l = sort_get_clients(&n, sc);
        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);

                format_add(nft, "loop_index", "%d", i);
                format_add(nft, "loop_last_flag", "%d", i == n - 1);

                format_defaults(nft, c, ft->s, ft->wl, ft->wp);
                format_copy_state(&next, es, 0);
                next.ft = nft;

                expanded = format_expand1(&next, fmt);
                format_free(nft);
                evbuffer_add(buffer, expanded, strlen(expanded));
                free(expanded);
        }

        if ((size = EVBUFFER_LENGTH(buffer)) != 0)
                value = xmemdup(EVBUFFER_DATA(buffer), size);
        else
                value = xstrdup("");
        evbuffer_free(buffer);
        return (value);
}

static char *
format_replace_expression(struct format_modifier *mexp,
    struct format_expand_state *es, const char *copy)
{
        int                      argc = mexp->argc;
        const char              *errstr;
        char                    *endch, *value, *left = NULL, *right = NULL;
        int                      use_fp = 0;
        u_int                    prec = 0;
        double                   mleft, mright, result;
        enum { ADD,
               SUBTRACT,
               MULTIPLY,
               DIVIDE,
               MODULUS,
               EQUAL,
               NOT_EQUAL,
               GREATER_THAN,
               GREATER_THAN_EQUAL,
               LESS_THAN,
               LESS_THAN_EQUAL } operator;

        if (strcmp(mexp->argv[0], "+") == 0)
                operator = ADD;
        else if (strcmp(mexp->argv[0], "-") == 0)
                operator = SUBTRACT;
        else if (strcmp(mexp->argv[0], "*") == 0)
                operator = MULTIPLY;
        else if (strcmp(mexp->argv[0], "/") == 0)
                operator = DIVIDE;
        else if (strcmp(mexp->argv[0], "%") == 0 ||
            strcmp(mexp->argv[0], "m") == 0)
                operator = MODULUS;
        else if (strcmp(mexp->argv[0], "==") == 0)
                operator = EQUAL;
        else if (strcmp(mexp->argv[0], "!=") == 0)
                operator = NOT_EQUAL;
        else if (strcmp(mexp->argv[0], ">") == 0)
                operator = GREATER_THAN;
        else if (strcmp(mexp->argv[0], "<") == 0)
                operator = LESS_THAN;
        else if (strcmp(mexp->argv[0], ">=") == 0)
                operator = GREATER_THAN_EQUAL;
        else if (strcmp(mexp->argv[0], "<=") == 0)
                operator = LESS_THAN_EQUAL;
        else {
                format_log(es, "expression has no valid operator: '%s'",
                    mexp->argv[0]);
                goto fail;
        }

        /* The second argument may be flags. */
        if (argc >= 2 && strchr(mexp->argv[1], 'f') != NULL) {
                use_fp = 1;
                prec = 2;
        }

        /* The third argument may be precision. */
        if (argc >= 3) {
                prec = strtonum(mexp->argv[2], -FORMAT_MAX_PRECISION,
                    FORMAT_MAX_PRECISION, &errstr);
                if (errstr != NULL) {
                        format_log(es, "expression precision %s: %s", errstr,
                            mexp->argv[2]);
                        goto fail;
                }
        }

        if (format_choose(es, copy, &left, &right, 1) != 0) {
                format_log(es, "expression syntax error");
                goto fail;
        }

        mleft = strtod(left, &endch);
        if (*endch != '\0') {
                format_log(es, "expression left side is invalid: %s", left);
                goto fail;
        }

        mright = strtod(right, &endch);
        if (*endch != '\0') {
                format_log(es, "expression right side is invalid: %s", right);
                goto fail;
        }

        if (!use_fp) {
                mleft = (long long)mleft;
                mright = (long long)mright;
        }
        format_log(es, "expression left side is: %.*f", prec, mleft);
        format_log(es, "expression right side is: %.*f", prec, mright);

        switch (operator) {
        case ADD:
                result = mleft + mright;
                break;
        case SUBTRACT:
                result = mleft - mright;
                break;
        case MULTIPLY:
                result = mleft * mright;
                break;
        case DIVIDE:
                result = mleft / mright;
                break;
        case MODULUS:
                result = fmod(mleft, mright);
                break;
        case EQUAL:
                result = fabs(mleft - mright) < 1e-9;
                break;
        case NOT_EQUAL:
                result = fabs(mleft - mright) > 1e-9;
                break;
        case GREATER_THAN:
                result = (mleft > mright);
                break;
        case GREATER_THAN_EQUAL:
                result = (mleft >= mright);
                break;
        case LESS_THAN:
                result = (mleft < mright);
                break;
        case LESS_THAN_EQUAL:
                result = (mleft <= mright);
                break;
        }
        if (use_fp)
                xasprintf(&value, "%.*f", prec, result);
        else
                xasprintf(&value, "%.*f", prec, (double)(long long)result);
        format_log(es, "expression result is %s", value);

        free(right);
        free(left);
        return (value);

fail:
        free(right);
        free(left);
        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,
    char **buf, size_t *len, size_t *off)
{
        struct sort_criteria             *sc = &sort_crit;
        struct format_tree               *ft = es->ft;
        struct window_pane               *wp = ft->wp;
        const char                       *errstr, *copy, *cp, *cp2;
        const char                       *marker = NULL;
        char                             *time_format = NULL;
        char                             *copy0, *condition, *found, *new;
        char                             *value, *left, *right;
        size_t                            valuelen;
        uint64_t                          modifiers = 0;
        int                               limit = 0, width = 0;
        int                               j, c;
        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;
        struct environ_entry             *envent;

        /* Set sorting defaults. */
        sc->order = SORT_ORDER;
        sc->reversed = 0;

        /* Make a copy of the key. */
        copy = copy0 = xstrndup(key, keylen);

        /* Process modifier list. */
        list = format_build_modifiers(es, &copy, &count);
        for (i = 0; i < count; i++) {
                fm = &list[i];
                if (format_logging(ft)) {
                        format_log(es, "modifier %u is %s", i, fm->modifier);
                        for (j = 0; j < fm->argc; j++) {
                                format_log(es, "modifier %u argument %d: %s", i,
                                    j, fm->argv[j]);
                        }
                }
                if (fm->size == 1) {
                        switch (fm->modifier[0]) {
                        case 'm':
                        case '<':
                        case '>':
                                cmp = fm;
                                break;
                        case '!':
                                modifiers |= FORMAT_NOT;
                                break;
                        case 'C':
                                search = fm;
                                break;
                        case 's':
                                if (fm->argc < 2)
                                        break;
                                sub = xreallocarray(sub, nsub + 1, sizeof *sub);
                                sub[nsub++] = fm;
                                break;
                        case '=':
                                if (fm->argc < 1)
                                        break;
                                limit = strtonum(fm->argv[0], -FORMAT_MAX_WIDTH,
                                    FORMAT_MAX_WIDTH, &errstr);
                                if (errstr != NULL)
                                        limit = 0;
                                if (fm->argc >= 2 && fm->argv[1] != NULL)
                                        marker = fm->argv[1];
                                break;
                        case 'p':
                                if (fm->argc < 1)
                                        break;
                                width = strtonum(fm->argv[0], -FORMAT_MAX_WIDTH,
                                    FORMAT_MAX_WIDTH, &errstr);
                                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;
                        case 'e':
                                if (fm->argc < 1 || fm->argc > 3)
                                        break;
                                mexp = fm;
                                break;
                        case 'l':
                                modifiers |= FORMAT_LITERAL;
                                break;
                        case 'a':
                                modifiers |= FORMAT_CHARACTER;
                                break;
                        case 'b':
                                modifiers |= FORMAT_BASENAME;
                                break;
                        case 'c':
                                modifiers |= FORMAT_COLOUR;
                                if (fm->argc < 1)
                                        break;
                                if (strchr(fm->argv[0], 'f') != NULL)
                                        modifiers |= FORMAT_COLOUR_ESC_FG;
                                if (strchr(fm->argv[0], 'b') != NULL)
                                        modifiers |= FORMAT_COLOUR_ESC_BG;
                                break;
                        case 'd':
                                modifiers |= FORMAT_DIRNAME;
                                break;
                        case 'n':
                                modifiers |= FORMAT_LENGTH;
                                break;
                        case 'I':
                                if (fm->argc < 1)
                                        break;
                                if (strchr(fm->argv[0], 'f') != NULL)
                                        modifiers |= FORMAT_CLIENT_TERMFEAT;
                                if (strchr(fm->argv[0], 'c') != NULL)
                                        modifiers |= FORMAT_CLIENT_TERMCAP;
                                if (strchr(fm->argv[0], 'e') != NULL)
                                        modifiers |= FORMAT_CLIENT_ENVIRON;
                                break;
                        case 't':
                                modifiers |= FORMAT_TIMESTRING;
                                if (fm->argc < 1)
                                        break;
                                if (strchr(fm->argv[0], 'p') != NULL)
                                        modifiers |= FORMAT_PRETTY;
                                else if (strchr(fm->argv[0], 'r') != NULL)
                                        modifiers |= FORMAT_RELATIVE;
                                else if (strchr(fm->argv[0], 'd') != NULL)
                                        modifiers |= FORMAT_DIFFERENCE;
                                else if (fm->argc >= 2 &&
                                    strchr(fm->argv[0], 'f') != NULL) {
                                        free(time_format);
                                        time_format = format_strip(es,
                                            fm->argv[1]);
                                }
                                break;
                        case 'q':
                                if (fm->argc < 1)
                                        modifiers |= FORMAT_QUOTE_SHELL;
                                else if (strchr(fm->argv[0], 's') != NULL)
                                        modifiers |= FORMAT_QUOTE_SHELL_SQ;
                                else if (strchr(fm->argv[0], 'e') != NULL ||
                                    strchr(fm->argv[0], 'h') != NULL)
                                        modifiers |= FORMAT_QUOTE_STYLE;
                                else if (strchr(fm->argv[0], 'a') != NULL)
                                        modifiers |= FORMAT_QUOTE_ARGUMENTS;
                                break;
                        case 'E':
                                modifiers |= FORMAT_EXPAND;
                                break;
                        case 'T':
                                modifiers |= FORMAT_EXPANDTIME;
                                break;
                        case 'N':
                                if (fm->argc < 1 ||
                                    strchr(fm->argv[0], 'w') != NULL)
                                        modifiers |= FORMAT_WINDOW_NAME;
                                else if (strchr(fm->argv[0], 's') != NULL)
                                        modifiers |= FORMAT_SESSION_NAME;
                                break;
                        case 'S':
                                modifiers |= FORMAT_SESSIONS;
                                if (fm->argc < 1) {
                                        sc->order= SORT_INDEX;
                                        sc->reversed = 0;
                                        break;
                                }
                                if (strchr(fm->argv[0], 'i') != NULL)
                                        sc->order = SORT_INDEX;
                                else if (strchr(fm->argv[0], 'n') != NULL)
                                        sc->order = SORT_NAME;
                                else if (strchr(fm->argv[0], 't') != NULL)
                                        sc->order = SORT_ACTIVITY;
                                else
                                        sc->order = SORT_INDEX;
                                if (strchr(fm->argv[0], 'r') != NULL)
                                        sc->reversed = 1;
                                else
                                        sc->reversed = 0;
                                break;
                        case 'W':
                                modifiers |= FORMAT_WINDOWS;
                                if (fm->argc < 1) {
                                        sc->order = SORT_ORDER;
                                        sc->reversed = 0;
                                        break;
                                }
                                if (strchr(fm->argv[0], 'i') != NULL)
                                        sc->order = SORT_ORDER;
                                else if (strchr(fm->argv[0], 'n') != NULL)
                                        sc->order = SORT_NAME;
                                else if (strchr(fm->argv[0], 't') != NULL)
                                        sc->order = SORT_ACTIVITY;
                                else
                                        sc->order = SORT_ORDER;
                                if (strchr(fm->argv[0], 'r') != NULL)
                                        sc->reversed = 1;
                                else
                                        sc->reversed = 0;
                                break;
                        case 'P':
                                modifiers |= FORMAT_PANES;
                                sc->order = SORT_CREATION;
                                if (fm->argc < 1) {
                                        sc->reversed = 0;
                                        break;
                                }
                                if (strchr(fm->argv[0], 'i') != NULL)
                                        sc->order = SORT_INDEX;
                                else if (strchr(fm->argv[0], 'z') != NULL)
                                        sc->order = SORT_Z;
                                else
                                        sc->order = SORT_CREATION;
                                if (strchr(fm->argv[0], 'r') != NULL)
                                        sc->reversed = 1;
                                else
                                        sc->reversed = 0;
                                break;
                        case 'O':
                                modifiers |= FORMAT_OPTIONS;
                                if (fm->argc == 1)
                                        loop_flags = fm->argv[0];
                                break;
                        case 'V':
                                modifiers |= FORMAT_ENVIRON;
                                if (fm->argc == 1)
                                        loop_flags = fm->argv[0];
                                break;
                        case 'L':
                                modifiers |= FORMAT_CLIENTS;
                                if (fm->argc < 1) {
                                        sc->order = SORT_ORDER;
                                        sc->reversed = 0;
                                        break;
                                }
                                if (strchr(fm->argv[0], 'i') != NULL)
                                        sc->order = SORT_ORDER;
                                else if (strchr(fm->argv[0], 'n') != NULL)
                                        sc->order = SORT_NAME;
                                else if (strchr(fm->argv[0], 't') != NULL)
                                        sc->order = SORT_ACTIVITY;
                                else
                                        sc->order = SORT_ORDER;
                                if (strchr(fm->argv[0], 'r') != NULL)
                                        sc->reversed = 1;
                                else
                                        sc->reversed = 0;
                                break;
                        case 'R':
                                modifiers |= FORMAT_REPEAT;
                                break;
                        }
                } else if (fm->size == 2) {
                        if (strcmp(fm->modifier, "||") == 0 ||
                            strcmp(fm->modifier, "&&") == 0)
                                bool_op_n = fm;
                        else if (strcmp(fm->modifier, "!!") == 0)
                                modifiers |= FORMAT_NOT_NOT;
                        else if (strcmp(fm->modifier, "==") == 0 ||
                            strcmp(fm->modifier, "!=") == 0 ||
                            strcmp(fm->modifier, ">=") == 0 ||
                            strcmp(fm->modifier, "<=") == 0)
                                cmp = fm;
                }
        }

        /* Look up client capability, feature or environment. */
        if ((modifiers & FORMAT_CLIENT_TERMCAP) ||
            (modifiers & FORMAT_CLIENT_TERMFEAT) ||
            (modifiers & FORMAT_CLIENT_ENVIRON)) {
                if (ft->c == NULL ||
                    ft->c->tty.term == NULL ||
                    ft->c->flags & CLIENT_UNATTACHEDFLAGS) {
                        value = xstrdup("");
                        goto done;
                }
                if (modifiers & FORMAT_CLIENT_TERMCAP) {
                        if (tty_term_has_name(ft->c->tty.term, copy))
                                value = xstrdup("1");
                        else
                                value = xstrdup("0");
                }
                if (modifiers & FORMAT_CLIENT_TERMFEAT) {
                        if (tty_feature_present(ft->c->tty.term, copy))
                                value = xstrdup("1");
                        else
                                value = xstrdup("0");
                }
                if (modifiers & FORMAT_CLIENT_ENVIRON) {
                        envent = environ_find(ft->c->environ, copy);
                        if (envent != NULL && envent->value != NULL)
                                value = xstrdup(envent->value);
                        else
                                value = xstrdup("");
                }
                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);
                value = format_unescape(es, copy, strlen(copy));
                goto done;
        }

        /* Is this a character? */
        if (modifiers & FORMAT_CHARACTER) {
                new = format_expand1(es, copy);
                c = strtonum(new, 32, 126, &errstr);
                if (errstr != NULL)
                        value = xstrdup("");
                else
                        xasprintf(&value, "%c", c);
                free(new);
                goto done;
        }

        /* Is this a colour? */
        if (modifiers & FORMAT_COLOUR) {
                new = format_expand1(es, copy);
                if (modifiers & (FORMAT_COLOUR_ESC_FG|FORMAT_COLOUR_ESC_BG)) {
                        if (strcasecmp(new, "none") == 0)
                                value = xstrdup("\033[0m");
                        else if ((c = colour_fromstring(new)) == -1)
                                value = xstrdup("");
                        else {
                                if (modifiers & FORMAT_COLOUR_ESC_BG)
                                        cp = colour_toescape(ft->c, c, 1);
                                else
                                        cp = colour_toescape(ft->c, c, 0);
                                if (cp == NULL)
                                        value = xstrdup("");
                                else
                                        value = xstrdup(cp);
                        }
                } else {
                        c = colour_fromstring(new);
                        if (c == -1 || (c = colour_force_rgb(c)) == -1)
                                value = xstrdup("");
                        else
                                xasprintf(&value, "%06x", c & 0xffffff);
                }
                free(new);
                goto done;
        }

        /* Is this a loop, operator, comparison or condition? */
        if (modifiers & FORMAT_SESSIONS) {
                value = format_loop_sessions(es, copy);
                if (value == NULL)
                        goto fail;
        } else if (modifiers & FORMAT_WINDOWS) {
                value = format_loop_windows(es, copy);
                if (value == NULL)
                        goto fail;
        } else if (modifiers & FORMAT_PANES) {
                value = format_loop_panes(es, copy);
                if (value == NULL)
                        goto fail;
        } else if (modifiers & FORMAT_CLIENTS) {
                value = format_loop_clients(es, copy);
                if (value == NULL)
                        goto fail;
        } else if (modifiers & FORMAT_OPTIONS) {
                value = format_loop_options(es, copy, loop_flags);
                if (value == NULL)
                        goto fail;
        } else if (modifiers & FORMAT_ENVIRON) {
                value = format_loop_environ(es, copy, loop_flags);
                if (value == NULL)
                        goto fail;
        } else if (modifiers & FORMAT_WINDOW_NAME) {
                value = format_window_name(es, copy);
                if (value == NULL)
                        goto fail;
        } else if (modifiers & FORMAT_SESSION_NAME) {
                value = format_session_name(es, copy);
                if (value == NULL)
                        goto fail;
        } else if (search != NULL) {
                /* Search in pane. */
                new = format_expand1(es, copy);
                if (wp == NULL) {
                        format_log(es, "search '%s' but no pane", new);
                        value = xstrdup("0");
                } else {
                        format_log(es, "search '%s' pane %%%u", new, wp->id);
                        value = format_search(search, wp, new);
                }
                free(new);
        } else if (modifiers & FORMAT_REPEAT) {
                /* Repeat multiple times. */
                if (format_choose(es, copy, &left, &right, 1) != 0) {
                        format_log(es, "repeat syntax error: %s", copy);
                        goto fail;
                }
                nrep = strtonum(right, 1, FORMAT_MAX_REPEAT, &errstr);
                if (errstr != NULL)
                        value = xstrdup("");
                else {
                        value = xstrdup("");
                        for (i = 0; i < nrep; i++) {
                                if (!format_check_time(es, &check)) {
                                        free(right);
                                        free(left);
                                        free(value);
                                        goto fail;
                                }
                                xasprintf(&new, "%s%s", value, left);
                                free(value);
                                value = new;
                        }
                }
                free(right);
                free(left);
        } else if (modifiers & FORMAT_NOT) {
                value = format_bool_op_1(es, copy, 1);
        } else if (modifiers & FORMAT_NOT_NOT) {
                value = format_bool_op_1(es, copy, 0);
        } else if (bool_op_n != NULL) {
                /* n-ary boolean operator. */
                if (strcmp(bool_op_n->modifier, "||") == 0)
                        value = format_bool_op_n(es, copy, 0);
                else if (strcmp(bool_op_n->modifier, "&&") == 0)
                        value = format_bool_op_n(es, copy, 1);
        } else if (cmp != NULL) {
                /* Comparison of left and right. */
                if (format_choose(es, copy, &left, &right, 1) != 0) {
                        format_log(es, "compare %s syntax error: %s",
                            cmp->modifier, copy);
                        goto fail;
                }
                format_log(es, "compare %s left is: %s", cmp->modifier, left);
                format_log(es, "compare %s right is: %s", cmp->modifier, right);

                if (strcmp(cmp->modifier, "==") == 0) {
                        if (strcmp(left, right) == 0)
                                value = xstrdup("1");
                        else
                                value = xstrdup("0");
                } else if (strcmp(cmp->modifier, "!=") == 0) {
                        if (strcmp(left, right) != 0)
                                value = xstrdup("1");
                        else
                                value = xstrdup("0");
                } else if (strcmp(cmp->modifier, "<") == 0) {
                        if (strcmp(left, right) < 0)
                                value = xstrdup("1");
                        else
                                value = xstrdup("0");
                } else if (strcmp(cmp->modifier, ">") == 0) {
                        if (strcmp(left, right) > 0)
                                value = xstrdup("1");
                        else
                                value = xstrdup("0");
                } else if (strcmp(cmp->modifier, "<=") == 0) {
                        if (strcmp(left, right) <= 0)
                                value = xstrdup("1");
                        else
                                value = xstrdup("0");
                } else if (strcmp(cmp->modifier, ">=") == 0) {
                        if (strcmp(left, right) >= 0)
                                value = xstrdup("1");
                        else
                                value = xstrdup("0");
                } else if (strcmp(cmp->modifier, "m") == 0)
                        value = format_match(cmp, left, right);

                free(right);
                free(left);
        } else if (*copy == '?') {
                /*
                 * Conditional: For each pair of (condition, value), check the
                 * condition and return the value if true. If no condition
                 * matches, return the last unpaired arg if there is one, or the
                 * empty string if not.
                 */
                cp = copy + 1;
                while (1) {
                        cp2 = format_skip1(es, cp, ",");
                        if (cp2 == NULL) {
                                format_log(es,
                                    "no condition matched in '%s'; using last "
                                    "arg", copy + 1);
                                value = format_expand1(es, cp);
                                break;
                        }

                        condition = xstrndup(cp, cp2 - cp);
                        format_log(es, "condition is: %s", condition);

                        found = format_find(ft, condition, modifiers,
                            time_format);
                        if (found == NULL) {
                                /*
                                 * If the condition not found, try to expand it.
                                 * If the expansion doesn't have any effect,
                                 * then assume false.
                                 */
                                found = format_expand1(es, condition);
                                if (strcmp(found, condition) == 0) {
                                        free(found);
                                        found = xstrdup("");
                                        format_log(es,
                                            "condition '%s' not found; "
                                            "assuming false",
                                            condition);
                                }
                        } else {
                                format_log(es, "condition '%s' found: %s",
                                    condition, found);
                        }

                        cp = cp2 + 1;
                        cp2 = format_skip1(es, cp, ",");
                        if (format_true(found)) {
                                format_log(es, "condition '%s' is true",
                                    condition);
                                if (cp2 == NULL)
                                        value = format_expand1(es, cp);
                                else {
                                        right = xstrndup(cp, cp2 - cp);
                                        value = format_expand1(es, right);
                                        free(right);
                                }
                                free(condition);
                                free(found);
                                break;
                        } else {
                                format_log(es, "condition '%s' is false",
                                    condition);
                        }

                        free(condition);
                        free(found);

                        if (cp2 == NULL) {
                                format_log(es,
                                    "no condition matched in '%s'; using empty "
                                    "string", copy + 1);
                                value = xstrdup("");
                                break;
                        }

                        cp = cp2 + 1;
                }
        } else if (mexp != NULL) {
                value = format_replace_expression(mexp, es, copy);
                if (value == NULL)
                        value = xstrdup("");
        } else {
                if (strstr(copy, "#{") != 0) {
                        format_log(es, "expanding inner format '%s'", copy);
                        value = format_expand1(es, copy);
                } else {
                        value = format_find(ft, copy, modifiers, time_format);
                        if (value == NULL) {
                                format_log(es, "format '%s' not found", copy);
                                value = xstrdup("");
                        } else {
                                format_log(es, "format '%s' found: %s", copy,
                                    value);
                        }
                }
        }

done:
        /* Expand again if required. */
        if (modifiers & FORMAT_EXPAND) {
                new = format_expand1(es, value);
                free(value);
                value = new;
        } else if (modifiers & FORMAT_EXPANDTIME) {
                format_copy_state(&next, es, FORMAT_EXPAND_TIME);
                new = format_expand1(&next, value);
                free(value);
                value = new;
        }

        /* Perform substitution if any. */
        for (i = 0; i < nsub; i++) {
                left = format_expand1(es, sub[i]->argv[0]);
                right = format_expand1(es, sub[i]->argv[1]);
                new = format_sub(sub[i], value, left, right);
                format_log(es, "substitute '%s' to '%s': %s", left, right, new);
                free(value);
                value = new;
                free(right);
                free(left);
        }

        /* Truncate the value if needed. */
        if (limit > 0) {
                new = format_trim_left(value, limit);
                if (marker != NULL && strcmp(new, value) != 0) {
                        free(value);
                        xasprintf(&value, "%s%s", new, marker);
                        free(new);
                } else {
                        free(value);
                        value = new;
                }
                format_log(es, "applied length limit %d: %s", limit, value);
        } else if (limit < 0) {
                new = format_trim_right(value, -limit);
                if (marker != NULL && strcmp(new, value) != 0) {
                        free(value);
                        xasprintf(&value, "%s%s", marker, new);
                        free(new);
                } else {
                        free(value);
                        value = new;
                }
                format_log(es, "applied length limit %d: %s", limit, value);
        }

        /* Pad the value if needed. */
        if (width > 0) {
                new = utf8_padcstr(value, width);
                free(value);
                value = new;
                format_log(es, "applied padding width %d: %s", width, value);
        } else if (width < 0) {
                new = utf8_rpadcstr(value, -width);
                free(value);
                value = new;
                format_log(es, "applied padding width %d: %s", width, value);
        }

        /* 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);
        while (*len - *off < valuelen + 1) {
                *buf = xreallocarray(*buf, 2, *len);
                *len *= 2;
        }
        memcpy(*buf + *off, value, valuelen);
        *off += valuelen;

        format_log(es, "replaced '%s' with '%s'", copy0, value);
        free(value);

        free(sub);
        format_free_modifiers(list, count);
        free(copy0);
        free(time_format);
        return (0);

fail:
        format_log(es, "failed %s", copy0);

        free(sub);
        format_free_modifiers(list, count);
        free(copy0);
        free(time_format);
        return (-1);
}

/* Expand keys in a template. */
static char *
format_expand1(struct format_expand_state *es, const char *fmt)
{
        struct format_tree      *ft = es->ft;
        char                    *buf, *out, *name;
        const char              *ptr, *s, *style_end = NULL;
        size_t                   off, len, n, outlen;
        int                      ch, brackets;
        char                     expanded[8192];

        if (fmt == NULL || *fmt == '\0' || !format_check_time(es, NULL))
                return (xstrdup(""));

        if (es->loop == FORMAT_LOOP_LIMIT) {
                format_log(es, "reached loop limit (%u)", FORMAT_LOOP_LIMIT);
                return (xstrdup(""));
        }
        es->loop++;

        format_log(es, "expanding format: %s", fmt);

        if ((es->flags & FORMAT_EXPAND_TIME) && strchr(fmt, '%') != NULL) {
                if (es->time == 0) {
                        es->time = time(NULL);
                        localtime_r(&es->time, &es->tm);
                }
                if (strftime(expanded, sizeof expanded, fmt, &es->tm) == 0) {
                        format_log(es, "format is too long");
                        return (xstrdup(""));
                }
                if (format_logging(ft) && strcmp(expanded, fmt) != 0)
                        format_log(es, "after time expanded: %s", expanded);
                fmt = expanded;
        }

        len = 64;
        buf = xmalloc(len);
        off = 0;

        while (*fmt != '\0') {
                if (*fmt != '#') {
                        while (len - off < 2) {
                                buf = xreallocarray(buf, 2, len);
                                len *= 2;
                        }
                        buf[off++] = *fmt++;
                        continue;
                }
                if (*++fmt == '\0')
                        break;

                ch = (u_char)*fmt++;
                switch (ch) {
                case '(':
                        brackets = 1;
                        for (ptr = fmt; *ptr != '\0'; ptr++) {
                                if (*ptr == '(')
                                        brackets++;
                                if (*ptr == ')' && --brackets == 0)
                                        break;
                        }
                        if (*ptr != ')' || brackets != 0)
                                break;
                        n = ptr - fmt;

                        name = xstrndup(fmt, n);
                        format_log(es, "found #(): %s", name);

                        if ((ft->flags & FORMAT_NOJOBS) ||
                            (es->flags & FORMAT_EXPAND_NOJOBS)) {
                                out = xstrdup("");
                                format_log(es, "#() is disabled");
                        } else {
                                out = format_job_get(es, name);
                                format_log(es, "#() result: %s", out);
                        }
                        free(name);

                        outlen = strlen(out);
                        while (len - off < outlen + 1) {
                                buf = xreallocarray(buf, 2, len);
                                len *= 2;
                        }
                        memcpy(buf + off, out, outlen);
                        off += outlen;

                        free(out);

                        fmt += n + 1;
                        continue;
                case '{':
                        ptr = format_skip1(es, (char *)fmt - 2, "}");
                        if (ptr == NULL)
                                break;
                        n = ptr - fmt;

                        format_log(es, "found #{}: %.*s", (int)n, fmt);
                        if (format_replace(es, fmt, n, &buf, &len, &off) != 0)
                                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 - (ch == '[');
                        n = 2 - (ch == '[');
                        while (*ptr == '#') {
                                ptr++;
                                n++;
                        }
                        if (*ptr == '[') {
                                style_end = format_skip1(es, fmt - 2, "]");
                                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) {
                                buf = xreallocarray(buf, 2, len);
                                len *= 2;
                        }
                        buf[off++] = ch;
                        continue;
                default:
                        s = NULL;
                        if (fmt > style_end) { /* skip inside #[] */
                                if (ch >= 'A' && ch <= 'Z')
                                        s = format_upper[ch - 'A'];
                                else if (ch >= 'a' && ch <= 'z')
                                        s = format_lower[ch - 'a'];
                        }
                        if (s == NULL) {
                                while (len - off < 3) {
                                        buf = xreallocarray(buf, 2, len);
                                        len *= 2;
                                }
                                buf[off++] = '#';
                                buf[off++] = ch;
                                continue;
                        }
                        n = strlen(s);
                        format_log(es, "found #%c: %s", ch, s);
                        if (format_replace(es, s, n, &buf, &len, &off) != 0)
                                break;
                        continue;
                }

                break;
        }
        buf[off] = '\0';

        format_log(es, "result is: %s", buf);
        es->loop--;

        return (buf);
}

/* Expand keys in a template, passing through strftime first. */
char *
format_expand_time(struct format_tree *ft, const char *fmt)
{
        struct format_expand_state      es;

        memset(&es, 0, sizeof es);
        es.ft = ft;
        es.flags = FORMAT_EXPAND_TIME;
        es.start_time = get_timer();
        return (format_expand1(&es, fmt));
}

/* Expand keys in a template. */
char *
format_expand(struct format_tree *ft, const char *fmt)
{
        struct format_expand_state      es;

        memset(&es, 0, sizeof es);
        es.ft = ft;
        es.flags = 0;
        es.start_time = get_timer();
        return (format_expand1(&es, fmt));
}

/* Expand a single string. */
char *
format_single(struct cmdq_item *item, const char *fmt, struct client *c,
    struct session *s, struct winlink *wl, struct window_pane *wp)
{
        struct format_tree      *ft;
        char                    *expanded;

        ft = format_create_defaults(item, c, s, wl, wp);
        expanded = format_expand(ft, fmt);
        format_free(ft);
        return (expanded);
}

/* Expand a single string using state. */
char *
format_single_from_state(struct cmdq_item *item, const char *fmt,
    struct client *c, struct cmd_find_state *fs)
{
        return (format_single(item, fmt, c, fs->s, fs->wl, fs->wp));
}

/* Expand a single string using target. */
char *
format_single_from_target(struct cmdq_item *item, const char *fmt)
{
        struct client   *tc = cmdq_get_target_client(item);

        return (format_single_from_state(item, fmt, tc, cmdq_get_target(item)));
}

/* Create and add defaults. */
struct format_tree *
format_create_defaults(struct cmdq_item *item, struct client *c,
    struct session *s, struct winlink *wl, struct window_pane *wp)
{
        struct format_tree      *ft;

        if (item != NULL)
                ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
        else
                ft = format_create(NULL, item, FORMAT_NONE, 0);
        format_defaults(ft, c, s, wl, wp);
        return (ft);
}

/* Create and add defaults using state. */
struct format_tree *
format_create_from_state(struct cmdq_item *item, struct client *c,
    struct cmd_find_state *fs)
{
        return (format_create_defaults(item, c, fs->s, fs->wl, fs->wp));
}

/* Create and add defaults using target. */
struct format_tree *
format_create_from_target(struct cmdq_item *item)
{
        struct client   *tc = cmdq_get_target_client(item);

        return (format_create_from_state(item, tc, cmdq_get_target(item)));
}

/* Set defaults for any of arguments that are not NULL. */
void
format_defaults(struct format_tree *ft, struct client *c, struct session *s,
    struct winlink *wl, struct window_pane *wp)
{
        struct paste_buffer     *pb;

        if (c != NULL && c->name != NULL)
                log_debug("%s: c=%s", __func__, c->name);
        else
                log_debug("%s: c=none", __func__);
        if (s != NULL)
                log_debug("%s: s=$%u", __func__, s->id);
        else
                log_debug("%s: s=none", __func__);
        if (wl != NULL)
                log_debug("%s: wl=%u", __func__, wl->idx);
        else
                log_debug("%s: wl=none", __func__);
        if (wp != NULL)
                log_debug("%s: wp=%%%u", __func__, wp->id);
        else
                log_debug("%s: wp=none", __func__);

        if (c != NULL && s != NULL && c->session != s)
                log_debug("%s: session does not match", __func__);

        if (wp != NULL)
                ft->type = FORMAT_TYPE_PANE;
        else if (wl != NULL)
                ft->type = FORMAT_TYPE_WINDOW;
        else if (s != NULL)
                ft->type = FORMAT_TYPE_SESSION;
        else
                ft->type = FORMAT_TYPE_UNKNOWN;

        if (s == NULL && c != NULL)
                s = c->session;
        if (wl == NULL && s != NULL)
                wl = s->curw;
        if (wp == NULL && wl != NULL)
                wp = wl->window->active;

        if (c != NULL)
                format_defaults_client(ft, c);
        if (s != NULL)
                format_defaults_session(ft, s);
        if (wl != NULL)
                format_defaults_winlink(ft, wl);
        if (wp != NULL)
                format_defaults_pane(ft, wp);

        pb = paste_get_top(NULL);
        if (pb != NULL)
                format_defaults_paste_buffer(ft, pb);
}

/* Set default format keys for a session. */
static void
format_defaults_session(struct format_tree *ft, struct session *s)
{
        ft->s = s;
}

/* Set default format keys for a client. */
static void
format_defaults_client(struct format_tree *ft, struct client *c)
{
        if (ft->s == NULL)
                ft->s = c->session;
        ft->c = c;
}

/* Set default format keys for a window. */
void
format_defaults_window(struct format_tree *ft, struct window *w)
{
        ft->w = w;
}

/* Set default format keys for a winlink. */
static void
format_defaults_winlink(struct format_tree *ft, struct winlink *wl)
{
        if (ft->w == NULL)
                format_defaults_window(ft, wl->window);
        ft->wl = wl;
}

/* Set default format keys for a window pane. */
void
format_defaults_pane(struct format_tree *ft, struct window_pane *wp)
{
        struct window_mode_entry        *wme;

        if (ft->w == NULL)
                format_defaults_window(ft, wp->window);
        ft->wp = wp;

        wme = TAILQ_FIRST(&wp->modes);
        if (wme != NULL && wme->mode->formats != NULL)
                wme->mode->formats(wme, ft);
}

/* Set default format keys for paste buffer. */
void
format_defaults_paste_buffer(struct format_tree *ft, struct paste_buffer *pb)
{
        ft->pb = pb;
}

static int
format_is_word_separator(const char *ws, const struct grid_cell *gc)
{
        if (utf8_cstrhas(ws, &gc->data))
                return (1);
        if (gc->flags & GRID_FLAG_TAB)
                return (1);
        return gc->data.size == 1 && *gc->data.data == ' ';
}

/* Return word at given coordinates. Caller frees. */
char *
format_grid_word(struct grid *gd, u_int x, u_int y)
{
        const struct grid_line  *gl;
        struct grid_cell         gc;
        const char              *ws;
        struct utf8_data        *ud = NULL;
        u_int                    end;
        size_t                   size = 0;
        int                      found = 0;
        char                    *s = NULL;

        ws = options_get_string(global_s_options, "word-separators");

        for (;;) {
                grid_get_cell(gd, x, y, &gc);
                if ((~gc.flags & GRID_FLAG_PADDING) &&
                    format_is_word_separator(ws, &gc)) {
                        found = 1;
                        break;
                }

                if (x == 0) {
                        if (y == 0)
                                break;
                        gl = grid_peek_line(gd, y - 1);
                        if (~gl->flags & GRID_LINE_WRAPPED)
                                break;
                        y--;
                        x = grid_line_length(gd, y);
                        if (x == 0)
                                break;
                }
                x--;
        }
        for (;;) {
                if (found) {
                        end = grid_line_length(gd, y);
                        if (end == 0 || x == end - 1) {
                                if (y == gd->hsize + gd->sy - 1)
                                        break;
                                gl = grid_peek_line(gd, y);
                                if (~gl->flags & GRID_LINE_WRAPPED)
                                        break;
                                y++;
                                x = 0;
                        } else
                                x++;
                }
                found = 1;

                grid_get_cell(gd, x, y, &gc);
                if (gc.flags & GRID_FLAG_PADDING)
                        continue;
                if (format_is_word_separator(ws, &gc))
                        break;

                ud = xreallocarray(ud, size + 2, sizeof *ud);
                memcpy(&ud[size++], &gc.data, sizeof *ud);
        }
        if (size != 0) {
                ud[size].size = 0;
                s = utf8_tocstr(ud);
                free(ud);
        }
        return (s);
}

/* Return line at given coordinates. Caller frees. */
char *
format_grid_line(struct grid *gd, u_int y)
{
        struct grid_cell         gc;
        struct utf8_data        *ud = NULL;
        u_int                    x;
        size_t                   size = 0;
        char                    *s = NULL;

        for (x = 0; x < grid_line_length(gd, y); x++) {
                grid_get_cell(gd, x, y, &gc);
                if (gc.flags & GRID_FLAG_PADDING)
                        continue;

                ud = xreallocarray(ud, size + 2, sizeof *ud);
                if (gc.flags & GRID_FLAG_TAB)
                        utf8_set(&ud[size++], '\t');
                else
                        memcpy(&ud[size++], &gc.data, sizeof *ud);
        }
        if (size != 0) {
                ud[size].size = 0;
                s = utf8_tocstr(ud);
                free(ud);
        }
        return (s);
}

/* Return hyperlink at given coordinates. Caller frees. */
char *
format_grid_hyperlink(struct grid *gd, u_int x, u_int y, struct screen* s)
{
        const char              *uri;
        struct grid_cell         gc;

        for (;;) {
                grid_get_cell(gd, x, y, &gc);
                if (~gc.flags & GRID_FLAG_PADDING)
                        break;
                if (x == 0)
                        return (NULL);
                x--;
        }
        if (s->hyperlinks == NULL || gc.link == 0)
                return (NULL);
        if (!hyperlinks_get(s->hyperlinks, gc.link, &uri, NULL, NULL))
                return (NULL);
        return (xstrdup(uri));
}