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.
pull/1735/head
nicm 2019-05-10 18:04:06 +00:00
parent 004a9b52f0
commit 6dcca5fda4
8 changed files with 661 additions and 17 deletions

View File

@ -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 \

163
cmd-display-menu.c Normal file
View File

@ -0,0 +1,163 @@
/* $OpenBSD$ */
/*
* 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);
}

2
cmd.c
View File

@ -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,

View File

@ -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);

View File

@ -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",

342
menu.c Normal file
View File

@ -0,0 +1,342 @@
/* $OpenBSD$ */
/*
* 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);
}

92
tmux.1
View File

@ -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

24
tmux.h
View File

@ -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 *);