Annotation of src/usr.bin/tmux/window-tree.c, Revision 1.2
1.2 ! nicm 1: /* $OpenBSD: window-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 <stdlib.h>
22: #include <string.h>
23:
24: #include "tmux.h"
25:
26: static struct screen *window_tree_init(struct window_pane *,
27: struct cmd_find_state *, struct args *);
28: static void window_tree_free(struct window_pane *);
29: static void window_tree_resize(struct window_pane *, u_int, u_int);
30: static void window_tree_key(struct window_pane *,
31: struct client *, struct session *, key_code,
32: struct mouse_event *);
33:
34: #define WINDOW_TREE_DEFAULT_COMMAND "switch-client -t '%%'"
35:
36: const struct window_mode window_tree_mode = {
37: .name = "tree-mode",
38:
39: .init = window_tree_init,
40: .free = window_tree_free,
41: .resize = window_tree_resize,
42: .key = window_tree_key,
43: };
44:
45: enum window_tree_sort_type {
46: WINDOW_TREE_BY_INDEX,
47: WINDOW_TREE_BY_NAME,
48: WINDOW_TREE_BY_TIME,
49: };
50: static const char *window_tree_sort_list[] = {
51: "index",
52: "name",
53: "time"
54: };
55:
56: enum window_tree_type {
57: WINDOW_TREE_NONE,
58: WINDOW_TREE_SESSION,
59: WINDOW_TREE_WINDOW,
60: WINDOW_TREE_PANE,
61: };
62:
63: struct window_tree_itemdata {
64: enum window_tree_type type;
65: int session;
66: int winlink;
67: int pane;
68: };
69:
70: struct window_tree_modedata {
71: struct window_pane *wp;
72: int dead;
73: int references;
74:
75: struct mode_tree_data *data;
76: char *command;
77:
78: struct window_tree_itemdata **item_list;
79: u_int item_size;
80:
81: struct client *client;
82: const char *entered;
83:
84: char *filter;
85:
86: struct cmd_find_state fs;
87: enum window_tree_type type;
88: };
89:
90: static void
91: window_tree_pull_item(struct window_tree_itemdata *item, struct session **sp,
92: struct winlink **wlp, struct window_pane **wp)
93: {
94: *wp = NULL;
95: *wlp = NULL;
96: *sp = session_find_by_id(item->session);
97: if (*sp == NULL)
98: return;
99: if (item->type == WINDOW_TREE_SESSION) {
100: *wlp = (*sp)->curw;
101: *wp = (*wlp)->window->active;
102: return;
103: }
104:
105: *wlp = winlink_find_by_index(&(*sp)->windows, item->winlink);
106: if (*wlp == NULL) {
107: *sp = NULL;
108: return;
109: }
110: if (item->type == WINDOW_TREE_WINDOW) {
111: *wp = (*wlp)->window->active;
112: return;
113: }
114:
115: *wp = window_pane_find_by_id(item->pane);
116: if (!window_has_pane((*wlp)->window, *wp))
117: *wp = NULL;
118: if (*wp == NULL) {
119: *sp = NULL;
120: *wlp = NULL;
121: return;
122: }
123: }
124:
125: static struct window_tree_itemdata *
126: window_tree_add_item(struct window_tree_modedata *data)
127: {
128: struct window_tree_itemdata *item;
129:
130: data->item_list = xreallocarray(data->item_list, data->item_size + 1,
131: sizeof *data->item_list);
132: item = data->item_list[data->item_size++] = xcalloc(1, sizeof *item);
133: return (item);
134: }
135:
136: static void
137: window_tree_free_item(struct window_tree_itemdata *item)
138: {
139: free(item);
140: }
141:
142: static int
143: window_tree_cmp_session_name(const void *a0, const void *b0)
144: {
145: const struct session *const *a = a0;
146: const struct session *const *b = b0;
147:
148: return (strcmp((*a)->name, (*b)->name));
149: }
150:
151: static int
152: window_tree_cmp_session_time(const void *a0, const void *b0)
153: {
154: const struct session *const *a = a0;
155: const struct session *const *b = b0;
156:
157: if (timercmp(&(*a)->activity_time, &(*b)->activity_time, >))
158: return (-1);
159: if (timercmp(&(*a)->activity_time, &(*b)->activity_time, <))
160: return (1);
161: return (strcmp((*a)->name, (*b)->name));
162: }
163:
164: static int
165: window_tree_cmp_window_name(const void *a0, const void *b0)
166: {
167: const struct winlink *const *a = a0;
168: const struct winlink *const *b = b0;
169:
170: return (strcmp((*a)->window->name, (*b)->window->name));
171: }
172:
173: static int
174: window_tree_cmp_window_time(const void *a0, const void *b0)
175: {
176: const struct winlink *const *a = a0;
177: const struct winlink *const *b = b0;
178:
179: if (timercmp(&(*a)->window->activity_time,
180: &(*b)->window->activity_time, >))
181: return (-1);
182: if (timercmp(&(*a)->window->activity_time,
183: &(*b)->window->activity_time, <))
184: return (1);
185: return (strcmp((*a)->window->name, (*b)->window->name));
186: }
187:
188: static int
189: window_tree_cmp_pane_time(const void *a0, const void *b0)
190: {
191: const struct window_pane *const *a = a0;
192: const struct window_pane *const *b = b0;
193:
194: if ((*a)->active_point < (*b)->active_point)
195: return (-1);
196: if ((*a)->active_point > (*b)->active_point)
197: return (1);
198: return (0);
199: }
200:
201: static void
202: window_tree_build_pane(struct session *s, struct winlink *wl,
203: struct window_pane *wp, void *modedata, struct mode_tree_item *parent)
204: {
205: struct window_tree_modedata *data = modedata;
206: struct window_tree_itemdata *item;
207: char *name, *text;
208: u_int idx;
209:
210: window_pane_index(wp, &idx);
211:
212: item = window_tree_add_item(data);
213: item->type = WINDOW_TREE_PANE;
214: item->session = s->id;
215: item->winlink = wl->idx;
216: item->pane = wp->id;
217:
218: text = format_single(NULL,
219: "#{pane_current_command} \"#{pane_title}\"",
220: NULL, s, wl, wp);
221: xasprintf(&name, "%u", idx);
222:
223: mode_tree_add(data->data, parent, item, (uint64_t)wp, name, text, -1);
224: free(text);
225: free(name);
226: }
227:
228: static int
229: window_tree_build_window(struct session *s, struct winlink *wl, void* modedata,
230: u_int sort_type, struct mode_tree_item *parent, int no_filter)
231: {
232: struct window_tree_modedata *data = modedata;
233: struct window_tree_itemdata *item;
234: struct mode_tree_item *mti;
235: char *name, *text, *cp;
236: struct window_pane *wp, **l;
237: u_int n, i;
238: int expanded;
239:
240: item = window_tree_add_item(data);
241: item->type = WINDOW_TREE_WINDOW;
242: item->session = s->id;
243: item->winlink = wl->idx;
244: item->pane = -1;
245:
246: text = format_single(NULL,
247: "#{window_name}#{window_flags} (#{window_panes} panes)",
248: NULL, s, wl, NULL);
249: xasprintf(&name, "%u", wl->idx);
250:
251: if (data->type == WINDOW_TREE_SESSION ||
252: data->type == WINDOW_TREE_WINDOW)
253: expanded = 0;
254: else
255: expanded = 1;
256: mti = mode_tree_add(data->data, parent, item, (uint64_t)wl, name, text,
257: expanded);
258: free(text);
259: free(name);
260:
261: l = NULL;
262: n = 0;
263: TAILQ_FOREACH(wp, &wl->window->panes, entry) {
264: if (!no_filter && data->filter != NULL) {
265: cp = format_single(NULL, data->filter, NULL, s, wl, wp);
266: if (!format_true(cp)) {
267: free(cp);
268: continue;
269: }
270: free(cp);
271: }
272: l = xreallocarray(l, n + 1, sizeof *l);
273: l[n++] = wp;
274: }
275: if (n == 0) {
276: window_tree_free_item(item);
277: data->item_size--;
278: mode_tree_remove(data->data, mti);
279: return (0);
280: }
281:
282: switch (sort_type) {
283: case WINDOW_TREE_BY_INDEX:
284: break;
285: case WINDOW_TREE_BY_NAME:
286: /* Panes don't have names, so leave in number order. */
287: break;
288: case WINDOW_TREE_BY_TIME:
289: qsort(l, n, sizeof *l, window_tree_cmp_pane_time);
290: break;
291: }
292:
293: for (i = 0; i < n; i++)
294: window_tree_build_pane(s, wl, l[i], modedata, mti);
295: free(l);
296: return (1);
297: }
298:
299: static void
300: window_tree_build_session(struct session *s, void* modedata,
301: u_int sort_type, int no_filter)
302: {
303: struct window_tree_modedata *data = modedata;
304: struct window_tree_itemdata *item;
305: struct mode_tree_item *mti;
306: char *text;
307: struct winlink *wl, **l;
308: u_int n, i, empty;
309: int expanded;
310:
311: item = window_tree_add_item(data);
312: item->type = WINDOW_TREE_SESSION;
313: item->session = s->id;
314: item->winlink = -1;
315: item->pane = -1;
316:
317: text = format_single(NULL,
318: "#{session_windows} windows"
319: "#{?session_grouped, (group ,}"
320: "#{session_group}#{?session_grouped,),}"
321: "#{?session_attached, (attached),}",
322: NULL, s, NULL, NULL);
323:
324: if (data->type == WINDOW_TREE_SESSION)
325: expanded = 0;
326: else
327: expanded = 1;
328: mti = mode_tree_add(data->data, NULL, item, (uint64_t)s, s->name, text,
329: expanded);
330: free(text);
331:
332: l = NULL;
333: n = 0;
334: RB_FOREACH(wl, winlinks, &s->windows) {
335: l = xreallocarray(l, n + 1, sizeof *l);
336: l[n++] = wl;
337: }
338: switch (sort_type) {
339: case WINDOW_TREE_BY_INDEX:
340: break;
341: case WINDOW_TREE_BY_NAME:
342: qsort(l, n, sizeof *l, window_tree_cmp_window_name);
343: break;
344: case WINDOW_TREE_BY_TIME:
345: qsort(l, n, sizeof *l, window_tree_cmp_window_time);
346: break;
347: }
348:
349: empty = 0;
350: for (i = 0; i < n; i++) {
351: if (!window_tree_build_window(s, l[i], modedata, sort_type, mti,
352: no_filter))
353: empty++;
354: }
355: if (empty == n) {
356: window_tree_free_item(item);
357: data->item_size--;
358: mode_tree_remove(data->data, mti);
359: }
360: free(l);
361: }
362:
363: static void
364: window_tree_build(void *modedata, u_int sort_type, uint64_t *tag)
365: {
366: struct window_tree_modedata *data = modedata;
367: struct session *s, **l;
368: u_int n, i;
369: int no_filter = 0;
370:
371: restart:
372: for (i = 0; i < data->item_size; i++)
373: window_tree_free_item(data->item_list[i]);
374: free(data->item_list);
375: data->item_list = NULL;
376: data->item_size = 0;
377:
378: l = NULL;
379: n = 0;
380: RB_FOREACH(s, sessions, &sessions) {
381: l = xreallocarray(l, n + 1, sizeof *l);
382: l[n++] = s;
383: }
384: switch (sort_type) {
385: case WINDOW_TREE_BY_INDEX:
386: break;
387: case WINDOW_TREE_BY_NAME:
388: qsort(l, n, sizeof *l, window_tree_cmp_session_name);
389: break;
390: case WINDOW_TREE_BY_TIME:
391: qsort(l, n, sizeof *l, window_tree_cmp_session_time);
392: break;
393: }
394:
395: for (i = 0; i < n; i++)
396: window_tree_build_session(l[i], modedata, sort_type, no_filter);
397: free(l);
398:
399: if (!no_filter && data->item_size == 0) {
400: no_filter = 1;
401: goto restart;
402: }
403:
404: switch (data->type) {
405: case WINDOW_TREE_NONE:
406: break;
407: case WINDOW_TREE_SESSION:
408: *tag = (uint64_t)data->fs.s;
409: break;
410: case WINDOW_TREE_WINDOW:
411: *tag = (uint64_t)data->fs.wl;
412: break;
413: case WINDOW_TREE_PANE:
414: *tag = (uint64_t)data->fs.wp;
415: break;
416: }
417: }
418:
419: static struct screen *
420: window_tree_draw(__unused void *modedata, void *itemdata, u_int sx, u_int sy)
421: {
422: struct window_tree_itemdata *item = itemdata;
423: struct session *sp;
424: struct winlink *wlp;
425: struct window_pane *wp;
426: static struct screen s;
427: struct screen_write_ctx ctx;
428:
429: window_tree_pull_item(item, &sp, &wlp, &wp);
430: if (wp == NULL)
431: return (NULL);
432:
433: screen_init(&s, sx, sy, 0);
434:
435: screen_write_start(&ctx, NULL, &s);
436:
437: screen_write_preview(&ctx, &wp->base, sx, sy);
438:
439: screen_write_stop(&ctx);
440: return (&s);
441: }
442:
443: static struct screen *
444: window_tree_init(struct window_pane *wp, struct cmd_find_state *fs,
445: struct args *args)
446: {
447: struct window_tree_modedata *data;
448: struct screen *s;
449:
450: wp->modedata = data = xcalloc(1, sizeof *data);
451:
452: if (args_has(args, 's'))
453: data->type = WINDOW_TREE_SESSION;
454: else if (args_has(args, 'w'))
455: data->type = WINDOW_TREE_WINDOW;
456: else
457: data->type = WINDOW_TREE_PANE;
458: memcpy(&data->fs, fs, sizeof data->fs);
459:
460: data->wp = wp;
461: data->references = 1;
462:
463: if (args_has(args, 'f'))
464: data->filter = xstrdup(args_get(args, 'f'));
465: else
466: data->filter = NULL;
467:
468: if (args == NULL || args->argc == 0)
469: data->command = xstrdup(WINDOW_TREE_DEFAULT_COMMAND);
470: else
471: data->command = xstrdup(args->argv[0]);
472:
473: data->data = mode_tree_start(wp, window_tree_build,
474: window_tree_draw, data, window_tree_sort_list,
475: nitems(window_tree_sort_list), &s);
476:
477: mode_tree_build(data->data);
478: mode_tree_draw(data->data);
479:
480: data->type = WINDOW_TREE_NONE;
481:
482: return (s);
483: }
484:
485: static void
486: window_tree_destroy(struct window_tree_modedata *data)
487: {
488: u_int i;
489:
490: if (--data->references != 0)
491: return;
492:
493: mode_tree_free(data->data);
494:
495: for (i = 0; i < data->item_size; i++)
496: window_tree_free_item(data->item_list[i]);
497: free(data->item_list);
498:
499: free(data->filter);
500:
501: free(data->command);
502: free(data);
503: }
504:
505: static void
506: window_tree_free(struct window_pane *wp)
507: {
508: struct window_tree_modedata *data = wp->modedata;
509:
510: if (data == NULL)
511: return;
512:
513: data->dead = 1;
514: window_tree_destroy(data);
515: }
516:
517: static void
518: window_tree_resize(struct window_pane *wp, u_int sx, u_int sy)
519: {
520: struct window_tree_modedata *data = wp->modedata;
521:
522: mode_tree_resize(data->data, sx, sy);
523: }
524:
525: static char *
526: window_tree_get_target(struct window_tree_itemdata *item,
527: struct cmd_find_state *fs)
528: {
529: struct session *s;
530: struct winlink *wl;
531: struct window_pane *wp;
532: char *target;
533:
534: window_tree_pull_item(item, &s, &wl, &wp);
535:
536: target = NULL;
537: switch (item->type) {
538: case WINDOW_TREE_NONE:
539: break;
540: case WINDOW_TREE_SESSION:
541: if (s == NULL)
542: break;
543: xasprintf(&target, "=%s:", s->name);
544: break;
545: case WINDOW_TREE_WINDOW:
546: if (s == NULL || wl == NULL)
547: break;
548: xasprintf(&target, "=%s:%u.", s->name, wl->idx);
549: break;
550: case WINDOW_TREE_PANE:
551: if (s == NULL || wl == NULL || wp == NULL)
552: break;
553: xasprintf(&target, "=%s:%u.%%%u", s->name, wl->idx, wp->id);
554: break;
555: }
556: if (target == NULL)
557: cmd_find_clear_state(fs, 0);
558: else
559: cmd_find_from_winlink_pane(fs, wl, wp);
560: return (target);
561: }
562:
563: static void
564: window_tree_command_each(void* modedata, void* itemdata, __unused key_code key)
565: {
566: struct window_tree_modedata *data = modedata;
567: struct window_tree_itemdata *item = itemdata;
568: char *name;
569: struct cmd_find_state fs;
570:
571: name = window_tree_get_target(item, &fs);
572: if (name != NULL)
573: mode_tree_run_command(data->client, &fs, data->entered, name);
574: free(name);
575: }
576:
577: static enum cmd_retval
578: window_tree_command_done(__unused struct cmdq_item *item, void *modedata)
579: {
580: struct window_tree_modedata *data = modedata;
581:
582: if (!data->dead) {
583: mode_tree_build(data->data);
584: mode_tree_draw(data->data);
585: data->wp->flags |= PANE_REDRAW;
586: }
587: window_tree_destroy(data);
588: return (CMD_RETURN_NORMAL);
589: }
590:
591: static int
592: window_tree_command_callback(struct client *c, void *modedata, const char *s,
593: __unused int done)
594: {
595: struct window_tree_modedata *data = modedata;
596:
597: if (data->dead)
598: return (0);
599:
600: data->client = c;
601: data->entered = s;
602:
603: mode_tree_each_tagged(data->data, window_tree_command_each, KEYC_NONE,
604: 1);
605:
606: data->client = NULL;
607: data->entered = NULL;
608:
609: data->references++;
610: cmdq_append(c, cmdq_get_callback(window_tree_command_done, data));
611:
612: return (0);
613: }
614:
615: static void
616: window_tree_command_free(void *modedata)
617: {
618: struct window_tree_modedata *data = modedata;
619:
620: window_tree_destroy(data);
621: }
622:
623: static int
624: window_tree_filter_callback(__unused struct client *c, void *modedata,
625: const char *s, __unused int done)
626: {
627: struct window_tree_modedata *data = modedata;
628:
629: if (data->dead)
630: return (0);
631:
632: if (data->filter != NULL)
633: free(data->filter);
634: if (s == NULL || *s == '\0')
635: data->filter = NULL;
636: else
637: data->filter = xstrdup(s);
638:
639: mode_tree_build(data->data);
640: mode_tree_draw(data->data);
641: data->wp->flags |= PANE_REDRAW;
642:
643: return (0);
644: }
645:
646: static void
647: window_tree_filter_free(void *modedata)
648: {
649: struct window_tree_modedata *data = modedata;
650:
651: window_tree_destroy(data);
652: }
653:
654: static void
655: window_tree_key(struct window_pane *wp, struct client *c,
656: __unused struct session *s, key_code key, struct mouse_event *m)
657: {
658: struct window_tree_modedata *data = wp->modedata;
659: struct window_tree_itemdata *item;
660: char *command, *name, *prompt;
661: struct cmd_find_state fs;
662: int finished;
663: u_int tagged;
664:
665: /*
666: * t = toggle tag
667: * T = tag none
668: * C-t = tag all
669: * q = exit
670: * O = change sort order
671: *
672: * Enter = select item
673: * : = enter command
674: * f = enter filter
675: */
676:
677: finished = mode_tree_key(data->data, &key, m);
678: switch (key) {
679: case 'f':
680: data->references++;
681: status_prompt_set(c, "(filter) ", data->filter,
682: window_tree_filter_callback, window_tree_filter_free, data,
683: PROMPT_NOFORMAT);
684: break;
685: case ':':
686: tagged = mode_tree_count_tagged(data->data);
687: if (tagged != 0)
688: xasprintf(&prompt, "(%u tagged) ", tagged);
689: else
690: xasprintf(&prompt, "(current) ");
691: data->references++;
692: status_prompt_set(c, prompt, "", window_tree_command_callback,
693: window_tree_command_free, data, PROMPT_NOFORMAT);
694: free(prompt);
695: break;
696: case '\r':
697: item = mode_tree_get_current(data->data);
698: command = xstrdup(data->command);
699: name = window_tree_get_target(item, &fs);
700: window_pane_reset_mode(wp);
701: if (name != NULL)
1.2 ! nicm 702: mode_tree_run_command(c, NULL, command, name);
1.1 nicm 703: free(name);
704: free(command);
705: return;
706: }
707: if (finished)
708: window_pane_reset_mode(wp);
709: else {
710: mode_tree_draw(data->data);
711: wp->flags |= PANE_REDRAW;
712: }
713: }