Enforce history-limit option when clearing the screen, memory leak
[tmux-openbsd.git] / status.c
blobfee9f2d6ebf839715cd20edac523cb115046e5eb
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 += s->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 time_t t;
164 char *left, *right;
165 u_int offset, needed;
166 u_int wlstart, wlwidth, wlavailable, wloffset, wlsize;
167 size_t llen, rlen;
168 int larrow, rarrow, utf8flag;
170 /* No status line? */
171 if (c->tty.sy == 0 || !options_get_number(&s->options, "status"))
172 return (1);
173 left = right = NULL;
174 larrow = rarrow = 0;
176 /* Update status timer. */
177 if (gettimeofday(&c->status_timer, NULL) != 0)
178 fatal("gettimeofday failed");
179 t = c->status_timer.tv_sec;
181 /* Set up default colour. */
182 memcpy(&stdgc, &grid_default_cell, sizeof gc);
183 colour_set_fg(&stdgc, options_get_number(&s->options, "status-fg"));
184 colour_set_bg(&stdgc, options_get_number(&s->options, "status-bg"));
185 stdgc.attr |= options_get_number(&s->options, "status-attr");
187 /* Create the target screen. */
188 memcpy(&old_status, &c->status, sizeof old_status);
189 screen_init(&c->status, c->tty.sx, 1, 0);
190 screen_write_start(&ctx, NULL, &c->status);
191 for (offset = 0; offset < c->tty.sx; offset++)
192 screen_write_putc(&ctx, &stdgc, ' ');
193 screen_write_stop(&ctx);
195 /* If the height is one line, blank status line. */
196 if (c->tty.sy <= 1)
197 goto out;
199 /* Get UTF-8 flag. */
200 utf8flag = options_get_number(&s->options, "status-utf8");
202 /* Work out left and right strings. */
203 memcpy(&lgc, &stdgc, sizeof lgc);
204 left = status_redraw_get_left(c, t, utf8flag, &lgc, &llen);
205 memcpy(&rgc, &stdgc, sizeof rgc);
206 right = status_redraw_get_right(c, t, utf8flag, &rgc, &rlen);
209 * Figure out how much space we have for the window list. If there
210 * isn't enough space, just show a blank status line.
212 needed = 0;
213 if (llen != 0)
214 needed += llen + 1;
215 if (rlen != 0)
216 needed += rlen + 1;
217 if (c->tty.sx == 0 || c->tty.sx <= needed)
218 goto out;
219 wlavailable = c->tty.sx - needed;
221 /* Calculate the total size needed for the window list. */
222 wlstart = wloffset = wlwidth = 0;
223 RB_FOREACH(wl, winlinks, &s->windows) {
224 if (wl->status_text != NULL)
225 xfree(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;
233 wlwidth += wl->status_width + 1;
236 /* Create a new screen for the window list. */
237 screen_init(&window_list, wlwidth, 1, 0);
239 /* And draw the window list into it. */
240 screen_write_start(&ctx, NULL, &window_list);
241 RB_FOREACH(wl, winlinks, &s->windows) {
242 screen_write_cnputs(&ctx,
243 -1, &wl->status_cell, utf8flag, "%s", wl->status_text);
244 screen_write_putc(&ctx, &stdgc, ' ');
246 screen_write_stop(&ctx);
248 /* If there is enough space for the total width, skip to draw now. */
249 if (wlwidth <= wlavailable)
250 goto draw;
252 /* Find size of current window text. */
253 wlsize = s->curw->status_width;
256 * If the current window is already on screen, good to draw from the
257 * start and just leave off the end.
259 if (wloffset + wlsize < wlavailable) {
260 if (wlavailable > 0) {
261 rarrow = 1;
262 wlavailable--;
264 wlwidth = wlavailable;
265 } else {
267 * Work out how many characters we need to omit from the
268 * start. There are wlavailable characters to fill, and
269 * wloffset + wlsize must be the last. So, the start character
270 * is wloffset + wlsize - wlavailable.
272 if (wlavailable > 0) {
273 larrow = 1;
274 wlavailable--;
277 wlstart = wloffset + wlsize - wlavailable;
278 if (wlavailable > 0 && wlwidth > wlstart + wlavailable + 1) {
279 rarrow = 1;
280 wlstart++;
281 wlavailable--;
283 wlwidth = wlavailable;
286 /* Bail if anything is now too small too. */
287 if (wlwidth == 0 || wlavailable == 0) {
288 screen_free(&window_list);
289 goto out;
293 * Now the start position is known, work out the state of the left and
294 * right arrows.
296 offset = 0;
297 RB_FOREACH(wl, winlinks, &s->windows) {
298 if (wl->flags & WINLINK_ALERTFLAGS &&
299 larrow == 1 && offset < wlstart)
300 larrow = -1;
302 offset += wl->status_width;
304 if (wl->flags & WINLINK_ALERTFLAGS &&
305 rarrow == 1 && offset > wlstart + wlwidth)
306 rarrow = -1;
309 draw:
310 /* Begin drawing. */
311 screen_write_start(&ctx, NULL, &c->status);
313 /* Draw the left string and arrow. */
314 screen_write_cursormove(&ctx, 0, 0);
315 if (llen != 0) {
316 screen_write_cnputs(&ctx, llen, &lgc, utf8flag, "%s", left);
317 screen_write_putc(&ctx, &stdgc, ' ');
319 if (larrow != 0) {
320 memcpy(&gc, &stdgc, sizeof gc);
321 if (larrow == -1)
322 gc.attr ^= GRID_ATTR_REVERSE;
323 screen_write_putc(&ctx, &gc, '<');
326 /* Draw the right string and arrow. */
327 if (rarrow != 0) {
328 screen_write_cursormove(&ctx, c->tty.sx - rlen - 2, 0);
329 memcpy(&gc, &stdgc, sizeof gc);
330 if (rarrow == -1)
331 gc.attr ^= GRID_ATTR_REVERSE;
332 screen_write_putc(&ctx, &gc, '>');
333 } else
334 screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, 0);
335 if (rlen != 0) {
336 screen_write_putc(&ctx, &stdgc, ' ');
337 screen_write_cnputs(&ctx, rlen, &rgc, utf8flag, "%s", right);
340 /* Figure out the offset for the window list. */
341 if (llen != 0)
342 wloffset = llen + 1;
343 else
344 wloffset = 0;
345 if (wlwidth < wlavailable) {
346 switch (options_get_number(&s->options, "status-justify")) {
347 case 1: /* centered */
348 wloffset += (wlavailable - wlwidth) / 2;
349 break;
350 case 2: /* right */
351 wloffset += (wlavailable - wlwidth);
352 break;
355 if (larrow != 0)
356 wloffset++;
358 /* Copy the window list. */
359 s->wlmouse = -wloffset + wlstart;
360 screen_write_cursormove(&ctx, wloffset, 0);
361 screen_write_copy(&ctx, &window_list, wlstart, 0, wlwidth, 1);
362 screen_free(&window_list);
364 screen_write_stop(&ctx);
366 out:
367 if (left != NULL)
368 xfree(left);
369 if (right != NULL)
370 xfree(right);
372 if (grid_compare(c->status.grid, old_status.grid) == 0) {
373 screen_free(&old_status);
374 return (0);
376 screen_free(&old_status);
377 return (1);
380 /* Replace a single special sequence (prefixed by #). */
381 void
382 status_replace1(struct client *c, struct session *s, struct winlink *wl,
383 struct window_pane *wp, char **iptr, char **optr, char *out,
384 size_t outsize, int jobsflag)
386 char ch, tmp[256], *ptr, *endptr, *freeptr;
387 size_t ptrlen;
388 long limit;
389 u_int idx;
391 if (s == NULL)
392 s = c->session;
393 if (wl == NULL)
394 wl = s->curw;
395 if (wp == NULL)
396 wp = wl->window->active;
398 errno = 0;
399 limit = strtol(*iptr, &endptr, 10);
400 if ((limit == 0 && errno != EINVAL) ||
401 (limit == LONG_MIN && errno != ERANGE) ||
402 (limit == LONG_MAX && errno != ERANGE) ||
403 limit != 0)
404 *iptr = endptr;
405 if (limit <= 0)
406 limit = LONG_MAX;
408 freeptr = NULL;
410 switch (*(*iptr)++) {
411 case '(':
412 if (!jobsflag) {
413 ch = ')';
414 goto skip_to;
416 if ((ptr = status_find_job(c, iptr)) == NULL)
417 return;
418 goto do_replace;
419 case 'D':
420 xsnprintf(tmp, sizeof tmp, "%%%u", wp->id);
421 ptr = tmp;
422 goto do_replace;
423 case 'H':
424 if (gethostname(tmp, sizeof tmp) != 0)
425 fatal("gethostname failed");
426 ptr = tmp;
427 goto do_replace;
428 case 'h':
429 if (gethostname(tmp, sizeof tmp) != 0)
430 fatal("gethostname failed");
431 if ((ptr = strchr(tmp, '.')) != NULL)
432 *ptr = '\0';
433 ptr = tmp;
434 goto do_replace;
435 case 'I':
436 xsnprintf(tmp, sizeof tmp, "%d", wl->idx);
437 ptr = tmp;
438 goto do_replace;
439 case 'P':
440 if (window_pane_index(wp, &idx) != 0)
441 fatalx("index not found");
442 xsnprintf(
443 tmp, sizeof tmp, "%u", idx);
444 ptr = tmp;
445 goto do_replace;
446 case 'S':
447 ptr = s->name;
448 goto do_replace;
449 case 'T':
450 ptr = wp->base.title;
451 goto do_replace;
452 case 'W':
453 ptr = wl->window->name;
454 goto do_replace;
455 case 'F':
456 ptr = window_printable_flags(s, wl);
457 freeptr = ptr;
458 goto do_replace;
459 case '[':
461 * Embedded style, handled at display time. Leave present and
462 * skip input until ].
464 ch = ']';
465 goto skip_to;
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 if (freeptr != NULL)
487 xfree(freeptr);
488 return;
490 skip_to:
491 *(*optr)++ = '#';
493 (*iptr)--; /* include ch */
494 while (**iptr != ch && **iptr != '\0') {
495 if (*optr >= out + outsize - 1)
496 break;
497 *(*optr)++ = *(*iptr)++;
501 /* Replace special sequences in fmt. */
502 char *
503 status_replace(struct client *c, struct session *s, struct winlink *wl,
504 struct window_pane *wp, const char *fmt, time_t t, int jobsflag)
506 static char out[BUFSIZ];
507 char in[BUFSIZ], ch, *iptr, *optr;
508 size_t len;
510 len = strftime(in, sizeof in, fmt, localtime(&t));
511 in[len] = '\0';
513 iptr = in;
514 optr = out;
516 while (*iptr != '\0') {
517 if (optr >= out + (sizeof out) - 1)
518 break;
519 ch = *iptr++;
521 if (ch != '#' || *iptr == '\0') {
522 *optr++ = ch;
523 continue;
525 status_replace1(
526 c, s, wl, wp, &iptr, &optr, out, sizeof out, jobsflag);
528 *optr = '\0';
530 return (xstrdup(out));
533 /* Figure out job name and get its result, starting it off if necessary. */
534 char *
535 status_find_job(struct client *c, char **iptr)
537 struct status_out *so, so_find;
538 char *cmd;
539 int lastesc;
540 size_t len;
542 if (**iptr == '\0')
543 return (NULL);
544 if (**iptr == ')') { /* no command given */
545 (*iptr)++;
546 return (NULL);
549 cmd = xmalloc(strlen(*iptr) + 1);
550 len = 0;
552 lastesc = 0;
553 for (; **iptr != '\0'; (*iptr)++) {
554 if (!lastesc && **iptr == ')')
555 break; /* unescaped ) is the end */
556 if (!lastesc && **iptr == '\\') {
557 lastesc = 1;
558 continue; /* skip \ if not escaped */
560 lastesc = 0;
561 cmd[len++] = **iptr;
563 if (**iptr == '\0') /* no terminating ) */ {
564 xfree(cmd);
565 return (NULL);
567 (*iptr)++; /* skip final ) */
568 cmd[len] = '\0';
570 /* First try in the new tree. */
571 so_find.cmd = cmd;
572 so = RB_FIND(status_out_tree, &c->status_new, &so_find);
573 if (so != NULL && so->out != NULL) {
574 xfree(cmd);
575 return (so->out);
578 /* If not found at all, start the job and add to the tree. */
579 if (so == NULL) {
580 job_run(cmd, status_job_callback, status_job_free, c);
581 c->references++;
583 so = xmalloc(sizeof *so);
584 so->cmd = xstrdup(cmd);
585 so->out = NULL;
586 RB_INSERT(status_out_tree, &c->status_new, so);
589 /* Lookup in the old tree. */
590 so_find.cmd = cmd;
591 so = RB_FIND(status_out_tree, &c->status_old, &so_find);
592 xfree(cmd);
593 if (so != NULL)
594 return (so->out);
595 return (NULL);
598 /* Free job tree. */
599 void
600 status_free_jobs(struct status_out_tree *sotree)
602 struct status_out *so, *so_next;
604 so_next = RB_MIN(status_out_tree, sotree);
605 while (so_next != NULL) {
606 so = so_next;
607 so_next = RB_NEXT(status_out_tree, sotree, so);
609 RB_REMOVE(status_out_tree, sotree, so);
610 if (so->out != NULL)
611 xfree(so->out);
612 xfree(so->cmd);
613 xfree(so);
617 /* Update jobs on status interval. */
618 void
619 status_update_jobs(struct client *c)
621 /* Free the old tree. */
622 status_free_jobs(&c->status_old);
624 /* Move the new to old. */
625 memcpy(&c->status_old, &c->status_new, sizeof c->status_old);
626 RB_INIT(&c->status_new);
629 /* Free status job. */
630 void
631 status_job_free(void *data)
633 struct client *c = data;
635 c->references--;
638 /* Job has finished: save its result. */
639 void
640 status_job_callback(struct job *job)
642 struct client *c = job->data;
643 struct status_out *so, so_find;
644 char *line, *buf;
645 size_t len;
647 if (c->flags & CLIENT_DEAD)
648 return;
650 so_find.cmd = job->cmd;
651 so = RB_FIND(status_out_tree, &c->status_new, &so_find);
652 if (so == NULL || so->out != NULL)
653 return;
655 buf = NULL;
656 if ((line = evbuffer_readline(job->event->input)) == NULL) {
657 len = EVBUFFER_LENGTH(job->event->input);
658 buf = xmalloc(len + 1);
659 if (len != 0)
660 memcpy(buf, EVBUFFER_DATA(job->event->input), len);
661 buf[len] = '\0';
662 } else
663 buf = xstrdup(line);
665 so->out = buf;
666 server_status_client(c);
669 /* Return winlink status line entry and adjust gc as necessary. */
670 char *
671 status_print(
672 struct client *c, struct winlink *wl, time_t t, struct grid_cell *gc)
674 struct options *oo = &wl->window->options;
675 struct session *s = c->session;
676 const char *fmt;
677 char *text;
678 u_char fg, bg, attr;
680 fg = options_get_number(oo, "window-status-fg");
681 if (fg != 8)
682 colour_set_fg(gc, fg);
683 bg = options_get_number(oo, "window-status-bg");
684 if (bg != 8)
685 colour_set_bg(gc, bg);
686 attr = options_get_number(oo, "window-status-attr");
687 if (attr != 0)
688 gc->attr = attr;
689 fmt = options_get_string(oo, "window-status-format");
690 if (wl == s->curw) {
691 fg = options_get_number(oo, "window-status-current-fg");
692 if (fg != 8)
693 colour_set_fg(gc, fg);
694 bg = options_get_number(oo, "window-status-current-bg");
695 if (bg != 8)
696 colour_set_bg(gc, bg);
697 attr = options_get_number(oo, "window-status-current-attr");
698 if (attr != 0)
699 gc->attr = attr;
700 fmt = options_get_string(oo, "window-status-current-format");
703 if (wl->flags & WINLINK_BELL) {
704 fg = options_get_number(oo, "window-status-bell-fg");
705 if (fg != 8)
706 colour_set_fg(gc, fg);
707 bg = options_get_number(oo, "window-status-bell-bg");
708 if (bg != 8)
709 colour_set_bg(gc, bg);
710 attr = options_get_number(oo, "window-status-bell-attr");
711 if (attr != 0)
712 gc->attr = attr;
713 } else if (wl->flags & WINLINK_CONTENT) {
714 fg = options_get_number(oo, "window-status-content-fg");
715 if (fg != 8)
716 colour_set_fg(gc, fg);
717 bg = options_get_number(oo, "window-status-content-bg");
718 if (bg != 8)
719 colour_set_bg(gc, bg);
720 attr = options_get_number(oo, "window-status-content-attr");
721 if (attr != 0)
722 gc->attr = attr;
723 } else if (wl->flags & (WINLINK_ACTIVITY|WINLINK_SILENCE)) {
724 fg = options_get_number(oo, "window-status-activity-fg");
725 if (fg != 8)
726 colour_set_fg(gc, fg);
727 bg = options_get_number(oo, "window-status-activity-bg");
728 if (bg != 8)
729 colour_set_bg(gc, bg);
730 attr = options_get_number(oo, "window-status-activity-attr");
731 if (attr != 0)
732 gc->attr = attr;
735 text = status_replace(c, NULL, wl, NULL, fmt, t, 1);
736 return (text);
739 /* Set a status line message. */
740 void printflike2
741 status_message_set(struct client *c, const char *fmt, ...)
743 struct timeval tv;
744 struct session *s = c->session;
745 struct message_entry *msg;
746 va_list ap;
747 int delay;
748 u_int i, limit;
750 status_prompt_clear(c);
751 status_message_clear(c);
753 va_start(ap, fmt);
754 xvasprintf(&c->message_string, fmt, ap);
755 va_end(ap);
757 ARRAY_EXPAND(&c->message_log, 1);
758 msg = &ARRAY_LAST(&c->message_log);
759 msg->msg_time = time(NULL);
760 msg->msg = xstrdup(c->message_string);
762 if (s == NULL)
763 limit = 0;
764 else
765 limit = options_get_number(&s->options, "message-limit");
766 if (ARRAY_LENGTH(&c->message_log) > limit) {
767 limit = ARRAY_LENGTH(&c->message_log) - limit;
768 for (i = 0; i < limit; i++) {
769 msg = &ARRAY_FIRST(&c->message_log);
770 xfree(msg->msg);
771 ARRAY_REMOVE(&c->message_log, 0);
775 delay = options_get_number(&c->session->options, "display-time");
776 tv.tv_sec = delay / 1000;
777 tv.tv_usec = (delay % 1000) * 1000L;
779 evtimer_del(&c->message_timer);
780 evtimer_set(&c->message_timer, status_message_callback, c);
781 evtimer_add(&c->message_timer, &tv);
783 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
784 c->flags |= CLIENT_STATUS;
787 /* Clear status line message. */
788 void
789 status_message_clear(struct client *c)
791 if (c->message_string == NULL)
792 return;
794 xfree(c->message_string);
795 c->message_string = NULL;
797 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
798 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
800 screen_reinit(&c->status);
803 /* Clear status line message after timer expires. */
804 /* ARGSUSED */
805 void
806 status_message_callback(unused int fd, unused short event, void *data)
808 struct client *c = data;
810 status_message_clear(c);
813 /* Draw client message on status line of present else on last line. */
815 status_message_redraw(struct client *c)
817 struct screen_write_ctx ctx;
818 struct session *s = c->session;
819 struct screen old_status;
820 size_t len;
821 struct grid_cell gc;
822 int utf8flag;
824 if (c->tty.sx == 0 || c->tty.sy == 0)
825 return (0);
826 memcpy(&old_status, &c->status, sizeof old_status);
827 screen_init(&c->status, c->tty.sx, 1, 0);
829 utf8flag = options_get_number(&s->options, "status-utf8");
831 len = screen_write_strlen(utf8flag, "%s", c->message_string);
832 if (len > c->tty.sx)
833 len = c->tty.sx;
835 memcpy(&gc, &grid_default_cell, sizeof gc);
836 colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
837 colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
838 gc.attr |= options_get_number(&s->options, "message-attr");
840 screen_write_start(&ctx, NULL, &c->status);
842 screen_write_cursormove(&ctx, 0, 0);
843 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->message_string);
844 for (; len < c->tty.sx; len++)
845 screen_write_putc(&ctx, &gc, ' ');
847 screen_write_stop(&ctx);
849 if (grid_compare(c->status.grid, old_status.grid) == 0) {
850 screen_free(&old_status);
851 return (0);
853 screen_free(&old_status);
854 return (1);
857 /* Enable status line prompt. */
858 void
859 status_prompt_set(struct client *c, const char *msg, const char *input,
860 int (*callbackfn)(void *, const char *), void (*freefn)(void *),
861 void *data, int flags)
863 int keys;
865 status_message_clear(c);
866 status_prompt_clear(c);
868 c->prompt_string = status_replace(c, NULL, NULL, NULL, msg,
869 time(NULL), 0);
871 if (input == NULL)
872 input = "";
873 c->prompt_buffer = status_replace(c, NULL, NULL, NULL, input,
874 time(NULL), 0);
875 c->prompt_index = strlen(c->prompt_buffer);
877 c->prompt_callbackfn = callbackfn;
878 c->prompt_freefn = freefn;
879 c->prompt_data = data;
881 c->prompt_hindex = 0;
883 c->prompt_flags = flags;
885 keys = options_get_number(&c->session->options, "status-keys");
886 if (keys == MODEKEY_EMACS)
887 mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit);
888 else
889 mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit);
891 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
892 c->flags |= CLIENT_STATUS;
895 /* Remove status line prompt. */
896 void
897 status_prompt_clear(struct client *c)
899 if (c->prompt_string == NULL)
900 return;
902 if (c->prompt_freefn != NULL && c->prompt_data != NULL)
903 c->prompt_freefn(c->prompt_data);
905 xfree(c->prompt_string);
906 c->prompt_string = NULL;
908 xfree(c->prompt_buffer);
909 c->prompt_buffer = NULL;
911 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
912 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
914 screen_reinit(&c->status);
917 /* Update status line prompt with a new prompt string. */
918 void
919 status_prompt_update(struct client *c, const char *msg, const char *input)
921 xfree(c->prompt_string);
922 c->prompt_string = status_replace(c, NULL, NULL, NULL, msg,
923 time(NULL), 0);
925 xfree(c->prompt_buffer);
926 if (input == NULL)
927 input = "";
928 c->prompt_buffer = status_replace(c, NULL, NULL, NULL, input,
929 time(NULL), 0);
930 c->prompt_index = strlen(c->prompt_buffer);
932 c->prompt_hindex = 0;
934 c->flags |= CLIENT_STATUS;
937 /* Draw client prompt on status line of present else on last line. */
939 status_prompt_redraw(struct client *c)
941 struct screen_write_ctx ctx;
942 struct session *s = c->session;
943 struct screen old_status;
944 size_t i, size, left, len, off;
945 struct grid_cell gc, *gcp;
946 int utf8flag;
948 if (c->tty.sx == 0 || c->tty.sy == 0)
949 return (0);
950 memcpy(&old_status, &c->status, sizeof old_status);
951 screen_init(&c->status, c->tty.sx, 1, 0);
953 utf8flag = options_get_number(&s->options, "status-utf8");
955 len = screen_write_strlen(utf8flag, "%s", c->prompt_string);
956 if (len > c->tty.sx)
957 len = c->tty.sx;
958 off = 0;
960 memcpy(&gc, &grid_default_cell, sizeof gc);
961 /* Change colours for command mode. */
962 if (c->prompt_mdata.mode == 1) {
963 colour_set_fg(&gc, options_get_number(&s->options, "message-command-fg"));
964 colour_set_bg(&gc, options_get_number(&s->options, "message-command-bg"));
965 gc.attr |= options_get_number(&s->options, "message-command-attr");
966 } else {
967 colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
968 colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
969 gc.attr |= options_get_number(&s->options, "message-attr");
972 screen_write_start(&ctx, NULL, &c->status);
974 screen_write_cursormove(&ctx, 0, 0);
975 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->prompt_string);
977 left = c->tty.sx - len;
978 if (left != 0) {
979 size = screen_write_strlen(utf8flag, "%s", c->prompt_buffer);
980 if (c->prompt_index >= left) {
981 off = c->prompt_index - left + 1;
982 if (c->prompt_index == size)
983 left--;
984 size = left;
986 screen_write_nputs(
987 &ctx, left, &gc, utf8flag, "%s", c->prompt_buffer + off);
989 for (i = len + size; i < c->tty.sx; i++)
990 screen_write_putc(&ctx, &gc, ' ');
993 screen_write_stop(&ctx);
995 /* Apply fake cursor. */
996 off = len + c->prompt_index - off;
997 gcp = grid_view_get_cell(c->status.grid, off, 0);
998 gcp->attr ^= GRID_ATTR_REVERSE;
1000 if (grid_compare(c->status.grid, old_status.grid) == 0) {
1001 screen_free(&old_status);
1002 return (0);
1004 screen_free(&old_status);
1005 return (1);
1008 /* Handle keys in prompt. */
1009 void
1010 status_prompt_key(struct client *c, int key)
1012 struct session *sess = c->session;
1013 struct options *oo = &sess->options;
1014 struct paste_buffer *pb;
1015 char *s, *first, *last, word[64], swapc;
1016 const char *histstr;
1017 const char *wsep = NULL;
1018 u_char ch;
1019 size_t size, n, off, idx;
1021 size = strlen(c->prompt_buffer);
1022 switch (mode_key_lookup(&c->prompt_mdata, key)) {
1023 case MODEKEYEDIT_CURSORLEFT:
1024 if (c->prompt_index > 0) {
1025 c->prompt_index--;
1026 c->flags |= CLIENT_STATUS;
1028 break;
1029 case MODEKEYEDIT_SWITCHMODE:
1030 c->flags |= CLIENT_STATUS;
1031 break;
1032 case MODEKEYEDIT_SWITCHMODEAPPEND:
1033 c->flags |= CLIENT_STATUS;
1034 /* FALLTHROUGH */
1035 case MODEKEYEDIT_CURSORRIGHT:
1036 if (c->prompt_index < size) {
1037 c->prompt_index++;
1038 c->flags |= CLIENT_STATUS;
1040 break;
1041 case MODEKEYEDIT_STARTOFLINE:
1042 if (c->prompt_index != 0) {
1043 c->prompt_index = 0;
1044 c->flags |= CLIENT_STATUS;
1046 break;
1047 case MODEKEYEDIT_ENDOFLINE:
1048 if (c->prompt_index != size) {
1049 c->prompt_index = size;
1050 c->flags |= CLIENT_STATUS;
1052 break;
1053 case MODEKEYEDIT_COMPLETE:
1054 if (*c->prompt_buffer == '\0')
1055 break;
1057 idx = c->prompt_index;
1058 if (idx != 0)
1059 idx--;
1061 /* Find the word we are in. */
1062 first = c->prompt_buffer + idx;
1063 while (first > c->prompt_buffer && *first != ' ')
1064 first--;
1065 while (*first == ' ')
1066 first++;
1067 last = c->prompt_buffer + idx;
1068 while (*last != '\0' && *last != ' ')
1069 last++;
1070 while (*last == ' ')
1071 last--;
1072 if (*last != '\0')
1073 last++;
1074 if (last <= first ||
1075 ((size_t) (last - first)) > (sizeof word) - 1)
1076 break;
1077 memcpy(word, first, last - first);
1078 word[last - first] = '\0';
1080 /* And try to complete it. */
1081 if ((s = status_prompt_complete(word)) == NULL)
1082 break;
1084 /* Trim out word. */
1085 n = size - (last - c->prompt_buffer) + 1; /* with \0 */
1086 memmove(first, last, n);
1087 size -= last - first;
1089 /* Insert the new word. */
1090 size += strlen(s);
1091 off = first - c->prompt_buffer;
1092 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1);
1093 first = c->prompt_buffer + off;
1094 memmove(first + strlen(s), first, n);
1095 memcpy(first, s, strlen(s));
1097 c->prompt_index = (first - c->prompt_buffer) + strlen(s);
1098 xfree(s);
1100 c->flags |= CLIENT_STATUS;
1101 break;
1102 case MODEKEYEDIT_BACKSPACE:
1103 if (c->prompt_index != 0) {
1104 if (c->prompt_index == size)
1105 c->prompt_buffer[--c->prompt_index] = '\0';
1106 else {
1107 memmove(c->prompt_buffer + c->prompt_index - 1,
1108 c->prompt_buffer + c->prompt_index,
1109 size + 1 - c->prompt_index);
1110 c->prompt_index--;
1112 c->flags |= CLIENT_STATUS;
1114 break;
1115 case MODEKEYEDIT_DELETE:
1116 if (c->prompt_index != size) {
1117 memmove(c->prompt_buffer + c->prompt_index,
1118 c->prompt_buffer + c->prompt_index + 1,
1119 size + 1 - c->prompt_index);
1120 c->flags |= CLIENT_STATUS;
1122 break;
1123 case MODEKEYEDIT_DELETELINE:
1124 *c->prompt_buffer = '\0';
1125 c->prompt_index = 0;
1126 c->flags |= CLIENT_STATUS;
1127 break;
1128 case MODEKEYEDIT_DELETETOENDOFLINE:
1129 if (c->prompt_index < size) {
1130 c->prompt_buffer[c->prompt_index] = '\0';
1131 c->flags |= CLIENT_STATUS;
1133 break;
1134 case MODEKEYEDIT_DELETEWORD:
1135 wsep = options_get_string(oo, "word-separators");
1136 idx = c->prompt_index;
1138 /* Find a non-separator. */
1139 while (idx != 0) {
1140 idx--;
1141 if (!strchr(wsep, c->prompt_buffer[idx]))
1142 break;
1145 /* Find the separator at the beginning of the word. */
1146 while (idx != 0) {
1147 idx--;
1148 if (strchr(wsep, c->prompt_buffer[idx])) {
1149 /* Go back to the word. */
1150 idx++;
1151 break;
1155 memmove(c->prompt_buffer + idx,
1156 c->prompt_buffer + c->prompt_index,
1157 size + 1 - c->prompt_index);
1158 memset(c->prompt_buffer + size - (c->prompt_index - idx),
1159 '\0', c->prompt_index - idx);
1160 c->prompt_index = idx;
1161 c->flags |= CLIENT_STATUS;
1162 break;
1163 case MODEKEYEDIT_NEXTSPACE:
1164 wsep = " ";
1165 /* FALLTHROUGH */
1166 case MODEKEYEDIT_NEXTWORD:
1167 if (wsep == NULL)
1168 wsep = options_get_string(oo, "word-separators");
1170 /* Find a separator. */
1171 while (c->prompt_index != size) {
1172 c->prompt_index++;
1173 if (strchr(wsep, c->prompt_buffer[c->prompt_index]))
1174 break;
1177 /* Find the word right after the separation. */
1178 while (c->prompt_index != size) {
1179 c->prompt_index++;
1180 if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1181 break;
1184 c->flags |= CLIENT_STATUS;
1185 break;
1186 case MODEKEYEDIT_NEXTSPACEEND:
1187 wsep = " ";
1188 /* FALLTHROUGH */
1189 case MODEKEYEDIT_NEXTWORDEND:
1190 if (wsep == NULL)
1191 wsep = options_get_string(oo, "word-separators");
1193 /* Find a word. */
1194 while (c->prompt_index != size) {
1195 c->prompt_index++;
1196 if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1197 break;
1200 /* Find the separator at the end of the word. */
1201 while (c->prompt_index != size) {
1202 c->prompt_index++;
1203 if (strchr(wsep, c->prompt_buffer[c->prompt_index]))
1204 break;
1207 c->flags |= CLIENT_STATUS;
1208 break;
1209 case MODEKEYEDIT_PREVIOUSSPACE:
1210 wsep = " ";
1211 /* FALLTHROUGH */
1212 case MODEKEYEDIT_PREVIOUSWORD:
1213 if (wsep == NULL)
1214 wsep = options_get_string(oo, "word-separators");
1216 /* Find a non-separator. */
1217 while (c->prompt_index != 0) {
1218 c->prompt_index--;
1219 if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1220 break;
1223 /* Find the separator at the beginning of the word. */
1224 while (c->prompt_index != 0) {
1225 c->prompt_index--;
1226 if (strchr(wsep, c->prompt_buffer[c->prompt_index])) {
1227 /* Go back to the word. */
1228 c->prompt_index++;
1229 break;
1233 c->flags |= CLIENT_STATUS;
1234 break;
1235 case MODEKEYEDIT_HISTORYUP:
1236 histstr = status_prompt_up_history(&c->prompt_hindex);
1237 if (histstr == NULL)
1238 break;
1239 xfree(c->prompt_buffer);
1240 c->prompt_buffer = xstrdup(histstr);
1241 c->prompt_index = strlen(c->prompt_buffer);
1242 c->flags |= CLIENT_STATUS;
1243 break;
1244 case MODEKEYEDIT_HISTORYDOWN:
1245 histstr = status_prompt_down_history(&c->prompt_hindex);
1246 if (histstr == NULL)
1247 break;
1248 xfree(c->prompt_buffer);
1249 c->prompt_buffer = xstrdup(histstr);
1250 c->prompt_index = strlen(c->prompt_buffer);
1251 c->flags |= CLIENT_STATUS;
1252 break;
1253 case MODEKEYEDIT_PASTE:
1254 if ((pb = paste_get_top(&global_buffers)) == NULL)
1255 break;
1256 for (n = 0; n < pb->size; n++) {
1257 ch = (u_char) pb->data[n];
1258 if (ch < 32 || ch == 127)
1259 break;
1262 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1);
1263 if (c->prompt_index == size) {
1264 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1265 c->prompt_index += n;
1266 c->prompt_buffer[c->prompt_index] = '\0';
1267 } else {
1268 memmove(c->prompt_buffer + c->prompt_index + n,
1269 c->prompt_buffer + c->prompt_index,
1270 size + 1 - c->prompt_index);
1271 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1272 c->prompt_index += n;
1275 c->flags |= CLIENT_STATUS;
1276 break;
1277 case MODEKEYEDIT_TRANSPOSECHARS:
1278 idx = c->prompt_index;
1279 if (idx < size)
1280 idx++;
1281 if (idx >= 2) {
1282 swapc = c->prompt_buffer[idx - 2];
1283 c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1];
1284 c->prompt_buffer[idx - 1] = swapc;
1285 c->prompt_index = idx;
1286 c->flags |= CLIENT_STATUS;
1288 break;
1289 case MODEKEYEDIT_ENTER:
1290 if (*c->prompt_buffer != '\0')
1291 status_prompt_add_history(c->prompt_buffer);
1292 if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0)
1293 status_prompt_clear(c);
1294 break;
1295 case MODEKEYEDIT_CANCEL:
1296 if (c->prompt_callbackfn(c->prompt_data, NULL) == 0)
1297 status_prompt_clear(c);
1298 break;
1299 case MODEKEY_OTHER:
1300 if ((key & 0xff00) != 0 || key < 32 || key == 127)
1301 break;
1302 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2);
1304 if (c->prompt_index == size) {
1305 c->prompt_buffer[c->prompt_index++] = key;
1306 c->prompt_buffer[c->prompt_index] = '\0';
1307 } else {
1308 memmove(c->prompt_buffer + c->prompt_index + 1,
1309 c->prompt_buffer + c->prompt_index,
1310 size + 1 - c->prompt_index);
1311 c->prompt_buffer[c->prompt_index++] = key;
1314 if (c->prompt_flags & PROMPT_SINGLE) {
1315 if (c->prompt_callbackfn(
1316 c->prompt_data, c->prompt_buffer) == 0)
1317 status_prompt_clear(c);
1320 c->flags |= CLIENT_STATUS;
1321 break;
1322 default:
1323 break;
1327 /* Get previous line from the history. */
1328 const char *
1329 status_prompt_up_history(u_int *idx)
1331 u_int size;
1334 * History runs from 0 to size - 1.
1336 * Index is from 0 to size. Zero is empty.
1339 size = ARRAY_LENGTH(&status_prompt_history);
1340 if (size == 0 || *idx == size)
1341 return (NULL);
1342 (*idx)++;
1343 return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1346 /* Get next line from the history. */
1347 const char *
1348 status_prompt_down_history(u_int *idx)
1350 u_int size;
1352 size = ARRAY_LENGTH(&status_prompt_history);
1353 if (size == 0 || *idx == 0)
1354 return ("");
1355 (*idx)--;
1356 if (*idx == 0)
1357 return ("");
1358 return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1361 /* Add line to the history. */
1362 void
1363 status_prompt_add_history(const char *line)
1365 u_int size;
1367 size = ARRAY_LENGTH(&status_prompt_history);
1368 if (size > 0 && strcmp(ARRAY_LAST(&status_prompt_history), line) == 0)
1369 return;
1371 if (size == PROMPT_HISTORY) {
1372 xfree(ARRAY_FIRST(&status_prompt_history));
1373 ARRAY_REMOVE(&status_prompt_history, 0);
1376 ARRAY_ADD(&status_prompt_history, xstrdup(line));
1379 /* Complete word. */
1380 char *
1381 status_prompt_complete(const char *s)
1383 const struct cmd_entry **cmdent;
1384 const struct options_table_entry *oe;
1385 ARRAY_DECL(, const char *) list;
1386 char *prefix, *s2;
1387 u_int i;
1388 size_t j;
1390 if (*s == '\0')
1391 return (NULL);
1393 /* First, build a list of all the possible matches. */
1394 ARRAY_INIT(&list);
1395 for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1396 if (strncmp((*cmdent)->name, s, strlen(s)) == 0)
1397 ARRAY_ADD(&list, (*cmdent)->name);
1399 for (oe = server_options_table; oe->name != NULL; oe++) {
1400 if (strncmp(oe->name, s, strlen(s)) == 0)
1401 ARRAY_ADD(&list, oe->name);
1403 for (oe = session_options_table; oe->name != NULL; oe++) {
1404 if (strncmp(oe->name, s, strlen(s)) == 0)
1405 ARRAY_ADD(&list, oe->name);
1407 for (oe = window_options_table; oe->name != NULL; oe++) {
1408 if (strncmp(oe->name, s, strlen(s)) == 0)
1409 ARRAY_ADD(&list, oe->name);
1412 /* If none, bail now. */
1413 if (ARRAY_LENGTH(&list) == 0) {
1414 ARRAY_FREE(&list);
1415 return (NULL);
1418 /* If an exact match, return it, with a trailing space. */
1419 if (ARRAY_LENGTH(&list) == 1) {
1420 xasprintf(&s2, "%s ", ARRAY_FIRST(&list));
1421 ARRAY_FREE(&list);
1422 return (s2);
1425 /* Now loop through the list and find the longest common prefix. */
1426 prefix = xstrdup(ARRAY_FIRST(&list));
1427 for (i = 1; i < ARRAY_LENGTH(&list); i++) {
1428 s = ARRAY_ITEM(&list, i);
1430 j = strlen(s);
1431 if (j > strlen(prefix))
1432 j = strlen(prefix);
1433 for (; j > 0; j--) {
1434 if (prefix[j - 1] != s[j - 1])
1435 prefix[j - 1] = '\0';
1439 ARRAY_FREE(&list);
1440 return (prefix);