summaryrefslogtreecommitdiffstats
path: root/mode-tree.c
diff options
context:
space:
mode:
authornicm <nicm>2017-05-30 21:44:59 +0000
committernicm <nicm>2017-05-30 21:44:59 +0000
commitaad4e4ddb194cba9c01b0ddd696fb7b214e1a7eb (patch)
tree8a8a273bb54a7b4010b48b64a59aa5d3c3c96b55 /mode-tree.c
parentbd39fcbeea1930a2b36e98a622d864e2e27e5d14 (diff)
Rewrite of choose mode, both to simplify and tidy the code and to add
some modern features. Now the common code is in mode-tree.c, which provides an API used by the three modes now separated into window-{buffer,client,tree}.c. Buffer mode shows buffers, client mode clients and tree mode a tree of sessions, windows and panes. Each mode has a common set of key bindings plus a few that are specific to the mode. Other changes are: - each mode has a preview pane: for buffers this is the buffer content (very useful), for others it is a preview of the pane; - items may be sorted in different ways ('O' key); - multiple items may be tagged and an operation applied to all of them (for example, to delete multiple buffers at once); - in tree mode a command may be run on the selected item (session, window, pane) or on tagged items (key ':'); - displayed items may be filtered in tree mode by using a format (this is used to implement find-window) (key 'f'); - the custom format (-F) for the display is no longer available; - shortcut keys change from 0-9, a-z, A-Z which was always a bit weird with keys used for other uses to 0-9, M-a to M-z. Now that the code is simpler, other improvements will come later. Primary key bindings for each mode are documented under the commands in the man page (choose-buffer, choose-client, choose-tree). Parts written by Thomas Adam.
Diffstat (limited to 'mode-tree.c')
-rw-r--r--mode-tree.c705
1 files changed, 705 insertions, 0 deletions
diff --git a/mode-tree.c b/mode-tree.c
new file mode 100644
index 00000000..f3155fbf
--- /dev/null
+++ b/mode-tree.c
@@ -0,0 +1,705 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmux.h"
+
+struct mode_tree_item;
+TAILQ_HEAD(mode_tree_list, mode_tree_item);
+
+struct mode_tree_data {
+ struct window_pane *wp;
+ void *modedata;
+
+ const char **sort_list;
+ u_int sort_size;
+ u_int sort_type;
+
+ void (*buildcb)(void *, u_int, uint64_t *);
+ struct screen *(*drawcb)(void *, void *, u_int, u_int);
+
+ struct mode_tree_list children;
+ struct mode_tree_list saved;
+
+ struct mode_tree_line *line_list;
+ u_int line_size;
+
+ u_int depth;
+
+ u_int width;
+ u_int height;
+
+ u_int offset;
+ u_int current;
+
+ struct screen screen;
+};
+
+struct mode_tree_item {
+ struct mode_tree_item *parent;
+ void *itemdata;
+ u_int line;
+
+ uint64_t tag;
+ const char *name;
+ const char *text;
+
+ int expanded;
+ int tagged;
+
+ struct mode_tree_list children;
+ TAILQ_ENTRY(mode_tree_item) entry;
+};
+
+struct mode_tree_line {
+ struct mode_tree_item *item;
+ u_int depth;
+ int last;
+ int flat;
+};
+
+static void mode_tree_free_items(struct mode_tree_list *);
+
+static struct mode_tree_item *
+mode_tree_find_item(struct mode_tree_list *mtl, uint64_t tag)
+{
+ struct mode_tree_item *mti, *child;
+
+ TAILQ_FOREACH(mti, mtl, entry) {
+ if (mti->tag == tag)
+ return (mti);
+ child = mode_tree_find_item(&mti->children, tag);
+ if (child != NULL)
+ return (child);
+ }
+ return (NULL);
+}
+
+static void
+mode_tree_free_item(struct mode_tree_item *mti)
+{
+ mode_tree_free_items(&mti->children);
+
+ free((void *)mti->name);
+ free((void *)mti->text);
+
+ free(mti);
+}
+
+static void
+mode_tree_free_items(struct mode_tree_list *mtl)
+{
+ struct mode_tree_item *mti, *mti1;
+
+ TAILQ_FOREACH_SAFE(mti, mtl, entry, mti1) {
+ TAILQ_REMOVE(mtl, mti, entry);
+ mode_tree_free_item(mti);
+ }
+}
+
+static void
+mode_tree_clear_lines(struct mode_tree_data *mtd)
+{
+ free(mtd->line_list);
+ mtd->line_list = NULL;
+ mtd->line_size = 0;
+}
+
+static void
+mode_tree_build_lines(struct mode_tree_data *mtd,
+ struct mode_tree_list *mtl, u_int depth)
+{
+ struct mode_tree_item *mti;
+ struct mode_tree_line *line;
+ u_int i;
+ int flat = 1;
+
+ mtd->depth = depth;
+ TAILQ_FOREACH(mti, mtl, entry) {
+ mtd->line_list = xreallocarray(mtd->line_list,
+ mtd->line_size + 1, sizeof *mtd->line_list);
+
+ line = &mtd->line_list[mtd->line_size++];
+ line->item = mti;
+ line->depth = depth;
+ line->last = (mti == TAILQ_LAST(mtl, mode_tree_list));
+
+ mti->line = (mtd->line_size - 1);
+ if (!TAILQ_EMPTY(&mti->children))
+ flat = 0;
+ if (mti->expanded)
+ mode_tree_build_lines(mtd, &mti->children, depth + 1);
+ }
+ TAILQ_FOREACH(mti, mtl, entry) {
+ for (i = 0; i < mtd->line_size; i++) {
+ line = &mtd->line_list[i];
+ if (line->item == mti)
+ line->flat = flat;
+ }
+ }
+}
+
+static void
+mode_tree_clear_tagged(struct mode_tree_list *mtl)
+{
+ struct mode_tree_item *mti;
+
+ TAILQ_FOREACH(mti, mtl, entry) {
+ mti->tagged = 0;
+ mode_tree_clear_tagged(&mti->children);
+ }
+}
+
+void
+mode_tree_up(struct mode_tree_data *mtd, int wrap)
+{
+ if (mtd->current == 0) {
+ if (wrap) {
+ mtd->current = mtd->line_size - 1;
+ if (mtd->line_size >= mtd->height)
+ mtd->offset = mtd->line_size - mtd->height;
+ }
+ } else {
+ mtd->current--;
+ if (mtd->current < mtd->offset)
+ mtd->offset--;
+ }
+}
+
+void
+mode_tree_down(struct mode_tree_data *mtd, int wrap)
+{
+ if (mtd->current == mtd->line_size - 1) {
+ if (wrap) {
+ mtd->current = 0;
+ mtd->offset = 0;
+ }
+ } else {
+ mtd->current++;
+ if (mtd->current > mtd->offset + mtd->height - 1)
+ mtd->offset++;
+ }
+}
+
+void *
+mode_tree_get_current(struct mode_tree_data *mtd)
+{
+ return (mtd->line_list[mtd->current].item->itemdata);
+}
+
+u_int
+mode_tree_count_tagged(struct mode_tree_data *mtd)
+{
+ struct mode_tree_item *mti;
+ u_int i, tagged;
+
+ tagged = 0;
+ for (i = 0; i < mtd->line_size; i++) {
+ mti = mtd->line_list[i].item;
+ if (mti->tagged)
+ tagged++;
+ }
+ return (tagged);
+}
+
+void
+mode_tree_each_tagged(struct mode_tree_data *mtd, void (*cb)(void *, void *,
+ key_code), key_code key, int current)
+{
+ struct mode_tree_item *mti;
+ u_int i;
+ int fired;
+
+ fired = 0;
+ for (i = 0; i < mtd->line_size; i++) {
+ mti = mtd->line_list[i].item;
+ if (mti->tagged) {
+ fired = 1;
+ cb(mtd->modedata, mti->itemdata, key);
+ }
+ }
+ if (!fired && current) {
+ mti = mtd->line_list[mtd->current].item;
+ cb(mtd->modedata, mti->itemdata, key);
+ }
+}
+
+struct mode_tree_data *
+mode_tree_start(struct window_pane *wp, void (*buildcb)(void *, u_int,
+ uint64_t *), struct screen *(*drawcb)(void *, void *, u_int, u_int),
+ void *modedata, const char **sort_list, u_int sort_size, struct screen **s)
+{
+ struct mode_tree_data *mtd;
+
+ mtd = xcalloc(1, sizeof *mtd);
+ mtd->wp = wp;
+ mtd->modedata = modedata;
+
+ mtd->sort_list = sort_list;
+ mtd->sort_size = sort_size;
+ mtd->sort_type = 0;
+
+ mtd->buildcb = buildcb;
+ mtd->drawcb = drawcb;
+
+ TAILQ_INIT(&mtd->children);
+
+ *s = &mtd->screen;
+ screen_init(*s, screen_size_x(&wp->base), screen_size_y(&wp->base), 0);
+ (*s)->mode &= ~MODE_CURSOR;
+
+ return (mtd);
+}
+
+void
+mode_tree_build(struct mode_tree_data *mtd)
+{
+ struct screen *s = &mtd->screen;
+ uint64_t tag;
+ u_int i;
+
+ if (mtd->line_list != NULL)
+ tag = mtd->line_list[mtd->current].item->tag;
+ else
+ tag = 0;
+
+ TAILQ_CONCAT(&mtd->saved, &mtd->children, entry);
+ TAILQ_INIT(&mtd->children);
+
+ mtd->buildcb(mtd->modedata, mtd->sort_type, &tag);
+
+ mode_tree_free_items(&mtd->saved);
+ TAILQ_INIT(&mtd->saved);
+
+ mode_tree_clear_lines(mtd);
+ mode_tree_build_lines(mtd, &mtd->children, 0);
+
+ for (i = 0; i < mtd->line_size; i++) {
+ if (mtd->line_list[i].item->tag == tag)
+ break;
+ }
+ if (i != mtd->line_size)
+ mtd->current = i;
+ else {
+ mtd->current = 0;
+ mtd->offset = 0;
+ }
+
+ mtd->width = screen_size_x(s);
+ mtd->height = (screen_size_y(s) / 3) * 2;
+ if (mtd->height > mtd->line_size)
+ mtd->height = screen_size_y(s) / 2;
+ if (mtd->height < 10)
+ mtd->height = screen_size_y(s);
+ if (screen_size_y(s) - mtd->height < 2)
+ mtd->height = screen_size_y(s);
+}
+
+void
+mode_tree_free(struct mode_tree_data *mtd)
+{
+ mode_tree_free_items(&mtd->children);
+ mode_tree_clear_lines(mtd);
+ screen_free(&mtd->screen);
+ free(mtd);
+}
+
+void
+mode_tree_resize(struct mode_tree_data *mtd, u_int sx, u_int sy)
+{
+ struct screen *s = &mtd->screen;
+
+ screen_resize(s, sx, sy, 0);
+
+ mode_tree_build(mtd);
+ mode_tree_draw(mtd);
+
+ mtd->wp->flags |= PANE_REDRAW;
+}
+
+struct mode_tree_item *
+mode_tree_add(struct mode_tree_data *mtd, struct mode_tree_item *parent,
+ void *itemdata, uint64_t tag, const char *name, const char *text,
+ int expanded)
+{
+ struct mode_tree_item *mti, *saved;
+
+ log_debug("%s: %llu, %s %s", __func__, (unsigned long long)tag,
+ name, text);
+
+ mti = xcalloc(1, sizeof *mti);
+ mti->parent = parent;
+ mti->itemdata = itemdata;
+
+ mti->tag = tag;
+ mti->name = xstrdup(name);
+ mti->text = xstrdup(text);
+
+ saved = mode_tree_find_item(&mtd->saved, tag);
+ if (saved != NULL) {
+ if (parent == NULL || (parent != NULL && parent->expanded))
+ mti->tagged = saved->tagged;
+ mti->expanded = saved->expanded;
+ } else if (expanded == -1)
+ mti->expanded = 1;
+ else
+ mti->expanded = expanded;
+
+ TAILQ_INIT(&mti->children);
+
+ if (parent != NULL)
+ TAILQ_INSERT_TAIL(&parent->children, mti, entry);
+ else
+ TAILQ_INSERT_TAIL(&mtd->children, mti, entry);
+
+ return (mti);
+}
+
+void
+mode_tree_remove(struct mode_tree_data *mtd, struct mode_tree_item *mti)
+{
+ struct mode_tree_item *parent = mti->parent;
+
+ if (parent != NULL)
+ TAILQ_REMOVE(&parent->children, mti, entry);
+ else
+ TAILQ_REMOVE(&mtd->children, mti, entry);
+ mode_tree_free_item(mti);
+}
+
+void
+mode_tree_draw(struct mode_tree_data *mtd)
+{
+ struct window_pane *wp = mtd->wp;
+ struct screen *s = &mtd->screen, *box;
+ struct mode_tree_line *line;
+ struct mode_tree_item *mti;
+ struct options *oo = wp->window->options;
+ struct screen_write_ctx ctx;
+ struct grid_cell gc0, gc;
+ u_int w, h, i, j, sy, box_x, box_y;
+ char *text, *start, key[7];
+ const char *tag, *symbol;
+ size_t size;
+ int keylen;
+
+ if (mtd->line_size == 0)
+ return;
+
+ memcpy(&gc0, &grid_default_cell, sizeof gc0);
+ memcpy(&gc, &grid_default_cell, sizeof gc);
+ style_apply(&gc, oo, "mode-style");
+
+ w = mtd->width;
+ h = mtd->height;
+
+ screen_write_start(&ctx, NULL, s);
+ screen_write_clearscreen(&ctx, 8);
+
+ if (mtd->line_size > 10)
+ keylen = 6;
+ else
+ keylen = 4;
+
+ for (i = 0; i < mtd->line_size; i++) {
+ if (i < mtd->offset)
+ continue;
+ if (i > mtd->offset + h - 1)
+ break;
+
+ line = &mtd->line_list[i];
+ mti = line->item;
+
+ screen_write_cursormove(&ctx, 0, i - mtd->offset);
+
+ if (i < 10)
+ snprintf(key, sizeof key, "(%c)", '0' + i);
+ else if (i < 36)
+ snprintf(key, sizeof key, "(M-%c)", 'a' + (i - 10));
+ else
+ *key = '\0';
+
+ if (line->flat)
+ symbol = "";
+ else if (TAILQ_EMPTY(&mti->children))
+ symbol = " ";
+ else if (mti->expanded)
+ symbol = "- ";
+ else
+ symbol = "+ ";
+
+ if (line->depth == 0)
+ start = xstrdup(symbol);
+ else {
+ size = (4 * line->depth) + 32;
+
+ start = xcalloc(1, size);
+ for (j = 1; j < line->depth; j++) {
+ if (mti->parent != NULL &&
+ mtd->line_list[mti->parent->line].last)
+ strlcat(start, " ", size);
+ else
+ strlcat(start, "\001x\001 ", size);
+ }
+ if (line->last)
+ strlcat(start, "\001mq\001> ", size);
+ else
+ strlcat(start, "\001tq\001> ", size);
+ strlcat(start, symbol, size);
+ }
+
+ if (mti->tagged)
+ tag = "*";
+ else
+ tag = "";
+ xasprintf(&text, "%-*s%s%s%s: %s", keylen, key, start,
+ mti->name, tag, mti->text);
+ free(start);
+
+ if (mti->tagged) {
+ gc.attr ^= GRID_ATTR_BRIGHT;
+ gc0.attr ^= GRID_ATTR_BRIGHT;
+ }
+
+ if (i != mtd->current) {
+ screen_write_puts(&ctx, &gc0, "%.*s", w, text);
+ screen_write_clearendofline(&ctx, 8);
+ } else
+ screen_write_puts(&ctx, &gc, "%-*.*s", w, w, text);
+ free(text);
+
+ if (mti->tagged) {
+ gc.attr ^= GRID_ATTR_BRIGHT;
+ gc0.attr ^= GRID_ATTR_BRIGHT;
+ }
+ }
+
+ sy = screen_size_y(s);
+ if (sy <= 4 || h <= 4 || sy - h <= 4 || w <= 4) {
+ screen_write_stop(&ctx);
+ return;
+ }
+
+ line = &mtd->line_list[mtd->current];
+ mti = line->item;
+
+ screen_write_cursormove(&ctx, 0, h);
+ screen_write_box(&ctx, w, sy - h);
+
+ xasprintf(&text, " %s (sort: %s) ", mti->name,
+ mtd->sort_list[mtd->sort_type]);
+ if (w - 2 >= strlen(text)) {
+ screen_write_cursormove(&ctx, 1, h);
+ screen_write_puts(&ctx, &gc0, "%s", text);
+ }
+ free(text);
+
+ box_x = w - 4;
+ box_y = sy - h - 2;
+
+ box = mtd->drawcb(mtd->modedata, mti->itemdata, box_x, box_y);
+ if (box != NULL) {
+ screen_write_cursormove(&ctx, 2, h + 1);
+ screen_write_copy(&ctx, box, 0, 0, box_x, box_y, NULL, NULL);
+
+ screen_free(box);
+ }
+
+ screen_write_stop(&ctx);
+}
+
+int
+mode_tree_key(struct mode_tree_data *mtd, key_code *key, struct mouse_event *m)
+{
+ struct mode_tree_line *line;
+ struct mode_tree_item *current, *parent;
+ u_int i, x, y;
+ int choice;
+ key_code tmp;
+
+ if (*key == KEYC_MOUSEDOWN1_PANE) {
+ if (cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) {
+ *key = KEYC_NONE;
+ return (0);
+ }
+ if (x > mtd->width || y > mtd->height) {
+ *key = KEYC_NONE;
+ return (0);
+ }
+ if (mtd->offset + y < mtd->line_size) {
+ mtd->current = mtd->offset + y;
+ *key = '\r';
+ return (0);
+ }
+ }
+
+ line = &mtd->line_list[mtd->current];
+ current = line->item;
+
+ choice = -1;
+ if (*key >= '0' && *key <= '9')
+ choice = (*key) - '0';
+ else if (((*key) & KEYC_MASK_MOD) == KEYC_ESCAPE) {
+ tmp = (*key) & KEYC_MASK_KEY;
+ if (tmp >= 'a' && tmp <= 'z')
+ choice = 10 + (tmp - 'a');
+ }
+ if (choice != -1) {
+ if ((u_int)choice > mtd->line_size - 1) {
+ *key = KEYC_NONE;
+ return (0);
+ }
+ mtd->current = choice;
+ *key = '\r';
+ return (0);
+ }
+
+ switch (*key) {
+ case 'q':
+ case '\033': /* Escape */
+ return (1);
+ case KEYC_UP:
+ case 'k':
+ case KEYC_WHEELUP_PANE:
+ mode_tree_up(mtd, 1);
+ break;
+ case KEYC_DOWN:
+ case 'j':
+ case KEYC_WHEELDOWN_PANE:
+ mode_tree_down(mtd, 1);
+ break;
+ case KEYC_PPAGE:
+ case '\002': /* C-b */
+ for (i = 0; i < mtd->height; i++) {
+ if (mtd->current == 0)
+ break;
+ mode_tree_up(mtd, 1);
+ }
+ break;
+ case KEYC_NPAGE:
+ case '\006': /* C-f */
+ for (i = 0; i < mtd->height; i++) {
+ if (mtd->current == mtd->line_size - 1)
+ break;
+ mode_tree_down(mtd, 1);
+ }
+ break;
+ case KEYC_HOME:
+ mtd->current = 0;
+ mtd->offset = 0;
+ break;
+ case KEYC_END:
+ mtd->current = mtd->line_size - 1;
+ if (mtd->current > mtd->height - 1)
+ mtd->offset = mtd->current - mtd->height;
+ else
+ mtd->offset = 0;
+ break;
+ case 't':
+ /*
+ * Do not allow parents and children to both be tagged: untag
+ * all parents and children of current.
+ */
+ if (!current->tagged) {
+ parent = current->parent;
+ while (parent != NULL) {
+ parent->tagged = 0;
+ parent = parent->parent;
+ }
+ mode_tree_clear_tagged(&current->children);
+ current->tagged = 1;
+ } else
+ current->tagged = 0;
+ mode_tree_down(mtd, 0);
+ break;
+ case 'T':
+ for (i = 0; i < mtd->line_size; i++)
+ mtd->line_list[i].item->tagged = 0;
+ break;
+ case '\024': /* C-t */
+ for (i = 0; i < mtd->line_size; i++) {
+ if (mtd->line_list[i].item->parent == NULL)
+ mtd->line_list[i].item->tagged = 1;
+ else
+ mtd->line_list[i].item->tagged = 0;
+ }
+ break;
+ case 'O':
+ mtd->sort_type++;
+ if (mtd->sort_type == mtd->sort_size)
+ mtd->sort_type = 0;
+ mode_tree_build(mtd);
+ break;
+ case KEYC_LEFT:
+ case '-':
+ if (line->flat || !current->expanded)
+ current = current->parent;
+ if (current == NULL)
+ mode_tree_up(mtd, 0);
+ else {
+ current->expanded = 0;
+ mtd->current = current->line;
+ mode_tree_build(mtd);
+ }
+ break;
+ case KEYC_RIGHT:
+ case '+':
+ if (line->flat || current->expanded)
+ mode_tree_down(mtd, 0);
+ else if (!line->flat) {
+ current->expanded = 1;
+ mode_tree_build(mtd);
+ }
+ break;
+ }
+ return (0);
+}
+
+void
+mode_tree_run_command(struct client *c, struct cmd_find_state *fs,
+ const char *template, const char *name)
+{
+ struct cmdq_item *new_item;
+ struct cmd_list *cmdlist;
+ char *command, *cause;
+
+ command = cmd_template_replace(template, name, 1);
+ if (command == NULL || *command == '\0')
+ return;
+
+ cmdlist = cmd_string_parse(command, NULL, 0, &cause);
+ if (cmdlist == NULL) {
+ if (cause != NULL && c != NULL) {
+ *cause = toupper((u_char)*cause);
+ status_message_set(c, "%s", cause);
+ }
+ free(cause);
+ } else {
+ new_item = cmdq_get_command(cmdlist, fs, NULL, 0);
+ cmdq_append(c, new_item);
+ cmd_list_free(cmdlist);
+ }
+
+ free(command);
+}