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