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