Fix another stray addition that was too early. Oops.
[tmux-openbsd.git] / status.c
blob74316f2d800df4a592b3d92380a2611744c1c930
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 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 /* Retrieve options for left string. */
53 char *
54 status_redraw_get_left(struct client *c,
55 time_t t, int utf8flag, struct grid_cell *gc, size_t *size)
57 struct session *s = c->session;
58 char *left;
59 u_char fg, bg, attr;
60 size_t leftlen;
62 fg = options_get_number(&s->options, "status-left-fg");
63 if (fg != 8)
64 colour_set_fg(gc, fg);
65 bg = options_get_number(&s->options, "status-left-bg");
66 if (bg != 8)
67 colour_set_bg(gc, bg);
68 attr = options_get_number(&s->options, "status-left-attr");
69 if (attr != 0)
70 gc->attr = attr;
72 left = status_replace(
73 c, NULL, options_get_string(&s->options, "status-left"), t, 1);
75 *size = options_get_number(&s->options, "status-left-length");
76 leftlen = screen_write_cstrlen(utf8flag, "%s", left);
77 if (leftlen < *size)
78 *size = leftlen;
79 return (left);
82 /* Retrieve options for right string. */
83 char *
84 status_redraw_get_right(struct client *c,
85 time_t t, int utf8flag, struct grid_cell *gc, size_t *size)
87 struct session *s = c->session;
88 char *right;
89 u_char fg, bg, attr;
90 size_t rightlen;
92 fg = options_get_number(&s->options, "status-right-fg");
93 if (fg != 8)
94 colour_set_fg(gc, fg);
95 bg = options_get_number(&s->options, "status-right-bg");
96 if (bg != 8)
97 colour_set_bg(gc, bg);
98 attr = options_get_number(&s->options, "status-right-attr");
99 if (attr != 0)
100 gc->attr = attr;
102 right = status_replace(
103 c, NULL, options_get_string(&s->options, "status-right"), t, 1);
105 *size = options_get_number(&s->options, "status-right-length");
106 rightlen = screen_write_cstrlen(utf8flag, "%s", right);
107 if (rightlen < *size)
108 *size = rightlen;
109 return (right);
112 /* Draw status for client on the last lines of given context. */
114 status_redraw(struct client *c)
116 struct screen_write_ctx ctx;
117 struct session *s = c->session;
118 struct winlink *wl;
119 struct screen old_status, window_list;
120 struct grid_cell stdgc, lgc, rgc, gc;
121 time_t t;
122 char *left, *right;
123 u_int offset, needed;
124 u_int wlstart, wlwidth, wlavailable, wloffset, wlsize;
125 size_t llen, rlen;
126 int larrow, rarrow, utf8flag;
128 /* No status line? */
129 if (c->tty.sy == 0 || !options_get_number(&s->options, "status"))
130 return (1);
131 left = right = NULL;
132 larrow = rarrow = 0;
134 /* Update status timer. */
135 if (gettimeofday(&c->status_timer, NULL) != 0)
136 fatal("gettimeofday failed");
137 t = c->status_timer.tv_sec;
139 /* Set up default colour. */
140 memcpy(&stdgc, &grid_default_cell, sizeof gc);
141 colour_set_fg(&stdgc, options_get_number(&s->options, "status-fg"));
142 colour_set_bg(&stdgc, options_get_number(&s->options, "status-bg"));
143 stdgc.attr |= options_get_number(&s->options, "status-attr");
145 /* Create the target screen. */
146 memcpy(&old_status, &c->status, sizeof old_status);
147 screen_init(&c->status, c->tty.sx, 1, 0);
148 screen_write_start(&ctx, NULL, &c->status);
149 for (offset = 0; offset < c->tty.sx; offset++)
150 screen_write_putc(&ctx, &stdgc, ' ');
151 screen_write_stop(&ctx);
153 /* If the height is one line, blank status line. */
154 if (c->tty.sy <= 1)
155 goto out;
157 /* Get UTF-8 flag. */
158 utf8flag = options_get_number(&s->options, "status-utf8");
160 /* Work out left and right strings. */
161 memcpy(&lgc, &stdgc, sizeof lgc);
162 left = status_redraw_get_left(c, t, utf8flag, &lgc, &llen);
163 memcpy(&rgc, &stdgc, sizeof rgc);
164 right = status_redraw_get_right(c, t, utf8flag, &rgc, &rlen);
167 * Figure out how much space we have for the window list. If there
168 * isn't enough space, just show a blank status line.
170 needed = 0;
171 if (llen != 0)
172 needed += llen + 1;
173 if (rlen != 0)
174 needed += rlen + 1;
175 if (c->tty.sx == 0 || c->tty.sx <= needed)
176 goto out;
177 wlavailable = c->tty.sx - needed;
179 /* Calculate the total size needed for the window list. */
180 wlstart = wloffset = wlwidth = 0;
181 RB_FOREACH(wl, winlinks, &s->windows) {
182 if (wl->status_text != NULL)
183 xfree(wl->status_text);
184 memcpy(&wl->status_cell, &stdgc, sizeof wl->status_cell);
185 wl->status_text = status_print(c, wl, t, &wl->status_cell);
186 wl->status_width =
187 screen_write_cstrlen(utf8flag, "%s", wl->status_text);
189 if (wl == s->curw)
190 wloffset = wlwidth;
191 wlwidth += wl->status_width + 1;
194 /* Create a new screen for the window list. */
195 screen_init(&window_list, wlwidth, 1, 0);
197 /* And draw the window list into it. */
198 screen_write_start(&ctx, NULL, &window_list);
199 RB_FOREACH(wl, winlinks, &s->windows) {
200 screen_write_cnputs(&ctx,
201 -1, &wl->status_cell, utf8flag, "%s", wl->status_text);
202 screen_write_putc(&ctx, &stdgc, ' ');
204 screen_write_stop(&ctx);
206 /* If there is enough space for the total width, skip to draw now. */
207 if (wlwidth <= wlavailable)
208 goto draw;
210 /* Find size of current window text. */
211 wlsize = s->curw->status_width;
214 * If the current window is already on screen, good to draw from the
215 * start and just leave off the end.
217 if (wloffset + wlsize < wlavailable) {
218 if (wlavailable > 0) {
219 rarrow = 1;
220 wlavailable--;
222 wlwidth = wlavailable;
223 } else {
225 * Work out how many characters we need to omit from the
226 * start. There are wlavailable characters to fill, and
227 * wloffset + wlsize must be the last. So, the start character
228 * is wloffset + wlsize - wlavailable.
230 if (wlavailable > 0) {
231 larrow = 1;
232 wlavailable--;
235 wlstart = wloffset + wlsize - wlavailable;
236 if (wlavailable > 0 && wlwidth > wlstart + wlavailable + 1) {
237 rarrow = 1;
238 wlstart++;
239 wlavailable--;
241 wlwidth = wlavailable;
244 /* Bail if anything is now too small too. */
245 if (wlwidth == 0 || wlavailable == 0) {
246 screen_free(&window_list);
247 goto out;
251 * Now the start position is known, work out the state of the left and
252 * right arrows.
254 offset = 0;
255 RB_FOREACH(wl, winlinks, &s->windows) {
256 if (wl->flags & WINLINK_ALERTFLAGS &&
257 larrow == 1 && offset < wlstart)
258 larrow = -1;
260 offset += wl->status_width;
262 if (wl->flags & WINLINK_ALERTFLAGS &&
263 rarrow == 1 && offset > wlstart + wlwidth)
264 rarrow = -1;
267 draw:
268 /* Begin drawing. */
269 screen_write_start(&ctx, NULL, &c->status);
271 /* Draw the left string and arrow. */
272 screen_write_cursormove(&ctx, 0, 0);
273 if (llen != 0) {
274 screen_write_cnputs(&ctx, llen, &lgc, utf8flag, "%s", left);
275 screen_write_putc(&ctx, &stdgc, ' ');
277 if (larrow != 0) {
278 memcpy(&gc, &stdgc, sizeof gc);
279 if (larrow == -1)
280 gc.attr ^= GRID_ATTR_REVERSE;
281 screen_write_putc(&ctx, &gc, '<');
284 /* Draw the right string and arrow. */
285 if (rarrow != 0) {
286 screen_write_cursormove(&ctx, c->tty.sx - rlen - 2, 0);
287 memcpy(&gc, &stdgc, sizeof gc);
288 if (rarrow == -1)
289 gc.attr ^= GRID_ATTR_REVERSE;
290 screen_write_putc(&ctx, &gc, '>');
291 } else
292 screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, 0);
293 if (rlen != 0) {
294 screen_write_putc(&ctx, &stdgc, ' ');
295 screen_write_cnputs(&ctx, rlen, &rgc, utf8flag, "%s", right);
298 /* Figure out the offset for the window list. */
299 if (llen != 0)
300 wloffset = llen + 1;
301 else
302 wloffset = 0;
303 if (wlwidth < wlavailable) {
304 switch (options_get_number(&s->options, "status-justify")) {
305 case 1: /* centered */
306 wloffset += (wlavailable - wlwidth) / 2;
307 break;
308 case 2: /* right */
309 wloffset += (wlavailable - wlwidth);
310 break;
313 if (larrow != 0)
314 wloffset++;
316 /* Copy the window list. */
317 screen_write_cursormove(&ctx, wloffset, 0);
318 screen_write_copy(&ctx, &window_list, wlstart, 0, wlwidth, 1);
319 screen_free(&window_list);
321 screen_write_stop(&ctx);
323 out:
324 if (left != NULL)
325 xfree(left);
326 if (right != NULL)
327 xfree(right);
329 if (grid_compare(c->status.grid, old_status.grid) == 0) {
330 screen_free(&old_status);
331 return (0);
333 screen_free(&old_status);
334 return (1);
337 /* Replace a single special sequence (prefixed by #). */
338 void
339 status_replace1(struct client *c,struct winlink *wl,
340 char **iptr, char **optr, char *out, size_t outsize, int jobsflag)
342 struct session *s = c->session;
343 char ch, tmp[256], *ptr, *endptr, *freeptr;
344 size_t ptrlen;
345 long limit;
347 if (wl == NULL)
348 wl = s->curw;
350 errno = 0;
351 limit = strtol(*iptr, &endptr, 10);
352 if ((limit == 0 && errno != EINVAL) ||
353 (limit == LONG_MIN && errno != ERANGE) ||
354 (limit == LONG_MAX && errno != ERANGE) ||
355 limit != 0)
356 *iptr = endptr;
357 if (limit <= 0)
358 limit = LONG_MAX;
360 freeptr = NULL;
362 switch (*(*iptr)++) {
363 case '(':
364 if (!jobsflag) {
365 ch = ')';
366 goto skip_to;
368 if ((ptr = status_job(c, iptr)) == NULL)
369 return;
370 freeptr = ptr;
371 goto do_replace;
372 case 'H':
373 if (gethostname(tmp, sizeof tmp) != 0)
374 fatal("gethostname failed");
375 ptr = tmp;
376 goto do_replace;
377 case 'I':
378 xsnprintf(tmp, sizeof tmp, "%d", wl->idx);
379 ptr = tmp;
380 goto do_replace;
381 case 'P':
382 xsnprintf(tmp, sizeof tmp, "%u",
383 window_pane_index(wl->window, wl->window->active));
384 ptr = tmp;
385 goto do_replace;
386 case 'S':
387 ptr = s->name;
388 goto do_replace;
389 case 'T':
390 ptr = wl->window->active->base.title;
391 goto do_replace;
392 case 'W':
393 ptr = wl->window->name;
394 goto do_replace;
395 case 'F':
396 tmp[0] = ' ';
397 if (wl->flags & WINLINK_CONTENT)
398 tmp[0] = '+';
399 else if (wl->flags & WINLINK_BELL)
400 tmp[0] = '!';
401 else if (wl->flags & WINLINK_ACTIVITY)
402 tmp[0] = '#';
403 else if (wl->flags & WINLINK_SILENCE)
404 tmp[0] = '~';
405 else if (wl == s->curw)
406 tmp[0] = '*';
407 else if (wl == TAILQ_FIRST(&s->lastw))
408 tmp[0] = '-';
409 tmp[1] = '\0';
410 ptr = tmp;
411 goto do_replace;
412 case '[':
414 * Embedded style, handled at display time. Leave present and
415 * skip input until ].
417 ch = ']';
418 goto skip_to;
419 case '#':
420 *(*optr)++ = '#';
421 break;
424 return;
426 do_replace:
427 ptrlen = strlen(ptr);
428 if ((size_t) limit < ptrlen)
429 ptrlen = limit;
431 if (*optr + ptrlen >= out + outsize - 1)
432 return;
433 while (ptrlen > 0 && *ptr != '\0') {
434 *(*optr)++ = *ptr++;
435 ptrlen--;
438 if (freeptr != NULL)
439 xfree(freeptr);
440 return;
442 skip_to:
443 *(*optr)++ = '#';
445 (*iptr)--; /* include ch */
446 while (**iptr != ch && **iptr != '\0') {
447 if (*optr >= out + outsize - 1)
448 break;
449 *(*optr)++ = *(*iptr)++;
453 /* Replace special sequences in fmt. */
454 char *
455 status_replace(struct client *c,
456 struct winlink *wl, const char *fmt, time_t t, int jobsflag)
458 static char out[BUFSIZ];
459 char in[BUFSIZ], ch, *iptr, *optr;
461 strftime(in, sizeof in, fmt, localtime(&t));
462 in[(sizeof in) - 1] = '\0';
464 iptr = in;
465 optr = out;
467 while (*iptr != '\0') {
468 if (optr >= out + (sizeof out) - 1)
469 break;
470 ch = *iptr++;
472 if (ch != '#') {
473 *optr++ = ch;
474 continue;
476 status_replace1(c, wl, &iptr, &optr, out, sizeof out, jobsflag);
478 *optr = '\0';
480 return (xstrdup(out));
483 /* Figure out job name and get its result, starting it off if necessary. */
484 char *
485 status_job(struct client *c, char **iptr)
487 struct job *job;
488 char *cmd;
489 int lastesc;
490 size_t len;
492 if (**iptr == '\0')
493 return (NULL);
494 if (**iptr == ')') { /* no command given */
495 (*iptr)++;
496 return (NULL);
499 cmd = xmalloc(strlen(*iptr) + 1);
500 len = 0;
502 lastesc = 0;
503 for (; **iptr != '\0'; (*iptr)++) {
504 if (!lastesc && **iptr == ')')
505 break; /* unescaped ) is the end */
506 if (!lastesc && **iptr == '\\') {
507 lastesc = 1;
508 continue; /* skip \ if not escaped */
510 lastesc = 0;
511 cmd[len++] = **iptr;
513 if (**iptr == '\0') /* no terminating ) */ {
514 xfree(cmd);
515 return (NULL);
517 (*iptr)++; /* skip final ) */
518 cmd[len] = '\0';
520 job = job_get(&c->status_jobs, cmd);
521 if (job == NULL) {
522 job = job_add(&c->status_jobs,
523 JOB_PERSIST, c, cmd, status_job_callback, xfree, NULL);
524 job_run(job);
526 xfree(cmd);
527 if (job->data == NULL)
528 return (xstrdup(""));
529 return (xstrdup(job->data));
532 /* Job has finished: save its result. */
533 void
534 status_job_callback(struct job *job)
536 char *line, *buf;
537 size_t len;
539 buf = NULL;
540 if ((line = evbuffer_readline(job->event->input)) == NULL) {
541 len = EVBUFFER_LENGTH(job->event->input);
542 buf = xmalloc(len + 1);
543 if (len != 0)
544 memcpy(buf, EVBUFFER_DATA(job->event->input), len);
545 buf[len] = '\0';
548 if (job->data != NULL)
549 xfree(job->data);
550 else
551 server_redraw_client(job->client);
553 if (line == NULL)
554 job->data = buf;
555 else
556 job->data = xstrdup(line);
559 /* Return winlink status line entry and adjust gc as necessary. */
560 char *
561 status_print(
562 struct client *c, struct winlink *wl, time_t t, struct grid_cell *gc)
564 struct options *oo = &wl->window->options;
565 struct session *s = c->session;
566 const char *fmt;
567 char *text;
568 u_char fg, bg, attr;
570 fg = options_get_number(oo, "window-status-fg");
571 if (fg != 8)
572 colour_set_fg(gc, fg);
573 bg = options_get_number(oo, "window-status-bg");
574 if (bg != 8)
575 colour_set_bg(gc, bg);
576 attr = options_get_number(oo, "window-status-attr");
577 if (attr != 0)
578 gc->attr = attr;
579 fmt = options_get_string(oo, "window-status-format");
580 if (wl == s->curw) {
581 fg = options_get_number(oo, "window-status-current-fg");
582 if (fg != 8)
583 colour_set_fg(gc, fg);
584 bg = options_get_number(oo, "window-status-current-bg");
585 if (bg != 8)
586 colour_set_bg(gc, bg);
587 attr = options_get_number(oo, "window-status-current-attr");
588 if (attr != 0)
589 gc->attr = attr;
590 fmt = options_get_string(oo, "window-status-current-format");
593 if (wl->flags & WINLINK_ALERTFLAGS) {
594 fg = options_get_number(oo, "window-status-alert-fg");
595 if (fg != 8)
596 colour_set_fg(gc, fg);
597 bg = options_get_number(oo, "window-status-alert-bg");
598 if (bg != 8)
599 colour_set_bg(gc, bg);
600 attr = options_get_number(oo, "window-status-alert-attr");
601 if (attr != 0)
602 gc->attr = attr;
605 text = status_replace(c, wl, fmt, t, 1);
606 return (text);
609 /* Set a status line message. */
610 void printflike2
611 status_message_set(struct client *c, const char *fmt, ...)
613 struct timeval tv;
614 struct session *s = c->session;
615 struct message_entry *msg;
616 va_list ap;
617 int delay;
618 u_int i, limit;
620 status_prompt_clear(c);
621 status_message_clear(c);
623 va_start(ap, fmt);
624 xvasprintf(&c->message_string, fmt, ap);
625 va_end(ap);
627 ARRAY_EXPAND(&c->message_log, 1);
628 msg = &ARRAY_LAST(&c->message_log);
629 msg->msg_time = time(NULL);
630 msg->msg = xstrdup(c->message_string);
632 if (s == NULL)
633 limit = 0;
634 else
635 limit = options_get_number(&s->options, "message-limit");
636 if (ARRAY_LENGTH(&c->message_log) > limit) {
637 limit = ARRAY_LENGTH(&c->message_log) - limit;
638 for (i = 0; i < limit; i++) {
639 msg = &ARRAY_FIRST(&c->message_log);
640 xfree(msg->msg);
641 ARRAY_REMOVE(&c->message_log, 0);
645 delay = options_get_number(&c->session->options, "display-time");
646 tv.tv_sec = delay / 1000;
647 tv.tv_usec = (delay % 1000) * 1000L;
649 evtimer_del(&c->message_timer);
650 evtimer_set(&c->message_timer, status_message_callback, c);
651 evtimer_add(&c->message_timer, &tv);
653 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
654 c->flags |= CLIENT_STATUS;
657 /* Clear status line message. */
658 void
659 status_message_clear(struct client *c)
661 if (c->message_string == NULL)
662 return;
664 xfree(c->message_string);
665 c->message_string = NULL;
667 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
668 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
670 screen_reinit(&c->status);
673 /* Clear status line message after timer expires. */
674 /* ARGSUSED */
675 void
676 status_message_callback(unused int fd, unused short event, void *data)
678 struct client *c = data;
680 status_message_clear(c);
683 /* Draw client message on status line of present else on last line. */
685 status_message_redraw(struct client *c)
687 struct screen_write_ctx ctx;
688 struct session *s = c->session;
689 struct screen old_status;
690 size_t len;
691 struct grid_cell gc;
692 int utf8flag;
694 if (c->tty.sx == 0 || c->tty.sy == 0)
695 return (0);
696 memcpy(&old_status, &c->status, sizeof old_status);
697 screen_init(&c->status, c->tty.sx, 1, 0);
699 utf8flag = options_get_number(&s->options, "status-utf8");
701 len = screen_write_strlen(utf8flag, "%s", c->message_string);
702 if (len > c->tty.sx)
703 len = c->tty.sx;
705 memcpy(&gc, &grid_default_cell, sizeof gc);
706 colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
707 colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
708 gc.attr |= options_get_number(&s->options, "message-attr");
710 screen_write_start(&ctx, NULL, &c->status);
712 screen_write_cursormove(&ctx, 0, 0);
713 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->message_string);
714 for (; len < c->tty.sx; len++)
715 screen_write_putc(&ctx, &gc, ' ');
717 screen_write_stop(&ctx);
719 if (grid_compare(c->status.grid, old_status.grid) == 0) {
720 screen_free(&old_status);
721 return (0);
723 screen_free(&old_status);
724 return (1);
727 /* Enable status line prompt. */
728 void
729 status_prompt_set(struct client *c, const char *msg,
730 int (*callbackfn)(void *, const char *), void (*freefn)(void *),
731 void *data, int flags)
733 int keys;
735 status_message_clear(c);
736 status_prompt_clear(c);
738 c->prompt_string = xstrdup(msg);
740 c->prompt_buffer = xstrdup("");
741 c->prompt_index = 0;
743 c->prompt_callbackfn = callbackfn;
744 c->prompt_freefn = freefn;
745 c->prompt_data = data;
747 c->prompt_hindex = 0;
749 c->prompt_flags = flags;
751 keys = options_get_number(&c->session->options, "status-keys");
752 if (keys == MODEKEY_EMACS)
753 mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit);
754 else
755 mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit);
757 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
758 c->flags |= CLIENT_STATUS;
761 /* Remove status line prompt. */
762 void
763 status_prompt_clear(struct client *c)
765 if (c->prompt_string == NULL)
766 return;
768 if (c->prompt_freefn != NULL && c->prompt_data != NULL)
769 c->prompt_freefn(c->prompt_data);
771 xfree(c->prompt_string);
772 c->prompt_string = NULL;
774 xfree(c->prompt_buffer);
775 c->prompt_buffer = NULL;
777 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
778 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
780 screen_reinit(&c->status);
783 /* Update status line prompt with a new prompt string. */
784 void
785 status_prompt_update(struct client *c, const char *msg)
787 xfree(c->prompt_string);
788 c->prompt_string = xstrdup(msg);
790 *c->prompt_buffer = '\0';
791 c->prompt_index = 0;
793 c->prompt_hindex = 0;
795 c->flags |= CLIENT_STATUS;
798 /* Draw client prompt on status line of present else on last line. */
800 status_prompt_redraw(struct client *c)
802 struct screen_write_ctx ctx;
803 struct session *s = c->session;
804 struct screen old_status;
805 size_t i, size, left, len, off;
806 struct grid_cell gc, *gcp;
807 int utf8flag;
809 if (c->tty.sx == 0 || c->tty.sy == 0)
810 return (0);
811 memcpy(&old_status, &c->status, sizeof old_status);
812 screen_init(&c->status, c->tty.sx, 1, 0);
814 utf8flag = options_get_number(&s->options, "status-utf8");
816 len = screen_write_strlen(utf8flag, "%s", c->prompt_string);
817 if (len > c->tty.sx)
818 len = c->tty.sx;
819 off = 0;
821 memcpy(&gc, &grid_default_cell, sizeof gc);
822 colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
823 colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
824 gc.attr |= options_get_number(&s->options, "message-attr");
826 screen_write_start(&ctx, NULL, &c->status);
828 screen_write_cursormove(&ctx, 0, 0);
829 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->prompt_string);
831 left = c->tty.sx - len;
832 if (left != 0) {
833 size = screen_write_strlen(utf8flag, "%s", c->prompt_buffer);
834 if (c->prompt_index >= left) {
835 off = c->prompt_index - left + 1;
836 if (c->prompt_index == size)
837 left--;
838 size = left;
840 screen_write_nputs(
841 &ctx, left, &gc, utf8flag, "%s", c->prompt_buffer + off);
843 for (i = len + size; i < c->tty.sx; i++)
844 screen_write_putc(&ctx, &gc, ' ');
847 screen_write_stop(&ctx);
849 /* Apply fake cursor. */
850 off = len + c->prompt_index - off;
851 gcp = grid_view_get_cell(c->status.grid, off, 0);
852 gcp->attr ^= GRID_ATTR_REVERSE;
854 if (grid_compare(c->status.grid, old_status.grid) == 0) {
855 screen_free(&old_status);
856 return (0);
858 screen_free(&old_status);
859 return (1);
862 /* Handle keys in prompt. */
863 void
864 status_prompt_key(struct client *c, int key)
866 struct paste_buffer *pb;
867 char *s, *first, *last, word[64], swapc;
868 const char *histstr;
869 u_char ch;
870 size_t size, n, off, idx;
872 size = strlen(c->prompt_buffer);
873 switch (mode_key_lookup(&c->prompt_mdata, key)) {
874 case MODEKEYEDIT_CURSORLEFT:
875 if (c->prompt_index > 0) {
876 c->prompt_index--;
877 c->flags |= CLIENT_STATUS;
879 break;
880 case MODEKEYEDIT_SWITCHMODEAPPEND:
881 case MODEKEYEDIT_CURSORRIGHT:
882 if (c->prompt_index < size) {
883 c->prompt_index++;
884 c->flags |= CLIENT_STATUS;
886 break;
887 case MODEKEYEDIT_STARTOFLINE:
888 if (c->prompt_index != 0) {
889 c->prompt_index = 0;
890 c->flags |= CLIENT_STATUS;
892 break;
893 case MODEKEYEDIT_ENDOFLINE:
894 if (c->prompt_index != size) {
895 c->prompt_index = size;
896 c->flags |= CLIENT_STATUS;
898 break;
899 case MODEKEYEDIT_COMPLETE:
900 if (*c->prompt_buffer == '\0')
901 break;
903 idx = c->prompt_index;
904 if (idx != 0)
905 idx--;
907 /* Find the word we are in. */
908 first = c->prompt_buffer + idx;
909 while (first > c->prompt_buffer && *first != ' ')
910 first--;
911 while (*first == ' ')
912 first++;
913 last = c->prompt_buffer + idx;
914 while (*last != '\0' && *last != ' ')
915 last++;
916 while (*last == ' ')
917 last--;
918 if (*last != '\0')
919 last++;
920 if (last <= first ||
921 ((size_t) (last - first)) > (sizeof word) - 1)
922 break;
923 memcpy(word, first, last - first);
924 word[last - first] = '\0';
926 /* And try to complete it. */
927 if ((s = status_prompt_complete(word)) == NULL)
928 break;
930 /* Trim out word. */
931 n = size - (last - c->prompt_buffer) + 1; /* with \0 */
932 memmove(first, last, n);
933 size -= last - first;
935 /* Insert the new word. */
936 size += strlen(s);
937 off = first - c->prompt_buffer;
938 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1);
939 first = c->prompt_buffer + off;
940 memmove(first + strlen(s), first, n);
941 memcpy(first, s, strlen(s));
943 c->prompt_index = (first - c->prompt_buffer) + strlen(s);
944 xfree(s);
946 c->flags |= CLIENT_STATUS;
947 break;
948 case MODEKEYEDIT_BACKSPACE:
949 if (c->prompt_index != 0) {
950 if (c->prompt_index == size)
951 c->prompt_buffer[--c->prompt_index] = '\0';
952 else {
953 memmove(c->prompt_buffer + c->prompt_index - 1,
954 c->prompt_buffer + c->prompt_index,
955 size + 1 - c->prompt_index);
956 c->prompt_index--;
958 c->flags |= CLIENT_STATUS;
960 break;
961 case MODEKEYEDIT_DELETE:
962 if (c->prompt_index != size) {
963 memmove(c->prompt_buffer + c->prompt_index,
964 c->prompt_buffer + c->prompt_index + 1,
965 size + 1 - c->prompt_index);
966 c->flags |= CLIENT_STATUS;
968 break;
969 case MODEKEYEDIT_DELETELINE:
970 *c->prompt_buffer = '\0';
971 c->prompt_index = 0;
972 c->flags |= CLIENT_STATUS;
973 break;
974 case MODEKEYEDIT_DELETETOENDOFLINE:
975 if (c->prompt_index < size) {
976 c->prompt_buffer[c->prompt_index] = '\0';
977 c->flags |= CLIENT_STATUS;
979 break;
980 case MODEKEYEDIT_HISTORYUP:
981 histstr = status_prompt_up_history(&c->prompt_hindex);
982 if (histstr == NULL)
983 break;
984 xfree(c->prompt_buffer);
985 c->prompt_buffer = xstrdup(histstr);
986 c->prompt_index = strlen(c->prompt_buffer);
987 c->flags |= CLIENT_STATUS;
988 break;
989 case MODEKEYEDIT_HISTORYDOWN:
990 histstr = status_prompt_down_history(&c->prompt_hindex);
991 if (histstr == NULL)
992 break;
993 xfree(c->prompt_buffer);
994 c->prompt_buffer = xstrdup(histstr);
995 c->prompt_index = strlen(c->prompt_buffer);
996 c->flags |= CLIENT_STATUS;
997 break;
998 case MODEKEYEDIT_PASTE:
999 if ((pb = paste_get_top(&c->session->buffers)) == NULL)
1000 break;
1001 for (n = 0; n < pb->size; n++) {
1002 ch = (u_char) pb->data[n];
1003 if (ch < 32 || ch == 127)
1004 break;
1007 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1);
1008 if (c->prompt_index == size) {
1009 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1010 c->prompt_index += n;
1011 c->prompt_buffer[c->prompt_index] = '\0';
1012 } else {
1013 memmove(c->prompt_buffer + c->prompt_index + n,
1014 c->prompt_buffer + c->prompt_index,
1015 size + 1 - c->prompt_index);
1016 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1017 c->prompt_index += n;
1020 c->flags |= CLIENT_STATUS;
1021 break;
1022 case MODEKEYEDIT_TRANSPOSECHARS:
1023 idx = c->prompt_index;
1024 if (idx < size)
1025 idx++;
1026 if (idx >= 2) {
1027 swapc = c->prompt_buffer[idx - 2];
1028 c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1];
1029 c->prompt_buffer[idx - 1] = swapc;
1030 c->prompt_index = idx;
1031 c->flags |= CLIENT_STATUS;
1033 break;
1034 case MODEKEYEDIT_ENTER:
1035 if (*c->prompt_buffer != '\0')
1036 status_prompt_add_history(c->prompt_buffer);
1037 if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0)
1038 status_prompt_clear(c);
1039 break;
1040 case MODEKEYEDIT_CANCEL:
1041 if (c->prompt_callbackfn(c->prompt_data, NULL) == 0)
1042 status_prompt_clear(c);
1043 break;
1044 case MODEKEY_OTHER:
1045 if ((key & 0xff00) != 0 || key < 32 || key == 127)
1046 break;
1047 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2);
1049 if (c->prompt_index == size) {
1050 c->prompt_buffer[c->prompt_index++] = key;
1051 c->prompt_buffer[c->prompt_index] = '\0';
1052 } else {
1053 memmove(c->prompt_buffer + c->prompt_index + 1,
1054 c->prompt_buffer + c->prompt_index,
1055 size + 1 - c->prompt_index);
1056 c->prompt_buffer[c->prompt_index++] = key;
1059 if (c->prompt_flags & PROMPT_SINGLE) {
1060 if (c->prompt_callbackfn(
1061 c->prompt_data, c->prompt_buffer) == 0)
1062 status_prompt_clear(c);
1065 c->flags |= CLIENT_STATUS;
1066 break;
1067 default:
1068 break;
1072 /* Get previous line from the history. */
1073 const char *
1074 status_prompt_up_history(u_int *idx)
1076 u_int size;
1079 * History runs from 0 to size - 1.
1081 * Index is from 0 to size. Zero is empty.
1084 size = ARRAY_LENGTH(&status_prompt_history);
1085 if (size == 0 || *idx == size)
1086 return (NULL);
1087 (*idx)++;
1088 return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1091 /* Get next line from the history. */
1092 const char *
1093 status_prompt_down_history(u_int *idx)
1095 u_int size;
1097 size = ARRAY_LENGTH(&status_prompt_history);
1098 if (size == 0 || *idx == 0)
1099 return ("");
1100 (*idx)--;
1101 if (*idx == 0)
1102 return ("");
1103 return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1106 /* Add line to the history. */
1107 void
1108 status_prompt_add_history(const char *line)
1110 u_int size;
1112 size = ARRAY_LENGTH(&status_prompt_history);
1113 if (size > 0 && strcmp(ARRAY_LAST(&status_prompt_history), line) == 0)
1114 return;
1116 if (size == PROMPT_HISTORY) {
1117 xfree(ARRAY_FIRST(&status_prompt_history));
1118 ARRAY_REMOVE(&status_prompt_history, 0);
1121 ARRAY_ADD(&status_prompt_history, xstrdup(line));
1124 /* Complete word. */
1125 char *
1126 status_prompt_complete(const char *s)
1128 const struct cmd_entry **cmdent;
1129 const struct set_option_entry *entry;
1130 ARRAY_DECL(, const char *) list;
1131 char *prefix, *s2;
1132 u_int i;
1133 size_t j;
1135 if (*s == '\0')
1136 return (NULL);
1138 /* First, build a list of all the possible matches. */
1139 ARRAY_INIT(&list);
1140 for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1141 if (strncmp((*cmdent)->name, s, strlen(s)) == 0)
1142 ARRAY_ADD(&list, (*cmdent)->name);
1144 for (entry = set_option_table; entry->name != NULL; entry++) {
1145 if (strncmp(entry->name, s, strlen(s)) == 0)
1146 ARRAY_ADD(&list, entry->name);
1148 for (entry = set_session_option_table; entry->name != NULL; entry++) {
1149 if (strncmp(entry->name, s, strlen(s)) == 0)
1150 ARRAY_ADD(&list, entry->name);
1152 for (entry = set_window_option_table; entry->name != NULL; entry++) {
1153 if (strncmp(entry->name, s, strlen(s)) == 0)
1154 ARRAY_ADD(&list, entry->name);
1157 /* If none, bail now. */
1158 if (ARRAY_LENGTH(&list) == 0) {
1159 ARRAY_FREE(&list);
1160 return (NULL);
1163 /* If an exact match, return it, with a trailing space. */
1164 if (ARRAY_LENGTH(&list) == 1) {
1165 xasprintf(&s2, "%s ", ARRAY_FIRST(&list));
1166 ARRAY_FREE(&list);
1167 return (s2);
1170 /* Now loop through the list and find the longest common prefix. */
1171 prefix = xstrdup(ARRAY_FIRST(&list));
1172 for (i = 1; i < ARRAY_LENGTH(&list); i++) {
1173 s = ARRAY_ITEM(&list, i);
1175 j = strlen(s);
1176 if (j > strlen(prefix))
1177 j = strlen(prefix);
1178 for (; j > 0; j--) {
1179 if (prefix[j - 1] != s[j - 1])
1180 prefix[j - 1] = '\0';
1184 ARRAY_FREE(&list);
1185 return (prefix);