From aad4e4ddb194cba9c01b0ddd696fb7b214e1a7eb Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 30 May 2017 21:44:59 +0000 Subject: 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. --- Makefile | 13 +- arguments.c | 3 +- cfg.c | 3 +- cmd-choose-buffer.c | 101 ----- cmd-choose-client.c | 135 ------- cmd-choose-tree.c | 219 ++--------- cmd-copy-mode.c | 6 +- cmd-find-window.c | 247 +++--------- cmd-queue.c | 3 +- cmd-run-shell.c | 2 +- cmd-split-window.c | 3 +- cmd.c | 4 - format.c | 4 +- key-bindings.c | 4 +- mode-tree.c | 705 +++++++++++++++++++++++++++++++++ options-table.c | 6 +- screen-write.c | 118 ++++++ tmux.1 | 197 ++++------ tmux.h | 69 ++-- window-buffer.c | 342 ++++++++++++++++ window-choose.c | 1078 --------------------------------------------------- window-client.c | 335 ++++++++++++++++ window-clock.c | 6 +- window-copy.c | 6 +- window-tree.c | 713 ++++++++++++++++++++++++++++++++++ window.c | 31 +- 26 files changed, 2441 insertions(+), 1912 deletions(-) delete mode 100644 cmd-choose-buffer.c delete mode 100644 cmd-choose-client.c create mode 100644 mode-tree.c create mode 100644 window-buffer.c delete mode 100644 window-choose.c create mode 100644 window-client.c create mode 100644 window-tree.c diff --git a/Makefile b/Makefile index 7f9fdc9d..7eb905ef 100644 --- a/Makefile +++ b/Makefile @@ -10,8 +10,6 @@ SRCS= alerts.c \ cmd-bind-key.c \ cmd-break-pane.c \ cmd-capture-pane.c \ - cmd-choose-buffer.c \ - cmd-choose-client.c \ cmd-choose-tree.c \ cmd-command-prompt.c \ cmd-confirm-before.c \ @@ -20,6 +18,7 @@ SRCS= alerts.c \ cmd-display-message.c \ cmd-display-panes.c \ cmd-find-window.c \ + cmd-find.c \ cmd-if-shell.c \ cmd-join-pane.c \ cmd-kill-pane.c \ @@ -40,6 +39,7 @@ SRCS= alerts.c \ cmd-new-window.c \ cmd-paste-buffer.c \ cmd-pipe-pane.c \ + cmd-queue.c \ cmd-refresh-client.c \ cmd-rename-session.c \ cmd-rename-window.c \ @@ -69,11 +69,9 @@ SRCS= alerts.c \ cmd-unbind-key.c \ cmd-wait-for.c \ cmd.c \ - cmd-find.c \ - cmd-queue.c \ colour.c \ - control.c \ control-notify.c \ + control.c \ environ.c \ format.c \ grid-view.c \ @@ -88,6 +86,7 @@ SRCS= alerts.c \ layout-set.c \ layout.c \ log.c \ + mode-tree.c \ names.c \ notify.c \ options-table.c \ @@ -112,9 +111,11 @@ SRCS= alerts.c \ tty-term.c \ tty.c \ utf8.c \ - window-choose.c \ + window-buffer.c \ + window-client.c \ window-clock.c \ window-copy.c \ + window-tree.c \ window.c \ xmalloc.c \ xterm-keys.c diff --git a/arguments.c b/arguments.c index 37028648..be918a5a 100644 --- a/arguments.c +++ b/arguments.c @@ -35,7 +35,6 @@ struct args_entry { RB_ENTRY(args_entry) entry; }; -static void args_set(struct args *, u_char, const char *); static struct args_entry *args_find(struct args *, u_char); static int args_cmp(struct args_entry *, struct args_entry *); @@ -196,7 +195,7 @@ args_has(struct args *args, u_char ch) } /* Set argument value in the arguments tree. */ -static void +void args_set(struct args *args, u_char ch, const char *value) { struct args_entry *entry; diff --git a/cfg.c b/cfg.c index a22b9d68..5f5df24a 100644 --- a/cfg.c +++ b/cfg.c @@ -23,7 +23,6 @@ #include #include #include -#include #include #include "tmux.h" @@ -234,7 +233,7 @@ cfg_show_causes(struct session *s) return; wp = s->curw->window->active; - window_pane_set_mode(wp, &window_copy_mode); + window_pane_set_mode(wp, &window_copy_mode, NULL, NULL); window_copy_init_for_output(wp); for (i = 0; i < cfg_ncauses; i++) { window_copy_add(wp, "%s", cfg_causes[i]); diff --git a/cmd-choose-buffer.c b/cmd-choose-buffer.c deleted file mode 100644 index 52ad0ac1..00000000 --- a/cmd-choose-buffer.c +++ /dev/null @@ -1,101 +0,0 @@ -/* $OpenBSD$ */ - -/* - * Copyright (c) 2010 Nicholas Marriott - * - * 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 - -#include -#include - -#include "tmux.h" - -/* - * Enter choice mode to choose a buffer. - */ - -#define CHOOSE_BUFFER_TEMPLATE \ - "#{buffer_name}: #{buffer_size} bytes: #{buffer_sample}" - -static enum cmd_retval cmd_choose_buffer_exec(struct cmd *, - struct cmdq_item *); - -const struct cmd_entry cmd_choose_buffer_entry = { - .name = "choose-buffer", - .alias = NULL, - - .args = { "F:t:", 0, 1 }, - .usage = CMD_TARGET_WINDOW_USAGE " [-F format] [template]", - - .target = { 't', CMD_FIND_WINDOW, 0 }, - - .flags = 0, - .exec = cmd_choose_buffer_exec -}; - -static enum cmd_retval -cmd_choose_buffer_exec(struct cmd *self, struct cmdq_item *item) -{ - struct args *args = self->args; - struct client *c = cmd_find_client(item, NULL, 1); - struct winlink *wl = item->target.wl; - struct window_choose_data *cdata; - struct paste_buffer *pb; - char *action, *action_data; - const char *template; - u_int idx; - - if (c == NULL) { - cmdq_error(item, "no client available"); - return (CMD_RETURN_ERROR); - } - - if ((template = args_get(args, 'F')) == NULL) - template = CHOOSE_BUFFER_TEMPLATE; - - if (paste_get_top(NULL) == NULL) - return (CMD_RETURN_NORMAL); - - if (window_pane_set_mode(wl->window->active, &window_choose_mode) != 0) - return (CMD_RETURN_NORMAL); - - if (args->argc != 0) - action = xstrdup(args->argv[0]); - else - action = xstrdup("paste-buffer -b '%%'"); - - idx = 0; - pb = NULL; - while ((pb = paste_walk(pb)) != NULL) { - cdata = window_choose_data_create(TREE_OTHER, c, c->session); - cdata->idx = idx; - - cdata->ft_template = xstrdup(template); - format_defaults_paste_buffer(cdata->ft, pb); - - xasprintf(&action_data, "%s", paste_buffer_name(pb)); - cdata->command = cmd_template_replace(action, action_data, 1); - free(action_data); - - window_choose_add(wl->window->active, cdata); - idx++; - } - free(action); - - window_choose_ready(wl->window->active, 0, NULL); - - return (CMD_RETURN_NORMAL); -} diff --git a/cmd-choose-client.c b/cmd-choose-client.c deleted file mode 100644 index 88118209..00000000 --- a/cmd-choose-client.c +++ /dev/null @@ -1,135 +0,0 @@ -/* $OpenBSD$ */ - -/* - * Copyright (c) 2009 Nicholas Marriott - * - * 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 - -#include -#include - -#include "tmux.h" - -/* - * Enter choice mode to choose a client. - */ - -#define CHOOSE_CLIENT_TEMPLATE \ - "#{client_name}: #{session_name} " \ - "[#{client_width}x#{client_height} #{client_termname}]" \ - "#{?client_utf8, (utf8),}#{?client_readonly, (ro),} " \ - "(last used #{t:client_activity})" - -static enum cmd_retval cmd_choose_client_exec(struct cmd *, - struct cmdq_item *); - -static void cmd_choose_client_callback(struct window_choose_data *); - -const struct cmd_entry cmd_choose_client_entry = { - .name = "choose-client", - .alias = NULL, - - .args = { "F:t:", 0, 1 }, - .usage = CMD_TARGET_WINDOW_USAGE " [-F format] [template]", - - .target = { 't', CMD_FIND_WINDOW, 0 }, - - .flags = 0, - .exec = cmd_choose_client_exec -}; - -struct cmd_choose_client_data { - struct client *client; -}; - -static enum cmd_retval -cmd_choose_client_exec(struct cmd *self, struct cmdq_item *item) -{ - struct args *args = self->args; - struct client *c = cmd_find_client(item, NULL, 1); - struct client *c1; - struct window_choose_data *cdata; - struct winlink *wl = item->target.wl; - const char *template; - char *action; - u_int idx, cur; - - if (c == NULL) { - cmdq_error(item, "no client available"); - return (CMD_RETURN_ERROR); - } - - if (window_pane_set_mode(wl->window->active, &window_choose_mode) != 0) - return (CMD_RETURN_NORMAL); - - if ((template = args_get(args, 'F')) == NULL) - template = CHOOSE_CLIENT_TEMPLATE; - - if (args->argc != 0) - action = xstrdup(args->argv[0]); - else - action = xstrdup("detach-client -t '%%'"); - - cur = idx = 0; - TAILQ_FOREACH(c1, &clients, entry) { - if (c1->session == NULL) - continue; - if (c1 == item->client) - cur = idx; - - cdata = window_choose_data_create(TREE_OTHER, c, c->session); - cdata->idx = idx; - - cdata->ft_template = xstrdup(template); - format_add(cdata->ft, "line", "%u", idx); - format_defaults(cdata->ft, c1, NULL, NULL, NULL); - - cdata->command = cmd_template_replace(action, c1->name, 1); - - window_choose_add(wl->window->active, cdata); - - idx++; - } - free(action); - - window_choose_ready(wl->window->active, cur, - cmd_choose_client_callback); - - return (CMD_RETURN_NORMAL); -} - -static void -cmd_choose_client_callback(struct window_choose_data *cdata) -{ - struct client *c; - u_int idx; - - if (cdata == NULL) - return; - if (cdata->start_client->flags & CLIENT_DEAD) - return; - - idx = 0; - TAILQ_FOREACH(c, &clients, entry) { - if (idx == cdata->idx) - break; - idx++; - } - if (c == NULL || c->session == NULL) - return; - - window_choose_data_run(cdata); -} diff --git a/cmd-choose-tree.c b/cmd-choose-tree.c index 28b5013a..f8f24f12 100644 --- a/cmd-choose-tree.c +++ b/cmd-choose-tree.c @@ -18,66 +18,48 @@ #include -#include -#include - -#include - #include "tmux.h" -#define CMD_CHOOSE_TREE_WINDOW_ACTION "select-window -t '%%'" -#define CMD_CHOOSE_TREE_SESSION_ACTION "switch-client -t '%%'" - /* - * Enter choice mode to choose a session and/or window. + * Enter a mode. */ -#define CHOOSE_TREE_SESSION_TEMPLATE \ - "#{session_name}: #{session_windows} windows" \ - "#{?session_grouped, (group ,}" \ - "#{session_group}#{?session_grouped,),}" \ - "#{?session_attached, (attached),}" -#define CHOOSE_TREE_WINDOW_TEMPLATE \ - "#{window_index}: #{window_name}#{window_flags} " \ - "\"#{pane_title}\"" - static enum cmd_retval cmd_choose_tree_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_choose_tree_entry = { .name = "choose-tree", .alias = NULL, - .args = { "S:W:swub:c:t:", 0, 1 }, - .usage = "[-suw] [-b session-template] [-c window template] " - "[-S format] [-W format] " CMD_TARGET_WINDOW_USAGE, + .args = { "st:w", 0, 1 }, + .usage = "[-sw] " CMD_TARGET_PANE_USAGE, - .target = { 't', CMD_FIND_WINDOW, 0 }, + .target = { 't', CMD_FIND_PANE, 0 }, .flags = 0, .exec = cmd_choose_tree_exec }; -const struct cmd_entry cmd_choose_session_entry = { - .name = "choose-session", +const struct cmd_entry cmd_choose_client_entry = { + .name = "choose-client", .alias = NULL, - .args = { "F:t:", 0, 1 }, - .usage = CMD_TARGET_WINDOW_USAGE " [-F format] [template]", + .args = { "t:", 0, 1 }, + .usage = CMD_TARGET_PANE_USAGE, - .target = { 't', CMD_FIND_WINDOW, 0 }, + .target = { 't', CMD_FIND_PANE, 0 }, .flags = 0, .exec = cmd_choose_tree_exec }; -const struct cmd_entry cmd_choose_window_entry = { - .name = "choose-window", +const struct cmd_entry cmd_choose_buffer_entry = { + .name = "choose-buffer", .alias = NULL, - .args = { "F:t:", 0, 1 }, - .usage = CMD_TARGET_WINDOW_USAGE "[-F format] [template]", + .args = { "t:", 0, 1 }, + .usage = CMD_TARGET_PANE_USAGE, - .target = { 't', CMD_FIND_WINDOW, 0 }, + .target = { 't', CMD_FIND_PANE, 0 }, .flags = 0, .exec = cmd_choose_tree_exec @@ -87,167 +69,20 @@ static enum cmd_retval cmd_choose_tree_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = self->args; - struct client *c = cmd_find_client(item, NULL, 1); - struct winlink *wl = item->target.wl, *wm; - struct session *s = item->target.s, *s2; - struct window_choose_data *wcd = NULL; - const char *ses_template, *win_template; - char *final_win_action, *cur_win_template; - char *final_win_template_middle; - char *final_win_template_last; - const char *ses_action, *win_action; - u_int cur_win, idx_ses, win_ses, win_max; - u_int wflag, sflag; - - ses_template = win_template = NULL; - ses_action = win_action = NULL; - - if (c == NULL) { - cmdq_error(item, "no client available"); - return (CMD_RETURN_ERROR); - } - - if (window_pane_set_mode(wl->window->active, &window_choose_mode) != 0) - return (CMD_RETURN_NORMAL); - - /* Sort out which command this is. */ - wflag = sflag = 0; - if (self->entry == &cmd_choose_session_entry) { - sflag = 1; - if ((ses_template = args_get(args, 'F')) == NULL) - ses_template = CHOOSE_TREE_SESSION_TEMPLATE; - - if (args->argc != 0) - ses_action = args->argv[0]; - else - ses_action = CMD_CHOOSE_TREE_SESSION_ACTION; - } else if (self->entry == &cmd_choose_window_entry) { - wflag = 1; - if ((win_template = args_get(args, 'F')) == NULL) - win_template = CHOOSE_TREE_WINDOW_TEMPLATE; - - if (args->argc != 0) - win_action = args->argv[0]; - else - win_action = CMD_CHOOSE_TREE_WINDOW_ACTION; - } else { - wflag = args_has(args, 'w'); - sflag = args_has(args, 's'); - - if ((ses_action = args_get(args, 'b')) == NULL) - ses_action = CMD_CHOOSE_TREE_SESSION_ACTION; - - if ((win_action = args_get(args, 'c')) == NULL) - win_action = CMD_CHOOSE_TREE_WINDOW_ACTION; - - if ((ses_template = args_get(args, 'S')) == NULL) - ses_template = CHOOSE_TREE_SESSION_TEMPLATE; - - if ((win_template = args_get(args, 'W')) == NULL) - win_template = CHOOSE_TREE_WINDOW_TEMPLATE; - } - - /* - * If not asking for windows and sessions, assume no "-ws" given and - * hence display the entire tree outright. - */ - if (!wflag && !sflag) - wflag = sflag = 1; - - /* - * If we're drawing in tree mode, including sessions, then pad the - * window template, otherwise just render the windows as a flat list - * without any padding. - */ - if (wflag && sflag) { - xasprintf(&final_win_template_middle, - " \001tq\001> %s", win_template); - xasprintf(&final_win_template_last, - " \001mq\001> %s", win_template); - } else if (wflag) { - final_win_template_middle = xstrdup(win_template); - final_win_template_last = xstrdup(win_template); + struct window_pane *wp = item->target.wp; + const struct window_mode *mode; + + if (self->entry == &cmd_choose_buffer_entry) { + if (paste_get_top(NULL) == NULL) + return (CMD_RETURN_NORMAL); + mode = &window_buffer_mode; + } else if (self->entry == &cmd_choose_client_entry) { + if (server_client_how_many() == 0) + return (CMD_RETURN_NORMAL); + mode = &window_client_mode; } else - final_win_template_middle = final_win_template_last = NULL; - - idx_ses = cur_win = -1; - RB_FOREACH(s2, sessions, &sessions) { - idx_ses++; - - /* - * If we're just choosing windows, jump straight there. Note - * that this implies the current session, so only choose - * windows when the session matches this one. - */ - if (wflag && !sflag) { - if (s != s2) - continue; - goto windows_only; - } - - wcd = window_choose_add_session(wl->window->active, - c, s2, ses_template, ses_action, idx_ses); - - /* If we're just choosing sessions, skip choosing windows. */ - if (sflag && !wflag) { - if (s == s2) - cur_win = idx_ses; - continue; - } -windows_only: - win_ses = win_max = -1; - RB_FOREACH(wm, winlinks, &s2->windows) - win_max++; - RB_FOREACH(wm, winlinks, &s2->windows) { - win_ses++; - if (sflag && wflag) - idx_ses++; - - if (wm == s2->curw && s == s2) { - if (wflag && !sflag) { - /* - * Then we're only counting windows. - * So remember which is the current - * window in the list. - */ - cur_win = win_ses; - } else - cur_win = idx_ses; - } - - xasprintf(&final_win_action, "%s %s %s", - wcd != NULL ? wcd->command : "", - wcd != NULL ? ";" : "", win_action); - - if (win_ses != win_max) - cur_win_template = final_win_template_middle; - else - cur_win_template = final_win_template_last; - - window_choose_add_window(wl->window->active, - c, s2, wm, cur_win_template, - final_win_action, - (wflag && !sflag) ? win_ses : idx_ses); - - free(final_win_action); - } - - /* - * If we're just drawing windows, don't consider moving on to - * other sessions as we only list windows in this session. - */ - if (wflag && !sflag) - break; - } - free(final_win_template_middle); - free(final_win_template_last); - - window_choose_ready(wl->window->active, cur_win, NULL); - - if (args_has(args, 'u')) { - window_choose_expand_all(wl->window->active); - window_choose_set_current(wl->window->active, cur_win); - } + mode = &window_tree_mode; + window_pane_set_mode(wp, mode, &item->target, args); return (CMD_RETURN_NORMAL); } diff --git a/cmd-copy-mode.c b/cmd-copy-mode.c index 5599bb96..699a868d 100644 --- a/cmd-copy-mode.c +++ b/cmd-copy-mode.c @@ -60,6 +60,7 @@ cmd_copy_mode_exec(struct cmd *self, struct cmdq_item *item) struct client *c = item->client; struct session *s; struct window_pane *wp = item->target.wp; + int flag; if (args_has(args, 'M')) { if ((wp = cmd_mouse_pane(&shared->mouse, &s, NULL)) == NULL) @@ -69,12 +70,13 @@ cmd_copy_mode_exec(struct cmd *self, struct cmdq_item *item) } if (self->entry == &cmd_clock_mode_entry) { - window_pane_set_mode(wp, &window_clock_mode); + window_pane_set_mode(wp, &window_clock_mode, NULL, NULL); return (CMD_RETURN_NORMAL); } if (wp->mode != &window_copy_mode) { - if (window_pane_set_mode(wp, &window_copy_mode) != 0) + flag = window_pane_set_mode(wp, &window_copy_mode, NULL, NULL); + if (flag != 0) return (CMD_RETURN_NORMAL); window_copy_init_from_pane(wp, args_has(self->args, 'e')); } diff --git a/cmd-find-window.c b/cmd-find-window.c index 6338e2e1..92714498 100644 --- a/cmd-find-window.c +++ b/cmd-find-window.c @@ -18,9 +18,7 @@ #include -#include #include -#include #include "tmux.h" @@ -28,216 +26,69 @@ * Find window containing text. */ -#define FIND_WINDOW_TEMPLATE \ - "#{window_index}: #{window_name} " \ - "[#{window_width}x#{window_height}] " \ - "(#{window_panes} panes) #{window_find_matches}" - static enum cmd_retval cmd_find_window_exec(struct cmd *, struct cmdq_item *); -static void cmd_find_window_callback(struct window_choose_data *); - -/* Flags for determining matching behavior. */ -#define CMD_FIND_WINDOW_BY_TITLE 0x1 -#define CMD_FIND_WINDOW_BY_CONTENT 0x2 -#define CMD_FIND_WINDOW_BY_NAME 0x4 - -#define CMD_FIND_WINDOW_ALL \ - (CMD_FIND_WINDOW_BY_TITLE | \ - CMD_FIND_WINDOW_BY_CONTENT | \ - CMD_FIND_WINDOW_BY_NAME) - const struct cmd_entry cmd_find_window_entry = { .name = "find-window", .alias = "findw", - .args = { "F:CNt:T", 1, 4 }, - .usage = "[-CNT] [-F format] " CMD_TARGET_WINDOW_USAGE " match-string", + .args = { "CNt:T", 1, 1 }, + .usage = "[-CNT] " CMD_TARGET_PANE_USAGE " match-string", - .target = { 't', CMD_FIND_WINDOW, 0 }, + .target = { 't', CMD_FIND_PANE, 0 }, .flags = 0, .exec = cmd_find_window_exec }; -struct cmd_find_window_data { - struct winlink *wl; - char *list_ctx; - u_int pane_id; - TAILQ_ENTRY(cmd_find_window_data) entry; -}; -TAILQ_HEAD(cmd_find_window_list, cmd_find_window_data); - -static u_int cmd_find_window_match_flags(struct args *); -static void cmd_find_window_match(struct cmd_find_window_list *, int, - struct winlink *, const char *, const char *); - -static u_int -cmd_find_window_match_flags(struct args *args) -{ - u_int match_flags = 0; - - /* Turn on flags based on the options. */ - if (args_has(args, 'T')) - match_flags |= CMD_FIND_WINDOW_BY_TITLE; - if (args_has(args, 'C')) - match_flags |= CMD_FIND_WINDOW_BY_CONTENT; - if (args_has(args, 'N')) - match_flags |= CMD_FIND_WINDOW_BY_NAME; - - /* If none of the flags were set, default to matching anything. */ - if (match_flags == 0) - match_flags = CMD_FIND_WINDOW_ALL; - - return (match_flags); -} - -static void -cmd_find_window_match(struct cmd_find_window_list *find_list, - int match_flags, struct winlink *wl, const char *str, - const char *searchstr) -{ - struct cmd_find_window_data *find_data; - struct window_pane *wp; - u_int i, line; - char *sres; - - find_data = xcalloc(1, sizeof *find_data); - - i = 0; - TAILQ_FOREACH(wp, &wl->window->panes, entry) { - i++; - - if ((match_flags & CMD_FIND_WINDOW_BY_NAME) && - fnmatch(searchstr, wl->window->name, 0) == 0) { - find_data->list_ctx = xstrdup(""); - break; - } - - if ((match_flags & CMD_FIND_WINDOW_BY_TITLE) && - fnmatch(searchstr, wp->base.title, 0) == 0) { - xasprintf(&find_data->list_ctx, - "pane %u title: \"%s\"", i - 1, wp->base.title); - break; - } - - if (match_flags & CMD_FIND_WINDOW_BY_CONTENT && - (sres = window_pane_search_old(wp, str, &line)) != NULL) { - xasprintf(&find_data->list_ctx, - "pane %u line %u: \"%s\"", i - 1, line + 1, sres); - free(sres); - break; - } - } - - if (find_data->list_ctx != NULL) { - find_data->wl = wl; - find_data->pane_id = i - 1; - TAILQ_INSERT_TAIL(find_list, find_data, entry); - } else - free(find_data); -} - static enum cmd_retval cmd_find_window_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct cmd_find_state *current = &item->shared->current; - struct client *c = cmd_find_client(item, NULL, 1); - struct window_choose_data *cdata; - struct session *s = item->target.s; - struct winlink *wl = item->target.wl, *wm; - struct cmd_find_window_list find_list; - struct cmd_find_window_data *find_data; - struct cmd_find_window_data *find_data1; - char *str, *searchstr; - const char *template; - u_int i, match_flags; - - if (c == NULL) { - cmdq_error(item, "no client available"); - return (CMD_RETURN_ERROR); - } - - if ((template = args_get(args, 'F')) == NULL) - template = FIND_WINDOW_TEMPLATE; - - match_flags = cmd_find_window_match_flags(args); - str = args->argv[0]; - - TAILQ_INIT(&find_list); - - xasprintf(&searchstr, "*%s*", str); - RB_FOREACH(wm, winlinks, &s->windows) - cmd_find_window_match(&find_list, match_flags, wm, str, searchstr); - free(searchstr); - - if (TAILQ_EMPTY(&find_list)) { - cmdq_error(item, "no windows matching: %s", str); - return (CMD_RETURN_ERROR); - } - - if (TAILQ_NEXT(TAILQ_FIRST(&find_list), entry) == NULL) { - if (session_select(s, TAILQ_FIRST(&find_list)->wl->idx) == 0) { - cmd_find_from_session(current, s); - server_redraw_session(s); - } - recalculate_sizes(); - goto out; - } - - if (window_pane_set_mode(wl->window->active, &window_choose_mode) != 0) - goto out; - - i = 0; - TAILQ_FOREACH(find_data, &find_list, entry) { - cdata = window_choose_data_create(TREE_OTHER, c, c->session); - cdata->idx = find_data->wl->idx; - cdata->wl = find_data->wl; - - cdata->ft_template = xstrdup(template); - cdata->pane_id = find_data->pane_id; - - format_add(cdata->ft, "line", "%u", i); - format_add(cdata->ft, "window_find_matches", "%s", - find_data->list_ctx); - format_defaults(cdata->ft, NULL, s, find_data->wl, NULL); - - window_choose_add(wl->window->active, cdata); - - i++; - } - - window_choose_ready(wl->window->active, 0, cmd_find_window_callback); - -out: - TAILQ_FOREACH_SAFE(find_data, &find_list, entry, find_data1) { - free(find_data->list_ctx); - TAILQ_REMOVE(&find_list, find_data, entry); - free(find_data); - } - return (CMD_RETURN_NORMAL); -} - -static void -cmd_find_window_callback(struct window_choose_data *cdata) -{ - struct session *s; - struct window_pane *wp; - - if (cdata == NULL) - return; + struct args *args = self->args, *new_args; + struct window_pane *wp = item->target.wp; + const char *s = args->argv[0]; + char *filter, *argv = { NULL }; + int C, N, T; + + C = args_has(args, 'C'); + N = args_has(args, 'N'); + T = args_has(args, 'T'); + + if (!C && !N && !T) + C = N = T = 1; + + if (C && N && T) { + xasprintf(&filter, + "#{||:" + "#{C:%s},#{||:#{m:*%s*,#{window_name}}," + "#{m:*%s*,#{pane_title}}}}", + s, s, s); + } else if (C && N) { + xasprintf(&filter, + "#{||:#{C:%s},#{m:*%s*,#{window_name}}}", + s, s); + } else if (C && T) { + xasprintf(&filter, + "#{||:#{C:%s},#{m:*%s*,#{pane_title}}}", + s, s); + } else if (N && T) { + xasprintf(&filter, + "#{||:#{m:*%s*,#{window_name}},#{m:*%s*,#{pane_title}}}", + s, s); + } else if (C) + xasprintf(&filter, "#{C:%s}", s); + else if (N) + xasprintf(&filter, "#{m:*%s*,#{window_name}}", s); + else if (T) + xasprintf(&filter, "#{m:*%s*,#{pane_title}}", s); + + new_args = args_parse("", 1, &argv); + args_set(new_args, 'f', filter); + + window_pane_set_mode(wp, &window_tree_mode, &item->target, new_args); + + args_free(new_args); + free(filter); - s = cdata->start_session; - if (!session_alive(s)) - return; - - wp = window_pane_at_index(cdata->wl->window, cdata->pane_id); - if (wp != NULL && window_pane_visible(wp)) - window_set_active_pane(cdata->wl->window, wp); - - if (session_select(s, cdata->idx) == 0) { - server_redraw_session(s); - recalculate_sizes(); - } + return (CMD_RETURN_NORMAL); } diff --git a/cmd-queue.c b/cmd-queue.c index b0885965..8d89e990 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -428,7 +428,8 @@ cmdq_print(struct cmdq_item *item, const char *fmt, ...) w = c->session->curw->window; if (w->active->mode != &window_copy_mode) { window_pane_reset_mode(w->active); - window_pane_set_mode(w->active, &window_copy_mode); + window_pane_set_mode(w->active, &window_copy_mode, NULL, + NULL); window_copy_init_for_output(w->active); } window_copy_vadd(w->active, fmt, ap); diff --git a/cmd-run-shell.c b/cmd-run-shell.c index 2565d6a1..f77fb36a 100644 --- a/cmd-run-shell.c +++ b/cmd-run-shell.c @@ -75,7 +75,7 @@ cmd_run_shell_print(struct job *job, const char *msg) return; } - if (window_pane_set_mode(wp, &window_copy_mode) == 0) + if (window_pane_set_mode(wp, &window_copy_mode, NULL, NULL) == 0) window_copy_init_for_output(wp); if (wp->mode == &window_copy_mode) window_copy_add(wp, "%s", msg); diff --git a/cmd-split-window.c b/cmd-split-window.c index 029e6900..4d94cd91 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -134,7 +134,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) goto error; } new_wp = window_add_pane(w, wp, args_has(args, 'b'), hlimit); - layout_assign_pane(lc, new_wp); + layout_make_leaf(lc, new_wp); path = NULL; if (item->client != NULL && item->client->session == NULL) @@ -152,6 +152,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) } environ_free(env); + layout_fix_panes(w, w->sx, w->sy); server_redraw_window(w); if (!args_has(args, 'd')) { diff --git a/cmd.c b/cmd.c index c61e1e07..ff32d5dd 100644 --- a/cmd.c +++ b/cmd.c @@ -34,9 +34,7 @@ extern const struct cmd_entry cmd_break_pane_entry; extern const struct cmd_entry cmd_capture_pane_entry; extern const struct cmd_entry cmd_choose_buffer_entry; extern const struct cmd_entry cmd_choose_client_entry; -extern const struct cmd_entry cmd_choose_session_entry; extern const struct cmd_entry cmd_choose_tree_entry; -extern const struct cmd_entry cmd_choose_window_entry; extern const struct cmd_entry cmd_clear_history_entry; extern const struct cmd_entry cmd_clock_mode_entry; extern const struct cmd_entry cmd_command_prompt_entry; @@ -123,9 +121,7 @@ const struct cmd_entry *cmd_table[] = { &cmd_capture_pane_entry, &cmd_choose_buffer_entry, &cmd_choose_client_entry, - &cmd_choose_session_entry, &cmd_choose_tree_entry, - &cmd_choose_window_entry, &cmd_clear_history_entry, &cmd_clock_mode_entry, &cmd_command_prompt_entry, diff --git a/format.c b/format.c index 7af7840d..7aad4cb8 100644 --- a/format.c +++ b/format.c @@ -19,11 +19,9 @@ #include #include -#include #include #include #include -#include #include #include #include @@ -840,7 +838,7 @@ format_choose(char *s, char **left, char **right) } /* Is this true? */ -static int +int format_true(const char *s) { if (s != NULL && *s != '\0' && (s[0] != '0' || s[1] != '\0')) diff --git a/key-bindings.c b/key-bindings.c index 11afe0a7..8e184aa0 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -196,9 +196,9 @@ key_bindings_init(void) "bind p previous-window", "bind q display-panes", "bind r refresh-client", - "bind s choose-tree", + "bind s choose-tree -s", "bind t clock-mode", - "bind w choose-window", + "bind w choose-tree -w", "bind x confirm-before -p\"kill-pane #P? (y/n)\" kill-pane", "bind z resize-pane -Z", "bind { swap-pane -U", 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 + * + * 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 + +#include +#include +#include +#include + +#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(¤t->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); +} diff --git a/options-table.c b/options-table.c index 4d646f73..a3767cc5 100644 --- a/options-table.c +++ b/options-table.c @@ -62,7 +62,7 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SERVER, .minimum = 1, .maximum = INT_MAX, - .default_num = 20 + .default_num = 50 }, { .name = "command-alias", @@ -71,7 +71,9 @@ const struct options_table_entry options_table[] = { .default_str = "split-pane=split-window," "splitp=split-window," "server-info=show-messages -JT," - "info=show-messages -JT", + "info=show-messages -JT," + "choose-window=choose-tree -w," + "choose-session=choose-tree -s", .separator = "," }, diff --git a/screen-write.c b/screen-write.c index 1af623b1..9f684bb3 100644 --- a/screen-write.c +++ b/screen-write.c @@ -362,6 +362,9 @@ screen_write_copy(struct screen_write_ctx *ctx, struct screen *src, u_int px, struct grid_cell gc; u_int xx, yy, cx, cy, b; + if (nx == 0 || ny == 0) + return; + cx = s->cx; cy = s->cy; @@ -384,6 +387,121 @@ screen_write_copy(struct screen_write_ctx *ctx, struct screen *src, u_int px, } } +/* Draw a line on screen. */ +void +screen_write_line(struct screen_write_ctx *ctx, u_int nx, int left, int right) +{ + struct screen *s = ctx->s; + struct grid_cell gc; + u_int cx, cy, i; + + cx = s->cx; + cy = s->cy; + + memcpy(&gc, &grid_default_cell, sizeof gc); + gc.attr |= GRID_ATTR_CHARSET; + + screen_write_putc(ctx, &gc, left ? 't' : 'q'); + for (i = 1; i < nx - 1; i++) + screen_write_putc(ctx, &gc, 'q'); + screen_write_putc(ctx, &gc, right ? 'u' : 'q'); + + screen_write_cursormove(ctx, cx, cy); +} + +/* Draw a box on screen. */ +void +screen_write_box(struct screen_write_ctx *ctx, u_int nx, u_int ny) +{ + struct screen *s = ctx->s; + struct grid_cell gc; + u_int cx, cy, i; + + cx = s->cx; + cy = s->cy; + + memcpy(&gc, &grid_default_cell, sizeof gc); + gc.attr |= GRID_ATTR_CHARSET; + + screen_write_putc(ctx, &gc, 'l'); + for (i = 1; i < nx - 1; i++) + screen_write_putc(ctx, &gc, 'q'); + screen_write_putc(ctx, &gc, 'k'); + + screen_write_cursormove(ctx, cx, cy + ny - 1); + screen_write_putc(ctx, &gc, 'm'); + for (i = 1; i < nx - 1; i++) + screen_write_putc(ctx, &gc, 'q'); + screen_write_putc(ctx, &gc, 'j'); + + for (i = 1; i < ny - 1; i++) { + screen_write_cursormove(ctx, cx, cy + i); + screen_write_putc(ctx, &gc, 'x'); + } + for (i = 1; i < ny - 1; i++) { + screen_write_cursormove(ctx, cx + nx - 1, cy + i); + screen_write_putc(ctx, &gc, 'x'); + } + + screen_write_cursormove(ctx, cx, cy); +} + +/* Write a preview version of a window. */ +void +screen_write_preview(struct screen_write_ctx *ctx, struct screen *src, u_int nx, + u_int ny) +{ + struct screen *s = ctx->s; + struct grid_cell gc; + u_int cx, cy, px, py; + + cx = s->cx; + cy = s->cy; + + /* + * If the cursor is on, pick the area around the cursor, otherwise use + * the top left. + */ + if (src->mode & MODE_CURSOR) { + px = src->cx; + if (px < nx / 3) + px = 0; + else + px = px - nx / 3; + if (px + nx > screen_size_x(src)) { + if (nx > screen_size_x(src)) + px = 0; + else + px = screen_size_x(src) - nx; + } + py = src->cy; + if (py < ny / 3) + py = 0; + else + py = py - ny / 3; + if (py + ny > screen_size_y(src)) { + if (ny > screen_size_y(src)) + py = 0; + else + py = screen_size_y(src) - ny; + } + } else { + px = 0; + py = 0; + } + + screen_write_copy(ctx, src, px, src->grid->hsize + py, nx, ny, NULL, + NULL); + + if (src->mode & MODE_CURSOR) { + grid_view_get_cell(src->grid, src->cx, src->cy, &gc); + gc.attr |= GRID_ATTR_REVERSE; + screen_write_cursormove(ctx, cx + (src->cx - px), + cy + (src->cy - py)); + screen_write_cell(ctx, &gc); + } +} + /* Set up context for TTY command. */ static void screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx) diff --git a/tmux.1 b/tmux.1 index 8110ae50..a93dbc43 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1339,136 +1339,76 @@ the end of the visible pane. The default is to capture only the visible contents of the pane. .It Xo .Ic choose-client -.Op Fl F Ar format -.Op Fl t Ar target-window +.Op Fl t Ar target-pane .Op Ar template .Xc -Put a window into client choice mode, allowing a client to be selected -interactively from a list. +Put a pane into client mode, allowing a client to be selected interactively from +a list. +The following keys may be used in client mode: +.Bl -column "Key" "Function" -offset indent +.It Sy "Key" Ta Sy "Function" +.It Li "Enter" Ta "Choose selected client" +.It Li "Up" Ta "Select previous client" +.It Li "Down" Ta "Select next client" +.It Li "t" Ta "Toggle if client is tagged" +.It Li "T" Ta "Tag no clients" +.It Li "C-t" Ta "Tag all clients" +.It Li "d" Ta "Detach selected client" +.It Li "D" Ta "Detach tagged clients" +.It Li "x" Ta "Detach and HUP selected client" +.It Li "X" Ta "Detach and HUP tagged clients" +.It Li "z" Ta "Suspend selected client" +.It Li "Z" Ta "Suspend tagged clients" +.It Li "O" Ta "Change sort order" +.It Li "q" Ta "Exit mode" +.El +.Pp After a client is chosen, .Ql %% -is replaced by the client -.Xr pty 4 -path in +is replaced by the client name in .Ar template and the result executed as a command. If .Ar template is not given, "detach-client -t '%%'" is used. -For the meaning of the -.Fl F -flag, see the -.Sx FORMATS -section. +.Pp This command works only if at least one client is attached. .It Xo -.Ic choose-session -.Op Fl F Ar format -.Op Fl t Ar target-window +.Ic choose-tree +.Op Fl sw +.Op Fl t Ar target-pane .Op Ar template .Xc -Put a window into session choice mode, where a session may be selected +Put a pane into tree mode, where a session, window or pane may be chosen interactively from a list. -When one is chosen, -.Ql %% -is replaced by the session name in -.Ar template -and the result executed as a command. -If -.Ar template -is not given, "switch-client -t '%%'" is used. -For the meaning of the -.Fl F -flag, see the -.Sx FORMATS -section. -This command works only if at least one client is attached. -.It Xo -.Ic choose-tree -.Op Fl suw -.Op Fl b Ar session-template -.Op Fl c Ar window-template -.Op Fl S Ar format -.Op Fl W Ar format -.Op Fl t Ar target-window -.Xc -Put a window into tree choice mode, where either sessions or windows may be -selected interactively from a list. -By default, windows belonging to a session are indented to show their -relationship to a session. -.Pp -Note that the -.Ic choose-window -and -.Ic choose-session -commands are wrappers around -.Ic choose-tree . -.Pp -If .Fl s -is given, will show sessions. -If +starts with sessions collapsed and .Fl w -is given, will show windows. -.Pp -By default, the tree is collapsed and sessions must be expanded to windows -with the right arrow key. -The -.Fl u -option will start with all sessions expanded instead. -.Pp -If -.Fl b -is given, will override the default session command. -Note that -.Ql %% -can be used and will be replaced with the session name. -The default option if not specified is "switch-client -t '%%'". -If -.Fl c -is given, will override the default window command. -Like -.Fl b , -.Ql %% -can be used and will be replaced with the session name and window index. -When a window is chosen from the list, the session command is run before the -window command. -.Pp -.Fl S -uses -.Ar format -instead of the default session -format and -.Fl W -instead of the default window format. -For the meaning of -.Ar format , -see the -.Sx FORMATS -section. +with windows collapsed. +The following keys may be used in tree mode: +.Bl -column "Key" "Function" -offset indent +.It Sy "Key" Ta Sy "Function" +.It Li "Enter" Ta "Choose selected item" +.It Li "Up" Ta "Select previous item" +.It Li "Down" Ta "Select next item" +.It Li "t" Ta "Toggle if item is tagged" +.It Li "T" Ta "Tag no items" +.It Li "C-t" Ta "Tag all items" +.It Li ":" Ta "Run a command for each tagged item" +.It Li "f" Ta "Enter a format to filter items" +.It Li "O" Ta "Change sort order" +.It Li "q" Ta "Exit mode" +.El .Pp -This command works only if at least one client is attached. -.It Xo -.Ic choose-window -.Op Fl F Ar format -.Op Fl t Ar target-window -.Op Ar template -.Xc -Put a window into window choice mode, where a window may be chosen -interactively from a list. -After a window is selected, +After a session, window or pane is chosen, .Ql %% -is replaced by the session name and window index in +is replaced by the target in .Ar template and the result executed as a command. If .Ar template -is not given, "select-window -t '%%'" is used. -For the meaning of the -.Fl F -flag, see the -.Sx FORMATS -section. +is not given, "switch-client -t '%%'" is used. +.Pp This command works only if at least one client is attached. .It Xo .Ic display-panes @@ -1498,8 +1438,7 @@ The default is "select-pane -t '%%'". .It Xo Ic find-window .Op Fl CNT -.Op Fl F Ar format -.Op Fl t Ar target-window +.Op Fl t Ar target-pane .Ar match-string .Xc .D1 (alias: Ic findw ) @@ -1517,13 +1456,7 @@ matches only the window name and matches only the window title. The default is .Fl CNT . -If only one window is matched, it'll be automatically selected, -otherwise a choice list is shown. -For the meaning of the -.Fl F -flag, see the -.Sx FORMATS -section. +.Pp This command works only if at least one client is attached. .It Xo Ic join-pane .Op Fl bdhv @@ -3982,13 +3915,27 @@ The buffer commands are as follows: .Bl -tag -width Ds .It Xo .Ic choose-buffer -.Op Fl F Ar format -.Op Fl t Ar target-window +.Op Fl t Ar target-pane .Op Ar template .Xc -Put a window into buffer choice mode, where a buffer may be chosen -interactively from a list. -After a buffer is selected, +Put a pane into buffer mode, where a buffer may be chosen interactively from +a list. +The following keys may be used in buffer mode: +.Bl -column "Key" "Function" -offset indent +.It Sy "Key" Ta Sy "Function" +.It Li "Enter" Ta "Choose selected buffer" +.It Li "Up" Ta "Select previous buffer" +.It Li "Down" Ta "Select next buffer" +.It Li "t" Ta "Toggle if buffer is tagged" +.It Li "T" Ta "Tag no buffers" +.It Li "C-t" Ta "Tag all buffers" +.It Li "d" Ta "Delete selected buffer" +.It Li "D" Ta "Delete tagged buffers" +.It Li "O" Ta "Change sort order" +.It Li "q" Ta "Exit mode" +.El +.Pp +After a buffer is chosen, .Ql %% is replaced by the buffer name in .Ar template @@ -3996,11 +3943,7 @@ and the result executed as a command. If .Ar template is not given, "paste-buffer -b '%%'" is used. -For the meaning of the -.Fl F -flag, see the -.Sx FORMATS -section. +.Pp This command works only if at least one client is attached. .It Ic clear-history Op Fl t Ar target-pane .D1 (alias: Ic clearhist ) diff --git a/tmux.h b/tmux.h index 3f62683f..eb63d11f 100644 --- a/tmux.h +++ b/tmux.h @@ -40,12 +40,13 @@ extern char **environ; struct args; struct client; +struct cmd_find_state; struct cmdq_item; struct cmdq_list; struct environ; struct format_job_tree; struct input_ctx; -struct mode_key_cmdstr; +struct mode_tree_data; struct mouse_event; struct options; struct options_entry; @@ -693,7 +694,8 @@ struct screen_write_ctx { struct window_mode { const char *name; - struct screen *(*init)(struct window_pane *); + struct screen *(*init)(struct window_pane *, struct cmd_find_state *, + struct args *); void (*free)(struct window_pane *); void (*resize)(struct window_pane *, u_int, u_int); void (*key)(struct window_pane *, struct client *, @@ -1518,6 +1520,7 @@ char *paste_make_sample(struct paste_buffer *); #define FORMAT_PANE 0x80000000U #define FORMAT_WINDOW 0x40000000U struct format_tree; +int format_true(const char *); struct format_tree *format_create(struct client *, struct cmdq_item *, int, int); void format_free(struct format_tree *); @@ -1717,6 +1720,7 @@ void tty_keys_free(struct tty *); key_code tty_keys_next(struct tty *); /* arguments.c */ +void args_set(struct args *, u_char, const char *); struct args *args_parse(const char *, int, char **); void args_free(struct args *); char *args_print(struct args *); @@ -1997,6 +2001,10 @@ void screen_write_putc(struct screen_write_ctx *, const struct grid_cell *, u_char); void screen_write_copy(struct screen_write_ctx *, struct screen *, u_int, u_int, u_int, u_int, bitstr_t *, const struct grid_cell *); +void screen_write_line(struct screen_write_ctx *, u_int, int, int); +void screen_write_box(struct screen_write_ctx *, u_int, u_int); +void screen_write_preview(struct screen_write_ctx *, struct screen *, u_int, + u_int); void screen_write_backspace(struct screen_write_ctx *); void screen_write_mode_set(struct screen_write_ctx *, int); void screen_write_mode_clear(struct screen_write_ctx *, int); @@ -2119,15 +2127,15 @@ void window_pane_unset_palette(struct window_pane *, u_int); void window_pane_reset_palette(struct window_pane *); int window_pane_get_palette(const struct window_pane *, int); int window_pane_set_mode(struct window_pane *, - const struct window_mode *); + const struct window_mode *, struct cmd_find_state *, + struct args *); void window_pane_reset_mode(struct window_pane *); void window_pane_key(struct window_pane *, struct client *, struct session *, key_code, struct mouse_event *); int window_pane_outside(struct window_pane *); int window_pane_visible(struct window_pane *); u_int window_pane_search(struct window_pane *, const char *); -char *window_pane_search_old(struct window_pane *, const char *, - u_int *); + const char *window_printable_flags(struct winlink *); struct window_pane *window_pane_find_up(struct window_pane *); struct window_pane *window_pane_find_down(struct window_pane *); @@ -2176,10 +2184,43 @@ u_int layout_set_select(struct window *, u_int); u_int layout_set_next(struct window *); u_int layout_set_previous(struct window *); +/* mode-tree.c */ +u_int mode_tree_count_tagged(struct mode_tree_data *); +void *mode_tree_get_current(struct mode_tree_data *); +void mode_tree_each_tagged(struct mode_tree_data *, void (*)(void *, void *, + key_code), key_code, int); +void mode_tree_up(struct mode_tree_data *, int); +void mode_tree_down(struct mode_tree_data *, int); +struct mode_tree_data *mode_tree_start(struct window_pane *, + void (*)(void *, u_int, uint64_t *), struct screen *(*)(void *, + void *, u_int, u_int), void *, const char **, u_int, + struct screen **); +void mode_tree_build(struct mode_tree_data *); +void mode_tree_free(struct mode_tree_data *); +void mode_tree_resize(struct mode_tree_data *, u_int, u_int); +struct mode_tree_item *mode_tree_add(struct mode_tree_data *, + struct mode_tree_item *, void *, uint64_t, const char *, + const char *, int); +void mode_tree_remove(struct mode_tree_data *, struct mode_tree_item *); +void mode_tree_draw(struct mode_tree_data *); +int mode_tree_key(struct mode_tree_data *, key_code *, + struct mouse_event *); +void mode_tree_run_command(struct client *, struct cmd_find_state *, + const char *, const char *); + +/* window-buffer.c */ +extern const struct window_mode window_buffer_mode; + +/* window-tree.c */ +extern const struct window_mode window_tree_mode; + /* window-clock.c */ extern const struct window_mode window_clock_mode; extern const char window_clock_table[14][5][5]; +/* window-client.c */ +extern const struct window_mode window_client_mode; + /* window-copy.c */ extern const struct window_mode window_copy_mode; void window_copy_init_from_pane(struct window_pane *, int); @@ -2190,24 +2231,6 @@ void window_copy_pageup(struct window_pane *, int); void window_copy_start_drag(struct client *, struct mouse_event *); int window_copy_scroll_position(struct window_pane *); -/* window-choose.c */ -extern const struct window_mode window_choose_mode; -void window_choose_add(struct window_pane *, - struct window_choose_data *); -void window_choose_ready(struct window_pane *, - u_int, void (*)(struct window_choose_data *)); -struct window_choose_data *window_choose_data_create (int, - struct client *, struct session *); -void window_choose_data_run(struct window_choose_data *); -struct window_choose_data *window_choose_add_window(struct window_pane *, - struct client *, struct session *, struct winlink *, - const char *, const char *, u_int); -struct window_choose_data *window_choose_add_session(struct window_pane *, - struct client *, struct session *, const char *, - const char *, u_int); -void window_choose_expand_all(struct window_pane *); -void window_choose_set_current(struct window_pane *, u_int); - /* names.c */ void check_window_name(struct window *); char *default_window_name(struct window *); diff --git a/window-buffer.c b/window-buffer.c new file mode 100644 index 00000000..f4d4c5f8 --- /dev/null +++ b/window-buffer.c @@ -0,0 +1,342 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2017 Nicholas Marriott + * + * 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