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