Terminate strftime buffer properly even if a really long format string
[tmux-openbsd.git] / status.c
blobae05853fdd905784274047077e74d781c2af9b3f
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 /* Retrieve options for left string. */
64 char *
65 status_redraw_get_left(struct client *c,
66 time_t t, int utf8flag, struct grid_cell *gc, size_t *size)
68 struct session *s = c->session;
69 char *left;
70 u_char fg, bg, attr;
71 size_t leftlen;
73 fg = options_get_number(&s->options, "status-left-fg");
74 if (fg != 8)
75 colour_set_fg(gc, fg);
76 bg = options_get_number(&s->options, "status-left-bg");
77 if (bg != 8)
78 colour_set_bg(gc, bg);
79 attr = options_get_number(&s->options, "status-left-attr");
80 if (attr != 0)
81 gc->attr = attr;
83 left = status_replace(c, NULL,
84 NULL, NULL, options_get_string(&s->options, "status-left"), t, 1);
86 *size = options_get_number(&s->options, "status-left-length");
87 leftlen = screen_write_cstrlen(utf8flag, "%s", left);
88 if (leftlen < *size)
89 *size = leftlen;
90 return (left);
93 /* Retrieve options for right string. */
94 char *
95 status_redraw_get_right(struct client *c,
96 time_t t, int utf8flag, struct grid_cell *gc, size_t *size)
98 struct session *s = c->session;
99 char *right;
100 u_char fg, bg, attr;
101 size_t rightlen;
103 fg = options_get_number(&s->options, "status-right-fg");
104 if (fg != 8)
105 colour_set_fg(gc, fg);
106 bg = options_get_number(&s->options, "status-right-bg");
107 if (bg != 8)
108 colour_set_bg(gc, bg);
109 attr = options_get_number(&s->options, "status-right-attr");
110 if (attr != 0)
111 gc->attr = attr;
113 right = status_replace(c, NULL,
114 NULL, NULL, options_get_string(&s->options, "status-right"), t, 1);
116 *size = options_get_number(&s->options, "status-right-length");
117 rightlen = screen_write_cstrlen(utf8flag, "%s", right);
118 if (rightlen < *size)
119 *size = rightlen;
120 return (right);
123 /* Set window at window list position. */
124 void
125 status_set_window_at(struct client *c, u_int x)
127 struct session *s = c->session;
128 struct winlink *wl;
130 x += s->wlmouse;
131 RB_FOREACH(wl, winlinks, &s->windows) {
132 if (x < wl->status_width &&
133 session_select(s, wl->idx) == 0) {
134 server_redraw_session(s);
136 x -= wl->status_width + 1;
140 /* Draw status for client on the last lines of given context. */
142 status_redraw(struct client *c)
144 struct screen_write_ctx ctx;
145 struct session *s = c->session;
146 struct winlink *wl;
147 struct screen old_status, window_list;
148 struct grid_cell stdgc, lgc, rgc, gc;
149 time_t t;
150 char *left, *right;
151 u_int offset, needed;
152 u_int wlstart, wlwidth, wlavailable, wloffset, wlsize;
153 size_t llen, rlen;
154 int larrow, rarrow, utf8flag;
156 /* No status line? */
157 if (c->tty.sy == 0 || !options_get_number(&s->options, "status"))
158 return (1);
159 left = right = NULL;
160 larrow = rarrow = 0;
162 /* Update status timer. */
163 if (gettimeofday(&c->status_timer, NULL) != 0)
164 fatal("gettimeofday failed");
165 t = c->status_timer.tv_sec;
167 /* Set up default colour. */
168 memcpy(&stdgc, &grid_default_cell, sizeof gc);
169 colour_set_fg(&stdgc, options_get_number(&s->options, "status-fg"));
170 colour_set_bg(&stdgc, options_get_number(&s->options, "status-bg"));
171 stdgc.attr |= options_get_number(&s->options, "status-attr");
173 /* Create the target screen. */
174 memcpy(&old_status, &c->status, sizeof old_status);
175 screen_init(&c->status, c->tty.sx, 1, 0);
176 screen_write_start(&ctx, NULL, &c->status);
177 for (offset = 0; offset < c->tty.sx; offset++)
178 screen_write_putc(&ctx, &stdgc, ' ');
179 screen_write_stop(&ctx);
181 /* If the height is one line, blank status line. */
182 if (c->tty.sy <= 1)
183 goto out;
185 /* Get UTF-8 flag. */
186 utf8flag = options_get_number(&s->options, "status-utf8");
188 /* Work out left and right strings. */
189 memcpy(&lgc, &stdgc, sizeof lgc);
190 left = status_redraw_get_left(c, t, utf8flag, &lgc, &llen);
191 memcpy(&rgc, &stdgc, sizeof rgc);
192 right = status_redraw_get_right(c, t, utf8flag, &rgc, &rlen);
195 * Figure out how much space we have for the window list. If there
196 * isn't enough space, just show a blank status line.
198 needed = 0;
199 if (llen != 0)
200 needed += llen + 1;
201 if (rlen != 0)
202 needed += rlen + 1;
203 if (c->tty.sx == 0 || c->tty.sx <= needed)
204 goto out;
205 wlavailable = c->tty.sx - needed;
207 /* Calculate the total size needed for the window list. */
208 wlstart = wloffset = wlwidth = 0;
209 RB_FOREACH(wl, winlinks, &s->windows) {
210 if (wl->status_text != NULL)
211 xfree(wl->status_text);
212 memcpy(&wl->status_cell, &stdgc, sizeof wl->status_cell);
213 wl->status_text = status_print(c, wl, t, &wl->status_cell);
214 wl->status_width =
215 screen_write_cstrlen(utf8flag, "%s", wl->status_text);
217 if (wl == s->curw)
218 wloffset = wlwidth;
219 wlwidth += wl->status_width + 1;
222 /* Create a new screen for the window list. */
223 screen_init(&window_list, wlwidth, 1, 0);
225 /* And draw the window list into it. */
226 screen_write_start(&ctx, NULL, &window_list);
227 RB_FOREACH(wl, winlinks, &s->windows) {
228 screen_write_cnputs(&ctx,
229 -1, &wl->status_cell, utf8flag, "%s", wl->status_text);
230 screen_write_putc(&ctx, &stdgc, ' ');
232 screen_write_stop(&ctx);
234 /* If there is enough space for the total width, skip to draw now. */
235 if (wlwidth <= wlavailable)
236 goto draw;
238 /* Find size of current window text. */
239 wlsize = s->curw->status_width;
242 * If the current window is already on screen, good to draw from the
243 * start and just leave off the end.
245 if (wloffset + wlsize < wlavailable) {
246 if (wlavailable > 0) {
247 rarrow = 1;
248 wlavailable--;
250 wlwidth = wlavailable;
251 } else {
253 * Work out how many characters we need to omit from the
254 * start. There are wlavailable characters to fill, and
255 * wloffset + wlsize must be the last. So, the start character
256 * is wloffset + wlsize - wlavailable.
258 if (wlavailable > 0) {
259 larrow = 1;
260 wlavailable--;
263 wlstart = wloffset + wlsize - wlavailable;
264 if (wlavailable > 0 && wlwidth > wlstart + wlavailable + 1) {
265 rarrow = 1;
266 wlstart++;
267 wlavailable--;
269 wlwidth = wlavailable;
272 /* Bail if anything is now too small too. */
273 if (wlwidth == 0 || wlavailable == 0) {
274 screen_free(&window_list);
275 goto out;
279 * Now the start position is known, work out the state of the left and
280 * right arrows.
282 offset = 0;
283 RB_FOREACH(wl, winlinks, &s->windows) {
284 if (wl->flags & WINLINK_ALERTFLAGS &&
285 larrow == 1 && offset < wlstart)
286 larrow = -1;
288 offset += wl->status_width;
290 if (wl->flags & WINLINK_ALERTFLAGS &&
291 rarrow == 1 && offset > wlstart + wlwidth)
292 rarrow = -1;
295 draw:
296 /* Begin drawing. */
297 screen_write_start(&ctx, NULL, &c->status);
299 /* Draw the left string and arrow. */
300 screen_write_cursormove(&ctx, 0, 0);
301 if (llen != 0) {
302 screen_write_cnputs(&ctx, llen, &lgc, utf8flag, "%s", left);
303 screen_write_putc(&ctx, &stdgc, ' ');
305 if (larrow != 0) {
306 memcpy(&gc, &stdgc, sizeof gc);
307 if (larrow == -1)
308 gc.attr ^= GRID_ATTR_REVERSE;
309 screen_write_putc(&ctx, &gc, '<');
312 /* Draw the right string and arrow. */
313 if (rarrow != 0) {
314 screen_write_cursormove(&ctx, c->tty.sx - rlen - 2, 0);
315 memcpy(&gc, &stdgc, sizeof gc);
316 if (rarrow == -1)
317 gc.attr ^= GRID_ATTR_REVERSE;
318 screen_write_putc(&ctx, &gc, '>');
319 } else
320 screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, 0);
321 if (rlen != 0) {
322 screen_write_putc(&ctx, &stdgc, ' ');
323 screen_write_cnputs(&ctx, rlen, &rgc, utf8flag, "%s", right);
326 /* Figure out the offset for the window list. */
327 if (llen != 0)
328 wloffset = llen + 1;
329 else
330 wloffset = 0;
331 if (wlwidth < wlavailable) {
332 switch (options_get_number(&s->options, "status-justify")) {
333 case 1: /* centered */
334 wloffset += (wlavailable - wlwidth) / 2;
335 break;
336 case 2: /* right */
337 wloffset += (wlavailable - wlwidth);
338 break;
341 if (larrow != 0)
342 wloffset++;
344 /* Copy the window list. */
345 s->wlmouse = -wloffset + wlstart;
346 screen_write_cursormove(&ctx, wloffset, 0);
347 screen_write_copy(&ctx, &window_list, wlstart, 0, wlwidth, 1);
348 screen_free(&window_list);
350 screen_write_stop(&ctx);
352 out:
353 if (left != NULL)
354 xfree(left);
355 if (right != NULL)
356 xfree(right);
358 if (grid_compare(c->status.grid, old_status.grid) == 0) {
359 screen_free(&old_status);
360 return (0);
362 screen_free(&old_status);
363 return (1);
366 /* Replace a single special sequence (prefixed by #). */
367 void
368 status_replace1(struct client *c, struct session *s, struct winlink *wl,
369 struct window_pane *wp, char **iptr, char **optr, char *out,
370 size_t outsize, int jobsflag)
372 char ch, tmp[256], *ptr, *endptr, *freeptr;
373 size_t ptrlen;
374 long limit;
375 u_int idx;
377 if (s == NULL)
378 s = c->session;
379 if (wl == NULL)
380 wl = s->curw;
381 if (wp == NULL)
382 wp = wl->window->active;
384 errno = 0;
385 limit = strtol(*iptr, &endptr, 10);
386 if ((limit == 0 && errno != EINVAL) ||
387 (limit == LONG_MIN && errno != ERANGE) ||
388 (limit == LONG_MAX && errno != ERANGE) ||
389 limit != 0)
390 *iptr = endptr;
391 if (limit <= 0)
392 limit = LONG_MAX;
394 freeptr = NULL;
396 switch (*(*iptr)++) {
397 case '(':
398 if (!jobsflag) {
399 ch = ')';
400 goto skip_to;
402 if ((ptr = status_find_job(c, iptr)) == NULL)
403 return;
404 goto do_replace;
405 case 'D':
406 xsnprintf(tmp, sizeof tmp, "%%%u", wp->id);
407 ptr = tmp;
408 goto do_replace;
409 case 'H':
410 if (gethostname(tmp, sizeof tmp) != 0)
411 fatal("gethostname failed");
412 ptr = tmp;
413 goto do_replace;
414 case 'h':
415 if (gethostname(tmp, sizeof tmp) != 0)
416 fatal("gethostname failed");
417 if ((ptr = strchr(tmp, '.')) != NULL)
418 *ptr = '\0';
419 ptr = tmp;
420 goto do_replace;
421 case 'I':
422 xsnprintf(tmp, sizeof tmp, "%d", wl->idx);
423 ptr = tmp;
424 goto do_replace;
425 case 'P':
426 if (window_pane_index(wp, &idx) != 0)
427 fatalx("index not found");
428 xsnprintf(
429 tmp, sizeof tmp, "%u", idx);
430 ptr = tmp;
431 goto do_replace;
432 case 'S':
433 ptr = s->name;
434 goto do_replace;
435 case 'T':
436 ptr = wp->base.title;
437 goto do_replace;
438 case 'W':
439 ptr = wl->window->name;
440 goto do_replace;
441 case 'F':
442 ptr = window_printable_flags(s, wl);
443 freeptr = ptr;
444 goto do_replace;
445 case '[':
447 * Embedded style, handled at display time. Leave present and
448 * skip input until ].
450 ch = ']';
451 goto skip_to;
452 case '#':
453 *(*optr)++ = '#';
454 break;
457 return;
459 do_replace:
460 ptrlen = strlen(ptr);
461 if ((size_t) limit < ptrlen)
462 ptrlen = limit;
464 if (*optr + ptrlen >= out + outsize - 1)
465 goto out;
466 while (ptrlen > 0 && *ptr != '\0') {
467 *(*optr)++ = *ptr++;
468 ptrlen--;
471 out:
472 if (freeptr != NULL)
473 xfree(freeptr);
474 return;
476 skip_to:
477 *(*optr)++ = '#';
479 (*iptr)--; /* include ch */
480 while (**iptr != ch && **iptr != '\0') {
481 if (*optr >= out + outsize - 1)
482 break;
483 *(*optr)++ = *(*iptr)++;
487 /* Replace special sequences in fmt. */
488 char *
489 status_replace(struct client *c, struct session *s, struct winlink *wl,
490 struct window_pane *wp, const char *fmt, time_t t, int jobsflag)
492 static char out[BUFSIZ];
493 char in[BUFSIZ], ch, *iptr, *optr;
494 size_t len;
496 len = strftime(in, sizeof in, fmt, localtime(&t));
497 in[len] = '\0';
499 iptr = in;
500 optr = out;
502 while (*iptr != '\0') {
503 if (optr >= out + (sizeof out) - 1)
504 break;
505 ch = *iptr++;
507 if (ch != '#' || *iptr == '\0') {
508 *optr++ = ch;
509 continue;
511 status_replace1(
512 c, s, wl, wp, &iptr, &optr, out, sizeof out, jobsflag);
514 *optr = '\0';
516 return (xstrdup(out));
519 /* Figure out job name and get its result, starting it off if necessary. */
520 char *
521 status_find_job(struct client *c, char **iptr)
523 struct status_out *so, so_find;
524 char *cmd;
525 int lastesc;
526 size_t len;
528 if (**iptr == '\0')
529 return (NULL);
530 if (**iptr == ')') { /* no command given */
531 (*iptr)++;
532 return (NULL);
535 cmd = xmalloc(strlen(*iptr) + 1);
536 len = 0;
538 lastesc = 0;
539 for (; **iptr != '\0'; (*iptr)++) {
540 if (!lastesc && **iptr == ')')
541 break; /* unescaped ) is the end */
542 if (!lastesc && **iptr == '\\') {
543 lastesc = 1;
544 continue; /* skip \ if not escaped */
546 lastesc = 0;
547 cmd[len++] = **iptr;
549 if (**iptr == '\0') /* no terminating ) */ {
550 xfree(cmd);
551 return (NULL);
553 (*iptr)++; /* skip final ) */
554 cmd[len] = '\0';
556 /* First try in the new tree. */
557 so_find.cmd = cmd;
558 so = RB_FIND(status_out_tree, &c->status_new, &so_find);
559 if (so != NULL && so->out != NULL) {
560 xfree(cmd);
561 return (so->out);
564 /* If not found at all, start the job and add to the tree. */
565 if (so == NULL) {
566 job_run(cmd, status_job_callback, status_job_free, c);
567 c->references++;
569 so = xmalloc(sizeof *so);
570 so->cmd = xstrdup(cmd);
571 so->out = NULL;
572 RB_INSERT(status_out_tree, &c->status_new, so);
575 /* Lookup in the old tree. */
576 so_find.cmd = cmd;
577 so = RB_FIND(status_out_tree, &c->status_old, &so_find);
578 xfree(cmd);
579 if (so != NULL)
580 return (so->out);
581 return (NULL);
584 /* Free job tree. */
585 void
586 status_free_jobs(struct status_out_tree *sotree)
588 struct status_out *so, *so_next;
590 so_next = RB_MIN(status_out_tree, sotree);
591 while (so_next != NULL) {
592 so = so_next;
593 so_next = RB_NEXT(status_out_tree, sotree, so);
595 RB_REMOVE(status_out_tree, sotree, so);
596 if (so->out != NULL)
597 xfree(so->out);
598 xfree(so->cmd);
599 xfree(so);
603 /* Update jobs on status interval. */
604 void
605 status_update_jobs(struct client *c)
607 /* Free the old tree. */
608 status_free_jobs(&c->status_old);
610 /* Move the new to old. */
611 memcpy(&c->status_old, &c->status_new, sizeof c->status_old);
612 RB_INIT(&c->status_new);
615 /* Free status job. */
616 void
617 status_job_free(void *data)
619 struct client *c = data;
621 c->references--;
624 /* Job has finished: save its result. */
625 void
626 status_job_callback(struct job *job)
628 struct client *c = job->data;
629 struct status_out *so, so_find;
630 char *line, *buf;
631 size_t len;
633 if (c->flags & CLIENT_DEAD)
634 return;
636 so_find.cmd = job->cmd;
637 so = RB_FIND(status_out_tree, &c->status_new, &so_find);
638 if (so == NULL || so->out != NULL)
639 return;
641 buf = NULL;
642 if ((line = evbuffer_readline(job->event->input)) == NULL) {
643 len = EVBUFFER_LENGTH(job->event->input);
644 buf = xmalloc(len + 1);
645 if (len != 0)
646 memcpy(buf, EVBUFFER_DATA(job->event->input), len);
647 buf[len] = '\0';
648 } else
649 buf = xstrdup(line);
651 so->out = buf;
652 server_status_client(c);
655 /* Return winlink status line entry and adjust gc as necessary. */
656 char *
657 status_print(
658 struct client *c, struct winlink *wl, time_t t, struct grid_cell *gc)
660 struct options *oo = &wl->window->options;
661 struct session *s = c->session;
662 const char *fmt;
663 char *text;
664 u_char fg, bg, attr;
666 fg = options_get_number(oo, "window-status-fg");
667 if (fg != 8)
668 colour_set_fg(gc, fg);
669 bg = options_get_number(oo, "window-status-bg");
670 if (bg != 8)
671 colour_set_bg(gc, bg);
672 attr = options_get_number(oo, "window-status-attr");
673 if (attr != 0)
674 gc->attr = attr;
675 fmt = options_get_string(oo, "window-status-format");
676 if (wl == s->curw) {
677 fg = options_get_number(oo, "window-status-current-fg");
678 if (fg != 8)
679 colour_set_fg(gc, fg);
680 bg = options_get_number(oo, "window-status-current-bg");
681 if (bg != 8)
682 colour_set_bg(gc, bg);
683 attr = options_get_number(oo, "window-status-current-attr");
684 if (attr != 0)
685 gc->attr = attr;
686 fmt = options_get_string(oo, "window-status-current-format");
689 if (wl->flags & WINLINK_BELL) {
690 fg = options_get_number(oo, "window-status-bell-fg");
691 if (fg != 8)
692 colour_set_fg(gc, fg);
693 bg = options_get_number(oo, "window-status-bell-bg");
694 if (bg != 8)
695 colour_set_bg(gc, bg);
696 attr = options_get_number(oo, "window-status-bell-attr");
697 if (attr != 0)
698 gc->attr = attr;
699 } else if (wl->flags & WINLINK_CONTENT) {
700 fg = options_get_number(oo, "window-status-content-fg");
701 if (fg != 8)
702 colour_set_fg(gc, fg);
703 bg = options_get_number(oo, "window-status-content-bg");
704 if (bg != 8)
705 colour_set_bg(gc, bg);
706 attr = options_get_number(oo, "window-status-content-attr");
707 if (attr != 0)
708 gc->attr = attr;
709 } else if (wl->flags & (WINLINK_ACTIVITY|WINLINK_SILENCE)) {
710 fg = options_get_number(oo, "window-status-activity-fg");
711 if (fg != 8)
712 colour_set_fg(gc, fg);
713 bg = options_get_number(oo, "window-status-activity-bg");
714 if (bg != 8)
715 colour_set_bg(gc, bg);
716 attr = options_get_number(oo, "window-status-activity-attr");
717 if (attr != 0)
718 gc->attr = attr;
721 text = status_replace(c, NULL, wl, NULL, fmt, t, 1);
722 return (text);
725 /* Set a status line message. */
726 void printflike2
727 status_message_set(struct client *c, const char *fmt, ...)
729 struct timeval tv;
730 struct session *s = c->session;
731 struct message_entry *msg;
732 va_list ap;
733 int delay;
734 u_int i, limit;
736 status_prompt_clear(c);
737 status_message_clear(c);
739 va_start(ap, fmt);
740 xvasprintf(&c->message_string, fmt, ap);
741 va_end(ap);
743 ARRAY_EXPAND(&c->message_log, 1);
744 msg = &ARRAY_LAST(&c->message_log);
745 msg->msg_time = time(NULL);
746 msg->msg = xstrdup(c->message_string);
748 if (s == NULL)
749 limit = 0;
750 else
751 limit = options_get_number(&s->options, "message-limit");
752 if (ARRAY_LENGTH(&c->message_log) > limit) {
753 limit = ARRAY_LENGTH(&c->message_log) - limit;
754 for (i = 0; i < limit; i++) {
755 msg = &ARRAY_FIRST(&c->message_log);
756 xfree(msg->msg);
757 ARRAY_REMOVE(&c->message_log, 0);
761 delay = options_get_number(&c->session->options, "display-time");
762 tv.tv_sec = delay / 1000;
763 tv.tv_usec = (delay % 1000) * 1000L;
765 evtimer_del(&c->message_timer);
766 evtimer_set(&c->message_timer, status_message_callback, c);
767 evtimer_add(&c->message_timer, &tv);
769 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
770 c->flags |= CLIENT_STATUS;
773 /* Clear status line message. */
774 void
775 status_message_clear(struct client *c)
777 if (c->message_string == NULL)
778 return;
780 xfree(c->message_string);
781 c->message_string = NULL;
783 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
784 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
786 screen_reinit(&c->status);
789 /* Clear status line message after timer expires. */
790 /* ARGSUSED */
791 void
792 status_message_callback(unused int fd, unused short event, void *data)
794 struct client *c = data;
796 status_message_clear(c);
799 /* Draw client message on status line of present else on last line. */
801 status_message_redraw(struct client *c)
803 struct screen_write_ctx ctx;
804 struct session *s = c->session;
805 struct screen old_status;
806 size_t len;
807 struct grid_cell gc;
808 int utf8flag;
810 if (c->tty.sx == 0 || c->tty.sy == 0)
811 return (0);
812 memcpy(&old_status, &c->status, sizeof old_status);
813 screen_init(&c->status, c->tty.sx, 1, 0);
815 utf8flag = options_get_number(&s->options, "status-utf8");
817 len = screen_write_strlen(utf8flag, "%s", c->message_string);
818 if (len > c->tty.sx)
819 len = c->tty.sx;
821 memcpy(&gc, &grid_default_cell, sizeof gc);
822 colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
823 colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
824 gc.attr |= options_get_number(&s->options, "message-attr");
826 screen_write_start(&ctx, NULL, &c->status);
828 screen_write_cursormove(&ctx, 0, 0);
829 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->message_string);
830 for (; len < c->tty.sx; len++)
831 screen_write_putc(&ctx, &gc, ' ');
833 screen_write_stop(&ctx);
835 if (grid_compare(c->status.grid, old_status.grid) == 0) {
836 screen_free(&old_status);
837 return (0);
839 screen_free(&old_status);
840 return (1);
843 /* Enable status line prompt. */
844 void
845 status_prompt_set(struct client *c, const char *msg, const char *input,
846 int (*callbackfn)(void *, const char *), void (*freefn)(void *),
847 void *data, int flags)
849 int keys;
851 status_message_clear(c);
852 status_prompt_clear(c);
854 c->prompt_string = status_replace(c, NULL, NULL, NULL, msg,
855 time(NULL), 0);
857 if (input == NULL)
858 input = "";
859 c->prompt_buffer = status_replace(c, NULL, NULL, NULL, input,
860 time(NULL), 0);
861 c->prompt_index = strlen(c->prompt_buffer);
863 c->prompt_callbackfn = callbackfn;
864 c->prompt_freefn = freefn;
865 c->prompt_data = data;
867 c->prompt_hindex = 0;
869 c->prompt_flags = flags;
871 keys = options_get_number(&c->session->options, "status-keys");
872 if (keys == MODEKEY_EMACS)
873 mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit);
874 else
875 mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit);
877 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
878 c->flags |= CLIENT_STATUS;
881 /* Remove status line prompt. */
882 void
883 status_prompt_clear(struct client *c)
885 if (c->prompt_string == NULL)
886 return;
888 if (c->prompt_freefn != NULL && c->prompt_data != NULL)
889 c->prompt_freefn(c->prompt_data);
891 xfree(c->prompt_string);
892 c->prompt_string = NULL;
894 xfree(c->prompt_buffer);
895 c->prompt_buffer = NULL;
897 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
898 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
900 screen_reinit(&c->status);
903 /* Update status line prompt with a new prompt string. */
904 void
905 status_prompt_update(struct client *c, const char *msg, const char *input)
907 xfree(c->prompt_string);
908 c->prompt_string = status_replace(c, NULL, NULL, NULL, msg,
909 time(NULL), 0);
911 xfree(c->prompt_buffer);
912 if (input == NULL)
913 input = "";
914 c->prompt_buffer = status_replace(c, NULL, NULL, NULL, input,
915 time(NULL), 0);
916 c->prompt_index = strlen(c->prompt_buffer);
918 c->prompt_hindex = 0;
920 c->flags |= CLIENT_STATUS;
923 /* Draw client prompt on status line of present else on last line. */
925 status_prompt_redraw(struct client *c)
927 struct screen_write_ctx ctx;
928 struct session *s = c->session;
929 struct screen old_status;
930 size_t i, size, left, len, off;
931 struct grid_cell gc, *gcp;
932 int utf8flag;
934 if (c->tty.sx == 0 || c->tty.sy == 0)
935 return (0);
936 memcpy(&old_status, &c->status, sizeof old_status);
937 screen_init(&c->status, c->tty.sx, 1, 0);
939 utf8flag = options_get_number(&s->options, "status-utf8");
941 len = screen_write_strlen(utf8flag, "%s", c->prompt_string);
942 if (len > c->tty.sx)
943 len = c->tty.sx;
944 off = 0;
946 memcpy(&gc, &grid_default_cell, sizeof gc);
947 /* Change colours for command mode. */
948 if (c->prompt_mdata.mode == 1) {
949 colour_set_fg(&gc, options_get_number(&s->options, "message-command-fg"));
950 colour_set_bg(&gc, options_get_number(&s->options, "message-command-bg"));
951 gc.attr |= options_get_number(&s->options, "message-command-attr");
952 } else {
953 colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
954 colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
955 gc.attr |= options_get_number(&s->options, "message-attr");
958 screen_write_start(&ctx, NULL, &c->status);
960 screen_write_cursormove(&ctx, 0, 0);
961 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->prompt_string);
963 left = c->tty.sx - len;
964 if (left != 0) {
965 size = screen_write_strlen(utf8flag, "%s", c->prompt_buffer);
966 if (c->prompt_index >= left) {
967 off = c->prompt_index - left + 1;
968 if (c->prompt_index == size)
969 left--;
970 size = left;
972 screen_write_nputs(
973 &ctx, left, &gc, utf8flag, "%s", c->prompt_buffer + off);
975 for (i = len + size; i < c->tty.sx; i++)
976 screen_write_putc(&ctx, &gc, ' ');
979 screen_write_stop(&ctx);
981 /* Apply fake cursor. */
982 off = len + c->prompt_index - off;
983 gcp = grid_view_get_cell(c->status.grid, off, 0);
984 gcp->attr ^= GRID_ATTR_REVERSE;
986 if (grid_compare(c->status.grid, old_status.grid) == 0) {
987 screen_free(&old_status);
988 return (0);
990 screen_free(&old_status);
991 return (1);
994 /* Handle keys in prompt. */
995 void
996 status_prompt_key(struct client *c, int key)
998 struct session *sess = c->session;
999 struct options *oo = &sess->options;
1000 struct paste_buffer *pb;
1001 char *s, *first, *last, word[64], swapc;
1002 const char *histstr;
1003 const char *wsep = NULL;
1004 u_char ch;
1005 size_t size, n, off, idx;
1007 size = strlen(c->prompt_buffer);
1008 switch (mode_key_lookup(&c->prompt_mdata, key)) {
1009 case MODEKEYEDIT_CURSORLEFT:
1010 if (c->prompt_index > 0) {
1011 c->prompt_index--;
1012 c->flags |= CLIENT_STATUS;
1014 break;
1015 case MODEKEYEDIT_SWITCHMODE:
1016 c->flags |= CLIENT_STATUS;
1017 break;
1018 case MODEKEYEDIT_SWITCHMODEAPPEND:
1019 c->flags |= CLIENT_STATUS;
1020 /* FALLTHROUGH */
1021 case MODEKEYEDIT_CURSORRIGHT:
1022 if (c->prompt_index < size) {
1023 c->prompt_index++;
1024 c->flags |= CLIENT_STATUS;
1026 break;
1027 case MODEKEYEDIT_STARTOFLINE:
1028 if (c->prompt_index != 0) {
1029 c->prompt_index = 0;
1030 c->flags |= CLIENT_STATUS;
1032 break;
1033 case MODEKEYEDIT_ENDOFLINE:
1034 if (c->prompt_index != size) {
1035 c->prompt_index = size;
1036 c->flags |= CLIENT_STATUS;
1038 break;
1039 case MODEKEYEDIT_COMPLETE:
1040 if (*c->prompt_buffer == '\0')
1041 break;
1043 idx = c->prompt_index;
1044 if (idx != 0)
1045 idx--;
1047 /* Find the word we are in. */
1048 first = c->prompt_buffer + idx;
1049 while (first > c->prompt_buffer && *first != ' ')
1050 first--;
1051 while (*first == ' ')
1052 first++;
1053 last = c->prompt_buffer + idx;
1054 while (*last != '\0' && *last != ' ')
1055 last++;
1056 while (*last == ' ')
1057 last--;
1058 if (*last != '\0')
1059 last++;
1060 if (last <= first ||
1061 ((size_t) (last - first)) > (sizeof word) - 1)
1062 break;
1063 memcpy(word, first, last - first);
1064 word[last - first] = '\0';
1066 /* And try to complete it. */
1067 if ((s = status_prompt_complete(word)) == NULL)
1068 break;
1070 /* Trim out word. */
1071 n = size - (last - c->prompt_buffer) + 1; /* with \0 */
1072 memmove(first, last, n);
1073 size -= last - first;
1075 /* Insert the new word. */
1076 size += strlen(s);
1077 off = first - c->prompt_buffer;
1078 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1);
1079 first = c->prompt_buffer + off;
1080 memmove(first + strlen(s), first, n);
1081 memcpy(first, s, strlen(s));
1083 c->prompt_index = (first - c->prompt_buffer) + strlen(s);
1084 xfree(s);
1086 c->flags |= CLIENT_STATUS;
1087 break;
1088 case MODEKEYEDIT_BACKSPACE:
1089 if (c->prompt_index != 0) {
1090 if (c->prompt_index == size)
1091 c->prompt_buffer[--c->prompt_index] = '\0';
1092 else {
1093 memmove(c->prompt_buffer + c->prompt_index - 1,
1094 c->prompt_buffer + c->prompt_index,
1095 size + 1 - c->prompt_index);
1096 c->prompt_index--;
1098 c->flags |= CLIENT_STATUS;
1100 break;
1101 case MODEKEYEDIT_DELETE:
1102 if (c->prompt_index != size) {
1103 memmove(c->prompt_buffer + c->prompt_index,
1104 c->prompt_buffer + c->prompt_index + 1,
1105 size + 1 - c->prompt_index);
1106 c->flags |= CLIENT_STATUS;
1108 break;
1109 case MODEKEYEDIT_DELETELINE:
1110 *c->prompt_buffer = '\0';
1111 c->prompt_index = 0;
1112 c->flags |= CLIENT_STATUS;
1113 break;
1114 case MODEKEYEDIT_DELETETOENDOFLINE:
1115 if (c->prompt_index < size) {
1116 c->prompt_buffer[c->prompt_index] = '\0';
1117 c->flags |= CLIENT_STATUS;
1119 break;
1120 case MODEKEYEDIT_DELETEWORD:
1121 wsep = options_get_string(oo, "word-separators");
1122 idx = c->prompt_index;
1124 /* Find a non-separator. */
1125 while (idx != 0) {
1126 idx--;
1127 if (!strchr(wsep, c->prompt_buffer[idx]))
1128 break;
1131 /* Find the separator at the beginning of the word. */
1132 while (idx != 0) {
1133 idx--;
1134 if (strchr(wsep, c->prompt_buffer[idx])) {
1135 /* Go back to the word. */
1136 idx++;
1137 break;
1141 memmove(c->prompt_buffer + idx,
1142 c->prompt_buffer + c->prompt_index,
1143 size + 1 - c->prompt_index);
1144 memset(c->prompt_buffer + size - (c->prompt_index - idx),
1145 '\0', c->prompt_index - idx);
1146 c->prompt_index = idx;
1147 c->flags |= CLIENT_STATUS;
1148 break;
1149 case MODEKEYEDIT_NEXTSPACE:
1150 wsep = " ";
1151 /* FALLTHROUGH */
1152 case MODEKEYEDIT_NEXTWORD:
1153 if (wsep == NULL)
1154 wsep = options_get_string(oo, "word-separators");
1156 /* Find a separator. */
1157 while (c->prompt_index != size) {
1158 c->prompt_index++;
1159 if (strchr(wsep, c->prompt_buffer[c->prompt_index]))
1160 break;
1163 /* Find the word right after the separation. */
1164 while (c->prompt_index != size) {
1165 c->prompt_index++;
1166 if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1167 break;
1170 c->flags |= CLIENT_STATUS;
1171 break;
1172 case MODEKEYEDIT_NEXTSPACEEND:
1173 wsep = " ";
1174 /* FALLTHROUGH */
1175 case MODEKEYEDIT_NEXTWORDEND:
1176 if (wsep == NULL)
1177 wsep = options_get_string(oo, "word-separators");
1179 /* Find a word. */
1180 while (c->prompt_index != size) {
1181 c->prompt_index++;
1182 if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1183 break;
1186 /* Find the separator at the end of the word. */
1187 while (c->prompt_index != size) {
1188 c->prompt_index++;
1189 if (strchr(wsep, c->prompt_buffer[c->prompt_index]))
1190 break;
1193 c->flags |= CLIENT_STATUS;
1194 break;
1195 case MODEKEYEDIT_PREVIOUSSPACE:
1196 wsep = " ";
1197 /* FALLTHROUGH */
1198 case MODEKEYEDIT_PREVIOUSWORD:
1199 if (wsep == NULL)
1200 wsep = options_get_string(oo, "word-separators");
1202 /* Find a non-separator. */
1203 while (c->prompt_index != 0) {
1204 c->prompt_index--;
1205 if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1206 break;
1209 /* Find the separator at the beginning of the word. */
1210 while (c->prompt_index != 0) {
1211 c->prompt_index--;
1212 if (strchr(wsep, c->prompt_buffer[c->prompt_index])) {
1213 /* Go back to the word. */
1214 c->prompt_index++;
1215 break;
1219 c->flags |= CLIENT_STATUS;
1220 break;
1221 case MODEKEYEDIT_HISTORYUP:
1222 histstr = status_prompt_up_history(&c->prompt_hindex);
1223 if (histstr == NULL)
1224 break;
1225 xfree(c->prompt_buffer);
1226 c->prompt_buffer = xstrdup(histstr);
1227 c->prompt_index = strlen(c->prompt_buffer);
1228 c->flags |= CLIENT_STATUS;
1229 break;
1230 case MODEKEYEDIT_HISTORYDOWN:
1231 histstr = status_prompt_down_history(&c->prompt_hindex);
1232 if (histstr == NULL)
1233 break;
1234 xfree(c->prompt_buffer);
1235 c->prompt_buffer = xstrdup(histstr);
1236 c->prompt_index = strlen(c->prompt_buffer);
1237 c->flags |= CLIENT_STATUS;
1238 break;
1239 case MODEKEYEDIT_PASTE:
1240 if ((pb = paste_get_top(&global_buffers)) == NULL)
1241 break;
1242 for (n = 0; n < pb->size; n++) {
1243 ch = (u_char) pb->data[n];
1244 if (ch < 32 || ch == 127)
1245 break;
1248 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1);
1249 if (c->prompt_index == size) {
1250 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1251 c->prompt_index += n;
1252 c->prompt_buffer[c->prompt_index] = '\0';
1253 } else {
1254 memmove(c->prompt_buffer + c->prompt_index + n,
1255 c->prompt_buffer + c->prompt_index,
1256 size + 1 - c->prompt_index);
1257 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1258 c->prompt_index += n;
1261 c->flags |= CLIENT_STATUS;
1262 break;
1263 case MODEKEYEDIT_TRANSPOSECHARS:
1264 idx = c->prompt_index;
1265 if (idx < size)
1266 idx++;
1267 if (idx >= 2) {
1268 swapc = c->prompt_buffer[idx - 2];
1269 c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1];
1270 c->prompt_buffer[idx - 1] = swapc;
1271 c->prompt_index = idx;
1272 c->flags |= CLIENT_STATUS;
1274 break;
1275 case MODEKEYEDIT_ENTER:
1276 if (*c->prompt_buffer != '\0')
1277 status_prompt_add_history(c->prompt_buffer);
1278 if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0)
1279 status_prompt_clear(c);
1280 break;
1281 case MODEKEYEDIT_CANCEL:
1282 if (c->prompt_callbackfn(c->prompt_data, NULL) == 0)
1283 status_prompt_clear(c);
1284 break;
1285 case MODEKEY_OTHER:
1286 if ((key & 0xff00) != 0 || key < 32 || key == 127)
1287 break;
1288 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2);
1290 if (c->prompt_index == size) {
1291 c->prompt_buffer[c->prompt_index++] = key;
1292 c->prompt_buffer[c->prompt_index] = '\0';
1293 } else {
1294 memmove(c->prompt_buffer + c->prompt_index + 1,
1295 c->prompt_buffer + c->prompt_index,
1296 size + 1 - c->prompt_index);
1297 c->prompt_buffer[c->prompt_index++] = key;
1300 if (c->prompt_flags & PROMPT_SINGLE) {
1301 if (c->prompt_callbackfn(
1302 c->prompt_data, c->prompt_buffer) == 0)
1303 status_prompt_clear(c);
1306 c->flags |= CLIENT_STATUS;
1307 break;
1308 default:
1309 break;
1313 /* Get previous line from the history. */
1314 const char *
1315 status_prompt_up_history(u_int *idx)
1317 u_int size;
1320 * History runs from 0 to size - 1.
1322 * Index is from 0 to size. Zero is empty.
1325 size = ARRAY_LENGTH(&status_prompt_history);
1326 if (size == 0 || *idx == size)
1327 return (NULL);
1328 (*idx)++;
1329 return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1332 /* Get next line from the history. */
1333 const char *
1334 status_prompt_down_history(u_int *idx)
1336 u_int size;
1338 size = ARRAY_LENGTH(&status_prompt_history);
1339 if (size == 0 || *idx == 0)
1340 return ("");
1341 (*idx)--;
1342 if (*idx == 0)
1343 return ("");
1344 return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1347 /* Add line to the history. */
1348 void
1349 status_prompt_add_history(const char *line)
1351 u_int size;
1353 size = ARRAY_LENGTH(&status_prompt_history);
1354 if (size > 0 && strcmp(ARRAY_LAST(&status_prompt_history), line) == 0)
1355 return;
1357 if (size == PROMPT_HISTORY) {
1358 xfree(ARRAY_FIRST(&status_prompt_history));
1359 ARRAY_REMOVE(&status_prompt_history, 0);
1362 ARRAY_ADD(&status_prompt_history, xstrdup(line));
1365 /* Complete word. */
1366 char *
1367 status_prompt_complete(const char *s)
1369 const struct cmd_entry **cmdent;
1370 const struct options_table_entry *oe;
1371 ARRAY_DECL(, const char *) list;
1372 char *prefix, *s2;
1373 u_int i;
1374 size_t j;
1376 if (*s == '\0')
1377 return (NULL);
1379 /* First, build a list of all the possible matches. */
1380 ARRAY_INIT(&list);
1381 for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1382 if (strncmp((*cmdent)->name, s, strlen(s)) == 0)
1383 ARRAY_ADD(&list, (*cmdent)->name);
1385 for (oe = server_options_table; oe->name != NULL; oe++) {
1386 if (strncmp(oe->name, s, strlen(s)) == 0)
1387 ARRAY_ADD(&list, oe->name);
1389 for (oe = session_options_table; oe->name != NULL; oe++) {
1390 if (strncmp(oe->name, s, strlen(s)) == 0)
1391 ARRAY_ADD(&list, oe->name);
1393 for (oe = window_options_table; oe->name != NULL; oe++) {
1394 if (strncmp(oe->name, s, strlen(s)) == 0)
1395 ARRAY_ADD(&list, oe->name);
1398 /* If none, bail now. */
1399 if (ARRAY_LENGTH(&list) == 0) {
1400 ARRAY_FREE(&list);
1401 return (NULL);
1404 /* If an exact match, return it, with a trailing space. */
1405 if (ARRAY_LENGTH(&list) == 1) {
1406 xasprintf(&s2, "%s ", ARRAY_FIRST(&list));
1407 ARRAY_FREE(&list);
1408 return (s2);
1411 /* Now loop through the list and find the longest common prefix. */
1412 prefix = xstrdup(ARRAY_FIRST(&list));
1413 for (i = 1; i < ARRAY_LENGTH(&list); i++) {
1414 s = ARRAY_ITEM(&list, i);
1416 j = strlen(s);
1417 if (j > strlen(prefix))
1418 j = strlen(prefix);
1419 for (; j > 0; j--) {
1420 if (prefix[j - 1] != s[j - 1])
1421 prefix[j - 1] = '\0';
1425 ARRAY_FREE(&list);
1426 return (prefix);