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