Use TMPDIR if set, from Han Boetes.
[tmux-openbsd.git] / status.c
blobc23f9ac3ac0eaf6791c8ae8d7423844c49c96983
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 ptr = window_printable_flags(s, wl);
397 freeptr = ptr;
398 goto do_replace;
399 case '[':
401 * Embedded style, handled at display time. Leave present and
402 * skip input until ].
404 ch = ']';
405 goto skip_to;
406 case '#':
407 *(*optr)++ = '#';
408 break;
411 return;
413 do_replace:
414 ptrlen = strlen(ptr);
415 if ((size_t) limit < ptrlen)
416 ptrlen = limit;
418 if (*optr + ptrlen >= out + outsize - 1)
419 return;
420 while (ptrlen > 0 && *ptr != '\0') {
421 *(*optr)++ = *ptr++;
422 ptrlen--;
425 if (freeptr != NULL)
426 xfree(freeptr);
427 return;
429 skip_to:
430 *(*optr)++ = '#';
432 (*iptr)--; /* include ch */
433 while (**iptr != ch && **iptr != '\0') {
434 if (*optr >= out + outsize - 1)
435 break;
436 *(*optr)++ = *(*iptr)++;
440 /* Replace special sequences in fmt. */
441 char *
442 status_replace(struct client *c,
443 struct winlink *wl, const char *fmt, time_t t, int jobsflag)
445 static char out[BUFSIZ];
446 char in[BUFSIZ], ch, *iptr, *optr;
448 strftime(in, sizeof in, fmt, localtime(&t));
449 in[(sizeof in) - 1] = '\0';
451 iptr = in;
452 optr = out;
454 while (*iptr != '\0') {
455 if (optr >= out + (sizeof out) - 1)
456 break;
457 ch = *iptr++;
459 if (ch != '#' || *iptr == '\0') {
460 *optr++ = ch;
461 continue;
463 status_replace1(c, wl, &iptr, &optr, out, sizeof out, jobsflag);
465 *optr = '\0';
467 return (xstrdup(out));
470 /* Figure out job name and get its result, starting it off if necessary. */
471 char *
472 status_job(struct client *c, char **iptr)
474 struct job *job;
475 char *cmd;
476 int lastesc;
477 size_t len;
479 if (**iptr == '\0')
480 return (NULL);
481 if (**iptr == ')') { /* no command given */
482 (*iptr)++;
483 return (NULL);
486 cmd = xmalloc(strlen(*iptr) + 1);
487 len = 0;
489 lastesc = 0;
490 for (; **iptr != '\0'; (*iptr)++) {
491 if (!lastesc && **iptr == ')')
492 break; /* unescaped ) is the end */
493 if (!lastesc && **iptr == '\\') {
494 lastesc = 1;
495 continue; /* skip \ if not escaped */
497 lastesc = 0;
498 cmd[len++] = **iptr;
500 if (**iptr == '\0') /* no terminating ) */ {
501 xfree(cmd);
502 return (NULL);
504 (*iptr)++; /* skip final ) */
505 cmd[len] = '\0';
507 job = job_get(&c->status_jobs, cmd);
508 if (job == NULL) {
509 job = job_add(&c->status_jobs,
510 JOB_PERSIST, c, cmd, status_job_callback, xfree, NULL);
511 job_run(job);
513 xfree(cmd);
514 if (job->data == NULL)
515 return (xstrdup(""));
516 return (xstrdup(job->data));
519 /* Job has finished: save its result. */
520 void
521 status_job_callback(struct job *job)
523 char *line, *buf;
524 size_t len;
526 buf = NULL;
527 if ((line = evbuffer_readline(job->event->input)) == NULL) {
528 len = EVBUFFER_LENGTH(job->event->input);
529 buf = xmalloc(len + 1);
530 if (len != 0)
531 memcpy(buf, EVBUFFER_DATA(job->event->input), len);
532 buf[len] = '\0';
535 if (job->data != NULL)
536 xfree(job->data);
537 else
538 server_redraw_client(job->client);
540 if (line == NULL)
541 job->data = buf;
542 else
543 job->data = xstrdup(line);
546 /* Return winlink status line entry and adjust gc as necessary. */
547 char *
548 status_print(
549 struct client *c, struct winlink *wl, time_t t, struct grid_cell *gc)
551 struct options *oo = &wl->window->options;
552 struct session *s = c->session;
553 const char *fmt;
554 char *text;
555 u_char fg, bg, attr;
557 fg = options_get_number(oo, "window-status-fg");
558 if (fg != 8)
559 colour_set_fg(gc, fg);
560 bg = options_get_number(oo, "window-status-bg");
561 if (bg != 8)
562 colour_set_bg(gc, bg);
563 attr = options_get_number(oo, "window-status-attr");
564 if (attr != 0)
565 gc->attr = attr;
566 fmt = options_get_string(oo, "window-status-format");
567 if (wl == s->curw) {
568 fg = options_get_number(oo, "window-status-current-fg");
569 if (fg != 8)
570 colour_set_fg(gc, fg);
571 bg = options_get_number(oo, "window-status-current-bg");
572 if (bg != 8)
573 colour_set_bg(gc, bg);
574 attr = options_get_number(oo, "window-status-current-attr");
575 if (attr != 0)
576 gc->attr = attr;
577 fmt = options_get_string(oo, "window-status-current-format");
580 if (wl->flags & WINLINK_ALERTFLAGS) {
581 fg = options_get_number(oo, "window-status-alert-fg");
582 if (fg != 8)
583 colour_set_fg(gc, fg);
584 bg = options_get_number(oo, "window-status-alert-bg");
585 if (bg != 8)
586 colour_set_bg(gc, bg);
587 attr = options_get_number(oo, "window-status-alert-attr");
588 if (attr != 0)
589 gc->attr = attr;
592 text = status_replace(c, wl, fmt, t, 1);
593 return (text);
596 /* Set a status line message. */
597 void printflike2
598 status_message_set(struct client *c, const char *fmt, ...)
600 struct timeval tv;
601 struct session *s = c->session;
602 struct message_entry *msg;
603 va_list ap;
604 int delay;
605 u_int i, limit;
607 status_prompt_clear(c);
608 status_message_clear(c);
610 va_start(ap, fmt);
611 xvasprintf(&c->message_string, fmt, ap);
612 va_end(ap);
614 ARRAY_EXPAND(&c->message_log, 1);
615 msg = &ARRAY_LAST(&c->message_log);
616 msg->msg_time = time(NULL);
617 msg->msg = xstrdup(c->message_string);
619 if (s == NULL)
620 limit = 0;
621 else
622 limit = options_get_number(&s->options, "message-limit");
623 if (ARRAY_LENGTH(&c->message_log) > limit) {
624 limit = ARRAY_LENGTH(&c->message_log) - limit;
625 for (i = 0; i < limit; i++) {
626 msg = &ARRAY_FIRST(&c->message_log);
627 xfree(msg->msg);
628 ARRAY_REMOVE(&c->message_log, 0);
632 delay = options_get_number(&c->session->options, "display-time");
633 tv.tv_sec = delay / 1000;
634 tv.tv_usec = (delay % 1000) * 1000L;
636 evtimer_del(&c->message_timer);
637 evtimer_set(&c->message_timer, status_message_callback, c);
638 evtimer_add(&c->message_timer, &tv);
640 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
641 c->flags |= CLIENT_STATUS;
644 /* Clear status line message. */
645 void
646 status_message_clear(struct client *c)
648 if (c->message_string == NULL)
649 return;
651 xfree(c->message_string);
652 c->message_string = NULL;
654 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
655 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
657 screen_reinit(&c->status);
660 /* Clear status line message after timer expires. */
661 /* ARGSUSED */
662 void
663 status_message_callback(unused int fd, unused short event, void *data)
665 struct client *c = data;
667 status_message_clear(c);
670 /* Draw client message on status line of present else on last line. */
672 status_message_redraw(struct client *c)
674 struct screen_write_ctx ctx;
675 struct session *s = c->session;
676 struct screen old_status;
677 size_t len;
678 struct grid_cell gc;
679 int utf8flag;
681 if (c->tty.sx == 0 || c->tty.sy == 0)
682 return (0);
683 memcpy(&old_status, &c->status, sizeof old_status);
684 screen_init(&c->status, c->tty.sx, 1, 0);
686 utf8flag = options_get_number(&s->options, "status-utf8");
688 len = screen_write_strlen(utf8flag, "%s", c->message_string);
689 if (len > c->tty.sx)
690 len = c->tty.sx;
692 memcpy(&gc, &grid_default_cell, sizeof gc);
693 colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
694 colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
695 gc.attr |= options_get_number(&s->options, "message-attr");
697 screen_write_start(&ctx, NULL, &c->status);
699 screen_write_cursormove(&ctx, 0, 0);
700 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->message_string);
701 for (; len < c->tty.sx; len++)
702 screen_write_putc(&ctx, &gc, ' ');
704 screen_write_stop(&ctx);
706 if (grid_compare(c->status.grid, old_status.grid) == 0) {
707 screen_free(&old_status);
708 return (0);
710 screen_free(&old_status);
711 return (1);
714 /* Enable status line prompt. */
715 void
716 status_prompt_set(struct client *c, const char *msg,
717 int (*callbackfn)(void *, const char *), void (*freefn)(void *),
718 void *data, int flags)
720 int keys;
722 status_message_clear(c);
723 status_prompt_clear(c);
725 c->prompt_string = xstrdup(msg);
727 c->prompt_buffer = xstrdup("");
728 c->prompt_index = 0;
730 c->prompt_callbackfn = callbackfn;
731 c->prompt_freefn = freefn;
732 c->prompt_data = data;
734 c->prompt_hindex = 0;
736 c->prompt_flags = flags;
738 keys = options_get_number(&c->session->options, "status-keys");
739 if (keys == MODEKEY_EMACS)
740 mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit);
741 else
742 mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit);
744 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
745 c->flags |= CLIENT_STATUS;
748 /* Remove status line prompt. */
749 void
750 status_prompt_clear(struct client *c)
752 if (c->prompt_string == NULL)
753 return;
755 if (c->prompt_freefn != NULL && c->prompt_data != NULL)
756 c->prompt_freefn(c->prompt_data);
758 xfree(c->prompt_string);
759 c->prompt_string = NULL;
761 xfree(c->prompt_buffer);
762 c->prompt_buffer = NULL;
764 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
765 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
767 screen_reinit(&c->status);
770 /* Update status line prompt with a new prompt string. */
771 void
772 status_prompt_update(struct client *c, const char *msg)
774 xfree(c->prompt_string);
775 c->prompt_string = xstrdup(msg);
777 *c->prompt_buffer = '\0';
778 c->prompt_index = 0;
780 c->prompt_hindex = 0;
782 c->flags |= CLIENT_STATUS;
785 /* Draw client prompt on status line of present else on last line. */
787 status_prompt_redraw(struct client *c)
789 struct screen_write_ctx ctx;
790 struct session *s = c->session;
791 struct screen old_status;
792 size_t i, size, left, len, off;
793 struct grid_cell gc, *gcp;
794 int utf8flag;
796 if (c->tty.sx == 0 || c->tty.sy == 0)
797 return (0);
798 memcpy(&old_status, &c->status, sizeof old_status);
799 screen_init(&c->status, c->tty.sx, 1, 0);
801 utf8flag = options_get_number(&s->options, "status-utf8");
803 len = screen_write_strlen(utf8flag, "%s", c->prompt_string);
804 if (len > c->tty.sx)
805 len = c->tty.sx;
806 off = 0;
808 memcpy(&gc, &grid_default_cell, sizeof gc);
809 colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
810 colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
811 gc.attr |= options_get_number(&s->options, "message-attr");
813 screen_write_start(&ctx, NULL, &c->status);
815 screen_write_cursormove(&ctx, 0, 0);
816 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->prompt_string);
818 left = c->tty.sx - len;
819 if (left != 0) {
820 size = screen_write_strlen(utf8flag, "%s", c->prompt_buffer);
821 if (c->prompt_index >= left) {
822 off = c->prompt_index - left + 1;
823 if (c->prompt_index == size)
824 left--;
825 size = left;
827 screen_write_nputs(
828 &ctx, left, &gc, utf8flag, "%s", c->prompt_buffer + off);
830 for (i = len + size; i < c->tty.sx; i++)
831 screen_write_putc(&ctx, &gc, ' ');
834 screen_write_stop(&ctx);
836 /* Apply fake cursor. */
837 off = len + c->prompt_index - off;
838 gcp = grid_view_get_cell(c->status.grid, off, 0);
839 gcp->attr ^= GRID_ATTR_REVERSE;
841 if (grid_compare(c->status.grid, old_status.grid) == 0) {
842 screen_free(&old_status);
843 return (0);
845 screen_free(&old_status);
846 return (1);
849 /* Handle keys in prompt. */
850 void
851 status_prompt_key(struct client *c, int key)
853 struct paste_buffer *pb;
854 char *s, *first, *last, word[64], swapc;
855 const char *histstr;
856 u_char ch;
857 size_t size, n, off, idx;
859 size = strlen(c->prompt_buffer);
860 switch (mode_key_lookup(&c->prompt_mdata, key)) {
861 case MODEKEYEDIT_CURSORLEFT:
862 if (c->prompt_index > 0) {
863 c->prompt_index--;
864 c->flags |= CLIENT_STATUS;
866 break;
867 case MODEKEYEDIT_SWITCHMODEAPPEND:
868 case MODEKEYEDIT_CURSORRIGHT:
869 if (c->prompt_index < size) {
870 c->prompt_index++;
871 c->flags |= CLIENT_STATUS;
873 break;
874 case MODEKEYEDIT_STARTOFLINE:
875 if (c->prompt_index != 0) {
876 c->prompt_index = 0;
877 c->flags |= CLIENT_STATUS;
879 break;
880 case MODEKEYEDIT_ENDOFLINE:
881 if (c->prompt_index != size) {
882 c->prompt_index = size;
883 c->flags |= CLIENT_STATUS;
885 break;
886 case MODEKEYEDIT_COMPLETE:
887 if (*c->prompt_buffer == '\0')
888 break;
890 idx = c->prompt_index;
891 if (idx != 0)
892 idx--;
894 /* Find the word we are in. */
895 first = c->prompt_buffer + idx;
896 while (first > c->prompt_buffer && *first != ' ')
897 first--;
898 while (*first == ' ')
899 first++;
900 last = c->prompt_buffer + idx;
901 while (*last != '\0' && *last != ' ')
902 last++;
903 while (*last == ' ')
904 last--;
905 if (*last != '\0')
906 last++;
907 if (last <= first ||
908 ((size_t) (last - first)) > (sizeof word) - 1)
909 break;
910 memcpy(word, first, last - first);
911 word[last - first] = '\0';
913 /* And try to complete it. */
914 if ((s = status_prompt_complete(word)) == NULL)
915 break;
917 /* Trim out word. */
918 n = size - (last - c->prompt_buffer) + 1; /* with \0 */
919 memmove(first, last, n);
920 size -= last - first;
922 /* Insert the new word. */
923 size += strlen(s);
924 off = first - c->prompt_buffer;
925 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1);
926 first = c->prompt_buffer + off;
927 memmove(first + strlen(s), first, n);
928 memcpy(first, s, strlen(s));
930 c->prompt_index = (first - c->prompt_buffer) + strlen(s);
931 xfree(s);
933 c->flags |= CLIENT_STATUS;
934 break;
935 case MODEKEYEDIT_BACKSPACE:
936 if (c->prompt_index != 0) {
937 if (c->prompt_index == size)
938 c->prompt_buffer[--c->prompt_index] = '\0';
939 else {
940 memmove(c->prompt_buffer + c->prompt_index - 1,
941 c->prompt_buffer + c->prompt_index,
942 size + 1 - c->prompt_index);
943 c->prompt_index--;
945 c->flags |= CLIENT_STATUS;
947 break;
948 case MODEKEYEDIT_DELETE:
949 if (c->prompt_index != size) {
950 memmove(c->prompt_buffer + c->prompt_index,
951 c->prompt_buffer + c->prompt_index + 1,
952 size + 1 - c->prompt_index);
953 c->flags |= CLIENT_STATUS;
955 break;
956 case MODEKEYEDIT_DELETELINE:
957 *c->prompt_buffer = '\0';
958 c->prompt_index = 0;
959 c->flags |= CLIENT_STATUS;
960 break;
961 case MODEKEYEDIT_DELETETOENDOFLINE:
962 if (c->prompt_index < size) {
963 c->prompt_buffer[c->prompt_index] = '\0';
964 c->flags |= CLIENT_STATUS;
966 break;
967 case MODEKEYEDIT_HISTORYUP:
968 histstr = status_prompt_up_history(&c->prompt_hindex);
969 if (histstr == NULL)
970 break;
971 xfree(c->prompt_buffer);
972 c->prompt_buffer = xstrdup(histstr);
973 c->prompt_index = strlen(c->prompt_buffer);
974 c->flags |= CLIENT_STATUS;
975 break;
976 case MODEKEYEDIT_HISTORYDOWN:
977 histstr = status_prompt_down_history(&c->prompt_hindex);
978 if (histstr == NULL)
979 break;
980 xfree(c->prompt_buffer);
981 c->prompt_buffer = xstrdup(histstr);
982 c->prompt_index = strlen(c->prompt_buffer);
983 c->flags |= CLIENT_STATUS;
984 break;
985 case MODEKEYEDIT_PASTE:
986 if ((pb = paste_get_top(&global_buffers)) == NULL)
987 break;
988 for (n = 0; n < pb->size; n++) {
989 ch = (u_char) pb->data[n];
990 if (ch < 32 || ch == 127)
991 break;
994 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1);
995 if (c->prompt_index == size) {
996 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
997 c->prompt_index += n;
998 c->prompt_buffer[c->prompt_index] = '\0';
999 } else {
1000 memmove(c->prompt_buffer + c->prompt_index + n,
1001 c->prompt_buffer + c->prompt_index,
1002 size + 1 - c->prompt_index);
1003 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1004 c->prompt_index += n;
1007 c->flags |= CLIENT_STATUS;
1008 break;
1009 case MODEKEYEDIT_TRANSPOSECHARS:
1010 idx = c->prompt_index;
1011 if (idx < size)
1012 idx++;
1013 if (idx >= 2) {
1014 swapc = c->prompt_buffer[idx - 2];
1015 c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1];
1016 c->prompt_buffer[idx - 1] = swapc;
1017 c->prompt_index = idx;
1018 c->flags |= CLIENT_STATUS;
1020 break;
1021 case MODEKEYEDIT_ENTER:
1022 if (*c->prompt_buffer != '\0')
1023 status_prompt_add_history(c->prompt_buffer);
1024 if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0)
1025 status_prompt_clear(c);
1026 break;
1027 case MODEKEYEDIT_CANCEL:
1028 if (c->prompt_callbackfn(c->prompt_data, NULL) == 0)
1029 status_prompt_clear(c);
1030 break;
1031 case MODEKEY_OTHER:
1032 if ((key & 0xff00) != 0 || key < 32 || key == 127)
1033 break;
1034 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2);
1036 if (c->prompt_index == size) {
1037 c->prompt_buffer[c->prompt_index++] = key;
1038 c->prompt_buffer[c->prompt_index] = '\0';
1039 } else {
1040 memmove(c->prompt_buffer + c->prompt_index + 1,
1041 c->prompt_buffer + c->prompt_index,
1042 size + 1 - c->prompt_index);
1043 c->prompt_buffer[c->prompt_index++] = key;
1046 if (c->prompt_flags & PROMPT_SINGLE) {
1047 if (c->prompt_callbackfn(
1048 c->prompt_data, c->prompt_buffer) == 0)
1049 status_prompt_clear(c);
1052 c->flags |= CLIENT_STATUS;
1053 break;
1054 default:
1055 break;
1059 /* Get previous line from the history. */
1060 const char *
1061 status_prompt_up_history(u_int *idx)
1063 u_int size;
1066 * History runs from 0 to size - 1.
1068 * Index is from 0 to size. Zero is empty.
1071 size = ARRAY_LENGTH(&status_prompt_history);
1072 if (size == 0 || *idx == size)
1073 return (NULL);
1074 (*idx)++;
1075 return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1078 /* Get next line from the history. */
1079 const char *
1080 status_prompt_down_history(u_int *idx)
1082 u_int size;
1084 size = ARRAY_LENGTH(&status_prompt_history);
1085 if (size == 0 || *idx == 0)
1086 return ("");
1087 (*idx)--;
1088 if (*idx == 0)
1089 return ("");
1090 return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1093 /* Add line to the history. */
1094 void
1095 status_prompt_add_history(const char *line)
1097 u_int size;
1099 size = ARRAY_LENGTH(&status_prompt_history);
1100 if (size > 0 && strcmp(ARRAY_LAST(&status_prompt_history), line) == 0)
1101 return;
1103 if (size == PROMPT_HISTORY) {
1104 xfree(ARRAY_FIRST(&status_prompt_history));
1105 ARRAY_REMOVE(&status_prompt_history, 0);
1108 ARRAY_ADD(&status_prompt_history, xstrdup(line));
1111 /* Complete word. */
1112 char *
1113 status_prompt_complete(const char *s)
1115 const struct cmd_entry **cmdent;
1116 const struct options_table_entry *oe;
1117 ARRAY_DECL(, const char *) list;
1118 char *prefix, *s2;
1119 u_int i;
1120 size_t j;
1122 if (*s == '\0')
1123 return (NULL);
1125 /* First, build a list of all the possible matches. */
1126 ARRAY_INIT(&list);
1127 for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1128 if (strncmp((*cmdent)->name, s, strlen(s)) == 0)
1129 ARRAY_ADD(&list, (*cmdent)->name);
1131 for (oe = server_options_table; oe->name != NULL; oe++) {
1132 if (strncmp(oe->name, s, strlen(s)) == 0)
1133 ARRAY_ADD(&list, oe->name);
1135 for (oe = session_options_table; oe->name != NULL; oe++) {
1136 if (strncmp(oe->name, s, strlen(s)) == 0)
1137 ARRAY_ADD(&list, oe->name);
1139 for (oe = window_options_table; oe->name != NULL; oe++) {
1140 if (strncmp(oe->name, s, strlen(s)) == 0)
1141 ARRAY_ADD(&list, oe->name);
1144 /* If none, bail now. */
1145 if (ARRAY_LENGTH(&list) == 0) {
1146 ARRAY_FREE(&list);
1147 return (NULL);
1150 /* If an exact match, return it, with a trailing space. */
1151 if (ARRAY_LENGTH(&list) == 1) {
1152 xasprintf(&s2, "%s ", ARRAY_FIRST(&list));
1153 ARRAY_FREE(&list);
1154 return (s2);
1157 /* Now loop through the list and find the longest common prefix. */
1158 prefix = xstrdup(ARRAY_FIRST(&list));
1159 for (i = 1; i < ARRAY_LENGTH(&list); i++) {
1160 s = ARRAY_ITEM(&list, i);
1162 j = strlen(s);
1163 if (j > strlen(prefix))
1164 j = strlen(prefix);
1165 for (; j > 0; j--) {
1166 if (prefix[j - 1] != s[j - 1])
1167 prefix[j - 1] = '\0';
1171 ARRAY_FREE(&list);
1172 return (prefix);