From aad4e4ddb194cba9c01b0ddd696fb7b214e1a7eb Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 30 May 2017 21:44:59 +0000 Subject: [PATCH 1/5] 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 | 217 ++------- cmd-copy-mode.c | 6 +- cmd-find-window.c | 233 ++-------- 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 | 199 +++----- 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, 2434 insertions(+), 1905 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; + struct window_pane *wp = item->target.wp; + const struct window_mode *mode; - 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); + 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; + 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; - if (c == NULL) { - cmdq_error(item, "no client available"); - return (CMD_RETURN_ERROR); - } + C = args_has(args, 'C'); + N = args_has(args, 'N'); + T = args_has(args, 'T'); - if ((template = args_get(args, 'F')) == NULL) - template = FIND_WINDOW_TEMPLATE; + if (!C && !N && !T) + C = N = T = 1; - match_flags = cmd_find_window_match_flags(args); - str = args->argv[0]; + 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); - TAILQ_INIT(&find_list); + new_args = args_parse("", 1, &argv); + args_set(new_args, 'f', filter); - xasprintf(&searchstr, "*%s*", str); - RB_FOREACH(wm, winlinks, &s->windows) - cmd_find_window_match(&find_list, match_flags, wm, str, searchstr); - free(searchstr); + window_pane_set_mode(wp, &window_tree_mode, &item->target, new_args); - if (TAILQ_EMPTY(&find_list)) { - cmdq_error(item, "no windows matching: %s", str); - return (CMD_RETURN_ERROR); - } + args_free(new_args); + free(filter); - 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; - - 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(); - } -} 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, +.Fl s +starts with sessions collapsed and +.Fl w +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 +After a session, window or pane is chosen, .Ql %% -is replaced by the session name in +is replaced by the target 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 -.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. -.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, -.Ql %% -is replaced by the session name and window index 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. 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 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 "tmux.h" + +static struct screen *window_buffer_init(struct window_pane *, + struct cmd_find_state *, struct args *); +static void window_buffer_free(struct window_pane *); +static void window_buffer_resize(struct window_pane *, u_int, + u_int); +static void window_buffer_key(struct window_pane *, + struct client *, struct session *, key_code, + struct mouse_event *); + +#define WINDOW_BUFFER_DEFAULT_COMMAND "paste-buffer -b '%%'" + +const struct window_mode window_buffer_mode = { + .name = "buffer-mode", + + .init = window_buffer_init, + .free = window_buffer_free, + .resize = window_buffer_resize, + .key = window_buffer_key, +}; + +enum window_buffer_sort_type { + WINDOW_BUFFER_BY_NAME, + WINDOW_BUFFER_BY_TIME, + WINDOW_BUFFER_BY_SIZE, +}; +static const char *window_buffer_sort_list[] = { + "name", + "time", + "size" +}; + +struct window_buffer_itemdata { + const char *name; + time_t created; + u_int order; + size_t size; +}; + +struct window_buffer_modedata { + struct mode_tree_data *data; + char *command; + + struct window_buffer_itemdata **item_list; + u_int item_size; +}; + +static struct window_buffer_itemdata * +window_buffer_add_item(struct window_buffer_modedata *data) +{ + struct window_buffer_itemdata *item; + + data->item_list = xreallocarray(data->item_list, data->item_size + 1, + sizeof *data->item_list); + item = data->item_list[data->item_size++] = xcalloc(1, sizeof *item); + return (item); +} + +static void +window_buffer_free_item(struct window_buffer_itemdata *item) +{ + free((void *)item->name); + free(item); +} + +static int +window_buffer_cmp_name(const void *a0, const void *b0) +{ + const struct window_buffer_itemdata *const *a = a0; + const struct window_buffer_itemdata *const *b = b0; + + return (strcmp((*a)->name, (*b)->name)); +} + +static int +window_buffer_cmp_time(const void *a0, const void *b0) +{ + const struct window_buffer_itemdata *const *a = a0; + const struct window_buffer_itemdata *const *b = b0; + + if ((*a)->order > (*b)->order) + return (-1); + if ((*a)->order < (*b)->order) + return (1); + return (strcmp((*a)->name, (*b)->name)); +} + +static int +window_buffer_cmp_size(const void *a0, const void *b0) +{ + const struct window_buffer_itemdata *const *a = a0; + const struct window_buffer_itemdata *const *b = b0; + + if ((*a)->size > (*b)->size) + return (-1); + if ((*a)->size < (*b)->size) + return (1); + return (strcmp((*a)->name, (*b)->name)); +} + +static void +window_buffer_build(void *modedata, u_int sort_type, __unused uint64_t *tag) +{ + struct window_buffer_modedata *data = modedata; + struct window_buffer_itemdata *item; + u_int i; + struct paste_buffer *pb; + char *tim; + char *text; + + for (i = 0; i < data->item_size; i++) + window_buffer_free_item(data->item_list[i]); + free(data->item_list); + data->item_list = NULL; + data->item_size = 0; + + pb = NULL; + while ((pb = paste_walk(pb)) != NULL) { + item = window_buffer_add_item(data); + item->name = xstrdup(paste_buffer_name(pb)); + item->created = paste_buffer_created(pb); + paste_buffer_data(pb, &item->size); + item->order = paste_buffer_order(pb); + } + + switch (sort_type) { + case WINDOW_BUFFER_BY_NAME: + qsort(data->item_list, data->item_size, sizeof *data->item_list, + window_buffer_cmp_name); + break; + case WINDOW_BUFFER_BY_TIME: + qsort(data->item_list, data->item_size, sizeof *data->item_list, + window_buffer_cmp_time); + break; + case WINDOW_BUFFER_BY_SIZE: + qsort(data->item_list, data->item_size, sizeof *data->item_list, + window_buffer_cmp_size); + break; + } + + for (i = 0; i < data->item_size; i++) { + item = data->item_list[i]; + + tim = ctime(&item->created); + *strchr(tim, '\n') = '\0'; + + xasprintf(&text, "%zu bytes (%s)", item->size, tim); + mode_tree_add(data->data, NULL, item, item->order, item->name, + text, -1); + free(text); + } + +} + +static struct screen * +window_buffer_draw(__unused void *modedata, void *itemdata, u_int sx, u_int sy) +{ + struct window_buffer_itemdata *item = itemdata; + struct paste_buffer *pb; + static struct screen s; + struct screen_write_ctx ctx; + char line[1024]; + const char *pdata, *end, *cp; + size_t psize, at; + u_int i; + + pb = paste_get_name(item->name); + if (pb == NULL) + return (NULL); + + screen_init(&s, sx, sy, 0); + + screen_write_start(&ctx, NULL, &s); + screen_write_clearscreen(&ctx, 8); + + pdata = end = paste_buffer_data (pb, &psize); + for (i = 0; i < sy; i++) { + at = 0; + while (end != pdata + psize && *end != '\n') { + if ((sizeof line) - at > 5) { + cp = vis(line + at, *end, VIS_TAB|VIS_OCTAL, 0); + at = cp - line; + } + end++; + } + if (at > sx) + at = sx; + line[at] = '\0'; + + if (*line != '\0') { + screen_write_cursormove(&ctx, 0, i); + screen_write_puts(&ctx, &grid_default_cell, "%s", line); + } + + if (end == pdata + psize) + break; + end++; + } + + screen_write_stop(&ctx); + return (&s); +} + +static struct screen * +window_buffer_init(struct window_pane *wp, __unused struct cmd_find_state *fs, + struct args *args) +{ + struct window_buffer_modedata *data; + struct screen *s; + + wp->modedata = data = xcalloc(1, sizeof *data); + + if (args == NULL || args->argc == 0) + data->command = xstrdup(WINDOW_BUFFER_DEFAULT_COMMAND); + else + data->command = xstrdup(args->argv[0]); + + data->data = mode_tree_start(wp, window_buffer_build, + window_buffer_draw, data, window_buffer_sort_list, + nitems(window_buffer_sort_list), &s); + + mode_tree_build(data->data); + mode_tree_draw(data->data); + + return (s); +} + +static void +window_buffer_free(struct window_pane *wp) +{ + struct window_buffer_modedata *data = wp->modedata; + u_int i; + + if (data == NULL) + return; + + mode_tree_free(data->data); + + for (i = 0; i < data->item_size; i++) + window_buffer_free_item(data->item_list[i]); + free(data->item_list); + + free(data->command); + free(data); +} + +static void +window_buffer_resize(struct window_pane *wp, u_int sx, u_int sy) +{ + struct window_buffer_modedata *data = wp->modedata; + + mode_tree_resize(data->data, sx, sy); +} + +static void +window_buffer_do_delete(void* modedata, void *itemdata, __unused key_code key) +{ + struct window_buffer_modedata *data = modedata; + struct window_buffer_itemdata *item = itemdata; + struct paste_buffer *pb; + + if (item == mode_tree_get_current(data->data)) + mode_tree_down(data->data, 0); + if ((pb = paste_get_name(item->name)) != NULL) + paste_free(pb); +} + +static void +window_buffer_key(struct window_pane *wp, struct client *c, + __unused struct session *s, key_code key, struct mouse_event *m) +{ + struct window_buffer_modedata *data = wp->modedata; + struct window_buffer_itemdata *item; + char *command, *name; + int finished; + + /* + * t = toggle tag + * T = tag none + * C-t = tag all + * q = exit + * O = change sort order + * + * d = delete buffer + * D = delete tagged buffers + * Enter = paste buffer + */ + + finished = mode_tree_key(data->data, &key, m); + switch (key) { + case 'd': + item = mode_tree_get_current(data->data); + window_buffer_do_delete(data, item, key); + mode_tree_build(data->data); + break; + case 'D': + mode_tree_each_tagged(data->data, window_buffer_do_delete, key, + 0); + mode_tree_build(data->data); + break; + case '\r': + item = mode_tree_get_current(data->data); + command = xstrdup(data->command); + name = xstrdup(item->name); + window_pane_reset_mode(wp); + mode_tree_run_command(c, NULL, command, name); + free(name); + free(command); + return; + } + if (finished || paste_get_top(NULL) == NULL) + window_pane_reset_mode(wp); + else { + mode_tree_draw(data->data); + wp->flags |= PANE_REDRAW; + } +} diff --git a/window-choose.c b/window-choose.c deleted file mode 100644 index 2f3a1631..00000000 --- a/window-choose.c +++ /dev/null @@ -1,1078 +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 - -#include "tmux.h" - -static struct screen *window_choose_init(struct window_pane *); -static void window_choose_free(struct window_pane *); -static void window_choose_resize(struct window_pane *, u_int, u_int); -static void window_choose_key(struct window_pane *, struct client *, - struct session *, key_code, struct mouse_event *); - -static void window_choose_default_callback(struct window_choose_data *); -static struct window_choose_mode_item *window_choose_get_item( - struct window_pane *, key_code, struct mouse_event *); - -static void window_choose_fire_callback(struct window_pane *, - struct window_choose_data *); -static void window_choose_redraw_screen(struct window_pane *); -static void window_choose_write_line(struct window_pane *, - struct screen_write_ctx *, u_int); - -static void window_choose_scroll_up(struct window_pane *); -static void window_choose_scroll_down(struct window_pane *); - -static void window_choose_collapse(struct window_pane *, struct session *, - u_int); -static void window_choose_expand(struct window_pane *, struct session *, - u_int); -static void window_choose_collapse_all(struct window_pane *); - -static void window_choose_data_free(struct window_choose_data *); - -enum window_choose_input_type { - WINDOW_CHOOSE_NORMAL = -1, - WINDOW_CHOOSE_GOTO_ITEM, -}; - -const struct window_mode window_choose_mode = { - .name = "choose-mode", - - .init = window_choose_init, - .free = window_choose_free, - .resize = window_choose_resize, - .key = window_choose_key, -}; - -struct window_choose_mode_item { - struct window_choose_data *wcd; - char *name; - int pos; - int state; -#define TREE_EXPANDED 0x1 -}; - -struct window_choose_mode_data { - struct screen screen; - - struct window_choose_mode_item *list; - u_int list_size; - struct window_choose_mode_item *old_list; - u_int old_list_size; - - int width; - u_int top; - u_int selected; - enum window_choose_input_type input_type; - const char *input_prompt; - char *input_str; - - void (*callbackfn)(struct window_choose_data *); -}; - -static const char window_choose_keys_emacs[] = "0123456789" - "abcdefghijklmnoprstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; -static const char window_choose_keys_vi[] = "0123456789" - "abcdefimnoprstuvwxyz" - "ABCDEFIJKMNOPQRSTUVWXYZ"; - -static void window_choose_free1(struct window_choose_mode_data *); -static int window_choose_key_index(struct window_pane *, u_int); -static int window_choose_index_key(struct window_pane *, key_code); -static void window_choose_prompt_input(enum window_choose_input_type, - const char *, struct window_pane *, key_code); -static void window_choose_reset_top(struct window_pane *, u_int); - -void -window_choose_add(struct window_pane *wp, struct window_choose_data *wcd) -{ - struct window_choose_mode_data *data = wp->modedata; - struct window_choose_mode_item *item; - char tmp[11]; - - data->list = xreallocarray(data->list, data->list_size + 1, - sizeof *data->list); - item = &data->list[data->list_size++]; - - item->name = format_expand(wcd->ft, wcd->ft_template); - item->wcd = wcd; - item->pos = data->list_size - 1; - item->state = 0; - - data->width = xsnprintf(tmp, sizeof tmp, "%d", item->pos); -} - -void -window_choose_set_current(struct window_pane *wp, u_int cur) -{ - struct window_choose_mode_data *data = wp->modedata; - struct screen *s = &data->screen; - - data->selected = cur; - window_choose_reset_top(wp, screen_size_y(s)); -} - -static void -window_choose_reset_top(struct window_pane *wp, u_int sy) -{ - struct window_choose_mode_data *data = wp->modedata; - - data->top = 0; - if (data->selected > sy - 1) - data->top = data->selected - (sy - 1); - - window_choose_redraw_screen(wp); -} - -void -window_choose_ready(struct window_pane *wp, u_int cur, - void (*callbackfn)(struct window_choose_data *)) -{ - struct window_choose_mode_data *data = wp->modedata; - u_int size; - - data->callbackfn = callbackfn; - if (data->callbackfn == NULL) - data->callbackfn = window_choose_default_callback; - - size = data->old_list_size; - data->old_list_size += data->list_size; - data->old_list = xreallocarray(data->old_list, data->old_list_size, - sizeof *data->old_list); - memcpy(data->old_list + size, data->list, data->list_size * - sizeof *data->list); - - window_choose_set_current(wp, cur); - window_choose_collapse_all(wp); -} - -static struct screen * -window_choose_init(struct window_pane *wp) -{ - struct window_choose_mode_data *data; - struct screen *s; - - wp->modedata = data = xcalloc(1, sizeof *data); - - data->callbackfn = NULL; - data->input_type = WINDOW_CHOOSE_NORMAL; - data->input_str = xstrdup(""); - data->input_prompt = NULL; - - data->list = NULL; - data->list_size = 0; - - data->old_list = NULL; - data->old_list_size = 0; - - data->top = 0; - - s = &data->screen; - screen_init(s, screen_size_x(&wp->base), screen_size_y(&wp->base), 0); - s->mode &= ~MODE_CURSOR; - - return (s); -} - -struct window_choose_data * -window_choose_data_create(int type, struct client *c, struct session *s) -{ - struct window_choose_data *wcd; - - wcd = xmalloc(sizeof *wcd); - wcd->type = type; - - wcd->ft = format_create(c, NULL, FORMAT_NONE, 0); - wcd->ft_template = NULL; - - wcd->command = NULL; - - wcd->wl = NULL; - wcd->pane_id = -1; - wcd->idx = -1; - - wcd->tree_session = NULL; - - wcd->start_client = c; - wcd->start_client->references++; - wcd->start_session = s; - wcd->start_session->references++; - - return (wcd); -} - -static void -window_choose_data_free(struct window_choose_data *wcd) -{ - server_client_unref(wcd->start_client); - session_remove_ref(wcd->start_session, __func__); - - if (wcd->tree_session != NULL) - session_remove_ref(wcd->tree_session, __func__); - - free(wcd->ft_template); - format_free(wcd->ft); - - free(wcd->command); - free(wcd); -} - -void -window_choose_data_run(struct window_choose_data *cdata) -{ - struct cmd_list *cmdlist; - char *cause; - struct cmdq_item *item; - - /* - * The command template will have already been replaced. But if it's - * NULL, bail here. - */ - if (cdata->command == NULL) - return; - - cmdlist = cmd_string_parse(cdata->command, NULL, 0, &cause); - if (cmdlist == NULL) { - if (cause != NULL) { - *cause = toupper((u_char) *cause); - status_message_set(cdata->start_client, "%s", cause); - free(cause); - } - return; - } - - item = cmdq_get_command(cmdlist, NULL, NULL, 0); - cmdq_append(cdata->start_client, item); - cmd_list_free(cmdlist); -} - -static void -window_choose_default_callback(struct window_choose_data *wcd) -{ - if (wcd == NULL) - return; - if (wcd->start_client->flags & CLIENT_DEAD) - return; - - window_choose_data_run(wcd); -} - -static void -window_choose_free(struct window_pane *wp) -{ - if (wp->modedata != NULL) - window_choose_free1(wp->modedata); -} - -static void -window_choose_free1(struct window_choose_mode_data *data) -{ - struct window_choose_mode_item *item; - u_int i; - - if (data == NULL) - return; - - for (i = 0; i < data->old_list_size; i++) { - item = &data->old_list[i]; - window_choose_data_free(item->wcd); - free(item->name); - } - free(data->list); - free(data->old_list); - - free(data->input_str); - - screen_free(&data->screen); - free(data); -} - -static void -window_choose_resize(struct window_pane *wp, u_int sx, u_int sy) -{ - struct window_choose_mode_data *data = wp->modedata; - struct screen *s = &data->screen; - - window_choose_reset_top(wp, sy); - screen_resize(s, sx, sy, 0); - window_choose_redraw_screen(wp); -} - -static void -window_choose_fire_callback(struct window_pane *wp, - struct window_choose_data *wcd) -{ - struct window_choose_mode_data *data = wp->modedata; - - wp->modedata = NULL; - window_pane_reset_mode(wp); - - data->callbackfn(wcd); - - window_choose_free1(data); -} - -static void -window_choose_prompt_input(enum window_choose_input_type input_type, - const char *prompt, struct window_pane *wp, key_code key) -{ - struct window_choose_mode_data *data = wp->modedata; - size_t input_len; - - data->input_type = input_type; - data->input_prompt = prompt; - input_len = strlen(data->input_str) + 2; - - data->input_str = xrealloc(data->input_str, input_len); - data->input_str[input_len - 2] = key; - data->input_str[input_len - 1] = '\0'; - - window_choose_redraw_screen(wp); -} - -static void -window_choose_collapse(struct window_pane *wp, struct session *s, u_int pos) -{ - struct window_choose_mode_data *data = wp->modedata; - struct window_choose_mode_item *item, *chosen, *copy = NULL; - struct window_choose_data *wcd; - u_int i, copy_size = 0; - - chosen = &data->list[pos]; - chosen->state &= ~TREE_EXPANDED; - - /* - * Trying to mangle the &data->list in-place has lots of problems, so - * assign the actual result we want to render and copy the new one over - * the top of it. - */ - for (i = 0; i < data->list_size; i++) { - item = &data->list[i]; - wcd = item->wcd; - - if (s == wcd->tree_session) { - /* We only show the session when collapsed. */ - if (wcd->type & TREE_SESSION) { - item->state &= ~TREE_EXPANDED; - - copy = xreallocarray(copy, copy_size + 1, - sizeof *copy); - memcpy(©[copy_size], item, sizeof *copy); - copy_size++; - - /* - * Update the selection to this session item so - * we don't end up highlighting a non-existent - * item. - */ - data->selected = i; - } - } else { - copy = xreallocarray(copy, copy_size + 1, sizeof *copy); - memcpy(©[copy_size], item, sizeof *copy); - copy_size++; - } - } - - if (copy_size != 0) { - free(data->list); - data->list = copy; - data->list_size = copy_size; - } -} - -static void -window_choose_collapse_all(struct window_pane *wp) -{ - struct window_choose_mode_data *data = wp->modedata; - struct window_choose_mode_item *item; - struct screen *scr = &data->screen; - struct session *s, *chosen; - u_int i; - - chosen = data->list[data->selected].wcd->start_session; - - RB_FOREACH(s, sessions, &sessions) - window_choose_collapse(wp, s, data->selected); - - /* Reset the selection back to the starting session. */ - for (i = 0; i < data->list_size; i++) { - item = &data->list[i]; - - if (chosen != item->wcd->tree_session) - continue; - - if (item->wcd->type & TREE_SESSION) - data->selected = i; - } - window_choose_reset_top(wp, screen_size_y(scr)); -} - -void -window_choose_expand_all(struct window_pane *wp) -{ - struct window_choose_mode_data *data = wp->modedata; - struct window_choose_mode_item *item; - struct screen *scr = &data->screen; - struct session *s; - u_int i; - - RB_FOREACH(s, sessions, &sessions) { - for (i = 0; i < data->list_size; i++) { - item = &data->list[i]; - - if (s != item->wcd->tree_session) - continue; - - if (item->wcd->type & TREE_SESSION) - window_choose_expand(wp, s, i); - } - } - - window_choose_reset_top(wp, screen_size_y(scr)); -} - -static void -window_choose_expand(struct window_pane *wp, struct session *s, u_int pos) -{ - struct window_choose_mode_data *data = wp->modedata; - struct window_choose_mode_item *item, *chosen; - struct window_choose_data *wcd; - u_int i, items; - - chosen = &data->list[pos]; - items = data->old_list_size - 1; - - /* It's not possible to expand anything other than sessions. */ - if (!(chosen->wcd->type & TREE_SESSION)) - return; - - /* Don't re-expand a session which is already expanded. */ - if (chosen->state & TREE_EXPANDED) - return; - - /* Mark the session entry as expanded. */ - chosen->state |= TREE_EXPANDED; - - /* - * Go back through the original list of all sessions and windows, and - * pull out the windows where the session matches the selection chosen - * to expand. - */ - for (i = items; i > 0; i--) { - item = &data->old_list[i]; - item->state |= TREE_EXPANDED; - wcd = item->wcd; - - if (s == wcd->tree_session) { - /* - * Since the session is already displayed, we only care - * to add back in window for it. - */ - if (wcd->type & TREE_WINDOW) { - /* - * If the insertion point for adding the - * windows to the session falls inside the - * range of the list, then we insert these - * entries in order *AFTER* the selected - * session. - */ - if (pos < i) { - data->list = xreallocarray(data->list, - data->list_size + 1, - sizeof *data->list); - memmove(&data->list[pos + 2], - &data->list[pos + 1], - (data->list_size - (pos + 1)) * - sizeof *data->list); - memcpy(&data->list[pos + 1], - &data->old_list[i], - sizeof *data->list); - data->list_size++; - } else { - /* Ran out of room, add to the end. */ - data->list = xreallocarray(data->list, - data->list_size + 1, - sizeof *data->list); - memcpy(&data->list[data->list_size], - &data->old_list[i], - sizeof *data->list); - data->list_size++; - } - } - } - } -} - -static struct window_choose_mode_item * -window_choose_get_item(struct window_pane *wp, key_code key, - struct mouse_event *m) -{ - struct window_choose_mode_data *data = wp->modedata; - u_int x, y, idx; - - if (!KEYC_IS_MOUSE(key)) - return (&data->list[data->selected]); - - if (cmd_mouse_at(wp, m, &x, &y, 0) != 0) - return (NULL); - - idx = data->top + y; - if (idx >= data->list_size) - return (NULL); - return (&data->list[idx]); -} - -static key_code -window_choose_translate_key(key_code key) -{ - switch (key) { - case '0'|KEYC_ESCAPE: - case '1'|KEYC_ESCAPE: - case '2'|KEYC_ESCAPE: - case '3'|KEYC_ESCAPE: - case '4'|KEYC_ESCAPE: - case '5'|KEYC_ESCAPE: - case '6'|KEYC_ESCAPE: - case '7'|KEYC_ESCAPE: - case '8'|KEYC_ESCAPE: - case '9'|KEYC_ESCAPE: - case '\003': /* C-c */ - case 'q': - case '\n': - case '\r': - case KEYC_BSPACE: - case ' ': - case KEYC_LEFT|KEYC_CTRL: - case KEYC_RIGHT|KEYC_CTRL: - case KEYC_MOUSEDOWN1_PANE: - case KEYC_MOUSEDOWN3_PANE: - case KEYC_WHEELUP_PANE: - case KEYC_WHEELDOWN_PANE: - return (key); - case '\031': /* C-y */ - case KEYC_UP|KEYC_CTRL: - return (KEYC_UP|KEYC_CTRL); - case '\002': /* C-b */ - case KEYC_PPAGE: - return (KEYC_PPAGE); - case '\005': /* C-e */ - case KEYC_DOWN|KEYC_CTRL: - return (KEYC_DOWN|KEYC_CTRL); - case '\006': /* C-f */ - case KEYC_NPAGE: - return (KEYC_NPAGE); - case 'h': - case KEYC_LEFT: - return (KEYC_LEFT); - case 'j': - case KEYC_DOWN: - return (KEYC_DOWN); - case 'k': - case KEYC_UP: - return (KEYC_UP); - case 'l': - case KEYC_RIGHT: - return (KEYC_RIGHT); - case 'g': - case KEYC_HOME: - return (KEYC_HOME); - case 'G': - case KEYC_END: - return (KEYC_END); - case 'H': - return ('R'|KEYC_ESCAPE); - case 'L': - return ('r'|KEYC_ESCAPE); - } - if ((key >= '0' && key <= '9') || - (key >= 'a' && key <= 'z') || - (key >= 'A' && key <= 'Z')) - return (key); - return (KEYC_NONE); -} - -static void -window_choose_key(struct window_pane *wp, __unused struct client *c, - __unused struct session *sp, key_code key, struct mouse_event *m) -{ - struct window_choose_mode_data *data = wp->modedata; - struct screen *s = &data->screen; - struct screen_write_ctx ctx; - struct window_choose_mode_item *item; - size_t input_len; - u_int items, n; - int idx, keys; - - keys = options_get_number(wp->window->options, "mode-keys"); - if (keys == MODEKEY_VI) { - key = window_choose_translate_key(key); - if (key == KEYC_NONE) - return; - } - items = data->list_size; - - if (data->input_type == WINDOW_CHOOSE_GOTO_ITEM) { - switch (key) { - case '\003': /* C-c */ - case '\033': /* Escape */ - case 'q': - data->input_type = WINDOW_CHOOSE_NORMAL; - window_choose_redraw_screen(wp); - break; - case '\n': - case '\r': - n = strtonum(data->input_str, 0, INT_MAX, NULL); - if (n > items - 1) { - data->input_type = WINDOW_CHOOSE_NORMAL; - window_choose_redraw_screen(wp); - break; - } - window_choose_fire_callback(wp, data->list[n].wcd); - break; - case KEYC_BSPACE: - input_len = strlen(data->input_str); - if (input_len > 0) - data->input_str[input_len - 1] = '\0'; - window_choose_redraw_screen(wp); - break; - default: - if (key < '0' || key > '9') - break; - window_choose_prompt_input(WINDOW_CHOOSE_GOTO_ITEM, - "Goto Item", wp, key); - break; - } - return; - } - - switch (key) { - case '\003': /* C-c */ - case '\033': /* Escape */ - case 'q': - window_choose_fire_callback(wp, NULL); - break; - case '\n': - case '\r': - case KEYC_MOUSEDOWN1_PANE: - if ((item = window_choose_get_item(wp, key, m)) == NULL) - break; - window_choose_fire_callback(wp, item->wcd); - break; - case ' ': - case KEYC_MOUSEDOWN3_PANE: - if ((item = window_choose_get_item(wp, key, m)) == NULL) - break; - if (item->state & TREE_EXPANDED) { - window_choose_collapse(wp, item->wcd->tree_session, - data->selected); - } else { - window_choose_expand(wp, item->wcd->tree_session, - data->selected); - } - window_choose_redraw_screen(wp); - break; - case KEYC_LEFT: - if ((item = window_choose_get_item(wp, key, m)) == NULL) - break; - if (item->state & TREE_EXPANDED) { - window_choose_collapse(wp, item->wcd->tree_session, - data->selected); - window_choose_redraw_screen(wp); - } - break; - case KEYC_LEFT|KEYC_CTRL: - window_choose_collapse_all(wp); - break; - case KEYC_RIGHT: - if ((item = window_choose_get_item(wp, key, m)) == NULL) - break; - if (!(item->state & TREE_EXPANDED)) { - window_choose_expand(wp, item->wcd->tree_session, - data->selected); - window_choose_redraw_screen(wp); - } - break; - case KEYC_RIGHT|KEYC_CTRL: - window_choose_expand_all(wp); - break; - case '\020': /* C-p */ - case KEYC_UP: - case KEYC_WHEELUP_PANE: - if (items == 0) - break; - if (data->selected == 0) { - data->selected = items - 1; - if (data->selected > screen_size_y(s) - 1) - data->top = items - screen_size_y(s); - window_choose_redraw_screen(wp); - break; - } - data->selected--; - if (data->selected < data->top) - window_choose_scroll_up(wp); - else { - screen_write_start(&ctx, wp, NULL); - window_choose_write_line(wp, &ctx, - data->selected - data->top); - window_choose_write_line(wp, &ctx, - data->selected + 1 - data->top); - screen_write_stop(&ctx); - } - break; - case '\016': /* C-n */ - case KEYC_DOWN: - case KEYC_WHEELDOWN_PANE: - if (items == 0) - break; - if (data->selected == items - 1) { - data->selected = 0; - data->top = 0; - window_choose_redraw_screen(wp); - break; - } - data->selected++; - - if (data->selected < data->top + screen_size_y(s)) { - screen_write_start(&ctx, wp, NULL); - window_choose_write_line(wp, &ctx, - data->selected - data->top); - window_choose_write_line(wp, &ctx, - data->selected - 1 - data->top); - screen_write_stop(&ctx); - } else - window_choose_scroll_down(wp); - break; - case KEYC_UP|KEYC_CTRL: - if (items == 0 || data->top == 0) - break; - if (data->selected == data->top + screen_size_y(s) - 1) { - data->selected--; - window_choose_scroll_up(wp); - screen_write_start(&ctx, wp, NULL); - window_choose_write_line(wp, &ctx, - screen_size_y(s) - 1); - screen_write_stop(&ctx); - } else - window_choose_scroll_up(wp); - break; - case KEYC_DOWN|KEYC_CTRL: - if (items == 0 || - data->top + screen_size_y(&data->screen) >= items) - break; - if (data->selected == data->top) { - data->selected++; - window_choose_scroll_down(wp); - screen_write_start(&ctx, wp, NULL); - window_choose_write_line(wp, &ctx, 0); - screen_write_stop(&ctx); - } else - window_choose_scroll_down(wp); - break; - case KEYC_PPAGE: - if (data->selected < screen_size_y(s)) { - data->selected = 0; - data->top = 0; - } else { - data->selected -= screen_size_y(s); - if (data->top < screen_size_y(s)) - data->top = 0; - else - data->top -= screen_size_y(s); - } - window_choose_redraw_screen(wp); - break; - case KEYC_NPAGE: - data->selected += screen_size_y(s); - if (data->selected > items - 1) - data->selected = items - 1; - data->top += screen_size_y(s); - if (screen_size_y(s) < items) { - if (data->top + screen_size_y(s) > items) - data->top = items - screen_size_y(s); - } else - data->top = 0; - if (data->selected < data->top) - data->top = data->selected; - window_choose_redraw_screen(wp); - break; - case KEYC_BSPACE: - input_len = strlen(data->input_str); - if (input_len > 0) - data->input_str[input_len - 1] = '\0'; - window_choose_redraw_screen(wp); - break; - case '0'|KEYC_ESCAPE: - case '1'|KEYC_ESCAPE: - case '2'|KEYC_ESCAPE: - case '3'|KEYC_ESCAPE: - case '4'|KEYC_ESCAPE: - case '5'|KEYC_ESCAPE: - case '6'|KEYC_ESCAPE: - case '7'|KEYC_ESCAPE: - case '8'|KEYC_ESCAPE: - case '9'|KEYC_ESCAPE: - key &= KEYC_MASK_KEY; - if (key < '0' || key > '9') - break; - window_choose_prompt_input(WINDOW_CHOOSE_GOTO_ITEM, - "Goto Item", wp, key); - break; - case KEYC_HOME: - case '<'|KEYC_ESCAPE: - data->selected = 0; - data->top = 0; - window_choose_redraw_screen(wp); - break; - case 'R'|KEYC_ESCAPE: - data->selected = data->top; - window_choose_redraw_screen(wp); - break; - case 'r'|KEYC_ESCAPE: - data->selected = data->top + screen_size_y(s) - 1; - if (data->selected > items - 1) - data->selected = items - 1; - window_choose_redraw_screen(wp); - break; - case KEYC_END: - case '>'|KEYC_ESCAPE: - data->selected = items - 1; - if (screen_size_y(s) < items) - data->top = items - screen_size_y(s); - else - data->top = 0; - window_choose_redraw_screen(wp); - break; - default: - idx = window_choose_index_key(wp, key); - if (idx < 0 || (u_int) idx >= data->list_size) - break; - data->selected = idx; - window_choose_fire_callback(wp, data->list[idx].wcd); - break; - } -} - -static void -window_choose_write_line(struct window_pane *wp, struct screen_write_ctx *ctx, - u_int py) -{ - struct window_choose_mode_data *data = wp->modedata; - struct window_choose_mode_item *item; - struct options *oo = wp->window->options; - struct screen *s = &data->screen; - struct grid_cell gc; - size_t last, xoff = 0; - char hdr[32], label[32]; - int key; - - if (data->callbackfn == NULL) - fatalx("called before callback assigned"); - - last = screen_size_y(s) - 1; - memcpy(&gc, &grid_default_cell, sizeof gc); - gc.flags |= GRID_FLAG_NOPALETTE; - if (data->selected == data->top + py) - style_apply(&gc, oo, "mode-style"); - - screen_write_cursormove(ctx, 0, py); - if (data->top + py < data->list_size) { - item = &data->list[data->top + py]; - if (item->wcd->wl != NULL && - item->wcd->wl->flags & WINLINK_ALERTFLAGS) - gc.attr |= GRID_ATTR_BRIGHT; - - key = window_choose_key_index(wp, data->top + py); - if (key != -1) - xsnprintf(label, sizeof label, "(%c)", key); - else - xsnprintf(label, sizeof label, "(%d)", item->pos); - screen_write_nputs(ctx, screen_size_x(s) - 1, &gc, - "%*s %s %s", data->width + 2, label, - /* - * Add indication to tree if necessary about whether it's - * expanded or not. - */ - (item->wcd->type & TREE_SESSION) ? - ((item->state & TREE_EXPANDED) ? "-" : "+") : "", item->name); - } - while (s->cx < screen_size_x(s) - 1) - screen_write_putc(ctx, &gc, ' '); - - if (data->input_type != WINDOW_CHOOSE_NORMAL) { - style_apply(&gc, oo, "mode-style"); - - xoff = xsnprintf(hdr, sizeof hdr, - "%s: %s", data->input_prompt, data->input_str); - screen_write_cursormove(ctx, 0, last); - screen_write_puts(ctx, &gc, "%s", hdr); - screen_write_cursormove(ctx, xoff, py); - memcpy(&gc, &grid_default_cell, sizeof gc); - } - -} - -static int -window_choose_key_index(struct window_pane *wp, u_int idx) -{ - const char *ptr; - int keys; - - keys = options_get_number(wp->window->options, "mode-keys"); - if (keys == MODEKEY_VI) - ptr = window_choose_keys_vi; - else - ptr = window_choose_keys_emacs; - for (; *ptr != '\0'; ptr++) { - if (idx-- == 0) - return (*ptr); - } - return (-1); -} - -static int -window_choose_index_key(struct window_pane *wp, key_code key) -{ - const char *ptr; - int keys; - u_int idx = 0; - - keys = options_get_number(wp->window->options, "mode-keys"); - if (keys == MODEKEY_VI) - ptr = window_choose_keys_vi; - else - ptr = window_choose_keys_emacs; - for (; *ptr != '\0'; ptr++) { - if (key == (key_code)*ptr) - return (idx); - idx++; - } - return (-1); -} - -static void -window_choose_redraw_screen(struct window_pane *wp) -{ - struct window_choose_mode_data *data = wp->modedata; - struct screen *s = &data->screen; - struct screen_write_ctx ctx; - u_int i; - - screen_write_start(&ctx, wp, NULL); - for (i = 0; i < screen_size_y(s); i++) - window_choose_write_line(wp, &ctx, i); - screen_write_stop(&ctx); -} - -static void -window_choose_scroll_up(struct window_pane *wp) -{ - struct window_choose_mode_data *data = wp->modedata; - struct screen_write_ctx ctx; - - if (data->top == 0) - return; - data->top--; - - screen_write_start(&ctx, wp, NULL); - screen_write_cursormove(&ctx, 0, 0); - screen_write_insertline(&ctx, 1, 8); - window_choose_write_line(wp, &ctx, 0); - if (screen_size_y(&data->screen) > 1) - window_choose_write_line(wp, &ctx, 1); - screen_write_stop(&ctx); -} - -static void -window_choose_scroll_down(struct window_pane *wp) -{ - struct window_choose_mode_data *data = wp->modedata; - struct screen *s = &data->screen; - struct screen_write_ctx ctx; - - if (data->top >= data->list_size) - return; - data->top++; - - screen_write_start(&ctx, wp, NULL); - screen_write_cursormove(&ctx, 0, 0); - screen_write_deleteline(&ctx, 1, 8); - window_choose_write_line(wp, &ctx, screen_size_y(s) - 1); - if (screen_size_y(&data->screen) > 1) - window_choose_write_line(wp, &ctx, screen_size_y(s) - 2); - screen_write_stop(&ctx); -} - -struct window_choose_data * -window_choose_add_session(struct window_pane *wp, struct client *c, - struct session *s, const char *template, const char *action, u_int idx) -{ - struct window_choose_data *wcd; - - wcd = window_choose_data_create(TREE_SESSION, c, c->session); - wcd->idx = s->id; - - wcd->tree_session = s; - wcd->tree_session->references++; - - wcd->ft_template = xstrdup(template); - format_add(wcd->ft, "line", "%u", idx); - format_defaults(wcd->ft, NULL, s, NULL, NULL); - - wcd->command = cmd_template_replace(action, s->name, 1); - - window_choose_add(wp, wcd); - - return (wcd); -} - -struct window_choose_data * -window_choose_add_window(struct window_pane *wp, struct client *c, - struct session *s, struct winlink *wl, const char *template, - const char *action, u_int idx) -{ - struct window_choose_data *wcd; - char *expanded; - - wcd = window_choose_data_create(TREE_WINDOW, c, c->session); - wcd->idx = wl->idx; - - wcd->wl = wl; - - wcd->tree_session = s; - wcd->tree_session->references++; - - wcd->ft_template = xstrdup(template); - format_add(wcd->ft, "line", "%u", idx); - format_defaults(wcd->ft, NULL, s, wl, NULL); - - xasprintf(&expanded, "%s:%d", s->name, wl->idx); - wcd->command = cmd_template_replace(action, expanded, 1); - free(expanded); - - window_choose_add(wp, wcd); - - return (wcd); -} diff --git a/window-client.c b/window-client.c new file mode 100644 index 00000000..4d720960 --- /dev/null +++ b/window-client.c @@ -0,0 +1,335 @@ +/* $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 "tmux.h" + +static struct screen *window_client_init(struct window_pane *, + struct cmd_find_state *, struct args *); +static void window_client_free(struct window_pane *); +static void window_client_resize(struct window_pane *, u_int, + u_int); +static void window_client_key(struct window_pane *, + struct client *, struct session *, key_code, + struct mouse_event *); + +#define WINDOW_CLIENT_DEFAULT_COMMAND "detach-client -t '%%'" + +const struct window_mode window_client_mode = { + .name = "client-mode", + + .init = window_client_init, + .free = window_client_free, + .resize = window_client_resize, + .key = window_client_key, +}; + +enum window_client_sort_type { + WINDOW_CLIENT_BY_NAME, + WINDOW_CLIENT_BY_CREATION_TIME, + WINDOW_CLIENT_BY_ACTIVITY_TIME, +}; +static const char *window_client_sort_list[] = { + "name", + "creation time", + "activity time" +}; + +struct window_client_itemdata { + struct client *c; +}; + +struct window_client_modedata { + struct mode_tree_data *data; + char *command; + + struct window_client_itemdata **item_list; + u_int item_size; +}; + +static struct window_client_itemdata * +window_client_add_item(struct window_client_modedata *data) +{ + struct window_client_itemdata *item; + + data->item_list = xreallocarray(data->item_list, data->item_size + 1, + sizeof *data->item_list); + item = data->item_list[data->item_size++] = xcalloc(1, sizeof *item); + return (item); +} + +static void +window_client_free_item(struct window_client_itemdata *item) +{ + server_client_unref(item->c); + free(item); +} + +static int +window_client_cmp_name(const void *a0, const void *b0) +{ + const struct window_client_itemdata *const *a = a0; + const struct window_client_itemdata *const *b = b0; + + return (strcmp((*a)->c->name, (*b)->c->name)); +} + +static int +window_client_cmp_creation_time(const void *a0, const void *b0) +{ + const struct window_client_itemdata *const *a = a0; + const struct window_client_itemdata *const *b = b0; + + if (timercmp(&(*a)->c->creation_time, &(*b)->c->creation_time, >)) + return (-1); + if (timercmp(&(*a)->c->creation_time, &(*b)->c->creation_time, <)) + return (1); + return (0); +} + +static int +window_client_cmp_activity_time(const void *a0, const void *b0) +{ + const struct window_client_itemdata *const *a = a0; + const struct window_client_itemdata *const *b = b0; + + if (timercmp(&(*a)->c->activity_time, &(*b)->c->activity_time, >)) + return (-1); + if (timercmp(&(*a)->c->activity_time, &(*b)->c->activity_time, <)) + return (1); + return (0); +} + +static void +window_client_build(void *modedata, u_int sort_type, __unused uint64_t *tag) +{ + struct window_client_modedata *data = modedata; + struct window_client_itemdata *item; + u_int i; + struct client *c; + char *tim; + char *text; + + for (i = 0; i < data->item_size; i++) + window_client_free_item(data->item_list[i]); + free(data->item_list); + data->item_list = NULL; + data->item_size = 0; + + TAILQ_FOREACH(c, &clients, entry) { + if (c->session == NULL || (c->flags & (CLIENT_DETACHING))) + continue; + + item = window_client_add_item(data); + item->c = c; + + c->references++; + } + + switch (sort_type) { + case WINDOW_CLIENT_BY_NAME: + qsort(data->item_list, data->item_size, sizeof *data->item_list, + window_client_cmp_name); + break; + case WINDOW_CLIENT_BY_CREATION_TIME: + qsort(data->item_list, data->item_size, sizeof *data->item_list, + window_client_cmp_creation_time); + break; + case WINDOW_CLIENT_BY_ACTIVITY_TIME: + qsort(data->item_list, data->item_size, sizeof *data->item_list, + window_client_cmp_activity_time); + break; + } + + for (i = 0; i < data->item_size; i++) { + item = data->item_list[i]; + c = item->c; + + tim = ctime(&c->activity_time.tv_sec); + *strchr(tim, '\n') = '\0'; + + xasprintf(&text, "session %s (%s)", c->session->name, tim); + mode_tree_add(data->data, NULL, item, (uint64_t)c, c->name, + text, -1); + free(text); + } +} + +static struct screen * +window_client_draw(__unused void *modedata, void *itemdata, u_int sx, u_int sy) +{ + struct window_client_itemdata *item = itemdata; + struct client *c = item->c; + struct window_pane *wp; + static struct screen s; + struct screen_write_ctx ctx; + + if (c->session == NULL || (c->flags & (CLIENT_DEAD|CLIENT_DETACHING))) + return (NULL); + wp = c->session->curw->window->active; + + screen_init(&s, sx, sy, 0); + + screen_write_start(&ctx, NULL, &s); + screen_write_clearscreen(&ctx, 8); + + screen_write_preview(&ctx, &wp->base, sx, sy - 3); + + screen_write_cursormove(&ctx, 0, sy - 2); + screen_write_line(&ctx, sx, 0, 0); + + screen_write_cursormove(&ctx, 0, sy - 1); + if (c->old_status != NULL) + screen_write_copy(&ctx, c->old_status, 0, 0, sx, 1, NULL, NULL); + else + screen_write_copy(&ctx, &c->status, 0, 0, sx, 1, NULL, NULL); + + screen_write_stop(&ctx); + return (&s); +} + +static struct screen * +window_client_init(struct window_pane *wp, __unused struct cmd_find_state *fs, + struct args *args) +{ + struct window_client_modedata *data; + struct screen *s; + + wp->modedata = data = xcalloc(1, sizeof *data); + + if (args == NULL || args->argc == 0) + data->command = xstrdup(WINDOW_CLIENT_DEFAULT_COMMAND); + else + data->command = xstrdup(args->argv[0]); + + data->data = mode_tree_start(wp, window_client_build, + window_client_draw, data, window_client_sort_list, + nitems(window_client_sort_list), &s); + + mode_tree_build(data->data); + mode_tree_draw(data->data); + + return (s); +} + +static void +window_client_free(struct window_pane *wp) +{ + struct window_client_modedata *data = wp->modedata; + u_int i; + + if (data == NULL) + return; + + mode_tree_free(data->data); + + for (i = 0; i < data->item_size; i++) + window_client_free_item(data->item_list[i]); + free(data->item_list); + + free(data->command); + free(data); +} + +static void +window_client_resize(struct window_pane *wp, u_int sx, u_int sy) +{ + struct window_client_modedata *data = wp->modedata; + + mode_tree_resize(data->data, sx, sy); +} + +static void +window_client_do_detach(void* modedata, void *itemdata, key_code key) +{ + struct window_client_modedata *data = modedata; + struct window_client_itemdata *item = itemdata; + + if (item == mode_tree_get_current(data->data)) + mode_tree_down(data->data, 0); + if (key == 'd' || key == 'D') + server_client_detach(item->c, MSG_DETACH); + else if (key == 'x' || key == 'X') + server_client_detach(item->c, MSG_DETACHKILL); + else if (key == 'z' || key == 'Z') + server_client_suspend(item->c); +} + +static void +window_client_key(struct window_pane *wp, struct client *c, + __unused struct session *s, key_code key, struct mouse_event *m) +{ + struct window_client_modedata *data = wp->modedata; + struct window_client_itemdata *item; + char *command, *name; + int finished; + + /* + * t = toggle tag + * T = tag none + * C-t = tag all + * q = exit + * O = change sort order + * + * d = detach client + * D = detach tagged clients + * x = detach and kill client + * X = detach and kill tagged clients + * z = suspend client + * Z = suspend tagged clients + * Enter = detach client + */ + + finished = mode_tree_key(data->data, &key, m); + switch (key) { + case 'd': + case 'x': + case 'z': + item = mode_tree_get_current(data->data); + window_client_do_detach(data, item, key); + mode_tree_build(data->data); + break; + case 'D': + case 'X': + case 'Z': + mode_tree_each_tagged(data->data, window_client_do_detach, key, + 0); + mode_tree_build(data->data); + break; + case '\r': + item = mode_tree_get_current(data->data); + command = xstrdup(data->command); + name = xstrdup(item->c->ttyname); + window_pane_reset_mode(wp); + mode_tree_run_command(c, NULL, command, name); + free(name); + free(command); + return; + } + if (finished || server_client_how_many() == 0) + window_pane_reset_mode(wp); + else { + mode_tree_draw(data->data); + wp->flags |= PANE_REDRAW; + } +} diff --git a/window-clock.c b/window-clock.c index d23ac136..9ecc68a1 100644 --- a/window-clock.c +++ b/window-clock.c @@ -24,7 +24,8 @@ #include "tmux.h" -static struct screen *window_clock_init(struct window_pane *); +static struct screen *window_clock_init(struct window_pane *, + struct cmd_find_state *, struct args *); static void window_clock_free(struct window_pane *); static void window_clock_resize(struct window_pane *, u_int, u_int); static void window_clock_key(struct window_pane *, struct client *, @@ -145,7 +146,8 @@ window_clock_timer_callback(__unused int fd, __unused short events, void *arg) } static struct screen * -window_clock_init(struct window_pane *wp) +window_clock_init(struct window_pane *wp, __unused struct cmd_find_state *fs, + __unused struct args *args) { struct window_clock_mode_data *data; struct screen *s; diff --git a/window-copy.c b/window-copy.c index c8807c99..def4b923 100644 --- a/window-copy.c +++ b/window-copy.c @@ -27,7 +27,8 @@ static const char *window_copy_key_table(struct window_pane *); static void window_copy_command(struct window_pane *, struct client *, struct session *, struct args *, struct mouse_event *); -static struct screen *window_copy_init(struct window_pane *); +static struct screen *window_copy_init(struct window_pane *, + struct cmd_find_state *, struct args *); static void window_copy_free(struct window_pane *); static int window_copy_pagedown(struct window_pane *, int); static void window_copy_next_paragraph(struct window_pane *); @@ -187,7 +188,8 @@ struct window_copy_mode_data { }; static struct screen * -window_copy_init(struct window_pane *wp) +window_copy_init(struct window_pane *wp, __unused struct cmd_find_state *fs, + __unused struct args *args) { struct window_copy_mode_data *data; struct screen *s; diff --git a/window-tree.c b/window-tree.c new file mode 100644 index 00000000..36a19e48 --- /dev/null +++ b/window-tree.c @@ -0,0 +1,713 @@ +/* $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 "tmux.h" + +static struct screen *window_tree_init(struct window_pane *, + struct cmd_find_state *, struct args *); +static void window_tree_free(struct window_pane *); +static void window_tree_resize(struct window_pane *, u_int, u_int); +static void window_tree_key(struct window_pane *, + struct client *, struct session *, key_code, + struct mouse_event *); + +#define WINDOW_TREE_DEFAULT_COMMAND "switch-client -t '%%'" + +const struct window_mode window_tree_mode = { + .name = "tree-mode", + + .init = window_tree_init, + .free = window_tree_free, + .resize = window_tree_resize, + .key = window_tree_key, +}; + +enum window_tree_sort_type { + WINDOW_TREE_BY_INDEX, + WINDOW_TREE_BY_NAME, + WINDOW_TREE_BY_TIME, +}; +static const char *window_tree_sort_list[] = { + "index", + "name", + "time" +}; + +enum window_tree_type { + WINDOW_TREE_NONE, + WINDOW_TREE_SESSION, + WINDOW_TREE_WINDOW, + WINDOW_TREE_PANE, +}; + +struct window_tree_itemdata { + enum window_tree_type type; + int session; + int winlink; + int pane; +}; + +struct window_tree_modedata { + struct window_pane *wp; + int dead; + int references; + + struct mode_tree_data *data; + char *command; + + struct window_tree_itemdata **item_list; + u_int item_size; + + struct client *client; + const char *entered; + + char *filter; + + struct cmd_find_state fs; + enum window_tree_type type; +}; + +static void +window_tree_pull_item(struct window_tree_itemdata *item, struct session **sp, + struct winlink **wlp, struct window_pane **wp) +{ + *wp = NULL; + *wlp = NULL; + *sp = session_find_by_id(item->session); + if (*sp == NULL) + return; + if (item->type == WINDOW_TREE_SESSION) { + *wlp = (*sp)->curw; + *wp = (*wlp)->window->active; + return; + } + + *wlp = winlink_find_by_index(&(*sp)->windows, item->winlink); + if (*wlp == NULL) { + *sp = NULL; + return; + } + if (item->type == WINDOW_TREE_WINDOW) { + *wp = (*wlp)->window->active; + return; + } + + *wp = window_pane_find_by_id(item->pane); + if (!window_has_pane((*wlp)->window, *wp)) + *wp = NULL; + if (*wp == NULL) { + *sp = NULL; + *wlp = NULL; + return; + } +} + +static struct window_tree_itemdata * +window_tree_add_item(struct window_tree_modedata *data) +{ + struct window_tree_itemdata *item; + + data->item_list = xreallocarray(data->item_list, data->item_size + 1, + sizeof *data->item_list); + item = data->item_list[data->item_size++] = xcalloc(1, sizeof *item); + return (item); +} + +static void +window_tree_free_item(struct window_tree_itemdata *item) +{ + free(item); +} + +static int +window_tree_cmp_session_name(const void *a0, const void *b0) +{ + const struct session *const *a = a0; + const struct session *const *b = b0; + + return (strcmp((*a)->name, (*b)->name)); +} + +static int +window_tree_cmp_session_time(const void *a0, const void *b0) +{ + const struct session *const *a = a0; + const struct session *const *b = b0; + + if (timercmp(&(*a)->activity_time, &(*b)->activity_time, >)) + return (-1); + if (timercmp(&(*a)->activity_time, &(*b)->activity_time, <)) + return (1); + return (strcmp((*a)->name, (*b)->name)); +} + +static int +window_tree_cmp_window_name(const void *a0, const void *b0) +{ + const struct winlink *const *a = a0; + const struct winlink *const *b = b0; + + return (strcmp((*a)->window->name, (*b)->window->name)); +} + +static int +window_tree_cmp_window_time(const void *a0, const void *b0) +{ + const struct winlink *const *a = a0; + const struct winlink *const *b = b0; + + if (timercmp(&(*a)->window->activity_time, + &(*b)->window->activity_time, >)) + return (-1); + if (timercmp(&(*a)->window->activity_time, + &(*b)->window->activity_time, <)) + return (1); + return (strcmp((*a)->window->name, (*b)->window->name)); +} + +static int +window_tree_cmp_pane_time(const void *a0, const void *b0) +{ + const struct window_pane *const *a = a0; + const struct window_pane *const *b = b0; + + if ((*a)->active_point < (*b)->active_point) + return (-1); + if ((*a)->active_point > (*b)->active_point) + return (1); + return (0); +} + +static void +window_tree_build_pane(struct session *s, struct winlink *wl, + struct window_pane *wp, void *modedata, struct mode_tree_item *parent) +{ + struct window_tree_modedata *data = modedata; + struct window_tree_itemdata *item; + char *name, *text; + u_int idx; + + window_pane_index(wp, &idx); + + item = window_tree_add_item(data); + item->type = WINDOW_TREE_PANE; + item->session = s->id; + item->winlink = wl->idx; + item->pane = wp->id; + + text = format_single(NULL, + "#{pane_current_command} \"#{pane_title}\"", + NULL, s, wl, wp); + xasprintf(&name, "%u", idx); + + mode_tree_add(data->data, parent, item, (uint64_t)wp, name, text, -1); + free(text); + free(name); +} + +static int +window_tree_build_window(struct session *s, struct winlink *wl, void* modedata, + u_int sort_type, struct mode_tree_item *parent, int no_filter) +{ + struct window_tree_modedata *data = modedata; + struct window_tree_itemdata *item; + struct mode_tree_item *mti; + char *name, *text, *cp; + struct window_pane *wp, **l; + u_int n, i; + int expanded; + + item = window_tree_add_item(data); + item->type = WINDOW_TREE_WINDOW; + item->session = s->id; + item->winlink = wl->idx; + item->pane = -1; + + text = format_single(NULL, + "#{window_name}#{window_flags} (#{window_panes} panes)", + NULL, s, wl, NULL); + xasprintf(&name, "%u", wl->idx); + + if (data->type == WINDOW_TREE_SESSION || + data->type == WINDOW_TREE_WINDOW) + expanded = 0; + else + expanded = 1; + mti = mode_tree_add(data->data, parent, item, (uint64_t)wl, name, text, + expanded); + free(text); + free(name); + + l = NULL; + n = 0; + TAILQ_FOREACH(wp, &wl->window->panes, entry) { + if (!no_filter && data->filter != NULL) { + cp = format_single(NULL, data->filter, NULL, s, wl, wp); + if (!format_true(cp)) { + free(cp); + continue; + } + free(cp); + } + l = xreallocarray(l, n + 1, sizeof *l); + l[n++] = wp; + } + if (n == 0) { + window_tree_free_item(item); + data->item_size--; + mode_tree_remove(data->data, mti); + return (0); + } + + switch (sort_type) { + case WINDOW_TREE_BY_INDEX: + break; + case WINDOW_TREE_BY_NAME: + /* Panes don't have names, so leave in number order. */ + break; + case WINDOW_TREE_BY_TIME: + qsort(l, n, sizeof *l, window_tree_cmp_pane_time); + break; + } + + for (i = 0; i < n; i++) + window_tree_build_pane(s, wl, l[i], modedata, mti); + free(l); + return (1); +} + +static void +window_tree_build_session(struct session *s, void* modedata, + u_int sort_type, int no_filter) +{ + struct window_tree_modedata *data = modedata; + struct window_tree_itemdata *item; + struct mode_tree_item *mti; + char *text; + struct winlink *wl, **l; + u_int n, i, empty; + int expanded; + + item = window_tree_add_item(data); + item->type = WINDOW_TREE_SESSION; + item->session = s->id; + item->winlink = -1; + item->pane = -1; + + text = format_single(NULL, + "#{session_windows} windows" + "#{?session_grouped, (group ,}" + "#{session_group}#{?session_grouped,),}" + "#{?session_attached, (attached),}", + NULL, s, NULL, NULL); + + if (data->type == WINDOW_TREE_SESSION) + expanded = 0; + else + expanded = 1; + mti = mode_tree_add(data->data, NULL, item, (uint64_t)s, s->name, text, + expanded); + free(text); + + l = NULL; + n = 0; + RB_FOREACH(wl, winlinks, &s->windows) { + l = xreallocarray(l, n + 1, sizeof *l); + l[n++] = wl; + } + switch (sort_type) { + case WINDOW_TREE_BY_INDEX: + break; + case WINDOW_TREE_BY_NAME: + qsort(l, n, sizeof *l, window_tree_cmp_window_name); + break; + case WINDOW_TREE_BY_TIME: + qsort(l, n, sizeof *l, window_tree_cmp_window_time); + break; + } + + empty = 0; + for (i = 0; i < n; i++) { + if (!window_tree_build_window(s, l[i], modedata, sort_type, mti, + no_filter)) + empty++; + } + if (empty == n) { + window_tree_free_item(item); + data->item_size--; + mode_tree_remove(data->data, mti); + } + free(l); +} + +static void +window_tree_build(void *modedata, u_int sort_type, uint64_t *tag) +{ + struct window_tree_modedata *data = modedata; + struct session *s, **l; + u_int n, i; + int no_filter = 0; + +restart: + for (i = 0; i < data->item_size; i++) + window_tree_free_item(data->item_list[i]); + free(data->item_list); + data->item_list = NULL; + data->item_size = 0; + + l = NULL; + n = 0; + RB_FOREACH(s, sessions, &sessions) { + l = xreallocarray(l, n + 1, sizeof *l); + l[n++] = s; + } + switch (sort_type) { + case WINDOW_TREE_BY_INDEX: + break; + case WINDOW_TREE_BY_NAME: + qsort(l, n, sizeof *l, window_tree_cmp_session_name); + break; + case WINDOW_TREE_BY_TIME: + qsort(l, n, sizeof *l, window_tree_cmp_session_time); + break; + } + + for (i = 0; i < n; i++) + window_tree_build_session(l[i], modedata, sort_type, no_filter); + free(l); + + if (!no_filter && data->item_size == 0) { + no_filter = 1; + goto restart; + } + + switch (data->type) { + case WINDOW_TREE_NONE: + break; + case WINDOW_TREE_SESSION: + *tag = (uint64_t)data->fs.s; + break; + case WINDOW_TREE_WINDOW: + *tag = (uint64_t)data->fs.wl; + break; + case WINDOW_TREE_PANE: + *tag = (uint64_t)data->fs.wp; + break; + } +} + +static struct screen * +window_tree_draw(__unused void *modedata, void *itemdata, u_int sx, u_int sy) +{ + struct window_tree_itemdata *item = itemdata; + struct session *sp; + struct winlink *wlp; + struct window_pane *wp; + static struct screen s; + struct screen_write_ctx ctx; + + window_tree_pull_item(item, &sp, &wlp, &wp); + if (wp == NULL) + return (NULL); + + screen_init(&s, sx, sy, 0); + + screen_write_start(&ctx, NULL, &s); + + screen_write_preview(&ctx, &wp->base, sx, sy); + + screen_write_stop(&ctx); + return (&s); +} + +static struct screen * +window_tree_init(struct window_pane *wp, struct cmd_find_state *fs, + struct args *args) +{ + struct window_tree_modedata *data; + struct screen *s; + + wp->modedata = data = xcalloc(1, sizeof *data); + + if (args_has(args, 's')) + data->type = WINDOW_TREE_SESSION; + else if (args_has(args, 'w')) + data->type = WINDOW_TREE_WINDOW; + else + data->type = WINDOW_TREE_PANE; + memcpy(&data->fs, fs, sizeof data->fs); + + data->wp = wp; + data->references = 1; + + if (args_has(args, 'f')) + data->filter = xstrdup(args_get(args, 'f')); + else + data->filter = NULL; + + if (args == NULL || args->argc == 0) + data->command = xstrdup(WINDOW_TREE_DEFAULT_COMMAND); + else + data->command = xstrdup(args->argv[0]); + + data->data = mode_tree_start(wp, window_tree_build, + window_tree_draw, data, window_tree_sort_list, + nitems(window_tree_sort_list), &s); + + mode_tree_build(data->data); + mode_tree_draw(data->data); + + data->type = WINDOW_TREE_NONE; + + return (s); +} + +static void +window_tree_destroy(struct window_tree_modedata *data) +{ + u_int i; + + if (--data->references != 0) + return; + + mode_tree_free(data->data); + + for (i = 0; i < data->item_size; i++) + window_tree_free_item(data->item_list[i]); + free(data->item_list); + + free(data->filter); + + free(data->command); + free(data); +} + +static void +window_tree_free(struct window_pane *wp) +{ + struct window_tree_modedata *data = wp->modedata; + + if (data == NULL) + return; + + data->dead = 1; + window_tree_destroy(data); +} + +static void +window_tree_resize(struct window_pane *wp, u_int sx, u_int sy) +{ + struct window_tree_modedata *data = wp->modedata; + + mode_tree_resize(data->data, sx, sy); +} + +static char * +window_tree_get_target(struct window_tree_itemdata *item, + struct cmd_find_state *fs) +{ + struct session *s; + struct winlink *wl; + struct window_pane *wp; + char *target; + + window_tree_pull_item(item, &s, &wl, &wp); + + target = NULL; + switch (item->type) { + case WINDOW_TREE_NONE: + break; + case WINDOW_TREE_SESSION: + if (s == NULL) + break; + xasprintf(&target, "=%s:", s->name); + break; + case WINDOW_TREE_WINDOW: + if (s == NULL || wl == NULL) + break; + xasprintf(&target, "=%s:%u.", s->name, wl->idx); + break; + case WINDOW_TREE_PANE: + if (s == NULL || wl == NULL || wp == NULL) + break; + xasprintf(&target, "=%s:%u.%%%u", s->name, wl->idx, wp->id); + break; + } + if (target == NULL) + cmd_find_clear_state(fs, 0); + else + cmd_find_from_winlink_pane(fs, wl, wp); + return (target); +} + +static void +window_tree_command_each(void* modedata, void* itemdata, __unused key_code key) +{ + struct window_tree_modedata *data = modedata; + struct window_tree_itemdata *item = itemdata; + char *name; + struct cmd_find_state fs; + + name = window_tree_get_target(item, &fs); + if (name != NULL) + mode_tree_run_command(data->client, &fs, data->entered, name); + free(name); +} + +static enum cmd_retval +window_tree_command_done(__unused struct cmdq_item *item, void *modedata) +{ + struct window_tree_modedata *data = modedata; + + if (!data->dead) { + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; + } + window_tree_destroy(data); + return (CMD_RETURN_NORMAL); +} + +static int +window_tree_command_callback(struct client *c, void *modedata, const char *s, + __unused int done) +{ + struct window_tree_modedata *data = modedata; + + if (data->dead) + return (0); + + data->client = c; + data->entered = s; + + mode_tree_each_tagged(data->data, window_tree_command_each, KEYC_NONE, + 1); + + data->client = NULL; + data->entered = NULL; + + data->references++; + cmdq_append(c, cmdq_get_callback(window_tree_command_done, data)); + + return (0); +} + +static void +window_tree_command_free(void *modedata) +{ + struct window_tree_modedata *data = modedata; + + window_tree_destroy(data); +} + +static int +window_tree_filter_callback(__unused struct client *c, void *modedata, + const char *s, __unused int done) +{ + struct window_tree_modedata *data = modedata; + + if (data->dead) + return (0); + + if (data->filter != NULL) + free(data->filter); + if (s == NULL || *s == '\0') + data->filter = NULL; + else + data->filter = xstrdup(s); + + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; + + return (0); +} + +static void +window_tree_filter_free(void *modedata) +{ + struct window_tree_modedata *data = modedata; + + window_tree_destroy(data); +} + +static void +window_tree_key(struct window_pane *wp, struct client *c, + __unused struct session *s, key_code key, struct mouse_event *m) +{ + struct window_tree_modedata *data = wp->modedata; + struct window_tree_itemdata *item; + char *command, *name, *prompt; + struct cmd_find_state fs; + int finished; + u_int tagged; + + /* + * t = toggle tag + * T = tag none + * C-t = tag all + * q = exit + * O = change sort order + * + * Enter = select item + * : = enter command + * f = enter filter + */ + + finished = mode_tree_key(data->data, &key, m); + switch (key) { + case 'f': + data->references++; + status_prompt_set(c, "(filter) ", data->filter, + window_tree_filter_callback, window_tree_filter_free, data, + PROMPT_NOFORMAT); + break; + case ':': + tagged = mode_tree_count_tagged(data->data); + if (tagged != 0) + xasprintf(&prompt, "(%u tagged) ", tagged); + else + xasprintf(&prompt, "(current) "); + data->references++; + status_prompt_set(c, prompt, "", window_tree_command_callback, + window_tree_command_free, data, PROMPT_NOFORMAT); + free(prompt); + break; + case '\r': + item = mode_tree_get_current(data->data); + command = xstrdup(data->command); + name = window_tree_get_target(item, &fs); + window_pane_reset_mode(wp); + if (name != NULL) + mode_tree_run_command(c, &fs, command, name); + free(name); + free(command); + return; + } + if (finished) + window_pane_reset_mode(wp); + else { + mode_tree_draw(data->data); + wp->flags |= PANE_REDRAW; + } +} diff --git a/window.c b/window.c index 5e49adc4..0ca0c72c 100644 --- a/window.c +++ b/window.c @@ -1171,7 +1171,8 @@ window_pane_mode_timer(__unused int fd, __unused short events, void *arg) } int -window_pane_set_mode(struct window_pane *wp, const struct window_mode *mode) +window_pane_set_mode(struct window_pane *wp, const struct window_mode *mode, + struct cmd_find_state *fs, struct args *args) { struct screen *s; struct timeval tv = { .tv_sec = 10 }; @@ -1184,7 +1185,7 @@ window_pane_set_mode(struct window_pane *wp, const struct window_mode *mode) evtimer_set(&wp->modetimer, window_pane_mode_timer, wp); evtimer_add(&wp->modetimer, &tv); - if ((s = wp->mode->init(wp)) != NULL) + if ((s = wp->mode->init(wp, fs, args)) != NULL) wp->screen = s; wp->flags |= (PANE_REDRAW|PANE_CHANGED); @@ -1291,32 +1292,6 @@ window_pane_search(struct window_pane *wp, const char *searchstr) return (i + 1); } -char * -window_pane_search_old(struct window_pane *wp, const char *searchstr, - u_int *lineno) -{ - struct screen *s = &wp->base; - char *newsearchstr, *line, *msg; - u_int i; - - msg = NULL; - xasprintf(&newsearchstr, "*%s*", searchstr); - - for (i = 0; i < screen_size_y(s); i++) { - line = grid_view_string_cells(s->grid, 0, i, screen_size_x(s)); - if (fnmatch(newsearchstr, line, 0) == 0) { - msg = line; - if (lineno != NULL) - *lineno = i; - break; - } - free(line); - } - - free(newsearchstr); - return (msg); -} - /* Get MRU pane from a list. */ static struct window_pane * window_pane_choose_best(struct window_pane **list, u_int size) From 7eb496c00c313c2f8ab8debe6d154d5ac0db277b Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 31 May 2017 08:43:44 +0000 Subject: [PATCH 2/5] Look for setrgbf and setrgbb terminfo extensions for RGB colour. This is the most reasonable of the various (some bizarre) suggestions for capabilities. --- tmux.h | 157 +++++++++++++++++++++++++++-------------------------- tty-term.c | 20 +++++++ tty.c | 30 +++++++--- 3 files changed, 122 insertions(+), 85 deletions(-) diff --git a/tmux.h b/tmux.h index eb63d11f..a3f9c352 100644 --- a/tmux.h +++ b/tmux.h @@ -204,67 +204,67 @@ enum { /* Termcap codes. */ enum tty_code_code { TTYC_AX = 0, - TTYC_ACSC, /* acs_chars, ac */ - TTYC_BCE, /* back_color_erase, ut */ - TTYC_BEL, /* bell, bl */ - TTYC_BLINK, /* enter_blink_mode, mb */ - TTYC_BOLD, /* enter_bold_mode, md */ - TTYC_CIVIS, /* cursor_invisible, vi */ - TTYC_CLEAR, /* clear_screen, cl */ - TTYC_CNORM, /* cursor_normal, ve */ - TTYC_COLORS, /* max_colors, Co */ - TTYC_CR, /* restore cursor colour, Cr */ - TTYC_CS, /* set cursor colour, Cs */ - TTYC_CSR, /* change_scroll_region, cs */ - TTYC_CUB, /* parm_left_cursor, LE */ - TTYC_CUB1, /* cursor_left, le */ - TTYC_CUD, /* parm_down_cursor, DO */ - TTYC_CUD1, /* cursor_down, do */ - TTYC_CUF, /* parm_right_cursor, RI */ - TTYC_CUF1, /* cursor_right, nd */ - TTYC_CUP, /* cursor_address, cm */ - TTYC_CUU, /* parm_up_cursor, UP */ - TTYC_CUU1, /* cursor_up, up */ - TTYC_CVVIS, /* cursor_visible, vs */ - TTYC_DCH, /* parm_dch, DC */ - TTYC_DCH1, /* delete_character, dc */ - TTYC_DIM, /* enter_dim_mode, mh */ - TTYC_DL, /* parm_delete_line, DL */ - TTYC_DL1, /* delete_line, dl */ + TTYC_ACSC, + TTYC_BCE, + TTYC_BEL, + TTYC_BLINK, + TTYC_BOLD, + TTYC_CIVIS, + TTYC_CLEAR, + TTYC_CNORM, + TTYC_COLORS, + TTYC_CR, + TTYC_CS, + TTYC_CSR, + TTYC_CUB, + TTYC_CUB1, + TTYC_CUD, + TTYC_CUD1, + TTYC_CUF, + TTYC_CUF1, + TTYC_CUP, + TTYC_CUU, + TTYC_CUU1, + TTYC_CVVIS, + TTYC_DCH, + TTYC_DCH1, + TTYC_DIM, + TTYC_DL, + TTYC_DL1, TTYC_E3, - TTYC_ECH, /* erase_chars, ec */ - TTYC_ED, /* clr_eos, cd */ - TTYC_EL, /* clr_eol, ce */ - TTYC_EL1, /* clr_bol, cb */ - TTYC_ENACS, /* ena_acs, eA */ - TTYC_FSL, /* from_status_line, fsl */ - TTYC_HOME, /* cursor_home, ho */ - TTYC_HPA, /* column_address, ch */ - TTYC_ICH, /* parm_ich, IC */ - TTYC_ICH1, /* insert_character, ic */ - TTYC_IL, /* parm_insert_line, IL */ - TTYC_IL1, /* insert_line, il */ - TTYC_INDN, /* parm_index, indn */ - TTYC_INVIS, /* enter_secure_mode, mk */ - TTYC_KCBT, /* key_btab, kB */ - TTYC_KCUB1, /* key_left, kl */ - TTYC_KCUD1, /* key_down, kd */ - TTYC_KCUF1, /* key_right, kr */ - TTYC_KCUU1, /* key_up, ku */ + TTYC_ECH, + TTYC_ED, + TTYC_EL, + TTYC_EL1, + TTYC_ENACS, + TTYC_FSL, + TTYC_HOME, + TTYC_HPA, + TTYC_ICH, + TTYC_ICH1, + TTYC_IL, + TTYC_IL1, + TTYC_INDN, + TTYC_INVIS, + TTYC_KCBT, + TTYC_KCUB1, + TTYC_KCUD1, + TTYC_KCUF1, + TTYC_KCUU1, TTYC_KDC2, TTYC_KDC3, TTYC_KDC4, TTYC_KDC5, TTYC_KDC6, TTYC_KDC7, - TTYC_KDCH1, /* key_dc, kD */ + TTYC_KDCH1, TTYC_KDN2, TTYC_KDN3, TTYC_KDN4, TTYC_KDN5, TTYC_KDN6, TTYC_KDN7, - TTYC_KEND, /* key_end, ke */ + TTYC_KEND, TTYC_KEND2, TTYC_KEND3, TTYC_KEND4, @@ -340,29 +340,29 @@ enum tty_code_code { TTYC_KHOM5, TTYC_KHOM6, TTYC_KHOM7, - TTYC_KHOME, /* key_home, kh */ + TTYC_KHOME, TTYC_KIC2, TTYC_KIC3, TTYC_KIC4, TTYC_KIC5, TTYC_KIC6, TTYC_KIC7, - TTYC_KICH1, /* key_ic, kI */ + TTYC_KICH1, TTYC_KLFT2, TTYC_KLFT3, TTYC_KLFT4, TTYC_KLFT5, TTYC_KLFT6, TTYC_KLFT7, - TTYC_KMOUS, /* key_mouse, Km */ - TTYC_KNP, /* key_npage, kN */ + TTYC_KMOUS, + TTYC_KNP, TTYC_KNXT2, TTYC_KNXT3, TTYC_KNXT4, TTYC_KNXT5, TTYC_KNXT6, TTYC_KNXT7, - TTYC_KPP, /* key_ppage, kP */ + TTYC_KPP, TTYC_KPRV2, TTYC_KPRV3, TTYC_KPRV4, @@ -381,31 +381,33 @@ enum tty_code_code { TTYC_KUP5, TTYC_KUP6, TTYC_KUP7, - TTYC_MS, /* modify xterm(1) selection */ - TTYC_OP, /* orig_pair, op */ - TTYC_REV, /* enter_reverse_mode, mr */ - TTYC_RI, /* scroll_reverse, sr */ - TTYC_RMACS, /* exit_alt_charset_mode */ - TTYC_RMCUP, /* exit_ca_mode, te */ - TTYC_RMKX, /* keypad_local, ke */ - TTYC_SE, /* reset cursor style, Se */ - TTYC_SETAB, /* set_a_background, AB */ - TTYC_SETAF, /* set_a_foreground, AF */ - TTYC_SGR0, /* exit_attribute_mode, me */ - TTYC_SITM, /* enter_italics_mode, it */ - TTYC_SMACS, /* enter_alt_charset_mode, as */ - TTYC_SMCUP, /* enter_ca_mode, ti */ - TTYC_SMKX, /* keypad_xmit, ks */ - TTYC_SMSO, /* enter_standout_mode, so */ - TTYC_SMUL, /* enter_underline_mode, us */ + TTYC_MS, + TTYC_OP, + TTYC_REV, + TTYC_RI, + TTYC_RMACS, + TTYC_RMCUP, + TTYC_RMKX, + TTYC_SE, + TTYC_SETAB, + TTYC_SETAF, + TTYC_SETRGBB, + TTYC_SETRGBF, + TTYC_SGR0, + TTYC_SITM, + TTYC_SMACS, + TTYC_SMCUP, + TTYC_SMKX, + TTYC_SMSO, + TTYC_SMUL, TTYC_SMXX, - TTYC_SS, /* set cursor style, Ss */ - TTYC_TC, /* 24-bit "true" colour, Tc */ - TTYC_TSL, /* to_status_line, tsl */ + TTYC_SS, + TTYC_TC, + TTYC_TSL, TTYC_U8, - TTYC_VPA, /* row_address, cv */ - TTYC_XENL, /* eat_newline_glitch, xn */ - TTYC_XT, /* xterm(1)-compatible title, XT */ + TTYC_VPA, + TTYC_XENL, + TTYC_XT, }; /* Message codes. */ @@ -1648,6 +1650,7 @@ void tty_cursor(struct tty *, u_int, u_int); void tty_putcode(struct tty *, enum tty_code_code); void tty_putcode1(struct tty *, enum tty_code_code, int); void tty_putcode2(struct tty *, enum tty_code_code, int, int); +void tty_putcode3(struct tty *, enum tty_code_code, int, int, int); void tty_putcode_ptr1(struct tty *, enum tty_code_code, const void *); void tty_putcode_ptr2(struct tty *, enum tty_code_code, const void *, const void *); @@ -1702,6 +1705,8 @@ const char *tty_term_string(struct tty_term *, enum tty_code_code); const char *tty_term_string1(struct tty_term *, enum tty_code_code, int); const char *tty_term_string2(struct tty_term *, enum tty_code_code, int, int); +const char *tty_term_string3(struct tty_term *, enum tty_code_code, int, + int, int); const char *tty_term_ptr1(struct tty_term *, enum tty_code_code, const void *); const char *tty_term_ptr2(struct tty_term *, enum tty_code_code, diff --git a/tty-term.c b/tty-term.c index d1142ad2..38829bdd 100644 --- a/tty-term.c +++ b/tty-term.c @@ -242,6 +242,8 @@ static const struct tty_term_code_entry tty_term_codes[] = { [TTYC_SE] = { TTYCODE_STRING, "Se" }, [TTYC_SETAB] = { TTYCODE_STRING, "setab" }, [TTYC_SETAF] = { TTYCODE_STRING, "setaf" }, + [TTYC_SETRGBB] = { TTYCODE_STRING, "setrgbb" }, + [TTYC_SETRGBF] = { TTYCODE_STRING, "setrgbf" }, [TTYC_SGR0] = { TTYCODE_STRING, "sgr0" }, [TTYC_SITM] = { TTYCODE_STRING, "sitm" }, [TTYC_SMACS] = { TTYCODE_STRING, "smacs" }, @@ -521,6 +523,18 @@ tty_term_find(char *name, int fd, char **cause) code->type = TTYCODE_STRING; } + /* On terminals with RGB colour (TC), fill in setrgbf and setrgbb. */ + if (tty_term_flag(term, TTYC_TC) && + !tty_term_has(term, TTYC_SETRGBF) && + !tty_term_has(term, TTYC_SETRGBB)) { + code = &term->codes[TTYC_SETRGBF]; + code->value.string = xstrdup("\033[38;2;%p1%d;%p2%d;%p3%dm"); + code->type = TTYCODE_STRING; + code = &term->codes[TTYC_SETRGBB]; + code->value.string = xstrdup("\033[48;2;%p1%d;%p2%d;%p3%dm"); + code->type = TTYCODE_STRING; + } + return (term); error: @@ -576,6 +590,12 @@ tty_term_string2(struct tty_term *term, enum tty_code_code code, int a, int b) return (tparm((char *) tty_term_string(term, code), a, b)); } +const char * +tty_term_string3(struct tty_term *term, enum tty_code_code code, int a, int b, int c) +{ + return (tparm((char *) tty_term_string(term, code), a, b, c)); +} + const char * tty_term_ptr1(struct tty_term *term, enum tty_code_code code, const void *a) { diff --git a/tty.c b/tty.c index d792c9c2..9de1f50a 100644 --- a/tty.c +++ b/tty.c @@ -472,6 +472,14 @@ tty_putcode2(struct tty *tty, enum tty_code_code code, int a, int b) tty_puts(tty, tty_term_string2(tty->term, code, a, b)); } +void +tty_putcode3(struct tty *tty, enum tty_code_code code, int a, int b, int c) +{ + if (a < 0 || b < 0 || c < 0) + return; + tty_puts(tty, tty_term_string3(tty->term, code, a, b, c)); +} + void tty_putcode_ptr1(struct tty *tty, enum tty_code_code code, const void *a) { @@ -1847,7 +1855,7 @@ tty_check_fg(struct tty *tty, const struct window_pane *wp, /* Is this a 24-bit colour? */ if (gc->fg & COLOUR_FLAG_RGB) { /* Not a 24-bit terminal? Translate to 256-colour palette. */ - if (!tty_term_flag(tty->term, TTYC_TC)) { + if (!tty_term_has(tty->term, TTYC_SETRGBF)) { colour_split_rgb(gc->fg, &r, &g, &b); gc->fg = colour_find_rgb(r, g, b); } else @@ -1900,7 +1908,7 @@ tty_check_bg(struct tty *tty, const struct window_pane *wp, /* Is this a 24-bit colour? */ if (gc->bg & COLOUR_FLAG_RGB) { /* Not a 24-bit terminal? Translate to 256-colour palette. */ - if (!tty_term_flag(tty->term, TTYC_TC)) { + if (!tty_term_has(tty->term, TTYC_SETRGBB)) { colour_split_rgb(gc->bg, &r, &g, &b); gc->bg = colour_find_rgb(r, g, b); } else @@ -2031,13 +2039,17 @@ tty_try_colour(struct tty *tty, int colour, const char *type) } if (colour & COLOUR_FLAG_RGB) { - if (!tty_term_flag(tty->term, TTYC_TC)) - return (-1); - - colour_split_rgb(colour & 0xffffff, &r, &g, &b); - xsnprintf(s, sizeof s, "\033[%s;2;%hhu;%hhu;%hhum", type, - r, g, b); - tty_puts(tty, s); + if (*type == '3') { + if (!tty_term_has(tty->term, TTYC_SETRGBF)) + return (-1); + colour_split_rgb(colour & 0xffffff, &r, &g, &b); + tty_putcode3(tty, TTYC_SETRGBF, r, g, b); + } else { + if (!tty_term_has(tty->term, TTYC_SETRGBB)) + return (-1); + colour_split_rgb(colour & 0xffffff, &r, &g, &b); + tty_putcode3(tty, TTYC_SETRGBB, r, g, b); + } return (0); } From 80c6b487dc05662efd675695b7db93563e582f7c Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 31 May 2017 10:15:51 +0000 Subject: [PATCH 3/5] Because we defer actually resizing applications (calling TIOCSWINSZ) until the end of the server loop, tmux may have gone through several internal resizes in between. This can be a problem if the final size is the same as the initial size (what the application things it currently is), because the application may choose not to redraw, assuming the screen state is unchanged, when in fact tmux has thrown away parts of the screen, assuming the application will redraw them. To avoid this, do an extra resize if the new size is the same size as the initial size. This should force the application to redraw when tmux needs it to, while retaining the benefits of deferring (so we now resize at most two times instead of at most one - and only two very rarely). Fixes a problem with break-pane and zoomed panes reported by Michal Mazurek. --- server-client.c | 25 ++++++++++++++++++++++++- tmux.h | 3 +++ window.c | 4 ++-- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/server-client.c b/server-client.c index 8de4f6bd..673f1642 100644 --- a/server-client.c +++ b/server-client.c @@ -1050,14 +1050,36 @@ server_client_resize_event(__unused int fd, __unused short events, void *data) if (!(wp->flags & PANE_RESIZE)) return; + /* + * If we are resizing to the same size as when we entered the loop + * (that is, to the same size the application currently thinks it is), + * tmux may have gone through several resizes internally and thrown + * away parts of the screen. So we need the application to actually + * redraw even though its final size has not changed. + */ + if (wp->sx == wp->osx && + wp->sy == wp->osy && + wp->sx > 1 && + wp->sy > 1) { + memset(&ws, 0, sizeof ws); + ws.ws_col = wp->sx - 1; + ws.ws_row = wp->sy - 1; + if (ioctl(wp->fd, TIOCSWINSZ, &ws) == -1) + fatal("ioctl failed"); + log_debug("%s: %%%u forcing resize", __func__, wp->id); + } + memset(&ws, 0, sizeof ws); ws.ws_col = wp->sx; ws.ws_row = wp->sy; - if (ioctl(wp->fd, TIOCSWINSZ, &ws) == -1) fatal("ioctl failed"); + log_debug("%s: %%%u resize to %u,%u", __func__, wp->id, wp->sx, wp->sy); wp->flags &= ~PANE_RESIZE; + + wp->osx = wp->sx; + wp->osy = wp->sy; } /* Check if pane should be resized. */ @@ -1068,6 +1090,7 @@ server_client_check_resize(struct window_pane *wp) if (!(wp->flags & PANE_RESIZE)) return; + log_debug("%s: %%%u resize to %u,%u", __func__, wp->id, wp->sx, wp->sy); if (!event_initialized(&wp->resize_timer)) evtimer_set(&wp->resize_timer, server_client_resize_event, wp); diff --git a/tmux.h b/tmux.h index a3f9c352..f56f3b1b 100644 --- a/tmux.h +++ b/tmux.h @@ -745,6 +745,9 @@ struct window_pane { u_int sx; u_int sy; + u_int osx; + u_int osy; + u_int xoff; u_int yoff; diff --git a/window.c b/window.c index 0ca0c72c..5fe7c8b8 100644 --- a/window.c +++ b/window.c @@ -800,8 +800,8 @@ window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit) wp->xoff = 0; wp->yoff = 0; - wp->sx = sx; - wp->sy = sy; + wp->sx = wp->osx = sx; + wp->sy = wp->osx = sy; wp->pipe_fd = -1; wp->pipe_off = 0; From ea6428a5d2c3c753c3123b3ecace7357e97eae50 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 31 May 2017 10:29:15 +0000 Subject: [PATCH 4/5] It is not OK to ignore SIGWINCH if SIOCGWINSZ reports the size has unchanged, because it may have changed and changed back in the time between us getting the signal and calling ioctl(). Always redraw when we see SIGWINCH. --- server-client.c | 7 +++---- tmux.h | 4 ++-- tty.c | 12 +++--------- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/server-client.c b/server-client.c index 673f1642..15b0d2dc 100644 --- a/server-client.c +++ b/server-client.c @@ -1451,10 +1451,9 @@ server_client_dispatch(struct imsg *imsg, void *arg) if (c->flags & CLIENT_CONTROL) break; - if (tty_resize(&c->tty)) { - recalculate_sizes(); - server_redraw_client(c); - } + tty_resize(&c->tty); + recalculate_sizes(); + server_redraw_client(c); if (c->session != NULL) notify_client("client-resized", c); break; diff --git a/tmux.h b/tmux.h index f56f3b1b..79ae8ddc 100644 --- a/tmux.h +++ b/tmux.h @@ -1661,8 +1661,8 @@ void tty_puts(struct tty *, const char *); void tty_putc(struct tty *, u_char); void tty_putn(struct tty *, const void *, size_t, u_int); int tty_init(struct tty *, struct client *, int, char *); -int tty_resize(struct tty *); -int tty_set_size(struct tty *, u_int, u_int); +void tty_resize(struct tty *); +void tty_set_size(struct tty *, u_int, u_int); void tty_start_tty(struct tty *); void tty_stop_tty(struct tty *); void tty_set_title(struct tty *, const char *); diff --git a/tty.c b/tty.c index 9de1f50a..72453821 100644 --- a/tty.c +++ b/tty.c @@ -120,7 +120,7 @@ tty_init(struct tty *tty, struct client *c, int fd, char *term) return (0); } -int +void tty_resize(struct tty *tty) { struct client *c = tty->client; @@ -139,21 +139,15 @@ tty_resize(struct tty *tty) sy = 24; } log_debug("%s: %s now %ux%u", __func__, c->name, sx, sy); - - if (!tty_set_size(tty, sx, sy)) - return (0); + tty_set_size(tty, sx, sy); tty_invalidate(tty); - return (1); } -int +void tty_set_size(struct tty *tty, u_int sx, u_int sy) { - if (sx == tty->sx && sy == tty->sy) - return (0); tty->sx = sx; tty->sy = sy; - return (1); } static void From d60663ea8664d1c71def883bd64d97af3f791f89 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 31 May 2017 11:00:00 +0000 Subject: [PATCH 5/5] Some applications like vi(1) and tmux until 10 minutes or so ago, do not redraw on SIGWINCH if the size returns to the original size between the original SIGWINCH and when they get around to calling TIOCGWINSZ. So use the existing resize timer to introduce a small delay between the two resizes. --- server-client.c | 59 +++++++++++++++++++++++++++++++++---------------- tmux.h | 7 +++--- 2 files changed, 44 insertions(+), 22 deletions(-) diff --git a/server-client.c b/server-client.c index 15b0d2dc..e8faedc7 100644 --- a/server-client.c +++ b/server-client.c @@ -1038,6 +1038,44 @@ server_client_loop(void) } } +/* Check if we need to force a resize. */ +static int +server_client_resize_force(struct window_pane *wp) +{ + struct timeval tv = { .tv_usec = 100000 }; + struct winsize ws; + + /* + * If we are resizing to the same size as when we entered the loop + * (that is, to the same size the application currently thinks it is), + * tmux may have gone through several resizes internally and thrown + * away parts of the screen. So we need the application to actually + * redraw even though its final size has not changed. + */ + + if (wp->flags & PANE_RESIZEFORCE) { + wp->flags &= ~PANE_RESIZEFORCE; + return (0); + } + + if (wp->sx != wp->osx || + wp->sy != wp->osy || + wp->sx <= 1 || + wp->sy <= 1) + return (0); + + memset(&ws, 0, sizeof ws); + ws.ws_col = wp->sx; + ws.ws_row = wp->sy - 1; + if (ioctl(wp->fd, TIOCSWINSZ, &ws) == -1) + fatal("ioctl failed"); + log_debug("%s: %%%u forcing resize", __func__, wp->id); + + evtimer_add(&wp->resize_timer, &tv); + wp->flags |= PANE_RESIZEFORCE; + return (1); +} + /* Resize timer event. */ static void server_client_resize_event(__unused int fd, __unused short events, void *data) @@ -1049,25 +1087,8 @@ server_client_resize_event(__unused int fd, __unused short events, void *data) if (!(wp->flags & PANE_RESIZE)) return; - - /* - * If we are resizing to the same size as when we entered the loop - * (that is, to the same size the application currently thinks it is), - * tmux may have gone through several resizes internally and thrown - * away parts of the screen. So we need the application to actually - * redraw even though its final size has not changed. - */ - if (wp->sx == wp->osx && - wp->sy == wp->osy && - wp->sx > 1 && - wp->sy > 1) { - memset(&ws, 0, sizeof ws); - ws.ws_col = wp->sx - 1; - ws.ws_row = wp->sy - 1; - if (ioctl(wp->fd, TIOCSWINSZ, &ws) == -1) - fatal("ioctl failed"); - log_debug("%s: %%%u forcing resize", __func__, wp->id); - } + if (server_client_resize_force(wp)) + return; memset(&ws, 0, sizeof ws); ws.ws_col = wp->sx; diff --git a/tmux.h b/tmux.h index 79ae8ddc..b47a6911 100644 --- a/tmux.h +++ b/tmux.h @@ -756,9 +756,10 @@ struct window_pane { #define PANE_DROP 0x2 #define PANE_FOCUSED 0x4 #define PANE_RESIZE 0x8 -#define PANE_FOCUSPUSH 0x10 -#define PANE_INPUTOFF 0x20 -#define PANE_CHANGED 0x40 +#define PANE_RESIZEFORCE 0x10 +#define PANE_FOCUSPUSH 0x20 +#define PANE_INPUTOFF 0x40 +#define PANE_CHANGED 0x80 int argc; char **argv;