mirror of
https://github.com/tmux/tmux.git
synced 2024-10-31 22:58:49 +00:00
1e42689661
renumber-windows is on, the window it wants to keep could be moved. Change to renumber afterwards and also behave better if the window is linked into the session twice. GitHub issue 2287.
1280 lines
30 KiB
C
1280 lines
30 KiB
C
/* $OpenBSD$ */
|
|
|
|
/*
|
|
* 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_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,#[reverse],}" \
|
|
"#{pane_current_command}#{?pane_active,*,}#{?pane_marked,M,}" \
|
|
"#{?#{&&:#{pane_title},#{!=:#{pane_title},#{host_short}}},: \"#{pane_title}\",}" \
|
|
"," \
|
|
"#{?window_format," \
|
|
"#{?window_marked_flag,#[reverse],}" \
|
|
"#{window_name}#{window_flags}" \
|
|
"#{?#{&&:#{==:#{window_panes},1},#{&&:#{pane_title},#{!=:#{pane_title},#{host_short}}}},: \"#{pane_title}\",}" \
|
|
"," \
|
|
"#{session_windows} windows" \
|
|
"#{?session_grouped, " \
|
|
"(group #{session_group}: " \
|
|
"#{session_group_list})," \
|
|
"}" \
|
|
"#{?session_attached, (attached),}" \
|
|
"}" \
|
|
"}"
|
|
|
|
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,
|
|
.key = window_tree_key,
|
|
};
|
|
|
|
enum window_tree_sort_type {
|
|
WINDOW_TREE_BY_INDEX,
|
|
WINDOW_TREE_BY_NAME,
|
|
WINDOW_TREE_BY_TIME,
|
|
};
|
|
static const char *window_tree_sort_list[] = {
|
|
"index",
|
|
"name",
|
|
"time"
|
|
};
|
|
static struct mode_tree_sort_criteria *window_tree_sort;
|
|
|
|
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 *command;
|
|
int squash_groups;
|
|
|
|
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 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 int
|
|
window_tree_cmp_session(const void *a0, const void *b0)
|
|
{
|
|
const struct session *const *a = a0;
|
|
const struct session *const *b = b0;
|
|
const struct session *sa = *a;
|
|
const struct session *sb = *b;
|
|
int result = 0;
|
|
|
|
switch (window_tree_sort->field) {
|
|
case WINDOW_TREE_BY_INDEX:
|
|
result = sa->id - sb->id;
|
|
break;
|
|
case WINDOW_TREE_BY_TIME:
|
|
if (timercmp(&sa->activity_time, &sb->activity_time, >)) {
|
|
result = -1;
|
|
break;
|
|
}
|
|
if (timercmp(&sa->activity_time, &sb->activity_time, <)) {
|
|
result = 1;
|
|
break;
|
|
}
|
|
/* FALLTHROUGH */
|
|
case WINDOW_TREE_BY_NAME:
|
|
result = strcmp(sa->name, sb->name);
|
|
break;
|
|
}
|
|
|
|
if (window_tree_sort->reversed)
|
|
result = -result;
|
|
return (result);
|
|
}
|
|
|
|
static int
|
|
window_tree_cmp_window(const void *a0, const void *b0)
|
|
{
|
|
const struct winlink *const *a = a0;
|
|
const struct winlink *const *b = b0;
|
|
const struct winlink *wla = *a;
|
|
const struct winlink *wlb = *b;
|
|
struct window *wa = wla->window;
|
|
struct window *wb = wlb->window;
|
|
int result = 0;
|
|
|
|
switch (window_tree_sort->field) {
|
|
case WINDOW_TREE_BY_INDEX:
|
|
result = wla->idx - wlb->idx;
|
|
break;
|
|
case WINDOW_TREE_BY_TIME:
|
|
if (timercmp(&wa->activity_time, &wb->activity_time, >)) {
|
|
result = -1;
|
|
break;
|
|
}
|
|
if (timercmp(&wa->activity_time, &wb->activity_time, <)) {
|
|
result = 1;
|
|
break;
|
|
}
|
|
/* FALLTHROUGH */
|
|
case WINDOW_TREE_BY_NAME:
|
|
result = strcmp(wa->name, wb->name);
|
|
break;
|
|
}
|
|
|
|
if (window_tree_sort->reversed)
|
|
result = -result;
|
|
return (result);
|
|
}
|
|
|
|
static int
|
|
window_tree_cmp_pane(const void *a0, const void *b0)
|
|
{
|
|
const struct window_pane *const *a = a0;
|
|
const struct window_pane *const *b = b0;
|
|
int result;
|
|
|
|
if (window_tree_sort->field == WINDOW_TREE_BY_TIME)
|
|
result = (*a)->active_point - (*b)->active_point;
|
|
else {
|
|
/*
|
|
* Panes don't have names, so use number order for any other
|
|
* sort field.
|
|
*/
|
|
result = (*a)->id - (*b)->id;
|
|
}
|
|
if (window_tree_sort->reversed)
|
|
result = -result;
|
|
return (result);
|
|
}
|
|
|
|
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;
|
|
char *name, *text;
|
|
u_int idx;
|
|
|
|
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;
|
|
|
|
text = format_single(NULL, data->format, NULL, s, wl, wp);
|
|
xasprintf(&name, "%u", idx);
|
|
|
|
mode_tree_add(data->data, parent, item, (uint64_t)wp, name, text, -1);
|
|
free(text);
|
|
free(name);
|
|
}
|
|
|
|
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 mode_tree_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 *wp, **l;
|
|
u_int n, i;
|
|
int expanded;
|
|
|
|
item = window_tree_add_item(data);
|
|
item->type = WINDOW_TREE_WINDOW;
|
|
item->session = s->id;
|
|
item->winlink = wl->idx;
|
|
item->pane = -1;
|
|
|
|
text = format_single(NULL, data->format, NULL, s, wl, NULL);
|
|
xasprintf(&name, "%u", wl->idx);
|
|
|
|
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);
|
|
|
|
if ((wp = TAILQ_FIRST(&wl->window->panes)) == NULL)
|
|
goto empty;
|
|
if (TAILQ_NEXT(wp, entry) == NULL) {
|
|
if (!window_tree_filter_pane(s, wl, wp, filter))
|
|
goto empty;
|
|
return (1);
|
|
}
|
|
|
|
l = NULL;
|
|
n = 0;
|
|
|
|
TAILQ_FOREACH(wp, &wl->window->panes, entry) {
|
|
if (!window_tree_filter_pane(s, wl, wp, filter))
|
|
continue;
|
|
l = xreallocarray(l, n + 1, sizeof *l);
|
|
l[n++] = wp;
|
|
}
|
|
if (n == 0)
|
|
goto empty;
|
|
|
|
window_tree_sort = sort_crit;
|
|
qsort(l, n, sizeof *l, window_tree_cmp_pane);
|
|
|
|
for (i = 0; i < n; i++)
|
|
window_tree_build_pane(s, wl, l[i], modedata, mti);
|
|
free(l);
|
|
return (1);
|
|
|
|
empty:
|
|
window_tree_free_item(item);
|
|
data->item_size--;
|
|
mode_tree_remove(data->data, mti);
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
window_tree_build_session(struct session *s, void *modedata,
|
|
struct mode_tree_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, **l;
|
|
u_int n, i, empty;
|
|
int expanded;
|
|
|
|
item = window_tree_add_item(data);
|
|
item->type = WINDOW_TREE_SESSION;
|
|
item->session = s->id;
|
|
item->winlink = -1;
|
|
item->pane = -1;
|
|
|
|
text = format_single(NULL, data->format, NULL, s, NULL, NULL);
|
|
|
|
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 = NULL;
|
|
n = 0;
|
|
RB_FOREACH(wl, winlinks, &s->windows) {
|
|
l = xreallocarray(l, n + 1, sizeof *l);
|
|
l[n++] = wl;
|
|
}
|
|
window_tree_sort = sort_crit;
|
|
qsort(l, n, sizeof *l, window_tree_cmp_window);
|
|
|
|
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);
|
|
}
|
|
free(l);
|
|
}
|
|
|
|
static void
|
|
window_tree_build(void *modedata, struct mode_tree_sort_criteria *sort_crit,
|
|
uint64_t *tag, const char *filter)
|
|
{
|
|
struct window_tree_modedata *data = modedata;
|
|
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 = NULL;
|
|
n = 0;
|
|
RB_FOREACH(s, sessions, &sessions) {
|
|
if (data->squash_groups &&
|
|
(sg = session_group_contains(s)) != NULL) {
|
|
if ((sg == current && s != data->fs.s) ||
|
|
(sg != current && s != TAILQ_FIRST(&sg->sessions)))
|
|
continue;
|
|
}
|
|
l = xreallocarray(l, n + 1, sizeof *l);
|
|
l[n++] = s;
|
|
}
|
|
window_tree_sort = sort_crit;
|
|
qsort(l, n, sizeof *l, window_tree_cmp_session);
|
|
|
|
for (i = 0; i < n; i++)
|
|
window_tree_build_session(l[i], modedata, sort_crit, filter);
|
|
free(l);
|
|
|
|
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)
|
|
*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 *gc, const char *label)
|
|
{
|
|
size_t len;
|
|
u_int ox, oy;
|
|
|
|
len = strlen(label);
|
|
if (sx == 0 || sy == 1 || len > sx)
|
|
return;
|
|
ox = (sx - len + 1) / 2;
|
|
oy = (sy + 1) / 2;
|
|
|
|
if (ox > 1 && ox + len < sx - 1 && sy >= 3) {
|
|
screen_write_cursormove(ctx, px + ox - 1, py + oy - 1, 0);
|
|
screen_write_box(ctx, len + 2, 3);
|
|
}
|
|
screen_write_cursormove(ctx, px + ox, py + oy, 0);
|
|
screen_write_puts(ctx, gc, "%s", label);
|
|
}
|
|
|
|
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 options *oo = s->options;
|
|
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;
|
|
int colour, active_colour, left, right;
|
|
char *label;
|
|
|
|
total = winlink_count(&s->windows);
|
|
|
|
memcpy(&gc, &grid_default_cell, sizeof gc);
|
|
colour = options_get_number(oo, "display-panes-colour");
|
|
active_colour = options_get_number(oo, "display-panes-active-colour");
|
|
|
|
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;
|
|
|
|
if (left) {
|
|
data->left = cx + 2;
|
|
screen_write_cursormove(ctx, cx + 2, cy, 0);
|
|
screen_write_vline(ctx, sy, 0, 0);
|
|
screen_write_cursormove(ctx, cx, cy + sy / 2, 0);
|
|
screen_write_puts(ctx, &grid_default_cell, "<");
|
|
} 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);
|
|
screen_write_cursormove(ctx, cx + sx - 1, cy + sy / 2, 0);
|
|
screen_write_puts(ctx, &grid_default_cell, ">");
|
|
} 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;
|
|
|
|
if (wl == s->curw)
|
|
gc.fg = active_colour;
|
|
else
|
|
gc.fg = colour;
|
|
|
|
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);
|
|
|
|
xasprintf(&label, " %u:%s ", wl->idx, w->name);
|
|
if (strlen(label) > width)
|
|
xasprintf(&label, " %u ", wl->idx);
|
|
window_tree_draw_label(ctx, cx + offset, cy, width, sy, &gc,
|
|
label);
|
|
free(label);
|
|
|
|
if (loop != end - 1) {
|
|
screen_write_cursormove(ctx, cx + offset + width, cy, 0);
|
|
screen_write_vline(ctx, sy, 0, 0);
|
|
}
|
|
loop++;
|
|
|
|
i++;
|
|
}
|
|
}
|
|
|
|
static void
|
|
window_tree_draw_window(struct window_tree_modedata *data, struct session *s,
|
|
struct window *w, struct screen_write_ctx *ctx, u_int sx, u_int sy)
|
|
{
|
|
struct options *oo = s->options;
|
|
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;
|
|
int colour, active_colour, left, right, pane_idx;
|
|
char *label;
|
|
|
|
total = window_count_panes(w);
|
|
|
|
memcpy(&gc, &grid_default_cell, sizeof gc);
|
|
colour = options_get_number(oo, "display-panes-colour");
|
|
active_colour = options_get_number(oo, "display-panes-active-colour");
|
|
|
|
if (sx / total < 24) {
|
|
visible = sx / 24;
|
|
if (visible == 0)
|
|
visible = 1;
|
|
} else
|
|
visible = total;
|
|
|
|
current = 0;
|
|
TAILQ_FOREACH(wp, &w->panes, entry) {
|
|
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;
|
|
|
|
if (left) {
|
|
data->left = cx + 2;
|
|
screen_write_cursormove(ctx, cx + 2, cy, 0);
|
|
screen_write_vline(ctx, sy, 0, 0);
|
|
screen_write_cursormove(ctx, cx, cy + sy / 2, 0);
|
|
screen_write_puts(ctx, &grid_default_cell, "<");
|
|
} 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);
|
|
screen_write_cursormove(ctx, cx + sx - 1, cy + sy / 2, 0);
|
|
screen_write_puts(ctx, &grid_default_cell, ">");
|
|
} else
|
|
data->right = -1;
|
|
|
|
data->start = start;
|
|
data->end = end;
|
|
data->each = each;
|
|
|
|
i = loop = 0;
|
|
TAILQ_FOREACH(wp, &w->panes, entry) {
|
|
if (loop == end)
|
|
break;
|
|
if (loop < start) {
|
|
loop++;
|
|
continue;
|
|
}
|
|
|
|
if (wp == w->active)
|
|
gc.fg = active_colour;
|
|
else
|
|
gc.fg = colour;
|
|
|
|
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);
|
|
|
|
if (window_pane_index(wp, &pane_idx) != 0)
|
|
pane_idx = loop;
|
|
xasprintf(&label, " %u ", pane_idx);
|
|
window_tree_draw_label(ctx, cx + offset, cy, each, sy, &gc,
|
|
label);
|
|
free(label);
|
|
|
|
if (loop != end - 1) {
|
|
screen_write_cursormove(ctx, cx + offset + width, cy, 0);
|
|
screen_write_vline(ctx, sy, 0, 0);
|
|
}
|
|
loop++;
|
|
|
|
i++;
|
|
}
|
|
}
|
|
|
|
static void
|
|
window_tree_draw(void *modedata, void *itemdata, struct screen_write_ctx *ctx,
|
|
u_int sx, u_int sy)
|
|
{
|
|
struct window_tree_itemdata *item = itemdata;
|
|
struct session *sp;
|
|
struct winlink *wlp;
|
|
struct window_pane *wp;
|
|
|
|
window_tree_pull_item(item, &sp, &wlp, &wp);
|
|
if (wp == NULL)
|
|
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, wlp->window, ctx, sx, sy);
|
|
break;
|
|
case WINDOW_TREE_PANE:
|
|
screen_write_preview(ctx, &wp->base, sx, sy);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int
|
|
window_tree_search(__unused void *modedata, void *itemdata, const char *ss)
|
|
{
|
|
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);
|
|
return (strstr(s->name, ss) != NULL);
|
|
case WINDOW_TREE_WINDOW:
|
|
if (s == NULL || wl == NULL)
|
|
return (0);
|
|
return (strstr(wl->window->name, ss) != NULL);
|
|
case WINDOW_TREE_PANE:
|
|
if (s == NULL || wl == NULL || wp == NULL)
|
|
break;
|
|
cmd = get_proc_name(wp->fd, wp->tty);
|
|
if (cmd == NULL || *cmd == '\0')
|
|
return (0);
|
|
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 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->argc == 0)
|
|
data->command = xstrdup(WINDOW_TREE_DEFAULT_COMMAND);
|
|
else
|
|
data->command = xstrdup(args->argv[0]);
|
|
data->squash_groups = !args_has(args, 'G');
|
|
|
|
data->data = mode_tree_start(wp, args, window_tree_build,
|
|
window_tree_draw, window_tree_search, window_tree_menu, NULL, data,
|
|
window_tree_menu_items, window_tree_sort_list,
|
|
nitems(window_tree_sort_list), &s);
|
|
mode_tree_zoom(data->data, args);
|
|
|
|
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->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 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 int
|
|
window_tree_command_callback(struct client *c, void *modedata, const char *s,
|
|
__unused int done)
|
|
{
|
|
struct window_tree_modedata *data = modedata;
|
|
|
|
if (s == NULL || *s == '\0' || data->dead)
|
|
return (0);
|
|
|
|
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 (0);
|
|
}
|
|
|
|
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, 1);
|
|
break;
|
|
case WINDOW_TREE_PANE:
|
|
if (wp != NULL)
|
|
server_kill_pane(wp);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int
|
|
window_tree_kill_current_callback(struct client *c, void *modedata,
|
|
const char *s, __unused int done)
|
|
{
|
|
struct window_tree_modedata *data = modedata;
|
|
struct mode_tree_data *mtd = data->data;
|
|
|
|
if (s == NULL || *s == '\0' || data->dead)
|
|
return (0);
|
|
if (tolower((u_char) s[0]) != 'y' || s[1] != '\0')
|
|
return (0);
|
|
|
|
window_tree_kill_each(data, mode_tree_get_current(mtd), c, KEYC_NONE);
|
|
|
|
data->references++;
|
|
cmdq_append(c, cmdq_get_callback(window_tree_command_done, data));
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
window_tree_kill_tagged_callback(struct client *c, void *modedata,
|
|
const char *s, __unused int done)
|
|
{
|
|
struct window_tree_modedata *data = modedata;
|
|
struct mode_tree_data *mtd = data->data;
|
|
|
|
if (s == NULL || *s == '\0' || data->dead)
|
|
return (0);
|
|
if (tolower((u_char) s[0]) != 'y' || s[1] != '\0')
|
|
return (0);
|
|
|
|
mode_tree_each_tagged(mtd, window_tree_kill_each, c, KEYC_NONE, 1);
|
|
|
|
data->references++;
|
|
cmdq_append(c, cmdq_get_callback(window_tree_command_done, data));
|
|
|
|
return (0);
|
|
}
|
|
|
|
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);
|
|
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);
|
|
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 '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++;
|
|
status_prompt_set(c, NULL, prompt, "",
|
|
window_tree_kill_current_callback, window_tree_command_free,
|
|
data, PROMPT_SINGLE|PROMPT_NOFORMAT);
|
|
free(prompt);
|
|
break;
|
|
case 'X':
|
|
tagged = mode_tree_count_tagged(data->data);
|
|
if (tagged == 0)
|
|
break;
|
|
xasprintf(&prompt, "Kill %u tagged? ", tagged);
|
|
data->references++;
|
|
status_prompt_set(c, NULL, prompt, "",
|
|
window_tree_kill_tagged_callback, window_tree_command_free,
|
|
data, PROMPT_SINGLE|PROMPT_NOFORMAT);
|
|
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++;
|
|
status_prompt_set(c, NULL, prompt, "",
|
|
window_tree_command_callback, window_tree_command_free,
|
|
data, PROMPT_NOFORMAT);
|
|
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;
|
|
}
|
|
}
|