xref: /openbsd-src/usr.bin/tmux/menu.c (revision e603c72f713dd59b67030a9b97ec661800da159e)
1 /* $OpenBSD: menu.c,v 1.5 2019/05/23 11:13:30 nicm Exp $ */
2 
3 /*
4  * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 
21 #include <stdlib.h>
22 #include <string.h>
23 
24 #include "tmux.h"
25 
26 struct menu_data {
27 	struct cmdq_item	*item;
28 	int			 flags;
29 
30 	struct cmd_find_state	 fs;
31 	struct screen		 s;
32 
33 	u_int			 px;
34 	u_int			 py;
35 
36 	struct menu		*menu;
37 	int			 choice;
38 
39 	menu_choice_cb		 cb;
40 	void			*data;
41 };
42 
43 static void
44 menu_add_item(struct menu *menu, struct menu_item *item, struct client *c,
45     struct cmd_find_state *fs)
46 {
47 	struct menu_item	*new_item;
48 	const char		*key;
49 	char			*name;
50 	u_int			 width;
51 
52 	menu->items = xreallocarray(menu->items, menu->count + 1,
53 	    sizeof *menu->items);
54 	new_item = &menu->items[menu->count++];
55 	memset(new_item, 0, sizeof *new_item);
56 
57 	if (item == NULL || *item->name == '\0') /* horizontal line */
58 		return;
59 	if (fs != NULL) {
60 		name = format_single(NULL, item->name, c, fs->s, fs->wl,
61 		    fs->wp);
62 	} else
63 		name = xstrdup(item->name);
64 	if (*name == '\0') { /* no item if empty after format expanded */
65 		menu->count--;
66 		return;
67 	}
68 	if (item->key != KEYC_UNKNOWN) {
69 		key = key_string_lookup_key(item->key);
70 		xasprintf(&new_item->name, "%s #[align=right](%s)", name, key);
71 	} else
72 		xasprintf(&new_item->name, "%s", name);
73 	free(name);
74 
75 	if (item->command != NULL)
76 		new_item->command = xstrdup(item->command);
77 	else
78 		new_item->command = NULL;
79 	new_item->key = item->key;
80 
81 	width = format_width(new_item->name);
82 	if (width > menu->width)
83 		menu->width = width;
84 }
85 
86 static void
87 menu_parse_item(struct menu *menu, const char *s, struct client *c,
88     struct cmd_find_state *fs)
89 {
90 	char			*copy, *first;
91 	const char		*second, *third;
92 	struct menu_item	 item;
93 
94 	first = copy = xstrdup(s);
95 	if ((second = format_skip(first, ",")) != NULL) {
96 		*(char *)second++ = '\0';
97 		if ((third = format_skip(second, ",")) != NULL) {
98 			*(char *)third++ = '\0';
99 
100 			item.name = first;
101 			item.command = (char *)third;
102 			item.key = key_string_lookup_string(second);
103 			menu_add_item(menu, &item, c, fs);
104 		}
105 	}
106 	free(copy);
107 }
108 
109 struct menu *
110 menu_create(const char *s, struct client *c, struct cmd_find_state *fs,
111     const char *title)
112 {
113 	struct menu	*menu;
114 	char		*copy, *string, *next;
115 
116 	if (*s == '\0')
117 		return (NULL);
118 
119 	menu = xcalloc(1, sizeof *menu);
120 	menu->title = xstrdup(title);
121 
122 	copy = string = xstrdup(s);
123 	do {
124 		next = (char *)format_skip(string, "|");
125 		if (next != NULL)
126 			*next++ = '\0';
127 		if (*string == '\0')
128 			menu_add_item(menu, NULL, c, fs);
129 		else
130 			menu_parse_item(menu, string, c, fs);
131 		string = next;
132 	} while (next != NULL);
133 	free(copy);
134 
135 	return (menu);
136 }
137 
138 void
139 menu_free(struct menu *menu)
140 {
141 	u_int	i;
142 
143 	for (i = 0; i < menu->count; i++) {
144 		free(menu->items[i].name);
145 		free(menu->items[i].command);
146 	}
147 	free(menu->items);
148 
149 	free(menu->title);
150 	free(menu);
151 }
152 
153 static void
154 menu_draw_cb(struct client *c, __unused struct screen_redraw_ctx *ctx0)
155 {
156 	struct menu_data	*md = c->overlay_data;
157 	struct tty		*tty = &c->tty;
158 	struct screen		*s = &md->s;
159 	struct menu		*menu = md->menu;
160 	struct screen_write_ctx	 ctx;
161 	u_int			 i, px, py;
162 
163 	screen_write_start(&ctx, NULL, s);
164 	screen_write_clearscreen(&ctx, 8);
165 	screen_write_menu(&ctx, menu, md->choice);
166 	screen_write_stop(&ctx);
167 
168 	px = md->px;
169 	py = md->py;
170 
171 	for (i = 0; i < screen_size_y(&md->s); i++)
172 		tty_draw_line(tty, NULL, s, 0, i, menu->width + 4, px, py + i);
173 
174 	if (~md->flags & MENU_NOMOUSE)
175 		tty_update_mode(tty, MODE_MOUSE_ALL, NULL);
176 }
177 
178 static void
179 menu_free_cb(struct client *c)
180 {
181 	struct menu_data	*md = c->overlay_data;
182 
183 	if (md->item != NULL)
184 		md->item->flags &= ~CMDQ_WAITING;
185 
186 	if (md->cb != NULL)
187 		md->cb(md->menu, UINT_MAX, KEYC_NONE, md->data);
188 
189 	screen_free(&md->s);
190 	menu_free(md->menu);
191 	free(md);
192 }
193 
194 static int
195 menu_key_cb(struct client *c, struct key_event *event)
196 {
197 	struct menu_data		*md = c->overlay_data;
198 	struct menu			*menu = md->menu;
199 	struct mouse_event		*m = &event->m;
200 	u_int				 i;
201 	int				 count = menu->count, old = md->choice;
202 	const struct menu_item		*item;
203 	struct cmdq_item		*new_item;
204 	struct cmd_parse_result		*pr;
205 
206 	if (KEYC_IS_MOUSE(event->key)) {
207 		if (md->flags & MENU_NOMOUSE)
208 			return (0);
209 		if (m->x < md->px ||
210 		    m->x > md->px + 4 + menu->width ||
211 		    m->y < md->py + 1 ||
212 		    m->y > md->py + 1 + count - 1) {
213 			if (MOUSE_RELEASE(m->b))
214 				return (1);
215 			if (md->choice != -1) {
216 				md->choice = -1;
217 				c->flags |= CLIENT_REDRAWOVERLAY;
218 			}
219 			return (0);
220 		}
221 		md->choice = m->y - (md->py + 1);
222 		if (MOUSE_RELEASE(m->b))
223 			goto chosen;
224 		if (md->choice != old)
225 			c->flags |= CLIENT_REDRAWOVERLAY;
226 		return (0);
227 	}
228 	switch (event->key) {
229 	case KEYC_UP:
230 		do {
231 			if (md->choice == -1 || md->choice == 0)
232 				md->choice = count - 1;
233 			else
234 				md->choice--;
235 		} while (menu->items[md->choice].name == NULL);
236 		c->flags |= CLIENT_REDRAWOVERLAY;
237 		return (0);
238 	case KEYC_DOWN:
239 		do {
240 			if (md->choice == -1 || md->choice == count - 1)
241 				md->choice = 0;
242 		else
243 			md->choice++;
244 		} while (menu->items[md->choice].name == NULL);
245 		c->flags |= CLIENT_REDRAWOVERLAY;
246 		return (0);
247 	case '\r':
248 		goto chosen;
249 	case '\033': /* Escape */
250 	case '\003': /* C-c */
251 	case '\007': /* C-g */
252 	case 'q':
253 		return (1);
254 	}
255 	for (i = 0; i < (u_int)count; i++) {
256 		if (event->key == menu->items[i].key) {
257 			md->choice = i;
258 			goto chosen;
259 		}
260 	}
261 	return (0);
262 
263 chosen:
264 	if (md->choice == -1)
265 		return (1);
266 	item = &menu->items[md->choice];
267 	if (item->name == NULL)
268 		return (1);
269 	if (md->cb != NULL) {
270 	    md->cb(md->menu, md->choice, item->key, md->data);
271 	    md->cb = NULL;
272 	    return (1);
273 	}
274 
275 	pr = cmd_parse_from_string(item->command, NULL);
276 	switch (pr->status) {
277 	case CMD_PARSE_EMPTY:
278 		new_item = NULL;
279 		break;
280 	case CMD_PARSE_ERROR:
281 		new_item = cmdq_get_error(pr->error);
282 		free(pr->error);
283 		cmdq_append(c, new_item);
284 		break;
285 	case CMD_PARSE_SUCCESS:
286 		new_item = cmdq_get_command(pr->cmdlist, NULL, NULL, 0);
287 		cmd_list_free(pr->cmdlist);
288 		cmdq_append(c, new_item);
289 		break;
290 	}
291 	return (1);
292 }
293 
294 int
295 menu_display(struct menu *menu, int flags, struct cmdq_item *item, u_int px,
296     u_int py, struct client *c, struct cmd_find_state *fs, menu_choice_cb cb,
297     void *data)
298 {
299 	struct menu_data	*md;
300 
301 	if (c->tty.sx < menu->width + 4 || c->tty.sy < menu->count + 2)
302 		return (-1);
303 
304 	md = xcalloc(1, sizeof *md);
305 	md->item = item;
306 	md->flags = flags;
307 
308 	if (fs != NULL)
309 		cmd_find_copy_state(&md->fs, fs);
310 	screen_init(&md->s, menu->width + 4, menu->count + 2, 0);
311 
312 	md->px = px;
313 	md->py = py;
314 
315 	md->menu = menu;
316 	md->choice = -1;
317 
318 	md->cb = cb;
319 	md->data = data;
320 
321 	server_client_set_overlay(c, 0, menu_draw_cb, menu_key_cb, menu_free_cb,
322 	    md);
323 	return (0);
324 }
325