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