xref: /openbsd-src/usr.bin/tmux/cmd.c (revision 5b859c19fe53bbea08f5c342e0a4470e99f883e1)
1 /* $OpenBSD: cmd.c,v 1.98 2014/10/08 17:35:58 nicm Exp $ */
2 
3 /*
4  * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
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 #include <sys/time.h>
21 
22 #include <fnmatch.h>
23 #include <paths.h>
24 #include <pwd.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 
29 #include "tmux.h"
30 
31 const struct cmd_entry *cmd_table[] = {
32 	&cmd_attach_session_entry,
33 	&cmd_bind_key_entry,
34 	&cmd_break_pane_entry,
35 	&cmd_capture_pane_entry,
36 	&cmd_choose_buffer_entry,
37 	&cmd_choose_client_entry,
38 	&cmd_choose_session_entry,
39 	&cmd_choose_tree_entry,
40 	&cmd_choose_window_entry,
41 	&cmd_clear_history_entry,
42 	&cmd_clock_mode_entry,
43 	&cmd_command_prompt_entry,
44 	&cmd_confirm_before_entry,
45 	&cmd_copy_mode_entry,
46 	&cmd_delete_buffer_entry,
47 	&cmd_detach_client_entry,
48 	&cmd_display_message_entry,
49 	&cmd_display_panes_entry,
50 	&cmd_find_window_entry,
51 	&cmd_has_session_entry,
52 	&cmd_if_shell_entry,
53 	&cmd_join_pane_entry,
54 	&cmd_kill_pane_entry,
55 	&cmd_kill_server_entry,
56 	&cmd_kill_session_entry,
57 	&cmd_kill_window_entry,
58 	&cmd_last_pane_entry,
59 	&cmd_last_window_entry,
60 	&cmd_link_window_entry,
61 	&cmd_list_buffers_entry,
62 	&cmd_list_clients_entry,
63 	&cmd_list_commands_entry,
64 	&cmd_list_keys_entry,
65 	&cmd_list_panes_entry,
66 	&cmd_list_sessions_entry,
67 	&cmd_list_windows_entry,
68 	&cmd_load_buffer_entry,
69 	&cmd_lock_client_entry,
70 	&cmd_lock_server_entry,
71 	&cmd_lock_session_entry,
72 	&cmd_move_pane_entry,
73 	&cmd_move_window_entry,
74 	&cmd_new_session_entry,
75 	&cmd_new_window_entry,
76 	&cmd_next_layout_entry,
77 	&cmd_next_window_entry,
78 	&cmd_paste_buffer_entry,
79 	&cmd_pipe_pane_entry,
80 	&cmd_previous_layout_entry,
81 	&cmd_previous_window_entry,
82 	&cmd_refresh_client_entry,
83 	&cmd_rename_session_entry,
84 	&cmd_rename_window_entry,
85 	&cmd_resize_pane_entry,
86 	&cmd_respawn_pane_entry,
87 	&cmd_respawn_window_entry,
88 	&cmd_rotate_window_entry,
89 	&cmd_run_shell_entry,
90 	&cmd_save_buffer_entry,
91 	&cmd_select_layout_entry,
92 	&cmd_select_pane_entry,
93 	&cmd_select_window_entry,
94 	&cmd_send_keys_entry,
95 	&cmd_send_prefix_entry,
96 	&cmd_server_info_entry,
97 	&cmd_set_buffer_entry,
98 	&cmd_set_environment_entry,
99 	&cmd_set_option_entry,
100 	&cmd_set_window_option_entry,
101 	&cmd_show_buffer_entry,
102 	&cmd_show_environment_entry,
103 	&cmd_show_messages_entry,
104 	&cmd_show_options_entry,
105 	&cmd_show_window_options_entry,
106 	&cmd_source_file_entry,
107 	&cmd_split_window_entry,
108 	&cmd_start_server_entry,
109 	&cmd_suspend_client_entry,
110 	&cmd_swap_pane_entry,
111 	&cmd_swap_window_entry,
112 	&cmd_switch_client_entry,
113 	&cmd_unbind_key_entry,
114 	&cmd_unlink_window_entry,
115 	&cmd_wait_for_entry,
116 	NULL
117 };
118 
119 int		 cmd_session_better(struct session *, struct session *, int);
120 struct session	*cmd_choose_session_list(struct sessionslist *);
121 struct session	*cmd_choose_session(int);
122 struct client	*cmd_choose_client(struct clients *);
123 struct client	*cmd_lookup_client(const char *);
124 struct session	*cmd_lookup_session(struct cmd_q *, const char *, int *);
125 struct session	*cmd_lookup_session_id(const char *);
126 struct winlink	*cmd_lookup_window(struct session *, const char *, int *);
127 int		 cmd_lookup_index(struct session *, const char *, int *);
128 struct winlink	*cmd_lookup_winlink_windowid(struct session *, const char *);
129 struct session	*cmd_window_session(struct cmd_q *, struct window *,
130 		    struct winlink **);
131 struct winlink	*cmd_find_window_offset(const char *, struct session *, int *);
132 int		 cmd_find_index_offset(const char *, struct session *, int *);
133 struct window_pane *cmd_find_pane_offset(const char *, struct winlink *);
134 
135 int
136 cmd_pack_argv(int argc, char **argv, char *buf, size_t len)
137 {
138 	size_t	arglen;
139 	int	i;
140 
141 	if (argc == 0)
142 		return (0);
143 
144 	*buf = '\0';
145 	for (i = 0; i < argc; i++) {
146 		if (strlcpy(buf, argv[i], len) >= len)
147 			return (-1);
148 		arglen = strlen(argv[i]) + 1;
149 		buf += arglen;
150 		len -= arglen;
151 	}
152 
153 	return (0);
154 }
155 
156 int
157 cmd_unpack_argv(char *buf, size_t len, int argc, char ***argv)
158 {
159 	int	i;
160 	size_t	arglen;
161 
162 	if (argc == 0)
163 		return (0);
164 	*argv = xcalloc(argc, sizeof **argv);
165 
166 	buf[len - 1] = '\0';
167 	for (i = 0; i < argc; i++) {
168 		if (len == 0) {
169 			cmd_free_argv(argc, *argv);
170 			return (-1);
171 		}
172 
173 		arglen = strlen(buf) + 1;
174 		(*argv)[i] = xstrdup(buf);
175 		buf += arglen;
176 		len -= arglen;
177 	}
178 
179 	return (0);
180 }
181 
182 char **
183 cmd_copy_argv(int argc, char **argv)
184 {
185 	char	**new_argv;
186 	int	  i;
187 
188 	if (argc == 0)
189 		return (NULL);
190 	new_argv = xcalloc(argc + 1, sizeof *new_argv);
191 	for (i = 0; i < argc; i++) {
192 		if (argv[i] != NULL)
193 			new_argv[i] = xstrdup(argv[i]);
194 	}
195 	return (new_argv);
196 }
197 
198 void
199 cmd_free_argv(int argc, char **argv)
200 {
201 	int	i;
202 
203 	if (argc == 0)
204 		return;
205 	for (i = 0; i < argc; i++)
206 		free(argv[i]);
207 	free(argv);
208 }
209 
210 char *
211 cmd_stringify_argv(int argc, char **argv)
212 {
213 	char	*buf;
214 	int	 i;
215 	size_t	 len;
216 
217 	if (argc == 0)
218 		return (xstrdup(""));
219 
220 	len = 0;
221 	buf = NULL;
222 
223 	for (i = 0; i < argc; i++) {
224 		len += strlen(argv[i]) + 1;
225 		buf = xrealloc(buf, len);
226 
227 		if (i == 0)
228 			*buf = '\0';
229 		else
230 			strlcat(buf, " ", len);
231 		strlcat(buf, argv[i], len);
232 	}
233 	return (buf);
234 }
235 
236 struct cmd *
237 cmd_parse(int argc, char **argv, const char *file, u_int line, char **cause)
238 {
239 	const struct cmd_entry **entryp, *entry;
240 	struct cmd		*cmd;
241 	struct args		*args;
242 	char			 s[BUFSIZ];
243 	int			 ambiguous = 0;
244 
245 	*cause = NULL;
246 	if (argc == 0) {
247 		xasprintf(cause, "no command");
248 		return (NULL);
249 	}
250 
251 	entry = NULL;
252 	for (entryp = cmd_table; *entryp != NULL; entryp++) {
253 		if ((*entryp)->alias != NULL &&
254 		    strcmp((*entryp)->alias, argv[0]) == 0) {
255 			ambiguous = 0;
256 			entry = *entryp;
257 			break;
258 		}
259 
260 		if (strncmp((*entryp)->name, argv[0], strlen(argv[0])) != 0)
261 			continue;
262 		if (entry != NULL)
263 			ambiguous = 1;
264 		entry = *entryp;
265 
266 		/* Bail now if an exact match. */
267 		if (strcmp(entry->name, argv[0]) == 0)
268 			break;
269 	}
270 	if (ambiguous)
271 		goto ambiguous;
272 	if (entry == NULL) {
273 		xasprintf(cause, "unknown command: %s", argv[0]);
274 		return (NULL);
275 	}
276 
277 	args = args_parse(entry->args_template, argc, argv);
278 	if (args == NULL)
279 		goto usage;
280 	if (entry->args_lower != -1 && args->argc < entry->args_lower)
281 		goto usage;
282 	if (entry->args_upper != -1 && args->argc > entry->args_upper)
283 		goto usage;
284 
285 	cmd = xcalloc(1, sizeof *cmd);
286 	cmd->entry = entry;
287 	cmd->args = args;
288 
289 	if (file != NULL)
290 		cmd->file = xstrdup(file);
291 	cmd->line = line;
292 
293 	return (cmd);
294 
295 ambiguous:
296 	*s = '\0';
297 	for (entryp = cmd_table; *entryp != NULL; entryp++) {
298 		if (strncmp((*entryp)->name, argv[0], strlen(argv[0])) != 0)
299 			continue;
300 		if (strlcat(s, (*entryp)->name, sizeof s) >= sizeof s)
301 			break;
302 		if (strlcat(s, ", ", sizeof s) >= sizeof s)
303 			break;
304 	}
305 	s[strlen(s) - 2] = '\0';
306 	xasprintf(cause, "ambiguous command: %s, could be: %s", argv[0], s);
307 	return (NULL);
308 
309 usage:
310 	if (args != NULL)
311 		args_free(args);
312 	xasprintf(cause, "usage: %s %s", entry->name, entry->usage);
313 	return (NULL);
314 }
315 
316 size_t
317 cmd_print(struct cmd *cmd, char *buf, size_t len)
318 {
319 	size_t	off, used;
320 
321 	off = xsnprintf(buf, len, "%s ", cmd->entry->name);
322 	if (off + 1 < len) {
323 		used = args_print(cmd->args, buf + off, len - off - 1);
324 		if (used == 0)
325 			off--;
326 		else
327 			off += used;
328 		buf[off] = '\0';
329 	}
330 	return (off);
331 }
332 
333 /*
334  * Figure out the current session. Use: 1) the current session, if the command
335  * context has one; 2) the most recently used session containing the pty of the
336  * calling client, if any; 3) the session specified in the TMUX variable from
337  * the environment (as passed from the client); 4) the most recently used
338  * session from all sessions.
339  */
340 struct session *
341 cmd_current_session(struct cmd_q *cmdq, int prefer_unattached)
342 {
343 	struct client		*c = cmdq->client;
344 	struct session		*s;
345 	struct sessionslist	 ss;
346 	struct winlink		*wl;
347 	struct window_pane	*wp;
348 	const char		*path;
349 	int			 found;
350 
351 	if (c != NULL && c->session != NULL)
352 		return (c->session);
353 
354 	/*
355 	 * If the name of the calling client's pty is known, build a list of
356 	 * the sessions that contain it and if any choose either the first or
357 	 * the newest.
358 	 */
359 	path = c == NULL ? NULL : c->tty.path;
360 	if (path != NULL) {
361 		ARRAY_INIT(&ss);
362 		RB_FOREACH(s, sessions, &sessions) {
363 			found = 0;
364 			RB_FOREACH(wl, winlinks, &s->windows) {
365 				TAILQ_FOREACH(wp, &wl->window->panes, entry) {
366 					if (strcmp(wp->tty, path) == 0) {
367 						found = 1;
368 						break;
369 					}
370 				}
371 				if (found)
372 					break;
373 			}
374 			if (found)
375 				ARRAY_ADD(&ss, s);
376 		}
377 
378 		s = cmd_choose_session_list(&ss);
379 		ARRAY_FREE(&ss);
380 		if (s != NULL)
381 			return (s);
382 	}
383 
384 	return (cmd_choose_session(prefer_unattached));
385 }
386 
387 /* Is this session better? */
388 int
389 cmd_session_better(struct session *s, struct session *best,
390     int prefer_unattached)
391 {
392 	if (best == NULL)
393 		return (1);
394 	if (prefer_unattached) {
395 		if (!(best->flags & SESSION_UNATTACHED) &&
396 		    (s->flags & SESSION_UNATTACHED))
397 			return (1);
398 		else if ((best->flags & SESSION_UNATTACHED) &&
399 		    !(s->flags & SESSION_UNATTACHED))
400 			return (0);
401 	}
402 	return (timercmp(&s->activity_time, &best->activity_time, >));
403 }
404 
405 /*
406  * Find the most recently used session, preferring unattached if the flag is
407  * set.
408  */
409 struct session *
410 cmd_choose_session(int prefer_unattached)
411 {
412 	struct session	*s, *best;
413 
414 	best = NULL;
415 	RB_FOREACH(s, sessions, &sessions) {
416 		if (cmd_session_better(s, best, prefer_unattached))
417 			best = s;
418 	}
419 	return (best);
420 }
421 
422 /* Find the most recently used session from a list. */
423 struct session *
424 cmd_choose_session_list(struct sessionslist *ss)
425 {
426 	struct session	*s, *sbest;
427 	struct timeval	*tv = NULL;
428 	u_int		 i;
429 
430 	sbest = NULL;
431 	for (i = 0; i < ARRAY_LENGTH(ss); i++) {
432 		if ((s = ARRAY_ITEM(ss, i)) == NULL)
433 			continue;
434 
435 		if (tv == NULL || timercmp(&s->activity_time, tv, >)) {
436 			sbest = s;
437 			tv = &s->activity_time;
438 		}
439 	}
440 
441 	return (sbest);
442 }
443 
444 /*
445  * Find the current client. First try the current client if set, then pick the
446  * most recently used of the clients attached to the current session if any,
447  * then of all clients.
448  */
449 struct client *
450 cmd_current_client(struct cmd_q *cmdq)
451 {
452 	struct session		*s;
453 	struct client		*c;
454 	struct clients		 cc;
455 	u_int			 i;
456 
457 	if (cmdq->client != NULL && cmdq->client->session != NULL)
458 		return (cmdq->client);
459 
460 	/*
461 	 * No current client set. Find the current session and return the
462 	 * newest of its clients.
463 	 */
464 	s = cmd_current_session(cmdq, 0);
465 	if (s != NULL && !(s->flags & SESSION_UNATTACHED)) {
466 		ARRAY_INIT(&cc);
467 		for (i = 0; i < ARRAY_LENGTH(&clients); i++) {
468 			if ((c = ARRAY_ITEM(&clients, i)) == NULL)
469 				continue;
470 			if (s == c->session)
471 				ARRAY_ADD(&cc, c);
472 		}
473 
474 		c = cmd_choose_client(&cc);
475 		ARRAY_FREE(&cc);
476 		if (c != NULL)
477 			return (c);
478 	}
479 
480 	return (cmd_choose_client(&clients));
481 }
482 
483 /* Choose the most recently used client from a list. */
484 struct client *
485 cmd_choose_client(struct clients *cc)
486 {
487 	struct client	*c, *cbest;
488 	struct timeval	*tv = NULL;
489 	u_int		 i;
490 
491 	cbest = NULL;
492 	for (i = 0; i < ARRAY_LENGTH(cc); i++) {
493 		if ((c = ARRAY_ITEM(cc, i)) == NULL)
494 			continue;
495 		if (c->session == NULL)
496 			continue;
497 
498 		if (tv == NULL || timercmp(&c->activity_time, tv, >)) {
499 			cbest = c;
500 			tv = &c->activity_time;
501 		}
502 	}
503 
504 	return (cbest);
505 }
506 
507 /* Find the target client or report an error and return NULL. */
508 struct client *
509 cmd_find_client(struct cmd_q *cmdq, const char *arg, int quiet)
510 {
511 	struct client	*c;
512 	char		*tmparg;
513 	size_t		 arglen;
514 
515 	/* A NULL argument means the current client. */
516 	if (arg == NULL) {
517 		c = cmd_current_client(cmdq);
518 		if (c == NULL && !quiet)
519 			cmdq_error(cmdq, "no clients");
520 		return (c);
521 	}
522 	tmparg = xstrdup(arg);
523 
524 	/* Trim a single trailing colon if any. */
525 	arglen = strlen(tmparg);
526 	if (arglen != 0 && tmparg[arglen - 1] == ':')
527 		tmparg[arglen - 1] = '\0';
528 
529 	/* Find the client, if any. */
530 	c = cmd_lookup_client(tmparg);
531 
532 	/* If no client found, report an error. */
533 	if (c == NULL && !quiet)
534 		cmdq_error(cmdq, "client not found: %s", tmparg);
535 
536 	free(tmparg);
537 	return (c);
538 }
539 
540 /*
541  * Lookup a client by device path. Either of a full match and a match without a
542  * leading _PATH_DEV ("/dev/") is accepted.
543  */
544 struct client *
545 cmd_lookup_client(const char *name)
546 {
547 	struct client	*c;
548 	const char	*path;
549 	u_int		 i;
550 
551 	for (i = 0; i < ARRAY_LENGTH(&clients); i++) {
552 		c = ARRAY_ITEM(&clients, i);
553 		if (c == NULL || c->session == NULL || c->tty.path == NULL)
554 			continue;
555 		path = c->tty.path;
556 
557 		/* Check for exact matches. */
558 		if (strcmp(name, path) == 0)
559 			return (c);
560 
561 		/* Check without leading /dev if present. */
562 		if (strncmp(path, _PATH_DEV, (sizeof _PATH_DEV) - 1) != 0)
563 			continue;
564 		if (strcmp(name, path + (sizeof _PATH_DEV) - 1) == 0)
565 			return (c);
566 	}
567 
568 	return (NULL);
569 }
570 
571 /* Find the target session or report an error and return NULL. */
572 struct session *
573 cmd_lookup_session_id(const char *arg)
574 {
575 	char	*endptr;
576 	long	 id;
577 
578 	if (arg[0] != '$')
579 		return (NULL);
580 	id = strtol(arg + 1, &endptr, 10);
581 	if (arg[1] != '\0' && *endptr == '\0')
582 		return (session_find_by_id(id));
583 	return (NULL);
584 }
585 
586 /* Lookup a session by name. If no session is found, NULL is returned. */
587 struct session *
588 cmd_lookup_session(struct cmd_q *cmdq, const char *name, int *ambiguous)
589 {
590 	struct session		*s, *sfound;
591 	struct window		*w;
592 	struct window_pane	*wp;
593 
594 	*ambiguous = 0;
595 
596 	/* Look for $id first. */
597 	if ((s = cmd_lookup_session_id(name)) != NULL)
598 		return (s);
599 
600 	/* Try as pane or window id. */
601 	if ((wp = cmd_lookup_paneid(name)) != NULL)
602 		return (cmd_window_session(cmdq, wp->window, NULL));
603 	if ((w = cmd_lookup_windowid(name)) != NULL)
604 		return (cmd_window_session(cmdq, w, NULL));
605 
606 	/*
607 	 * Look for matches. First look for exact matches - session names must
608 	 * be unique so an exact match can't be ambigious and can just be
609 	 * returned.
610 	 */
611 	if ((s = session_find(name)) != NULL)
612 		return (s);
613 
614 	/*
615 	 * Otherwise look for partial matches, returning early if it is found to
616 	 * be ambiguous.
617 	 */
618 	sfound = NULL;
619 	RB_FOREACH(s, sessions, &sessions) {
620 		if (strncmp(name, s->name, strlen(name)) == 0 ||
621 		    fnmatch(name, s->name, 0) == 0) {
622 			if (sfound != NULL) {
623 				*ambiguous = 1;
624 				return (NULL);
625 			}
626 			sfound = s;
627 		}
628 	}
629 	return (sfound);
630 }
631 
632 /*
633  * Lookup a window or return -1 if not found or ambigious. First try as an
634  * index and if invalid, use fnmatch or leading prefix. Return NULL but fill in
635  * idx if the window index is a valid number but there is no window with that
636  * index.
637  */
638 struct winlink *
639 cmd_lookup_window(struct session *s, const char *name, int *ambiguous)
640 {
641 	struct winlink		*wl, *wlfound;
642 	struct window		*w;
643 	struct window_pane	*wp;
644 	const char		*errstr;
645 	u_int			 idx;
646 
647 	*ambiguous = 0;
648 
649 	/* Try as pane or window id. */
650 	if ((wl = cmd_lookup_winlink_windowid(s, name)) != NULL)
651 	    return (wl);
652 
653 	/* Lookup as pane or window id. */
654 	if ((wp = cmd_lookup_paneid(name)) != NULL) {
655 		wl = winlink_find_by_window(&s->windows, wp->window);
656 		if (wl != NULL)
657 			return (wl);
658 	}
659 	if ((w = cmd_lookup_windowid(name)) != NULL) {
660 		wl = winlink_find_by_window(&s->windows, w);
661 		if (wl != NULL)
662 			return (wl);
663 	}
664 
665 	/* First see if this is a valid window index in this session. */
666 	idx = strtonum(name, 0, INT_MAX, &errstr);
667 	if (errstr == NULL) {
668 		if ((wl = winlink_find_by_index(&s->windows, idx)) != NULL)
669 			return (wl);
670 	}
671 
672 	/* Look for exact matches, error if more than one. */
673 	wlfound = NULL;
674 	RB_FOREACH(wl, winlinks, &s->windows) {
675 		if (strcmp(name, wl->window->name) == 0) {
676 			if (wlfound != NULL) {
677 				*ambiguous = 1;
678 				return (NULL);
679 			}
680 			wlfound = wl;
681 		}
682 	}
683 	if (wlfound != NULL)
684 		return (wlfound);
685 
686 	/* Now look for pattern matches, again error if multiple. */
687 	wlfound = NULL;
688 	RB_FOREACH(wl, winlinks, &s->windows) {
689 		if (strncmp(name, wl->window->name, strlen(name)) == 0 ||
690 		    fnmatch(name, wl->window->name, 0) == 0) {
691 			if (wlfound != NULL) {
692 				*ambiguous = 1;
693 				return (NULL);
694 			}
695 			wlfound = wl;
696 		}
697 	}
698 	if (wlfound != NULL)
699 		return (wlfound);
700 
701 	return (NULL);
702 }
703 
704 /*
705  * Find a window index - if the window doesn't exist, check if it is a
706  * potential index and return it anyway.
707  */
708 int
709 cmd_lookup_index(struct session *s, const char *name, int *ambiguous)
710 {
711 	struct winlink	*wl;
712 	const char	*errstr;
713 	u_int		 idx;
714 
715 	if ((wl = cmd_lookup_window(s, name, ambiguous)) != NULL)
716 		return (wl->idx);
717 	if (*ambiguous)
718 		return (-1);
719 
720 	idx = strtonum(name, 0, INT_MAX, &errstr);
721 	if (errstr == NULL)
722 		return (idx);
723 
724 	return (-1);
725 }
726 
727 /* Lookup pane id. An initial % means a pane id. */
728 struct window_pane *
729 cmd_lookup_paneid(const char *arg)
730 {
731 	const char	*errstr;
732 	u_int		 paneid;
733 
734 	if (*arg != '%')
735 		return (NULL);
736 
737 	paneid = strtonum(arg + 1, 0, UINT_MAX, &errstr);
738 	if (errstr != NULL)
739 		return (NULL);
740 	return (window_pane_find_by_id(paneid));
741 }
742 
743 /* Lookup window id in a session. An initial @ means a window id. */
744 struct winlink *
745 cmd_lookup_winlink_windowid(struct session *s, const char *arg)
746 {
747 	const char	*errstr;
748 	u_int		 windowid;
749 
750 	if (*arg != '@')
751 		return (NULL);
752 
753 	windowid = strtonum(arg + 1, 0, UINT_MAX, &errstr);
754 	if (errstr != NULL)
755 		return (NULL);
756 	return (winlink_find_by_window_id(&s->windows, windowid));
757 }
758 
759 /* Lookup window id. An initial @ means a window id. */
760 struct window *
761 cmd_lookup_windowid(const char *arg)
762 {
763 	const char	*errstr;
764 	u_int		 windowid;
765 
766 	if (*arg != '@')
767 		return (NULL);
768 
769 	windowid = strtonum(arg + 1, 0, UINT_MAX, &errstr);
770 	if (errstr != NULL)
771 		return (NULL);
772 	return (window_find_by_id(windowid));
773 }
774 
775 /* Find session and winlink for window. */
776 struct session *
777 cmd_window_session(struct cmd_q *cmdq, struct window *w, struct winlink **wlp)
778 {
779 	struct session		*s;
780 	struct sessionslist	 ss;
781 	struct winlink		*wl;
782 
783 	/* If this window is in the current session, return that winlink. */
784 	s = cmd_current_session(cmdq, 0);
785 	if (s != NULL) {
786 		wl = winlink_find_by_window(&s->windows, w);
787 		if (wl != NULL) {
788 			if (wlp != NULL)
789 				*wlp = wl;
790 			return (s);
791 		}
792 	}
793 
794 	/* Otherwise choose from all sessions with this window. */
795 	ARRAY_INIT(&ss);
796 	RB_FOREACH(s, sessions, &sessions) {
797 		if (winlink_find_by_window(&s->windows, w) != NULL)
798 			ARRAY_ADD(&ss, s);
799 	}
800 	s = cmd_choose_session_list(&ss);
801 	ARRAY_FREE(&ss);
802 	if (wlp != NULL)
803 		*wlp = winlink_find_by_window(&s->windows, w);
804 	return (s);
805 }
806 
807 /* Find the target session or report an error and return NULL. */
808 struct session *
809 cmd_find_session(struct cmd_q *cmdq, const char *arg, int prefer_unattached)
810 {
811 	struct session	*s;
812 	struct client	*c;
813 	char		*tmparg;
814 	size_t		 arglen;
815 	int		 ambiguous;
816 
817 	/* A NULL argument means the current session. */
818 	if (arg == NULL) {
819 		if ((s = cmd_current_session(cmdq, prefer_unattached)) == NULL)
820 			cmdq_error(cmdq, "can't establish current session");
821 		return (s);
822 	}
823 
824 	/* Trim a single trailing colon if any. */
825 	tmparg = xstrdup(arg);
826 	arglen = strlen(tmparg);
827 	if (arglen != 0 && tmparg[arglen - 1] == ':')
828 		tmparg[arglen - 1] = '\0';
829 
830 	/* An empty session name is the current session. */
831 	if (*tmparg == '\0') {
832 		free(tmparg);
833 		if ((s = cmd_current_session(cmdq, prefer_unattached)) == NULL)
834 			cmdq_error(cmdq, "can't establish current session");
835 		return (s);
836 	}
837 
838 	/* Find the session, if any. */
839 	s = cmd_lookup_session(cmdq, tmparg, &ambiguous);
840 
841 	/* If it doesn't, try to match it as a client. */
842 	if (s == NULL && (c = cmd_lookup_client(tmparg)) != NULL)
843 		s = c->session;
844 
845 	/* If no session found, report an error. */
846 	if (s == NULL) {
847 		if (ambiguous)
848 			cmdq_error(cmdq, "more than one session: %s", tmparg);
849 		else
850 			cmdq_error(cmdq, "session not found: %s", tmparg);
851 	}
852 
853 	free(tmparg);
854 	return (s);
855 }
856 
857 /* Find the target session and window or report an error and return NULL. */
858 struct winlink *
859 cmd_find_window(struct cmd_q *cmdq, const char *arg, struct session **sp)
860 {
861 	struct session	*s;
862 	struct winlink	*wl;
863 	const char	*winptr;
864 	char		*sessptr = NULL;
865 	int		 ambiguous = 0;
866 
867 	/*
868 	 * Find the current session. There must always be a current session, if
869 	 * it can't be found, report an error.
870 	 */
871 	if ((s = cmd_current_session(cmdq, 0)) == NULL) {
872 		cmdq_error(cmdq, "can't establish current session");
873 		return (NULL);
874 	}
875 
876 	/* A NULL argument means the current session and window. */
877 	if (arg == NULL) {
878 		if (sp != NULL)
879 			*sp = s;
880 		return (s->curw);
881 	}
882 
883 	/* Time to look at the argument. If it is empty, that is an error. */
884 	if (*arg == '\0')
885 		goto not_found;
886 
887 	/* Find the separating colon and split into window and session. */
888 	winptr = strchr(arg, ':');
889 	if (winptr == NULL)
890 		goto no_colon;
891 	winptr++;	/* skip : */
892 	sessptr = xstrdup(arg);
893 	*strchr(sessptr, ':') = '\0';
894 
895 	/* Try to lookup the session if present. */
896 	if (*sessptr != '\0') {
897 		if ((s = cmd_lookup_session(cmdq, sessptr, &ambiguous)) == NULL)
898 			goto no_session;
899 	}
900 	if (sp != NULL)
901 		*sp = s;
902 
903 	/*
904 	 * Then work out the window. An empty string is the current window,
905 	 * otherwise try special cases then to look it up in the session.
906 	 */
907 	if (*winptr == '\0')
908 		wl = s->curw;
909 	else if (winptr[0] == '!' && winptr[1] == '\0')
910 		wl = TAILQ_FIRST(&s->lastw);
911 	else if (winptr[0] == '^' && winptr[1] == '\0')
912 		wl = RB_MIN(winlinks, &s->windows);
913 	else if (winptr[0] == '$' && winptr[1] == '\0')
914 		wl = RB_MAX(winlinks, &s->windows);
915 	else if (winptr[0] == '+' || winptr[0] == '-')
916 		wl = cmd_find_window_offset(winptr, s, &ambiguous);
917 	else
918 		wl = cmd_lookup_window(s, winptr, &ambiguous);
919 	if (wl == NULL)
920 		goto not_found;
921 
922 	if (sessptr != NULL)
923 		free(sessptr);
924 	return (wl);
925 
926 no_colon:
927 	/*
928 	 * No colon in the string, first try special cases, then as a window
929 	 * and lastly as a session.
930 	 */
931 	if (arg[0] == '!' && arg[1] == '\0') {
932 		if ((wl = TAILQ_FIRST(&s->lastw)) == NULL)
933 			goto not_found;
934 	} else if (arg[0] == '+' || arg[0] == '-') {
935 		if ((wl = cmd_find_window_offset(arg, s, &ambiguous)) == NULL)
936 			goto lookup_session;
937 	} else if ((wl = cmd_lookup_window(s, arg, &ambiguous)) == NULL)
938 		goto lookup_session;
939 
940 	if (sp != NULL)
941 		*sp = s;
942 
943 	return (wl);
944 
945 lookup_session:
946 	if (ambiguous)
947 		goto not_found;
948 	if (*arg != '\0' &&
949 	    (s = cmd_lookup_session(cmdq, arg, &ambiguous)) == NULL)
950 		goto no_session;
951 
952 	if (sp != NULL)
953 		*sp = s;
954 
955 	return (s->curw);
956 
957 no_session:
958 	if (ambiguous)
959 		cmdq_error(cmdq, "multiple sessions: %s", arg);
960 	else
961 		cmdq_error(cmdq, "session not found: %s", arg);
962 	free(sessptr);
963 	return (NULL);
964 
965 not_found:
966 	if (ambiguous)
967 		cmdq_error(cmdq, "multiple windows: %s", arg);
968 	else
969 		cmdq_error(cmdq, "window not found: %s", arg);
970 	free(sessptr);
971 	return (NULL);
972 }
973 
974 struct winlink *
975 cmd_find_window_offset(const char *winptr, struct session *s, int *ambiguous)
976 {
977 	struct winlink	*wl;
978 	int		 offset = 1;
979 
980 	if (winptr[1] != '\0')
981 		offset = strtonum(winptr + 1, 1, INT_MAX, NULL);
982 	if (offset == 0)
983 		wl = cmd_lookup_window(s, winptr, ambiguous);
984 	else {
985 		if (winptr[0] == '+')
986 			wl = winlink_next_by_number(s->curw, s, offset);
987 		else
988 			wl = winlink_previous_by_number(s->curw, s, offset);
989 	}
990 
991 	return (wl);
992 }
993 
994 /*
995  * Find the target session and window index, whether or not it exists in the
996  * session. Return -2 on error or -1 if no window index is specified. This is
997  * used when parsing an argument for a window target that may not exist (for
998  * example if it is going to be created).
999  */
1000 int
1001 cmd_find_index(struct cmd_q *cmdq, const char *arg, struct session **sp)
1002 {
1003 	struct session	*s;
1004 	struct winlink	*wl;
1005 	const char	*winptr;
1006 	char		*sessptr = NULL;
1007 	int		 idx, ambiguous = 0;
1008 
1009 	/*
1010 	 * Find the current session. There must always be a current session, if
1011 	 * it can't be found, report an error.
1012 	 */
1013 	if ((s = cmd_current_session(cmdq, 0)) == NULL) {
1014 		cmdq_error(cmdq, "can't establish current session");
1015 		return (-2);
1016 	}
1017 
1018 	/* A NULL argument means the current session and "no window" (-1). */
1019 	if (arg == NULL) {
1020 		if (sp != NULL)
1021 			*sp = s;
1022 		return (-1);
1023 	}
1024 
1025 	/* Time to look at the argument. If it is empty, that is an error. */
1026 	if (*arg == '\0')
1027 		goto not_found;
1028 
1029 	/* Find the separating colon. If none, assume the current session. */
1030 	winptr = strchr(arg, ':');
1031 	if (winptr == NULL)
1032 		goto no_colon;
1033 	winptr++;	/* skip : */
1034 	sessptr = xstrdup(arg);
1035 	*strchr(sessptr, ':') = '\0';
1036 
1037 	/* Try to lookup the session if present. */
1038 	if (sessptr != NULL && *sessptr != '\0') {
1039 		if ((s = cmd_lookup_session(cmdq, sessptr, &ambiguous)) == NULL)
1040 			goto no_session;
1041 	}
1042 	if (sp != NULL)
1043 		*sp = s;
1044 
1045 	/*
1046 	 * Then work out the window. An empty string is a new window otherwise
1047 	 * try to look it up in the session.
1048 	 */
1049 	if (*winptr == '\0')
1050 		idx = -1;
1051 	else if (winptr[0] == '!' && winptr[1] == '\0') {
1052 		if ((wl = TAILQ_FIRST(&s->lastw)) == NULL)
1053 			goto not_found;
1054 		idx = wl->idx;
1055 	} else if (winptr[0] == '+' || winptr[0] == '-') {
1056 		if ((idx = cmd_find_index_offset(winptr, s, &ambiguous)) < 0)
1057 			goto invalid_index;
1058 	} else if ((idx = cmd_lookup_index(s, winptr, &ambiguous)) == -1)
1059 		goto invalid_index;
1060 
1061 	free(sessptr);
1062 	return (idx);
1063 
1064 no_colon:
1065 	/*
1066 	 * No colon in the string, first try special cases, then as a window
1067 	 * and lastly as a session.
1068 	 */
1069 	if (arg[0] == '!' && arg[1] == '\0') {
1070 		if ((wl = TAILQ_FIRST(&s->lastw)) == NULL)
1071 			goto not_found;
1072 		idx = wl->idx;
1073 	} else if (arg[0] == '+' || arg[0] == '-') {
1074 		if ((idx = cmd_find_index_offset(arg, s, &ambiguous)) < 0)
1075 			goto lookup_session;
1076 	} else if ((idx = cmd_lookup_index(s, arg, &ambiguous)) == -1)
1077 		goto lookup_session;
1078 
1079 	if (sp != NULL)
1080 		*sp = s;
1081 
1082 	return (idx);
1083 
1084 lookup_session:
1085 	if (ambiguous)
1086 		goto not_found;
1087 	if (*arg != '\0' &&
1088 	    (s = cmd_lookup_session(cmdq, arg, &ambiguous)) == NULL)
1089 		goto no_session;
1090 
1091 	if (sp != NULL)
1092 		*sp = s;
1093 
1094 	return (-1);
1095 
1096 no_session:
1097 	if (ambiguous)
1098 		cmdq_error(cmdq, "multiple sessions: %s", arg);
1099 	else
1100 		cmdq_error(cmdq, "session not found: %s", arg);
1101 	free(sessptr);
1102 	return (-2);
1103 
1104 invalid_index:
1105 	if (ambiguous)
1106 		goto not_found;
1107 	cmdq_error(cmdq, "invalid index: %s", arg);
1108 
1109 	free(sessptr);
1110 	return (-2);
1111 
1112 not_found:
1113 	if (ambiguous)
1114 		cmdq_error(cmdq, "multiple windows: %s", arg);
1115 	else
1116 		cmdq_error(cmdq, "window not found: %s", arg);
1117 	free(sessptr);
1118 	return (-2);
1119 }
1120 
1121 int
1122 cmd_find_index_offset(const char *winptr, struct session *s, int *ambiguous)
1123 {
1124 	int	idx, offset = 1;
1125 
1126 	if (winptr[1] != '\0')
1127 		offset = strtonum(winptr + 1, 1, INT_MAX, NULL);
1128 	if (offset == 0)
1129 		idx = cmd_lookup_index(s, winptr, ambiguous);
1130 	else {
1131 		if (winptr[0] == '+') {
1132 			if (s->curw->idx == INT_MAX)
1133 				idx = cmd_lookup_index(s, winptr, ambiguous);
1134 			else
1135 				idx = s->curw->idx + offset;
1136 		} else {
1137 			if (s->curw->idx == 0)
1138 				idx = cmd_lookup_index(s, winptr, ambiguous);
1139 			else
1140 				idx = s->curw->idx - offset;
1141 		}
1142 	}
1143 
1144 	return (idx);
1145 }
1146 
1147 /*
1148  * Find the target session, window and pane number or report an error and
1149  * return NULL. The pane number is separated from the session:window by a .,
1150  * such as mysession:mywindow.0.
1151  */
1152 struct winlink *
1153 cmd_find_pane(struct cmd_q *cmdq,
1154     const char *arg, struct session **sp, struct window_pane **wpp)
1155 {
1156 	struct session	*s;
1157 	struct winlink	*wl;
1158 	const char	*period, *errstr;
1159 	char		*winptr, *paneptr;
1160 	u_int		 idx;
1161 
1162 	/* Get the current session. */
1163 	if ((s = cmd_current_session(cmdq, 0)) == NULL) {
1164 		cmdq_error(cmdq, "can't establish current session");
1165 		return (NULL);
1166 	}
1167 	if (sp != NULL)
1168 		*sp = s;
1169 
1170 	/* A NULL argument means the current session, window and pane. */
1171 	if (arg == NULL) {
1172 		*wpp = s->curw->window->active;
1173 		return (s->curw);
1174 	}
1175 
1176 	/* Lookup as pane id. */
1177 	if ((*wpp = cmd_lookup_paneid(arg)) != NULL) {
1178 		s = cmd_window_session(cmdq, (*wpp)->window, &wl);
1179 		if (sp != NULL)
1180 			*sp = s;
1181 		return (wl);
1182 	}
1183 
1184 	/* Look for a separating period. */
1185 	if ((period = strrchr(arg, '.')) == NULL)
1186 		goto no_period;
1187 
1188 	/* Pull out the window part and parse it. */
1189 	winptr = xstrdup(arg);
1190 	winptr[period - arg] = '\0';
1191 	if (*winptr == '\0')
1192 		wl = s->curw;
1193 	else if ((wl = cmd_find_window(cmdq, winptr, sp)) == NULL)
1194 		goto error;
1195 
1196 	/* Find the pane section and look it up. */
1197 	paneptr = winptr + (period - arg) + 1;
1198 	if (*paneptr == '\0')
1199 		*wpp = wl->window->active;
1200 	else if (paneptr[0] == '+' || paneptr[0] == '-')
1201 		*wpp = cmd_find_pane_offset(paneptr, wl);
1202 	else if (paneptr[0] == '!' && paneptr[1] == '\0') {
1203 		if (wl->window->last == NULL) {
1204 			cmdq_error(cmdq, "no last pane");
1205 			goto error;
1206 		}
1207 		*wpp = wl->window->last;
1208 	} else {
1209 		idx = strtonum(paneptr, 0, INT_MAX, &errstr);
1210 		if (errstr != NULL)
1211 			goto lookup_string;
1212 		*wpp = window_pane_at_index(wl->window, idx);
1213 		if (*wpp == NULL)
1214 			goto lookup_string;
1215 	}
1216 
1217 	free(winptr);
1218 	return (wl);
1219 
1220 lookup_string:
1221 	/* Try pane string description. */
1222 	if ((*wpp = window_find_string(wl->window, paneptr)) == NULL) {
1223 		cmdq_error(cmdq, "can't find pane: %s", paneptr);
1224 		goto error;
1225 	}
1226 
1227 	free(winptr);
1228 	return (wl);
1229 
1230 no_period:
1231 	/* Try as a pane number alone. */
1232 	idx = strtonum(arg, 0, INT_MAX, &errstr);
1233 	if (errstr != NULL)
1234 		goto lookup_window;
1235 
1236 	/* Try index in the current session and window. */
1237 	if ((*wpp = window_pane_at_index(s->curw->window, idx)) == NULL)
1238 		goto lookup_window;
1239 
1240 	return (s->curw);
1241 
1242 lookup_window:
1243 	/* Try pane string description. */
1244 	if ((*wpp = window_find_string(s->curw->window, arg)) != NULL)
1245 		return (s->curw);
1246 
1247 	/* Try as a window and use the active pane. */
1248 	if ((wl = cmd_find_window(cmdq, arg, sp)) != NULL)
1249 		*wpp = wl->window->active;
1250 	return (wl);
1251 
1252 error:
1253 	free(winptr);
1254 	return (NULL);
1255 }
1256 
1257 struct window_pane *
1258 cmd_find_pane_offset(const char *paneptr, struct winlink *wl)
1259 {
1260 	struct window		*w = wl->window;
1261 	struct window_pane	*wp = w->active;
1262 	u_int			 offset = 1;
1263 
1264 	if (paneptr[1] != '\0')
1265 		offset = strtonum(paneptr + 1, 1, INT_MAX, NULL);
1266 	if (offset > 0) {
1267 		if (paneptr[0] == '+')
1268 			wp = window_pane_next_by_number(w, wp, offset);
1269 		else
1270 			wp = window_pane_previous_by_number(w, wp, offset);
1271 	}
1272 
1273 	return (wp);
1274 }
1275 
1276 /* Replace the first %% or %idx in template by s. */
1277 char *
1278 cmd_template_replace(const char *template, const char *s, int idx)
1279 {
1280 	char		 ch, *buf;
1281 	const char	*ptr;
1282 	int		 replaced;
1283 	size_t		 len;
1284 
1285 	if (strchr(template, '%') == NULL)
1286 		return (xstrdup(template));
1287 
1288 	buf = xmalloc(1);
1289 	*buf = '\0';
1290 	len = 0;
1291 	replaced = 0;
1292 
1293 	ptr = template;
1294 	while (*ptr != '\0') {
1295 		switch (ch = *ptr++) {
1296 		case '%':
1297 			if (*ptr < '1' || *ptr > '9' || *ptr - '0' != idx) {
1298 				if (*ptr != '%' || replaced)
1299 					break;
1300 				replaced = 1;
1301 			}
1302 			ptr++;
1303 
1304 			len += strlen(s);
1305 			buf = xrealloc(buf, len + 1);
1306 			strlcat(buf, s, len + 1);
1307 			continue;
1308 		}
1309 		buf = xrealloc(buf, len + 2);
1310 		buf[len++] = ch;
1311 		buf[len] = '\0';
1312 	}
1313 
1314 	return (buf);
1315 }
1316