MSG_EXIT can now have a return code in the message, so check for that
[tmux-openbsd.git] / status.c
blobb1d93c97d5cb7fb6d26e9b2d209097bb2fadb078
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_job(struct client *, char **);
37 void status_job_callback(struct job *);
38 char *status_print(
39 struct client *, struct winlink *, time_t, struct grid_cell *);
40 void status_replace1(struct client *,
41 struct winlink *, char **, char **, char *, size_t, int);
42 void status_message_callback(int, short, void *);
44 void status_prompt_add_history(struct client *);
45 char *status_prompt_complete(const char *);
47 /* Retrieve options for left string. */
48 char *
49 status_redraw_get_left(struct client *c,
50 time_t t, int utf8flag, struct grid_cell *gc, size_t *size)
52 struct session *s = c->session;
53 char *left;
54 u_char fg, bg, attr;
55 size_t leftlen;
57 fg = options_get_number(&s->options, "status-left-fg");
58 if (fg != 8)
59 colour_set_fg(gc, fg);
60 bg = options_get_number(&s->options, "status-left-bg");
61 if (bg != 8)
62 colour_set_bg(gc, bg);
63 attr = options_get_number(&s->options, "status-left-attr");
64 if (attr != 0)
65 gc->attr = attr;
67 left = status_replace(
68 c, NULL, options_get_string(&s->options, "status-left"), t, 1);
70 *size = options_get_number(&s->options, "status-left-length");
71 leftlen = screen_write_cstrlen(utf8flag, "%s", left);
72 if (leftlen < *size)
73 *size = leftlen;
74 return (left);
77 /* Retrieve options for right string. */
78 char *
79 status_redraw_get_right(struct client *c,
80 time_t t, int utf8flag, struct grid_cell *gc, size_t *size)
82 struct session *s = c->session;
83 char *right;
84 u_char fg, bg, attr;
85 size_t rightlen;
87 fg = options_get_number(&s->options, "status-right-fg");
88 if (fg != 8)
89 colour_set_fg(gc, fg);
90 bg = options_get_number(&s->options, "status-right-bg");
91 if (bg != 8)
92 colour_set_bg(gc, bg);
93 attr = options_get_number(&s->options, "status-right-attr");
94 if (attr != 0)
95 gc->attr = attr;
97 right = status_replace(
98 c, NULL, options_get_string(&s->options, "status-right"), t, 1);
100 *size = options_get_number(&s->options, "status-right-length");
101 rightlen = screen_write_cstrlen(utf8flag, "%s", right);
102 if (rightlen < *size)
103 *size = rightlen;
104 return (right);
107 /* Draw status for client on the last lines of given context. */
109 status_redraw(struct client *c)
111 struct screen_write_ctx ctx;
112 struct session *s = c->session;
113 struct winlink *wl;
114 struct screen old_status, window_list;
115 struct grid_cell stdgc, lgc, rgc, gc;
116 time_t t;
117 char *left, *right;
118 u_int offset, needed;
119 u_int wlstart, wlwidth, wlavailable, wloffset, wlsize;
120 size_t llen, rlen;
121 int larrow, rarrow, utf8flag;
123 /* No status line? */
124 if (c->tty.sy == 0 || !options_get_number(&s->options, "status"))
125 return (1);
126 left = right = NULL;
127 larrow = rarrow = 0;
129 /* Update status timer. */
130 if (gettimeofday(&c->status_timer, NULL) != 0)
131 fatal("gettimeofday failed");
132 t = c->status_timer.tv_sec;
134 /* Set up default colour. */
135 memcpy(&stdgc, &grid_default_cell, sizeof gc);
136 colour_set_fg(&stdgc, options_get_number(&s->options, "status-fg"));
137 colour_set_bg(&stdgc, options_get_number(&s->options, "status-bg"));
138 stdgc.attr |= options_get_number(&s->options, "status-attr");
140 /* Create the target screen. */
141 memcpy(&old_status, &c->status, sizeof old_status);
142 screen_init(&c->status, c->tty.sx, 1, 0);
143 screen_write_start(&ctx, NULL, &c->status);
144 for (offset = 0; offset < c->tty.sx; offset++)
145 screen_write_putc(&ctx, &stdgc, ' ');
146 screen_write_stop(&ctx);
148 /* If the height is one line, blank status line. */
149 if (c->tty.sy <= 1)
150 goto out;
152 /* Get UTF-8 flag. */
153 utf8flag = options_get_number(&s->options, "status-utf8");
155 /* Work out left and right strings. */
156 memcpy(&lgc, &stdgc, sizeof lgc);
157 left = status_redraw_get_left(c, t, utf8flag, &lgc, &llen);
158 memcpy(&rgc, &stdgc, sizeof rgc);
159 right = status_redraw_get_right(c, t, utf8flag, &rgc, &rlen);
162 * Figure out how much space we have for the window list. If there
163 * isn't enough space, just show a blank status line.
165 needed = 0;
166 if (llen != 0)
167 needed += llen + 1;
168 if (rlen != 0)
169 needed += rlen + 1;
170 if (c->tty.sx == 0 || c->tty.sx <= needed)
171 goto out;
172 wlavailable = c->tty.sx - needed;
174 /* Calculate the total size needed for the window list. */
175 wlstart = wloffset = wlwidth = 0;
176 RB_FOREACH(wl, winlinks, &s->windows) {
177 if (wl->status_text != NULL)
178 xfree(wl->status_text);
179 memcpy(&wl->status_cell, &stdgc, sizeof wl->status_cell);
180 wl->status_text = status_print(c, wl, t, &wl->status_cell);
181 wl->status_width =
182 screen_write_cstrlen(utf8flag, "%s", wl->status_text);
184 if (wl == s->curw)
185 wloffset = wlwidth;
186 wlwidth += wl->status_width + 1;
189 /* Create a new screen for the window list. */
190 screen_init(&window_list, wlwidth, 1, 0);
192 /* And draw the window list into it. */
193 screen_write_start(&ctx, NULL, &window_list);
194 RB_FOREACH(wl, winlinks, &s->windows) {
195 screen_write_cnputs(&ctx,
196 -1, &wl->status_cell, utf8flag, "%s", wl->status_text);
197 screen_write_putc(&ctx, &stdgc, ' ');
199 screen_write_stop(&ctx);
201 /* If there is enough space for the total width, skip to draw now. */
202 if (wlwidth <= wlavailable)
203 goto draw;
205 /* Find size of current window text. */
206 wlsize = s->curw->status_width;
209 * If the current window is already on screen, good to draw from the
210 * start and just leave off the end.
212 if (wloffset + wlsize < wlavailable) {
213 if (wlavailable > 0) {
214 rarrow = 1;
215 wlavailable--;
217 wlwidth = wlavailable;
218 } else {
220 * Work out how many characters we need to omit from the
221 * start. There are wlavailable characters to fill, and
222 * wloffset + wlsize must be the last. So, the start character
223 * is wloffset + wlsize - wlavailable.
225 if (wlavailable > 0) {
226 larrow = 1;
227 wlavailable--;
230 wlstart = wloffset + wlsize - wlavailable;
231 if (wlavailable > 0 && wlwidth > wlstart + wlavailable + 1) {
232 rarrow = 1;
233 wlstart++;
234 wlavailable--;
236 wlwidth = wlavailable;
239 /* Bail if anything is now too small too. */
240 if (wlwidth == 0 || wlavailable == 0) {
241 screen_free(&window_list);
242 goto out;
246 * Now the start position is known, work out the state of the left and
247 * right arrows.
249 offset = 0;
250 RB_FOREACH(wl, winlinks, &s->windows) {
251 if (wl->flags & WINLINK_ALERTFLAGS &&
252 larrow == 1 && offset < wlstart)
253 larrow = -1;
255 offset += wl->status_width;
257 if (wl->flags & WINLINK_ALERTFLAGS &&
258 rarrow == 1 && offset > wlstart + wlwidth)
259 rarrow = -1;
262 draw:
263 /* Begin drawing. */
264 screen_write_start(&ctx, NULL, &c->status);
266 /* Draw the left string and arrow. */
267 screen_write_cursormove(&ctx, 0, 0);
268 if (llen != 0) {
269 screen_write_cnputs(&ctx, llen, &lgc, utf8flag, "%s", left);
270 screen_write_putc(&ctx, &stdgc, ' ');
272 if (larrow != 0) {
273 memcpy(&gc, &stdgc, sizeof gc);
274 if (larrow == -1)
275 gc.attr ^= GRID_ATTR_REVERSE;
276 screen_write_putc(&ctx, &gc, '<');
279 /* Draw the right string and arrow. */
280 if (rarrow != 0) {
281 screen_write_cursormove(&ctx, c->tty.sx - rlen - 2, 0);
282 memcpy(&gc, &stdgc, sizeof gc);
283 if (rarrow == -1)
284 gc.attr ^= GRID_ATTR_REVERSE;
285 screen_write_putc(&ctx, &gc, '>');
286 } else
287 screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, 0);
288 if (rlen != 0) {
289 screen_write_putc(&ctx, &stdgc, ' ');
290 screen_write_cnputs(&ctx, rlen, &rgc, utf8flag, "%s", right);
293 /* Figure out the offset for the window list. */
294 if (llen != 0)
295 wloffset = llen + 1;
296 else
297 wloffset = 0;
298 if (wlwidth < wlavailable) {
299 switch (options_get_number(&s->options, "status-justify")) {
300 case 1: /* centered */
301 wloffset += (wlavailable - wlwidth) / 2;
302 break;
303 case 2: /* right */
304 wloffset += (wlavailable - wlwidth);
305 break;
308 if (larrow != 0)
309 wloffset++;
311 /* Copy the window list. */
312 screen_write_cursormove(&ctx, wloffset, 0);
313 screen_write_copy(&ctx, &window_list, wlstart, 0, wlwidth, 1);
314 screen_free(&window_list);
316 screen_write_stop(&ctx);
318 out:
319 if (left != NULL)
320 xfree(left);
321 if (right != NULL)
322 xfree(right);
324 if (grid_compare(c->status.grid, old_status.grid) == 0) {
325 screen_free(&old_status);
326 return (0);
328 screen_free(&old_status);
329 return (1);
332 /* Replace a single special sequence (prefixed by #). */
333 void
334 status_replace1(struct client *c,struct winlink *wl,
335 char **iptr, char **optr, char *out, size_t outsize, int jobsflag)
337 struct session *s = c->session;
338 char ch, tmp[256], *ptr, *endptr, *freeptr;
339 size_t ptrlen;
340 long limit;
342 if (wl == NULL)
343 wl = s->curw;
345 errno = 0;
346 limit = strtol(*iptr, &endptr, 10);
347 if ((limit == 0 && errno != EINVAL) ||
348 (limit == LONG_MIN && errno != ERANGE) ||
349 (limit == LONG_MAX && errno != ERANGE) ||
350 limit != 0)
351 *iptr = endptr;
352 if (limit <= 0)
353 limit = LONG_MAX;
355 freeptr = NULL;
357 switch (*(*iptr)++) {
358 case '(':
359 if (!jobsflag) {
360 ch = ')';
361 goto skip_to;
363 if ((ptr = status_job(c, iptr)) == NULL)
364 return;
365 freeptr = ptr;
366 goto do_replace;
367 case 'H':
368 if (gethostname(tmp, sizeof tmp) != 0)
369 fatal("gethostname failed");
370 ptr = tmp;
371 goto do_replace;
372 case 'I':
373 xsnprintf(tmp, sizeof tmp, "%d", wl->idx);
374 ptr = tmp;
375 goto do_replace;
376 case 'P':
377 xsnprintf(tmp, sizeof tmp, "%u",
378 window_pane_index(wl->window, wl->window->active));
379 ptr = tmp;
380 goto do_replace;
381 case 'S':
382 ptr = s->name;
383 goto do_replace;
384 case 'T':
385 ptr = wl->window->active->base.title;
386 goto do_replace;
387 case 'W':
388 ptr = wl->window->name;
389 goto do_replace;
390 case 'F':
391 tmp[0] = ' ';
392 if (wl->flags & WINLINK_CONTENT)
393 tmp[0] = '+';
394 else if (wl->flags & WINLINK_BELL)
395 tmp[0] = '!';
396 else if (wl->flags & WINLINK_ACTIVITY)
397 tmp[0] = '#';
398 else if (wl == s->curw)
399 tmp[0] = '*';
400 else if (wl == TAILQ_FIRST(&s->lastw))
401 tmp[0] = '-';
402 tmp[1] = '\0';
403 ptr = tmp;
404 goto do_replace;
405 case '[':
407 * Embedded style, handled at display time. Leave present and
408 * skip input until ].
410 ch = ']';
411 goto skip_to;
412 case '#':
413 *(*optr)++ = '#';
414 break;
417 return;
419 do_replace:
420 ptrlen = strlen(ptr);
421 if ((size_t) limit < ptrlen)
422 ptrlen = limit;
424 if (*optr + ptrlen >= out + outsize - 1)
425 return;
426 while (ptrlen > 0 && *ptr != '\0') {
427 *(*optr)++ = *ptr++;
428 ptrlen--;
431 if (freeptr != NULL)
432 xfree(freeptr);
433 return;
435 skip_to:
436 *(*optr)++ = '#';
438 (*iptr)--; /* include ch */
439 while (**iptr != ch && **iptr != '\0') {
440 if (*optr >= out + outsize - 1)
441 break;
442 *(*optr)++ = *(*iptr)++;
446 /* Replace special sequences in fmt. */
447 char *
448 status_replace(struct client *c,
449 struct winlink *wl, const char *fmt, time_t t, int jobsflag)
451 static char out[BUFSIZ];
452 char in[BUFSIZ], ch, *iptr, *optr;
454 strftime(in, sizeof in, fmt, localtime(&t));
455 in[(sizeof in) - 1] = '\0';
457 iptr = in;
458 optr = out;
460 while (*iptr != '\0') {
461 if (optr >= out + (sizeof out) - 1)
462 break;
463 ch = *iptr++;
465 if (ch != '#') {
466 *optr++ = ch;
467 continue;
469 status_replace1(c, wl, &iptr, &optr, out, sizeof out, jobsflag);
471 *optr = '\0';
473 return (xstrdup(out));
476 /* Figure out job name and get its result, starting it off if necessary. */
477 char *
478 status_job(struct client *c, char **iptr)
480 struct job *job;
481 char *cmd;
482 int lastesc;
483 size_t len;
485 if (**iptr == '\0')
486 return (NULL);
487 if (**iptr == ')') { /* no command given */
488 (*iptr)++;
489 return (NULL);
492 cmd = xmalloc(strlen(*iptr) + 1);
493 len = 0;
495 lastesc = 0;
496 for (; **iptr != '\0'; (*iptr)++) {
497 if (!lastesc && **iptr == ')')
498 break; /* unescaped ) is the end */
499 if (!lastesc && **iptr == '\\') {
500 lastesc = 1;
501 continue; /* skip \ if not escaped */
503 lastesc = 0;
504 cmd[len++] = **iptr;
506 if (**iptr == '\0') /* no terminating ) */ {
507 xfree(cmd);
508 return (NULL);
510 (*iptr)++; /* skip final ) */
511 cmd[len] = '\0';
513 job = job_get(&c->status_jobs, cmd);
514 if (job == NULL) {
515 job = job_add(&c->status_jobs,
516 JOB_PERSIST, c, cmd, status_job_callback, xfree, NULL);
517 job_run(job);
519 xfree(cmd);
520 if (job->data == NULL)
521 return (xstrdup(""));
522 return (xstrdup(job->data));
525 /* Job has finished: save its result. */
526 void
527 status_job_callback(struct job *job)
529 char *line, *buf;
530 size_t len;
532 buf = NULL;
533 if ((line = evbuffer_readline(job->event->input)) == NULL) {
534 len = EVBUFFER_LENGTH(job->event->input);
535 buf = xmalloc(len + 1);
536 if (len != 0)
537 memcpy(buf, EVBUFFER_DATA(job->event->input), len);
538 buf[len] = '\0';
541 if (job->data != NULL)
542 xfree(job->data);
543 else
544 server_redraw_client(job->client);
546 if (line == NULL)
547 job->data = buf;
548 else
549 job->data = xstrdup(line);
552 /* Return winlink status line entry and adjust gc as necessary. */
553 char *
554 status_print(
555 struct client *c, struct winlink *wl, time_t t, struct grid_cell *gc)
557 struct options *oo = &wl->window->options;
558 struct session *s = c->session;
559 const char *fmt;
560 char *text;
561 u_char fg, bg, attr;
563 fg = options_get_number(oo, "window-status-fg");
564 if (fg != 8)
565 colour_set_fg(gc, fg);
566 bg = options_get_number(oo, "window-status-bg");
567 if (bg != 8)
568 colour_set_bg(gc, bg);
569 attr = options_get_number(oo, "window-status-attr");
570 if (attr != 0)
571 gc->attr = attr;
572 fmt = options_get_string(oo, "window-status-format");
573 if (wl == s->curw) {
574 fg = options_get_number(oo, "window-status-current-fg");
575 if (fg != 8)
576 colour_set_fg(gc, fg);
577 bg = options_get_number(oo, "window-status-current-bg");
578 if (bg != 8)
579 colour_set_bg(gc, bg);
580 attr = options_get_number(oo, "window-status-current-attr");
581 if (attr != 0)
582 gc->attr = attr;
583 fmt = options_get_string(oo, "window-status-current-format");
586 if (wl->flags & WINLINK_ALERTFLAGS) {
587 fg = options_get_number(oo, "window-status-alert-fg");
588 if (fg != 8)
589 colour_set_fg(gc, fg);
590 bg = options_get_number(oo, "window-status-alert-bg");
591 if (bg != 8)
592 colour_set_bg(gc, bg);
593 attr = options_get_number(oo, "window-status-alert-attr");
594 if (attr != 0)
595 gc->attr = attr;
598 text = status_replace(c, wl, fmt, t, 1);
599 return (text);
602 /* Set a status line message. */
603 void printflike2
604 status_message_set(struct client *c, const char *fmt, ...)
606 struct timeval tv;
607 struct session *s = c->session;
608 struct message_entry *msg;
609 va_list ap;
610 int delay;
611 u_int i, limit;
613 status_prompt_clear(c);
614 status_message_clear(c);
616 va_start(ap, fmt);
617 xvasprintf(&c->message_string, fmt, ap);
618 va_end(ap);
620 ARRAY_EXPAND(&c->message_log, 1);
621 msg = &ARRAY_LAST(&c->message_log);
622 msg->msg_time = time(NULL);
623 msg->msg = xstrdup(c->message_string);
625 if (s == NULL)
626 limit = 0;
627 else
628 limit = options_get_number(&s->options, "message-limit");
629 if (ARRAY_LENGTH(&c->message_log) > limit) {
630 limit = ARRAY_LENGTH(&c->message_log) - limit;
631 for (i = 0; i < limit; i++) {
632 msg = &ARRAY_FIRST(&c->message_log);
633 xfree(msg->msg);
634 ARRAY_REMOVE(&c->message_log, 0);
638 delay = options_get_number(&c->session->options, "display-time");
639 tv.tv_sec = delay / 1000;
640 tv.tv_usec = (delay % 1000) * 1000L;
642 evtimer_del(&c->message_timer);
643 evtimer_set(&c->message_timer, status_message_callback, c);
644 evtimer_add(&c->message_timer, &tv);
646 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
647 c->flags |= CLIENT_STATUS;
650 /* Clear status line message. */
651 void
652 status_message_clear(struct client *c)
654 if (c->message_string == NULL)
655 return;
657 xfree(c->message_string);
658 c->message_string = NULL;
660 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
661 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
663 screen_reinit(&c->status);
666 /* Clear status line message after timer expires. */
667 /* ARGSUSED */
668 void
669 status_message_callback(unused int fd, unused short event, void *data)
671 struct client *c = data;
673 status_message_clear(c);
676 /* Draw client message on status line of present else on last line. */
678 status_message_redraw(struct client *c)
680 struct screen_write_ctx ctx;
681 struct session *s = c->session;
682 struct screen old_status;
683 size_t len;
684 struct grid_cell gc;
685 int utf8flag;
687 if (c->tty.sx == 0 || c->tty.sy == 0)
688 return (0);
689 memcpy(&old_status, &c->status, sizeof old_status);
690 screen_init(&c->status, c->tty.sx, 1, 0);
692 utf8flag = options_get_number(&s->options, "status-utf8");
694 len = screen_write_strlen(utf8flag, "%s", c->message_string);
695 if (len > c->tty.sx)
696 len = c->tty.sx;
698 memcpy(&gc, &grid_default_cell, sizeof gc);
699 colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
700 colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
701 gc.attr |= options_get_number(&s->options, "message-attr");
703 screen_write_start(&ctx, NULL, &c->status);
705 screen_write_cursormove(&ctx, 0, 0);
706 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->message_string);
707 for (; len < c->tty.sx; len++)
708 screen_write_putc(&ctx, &gc, ' ');
710 screen_write_stop(&ctx);
712 if (grid_compare(c->status.grid, old_status.grid) == 0) {
713 screen_free(&old_status);
714 return (0);
716 screen_free(&old_status);
717 return (1);
720 /* Enable status line prompt. */
721 void
722 status_prompt_set(struct client *c, const char *msg,
723 int (*callbackfn)(void *, const char *), void (*freefn)(void *),
724 void *data, int flags)
726 int keys;
728 status_message_clear(c);
729 status_prompt_clear(c);
731 c->prompt_string = xstrdup(msg);
733 c->prompt_buffer = xstrdup("");
734 c->prompt_index = 0;
736 c->prompt_callbackfn = callbackfn;
737 c->prompt_freefn = freefn;
738 c->prompt_data = data;
740 c->prompt_hindex = 0;
742 c->prompt_flags = flags;
744 keys = options_get_number(&c->session->options, "status-keys");
745 if (keys == MODEKEY_EMACS)
746 mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit);
747 else
748 mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit);
750 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
751 c->flags |= CLIENT_STATUS;
754 /* Remove status line prompt. */
755 void
756 status_prompt_clear(struct client *c)
758 if (c->prompt_string == NULL)
759 return;
761 if (c->prompt_freefn != NULL && c->prompt_data != NULL)
762 c->prompt_freefn(c->prompt_data);
764 xfree(c->prompt_string);
765 c->prompt_string = NULL;
767 xfree(c->prompt_buffer);
768 c->prompt_buffer = NULL;
770 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
771 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
773 screen_reinit(&c->status);
776 /* Update status line prompt with a new prompt string. */
777 void
778 status_prompt_update(struct client *c, const char *msg)
780 xfree(c->prompt_string);
781 c->prompt_string = xstrdup(msg);
783 *c->prompt_buffer = '\0';
784 c->prompt_index = 0;
786 c->prompt_hindex = 0;
788 c->flags |= CLIENT_STATUS;
791 /* Draw client prompt on status line of present else on last line. */
793 status_prompt_redraw(struct client *c)
795 struct screen_write_ctx ctx;
796 struct session *s = c->session;
797 struct screen old_status;
798 size_t i, size, left, len, off;
799 struct grid_cell gc, *gcp;
800 int utf8flag;
802 if (c->tty.sx == 0 || c->tty.sy == 0)
803 return (0);
804 memcpy(&old_status, &c->status, sizeof old_status);
805 screen_init(&c->status, c->tty.sx, 1, 0);
807 utf8flag = options_get_number(&s->options, "status-utf8");
809 len = screen_write_strlen(utf8flag, "%s", c->prompt_string);
810 if (len > c->tty.sx)
811 len = c->tty.sx;
812 off = 0;
814 memcpy(&gc, &grid_default_cell, sizeof gc);
815 colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
816 colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
817 gc.attr |= options_get_number(&s->options, "message-attr");
819 screen_write_start(&ctx, NULL, &c->status);
821 screen_write_cursormove(&ctx, 0, 0);
822 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->prompt_string);
824 left = c->tty.sx - len;
825 if (left != 0) {
826 size = screen_write_strlen(utf8flag, "%s", c->prompt_buffer);
827 if (c->prompt_index >= left) {
828 off = c->prompt_index - left + 1;
829 if (c->prompt_index == size)
830 left--;
831 size = left;
833 screen_write_nputs(
834 &ctx, left, &gc, utf8flag, "%s", c->prompt_buffer + off);
836 for (i = len + size; i < c->tty.sx; i++)
837 screen_write_putc(&ctx, &gc, ' ');
840 screen_write_stop(&ctx);
842 /* Apply fake cursor. */
843 off = len + c->prompt_index - off;
844 gcp = grid_view_get_cell(c->status.grid, off, 0);
845 gcp->attr ^= GRID_ATTR_REVERSE;
847 if (grid_compare(c->status.grid, old_status.grid) == 0) {
848 screen_free(&old_status);
849 return (0);
851 screen_free(&old_status);
852 return (1);
855 /* Handle keys in prompt. */
856 void
857 status_prompt_key(struct client *c, int key)
859 struct paste_buffer *pb;
860 char *s, *first, *last, word[64], swapc;
861 u_char ch;
862 size_t size, n, off, idx;
864 size = strlen(c->prompt_buffer);
865 switch (mode_key_lookup(&c->prompt_mdata, key)) {
866 case MODEKEYEDIT_CURSORLEFT:
867 if (c->prompt_index > 0) {
868 c->prompt_index--;
869 c->flags |= CLIENT_STATUS;
871 break;
872 case MODEKEYEDIT_SWITCHMODEAPPEND:
873 case MODEKEYEDIT_CURSORRIGHT:
874 if (c->prompt_index < size) {
875 c->prompt_index++;
876 c->flags |= CLIENT_STATUS;
878 break;
879 case MODEKEYEDIT_STARTOFLINE:
880 if (c->prompt_index != 0) {
881 c->prompt_index = 0;
882 c->flags |= CLIENT_STATUS;
884 break;
885 case MODEKEYEDIT_ENDOFLINE:
886 if (c->prompt_index != size) {
887 c->prompt_index = size;
888 c->flags |= CLIENT_STATUS;
890 break;
891 case MODEKEYEDIT_COMPLETE:
892 if (*c->prompt_buffer == '\0')
893 break;
895 idx = c->prompt_index;
896 if (idx != 0)
897 idx--;
899 /* Find the word we are in. */
900 first = c->prompt_buffer + idx;
901 while (first > c->prompt_buffer && *first != ' ')
902 first--;
903 while (*first == ' ')
904 first++;
905 last = c->prompt_buffer + idx;
906 while (*last != '\0' && *last != ' ')
907 last++;
908 while (*last == ' ')
909 last--;
910 if (*last != '\0')
911 last++;
912 if (last <= first ||
913 ((size_t) (last - first)) > (sizeof word) - 1)
914 break;
915 memcpy(word, first, last - first);
916 word[last - first] = '\0';
918 /* And try to complete it. */
919 if ((s = status_prompt_complete(word)) == NULL)
920 break;
922 /* Trim out word. */
923 n = size - (last - c->prompt_buffer) + 1; /* with \0 */
924 memmove(first, last, n);
925 size -= last - first;
927 /* Insert the new word. */
928 size += strlen(s);
929 off = first - c->prompt_buffer;
930 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1);
931 first = c->prompt_buffer + off;
932 memmove(first + strlen(s), first, n);
933 memcpy(first, s, strlen(s));
935 c->prompt_index = (first - c->prompt_buffer) + strlen(s);
936 xfree(s);
938 c->flags |= CLIENT_STATUS;
939 break;
940 case MODEKEYEDIT_BACKSPACE:
941 if (c->prompt_index != 0) {
942 if (c->prompt_index == size)
943 c->prompt_buffer[--c->prompt_index] = '\0';
944 else {
945 memmove(c->prompt_buffer + c->prompt_index - 1,
946 c->prompt_buffer + c->prompt_index,
947 size + 1 - c->prompt_index);
948 c->prompt_index--;
950 c->flags |= CLIENT_STATUS;
952 break;
953 case MODEKEYEDIT_DELETE:
954 if (c->prompt_index != size) {
955 memmove(c->prompt_buffer + c->prompt_index,
956 c->prompt_buffer + c->prompt_index + 1,
957 size + 1 - c->prompt_index);
958 c->flags |= CLIENT_STATUS;
960 break;
961 case MODEKEYEDIT_DELETELINE:
962 *c->prompt_buffer = '\0';
963 c->prompt_index = 0;
964 c->flags |= CLIENT_STATUS;
965 break;
966 case MODEKEYEDIT_DELETETOENDOFLINE:
967 if (c->prompt_index < size) {
968 c->prompt_buffer[c->prompt_index] = '\0';
969 c->flags |= CLIENT_STATUS;
971 break;
972 case MODEKEYEDIT_HISTORYUP:
973 if (ARRAY_LENGTH(&c->prompt_hdata) == 0)
974 break;
975 xfree(c->prompt_buffer);
977 c->prompt_buffer = xstrdup(ARRAY_ITEM(&c->prompt_hdata,
978 ARRAY_LENGTH(&c->prompt_hdata) - 1 - c->prompt_hindex));
979 if (c->prompt_hindex != ARRAY_LENGTH(&c->prompt_hdata) - 1)
980 c->prompt_hindex++;
982 c->prompt_index = strlen(c->prompt_buffer);
983 c->flags |= CLIENT_STATUS;
984 break;
985 case MODEKEYEDIT_HISTORYDOWN:
986 xfree(c->prompt_buffer);
988 if (c->prompt_hindex != 0) {
989 c->prompt_hindex--;
990 c->prompt_buffer = xstrdup(ARRAY_ITEM(
991 &c->prompt_hdata, ARRAY_LENGTH(
992 &c->prompt_hdata) - 1 - c->prompt_hindex));
993 } else
994 c->prompt_buffer = xstrdup("");
996 c->prompt_index = strlen(c->prompt_buffer);
997 c->flags |= CLIENT_STATUS;
998 break;
999 case MODEKEYEDIT_PASTE:
1000 if ((pb = paste_get_top(&c->session->buffers)) == NULL)
1001 break;
1002 for (n = 0; n < pb->size; n++) {
1003 ch = (u_char) pb->data[n];
1004 if (ch < 32 || ch == 127)
1005 break;
1008 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1);
1009 if (c->prompt_index == size) {
1010 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1011 c->prompt_index += n;
1012 c->prompt_buffer[c->prompt_index] = '\0';
1013 } else {
1014 memmove(c->prompt_buffer + c->prompt_index + n,
1015 c->prompt_buffer + c->prompt_index,
1016 size + 1 - c->prompt_index);
1017 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1018 c->prompt_index += n;
1021 c->flags |= CLIENT_STATUS;
1022 break;
1023 case MODEKEYEDIT_TRANSPOSECHARS:
1024 idx = c->prompt_index;
1025 if (idx < size)
1026 idx++;
1027 if (idx >= 2) {
1028 swapc = c->prompt_buffer[idx - 2];
1029 c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1];
1030 c->prompt_buffer[idx - 1] = swapc;
1031 c->prompt_index = idx;
1032 c->flags |= CLIENT_STATUS;
1034 break;
1035 case MODEKEYEDIT_ENTER:
1036 if (*c->prompt_buffer != '\0')
1037 status_prompt_add_history(c);
1038 if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0)
1039 status_prompt_clear(c);
1040 break;
1041 case MODEKEYEDIT_CANCEL:
1042 if (c->prompt_callbackfn(c->prompt_data, NULL) == 0)
1043 status_prompt_clear(c);
1044 break;
1045 case MODEKEY_OTHER:
1046 if ((key & 0xff00) != 0 || key < 32 || key == 127)
1047 break;
1048 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2);
1050 if (c->prompt_index == size) {
1051 c->prompt_buffer[c->prompt_index++] = key;
1052 c->prompt_buffer[c->prompt_index] = '\0';
1053 } else {
1054 memmove(c->prompt_buffer + c->prompt_index + 1,
1055 c->prompt_buffer + c->prompt_index,
1056 size + 1 - c->prompt_index);
1057 c->prompt_buffer[c->prompt_index++] = key;
1060 if (c->prompt_flags & PROMPT_SINGLE) {
1061 if (c->prompt_callbackfn(
1062 c->prompt_data, c->prompt_buffer) == 0)
1063 status_prompt_clear(c);
1066 c->flags |= CLIENT_STATUS;
1067 break;
1068 default:
1069 break;
1073 /* Add line to the history. */
1074 void
1075 status_prompt_add_history(struct client *c)
1077 if (ARRAY_LENGTH(&c->prompt_hdata) > 0 &&
1078 strcmp(ARRAY_LAST(&c->prompt_hdata), c->prompt_buffer) == 0)
1079 return;
1081 if (ARRAY_LENGTH(&c->prompt_hdata) == PROMPT_HISTORY) {
1082 xfree(ARRAY_FIRST(&c->prompt_hdata));
1083 ARRAY_REMOVE(&c->prompt_hdata, 0);
1086 ARRAY_ADD(&c->prompt_hdata, xstrdup(c->prompt_buffer));
1089 /* Complete word. */
1090 char *
1091 status_prompt_complete(const char *s)
1093 const struct cmd_entry **cmdent;
1094 const struct set_option_entry *entry;
1095 ARRAY_DECL(, const char *) list;
1096 char *prefix, *s2;
1097 u_int i;
1098 size_t j;
1100 if (*s == '\0')
1101 return (NULL);
1103 /* First, build a list of all the possible matches. */
1104 ARRAY_INIT(&list);
1105 for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1106 if (strncmp((*cmdent)->name, s, strlen(s)) == 0)
1107 ARRAY_ADD(&list, (*cmdent)->name);
1109 for (entry = set_option_table; entry->name != NULL; entry++) {
1110 if (strncmp(entry->name, s, strlen(s)) == 0)
1111 ARRAY_ADD(&list, entry->name);
1113 for (entry = set_session_option_table; entry->name != NULL; entry++) {
1114 if (strncmp(entry->name, s, strlen(s)) == 0)
1115 ARRAY_ADD(&list, entry->name);
1117 for (entry = set_window_option_table; entry->name != NULL; entry++) {
1118 if (strncmp(entry->name, s, strlen(s)) == 0)
1119 ARRAY_ADD(&list, entry->name);
1122 /* If none, bail now. */
1123 if (ARRAY_LENGTH(&list) == 0) {
1124 ARRAY_FREE(&list);
1125 return (NULL);
1128 /* If an exact match, return it, with a trailing space. */
1129 if (ARRAY_LENGTH(&list) == 1) {
1130 xasprintf(&s2, "%s ", ARRAY_FIRST(&list));
1131 ARRAY_FREE(&list);
1132 return (s2);
1135 /* Now loop through the list and find the longest common prefix. */
1136 prefix = xstrdup(ARRAY_FIRST(&list));
1137 for (i = 1; i < ARRAY_LENGTH(&list); i++) {
1138 s = ARRAY_ITEM(&list, i);
1140 j = strlen(s);
1141 if (j > strlen(prefix))
1142 j = strlen(prefix);
1143 for (; j > 0; j--) {
1144 if (prefix[j - 1] != s[j - 1])
1145 prefix[j - 1] = '\0';
1149 ARRAY_FREE(&list);
1150 return (prefix);