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