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