Do not escape $ unless DQ is set, that is the only case where we need to
[tmux-openbsd.git] / mode-tree.c
blob5a4e3bbc69c701ce9f2a9abae3cc5f0954b3734c
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 enum mode_tree_search_dir {
29 MODE_TREE_SEARCH_FORWARD,
30 MODE_TREE_SEARCH_BACKWARD
33 struct mode_tree_item;
34 TAILQ_HEAD(mode_tree_list, mode_tree_item);
36 struct mode_tree_data {
37 int dead;
38 u_int references;
39 int zoomed;
41 struct window_pane *wp;
42 void *modedata;
43 const struct menu_item *menu;
45 const char **sort_list;
46 u_int sort_size;
47 struct mode_tree_sort_criteria sort_crit;
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;
56 struct mode_tree_list children;
57 struct mode_tree_list saved;
59 struct mode_tree_line *line_list;
60 u_int line_size;
62 u_int depth;
64 u_int width;
65 u_int height;
67 u_int offset;
68 u_int current;
70 struct screen screen;
72 int preview;
73 char *search;
74 char *filter;
75 int no_matches;
76 enum mode_tree_search_dir search_dir;
79 struct mode_tree_item {
80 struct mode_tree_item *parent;
81 void *itemdata;
82 u_int line;
84 key_code key;
85 const char *keystr;
86 size_t keylen;
88 uint64_t tag;
89 const char *name;
90 const char *text;
92 int expanded;
93 int tagged;
95 int draw_as_parent;
96 int no_tag;
98 struct mode_tree_list children;
99 TAILQ_ENTRY(mode_tree_item) entry;
102 struct mode_tree_line {
103 struct mode_tree_item *item;
104 u_int depth;
105 int last;
106 int flat;
109 struct mode_tree_menu {
110 struct mode_tree_data *data;
111 struct client *c;
112 u_int line;
115 static void mode_tree_free_items(struct mode_tree_list *);
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 },
123 { NULL, KEYC_NONE, NULL }
126 static struct mode_tree_item *
127 mode_tree_find_item(struct mode_tree_list *mtl, uint64_t tag)
129 struct mode_tree_item *mti, *child;
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);
138 return (NULL);
141 static void
142 mode_tree_free_item(struct mode_tree_item *mti)
144 mode_tree_free_items(&mti->children);
146 free((void *)mti->name);
147 free((void *)mti->text);
148 free((void *)mti->keystr);
150 free(mti);
153 static void
154 mode_tree_free_items(struct mode_tree_list *mtl)
156 struct mode_tree_item *mti, *mti1;
158 TAILQ_FOREACH_SAFE(mti, mtl, entry, mti1) {
159 TAILQ_REMOVE(mtl, mti, entry);
160 mode_tree_free_item(mti);
164 static void
165 mode_tree_check_selected(struct mode_tree_data *mtd)
168 * If the current line would now be off screen reset the offset to the
169 * last visible line.
171 if (mtd->current > mtd->height - 1)
172 mtd->offset = mtd->current - mtd->height + 1;
175 static void
176 mode_tree_clear_lines(struct mode_tree_data *mtd)
178 free(mtd->line_list);
179 mtd->line_list = NULL;
180 mtd->line_size = 0;
183 static void
184 mode_tree_build_lines(struct mode_tree_data *mtd,
185 struct mode_tree_list *mtl, u_int depth)
187 struct mode_tree_item *mti;
188 struct mode_tree_line *line;
189 u_int i;
190 int flat = 1;
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);
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));
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);
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;
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;
237 static void
238 mode_tree_clear_tagged(struct mode_tree_list *mtl)
240 struct mode_tree_item *mti;
242 TAILQ_FOREACH(mti, mtl, entry) {
243 mti->tagged = 0;
244 mode_tree_clear_tagged(&mti->children);
248 void
249 mode_tree_up(struct mode_tree_data *mtd, int wrap)
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;
257 } else {
258 mtd->current--;
259 if (mtd->current < mtd->offset)
260 mtd->offset--;
264 void
265 mode_tree_down(struct mode_tree_data *mtd, int wrap)
267 if (mtd->current == mtd->line_size - 1) {
268 if (wrap) {
269 mtd->current = 0;
270 mtd->offset = 0;
272 } else {
273 mtd->current++;
274 if (mtd->current > mtd->offset + mtd->height - 1)
275 mtd->offset++;
279 void *
280 mode_tree_get_current(struct mode_tree_data *mtd)
282 return (mtd->line_list[mtd->current].item->itemdata);
285 const char *
286 mode_tree_get_current_name(struct mode_tree_data *mtd)
288 return (mtd->line_list[mtd->current].item->name);
291 void
292 mode_tree_expand_current(struct mode_tree_data *mtd)
294 if (!mtd->line_list[mtd->current].item->expanded) {
295 mtd->line_list[mtd->current].item->expanded = 1;
296 mode_tree_build(mtd);
300 void
301 mode_tree_collapse_current(struct mode_tree_data *mtd)
303 if (mtd->line_list[mtd->current].item->expanded) {
304 mtd->line_list[mtd->current].item->expanded = 0;
305 mode_tree_build(mtd);
309 static int
310 mode_tree_get_tag(struct mode_tree_data *mtd, uint64_t tag, u_int *found)
312 u_int i;
314 for (i = 0; i < mtd->line_size; i++) {
315 if (mtd->line_list[i].item->tag == tag)
316 break;
318 if (i != mtd->line_size) {
319 *found = i;
320 return (1);
322 return (0);
325 void
326 mode_tree_expand(struct mode_tree_data *mtd, uint64_t tag)
328 u_int found;
330 if (!mode_tree_get_tag(mtd, tag, &found))
331 return;
332 if (!mtd->line_list[found].item->expanded) {
333 mtd->line_list[found].item->expanded = 1;
334 mode_tree_build(mtd);
339 mode_tree_set_current(struct mode_tree_data *mtd, uint64_t tag)
341 u_int found;
343 if (mode_tree_get_tag(mtd, tag, &found)) {
344 mtd->current = found;
345 if (mtd->current > mtd->height - 1)
346 mtd->offset = mtd->current - mtd->height + 1;
347 else
348 mtd->offset = 0;
349 return (1);
351 mtd->current = 0;
352 mtd->offset = 0;
353 return (0);
356 u_int
357 mode_tree_count_tagged(struct mode_tree_data *mtd)
359 struct mode_tree_item *mti;
360 u_int i, tagged;
362 tagged = 0;
363 for (i = 0; i < mtd->line_size; i++) {
364 mti = mtd->line_list[i].item;
365 if (mti->tagged)
366 tagged++;
368 return (tagged);
371 void
372 mode_tree_each_tagged(struct mode_tree_data *mtd, mode_tree_each_cb cb,
373 struct client *c, key_code key, int current)
375 struct mode_tree_item *mti;
376 u_int i;
377 int fired;
379 fired = 0;
380 for (i = 0; i < mtd->line_size; i++) {
381 mti = mtd->line_list[i].item;
382 if (mti->tagged) {
383 fired = 1;
384 cb(mtd->modedata, mti->itemdata, c, key);
387 if (!fired && current) {
388 mti = mtd->line_list[mtd->current].item;
389 cb(mtd->modedata, mti->itemdata, c, key);
393 struct mode_tree_data *
394 mode_tree_start(struct window_pane *wp, struct args *args,
395 mode_tree_build_cb buildcb, mode_tree_draw_cb drawcb,
396 mode_tree_search_cb searchcb, mode_tree_menu_cb menucb,
397 mode_tree_height_cb heightcb, mode_tree_key_cb keycb, void *modedata,
398 const struct menu_item *menu, const char **sort_list, u_int sort_size,
399 struct screen **s)
401 struct mode_tree_data *mtd;
402 const char *sort;
403 u_int i;
405 mtd = xcalloc(1, sizeof *mtd);
406 mtd->references = 1;
408 mtd->wp = wp;
409 mtd->modedata = modedata;
410 mtd->menu = menu;
412 mtd->sort_list = sort_list;
413 mtd->sort_size = sort_size;
415 mtd->preview = !args_has(args, 'N');
417 sort = args_get(args, 'O');
418 if (sort != NULL) {
419 for (i = 0; i < sort_size; i++) {
420 if (strcasecmp(sort, sort_list[i]) == 0)
421 mtd->sort_crit.field = i;
424 mtd->sort_crit.reversed = args_has(args, 'r');
426 if (args_has(args, 'f'))
427 mtd->filter = xstrdup(args_get(args, 'f'));
428 else
429 mtd->filter = NULL;
431 mtd->buildcb = buildcb;
432 mtd->drawcb = drawcb;
433 mtd->searchcb = searchcb;
434 mtd->menucb = menucb;
435 mtd->heightcb = heightcb;
436 mtd->keycb = keycb;
438 TAILQ_INIT(&mtd->children);
440 *s = &mtd->screen;
441 screen_init(*s, screen_size_x(&wp->base), screen_size_y(&wp->base), 0);
442 (*s)->mode &= ~MODE_CURSOR;
444 return (mtd);
447 void
448 mode_tree_zoom(struct mode_tree_data *mtd, struct args *args)
450 struct window_pane *wp = mtd->wp;
452 if (args_has(args, 'Z')) {
453 mtd->zoomed = (wp->window->flags & WINDOW_ZOOMED);
454 if (!mtd->zoomed && window_zoom(wp) == 0)
455 server_redraw_window(wp->window);
456 } else
457 mtd->zoomed = -1;
460 static void
461 mode_tree_set_height(struct mode_tree_data *mtd)
463 struct screen *s = &mtd->screen;
464 u_int height;
466 if (mtd->heightcb != NULL) {
467 height = mtd->heightcb(mtd, screen_size_y(s));
468 if (height < screen_size_y(s))
469 mtd->height = screen_size_y(s) - height;
470 } else {
471 mtd->height = (screen_size_y(s) / 3) * 2;
472 if (mtd->height > mtd->line_size)
473 mtd->height = screen_size_y(s) / 2;
475 if (mtd->height < 10)
476 mtd->height = screen_size_y(s);
477 if (screen_size_y(s) - mtd->height < 2)
478 mtd->height = screen_size_y(s);
481 void
482 mode_tree_build(struct mode_tree_data *mtd)
484 struct screen *s = &mtd->screen;
485 uint64_t tag;
487 if (mtd->line_list != NULL)
488 tag = mtd->line_list[mtd->current].item->tag;
489 else
490 tag = UINT64_MAX;
492 TAILQ_CONCAT(&mtd->saved, &mtd->children, entry);
493 TAILQ_INIT(&mtd->children);
495 mtd->buildcb(mtd->modedata, &mtd->sort_crit, &tag, mtd->filter);
496 mtd->no_matches = TAILQ_EMPTY(&mtd->children);
497 if (mtd->no_matches)
498 mtd->buildcb(mtd->modedata, &mtd->sort_crit, &tag, NULL);
500 mode_tree_free_items(&mtd->saved);
501 TAILQ_INIT(&mtd->saved);
503 mode_tree_clear_lines(mtd);
504 mode_tree_build_lines(mtd, &mtd->children, 0);
506 if (mtd->line_list != NULL && tag == UINT64_MAX)
507 tag = mtd->line_list[mtd->current].item->tag;
508 mode_tree_set_current(mtd, tag);
510 mtd->width = screen_size_x(s);
511 if (mtd->preview)
512 mode_tree_set_height(mtd);
513 else
514 mtd->height = screen_size_y(s);
515 mode_tree_check_selected(mtd);
518 static void
519 mode_tree_remove_ref(struct mode_tree_data *mtd)
521 if (--mtd->references == 0)
522 free(mtd);
525 void
526 mode_tree_free(struct mode_tree_data *mtd)
528 struct window_pane *wp = mtd->wp;
530 if (mtd->zoomed == 0)
531 server_unzoom_window(wp->window);
533 mode_tree_free_items(&mtd->children);
534 mode_tree_clear_lines(mtd);
535 screen_free(&mtd->screen);
537 free(mtd->search);
538 free(mtd->filter);
540 mtd->dead = 1;
541 mode_tree_remove_ref(mtd);
544 void
545 mode_tree_resize(struct mode_tree_data *mtd, u_int sx, u_int sy)
547 struct screen *s = &mtd->screen;
549 screen_resize(s, sx, sy, 0);
551 mode_tree_build(mtd);
552 mode_tree_draw(mtd);
554 mtd->wp->flags |= PANE_REDRAW;
557 struct mode_tree_item *
558 mode_tree_add(struct mode_tree_data *mtd, struct mode_tree_item *parent,
559 void *itemdata, uint64_t tag, const char *name, const char *text,
560 int expanded)
562 struct mode_tree_item *mti, *saved;
564 log_debug("%s: %llu, %s %s", __func__, (unsigned long long)tag,
565 name, (text == NULL ? "" : text));
567 mti = xcalloc(1, sizeof *mti);
568 mti->parent = parent;
569 mti->itemdata = itemdata;
571 mti->tag = tag;
572 mti->name = xstrdup(name);
573 if (text != NULL)
574 mti->text = xstrdup(text);
576 saved = mode_tree_find_item(&mtd->saved, tag);
577 if (saved != NULL) {
578 if (parent == NULL || parent->expanded)
579 mti->tagged = saved->tagged;
580 mti->expanded = saved->expanded;
581 } else if (expanded == -1)
582 mti->expanded = 1;
583 else
584 mti->expanded = expanded;
586 TAILQ_INIT(&mti->children);
588 if (parent != NULL)
589 TAILQ_INSERT_TAIL(&parent->children, mti, entry);
590 else
591 TAILQ_INSERT_TAIL(&mtd->children, mti, entry);
593 return (mti);
596 void
597 mode_tree_draw_as_parent(struct mode_tree_item *mti)
599 mti->draw_as_parent = 1;
602 void
603 mode_tree_no_tag(struct mode_tree_item *mti)
605 mti->no_tag = 1;
608 void
609 mode_tree_remove(struct mode_tree_data *mtd, struct mode_tree_item *mti)
611 struct mode_tree_item *parent = mti->parent;
613 if (parent != NULL)
614 TAILQ_REMOVE(&parent->children, mti, entry);
615 else
616 TAILQ_REMOVE(&mtd->children, mti, entry);
617 mode_tree_free_item(mti);
620 void
621 mode_tree_draw(struct mode_tree_data *mtd)
623 struct window_pane *wp = mtd->wp;
624 struct screen *s = &mtd->screen;
625 struct mode_tree_line *line;
626 struct mode_tree_item *mti;
627 struct options *oo = wp->window->options;
628 struct screen_write_ctx ctx;
629 struct grid_cell gc0, gc;
630 u_int w, h, i, j, sy, box_x, box_y, width;
631 char *text, *start, *key;
632 const char *tag, *symbol;
633 size_t size, n;
634 int keylen, pad;
636 if (mtd->line_size == 0)
637 return;
639 memcpy(&gc0, &grid_default_cell, sizeof gc0);
640 memcpy(&gc, &grid_default_cell, sizeof gc);
641 style_apply(&gc, oo, "mode-style", NULL);
643 w = mtd->width;
644 h = mtd->height;
646 screen_write_start(&ctx, s);
647 screen_write_clearscreen(&ctx, 8);
649 keylen = 0;
650 for (i = 0; i < mtd->line_size; i++) {
651 mti = mtd->line_list[i].item;
652 if (mti->key == KEYC_NONE)
653 continue;
654 if ((int)mti->keylen + 3 > keylen)
655 keylen = mti->keylen + 3;
658 for (i = 0; i < mtd->line_size; i++) {
659 if (i < mtd->offset)
660 continue;
661 if (i > mtd->offset + h - 1)
662 break;
663 line = &mtd->line_list[i];
664 mti = line->item;
666 screen_write_cursormove(&ctx, 0, i - mtd->offset, 0);
668 pad = keylen - 2 - mti->keylen;
669 if (mti->key != KEYC_NONE)
670 xasprintf(&key, "(%s)%*s", mti->keystr, pad, "");
671 else
672 key = xstrdup("");
674 if (line->flat)
675 symbol = "";
676 else if (TAILQ_EMPTY(&mti->children))
677 symbol = " ";
678 else if (mti->expanded)
679 symbol = "- ";
680 else
681 symbol = "+ ";
683 if (line->depth == 0)
684 start = xstrdup(symbol);
685 else {
686 size = (4 * line->depth) + 32;
688 start = xcalloc(1, size);
689 for (j = 1; j < line->depth; j++) {
690 if (mti->parent != NULL &&
691 mtd->line_list[mti->parent->line].last)
692 strlcat(start, " ", size);
693 else
694 strlcat(start, "\001x\001 ", size);
696 if (line->last)
697 strlcat(start, "\001mq\001> ", size);
698 else
699 strlcat(start, "\001tq\001> ", size);
700 strlcat(start, symbol, size);
703 if (mti->tagged)
704 tag = "*";
705 else
706 tag = "";
707 xasprintf(&text, "%-*s%s%s%s%s", keylen, key, start, mti->name,
708 tag, (mti->text != NULL) ? ": " : "" );
709 width = utf8_cstrwidth(text);
710 if (width > w)
711 width = w;
712 free(start);
714 if (mti->tagged) {
715 gc.attr ^= GRID_ATTR_BRIGHT;
716 gc0.attr ^= GRID_ATTR_BRIGHT;
719 if (i != mtd->current) {
720 screen_write_clearendofline(&ctx, 8);
721 screen_write_nputs(&ctx, w, &gc0, "%s", text);
722 if (mti->text != NULL) {
723 format_draw(&ctx, &gc0, w - width, mti->text,
724 NULL, 0);
726 } else {
727 screen_write_clearendofline(&ctx, gc.bg);
728 screen_write_nputs(&ctx, w, &gc, "%s", text);
729 if (mti->text != NULL) {
730 format_draw(&ctx, &gc, w - width, mti->text,
731 NULL, 0);
734 free(text);
735 free(key);
737 if (mti->tagged) {
738 gc.attr ^= GRID_ATTR_BRIGHT;
739 gc0.attr ^= GRID_ATTR_BRIGHT;
743 sy = screen_size_y(s);
744 if (!mtd->preview || sy <= 4 || h <= 4 || sy - h <= 4 || w <= 4)
745 goto done;
747 line = &mtd->line_list[mtd->current];
748 mti = line->item;
749 if (mti->draw_as_parent)
750 mti = mti->parent;
752 screen_write_cursormove(&ctx, 0, h, 0);
753 screen_write_box(&ctx, w, sy - h, BOX_LINES_DEFAULT, NULL, NULL);
755 if (mtd->sort_list != NULL) {
756 xasprintf(&text, " %s (sort: %s%s)", mti->name,
757 mtd->sort_list[mtd->sort_crit.field],
758 mtd->sort_crit.reversed ? ", reversed" : "");
759 } else
760 xasprintf(&text, " %s", mti->name);
761 if (w - 2 >= strlen(text)) {
762 screen_write_cursormove(&ctx, 1, h, 0);
763 screen_write_puts(&ctx, &gc0, "%s", text);
765 if (mtd->no_matches)
766 n = (sizeof "no matches") - 1;
767 else
768 n = (sizeof "active") - 1;
769 if (mtd->filter != NULL && w - 2 >= strlen(text) + 10 + n + 2) {
770 screen_write_puts(&ctx, &gc0, " (filter: ");
771 if (mtd->no_matches)
772 screen_write_puts(&ctx, &gc, "no matches");
773 else
774 screen_write_puts(&ctx, &gc0, "active");
775 screen_write_puts(&ctx, &gc0, ") ");
776 } else
777 screen_write_puts(&ctx, &gc0, " ");
779 free(text);
781 box_x = w - 4;
782 box_y = sy - h - 2;
784 if (box_x != 0 && box_y != 0) {
785 screen_write_cursormove(&ctx, 2, h + 1, 0);
786 mtd->drawcb(mtd->modedata, mti->itemdata, &ctx, box_x, box_y);
789 done:
790 screen_write_cursormove(&ctx, 0, mtd->current - mtd->offset, 0);
791 screen_write_stop(&ctx);
794 static struct mode_tree_item *
795 mode_tree_search_backward(struct mode_tree_data *mtd)
797 struct mode_tree_item *mti, *last, *prev;
799 if (mtd->search == NULL)
800 return (NULL);
802 mti = last = mtd->line_list[mtd->current].item;
803 for (;;) {
804 if ((prev = TAILQ_PREV(mti, mode_tree_list, entry)) != NULL) {
805 /* Point to the last child in the previous subtree. */
806 while (!TAILQ_EMPTY(&prev->children))
807 prev = TAILQ_LAST(&prev->children, mode_tree_list);
808 mti = prev;
809 } else {
810 /* If prev is NULL, jump to the parent. */
811 mti = mti->parent;
814 if (mti == NULL) {
815 /* Point to the last child in the last root subtree. */
816 prev = TAILQ_LAST(&mtd->children, mode_tree_list);
817 while (!TAILQ_EMPTY(&prev->children))
818 prev = TAILQ_LAST(&prev->children, mode_tree_list);
819 mti = prev;
821 if (mti == last)
822 break;
824 if (mtd->searchcb == NULL) {
825 if (strstr(mti->name, mtd->search) != NULL)
826 return (mti);
827 continue;
829 if (mtd->searchcb(mtd->modedata, mti->itemdata, mtd->search))
830 return (mti);
832 return (NULL);
836 static struct mode_tree_item *
837 mode_tree_search_forward(struct mode_tree_data *mtd)
839 struct mode_tree_item *mti, *last, *next;
841 if (mtd->search == NULL)
842 return (NULL);
844 mti = last = mtd->line_list[mtd->current].item;
845 for (;;) {
846 if (!TAILQ_EMPTY(&mti->children))
847 mti = TAILQ_FIRST(&mti->children);
848 else if ((next = TAILQ_NEXT(mti, entry)) != NULL)
849 mti = next;
850 else {
851 for (;;) {
852 mti = mti->parent;
853 if (mti == NULL)
854 break;
855 if ((next = TAILQ_NEXT(mti, entry)) != NULL) {
856 mti = next;
857 break;
861 if (mti == NULL)
862 mti = TAILQ_FIRST(&mtd->children);
863 if (mti == last)
864 break;
866 if (mtd->searchcb == NULL) {
867 if (strstr(mti->name, mtd->search) != NULL)
868 return (mti);
869 continue;
871 if (mtd->searchcb(mtd->modedata, mti->itemdata, mtd->search))
872 return (mti);
874 return (NULL);
877 static void
878 mode_tree_search_set(struct mode_tree_data *mtd)
880 struct mode_tree_item *mti, *loop;
881 uint64_t tag;
883 if (mtd->search_dir == MODE_TREE_SEARCH_FORWARD)
884 mti = mode_tree_search_forward(mtd);
885 else
886 mti = mode_tree_search_backward(mtd);
887 if (mti == NULL)
888 return;
889 tag = mti->tag;
891 loop = mti->parent;
892 while (loop != NULL) {
893 loop->expanded = 1;
894 loop = loop->parent;
897 mode_tree_build(mtd);
898 mode_tree_set_current(mtd, tag);
899 mode_tree_draw(mtd);
900 mtd->wp->flags |= PANE_REDRAW;
903 static int
904 mode_tree_search_callback(__unused struct client *c, void *data, const char *s,
905 __unused int done)
907 struct mode_tree_data *mtd = data;
909 if (mtd->dead)
910 return (0);
912 free(mtd->search);
913 if (s == NULL || *s == '\0') {
914 mtd->search = NULL;
915 return (0);
917 mtd->search = xstrdup(s);
918 mode_tree_search_set(mtd);
920 return (0);
923 static void
924 mode_tree_search_free(void *data)
926 mode_tree_remove_ref(data);
929 static int
930 mode_tree_filter_callback(__unused struct client *c, void *data, const char *s,
931 __unused int done)
933 struct mode_tree_data *mtd = data;
935 if (mtd->dead)
936 return (0);
938 if (mtd->filter != NULL)
939 free(mtd->filter);
940 if (s == NULL || *s == '\0')
941 mtd->filter = NULL;
942 else
943 mtd->filter = xstrdup(s);
945 mode_tree_build(mtd);
946 mode_tree_draw(mtd);
947 mtd->wp->flags |= PANE_REDRAW;
949 return (0);
952 static void
953 mode_tree_filter_free(void *data)
955 mode_tree_remove_ref(data);
958 static void
959 mode_tree_menu_callback(__unused struct menu *menu, __unused u_int idx,
960 key_code key, void *data)
962 struct mode_tree_menu *mtm = data;
963 struct mode_tree_data *mtd = mtm->data;
965 if (mtd->dead || key == KEYC_NONE)
966 goto out;
968 if (mtm->line >= mtd->line_size)
969 goto out;
970 mtd->current = mtm->line;
971 mtd->menucb(mtd->modedata, mtm->c, key);
973 out:
974 mode_tree_remove_ref(mtd);
975 free(mtm);
978 static void
979 mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x,
980 u_int y, int outside)
982 struct mode_tree_item *mti;
983 struct menu *menu;
984 const struct menu_item *items;
985 struct mode_tree_menu *mtm;
986 char *title;
987 u_int line;
989 if (mtd->offset + y > mtd->line_size - 1)
990 line = mtd->current;
991 else
992 line = mtd->offset + y;
993 mti = mtd->line_list[line].item;
995 if (!outside) {
996 items = mtd->menu;
997 xasprintf(&title, "#[align=centre]%s", mti->name);
998 } else {
999 items = mode_tree_menu_items;
1000 title = xstrdup("");
1002 menu = menu_create(title);
1003 menu_add_items(menu, items, NULL, c, NULL);
1004 free(title);
1006 mtm = xmalloc(sizeof *mtm);
1007 mtm->data = mtd;
1008 mtm->c = c;
1009 mtm->line = line;
1010 mtd->references++;
1012 if (x >= (menu->width + 4) / 2)
1013 x -= (menu->width + 4) / 2;
1014 else
1015 x = 0;
1016 if (menu_display(menu, 0, 0, NULL, x, y, c, BOX_LINES_DEFAULT, NULL,
1017 NULL, NULL, NULL, mode_tree_menu_callback, mtm) != 0)
1018 menu_free(menu);
1022 mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
1023 struct mouse_event *m, u_int *xp, u_int *yp)
1025 struct mode_tree_line *line;
1026 struct mode_tree_item *current, *parent, *mti;
1027 u_int i, x, y;
1028 int choice;
1030 if (KEYC_IS_MOUSE(*key) && m != NULL) {
1031 if (cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) {
1032 *key = KEYC_NONE;
1033 return (0);
1035 if (xp != NULL)
1036 *xp = x;
1037 if (yp != NULL)
1038 *yp = y;
1039 if (x > mtd->width || y > mtd->height) {
1040 if (*key == KEYC_MOUSEDOWN3_PANE)
1041 mode_tree_display_menu(mtd, c, x, y, 1);
1042 if (!mtd->preview)
1043 *key = KEYC_NONE;
1044 return (0);
1046 if (mtd->offset + y < mtd->line_size) {
1047 if (*key == KEYC_MOUSEDOWN1_PANE ||
1048 *key == KEYC_MOUSEDOWN3_PANE ||
1049 *key == KEYC_DOUBLECLICK1_PANE)
1050 mtd->current = mtd->offset + y;
1051 if (*key == KEYC_DOUBLECLICK1_PANE)
1052 *key = '\r';
1053 else {
1054 if (*key == KEYC_MOUSEDOWN3_PANE)
1055 mode_tree_display_menu(mtd, c, x, y, 0);
1056 *key = KEYC_NONE;
1058 } else {
1059 if (*key == KEYC_MOUSEDOWN3_PANE)
1060 mode_tree_display_menu(mtd, c, x, y, 0);
1061 *key = KEYC_NONE;
1063 return (0);
1066 line = &mtd->line_list[mtd->current];
1067 current = line->item;
1069 choice = -1;
1070 for (i = 0; i < mtd->line_size; i++) {
1071 if (*key == mtd->line_list[i].item->key) {
1072 choice = i;
1073 break;
1076 if (choice != -1) {
1077 if ((u_int)choice > mtd->line_size - 1) {
1078 *key = KEYC_NONE;
1079 return (0);
1081 mtd->current = choice;
1082 *key = '\r';
1083 return (0);
1086 switch (*key) {
1087 case 'q':
1088 case '\033': /* Escape */
1089 case '\007': /* C-g */
1090 return (1);
1091 case KEYC_UP:
1092 case 'k':
1093 case KEYC_WHEELUP_PANE:
1094 case '\020': /* C-p */
1095 mode_tree_up(mtd, 1);
1096 break;
1097 case KEYC_DOWN:
1098 case 'j':
1099 case KEYC_WHEELDOWN_PANE:
1100 case '\016': /* C-n */
1101 mode_tree_down(mtd, 1);
1102 break;
1103 case KEYC_PPAGE:
1104 case '\002': /* C-b */
1105 for (i = 0; i < mtd->height; i++) {
1106 if (mtd->current == 0)
1107 break;
1108 mode_tree_up(mtd, 1);
1110 break;
1111 case KEYC_NPAGE:
1112 case '\006': /* C-f */
1113 for (i = 0; i < mtd->height; i++) {
1114 if (mtd->current == mtd->line_size - 1)
1115 break;
1116 mode_tree_down(mtd, 1);
1118 break;
1119 case 'g':
1120 case KEYC_HOME:
1121 mtd->current = 0;
1122 mtd->offset = 0;
1123 break;
1124 case 'G':
1125 case KEYC_END:
1126 mtd->current = mtd->line_size - 1;
1127 if (mtd->current > mtd->height - 1)
1128 mtd->offset = mtd->current - mtd->height + 1;
1129 else
1130 mtd->offset = 0;
1131 break;
1132 case 't':
1134 * Do not allow parents and children to both be tagged: untag
1135 * all parents and children of current.
1137 if (current->no_tag)
1138 break;
1139 if (!current->tagged) {
1140 parent = current->parent;
1141 while (parent != NULL) {
1142 parent->tagged = 0;
1143 parent = parent->parent;
1145 mode_tree_clear_tagged(&current->children);
1146 current->tagged = 1;
1147 } else
1148 current->tagged = 0;
1149 if (m != NULL)
1150 mode_tree_down(mtd, 0);
1151 break;
1152 case 'T':
1153 for (i = 0; i < mtd->line_size; i++)
1154 mtd->line_list[i].item->tagged = 0;
1155 break;
1156 case '\024': /* C-t */
1157 for (i = 0; i < mtd->line_size; i++) {
1158 if ((mtd->line_list[i].item->parent == NULL &&
1159 !mtd->line_list[i].item->no_tag) ||
1160 (mtd->line_list[i].item->parent != NULL &&
1161 mtd->line_list[i].item->parent->no_tag))
1162 mtd->line_list[i].item->tagged = 1;
1163 else
1164 mtd->line_list[i].item->tagged = 0;
1166 break;
1167 case 'O':
1168 mtd->sort_crit.field++;
1169 if (mtd->sort_crit.field >= mtd->sort_size)
1170 mtd->sort_crit.field = 0;
1171 mode_tree_build(mtd);
1172 break;
1173 case 'r':
1174 mtd->sort_crit.reversed = !mtd->sort_crit.reversed;
1175 mode_tree_build(mtd);
1176 break;
1177 case KEYC_LEFT:
1178 case 'h':
1179 case '-':
1180 if (line->flat || !current->expanded)
1181 current = current->parent;
1182 if (current == NULL)
1183 mode_tree_up(mtd, 0);
1184 else {
1185 current->expanded = 0;
1186 mtd->current = current->line;
1187 mode_tree_build(mtd);
1189 break;
1190 case KEYC_RIGHT:
1191 case 'l':
1192 case '+':
1193 if (line->flat || current->expanded)
1194 mode_tree_down(mtd, 0);
1195 else if (!line->flat) {
1196 current->expanded = 1;
1197 mode_tree_build(mtd);
1199 break;
1200 case '-'|KEYC_META:
1201 TAILQ_FOREACH(mti, &mtd->children, entry)
1202 mti->expanded = 0;
1203 mode_tree_build(mtd);
1204 break;
1205 case '+'|KEYC_META:
1206 TAILQ_FOREACH(mti, &mtd->children, entry)
1207 mti->expanded = 1;
1208 mode_tree_build(mtd);
1209 break;
1210 case '?':
1211 case '/':
1212 case '\023': /* C-s */
1213 mtd->references++;
1214 status_prompt_set(c, NULL, "(search) ", "",
1215 mode_tree_search_callback, mode_tree_search_free, mtd,
1216 PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH);
1217 break;
1218 case 'n':
1219 mtd->search_dir = MODE_TREE_SEARCH_FORWARD;
1220 mode_tree_search_set(mtd);
1221 break;
1222 case 'N':
1223 mtd->search_dir = MODE_TREE_SEARCH_BACKWARD;
1224 mode_tree_search_set(mtd);
1225 break;
1226 case 'f':
1227 mtd->references++;
1228 status_prompt_set(c, NULL, "(filter) ", mtd->filter,
1229 mode_tree_filter_callback, mode_tree_filter_free, mtd,
1230 PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH);
1231 break;
1232 case 'v':
1233 mtd->preview = !mtd->preview;
1234 mode_tree_build(mtd);
1235 if (mtd->preview)
1236 mode_tree_check_selected(mtd);
1237 break;
1239 return (0);
1242 void
1243 mode_tree_run_command(struct client *c, struct cmd_find_state *fs,
1244 const char *template, const char *name)
1246 struct cmdq_state *state;
1247 char *command, *error;
1248 enum cmd_parse_status status;
1250 command = cmd_template_replace(template, name, 1);
1251 if (command != NULL && *command != '\0') {
1252 state = cmdq_new_state(fs, NULL, 0);
1253 status = cmd_parse_and_append(command, NULL, c, state, &error);
1254 if (status == CMD_PARSE_ERROR) {
1255 if (c != NULL) {
1256 *error = toupper((u_char)*error);
1257 status_message_set(c, -1, 1, 0, "%s", error);
1259 free(error);
1261 cmdq_free_state(state);
1263 free(command);