status.c revision 1.36
1/* $OpenBSD: status.c,v 1.36 2009/09/23 14:42:48 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 failed"); 68 memcpy(&stdgc, &grid_default_cell, sizeof gc); 69 colour_set_fg(&stdgc, options_get_number(&s->options, "status-fg")); 70 colour_set_bg(&stdgc, 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 colour_set_fg(&sl_stdgc, sl_fg); 83 sl_bg = options_get_number(&s->options, "status-left-bg"); 84 if (sl_bg != 8) 85 colour_set_bg(&sl_stdgc, 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 colour_set_fg(&sr_stdgc, sr_fg); 92 sr_bg = options_get_number(&s->options, "status-right-bg"); 93 if (sr_bg != 8) 94 colour_set_bg(&sr_stdgc, 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_cstrlen(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_cstrlen(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_cnputs(&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_cnputs(&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 failed"); 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 /* 405 * Embedded style, handled at display time. 406 * Leave present and skip input until ]. 407 */ 408 *optr++ = '#'; 409 410 iptr--; /* include [ */ 411 while (*iptr != ']' && *iptr != '\0') { 412 if (optr >= out + (sizeof out) - 1) 413 break; 414 *optr++ = *iptr++; 415 } 416 break; 417 case '#': 418 *optr++ = '#'; 419 break; 420 } 421 if (savedptr != NULL) { 422 xfree(savedptr); 423 savedptr = NULL; 424 } 425 break; 426 default: 427 *optr++ = ch; 428 break; 429 } 430 } 431 *optr = '\0'; 432 433 return (xstrdup(out)); 434} 435 436char * 437status_replace_popen(char **iptr) 438{ 439 FILE *f; 440 char *buf, *cmd, *ptr; 441 int lastesc; 442 size_t len; 443 444 if (**iptr == '\0') 445 return (NULL); 446 if (**iptr == ')') { /* no command given */ 447 (*iptr)++; 448 return (NULL); 449 } 450 451 buf = NULL; 452 453 cmd = xmalloc(strlen(*iptr) + 1); 454 len = 0; 455 456 lastesc = 0; 457 for (; **iptr != '\0'; (*iptr)++) { 458 if (!lastesc && **iptr == ')') 459 break; /* unescaped ) is the end */ 460 if (!lastesc && **iptr == '\\') { 461 lastesc = 1; 462 continue; /* skip \ if not escaped */ 463 } 464 lastesc = 0; 465 cmd[len++] = **iptr; 466 } 467 if (**iptr == '\0') /* no terminating ) */ 468 goto out; 469 (*iptr)++; /* skip final ) */ 470 cmd[len] = '\0'; 471 472 if ((f = popen(cmd, "r")) == NULL) 473 goto out; 474 475 if ((buf = fgetln(f, &len)) == NULL) { 476 pclose(f); 477 goto out; 478 } 479 if (buf[len - 1] == '\n') { 480 buf[len - 1] = '\0'; 481 buf = xstrdup(buf); 482 } else { 483 ptr = xmalloc(len + 1); 484 memcpy(ptr, buf, len); 485 ptr[len] = '\0'; 486 buf = ptr; 487 } 488 pclose(f); 489 490out: 491 xfree(cmd); 492 return (buf); 493} 494 495size_t 496status_width(struct winlink *wl) 497{ 498 return (xsnprintf(NULL, 0, "%d:%s ", wl->idx, wl->window->name)); 499} 500 501char * 502status_print(struct session *s, struct winlink *wl, struct grid_cell *gc) 503{ 504 struct options *oo = &wl->window->options; 505 char *text, flag; 506 u_char fg, bg, attr; 507 508 fg = options_get_number(oo, "window-status-fg"); 509 if (fg != 8) 510 colour_set_fg(gc, fg); 511 bg = options_get_number(oo, "window-status-bg"); 512 if (bg != 8) 513 colour_set_bg(gc, bg); 514 attr = options_get_number(oo, "window-status-attr"); 515 if (attr != 0) 516 gc->attr = attr; 517 518 flag = ' '; 519 if (wl == SLIST_FIRST(&s->lastw)) 520 flag = '-'; 521 if (wl == s->curw) { 522 fg = options_get_number(oo, "window-status-current-fg"); 523 if (fg != 8) 524 colour_set_fg(gc, fg); 525 bg = options_get_number(oo, "window-status-current-bg"); 526 if (bg != 8) 527 colour_set_bg(gc, bg); 528 attr = options_get_number(oo, "window-status-current-attr"); 529 if (attr != 0) 530 gc->attr = attr; 531 flag = '*'; 532 } 533 534 if (session_alert_has(s, wl, WINDOW_ACTIVITY)) { 535 flag = '#'; 536 gc->attr ^= GRID_ATTR_REVERSE; 537 } else if (session_alert_has(s, wl, WINDOW_BELL)) { 538 flag = '!'; 539 gc->attr ^= GRID_ATTR_REVERSE; 540 } else if (session_alert_has(s, wl, WINDOW_CONTENT)) { 541 flag = '+'; 542 gc->attr ^= GRID_ATTR_REVERSE; 543 } 544 545 xasprintf(&text, "%d:%s%c", wl->idx, wl->window->name, flag); 546 return (text); 547} 548 549void printflike2 550status_message_set(struct client *c, const char *fmt, ...) 551{ 552 struct timeval tv; 553 va_list ap; 554 int delay; 555 556 status_prompt_clear(c); 557 status_message_clear(c); 558 559 va_start(ap, fmt); 560 xvasprintf(&c->message_string, fmt, ap); 561 va_end(ap); 562 563 delay = options_get_number(&c->session->options, "display-time"); 564 tv.tv_sec = delay / 1000; 565 tv.tv_usec = (delay % 1000) * 1000L; 566 567 if (gettimeofday(&c->message_timer, NULL) != 0) 568 fatal("gettimeofday failed"); 569 timeradd(&c->message_timer, &tv, &c->message_timer); 570 571 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); 572 c->flags |= CLIENT_STATUS; 573} 574 575void 576status_message_clear(struct client *c) 577{ 578 if (c->message_string == NULL) 579 return; 580 581 xfree(c->message_string); 582 c->message_string = NULL; 583 584 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 585 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */ 586 587 screen_reinit(&c->status); 588} 589 590/* Draw client message on status line of present else on last line. */ 591int 592status_message_redraw(struct client *c) 593{ 594 struct screen_write_ctx ctx; 595 struct session *s = c->session; 596 struct screen old_status; 597 size_t len; 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 605 len = strlen(c->message_string); 606 if (len > c->tty.sx) 607 len = c->tty.sx; 608 609 memcpy(&gc, &grid_default_cell, sizeof gc); 610 colour_set_fg(&gc, options_get_number(&s->options, "message-fg")); 611 colour_set_bg(&gc, options_get_number(&s->options, "message-bg")); 612 gc.attr |= options_get_number(&s->options, "message-attr"); 613 614 screen_write_start(&ctx, NULL, &c->status); 615 616 screen_write_cursormove(&ctx, 0, 0); 617 screen_write_puts(&ctx, &gc, "%.*s", (int) len, c->message_string); 618 for (; len < c->tty.sx; len++) 619 screen_write_putc(&ctx, &gc, ' '); 620 621 screen_write_stop(&ctx); 622 623 if (grid_compare(c->status.grid, old_status.grid) == 0) { 624 screen_free(&old_status); 625 return (0); 626 } 627 screen_free(&old_status); 628 return (1); 629} 630 631void 632status_prompt_set(struct client *c, const char *msg, 633 int (*callbackfn)(void *, const char *), void (*freefn)(void *), 634 void *data, int flags) 635{ 636 int keys; 637 638 status_message_clear(c); 639 status_prompt_clear(c); 640 641 c->prompt_string = xstrdup(msg); 642 643 c->prompt_buffer = xstrdup(""); 644 c->prompt_index = 0; 645 646 c->prompt_callbackfn = callbackfn; 647 c->prompt_freefn = freefn; 648 c->prompt_data = data; 649 650 c->prompt_hindex = 0; 651 652 c->prompt_flags = flags; 653 654 keys = options_get_number(&c->session->options, "status-keys"); 655 if (keys == MODEKEY_EMACS) 656 mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit); 657 else 658 mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit); 659 660 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); 661 c->flags |= CLIENT_STATUS; 662} 663 664void 665status_prompt_clear(struct client *c) 666{ 667 if (c->prompt_string == NULL) 668 return; 669 670 if (c->prompt_freefn != NULL && c->prompt_data != NULL) 671 c->prompt_freefn(c->prompt_data); 672 673 xfree(c->prompt_string); 674 c->prompt_string = NULL; 675 676 xfree(c->prompt_buffer); 677 c->prompt_buffer = NULL; 678 679 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 680 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */ 681 682 screen_reinit(&c->status); 683} 684 685void 686status_prompt_update(struct client *c, const char *msg) 687{ 688 xfree(c->prompt_string); 689 c->prompt_string = xstrdup(msg); 690 691 *c->prompt_buffer = '\0'; 692 c->prompt_index = 0; 693 694 c->prompt_hindex = 0; 695 696 c->flags |= CLIENT_STATUS; 697} 698 699/* Draw client prompt on status line of present else on last line. */ 700int 701status_prompt_redraw(struct client *c) 702{ 703 struct screen_write_ctx ctx; 704 struct session *s = c->session; 705 struct screen old_status; 706 size_t i, size, left, len, off; 707 char ch; 708 struct grid_cell gc; 709 710 if (c->tty.sx == 0 || c->tty.sy == 0) 711 return (0); 712 memcpy(&old_status, &c->status, sizeof old_status); 713 screen_init(&c->status, c->tty.sx, 1, 0); 714 off = 0; 715 716 len = strlen(c->prompt_string); 717 if (len > c->tty.sx) 718 len = c->tty.sx; 719 720 memcpy(&gc, &grid_default_cell, sizeof gc); 721 colour_set_fg(&gc, options_get_number(&s->options, "message-fg")); 722 colour_set_bg(&gc, options_get_number(&s->options, "message-bg")); 723 gc.attr |= options_get_number(&s->options, "message-attr"); 724 725 screen_write_start(&ctx, NULL, &c->status); 726 727 screen_write_cursormove(&ctx, 0, 0); 728 screen_write_puts(&ctx, &gc, "%.*s", (int) len, c->prompt_string); 729 730 left = c->tty.sx - len; 731 if (left != 0) { 732 if (c->prompt_index < left) 733 size = strlen(c->prompt_buffer); 734 else { 735 off = c->prompt_index - left + 1; 736 if (c->prompt_index == strlen(c->prompt_buffer)) 737 left--; 738 size = left; 739 } 740 screen_write_puts( 741 &ctx, &gc, "%.*s", (int) left, c->prompt_buffer + off); 742 743 for (i = len + size; i < c->tty.sx; i++) 744 screen_write_putc(&ctx, &gc, ' '); 745 746 /* Draw a fake cursor. */ 747 ch = ' '; 748 screen_write_cursormove(&ctx, len + c->prompt_index - off, 0); 749 if (c->prompt_index < strlen(c->prompt_buffer)) 750 ch = c->prompt_buffer[c->prompt_index]; 751 gc.attr ^= GRID_ATTR_REVERSE; 752 screen_write_putc(&ctx, &gc, ch); 753 } 754 755 screen_write_stop(&ctx); 756 757 if (grid_compare(c->status.grid, old_status.grid) == 0) { 758 screen_free(&old_status); 759 return (0); 760 } 761 screen_free(&old_status); 762 return (1); 763} 764 765/* Handle keys in prompt. */ 766void 767status_prompt_key(struct client *c, int key) 768{ 769 struct paste_buffer *pb; 770 char *s, *first, *last, word[64], swapc; 771 size_t size, n, off, idx; 772 773 size = strlen(c->prompt_buffer); 774 switch (mode_key_lookup(&c->prompt_mdata, key)) { 775 case MODEKEYEDIT_CURSORLEFT: 776 if (c->prompt_index > 0) { 777 c->prompt_index--; 778 c->flags |= CLIENT_STATUS; 779 } 780 break; 781 case MODEKEYEDIT_SWITCHMODEAPPEND: 782 case MODEKEYEDIT_CURSORRIGHT: 783 if (c->prompt_index < size) { 784 c->prompt_index++; 785 c->flags |= CLIENT_STATUS; 786 } 787 break; 788 case MODEKEYEDIT_STARTOFLINE: 789 if (c->prompt_index != 0) { 790 c->prompt_index = 0; 791 c->flags |= CLIENT_STATUS; 792 } 793 break; 794 case MODEKEYEDIT_ENDOFLINE: 795 if (c->prompt_index != size) { 796 c->prompt_index = size; 797 c->flags |= CLIENT_STATUS; 798 } 799 break; 800 case MODEKEYEDIT_COMPLETE: 801 if (*c->prompt_buffer == '\0') 802 break; 803 804 idx = c->prompt_index; 805 if (idx != 0) 806 idx--; 807 808 /* Find the word we are in. */ 809 first = c->prompt_buffer + idx; 810 while (first > c->prompt_buffer && *first != ' ') 811 first--; 812 while (*first == ' ') 813 first++; 814 last = c->prompt_buffer + idx; 815 while (*last != '\0' && *last != ' ') 816 last++; 817 while (*last == ' ') 818 last--; 819 if (*last != '\0') 820 last++; 821 if (last <= first || 822 ((size_t) (last - first)) > (sizeof word) - 1) 823 break; 824 memcpy(word, first, last - first); 825 word[last - first] = '\0'; 826 827 /* And try to complete it. */ 828 if ((s = status_prompt_complete(word)) == NULL) 829 break; 830 831 /* Trim out word. */ 832 n = size - (last - c->prompt_buffer) + 1; /* with \0 */ 833 memmove(first, last, n); 834 size -= last - first; 835 836 /* Insert the new word. */ 837 size += strlen(s); 838 off = first - c->prompt_buffer; 839 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1); 840 first = c->prompt_buffer + off; 841 memmove(first + strlen(s), first, n); 842 memcpy(first, s, strlen(s)); 843 844 c->prompt_index = (first - c->prompt_buffer) + strlen(s); 845 xfree(s); 846 847 c->flags |= CLIENT_STATUS; 848 break; 849 case MODEKEYEDIT_BACKSPACE: 850 if (c->prompt_index != 0) { 851 if (c->prompt_index == size) 852 c->prompt_buffer[--c->prompt_index] = '\0'; 853 else { 854 memmove(c->prompt_buffer + c->prompt_index - 1, 855 c->prompt_buffer + c->prompt_index, 856 size + 1 - c->prompt_index); 857 c->prompt_index--; 858 } 859 c->flags |= CLIENT_STATUS; 860 } 861 break; 862 case MODEKEYEDIT_DELETE: 863 if (c->prompt_index != size) { 864 memmove(c->prompt_buffer + c->prompt_index, 865 c->prompt_buffer + c->prompt_index + 1, 866 size + 1 - c->prompt_index); 867 c->flags |= CLIENT_STATUS; 868 } 869 break; 870 case MODEKEYEDIT_DELETELINE: 871 *c->prompt_buffer = '\0'; 872 c->prompt_index = 0; 873 c->flags |= CLIENT_STATUS; 874 break; 875 case MODEKEYEDIT_DELETETOENDOFLINE: 876 if (c->prompt_index < size) { 877 c->prompt_buffer[c->prompt_index] = '\0'; 878 c->flags |= CLIENT_STATUS; 879 } 880 break; 881 case MODEKEYEDIT_HISTORYUP: 882 if (ARRAY_LENGTH(&c->prompt_hdata) == 0) 883 break; 884 xfree(c->prompt_buffer); 885 886 c->prompt_buffer = xstrdup(ARRAY_ITEM(&c->prompt_hdata, 887 ARRAY_LENGTH(&c->prompt_hdata) - 1 - c->prompt_hindex)); 888 if (c->prompt_hindex != ARRAY_LENGTH(&c->prompt_hdata) - 1) 889 c->prompt_hindex++; 890 891 c->prompt_index = strlen(c->prompt_buffer); 892 c->flags |= CLIENT_STATUS; 893 break; 894 case MODEKEYEDIT_HISTORYDOWN: 895 xfree(c->prompt_buffer); 896 897 if (c->prompt_hindex != 0) { 898 c->prompt_hindex--; 899 c->prompt_buffer = xstrdup(ARRAY_ITEM( 900 &c->prompt_hdata, ARRAY_LENGTH( 901 &c->prompt_hdata) - 1 - c->prompt_hindex)); 902 } else 903 c->prompt_buffer = xstrdup(""); 904 905 c->prompt_index = strlen(c->prompt_buffer); 906 c->flags |= CLIENT_STATUS; 907 break; 908 case MODEKEYEDIT_PASTE: 909 if ((pb = paste_get_top(&c->session->buffers)) == NULL) 910 break; 911 for (n = 0; n < pb->size; n++) { 912 if (pb->data[n] < 32 || pb->data[n] == 127) 913 break; 914 } 915 916 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1); 917 if (c->prompt_index == size) { 918 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n); 919 c->prompt_index += n; 920 c->prompt_buffer[c->prompt_index] = '\0'; 921 } else { 922 memmove(c->prompt_buffer + c->prompt_index + n, 923 c->prompt_buffer + c->prompt_index, 924 size + 1 - c->prompt_index); 925 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n); 926 c->prompt_index += n; 927 } 928 929 c->flags |= CLIENT_STATUS; 930 break; 931 case MODEKEYEDIT_TRANSPOSECHARS: 932 idx = c->prompt_index; 933 if (idx < size) 934 idx++; 935 if (idx >= 2) { 936 swapc = c->prompt_buffer[idx - 2]; 937 c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1]; 938 c->prompt_buffer[idx - 1] = swapc; 939 c->prompt_index = idx; 940 c->flags |= CLIENT_STATUS; 941 } 942 break; 943 case MODEKEYEDIT_ENTER: 944 if (*c->prompt_buffer != '\0') 945 status_prompt_add_history(c); 946 if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0) 947 status_prompt_clear(c); 948 break; 949 case MODEKEYEDIT_CANCEL: 950 if (c->prompt_callbackfn(c->prompt_data, NULL) == 0) 951 status_prompt_clear(c); 952 break; 953 case MODEKEY_OTHER: 954 if (key < 32 || key > 126) 955 break; 956 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2); 957 958 if (c->prompt_index == size) { 959 c->prompt_buffer[c->prompt_index++] = key; 960 c->prompt_buffer[c->prompt_index] = '\0'; 961 } else { 962 memmove(c->prompt_buffer + c->prompt_index + 1, 963 c->prompt_buffer + c->prompt_index, 964 size + 1 - c->prompt_index); 965 c->prompt_buffer[c->prompt_index++] = key; 966 } 967 968 if (c->prompt_flags & PROMPT_SINGLE) { 969 if (c->prompt_callbackfn( 970 c->prompt_data, c->prompt_buffer) == 0) 971 status_prompt_clear(c); 972 } 973 974 c->flags |= CLIENT_STATUS; 975 break; 976 default: 977 break; 978 } 979} 980 981/* Add line to the history. */ 982void 983status_prompt_add_history(struct client *c) 984{ 985 if (ARRAY_LENGTH(&c->prompt_hdata) > 0 && 986 strcmp(ARRAY_LAST(&c->prompt_hdata), c->prompt_buffer) == 0) 987 return; 988 989 if (ARRAY_LENGTH(&c->prompt_hdata) == PROMPT_HISTORY) { 990 xfree(ARRAY_FIRST(&c->prompt_hdata)); 991 ARRAY_REMOVE(&c->prompt_hdata, 0); 992 } 993 994 ARRAY_ADD(&c->prompt_hdata, xstrdup(c->prompt_buffer)); 995} 996 997/* Complete word. */ 998char * 999status_prompt_complete(const char *s) 1000{ 1001 const struct cmd_entry **cmdent; 1002 const struct set_option_entry *optent; 1003 ARRAY_DECL(, const char *) list; 1004 char *prefix, *s2; 1005 u_int i; 1006 size_t j; 1007 1008 if (*s == '\0') 1009 return (NULL); 1010 1011 /* First, build a list of all the possible matches. */ 1012 ARRAY_INIT(&list); 1013 for (cmdent = cmd_table; *cmdent != NULL; cmdent++) { 1014 if (strncmp((*cmdent)->name, s, strlen(s)) == 0) 1015 ARRAY_ADD(&list, (*cmdent)->name); 1016 } 1017 for (optent = set_option_table; optent->name != NULL; optent++) { 1018 if (strncmp(optent->name, s, strlen(s)) == 0) 1019 ARRAY_ADD(&list, optent->name); 1020 } 1021 for (optent = set_window_option_table; optent->name != NULL; optent++) { 1022 if (strncmp(optent->name, s, strlen(s)) == 0) 1023 ARRAY_ADD(&list, optent->name); 1024 } 1025 1026 /* If none, bail now. */ 1027 if (ARRAY_LENGTH(&list) == 0) { 1028 ARRAY_FREE(&list); 1029 return (NULL); 1030 } 1031 1032 /* If an exact match, return it, with a trailing space. */ 1033 if (ARRAY_LENGTH(&list) == 1) { 1034 xasprintf(&s2, "%s ", ARRAY_FIRST(&list)); 1035 ARRAY_FREE(&list); 1036 return (s2); 1037 } 1038 1039 /* Now loop through the list and find the longest common prefix. */ 1040 prefix = xstrdup(ARRAY_FIRST(&list)); 1041 for (i = 1; i < ARRAY_LENGTH(&list); i++) { 1042 s = ARRAY_ITEM(&list, i); 1043 1044 j = strlen(s); 1045 if (j > strlen(prefix)) 1046 j = strlen(prefix); 1047 for (; j > 0; j--) { 1048 if (prefix[j - 1] != s[j - 1]) 1049 prefix[j - 1] = '\0'; 1050 } 1051 } 1052 1053 ARRAY_FREE(&list); 1054 return (prefix); 1055} 1056