groff apparently generates broken output for some common characters in
[tmux-openbsd.git] / mode-tree.c
blobcebd4f05ade19857959080792db8cd9b88197bf4
1 /* $OpenBSD$ */
3 /*
4 * Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
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.
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.
19 #include <sys/types.h>
21 #include <ctype.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
26 #include "tmux.h"
28 struct mode_tree_item;
29 TAILQ_HEAD(mode_tree_list, mode_tree_item);
31 struct mode_tree_data {
32 int dead;
33 u_int references;
34 int zoomed;
36 struct window_pane *wp;
37 void *modedata;
38 const struct menu_item *menu;
40 const char **sort_list;
41 u_int sort_size;
42 struct mode_tree_sort_criteria sort_crit;
44 mode_tree_build_cb buildcb;
45 mode_tree_draw_cb drawcb;
46 mode_tree_search_cb searchcb;
47 mode_tree_menu_cb menucb;
48 mode_tree_height_cb heightcb;
49 mode_tree_key_cb keycb;
51 struct mode_tree_list children;
52 struct mode_tree_list saved;
54 struct mode_tree_line *line_list;
55 u_int line_size;
57 u_int depth;
59 u_int width;
60 u_int height;
62 u_int offset;
63 u_int current;
65 struct screen screen;
67 int preview;
68 char *search;
69 char *filter;
70 int no_matches;
73 struct mode_tree_item {
74 struct mode_tree_item *parent;
75 void *itemdata;
76 u_int line;
78 key_code key;
79 const char *keystr;
80 size_t keylen;
82 uint64_t tag;
83 const char *name;
84 const char *text;
86 int expanded;
87 int tagged;
89 int draw_as_parent;
90 int no_tag;
92 struct mode_tree_list children;
93 TAILQ_ENTRY(mode_tree_item) entry;
96 struct mode_tree_line {
97 struct mode_tree_item *item;
98 u_int depth;
99 int last;
100 int flat;
103 struct mode_tree_menu {
104 struct mode_tree_data *data;
105 struct client *c;
106 u_int line;
109 static void mode_tree_free_items(struct mode_tree_list *);
111 static const struct menu_item mode_tree_menu_items[] = {
112 { "Scroll Left", '<', NULL },
113 { "Scroll Right", '>', NULL },
114 { "", KEYC_NONE, NULL },
115 { "Cancel", 'q', NULL },
117 { NULL, KEYC_NONE, NULL }
120 static struct mode_tree_item *
121 mode_tree_find_item(struct mode_tree_list *mtl, uint64_t tag)
123 struct mode_tree_item *mti, *child;
125 TAILQ_FOREACH(mti, mtl, entry) {
126 if (mti->tag == tag)
127 return (mti);
128 child = mode_tree_find_item(&mti->children, tag);
129 if (child != NULL)
130 return (child);
132 return (NULL);
135 static void
136 mode_tree_free_item(struct mode_tree_item *mti)
138 mode_tree_free_items(&mti->children);
140 free((void *)mti->name);
141 free((void *)mti->text);
142 free((void *)mti->keystr);
144 free(mti);
147 static void
148 mode_tree_free_items(struct mode_tree_list *mtl)
150 struct mode_tree_item *mti, *mti1;
152 TAILQ_FOREACH_SAFE(mti, mtl, entry, mti1) {
153 TAILQ_REMOVE(mtl, mti, entry);
154 mode_tree_free_item(mti);
158 static void
159 mode_tree_check_selected(struct mode_tree_data *mtd)
162 * If the current line would now be off screen reset the offset to the
163 * last visible line.
165 if (mtd->current > mtd->height - 1)
166 mtd->offset = mtd->current - mtd->height + 1;
169 static void
170 mode_tree_clear_lines(struct mode_tree_data *mtd)
172 free(mtd->line_list);
173 mtd->line_list = NULL;
174 mtd->line_size = 0;
177 static void
178 mode_tree_build_lines(struct mode_tree_data *mtd,
179 struct mode_tree_list *mtl, u_int depth)
181 struct mode_tree_item *mti;
182 struct mode_tree_line *line;
183 u_int i;
184 int flat = 1;
186 mtd->depth = depth;
187 TAILQ_FOREACH(mti, mtl, entry) {
188 mtd->line_list = xreallocarray(mtd->line_list,
189 mtd->line_size + 1, sizeof *mtd->line_list);
191 line = &mtd->line_list[mtd->line_size++];
192 line->item = mti;
193 line->depth = depth;
194 line->last = (mti == TAILQ_LAST(mtl, mode_tree_list));
196 mti->line = (mtd->line_size - 1);
197 if (!TAILQ_EMPTY(&mti->children))
198 flat = 0;
199 if (mti->expanded)
200 mode_tree_build_lines(mtd, &mti->children, depth + 1);
202 if (mtd->keycb != NULL) {
203 mti->key = mtd->keycb(mtd->modedata, mti->itemdata,
204 mti->line);
205 if (mti->key == KEYC_UNKNOWN)
206 mti->key = KEYC_NONE;
207 } else if (mti->line < 10)
208 mti->key = '0' + mti->line;
209 else if (mti->line < 36)
210 mti->key = KEYC_META|('a' + mti->line - 10);
211 else
212 mti->key = KEYC_NONE;
213 if (mti->key != KEYC_NONE) {
214 mti->keystr = xstrdup(key_string_lookup_key(mti->key,
215 0));
216 mti->keylen = strlen(mti->keystr);
217 } else {
218 mti->keystr = NULL;
219 mti->keylen = 0;
222 TAILQ_FOREACH(mti, mtl, entry) {
223 for (i = 0; i < mtd->line_size; i++) {
224 line = &mtd->line_list[i];
225 if (line->item == mti)
226 line->flat = flat;
231 static void
232 mode_tree_clear_tagged(struct mode_tree_list *mtl)
234 struct mode_tree_item *mti;
236 TAILQ_FOREACH(mti, mtl, entry) {
237 mti->tagged = 0;
238 mode_tree_clear_tagged(&mti->children);
242 void
243 mode_tree_up(struct mode_tree_data *mtd, int wrap)
245 if (mtd->current == 0) {
246 if (wrap) {
247 mtd->current = mtd->line_size - 1;
248 if (mtd->line_size >= mtd->height)
249 mtd->offset = mtd->line_size - mtd->height;
251 } else {
252 mtd->current--;
253 if (mtd->current < mtd->offset)
254 mtd->offset--;
258 void
259 mode_tree_down(struct mode_tree_data *mtd, int wrap)
261 if (mtd->current == mtd->line_size - 1) {
262 if (wrap) {
263 mtd->current = 0;
264 mtd->offset = 0;
266 } else {
267 mtd->current++;
268 if (mtd->current > mtd->offset + mtd->height - 1)
269 mtd->offset++;
273 void *
274 mode_tree_get_current(struct mode_tree_data *mtd)
276 return (mtd->line_list[mtd->current].item->itemdata);
279 const char *
280 mode_tree_get_current_name(struct mode_tree_data *mtd)
282 return (mtd->line_list[mtd->current].item->name);
285 void
286 mode_tree_expand_current(struct mode_tree_data *mtd)
288 if (!mtd->line_list[mtd->current].item->expanded) {
289 mtd->line_list[mtd->current].item->expanded = 1;
290 mode_tree_build(mtd);
294 void
295 mode_tree_collapse_current(struct mode_tree_data *mtd)
297 if (mtd->line_list[mtd->current].item->expanded) {
298 mtd->line_list[mtd->current].item->expanded = 0;
299 mode_tree_build(mtd);
303 static int
304 mode_tree_get_tag(struct mode_tree_data *mtd, uint64_t tag, u_int *found)
306 u_int i;
308 for (i = 0; i < mtd->line_size; i++) {
309 if (mtd->line_list[i].item->tag == tag)
310 break;
312 if (i != mtd->line_size) {
313 *found = i;
314 return (1);
316 return (0);
319 void
320 mode_tree_expand(struct mode_tree_data *mtd, uint64_t tag)
322 u_int found;
324 if (!mode_tree_get_tag(mtd, tag, &found))
325 return;
326 if (!mtd->line_list[found].item->expanded) {
327 mtd->line_list[found].item->expanded = 1;
328 mode_tree_build(mtd);
333 mode_tree_set_current(struct mode_tree_data *mtd, uint64_t tag)
335 u_int found;
337 if (mode_tree_get_tag(mtd, tag, &found)) {
338 mtd->current = found;
339 if (mtd->current > mtd->height - 1)
340 mtd->offset = mtd->current - mtd->height + 1;
341 else
342 mtd->offset = 0;
343 return (1);
345 mtd->current = 0;
346 mtd->offset = 0;
347 return (0);
350 u_int
351 mode_tree_count_tagged(struct mode_tree_data *mtd)
353 struct mode_tree_item *mti;
354 u_int i, tagged;
356 tagged = 0;
357 for (i = 0; i < mtd->line_size; i++) {
358 mti = mtd->line_list[i].item;
359 if (mti->tagged)
360 tagged++;
362 return (tagged);
365 void
366 mode_tree_each_tagged(struct mode_tree_data *mtd, mode_tree_each_cb cb,
367 struct client *c, key_code key, int current)
369 struct mode_tree_item *mti;
370 u_int i;
371 int fired;
373 fired = 0;
374 for (i = 0; i < mtd->line_size; i++) {
375 mti = mtd->line_list[i].item;
376 if (mti->tagged) {
377 fired = 1;
378 cb(mtd->modedata, mti->itemdata, c, key);
381 if (!fired && current) {
382 mti = mtd->line_list[mtd->current].item;
383 cb(mtd->modedata, mti->itemdata, c, key);
387 struct mode_tree_data *
388 mode_tree_start(struct window_pane *wp, struct args *args,
389 mode_tree_build_cb buildcb, mode_tree_draw_cb drawcb,
390 mode_tree_search_cb searchcb, mode_tree_menu_cb menucb,
391 mode_tree_height_cb heightcb, mode_tree_key_cb keycb, void *modedata,
392 const struct menu_item *menu, const char **sort_list, u_int sort_size,
393 struct screen **s)
395 struct mode_tree_data *mtd;
396 const char *sort;
397 u_int i;
399 mtd = xcalloc(1, sizeof *mtd);
400 mtd->references = 1;
402 mtd->wp = wp;
403 mtd->modedata = modedata;
404 mtd->menu = menu;
406 mtd->sort_list = sort_list;
407 mtd->sort_size = sort_size;
409 mtd->preview = !args_has(args, 'N');
411 sort = args_get(args, 'O');
412 if (sort != NULL) {
413 for (i = 0; i < sort_size; i++) {
414 if (strcasecmp(sort, sort_list[i]) == 0)
415 mtd->sort_crit.field = i;
418 mtd->sort_crit.reversed = args_has(args, 'r');
420 if (args_has(args, 'f'))
421 mtd->filter = xstrdup(args_get(args, 'f'));
422 else
423 mtd->filter = NULL;
425 mtd->buildcb = buildcb;
426 mtd->drawcb = drawcb;
427 mtd->searchcb = searchcb;
428 mtd->menucb = menucb;
429 mtd->heightcb = heightcb;
430 mtd->keycb = keycb;
432 TAILQ_INIT(&mtd->children);
434 *s = &mtd->screen;
435 screen_init(*s, screen_size_x(&wp->base), screen_size_y(&wp->base), 0);
436 (*s)->mode &= ~MODE_CURSOR;
438 return (mtd);
441 void
442 mode_tree_zoom(struct mode_tree_data *mtd, struct args *args)
444 struct window_pane *wp = mtd->wp;
446 if (args_has(args, 'Z')) {
447 mtd->zoomed = (wp->window->flags & WINDOW_ZOOMED);
448 if (!mtd->zoomed && window_zoom(wp) == 0)
449 server_redraw_window(wp->window);
450 } else
451 mtd->zoomed = -1;
454 static void
455 mode_tree_set_height(struct mode_tree_data *mtd)
457 struct screen *s = &mtd->screen;
458 u_int height;
460 if (mtd->heightcb != NULL) {
461 height = mtd->heightcb(mtd, screen_size_y(s));
462 if (height < screen_size_y(s))
463 mtd->height = screen_size_y(s) - height;
464 } else {
465 mtd->height = (screen_size_y(s) / 3) * 2;
466 if (mtd->height > mtd->line_size)
467 mtd->height = screen_size_y(s) / 2;
469 if (mtd->height < 10)
470 mtd->height = screen_size_y(s);
471 if (screen_size_y(s) - mtd->height < 2)
472 mtd->height = screen_size_y(s);
475 void
476 mode_tree_build(struct mode_tree_data *mtd)
478 struct screen *s = &mtd->screen;
479 uint64_t tag;
481 if (mtd->line_list != NULL)
482 tag = mtd->line_list[mtd->current].item->tag;
483 else
484 tag = UINT64_MAX;
486 TAILQ_CONCAT(&mtd->saved, &mtd->children, entry);
487 TAILQ_INIT(&mtd->children);
489 mtd->buildcb(mtd->modedata, &mtd->sort_crit, &tag, mtd->filter);
490 mtd->no_matches = TAILQ_EMPTY(&mtd->children);
491 if (mtd->no_matches)
492 mtd->buildcb(mtd->modedata, &mtd->sort_crit, &tag, NULL);
494 mode_tree_free_items(&mtd->saved);
495 TAILQ_INIT(&mtd->saved);
497 mode_tree_clear_lines(mtd);
498 mode_tree_build_lines(mtd, &mtd->children, 0);
500 if (mtd->line_list != NULL && tag == UINT64_MAX)
501 tag = mtd->line_list[mtd->current].item->tag;
502 mode_tree_set_current(mtd, tag);
504 mtd->width = screen_size_x(s);
505 if (mtd->preview)
506 mode_tree_set_height(mtd);
507 else
508 mtd->height = screen_size_y(s);
509 mode_tree_check_selected(mtd);
512 static void
513 mode_tree_remove_ref(struct mode_tree_data *mtd)
515 if (--mtd->references == 0)
516 free(mtd);
519 void
520 mode_tree_free(struct mode_tree_data *mtd)
522 struct window_pane *wp = mtd->wp;
524 if (mtd->zoomed == 0)
525 server_unzoom_window(wp->window);
527 mode_tree_free_items(&mtd->children);
528 mode_tree_clear_lines(mtd);
529 screen_free(&mtd->screen);
531 free(mtd->search);
532 free(mtd->filter);
534 mtd->dead = 1;
535 mode_tree_remove_ref(mtd);
538 void
539 mode_tree_resize(struct mode_tree_data *mtd, u_int sx, u_int sy)
541 struct screen *s = &mtd->screen;
543 screen_resize(s, sx, sy, 0);
545 mode_tree_build(mtd);
546 mode_tree_draw(mtd);
548 mtd->wp->flags |= PANE_REDRAW;
551 struct mode_tree_item *
552 mode_tree_add(struct mode_tree_data *mtd, struct mode_tree_item *parent,
553 void *itemdata, uint64_t tag, const char *name, const char *text,
554 int expanded)
556 struct mode_tree_item *mti, *saved;
558 log_debug("%s: %llu, %s %s", __func__, (unsigned long long)tag,
559 name, (text == NULL ? "" : text));
561 mti = xcalloc(1, sizeof *mti);
562 mti->parent = parent;
563 mti->itemdata = itemdata;
565 mti->tag = tag;
566 mti->name = xstrdup(name);
567 if (text != NULL)
568 mti->text = xstrdup(text);
570 saved = mode_tree_find_item(&mtd->saved, tag);
571 if (saved != NULL) {
572 if (parent == NULL || parent->expanded)
573 mti->tagged = saved->tagged;
574 mti->expanded = saved->expanded;
575 } else if (expanded == -1)
576 mti->expanded = 1;
577 else
578 mti->expanded = expanded;
580 TAILQ_INIT(&mti->children);
582 if (parent != NULL)
583 TAILQ_INSERT_TAIL(&parent->children, mti, entry);
584 else
585 TAILQ_INSERT_TAIL(&mtd->children, mti, entry);
587 return (mti);
590 void
591 mode_tree_draw_as_parent(struct mode_tree_item *mti)
593 mti->draw_as_parent = 1;
596 void
597 mode_tree_no_tag(struct mode_tree_item *mti)
599 mti->no_tag = 1;
602 void
603 mode_tree_remove(struct mode_tree_data *mtd, struct mode_tree_item *mti)
605 struct mode_tree_item *parent = mti->parent;
607 if (parent != NULL)
608 TAILQ_REMOVE(&parent->children, mti, entry);
609 else
610 TAILQ_REMOVE(&mtd->children, mti, entry);
611 mode_tree_free_item(mti);
614 void
615 mode_tree_draw(struct mode_tree_data *mtd)
617 struct window_pane *wp = mtd->wp;
618 struct screen *s = &mtd->screen;
619 struct mode_tree_line *line;
620 struct mode_tree_item *mti;
621 struct options *oo = wp->window->options;
622 struct screen_write_ctx ctx;
623 struct grid_cell gc0, gc;
624 u_int w, h, i, j, sy, box_x, box_y, width;
625 char *text, *start, *key;
626 const char *tag, *symbol;
627 size_t size, n;
628 int keylen, pad;
630 if (mtd->line_size == 0)
631 return;
633 memcpy(&gc0, &grid_default_cell, sizeof gc0);
634 memcpy(&gc, &grid_default_cell, sizeof gc);
635 style_apply(&gc, oo, "mode-style", NULL);
637 w = mtd->width;
638 h = mtd->height;
640 screen_write_start(&ctx, s);
641 screen_write_clearscreen(&ctx, 8);
643 keylen = 0;
644 for (i = 0; i < mtd->line_size; i++) {
645 mti = mtd->line_list[i].item;
646 if (mti->key == KEYC_NONE)
647 continue;
648 if ((int)mti->keylen + 3 > keylen)
649 keylen = mti->keylen + 3;
652 for (i = 0; i < mtd->line_size; i++) {
653 if (i < mtd->offset)
654 continue;
655 if (i > mtd->offset + h - 1)
656 break;
657 line = &mtd->line_list[i];
658 mti = line->item;
660 screen_write_cursormove(&ctx, 0, i - mtd->offset, 0);
662 pad = keylen - 2 - mti->keylen;
663 if (mti->key != KEYC_NONE)
664 xasprintf(&key, "(%s)%*s", mti->keystr, pad, "");
665 else
666 key = xstrdup("");
668 if (line->flat)
669 symbol = "";
670 else if (TAILQ_EMPTY(&mti->children))
671 symbol = " ";
672 else if (mti->expanded)
673 symbol = "- ";
674 else
675 symbol = "+ ";
677 if (line->depth == 0)
678 start = xstrdup(symbol);
679 else {
680 size = (4 * line->depth) + 32;
682 start = xcalloc(1, size);
683 for (j = 1; j < line->depth; j++) {
684 if (mti->parent != NULL &&
685 mtd->line_list[mti->parent->line].last)
686 strlcat(start, " ", size);
687 else
688 strlcat(start, "\001x\001 ", size);
690 if (line->last)
691 strlcat(start, "\001mq\001> ", size);
692 else
693 strlcat(start, "\001tq\001> ", size);
694 strlcat(start, symbol, size);
697 if (mti->tagged)
698 tag = "*";
699 else
700 tag = "";
701 xasprintf(&text, "%-*s%s%s%s%s", keylen, key, start, mti->name,
702 tag, (mti->text != NULL) ? ": " : "" );
703 width = utf8_cstrwidth(text);
704 if (width > w)
705 width = w;
706 free(start);
708 if (mti->tagged) {
709 gc.attr ^= GRID_ATTR_BRIGHT;
710 gc0.attr ^= GRID_ATTR_BRIGHT;
713 if (i != mtd->current) {
714 screen_write_clearendofline(&ctx, 8);
715 screen_write_nputs(&ctx, w, &gc0, "%s", text);
716 if (mti->text != NULL) {
717 format_draw(&ctx, &gc0, w - width, mti->text,
718 NULL, 0);
720 } else {
721 screen_write_clearendofline(&ctx, gc.bg);
722 screen_write_nputs(&ctx, w, &gc, "%s", text);
723 if (mti->text != NULL) {
724 format_draw(&ctx, &gc, w - width, mti->text,
725 NULL, 0);
728 free(text);
729 free(key);
731 if (mti->tagged) {
732 gc.attr ^= GRID_ATTR_BRIGHT;
733 gc0.attr ^= GRID_ATTR_BRIGHT;
737 sy = screen_size_y(s);
738 if (!mtd->preview || sy <= 4 || h <= 4 || sy - h <= 4 || w <= 4)
739 goto done;
741 line = &mtd->line_list[mtd->current];
742 mti = line->item;
743 if (mti->draw_as_parent)
744 mti = mti->parent;
746 screen_write_cursormove(&ctx, 0, h, 0);
747 screen_write_box(&ctx, w, sy - h, BOX_LINES_DEFAULT, NULL, NULL);
749 if (mtd->sort_list != NULL) {
750 xasprintf(&text, " %s (sort: %s%s)", mti->name,
751 mtd->sort_list[mtd->sort_crit.field],
752 mtd->sort_crit.reversed ? ", reversed" : "");
753 } else
754 xasprintf(&text, " %s", mti->name);
755 if (w - 2 >= strlen(text)) {
756 screen_write_cursormove(&ctx, 1, h, 0);
757 screen_write_puts(&ctx, &gc0, "%s", text);
759 if (mtd->no_matches)
760 n = (sizeof "no matches") - 1;
761 else
762 n = (sizeof "active") - 1;
763 if (mtd->filter != NULL && w - 2 >= strlen(text) + 10 + n + 2) {
764 screen_write_puts(&ctx, &gc0, " (filter: ");
765 if (mtd->no_matches)
766 screen_write_puts(&ctx, &gc, "no matches");
767 else
768 screen_write_puts(&ctx, &gc0, "active");
769 screen_write_puts(&ctx, &gc0, ") ");
770 } else
771 screen_write_puts(&ctx, &gc0, " ");
773 free(text);
775 box_x = w - 4;
776 box_y = sy - h - 2;
778 if (box_x != 0 && box_y != 0) {
779 screen_write_cursormove(&ctx, 2, h + 1, 0);
780 mtd->drawcb(mtd->modedata, mti->itemdata, &ctx, box_x, box_y);
783 done:
784 screen_write_cursormove(&ctx, 0, mtd->current - mtd->offset, 0);
785 screen_write_stop(&ctx);
788 static struct mode_tree_item *
789 mode_tree_search_for(struct mode_tree_data *mtd)
791 struct mode_tree_item *mti, *last, *next;
793 if (mtd->search == NULL)
794 return (NULL);
796 mti = last = mtd->line_list[mtd->current].item;
797 for (;;) {
798 if (!TAILQ_EMPTY(&mti->children))
799 mti = TAILQ_FIRST(&mti->children);
800 else if ((next = TAILQ_NEXT(mti, entry)) != NULL)
801 mti = next;
802 else {
803 for (;;) {
804 mti = mti->parent;
805 if (mti == NULL)
806 break;
807 if ((next = TAILQ_NEXT(mti, entry)) != NULL) {
808 mti = next;
809 break;
813 if (mti == NULL)
814 mti = TAILQ_FIRST(&mtd->children);
815 if (mti == last)
816 break;
818 if (mtd->searchcb == NULL) {
819 if (strstr(mti->name, mtd->search) != NULL)
820 return (mti);
821 continue;
823 if (mtd->searchcb(mtd->modedata, mti->itemdata, mtd->search))
824 return (mti);
826 return (NULL);
829 static void
830 mode_tree_search_set(struct mode_tree_data *mtd)
832 struct mode_tree_item *mti, *loop;
833 uint64_t tag;
835 mti = mode_tree_search_for(mtd);
836 if (mti == NULL)
837 return;
838 tag = mti->tag;
840 loop = mti->parent;
841 while (loop != NULL) {
842 loop->expanded = 1;
843 loop = loop->parent;
846 mode_tree_build(mtd);
847 mode_tree_set_current(mtd, tag);
848 mode_tree_draw(mtd);
849 mtd->wp->flags |= PANE_REDRAW;
852 static int
853 mode_tree_search_callback(__unused struct client *c, void *data, const char *s,
854 __unused int done)
856 struct mode_tree_data *mtd = data;
858 if (mtd->dead)
859 return (0);
861 free(mtd->search);
862 if (s == NULL || *s == '\0') {
863 mtd->search = NULL;
864 return (0);
866 mtd->search = xstrdup(s);
867 mode_tree_search_set(mtd);
869 return (0);
872 static void
873 mode_tree_search_free(void *data)
875 mode_tree_remove_ref(data);
878 static int
879 mode_tree_filter_callback(__unused struct client *c, void *data, const char *s,
880 __unused int done)
882 struct mode_tree_data *mtd = data;
884 if (mtd->dead)
885 return (0);
887 if (mtd->filter != NULL)
888 free(mtd->filter);
889 if (s == NULL || *s == '\0')
890 mtd->filter = NULL;
891 else
892 mtd->filter = xstrdup(s);
894 mode_tree_build(mtd);
895 mode_tree_draw(mtd);
896 mtd->wp->flags |= PANE_REDRAW;
898 return (0);
901 static void
902 mode_tree_filter_free(void *data)
904 mode_tree_remove_ref(data);
907 static void
908 mode_tree_menu_callback(__unused struct menu *menu, __unused u_int idx,
909 key_code key, void *data)
911 struct mode_tree_menu *mtm = data;
912 struct mode_tree_data *mtd = mtm->data;
914 if (mtd->dead || key == KEYC_NONE)
915 goto out;
917 if (mtm->line >= mtd->line_size)
918 goto out;
919 mtd->current = mtm->line;
920 mtd->menucb(mtd->modedata, mtm->c, key);
922 out:
923 mode_tree_remove_ref(mtd);
924 free(mtm);
927 static void
928 mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x,
929 u_int y, int outside)
931 struct mode_tree_item *mti;
932 struct menu *menu;
933 const struct menu_item *items;
934 struct mode_tree_menu *mtm;
935 char *title;
936 u_int line;
938 if (mtd->offset + y > mtd->line_size - 1)
939 line = mtd->current;
940 else
941 line = mtd->offset + y;
942 mti = mtd->line_list[line].item;
944 if (!outside) {
945 items = mtd->menu;
946 xasprintf(&title, "#[align=centre]%s", mti->name);
947 } else {
948 items = mode_tree_menu_items;
949 title = xstrdup("");
951 menu = menu_create(title);
952 menu_add_items(menu, items, NULL, c, NULL);
953 free(title);
955 mtm = xmalloc(sizeof *mtm);
956 mtm->data = mtd;
957 mtm->c = c;
958 mtm->line = line;
959 mtd->references++;
961 if (x >= (menu->width + 4) / 2)
962 x -= (menu->width + 4) / 2;
963 else
964 x = 0;
965 if (menu_display(menu, 0, 0, NULL, x, y, c, BOX_LINES_DEFAULT, NULL,
966 NULL, NULL, NULL, mode_tree_menu_callback, mtm) != 0)
967 menu_free(menu);
971 mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
972 struct mouse_event *m, u_int *xp, u_int *yp)
974 struct mode_tree_line *line;
975 struct mode_tree_item *current, *parent, *mti;
976 u_int i, x, y;
977 int choice;
979 if (KEYC_IS_MOUSE(*key) && m != NULL) {
980 if (cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) {
981 *key = KEYC_NONE;
982 return (0);
984 if (xp != NULL)
985 *xp = x;
986 if (yp != NULL)
987 *yp = y;
988 if (x > mtd->width || y > mtd->height) {
989 if (*key == KEYC_MOUSEDOWN3_PANE)
990 mode_tree_display_menu(mtd, c, x, y, 1);
991 if (!mtd->preview)
992 *key = KEYC_NONE;
993 return (0);
995 if (mtd->offset + y < mtd->line_size) {
996 if (*key == KEYC_MOUSEDOWN1_PANE ||
997 *key == KEYC_MOUSEDOWN3_PANE ||
998 *key == KEYC_DOUBLECLICK1_PANE)
999 mtd->current = mtd->offset + y;
1000 if (*key == KEYC_DOUBLECLICK1_PANE)
1001 *key = '\r';
1002 else {
1003 if (*key == KEYC_MOUSEDOWN3_PANE)
1004 mode_tree_display_menu(mtd, c, x, y, 0);
1005 *key = KEYC_NONE;
1007 } else {
1008 if (*key == KEYC_MOUSEDOWN3_PANE)
1009 mode_tree_display_menu(mtd, c, x, y, 0);
1010 *key = KEYC_NONE;
1012 return (0);
1015 line = &mtd->line_list[mtd->current];
1016 current = line->item;
1018 choice = -1;
1019 for (i = 0; i < mtd->line_size; i++) {
1020 if (*key == mtd->line_list[i].item->key) {
1021 choice = i;
1022 break;
1025 if (choice != -1) {
1026 if ((u_int)choice > mtd->line_size - 1) {
1027 *key = KEYC_NONE;
1028 return (0);
1030 mtd->current = choice;
1031 *key = '\r';
1032 return (0);
1035 switch (*key) {
1036 case 'q':
1037 case '\033': /* Escape */
1038 case '\007': /* C-g */
1039 return (1);
1040 case KEYC_UP:
1041 case 'k':
1042 case KEYC_WHEELUP_PANE:
1043 case '\020': /* C-p */
1044 mode_tree_up(mtd, 1);
1045 break;
1046 case KEYC_DOWN:
1047 case 'j':
1048 case KEYC_WHEELDOWN_PANE:
1049 case '\016': /* C-n */
1050 mode_tree_down(mtd, 1);
1051 break;
1052 case KEYC_PPAGE:
1053 case '\002': /* C-b */
1054 for (i = 0; i < mtd->height; i++) {
1055 if (mtd->current == 0)
1056 break;
1057 mode_tree_up(mtd, 1);
1059 break;
1060 case KEYC_NPAGE:
1061 case '\006': /* C-f */
1062 for (i = 0; i < mtd->height; i++) {
1063 if (mtd->current == mtd->line_size - 1)
1064 break;
1065 mode_tree_down(mtd, 1);
1067 break;
1068 case 'g':
1069 case KEYC_HOME:
1070 mtd->current = 0;
1071 mtd->offset = 0;
1072 break;
1073 case 'G':
1074 case KEYC_END:
1075 mtd->current = mtd->line_size - 1;
1076 if (mtd->current > mtd->height - 1)
1077 mtd->offset = mtd->current - mtd->height + 1;
1078 else
1079 mtd->offset = 0;
1080 break;
1081 case 't':
1083 * Do not allow parents and children to both be tagged: untag
1084 * all parents and children of current.
1086 if (current->no_tag)
1087 break;
1088 if (!current->tagged) {
1089 parent = current->parent;
1090 while (parent != NULL) {
1091 parent->tagged = 0;
1092 parent = parent->parent;
1094 mode_tree_clear_tagged(&current->children);
1095 current->tagged = 1;
1096 } else
1097 current->tagged = 0;
1098 if (m != NULL)
1099 mode_tree_down(mtd, 0);
1100 break;
1101 case 'T':
1102 for (i = 0; i < mtd->line_size; i++)
1103 mtd->line_list[i].item->tagged = 0;
1104 break;
1105 case '\024': /* C-t */
1106 for (i = 0; i < mtd->line_size; i++) {
1107 if ((mtd->line_list[i].item->parent == NULL &&
1108 !mtd->line_list[i].item->no_tag) ||
1109 (mtd->line_list[i].item->parent != NULL &&
1110 mtd->line_list[i].item->parent->no_tag))
1111 mtd->line_list[i].item->tagged = 1;
1112 else
1113 mtd->line_list[i].item->tagged = 0;
1115 break;
1116 case 'O':
1117 mtd->sort_crit.field++;
1118 if (mtd->sort_crit.field >= mtd->sort_size)
1119 mtd->sort_crit.field = 0;
1120 mode_tree_build(mtd);
1121 break;
1122 case 'r':
1123 mtd->sort_crit.reversed = !mtd->sort_crit.reversed;
1124 mode_tree_build(mtd);
1125 break;
1126 case KEYC_LEFT:
1127 case 'h':
1128 case '-':
1129 if (line->flat || !current->expanded)
1130 current = current->parent;
1131 if (current == NULL)
1132 mode_tree_up(mtd, 0);
1133 else {
1134 current->expanded = 0;
1135 mtd->current = current->line;
1136 mode_tree_build(mtd);
1138 break;
1139 case KEYC_RIGHT:
1140 case 'l':
1141 case '+':
1142 if (line->flat || current->expanded)
1143 mode_tree_down(mtd, 0);
1144 else if (!line->flat) {
1145 current->expanded = 1;
1146 mode_tree_build(mtd);
1148 break;
1149 case '-'|KEYC_META:
1150 TAILQ_FOREACH(mti, &mtd->children, entry)
1151 mti->expanded = 0;
1152 mode_tree_build(mtd);
1153 break;
1154 case '+'|KEYC_META:
1155 TAILQ_FOREACH(mti, &mtd->children, entry)
1156 mti->expanded = 1;
1157 mode_tree_build(mtd);
1158 break;
1159 case '?':
1160 case '/':
1161 case '\023': /* C-s */
1162 mtd->references++;
1163 status_prompt_set(c, NULL, "(search) ", "",
1164 mode_tree_search_callback, mode_tree_search_free, mtd,
1165 PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH);
1166 break;
1167 case 'n':
1168 mode_tree_search_set(mtd);
1169 break;
1170 case 'f':
1171 mtd->references++;
1172 status_prompt_set(c, NULL, "(filter) ", mtd->filter,
1173 mode_tree_filter_callback, mode_tree_filter_free, mtd,
1174 PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH);
1175 break;
1176 case 'v':
1177 mtd->preview = !mtd->preview;
1178 mode_tree_build(mtd);
1179 if (mtd->preview)
1180 mode_tree_check_selected(mtd);
1181 break;
1183 return (0);
1186 void
1187 mode_tree_run_command(struct client *c, struct cmd_find_state *fs,
1188 const char *template, const char *name)
1190 struct cmdq_state *state;
1191 char *command, *error;
1192 enum cmd_parse_status status;
1194 command = cmd_template_replace(template, name, 1);
1195 if (command != NULL && *command != '\0') {
1196 state = cmdq_new_state(fs, NULL, 0);
1197 status = cmd_parse_and_append(command, NULL, c, state, &error);
1198 if (status == CMD_PARSE_ERROR) {
1199 if (c != NULL) {
1200 *error = toupper((u_char)*error);
1201 status_message_set(c, -1, 1, 0, "%s", error);
1203 free(error);
1205 cmdq_free_state(state);
1207 free(command);