summaryrefslogtreecommitdiff
path: root/usr.bin/tmux
diff options
context:
space:
mode:
authorNicholas Marriott <nicm@cvs.openbsd.org>2019-05-10 18:04:07 +0000
committerNicholas Marriott <nicm@cvs.openbsd.org>2019-05-10 18:04:07 +0000
commitc2e749ce9977afc1368916e031b551400ef29f2f (patch)
tree5a2aed239fab21d4b8e4b0268eb757b98aea2334 /usr.bin/tmux
parent77610458f85b49f77da7ec69e4a63942510bd1dd (diff)
Add support for simple menus usable with mouse or keyboard. New command
display-menu shows a menu (bound to the mouse on status line by default) and a couple of extra formats for the default menus.
Diffstat (limited to 'usr.bin/tmux')
-rw-r--r--usr.bin/tmux/Makefile4
-rw-r--r--usr.bin/tmux/cmd-display-menu.c163
-rw-r--r--usr.bin/tmux/cmd.c4
-rw-r--r--usr.bin/tmux/format.c50
-rw-r--r--usr.bin/tmux/key-bindings.c7
-rw-r--r--usr.bin/tmux/menu.c342
-rw-r--r--usr.bin/tmux/tmux.196
-rw-r--r--usr.bin/tmux/tmux.h24
8 files changed, 667 insertions, 23 deletions
diff --git a/usr.bin/tmux/Makefile b/usr.bin/tmux/Makefile
index e107e9ab099..4f93457924e 100644
--- a/usr.bin/tmux/Makefile
+++ b/usr.bin/tmux/Makefile
@@ -1,4 +1,4 @@
-# $OpenBSD: Makefile,v 1.95 2019/04/26 11:38:51 nicm Exp $
+# $OpenBSD: Makefile,v 1.96 2019/05/10 18:04:06 nicm Exp $
PROG= tmux
SRCS= alerts.c \
@@ -15,6 +15,7 @@ SRCS= alerts.c \
cmd-confirm-before.c \
cmd-copy-mode.c \
cmd-detach-client.c \
+ cmd-display-menu.c \
cmd-display-message.c \
cmd-display-panes.c \
cmd-find-window.c \
@@ -86,6 +87,7 @@ SRCS= alerts.c \
layout-set.c \
layout.c \
log.c \
+ menu.c \
mode-tree.c \
names.c \
notify.c \
diff --git a/usr.bin/tmux/cmd-display-menu.c b/usr.bin/tmux/cmd-display-menu.c
new file mode 100644
index 00000000000..dfe4f9694d2
--- /dev/null
+++ b/usr.bin/tmux/cmd-display-menu.c
@@ -0,0 +1,163 @@
+/* $OpenBSD: cmd-display-menu.c,v 1.1 2019/05/10 18:04:06 nicm Exp $ */
+
+/*
+ * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmux.h"
+
+/*
+ * Display a menu on a client.
+ */
+
+static enum cmd_retval cmd_display_menu_exec(struct cmd *,
+ struct cmdq_item *);
+
+const struct cmd_entry cmd_display_menu_entry = {
+ .name = "display-menu",
+ .alias = "menu",
+
+ .args = { "c:FM:t:T:x:y:", 0, 0 },
+ .usage = "[-F] [-c target-client] [-M menu] " CMD_TARGET_PANE_USAGE " "
+ "[-T title] [-x position] [-y position]",
+
+ .target = { 't', CMD_FIND_PANE, 0 },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_display_menu_exec
+};
+
+static enum cmd_retval
+cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = self->args;
+ struct client *c;
+ struct session *s = item->target.s;
+ struct winlink *wl = item->target.wl;
+ struct window_pane *wp = item->target.wp;
+ struct cmd_find_state *fs = &item->target;
+ struct menu *menu = NULL;
+ struct style_range *sr;
+ const char *string, *xp, *yp;
+ int at, flags;
+ u_int px, py, ox, oy, sx, sy;
+ char *title;
+
+ if ((c = cmd_find_client(item, args_get(args, 'c'), 0)) == NULL)
+ return (CMD_RETURN_ERROR);
+ if (c->overlay_draw != NULL)
+ return (CMD_RETURN_NORMAL);
+ at = status_at_line(c);
+
+ string = args_get(args, 'M');
+ if (string == NULL) {
+ cmdq_error(item, "no menu specified");
+ return (CMD_RETURN_ERROR);
+ }
+ if (args_has(args, 'F'))
+ string = format_single(NULL, string, c, s, wl, wp);
+ else
+ string = xstrdup(string);
+ if (args_has(args, 'T'))
+ title = format_single(NULL, args_get(args, 'T'), c, s, wl, wp);
+ else
+ title = xstrdup("");
+ menu = menu_create_from_string(string, c, fs, title);
+ free(title);
+ if (menu == NULL) {
+ cmdq_error(item, "invalid menu %s", string);
+ return (CMD_RETURN_ERROR);
+ }
+ if (menu->count == 0) {
+ menu_free(menu);
+ return (CMD_RETURN_NORMAL);
+ }
+
+ xp = args_get(args, 'x');
+ if (xp == NULL)
+ px = 0;
+ else if (strcmp(xp, "R") == 0)
+ px = c->tty.sx - 1;
+ else if (strcmp(xp, "P") == 0) {
+ tty_window_offset(&c->tty, &ox, &oy, &sx, &sy);
+ if (wp->xoff >= ox)
+ px = wp->xoff - ox;
+ else
+ px = 0;
+ } else if (strcmp(xp, "M") == 0 && item->shared->mouse.valid) {
+ if (item->shared->mouse.x > (menu->width + 4) / 2)
+ px = item->shared->mouse.x - (menu->width + 4) / 2;
+ else
+ px = 0;
+ }
+ else if (strcmp(xp, "W") == 0) {
+ if (at == -1)
+ px = 0;
+ else {
+ TAILQ_FOREACH(sr, &c->status.entries[0].ranges, entry) {
+ if (sr->type != STYLE_RANGE_WINDOW)
+ continue;
+ if (sr->argument == (u_int)wl->idx)
+ break;
+ }
+ if (sr != NULL)
+ px = sr->start;
+ else
+ px = 0;
+ }
+ } else
+ px = strtoul(xp, NULL, 10);
+ if (px + menu->width + 4 >= c->tty.sx)
+ px = c->tty.sx - menu->width - 4;
+
+ yp = args_get(args, 'y');
+ if (yp == NULL)
+ py = 0;
+ else if (strcmp(yp, "P") == 0) {
+ tty_window_offset(&c->tty, &ox, &oy, &sx, &sy);
+ if (wp->yoff + wp->sy >= oy)
+ py = wp->yoff + wp->sy - oy;
+ else
+ py = 0;
+ } else if (strcmp(yp, "M") == 0 && item->shared->mouse.valid)
+ py = item->shared->mouse.y + menu->count + 2;
+ else if (strcmp(yp, "S") == 0) {
+ if (at == -1)
+ py = c->tty.sy;
+ else if (at == 0)
+ py = status_line_size(c) + menu->count + 2;
+ else
+ py = at;
+ } else
+ py = strtoul(yp, NULL, 10);
+ if (py < menu->count + 2)
+ py = 0;
+ else
+ py -= menu->count + 2;
+ if (py + menu->count + 2 >= c->tty.sy)
+ py = c->tty.sy - menu->count - 2;
+
+ flags = 0;
+ if (!item->shared->mouse.valid)
+ flags |= MENU_NOMOUSE;
+ if (menu_display(menu, flags, item, px, py, c, fs, NULL, NULL) != 0)
+ return (CMD_RETURN_NORMAL);
+ return (CMD_RETURN_WAIT);
+}
diff --git a/usr.bin/tmux/cmd.c b/usr.bin/tmux/cmd.c
index abb2e9a2917..e36a395e909 100644
--- a/usr.bin/tmux/cmd.c
+++ b/usr.bin/tmux/cmd.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: cmd.c,v 1.147 2019/05/08 18:05:03 nicm Exp $ */
+/* $OpenBSD: cmd.c,v 1.148 2019/05/10 18:04:06 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -42,6 +42,7 @@ extern const struct cmd_entry cmd_confirm_before_entry;
extern const struct cmd_entry cmd_copy_mode_entry;
extern const struct cmd_entry cmd_delete_buffer_entry;
extern const struct cmd_entry cmd_detach_client_entry;
+extern const struct cmd_entry cmd_display_menu_entry;
extern const struct cmd_entry cmd_display_message_entry;
extern const struct cmd_entry cmd_display_panes_entry;
extern const struct cmd_entry cmd_down_pane_entry;
@@ -130,6 +131,7 @@ const struct cmd_entry *cmd_table[] = {
&cmd_copy_mode_entry,
&cmd_delete_buffer_entry,
&cmd_detach_client_entry,
+ &cmd_display_menu_entry,
&cmd_display_message_entry,
&cmd_display_panes_entry,
&cmd_find_window_entry,
diff --git a/usr.bin/tmux/format.c b/usr.bin/tmux/format.c
index ccfac2b2512..e393430fef6 100644
--- a/usr.bin/tmux/format.c
+++ b/usr.bin/tmux/format.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: format.c,v 1.191 2019/05/09 08:38:13 nicm Exp $ */
+/* $OpenBSD: format.c,v 1.192 2019/05/10 18:04:06 nicm Exp $ */
/*
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -54,6 +54,49 @@ static void format_defaults_session(struct format_tree *,
static void format_defaults_client(struct format_tree *, struct client *);
static void format_defaults_winlink(struct format_tree *, struct winlink *);
+/* Default menus. */
+#define DEFAULT_CLIENT_MENU \
+ "Detach,d,detach-client|" \
+ "Detach & Kill,X,detach-client -P|" \
+ "Detach Others,o,detach-client -a|" \
+ "|" \
+ "#{?#{lock-command},Lock,},l,lock-client"
+#define DEFAULT_SESSION_MENU \
+ "Next,n,switch-client -n|" \
+ "Previous,p,switch-client -p|" \
+ "|" \
+ "Renumber,N,move-window -r|" \
+ "Rename,n,command-prompt -I \"#S\" \"rename-session -- '%%'\"|" \
+ "|" \
+ "New Session,s,new-session|" \
+ "New Window,w,new-window"
+#define DEFAULT_WINDOW_MENU \
+ "Swap Left,l,swap-window -t,-1|" \
+ "Swap Right,r,swap-window -t,+1|" \
+ "#{?pane_marked_set,,#[dim]}Swap Marked,s,swap-window|" \
+ "|" \
+ "Kill,X,kill-window|" \
+ "Respawn,R,respawn-window -k|" \
+ "|" \
+ "#{?pane_marked,Unmark,Mark},m,select-pane -m|" \
+ "Rename,n,command-prompt -I \"#W\" \"rename-window -- '%%'\"|" \
+ "|" \
+ "New After,w,new-window -a|" \
+ "New At End,W,new-window"
+#define DEFAULT_PANE_MENU \
+ "Horizontal Split,h,split-window -h|" \
+ "Vertical Split,v,split-window -v|" \
+ "|" \
+ "Swap Up,u,swap-pane -U|" \
+ "Swap Down,d,swap-pane -D|" \
+ "#{?pane_marked_set,,#[dim]}Swap Marked,s,swap-pane|" \
+ "|" \
+ "Kill,X,kill-pane|" \
+ "Respawn,R,respawn-pane -k|" \
+ "|" \
+ "#{?pane_marked,Unmark,Mark},m,select-pane -m|" \
+ "#{?window_zoomed_flag,Unzoom,Zoom},z,resize-pane -Z"
+
/* Entry in format job tree. */
struct format_job {
struct client *client;
@@ -752,6 +795,11 @@ format_create(struct client *c, struct cmdq_item *item, int tag, int flags)
}
}
+ format_add(ft, "client_menu", "%s", DEFAULT_CLIENT_MENU);
+ format_add(ft, "session_menu", "%s", DEFAULT_SESSION_MENU);
+ format_add(ft, "window_menu", "%s", DEFAULT_WINDOW_MENU);
+ format_add(ft, "pane_menu", "%s", DEFAULT_PANE_MENU);
+
if (item != NULL) {
if (item->cmd != NULL)
format_add(ft, "command", "%s", item->cmd->entry->name);
diff --git a/usr.bin/tmux/key-bindings.c b/usr.bin/tmux/key-bindings.c
index c484f2301c2..375b939d775 100644
--- a/usr.bin/tmux/key-bindings.c
+++ b/usr.bin/tmux/key-bindings.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: key-bindings.c,v 1.90 2019/05/09 13:12:59 nicm Exp $ */
+/* $OpenBSD: key-bindings.c,v 1.91 2019/05/10 18:04:06 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -271,6 +271,7 @@ key_bindings_init(void)
"bind -r C-Down resize-pane -D",
"bind -r C-Left resize-pane -L",
"bind -r C-Right resize-pane -R",
+
"bind -n MouseDown1Pane select-pane -t=\\; send-keys -M",
"bind -n MouseDrag1Border resize-pane -M",
"bind -n MouseDown1Status select-window -t=",
@@ -279,6 +280,10 @@ key_bindings_init(void)
"bind -n MouseDrag1Pane if -Ft= '#{mouse_any_flag}' 'if -Ft= \"#{pane_in_mode}\" \"copy-mode -M\" \"send-keys -M\"' 'copy-mode -M'",
"bind -n MouseDown3Pane if-shell -Ft= '#{mouse_any_flag}' 'select-pane -t=; send-keys -M' 'select-pane -mt='",
"bind -n WheelUpPane if-shell -Ft= '#{mouse_any_flag}' 'send-keys -M' 'if -Ft= \"#{pane_in_mode}\" \"send-keys -M\" \"copy-mode -et=\"'",
+ "bind -n MouseDown3StatusRight display-menu -t= -xM -yS -F -M \"#{client_menu}\" -T \"#[align=centre]#{client_name}\"",
+ "bind -n MouseDown3StatusLeft display-menu -t= -xM -yS -F -M \"#{session_menu}\" -T \"#[align=centre]#{session_name}\"",
+ "bind -n MouseDown3Status display-menu -t= -xW -yS -F -M \"#{window_menu}\" -T \"#[align=centre]#{window_index}:#{window_name}\"",
+ "bind -n M-MouseDown3Pane display-menu -t= -xM -yM -F -M \"#{pane_menu}\" -T \"#[align=centre]#{pane_index} (#{pane_id})\"",
"bind -Tcopy-mode C-Space send -X begin-selection",
"bind -Tcopy-mode C-a send -X start-of-line",
diff --git a/usr.bin/tmux/menu.c b/usr.bin/tmux/menu.c
new file mode 100644
index 00000000000..f0452e7a5af
--- /dev/null
+++ b/usr.bin/tmux/menu.c
@@ -0,0 +1,342 @@
+/* $OpenBSD: menu.c,v 1.1 2019/05/10 18:04:06 nicm Exp $ */
+
+/*
+ * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmux.h"
+
+struct menu_data {
+ struct cmdq_item *item;
+ int flags;
+
+ struct cmd_find_state fs;
+ struct screen s;
+
+ u_int px;
+ u_int py;
+
+ struct menu *menu;
+ int choice;
+
+ menu_choice_cb cb;
+ void *data;
+};
+
+static void
+menu_add_item(struct menu *menu, struct menu_item *item, struct client *c,
+ struct cmd_find_state *fs)
+{
+ struct menu_item *new_item;
+ const char *key;
+ char *name;
+ u_int width;
+
+ menu->items = xreallocarray(menu->items, menu->count + 1,
+ sizeof *menu->items);
+ new_item = &menu->items[menu->count++];
+ memset(new_item, 0, sizeof *new_item);
+
+ if (item == NULL || *item->name == '\0') /* horizontal line */
+ return;
+ name = format_single(NULL, item->name, c, fs->s, fs->wl, fs->wp);
+ if (*name == '\0') { /* no item if empty after format expanded */
+ menu->count--;
+ return;
+ }
+ if (item->key != KEYC_UNKNOWN) {
+ key = key_string_lookup_key(item->key);
+ xasprintf(&new_item->name, "%s #[align=right](%s)", name, key);
+ } else
+ xasprintf(&new_item->name, "%s", name);
+ free(name);
+
+ if (item->command != NULL)
+ new_item->command = xstrdup(item->command);
+ else
+ new_item->command = NULL;
+ new_item->key = item->key;
+
+ width = format_width(new_item->name);
+ if (width > menu->width)
+ menu->width = width;
+}
+
+static void
+menu_parse_item(struct menu *menu, const char *s, struct client *c,
+ struct cmd_find_state *fs)
+{
+ char *copy, *first;
+ const char *second, *third;
+ struct menu_item item;
+
+ first = copy = xstrdup(s);
+ if ((second = format_skip(first, ",")) != NULL) {
+ *(char *)second++ = '\0';
+ if ((third = format_skip(second, ",")) != NULL) {
+ *(char *)third++ = '\0';
+
+ item.name = first;
+ item.command = (char *)third;
+ item.key = key_string_lookup_string(second);
+ menu_add_item(menu, &item, c, fs);
+ }
+ }
+ free(copy);
+}
+
+struct menu *
+menu_create_from_items(struct menu_item *items, u_int count, struct client *c,
+ struct cmd_find_state *fs, const char *title)
+{
+ struct menu *menu;
+ u_int i;
+
+ menu = xcalloc(1, sizeof *menu);
+ menu->title = xstrdup(title);
+
+ for (i = 0; i < count; i++)
+ menu_add_item(menu, &items[i], c, fs);
+
+ return (menu);
+}
+
+struct menu *
+menu_create_from_string(const char *s, struct client *c,
+ struct cmd_find_state *fs, const char *title)
+{
+ struct menu *menu;
+ char *copy, *string, *next;
+
+ if (*s == '\0')
+ return (NULL);
+
+ menu = xcalloc(1, sizeof *menu);
+ menu->title = xstrdup(title);
+
+ copy = string = xstrdup(s);
+ do {
+ next = (char *)format_skip(string, "|");
+ log_debug("XXX %s -- %s", next, string);
+ if (next != NULL)
+ *next++ = '\0';
+ if (*string == '\0')
+ menu_add_item(menu, NULL, c, fs);
+ else
+ menu_parse_item(menu, string, c, fs);
+ string = next;
+ } while (next != NULL);
+ free(copy);
+
+ return (menu);
+}
+
+void
+menu_free(struct menu *menu)
+{
+ u_int i;
+
+ for (i = 0; i < menu->count; i++) {
+ free(menu->items[i].name);
+ free(menu->items[i].command);
+ }
+ free(menu->items);
+
+ free(menu->title);
+ free(menu);
+}
+
+static void
+menu_draw_cb(struct client *c, __unused struct screen_redraw_ctx *ctx0)
+{
+ struct menu_data *md = c->overlay_data;
+ struct tty *tty = &c->tty;
+ struct screen *s = &md->s;
+ struct menu *menu = md->menu;
+ struct screen_write_ctx ctx;
+ u_int i, px, py;
+
+ screen_write_start(&ctx, NULL, s);
+ screen_write_clearscreen(&ctx, 8);
+ screen_write_menu(&ctx, menu, md->choice);
+ screen_write_stop(&ctx);
+
+ px = md->px;
+ py = md->py;
+
+ for (i = 0; i < screen_size_y(&md->s); i++)
+ tty_draw_line(tty, NULL, s, 0, i, menu->width + 4, px, py + i);
+
+ if (~md->flags & MENU_NOMOUSE)
+ tty_update_mode(tty, MODE_MOUSE_ALL, NULL);
+}
+
+static void
+menu_free_cb(struct client *c)
+{
+ struct menu_data *md = c->overlay_data;
+
+ if (md->item != NULL)
+ md->item->flags &= ~CMDQ_WAITING;
+
+ screen_free(&md->s);
+ menu_free(md->menu);
+ free(md);
+}
+
+static enum cmd_retval
+menu_error_cb(struct cmdq_item *item, void *data)
+{
+ char *error = data;
+
+ cmdq_error(item, "%s", error);
+ free(error);
+
+ return (CMD_RETURN_NORMAL);
+}
+
+static int
+menu_key_cb(struct client *c, struct key_event *event)
+{
+ struct menu_data *md = c->overlay_data;
+ struct menu *menu = md->menu;
+ struct mouse_event *m = &event->m;
+ u_int i;
+ int count = menu->count, old = md->choice;
+ const struct menu_item *item;
+ struct cmd_list *cmdlist;
+ struct cmdq_item *new_item;
+ char *cause;
+
+ if (KEYC_IS_MOUSE(event->key)) {
+ if (md->flags & MENU_NOMOUSE)
+ return (0);
+ if (m->x < md->px ||
+ m->x > md->px + 4 + menu->width ||
+ m->y < md->py + 1 ||
+ m->y > md->py + 1 + count - 1) {
+ if (MOUSE_RELEASE(m->b))
+ return (1);
+ if (md->choice != -1) {
+ md->choice = -1;
+ c->flags |= CLIENT_REDRAWOVERLAY;
+ }
+ return (0);
+ }
+ md->choice = m->y - (md->py + 1);
+ if (MOUSE_RELEASE(m->b))
+ goto chosen;
+ if (md->choice != old)
+ c->flags |= CLIENT_REDRAWOVERLAY;
+ return (0);
+ }
+ switch (event->key) {
+ case KEYC_UP:
+ do {
+ if (md->choice == -1 || md->choice == 0)
+ md->choice = count - 1;
+ else
+ md->choice--;
+ } while (menu->items[md->choice].name == NULL);
+ c->flags |= CLIENT_REDRAWOVERLAY;
+ return (0);
+ case KEYC_DOWN:
+ do {
+ if (md->choice == -1 || md->choice == count - 1)
+ md->choice = 0;
+ else
+ md->choice++;
+ } while (menu->items[md->choice].name == NULL);
+ c->flags |= CLIENT_REDRAWOVERLAY;
+ return (0);
+ case '\r':
+ goto chosen;
+ case '\033': /* Escape */
+ case '\003': /* C-c */
+ case '\007': /* C-g */
+ case 'q':
+ return (1);
+ }
+ for (i = 0; i < (u_int)count; i++) {
+ if (event->key == menu->items[i].key) {
+ md->choice = i;
+ goto chosen;
+ }
+ }
+ return (0);
+
+chosen:
+ if (md->choice == -1)
+ return (1);
+ item = &menu->items[md->choice];
+ if (item->name == NULL)
+ return (1);
+ if (md->cb != NULL) {
+ md->cb(md->menu, md->choice, item->key, md->data);
+ return (1);
+ }
+ cmdlist = cmd_string_parse(item->command, NULL, 0, &cause);
+ if (cmdlist == NULL && cause != NULL)
+ new_item = cmdq_get_callback(menu_error_cb, cause);
+ else if (cmdlist == NULL)
+ new_item = NULL;
+ else {
+ new_item = cmdq_get_command(cmdlist, &md->fs, NULL, 0);
+ cmd_list_free(cmdlist);
+ }
+ if (new_item != NULL) {
+ if (md->item != NULL)
+ cmdq_insert_after(md->item, new_item);
+ else
+ cmdq_append(c, new_item);
+ }
+ return (1);
+}
+
+int
+menu_display(struct menu *menu, int flags, struct cmdq_item *item, u_int px,
+ u_int py, struct client *c, struct cmd_find_state *fs, menu_choice_cb cb,
+ void *data)
+{
+ struct menu_data *md;
+
+ if (c->tty.sx < menu->width + 4 || c->tty.sy < menu->count + 2)
+ return (-1);
+
+ md = xcalloc(1, sizeof *md);
+ md->item = item;
+ md->flags = flags;
+
+ cmd_find_copy_state(&md->fs, fs);
+ screen_init(&md->s, menu->width + 4, menu->count + 2, 0);
+
+ md->px = px;
+ md->py = py;
+
+ md->menu = menu;
+ md->choice = -1;
+
+ md->cb = cb;
+ md->data = data;
+
+ server_client_set_overlay(c, 0, menu_draw_cb, menu_key_cb, menu_free_cb,
+ md);
+ return (0);
+}
diff --git a/usr.bin/tmux/tmux.1 b/usr.bin/tmux/tmux.1
index 765b145d2f0..c746c624f49 100644
--- a/usr.bin/tmux/tmux.1
+++ b/usr.bin/tmux/tmux.1
@@ -1,4 +1,4 @@
-.\" $OpenBSD: tmux.1,v 1.645 2019/05/09 08:38:13 nicm Exp $
+.\" $OpenBSD: tmux.1,v 1.646 2019/05/10 18:04:06 nicm Exp $
.\"
.\" Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
.\"
@@ -14,7 +14,7 @@
.\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
.\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
-.Dd $Mdocdate: May 9 2019 $
+.Dd $Mdocdate: May 10 2019 $
.Dt TMUX 1
.Os
.Sh NAME
@@ -3909,12 +3909,13 @@ The following variables are available, where appropriate:
.It Li "buffer_sample" Ta "" Ta "Sample of start of buffer"
.It Li "buffer_size" Ta "" Ta "Size of the specified buffer in bytes"
.It Li "client_activity" Ta "" Ta "Time client last had activity"
-.It Li "client_created" Ta "" Ta "Time client created"
.It Li "client_control_mode" Ta "" Ta "1 if client is in control mode"
+.It Li "client_created" Ta "" Ta "Time client created"
.It Li "client_discarded" Ta "" Ta "Bytes discarded when client behind"
.It Li "client_height" Ta "" Ta "Height of client"
.It Li "client_key_table" Ta "" Ta "Current key table"
.It Li "client_last_session" Ta "" Ta "Name of the client's last session"
+.It Li "client_menu" Ta "" Ta "The default client menu"
.It Li "client_name" Ta "" Ta "Name of client"
.It Li "client_pid" Ta "" Ta "PID of client process"
.It Li "client_prefix" Ta "" Ta "1 if prefix key has been pressed"
@@ -3927,11 +3928,11 @@ The following variables are available, where appropriate:
.It Li "client_width" Ta "" Ta "Width of client"
.It Li "client_written" Ta "" Ta "Bytes written to client"
.It Li "command" Ta "" Ta "Name of command in use, if any"
-.It Li "command_list_name" Ta "" Ta "Command name if listing commands"
.It Li "command_list_alias" Ta "" Ta "Command alias if listing commands"
+.It Li "command_list_name" Ta "" Ta "Command name if listing commands"
.It Li "command_list_usage" Ta "" Ta "Command usage if listing commands"
-.It Li "cursor_flag" Ta "" Ta "Pane cursor flag"
.It Li "cursor_character" Ta "" Ta "Character at cursor in pane"
+.It Li "cursor_flag" Ta "" Ta "Pane cursor flag"
.It Li "cursor_x" Ta "" Ta "Cursor X position in pane"
.It Li "cursor_y" Ta "" Ta "Cursor Y position in pane"
.It Li "history_bytes" Ta "" Ta "Number of bytes in window history"
@@ -3949,10 +3950,10 @@ The following variables are available, where appropriate:
.It Li "keypad_cursor_flag" Ta "" Ta "Pane keypad cursor flag"
.It Li "keypad_flag" Ta "" Ta "Pane keypad flag"
.It Li "line" Ta "" Ta "Line number in the list"
+.It Li "mouse_all_flag" Ta "" Ta "Pane mouse all flag"
.It Li "mouse_any_flag" Ta "" Ta "Pane mouse any flag"
.It Li "mouse_button_flag" Ta "" Ta "Pane mouse button flag"
.It Li "mouse_standard_flag" Ta "" Ta "Pane mouse standard flag"
-.It Li "mouse_all_flag" Ta "" Ta "Pane mouse all flag"
.It Li "pane_active" Ta "" Ta "1 if active pane"
.It Li "pane_at_bottom" Ta "" Ta "1 if pane is at the bottom of window"
.It Li "pane_at_left" Ta "" Ta "1 if pane is at the left of window"
@@ -3966,11 +3967,12 @@ The following variables are available, where appropriate:
.It Li "pane_height" Ta "" Ta "Height of pane"
.It Li "pane_id" Ta "#D" Ta "Unique pane ID"
.It Li "pane_in_mode" Ta "" Ta "If pane is in a mode"
-.It Li "pane_input_off" Ta "" Ta "If input to pane is disabled"
.It Li "pane_index" Ta "#P" Ta "Index of pane"
+.It Li "pane_input_off" Ta "" Ta "If input to pane is disabled"
.It Li "pane_left" Ta "" Ta "Left of pane"
.It Li "pane_marked" Ta " Ta "1 if this is the marked pane"
.It Li "pane_marked_set" Ta " Ta "1 if a market pane is set"
+.It Li "pane_menu" Ta "" Ta "The default pane menu"
.It Li "pane_mode" Ta "" Ta "Name of pane mode, if any."
.It Li "pane_pid" Ta "" Ta "PID of first process in pane"
.It Li "pane_pipe" Ta "" Ta "1 if pane is being piped"
@@ -3985,30 +3987,31 @@ The following variables are available, where appropriate:
.It Li "pane_width" Ta "" Ta "Width of pane"
.It Li "pid" Ta "" Ta "Server PID"
.It Li "rectangle_toggle" Ta "" Ta "1 if rectangle selection is activated"
+.It Li "scroll_position" Ta "" Ta "Scroll position in copy mode"
.It Li "scroll_region_lower" Ta "" Ta "Bottom of scroll region in pane"
.It Li "scroll_region_upper" Ta "" Ta "Top of scroll region in pane"
-.It Li "scroll_position" Ta "" Ta "Scroll position in copy mode"
.It Li "selection_present" Ta "" Ta "1 if selection started in copy mode"
+.It Li "session_activity" Ta "" Ta "Time of session last activity"
.It Li "session_alerts" Ta "" Ta "List of window indexes with alerts"
.It Li "session_attached" Ta "" Ta "Number of clients session is attached to"
-.It Li "session_activity" Ta "" Ta "Time of session last activity"
.It Li "session_created" Ta "" Ta "Time session created"
.It Li "session_format" Ta "" Ta "1 if format is for a session (not assuming the current)"
-.It Li "session_last_attached" Ta "" Ta "Time session last attached"
.It Li "session_group" Ta "" Ta "Name of session group"
-.It Li "session_group_size" Ta "" Ta "Size of session group"
.It Li "session_group_list" Ta "" Ta "List of sessions in group"
+.It Li "session_group_size" Ta "" Ta "Size of session group"
.It Li "session_grouped" Ta "" Ta "1 if session in a group"
.It Li "session_id" Ta "" Ta "Unique session ID"
+.It Li "session_last_attached" Ta "" Ta "Time session last attached"
.It Li "session_many_attached" Ta "" Ta "1 if multiple clients attached"
+.It Li "session_menu" Ta "" Ta "The default session menu"
.It Li "session_name" Ta "#S" Ta "Name of session"
.It Li "session_stack" Ta "" Ta "Window indexes in most recent order"
.It Li "session_windows" Ta "" Ta "Number of windows in session"
.It Li "socket_path" Ta "" Ta "Server socket path"
.It Li "start_time" Ta "" Ta "Server start time"
+.It Li "window_active" Ta "" Ta "1 if window active"
.It Li "window_activity" Ta "" Ta "Time of window last activity"
.It Li "window_activity_flag" Ta "" Ta "1 if window has activity"
-.It Li "window_active" Ta "" Ta "1 if window active"
.It Li "window_bell_flag" Ta "" Ta "1 if window has bell"
.It Li "window_bigger" Ta "" Ta "1 if window is larger than client"
.It Li "window_end_flag" Ta "" Ta "1 if window has the highest index"
@@ -4020,6 +4023,7 @@ The following variables are available, where appropriate:
.It Li "window_last_flag" Ta "" Ta "1 if window is the last used"
.It Li "window_layout" Ta "" Ta "Window layout description, ignoring zoomed window panes"
.It Li "window_linked" Ta "" Ta "1 if window is linked across sessions"
+.It Li "window_menu" Ta "" Ta "The default window menu"
.It Li "window_name" Ta "#W" Ta "Name of window"
.It Li "window_offset_x" Ta "" Ta "X offset into window if larger than client"
.It Li "window_offset_y" Ta "" Ta "Y offset into window if larger than client"
@@ -4436,6 +4440,74 @@ option.
.Pp
This command works only from inside
.Nm .
+.It Xo Ic display-menu
+.Op Fl F
+.Op Fl c Ar target-client
+.Op Fl M Ar menu
+.Op Fl t Ar target-pane
+.Op Fl T Ar title
+.Op Fl x Ar position
+.Op Fl y Ar position
+.Xc
+.D1 (alias: Ic menu)
+Display a menu on
+.Ar target-client .
+.Ar target-pane
+gives the target for any commands run from the menu.
+.Pp
+A menu is passed to
+.Fl M
+as a list of menu items separated by
+.Ql | .
+Each menu item consists of three comma-separated parts:
+.Bl -enum -width Ds
+.It name
+The menu item name.
+This is a format and may include embedded styles, see the
+.Sx FORMATS
+and
+.Sx STYLES
+sections.
+.It key
+The menu item shortcut key.
+If this is empty the menu item has no key shortcut.
+.It command
+The command run when the menu item is chosen.
+.El
+.Pp
+An empty menu item is a separator line.
+.Pp
+.Fl T
+is a format for the menu title (see
+.Sx FORMATS ) .
+.Pp
+.Fl x
+and
+.Fl y
+give the position of the menu.
+Both may be a row or column number, or one of the following special values:
+.Bl -column "XXXXX" "XXXX" -offset indent
+.It Sy "Value" Ta Sy "Flag" Ta Sy "Meaning"
+.It Li "R" Ta Fl x Ta "The right side of the terminal"
+.It Li "P" Ta "Both" Ta "The bottom left of the pane"
+.It Li "M" Ta "Both" Ta "The mouse position"
+.It Li "W" Ta Fl x Ta "The window position on the status line"
+.It Li "S" Ta Fl y Ta "The line above or below the status line"
+.El
+.Pp
+Each menu consists of items followed by a key shortcut shown in brackets.
+If the menu is too large to fit on the terminal, it is not displayed.
+Pressing the key shortcut chooses the corresponding item.
+If the mouse is enabled and the menu is opened from a mouse key binding, releasing
+the mouse button with an item selected will choose that item.
+The following keys are also available:
+.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 "q" Ta "Exit menu"
+.El
.It Xo Ic display-message
.Op Fl aIpv
.Op Fl c Ar target-client
diff --git a/usr.bin/tmux/tmux.h b/usr.bin/tmux/tmux.h
index 74bf710149b..7ff83cd7e2c 100644
--- a/usr.bin/tmux/tmux.h
+++ b/usr.bin/tmux/tmux.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: tmux.h,v 1.894 2019/05/10 14:12:47 nicm Exp $ */
+/* $OpenBSD: tmux.h,v 1.895 2019/05/10 18:04:06 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -748,20 +748,20 @@ struct screen_redraw_ctx {
#define screen_hsize(s) ((s)->grid->hsize)
#define screen_hlimit(s) ((s)->grid->hlimit)
-/* Menu item. */
+/* Menu. */
struct menu_item {
- char *name;
- char *command;
- key_code key;
+ char *name;
+ char *command;
+ key_code key;
};
-
-/* Menu. */
struct menu {
char *title;
struct menu_item *items;
u_int count;
u_int width;
};
+typedef void (*menu_choice_cb)(struct menu *, u_int, key_code, void *);
+#define MENU_NOMOUSE 0x1
/*
* Window mode. Windows can be in several modes and this is used to call the
@@ -2550,6 +2550,16 @@ void printflike(1, 2) log_debug(const char *, ...);
__dead void printflike(1, 2) fatal(const char *, ...);
__dead void printflike(1, 2) fatalx(const char *, ...);
+/* menu.c */
+struct menu *menu_create_from_items(struct menu_item *, u_int,
+ struct client *, struct cmd_find_state *, const char *);
+struct menu *menu_create_from_string(const char *, struct client *,
+ struct cmd_find_state *, const char *);
+void menu_free(struct menu *);
+int menu_display(struct menu *, int, struct cmdq_item *, u_int,
+ u_int, struct client *, struct cmd_find_state *,
+ menu_choice_cb, void *);
+
/* style.c */
int style_parse(struct style *,const struct grid_cell *,
const char *);