Files
tmux/window-tree.c
Thomas Adam 25e2e1d671 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  build: don't exit
  build: always attempt portable merge
  build: capture openbsd-base before filtering
  build: fetch cutover master before merge
  Use _exit in child after fork, from Yayo Razo in GitHub issue 5376.
  Add missing calloc check and use fatal. Prompted by a similar change from Yayo Razo.
  actions: use published openbsd-git as base update
  github actions: automate updates
  Do not make pty fds blocking again until all the data has been consumed or control mode clients can get stuck, GitHub issue 5356 from Ben Maurer.
  Add formats and events for OSC 133 commmands, as well as a -T flag to set-hook -B to only fire when the format is true.
  Replace the notification system with events. Events can carry a payload of additional payload (to reduce problems with lifetime of objects) and are delivered to one or more event sinks. This is more powerful and reduces the complex dependencies between control mode and hooks. Events are now used for hooks, control mode notifications and for monitors (set-hook -B).
2026-07-13 09:22:25 +01:00

1560 lines
41 KiB
C

/* $OpenBSD: window-tree.c,v 1.93 2026/07/04 22:09:06 nicm Exp $ */
/*
* Copyright (c) 2017 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 <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "tmux.h"
static struct screen *window_tree_init(struct window_mode_entry *,
struct cmd_find_state *, struct args *);
static void window_tree_free(struct window_mode_entry *);
static void window_tree_resize(struct window_mode_entry *, u_int,
u_int);
static void window_tree_update(struct window_mode_entry *);
static void window_tree_key(struct window_mode_entry *,
struct client *, struct session *,
struct winlink *, key_code, struct mouse_event *);
#define WINDOW_TREE_DEFAULT_COMMAND "switch-client -Zt '%%'"
#define WINDOW_TREE_DEFAULT_FORMAT \
"#{?pane_format," \
"#{?pane_marked,#[fg=thememagenta],}#{?pane_floating_flag,#[underscore],}" \
"#{pane_current_command}#[fg=themelightgrey]#{pane_flags}" \
"#{?#{&&:#{pane_title},#{!=:#{pane_title},#{host_short}}},: \"#{pane_title}\",}" \
",window_format," \
"#{?window_marked_flag,#[fg=thememagenta],}" \
"#{window_name}#[fg=themelightgrey]#{window_flags}" \
"#{?#{&&:#{==:#{window_panes},1},#{&&:#{pane_title},#{!=:#{pane_title},#{host_short}}}},: \"#{pane_title}\",}" \
"," \
"#[fg=themelightgrey]#{session_windows} windows" \
"#{?session_grouped, " \
"(group #{session_group}: " \
"#{session_group_list})," \
"}" \
"#{?session_attached, (attached),}" \
"}"
#define WINDOW_TREE_DEFAULT_KEY_FORMAT \
"#{?#{e|<:#{line},10}," \
"#{line}" \
",#{e|<:#{line},36}," \
"M-#{a:#{e|+:97,#{e|-:#{line},10}}}" \
"}"
static const struct menu_item window_tree_menu_items[] = {
{ "Select", '\r', NULL },
{ "Expand", KEYC_RIGHT, NULL },
{ "Mark", 'm', NULL },
{ "", KEYC_NONE, NULL },
{ "Tag", 't', NULL },
{ "Tag All", '\024', NULL },
{ "Tag None", 'T', NULL },
{ "", KEYC_NONE, NULL },
{ "Kill", 'x', NULL },
{ "Kill Tagged", 'X', NULL },
{ "", KEYC_NONE, NULL },
{ "Cancel", 'q', NULL },
{ NULL, KEYC_NONE, NULL }
};
const struct window_mode window_tree_mode = {
.name = "tree-mode",
.default_format = WINDOW_TREE_DEFAULT_FORMAT,
.init = window_tree_init,
.free = window_tree_free,
.resize = window_tree_resize,
.update = window_tree_update,
.key = window_tree_key,
};
enum window_tree_type {
WINDOW_TREE_NONE,
WINDOW_TREE_SESSION,
WINDOW_TREE_WINDOW,
WINDOW_TREE_PANE,
};
struct window_tree_itemdata {
enum window_tree_type type;
int session;
int winlink;
int pane;
};
struct window_tree_modedata {
struct window_pane *wp;
int dead;
int references;
struct mode_tree_data *data;
char *format;
char *key_format;
char *command;
int squash_groups;
int hide_preview_this_pane;
int preview_is_info;
int prompt_flags;
struct window_tree_itemdata **item_list;
u_int item_size;
const char *entered;
struct cmd_find_state fs;
enum window_tree_type type;
int offset;
int left;
int right;
u_int start;
u_int end;
u_int each;
};
static enum sort_order window_tree_order_seq[] = {
SORT_INDEX,
SORT_NAME,
SORT_ACTIVITY,
SORT_Z,
SORT_END,
};
#define WINDOW_TREE_FLAG(label, cond) \
"#{?" cond ",#[fg=themegreen],#[fg=themelightgrey]}" label "#[default]"
static const char *window_tree_pane_info_lines[] = {
"#[fg=themelightgrey]Pane #[#{E:tree-mode-border-style},acs]x#[default] "
"#{pane_index} #[fg=themelightgrey](#{pane_id})#[default]",
"#[fg=themelightgrey]Title #[#{E:tree-mode-border-style},acs]x#[default] "
"#{pane_title}",
"#[fg=themelightgrey]Command #[#{E:tree-mode-border-style},acs]x#[default] "
"#{pane_current_command} #[fg=themelightgrey](PID #{pane_pid})#[default]",
"#[fg=themelightgrey]Path #[#{E:tree-mode-border-style},acs]x#[default] "
"#{pane_current_path}",
"#[fg=themelightgrey]TTY #[#{E:tree-mode-border-style},acs]x#[default] "
"#{pane_tty}",
"#[fg=themelightgrey]Position #[#{E:tree-mode-border-style},acs]x#[default] "
"#{pane_x},#{pane_y} #{pane_width}x#{pane_height}",
"#[fg=themelightgrey]Mode #[#{E:tree-mode-border-style},acs]x#[default] "
"#{?pane_in_mode,#{pane_mode},none}",
"#[fg=themelightgrey]Flags #[#{E:tree-mode-border-style},acs]x#[default] "
WINDOW_TREE_FLAG("active", "pane_active") " "
WINDOW_TREE_FLAG("zoomed", "window_zoomed_flag") " "
WINDOW_TREE_FLAG("marked", "pane_marked") " "
WINDOW_TREE_FLAG("sync", "pane_synchronized") " "
WINDOW_TREE_FLAG("dead", "pane_dead") " "
WINDOW_TREE_FLAG("piped", "pane_pipe"),
};
static const char *window_tree_window_info_lines[] = {
"#[fg=themelightgrey]Window #[#{E:tree-mode-border-style},acs]x#[default] "
"#{window_index}: #{window_name} #[fg=themelightgrey](#{window_id})#[default]",
"#[fg=themelightgrey]Size #[#{E:tree-mode-border-style},acs]x#[default] "
"#{window_width}x#{window_height}",
"#[fg=themelightgrey]Panes #[#{E:tree-mode-border-style},acs]x#[default] "
"#{window_panes}",
"#[fg=themelightgrey]Activity Time #[#{E:tree-mode-border-style},acs]x#[default] "
"#{t:window_activity} #[fg=themelightgrey](#{t/r:window_activity})#[default]",
"#[fg=themelightgrey]Sessions #[#{E:tree-mode-border-style},acs]x#[default] "
"#{s/,/ /:window_linked_sessions_list}",
"#[fg=themelightgrey]Flags #[#{E:tree-mode-border-style},acs]x#[default] "
WINDOW_TREE_FLAG("active", "window_active") " "
WINDOW_TREE_FLAG("last", "window_last_flag") " "
WINDOW_TREE_FLAG("bell", "window_bell_flag") " "
WINDOW_TREE_FLAG("activity", "window_activity_flag") " "
WINDOW_TREE_FLAG("silence", "window_silence_flag") " "
WINDOW_TREE_FLAG("zoomed", "window_zoomed_flag") " "
WINDOW_TREE_FLAG("marked", "window_marked_flag"),
};
static const char *window_tree_session_info_lines[] = {
"#[fg=themelightgrey]Session #[#{E:tree-mode-border-style},acs]x#[default] "
"#{session_name} #[fg=themelightgrey](#{session_id})#[default]",
"#[fg=themelightgrey]Created Time #[#{E:tree-mode-border-style},acs]x#[default] "
"#{t:session_created} #[fg=themelightgrey](#{t/r:session_created})#[default]",
"#[fg=themelightgrey]Activity Time #[#{E:tree-mode-border-style},acs]x#[default] "
"#{t:session_activity} #[fg=themelightgrey](#{t/r:session_activity})#[default]",
"#[fg=themelightgrey]Attached Time #[#{E:tree-mode-border-style},acs]x#[default] "
"#{?#{t:session_last_attached},#{t:session_last_attached} #[fg=themelightgrey](#{t/r:session_last_attached})#[default],never}",
"#[fg=themelightgrey]Clients #[#{E:tree-mode-border-style},acs]x#[default] "
"#{s/,/ /:session_attached_list}",
"#[fg=themelightgrey]Windows #[#{E:tree-mode-border-style},acs]x#[default] "
"#{session_windows}",
"#[fg=themelightgrey]Path #[#{E:tree-mode-border-style},acs]x#[default] "
"#{session_path}",
"#[fg=themelightgrey]Group #[#{E:tree-mode-border-style},acs]x#[default] "
"#{?session_grouped,#{session_group} (#{session_group_size}),none}",
"#[fg=themelightgrey]Flags #[#{E:tree-mode-border-style},acs]x#[default] "
WINDOW_TREE_FLAG("attached", "session_attached") " "
WINDOW_TREE_FLAG("grouped", "session_grouped") " "
WINDOW_TREE_FLAG("marked", "session_marked") " "
WINDOW_TREE_FLAG("bell", "session_bell_flag") " "
WINDOW_TREE_FLAG("activity", "session_activity_flag") " "
WINDOW_TREE_FLAG("silence", "session_silence_flag"),
};
static void
window_tree_pull_item(struct window_tree_itemdata *item, struct session **sp,
struct winlink **wlp, struct window_pane **wp)
{
*wp = NULL;
*wlp = NULL;
*sp = session_find_by_id(item->session);
if (*sp == NULL)
return;
if (item->type == WINDOW_TREE_SESSION) {
*wlp = (*sp)->curw;
*wp = (*wlp)->window->active;
return;
}
*wlp = winlink_find_by_index(&(*sp)->windows, item->winlink);
if (*wlp == NULL) {
*sp = NULL;
return;
}
if (item->type == WINDOW_TREE_WINDOW) {
*wp = (*wlp)->window->active;
return;
}
*wp = window_pane_find_by_id(item->pane);
if (!window_has_pane((*wlp)->window, *wp))
*wp = NULL;
if (*wp == NULL) {
*sp = NULL;
*wlp = NULL;
return;
}
}
static struct window_tree_itemdata *
window_tree_add_item(struct window_tree_modedata *data)
{
struct window_tree_itemdata *item;
data->item_list = xreallocarray(data->item_list, data->item_size + 1,
sizeof *data->item_list);
item = data->item_list[data->item_size++] = xcalloc(1, sizeof *item);
return (item);
}
static void
window_tree_free_item(struct window_tree_itemdata *item)
{
free(item);
}
static void
window_tree_build_pane(struct session *s, struct winlink *wl,
struct window_pane *wp, void *modedata, struct mode_tree_item *parent)
{
struct window_tree_modedata *data = modedata;
struct window_tree_itemdata *item;
struct mode_tree_item *mti;
char *name, *text;
u_int idx;
struct format_tree *ft;
window_pane_index(wp, &idx);
item = window_tree_add_item(data);
item->type = WINDOW_TREE_PANE;
item->session = s->id;
item->winlink = wl->idx;
item->pane = wp->id;
ft = format_create(NULL, NULL, FORMAT_PANE|wp->id, 0);
format_defaults(ft, NULL, s, wl, wp);
text = format_expand(ft, data->format);
xasprintf(&name, "%u", idx);
format_free(ft);
mti = mode_tree_add(data->data, parent, item, (uint64_t)wp, name, text,
-1);
free(text);
free(name);
mode_tree_align(mti, 1);
}
static int
window_tree_filter_pane(struct session *s, struct winlink *wl,
struct window_pane *wp, const char *filter)
{
char *cp;
int result;
if (filter == NULL)
return (1);
cp = format_single(NULL, filter, NULL, s, wl, wp);
result = format_true(cp);
free(cp);
return (result);
}
static int
window_tree_build_window(struct session *s, struct winlink *wl,
void *modedata, struct sort_criteria *sort_crit,
struct mode_tree_item *parent, const char *filter)
{
struct window_tree_modedata *data = modedata;
struct window_tree_itemdata *item;
struct mode_tree_item *mti;
char *name, *text;
struct window_pane **l;
u_int n, i, found;
int expanded;
struct format_tree *ft;
uint64_t tag = FORMAT_NONE;
item = window_tree_add_item(data);
item->type = WINDOW_TREE_WINDOW;
item->session = s->id;
item->winlink = wl->idx;
item->pane = -1;
if (wl->window != NULL && wl->window->active != NULL)
tag = FORMAT_PANE|wl->window->active->id;
ft = format_create(NULL, NULL, tag, 0);
format_defaults(ft, NULL, s, wl, NULL);
text = format_expand(ft, data->format);
xasprintf(&name, "%u", wl->idx);
format_free(ft);
if (data->type == WINDOW_TREE_SESSION ||
data->type == WINDOW_TREE_WINDOW)
expanded = 0;
else
expanded = 1;
mti = mode_tree_add(data->data, parent, item, (uint64_t)wl, name, text,
expanded);
free(text);
free(name);
mode_tree_align(mti, 1);
l = sort_get_panes_window(wl->window, &n, sort_crit);
found = 0;
for (i = 0; i < n; i++) {
if (!window_tree_filter_pane(s, wl, l[i], filter))
continue;
found++;
if (data->hide_preview_this_pane && l[i] == data->wp)
continue;
window_tree_build_pane(s, wl, l[i], modedata, mti);
}
if (found == 0) {
window_tree_free_item(item);
data->item_size--;
mode_tree_remove(data->data, mti);
return (0);
}
return (1);
}
static void
window_tree_build_session(struct session *s, void *modedata,
struct sort_criteria *sort_crit, const char *filter)
{
struct window_tree_modedata *data = modedata;
struct window_tree_itemdata *item;
struct mode_tree_item *mti;
char *text;
struct winlink *wl = s->curw, **l;
u_int n, i, empty;
int expanded;
struct format_tree *ft;
uint64_t tag = FORMAT_NONE;
item = window_tree_add_item(data);
item->type = WINDOW_TREE_SESSION;
item->session = s->id;
item->winlink = -1;
item->pane = -1;
if (wl != NULL && wl->window != NULL && wl->window->active != NULL)
tag = FORMAT_PANE|wl->window->active->id;
ft = format_create(NULL, NULL, tag, 0);
format_defaults(ft, NULL, s, NULL, NULL);
text = format_expand(ft, data->format);
format_free(ft);
if (data->type == WINDOW_TREE_SESSION)
expanded = 0;
else
expanded = 1;
mti = mode_tree_add(data->data, NULL, item, (uint64_t)s, s->name, text,
expanded);
free(text);
l = sort_get_winlinks_session(s, &n, sort_crit);
empty = 0;
for (i = 0; i < n; i++) {
if (!window_tree_build_window(s, l[i], modedata, sort_crit, mti,
filter))
empty++;
}
if (empty == n) {
window_tree_free_item(item);
data->item_size--;
mode_tree_remove(data->data, mti);
}
}
static void
window_tree_build(void *modedata, struct sort_criteria *sort_crit,
uint64_t *tag, const char *filter)
{
struct window_tree_modedata *data = modedata;
int squash_groups = data->squash_groups;
struct session *s, **l;
struct session_group *sg, *current;
u_int n, i;
current = session_group_contains(data->fs.s);
for (i = 0; i < data->item_size; i++)
window_tree_free_item(data->item_list[i]);
free(data->item_list);
data->item_list = NULL;
data->item_size = 0;
l = sort_get_sessions(&n, sort_crit);
if (n == 0)
return;
for (i = 0; i < n; i++) {
s = l[i];
if (squash_groups && (sg = session_group_contains(s)) != NULL) {
if ((sg == current && s != data->fs.s) ||
(sg != current && s != TAILQ_FIRST(&sg->sessions)))
continue;
}
window_tree_build_session(s, modedata, sort_crit, filter);
}
switch (data->type) {
case WINDOW_TREE_NONE:
break;
case WINDOW_TREE_SESSION:
*tag = (uint64_t)data->fs.s;
break;
case WINDOW_TREE_WINDOW:
*tag = (uint64_t)data->fs.wl;
break;
case WINDOW_TREE_PANE:
if (window_count_panes(data->fs.wl->window, 1) == 1)
*tag = (uint64_t)data->fs.wl;
else
*tag = (uint64_t)data->fs.wp;
break;
}
}
static void
window_tree_draw_label(struct screen_write_ctx *ctx, u_int px, u_int py,
u_int sx, u_int sy, const struct grid_cell *border_gc,
const struct grid_cell *label_gc, const char *label)
{
u_int width, ox, oy;
char *new_label = NULL;
if (sx < 5 || sy < 3)
return;
width = format_width(label);
if (width > sx - 4) {
label = new_label = format_trim_left(label, sx - 4);
width = format_width(new_label);
}
if (width == 0)
return;
ox = (sx - width + 1) / 2;
oy = (sy + 1) / 2;
screen_write_cursormove(ctx, px + ox - 2, py + oy - 1, 0);
screen_write_box(ctx, width + 4, 3, BOX_LINES_DEFAULT,
border_gc, NULL);
screen_write_cursormove(ctx, px + ox - 1, py + oy, 0);
screen_write_clearcharacter(ctx, width + 2, border_gc->bg);
screen_write_cursormove(ctx, px + ox, py + oy, 0);
format_draw(ctx, label_gc, width, label, NULL, 0);
free(new_label);
}
static void
window_tree_border_cell(struct grid_cell *gc, struct options *oo,
struct format_tree *ft)
{
memcpy(gc, &grid_default_cell, sizeof *gc);
style_apply(gc, oo, "tree-mode-border-style", ft);
}
static void
window_tree_draw_session(struct window_tree_modedata *data, struct session *s,
struct screen_write_ctx *ctx, u_int sx, u_int sy)
{
struct winlink *wl;
struct window *w;
u_int cx = ctx->s->cx, cy = ctx->s->cy;
u_int loop, total, visible, each, width, offset;
u_int current, start, end, remaining, i;
struct grid_cell gc, label_gc;
int left, right;
char *label;
const char *format;
struct format_tree *ft;
struct options *oo;
total = winlink_count(&s->windows);
if (sx / total < 24) {
visible = sx / 24;
if (visible == 0)
visible = 1;
} else
visible = total;
current = 0;
RB_FOREACH(wl, winlinks, &s->windows) {
if (wl == s->curw)
break;
current++;
}
if (current < visible) {
start = 0;
end = visible;
} else if (current >= total - visible) {
start = total - visible;
end = total;
} else {
start = current - (visible / 2);
end = start + visible;
}
if (data->offset < -(int)start)
data->offset = -(int)start;
if (data->offset > (int)(total - end))
data->offset = (int)(total - end);
start += data->offset;
end += data->offset;
left = (start != 0);
right = (end != total);
if (((left && right) && sx <= 6) || ((left || right) && sx <= 3))
left = right = 0;
if (left && right) {
each = (sx - 6) / visible;
remaining = (sx - 6) - (visible * each);
} else if (left || right) {
each = (sx - 3) / visible;
remaining = (sx - 3) - (visible * each);
} else {
each = sx / visible;
remaining = sx - (visible * each);
}
if (each == 0)
return;
window_tree_border_cell(&gc, data->wp->window->options, NULL);
if (left) {
data->left = cx + 2;
screen_write_cursormove(ctx, cx + 2, cy, 0);
screen_write_vline(ctx, sy, 0, 0, &gc);
screen_write_cursormove(ctx, cx, cy + sy / 2, 0);
screen_write_puts(ctx, &gc, "<");
} else
data->left = -1;
if (right) {
data->right = cx + sx - 3;
screen_write_cursormove(ctx, cx + sx - 3, cy, 0);
screen_write_vline(ctx, sy, 0, 0, &gc);
screen_write_cursormove(ctx, cx + sx - 1, cy + sy / 2, 0);
screen_write_puts(ctx, &gc, ">");
} else
data->right = -1;
data->start = start;
data->end = end;
data->each = each;
i = loop = 0;
RB_FOREACH(wl, winlinks, &s->windows) {
if (loop == end)
break;
if (loop < start) {
loop++;
continue;
}
w = wl->window;
oo = w->options;
ft = format_create(NULL, NULL, FORMAT_WINDOW|w->id, 0);
format_defaults(ft, NULL, s, wl, NULL);
window_tree_border_cell(&gc, oo, ft);
memcpy(&label_gc, &grid_default_cell, sizeof label_gc);
style_apply(&label_gc, oo, "tree-mode-preview-style", ft);
label_gc.bg = gc.bg;
if (left)
offset = 3 + (i * each);
else
offset = (i * each);
if (loop == end - 1)
width = each + remaining;
else
width = each - 1;
screen_write_cursormove(ctx, cx + offset, cy, 0);
screen_write_preview(ctx, &w->active->base, width, sy);
format = options_get_string(oo, "tree-mode-preview-format");
if (*format != '\0') {
label = format_expand(ft, format);
if (*label != '\0') {
window_tree_draw_label(ctx, cx + offset, cy,
width, sy, &gc, &label_gc, label);
}
free(label);
}
if (loop != end - 1) {
screen_write_cursormove(ctx, cx + offset + width, cy,
0);
screen_write_vline(ctx, sy, 0, 0, &gc);
}
loop++;
i++;
}
}
static void
window_tree_draw_window(struct window_tree_modedata *data, struct session *s,
struct winlink *wl, struct screen_write_ctx *ctx, u_int sx, u_int sy)
{
struct window *w = wl->window;
struct window_pane *wp;
u_int cx = ctx->s->cx, cy = ctx->s->cy;
u_int loop, total, visible, each, width, offset;
u_int current, start, end, remaining, i;
struct grid_cell gc, label_gc;
int left, right;
char *label;
const char *format;
struct format_tree *ft;
struct options *oo;
total = window_count_panes(w, 1);
if (data->hide_preview_this_pane && data->wp->window == w)
total--;
if (total == 0)
return;
if (sx / total < 24) {
visible = sx / 24;
if (visible == 0)
visible = 1;
} else
visible = total;
current = 0;
TAILQ_FOREACH(wp, &w->panes, entry) {
if (data->hide_preview_this_pane && wp == data->wp)
continue;
if (wp == w->active)
break;
current++;
}
if (current < visible) {
start = 0;
end = visible;
} else if (current >= total - visible) {
start = total - visible;
end = total;
} else {
start = current - (visible / 2);
end = start + visible;
}
if (data->offset < -(int)start)
data->offset = -(int)start;
if (data->offset > (int)(total - end))
data->offset = (int)(total - end);
start += data->offset;
end += data->offset;
left = (start != 0);
right = (end != total);
if (((left && right) && sx <= 6) || ((left || right) && sx <= 3))
left = right = 0;
if (left && right) {
each = (sx - 6) / visible;
remaining = (sx - 6) - (visible * each);
} else if (left || right) {
each = (sx - 3) / visible;
remaining = (sx - 3) - (visible * each);
} else {
each = sx / visible;
remaining = sx - (visible * each);
}
if (each == 0)
return;
window_tree_border_cell(&gc, data->wp->window->options, NULL);
if (left) {
data->left = cx + 2;
screen_write_cursormove(ctx, cx + 2, cy, 0);
screen_write_vline(ctx, sy, 0, 0, &gc);
screen_write_cursormove(ctx, cx, cy + sy / 2, 0);
screen_write_puts(ctx, &gc, "<");
} else
data->left = -1;
if (right) {
data->right = cx + sx - 3;
screen_write_cursormove(ctx, cx + sx - 3, cy, 0);
screen_write_vline(ctx, sy, 0, 0, &gc);
screen_write_cursormove(ctx, cx + sx - 1, cy + sy / 2, 0);
screen_write_puts(ctx, &gc, ">");
} else
data->right = -1;
data->start = start;
data->end = end;
data->each = each;
i = loop = 0;
TAILQ_FOREACH(wp, &w->panes, entry) {
if (data->hide_preview_this_pane && wp == data->wp)
continue;
if (loop == end)
break;
if (loop < start) {
loop++;
continue;
}
oo = wp->options;
ft = format_create(NULL, NULL, FORMAT_PANE|wp->id, 0);
format_defaults(ft, NULL, s, wl, wp);
window_tree_border_cell(&gc, oo, ft);
memcpy(&label_gc, &grid_default_cell, sizeof label_gc);
style_apply(&label_gc, oo, "tree-mode-preview-style", ft);
label_gc.bg = gc.bg;
if (left)
offset = 3 + (i * each);
else
offset = (i * each);
if (loop == end - 1)
width = each + remaining;
else
width = each - 1;
screen_write_cursormove(ctx, cx + offset, cy, 0);
screen_write_preview(ctx, &wp->base, width, sy);
format = options_get_string(oo, "tree-mode-preview-format");
if (*format != '\0') {
label = format_expand(ft, format);
if (*label != '\0') {
window_tree_draw_label(ctx, cx + offset, cy,
width, sy, &gc, &label_gc, label);
}
free(label);
}
if (loop != end - 1) {
screen_write_cursormove(ctx, cx + offset + width, cy,
0);
screen_write_vline(ctx, sy, 0, 0, &gc);
}
loop++;
i++;
}
}
static void
window_tree_draw_info(struct window_tree_modedata *data, void *itemdata,
struct screen_write_ctx *ctx, u_int sx, u_int sy)
{
struct window_tree_itemdata *item = itemdata;
struct screen *s = ctx->s;
struct session *sp;
struct winlink *wl;
struct window_pane *wp;
struct grid_cell gc;
u_int cx = s->cx, cy = s->cy, i, j, k;
struct format_tree *ft;
char *expanded;
const char *const *lines[3];
u_int count[3], n = 0;
window_tree_pull_item(item, &sp, &wl, &wp);
if (sp == NULL || wp == NULL)
return;
if (item->type == WINDOW_TREE_PANE) {
lines[n] = window_tree_pane_info_lines;
count[n++] = nitems(window_tree_pane_info_lines);
}
if (item->type == WINDOW_TREE_PANE ||
item->type == WINDOW_TREE_WINDOW) {
lines[n] = window_tree_window_info_lines;
count[n++] = nitems(window_tree_window_info_lines);
}
lines[n] = window_tree_session_info_lines;
count[n++] = nitems(window_tree_session_info_lines);
ft = format_create(NULL, NULL, FORMAT_NONE, 0);
format_defaults(ft, NULL, sp, wl, wp);
i = 0;
for (j = 0; j < n; j++) {
if (j != 0) {
if (i == sy)
break;
window_tree_border_cell(&gc, data->wp->window->options,
NULL);
screen_write_cursormove(ctx, cx, cy + i, 0);
screen_write_hline(ctx, sx, 0, 0, BOX_LINES_DEFAULT,
&gc);
if (sx > 14) {
gc.attr |= GRID_ATTR_CHARSET;
screen_write_cursormove(ctx, cx + 14, cy + i,
0);
screen_write_putc(ctx, &gc, 'n');
}
i++;
}
for (k = 0; k < count[j]; k++) {
if (i == sy)
break;
expanded = format_expand(ft, lines[j][k]);
screen_write_cursormove(ctx, cx, cy + i, 0);
format_draw(ctx, &grid_default_cell, sx, expanded, NULL,
0);
free(expanded);
i++;
}
if (i == sy)
break;
}
if (sx > 14 && i < sy) {
window_tree_border_cell(&gc, data->wp->window->options, NULL);
screen_write_cursormove(ctx, cx + 14, cy + i, 0);
screen_write_vline(ctx, sy - i, 0, 0, &gc);
}
format_free(ft);
}
static void
window_tree_draw(void *modedata, void *itemdata, struct screen_write_ctx *ctx,
u_int sx, u_int sy)
{
struct window_tree_modedata *data = modedata;
struct window_tree_itemdata *item = itemdata;
struct session *sp;
struct winlink *wl;
struct window_pane *wp;
window_tree_pull_item(item, &sp, &wl, &wp);
if (wp == NULL)
return;
if (data->preview_is_info) {
window_tree_draw_info(data, item, ctx, sx, sy);
return;
}
switch (item->type) {
case WINDOW_TREE_NONE:
break;
case WINDOW_TREE_SESSION:
window_tree_draw_session(modedata, sp, ctx, sx, sy);
break;
case WINDOW_TREE_WINDOW:
window_tree_draw_window(modedata, sp, wl, ctx, sx, sy);
break;
case WINDOW_TREE_PANE:
if (!data->hide_preview_this_pane || wp != data->wp)
screen_write_preview(ctx, &wp->base, sx, sy);
break;
}
}
static int
window_tree_search(__unused void *modedata, void *itemdata, const char *ss,
int icase)
{
struct window_tree_itemdata *item = itemdata;
struct session *s;
struct winlink *wl;
struct window_pane *wp;
char *cmd;
int retval;
window_tree_pull_item(item, &s, &wl, &wp);
switch (item->type) {
case WINDOW_TREE_NONE:
return (0);
case WINDOW_TREE_SESSION:
if (s == NULL)
return (0);
if (icase)
return (strcasestr(s->name, ss) != NULL);
return (strstr(s->name, ss) != NULL);
case WINDOW_TREE_WINDOW:
if (s == NULL || wl == NULL)
return (0);
if (icase)
return (strcasestr(wl->window->name, ss) != NULL);
return (strstr(wl->window->name, ss) != NULL);
case WINDOW_TREE_PANE:
if (s == NULL || wl == NULL || wp == NULL)
break;
cmd = osdep_get_name(wp->fd, wp->tty);
if (cmd == NULL || *cmd == '\0') {
free(cmd);
return (0);
}
if (icase)
retval = (strcasestr(cmd, ss) != NULL);
else
retval = (strstr(cmd, ss) != NULL);
free(cmd);
return (retval);
}
return (0);
}
static void
window_tree_menu(void *modedata, struct client *c, key_code key)
{
struct window_tree_modedata *data = modedata;
struct window_pane *wp = data->wp;
struct window_mode_entry *wme;
wme = TAILQ_FIRST(&wp->modes);
if (wme == NULL || wme->data != modedata)
return;
window_tree_key(wme, c, NULL, NULL, key, NULL);
}
static key_code
window_tree_get_key(void *modedata, void *itemdata, u_int line)
{
struct window_tree_modedata *data = modedata;
struct window_tree_itemdata *item = itemdata;
struct format_tree *ft;
struct session *s;
struct winlink *wl;
struct window_pane *wp;
char *expanded;
key_code key;
ft = format_create(NULL, NULL, FORMAT_NONE, 0);
window_tree_pull_item(item, &s, &wl, &wp);
if (item->type == WINDOW_TREE_SESSION)
format_defaults(ft, NULL, s, NULL, NULL);
else if (item->type == WINDOW_TREE_WINDOW)
format_defaults(ft, NULL, s, wl, NULL);
else
format_defaults(ft, NULL, s, wl, wp);
format_add(ft, "line", "%u", line);
expanded = format_expand(ft, data->key_format);
key = key_string_lookup_string(expanded);
free(expanded);
format_free(ft);
return (key);
}
static int
window_tree_swap(void *cur_itemdata, void *other_itemdata,
struct sort_criteria *sort_crit)
{
struct window_tree_itemdata *cur = cur_itemdata;
struct window_tree_itemdata *other = other_itemdata;
struct session *cur_session, *other_session;
struct winlink *cur_winlink, *other_winlink;
struct window *cur_window, *other_window;
struct window_pane *cur_pane, *other_pane;
if (cur->type != other->type)
return (0);
if (cur->type != WINDOW_TREE_WINDOW)
return (0);
window_tree_pull_item(cur, &cur_session, &cur_winlink, &cur_pane);
window_tree_pull_item(other, &other_session, &other_winlink,
&other_pane);
if (cur_session != other_session)
return (0);
/*
* Swapping indexes would not swap positions in the tree, so prevent
* swapping to avoid confusing the user.
*/
if (sort_would_window_tree_swap(sort_crit, cur_winlink, other_winlink))
return (0);
other_window = other_winlink->window;
TAILQ_REMOVE(&other_window->winlinks, other_winlink, wentry);
cur_window = cur_winlink->window;
TAILQ_REMOVE(&cur_window->winlinks, cur_winlink, wentry);
other_winlink->window = cur_window;
TAILQ_INSERT_TAIL(&cur_window->winlinks, other_winlink, wentry);
cur_winlink->window = other_window;
TAILQ_INSERT_TAIL(&other_window->winlinks, cur_winlink, wentry);
if (cur_session->curw == cur_winlink)
session_set_current(cur_session, other_winlink);
else if (cur_session->curw == other_winlink)
session_set_current(cur_session, cur_winlink);
session_group_synchronize_from(cur_session);
server_redraw_session_group(cur_session);
recalculate_sizes();
return (1);
}
static void
window_tree_sort(struct sort_criteria *sort_crit)
{
sort_crit->order_seq = window_tree_order_seq;
if (sort_crit->order == SORT_END)
sort_crit->order = sort_crit->order_seq[0];
}
static const char* window_tree_help_lines[] = {
"#[fg=themelightgrey]"
" Enter #[#{E:tree-mode-border-style},acs]x#[default] Choose selected item",
"#[fg=themelightgrey]"
" S-Up #[#{E:tree-mode-border-style},acs]x#[default] Swap current and previous window",
"#[fg=themelightgrey]"
" S-Down #[#{E:tree-mode-border-style},acs]x#[default] Swap current and next window",
"#[fg=themelightgrey]"
" x #[#{E:tree-mode-border-style},acs]x#[default] Kill selected item",
"#[fg=themelightgrey]"
" X #[#{E:tree-mode-border-style},acs]x#[default] Kill tagged items",
"#[fg=themelightgrey]"
" < #[#{E:tree-mode-border-style},acs]x#[default] Scroll previews left",
"#[fg=themelightgrey]"
" > #[#{E:tree-mode-border-style},acs]x#[default] Scroll previews right",
"#[fg=themelightgrey]"
" m #[#{E:tree-mode-border-style},acs]x#[default] Set the marked pane",
"#[fg=themelightgrey]"
" M #[#{E:tree-mode-border-style},acs]x#[default] Clear the marked pane",
"#[fg=themelightgrey]"
" i #[#{E:tree-mode-border-style},acs]x#[default] Toggle session, window and pane information",
"#[fg=themelightgrey]"
" : #[#{E:tree-mode-border-style},acs]x#[default] Run a command for each tagged item",
"#[fg=themelightgrey]"
" f #[#{E:tree-mode-border-style},acs]x#[default] Enter a format",
"#[fg=themelightgrey]"
" H #[#{E:tree-mode-border-style},acs]x#[default] Jump to the starting pane",
NULL
};
static const char**
window_tree_help(u_int *width, const char **item)
{
*width = 51;
*item = "item";
return (window_tree_help_lines);
}
static struct screen *
window_tree_init(struct window_mode_entry *wme, struct cmd_find_state *fs,
struct args *args)
{
struct window_pane *wp = wme->wp;
struct window_tree_modedata *data;
struct screen *s;
wme->data = data = xcalloc(1, sizeof *data);
data->wp = wp;
data->references = 1;
if (args_has(args, 's'))
data->type = WINDOW_TREE_SESSION;
else if (args_has(args, 'w'))
data->type = WINDOW_TREE_WINDOW;
else
data->type = WINDOW_TREE_PANE;
memcpy(&data->fs, fs, sizeof data->fs);
if (args == NULL || !args_has(args, 'F'))
data->format = xstrdup(WINDOW_TREE_DEFAULT_FORMAT);
else
data->format = xstrdup(args_get(args, 'F'));
if (args == NULL || !args_has(args, 'K'))
data->key_format = xstrdup(WINDOW_TREE_DEFAULT_KEY_FORMAT);
else
data->key_format = xstrdup(args_get(args, 'K'));
if (args == NULL || args_count(args) == 0)
data->command = xstrdup(WINDOW_TREE_DEFAULT_COMMAND);
else
data->command = xstrdup(args_string(args, 0));
data->squash_groups = !args_has(args, 'G');
data->hide_preview_this_pane = args_has(args, 'h');
if (args_has(args, 'y'))
data->prompt_flags = PROMPT_ACCEPT;
data->data = mode_tree_start(wp, args, window_tree_build,
window_tree_draw, window_tree_search, window_tree_menu, NULL,
window_tree_get_key, window_tree_swap, window_tree_sort,
window_tree_help, data, window_tree_menu_items, &s);
mode_tree_zoom(data->data, args);
mode_tree_view_name(data->data, "preview");
mode_tree_build(data->data);
mode_tree_draw(data->data);
data->type = WINDOW_TREE_NONE;
return (s);
}
static void
window_tree_destroy(struct window_tree_modedata *data)
{
u_int i;
if (--data->references != 0)
return;
for (i = 0; i < data->item_size; i++)
window_tree_free_item(data->item_list[i]);
free(data->item_list);
free(data->format);
free(data->key_format);
free(data->command);
free(data);
}
static void
window_tree_free(struct window_mode_entry *wme)
{
struct window_tree_modedata *data = wme->data;
if (data == NULL)
return;
data->dead = 1;
mode_tree_free(data->data);
window_tree_destroy(data);
}
static void
window_tree_resize(struct window_mode_entry *wme, u_int sx, u_int sy)
{
struct window_tree_modedata *data = wme->data;
mode_tree_resize(data->data, sx, sy);
}
static void
window_tree_update(struct window_mode_entry *wme)
{
struct window_tree_modedata *data = wme->data;
mode_tree_build(data->data);
mode_tree_draw(data->data);
data->wp->flags |= PANE_REDRAW;
}
static char *
window_tree_get_target(struct window_tree_itemdata *item,
struct cmd_find_state *fs)
{
struct session *s;
struct winlink *wl;
struct window_pane *wp;
char *target;
window_tree_pull_item(item, &s, &wl, &wp);
target = NULL;
switch (item->type) {
case WINDOW_TREE_NONE:
break;
case WINDOW_TREE_SESSION:
if (s == NULL)
break;
xasprintf(&target, "=%s:", s->name);
break;
case WINDOW_TREE_WINDOW:
if (s == NULL || wl == NULL)
break;
xasprintf(&target, "=%s:%u.", s->name, wl->idx);
break;
case WINDOW_TREE_PANE:
if (s == NULL || wl == NULL || wp == NULL)
break;
xasprintf(&target, "=%s:%u.%%%u", s->name, wl->idx, wp->id);
break;
}
if (target == NULL)
cmd_find_clear_state(fs, 0);
else
cmd_find_from_winlink_pane(fs, wl, wp, 0);
return (target);
}
static void
window_tree_command_each(void *modedata, void *itemdata, struct client *c,
__unused key_code key)
{
struct window_tree_modedata *data = modedata;
struct window_tree_itemdata *item = itemdata;
char *name;
struct cmd_find_state fs;
name = window_tree_get_target(item, &fs);
if (name != NULL)
mode_tree_run_command(c, &fs, data->entered, name);
free(name);
}
static enum cmd_retval
window_tree_command_done(__unused struct cmdq_item *item, void *modedata)
{
struct window_tree_modedata *data = modedata;
if (!data->dead) {
mode_tree_build(data->data);
mode_tree_draw(data->data);
data->wp->flags |= PANE_REDRAW;
}
window_tree_destroy(data);
return (CMD_RETURN_NORMAL);
}
static enum prompt_result
window_tree_command_callback(struct client *c, void *modedata, const char *s,
__unused enum prompt_key_result key)
{
struct window_tree_modedata *data = modedata;
if (s == NULL || *s == '\0' || data->dead)
return (PROMPT_CLOSE);
data->entered = s;
mode_tree_each_tagged(data->data, window_tree_command_each, c,
KEYC_NONE, 1);
data->entered = NULL;
data->references++;
cmdq_append(c, cmdq_get_callback(window_tree_command_done, data));
return (PROMPT_CLOSE);
}
static void
window_tree_command_free(void *modedata)
{
struct window_tree_modedata *data = modedata;
window_tree_destroy(data);
}
static void
window_tree_kill_each(__unused void *modedata, void *itemdata,
__unused struct client *c, __unused key_code key)
{
struct window_tree_itemdata *item = itemdata;
struct session *s;
struct winlink *wl;
struct window_pane *wp;
window_tree_pull_item(item, &s, &wl, &wp);
switch (item->type) {
case WINDOW_TREE_NONE:
break;
case WINDOW_TREE_SESSION:
if (s != NULL) {
server_destroy_session(s);
session_destroy(s, 1, __func__);
}
break;
case WINDOW_TREE_WINDOW:
if (wl != NULL)
server_kill_window(wl->window, 0);
break;
case WINDOW_TREE_PANE:
if (wp != NULL)
server_kill_pane(wp);
break;
}
}
static enum prompt_result
window_tree_kill_current_callback(struct client *c, void *modedata,
const char *s, __unused enum prompt_key_result key)
{
struct window_tree_modedata *data = modedata;
struct mode_tree_data *mtd = data->data;
if (s == NULL || *s == '\0' || data->dead)
return (PROMPT_CLOSE);
if (tolower((u_char) s[0]) != 'y' || s[1] != '\0')
return (PROMPT_CLOSE);
window_tree_kill_each(data, mode_tree_get_current(mtd), c, KEYC_NONE);
server_renumber_all();
data->references++;
cmdq_append(c, cmdq_get_callback(window_tree_command_done, data));
return (PROMPT_CLOSE);
}
static enum prompt_result
window_tree_kill_tagged_callback(struct client *c, void *modedata,
const char *s, __unused enum prompt_key_result key)
{
struct window_tree_modedata *data = modedata;
struct mode_tree_data *mtd = data->data;
if (s == NULL || *s == '\0' || data->dead)
return (PROMPT_CLOSE);
if (tolower((u_char) s[0]) != 'y' || s[1] != '\0')
return (PROMPT_CLOSE);
mode_tree_each_tagged(mtd, window_tree_kill_each, c, KEYC_NONE, 1);
server_renumber_all();
data->references++;
cmdq_append(c, cmdq_get_callback(window_tree_command_done, data));
return (PROMPT_CLOSE);
}
static key_code
window_tree_mouse(struct window_tree_modedata *data, key_code key, u_int x,
struct window_tree_itemdata *item)
{
struct session *s;
struct winlink *wl;
struct window_pane *wp;
u_int loop;
if (key != KEYC_MOUSEDOWN1_PANE)
return (KEYC_NONE);
if (data->left != -1 && x <= (u_int)data->left)
return ('<');
if (data->right != -1 && x >= (u_int)data->right)
return ('>');
if (data->left != -1)
x -= data->left;
else if (x != 0)
x--;
if (x == 0 || data->end == 0)
x = 0;
else {
x = x / data->each;
if (data->start + x >= data->end)
x = data->end - 1;
}
window_tree_pull_item(item, &s, &wl, &wp);
if (item->type == WINDOW_TREE_SESSION) {
if (s == NULL)
return (KEYC_NONE);
mode_tree_expand_current(data->data);
loop = 0;
RB_FOREACH(wl, winlinks, &s->windows) {
if (loop == data->start + x)
break;
loop++;
}
if (wl != NULL)
mode_tree_set_current(data->data, (uint64_t)wl);
return ('\r');
}
if (item->type == WINDOW_TREE_WINDOW) {
if (wl == NULL)
return (KEYC_NONE);
mode_tree_expand_current(data->data);
loop = 0;
TAILQ_FOREACH(wp, &wl->window->panes, entry) {
if (loop == data->start + x)
break;
loop++;
}
if (wp != NULL)
mode_tree_set_current(data->data, (uint64_t)wp);
return ('\r');
}
return (KEYC_NONE);
}
static void
window_tree_key(struct window_mode_entry *wme, struct client *c,
__unused struct session *s, __unused struct winlink *wl, key_code key,
struct mouse_event *m)
{
struct window_pane *wp = wme->wp;
struct window_tree_modedata *data = wme->data;
struct window_tree_itemdata *item, *new_item;
char *name, *prompt = NULL;
struct cmd_find_state fs, *fsp = &data->fs;
int finished;
u_int tagged, x, y, idx;
struct session *ns;
struct winlink *nwl;
struct window_pane *nwp;
item = mode_tree_get_current(data->data);
finished = mode_tree_key(data->data, c, &key, m, &x, &y);
again:
if (item != (new_item = mode_tree_get_current(data->data))) {
item = new_item;
data->offset = 0;
}
if (KEYC_IS_MOUSE(key) && m != NULL) {
key = window_tree_mouse(data, key, x, item);
goto again;
}
switch (key) {
case '<':
data->offset--;
break;
case '>':
data->offset++;
break;
case 'H':
mode_tree_expand(data->data, (uint64_t)fsp->s);
mode_tree_expand(data->data, (uint64_t)fsp->wl);
if (!mode_tree_set_current(data->data, (uint64_t)wme->wp))
mode_tree_set_current(data->data, (uint64_t)fsp->wl);
break;
case 'm':
window_tree_pull_item(item, &ns, &nwl, &nwp);
server_set_marked(ns, nwl, nwp);
mode_tree_build(data->data);
break;
case 'M':
server_clear_marked();
mode_tree_build(data->data);
break;
case 'i':
data->preview_is_info = !data->preview_is_info;
if (data->preview_is_info)
mode_tree_view_name(data->data, "info");
else
mode_tree_view_name(data->data, "preview");
break;
case 'x':
window_tree_pull_item(item, &ns, &nwl, &nwp);
switch (item->type) {
case WINDOW_TREE_NONE:
break;
case WINDOW_TREE_SESSION:
if (ns == NULL)
break;
xasprintf(&prompt, "Kill session %s? ", ns->name);
break;
case WINDOW_TREE_WINDOW:
if (nwl == NULL)
break;
xasprintf(&prompt, "Kill window %u? ", nwl->idx);
break;
case WINDOW_TREE_PANE:
if (nwp == NULL || window_pane_index(nwp, &idx) != 0)
break;
xasprintf(&prompt, "Kill pane %u? ", idx);
break;
}
if (prompt == NULL)
break;
data->references++;
mode_tree_set_prompt(data->data, c, prompt, "",
PROMPT_TYPE_COMMAND,
PROMPT_SINGLE|PROMPT_NOFORMAT|data->prompt_flags,
window_tree_kill_current_callback, window_tree_command_free,
data);
free(prompt);
break;
case 'X':
tagged = mode_tree_count_tagged(data->data);
if (tagged == 0)
break;
xasprintf(&prompt, "Kill %u tagged? ", tagged);
data->references++;
mode_tree_set_prompt(data->data, c, prompt, "",
PROMPT_TYPE_COMMAND,
PROMPT_SINGLE|PROMPT_NOFORMAT|data->prompt_flags,
window_tree_kill_tagged_callback, window_tree_command_free,
data);
free(prompt);
break;
case ':':
tagged = mode_tree_count_tagged(data->data);
if (tagged != 0)
xasprintf(&prompt, "(%u tagged) ", tagged);
else
xasprintf(&prompt, "(current) ");
data->references++;
mode_tree_set_prompt(data->data, c, prompt, "",
PROMPT_TYPE_COMMAND, PROMPT_NOFORMAT,
window_tree_command_callback, window_tree_command_free,
data);
free(prompt);
break;
case '\r':
name = window_tree_get_target(item, &fs);
if (name != NULL)
mode_tree_run_command(c, NULL, data->command, name);
finished = 1;
free(name);
break;
}
if (finished)
window_pane_reset_mode(wp);
else {
mode_tree_draw(data->data);
wp->flags |= PANE_REDRAW;
}
}