Annotation of src/usr.bin/tmux/mode-tree.c, Revision 1.31
1.31 ! nicm 1: /* $OpenBSD: mode-tree.c,v 1.30 2019/05/23 11:13:30 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
387: tag = 0;
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.3 nicm 403: mode_tree_set_current(mtd, tag);
1.1 nicm 404:
405: mtd->width = screen_size_x(s);
1.9 nicm 406: if (mtd->preview) {
407: mtd->height = (screen_size_y(s) / 3) * 2;
408: if (mtd->height > mtd->line_size)
409: mtd->height = screen_size_y(s) / 2;
410: if (mtd->height < 10)
411: mtd->height = screen_size_y(s);
412: if (screen_size_y(s) - mtd->height < 2)
413: mtd->height = screen_size_y(s);
414: } else
1.1 nicm 415: mtd->height = screen_size_y(s);
1.11 nicm 416: mode_tree_check_selected(mtd);
1.1 nicm 417: }
418:
1.3 nicm 419: static void
420: mode_tree_remove_ref(struct mode_tree_data *mtd)
421: {
422: if (--mtd->references == 0)
423: free(mtd);
424: }
425:
1.1 nicm 426: void
427: mode_tree_free(struct mode_tree_data *mtd)
428: {
1.23 nicm 429: struct window_pane *wp = mtd->wp;
430:
431: if (mtd->zoomed == 0)
432: server_unzoom_window(wp->window);
433:
1.1 nicm 434: mode_tree_free_items(&mtd->children);
435: mode_tree_clear_lines(mtd);
436: screen_free(&mtd->screen);
1.3 nicm 437:
1.6 nicm 438: free(mtd->search);
439: free(mtd->filter);
440:
1.3 nicm 441: mtd->dead = 1;
442: mode_tree_remove_ref(mtd);
1.1 nicm 443: }
444:
445: void
446: mode_tree_resize(struct mode_tree_data *mtd, u_int sx, u_int sy)
447: {
448: struct screen *s = &mtd->screen;
449:
450: screen_resize(s, sx, sy, 0);
451:
452: mode_tree_build(mtd);
453: mode_tree_draw(mtd);
454:
455: mtd->wp->flags |= PANE_REDRAW;
456: }
457:
458: struct mode_tree_item *
459: mode_tree_add(struct mode_tree_data *mtd, struct mode_tree_item *parent,
460: void *itemdata, uint64_t tag, const char *name, const char *text,
461: int expanded)
462: {
463: struct mode_tree_item *mti, *saved;
464:
465: log_debug("%s: %llu, %s %s", __func__, (unsigned long long)tag,
466: name, text);
467:
468: mti = xcalloc(1, sizeof *mti);
469: mti->parent = parent;
470: mti->itemdata = itemdata;
471:
472: mti->tag = tag;
473: mti->name = xstrdup(name);
474: mti->text = xstrdup(text);
475:
476: saved = mode_tree_find_item(&mtd->saved, tag);
477: if (saved != NULL) {
478: if (parent == NULL || (parent != NULL && parent->expanded))
479: mti->tagged = saved->tagged;
480: mti->expanded = saved->expanded;
481: } else if (expanded == -1)
482: mti->expanded = 1;
483: else
484: mti->expanded = expanded;
485:
486: TAILQ_INIT(&mti->children);
487:
488: if (parent != NULL)
489: TAILQ_INSERT_TAIL(&parent->children, mti, entry);
490: else
491: TAILQ_INSERT_TAIL(&mtd->children, mti, entry);
492:
493: return (mti);
494: }
495:
496: void
497: mode_tree_remove(struct mode_tree_data *mtd, struct mode_tree_item *mti)
498: {
499: struct mode_tree_item *parent = mti->parent;
500:
501: if (parent != NULL)
502: TAILQ_REMOVE(&parent->children, mti, entry);
503: else
504: TAILQ_REMOVE(&mtd->children, mti, entry);
505: mode_tree_free_item(mti);
506: }
507:
508: void
509: mode_tree_draw(struct mode_tree_data *mtd)
510: {
511: struct window_pane *wp = mtd->wp;
1.17 nicm 512: struct screen *s = &mtd->screen;
1.1 nicm 513: struct mode_tree_line *line;
514: struct mode_tree_item *mti;
515: struct options *oo = wp->window->options;
516: struct screen_write_ctx ctx;
517: struct grid_cell gc0, gc;
1.27 nicm 518: u_int w, h, i, j, sy, box_x, box_y, width;
1.1 nicm 519: char *text, *start, key[7];
520: const char *tag, *symbol;
1.21 nicm 521: size_t size, n;
1.1 nicm 522: int keylen;
523:
524: if (mtd->line_size == 0)
525: return;
526:
527: memcpy(&gc0, &grid_default_cell, sizeof gc0);
528: memcpy(&gc, &grid_default_cell, sizeof gc);
529: style_apply(&gc, oo, "mode-style");
530:
531: w = mtd->width;
532: h = mtd->height;
533:
534: screen_write_start(&ctx, NULL, s);
535: screen_write_clearscreen(&ctx, 8);
536:
537: if (mtd->line_size > 10)
538: keylen = 6;
539: else
540: keylen = 4;
541:
542: for (i = 0; i < mtd->line_size; i++) {
543: if (i < mtd->offset)
544: continue;
545: if (i > mtd->offset + h - 1)
546: break;
547:
548: line = &mtd->line_list[i];
549: mti = line->item;
550:
1.26 nicm 551: screen_write_cursormove(&ctx, 0, i - mtd->offset, 0);
1.1 nicm 552:
553: if (i < 10)
1.8 nicm 554: snprintf(key, sizeof key, "(%c) ", '0' + i);
1.1 nicm 555: else if (i < 36)
556: snprintf(key, sizeof key, "(M-%c)", 'a' + (i - 10));
557: else
558: *key = '\0';
559:
560: if (line->flat)
561: symbol = "";
562: else if (TAILQ_EMPTY(&mti->children))
563: symbol = " ";
564: else if (mti->expanded)
565: symbol = "- ";
566: else
567: symbol = "+ ";
568:
569: if (line->depth == 0)
570: start = xstrdup(symbol);
571: else {
572: size = (4 * line->depth) + 32;
573:
574: start = xcalloc(1, size);
575: for (j = 1; j < line->depth; j++) {
576: if (mti->parent != NULL &&
577: mtd->line_list[mti->parent->line].last)
578: strlcat(start, " ", size);
579: else
580: strlcat(start, "\001x\001 ", size);
581: }
582: if (line->last)
583: strlcat(start, "\001mq\001> ", size);
584: else
585: strlcat(start, "\001tq\001> ", size);
586: strlcat(start, symbol, size);
587: }
588:
589: if (mti->tagged)
590: tag = "*";
591: else
592: tag = "";
1.27 nicm 593: xasprintf(&text, "%-*s%s%s%s: ", keylen, key, start, mti->name,
594: tag);
595: width = utf8_cstrwidth(text);
1.1 nicm 596: free(start);
597:
598: if (mti->tagged) {
599: gc.attr ^= GRID_ATTR_BRIGHT;
600: gc0.attr ^= GRID_ATTR_BRIGHT;
601: }
602:
603: if (i != mtd->current) {
604: screen_write_clearendofline(&ctx, 8);
1.27 nicm 605: screen_write_puts(&ctx, &gc0, "%s", text);
606: format_draw(&ctx, &gc0, w - width, mti->text, NULL);
1.13 nicm 607: } else {
608: screen_write_clearendofline(&ctx, gc.bg);
1.27 nicm 609: screen_write_puts(&ctx, &gc, "%s", text);
610: format_draw(&ctx, &gc, w - width, mti->text, NULL);
1.13 nicm 611: }
1.1 nicm 612: free(text);
613:
614: if (mti->tagged) {
615: gc.attr ^= GRID_ATTR_BRIGHT;
616: gc0.attr ^= GRID_ATTR_BRIGHT;
617: }
618: }
619:
620: sy = screen_size_y(s);
1.9 nicm 621: if (!mtd->preview || sy <= 4 || h <= 4 || sy - h <= 4 || w <= 4) {
1.1 nicm 622: screen_write_stop(&ctx);
623: return;
624: }
625:
626: line = &mtd->line_list[mtd->current];
627: mti = line->item;
628:
1.26 nicm 629: screen_write_cursormove(&ctx, 0, h, 0);
1.1 nicm 630: screen_write_box(&ctx, w, sy - h);
631:
1.21 nicm 632: xasprintf(&text, " %s (sort: %s)", mti->name,
1.1 nicm 633: mtd->sort_list[mtd->sort_type]);
634: if (w - 2 >= strlen(text)) {
1.26 nicm 635: screen_write_cursormove(&ctx, 1, h, 0);
1.1 nicm 636: screen_write_puts(&ctx, &gc0, "%s", text);
1.21 nicm 637:
638: if (mtd->no_matches)
639: n = (sizeof "no matches") - 1;
640: else
641: n = (sizeof "active") - 1;
642: if (mtd->filter != NULL && w - 2 >= strlen(text) + 10 + n + 2) {
643: screen_write_puts(&ctx, &gc0, " (filter: ");
644: if (mtd->no_matches)
645: screen_write_puts(&ctx, &gc, "no matches");
646: else
647: screen_write_puts(&ctx, &gc0, "active");
648: screen_write_puts(&ctx, &gc0, ") ");
649: }
1.1 nicm 650: }
651: free(text);
652:
653: box_x = w - 4;
654: box_y = sy - h - 2;
655:
1.17 nicm 656: if (box_x != 0 && box_y != 0) {
1.26 nicm 657: screen_write_cursormove(&ctx, 2, h + 1, 0);
1.17 nicm 658: mtd->drawcb(mtd->modedata, mti->itemdata, &ctx, box_x, box_y);
1.1 nicm 659: }
660:
661: screen_write_stop(&ctx);
662: }
663:
1.3 nicm 664: static struct mode_tree_item *
665: mode_tree_search_for(struct mode_tree_data *mtd)
666: {
667: struct mode_tree_item *mti, *last, *next;
668:
1.6 nicm 669: if (mtd->search == NULL)
1.3 nicm 670: return (NULL);
671:
672: mti = last = mtd->line_list[mtd->current].item;
673: for (;;) {
674: if (!TAILQ_EMPTY(&mti->children))
675: mti = TAILQ_FIRST(&mti->children);
676: else if ((next = TAILQ_NEXT(mti, entry)) != NULL)
677: mti = next;
678: else {
679: for (;;) {
680: mti = mti->parent;
681: if (mti == NULL)
682: break;
683: if ((next = TAILQ_NEXT(mti, entry)) != NULL) {
684: mti = next;
685: break;
686: }
687: }
688: }
689: if (mti == NULL)
690: mti = TAILQ_FIRST(&mtd->children);
691: if (mti == last)
692: break;
693:
694: if (mtd->searchcb == NULL) {
1.6 nicm 695: if (strstr(mti->name, mtd->search) != NULL)
1.3 nicm 696: return (mti);
697: continue;
698: }
1.6 nicm 699: if (mtd->searchcb(mtd->modedata, mti->itemdata, mtd->search))
1.4 nicm 700: return (mti);
1.3 nicm 701: }
702: return (NULL);
703: }
704:
705: static void
706: mode_tree_search_set(struct mode_tree_data *mtd)
707: {
708: struct mode_tree_item *mti, *loop;
709: uint64_t tag;
710:
711: mti = mode_tree_search_for(mtd);
712: if (mti == NULL)
713: return;
714: tag = mti->tag;
715:
716: loop = mti->parent;
717: while (loop != NULL) {
718: loop->expanded = 1;
719: loop = loop->parent;
720: }
1.6 nicm 721:
1.3 nicm 722: mode_tree_build(mtd);
723: mode_tree_set_current(mtd, tag);
724: mode_tree_draw(mtd);
1.6 nicm 725: mtd->wp->flags |= PANE_REDRAW;
1.3 nicm 726: }
727:
728: static int
729: mode_tree_search_callback(__unused struct client *c, void *data, const char *s,
730: __unused int done)
731: {
732: struct mode_tree_data *mtd = data;
733:
734: if (mtd->dead)
735: return (0);
736:
1.6 nicm 737: free(mtd->search);
738: if (s == NULL || *s == '\0') {
739: mtd->search = NULL;
1.3 nicm 740: return (0);
741: }
1.6 nicm 742: mtd->search = xstrdup(s);
1.3 nicm 743: mode_tree_search_set(mtd);
744:
745: return (0);
746: }
747:
748: static void
749: mode_tree_search_free(void *data)
750: {
751: mode_tree_remove_ref(data);
752: }
753:
1.6 nicm 754: static int
755: mode_tree_filter_callback(__unused struct client *c, void *data, const char *s,
756: __unused int done)
757: {
758: struct mode_tree_data *mtd = data;
759:
760: if (mtd->dead)
761: return (0);
762:
763: if (mtd->filter != NULL)
764: free(mtd->filter);
765: if (s == NULL || *s == '\0')
766: mtd->filter = NULL;
767: else
768: mtd->filter = xstrdup(s);
769:
770: mode_tree_build(mtd);
771: mode_tree_draw(mtd);
772: mtd->wp->flags |= PANE_REDRAW;
773:
774: return (0);
775: }
776:
777: static void
778: mode_tree_filter_free(void *data)
779: {
780: mode_tree_remove_ref(data);
781: }
782:
1.28 nicm 783: static void
784: mode_tree_menu_callback(__unused struct menu *menu, __unused u_int idx,
785: key_code key, void *data)
786: {
787: struct mode_tree_menu *mtm = data;
788: struct mode_tree_data *mtd = mtm->data;
789: struct mode_tree_item *mti;
790:
791: if (mtd->dead || key == KEYC_NONE)
792: goto out;
793:
794: if (mtm->line >= mtd->line_size)
795: goto out;
796: mti = mtd->line_list[mtm->line].item;
797: if (mti->itemdata != mtm->itemdata)
798: goto out;
799: mtd->current = mtm->line;
800: mtd->menucb (mtd->modedata, mtm->c, key);
801:
802: out:
803: mode_tree_remove_ref(mtd);
804: free(mtm);
805: }
806:
807: static void
808: mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x,
809: u_int y, int outside)
810: {
811: struct mode_tree_item *mti;
812: struct menu *menu;
813: struct mode_tree_menu *mtm;
814: const char *s;
815: char *title;
816: u_int line;
817:
818: if (mtd->offset + y > mtd->line_size - 1)
819: line = mtd->current;
820: else
821: line = mtd->offset + y;
1.29 nicm 822: mti = mtd->line_list[line].item;
1.28 nicm 823:
824: if (!outside) {
825: s = mtd->menu;
826: xasprintf(&title, "#[align=centre]%s", mti->name);
827: } else {
828: s = MODE_TREE_MENU;
829: title = xstrdup("");
830: }
1.31 ! nicm 831: menu = menu_create(s, NULL, c, NULL, title);
1.28 nicm 832: free(title);
833: if (menu == NULL)
834: return;
835:
836: mtm = xmalloc(sizeof *mtm);
837: mtm->data = mtd;
838: mtm->c = c;
839: mtm->line = line;
840: mtm->itemdata = mti->itemdata;
841: mtd->references++;
842:
843: if (menu_display(menu, 0, NULL, x, y, c, NULL, mode_tree_menu_callback,
844: mtm) != 0)
845: menu_free(menu);
846: }
847:
1.1 nicm 848: int
1.3 nicm 849: mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
1.19 nicm 850: struct mouse_event *m, u_int *xp, u_int *yp)
1.1 nicm 851: {
852: struct mode_tree_line *line;
853: struct mode_tree_item *current, *parent;
854: u_int i, x, y;
855: int choice;
856: key_code tmp;
857:
1.28 nicm 858: if (KEYC_IS_MOUSE(*key) && m != NULL) {
1.1 nicm 859: if (cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) {
860: *key = KEYC_NONE;
861: return (0);
862: }
1.19 nicm 863: if (xp != NULL)
864: *xp = x;
865: if (yp != NULL)
866: *yp = y;
1.1 nicm 867: if (x > mtd->width || y > mtd->height) {
1.28 nicm 868: if (*key == KEYC_MOUSEDOWN3_PANE)
869: mode_tree_display_menu(mtd, c, x, y, 1);
1.20 nicm 870: if (!mtd->preview)
1.19 nicm 871: *key = KEYC_NONE;
1.1 nicm 872: return (0);
873: }
874: if (mtd->offset + y < mtd->line_size) {
1.18 nicm 875: if (*key == KEYC_MOUSEDOWN1_PANE ||
1.28 nicm 876: *key == KEYC_MOUSEDOWN3_PANE ||
1.18 nicm 877: *key == KEYC_DOUBLECLICK1_PANE)
878: mtd->current = mtd->offset + y;
879: if (*key == KEYC_DOUBLECLICK1_PANE)
880: *key = '\r';
1.28 nicm 881: else {
882: if (*key == KEYC_MOUSEDOWN3_PANE)
883: mode_tree_display_menu(mtd, c, x, y, 0);
1.20 nicm 884: *key = KEYC_NONE;
1.28 nicm 885: }
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: }
1.20 nicm 891: return (0);
1.1 nicm 892: }
893:
894: line = &mtd->line_list[mtd->current];
895: current = line->item;
896:
897: choice = -1;
898: if (*key >= '0' && *key <= '9')
899: choice = (*key) - '0';
900: else if (((*key) & KEYC_MASK_MOD) == KEYC_ESCAPE) {
901: tmp = (*key) & KEYC_MASK_KEY;
902: if (tmp >= 'a' && tmp <= 'z')
903: choice = 10 + (tmp - 'a');
904: }
905: if (choice != -1) {
906: if ((u_int)choice > mtd->line_size - 1) {
907: *key = KEYC_NONE;
908: return (0);
909: }
910: mtd->current = choice;
911: *key = '\r';
912: return (0);
913: }
914:
915: switch (*key) {
916: case 'q':
917: case '\033': /* Escape */
1.22 nicm 918: case '\007': /* C-g */
1.1 nicm 919: return (1);
920: case KEYC_UP:
921: case 'k':
922: case KEYC_WHEELUP_PANE:
1.12 nicm 923: case '\020': /* C-p */
1.1 nicm 924: mode_tree_up(mtd, 1);
925: break;
926: case KEYC_DOWN:
927: case 'j':
928: case KEYC_WHEELDOWN_PANE:
1.12 nicm 929: case '\016': /* C-n */
1.1 nicm 930: mode_tree_down(mtd, 1);
931: break;
932: case KEYC_PPAGE:
933: case '\002': /* C-b */
934: for (i = 0; i < mtd->height; i++) {
935: if (mtd->current == 0)
936: break;
937: mode_tree_up(mtd, 1);
938: }
939: break;
940: case KEYC_NPAGE:
941: case '\006': /* C-f */
942: for (i = 0; i < mtd->height; i++) {
943: if (mtd->current == mtd->line_size - 1)
944: break;
945: mode_tree_down(mtd, 1);
946: }
947: break;
948: case KEYC_HOME:
949: mtd->current = 0;
950: mtd->offset = 0;
951: break;
952: case KEYC_END:
953: mtd->current = mtd->line_size - 1;
954: if (mtd->current > mtd->height - 1)
1.11 nicm 955: mtd->offset = mtd->current - mtd->height + 1;
1.1 nicm 956: else
957: mtd->offset = 0;
958: break;
959: case 't':
960: /*
961: * Do not allow parents and children to both be tagged: untag
962: * all parents and children of current.
963: */
964: if (!current->tagged) {
965: parent = current->parent;
966: while (parent != NULL) {
967: parent->tagged = 0;
968: parent = parent->parent;
969: }
970: mode_tree_clear_tagged(¤t->children);
971: current->tagged = 1;
972: } else
973: current->tagged = 0;
1.28 nicm 974: if (m != NULL)
975: mode_tree_down(mtd, 0);
1.1 nicm 976: break;
977: case 'T':
978: for (i = 0; i < mtd->line_size; i++)
979: mtd->line_list[i].item->tagged = 0;
980: break;
981: case '\024': /* C-t */
982: for (i = 0; i < mtd->line_size; i++) {
983: if (mtd->line_list[i].item->parent == NULL)
984: mtd->line_list[i].item->tagged = 1;
985: else
986: mtd->line_list[i].item->tagged = 0;
987: }
988: break;
989: case 'O':
990: mtd->sort_type++;
991: if (mtd->sort_type == mtd->sort_size)
992: mtd->sort_type = 0;
993: mode_tree_build(mtd);
994: break;
995: case KEYC_LEFT:
1.15 nicm 996: case 'h':
1.1 nicm 997: case '-':
998: if (line->flat || !current->expanded)
999: current = current->parent;
1000: if (current == NULL)
1001: mode_tree_up(mtd, 0);
1002: else {
1003: current->expanded = 0;
1004: mtd->current = current->line;
1005: mode_tree_build(mtd);
1006: }
1007: break;
1008: case KEYC_RIGHT:
1.15 nicm 1009: case 'l':
1.1 nicm 1010: case '+':
1011: if (line->flat || current->expanded)
1012: mode_tree_down(mtd, 0);
1013: else if (!line->flat) {
1014: current->expanded = 1;
1015: mode_tree_build(mtd);
1016: }
1.3 nicm 1017: break;
1018: case '\023': /* C-s */
1019: mtd->references++;
1020: status_prompt_set(c, "(search) ", "",
1021: mode_tree_search_callback, mode_tree_search_free, mtd,
1022: PROMPT_NOFORMAT);
1023: break;
1024: case 'n':
1025: mode_tree_search_set(mtd);
1.6 nicm 1026: break;
1027: case 'f':
1028: mtd->references++;
1029: status_prompt_set(c, "(filter) ", mtd->filter,
1030: mode_tree_filter_callback, mode_tree_filter_free, mtd,
1031: PROMPT_NOFORMAT);
1.9 nicm 1032: break;
1033: case 'v':
1034: mtd->preview = !mtd->preview;
1035: mode_tree_build(mtd);
1.11 nicm 1036: if (mtd->preview)
1037: mode_tree_check_selected(mtd);
1.1 nicm 1038: break;
1039: }
1040: return (0);
1041: }
1042:
1043: void
1044: mode_tree_run_command(struct client *c, struct cmd_find_state *fs,
1045: const char *template, const char *name)
1046: {
1047: struct cmdq_item *new_item;
1.30 nicm 1048: char *command;
1049: struct cmd_parse_result *pr;
1.1 nicm 1050:
1051: command = cmd_template_replace(template, name, 1);
1.2 nicm 1052: if (command == NULL || *command == '\0') {
1053: free(command);
1.1 nicm 1054: return;
1.2 nicm 1055: }
1.1 nicm 1056:
1.30 nicm 1057: pr = cmd_parse_from_string(command, NULL);
1058: switch (pr->status) {
1059: case CMD_PARSE_EMPTY:
1060: break;
1061: case CMD_PARSE_ERROR:
1062: if (c != NULL) {
1063: *pr->error = toupper((u_char)*pr->error);
1064: status_message_set(c, "%s", pr->error);
1.1 nicm 1065: }
1.30 nicm 1066: free(pr->error);
1067: break;
1068: case CMD_PARSE_SUCCESS:
1069: new_item = cmdq_get_command(pr->cmdlist, fs, NULL, 0);
1.1 nicm 1070: cmdq_append(c, new_item);
1.30 nicm 1071: cmd_list_free(pr->cmdlist);
1072: break;
1.1 nicm 1073: }
1074:
1075: free(command);
1076: }