Annotation of src/usr.bin/tmux/status.c, Revision 1.43
1.43 ! nicm 1: /* $OpenBSD: status.c,v 1.42 2009/11/04 23:29:42 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: {
569: struct timeval tv;
1.10 nicm 570: va_list ap;
1.1 nicm 571: int delay;
572:
1.12 nicm 573: status_prompt_clear(c);
574: status_message_clear(c);
575:
1.28 nicm 576: va_start(ap, fmt);
577: xvasprintf(&c->message_string, fmt, ap);
578: va_end(ap);
579:
1.1 nicm 580: delay = options_get_number(&c->session->options, "display-time");
581: tv.tv_sec = delay / 1000;
582: tv.tv_usec = (delay % 1000) * 1000L;
1.42 nicm 583:
584: evtimer_del(&c->message_timer);
585: evtimer_set(&c->message_timer, status_message_callback, c);
586: evtimer_add(&c->message_timer, &tv);
1.1 nicm 587:
588: c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
589: c->flags |= CLIENT_STATUS;
590: }
591:
592: void
593: status_message_clear(struct client *c)
594: {
595: if (c->message_string == NULL)
596: return;
597:
598: xfree(c->message_string);
599: c->message_string = NULL;
600:
601: c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
1.12 nicm 602: c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
1.7 nicm 603:
604: screen_reinit(&c->status);
1.42 nicm 605: }
606:
607: void
608: status_message_callback(unused int fd, unused short event, void *data)
609: {
610: struct client *c = data;
611:
612: status_message_clear(c);
1.1 nicm 613: }
614:
615: /* Draw client message on status line of present else on last line. */
616: int
617: status_message_redraw(struct client *c)
618: {
619: struct screen_write_ctx ctx;
620: struct session *s = c->session;
621: struct screen old_status;
622: size_t len;
623: struct grid_cell gc;
624:
625: if (c->tty.sx == 0 || c->tty.sy == 0)
626: return (0);
627: memcpy(&old_status, &c->status, sizeof old_status);
628: screen_init(&c->status, c->tty.sx, 1, 0);
629:
630: len = strlen(c->message_string);
631: if (len > c->tty.sx)
632: len = c->tty.sx;
633:
634: memcpy(&gc, &grid_default_cell, sizeof gc);
1.33 nicm 635: colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
636: colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
1.1 nicm 637: gc.attr |= options_get_number(&s->options, "message-attr");
638:
639: screen_write_start(&ctx, NULL, &c->status);
640:
641: screen_write_cursormove(&ctx, 0, 0);
642: screen_write_puts(&ctx, &gc, "%.*s", (int) len, c->message_string);
643: for (; len < c->tty.sx; len++)
644: screen_write_putc(&ctx, &gc, ' ');
645:
646: screen_write_stop(&ctx);
647:
648: if (grid_compare(c->status.grid, old_status.grid) == 0) {
649: screen_free(&old_status);
650: return (0);
651: }
652: screen_free(&old_status);
653: return (1);
654: }
655:
656: void
1.12 nicm 657: status_prompt_set(struct client *c, const char *msg,
658: int (*callbackfn)(void *, const char *), void (*freefn)(void *),
659: void *data, int flags)
1.1 nicm 660: {
1.20 nicm 661: int keys;
662:
1.12 nicm 663: status_message_clear(c);
664: status_prompt_clear(c);
665:
1.1 nicm 666: c->prompt_string = xstrdup(msg);
667:
668: c->prompt_buffer = xstrdup("");
669: c->prompt_index = 0;
670:
1.12 nicm 671: c->prompt_callbackfn = callbackfn;
672: c->prompt_freefn = freefn;
1.1 nicm 673: c->prompt_data = data;
674:
675: c->prompt_hindex = 0;
676:
677: c->prompt_flags = flags;
678:
1.20 nicm 679: keys = options_get_number(&c->session->options, "status-keys");
680: if (keys == MODEKEY_EMACS)
1.21 nicm 681: mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit);
1.20 nicm 682: else
1.21 nicm 683: mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit);
1.1 nicm 684:
685: c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
686: c->flags |= CLIENT_STATUS;
687: }
688:
689: void
690: status_prompt_clear(struct client *c)
691: {
1.12 nicm 692: if (c->prompt_string == NULL)
1.1 nicm 693: return;
694:
1.12 nicm 695: if (c->prompt_freefn != NULL && c->prompt_data != NULL)
696: c->prompt_freefn(c->prompt_data);
1.1 nicm 697:
698: xfree(c->prompt_string);
699: c->prompt_string = NULL;
700:
701: xfree(c->prompt_buffer);
702: c->prompt_buffer = NULL;
703:
704: c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
1.12 nicm 705: c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
1.7 nicm 706:
707: screen_reinit(&c->status);
1.27 nicm 708: }
709:
710: void
711: status_prompt_update(struct client *c, const char *msg)
712: {
713: xfree(c->prompt_string);
714: c->prompt_string = xstrdup(msg);
715:
716: *c->prompt_buffer = '\0';
717: c->prompt_index = 0;
718:
719: c->prompt_hindex = 0;
720:
721: c->flags |= CLIENT_STATUS;
1.1 nicm 722: }
723:
724: /* Draw client prompt on status line of present else on last line. */
725: int
726: status_prompt_redraw(struct client *c)
727: {
728: struct screen_write_ctx ctx;
729: struct session *s = c->session;
730: struct screen old_status;
1.29 nicm 731: size_t i, size, left, len, off;
1.1 nicm 732: char ch;
733: struct grid_cell gc;
734:
735: if (c->tty.sx == 0 || c->tty.sy == 0)
736: return (0);
737: memcpy(&old_status, &c->status, sizeof old_status);
738: screen_init(&c->status, c->tty.sx, 1, 0);
1.17 nicm 739: off = 0;
1.1 nicm 740:
741: len = strlen(c->prompt_string);
742: if (len > c->tty.sx)
743: len = c->tty.sx;
744:
745: memcpy(&gc, &grid_default_cell, sizeof gc);
1.33 nicm 746: colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
747: colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
1.1 nicm 748: gc.attr |= options_get_number(&s->options, "message-attr");
749:
750: screen_write_start(&ctx, NULL, &c->status);
751:
752: screen_write_cursormove(&ctx, 0, 0);
753: screen_write_puts(&ctx, &gc, "%.*s", (int) len, c->prompt_string);
754:
755: left = c->tty.sx - len;
756: if (left != 0) {
757: if (c->prompt_index < left)
758: size = strlen(c->prompt_buffer);
759: else {
1.17 nicm 760: off = c->prompt_index - left + 1;
1.1 nicm 761: if (c->prompt_index == strlen(c->prompt_buffer))
762: left--;
763: size = left;
764: }
1.36 nicm 765: screen_write_puts(
766: &ctx, &gc, "%.*s", (int) left, c->prompt_buffer + off);
1.1 nicm 767:
768: for (i = len + size; i < c->tty.sx; i++)
769: screen_write_putc(&ctx, &gc, ' ');
770:
1.17 nicm 771: /* Draw a fake cursor. */
1.29 nicm 772: ch = ' ';
1.36 nicm 773: screen_write_cursormove(&ctx, len + c->prompt_index - off, 0);
774: if (c->prompt_index < strlen(c->prompt_buffer))
775: ch = c->prompt_buffer[c->prompt_index];
1.17 nicm 776: gc.attr ^= GRID_ATTR_REVERSE;
777: screen_write_putc(&ctx, &gc, ch);
1.1 nicm 778: }
779:
780: screen_write_stop(&ctx);
781:
782: if (grid_compare(c->status.grid, old_status.grid) == 0) {
783: screen_free(&old_status);
784: return (0);
785: }
786: screen_free(&old_status);
787: return (1);
788: }
789:
790: /* Handle keys in prompt. */
791: void
792: status_prompt_key(struct client *c, int key)
793: {
794: struct paste_buffer *pb;
1.30 nicm 795: char *s, *first, *last, word[64], swapc;
1.1 nicm 796: size_t size, n, off, idx;
797:
798: size = strlen(c->prompt_buffer);
799: switch (mode_key_lookup(&c->prompt_mdata, key)) {
1.20 nicm 800: case MODEKEYEDIT_CURSORLEFT:
1.1 nicm 801: if (c->prompt_index > 0) {
802: c->prompt_index--;
803: c->flags |= CLIENT_STATUS;
804: }
805: break;
1.20 nicm 806: case MODEKEYEDIT_SWITCHMODEAPPEND:
807: case MODEKEYEDIT_CURSORRIGHT:
1.1 nicm 808: if (c->prompt_index < size) {
809: c->prompt_index++;
810: c->flags |= CLIENT_STATUS;
811: }
812: break;
1.20 nicm 813: case MODEKEYEDIT_STARTOFLINE:
1.1 nicm 814: if (c->prompt_index != 0) {
815: c->prompt_index = 0;
816: c->flags |= CLIENT_STATUS;
817: }
818: break;
1.20 nicm 819: case MODEKEYEDIT_ENDOFLINE:
1.1 nicm 820: if (c->prompt_index != size) {
821: c->prompt_index = size;
822: c->flags |= CLIENT_STATUS;
823: }
824: break;
1.20 nicm 825: case MODEKEYEDIT_COMPLETE:
1.1 nicm 826: if (*c->prompt_buffer == '\0')
827: break;
828:
829: idx = c->prompt_index;
830: if (idx != 0)
831: idx--;
832:
833: /* Find the word we are in. */
834: first = c->prompt_buffer + idx;
835: while (first > c->prompt_buffer && *first != ' ')
836: first--;
837: while (*first == ' ')
838: first++;
839: last = c->prompt_buffer + idx;
840: while (*last != '\0' && *last != ' ')
841: last++;
842: while (*last == ' ')
843: last--;
844: if (*last != '\0')
845: last++;
846: if (last <= first ||
847: ((size_t) (last - first)) > (sizeof word) - 1)
848: break;
849: memcpy(word, first, last - first);
850: word[last - first] = '\0';
851:
852: /* And try to complete it. */
853: if ((s = status_prompt_complete(word)) == NULL)
854: break;
855:
856: /* Trim out word. */
857: n = size - (last - c->prompt_buffer) + 1; /* with \0 */
858: memmove(first, last, n);
859: size -= last - first;
860:
861: /* Insert the new word. */
862: size += strlen(s);
863: off = first - c->prompt_buffer;
864: c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1);
865: first = c->prompt_buffer + off;
866: memmove(first + strlen(s), first, n);
867: memcpy(first, s, strlen(s));
868:
869: c->prompt_index = (first - c->prompt_buffer) + strlen(s);
1.22 nicm 870: xfree(s);
1.1 nicm 871:
872: c->flags |= CLIENT_STATUS;
873: break;
1.20 nicm 874: case MODEKEYEDIT_BACKSPACE:
1.1 nicm 875: if (c->prompt_index != 0) {
876: if (c->prompt_index == size)
877: c->prompt_buffer[--c->prompt_index] = '\0';
878: else {
879: memmove(c->prompt_buffer + c->prompt_index - 1,
880: c->prompt_buffer + c->prompt_index,
881: size + 1 - c->prompt_index);
882: c->prompt_index--;
883: }
884: c->flags |= CLIENT_STATUS;
885: }
886: break;
1.20 nicm 887: case MODEKEYEDIT_DELETE:
1.1 nicm 888: if (c->prompt_index != size) {
889: memmove(c->prompt_buffer + c->prompt_index,
890: c->prompt_buffer + c->prompt_index + 1,
891: size + 1 - c->prompt_index);
1.18 nicm 892: c->flags |= CLIENT_STATUS;
893: }
1.26 nicm 894: break;
895: case MODEKEYEDIT_DELETELINE:
896: *c->prompt_buffer = '\0';
897: c->prompt_index = 0;
898: c->flags |= CLIENT_STATUS;
1.18 nicm 899: break;
1.20 nicm 900: case MODEKEYEDIT_DELETETOENDOFLINE:
1.18 nicm 901: if (c->prompt_index < size) {
902: c->prompt_buffer[c->prompt_index] = '\0';
1.1 nicm 903: c->flags |= CLIENT_STATUS;
904: }
905: break;
1.20 nicm 906: case MODEKEYEDIT_HISTORYUP:
1.1 nicm 907: if (ARRAY_LENGTH(&c->prompt_hdata) == 0)
908: break;
909: xfree(c->prompt_buffer);
910:
911: c->prompt_buffer = xstrdup(ARRAY_ITEM(&c->prompt_hdata,
912: ARRAY_LENGTH(&c->prompt_hdata) - 1 - c->prompt_hindex));
913: if (c->prompt_hindex != ARRAY_LENGTH(&c->prompt_hdata) - 1)
914: c->prompt_hindex++;
915:
916: c->prompt_index = strlen(c->prompt_buffer);
917: c->flags |= CLIENT_STATUS;
918: break;
1.20 nicm 919: case MODEKEYEDIT_HISTORYDOWN:
1.1 nicm 920: xfree(c->prompt_buffer);
921:
922: if (c->prompt_hindex != 0) {
923: c->prompt_hindex--;
924: c->prompt_buffer = xstrdup(ARRAY_ITEM(
925: &c->prompt_hdata, ARRAY_LENGTH(
926: &c->prompt_hdata) - 1 - c->prompt_hindex));
927: } else
928: c->prompt_buffer = xstrdup("");
929:
930: c->prompt_index = strlen(c->prompt_buffer);
931: c->flags |= CLIENT_STATUS;
932: break;
1.20 nicm 933: case MODEKEYEDIT_PASTE:
1.1 nicm 934: if ((pb = paste_get_top(&c->session->buffers)) == NULL)
935: break;
1.32 nicm 936: for (n = 0; n < pb->size; n++) {
937: if (pb->data[n] < 32 || pb->data[n] == 127)
938: break;
939: }
1.1 nicm 940:
941: c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1);
942: if (c->prompt_index == size) {
943: memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
944: c->prompt_index += n;
945: c->prompt_buffer[c->prompt_index] = '\0';
946: } else {
947: memmove(c->prompt_buffer + c->prompt_index + n,
948: c->prompt_buffer + c->prompt_index,
949: size + 1 - c->prompt_index);
950: memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
951: c->prompt_index += n;
952: }
953:
954: c->flags |= CLIENT_STATUS;
1.30 nicm 955: break;
956: case MODEKEYEDIT_TRANSPOSECHARS:
957: idx = c->prompt_index;
958: if (idx < size)
959: idx++;
960: if (idx >= 2) {
961: swapc = c->prompt_buffer[idx - 2];
962: c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1];
963: c->prompt_buffer[idx - 1] = swapc;
964: c->prompt_index = idx;
965: c->flags |= CLIENT_STATUS;
966: }
1.1 nicm 967: break;
1.20 nicm 968: case MODEKEYEDIT_ENTER:
1.25 nicm 969: if (*c->prompt_buffer != '\0')
1.1 nicm 970: status_prompt_add_history(c);
1.25 nicm 971: if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0)
972: status_prompt_clear(c);
973: break;
1.20 nicm 974: case MODEKEYEDIT_CANCEL:
1.12 nicm 975: if (c->prompt_callbackfn(c->prompt_data, NULL) == 0)
1.1 nicm 976: status_prompt_clear(c);
977: break;
1.20 nicm 978: case MODEKEY_OTHER:
1.43 ! nicm 979: if (key < 32 || key == 127)
1.1 nicm 980: break;
981: c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2);
982:
983: if (c->prompt_index == size) {
984: c->prompt_buffer[c->prompt_index++] = key;
985: c->prompt_buffer[c->prompt_index] = '\0';
986: } else {
987: memmove(c->prompt_buffer + c->prompt_index + 1,
988: c->prompt_buffer + c->prompt_index,
989: size + 1 - c->prompt_index);
990: c->prompt_buffer[c->prompt_index++] = key;
991: }
992:
993: if (c->prompt_flags & PROMPT_SINGLE) {
1.12 nicm 994: if (c->prompt_callbackfn(
1.1 nicm 995: c->prompt_data, c->prompt_buffer) == 0)
996: status_prompt_clear(c);
997: }
998:
999: c->flags |= CLIENT_STATUS;
1000: break;
1001: default:
1002: break;
1003: }
1004: }
1005:
1006: /* Add line to the history. */
1007: void
1008: status_prompt_add_history(struct client *c)
1009: {
1010: if (ARRAY_LENGTH(&c->prompt_hdata) > 0 &&
1011: strcmp(ARRAY_LAST(&c->prompt_hdata), c->prompt_buffer) == 0)
1012: return;
1013:
1014: if (ARRAY_LENGTH(&c->prompt_hdata) == PROMPT_HISTORY) {
1015: xfree(ARRAY_FIRST(&c->prompt_hdata));
1016: ARRAY_REMOVE(&c->prompt_hdata, 0);
1017: }
1018:
1019: ARRAY_ADD(&c->prompt_hdata, xstrdup(c->prompt_buffer));
1020: }
1021:
1022: /* Complete word. */
1023: char *
1024: status_prompt_complete(const char *s)
1025: {
1026: const struct cmd_entry **cmdent;
1027: const struct set_option_entry *optent;
1028: ARRAY_DECL(, const char *) list;
1029: char *prefix, *s2;
1.9 nicm 1030: u_int i;
1.1 nicm 1031: size_t j;
1032:
1033: if (*s == '\0')
1034: return (NULL);
1035:
1036: /* First, build a list of all the possible matches. */
1037: ARRAY_INIT(&list);
1038: for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1039: if (strncmp((*cmdent)->name, s, strlen(s)) == 0)
1040: ARRAY_ADD(&list, (*cmdent)->name);
1041: }
1.9 nicm 1042: for (optent = set_option_table; optent->name != NULL; optent++) {
1.1 nicm 1043: if (strncmp(optent->name, s, strlen(s)) == 0)
1044: ARRAY_ADD(&list, optent->name);
1045: }
1.9 nicm 1046: for (optent = set_window_option_table; optent->name != NULL; optent++) {
1.1 nicm 1047: if (strncmp(optent->name, s, strlen(s)) == 0)
1048: ARRAY_ADD(&list, optent->name);
1049: }
1050:
1051: /* If none, bail now. */
1052: if (ARRAY_LENGTH(&list) == 0) {
1053: ARRAY_FREE(&list);
1054: return (NULL);
1055: }
1056:
1057: /* If an exact match, return it, with a trailing space. */
1058: if (ARRAY_LENGTH(&list) == 1) {
1059: xasprintf(&s2, "%s ", ARRAY_FIRST(&list));
1060: ARRAY_FREE(&list);
1061: return (s2);
1062: }
1063:
1064: /* Now loop through the list and find the longest common prefix. */
1065: prefix = xstrdup(ARRAY_FIRST(&list));
1066: for (i = 1; i < ARRAY_LENGTH(&list); i++) {
1067: s = ARRAY_ITEM(&list, i);
1068:
1069: j = strlen(s);
1070: if (j > strlen(prefix))
1071: j = strlen(prefix);
1072: for (; j > 0; j--) {
1073: if (prefix[j - 1] != s[j - 1])
1074: prefix[j - 1] = '\0';
1075: }
1076: }
1077:
1078: ARRAY_FREE(&list);
1079: return (prefix);
1080: }