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