xref: /openbsd-src/usr.bin/tmux/mode-tree.c (revision b0f539e9923c93d213bbde92bfd6b7a67cb6927c)
1 /* $OpenBSD: mode-tree.c,v 1.29 2019/05/12 18:16: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 <ctype.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include "tmux.h"
27 
28 struct mode_tree_item;
29 TAILQ_HEAD(mode_tree_list, mode_tree_item);
30 
31 struct mode_tree_data {
32 	int			  dead;
33 	u_int			  references;
34 	int			  zoomed;
35 
36 	struct window_pane	 *wp;
37 	void			 *modedata;
38 	const char		 *menu;
39 
40 	const char		**sort_list;
41 	u_int			  sort_size;
42 	u_int			  sort_type;
43 
44 	mode_tree_build_cb        buildcb;
45 	mode_tree_draw_cb         drawcb;
46 	mode_tree_search_cb       searchcb;
47 	mode_tree_menu_cb         menucb;
48 
49 	struct mode_tree_list	  children;
50 	struct mode_tree_list	  saved;
51 
52 	struct mode_tree_line	 *line_list;
53 	u_int			  line_size;
54 
55 	u_int			  depth;
56 
57 	u_int			  width;
58 	u_int			  height;
59 
60 	u_int			  offset;
61 	u_int			  current;
62 
63 	struct screen		  screen;
64 
65 	int			  preview;
66 	char			 *search;
67 	char			 *filter;
68 	int			  no_matches;
69 };
70 
71 struct mode_tree_item {
72 	struct mode_tree_item		*parent;
73 	void				*itemdata;
74 	u_int				 line;
75 
76 	uint64_t			 tag;
77 	const char			*name;
78 	const char			*text;
79 
80 	int				 expanded;
81 	int				 tagged;
82 
83 	struct mode_tree_list		 children;
84 	TAILQ_ENTRY(mode_tree_item)	 entry;
85 };
86 
87 struct mode_tree_line {
88 	struct mode_tree_item		*item;
89 	u_int				 depth;
90 	int				 last;
91 	int				 flat;
92 };
93 
94 struct mode_tree_menu {
95 	struct mode_tree_data		*data;
96 	struct client			*c;
97 	u_int				 line;
98 	void				*itemdata;
99 };
100 
101 static void mode_tree_free_items(struct mode_tree_list *);
102 
103 #define MODE_TREE_MENU \
104 	"Scroll Left,<,|" \
105 	"Scroll Right,>,|" \
106 	"|" \
107 	"Cancel,q,"
108 
109 static struct mode_tree_item *
110 mode_tree_find_item(struct mode_tree_list *mtl, uint64_t tag)
111 {
112 	struct mode_tree_item	*mti, *child;
113 
114 	TAILQ_FOREACH(mti, mtl, entry) {
115 		if (mti->tag == tag)
116 			return (mti);
117 		child = mode_tree_find_item(&mti->children, tag);
118 		if (child != NULL)
119 			return (child);
120 	}
121 	return (NULL);
122 }
123 
124 static void
125 mode_tree_free_item(struct mode_tree_item *mti)
126 {
127 	mode_tree_free_items(&mti->children);
128 
129 	free((void *)mti->name);
130 	free((void *)mti->text);
131 
132 	free(mti);
133 }
134 
135 static void
136 mode_tree_free_items(struct mode_tree_list *mtl)
137 {
138 	struct mode_tree_item	*mti, *mti1;
139 
140 	TAILQ_FOREACH_SAFE(mti, mtl, entry, mti1) {
141 		TAILQ_REMOVE(mtl, mti, entry);
142 		mode_tree_free_item(mti);
143 	}
144 }
145 
146 static void
147 mode_tree_check_selected(struct mode_tree_data *mtd)
148 {
149 	/*
150 	 * If the current line would now be off screen reset the offset to the
151 	 * last visible line.
152 	 */
153 	if (mtd->current > mtd->height - 1)
154 		mtd->offset = mtd->current - mtd->height + 1;
155 }
156 
157 static void
158 mode_tree_clear_lines(struct mode_tree_data *mtd)
159 {
160 	free(mtd->line_list);
161 	mtd->line_list = NULL;
162 	mtd->line_size = 0;
163 }
164 
165 static void
166 mode_tree_build_lines(struct mode_tree_data *mtd,
167     struct mode_tree_list *mtl, u_int depth)
168 {
169 	struct mode_tree_item	*mti;
170 	struct mode_tree_line	*line;
171 	u_int			 i;
172 	int			 flat = 1;
173 
174 	mtd->depth = depth;
175 	TAILQ_FOREACH(mti, mtl, entry) {
176 		mtd->line_list = xreallocarray(mtd->line_list,
177 		    mtd->line_size + 1, sizeof *mtd->line_list);
178 
179 		line = &mtd->line_list[mtd->line_size++];
180 		line->item = mti;
181 		line->depth = depth;
182 		line->last = (mti == TAILQ_LAST(mtl, mode_tree_list));
183 
184 		mti->line = (mtd->line_size - 1);
185 		if (!TAILQ_EMPTY(&mti->children))
186 			flat = 0;
187 		if (mti->expanded)
188 			mode_tree_build_lines(mtd, &mti->children, depth + 1);
189 	}
190 	TAILQ_FOREACH(mti, mtl, entry) {
191 		for (i = 0; i < mtd->line_size; i++) {
192 			line = &mtd->line_list[i];
193 			if (line->item == mti)
194 				line->flat = flat;
195 		}
196 	}
197 }
198 
199 static void
200 mode_tree_clear_tagged(struct mode_tree_list *mtl)
201 {
202 	struct mode_tree_item	*mti;
203 
204 	TAILQ_FOREACH(mti, mtl, entry) {
205 		mti->tagged = 0;
206 		mode_tree_clear_tagged(&mti->children);
207 	}
208 }
209 
210 static void
211 mode_tree_up(struct mode_tree_data *mtd, int wrap)
212 {
213 	if (mtd->current == 0) {
214 		if (wrap) {
215 			mtd->current = mtd->line_size - 1;
216 			if (mtd->line_size >= mtd->height)
217 				mtd->offset = mtd->line_size - mtd->height;
218 		}
219 	} else {
220 		mtd->current--;
221 		if (mtd->current < mtd->offset)
222 			mtd->offset--;
223 	}
224 }
225 
226 void
227 mode_tree_down(struct mode_tree_data *mtd, int wrap)
228 {
229 	if (mtd->current == mtd->line_size - 1) {
230 		if (wrap) {
231 			mtd->current = 0;
232 			mtd->offset = 0;
233 		}
234 	} else {
235 		mtd->current++;
236 		if (mtd->current > mtd->offset + mtd->height - 1)
237 			mtd->offset++;
238 	}
239 }
240 
241 void *
242 mode_tree_get_current(struct mode_tree_data *mtd)
243 {
244 	return (mtd->line_list[mtd->current].item->itemdata);
245 }
246 
247 void
248 mode_tree_expand_current(struct mode_tree_data *mtd)
249 {
250 	if (!mtd->line_list[mtd->current].item->expanded) {
251 		mtd->line_list[mtd->current].item->expanded = 1;
252 		mode_tree_build(mtd);
253 	}
254 }
255 
256 void
257 mode_tree_set_current(struct mode_tree_data *mtd, uint64_t tag)
258 {
259 	u_int	i;
260 
261 	for (i = 0; i < mtd->line_size; i++) {
262 		if (mtd->line_list[i].item->tag == tag)
263 			break;
264 	}
265 	if (i != mtd->line_size) {
266 		mtd->current = i;
267 		if (mtd->current > mtd->height - 1)
268 			mtd->offset = mtd->current - mtd->height + 1;
269 		else
270 			mtd->offset = 0;
271 	} else {
272 		mtd->current = 0;
273 		mtd->offset = 0;
274 	}
275 }
276 
277 u_int
278 mode_tree_count_tagged(struct mode_tree_data *mtd)
279 {
280 	struct mode_tree_item	*mti;
281 	u_int			 i, tagged;
282 
283 	tagged = 0;
284 	for (i = 0; i < mtd->line_size; i++) {
285 		mti = mtd->line_list[i].item;
286 		if (mti->tagged)
287 			tagged++;
288 	}
289 	return (tagged);
290 }
291 
292 void
293 mode_tree_each_tagged(struct mode_tree_data *mtd, mode_tree_each_cb cb,
294     struct client *c, key_code key, int current)
295 {
296 	struct mode_tree_item	*mti;
297 	u_int			 i;
298 	int			 fired;
299 
300 	fired = 0;
301 	for (i = 0; i < mtd->line_size; i++) {
302 		mti = mtd->line_list[i].item;
303 		if (mti->tagged) {
304 			fired = 1;
305 			cb(mtd->modedata, mti->itemdata, c, key);
306 		}
307 	}
308 	if (!fired && current) {
309 		mti = mtd->line_list[mtd->current].item;
310 		cb(mtd->modedata, mti->itemdata, c, key);
311 	}
312 }
313 
314 struct mode_tree_data *
315 mode_tree_start(struct window_pane *wp, struct args *args,
316     mode_tree_build_cb buildcb, mode_tree_draw_cb drawcb,
317     mode_tree_search_cb searchcb, mode_tree_menu_cb menucb, void *modedata,
318     const char *menu, const char **sort_list, u_int sort_size,
319     struct screen **s)
320 {
321 	struct mode_tree_data	*mtd;
322 	const char		*sort;
323 	u_int			 i;
324 
325 	mtd = xcalloc(1, sizeof *mtd);
326 	mtd->references = 1;
327 
328 	mtd->wp = wp;
329 	mtd->modedata = modedata;
330 	mtd->menu = menu;
331 
332 	mtd->sort_list = sort_list;
333 	mtd->sort_size = sort_size;
334 	mtd->sort_type = 0;
335 
336 	mtd->preview = !args_has(args, 'N');
337 
338 	sort = args_get(args, 'O');
339 	if (sort != NULL) {
340 		for (i = 0; i < sort_size; i++) {
341 			if (strcasecmp(sort, sort_list[i]) == 0)
342 				mtd->sort_type = i;
343 		}
344 	}
345 
346 	if (args_has(args, 'f'))
347 		mtd->filter = xstrdup(args_get(args, 'f'));
348 	else
349 		mtd->filter = NULL;
350 
351 	mtd->buildcb = buildcb;
352 	mtd->drawcb = drawcb;
353 	mtd->searchcb = searchcb;
354 	mtd->menucb = menucb;
355 
356 	TAILQ_INIT(&mtd->children);
357 
358 	*s = &mtd->screen;
359 	screen_init(*s, screen_size_x(&wp->base), screen_size_y(&wp->base), 0);
360 	(*s)->mode &= ~MODE_CURSOR;
361 
362 	return (mtd);
363 }
364 
365 void
366 mode_tree_zoom(struct mode_tree_data *mtd, struct args *args)
367 {
368 	struct window_pane	*wp = mtd->wp;
369 
370 	if (args_has(args, 'Z')) {
371 		mtd->zoomed = (wp->window->flags & WINDOW_ZOOMED);
372 		if (!mtd->zoomed && window_zoom(wp) == 0)
373 			server_redraw_window(wp->window);
374 	} else
375 		mtd->zoomed = -1;
376 }
377 
378 void
379 mode_tree_build(struct mode_tree_data *mtd)
380 {
381 	struct screen	*s = &mtd->screen;
382 	uint64_t	 tag;
383 
384 	if (mtd->line_list != NULL)
385 		tag = mtd->line_list[mtd->current].item->tag;
386 	else
387 		tag = 0;
388 
389 	TAILQ_CONCAT(&mtd->saved, &mtd->children, entry);
390 	TAILQ_INIT(&mtd->children);
391 
392 	mtd->buildcb(mtd->modedata, mtd->sort_type, &tag, mtd->filter);
393 	mtd->no_matches = TAILQ_EMPTY(&mtd->children);
394 	if (mtd->no_matches)
395 		mtd->buildcb(mtd->modedata, mtd->sort_type, &tag, NULL);
396 
397 	mode_tree_free_items(&mtd->saved);
398 	TAILQ_INIT(&mtd->saved);
399 
400 	mode_tree_clear_lines(mtd);
401 	mode_tree_build_lines(mtd, &mtd->children, 0);
402 
403 	mode_tree_set_current(mtd, tag);
404 
405 	mtd->width = screen_size_x(s);
406 	if (mtd->preview) {
407 		mtd->height = (screen_size_y(s) / 3) * 2;
408 		if (mtd->height > mtd->line_size)
409 			mtd->height = screen_size_y(s) / 2;
410 		if (mtd->height < 10)
411 			mtd->height = screen_size_y(s);
412 		if (screen_size_y(s) - mtd->height < 2)
413 			mtd->height = screen_size_y(s);
414 	} else
415 		mtd->height = screen_size_y(s);
416 	mode_tree_check_selected(mtd);
417 }
418 
419 static void
420 mode_tree_remove_ref(struct mode_tree_data *mtd)
421 {
422 	if (--mtd->references == 0)
423 		free(mtd);
424 }
425 
426 void
427 mode_tree_free(struct mode_tree_data *mtd)
428 {
429 	struct window_pane	*wp = mtd->wp;
430 
431 	if (mtd->zoomed == 0)
432 		server_unzoom_window(wp->window);
433 
434 	mode_tree_free_items(&mtd->children);
435 	mode_tree_clear_lines(mtd);
436 	screen_free(&mtd->screen);
437 
438 	free(mtd->search);
439 	free(mtd->filter);
440 
441 	mtd->dead = 1;
442 	mode_tree_remove_ref(mtd);
443 }
444 
445 void
446 mode_tree_resize(struct mode_tree_data *mtd, u_int sx, u_int sy)
447 {
448 	struct screen	*s = &mtd->screen;
449 
450 	screen_resize(s, sx, sy, 0);
451 
452 	mode_tree_build(mtd);
453 	mode_tree_draw(mtd);
454 
455 	mtd->wp->flags |= PANE_REDRAW;
456 }
457 
458 struct mode_tree_item *
459 mode_tree_add(struct mode_tree_data *mtd, struct mode_tree_item *parent,
460     void *itemdata, uint64_t tag, const char *name, const char *text,
461     int expanded)
462 {
463 	struct mode_tree_item	*mti, *saved;
464 
465 	log_debug("%s: %llu, %s %s", __func__, (unsigned long long)tag,
466 	    name, text);
467 
468 	mti = xcalloc(1, sizeof *mti);
469 	mti->parent = parent;
470 	mti->itemdata = itemdata;
471 
472 	mti->tag = tag;
473 	mti->name = xstrdup(name);
474 	mti->text = xstrdup(text);
475 
476 	saved = mode_tree_find_item(&mtd->saved, tag);
477 	if (saved != NULL) {
478 		if (parent == NULL || (parent != NULL && parent->expanded))
479 			mti->tagged = saved->tagged;
480 		mti->expanded = saved->expanded;
481 	} else if (expanded == -1)
482 		mti->expanded = 1;
483 	else
484 		mti->expanded = expanded;
485 
486 	TAILQ_INIT(&mti->children);
487 
488 	if (parent != NULL)
489 		TAILQ_INSERT_TAIL(&parent->children, mti, entry);
490 	else
491 		TAILQ_INSERT_TAIL(&mtd->children, mti, entry);
492 
493 	return (mti);
494 }
495 
496 void
497 mode_tree_remove(struct mode_tree_data *mtd, struct mode_tree_item *mti)
498 {
499 	struct mode_tree_item	*parent = mti->parent;
500 
501 	if (parent != NULL)
502 		TAILQ_REMOVE(&parent->children, mti, entry);
503 	else
504 		TAILQ_REMOVE(&mtd->children, mti, entry);
505 	mode_tree_free_item(mti);
506 }
507 
508 void
509 mode_tree_draw(struct mode_tree_data *mtd)
510 {
511 	struct window_pane	*wp = mtd->wp;
512 	struct screen		*s = &mtd->screen;
513 	struct mode_tree_line	*line;
514 	struct mode_tree_item	*mti;
515 	struct options		*oo = wp->window->options;
516 	struct screen_write_ctx	 ctx;
517 	struct grid_cell	 gc0, gc;
518 	u_int			 w, h, i, j, sy, box_x, box_y, width;
519 	char			*text, *start, key[7];
520 	const char		*tag, *symbol;
521 	size_t			 size, n;
522 	int			 keylen;
523 
524 	if (mtd->line_size == 0)
525 		return;
526 
527 	memcpy(&gc0, &grid_default_cell, sizeof gc0);
528 	memcpy(&gc, &grid_default_cell, sizeof gc);
529 	style_apply(&gc, oo, "mode-style");
530 
531 	w = mtd->width;
532 	h = mtd->height;
533 
534 	screen_write_start(&ctx, NULL, s);
535 	screen_write_clearscreen(&ctx, 8);
536 
537 	if (mtd->line_size > 10)
538 		keylen = 6;
539 	else
540 		keylen = 4;
541 
542 	for (i = 0; i < mtd->line_size; i++) {
543 		if (i < mtd->offset)
544 			continue;
545 		if (i > mtd->offset + h - 1)
546 			break;
547 
548 		line = &mtd->line_list[i];
549 		mti = line->item;
550 
551 		screen_write_cursormove(&ctx, 0, i - mtd->offset, 0);
552 
553 		if (i < 10)
554 			snprintf(key, sizeof key, "(%c)  ", '0' + i);
555 		else if (i < 36)
556 			snprintf(key, sizeof key, "(M-%c)", 'a' + (i - 10));
557 		else
558 			*key = '\0';
559 
560 		if (line->flat)
561 			symbol = "";
562 		else if (TAILQ_EMPTY(&mti->children))
563 			symbol = "  ";
564 		else if (mti->expanded)
565 			symbol = "- ";
566 		else
567 			symbol = "+ ";
568 
569 		if (line->depth == 0)
570 			start = xstrdup(symbol);
571 		else {
572 			size = (4 * line->depth) + 32;
573 
574 			start = xcalloc(1, size);
575 			for (j = 1; j < line->depth; j++) {
576 				if (mti->parent != NULL &&
577 				    mtd->line_list[mti->parent->line].last)
578 					strlcat(start, "    ", size);
579 				else
580 					strlcat(start, "\001x\001   ", size);
581 			}
582 			if (line->last)
583 				strlcat(start, "\001mq\001> ", size);
584 			else
585 				strlcat(start, "\001tq\001> ", size);
586 			strlcat(start, symbol, size);
587 		}
588 
589 		if (mti->tagged)
590 			tag = "*";
591 		else
592 			tag = "";
593 		xasprintf(&text, "%-*s%s%s%s: ", keylen, key, start, mti->name,
594 		    tag);
595 		width = utf8_cstrwidth(text);
596 		free(start);
597 
598 		if (mti->tagged) {
599 			gc.attr ^= GRID_ATTR_BRIGHT;
600 			gc0.attr ^= GRID_ATTR_BRIGHT;
601 		}
602 
603 		if (i != mtd->current) {
604 			screen_write_clearendofline(&ctx, 8);
605 			screen_write_puts(&ctx, &gc0, "%s", text);
606 			format_draw(&ctx, &gc0, w - width, mti->text, NULL);
607 		} else {
608 			screen_write_clearendofline(&ctx, gc.bg);
609 			screen_write_puts(&ctx, &gc, "%s", text);
610 			format_draw(&ctx, &gc, w - width, mti->text, NULL);
611 		}
612 		free(text);
613 
614 		if (mti->tagged) {
615 			gc.attr ^= GRID_ATTR_BRIGHT;
616 			gc0.attr ^= GRID_ATTR_BRIGHT;
617 		}
618 	}
619 
620 	sy = screen_size_y(s);
621 	if (!mtd->preview || sy <= 4 || h <= 4 || sy - h <= 4 || w <= 4) {
622 		screen_write_stop(&ctx);
623 		return;
624 	}
625 
626 	line = &mtd->line_list[mtd->current];
627 	mti = line->item;
628 
629 	screen_write_cursormove(&ctx, 0, h, 0);
630 	screen_write_box(&ctx, w, sy - h);
631 
632 	xasprintf(&text, " %s (sort: %s)", mti->name,
633 	    mtd->sort_list[mtd->sort_type]);
634 	if (w - 2 >= strlen(text)) {
635 		screen_write_cursormove(&ctx, 1, h, 0);
636 		screen_write_puts(&ctx, &gc0, "%s", text);
637 
638 		if (mtd->no_matches)
639 			n = (sizeof "no matches") - 1;
640 		else
641 			n = (sizeof "active") - 1;
642 		if (mtd->filter != NULL && w - 2 >= strlen(text) + 10 + n + 2) {
643 			screen_write_puts(&ctx, &gc0, " (filter: ");
644 			if (mtd->no_matches)
645 				screen_write_puts(&ctx, &gc, "no matches");
646 			else
647 				screen_write_puts(&ctx, &gc0, "active");
648 			screen_write_puts(&ctx, &gc0, ") ");
649 		}
650 	}
651 	free(text);
652 
653 	box_x = w - 4;
654 	box_y = sy - h - 2;
655 
656 	if (box_x != 0 && box_y != 0) {
657 		screen_write_cursormove(&ctx, 2, h + 1, 0);
658 		mtd->drawcb(mtd->modedata, mti->itemdata, &ctx, box_x, box_y);
659 	}
660 
661 	screen_write_stop(&ctx);
662 }
663 
664 static struct mode_tree_item *
665 mode_tree_search_for(struct mode_tree_data *mtd)
666 {
667 	struct mode_tree_item	*mti, *last, *next;
668 
669 	if (mtd->search == NULL)
670 		return (NULL);
671 
672 	mti = last = mtd->line_list[mtd->current].item;
673 	for (;;) {
674 		if (!TAILQ_EMPTY(&mti->children))
675 			mti = TAILQ_FIRST(&mti->children);
676 		else if ((next = TAILQ_NEXT(mti, entry)) != NULL)
677 			mti = next;
678 		else {
679 			for (;;) {
680 				mti = mti->parent;
681 				if (mti == NULL)
682 					break;
683 				if ((next = TAILQ_NEXT(mti, entry)) != NULL) {
684 					mti = next;
685 					break;
686 				}
687 			}
688 		}
689 		if (mti == NULL)
690 			mti = TAILQ_FIRST(&mtd->children);
691 		if (mti == last)
692 			break;
693 
694 		if (mtd->searchcb == NULL) {
695 			if (strstr(mti->name, mtd->search) != NULL)
696 				return (mti);
697 			continue;
698 		}
699 		if (mtd->searchcb(mtd->modedata, mti->itemdata, mtd->search))
700 			return (mti);
701 	}
702 	return (NULL);
703 }
704 
705 static void
706 mode_tree_search_set(struct mode_tree_data *mtd)
707 {
708 	struct mode_tree_item	*mti, *loop;
709 	uint64_t		 tag;
710 
711 	mti = mode_tree_search_for(mtd);
712 	if (mti == NULL)
713 		return;
714 	tag = mti->tag;
715 
716 	loop = mti->parent;
717 	while (loop != NULL) {
718 		loop->expanded = 1;
719 		loop = loop->parent;
720 	}
721 
722 	mode_tree_build(mtd);
723 	mode_tree_set_current(mtd, tag);
724 	mode_tree_draw(mtd);
725 	mtd->wp->flags |= PANE_REDRAW;
726 }
727 
728 static int
729 mode_tree_search_callback(__unused struct client *c, void *data, const char *s,
730     __unused int done)
731 {
732 	struct mode_tree_data	*mtd = data;
733 
734 	if (mtd->dead)
735 		return (0);
736 
737 	free(mtd->search);
738 	if (s == NULL || *s == '\0') {
739 		mtd->search = NULL;
740 		return (0);
741 	}
742 	mtd->search = xstrdup(s);
743 	mode_tree_search_set(mtd);
744 
745 	return (0);
746 }
747 
748 static void
749 mode_tree_search_free(void *data)
750 {
751 	mode_tree_remove_ref(data);
752 }
753 
754 static int
755 mode_tree_filter_callback(__unused struct client *c, void *data, const char *s,
756     __unused int done)
757 {
758 	struct mode_tree_data	*mtd = data;
759 
760 	if (mtd->dead)
761 		return (0);
762 
763 	if (mtd->filter != NULL)
764 		free(mtd->filter);
765 	if (s == NULL || *s == '\0')
766 		mtd->filter = NULL;
767 	else
768 		mtd->filter = xstrdup(s);
769 
770 	mode_tree_build(mtd);
771 	mode_tree_draw(mtd);
772 	mtd->wp->flags |= PANE_REDRAW;
773 
774 	return (0);
775 }
776 
777 static void
778 mode_tree_filter_free(void *data)
779 {
780 	mode_tree_remove_ref(data);
781 }
782 
783 static void
784 mode_tree_menu_callback(__unused struct menu *menu, __unused u_int idx,
785     key_code key, void *data)
786 {
787 	struct mode_tree_menu		*mtm = data;
788 	struct mode_tree_data		*mtd = mtm->data;
789 	struct mode_tree_item		*mti;
790 
791 	if (mtd->dead || key == KEYC_NONE)
792 		goto out;
793 
794 	if (mtm->line >= mtd->line_size)
795 		goto out;
796 	mti = mtd->line_list[mtm->line].item;
797 	if (mti->itemdata != mtm->itemdata)
798 		goto out;
799 	mtd->current = mtm->line;
800 	mtd->menucb (mtd->modedata, mtm->c, key);
801 
802 out:
803 	mode_tree_remove_ref(mtd);
804 	free(mtm);
805 }
806 
807 static void
808 mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x,
809     u_int y, int outside)
810 {
811 	struct mode_tree_item	*mti;
812 	struct menu		*menu;
813 	struct mode_tree_menu	*mtm;
814 	const char		*s;
815 	char			*title;
816 	u_int			 line;
817 
818 	if (mtd->offset + y > mtd->line_size - 1)
819 		line = mtd->current;
820 	else
821 		line = mtd->offset + y;
822 	mti = mtd->line_list[line].item;
823 
824 	if (!outside) {
825 		s = mtd->menu;
826 		xasprintf(&title, "#[align=centre]%s", mti->name);
827 	} else {
828 		s = MODE_TREE_MENU;
829 		title = xstrdup("");
830 	}
831 	menu = menu_create(s, c, NULL, title);
832 	free(title);
833 	if (menu == NULL)
834 		return;
835 
836 	mtm = xmalloc(sizeof *mtm);
837 	mtm->data = mtd;
838 	mtm->c = c;
839 	mtm->line = line;
840 	mtm->itemdata = mti->itemdata;
841 	mtd->references++;
842 
843 	if (menu_display(menu, 0, NULL, x, y, c, NULL, mode_tree_menu_callback,
844 	    mtm) != 0)
845 		menu_free(menu);
846 }
847 
848 int
849 mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
850     struct mouse_event *m, u_int *xp, u_int *yp)
851 {
852 	struct mode_tree_line	*line;
853 	struct mode_tree_item	*current, *parent;
854 	u_int			 i, x, y;
855 	int			 choice;
856 	key_code		 tmp;
857 
858 	if (KEYC_IS_MOUSE(*key) && m != NULL) {
859 		if (cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) {
860 			*key = KEYC_NONE;
861 			return (0);
862 		}
863 		if (xp != NULL)
864 			*xp = x;
865 		if (yp != NULL)
866 			*yp = y;
867 		if (x > mtd->width || y > mtd->height) {
868 			if (*key == KEYC_MOUSEDOWN3_PANE)
869 				mode_tree_display_menu(mtd, c, x, y, 1);
870 			if (!mtd->preview)
871 				*key = KEYC_NONE;
872 			return (0);
873 		}
874 		if (mtd->offset + y < mtd->line_size) {
875 			if (*key == KEYC_MOUSEDOWN1_PANE ||
876 			    *key == KEYC_MOUSEDOWN3_PANE ||
877 			    *key == KEYC_DOUBLECLICK1_PANE)
878 				mtd->current = mtd->offset + y;
879 			if (*key == KEYC_DOUBLECLICK1_PANE)
880 				*key = '\r';
881 			else {
882 				if (*key == KEYC_MOUSEDOWN3_PANE)
883 					mode_tree_display_menu(mtd, c, x, y, 0);
884 				*key = KEYC_NONE;
885 			}
886 		} else {
887 			if (*key == KEYC_MOUSEDOWN3_PANE)
888 				mode_tree_display_menu(mtd, c, x, y, 0);
889 			*key = KEYC_NONE;
890 		}
891 		return (0);
892 	}
893 
894 	line = &mtd->line_list[mtd->current];
895 	current = line->item;
896 
897 	choice = -1;
898 	if (*key >= '0' && *key <= '9')
899 		choice = (*key) - '0';
900 	else if (((*key) & KEYC_MASK_MOD) == KEYC_ESCAPE) {
901 		tmp = (*key) & KEYC_MASK_KEY;
902 		if (tmp >= 'a' && tmp <= 'z')
903 			choice = 10 + (tmp - 'a');
904 	}
905 	if (choice != -1) {
906 		if ((u_int)choice > mtd->line_size - 1) {
907 			*key = KEYC_NONE;
908 			return (0);
909 		}
910 		mtd->current = choice;
911 		*key = '\r';
912 		return (0);
913 	}
914 
915 	switch (*key) {
916 	case 'q':
917 	case '\033': /* Escape */
918 	case '\007': /* C-g */
919 		return (1);
920 	case KEYC_UP:
921 	case 'k':
922 	case KEYC_WHEELUP_PANE:
923 	case '\020': /* C-p */
924 		mode_tree_up(mtd, 1);
925 		break;
926 	case KEYC_DOWN:
927 	case 'j':
928 	case KEYC_WHEELDOWN_PANE:
929 	case '\016': /* C-n */
930 		mode_tree_down(mtd, 1);
931 		break;
932 	case KEYC_PPAGE:
933 	case '\002': /* C-b */
934 		for (i = 0; i < mtd->height; i++) {
935 			if (mtd->current == 0)
936 				break;
937 			mode_tree_up(mtd, 1);
938 		}
939 		break;
940 	case KEYC_NPAGE:
941 	case '\006': /* C-f */
942 		for (i = 0; i < mtd->height; i++) {
943 			if (mtd->current == mtd->line_size - 1)
944 				break;
945 			mode_tree_down(mtd, 1);
946 		}
947 		break;
948 	case KEYC_HOME:
949 		mtd->current = 0;
950 		mtd->offset = 0;
951 		break;
952 	case KEYC_END:
953 		mtd->current = mtd->line_size - 1;
954 		if (mtd->current > mtd->height - 1)
955 			mtd->offset = mtd->current - mtd->height + 1;
956 		else
957 			mtd->offset = 0;
958 		break;
959 	case 't':
960 		/*
961 		 * Do not allow parents and children to both be tagged: untag
962 		 * all parents and children of current.
963 		 */
964 		if (!current->tagged) {
965 			parent = current->parent;
966 			while (parent != NULL) {
967 				parent->tagged = 0;
968 				parent = parent->parent;
969 			}
970 			mode_tree_clear_tagged(&current->children);
971 			current->tagged = 1;
972 		} else
973 			current->tagged = 0;
974 		if (m != NULL)
975 			mode_tree_down(mtd, 0);
976 		break;
977 	case 'T':
978 		for (i = 0; i < mtd->line_size; i++)
979 			mtd->line_list[i].item->tagged = 0;
980 		break;
981 	case '\024': /* C-t */
982 		for (i = 0; i < mtd->line_size; i++) {
983 			if (mtd->line_list[i].item->parent == NULL)
984 				mtd->line_list[i].item->tagged = 1;
985 			else
986 				mtd->line_list[i].item->tagged = 0;
987 		}
988 		break;
989 	case 'O':
990 		mtd->sort_type++;
991 		if (mtd->sort_type == mtd->sort_size)
992 			mtd->sort_type = 0;
993 		mode_tree_build(mtd);
994 		break;
995 	case KEYC_LEFT:
996 	case 'h':
997 	case '-':
998 		if (line->flat || !current->expanded)
999 			current = current->parent;
1000 		if (current == NULL)
1001 			mode_tree_up(mtd, 0);
1002 		else {
1003 			current->expanded = 0;
1004 			mtd->current = current->line;
1005 			mode_tree_build(mtd);
1006 		}
1007 		break;
1008 	case KEYC_RIGHT:
1009 	case 'l':
1010 	case '+':
1011 		if (line->flat || current->expanded)
1012 			mode_tree_down(mtd, 0);
1013 		else if (!line->flat) {
1014 			current->expanded = 1;
1015 			mode_tree_build(mtd);
1016 		}
1017 		break;
1018 	case '\023': /* C-s */
1019 		mtd->references++;
1020 		status_prompt_set(c, "(search) ", "",
1021 		    mode_tree_search_callback, mode_tree_search_free, mtd,
1022 		    PROMPT_NOFORMAT);
1023 		break;
1024 	case 'n':
1025 		mode_tree_search_set(mtd);
1026 		break;
1027 	case 'f':
1028 		mtd->references++;
1029 		status_prompt_set(c, "(filter) ", mtd->filter,
1030 		    mode_tree_filter_callback, mode_tree_filter_free, mtd,
1031 		    PROMPT_NOFORMAT);
1032 		break;
1033 	case 'v':
1034 		mtd->preview = !mtd->preview;
1035 		mode_tree_build(mtd);
1036 		if (mtd->preview)
1037 			mode_tree_check_selected(mtd);
1038 		break;
1039 	}
1040 	return (0);
1041 }
1042 
1043 void
1044 mode_tree_run_command(struct client *c, struct cmd_find_state *fs,
1045     const char *template, const char *name)
1046 {
1047 	struct cmdq_item	*new_item;
1048 	struct cmd_list		*cmdlist;
1049 	char			*command, *cause;
1050 
1051 	command = cmd_template_replace(template, name, 1);
1052 	if (command == NULL || *command == '\0') {
1053 		free(command);
1054 		return;
1055 	}
1056 
1057 	cmdlist = cmd_string_parse(command, NULL, 0, &cause);
1058 	if (cmdlist == NULL) {
1059 		if (cause != NULL && c != NULL) {
1060 			*cause = toupper((u_char)*cause);
1061 			status_message_set(c, "%s", cause);
1062 		}
1063 		free(cause);
1064 	} else {
1065 		new_item = cmdq_get_command(cmdlist, fs, NULL, 0);
1066 		cmdq_append(c, new_item);
1067 		cmd_list_free(cmdlist);
1068 	}
1069 
1070 	free(command);
1071 }
1072