Annotation of src/usr.bin/tmux/status.c, Revision 1.45
1.45 ! nicm 1: /* $OpenBSD: status.c,v 1.44 2009/11/18 13:16:33 nicm Exp $ */
1.1 nicm 2:
3: /*
4: * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
5: *
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.
9: *
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.
17: */
18:
19: #include <sys/types.h>
20: #include <sys/time.h>
21:
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>
29:
30: #include "tmux.h"
31:
1.38 nicm 32: char *status_job(struct client *, char **);
33: void status_job_callback(struct job *);
1.1 nicm 34: size_t status_width(struct winlink *);
35: char *status_print(struct session *, struct winlink *, struct grid_cell *);
1.42 nicm 36: void status_message_callback(int, short, void *);
1.1 nicm 37:
38: void status_prompt_add_history(struct client *);
39: char *status_prompt_complete(const char *);
40:
41: /* Draw status for client on the last lines of given context. */
42: int
43: status_redraw(struct client *c)
44: {
45: struct screen_write_ctx ctx;
46: struct session *s = c->session;
47: struct winlink *wl;
1.7 nicm 48: struct screen old_status;
1.1 nicm 49: char *left, *right, *text, *ptr;
1.2 nicm 50: size_t llen, llen2, rlen, rlen2, offset;
1.15 nicm 51: size_t ox, xx, yy, size, start, width;
1.24 nicm 52: struct grid_cell stdgc, sl_stdgc, sr_stdgc, gc;
1.3 nicm 53: int larrow, rarrow, utf8flag;
1.24 nicm 54: int sl_fg, sl_bg, sr_fg, sr_bg;
55: int sl_attr, sr_attr;
1.1 nicm 56:
57: left = right = NULL;
58:
1.7 nicm 59: /* No status line?*/
60: if (c->tty.sy == 0 || !options_get_number(&s->options, "status"))
61: return (1);
62: larrow = rarrow = 0;
1.1 nicm 63:
1.7 nicm 64: /* Create the target screen. */
65: memcpy(&old_status, &c->status, sizeof old_status);
66: screen_init(&c->status, c->tty.sx, 1, 0);
1.1 nicm 67:
68: if (gettimeofday(&c->status_timer, NULL) != 0)
1.34 nicm 69: fatal("gettimeofday failed");
1.1 nicm 70: memcpy(&stdgc, &grid_default_cell, sizeof gc);
1.33 nicm 71: colour_set_fg(&stdgc, options_get_number(&s->options, "status-fg"));
72: colour_set_bg(&stdgc, options_get_number(&s->options, "status-bg"));
1.1 nicm 73: stdgc.attr |= options_get_number(&s->options, "status-attr");
74:
1.24 nicm 75: /*
76: * Set the status-left and status-right parts to the default status
77: * line options and only change them where they differ from the
78: * defaults.
79: */
80: memcpy(&sl_stdgc, &stdgc, sizeof sl_stdgc);
81: memcpy(&sr_stdgc, &stdgc, sizeof sr_stdgc);
82: sl_fg = options_get_number(&s->options, "status-left-fg");
83: if (sl_fg != 8)
1.33 nicm 84: colour_set_fg(&sl_stdgc, sl_fg);
1.24 nicm 85: sl_bg = options_get_number(&s->options, "status-left-bg");
86: if (sl_bg != 8)
1.33 nicm 87: colour_set_bg(&sl_stdgc, sl_bg);
1.24 nicm 88: sl_attr = options_get_number(&s->options, "status-left-attr");
89: if (sl_attr != 0)
90: sl_stdgc.attr = sl_attr;
91: sr_fg = options_get_number(&s->options, "status-right-fg");
92: if (sr_fg != 8)
1.33 nicm 93: colour_set_fg(&sr_stdgc, sr_fg);
1.24 nicm 94: sr_bg = options_get_number(&s->options, "status-right-bg");
95: if (sr_bg != 8)
1.33 nicm 96: colour_set_bg(&sr_stdgc, sr_bg);
1.24 nicm 97: sr_attr = options_get_number(&s->options, "status-right-attr");
98: if (sr_attr != 0)
99: sr_stdgc.attr = sr_attr;
100:
1.1 nicm 101: yy = c->tty.sy - 1;
102: if (yy == 0)
103: goto blank;
104:
1.3 nicm 105: /* Caring about UTF-8 in status line? */
106: utf8flag = options_get_number(&s->options, "status-utf8");
107:
1.1 nicm 108: /* Work out the left and right strings. */
1.38 nicm 109: left = status_replace(c, options_get_string(
1.45 ! nicm 110: &s->options, "status-left"), c->status_timer.tv_sec, 1);
1.1 nicm 111: llen = options_get_number(&s->options, "status-left-length");
1.31 nicm 112: llen2 = screen_write_cstrlen(utf8flag, "%s", left);
1.2 nicm 113: if (llen2 < llen)
114: llen = llen2;
1.1 nicm 115:
1.38 nicm 116: right = status_replace(c, options_get_string(
1.45 ! nicm 117: &s->options, "status-right"), c->status_timer.tv_sec, 1);
1.1 nicm 118: rlen = options_get_number(&s->options, "status-right-length");
1.31 nicm 119: rlen2 = screen_write_cstrlen(utf8flag, "%s", right);
1.2 nicm 120: if (rlen2 < rlen)
121: rlen = rlen2;
1.1 nicm 122:
123: /*
124: * Figure out how much space we have for the window list. If there isn't
125: * enough space, just wimp out.
126: */
127: xx = 0;
128: if (llen != 0)
129: xx += llen + 1;
130: if (rlen != 0)
131: xx += rlen + 1;
132: if (c->tty.sx == 0 || c->tty.sx <= xx)
133: goto blank;
134: xx = c->tty.sx - xx;
135:
136: /*
137: * Right. We have xx characters to fill. Find out how much is to go in
138: * them and the offset of the current window (it must be on screen).
139: */
140: width = offset = 0;
141: RB_FOREACH(wl, winlinks, &s->windows) {
142: size = status_width(wl) + 1;
143: if (wl == s->curw)
144: offset = width;
145: width += size;
146: }
147: start = 0;
148:
149: /* If there is enough space for the total width, all is gravy. */
150: if (width <= xx)
151: goto draw;
152:
153: /* Find size of current window text. */
154: size = status_width(s->curw);
155:
156: /*
157: * If the offset is already on screen, we're good to draw from the
158: * start and just leave off the end.
159: */
160: if (offset + size < xx) {
161: if (xx > 0) {
162: rarrow = 1;
163: xx--;
164: }
165:
166: width = xx;
167: goto draw;
168: }
169:
170: /*
171: * Work out how many characters we need to omit from the start. There
172: * are xx characters to fill, and offset + size must be the last. So,
173: * the start character is offset + size - xx.
174: */
175: if (xx > 0) {
176: larrow = 1;
177: xx--;
178: }
179:
180: start = offset + size - xx;
181: if (xx > 0 && width > start + xx + 1) { /* + 1, eh? */
182: rarrow = 1;
183: start++;
184: xx--;
185: }
186: width = xx;
187:
188: draw:
189: /* Bail here if anything is too small too. XXX. */
190: if (width == 0 || xx == 0)
191: goto blank;
192:
193: /* Begin drawing and move to the starting position. */
194: screen_write_start(&ctx, NULL, &c->status);
195: if (llen != 0) {
196: screen_write_cursormove(&ctx, 0, yy);
1.31 nicm 197: screen_write_cnputs(&ctx, llen, &sl_stdgc, utf8flag, "%s", left);
1.5 nicm 198: screen_write_putc(&ctx, &stdgc, ' ');
1.1 nicm 199: if (larrow)
200: screen_write_putc(&ctx, &stdgc, ' ');
201: } else {
202: if (larrow)
203: screen_write_cursormove(&ctx, 1, yy);
204: else
205: screen_write_cursormove(&ctx, 0, yy);
1.15 nicm 206: }
207:
208: ox = 0;
209: if (width < xx) {
210: switch (options_get_number(&s->options, "status-justify")) {
211: case 1: /* centered */
212: ox = 1 + (xx - width) / 2;
213: break;
214: case 2: /* right */
215: ox = 1 + (xx - width);
216: break;
217: }
218: xx -= ox;
219: while (ox-- > 0)
220: screen_write_putc(&ctx, &stdgc, ' ');
1.1 nicm 221: }
222:
223: /* Draw each character in succession. */
224: offset = 0;
225: RB_FOREACH(wl, winlinks, &s->windows) {
226: memcpy(&gc, &stdgc, sizeof gc);
227: text = status_print(s, wl, &gc);
228:
229: if (larrow == 1 && offset < start) {
230: if (session_alert_has(s, wl, WINDOW_ACTIVITY))
231: larrow = -1;
232: else if (session_alert_has(s, wl, WINDOW_BELL))
233: larrow = -1;
234: else if (session_alert_has(s, wl, WINDOW_CONTENT))
235: larrow = -1;
236: }
237:
238: for (ptr = text; *ptr != '\0'; ptr++) {
239: if (offset >= start && offset < start + width)
240: screen_write_putc(&ctx, &gc, *ptr);
241: offset++;
242: }
243:
244: if (rarrow == 1 && offset > start + width) {
245: if (session_alert_has(s, wl, WINDOW_ACTIVITY))
246: rarrow = -1;
247: else if (session_alert_has(s, wl, WINDOW_BELL))
248: rarrow = -1;
249: else if (session_alert_has(s, wl, WINDOW_CONTENT))
250: rarrow = -1;
251: }
252:
253: if (offset < start + width) {
254: if (offset >= start) {
255: screen_write_putc(&ctx, &stdgc, ' ');
256: }
257: offset++;
258: }
259:
260: xfree(text);
261: }
262:
263: /* Fill the remaining space if any. */
264: while (offset++ < xx)
265: screen_write_putc(&ctx, &stdgc, ' ');
266:
267: /* Draw the last item. */
268: if (rlen != 0) {
269: screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, yy);
1.5 nicm 270: screen_write_putc(&ctx, &stdgc, ' ');
1.31 nicm 271: screen_write_cnputs(&ctx, rlen, &sr_stdgc, utf8flag, "%s", right);
1.1 nicm 272: }
273:
274: /* Draw the arrows. */
275: if (larrow != 0) {
276: memcpy(&gc, &stdgc, sizeof gc);
277: if (larrow == -1)
278: gc.attr ^= GRID_ATTR_REVERSE;
279: if (llen != 0)
280: screen_write_cursormove(&ctx, llen + 1, yy);
281: else
282: screen_write_cursormove(&ctx, 0, yy);
283: screen_write_putc(&ctx, &gc, '<');
284: }
285: if (rarrow != 0) {
286: memcpy(&gc, &stdgc, sizeof gc);
287: if (rarrow == -1)
288: gc.attr ^= GRID_ATTR_REVERSE;
289: if (rlen != 0)
290: screen_write_cursormove(&ctx, c->tty.sx - rlen - 2, yy);
291: else
292: screen_write_cursormove(&ctx, c->tty.sx - 1, yy);
293: screen_write_putc(&ctx, &gc, '>');
294: }
295:
296: goto out;
297:
298: blank:
299: /* Just draw the whole line as blank. */
300: screen_write_start(&ctx, NULL, &c->status);
301: screen_write_cursormove(&ctx, 0, yy);
302: for (offset = 0; offset < c->tty.sx; offset++)
303: screen_write_putc(&ctx, &stdgc, ' ');
304:
305: out:
306: screen_write_stop(&ctx);
307:
308: if (left != NULL)
309: xfree(left);
310: if (right != NULL)
311: xfree(right);
312:
313: if (grid_compare(c->status.grid, old_status.grid) == 0) {
314: screen_free(&old_status);
315: return (0);
316: }
317: screen_free(&old_status);
318: return (1);
319: }
320:
321: char *
1.45 ! nicm 322: status_replace(struct client *c, const char *fmt, time_t t, int run_jobs)
1.1 nicm 323: {
1.38 nicm 324: struct session *s = c->session;
1.1 nicm 325: struct winlink *wl = s->curw;
326: static char out[BUFSIZ];
327: char in[BUFSIZ], tmp[256], ch, *iptr, *optr, *ptr, *endptr;
1.38 nicm 328: char *savedptr; /* freed at end of each loop */
1.1 nicm 329: size_t len;
330: long n;
1.38 nicm 331:
332:
1.1 nicm 333: strftime(in, sizeof in, fmt, localtime(&t));
334: in[(sizeof in) - 1] = '\0';
335:
336: iptr = in;
337: optr = out;
338: savedptr = NULL;
339:
340: while (*iptr != '\0') {
341: if (optr >= out + (sizeof out) - 1)
342: break;
343: switch (ch = *iptr++) {
344: case '#':
345: errno = 0;
346: n = strtol(iptr, &endptr, 10);
347: if ((n == 0 && errno != EINVAL) ||
348: (n == LONG_MIN && errno != ERANGE) ||
349: (n == LONG_MAX && errno != ERANGE) ||
350: n != 0)
351: iptr = endptr;
352: if (n <= 0)
353: n = LONG_MAX;
354:
355: ptr = NULL;
356: switch (*iptr++) {
357: case '(':
1.45 ! nicm 358: if (run_jobs) {
! 359: if (ptr == NULL) {
! 360: ptr = status_job(c, &iptr);
! 361: if (ptr == NULL)
! 362: break;
! 363: savedptr = ptr;
! 364: }
! 365: } else {
! 366: /* Don't run jobs. Copy to ). */
! 367: *optr++ = '#';
! 368:
! 369: iptr--; /* include [ */
! 370: while (*iptr != ')' && *iptr != '\0') {
! 371: if (optr >=
! 372: out + (sizeof out) - 1)
! 373: break;
! 374: *optr++ = *iptr++;
! 375: }
! 376: break;
1.1 nicm 377: }
378: /* FALLTHROUGH */
379: case 'H':
380: if (ptr == NULL) {
381: if (gethostname(tmp, sizeof tmp) != 0)
1.34 nicm 382: fatal("gethostname failed");
1.1 nicm 383: ptr = tmp;
384: }
385: /* FALLTHROUGH */
1.13 nicm 386: case 'I':
387: if (ptr == NULL) {
388: xsnprintf(tmp, sizeof tmp, "%d", wl->idx);
389: ptr = tmp;
390: }
391: /* FALLTHROUGH */
392: case 'P':
393: if (ptr == NULL) {
394: xsnprintf(tmp, sizeof tmp, "%u",
1.38 nicm 395: window_pane_index(wl->window,
396: wl->window->active));
1.13 nicm 397: ptr = tmp;
398: }
1.16 nicm 399: /* FALLTHROUGH */
1.1 nicm 400: case 'S':
401: if (ptr == NULL)
402: ptr = s->name;
403: /* FALLTHROUGH */
404: case 'T':
405: if (ptr == NULL)
406: ptr = wl->window->active->base.title;
1.13 nicm 407: /* FALLTHROUGH */
408: case 'W':
409: if (ptr == NULL)
410: ptr = wl->window->name;
1.1 nicm 411: len = strlen(ptr);
412: if ((size_t) n < len)
413: len = n;
414: if (optr + len >= out + (sizeof out) - 1)
415: break;
416: while (len > 0 && *ptr != '\0') {
417: *optr++ = *ptr++;
418: len--;
1.31 nicm 419: }
420: break;
421: case '[':
422: /*
423: * Embedded style, handled at display time.
424: * Leave present and skip input until ].
425: */
426: *optr++ = '#';
427:
428: iptr--; /* include [ */
429: while (*iptr != ']' && *iptr != '\0') {
430: if (optr >= out + (sizeof out) - 1)
431: break;
432: *optr++ = *iptr++;
1.1 nicm 433: }
434: break;
435: case '#':
436: *optr++ = '#';
437: break;
438: }
439: if (savedptr != NULL) {
440: xfree(savedptr);
441: savedptr = NULL;
442: }
443: break;
444: default:
445: *optr++ = ch;
446: break;
447: }
448: }
449: *optr = '\0';
450:
451: return (xstrdup(out));
452: }
453:
454: char *
1.38 nicm 455: status_job(struct client *c, char **iptr)
1.1 nicm 456: {
1.38 nicm 457: struct job *job;
1.40 nicm 458: char *cmd;
1.38 nicm 459: int lastesc;
460: size_t len;
1.1 nicm 461:
462: if (**iptr == '\0')
463: return (NULL);
464: if (**iptr == ')') { /* no command given */
465: (*iptr)++;
466: return (NULL);
467: }
468:
469: cmd = xmalloc(strlen(*iptr) + 1);
470: len = 0;
471:
472: lastesc = 0;
473: for (; **iptr != '\0'; (*iptr)++) {
474: if (!lastesc && **iptr == ')')
475: break; /* unescaped ) is the end */
476: if (!lastesc && **iptr == '\\') {
477: lastesc = 1;
478: continue; /* skip \ if not escaped */
479: }
480: lastesc = 0;
481: cmd[len++] = **iptr;
482: }
1.38 nicm 483: if (**iptr == '\0') /* no terminating ) */ {
484: xfree(cmd);
485: return (NULL);
486: }
1.1 nicm 487: (*iptr)++; /* skip final ) */
488: cmd[len] = '\0';
489:
1.38 nicm 490: job = job_get(&c->status_jobs, cmd);
491: if (job == NULL) {
1.39 nicm 492: job = job_add(&c->status_jobs,
493: JOB_PERSIST, c, cmd, status_job_callback, xfree, NULL);
1.38 nicm 494: job_run(job);
495: }
496: if (job->data == NULL)
497: return (xstrdup(""));
498: return (xstrdup(job->data));
499: }
1.1 nicm 500:
1.38 nicm 501: void
502: status_job_callback(struct job *job)
503: {
1.41 nicm 504: char *line, *buf;
1.38 nicm 505: size_t len;
506:
1.41 nicm 507: buf = NULL;
508: if ((line = evbuffer_readline(job->event->input)) == NULL) {
509: len = EVBUFFER_LENGTH(job->event->input);
510: buf = xmalloc(len + 1);
511: if (len != 0)
512: memcpy(buf, EVBUFFER_DATA(job->event->input), len);
513: buf[len] = '\0';
514: }
1.38 nicm 515:
516: if (job->data != NULL)
517: xfree(job->data);
518: else
519: server_redraw_client(job->client);
1.41 nicm 520: job->data = xstrdup(line);
1.1 nicm 521:
1.41 nicm 522: if (buf != NULL)
523: xfree(buf);
1.1 nicm 524: }
525:
526: size_t
527: status_width(struct winlink *wl)
528: {
529: return (xsnprintf(NULL, 0, "%d:%s ", wl->idx, wl->window->name));
530: }
531:
532: char *
533: status_print(struct session *s, struct winlink *wl, struct grid_cell *gc)
534: {
1.33 nicm 535: struct options *oo = &wl->window->options;
536: char *text, flag;
537: u_char fg, bg, attr;
1.1 nicm 538:
1.33 nicm 539: fg = options_get_number(oo, "window-status-fg");
1.1 nicm 540: if (fg != 8)
1.33 nicm 541: colour_set_fg(gc, fg);
542: bg = options_get_number(oo, "window-status-bg");
1.1 nicm 543: if (bg != 8)
1.33 nicm 544: colour_set_bg(gc, bg);
545: attr = options_get_number(oo, "window-status-attr");
1.1 nicm 546: if (attr != 0)
547: gc->attr = attr;
548:
549: flag = ' ';
1.37 nicm 550: if (wl == TAILQ_FIRST(&s->lastw))
1.1 nicm 551: flag = '-';
1.14 nicm 552: if (wl == s->curw) {
1.33 nicm 553: fg = options_get_number(oo, "window-status-current-fg");
1.14 nicm 554: if (fg != 8)
1.33 nicm 555: colour_set_fg(gc, fg);
556: bg = options_get_number(oo, "window-status-current-bg");
1.14 nicm 557: if (bg != 8)
1.33 nicm 558: colour_set_bg(gc, bg);
559: attr = options_get_number(oo, "window-status-current-attr");
1.14 nicm 560: if (attr != 0)
561: gc->attr = attr;
1.1 nicm 562: flag = '*';
1.14 nicm 563: }
1.1 nicm 564:
565: if (session_alert_has(s, wl, WINDOW_ACTIVITY)) {
566: flag = '#';
567: gc->attr ^= GRID_ATTR_REVERSE;
568: } else if (session_alert_has(s, wl, WINDOW_BELL)) {
569: flag = '!';
570: gc->attr ^= GRID_ATTR_REVERSE;
571: } else if (session_alert_has(s, wl, WINDOW_CONTENT)) {
572: flag = '+';
573: gc->attr ^= GRID_ATTR_REVERSE;
574: }
575:
576: xasprintf(&text, "%d:%s%c", wl->idx, wl->window->name, flag);
577: return (text);
578: }
579:
1.10 nicm 580: void printflike2
581: status_message_set(struct client *c, const char *fmt, ...)
1.1 nicm 582: {
1.44 nicm 583: struct timeval tv;
584: struct session *s = c->session;
585: struct message_entry *msg;
586: va_list ap;
587: int delay;
588: u_int i, limit;
1.1 nicm 589:
1.12 nicm 590: status_prompt_clear(c);
591: status_message_clear(c);
592:
1.28 nicm 593: va_start(ap, fmt);
594: xvasprintf(&c->message_string, fmt, ap);
595: va_end(ap);
596:
1.44 nicm 597: ARRAY_EXPAND(&c->message_log, 1);
598: msg = &ARRAY_LAST(&c->message_log);
599: msg->msg_time = time(NULL);
600: msg->msg = xstrdup(c->message_string);
601:
602: if (s == NULL)
603: limit = 0;
604: else
605: limit = options_get_number(&s->options, "message-limit");
606: for (i = ARRAY_LENGTH(&c->message_log); i > limit; i--) {
607: msg = &ARRAY_ITEM(&c->message_log, i - 1);
608: xfree(msg->msg);
609: ARRAY_REMOVE(&c->message_log, i - 1);
610: }
611:
1.1 nicm 612: delay = options_get_number(&c->session->options, "display-time");
613: tv.tv_sec = delay / 1000;
614: tv.tv_usec = (delay % 1000) * 1000L;
1.44 nicm 615:
1.42 nicm 616: evtimer_del(&c->message_timer);
617: evtimer_set(&c->message_timer, status_message_callback, c);
618: evtimer_add(&c->message_timer, &tv);
1.1 nicm 619:
620: c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
621: c->flags |= CLIENT_STATUS;
622: }
623:
624: void
625: status_message_clear(struct client *c)
626: {
627: if (c->message_string == NULL)
628: return;
629:
630: xfree(c->message_string);
631: c->message_string = NULL;
632:
633: c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
1.12 nicm 634: c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
1.7 nicm 635:
636: screen_reinit(&c->status);
1.42 nicm 637: }
638:
639: void
640: status_message_callback(unused int fd, unused short event, void *data)
641: {
642: struct client *c = data;
643:
644: status_message_clear(c);
1.1 nicm 645: }
646:
647: /* Draw client message on status line of present else on last line. */
648: int
649: status_message_redraw(struct client *c)
650: {
651: struct screen_write_ctx ctx;
652: struct session *s = c->session;
653: struct screen old_status;
654: size_t len;
655: struct grid_cell gc;
656:
657: if (c->tty.sx == 0 || c->tty.sy == 0)
658: return (0);
659: memcpy(&old_status, &c->status, sizeof old_status);
660: screen_init(&c->status, c->tty.sx, 1, 0);
661:
662: len = strlen(c->message_string);
663: if (len > c->tty.sx)
664: len = c->tty.sx;
665:
666: memcpy(&gc, &grid_default_cell, sizeof gc);
1.33 nicm 667: colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
668: colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
1.1 nicm 669: gc.attr |= options_get_number(&s->options, "message-attr");
670:
671: screen_write_start(&ctx, NULL, &c->status);
672:
673: screen_write_cursormove(&ctx, 0, 0);
674: screen_write_puts(&ctx, &gc, "%.*s", (int) len, c->message_string);
675: for (; len < c->tty.sx; len++)
676: screen_write_putc(&ctx, &gc, ' ');
677:
678: screen_write_stop(&ctx);
679:
680: if (grid_compare(c->status.grid, old_status.grid) == 0) {
681: screen_free(&old_status);
682: return (0);
683: }
684: screen_free(&old_status);
685: return (1);
686: }
687:
688: void
1.12 nicm 689: status_prompt_set(struct client *c, const char *msg,
690: int (*callbackfn)(void *, const char *), void (*freefn)(void *),
691: void *data, int flags)
1.1 nicm 692: {
1.20 nicm 693: int keys;
694:
1.12 nicm 695: status_message_clear(c);
696: status_prompt_clear(c);
697:
1.1 nicm 698: c->prompt_string = xstrdup(msg);
699:
700: c->prompt_buffer = xstrdup("");
701: c->prompt_index = 0;
702:
1.12 nicm 703: c->prompt_callbackfn = callbackfn;
704: c->prompt_freefn = freefn;
1.1 nicm 705: c->prompt_data = data;
706:
707: c->prompt_hindex = 0;
708:
709: c->prompt_flags = flags;
710:
1.20 nicm 711: keys = options_get_number(&c->session->options, "status-keys");
712: if (keys == MODEKEY_EMACS)
1.21 nicm 713: mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit);
1.20 nicm 714: else
1.21 nicm 715: mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit);
1.1 nicm 716:
717: c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
718: c->flags |= CLIENT_STATUS;
719: }
720:
721: void
722: status_prompt_clear(struct client *c)
723: {
1.12 nicm 724: if (c->prompt_string == NULL)
1.1 nicm 725: return;
726:
1.12 nicm 727: if (c->prompt_freefn != NULL && c->prompt_data != NULL)
728: c->prompt_freefn(c->prompt_data);
1.1 nicm 729:
730: xfree(c->prompt_string);
731: c->prompt_string = NULL;
732:
733: xfree(c->prompt_buffer);
734: c->prompt_buffer = NULL;
735:
736: c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
1.12 nicm 737: c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
1.7 nicm 738:
739: screen_reinit(&c->status);
1.27 nicm 740: }
741:
742: void
743: status_prompt_update(struct client *c, const char *msg)
744: {
745: xfree(c->prompt_string);
746: c->prompt_string = xstrdup(msg);
747:
748: *c->prompt_buffer = '\0';
749: c->prompt_index = 0;
750:
751: c->prompt_hindex = 0;
752:
753: c->flags |= CLIENT_STATUS;
1.1 nicm 754: }
755:
756: /* Draw client prompt on status line of present else on last line. */
757: int
758: status_prompt_redraw(struct client *c)
759: {
760: struct screen_write_ctx ctx;
761: struct session *s = c->session;
762: struct screen old_status;
1.29 nicm 763: size_t i, size, left, len, off;
1.1 nicm 764: char ch;
765: struct grid_cell gc;
766:
767: if (c->tty.sx == 0 || c->tty.sy == 0)
768: return (0);
769: memcpy(&old_status, &c->status, sizeof old_status);
770: screen_init(&c->status, c->tty.sx, 1, 0);
1.17 nicm 771: off = 0;
1.1 nicm 772:
773: len = strlen(c->prompt_string);
774: if (len > c->tty.sx)
775: len = c->tty.sx;
776:
777: memcpy(&gc, &grid_default_cell, sizeof gc);
1.33 nicm 778: colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
779: colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
1.1 nicm 780: gc.attr |= options_get_number(&s->options, "message-attr");
781:
782: screen_write_start(&ctx, NULL, &c->status);
783:
784: screen_write_cursormove(&ctx, 0, 0);
785: screen_write_puts(&ctx, &gc, "%.*s", (int) len, c->prompt_string);
786:
787: left = c->tty.sx - len;
788: if (left != 0) {
789: if (c->prompt_index < left)
790: size = strlen(c->prompt_buffer);
791: else {
1.17 nicm 792: off = c->prompt_index - left + 1;
1.1 nicm 793: if (c->prompt_index == strlen(c->prompt_buffer))
794: left--;
795: size = left;
796: }
1.36 nicm 797: screen_write_puts(
798: &ctx, &gc, "%.*s", (int) left, c->prompt_buffer + off);
1.1 nicm 799:
800: for (i = len + size; i < c->tty.sx; i++)
801: screen_write_putc(&ctx, &gc, ' ');
802:
1.17 nicm 803: /* Draw a fake cursor. */
1.29 nicm 804: ch = ' ';
1.36 nicm 805: screen_write_cursormove(&ctx, len + c->prompt_index - off, 0);
806: if (c->prompt_index < strlen(c->prompt_buffer))
807: ch = c->prompt_buffer[c->prompt_index];
1.17 nicm 808: gc.attr ^= GRID_ATTR_REVERSE;
809: screen_write_putc(&ctx, &gc, ch);
1.1 nicm 810: }
811:
812: screen_write_stop(&ctx);
813:
814: if (grid_compare(c->status.grid, old_status.grid) == 0) {
815: screen_free(&old_status);
816: return (0);
817: }
818: screen_free(&old_status);
819: return (1);
820: }
821:
822: /* Handle keys in prompt. */
823: void
824: status_prompt_key(struct client *c, int key)
825: {
826: struct paste_buffer *pb;
1.30 nicm 827: char *s, *first, *last, word[64], swapc;
1.1 nicm 828: size_t size, n, off, idx;
829:
830: size = strlen(c->prompt_buffer);
831: switch (mode_key_lookup(&c->prompt_mdata, key)) {
1.20 nicm 832: case MODEKEYEDIT_CURSORLEFT:
1.1 nicm 833: if (c->prompt_index > 0) {
834: c->prompt_index--;
835: c->flags |= CLIENT_STATUS;
836: }
837: break;
1.20 nicm 838: case MODEKEYEDIT_SWITCHMODEAPPEND:
839: case MODEKEYEDIT_CURSORRIGHT:
1.1 nicm 840: if (c->prompt_index < size) {
841: c->prompt_index++;
842: c->flags |= CLIENT_STATUS;
843: }
844: break;
1.20 nicm 845: case MODEKEYEDIT_STARTOFLINE:
1.1 nicm 846: if (c->prompt_index != 0) {
847: c->prompt_index = 0;
848: c->flags |= CLIENT_STATUS;
849: }
850: break;
1.20 nicm 851: case MODEKEYEDIT_ENDOFLINE:
1.1 nicm 852: if (c->prompt_index != size) {
853: c->prompt_index = size;
854: c->flags |= CLIENT_STATUS;
855: }
856: break;
1.20 nicm 857: case MODEKEYEDIT_COMPLETE:
1.1 nicm 858: if (*c->prompt_buffer == '\0')
859: break;
860:
861: idx = c->prompt_index;
862: if (idx != 0)
863: idx--;
864:
865: /* Find the word we are in. */
866: first = c->prompt_buffer + idx;
867: while (first > c->prompt_buffer && *first != ' ')
868: first--;
869: while (*first == ' ')
870: first++;
871: last = c->prompt_buffer + idx;
872: while (*last != '\0' && *last != ' ')
873: last++;
874: while (*last == ' ')
875: last--;
876: if (*last != '\0')
877: last++;
878: if (last <= first ||
879: ((size_t) (last - first)) > (sizeof word) - 1)
880: break;
881: memcpy(word, first, last - first);
882: word[last - first] = '\0';
883:
884: /* And try to complete it. */
885: if ((s = status_prompt_complete(word)) == NULL)
886: break;
887:
888: /* Trim out word. */
889: n = size - (last - c->prompt_buffer) + 1; /* with \0 */
890: memmove(first, last, n);
891: size -= last - first;
892:
893: /* Insert the new word. */
894: size += strlen(s);
895: off = first - c->prompt_buffer;
896: c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1);
897: first = c->prompt_buffer + off;
898: memmove(first + strlen(s), first, n);
899: memcpy(first, s, strlen(s));
900:
901: c->prompt_index = (first - c->prompt_buffer) + strlen(s);
1.22 nicm 902: xfree(s);
1.1 nicm 903:
904: c->flags |= CLIENT_STATUS;
905: break;
1.20 nicm 906: case MODEKEYEDIT_BACKSPACE:
1.1 nicm 907: if (c->prompt_index != 0) {
908: if (c->prompt_index == size)
909: c->prompt_buffer[--c->prompt_index] = '\0';
910: else {
911: memmove(c->prompt_buffer + c->prompt_index - 1,
912: c->prompt_buffer + c->prompt_index,
913: size + 1 - c->prompt_index);
914: c->prompt_index--;
915: }
916: c->flags |= CLIENT_STATUS;
917: }
918: break;
1.20 nicm 919: case MODEKEYEDIT_DELETE:
1.1 nicm 920: if (c->prompt_index != size) {
921: memmove(c->prompt_buffer + c->prompt_index,
922: c->prompt_buffer + c->prompt_index + 1,
923: size + 1 - c->prompt_index);
1.18 nicm 924: c->flags |= CLIENT_STATUS;
925: }
1.26 nicm 926: break;
927: case MODEKEYEDIT_DELETELINE:
928: *c->prompt_buffer = '\0';
929: c->prompt_index = 0;
930: c->flags |= CLIENT_STATUS;
1.18 nicm 931: break;
1.20 nicm 932: case MODEKEYEDIT_DELETETOENDOFLINE:
1.18 nicm 933: if (c->prompt_index < size) {
934: c->prompt_buffer[c->prompt_index] = '\0';
1.1 nicm 935: c->flags |= CLIENT_STATUS;
936: }
937: break;
1.20 nicm 938: case MODEKEYEDIT_HISTORYUP:
1.1 nicm 939: if (ARRAY_LENGTH(&c->prompt_hdata) == 0)
940: break;
941: xfree(c->prompt_buffer);
942:
943: c->prompt_buffer = xstrdup(ARRAY_ITEM(&c->prompt_hdata,
944: ARRAY_LENGTH(&c->prompt_hdata) - 1 - c->prompt_hindex));
945: if (c->prompt_hindex != ARRAY_LENGTH(&c->prompt_hdata) - 1)
946: c->prompt_hindex++;
947:
948: c->prompt_index = strlen(c->prompt_buffer);
949: c->flags |= CLIENT_STATUS;
950: break;
1.20 nicm 951: case MODEKEYEDIT_HISTORYDOWN:
1.1 nicm 952: xfree(c->prompt_buffer);
953:
954: if (c->prompt_hindex != 0) {
955: c->prompt_hindex--;
956: c->prompt_buffer = xstrdup(ARRAY_ITEM(
957: &c->prompt_hdata, ARRAY_LENGTH(
958: &c->prompt_hdata) - 1 - c->prompt_hindex));
959: } else
960: c->prompt_buffer = xstrdup("");
961:
962: c->prompt_index = strlen(c->prompt_buffer);
963: c->flags |= CLIENT_STATUS;
964: break;
1.20 nicm 965: case MODEKEYEDIT_PASTE:
1.1 nicm 966: if ((pb = paste_get_top(&c->session->buffers)) == NULL)
967: break;
1.32 nicm 968: for (n = 0; n < pb->size; n++) {
969: if (pb->data[n] < 32 || pb->data[n] == 127)
970: break;
971: }
1.1 nicm 972:
973: c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1);
974: if (c->prompt_index == size) {
975: memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
976: c->prompt_index += n;
977: c->prompt_buffer[c->prompt_index] = '\0';
978: } else {
979: memmove(c->prompt_buffer + c->prompt_index + n,
980: c->prompt_buffer + c->prompt_index,
981: size + 1 - c->prompt_index);
982: memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
983: c->prompt_index += n;
984: }
985:
986: c->flags |= CLIENT_STATUS;
1.30 nicm 987: break;
988: case MODEKEYEDIT_TRANSPOSECHARS:
989: idx = c->prompt_index;
990: if (idx < size)
991: idx++;
992: if (idx >= 2) {
993: swapc = c->prompt_buffer[idx - 2];
994: c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1];
995: c->prompt_buffer[idx - 1] = swapc;
996: c->prompt_index = idx;
997: c->flags |= CLIENT_STATUS;
998: }
1.1 nicm 999: break;
1.20 nicm 1000: case MODEKEYEDIT_ENTER:
1.25 nicm 1001: if (*c->prompt_buffer != '\0')
1.1 nicm 1002: status_prompt_add_history(c);
1.25 nicm 1003: if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0)
1004: status_prompt_clear(c);
1005: break;
1.20 nicm 1006: case MODEKEYEDIT_CANCEL:
1.12 nicm 1007: if (c->prompt_callbackfn(c->prompt_data, NULL) == 0)
1.1 nicm 1008: status_prompt_clear(c);
1009: break;
1.20 nicm 1010: case MODEKEY_OTHER:
1.43 nicm 1011: if (key < 32 || key == 127)
1.1 nicm 1012: break;
1013: c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2);
1014:
1015: if (c->prompt_index == size) {
1016: c->prompt_buffer[c->prompt_index++] = key;
1017: c->prompt_buffer[c->prompt_index] = '\0';
1018: } else {
1019: memmove(c->prompt_buffer + c->prompt_index + 1,
1020: c->prompt_buffer + c->prompt_index,
1021: size + 1 - c->prompt_index);
1022: c->prompt_buffer[c->prompt_index++] = key;
1023: }
1024:
1025: if (c->prompt_flags & PROMPT_SINGLE) {
1.12 nicm 1026: if (c->prompt_callbackfn(
1.1 nicm 1027: c->prompt_data, c->prompt_buffer) == 0)
1028: status_prompt_clear(c);
1029: }
1030:
1031: c->flags |= CLIENT_STATUS;
1032: break;
1033: default:
1034: break;
1035: }
1036: }
1037:
1038: /* Add line to the history. */
1039: void
1040: status_prompt_add_history(struct client *c)
1041: {
1042: if (ARRAY_LENGTH(&c->prompt_hdata) > 0 &&
1043: strcmp(ARRAY_LAST(&c->prompt_hdata), c->prompt_buffer) == 0)
1044: return;
1045:
1046: if (ARRAY_LENGTH(&c->prompt_hdata) == PROMPT_HISTORY) {
1047: xfree(ARRAY_FIRST(&c->prompt_hdata));
1048: ARRAY_REMOVE(&c->prompt_hdata, 0);
1049: }
1050:
1051: ARRAY_ADD(&c->prompt_hdata, xstrdup(c->prompt_buffer));
1052: }
1053:
1054: /* Complete word. */
1055: char *
1056: status_prompt_complete(const char *s)
1057: {
1058: const struct cmd_entry **cmdent;
1059: const struct set_option_entry *optent;
1060: ARRAY_DECL(, const char *) list;
1061: char *prefix, *s2;
1.9 nicm 1062: u_int i;
1.1 nicm 1063: size_t j;
1064:
1065: if (*s == '\0')
1066: return (NULL);
1067:
1068: /* First, build a list of all the possible matches. */
1069: ARRAY_INIT(&list);
1070: for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1071: if (strncmp((*cmdent)->name, s, strlen(s)) == 0)
1072: ARRAY_ADD(&list, (*cmdent)->name);
1073: }
1.9 nicm 1074: for (optent = set_option_table; optent->name != NULL; optent++) {
1.1 nicm 1075: if (strncmp(optent->name, s, strlen(s)) == 0)
1076: ARRAY_ADD(&list, optent->name);
1077: }
1.9 nicm 1078: for (optent = set_window_option_table; optent->name != NULL; optent++) {
1.1 nicm 1079: if (strncmp(optent->name, s, strlen(s)) == 0)
1080: ARRAY_ADD(&list, optent->name);
1081: }
1082:
1083: /* If none, bail now. */
1084: if (ARRAY_LENGTH(&list) == 0) {
1085: ARRAY_FREE(&list);
1086: return (NULL);
1087: }
1088:
1089: /* If an exact match, return it, with a trailing space. */
1090: if (ARRAY_LENGTH(&list) == 1) {
1091: xasprintf(&s2, "%s ", ARRAY_FIRST(&list));
1092: ARRAY_FREE(&list);
1093: return (s2);
1094: }
1095:
1096: /* Now loop through the list and find the longest common prefix. */
1097: prefix = xstrdup(ARRAY_FIRST(&list));
1098: for (i = 1; i < ARRAY_LENGTH(&list); i++) {
1099: s = ARRAY_ITEM(&list, i);
1100:
1101: j = strlen(s);
1102: if (j > strlen(prefix))
1103: j = strlen(prefix);
1104: for (; j > 0; j--) {
1105: if (prefix[j - 1] != s[j - 1])
1106: prefix[j - 1] = '\0';
1107: }
1108: }
1109:
1110: ARRAY_FREE(&list);
1111: return (prefix);
1112: }