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