1c9ad075bSchristos /* $OpenBSD$ */ 2c9ad075bSchristos 3c9ad075bSchristos /* 4c9ad075bSchristos * Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com> 5c9ad075bSchristos * 6c9ad075bSchristos * Permission to use, copy, modify, and distribute this software for any 7c9ad075bSchristos * purpose with or without fee is hereby granted, provided that the above 8c9ad075bSchristos * copyright notice and this permission notice appear in all copies. 9c9ad075bSchristos * 10c9ad075bSchristos * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11c9ad075bSchristos * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12c9ad075bSchristos * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13c9ad075bSchristos * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14c9ad075bSchristos * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15c9ad075bSchristos * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16c9ad075bSchristos * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17c9ad075bSchristos */ 18c9ad075bSchristos 19c9ad075bSchristos #include <sys/types.h> 20c9ad075bSchristos 21c9ad075bSchristos #include <ctype.h> 22c9ad075bSchristos #include <stdio.h> 23c9ad075bSchristos #include <stdlib.h> 24c9ad075bSchristos #include <string.h> 25c9ad075bSchristos 26c9ad075bSchristos #include "tmux.h" 27c9ad075bSchristos 28*890b6d91Swiz enum mode_tree_search_dir { 29*890b6d91Swiz MODE_TREE_SEARCH_FORWARD, 30*890b6d91Swiz MODE_TREE_SEARCH_BACKWARD 31*890b6d91Swiz }; 32*890b6d91Swiz 33c9ad075bSchristos struct mode_tree_item; 34c9ad075bSchristos TAILQ_HEAD(mode_tree_list, mode_tree_item); 35c9ad075bSchristos 36c9ad075bSchristos struct mode_tree_data { 37c9ad075bSchristos int dead; 38c9ad075bSchristos u_int references; 39c7e17de0Schristos int zoomed; 40c9ad075bSchristos 41c9ad075bSchristos struct window_pane *wp; 42c9ad075bSchristos void *modedata; 4330744affSchristos const struct menu_item *menu; 44c9ad075bSchristos 45c9ad075bSchristos const char **sort_list; 46c9ad075bSchristos u_int sort_size; 4768e6ba84Schristos struct mode_tree_sort_criteria sort_crit; 48c9ad075bSchristos 49c7e17de0Schristos mode_tree_build_cb buildcb; 50c7e17de0Schristos mode_tree_draw_cb drawcb; 51c7e17de0Schristos mode_tree_search_cb searchcb; 5230744affSchristos mode_tree_menu_cb menucb; 53e271dbb8Schristos mode_tree_height_cb heightcb; 54e271dbb8Schristos mode_tree_key_cb keycb; 55c9ad075bSchristos 56c9ad075bSchristos struct mode_tree_list children; 57c9ad075bSchristos struct mode_tree_list saved; 58c9ad075bSchristos 59c9ad075bSchristos struct mode_tree_line *line_list; 60c9ad075bSchristos u_int line_size; 61c9ad075bSchristos 62c9ad075bSchristos u_int depth; 63c9ad075bSchristos 64c9ad075bSchristos u_int width; 65c9ad075bSchristos u_int height; 66c9ad075bSchristos 67c9ad075bSchristos u_int offset; 68c9ad075bSchristos u_int current; 69c9ad075bSchristos 70c9ad075bSchristos struct screen screen; 71c9ad075bSchristos 72c9ad075bSchristos int preview; 73c9ad075bSchristos char *search; 74c9ad075bSchristos char *filter; 75c7e17de0Schristos int no_matches; 76*890b6d91Swiz enum mode_tree_search_dir search_dir; 77c9ad075bSchristos }; 78c9ad075bSchristos 79c9ad075bSchristos struct mode_tree_item { 80c9ad075bSchristos struct mode_tree_item *parent; 81c9ad075bSchristos void *itemdata; 82c9ad075bSchristos u_int line; 83c9ad075bSchristos 84e271dbb8Schristos key_code key; 85e271dbb8Schristos const char *keystr; 86e271dbb8Schristos size_t keylen; 87e271dbb8Schristos 88c9ad075bSchristos uint64_t tag; 89c9ad075bSchristos const char *name; 90c9ad075bSchristos const char *text; 91c9ad075bSchristos 92c9ad075bSchristos int expanded; 93c9ad075bSchristos int tagged; 94c9ad075bSchristos 95e271dbb8Schristos int draw_as_parent; 96e271dbb8Schristos int no_tag; 97e271dbb8Schristos 98c9ad075bSchristos struct mode_tree_list children; 99c9ad075bSchristos TAILQ_ENTRY(mode_tree_item) entry; 100c9ad075bSchristos }; 101c9ad075bSchristos 102c9ad075bSchristos struct mode_tree_line { 103c9ad075bSchristos struct mode_tree_item *item; 104c9ad075bSchristos u_int depth; 105c9ad075bSchristos int last; 106c9ad075bSchristos int flat; 107c9ad075bSchristos }; 108c9ad075bSchristos 10930744affSchristos struct mode_tree_menu { 11030744affSchristos struct mode_tree_data *data; 11130744affSchristos struct client *c; 11230744affSchristos u_int line; 11330744affSchristos }; 11430744affSchristos 115c9ad075bSchristos static void mode_tree_free_items(struct mode_tree_list *); 116c9ad075bSchristos 11730744affSchristos static const struct menu_item mode_tree_menu_items[] = { 11830744affSchristos { "Scroll Left", '<', NULL }, 11930744affSchristos { "Scroll Right", '>', NULL }, 12030744affSchristos { "", KEYC_NONE, NULL }, 12130744affSchristos { "Cancel", 'q', NULL }, 12230744affSchristos 12330744affSchristos { NULL, KEYC_NONE, NULL } 12430744affSchristos }; 12530744affSchristos 126c9ad075bSchristos static struct mode_tree_item * 127c9ad075bSchristos mode_tree_find_item(struct mode_tree_list *mtl, uint64_t tag) 128c9ad075bSchristos { 129c9ad075bSchristos struct mode_tree_item *mti, *child; 130c9ad075bSchristos 131c9ad075bSchristos TAILQ_FOREACH(mti, mtl, entry) { 132c9ad075bSchristos if (mti->tag == tag) 133c9ad075bSchristos return (mti); 134c9ad075bSchristos child = mode_tree_find_item(&mti->children, tag); 135c9ad075bSchristos if (child != NULL) 136c9ad075bSchristos return (child); 137c9ad075bSchristos } 138c9ad075bSchristos return (NULL); 139c9ad075bSchristos } 140c9ad075bSchristos 141c9ad075bSchristos static void 142c9ad075bSchristos mode_tree_free_item(struct mode_tree_item *mti) 143c9ad075bSchristos { 144c9ad075bSchristos mode_tree_free_items(&mti->children); 145c9ad075bSchristos 146fe99a117Schristos free(__UNCONST(mti->name)); 147bcc4e02eSchristos free(__UNCONST(mti->text)); 148e271dbb8Schristos free(__UNCONST(mti->keystr)); 149c9ad075bSchristos 150c9ad075bSchristos free(mti); 151c9ad075bSchristos } 152c9ad075bSchristos 153c9ad075bSchristos static void 154c9ad075bSchristos mode_tree_free_items(struct mode_tree_list *mtl) 155c9ad075bSchristos { 156c9ad075bSchristos struct mode_tree_item *mti, *mti1; 157c9ad075bSchristos 158c9ad075bSchristos TAILQ_FOREACH_SAFE(mti, mtl, entry, mti1) { 159c9ad075bSchristos TAILQ_REMOVE(mtl, mti, entry); 160c9ad075bSchristos mode_tree_free_item(mti); 161c9ad075bSchristos } 162c9ad075bSchristos } 163c9ad075bSchristos 164c9ad075bSchristos static void 165c9ad075bSchristos mode_tree_check_selected(struct mode_tree_data *mtd) 166c9ad075bSchristos { 167c9ad075bSchristos /* 168c9ad075bSchristos * If the current line would now be off screen reset the offset to the 169c9ad075bSchristos * last visible line. 170c9ad075bSchristos */ 171c9ad075bSchristos if (mtd->current > mtd->height - 1) 172c9ad075bSchristos mtd->offset = mtd->current - mtd->height + 1; 173c9ad075bSchristos } 174c9ad075bSchristos 175c9ad075bSchristos static void 176c9ad075bSchristos mode_tree_clear_lines(struct mode_tree_data *mtd) 177c9ad075bSchristos { 178c9ad075bSchristos free(mtd->line_list); 179c9ad075bSchristos mtd->line_list = NULL; 180c9ad075bSchristos mtd->line_size = 0; 181c9ad075bSchristos } 182c9ad075bSchristos 183c9ad075bSchristos static void 184c9ad075bSchristos mode_tree_build_lines(struct mode_tree_data *mtd, 185c9ad075bSchristos struct mode_tree_list *mtl, u_int depth) 186c9ad075bSchristos { 187c9ad075bSchristos struct mode_tree_item *mti; 188c9ad075bSchristos struct mode_tree_line *line; 189c9ad075bSchristos u_int i; 190c9ad075bSchristos int flat = 1; 191c9ad075bSchristos 192c9ad075bSchristos mtd->depth = depth; 193c9ad075bSchristos TAILQ_FOREACH(mti, mtl, entry) { 194c9ad075bSchristos mtd->line_list = xreallocarray(mtd->line_list, 195c9ad075bSchristos mtd->line_size + 1, sizeof *mtd->line_list); 196c9ad075bSchristos 197c9ad075bSchristos line = &mtd->line_list[mtd->line_size++]; 198c9ad075bSchristos line->item = mti; 199c9ad075bSchristos line->depth = depth; 200c9ad075bSchristos line->last = (mti == TAILQ_LAST(mtl, mode_tree_list)); 201c9ad075bSchristos 202c9ad075bSchristos mti->line = (mtd->line_size - 1); 203c9ad075bSchristos if (!TAILQ_EMPTY(&mti->children)) 204c9ad075bSchristos flat = 0; 205c9ad075bSchristos if (mti->expanded) 206c9ad075bSchristos mode_tree_build_lines(mtd, &mti->children, depth + 1); 207e271dbb8Schristos 208e271dbb8Schristos if (mtd->keycb != NULL) { 209e271dbb8Schristos mti->key = mtd->keycb(mtd->modedata, mti->itemdata, 210e271dbb8Schristos mti->line); 211e271dbb8Schristos if (mti->key == KEYC_UNKNOWN) 212e271dbb8Schristos mti->key = KEYC_NONE; 213e271dbb8Schristos } else if (mti->line < 10) 214e271dbb8Schristos mti->key = '0' + mti->line; 215e271dbb8Schristos else if (mti->line < 36) 216e271dbb8Schristos mti->key = KEYC_META|('a' + mti->line - 10); 217e271dbb8Schristos else 218e271dbb8Schristos mti->key = KEYC_NONE; 219e271dbb8Schristos if (mti->key != KEYC_NONE) { 220e271dbb8Schristos mti->keystr = xstrdup(key_string_lookup_key(mti->key, 221e271dbb8Schristos 0)); 222e271dbb8Schristos mti->keylen = strlen(mti->keystr); 223e271dbb8Schristos } else { 224e271dbb8Schristos mti->keystr = NULL; 225e271dbb8Schristos mti->keylen = 0; 226e271dbb8Schristos } 227c9ad075bSchristos } 228c9ad075bSchristos TAILQ_FOREACH(mti, mtl, entry) { 229c9ad075bSchristos for (i = 0; i < mtd->line_size; i++) { 230c9ad075bSchristos line = &mtd->line_list[i]; 231c9ad075bSchristos if (line->item == mti) 232c9ad075bSchristos line->flat = flat; 233c9ad075bSchristos } 234c9ad075bSchristos } 235c9ad075bSchristos } 236c9ad075bSchristos 237c9ad075bSchristos static void 238c9ad075bSchristos mode_tree_clear_tagged(struct mode_tree_list *mtl) 239c9ad075bSchristos { 240c9ad075bSchristos struct mode_tree_item *mti; 241c9ad075bSchristos 242c9ad075bSchristos TAILQ_FOREACH(mti, mtl, entry) { 243c9ad075bSchristos mti->tagged = 0; 244c9ad075bSchristos mode_tree_clear_tagged(&mti->children); 245c9ad075bSchristos } 246c9ad075bSchristos } 247c9ad075bSchristos 248e271dbb8Schristos void 249c9ad075bSchristos mode_tree_up(struct mode_tree_data *mtd, int wrap) 250c9ad075bSchristos { 251c9ad075bSchristos if (mtd->current == 0) { 252c9ad075bSchristos if (wrap) { 253c9ad075bSchristos mtd->current = mtd->line_size - 1; 254c9ad075bSchristos if (mtd->line_size >= mtd->height) 255c9ad075bSchristos mtd->offset = mtd->line_size - mtd->height; 256c9ad075bSchristos } 257c9ad075bSchristos } else { 258c9ad075bSchristos mtd->current--; 259c9ad075bSchristos if (mtd->current < mtd->offset) 260c9ad075bSchristos mtd->offset--; 261c9ad075bSchristos } 262c9ad075bSchristos } 263c9ad075bSchristos 264*890b6d91Swiz int 265c9ad075bSchristos mode_tree_down(struct mode_tree_data *mtd, int wrap) 266c9ad075bSchristos { 267c9ad075bSchristos if (mtd->current == mtd->line_size - 1) { 268c9ad075bSchristos if (wrap) { 269c9ad075bSchristos mtd->current = 0; 270c9ad075bSchristos mtd->offset = 0; 271*890b6d91Swiz } else 272*890b6d91Swiz return (0); 273c9ad075bSchristos } else { 274c9ad075bSchristos mtd->current++; 275c9ad075bSchristos if (mtd->current > mtd->offset + mtd->height - 1) 276c9ad075bSchristos mtd->offset++; 277c9ad075bSchristos } 278*890b6d91Swiz return (1); 279c9ad075bSchristos } 280c9ad075bSchristos 281c9ad075bSchristos void * 282c9ad075bSchristos mode_tree_get_current(struct mode_tree_data *mtd) 283c9ad075bSchristos { 284c9ad075bSchristos return (mtd->line_list[mtd->current].item->itemdata); 285c9ad075bSchristos } 286c9ad075bSchristos 287e271dbb8Schristos const char * 288e271dbb8Schristos mode_tree_get_current_name(struct mode_tree_data *mtd) 289e271dbb8Schristos { 290e271dbb8Schristos return (mtd->line_list[mtd->current].item->name); 291e271dbb8Schristos } 292e271dbb8Schristos 293c7e17de0Schristos void 294c7e17de0Schristos mode_tree_expand_current(struct mode_tree_data *mtd) 295c7e17de0Schristos { 296c7e17de0Schristos if (!mtd->line_list[mtd->current].item->expanded) { 297c7e17de0Schristos mtd->line_list[mtd->current].item->expanded = 1; 298c7e17de0Schristos mode_tree_build(mtd); 299c7e17de0Schristos } 300c7e17de0Schristos } 301c7e17de0Schristos 302c7e17de0Schristos void 303e271dbb8Schristos mode_tree_collapse_current(struct mode_tree_data *mtd) 304e271dbb8Schristos { 305e271dbb8Schristos if (mtd->line_list[mtd->current].item->expanded) { 306e271dbb8Schristos mtd->line_list[mtd->current].item->expanded = 0; 307e271dbb8Schristos mode_tree_build(mtd); 308e271dbb8Schristos } 309e271dbb8Schristos } 310e271dbb8Schristos 311e271dbb8Schristos static int 312e271dbb8Schristos mode_tree_get_tag(struct mode_tree_data *mtd, uint64_t tag, u_int *found) 313c7e17de0Schristos { 314c7e17de0Schristos u_int i; 315c7e17de0Schristos 316c7e17de0Schristos for (i = 0; i < mtd->line_size; i++) { 317c7e17de0Schristos if (mtd->line_list[i].item->tag == tag) 318c7e17de0Schristos break; 319c7e17de0Schristos } 320c7e17de0Schristos if (i != mtd->line_size) { 321e271dbb8Schristos *found = i; 322e271dbb8Schristos return (1); 323e271dbb8Schristos } 324e271dbb8Schristos return (0); 325e271dbb8Schristos } 326e271dbb8Schristos 327e271dbb8Schristos void 328e271dbb8Schristos mode_tree_expand(struct mode_tree_data *mtd, uint64_t tag) 329e271dbb8Schristos { 330e271dbb8Schristos u_int found; 331e271dbb8Schristos 332e271dbb8Schristos if (!mode_tree_get_tag(mtd, tag, &found)) 333e271dbb8Schristos return; 334e271dbb8Schristos if (!mtd->line_list[found].item->expanded) { 335e271dbb8Schristos mtd->line_list[found].item->expanded = 1; 336e271dbb8Schristos mode_tree_build(mtd); 337e271dbb8Schristos } 338e271dbb8Schristos } 339e271dbb8Schristos 340e271dbb8Schristos int 341e271dbb8Schristos mode_tree_set_current(struct mode_tree_data *mtd, uint64_t tag) 342e271dbb8Schristos { 343e271dbb8Schristos u_int found; 344e271dbb8Schristos 345e271dbb8Schristos if (mode_tree_get_tag(mtd, tag, &found)) { 346e271dbb8Schristos mtd->current = found; 347c7e17de0Schristos if (mtd->current > mtd->height - 1) 348c7e17de0Schristos mtd->offset = mtd->current - mtd->height + 1; 349c7e17de0Schristos else 350c7e17de0Schristos mtd->offset = 0; 351e271dbb8Schristos return (1); 352e271dbb8Schristos } 353c7e17de0Schristos mtd->current = 0; 354c7e17de0Schristos mtd->offset = 0; 355e271dbb8Schristos return (0); 356c7e17de0Schristos } 357c7e17de0Schristos 358c9ad075bSchristos u_int 359c9ad075bSchristos mode_tree_count_tagged(struct mode_tree_data *mtd) 360c9ad075bSchristos { 361c9ad075bSchristos struct mode_tree_item *mti; 362c9ad075bSchristos u_int i, tagged; 363c9ad075bSchristos 364c9ad075bSchristos tagged = 0; 365c9ad075bSchristos for (i = 0; i < mtd->line_size; i++) { 366c9ad075bSchristos mti = mtd->line_list[i].item; 367c9ad075bSchristos if (mti->tagged) 368c9ad075bSchristos tagged++; 369c9ad075bSchristos } 370c9ad075bSchristos return (tagged); 371c9ad075bSchristos } 372c9ad075bSchristos 373c9ad075bSchristos void 374c7e17de0Schristos mode_tree_each_tagged(struct mode_tree_data *mtd, mode_tree_each_cb cb, 375c7e17de0Schristos struct client *c, key_code key, int current) 376c9ad075bSchristos { 377c9ad075bSchristos struct mode_tree_item *mti; 378c9ad075bSchristos u_int i; 379c9ad075bSchristos int fired; 380c9ad075bSchristos 381c9ad075bSchristos fired = 0; 382c9ad075bSchristos for (i = 0; i < mtd->line_size; i++) { 383c9ad075bSchristos mti = mtd->line_list[i].item; 384c9ad075bSchristos if (mti->tagged) { 385c9ad075bSchristos fired = 1; 386c7e17de0Schristos cb(mtd->modedata, mti->itemdata, c, key); 387c9ad075bSchristos } 388c9ad075bSchristos } 389c9ad075bSchristos if (!fired && current) { 390c9ad075bSchristos mti = mtd->line_list[mtd->current].item; 391c7e17de0Schristos cb(mtd->modedata, mti->itemdata, c, key); 392c9ad075bSchristos } 393c9ad075bSchristos } 394c9ad075bSchristos 395c9ad075bSchristos struct mode_tree_data * 396c9ad075bSchristos mode_tree_start(struct window_pane *wp, struct args *args, 397c7e17de0Schristos mode_tree_build_cb buildcb, mode_tree_draw_cb drawcb, 398e271dbb8Schristos mode_tree_search_cb searchcb, mode_tree_menu_cb menucb, 399e271dbb8Schristos mode_tree_height_cb heightcb, mode_tree_key_cb keycb, void *modedata, 40030744affSchristos const struct menu_item *menu, const char **sort_list, u_int sort_size, 40130744affSchristos struct screen **s) 402c9ad075bSchristos { 403c9ad075bSchristos struct mode_tree_data *mtd; 404c9ad075bSchristos const char *sort; 405c9ad075bSchristos u_int i; 406c9ad075bSchristos 407c9ad075bSchristos mtd = xcalloc(1, sizeof *mtd); 408c9ad075bSchristos mtd->references = 1; 409c9ad075bSchristos 410c9ad075bSchristos mtd->wp = wp; 411c9ad075bSchristos mtd->modedata = modedata; 41230744affSchristos mtd->menu = menu; 413c9ad075bSchristos 414c9ad075bSchristos mtd->sort_list = sort_list; 415c9ad075bSchristos mtd->sort_size = sort_size; 416c9ad075bSchristos 417c9ad075bSchristos mtd->preview = !args_has(args, 'N'); 418c9ad075bSchristos 419c9ad075bSchristos sort = args_get(args, 'O'); 420c9ad075bSchristos if (sort != NULL) { 421c9ad075bSchristos for (i = 0; i < sort_size; i++) { 422c9ad075bSchristos if (strcasecmp(sort, sort_list[i]) == 0) 42368e6ba84Schristos mtd->sort_crit.field = i; 424c9ad075bSchristos } 425c9ad075bSchristos } 42668e6ba84Schristos mtd->sort_crit.reversed = args_has(args, 'r'); 427c9ad075bSchristos 428c9ad075bSchristos if (args_has(args, 'f')) 429c9ad075bSchristos mtd->filter = xstrdup(args_get(args, 'f')); 430c9ad075bSchristos else 431c9ad075bSchristos mtd->filter = NULL; 432c9ad075bSchristos 433c9ad075bSchristos mtd->buildcb = buildcb; 434c9ad075bSchristos mtd->drawcb = drawcb; 435c9ad075bSchristos mtd->searchcb = searchcb; 43630744affSchristos mtd->menucb = menucb; 437e271dbb8Schristos mtd->heightcb = heightcb; 438e271dbb8Schristos mtd->keycb = keycb; 439c9ad075bSchristos 440c9ad075bSchristos TAILQ_INIT(&mtd->children); 441c9ad075bSchristos 442c9ad075bSchristos *s = &mtd->screen; 443c9ad075bSchristos screen_init(*s, screen_size_x(&wp->base), screen_size_y(&wp->base), 0); 444c9ad075bSchristos (*s)->mode &= ~MODE_CURSOR; 445c9ad075bSchristos 446c9ad075bSchristos return (mtd); 447c9ad075bSchristos } 448c9ad075bSchristos 449c9ad075bSchristos void 450c7e17de0Schristos mode_tree_zoom(struct mode_tree_data *mtd, struct args *args) 451c7e17de0Schristos { 452c7e17de0Schristos struct window_pane *wp = mtd->wp; 453c7e17de0Schristos 454c7e17de0Schristos if (args_has(args, 'Z')) { 455c7e17de0Schristos mtd->zoomed = (wp->window->flags & WINDOW_ZOOMED); 456c7e17de0Schristos if (!mtd->zoomed && window_zoom(wp) == 0) 457c7e17de0Schristos server_redraw_window(wp->window); 458c7e17de0Schristos } else 459c7e17de0Schristos mtd->zoomed = -1; 460c7e17de0Schristos } 461c7e17de0Schristos 462e271dbb8Schristos static void 463e271dbb8Schristos mode_tree_set_height(struct mode_tree_data *mtd) 464e271dbb8Schristos { 465e271dbb8Schristos struct screen *s = &mtd->screen; 466e271dbb8Schristos u_int height; 467e271dbb8Schristos 468e271dbb8Schristos if (mtd->heightcb != NULL) { 469e271dbb8Schristos height = mtd->heightcb(mtd, screen_size_y(s)); 470e271dbb8Schristos if (height < screen_size_y(s)) 471e271dbb8Schristos mtd->height = screen_size_y(s) - height; 472e271dbb8Schristos } else { 473e271dbb8Schristos mtd->height = (screen_size_y(s) / 3) * 2; 474e271dbb8Schristos if (mtd->height > mtd->line_size) 475e271dbb8Schristos mtd->height = screen_size_y(s) / 2; 476e271dbb8Schristos } 477e271dbb8Schristos if (mtd->height < 10) 478e271dbb8Schristos mtd->height = screen_size_y(s); 479e271dbb8Schristos if (screen_size_y(s) - mtd->height < 2) 480e271dbb8Schristos mtd->height = screen_size_y(s); 481e271dbb8Schristos } 482e271dbb8Schristos 483c7e17de0Schristos void 484c9ad075bSchristos mode_tree_build(struct mode_tree_data *mtd) 485c9ad075bSchristos { 486c9ad075bSchristos struct screen *s = &mtd->screen; 487c9ad075bSchristos uint64_t tag; 488c9ad075bSchristos 489c9ad075bSchristos if (mtd->line_list != NULL) 490c9ad075bSchristos tag = mtd->line_list[mtd->current].item->tag; 491c9ad075bSchristos else 49230744affSchristos tag = UINT64_MAX; 493c9ad075bSchristos 494c9ad075bSchristos TAILQ_CONCAT(&mtd->saved, &mtd->children, entry); 495c9ad075bSchristos TAILQ_INIT(&mtd->children); 496c9ad075bSchristos 49768e6ba84Schristos mtd->buildcb(mtd->modedata, &mtd->sort_crit, &tag, mtd->filter); 498c7e17de0Schristos mtd->no_matches = TAILQ_EMPTY(&mtd->children); 499c7e17de0Schristos if (mtd->no_matches) 50068e6ba84Schristos mtd->buildcb(mtd->modedata, &mtd->sort_crit, &tag, NULL); 501c9ad075bSchristos 502c9ad075bSchristos mode_tree_free_items(&mtd->saved); 503c9ad075bSchristos TAILQ_INIT(&mtd->saved); 504c9ad075bSchristos 505c9ad075bSchristos mode_tree_clear_lines(mtd); 506c9ad075bSchristos mode_tree_build_lines(mtd, &mtd->children, 0); 507c9ad075bSchristos 508f844e94eSwiz if (mtd->line_list != NULL && tag == UINT64_MAX) 50930744affSchristos tag = mtd->line_list[mtd->current].item->tag; 510c9ad075bSchristos mode_tree_set_current(mtd, tag); 511c9ad075bSchristos 512c9ad075bSchristos mtd->width = screen_size_x(s); 513e271dbb8Schristos if (mtd->preview) 514e271dbb8Schristos mode_tree_set_height(mtd); 515e271dbb8Schristos else 516c9ad075bSchristos mtd->height = screen_size_y(s); 517c9ad075bSchristos mode_tree_check_selected(mtd); 518c9ad075bSchristos } 519c9ad075bSchristos 520c9ad075bSchristos static void 521c9ad075bSchristos mode_tree_remove_ref(struct mode_tree_data *mtd) 522c9ad075bSchristos { 523c9ad075bSchristos if (--mtd->references == 0) 524c9ad075bSchristos free(mtd); 525c9ad075bSchristos } 526c9ad075bSchristos 527c9ad075bSchristos void 528c9ad075bSchristos mode_tree_free(struct mode_tree_data *mtd) 529c9ad075bSchristos { 530c7e17de0Schristos struct window_pane *wp = mtd->wp; 531c7e17de0Schristos 532c7e17de0Schristos if (mtd->zoomed == 0) 533c7e17de0Schristos server_unzoom_window(wp->window); 534c7e17de0Schristos 535c9ad075bSchristos mode_tree_free_items(&mtd->children); 536c9ad075bSchristos mode_tree_clear_lines(mtd); 537c9ad075bSchristos screen_free(&mtd->screen); 538c9ad075bSchristos 539c9ad075bSchristos free(mtd->search); 540c9ad075bSchristos free(mtd->filter); 541c9ad075bSchristos 542c9ad075bSchristos mtd->dead = 1; 543c9ad075bSchristos mode_tree_remove_ref(mtd); 544c9ad075bSchristos } 545c9ad075bSchristos 546c9ad075bSchristos void 547c9ad075bSchristos mode_tree_resize(struct mode_tree_data *mtd, u_int sx, u_int sy) 548c9ad075bSchristos { 549c9ad075bSchristos struct screen *s = &mtd->screen; 550c9ad075bSchristos 551c9ad075bSchristos screen_resize(s, sx, sy, 0); 552c9ad075bSchristos 553c9ad075bSchristos mode_tree_build(mtd); 554c9ad075bSchristos mode_tree_draw(mtd); 555c9ad075bSchristos 556c9ad075bSchristos mtd->wp->flags |= PANE_REDRAW; 557c9ad075bSchristos } 558c9ad075bSchristos 559c9ad075bSchristos struct mode_tree_item * 560c9ad075bSchristos mode_tree_add(struct mode_tree_data *mtd, struct mode_tree_item *parent, 561c9ad075bSchristos void *itemdata, uint64_t tag, const char *name, const char *text, 562c9ad075bSchristos int expanded) 563c9ad075bSchristos { 564c9ad075bSchristos struct mode_tree_item *mti, *saved; 565c9ad075bSchristos 566c9ad075bSchristos log_debug("%s: %llu, %s %s", __func__, (unsigned long long)tag, 567e271dbb8Schristos name, (text == NULL ? "" : text)); 568c9ad075bSchristos 569c9ad075bSchristos mti = xcalloc(1, sizeof *mti); 570c9ad075bSchristos mti->parent = parent; 571c9ad075bSchristos mti->itemdata = itemdata; 572c9ad075bSchristos 573c9ad075bSchristos mti->tag = tag; 574c9ad075bSchristos mti->name = xstrdup(name); 575e271dbb8Schristos if (text != NULL) 576c9ad075bSchristos mti->text = xstrdup(text); 577c9ad075bSchristos 578c9ad075bSchristos saved = mode_tree_find_item(&mtd->saved, tag); 579c9ad075bSchristos if (saved != NULL) { 58030744affSchristos if (parent == NULL || parent->expanded) 581c9ad075bSchristos mti->tagged = saved->tagged; 582c9ad075bSchristos mti->expanded = saved->expanded; 583c9ad075bSchristos } else if (expanded == -1) 584c9ad075bSchristos mti->expanded = 1; 585c9ad075bSchristos else 586c9ad075bSchristos mti->expanded = expanded; 587c9ad075bSchristos 588c9ad075bSchristos TAILQ_INIT(&mti->children); 589c9ad075bSchristos 590c9ad075bSchristos if (parent != NULL) 591c9ad075bSchristos TAILQ_INSERT_TAIL(&parent->children, mti, entry); 592c9ad075bSchristos else 593c9ad075bSchristos TAILQ_INSERT_TAIL(&mtd->children, mti, entry); 594c9ad075bSchristos 595c9ad075bSchristos return (mti); 596c9ad075bSchristos } 597c9ad075bSchristos 598c9ad075bSchristos void 599e271dbb8Schristos mode_tree_draw_as_parent(struct mode_tree_item *mti) 600e271dbb8Schristos { 601e271dbb8Schristos mti->draw_as_parent = 1; 602e271dbb8Schristos } 603e271dbb8Schristos 604e271dbb8Schristos void 605e271dbb8Schristos mode_tree_no_tag(struct mode_tree_item *mti) 606e271dbb8Schristos { 607e271dbb8Schristos mti->no_tag = 1; 608e271dbb8Schristos } 609e271dbb8Schristos 610e271dbb8Schristos void 611c9ad075bSchristos mode_tree_remove(struct mode_tree_data *mtd, struct mode_tree_item *mti) 612c9ad075bSchristos { 613c9ad075bSchristos struct mode_tree_item *parent = mti->parent; 614c9ad075bSchristos 615c9ad075bSchristos if (parent != NULL) 616c9ad075bSchristos TAILQ_REMOVE(&parent->children, mti, entry); 617c9ad075bSchristos else 618c9ad075bSchristos TAILQ_REMOVE(&mtd->children, mti, entry); 619c9ad075bSchristos mode_tree_free_item(mti); 620c9ad075bSchristos } 621c9ad075bSchristos 622c9ad075bSchristos void 623c9ad075bSchristos mode_tree_draw(struct mode_tree_data *mtd) 624c9ad075bSchristos { 625c9ad075bSchristos struct window_pane *wp = mtd->wp; 626c7e17de0Schristos struct screen *s = &mtd->screen; 627c9ad075bSchristos struct mode_tree_line *line; 628c9ad075bSchristos struct mode_tree_item *mti; 629c9ad075bSchristos struct options *oo = wp->window->options; 630c9ad075bSchristos struct screen_write_ctx ctx; 631c9ad075bSchristos struct grid_cell gc0, gc; 6320a274e86Schristos u_int w, h, i, j, sy, box_x, box_y, width; 633e271dbb8Schristos char *text, *start, *key; 634c9ad075bSchristos const char *tag, *symbol; 635c7e17de0Schristos size_t size, n; 636e271dbb8Schristos int keylen, pad; 637c9ad075bSchristos 638c9ad075bSchristos if (mtd->line_size == 0) 639c9ad075bSchristos return; 640c9ad075bSchristos 641c9ad075bSchristos memcpy(&gc0, &grid_default_cell, sizeof gc0); 642c9ad075bSchristos memcpy(&gc, &grid_default_cell, sizeof gc); 643e271dbb8Schristos style_apply(&gc, oo, "mode-style", NULL); 644c9ad075bSchristos 645c9ad075bSchristos w = mtd->width; 646c9ad075bSchristos h = mtd->height; 647c9ad075bSchristos 648e271dbb8Schristos screen_write_start(&ctx, s); 649c9ad075bSchristos screen_write_clearscreen(&ctx, 8); 650c9ad075bSchristos 651e271dbb8Schristos keylen = 0; 652e271dbb8Schristos for (i = 0; i < mtd->line_size; i++) { 653e271dbb8Schristos mti = mtd->line_list[i].item; 654e271dbb8Schristos if (mti->key == KEYC_NONE) 655e271dbb8Schristos continue; 656e271dbb8Schristos if ((int)mti->keylen + 3 > keylen) 657e271dbb8Schristos keylen = mti->keylen + 3; 658e271dbb8Schristos } 659c9ad075bSchristos 660c9ad075bSchristos for (i = 0; i < mtd->line_size; i++) { 661c9ad075bSchristos if (i < mtd->offset) 662c9ad075bSchristos continue; 663c9ad075bSchristos if (i > mtd->offset + h - 1) 664c9ad075bSchristos break; 665c9ad075bSchristos line = &mtd->line_list[i]; 666c9ad075bSchristos mti = line->item; 667c9ad075bSchristos 6680a274e86Schristos screen_write_cursormove(&ctx, 0, i - mtd->offset, 0); 669c9ad075bSchristos 670e271dbb8Schristos pad = keylen - 2 - mti->keylen; 671e271dbb8Schristos if (mti->key != KEYC_NONE) 672e271dbb8Schristos xasprintf(&key, "(%s)%*s", mti->keystr, pad, ""); 673c9ad075bSchristos else 674e271dbb8Schristos key = xstrdup(""); 675c9ad075bSchristos 676c9ad075bSchristos if (line->flat) 677c9ad075bSchristos symbol = ""; 678c9ad075bSchristos else if (TAILQ_EMPTY(&mti->children)) 679c9ad075bSchristos symbol = " "; 680c9ad075bSchristos else if (mti->expanded) 681c9ad075bSchristos symbol = "- "; 682c9ad075bSchristos else 683c9ad075bSchristos symbol = "+ "; 684c9ad075bSchristos 685c9ad075bSchristos if (line->depth == 0) 686c9ad075bSchristos start = xstrdup(symbol); 687c9ad075bSchristos else { 688c9ad075bSchristos size = (4 * line->depth) + 32; 689c9ad075bSchristos 690c9ad075bSchristos start = xcalloc(1, size); 691c9ad075bSchristos for (j = 1; j < line->depth; j++) { 692c9ad075bSchristos if (mti->parent != NULL && 693c9ad075bSchristos mtd->line_list[mti->parent->line].last) 694c9ad075bSchristos strlcat(start, " ", size); 695c9ad075bSchristos else 696c9ad075bSchristos strlcat(start, "\001x\001 ", size); 697c9ad075bSchristos } 698c9ad075bSchristos if (line->last) 699c9ad075bSchristos strlcat(start, "\001mq\001> ", size); 700c9ad075bSchristos else 701c9ad075bSchristos strlcat(start, "\001tq\001> ", size); 702c9ad075bSchristos strlcat(start, symbol, size); 703c9ad075bSchristos } 704c9ad075bSchristos 705c9ad075bSchristos if (mti->tagged) 706c9ad075bSchristos tag = "*"; 707c9ad075bSchristos else 708c9ad075bSchristos tag = ""; 709e271dbb8Schristos xasprintf(&text, "%-*s%s%s%s%s", keylen, key, start, mti->name, 710e271dbb8Schristos tag, (mti->text != NULL) ? ": " : "" ); 7110a274e86Schristos width = utf8_cstrwidth(text); 71268e6ba84Schristos if (width > w) 71368e6ba84Schristos width = w; 714c9ad075bSchristos free(start); 715c9ad075bSchristos 716c9ad075bSchristos if (mti->tagged) { 717c9ad075bSchristos gc.attr ^= GRID_ATTR_BRIGHT; 718c9ad075bSchristos gc0.attr ^= GRID_ATTR_BRIGHT; 719c9ad075bSchristos } 720c9ad075bSchristos 721c9ad075bSchristos if (i != mtd->current) { 722c9ad075bSchristos screen_write_clearendofline(&ctx, 8); 72368e6ba84Schristos screen_write_nputs(&ctx, w, &gc0, "%s", text); 724e271dbb8Schristos if (mti->text != NULL) { 725e271dbb8Schristos format_draw(&ctx, &gc0, w - width, mti->text, 72646548964Swiz NULL, 0); 727e271dbb8Schristos } 728c7e17de0Schristos } else { 729c7e17de0Schristos screen_write_clearendofline(&ctx, gc.bg); 73068e6ba84Schristos screen_write_nputs(&ctx, w, &gc, "%s", text); 731e271dbb8Schristos if (mti->text != NULL) { 732e271dbb8Schristos format_draw(&ctx, &gc, w - width, mti->text, 73346548964Swiz NULL, 0); 734e271dbb8Schristos } 735c7e17de0Schristos } 736c9ad075bSchristos free(text); 737e271dbb8Schristos free(key); 738c9ad075bSchristos 739c9ad075bSchristos if (mti->tagged) { 740c9ad075bSchristos gc.attr ^= GRID_ATTR_BRIGHT; 741c9ad075bSchristos gc0.attr ^= GRID_ATTR_BRIGHT; 742c9ad075bSchristos } 743c9ad075bSchristos } 744c9ad075bSchristos 745c9ad075bSchristos sy = screen_size_y(s); 74646548964Swiz if (!mtd->preview || sy <= 4 || h <= 4 || sy - h <= 4 || w <= 4) 74746548964Swiz goto done; 748c9ad075bSchristos 749c9ad075bSchristos line = &mtd->line_list[mtd->current]; 750c9ad075bSchristos mti = line->item; 751e271dbb8Schristos if (mti->draw_as_parent) 752e271dbb8Schristos mti = mti->parent; 753c9ad075bSchristos 7540a274e86Schristos screen_write_cursormove(&ctx, 0, h, 0); 75546548964Swiz screen_write_box(&ctx, w, sy - h, BOX_LINES_DEFAULT, NULL, NULL); 756c9ad075bSchristos 757e271dbb8Schristos if (mtd->sort_list != NULL) { 75868e6ba84Schristos xasprintf(&text, " %s (sort: %s%s)", mti->name, 75968e6ba84Schristos mtd->sort_list[mtd->sort_crit.field], 76068e6ba84Schristos mtd->sort_crit.reversed ? ", reversed" : ""); 761e271dbb8Schristos } else 762e271dbb8Schristos xasprintf(&text, " %s", mti->name); 763c9ad075bSchristos if (w - 2 >= strlen(text)) { 7640a274e86Schristos screen_write_cursormove(&ctx, 1, h, 0); 765c9ad075bSchristos screen_write_puts(&ctx, &gc0, "%s", text); 766c7e17de0Schristos 767c7e17de0Schristos if (mtd->no_matches) 768c7e17de0Schristos n = (sizeof "no matches") - 1; 769c7e17de0Schristos else 770c7e17de0Schristos n = (sizeof "active") - 1; 771c7e17de0Schristos if (mtd->filter != NULL && w - 2 >= strlen(text) + 10 + n + 2) { 772c7e17de0Schristos screen_write_puts(&ctx, &gc0, " (filter: "); 773c7e17de0Schristos if (mtd->no_matches) 774c7e17de0Schristos screen_write_puts(&ctx, &gc, "no matches"); 775c7e17de0Schristos else 776c7e17de0Schristos screen_write_puts(&ctx, &gc0, "active"); 777c7e17de0Schristos screen_write_puts(&ctx, &gc0, ") "); 778e271dbb8Schristos } else 779e271dbb8Schristos screen_write_puts(&ctx, &gc0, " "); 780c9ad075bSchristos } 781c9ad075bSchristos free(text); 782c9ad075bSchristos 783c9ad075bSchristos box_x = w - 4; 784c9ad075bSchristos box_y = sy - h - 2; 785c9ad075bSchristos 786c7e17de0Schristos if (box_x != 0 && box_y != 0) { 7870a274e86Schristos screen_write_cursormove(&ctx, 2, h + 1, 0); 788c7e17de0Schristos mtd->drawcb(mtd->modedata, mti->itemdata, &ctx, box_x, box_y); 789c9ad075bSchristos } 790c9ad075bSchristos 79146548964Swiz done: 79246548964Swiz screen_write_cursormove(&ctx, 0, mtd->current - mtd->offset, 0); 793c9ad075bSchristos screen_write_stop(&ctx); 794c9ad075bSchristos } 795c9ad075bSchristos 796c9ad075bSchristos static struct mode_tree_item * 797*890b6d91Swiz mode_tree_search_backward(struct mode_tree_data *mtd) 798*890b6d91Swiz { 799*890b6d91Swiz struct mode_tree_item *mti, *last, *prev; 800*890b6d91Swiz 801*890b6d91Swiz if (mtd->search == NULL) 802*890b6d91Swiz return (NULL); 803*890b6d91Swiz 804*890b6d91Swiz mti = last = mtd->line_list[mtd->current].item; 805*890b6d91Swiz for (;;) { 806*890b6d91Swiz if ((prev = TAILQ_PREV(mti, mode_tree_list, entry)) != NULL) { 807*890b6d91Swiz /* Point to the last child in the previous subtree. */ 808*890b6d91Swiz while (!TAILQ_EMPTY(&prev->children)) 809*890b6d91Swiz prev = TAILQ_LAST(&prev->children, mode_tree_list); 810*890b6d91Swiz mti = prev; 811*890b6d91Swiz } else { 812*890b6d91Swiz /* If prev is NULL, jump to the parent. */ 813*890b6d91Swiz mti = mti->parent; 814*890b6d91Swiz } 815*890b6d91Swiz 816*890b6d91Swiz if (mti == NULL) { 817*890b6d91Swiz /* Point to the last child in the last root subtree. */ 818*890b6d91Swiz prev = TAILQ_LAST(&mtd->children, mode_tree_list); 819*890b6d91Swiz while (!TAILQ_EMPTY(&prev->children)) 820*890b6d91Swiz prev = TAILQ_LAST(&prev->children, mode_tree_list); 821*890b6d91Swiz mti = prev; 822*890b6d91Swiz } 823*890b6d91Swiz if (mti == last) 824*890b6d91Swiz break; 825*890b6d91Swiz 826*890b6d91Swiz if (mtd->searchcb == NULL) { 827*890b6d91Swiz if (strstr(mti->name, mtd->search) != NULL) 828*890b6d91Swiz return (mti); 829*890b6d91Swiz continue; 830*890b6d91Swiz } 831*890b6d91Swiz if (mtd->searchcb(mtd->modedata, mti->itemdata, mtd->search)) 832*890b6d91Swiz return (mti); 833*890b6d91Swiz } 834*890b6d91Swiz return (NULL); 835*890b6d91Swiz } 836*890b6d91Swiz 837*890b6d91Swiz 838*890b6d91Swiz static struct mode_tree_item * 839*890b6d91Swiz mode_tree_search_forward(struct mode_tree_data *mtd) 840c9ad075bSchristos { 841c9ad075bSchristos struct mode_tree_item *mti, *last, *next; 842c9ad075bSchristos 843c9ad075bSchristos if (mtd->search == NULL) 844c9ad075bSchristos return (NULL); 845c9ad075bSchristos 846c9ad075bSchristos mti = last = mtd->line_list[mtd->current].item; 847c9ad075bSchristos for (;;) { 848c9ad075bSchristos if (!TAILQ_EMPTY(&mti->children)) 849c9ad075bSchristos mti = TAILQ_FIRST(&mti->children); 850c9ad075bSchristos else if ((next = TAILQ_NEXT(mti, entry)) != NULL) 851c9ad075bSchristos mti = next; 852c9ad075bSchristos else { 853c9ad075bSchristos for (;;) { 854c9ad075bSchristos mti = mti->parent; 855c9ad075bSchristos if (mti == NULL) 856c9ad075bSchristos break; 857c9ad075bSchristos if ((next = TAILQ_NEXT(mti, entry)) != NULL) { 858c9ad075bSchristos mti = next; 859c9ad075bSchristos break; 860c9ad075bSchristos } 861c9ad075bSchristos } 862c9ad075bSchristos } 863c9ad075bSchristos if (mti == NULL) 864c9ad075bSchristos mti = TAILQ_FIRST(&mtd->children); 865c9ad075bSchristos if (mti == last) 866c9ad075bSchristos break; 867c9ad075bSchristos 868c9ad075bSchristos if (mtd->searchcb == NULL) { 869c9ad075bSchristos if (strstr(mti->name, mtd->search) != NULL) 870c9ad075bSchristos return (mti); 871c9ad075bSchristos continue; 872c9ad075bSchristos } 873c9ad075bSchristos if (mtd->searchcb(mtd->modedata, mti->itemdata, mtd->search)) 874c9ad075bSchristos return (mti); 875c9ad075bSchristos } 876c9ad075bSchristos return (NULL); 877c9ad075bSchristos } 878c9ad075bSchristos 879c9ad075bSchristos static void 880c9ad075bSchristos mode_tree_search_set(struct mode_tree_data *mtd) 881c9ad075bSchristos { 882c9ad075bSchristos struct mode_tree_item *mti, *loop; 883c9ad075bSchristos uint64_t tag; 884c9ad075bSchristos 885*890b6d91Swiz if (mtd->search_dir == MODE_TREE_SEARCH_FORWARD) 886*890b6d91Swiz mti = mode_tree_search_forward(mtd); 887*890b6d91Swiz else 888*890b6d91Swiz mti = mode_tree_search_backward(mtd); 889c9ad075bSchristos if (mti == NULL) 890c9ad075bSchristos return; 891c9ad075bSchristos tag = mti->tag; 892c9ad075bSchristos 893c9ad075bSchristos loop = mti->parent; 894c9ad075bSchristos while (loop != NULL) { 895c9ad075bSchristos loop->expanded = 1; 896c9ad075bSchristos loop = loop->parent; 897c9ad075bSchristos } 898c9ad075bSchristos 899c9ad075bSchristos mode_tree_build(mtd); 900c9ad075bSchristos mode_tree_set_current(mtd, tag); 901c9ad075bSchristos mode_tree_draw(mtd); 902c9ad075bSchristos mtd->wp->flags |= PANE_REDRAW; 903c9ad075bSchristos } 904c9ad075bSchristos 905c9ad075bSchristos static int 906c9ad075bSchristos mode_tree_search_callback(__unused struct client *c, void *data, const char *s, 907c9ad075bSchristos __unused int done) 908c9ad075bSchristos { 909c9ad075bSchristos struct mode_tree_data *mtd = data; 910c9ad075bSchristos 911c9ad075bSchristos if (mtd->dead) 912c9ad075bSchristos return (0); 913c9ad075bSchristos 914c9ad075bSchristos free(mtd->search); 915c9ad075bSchristos if (s == NULL || *s == '\0') { 916c9ad075bSchristos mtd->search = NULL; 917c9ad075bSchristos return (0); 918c9ad075bSchristos } 919c9ad075bSchristos mtd->search = xstrdup(s); 920c9ad075bSchristos mode_tree_search_set(mtd); 921c9ad075bSchristos 922c9ad075bSchristos return (0); 923c9ad075bSchristos } 924c9ad075bSchristos 925c9ad075bSchristos static void 926c9ad075bSchristos mode_tree_search_free(void *data) 927c9ad075bSchristos { 928c9ad075bSchristos mode_tree_remove_ref(data); 929c9ad075bSchristos } 930c9ad075bSchristos 931c9ad075bSchristos static int 932c9ad075bSchristos mode_tree_filter_callback(__unused struct client *c, void *data, const char *s, 933c9ad075bSchristos __unused int done) 934c9ad075bSchristos { 935c9ad075bSchristos struct mode_tree_data *mtd = data; 936c9ad075bSchristos 937c9ad075bSchristos if (mtd->dead) 938c9ad075bSchristos return (0); 939c9ad075bSchristos 940c9ad075bSchristos if (mtd->filter != NULL) 941c9ad075bSchristos free(mtd->filter); 942c9ad075bSchristos if (s == NULL || *s == '\0') 943c9ad075bSchristos mtd->filter = NULL; 944c9ad075bSchristos else 945c9ad075bSchristos mtd->filter = xstrdup(s); 946c9ad075bSchristos 947c9ad075bSchristos mode_tree_build(mtd); 948c9ad075bSchristos mode_tree_draw(mtd); 949c9ad075bSchristos mtd->wp->flags |= PANE_REDRAW; 950c9ad075bSchristos 951c9ad075bSchristos return (0); 952c9ad075bSchristos } 953c9ad075bSchristos 954c9ad075bSchristos static void 955c9ad075bSchristos mode_tree_filter_free(void *data) 956c9ad075bSchristos { 957c9ad075bSchristos mode_tree_remove_ref(data); 958c9ad075bSchristos } 959c9ad075bSchristos 96030744affSchristos static void 96130744affSchristos mode_tree_menu_callback(__unused struct menu *menu, __unused u_int idx, 96230744affSchristos key_code key, void *data) 96330744affSchristos { 96430744affSchristos struct mode_tree_menu *mtm = data; 96530744affSchristos struct mode_tree_data *mtd = mtm->data; 96630744affSchristos 96730744affSchristos if (mtd->dead || key == KEYC_NONE) 96830744affSchristos goto out; 96930744affSchristos 97030744affSchristos if (mtm->line >= mtd->line_size) 97130744affSchristos goto out; 97230744affSchristos mtd->current = mtm->line; 97330744affSchristos mtd->menucb(mtd->modedata, mtm->c, key); 97430744affSchristos 97530744affSchristos out: 97630744affSchristos mode_tree_remove_ref(mtd); 97730744affSchristos free(mtm); 97830744affSchristos } 97930744affSchristos 98030744affSchristos static void 98130744affSchristos mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x, 98230744affSchristos u_int y, int outside) 98330744affSchristos { 98430744affSchristos struct mode_tree_item *mti; 98530744affSchristos struct menu *menu; 98630744affSchristos const struct menu_item *items; 98730744affSchristos struct mode_tree_menu *mtm; 98830744affSchristos char *title; 98930744affSchristos u_int line; 99030744affSchristos 99130744affSchristos if (mtd->offset + y > mtd->line_size - 1) 99230744affSchristos line = mtd->current; 99330744affSchristos else 99430744affSchristos line = mtd->offset + y; 99530744affSchristos mti = mtd->line_list[line].item; 99630744affSchristos 99730744affSchristos if (!outside) { 99830744affSchristos items = mtd->menu; 99930744affSchristos xasprintf(&title, "#[align=centre]%s", mti->name); 100030744affSchristos } else { 100130744affSchristos items = mode_tree_menu_items; 100230744affSchristos title = xstrdup(""); 100330744affSchristos } 100430744affSchristos menu = menu_create(title); 100546548964Swiz menu_add_items(menu, items, NULL, c, NULL); 100630744affSchristos free(title); 100730744affSchristos 100830744affSchristos mtm = xmalloc(sizeof *mtm); 100930744affSchristos mtm->data = mtd; 101030744affSchristos mtm->c = c; 101130744affSchristos mtm->line = line; 101230744affSchristos mtd->references++; 101330744affSchristos 1014e271dbb8Schristos if (x >= (menu->width + 4) / 2) 1015e271dbb8Schristos x -= (menu->width + 4) / 2; 1016e271dbb8Schristos else 1017e271dbb8Schristos x = 0; 1018f844e94eSwiz if (menu_display(menu, 0, 0, NULL, x, y, c, BOX_LINES_DEFAULT, NULL, 1019f844e94eSwiz NULL, NULL, NULL, mode_tree_menu_callback, mtm) != 0) 102030744affSchristos menu_free(menu); 102130744affSchristos } 102230744affSchristos 1023c9ad075bSchristos int 1024c9ad075bSchristos mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, 1025c7e17de0Schristos struct mouse_event *m, u_int *xp, u_int *yp) 1026c9ad075bSchristos { 1027c9ad075bSchristos struct mode_tree_line *line; 1028e271dbb8Schristos struct mode_tree_item *current, *parent, *mti; 1029c9ad075bSchristos u_int i, x, y; 1030c9ad075bSchristos int choice; 1031c9ad075bSchristos 103230744affSchristos if (KEYC_IS_MOUSE(*key) && m != NULL) { 1033c9ad075bSchristos if (cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) { 1034c9ad075bSchristos *key = KEYC_NONE; 1035c9ad075bSchristos return (0); 1036c9ad075bSchristos } 1037c7e17de0Schristos if (xp != NULL) 1038c7e17de0Schristos *xp = x; 1039c7e17de0Schristos if (yp != NULL) 1040c7e17de0Schristos *yp = y; 1041c9ad075bSchristos if (x > mtd->width || y > mtd->height) { 104230744affSchristos if (*key == KEYC_MOUSEDOWN3_PANE) 104330744affSchristos mode_tree_display_menu(mtd, c, x, y, 1); 1044c7e17de0Schristos if (!mtd->preview) 1045c9ad075bSchristos *key = KEYC_NONE; 1046c9ad075bSchristos return (0); 1047c9ad075bSchristos } 1048c9ad075bSchristos if (mtd->offset + y < mtd->line_size) { 1049c7e17de0Schristos if (*key == KEYC_MOUSEDOWN1_PANE || 105030744affSchristos *key == KEYC_MOUSEDOWN3_PANE || 1051c7e17de0Schristos *key == KEYC_DOUBLECLICK1_PANE) 1052c9ad075bSchristos mtd->current = mtd->offset + y; 1053c7e17de0Schristos if (*key == KEYC_DOUBLECLICK1_PANE) 1054c9ad075bSchristos *key = '\r'; 105530744affSchristos else { 105630744affSchristos if (*key == KEYC_MOUSEDOWN3_PANE) 105730744affSchristos mode_tree_display_menu(mtd, c, x, y, 0); 1058c7e17de0Schristos *key = KEYC_NONE; 105930744affSchristos } 106030744affSchristos } else { 106130744affSchristos if (*key == KEYC_MOUSEDOWN3_PANE) 106230744affSchristos mode_tree_display_menu(mtd, c, x, y, 0); 1063c7e17de0Schristos *key = KEYC_NONE; 106430744affSchristos } 1065c9ad075bSchristos return (0); 1066c9ad075bSchristos } 1067c9ad075bSchristos 1068c9ad075bSchristos line = &mtd->line_list[mtd->current]; 1069c9ad075bSchristos current = line->item; 1070c9ad075bSchristos 1071c9ad075bSchristos choice = -1; 1072e271dbb8Schristos for (i = 0; i < mtd->line_size; i++) { 1073e271dbb8Schristos if (*key == mtd->line_list[i].item->key) { 1074e271dbb8Schristos choice = i; 1075e271dbb8Schristos break; 1076e271dbb8Schristos } 1077c9ad075bSchristos } 1078c9ad075bSchristos if (choice != -1) { 1079c9ad075bSchristos if ((u_int)choice > mtd->line_size - 1) { 1080c9ad075bSchristos *key = KEYC_NONE; 1081c9ad075bSchristos return (0); 1082c9ad075bSchristos } 1083c9ad075bSchristos mtd->current = choice; 1084c9ad075bSchristos *key = '\r'; 1085c9ad075bSchristos return (0); 1086c9ad075bSchristos } 1087c9ad075bSchristos 1088c9ad075bSchristos switch (*key) { 1089c9ad075bSchristos case 'q': 1090c9ad075bSchristos case '\033': /* Escape */ 1091*890b6d91Swiz case 'g'|KEYC_CTRL: 1092c9ad075bSchristos return (1); 1093c9ad075bSchristos case KEYC_UP: 1094c9ad075bSchristos case 'k': 1095c9ad075bSchristos case KEYC_WHEELUP_PANE: 1096*890b6d91Swiz case 'p'|KEYC_CTRL: 1097c9ad075bSchristos mode_tree_up(mtd, 1); 1098c9ad075bSchristos break; 1099c9ad075bSchristos case KEYC_DOWN: 1100c9ad075bSchristos case 'j': 1101c9ad075bSchristos case KEYC_WHEELDOWN_PANE: 1102*890b6d91Swiz case 'n'|KEYC_CTRL: 1103c9ad075bSchristos mode_tree_down(mtd, 1); 1104c9ad075bSchristos break; 1105c9ad075bSchristos case KEYC_PPAGE: 1106*890b6d91Swiz case 'b'|KEYC_CTRL: 1107c9ad075bSchristos for (i = 0; i < mtd->height; i++) { 1108c9ad075bSchristos if (mtd->current == 0) 1109c9ad075bSchristos break; 1110c9ad075bSchristos mode_tree_up(mtd, 1); 1111c9ad075bSchristos } 1112c9ad075bSchristos break; 1113c9ad075bSchristos case KEYC_NPAGE: 1114*890b6d91Swiz case 'f'|KEYC_CTRL: 1115c9ad075bSchristos for (i = 0; i < mtd->height; i++) { 1116c9ad075bSchristos if (mtd->current == mtd->line_size - 1) 1117c9ad075bSchristos break; 1118c9ad075bSchristos mode_tree_down(mtd, 1); 1119c9ad075bSchristos } 1120c9ad075bSchristos break; 112146548964Swiz case 'g': 1122c9ad075bSchristos case KEYC_HOME: 1123c9ad075bSchristos mtd->current = 0; 1124c9ad075bSchristos mtd->offset = 0; 1125c9ad075bSchristos break; 112646548964Swiz case 'G': 1127c9ad075bSchristos case KEYC_END: 1128c9ad075bSchristos mtd->current = mtd->line_size - 1; 1129c9ad075bSchristos if (mtd->current > mtd->height - 1) 1130c9ad075bSchristos mtd->offset = mtd->current - mtd->height + 1; 1131c9ad075bSchristos else 1132c9ad075bSchristos mtd->offset = 0; 1133c9ad075bSchristos break; 1134c9ad075bSchristos case 't': 1135c9ad075bSchristos /* 1136c9ad075bSchristos * Do not allow parents and children to both be tagged: untag 1137c9ad075bSchristos * all parents and children of current. 1138c9ad075bSchristos */ 1139e271dbb8Schristos if (current->no_tag) 1140e271dbb8Schristos break; 1141c9ad075bSchristos if (!current->tagged) { 1142c9ad075bSchristos parent = current->parent; 1143c9ad075bSchristos while (parent != NULL) { 1144c9ad075bSchristos parent->tagged = 0; 1145c9ad075bSchristos parent = parent->parent; 1146c9ad075bSchristos } 1147c9ad075bSchristos mode_tree_clear_tagged(¤t->children); 1148c9ad075bSchristos current->tagged = 1; 1149c9ad075bSchristos } else 1150c9ad075bSchristos current->tagged = 0; 115130744affSchristos if (m != NULL) 1152c9ad075bSchristos mode_tree_down(mtd, 0); 1153c9ad075bSchristos break; 1154c9ad075bSchristos case 'T': 1155c9ad075bSchristos for (i = 0; i < mtd->line_size; i++) 1156c9ad075bSchristos mtd->line_list[i].item->tagged = 0; 1157c9ad075bSchristos break; 1158*890b6d91Swiz case 't'|KEYC_CTRL: 1159c9ad075bSchristos for (i = 0; i < mtd->line_size; i++) { 1160e271dbb8Schristos if ((mtd->line_list[i].item->parent == NULL && 1161e271dbb8Schristos !mtd->line_list[i].item->no_tag) || 1162e271dbb8Schristos (mtd->line_list[i].item->parent != NULL && 1163e271dbb8Schristos mtd->line_list[i].item->parent->no_tag)) 1164c9ad075bSchristos mtd->line_list[i].item->tagged = 1; 1165c9ad075bSchristos else 1166c9ad075bSchristos mtd->line_list[i].item->tagged = 0; 1167c9ad075bSchristos } 1168c9ad075bSchristos break; 1169c9ad075bSchristos case 'O': 117068e6ba84Schristos mtd->sort_crit.field++; 1171e271dbb8Schristos if (mtd->sort_crit.field >= mtd->sort_size) 117268e6ba84Schristos mtd->sort_crit.field = 0; 117368e6ba84Schristos mode_tree_build(mtd); 117468e6ba84Schristos break; 117568e6ba84Schristos case 'r': 117668e6ba84Schristos mtd->sort_crit.reversed = !mtd->sort_crit.reversed; 1177c9ad075bSchristos mode_tree_build(mtd); 1178c9ad075bSchristos break; 1179c9ad075bSchristos case KEYC_LEFT: 1180c7e17de0Schristos case 'h': 1181c9ad075bSchristos case '-': 1182c9ad075bSchristos if (line->flat || !current->expanded) 1183c9ad075bSchristos current = current->parent; 1184c9ad075bSchristos if (current == NULL) 1185c9ad075bSchristos mode_tree_up(mtd, 0); 1186c9ad075bSchristos else { 1187c9ad075bSchristos current->expanded = 0; 1188c9ad075bSchristos mtd->current = current->line; 1189c9ad075bSchristos mode_tree_build(mtd); 1190c9ad075bSchristos } 1191c9ad075bSchristos break; 1192c9ad075bSchristos case KEYC_RIGHT: 1193c7e17de0Schristos case 'l': 1194c9ad075bSchristos case '+': 1195c9ad075bSchristos if (line->flat || current->expanded) 1196c9ad075bSchristos mode_tree_down(mtd, 0); 1197c9ad075bSchristos else if (!line->flat) { 1198c9ad075bSchristos current->expanded = 1; 1199c9ad075bSchristos mode_tree_build(mtd); 1200c9ad075bSchristos } 1201c9ad075bSchristos break; 1202e271dbb8Schristos case '-'|KEYC_META: 1203e271dbb8Schristos TAILQ_FOREACH(mti, &mtd->children, entry) 1204e271dbb8Schristos mti->expanded = 0; 1205e271dbb8Schristos mode_tree_build(mtd); 1206e271dbb8Schristos break; 1207e271dbb8Schristos case '+'|KEYC_META: 1208e271dbb8Schristos TAILQ_FOREACH(mti, &mtd->children, entry) 1209e271dbb8Schristos mti->expanded = 1; 1210e271dbb8Schristos mode_tree_build(mtd); 1211e271dbb8Schristos break; 121230744affSchristos case '?': 121330744affSchristos case '/': 1214*890b6d91Swiz case 's'|KEYC_CTRL: 1215c9ad075bSchristos mtd->references++; 1216e271dbb8Schristos status_prompt_set(c, NULL, "(search) ", "", 1217c9ad075bSchristos mode_tree_search_callback, mode_tree_search_free, mtd, 121846548964Swiz PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH); 1219c9ad075bSchristos break; 1220c9ad075bSchristos case 'n': 1221*890b6d91Swiz mtd->search_dir = MODE_TREE_SEARCH_FORWARD; 1222*890b6d91Swiz mode_tree_search_set(mtd); 1223*890b6d91Swiz break; 1224*890b6d91Swiz case 'N': 1225*890b6d91Swiz mtd->search_dir = MODE_TREE_SEARCH_BACKWARD; 1226c9ad075bSchristos mode_tree_search_set(mtd); 1227c9ad075bSchristos break; 1228c9ad075bSchristos case 'f': 1229c9ad075bSchristos mtd->references++; 1230e271dbb8Schristos status_prompt_set(c, NULL, "(filter) ", mtd->filter, 1231c9ad075bSchristos mode_tree_filter_callback, mode_tree_filter_free, mtd, 123246548964Swiz PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH); 1233c9ad075bSchristos break; 1234c9ad075bSchristos case 'v': 1235c9ad075bSchristos mtd->preview = !mtd->preview; 1236c9ad075bSchristos mode_tree_build(mtd); 1237c9ad075bSchristos if (mtd->preview) 1238c9ad075bSchristos mode_tree_check_selected(mtd); 1239c9ad075bSchristos break; 1240c9ad075bSchristos } 1241c9ad075bSchristos return (0); 1242c9ad075bSchristos } 1243c9ad075bSchristos 1244c9ad075bSchristos void 1245c9ad075bSchristos mode_tree_run_command(struct client *c, struct cmd_find_state *fs, 1246c9ad075bSchristos const char *template, const char *name) 1247c9ad075bSchristos { 1248e271dbb8Schristos struct cmdq_state *state; 1249e271dbb8Schristos char *command, *error; 1250e271dbb8Schristos enum cmd_parse_status status; 1251c9ad075bSchristos 1252c9ad075bSchristos command = cmd_template_replace(template, name, 1); 1253e271dbb8Schristos if (command != NULL && *command != '\0') { 1254e271dbb8Schristos state = cmdq_new_state(fs, NULL, 0); 1255e271dbb8Schristos status = cmd_parse_and_append(command, NULL, c, state, &error); 1256e271dbb8Schristos if (status == CMD_PARSE_ERROR) { 125730744affSchristos if (c != NULL) { 1258e271dbb8Schristos *error = toupper((u_char)*error); 1259e271dbb8Schristos status_message_set(c, -1, 1, 0, "%s", error); 1260c9ad075bSchristos } 1261e271dbb8Schristos free(error); 1262c9ad075bSchristos } 1263e271dbb8Schristos cmdq_free_state(state); 1264e271dbb8Schristos } 1265c9ad075bSchristos free(command); 1266c9ad075bSchristos } 1267