Annotation of src/usr.bin/tmux/mode-tree.c, Revision 1.2
1.2 ! nicm 1: /* $OpenBSD: mode-tree.c,v 1.1 2017/05/30 21:44:59 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 {
32: struct window_pane *wp;
33: void *modedata;
34:
35: const char **sort_list;
36: u_int sort_size;
37: u_int sort_type;
38:
39: void (*buildcb)(void *, u_int, uint64_t *);
40: struct screen *(*drawcb)(void *, void *, u_int, u_int);
41:
42: struct mode_tree_list children;
43: struct mode_tree_list saved;
44:
45: struct mode_tree_line *line_list;
46: u_int line_size;
47:
48: u_int depth;
49:
50: u_int width;
51: u_int height;
52:
53: u_int offset;
54: u_int current;
55:
56: struct screen screen;
57: };
58:
59: struct mode_tree_item {
60: struct mode_tree_item *parent;
61: void *itemdata;
62: u_int line;
63:
64: uint64_t tag;
65: const char *name;
66: const char *text;
67:
68: int expanded;
69: int tagged;
70:
71: struct mode_tree_list children;
72: TAILQ_ENTRY(mode_tree_item) entry;
73: };
74:
75: struct mode_tree_line {
76: struct mode_tree_item *item;
77: u_int depth;
78: int last;
79: int flat;
80: };
81:
82: static void mode_tree_free_items(struct mode_tree_list *);
83:
84: static struct mode_tree_item *
85: mode_tree_find_item(struct mode_tree_list *mtl, uint64_t tag)
86: {
87: struct mode_tree_item *mti, *child;
88:
89: TAILQ_FOREACH(mti, mtl, entry) {
90: if (mti->tag == tag)
91: return (mti);
92: child = mode_tree_find_item(&mti->children, tag);
93: if (child != NULL)
94: return (child);
95: }
96: return (NULL);
97: }
98:
99: static void
100: mode_tree_free_item(struct mode_tree_item *mti)
101: {
102: mode_tree_free_items(&mti->children);
103:
104: free((void *)mti->name);
105: free((void *)mti->text);
106:
107: free(mti);
108: }
109:
110: static void
111: mode_tree_free_items(struct mode_tree_list *mtl)
112: {
113: struct mode_tree_item *mti, *mti1;
114:
115: TAILQ_FOREACH_SAFE(mti, mtl, entry, mti1) {
116: TAILQ_REMOVE(mtl, mti, entry);
117: mode_tree_free_item(mti);
118: }
119: }
120:
121: static void
122: mode_tree_clear_lines(struct mode_tree_data *mtd)
123: {
124: free(mtd->line_list);
125: mtd->line_list = NULL;
126: mtd->line_size = 0;
127: }
128:
129: static void
130: mode_tree_build_lines(struct mode_tree_data *mtd,
131: struct mode_tree_list *mtl, u_int depth)
132: {
133: struct mode_tree_item *mti;
134: struct mode_tree_line *line;
135: u_int i;
136: int flat = 1;
137:
138: mtd->depth = depth;
139: TAILQ_FOREACH(mti, mtl, entry) {
140: mtd->line_list = xreallocarray(mtd->line_list,
141: mtd->line_size + 1, sizeof *mtd->line_list);
142:
143: line = &mtd->line_list[mtd->line_size++];
144: line->item = mti;
145: line->depth = depth;
146: line->last = (mti == TAILQ_LAST(mtl, mode_tree_list));
147:
148: mti->line = (mtd->line_size - 1);
149: if (!TAILQ_EMPTY(&mti->children))
150: flat = 0;
151: if (mti->expanded)
152: mode_tree_build_lines(mtd, &mti->children, depth + 1);
153: }
154: TAILQ_FOREACH(mti, mtl, entry) {
155: for (i = 0; i < mtd->line_size; i++) {
156: line = &mtd->line_list[i];
157: if (line->item == mti)
158: line->flat = flat;
159: }
160: }
161: }
162:
163: static void
164: mode_tree_clear_tagged(struct mode_tree_list *mtl)
165: {
166: struct mode_tree_item *mti;
167:
168: TAILQ_FOREACH(mti, mtl, entry) {
169: mti->tagged = 0;
170: mode_tree_clear_tagged(&mti->children);
171: }
172: }
173:
174: void
175: mode_tree_up(struct mode_tree_data *mtd, int wrap)
176: {
177: if (mtd->current == 0) {
178: if (wrap) {
179: mtd->current = mtd->line_size - 1;
180: if (mtd->line_size >= mtd->height)
181: mtd->offset = mtd->line_size - mtd->height;
182: }
183: } else {
184: mtd->current--;
185: if (mtd->current < mtd->offset)
186: mtd->offset--;
187: }
188: }
189:
190: void
191: mode_tree_down(struct mode_tree_data *mtd, int wrap)
192: {
193: if (mtd->current == mtd->line_size - 1) {
194: if (wrap) {
195: mtd->current = 0;
196: mtd->offset = 0;
197: }
198: } else {
199: mtd->current++;
200: if (mtd->current > mtd->offset + mtd->height - 1)
201: mtd->offset++;
202: }
203: }
204:
205: void *
206: mode_tree_get_current(struct mode_tree_data *mtd)
207: {
208: return (mtd->line_list[mtd->current].item->itemdata);
209: }
210:
211: u_int
212: mode_tree_count_tagged(struct mode_tree_data *mtd)
213: {
214: struct mode_tree_item *mti;
215: u_int i, tagged;
216:
217: tagged = 0;
218: for (i = 0; i < mtd->line_size; i++) {
219: mti = mtd->line_list[i].item;
220: if (mti->tagged)
221: tagged++;
222: }
223: return (tagged);
224: }
225:
226: void
227: mode_tree_each_tagged(struct mode_tree_data *mtd, void (*cb)(void *, void *,
228: key_code), key_code key, int current)
229: {
230: struct mode_tree_item *mti;
231: u_int i;
232: int fired;
233:
234: fired = 0;
235: for (i = 0; i < mtd->line_size; i++) {
236: mti = mtd->line_list[i].item;
237: if (mti->tagged) {
238: fired = 1;
239: cb(mtd->modedata, mti->itemdata, key);
240: }
241: }
242: if (!fired && current) {
243: mti = mtd->line_list[mtd->current].item;
244: cb(mtd->modedata, mti->itemdata, key);
245: }
246: }
247:
248: struct mode_tree_data *
249: mode_tree_start(struct window_pane *wp, void (*buildcb)(void *, u_int,
250: uint64_t *), struct screen *(*drawcb)(void *, void *, u_int, u_int),
251: void *modedata, const char **sort_list, u_int sort_size, struct screen **s)
252: {
253: struct mode_tree_data *mtd;
254:
255: mtd = xcalloc(1, sizeof *mtd);
256: mtd->wp = wp;
257: mtd->modedata = modedata;
258:
259: mtd->sort_list = sort_list;
260: mtd->sort_size = sort_size;
261: mtd->sort_type = 0;
262:
263: mtd->buildcb = buildcb;
264: mtd->drawcb = drawcb;
265:
266: TAILQ_INIT(&mtd->children);
267:
268: *s = &mtd->screen;
269: screen_init(*s, screen_size_x(&wp->base), screen_size_y(&wp->base), 0);
270: (*s)->mode &= ~MODE_CURSOR;
271:
272: return (mtd);
273: }
274:
275: void
276: mode_tree_build(struct mode_tree_data *mtd)
277: {
278: struct screen *s = &mtd->screen;
279: uint64_t tag;
280: u_int i;
281:
282: if (mtd->line_list != NULL)
283: tag = mtd->line_list[mtd->current].item->tag;
284: else
285: tag = 0;
286:
287: TAILQ_CONCAT(&mtd->saved, &mtd->children, entry);
288: TAILQ_INIT(&mtd->children);
289:
290: mtd->buildcb(mtd->modedata, mtd->sort_type, &tag);
291:
292: mode_tree_free_items(&mtd->saved);
293: TAILQ_INIT(&mtd->saved);
294:
295: mode_tree_clear_lines(mtd);
296: mode_tree_build_lines(mtd, &mtd->children, 0);
297:
298: for (i = 0; i < mtd->line_size; i++) {
299: if (mtd->line_list[i].item->tag == tag)
300: break;
301: }
302: if (i != mtd->line_size)
303: mtd->current = i;
304: else {
305: mtd->current = 0;
306: mtd->offset = 0;
307: }
308:
309: mtd->width = screen_size_x(s);
310: mtd->height = (screen_size_y(s) / 3) * 2;
311: if (mtd->height > mtd->line_size)
312: mtd->height = screen_size_y(s) / 2;
313: if (mtd->height < 10)
314: mtd->height = screen_size_y(s);
315: if (screen_size_y(s) - mtd->height < 2)
316: mtd->height = screen_size_y(s);
317: }
318:
319: void
320: mode_tree_free(struct mode_tree_data *mtd)
321: {
322: mode_tree_free_items(&mtd->children);
323: mode_tree_clear_lines(mtd);
324: screen_free(&mtd->screen);
325: free(mtd);
326: }
327:
328: void
329: mode_tree_resize(struct mode_tree_data *mtd, u_int sx, u_int sy)
330: {
331: struct screen *s = &mtd->screen;
332:
333: screen_resize(s, sx, sy, 0);
334:
335: mode_tree_build(mtd);
336: mode_tree_draw(mtd);
337:
338: mtd->wp->flags |= PANE_REDRAW;
339: }
340:
341: struct mode_tree_item *
342: mode_tree_add(struct mode_tree_data *mtd, struct mode_tree_item *parent,
343: void *itemdata, uint64_t tag, const char *name, const char *text,
344: int expanded)
345: {
346: struct mode_tree_item *mti, *saved;
347:
348: log_debug("%s: %llu, %s %s", __func__, (unsigned long long)tag,
349: name, text);
350:
351: mti = xcalloc(1, sizeof *mti);
352: mti->parent = parent;
353: mti->itemdata = itemdata;
354:
355: mti->tag = tag;
356: mti->name = xstrdup(name);
357: mti->text = xstrdup(text);
358:
359: saved = mode_tree_find_item(&mtd->saved, tag);
360: if (saved != NULL) {
361: if (parent == NULL || (parent != NULL && parent->expanded))
362: mti->tagged = saved->tagged;
363: mti->expanded = saved->expanded;
364: } else if (expanded == -1)
365: mti->expanded = 1;
366: else
367: mti->expanded = expanded;
368:
369: TAILQ_INIT(&mti->children);
370:
371: if (parent != NULL)
372: TAILQ_INSERT_TAIL(&parent->children, mti, entry);
373: else
374: TAILQ_INSERT_TAIL(&mtd->children, mti, entry);
375:
376: return (mti);
377: }
378:
379: void
380: mode_tree_remove(struct mode_tree_data *mtd, struct mode_tree_item *mti)
381: {
382: struct mode_tree_item *parent = mti->parent;
383:
384: if (parent != NULL)
385: TAILQ_REMOVE(&parent->children, mti, entry);
386: else
387: TAILQ_REMOVE(&mtd->children, mti, entry);
388: mode_tree_free_item(mti);
389: }
390:
391: void
392: mode_tree_draw(struct mode_tree_data *mtd)
393: {
394: struct window_pane *wp = mtd->wp;
395: struct screen *s = &mtd->screen, *box;
396: struct mode_tree_line *line;
397: struct mode_tree_item *mti;
398: struct options *oo = wp->window->options;
399: struct screen_write_ctx ctx;
400: struct grid_cell gc0, gc;
401: u_int w, h, i, j, sy, box_x, box_y;
402: char *text, *start, key[7];
403: const char *tag, *symbol;
404: size_t size;
405: int keylen;
406:
407: if (mtd->line_size == 0)
408: return;
409:
410: memcpy(&gc0, &grid_default_cell, sizeof gc0);
411: memcpy(&gc, &grid_default_cell, sizeof gc);
412: style_apply(&gc, oo, "mode-style");
413:
414: w = mtd->width;
415: h = mtd->height;
416:
417: screen_write_start(&ctx, NULL, s);
418: screen_write_clearscreen(&ctx, 8);
419:
420: if (mtd->line_size > 10)
421: keylen = 6;
422: else
423: keylen = 4;
424:
425: for (i = 0; i < mtd->line_size; i++) {
426: if (i < mtd->offset)
427: continue;
428: if (i > mtd->offset + h - 1)
429: break;
430:
431: line = &mtd->line_list[i];
432: mti = line->item;
433:
434: screen_write_cursormove(&ctx, 0, i - mtd->offset);
435:
436: if (i < 10)
437: snprintf(key, sizeof key, "(%c)", '0' + i);
438: else if (i < 36)
439: snprintf(key, sizeof key, "(M-%c)", 'a' + (i - 10));
440: else
441: *key = '\0';
442:
443: if (line->flat)
444: symbol = "";
445: else if (TAILQ_EMPTY(&mti->children))
446: symbol = " ";
447: else if (mti->expanded)
448: symbol = "- ";
449: else
450: symbol = "+ ";
451:
452: if (line->depth == 0)
453: start = xstrdup(symbol);
454: else {
455: size = (4 * line->depth) + 32;
456:
457: start = xcalloc(1, size);
458: for (j = 1; j < line->depth; j++) {
459: if (mti->parent != NULL &&
460: mtd->line_list[mti->parent->line].last)
461: strlcat(start, " ", size);
462: else
463: strlcat(start, "\001x\001 ", size);
464: }
465: if (line->last)
466: strlcat(start, "\001mq\001> ", size);
467: else
468: strlcat(start, "\001tq\001> ", size);
469: strlcat(start, symbol, size);
470: }
471:
472: if (mti->tagged)
473: tag = "*";
474: else
475: tag = "";
476: xasprintf(&text, "%-*s%s%s%s: %s", keylen, key, start,
477: mti->name, tag, mti->text);
478: free(start);
479:
480: if (mti->tagged) {
481: gc.attr ^= GRID_ATTR_BRIGHT;
482: gc0.attr ^= GRID_ATTR_BRIGHT;
483: }
484:
485: if (i != mtd->current) {
486: screen_write_puts(&ctx, &gc0, "%.*s", w, text);
487: screen_write_clearendofline(&ctx, 8);
488: } else
489: screen_write_puts(&ctx, &gc, "%-*.*s", w, w, text);
490: free(text);
491:
492: if (mti->tagged) {
493: gc.attr ^= GRID_ATTR_BRIGHT;
494: gc0.attr ^= GRID_ATTR_BRIGHT;
495: }
496: }
497:
498: sy = screen_size_y(s);
499: if (sy <= 4 || h <= 4 || sy - h <= 4 || w <= 4) {
500: screen_write_stop(&ctx);
501: return;
502: }
503:
504: line = &mtd->line_list[mtd->current];
505: mti = line->item;
506:
507: screen_write_cursormove(&ctx, 0, h);
508: screen_write_box(&ctx, w, sy - h);
509:
510: xasprintf(&text, " %s (sort: %s) ", mti->name,
511: mtd->sort_list[mtd->sort_type]);
512: if (w - 2 >= strlen(text)) {
513: screen_write_cursormove(&ctx, 1, h);
514: screen_write_puts(&ctx, &gc0, "%s", text);
515: }
516: free(text);
517:
518: box_x = w - 4;
519: box_y = sy - h - 2;
520:
521: box = mtd->drawcb(mtd->modedata, mti->itemdata, box_x, box_y);
522: if (box != NULL) {
523: screen_write_cursormove(&ctx, 2, h + 1);
524: screen_write_copy(&ctx, box, 0, 0, box_x, box_y, NULL, NULL);
525:
526: screen_free(box);
527: }
528:
529: screen_write_stop(&ctx);
530: }
531:
532: int
533: mode_tree_key(struct mode_tree_data *mtd, key_code *key, struct mouse_event *m)
534: {
535: struct mode_tree_line *line;
536: struct mode_tree_item *current, *parent;
537: u_int i, x, y;
538: int choice;
539: key_code tmp;
540:
541: if (*key == KEYC_MOUSEDOWN1_PANE) {
542: if (cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) {
543: *key = KEYC_NONE;
544: return (0);
545: }
546: if (x > mtd->width || y > mtd->height) {
547: *key = KEYC_NONE;
548: return (0);
549: }
550: if (mtd->offset + y < mtd->line_size) {
551: mtd->current = mtd->offset + y;
552: *key = '\r';
553: return (0);
554: }
555: }
556:
557: line = &mtd->line_list[mtd->current];
558: current = line->item;
559:
560: choice = -1;
561: if (*key >= '0' && *key <= '9')
562: choice = (*key) - '0';
563: else if (((*key) & KEYC_MASK_MOD) == KEYC_ESCAPE) {
564: tmp = (*key) & KEYC_MASK_KEY;
565: if (tmp >= 'a' && tmp <= 'z')
566: choice = 10 + (tmp - 'a');
567: }
568: if (choice != -1) {
569: if ((u_int)choice > mtd->line_size - 1) {
570: *key = KEYC_NONE;
571: return (0);
572: }
573: mtd->current = choice;
574: *key = '\r';
575: return (0);
576: }
577:
578: switch (*key) {
579: case 'q':
580: case '\033': /* Escape */
581: return (1);
582: case KEYC_UP:
583: case 'k':
584: case KEYC_WHEELUP_PANE:
585: mode_tree_up(mtd, 1);
586: break;
587: case KEYC_DOWN:
588: case 'j':
589: case KEYC_WHEELDOWN_PANE:
590: mode_tree_down(mtd, 1);
591: break;
592: case KEYC_PPAGE:
593: case '\002': /* C-b */
594: for (i = 0; i < mtd->height; i++) {
595: if (mtd->current == 0)
596: break;
597: mode_tree_up(mtd, 1);
598: }
599: break;
600: case KEYC_NPAGE:
601: case '\006': /* C-f */
602: for (i = 0; i < mtd->height; i++) {
603: if (mtd->current == mtd->line_size - 1)
604: break;
605: mode_tree_down(mtd, 1);
606: }
607: break;
608: case KEYC_HOME:
609: mtd->current = 0;
610: mtd->offset = 0;
611: break;
612: case KEYC_END:
613: mtd->current = mtd->line_size - 1;
614: if (mtd->current > mtd->height - 1)
615: mtd->offset = mtd->current - mtd->height;
616: else
617: mtd->offset = 0;
618: break;
619: case 't':
620: /*
621: * Do not allow parents and children to both be tagged: untag
622: * all parents and children of current.
623: */
624: if (!current->tagged) {
625: parent = current->parent;
626: while (parent != NULL) {
627: parent->tagged = 0;
628: parent = parent->parent;
629: }
630: mode_tree_clear_tagged(¤t->children);
631: current->tagged = 1;
632: } else
633: current->tagged = 0;
634: mode_tree_down(mtd, 0);
635: break;
636: case 'T':
637: for (i = 0; i < mtd->line_size; i++)
638: mtd->line_list[i].item->tagged = 0;
639: break;
640: case '\024': /* C-t */
641: for (i = 0; i < mtd->line_size; i++) {
642: if (mtd->line_list[i].item->parent == NULL)
643: mtd->line_list[i].item->tagged = 1;
644: else
645: mtd->line_list[i].item->tagged = 0;
646: }
647: break;
648: case 'O':
649: mtd->sort_type++;
650: if (mtd->sort_type == mtd->sort_size)
651: mtd->sort_type = 0;
652: mode_tree_build(mtd);
653: break;
654: case KEYC_LEFT:
655: case '-':
656: if (line->flat || !current->expanded)
657: current = current->parent;
658: if (current == NULL)
659: mode_tree_up(mtd, 0);
660: else {
661: current->expanded = 0;
662: mtd->current = current->line;
663: mode_tree_build(mtd);
664: }
665: break;
666: case KEYC_RIGHT:
667: case '+':
668: if (line->flat || current->expanded)
669: mode_tree_down(mtd, 0);
670: else if (!line->flat) {
671: current->expanded = 1;
672: mode_tree_build(mtd);
673: }
674: break;
675: }
676: return (0);
677: }
678:
679: void
680: mode_tree_run_command(struct client *c, struct cmd_find_state *fs,
681: const char *template, const char *name)
682: {
683: struct cmdq_item *new_item;
684: struct cmd_list *cmdlist;
685: char *command, *cause;
686:
687: command = cmd_template_replace(template, name, 1);
1.2 ! nicm 688: if (command == NULL || *command == '\0') {
! 689: free(command);
1.1 nicm 690: return;
1.2 ! nicm 691: }
1.1 nicm 692:
693: cmdlist = cmd_string_parse(command, NULL, 0, &cause);
694: if (cmdlist == NULL) {
695: if (cause != NULL && c != NULL) {
696: *cause = toupper((u_char)*cause);
697: status_message_set(c, "%s", cause);
698: }
699: free(cause);
700: } else {
701: new_item = cmdq_get_command(cmdlist, fs, NULL, 0);
702: cmdq_append(c, new_item);
703: cmd_list_free(cmdlist);
704: }
705:
706: free(command);
707: }