Rename some imsg bits to make namespace collisions less likely buf to
[tmux-openbsd.git] / status.c
blob4399ae43931849a13ccf3f78e2b22111dab6595c
1 /* $OpenBSD$ */
3 /*
4 * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 #include <sys/types.h>
20 #include <sys/time.h>
22 #include <errno.h>
23 #include <limits.h>
24 #include <stdarg.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <time.h>
28 #include <unistd.h>
30 #include "tmux.h"
32 char *status_redraw_get_left(
33 struct client *, time_t, int, struct grid_cell *, size_t *);
34 char *status_redraw_get_right(
35 struct client *, time_t, int, struct grid_cell *, size_t *);
36 char *status_job(struct client *, char **);
37 void status_job_callback(struct job *);
38 char *status_print(
39 struct client *, struct winlink *, time_t, struct grid_cell *);
40 void status_replace1(struct client *,
41 struct winlink *, char **, char **, char *, size_t, int);
42 void status_message_callback(int, short, void *);
44 void status_prompt_add_history(struct client *);
45 char *status_prompt_complete(const char *);
47 /* Retrieve options for left string. */
48 char *
49 status_redraw_get_left(struct client *c,
50 time_t t, int utf8flag, struct grid_cell *gc, size_t *size)
52 struct session *s = c->session;
53 char *left;
54 u_char fg, bg, attr;
55 size_t leftlen;
57 fg = options_get_number(&s->options, "status-left-fg");
58 if (fg != 8)
59 colour_set_fg(gc, fg);
60 bg = options_get_number(&s->options, "status-left-bg");
61 if (bg != 8)
62 colour_set_bg(gc, bg);
63 attr = options_get_number(&s->options, "status-left-attr");
64 if (attr != 0)
65 gc->attr = attr;
67 left = status_replace(
68 c, NULL, options_get_string(&s->options, "status-left"), t, 1);
70 *size = options_get_number(&s->options, "status-left-length");
71 leftlen = screen_write_cstrlen(utf8flag, "%s", left);
72 if (leftlen < *size)
73 *size = leftlen;
74 return (left);
77 /* Retrieve options for right string. */
78 char *
79 status_redraw_get_right(struct client *c,
80 time_t t, int utf8flag, struct grid_cell *gc, size_t *size)
82 struct session *s = c->session;
83 char *right;
84 u_char fg, bg, attr;
85 size_t rightlen;
87 fg = options_get_number(&s->options, "status-right-fg");
88 if (fg != 8)
89 colour_set_fg(gc, fg);
90 bg = options_get_number(&s->options, "status-right-bg");
91 if (bg != 8)
92 colour_set_bg(gc, bg);
93 attr = options_get_number(&s->options, "status-right-attr");
94 if (attr != 0)
95 gc->attr = attr;
97 right = status_replace(
98 c, NULL, options_get_string(&s->options, "status-right"), t, 1);
100 *size = options_get_number(&s->options, "status-right-length");
101 rightlen = screen_write_cstrlen(utf8flag, "%s", right);
102 if (rightlen < *size)
103 *size = rightlen;
104 return (right);
107 /* Draw status for client on the last lines of given context. */
109 status_redraw(struct client *c)
111 struct screen_write_ctx ctx;
112 struct session *s = c->session;
113 struct winlink *wl;
114 struct screen old_status, window_list;
115 struct grid_cell stdgc, lgc, rgc, gc;
116 time_t t;
117 char *left, *right;
118 u_int offset, needed;
119 u_int wlstart, wlwidth, wlavailable, wloffset, wlsize;
120 size_t llen, rlen;
121 int larrow, rarrow, utf8flag;
123 /* No status line? */
124 if (c->tty.sy == 0 || !options_get_number(&s->options, "status"))
125 return (1);
126 left = right = NULL;
127 larrow = rarrow = 0;
129 /* Update status timer. */
130 if (gettimeofday(&c->status_timer, NULL) != 0)
131 fatal("gettimeofday failed");
132 t = c->status_timer.tv_sec;
134 /* Set up default colour. */
135 memcpy(&stdgc, &grid_default_cell, sizeof gc);
136 colour_set_fg(&stdgc, options_get_number(&s->options, "status-fg"));
137 colour_set_bg(&stdgc, options_get_number(&s->options, "status-bg"));
138 stdgc.attr |= options_get_number(&s->options, "status-attr");
140 /* Create the target screen. */
141 memcpy(&old_status, &c->status, sizeof old_status);
142 screen_init(&c->status, c->tty.sx, 1, 0);
143 screen_write_start(&ctx, NULL, &c->status);
144 for (offset = 0; offset < c->tty.sx; offset++)
145 screen_write_putc(&ctx, &stdgc, ' ');
146 screen_write_stop(&ctx);
148 /* If the height is one line, blank status line. */
149 if (c->tty.sy <= 1)
150 goto out;
152 /* Get UTF-8 flag. */
153 utf8flag = options_get_number(&s->options, "status-utf8");
155 /* Work out left and right strings. */
156 memcpy(&lgc, &stdgc, sizeof lgc);
157 left = status_redraw_get_left(c, t, utf8flag, &lgc, &llen);
158 memcpy(&rgc, &stdgc, sizeof rgc);
159 right = status_redraw_get_right(c, t, utf8flag, &rgc, &rlen);
162 * Figure out how much space we have for the window list. If there
163 * isn't enough space, just show a blank status line.
165 needed = 0;
166 if (llen != 0)
167 needed += llen + 1;
168 if (rlen != 0)
169 needed += rlen + 1;
170 if (c->tty.sx == 0 || c->tty.sx <= needed)
171 goto out;
172 wlavailable = c->tty.sx - needed;
174 /* Calculate the total size needed for the window list. */
175 wlstart = wloffset = wlwidth = 0;
176 RB_FOREACH(wl, winlinks, &s->windows) {
177 if (wl->status_text != NULL)
178 xfree(wl->status_text);
179 memcpy(&wl->status_cell, &stdgc, sizeof wl->status_cell);
180 wl->status_text = status_print(c, wl, t, &wl->status_cell);
181 wl->status_width =
182 screen_write_cstrlen(utf8flag, "%s", wl->status_text);
184 if (wl == s->curw)
185 wloffset = wlwidth;
186 wlwidth += wl->status_width + 1;
189 /* Create a new screen for the window list. */
190 screen_init(&window_list, wlwidth, 1, 0);
192 /* And draw the window list into it. */
193 screen_write_start(&ctx, NULL, &window_list);
194 RB_FOREACH(wl, winlinks, &s->windows) {
195 screen_write_cnputs(&ctx,
196 -1, &wl->status_cell, utf8flag, "%s", wl->status_text);
197 screen_write_putc(&ctx, &stdgc, ' ');
199 screen_write_stop(&ctx);
201 /* If there is enough space for the total width, skip to draw now. */
202 if (wlwidth <= wlavailable)
203 goto draw;
205 /* Find size of current window text. */
206 wlsize = s->curw->status_width;
209 * If the current window is already on screen, good to draw from the
210 * start and just leave off the end.
212 if (wloffset + wlsize < wlavailable) {
213 if (wlavailable > 0) {
214 rarrow = 1;
215 wlavailable--;
217 wlwidth = wlavailable;
218 } else {
220 * Work out how many characters we need to omit from the
221 * start. There are wlavailable characters to fill, and
222 * wloffset + wlsize must be the last. So, the start character
223 * is wloffset + wlsize - wlavailable.
225 if (wlavailable > 0) {
226 larrow = 1;
227 wlavailable--;
230 wlstart = wloffset + wlsize - wlavailable;
231 if (wlavailable > 0 && wlwidth > wlstart + wlavailable + 1) {
232 rarrow = 1;
233 wlstart++;
234 wlavailable--;
236 wlwidth = wlavailable;
239 /* Bail if anything is now too small too. */
240 if (wlwidth == 0 || wlavailable == 0) {
241 screen_free(&window_list);
242 goto out;
246 * Now the start position is known, work out the state of the left and
247 * right arrows.
249 offset = 0;
250 RB_FOREACH(wl, winlinks, &s->windows) {
251 if (larrow == 1 && offset < wlstart) {
252 if (session_alert_has(s, wl, WINDOW_ACTIVITY))
253 larrow = -1;
254 else if (session_alert_has(s, wl, WINDOW_BELL))
255 larrow = -1;
256 else if (session_alert_has(s, wl, WINDOW_CONTENT))
257 larrow = -1;
260 offset += wl->status_width;
262 if (rarrow == 1 && offset > wlstart + wlwidth) {
263 if (session_alert_has(s, wl, WINDOW_ACTIVITY))
264 rarrow = -1;
265 else if (session_alert_has(s, wl, WINDOW_BELL))
266 rarrow = -1;
267 else if (session_alert_has(s, wl, WINDOW_CONTENT))
268 rarrow = -1;
272 draw:
273 /* Begin drawing. */
274 screen_write_start(&ctx, NULL, &c->status);
276 /* Draw the left string and arrow. */
277 screen_write_cursormove(&ctx, 0, 0);
278 if (llen != 0) {
279 screen_write_cnputs(&ctx, llen, &lgc, utf8flag, "%s", left);
280 screen_write_putc(&ctx, &stdgc, ' ');
282 if (larrow != 0) {
283 memcpy(&gc, &stdgc, sizeof gc);
284 if (larrow == -1)
285 gc.attr ^= GRID_ATTR_REVERSE;
286 screen_write_putc(&ctx, &gc, '<');
289 /* Draw the right string and arrow. */
290 if (rarrow != 0) {
291 screen_write_cursormove(&ctx, c->tty.sx - rlen - 2, 0);
292 memcpy(&gc, &stdgc, sizeof gc);
293 if (rarrow == -1)
294 gc.attr ^= GRID_ATTR_REVERSE;
295 screen_write_putc(&ctx, &gc, '>');
296 } else
297 screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, 0);
298 if (rlen != 0) {
299 screen_write_putc(&ctx, &stdgc, ' ');
300 screen_write_cnputs(&ctx, rlen, &rgc, utf8flag, "%s", right);
303 /* Figure out the offset for the window list. */
304 if (llen != 0)
305 wloffset = llen + 1;
306 else
307 wloffset = 0;
308 if (wlwidth < wlavailable) {
309 switch (options_get_number(&s->options, "status-justify")) {
310 case 1: /* centered */
311 wloffset += (wlavailable - wlwidth) / 2;
312 break;
313 case 2: /* right */
314 wloffset += (wlavailable - wlwidth);
315 break;
318 if (larrow != 0)
319 wloffset++;
321 /* Copy the window list. */
322 screen_write_cursormove(&ctx, wloffset, 0);
323 screen_write_copy(&ctx, &window_list, wlstart, 0, wlwidth, 1);
324 screen_free(&window_list);
326 screen_write_stop(&ctx);
328 out:
329 if (left != NULL)
330 xfree(left);
331 if (right != NULL)
332 xfree(right);
334 if (grid_compare(c->status.grid, old_status.grid) == 0) {
335 screen_free(&old_status);
336 return (0);
338 screen_free(&old_status);
339 return (1);
342 /* Replace a single special sequence (prefixed by #). */
343 void
344 status_replace1(struct client *c,struct winlink *wl,
345 char **iptr, char **optr, char *out, size_t outsize, int jobsflag)
347 struct session *s = c->session;
348 char ch, tmp[256], *ptr, *endptr, *freeptr;
349 size_t ptrlen;
350 long limit;
352 if (wl == NULL)
353 wl = s->curw;
355 errno = 0;
356 limit = strtol(*iptr, &endptr, 10);
357 if ((limit == 0 && errno != EINVAL) ||
358 (limit == LONG_MIN && errno != ERANGE) ||
359 (limit == LONG_MAX && errno != ERANGE) ||
360 limit != 0)
361 *iptr = endptr;
362 if (limit <= 0)
363 limit = LONG_MAX;
365 freeptr = NULL;
367 switch (*(*iptr)++) {
368 case '(':
369 if (!jobsflag) {
370 ch = ')';
371 goto skip_to;
373 if ((ptr = status_job(c, iptr)) == NULL)
374 return;
375 freeptr = ptr;
376 goto do_replace;
377 case 'H':
378 if (gethostname(tmp, sizeof tmp) != 0)
379 fatal("gethostname failed");
380 ptr = tmp;
381 goto do_replace;
382 case 'I':
383 xsnprintf(tmp, sizeof tmp, "%d", wl->idx);
384 ptr = tmp;
385 goto do_replace;
386 case 'P':
387 xsnprintf(tmp, sizeof tmp, "%u",
388 window_pane_index(wl->window, wl->window->active));
389 ptr = tmp;
390 goto do_replace;
391 case 'S':
392 ptr = s->name;
393 goto do_replace;
394 case 'T':
395 ptr = wl->window->active->base.title;
396 goto do_replace;
397 case 'W':
398 ptr = wl->window->name;
399 goto do_replace;
400 case 'F':
401 tmp[0] = ' ';
402 if (session_alert_has(s, wl, WINDOW_CONTENT))
403 tmp[0] = '+';
404 else if (session_alert_has(s, wl, WINDOW_BELL))
405 tmp[0] = '!';
406 else if (session_alert_has(s, wl, WINDOW_ACTIVITY))
407 tmp[0] = '#';
408 else if (wl == s->curw)
409 tmp[0] = '*';
410 else if (wl == TAILQ_FIRST(&s->lastw))
411 tmp[0] = '-';
412 tmp[1] = '\0';
413 ptr = tmp;
414 goto do_replace;
415 case '[':
417 * Embedded style, handled at display time. Leave present and
418 * skip input until ].
420 ch = ']';
421 goto skip_to;
422 case '#':
423 *(*optr)++ = '#';
424 break;
427 return;
429 do_replace:
430 ptrlen = strlen(ptr);
431 if ((size_t) limit < ptrlen)
432 ptrlen = limit;
434 if (*optr + ptrlen >= out + outsize - 1)
435 return;
436 while (ptrlen > 0 && *ptr != '\0') {
437 *(*optr)++ = *ptr++;
438 ptrlen--;
441 if (freeptr != NULL)
442 xfree(freeptr);
443 return;
445 skip_to:
446 *(*optr)++ = '#';
448 (*iptr)--; /* include ch */
449 while (**iptr != ch && **iptr != '\0') {
450 if (*optr >= out + outsize - 1)
451 break;
452 *(*optr)++ = *(*iptr)++;
456 /* Replace special sequences in fmt. */
457 char *
458 status_replace(struct client *c,
459 struct winlink *wl, const char *fmt, time_t t, int jobsflag)
461 static char out[BUFSIZ];
462 char in[BUFSIZ], ch, *iptr, *optr;
464 strftime(in, sizeof in, fmt, localtime(&t));
465 in[(sizeof in) - 1] = '\0';
467 iptr = in;
468 optr = out;
470 while (*iptr != '\0') {
471 if (optr >= out + (sizeof out) - 1)
472 break;
473 ch = *iptr++;
475 if (ch != '#') {
476 *optr++ = ch;
477 continue;
479 status_replace1(c, wl, &iptr, &optr, out, sizeof out, jobsflag);
481 *optr = '\0';
483 return (xstrdup(out));
486 /* Figure out job name and get its result, starting it off if necessary. */
487 char *
488 status_job(struct client *c, char **iptr)
490 struct job *job;
491 char *cmd;
492 int lastesc;
493 size_t len;
495 if (**iptr == '\0')
496 return (NULL);
497 if (**iptr == ')') { /* no command given */
498 (*iptr)++;
499 return (NULL);
502 cmd = xmalloc(strlen(*iptr) + 1);
503 len = 0;
505 lastesc = 0;
506 for (; **iptr != '\0'; (*iptr)++) {
507 if (!lastesc && **iptr == ')')
508 break; /* unescaped ) is the end */
509 if (!lastesc && **iptr == '\\') {
510 lastesc = 1;
511 continue; /* skip \ if not escaped */
513 lastesc = 0;
514 cmd[len++] = **iptr;
516 if (**iptr == '\0') /* no terminating ) */ {
517 xfree(cmd);
518 return (NULL);
520 (*iptr)++; /* skip final ) */
521 cmd[len] = '\0';
523 job = job_get(&c->status_jobs, cmd);
524 if (job == NULL) {
525 job = job_add(&c->status_jobs,
526 JOB_PERSIST, c, cmd, status_job_callback, xfree, NULL);
527 job_run(job);
529 xfree(cmd);
530 if (job->data == NULL)
531 return (xstrdup(""));
532 return (xstrdup(job->data));
535 /* Job has finished: save its result. */
536 void
537 status_job_callback(struct job *job)
539 char *line, *buf;
540 size_t len;
542 buf = NULL;
543 if ((line = evbuffer_readline(job->event->input)) == NULL) {
544 len = EVBUFFER_LENGTH(job->event->input);
545 buf = xmalloc(len + 1);
546 if (len != 0)
547 memcpy(buf, EVBUFFER_DATA(job->event->input), len);
548 buf[len] = '\0';
551 if (job->data != NULL)
552 xfree(job->data);
553 else
554 server_redraw_client(job->client);
556 if (line == NULL)
557 job->data = buf;
558 else
559 job->data = xstrdup(line);
562 /* Return winlink status line entry and adjust gc as necessary. */
563 char *
564 status_print(
565 struct client *c, struct winlink *wl, time_t t, struct grid_cell *gc)
567 struct options *oo = &wl->window->options;
568 struct session *s = c->session;
569 const char *fmt;
570 char *text;
571 u_char fg, bg, attr;
573 fg = options_get_number(oo, "window-status-fg");
574 if (fg != 8)
575 colour_set_fg(gc, fg);
576 bg = options_get_number(oo, "window-status-bg");
577 if (bg != 8)
578 colour_set_bg(gc, bg);
579 attr = options_get_number(oo, "window-status-attr");
580 if (attr != 0)
581 gc->attr = attr;
582 fmt = options_get_string(oo, "window-status-format");
583 if (wl == s->curw) {
584 fg = options_get_number(oo, "window-status-current-fg");
585 if (fg != 8)
586 colour_set_fg(gc, fg);
587 bg = options_get_number(oo, "window-status-current-bg");
588 if (bg != 8)
589 colour_set_bg(gc, bg);
590 attr = options_get_number(oo, "window-status-current-attr");
591 if (attr != 0)
592 gc->attr = attr;
593 fmt = options_get_string(oo, "window-status-current-format");
596 if (session_alert_has(s, wl, WINDOW_ACTIVITY) ||
597 session_alert_has(s, wl, WINDOW_BELL) ||
598 session_alert_has(s, wl, WINDOW_CONTENT)) {
599 fg = options_get_number(oo, "window-status-alert-fg");
600 if (fg != 8)
601 colour_set_fg(gc, fg);
602 bg = options_get_number(oo, "window-status-alert-bg");
603 if (bg != 8)
604 colour_set_bg(gc, bg);
605 attr = options_get_number(oo, "window-status-alert-attr");
606 if (attr != 0)
607 gc->attr = attr;
610 text = status_replace(c, wl, fmt, t, 1);
611 return (text);
614 /* Set a status line message. */
615 void printflike2
616 status_message_set(struct client *c, const char *fmt, ...)
618 struct timeval tv;
619 struct session *s = c->session;
620 struct message_entry *msg;
621 va_list ap;
622 int delay;
623 u_int i, limit;
625 status_prompt_clear(c);
626 status_message_clear(c);
628 va_start(ap, fmt);
629 xvasprintf(&c->message_string, fmt, ap);
630 va_end(ap);
632 ARRAY_EXPAND(&c->message_log, 1);
633 msg = &ARRAY_LAST(&c->message_log);
634 msg->msg_time = time(NULL);
635 msg->msg = xstrdup(c->message_string);
637 if (s == NULL)
638 limit = 0;
639 else
640 limit = options_get_number(&s->options, "message-limit");
641 if (ARRAY_LENGTH(&c->message_log) > limit) {
642 limit = ARRAY_LENGTH(&c->message_log) - limit;
643 for (i = 0; i < limit; i++) {
644 msg = &ARRAY_FIRST(&c->message_log);
645 xfree(msg->msg);
646 ARRAY_REMOVE(&c->message_log, 0);
650 delay = options_get_number(&c->session->options, "display-time");
651 tv.tv_sec = delay / 1000;
652 tv.tv_usec = (delay % 1000) * 1000L;
654 evtimer_del(&c->message_timer);
655 evtimer_set(&c->message_timer, status_message_callback, c);
656 evtimer_add(&c->message_timer, &tv);
658 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
659 c->flags |= CLIENT_STATUS;
662 /* Clear status line message. */
663 void
664 status_message_clear(struct client *c)
666 if (c->message_string == NULL)
667 return;
669 xfree(c->message_string);
670 c->message_string = NULL;
672 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
673 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
675 screen_reinit(&c->status);
678 /* Clear status line message after timer expires. */
679 /* ARGSUSED */
680 void
681 status_message_callback(unused int fd, unused short event, void *data)
683 struct client *c = data;
685 status_message_clear(c);
688 /* Draw client message on status line of present else on last line. */
690 status_message_redraw(struct client *c)
692 struct screen_write_ctx ctx;
693 struct session *s = c->session;
694 struct screen old_status;
695 size_t len;
696 struct grid_cell gc;
697 int utf8flag;
699 if (c->tty.sx == 0 || c->tty.sy == 0)
700 return (0);
701 memcpy(&old_status, &c->status, sizeof old_status);
702 screen_init(&c->status, c->tty.sx, 1, 0);
704 utf8flag = options_get_number(&s->options, "status-utf8");
706 len = screen_write_strlen(utf8flag, "%s", c->message_string);
707 if (len > c->tty.sx)
708 len = c->tty.sx;
710 memcpy(&gc, &grid_default_cell, sizeof gc);
711 colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
712 colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
713 gc.attr |= options_get_number(&s->options, "message-attr");
715 screen_write_start(&ctx, NULL, &c->status);
717 screen_write_cursormove(&ctx, 0, 0);
718 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->message_string);
719 for (; len < c->tty.sx; len++)
720 screen_write_putc(&ctx, &gc, ' ');
722 screen_write_stop(&ctx);
724 if (grid_compare(c->status.grid, old_status.grid) == 0) {
725 screen_free(&old_status);
726 return (0);
728 screen_free(&old_status);
729 return (1);
732 /* Enable status line prompt. */
733 void
734 status_prompt_set(struct client *c, const char *msg,
735 int (*callbackfn)(void *, const char *), void (*freefn)(void *),
736 void *data, int flags)
738 int keys;
740 status_message_clear(c);
741 status_prompt_clear(c);
743 c->prompt_string = xstrdup(msg);
745 c->prompt_buffer = xstrdup("");
746 c->prompt_index = 0;
748 c->prompt_callbackfn = callbackfn;
749 c->prompt_freefn = freefn;
750 c->prompt_data = data;
752 c->prompt_hindex = 0;
754 c->prompt_flags = flags;
756 keys = options_get_number(&c->session->options, "status-keys");
757 if (keys == MODEKEY_EMACS)
758 mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit);
759 else
760 mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit);
762 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
763 c->flags |= CLIENT_STATUS;
766 /* Remove status line prompt. */
767 void
768 status_prompt_clear(struct client *c)
770 if (c->prompt_string == NULL)
771 return;
773 if (c->prompt_freefn != NULL && c->prompt_data != NULL)
774 c->prompt_freefn(c->prompt_data);
776 xfree(c->prompt_string);
777 c->prompt_string = NULL;
779 xfree(c->prompt_buffer);
780 c->prompt_buffer = NULL;
782 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
783 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
785 screen_reinit(&c->status);
788 /* Update status line prompt with a new prompt string. */
789 void
790 status_prompt_update(struct client *c, const char *msg)
792 xfree(c->prompt_string);
793 c->prompt_string = xstrdup(msg);
795 *c->prompt_buffer = '\0';
796 c->prompt_index = 0;
798 c->prompt_hindex = 0;
800 c->flags |= CLIENT_STATUS;
803 /* Draw client prompt on status line of present else on last line. */
805 status_prompt_redraw(struct client *c)
807 struct screen_write_ctx ctx;
808 struct session *s = c->session;
809 struct screen old_status;
810 size_t i, size, left, len, off;
811 struct grid_cell gc, *gcp;
812 int utf8flag;
814 if (c->tty.sx == 0 || c->tty.sy == 0)
815 return (0);
816 memcpy(&old_status, &c->status, sizeof old_status);
817 screen_init(&c->status, c->tty.sx, 1, 0);
819 utf8flag = options_get_number(&s->options, "status-utf8");
821 len = screen_write_strlen(utf8flag, "%s", c->prompt_string);
822 if (len > c->tty.sx)
823 len = c->tty.sx;
824 off = 0;
826 memcpy(&gc, &grid_default_cell, sizeof gc);
827 colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
828 colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
829 gc.attr |= options_get_number(&s->options, "message-attr");
831 screen_write_start(&ctx, NULL, &c->status);
833 screen_write_cursormove(&ctx, 0, 0);
834 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->prompt_string);
836 left = c->tty.sx - len;
837 if (left != 0) {
838 size = screen_write_strlen(utf8flag, "%s", c->prompt_buffer);
839 if (c->prompt_index >= left) {
840 off = c->prompt_index - left + 1;
841 if (c->prompt_index == size)
842 left--;
843 size = left;
845 screen_write_nputs(
846 &ctx, left, &gc, utf8flag, "%s", c->prompt_buffer + off);
848 for (i = len + size; i < c->tty.sx; i++)
849 screen_write_putc(&ctx, &gc, ' ');
852 screen_write_stop(&ctx);
854 /* Apply fake cursor. */
855 off = len + c->prompt_index - off;
856 gcp = grid_view_get_cell(c->status.grid, off, 0);
857 gcp->attr ^= GRID_ATTR_REVERSE;
859 if (grid_compare(c->status.grid, old_status.grid) == 0) {
860 screen_free(&old_status);
861 return (0);
863 screen_free(&old_status);
864 return (1);
867 /* Handle keys in prompt. */
868 void
869 status_prompt_key(struct client *c, int key)
871 struct paste_buffer *pb;
872 char *s, *first, *last, word[64], swapc;
873 u_char ch;
874 size_t size, n, off, idx;
876 size = strlen(c->prompt_buffer);
877 switch (mode_key_lookup(&c->prompt_mdata, key)) {
878 case MODEKEYEDIT_CURSORLEFT:
879 if (c->prompt_index > 0) {
880 c->prompt_index--;
881 c->flags |= CLIENT_STATUS;
883 break;
884 case MODEKEYEDIT_SWITCHMODEAPPEND:
885 case MODEKEYEDIT_CURSORRIGHT:
886 if (c->prompt_index < size) {
887 c->prompt_index++;
888 c->flags |= CLIENT_STATUS;
890 break;
891 case MODEKEYEDIT_STARTOFLINE:
892 if (c->prompt_index != 0) {
893 c->prompt_index = 0;
894 c->flags |= CLIENT_STATUS;
896 break;
897 case MODEKEYEDIT_ENDOFLINE:
898 if (c->prompt_index != size) {
899 c->prompt_index = size;
900 c->flags |= CLIENT_STATUS;
902 break;
903 case MODEKEYEDIT_COMPLETE:
904 if (*c->prompt_buffer == '\0')
905 break;
907 idx = c->prompt_index;
908 if (idx != 0)
909 idx--;
911 /* Find the word we are in. */
912 first = c->prompt_buffer + idx;
913 while (first > c->prompt_buffer && *first != ' ')
914 first--;
915 while (*first == ' ')
916 first++;
917 last = c->prompt_buffer + idx;
918 while (*last != '\0' && *last != ' ')
919 last++;
920 while (*last == ' ')
921 last--;
922 if (*last != '\0')
923 last++;
924 if (last <= first ||
925 ((size_t) (last - first)) > (sizeof word) - 1)
926 break;
927 memcpy(word, first, last - first);
928 word[last - first] = '\0';
930 /* And try to complete it. */
931 if ((s = status_prompt_complete(word)) == NULL)
932 break;
934 /* Trim out word. */
935 n = size - (last - c->prompt_buffer) + 1; /* with \0 */
936 memmove(first, last, n);
937 size -= last - first;
939 /* Insert the new word. */
940 size += strlen(s);
941 off = first - c->prompt_buffer;
942 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1);
943 first = c->prompt_buffer + off;
944 memmove(first + strlen(s), first, n);
945 memcpy(first, s, strlen(s));
947 c->prompt_index = (first - c->prompt_buffer) + strlen(s);
948 xfree(s);
950 c->flags |= CLIENT_STATUS;
951 break;
952 case MODEKEYEDIT_BACKSPACE:
953 if (c->prompt_index != 0) {
954 if (c->prompt_index == size)
955 c->prompt_buffer[--c->prompt_index] = '\0';
956 else {
957 memmove(c->prompt_buffer + c->prompt_index - 1,
958 c->prompt_buffer + c->prompt_index,
959 size + 1 - c->prompt_index);
960 c->prompt_index--;
962 c->flags |= CLIENT_STATUS;
964 break;
965 case MODEKEYEDIT_DELETE:
966 if (c->prompt_index != size) {
967 memmove(c->prompt_buffer + c->prompt_index,
968 c->prompt_buffer + c->prompt_index + 1,
969 size + 1 - c->prompt_index);
970 c->flags |= CLIENT_STATUS;
972 break;
973 case MODEKEYEDIT_DELETELINE:
974 *c->prompt_buffer = '\0';
975 c->prompt_index = 0;
976 c->flags |= CLIENT_STATUS;
977 break;
978 case MODEKEYEDIT_DELETETOENDOFLINE:
979 if (c->prompt_index < size) {
980 c->prompt_buffer[c->prompt_index] = '\0';
981 c->flags |= CLIENT_STATUS;
983 break;
984 case MODEKEYEDIT_HISTORYUP:
985 if (ARRAY_LENGTH(&c->prompt_hdata) == 0)
986 break;
987 xfree(c->prompt_buffer);
989 c->prompt_buffer = xstrdup(ARRAY_ITEM(&c->prompt_hdata,
990 ARRAY_LENGTH(&c->prompt_hdata) - 1 - c->prompt_hindex));
991 if (c->prompt_hindex != ARRAY_LENGTH(&c->prompt_hdata) - 1)
992 c->prompt_hindex++;
994 c->prompt_index = strlen(c->prompt_buffer);
995 c->flags |= CLIENT_STATUS;
996 break;
997 case MODEKEYEDIT_HISTORYDOWN:
998 xfree(c->prompt_buffer);
1000 if (c->prompt_hindex != 0) {
1001 c->prompt_hindex--;
1002 c->prompt_buffer = xstrdup(ARRAY_ITEM(
1003 &c->prompt_hdata, ARRAY_LENGTH(
1004 &c->prompt_hdata) - 1 - c->prompt_hindex));
1005 } else
1006 c->prompt_buffer = xstrdup("");
1008 c->prompt_index = strlen(c->prompt_buffer);
1009 c->flags |= CLIENT_STATUS;
1010 break;
1011 case MODEKEYEDIT_PASTE:
1012 if ((pb = paste_get_top(&c->session->buffers)) == NULL)
1013 break;
1014 for (n = 0; n < pb->size; n++) {
1015 ch = (u_char) pb->data[n];
1016 if (ch < 32 || ch == 127)
1017 break;
1020 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1);
1021 if (c->prompt_index == size) {
1022 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1023 c->prompt_index += n;
1024 c->prompt_buffer[c->prompt_index] = '\0';
1025 } else {
1026 memmove(c->prompt_buffer + c->prompt_index + n,
1027 c->prompt_buffer + c->prompt_index,
1028 size + 1 - c->prompt_index);
1029 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1030 c->prompt_index += n;
1033 c->flags |= CLIENT_STATUS;
1034 break;
1035 case MODEKEYEDIT_TRANSPOSECHARS:
1036 idx = c->prompt_index;
1037 if (idx < size)
1038 idx++;
1039 if (idx >= 2) {
1040 swapc = c->prompt_buffer[idx - 2];
1041 c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1];
1042 c->prompt_buffer[idx - 1] = swapc;
1043 c->prompt_index = idx;
1044 c->flags |= CLIENT_STATUS;
1046 break;
1047 case MODEKEYEDIT_ENTER:
1048 if (*c->prompt_buffer != '\0')
1049 status_prompt_add_history(c);
1050 if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0)
1051 status_prompt_clear(c);
1052 break;
1053 case MODEKEYEDIT_CANCEL:
1054 if (c->prompt_callbackfn(c->prompt_data, NULL) == 0)
1055 status_prompt_clear(c);
1056 break;
1057 case MODEKEY_OTHER:
1058 if ((key & 0xff00) != 0 || key < 32 || key == 127)
1059 break;
1060 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2);
1062 if (c->prompt_index == size) {
1063 c->prompt_buffer[c->prompt_index++] = key;
1064 c->prompt_buffer[c->prompt_index] = '\0';
1065 } else {
1066 memmove(c->prompt_buffer + c->prompt_index + 1,
1067 c->prompt_buffer + c->prompt_index,
1068 size + 1 - c->prompt_index);
1069 c->prompt_buffer[c->prompt_index++] = key;
1072 if (c->prompt_flags & PROMPT_SINGLE) {
1073 if (c->prompt_callbackfn(
1074 c->prompt_data, c->prompt_buffer) == 0)
1075 status_prompt_clear(c);
1078 c->flags |= CLIENT_STATUS;
1079 break;
1080 default:
1081 break;
1085 /* Add line to the history. */
1086 void
1087 status_prompt_add_history(struct client *c)
1089 if (ARRAY_LENGTH(&c->prompt_hdata) > 0 &&
1090 strcmp(ARRAY_LAST(&c->prompt_hdata), c->prompt_buffer) == 0)
1091 return;
1093 if (ARRAY_LENGTH(&c->prompt_hdata) == PROMPT_HISTORY) {
1094 xfree(ARRAY_FIRST(&c->prompt_hdata));
1095 ARRAY_REMOVE(&c->prompt_hdata, 0);
1098 ARRAY_ADD(&c->prompt_hdata, xstrdup(c->prompt_buffer));
1101 /* Complete word. */
1102 char *
1103 status_prompt_complete(const char *s)
1105 const struct cmd_entry **cmdent;
1106 const struct set_option_entry *entry;
1107 ARRAY_DECL(, const char *) list;
1108 char *prefix, *s2;
1109 u_int i;
1110 size_t j;
1112 if (*s == '\0')
1113 return (NULL);
1115 /* First, build a list of all the possible matches. */
1116 ARRAY_INIT(&list);
1117 for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1118 if (strncmp((*cmdent)->name, s, strlen(s)) == 0)
1119 ARRAY_ADD(&list, (*cmdent)->name);
1121 for (entry = set_option_table; entry->name != NULL; entry++) {
1122 if (strncmp(entry->name, s, strlen(s)) == 0)
1123 ARRAY_ADD(&list, entry->name);
1125 for (entry = set_session_option_table; entry->name != NULL; entry++) {
1126 if (strncmp(entry->name, s, strlen(s)) == 0)
1127 ARRAY_ADD(&list, entry->name);
1129 for (entry = set_window_option_table; entry->name != NULL; entry++) {
1130 if (strncmp(entry->name, s, strlen(s)) == 0)
1131 ARRAY_ADD(&list, entry->name);
1134 /* If none, bail now. */
1135 if (ARRAY_LENGTH(&list) == 0) {
1136 ARRAY_FREE(&list);
1137 return (NULL);
1140 /* If an exact match, return it, with a trailing space. */
1141 if (ARRAY_LENGTH(&list) == 1) {
1142 xasprintf(&s2, "%s ", ARRAY_FIRST(&list));
1143 ARRAY_FREE(&list);
1144 return (s2);
1147 /* Now loop through the list and find the longest common prefix. */
1148 prefix = xstrdup(ARRAY_FIRST(&list));
1149 for (i = 1; i < ARRAY_LENGTH(&list); i++) {
1150 s = ARRAY_ITEM(&list, i);
1152 j = strlen(s);
1153 if (j > strlen(prefix))
1154 j = strlen(prefix);
1155 for (; j > 0; j--) {
1156 if (prefix[j - 1] != s[j - 1])
1157 prefix[j - 1] = '\0';
1161 ARRAY_FREE(&list);
1162 return (prefix);