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