xref: /netbsd-src/external/bsd/tmux/dist/mode-tree.c (revision 890b6d91a44b7fcb2dfbcbc1e93463086e462d2d)
1c9ad075bSchristos /* $OpenBSD$ */
2c9ad075bSchristos 
3c9ad075bSchristos /*
4c9ad075bSchristos  * Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
5c9ad075bSchristos  *
6c9ad075bSchristos  * Permission to use, copy, modify, and distribute this software for any
7c9ad075bSchristos  * purpose with or without fee is hereby granted, provided that the above
8c9ad075bSchristos  * copyright notice and this permission notice appear in all copies.
9c9ad075bSchristos  *
10c9ad075bSchristos  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11c9ad075bSchristos  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12c9ad075bSchristos  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13c9ad075bSchristos  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14c9ad075bSchristos  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15c9ad075bSchristos  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16c9ad075bSchristos  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17c9ad075bSchristos  */
18c9ad075bSchristos 
19c9ad075bSchristos #include <sys/types.h>
20c9ad075bSchristos 
21c9ad075bSchristos #include <ctype.h>
22c9ad075bSchristos #include <stdio.h>
23c9ad075bSchristos #include <stdlib.h>
24c9ad075bSchristos #include <string.h>
25c9ad075bSchristos 
26c9ad075bSchristos #include "tmux.h"
27c9ad075bSchristos 
28*890b6d91Swiz enum mode_tree_search_dir {
29*890b6d91Swiz 	MODE_TREE_SEARCH_FORWARD,
30*890b6d91Swiz 	MODE_TREE_SEARCH_BACKWARD
31*890b6d91Swiz };
32*890b6d91Swiz 
33c9ad075bSchristos struct mode_tree_item;
34c9ad075bSchristos TAILQ_HEAD(mode_tree_list, mode_tree_item);
35c9ad075bSchristos 
36c9ad075bSchristos struct mode_tree_data {
37c9ad075bSchristos 	int			  dead;
38c9ad075bSchristos 	u_int			  references;
39c7e17de0Schristos 	int			  zoomed;
40c9ad075bSchristos 
41c9ad075bSchristos 	struct window_pane	 *wp;
42c9ad075bSchristos 	void			 *modedata;
4330744affSchristos 	const struct menu_item	 *menu;
44c9ad075bSchristos 
45c9ad075bSchristos 	const char		**sort_list;
46c9ad075bSchristos 	u_int			  sort_size;
4768e6ba84Schristos 	struct mode_tree_sort_criteria sort_crit;
48c9ad075bSchristos 
49c7e17de0Schristos 	mode_tree_build_cb        buildcb;
50c7e17de0Schristos 	mode_tree_draw_cb         drawcb;
51c7e17de0Schristos 	mode_tree_search_cb       searchcb;
5230744affSchristos 	mode_tree_menu_cb         menucb;
53e271dbb8Schristos 	mode_tree_height_cb       heightcb;
54e271dbb8Schristos 	mode_tree_key_cb	  keycb;
55c9ad075bSchristos 
56c9ad075bSchristos 	struct mode_tree_list	  children;
57c9ad075bSchristos 	struct mode_tree_list	  saved;
58c9ad075bSchristos 
59c9ad075bSchristos 	struct mode_tree_line	 *line_list;
60c9ad075bSchristos 	u_int			  line_size;
61c9ad075bSchristos 
62c9ad075bSchristos 	u_int			  depth;
63c9ad075bSchristos 
64c9ad075bSchristos 	u_int			  width;
65c9ad075bSchristos 	u_int			  height;
66c9ad075bSchristos 
67c9ad075bSchristos 	u_int			  offset;
68c9ad075bSchristos 	u_int			  current;
69c9ad075bSchristos 
70c9ad075bSchristos 	struct screen		  screen;
71c9ad075bSchristos 
72c9ad075bSchristos 	int			  preview;
73c9ad075bSchristos 	char			 *search;
74c9ad075bSchristos 	char			 *filter;
75c7e17de0Schristos 	int			  no_matches;
76*890b6d91Swiz 	enum mode_tree_search_dir search_dir;
77c9ad075bSchristos };
78c9ad075bSchristos 
79c9ad075bSchristos struct mode_tree_item {
80c9ad075bSchristos 	struct mode_tree_item		*parent;
81c9ad075bSchristos 	void				*itemdata;
82c9ad075bSchristos 	u_int				 line;
83c9ad075bSchristos 
84e271dbb8Schristos 	key_code			 key;
85e271dbb8Schristos 	const char			*keystr;
86e271dbb8Schristos 	size_t				 keylen;
87e271dbb8Schristos 
88c9ad075bSchristos 	uint64_t			 tag;
89c9ad075bSchristos 	const char			*name;
90c9ad075bSchristos 	const char			*text;
91c9ad075bSchristos 
92c9ad075bSchristos 	int				 expanded;
93c9ad075bSchristos 	int				 tagged;
94c9ad075bSchristos 
95e271dbb8Schristos 	int				 draw_as_parent;
96e271dbb8Schristos 	int				 no_tag;
97e271dbb8Schristos 
98c9ad075bSchristos 	struct mode_tree_list		 children;
99c9ad075bSchristos 	TAILQ_ENTRY(mode_tree_item)	 entry;
100c9ad075bSchristos };
101c9ad075bSchristos 
102c9ad075bSchristos struct mode_tree_line {
103c9ad075bSchristos 	struct mode_tree_item		*item;
104c9ad075bSchristos 	u_int				 depth;
105c9ad075bSchristos 	int				 last;
106c9ad075bSchristos 	int				 flat;
107c9ad075bSchristos };
108c9ad075bSchristos 
10930744affSchristos struct mode_tree_menu {
11030744affSchristos 	struct mode_tree_data		*data;
11130744affSchristos 	struct client			*c;
11230744affSchristos 	u_int				 line;
11330744affSchristos };
11430744affSchristos 
115c9ad075bSchristos static void mode_tree_free_items(struct mode_tree_list *);
116c9ad075bSchristos 
11730744affSchristos static const struct menu_item mode_tree_menu_items[] = {
11830744affSchristos 	{ "Scroll Left", '<', NULL },
11930744affSchristos 	{ "Scroll Right", '>', NULL },
12030744affSchristos 	{ "", KEYC_NONE, NULL },
12130744affSchristos 	{ "Cancel", 'q', NULL },
12230744affSchristos 
12330744affSchristos 	{ NULL, KEYC_NONE, NULL }
12430744affSchristos };
12530744affSchristos 
126c9ad075bSchristos static struct mode_tree_item *
127c9ad075bSchristos mode_tree_find_item(struct mode_tree_list *mtl, uint64_t tag)
128c9ad075bSchristos {
129c9ad075bSchristos 	struct mode_tree_item	*mti, *child;
130c9ad075bSchristos 
131c9ad075bSchristos 	TAILQ_FOREACH(mti, mtl, entry) {
132c9ad075bSchristos 		if (mti->tag == tag)
133c9ad075bSchristos 			return (mti);
134c9ad075bSchristos 		child = mode_tree_find_item(&mti->children, tag);
135c9ad075bSchristos 		if (child != NULL)
136c9ad075bSchristos 			return (child);
137c9ad075bSchristos 	}
138c9ad075bSchristos 	return (NULL);
139c9ad075bSchristos }
140c9ad075bSchristos 
141c9ad075bSchristos static void
142c9ad075bSchristos mode_tree_free_item(struct mode_tree_item *mti)
143c9ad075bSchristos {
144c9ad075bSchristos 	mode_tree_free_items(&mti->children);
145c9ad075bSchristos 
146fe99a117Schristos 	free(__UNCONST(mti->name));
147bcc4e02eSchristos 	free(__UNCONST(mti->text));
148e271dbb8Schristos 	free(__UNCONST(mti->keystr));
149c9ad075bSchristos 
150c9ad075bSchristos 	free(mti);
151c9ad075bSchristos }
152c9ad075bSchristos 
153c9ad075bSchristos static void
154c9ad075bSchristos mode_tree_free_items(struct mode_tree_list *mtl)
155c9ad075bSchristos {
156c9ad075bSchristos 	struct mode_tree_item	*mti, *mti1;
157c9ad075bSchristos 
158c9ad075bSchristos 	TAILQ_FOREACH_SAFE(mti, mtl, entry, mti1) {
159c9ad075bSchristos 		TAILQ_REMOVE(mtl, mti, entry);
160c9ad075bSchristos 		mode_tree_free_item(mti);
161c9ad075bSchristos 	}
162c9ad075bSchristos }
163c9ad075bSchristos 
164c9ad075bSchristos static void
165c9ad075bSchristos mode_tree_check_selected(struct mode_tree_data *mtd)
166c9ad075bSchristos {
167c9ad075bSchristos 	/*
168c9ad075bSchristos 	 * If the current line would now be off screen reset the offset to the
169c9ad075bSchristos 	 * last visible line.
170c9ad075bSchristos 	 */
171c9ad075bSchristos 	if (mtd->current > mtd->height - 1)
172c9ad075bSchristos 		mtd->offset = mtd->current - mtd->height + 1;
173c9ad075bSchristos }
174c9ad075bSchristos 
175c9ad075bSchristos static void
176c9ad075bSchristos mode_tree_clear_lines(struct mode_tree_data *mtd)
177c9ad075bSchristos {
178c9ad075bSchristos 	free(mtd->line_list);
179c9ad075bSchristos 	mtd->line_list = NULL;
180c9ad075bSchristos 	mtd->line_size = 0;
181c9ad075bSchristos }
182c9ad075bSchristos 
183c9ad075bSchristos static void
184c9ad075bSchristos mode_tree_build_lines(struct mode_tree_data *mtd,
185c9ad075bSchristos     struct mode_tree_list *mtl, u_int depth)
186c9ad075bSchristos {
187c9ad075bSchristos 	struct mode_tree_item	*mti;
188c9ad075bSchristos 	struct mode_tree_line	*line;
189c9ad075bSchristos 	u_int			 i;
190c9ad075bSchristos 	int			 flat = 1;
191c9ad075bSchristos 
192c9ad075bSchristos 	mtd->depth = depth;
193c9ad075bSchristos 	TAILQ_FOREACH(mti, mtl, entry) {
194c9ad075bSchristos 		mtd->line_list = xreallocarray(mtd->line_list,
195c9ad075bSchristos 		    mtd->line_size + 1, sizeof *mtd->line_list);
196c9ad075bSchristos 
197c9ad075bSchristos 		line = &mtd->line_list[mtd->line_size++];
198c9ad075bSchristos 		line->item = mti;
199c9ad075bSchristos 		line->depth = depth;
200c9ad075bSchristos 		line->last = (mti == TAILQ_LAST(mtl, mode_tree_list));
201c9ad075bSchristos 
202c9ad075bSchristos 		mti->line = (mtd->line_size - 1);
203c9ad075bSchristos 		if (!TAILQ_EMPTY(&mti->children))
204c9ad075bSchristos 			flat = 0;
205c9ad075bSchristos 		if (mti->expanded)
206c9ad075bSchristos 			mode_tree_build_lines(mtd, &mti->children, depth + 1);
207e271dbb8Schristos 
208e271dbb8Schristos 		if (mtd->keycb != NULL) {
209e271dbb8Schristos 			mti->key = mtd->keycb(mtd->modedata, mti->itemdata,
210e271dbb8Schristos 			    mti->line);
211e271dbb8Schristos 			if (mti->key == KEYC_UNKNOWN)
212e271dbb8Schristos 				mti->key = KEYC_NONE;
213e271dbb8Schristos 		} else if (mti->line < 10)
214e271dbb8Schristos 			mti->key = '0' + mti->line;
215e271dbb8Schristos 		else if (mti->line < 36)
216e271dbb8Schristos 			mti->key = KEYC_META|('a' + mti->line - 10);
217e271dbb8Schristos 		else
218e271dbb8Schristos 			mti->key = KEYC_NONE;
219e271dbb8Schristos 		if (mti->key != KEYC_NONE) {
220e271dbb8Schristos 			mti->keystr = xstrdup(key_string_lookup_key(mti->key,
221e271dbb8Schristos 			    0));
222e271dbb8Schristos 			mti->keylen = strlen(mti->keystr);
223e271dbb8Schristos 		} else {
224e271dbb8Schristos 			mti->keystr = NULL;
225e271dbb8Schristos 			mti->keylen = 0;
226e271dbb8Schristos 		}
227c9ad075bSchristos 	}
228c9ad075bSchristos 	TAILQ_FOREACH(mti, mtl, entry) {
229c9ad075bSchristos 		for (i = 0; i < mtd->line_size; i++) {
230c9ad075bSchristos 			line = &mtd->line_list[i];
231c9ad075bSchristos 			if (line->item == mti)
232c9ad075bSchristos 				line->flat = flat;
233c9ad075bSchristos 		}
234c9ad075bSchristos 	}
235c9ad075bSchristos }
236c9ad075bSchristos 
237c9ad075bSchristos static void
238c9ad075bSchristos mode_tree_clear_tagged(struct mode_tree_list *mtl)
239c9ad075bSchristos {
240c9ad075bSchristos 	struct mode_tree_item	*mti;
241c9ad075bSchristos 
242c9ad075bSchristos 	TAILQ_FOREACH(mti, mtl, entry) {
243c9ad075bSchristos 		mti->tagged = 0;
244c9ad075bSchristos 		mode_tree_clear_tagged(&mti->children);
245c9ad075bSchristos 	}
246c9ad075bSchristos }
247c9ad075bSchristos 
248e271dbb8Schristos void
249c9ad075bSchristos mode_tree_up(struct mode_tree_data *mtd, int wrap)
250c9ad075bSchristos {
251c9ad075bSchristos 	if (mtd->current == 0) {
252c9ad075bSchristos 		if (wrap) {
253c9ad075bSchristos 			mtd->current = mtd->line_size - 1;
254c9ad075bSchristos 			if (mtd->line_size >= mtd->height)
255c9ad075bSchristos 				mtd->offset = mtd->line_size - mtd->height;
256c9ad075bSchristos 		}
257c9ad075bSchristos 	} else {
258c9ad075bSchristos 		mtd->current--;
259c9ad075bSchristos 		if (mtd->current < mtd->offset)
260c9ad075bSchristos 			mtd->offset--;
261c9ad075bSchristos 	}
262c9ad075bSchristos }
263c9ad075bSchristos 
264*890b6d91Swiz int
265c9ad075bSchristos mode_tree_down(struct mode_tree_data *mtd, int wrap)
266c9ad075bSchristos {
267c9ad075bSchristos 	if (mtd->current == mtd->line_size - 1) {
268c9ad075bSchristos 		if (wrap) {
269c9ad075bSchristos 			mtd->current = 0;
270c9ad075bSchristos 			mtd->offset = 0;
271*890b6d91Swiz 		} else
272*890b6d91Swiz 			return (0);
273c9ad075bSchristos 	} else {
274c9ad075bSchristos 		mtd->current++;
275c9ad075bSchristos 		if (mtd->current > mtd->offset + mtd->height - 1)
276c9ad075bSchristos 			mtd->offset++;
277c9ad075bSchristos 	}
278*890b6d91Swiz 	return (1);
279c9ad075bSchristos }
280c9ad075bSchristos 
281c9ad075bSchristos void *
282c9ad075bSchristos mode_tree_get_current(struct mode_tree_data *mtd)
283c9ad075bSchristos {
284c9ad075bSchristos 	return (mtd->line_list[mtd->current].item->itemdata);
285c9ad075bSchristos }
286c9ad075bSchristos 
287e271dbb8Schristos const char *
288e271dbb8Schristos mode_tree_get_current_name(struct mode_tree_data *mtd)
289e271dbb8Schristos {
290e271dbb8Schristos 	return (mtd->line_list[mtd->current].item->name);
291e271dbb8Schristos }
292e271dbb8Schristos 
293c7e17de0Schristos void
294c7e17de0Schristos mode_tree_expand_current(struct mode_tree_data *mtd)
295c7e17de0Schristos {
296c7e17de0Schristos 	if (!mtd->line_list[mtd->current].item->expanded) {
297c7e17de0Schristos 		mtd->line_list[mtd->current].item->expanded = 1;
298c7e17de0Schristos 		mode_tree_build(mtd);
299c7e17de0Schristos 	}
300c7e17de0Schristos }
301c7e17de0Schristos 
302c7e17de0Schristos void
303e271dbb8Schristos mode_tree_collapse_current(struct mode_tree_data *mtd)
304e271dbb8Schristos {
305e271dbb8Schristos 	if (mtd->line_list[mtd->current].item->expanded) {
306e271dbb8Schristos 		mtd->line_list[mtd->current].item->expanded = 0;
307e271dbb8Schristos 		mode_tree_build(mtd);
308e271dbb8Schristos 	}
309e271dbb8Schristos }
310e271dbb8Schristos 
311e271dbb8Schristos static int
312e271dbb8Schristos mode_tree_get_tag(struct mode_tree_data *mtd, uint64_t tag, u_int *found)
313c7e17de0Schristos {
314c7e17de0Schristos 	u_int	i;
315c7e17de0Schristos 
316c7e17de0Schristos 	for (i = 0; i < mtd->line_size; i++) {
317c7e17de0Schristos 		if (mtd->line_list[i].item->tag == tag)
318c7e17de0Schristos 			break;
319c7e17de0Schristos 	}
320c7e17de0Schristos 	if (i != mtd->line_size) {
321e271dbb8Schristos 		*found = i;
322e271dbb8Schristos 		return (1);
323e271dbb8Schristos 	}
324e271dbb8Schristos 	return (0);
325e271dbb8Schristos }
326e271dbb8Schristos 
327e271dbb8Schristos void
328e271dbb8Schristos mode_tree_expand(struct mode_tree_data *mtd, uint64_t tag)
329e271dbb8Schristos {
330e271dbb8Schristos 	u_int	found;
331e271dbb8Schristos 
332e271dbb8Schristos 	if (!mode_tree_get_tag(mtd, tag, &found))
333e271dbb8Schristos 	    return;
334e271dbb8Schristos 	if (!mtd->line_list[found].item->expanded) {
335e271dbb8Schristos 		mtd->line_list[found].item->expanded = 1;
336e271dbb8Schristos 		mode_tree_build(mtd);
337e271dbb8Schristos 	}
338e271dbb8Schristos }
339e271dbb8Schristos 
340e271dbb8Schristos int
341e271dbb8Schristos mode_tree_set_current(struct mode_tree_data *mtd, uint64_t tag)
342e271dbb8Schristos {
343e271dbb8Schristos 	u_int	found;
344e271dbb8Schristos 
345e271dbb8Schristos 	if (mode_tree_get_tag(mtd, tag, &found)) {
346e271dbb8Schristos 		mtd->current = found;
347c7e17de0Schristos 		if (mtd->current > mtd->height - 1)
348c7e17de0Schristos 			mtd->offset = mtd->current - mtd->height + 1;
349c7e17de0Schristos 		else
350c7e17de0Schristos 			mtd->offset = 0;
351e271dbb8Schristos 		return (1);
352e271dbb8Schristos 	}
353c7e17de0Schristos 	mtd->current = 0;
354c7e17de0Schristos 	mtd->offset = 0;
355e271dbb8Schristos 	return (0);
356c7e17de0Schristos }
357c7e17de0Schristos 
358c9ad075bSchristos u_int
359c9ad075bSchristos mode_tree_count_tagged(struct mode_tree_data *mtd)
360c9ad075bSchristos {
361c9ad075bSchristos 	struct mode_tree_item	*mti;
362c9ad075bSchristos 	u_int			 i, tagged;
363c9ad075bSchristos 
364c9ad075bSchristos 	tagged = 0;
365c9ad075bSchristos 	for (i = 0; i < mtd->line_size; i++) {
366c9ad075bSchristos 		mti = mtd->line_list[i].item;
367c9ad075bSchristos 		if (mti->tagged)
368c9ad075bSchristos 			tagged++;
369c9ad075bSchristos 	}
370c9ad075bSchristos 	return (tagged);
371c9ad075bSchristos }
372c9ad075bSchristos 
373c9ad075bSchristos void
374c7e17de0Schristos mode_tree_each_tagged(struct mode_tree_data *mtd, mode_tree_each_cb cb,
375c7e17de0Schristos     struct client *c, key_code key, int current)
376c9ad075bSchristos {
377c9ad075bSchristos 	struct mode_tree_item	*mti;
378c9ad075bSchristos 	u_int			 i;
379c9ad075bSchristos 	int			 fired;
380c9ad075bSchristos 
381c9ad075bSchristos 	fired = 0;
382c9ad075bSchristos 	for (i = 0; i < mtd->line_size; i++) {
383c9ad075bSchristos 		mti = mtd->line_list[i].item;
384c9ad075bSchristos 		if (mti->tagged) {
385c9ad075bSchristos 			fired = 1;
386c7e17de0Schristos 			cb(mtd->modedata, mti->itemdata, c, key);
387c9ad075bSchristos 		}
388c9ad075bSchristos 	}
389c9ad075bSchristos 	if (!fired && current) {
390c9ad075bSchristos 		mti = mtd->line_list[mtd->current].item;
391c7e17de0Schristos 		cb(mtd->modedata, mti->itemdata, c, key);
392c9ad075bSchristos 	}
393c9ad075bSchristos }
394c9ad075bSchristos 
395c9ad075bSchristos struct mode_tree_data *
396c9ad075bSchristos mode_tree_start(struct window_pane *wp, struct args *args,
397c7e17de0Schristos     mode_tree_build_cb buildcb, mode_tree_draw_cb drawcb,
398e271dbb8Schristos     mode_tree_search_cb searchcb, mode_tree_menu_cb menucb,
399e271dbb8Schristos     mode_tree_height_cb heightcb, mode_tree_key_cb keycb, void *modedata,
40030744affSchristos     const struct menu_item *menu, const char **sort_list, u_int sort_size,
40130744affSchristos     struct screen **s)
402c9ad075bSchristos {
403c9ad075bSchristos 	struct mode_tree_data	*mtd;
404c9ad075bSchristos 	const char		*sort;
405c9ad075bSchristos 	u_int			 i;
406c9ad075bSchristos 
407c9ad075bSchristos 	mtd = xcalloc(1, sizeof *mtd);
408c9ad075bSchristos 	mtd->references = 1;
409c9ad075bSchristos 
410c9ad075bSchristos 	mtd->wp = wp;
411c9ad075bSchristos 	mtd->modedata = modedata;
41230744affSchristos 	mtd->menu = menu;
413c9ad075bSchristos 
414c9ad075bSchristos 	mtd->sort_list = sort_list;
415c9ad075bSchristos 	mtd->sort_size = sort_size;
416c9ad075bSchristos 
417c9ad075bSchristos 	mtd->preview = !args_has(args, 'N');
418c9ad075bSchristos 
419c9ad075bSchristos 	sort = args_get(args, 'O');
420c9ad075bSchristos 	if (sort != NULL) {
421c9ad075bSchristos 		for (i = 0; i < sort_size; i++) {
422c9ad075bSchristos 			if (strcasecmp(sort, sort_list[i]) == 0)
42368e6ba84Schristos 				mtd->sort_crit.field = i;
424c9ad075bSchristos 		}
425c9ad075bSchristos 	}
42668e6ba84Schristos 	mtd->sort_crit.reversed = args_has(args, 'r');
427c9ad075bSchristos 
428c9ad075bSchristos 	if (args_has(args, 'f'))
429c9ad075bSchristos 		mtd->filter = xstrdup(args_get(args, 'f'));
430c9ad075bSchristos 	else
431c9ad075bSchristos 		mtd->filter = NULL;
432c9ad075bSchristos 
433c9ad075bSchristos 	mtd->buildcb = buildcb;
434c9ad075bSchristos 	mtd->drawcb = drawcb;
435c9ad075bSchristos 	mtd->searchcb = searchcb;
43630744affSchristos 	mtd->menucb = menucb;
437e271dbb8Schristos 	mtd->heightcb = heightcb;
438e271dbb8Schristos 	mtd->keycb = keycb;
439c9ad075bSchristos 
440c9ad075bSchristos 	TAILQ_INIT(&mtd->children);
441c9ad075bSchristos 
442c9ad075bSchristos 	*s = &mtd->screen;
443c9ad075bSchristos 	screen_init(*s, screen_size_x(&wp->base), screen_size_y(&wp->base), 0);
444c9ad075bSchristos 	(*s)->mode &= ~MODE_CURSOR;
445c9ad075bSchristos 
446c9ad075bSchristos 	return (mtd);
447c9ad075bSchristos }
448c9ad075bSchristos 
449c9ad075bSchristos void
450c7e17de0Schristos mode_tree_zoom(struct mode_tree_data *mtd, struct args *args)
451c7e17de0Schristos {
452c7e17de0Schristos 	struct window_pane	*wp = mtd->wp;
453c7e17de0Schristos 
454c7e17de0Schristos 	if (args_has(args, 'Z')) {
455c7e17de0Schristos 		mtd->zoomed = (wp->window->flags & WINDOW_ZOOMED);
456c7e17de0Schristos 		if (!mtd->zoomed && window_zoom(wp) == 0)
457c7e17de0Schristos 			server_redraw_window(wp->window);
458c7e17de0Schristos 	} else
459c7e17de0Schristos 		mtd->zoomed = -1;
460c7e17de0Schristos }
461c7e17de0Schristos 
462e271dbb8Schristos static void
463e271dbb8Schristos mode_tree_set_height(struct mode_tree_data *mtd)
464e271dbb8Schristos {
465e271dbb8Schristos 	struct screen	*s = &mtd->screen;
466e271dbb8Schristos 	u_int		 height;
467e271dbb8Schristos 
468e271dbb8Schristos 	if (mtd->heightcb != NULL) {
469e271dbb8Schristos 		height = mtd->heightcb(mtd, screen_size_y(s));
470e271dbb8Schristos 		if (height < screen_size_y(s))
471e271dbb8Schristos 		    mtd->height = screen_size_y(s) - height;
472e271dbb8Schristos 	} else {
473e271dbb8Schristos 		mtd->height = (screen_size_y(s) / 3) * 2;
474e271dbb8Schristos 		if (mtd->height > mtd->line_size)
475e271dbb8Schristos 			mtd->height = screen_size_y(s) / 2;
476e271dbb8Schristos 	}
477e271dbb8Schristos 	if (mtd->height < 10)
478e271dbb8Schristos 		mtd->height = screen_size_y(s);
479e271dbb8Schristos 	if (screen_size_y(s) - mtd->height < 2)
480e271dbb8Schristos 		mtd->height = screen_size_y(s);
481e271dbb8Schristos }
482e271dbb8Schristos 
483c7e17de0Schristos void
484c9ad075bSchristos mode_tree_build(struct mode_tree_data *mtd)
485c9ad075bSchristos {
486c9ad075bSchristos 	struct screen	*s = &mtd->screen;
487c9ad075bSchristos 	uint64_t	 tag;
488c9ad075bSchristos 
489c9ad075bSchristos 	if (mtd->line_list != NULL)
490c9ad075bSchristos 		tag = mtd->line_list[mtd->current].item->tag;
491c9ad075bSchristos 	else
49230744affSchristos 		tag = UINT64_MAX;
493c9ad075bSchristos 
494c9ad075bSchristos 	TAILQ_CONCAT(&mtd->saved, &mtd->children, entry);
495c9ad075bSchristos 	TAILQ_INIT(&mtd->children);
496c9ad075bSchristos 
49768e6ba84Schristos 	mtd->buildcb(mtd->modedata, &mtd->sort_crit, &tag, mtd->filter);
498c7e17de0Schristos 	mtd->no_matches = TAILQ_EMPTY(&mtd->children);
499c7e17de0Schristos 	if (mtd->no_matches)
50068e6ba84Schristos 		mtd->buildcb(mtd->modedata, &mtd->sort_crit, &tag, NULL);
501c9ad075bSchristos 
502c9ad075bSchristos 	mode_tree_free_items(&mtd->saved);
503c9ad075bSchristos 	TAILQ_INIT(&mtd->saved);
504c9ad075bSchristos 
505c9ad075bSchristos 	mode_tree_clear_lines(mtd);
506c9ad075bSchristos 	mode_tree_build_lines(mtd, &mtd->children, 0);
507c9ad075bSchristos 
508f844e94eSwiz 	if (mtd->line_list != NULL && tag == UINT64_MAX)
50930744affSchristos 		tag = mtd->line_list[mtd->current].item->tag;
510c9ad075bSchristos 	mode_tree_set_current(mtd, tag);
511c9ad075bSchristos 
512c9ad075bSchristos 	mtd->width = screen_size_x(s);
513e271dbb8Schristos 	if (mtd->preview)
514e271dbb8Schristos 		mode_tree_set_height(mtd);
515e271dbb8Schristos 	else
516c9ad075bSchristos 		mtd->height = screen_size_y(s);
517c9ad075bSchristos 	mode_tree_check_selected(mtd);
518c9ad075bSchristos }
519c9ad075bSchristos 
520c9ad075bSchristos static void
521c9ad075bSchristos mode_tree_remove_ref(struct mode_tree_data *mtd)
522c9ad075bSchristos {
523c9ad075bSchristos 	if (--mtd->references == 0)
524c9ad075bSchristos 		free(mtd);
525c9ad075bSchristos }
526c9ad075bSchristos 
527c9ad075bSchristos void
528c9ad075bSchristos mode_tree_free(struct mode_tree_data *mtd)
529c9ad075bSchristos {
530c7e17de0Schristos 	struct window_pane	*wp = mtd->wp;
531c7e17de0Schristos 
532c7e17de0Schristos 	if (mtd->zoomed == 0)
533c7e17de0Schristos 		server_unzoom_window(wp->window);
534c7e17de0Schristos 
535c9ad075bSchristos 	mode_tree_free_items(&mtd->children);
536c9ad075bSchristos 	mode_tree_clear_lines(mtd);
537c9ad075bSchristos 	screen_free(&mtd->screen);
538c9ad075bSchristos 
539c9ad075bSchristos 	free(mtd->search);
540c9ad075bSchristos 	free(mtd->filter);
541c9ad075bSchristos 
542c9ad075bSchristos 	mtd->dead = 1;
543c9ad075bSchristos 	mode_tree_remove_ref(mtd);
544c9ad075bSchristos }
545c9ad075bSchristos 
546c9ad075bSchristos void
547c9ad075bSchristos mode_tree_resize(struct mode_tree_data *mtd, u_int sx, u_int sy)
548c9ad075bSchristos {
549c9ad075bSchristos 	struct screen	*s = &mtd->screen;
550c9ad075bSchristos 
551c9ad075bSchristos 	screen_resize(s, sx, sy, 0);
552c9ad075bSchristos 
553c9ad075bSchristos 	mode_tree_build(mtd);
554c9ad075bSchristos 	mode_tree_draw(mtd);
555c9ad075bSchristos 
556c9ad075bSchristos 	mtd->wp->flags |= PANE_REDRAW;
557c9ad075bSchristos }
558c9ad075bSchristos 
559c9ad075bSchristos struct mode_tree_item *
560c9ad075bSchristos mode_tree_add(struct mode_tree_data *mtd, struct mode_tree_item *parent,
561c9ad075bSchristos     void *itemdata, uint64_t tag, const char *name, const char *text,
562c9ad075bSchristos     int expanded)
563c9ad075bSchristos {
564c9ad075bSchristos 	struct mode_tree_item	*mti, *saved;
565c9ad075bSchristos 
566c9ad075bSchristos 	log_debug("%s: %llu, %s %s", __func__, (unsigned long long)tag,
567e271dbb8Schristos 	    name, (text == NULL ? "" : text));
568c9ad075bSchristos 
569c9ad075bSchristos 	mti = xcalloc(1, sizeof *mti);
570c9ad075bSchristos 	mti->parent = parent;
571c9ad075bSchristos 	mti->itemdata = itemdata;
572c9ad075bSchristos 
573c9ad075bSchristos 	mti->tag = tag;
574c9ad075bSchristos 	mti->name = xstrdup(name);
575e271dbb8Schristos 	if (text != NULL)
576c9ad075bSchristos 		mti->text = xstrdup(text);
577c9ad075bSchristos 
578c9ad075bSchristos 	saved = mode_tree_find_item(&mtd->saved, tag);
579c9ad075bSchristos 	if (saved != NULL) {
58030744affSchristos 		if (parent == NULL || parent->expanded)
581c9ad075bSchristos 			mti->tagged = saved->tagged;
582c9ad075bSchristos 		mti->expanded = saved->expanded;
583c9ad075bSchristos 	} else if (expanded == -1)
584c9ad075bSchristos 		mti->expanded = 1;
585c9ad075bSchristos 	else
586c9ad075bSchristos 		mti->expanded = expanded;
587c9ad075bSchristos 
588c9ad075bSchristos 	TAILQ_INIT(&mti->children);
589c9ad075bSchristos 
590c9ad075bSchristos 	if (parent != NULL)
591c9ad075bSchristos 		TAILQ_INSERT_TAIL(&parent->children, mti, entry);
592c9ad075bSchristos 	else
593c9ad075bSchristos 		TAILQ_INSERT_TAIL(&mtd->children, mti, entry);
594c9ad075bSchristos 
595c9ad075bSchristos 	return (mti);
596c9ad075bSchristos }
597c9ad075bSchristos 
598c9ad075bSchristos void
599e271dbb8Schristos mode_tree_draw_as_parent(struct mode_tree_item *mti)
600e271dbb8Schristos {
601e271dbb8Schristos 	mti->draw_as_parent = 1;
602e271dbb8Schristos }
603e271dbb8Schristos 
604e271dbb8Schristos void
605e271dbb8Schristos mode_tree_no_tag(struct mode_tree_item *mti)
606e271dbb8Schristos {
607e271dbb8Schristos 	mti->no_tag = 1;
608e271dbb8Schristos }
609e271dbb8Schristos 
610e271dbb8Schristos void
611c9ad075bSchristos mode_tree_remove(struct mode_tree_data *mtd, struct mode_tree_item *mti)
612c9ad075bSchristos {
613c9ad075bSchristos 	struct mode_tree_item	*parent = mti->parent;
614c9ad075bSchristos 
615c9ad075bSchristos 	if (parent != NULL)
616c9ad075bSchristos 		TAILQ_REMOVE(&parent->children, mti, entry);
617c9ad075bSchristos 	else
618c9ad075bSchristos 		TAILQ_REMOVE(&mtd->children, mti, entry);
619c9ad075bSchristos 	mode_tree_free_item(mti);
620c9ad075bSchristos }
621c9ad075bSchristos 
622c9ad075bSchristos void
623c9ad075bSchristos mode_tree_draw(struct mode_tree_data *mtd)
624c9ad075bSchristos {
625c9ad075bSchristos 	struct window_pane	*wp = mtd->wp;
626c7e17de0Schristos 	struct screen		*s = &mtd->screen;
627c9ad075bSchristos 	struct mode_tree_line	*line;
628c9ad075bSchristos 	struct mode_tree_item	*mti;
629c9ad075bSchristos 	struct options		*oo = wp->window->options;
630c9ad075bSchristos 	struct screen_write_ctx	 ctx;
631c9ad075bSchristos 	struct grid_cell	 gc0, gc;
6320a274e86Schristos 	u_int			 w, h, i, j, sy, box_x, box_y, width;
633e271dbb8Schristos 	char			*text, *start, *key;
634c9ad075bSchristos 	const char		*tag, *symbol;
635c7e17de0Schristos 	size_t			 size, n;
636e271dbb8Schristos 	int			 keylen, pad;
637c9ad075bSchristos 
638c9ad075bSchristos 	if (mtd->line_size == 0)
639c9ad075bSchristos 		return;
640c9ad075bSchristos 
641c9ad075bSchristos 	memcpy(&gc0, &grid_default_cell, sizeof gc0);
642c9ad075bSchristos 	memcpy(&gc, &grid_default_cell, sizeof gc);
643e271dbb8Schristos 	style_apply(&gc, oo, "mode-style", NULL);
644c9ad075bSchristos 
645c9ad075bSchristos 	w = mtd->width;
646c9ad075bSchristos 	h = mtd->height;
647c9ad075bSchristos 
648e271dbb8Schristos 	screen_write_start(&ctx, s);
649c9ad075bSchristos 	screen_write_clearscreen(&ctx, 8);
650c9ad075bSchristos 
651e271dbb8Schristos 	keylen = 0;
652e271dbb8Schristos 	for (i = 0; i < mtd->line_size; i++) {
653e271dbb8Schristos 		mti = mtd->line_list[i].item;
654e271dbb8Schristos 		if (mti->key == KEYC_NONE)
655e271dbb8Schristos 			continue;
656e271dbb8Schristos 		if ((int)mti->keylen + 3 > keylen)
657e271dbb8Schristos 			keylen = mti->keylen + 3;
658e271dbb8Schristos 	}
659c9ad075bSchristos 
660c9ad075bSchristos 	for (i = 0; i < mtd->line_size; i++) {
661c9ad075bSchristos 		if (i < mtd->offset)
662c9ad075bSchristos 			continue;
663c9ad075bSchristos 		if (i > mtd->offset + h - 1)
664c9ad075bSchristos 			break;
665c9ad075bSchristos 		line = &mtd->line_list[i];
666c9ad075bSchristos 		mti = line->item;
667c9ad075bSchristos 
6680a274e86Schristos 		screen_write_cursormove(&ctx, 0, i - mtd->offset, 0);
669c9ad075bSchristos 
670e271dbb8Schristos 		pad = keylen - 2 - mti->keylen;
671e271dbb8Schristos 		if (mti->key != KEYC_NONE)
672e271dbb8Schristos 			xasprintf(&key, "(%s)%*s", mti->keystr, pad, "");
673c9ad075bSchristos 		else
674e271dbb8Schristos 			key = xstrdup("");
675c9ad075bSchristos 
676c9ad075bSchristos 		if (line->flat)
677c9ad075bSchristos 			symbol = "";
678c9ad075bSchristos 		else if (TAILQ_EMPTY(&mti->children))
679c9ad075bSchristos 			symbol = "  ";
680c9ad075bSchristos 		else if (mti->expanded)
681c9ad075bSchristos 			symbol = "- ";
682c9ad075bSchristos 		else
683c9ad075bSchristos 			symbol = "+ ";
684c9ad075bSchristos 
685c9ad075bSchristos 		if (line->depth == 0)
686c9ad075bSchristos 			start = xstrdup(symbol);
687c9ad075bSchristos 		else {
688c9ad075bSchristos 			size = (4 * line->depth) + 32;
689c9ad075bSchristos 
690c9ad075bSchristos 			start = xcalloc(1, size);
691c9ad075bSchristos 			for (j = 1; j < line->depth; j++) {
692c9ad075bSchristos 				if (mti->parent != NULL &&
693c9ad075bSchristos 				    mtd->line_list[mti->parent->line].last)
694c9ad075bSchristos 					strlcat(start, "    ", size);
695c9ad075bSchristos 				else
696c9ad075bSchristos 					strlcat(start, "\001x\001   ", size);
697c9ad075bSchristos 			}
698c9ad075bSchristos 			if (line->last)
699c9ad075bSchristos 				strlcat(start, "\001mq\001> ", size);
700c9ad075bSchristos 			else
701c9ad075bSchristos 				strlcat(start, "\001tq\001> ", size);
702c9ad075bSchristos 			strlcat(start, symbol, size);
703c9ad075bSchristos 		}
704c9ad075bSchristos 
705c9ad075bSchristos 		if (mti->tagged)
706c9ad075bSchristos 			tag = "*";
707c9ad075bSchristos 		else
708c9ad075bSchristos 			tag = "";
709e271dbb8Schristos 		xasprintf(&text, "%-*s%s%s%s%s", keylen, key, start, mti->name,
710e271dbb8Schristos 		    tag, (mti->text != NULL) ? ": " : "" );
7110a274e86Schristos 		width = utf8_cstrwidth(text);
71268e6ba84Schristos 		if (width > w)
71368e6ba84Schristos 			width = w;
714c9ad075bSchristos 		free(start);
715c9ad075bSchristos 
716c9ad075bSchristos 		if (mti->tagged) {
717c9ad075bSchristos 			gc.attr ^= GRID_ATTR_BRIGHT;
718c9ad075bSchristos 			gc0.attr ^= GRID_ATTR_BRIGHT;
719c9ad075bSchristos 		}
720c9ad075bSchristos 
721c9ad075bSchristos 		if (i != mtd->current) {
722c9ad075bSchristos 			screen_write_clearendofline(&ctx, 8);
72368e6ba84Schristos 			screen_write_nputs(&ctx, w, &gc0, "%s", text);
724e271dbb8Schristos 			if (mti->text != NULL) {
725e271dbb8Schristos 				format_draw(&ctx, &gc0, w - width, mti->text,
72646548964Swiz 				    NULL, 0);
727e271dbb8Schristos 			}
728c7e17de0Schristos 		} else {
729c7e17de0Schristos 			screen_write_clearendofline(&ctx, gc.bg);
73068e6ba84Schristos 			screen_write_nputs(&ctx, w, &gc, "%s", text);
731e271dbb8Schristos 			if (mti->text != NULL) {
732e271dbb8Schristos 				format_draw(&ctx, &gc, w - width, mti->text,
73346548964Swiz 				    NULL, 0);
734e271dbb8Schristos 			}
735c7e17de0Schristos 		}
736c9ad075bSchristos 		free(text);
737e271dbb8Schristos 		free(key);
738c9ad075bSchristos 
739c9ad075bSchristos 		if (mti->tagged) {
740c9ad075bSchristos 			gc.attr ^= GRID_ATTR_BRIGHT;
741c9ad075bSchristos 			gc0.attr ^= GRID_ATTR_BRIGHT;
742c9ad075bSchristos 		}
743c9ad075bSchristos 	}
744c9ad075bSchristos 
745c9ad075bSchristos 	sy = screen_size_y(s);
74646548964Swiz 	if (!mtd->preview || sy <= 4 || h <= 4 || sy - h <= 4 || w <= 4)
74746548964Swiz 		goto done;
748c9ad075bSchristos 
749c9ad075bSchristos 	line = &mtd->line_list[mtd->current];
750c9ad075bSchristos 	mti = line->item;
751e271dbb8Schristos 	if (mti->draw_as_parent)
752e271dbb8Schristos 		mti = mti->parent;
753c9ad075bSchristos 
7540a274e86Schristos 	screen_write_cursormove(&ctx, 0, h, 0);
75546548964Swiz 	screen_write_box(&ctx, w, sy - h, BOX_LINES_DEFAULT, NULL, NULL);
756c9ad075bSchristos 
757e271dbb8Schristos 	if (mtd->sort_list != NULL) {
75868e6ba84Schristos 		xasprintf(&text, " %s (sort: %s%s)", mti->name,
75968e6ba84Schristos 		    mtd->sort_list[mtd->sort_crit.field],
76068e6ba84Schristos 		    mtd->sort_crit.reversed ? ", reversed" : "");
761e271dbb8Schristos 	} else
762e271dbb8Schristos 		xasprintf(&text, " %s", mti->name);
763c9ad075bSchristos 	if (w - 2 >= strlen(text)) {
7640a274e86Schristos 		screen_write_cursormove(&ctx, 1, h, 0);
765c9ad075bSchristos 		screen_write_puts(&ctx, &gc0, "%s", text);
766c7e17de0Schristos 
767c7e17de0Schristos 		if (mtd->no_matches)
768c7e17de0Schristos 			n = (sizeof "no matches") - 1;
769c7e17de0Schristos 		else
770c7e17de0Schristos 			n = (sizeof "active") - 1;
771c7e17de0Schristos 		if (mtd->filter != NULL && w - 2 >= strlen(text) + 10 + n + 2) {
772c7e17de0Schristos 			screen_write_puts(&ctx, &gc0, " (filter: ");
773c7e17de0Schristos 			if (mtd->no_matches)
774c7e17de0Schristos 				screen_write_puts(&ctx, &gc, "no matches");
775c7e17de0Schristos 			else
776c7e17de0Schristos 				screen_write_puts(&ctx, &gc0, "active");
777c7e17de0Schristos 			screen_write_puts(&ctx, &gc0, ") ");
778e271dbb8Schristos 		} else
779e271dbb8Schristos 			screen_write_puts(&ctx, &gc0, " ");
780c9ad075bSchristos 	}
781c9ad075bSchristos 	free(text);
782c9ad075bSchristos 
783c9ad075bSchristos 	box_x = w - 4;
784c9ad075bSchristos 	box_y = sy - h - 2;
785c9ad075bSchristos 
786c7e17de0Schristos 	if (box_x != 0 && box_y != 0) {
7870a274e86Schristos 		screen_write_cursormove(&ctx, 2, h + 1, 0);
788c7e17de0Schristos 		mtd->drawcb(mtd->modedata, mti->itemdata, &ctx, box_x, box_y);
789c9ad075bSchristos 	}
790c9ad075bSchristos 
79146548964Swiz done:
79246548964Swiz 	screen_write_cursormove(&ctx, 0, mtd->current - mtd->offset, 0);
793c9ad075bSchristos 	screen_write_stop(&ctx);
794c9ad075bSchristos }
795c9ad075bSchristos 
796c9ad075bSchristos static struct mode_tree_item *
797*890b6d91Swiz mode_tree_search_backward(struct mode_tree_data *mtd)
798*890b6d91Swiz {
799*890b6d91Swiz     struct mode_tree_item	*mti, *last, *prev;
800*890b6d91Swiz 
801*890b6d91Swiz     if (mtd->search == NULL)
802*890b6d91Swiz 	    return (NULL);
803*890b6d91Swiz 
804*890b6d91Swiz     mti = last = mtd->line_list[mtd->current].item;
805*890b6d91Swiz     for (;;) {
806*890b6d91Swiz         if ((prev = TAILQ_PREV(mti, mode_tree_list, entry)) != NULL) {
807*890b6d91Swiz 		/* Point to the last child in the previous subtree. */
808*890b6d91Swiz 		while (!TAILQ_EMPTY(&prev->children))
809*890b6d91Swiz 			prev = TAILQ_LAST(&prev->children, mode_tree_list);
810*890b6d91Swiz 		mti = prev;
811*890b6d91Swiz         } else {
812*890b6d91Swiz 		/* If prev is NULL, jump to the parent. */
813*890b6d91Swiz 		mti = mti->parent;
814*890b6d91Swiz         }
815*890b6d91Swiz 
816*890b6d91Swiz 	if (mti == NULL) {
817*890b6d91Swiz 		/* Point to the last child in the last root subtree. */
818*890b6d91Swiz 		prev = TAILQ_LAST(&mtd->children, mode_tree_list);
819*890b6d91Swiz 		while (!TAILQ_EMPTY(&prev->children))
820*890b6d91Swiz 			prev = TAILQ_LAST(&prev->children, mode_tree_list);
821*890b6d91Swiz 		mti = prev;
822*890b6d91Swiz 	}
823*890b6d91Swiz 	if (mti == last)
824*890b6d91Swiz 		break;
825*890b6d91Swiz 
826*890b6d91Swiz 	if (mtd->searchcb == NULL) {
827*890b6d91Swiz 		if (strstr(mti->name, mtd->search) != NULL)
828*890b6d91Swiz 			return (mti);
829*890b6d91Swiz 		continue;
830*890b6d91Swiz 	}
831*890b6d91Swiz 	if (mtd->searchcb(mtd->modedata, mti->itemdata, mtd->search))
832*890b6d91Swiz 		return (mti);
833*890b6d91Swiz     }
834*890b6d91Swiz     return (NULL);
835*890b6d91Swiz }
836*890b6d91Swiz 
837*890b6d91Swiz 
838*890b6d91Swiz static struct mode_tree_item *
839*890b6d91Swiz mode_tree_search_forward(struct mode_tree_data *mtd)
840c9ad075bSchristos {
841c9ad075bSchristos 	struct mode_tree_item	*mti, *last, *next;
842c9ad075bSchristos 
843c9ad075bSchristos 	if (mtd->search == NULL)
844c9ad075bSchristos 		return (NULL);
845c9ad075bSchristos 
846c9ad075bSchristos 	mti = last = mtd->line_list[mtd->current].item;
847c9ad075bSchristos 	for (;;) {
848c9ad075bSchristos 		if (!TAILQ_EMPTY(&mti->children))
849c9ad075bSchristos 			mti = TAILQ_FIRST(&mti->children);
850c9ad075bSchristos 		else if ((next = TAILQ_NEXT(mti, entry)) != NULL)
851c9ad075bSchristos 			mti = next;
852c9ad075bSchristos 		else {
853c9ad075bSchristos 			for (;;) {
854c9ad075bSchristos 				mti = mti->parent;
855c9ad075bSchristos 				if (mti == NULL)
856c9ad075bSchristos 					break;
857c9ad075bSchristos 				if ((next = TAILQ_NEXT(mti, entry)) != NULL) {
858c9ad075bSchristos 					mti = next;
859c9ad075bSchristos 					break;
860c9ad075bSchristos 				}
861c9ad075bSchristos 			}
862c9ad075bSchristos 		}
863c9ad075bSchristos 		if (mti == NULL)
864c9ad075bSchristos 			mti = TAILQ_FIRST(&mtd->children);
865c9ad075bSchristos 		if (mti == last)
866c9ad075bSchristos 			break;
867c9ad075bSchristos 
868c9ad075bSchristos 		if (mtd->searchcb == NULL) {
869c9ad075bSchristos 			if (strstr(mti->name, mtd->search) != NULL)
870c9ad075bSchristos 				return (mti);
871c9ad075bSchristos 			continue;
872c9ad075bSchristos 		}
873c9ad075bSchristos 		if (mtd->searchcb(mtd->modedata, mti->itemdata, mtd->search))
874c9ad075bSchristos 			return (mti);
875c9ad075bSchristos 	}
876c9ad075bSchristos 	return (NULL);
877c9ad075bSchristos }
878c9ad075bSchristos 
879c9ad075bSchristos static void
880c9ad075bSchristos mode_tree_search_set(struct mode_tree_data *mtd)
881c9ad075bSchristos {
882c9ad075bSchristos 	struct mode_tree_item	*mti, *loop;
883c9ad075bSchristos 	uint64_t		 tag;
884c9ad075bSchristos 
885*890b6d91Swiz 	if (mtd->search_dir == MODE_TREE_SEARCH_FORWARD)
886*890b6d91Swiz 		mti = mode_tree_search_forward(mtd);
887*890b6d91Swiz 	else
888*890b6d91Swiz 		mti = mode_tree_search_backward(mtd);
889c9ad075bSchristos 	if (mti == NULL)
890c9ad075bSchristos 		return;
891c9ad075bSchristos 	tag = mti->tag;
892c9ad075bSchristos 
893c9ad075bSchristos 	loop = mti->parent;
894c9ad075bSchristos 	while (loop != NULL) {
895c9ad075bSchristos 		loop->expanded = 1;
896c9ad075bSchristos 		loop = loop->parent;
897c9ad075bSchristos 	}
898c9ad075bSchristos 
899c9ad075bSchristos 	mode_tree_build(mtd);
900c9ad075bSchristos 	mode_tree_set_current(mtd, tag);
901c9ad075bSchristos 	mode_tree_draw(mtd);
902c9ad075bSchristos 	mtd->wp->flags |= PANE_REDRAW;
903c9ad075bSchristos }
904c9ad075bSchristos 
905c9ad075bSchristos static int
906c9ad075bSchristos mode_tree_search_callback(__unused struct client *c, void *data, const char *s,
907c9ad075bSchristos     __unused int done)
908c9ad075bSchristos {
909c9ad075bSchristos 	struct mode_tree_data	*mtd = data;
910c9ad075bSchristos 
911c9ad075bSchristos 	if (mtd->dead)
912c9ad075bSchristos 		return (0);
913c9ad075bSchristos 
914c9ad075bSchristos 	free(mtd->search);
915c9ad075bSchristos 	if (s == NULL || *s == '\0') {
916c9ad075bSchristos 		mtd->search = NULL;
917c9ad075bSchristos 		return (0);
918c9ad075bSchristos 	}
919c9ad075bSchristos 	mtd->search = xstrdup(s);
920c9ad075bSchristos 	mode_tree_search_set(mtd);
921c9ad075bSchristos 
922c9ad075bSchristos 	return (0);
923c9ad075bSchristos }
924c9ad075bSchristos 
925c9ad075bSchristos static void
926c9ad075bSchristos mode_tree_search_free(void *data)
927c9ad075bSchristos {
928c9ad075bSchristos 	mode_tree_remove_ref(data);
929c9ad075bSchristos }
930c9ad075bSchristos 
931c9ad075bSchristos static int
932c9ad075bSchristos mode_tree_filter_callback(__unused struct client *c, void *data, const char *s,
933c9ad075bSchristos     __unused int done)
934c9ad075bSchristos {
935c9ad075bSchristos 	struct mode_tree_data	*mtd = data;
936c9ad075bSchristos 
937c9ad075bSchristos 	if (mtd->dead)
938c9ad075bSchristos 		return (0);
939c9ad075bSchristos 
940c9ad075bSchristos 	if (mtd->filter != NULL)
941c9ad075bSchristos 		free(mtd->filter);
942c9ad075bSchristos 	if (s == NULL || *s == '\0')
943c9ad075bSchristos 		mtd->filter = NULL;
944c9ad075bSchristos 	else
945c9ad075bSchristos 		mtd->filter = xstrdup(s);
946c9ad075bSchristos 
947c9ad075bSchristos 	mode_tree_build(mtd);
948c9ad075bSchristos 	mode_tree_draw(mtd);
949c9ad075bSchristos 	mtd->wp->flags |= PANE_REDRAW;
950c9ad075bSchristos 
951c9ad075bSchristos 	return (0);
952c9ad075bSchristos }
953c9ad075bSchristos 
954c9ad075bSchristos static void
955c9ad075bSchristos mode_tree_filter_free(void *data)
956c9ad075bSchristos {
957c9ad075bSchristos 	mode_tree_remove_ref(data);
958c9ad075bSchristos }
959c9ad075bSchristos 
96030744affSchristos static void
96130744affSchristos mode_tree_menu_callback(__unused struct menu *menu, __unused u_int idx,
96230744affSchristos     key_code key, void *data)
96330744affSchristos {
96430744affSchristos 	struct mode_tree_menu	*mtm = data;
96530744affSchristos 	struct mode_tree_data	*mtd = mtm->data;
96630744affSchristos 
96730744affSchristos 	if (mtd->dead || key == KEYC_NONE)
96830744affSchristos 		goto out;
96930744affSchristos 
97030744affSchristos 	if (mtm->line >= mtd->line_size)
97130744affSchristos 		goto out;
97230744affSchristos 	mtd->current = mtm->line;
97330744affSchristos 	mtd->menucb(mtd->modedata, mtm->c, key);
97430744affSchristos 
97530744affSchristos out:
97630744affSchristos 	mode_tree_remove_ref(mtd);
97730744affSchristos 	free(mtm);
97830744affSchristos }
97930744affSchristos 
98030744affSchristos static void
98130744affSchristos mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x,
98230744affSchristos     u_int y, int outside)
98330744affSchristos {
98430744affSchristos 	struct mode_tree_item	*mti;
98530744affSchristos 	struct menu		*menu;
98630744affSchristos 	const struct menu_item	*items;
98730744affSchristos 	struct mode_tree_menu	*mtm;
98830744affSchristos 	char			*title;
98930744affSchristos 	u_int			 line;
99030744affSchristos 
99130744affSchristos 	if (mtd->offset + y > mtd->line_size - 1)
99230744affSchristos 		line = mtd->current;
99330744affSchristos 	else
99430744affSchristos 		line = mtd->offset + y;
99530744affSchristos 	mti = mtd->line_list[line].item;
99630744affSchristos 
99730744affSchristos 	if (!outside) {
99830744affSchristos 		items = mtd->menu;
99930744affSchristos 		xasprintf(&title, "#[align=centre]%s", mti->name);
100030744affSchristos 	} else {
100130744affSchristos 		items = mode_tree_menu_items;
100230744affSchristos 		title = xstrdup("");
100330744affSchristos 	}
100430744affSchristos 	menu = menu_create(title);
100546548964Swiz 	menu_add_items(menu, items, NULL, c, NULL);
100630744affSchristos 	free(title);
100730744affSchristos 
100830744affSchristos 	mtm = xmalloc(sizeof *mtm);
100930744affSchristos 	mtm->data = mtd;
101030744affSchristos 	mtm->c = c;
101130744affSchristos 	mtm->line = line;
101230744affSchristos 	mtd->references++;
101330744affSchristos 
1014e271dbb8Schristos 	if (x >= (menu->width + 4) / 2)
1015e271dbb8Schristos 		x -= (menu->width + 4) / 2;
1016e271dbb8Schristos 	else
1017e271dbb8Schristos 		x = 0;
1018f844e94eSwiz 	if (menu_display(menu, 0, 0, NULL, x, y, c, BOX_LINES_DEFAULT, NULL,
1019f844e94eSwiz 	    NULL, NULL, NULL, mode_tree_menu_callback, mtm) != 0)
102030744affSchristos 		menu_free(menu);
102130744affSchristos }
102230744affSchristos 
1023c9ad075bSchristos int
1024c9ad075bSchristos mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
1025c7e17de0Schristos     struct mouse_event *m, u_int *xp, u_int *yp)
1026c9ad075bSchristos {
1027c9ad075bSchristos 	struct mode_tree_line	*line;
1028e271dbb8Schristos 	struct mode_tree_item	*current, *parent, *mti;
1029c9ad075bSchristos 	u_int			 i, x, y;
1030c9ad075bSchristos 	int			 choice;
1031c9ad075bSchristos 
103230744affSchristos 	if (KEYC_IS_MOUSE(*key) && m != NULL) {
1033c9ad075bSchristos 		if (cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) {
1034c9ad075bSchristos 			*key = KEYC_NONE;
1035c9ad075bSchristos 			return (0);
1036c9ad075bSchristos 		}
1037c7e17de0Schristos 		if (xp != NULL)
1038c7e17de0Schristos 			*xp = x;
1039c7e17de0Schristos 		if (yp != NULL)
1040c7e17de0Schristos 			*yp = y;
1041c9ad075bSchristos 		if (x > mtd->width || y > mtd->height) {
104230744affSchristos 			if (*key == KEYC_MOUSEDOWN3_PANE)
104330744affSchristos 				mode_tree_display_menu(mtd, c, x, y, 1);
1044c7e17de0Schristos 			if (!mtd->preview)
1045c9ad075bSchristos 				*key = KEYC_NONE;
1046c9ad075bSchristos 			return (0);
1047c9ad075bSchristos 		}
1048c9ad075bSchristos 		if (mtd->offset + y < mtd->line_size) {
1049c7e17de0Schristos 			if (*key == KEYC_MOUSEDOWN1_PANE ||
105030744affSchristos 			    *key == KEYC_MOUSEDOWN3_PANE ||
1051c7e17de0Schristos 			    *key == KEYC_DOUBLECLICK1_PANE)
1052c9ad075bSchristos 				mtd->current = mtd->offset + y;
1053c7e17de0Schristos 			if (*key == KEYC_DOUBLECLICK1_PANE)
1054c9ad075bSchristos 				*key = '\r';
105530744affSchristos 			else {
105630744affSchristos 				if (*key == KEYC_MOUSEDOWN3_PANE)
105730744affSchristos 					mode_tree_display_menu(mtd, c, x, y, 0);
1058c7e17de0Schristos 				*key = KEYC_NONE;
105930744affSchristos 			}
106030744affSchristos 		} else {
106130744affSchristos 			if (*key == KEYC_MOUSEDOWN3_PANE)
106230744affSchristos 				mode_tree_display_menu(mtd, c, x, y, 0);
1063c7e17de0Schristos 			*key = KEYC_NONE;
106430744affSchristos 		}
1065c9ad075bSchristos 		return (0);
1066c9ad075bSchristos 	}
1067c9ad075bSchristos 
1068c9ad075bSchristos 	line = &mtd->line_list[mtd->current];
1069c9ad075bSchristos 	current = line->item;
1070c9ad075bSchristos 
1071c9ad075bSchristos 	choice = -1;
1072e271dbb8Schristos 	for (i = 0; i < mtd->line_size; i++) {
1073e271dbb8Schristos 		if (*key == mtd->line_list[i].item->key) {
1074e271dbb8Schristos 			choice = i;
1075e271dbb8Schristos 			break;
1076e271dbb8Schristos 		}
1077c9ad075bSchristos 	}
1078c9ad075bSchristos 	if (choice != -1) {
1079c9ad075bSchristos 		if ((u_int)choice > mtd->line_size - 1) {
1080c9ad075bSchristos 			*key = KEYC_NONE;
1081c9ad075bSchristos 			return (0);
1082c9ad075bSchristos 		}
1083c9ad075bSchristos 		mtd->current = choice;
1084c9ad075bSchristos 		*key = '\r';
1085c9ad075bSchristos 		return (0);
1086c9ad075bSchristos 	}
1087c9ad075bSchristos 
1088c9ad075bSchristos 	switch (*key) {
1089c9ad075bSchristos 	case 'q':
1090c9ad075bSchristos 	case '\033': /* Escape */
1091*890b6d91Swiz 	case 'g'|KEYC_CTRL:
1092c9ad075bSchristos 		return (1);
1093c9ad075bSchristos 	case KEYC_UP:
1094c9ad075bSchristos 	case 'k':
1095c9ad075bSchristos 	case KEYC_WHEELUP_PANE:
1096*890b6d91Swiz 	case 'p'|KEYC_CTRL:
1097c9ad075bSchristos 		mode_tree_up(mtd, 1);
1098c9ad075bSchristos 		break;
1099c9ad075bSchristos 	case KEYC_DOWN:
1100c9ad075bSchristos 	case 'j':
1101c9ad075bSchristos 	case KEYC_WHEELDOWN_PANE:
1102*890b6d91Swiz 	case 'n'|KEYC_CTRL:
1103c9ad075bSchristos 		mode_tree_down(mtd, 1);
1104c9ad075bSchristos 		break;
1105c9ad075bSchristos 	case KEYC_PPAGE:
1106*890b6d91Swiz 	case 'b'|KEYC_CTRL:
1107c9ad075bSchristos 		for (i = 0; i < mtd->height; i++) {
1108c9ad075bSchristos 			if (mtd->current == 0)
1109c9ad075bSchristos 				break;
1110c9ad075bSchristos 			mode_tree_up(mtd, 1);
1111c9ad075bSchristos 		}
1112c9ad075bSchristos 		break;
1113c9ad075bSchristos 	case KEYC_NPAGE:
1114*890b6d91Swiz 	case 'f'|KEYC_CTRL:
1115c9ad075bSchristos 		for (i = 0; i < mtd->height; i++) {
1116c9ad075bSchristos 			if (mtd->current == mtd->line_size - 1)
1117c9ad075bSchristos 				break;
1118c9ad075bSchristos 			mode_tree_down(mtd, 1);
1119c9ad075bSchristos 		}
1120c9ad075bSchristos 		break;
112146548964Swiz 	case 'g':
1122c9ad075bSchristos 	case KEYC_HOME:
1123c9ad075bSchristos 		mtd->current = 0;
1124c9ad075bSchristos 		mtd->offset = 0;
1125c9ad075bSchristos 		break;
112646548964Swiz 	case 'G':
1127c9ad075bSchristos 	case KEYC_END:
1128c9ad075bSchristos 		mtd->current = mtd->line_size - 1;
1129c9ad075bSchristos 		if (mtd->current > mtd->height - 1)
1130c9ad075bSchristos 			mtd->offset = mtd->current - mtd->height + 1;
1131c9ad075bSchristos 		else
1132c9ad075bSchristos 			mtd->offset = 0;
1133c9ad075bSchristos 		break;
1134c9ad075bSchristos 	case 't':
1135c9ad075bSchristos 		/*
1136c9ad075bSchristos 		 * Do not allow parents and children to both be tagged: untag
1137c9ad075bSchristos 		 * all parents and children of current.
1138c9ad075bSchristos 		 */
1139e271dbb8Schristos 		if (current->no_tag)
1140e271dbb8Schristos 			break;
1141c9ad075bSchristos 		if (!current->tagged) {
1142c9ad075bSchristos 			parent = current->parent;
1143c9ad075bSchristos 			while (parent != NULL) {
1144c9ad075bSchristos 				parent->tagged = 0;
1145c9ad075bSchristos 				parent = parent->parent;
1146c9ad075bSchristos 			}
1147c9ad075bSchristos 			mode_tree_clear_tagged(&current->children);
1148c9ad075bSchristos 			current->tagged = 1;
1149c9ad075bSchristos 		} else
1150c9ad075bSchristos 			current->tagged = 0;
115130744affSchristos 		if (m != NULL)
1152c9ad075bSchristos 			mode_tree_down(mtd, 0);
1153c9ad075bSchristos 		break;
1154c9ad075bSchristos 	case 'T':
1155c9ad075bSchristos 		for (i = 0; i < mtd->line_size; i++)
1156c9ad075bSchristos 			mtd->line_list[i].item->tagged = 0;
1157c9ad075bSchristos 		break;
1158*890b6d91Swiz 	case 't'|KEYC_CTRL:
1159c9ad075bSchristos 		for (i = 0; i < mtd->line_size; i++) {
1160e271dbb8Schristos 			if ((mtd->line_list[i].item->parent == NULL &&
1161e271dbb8Schristos 			    !mtd->line_list[i].item->no_tag) ||
1162e271dbb8Schristos 			    (mtd->line_list[i].item->parent != NULL &&
1163e271dbb8Schristos 			    mtd->line_list[i].item->parent->no_tag))
1164c9ad075bSchristos 				mtd->line_list[i].item->tagged = 1;
1165c9ad075bSchristos 			else
1166c9ad075bSchristos 				mtd->line_list[i].item->tagged = 0;
1167c9ad075bSchristos 		}
1168c9ad075bSchristos 		break;
1169c9ad075bSchristos 	case 'O':
117068e6ba84Schristos 		mtd->sort_crit.field++;
1171e271dbb8Schristos 		if (mtd->sort_crit.field >= mtd->sort_size)
117268e6ba84Schristos 			mtd->sort_crit.field = 0;
117368e6ba84Schristos 		mode_tree_build(mtd);
117468e6ba84Schristos 		break;
117568e6ba84Schristos 	case 'r':
117668e6ba84Schristos 		mtd->sort_crit.reversed = !mtd->sort_crit.reversed;
1177c9ad075bSchristos 		mode_tree_build(mtd);
1178c9ad075bSchristos 		break;
1179c9ad075bSchristos 	case KEYC_LEFT:
1180c7e17de0Schristos 	case 'h':
1181c9ad075bSchristos 	case '-':
1182c9ad075bSchristos 		if (line->flat || !current->expanded)
1183c9ad075bSchristos 			current = current->parent;
1184c9ad075bSchristos 		if (current == NULL)
1185c9ad075bSchristos 			mode_tree_up(mtd, 0);
1186c9ad075bSchristos 		else {
1187c9ad075bSchristos 			current->expanded = 0;
1188c9ad075bSchristos 			mtd->current = current->line;
1189c9ad075bSchristos 			mode_tree_build(mtd);
1190c9ad075bSchristos 		}
1191c9ad075bSchristos 		break;
1192c9ad075bSchristos 	case KEYC_RIGHT:
1193c7e17de0Schristos 	case 'l':
1194c9ad075bSchristos 	case '+':
1195c9ad075bSchristos 		if (line->flat || current->expanded)
1196c9ad075bSchristos 			mode_tree_down(mtd, 0);
1197c9ad075bSchristos 		else if (!line->flat) {
1198c9ad075bSchristos 			current->expanded = 1;
1199c9ad075bSchristos 			mode_tree_build(mtd);
1200c9ad075bSchristos 		}
1201c9ad075bSchristos 		break;
1202e271dbb8Schristos 	case '-'|KEYC_META:
1203e271dbb8Schristos 		TAILQ_FOREACH(mti, &mtd->children, entry)
1204e271dbb8Schristos 			mti->expanded = 0;
1205e271dbb8Schristos 		mode_tree_build(mtd);
1206e271dbb8Schristos 		break;
1207e271dbb8Schristos 	case '+'|KEYC_META:
1208e271dbb8Schristos 		TAILQ_FOREACH(mti, &mtd->children, entry)
1209e271dbb8Schristos 			mti->expanded = 1;
1210e271dbb8Schristos 		mode_tree_build(mtd);
1211e271dbb8Schristos 		break;
121230744affSchristos 	case '?':
121330744affSchristos 	case '/':
1214*890b6d91Swiz 	case 's'|KEYC_CTRL:
1215c9ad075bSchristos 		mtd->references++;
1216e271dbb8Schristos 		status_prompt_set(c, NULL, "(search) ", "",
1217c9ad075bSchristos 		    mode_tree_search_callback, mode_tree_search_free, mtd,
121846548964Swiz 		    PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH);
1219c9ad075bSchristos 		break;
1220c9ad075bSchristos 	case 'n':
1221*890b6d91Swiz 		mtd->search_dir = MODE_TREE_SEARCH_FORWARD;
1222*890b6d91Swiz 		mode_tree_search_set(mtd);
1223*890b6d91Swiz 		break;
1224*890b6d91Swiz 	case 'N':
1225*890b6d91Swiz 		mtd->search_dir = MODE_TREE_SEARCH_BACKWARD;
1226c9ad075bSchristos 		mode_tree_search_set(mtd);
1227c9ad075bSchristos 		break;
1228c9ad075bSchristos 	case 'f':
1229c9ad075bSchristos 		mtd->references++;
1230e271dbb8Schristos 		status_prompt_set(c, NULL, "(filter) ", mtd->filter,
1231c9ad075bSchristos 		    mode_tree_filter_callback, mode_tree_filter_free, mtd,
123246548964Swiz 		    PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH);
1233c9ad075bSchristos 		break;
1234c9ad075bSchristos 	case 'v':
1235c9ad075bSchristos 		mtd->preview = !mtd->preview;
1236c9ad075bSchristos 		mode_tree_build(mtd);
1237c9ad075bSchristos 		if (mtd->preview)
1238c9ad075bSchristos 			mode_tree_check_selected(mtd);
1239c9ad075bSchristos 		break;
1240c9ad075bSchristos 	}
1241c9ad075bSchristos 	return (0);
1242c9ad075bSchristos }
1243c9ad075bSchristos 
1244c9ad075bSchristos void
1245c9ad075bSchristos mode_tree_run_command(struct client *c, struct cmd_find_state *fs,
1246c9ad075bSchristos     const char *template, const char *name)
1247c9ad075bSchristos {
1248e271dbb8Schristos 	struct cmdq_state	*state;
1249e271dbb8Schristos 	char			*command, *error;
1250e271dbb8Schristos 	enum cmd_parse_status	 status;
1251c9ad075bSchristos 
1252c9ad075bSchristos 	command = cmd_template_replace(template, name, 1);
1253e271dbb8Schristos 	if (command != NULL && *command != '\0') {
1254e271dbb8Schristos 		state = cmdq_new_state(fs, NULL, 0);
1255e271dbb8Schristos 		status = cmd_parse_and_append(command, NULL, c, state, &error);
1256e271dbb8Schristos 		if (status == CMD_PARSE_ERROR) {
125730744affSchristos 			if (c != NULL) {
1258e271dbb8Schristos 				*error = toupper((u_char)*error);
1259e271dbb8Schristos 				status_message_set(c, -1, 1, 0, "%s", error);
1260c9ad075bSchristos 			}
1261e271dbb8Schristos 			free(error);
1262c9ad075bSchristos 		}
1263e271dbb8Schristos 		cmdq_free_state(state);
1264e271dbb8Schristos 	}
1265c9ad075bSchristos 	free(command);
1266c9ad075bSchristos }
1267