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