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