1 /* $OpenBSD: window-tree.c,v 1.5 2017/06/09 16:01:39 nicm Exp $ */ 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, 228 u_int sort_type, struct mode_tree_item *parent, const char *filter) 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) { 262 if (filter != NULL) { 263 cp = format_single(NULL, filter, NULL, s, wl, wp); 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, 299 u_int sort_type, const char *filter) 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, 350 filter)) 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 362 window_tree_build(void *modedata, u_int sort_type, uint64_t *tag, 363 const char *filter) 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++) 393 window_tree_build_session(l[i], modedata, sort_type, filter); 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 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 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 493 data->data = mode_tree_start(wp, args, window_tree_build, 494 window_tree_draw, window_tree_search, data, window_tree_sort_list, 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 652 finished = mode_tree_key(data->data, c, &key, m); 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) 671 mode_tree_run_command(c, NULL, command, name); 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 } 683