xref: /netbsd-src/external/bsd/tmux/dist/window-tree.c (revision f3cfa6f6ce31685c6c4a758bc430e69eb99f50a4)
1 /* $OpenBSD$ */
2 
3 /*
4  * Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 
21 #include <ctype.h>
22 #include <stdlib.h>
23 #include <string.h>
24 
25 #include "tmux.h"
26 
27 static struct screen	*window_tree_init(struct window_pane *,
28 			     struct cmd_find_state *, struct args *);
29 static void		 window_tree_free(struct window_pane *);
30 static void		 window_tree_resize(struct window_pane *, u_int, u_int);
31 static void		 window_tree_key(struct window_pane *,
32 			     struct client *, struct session *, key_code,
33 			     struct mouse_event *);
34 
35 #define WINDOW_TREE_DEFAULT_COMMAND "switch-client -t '%%'"
36 
37 #define WINDOW_TREE_DEFAULT_FORMAT \
38 	"#{?pane_format," \
39 		"#{pane_current_command} \"#{pane_title}\"" \
40 	"," \
41 		"#{?window_format," \
42 			"#{window_name}#{window_flags} " \
43 			"(#{window_panes} panes)" \
44 			"#{?#{==:#{window_panes},1}, \"#{pane_title}\",}" \
45 		"," \
46 			"#{session_windows} windows" \
47 			"#{?session_grouped, " \
48 				"(group #{session_group}: " \
49 				"#{session_group_list})," \
50 			"}" \
51 			"#{?session_attached, (attached),}" \
52 		"}" \
53 	"}"
54 
55 const struct window_mode window_tree_mode = {
56 	.name = "tree-mode",
57 
58 	.init = window_tree_init,
59 	.free = window_tree_free,
60 	.resize = window_tree_resize,
61 	.key = window_tree_key,
62 };
63 
64 enum window_tree_sort_type {
65 	WINDOW_TREE_BY_INDEX,
66 	WINDOW_TREE_BY_NAME,
67 	WINDOW_TREE_BY_TIME,
68 };
69 static const char *window_tree_sort_list[] = {
70 	"index",
71 	"name",
72 	"time"
73 };
74 
75 enum window_tree_type {
76 	WINDOW_TREE_NONE,
77 	WINDOW_TREE_SESSION,
78 	WINDOW_TREE_WINDOW,
79 	WINDOW_TREE_PANE,
80 };
81 
82 struct window_tree_itemdata {
83 	enum window_tree_type	type;
84 	int			session;
85 	int			winlink;
86 	int			pane;
87 };
88 
89 struct window_tree_modedata {
90 	struct window_pane		 *wp;
91 	int				  dead;
92 	int				  references;
93 
94 	struct mode_tree_data		 *data;
95 	char				 *format;
96 	char				 *command;
97 	int				  squash_groups;
98 
99 	struct window_tree_itemdata	**item_list;
100 	u_int				  item_size;
101 
102 	const char			 *entered;
103 
104 	struct cmd_find_state		  fs;
105 	enum window_tree_type		  type;
106 
107 	int				  offset;
108 
109 	int				  left;
110 	int				  right;
111 	u_int				  start;
112 	u_int				  end;
113 	u_int				  each;
114 };
115 
116 static void
117 window_tree_pull_item(struct window_tree_itemdata *item, struct session **sp,
118     struct winlink **wlp, struct window_pane **wp)
119 {
120 	*wp = NULL;
121 	*wlp = NULL;
122 	*sp = session_find_by_id(item->session);
123 	if (*sp == NULL)
124 		return;
125 	if (item->type == WINDOW_TREE_SESSION) {
126 		*wlp = (*sp)->curw;
127 		*wp = (*wlp)->window->active;
128 		return;
129 	}
130 
131 	*wlp = winlink_find_by_index(&(*sp)->windows, item->winlink);
132 	if (*wlp == NULL) {
133 		*sp = NULL;
134 		return;
135 	}
136 	if (item->type == WINDOW_TREE_WINDOW) {
137 		*wp = (*wlp)->window->active;
138 		return;
139 	}
140 
141 	*wp = window_pane_find_by_id(item->pane);
142 	if (!window_has_pane((*wlp)->window, *wp))
143 		*wp = NULL;
144 	if (*wp == NULL) {
145 		*sp = NULL;
146 		*wlp = NULL;
147 		return;
148 	}
149 }
150 
151 static struct window_tree_itemdata *
152 window_tree_add_item(struct window_tree_modedata *data)
153 {
154 	struct window_tree_itemdata	*item;
155 
156 	data->item_list = xreallocarray(data->item_list, data->item_size + 1,
157 	    sizeof *data->item_list);
158 	item = data->item_list[data->item_size++] = xcalloc(1, sizeof *item);
159 	return (item);
160 }
161 
162 static void
163 window_tree_free_item(struct window_tree_itemdata *item)
164 {
165 	free(item);
166 }
167 
168 static int
169 window_tree_cmp_session_name(const void *a0, const void *b0)
170 {
171 	const struct session *const *a = a0;
172 	const struct session *const *b = b0;
173 
174 	return (strcmp((*a)->name, (*b)->name));
175 }
176 
177 static int
178 window_tree_cmp_session_time(const void *a0, const void *b0)
179 {
180 	const struct session *const *a = a0;
181 	const struct session *const *b = b0;
182 
183 	if (timercmp(&(*a)->activity_time, &(*b)->activity_time, >))
184 		return (-1);
185 	if (timercmp(&(*a)->activity_time, &(*b)->activity_time, <))
186 		return (1);
187 	return (strcmp((*a)->name, (*b)->name));
188 }
189 
190 static int
191 window_tree_cmp_window_name(const void *a0, const void *b0)
192 {
193 	const struct winlink *const *a = a0;
194 	const struct winlink *const *b = b0;
195 
196 	return (strcmp((*a)->window->name, (*b)->window->name));
197 }
198 
199 static int
200 window_tree_cmp_window_time(const void *a0, const void *b0)
201 {
202 	const struct winlink *const *a = a0;
203 	const struct winlink *const *b = b0;
204 
205 	if (timercmp(&(*a)->window->activity_time,
206 	    &(*b)->window->activity_time, >))
207 		return (-1);
208 	if (timercmp(&(*a)->window->activity_time,
209 	    &(*b)->window->activity_time, <))
210 		return (1);
211 	return (strcmp((*a)->window->name, (*b)->window->name));
212 }
213 
214 static int
215 window_tree_cmp_pane_time(const void *a0, const void *b0)
216 {
217 	const struct window_pane *const *a = a0;
218 	const struct window_pane *const *b = b0;
219 
220 	if ((*a)->active_point < (*b)->active_point)
221 		return (-1);
222 	if ((*a)->active_point > (*b)->active_point)
223 		return (1);
224 	return (0);
225 }
226 
227 static void
228 window_tree_build_pane(struct session *s, struct winlink *wl,
229     struct window_pane *wp, void *modedata, struct mode_tree_item *parent)
230 {
231 	struct window_tree_modedata	*data = modedata;
232 	struct window_tree_itemdata	*item;
233 	char				*name, *text;
234 	u_int				 idx;
235 
236 	window_pane_index(wp, &idx);
237 
238 	item = window_tree_add_item(data);
239 	item->type = WINDOW_TREE_PANE;
240 	item->session = s->id;
241 	item->winlink = wl->idx;
242 	item->pane = wp->id;
243 
244 	text = format_single(NULL, data->format, NULL, s, wl, wp);
245 	xasprintf(&name, "%u", idx);
246 
247 	mode_tree_add(data->data, parent, item, (uintptr_t)wp, name, text, -1);
248 	free(text);
249 	free(name);
250 }
251 
252 static int
253 window_tree_filter_pane(struct session *s, struct winlink *wl,
254     struct window_pane *wp, const char *filter)
255 {
256 	char	*cp;
257 	int	 result;
258 
259 	if (filter == NULL)
260 		return (1);
261 
262 	cp = format_single(NULL, filter, NULL, s, wl, wp);
263 	result = format_true(cp);
264 	free(cp);
265 
266 	return (result);
267 }
268 
269 static int
270 window_tree_build_window(struct session *s, struct winlink *wl, void* modedata,
271     u_int sort_type, struct mode_tree_item *parent, const char *filter)
272 {
273 	struct window_tree_modedata	*data = modedata;
274 	struct window_tree_itemdata	*item;
275 	struct mode_tree_item		*mti;
276 	char				*name, *text;
277 	struct window_pane		*wp, **l;
278 	u_int				 n, i;
279 	int				 expanded;
280 
281 	item = window_tree_add_item(data);
282 	item->type = WINDOW_TREE_WINDOW;
283 	item->session = s->id;
284 	item->winlink = wl->idx;
285 	item->pane = -1;
286 
287 	text = format_single(NULL, data->format, NULL, s, wl, NULL);
288 	xasprintf(&name, "%u", wl->idx);
289 
290 	if (data->type == WINDOW_TREE_SESSION ||
291 	    data->type == WINDOW_TREE_WINDOW)
292 		expanded = 0;
293 	else
294 		expanded = 1;
295 	mti = mode_tree_add(data->data, parent, item, (uintptr_t)wl, name, text,
296 	    expanded);
297 	free(text);
298 	free(name);
299 
300 	if ((wp = TAILQ_FIRST(&wl->window->panes)) == NULL)
301 		goto empty;
302 	if (TAILQ_NEXT(wp, entry) == NULL) {
303 		if (!window_tree_filter_pane(s, wl, wp, filter))
304 			goto empty;
305 		return (1);
306 	}
307 
308 	l = NULL;
309 	n = 0;
310 
311 	TAILQ_FOREACH(wp, &wl->window->panes, entry) {
312 		if (!window_tree_filter_pane(s, wl, wp, filter))
313 			continue;
314 		l = xreallocarray(l, n + 1, sizeof *l);
315 		l[n++] = wp;
316 	}
317 	if (n == 0)
318 		goto empty;
319 
320 	switch (sort_type) {
321 	case WINDOW_TREE_BY_INDEX:
322 		break;
323 	case WINDOW_TREE_BY_NAME:
324 		/* Panes don't have names, so leave in number order. */
325 		break;
326 	case WINDOW_TREE_BY_TIME:
327 		qsort(l, n, sizeof *l, window_tree_cmp_pane_time);
328 		break;
329 	}
330 
331 	for (i = 0; i < n; i++)
332 		window_tree_build_pane(s, wl, l[i], modedata, mti);
333 	free(l);
334 	return (1);
335 
336 empty:
337 	window_tree_free_item(item);
338 	data->item_size--;
339 	mode_tree_remove(data->data, mti);
340 	return (0);
341 }
342 
343 static void
344 window_tree_build_session(struct session *s, void* modedata,
345     u_int sort_type, const char *filter)
346 {
347 	struct window_tree_modedata	*data = modedata;
348 	struct window_tree_itemdata	*item;
349 	struct mode_tree_item		*mti;
350 	char				*text;
351 	struct winlink			*wl, **l;
352 	u_int				 n, i, empty;
353 	int				 expanded;
354 
355 	item = window_tree_add_item(data);
356 	item->type = WINDOW_TREE_SESSION;
357 	item->session = s->id;
358 	item->winlink = -1;
359 	item->pane = -1;
360 
361 	text = format_single(NULL, data->format, NULL, s, NULL, NULL);
362 
363 	if (data->type == WINDOW_TREE_SESSION)
364 		expanded = 0;
365 	else
366 		expanded = 1;
367 	mti = mode_tree_add(data->data, NULL, item, (uintptr_t)s, s->name, text,
368 	    expanded);
369 	free(text);
370 
371 	l = NULL;
372 	n = 0;
373 	RB_FOREACH(wl, winlinks, &s->windows) {
374 		l = xreallocarray(l, n + 1, sizeof *l);
375 		l[n++] = wl;
376 	}
377 	switch (sort_type) {
378 	case WINDOW_TREE_BY_INDEX:
379 		break;
380 	case WINDOW_TREE_BY_NAME:
381 		qsort(l, n, sizeof *l, window_tree_cmp_window_name);
382 		break;
383 	case WINDOW_TREE_BY_TIME:
384 		qsort(l, n, sizeof *l, window_tree_cmp_window_time);
385 		break;
386 	}
387 
388 	empty = 0;
389 	for (i = 0; i < n; i++) {
390 		if (!window_tree_build_window(s, l[i], modedata, sort_type, mti,
391 		    filter))
392 			empty++;
393 	}
394 	if (empty == n) {
395 		window_tree_free_item(item);
396 		data->item_size--;
397 		mode_tree_remove(data->data, mti);
398 	}
399 	free(l);
400 }
401 
402 static void
403 window_tree_build(void *modedata, u_int sort_type, uint64_t *tag,
404     const char *filter)
405 {
406 	struct window_tree_modedata	*data = modedata;
407 	struct session			*s, **l;
408 	struct session_group		*sg, *current;
409 	u_int				 n, i;
410 
411 	current = session_group_contains(data->fs.s);
412 
413 	for (i = 0; i < data->item_size; i++)
414 		window_tree_free_item(data->item_list[i]);
415 	free(data->item_list);
416 	data->item_list = NULL;
417 	data->item_size = 0;
418 
419 	l = NULL;
420 	n = 0;
421 	RB_FOREACH(s, sessions, &sessions) {
422 		if (data->squash_groups &&
423 		    (sg = session_group_contains(s)) != NULL) {
424 			if ((sg == current && s != data->fs.s) ||
425 			    (sg != current && s != TAILQ_FIRST(&sg->sessions)))
426 				continue;
427 		}
428 		l = xreallocarray(l, n + 1, sizeof *l);
429 		l[n++] = s;
430 	}
431 	switch (sort_type) {
432 	case WINDOW_TREE_BY_INDEX:
433 		break;
434 	case WINDOW_TREE_BY_NAME:
435 		qsort(l, n, sizeof *l, window_tree_cmp_session_name);
436 		break;
437 	case WINDOW_TREE_BY_TIME:
438 		qsort(l, n, sizeof *l, window_tree_cmp_session_time);
439 		break;
440 	}
441 
442 	for (i = 0; i < n; i++)
443 		window_tree_build_session(l[i], modedata, sort_type, filter);
444 	free(l);
445 
446 	switch (data->type) {
447 	case WINDOW_TREE_NONE:
448 		break;
449 	case WINDOW_TREE_SESSION:
450 		*tag = (uintptr_t)data->fs.s;
451 		break;
452 	case WINDOW_TREE_WINDOW:
453 		*tag = (uintptr_t)data->fs.wl;
454 		break;
455 	case WINDOW_TREE_PANE:
456 		if (window_count_panes(data->fs.wl->window) == 1)
457 			*tag = (uintptr_t)data->fs.wl;
458 		else
459 			*tag = (uintptr_t)data->fs.wp;
460 		break;
461 	}
462 }
463 
464 
465 static void
466 window_tree_draw_label(struct screen_write_ctx *ctx, u_int px, u_int py,
467     u_int sx, u_int sy, const struct grid_cell *gc, const char *label)
468 {
469 	size_t	 len;
470 	u_int	 ox, oy;
471 
472 	len = strlen(label);
473 	if (sx == 0 || sy == 1 || len > sx)
474 		return;
475 	ox = (sx - len + 1) / 2;
476 	oy = (sy + 1) / 2;
477 
478 	if (ox > 1 && ox + len < sx - 1 && sy >= 3) {
479 		screen_write_cursormove(ctx, px + ox - 1, py + oy - 1);
480 		screen_write_box(ctx, len + 2, 3);
481 	}
482 	screen_write_cursormove(ctx, px + ox, py + oy);
483 	screen_write_puts(ctx, gc, "%s", label);
484 }
485 
486 static void
487 window_tree_draw_session(struct window_tree_modedata *data, struct session *s,
488     struct screen_write_ctx *ctx, u_int sx, u_int sy)
489 {
490 	struct options		*oo = s->options;
491 	struct winlink		*wl;
492 	struct window		*w;
493 	u_int			 cx = ctx->s->cx, cy = ctx->s->cy;
494 	u_int			 loop, total, visible, each, width, offset;
495 	u_int			 current, start, end, remaining, i;
496 	struct grid_cell	 gc;
497 	int			 colour, active_colour, left, right;
498 	char			*label;
499 
500 	total = winlink_count(&s->windows);
501 
502 	memcpy(&gc, &grid_default_cell, sizeof gc);
503 	colour = options_get_number(oo, "display-panes-colour");
504 	active_colour = options_get_number(oo, "display-panes-active-colour");
505 
506 	if (sx / total < 24) {
507 		visible = sx / 24;
508 		if (visible == 0)
509 			visible = 1;
510 	} else
511 		visible = total;
512 
513 	current = 0;
514 	RB_FOREACH(wl, winlinks, &s->windows) {
515 		if (wl == s->curw)
516 			break;
517 		current++;
518 	}
519 
520 	if (current < visible) {
521 		start = 0;
522 		end = visible;
523 	} else if (current >= total - visible) {
524 		start = total - visible;
525 		end = total;
526 	} else {
527 		start = current - (visible / 2);
528 		end = start + visible;
529 	}
530 
531 	if (data->offset < -(int)start)
532 		data->offset = -(int)start;
533 	if (data->offset > (int)(total - end))
534 		data->offset = (int)(total - end);
535 	start += data->offset;
536 	end += data->offset;
537 
538 	left = (start != 0);
539 	right = (end != total);
540 	if (((left && right) && sx <= 6) || ((left || right) && sx <= 3))
541 		left = right = 0;
542 	if (left && right) {
543 		each = (sx - 6) / visible;
544 		remaining = (sx - 6) - (visible * each);
545 	} else if (left || right) {
546 		each = (sx - 3) / visible;
547 		remaining = (sx - 3) - (visible * each);
548 	} else {
549 		each = sx / visible;
550 		remaining = sx - (visible * each);
551 	}
552 	if (each == 0)
553 		return;
554 
555 	if (left) {
556 		data->left = cx + 2;
557 		screen_write_cursormove(ctx, cx + 2, cy);
558 		screen_write_vline(ctx, sy, 0, 0);
559 		screen_write_cursormove(ctx, cx, cy + sy / 2);
560 		screen_write_puts(ctx, &grid_default_cell, "<");
561 	} else
562 		data->left = -1;
563 	if (right) {
564 		data->right = cx + sx - 3;
565 		screen_write_cursormove(ctx, cx + sx - 3, cy);
566 		screen_write_vline(ctx, sy, 0, 0);
567 		screen_write_cursormove(ctx, cx + sx - 1, cy + sy / 2);
568 		screen_write_puts(ctx, &grid_default_cell, ">");
569 	} else
570 		data->right = -1;
571 
572 	data->start = start;
573 	data->end = end;
574 	data->each = each;
575 
576 	i = loop = 0;
577 	RB_FOREACH(wl, winlinks, &s->windows) {
578 		if (loop == end)
579 			break;
580 		if (loop < start) {
581 			loop++;
582 			continue;
583 		}
584 		w = wl->window;
585 
586 		if (wl == s->curw)
587 			gc.fg = active_colour;
588 		else
589 			gc.fg = colour;
590 
591 		if (left)
592 			offset = 3 + (i * each);
593 		else
594 			offset = (i * each);
595 		if (loop == end - 1)
596 			width = each + remaining;
597 		else
598 			width = each - 1;
599 
600 		screen_write_cursormove(ctx, cx + offset, cy);
601 		screen_write_preview(ctx, &w->active->base, width, sy);
602 
603 		xasprintf(&label, " %u:%s ", wl->idx, w->name);
604 		if (strlen(label) > width)
605 			xasprintf(&label, " %u ", wl->idx);
606 		window_tree_draw_label(ctx, cx + offset, cy, width, sy, &gc,
607 		    label);
608 		free(label);
609 
610 		if (loop != end - 1) {
611 			screen_write_cursormove(ctx, cx + offset + width, cy);
612 			screen_write_vline(ctx, sy, 0, 0);
613 		}
614 		loop++;
615 
616 		i++;
617 	}
618 }
619 
620 static void
621 window_tree_draw_window(struct window_tree_modedata *data, struct session *s,
622     struct window *w, struct screen_write_ctx *ctx, u_int sx, u_int sy)
623 {
624 	struct options		*oo = s->options;
625 	struct window_pane	*wp;
626 	u_int			 cx = ctx->s->cx, cy = ctx->s->cy;
627 	u_int			 loop, total, visible, each, width, offset;
628 	u_int			 current, start, end, remaining, i;
629 	u_int			 pane_idx;
630 	struct grid_cell	 gc;
631 	int			 colour, active_colour, left, right;
632 	char			*label;
633 
634 	total = window_count_panes(w);
635 
636 	memcpy(&gc, &grid_default_cell, sizeof gc);
637 	colour = options_get_number(oo, "display-panes-colour");
638 	active_colour = options_get_number(oo, "display-panes-active-colour");
639 
640 	if (sx / total < 24) {
641 		visible = sx / 24;
642 		if (visible == 0)
643 			visible = 1;
644 	} else
645 		visible = total;
646 
647 	current = 0;
648 	TAILQ_FOREACH(wp, &w->panes, entry) {
649 		if (wp == w->active)
650 			break;
651 		current++;
652 	}
653 
654 	if (current < visible) {
655 		start = 0;
656 		end = visible;
657 	} else if (current >= total - visible) {
658 		start = total - visible;
659 		end = total;
660 	} else {
661 		start = current - (visible / 2);
662 		end = start + visible;
663 	}
664 
665 	if (data->offset < -(int)start)
666 		data->offset = -(int)start;
667 	if (data->offset > (int)(total - end))
668 		data->offset = (int)(total - end);
669 	start += data->offset;
670 	end += data->offset;
671 
672 	left = (start != 0);
673 	right = (end != total);
674 	if (((left && right) && sx <= 6) || ((left || right) && sx <= 3))
675 		left = right = 0;
676 	if (left && right) {
677 		each = (sx - 6) / visible;
678 		remaining = (sx - 6) - (visible * each);
679 	} else if (left || right) {
680 		each = (sx - 3) / visible;
681 		remaining = (sx - 3) - (visible * each);
682 	} else {
683 		each = sx / visible;
684 		remaining = sx - (visible * each);
685 	}
686 	if (each == 0)
687 		return;
688 
689 	if (left) {
690 		data->left = cx + 2;
691 		screen_write_cursormove(ctx, cx + 2, cy);
692 		screen_write_vline(ctx, sy, 0, 0);
693 		screen_write_cursormove(ctx, cx, cy + sy / 2);
694 		screen_write_puts(ctx, &grid_default_cell, "<");
695 	} else
696 		data->left = -1;
697 	if (right) {
698 		data->right = cx + sx - 3;
699 		screen_write_cursormove(ctx, cx + sx - 3, cy);
700 		screen_write_vline(ctx, sy, 0, 0);
701 		screen_write_cursormove(ctx, cx + sx - 1, cy + sy / 2);
702 		screen_write_puts(ctx, &grid_default_cell, ">");
703 	} else
704 		data->right = -1;
705 
706 	data->start = start;
707 	data->end = end;
708 	data->each = each;
709 
710 	i = loop = 0;
711 	TAILQ_FOREACH(wp, &w->panes, entry) {
712 		if (loop == end)
713 			break;
714 		if (loop < start) {
715 			loop++;
716 			continue;
717 		}
718 
719 		if (wp == w->active)
720 			gc.fg = active_colour;
721 		else
722 			gc.fg = colour;
723 
724 		if (left)
725 			offset = 3 + (i * each);
726 		else
727 			offset = (i * each);
728 		if (loop == end - 1)
729 			width = each + remaining;
730 		else
731 			width = each - 1;
732 
733 		screen_write_cursormove(ctx, cx + offset, cy);
734 		screen_write_preview(ctx, &wp->base, width, sy);
735 
736 		if (window_pane_index(wp, &pane_idx) != 0)
737 			pane_idx = loop;
738 		xasprintf(&label, " %u ", pane_idx);
739 		window_tree_draw_label(ctx, cx + offset, cy, each, sy, &gc,
740 		    label);
741 		free(label);
742 
743 		if (loop != end - 1) {
744 			screen_write_cursormove(ctx, cx + offset + width, cy);
745 			screen_write_vline(ctx, sy, 0, 0);
746 		}
747 		loop++;
748 
749 		i++;
750 	}
751 }
752 
753 static void
754 window_tree_draw(void *modedata, void *itemdata, struct screen_write_ctx *ctx,
755     u_int sx, u_int sy)
756 {
757 	struct window_tree_itemdata	*item = itemdata;
758 	struct session			*sp;
759 	struct winlink			*wlp;
760 	struct window_pane		*wp;
761 
762 	window_tree_pull_item(item, &sp, &wlp, &wp);
763 	if (wp == NULL)
764 		return;
765 
766 	switch (item->type) {
767 	case WINDOW_TREE_NONE:
768 		break;
769 	case WINDOW_TREE_SESSION:
770 		window_tree_draw_session(modedata, sp, ctx, sx, sy);
771 		break;
772 	case WINDOW_TREE_WINDOW:
773 		window_tree_draw_window(modedata, sp, wlp->window, ctx, sx, sy);
774 		break;
775 	case WINDOW_TREE_PANE:
776 		screen_write_preview(ctx, &wp->base, sx, sy);
777 		break;
778 	}
779 }
780 
781 static int
782 window_tree_search(__unused void *modedata, void *itemdata, const char *ss)
783 {
784 	struct window_tree_itemdata	*item = itemdata;
785 	struct session			*s;
786 	struct winlink			*wl;
787 	struct window_pane		*wp;
788 	const char			*cmd;
789 
790 	window_tree_pull_item(item, &s, &wl, &wp);
791 
792 	switch (item->type) {
793 	case WINDOW_TREE_NONE:
794 		return (0);
795 	case WINDOW_TREE_SESSION:
796 		if (s == NULL)
797 			return (0);
798 		return (strstr(s->name, ss) != NULL);
799 	case WINDOW_TREE_WINDOW:
800 		if (s == NULL || wl == NULL)
801 			return (0);
802 		return (strstr(wl->window->name, ss) != NULL);
803 	case WINDOW_TREE_PANE:
804 		if (s == NULL || wl == NULL || wp == NULL)
805 			break;
806 		cmd = osdep_get_name(wp->fd, wp->tty);
807 		if (cmd == NULL || *cmd == '\0')
808 			return (0);
809 		return (strstr(cmd, ss) != NULL);
810 	}
811 	return (0);
812 }
813 
814 static struct screen *
815 window_tree_init(struct window_pane *wp, struct cmd_find_state *fs,
816     struct args *args)
817 {
818 	struct window_tree_modedata	*data;
819 	struct screen			*s;
820 
821 	wp->modedata = data = xcalloc(1, sizeof *data);
822 
823 	if (args_has(args, 's'))
824 		data->type = WINDOW_TREE_SESSION;
825 	else if (args_has(args, 'w'))
826 		data->type = WINDOW_TREE_WINDOW;
827 	else
828 		data->type = WINDOW_TREE_PANE;
829 	memcpy(&data->fs, fs, sizeof data->fs);
830 
831 	data->wp = wp;
832 	data->references = 1;
833 
834 	if (args == NULL || !args_has(args, 'F'))
835 		data->format = xstrdup(WINDOW_TREE_DEFAULT_FORMAT);
836 	else
837 		data->format = xstrdup(args_get(args, 'F'));
838 	if (args == NULL || args->argc == 0)
839 		data->command = xstrdup(WINDOW_TREE_DEFAULT_COMMAND);
840 	else
841 		data->command = xstrdup(args->argv[0]);
842 	data->squash_groups = !args_has(args, 'G');
843 
844 	data->data = mode_tree_start(wp, args, window_tree_build,
845 	    window_tree_draw, window_tree_search, data, window_tree_sort_list,
846 	    nitems(window_tree_sort_list), &s);
847 	mode_tree_zoom(data->data, args);
848 
849 	mode_tree_build(data->data);
850 	mode_tree_draw(data->data);
851 
852 	data->type = WINDOW_TREE_NONE;
853 
854 	return (s);
855 }
856 
857 static void
858 window_tree_destroy(struct window_tree_modedata *data)
859 {
860 	u_int	i;
861 
862 	if (--data->references != 0)
863 		return;
864 
865 	for (i = 0; i < data->item_size; i++)
866 		window_tree_free_item(data->item_list[i]);
867 	free(data->item_list);
868 
869 	free(data->format);
870 	free(data->command);
871 
872 	free(data);
873 }
874 
875 static void
876 window_tree_free(struct window_pane *wp)
877 {
878 	struct window_tree_modedata *data = wp->modedata;
879 
880 	if (data == NULL)
881 		return;
882 
883 	data->dead = 1;
884 	mode_tree_free(data->data);
885 	window_tree_destroy(data);
886 }
887 
888 static void
889 window_tree_resize(struct window_pane *wp, u_int sx, u_int sy)
890 {
891 	struct window_tree_modedata	*data = wp->modedata;
892 
893 	mode_tree_resize(data->data, sx, sy);
894 }
895 
896 static char *
897 window_tree_get_target(struct window_tree_itemdata *item,
898     struct cmd_find_state *fs)
899 {
900 	struct session		*s;
901 	struct winlink		*wl;
902 	struct window_pane	*wp;
903 	char			*target;
904 
905 	window_tree_pull_item(item, &s, &wl, &wp);
906 
907 	target = NULL;
908 	switch (item->type) {
909 	case WINDOW_TREE_NONE:
910 		break;
911 	case WINDOW_TREE_SESSION:
912 		if (s == NULL)
913 			break;
914 		xasprintf(&target, "=%s:", s->name);
915 		break;
916 	case WINDOW_TREE_WINDOW:
917 		if (s == NULL || wl == NULL)
918 			break;
919 		xasprintf(&target, "=%s:%u.", s->name, wl->idx);
920 		break;
921 	case WINDOW_TREE_PANE:
922 		if (s == NULL || wl == NULL || wp == NULL)
923 			break;
924 		xasprintf(&target, "=%s:%u.%%%u", s->name, wl->idx, wp->id);
925 		break;
926 	}
927 	if (target == NULL)
928 		cmd_find_clear_state(fs, 0);
929 	else
930 		cmd_find_from_winlink_pane(fs, wl, wp, 0);
931 	return (target);
932 }
933 
934 static void
935 window_tree_command_each(void* modedata, void* itemdata, struct client *c,
936     __unused key_code key)
937 {
938 	struct window_tree_modedata	*data = modedata;
939 	struct window_tree_itemdata	*item = itemdata;
940 	char				*name;
941 	struct cmd_find_state		 fs;
942 
943 	name = window_tree_get_target(item, &fs);
944 	if (name != NULL)
945 		mode_tree_run_command(c, &fs, data->entered, name);
946 	free(name);
947 }
948 
949 static enum cmd_retval
950 window_tree_command_done(__unused struct cmdq_item *item, void *modedata)
951 {
952 	struct window_tree_modedata	*data = modedata;
953 
954 	if (!data->dead) {
955 		mode_tree_build(data->data);
956 		mode_tree_draw(data->data);
957 		data->wp->flags |= PANE_REDRAW;
958 	}
959 	window_tree_destroy(data);
960 	return (CMD_RETURN_NORMAL);
961 }
962 
963 static int
964 window_tree_command_callback(struct client *c, void *modedata, const char *s,
965     __unused int done)
966 {
967 	struct window_tree_modedata	*data = modedata;
968 
969 	if (s == NULL || *s == '\0' || data->dead)
970 		return (0);
971 
972 	data->entered = s;
973 	mode_tree_each_tagged(data->data, window_tree_command_each, c,
974 	    KEYC_NONE, 1);
975 	data->entered = NULL;
976 
977 	data->references++;
978 	cmdq_append(c, cmdq_get_callback(window_tree_command_done, data));
979 
980 	return (0);
981 }
982 
983 static void
984 window_tree_command_free(void *modedata)
985 {
986 	struct window_tree_modedata	*data = modedata;
987 
988 	window_tree_destroy(data);
989 }
990 
991 static void
992 window_tree_kill_each(__unused void* modedata, void* itemdata,
993     __unused struct client *c, __unused key_code key)
994 {
995 	struct window_tree_itemdata	*item = itemdata;
996 	struct session			*s;
997 	struct winlink			*wl;
998 	struct window_pane		*wp;
999 
1000 	window_tree_pull_item(item, &s, &wl, &wp);
1001 
1002 	switch (item->type) {
1003 	case WINDOW_TREE_NONE:
1004 		break;
1005 	case WINDOW_TREE_SESSION:
1006 		if (s != NULL) {
1007 			server_destroy_session(s);
1008 			session_destroy(s, __func__);
1009 		}
1010 		break;
1011 	case WINDOW_TREE_WINDOW:
1012 		if (wl != NULL)
1013 			server_kill_window(wl->window);
1014 		break;
1015 	case WINDOW_TREE_PANE:
1016 		if (wp != NULL)
1017 			server_kill_pane(wp);
1018 		break;
1019 	}
1020 }
1021 
1022 static int
1023 window_tree_kill_current_callback(struct client *c, void *modedata,
1024     const char *s, __unused int done)
1025 {
1026 	struct window_tree_modedata	*data = modedata;
1027 	struct mode_tree_data		*mtd = data->data;
1028 
1029 	if (s == NULL || *s == '\0' || data->dead)
1030 		return (0);
1031 	if (tolower((u_char) s[0]) != 'y' || s[1] != '\0')
1032 		return (0);
1033 
1034 	window_tree_kill_each(data, mode_tree_get_current(mtd), c, KEYC_NONE);
1035 
1036 	data->references++;
1037 	cmdq_append(c, cmdq_get_callback(window_tree_command_done, data));
1038 
1039 	return (0);
1040 }
1041 
1042 static int
1043 window_tree_kill_tagged_callback(struct client *c, void *modedata,
1044     const char *s, __unused int done)
1045 {
1046 	struct window_tree_modedata	*data = modedata;
1047 	struct mode_tree_data		*mtd = data->data;
1048 
1049 	if (s == NULL || *s == '\0' || data->dead)
1050 		return (0);
1051 	if (tolower((u_char) s[0]) != 'y' || s[1] != '\0')
1052 		return (0);
1053 
1054 	mode_tree_each_tagged(mtd, window_tree_kill_each, c, KEYC_NONE, 1);
1055 
1056 	data->references++;
1057 	cmdq_append(c, cmdq_get_callback(window_tree_command_done, data));
1058 
1059 	return (0);
1060 }
1061 
1062 static key_code
1063 window_tree_mouse(struct window_tree_modedata *data, key_code key, u_int x,
1064     struct window_tree_itemdata *item)
1065 {
1066 	struct session		*s;
1067 	struct winlink		*wl;
1068 	struct window_pane	*wp;
1069 	u_int			 loop;
1070 
1071 	if (key != KEYC_MOUSEDOWN1_PANE)
1072 		return (KEYC_NONE);
1073 
1074 	if (data->left != -1 && x <= (u_int)data->left)
1075 		return ('<');
1076 	if (data->right != -1 && x >= (u_int)data->right)
1077 		return ('>');
1078 
1079 	if (data->left != -1)
1080 		x -= data->left;
1081 	else if (x != 0)
1082 		x--;
1083 	if (x == 0 || data->end == 0)
1084 		x = 0;
1085 	else {
1086 		x = x / data->each;
1087 		if (data->start + x >= data->end)
1088 			x = data->end - 1;
1089 	}
1090 
1091 	window_tree_pull_item(item, &s, &wl, &wp);
1092 	if (item->type == WINDOW_TREE_SESSION) {
1093 		if (s == NULL)
1094 			return (KEYC_NONE);
1095 		mode_tree_expand_current(data->data);
1096 		loop = 0;
1097 		RB_FOREACH(wl, winlinks, &s->windows) {
1098 			if (loop == data->start + x)
1099 				break;
1100 			loop++;
1101 		}
1102 		if (wl != NULL)
1103 			mode_tree_set_current(data->data, (uintptr_t)wl);
1104 		return ('\r');
1105 	}
1106 	if (item->type == WINDOW_TREE_WINDOW) {
1107 		if (wl == NULL)
1108 			return (KEYC_NONE);
1109 		mode_tree_expand_current(data->data);
1110 		loop = 0;
1111 		TAILQ_FOREACH(wp, &wl->window->panes, entry) {
1112 			if (loop == data->start + x)
1113 				break;
1114 			loop++;
1115 		}
1116 		if (wp != NULL)
1117 			mode_tree_set_current(data->data, (uintptr_t)wp);
1118 		return ('\r');
1119 	}
1120 	return (KEYC_NONE);
1121 }
1122 
1123 static void
1124 window_tree_key(struct window_pane *wp, struct client *c,
1125     __unused struct session *s, key_code key, struct mouse_event *m)
1126 {
1127 	struct window_tree_modedata	*data = wp->modedata;
1128 	struct window_tree_itemdata	*item, *new_item;
1129 	char				*name, *prompt = NULL;
1130 	struct cmd_find_state		 fs;
1131 	int				 finished;
1132 	u_int				 tagged, x, y, idx;
1133 	struct session			*ns;
1134 	struct winlink			*nwl;
1135 	struct window_pane		*nwp;
1136 
1137 	item = mode_tree_get_current(data->data);
1138 	finished = mode_tree_key(data->data, c, &key, m, &x, &y);
1139 	if (item != (new_item = mode_tree_get_current(data->data))) {
1140 		item = new_item;
1141 		data->offset = 0;
1142 	}
1143 	if (KEYC_IS_MOUSE(key))
1144 		key = window_tree_mouse(data, key, x, item);
1145 	switch (key) {
1146 	case '<':
1147 		data->offset--;
1148 		break;
1149 	case '>':
1150 		data->offset++;
1151 		break;
1152 	case 'x':
1153 		window_tree_pull_item(item, &ns, &nwl, &nwp);
1154 		switch (item->type) {
1155 		case WINDOW_TREE_NONE:
1156 			break;
1157 		case WINDOW_TREE_SESSION:
1158 			if (ns == NULL)
1159 				break;
1160 			xasprintf(&prompt, "Kill session %s? ", ns->name);
1161 			break;
1162 		case WINDOW_TREE_WINDOW:
1163 			if (nwl == NULL)
1164 				break;
1165 			xasprintf(&prompt, "Kill window %u? ", nwl->idx);
1166 			break;
1167 		case WINDOW_TREE_PANE:
1168 			if (nwp == NULL || window_pane_index(nwp, &idx) != 0)
1169 				break;
1170 			xasprintf(&prompt, "Kill pane %u? ", idx);
1171 			break;
1172 		}
1173 		if (prompt == NULL)
1174 			break;
1175 		data->references++;
1176 		status_prompt_set(c, prompt, "",
1177 		    window_tree_kill_current_callback, window_tree_command_free,
1178 		    data, PROMPT_SINGLE|PROMPT_NOFORMAT);
1179 		free(prompt);
1180 		break;
1181 	case 'X':
1182 		tagged = mode_tree_count_tagged(data->data);
1183 		if (tagged == 0)
1184 			break;
1185 		xasprintf(&prompt, "Kill %u tagged? ", tagged);
1186 		data->references++;
1187 		status_prompt_set(c, prompt, "",
1188 		    window_tree_kill_tagged_callback, window_tree_command_free,
1189 		    data, PROMPT_SINGLE|PROMPT_NOFORMAT);
1190 		free(prompt);
1191 		break;
1192 	case ':':
1193 		tagged = mode_tree_count_tagged(data->data);
1194 		if (tagged != 0)
1195 			xasprintf(&prompt, "(%u tagged) ", tagged);
1196 		else
1197 			xasprintf(&prompt, "(current) ");
1198 		data->references++;
1199 		status_prompt_set(c, prompt, "", window_tree_command_callback,
1200 		    window_tree_command_free, data, PROMPT_NOFORMAT);
1201 		free(prompt);
1202 		break;
1203 	case '\r':
1204 		item = mode_tree_get_current(data->data);
1205 		name = window_tree_get_target(item, &fs);
1206 		if (name != NULL)
1207 			mode_tree_run_command(c, NULL, data->command, name);
1208 		finished = 1;
1209 		free(name);
1210 		break;
1211 	}
1212 	if (finished)
1213 		window_pane_reset_mode(wp);
1214 	else {
1215 		mode_tree_draw(data->data);
1216 		wp->flags |= PANE_REDRAW;
1217 	}
1218 }
1219