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