Annotation of src/usr.bin/tmux/mode-tree.c, Revision 1.36
1.36 ! nicm 1: /* $OpenBSD: mode-tree.c,v 1.35 2019/07/19 07:20:51 nicm Exp $ */
1.1 nicm 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: struct mode_tree_item;
29: TAILQ_HEAD(mode_tree_list, mode_tree_item);
30:
31: struct mode_tree_data {
1.3 nicm 32: int dead;
33: u_int references;
1.23 nicm 34: int zoomed;
1.3 nicm 35:
1.1 nicm 36: struct window_pane *wp;
37: void *modedata;
1.33 nicm 38: const struct menu_item *menu;
1.1 nicm 39:
40: const char **sort_list;
41: u_int sort_size;
1.36 ! nicm 42: struct mode_tree_sort_criteria sort_crit;
1.1 nicm 43:
1.17 nicm 44: mode_tree_build_cb buildcb;
45: mode_tree_draw_cb drawcb;
46: mode_tree_search_cb searchcb;
1.28 nicm 47: mode_tree_menu_cb menucb;
1.1 nicm 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;
1.3 nicm 64:
1.9 nicm 65: int preview;
1.6 nicm 66: char *search;
67: char *filter;
1.21 nicm 68: int no_matches;
1.1 nicm 69: };
70:
71: struct 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:
87: struct mode_tree_line {
88: struct mode_tree_item *item;
89: u_int depth;
90: int last;
91: int flat;
92: };
93:
1.28 nicm 94: struct mode_tree_menu {
95: struct mode_tree_data *data;
96: struct client *c;
97: u_int line;
98: void *itemdata;
99: };
100:
1.1 nicm 101: static void mode_tree_free_items(struct mode_tree_list *);
102:
1.33 nicm 103: static 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: };
1.28 nicm 111:
1.1 nicm 112: static struct mode_tree_item *
113: mode_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:
127: static void
128: mode_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:
138: static void
139: mode_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:
149: static void
1.11 nicm 150: mode_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:
160: static void
1.1 nicm 161: mode_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:
168: static void
169: mode_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:
202: static void
203: mode_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:
1.24 nicm 213: static void
1.1 nicm 214: mode_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:
229: void
230: mode_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:
244: void *
245: mode_tree_get_current(struct mode_tree_data *mtd)
246: {
247: return (mtd->line_list[mtd->current].item->itemdata);
248: }
249:
1.19 nicm 250: void
251: mode_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:
259: void
260: mode_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:
1.1 nicm 280: u_int
281: mode_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:
295: void
1.17 nicm 296: mode_tree_each_tagged(struct mode_tree_data *mtd, mode_tree_each_cb cb,
297: struct client *c, key_code key, int current)
1.1 nicm 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;
1.14 nicm 308: cb(mtd->modedata, mti->itemdata, c, key);
1.1 nicm 309: }
310: }
311: if (!fired && current) {
312: mti = mtd->line_list[mtd->current].item;
1.14 nicm 313: cb(mtd->modedata, mti->itemdata, c, key);
1.1 nicm 314: }
315: }
316:
317: struct mode_tree_data *
1.5 nicm 318: mode_tree_start(struct window_pane *wp, struct args *args,
1.17 nicm 319: mode_tree_build_cb buildcb, mode_tree_draw_cb drawcb,
1.28 nicm 320: mode_tree_search_cb searchcb, mode_tree_menu_cb menucb, void *modedata,
1.33 nicm 321: const struct menu_item *menu, const char **sort_list, u_int sort_size,
1.28 nicm 322: struct screen **s)
1.1 nicm 323: {
324: struct mode_tree_data *mtd;
1.5 nicm 325: const char *sort;
326: u_int i;
1.1 nicm 327:
328: mtd = xcalloc(1, sizeof *mtd);
1.3 nicm 329: mtd->references = 1;
330:
1.1 nicm 331: mtd->wp = wp;
332: mtd->modedata = modedata;
1.28 nicm 333: mtd->menu = menu;
1.1 nicm 334:
335: mtd->sort_list = sort_list;
336: mtd->sort_size = sort_size;
1.5 nicm 337:
1.9 nicm 338: mtd->preview = !args_has(args, 'N');
339:
1.5 nicm 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)
1.36 ! nicm 344: mtd->sort_crit.field = i;
1.5 nicm 345: }
346: }
1.36 ! nicm 347: mtd->sort_crit.reversed = args_has(args, 'r');
1.1 nicm 348:
1.6 nicm 349: if (args_has(args, 'f'))
350: mtd->filter = xstrdup(args_get(args, 'f'));
351: else
352: mtd->filter = NULL;
353:
1.1 nicm 354: mtd->buildcb = buildcb;
355: mtd->drawcb = drawcb;
1.3 nicm 356: mtd->searchcb = searchcb;
1.28 nicm 357: mtd->menucb = menucb;
1.1 nicm 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:
368: void
1.23 nicm 369: mode_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:
381: void
1.1 nicm 382: mode_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
1.32 nicm 390: tag = UINT64_MAX;
1.1 nicm 391:
392: TAILQ_CONCAT(&mtd->saved, &mtd->children, entry);
393: TAILQ_INIT(&mtd->children);
394:
1.36 ! nicm 395: mtd->buildcb(mtd->modedata, &mtd->sort_crit, &tag, mtd->filter);
1.21 nicm 396: mtd->no_matches = TAILQ_EMPTY(&mtd->children);
397: if (mtd->no_matches)
1.36 ! nicm 398: mtd->buildcb(mtd->modedata, &mtd->sort_crit, &tag, NULL);
1.1 nicm 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:
1.32 nicm 406: if (tag == UINT64_MAX)
407: tag = mtd->line_list[mtd->current].item->tag;
1.3 nicm 408: mode_tree_set_current(mtd, tag);
1.1 nicm 409:
410: mtd->width = screen_size_x(s);
1.9 nicm 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
1.1 nicm 420: mtd->height = screen_size_y(s);
1.11 nicm 421: mode_tree_check_selected(mtd);
1.1 nicm 422: }
423:
1.3 nicm 424: static void
425: mode_tree_remove_ref(struct mode_tree_data *mtd)
426: {
427: if (--mtd->references == 0)
428: free(mtd);
429: }
430:
1.1 nicm 431: void
432: mode_tree_free(struct mode_tree_data *mtd)
433: {
1.23 nicm 434: struct window_pane *wp = mtd->wp;
435:
436: if (mtd->zoomed == 0)
437: server_unzoom_window(wp->window);
438:
1.1 nicm 439: mode_tree_free_items(&mtd->children);
440: mode_tree_clear_lines(mtd);
441: screen_free(&mtd->screen);
1.3 nicm 442:
1.6 nicm 443: free(mtd->search);
444: free(mtd->filter);
445:
1.3 nicm 446: mtd->dead = 1;
447: mode_tree_remove_ref(mtd);
1.1 nicm 448: }
449:
450: void
451: mode_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:
463: struct mode_tree_item *
464: mode_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) {
1.34 nicm 483: if (parent == NULL || parent->expanded)
1.1 nicm 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:
501: void
502: mode_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:
513: void
514: mode_tree_draw(struct mode_tree_data *mtd)
515: {
516: struct window_pane *wp = mtd->wp;
1.17 nicm 517: struct screen *s = &mtd->screen;
1.1 nicm 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;
1.27 nicm 523: u_int w, h, i, j, sy, box_x, box_y, width;
1.1 nicm 524: char *text, *start, key[7];
525: const char *tag, *symbol;
1.21 nicm 526: size_t size, n;
1.1 nicm 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:
1.26 nicm 556: screen_write_cursormove(&ctx, 0, i - mtd->offset, 0);
1.1 nicm 557:
558: if (i < 10)
1.8 nicm 559: snprintf(key, sizeof key, "(%c) ", '0' + i);
1.1 nicm 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 = "";
1.27 nicm 598: xasprintf(&text, "%-*s%s%s%s: ", keylen, key, start, mti->name,
599: tag);
600: width = utf8_cstrwidth(text);
1.1 nicm 601: free(start);
602:
603: if (mti->tagged) {
604: gc.attr ^= GRID_ATTR_BRIGHT;
605: gc0.attr ^= GRID_ATTR_BRIGHT;
606: }
607:
608: if (i != mtd->current) {
609: screen_write_clearendofline(&ctx, 8);
1.27 nicm 610: screen_write_puts(&ctx, &gc0, "%s", text);
611: format_draw(&ctx, &gc0, w - width, mti->text, NULL);
1.13 nicm 612: } else {
613: screen_write_clearendofline(&ctx, gc.bg);
1.27 nicm 614: screen_write_puts(&ctx, &gc, "%s", text);
615: format_draw(&ctx, &gc, w - width, mti->text, NULL);
1.13 nicm 616: }
1.1 nicm 617: free(text);
618:
619: if (mti->tagged) {
620: gc.attr ^= GRID_ATTR_BRIGHT;
621: gc0.attr ^= GRID_ATTR_BRIGHT;
622: }
623: }
624:
625: sy = screen_size_y(s);
1.9 nicm 626: if (!mtd->preview || sy <= 4 || h <= 4 || sy - h <= 4 || w <= 4) {
1.1 nicm 627: screen_write_stop(&ctx);
628: return;
629: }
630:
631: line = &mtd->line_list[mtd->current];
632: mti = line->item;
633:
1.26 nicm 634: screen_write_cursormove(&ctx, 0, h, 0);
1.1 nicm 635: screen_write_box(&ctx, w, sy - h);
636:
1.36 ! nicm 637: xasprintf(&text, " %s (sort: %s%s)", mti->name,
! 638: mtd->sort_list[mtd->sort_crit.field],
! 639: mtd->sort_crit.reversed ? ", reversed" : "");
1.1 nicm 640: if (w - 2 >= strlen(text)) {
1.26 nicm 641: screen_write_cursormove(&ctx, 1, h, 0);
1.1 nicm 642: screen_write_puts(&ctx, &gc0, "%s", text);
1.21 nicm 643:
644: if (mtd->no_matches)
645: n = (sizeof "no matches") - 1;
646: else
647: n = (sizeof "active") - 1;
648: if (mtd->filter != NULL && w - 2 >= strlen(text) + 10 + n + 2) {
649: screen_write_puts(&ctx, &gc0, " (filter: ");
650: if (mtd->no_matches)
651: screen_write_puts(&ctx, &gc, "no matches");
652: else
653: screen_write_puts(&ctx, &gc0, "active");
654: screen_write_puts(&ctx, &gc0, ") ");
655: }
1.1 nicm 656: }
657: free(text);
658:
659: box_x = w - 4;
660: box_y = sy - h - 2;
661:
1.17 nicm 662: if (box_x != 0 && box_y != 0) {
1.26 nicm 663: screen_write_cursormove(&ctx, 2, h + 1, 0);
1.17 nicm 664: mtd->drawcb(mtd->modedata, mti->itemdata, &ctx, box_x, box_y);
1.1 nicm 665: }
666:
667: screen_write_stop(&ctx);
668: }
669:
1.3 nicm 670: static struct mode_tree_item *
671: mode_tree_search_for(struct mode_tree_data *mtd)
672: {
673: struct mode_tree_item *mti, *last, *next;
674:
1.6 nicm 675: if (mtd->search == NULL)
1.3 nicm 676: return (NULL);
677:
678: mti = last = mtd->line_list[mtd->current].item;
679: for (;;) {
680: if (!TAILQ_EMPTY(&mti->children))
681: mti = TAILQ_FIRST(&mti->children);
682: else if ((next = TAILQ_NEXT(mti, entry)) != NULL)
683: mti = next;
684: else {
685: for (;;) {
686: mti = mti->parent;
687: if (mti == NULL)
688: break;
689: if ((next = TAILQ_NEXT(mti, entry)) != NULL) {
690: mti = next;
691: break;
692: }
693: }
694: }
695: if (mti == NULL)
696: mti = TAILQ_FIRST(&mtd->children);
697: if (mti == last)
698: break;
699:
700: if (mtd->searchcb == NULL) {
1.6 nicm 701: if (strstr(mti->name, mtd->search) != NULL)
1.3 nicm 702: return (mti);
703: continue;
704: }
1.6 nicm 705: if (mtd->searchcb(mtd->modedata, mti->itemdata, mtd->search))
1.4 nicm 706: return (mti);
1.3 nicm 707: }
708: return (NULL);
709: }
710:
711: static void
712: mode_tree_search_set(struct mode_tree_data *mtd)
713: {
714: struct mode_tree_item *mti, *loop;
715: uint64_t tag;
716:
717: mti = mode_tree_search_for(mtd);
718: if (mti == NULL)
719: return;
720: tag = mti->tag;
721:
722: loop = mti->parent;
723: while (loop != NULL) {
724: loop->expanded = 1;
725: loop = loop->parent;
726: }
1.6 nicm 727:
1.3 nicm 728: mode_tree_build(mtd);
729: mode_tree_set_current(mtd, tag);
730: mode_tree_draw(mtd);
1.6 nicm 731: mtd->wp->flags |= PANE_REDRAW;
1.3 nicm 732: }
733:
734: static int
735: mode_tree_search_callback(__unused struct client *c, void *data, const char *s,
736: __unused int done)
737: {
738: struct mode_tree_data *mtd = data;
739:
740: if (mtd->dead)
741: return (0);
742:
1.6 nicm 743: free(mtd->search);
744: if (s == NULL || *s == '\0') {
745: mtd->search = NULL;
1.3 nicm 746: return (0);
747: }
1.6 nicm 748: mtd->search = xstrdup(s);
1.3 nicm 749: mode_tree_search_set(mtd);
750:
751: return (0);
752: }
753:
754: static void
755: mode_tree_search_free(void *data)
756: {
757: mode_tree_remove_ref(data);
758: }
759:
1.6 nicm 760: static int
761: mode_tree_filter_callback(__unused struct client *c, void *data, const char *s,
762: __unused int done)
763: {
764: struct mode_tree_data *mtd = data;
765:
766: if (mtd->dead)
767: return (0);
768:
769: if (mtd->filter != NULL)
770: free(mtd->filter);
771: if (s == NULL || *s == '\0')
772: mtd->filter = NULL;
773: else
774: mtd->filter = xstrdup(s);
775:
776: mode_tree_build(mtd);
777: mode_tree_draw(mtd);
778: mtd->wp->flags |= PANE_REDRAW;
779:
780: return (0);
781: }
782:
783: static void
784: mode_tree_filter_free(void *data)
785: {
786: mode_tree_remove_ref(data);
787: }
788:
1.28 nicm 789: static void
790: mode_tree_menu_callback(__unused struct menu *menu, __unused u_int idx,
791: key_code key, void *data)
792: {
793: struct mode_tree_menu *mtm = data;
794: struct mode_tree_data *mtd = mtm->data;
795: struct mode_tree_item *mti;
796:
797: if (mtd->dead || key == KEYC_NONE)
798: goto out;
799:
800: if (mtm->line >= mtd->line_size)
801: goto out;
802: mti = mtd->line_list[mtm->line].item;
803: if (mti->itemdata != mtm->itemdata)
804: goto out;
805: mtd->current = mtm->line;
806: mtd->menucb (mtd->modedata, mtm->c, key);
807:
808: out:
809: mode_tree_remove_ref(mtd);
810: free(mtm);
811: }
812:
813: static void
814: mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x,
815: u_int y, int outside)
816: {
817: struct mode_tree_item *mti;
818: struct menu *menu;
1.33 nicm 819: const struct menu_item *items;
1.28 nicm 820: struct mode_tree_menu *mtm;
821: char *title;
822: u_int line;
823:
824: if (mtd->offset + y > mtd->line_size - 1)
825: line = mtd->current;
826: else
827: line = mtd->offset + y;
1.29 nicm 828: mti = mtd->line_list[line].item;
1.28 nicm 829:
830: if (!outside) {
1.33 nicm 831: items = mtd->menu;
1.28 nicm 832: xasprintf(&title, "#[align=centre]%s", mti->name);
833: } else {
1.33 nicm 834: items = mode_tree_menu_items;
1.28 nicm 835: title = xstrdup("");
836: }
1.33 nicm 837: menu = menu_create(title);
838: menu_add_items(menu, items, NULL, NULL, NULL);
1.28 nicm 839: free(title);
840:
841: mtm = xmalloc(sizeof *mtm);
842: mtm->data = mtd;
843: mtm->c = c;
844: mtm->line = line;
845: mtm->itemdata = mti->itemdata;
846: mtd->references++;
847:
848: if (menu_display(menu, 0, NULL, x, y, c, NULL, mode_tree_menu_callback,
849: mtm) != 0)
850: menu_free(menu);
851: }
852:
1.1 nicm 853: int
1.3 nicm 854: mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
1.19 nicm 855: struct mouse_event *m, u_int *xp, u_int *yp)
1.1 nicm 856: {
857: struct mode_tree_line *line;
858: struct mode_tree_item *current, *parent;
859: u_int i, x, y;
860: int choice;
861: key_code tmp;
862:
1.28 nicm 863: if (KEYC_IS_MOUSE(*key) && m != NULL) {
1.1 nicm 864: if (cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) {
865: *key = KEYC_NONE;
866: return (0);
867: }
1.19 nicm 868: if (xp != NULL)
869: *xp = x;
870: if (yp != NULL)
871: *yp = y;
1.1 nicm 872: if (x > mtd->width || y > mtd->height) {
1.28 nicm 873: if (*key == KEYC_MOUSEDOWN3_PANE)
874: mode_tree_display_menu(mtd, c, x, y, 1);
1.20 nicm 875: if (!mtd->preview)
1.19 nicm 876: *key = KEYC_NONE;
1.1 nicm 877: return (0);
878: }
879: if (mtd->offset + y < mtd->line_size) {
1.18 nicm 880: if (*key == KEYC_MOUSEDOWN1_PANE ||
1.28 nicm 881: *key == KEYC_MOUSEDOWN3_PANE ||
1.18 nicm 882: *key == KEYC_DOUBLECLICK1_PANE)
883: mtd->current = mtd->offset + y;
884: if (*key == KEYC_DOUBLECLICK1_PANE)
885: *key = '\r';
1.28 nicm 886: else {
887: if (*key == KEYC_MOUSEDOWN3_PANE)
888: mode_tree_display_menu(mtd, c, x, y, 0);
1.20 nicm 889: *key = KEYC_NONE;
1.28 nicm 890: }
891: } else {
892: if (*key == KEYC_MOUSEDOWN3_PANE)
893: mode_tree_display_menu(mtd, c, x, y, 0);
1.20 nicm 894: *key = KEYC_NONE;
1.28 nicm 895: }
1.20 nicm 896: return (0);
1.1 nicm 897: }
898:
899: line = &mtd->line_list[mtd->current];
900: current = line->item;
901:
902: choice = -1;
903: if (*key >= '0' && *key <= '9')
904: choice = (*key) - '0';
905: else if (((*key) & KEYC_MASK_MOD) == KEYC_ESCAPE) {
906: tmp = (*key) & KEYC_MASK_KEY;
907: if (tmp >= 'a' && tmp <= 'z')
908: choice = 10 + (tmp - 'a');
909: }
910: if (choice != -1) {
911: if ((u_int)choice > mtd->line_size - 1) {
912: *key = KEYC_NONE;
913: return (0);
914: }
915: mtd->current = choice;
916: *key = '\r';
917: return (0);
918: }
919:
920: switch (*key) {
921: case 'q':
922: case '\033': /* Escape */
1.22 nicm 923: case '\007': /* C-g */
1.1 nicm 924: return (1);
925: case KEYC_UP:
926: case 'k':
927: case KEYC_WHEELUP_PANE:
1.12 nicm 928: case '\020': /* C-p */
1.1 nicm 929: mode_tree_up(mtd, 1);
930: break;
931: case KEYC_DOWN:
932: case 'j':
933: case KEYC_WHEELDOWN_PANE:
1.12 nicm 934: case '\016': /* C-n */
1.1 nicm 935: mode_tree_down(mtd, 1);
936: break;
1.35 nicm 937: case 'g':
1.1 nicm 938: case KEYC_PPAGE:
939: case '\002': /* C-b */
940: for (i = 0; i < mtd->height; i++) {
941: if (mtd->current == 0)
942: break;
943: mode_tree_up(mtd, 1);
944: }
945: break;
1.35 nicm 946: case 'G':
1.1 nicm 947: case KEYC_NPAGE:
948: case '\006': /* C-f */
949: for (i = 0; i < mtd->height; i++) {
950: if (mtd->current == mtd->line_size - 1)
951: break;
952: mode_tree_down(mtd, 1);
953: }
954: break;
955: case KEYC_HOME:
956: mtd->current = 0;
957: mtd->offset = 0;
958: break;
959: case KEYC_END:
960: mtd->current = mtd->line_size - 1;
961: if (mtd->current > mtd->height - 1)
1.11 nicm 962: mtd->offset = mtd->current - mtd->height + 1;
1.1 nicm 963: else
964: mtd->offset = 0;
965: break;
966: case 't':
967: /*
968: * Do not allow parents and children to both be tagged: untag
969: * all parents and children of current.
970: */
971: if (!current->tagged) {
972: parent = current->parent;
973: while (parent != NULL) {
974: parent->tagged = 0;
975: parent = parent->parent;
976: }
977: mode_tree_clear_tagged(¤t->children);
978: current->tagged = 1;
979: } else
980: current->tagged = 0;
1.28 nicm 981: if (m != NULL)
982: mode_tree_down(mtd, 0);
1.1 nicm 983: break;
984: case 'T':
985: for (i = 0; i < mtd->line_size; i++)
986: mtd->line_list[i].item->tagged = 0;
987: break;
988: case '\024': /* C-t */
989: for (i = 0; i < mtd->line_size; i++) {
990: if (mtd->line_list[i].item->parent == NULL)
991: mtd->line_list[i].item->tagged = 1;
992: else
993: mtd->line_list[i].item->tagged = 0;
994: }
995: break;
996: case 'O':
1.36 ! nicm 997: mtd->sort_crit.field++;
! 998: if (mtd->sort_crit.field == mtd->sort_size)
! 999: mtd->sort_crit.field = 0;
! 1000: mode_tree_build(mtd);
! 1001: break;
! 1002: case 'r':
! 1003: mtd->sort_crit.reversed = !mtd->sort_crit.reversed;
1.1 nicm 1004: mode_tree_build(mtd);
1005: break;
1006: case KEYC_LEFT:
1.15 nicm 1007: case 'h':
1.1 nicm 1008: case '-':
1009: if (line->flat || !current->expanded)
1010: current = current->parent;
1011: if (current == NULL)
1012: mode_tree_up(mtd, 0);
1013: else {
1014: current->expanded = 0;
1015: mtd->current = current->line;
1016: mode_tree_build(mtd);
1017: }
1018: break;
1019: case KEYC_RIGHT:
1.15 nicm 1020: case 'l':
1.1 nicm 1021: case '+':
1022: if (line->flat || current->expanded)
1023: mode_tree_down(mtd, 0);
1024: else if (!line->flat) {
1025: current->expanded = 1;
1026: mode_tree_build(mtd);
1027: }
1.3 nicm 1028: break;
1.35 nicm 1029: case '?':
1030: case '/':
1.3 nicm 1031: case '\023': /* C-s */
1032: mtd->references++;
1033: status_prompt_set(c, "(search) ", "",
1034: mode_tree_search_callback, mode_tree_search_free, mtd,
1035: PROMPT_NOFORMAT);
1036: break;
1037: case 'n':
1038: mode_tree_search_set(mtd);
1.6 nicm 1039: break;
1040: case 'f':
1041: mtd->references++;
1042: status_prompt_set(c, "(filter) ", mtd->filter,
1043: mode_tree_filter_callback, mode_tree_filter_free, mtd,
1044: PROMPT_NOFORMAT);
1.9 nicm 1045: break;
1046: case 'v':
1047: mtd->preview = !mtd->preview;
1048: mode_tree_build(mtd);
1.11 nicm 1049: if (mtd->preview)
1050: mode_tree_check_selected(mtd);
1.1 nicm 1051: break;
1052: }
1053: return (0);
1054: }
1055:
1056: void
1057: mode_tree_run_command(struct client *c, struct cmd_find_state *fs,
1058: const char *template, const char *name)
1059: {
1060: struct cmdq_item *new_item;
1.30 nicm 1061: char *command;
1062: struct cmd_parse_result *pr;
1.1 nicm 1063:
1064: command = cmd_template_replace(template, name, 1);
1.2 nicm 1065: if (command == NULL || *command == '\0') {
1066: free(command);
1.1 nicm 1067: return;
1.2 nicm 1068: }
1.1 nicm 1069:
1.30 nicm 1070: pr = cmd_parse_from_string(command, NULL);
1071: switch (pr->status) {
1072: case CMD_PARSE_EMPTY:
1073: break;
1074: case CMD_PARSE_ERROR:
1075: if (c != NULL) {
1076: *pr->error = toupper((u_char)*pr->error);
1077: status_message_set(c, "%s", pr->error);
1.1 nicm 1078: }
1.30 nicm 1079: free(pr->error);
1080: break;
1081: case CMD_PARSE_SUCCESS:
1082: new_item = cmdq_get_command(pr->cmdlist, fs, NULL, 0);
1.1 nicm 1083: cmdq_append(c, new_item);
1.30 nicm 1084: cmd_list_free(pr->cmdlist);
1085: break;
1.1 nicm 1086: }
1087:
1088: free(command);
1089: }