1 /* $OpenBSD: mode-tree.c,v 1.34 2019/05/30 20:54:03 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 u_int sort_type; 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 mtd->sort_type = 0; 338 339 mtd->preview = !args_has(args, 'N'); 340 341 sort = args_get(args, 'O'); 342 if (sort != NULL) { 343 for (i = 0; i < sort_size; i++) { 344 if (strcasecmp(sort, sort_list[i]) == 0) 345 mtd->sort_type = i; 346 } 347 } 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_type, &tag, mtd->filter); 396 mtd->no_matches = TAILQ_EMPTY(&mtd->children); 397 if (mtd->no_matches) 398 mtd->buildcb(mtd->modedata, mtd->sort_type, &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 free(start); 602 603 if (mti->tagged) { 604 gc.attr ^= GRID_ATTR_BRIGHT; 605 gc0.attr ^= GRID_ATTR_BRIGHT; 606 } 607 608 if (i != mtd->current) { 609 screen_write_clearendofline(&ctx, 8); 610 screen_write_puts(&ctx, &gc0, "%s", text); 611 format_draw(&ctx, &gc0, w - width, mti->text, NULL); 612 } else { 613 screen_write_clearendofline(&ctx, gc.bg); 614 screen_write_puts(&ctx, &gc, "%s", text); 615 format_draw(&ctx, &gc, w - width, mti->text, NULL); 616 } 617 free(text); 618 619 if (mti->tagged) { 620 gc.attr ^= GRID_ATTR_BRIGHT; 621 gc0.attr ^= GRID_ATTR_BRIGHT; 622 } 623 } 624 625 sy = screen_size_y(s); 626 if (!mtd->preview || sy <= 4 || h <= 4 || sy - h <= 4 || w <= 4) { 627 screen_write_stop(&ctx); 628 return; 629 } 630 631 line = &mtd->line_list[mtd->current]; 632 mti = line->item; 633 634 screen_write_cursormove(&ctx, 0, h, 0); 635 screen_write_box(&ctx, w, sy - h); 636 637 xasprintf(&text, " %s (sort: %s)", mti->name, 638 mtd->sort_list[mtd->sort_type]); 639 if (w - 2 >= strlen(text)) { 640 screen_write_cursormove(&ctx, 1, h, 0); 641 screen_write_puts(&ctx, &gc0, "%s", text); 642 643 if (mtd->no_matches) 644 n = (sizeof "no matches") - 1; 645 else 646 n = (sizeof "active") - 1; 647 if (mtd->filter != NULL && w - 2 >= strlen(text) + 10 + n + 2) { 648 screen_write_puts(&ctx, &gc0, " (filter: "); 649 if (mtd->no_matches) 650 screen_write_puts(&ctx, &gc, "no matches"); 651 else 652 screen_write_puts(&ctx, &gc0, "active"); 653 screen_write_puts(&ctx, &gc0, ") "); 654 } 655 } 656 free(text); 657 658 box_x = w - 4; 659 box_y = sy - h - 2; 660 661 if (box_x != 0 && box_y != 0) { 662 screen_write_cursormove(&ctx, 2, h + 1, 0); 663 mtd->drawcb(mtd->modedata, mti->itemdata, &ctx, box_x, box_y); 664 } 665 666 screen_write_stop(&ctx); 667 } 668 669 static struct mode_tree_item * 670 mode_tree_search_for(struct mode_tree_data *mtd) 671 { 672 struct mode_tree_item *mti, *last, *next; 673 674 if (mtd->search == NULL) 675 return (NULL); 676 677 mti = last = mtd->line_list[mtd->current].item; 678 for (;;) { 679 if (!TAILQ_EMPTY(&mti->children)) 680 mti = TAILQ_FIRST(&mti->children); 681 else if ((next = TAILQ_NEXT(mti, entry)) != NULL) 682 mti = next; 683 else { 684 for (;;) { 685 mti = mti->parent; 686 if (mti == NULL) 687 break; 688 if ((next = TAILQ_NEXT(mti, entry)) != NULL) { 689 mti = next; 690 break; 691 } 692 } 693 } 694 if (mti == NULL) 695 mti = TAILQ_FIRST(&mtd->children); 696 if (mti == last) 697 break; 698 699 if (mtd->searchcb == NULL) { 700 if (strstr(mti->name, mtd->search) != NULL) 701 return (mti); 702 continue; 703 } 704 if (mtd->searchcb(mtd->modedata, mti->itemdata, mtd->search)) 705 return (mti); 706 } 707 return (NULL); 708 } 709 710 static void 711 mode_tree_search_set(struct mode_tree_data *mtd) 712 { 713 struct mode_tree_item *mti, *loop; 714 uint64_t tag; 715 716 mti = mode_tree_search_for(mtd); 717 if (mti == NULL) 718 return; 719 tag = mti->tag; 720 721 loop = mti->parent; 722 while (loop != NULL) { 723 loop->expanded = 1; 724 loop = loop->parent; 725 } 726 727 mode_tree_build(mtd); 728 mode_tree_set_current(mtd, tag); 729 mode_tree_draw(mtd); 730 mtd->wp->flags |= PANE_REDRAW; 731 } 732 733 static int 734 mode_tree_search_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 free(mtd->search); 743 if (s == NULL || *s == '\0') { 744 mtd->search = NULL; 745 return (0); 746 } 747 mtd->search = xstrdup(s); 748 mode_tree_search_set(mtd); 749 750 return (0); 751 } 752 753 static void 754 mode_tree_search_free(void *data) 755 { 756 mode_tree_remove_ref(data); 757 } 758 759 static int 760 mode_tree_filter_callback(__unused struct client *c, void *data, const char *s, 761 __unused int done) 762 { 763 struct mode_tree_data *mtd = data; 764 765 if (mtd->dead) 766 return (0); 767 768 if (mtd->filter != NULL) 769 free(mtd->filter); 770 if (s == NULL || *s == '\0') 771 mtd->filter = NULL; 772 else 773 mtd->filter = xstrdup(s); 774 775 mode_tree_build(mtd); 776 mode_tree_draw(mtd); 777 mtd->wp->flags |= PANE_REDRAW; 778 779 return (0); 780 } 781 782 static void 783 mode_tree_filter_free(void *data) 784 { 785 mode_tree_remove_ref(data); 786 } 787 788 static void 789 mode_tree_menu_callback(__unused struct menu *menu, __unused u_int idx, 790 key_code key, void *data) 791 { 792 struct mode_tree_menu *mtm = data; 793 struct mode_tree_data *mtd = mtm->data; 794 struct mode_tree_item *mti; 795 796 if (mtd->dead || key == KEYC_NONE) 797 goto out; 798 799 if (mtm->line >= mtd->line_size) 800 goto out; 801 mti = mtd->line_list[mtm->line].item; 802 if (mti->itemdata != mtm->itemdata) 803 goto out; 804 mtd->current = mtm->line; 805 mtd->menucb (mtd->modedata, mtm->c, key); 806 807 out: 808 mode_tree_remove_ref(mtd); 809 free(mtm); 810 } 811 812 static void 813 mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x, 814 u_int y, int outside) 815 { 816 struct mode_tree_item *mti; 817 struct menu *menu; 818 const struct menu_item *items; 819 struct mode_tree_menu *mtm; 820 char *title; 821 u_int line; 822 823 if (mtd->offset + y > mtd->line_size - 1) 824 line = mtd->current; 825 else 826 line = mtd->offset + y; 827 mti = mtd->line_list[line].item; 828 829 if (!outside) { 830 items = mtd->menu; 831 xasprintf(&title, "#[align=centre]%s", mti->name); 832 } else { 833 items = mode_tree_menu_items; 834 title = xstrdup(""); 835 } 836 menu = menu_create(title); 837 menu_add_items(menu, items, NULL, NULL, NULL); 838 free(title); 839 840 mtm = xmalloc(sizeof *mtm); 841 mtm->data = mtd; 842 mtm->c = c; 843 mtm->line = line; 844 mtm->itemdata = mti->itemdata; 845 mtd->references++; 846 847 if (menu_display(menu, 0, NULL, x, y, c, NULL, mode_tree_menu_callback, 848 mtm) != 0) 849 menu_free(menu); 850 } 851 852 int 853 mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, 854 struct mouse_event *m, u_int *xp, u_int *yp) 855 { 856 struct mode_tree_line *line; 857 struct mode_tree_item *current, *parent; 858 u_int i, x, y; 859 int choice; 860 key_code tmp; 861 862 if (KEYC_IS_MOUSE(*key) && m != NULL) { 863 if (cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) { 864 *key = KEYC_NONE; 865 return (0); 866 } 867 if (xp != NULL) 868 *xp = x; 869 if (yp != NULL) 870 *yp = y; 871 if (x > mtd->width || y > mtd->height) { 872 if (*key == KEYC_MOUSEDOWN3_PANE) 873 mode_tree_display_menu(mtd, c, x, y, 1); 874 if (!mtd->preview) 875 *key = KEYC_NONE; 876 return (0); 877 } 878 if (mtd->offset + y < mtd->line_size) { 879 if (*key == KEYC_MOUSEDOWN1_PANE || 880 *key == KEYC_MOUSEDOWN3_PANE || 881 *key == KEYC_DOUBLECLICK1_PANE) 882 mtd->current = mtd->offset + y; 883 if (*key == KEYC_DOUBLECLICK1_PANE) 884 *key = '\r'; 885 else { 886 if (*key == KEYC_MOUSEDOWN3_PANE) 887 mode_tree_display_menu(mtd, c, x, y, 0); 888 *key = KEYC_NONE; 889 } 890 } else { 891 if (*key == KEYC_MOUSEDOWN3_PANE) 892 mode_tree_display_menu(mtd, c, x, y, 0); 893 *key = KEYC_NONE; 894 } 895 return (0); 896 } 897 898 line = &mtd->line_list[mtd->current]; 899 current = line->item; 900 901 choice = -1; 902 if (*key >= '0' && *key <= '9') 903 choice = (*key) - '0'; 904 else if (((*key) & KEYC_MASK_MOD) == KEYC_ESCAPE) { 905 tmp = (*key) & KEYC_MASK_KEY; 906 if (tmp >= 'a' && tmp <= 'z') 907 choice = 10 + (tmp - 'a'); 908 } 909 if (choice != -1) { 910 if ((u_int)choice > mtd->line_size - 1) { 911 *key = KEYC_NONE; 912 return (0); 913 } 914 mtd->current = choice; 915 *key = '\r'; 916 return (0); 917 } 918 919 switch (*key) { 920 case 'q': 921 case '\033': /* Escape */ 922 case '\007': /* C-g */ 923 return (1); 924 case KEYC_UP: 925 case 'k': 926 case KEYC_WHEELUP_PANE: 927 case '\020': /* C-p */ 928 mode_tree_up(mtd, 1); 929 break; 930 case KEYC_DOWN: 931 case 'j': 932 case KEYC_WHEELDOWN_PANE: 933 case '\016': /* C-n */ 934 mode_tree_down(mtd, 1); 935 break; 936 case KEYC_PPAGE: 937 case '\002': /* C-b */ 938 for (i = 0; i < mtd->height; i++) { 939 if (mtd->current == 0) 940 break; 941 mode_tree_up(mtd, 1); 942 } 943 break; 944 case KEYC_NPAGE: 945 case '\006': /* C-f */ 946 for (i = 0; i < mtd->height; i++) { 947 if (mtd->current == mtd->line_size - 1) 948 break; 949 mode_tree_down(mtd, 1); 950 } 951 break; 952 case KEYC_HOME: 953 mtd->current = 0; 954 mtd->offset = 0; 955 break; 956 case KEYC_END: 957 mtd->current = mtd->line_size - 1; 958 if (mtd->current > mtd->height - 1) 959 mtd->offset = mtd->current - mtd->height + 1; 960 else 961 mtd->offset = 0; 962 break; 963 case 't': 964 /* 965 * Do not allow parents and children to both be tagged: untag 966 * all parents and children of current. 967 */ 968 if (!current->tagged) { 969 parent = current->parent; 970 while (parent != NULL) { 971 parent->tagged = 0; 972 parent = parent->parent; 973 } 974 mode_tree_clear_tagged(¤t->children); 975 current->tagged = 1; 976 } else 977 current->tagged = 0; 978 if (m != NULL) 979 mode_tree_down(mtd, 0); 980 break; 981 case 'T': 982 for (i = 0; i < mtd->line_size; i++) 983 mtd->line_list[i].item->tagged = 0; 984 break; 985 case '\024': /* C-t */ 986 for (i = 0; i < mtd->line_size; i++) { 987 if (mtd->line_list[i].item->parent == NULL) 988 mtd->line_list[i].item->tagged = 1; 989 else 990 mtd->line_list[i].item->tagged = 0; 991 } 992 break; 993 case 'O': 994 mtd->sort_type++; 995 if (mtd->sort_type == mtd->sort_size) 996 mtd->sort_type = 0; 997 mode_tree_build(mtd); 998 break; 999 case KEYC_LEFT: 1000 case 'h': 1001 case '-': 1002 if (line->flat || !current->expanded) 1003 current = current->parent; 1004 if (current == NULL) 1005 mode_tree_up(mtd, 0); 1006 else { 1007 current->expanded = 0; 1008 mtd->current = current->line; 1009 mode_tree_build(mtd); 1010 } 1011 break; 1012 case KEYC_RIGHT: 1013 case 'l': 1014 case '+': 1015 if (line->flat || current->expanded) 1016 mode_tree_down(mtd, 0); 1017 else if (!line->flat) { 1018 current->expanded = 1; 1019 mode_tree_build(mtd); 1020 } 1021 break; 1022 case '\023': /* C-s */ 1023 mtd->references++; 1024 status_prompt_set(c, "(search) ", "", 1025 mode_tree_search_callback, mode_tree_search_free, mtd, 1026 PROMPT_NOFORMAT); 1027 break; 1028 case 'n': 1029 mode_tree_search_set(mtd); 1030 break; 1031 case 'f': 1032 mtd->references++; 1033 status_prompt_set(c, "(filter) ", mtd->filter, 1034 mode_tree_filter_callback, mode_tree_filter_free, mtd, 1035 PROMPT_NOFORMAT); 1036 break; 1037 case 'v': 1038 mtd->preview = !mtd->preview; 1039 mode_tree_build(mtd); 1040 if (mtd->preview) 1041 mode_tree_check_selected(mtd); 1042 break; 1043 } 1044 return (0); 1045 } 1046 1047 void 1048 mode_tree_run_command(struct client *c, struct cmd_find_state *fs, 1049 const char *template, const char *name) 1050 { 1051 struct cmdq_item *new_item; 1052 char *command; 1053 struct cmd_parse_result *pr; 1054 1055 command = cmd_template_replace(template, name, 1); 1056 if (command == NULL || *command == '\0') { 1057 free(command); 1058 return; 1059 } 1060 1061 pr = cmd_parse_from_string(command, NULL); 1062 switch (pr->status) { 1063 case CMD_PARSE_EMPTY: 1064 break; 1065 case CMD_PARSE_ERROR: 1066 if (c != NULL) { 1067 *pr->error = toupper((u_char)*pr->error); 1068 status_message_set(c, "%s", pr->error); 1069 } 1070 free(pr->error); 1071 break; 1072 case CMD_PARSE_SUCCESS: 1073 new_item = cmdq_get_command(pr->cmdlist, fs, NULL, 0); 1074 cmdq_append(c, new_item); 1075 cmd_list_free(pr->cmdlist); 1076 break; 1077 } 1078 1079 free(command); 1080 } 1081