1 | /* $OpenBSD$ */ |
2 | |
3 | /* |
4 | * Copyright (c) 2017 Nicholas Marriott <[email protected]> |
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 <stdlib.h> |
23 | #include <string.h> |
24 | |
25 | #include "tmux.h" |
26 | |
27 | static struct screen *window_tree_init(struct window_mode_entry *, |
28 | struct cmd_find_state *, struct args *); |
29 | static void window_tree_free(struct window_mode_entry *); |
30 | static void window_tree_resize(struct window_mode_entry *, u_int, |
31 | u_int); |
32 | static void window_tree_key(struct window_mode_entry *, |
33 | struct client *, struct session *, |
34 | struct winlink *, key_code, struct mouse_event *); |
35 | |
36 | #define WINDOW_TREE_DEFAULT_COMMAND "switch-client -Zt '%%'" |
37 | |
38 | #define WINDOW_TREE_DEFAULT_FORMAT \ |
39 | "#{?pane_format," \ |
40 | "#{pane_current_command} \"#{pane_title}\"" \ |
41 | "," \ |
42 | "#{?window_format," \ |
43 | "#{window_name}#{window_flags} " \ |
44 | "(#{window_panes} panes)" \ |
45 | "#{?#{==:#{window_panes},1}, \"#{pane_title}\",}" \ |
46 | "," \ |
47 | "#{session_windows} windows" \ |
48 | "#{?session_grouped, " \ |
49 | "(group #{session_group}: " \ |
50 | "#{session_group_list})," \ |
51 | "}" \ |
52 | "#{?session_attached, (attached),}" \ |
53 | "}" \ |
54 | "}" |
55 | |
56 | static const struct menu_item [] = { |
57 | { "Select" , 'E', NULL }, |
58 | { "Expand" , 'R', NULL }, |
59 | { "" , KEYC_NONE, NULL }, |
60 | { "Tag" , 't', NULL }, |
61 | { "Tag All" , '\024', NULL }, |
62 | { "Tag None" , 'T', NULL }, |
63 | { "" , KEYC_NONE, NULL }, |
64 | { "Kill" , 'x', NULL }, |
65 | { "Kill Tagged" , 'X', NULL }, |
66 | { "" , KEYC_NONE, NULL }, |
67 | { "Cancel" , 'q', NULL }, |
68 | |
69 | { NULL, KEYC_NONE, NULL } |
70 | }; |
71 | |
72 | const struct window_mode window_tree_mode = { |
73 | .name = "tree-mode" , |
74 | .default_format = WINDOW_TREE_DEFAULT_FORMAT, |
75 | |
76 | .init = window_tree_init, |
77 | .free = window_tree_free, |
78 | .resize = window_tree_resize, |
79 | .key = window_tree_key, |
80 | }; |
81 | |
82 | enum window_tree_sort_type { |
83 | WINDOW_TREE_BY_INDEX, |
84 | WINDOW_TREE_BY_NAME, |
85 | WINDOW_TREE_BY_TIME, |
86 | }; |
87 | static const char *window_tree_sort_list[] = { |
88 | "index" , |
89 | "name" , |
90 | "time" |
91 | }; |
92 | static struct mode_tree_sort_criteria *window_tree_sort; |
93 | |
94 | enum window_tree_type { |
95 | WINDOW_TREE_NONE, |
96 | WINDOW_TREE_SESSION, |
97 | WINDOW_TREE_WINDOW, |
98 | WINDOW_TREE_PANE, |
99 | }; |
100 | |
101 | struct window_tree_itemdata { |
102 | enum window_tree_type type; |
103 | int session; |
104 | int winlink; |
105 | int pane; |
106 | }; |
107 | |
108 | struct window_tree_modedata { |
109 | struct window_pane *wp; |
110 | int dead; |
111 | int references; |
112 | |
113 | struct mode_tree_data *data; |
114 | char *format; |
115 | char *command; |
116 | int squash_groups; |
117 | |
118 | struct window_tree_itemdata **item_list; |
119 | u_int item_size; |
120 | |
121 | const char *entered; |
122 | |
123 | struct cmd_find_state fs; |
124 | enum window_tree_type type; |
125 | |
126 | int offset; |
127 | |
128 | int left; |
129 | int right; |
130 | u_int start; |
131 | u_int end; |
132 | u_int each; |
133 | }; |
134 | |
135 | static void |
136 | window_tree_pull_item(struct window_tree_itemdata *item, struct session **sp, |
137 | struct winlink **wlp, struct window_pane **wp) |
138 | { |
139 | *wp = NULL; |
140 | *wlp = NULL; |
141 | *sp = session_find_by_id(item->session); |
142 | if (*sp == NULL) |
143 | return; |
144 | if (item->type == WINDOW_TREE_SESSION) { |
145 | *wlp = (*sp)->curw; |
146 | *wp = (*wlp)->window->active; |
147 | return; |
148 | } |
149 | |
150 | *wlp = winlink_find_by_index(&(*sp)->windows, item->winlink); |
151 | if (*wlp == NULL) { |
152 | *sp = NULL; |
153 | return; |
154 | } |
155 | if (item->type == WINDOW_TREE_WINDOW) { |
156 | *wp = (*wlp)->window->active; |
157 | return; |
158 | } |
159 | |
160 | *wp = window_pane_find_by_id(item->pane); |
161 | if (!window_has_pane((*wlp)->window, *wp)) |
162 | *wp = NULL; |
163 | if (*wp == NULL) { |
164 | *sp = NULL; |
165 | *wlp = NULL; |
166 | return; |
167 | } |
168 | } |
169 | |
170 | static struct window_tree_itemdata * |
171 | window_tree_add_item(struct window_tree_modedata *data) |
172 | { |
173 | struct window_tree_itemdata *item; |
174 | |
175 | data->item_list = xreallocarray(data->item_list, data->item_size + 1, |
176 | sizeof *data->item_list); |
177 | item = data->item_list[data->item_size++] = xcalloc(1, sizeof *item); |
178 | return (item); |
179 | } |
180 | |
181 | static void |
182 | window_tree_free_item(struct window_tree_itemdata *item) |
183 | { |
184 | free(item); |
185 | } |
186 | |
187 | static int |
188 | window_tree_cmp_session(const void *a0, const void *b0) |
189 | { |
190 | const struct session *const *a = a0; |
191 | const struct session *const *b = b0; |
192 | const struct session *sa = *a; |
193 | const struct session *sb = *b; |
194 | int result = 0; |
195 | |
196 | switch (window_tree_sort->field) { |
197 | case WINDOW_TREE_BY_INDEX: |
198 | result = sa->id - sb->id; |
199 | break; |
200 | case WINDOW_TREE_BY_TIME: |
201 | if (timercmp(&sa->activity_time, &sb->activity_time, >)) { |
202 | result = -1; |
203 | break; |
204 | } |
205 | if (timercmp(&sa->activity_time, &sb->activity_time, <)) { |
206 | result = 1; |
207 | break; |
208 | } |
209 | /* FALLTHROUGH */ |
210 | case WINDOW_TREE_BY_NAME: |
211 | result = strcmp(sa->name, sb->name); |
212 | break; |
213 | } |
214 | |
215 | if (window_tree_sort->reversed) |
216 | result = -result; |
217 | return (result); |
218 | } |
219 | |
220 | static int |
221 | window_tree_cmp_window(const void *a0, const void *b0) |
222 | { |
223 | const struct winlink *const *a = a0; |
224 | const struct winlink *const *b = b0; |
225 | const struct winlink *wla = *a; |
226 | const struct winlink *wlb = *b; |
227 | struct window *wa = wla->window; |
228 | struct window *wb = wlb->window; |
229 | int result = 0; |
230 | |
231 | switch (window_tree_sort->field) { |
232 | case WINDOW_TREE_BY_INDEX: |
233 | result = wla->idx - wlb->idx; |
234 | break; |
235 | case WINDOW_TREE_BY_TIME: |
236 | if (timercmp(&wa->activity_time, &wb->activity_time, >)) { |
237 | result = -1; |
238 | break; |
239 | } |
240 | if (timercmp(&wa->activity_time, &wb->activity_time, <)) { |
241 | result = 1; |
242 | break; |
243 | } |
244 | /* FALLTHROUGH */ |
245 | case WINDOW_TREE_BY_NAME: |
246 | result = strcmp(wa->name, wb->name); |
247 | break; |
248 | } |
249 | |
250 | if (window_tree_sort->reversed) |
251 | result = -result; |
252 | return (result); |
253 | } |
254 | |
255 | static int |
256 | window_tree_cmp_pane(const void *a0, const void *b0) |
257 | { |
258 | const struct window_pane *const *a = a0; |
259 | const struct window_pane *const *b = b0; |
260 | int result; |
261 | |
262 | if (window_tree_sort->field == WINDOW_TREE_BY_TIME) |
263 | result = (*a)->active_point - (*b)->active_point; |
264 | else { |
265 | /* |
266 | * Panes don't have names, so use number order for any other |
267 | * sort field. |
268 | */ |
269 | result = (*a)->id - (*b)->id; |
270 | } |
271 | if (window_tree_sort->reversed) |
272 | result = -result; |
273 | return (result); |
274 | } |
275 | |
276 | static void |
277 | window_tree_build_pane(struct session *s, struct winlink *wl, |
278 | struct window_pane *wp, void *modedata, struct mode_tree_item *parent) |
279 | { |
280 | struct window_tree_modedata *data = modedata; |
281 | struct window_tree_itemdata *item; |
282 | char *name, *text; |
283 | u_int idx; |
284 | |
285 | window_pane_index(wp, &idx); |
286 | |
287 | item = window_tree_add_item(data); |
288 | item->type = WINDOW_TREE_PANE; |
289 | item->session = s->id; |
290 | item->winlink = wl->idx; |
291 | item->pane = wp->id; |
292 | |
293 | text = format_single(NULL, data->format, NULL, s, wl, wp); |
294 | xasprintf(&name, "%u" , idx); |
295 | |
296 | mode_tree_add(data->data, parent, item, (uint64_t)wp, name, text, -1); |
297 | free(text); |
298 | free(name); |
299 | } |
300 | |
301 | static int |
302 | window_tree_filter_pane(struct session *s, struct winlink *wl, |
303 | struct window_pane *wp, const char *filter) |
304 | { |
305 | char *cp; |
306 | int result; |
307 | |
308 | if (filter == NULL) |
309 | return (1); |
310 | |
311 | cp = format_single(NULL, filter, NULL, s, wl, wp); |
312 | result = format_true(cp); |
313 | free(cp); |
314 | |
315 | return (result); |
316 | } |
317 | |
318 | static int |
319 | window_tree_build_window(struct session *s, struct winlink *wl, |
320 | void *modedata, struct mode_tree_sort_criteria *sort_crit, |
321 | struct mode_tree_item *parent, const char *filter) |
322 | { |
323 | struct window_tree_modedata *data = modedata; |
324 | struct window_tree_itemdata *item; |
325 | struct mode_tree_item *mti; |
326 | char *name, *text; |
327 | struct window_pane *wp, **l; |
328 | u_int n, i; |
329 | int expanded; |
330 | |
331 | item = window_tree_add_item(data); |
332 | item->type = WINDOW_TREE_WINDOW; |
333 | item->session = s->id; |
334 | item->winlink = wl->idx; |
335 | item->pane = -1; |
336 | |
337 | text = format_single(NULL, data->format, NULL, s, wl, NULL); |
338 | xasprintf(&name, "%u" , wl->idx); |
339 | |
340 | if (data->type == WINDOW_TREE_SESSION || |
341 | data->type == WINDOW_TREE_WINDOW) |
342 | expanded = 0; |
343 | else |
344 | expanded = 1; |
345 | mti = mode_tree_add(data->data, parent, item, (uint64_t)wl, name, text, |
346 | expanded); |
347 | free(text); |
348 | free(name); |
349 | |
350 | if ((wp = TAILQ_FIRST(&wl->window->panes)) == NULL) |
351 | goto empty; |
352 | if (TAILQ_NEXT(wp, entry) == NULL) { |
353 | if (!window_tree_filter_pane(s, wl, wp, filter)) |
354 | goto empty; |
355 | return (1); |
356 | } |
357 | |
358 | l = NULL; |
359 | n = 0; |
360 | |
361 | TAILQ_FOREACH(wp, &wl->window->panes, entry) { |
362 | if (!window_tree_filter_pane(s, wl, wp, filter)) |
363 | continue; |
364 | l = xreallocarray(l, n + 1, sizeof *l); |
365 | l[n++] = wp; |
366 | } |
367 | if (n == 0) |
368 | goto empty; |
369 | |
370 | window_tree_sort = sort_crit; |
371 | qsort(l, n, sizeof *l, window_tree_cmp_pane); |
372 | |
373 | for (i = 0; i < n; i++) |
374 | window_tree_build_pane(s, wl, l[i], modedata, mti); |
375 | free(l); |
376 | return (1); |
377 | |
378 | empty: |
379 | window_tree_free_item(item); |
380 | data->item_size--; |
381 | mode_tree_remove(data->data, mti); |
382 | return (0); |
383 | } |
384 | |
385 | static void |
386 | window_tree_build_session(struct session *s, void *modedata, |
387 | struct mode_tree_sort_criteria *sort_crit, const char *filter) |
388 | { |
389 | struct window_tree_modedata *data = modedata; |
390 | struct window_tree_itemdata *item; |
391 | struct mode_tree_item *mti; |
392 | char *text; |
393 | struct winlink *wl, **l; |
394 | u_int n, i, empty; |
395 | int expanded; |
396 | |
397 | item = window_tree_add_item(data); |
398 | item->type = WINDOW_TREE_SESSION; |
399 | item->session = s->id; |
400 | item->winlink = -1; |
401 | item->pane = -1; |
402 | |
403 | text = format_single(NULL, data->format, NULL, s, NULL, NULL); |
404 | |
405 | if (data->type == WINDOW_TREE_SESSION) |
406 | expanded = 0; |
407 | else |
408 | expanded = 1; |
409 | mti = mode_tree_add(data->data, NULL, item, (uint64_t)s, s->name, text, |
410 | expanded); |
411 | free(text); |
412 | |
413 | l = NULL; |
414 | n = 0; |
415 | RB_FOREACH(wl, winlinks, &s->windows) { |
416 | l = xreallocarray(l, n + 1, sizeof *l); |
417 | l[n++] = wl; |
418 | } |
419 | window_tree_sort = sort_crit; |
420 | qsort(l, n, sizeof *l, window_tree_cmp_window); |
421 | |
422 | empty = 0; |
423 | for (i = 0; i < n; i++) { |
424 | if (!window_tree_build_window(s, l[i], modedata, sort_crit, mti, |
425 | filter)) |
426 | empty++; |
427 | } |
428 | if (empty == n) { |
429 | window_tree_free_item(item); |
430 | data->item_size--; |
431 | mode_tree_remove(data->data, mti); |
432 | } |
433 | free(l); |
434 | } |
435 | |
436 | static void |
437 | window_tree_build(void *modedata, struct mode_tree_sort_criteria *sort_crit, |
438 | uint64_t *tag, const char *filter) |
439 | { |
440 | struct window_tree_modedata *data = modedata; |
441 | struct session *s, **l; |
442 | struct session_group *sg, *current; |
443 | u_int n, i; |
444 | |
445 | current = session_group_contains(data->fs.s); |
446 | |
447 | for (i = 0; i < data->item_size; i++) |
448 | window_tree_free_item(data->item_list[i]); |
449 | free(data->item_list); |
450 | data->item_list = NULL; |
451 | data->item_size = 0; |
452 | |
453 | l = NULL; |
454 | n = 0; |
455 | RB_FOREACH(s, sessions, &sessions) { |
456 | if (data->squash_groups && |
457 | (sg = session_group_contains(s)) != NULL) { |
458 | if ((sg == current && s != data->fs.s) || |
459 | (sg != current && s != TAILQ_FIRST(&sg->sessions))) |
460 | continue; |
461 | } |
462 | l = xreallocarray(l, n + 1, sizeof *l); |
463 | l[n++] = s; |
464 | } |
465 | window_tree_sort = sort_crit; |
466 | qsort(l, n, sizeof *l, window_tree_cmp_session); |
467 | |
468 | for (i = 0; i < n; i++) |
469 | window_tree_build_session(l[i], modedata, sort_crit, filter); |
470 | free(l); |
471 | |
472 | switch (data->type) { |
473 | case WINDOW_TREE_NONE: |
474 | break; |
475 | case WINDOW_TREE_SESSION: |
476 | *tag = (uint64_t)data->fs.s; |
477 | break; |
478 | case WINDOW_TREE_WINDOW: |
479 | *tag = (uint64_t)data->fs.wl; |
480 | break; |
481 | case WINDOW_TREE_PANE: |
482 | if (window_count_panes(data->fs.wl->window) == 1) |
483 | *tag = (uint64_t)data->fs.wl; |
484 | else |
485 | *tag = (uint64_t)data->fs.wp; |
486 | break; |
487 | } |
488 | } |
489 | |
490 | static void |
491 | window_tree_draw_label(struct screen_write_ctx *ctx, u_int px, u_int py, |
492 | u_int sx, u_int sy, const struct grid_cell *gc, const char *label) |
493 | { |
494 | size_t len; |
495 | u_int ox, oy; |
496 | |
497 | len = strlen(label); |
498 | if (sx == 0 || sy == 1 || len > sx) |
499 | return; |
500 | ox = (sx - len + 1) / 2; |
501 | oy = (sy + 1) / 2; |
502 | |
503 | if (ox > 1 && ox + len < sx - 1 && sy >= 3) { |
504 | screen_write_cursormove(ctx, px + ox - 1, py + oy - 1, 0); |
505 | screen_write_box(ctx, len + 2, 3); |
506 | } |
507 | screen_write_cursormove(ctx, px + ox, py + oy, 0); |
508 | screen_write_puts(ctx, gc, "%s" , label); |
509 | } |
510 | |
511 | static void |
512 | window_tree_draw_session(struct window_tree_modedata *data, struct session *s, |
513 | struct screen_write_ctx *ctx, u_int sx, u_int sy) |
514 | { |
515 | struct options *oo = s->options; |
516 | struct winlink *wl; |
517 | struct window *w; |
518 | u_int cx = ctx->s->cx, cy = ctx->s->cy; |
519 | u_int loop, total, visible, each, width, offset; |
520 | u_int current, start, end, remaining, i; |
521 | struct grid_cell gc; |
522 | int colour, active_colour, left, right; |
523 | char *label; |
524 | |
525 | total = winlink_count(&s->windows); |
526 | |
527 | memcpy(&gc, &grid_default_cell, sizeof gc); |
528 | colour = options_get_number(oo, "display-panes-colour" ); |
529 | active_colour = options_get_number(oo, "display-panes-active-colour" ); |
530 | |
531 | if (sx / total < 24) { |
532 | visible = sx / 24; |
533 | if (visible == 0) |
534 | visible = 1; |
535 | } else |
536 | visible = total; |
537 | |
538 | current = 0; |
539 | RB_FOREACH(wl, winlinks, &s->windows) { |
540 | if (wl == s->curw) |
541 | break; |
542 | current++; |
543 | } |
544 | |
545 | if (current < visible) { |
546 | start = 0; |
547 | end = visible; |
548 | } else if (current >= total - visible) { |
549 | start = total - visible; |
550 | end = total; |
551 | } else { |
552 | start = current - (visible / 2); |
553 | end = start + visible; |
554 | } |
555 | |
556 | if (data->offset < -(int)start) |
557 | data->offset = -(int)start; |
558 | if (data->offset > (int)(total - end)) |
559 | data->offset = (int)(total - end); |
560 | start += data->offset; |
561 | end += data->offset; |
562 | |
563 | left = (start != 0); |
564 | right = (end != total); |
565 | if (((left && right) && sx <= 6) || ((left || right) && sx <= 3)) |
566 | left = right = 0; |
567 | if (left && right) { |
568 | each = (sx - 6) / visible; |
569 | remaining = (sx - 6) - (visible * each); |
570 | } else if (left || right) { |
571 | each = (sx - 3) / visible; |
572 | remaining = (sx - 3) - (visible * each); |
573 | } else { |
574 | each = sx / visible; |
575 | remaining = sx - (visible * each); |
576 | } |
577 | if (each == 0) |
578 | return; |
579 | |
580 | if (left) { |
581 | data->left = cx + 2; |
582 | screen_write_cursormove(ctx, cx + 2, cy, 0); |
583 | screen_write_vline(ctx, sy, 0, 0); |
584 | screen_write_cursormove(ctx, cx, cy + sy / 2, 0); |
585 | screen_write_puts(ctx, &grid_default_cell, "<" ); |
586 | } else |
587 | data->left = -1; |
588 | if (right) { |
589 | data->right = cx + sx - 3; |
590 | screen_write_cursormove(ctx, cx + sx - 3, cy, 0); |
591 | screen_write_vline(ctx, sy, 0, 0); |
592 | screen_write_cursormove(ctx, cx + sx - 1, cy + sy / 2, 0); |
593 | screen_write_puts(ctx, &grid_default_cell, ">" ); |
594 | } else |
595 | data->right = -1; |
596 | |
597 | data->start = start; |
598 | data->end = end; |
599 | data->each = each; |
600 | |
601 | i = loop = 0; |
602 | RB_FOREACH(wl, winlinks, &s->windows) { |
603 | if (loop == end) |
604 | break; |
605 | if (loop < start) { |
606 | loop++; |
607 | continue; |
608 | } |
609 | w = wl->window; |
610 | |
611 | if (wl == s->curw) |
612 | gc.fg = active_colour; |
613 | else |
614 | gc.fg = colour; |
615 | |
616 | if (left) |
617 | offset = 3 + (i * each); |
618 | else |
619 | offset = (i * each); |
620 | if (loop == end - 1) |
621 | width = each + remaining; |
622 | else |
623 | width = each - 1; |
624 | |
625 | screen_write_cursormove(ctx, cx + offset, cy, 0); |
626 | screen_write_preview(ctx, &w->active->base, width, sy); |
627 | |
628 | xasprintf(&label, " %u:%s " , wl->idx, w->name); |
629 | if (strlen(label) > width) |
630 | xasprintf(&label, " %u " , wl->idx); |
631 | window_tree_draw_label(ctx, cx + offset, cy, width, sy, &gc, |
632 | label); |
633 | free(label); |
634 | |
635 | if (loop != end - 1) { |
636 | screen_write_cursormove(ctx, cx + offset + width, cy, 0); |
637 | screen_write_vline(ctx, sy, 0, 0); |
638 | } |
639 | loop++; |
640 | |
641 | i++; |
642 | } |
643 | } |
644 | |
645 | static void |
646 | window_tree_draw_window(struct window_tree_modedata *data, struct session *s, |
647 | struct window *w, struct screen_write_ctx *ctx, u_int sx, u_int sy) |
648 | { |
649 | struct options *oo = s->options; |
650 | struct window_pane *wp; |
651 | u_int cx = ctx->s->cx, cy = ctx->s->cy; |
652 | u_int loop, total, visible, each, width, offset; |
653 | u_int current, start, end, remaining, i; |
654 | struct grid_cell gc; |
655 | int colour, active_colour, left, right, pane_idx; |
656 | char *label; |
657 | |
658 | total = window_count_panes(w); |
659 | |
660 | memcpy(&gc, &grid_default_cell, sizeof gc); |
661 | colour = options_get_number(oo, "display-panes-colour" ); |
662 | active_colour = options_get_number(oo, "display-panes-active-colour" ); |
663 | |
664 | if (sx / total < 24) { |
665 | visible = sx / 24; |
666 | if (visible == 0) |
667 | visible = 1; |
668 | } else |
669 | visible = total; |
670 | |
671 | current = 0; |
672 | TAILQ_FOREACH(wp, &w->panes, entry) { |
673 | if (wp == w->active) |
674 | break; |
675 | current++; |
676 | } |
677 | |
678 | if (current < visible) { |
679 | start = 0; |
680 | end = visible; |
681 | } else if (current >= total - visible) { |
682 | start = total - visible; |
683 | end = total; |
684 | } else { |
685 | start = current - (visible / 2); |
686 | end = start + visible; |
687 | } |
688 | |
689 | if (data->offset < -(int)start) |
690 | data->offset = -(int)start; |
691 | if (data->offset > (int)(total - end)) |
692 | data->offset = (int)(total - end); |
693 | start += data->offset; |
694 | end += data->offset; |
695 | |
696 | left = (start != 0); |
697 | right = (end != total); |
698 | if (((left && right) && sx <= 6) || ((left || right) && sx <= 3)) |
699 | left = right = 0; |
700 | if (left && right) { |
701 | each = (sx - 6) / visible; |
702 | remaining = (sx - 6) - (visible * each); |
703 | } else if (left || right) { |
704 | each = (sx - 3) / visible; |
705 | remaining = (sx - 3) - (visible * each); |
706 | } else { |
707 | each = sx / visible; |
708 | remaining = sx - (visible * each); |
709 | } |
710 | if (each == 0) |
711 | return; |
712 | |
713 | if (left) { |
714 | data->left = cx + 2; |
715 | screen_write_cursormove(ctx, cx + 2, cy, 0); |
716 | screen_write_vline(ctx, sy, 0, 0); |
717 | screen_write_cursormove(ctx, cx, cy + sy / 2, 0); |
718 | screen_write_puts(ctx, &grid_default_cell, "<" ); |
719 | } else |
720 | data->left = -1; |
721 | if (right) { |
722 | data->right = cx + sx - 3; |
723 | screen_write_cursormove(ctx, cx + sx - 3, cy, 0); |
724 | screen_write_vline(ctx, sy, 0, 0); |
725 | screen_write_cursormove(ctx, cx + sx - 1, cy + sy / 2, 0); |
726 | screen_write_puts(ctx, &grid_default_cell, ">" ); |
727 | } else |
728 | data->right = -1; |
729 | |
730 | data->start = start; |
731 | data->end = end; |
732 | data->each = each; |
733 | |
734 | i = loop = 0; |
735 | TAILQ_FOREACH(wp, &w->panes, entry) { |
736 | if (loop == end) |
737 | break; |
738 | if (loop < start) { |
739 | loop++; |
740 | continue; |
741 | } |
742 | |
743 | if (wp == w->active) |
744 | gc.fg = active_colour; |
745 | else |
746 | gc.fg = colour; |
747 | |
748 | if (left) |
749 | offset = 3 + (i * each); |
750 | else |
751 | offset = (i * each); |
752 | if (loop == end - 1) |
753 | width = each + remaining; |
754 | else |
755 | width = each - 1; |
756 | |
757 | screen_write_cursormove(ctx, cx + offset, cy, 0); |
758 | screen_write_preview(ctx, &wp->base, width, sy); |
759 | |
760 | if (window_pane_index(wp, &pane_idx) != 0) |
761 | pane_idx = loop; |
762 | xasprintf(&label, " %u " , pane_idx); |
763 | window_tree_draw_label(ctx, cx + offset, cy, each, sy, &gc, |
764 | label); |
765 | free(label); |
766 | |
767 | if (loop != end - 1) { |
768 | screen_write_cursormove(ctx, cx + offset + width, cy, 0); |
769 | screen_write_vline(ctx, sy, 0, 0); |
770 | } |
771 | loop++; |
772 | |
773 | i++; |
774 | } |
775 | } |
776 | |
777 | static void |
778 | window_tree_draw(void *modedata, void *itemdata, struct screen_write_ctx *ctx, |
779 | u_int sx, u_int sy) |
780 | { |
781 | struct window_tree_itemdata *item = itemdata; |
782 | struct session *sp; |
783 | struct winlink *wlp; |
784 | struct window_pane *wp; |
785 | |
786 | window_tree_pull_item(item, &sp, &wlp, &wp); |
787 | if (wp == NULL) |
788 | return; |
789 | |
790 | switch (item->type) { |
791 | case WINDOW_TREE_NONE: |
792 | break; |
793 | case WINDOW_TREE_SESSION: |
794 | window_tree_draw_session(modedata, sp, ctx, sx, sy); |
795 | break; |
796 | case WINDOW_TREE_WINDOW: |
797 | window_tree_draw_window(modedata, sp, wlp->window, ctx, sx, sy); |
798 | break; |
799 | case WINDOW_TREE_PANE: |
800 | screen_write_preview(ctx, &wp->base, sx, sy); |
801 | break; |
802 | } |
803 | } |
804 | |
805 | static int |
806 | window_tree_search(__unused void *modedata, void *itemdata, const char *ss) |
807 | { |
808 | struct window_tree_itemdata *item = itemdata; |
809 | struct session *s; |
810 | struct winlink *wl; |
811 | struct window_pane *wp; |
812 | char *cmd; |
813 | int retval; |
814 | |
815 | window_tree_pull_item(item, &s, &wl, &wp); |
816 | |
817 | switch (item->type) { |
818 | case WINDOW_TREE_NONE: |
819 | return (0); |
820 | case WINDOW_TREE_SESSION: |
821 | if (s == NULL) |
822 | return (0); |
823 | return (strstr(s->name, ss) != NULL); |
824 | case WINDOW_TREE_WINDOW: |
825 | if (s == NULL || wl == NULL) |
826 | return (0); |
827 | return (strstr(wl->window->name, ss) != NULL); |
828 | case WINDOW_TREE_PANE: |
829 | if (s == NULL || wl == NULL || wp == NULL) |
830 | break; |
831 | cmd = osdep_get_name(wp->fd, wp->tty); |
832 | if (cmd == NULL || *cmd == '\0') |
833 | return (0); |
834 | retval = (strstr(cmd, ss) != NULL); |
835 | free(cmd); |
836 | return retval; |
837 | } |
838 | return (0); |
839 | } |
840 | |
841 | static void |
842 | (void *modedata, struct client *c, key_code key) |
843 | { |
844 | struct window_tree_modedata *data = modedata; |
845 | struct window_pane *wp = data->wp; |
846 | struct window_mode_entry *wme; |
847 | |
848 | wme = TAILQ_FIRST(&wp->modes); |
849 | if (wme == NULL || wme->data != modedata) |
850 | return; |
851 | window_tree_key(wme, c, NULL, NULL, key, NULL); |
852 | } |
853 | |
854 | static struct screen * |
855 | window_tree_init(struct window_mode_entry *wme, struct cmd_find_state *fs, |
856 | struct args *args) |
857 | { |
858 | struct window_pane *wp = wme->wp; |
859 | struct window_tree_modedata *data; |
860 | struct screen *s; |
861 | |
862 | wme->data = data = xcalloc(1, sizeof *data); |
863 | data->wp = wp; |
864 | data->references = 1; |
865 | |
866 | if (args_has(args, 's')) |
867 | data->type = WINDOW_TREE_SESSION; |
868 | else if (args_has(args, 'w')) |
869 | data->type = WINDOW_TREE_WINDOW; |
870 | else |
871 | data->type = WINDOW_TREE_PANE; |
872 | memcpy(&data->fs, fs, sizeof data->fs); |
873 | |
874 | if (args == NULL || !args_has(args, 'F')) |
875 | data->format = xstrdup(WINDOW_TREE_DEFAULT_FORMAT); |
876 | else |
877 | data->format = xstrdup(args_get(args, 'F')); |
878 | if (args == NULL || args->argc == 0) |
879 | data->command = xstrdup(WINDOW_TREE_DEFAULT_COMMAND); |
880 | else |
881 | data->command = xstrdup(args->argv[0]); |
882 | data->squash_groups = !args_has(args, 'G'); |
883 | |
884 | data->data = mode_tree_start(wp, args, window_tree_build, |
885 | window_tree_draw, window_tree_search, window_tree_menu, data, |
886 | window_tree_menu_items, window_tree_sort_list, |
887 | nitems(window_tree_sort_list), &s); |
888 | mode_tree_zoom(data->data, args); |
889 | |
890 | mode_tree_build(data->data); |
891 | mode_tree_draw(data->data); |
892 | |
893 | data->type = WINDOW_TREE_NONE; |
894 | |
895 | return (s); |
896 | } |
897 | |
898 | static void |
899 | window_tree_destroy(struct window_tree_modedata *data) |
900 | { |
901 | u_int i; |
902 | |
903 | if (--data->references != 0) |
904 | return; |
905 | |
906 | for (i = 0; i < data->item_size; i++) |
907 | window_tree_free_item(data->item_list[i]); |
908 | free(data->item_list); |
909 | |
910 | free(data->format); |
911 | free(data->command); |
912 | |
913 | free(data); |
914 | } |
915 | |
916 | static void |
917 | window_tree_free(struct window_mode_entry *wme) |
918 | { |
919 | struct window_tree_modedata *data = wme->data; |
920 | |
921 | if (data == NULL) |
922 | return; |
923 | |
924 | data->dead = 1; |
925 | mode_tree_free(data->data); |
926 | window_tree_destroy(data); |
927 | } |
928 | |
929 | static void |
930 | window_tree_resize(struct window_mode_entry *wme, u_int sx, u_int sy) |
931 | { |
932 | struct window_tree_modedata *data = wme->data; |
933 | |
934 | mode_tree_resize(data->data, sx, sy); |
935 | } |
936 | |
937 | static char * |
938 | window_tree_get_target(struct window_tree_itemdata *item, |
939 | struct cmd_find_state *fs) |
940 | { |
941 | struct session *s; |
942 | struct winlink *wl; |
943 | struct window_pane *wp; |
944 | char *target; |
945 | |
946 | window_tree_pull_item(item, &s, &wl, &wp); |
947 | |
948 | target = NULL; |
949 | switch (item->type) { |
950 | case WINDOW_TREE_NONE: |
951 | break; |
952 | case WINDOW_TREE_SESSION: |
953 | if (s == NULL) |
954 | break; |
955 | xasprintf(&target, "=%s:" , s->name); |
956 | break; |
957 | case WINDOW_TREE_WINDOW: |
958 | if (s == NULL || wl == NULL) |
959 | break; |
960 | xasprintf(&target, "=%s:%u." , s->name, wl->idx); |
961 | break; |
962 | case WINDOW_TREE_PANE: |
963 | if (s == NULL || wl == NULL || wp == NULL) |
964 | break; |
965 | xasprintf(&target, "=%s:%u.%%%u" , s->name, wl->idx, wp->id); |
966 | break; |
967 | } |
968 | if (target == NULL) |
969 | cmd_find_clear_state(fs, 0); |
970 | else |
971 | cmd_find_from_winlink_pane(fs, wl, wp, 0); |
972 | return (target); |
973 | } |
974 | |
975 | static void |
976 | window_tree_command_each(void *modedata, void *itemdata, struct client *c, |
977 | __unused key_code key) |
978 | { |
979 | struct window_tree_modedata *data = modedata; |
980 | struct window_tree_itemdata *item = itemdata; |
981 | char *name; |
982 | struct cmd_find_state fs; |
983 | |
984 | name = window_tree_get_target(item, &fs); |
985 | if (name != NULL) |
986 | mode_tree_run_command(c, &fs, data->entered, name); |
987 | free(name); |
988 | } |
989 | |
990 | static enum cmd_retval |
991 | window_tree_command_done(__unused struct cmdq_item *item, void *modedata) |
992 | { |
993 | struct window_tree_modedata *data = modedata; |
994 | |
995 | if (!data->dead) { |
996 | mode_tree_build(data->data); |
997 | mode_tree_draw(data->data); |
998 | data->wp->flags |= PANE_REDRAW; |
999 | } |
1000 | window_tree_destroy(data); |
1001 | return (CMD_RETURN_NORMAL); |
1002 | } |
1003 | |
1004 | static int |
1005 | window_tree_command_callback(struct client *c, void *modedata, const char *s, |
1006 | __unused int done) |
1007 | { |
1008 | struct window_tree_modedata *data = modedata; |
1009 | |
1010 | if (s == NULL || *s == '\0' || data->dead) |
1011 | return (0); |
1012 | |
1013 | data->entered = s; |
1014 | mode_tree_each_tagged(data->data, window_tree_command_each, c, |
1015 | KEYC_NONE, 1); |
1016 | data->entered = NULL; |
1017 | |
1018 | data->references++; |
1019 | cmdq_append(c, cmdq_get_callback(window_tree_command_done, data)); |
1020 | |
1021 | return (0); |
1022 | } |
1023 | |
1024 | static void |
1025 | window_tree_command_free(void *modedata) |
1026 | { |
1027 | struct window_tree_modedata *data = modedata; |
1028 | |
1029 | window_tree_destroy(data); |
1030 | } |
1031 | |
1032 | static void |
1033 | window_tree_kill_each(__unused void *modedata, void *itemdata, |
1034 | __unused struct client *c, __unused key_code key) |
1035 | { |
1036 | struct window_tree_itemdata *item = itemdata; |
1037 | struct session *s; |
1038 | struct winlink *wl; |
1039 | struct window_pane *wp; |
1040 | |
1041 | window_tree_pull_item(item, &s, &wl, &wp); |
1042 | |
1043 | switch (item->type) { |
1044 | case WINDOW_TREE_NONE: |
1045 | break; |
1046 | case WINDOW_TREE_SESSION: |
1047 | if (s != NULL) { |
1048 | server_destroy_session(s); |
1049 | session_destroy(s, 1, __func__); |
1050 | } |
1051 | break; |
1052 | case WINDOW_TREE_WINDOW: |
1053 | if (wl != NULL) |
1054 | server_kill_window(wl->window); |
1055 | break; |
1056 | case WINDOW_TREE_PANE: |
1057 | if (wp != NULL) |
1058 | server_kill_pane(wp); |
1059 | break; |
1060 | } |
1061 | } |
1062 | |
1063 | static int |
1064 | window_tree_kill_current_callback(struct client *c, void *modedata, |
1065 | const char *s, __unused int done) |
1066 | { |
1067 | struct window_tree_modedata *data = modedata; |
1068 | struct mode_tree_data *mtd = data->data; |
1069 | |
1070 | if (s == NULL || *s == '\0' || data->dead) |
1071 | return (0); |
1072 | if (tolower((u_char) s[0]) != 'y' || s[1] != '\0') |
1073 | return (0); |
1074 | |
1075 | window_tree_kill_each(data, mode_tree_get_current(mtd), c, KEYC_NONE); |
1076 | |
1077 | data->references++; |
1078 | cmdq_append(c, cmdq_get_callback(window_tree_command_done, data)); |
1079 | |
1080 | return (0); |
1081 | } |
1082 | |
1083 | static int |
1084 | window_tree_kill_tagged_callback(struct client *c, void *modedata, |
1085 | const char *s, __unused int done) |
1086 | { |
1087 | struct window_tree_modedata *data = modedata; |
1088 | struct mode_tree_data *mtd = data->data; |
1089 | |
1090 | if (s == NULL || *s == '\0' || data->dead) |
1091 | return (0); |
1092 | if (tolower((u_char) s[0]) != 'y' || s[1] != '\0') |
1093 | return (0); |
1094 | |
1095 | mode_tree_each_tagged(mtd, window_tree_kill_each, c, KEYC_NONE, 1); |
1096 | |
1097 | data->references++; |
1098 | cmdq_append(c, cmdq_get_callback(window_tree_command_done, data)); |
1099 | |
1100 | return (0); |
1101 | } |
1102 | |
1103 | static key_code |
1104 | window_tree_mouse(struct window_tree_modedata *data, key_code key, u_int x, |
1105 | struct window_tree_itemdata *item) |
1106 | { |
1107 | struct session *s; |
1108 | struct winlink *wl; |
1109 | struct window_pane *wp; |
1110 | u_int loop; |
1111 | |
1112 | if (key != KEYC_MOUSEDOWN1_PANE) |
1113 | return (KEYC_NONE); |
1114 | |
1115 | if (data->left != -1 && x <= (u_int)data->left) |
1116 | return ('<'); |
1117 | if (data->right != -1 && x >= (u_int)data->right) |
1118 | return ('>'); |
1119 | |
1120 | if (data->left != -1) |
1121 | x -= data->left; |
1122 | else if (x != 0) |
1123 | x--; |
1124 | if (x == 0 || data->end == 0) |
1125 | x = 0; |
1126 | else { |
1127 | x = x / data->each; |
1128 | if (data->start + x >= data->end) |
1129 | x = data->end - 1; |
1130 | } |
1131 | |
1132 | window_tree_pull_item(item, &s, &wl, &wp); |
1133 | if (item->type == WINDOW_TREE_SESSION) { |
1134 | if (s == NULL) |
1135 | return (KEYC_NONE); |
1136 | mode_tree_expand_current(data->data); |
1137 | loop = 0; |
1138 | RB_FOREACH(wl, winlinks, &s->windows) { |
1139 | if (loop == data->start + x) |
1140 | break; |
1141 | loop++; |
1142 | } |
1143 | if (wl != NULL) |
1144 | mode_tree_set_current(data->data, (uint64_t)wl); |
1145 | return ('\r'); |
1146 | } |
1147 | if (item->type == WINDOW_TREE_WINDOW) { |
1148 | if (wl == NULL) |
1149 | return (KEYC_NONE); |
1150 | mode_tree_expand_current(data->data); |
1151 | loop = 0; |
1152 | TAILQ_FOREACH(wp, &wl->window->panes, entry) { |
1153 | if (loop == data->start + x) |
1154 | break; |
1155 | loop++; |
1156 | } |
1157 | if (wp != NULL) |
1158 | mode_tree_set_current(data->data, (uint64_t)wp); |
1159 | return ('\r'); |
1160 | } |
1161 | return (KEYC_NONE); |
1162 | } |
1163 | |
1164 | static void |
1165 | window_tree_key(struct window_mode_entry *wme, struct client *c, |
1166 | __unused struct session *s, __unused struct winlink *wl, key_code key, |
1167 | struct mouse_event *m) |
1168 | { |
1169 | struct window_pane *wp = wme->wp; |
1170 | struct window_tree_modedata *data = wme->data; |
1171 | struct window_tree_itemdata *item, *new_item; |
1172 | char *name, *prompt = NULL; |
1173 | struct cmd_find_state fs; |
1174 | int finished; |
1175 | u_int tagged, x, y, idx; |
1176 | struct session *ns; |
1177 | struct winlink *nwl; |
1178 | struct window_pane *nwp; |
1179 | |
1180 | item = mode_tree_get_current(data->data); |
1181 | finished = mode_tree_key(data->data, c, &key, m, &x, &y); |
1182 | if (item != (new_item = mode_tree_get_current(data->data))) { |
1183 | item = new_item; |
1184 | data->offset = 0; |
1185 | } |
1186 | if (KEYC_IS_MOUSE(key) && m != NULL) |
1187 | key = window_tree_mouse(data, key, x, item); |
1188 | switch (key) { |
1189 | case '<': |
1190 | data->offset--; |
1191 | break; |
1192 | case '>': |
1193 | data->offset++; |
1194 | break; |
1195 | case 'x': |
1196 | window_tree_pull_item(item, &ns, &nwl, &nwp); |
1197 | switch (item->type) { |
1198 | case WINDOW_TREE_NONE: |
1199 | break; |
1200 | case WINDOW_TREE_SESSION: |
1201 | if (ns == NULL) |
1202 | break; |
1203 | xasprintf(&prompt, "Kill session %s? " , ns->name); |
1204 | break; |
1205 | case WINDOW_TREE_WINDOW: |
1206 | if (nwl == NULL) |
1207 | break; |
1208 | xasprintf(&prompt, "Kill window %u? " , nwl->idx); |
1209 | break; |
1210 | case WINDOW_TREE_PANE: |
1211 | if (nwp == NULL || window_pane_index(nwp, &idx) != 0) |
1212 | break; |
1213 | xasprintf(&prompt, "Kill pane %u? " , idx); |
1214 | break; |
1215 | } |
1216 | if (prompt == NULL) |
1217 | break; |
1218 | data->references++; |
1219 | status_prompt_set(c, prompt, "" , |
1220 | window_tree_kill_current_callback, window_tree_command_free, |
1221 | data, PROMPT_SINGLE|PROMPT_NOFORMAT); |
1222 | free(prompt); |
1223 | break; |
1224 | case 'X': |
1225 | tagged = mode_tree_count_tagged(data->data); |
1226 | if (tagged == 0) |
1227 | break; |
1228 | xasprintf(&prompt, "Kill %u tagged? " , tagged); |
1229 | data->references++; |
1230 | status_prompt_set(c, prompt, "" , |
1231 | window_tree_kill_tagged_callback, window_tree_command_free, |
1232 | data, PROMPT_SINGLE|PROMPT_NOFORMAT); |
1233 | free(prompt); |
1234 | break; |
1235 | case ':': |
1236 | tagged = mode_tree_count_tagged(data->data); |
1237 | if (tagged != 0) |
1238 | xasprintf(&prompt, "(%u tagged) " , tagged); |
1239 | else |
1240 | xasprintf(&prompt, "(current) " ); |
1241 | data->references++; |
1242 | status_prompt_set(c, prompt, "" , window_tree_command_callback, |
1243 | window_tree_command_free, data, PROMPT_NOFORMAT); |
1244 | free(prompt); |
1245 | break; |
1246 | case '\r': |
1247 | name = window_tree_get_target(item, &fs); |
1248 | if (name != NULL) |
1249 | mode_tree_run_command(c, NULL, data->command, name); |
1250 | finished = 1; |
1251 | free(name); |
1252 | break; |
1253 | } |
1254 | if (finished) |
1255 | window_pane_reset_mode(wp); |
1256 | else { |
1257 | mode_tree_draw(data->data); |
1258 | wp->flags |= PANE_REDRAW; |
1259 | } |
1260 | } |
1261 | |