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