Add window-status-separator option, from Thomas Adam.
[tmux-openbsd.git] / status.c
blob33ac81ff8f10bcd3b9cadc93715ae3e405abcfda
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 <errno.h>
23 #include <limits.h>
24 #include <stdarg.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <time.h>
28 #include <unistd.h>
30 #include "tmux.h"
32 char *status_redraw_get_left(
33 struct client *, time_t, int, struct grid_cell *, size_t *);
34 char *status_redraw_get_right(
35 struct client *, time_t, int, struct grid_cell *, size_t *);
36 char *status_find_job(struct client *, char **);
37 void status_job_free(void *);
38 void status_job_callback(struct job *);
39 char *status_print(
40 struct client *, struct winlink *, time_t, struct grid_cell *);
41 void status_replace1(struct client *, struct session *, struct winlink *,
42 struct window_pane *, char **, char **, char *, size_t, int);
43 void status_message_callback(int, short, void *);
45 const char *status_prompt_up_history(u_int *);
46 const char *status_prompt_down_history(u_int *);
47 void status_prompt_add_history(const char *);
48 char *status_prompt_complete(const char *);
50 /* Status prompt history. */
51 ARRAY_DECL(, char *) status_prompt_history = ARRAY_INITIALIZER;
53 /* Status output tree. */
54 RB_GENERATE(status_out_tree, status_out, entry, status_out_cmp);
56 /* Output tree comparison function. */
57 int
58 status_out_cmp(struct status_out *so1, struct status_out *so2)
60 return (strcmp(so1->cmd, so2->cmd));
63 /* Get screen line of status line. -1 means off. */
64 int
65 status_at_line(struct client *c)
67 struct session *s = c->session;
69 if (!options_get_number(&s->options, "status"))
70 return (-1);
72 if (options_get_number(&s->options, "status-position") == 0)
73 return (0);
74 return (c->tty.sy - 1);
77 /* Retrieve options for left string. */
78 char *
79 status_redraw_get_left(struct client *c,
80 time_t t, int utf8flag, struct grid_cell *gc, size_t *size)
82 struct session *s = c->session;
83 char *left;
84 u_char fg, bg, attr;
85 size_t leftlen;
87 fg = options_get_number(&s->options, "status-left-fg");
88 if (fg != 8)
89 colour_set_fg(gc, fg);
90 bg = options_get_number(&s->options, "status-left-bg");
91 if (bg != 8)
92 colour_set_bg(gc, bg);
93 attr = options_get_number(&s->options, "status-left-attr");
94 if (attr != 0)
95 gc->attr = attr;
97 left = status_replace(c, NULL,
98 NULL, NULL, options_get_string(&s->options, "status-left"), t, 1);
100 *size = options_get_number(&s->options, "status-left-length");
101 leftlen = screen_write_cstrlen(utf8flag, "%s", left);
102 if (leftlen < *size)
103 *size = leftlen;
104 return (left);
107 /* Retrieve options for right string. */
108 char *
109 status_redraw_get_right(struct client *c,
110 time_t t, int utf8flag, struct grid_cell *gc, size_t *size)
112 struct session *s = c->session;
113 char *right;
114 u_char fg, bg, attr;
115 size_t rightlen;
117 fg = options_get_number(&s->options, "status-right-fg");
118 if (fg != 8)
119 colour_set_fg(gc, fg);
120 bg = options_get_number(&s->options, "status-right-bg");
121 if (bg != 8)
122 colour_set_bg(gc, bg);
123 attr = options_get_number(&s->options, "status-right-attr");
124 if (attr != 0)
125 gc->attr = attr;
127 right = status_replace(c, NULL,
128 NULL, NULL, options_get_string(&s->options, "status-right"), t, 1);
130 *size = options_get_number(&s->options, "status-right-length");
131 rightlen = screen_write_cstrlen(utf8flag, "%s", right);
132 if (rightlen < *size)
133 *size = rightlen;
134 return (right);
137 /* Set window at window list position. */
138 void
139 status_set_window_at(struct client *c, u_int x)
141 struct session *s = c->session;
142 struct winlink *wl;
144 x += c->wlmouse;
145 RB_FOREACH(wl, winlinks, &s->windows) {
146 if (x < wl->status_width &&
147 session_select(s, wl->idx) == 0) {
148 server_redraw_session(s);
150 x -= wl->status_width + 1;
154 /* Draw status for client on the last lines of given context. */
156 status_redraw(struct client *c)
158 struct screen_write_ctx ctx;
159 struct session *s = c->session;
160 struct winlink *wl;
161 struct screen old_status, window_list;
162 struct grid_cell stdgc, lgc, rgc, gc;
163 struct options *oo;
164 time_t t;
165 char *left, *right, *sep;
166 u_int offset, needed;
167 u_int wlstart, wlwidth, wlavailable, wloffset, wlsize;
168 size_t llen, rlen, seplen;
169 int larrow, rarrow, utf8flag;
171 /* No status line? */
172 if (c->tty.sy == 0 || !options_get_number(&s->options, "status"))
173 return (1);
174 left = right = NULL;
175 larrow = rarrow = 0;
177 /* Update status timer. */
178 if (gettimeofday(&c->status_timer, NULL) != 0)
179 fatal("gettimeofday failed");
180 t = c->status_timer.tv_sec;
182 /* Set up default colour. */
183 memcpy(&stdgc, &grid_default_cell, sizeof gc);
184 colour_set_fg(&stdgc, options_get_number(&s->options, "status-fg"));
185 colour_set_bg(&stdgc, options_get_number(&s->options, "status-bg"));
186 stdgc.attr |= options_get_number(&s->options, "status-attr");
188 /* Create the target screen. */
189 memcpy(&old_status, &c->status, sizeof old_status);
190 screen_init(&c->status, c->tty.sx, 1, 0);
191 screen_write_start(&ctx, NULL, &c->status);
192 for (offset = 0; offset < c->tty.sx; offset++)
193 screen_write_putc(&ctx, &stdgc, ' ');
194 screen_write_stop(&ctx);
196 /* If the height is one line, blank status line. */
197 if (c->tty.sy <= 1)
198 goto out;
200 /* Get UTF-8 flag. */
201 utf8flag = options_get_number(&s->options, "status-utf8");
203 /* Work out left and right strings. */
204 memcpy(&lgc, &stdgc, sizeof lgc);
205 left = status_redraw_get_left(c, t, utf8flag, &lgc, &llen);
206 memcpy(&rgc, &stdgc, sizeof rgc);
207 right = status_redraw_get_right(c, t, utf8flag, &rgc, &rlen);
210 * Figure out how much space we have for the window list. If there
211 * isn't enough space, just show a blank status line.
213 needed = 0;
214 if (llen != 0)
215 needed += llen + 1;
216 if (rlen != 0)
217 needed += rlen + 1;
218 if (c->tty.sx == 0 || c->tty.sx <= needed)
219 goto out;
220 wlavailable = c->tty.sx - needed;
222 /* Calculate the total size needed for the window list. */
223 wlstart = wloffset = wlwidth = 0;
224 RB_FOREACH(wl, winlinks, &s->windows) {
225 if (wl->status_text != NULL)
226 xfree(wl->status_text);
227 memcpy(&wl->status_cell, &stdgc, sizeof wl->status_cell);
228 wl->status_text = status_print(c, wl, t, &wl->status_cell);
229 wl->status_width =
230 screen_write_cstrlen(utf8flag, "%s", wl->status_text);
232 if (wl == s->curw)
233 wloffset = wlwidth;
235 oo = &wl->window->options;
236 sep = options_get_string(oo, "window-status-separator");
237 seplen = screen_write_strlen(utf8flag, "%s", sep);
238 wlwidth += wl->status_width + seplen;
241 /* Create a new screen for the window list. */
242 screen_init(&window_list, wlwidth, 1, 0);
244 /* And draw the window list into it. */
245 screen_write_start(&ctx, NULL, &window_list);
246 RB_FOREACH(wl, winlinks, &s->windows) {
247 screen_write_cnputs(&ctx,
248 -1, &wl->status_cell, utf8flag, "%s", wl->status_text);
250 oo = &wl->window->options;
251 sep = options_get_string(oo, "window-status-separator");
252 screen_write_nputs(&ctx, -1, &stdgc, utf8flag, "%s", sep);
254 screen_write_stop(&ctx);
256 /* If there is enough space for the total width, skip to draw now. */
257 if (wlwidth <= wlavailable)
258 goto draw;
260 /* Find size of current window text. */
261 wlsize = s->curw->status_width;
264 * If the current window is already on screen, good to draw from the
265 * start and just leave off the end.
267 if (wloffset + wlsize < wlavailable) {
268 if (wlavailable > 0) {
269 rarrow = 1;
270 wlavailable--;
272 wlwidth = wlavailable;
273 } else {
275 * Work out how many characters we need to omit from the
276 * start. There are wlavailable characters to fill, and
277 * wloffset + wlsize must be the last. So, the start character
278 * is wloffset + wlsize - wlavailable.
280 if (wlavailable > 0) {
281 larrow = 1;
282 wlavailable--;
285 wlstart = wloffset + wlsize - wlavailable;
286 if (wlavailable > 0 && wlwidth > wlstart + wlavailable + 1) {
287 rarrow = 1;
288 wlstart++;
289 wlavailable--;
291 wlwidth = wlavailable;
294 /* Bail if anything is now too small too. */
295 if (wlwidth == 0 || wlavailable == 0) {
296 screen_free(&window_list);
297 goto out;
301 * Now the start position is known, work out the state of the left and
302 * right arrows.
304 offset = 0;
305 RB_FOREACH(wl, winlinks, &s->windows) {
306 if (wl->flags & WINLINK_ALERTFLAGS &&
307 larrow == 1 && offset < wlstart)
308 larrow = -1;
310 offset += wl->status_width;
312 if (wl->flags & WINLINK_ALERTFLAGS &&
313 rarrow == 1 && offset > wlstart + wlwidth)
314 rarrow = -1;
317 draw:
318 /* Begin drawing. */
319 screen_write_start(&ctx, NULL, &c->status);
321 /* Draw the left string and arrow. */
322 screen_write_cursormove(&ctx, 0, 0);
323 if (llen != 0) {
324 screen_write_cnputs(&ctx, llen, &lgc, utf8flag, "%s", left);
325 screen_write_putc(&ctx, &stdgc, ' ');
327 if (larrow != 0) {
328 memcpy(&gc, &stdgc, sizeof gc);
329 if (larrow == -1)
330 gc.attr ^= GRID_ATTR_REVERSE;
331 screen_write_putc(&ctx, &gc, '<');
334 /* Draw the right string and arrow. */
335 if (rarrow != 0) {
336 screen_write_cursormove(&ctx, c->tty.sx - rlen - 2, 0);
337 memcpy(&gc, &stdgc, sizeof gc);
338 if (rarrow == -1)
339 gc.attr ^= GRID_ATTR_REVERSE;
340 screen_write_putc(&ctx, &gc, '>');
341 } else
342 screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, 0);
343 if (rlen != 0) {
344 screen_write_putc(&ctx, &stdgc, ' ');
345 screen_write_cnputs(&ctx, rlen, &rgc, utf8flag, "%s", right);
348 /* Figure out the offset for the window list. */
349 if (llen != 0)
350 wloffset = llen + 1;
351 else
352 wloffset = 0;
353 if (wlwidth < wlavailable) {
354 switch (options_get_number(&s->options, "status-justify")) {
355 case 1: /* centered */
356 wloffset += (wlavailable - wlwidth) / 2;
357 break;
358 case 2: /* right */
359 wloffset += (wlavailable - wlwidth);
360 break;
363 if (larrow != 0)
364 wloffset++;
366 /* Copy the window list. */
367 c->wlmouse = -wloffset + wlstart;
368 screen_write_cursormove(&ctx, wloffset, 0);
369 screen_write_copy(&ctx, &window_list, wlstart, 0, wlwidth, 1);
370 screen_free(&window_list);
372 screen_write_stop(&ctx);
374 out:
375 if (left != NULL)
376 xfree(left);
377 if (right != NULL)
378 xfree(right);
380 if (grid_compare(c->status.grid, old_status.grid) == 0) {
381 screen_free(&old_status);
382 return (0);
384 screen_free(&old_status);
385 return (1);
388 /* Replace a single special sequence (prefixed by #). */
389 void
390 status_replace1(struct client *c, struct session *s, struct winlink *wl,
391 struct window_pane *wp, char **iptr, char **optr, char *out,
392 size_t outsize, int jobsflag)
394 char ch, tmp[256], *ptr, *endptr, *freeptr;
395 size_t ptrlen;
396 long limit;
397 u_int idx;
399 if (s == NULL)
400 s = c->session;
401 if (wl == NULL)
402 wl = s->curw;
403 if (wp == NULL)
404 wp = wl->window->active;
406 errno = 0;
407 limit = strtol(*iptr, &endptr, 10);
408 if ((limit == 0 && errno != EINVAL) ||
409 (limit == LONG_MIN && errno != ERANGE) ||
410 (limit == LONG_MAX && errno != ERANGE) ||
411 limit != 0)
412 *iptr = endptr;
413 if (limit <= 0)
414 limit = LONG_MAX;
416 freeptr = NULL;
418 switch (*(*iptr)++) {
419 case '(':
420 if (!jobsflag) {
421 ch = ')';
422 goto skip_to;
424 if ((ptr = status_find_job(c, iptr)) == NULL)
425 return;
426 goto do_replace;
427 case 'D':
428 xsnprintf(tmp, sizeof tmp, "%%%u", wp->id);
429 ptr = tmp;
430 goto do_replace;
431 case 'H':
432 if (gethostname(tmp, sizeof tmp) != 0)
433 fatal("gethostname failed");
434 ptr = tmp;
435 goto do_replace;
436 case 'h':
437 if (gethostname(tmp, sizeof tmp) != 0)
438 fatal("gethostname failed");
439 if ((ptr = strchr(tmp, '.')) != NULL)
440 *ptr = '\0';
441 ptr = tmp;
442 goto do_replace;
443 case 'I':
444 xsnprintf(tmp, sizeof tmp, "%d", wl->idx);
445 ptr = tmp;
446 goto do_replace;
447 case 'P':
448 if (window_pane_index(wp, &idx) != 0)
449 fatalx("index not found");
450 xsnprintf(
451 tmp, sizeof tmp, "%u", idx);
452 ptr = tmp;
453 goto do_replace;
454 case 'S':
455 ptr = s->name;
456 goto do_replace;
457 case 'T':
458 ptr = wp->base.title;
459 goto do_replace;
460 case 'W':
461 ptr = wl->window->name;
462 goto do_replace;
463 case 'F':
464 ptr = window_printable_flags(s, wl);
465 freeptr = ptr;
466 goto do_replace;
467 case '[':
469 * Embedded style, handled at display time. Leave present and
470 * skip input until ].
472 ch = ']';
473 goto skip_to;
474 case '#':
475 *(*optr)++ = '#';
476 break;
479 return;
481 do_replace:
482 ptrlen = strlen(ptr);
483 if ((size_t) limit < ptrlen)
484 ptrlen = limit;
486 if (*optr + ptrlen >= out + outsize - 1)
487 goto out;
488 while (ptrlen > 0 && *ptr != '\0') {
489 *(*optr)++ = *ptr++;
490 ptrlen--;
493 out:
494 if (freeptr != NULL)
495 xfree(freeptr);
496 return;
498 skip_to:
499 *(*optr)++ = '#';
501 (*iptr)--; /* include ch */
502 while (**iptr != ch && **iptr != '\0') {
503 if (*optr >= out + outsize - 1)
504 break;
505 *(*optr)++ = *(*iptr)++;
509 /* Replace special sequences in fmt. */
510 char *
511 status_replace(struct client *c, struct session *s, struct winlink *wl,
512 struct window_pane *wp, const char *fmt, time_t t, int jobsflag)
514 static char out[BUFSIZ];
515 char in[BUFSIZ], ch, *iptr, *optr;
516 size_t len;
518 len = strftime(in, sizeof in, fmt, localtime(&t));
519 in[len] = '\0';
521 iptr = in;
522 optr = out;
524 while (*iptr != '\0') {
525 if (optr >= out + (sizeof out) - 1)
526 break;
527 ch = *iptr++;
529 if (ch != '#' || *iptr == '\0') {
530 *optr++ = ch;
531 continue;
533 status_replace1(
534 c, s, wl, wp, &iptr, &optr, out, sizeof out, jobsflag);
536 *optr = '\0';
538 return (xstrdup(out));
541 /* Figure out job name and get its result, starting it off if necessary. */
542 char *
543 status_find_job(struct client *c, char **iptr)
545 struct status_out *so, so_find;
546 char *cmd;
547 int lastesc;
548 size_t len;
550 if (**iptr == '\0')
551 return (NULL);
552 if (**iptr == ')') { /* no command given */
553 (*iptr)++;
554 return (NULL);
557 cmd = xmalloc(strlen(*iptr) + 1);
558 len = 0;
560 lastesc = 0;
561 for (; **iptr != '\0'; (*iptr)++) {
562 if (!lastesc && **iptr == ')')
563 break; /* unescaped ) is the end */
564 if (!lastesc && **iptr == '\\') {
565 lastesc = 1;
566 continue; /* skip \ if not escaped */
568 lastesc = 0;
569 cmd[len++] = **iptr;
571 if (**iptr == '\0') /* no terminating ) */ {
572 xfree(cmd);
573 return (NULL);
575 (*iptr)++; /* skip final ) */
576 cmd[len] = '\0';
578 /* First try in the new tree. */
579 so_find.cmd = cmd;
580 so = RB_FIND(status_out_tree, &c->status_new, &so_find);
581 if (so != NULL && so->out != NULL) {
582 xfree(cmd);
583 return (so->out);
586 /* If not found at all, start the job and add to the tree. */
587 if (so == NULL) {
588 job_run(cmd, status_job_callback, status_job_free, c);
589 c->references++;
591 so = xmalloc(sizeof *so);
592 so->cmd = xstrdup(cmd);
593 so->out = NULL;
594 RB_INSERT(status_out_tree, &c->status_new, so);
597 /* Lookup in the old tree. */
598 so_find.cmd = cmd;
599 so = RB_FIND(status_out_tree, &c->status_old, &so_find);
600 xfree(cmd);
601 if (so != NULL)
602 return (so->out);
603 return (NULL);
606 /* Free job tree. */
607 void
608 status_free_jobs(struct status_out_tree *sotree)
610 struct status_out *so, *so_next;
612 so_next = RB_MIN(status_out_tree, sotree);
613 while (so_next != NULL) {
614 so = so_next;
615 so_next = RB_NEXT(status_out_tree, sotree, so);
617 RB_REMOVE(status_out_tree, sotree, so);
618 if (so->out != NULL)
619 xfree(so->out);
620 xfree(so->cmd);
621 xfree(so);
625 /* Update jobs on status interval. */
626 void
627 status_update_jobs(struct client *c)
629 /* Free the old tree. */
630 status_free_jobs(&c->status_old);
632 /* Move the new to old. */
633 memcpy(&c->status_old, &c->status_new, sizeof c->status_old);
634 RB_INIT(&c->status_new);
637 /* Free status job. */
638 void
639 status_job_free(void *data)
641 struct client *c = data;
643 c->references--;
646 /* Job has finished: save its result. */
647 void
648 status_job_callback(struct job *job)
650 struct client *c = job->data;
651 struct status_out *so, so_find;
652 char *line, *buf;
653 size_t len;
655 if (c->flags & CLIENT_DEAD)
656 return;
658 so_find.cmd = job->cmd;
659 so = RB_FIND(status_out_tree, &c->status_new, &so_find);
660 if (so == NULL || so->out != NULL)
661 return;
663 buf = NULL;
664 if ((line = evbuffer_readline(job->event->input)) == NULL) {
665 len = EVBUFFER_LENGTH(job->event->input);
666 buf = xmalloc(len + 1);
667 if (len != 0)
668 memcpy(buf, EVBUFFER_DATA(job->event->input), len);
669 buf[len] = '\0';
670 } else
671 buf = xstrdup(line);
673 so->out = buf;
674 server_status_client(c);
677 /* Return winlink status line entry and adjust gc as necessary. */
678 char *
679 status_print(
680 struct client *c, struct winlink *wl, time_t t, struct grid_cell *gc)
682 struct options *oo = &wl->window->options;
683 struct session *s = c->session;
684 const char *fmt;
685 char *text;
686 u_char fg, bg, attr;
688 fg = options_get_number(oo, "window-status-fg");
689 if (fg != 8)
690 colour_set_fg(gc, fg);
691 bg = options_get_number(oo, "window-status-bg");
692 if (bg != 8)
693 colour_set_bg(gc, bg);
694 attr = options_get_number(oo, "window-status-attr");
695 if (attr != 0)
696 gc->attr = attr;
697 fmt = options_get_string(oo, "window-status-format");
698 if (wl == s->curw) {
699 fg = options_get_number(oo, "window-status-current-fg");
700 if (fg != 8)
701 colour_set_fg(gc, fg);
702 bg = options_get_number(oo, "window-status-current-bg");
703 if (bg != 8)
704 colour_set_bg(gc, bg);
705 attr = options_get_number(oo, "window-status-current-attr");
706 if (attr != 0)
707 gc->attr = attr;
708 fmt = options_get_string(oo, "window-status-current-format");
711 if (wl->flags & WINLINK_BELL) {
712 fg = options_get_number(oo, "window-status-bell-fg");
713 if (fg != 8)
714 colour_set_fg(gc, fg);
715 bg = options_get_number(oo, "window-status-bell-bg");
716 if (bg != 8)
717 colour_set_bg(gc, bg);
718 attr = options_get_number(oo, "window-status-bell-attr");
719 if (attr != 0)
720 gc->attr = attr;
721 } else if (wl->flags & WINLINK_CONTENT) {
722 fg = options_get_number(oo, "window-status-content-fg");
723 if (fg != 8)
724 colour_set_fg(gc, fg);
725 bg = options_get_number(oo, "window-status-content-bg");
726 if (bg != 8)
727 colour_set_bg(gc, bg);
728 attr = options_get_number(oo, "window-status-content-attr");
729 if (attr != 0)
730 gc->attr = attr;
731 } else if (wl->flags & (WINLINK_ACTIVITY|WINLINK_SILENCE)) {
732 fg = options_get_number(oo, "window-status-activity-fg");
733 if (fg != 8)
734 colour_set_fg(gc, fg);
735 bg = options_get_number(oo, "window-status-activity-bg");
736 if (bg != 8)
737 colour_set_bg(gc, bg);
738 attr = options_get_number(oo, "window-status-activity-attr");
739 if (attr != 0)
740 gc->attr = attr;
743 text = status_replace(c, NULL, wl, NULL, fmt, t, 1);
744 return (text);
747 /* Set a status line message. */
748 void printflike2
749 status_message_set(struct client *c, const char *fmt, ...)
751 struct timeval tv;
752 struct session *s = c->session;
753 struct message_entry *msg;
754 va_list ap;
755 int delay;
756 u_int i, limit;
758 status_prompt_clear(c);
759 status_message_clear(c);
761 va_start(ap, fmt);
762 xvasprintf(&c->message_string, fmt, ap);
763 va_end(ap);
765 ARRAY_EXPAND(&c->message_log, 1);
766 msg = &ARRAY_LAST(&c->message_log);
767 msg->msg_time = time(NULL);
768 msg->msg = xstrdup(c->message_string);
770 if (s == NULL)
771 limit = 0;
772 else
773 limit = options_get_number(&s->options, "message-limit");
774 if (ARRAY_LENGTH(&c->message_log) > limit) {
775 limit = ARRAY_LENGTH(&c->message_log) - limit;
776 for (i = 0; i < limit; i++) {
777 msg = &ARRAY_FIRST(&c->message_log);
778 xfree(msg->msg);
779 ARRAY_REMOVE(&c->message_log, 0);
783 delay = options_get_number(&c->session->options, "display-time");
784 tv.tv_sec = delay / 1000;
785 tv.tv_usec = (delay % 1000) * 1000L;
787 if (event_initialized (&c->message_timer))
788 evtimer_del(&c->message_timer);
789 evtimer_set(&c->message_timer, status_message_callback, c);
790 evtimer_add(&c->message_timer, &tv);
792 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
793 c->flags |= CLIENT_STATUS;
796 /* Clear status line message. */
797 void
798 status_message_clear(struct client *c)
800 if (c->message_string == NULL)
801 return;
803 xfree(c->message_string);
804 c->message_string = NULL;
806 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
807 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
809 screen_reinit(&c->status);
812 /* Clear status line message after timer expires. */
813 /* ARGSUSED */
814 void
815 status_message_callback(unused int fd, unused short event, void *data)
817 struct client *c = data;
819 status_message_clear(c);
822 /* Draw client message on status line of present else on last line. */
824 status_message_redraw(struct client *c)
826 struct screen_write_ctx ctx;
827 struct session *s = c->session;
828 struct screen old_status;
829 size_t len;
830 struct grid_cell gc;
831 int utf8flag;
833 if (c->tty.sx == 0 || c->tty.sy == 0)
834 return (0);
835 memcpy(&old_status, &c->status, sizeof old_status);
836 screen_init(&c->status, c->tty.sx, 1, 0);
838 utf8flag = options_get_number(&s->options, "status-utf8");
840 len = screen_write_strlen(utf8flag, "%s", c->message_string);
841 if (len > c->tty.sx)
842 len = c->tty.sx;
844 memcpy(&gc, &grid_default_cell, sizeof gc);
845 colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
846 colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
847 gc.attr |= options_get_number(&s->options, "message-attr");
849 screen_write_start(&ctx, NULL, &c->status);
851 screen_write_cursormove(&ctx, 0, 0);
852 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->message_string);
853 for (; len < c->tty.sx; len++)
854 screen_write_putc(&ctx, &gc, ' ');
856 screen_write_stop(&ctx);
858 if (grid_compare(c->status.grid, old_status.grid) == 0) {
859 screen_free(&old_status);
860 return (0);
862 screen_free(&old_status);
863 return (1);
866 /* Enable status line prompt. */
867 void
868 status_prompt_set(struct client *c, const char *msg, const char *input,
869 int (*callbackfn)(void *, const char *), void (*freefn)(void *),
870 void *data, int flags)
872 int keys;
874 status_message_clear(c);
875 status_prompt_clear(c);
877 c->prompt_string = status_replace(c, NULL, NULL, NULL, msg,
878 time(NULL), 0);
880 if (input == NULL)
881 input = "";
882 c->prompt_buffer = status_replace(c, NULL, NULL, NULL, input,
883 time(NULL), 0);
884 c->prompt_index = strlen(c->prompt_buffer);
886 c->prompt_callbackfn = callbackfn;
887 c->prompt_freefn = freefn;
888 c->prompt_data = data;
890 c->prompt_hindex = 0;
892 c->prompt_flags = flags;
894 keys = options_get_number(&c->session->options, "status-keys");
895 if (keys == MODEKEY_EMACS)
896 mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit);
897 else
898 mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit);
900 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
901 c->flags |= CLIENT_STATUS;
904 /* Remove status line prompt. */
905 void
906 status_prompt_clear(struct client *c)
908 if (c->prompt_string == NULL)
909 return;
911 if (c->prompt_freefn != NULL && c->prompt_data != NULL)
912 c->prompt_freefn(c->prompt_data);
914 xfree(c->prompt_string);
915 c->prompt_string = NULL;
917 xfree(c->prompt_buffer);
918 c->prompt_buffer = NULL;
920 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
921 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
923 screen_reinit(&c->status);
926 /* Update status line prompt with a new prompt string. */
927 void
928 status_prompt_update(struct client *c, const char *msg, const char *input)
930 xfree(c->prompt_string);
931 c->prompt_string = status_replace(c, NULL, NULL, NULL, msg,
932 time(NULL), 0);
934 xfree(c->prompt_buffer);
935 if (input == NULL)
936 input = "";
937 c->prompt_buffer = status_replace(c, NULL, NULL, NULL, input,
938 time(NULL), 0);
939 c->prompt_index = strlen(c->prompt_buffer);
941 c->prompt_hindex = 0;
943 c->flags |= CLIENT_STATUS;
946 /* Draw client prompt on status line of present else on last line. */
948 status_prompt_redraw(struct client *c)
950 struct screen_write_ctx ctx;
951 struct session *s = c->session;
952 struct screen old_status;
953 size_t i, size, left, len, off;
954 struct grid_cell gc, *gcp;
955 int utf8flag;
957 if (c->tty.sx == 0 || c->tty.sy == 0)
958 return (0);
959 memcpy(&old_status, &c->status, sizeof old_status);
960 screen_init(&c->status, c->tty.sx, 1, 0);
962 utf8flag = options_get_number(&s->options, "status-utf8");
964 len = screen_write_strlen(utf8flag, "%s", c->prompt_string);
965 if (len > c->tty.sx)
966 len = c->tty.sx;
967 off = 0;
969 memcpy(&gc, &grid_default_cell, sizeof gc);
970 /* Change colours for command mode. */
971 if (c->prompt_mdata.mode == 1) {
972 colour_set_fg(&gc, options_get_number(&s->options, "message-command-fg"));
973 colour_set_bg(&gc, options_get_number(&s->options, "message-command-bg"));
974 gc.attr |= options_get_number(&s->options, "message-command-attr");
975 } else {
976 colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
977 colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
978 gc.attr |= options_get_number(&s->options, "message-attr");
981 screen_write_start(&ctx, NULL, &c->status);
983 screen_write_cursormove(&ctx, 0, 0);
984 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->prompt_string);
986 left = c->tty.sx - len;
987 if (left != 0) {
988 size = screen_write_strlen(utf8flag, "%s", c->prompt_buffer);
989 if (c->prompt_index >= left) {
990 off = c->prompt_index - left + 1;
991 if (c->prompt_index == size)
992 left--;
993 size = left;
995 screen_write_nputs(
996 &ctx, left, &gc, utf8flag, "%s", c->prompt_buffer + off);
998 for (i = len + size; i < c->tty.sx; i++)
999 screen_write_putc(&ctx, &gc, ' ');
1002 screen_write_stop(&ctx);
1004 /* Apply fake cursor. */
1005 off = len + c->prompt_index - off;
1006 gcp = grid_view_get_cell(c->status.grid, off, 0);
1007 gcp->attr ^= GRID_ATTR_REVERSE;
1009 if (grid_compare(c->status.grid, old_status.grid) == 0) {
1010 screen_free(&old_status);
1011 return (0);
1013 screen_free(&old_status);
1014 return (1);
1017 /* Handle keys in prompt. */
1018 void
1019 status_prompt_key(struct client *c, int key)
1021 struct session *sess = c->session;
1022 struct options *oo = &sess->options;
1023 struct paste_buffer *pb;
1024 char *s, *first, *last, word[64], swapc;
1025 const char *histstr;
1026 const char *wsep = NULL;
1027 u_char ch;
1028 size_t size, n, off, idx;
1030 size = strlen(c->prompt_buffer);
1031 switch (mode_key_lookup(&c->prompt_mdata, key)) {
1032 case MODEKEYEDIT_CURSORLEFT:
1033 if (c->prompt_index > 0) {
1034 c->prompt_index--;
1035 c->flags |= CLIENT_STATUS;
1037 break;
1038 case MODEKEYEDIT_SWITCHMODE:
1039 c->flags |= CLIENT_STATUS;
1040 break;
1041 case MODEKEYEDIT_SWITCHMODEAPPEND:
1042 c->flags |= CLIENT_STATUS;
1043 /* FALLTHROUGH */
1044 case MODEKEYEDIT_CURSORRIGHT:
1045 if (c->prompt_index < size) {
1046 c->prompt_index++;
1047 c->flags |= CLIENT_STATUS;
1049 break;
1050 case MODEKEYEDIT_SWITCHMODEBEGINLINE:
1051 c->flags |= CLIENT_STATUS;
1052 /* FALLTHROUGH */
1053 case MODEKEYEDIT_STARTOFLINE:
1054 if (c->prompt_index != 0) {
1055 c->prompt_index = 0;
1056 c->flags |= CLIENT_STATUS;
1058 break;
1059 case MODEKEYEDIT_SWITCHMODEAPPENDLINE:
1060 c->flags |= CLIENT_STATUS;
1061 /* FALLTHROUGH */
1062 case MODEKEYEDIT_ENDOFLINE:
1063 if (c->prompt_index != size) {
1064 c->prompt_index = size;
1065 c->flags |= CLIENT_STATUS;
1067 break;
1068 case MODEKEYEDIT_COMPLETE:
1069 if (*c->prompt_buffer == '\0')
1070 break;
1072 idx = c->prompt_index;
1073 if (idx != 0)
1074 idx--;
1076 /* Find the word we are in. */
1077 first = c->prompt_buffer + idx;
1078 while (first > c->prompt_buffer && *first != ' ')
1079 first--;
1080 while (*first == ' ')
1081 first++;
1082 last = c->prompt_buffer + idx;
1083 while (*last != '\0' && *last != ' ')
1084 last++;
1085 while (*last == ' ')
1086 last--;
1087 if (*last != '\0')
1088 last++;
1089 if (last <= first ||
1090 ((size_t) (last - first)) > (sizeof word) - 1)
1091 break;
1092 memcpy(word, first, last - first);
1093 word[last - first] = '\0';
1095 /* And try to complete it. */
1096 if ((s = status_prompt_complete(word)) == NULL)
1097 break;
1099 /* Trim out word. */
1100 n = size - (last - c->prompt_buffer) + 1; /* with \0 */
1101 memmove(first, last, n);
1102 size -= last - first;
1104 /* Insert the new word. */
1105 size += strlen(s);
1106 off = first - c->prompt_buffer;
1107 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1);
1108 first = c->prompt_buffer + off;
1109 memmove(first + strlen(s), first, n);
1110 memcpy(first, s, strlen(s));
1112 c->prompt_index = (first - c->prompt_buffer) + strlen(s);
1113 xfree(s);
1115 c->flags |= CLIENT_STATUS;
1116 break;
1117 case MODEKEYEDIT_BACKSPACE:
1118 if (c->prompt_index != 0) {
1119 if (c->prompt_index == size)
1120 c->prompt_buffer[--c->prompt_index] = '\0';
1121 else {
1122 memmove(c->prompt_buffer + c->prompt_index - 1,
1123 c->prompt_buffer + c->prompt_index,
1124 size + 1 - c->prompt_index);
1125 c->prompt_index--;
1127 c->flags |= CLIENT_STATUS;
1129 break;
1130 case MODEKEYEDIT_DELETE:
1131 if (c->prompt_index != size) {
1132 memmove(c->prompt_buffer + c->prompt_index,
1133 c->prompt_buffer + c->prompt_index + 1,
1134 size + 1 - c->prompt_index);
1135 c->flags |= CLIENT_STATUS;
1137 break;
1138 case MODEKEYEDIT_DELETELINE:
1139 *c->prompt_buffer = '\0';
1140 c->prompt_index = 0;
1141 c->flags |= CLIENT_STATUS;
1142 break;
1143 case MODEKEYEDIT_DELETETOENDOFLINE:
1144 if (c->prompt_index < size) {
1145 c->prompt_buffer[c->prompt_index] = '\0';
1146 c->flags |= CLIENT_STATUS;
1148 break;
1149 case MODEKEYEDIT_DELETEWORD:
1150 wsep = options_get_string(oo, "word-separators");
1151 idx = c->prompt_index;
1153 /* Find a non-separator. */
1154 while (idx != 0) {
1155 idx--;
1156 if (!strchr(wsep, c->prompt_buffer[idx]))
1157 break;
1160 /* Find the separator at the beginning of the word. */
1161 while (idx != 0) {
1162 idx--;
1163 if (strchr(wsep, c->prompt_buffer[idx])) {
1164 /* Go back to the word. */
1165 idx++;
1166 break;
1170 memmove(c->prompt_buffer + idx,
1171 c->prompt_buffer + c->prompt_index,
1172 size + 1 - c->prompt_index);
1173 memset(c->prompt_buffer + size - (c->prompt_index - idx),
1174 '\0', c->prompt_index - idx);
1175 c->prompt_index = idx;
1176 c->flags |= CLIENT_STATUS;
1177 break;
1178 case MODEKEYEDIT_NEXTSPACE:
1179 wsep = " ";
1180 /* FALLTHROUGH */
1181 case MODEKEYEDIT_NEXTWORD:
1182 if (wsep == NULL)
1183 wsep = options_get_string(oo, "word-separators");
1185 /* Find a separator. */
1186 while (c->prompt_index != size) {
1187 c->prompt_index++;
1188 if (strchr(wsep, c->prompt_buffer[c->prompt_index]))
1189 break;
1192 /* Find the word right after the separation. */
1193 while (c->prompt_index != size) {
1194 c->prompt_index++;
1195 if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1196 break;
1199 c->flags |= CLIENT_STATUS;
1200 break;
1201 case MODEKEYEDIT_NEXTSPACEEND:
1202 wsep = " ";
1203 /* FALLTHROUGH */
1204 case MODEKEYEDIT_NEXTWORDEND:
1205 if (wsep == NULL)
1206 wsep = options_get_string(oo, "word-separators");
1208 /* Find a word. */
1209 while (c->prompt_index != size) {
1210 c->prompt_index++;
1211 if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1212 break;
1215 /* Find the separator at the end of the word. */
1216 while (c->prompt_index != size) {
1217 c->prompt_index++;
1218 if (strchr(wsep, c->prompt_buffer[c->prompt_index]))
1219 break;
1222 c->flags |= CLIENT_STATUS;
1223 break;
1224 case MODEKEYEDIT_PREVIOUSSPACE:
1225 wsep = " ";
1226 /* FALLTHROUGH */
1227 case MODEKEYEDIT_PREVIOUSWORD:
1228 if (wsep == NULL)
1229 wsep = options_get_string(oo, "word-separators");
1231 /* Find a non-separator. */
1232 while (c->prompt_index != 0) {
1233 c->prompt_index--;
1234 if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1235 break;
1238 /* Find the separator at the beginning of the word. */
1239 while (c->prompt_index != 0) {
1240 c->prompt_index--;
1241 if (strchr(wsep, c->prompt_buffer[c->prompt_index])) {
1242 /* Go back to the word. */
1243 c->prompt_index++;
1244 break;
1248 c->flags |= CLIENT_STATUS;
1249 break;
1250 case MODEKEYEDIT_HISTORYUP:
1251 histstr = status_prompt_up_history(&c->prompt_hindex);
1252 if (histstr == NULL)
1253 break;
1254 xfree(c->prompt_buffer);
1255 c->prompt_buffer = xstrdup(histstr);
1256 c->prompt_index = strlen(c->prompt_buffer);
1257 c->flags |= CLIENT_STATUS;
1258 break;
1259 case MODEKEYEDIT_HISTORYDOWN:
1260 histstr = status_prompt_down_history(&c->prompt_hindex);
1261 if (histstr == NULL)
1262 break;
1263 xfree(c->prompt_buffer);
1264 c->prompt_buffer = xstrdup(histstr);
1265 c->prompt_index = strlen(c->prompt_buffer);
1266 c->flags |= CLIENT_STATUS;
1267 break;
1268 case MODEKEYEDIT_PASTE:
1269 if ((pb = paste_get_top(&global_buffers)) == NULL)
1270 break;
1271 for (n = 0; n < pb->size; n++) {
1272 ch = (u_char) pb->data[n];
1273 if (ch < 32 || ch == 127)
1274 break;
1277 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1);
1278 if (c->prompt_index == size) {
1279 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1280 c->prompt_index += n;
1281 c->prompt_buffer[c->prompt_index] = '\0';
1282 } else {
1283 memmove(c->prompt_buffer + c->prompt_index + n,
1284 c->prompt_buffer + c->prompt_index,
1285 size + 1 - c->prompt_index);
1286 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1287 c->prompt_index += n;
1290 c->flags |= CLIENT_STATUS;
1291 break;
1292 case MODEKEYEDIT_TRANSPOSECHARS:
1293 idx = c->prompt_index;
1294 if (idx < size)
1295 idx++;
1296 if (idx >= 2) {
1297 swapc = c->prompt_buffer[idx - 2];
1298 c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1];
1299 c->prompt_buffer[idx - 1] = swapc;
1300 c->prompt_index = idx;
1301 c->flags |= CLIENT_STATUS;
1303 break;
1304 case MODEKEYEDIT_ENTER:
1305 if (*c->prompt_buffer != '\0')
1306 status_prompt_add_history(c->prompt_buffer);
1307 if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0)
1308 status_prompt_clear(c);
1309 break;
1310 case MODEKEYEDIT_CANCEL:
1311 if (c->prompt_callbackfn(c->prompt_data, NULL) == 0)
1312 status_prompt_clear(c);
1313 break;
1314 case MODEKEY_OTHER:
1315 if ((key & 0xff00) != 0 || key < 32 || key == 127)
1316 break;
1317 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2);
1319 if (c->prompt_index == size) {
1320 c->prompt_buffer[c->prompt_index++] = key;
1321 c->prompt_buffer[c->prompt_index] = '\0';
1322 } else {
1323 memmove(c->prompt_buffer + c->prompt_index + 1,
1324 c->prompt_buffer + c->prompt_index,
1325 size + 1 - c->prompt_index);
1326 c->prompt_buffer[c->prompt_index++] = key;
1329 if (c->prompt_flags & PROMPT_SINGLE) {
1330 if (c->prompt_callbackfn(
1331 c->prompt_data, c->prompt_buffer) == 0)
1332 status_prompt_clear(c);
1335 c->flags |= CLIENT_STATUS;
1336 break;
1337 default:
1338 break;
1342 /* Get previous line from the history. */
1343 const char *
1344 status_prompt_up_history(u_int *idx)
1346 u_int size;
1349 * History runs from 0 to size - 1.
1351 * Index is from 0 to size. Zero is empty.
1354 size = ARRAY_LENGTH(&status_prompt_history);
1355 if (size == 0 || *idx == size)
1356 return (NULL);
1357 (*idx)++;
1358 return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1361 /* Get next line from the history. */
1362 const char *
1363 status_prompt_down_history(u_int *idx)
1365 u_int size;
1367 size = ARRAY_LENGTH(&status_prompt_history);
1368 if (size == 0 || *idx == 0)
1369 return ("");
1370 (*idx)--;
1371 if (*idx == 0)
1372 return ("");
1373 return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1376 /* Add line to the history. */
1377 void
1378 status_prompt_add_history(const char *line)
1380 u_int size;
1382 size = ARRAY_LENGTH(&status_prompt_history);
1383 if (size > 0 && strcmp(ARRAY_LAST(&status_prompt_history), line) == 0)
1384 return;
1386 if (size == PROMPT_HISTORY) {
1387 xfree(ARRAY_FIRST(&status_prompt_history));
1388 ARRAY_REMOVE(&status_prompt_history, 0);
1391 ARRAY_ADD(&status_prompt_history, xstrdup(line));
1394 /* Complete word. */
1395 char *
1396 status_prompt_complete(const char *s)
1398 const struct cmd_entry **cmdent;
1399 const struct options_table_entry *oe;
1400 ARRAY_DECL(, const char *) list;
1401 char *prefix, *s2;
1402 u_int i;
1403 size_t j;
1405 if (*s == '\0')
1406 return (NULL);
1408 /* First, build a list of all the possible matches. */
1409 ARRAY_INIT(&list);
1410 for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1411 if (strncmp((*cmdent)->name, s, strlen(s)) == 0)
1412 ARRAY_ADD(&list, (*cmdent)->name);
1414 for (oe = server_options_table; oe->name != NULL; oe++) {
1415 if (strncmp(oe->name, s, strlen(s)) == 0)
1416 ARRAY_ADD(&list, oe->name);
1418 for (oe = session_options_table; oe->name != NULL; oe++) {
1419 if (strncmp(oe->name, s, strlen(s)) == 0)
1420 ARRAY_ADD(&list, oe->name);
1422 for (oe = window_options_table; oe->name != NULL; oe++) {
1423 if (strncmp(oe->name, s, strlen(s)) == 0)
1424 ARRAY_ADD(&list, oe->name);
1427 /* If none, bail now. */
1428 if (ARRAY_LENGTH(&list) == 0) {
1429 ARRAY_FREE(&list);
1430 return (NULL);
1433 /* If an exact match, return it, with a trailing space. */
1434 if (ARRAY_LENGTH(&list) == 1) {
1435 xasprintf(&s2, "%s ", ARRAY_FIRST(&list));
1436 ARRAY_FREE(&list);
1437 return (s2);
1440 /* Now loop through the list and find the longest common prefix. */
1441 prefix = xstrdup(ARRAY_FIRST(&list));
1442 for (i = 1; i < ARRAY_LENGTH(&list); i++) {
1443 s = ARRAY_ITEM(&list, i);
1445 j = strlen(s);
1446 if (j > strlen(prefix))
1447 j = strlen(prefix);
1448 for (; j > 0; j--) {
1449 if (prefix[j - 1] != s[j - 1])
1450 prefix[j - 1] = '\0';
1454 ARRAY_FREE(&list);
1455 return (prefix);