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