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