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