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