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