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