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