Change terminal-overrides to a server option (now that we have them), it
[tmux-openbsd.git] / status.c
blob84589427dc585a41178c1f0e52479a9494c6d22e
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 size_t leftlen;
85 style_apply_update(gc, &s->options, "status-left-style");
87 left = status_replace(c, NULL,
88 NULL, NULL, options_get_string(&s->options, "status-left"), t, 1);
90 *size = options_get_number(&s->options, "status-left-length");
91 leftlen = screen_write_cstrlen(utf8flag, "%s", left);
92 if (leftlen < *size)
93 *size = leftlen;
94 return (left);
97 /* Retrieve options for right string. */
98 char *
99 status_redraw_get_right(struct client *c,
100 time_t t, int utf8flag, struct grid_cell *gc, size_t *size)
102 struct session *s = c->session;
103 char *right;
104 size_t rightlen;
106 style_apply_update(gc, &s->options, "status-right-style");
108 right = status_replace(c, NULL,
109 NULL, NULL, options_get_string(&s->options, "status-right"), t, 1);
111 *size = options_get_number(&s->options, "status-right-length");
112 rightlen = screen_write_cstrlen(utf8flag, "%s", right);
113 if (rightlen < *size)
114 *size = rightlen;
115 return (right);
118 /* Set window at window list position. */
119 void
120 status_set_window_at(struct client *c, u_int x)
122 struct session *s = c->session;
123 struct winlink *wl;
125 x += c->wlmouse;
126 RB_FOREACH(wl, winlinks, &s->windows) {
127 if (x < wl->status_width && session_select(s, wl->idx) == 0)
128 server_redraw_session(s);
129 x -= wl->status_width + 1;
133 /* Draw status for client on the last lines of given context. */
135 status_redraw(struct client *c)
137 struct screen_write_ctx ctx;
138 struct session *s = c->session;
139 struct winlink *wl;
140 struct screen old_status, window_list;
141 struct grid_cell stdgc, lgc, rgc, gc;
142 struct options *oo;
143 time_t t;
144 char *left, *right, *sep;
145 u_int offset, needed;
146 u_int wlstart, wlwidth, wlavailable, wloffset, wlsize;
147 size_t llen, rlen, seplen;
148 int larrow, rarrow, utf8flag;
150 /* No status line? */
151 if (c->tty.sy == 0 || !options_get_number(&s->options, "status"))
152 return (1);
153 left = right = NULL;
154 larrow = rarrow = 0;
156 /* Update status timer. */
157 if (gettimeofday(&c->status_timer, NULL) != 0)
158 fatal("gettimeofday failed");
159 t = c->status_timer.tv_sec;
161 /* Set up default colour. */
162 style_apply(&stdgc, &s->options, "status-style");
164 /* Create the target screen. */
165 memcpy(&old_status, &c->status, sizeof old_status);
166 screen_init(&c->status, c->tty.sx, 1, 0);
167 screen_write_start(&ctx, NULL, &c->status);
168 for (offset = 0; offset < c->tty.sx; offset++)
169 screen_write_putc(&ctx, &stdgc, ' ');
170 screen_write_stop(&ctx);
172 /* If the height is one line, blank status line. */
173 if (c->tty.sy <= 1)
174 goto out;
176 /* Get UTF-8 flag. */
177 utf8flag = options_get_number(&s->options, "status-utf8");
179 /* Work out left and right strings. */
180 memcpy(&lgc, &stdgc, sizeof lgc);
181 left = status_redraw_get_left(c, t, utf8flag, &lgc, &llen);
182 memcpy(&rgc, &stdgc, sizeof rgc);
183 right = status_redraw_get_right(c, t, utf8flag, &rgc, &rlen);
186 * Figure out how much space we have for the window list. If there
187 * isn't enough space, just show a blank status line.
189 needed = 0;
190 if (llen != 0)
191 needed += llen + 1;
192 if (rlen != 0)
193 needed += rlen + 1;
194 if (c->tty.sx == 0 || c->tty.sx <= needed)
195 goto out;
196 wlavailable = c->tty.sx - needed;
198 /* Calculate the total size needed for the window list. */
199 wlstart = wloffset = wlwidth = 0;
200 RB_FOREACH(wl, winlinks, &s->windows) {
201 free(wl->status_text);
202 memcpy(&wl->status_cell, &stdgc, sizeof wl->status_cell);
203 wl->status_text = status_print(c, wl, t, &wl->status_cell);
204 wl->status_width =
205 screen_write_cstrlen(utf8flag, "%s", wl->status_text);
207 if (wl == s->curw)
208 wloffset = wlwidth;
210 oo = &wl->window->options;
211 sep = options_get_string(oo, "window-status-separator");
212 seplen = screen_write_strlen(utf8flag, "%s", sep);
213 wlwidth += wl->status_width + seplen;
216 /* Create a new screen for the window list. */
217 screen_init(&window_list, wlwidth, 1, 0);
219 /* And draw the window list into it. */
220 screen_write_start(&ctx, NULL, &window_list);
221 RB_FOREACH(wl, winlinks, &s->windows) {
222 screen_write_cnputs(&ctx,
223 -1, &wl->status_cell, utf8flag, "%s", wl->status_text);
225 oo = &wl->window->options;
226 sep = options_get_string(oo, "window-status-separator");
227 screen_write_nputs(&ctx, -1, &stdgc, utf8flag, "%s", sep);
229 screen_write_stop(&ctx);
231 /* If there is enough space for the total width, skip to draw now. */
232 if (wlwidth <= wlavailable)
233 goto draw;
235 /* Find size of current window text. */
236 wlsize = s->curw->status_width;
239 * If the current window is already on screen, good to draw from the
240 * start and just leave off the end.
242 if (wloffset + wlsize < wlavailable) {
243 if (wlavailable > 0) {
244 rarrow = 1;
245 wlavailable--;
247 wlwidth = wlavailable;
248 } else {
250 * Work out how many characters we need to omit from the
251 * start. There are wlavailable characters to fill, and
252 * wloffset + wlsize must be the last. So, the start character
253 * is wloffset + wlsize - wlavailable.
255 if (wlavailable > 0) {
256 larrow = 1;
257 wlavailable--;
260 wlstart = wloffset + wlsize - wlavailable;
261 if (wlavailable > 0 && wlwidth > wlstart + wlavailable + 1) {
262 rarrow = 1;
263 wlstart++;
264 wlavailable--;
266 wlwidth = wlavailable;
269 /* Bail if anything is now too small too. */
270 if (wlwidth == 0 || wlavailable == 0) {
271 screen_free(&window_list);
272 goto out;
276 * Now the start position is known, work out the state of the left and
277 * right arrows.
279 offset = 0;
280 RB_FOREACH(wl, winlinks, &s->windows) {
281 if (wl->flags & WINLINK_ALERTFLAGS &&
282 larrow == 1 && offset < wlstart)
283 larrow = -1;
285 offset += wl->status_width;
287 if (wl->flags & WINLINK_ALERTFLAGS &&
288 rarrow == 1 && offset > wlstart + wlwidth)
289 rarrow = -1;
292 draw:
293 /* Begin drawing. */
294 screen_write_start(&ctx, NULL, &c->status);
296 /* Draw the left string and arrow. */
297 screen_write_cursormove(&ctx, 0, 0);
298 if (llen != 0) {
299 screen_write_cnputs(&ctx, llen, &lgc, utf8flag, "%s", left);
300 screen_write_putc(&ctx, &stdgc, ' ');
302 if (larrow != 0) {
303 memcpy(&gc, &stdgc, sizeof gc);
304 if (larrow == -1)
305 gc.attr ^= GRID_ATTR_REVERSE;
306 screen_write_putc(&ctx, &gc, '<');
309 /* Draw the right string and arrow. */
310 if (rarrow != 0) {
311 screen_write_cursormove(&ctx, c->tty.sx - rlen - 2, 0);
312 memcpy(&gc, &stdgc, sizeof gc);
313 if (rarrow == -1)
314 gc.attr ^= GRID_ATTR_REVERSE;
315 screen_write_putc(&ctx, &gc, '>');
316 } else
317 screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, 0);
318 if (rlen != 0) {
319 screen_write_putc(&ctx, &stdgc, ' ');
320 screen_write_cnputs(&ctx, rlen, &rgc, utf8flag, "%s", right);
323 /* Figure out the offset for the window list. */
324 if (llen != 0)
325 wloffset = llen + 1;
326 else
327 wloffset = 0;
328 if (wlwidth < wlavailable) {
329 switch (options_get_number(&s->options, "status-justify")) {
330 case 1: /* centered */
331 wloffset += (wlavailable - wlwidth) / 2;
332 break;
333 case 2: /* right */
334 wloffset += (wlavailable - wlwidth);
335 break;
338 if (larrow != 0)
339 wloffset++;
341 /* Copy the window list. */
342 c->wlmouse = -wloffset + wlstart;
343 screen_write_cursormove(&ctx, wloffset, 0);
344 screen_write_copy(&ctx, &window_list, wlstart, 0, wlwidth, 1);
345 screen_free(&window_list);
347 screen_write_stop(&ctx);
349 out:
350 free(left);
351 free(right);
353 if (grid_compare(c->status.grid, old_status.grid) == 0) {
354 screen_free(&old_status);
355 return (0);
357 screen_free(&old_status);
358 return (1);
361 /* Replace a single special sequence (prefixed by #). */
362 void
363 status_replace1(struct client *c, char **iptr, char **optr, char *out,
364 size_t outsize, int jobsflag)
366 char ch, tmp[256], *ptr, *endptr;
367 size_t ptrlen;
368 long limit;
370 errno = 0;
371 limit = strtol(*iptr, &endptr, 10);
372 if ((limit == 0 && errno != EINVAL) ||
373 (limit == LONG_MIN && errno != ERANGE) ||
374 (limit == LONG_MAX && errno != ERANGE) ||
375 limit != 0)
376 *iptr = endptr;
377 if (limit <= 0)
378 limit = LONG_MAX;
380 switch (*(*iptr)++) {
381 case '(':
382 if (!jobsflag) {
383 ch = ')';
384 goto skip_to;
386 if ((ptr = status_find_job(c, iptr)) == NULL)
387 return;
388 goto do_replace;
389 case '[':
391 * Embedded style, handled at display time. Leave present and
392 * skip input until ].
394 ch = ']';
395 goto skip_to;
396 case '{':
397 ptr = (char *) "#{";
398 goto do_replace;
399 case '#':
400 *(*optr)++ = '#';
401 break;
402 default:
403 xsnprintf(tmp, sizeof tmp, "#%c", *(*iptr - 1));
404 ptr = tmp;
405 goto do_replace;
408 return;
410 do_replace:
411 ptrlen = strlen(ptr);
412 if ((size_t) limit < ptrlen)
413 ptrlen = limit;
415 if (*optr + ptrlen >= out + outsize - 1)
416 return;
417 while (ptrlen > 0 && *ptr != '\0') {
418 *(*optr)++ = *ptr++;
419 ptrlen--;
422 return;
424 skip_to:
425 *(*optr)++ = '#';
427 (*iptr)--; /* include ch */
428 while (**iptr != ch && **iptr != '\0') {
429 if (*optr >= out + outsize - 1)
430 break;
431 *(*optr)++ = *(*iptr)++;
435 /* Replace special sequences in fmt. */
436 char *
437 status_replace(struct client *c, struct session *s, struct winlink *wl,
438 struct window_pane *wp, const char *fmt, time_t t, int jobsflag)
440 static char out[BUFSIZ];
441 char in[BUFSIZ], ch, *iptr, *optr, *expanded;
442 size_t len;
443 struct format_tree *ft;
445 if (fmt == NULL)
446 return (xstrdup(""));
448 if (s == NULL && c != NULL)
449 s = c->session;
450 if (wl == NULL && s != NULL)
451 wl = s->curw;
452 if (wp == NULL && wl != NULL)
453 wp = wl->window->active;
455 len = strftime(in, sizeof in, fmt, localtime(&t));
456 in[len] = '\0';
458 iptr = in;
459 optr = out;
461 while (*iptr != '\0') {
462 if (optr >= out + (sizeof out) - 1)
463 break;
464 ch = *iptr++;
466 if (ch != '#' || *iptr == '\0') {
467 *optr++ = ch;
468 continue;
470 status_replace1(c, &iptr, &optr, out, sizeof out, jobsflag);
472 *optr = '\0';
474 ft = format_create();
475 if (c != NULL)
476 format_client(ft, c);
477 if (s != NULL)
478 format_session(ft, s);
479 if (s != NULL && wl != NULL)
480 format_winlink(ft, s, wl);
481 if (wp != NULL)
482 format_window_pane(ft, wp);
483 expanded = format_expand(ft, out);
484 format_free(ft);
485 return (expanded);
488 /* Figure out job name and get its result, starting it off if necessary. */
489 char *
490 status_find_job(struct client *c, char **iptr)
492 struct status_out *so, so_find;
493 char *cmd;
494 int lastesc;
495 size_t len;
497 if (**iptr == '\0')
498 return (NULL);
499 if (**iptr == ')') { /* no command given */
500 (*iptr)++;
501 return (NULL);
504 cmd = xmalloc(strlen(*iptr) + 1);
505 len = 0;
507 lastesc = 0;
508 for (; **iptr != '\0'; (*iptr)++) {
509 if (!lastesc && **iptr == ')')
510 break; /* unescaped ) is the end */
511 if (!lastesc && **iptr == '\\') {
512 lastesc = 1;
513 continue; /* skip \ if not escaped */
515 lastesc = 0;
516 cmd[len++] = **iptr;
518 if (**iptr == '\0') /* no terminating ) */ {
519 free(cmd);
520 return (NULL);
522 (*iptr)++; /* skip final ) */
523 cmd[len] = '\0';
525 /* First try in the new tree. */
526 so_find.cmd = cmd;
527 so = RB_FIND(status_out_tree, &c->status_new, &so_find);
528 if (so != NULL && so->out != NULL) {
529 free(cmd);
530 return (so->out);
533 /* If not found at all, start the job and add to the tree. */
534 if (so == NULL) {
535 job_run(cmd, NULL, status_job_callback, status_job_free, c);
536 c->references++;
538 so = xmalloc(sizeof *so);
539 so->cmd = xstrdup(cmd);
540 so->out = NULL;
541 RB_INSERT(status_out_tree, &c->status_new, so);
544 /* Lookup in the old tree. */
545 so_find.cmd = cmd;
546 so = RB_FIND(status_out_tree, &c->status_old, &so_find);
547 free(cmd);
548 if (so != NULL)
549 return (so->out);
550 return (NULL);
553 /* Free job tree. */
554 void
555 status_free_jobs(struct status_out_tree *sotree)
557 struct status_out *so, *so_next;
559 so_next = RB_MIN(status_out_tree, sotree);
560 while (so_next != NULL) {
561 so = so_next;
562 so_next = RB_NEXT(status_out_tree, sotree, so);
564 RB_REMOVE(status_out_tree, sotree, so);
565 free(so->out);
566 free(so->cmd);
567 free(so);
571 /* Update jobs on status interval. */
572 void
573 status_update_jobs(struct client *c)
575 /* Free the old tree. */
576 status_free_jobs(&c->status_old);
578 /* Move the new to old. */
579 memcpy(&c->status_old, &c->status_new, sizeof c->status_old);
580 RB_INIT(&c->status_new);
583 /* Free status job. */
584 void
585 status_job_free(void *data)
587 struct client *c = data;
589 c->references--;
592 /* Job has finished: save its result. */
593 void
594 status_job_callback(struct job *job)
596 struct client *c = job->data;
597 struct status_out *so, so_find;
598 char *line, *buf;
599 size_t len;
601 if (c->flags & CLIENT_DEAD)
602 return;
604 so_find.cmd = job->cmd;
605 so = RB_FIND(status_out_tree, &c->status_new, &so_find);
606 if (so == NULL || so->out != NULL)
607 return;
609 buf = NULL;
610 if ((line = evbuffer_readline(job->event->input)) == NULL) {
611 len = EVBUFFER_LENGTH(job->event->input);
612 buf = xmalloc(len + 1);
613 if (len != 0)
614 memcpy(buf, EVBUFFER_DATA(job->event->input), len);
615 buf[len] = '\0';
616 } else
617 buf = line;
619 so->out = buf;
620 server_status_client(c);
623 /* Return winlink status line entry and adjust gc as necessary. */
624 char *
625 status_print(
626 struct client *c, struct winlink *wl, time_t t, struct grid_cell *gc)
628 struct options *oo = &wl->window->options;
629 struct session *s = c->session;
630 const char *fmt;
631 char *text;
633 style_apply_update(gc, oo, "window-status-style");
634 fmt = options_get_string(oo, "window-status-format");
635 if (wl == s->curw) {
636 style_apply_update(gc, oo, "window-status-current-style");
637 fmt = options_get_string(oo, "window-status-current-format");
639 if (wl == TAILQ_FIRST(&s->lastw))
640 style_apply_update(gc, oo, "window-status-last-style");
642 if (wl->flags & WINLINK_BELL)
643 style_apply_update(gc, oo, "window-status-bell-style");
644 else if (wl->flags & WINLINK_CONTENT)
645 style_apply_update(gc, oo, "window-status-content-style");
646 else if (wl->flags & (WINLINK_ACTIVITY|WINLINK_SILENCE))
647 style_apply_update(gc, oo, "window-status-activity-style");
649 text = status_replace(c, NULL, wl, NULL, fmt, t, 1);
650 return (text);
653 /* Set a status line message. */
654 void printflike2
655 status_message_set(struct client *c, const char *fmt, ...)
657 struct timeval tv;
658 struct session *s = c->session;
659 struct message_entry *msg;
660 va_list ap;
661 int delay;
662 u_int i, limit;
664 status_prompt_clear(c);
665 status_message_clear(c);
667 va_start(ap, fmt);
668 xvasprintf(&c->message_string, fmt, ap);
669 va_end(ap);
671 ARRAY_EXPAND(&c->message_log, 1);
672 msg = &ARRAY_LAST(&c->message_log);
673 msg->msg_time = time(NULL);
674 msg->msg = xstrdup(c->message_string);
676 if (s == NULL)
677 limit = 0;
678 else
679 limit = options_get_number(&s->options, "message-limit");
680 if (ARRAY_LENGTH(&c->message_log) > limit) {
681 limit = ARRAY_LENGTH(&c->message_log) - limit;
682 for (i = 0; i < limit; i++) {
683 msg = &ARRAY_FIRST(&c->message_log);
684 free(msg->msg);
685 ARRAY_REMOVE(&c->message_log, 0);
689 delay = options_get_number(&c->session->options, "display-time");
690 tv.tv_sec = delay / 1000;
691 tv.tv_usec = (delay % 1000) * 1000L;
693 if (event_initialized(&c->message_timer))
694 evtimer_del(&c->message_timer);
695 evtimer_set(&c->message_timer, status_message_callback, c);
696 evtimer_add(&c->message_timer, &tv);
698 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
699 c->flags |= CLIENT_STATUS;
702 /* Clear status line message. */
703 void
704 status_message_clear(struct client *c)
706 if (c->message_string == NULL)
707 return;
709 free(c->message_string);
710 c->message_string = NULL;
712 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
713 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
715 screen_reinit(&c->status);
718 /* Clear status line message after timer expires. */
719 void
720 status_message_callback(unused int fd, unused short event, void *data)
722 struct client *c = data;
724 status_message_clear(c);
727 /* Draw client message on status line of present else on last line. */
729 status_message_redraw(struct client *c)
731 struct screen_write_ctx ctx;
732 struct session *s = c->session;
733 struct screen old_status;
734 size_t len;
735 struct grid_cell gc;
736 int utf8flag;
738 if (c->tty.sx == 0 || c->tty.sy == 0)
739 return (0);
740 memcpy(&old_status, &c->status, sizeof old_status);
741 screen_init(&c->status, c->tty.sx, 1, 0);
743 utf8flag = options_get_number(&s->options, "status-utf8");
745 len = screen_write_strlen(utf8flag, "%s", c->message_string);
746 if (len > c->tty.sx)
747 len = c->tty.sx;
749 style_apply(&gc, &s->options, "message-style");
751 screen_write_start(&ctx, NULL, &c->status);
753 screen_write_cursormove(&ctx, 0, 0);
754 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->message_string);
755 for (; len < c->tty.sx; len++)
756 screen_write_putc(&ctx, &gc, ' ');
758 screen_write_stop(&ctx);
760 if (grid_compare(c->status.grid, old_status.grid) == 0) {
761 screen_free(&old_status);
762 return (0);
764 screen_free(&old_status);
765 return (1);
768 /* Enable status line prompt. */
769 void
770 status_prompt_set(struct client *c, const char *msg, const char *input,
771 int (*callbackfn)(void *, const char *), void (*freefn)(void *),
772 void *data, int flags)
774 int keys;
776 status_message_clear(c);
777 status_prompt_clear(c);
779 c->prompt_string = status_replace(c, NULL, NULL, NULL, msg,
780 time(NULL), 0);
782 c->prompt_buffer = status_replace(c, NULL, NULL, NULL, input,
783 time(NULL), 0);
784 c->prompt_index = strlen(c->prompt_buffer);
786 c->prompt_callbackfn = callbackfn;
787 c->prompt_freefn = freefn;
788 c->prompt_data = data;
790 c->prompt_hindex = 0;
792 c->prompt_flags = flags;
794 keys = options_get_number(&c->session->options, "status-keys");
795 if (keys == MODEKEY_EMACS)
796 mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit);
797 else
798 mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit);
800 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
801 c->flags |= CLIENT_STATUS;
804 /* Remove status line prompt. */
805 void
806 status_prompt_clear(struct client *c)
808 if (c->prompt_string == NULL)
809 return;
811 if (c->prompt_freefn != NULL && c->prompt_data != NULL)
812 c->prompt_freefn(c->prompt_data);
814 free(c->prompt_string);
815 c->prompt_string = NULL;
817 free(c->prompt_buffer);
818 c->prompt_buffer = NULL;
820 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
821 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
823 screen_reinit(&c->status);
826 /* Update status line prompt with a new prompt string. */
827 void
828 status_prompt_update(struct client *c, const char *msg, const char *input)
830 free(c->prompt_string);
831 c->prompt_string = status_replace(c, NULL, NULL, NULL, msg,
832 time(NULL), 0);
834 free(c->prompt_buffer);
835 c->prompt_buffer = status_replace(c, NULL, NULL, NULL, input,
836 time(NULL), 0);
837 c->prompt_index = strlen(c->prompt_buffer);
839 c->prompt_hindex = 0;
841 c->flags |= CLIENT_STATUS;
844 /* Draw client prompt on status line of present else on last line. */
846 status_prompt_redraw(struct client *c)
848 struct screen_write_ctx ctx;
849 struct session *s = c->session;
850 struct screen old_status;
851 size_t i, size, left, len, off;
852 struct grid_cell gc, *gcp;
853 int utf8flag;
855 if (c->tty.sx == 0 || c->tty.sy == 0)
856 return (0);
857 memcpy(&old_status, &c->status, sizeof old_status);
858 screen_init(&c->status, c->tty.sx, 1, 0);
860 utf8flag = options_get_number(&s->options, "status-utf8");
862 len = screen_write_strlen(utf8flag, "%s", c->prompt_string);
863 if (len > c->tty.sx)
864 len = c->tty.sx;
865 off = 0;
867 /* Change colours for command mode. */
868 if (c->prompt_mdata.mode == 1)
869 style_apply(&gc, &s->options, "message-command-style");
870 else
871 style_apply(&gc, &s->options, "message-style");
873 screen_write_start(&ctx, NULL, &c->status);
875 screen_write_cursormove(&ctx, 0, 0);
876 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->prompt_string);
878 left = c->tty.sx - len;
879 if (left != 0) {
880 size = screen_write_strlen(utf8flag, "%s", c->prompt_buffer);
881 if (c->prompt_index >= left) {
882 off = c->prompt_index - left + 1;
883 if (c->prompt_index == size)
884 left--;
885 size = left;
887 screen_write_nputs(
888 &ctx, left, &gc, utf8flag, "%s", c->prompt_buffer + off);
890 for (i = len + size; i < c->tty.sx; i++)
891 screen_write_putc(&ctx, &gc, ' ');
894 screen_write_stop(&ctx);
896 /* Apply fake cursor. */
897 off = len + c->prompt_index - off;
898 gcp = grid_view_get_cell(c->status.grid, off, 0);
899 gcp->attr ^= GRID_ATTR_REVERSE;
901 if (grid_compare(c->status.grid, old_status.grid) == 0) {
902 screen_free(&old_status);
903 return (0);
905 screen_free(&old_status);
906 return (1);
909 /* Handle keys in prompt. */
910 void
911 status_prompt_key(struct client *c, int key)
913 struct session *sess = c->session;
914 struct options *oo = &sess->options;
915 struct paste_buffer *pb;
916 char *s, *first, *last, word[64], swapc;
917 const char *histstr;
918 const char *wsep = NULL;
919 u_char ch;
920 size_t size, n, off, idx;
922 size = strlen(c->prompt_buffer);
923 switch (mode_key_lookup(&c->prompt_mdata, key, NULL)) {
924 case MODEKEYEDIT_CURSORLEFT:
925 if (c->prompt_index > 0) {
926 c->prompt_index--;
927 c->flags |= CLIENT_STATUS;
929 break;
930 case MODEKEYEDIT_SWITCHMODE:
931 c->flags |= CLIENT_STATUS;
932 break;
933 case MODEKEYEDIT_SWITCHMODEAPPEND:
934 c->flags |= CLIENT_STATUS;
935 /* FALLTHROUGH */
936 case MODEKEYEDIT_CURSORRIGHT:
937 if (c->prompt_index < size) {
938 c->prompt_index++;
939 c->flags |= CLIENT_STATUS;
941 break;
942 case MODEKEYEDIT_SWITCHMODEBEGINLINE:
943 c->flags |= CLIENT_STATUS;
944 /* FALLTHROUGH */
945 case MODEKEYEDIT_STARTOFLINE:
946 if (c->prompt_index != 0) {
947 c->prompt_index = 0;
948 c->flags |= CLIENT_STATUS;
950 break;
951 case MODEKEYEDIT_SWITCHMODEAPPENDLINE:
952 c->flags |= CLIENT_STATUS;
953 /* FALLTHROUGH */
954 case MODEKEYEDIT_ENDOFLINE:
955 if (c->prompt_index != size) {
956 c->prompt_index = size;
957 c->flags |= CLIENT_STATUS;
959 break;
960 case MODEKEYEDIT_COMPLETE:
961 if (*c->prompt_buffer == '\0')
962 break;
964 idx = c->prompt_index;
965 if (idx != 0)
966 idx--;
968 /* Find the word we are in. */
969 first = c->prompt_buffer + idx;
970 while (first > c->prompt_buffer && *first != ' ')
971 first--;
972 while (*first == ' ')
973 first++;
974 last = c->prompt_buffer + idx;
975 while (*last != '\0' && *last != ' ')
976 last++;
977 while (*last == ' ')
978 last--;
979 if (*last != '\0')
980 last++;
981 if (last <= first ||
982 ((size_t) (last - first)) > (sizeof word) - 1)
983 break;
984 memcpy(word, first, last - first);
985 word[last - first] = '\0';
987 /* And try to complete it. */
988 if ((s = status_prompt_complete(word)) == NULL)
989 break;
991 /* Trim out word. */
992 n = size - (last - c->prompt_buffer) + 1; /* with \0 */
993 memmove(first, last, n);
994 size -= last - first;
996 /* Insert the new word. */
997 size += strlen(s);
998 off = first - c->prompt_buffer;
999 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1);
1000 first = c->prompt_buffer + off;
1001 memmove(first + strlen(s), first, n);
1002 memcpy(first, s, strlen(s));
1004 c->prompt_index = (first - c->prompt_buffer) + strlen(s);
1005 free(s);
1007 c->flags |= CLIENT_STATUS;
1008 break;
1009 case MODEKEYEDIT_BACKSPACE:
1010 if (c->prompt_index != 0) {
1011 if (c->prompt_index == size)
1012 c->prompt_buffer[--c->prompt_index] = '\0';
1013 else {
1014 memmove(c->prompt_buffer + c->prompt_index - 1,
1015 c->prompt_buffer + c->prompt_index,
1016 size + 1 - c->prompt_index);
1017 c->prompt_index--;
1019 c->flags |= CLIENT_STATUS;
1021 break;
1022 case MODEKEYEDIT_DELETE:
1023 case MODEKEYEDIT_SWITCHMODESUBSTITUTE:
1024 if (c->prompt_index != size) {
1025 memmove(c->prompt_buffer + c->prompt_index,
1026 c->prompt_buffer + c->prompt_index + 1,
1027 size + 1 - c->prompt_index);
1028 c->flags |= CLIENT_STATUS;
1030 break;
1031 case MODEKEYEDIT_DELETELINE:
1032 case MODEKEYEDIT_SWITCHMODESUBSTITUTELINE:
1033 *c->prompt_buffer = '\0';
1034 c->prompt_index = 0;
1035 c->flags |= CLIENT_STATUS;
1036 break;
1037 case MODEKEYEDIT_DELETETOENDOFLINE:
1038 case MODEKEYEDIT_SWITCHMODECHANGELINE:
1039 if (c->prompt_index < size) {
1040 c->prompt_buffer[c->prompt_index] = '\0';
1041 c->flags |= CLIENT_STATUS;
1043 break;
1044 case MODEKEYEDIT_DELETEWORD:
1045 wsep = options_get_string(oo, "word-separators");
1046 idx = c->prompt_index;
1048 /* Find a non-separator. */
1049 while (idx != 0) {
1050 idx--;
1051 if (!strchr(wsep, c->prompt_buffer[idx]))
1052 break;
1055 /* Find the separator at the beginning of the word. */
1056 while (idx != 0) {
1057 idx--;
1058 if (strchr(wsep, c->prompt_buffer[idx])) {
1059 /* Go back to the word. */
1060 idx++;
1061 break;
1065 memmove(c->prompt_buffer + idx,
1066 c->prompt_buffer + c->prompt_index,
1067 size + 1 - c->prompt_index);
1068 memset(c->prompt_buffer + size - (c->prompt_index - idx),
1069 '\0', c->prompt_index - idx);
1070 c->prompt_index = idx;
1071 c->flags |= CLIENT_STATUS;
1072 break;
1073 case MODEKEYEDIT_NEXTSPACE:
1074 wsep = " ";
1075 /* FALLTHROUGH */
1076 case MODEKEYEDIT_NEXTWORD:
1077 if (wsep == NULL)
1078 wsep = options_get_string(oo, "word-separators");
1080 /* Find a separator. */
1081 while (c->prompt_index != size) {
1082 c->prompt_index++;
1083 if (strchr(wsep, c->prompt_buffer[c->prompt_index]))
1084 break;
1087 /* Find the word right after the separation. */
1088 while (c->prompt_index != size) {
1089 c->prompt_index++;
1090 if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1091 break;
1094 c->flags |= CLIENT_STATUS;
1095 break;
1096 case MODEKEYEDIT_NEXTSPACEEND:
1097 wsep = " ";
1098 /* FALLTHROUGH */
1099 case MODEKEYEDIT_NEXTWORDEND:
1100 if (wsep == NULL)
1101 wsep = options_get_string(oo, "word-separators");
1103 /* Find a word. */
1104 while (c->prompt_index != size) {
1105 c->prompt_index++;
1106 if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1107 break;
1110 /* Find the separator at the end of the word. */
1111 while (c->prompt_index != size) {
1112 c->prompt_index++;
1113 if (strchr(wsep, c->prompt_buffer[c->prompt_index]))
1114 break;
1117 /* Back up to the end-of-word like vi. */
1118 if (options_get_number(oo, "status-keys") == MODEKEY_VI &&
1119 c->prompt_index != 0)
1120 c->prompt_index--;
1122 c->flags |= CLIENT_STATUS;
1123 break;
1124 case MODEKEYEDIT_PREVIOUSSPACE:
1125 wsep = " ";
1126 /* FALLTHROUGH */
1127 case MODEKEYEDIT_PREVIOUSWORD:
1128 if (wsep == NULL)
1129 wsep = options_get_string(oo, "word-separators");
1131 /* Find a non-separator. */
1132 while (c->prompt_index != 0) {
1133 c->prompt_index--;
1134 if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1135 break;
1138 /* Find the separator at the beginning of the word. */
1139 while (c->prompt_index != 0) {
1140 c->prompt_index--;
1141 if (strchr(wsep, c->prompt_buffer[c->prompt_index])) {
1142 /* Go back to the word. */
1143 c->prompt_index++;
1144 break;
1148 c->flags |= CLIENT_STATUS;
1149 break;
1150 case MODEKEYEDIT_HISTORYUP:
1151 histstr = status_prompt_up_history(&c->prompt_hindex);
1152 if (histstr == NULL)
1153 break;
1154 free(c->prompt_buffer);
1155 c->prompt_buffer = xstrdup(histstr);
1156 c->prompt_index = strlen(c->prompt_buffer);
1157 c->flags |= CLIENT_STATUS;
1158 break;
1159 case MODEKEYEDIT_HISTORYDOWN:
1160 histstr = status_prompt_down_history(&c->prompt_hindex);
1161 if (histstr == NULL)
1162 break;
1163 free(c->prompt_buffer);
1164 c->prompt_buffer = xstrdup(histstr);
1165 c->prompt_index = strlen(c->prompt_buffer);
1166 c->flags |= CLIENT_STATUS;
1167 break;
1168 case MODEKEYEDIT_PASTE:
1169 if ((pb = paste_get_top(&global_buffers)) == NULL)
1170 break;
1171 for (n = 0; n < pb->size; n++) {
1172 ch = (u_char) pb->data[n];
1173 if (ch < 32 || ch == 127)
1174 break;
1177 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1);
1178 if (c->prompt_index == size) {
1179 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1180 c->prompt_index += n;
1181 c->prompt_buffer[c->prompt_index] = '\0';
1182 } else {
1183 memmove(c->prompt_buffer + c->prompt_index + n,
1184 c->prompt_buffer + c->prompt_index,
1185 size + 1 - c->prompt_index);
1186 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1187 c->prompt_index += n;
1190 c->flags |= CLIENT_STATUS;
1191 break;
1192 case MODEKEYEDIT_TRANSPOSECHARS:
1193 idx = c->prompt_index;
1194 if (idx < size)
1195 idx++;
1196 if (idx >= 2) {
1197 swapc = c->prompt_buffer[idx - 2];
1198 c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1];
1199 c->prompt_buffer[idx - 1] = swapc;
1200 c->prompt_index = idx;
1201 c->flags |= CLIENT_STATUS;
1203 break;
1204 case MODEKEYEDIT_ENTER:
1205 if (*c->prompt_buffer != '\0')
1206 status_prompt_add_history(c->prompt_buffer);
1207 if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0)
1208 status_prompt_clear(c);
1209 break;
1210 case MODEKEYEDIT_CANCEL:
1211 if (c->prompt_callbackfn(c->prompt_data, NULL) == 0)
1212 status_prompt_clear(c);
1213 break;
1214 case MODEKEY_OTHER:
1215 if ((key & 0xff00) != 0 || key < 32 || key == 127)
1216 break;
1217 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2);
1219 if (c->prompt_index == size) {
1220 c->prompt_buffer[c->prompt_index++] = key;
1221 c->prompt_buffer[c->prompt_index] = '\0';
1222 } else {
1223 memmove(c->prompt_buffer + c->prompt_index + 1,
1224 c->prompt_buffer + c->prompt_index,
1225 size + 1 - c->prompt_index);
1226 c->prompt_buffer[c->prompt_index++] = key;
1229 if (c->prompt_flags & PROMPT_SINGLE) {
1230 if (c->prompt_callbackfn(
1231 c->prompt_data, c->prompt_buffer) == 0)
1232 status_prompt_clear(c);
1235 c->flags |= CLIENT_STATUS;
1236 break;
1237 default:
1238 break;
1242 /* Get previous line from the history. */
1243 const char *
1244 status_prompt_up_history(u_int *idx)
1246 u_int size;
1249 * History runs from 0 to size - 1.
1251 * Index is from 0 to size. Zero is empty.
1254 size = ARRAY_LENGTH(&status_prompt_history);
1255 if (size == 0 || *idx == size)
1256 return (NULL);
1257 (*idx)++;
1258 return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1261 /* Get next line from the history. */
1262 const char *
1263 status_prompt_down_history(u_int *idx)
1265 u_int size;
1267 size = ARRAY_LENGTH(&status_prompt_history);
1268 if (size == 0 || *idx == 0)
1269 return ("");
1270 (*idx)--;
1271 if (*idx == 0)
1272 return ("");
1273 return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1276 /* Add line to the history. */
1277 void
1278 status_prompt_add_history(const char *line)
1280 u_int size;
1282 size = ARRAY_LENGTH(&status_prompt_history);
1283 if (size > 0 && strcmp(ARRAY_LAST(&status_prompt_history), line) == 0)
1284 return;
1286 if (size == PROMPT_HISTORY) {
1287 free(ARRAY_FIRST(&status_prompt_history));
1288 ARRAY_REMOVE(&status_prompt_history, 0);
1291 ARRAY_ADD(&status_prompt_history, xstrdup(line));
1294 /* Complete word. */
1295 char *
1296 status_prompt_complete(const char *s)
1298 const struct cmd_entry **cmdent;
1299 const struct options_table_entry *oe;
1300 ARRAY_DECL(, const char *) list;
1301 char *prefix, *s2;
1302 u_int i;
1303 size_t j;
1305 if (*s == '\0')
1306 return (NULL);
1308 /* First, build a list of all the possible matches. */
1309 ARRAY_INIT(&list);
1310 for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1311 if (strncmp((*cmdent)->name, s, strlen(s)) == 0)
1312 ARRAY_ADD(&list, (*cmdent)->name);
1314 for (oe = server_options_table; oe->name != NULL; oe++) {
1315 if (strncmp(oe->name, s, strlen(s)) == 0)
1316 ARRAY_ADD(&list, oe->name);
1318 for (oe = session_options_table; oe->name != NULL; oe++) {
1319 if (strncmp(oe->name, s, strlen(s)) == 0)
1320 ARRAY_ADD(&list, oe->name);
1322 for (oe = window_options_table; oe->name != NULL; oe++) {
1323 if (strncmp(oe->name, s, strlen(s)) == 0)
1324 ARRAY_ADD(&list, oe->name);
1327 /* If none, bail now. */
1328 if (ARRAY_LENGTH(&list) == 0) {
1329 ARRAY_FREE(&list);
1330 return (NULL);
1333 /* If an exact match, return it, with a trailing space. */
1334 if (ARRAY_LENGTH(&list) == 1) {
1335 xasprintf(&s2, "%s ", ARRAY_FIRST(&list));
1336 ARRAY_FREE(&list);
1337 return (s2);
1340 /* Now loop through the list and find the longest common prefix. */
1341 prefix = xstrdup(ARRAY_FIRST(&list));
1342 for (i = 1; i < ARRAY_LENGTH(&list); i++) {
1343 s = ARRAY_ITEM(&list, i);
1345 j = strlen(s);
1346 if (j > strlen(prefix))
1347 j = strlen(prefix);
1348 for (; j > 0; j--) {
1349 if (prefix[j - 1] != s[j - 1])
1350 prefix[j - 1] = '\0';
1354 ARRAY_FREE(&list);
1355 return (prefix);