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