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