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