First period not last for host_short, from Michael Scholz.
[tmux-openbsd.git] / status.c
blobc5572b747a8b93e93233e5378c61d1c298a51da7
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 *, char **, char **, char *, size_t, int);
42 void status_message_callback(int, short, void *);
44 const char *status_prompt_up_history(u_int *);
45 const char *status_prompt_down_history(u_int *);
46 void status_prompt_add_history(const char *);
47 char *status_prompt_complete(const char *);
49 /* Status prompt history. */
50 ARRAY_DECL(, char *) status_prompt_history = ARRAY_INITIALIZER;
52 /* Status output tree. */
53 RB_GENERATE(status_out_tree, status_out, entry, status_out_cmp);
55 /* Output tree comparison function. */
56 int
57 status_out_cmp(struct status_out *so1, struct status_out *so2)
59 return (strcmp(so1->cmd, so2->cmd));
62 /* Get screen line of status line. -1 means off. */
63 int
64 status_at_line(struct client *c)
66 struct session *s = c->session;
68 if (!options_get_number(&s->options, "status"))
69 return (-1);
71 if (options_get_number(&s->options, "status-position") == 0)
72 return (0);
73 return (c->tty.sy - 1);
76 /* Retrieve options for left string. */
77 char *
78 status_redraw_get_left(struct client *c,
79 time_t t, int utf8flag, struct grid_cell *gc, size_t *size)
81 struct session *s = c->session;
82 char *left;
83 int fg, bg, attr;
84 size_t leftlen;
86 fg = options_get_number(&s->options, "status-left-fg");
87 if (fg != 8)
88 colour_set_fg(gc, fg);
89 bg = options_get_number(&s->options, "status-left-bg");
90 if (bg != 8)
91 colour_set_bg(gc, bg);
92 attr = options_get_number(&s->options, "status-left-attr");
93 if (attr != 0)
94 gc->attr = attr;
96 left = status_replace(c, NULL,
97 NULL, NULL, options_get_string(&s->options, "status-left"), t, 1);
99 *size = options_get_number(&s->options, "status-left-length");
100 leftlen = screen_write_cstrlen(utf8flag, "%s", left);
101 if (leftlen < *size)
102 *size = leftlen;
103 return (left);
106 /* Retrieve options for right string. */
107 char *
108 status_redraw_get_right(struct client *c,
109 time_t t, int utf8flag, struct grid_cell *gc, size_t *size)
111 struct session *s = c->session;
112 char *right;
113 int fg, bg, attr;
114 size_t rightlen;
116 fg = options_get_number(&s->options, "status-right-fg");
117 if (fg != 8)
118 colour_set_fg(gc, fg);
119 bg = options_get_number(&s->options, "status-right-bg");
120 if (bg != 8)
121 colour_set_bg(gc, bg);
122 attr = options_get_number(&s->options, "status-right-attr");
123 if (attr != 0)
124 gc->attr = attr;
126 right = status_replace(c, NULL,
127 NULL, NULL, options_get_string(&s->options, "status-right"), t, 1);
129 *size = options_get_number(&s->options, "status-right-length");
130 rightlen = screen_write_cstrlen(utf8flag, "%s", right);
131 if (rightlen < *size)
132 *size = rightlen;
133 return (right);
136 /* Set window at window list position. */
137 void
138 status_set_window_at(struct client *c, u_int x)
140 struct session *s = c->session;
141 struct winlink *wl;
143 x += c->wlmouse;
144 RB_FOREACH(wl, winlinks, &s->windows) {
145 if (x < wl->status_width && session_select(s, wl->idx) == 0)
146 server_redraw_session(s);
147 x -= wl->status_width + 1;
151 /* Draw status for client on the last lines of given context. */
153 status_redraw(struct client *c)
155 struct screen_write_ctx ctx;
156 struct session *s = c->session;
157 struct winlink *wl;
158 struct screen old_status, window_list;
159 struct grid_cell stdgc, lgc, rgc, gc;
160 struct options *oo;
161 time_t t;
162 char *left, *right, *sep;
163 u_int offset, needed;
164 u_int wlstart, wlwidth, wlavailable, wloffset, wlsize;
165 size_t llen, rlen, seplen;
166 int larrow, rarrow, utf8flag;
168 /* No status line? */
169 if (c->tty.sy == 0 || !options_get_number(&s->options, "status"))
170 return (1);
171 left = right = NULL;
172 larrow = rarrow = 0;
174 /* Update status timer. */
175 if (gettimeofday(&c->status_timer, NULL) != 0)
176 fatal("gettimeofday failed");
177 t = c->status_timer.tv_sec;
179 /* Set up default colour. */
180 memcpy(&stdgc, &grid_default_cell, sizeof gc);
181 colour_set_fg(&stdgc, options_get_number(&s->options, "status-fg"));
182 colour_set_bg(&stdgc, options_get_number(&s->options, "status-bg"));
183 stdgc.attr |= options_get_number(&s->options, "status-attr");
185 /* Create the target screen. */
186 memcpy(&old_status, &c->status, sizeof old_status);
187 screen_init(&c->status, c->tty.sx, 1, 0);
188 screen_write_start(&ctx, NULL, &c->status);
189 for (offset = 0; offset < c->tty.sx; offset++)
190 screen_write_putc(&ctx, &stdgc, ' ');
191 screen_write_stop(&ctx);
193 /* If the height is one line, blank status line. */
194 if (c->tty.sy <= 1)
195 goto out;
197 /* Get UTF-8 flag. */
198 utf8flag = options_get_number(&s->options, "status-utf8");
200 /* Work out left and right strings. */
201 memcpy(&lgc, &stdgc, sizeof lgc);
202 left = status_redraw_get_left(c, t, utf8flag, &lgc, &llen);
203 memcpy(&rgc, &stdgc, sizeof rgc);
204 right = status_redraw_get_right(c, t, utf8flag, &rgc, &rlen);
207 * Figure out how much space we have for the window list. If there
208 * isn't enough space, just show a blank status line.
210 needed = 0;
211 if (llen != 0)
212 needed += llen + 1;
213 if (rlen != 0)
214 needed += rlen + 1;
215 if (c->tty.sx == 0 || c->tty.sx <= needed)
216 goto out;
217 wlavailable = c->tty.sx - needed;
219 /* Calculate the total size needed for the window list. */
220 wlstart = wloffset = wlwidth = 0;
221 RB_FOREACH(wl, winlinks, &s->windows) {
222 free(wl->status_text);
223 memcpy(&wl->status_cell, &stdgc, sizeof wl->status_cell);
224 wl->status_text = status_print(c, wl, t, &wl->status_cell);
225 wl->status_width =
226 screen_write_cstrlen(utf8flag, "%s", wl->status_text);
228 if (wl == s->curw)
229 wloffset = wlwidth;
231 oo = &wl->window->options;
232 sep = options_get_string(oo, "window-status-separator");
233 seplen = screen_write_strlen(utf8flag, "%s", sep);
234 wlwidth += wl->status_width + seplen;
237 /* Create a new screen for the window list. */
238 screen_init(&window_list, wlwidth, 1, 0);
240 /* And draw the window list into it. */
241 screen_write_start(&ctx, NULL, &window_list);
242 RB_FOREACH(wl, winlinks, &s->windows) {
243 screen_write_cnputs(&ctx,
244 -1, &wl->status_cell, utf8flag, "%s", wl->status_text);
246 oo = &wl->window->options;
247 sep = options_get_string(oo, "window-status-separator");
248 screen_write_nputs(&ctx, -1, &stdgc, utf8flag, "%s", sep);
250 screen_write_stop(&ctx);
252 /* If there is enough space for the total width, skip to draw now. */
253 if (wlwidth <= wlavailable)
254 goto draw;
256 /* Find size of current window text. */
257 wlsize = s->curw->status_width;
260 * If the current window is already on screen, good to draw from the
261 * start and just leave off the end.
263 if (wloffset + wlsize < wlavailable) {
264 if (wlavailable > 0) {
265 rarrow = 1;
266 wlavailable--;
268 wlwidth = wlavailable;
269 } else {
271 * Work out how many characters we need to omit from the
272 * start. There are wlavailable characters to fill, and
273 * wloffset + wlsize must be the last. So, the start character
274 * is wloffset + wlsize - wlavailable.
276 if (wlavailable > 0) {
277 larrow = 1;
278 wlavailable--;
281 wlstart = wloffset + wlsize - wlavailable;
282 if (wlavailable > 0 && wlwidth > wlstart + wlavailable + 1) {
283 rarrow = 1;
284 wlstart++;
285 wlavailable--;
287 wlwidth = wlavailable;
290 /* Bail if anything is now too small too. */
291 if (wlwidth == 0 || wlavailable == 0) {
292 screen_free(&window_list);
293 goto out;
297 * Now the start position is known, work out the state of the left and
298 * right arrows.
300 offset = 0;
301 RB_FOREACH(wl, winlinks, &s->windows) {
302 if (wl->flags & WINLINK_ALERTFLAGS &&
303 larrow == 1 && offset < wlstart)
304 larrow = -1;
306 offset += wl->status_width;
308 if (wl->flags & WINLINK_ALERTFLAGS &&
309 rarrow == 1 && offset > wlstart + wlwidth)
310 rarrow = -1;
313 draw:
314 /* Begin drawing. */
315 screen_write_start(&ctx, NULL, &c->status);
317 /* Draw the left string and arrow. */
318 screen_write_cursormove(&ctx, 0, 0);
319 if (llen != 0) {
320 screen_write_cnputs(&ctx, llen, &lgc, utf8flag, "%s", left);
321 screen_write_putc(&ctx, &stdgc, ' ');
323 if (larrow != 0) {
324 memcpy(&gc, &stdgc, sizeof gc);
325 if (larrow == -1)
326 gc.attr ^= GRID_ATTR_REVERSE;
327 screen_write_putc(&ctx, &gc, '<');
330 /* Draw the right string and arrow. */
331 if (rarrow != 0) {
332 screen_write_cursormove(&ctx, c->tty.sx - rlen - 2, 0);
333 memcpy(&gc, &stdgc, sizeof gc);
334 if (rarrow == -1)
335 gc.attr ^= GRID_ATTR_REVERSE;
336 screen_write_putc(&ctx, &gc, '>');
337 } else
338 screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, 0);
339 if (rlen != 0) {
340 screen_write_putc(&ctx, &stdgc, ' ');
341 screen_write_cnputs(&ctx, rlen, &rgc, utf8flag, "%s", right);
344 /* Figure out the offset for the window list. */
345 if (llen != 0)
346 wloffset = llen + 1;
347 else
348 wloffset = 0;
349 if (wlwidth < wlavailable) {
350 switch (options_get_number(&s->options, "status-justify")) {
351 case 1: /* centered */
352 wloffset += (wlavailable - wlwidth) / 2;
353 break;
354 case 2: /* right */
355 wloffset += (wlavailable - wlwidth);
356 break;
359 if (larrow != 0)
360 wloffset++;
362 /* Copy the window list. */
363 c->wlmouse = -wloffset + wlstart;
364 screen_write_cursormove(&ctx, wloffset, 0);
365 screen_write_copy(&ctx, &window_list, wlstart, 0, wlwidth, 1);
366 screen_free(&window_list);
368 screen_write_stop(&ctx);
370 out:
371 free(left);
372 free(right);
374 if (grid_compare(c->status.grid, old_status.grid) == 0) {
375 screen_free(&old_status);
376 return (0);
378 screen_free(&old_status);
379 return (1);
382 /* Replace a single special sequence (prefixed by #). */
383 void
384 status_replace1(struct client *c, char **iptr, char **optr, char *out,
385 size_t outsize, int jobsflag)
387 char ch, tmp[256], *ptr, *endptr;
388 size_t ptrlen;
389 long limit;
391 errno = 0;
392 limit = strtol(*iptr, &endptr, 10);
393 if ((limit == 0 && errno != EINVAL) ||
394 (limit == LONG_MIN && errno != ERANGE) ||
395 (limit == LONG_MAX && errno != ERANGE) ||
396 limit != 0)
397 *iptr = endptr;
398 if (limit <= 0)
399 limit = LONG_MAX;
401 switch (*(*iptr)++) {
402 case '(':
403 if (!jobsflag) {
404 ch = ')';
405 goto skip_to;
407 if ((ptr = status_find_job(c, iptr)) == NULL)
408 return;
409 goto do_replace;
410 case '[':
412 * Embedded style, handled at display time. Leave present and
413 * skip input until ].
415 ch = ']';
416 goto skip_to;
417 case '{':
418 ptr = (char *) "#{";
419 goto do_replace;
420 case '#':
421 *(*optr)++ = '#';
422 break;
423 default:
424 xsnprintf(tmp, sizeof tmp, "#%c", *(*iptr - 1));
425 ptr = tmp;
426 goto do_replace;
429 return;
431 do_replace:
432 ptrlen = strlen(ptr);
433 if ((size_t) limit < ptrlen)
434 ptrlen = limit;
436 if (*optr + ptrlen >= out + outsize - 1)
437 return;
438 while (ptrlen > 0 && *ptr != '\0') {
439 *(*optr)++ = *ptr++;
440 ptrlen--;
443 return;
445 skip_to:
446 *(*optr)++ = '#';
448 (*iptr)--; /* include ch */
449 while (**iptr != ch && **iptr != '\0') {
450 if (*optr >= out + outsize - 1)
451 break;
452 *(*optr)++ = *(*iptr)++;
456 /* Replace special sequences in fmt. */
457 char *
458 status_replace(struct client *c, struct session *s, struct winlink *wl,
459 struct window_pane *wp, const char *fmt, time_t t, int jobsflag)
461 static char out[BUFSIZ];
462 char in[BUFSIZ], ch, *iptr, *optr, *expanded;
463 size_t len;
464 struct format_tree *ft;
466 if (fmt == NULL)
467 return (xstrdup(""));
469 if (s == NULL)
470 s = c->session;
471 if (wl == NULL)
472 wl = s->curw;
473 if (wp == NULL)
474 wp = wl->window->active;
476 len = strftime(in, sizeof in, fmt, localtime(&t));
477 in[len] = '\0';
479 iptr = in;
480 optr = out;
482 while (*iptr != '\0') {
483 if (optr >= out + (sizeof out) - 1)
484 break;
485 ch = *iptr++;
487 if (ch != '#' || *iptr == '\0') {
488 *optr++ = ch;
489 continue;
491 status_replace1(c, &iptr, &optr, out, sizeof out, jobsflag);
493 *optr = '\0';
495 ft = format_create();
496 format_client(ft, c);
497 format_session(ft, s);
498 format_winlink(ft, s, wl);
499 format_window_pane(ft, wp);
500 expanded = format_expand(ft, out);
501 format_free(ft);
502 return (expanded);
505 /* Figure out job name and get its result, starting it off if necessary. */
506 char *
507 status_find_job(struct client *c, char **iptr)
509 struct status_out *so, so_find;
510 char *cmd;
511 int lastesc;
512 size_t len;
514 if (**iptr == '\0')
515 return (NULL);
516 if (**iptr == ')') { /* no command given */
517 (*iptr)++;
518 return (NULL);
521 cmd = xmalloc(strlen(*iptr) + 1);
522 len = 0;
524 lastesc = 0;
525 for (; **iptr != '\0'; (*iptr)++) {
526 if (!lastesc && **iptr == ')')
527 break; /* unescaped ) is the end */
528 if (!lastesc && **iptr == '\\') {
529 lastesc = 1;
530 continue; /* skip \ if not escaped */
532 lastesc = 0;
533 cmd[len++] = **iptr;
535 if (**iptr == '\0') /* no terminating ) */ {
536 free(cmd);
537 return (NULL);
539 (*iptr)++; /* skip final ) */
540 cmd[len] = '\0';
542 /* First try in the new tree. */
543 so_find.cmd = cmd;
544 so = RB_FIND(status_out_tree, &c->status_new, &so_find);
545 if (so != NULL && so->out != NULL) {
546 free(cmd);
547 return (so->out);
550 /* If not found at all, start the job and add to the tree. */
551 if (so == NULL) {
552 job_run(cmd, NULL, status_job_callback, status_job_free, c);
553 c->references++;
555 so = xmalloc(sizeof *so);
556 so->cmd = xstrdup(cmd);
557 so->out = NULL;
558 RB_INSERT(status_out_tree, &c->status_new, so);
561 /* Lookup in the old tree. */
562 so_find.cmd = cmd;
563 so = RB_FIND(status_out_tree, &c->status_old, &so_find);
564 free(cmd);
565 if (so != NULL)
566 return (so->out);
567 return (NULL);
570 /* Free job tree. */
571 void
572 status_free_jobs(struct status_out_tree *sotree)
574 struct status_out *so, *so_next;
576 so_next = RB_MIN(status_out_tree, sotree);
577 while (so_next != NULL) {
578 so = so_next;
579 so_next = RB_NEXT(status_out_tree, sotree, so);
581 RB_REMOVE(status_out_tree, sotree, so);
582 free(so->out);
583 free(so->cmd);
584 free(so);
588 /* Update jobs on status interval. */
589 void
590 status_update_jobs(struct client *c)
592 /* Free the old tree. */
593 status_free_jobs(&c->status_old);
595 /* Move the new to old. */
596 memcpy(&c->status_old, &c->status_new, sizeof c->status_old);
597 RB_INIT(&c->status_new);
600 /* Free status job. */
601 void
602 status_job_free(void *data)
604 struct client *c = data;
606 c->references--;
609 /* Job has finished: save its result. */
610 void
611 status_job_callback(struct job *job)
613 struct client *c = job->data;
614 struct status_out *so, so_find;
615 char *line, *buf;
616 size_t len;
618 if (c->flags & CLIENT_DEAD)
619 return;
621 so_find.cmd = job->cmd;
622 so = RB_FIND(status_out_tree, &c->status_new, &so_find);
623 if (so == NULL || so->out != NULL)
624 return;
626 buf = NULL;
627 if ((line = evbuffer_readline(job->event->input)) == NULL) {
628 len = EVBUFFER_LENGTH(job->event->input);
629 buf = xmalloc(len + 1);
630 if (len != 0)
631 memcpy(buf, EVBUFFER_DATA(job->event->input), len);
632 buf[len] = '\0';
633 } else
634 buf = line;
636 so->out = buf;
637 server_status_client(c);
640 /* Return winlink status line entry and adjust gc as necessary. */
641 char *
642 status_print(
643 struct client *c, struct winlink *wl, time_t t, struct grid_cell *gc)
645 struct options *oo = &wl->window->options;
646 struct session *s = c->session;
647 const char *fmt;
648 char *text;
649 int fg, bg, attr;
651 fg = options_get_number(oo, "window-status-fg");
652 if (fg != 8)
653 colour_set_fg(gc, fg);
654 bg = options_get_number(oo, "window-status-bg");
655 if (bg != 8)
656 colour_set_bg(gc, bg);
657 attr = options_get_number(oo, "window-status-attr");
658 if (attr != 0)
659 gc->attr = attr;
660 fmt = options_get_string(oo, "window-status-format");
661 if (wl == s->curw) {
662 fg = options_get_number(oo, "window-status-current-fg");
663 if (fg != 8)
664 colour_set_fg(gc, fg);
665 bg = options_get_number(oo, "window-status-current-bg");
666 if (bg != 8)
667 colour_set_bg(gc, bg);
668 attr = options_get_number(oo, "window-status-current-attr");
669 if (attr != 0)
670 gc->attr = attr;
671 fmt = options_get_string(oo, "window-status-current-format");
673 if (wl == TAILQ_FIRST(&s->lastw)) {
674 fg = options_get_number(oo, "window-status-last-fg");
675 if (fg != 8)
676 colour_set_fg(gc, fg);
677 bg = options_get_number(oo, "window-status-last-bg");
678 if (bg != 8)
679 colour_set_bg(gc, bg);
680 attr = options_get_number(oo, "window-status-last-attr");
681 if (attr != 0)
682 gc->attr = attr;
685 if (wl->flags & WINLINK_BELL) {
686 fg = options_get_number(oo, "window-status-bell-fg");
687 if (fg != 8)
688 colour_set_fg(gc, fg);
689 bg = options_get_number(oo, "window-status-bell-bg");
690 if (bg != 8)
691 colour_set_bg(gc, bg);
692 attr = options_get_number(oo, "window-status-bell-attr");
693 if (attr != 0)
694 gc->attr = attr;
695 } else if (wl->flags & WINLINK_CONTENT) {
696 fg = options_get_number(oo, "window-status-content-fg");
697 if (fg != 8)
698 colour_set_fg(gc, fg);
699 bg = options_get_number(oo, "window-status-content-bg");
700 if (bg != 8)
701 colour_set_bg(gc, bg);
702 attr = options_get_number(oo, "window-status-content-attr");
703 if (attr != 0)
704 gc->attr = attr;
705 } else if (wl->flags & (WINLINK_ACTIVITY|WINLINK_SILENCE)) {
706 fg = options_get_number(oo, "window-status-activity-fg");
707 if (fg != 8)
708 colour_set_fg(gc, fg);
709 bg = options_get_number(oo, "window-status-activity-bg");
710 if (bg != 8)
711 colour_set_bg(gc, bg);
712 attr = options_get_number(oo, "window-status-activity-attr");
713 if (attr != 0)
714 gc->attr = attr;
717 text = status_replace(c, NULL, wl, NULL, fmt, t, 1);
718 return (text);
721 /* Set a status line message. */
722 void printflike2
723 status_message_set(struct client *c, const char *fmt, ...)
725 struct timeval tv;
726 struct session *s = c->session;
727 struct message_entry *msg;
728 va_list ap;
729 int delay;
730 u_int i, limit;
732 status_prompt_clear(c);
733 status_message_clear(c);
735 va_start(ap, fmt);
736 xvasprintf(&c->message_string, fmt, ap);
737 va_end(ap);
739 ARRAY_EXPAND(&c->message_log, 1);
740 msg = &ARRAY_LAST(&c->message_log);
741 msg->msg_time = time(NULL);
742 msg->msg = xstrdup(c->message_string);
744 if (s == NULL)
745 limit = 0;
746 else
747 limit = options_get_number(&s->options, "message-limit");
748 if (ARRAY_LENGTH(&c->message_log) > limit) {
749 limit = ARRAY_LENGTH(&c->message_log) - limit;
750 for (i = 0; i < limit; i++) {
751 msg = &ARRAY_FIRST(&c->message_log);
752 free(msg->msg);
753 ARRAY_REMOVE(&c->message_log, 0);
757 delay = options_get_number(&c->session->options, "display-time");
758 tv.tv_sec = delay / 1000;
759 tv.tv_usec = (delay % 1000) * 1000L;
761 if (event_initialized (&c->message_timer))
762 evtimer_del(&c->message_timer);
763 evtimer_set(&c->message_timer, status_message_callback, c);
764 evtimer_add(&c->message_timer, &tv);
766 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
767 c->flags |= CLIENT_STATUS;
770 /* Clear status line message. */
771 void
772 status_message_clear(struct client *c)
774 if (c->message_string == NULL)
775 return;
777 free(c->message_string);
778 c->message_string = NULL;
780 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
781 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
783 screen_reinit(&c->status);
786 /* Clear status line message after timer expires. */
787 void
788 status_message_callback(unused int fd, unused short event, void *data)
790 struct client *c = data;
792 status_message_clear(c);
795 /* Draw client message on status line of present else on last line. */
797 status_message_redraw(struct client *c)
799 struct screen_write_ctx ctx;
800 struct session *s = c->session;
801 struct screen old_status;
802 size_t len;
803 struct grid_cell gc;
804 int utf8flag;
806 if (c->tty.sx == 0 || c->tty.sy == 0)
807 return (0);
808 memcpy(&old_status, &c->status, sizeof old_status);
809 screen_init(&c->status, c->tty.sx, 1, 0);
811 utf8flag = options_get_number(&s->options, "status-utf8");
813 len = screen_write_strlen(utf8flag, "%s", c->message_string);
814 if (len > c->tty.sx)
815 len = c->tty.sx;
817 memcpy(&gc, &grid_default_cell, sizeof gc);
818 colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
819 colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
820 gc.attr |= options_get_number(&s->options, "message-attr");
822 screen_write_start(&ctx, NULL, &c->status);
824 screen_write_cursormove(&ctx, 0, 0);
825 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->message_string);
826 for (; len < c->tty.sx; len++)
827 screen_write_putc(&ctx, &gc, ' ');
829 screen_write_stop(&ctx);
831 if (grid_compare(c->status.grid, old_status.grid) == 0) {
832 screen_free(&old_status);
833 return (0);
835 screen_free(&old_status);
836 return (1);
839 /* Enable status line prompt. */
840 void
841 status_prompt_set(struct client *c, const char *msg, const char *input,
842 int (*callbackfn)(void *, const char *), void (*freefn)(void *),
843 void *data, int flags)
845 int keys;
847 status_message_clear(c);
848 status_prompt_clear(c);
850 c->prompt_string = status_replace(c, NULL, NULL, NULL, msg,
851 time(NULL), 0);
853 c->prompt_buffer = status_replace(c, NULL, NULL, NULL, input,
854 time(NULL), 0);
855 c->prompt_index = strlen(c->prompt_buffer);
857 c->prompt_callbackfn = callbackfn;
858 c->prompt_freefn = freefn;
859 c->prompt_data = data;
861 c->prompt_hindex = 0;
863 c->prompt_flags = flags;
865 keys = options_get_number(&c->session->options, "status-keys");
866 if (keys == MODEKEY_EMACS)
867 mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit);
868 else
869 mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit);
871 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
872 c->flags |= CLIENT_STATUS;
875 /* Remove status line prompt. */
876 void
877 status_prompt_clear(struct client *c)
879 if (c->prompt_string == NULL)
880 return;
882 if (c->prompt_freefn != NULL && c->prompt_data != NULL)
883 c->prompt_freefn(c->prompt_data);
885 free(c->prompt_string);
886 c->prompt_string = NULL;
888 free(c->prompt_buffer);
889 c->prompt_buffer = NULL;
891 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
892 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
894 screen_reinit(&c->status);
897 /* Update status line prompt with a new prompt string. */
898 void
899 status_prompt_update(struct client *c, const char *msg, const char *input)
901 free(c->prompt_string);
902 c->prompt_string = status_replace(c, NULL, NULL, NULL, msg,
903 time(NULL), 0);
905 free(c->prompt_buffer);
906 c->prompt_buffer = status_replace(c, NULL, NULL, NULL, input,
907 time(NULL), 0);
908 c->prompt_index = strlen(c->prompt_buffer);
910 c->prompt_hindex = 0;
912 c->flags |= CLIENT_STATUS;
915 /* Draw client prompt on status line of present else on last line. */
917 status_prompt_redraw(struct client *c)
919 struct screen_write_ctx ctx;
920 struct session *s = c->session;
921 struct screen old_status;
922 size_t i, size, left, len, off;
923 struct grid_cell gc, *gcp;
924 int utf8flag;
926 if (c->tty.sx == 0 || c->tty.sy == 0)
927 return (0);
928 memcpy(&old_status, &c->status, sizeof old_status);
929 screen_init(&c->status, c->tty.sx, 1, 0);
931 utf8flag = options_get_number(&s->options, "status-utf8");
933 len = screen_write_strlen(utf8flag, "%s", c->prompt_string);
934 if (len > c->tty.sx)
935 len = c->tty.sx;
936 off = 0;
938 memcpy(&gc, &grid_default_cell, sizeof gc);
940 /* Change colours for command mode. */
941 if (c->prompt_mdata.mode == 1) {
942 colour_set_fg(&gc, options_get_number(&s->options, "message-command-fg"));
943 colour_set_bg(&gc, options_get_number(&s->options, "message-command-bg"));
944 gc.attr |= options_get_number(&s->options, "message-command-attr");
945 } else {
946 colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
947 colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
948 gc.attr |= options_get_number(&s->options, "message-attr");
951 screen_write_start(&ctx, NULL, &c->status);
953 screen_write_cursormove(&ctx, 0, 0);
954 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->prompt_string);
956 left = c->tty.sx - len;
957 if (left != 0) {
958 size = screen_write_strlen(utf8flag, "%s", c->prompt_buffer);
959 if (c->prompt_index >= left) {
960 off = c->prompt_index - left + 1;
961 if (c->prompt_index == size)
962 left--;
963 size = left;
965 screen_write_nputs(
966 &ctx, left, &gc, utf8flag, "%s", c->prompt_buffer + off);
968 for (i = len + size; i < c->tty.sx; i++)
969 screen_write_putc(&ctx, &gc, ' ');
972 screen_write_stop(&ctx);
974 /* Apply fake cursor. */
975 off = len + c->prompt_index - off;
976 gcp = grid_view_get_cell(c->status.grid, off, 0);
977 gcp->attr ^= GRID_ATTR_REVERSE;
979 if (grid_compare(c->status.grid, old_status.grid) == 0) {
980 screen_free(&old_status);
981 return (0);
983 screen_free(&old_status);
984 return (1);
987 /* Handle keys in prompt. */
988 void
989 status_prompt_key(struct client *c, int key)
991 struct session *sess = c->session;
992 struct options *oo = &sess->options;
993 struct paste_buffer *pb;
994 char *s, *first, *last, word[64], swapc;
995 const char *histstr;
996 const char *wsep = NULL;
997 u_char ch;
998 size_t size, n, off, idx;
1000 size = strlen(c->prompt_buffer);
1001 switch (mode_key_lookup(&c->prompt_mdata, key, NULL)) {
1002 case MODEKEYEDIT_CURSORLEFT:
1003 if (c->prompt_index > 0) {
1004 c->prompt_index--;
1005 c->flags |= CLIENT_STATUS;
1007 break;
1008 case MODEKEYEDIT_SWITCHMODE:
1009 c->flags |= CLIENT_STATUS;
1010 break;
1011 case MODEKEYEDIT_SWITCHMODEAPPEND:
1012 c->flags |= CLIENT_STATUS;
1013 /* FALLTHROUGH */
1014 case MODEKEYEDIT_CURSORRIGHT:
1015 if (c->prompt_index < size) {
1016 c->prompt_index++;
1017 c->flags |= CLIENT_STATUS;
1019 break;
1020 case MODEKEYEDIT_SWITCHMODEBEGINLINE:
1021 c->flags |= CLIENT_STATUS;
1022 /* FALLTHROUGH */
1023 case MODEKEYEDIT_STARTOFLINE:
1024 if (c->prompt_index != 0) {
1025 c->prompt_index = 0;
1026 c->flags |= CLIENT_STATUS;
1028 break;
1029 case MODEKEYEDIT_SWITCHMODEAPPENDLINE:
1030 c->flags |= CLIENT_STATUS;
1031 /* FALLTHROUGH */
1032 case MODEKEYEDIT_ENDOFLINE:
1033 if (c->prompt_index != size) {
1034 c->prompt_index = size;
1035 c->flags |= CLIENT_STATUS;
1037 break;
1038 case MODEKEYEDIT_COMPLETE:
1039 if (*c->prompt_buffer == '\0')
1040 break;
1042 idx = c->prompt_index;
1043 if (idx != 0)
1044 idx--;
1046 /* Find the word we are in. */
1047 first = c->prompt_buffer + idx;
1048 while (first > c->prompt_buffer && *first != ' ')
1049 first--;
1050 while (*first == ' ')
1051 first++;
1052 last = c->prompt_buffer + idx;
1053 while (*last != '\0' && *last != ' ')
1054 last++;
1055 while (*last == ' ')
1056 last--;
1057 if (*last != '\0')
1058 last++;
1059 if (last <= first ||
1060 ((size_t) (last - first)) > (sizeof word) - 1)
1061 break;
1062 memcpy(word, first, last - first);
1063 word[last - first] = '\0';
1065 /* And try to complete it. */
1066 if ((s = status_prompt_complete(word)) == NULL)
1067 break;
1069 /* Trim out word. */
1070 n = size - (last - c->prompt_buffer) + 1; /* with \0 */
1071 memmove(first, last, n);
1072 size -= last - first;
1074 /* Insert the new word. */
1075 size += strlen(s);
1076 off = first - c->prompt_buffer;
1077 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1);
1078 first = c->prompt_buffer + off;
1079 memmove(first + strlen(s), first, n);
1080 memcpy(first, s, strlen(s));
1082 c->prompt_index = (first - c->prompt_buffer) + strlen(s);
1083 free(s);
1085 c->flags |= CLIENT_STATUS;
1086 break;
1087 case MODEKEYEDIT_BACKSPACE:
1088 if (c->prompt_index != 0) {
1089 if (c->prompt_index == size)
1090 c->prompt_buffer[--c->prompt_index] = '\0';
1091 else {
1092 memmove(c->prompt_buffer + c->prompt_index - 1,
1093 c->prompt_buffer + c->prompt_index,
1094 size + 1 - c->prompt_index);
1095 c->prompt_index--;
1097 c->flags |= CLIENT_STATUS;
1099 break;
1100 case MODEKEYEDIT_DELETE:
1101 case MODEKEYEDIT_SWITCHMODESUBSTITUTE:
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 case MODEKEYEDIT_SWITCHMODESUBSTITUTELINE:
1111 *c->prompt_buffer = '\0';
1112 c->prompt_index = 0;
1113 c->flags |= CLIENT_STATUS;
1114 break;
1115 case MODEKEYEDIT_DELETETOENDOFLINE:
1116 case MODEKEYEDIT_SWITCHMODECHANGELINE:
1117 if (c->prompt_index < size) {
1118 c->prompt_buffer[c->prompt_index] = '\0';
1119 c->flags |= CLIENT_STATUS;
1121 break;
1122 case MODEKEYEDIT_DELETEWORD:
1123 wsep = options_get_string(oo, "word-separators");
1124 idx = c->prompt_index;
1126 /* Find a non-separator. */
1127 while (idx != 0) {
1128 idx--;
1129 if (!strchr(wsep, c->prompt_buffer[idx]))
1130 break;
1133 /* Find the separator at the beginning of the word. */
1134 while (idx != 0) {
1135 idx--;
1136 if (strchr(wsep, c->prompt_buffer[idx])) {
1137 /* Go back to the word. */
1138 idx++;
1139 break;
1143 memmove(c->prompt_buffer + idx,
1144 c->prompt_buffer + c->prompt_index,
1145 size + 1 - c->prompt_index);
1146 memset(c->prompt_buffer + size - (c->prompt_index - idx),
1147 '\0', c->prompt_index - idx);
1148 c->prompt_index = idx;
1149 c->flags |= CLIENT_STATUS;
1150 break;
1151 case MODEKEYEDIT_NEXTSPACE:
1152 wsep = " ";
1153 /* FALLTHROUGH */
1154 case MODEKEYEDIT_NEXTWORD:
1155 if (wsep == NULL)
1156 wsep = options_get_string(oo, "word-separators");
1158 /* Find a separator. */
1159 while (c->prompt_index != size) {
1160 c->prompt_index++;
1161 if (strchr(wsep, c->prompt_buffer[c->prompt_index]))
1162 break;
1165 /* Find the word right after the separation. */
1166 while (c->prompt_index != size) {
1167 c->prompt_index++;
1168 if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1169 break;
1172 c->flags |= CLIENT_STATUS;
1173 break;
1174 case MODEKEYEDIT_NEXTSPACEEND:
1175 wsep = " ";
1176 /* FALLTHROUGH */
1177 case MODEKEYEDIT_NEXTWORDEND:
1178 if (wsep == NULL)
1179 wsep = options_get_string(oo, "word-separators");
1181 /* Find a word. */
1182 while (c->prompt_index != size) {
1183 c->prompt_index++;
1184 if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1185 break;
1188 /* Find the separator at the end of the word. */
1189 while (c->prompt_index != size) {
1190 c->prompt_index++;
1191 if (strchr(wsep, c->prompt_buffer[c->prompt_index]))
1192 break;
1195 /* Back up to the end-of-word like vi. */
1196 if (options_get_number(oo, "status-keys") == MODEKEY_VI &&
1197 c->prompt_index != 0)
1198 c->prompt_index--;
1200 c->flags |= CLIENT_STATUS;
1201 break;
1202 case MODEKEYEDIT_PREVIOUSSPACE:
1203 wsep = " ";
1204 /* FALLTHROUGH */
1205 case MODEKEYEDIT_PREVIOUSWORD:
1206 if (wsep == NULL)
1207 wsep = options_get_string(oo, "word-separators");
1209 /* Find a non-separator. */
1210 while (c->prompt_index != 0) {
1211 c->prompt_index--;
1212 if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1213 break;
1216 /* Find the separator at the beginning of the word. */
1217 while (c->prompt_index != 0) {
1218 c->prompt_index--;
1219 if (strchr(wsep, c->prompt_buffer[c->prompt_index])) {
1220 /* Go back to the word. */
1221 c->prompt_index++;
1222 break;
1226 c->flags |= CLIENT_STATUS;
1227 break;
1228 case MODEKEYEDIT_HISTORYUP:
1229 histstr = status_prompt_up_history(&c->prompt_hindex);
1230 if (histstr == NULL)
1231 break;
1232 free(c->prompt_buffer);
1233 c->prompt_buffer = xstrdup(histstr);
1234 c->prompt_index = strlen(c->prompt_buffer);
1235 c->flags |= CLIENT_STATUS;
1236 break;
1237 case MODEKEYEDIT_HISTORYDOWN:
1238 histstr = status_prompt_down_history(&c->prompt_hindex);
1239 if (histstr == NULL)
1240 break;
1241 free(c->prompt_buffer);
1242 c->prompt_buffer = xstrdup(histstr);
1243 c->prompt_index = strlen(c->prompt_buffer);
1244 c->flags |= CLIENT_STATUS;
1245 break;
1246 case MODEKEYEDIT_PASTE:
1247 if ((pb = paste_get_top(&global_buffers)) == NULL)
1248 break;
1249 for (n = 0; n < pb->size; n++) {
1250 ch = (u_char) pb->data[n];
1251 if (ch < 32 || ch == 127)
1252 break;
1255 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1);
1256 if (c->prompt_index == size) {
1257 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1258 c->prompt_index += n;
1259 c->prompt_buffer[c->prompt_index] = '\0';
1260 } else {
1261 memmove(c->prompt_buffer + c->prompt_index + n,
1262 c->prompt_buffer + c->prompt_index,
1263 size + 1 - c->prompt_index);
1264 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1265 c->prompt_index += n;
1268 c->flags |= CLIENT_STATUS;
1269 break;
1270 case MODEKEYEDIT_TRANSPOSECHARS:
1271 idx = c->prompt_index;
1272 if (idx < size)
1273 idx++;
1274 if (idx >= 2) {
1275 swapc = c->prompt_buffer[idx - 2];
1276 c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1];
1277 c->prompt_buffer[idx - 1] = swapc;
1278 c->prompt_index = idx;
1279 c->flags |= CLIENT_STATUS;
1281 break;
1282 case MODEKEYEDIT_ENTER:
1283 if (*c->prompt_buffer != '\0')
1284 status_prompt_add_history(c->prompt_buffer);
1285 if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0)
1286 status_prompt_clear(c);
1287 break;
1288 case MODEKEYEDIT_CANCEL:
1289 if (c->prompt_callbackfn(c->prompt_data, NULL) == 0)
1290 status_prompt_clear(c);
1291 break;
1292 case MODEKEY_OTHER:
1293 if ((key & 0xff00) != 0 || key < 32 || key == 127)
1294 break;
1295 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2);
1297 if (c->prompt_index == size) {
1298 c->prompt_buffer[c->prompt_index++] = key;
1299 c->prompt_buffer[c->prompt_index] = '\0';
1300 } else {
1301 memmove(c->prompt_buffer + c->prompt_index + 1,
1302 c->prompt_buffer + c->prompt_index,
1303 size + 1 - c->prompt_index);
1304 c->prompt_buffer[c->prompt_index++] = key;
1307 if (c->prompt_flags & PROMPT_SINGLE) {
1308 if (c->prompt_callbackfn(
1309 c->prompt_data, c->prompt_buffer) == 0)
1310 status_prompt_clear(c);
1313 c->flags |= CLIENT_STATUS;
1314 break;
1315 default:
1316 break;
1320 /* Get previous line from the history. */
1321 const char *
1322 status_prompt_up_history(u_int *idx)
1324 u_int size;
1327 * History runs from 0 to size - 1.
1329 * Index is from 0 to size. Zero is empty.
1332 size = ARRAY_LENGTH(&status_prompt_history);
1333 if (size == 0 || *idx == size)
1334 return (NULL);
1335 (*idx)++;
1336 return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1339 /* Get next line from the history. */
1340 const char *
1341 status_prompt_down_history(u_int *idx)
1343 u_int size;
1345 size = ARRAY_LENGTH(&status_prompt_history);
1346 if (size == 0 || *idx == 0)
1347 return ("");
1348 (*idx)--;
1349 if (*idx == 0)
1350 return ("");
1351 return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1354 /* Add line to the history. */
1355 void
1356 status_prompt_add_history(const char *line)
1358 u_int size;
1360 size = ARRAY_LENGTH(&status_prompt_history);
1361 if (size > 0 && strcmp(ARRAY_LAST(&status_prompt_history), line) == 0)
1362 return;
1364 if (size == PROMPT_HISTORY) {
1365 free(ARRAY_FIRST(&status_prompt_history));
1366 ARRAY_REMOVE(&status_prompt_history, 0);
1369 ARRAY_ADD(&status_prompt_history, xstrdup(line));
1372 /* Complete word. */
1373 char *
1374 status_prompt_complete(const char *s)
1376 const struct cmd_entry **cmdent;
1377 const struct options_table_entry *oe;
1378 ARRAY_DECL(, const char *) list;
1379 char *prefix, *s2;
1380 u_int i;
1381 size_t j;
1383 if (*s == '\0')
1384 return (NULL);
1386 /* First, build a list of all the possible matches. */
1387 ARRAY_INIT(&list);
1388 for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1389 if (strncmp((*cmdent)->name, s, strlen(s)) == 0)
1390 ARRAY_ADD(&list, (*cmdent)->name);
1392 for (oe = server_options_table; oe->name != NULL; oe++) {
1393 if (strncmp(oe->name, s, strlen(s)) == 0)
1394 ARRAY_ADD(&list, oe->name);
1396 for (oe = session_options_table; oe->name != NULL; oe++) {
1397 if (strncmp(oe->name, s, strlen(s)) == 0)
1398 ARRAY_ADD(&list, oe->name);
1400 for (oe = window_options_table; oe->name != NULL; oe++) {
1401 if (strncmp(oe->name, s, strlen(s)) == 0)
1402 ARRAY_ADD(&list, oe->name);
1405 /* If none, bail now. */
1406 if (ARRAY_LENGTH(&list) == 0) {
1407 ARRAY_FREE(&list);
1408 return (NULL);
1411 /* If an exact match, return it, with a trailing space. */
1412 if (ARRAY_LENGTH(&list) == 1) {
1413 xasprintf(&s2, "%s ", ARRAY_FIRST(&list));
1414 ARRAY_FREE(&list);
1415 return (s2);
1418 /* Now loop through the list and find the longest common prefix. */
1419 prefix = xstrdup(ARRAY_FIRST(&list));
1420 for (i = 1; i < ARRAY_LENGTH(&list); i++) {
1421 s = ARRAY_ITEM(&list, i);
1423 j = strlen(s);
1424 if (j > strlen(prefix))
1425 j = strlen(prefix);
1426 for (; j > 0; j--) {
1427 if (prefix[j - 1] != s[j - 1])
1428 prefix[j - 1] = '\0';
1432 ARRAY_FREE(&list);
1433 return (prefix);