diff --git a/Makefile.am b/Makefile.am index 4f267a230..06da1e85c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -72,6 +72,7 @@ dist_tmux_SOURCES = \ cmd-copy-mode.c \ cmd-detach-client.c \ cmd-display-message.c \ + cmd-display-menu.c \ cmd-display-panes.c \ cmd-find-window.c \ cmd-find.c \ @@ -143,6 +144,7 @@ dist_tmux_SOURCES = \ 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 000000000..e5badb9fc --- /dev/null +++ b/cmd-display-menu.c @@ -0,0 +1,159 @@ +/* $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:M:t:T:x:y:", 0, 0 }, + .usage = "[-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 *option, *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); + + option = args_get(args, 'M'); + if (option == NULL) { + cmdq_error(item, "no menu specified"); + return (CMD_RETURN_ERROR); + } + if (args_has(args, 'T')) + title = format_single(NULL, args_get(args, 'T'), c, s, wl, wp); + else + title = xstrdup(""); + menu = menu_create_from_option(option, c, fs, title); + free(title); + if (menu == NULL) { + cmdq_error(item, "unknown menu %s", option); + return (CMD_RETURN_ERROR); + } + + if (menu->count == 0) + 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_ERROR); + + return (CMD_RETURN_WAIT); +} diff --git a/cmd.c b/cmd.c index 3e35ef3ca..7f7348a9c 100644 --- a/cmd.c +++ b/cmd.c @@ -41,6 +41,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; @@ -129,6 +130,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/key-bindings.c b/key-bindings.c index 4d5f7278b..ca7d63d5e 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 -Mclient-menu -T \"#[align=centre]#{client_name}\"", + "bind -n MouseDown3StatusLeft display-menu -t= -xM -yS -Msession-menu -T \"#[align=centre]#{session_name}\"", + "bind -n MouseDown3Status display-menu -t= -xW -yS -Mwindow-menu -T \"#[align=centre]#{window_index}:#{window_name}\"", + "bind -n M-MouseDown3Pane display-menu -t= -xM -yM -Mpane-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 000000000..16de17c30 --- /dev/null +++ b/menu.c @@ -0,0 +1,335 @@ +/* $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; +} + +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_option(const char *option, struct client *c, + struct cmd_find_state *fs, const char *title) +{ + struct options_entry *o; + struct options_array_item *a; + struct menu *menu; + char *copy, *first; + const char *value, *second, *third; + struct menu_item item; + int valid; + + o = options_get(global_options, option); + if (o == NULL || !options_isarray(o)) + return (NULL); + + menu = xcalloc(1, sizeof *menu); + menu->title = xstrdup(title); + + a = options_array_first(o); + while (a != NULL) { + value = options_array_item_value(a)->string; + if (*value == '\0') + menu_add_item(menu, NULL, c, fs); + else { + valid = 1; + first = copy = xstrdup(value); + if ((second = format_skip(first, ",")) != NULL) { + *(char *)second++ = '\0'; + if ((third = format_skip(second, ",")) != NULL) + *(char *)third++ = '\0'; + else + valid = 0; + } else + valid = 0; + if (valid) { + item.name = first; + item.command = (char *)third; + item.key = key_string_lookup_string(second); + menu_add_item(menu, &item, c, fs); + } + free(copy); + } + a = options_array_next(a); + } + 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: + 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/options-table.c b/options-table.c index 19cf39f44..ba1359ff4 100644 --- a/options-table.c +++ b/options-table.c @@ -129,6 +129,60 @@ static const char *options_table_status_format_default[] = { OPTIONS_TABLE_STATUS_FORMAT1, OPTIONS_TABLE_STATUS_FORMAT2, NULL }; +/* Menu option defaults. */ +static const char *options_table_session_menu_default[] = { + "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", + NULL +}; +static const char *options_table_window_menu_default[] = { + "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", + NULL +}; +static const char *options_table_pane_menu_default[] = { + "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", + NULL +}; +static const char *options_table_client_menu_default[] = { + "Detach,d,detach-client", + "Detach & Kill,X,detach-client -P", + "Detach Others,o,detach-client -a", + "", + "#{?#{lock-command},Lock,},l,lock-client", + NULL +}; +static const char *options_table_user_menu_default[] = { + NULL +}; + /* Helper for hook options. */ #define OPTIONS_TABLE_HOOK(hook_name, default_value) \ { .name = hook_name, \ @@ -788,6 +842,42 @@ const struct options_table_entry options_table[] = { .default_num = 1 }, + /* Menu options. */ + { .name = "session-menu", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_ARRAY, + .default_arr = options_table_session_menu_default + }, + + { .name = "window-menu", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_ARRAY, + .default_arr = options_table_window_menu_default + }, + + { .name = "pane-menu", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_ARRAY, + .default_arr = options_table_pane_menu_default + }, + + { .name = "client-menu", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_ARRAY, + .default_arr = options_table_client_menu_default + }, + + { .name = "user-menu", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_ARRAY, + .default_arr = options_table_user_menu_default + }, + /* Hook options. */ OPTIONS_TABLE_HOOK("after-bind-key", ""), OPTIONS_TABLE_HOOK("after-capture-pane", ""), diff --git a/screen-write.c b/screen-write.c index 237b63593..ad3808aee 100644 --- a/screen-write.c +++ b/screen-write.c @@ -403,6 +403,44 @@ screen_write_vline(struct screen_write_ctx *ctx, u_int ny, int top, int bottom) screen_write_set_cursor(ctx, cx, cy); } +/* Draw a menu on screen. */ +void +screen_write_menu(struct screen_write_ctx *ctx, struct menu *menu, int choice) +{ + struct screen *s = ctx->s; + struct grid_cell gc; + u_int cx, cy, i, j; + + cx = s->cx; + cy = s->cy; + + memcpy(&gc, &grid_default_cell, sizeof gc); + + screen_write_box(ctx, menu->width + 4, menu->count + 2); + screen_write_cursormove(ctx, cx + 2, cy, 0); + format_draw(ctx, &gc, menu->width, menu->title, NULL); + + for (i = 0; i < menu->count; i++) { + if (menu->items[i].name == NULL) { + screen_write_cursormove(ctx, cx, cy + 1 + i, 0); + screen_write_hline(ctx, menu->width + 4, 1, 1); + } else { + if (choice >= 0 && i == (u_int)choice) + gc.attr |= GRID_ATTR_REVERSE; + screen_write_cursormove(ctx, cx + 2, cy + 1 + i, 0); + for (j = 0; j < menu->width; j++) + screen_write_putc(ctx, &gc, ' '); + screen_write_cursormove(ctx, cx + 2, cy + 1 + i, 0); + format_draw(ctx, &gc, menu->width, menu->items[i].name, + NULL); + if (choice >= 0 && i == (u_int)choice) + gc.attr &= ~GRID_ATTR_REVERSE; + } + } + + screen_write_set_cursor(ctx, cx, cy); +} + /* Draw a box on screen. */ void screen_write_box(struct screen_write_ctx *ctx, u_int nx, u_int ny) diff --git a/tmux.1 b/tmux.1 index 75d3506c5..a3c053119 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2629,6 +2629,32 @@ Available server options are: Set the number of buffers; as new buffers are added to the top of the stack, old ones are removed from the bottom if necessary to maintain this maximum length. +.It Xo Ic client-menu[] +.Ar name,key,command +.Xc +Specify the client menu. +See the +.Ic display-menu +command for more information on menus. +.Pp +Each item in the array specifies a menu item. +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 array item is a separator line in the menu. .It Xo Ic command-alias[] .Ar name=value .Xc @@ -2697,7 +2723,34 @@ will write command prompt history on exit and load it from on start. Set the number of error or information messages to save in the message log for each client. The default is 100. +.It Xo Ic pane-menu[] +.Ar name,key,command +.Xc +Specify the pane menu. +See the +.Ic display-menu +command for more information on menus and the +.Ic client-menu +option for the format of menu options. +.It Xo Ic session-menu[] +.Ar name,key,command +.Xc +Specify the session menu. +See the +.Ic display-menu +command for more information on menus and the +.Ic client-menu +option for the format of menu options. .It Xo Ic set-clipboard +.It Xo Ic user-menu[] +.Ar name,key,command +.Xc +Specify the user menu. +See the +.Ic display-menu +command for more information on menus and the +.Ic client-menu +option for the format of menu options. .Op Ic on | external | off .Xc Attempt to set the terminal clipboard content using the @@ -2762,6 +2815,15 @@ for all terminal types matching The terminal entry value is passed through .Xr strunvis 3 before interpretation. +.It Xo Ic window-menu[] +.Ar name,key,command +.Xc +Specify the window menu. +See the +.Ic display-menu +command for more information on menus and the +.Ic client-menu +option for the format of menu options. .El .Pp Available session options are: @@ -4443,6 +4505,62 @@ option. .Pp This command works only from inside .Nm . +.It Xo Ic display-menu +.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. +A menu is specified by a server option, the option name must be given to +.Fl M . +It may be one of: +.Ic client-menu , +.Ic session-menu , +.Ic window-menu , +.Ic pane-menu , +or +.Ic user-menu . +See the +.Ic client-menu +option for the option format. +.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 d7293adaa..765dd7629 100644 --- a/tmux.h +++ b/tmux.h @@ -750,6 +750,21 @@ struct screen_redraw_ctx { #define screen_hsize(s) ((s)->grid->hsize) #define screen_hlimit(s) ((s)->grid->hlimit) +/* 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 * right function to handle input and output. @@ -1689,7 +1704,7 @@ char *paste_make_sample(struct paste_buffer *); #define FORMAT_PANE 0x80000000U #define FORMAT_WINDOW 0x40000000U struct format_tree; -const char *format_skip(const char *s, const char *end); +const char *format_skip(const char *, const char *); int format_true(const char *); struct format_tree *format_create(struct client *, struct cmdq_item *, int, int); @@ -2202,6 +2217,7 @@ void screen_write_fast_copy(struct screen_write_ctx *, struct screen *, u_int, u_int, u_int, u_int); void screen_write_hline(struct screen_write_ctx *, u_int, int, int); void screen_write_vline(struct screen_write_ctx *, u_int, int, int); +void screen_write_menu(struct screen_write_ctx *, struct menu *, 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); @@ -2538,6 +2554,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_option(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 *);