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