xref: /openbsd-src/usr.bin/tmux/mode-tree.c (revision c0dd97bfcad3dab6c31ec12b9de1274fd2d2f993)
1 /* $OpenBSD: mode-tree.c,v 1.15 2017/10/25 12:13: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 	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     struct client *, key_code), struct client *c, 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, c, key);
281 		}
282 	}
283 	if (!fired && current) {
284 		mti = mtd->line_list[mtd->current].item;
285 		cb(mtd->modedata, mti->itemdata, c, 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_nputs(&ctx, w, &gc0, "%s", text);
558 			screen_write_clearendofline(&ctx, 8);
559 		} else {
560 			screen_write_nputs(&ctx, w, &gc, "%s", text);
561 			screen_write_clearendofline(&ctx, gc.bg);
562 		}
563 		free(text);
564 
565 		if (mti->tagged) {
566 			gc.attr ^= GRID_ATTR_BRIGHT;
567 			gc0.attr ^= GRID_ATTR_BRIGHT;
568 		}
569 	}
570 
571 	sy = screen_size_y(s);
572 	if (!mtd->preview || sy <= 4 || h <= 4 || sy - h <= 4 || w <= 4) {
573 		screen_write_stop(&ctx);
574 		return;
575 	}
576 
577 	line = &mtd->line_list[mtd->current];
578 	mti = line->item;
579 
580 	screen_write_cursormove(&ctx, 0, h);
581 	screen_write_box(&ctx, w, sy - h);
582 
583 	xasprintf(&text, " %s (sort: %s) ", mti->name,
584 	    mtd->sort_list[mtd->sort_type]);
585 	if (w - 2 >= strlen(text)) {
586 		screen_write_cursormove(&ctx, 1, h);
587 		screen_write_puts(&ctx, &gc0, "%s", text);
588 	}
589 	free(text);
590 
591 	box_x = w - 4;
592 	box_y = sy - h - 2;
593 
594 	if (box_x != 0 && box_y != 0)
595 		box = mtd->drawcb(mtd->modedata, mti->itemdata, box_x, box_y);
596 	if (box != NULL) {
597 		screen_write_cursormove(&ctx, 2, h + 1);
598 		screen_write_copy(&ctx, box, 0, 0, box_x, box_y, NULL, NULL);
599 
600 		screen_free(box);
601 	}
602 
603 	screen_write_stop(&ctx);
604 }
605 
606 static struct mode_tree_item *
607 mode_tree_search_for(struct mode_tree_data *mtd)
608 {
609 	struct mode_tree_item	*mti, *last, *next;
610 
611 	if (mtd->search == NULL)
612 		return (NULL);
613 
614 	mti = last = mtd->line_list[mtd->current].item;
615 	for (;;) {
616 		if (!TAILQ_EMPTY(&mti->children))
617 			mti = TAILQ_FIRST(&mti->children);
618 		else if ((next = TAILQ_NEXT(mti, entry)) != NULL)
619 			mti = next;
620 		else {
621 			for (;;) {
622 				mti = mti->parent;
623 				if (mti == NULL)
624 					break;
625 				if ((next = TAILQ_NEXT(mti, entry)) != NULL) {
626 					mti = next;
627 					break;
628 				}
629 			}
630 		}
631 		if (mti == NULL)
632 			mti = TAILQ_FIRST(&mtd->children);
633 		if (mti == last)
634 			break;
635 
636 		if (mtd->searchcb == NULL) {
637 			if (strstr(mti->name, mtd->search) != NULL)
638 				return (mti);
639 			continue;
640 		}
641 		if (mtd->searchcb(mtd->modedata, mti->itemdata, mtd->search))
642 			return (mti);
643 	}
644 	return (NULL);
645 }
646 
647 static void
648 mode_tree_search_set(struct mode_tree_data *mtd)
649 {
650 	struct mode_tree_item	*mti, *loop;
651 	uint64_t		 tag;
652 
653 	mti = mode_tree_search_for(mtd);
654 	if (mti == NULL)
655 		return;
656 	tag = mti->tag;
657 
658 	loop = mti->parent;
659 	while (loop != NULL) {
660 		loop->expanded = 1;
661 		loop = loop->parent;
662 	}
663 
664 	mode_tree_build(mtd);
665 	mode_tree_set_current(mtd, tag);
666 	mode_tree_draw(mtd);
667 	mtd->wp->flags |= PANE_REDRAW;
668 }
669 
670 static int
671 mode_tree_search_callback(__unused struct client *c, void *data, const char *s,
672     __unused int done)
673 {
674 	struct mode_tree_data	*mtd = data;
675 
676 	if (mtd->dead)
677 		return (0);
678 
679 	free(mtd->search);
680 	if (s == NULL || *s == '\0') {
681 		mtd->search = NULL;
682 		return (0);
683 	}
684 	mtd->search = xstrdup(s);
685 	mode_tree_search_set(mtd);
686 
687 	return (0);
688 }
689 
690 static void
691 mode_tree_search_free(void *data)
692 {
693 	mode_tree_remove_ref(data);
694 }
695 
696 static int
697 mode_tree_filter_callback(__unused struct client *c, void *data, const char *s,
698     __unused int done)
699 {
700 	struct mode_tree_data	*mtd = data;
701 
702 	if (mtd->dead)
703 		return (0);
704 
705 	if (mtd->filter != NULL)
706 		free(mtd->filter);
707 	if (s == NULL || *s == '\0')
708 		mtd->filter = NULL;
709 	else
710 		mtd->filter = xstrdup(s);
711 
712 	mode_tree_build(mtd);
713 	mode_tree_draw(mtd);
714 	mtd->wp->flags |= PANE_REDRAW;
715 
716 	return (0);
717 }
718 
719 static void
720 mode_tree_filter_free(void *data)
721 {
722 	mode_tree_remove_ref(data);
723 }
724 
725 int
726 mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
727     struct mouse_event *m)
728 {
729 	struct mode_tree_line	*line;
730 	struct mode_tree_item	*current, *parent;
731 	u_int			 i, x, y;
732 	int			 choice;
733 	key_code		 tmp;
734 
735 	if (*key == KEYC_MOUSEDOWN1_PANE) {
736 		if (cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) {
737 			*key = KEYC_NONE;
738 			return (0);
739 		}
740 		if (x > mtd->width || y > mtd->height) {
741 			*key = KEYC_NONE;
742 			return (0);
743 		}
744 		if (mtd->offset + y < mtd->line_size) {
745 			mtd->current = mtd->offset + y;
746 			*key = '\r';
747 			return (0);
748 		}
749 	}
750 
751 	line = &mtd->line_list[mtd->current];
752 	current = line->item;
753 
754 	choice = -1;
755 	if (*key >= '0' && *key <= '9')
756 		choice = (*key) - '0';
757 	else if (((*key) & KEYC_MASK_MOD) == KEYC_ESCAPE) {
758 		tmp = (*key) & KEYC_MASK_KEY;
759 		if (tmp >= 'a' && tmp <= 'z')
760 			choice = 10 + (tmp - 'a');
761 	}
762 	if (choice != -1) {
763 		if ((u_int)choice > mtd->line_size - 1) {
764 			*key = KEYC_NONE;
765 			return (0);
766 		}
767 		mtd->current = choice;
768 		*key = '\r';
769 		return (0);
770 	}
771 
772 	switch (*key) {
773 	case 'q':
774 	case '\033': /* Escape */
775 		return (1);
776 	case KEYC_UP:
777 	case 'k':
778 	case KEYC_WHEELUP_PANE:
779 	case '\020': /* C-p */
780 		mode_tree_up(mtd, 1);
781 		break;
782 	case KEYC_DOWN:
783 	case 'j':
784 	case KEYC_WHEELDOWN_PANE:
785 	case '\016': /* C-n */
786 		mode_tree_down(mtd, 1);
787 		break;
788 	case KEYC_PPAGE:
789 	case '\002': /* C-b */
790 		for (i = 0; i < mtd->height; i++) {
791 			if (mtd->current == 0)
792 				break;
793 			mode_tree_up(mtd, 1);
794 		}
795 		break;
796 	case KEYC_NPAGE:
797 	case '\006': /* C-f */
798 		for (i = 0; i < mtd->height; i++) {
799 			if (mtd->current == mtd->line_size - 1)
800 				break;
801 			mode_tree_down(mtd, 1);
802 		}
803 		break;
804 	case KEYC_HOME:
805 		mtd->current = 0;
806 		mtd->offset = 0;
807 		break;
808 	case KEYC_END:
809 		mtd->current = mtd->line_size - 1;
810 		if (mtd->current > mtd->height - 1)
811 			mtd->offset = mtd->current - mtd->height + 1;
812 		else
813 			mtd->offset = 0;
814 		break;
815 	case 't':
816 		/*
817 		 * Do not allow parents and children to both be tagged: untag
818 		 * all parents and children of current.
819 		 */
820 		if (!current->tagged) {
821 			parent = current->parent;
822 			while (parent != NULL) {
823 				parent->tagged = 0;
824 				parent = parent->parent;
825 			}
826 			mode_tree_clear_tagged(&current->children);
827 			current->tagged = 1;
828 		} else
829 			current->tagged = 0;
830 		mode_tree_down(mtd, 0);
831 		break;
832 	case 'T':
833 		for (i = 0; i < mtd->line_size; i++)
834 			mtd->line_list[i].item->tagged = 0;
835 		break;
836 	case '\024': /* C-t */
837 		for (i = 0; i < mtd->line_size; i++) {
838 			if (mtd->line_list[i].item->parent == NULL)
839 				mtd->line_list[i].item->tagged = 1;
840 			else
841 				mtd->line_list[i].item->tagged = 0;
842 		}
843 		break;
844 	case 'O':
845 		mtd->sort_type++;
846 		if (mtd->sort_type == mtd->sort_size)
847 			mtd->sort_type = 0;
848 		mode_tree_build(mtd);
849 		break;
850 	case KEYC_LEFT:
851 	case 'h':
852 	case '-':
853 		if (line->flat || !current->expanded)
854 			current = current->parent;
855 		if (current == NULL)
856 			mode_tree_up(mtd, 0);
857 		else {
858 			current->expanded = 0;
859 			mtd->current = current->line;
860 			mode_tree_build(mtd);
861 		}
862 		break;
863 	case KEYC_RIGHT:
864 	case 'l':
865 	case '+':
866 		if (line->flat || current->expanded)
867 			mode_tree_down(mtd, 0);
868 		else if (!line->flat) {
869 			current->expanded = 1;
870 			mode_tree_build(mtd);
871 		}
872 		break;
873 	case '\023': /* C-s */
874 		mtd->references++;
875 		status_prompt_set(c, "(search) ", "",
876 		    mode_tree_search_callback, mode_tree_search_free, mtd,
877 		    PROMPT_NOFORMAT);
878 		break;
879 	case 'n':
880 		mode_tree_search_set(mtd);
881 		break;
882 	case 'f':
883 		mtd->references++;
884 		status_prompt_set(c, "(filter) ", mtd->filter,
885 		    mode_tree_filter_callback, mode_tree_filter_free, mtd,
886 		    PROMPT_NOFORMAT);
887 		break;
888 	case 'v':
889 		mtd->preview = !mtd->preview;
890 		mode_tree_build(mtd);
891 		if (mtd->preview)
892 			mode_tree_check_selected(mtd);
893 		break;
894 	}
895 	return (0);
896 }
897 
898 void
899 mode_tree_run_command(struct client *c, struct cmd_find_state *fs,
900     const char *template, const char *name)
901 {
902 	struct cmdq_item	*new_item;
903 	struct cmd_list		*cmdlist;
904 	char			*command, *cause;
905 
906 	command = cmd_template_replace(template, name, 1);
907 	if (command == NULL || *command == '\0') {
908 		free(command);
909 		return;
910 	}
911 
912 	cmdlist = cmd_string_parse(command, NULL, 0, &cause);
913 	if (cmdlist == NULL) {
914 		if (cause != NULL && c != NULL) {
915 			*cause = toupper((u_char)*cause);
916 			status_message_set(c, "%s", cause);
917 		}
918 		free(cause);
919 	} else {
920 		new_item = cmdq_get_command(cmdlist, fs, NULL, 0);
921 		cmdq_append(c, new_item);
922 		cmd_list_free(cmdlist);
923 	}
924 
925 	free(command);
926 }
927