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