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