Remove tmux's (already minimal) 88 colour support. Such terminals are
[tmux-openbsd.git] / status.c
blob6ce0b8710ea265efec0405547c3cd36ec85d0532
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 int 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 int 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 free(wl->status_text);
226 memcpy(&wl->status_cell, &stdgc, sizeof wl->status_cell);
227 wl->status_text = status_print(c, wl, t, &wl->status_cell);
228 wl->status_width =
229 screen_write_cstrlen(utf8flag, "%s", wl->status_text);
231 if (wl == s->curw)
232 wloffset = wlwidth;
234 oo = &wl->window->options;
235 sep = options_get_string(oo, "window-status-separator");
236 seplen = screen_write_strlen(utf8flag, "%s", sep);
237 wlwidth += wl->status_width + seplen;
240 /* Create a new screen for the window list. */
241 screen_init(&window_list, wlwidth, 1, 0);
243 /* And draw the window list into it. */
244 screen_write_start(&ctx, NULL, &window_list);
245 RB_FOREACH(wl, winlinks, &s->windows) {
246 screen_write_cnputs(&ctx,
247 -1, &wl->status_cell, utf8flag, "%s", wl->status_text);
249 oo = &wl->window->options;
250 sep = options_get_string(oo, "window-status-separator");
251 screen_write_nputs(&ctx, -1, &stdgc, utf8flag, "%s", sep);
253 screen_write_stop(&ctx);
255 /* If there is enough space for the total width, skip to draw now. */
256 if (wlwidth <= wlavailable)
257 goto draw;
259 /* Find size of current window text. */
260 wlsize = s->curw->status_width;
263 * If the current window is already on screen, good to draw from the
264 * start and just leave off the end.
266 if (wloffset + wlsize < wlavailable) {
267 if (wlavailable > 0) {
268 rarrow = 1;
269 wlavailable--;
271 wlwidth = wlavailable;
272 } else {
274 * Work out how many characters we need to omit from the
275 * start. There are wlavailable characters to fill, and
276 * wloffset + wlsize must be the last. So, the start character
277 * is wloffset + wlsize - wlavailable.
279 if (wlavailable > 0) {
280 larrow = 1;
281 wlavailable--;
284 wlstart = wloffset + wlsize - wlavailable;
285 if (wlavailable > 0 && wlwidth > wlstart + wlavailable + 1) {
286 rarrow = 1;
287 wlstart++;
288 wlavailable--;
290 wlwidth = wlavailable;
293 /* Bail if anything is now too small too. */
294 if (wlwidth == 0 || wlavailable == 0) {
295 screen_free(&window_list);
296 goto out;
300 * Now the start position is known, work out the state of the left and
301 * right arrows.
303 offset = 0;
304 RB_FOREACH(wl, winlinks, &s->windows) {
305 if (wl->flags & WINLINK_ALERTFLAGS &&
306 larrow == 1 && offset < wlstart)
307 larrow = -1;
309 offset += wl->status_width;
311 if (wl->flags & WINLINK_ALERTFLAGS &&
312 rarrow == 1 && offset > wlstart + wlwidth)
313 rarrow = -1;
316 draw:
317 /* Begin drawing. */
318 screen_write_start(&ctx, NULL, &c->status);
320 /* Draw the left string and arrow. */
321 screen_write_cursormove(&ctx, 0, 0);
322 if (llen != 0) {
323 screen_write_cnputs(&ctx, llen, &lgc, utf8flag, "%s", left);
324 screen_write_putc(&ctx, &stdgc, ' ');
326 if (larrow != 0) {
327 memcpy(&gc, &stdgc, sizeof gc);
328 if (larrow == -1)
329 gc.attr ^= GRID_ATTR_REVERSE;
330 screen_write_putc(&ctx, &gc, '<');
333 /* Draw the right string and arrow. */
334 if (rarrow != 0) {
335 screen_write_cursormove(&ctx, c->tty.sx - rlen - 2, 0);
336 memcpy(&gc, &stdgc, sizeof gc);
337 if (rarrow == -1)
338 gc.attr ^= GRID_ATTR_REVERSE;
339 screen_write_putc(&ctx, &gc, '>');
340 } else
341 screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, 0);
342 if (rlen != 0) {
343 screen_write_putc(&ctx, &stdgc, ' ');
344 screen_write_cnputs(&ctx, rlen, &rgc, utf8flag, "%s", right);
347 /* Figure out the offset for the window list. */
348 if (llen != 0)
349 wloffset = llen + 1;
350 else
351 wloffset = 0;
352 if (wlwidth < wlavailable) {
353 switch (options_get_number(&s->options, "status-justify")) {
354 case 1: /* centered */
355 wloffset += (wlavailable - wlwidth) / 2;
356 break;
357 case 2: /* right */
358 wloffset += (wlavailable - wlwidth);
359 break;
362 if (larrow != 0)
363 wloffset++;
365 /* Copy the window list. */
366 c->wlmouse = -wloffset + wlstart;
367 screen_write_cursormove(&ctx, wloffset, 0);
368 screen_write_copy(&ctx, &window_list, wlstart, 0, wlwidth, 1);
369 screen_free(&window_list);
371 screen_write_stop(&ctx);
373 out:
374 free(left);
375 free(right);
377 if (grid_compare(c->status.grid, old_status.grid) == 0) {
378 screen_free(&old_status);
379 return (0);
381 screen_free(&old_status);
382 return (1);
385 /* Replace a single special sequence (prefixed by #). */
386 void
387 status_replace1(struct client *c, struct session *s, struct winlink *wl,
388 struct window_pane *wp, char **iptr, char **optr, char *out,
389 size_t outsize, int jobsflag)
391 char ch, tmp[256], *ptr, *endptr, *freeptr;
392 size_t ptrlen;
393 long limit;
394 u_int idx;
396 errno = 0;
397 limit = strtol(*iptr, &endptr, 10);
398 if ((limit == 0 && errno != EINVAL) ||
399 (limit == LONG_MIN && errno != ERANGE) ||
400 (limit == LONG_MAX && errno != ERANGE) ||
401 limit != 0)
402 *iptr = endptr;
403 if (limit <= 0)
404 limit = LONG_MAX;
406 freeptr = NULL;
408 switch (*(*iptr)++) {
409 case '(':
410 if (!jobsflag) {
411 ch = ')';
412 goto skip_to;
414 if ((ptr = status_find_job(c, iptr)) == NULL)
415 return;
416 goto do_replace;
417 case 'D':
418 xsnprintf(tmp, sizeof tmp, "%%%u", wp->id);
419 ptr = tmp;
420 goto do_replace;
421 case 'H':
422 if (gethostname(tmp, sizeof tmp) != 0)
423 fatal("gethostname failed");
424 ptr = tmp;
425 goto do_replace;
426 case 'h':
427 if (gethostname(tmp, sizeof tmp) != 0)
428 fatal("gethostname failed");
429 if ((ptr = strchr(tmp, '.')) != NULL)
430 *ptr = '\0';
431 ptr = tmp;
432 goto do_replace;
433 case 'I':
434 xsnprintf(tmp, sizeof tmp, "%d", wl->idx);
435 ptr = tmp;
436 goto do_replace;
437 case 'P':
438 if (window_pane_index(wp, &idx) != 0)
439 fatalx("index not found");
440 xsnprintf(tmp, sizeof tmp, "%u", idx);
441 ptr = tmp;
442 goto do_replace;
443 case 'S':
444 ptr = s->name;
445 goto do_replace;
446 case 'T':
447 ptr = wp->base.title;
448 goto do_replace;
449 case 'W':
450 ptr = wl->window->name;
451 goto do_replace;
452 case 'F':
453 ptr = window_printable_flags(s, wl);
454 freeptr = ptr;
455 goto do_replace;
456 case '[':
458 * Embedded style, handled at display time. Leave present and
459 * skip input until ].
461 ch = ']';
462 goto skip_to;
463 case '{':
464 ptr = (char *) "#{";
465 goto do_replace;
466 case '#':
467 *(*optr)++ = '#';
468 break;
471 return;
473 do_replace:
474 ptrlen = strlen(ptr);
475 if ((size_t) limit < ptrlen)
476 ptrlen = limit;
478 if (*optr + ptrlen >= out + outsize - 1)
479 goto out;
480 while (ptrlen > 0 && *ptr != '\0') {
481 *(*optr)++ = *ptr++;
482 ptrlen--;
485 out:
486 free(freeptr);
487 return;
489 skip_to:
490 *(*optr)++ = '#';
492 (*iptr)--; /* include ch */
493 while (**iptr != ch && **iptr != '\0') {
494 if (*optr >= out + outsize - 1)
495 break;
496 *(*optr)++ = *(*iptr)++;
500 /* Replace special sequences in fmt. */
501 char *
502 status_replace(struct client *c, struct session *s, struct winlink *wl,
503 struct window_pane *wp, const char *fmt, time_t t, int jobsflag)
505 static char out[BUFSIZ];
506 char in[BUFSIZ], ch, *iptr, *optr, *expanded;
507 size_t len;
508 struct format_tree *ft;
510 if (fmt == NULL)
511 return (xstrdup(""));
513 if (s == NULL)
514 s = c->session;
515 if (wl == NULL)
516 wl = s->curw;
517 if (wp == NULL)
518 wp = wl->window->active;
520 len = strftime(in, sizeof in, fmt, localtime(&t));
521 in[len] = '\0';
523 iptr = in;
524 optr = out;
526 while (*iptr != '\0') {
527 if (optr >= out + (sizeof out) - 1)
528 break;
529 ch = *iptr++;
531 if (ch != '#' || *iptr == '\0') {
532 *optr++ = ch;
533 continue;
535 status_replace1(
536 c, s, wl, wp, &iptr, &optr, out, sizeof out, jobsflag);
538 *optr = '\0';
540 ft = format_create();
541 format_client(ft, c);
542 format_session(ft, s);
543 format_winlink(ft, s, wl);
544 format_window_pane(ft, wp);
545 expanded = format_expand(ft, out);
546 format_free(ft);
547 return (expanded);
550 /* Figure out job name and get its result, starting it off if necessary. */
551 char *
552 status_find_job(struct client *c, char **iptr)
554 struct status_out *so, so_find;
555 char *cmd;
556 int lastesc;
557 size_t len;
559 if (**iptr == '\0')
560 return (NULL);
561 if (**iptr == ')') { /* no command given */
562 (*iptr)++;
563 return (NULL);
566 cmd = xmalloc(strlen(*iptr) + 1);
567 len = 0;
569 lastesc = 0;
570 for (; **iptr != '\0'; (*iptr)++) {
571 if (!lastesc && **iptr == ')')
572 break; /* unescaped ) is the end */
573 if (!lastesc && **iptr == '\\') {
574 lastesc = 1;
575 continue; /* skip \ if not escaped */
577 lastesc = 0;
578 cmd[len++] = **iptr;
580 if (**iptr == '\0') /* no terminating ) */ {
581 free(cmd);
582 return (NULL);
584 (*iptr)++; /* skip final ) */
585 cmd[len] = '\0';
587 /* First try in the new tree. */
588 so_find.cmd = cmd;
589 so = RB_FIND(status_out_tree, &c->status_new, &so_find);
590 if (so != NULL && so->out != NULL) {
591 free(cmd);
592 return (so->out);
595 /* If not found at all, start the job and add to the tree. */
596 if (so == NULL) {
597 job_run(cmd, NULL, status_job_callback, status_job_free, c);
598 c->references++;
600 so = xmalloc(sizeof *so);
601 so->cmd = xstrdup(cmd);
602 so->out = NULL;
603 RB_INSERT(status_out_tree, &c->status_new, so);
606 /* Lookup in the old tree. */
607 so_find.cmd = cmd;
608 so = RB_FIND(status_out_tree, &c->status_old, &so_find);
609 free(cmd);
610 if (so != NULL)
611 return (so->out);
612 return (NULL);
615 /* Free job tree. */
616 void
617 status_free_jobs(struct status_out_tree *sotree)
619 struct status_out *so, *so_next;
621 so_next = RB_MIN(status_out_tree, sotree);
622 while (so_next != NULL) {
623 so = so_next;
624 so_next = RB_NEXT(status_out_tree, sotree, so);
626 RB_REMOVE(status_out_tree, sotree, so);
627 free(so->out);
628 free(so->cmd);
629 free(so);
633 /* Update jobs on status interval. */
634 void
635 status_update_jobs(struct client *c)
637 /* Free the old tree. */
638 status_free_jobs(&c->status_old);
640 /* Move the new to old. */
641 memcpy(&c->status_old, &c->status_new, sizeof c->status_old);
642 RB_INIT(&c->status_new);
645 /* Free status job. */
646 void
647 status_job_free(void *data)
649 struct client *c = data;
651 c->references--;
654 /* Job has finished: save its result. */
655 void
656 status_job_callback(struct job *job)
658 struct client *c = job->data;
659 struct status_out *so, so_find;
660 char *line, *buf;
661 size_t len;
663 if (c->flags & CLIENT_DEAD)
664 return;
666 so_find.cmd = job->cmd;
667 so = RB_FIND(status_out_tree, &c->status_new, &so_find);
668 if (so == NULL || so->out != NULL)
669 return;
671 buf = NULL;
672 if ((line = evbuffer_readline(job->event->input)) == NULL) {
673 len = EVBUFFER_LENGTH(job->event->input);
674 buf = xmalloc(len + 1);
675 if (len != 0)
676 memcpy(buf, EVBUFFER_DATA(job->event->input), len);
677 buf[len] = '\0';
678 } else
679 buf = line;
681 so->out = buf;
682 server_status_client(c);
685 /* Return winlink status line entry and adjust gc as necessary. */
686 char *
687 status_print(
688 struct client *c, struct winlink *wl, time_t t, struct grid_cell *gc)
690 struct options *oo = &wl->window->options;
691 struct session *s = c->session;
692 const char *fmt;
693 char *text;
694 int fg, bg, attr;
696 fg = options_get_number(oo, "window-status-fg");
697 if (fg != 8)
698 colour_set_fg(gc, fg);
699 bg = options_get_number(oo, "window-status-bg");
700 if (bg != 8)
701 colour_set_bg(gc, bg);
702 attr = options_get_number(oo, "window-status-attr");
703 if (attr != 0)
704 gc->attr = attr;
705 fmt = options_get_string(oo, "window-status-format");
706 if (wl == s->curw) {
707 fg = options_get_number(oo, "window-status-current-fg");
708 if (fg != 8)
709 colour_set_fg(gc, fg);
710 bg = options_get_number(oo, "window-status-current-bg");
711 if (bg != 8)
712 colour_set_bg(gc, bg);
713 attr = options_get_number(oo, "window-status-current-attr");
714 if (attr != 0)
715 gc->attr = attr;
716 fmt = options_get_string(oo, "window-status-current-format");
718 if (wl == TAILQ_FIRST(&s->lastw)) {
719 fg = options_get_number(oo, "window-status-last-fg");
720 if (fg != 8)
721 colour_set_fg(gc, fg);
722 bg = options_get_number(oo, "window-status-last-bg");
723 if (bg != 8)
724 colour_set_bg(gc, bg);
725 attr = options_get_number(oo, "window-status-last-attr");
726 if (attr != 0)
727 gc->attr = attr;
730 if (wl->flags & WINLINK_BELL) {
731 fg = options_get_number(oo, "window-status-bell-fg");
732 if (fg != 8)
733 colour_set_fg(gc, fg);
734 bg = options_get_number(oo, "window-status-bell-bg");
735 if (bg != 8)
736 colour_set_bg(gc, bg);
737 attr = options_get_number(oo, "window-status-bell-attr");
738 if (attr != 0)
739 gc->attr = attr;
740 } else if (wl->flags & WINLINK_CONTENT) {
741 fg = options_get_number(oo, "window-status-content-fg");
742 if (fg != 8)
743 colour_set_fg(gc, fg);
744 bg = options_get_number(oo, "window-status-content-bg");
745 if (bg != 8)
746 colour_set_bg(gc, bg);
747 attr = options_get_number(oo, "window-status-content-attr");
748 if (attr != 0)
749 gc->attr = attr;
750 } else if (wl->flags & (WINLINK_ACTIVITY|WINLINK_SILENCE)) {
751 fg = options_get_number(oo, "window-status-activity-fg");
752 if (fg != 8)
753 colour_set_fg(gc, fg);
754 bg = options_get_number(oo, "window-status-activity-bg");
755 if (bg != 8)
756 colour_set_bg(gc, bg);
757 attr = options_get_number(oo, "window-status-activity-attr");
758 if (attr != 0)
759 gc->attr = attr;
762 text = status_replace(c, NULL, wl, NULL, fmt, t, 1);
763 return (text);
766 /* Set a status line message. */
767 void printflike2
768 status_message_set(struct client *c, const char *fmt, ...)
770 struct timeval tv;
771 struct session *s = c->session;
772 struct message_entry *msg;
773 va_list ap;
774 int delay;
775 u_int i, limit;
777 status_prompt_clear(c);
778 status_message_clear(c);
780 va_start(ap, fmt);
781 xvasprintf(&c->message_string, fmt, ap);
782 va_end(ap);
784 ARRAY_EXPAND(&c->message_log, 1);
785 msg = &ARRAY_LAST(&c->message_log);
786 msg->msg_time = time(NULL);
787 msg->msg = xstrdup(c->message_string);
789 if (s == NULL)
790 limit = 0;
791 else
792 limit = options_get_number(&s->options, "message-limit");
793 if (ARRAY_LENGTH(&c->message_log) > limit) {
794 limit = ARRAY_LENGTH(&c->message_log) - limit;
795 for (i = 0; i < limit; i++) {
796 msg = &ARRAY_FIRST(&c->message_log);
797 free(msg->msg);
798 ARRAY_REMOVE(&c->message_log, 0);
802 delay = options_get_number(&c->session->options, "display-time");
803 tv.tv_sec = delay / 1000;
804 tv.tv_usec = (delay % 1000) * 1000L;
806 if (event_initialized (&c->message_timer))
807 evtimer_del(&c->message_timer);
808 evtimer_set(&c->message_timer, status_message_callback, c);
809 evtimer_add(&c->message_timer, &tv);
811 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
812 c->flags |= CLIENT_STATUS;
815 /* Clear status line message. */
816 void
817 status_message_clear(struct client *c)
819 if (c->message_string == NULL)
820 return;
822 free(c->message_string);
823 c->message_string = NULL;
825 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
826 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
828 screen_reinit(&c->status);
831 /* Clear status line message after timer expires. */
832 void
833 status_message_callback(unused int fd, unused short event, void *data)
835 struct client *c = data;
837 status_message_clear(c);
840 /* Draw client message on status line of present else on last line. */
842 status_message_redraw(struct client *c)
844 struct screen_write_ctx ctx;
845 struct session *s = c->session;
846 struct screen old_status;
847 size_t len;
848 struct grid_cell gc;
849 int utf8flag;
851 if (c->tty.sx == 0 || c->tty.sy == 0)
852 return (0);
853 memcpy(&old_status, &c->status, sizeof old_status);
854 screen_init(&c->status, c->tty.sx, 1, 0);
856 utf8flag = options_get_number(&s->options, "status-utf8");
858 len = screen_write_strlen(utf8flag, "%s", c->message_string);
859 if (len > c->tty.sx)
860 len = c->tty.sx;
862 memcpy(&gc, &grid_default_cell, sizeof gc);
863 colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
864 colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
865 gc.attr |= options_get_number(&s->options, "message-attr");
867 screen_write_start(&ctx, NULL, &c->status);
869 screen_write_cursormove(&ctx, 0, 0);
870 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->message_string);
871 for (; len < c->tty.sx; len++)
872 screen_write_putc(&ctx, &gc, ' ');
874 screen_write_stop(&ctx);
876 if (grid_compare(c->status.grid, old_status.grid) == 0) {
877 screen_free(&old_status);
878 return (0);
880 screen_free(&old_status);
881 return (1);
884 /* Enable status line prompt. */
885 void
886 status_prompt_set(struct client *c, const char *msg, const char *input,
887 int (*callbackfn)(void *, const char *), void (*freefn)(void *),
888 void *data, int flags)
890 int keys;
892 status_message_clear(c);
893 status_prompt_clear(c);
895 c->prompt_string = status_replace(c, NULL, NULL, NULL, msg,
896 time(NULL), 0);
898 c->prompt_buffer = status_replace(c, NULL, NULL, NULL, input,
899 time(NULL), 0);
900 c->prompt_index = strlen(c->prompt_buffer);
902 c->prompt_callbackfn = callbackfn;
903 c->prompt_freefn = freefn;
904 c->prompt_data = data;
906 c->prompt_hindex = 0;
908 c->prompt_flags = flags;
910 keys = options_get_number(&c->session->options, "status-keys");
911 if (keys == MODEKEY_EMACS)
912 mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit);
913 else
914 mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit);
916 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
917 c->flags |= CLIENT_STATUS;
920 /* Remove status line prompt. */
921 void
922 status_prompt_clear(struct client *c)
924 if (c->prompt_string == NULL)
925 return;
927 if (c->prompt_freefn != NULL && c->prompt_data != NULL)
928 c->prompt_freefn(c->prompt_data);
930 free(c->prompt_string);
931 c->prompt_string = NULL;
933 free(c->prompt_buffer);
934 c->prompt_buffer = NULL;
936 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
937 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
939 screen_reinit(&c->status);
942 /* Update status line prompt with a new prompt string. */
943 void
944 status_prompt_update(struct client *c, const char *msg, const char *input)
946 free(c->prompt_string);
947 c->prompt_string = status_replace(c, NULL, NULL, NULL, msg,
948 time(NULL), 0);
950 free(c->prompt_buffer);
951 c->prompt_buffer = status_replace(c, NULL, NULL, NULL, input,
952 time(NULL), 0);
953 c->prompt_index = strlen(c->prompt_buffer);
955 c->prompt_hindex = 0;
957 c->flags |= CLIENT_STATUS;
960 /* Draw client prompt on status line of present else on last line. */
962 status_prompt_redraw(struct client *c)
964 struct screen_write_ctx ctx;
965 struct session *s = c->session;
966 struct screen old_status;
967 size_t i, size, left, len, off;
968 struct grid_cell gc, *gcp;
969 int utf8flag;
971 if (c->tty.sx == 0 || c->tty.sy == 0)
972 return (0);
973 memcpy(&old_status, &c->status, sizeof old_status);
974 screen_init(&c->status, c->tty.sx, 1, 0);
976 utf8flag = options_get_number(&s->options, "status-utf8");
978 len = screen_write_strlen(utf8flag, "%s", c->prompt_string);
979 if (len > c->tty.sx)
980 len = c->tty.sx;
981 off = 0;
983 memcpy(&gc, &grid_default_cell, sizeof gc);
984 /* Change colours for command mode. */
985 if (c->prompt_mdata.mode == 1) {
986 colour_set_fg(&gc, options_get_number(&s->options, "message-command-fg"));
987 colour_set_bg(&gc, options_get_number(&s->options, "message-command-bg"));
988 gc.attr |= options_get_number(&s->options, "message-command-attr");
989 } else {
990 colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
991 colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
992 gc.attr |= options_get_number(&s->options, "message-attr");
995 screen_write_start(&ctx, NULL, &c->status);
997 screen_write_cursormove(&ctx, 0, 0);
998 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->prompt_string);
1000 left = c->tty.sx - len;
1001 if (left != 0) {
1002 size = screen_write_strlen(utf8flag, "%s", c->prompt_buffer);
1003 if (c->prompt_index >= left) {
1004 off = c->prompt_index - left + 1;
1005 if (c->prompt_index == size)
1006 left--;
1007 size = left;
1009 screen_write_nputs(
1010 &ctx, left, &gc, utf8flag, "%s", c->prompt_buffer + off);
1012 for (i = len + size; i < c->tty.sx; i++)
1013 screen_write_putc(&ctx, &gc, ' ');
1016 screen_write_stop(&ctx);
1018 /* Apply fake cursor. */
1019 off = len + c->prompt_index - off;
1020 gcp = grid_view_get_cell(c->status.grid, off, 0);
1021 gcp->attr ^= GRID_ATTR_REVERSE;
1023 if (grid_compare(c->status.grid, old_status.grid) == 0) {
1024 screen_free(&old_status);
1025 return (0);
1027 screen_free(&old_status);
1028 return (1);
1031 /* Handle keys in prompt. */
1032 void
1033 status_prompt_key(struct client *c, int key)
1035 struct session *sess = c->session;
1036 struct options *oo = &sess->options;
1037 struct paste_buffer *pb;
1038 char *s, *first, *last, word[64], swapc;
1039 const char *histstr;
1040 const char *wsep = NULL;
1041 u_char ch;
1042 size_t size, n, off, idx;
1044 size = strlen(c->prompt_buffer);
1045 switch (mode_key_lookup(&c->prompt_mdata, key, NULL)) {
1046 case MODEKEYEDIT_CURSORLEFT:
1047 if (c->prompt_index > 0) {
1048 c->prompt_index--;
1049 c->flags |= CLIENT_STATUS;
1051 break;
1052 case MODEKEYEDIT_SWITCHMODE:
1053 c->flags |= CLIENT_STATUS;
1054 break;
1055 case MODEKEYEDIT_SWITCHMODEAPPEND:
1056 c->flags |= CLIENT_STATUS;
1057 /* FALLTHROUGH */
1058 case MODEKEYEDIT_CURSORRIGHT:
1059 if (c->prompt_index < size) {
1060 c->prompt_index++;
1061 c->flags |= CLIENT_STATUS;
1063 break;
1064 case MODEKEYEDIT_SWITCHMODEBEGINLINE:
1065 c->flags |= CLIENT_STATUS;
1066 /* FALLTHROUGH */
1067 case MODEKEYEDIT_STARTOFLINE:
1068 if (c->prompt_index != 0) {
1069 c->prompt_index = 0;
1070 c->flags |= CLIENT_STATUS;
1072 break;
1073 case MODEKEYEDIT_SWITCHMODEAPPENDLINE:
1074 c->flags |= CLIENT_STATUS;
1075 /* FALLTHROUGH */
1076 case MODEKEYEDIT_ENDOFLINE:
1077 if (c->prompt_index != size) {
1078 c->prompt_index = size;
1079 c->flags |= CLIENT_STATUS;
1081 break;
1082 case MODEKEYEDIT_COMPLETE:
1083 if (*c->prompt_buffer == '\0')
1084 break;
1086 idx = c->prompt_index;
1087 if (idx != 0)
1088 idx--;
1090 /* Find the word we are in. */
1091 first = c->prompt_buffer + idx;
1092 while (first > c->prompt_buffer && *first != ' ')
1093 first--;
1094 while (*first == ' ')
1095 first++;
1096 last = c->prompt_buffer + idx;
1097 while (*last != '\0' && *last != ' ')
1098 last++;
1099 while (*last == ' ')
1100 last--;
1101 if (*last != '\0')
1102 last++;
1103 if (last <= first ||
1104 ((size_t) (last - first)) > (sizeof word) - 1)
1105 break;
1106 memcpy(word, first, last - first);
1107 word[last - first] = '\0';
1109 /* And try to complete it. */
1110 if ((s = status_prompt_complete(word)) == NULL)
1111 break;
1113 /* Trim out word. */
1114 n = size - (last - c->prompt_buffer) + 1; /* with \0 */
1115 memmove(first, last, n);
1116 size -= last - first;
1118 /* Insert the new word. */
1119 size += strlen(s);
1120 off = first - c->prompt_buffer;
1121 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1);
1122 first = c->prompt_buffer + off;
1123 memmove(first + strlen(s), first, n);
1124 memcpy(first, s, strlen(s));
1126 c->prompt_index = (first - c->prompt_buffer) + strlen(s);
1127 free(s);
1129 c->flags |= CLIENT_STATUS;
1130 break;
1131 case MODEKEYEDIT_BACKSPACE:
1132 if (c->prompt_index != 0) {
1133 if (c->prompt_index == size)
1134 c->prompt_buffer[--c->prompt_index] = '\0';
1135 else {
1136 memmove(c->prompt_buffer + c->prompt_index - 1,
1137 c->prompt_buffer + c->prompt_index,
1138 size + 1 - c->prompt_index);
1139 c->prompt_index--;
1141 c->flags |= CLIENT_STATUS;
1143 break;
1144 case MODEKEYEDIT_DELETE:
1145 if (c->prompt_index != size) {
1146 memmove(c->prompt_buffer + c->prompt_index,
1147 c->prompt_buffer + c->prompt_index + 1,
1148 size + 1 - c->prompt_index);
1149 c->flags |= CLIENT_STATUS;
1151 break;
1152 case MODEKEYEDIT_DELETELINE:
1153 *c->prompt_buffer = '\0';
1154 c->prompt_index = 0;
1155 c->flags |= CLIENT_STATUS;
1156 break;
1157 case MODEKEYEDIT_DELETETOENDOFLINE:
1158 if (c->prompt_index < size) {
1159 c->prompt_buffer[c->prompt_index] = '\0';
1160 c->flags |= CLIENT_STATUS;
1162 break;
1163 case MODEKEYEDIT_DELETEWORD:
1164 wsep = options_get_string(oo, "word-separators");
1165 idx = c->prompt_index;
1167 /* Find a non-separator. */
1168 while (idx != 0) {
1169 idx--;
1170 if (!strchr(wsep, c->prompt_buffer[idx]))
1171 break;
1174 /* Find the separator at the beginning of the word. */
1175 while (idx != 0) {
1176 idx--;
1177 if (strchr(wsep, c->prompt_buffer[idx])) {
1178 /* Go back to the word. */
1179 idx++;
1180 break;
1184 memmove(c->prompt_buffer + idx,
1185 c->prompt_buffer + c->prompt_index,
1186 size + 1 - c->prompt_index);
1187 memset(c->prompt_buffer + size - (c->prompt_index - idx),
1188 '\0', c->prompt_index - idx);
1189 c->prompt_index = idx;
1190 c->flags |= CLIENT_STATUS;
1191 break;
1192 case MODEKEYEDIT_NEXTSPACE:
1193 wsep = " ";
1194 /* FALLTHROUGH */
1195 case MODEKEYEDIT_NEXTWORD:
1196 if (wsep == NULL)
1197 wsep = options_get_string(oo, "word-separators");
1199 /* Find a separator. */
1200 while (c->prompt_index != size) {
1201 c->prompt_index++;
1202 if (strchr(wsep, c->prompt_buffer[c->prompt_index]))
1203 break;
1206 /* Find the word right after the separation. */
1207 while (c->prompt_index != size) {
1208 c->prompt_index++;
1209 if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1210 break;
1213 c->flags |= CLIENT_STATUS;
1214 break;
1215 case MODEKEYEDIT_NEXTSPACEEND:
1216 wsep = " ";
1217 /* FALLTHROUGH */
1218 case MODEKEYEDIT_NEXTWORDEND:
1219 if (wsep == NULL)
1220 wsep = options_get_string(oo, "word-separators");
1222 /* Find a word. */
1223 while (c->prompt_index != size) {
1224 c->prompt_index++;
1225 if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1226 break;
1229 /* Find the separator at the end of the word. */
1230 while (c->prompt_index != size) {
1231 c->prompt_index++;
1232 if (strchr(wsep, c->prompt_buffer[c->prompt_index]))
1233 break;
1236 c->flags |= CLIENT_STATUS;
1237 break;
1238 case MODEKEYEDIT_PREVIOUSSPACE:
1239 wsep = " ";
1240 /* FALLTHROUGH */
1241 case MODEKEYEDIT_PREVIOUSWORD:
1242 if (wsep == NULL)
1243 wsep = options_get_string(oo, "word-separators");
1245 /* Find a non-separator. */
1246 while (c->prompt_index != 0) {
1247 c->prompt_index--;
1248 if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1249 break;
1252 /* Find the separator at the beginning of the word. */
1253 while (c->prompt_index != 0) {
1254 c->prompt_index--;
1255 if (strchr(wsep, c->prompt_buffer[c->prompt_index])) {
1256 /* Go back to the word. */
1257 c->prompt_index++;
1258 break;
1262 c->flags |= CLIENT_STATUS;
1263 break;
1264 case MODEKEYEDIT_HISTORYUP:
1265 histstr = status_prompt_up_history(&c->prompt_hindex);
1266 if (histstr == NULL)
1267 break;
1268 free(c->prompt_buffer);
1269 c->prompt_buffer = xstrdup(histstr);
1270 c->prompt_index = strlen(c->prompt_buffer);
1271 c->flags |= CLIENT_STATUS;
1272 break;
1273 case MODEKEYEDIT_HISTORYDOWN:
1274 histstr = status_prompt_down_history(&c->prompt_hindex);
1275 if (histstr == NULL)
1276 break;
1277 free(c->prompt_buffer);
1278 c->prompt_buffer = xstrdup(histstr);
1279 c->prompt_index = strlen(c->prompt_buffer);
1280 c->flags |= CLIENT_STATUS;
1281 break;
1282 case MODEKEYEDIT_PASTE:
1283 if ((pb = paste_get_top(&global_buffers)) == NULL)
1284 break;
1285 for (n = 0; n < pb->size; n++) {
1286 ch = (u_char) pb->data[n];
1287 if (ch < 32 || ch == 127)
1288 break;
1291 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1);
1292 if (c->prompt_index == size) {
1293 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1294 c->prompt_index += n;
1295 c->prompt_buffer[c->prompt_index] = '\0';
1296 } else {
1297 memmove(c->prompt_buffer + c->prompt_index + n,
1298 c->prompt_buffer + c->prompt_index,
1299 size + 1 - c->prompt_index);
1300 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1301 c->prompt_index += n;
1304 c->flags |= CLIENT_STATUS;
1305 break;
1306 case MODEKEYEDIT_TRANSPOSECHARS:
1307 idx = c->prompt_index;
1308 if (idx < size)
1309 idx++;
1310 if (idx >= 2) {
1311 swapc = c->prompt_buffer[idx - 2];
1312 c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1];
1313 c->prompt_buffer[idx - 1] = swapc;
1314 c->prompt_index = idx;
1315 c->flags |= CLIENT_STATUS;
1317 break;
1318 case MODEKEYEDIT_ENTER:
1319 if (*c->prompt_buffer != '\0')
1320 status_prompt_add_history(c->prompt_buffer);
1321 if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0)
1322 status_prompt_clear(c);
1323 break;
1324 case MODEKEYEDIT_CANCEL:
1325 if (c->prompt_callbackfn(c->prompt_data, NULL) == 0)
1326 status_prompt_clear(c);
1327 break;
1328 case MODEKEY_OTHER:
1329 if ((key & 0xff00) != 0 || key < 32 || key == 127)
1330 break;
1331 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2);
1333 if (c->prompt_index == size) {
1334 c->prompt_buffer[c->prompt_index++] = key;
1335 c->prompt_buffer[c->prompt_index] = '\0';
1336 } else {
1337 memmove(c->prompt_buffer + c->prompt_index + 1,
1338 c->prompt_buffer + c->prompt_index,
1339 size + 1 - c->prompt_index);
1340 c->prompt_buffer[c->prompt_index++] = key;
1343 if (c->prompt_flags & PROMPT_SINGLE) {
1344 if (c->prompt_callbackfn(
1345 c->prompt_data, c->prompt_buffer) == 0)
1346 status_prompt_clear(c);
1349 c->flags |= CLIENT_STATUS;
1350 break;
1351 default:
1352 break;
1356 /* Get previous line from the history. */
1357 const char *
1358 status_prompt_up_history(u_int *idx)
1360 u_int size;
1363 * History runs from 0 to size - 1.
1365 * Index is from 0 to size. Zero is empty.
1368 size = ARRAY_LENGTH(&status_prompt_history);
1369 if (size == 0 || *idx == size)
1370 return (NULL);
1371 (*idx)++;
1372 return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1375 /* Get next line from the history. */
1376 const char *
1377 status_prompt_down_history(u_int *idx)
1379 u_int size;
1381 size = ARRAY_LENGTH(&status_prompt_history);
1382 if (size == 0 || *idx == 0)
1383 return ("");
1384 (*idx)--;
1385 if (*idx == 0)
1386 return ("");
1387 return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1390 /* Add line to the history. */
1391 void
1392 status_prompt_add_history(const char *line)
1394 u_int size;
1396 size = ARRAY_LENGTH(&status_prompt_history);
1397 if (size > 0 && strcmp(ARRAY_LAST(&status_prompt_history), line) == 0)
1398 return;
1400 if (size == PROMPT_HISTORY) {
1401 free(ARRAY_FIRST(&status_prompt_history));
1402 ARRAY_REMOVE(&status_prompt_history, 0);
1405 ARRAY_ADD(&status_prompt_history, xstrdup(line));
1408 /* Complete word. */
1409 char *
1410 status_prompt_complete(const char *s)
1412 const struct cmd_entry **cmdent;
1413 const struct options_table_entry *oe;
1414 ARRAY_DECL(, const char *) list;
1415 char *prefix, *s2;
1416 u_int i;
1417 size_t j;
1419 if (*s == '\0')
1420 return (NULL);
1422 /* First, build a list of all the possible matches. */
1423 ARRAY_INIT(&list);
1424 for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1425 if (strncmp((*cmdent)->name, s, strlen(s)) == 0)
1426 ARRAY_ADD(&list, (*cmdent)->name);
1428 for (oe = server_options_table; oe->name != NULL; oe++) {
1429 if (strncmp(oe->name, s, strlen(s)) == 0)
1430 ARRAY_ADD(&list, oe->name);
1432 for (oe = session_options_table; oe->name != NULL; oe++) {
1433 if (strncmp(oe->name, s, strlen(s)) == 0)
1434 ARRAY_ADD(&list, oe->name);
1436 for (oe = window_options_table; oe->name != NULL; oe++) {
1437 if (strncmp(oe->name, s, strlen(s)) == 0)
1438 ARRAY_ADD(&list, oe->name);
1441 /* If none, bail now. */
1442 if (ARRAY_LENGTH(&list) == 0) {
1443 ARRAY_FREE(&list);
1444 return (NULL);
1447 /* If an exact match, return it, with a trailing space. */
1448 if (ARRAY_LENGTH(&list) == 1) {
1449 xasprintf(&s2, "%s ", ARRAY_FIRST(&list));
1450 ARRAY_FREE(&list);
1451 return (s2);
1454 /* Now loop through the list and find the longest common prefix. */
1455 prefix = xstrdup(ARRAY_FIRST(&list));
1456 for (i = 1; i < ARRAY_LENGTH(&list); i++) {
1457 s = ARRAY_ITEM(&list, i);
1459 j = strlen(s);
1460 if (j > strlen(prefix))
1461 j = strlen(prefix);
1462 for (; j > 0; j--) {
1463 if (prefix[j - 1] != s[j - 1])
1464 prefix[j - 1] = '\0';
1468 ARRAY_FREE(&list);
1469 return (prefix);