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