Annotation of src/usr.bin/tmux/window-tree.c, Revision 1.3
1.3 ! nicm 1: /* $OpenBSD: window-tree.c,v 1.2 2017/06/06 15:07:35 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:
1.3 ! nicm 443: static int
! 444: window_tree_search(__unused void *modedata, void *itemdata, const char *ss)
! 445: {
! 446: struct window_tree_itemdata *item = itemdata;
! 447: struct session *s;
! 448: struct winlink *wl;
! 449: struct window_pane *wp;
! 450: const char *cmd;
! 451:
! 452: window_tree_pull_item(item, &s, &wl, &wp);
! 453:
! 454: switch (item->type) {
! 455: case WINDOW_TREE_NONE:
! 456: return (0);
! 457: case WINDOW_TREE_SESSION:
! 458: if (s == NULL)
! 459: return (0);
! 460: return (strstr(s->name, ss) != NULL);
! 461: case WINDOW_TREE_WINDOW:
! 462: if (s == NULL || wl == NULL)
! 463: return (0);
! 464: return (strstr(wl->window->name, ss) != NULL);
! 465: case WINDOW_TREE_PANE:
! 466: if (s == NULL || wl == NULL || wp == NULL)
! 467: break;
! 468: cmd = get_proc_name(wp->fd, wp->tty);
! 469: if (cmd == NULL || *cmd == '\0')
! 470: return (0);
! 471: return (strstr(cmd, ss) != NULL);
! 472: }
! 473: return (0);
! 474: }
! 475:
1.1 nicm 476: static struct screen *
477: window_tree_init(struct window_pane *wp, struct cmd_find_state *fs,
478: struct args *args)
479: {
480: struct window_tree_modedata *data;
481: struct screen *s;
482:
483: wp->modedata = data = xcalloc(1, sizeof *data);
484:
485: if (args_has(args, 's'))
486: data->type = WINDOW_TREE_SESSION;
487: else if (args_has(args, 'w'))
488: data->type = WINDOW_TREE_WINDOW;
489: else
490: data->type = WINDOW_TREE_PANE;
491: memcpy(&data->fs, fs, sizeof data->fs);
492:
493: data->wp = wp;
494: data->references = 1;
495:
496: if (args_has(args, 'f'))
497: data->filter = xstrdup(args_get(args, 'f'));
498: else
499: data->filter = NULL;
500:
501: if (args == NULL || args->argc == 0)
502: data->command = xstrdup(WINDOW_TREE_DEFAULT_COMMAND);
503: else
504: data->command = xstrdup(args->argv[0]);
505:
1.3 ! nicm 506: data->data = mode_tree_start(wp, window_tree_build, window_tree_draw,
! 507: window_tree_search, data, window_tree_sort_list,
1.1 nicm 508: nitems(window_tree_sort_list), &s);
509:
510: mode_tree_build(data->data);
511: mode_tree_draw(data->data);
512:
513: data->type = WINDOW_TREE_NONE;
514:
515: return (s);
516: }
517:
518: static void
519: window_tree_destroy(struct window_tree_modedata *data)
520: {
521: u_int i;
522:
523: if (--data->references != 0)
524: return;
525:
526: mode_tree_free(data->data);
527:
528: for (i = 0; i < data->item_size; i++)
529: window_tree_free_item(data->item_list[i]);
530: free(data->item_list);
531:
532: free(data->filter);
533:
534: free(data->command);
535: free(data);
536: }
537:
538: static void
539: window_tree_free(struct window_pane *wp)
540: {
541: struct window_tree_modedata *data = wp->modedata;
542:
543: if (data == NULL)
544: return;
545:
546: data->dead = 1;
547: window_tree_destroy(data);
548: }
549:
550: static void
551: window_tree_resize(struct window_pane *wp, u_int sx, u_int sy)
552: {
553: struct window_tree_modedata *data = wp->modedata;
554:
555: mode_tree_resize(data->data, sx, sy);
556: }
557:
558: static char *
559: window_tree_get_target(struct window_tree_itemdata *item,
560: struct cmd_find_state *fs)
561: {
562: struct session *s;
563: struct winlink *wl;
564: struct window_pane *wp;
565: char *target;
566:
567: window_tree_pull_item(item, &s, &wl, &wp);
568:
569: target = NULL;
570: switch (item->type) {
571: case WINDOW_TREE_NONE:
572: break;
573: case WINDOW_TREE_SESSION:
574: if (s == NULL)
575: break;
576: xasprintf(&target, "=%s:", s->name);
577: break;
578: case WINDOW_TREE_WINDOW:
579: if (s == NULL || wl == NULL)
580: break;
581: xasprintf(&target, "=%s:%u.", s->name, wl->idx);
582: break;
583: case WINDOW_TREE_PANE:
584: if (s == NULL || wl == NULL || wp == NULL)
585: break;
586: xasprintf(&target, "=%s:%u.%%%u", s->name, wl->idx, wp->id);
587: break;
588: }
589: if (target == NULL)
590: cmd_find_clear_state(fs, 0);
591: else
592: cmd_find_from_winlink_pane(fs, wl, wp);
593: return (target);
594: }
595:
596: static void
597: window_tree_command_each(void* modedata, void* itemdata, __unused key_code key)
598: {
599: struct window_tree_modedata *data = modedata;
600: struct window_tree_itemdata *item = itemdata;
601: char *name;
602: struct cmd_find_state fs;
603:
604: name = window_tree_get_target(item, &fs);
605: if (name != NULL)
606: mode_tree_run_command(data->client, &fs, data->entered, name);
607: free(name);
608: }
609:
610: static enum cmd_retval
611: window_tree_command_done(__unused struct cmdq_item *item, void *modedata)
612: {
613: struct window_tree_modedata *data = modedata;
614:
615: if (!data->dead) {
616: mode_tree_build(data->data);
617: mode_tree_draw(data->data);
618: data->wp->flags |= PANE_REDRAW;
619: }
620: window_tree_destroy(data);
621: return (CMD_RETURN_NORMAL);
622: }
623:
624: static int
625: window_tree_command_callback(struct client *c, void *modedata, const char *s,
626: __unused int done)
627: {
628: struct window_tree_modedata *data = modedata;
629:
630: if (data->dead)
631: return (0);
632:
633: data->client = c;
634: data->entered = s;
635:
636: mode_tree_each_tagged(data->data, window_tree_command_each, KEYC_NONE,
637: 1);
638:
639: data->client = NULL;
640: data->entered = NULL;
641:
642: data->references++;
643: cmdq_append(c, cmdq_get_callback(window_tree_command_done, data));
644:
645: return (0);
646: }
647:
648: static void
649: window_tree_command_free(void *modedata)
650: {
651: struct window_tree_modedata *data = modedata;
652:
653: window_tree_destroy(data);
654: }
655:
656: static int
657: window_tree_filter_callback(__unused struct client *c, void *modedata,
658: const char *s, __unused int done)
659: {
660: struct window_tree_modedata *data = modedata;
661:
662: if (data->dead)
663: return (0);
664:
665: if (data->filter != NULL)
666: free(data->filter);
667: if (s == NULL || *s == '\0')
668: data->filter = NULL;
669: else
670: data->filter = xstrdup(s);
671:
672: mode_tree_build(data->data);
673: mode_tree_draw(data->data);
674: data->wp->flags |= PANE_REDRAW;
675:
676: return (0);
677: }
678:
679: static void
680: window_tree_filter_free(void *modedata)
681: {
682: struct window_tree_modedata *data = modedata;
683:
684: window_tree_destroy(data);
685: }
686:
687: static void
688: window_tree_key(struct window_pane *wp, struct client *c,
689: __unused struct session *s, key_code key, struct mouse_event *m)
690: {
691: struct window_tree_modedata *data = wp->modedata;
692: struct window_tree_itemdata *item;
693: char *command, *name, *prompt;
694: struct cmd_find_state fs;
695: int finished;
696: u_int tagged;
697:
698: /*
699: * t = toggle tag
700: * T = tag none
701: * C-t = tag all
702: * q = exit
703: * O = change sort order
704: *
705: * Enter = select item
706: * : = enter command
707: * f = enter filter
708: */
709:
1.3 ! nicm 710: finished = mode_tree_key(data->data, c, &key, m);
1.1 nicm 711: switch (key) {
712: case 'f':
713: data->references++;
714: status_prompt_set(c, "(filter) ", data->filter,
715: window_tree_filter_callback, window_tree_filter_free, data,
716: PROMPT_NOFORMAT);
717: break;
718: case ':':
719: tagged = mode_tree_count_tagged(data->data);
720: if (tagged != 0)
721: xasprintf(&prompt, "(%u tagged) ", tagged);
722: else
723: xasprintf(&prompt, "(current) ");
724: data->references++;
725: status_prompt_set(c, prompt, "", window_tree_command_callback,
726: window_tree_command_free, data, PROMPT_NOFORMAT);
727: free(prompt);
728: break;
729: case '\r':
730: item = mode_tree_get_current(data->data);
731: command = xstrdup(data->command);
732: name = window_tree_get_target(item, &fs);
733: window_pane_reset_mode(wp);
734: if (name != NULL)
1.2 nicm 735: mode_tree_run_command(c, NULL, command, name);
1.1 nicm 736: free(name);
737: free(command);
738: return;
739: }
740: if (finished)
741: window_pane_reset_mode(wp);
742: else {
743: mode_tree_draw(data->data);
744: wp->flags |= PANE_REDRAW;
745: }
746: }