1 /* $OpenBSD: mode-tree.c,v 1.37 2019/12/12 15:03:13 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 <ctype.h> 22 #include <stdio.h> 23 #include <stdlib.h> 24 #include <string.h> 25 26 #include "tmux.h" 27 28 struct mode_tree_item; 29 TAILQ_HEAD(mode_tree_list, mode_tree_item); 30 31 struct mode_tree_data { 32 int dead; 33 u_int references; 34 int zoomed; 35 36 struct window_pane *wp; 37 void *modedata; 38 const struct menu_item *menu; 39 40 const char **sort_list; 41 u_int sort_size; 42 struct mode_tree_sort_criteria sort_crit; 43 44 mode_tree_build_cb buildcb; 45 mode_tree_draw_cb drawcb; 46 mode_tree_search_cb searchcb; 47 mode_tree_menu_cb menucb; 48 49 struct mode_tree_list children; 50 struct mode_tree_list saved; 51 52 struct mode_tree_line *line_list; 53 u_int line_size; 54 55 u_int depth; 56 57 u_int width; 58 u_int height; 59 60 u_int offset; 61 u_int current; 62 63 struct screen screen; 64 65 int preview; 66 char *search; 67 char *filter; 68 int no_matches; 69 }; 70 71 struct mode_tree_item { 72 struct mode_tree_item *parent; 73 void *itemdata; 74 u_int line; 75 76 uint64_t tag; 77 const char *name; 78 const char *text; 79 80 int expanded; 81 int tagged; 82 83 struct mode_tree_list children; 84 TAILQ_ENTRY(mode_tree_item) entry; 85 }; 86 87 struct mode_tree_line { 88 struct mode_tree_item *item; 89 u_int depth; 90 int last; 91 int flat; 92 }; 93 94 struct mode_tree_menu { 95 struct mode_tree_data *data; 96 struct client *c; 97 u_int line; 98 void *itemdata; 99 }; 100 101 static void mode_tree_free_items(struct mode_tree_list *); 102 103 static const struct menu_item mode_tree_menu_items[] = { 104 { "Scroll Left", '<', NULL }, 105 { "Scroll Right", '>', NULL }, 106 { "", KEYC_NONE, NULL }, 107 { "Cancel", 'q', NULL }, 108 109 { NULL, KEYC_NONE, NULL } 110 }; 111 112 static struct mode_tree_item * 113 mode_tree_find_item(struct mode_tree_list *mtl, uint64_t tag) 114 { 115 struct mode_tree_item *mti, *child; 116 117 TAILQ_FOREACH(mti, mtl, entry) { 118 if (mti->tag == tag) 119 return (mti); 120 child = mode_tree_find_item(&mti->children, tag); 121 if (child != NULL) 122 return (child); 123 } 124 return (NULL); 125 } 126 127 static void 128 mode_tree_free_item(struct mode_tree_item *mti) 129 { 130 mode_tree_free_items(&mti->children); 131 132 free((void *)mti->name); 133 free((void *)mti->text); 134 135 free(mti); 136 } 137 138 static void 139 mode_tree_free_items(struct mode_tree_list *mtl) 140 { 141 struct mode_tree_item *mti, *mti1; 142 143 TAILQ_FOREACH_SAFE(mti, mtl, entry, mti1) { 144 TAILQ_REMOVE(mtl, mti, entry); 145 mode_tree_free_item(mti); 146 } 147 } 148 149 static void 150 mode_tree_check_selected(struct mode_tree_data *mtd) 151 { 152 /* 153 * If the current line would now be off screen reset the offset to the 154 * last visible line. 155 */ 156 if (mtd->current > mtd->height - 1) 157 mtd->offset = mtd->current - mtd->height + 1; 158 } 159 160 static void 161 mode_tree_clear_lines(struct mode_tree_data *mtd) 162 { 163 free(mtd->line_list); 164 mtd->line_list = NULL; 165 mtd->line_size = 0; 166 } 167 168 static void 169 mode_tree_build_lines(struct mode_tree_data *mtd, 170 struct mode_tree_list *mtl, u_int depth) 171 { 172 struct mode_tree_item *mti; 173 struct mode_tree_line *line; 174 u_int i; 175 int flat = 1; 176 177 mtd->depth = depth; 178 TAILQ_FOREACH(mti, mtl, entry) { 179 mtd->line_list = xreallocarray(mtd->line_list, 180 mtd->line_size + 1, sizeof *mtd->line_list); 181 182 line = &mtd->line_list[mtd->line_size++]; 183 line->item = mti; 184 line->depth = depth; 185 line->last = (mti == TAILQ_LAST(mtl, mode_tree_list)); 186 187 mti->line = (mtd->line_size - 1); 188 if (!TAILQ_EMPTY(&mti->children)) 189 flat = 0; 190 if (mti->expanded) 191 mode_tree_build_lines(mtd, &mti->children, depth + 1); 192 } 193 TAILQ_FOREACH(mti, mtl, entry) { 194 for (i = 0; i < mtd->line_size; i++) { 195 line = &mtd->line_list[i]; 196 if (line->item == mti) 197 line->flat = flat; 198 } 199 } 200 } 201 202 static void 203 mode_tree_clear_tagged(struct mode_tree_list *mtl) 204 { 205 struct mode_tree_item *mti; 206 207 TAILQ_FOREACH(mti, mtl, entry) { 208 mti->tagged = 0; 209 mode_tree_clear_tagged(&mti->children); 210 } 211 } 212 213 static void 214 mode_tree_up(struct mode_tree_data *mtd, int wrap) 215 { 216 if (mtd->current == 0) { 217 if (wrap) { 218 mtd->current = mtd->line_size - 1; 219 if (mtd->line_size >= mtd->height) 220 mtd->offset = mtd->line_size - mtd->height; 221 } 222 } else { 223 mtd->current--; 224 if (mtd->current < mtd->offset) 225 mtd->offset--; 226 } 227 } 228 229 void 230 mode_tree_down(struct mode_tree_data *mtd, int wrap) 231 { 232 if (mtd->current == mtd->line_size - 1) { 233 if (wrap) { 234 mtd->current = 0; 235 mtd->offset = 0; 236 } 237 } else { 238 mtd->current++; 239 if (mtd->current > mtd->offset + mtd->height - 1) 240 mtd->offset++; 241 } 242 } 243 244 void * 245 mode_tree_get_current(struct mode_tree_data *mtd) 246 { 247 return (mtd->line_list[mtd->current].item->itemdata); 248 } 249 250 void 251 mode_tree_expand_current(struct mode_tree_data *mtd) 252 { 253 if (!mtd->line_list[mtd->current].item->expanded) { 254 mtd->line_list[mtd->current].item->expanded = 1; 255 mode_tree_build(mtd); 256 } 257 } 258 259 void 260 mode_tree_set_current(struct mode_tree_data *mtd, uint64_t tag) 261 { 262 u_int i; 263 264 for (i = 0; i < mtd->line_size; i++) { 265 if (mtd->line_list[i].item->tag == tag) 266 break; 267 } 268 if (i != mtd->line_size) { 269 mtd->current = i; 270 if (mtd->current > mtd->height - 1) 271 mtd->offset = mtd->current - mtd->height + 1; 272 else 273 mtd->offset = 0; 274 } else { 275 mtd->current = 0; 276 mtd->offset = 0; 277 } 278 } 279 280 u_int 281 mode_tree_count_tagged(struct mode_tree_data *mtd) 282 { 283 struct mode_tree_item *mti; 284 u_int i, tagged; 285 286 tagged = 0; 287 for (i = 0; i < mtd->line_size; i++) { 288 mti = mtd->line_list[i].item; 289 if (mti->tagged) 290 tagged++; 291 } 292 return (tagged); 293 } 294 295 void 296 mode_tree_each_tagged(struct mode_tree_data *mtd, mode_tree_each_cb cb, 297 struct client *c, key_code key, int current) 298 { 299 struct mode_tree_item *mti; 300 u_int i; 301 int fired; 302 303 fired = 0; 304 for (i = 0; i < mtd->line_size; i++) { 305 mti = mtd->line_list[i].item; 306 if (mti->tagged) { 307 fired = 1; 308 cb(mtd->modedata, mti->itemdata, c, key); 309 } 310 } 311 if (!fired && current) { 312 mti = mtd->line_list[mtd->current].item; 313 cb(mtd->modedata, mti->itemdata, c, key); 314 } 315 } 316 317 struct mode_tree_data * 318 mode_tree_start(struct window_pane *wp, struct args *args, 319 mode_tree_build_cb buildcb, mode_tree_draw_cb drawcb, 320 mode_tree_search_cb searchcb, mode_tree_menu_cb menucb, void *modedata, 321 const struct menu_item *menu, const char **sort_list, u_int sort_size, 322 struct screen **s) 323 { 324 struct mode_tree_data *mtd; 325 const char *sort; 326 u_int i; 327 328 mtd = xcalloc(1, sizeof *mtd); 329 mtd->references = 1; 330 331 mtd->wp = wp; 332 mtd->modedata = modedata; 333 mtd->menu = menu; 334 335 mtd->sort_list = sort_list; 336 mtd->sort_size = sort_size; 337 338 mtd->preview = !args_has(args, 'N'); 339 340 sort = args_get(args, 'O'); 341 if (sort != NULL) { 342 for (i = 0; i < sort_size; i++) { 343 if (strcasecmp(sort, sort_list[i]) == 0) 344 mtd->sort_crit.field = i; 345 } 346 } 347 mtd->sort_crit.reversed = args_has(args, 'r'); 348 349 if (args_has(args, 'f')) 350 mtd->filter = xstrdup(args_get(args, 'f')); 351 else 352 mtd->filter = NULL; 353 354 mtd->buildcb = buildcb; 355 mtd->drawcb = drawcb; 356 mtd->searchcb = searchcb; 357 mtd->menucb = menucb; 358 359 TAILQ_INIT(&mtd->children); 360 361 *s = &mtd->screen; 362 screen_init(*s, screen_size_x(&wp->base), screen_size_y(&wp->base), 0); 363 (*s)->mode &= ~MODE_CURSOR; 364 365 return (mtd); 366 } 367 368 void 369 mode_tree_zoom(struct mode_tree_data *mtd, struct args *args) 370 { 371 struct window_pane *wp = mtd->wp; 372 373 if (args_has(args, 'Z')) { 374 mtd->zoomed = (wp->window->flags & WINDOW_ZOOMED); 375 if (!mtd->zoomed && window_zoom(wp) == 0) 376 server_redraw_window(wp->window); 377 } else 378 mtd->zoomed = -1; 379 } 380 381 void 382 mode_tree_build(struct mode_tree_data *mtd) 383 { 384 struct screen *s = &mtd->screen; 385 uint64_t tag; 386 387 if (mtd->line_list != NULL) 388 tag = mtd->line_list[mtd->current].item->tag; 389 else 390 tag = UINT64_MAX; 391 392 TAILQ_CONCAT(&mtd->saved, &mtd->children, entry); 393 TAILQ_INIT(&mtd->children); 394 395 mtd->buildcb(mtd->modedata, &mtd->sort_crit, &tag, mtd->filter); 396 mtd->no_matches = TAILQ_EMPTY(&mtd->children); 397 if (mtd->no_matches) 398 mtd->buildcb(mtd->modedata, &mtd->sort_crit, &tag, NULL); 399 400 mode_tree_free_items(&mtd->saved); 401 TAILQ_INIT(&mtd->saved); 402 403 mode_tree_clear_lines(mtd); 404 mode_tree_build_lines(mtd, &mtd->children, 0); 405 406 if (tag == UINT64_MAX) 407 tag = mtd->line_list[mtd->current].item->tag; 408 mode_tree_set_current(mtd, tag); 409 410 mtd->width = screen_size_x(s); 411 if (mtd->preview) { 412 mtd->height = (screen_size_y(s) / 3) * 2; 413 if (mtd->height > mtd->line_size) 414 mtd->height = screen_size_y(s) / 2; 415 if (mtd->height < 10) 416 mtd->height = screen_size_y(s); 417 if (screen_size_y(s) - mtd->height < 2) 418 mtd->height = screen_size_y(s); 419 } else 420 mtd->height = screen_size_y(s); 421 mode_tree_check_selected(mtd); 422 } 423 424 static void 425 mode_tree_remove_ref(struct mode_tree_data *mtd) 426 { 427 if (--mtd->references == 0) 428 free(mtd); 429 } 430 431 void 432 mode_tree_free(struct mode_tree_data *mtd) 433 { 434 struct window_pane *wp = mtd->wp; 435 436 if (mtd->zoomed == 0) 437 server_unzoom_window(wp->window); 438 439 mode_tree_free_items(&mtd->children); 440 mode_tree_clear_lines(mtd); 441 screen_free(&mtd->screen); 442 443 free(mtd->search); 444 free(mtd->filter); 445 446 mtd->dead = 1; 447 mode_tree_remove_ref(mtd); 448 } 449 450 void 451 mode_tree_resize(struct mode_tree_data *mtd, u_int sx, u_int sy) 452 { 453 struct screen *s = &mtd->screen; 454 455 screen_resize(s, sx, sy, 0); 456 457 mode_tree_build(mtd); 458 mode_tree_draw(mtd); 459 460 mtd->wp->flags |= PANE_REDRAW; 461 } 462 463 struct mode_tree_item * 464 mode_tree_add(struct mode_tree_data *mtd, struct mode_tree_item *parent, 465 void *itemdata, uint64_t tag, const char *name, const char *text, 466 int expanded) 467 { 468 struct mode_tree_item *mti, *saved; 469 470 log_debug("%s: %llu, %s %s", __func__, (unsigned long long)tag, 471 name, text); 472 473 mti = xcalloc(1, sizeof *mti); 474 mti->parent = parent; 475 mti->itemdata = itemdata; 476 477 mti->tag = tag; 478 mti->name = xstrdup(name); 479 mti->text = xstrdup(text); 480 481 saved = mode_tree_find_item(&mtd->saved, tag); 482 if (saved != NULL) { 483 if (parent == NULL || parent->expanded) 484 mti->tagged = saved->tagged; 485 mti->expanded = saved->expanded; 486 } else if (expanded == -1) 487 mti->expanded = 1; 488 else 489 mti->expanded = expanded; 490 491 TAILQ_INIT(&mti->children); 492 493 if (parent != NULL) 494 TAILQ_INSERT_TAIL(&parent->children, mti, entry); 495 else 496 TAILQ_INSERT_TAIL(&mtd->children, mti, entry); 497 498 return (mti); 499 } 500 501 void 502 mode_tree_remove(struct mode_tree_data *mtd, struct mode_tree_item *mti) 503 { 504 struct mode_tree_item *parent = mti->parent; 505 506 if (parent != NULL) 507 TAILQ_REMOVE(&parent->children, mti, entry); 508 else 509 TAILQ_REMOVE(&mtd->children, mti, entry); 510 mode_tree_free_item(mti); 511 } 512 513 void 514 mode_tree_draw(struct mode_tree_data *mtd) 515 { 516 struct window_pane *wp = mtd->wp; 517 struct screen *s = &mtd->screen; 518 struct mode_tree_line *line; 519 struct mode_tree_item *mti; 520 struct options *oo = wp->window->options; 521 struct screen_write_ctx ctx; 522 struct grid_cell gc0, gc; 523 u_int w, h, i, j, sy, box_x, box_y, width; 524 char *text, *start, key[7]; 525 const char *tag, *symbol; 526 size_t size, n; 527 int keylen; 528 529 if (mtd->line_size == 0) 530 return; 531 532 memcpy(&gc0, &grid_default_cell, sizeof gc0); 533 memcpy(&gc, &grid_default_cell, sizeof gc); 534 style_apply(&gc, oo, "mode-style"); 535 536 w = mtd->width; 537 h = mtd->height; 538 539 screen_write_start(&ctx, NULL, s); 540 screen_write_clearscreen(&ctx, 8); 541 542 if (mtd->line_size > 10) 543 keylen = 6; 544 else 545 keylen = 4; 546 547 for (i = 0; i < mtd->line_size; i++) { 548 if (i < mtd->offset) 549 continue; 550 if (i > mtd->offset + h - 1) 551 break; 552 553 line = &mtd->line_list[i]; 554 mti = line->item; 555 556 screen_write_cursormove(&ctx, 0, i - mtd->offset, 0); 557 558 if (i < 10) 559 snprintf(key, sizeof key, "(%c) ", '0' + i); 560 else if (i < 36) 561 snprintf(key, sizeof key, "(M-%c)", 'a' + (i - 10)); 562 else 563 *key = '\0'; 564 565 if (line->flat) 566 symbol = ""; 567 else if (TAILQ_EMPTY(&mti->children)) 568 symbol = " "; 569 else if (mti->expanded) 570 symbol = "- "; 571 else 572 symbol = "+ "; 573 574 if (line->depth == 0) 575 start = xstrdup(symbol); 576 else { 577 size = (4 * line->depth) + 32; 578 579 start = xcalloc(1, size); 580 for (j = 1; j < line->depth; j++) { 581 if (mti->parent != NULL && 582 mtd->line_list[mti->parent->line].last) 583 strlcat(start, " ", size); 584 else 585 strlcat(start, "\001x\001 ", size); 586 } 587 if (line->last) 588 strlcat(start, "\001mq\001> ", size); 589 else 590 strlcat(start, "\001tq\001> ", size); 591 strlcat(start, symbol, size); 592 } 593 594 if (mti->tagged) 595 tag = "*"; 596 else 597 tag = ""; 598 xasprintf(&text, "%-*s%s%s%s: ", keylen, key, start, mti->name, 599 tag); 600 width = utf8_cstrwidth(text); 601 if (width > w) 602 width = w; 603 free(start); 604 605 if (mti->tagged) { 606 gc.attr ^= GRID_ATTR_BRIGHT; 607 gc0.attr ^= GRID_ATTR_BRIGHT; 608 } 609 610 if (i != mtd->current) { 611 screen_write_clearendofline(&ctx, 8); 612 screen_write_nputs(&ctx, w, &gc0, "%s", text); 613 format_draw(&ctx, &gc0, w - width, mti->text, NULL); 614 } else { 615 screen_write_clearendofline(&ctx, gc.bg); 616 screen_write_nputs(&ctx, w, &gc, "%s", text); 617 format_draw(&ctx, &gc, w - width, mti->text, NULL); 618 } 619 free(text); 620 621 if (mti->tagged) { 622 gc.attr ^= GRID_ATTR_BRIGHT; 623 gc0.attr ^= GRID_ATTR_BRIGHT; 624 } 625 } 626 627 sy = screen_size_y(s); 628 if (!mtd->preview || sy <= 4 || h <= 4 || sy - h <= 4 || w <= 4) { 629 screen_write_stop(&ctx); 630 return; 631 } 632 633 line = &mtd->line_list[mtd->current]; 634 mti = line->item; 635 636 screen_write_cursormove(&ctx, 0, h, 0); 637 screen_write_box(&ctx, w, sy - h); 638 639 xasprintf(&text, " %s (sort: %s%s)", mti->name, 640 mtd->sort_list[mtd->sort_crit.field], 641 mtd->sort_crit.reversed ? ", reversed" : ""); 642 if (w - 2 >= strlen(text)) { 643 screen_write_cursormove(&ctx, 1, h, 0); 644 screen_write_puts(&ctx, &gc0, "%s", text); 645 646 if (mtd->no_matches) 647 n = (sizeof "no matches") - 1; 648 else 649 n = (sizeof "active") - 1; 650 if (mtd->filter != NULL && w - 2 >= strlen(text) + 10 + n + 2) { 651 screen_write_puts(&ctx, &gc0, " (filter: "); 652 if (mtd->no_matches) 653 screen_write_puts(&ctx, &gc, "no matches"); 654 else 655 screen_write_puts(&ctx, &gc0, "active"); 656 screen_write_puts(&ctx, &gc0, ") "); 657 } 658 } 659 free(text); 660 661 box_x = w - 4; 662 box_y = sy - h - 2; 663 664 if (box_x != 0 && box_y != 0) { 665 screen_write_cursormove(&ctx, 2, h + 1, 0); 666 mtd->drawcb(mtd->modedata, mti->itemdata, &ctx, box_x, box_y); 667 } 668 669 screen_write_stop(&ctx); 670 } 671 672 static struct mode_tree_item * 673 mode_tree_search_for(struct mode_tree_data *mtd) 674 { 675 struct mode_tree_item *mti, *last, *next; 676 677 if (mtd->search == NULL) 678 return (NULL); 679 680 mti = last = mtd->line_list[mtd->current].item; 681 for (;;) { 682 if (!TAILQ_EMPTY(&mti->children)) 683 mti = TAILQ_FIRST(&mti->children); 684 else if ((next = TAILQ_NEXT(mti, entry)) != NULL) 685 mti = next; 686 else { 687 for (;;) { 688 mti = mti->parent; 689 if (mti == NULL) 690 break; 691 if ((next = TAILQ_NEXT(mti, entry)) != NULL) { 692 mti = next; 693 break; 694 } 695 } 696 } 697 if (mti == NULL) 698 mti = TAILQ_FIRST(&mtd->children); 699 if (mti == last) 700 break; 701 702 if (mtd->searchcb == NULL) { 703 if (strstr(mti->name, mtd->search) != NULL) 704 return (mti); 705 continue; 706 } 707 if (mtd->searchcb(mtd->modedata, mti->itemdata, mtd->search)) 708 return (mti); 709 } 710 return (NULL); 711 } 712 713 static void 714 mode_tree_search_set(struct mode_tree_data *mtd) 715 { 716 struct mode_tree_item *mti, *loop; 717 uint64_t tag; 718 719 mti = mode_tree_search_for(mtd); 720 if (mti == NULL) 721 return; 722 tag = mti->tag; 723 724 loop = mti->parent; 725 while (loop != NULL) { 726 loop->expanded = 1; 727 loop = loop->parent; 728 } 729 730 mode_tree_build(mtd); 731 mode_tree_set_current(mtd, tag); 732 mode_tree_draw(mtd); 733 mtd->wp->flags |= PANE_REDRAW; 734 } 735 736 static int 737 mode_tree_search_callback(__unused struct client *c, void *data, const char *s, 738 __unused int done) 739 { 740 struct mode_tree_data *mtd = data; 741 742 if (mtd->dead) 743 return (0); 744 745 free(mtd->search); 746 if (s == NULL || *s == '\0') { 747 mtd->search = NULL; 748 return (0); 749 } 750 mtd->search = xstrdup(s); 751 mode_tree_search_set(mtd); 752 753 return (0); 754 } 755 756 static void 757 mode_tree_search_free(void *data) 758 { 759 mode_tree_remove_ref(data); 760 } 761 762 static int 763 mode_tree_filter_callback(__unused struct client *c, void *data, const char *s, 764 __unused int done) 765 { 766 struct mode_tree_data *mtd = data; 767 768 if (mtd->dead) 769 return (0); 770 771 if (mtd->filter != NULL) 772 free(mtd->filter); 773 if (s == NULL || *s == '\0') 774 mtd->filter = NULL; 775 else 776 mtd->filter = xstrdup(s); 777 778 mode_tree_build(mtd); 779 mode_tree_draw(mtd); 780 mtd->wp->flags |= PANE_REDRAW; 781 782 return (0); 783 } 784 785 static void 786 mode_tree_filter_free(void *data) 787 { 788 mode_tree_remove_ref(data); 789 } 790 791 static void 792 mode_tree_menu_callback(__unused struct menu *menu, __unused u_int idx, 793 key_code key, void *data) 794 { 795 struct mode_tree_menu *mtm = data; 796 struct mode_tree_data *mtd = mtm->data; 797 struct mode_tree_item *mti; 798 799 if (mtd->dead || key == KEYC_NONE) 800 goto out; 801 802 if (mtm->line >= mtd->line_size) 803 goto out; 804 mti = mtd->line_list[mtm->line].item; 805 if (mti->itemdata != mtm->itemdata) 806 goto out; 807 mtd->current = mtm->line; 808 mtd->menucb (mtd->modedata, mtm->c, key); 809 810 out: 811 mode_tree_remove_ref(mtd); 812 free(mtm); 813 } 814 815 static void 816 mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x, 817 u_int y, int outside) 818 { 819 struct mode_tree_item *mti; 820 struct menu *menu; 821 const struct menu_item *items; 822 struct mode_tree_menu *mtm; 823 char *title; 824 u_int line; 825 826 if (mtd->offset + y > mtd->line_size - 1) 827 line = mtd->current; 828 else 829 line = mtd->offset + y; 830 mti = mtd->line_list[line].item; 831 832 if (!outside) { 833 items = mtd->menu; 834 xasprintf(&title, "#[align=centre]%s", mti->name); 835 } else { 836 items = mode_tree_menu_items; 837 title = xstrdup(""); 838 } 839 menu = menu_create(title); 840 menu_add_items(menu, items, NULL, NULL, NULL); 841 free(title); 842 843 mtm = xmalloc(sizeof *mtm); 844 mtm->data = mtd; 845 mtm->c = c; 846 mtm->line = line; 847 mtm->itemdata = mti->itemdata; 848 mtd->references++; 849 850 if (menu_display(menu, 0, NULL, x, y, c, NULL, mode_tree_menu_callback, 851 mtm) != 0) 852 menu_free(menu); 853 } 854 855 int 856 mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, 857 struct mouse_event *m, u_int *xp, u_int *yp) 858 { 859 struct mode_tree_line *line; 860 struct mode_tree_item *current, *parent; 861 u_int i, x, y; 862 int choice; 863 key_code tmp; 864 865 if (KEYC_IS_MOUSE(*key) && m != NULL) { 866 if (cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) { 867 *key = KEYC_NONE; 868 return (0); 869 } 870 if (xp != NULL) 871 *xp = x; 872 if (yp != NULL) 873 *yp = y; 874 if (x > mtd->width || y > mtd->height) { 875 if (*key == KEYC_MOUSEDOWN3_PANE) 876 mode_tree_display_menu(mtd, c, x, y, 1); 877 if (!mtd->preview) 878 *key = KEYC_NONE; 879 return (0); 880 } 881 if (mtd->offset + y < mtd->line_size) { 882 if (*key == KEYC_MOUSEDOWN1_PANE || 883 *key == KEYC_MOUSEDOWN3_PANE || 884 *key == KEYC_DOUBLECLICK1_PANE) 885 mtd->current = mtd->offset + y; 886 if (*key == KEYC_DOUBLECLICK1_PANE) 887 *key = '\r'; 888 else { 889 if (*key == KEYC_MOUSEDOWN3_PANE) 890 mode_tree_display_menu(mtd, c, x, y, 0); 891 *key = KEYC_NONE; 892 } 893 } else { 894 if (*key == KEYC_MOUSEDOWN3_PANE) 895 mode_tree_display_menu(mtd, c, x, y, 0); 896 *key = KEYC_NONE; 897 } 898 return (0); 899 } 900 901 line = &mtd->line_list[mtd->current]; 902 current = line->item; 903 904 choice = -1; 905 if (*key >= '0' && *key <= '9') 906 choice = (*key) - '0'; 907 else if (((*key) & KEYC_MASK_MOD) == KEYC_ESCAPE) { 908 tmp = (*key) & KEYC_MASK_KEY; 909 if (tmp >= 'a' && tmp <= 'z') 910 choice = 10 + (tmp - 'a'); 911 } 912 if (choice != -1) { 913 if ((u_int)choice > mtd->line_size - 1) { 914 *key = KEYC_NONE; 915 return (0); 916 } 917 mtd->current = choice; 918 *key = '\r'; 919 return (0); 920 } 921 922 switch (*key) { 923 case 'q': 924 case '\033': /* Escape */ 925 case '\007': /* C-g */ 926 return (1); 927 case KEYC_UP: 928 case 'k': 929 case KEYC_WHEELUP_PANE: 930 case '\020': /* C-p */ 931 mode_tree_up(mtd, 1); 932 break; 933 case KEYC_DOWN: 934 case 'j': 935 case KEYC_WHEELDOWN_PANE: 936 case '\016': /* C-n */ 937 mode_tree_down(mtd, 1); 938 break; 939 case 'g': 940 case KEYC_PPAGE: 941 case '\002': /* C-b */ 942 for (i = 0; i < mtd->height; i++) { 943 if (mtd->current == 0) 944 break; 945 mode_tree_up(mtd, 1); 946 } 947 break; 948 case 'G': 949 case KEYC_NPAGE: 950 case '\006': /* C-f */ 951 for (i = 0; i < mtd->height; i++) { 952 if (mtd->current == mtd->line_size - 1) 953 break; 954 mode_tree_down(mtd, 1); 955 } 956 break; 957 case KEYC_HOME: 958 mtd->current = 0; 959 mtd->offset = 0; 960 break; 961 case KEYC_END: 962 mtd->current = mtd->line_size - 1; 963 if (mtd->current > mtd->height - 1) 964 mtd->offset = mtd->current - mtd->height + 1; 965 else 966 mtd->offset = 0; 967 break; 968 case 't': 969 /* 970 * Do not allow parents and children to both be tagged: untag 971 * all parents and children of current. 972 */ 973 if (!current->tagged) { 974 parent = current->parent; 975 while (parent != NULL) { 976 parent->tagged = 0; 977 parent = parent->parent; 978 } 979 mode_tree_clear_tagged(¤t->children); 980 current->tagged = 1; 981 } else 982 current->tagged = 0; 983 if (m != NULL) 984 mode_tree_down(mtd, 0); 985 break; 986 case 'T': 987 for (i = 0; i < mtd->line_size; i++) 988 mtd->line_list[i].item->tagged = 0; 989 break; 990 case '\024': /* C-t */ 991 for (i = 0; i < mtd->line_size; i++) { 992 if (mtd->line_list[i].item->parent == NULL) 993 mtd->line_list[i].item->tagged = 1; 994 else 995 mtd->line_list[i].item->tagged = 0; 996 } 997 break; 998 case 'O': 999 mtd->sort_crit.field++; 1000 if (mtd->sort_crit.field == mtd->sort_size) 1001 mtd->sort_crit.field = 0; 1002 mode_tree_build(mtd); 1003 break; 1004 case 'r': 1005 mtd->sort_crit.reversed = !mtd->sort_crit.reversed; 1006 mode_tree_build(mtd); 1007 break; 1008 case KEYC_LEFT: 1009 case 'h': 1010 case '-': 1011 if (line->flat || !current->expanded) 1012 current = current->parent; 1013 if (current == NULL) 1014 mode_tree_up(mtd, 0); 1015 else { 1016 current->expanded = 0; 1017 mtd->current = current->line; 1018 mode_tree_build(mtd); 1019 } 1020 break; 1021 case KEYC_RIGHT: 1022 case 'l': 1023 case '+': 1024 if (line->flat || current->expanded) 1025 mode_tree_down(mtd, 0); 1026 else if (!line->flat) { 1027 current->expanded = 1; 1028 mode_tree_build(mtd); 1029 } 1030 break; 1031 case '?': 1032 case '/': 1033 case '\023': /* C-s */ 1034 mtd->references++; 1035 status_prompt_set(c, "(search) ", "", 1036 mode_tree_search_callback, mode_tree_search_free, mtd, 1037 PROMPT_NOFORMAT); 1038 break; 1039 case 'n': 1040 mode_tree_search_set(mtd); 1041 break; 1042 case 'f': 1043 mtd->references++; 1044 status_prompt_set(c, "(filter) ", mtd->filter, 1045 mode_tree_filter_callback, mode_tree_filter_free, mtd, 1046 PROMPT_NOFORMAT); 1047 break; 1048 case 'v': 1049 mtd->preview = !mtd->preview; 1050 mode_tree_build(mtd); 1051 if (mtd->preview) 1052 mode_tree_check_selected(mtd); 1053 break; 1054 } 1055 return (0); 1056 } 1057 1058 void 1059 mode_tree_run_command(struct client *c, struct cmd_find_state *fs, 1060 const char *template, const char *name) 1061 { 1062 struct cmdq_item *new_item; 1063 char *command; 1064 struct cmd_parse_result *pr; 1065 1066 command = cmd_template_replace(template, name, 1); 1067 if (command == NULL || *command == '\0') { 1068 free(command); 1069 return; 1070 } 1071 1072 pr = cmd_parse_from_string(command, NULL); 1073 switch (pr->status) { 1074 case CMD_PARSE_EMPTY: 1075 break; 1076 case CMD_PARSE_ERROR: 1077 if (c != NULL) { 1078 *pr->error = toupper((u_char)*pr->error); 1079 status_message_set(c, "%s", pr->error); 1080 } 1081 free(pr->error); 1082 break; 1083 case CMD_PARSE_SUCCESS: 1084 new_item = cmdq_get_command(pr->cmdlist, fs, NULL, 0); 1085 cmdq_append(c, new_item); 1086 cmd_list_free(pr->cmdlist); 1087 break; 1088 } 1089 1090 free(command); 1091 } 1092