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