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