From 6dcca5fda4b21cb1903204d6945bd7b418858afd Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 10 May 2019 18:04:06 +0000 Subject: [PATCH 1/2] 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. --- Makefile | 2 + cmd-display-menu.c | 163 +++++++++++++++++++++ cmd.c | 2 + format.c | 48 +++++++ key-bindings.c | 5 + menu.c | 342 +++++++++++++++++++++++++++++++++++++++++++++ tmux.1 | 92 ++++++++++-- tmux.h | 24 +++- 8 files changed, 661 insertions(+), 17 deletions(-) create mode 100644 cmd-display-menu.c create mode 100644 menu.c diff --git a/Makefile b/Makefile index 1dd4ed8c..90ecbd84 100644 --- a/Makefile +++ b/Makefile @@ -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/cmd-display-menu.c b/cmd-display-menu.c new file mode 100644 index 00000000..27bf454d --- /dev/null +++ b/cmd-display-menu.c @@ -0,0 +1,163 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2019 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" + +/* + * 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/cmd.c b/cmd.c index c81ed12d..59f2d4e9 100644 --- a/cmd.c +++ b/cmd.c @@ -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/format.c b/format.c index febf160b..11c1a5a3 100644 --- a/format.c +++ b/format.c @@ -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/key-bindings.c b/key-bindings.c index 4d5f7278..ca4fdc52 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -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/menu.c b/menu.c new file mode 100644 index 00000000..0443d22f --- /dev/null +++ b/menu.c @@ -0,0 +1,342 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2019 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" + +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/tmux.1 b/tmux.1 index fcc1b9c1..f2c529d7 100644 --- a/tmux.1 +++ b/tmux.1 @@ -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/tmux.h b/tmux.h index 7bff1a1a..757e7673 100644 --- a/tmux.h +++ b/tmux.h @@ -748,20 +748,20 @@ struct screen_redraw_ctx { #define screen_hsize(s) ((s)->grid->hsize) #define screen_hlimit(s) ((s)->grid->hlimit) -/* Menu item. */ -struct menu_item { - char *name; - char *command; - key_code key; -}; - /* Menu. */ +struct menu_item { + char *name; + char *command; + key_code key; +}; 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 *); From d62fd78655b305f4db9b41ff3b5a35b909674d74 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 10 May 2019 18:09:51 +0000 Subject: [PATCH 2/2] Fix a typo in previous (, -> :). --- format.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/format.c b/format.c index 11c1a5a3..79e7a94b 100644 --- a/format.c +++ b/format.c @@ -71,8 +71,8 @@ static void format_defaults_winlink(struct format_tree *, struct winlink *); "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|" \ + "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|" \