Annotation of src/usr.bin/tmux/mode-tree.c, Revision 1.35
1.35 ! nicm 1: /* $OpenBSD: mode-tree.c,v 1.34 2019/05/30 20:54:03 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;
42: u_int sort_type;
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;
337: mtd->sort_type = 0;
1.5 nicm 338:
1.9 nicm 339: mtd->preview = !args_has(args, 'N');
340:
1.5 nicm 341: sort = args_get(args, 'O');
342: if (sort != NULL) {
343: for (i = 0; i < sort_size; i++) {
344: if (strcasecmp(sort, sort_list[i]) == 0)
345: mtd->sort_type = i;
346: }
347: }
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.6 nicm 395: mtd->buildcb(mtd->modedata, mtd->sort_type, &tag, mtd->filter);
1.21 nicm 396: mtd->no_matches = TAILQ_EMPTY(&mtd->children);
397: if (mtd->no_matches)
1.6 nicm 398: mtd->buildcb(mtd->modedata, mtd->sort_type, &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.21 nicm 637: xasprintf(&text, " %s (sort: %s)", mti->name,
1.1 nicm 638: mtd->sort_list[mtd->sort_type]);
639: if (w - 2 >= strlen(text)) {
1.26 nicm 640: screen_write_cursormove(&ctx, 1, h, 0);
1.1 nicm 641: screen_write_puts(&ctx, &gc0, "%s", text);
1.21 nicm 642:
643: if (mtd->no_matches)
644: n = (sizeof "no matches") - 1;
645: else
646: n = (sizeof "active") - 1;
647: if (mtd->filter != NULL && w - 2 >= strlen(text) + 10 + n + 2) {
648: screen_write_puts(&ctx, &gc0, " (filter: ");
649: if (mtd->no_matches)
650: screen_write_puts(&ctx, &gc, "no matches");
651: else
652: screen_write_puts(&ctx, &gc0, "active");
653: screen_write_puts(&ctx, &gc0, ") ");
654: }
1.1 nicm 655: }
656: free(text);
657:
658: box_x = w - 4;
659: box_y = sy - h - 2;
660:
1.17 nicm 661: if (box_x != 0 && box_y != 0) {
1.26 nicm 662: screen_write_cursormove(&ctx, 2, h + 1, 0);
1.17 nicm 663: mtd->drawcb(mtd->modedata, mti->itemdata, &ctx, box_x, box_y);
1.1 nicm 664: }
665:
666: screen_write_stop(&ctx);
667: }
668:
1.3 nicm 669: static struct mode_tree_item *
670: mode_tree_search_for(struct mode_tree_data *mtd)
671: {
672: struct mode_tree_item *mti, *last, *next;
673:
1.6 nicm 674: if (mtd->search == NULL)
1.3 nicm 675: return (NULL);
676:
677: mti = last = mtd->line_list[mtd->current].item;
678: for (;;) {
679: if (!TAILQ_EMPTY(&mti->children))
680: mti = TAILQ_FIRST(&mti->children);
681: else if ((next = TAILQ_NEXT(mti, entry)) != NULL)
682: mti = next;
683: else {
684: for (;;) {
685: mti = mti->parent;
686: if (mti == NULL)
687: break;
688: if ((next = TAILQ_NEXT(mti, entry)) != NULL) {
689: mti = next;
690: break;
691: }
692: }
693: }
694: if (mti == NULL)
695: mti = TAILQ_FIRST(&mtd->children);
696: if (mti == last)
697: break;
698:
699: if (mtd->searchcb == NULL) {
1.6 nicm 700: if (strstr(mti->name, mtd->search) != NULL)
1.3 nicm 701: return (mti);
702: continue;
703: }
1.6 nicm 704: if (mtd->searchcb(mtd->modedata, mti->itemdata, mtd->search))
1.4 nicm 705: return (mti);
1.3 nicm 706: }
707: return (NULL);
708: }
709:
710: static void
711: mode_tree_search_set(struct mode_tree_data *mtd)
712: {
713: struct mode_tree_item *mti, *loop;
714: uint64_t tag;
715:
716: mti = mode_tree_search_for(mtd);
717: if (mti == NULL)
718: return;
719: tag = mti->tag;
720:
721: loop = mti->parent;
722: while (loop != NULL) {
723: loop->expanded = 1;
724: loop = loop->parent;
725: }
1.6 nicm 726:
1.3 nicm 727: mode_tree_build(mtd);
728: mode_tree_set_current(mtd, tag);
729: mode_tree_draw(mtd);
1.6 nicm 730: mtd->wp->flags |= PANE_REDRAW;
1.3 nicm 731: }
732:
733: static int
734: mode_tree_search_callback(__unused struct client *c, void *data, const char *s,
735: __unused int done)
736: {
737: struct mode_tree_data *mtd = data;
738:
739: if (mtd->dead)
740: return (0);
741:
1.6 nicm 742: free(mtd->search);
743: if (s == NULL || *s == '\0') {
744: mtd->search = NULL;
1.3 nicm 745: return (0);
746: }
1.6 nicm 747: mtd->search = xstrdup(s);
1.3 nicm 748: mode_tree_search_set(mtd);
749:
750: return (0);
751: }
752:
753: static void
754: mode_tree_search_free(void *data)
755: {
756: mode_tree_remove_ref(data);
757: }
758:
1.6 nicm 759: static int
760: mode_tree_filter_callback(__unused struct client *c, void *data, const char *s,
761: __unused int done)
762: {
763: struct mode_tree_data *mtd = data;
764:
765: if (mtd->dead)
766: return (0);
767:
768: if (mtd->filter != NULL)
769: free(mtd->filter);
770: if (s == NULL || *s == '\0')
771: mtd->filter = NULL;
772: else
773: mtd->filter = xstrdup(s);
774:
775: mode_tree_build(mtd);
776: mode_tree_draw(mtd);
777: mtd->wp->flags |= PANE_REDRAW;
778:
779: return (0);
780: }
781:
782: static void
783: mode_tree_filter_free(void *data)
784: {
785: mode_tree_remove_ref(data);
786: }
787:
1.28 nicm 788: static void
789: mode_tree_menu_callback(__unused struct menu *menu, __unused u_int idx,
790: key_code key, void *data)
791: {
792: struct mode_tree_menu *mtm = data;
793: struct mode_tree_data *mtd = mtm->data;
794: struct mode_tree_item *mti;
795:
796: if (mtd->dead || key == KEYC_NONE)
797: goto out;
798:
799: if (mtm->line >= mtd->line_size)
800: goto out;
801: mti = mtd->line_list[mtm->line].item;
802: if (mti->itemdata != mtm->itemdata)
803: goto out;
804: mtd->current = mtm->line;
805: mtd->menucb (mtd->modedata, mtm->c, key);
806:
807: out:
808: mode_tree_remove_ref(mtd);
809: free(mtm);
810: }
811:
812: static void
813: mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x,
814: u_int y, int outside)
815: {
816: struct mode_tree_item *mti;
817: struct menu *menu;
1.33 nicm 818: const struct menu_item *items;
1.28 nicm 819: struct mode_tree_menu *mtm;
820: char *title;
821: u_int line;
822:
823: if (mtd->offset + y > mtd->line_size - 1)
824: line = mtd->current;
825: else
826: line = mtd->offset + y;
1.29 nicm 827: mti = mtd->line_list[line].item;
1.28 nicm 828:
829: if (!outside) {
1.33 nicm 830: items = mtd->menu;
1.28 nicm 831: xasprintf(&title, "#[align=centre]%s", mti->name);
832: } else {
1.33 nicm 833: items = mode_tree_menu_items;
1.28 nicm 834: title = xstrdup("");
835: }
1.33 nicm 836: menu = menu_create(title);
837: menu_add_items(menu, items, NULL, NULL, NULL);
1.28 nicm 838: free(title);
839:
840: mtm = xmalloc(sizeof *mtm);
841: mtm->data = mtd;
842: mtm->c = c;
843: mtm->line = line;
844: mtm->itemdata = mti->itemdata;
845: mtd->references++;
846:
847: if (menu_display(menu, 0, NULL, x, y, c, NULL, mode_tree_menu_callback,
848: mtm) != 0)
849: menu_free(menu);
850: }
851:
1.1 nicm 852: int
1.3 nicm 853: mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
1.19 nicm 854: struct mouse_event *m, u_int *xp, u_int *yp)
1.1 nicm 855: {
856: struct mode_tree_line *line;
857: struct mode_tree_item *current, *parent;
858: u_int i, x, y;
859: int choice;
860: key_code tmp;
861:
1.28 nicm 862: if (KEYC_IS_MOUSE(*key) && m != NULL) {
1.1 nicm 863: if (cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) {
864: *key = KEYC_NONE;
865: return (0);
866: }
1.19 nicm 867: if (xp != NULL)
868: *xp = x;
869: if (yp != NULL)
870: *yp = y;
1.1 nicm 871: if (x > mtd->width || y > mtd->height) {
1.28 nicm 872: if (*key == KEYC_MOUSEDOWN3_PANE)
873: mode_tree_display_menu(mtd, c, x, y, 1);
1.20 nicm 874: if (!mtd->preview)
1.19 nicm 875: *key = KEYC_NONE;
1.1 nicm 876: return (0);
877: }
878: if (mtd->offset + y < mtd->line_size) {
1.18 nicm 879: if (*key == KEYC_MOUSEDOWN1_PANE ||
1.28 nicm 880: *key == KEYC_MOUSEDOWN3_PANE ||
1.18 nicm 881: *key == KEYC_DOUBLECLICK1_PANE)
882: mtd->current = mtd->offset + y;
883: if (*key == KEYC_DOUBLECLICK1_PANE)
884: *key = '\r';
1.28 nicm 885: else {
886: if (*key == KEYC_MOUSEDOWN3_PANE)
887: mode_tree_display_menu(mtd, c, x, y, 0);
1.20 nicm 888: *key = KEYC_NONE;
1.28 nicm 889: }
890: } else {
891: if (*key == KEYC_MOUSEDOWN3_PANE)
892: mode_tree_display_menu(mtd, c, x, y, 0);
1.20 nicm 893: *key = KEYC_NONE;
1.28 nicm 894: }
1.20 nicm 895: return (0);
1.1 nicm 896: }
897:
898: line = &mtd->line_list[mtd->current];
899: current = line->item;
900:
901: choice = -1;
902: if (*key >= '0' && *key <= '9')
903: choice = (*key) - '0';
904: else if (((*key) & KEYC_MASK_MOD) == KEYC_ESCAPE) {
905: tmp = (*key) & KEYC_MASK_KEY;
906: if (tmp >= 'a' && tmp <= 'z')
907: choice = 10 + (tmp - 'a');
908: }
909: if (choice != -1) {
910: if ((u_int)choice > mtd->line_size - 1) {
911: *key = KEYC_NONE;
912: return (0);
913: }
914: mtd->current = choice;
915: *key = '\r';
916: return (0);
917: }
918:
919: switch (*key) {
920: case 'q':
921: case '\033': /* Escape */
1.22 nicm 922: case '\007': /* C-g */
1.1 nicm 923: return (1);
924: case KEYC_UP:
925: case 'k':
926: case KEYC_WHEELUP_PANE:
1.12 nicm 927: case '\020': /* C-p */
1.1 nicm 928: mode_tree_up(mtd, 1);
929: break;
930: case KEYC_DOWN:
931: case 'j':
932: case KEYC_WHEELDOWN_PANE:
1.12 nicm 933: case '\016': /* C-n */
1.1 nicm 934: mode_tree_down(mtd, 1);
935: break;
1.35 ! nicm 936: case 'g':
1.1 nicm 937: case KEYC_PPAGE:
938: case '\002': /* C-b */
939: for (i = 0; i < mtd->height; i++) {
940: if (mtd->current == 0)
941: break;
942: mode_tree_up(mtd, 1);
943: }
944: break;
1.35 ! nicm 945: case 'G':
1.1 nicm 946: case KEYC_NPAGE:
947: case '\006': /* C-f */
948: for (i = 0; i < mtd->height; i++) {
949: if (mtd->current == mtd->line_size - 1)
950: break;
951: mode_tree_down(mtd, 1);
952: }
953: break;
954: case KEYC_HOME:
955: mtd->current = 0;
956: mtd->offset = 0;
957: break;
958: case KEYC_END:
959: mtd->current = mtd->line_size - 1;
960: if (mtd->current > mtd->height - 1)
1.11 nicm 961: mtd->offset = mtd->current - mtd->height + 1;
1.1 nicm 962: else
963: mtd->offset = 0;
964: break;
965: case 't':
966: /*
967: * Do not allow parents and children to both be tagged: untag
968: * all parents and children of current.
969: */
970: if (!current->tagged) {
971: parent = current->parent;
972: while (parent != NULL) {
973: parent->tagged = 0;
974: parent = parent->parent;
975: }
976: mode_tree_clear_tagged(¤t->children);
977: current->tagged = 1;
978: } else
979: current->tagged = 0;
1.28 nicm 980: if (m != NULL)
981: mode_tree_down(mtd, 0);
1.1 nicm 982: break;
983: case 'T':
984: for (i = 0; i < mtd->line_size; i++)
985: mtd->line_list[i].item->tagged = 0;
986: break;
987: case '\024': /* C-t */
988: for (i = 0; i < mtd->line_size; i++) {
989: if (mtd->line_list[i].item->parent == NULL)
990: mtd->line_list[i].item->tagged = 1;
991: else
992: mtd->line_list[i].item->tagged = 0;
993: }
994: break;
995: case 'O':
996: mtd->sort_type++;
997: if (mtd->sort_type == mtd->sort_size)
998: mtd->sort_type = 0;
999: mode_tree_build(mtd);
1000: break;
1001: case KEYC_LEFT:
1.15 nicm 1002: case 'h':
1.1 nicm 1003: case '-':
1004: if (line->flat || !current->expanded)
1005: current = current->parent;
1006: if (current == NULL)
1007: mode_tree_up(mtd, 0);
1008: else {
1009: current->expanded = 0;
1010: mtd->current = current->line;
1011: mode_tree_build(mtd);
1012: }
1013: break;
1014: case KEYC_RIGHT:
1.15 nicm 1015: case 'l':
1.1 nicm 1016: case '+':
1017: if (line->flat || current->expanded)
1018: mode_tree_down(mtd, 0);
1019: else if (!line->flat) {
1020: current->expanded = 1;
1021: mode_tree_build(mtd);
1022: }
1.3 nicm 1023: break;
1.35 ! nicm 1024: case '?':
! 1025: case '/':
1.3 nicm 1026: case '\023': /* C-s */
1027: mtd->references++;
1028: status_prompt_set(c, "(search) ", "",
1029: mode_tree_search_callback, mode_tree_search_free, mtd,
1030: PROMPT_NOFORMAT);
1031: break;
1032: case 'n':
1033: mode_tree_search_set(mtd);
1.6 nicm 1034: break;
1035: case 'f':
1036: mtd->references++;
1037: status_prompt_set(c, "(filter) ", mtd->filter,
1038: mode_tree_filter_callback, mode_tree_filter_free, mtd,
1039: PROMPT_NOFORMAT);
1.9 nicm 1040: break;
1041: case 'v':
1042: mtd->preview = !mtd->preview;
1043: mode_tree_build(mtd);
1.11 nicm 1044: if (mtd->preview)
1045: mode_tree_check_selected(mtd);
1.1 nicm 1046: break;
1047: }
1048: return (0);
1049: }
1050:
1051: void
1052: mode_tree_run_command(struct client *c, struct cmd_find_state *fs,
1053: const char *template, const char *name)
1054: {
1055: struct cmdq_item *new_item;
1.30 nicm 1056: char *command;
1057: struct cmd_parse_result *pr;
1.1 nicm 1058:
1059: command = cmd_template_replace(template, name, 1);
1.2 nicm 1060: if (command == NULL || *command == '\0') {
1061: free(command);
1.1 nicm 1062: return;
1.2 nicm 1063: }
1.1 nicm 1064:
1.30 nicm 1065: pr = cmd_parse_from_string(command, NULL);
1066: switch (pr->status) {
1067: case CMD_PARSE_EMPTY:
1068: break;
1069: case CMD_PARSE_ERROR:
1070: if (c != NULL) {
1071: *pr->error = toupper((u_char)*pr->error);
1072: status_message_set(c, "%s", pr->error);
1.1 nicm 1073: }
1.30 nicm 1074: free(pr->error);
1075: break;
1076: case CMD_PARSE_SUCCESS:
1077: new_item = cmdq_get_command(pr->cmdlist, fs, NULL, 0);
1.1 nicm 1078: cmdq_append(c, new_item);
1.30 nicm 1079: cmd_list_free(pr->cmdlist);
1080: break;
1.1 nicm 1081: }
1082:
1083: free(command);
1084: }