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