This commit was manufactured by cvs2svn to create tag 'TMUX_1_2'.
[tmux.git] / status.c
blob1056bf5931e7336dc90d550fa31ed6370e89a416
1 /* $Id: status.c,v 1.144 2010-03-10 13:41:13 nicm Exp $ */
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 size_t status_width(struct client *, struct winlink *, time_t);
39 char *status_print(
40 struct client *, struct winlink *, time_t, struct grid_cell *);
41 void status_replace1(struct client *,
42 struct winlink *, char **, char **, char *, size_t, int);
43 void status_message_callback(int, short, void *);
45 void status_prompt_add_history(struct client *);
46 char *status_prompt_complete(const char *);
48 /* Retrieve options for left string. */
49 char *
50 status_redraw_get_left(struct client *c,
51 time_t t, int utf8flag, struct grid_cell *gc, size_t *size)
53 struct session *s = c->session;
54 char *left;
55 u_char fg, bg, attr;
56 size_t leftlen;
58 fg = options_get_number(&s->options, "status-left-fg");
59 if (fg != 8)
60 colour_set_fg(gc, fg);
61 bg = options_get_number(&s->options, "status-left-bg");
62 if (bg != 8)
63 colour_set_bg(gc, bg);
64 attr = options_get_number(&s->options, "status-left-attr");
65 if (attr != 0)
66 gc->attr = attr;
68 left = status_replace(
69 c, NULL, options_get_string(&s->options, "status-left"), t, 1);
71 *size = options_get_number(&s->options, "status-left-length");
72 leftlen = screen_write_cstrlen(utf8flag, "%s", left);
73 if (leftlen < *size)
74 *size = leftlen;
75 return (left);
78 /* Retrieve options for right string. */
79 char *
80 status_redraw_get_right(struct client *c,
81 time_t t, int utf8flag, struct grid_cell *gc, size_t *size)
83 struct session *s = c->session;
84 char *right;
85 u_char fg, bg, attr;
86 size_t rightlen;
88 fg = options_get_number(&s->options, "status-right-fg");
89 if (fg != 8)
90 colour_set_fg(gc, fg);
91 bg = options_get_number(&s->options, "status-right-bg");
92 if (bg != 8)
93 colour_set_bg(gc, bg);
94 attr = options_get_number(&s->options, "status-right-attr");
95 if (attr != 0)
96 gc->attr = attr;
98 right = status_replace(
99 c, NULL, options_get_string(&s->options, "status-right"), t, 1);
101 *size = options_get_number(&s->options, "status-right-length");
102 rightlen = screen_write_cstrlen(utf8flag, "%s", right);
103 if (rightlen < *size)
104 *size = rightlen;
105 return (right);
108 /* Draw status for client on the last lines of given context. */
110 status_redraw(struct client *c)
112 struct screen_write_ctx ctx;
113 struct session *s = c->session;
114 struct winlink *wl;
115 struct screen old_status, window_list;
116 struct grid_cell stdgc, lgc, rgc, gc;
117 time_t t;
118 char *left, *right;
119 u_int offset, needed;
120 u_int wlstart, wlwidth, wlavailable, wloffset, wlsize;
121 size_t llen, rlen;
122 int larrow, rarrow, utf8flag;
124 /* No status line? */
125 if (c->tty.sy == 0 || !options_get_number(&s->options, "status"))
126 return (1);
127 left = right = NULL;
128 larrow = rarrow = 0;
130 /* Update status timer. */
131 if (gettimeofday(&c->status_timer, NULL) != 0)
132 fatal("gettimeofday failed");
133 t = c->status_timer.tv_sec;
135 /* Set up default colour. */
136 memcpy(&stdgc, &grid_default_cell, sizeof gc);
137 colour_set_fg(&stdgc, options_get_number(&s->options, "status-fg"));
138 colour_set_bg(&stdgc, options_get_number(&s->options, "status-bg"));
139 stdgc.attr |= options_get_number(&s->options, "status-attr");
141 /* Create the target screen. */
142 memcpy(&old_status, &c->status, sizeof old_status);
143 screen_init(&c->status, c->tty.sx, 1, 0);
144 screen_write_start(&ctx, NULL, &c->status);
145 for (offset = 0; offset < c->tty.sx; offset++)
146 screen_write_putc(&ctx, &stdgc, ' ');
147 screen_write_stop(&ctx);
149 /* If the height is one line, blank status line. */
150 if (c->tty.sy <= 1)
151 goto out;
153 /* Get UTF-8 flag. */
154 utf8flag = options_get_number(&s->options, "status-utf8");
156 /* Work out left and right strings. */
157 memcpy(&lgc, &stdgc, sizeof lgc);
158 left = status_redraw_get_left(c, t, utf8flag, &lgc, &llen);
159 memcpy(&rgc, &stdgc, sizeof rgc);
160 right = status_redraw_get_right(c, t, utf8flag, &rgc, &rlen);
163 * Figure out how much space we have for the window list. If there
164 * isn't enough space, just show a blank status line.
166 needed = 0;
167 if (llen != 0)
168 needed += llen + 1;
169 if (rlen != 0)
170 needed += rlen + 1;
171 if (c->tty.sx == 0 || c->tty.sx <= needed)
172 goto out;
173 wlavailable = c->tty.sx - needed;
175 /* Calculate the total size needed for the window list. */
176 wlstart = wloffset = wlwidth = 0;
177 RB_FOREACH(wl, winlinks, &s->windows) {
178 if (wl->status_text != NULL)
179 xfree(wl->status_text);
180 memcpy(&wl->status_cell, &stdgc, sizeof wl->status_cell);
181 wl->status_text = status_print(c, wl, t, &wl->status_cell);
182 wl->status_width =
183 screen_write_cstrlen(utf8flag, "%s", wl->status_text);
185 if (wl == s->curw)
186 wloffset = wlwidth;
187 wlwidth += wl->status_width + 1;
190 /* Create a new screen for the window list. */
191 screen_init(&window_list, wlwidth, 1, 0);
193 /* And draw the window list into it. */
194 screen_write_start(&ctx, NULL, &window_list);
195 RB_FOREACH(wl, winlinks, &s->windows) {
196 screen_write_cnputs(&ctx,
197 -1, &wl->status_cell, utf8flag, "%s", wl->status_text);
198 screen_write_putc(&ctx, &stdgc, ' ');
200 screen_write_stop(&ctx);
202 /* If there is enough space for the total width, skip to draw now. */
203 if (wlwidth <= wlavailable)
204 goto draw;
206 /* Find size of current window text. */
207 wlsize = s->curw->status_width;
210 * If the current window is already on screen, good to draw from the
211 * start and just leave off the end.
213 if (wloffset + wlsize < wlavailable) {
214 if (wlavailable > 0) {
215 rarrow = 1;
216 wlavailable--;
218 wlwidth = wlavailable;
219 } else {
221 * Work out how many characters we need to omit from the
222 * start. There are wlavailable characters to fill, and
223 * wloffset + wlsize must be the last. So, the start character
224 * is wloffset + wlsize - wlavailable.
226 if (wlavailable > 0) {
227 larrow = 1;
228 wlavailable--;
231 wlstart = wloffset + wlsize - wlavailable;
232 if (wlavailable > 0 && wlwidth > wlstart + wlavailable + 1) {
233 rarrow = 1;
234 wlstart++;
235 wlavailable--;
237 wlwidth = wlavailable;
240 /* Bail if anything is now too small too. */
241 if (wlwidth == 0 || wlavailable == 0) {
242 screen_free(&window_list);
243 goto out;
247 * Now the start position is known, work out the state of the left and
248 * right arrows.
250 offset = 0;
251 RB_FOREACH(wl, winlinks, &s->windows) {
252 if (larrow == 1 && offset < wlstart) {
253 if (session_alert_has(s, wl, WINDOW_ACTIVITY))
254 larrow = -1;
255 else if (session_alert_has(s, wl, WINDOW_BELL))
256 larrow = -1;
257 else if (session_alert_has(s, wl, WINDOW_CONTENT))
258 larrow = -1;
261 offset += wl->status_width;
263 if (rarrow == 1 && offset > wlstart + wlwidth) {
264 if (session_alert_has(s, wl, WINDOW_ACTIVITY))
265 rarrow = -1;
266 else if (session_alert_has(s, wl, WINDOW_BELL))
267 rarrow = -1;
268 else if (session_alert_has(s, wl, WINDOW_CONTENT))
269 rarrow = -1;
273 draw:
274 /* Begin drawing. */
275 screen_write_start(&ctx, NULL, &c->status);
277 /* Draw the left string and arrow. */
278 screen_write_cursormove(&ctx, 0, 0);
279 if (llen != 0) {
280 screen_write_cnputs(&ctx, llen, &lgc, utf8flag, "%s", left);
281 screen_write_putc(&ctx, &stdgc, ' ');
283 if (larrow != 0) {
284 memcpy(&gc, &stdgc, sizeof gc);
285 if (larrow == -1)
286 gc.attr ^= GRID_ATTR_REVERSE;
287 screen_write_putc(&ctx, &gc, '<');
290 /* Draw the right string and arrow. */
291 if (rarrow != 0) {
292 screen_write_cursormove(&ctx, c->tty.sx - rlen - 2, 0);
293 memcpy(&gc, &stdgc, sizeof gc);
294 if (rarrow == -1)
295 gc.attr ^= GRID_ATTR_REVERSE;
296 screen_write_putc(&ctx, &gc, '>');
297 } else
298 screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, 0);
299 if (rlen != 0) {
300 screen_write_putc(&ctx, &stdgc, ' ');
301 screen_write_cnputs(&ctx, rlen, &rgc, utf8flag, "%s", right);
304 /* Figure out the offset for the window list. */
305 if (llen != 0)
306 wloffset = llen + 1;
307 else
308 wloffset = 0;
309 if (wlwidth < wlavailable) {
310 switch (options_get_number(&s->options, "status-justify")) {
311 case 1: /* centered */
312 wloffset += (wlavailable - wlwidth) / 2;
313 break;
314 case 2: /* right */
315 wloffset += (wlavailable - wlwidth);
316 break;
319 if (larrow != 0)
320 wloffset++;
322 /* Copy the window list. */
323 screen_write_cursormove(&ctx, wloffset, 0);
324 screen_write_copy(&ctx, &window_list, wlstart, 0, wlwidth, 1);
325 screen_free(&window_list);
327 screen_write_stop(&ctx);
329 out:
330 if (left != NULL)
331 xfree(left);
332 if (right != NULL)
333 xfree(right);
335 if (grid_compare(c->status.grid, old_status.grid) == 0) {
336 screen_free(&old_status);
337 return (0);
339 screen_free(&old_status);
340 return (1);
343 /* Replace a single special sequence (prefixed by #). */
344 void
345 status_replace1(struct client *c,struct winlink *wl,
346 char **iptr, char **optr, char *out, size_t outsize, int jobsflag)
348 struct session *s = c->session;
349 char ch, tmp[256], *ptr, *endptr, *freeptr;
350 size_t ptrlen;
351 long limit;
353 if (wl == NULL)
354 wl = s->curw;
356 errno = 0;
357 limit = strtol(*iptr, &endptr, 10);
358 if ((limit == 0 && errno != EINVAL) ||
359 (limit == LONG_MIN && errno != ERANGE) ||
360 (limit == LONG_MAX && errno != ERANGE) ||
361 limit != 0)
362 *iptr = endptr;
363 if (limit <= 0)
364 limit = LONG_MAX;
366 freeptr = NULL;
368 switch (*(*iptr)++) {
369 case '(':
370 if (!jobsflag) {
371 ch = ')';
372 goto skip_to;
374 if ((ptr = status_job(c, iptr)) == NULL)
375 return;
376 freeptr = ptr;
377 goto do_replace;
378 case 'H':
379 if (gethostname(tmp, sizeof tmp) != 0)
380 fatal("gethostname failed");
381 ptr = tmp;
382 goto do_replace;
383 case 'I':
384 xsnprintf(tmp, sizeof tmp, "%d", wl->idx);
385 ptr = tmp;
386 goto do_replace;
387 case 'P':
388 xsnprintf(tmp, sizeof tmp, "%u",
389 window_pane_index(wl->window, wl->window->active));
390 ptr = tmp;
391 goto do_replace;
392 case 'S':
393 ptr = s->name;
394 goto do_replace;
395 case 'T':
396 ptr = wl->window->active->base.title;
397 goto do_replace;
398 case 'W':
399 ptr = wl->window->name;
400 goto do_replace;
401 case 'F':
402 tmp[0] = ' ';
403 if (session_alert_has(s, wl, WINDOW_CONTENT))
404 tmp[0] = '+';
405 else if (session_alert_has(s, wl, WINDOW_BELL))
406 tmp[0] = '!';
407 else if (session_alert_has(s, wl, WINDOW_ACTIVITY))
408 tmp[0] = '#';
409 else if (wl == s->curw)
410 tmp[0] = '*';
411 else if (wl == TAILQ_FIRST(&s->lastw))
412 tmp[0] = '-';
413 tmp[1] = '\0';
414 ptr = tmp;
415 goto do_replace;
416 case '[':
418 * Embedded style, handled at display time. Leave present and
419 * skip input until ].
421 ch = ']';
422 goto skip_to;
423 case '#':
424 *(*optr)++ = '#';
425 break;
428 return;
430 do_replace:
431 ptrlen = strlen(ptr);
432 if ((size_t) limit < ptrlen)
433 ptrlen = limit;
435 if (*optr + ptrlen >= out + outsize - 1)
436 return;
437 while (ptrlen > 0 && *ptr != '\0') {
438 *(*optr)++ = *ptr++;
439 ptrlen--;
442 if (freeptr != NULL)
443 xfree(freeptr);
444 return;
446 skip_to:
447 *(*optr)++ = '#';
449 (*iptr)--; /* include ch */
450 while (**iptr != ch && **iptr != '\0') {
451 if (*optr >= out + outsize - 1)
452 break;
453 *(*optr)++ = *(*iptr)++;
457 /* Replace special sequences in fmt. */
458 char *
459 status_replace(struct client *c,
460 struct winlink *wl, const char *fmt, time_t t, int jobsflag)
462 static char out[BUFSIZ];
463 char in[BUFSIZ], ch, *iptr, *optr;
465 strftime(in, sizeof in, fmt, localtime(&t));
466 in[(sizeof in) - 1] = '\0';
468 iptr = in;
469 optr = out;
471 while (*iptr != '\0') {
472 if (optr >= out + (sizeof out) - 1)
473 break;
474 ch = *iptr++;
476 if (ch != '#') {
477 *optr++ = ch;
478 continue;
480 status_replace1(c, wl, &iptr, &optr, out, sizeof out, jobsflag);
482 *optr = '\0';
484 return (xstrdup(out));
487 /* Figure out job name and get its result, starting it off if necessary. */
488 char *
489 status_job(struct client *c, char **iptr)
491 struct job *job;
492 char *cmd;
493 int lastesc;
494 size_t len;
496 if (**iptr == '\0')
497 return (NULL);
498 if (**iptr == ')') { /* no command given */
499 (*iptr)++;
500 return (NULL);
503 cmd = xmalloc(strlen(*iptr) + 1);
504 len = 0;
506 lastesc = 0;
507 for (; **iptr != '\0'; (*iptr)++) {
508 if (!lastesc && **iptr == ')')
509 break; /* unescaped ) is the end */
510 if (!lastesc && **iptr == '\\') {
511 lastesc = 1;
512 continue; /* skip \ if not escaped */
514 lastesc = 0;
515 cmd[len++] = **iptr;
517 if (**iptr == '\0') /* no terminating ) */ {
518 xfree(cmd);
519 return (NULL);
521 (*iptr)++; /* skip final ) */
522 cmd[len] = '\0';
524 job = job_get(&c->status_jobs, cmd);
525 if (job == NULL) {
526 job = job_add(&c->status_jobs,
527 JOB_PERSIST, c, cmd, status_job_callback, xfree, NULL);
528 job_run(job);
530 xfree(cmd);
531 if (job->data == NULL)
532 return (xstrdup(""));
533 return (xstrdup(job->data));
536 /* Job has finished: save its result. */
537 void
538 status_job_callback(struct job *job)
540 char *line, *buf;
541 size_t len;
543 buf = NULL;
544 if ((line = evbuffer_readline(job->event->input)) == NULL) {
545 len = EVBUFFER_LENGTH(job->event->input);
546 buf = xmalloc(len + 1);
547 if (len != 0)
548 memcpy(buf, EVBUFFER_DATA(job->event->input), len);
549 buf[len] = '\0';
552 if (job->data != NULL)
553 xfree(job->data);
554 else
555 server_redraw_client(job->client);
557 if (line == NULL)
558 job->data = buf;
559 else
560 job->data = xstrdup(line);
563 /* Calculate winlink status line entry width. */
564 size_t
565 status_width(struct client *c, struct winlink *wl, time_t t)
567 struct options *oo = &wl->window->options;
568 struct session *s = c->session;
569 const char *fmt;
570 char *text;
571 size_t size;
572 int utf8flag;
574 utf8flag = options_get_number(&s->options, "status-utf8");
576 fmt = options_get_string(&wl->window->options, "window-status-format");
577 if (wl == s->curw)
578 fmt = options_get_string(oo, "window-status-current-format");
580 text = status_replace(c, wl, fmt, t, 1);
581 size = screen_write_cstrlen(utf8flag, "%s", text);
582 xfree(text);
584 return (size);
587 /* Return winlink status line entry and adjust gc as necessary. */
588 char *
589 status_print(
590 struct client *c, struct winlink *wl, time_t t, struct grid_cell *gc)
592 struct options *oo = &wl->window->options;
593 struct session *s = c->session;
594 const char *fmt;
595 char *text;
596 u_char fg, bg, attr;
598 fg = options_get_number(oo, "window-status-fg");
599 if (fg != 8)
600 colour_set_fg(gc, fg);
601 bg = options_get_number(oo, "window-status-bg");
602 if (bg != 8)
603 colour_set_bg(gc, bg);
604 attr = options_get_number(oo, "window-status-attr");
605 if (attr != 0)
606 gc->attr = attr;
607 fmt = options_get_string(oo, "window-status-format");
608 if (wl == s->curw) {
609 fg = options_get_number(oo, "window-status-current-fg");
610 if (fg != 8)
611 colour_set_fg(gc, fg);
612 bg = options_get_number(oo, "window-status-current-bg");
613 if (bg != 8)
614 colour_set_bg(gc, bg);
615 attr = options_get_number(oo, "window-status-current-attr");
616 if (attr != 0)
617 gc->attr = attr;
618 fmt = options_get_string(oo, "window-status-current-format");
621 if (session_alert_has(s, wl, WINDOW_ACTIVITY) ||
622 session_alert_has(s, wl, WINDOW_BELL) ||
623 session_alert_has(s, wl, WINDOW_CONTENT))
624 gc->attr ^= GRID_ATTR_REVERSE;
626 text = status_replace(c, wl, fmt, t, 1);
627 return (text);
630 /* Set a status line message. */
631 void printflike2
632 status_message_set(struct client *c, const char *fmt, ...)
634 struct timeval tv;
635 struct session *s = c->session;
636 struct message_entry *msg;
637 va_list ap;
638 int delay;
639 u_int i, limit;
641 status_prompt_clear(c);
642 status_message_clear(c);
644 va_start(ap, fmt);
645 xvasprintf(&c->message_string, fmt, ap);
646 va_end(ap);
648 ARRAY_EXPAND(&c->message_log, 1);
649 msg = &ARRAY_LAST(&c->message_log);
650 msg->msg_time = time(NULL);
651 msg->msg = xstrdup(c->message_string);
653 if (s == NULL)
654 limit = 0;
655 else
656 limit = options_get_number(&s->options, "message-limit");
657 if (ARRAY_LENGTH(&c->message_log) > limit) {
658 limit = ARRAY_LENGTH(&c->message_log) - limit;
659 for (i = 0; i < limit; i++) {
660 msg = &ARRAY_FIRST(&c->message_log);
661 xfree(msg->msg);
662 ARRAY_REMOVE(&c->message_log, 0);
666 delay = options_get_number(&c->session->options, "display-time");
667 tv.tv_sec = delay / 1000;
668 tv.tv_usec = (delay % 1000) * 1000L;
670 evtimer_del(&c->message_timer);
671 evtimer_set(&c->message_timer, status_message_callback, c);
672 evtimer_add(&c->message_timer, &tv);
674 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
675 c->flags |= CLIENT_STATUS;
678 /* Clear status line message. */
679 void
680 status_message_clear(struct client *c)
682 if (c->message_string == NULL)
683 return;
685 xfree(c->message_string);
686 c->message_string = NULL;
688 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
689 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
691 screen_reinit(&c->status);
694 /* Clear status line message after timer expires. */
695 /* ARGSUSED */
696 void
697 status_message_callback(unused int fd, unused short event, void *data)
699 struct client *c = data;
701 status_message_clear(c);
704 /* Draw client message on status line of present else on last line. */
706 status_message_redraw(struct client *c)
708 struct screen_write_ctx ctx;
709 struct session *s = c->session;
710 struct screen old_status;
711 size_t len;
712 struct grid_cell gc;
713 int utf8flag;
715 if (c->tty.sx == 0 || c->tty.sy == 0)
716 return (0);
717 memcpy(&old_status, &c->status, sizeof old_status);
718 screen_init(&c->status, c->tty.sx, 1, 0);
720 utf8flag = options_get_number(&s->options, "status-utf8");
722 len = screen_write_strlen(utf8flag, "%s", c->message_string);
723 if (len > c->tty.sx)
724 len = c->tty.sx;
726 memcpy(&gc, &grid_default_cell, sizeof gc);
727 colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
728 colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
729 gc.attr |= options_get_number(&s->options, "message-attr");
731 screen_write_start(&ctx, NULL, &c->status);
733 screen_write_cursormove(&ctx, 0, 0);
734 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->message_string);
735 for (; len < c->tty.sx; len++)
736 screen_write_putc(&ctx, &gc, ' ');
738 screen_write_stop(&ctx);
740 if (grid_compare(c->status.grid, old_status.grid) == 0) {
741 screen_free(&old_status);
742 return (0);
744 screen_free(&old_status);
745 return (1);
748 /* Enable status line prompt. */
749 void
750 status_prompt_set(struct client *c, const char *msg,
751 int (*callbackfn)(void *, const char *), void (*freefn)(void *),
752 void *data, int flags)
754 int keys;
756 status_message_clear(c);
757 status_prompt_clear(c);
759 c->prompt_string = xstrdup(msg);
761 c->prompt_buffer = xstrdup("");
762 c->prompt_index = 0;
764 c->prompt_callbackfn = callbackfn;
765 c->prompt_freefn = freefn;
766 c->prompt_data = data;
768 c->prompt_hindex = 0;
770 c->prompt_flags = flags;
772 keys = options_get_number(&c->session->options, "status-keys");
773 if (keys == MODEKEY_EMACS)
774 mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit);
775 else
776 mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit);
778 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
779 c->flags |= CLIENT_STATUS;
782 /* Remove status line prompt. */
783 void
784 status_prompt_clear(struct client *c)
786 if (c->prompt_string == NULL)
787 return;
789 if (c->prompt_freefn != NULL && c->prompt_data != NULL)
790 c->prompt_freefn(c->prompt_data);
792 xfree(c->prompt_string);
793 c->prompt_string = NULL;
795 xfree(c->prompt_buffer);
796 c->prompt_buffer = NULL;
798 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
799 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
801 screen_reinit(&c->status);
804 /* Update status line prompt with a new prompt string. */
805 void
806 status_prompt_update(struct client *c, const char *msg)
808 xfree(c->prompt_string);
809 c->prompt_string = xstrdup(msg);
811 *c->prompt_buffer = '\0';
812 c->prompt_index = 0;
814 c->prompt_hindex = 0;
816 c->flags |= CLIENT_STATUS;
819 /* Draw client prompt on status line of present else on last line. */
821 status_prompt_redraw(struct client *c)
823 struct screen_write_ctx ctx;
824 struct session *s = c->session;
825 struct screen old_status;
826 size_t i, size, left, len, off;
827 struct grid_cell gc, *gcp;
828 int utf8flag;
830 if (c->tty.sx == 0 || c->tty.sy == 0)
831 return (0);
832 memcpy(&old_status, &c->status, sizeof old_status);
833 screen_init(&c->status, c->tty.sx, 1, 0);
835 utf8flag = options_get_number(&s->options, "status-utf8");
837 len = screen_write_strlen(utf8flag, "%s", c->prompt_string);
838 if (len > c->tty.sx)
839 len = c->tty.sx;
840 off = 0;
842 memcpy(&gc, &grid_default_cell, sizeof gc);
843 colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
844 colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
845 gc.attr |= options_get_number(&s->options, "message-attr");
847 screen_write_start(&ctx, NULL, &c->status);
849 screen_write_cursormove(&ctx, 0, 0);
850 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->prompt_string);
852 left = c->tty.sx - len;
853 if (left != 0) {
854 size = screen_write_strlen(utf8flag, "%s", c->prompt_buffer);
855 if (c->prompt_index >= left) {
856 off = c->prompt_index - left + 1;
857 if (c->prompt_index == size)
858 left--;
859 size = left;
861 screen_write_nputs(
862 &ctx, left, &gc, utf8flag, "%s", c->prompt_buffer + off);
864 for (i = len + size; i < c->tty.sx; i++)
865 screen_write_putc(&ctx, &gc, ' ');
868 screen_write_stop(&ctx);
870 /* Apply fake cursor. */
871 off = len + c->prompt_index - off;
872 gcp = grid_view_get_cell(c->status.grid, off, 0);
873 gcp->attr ^= GRID_ATTR_REVERSE;
875 if (grid_compare(c->status.grid, old_status.grid) == 0) {
876 screen_free(&old_status);
877 return (0);
879 screen_free(&old_status);
880 return (1);
883 /* Handle keys in prompt. */
884 void
885 status_prompt_key(struct client *c, int key)
887 struct paste_buffer *pb;
888 char *s, *first, *last, word[64], swapc;
889 u_char ch;
890 size_t size, n, off, idx;
892 size = strlen(c->prompt_buffer);
893 switch (mode_key_lookup(&c->prompt_mdata, key)) {
894 case MODEKEYEDIT_CURSORLEFT:
895 if (c->prompt_index > 0) {
896 c->prompt_index--;
897 c->flags |= CLIENT_STATUS;
899 break;
900 case MODEKEYEDIT_SWITCHMODEAPPEND:
901 case MODEKEYEDIT_CURSORRIGHT:
902 if (c->prompt_index < size) {
903 c->prompt_index++;
904 c->flags |= CLIENT_STATUS;
906 break;
907 case MODEKEYEDIT_STARTOFLINE:
908 if (c->prompt_index != 0) {
909 c->prompt_index = 0;
910 c->flags |= CLIENT_STATUS;
912 break;
913 case MODEKEYEDIT_ENDOFLINE:
914 if (c->prompt_index != size) {
915 c->prompt_index = size;
916 c->flags |= CLIENT_STATUS;
918 break;
919 case MODEKEYEDIT_COMPLETE:
920 if (*c->prompt_buffer == '\0')
921 break;
923 idx = c->prompt_index;
924 if (idx != 0)
925 idx--;
927 /* Find the word we are in. */
928 first = c->prompt_buffer + idx;
929 while (first > c->prompt_buffer && *first != ' ')
930 first--;
931 while (*first == ' ')
932 first++;
933 last = c->prompt_buffer + idx;
934 while (*last != '\0' && *last != ' ')
935 last++;
936 while (*last == ' ')
937 last--;
938 if (*last != '\0')
939 last++;
940 if (last <= first ||
941 ((size_t) (last - first)) > (sizeof word) - 1)
942 break;
943 memcpy(word, first, last - first);
944 word[last - first] = '\0';
946 /* And try to complete it. */
947 if ((s = status_prompt_complete(word)) == NULL)
948 break;
950 /* Trim out word. */
951 n = size - (last - c->prompt_buffer) + 1; /* with \0 */
952 memmove(first, last, n);
953 size -= last - first;
955 /* Insert the new word. */
956 size += strlen(s);
957 off = first - c->prompt_buffer;
958 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1);
959 first = c->prompt_buffer + off;
960 memmove(first + strlen(s), first, n);
961 memcpy(first, s, strlen(s));
963 c->prompt_index = (first - c->prompt_buffer) + strlen(s);
964 xfree(s);
966 c->flags |= CLIENT_STATUS;
967 break;
968 case MODEKEYEDIT_BACKSPACE:
969 if (c->prompt_index != 0) {
970 if (c->prompt_index == size)
971 c->prompt_buffer[--c->prompt_index] = '\0';
972 else {
973 memmove(c->prompt_buffer + c->prompt_index - 1,
974 c->prompt_buffer + c->prompt_index,
975 size + 1 - c->prompt_index);
976 c->prompt_index--;
978 c->flags |= CLIENT_STATUS;
980 break;
981 case MODEKEYEDIT_DELETE:
982 if (c->prompt_index != size) {
983 memmove(c->prompt_buffer + c->prompt_index,
984 c->prompt_buffer + c->prompt_index + 1,
985 size + 1 - c->prompt_index);
986 c->flags |= CLIENT_STATUS;
988 break;
989 case MODEKEYEDIT_DELETELINE:
990 *c->prompt_buffer = '\0';
991 c->prompt_index = 0;
992 c->flags |= CLIENT_STATUS;
993 break;
994 case MODEKEYEDIT_DELETETOENDOFLINE:
995 if (c->prompt_index < size) {
996 c->prompt_buffer[c->prompt_index] = '\0';
997 c->flags |= CLIENT_STATUS;
999 break;
1000 case MODEKEYEDIT_HISTORYUP:
1001 if (ARRAY_LENGTH(&c->prompt_hdata) == 0)
1002 break;
1003 xfree(c->prompt_buffer);
1005 c->prompt_buffer = xstrdup(ARRAY_ITEM(&c->prompt_hdata,
1006 ARRAY_LENGTH(&c->prompt_hdata) - 1 - c->prompt_hindex));
1007 if (c->prompt_hindex != ARRAY_LENGTH(&c->prompt_hdata) - 1)
1008 c->prompt_hindex++;
1010 c->prompt_index = strlen(c->prompt_buffer);
1011 c->flags |= CLIENT_STATUS;
1012 break;
1013 case MODEKEYEDIT_HISTORYDOWN:
1014 xfree(c->prompt_buffer);
1016 if (c->prompt_hindex != 0) {
1017 c->prompt_hindex--;
1018 c->prompt_buffer = xstrdup(ARRAY_ITEM(
1019 &c->prompt_hdata, ARRAY_LENGTH(
1020 &c->prompt_hdata) - 1 - c->prompt_hindex));
1021 } else
1022 c->prompt_buffer = xstrdup("");
1024 c->prompt_index = strlen(c->prompt_buffer);
1025 c->flags |= CLIENT_STATUS;
1026 break;
1027 case MODEKEYEDIT_PASTE:
1028 if ((pb = paste_get_top(&c->session->buffers)) == NULL)
1029 break;
1030 for (n = 0; n < pb->size; n++) {
1031 ch = (u_char) pb->data[n];
1032 if (ch < 32 || ch == 127)
1033 break;
1036 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1);
1037 if (c->prompt_index == size) {
1038 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1039 c->prompt_index += n;
1040 c->prompt_buffer[c->prompt_index] = '\0';
1041 } else {
1042 memmove(c->prompt_buffer + c->prompt_index + n,
1043 c->prompt_buffer + c->prompt_index,
1044 size + 1 - c->prompt_index);
1045 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1046 c->prompt_index += n;
1049 c->flags |= CLIENT_STATUS;
1050 break;
1051 case MODEKEYEDIT_TRANSPOSECHARS:
1052 idx = c->prompt_index;
1053 if (idx < size)
1054 idx++;
1055 if (idx >= 2) {
1056 swapc = c->prompt_buffer[idx - 2];
1057 c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1];
1058 c->prompt_buffer[idx - 1] = swapc;
1059 c->prompt_index = idx;
1060 c->flags |= CLIENT_STATUS;
1062 break;
1063 case MODEKEYEDIT_ENTER:
1064 if (*c->prompt_buffer != '\0')
1065 status_prompt_add_history(c);
1066 if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0)
1067 status_prompt_clear(c);
1068 break;
1069 case MODEKEYEDIT_CANCEL:
1070 if (c->prompt_callbackfn(c->prompt_data, NULL) == 0)
1071 status_prompt_clear(c);
1072 break;
1073 case MODEKEY_OTHER:
1074 if (key < 32 || key == 127)
1075 break;
1076 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2);
1078 if (c->prompt_index == size) {
1079 c->prompt_buffer[c->prompt_index++] = key;
1080 c->prompt_buffer[c->prompt_index] = '\0';
1081 } else {
1082 memmove(c->prompt_buffer + c->prompt_index + 1,
1083 c->prompt_buffer + c->prompt_index,
1084 size + 1 - c->prompt_index);
1085 c->prompt_buffer[c->prompt_index++] = key;
1088 if (c->prompt_flags & PROMPT_SINGLE) {
1089 if (c->prompt_callbackfn(
1090 c->prompt_data, c->prompt_buffer) == 0)
1091 status_prompt_clear(c);
1094 c->flags |= CLIENT_STATUS;
1095 break;
1096 default:
1097 break;
1101 /* Add line to the history. */
1102 void
1103 status_prompt_add_history(struct client *c)
1105 if (ARRAY_LENGTH(&c->prompt_hdata) > 0 &&
1106 strcmp(ARRAY_LAST(&c->prompt_hdata), c->prompt_buffer) == 0)
1107 return;
1109 if (ARRAY_LENGTH(&c->prompt_hdata) == PROMPT_HISTORY) {
1110 xfree(ARRAY_FIRST(&c->prompt_hdata));
1111 ARRAY_REMOVE(&c->prompt_hdata, 0);
1114 ARRAY_ADD(&c->prompt_hdata, xstrdup(c->prompt_buffer));
1117 /* Complete word. */
1118 char *
1119 status_prompt_complete(const char *s)
1121 const struct cmd_entry **cmdent;
1122 const struct set_option_entry *entry;
1123 ARRAY_DECL(, const char *) list;
1124 char *prefix, *s2;
1125 u_int i;
1126 size_t j;
1128 if (*s == '\0')
1129 return (NULL);
1131 /* First, build a list of all the possible matches. */
1132 ARRAY_INIT(&list);
1133 for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1134 if (strncmp((*cmdent)->name, s, strlen(s)) == 0)
1135 ARRAY_ADD(&list, (*cmdent)->name);
1137 for (entry = set_option_table; entry->name != NULL; entry++) {
1138 if (strncmp(entry->name, s, strlen(s)) == 0)
1139 ARRAY_ADD(&list, entry->name);
1141 for (entry = set_session_option_table; entry->name != NULL; entry++) {
1142 if (strncmp(entry->name, s, strlen(s)) == 0)
1143 ARRAY_ADD(&list, entry->name);
1145 for (entry = set_window_option_table; entry->name != NULL; entry++) {
1146 if (strncmp(entry->name, s, strlen(s)) == 0)
1147 ARRAY_ADD(&list, entry->name);
1150 /* If none, bail now. */
1151 if (ARRAY_LENGTH(&list) == 0) {
1152 ARRAY_FREE(&list);
1153 return (NULL);
1156 /* If an exact match, return it, with a trailing space. */
1157 if (ARRAY_LENGTH(&list) == 1) {
1158 xasprintf(&s2, "%s ", ARRAY_FIRST(&list));
1159 ARRAY_FREE(&list);
1160 return (s2);
1163 /* Now loop through the list and find the longest common prefix. */
1164 prefix = xstrdup(ARRAY_FIRST(&list));
1165 for (i = 1; i < ARRAY_LENGTH(&list); i++) {
1166 s = ARRAY_ITEM(&list, i);
1168 j = strlen(s);
1169 if (j > strlen(prefix))
1170 j = strlen(prefix);
1171 for (; j > 0; j--) {
1172 if (prefix[j - 1] != s[j - 1])
1173 prefix[j - 1] = '\0';
1177 ARRAY_FREE(&list);
1178 return (prefix);