Annotation of src/usr.bin/tmux/status.c, Revision 1.2
1.2 ! nicm 1: /* $OpenBSD: status.c,v 1.1 2009/06/01 22:58:49 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:
32: char *status_replace(struct session *, char *, time_t);
33: char *status_replace_popen(char **);
34: size_t status_width(struct winlink *);
35: char *status_print(struct session *, struct winlink *, struct grid_cell *);
36:
37: void status_prompt_add_history(struct client *);
38: char *status_prompt_complete(const char *);
39:
40: /* Draw status for client on the last lines of given context. */
41: int
42: status_redraw(struct client *c)
43: {
44: struct screen_write_ctx ctx;
45: struct session *s = c->session;
46: struct winlink *wl;
47: struct window_pane *wp;
48: struct screen *sc = NULL, old_status;
49: char *left, *right, *text, *ptr;
1.2 ! nicm 50: size_t llen, llen2, rlen, rlen2, offset;
! 51: size_t xx, yy, sy, size, start, width;
1.1 nicm 52: struct grid_cell stdgc, gc;
53: int larrow, rarrow;
54:
55: left = right = NULL;
56:
57: /* Create the target screen. */
58: memcpy(&old_status, &c->status, sizeof old_status);
59: screen_init(&c->status, c->tty.sx, 1, 0);
60:
61: /* No status line? */
62: if (c->tty.sy == 0 || !options_get_number(&s->options, "status"))
63: goto off;
64: larrow = rarrow = 0;
65:
66: if (gettimeofday(&c->status_timer, NULL) != 0)
67: fatal("gettimeofday");
68: memcpy(&stdgc, &grid_default_cell, sizeof gc);
69: stdgc.bg = options_get_number(&s->options, "status-fg");
70: stdgc.fg = options_get_number(&s->options, "status-bg");
71: stdgc.attr |= options_get_number(&s->options, "status-attr");
72:
73: yy = c->tty.sy - 1;
74: if (yy == 0)
75: goto blank;
76:
77: /* Work out the left and right strings. */
78: left = status_replace(s, options_get_string(
79: &s->options, "status-left"), c->status_timer.tv_sec);
80: llen = options_get_number(&s->options, "status-left-length");
1.2 ! nicm 81: llen2 = screen_write_strlen("%s", left);
! 82: if (llen2 < llen)
! 83: llen = llen2;
1.1 nicm 84:
85: right = status_replace(s, options_get_string(
86: &s->options, "status-right"), c->status_timer.tv_sec);
87: rlen = options_get_number(&s->options, "status-right-length");
1.2 ! nicm 88: rlen2 = screen_write_strlen("%s", right);
! 89: if (rlen2 < rlen)
! 90: rlen = rlen2;
1.1 nicm 91: right[rlen] = '\0';
92:
93: /*
94: * Figure out how much space we have for the window list. If there isn't
95: * enough space, just wimp out.
96: */
97: xx = 0;
98: if (llen != 0)
99: xx += llen + 1;
100: if (rlen != 0)
101: xx += rlen + 1;
102: if (c->tty.sx == 0 || c->tty.sx <= xx)
103: goto blank;
104: xx = c->tty.sx - xx;
105:
106: /*
107: * Right. We have xx characters to fill. Find out how much is to go in
108: * them and the offset of the current window (it must be on screen).
109: */
110: width = offset = 0;
111: RB_FOREACH(wl, winlinks, &s->windows) {
112: size = status_width(wl) + 1;
113: if (wl == s->curw)
114: offset = width;
115: width += size;
116: }
117: start = 0;
118:
119: /* If there is enough space for the total width, all is gravy. */
120: if (width <= xx)
121: goto draw;
122:
123: /* Find size of current window text. */
124: size = status_width(s->curw);
125:
126: /*
127: * If the offset is already on screen, we're good to draw from the
128: * start and just leave off the end.
129: */
130: if (offset + size < xx) {
131: if (xx > 0) {
132: rarrow = 1;
133: xx--;
134: }
135:
136: width = xx;
137: goto draw;
138: }
139:
140: /*
141: * Work out how many characters we need to omit from the start. There
142: * are xx characters to fill, and offset + size must be the last. So,
143: * the start character is offset + size - xx.
144: */
145: if (xx > 0) {
146: larrow = 1;
147: xx--;
148: }
149:
150: start = offset + size - xx;
151: if (xx > 0 && width > start + xx + 1) { /* + 1, eh? */
152: rarrow = 1;
153: start++;
154: xx--;
155: }
156: width = xx;
157:
158: draw:
159: /* Bail here if anything is too small too. XXX. */
160: if (width == 0 || xx == 0)
161: goto blank;
162:
163: /* Begin drawing and move to the starting position. */
164: screen_write_start(&ctx, NULL, &c->status);
165: if (llen != 0) {
166: screen_write_cursormove(&ctx, 0, yy);
1.2 ! nicm 167: screen_write_nputs(&ctx, llen + 1, &stdgc, "%s ", left);
1.1 nicm 168: if (larrow)
169: screen_write_putc(&ctx, &stdgc, ' ');
170: } else {
171: if (larrow)
172: screen_write_cursormove(&ctx, 1, yy);
173: else
174: screen_write_cursormove(&ctx, 0, yy);
175: }
176:
177: /* Draw each character in succession. */
178: offset = 0;
179: RB_FOREACH(wl, winlinks, &s->windows) {
180: memcpy(&gc, &stdgc, sizeof gc);
181: text = status_print(s, wl, &gc);
182:
183: if (larrow == 1 && offset < start) {
184: if (session_alert_has(s, wl, WINDOW_ACTIVITY))
185: larrow = -1;
186: else if (session_alert_has(s, wl, WINDOW_BELL))
187: larrow = -1;
188: else if (session_alert_has(s, wl, WINDOW_CONTENT))
189: larrow = -1;
190: }
191:
192: for (ptr = text; *ptr != '\0'; ptr++) {
193: if (offset >= start && offset < start + width)
194: screen_write_putc(&ctx, &gc, *ptr);
195: offset++;
196: }
197:
198: if (rarrow == 1 && offset > start + width) {
199: if (session_alert_has(s, wl, WINDOW_ACTIVITY))
200: rarrow = -1;
201: else if (session_alert_has(s, wl, WINDOW_BELL))
202: rarrow = -1;
203: else if (session_alert_has(s, wl, WINDOW_CONTENT))
204: rarrow = -1;
205: }
206:
207: if (offset < start + width) {
208: if (offset >= start) {
209: screen_write_putc(&ctx, &stdgc, ' ');
210: }
211: offset++;
212: }
213:
214: xfree(text);
215: }
216:
217: /* Fill the remaining space if any. */
218: while (offset++ < xx)
219: screen_write_putc(&ctx, &stdgc, ' ');
220:
221: /* Draw the last item. */
222: if (rlen != 0) {
223: screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, yy);
1.2 ! nicm 224: screen_write_nputs(&ctx, rlen + 1, &stdgc, " %s", right);
1.1 nicm 225: }
226:
227: /* Draw the arrows. */
228: if (larrow != 0) {
229: memcpy(&gc, &stdgc, sizeof gc);
230: if (larrow == -1)
231: gc.attr ^= GRID_ATTR_REVERSE;
232: if (llen != 0)
233: screen_write_cursormove(&ctx, llen + 1, yy);
234: else
235: screen_write_cursormove(&ctx, 0, yy);
236: screen_write_putc(&ctx, &gc, '<');
237: }
238: if (rarrow != 0) {
239: memcpy(&gc, &stdgc, sizeof gc);
240: if (rarrow == -1)
241: gc.attr ^= GRID_ATTR_REVERSE;
242: if (rlen != 0)
243: screen_write_cursormove(&ctx, c->tty.sx - rlen - 2, yy);
244: else
245: screen_write_cursormove(&ctx, c->tty.sx - 1, yy);
246: screen_write_putc(&ctx, &gc, '>');
247: }
248:
249: goto out;
250:
251: blank:
252: /* Just draw the whole line as blank. */
253: screen_write_start(&ctx, NULL, &c->status);
254: screen_write_cursormove(&ctx, 0, yy);
255: for (offset = 0; offset < c->tty.sx; offset++)
256: screen_write_putc(&ctx, &stdgc, ' ');
257:
258: goto out;
259:
260: off:
261: /*
262: * Draw the real window last line. Necessary to wipe over message if
263: * status is off. Not sure this is the right place for this.
264: */
265: memcpy(&stdgc, &grid_default_cell, sizeof stdgc);
266: screen_write_start(&ctx, NULL, &c->status);
267:
268: sy = 0;
269: TAILQ_FOREACH(wp, &s->curw->window->panes, entry) {
270: sy += wp->sy + 1;
271: sc = wp->screen;
272: }
273:
274: screen_write_cursormove(&ctx, 0, 0);
275: if (sy < c->tty.sy) {
276: /* If the screen is too small, use blank. */
277: for (offset = 0; offset < c->tty.sx; offset++)
278: screen_write_putc(&ctx, &stdgc, ' ');
279: } else {
280: screen_write_copy(&ctx,
281: sc, 0, sc->grid->hsize + screen_size_y(sc) - 1, c->tty.sx, 1);
282: }
283:
284: out:
285: screen_write_stop(&ctx);
286:
287: if (left != NULL)
288: xfree(left);
289: if (right != NULL)
290: xfree(right);
291:
292: if (grid_compare(c->status.grid, old_status.grid) == 0) {
293: screen_free(&old_status);
294: return (0);
295: }
296: screen_free(&old_status);
297: return (1);
298: }
299:
300: char *
301: status_replace(struct session *s, char *fmt, time_t t)
302: {
303: struct winlink *wl = s->curw;
304: static char out[BUFSIZ];
305: char in[BUFSIZ], tmp[256], ch, *iptr, *optr, *ptr, *endptr;
306: char *savedptr;
307: size_t len;
308: long n;
309:
310: strftime(in, sizeof in, fmt, localtime(&t));
311: in[(sizeof in) - 1] = '\0';
312:
313: iptr = in;
314: optr = out;
315: savedptr = NULL;
316:
317: while (*iptr != '\0') {
318: if (optr >= out + (sizeof out) - 1)
319: break;
320: switch (ch = *iptr++) {
321: case '#':
322: errno = 0;
323: n = strtol(iptr, &endptr, 10);
324: if ((n == 0 && errno != EINVAL) ||
325: (n == LONG_MIN && errno != ERANGE) ||
326: (n == LONG_MAX && errno != ERANGE) ||
327: n != 0)
328: iptr = endptr;
329: if (n <= 0)
330: n = LONG_MAX;
331:
332: ptr = NULL;
333: switch (*iptr++) {
334: case '(':
335: if (ptr == NULL) {
336: ptr = status_replace_popen(&iptr);
337: if (ptr == NULL)
338: break;
339: savedptr = ptr;
340: }
341: /* FALLTHROUGH */
342: case 'H':
343: if (ptr == NULL) {
344: if (gethostname(tmp, sizeof tmp) != 0)
345: fatal("gethostname");
346: ptr = tmp;
347: }
348: /* FALLTHROUGH */
349: case 'S':
350: if (ptr == NULL)
351: ptr = s->name;
352: /* FALLTHROUGH */
353: case 'T':
354: if (ptr == NULL)
355: ptr = wl->window->active->base.title;
356: len = strlen(ptr);
357: if ((size_t) n < len)
358: len = n;
359: if (optr + len >= out + (sizeof out) - 1)
360: break;
361: while (len > 0 && *ptr != '\0') {
362: *optr++ = *ptr++;
363: len--;
364: }
365: break;
366: case '#':
367: *optr++ = '#';
368: break;
369: }
370: if (savedptr != NULL) {
371: xfree(savedptr);
372: savedptr = NULL;
373: }
374: break;
375: default:
376: *optr++ = ch;
377: break;
378: }
379: }
380: *optr = '\0';
381:
382: return (xstrdup(out));
383: }
384:
385: char *
386: status_replace_popen(char **iptr)
387: {
388: FILE *f;
389: char *buf, *cmd, *ptr;
390: int lastesc;
391: size_t len;
392:
393: if (**iptr == '\0')
394: return (NULL);
395: if (**iptr == ')') { /* no command given */
396: (*iptr)++;
397: return (NULL);
398: }
399:
400: buf = NULL;
401:
402: cmd = xmalloc(strlen(*iptr) + 1);
403: len = 0;
404:
405: lastesc = 0;
406: for (; **iptr != '\0'; (*iptr)++) {
407: if (!lastesc && **iptr == ')')
408: break; /* unescaped ) is the end */
409: if (!lastesc && **iptr == '\\') {
410: lastesc = 1;
411: continue; /* skip \ if not escaped */
412: }
413: lastesc = 0;
414: cmd[len++] = **iptr;
415: }
416: if (**iptr == '\0') /* no terminating ) */
417: goto out;
418: (*iptr)++; /* skip final ) */
419: cmd[len] = '\0';
420:
421: if ((f = popen(cmd, "r")) == NULL)
422: goto out;
423:
424: if ((buf = fgetln(f, &len)) == NULL) {
425: pclose(f);
426: goto out;
427: }
428: if (buf[len - 1] == '\n') {
429: buf[len - 1] = '\0';
430: buf = xstrdup(buf);
431: } else {
432: ptr = xmalloc(len + 1);
433: memcpy(ptr, buf, len);
434: ptr[len] = '\0';
435: buf = ptr;
436: }
437: pclose(f);
438:
439: out:
440: xfree(cmd);
441: return (buf);
442: }
443:
444: size_t
445: status_width(struct winlink *wl)
446: {
447: return (xsnprintf(NULL, 0, "%d:%s ", wl->idx, wl->window->name));
448: }
449:
450: char *
451: status_print(struct session *s, struct winlink *wl, struct grid_cell *gc)
452: {
453: char *text, flag;
454: u_char fg, bg, attr;
455:
456: fg = options_get_number(&wl->window->options, "window-status-fg");
457: if (fg != 8)
458: gc->fg = fg;
459: bg = options_get_number(&wl->window->options, "window-status-bg");
460: if (bg != 8)
461: gc->bg = bg;
462: attr = options_get_number(&wl->window->options, "window-status-attr");
463: if (attr != 0)
464: gc->attr = attr;
465:
466: flag = ' ';
467: if (wl == SLIST_FIRST(&s->lastw))
468: flag = '-';
469: if (wl == s->curw)
470: flag = '*';
471:
472: if (session_alert_has(s, wl, WINDOW_ACTIVITY)) {
473: flag = '#';
474: gc->attr ^= GRID_ATTR_REVERSE;
475: } else if (session_alert_has(s, wl, WINDOW_BELL)) {
476: flag = '!';
477: gc->attr ^= GRID_ATTR_REVERSE;
478: } else if (session_alert_has(s, wl, WINDOW_CONTENT)) {
479: flag = '+';
480: gc->attr ^= GRID_ATTR_REVERSE;
481: }
482:
483: xasprintf(&text, "%d:%s%c", wl->idx, wl->window->name, flag);
484: return (text);
485: }
486:
487: void
488: status_message_set(struct client *c, const char *msg)
489: {
490: struct timeval tv;
491: int delay;
492:
493: delay = options_get_number(&c->session->options, "display-time");
494: tv.tv_sec = delay / 1000;
495: tv.tv_usec = (delay % 1000) * 1000L;
496:
497: c->message_string = xstrdup(msg);
498: if (gettimeofday(&c->message_timer, NULL) != 0)
499: fatal("gettimeofday");
500: timeradd(&c->message_timer, &tv, &c->message_timer);
501:
502: c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
503: c->flags |= CLIENT_STATUS;
504: }
505:
506: void
507: status_message_clear(struct client *c)
508: {
509: if (c->message_string == NULL)
510: return;
511:
512: xfree(c->message_string);
513: c->message_string = NULL;
514:
515: c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
516: c->flags |= CLIENT_REDRAW;
517: }
518:
519: /* Draw client message on status line of present else on last line. */
520: int
521: status_message_redraw(struct client *c)
522: {
523: struct screen_write_ctx ctx;
524: struct session *s = c->session;
525: struct screen old_status;
526: size_t len;
527: struct grid_cell gc;
528:
529: if (c->tty.sx == 0 || c->tty.sy == 0)
530: return (0);
531: memcpy(&old_status, &c->status, sizeof old_status);
532: screen_init(&c->status, c->tty.sx, 1, 0);
533:
534: len = strlen(c->message_string);
535: if (len > c->tty.sx)
536: len = c->tty.sx;
537:
538: memcpy(&gc, &grid_default_cell, sizeof gc);
539: gc.bg = options_get_number(&s->options, "message-fg");
540: gc.fg = options_get_number(&s->options, "message-bg");
541: gc.attr |= options_get_number(&s->options, "message-attr");
542:
543: screen_write_start(&ctx, NULL, &c->status);
544:
545: screen_write_cursormove(&ctx, 0, 0);
546: screen_write_puts(&ctx, &gc, "%.*s", (int) len, c->message_string);
547: for (; len < c->tty.sx; len++)
548: screen_write_putc(&ctx, &gc, ' ');
549:
550: screen_write_stop(&ctx);
551:
552: if (grid_compare(c->status.grid, old_status.grid) == 0) {
553: screen_free(&old_status);
554: return (0);
555: }
556: screen_free(&old_status);
557: return (1);
558: }
559:
560: void
561: status_prompt_set(struct client *c,
562: const char *msg, int (*fn)(void *, const char *), void *data, int flags)
563: {
564: c->prompt_string = xstrdup(msg);
565:
566: c->prompt_buffer = xstrdup("");
567: c->prompt_index = 0;
568:
569: c->prompt_callback = fn;
570: c->prompt_data = data;
571:
572: c->prompt_hindex = 0;
573:
574: c->prompt_flags = flags;
575:
576: mode_key_init(&c->prompt_mdata,
577: options_get_number(&c->session->options, "status-keys"),
578: MODEKEY_CANEDIT);
579:
580: c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
581: c->flags |= CLIENT_STATUS;
582: }
583:
584: void
585: status_prompt_clear(struct client *c)
586: {
587: if (c->prompt_string == NULL)
588: return;
589:
590: mode_key_free(&c->prompt_mdata);
591:
592: xfree(c->prompt_string);
593: c->prompt_string = NULL;
594:
595: xfree(c->prompt_buffer);
596: c->prompt_buffer = NULL;
597:
598: c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
599: c->flags |= CLIENT_REDRAW;
600: }
601:
602: /* Draw client prompt on status line of present else on last line. */
603: int
604: status_prompt_redraw(struct client *c)
605: {
606: struct screen_write_ctx ctx;
607: struct session *s = c->session;
608: struct screen old_status;
609: size_t i, size, left, len, offset, n;
610: char ch;
611: struct grid_cell gc;
612:
613: if (c->tty.sx == 0 || c->tty.sy == 0)
614: return (0);
615: memcpy(&old_status, &c->status, sizeof old_status);
616: screen_init(&c->status, c->tty.sx, 1, 0);
617: offset = 0;
618:
619: len = strlen(c->prompt_string);
620: if (len > c->tty.sx)
621: len = c->tty.sx;
622:
623: memcpy(&gc, &grid_default_cell, sizeof gc);
624: gc.bg = options_get_number(&s->options, "message-fg");
625: gc.fg = options_get_number(&s->options, "message-bg");
626: gc.attr |= options_get_number(&s->options, "message-attr");
627:
628: screen_write_start(&ctx, NULL, &c->status);
629:
630: screen_write_cursormove(&ctx, 0, 0);
631: screen_write_puts(&ctx, &gc, "%.*s", (int) len, c->prompt_string);
632:
633: left = c->tty.sx - len;
634: if (left != 0) {
635: if (c->prompt_index < left)
636: size = strlen(c->prompt_buffer);
637: else {
638: offset = c->prompt_index - left - 1;
639: if (c->prompt_index == strlen(c->prompt_buffer))
640: left--;
641: size = left;
642: }
643: if (c->prompt_flags & PROMPT_HIDDEN) {
644: n = strlen(c->prompt_buffer);
645: if (n > left)
646: n = left;
647: for (i = 0; i < n; i++)
648: screen_write_putc(&ctx, &gc, '*');
649: } else {
650: screen_write_puts(&ctx, &gc,
651: "%.*s", (int) left, c->prompt_buffer + offset);
652: }
653:
654: for (i = len + size; i < c->tty.sx; i++)
655: screen_write_putc(&ctx, &gc, ' ');
656: }
657:
658: /* Draw a fake cursor. */
659: screen_write_cursormove(&ctx, len + c->prompt_index - offset, 0);
660: if (c->prompt_index == strlen(c->prompt_buffer))
661: ch = ' ';
662: else {
663: if (c->prompt_flags & PROMPT_HIDDEN)
664: ch = '*';
665: else
666: ch = c->prompt_buffer[c->prompt_index];
667: }
668: if (ch == '\0')
669: ch = ' ';
670: gc.attr ^= GRID_ATTR_REVERSE;
671: screen_write_putc(&ctx, &gc, ch);
672:
673: screen_write_stop(&ctx);
674:
675: if (grid_compare(c->status.grid, old_status.grid) == 0) {
676: screen_free(&old_status);
677: return (0);
678: }
679: screen_free(&old_status);
680: return (1);
681: }
682:
683: /* Handle keys in prompt. */
684: void
685: status_prompt_key(struct client *c, int key)
686: {
687: struct paste_buffer *pb;
688: char *s, *first, *last, word[64];
689: size_t size, n, off, idx;
690:
691: size = strlen(c->prompt_buffer);
692: switch (mode_key_lookup(&c->prompt_mdata, key)) {
693: case MODEKEYCMD_LEFT:
694: if (c->prompt_index > 0) {
695: c->prompt_index--;
696: c->flags |= CLIENT_STATUS;
697: }
698: break;
699: case MODEKEYCMD_RIGHT:
700: if (c->prompt_index < size) {
701: c->prompt_index++;
702: c->flags |= CLIENT_STATUS;
703: }
704: break;
705: case MODEKEYCMD_STARTOFLINE:
706: if (c->prompt_index != 0) {
707: c->prompt_index = 0;
708: c->flags |= CLIENT_STATUS;
709: }
710: break;
711: case MODEKEYCMD_ENDOFLINE:
712: if (c->prompt_index != size) {
713: c->prompt_index = size;
714: c->flags |= CLIENT_STATUS;
715: }
716: break;
717: case MODEKEYCMD_COMPLETE:
718: if (*c->prompt_buffer == '\0')
719: break;
720:
721: idx = c->prompt_index;
722: if (idx != 0)
723: idx--;
724:
725: /* Find the word we are in. */
726: first = c->prompt_buffer + idx;
727: while (first > c->prompt_buffer && *first != ' ')
728: first--;
729: while (*first == ' ')
730: first++;
731: last = c->prompt_buffer + idx;
732: while (*last != '\0' && *last != ' ')
733: last++;
734: while (*last == ' ')
735: last--;
736: if (*last != '\0')
737: last++;
738: if (last <= first ||
739: ((size_t) (last - first)) > (sizeof word) - 1)
740: break;
741: memcpy(word, first, last - first);
742: word[last - first] = '\0';
743:
744: /* And try to complete it. */
745: if ((s = status_prompt_complete(word)) == NULL)
746: break;
747:
748: /* Trim out word. */
749: n = size - (last - c->prompt_buffer) + 1; /* with \0 */
750: memmove(first, last, n);
751: size -= last - first;
752:
753: /* Insert the new word. */
754: size += strlen(s);
755: off = first - c->prompt_buffer;
756: c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1);
757: first = c->prompt_buffer + off;
758: memmove(first + strlen(s), first, n);
759: memcpy(first, s, strlen(s));
760:
761: c->prompt_index = (first - c->prompt_buffer) + strlen(s);
762:
763: c->flags |= CLIENT_STATUS;
764: break;
765: case MODEKEYCMD_BACKSPACE:
766: if (c->prompt_index != 0) {
767: if (c->prompt_index == size)
768: c->prompt_buffer[--c->prompt_index] = '\0';
769: else {
770: memmove(c->prompt_buffer + c->prompt_index - 1,
771: c->prompt_buffer + c->prompt_index,
772: size + 1 - c->prompt_index);
773: c->prompt_index--;
774: }
775: c->flags |= CLIENT_STATUS;
776: }
777: break;
778: case MODEKEYCMD_DELETE:
779: if (c->prompt_index != size) {
780: memmove(c->prompt_buffer + c->prompt_index,
781: c->prompt_buffer + c->prompt_index + 1,
782: size + 1 - c->prompt_index);
783: c->flags |= CLIENT_STATUS;
784: }
785: break;
786: case MODEKEYCMD_UP:
787: if (server_locked)
788: break;
789:
790: if (ARRAY_LENGTH(&c->prompt_hdata) == 0)
791: break;
792: xfree(c->prompt_buffer);
793:
794: c->prompt_buffer = xstrdup(ARRAY_ITEM(&c->prompt_hdata,
795: ARRAY_LENGTH(&c->prompt_hdata) - 1 - c->prompt_hindex));
796: if (c->prompt_hindex != ARRAY_LENGTH(&c->prompt_hdata) - 1)
797: c->prompt_hindex++;
798:
799: c->prompt_index = strlen(c->prompt_buffer);
800: c->flags |= CLIENT_STATUS;
801: break;
802: case MODEKEYCMD_DOWN:
803: if (server_locked)
804: break;
805:
806: xfree(c->prompt_buffer);
807:
808: if (c->prompt_hindex != 0) {
809: c->prompt_hindex--;
810: c->prompt_buffer = xstrdup(ARRAY_ITEM(
811: &c->prompt_hdata, ARRAY_LENGTH(
812: &c->prompt_hdata) - 1 - c->prompt_hindex));
813: } else
814: c->prompt_buffer = xstrdup("");
815:
816: c->prompt_index = strlen(c->prompt_buffer);
817: c->flags |= CLIENT_STATUS;
818: break;
819: case MODEKEYCMD_PASTE:
820: if ((pb = paste_get_top(&c->session->buffers)) == NULL)
821: break;
822: if ((last = strchr(pb->data, '\n')) == NULL)
823: last = strchr(pb->data, '\0');
824: n = last - pb->data;
825:
826: c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1);
827: if (c->prompt_index == size) {
828: memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
829: c->prompt_index += n;
830: c->prompt_buffer[c->prompt_index] = '\0';
831: } else {
832: memmove(c->prompt_buffer + c->prompt_index + n,
833: c->prompt_buffer + c->prompt_index,
834: size + 1 - c->prompt_index);
835: memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
836: c->prompt_index += n;
837: }
838:
839: c->flags |= CLIENT_STATUS;
840: break;
841: case MODEKEYCMD_CHOOSE:
842: if (*c->prompt_buffer != '\0') {
843: status_prompt_add_history(c);
844: if (c->prompt_callback(
845: c->prompt_data, c->prompt_buffer) == 0)
846: status_prompt_clear(c);
847: break;
848: }
849: /* FALLTHROUGH */
850: case MODEKEYCMD_QUIT:
851: if (c->prompt_callback(c->prompt_data, NULL) == 0)
852: status_prompt_clear(c);
853: break;
854: case MODEKEYCMD_OTHERKEY:
855: if (key < 32 || key > 126)
856: break;
857: c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2);
858:
859: if (c->prompt_index == size) {
860: c->prompt_buffer[c->prompt_index++] = key;
861: c->prompt_buffer[c->prompt_index] = '\0';
862: } else {
863: memmove(c->prompt_buffer + c->prompt_index + 1,
864: c->prompt_buffer + c->prompt_index,
865: size + 1 - c->prompt_index);
866: c->prompt_buffer[c->prompt_index++] = key;
867: }
868:
869: if (c->prompt_flags & PROMPT_SINGLE) {
870: if (c->prompt_callback(
871: c->prompt_data, c->prompt_buffer) == 0)
872: status_prompt_clear(c);
873: }
874:
875: c->flags |= CLIENT_STATUS;
876: break;
877: default:
878: break;
879: }
880: }
881:
882: /* Add line to the history. */
883: void
884: status_prompt_add_history(struct client *c)
885: {
886: if (server_locked)
887: return;
888:
889: if (ARRAY_LENGTH(&c->prompt_hdata) > 0 &&
890: strcmp(ARRAY_LAST(&c->prompt_hdata), c->prompt_buffer) == 0)
891: return;
892:
893: if (ARRAY_LENGTH(&c->prompt_hdata) == PROMPT_HISTORY) {
894: xfree(ARRAY_FIRST(&c->prompt_hdata));
895: ARRAY_REMOVE(&c->prompt_hdata, 0);
896: }
897:
898: ARRAY_ADD(&c->prompt_hdata, xstrdup(c->prompt_buffer));
899: }
900:
901: /* Complete word. */
902: char *
903: status_prompt_complete(const char *s)
904: {
905: const struct cmd_entry **cmdent;
906: const struct set_option_entry *optent;
907: ARRAY_DECL(, const char *) list;
908: char *prefix, *s2;
909: u_int i;
910: size_t j;
911:
912: if (*s == '\0')
913: return (NULL);
914:
915: /* First, build a list of all the possible matches. */
916: ARRAY_INIT(&list);
917: for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
918: if (strncmp((*cmdent)->name, s, strlen(s)) == 0)
919: ARRAY_ADD(&list, (*cmdent)->name);
920: }
921: for (i = 0; i < NSETOPTION; i++) {
922: optent = &set_option_table[i];
923: if (strncmp(optent->name, s, strlen(s)) == 0)
924: ARRAY_ADD(&list, optent->name);
925: }
926: for (i = 0; i < NSETWINDOWOPTION; i++) {
927: optent = &set_window_option_table[i];
928: if (strncmp(optent->name, s, strlen(s)) == 0)
929: ARRAY_ADD(&list, optent->name);
930: }
931:
932: /* If none, bail now. */
933: if (ARRAY_LENGTH(&list) == 0) {
934: ARRAY_FREE(&list);
935: return (NULL);
936: }
937:
938: /* If an exact match, return it, with a trailing space. */
939: if (ARRAY_LENGTH(&list) == 1) {
940: xasprintf(&s2, "%s ", ARRAY_FIRST(&list));
941: ARRAY_FREE(&list);
942: return (s2);
943: }
944:
945: /* Now loop through the list and find the longest common prefix. */
946: prefix = xstrdup(ARRAY_FIRST(&list));
947: for (i = 1; i < ARRAY_LENGTH(&list); i++) {
948: s = ARRAY_ITEM(&list, i);
949:
950: j = strlen(s);
951: if (j > strlen(prefix))
952: j = strlen(prefix);
953: for (; j > 0; j--) {
954: if (prefix[j - 1] != s[j - 1])
955: prefix[j - 1] = '\0';
956: }
957: }
958:
959: ARRAY_FREE(&list);
960: return (prefix);
961: }