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