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