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