Fix crash when resizing in copy mode, when cursor can end up outside screen.
[tmux-openbsd.git] / cmd.c
blob09b58adf8e9791f5f44885502c5ba60a0b5174bc
1 /* $OpenBSD$ */
3 /*
4 * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
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.
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.
19 #include <sys/types.h>
20 #include <sys/time.h>
22 #include <fnmatch.h>
23 #include <paths.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
28 #include "tmux.h"
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_client_entry,
36 &cmd_choose_session_entry,
37 &cmd_choose_window_entry,
38 &cmd_clear_history_entry,
39 &cmd_clock_mode_entry,
40 &cmd_command_prompt_entry,
41 &cmd_confirm_before_entry,
42 &cmd_copy_buffer_entry,
43 &cmd_copy_mode_entry,
44 &cmd_delete_buffer_entry,
45 &cmd_detach_client_entry,
46 &cmd_display_message_entry,
47 &cmd_display_panes_entry,
48 &cmd_find_window_entry,
49 &cmd_has_session_entry,
50 &cmd_if_shell_entry,
51 &cmd_join_pane_entry,
52 &cmd_kill_pane_entry,
53 &cmd_kill_server_entry,
54 &cmd_kill_session_entry,
55 &cmd_kill_window_entry,
56 &cmd_last_window_entry,
57 &cmd_link_window_entry,
58 &cmd_list_buffers_entry,
59 &cmd_list_clients_entry,
60 &cmd_list_commands_entry,
61 &cmd_list_keys_entry,
62 &cmd_list_panes_entry,
63 &cmd_list_sessions_entry,
64 &cmd_list_windows_entry,
65 &cmd_load_buffer_entry,
66 &cmd_lock_client_entry,
67 &cmd_lock_server_entry,
68 &cmd_lock_session_entry,
69 &cmd_move_window_entry,
70 &cmd_new_session_entry,
71 &cmd_new_window_entry,
72 &cmd_next_layout_entry,
73 &cmd_next_window_entry,
74 &cmd_paste_buffer_entry,
75 &cmd_pipe_pane_entry,
76 &cmd_previous_layout_entry,
77 &cmd_previous_window_entry,
78 &cmd_refresh_client_entry,
79 &cmd_rename_session_entry,
80 &cmd_rename_window_entry,
81 &cmd_resize_pane_entry,
82 &cmd_respawn_window_entry,
83 &cmd_rotate_window_entry,
84 &cmd_run_shell_entry,
85 &cmd_save_buffer_entry,
86 &cmd_select_layout_entry,
87 &cmd_select_pane_entry,
88 &cmd_select_prompt_entry,
89 &cmd_select_window_entry,
90 &cmd_send_keys_entry,
91 &cmd_send_prefix_entry,
92 &cmd_server_info_entry,
93 &cmd_set_buffer_entry,
94 &cmd_set_environment_entry,
95 &cmd_set_option_entry,
96 &cmd_set_window_option_entry,
97 &cmd_show_buffer_entry,
98 &cmd_show_environment_entry,
99 &cmd_show_messages_entry,
100 &cmd_show_options_entry,
101 &cmd_show_window_options_entry,
102 &cmd_source_file_entry,
103 &cmd_split_window_entry,
104 &cmd_start_server_entry,
105 &cmd_suspend_client_entry,
106 &cmd_swap_pane_entry,
107 &cmd_swap_window_entry,
108 &cmd_switch_client_entry,
109 &cmd_unbind_key_entry,
110 &cmd_unlink_window_entry,
111 NULL
114 struct session *cmd_choose_session(struct sessions *);
115 struct client *cmd_choose_client(struct clients *);
116 struct client *cmd_lookup_client(const char *);
117 struct session *cmd_lookup_session(const char *, int *);
118 struct winlink *cmd_lookup_window(struct session *, const char *, int *);
119 int cmd_lookup_index(struct session *, const char *, int *);
122 cmd_pack_argv(int argc, char **argv, char *buf, size_t len)
124 size_t arglen;
125 int i;
127 *buf = '\0';
128 for (i = 0; i < argc; i++) {
129 if (strlcpy(buf, argv[i], len) >= len)
130 return (-1);
131 arglen = strlen(argv[i]) + 1;
132 buf += arglen;
133 len -= arglen;
136 return (0);
140 cmd_unpack_argv(char *buf, size_t len, int argc, char ***argv)
142 int i;
143 size_t arglen;
145 if (argc == 0)
146 return (0);
147 *argv = xcalloc(argc, sizeof **argv);
149 buf[len - 1] = '\0';
150 for (i = 0; i < argc; i++) {
151 if (len == 0) {
152 cmd_free_argv(argc, *argv);
153 return (-1);
156 arglen = strlen(buf) + 1;
157 (*argv)[i] = xstrdup(buf);
158 buf += arglen;
159 len -= arglen;
162 return (0);
165 void
166 cmd_free_argv(int argc, char **argv)
168 int i;
170 if (argc == 0)
171 return;
172 for (i = 0; i < argc; i++) {
173 if (argv[i] != NULL)
174 xfree(argv[i]);
176 xfree(argv);
179 struct cmd *
180 cmd_parse(int argc, char **argv, char **cause)
182 const struct cmd_entry **entryp, *entry;
183 struct cmd *cmd;
184 char s[BUFSIZ];
185 int opt, ambiguous = 0;
187 *cause = NULL;
188 if (argc == 0) {
189 xasprintf(cause, "no command");
190 return (NULL);
193 entry = NULL;
194 for (entryp = cmd_table; *entryp != NULL; entryp++) {
195 if ((*entryp)->alias != NULL &&
196 strcmp((*entryp)->alias, argv[0]) == 0) {
197 ambiguous = 0;
198 entry = *entryp;
199 break;
202 if (strncmp((*entryp)->name, argv[0], strlen(argv[0])) != 0)
203 continue;
204 if (entry != NULL)
205 ambiguous = 1;
206 entry = *entryp;
208 /* Bail now if an exact match. */
209 if (strcmp(entry->name, argv[0]) == 0)
210 break;
212 if (ambiguous)
213 goto ambiguous;
214 if (entry == NULL) {
215 xasprintf(cause, "unknown command: %s", argv[0]);
216 return (NULL);
219 optreset = 1;
220 optind = 1;
221 if (entry->parse == NULL) {
222 while ((opt = getopt(argc, argv, "")) != -1) {
223 switch (opt) {
224 default:
225 goto usage;
228 argc -= optind;
229 argv += optind;
230 if (argc != 0)
231 goto usage;
234 cmd = xmalloc(sizeof *cmd);
235 cmd->entry = entry;
236 cmd->data = NULL;
237 if (entry->parse != NULL) {
238 if (entry->parse(cmd, argc, argv, cause) != 0) {
239 xfree(cmd);
240 return (NULL);
243 return (cmd);
245 ambiguous:
246 *s = '\0';
247 for (entryp = cmd_table; *entryp != NULL; entryp++) {
248 if (strncmp((*entryp)->name, argv[0], strlen(argv[0])) != 0)
249 continue;
250 if (strlcat(s, (*entryp)->name, sizeof s) >= sizeof s)
251 break;
252 if (strlcat(s, ", ", sizeof s) >= sizeof s)
253 break;
255 s[strlen(s) - 2] = '\0';
256 xasprintf(cause, "ambiguous command: %s, could be: %s", argv[0], s);
257 return (NULL);
259 usage:
260 xasprintf(cause, "usage: %s %s", entry->name, entry->usage);
261 return (NULL);
265 cmd_exec(struct cmd *cmd, struct cmd_ctx *ctx)
267 return (cmd->entry->exec(cmd, ctx));
270 void
271 cmd_free(struct cmd *cmd)
273 if (cmd->data != NULL && cmd->entry->free != NULL)
274 cmd->entry->free(cmd);
275 xfree(cmd);
278 size_t
279 cmd_print(struct cmd *cmd, char *buf, size_t len)
281 if (cmd->entry->print == NULL)
282 return (xsnprintf(buf, len, "%s", cmd->entry->name));
283 return (cmd->entry->print(cmd, buf, len));
287 * Figure out the current session. Use: 1) the current session, if the command
288 * context has one; 2) the most recently used session containing the pty of the
289 * calling client, if any; 3) the session specified in the TMUX variable from
290 * the environment (as passed from the client); 4) the most recently used
291 * session from all sessions.
293 struct session *
294 cmd_current_session(struct cmd_ctx *ctx)
296 struct msg_command_data *data = ctx->msgdata;
297 struct client *c = ctx->cmdclient;
298 struct session *s;
299 struct sessions ss;
300 struct winlink *wl;
301 struct window_pane *wp;
302 u_int i;
303 int found;
305 if (ctx->curclient != NULL && ctx->curclient->session != NULL)
306 return (ctx->curclient->session);
309 * If the name of the calling client's pty is know, build a list of the
310 * sessions that contain it and if any choose either the first or the
311 * newest.
313 if (c != NULL && c->tty.path != NULL) {
314 ARRAY_INIT(&ss);
315 for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
316 if ((s = ARRAY_ITEM(&sessions, i)) == NULL)
317 continue;
318 found = 0;
319 RB_FOREACH(wl, winlinks, &s->windows) {
320 TAILQ_FOREACH(wp, &wl->window->panes, entry) {
321 if (strcmp(wp->tty, c->tty.path) == 0) {
322 found = 1;
323 break;
326 if (found)
327 break;
329 if (found)
330 ARRAY_ADD(&ss, s);
333 s = cmd_choose_session(&ss);
334 ARRAY_FREE(&ss);
335 if (s != NULL)
336 return (s);
339 /* Use the session from the TMUX environment variable. */
340 if (data != NULL && data->pid != -1) {
341 if (data->pid != getpid())
342 return (NULL);
343 if (data->idx > ARRAY_LENGTH(&sessions))
344 return (NULL);
345 if ((s = ARRAY_ITEM(&sessions, data->idx)) == NULL)
346 return (NULL);
347 return (s);
350 return (cmd_choose_session(&sessions));
353 /* Find the most recently used session from a list. */
354 struct session *
355 cmd_choose_session(struct sessions *ss)
357 struct session *s, *sbest;
358 struct timeval *tv = NULL;
359 u_int i;
361 sbest = NULL;
362 for (i = 0; i < ARRAY_LENGTH(ss); i++) {
363 if ((s = ARRAY_ITEM(ss, i)) == NULL)
364 continue;
366 if (tv == NULL || timercmp(&s->activity_time, tv, >)) {
367 sbest = s;
368 tv = &s->activity_time;
372 return (sbest);
376 * Find the current client. First try the current client if set, then pick the
377 * most recently used of the clients attached to the current session if any,
378 * then of all clients.
380 struct client *
381 cmd_current_client(struct cmd_ctx *ctx)
383 struct session *s;
384 struct client *c;
385 struct clients cc;
386 u_int i;
388 if (ctx->curclient != NULL)
389 return (ctx->curclient);
392 * No current client set. Find the current session and return the
393 * newest of its clients.
395 s = cmd_current_session(ctx);
396 if (s != NULL && !(s->flags & SESSION_UNATTACHED)) {
397 ARRAY_INIT(&cc);
398 for (i = 0; i < ARRAY_LENGTH(&clients); i++) {
399 if ((c = ARRAY_ITEM(&clients, i)) == NULL)
400 continue;
401 if (s == c->session)
402 ARRAY_ADD(&cc, c);
405 c = cmd_choose_client(&cc);
406 ARRAY_FREE(&cc);
407 if (c != NULL)
408 return (c);
411 return (cmd_choose_client(&clients));
414 /* Choose the most recently used client from a list. */
415 struct client *
416 cmd_choose_client(struct clients *cc)
418 struct client *c, *cbest;
419 struct timeval *tv = NULL;
420 u_int i;
422 cbest = NULL;
423 for (i = 0; i < ARRAY_LENGTH(cc); i++) {
424 if ((c = ARRAY_ITEM(cc, i)) == NULL)
425 continue;
426 if (c->session == NULL)
427 continue;
429 if (tv == NULL || timercmp(&c->activity_time, tv, >)) {
430 cbest = c;
431 tv = &c->activity_time;
435 return (cbest);
438 /* Find the target client or report an error and return NULL. */
439 struct client *
440 cmd_find_client(struct cmd_ctx *ctx, const char *arg)
442 struct client *c;
443 char *tmparg;
444 size_t arglen;
446 /* A NULL argument means the current client. */
447 if (arg == NULL)
448 return (cmd_current_client(ctx));
449 tmparg = xstrdup(arg);
451 /* Trim a single trailing colon if any. */
452 arglen = strlen(tmparg);
453 if (arglen != 0 && tmparg[arglen - 1] == ':')
454 tmparg[arglen - 1] = '\0';
456 /* Find the client, if any. */
457 c = cmd_lookup_client(tmparg);
459 /* If no client found, report an error. */
460 if (c == NULL)
461 ctx->error(ctx, "client not found: %s", tmparg);
463 xfree(tmparg);
464 return (c);
468 * Lookup a client by device path. Either of a full match and a match without a
469 * leading _PATH_DEV ("/dev/") is accepted.
471 struct client *
472 cmd_lookup_client(const char *name)
474 struct client *c;
475 const char *path;
476 u_int i;
478 for (i = 0; i < ARRAY_LENGTH(&clients); i++) {
479 c = ARRAY_ITEM(&clients, i);
480 if (c == NULL || c->session == NULL)
481 continue;
482 path = c->tty.path;
484 /* Check for exact matches. */
485 if (strcmp(name, path) == 0)
486 return (c);
488 /* Check without leading /dev if present. */
489 if (strncmp(path, _PATH_DEV, (sizeof _PATH_DEV) - 1) != 0)
490 continue;
491 if (strcmp(name, path + (sizeof _PATH_DEV) - 1) == 0)
492 return (c);
495 return (NULL);
498 /* Lookup a session by name. If no session is found, NULL is returned. */
499 struct session *
500 cmd_lookup_session(const char *name, int *ambiguous)
502 struct session *s, *sfound;
503 u_int i;
505 *ambiguous = 0;
508 * Look for matches. First look for exact matches - session names must
509 * be unique so an exact match can't be ambigious and can just be
510 * returned.
512 for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
513 if ((s = ARRAY_ITEM(&sessions, i)) == NULL)
514 continue;
515 if (strcmp(name, s->name) == 0)
516 return (s);
520 * Otherwise look for partial matches, returning early if it is found to
521 * be ambiguous.
523 sfound = NULL;
524 for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
525 if ((s = ARRAY_ITEM(&sessions, i)) == NULL)
526 continue;
527 if (strncmp(name, s->name, strlen(name)) == 0 ||
528 fnmatch(name, s->name, 0) == 0) {
529 if (sfound != NULL) {
530 *ambiguous = 1;
531 return (NULL);
533 sfound = s;
536 return (sfound);
540 * Lookup a window or return -1 if not found or ambigious. First try as an
541 * index and if invalid, use fnmatch or leading prefix. Return NULL but fill in
542 * idx if the window index is a valid number but there is now window with that
543 * index.
545 struct winlink *
546 cmd_lookup_window(struct session *s, const char *name, int *ambiguous)
548 struct winlink *wl, *wlfound;
549 const char *errstr;
550 u_int idx;
552 *ambiguous = 0;
554 /* First see if this is a valid window index in this session. */
555 idx = strtonum(name, 0, INT_MAX, &errstr);
556 if (errstr == NULL) {
557 if ((wl = winlink_find_by_index(&s->windows, idx)) != NULL)
558 return (wl);
561 /* Look for exact matches, error if more than one. */
562 wlfound = NULL;
563 RB_FOREACH(wl, winlinks, &s->windows) {
564 if (strcmp(name, wl->window->name) == 0) {
565 if (wlfound != NULL) {
566 *ambiguous = 1;
567 return (NULL);
569 wlfound = wl;
572 if (wlfound != NULL)
573 return (wlfound);
575 /* Now look for pattern matches, again error if multiple. */
576 wlfound = NULL;
577 RB_FOREACH(wl, winlinks, &s->windows) {
578 if (strncmp(name, wl->window->name, strlen(name)) == 0 ||
579 fnmatch(name, wl->window->name, 0) == 0) {
580 if (wlfound != NULL) {
581 *ambiguous = 1;
582 return (NULL);
584 wlfound = wl;
587 if (wlfound != NULL)
588 return (wlfound);
590 return (NULL);
594 * Find a window index - if the window doesn't exist, check if it is a
595 * potential index and return it anyway.
598 cmd_lookup_index(struct session *s, const char *name, int *ambiguous)
600 struct winlink *wl;
601 const char *errstr;
602 u_int idx;
604 if ((wl = cmd_lookup_window(s, name, ambiguous)) != NULL)
605 return (wl->idx);
606 if (*ambiguous)
607 return (-1);
609 idx = strtonum(name, 0, INT_MAX, &errstr);
610 if (errstr == NULL)
611 return (idx);
613 return (-1);
616 /* Find the target session or report an error and return NULL. */
617 struct session *
618 cmd_find_session(struct cmd_ctx *ctx, const char *arg)
620 struct session *s;
621 struct client *c;
622 char *tmparg;
623 size_t arglen;
624 int ambiguous;
626 /* A NULL argument means the current session. */
627 if (arg == NULL)
628 return (cmd_current_session(ctx));
629 tmparg = xstrdup(arg);
631 /* Trim a single trailing colon if any. */
632 arglen = strlen(tmparg);
633 if (arglen != 0 && tmparg[arglen - 1] == ':')
634 tmparg[arglen - 1] = '\0';
636 /* Find the session, if any. */
637 s = cmd_lookup_session(tmparg, &ambiguous);
639 /* If it doesn't, try to match it as a client. */
640 if (s == NULL && (c = cmd_lookup_client(tmparg)) != NULL)
641 s = c->session;
643 /* If no session found, report an error. */
644 if (s == NULL) {
645 if (ambiguous)
646 ctx->error(ctx, "more than one session: %s", tmparg);
647 else
648 ctx->error(ctx, "session not found: %s", tmparg);
651 xfree(tmparg);
652 return (s);
655 /* Find the target session and window or report an error and return NULL. */
656 struct winlink *
657 cmd_find_window(struct cmd_ctx *ctx, const char *arg, struct session **sp)
659 struct session *s;
660 struct winlink *wl;
661 const char *winptr;
662 char *sessptr = NULL;
663 int ambiguous = 0;
666 * Find the current session. There must always be a current session, if
667 * it can't be found, report an error.
669 if ((s = cmd_current_session(ctx)) == NULL) {
670 ctx->error(ctx, "can't establish current session");
671 return (NULL);
674 /* A NULL argument means the current session and window. */
675 if (arg == NULL) {
676 if (sp != NULL)
677 *sp = s;
678 return (s->curw);
681 /* Time to look at the argument. If it is empty, that is an error. */
682 if (*arg == '\0')
683 goto not_found;
685 /* Find the separating colon and split into window and session. */
686 winptr = strchr(arg, ':');
687 if (winptr == NULL)
688 goto no_colon;
689 winptr++; /* skip : */
690 sessptr = xstrdup(arg);
691 *strchr(sessptr, ':') = '\0';
693 /* Try to lookup the session if present. */
694 if (*sessptr != '\0') {
695 if ((s = cmd_lookup_session(sessptr, &ambiguous)) == NULL)
696 goto no_session;
698 if (sp != NULL)
699 *sp = s;
702 * Then work out the window. An empty string is the current window,
703 * otherwise try special cases then to look it up in the session.
705 if (*winptr == '\0')
706 wl = s->curw;
707 else if (winptr[0] == '!' && winptr[1] == '\0')
708 wl = TAILQ_FIRST(&s->lastw);
709 else if (winptr[0] == '+' && winptr[1] == '\0')
710 wl = winlink_next(s->curw);
711 else if (winptr[0] == '-' && winptr[1] == '\0')
712 wl = winlink_previous(s->curw);
713 else
714 wl = cmd_lookup_window(s, winptr, &ambiguous);
715 if (wl == NULL)
716 goto not_found;
718 if (sessptr != NULL)
719 xfree(sessptr);
720 return (wl);
722 no_colon:
724 * No colon in the string, first try special cases, then as a window
725 * and lastly as a session.
727 if (arg[0] == '!' && arg[1] == '\0') {
728 if ((wl = TAILQ_FIRST(&s->lastw)) == NULL)
729 goto not_found;
730 } else if (arg[0] == '+' && arg[1] == '\0') {
731 if ((wl = winlink_next(s->curw)) == NULL)
732 goto not_found;
733 } else if (arg[0] == '-' && arg[1] == '\0') {
734 if ((wl = winlink_previous(s->curw)) == NULL)
735 goto not_found;
736 } else if ((wl = cmd_lookup_window(s, arg, &ambiguous)) == NULL) {
737 if (ambiguous)
738 goto not_found;
739 if ((s = cmd_lookup_session(arg, &ambiguous)) == NULL)
740 goto no_session;
741 wl = s->curw;
744 if (sp != NULL)
745 *sp = s;
747 return (wl);
749 no_session:
750 if (ambiguous)
751 ctx->error(ctx, "multiple sessions: %s", arg);
752 else
753 ctx->error(ctx, "session not found: %s", arg);
754 if (sessptr != NULL)
755 xfree(sessptr);
756 return (NULL);
758 not_found:
759 if (ambiguous)
760 ctx->error(ctx, "multiple windows: %s", arg);
761 else
762 ctx->error(ctx, "window not found: %s", arg);
763 if (sessptr != NULL)
764 xfree(sessptr);
765 return (NULL);
769 * Find the target session and window index, whether or not it exists in the
770 * session. Return -2 on error or -1 if no window index is specified. This is
771 * used when parsing an argument for a window target that may not exist (for
772 * example if it is going to be created).
775 cmd_find_index(struct cmd_ctx *ctx, const char *arg, struct session **sp)
777 struct session *s;
778 struct winlink *wl;
779 const char *winptr;
780 char *sessptr = NULL;
781 int idx, ambiguous = 0;
784 * Find the current session. There must always be a current session, if
785 * it can't be found, report an error.
787 if ((s = cmd_current_session(ctx)) == NULL) {
788 ctx->error(ctx, "can't establish current session");
789 return (-2);
792 /* A NULL argument means the current session and "no window" (-1). */
793 if (arg == NULL) {
794 if (sp != NULL)
795 *sp = s;
796 return (-1);
799 /* Time to look at the argument. If it is empty, that is an error. */
800 if (*arg == '\0')
801 goto not_found;
803 /* Find the separating colon. If none, assume the current session. */
804 winptr = strchr(arg, ':');
805 if (winptr == NULL)
806 goto no_colon;
807 winptr++; /* skip : */
808 sessptr = xstrdup(arg);
809 *strchr(sessptr, ':') = '\0';
811 /* Try to lookup the session if present. */
812 if (sessptr != NULL && *sessptr != '\0') {
813 if ((s = cmd_lookup_session(sessptr, &ambiguous)) == NULL)
814 goto no_session;
816 if (sp != NULL)
817 *sp = s;
820 * Then work out the window. An empty string is a new window otherwise
821 * try to look it up in the session.
823 if (*winptr == '\0')
824 idx = -1;
825 else if (winptr[0] == '!' && winptr[1] == '\0') {
826 if ((wl = TAILQ_FIRST(&s->lastw)) == NULL)
827 goto not_found;
828 idx = wl->idx;
829 } else if (winptr[0] == '+' && winptr[1] == '\0') {
830 if (s->curw->idx == INT_MAX)
831 goto not_found;
832 idx = s->curw->idx + 1;
833 } else if (winptr[0] == '-' && winptr[1] == '\0') {
834 if (s->curw->idx == 0)
835 goto not_found;
836 idx = s->curw->idx - 1;
837 } else if ((idx = cmd_lookup_index(s, winptr, &ambiguous)) == -1) {
838 if (ambiguous)
839 goto not_found;
840 ctx->error(ctx, "invalid index: %s", arg);
841 idx = -2;
844 if (sessptr != NULL)
845 xfree(sessptr);
846 return (idx);
848 no_colon:
850 * No colon in the string, first try special cases, then as a window
851 * and lastly as a session.
853 if (arg[0] == '!' && arg[1] == '\0') {
854 if ((wl = TAILQ_FIRST(&s->lastw)) == NULL)
855 goto not_found;
856 idx = wl->idx;
857 } else if (arg[0] == '+' && arg[1] == '\0') {
858 if (s->curw->idx == INT_MAX)
859 goto not_found;
860 idx = s->curw->idx + 1;
861 } else if (arg[0] == '-' && arg[1] == '\0') {
862 if (s->curw->idx == 0)
863 goto not_found;
864 idx = s->curw->idx - 1;
865 } else if ((idx = cmd_lookup_index(s, arg, &ambiguous)) == -1) {
866 if (ambiguous)
867 goto not_found;
868 if ((s = cmd_lookup_session(arg, &ambiguous)) == NULL)
869 goto no_session;
870 idx = -1;
873 if (sp != NULL)
874 *sp = s;
876 return (idx);
878 no_session:
879 if (ambiguous)
880 ctx->error(ctx, "multiple sessions: %s", arg);
881 else
882 ctx->error(ctx, "session not found: %s", arg);
883 if (sessptr != NULL)
884 xfree(sessptr);
885 return (-2);
887 not_found:
888 if (ambiguous)
889 ctx->error(ctx, "multiple windows: %s", arg);
890 else
891 ctx->error(ctx, "window not found: %s", arg);
892 if (sessptr != NULL)
893 xfree(sessptr);
894 return (-2);
898 * Find the target session, window and pane number or report an error and
899 * return NULL. The pane number is separated from the session:window by a .,
900 * such as mysession:mywindow.0.
902 struct winlink *
903 cmd_find_pane(struct cmd_ctx *ctx,
904 const char *arg, struct session **sp, struct window_pane **wpp)
906 struct session *s;
907 struct winlink *wl;
908 struct layout_cell *lc;
909 const char *period, *errstr;
910 char *winptr, *paneptr;
911 u_int idx;
913 /* Get the current session. */
914 if ((s = cmd_current_session(ctx)) == NULL) {
915 ctx->error(ctx, "can't establish current session");
916 return (NULL);
918 if (sp != NULL)
919 *sp = s;
921 /* A NULL argument means the current session, window and pane. */
922 if (arg == NULL) {
923 *wpp = s->curw->window->active;
924 return (s->curw);
927 /* Look for a separating period. */
928 if ((period = strrchr(arg, '.')) == NULL)
929 goto no_period;
931 /* Pull out the window part and parse it. */
932 winptr = xstrdup(arg);
933 winptr[period - arg] = '\0';
934 if (*winptr == '\0')
935 wl = s->curw;
936 else if ((wl = cmd_find_window(ctx, winptr, sp)) == NULL)
937 goto error;
939 /* Find the pane section and look it up. */
940 paneptr = winptr + (period - arg) + 1;
941 if (*paneptr == '\0')
942 *wpp = wl->window->active;
943 else {
944 idx = strtonum(paneptr, 0, INT_MAX, &errstr);
945 if (errstr != NULL)
946 goto lookup_string;
947 *wpp = window_pane_at_index(wl->window, idx);
948 if (*wpp == NULL)
949 goto lookup_string;
952 xfree(winptr);
953 return (wl);
955 lookup_string:
956 /* Try as next or previous pane. */
957 if (paneptr[0] == '+' && paneptr[1] == '\0') {
958 *wpp = TAILQ_NEXT(wl->window->active, entry);
959 if (*wpp == NULL)
960 *wpp = TAILQ_FIRST(&wl->window->panes);
961 return (wl);
963 if (paneptr[0] == '-' && paneptr[1] == '\0') {
964 *wpp = TAILQ_PREV(wl->window->active, window_panes, entry);
965 if (*wpp == NULL)
966 *wpp = TAILQ_LAST(&wl->window->panes, window_panes);
967 return (wl);
970 /* Try pane string description. */
971 if ((lc = layout_find_string(wl->window, paneptr)) == NULL) {
972 ctx->error(ctx, "can't find pane: %s", paneptr);
973 goto error;
975 *wpp = lc->wp;
977 xfree(winptr);
978 return (wl);
980 no_period:
981 /* Try as a pane number alone. */
982 idx = strtonum(arg, 0, INT_MAX, &errstr);
983 if (errstr != NULL)
984 goto lookup_window;
986 /* Try index in the current session and window. */
987 if ((*wpp = window_pane_at_index(s->curw->window, idx)) == NULL)
988 goto lookup_window;
990 return (s->curw);
992 lookup_window:
993 /* Try pane string description. */
994 if ((lc = layout_find_string(s->curw->window, arg)) != NULL) {
995 *wpp = lc->wp;
996 return (s->curw);
999 /* Try as a window and use the active pane. */
1000 if ((wl = cmd_find_window(ctx, arg, sp)) != NULL)
1001 *wpp = wl->window->active;
1002 return (wl);
1004 error:
1005 xfree(winptr);
1006 return (NULL);
1009 /* Replace the first %% or %idx in template by s. */
1010 char *
1011 cmd_template_replace(char *template, const char *s, int idx)
1013 char ch;
1014 char *buf, *ptr;
1015 int replaced;
1016 size_t len;
1018 if (strstr(template, "%") == NULL)
1019 return (xstrdup(template));
1021 buf = xmalloc(1);
1022 *buf = '\0';
1023 len = 0;
1024 replaced = 0;
1026 ptr = template;
1027 while (*ptr != '\0') {
1028 switch (ch = *ptr++) {
1029 case '%':
1030 if (*ptr < '1' || *ptr > '9' || *ptr - '0' != idx) {
1031 if (*ptr != '%' || replaced)
1032 break;
1033 replaced = 1;
1035 ptr++;
1037 len += strlen(s);
1038 buf = xrealloc(buf, 1, len + 1);
1039 strlcat(buf, s, len + 1);
1040 continue;
1042 buf = xrealloc(buf, 1, len + 2);
1043 buf[len++] = ch;
1044 buf[len] = '\0';
1047 return (buf);