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