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