xref: /netbsd-src/external/bsd/tmux/dist/cmd-find.c (revision f3cfa6f6ce31685c6c4a758bc430e69eb99f50a4)
1 /* $OpenBSD$ */
2 
3 /*
4  * Copyright (c) 2015 Nicholas Marriott <nicholas.marriott@gmail.com>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 
21 #include <fnmatch.h>
22 #include <limits.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26 
27 #include "tmux.h"
28 
29 static int	cmd_find_session_better(struct session *, struct session *,
30 		    int);
31 static struct session *cmd_find_best_session(struct session **, u_int, int);
32 static int	cmd_find_best_session_with_window(struct cmd_find_state *);
33 static int	cmd_find_best_winlink_with_window(struct cmd_find_state *);
34 
35 static const char *cmd_find_map_table(const char *[][2], const char *);
36 
37 static void	cmd_find_log_state(const char *, struct cmd_find_state *);
38 static int	cmd_find_get_session(struct cmd_find_state *, const char *);
39 static int	cmd_find_get_window(struct cmd_find_state *, const char *, int);
40 static int	cmd_find_get_window_with_session(struct cmd_find_state *,
41 		    const char *);
42 static int	cmd_find_get_pane(struct cmd_find_state *, const char *, int);
43 static int	cmd_find_get_pane_with_session(struct cmd_find_state *,
44 		    const char *);
45 static int	cmd_find_get_pane_with_window(struct cmd_find_state *,
46 		    const char *);
47 
48 static const char *cmd_find_session_table[][2] = {
49 	{ NULL, NULL }
50 };
51 static const char *cmd_find_window_table[][2] = {
52 	{ "{start}", "^" },
53 	{ "{last}", "!" },
54 	{ "{end}", "$" },
55 	{ "{next}", "+" },
56 	{ "{previous}", "-" },
57 	{ NULL, NULL }
58 };
59 static const char *cmd_find_pane_table[][2] = {
60 	{ "{last}", "!" },
61 	{ "{next}", "+" },
62 	{ "{previous}", "-" },
63 	{ "{top}", "top" },
64 	{ "{bottom}", "bottom" },
65 	{ "{left}", "left" },
66 	{ "{right}", "right" },
67 	{ "{top-left}", "top-left" },
68 	{ "{top-right}", "top-right" },
69 	{ "{bottom-left}", "bottom-left" },
70 	{ "{bottom-right}", "bottom-right" },
71 	{ "{up-of}", "{up-of}" },
72 	{ "{down-of}", "{down-of}" },
73 	{ "{left-of}", "{left-of}" },
74 	{ "{right-of}", "{right-of}" },
75 	{ NULL, NULL }
76 };
77 
78 /* Get session from TMUX if present. */
79 static struct session *
80 cmd_find_try_TMUX(struct client *c)
81 {
82 	struct environ_entry	*envent;
83 	char			 tmp[256];
84 	long long		 pid;
85 	u_int			 session;
86 	struct session		*s;
87 
88 	envent = environ_find(c->environ, "TMUX");
89 	if (envent == NULL)
90 		return (NULL);
91 
92 	if (sscanf(envent->value, "%255[^,],%lld,%d", tmp, &pid, &session) != 3)
93 		return (NULL);
94 	if (pid != getpid())
95 		return (NULL);
96 	log_debug("%s: client %p TMUX %s (session $%u)", __func__, c,
97 	    envent->value, session);
98 
99 	s = session_find_by_id(session);
100 	if (s != NULL)
101 		log_debug("%s: session $%u still exists", __func__, s->id);
102 	return (s);
103 }
104 
105 /* Find pane containing client if any. */
106 static struct window_pane *
107 cmd_find_inside_pane(struct client *c)
108 {
109 	struct window_pane	*wp;
110 
111 	if (c == NULL)
112 		return (NULL);
113 
114 	RB_FOREACH(wp, window_pane_tree, &all_window_panes) {
115 		if (wp->fd != -1 && strcmp(wp->tty, c->ttyname) == 0)
116 			break;
117 	}
118 	if (wp != NULL)
119 		log_debug("%s: got pane %%%u (%s)", __func__, wp->id, wp->tty);
120 	return (wp);
121 }
122 
123 /* Is this client better? */
124 static int
125 cmd_find_client_better(struct client *c, struct client *than)
126 {
127 	if (than == NULL)
128 		return (1);
129 	return (timercmp(&c->activity_time, &than->activity_time, >));
130 }
131 
132 /* Find best client for session. */
133 struct client *
134 cmd_find_best_client(struct session *s)
135 {
136 	struct client	*c_loop, *c;
137 
138 	if (s->flags & SESSION_UNATTACHED)
139 		s = NULL;
140 
141 	c = NULL;
142 	TAILQ_FOREACH(c_loop, &clients, entry) {
143 		if (c_loop->session == NULL)
144 			continue;
145 		if (s != NULL && c_loop->session != s)
146 			continue;
147 		if (cmd_find_client_better(c_loop, c))
148 			c = c_loop;
149 	}
150 	return (c);
151 }
152 
153 /* Is this session better? */
154 static int
155 cmd_find_session_better(struct session *s, struct session *than, int flags)
156 {
157 	int	attached;
158 
159 	if (than == NULL)
160 		return (1);
161 	if (flags & CMD_FIND_PREFER_UNATTACHED) {
162 		attached = (~than->flags & SESSION_UNATTACHED);
163 		if (attached && (s->flags & SESSION_UNATTACHED))
164 			return (1);
165 		else if (!attached && (~s->flags & SESSION_UNATTACHED))
166 			return (0);
167 	}
168 	return (timercmp(&s->activity_time, &than->activity_time, >));
169 }
170 
171 /* Find best session from a list, or all if list is NULL. */
172 static struct session *
173 cmd_find_best_session(struct session **slist, u_int ssize, int flags)
174 {
175 	struct session	 *s_loop, *s;
176 	u_int		  i;
177 
178 	log_debug("%s: %u sessions to try", __func__, ssize);
179 
180 	s = NULL;
181 	if (slist != NULL) {
182 		for (i = 0; i < ssize; i++) {
183 			if (cmd_find_session_better(slist[i], s, flags))
184 				s = slist[i];
185 		}
186 	} else {
187 		RB_FOREACH(s_loop, sessions, &sessions) {
188 			if (cmd_find_session_better(s_loop, s, flags))
189 				s = s_loop;
190 		}
191 	}
192 	return (s);
193 }
194 
195 /* Find best session and winlink for window. */
196 static int
197 cmd_find_best_session_with_window(struct cmd_find_state *fs)
198 {
199 	struct session	**slist = NULL;
200 	u_int		  ssize;
201 	struct session	 *s;
202 
203 	log_debug("%s: window is @%u", __func__, fs->w->id);
204 
205 	ssize = 0;
206 	RB_FOREACH(s, sessions, &sessions) {
207 		if (!session_has(s, fs->w))
208 			continue;
209 		slist = xreallocarray(slist, ssize + 1, sizeof *slist);
210 		slist[ssize++] = s;
211 	}
212 	if (ssize == 0)
213 		goto fail;
214 	fs->s = cmd_find_best_session(slist, ssize, fs->flags);
215 	if (fs->s == NULL)
216 		goto fail;
217 	free(slist);
218 	return (cmd_find_best_winlink_with_window(fs));
219 
220 fail:
221 	free(slist);
222 	return (-1);
223 }
224 
225 /*
226  * Find the best winlink for a window (the current if it contains the window,
227  * otherwise the first).
228  */
229 static int
230 cmd_find_best_winlink_with_window(struct cmd_find_state *fs)
231 {
232 	struct winlink	 *wl, *wl_loop;
233 
234 	log_debug("%s: window is @%u", __func__, fs->w->id);
235 
236 	wl = NULL;
237 	if (fs->s->curw != NULL && fs->s->curw->window == fs->w)
238 		wl = fs->s->curw;
239 	else {
240 		RB_FOREACH(wl_loop, winlinks, &fs->s->windows) {
241 			if (wl_loop->window == fs->w) {
242 				wl = wl_loop;
243 				break;
244 			}
245 		}
246 	}
247 	if (wl == NULL)
248 		return (-1);
249 	fs->wl = wl;
250 	fs->idx = fs->wl->idx;
251 	return (0);
252 }
253 
254 /* Maps string in table. */
255 static const char *
256 cmd_find_map_table(const char *table[][2], const char *s)
257 {
258 	u_int	i;
259 
260 	for (i = 0; table[i][0] != NULL; i++) {
261 		if (strcmp(s, table[i][0]) == 0)
262 			return (table[i][1]);
263 	}
264 	return (s);
265 }
266 
267 /* Find session from string. Fills in s. */
268 static int
269 cmd_find_get_session(struct cmd_find_state *fs, const char *session)
270 {
271 	struct session	*s, *s_loop;
272 	struct client	*c;
273 
274 	log_debug("%s: %s", __func__, session);
275 
276 	/* Check for session ids starting with $. */
277 	if (*session == '$') {
278 		fs->s = session_find_by_id_str(session);
279 		if (fs->s == NULL)
280 			return (-1);
281 		return (0);
282 	}
283 
284 	/* Look for exactly this session. */
285 	fs->s = session_find(session);
286 	if (fs->s != NULL)
287 		return (0);
288 
289 	/* Look for as a client. */
290 	c = cmd_find_client(NULL, session, 1);
291 	if (c != NULL && c->session != NULL) {
292 		fs->s = c->session;
293 		return (0);
294 	}
295 
296 	/* Stop now if exact only. */
297 	if (fs->flags & CMD_FIND_EXACT_SESSION)
298 		return (-1);
299 
300 	/* Otherwise look for prefix. */
301 	s = NULL;
302 	RB_FOREACH(s_loop, sessions, &sessions) {
303 		if (strncmp(session, s_loop->name, strlen(session)) == 0) {
304 			if (s != NULL)
305 				return (-1);
306 			s = s_loop;
307 		}
308 	}
309 	if (s != NULL) {
310 		fs->s = s;
311 		return (0);
312 	}
313 
314 	/* Then as a pattern. */
315 	s = NULL;
316 	RB_FOREACH(s_loop, sessions, &sessions) {
317 		if (fnmatch(session, s_loop->name, 0) == 0) {
318 			if (s != NULL)
319 				return (-1);
320 			s = s_loop;
321 		}
322 	}
323 	if (s != NULL) {
324 		fs->s = s;
325 		return (0);
326 	}
327 
328 	return (-1);
329 }
330 
331 /* Find window from string. Fills in s, wl, w. */
332 static int
333 cmd_find_get_window(struct cmd_find_state *fs, const char *window, int only)
334 {
335 	log_debug("%s: %s", __func__, window);
336 
337 	/* Check for window ids starting with @. */
338 	if (*window == '@') {
339 		fs->w = window_find_by_id_str(window);
340 		if (fs->w == NULL)
341 			return (-1);
342 		return (cmd_find_best_session_with_window(fs));
343 	}
344 
345 	/* Not a window id, so use the current session. */
346 	fs->s = fs->current->s;
347 
348 	/* We now only need to find the winlink in this session. */
349 	if (cmd_find_get_window_with_session(fs, window) == 0)
350 		return (0);
351 
352 	/* Otherwise try as a session itself. */
353 	if (!only && cmd_find_get_session(fs, window) == 0) {
354 		fs->wl = fs->s->curw;
355 		fs->w = fs->wl->window;
356 		if (~fs->flags & CMD_FIND_WINDOW_INDEX)
357 			fs->idx = fs->wl->idx;
358 		return (0);
359 	}
360 
361 	return (-1);
362 }
363 
364 /*
365  * Find window from string, assuming it is in given session. Needs s, fills in
366  * wl and w.
367  */
368 static int
369 cmd_find_get_window_with_session(struct cmd_find_state *fs, const char *window)
370 {
371 	struct winlink	*wl;
372 	const char	*errstr;
373 	int		 idx, n, exact;
374 	struct session	*s;
375 
376 	log_debug("%s: %s", __func__, window);
377 	exact = (fs->flags & CMD_FIND_EXACT_WINDOW);
378 
379 	/*
380 	 * Start with the current window as the default. So if only an index is
381 	 * found, the window will be the current.
382 	 */
383 	fs->wl = fs->s->curw;
384 	fs->w = fs->wl->window;
385 
386 	/* Check for window ids starting with @. */
387 	if (*window == '@') {
388 		fs->w = window_find_by_id_str(window);
389 		if (fs->w == NULL || !session_has(fs->s, fs->w))
390 			return (-1);
391 		return (cmd_find_best_winlink_with_window(fs));
392 	}
393 
394 	/* Try as an offset. */
395 	if (!exact && (window[0] == '+' || window[0] == '-')) {
396 		if (window[1] != '\0')
397 			n = strtonum(window + 1, 1, INT_MAX, NULL);
398 		else
399 			n = 1;
400 		s = fs->s;
401 		if (fs->flags & CMD_FIND_WINDOW_INDEX) {
402 			if (window[0] == '+') {
403 				if (INT_MAX - s->curw->idx < n)
404 					return (-1);
405 				fs->idx = s->curw->idx + n;
406 			} else {
407 				if (n > s->curw->idx)
408 					return (-1);
409 				fs->idx = s->curw->idx - n;
410 			}
411 			return (0);
412 		}
413 		if (window[0] == '+')
414 			fs->wl = winlink_next_by_number(s->curw, s, n);
415 		else
416 			fs->wl = winlink_previous_by_number(s->curw, s, n);
417 		if (fs->wl != NULL) {
418 			fs->idx = fs->wl->idx;
419 			fs->w = fs->wl->window;
420 			return (0);
421 		}
422 	}
423 
424 	/* Try special characters. */
425 	if (!exact) {
426 		if (strcmp(window, "!") == 0) {
427 			fs->wl = TAILQ_FIRST(&fs->s->lastw);
428 			if (fs->wl == NULL)
429 				return (-1);
430 			fs->idx = fs->wl->idx;
431 			fs->w = fs->wl->window;
432 			return (0);
433 		} else if (strcmp(window, "^") == 0) {
434 			fs->wl = RB_MIN(winlinks, &fs->s->windows);
435 			if (fs->wl == NULL)
436 				return (-1);
437 			fs->idx = fs->wl->idx;
438 			fs->w = fs->wl->window;
439 			return (0);
440 		} else if (strcmp(window, "$") == 0) {
441 			fs->wl = RB_MAX(winlinks, &fs->s->windows);
442 			if (fs->wl == NULL)
443 				return (-1);
444 			fs->idx = fs->wl->idx;
445 			fs->w = fs->wl->window;
446 			return (0);
447 		}
448 	}
449 
450 	/* First see if this is a valid window index in this session. */
451 	if (window[0] != '+' && window[0] != '-') {
452 		idx = strtonum(window, 0, INT_MAX, &errstr);
453 		if (errstr == NULL) {
454 			fs->wl = winlink_find_by_index(&fs->s->windows, idx);
455 			if (fs->wl != NULL) {
456 				fs->w = fs->wl->window;
457 				return (0);
458 			}
459 			if (fs->flags & CMD_FIND_WINDOW_INDEX) {
460 				fs->idx = idx;
461 				return (0);
462 			}
463 		}
464 	}
465 
466 	/* Look for exact matches, error if more than one. */
467 	fs->wl = NULL;
468 	RB_FOREACH(wl, winlinks, &fs->s->windows) {
469 		if (strcmp(window, wl->window->name) == 0) {
470 			if (fs->wl != NULL)
471 				return (-1);
472 			fs->wl = wl;
473 		}
474 	}
475 	if (fs->wl != NULL) {
476 		fs->idx = fs->wl->idx;
477 		fs->w = fs->wl->window;
478 		return (0);
479 	}
480 
481 	/* Stop now if exact only. */
482 	if (exact)
483 		return (-1);
484 
485 	/* Try as the start of a window name, error if multiple. */
486 	fs->wl = NULL;
487 	RB_FOREACH(wl, winlinks, &fs->s->windows) {
488 		if (strncmp(window, wl->window->name, strlen(window)) == 0) {
489 			if (fs->wl != NULL)
490 				return (-1);
491 			fs->wl = wl;
492 		}
493 	}
494 	if (fs->wl != NULL) {
495 		fs->idx = fs->wl->idx;
496 		fs->w = fs->wl->window;
497 		return (0);
498 	}
499 
500 	/* Now look for pattern matches, again error if multiple. */
501 	fs->wl = NULL;
502 	RB_FOREACH(wl, winlinks, &fs->s->windows) {
503 		if (fnmatch(window, wl->window->name, 0) == 0) {
504 			if (fs->wl != NULL)
505 				return (-1);
506 			fs->wl = wl;
507 		}
508 	}
509 	if (fs->wl != NULL) {
510 		fs->idx = fs->wl->idx;
511 		fs->w = fs->wl->window;
512 		return (0);
513 	}
514 
515 	return (-1);
516 }
517 
518 /* Find pane from string. Fills in s, wl, w, wp. */
519 static int
520 cmd_find_get_pane(struct cmd_find_state *fs, const char *pane, int only)
521 {
522 	log_debug("%s: %s", __func__, pane);
523 
524 	/* Check for pane ids starting with %. */
525 	if (*pane == '%') {
526 		fs->wp = window_pane_find_by_id_str(pane);
527 		if (fs->wp == NULL)
528 			return (-1);
529 		fs->w = fs->wp->window;
530 		return (cmd_find_best_session_with_window(fs));
531 	}
532 
533 	/* Not a pane id, so try the current session and window. */
534 	fs->s = fs->current->s;
535 	fs->wl = fs->current->wl;
536 	fs->idx = fs->current->idx;
537 	fs->w = fs->current->w;
538 
539 	/* We now only need to find the pane in this window. */
540 	if (cmd_find_get_pane_with_window(fs, pane) == 0)
541 		return (0);
542 
543 	/* Otherwise try as a window itself (this will also try as session). */
544 	if (!only && cmd_find_get_window(fs, pane, 0) == 0) {
545 		fs->wp = fs->w->active;
546 		return (0);
547 	}
548 
549 	return (-1);
550 }
551 
552 /*
553  * Find pane from string, assuming it is in given session. Needs s, fills in wl
554  * and w and wp.
555  */
556 static int
557 cmd_find_get_pane_with_session(struct cmd_find_state *fs, const char *pane)
558 {
559 	log_debug("%s: %s", __func__, pane);
560 
561 	/* Check for pane ids starting with %. */
562 	if (*pane == '%') {
563 		fs->wp = window_pane_find_by_id_str(pane);
564 		if (fs->wp == NULL)
565 			return (-1);
566 		fs->w = fs->wp->window;
567 		return (cmd_find_best_winlink_with_window(fs));
568 	}
569 
570 	/* Otherwise use the current window. */
571 	fs->wl = fs->s->curw;
572 	fs->idx = fs->wl->idx;
573 	fs->w = fs->wl->window;
574 
575 	/* Now we just need to look up the pane. */
576 	return (cmd_find_get_pane_with_window(fs, pane));
577 }
578 
579 /*
580  * Find pane from string, assuming it is in the given window. Needs w, fills in
581  * wp.
582  */
583 static int
584 cmd_find_get_pane_with_window(struct cmd_find_state *fs, const char *pane)
585 {
586 	const char		*errstr;
587 	int			 idx;
588 	struct window_pane	*wp;
589 	u_int			 n;
590 
591 	log_debug("%s: %s", __func__, pane);
592 
593 	/* Check for pane ids starting with %. */
594 	if (*pane == '%') {
595 		fs->wp = window_pane_find_by_id_str(pane);
596 		if (fs->wp == NULL)
597 			return (-1);
598 		if (fs->wp->window != fs->w)
599 			return (-1);
600 		return (0);
601 	}
602 
603 	/* Try special characters. */
604 	if (strcmp(pane, "!") == 0) {
605 		fs->wp = fs->w->last;
606 		if (fs->wp == NULL)
607 			return (-1);
608 		return (0);
609 	} else if (strcmp(pane, "{up-of}") == 0) {
610 		fs->wp = window_pane_find_up(fs->w->active);
611 		if (fs->wp == NULL)
612 			return (-1);
613 		return (0);
614 	} else if (strcmp(pane, "{down-of}") == 0) {
615 		fs->wp = window_pane_find_down(fs->w->active);
616 		if (fs->wp == NULL)
617 			return (-1);
618 		return (0);
619 	} else if (strcmp(pane, "{left-of}") == 0) {
620 		fs->wp = window_pane_find_left(fs->w->active);
621 		if (fs->wp == NULL)
622 			return (-1);
623 		return (0);
624 	} else if (strcmp(pane, "{right-of}") == 0) {
625 		fs->wp = window_pane_find_right(fs->w->active);
626 		if (fs->wp == NULL)
627 			return (-1);
628 		return (0);
629 	}
630 
631 	/* Try as an offset. */
632 	if (pane[0] == '+' || pane[0] == '-') {
633 		if (pane[1] != '\0')
634 			n = strtonum(pane + 1, 1, INT_MAX, NULL);
635 		else
636 			n = 1;
637 		wp = fs->w->active;
638 		if (pane[0] == '+')
639 			fs->wp = window_pane_next_by_number(fs->w, wp, n);
640 		else
641 			fs->wp = window_pane_previous_by_number(fs->w, wp, n);
642 		if (fs->wp != NULL)
643 			return (0);
644 	}
645 
646 	/* Get pane by index. */
647 	idx = strtonum(pane, 0, INT_MAX, &errstr);
648 	if (errstr == NULL) {
649 		fs->wp = window_pane_at_index(fs->w, idx);
650 		if (fs->wp != NULL)
651 			return (0);
652 	}
653 
654 	/* Try as a description. */
655 	fs->wp = window_find_string(fs->w, pane);
656 	if (fs->wp != NULL)
657 		return (0);
658 
659 	return (-1);
660 }
661 
662 /* Clear state. */
663 void
664 cmd_find_clear_state(struct cmd_find_state *fs, int flags)
665 {
666 	memset(fs, 0, sizeof *fs);
667 
668 	fs->flags = flags;
669 
670 	fs->idx = -1;
671 }
672 
673 /* Check if state is empty. */
674 int
675 cmd_find_empty_state(struct cmd_find_state *fs)
676 {
677 	if (fs->s == NULL && fs->wl == NULL && fs->w == NULL && fs->wp == NULL)
678 		return (1);
679 	return (0);
680 }
681 
682 /* Check if a state if valid. */
683 int
684 cmd_find_valid_state(struct cmd_find_state *fs)
685 {
686 	struct winlink	*wl;
687 
688 	if (fs->s == NULL || fs->wl == NULL || fs->w == NULL || fs->wp == NULL)
689 		return (0);
690 
691 	if (!session_alive(fs->s))
692 		return (0);
693 
694 	RB_FOREACH(wl, winlinks, &fs->s->windows) {
695 		if (wl->window == fs->w && wl == fs->wl)
696 			break;
697 	}
698 	if (wl == NULL)
699 		return (0);
700 
701 	if (fs->w != fs->wl->window)
702 		return (0);
703 
704 	return (window_has_pane(fs->w, fs->wp));
705 }
706 
707 /* Copy a state. */
708 void
709 cmd_find_copy_state(struct cmd_find_state *dst, struct cmd_find_state *src)
710 {
711 	dst->s = src->s;
712 	dst->wl = src->wl;
713 	dst->idx = src->idx;
714 	dst->w = src->w;
715 	dst->wp = src->wp;
716 }
717 
718 /* Log the result. */
719 static void
720 cmd_find_log_state(const char *prefix, struct cmd_find_state *fs)
721 {
722 	if (fs->s != NULL)
723 		log_debug("%s: s=$%u %s", prefix, fs->s->id, fs->s->name);
724 	else
725 		log_debug("%s: s=none", prefix);
726 	if (fs->wl != NULL) {
727 		log_debug("%s: wl=%u %d w=@%u %s", prefix, fs->wl->idx,
728 		    fs->wl->window == fs->w, fs->w->id, fs->w->name);
729 	} else
730 		log_debug("%s: wl=none", prefix);
731 	if (fs->wp != NULL)
732 		log_debug("%s: wp=%%%u", prefix, fs->wp->id);
733 	else
734 		log_debug("%s: wp=none", prefix);
735 	if (fs->idx != -1)
736 		log_debug("%s: idx=%d", prefix, fs->idx);
737 	else
738 		log_debug("%s: idx=none", prefix);
739 }
740 
741 /* Find state from a session. */
742 void
743 cmd_find_from_session(struct cmd_find_state *fs, struct session *s, int flags)
744 {
745 	cmd_find_clear_state(fs, flags);
746 
747 	fs->s = s;
748 	fs->wl = fs->s->curw;
749 	fs->w = fs->wl->window;
750 	fs->wp = fs->w->active;
751 
752 	cmd_find_log_state(__func__, fs);
753 }
754 
755 /* Find state from a winlink. */
756 void
757 cmd_find_from_winlink(struct cmd_find_state *fs, struct winlink *wl, int flags)
758 {
759 	cmd_find_clear_state(fs, flags);
760 
761 	fs->s = wl->session;
762 	fs->wl = wl;
763 	fs->w = wl->window;
764 	fs->wp = wl->window->active;
765 
766 	cmd_find_log_state(__func__, fs);
767 }
768 
769 /* Find state from a session and window. */
770 int
771 cmd_find_from_session_window(struct cmd_find_state *fs, struct session *s,
772     struct window *w, int flags)
773 {
774 	cmd_find_clear_state(fs, flags);
775 
776 	fs->s = s;
777 	fs->w = w;
778 	if (cmd_find_best_winlink_with_window(fs) != 0) {
779 		cmd_find_clear_state(fs, flags);
780 		return (-1);
781 	}
782 	fs->wp = fs->w->active;
783 
784 	cmd_find_log_state(__func__, fs);
785 	return (0);
786 }
787 
788 /* Find state from a window. */
789 int
790 cmd_find_from_window(struct cmd_find_state *fs, struct window *w, int flags)
791 {
792 	cmd_find_clear_state(fs, flags);
793 
794 	fs->w = w;
795 	if (cmd_find_best_session_with_window(fs) != 0) {
796 		cmd_find_clear_state(fs, flags);
797 		return (-1);
798 	}
799 	if (cmd_find_best_winlink_with_window(fs) != 0) {
800 		cmd_find_clear_state(fs, flags);
801 		return (-1);
802 	}
803 	fs->wp = fs->w->active;
804 
805 	cmd_find_log_state(__func__, fs);
806 	return (0);
807 }
808 
809 /* Find state from a winlink and pane. */
810 void
811 cmd_find_from_winlink_pane(struct cmd_find_state *fs, struct winlink *wl,
812     struct window_pane *wp, int flags)
813 {
814 	cmd_find_clear_state(fs, flags);
815 
816 	fs->s = wl->session;
817 	fs->wl = wl;
818 	fs->idx = fs->wl->idx;
819 	fs->w = fs->wl->window;
820 	fs->wp = wp;
821 
822 	cmd_find_log_state(__func__, fs);
823 }
824 
825 /* Find state from a pane. */
826 int
827 cmd_find_from_pane(struct cmd_find_state *fs, struct window_pane *wp, int flags)
828 {
829 	if (cmd_find_from_window(fs, wp->window, flags) != 0)
830 		return (-1);
831 	fs->wp = wp;
832 
833 	cmd_find_log_state(__func__, fs);
834 	return (0);
835 }
836 
837 /* Find state from nothing. */
838 int
839 cmd_find_from_nothing(struct cmd_find_state *fs, int flags)
840 {
841 	cmd_find_clear_state(fs, flags);
842 
843 	fs->s = cmd_find_best_session(NULL, 0, flags);
844 	if (fs->s == NULL) {
845 		cmd_find_clear_state(fs, flags);
846 		return (-1);
847 	}
848 	fs->wl = fs->s->curw;
849 	fs->idx = fs->wl->idx;
850 	fs->w = fs->wl->window;
851 	fs->wp = fs->w->active;
852 
853 	cmd_find_log_state(__func__, fs);
854 	return (0);
855 }
856 
857 /* Find state from mouse. */
858 int
859 cmd_find_from_mouse(struct cmd_find_state *fs, struct mouse_event *m, int flags)
860 {
861 	cmd_find_clear_state(fs, flags);
862 
863 	if (!m->valid)
864 		return (-1);
865 
866 	fs->wp = cmd_mouse_pane(m, &fs->s, &fs->wl);
867 	if (fs->wp == NULL) {
868 		cmd_find_clear_state(fs, flags);
869 		return (-1);
870 	}
871 	fs->w = fs->wl->window;
872 
873 	cmd_find_log_state(__func__, fs);
874 	return (0);
875 }
876 
877 /* Find state from client. */
878 int
879 cmd_find_from_client(struct cmd_find_state *fs, struct client *c, int flags)
880 {
881 	struct session		*s;
882 	struct winlink		*wl;
883 	struct window_pane	*wp;
884 
885 	/* If no client, treat as from nothing. */
886 	if (c == NULL)
887 		return (cmd_find_from_nothing(fs, flags));
888 
889 	/* If this is an attached client, all done. */
890 	if (c->session != NULL) {
891 		cmd_find_from_session(fs, c->session, flags);
892 		return (0);
893 	}
894 	cmd_find_clear_state(fs, flags);
895 
896 	/*
897 	 * If this is an unattached client running in a pane, we can use that
898 	 * to limit the list of sessions to those containing that pane.
899 	 */
900 	wp = cmd_find_inside_pane(c);
901 	if (wp == NULL)
902 		goto unknown_pane;
903 
904 	/* If we have a session in TMUX, see if it has this pane. */
905 	s = cmd_find_try_TMUX(c);
906 	if (s != NULL) {
907 		RB_FOREACH(wl, winlinks, &s->windows) {
908 			if (window_has_pane(wl->window, wp))
909 				break;
910 		}
911 		if (wl != NULL) {
912 			log_debug("%s: session $%u has pane %%%u", __func__,
913 			    s->id, wp->id);
914 
915 			fs->s = s;
916 			fs->wl = s->curw; /* use current session */
917 			fs->w = fs->wl->window;
918 			fs->wp = fs->w->active; /* use active pane */
919 
920 			cmd_find_log_state(__func__, fs);
921 			return (0);
922 		} else {
923 			log_debug("%s: session $%u does not have pane %%%u",
924 			    __func__, s->id, wp->id);
925 		}
926 	}
927 
928 	/*
929 	 * Don't have a session, or it doesn't have this pane. Try all
930 	 * sessions.
931 	 */
932 	fs->w = wp->window;
933 	if (cmd_find_best_session_with_window(fs) != 0) {
934 		/*
935 		 * The window may have been destroyed but the pane
936 		 * still on all_window_panes due to something else
937 		 * holding a reference.
938 		 */
939 		goto unknown_pane;
940 	}
941 	fs->wl = fs->s->curw;
942 	fs->w = fs->wl->window;
943 	fs->wp = fs->w->active; /* use active pane */
944 
945 	cmd_find_log_state(__func__, fs);
946 	return (0);
947 
948 unknown_pane:
949 	/*
950 	 * We're not running in a known pane, but maybe this client has TMUX
951 	 * in the environment. That'd give us a session.
952 	 */
953 	s = cmd_find_try_TMUX(c);
954 	if (s != NULL) {
955 		cmd_find_from_session(fs, s, flags);
956 		return (0);
957 	}
958 
959 	/* Otherwise we need to guess. */
960 	return (cmd_find_from_nothing(fs, flags));
961 }
962 
963 /*
964  * Split target into pieces and resolve for the given type. Fills in the given
965  * state. Returns 0 on success or -1 on error.
966  */
967 int
968 cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item,
969     const char *target, enum cmd_find_type type, int flags)
970 {
971 	struct mouse_event	*m;
972 	struct cmd_find_state	 current;
973 	char			*colon, *period, *copy = NULL;
974 	const char		*session, *window, *pane, *s;
975 	int			 window_only = 0, pane_only = 0;
976 
977 	/* Can fail flag implies quiet. */
978 	if (flags & CMD_FIND_CANFAIL)
979 		flags |= CMD_FIND_QUIET;
980 
981 	/* Log the arguments. */
982 	if (type == CMD_FIND_PANE)
983 		s = "pane";
984 	else if (type == CMD_FIND_WINDOW)
985 		s = "window";
986 	else if (type == CMD_FIND_SESSION)
987 		s = "session";
988 	else
989 		s = "unknown";
990 	if (target == NULL)
991 		log_debug("%s: target none, type %s", __func__, s);
992 	else
993 		log_debug("%s: target %s, type %s", __func__, target, s);
994 	log_debug("%s: item %p, flags %#x", __func__, item, flags);
995 
996 	/* Clear new state. */
997 	cmd_find_clear_state(fs, flags);
998 
999 	/* Find current state. */
1000 	if (server_check_marked() && (flags & CMD_FIND_DEFAULT_MARKED)) {
1001 		fs->current = &marked_pane;
1002 		log_debug("%s: current is marked pane", __func__);
1003 	} else if (cmd_find_valid_state(&item->shared->current)) {
1004 		fs->current = &item->shared->current;
1005 		log_debug("%s: current is from queue", __func__);
1006 	} else if (cmd_find_from_client(&current, item->client, flags) == 0) {
1007 		fs->current = &current;
1008 		log_debug("%s: current is from client", __func__);
1009 	} else {
1010 		if (~flags & CMD_FIND_QUIET)
1011 			cmdq_error(item, "no current target");
1012 		goto error;
1013 	}
1014 	if (!cmd_find_valid_state(fs->current))
1015 		fatalx("invalid current find state");
1016 
1017 	/* An empty or NULL target is the current. */
1018 	if (target == NULL || *target == '\0')
1019 		goto current;
1020 
1021 	/* Mouse target is a plain = or {mouse}. */
1022 	if (strcmp(target, "=") == 0 || strcmp(target, "{mouse}") == 0) {
1023 		m = &item->shared->mouse;
1024 		switch (type) {
1025 		case CMD_FIND_PANE:
1026 			fs->wp = cmd_mouse_pane(m, &fs->s, &fs->wl);
1027 			if (fs->wp != NULL)
1028 				fs->w = fs->wl->window;
1029 			break;
1030 		case CMD_FIND_WINDOW:
1031 		case CMD_FIND_SESSION:
1032 			fs->wl = cmd_mouse_window(m, &fs->s);
1033 			if (fs->wl != NULL) {
1034 				fs->w = fs->wl->window;
1035 				fs->wp = fs->w->active;
1036 			}
1037 			break;
1038 		}
1039 		if (fs->wp == NULL) {
1040 			if (~flags & CMD_FIND_QUIET)
1041 				cmdq_error(item, "no mouse target");
1042 			goto error;
1043 		}
1044 		goto found;
1045 	}
1046 
1047 	/* Marked target is a plain ~ or {marked}. */
1048 	if (strcmp(target, "~") == 0 || strcmp(target, "{marked}") == 0) {
1049 		if (!server_check_marked()) {
1050 			if (~flags & CMD_FIND_QUIET)
1051 				cmdq_error(item, "no marked target");
1052 			goto error;
1053 		}
1054 		cmd_find_copy_state(fs, &marked_pane);
1055 		goto found;
1056 	}
1057 
1058 	/* Find separators if they exist. */
1059 	copy = xstrdup(target);
1060 	colon = strchr(copy, ':');
1061 	if (colon != NULL)
1062 		*colon++ = '\0';
1063 	if (colon == NULL)
1064 		period = strchr(copy, '.');
1065 	else
1066 		period = strchr(colon, '.');
1067 	if (period != NULL)
1068 		*period++ = '\0';
1069 
1070 	/* Set session, window and pane parts. */
1071 	session = window = pane = NULL;
1072 	if (colon != NULL && period != NULL) {
1073 		session = copy;
1074 		window = colon;
1075 		window_only = 1;
1076 		pane = period;
1077 		pane_only = 1;
1078 	} else if (colon != NULL && period == NULL) {
1079 		session = copy;
1080 		window = colon;
1081 		window_only = 1;
1082 	} else if (colon == NULL && period != NULL) {
1083 		window = copy;
1084 		pane = period;
1085 		pane_only = 1;
1086 	} else {
1087 		if (*copy == '$')
1088 			session = copy;
1089 		else if (*copy == '@')
1090 			window = copy;
1091 		else if (*copy == '%')
1092 			pane = copy;
1093 		else {
1094 			switch (type) {
1095 			case CMD_FIND_SESSION:
1096 				session = copy;
1097 				break;
1098 			case CMD_FIND_WINDOW:
1099 				window = copy;
1100 				break;
1101 			case CMD_FIND_PANE:
1102 				pane = copy;
1103 				break;
1104 			}
1105 		}
1106 	}
1107 
1108 	/* Set exact match flags. */
1109 	if (session != NULL && *session == '=') {
1110 		session++;
1111 		fs->flags |= CMD_FIND_EXACT_SESSION;
1112 	}
1113 	if (window != NULL && *window == '=') {
1114 		window++;
1115 		fs->flags |= CMD_FIND_EXACT_WINDOW;
1116 	}
1117 
1118 	/* Empty is the same as NULL. */
1119 	if (session != NULL && *session == '\0')
1120 		session = NULL;
1121 	if (window != NULL && *window == '\0')
1122 		window = NULL;
1123 	if (pane != NULL && *pane == '\0')
1124 		pane = NULL;
1125 
1126 	/* Map though conversion table. */
1127 	if (session != NULL)
1128 		session = cmd_find_map_table(cmd_find_session_table, session);
1129 	if (window != NULL)
1130 		window = cmd_find_map_table(cmd_find_window_table, window);
1131 	if (pane != NULL)
1132 		pane = cmd_find_map_table(cmd_find_pane_table, pane);
1133 
1134 	log_debug("%s: target %s (flags %#x): session=%s, window=%s, pane=%s",
1135 	    __func__, target, flags, session == NULL ? "none" : session,
1136 	    window == NULL ? "none" : window, pane == NULL ? "none" : pane);
1137 
1138 	/* No pane is allowed if want an index. */
1139 	if (pane != NULL && (flags & CMD_FIND_WINDOW_INDEX)) {
1140 		if (~flags & CMD_FIND_QUIET)
1141 			cmdq_error(item, "can't specify pane here");
1142 		goto error;
1143 	}
1144 
1145 	/* If the session isn't NULL, look it up. */
1146 	if (session != NULL) {
1147 		/* This will fill in session. */
1148 		if (cmd_find_get_session(fs, session) != 0)
1149 			goto no_session;
1150 
1151 		/* If window and pane are NULL, use that session's current. */
1152 		if (window == NULL && pane == NULL) {
1153 			fs->wl = fs->s->curw;
1154 			fs->idx = -1;
1155 			fs->w = fs->wl->window;
1156 			fs->wp = fs->w->active;
1157 			goto found;
1158 		}
1159 
1160 		/* If window is present but pane not, find window in session. */
1161 		if (window != NULL && pane == NULL) {
1162 			/* This will fill in winlink and window. */
1163 			if (cmd_find_get_window_with_session(fs, window) != 0)
1164 				goto no_window;
1165 			if (fs->wl != NULL) /* can be NULL if index only */
1166 				fs->wp = fs->wl->window->active;
1167 			goto found;
1168 		}
1169 
1170 		/* If pane is present but window not, find pane. */
1171 		if (window == NULL && pane != NULL) {
1172 			/* This will fill in winlink and window and pane. */
1173 			if (cmd_find_get_pane_with_session(fs, pane) != 0)
1174 				goto no_pane;
1175 			goto found;
1176 		}
1177 
1178 		/*
1179 		 * If window and pane are present, find both in session. This
1180 		 * will fill in winlink and window.
1181 		 */
1182 		if (cmd_find_get_window_with_session(fs, window) != 0)
1183 			goto no_window;
1184 		/* This will fill in pane. */
1185 		if (cmd_find_get_pane_with_window(fs, pane) != 0)
1186 			goto no_pane;
1187 		goto found;
1188 	}
1189 
1190 	/* No session. If window and pane, try them. */
1191 	if (window != NULL && pane != NULL) {
1192 		/* This will fill in session, winlink and window. */
1193 		if (cmd_find_get_window(fs, window, window_only) != 0)
1194 			goto no_window;
1195 		/* This will fill in pane. */
1196 		if (cmd_find_get_pane_with_window(fs, pane) != 0)
1197 			goto no_pane;
1198 		goto found;
1199 	}
1200 
1201 	/* If just window is present, try it. */
1202 	if (window != NULL && pane == NULL) {
1203 		/* This will fill in session, winlink and window. */
1204 		if (cmd_find_get_window(fs, window, window_only) != 0)
1205 			goto no_window;
1206 		if (fs->wl != NULL) /* can be NULL if index only */
1207 			fs->wp = fs->wl->window->active;
1208 		goto found;
1209 	}
1210 
1211 	/* If just pane is present, try it. */
1212 	if (window == NULL && pane != NULL) {
1213 		/* This will fill in session, winlink, window and pane. */
1214 		if (cmd_find_get_pane(fs, pane, pane_only) != 0)
1215 			goto no_pane;
1216 		goto found;
1217 	}
1218 
1219 current:
1220 	/* Use the current session. */
1221 	cmd_find_copy_state(fs, fs->current);
1222 	if (flags & CMD_FIND_WINDOW_INDEX)
1223 		fs->idx = -1;
1224 	goto found;
1225 
1226 error:
1227 	fs->current = NULL;
1228 	log_debug("%s: error", __func__);
1229 
1230 	free(copy);
1231 	if (flags & CMD_FIND_CANFAIL)
1232 		return (0);
1233 	return (-1);
1234 
1235 found:
1236 	fs->current = NULL;
1237 	cmd_find_log_state(__func__, fs);
1238 
1239 	free(copy);
1240 	return (0);
1241 
1242 no_session:
1243 	if (~flags & CMD_FIND_QUIET)
1244 		cmdq_error(item, "can't find session %s", session);
1245 	goto error;
1246 
1247 no_window:
1248 	if (~flags & CMD_FIND_QUIET)
1249 		cmdq_error(item, "can't find window %s", window);
1250 	goto error;
1251 
1252 no_pane:
1253 	if (~flags & CMD_FIND_QUIET)
1254 		cmdq_error(item, "can't find pane %s", pane);
1255 	goto error;
1256 }
1257 
1258 /* Find the current client. */
1259 static struct client *
1260 cmd_find_current_client(struct cmdq_item *item, int quiet)
1261 {
1262 	struct client		*c;
1263 	struct session		*s;
1264 	struct window_pane	*wp;
1265 	struct cmd_find_state	 fs;
1266 
1267 	if (item->client != NULL && item->client->session != NULL)
1268 		return (item->client);
1269 
1270 	c = NULL;
1271 	if ((wp = cmd_find_inside_pane(item->client)) != NULL) {
1272 		cmd_find_clear_state(&fs, CMD_FIND_QUIET);
1273 		fs.w = wp->window;
1274 		if (cmd_find_best_session_with_window(&fs) == 0)
1275 			c = cmd_find_best_client(fs.s);
1276 	} else {
1277 		s = cmd_find_best_session(NULL, 0, CMD_FIND_QUIET);
1278 		if (s != NULL)
1279 			c = cmd_find_best_client(s);
1280 	}
1281 	if (c == NULL && !quiet)
1282 		cmdq_error(item, "no current client");
1283 	log_debug("%s: no target, return %p", __func__, c);
1284 	return (c);
1285 }
1286 
1287 /* Find the target client or report an error and return NULL. */
1288 struct client *
1289 cmd_find_client(struct cmdq_item *item, const char *target, int quiet)
1290 {
1291 	struct client	*c;
1292 	char		*copy;
1293 	size_t		 size;
1294 
1295 	/* A NULL argument means the current client. */
1296 	if (target == NULL)
1297 		return (cmd_find_current_client(item, quiet));
1298 	copy = xstrdup(target);
1299 
1300 	/* Trim a single trailing colon if any. */
1301 	size = strlen(copy);
1302 	if (size != 0 && copy[size - 1] == ':')
1303 		copy[size - 1] = '\0';
1304 
1305 	/* Check name and path of each client. */
1306 	TAILQ_FOREACH(c, &clients, entry) {
1307 		if (c->session == NULL)
1308 			continue;
1309 		if (strcmp(copy, c->name) == 0)
1310 			break;
1311 
1312 		if (*c->ttyname == '\0')
1313 			continue;
1314 		if (strcmp(copy, c->ttyname) == 0)
1315 			break;
1316 		if (strncmp(c->ttyname, _PATH_DEV, (sizeof _PATH_DEV) - 1) != 0)
1317 			continue;
1318 		if (strcmp(copy, c->ttyname + (sizeof _PATH_DEV) - 1) == 0)
1319 			break;
1320 	}
1321 
1322 	/* If no client found, report an error. */
1323 	if (c == NULL && !quiet)
1324 		cmdq_error(item, "can't find client %s", copy);
1325 
1326 	free(copy);
1327 	log_debug("%s: target %s, return %p", __func__, target, c);
1328 	return (c);
1329 }
1330