Annotation of src/usr.bin/tmux/status.c, Revision 1.44
1.44 ! nicm 1: /* $OpenBSD: status.c,v 1.43 2009/11/17 13:30:07 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.1 nicm 110: &s->options, "status-left"), c->status_timer.tv_sec);
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.1 nicm 117: &s->options, "status-right"), c->status_timer.tv_sec);
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.38 nicm 322: status_replace(struct client *c, const char *fmt, time_t t)
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 '(':
358: if (ptr == NULL) {
1.38 nicm 359: ptr = status_job(c, &iptr);
1.1 nicm 360: if (ptr == NULL)
361: break;
362: savedptr = ptr;
363: }
364: /* FALLTHROUGH */
365: case 'H':
366: if (ptr == NULL) {
367: if (gethostname(tmp, sizeof tmp) != 0)
1.34 nicm 368: fatal("gethostname failed");
1.1 nicm 369: ptr = tmp;
370: }
371: /* FALLTHROUGH */
1.13 nicm 372: case 'I':
373: if (ptr == NULL) {
374: xsnprintf(tmp, sizeof tmp, "%d", wl->idx);
375: ptr = tmp;
376: }
377: /* FALLTHROUGH */
378: case 'P':
379: if (ptr == NULL) {
380: xsnprintf(tmp, sizeof tmp, "%u",
1.38 nicm 381: window_pane_index(wl->window,
382: wl->window->active));
1.13 nicm 383: ptr = tmp;
384: }
1.16 nicm 385: /* FALLTHROUGH */
1.1 nicm 386: case 'S':
387: if (ptr == NULL)
388: ptr = s->name;
389: /* FALLTHROUGH */
390: case 'T':
391: if (ptr == NULL)
392: ptr = wl->window->active->base.title;
1.13 nicm 393: /* FALLTHROUGH */
394: case 'W':
395: if (ptr == NULL)
396: ptr = wl->window->name;
1.1 nicm 397: len = strlen(ptr);
398: if ((size_t) n < len)
399: len = n;
400: if (optr + len >= out + (sizeof out) - 1)
401: break;
402: while (len > 0 && *ptr != '\0') {
403: *optr++ = *ptr++;
404: len--;
1.31 nicm 405: }
406: break;
407: case '[':
408: /*
409: * Embedded style, handled at display time.
410: * Leave present and skip input until ].
411: */
412: *optr++ = '#';
413:
414: iptr--; /* include [ */
415: while (*iptr != ']' && *iptr != '\0') {
416: if (optr >= out + (sizeof out) - 1)
417: break;
418: *optr++ = *iptr++;
1.1 nicm 419: }
420: break;
421: case '#':
422: *optr++ = '#';
423: break;
424: }
425: if (savedptr != NULL) {
426: xfree(savedptr);
427: savedptr = NULL;
428: }
429: break;
430: default:
431: *optr++ = ch;
432: break;
433: }
434: }
435: *optr = '\0';
436:
437: return (xstrdup(out));
438: }
439:
440: char *
1.38 nicm 441: status_job(struct client *c, char **iptr)
1.1 nicm 442: {
1.38 nicm 443: struct job *job;
1.40 nicm 444: char *cmd;
1.38 nicm 445: int lastesc;
446: size_t len;
1.1 nicm 447:
448: if (**iptr == '\0')
449: return (NULL);
450: if (**iptr == ')') { /* no command given */
451: (*iptr)++;
452: return (NULL);
453: }
454:
455: cmd = xmalloc(strlen(*iptr) + 1);
456: len = 0;
457:
458: lastesc = 0;
459: for (; **iptr != '\0'; (*iptr)++) {
460: if (!lastesc && **iptr == ')')
461: break; /* unescaped ) is the end */
462: if (!lastesc && **iptr == '\\') {
463: lastesc = 1;
464: continue; /* skip \ if not escaped */
465: }
466: lastesc = 0;
467: cmd[len++] = **iptr;
468: }
1.38 nicm 469: if (**iptr == '\0') /* no terminating ) */ {
470: xfree(cmd);
471: return (NULL);
472: }
1.1 nicm 473: (*iptr)++; /* skip final ) */
474: cmd[len] = '\0';
475:
1.38 nicm 476: job = job_get(&c->status_jobs, cmd);
477: if (job == NULL) {
1.39 nicm 478: job = job_add(&c->status_jobs,
479: JOB_PERSIST, c, cmd, status_job_callback, xfree, NULL);
1.38 nicm 480: job_run(job);
481: }
482: if (job->data == NULL)
483: return (xstrdup(""));
484: return (xstrdup(job->data));
485: }
1.1 nicm 486:
1.38 nicm 487: void
488: status_job_callback(struct job *job)
489: {
1.41 nicm 490: char *line, *buf;
1.38 nicm 491: size_t len;
492:
1.41 nicm 493: buf = NULL;
494: if ((line = evbuffer_readline(job->event->input)) == NULL) {
495: len = EVBUFFER_LENGTH(job->event->input);
496: buf = xmalloc(len + 1);
497: if (len != 0)
498: memcpy(buf, EVBUFFER_DATA(job->event->input), len);
499: buf[len] = '\0';
500: }
1.38 nicm 501:
502: if (job->data != NULL)
503: xfree(job->data);
504: else
505: server_redraw_client(job->client);
1.41 nicm 506: job->data = xstrdup(line);
1.1 nicm 507:
1.41 nicm 508: if (buf != NULL)
509: xfree(buf);
1.1 nicm 510: }
511:
512: size_t
513: status_width(struct winlink *wl)
514: {
515: return (xsnprintf(NULL, 0, "%d:%s ", wl->idx, wl->window->name));
516: }
517:
518: char *
519: status_print(struct session *s, struct winlink *wl, struct grid_cell *gc)
520: {
1.33 nicm 521: struct options *oo = &wl->window->options;
522: char *text, flag;
523: u_char fg, bg, attr;
1.1 nicm 524:
1.33 nicm 525: fg = options_get_number(oo, "window-status-fg");
1.1 nicm 526: if (fg != 8)
1.33 nicm 527: colour_set_fg(gc, fg);
528: bg = options_get_number(oo, "window-status-bg");
1.1 nicm 529: if (bg != 8)
1.33 nicm 530: colour_set_bg(gc, bg);
531: attr = options_get_number(oo, "window-status-attr");
1.1 nicm 532: if (attr != 0)
533: gc->attr = attr;
534:
535: flag = ' ';
1.37 nicm 536: if (wl == TAILQ_FIRST(&s->lastw))
1.1 nicm 537: flag = '-';
1.14 nicm 538: if (wl == s->curw) {
1.33 nicm 539: fg = options_get_number(oo, "window-status-current-fg");
1.14 nicm 540: if (fg != 8)
1.33 nicm 541: colour_set_fg(gc, fg);
542: bg = options_get_number(oo, "window-status-current-bg");
1.14 nicm 543: if (bg != 8)
1.33 nicm 544: colour_set_bg(gc, bg);
545: attr = options_get_number(oo, "window-status-current-attr");
1.14 nicm 546: if (attr != 0)
547: gc->attr = attr;
1.1 nicm 548: flag = '*';
1.14 nicm 549: }
1.1 nicm 550:
551: if (session_alert_has(s, wl, WINDOW_ACTIVITY)) {
552: flag = '#';
553: gc->attr ^= GRID_ATTR_REVERSE;
554: } else if (session_alert_has(s, wl, WINDOW_BELL)) {
555: flag = '!';
556: gc->attr ^= GRID_ATTR_REVERSE;
557: } else if (session_alert_has(s, wl, WINDOW_CONTENT)) {
558: flag = '+';
559: gc->attr ^= GRID_ATTR_REVERSE;
560: }
561:
562: xasprintf(&text, "%d:%s%c", wl->idx, wl->window->name, flag);
563: return (text);
564: }
565:
1.10 nicm 566: void printflike2
567: status_message_set(struct client *c, const char *fmt, ...)
1.1 nicm 568: {
1.44 ! nicm 569: struct timeval tv;
! 570: struct session *s = c->session;
! 571: struct message_entry *msg;
! 572: va_list ap;
! 573: int delay;
! 574: u_int i, limit;
1.1 nicm 575:
1.12 nicm 576: status_prompt_clear(c);
577: status_message_clear(c);
578:
1.28 nicm 579: va_start(ap, fmt);
580: xvasprintf(&c->message_string, fmt, ap);
581: va_end(ap);
582:
1.44 ! nicm 583: ARRAY_EXPAND(&c->message_log, 1);
! 584: msg = &ARRAY_LAST(&c->message_log);
! 585: msg->msg_time = time(NULL);
! 586: msg->msg = xstrdup(c->message_string);
! 587:
! 588: if (s == NULL)
! 589: limit = 0;
! 590: else
! 591: limit = options_get_number(&s->options, "message-limit");
! 592: for (i = ARRAY_LENGTH(&c->message_log); i > limit; i--) {
! 593: msg = &ARRAY_ITEM(&c->message_log, i - 1);
! 594: xfree(msg->msg);
! 595: ARRAY_REMOVE(&c->message_log, i - 1);
! 596: }
! 597:
1.1 nicm 598: delay = options_get_number(&c->session->options, "display-time");
599: tv.tv_sec = delay / 1000;
600: tv.tv_usec = (delay % 1000) * 1000L;
1.44 ! nicm 601:
1.42 nicm 602: evtimer_del(&c->message_timer);
603: evtimer_set(&c->message_timer, status_message_callback, c);
604: evtimer_add(&c->message_timer, &tv);
1.1 nicm 605:
606: c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
607: c->flags |= CLIENT_STATUS;
608: }
609:
610: void
611: status_message_clear(struct client *c)
612: {
613: if (c->message_string == NULL)
614: return;
615:
616: xfree(c->message_string);
617: c->message_string = NULL;
618:
619: c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
1.12 nicm 620: c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
1.7 nicm 621:
622: screen_reinit(&c->status);
1.42 nicm 623: }
624:
625: void
626: status_message_callback(unused int fd, unused short event, void *data)
627: {
628: struct client *c = data;
629:
630: status_message_clear(c);
1.1 nicm 631: }
632:
633: /* Draw client message on status line of present else on last line. */
634: int
635: status_message_redraw(struct client *c)
636: {
637: struct screen_write_ctx ctx;
638: struct session *s = c->session;
639: struct screen old_status;
640: size_t len;
641: struct grid_cell gc;
642:
643: if (c->tty.sx == 0 || c->tty.sy == 0)
644: return (0);
645: memcpy(&old_status, &c->status, sizeof old_status);
646: screen_init(&c->status, c->tty.sx, 1, 0);
647:
648: len = strlen(c->message_string);
649: if (len > c->tty.sx)
650: len = c->tty.sx;
651:
652: memcpy(&gc, &grid_default_cell, sizeof gc);
1.33 nicm 653: colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
654: colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
1.1 nicm 655: gc.attr |= options_get_number(&s->options, "message-attr");
656:
657: screen_write_start(&ctx, NULL, &c->status);
658:
659: screen_write_cursormove(&ctx, 0, 0);
660: screen_write_puts(&ctx, &gc, "%.*s", (int) len, c->message_string);
661: for (; len < c->tty.sx; len++)
662: screen_write_putc(&ctx, &gc, ' ');
663:
664: screen_write_stop(&ctx);
665:
666: if (grid_compare(c->status.grid, old_status.grid) == 0) {
667: screen_free(&old_status);
668: return (0);
669: }
670: screen_free(&old_status);
671: return (1);
672: }
673:
674: void
1.12 nicm 675: status_prompt_set(struct client *c, const char *msg,
676: int (*callbackfn)(void *, const char *), void (*freefn)(void *),
677: void *data, int flags)
1.1 nicm 678: {
1.20 nicm 679: int keys;
680:
1.12 nicm 681: status_message_clear(c);
682: status_prompt_clear(c);
683:
1.1 nicm 684: c->prompt_string = xstrdup(msg);
685:
686: c->prompt_buffer = xstrdup("");
687: c->prompt_index = 0;
688:
1.12 nicm 689: c->prompt_callbackfn = callbackfn;
690: c->prompt_freefn = freefn;
1.1 nicm 691: c->prompt_data = data;
692:
693: c->prompt_hindex = 0;
694:
695: c->prompt_flags = flags;
696:
1.20 nicm 697: keys = options_get_number(&c->session->options, "status-keys");
698: if (keys == MODEKEY_EMACS)
1.21 nicm 699: mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit);
1.20 nicm 700: else
1.21 nicm 701: mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit);
1.1 nicm 702:
703: c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
704: c->flags |= CLIENT_STATUS;
705: }
706:
707: void
708: status_prompt_clear(struct client *c)
709: {
1.12 nicm 710: if (c->prompt_string == NULL)
1.1 nicm 711: return;
712:
1.12 nicm 713: if (c->prompt_freefn != NULL && c->prompt_data != NULL)
714: c->prompt_freefn(c->prompt_data);
1.1 nicm 715:
716: xfree(c->prompt_string);
717: c->prompt_string = NULL;
718:
719: xfree(c->prompt_buffer);
720: c->prompt_buffer = NULL;
721:
722: c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
1.12 nicm 723: c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
1.7 nicm 724:
725: screen_reinit(&c->status);
1.27 nicm 726: }
727:
728: void
729: status_prompt_update(struct client *c, const char *msg)
730: {
731: xfree(c->prompt_string);
732: c->prompt_string = xstrdup(msg);
733:
734: *c->prompt_buffer = '\0';
735: c->prompt_index = 0;
736:
737: c->prompt_hindex = 0;
738:
739: c->flags |= CLIENT_STATUS;
1.1 nicm 740: }
741:
742: /* Draw client prompt on status line of present else on last line. */
743: int
744: status_prompt_redraw(struct client *c)
745: {
746: struct screen_write_ctx ctx;
747: struct session *s = c->session;
748: struct screen old_status;
1.29 nicm 749: size_t i, size, left, len, off;
1.1 nicm 750: char ch;
751: struct grid_cell gc;
752:
753: if (c->tty.sx == 0 || c->tty.sy == 0)
754: return (0);
755: memcpy(&old_status, &c->status, sizeof old_status);
756: screen_init(&c->status, c->tty.sx, 1, 0);
1.17 nicm 757: off = 0;
1.1 nicm 758:
759: len = strlen(c->prompt_string);
760: if (len > c->tty.sx)
761: len = c->tty.sx;
762:
763: memcpy(&gc, &grid_default_cell, sizeof gc);
1.33 nicm 764: colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
765: colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
1.1 nicm 766: gc.attr |= options_get_number(&s->options, "message-attr");
767:
768: screen_write_start(&ctx, NULL, &c->status);
769:
770: screen_write_cursormove(&ctx, 0, 0);
771: screen_write_puts(&ctx, &gc, "%.*s", (int) len, c->prompt_string);
772:
773: left = c->tty.sx - len;
774: if (left != 0) {
775: if (c->prompt_index < left)
776: size = strlen(c->prompt_buffer);
777: else {
1.17 nicm 778: off = c->prompt_index - left + 1;
1.1 nicm 779: if (c->prompt_index == strlen(c->prompt_buffer))
780: left--;
781: size = left;
782: }
1.36 nicm 783: screen_write_puts(
784: &ctx, &gc, "%.*s", (int) left, c->prompt_buffer + off);
1.1 nicm 785:
786: for (i = len + size; i < c->tty.sx; i++)
787: screen_write_putc(&ctx, &gc, ' ');
788:
1.17 nicm 789: /* Draw a fake cursor. */
1.29 nicm 790: ch = ' ';
1.36 nicm 791: screen_write_cursormove(&ctx, len + c->prompt_index - off, 0);
792: if (c->prompt_index < strlen(c->prompt_buffer))
793: ch = c->prompt_buffer[c->prompt_index];
1.17 nicm 794: gc.attr ^= GRID_ATTR_REVERSE;
795: screen_write_putc(&ctx, &gc, ch);
1.1 nicm 796: }
797:
798: screen_write_stop(&ctx);
799:
800: if (grid_compare(c->status.grid, old_status.grid) == 0) {
801: screen_free(&old_status);
802: return (0);
803: }
804: screen_free(&old_status);
805: return (1);
806: }
807:
808: /* Handle keys in prompt. */
809: void
810: status_prompt_key(struct client *c, int key)
811: {
812: struct paste_buffer *pb;
1.30 nicm 813: char *s, *first, *last, word[64], swapc;
1.1 nicm 814: size_t size, n, off, idx;
815:
816: size = strlen(c->prompt_buffer);
817: switch (mode_key_lookup(&c->prompt_mdata, key)) {
1.20 nicm 818: case MODEKEYEDIT_CURSORLEFT:
1.1 nicm 819: if (c->prompt_index > 0) {
820: c->prompt_index--;
821: c->flags |= CLIENT_STATUS;
822: }
823: break;
1.20 nicm 824: case MODEKEYEDIT_SWITCHMODEAPPEND:
825: case MODEKEYEDIT_CURSORRIGHT:
1.1 nicm 826: if (c->prompt_index < size) {
827: c->prompt_index++;
828: c->flags |= CLIENT_STATUS;
829: }
830: break;
1.20 nicm 831: case MODEKEYEDIT_STARTOFLINE:
1.1 nicm 832: if (c->prompt_index != 0) {
833: c->prompt_index = 0;
834: c->flags |= CLIENT_STATUS;
835: }
836: break;
1.20 nicm 837: case MODEKEYEDIT_ENDOFLINE:
1.1 nicm 838: if (c->prompt_index != size) {
839: c->prompt_index = size;
840: c->flags |= CLIENT_STATUS;
841: }
842: break;
1.20 nicm 843: case MODEKEYEDIT_COMPLETE:
1.1 nicm 844: if (*c->prompt_buffer == '\0')
845: break;
846:
847: idx = c->prompt_index;
848: if (idx != 0)
849: idx--;
850:
851: /* Find the word we are in. */
852: first = c->prompt_buffer + idx;
853: while (first > c->prompt_buffer && *first != ' ')
854: first--;
855: while (*first == ' ')
856: first++;
857: last = c->prompt_buffer + idx;
858: while (*last != '\0' && *last != ' ')
859: last++;
860: while (*last == ' ')
861: last--;
862: if (*last != '\0')
863: last++;
864: if (last <= first ||
865: ((size_t) (last - first)) > (sizeof word) - 1)
866: break;
867: memcpy(word, first, last - first);
868: word[last - first] = '\0';
869:
870: /* And try to complete it. */
871: if ((s = status_prompt_complete(word)) == NULL)
872: break;
873:
874: /* Trim out word. */
875: n = size - (last - c->prompt_buffer) + 1; /* with \0 */
876: memmove(first, last, n);
877: size -= last - first;
878:
879: /* Insert the new word. */
880: size += strlen(s);
881: off = first - c->prompt_buffer;
882: c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1);
883: first = c->prompt_buffer + off;
884: memmove(first + strlen(s), first, n);
885: memcpy(first, s, strlen(s));
886:
887: c->prompt_index = (first - c->prompt_buffer) + strlen(s);
1.22 nicm 888: xfree(s);
1.1 nicm 889:
890: c->flags |= CLIENT_STATUS;
891: break;
1.20 nicm 892: case MODEKEYEDIT_BACKSPACE:
1.1 nicm 893: if (c->prompt_index != 0) {
894: if (c->prompt_index == size)
895: c->prompt_buffer[--c->prompt_index] = '\0';
896: else {
897: memmove(c->prompt_buffer + c->prompt_index - 1,
898: c->prompt_buffer + c->prompt_index,
899: size + 1 - c->prompt_index);
900: c->prompt_index--;
901: }
902: c->flags |= CLIENT_STATUS;
903: }
904: break;
1.20 nicm 905: case MODEKEYEDIT_DELETE:
1.1 nicm 906: if (c->prompt_index != size) {
907: memmove(c->prompt_buffer + c->prompt_index,
908: c->prompt_buffer + c->prompt_index + 1,
909: size + 1 - c->prompt_index);
1.18 nicm 910: c->flags |= CLIENT_STATUS;
911: }
1.26 nicm 912: break;
913: case MODEKEYEDIT_DELETELINE:
914: *c->prompt_buffer = '\0';
915: c->prompt_index = 0;
916: c->flags |= CLIENT_STATUS;
1.18 nicm 917: break;
1.20 nicm 918: case MODEKEYEDIT_DELETETOENDOFLINE:
1.18 nicm 919: if (c->prompt_index < size) {
920: c->prompt_buffer[c->prompt_index] = '\0';
1.1 nicm 921: c->flags |= CLIENT_STATUS;
922: }
923: break;
1.20 nicm 924: case MODEKEYEDIT_HISTORYUP:
1.1 nicm 925: if (ARRAY_LENGTH(&c->prompt_hdata) == 0)
926: break;
927: xfree(c->prompt_buffer);
928:
929: c->prompt_buffer = xstrdup(ARRAY_ITEM(&c->prompt_hdata,
930: ARRAY_LENGTH(&c->prompt_hdata) - 1 - c->prompt_hindex));
931: if (c->prompt_hindex != ARRAY_LENGTH(&c->prompt_hdata) - 1)
932: c->prompt_hindex++;
933:
934: c->prompt_index = strlen(c->prompt_buffer);
935: c->flags |= CLIENT_STATUS;
936: break;
1.20 nicm 937: case MODEKEYEDIT_HISTORYDOWN:
1.1 nicm 938: xfree(c->prompt_buffer);
939:
940: if (c->prompt_hindex != 0) {
941: c->prompt_hindex--;
942: c->prompt_buffer = xstrdup(ARRAY_ITEM(
943: &c->prompt_hdata, ARRAY_LENGTH(
944: &c->prompt_hdata) - 1 - c->prompt_hindex));
945: } else
946: c->prompt_buffer = xstrdup("");
947:
948: c->prompt_index = strlen(c->prompt_buffer);
949: c->flags |= CLIENT_STATUS;
950: break;
1.20 nicm 951: case MODEKEYEDIT_PASTE:
1.1 nicm 952: if ((pb = paste_get_top(&c->session->buffers)) == NULL)
953: break;
1.32 nicm 954: for (n = 0; n < pb->size; n++) {
955: if (pb->data[n] < 32 || pb->data[n] == 127)
956: break;
957: }
1.1 nicm 958:
959: c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1);
960: if (c->prompt_index == size) {
961: memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
962: c->prompt_index += n;
963: c->prompt_buffer[c->prompt_index] = '\0';
964: } else {
965: memmove(c->prompt_buffer + c->prompt_index + n,
966: c->prompt_buffer + c->prompt_index,
967: size + 1 - c->prompt_index);
968: memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
969: c->prompt_index += n;
970: }
971:
972: c->flags |= CLIENT_STATUS;
1.30 nicm 973: break;
974: case MODEKEYEDIT_TRANSPOSECHARS:
975: idx = c->prompt_index;
976: if (idx < size)
977: idx++;
978: if (idx >= 2) {
979: swapc = c->prompt_buffer[idx - 2];
980: c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1];
981: c->prompt_buffer[idx - 1] = swapc;
982: c->prompt_index = idx;
983: c->flags |= CLIENT_STATUS;
984: }
1.1 nicm 985: break;
1.20 nicm 986: case MODEKEYEDIT_ENTER:
1.25 nicm 987: if (*c->prompt_buffer != '\0')
1.1 nicm 988: status_prompt_add_history(c);
1.25 nicm 989: if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0)
990: status_prompt_clear(c);
991: break;
1.20 nicm 992: case MODEKEYEDIT_CANCEL:
1.12 nicm 993: if (c->prompt_callbackfn(c->prompt_data, NULL) == 0)
1.1 nicm 994: status_prompt_clear(c);
995: break;
1.20 nicm 996: case MODEKEY_OTHER:
1.43 nicm 997: if (key < 32 || key == 127)
1.1 nicm 998: break;
999: c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2);
1000:
1001: if (c->prompt_index == size) {
1002: c->prompt_buffer[c->prompt_index++] = key;
1003: c->prompt_buffer[c->prompt_index] = '\0';
1004: } else {
1005: memmove(c->prompt_buffer + c->prompt_index + 1,
1006: c->prompt_buffer + c->prompt_index,
1007: size + 1 - c->prompt_index);
1008: c->prompt_buffer[c->prompt_index++] = key;
1009: }
1010:
1011: if (c->prompt_flags & PROMPT_SINGLE) {
1.12 nicm 1012: if (c->prompt_callbackfn(
1.1 nicm 1013: c->prompt_data, c->prompt_buffer) == 0)
1014: status_prompt_clear(c);
1015: }
1016:
1017: c->flags |= CLIENT_STATUS;
1018: break;
1019: default:
1020: break;
1021: }
1022: }
1023:
1024: /* Add line to the history. */
1025: void
1026: status_prompt_add_history(struct client *c)
1027: {
1028: if (ARRAY_LENGTH(&c->prompt_hdata) > 0 &&
1029: strcmp(ARRAY_LAST(&c->prompt_hdata), c->prompt_buffer) == 0)
1030: return;
1031:
1032: if (ARRAY_LENGTH(&c->prompt_hdata) == PROMPT_HISTORY) {
1033: xfree(ARRAY_FIRST(&c->prompt_hdata));
1034: ARRAY_REMOVE(&c->prompt_hdata, 0);
1035: }
1036:
1037: ARRAY_ADD(&c->prompt_hdata, xstrdup(c->prompt_buffer));
1038: }
1039:
1040: /* Complete word. */
1041: char *
1042: status_prompt_complete(const char *s)
1043: {
1044: const struct cmd_entry **cmdent;
1045: const struct set_option_entry *optent;
1046: ARRAY_DECL(, const char *) list;
1047: char *prefix, *s2;
1.9 nicm 1048: u_int i;
1.1 nicm 1049: size_t j;
1050:
1051: if (*s == '\0')
1052: return (NULL);
1053:
1054: /* First, build a list of all the possible matches. */
1055: ARRAY_INIT(&list);
1056: for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1057: if (strncmp((*cmdent)->name, s, strlen(s)) == 0)
1058: ARRAY_ADD(&list, (*cmdent)->name);
1059: }
1.9 nicm 1060: for (optent = set_option_table; optent->name != NULL; optent++) {
1.1 nicm 1061: if (strncmp(optent->name, s, strlen(s)) == 0)
1062: ARRAY_ADD(&list, optent->name);
1063: }
1.9 nicm 1064: for (optent = set_window_option_table; optent->name != NULL; optent++) {
1.1 nicm 1065: if (strncmp(optent->name, s, strlen(s)) == 0)
1066: ARRAY_ADD(&list, optent->name);
1067: }
1068:
1069: /* If none, bail now. */
1070: if (ARRAY_LENGTH(&list) == 0) {
1071: ARRAY_FREE(&list);
1072: return (NULL);
1073: }
1074:
1075: /* If an exact match, return it, with a trailing space. */
1076: if (ARRAY_LENGTH(&list) == 1) {
1077: xasprintf(&s2, "%s ", ARRAY_FIRST(&list));
1078: ARRAY_FREE(&list);
1079: return (s2);
1080: }
1081:
1082: /* Now loop through the list and find the longest common prefix. */
1083: prefix = xstrdup(ARRAY_FIRST(&list));
1084: for (i = 1; i < ARRAY_LENGTH(&list); i++) {
1085: s = ARRAY_ITEM(&list, i);
1086:
1087: j = strlen(s);
1088: if (j > strlen(prefix))
1089: j = strlen(prefix);
1090: for (; j > 0; j--) {
1091: if (prefix[j - 1] != s[j - 1])
1092: prefix[j - 1] = '\0';
1093: }
1094: }
1095:
1096: ARRAY_FREE(&list);
1097: return (prefix);
1098: }