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