[BACK]Return to mode-tree.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / tmux

Annotation of src/usr.bin/tmux/mode-tree.c, Revision 1.36

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