xref: /netbsd-src/external/bsd/tmux/dist/mode-tree.c (revision 890b6d91a44b7fcb2dfbcbc1e93463086e462d2d)
1 /* $OpenBSD$ */
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 enum mode_tree_search_dir {
29 	MODE_TREE_SEARCH_FORWARD,
30 	MODE_TREE_SEARCH_BACKWARD
31 };
32 
33 struct mode_tree_item;
34 TAILQ_HEAD(mode_tree_list, mode_tree_item);
35 
36 struct mode_tree_data {
37 	int			  dead;
38 	u_int			  references;
39 	int			  zoomed;
40 
41 	struct window_pane	 *wp;
42 	void			 *modedata;
43 	const struct menu_item	 *menu;
44 
45 	const char		**sort_list;
46 	u_int			  sort_size;
47 	struct mode_tree_sort_criteria sort_crit;
48 
49 	mode_tree_build_cb        buildcb;
50 	mode_tree_draw_cb         drawcb;
51 	mode_tree_search_cb       searchcb;
52 	mode_tree_menu_cb         menucb;
53 	mode_tree_height_cb       heightcb;
54 	mode_tree_key_cb	  keycb;
55 
56 	struct mode_tree_list	  children;
57 	struct mode_tree_list	  saved;
58 
59 	struct mode_tree_line	 *line_list;
60 	u_int			  line_size;
61 
62 	u_int			  depth;
63 
64 	u_int			  width;
65 	u_int			  height;
66 
67 	u_int			  offset;
68 	u_int			  current;
69 
70 	struct screen		  screen;
71 
72 	int			  preview;
73 	char			 *search;
74 	char			 *filter;
75 	int			  no_matches;
76 	enum mode_tree_search_dir search_dir;
77 };
78 
79 struct mode_tree_item {
80 	struct mode_tree_item		*parent;
81 	void				*itemdata;
82 	u_int				 line;
83 
84 	key_code			 key;
85 	const char			*keystr;
86 	size_t				 keylen;
87 
88 	uint64_t			 tag;
89 	const char			*name;
90 	const char			*text;
91 
92 	int				 expanded;
93 	int				 tagged;
94 
95 	int				 draw_as_parent;
96 	int				 no_tag;
97 
98 	struct mode_tree_list		 children;
99 	TAILQ_ENTRY(mode_tree_item)	 entry;
100 };
101 
102 struct mode_tree_line {
103 	struct mode_tree_item		*item;
104 	u_int				 depth;
105 	int				 last;
106 	int				 flat;
107 };
108 
109 struct mode_tree_menu {
110 	struct mode_tree_data		*data;
111 	struct client			*c;
112 	u_int				 line;
113 };
114 
115 static void mode_tree_free_items(struct mode_tree_list *);
116 
117 static const struct menu_item mode_tree_menu_items[] = {
118 	{ "Scroll Left", '<', NULL },
119 	{ "Scroll Right", '>', NULL },
120 	{ "", KEYC_NONE, NULL },
121 	{ "Cancel", 'q', NULL },
122 
123 	{ NULL, KEYC_NONE, NULL }
124 };
125 
126 static struct mode_tree_item *
127 mode_tree_find_item(struct mode_tree_list *mtl, uint64_t tag)
128 {
129 	struct mode_tree_item	*mti, *child;
130 
131 	TAILQ_FOREACH(mti, mtl, entry) {
132 		if (mti->tag == tag)
133 			return (mti);
134 		child = mode_tree_find_item(&mti->children, tag);
135 		if (child != NULL)
136 			return (child);
137 	}
138 	return (NULL);
139 }
140 
141 static void
142 mode_tree_free_item(struct mode_tree_item *mti)
143 {
144 	mode_tree_free_items(&mti->children);
145 
146 	free(__UNCONST(mti->name));
147 	free(__UNCONST(mti->text));
148 	free(__UNCONST(mti->keystr));
149 
150 	free(mti);
151 }
152 
153 static void
154 mode_tree_free_items(struct mode_tree_list *mtl)
155 {
156 	struct mode_tree_item	*mti, *mti1;
157 
158 	TAILQ_FOREACH_SAFE(mti, mtl, entry, mti1) {
159 		TAILQ_REMOVE(mtl, mti, entry);
160 		mode_tree_free_item(mti);
161 	}
162 }
163 
164 static void
165 mode_tree_check_selected(struct mode_tree_data *mtd)
166 {
167 	/*
168 	 * If the current line would now be off screen reset the offset to the
169 	 * last visible line.
170 	 */
171 	if (mtd->current > mtd->height - 1)
172 		mtd->offset = mtd->current - mtd->height + 1;
173 }
174 
175 static void
176 mode_tree_clear_lines(struct mode_tree_data *mtd)
177 {
178 	free(mtd->line_list);
179 	mtd->line_list = NULL;
180 	mtd->line_size = 0;
181 }
182 
183 static void
184 mode_tree_build_lines(struct mode_tree_data *mtd,
185     struct mode_tree_list *mtl, u_int depth)
186 {
187 	struct mode_tree_item	*mti;
188 	struct mode_tree_line	*line;
189 	u_int			 i;
190 	int			 flat = 1;
191 
192 	mtd->depth = depth;
193 	TAILQ_FOREACH(mti, mtl, entry) {
194 		mtd->line_list = xreallocarray(mtd->line_list,
195 		    mtd->line_size + 1, sizeof *mtd->line_list);
196 
197 		line = &mtd->line_list[mtd->line_size++];
198 		line->item = mti;
199 		line->depth = depth;
200 		line->last = (mti == TAILQ_LAST(mtl, mode_tree_list));
201 
202 		mti->line = (mtd->line_size - 1);
203 		if (!TAILQ_EMPTY(&mti->children))
204 			flat = 0;
205 		if (mti->expanded)
206 			mode_tree_build_lines(mtd, &mti->children, depth + 1);
207 
208 		if (mtd->keycb != NULL) {
209 			mti->key = mtd->keycb(mtd->modedata, mti->itemdata,
210 			    mti->line);
211 			if (mti->key == KEYC_UNKNOWN)
212 				mti->key = KEYC_NONE;
213 		} else if (mti->line < 10)
214 			mti->key = '0' + mti->line;
215 		else if (mti->line < 36)
216 			mti->key = KEYC_META|('a' + mti->line - 10);
217 		else
218 			mti->key = KEYC_NONE;
219 		if (mti->key != KEYC_NONE) {
220 			mti->keystr = xstrdup(key_string_lookup_key(mti->key,
221 			    0));
222 			mti->keylen = strlen(mti->keystr);
223 		} else {
224 			mti->keystr = NULL;
225 			mti->keylen = 0;
226 		}
227 	}
228 	TAILQ_FOREACH(mti, mtl, entry) {
229 		for (i = 0; i < mtd->line_size; i++) {
230 			line = &mtd->line_list[i];
231 			if (line->item == mti)
232 				line->flat = flat;
233 		}
234 	}
235 }
236 
237 static void
238 mode_tree_clear_tagged(struct mode_tree_list *mtl)
239 {
240 	struct mode_tree_item	*mti;
241 
242 	TAILQ_FOREACH(mti, mtl, entry) {
243 		mti->tagged = 0;
244 		mode_tree_clear_tagged(&mti->children);
245 	}
246 }
247 
248 void
249 mode_tree_up(struct mode_tree_data *mtd, int wrap)
250 {
251 	if (mtd->current == 0) {
252 		if (wrap) {
253 			mtd->current = mtd->line_size - 1;
254 			if (mtd->line_size >= mtd->height)
255 				mtd->offset = mtd->line_size - mtd->height;
256 		}
257 	} else {
258 		mtd->current--;
259 		if (mtd->current < mtd->offset)
260 			mtd->offset--;
261 	}
262 }
263 
264 int
265 mode_tree_down(struct mode_tree_data *mtd, int wrap)
266 {
267 	if (mtd->current == mtd->line_size - 1) {
268 		if (wrap) {
269 			mtd->current = 0;
270 			mtd->offset = 0;
271 		} else
272 			return (0);
273 	} else {
274 		mtd->current++;
275 		if (mtd->current > mtd->offset + mtd->height - 1)
276 			mtd->offset++;
277 	}
278 	return (1);
279 }
280 
281 void *
282 mode_tree_get_current(struct mode_tree_data *mtd)
283 {
284 	return (mtd->line_list[mtd->current].item->itemdata);
285 }
286 
287 const char *
288 mode_tree_get_current_name(struct mode_tree_data *mtd)
289 {
290 	return (mtd->line_list[mtd->current].item->name);
291 }
292 
293 void
294 mode_tree_expand_current(struct mode_tree_data *mtd)
295 {
296 	if (!mtd->line_list[mtd->current].item->expanded) {
297 		mtd->line_list[mtd->current].item->expanded = 1;
298 		mode_tree_build(mtd);
299 	}
300 }
301 
302 void
303 mode_tree_collapse_current(struct mode_tree_data *mtd)
304 {
305 	if (mtd->line_list[mtd->current].item->expanded) {
306 		mtd->line_list[mtd->current].item->expanded = 0;
307 		mode_tree_build(mtd);
308 	}
309 }
310 
311 static int
312 mode_tree_get_tag(struct mode_tree_data *mtd, uint64_t tag, u_int *found)
313 {
314 	u_int	i;
315 
316 	for (i = 0; i < mtd->line_size; i++) {
317 		if (mtd->line_list[i].item->tag == tag)
318 			break;
319 	}
320 	if (i != mtd->line_size) {
321 		*found = i;
322 		return (1);
323 	}
324 	return (0);
325 }
326 
327 void
328 mode_tree_expand(struct mode_tree_data *mtd, uint64_t tag)
329 {
330 	u_int	found;
331 
332 	if (!mode_tree_get_tag(mtd, tag, &found))
333 	    return;
334 	if (!mtd->line_list[found].item->expanded) {
335 		mtd->line_list[found].item->expanded = 1;
336 		mode_tree_build(mtd);
337 	}
338 }
339 
340 int
341 mode_tree_set_current(struct mode_tree_data *mtd, uint64_t tag)
342 {
343 	u_int	found;
344 
345 	if (mode_tree_get_tag(mtd, tag, &found)) {
346 		mtd->current = found;
347 		if (mtd->current > mtd->height - 1)
348 			mtd->offset = mtd->current - mtd->height + 1;
349 		else
350 			mtd->offset = 0;
351 		return (1);
352 	}
353 	mtd->current = 0;
354 	mtd->offset = 0;
355 	return (0);
356 }
357 
358 u_int
359 mode_tree_count_tagged(struct mode_tree_data *mtd)
360 {
361 	struct mode_tree_item	*mti;
362 	u_int			 i, tagged;
363 
364 	tagged = 0;
365 	for (i = 0; i < mtd->line_size; i++) {
366 		mti = mtd->line_list[i].item;
367 		if (mti->tagged)
368 			tagged++;
369 	}
370 	return (tagged);
371 }
372 
373 void
374 mode_tree_each_tagged(struct mode_tree_data *mtd, mode_tree_each_cb cb,
375     struct client *c, key_code key, int current)
376 {
377 	struct mode_tree_item	*mti;
378 	u_int			 i;
379 	int			 fired;
380 
381 	fired = 0;
382 	for (i = 0; i < mtd->line_size; i++) {
383 		mti = mtd->line_list[i].item;
384 		if (mti->tagged) {
385 			fired = 1;
386 			cb(mtd->modedata, mti->itemdata, c, key);
387 		}
388 	}
389 	if (!fired && current) {
390 		mti = mtd->line_list[mtd->current].item;
391 		cb(mtd->modedata, mti->itemdata, c, key);
392 	}
393 }
394 
395 struct mode_tree_data *
396 mode_tree_start(struct window_pane *wp, struct args *args,
397     mode_tree_build_cb buildcb, mode_tree_draw_cb drawcb,
398     mode_tree_search_cb searchcb, mode_tree_menu_cb menucb,
399     mode_tree_height_cb heightcb, mode_tree_key_cb keycb, void *modedata,
400     const struct menu_item *menu, const char **sort_list, u_int sort_size,
401     struct screen **s)
402 {
403 	struct mode_tree_data	*mtd;
404 	const char		*sort;
405 	u_int			 i;
406 
407 	mtd = xcalloc(1, sizeof *mtd);
408 	mtd->references = 1;
409 
410 	mtd->wp = wp;
411 	mtd->modedata = modedata;
412 	mtd->menu = menu;
413 
414 	mtd->sort_list = sort_list;
415 	mtd->sort_size = sort_size;
416 
417 	mtd->preview = !args_has(args, 'N');
418 
419 	sort = args_get(args, 'O');
420 	if (sort != NULL) {
421 		for (i = 0; i < sort_size; i++) {
422 			if (strcasecmp(sort, sort_list[i]) == 0)
423 				mtd->sort_crit.field = i;
424 		}
425 	}
426 	mtd->sort_crit.reversed = args_has(args, 'r');
427 
428 	if (args_has(args, 'f'))
429 		mtd->filter = xstrdup(args_get(args, 'f'));
430 	else
431 		mtd->filter = NULL;
432 
433 	mtd->buildcb = buildcb;
434 	mtd->drawcb = drawcb;
435 	mtd->searchcb = searchcb;
436 	mtd->menucb = menucb;
437 	mtd->heightcb = heightcb;
438 	mtd->keycb = keycb;
439 
440 	TAILQ_INIT(&mtd->children);
441 
442 	*s = &mtd->screen;
443 	screen_init(*s, screen_size_x(&wp->base), screen_size_y(&wp->base), 0);
444 	(*s)->mode &= ~MODE_CURSOR;
445 
446 	return (mtd);
447 }
448 
449 void
450 mode_tree_zoom(struct mode_tree_data *mtd, struct args *args)
451 {
452 	struct window_pane	*wp = mtd->wp;
453 
454 	if (args_has(args, 'Z')) {
455 		mtd->zoomed = (wp->window->flags & WINDOW_ZOOMED);
456 		if (!mtd->zoomed && window_zoom(wp) == 0)
457 			server_redraw_window(wp->window);
458 	} else
459 		mtd->zoomed = -1;
460 }
461 
462 static void
463 mode_tree_set_height(struct mode_tree_data *mtd)
464 {
465 	struct screen	*s = &mtd->screen;
466 	u_int		 height;
467 
468 	if (mtd->heightcb != NULL) {
469 		height = mtd->heightcb(mtd, screen_size_y(s));
470 		if (height < screen_size_y(s))
471 		    mtd->height = screen_size_y(s) - height;
472 	} else {
473 		mtd->height = (screen_size_y(s) / 3) * 2;
474 		if (mtd->height > mtd->line_size)
475 			mtd->height = screen_size_y(s) / 2;
476 	}
477 	if (mtd->height < 10)
478 		mtd->height = screen_size_y(s);
479 	if (screen_size_y(s) - mtd->height < 2)
480 		mtd->height = screen_size_y(s);
481 }
482 
483 void
484 mode_tree_build(struct mode_tree_data *mtd)
485 {
486 	struct screen	*s = &mtd->screen;
487 	uint64_t	 tag;
488 
489 	if (mtd->line_list != NULL)
490 		tag = mtd->line_list[mtd->current].item->tag;
491 	else
492 		tag = UINT64_MAX;
493 
494 	TAILQ_CONCAT(&mtd->saved, &mtd->children, entry);
495 	TAILQ_INIT(&mtd->children);
496 
497 	mtd->buildcb(mtd->modedata, &mtd->sort_crit, &tag, mtd->filter);
498 	mtd->no_matches = TAILQ_EMPTY(&mtd->children);
499 	if (mtd->no_matches)
500 		mtd->buildcb(mtd->modedata, &mtd->sort_crit, &tag, NULL);
501 
502 	mode_tree_free_items(&mtd->saved);
503 	TAILQ_INIT(&mtd->saved);
504 
505 	mode_tree_clear_lines(mtd);
506 	mode_tree_build_lines(mtd, &mtd->children, 0);
507 
508 	if (mtd->line_list != NULL && tag == UINT64_MAX)
509 		tag = mtd->line_list[mtd->current].item->tag;
510 	mode_tree_set_current(mtd, tag);
511 
512 	mtd->width = screen_size_x(s);
513 	if (mtd->preview)
514 		mode_tree_set_height(mtd);
515 	else
516 		mtd->height = screen_size_y(s);
517 	mode_tree_check_selected(mtd);
518 }
519 
520 static void
521 mode_tree_remove_ref(struct mode_tree_data *mtd)
522 {
523 	if (--mtd->references == 0)
524 		free(mtd);
525 }
526 
527 void
528 mode_tree_free(struct mode_tree_data *mtd)
529 {
530 	struct window_pane	*wp = mtd->wp;
531 
532 	if (mtd->zoomed == 0)
533 		server_unzoom_window(wp->window);
534 
535 	mode_tree_free_items(&mtd->children);
536 	mode_tree_clear_lines(mtd);
537 	screen_free(&mtd->screen);
538 
539 	free(mtd->search);
540 	free(mtd->filter);
541 
542 	mtd->dead = 1;
543 	mode_tree_remove_ref(mtd);
544 }
545 
546 void
547 mode_tree_resize(struct mode_tree_data *mtd, u_int sx, u_int sy)
548 {
549 	struct screen	*s = &mtd->screen;
550 
551 	screen_resize(s, sx, sy, 0);
552 
553 	mode_tree_build(mtd);
554 	mode_tree_draw(mtd);
555 
556 	mtd->wp->flags |= PANE_REDRAW;
557 }
558 
559 struct mode_tree_item *
560 mode_tree_add(struct mode_tree_data *mtd, struct mode_tree_item *parent,
561     void *itemdata, uint64_t tag, const char *name, const char *text,
562     int expanded)
563 {
564 	struct mode_tree_item	*mti, *saved;
565 
566 	log_debug("%s: %llu, %s %s", __func__, (unsigned long long)tag,
567 	    name, (text == NULL ? "" : text));
568 
569 	mti = xcalloc(1, sizeof *mti);
570 	mti->parent = parent;
571 	mti->itemdata = itemdata;
572 
573 	mti->tag = tag;
574 	mti->name = xstrdup(name);
575 	if (text != NULL)
576 		mti->text = xstrdup(text);
577 
578 	saved = mode_tree_find_item(&mtd->saved, tag);
579 	if (saved != NULL) {
580 		if (parent == NULL || parent->expanded)
581 			mti->tagged = saved->tagged;
582 		mti->expanded = saved->expanded;
583 	} else if (expanded == -1)
584 		mti->expanded = 1;
585 	else
586 		mti->expanded = expanded;
587 
588 	TAILQ_INIT(&mti->children);
589 
590 	if (parent != NULL)
591 		TAILQ_INSERT_TAIL(&parent->children, mti, entry);
592 	else
593 		TAILQ_INSERT_TAIL(&mtd->children, mti, entry);
594 
595 	return (mti);
596 }
597 
598 void
599 mode_tree_draw_as_parent(struct mode_tree_item *mti)
600 {
601 	mti->draw_as_parent = 1;
602 }
603 
604 void
605 mode_tree_no_tag(struct mode_tree_item *mti)
606 {
607 	mti->no_tag = 1;
608 }
609 
610 void
611 mode_tree_remove(struct mode_tree_data *mtd, struct mode_tree_item *mti)
612 {
613 	struct mode_tree_item	*parent = mti->parent;
614 
615 	if (parent != NULL)
616 		TAILQ_REMOVE(&parent->children, mti, entry);
617 	else
618 		TAILQ_REMOVE(&mtd->children, mti, entry);
619 	mode_tree_free_item(mti);
620 }
621 
622 void
623 mode_tree_draw(struct mode_tree_data *mtd)
624 {
625 	struct window_pane	*wp = mtd->wp;
626 	struct screen		*s = &mtd->screen;
627 	struct mode_tree_line	*line;
628 	struct mode_tree_item	*mti;
629 	struct options		*oo = wp->window->options;
630 	struct screen_write_ctx	 ctx;
631 	struct grid_cell	 gc0, gc;
632 	u_int			 w, h, i, j, sy, box_x, box_y, width;
633 	char			*text, *start, *key;
634 	const char		*tag, *symbol;
635 	size_t			 size, n;
636 	int			 keylen, pad;
637 
638 	if (mtd->line_size == 0)
639 		return;
640 
641 	memcpy(&gc0, &grid_default_cell, sizeof gc0);
642 	memcpy(&gc, &grid_default_cell, sizeof gc);
643 	style_apply(&gc, oo, "mode-style", NULL);
644 
645 	w = mtd->width;
646 	h = mtd->height;
647 
648 	screen_write_start(&ctx, s);
649 	screen_write_clearscreen(&ctx, 8);
650 
651 	keylen = 0;
652 	for (i = 0; i < mtd->line_size; i++) {
653 		mti = mtd->line_list[i].item;
654 		if (mti->key == KEYC_NONE)
655 			continue;
656 		if ((int)mti->keylen + 3 > keylen)
657 			keylen = mti->keylen + 3;
658 	}
659 
660 	for (i = 0; i < mtd->line_size; i++) {
661 		if (i < mtd->offset)
662 			continue;
663 		if (i > mtd->offset + h - 1)
664 			break;
665 		line = &mtd->line_list[i];
666 		mti = line->item;
667 
668 		screen_write_cursormove(&ctx, 0, i - mtd->offset, 0);
669 
670 		pad = keylen - 2 - mti->keylen;
671 		if (mti->key != KEYC_NONE)
672 			xasprintf(&key, "(%s)%*s", mti->keystr, pad, "");
673 		else
674 			key = xstrdup("");
675 
676 		if (line->flat)
677 			symbol = "";
678 		else if (TAILQ_EMPTY(&mti->children))
679 			symbol = "  ";
680 		else if (mti->expanded)
681 			symbol = "- ";
682 		else
683 			symbol = "+ ";
684 
685 		if (line->depth == 0)
686 			start = xstrdup(symbol);
687 		else {
688 			size = (4 * line->depth) + 32;
689 
690 			start = xcalloc(1, size);
691 			for (j = 1; j < line->depth; j++) {
692 				if (mti->parent != NULL &&
693 				    mtd->line_list[mti->parent->line].last)
694 					strlcat(start, "    ", size);
695 				else
696 					strlcat(start, "\001x\001   ", size);
697 			}
698 			if (line->last)
699 				strlcat(start, "\001mq\001> ", size);
700 			else
701 				strlcat(start, "\001tq\001> ", size);
702 			strlcat(start, symbol, size);
703 		}
704 
705 		if (mti->tagged)
706 			tag = "*";
707 		else
708 			tag = "";
709 		xasprintf(&text, "%-*s%s%s%s%s", keylen, key, start, mti->name,
710 		    tag, (mti->text != NULL) ? ": " : "" );
711 		width = utf8_cstrwidth(text);
712 		if (width > w)
713 			width = w;
714 		free(start);
715 
716 		if (mti->tagged) {
717 			gc.attr ^= GRID_ATTR_BRIGHT;
718 			gc0.attr ^= GRID_ATTR_BRIGHT;
719 		}
720 
721 		if (i != mtd->current) {
722 			screen_write_clearendofline(&ctx, 8);
723 			screen_write_nputs(&ctx, w, &gc0, "%s", text);
724 			if (mti->text != NULL) {
725 				format_draw(&ctx, &gc0, w - width, mti->text,
726 				    NULL, 0);
727 			}
728 		} else {
729 			screen_write_clearendofline(&ctx, gc.bg);
730 			screen_write_nputs(&ctx, w, &gc, "%s", text);
731 			if (mti->text != NULL) {
732 				format_draw(&ctx, &gc, w - width, mti->text,
733 				    NULL, 0);
734 			}
735 		}
736 		free(text);
737 		free(key);
738 
739 		if (mti->tagged) {
740 			gc.attr ^= GRID_ATTR_BRIGHT;
741 			gc0.attr ^= GRID_ATTR_BRIGHT;
742 		}
743 	}
744 
745 	sy = screen_size_y(s);
746 	if (!mtd->preview || sy <= 4 || h <= 4 || sy - h <= 4 || w <= 4)
747 		goto done;
748 
749 	line = &mtd->line_list[mtd->current];
750 	mti = line->item;
751 	if (mti->draw_as_parent)
752 		mti = mti->parent;
753 
754 	screen_write_cursormove(&ctx, 0, h, 0);
755 	screen_write_box(&ctx, w, sy - h, BOX_LINES_DEFAULT, NULL, NULL);
756 
757 	if (mtd->sort_list != NULL) {
758 		xasprintf(&text, " %s (sort: %s%s)", mti->name,
759 		    mtd->sort_list[mtd->sort_crit.field],
760 		    mtd->sort_crit.reversed ? ", reversed" : "");
761 	} else
762 		xasprintf(&text, " %s", mti->name);
763 	if (w - 2 >= strlen(text)) {
764 		screen_write_cursormove(&ctx, 1, h, 0);
765 		screen_write_puts(&ctx, &gc0, "%s", text);
766 
767 		if (mtd->no_matches)
768 			n = (sizeof "no matches") - 1;
769 		else
770 			n = (sizeof "active") - 1;
771 		if (mtd->filter != NULL && w - 2 >= strlen(text) + 10 + n + 2) {
772 			screen_write_puts(&ctx, &gc0, " (filter: ");
773 			if (mtd->no_matches)
774 				screen_write_puts(&ctx, &gc, "no matches");
775 			else
776 				screen_write_puts(&ctx, &gc0, "active");
777 			screen_write_puts(&ctx, &gc0, ") ");
778 		} else
779 			screen_write_puts(&ctx, &gc0, " ");
780 	}
781 	free(text);
782 
783 	box_x = w - 4;
784 	box_y = sy - h - 2;
785 
786 	if (box_x != 0 && box_y != 0) {
787 		screen_write_cursormove(&ctx, 2, h + 1, 0);
788 		mtd->drawcb(mtd->modedata, mti->itemdata, &ctx, box_x, box_y);
789 	}
790 
791 done:
792 	screen_write_cursormove(&ctx, 0, mtd->current - mtd->offset, 0);
793 	screen_write_stop(&ctx);
794 }
795 
796 static struct mode_tree_item *
797 mode_tree_search_backward(struct mode_tree_data *mtd)
798 {
799     struct mode_tree_item	*mti, *last, *prev;
800 
801     if (mtd->search == NULL)
802 	    return (NULL);
803 
804     mti = last = mtd->line_list[mtd->current].item;
805     for (;;) {
806         if ((prev = TAILQ_PREV(mti, mode_tree_list, entry)) != NULL) {
807 		/* Point to the last child in the previous subtree. */
808 		while (!TAILQ_EMPTY(&prev->children))
809 			prev = TAILQ_LAST(&prev->children, mode_tree_list);
810 		mti = prev;
811         } else {
812 		/* If prev is NULL, jump to the parent. */
813 		mti = mti->parent;
814         }
815 
816 	if (mti == NULL) {
817 		/* Point to the last child in the last root subtree. */
818 		prev = TAILQ_LAST(&mtd->children, mode_tree_list);
819 		while (!TAILQ_EMPTY(&prev->children))
820 			prev = TAILQ_LAST(&prev->children, mode_tree_list);
821 		mti = prev;
822 	}
823 	if (mti == last)
824 		break;
825 
826 	if (mtd->searchcb == NULL) {
827 		if (strstr(mti->name, mtd->search) != NULL)
828 			return (mti);
829 		continue;
830 	}
831 	if (mtd->searchcb(mtd->modedata, mti->itemdata, mtd->search))
832 		return (mti);
833     }
834     return (NULL);
835 }
836 
837 
838 static struct mode_tree_item *
839 mode_tree_search_forward(struct mode_tree_data *mtd)
840 {
841 	struct mode_tree_item	*mti, *last, *next;
842 
843 	if (mtd->search == NULL)
844 		return (NULL);
845 
846 	mti = last = mtd->line_list[mtd->current].item;
847 	for (;;) {
848 		if (!TAILQ_EMPTY(&mti->children))
849 			mti = TAILQ_FIRST(&mti->children);
850 		else if ((next = TAILQ_NEXT(mti, entry)) != NULL)
851 			mti = next;
852 		else {
853 			for (;;) {
854 				mti = mti->parent;
855 				if (mti == NULL)
856 					break;
857 				if ((next = TAILQ_NEXT(mti, entry)) != NULL) {
858 					mti = next;
859 					break;
860 				}
861 			}
862 		}
863 		if (mti == NULL)
864 			mti = TAILQ_FIRST(&mtd->children);
865 		if (mti == last)
866 			break;
867 
868 		if (mtd->searchcb == NULL) {
869 			if (strstr(mti->name, mtd->search) != NULL)
870 				return (mti);
871 			continue;
872 		}
873 		if (mtd->searchcb(mtd->modedata, mti->itemdata, mtd->search))
874 			return (mti);
875 	}
876 	return (NULL);
877 }
878 
879 static void
880 mode_tree_search_set(struct mode_tree_data *mtd)
881 {
882 	struct mode_tree_item	*mti, *loop;
883 	uint64_t		 tag;
884 
885 	if (mtd->search_dir == MODE_TREE_SEARCH_FORWARD)
886 		mti = mode_tree_search_forward(mtd);
887 	else
888 		mti = mode_tree_search_backward(mtd);
889 	if (mti == NULL)
890 		return;
891 	tag = mti->tag;
892 
893 	loop = mti->parent;
894 	while (loop != NULL) {
895 		loop->expanded = 1;
896 		loop = loop->parent;
897 	}
898 
899 	mode_tree_build(mtd);
900 	mode_tree_set_current(mtd, tag);
901 	mode_tree_draw(mtd);
902 	mtd->wp->flags |= PANE_REDRAW;
903 }
904 
905 static int
906 mode_tree_search_callback(__unused struct client *c, void *data, const char *s,
907     __unused int done)
908 {
909 	struct mode_tree_data	*mtd = data;
910 
911 	if (mtd->dead)
912 		return (0);
913 
914 	free(mtd->search);
915 	if (s == NULL || *s == '\0') {
916 		mtd->search = NULL;
917 		return (0);
918 	}
919 	mtd->search = xstrdup(s);
920 	mode_tree_search_set(mtd);
921 
922 	return (0);
923 }
924 
925 static void
926 mode_tree_search_free(void *data)
927 {
928 	mode_tree_remove_ref(data);
929 }
930 
931 static int
932 mode_tree_filter_callback(__unused struct client *c, void *data, const char *s,
933     __unused int done)
934 {
935 	struct mode_tree_data	*mtd = data;
936 
937 	if (mtd->dead)
938 		return (0);
939 
940 	if (mtd->filter != NULL)
941 		free(mtd->filter);
942 	if (s == NULL || *s == '\0')
943 		mtd->filter = NULL;
944 	else
945 		mtd->filter = xstrdup(s);
946 
947 	mode_tree_build(mtd);
948 	mode_tree_draw(mtd);
949 	mtd->wp->flags |= PANE_REDRAW;
950 
951 	return (0);
952 }
953 
954 static void
955 mode_tree_filter_free(void *data)
956 {
957 	mode_tree_remove_ref(data);
958 }
959 
960 static void
961 mode_tree_menu_callback(__unused struct menu *menu, __unused u_int idx,
962     key_code key, void *data)
963 {
964 	struct mode_tree_menu	*mtm = data;
965 	struct mode_tree_data	*mtd = mtm->data;
966 
967 	if (mtd->dead || key == KEYC_NONE)
968 		goto out;
969 
970 	if (mtm->line >= mtd->line_size)
971 		goto out;
972 	mtd->current = mtm->line;
973 	mtd->menucb(mtd->modedata, mtm->c, key);
974 
975 out:
976 	mode_tree_remove_ref(mtd);
977 	free(mtm);
978 }
979 
980 static void
981 mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x,
982     u_int y, int outside)
983 {
984 	struct mode_tree_item	*mti;
985 	struct menu		*menu;
986 	const struct menu_item	*items;
987 	struct mode_tree_menu	*mtm;
988 	char			*title;
989 	u_int			 line;
990 
991 	if (mtd->offset + y > mtd->line_size - 1)
992 		line = mtd->current;
993 	else
994 		line = mtd->offset + y;
995 	mti = mtd->line_list[line].item;
996 
997 	if (!outside) {
998 		items = mtd->menu;
999 		xasprintf(&title, "#[align=centre]%s", mti->name);
1000 	} else {
1001 		items = mode_tree_menu_items;
1002 		title = xstrdup("");
1003 	}
1004 	menu = menu_create(title);
1005 	menu_add_items(menu, items, NULL, c, NULL);
1006 	free(title);
1007 
1008 	mtm = xmalloc(sizeof *mtm);
1009 	mtm->data = mtd;
1010 	mtm->c = c;
1011 	mtm->line = line;
1012 	mtd->references++;
1013 
1014 	if (x >= (menu->width + 4) / 2)
1015 		x -= (menu->width + 4) / 2;
1016 	else
1017 		x = 0;
1018 	if (menu_display(menu, 0, 0, NULL, x, y, c, BOX_LINES_DEFAULT, NULL,
1019 	    NULL, NULL, NULL, mode_tree_menu_callback, mtm) != 0)
1020 		menu_free(menu);
1021 }
1022 
1023 int
1024 mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
1025     struct mouse_event *m, u_int *xp, u_int *yp)
1026 {
1027 	struct mode_tree_line	*line;
1028 	struct mode_tree_item	*current, *parent, *mti;
1029 	u_int			 i, x, y;
1030 	int			 choice;
1031 
1032 	if (KEYC_IS_MOUSE(*key) && m != NULL) {
1033 		if (cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) {
1034 			*key = KEYC_NONE;
1035 			return (0);
1036 		}
1037 		if (xp != NULL)
1038 			*xp = x;
1039 		if (yp != NULL)
1040 			*yp = y;
1041 		if (x > mtd->width || y > mtd->height) {
1042 			if (*key == KEYC_MOUSEDOWN3_PANE)
1043 				mode_tree_display_menu(mtd, c, x, y, 1);
1044 			if (!mtd->preview)
1045 				*key = KEYC_NONE;
1046 			return (0);
1047 		}
1048 		if (mtd->offset + y < mtd->line_size) {
1049 			if (*key == KEYC_MOUSEDOWN1_PANE ||
1050 			    *key == KEYC_MOUSEDOWN3_PANE ||
1051 			    *key == KEYC_DOUBLECLICK1_PANE)
1052 				mtd->current = mtd->offset + y;
1053 			if (*key == KEYC_DOUBLECLICK1_PANE)
1054 				*key = '\r';
1055 			else {
1056 				if (*key == KEYC_MOUSEDOWN3_PANE)
1057 					mode_tree_display_menu(mtd, c, x, y, 0);
1058 				*key = KEYC_NONE;
1059 			}
1060 		} else {
1061 			if (*key == KEYC_MOUSEDOWN3_PANE)
1062 				mode_tree_display_menu(mtd, c, x, y, 0);
1063 			*key = KEYC_NONE;
1064 		}
1065 		return (0);
1066 	}
1067 
1068 	line = &mtd->line_list[mtd->current];
1069 	current = line->item;
1070 
1071 	choice = -1;
1072 	for (i = 0; i < mtd->line_size; i++) {
1073 		if (*key == mtd->line_list[i].item->key) {
1074 			choice = i;
1075 			break;
1076 		}
1077 	}
1078 	if (choice != -1) {
1079 		if ((u_int)choice > mtd->line_size - 1) {
1080 			*key = KEYC_NONE;
1081 			return (0);
1082 		}
1083 		mtd->current = choice;
1084 		*key = '\r';
1085 		return (0);
1086 	}
1087 
1088 	switch (*key) {
1089 	case 'q':
1090 	case '\033': /* Escape */
1091 	case 'g'|KEYC_CTRL:
1092 		return (1);
1093 	case KEYC_UP:
1094 	case 'k':
1095 	case KEYC_WHEELUP_PANE:
1096 	case 'p'|KEYC_CTRL:
1097 		mode_tree_up(mtd, 1);
1098 		break;
1099 	case KEYC_DOWN:
1100 	case 'j':
1101 	case KEYC_WHEELDOWN_PANE:
1102 	case 'n'|KEYC_CTRL:
1103 		mode_tree_down(mtd, 1);
1104 		break;
1105 	case KEYC_PPAGE:
1106 	case 'b'|KEYC_CTRL:
1107 		for (i = 0; i < mtd->height; i++) {
1108 			if (mtd->current == 0)
1109 				break;
1110 			mode_tree_up(mtd, 1);
1111 		}
1112 		break;
1113 	case KEYC_NPAGE:
1114 	case 'f'|KEYC_CTRL:
1115 		for (i = 0; i < mtd->height; i++) {
1116 			if (mtd->current == mtd->line_size - 1)
1117 				break;
1118 			mode_tree_down(mtd, 1);
1119 		}
1120 		break;
1121 	case 'g':
1122 	case KEYC_HOME:
1123 		mtd->current = 0;
1124 		mtd->offset = 0;
1125 		break;
1126 	case 'G':
1127 	case KEYC_END:
1128 		mtd->current = mtd->line_size - 1;
1129 		if (mtd->current > mtd->height - 1)
1130 			mtd->offset = mtd->current - mtd->height + 1;
1131 		else
1132 			mtd->offset = 0;
1133 		break;
1134 	case 't':
1135 		/*
1136 		 * Do not allow parents and children to both be tagged: untag
1137 		 * all parents and children of current.
1138 		 */
1139 		if (current->no_tag)
1140 			break;
1141 		if (!current->tagged) {
1142 			parent = current->parent;
1143 			while (parent != NULL) {
1144 				parent->tagged = 0;
1145 				parent = parent->parent;
1146 			}
1147 			mode_tree_clear_tagged(&current->children);
1148 			current->tagged = 1;
1149 		} else
1150 			current->tagged = 0;
1151 		if (m != NULL)
1152 			mode_tree_down(mtd, 0);
1153 		break;
1154 	case 'T':
1155 		for (i = 0; i < mtd->line_size; i++)
1156 			mtd->line_list[i].item->tagged = 0;
1157 		break;
1158 	case 't'|KEYC_CTRL:
1159 		for (i = 0; i < mtd->line_size; i++) {
1160 			if ((mtd->line_list[i].item->parent == NULL &&
1161 			    !mtd->line_list[i].item->no_tag) ||
1162 			    (mtd->line_list[i].item->parent != NULL &&
1163 			    mtd->line_list[i].item->parent->no_tag))
1164 				mtd->line_list[i].item->tagged = 1;
1165 			else
1166 				mtd->line_list[i].item->tagged = 0;
1167 		}
1168 		break;
1169 	case 'O':
1170 		mtd->sort_crit.field++;
1171 		if (mtd->sort_crit.field >= mtd->sort_size)
1172 			mtd->sort_crit.field = 0;
1173 		mode_tree_build(mtd);
1174 		break;
1175 	case 'r':
1176 		mtd->sort_crit.reversed = !mtd->sort_crit.reversed;
1177 		mode_tree_build(mtd);
1178 		break;
1179 	case KEYC_LEFT:
1180 	case 'h':
1181 	case '-':
1182 		if (line->flat || !current->expanded)
1183 			current = current->parent;
1184 		if (current == NULL)
1185 			mode_tree_up(mtd, 0);
1186 		else {
1187 			current->expanded = 0;
1188 			mtd->current = current->line;
1189 			mode_tree_build(mtd);
1190 		}
1191 		break;
1192 	case KEYC_RIGHT:
1193 	case 'l':
1194 	case '+':
1195 		if (line->flat || current->expanded)
1196 			mode_tree_down(mtd, 0);
1197 		else if (!line->flat) {
1198 			current->expanded = 1;
1199 			mode_tree_build(mtd);
1200 		}
1201 		break;
1202 	case '-'|KEYC_META:
1203 		TAILQ_FOREACH(mti, &mtd->children, entry)
1204 			mti->expanded = 0;
1205 		mode_tree_build(mtd);
1206 		break;
1207 	case '+'|KEYC_META:
1208 		TAILQ_FOREACH(mti, &mtd->children, entry)
1209 			mti->expanded = 1;
1210 		mode_tree_build(mtd);
1211 		break;
1212 	case '?':
1213 	case '/':
1214 	case 's'|KEYC_CTRL:
1215 		mtd->references++;
1216 		status_prompt_set(c, NULL, "(search) ", "",
1217 		    mode_tree_search_callback, mode_tree_search_free, mtd,
1218 		    PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH);
1219 		break;
1220 	case 'n':
1221 		mtd->search_dir = MODE_TREE_SEARCH_FORWARD;
1222 		mode_tree_search_set(mtd);
1223 		break;
1224 	case 'N':
1225 		mtd->search_dir = MODE_TREE_SEARCH_BACKWARD;
1226 		mode_tree_search_set(mtd);
1227 		break;
1228 	case 'f':
1229 		mtd->references++;
1230 		status_prompt_set(c, NULL, "(filter) ", mtd->filter,
1231 		    mode_tree_filter_callback, mode_tree_filter_free, mtd,
1232 		    PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH);
1233 		break;
1234 	case 'v':
1235 		mtd->preview = !mtd->preview;
1236 		mode_tree_build(mtd);
1237 		if (mtd->preview)
1238 			mode_tree_check_selected(mtd);
1239 		break;
1240 	}
1241 	return (0);
1242 }
1243 
1244 void
1245 mode_tree_run_command(struct client *c, struct cmd_find_state *fs,
1246     const char *template, const char *name)
1247 {
1248 	struct cmdq_state	*state;
1249 	char			*command, *error;
1250 	enum cmd_parse_status	 status;
1251 
1252 	command = cmd_template_replace(template, name, 1);
1253 	if (command != NULL && *command != '\0') {
1254 		state = cmdq_new_state(fs, NULL, 0);
1255 		status = cmd_parse_and_append(command, NULL, c, state, &error);
1256 		if (status == CMD_PARSE_ERROR) {
1257 			if (c != NULL) {
1258 				*error = toupper((u_char)*error);
1259 				status_message_set(c, -1, 1, 0, "%s", error);
1260 			}
1261 			free(error);
1262 		}
1263 		cmdq_free_state(state);
1264 	}
1265 	free(command);
1266 }
1267