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