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