Make the prompt history global for all clients which is much more useful than per...
[tmux-openbsd.git] / session.c
blob7d3fb32d4652e263f2ae19b8fbeadfbc47749668
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 <paths.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <time.h>
28 #include "tmux.h"
30 /* Global session list. */
31 struct sessions sessions;
32 struct sessions dead_sessions;
33 struct session_groups session_groups;
35 struct winlink *session_next_alert(struct winlink *);
36 struct winlink *session_previous_alert(struct winlink *);
38 /* Find session by name. */
39 struct session *
40 session_find(const char *name)
42 struct session *s;
43 u_int i;
45 for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
46 s = ARRAY_ITEM(&sessions, i);
47 if (s != NULL && strcmp(s->name, name) == 0)
48 return (s);
51 return (NULL);
54 /* Create a new session. */
55 struct session *
56 session_create(const char *name, const char *cmd, const char *cwd,
57 struct environ *env, struct termios *tio, int idx, u_int sx, u_int sy,
58 char **cause)
60 struct session *s;
61 u_int i;
63 s = xmalloc(sizeof *s);
64 s->references = 0;
65 s->flags = 0;
67 if (gettimeofday(&s->creation_time, NULL) != 0)
68 fatal("gettimeofday failed");
69 memcpy(&s->activity_time, &s->creation_time, sizeof s->activity_time);
71 s->cwd = xstrdup(cwd);
73 s->curw = NULL;
74 TAILQ_INIT(&s->lastw);
75 RB_INIT(&s->windows);
77 paste_init_stack(&s->buffers);
79 options_init(&s->options, &global_s_options);
80 environ_init(&s->environ);
81 if (env != NULL)
82 environ_copy(env, &s->environ);
84 s->tio = NULL;
85 if (tio != NULL) {
86 s->tio = xmalloc(sizeof *s->tio);
87 memcpy(s->tio, tio, sizeof *s->tio);
90 s->sx = sx;
91 s->sy = sy;
93 for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
94 if (ARRAY_ITEM(&sessions, i) == NULL) {
95 ARRAY_SET(&sessions, i, s);
96 break;
99 if (i == ARRAY_LENGTH(&sessions))
100 ARRAY_ADD(&sessions, s);
102 if (name != NULL)
103 s->name = xstrdup(name);
104 else
105 xasprintf(&s->name, "%u", i);
107 if (cmd != NULL) {
108 if (session_new(s, NULL, cmd, cwd, idx, cause) == NULL) {
109 session_destroy(s);
110 return (NULL);
112 session_select(s, RB_ROOT(&s->windows)->idx);
115 log_debug("session %s created", s->name);
117 return (s);
120 /* Destroy a session. */
121 void
122 session_destroy(struct session *s)
124 u_int i;
126 log_debug("session %s destroyed", s->name);
128 if (session_index(s, &i) != 0)
129 fatalx("session not found");
130 ARRAY_SET(&sessions, i, NULL);
131 while (!ARRAY_EMPTY(&sessions) && ARRAY_LAST(&sessions) == NULL)
132 ARRAY_TRUNC(&sessions, 1);
134 if (s->tio != NULL)
135 xfree(s->tio);
137 session_group_remove(s);
138 environ_free(&s->environ);
139 options_free(&s->options);
140 paste_free_stack(&s->buffers);
142 while (!TAILQ_EMPTY(&s->lastw))
143 winlink_stack_remove(&s->lastw, TAILQ_FIRST(&s->lastw));
144 while (!RB_EMPTY(&s->windows))
145 winlink_remove(&s->windows, RB_ROOT(&s->windows));
147 xfree(s->cwd);
148 xfree(s->name);
150 for (i = 0; i < ARRAY_LENGTH(&dead_sessions); i++) {
151 if (ARRAY_ITEM(&dead_sessions, i) == NULL) {
152 ARRAY_SET(&dead_sessions, i, s);
153 break;
156 if (i == ARRAY_LENGTH(&dead_sessions))
157 ARRAY_ADD(&dead_sessions, s);
158 s->flags |= SESSION_DEAD;
161 /* Find session index. */
163 session_index(struct session *s, u_int *i)
165 for (*i = 0; *i < ARRAY_LENGTH(&sessions); (*i)++) {
166 if (s == ARRAY_ITEM(&sessions, *i))
167 return (0);
169 return (-1);
172 /* Find the next usable session. */
173 struct session *
174 session_next_session(struct session *s)
176 struct session *s2;
177 u_int i;
179 if (ARRAY_LENGTH(&sessions) == 0 || session_index(s, &i) != 0)
180 return (NULL);
182 do {
183 if (i == ARRAY_LENGTH(&sessions) - 1)
184 i = 0;
185 else
186 i++;
187 s2 = ARRAY_ITEM(&sessions, i);
188 } while (s2 == NULL || s2->flags & SESSION_DEAD);
190 return (s2);
193 /* Find the previous usable session. */
194 struct session *
195 session_previous_session(struct session *s)
197 struct session *s2;
198 u_int i;
200 if (ARRAY_LENGTH(&sessions) == 0 || session_index(s, &i) != 0)
201 return (NULL);
203 do {
204 if (i == 0)
205 i = ARRAY_LENGTH(&sessions) - 1;
206 else
207 i--;
208 s2 = ARRAY_ITEM(&sessions, i);
209 } while (s2 == NULL || s2->flags & SESSION_DEAD);
211 return (s2);
214 /* Create a new window on a session. */
215 struct winlink *
216 session_new(struct session *s,
217 const char *name, const char *cmd, const char *cwd, int idx, char **cause)
219 struct window *w;
220 struct environ env;
221 const char *shell;
222 u_int hlimit;
224 environ_init(&env);
225 environ_copy(&global_environ, &env);
226 environ_copy(&s->environ, &env);
227 server_fill_environ(s, &env);
229 shell = options_get_string(&s->options, "default-shell");
230 if (*shell == '\0' || areshell(shell))
231 shell = _PATH_BSHELL;
233 hlimit = options_get_number(&s->options, "history-limit");
234 w = window_create(
235 name, cmd, shell, cwd, &env, s->tio, s->sx, s->sy, hlimit, cause);
236 if (w == NULL) {
237 environ_free(&env);
238 return (NULL);
240 environ_free(&env);
242 if (options_get_number(&s->options, "set-remain-on-exit"))
243 options_set_number(&w->options, "remain-on-exit", 1);
245 return (session_attach(s, w, idx, cause));
248 /* Attach a window to a session. */
249 struct winlink *
250 session_attach(struct session *s, struct window *w, int idx, char **cause)
252 struct winlink *wl;
254 if ((wl = winlink_add(&s->windows, w, idx)) == NULL)
255 xasprintf(cause, "index in use: %d", idx);
256 session_group_synchronize_from(s);
257 return (wl);
260 /* Detach a window from a session. */
262 session_detach(struct session *s, struct winlink *wl)
264 if (s->curw == wl &&
265 session_last(s) != 0 && session_previous(s, 0) != 0)
266 session_next(s, 0);
268 wl->flags &= ~WINLINK_ALERTFLAGS;
269 winlink_stack_remove(&s->lastw, wl);
270 winlink_remove(&s->windows, wl);
271 session_group_synchronize_from(s);
272 if (RB_EMPTY(&s->windows)) {
273 session_destroy(s);
274 return (1);
276 return (0);
279 /* Return if session has window. */
280 struct winlink *
281 session_has(struct session *s, struct window *w)
283 struct winlink *wl;
285 RB_FOREACH(wl, winlinks, &s->windows) {
286 if (wl->window == w)
287 return (wl);
289 return (NULL);
292 struct winlink *
293 session_next_alert(struct winlink *wl)
295 while (wl != NULL) {
296 if (wl->flags & WINLINK_ALERTFLAGS)
297 break;
298 wl = winlink_next(wl);
300 return (wl);
303 /* Move session to next window. */
305 session_next(struct session *s, int alert)
307 struct winlink *wl;
309 if (s->curw == NULL)
310 return (-1);
312 wl = winlink_next(s->curw);
313 if (alert)
314 wl = session_next_alert(wl);
315 if (wl == NULL) {
316 wl = RB_MIN(winlinks, &s->windows);
317 if (alert && ((wl = session_next_alert(wl)) == NULL))
318 return (-1);
320 if (wl == s->curw)
321 return (1);
322 winlink_stack_remove(&s->lastw, wl);
323 winlink_stack_push(&s->lastw, s->curw);
324 s->curw = wl;
325 wl->flags &= ~WINLINK_ALERTFLAGS;
326 return (0);
329 struct winlink *
330 session_previous_alert(struct winlink *wl)
332 while (wl != NULL) {
333 if (wl->flags & WINLINK_ALERTFLAGS)
334 break;
335 wl = winlink_previous(wl);
337 return (wl);
340 /* Move session to previous window. */
342 session_previous(struct session *s, int alert)
344 struct winlink *wl;
346 if (s->curw == NULL)
347 return (-1);
349 wl = winlink_previous(s->curw);
350 if (alert)
351 wl = session_previous_alert(wl);
352 if (wl == NULL) {
353 wl = RB_MAX(winlinks, &s->windows);
354 if (alert && (wl = session_previous_alert(wl)) == NULL)
355 return (-1);
357 if (wl == s->curw)
358 return (1);
359 winlink_stack_remove(&s->lastw, wl);
360 winlink_stack_push(&s->lastw, s->curw);
361 s->curw = wl;
362 wl->flags &= ~WINLINK_ALERTFLAGS;
363 return (0);
366 /* Move session to specific window. */
368 session_select(struct session *s, int idx)
370 struct winlink *wl;
372 wl = winlink_find_by_index(&s->windows, idx);
373 if (wl == NULL)
374 return (-1);
375 if (wl == s->curw)
376 return (1);
377 winlink_stack_remove(&s->lastw, wl);
378 winlink_stack_push(&s->lastw, s->curw);
379 s->curw = wl;
380 wl->flags &= ~WINLINK_ALERTFLAGS;
381 return (0);
384 /* Move session to last used window. */
386 session_last(struct session *s)
388 struct winlink *wl;
390 wl = TAILQ_FIRST(&s->lastw);
391 if (wl == NULL)
392 return (-1);
393 if (wl == s->curw)
394 return (1);
396 winlink_stack_remove(&s->lastw, wl);
397 winlink_stack_push(&s->lastw, s->curw);
398 s->curw = wl;
399 wl->flags &= ~WINLINK_ALERTFLAGS;
400 return (0);
403 /* Find the session group containing a session. */
404 struct session_group *
405 session_group_find(struct session *target)
407 struct session_group *sg;
408 struct session *s;
410 TAILQ_FOREACH(sg, &session_groups, entry) {
411 TAILQ_FOREACH(s, &sg->sessions, gentry) {
412 if (s == target)
413 return (sg);
416 return (NULL);
419 /* Find session group index. */
420 u_int
421 session_group_index(struct session_group *sg)
423 struct session_group *sg2;
424 u_int i;
426 i = 0;
427 TAILQ_FOREACH(sg2, &session_groups, entry) {
428 if (sg == sg2)
429 return (i);
430 i++;
433 fatalx("session group not found");
437 * Add a session to the session group containing target, creating it if
438 * necessary.
440 void
441 session_group_add(struct session *target, struct session *s)
443 struct session_group *sg;
445 if ((sg = session_group_find(target)) == NULL) {
446 sg = xmalloc(sizeof *sg);
447 TAILQ_INSERT_TAIL(&session_groups, sg, entry);
448 TAILQ_INIT(&sg->sessions);
449 TAILQ_INSERT_TAIL(&sg->sessions, target, gentry);
451 TAILQ_INSERT_TAIL(&sg->sessions, s, gentry);
454 /* Remove a session from its group and destroy the group if empty. */
455 void
456 session_group_remove(struct session *s)
458 struct session_group *sg;
460 if ((sg = session_group_find(s)) == NULL)
461 return;
462 TAILQ_REMOVE(&sg->sessions, s, gentry);
463 if (TAILQ_NEXT(TAILQ_FIRST(&sg->sessions), gentry) == NULL)
464 TAILQ_REMOVE(&sg->sessions, TAILQ_FIRST(&sg->sessions), gentry);
465 if (TAILQ_EMPTY(&sg->sessions)) {
466 TAILQ_REMOVE(&session_groups, sg, entry);
467 xfree(sg);
471 /* Synchronize a session to its session group. */
472 void
473 session_group_synchronize_to(struct session *s)
475 struct session_group *sg;
476 struct session *target;
478 if ((sg = session_group_find(s)) == NULL)
479 return;
481 target = NULL;
482 TAILQ_FOREACH(target, &sg->sessions, gentry) {
483 if (target != s)
484 break;
486 session_group_synchronize1(target, s);
489 /* Synchronize a session group to a session. */
490 void
491 session_group_synchronize_from(struct session *target)
493 struct session_group *sg;
494 struct session *s;
496 if ((sg = session_group_find(target)) == NULL)
497 return;
499 TAILQ_FOREACH(s, &sg->sessions, gentry) {
500 if (s != target)
501 session_group_synchronize1(target, s);
506 * Synchronize a session with a target session. This means destroying all
507 * winlinks then recreating them, then updating the current window, last window
508 * stack and alerts.
510 void
511 session_group_synchronize1(struct session *target, struct session *s)
513 struct winlinks old_windows, *ww;
514 struct winlink_stack old_lastw;
515 struct winlink *wl, *wl2;
517 /* Don't do anything if the session is empty (it'll be destroyed). */
518 ww = &target->windows;
519 if (RB_EMPTY(ww))
520 return;
522 /* If the current window has vanished, move to the next now. */
523 if (s->curw != NULL &&
524 winlink_find_by_index(ww, s->curw->idx) == NULL &&
525 session_last(s) != 0 && session_previous(s, 0) != 0)
526 session_next(s, 0);
528 /* Save the old pointer and reset it. */
529 memcpy(&old_windows, &s->windows, sizeof old_windows);
530 RB_INIT(&s->windows);
532 /* Link all the windows from the target. */
533 RB_FOREACH(wl, winlinks, ww) {
534 wl2 = winlink_add(&s->windows, wl->window, wl->idx);
535 wl2->flags |= wl->flags & WINLINK_ALERTFLAGS;
538 /* Fix up the current window. */
539 if (s->curw != NULL)
540 s->curw = winlink_find_by_index(&s->windows, s->curw->idx);
541 else
542 s->curw = winlink_find_by_index(&s->windows, target->curw->idx);
544 /* Fix up the last window stack. */
545 memcpy(&old_lastw, &s->lastw, sizeof old_lastw);
546 TAILQ_INIT(&s->lastw);
547 TAILQ_FOREACH(wl, &old_lastw, sentry) {
548 wl2 = winlink_find_by_index(&s->windows, wl->idx);
549 if (wl2 != NULL)
550 TAILQ_INSERT_TAIL(&s->lastw, wl2, sentry);
553 /* Then free the old winlinks list. */
554 while (!RB_EMPTY(&old_windows)) {
555 wl = RB_ROOT(&old_windows);
556 winlink_remove(&old_windows, wl);