status.c revision 1.44
1/* $OpenBSD: status.c,v 1.44 2009/11/18 13:16:33 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 struct session *s = c->session; 571 struct message_entry *msg; 572 va_list ap; 573 int delay; 574 u_int i, limit; 575 576 status_prompt_clear(c); 577 status_message_clear(c); 578 579 va_start(ap, fmt); 580 xvasprintf(&c->message_string, fmt, ap); 581 va_end(ap); 582 583 ARRAY_EXPAND(&c->message_log, 1); 584 msg = &ARRAY_LAST(&c->message_log); 585 msg->msg_time = time(NULL); 586 msg->msg = xstrdup(c->message_string); 587 588 if (s == NULL) 589 limit = 0; 590 else 591 limit = options_get_number(&s->options, "message-limit"); 592 for (i = ARRAY_LENGTH(&c->message_log); i > limit; i--) { 593 msg = &ARRAY_ITEM(&c->message_log, i - 1); 594 xfree(msg->msg); 595 ARRAY_REMOVE(&c->message_log, i - 1); 596 } 597 598 delay = options_get_number(&c->session->options, "display-time"); 599 tv.tv_sec = delay / 1000; 600 tv.tv_usec = (delay % 1000) * 1000L; 601 602 evtimer_del(&c->message_timer); 603 evtimer_set(&c->message_timer, status_message_callback, c); 604 evtimer_add(&c->message_timer, &tv); 605 606 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); 607 c->flags |= CLIENT_STATUS; 608} 609 610void 611status_message_clear(struct client *c) 612{ 613 if (c->message_string == NULL) 614 return; 615 616 xfree(c->message_string); 617 c->message_string = NULL; 618 619 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 620 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */ 621 622 screen_reinit(&c->status); 623} 624 625void 626status_message_callback(unused int fd, unused short event, void *data) 627{ 628 struct client *c = data; 629 630 status_message_clear(c); 631} 632 633/* Draw client message on status line of present else on last line. */ 634int 635status_message_redraw(struct client *c) 636{ 637 struct screen_write_ctx ctx; 638 struct session *s = c->session; 639 struct screen old_status; 640 size_t len; 641 struct grid_cell gc; 642 643 if (c->tty.sx == 0 || c->tty.sy == 0) 644 return (0); 645 memcpy(&old_status, &c->status, sizeof old_status); 646 screen_init(&c->status, c->tty.sx, 1, 0); 647 648 len = strlen(c->message_string); 649 if (len > c->tty.sx) 650 len = c->tty.sx; 651 652 memcpy(&gc, &grid_default_cell, sizeof gc); 653 colour_set_fg(&gc, options_get_number(&s->options, "message-fg")); 654 colour_set_bg(&gc, options_get_number(&s->options, "message-bg")); 655 gc.attr |= options_get_number(&s->options, "message-attr"); 656 657 screen_write_start(&ctx, NULL, &c->status); 658 659 screen_write_cursormove(&ctx, 0, 0); 660 screen_write_puts(&ctx, &gc, "%.*s", (int) len, c->message_string); 661 for (; len < c->tty.sx; len++) 662 screen_write_putc(&ctx, &gc, ' '); 663 664 screen_write_stop(&ctx); 665 666 if (grid_compare(c->status.grid, old_status.grid) == 0) { 667 screen_free(&old_status); 668 return (0); 669 } 670 screen_free(&old_status); 671 return (1); 672} 673 674void 675status_prompt_set(struct client *c, const char *msg, 676 int (*callbackfn)(void *, const char *), void (*freefn)(void *), 677 void *data, int flags) 678{ 679 int keys; 680 681 status_message_clear(c); 682 status_prompt_clear(c); 683 684 c->prompt_string = xstrdup(msg); 685 686 c->prompt_buffer = xstrdup(""); 687 c->prompt_index = 0; 688 689 c->prompt_callbackfn = callbackfn; 690 c->prompt_freefn = freefn; 691 c->prompt_data = data; 692 693 c->prompt_hindex = 0; 694 695 c->prompt_flags = flags; 696 697 keys = options_get_number(&c->session->options, "status-keys"); 698 if (keys == MODEKEY_EMACS) 699 mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit); 700 else 701 mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit); 702 703 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); 704 c->flags |= CLIENT_STATUS; 705} 706 707void 708status_prompt_clear(struct client *c) 709{ 710 if (c->prompt_string == NULL) 711 return; 712 713 if (c->prompt_freefn != NULL && c->prompt_data != NULL) 714 c->prompt_freefn(c->prompt_data); 715 716 xfree(c->prompt_string); 717 c->prompt_string = NULL; 718 719 xfree(c->prompt_buffer); 720 c->prompt_buffer = NULL; 721 722 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 723 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */ 724 725 screen_reinit(&c->status); 726} 727 728void 729status_prompt_update(struct client *c, const char *msg) 730{ 731 xfree(c->prompt_string); 732 c->prompt_string = xstrdup(msg); 733 734 *c->prompt_buffer = '\0'; 735 c->prompt_index = 0; 736 737 c->prompt_hindex = 0; 738 739 c->flags |= CLIENT_STATUS; 740} 741 742/* Draw client prompt on status line of present else on last line. */ 743int 744status_prompt_redraw(struct client *c) 745{ 746 struct screen_write_ctx ctx; 747 struct session *s = c->session; 748 struct screen old_status; 749 size_t i, size, left, len, off; 750 char ch; 751 struct grid_cell gc; 752 753 if (c->tty.sx == 0 || c->tty.sy == 0) 754 return (0); 755 memcpy(&old_status, &c->status, sizeof old_status); 756 screen_init(&c->status, c->tty.sx, 1, 0); 757 off = 0; 758 759 len = strlen(c->prompt_string); 760 if (len > c->tty.sx) 761 len = c->tty.sx; 762 763 memcpy(&gc, &grid_default_cell, sizeof gc); 764 colour_set_fg(&gc, options_get_number(&s->options, "message-fg")); 765 colour_set_bg(&gc, options_get_number(&s->options, "message-bg")); 766 gc.attr |= options_get_number(&s->options, "message-attr"); 767 768 screen_write_start(&ctx, NULL, &c->status); 769 770 screen_write_cursormove(&ctx, 0, 0); 771 screen_write_puts(&ctx, &gc, "%.*s", (int) len, c->prompt_string); 772 773 left = c->tty.sx - len; 774 if (left != 0) { 775 if (c->prompt_index < left) 776 size = strlen(c->prompt_buffer); 777 else { 778 off = c->prompt_index - left + 1; 779 if (c->prompt_index == strlen(c->prompt_buffer)) 780 left--; 781 size = left; 782 } 783 screen_write_puts( 784 &ctx, &gc, "%.*s", (int) left, c->prompt_buffer + off); 785 786 for (i = len + size; i < c->tty.sx; i++) 787 screen_write_putc(&ctx, &gc, ' '); 788 789 /* Draw a fake cursor. */ 790 ch = ' '; 791 screen_write_cursormove(&ctx, len + c->prompt_index - off, 0); 792 if (c->prompt_index < strlen(c->prompt_buffer)) 793 ch = c->prompt_buffer[c->prompt_index]; 794 gc.attr ^= GRID_ATTR_REVERSE; 795 screen_write_putc(&ctx, &gc, ch); 796 } 797 798 screen_write_stop(&ctx); 799 800 if (grid_compare(c->status.grid, old_status.grid) == 0) { 801 screen_free(&old_status); 802 return (0); 803 } 804 screen_free(&old_status); 805 return (1); 806} 807 808/* Handle keys in prompt. */ 809void 810status_prompt_key(struct client *c, int key) 811{ 812 struct paste_buffer *pb; 813 char *s, *first, *last, word[64], swapc; 814 size_t size, n, off, idx; 815 816 size = strlen(c->prompt_buffer); 817 switch (mode_key_lookup(&c->prompt_mdata, key)) { 818 case MODEKEYEDIT_CURSORLEFT: 819 if (c->prompt_index > 0) { 820 c->prompt_index--; 821 c->flags |= CLIENT_STATUS; 822 } 823 break; 824 case MODEKEYEDIT_SWITCHMODEAPPEND: 825 case MODEKEYEDIT_CURSORRIGHT: 826 if (c->prompt_index < size) { 827 c->prompt_index++; 828 c->flags |= CLIENT_STATUS; 829 } 830 break; 831 case MODEKEYEDIT_STARTOFLINE: 832 if (c->prompt_index != 0) { 833 c->prompt_index = 0; 834 c->flags |= CLIENT_STATUS; 835 } 836 break; 837 case MODEKEYEDIT_ENDOFLINE: 838 if (c->prompt_index != size) { 839 c->prompt_index = size; 840 c->flags |= CLIENT_STATUS; 841 } 842 break; 843 case MODEKEYEDIT_COMPLETE: 844 if (*c->prompt_buffer == '\0') 845 break; 846 847 idx = c->prompt_index; 848 if (idx != 0) 849 idx--; 850 851 /* Find the word we are in. */ 852 first = c->prompt_buffer + idx; 853 while (first > c->prompt_buffer && *first != ' ') 854 first--; 855 while (*first == ' ') 856 first++; 857 last = c->prompt_buffer + idx; 858 while (*last != '\0' && *last != ' ') 859 last++; 860 while (*last == ' ') 861 last--; 862 if (*last != '\0') 863 last++; 864 if (last <= first || 865 ((size_t) (last - first)) > (sizeof word) - 1) 866 break; 867 memcpy(word, first, last - first); 868 word[last - first] = '\0'; 869 870 /* And try to complete it. */ 871 if ((s = status_prompt_complete(word)) == NULL) 872 break; 873 874 /* Trim out word. */ 875 n = size - (last - c->prompt_buffer) + 1; /* with \0 */ 876 memmove(first, last, n); 877 size -= last - first; 878 879 /* Insert the new word. */ 880 size += strlen(s); 881 off = first - c->prompt_buffer; 882 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1); 883 first = c->prompt_buffer + off; 884 memmove(first + strlen(s), first, n); 885 memcpy(first, s, strlen(s)); 886 887 c->prompt_index = (first - c->prompt_buffer) + strlen(s); 888 xfree(s); 889 890 c->flags |= CLIENT_STATUS; 891 break; 892 case MODEKEYEDIT_BACKSPACE: 893 if (c->prompt_index != 0) { 894 if (c->prompt_index == size) 895 c->prompt_buffer[--c->prompt_index] = '\0'; 896 else { 897 memmove(c->prompt_buffer + c->prompt_index - 1, 898 c->prompt_buffer + c->prompt_index, 899 size + 1 - c->prompt_index); 900 c->prompt_index--; 901 } 902 c->flags |= CLIENT_STATUS; 903 } 904 break; 905 case MODEKEYEDIT_DELETE: 906 if (c->prompt_index != size) { 907 memmove(c->prompt_buffer + c->prompt_index, 908 c->prompt_buffer + c->prompt_index + 1, 909 size + 1 - c->prompt_index); 910 c->flags |= CLIENT_STATUS; 911 } 912 break; 913 case MODEKEYEDIT_DELETELINE: 914 *c->prompt_buffer = '\0'; 915 c->prompt_index = 0; 916 c->flags |= CLIENT_STATUS; 917 break; 918 case MODEKEYEDIT_DELETETOENDOFLINE: 919 if (c->prompt_index < size) { 920 c->prompt_buffer[c->prompt_index] = '\0'; 921 c->flags |= CLIENT_STATUS; 922 } 923 break; 924 case MODEKEYEDIT_HISTORYUP: 925 if (ARRAY_LENGTH(&c->prompt_hdata) == 0) 926 break; 927 xfree(c->prompt_buffer); 928 929 c->prompt_buffer = xstrdup(ARRAY_ITEM(&c->prompt_hdata, 930 ARRAY_LENGTH(&c->prompt_hdata) - 1 - c->prompt_hindex)); 931 if (c->prompt_hindex != ARRAY_LENGTH(&c->prompt_hdata) - 1) 932 c->prompt_hindex++; 933 934 c->prompt_index = strlen(c->prompt_buffer); 935 c->flags |= CLIENT_STATUS; 936 break; 937 case MODEKEYEDIT_HISTORYDOWN: 938 xfree(c->prompt_buffer); 939 940 if (c->prompt_hindex != 0) { 941 c->prompt_hindex--; 942 c->prompt_buffer = xstrdup(ARRAY_ITEM( 943 &c->prompt_hdata, ARRAY_LENGTH( 944 &c->prompt_hdata) - 1 - c->prompt_hindex)); 945 } else 946 c->prompt_buffer = xstrdup(""); 947 948 c->prompt_index = strlen(c->prompt_buffer); 949 c->flags |= CLIENT_STATUS; 950 break; 951 case MODEKEYEDIT_PASTE: 952 if ((pb = paste_get_top(&c->session->buffers)) == NULL) 953 break; 954 for (n = 0; n < pb->size; n++) { 955 if (pb->data[n] < 32 || pb->data[n] == 127) 956 break; 957 } 958 959 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1); 960 if (c->prompt_index == size) { 961 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n); 962 c->prompt_index += n; 963 c->prompt_buffer[c->prompt_index] = '\0'; 964 } else { 965 memmove(c->prompt_buffer + c->prompt_index + n, 966 c->prompt_buffer + c->prompt_index, 967 size + 1 - c->prompt_index); 968 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n); 969 c->prompt_index += n; 970 } 971 972 c->flags |= CLIENT_STATUS; 973 break; 974 case MODEKEYEDIT_TRANSPOSECHARS: 975 idx = c->prompt_index; 976 if (idx < size) 977 idx++; 978 if (idx >= 2) { 979 swapc = c->prompt_buffer[idx - 2]; 980 c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1]; 981 c->prompt_buffer[idx - 1] = swapc; 982 c->prompt_index = idx; 983 c->flags |= CLIENT_STATUS; 984 } 985 break; 986 case MODEKEYEDIT_ENTER: 987 if (*c->prompt_buffer != '\0') 988 status_prompt_add_history(c); 989 if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0) 990 status_prompt_clear(c); 991 break; 992 case MODEKEYEDIT_CANCEL: 993 if (c->prompt_callbackfn(c->prompt_data, NULL) == 0) 994 status_prompt_clear(c); 995 break; 996 case MODEKEY_OTHER: 997 if (key < 32 || key == 127) 998 break; 999 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2); 1000 1001 if (c->prompt_index == size) { 1002 c->prompt_buffer[c->prompt_index++] = key; 1003 c->prompt_buffer[c->prompt_index] = '\0'; 1004 } else { 1005 memmove(c->prompt_buffer + c->prompt_index + 1, 1006 c->prompt_buffer + c->prompt_index, 1007 size + 1 - c->prompt_index); 1008 c->prompt_buffer[c->prompt_index++] = key; 1009 } 1010 1011 if (c->prompt_flags & PROMPT_SINGLE) { 1012 if (c->prompt_callbackfn( 1013 c->prompt_data, c->prompt_buffer) == 0) 1014 status_prompt_clear(c); 1015 } 1016 1017 c->flags |= CLIENT_STATUS; 1018 break; 1019 default: 1020 break; 1021 } 1022} 1023 1024/* Add line to the history. */ 1025void 1026status_prompt_add_history(struct client *c) 1027{ 1028 if (ARRAY_LENGTH(&c->prompt_hdata) > 0 && 1029 strcmp(ARRAY_LAST(&c->prompt_hdata), c->prompt_buffer) == 0) 1030 return; 1031 1032 if (ARRAY_LENGTH(&c->prompt_hdata) == PROMPT_HISTORY) { 1033 xfree(ARRAY_FIRST(&c->prompt_hdata)); 1034 ARRAY_REMOVE(&c->prompt_hdata, 0); 1035 } 1036 1037 ARRAY_ADD(&c->prompt_hdata, xstrdup(c->prompt_buffer)); 1038} 1039 1040/* Complete word. */ 1041char * 1042status_prompt_complete(const char *s) 1043{ 1044 const struct cmd_entry **cmdent; 1045 const struct set_option_entry *optent; 1046 ARRAY_DECL(, const char *) list; 1047 char *prefix, *s2; 1048 u_int i; 1049 size_t j; 1050 1051 if (*s == '\0') 1052 return (NULL); 1053 1054 /* First, build a list of all the possible matches. */ 1055 ARRAY_INIT(&list); 1056 for (cmdent = cmd_table; *cmdent != NULL; cmdent++) { 1057 if (strncmp((*cmdent)->name, s, strlen(s)) == 0) 1058 ARRAY_ADD(&list, (*cmdent)->name); 1059 } 1060 for (optent = set_option_table; optent->name != NULL; optent++) { 1061 if (strncmp(optent->name, s, strlen(s)) == 0) 1062 ARRAY_ADD(&list, optent->name); 1063 } 1064 for (optent = set_window_option_table; optent->name != NULL; optent++) { 1065 if (strncmp(optent->name, s, strlen(s)) == 0) 1066 ARRAY_ADD(&list, optent->name); 1067 } 1068 1069 /* If none, bail now. */ 1070 if (ARRAY_LENGTH(&list) == 0) { 1071 ARRAY_FREE(&list); 1072 return (NULL); 1073 } 1074 1075 /* If an exact match, return it, with a trailing space. */ 1076 if (ARRAY_LENGTH(&list) == 1) { 1077 xasprintf(&s2, "%s ", ARRAY_FIRST(&list)); 1078 ARRAY_FREE(&list); 1079 return (s2); 1080 } 1081 1082 /* Now loop through the list and find the longest common prefix. */ 1083 prefix = xstrdup(ARRAY_FIRST(&list)); 1084 for (i = 1; i < ARRAY_LENGTH(&list); i++) { 1085 s = ARRAY_ITEM(&list, i); 1086 1087 j = strlen(s); 1088 if (j > strlen(prefix)) 1089 j = strlen(prefix); 1090 for (; j > 0; j--) { 1091 if (prefix[j - 1] != s[j - 1]) 1092 prefix[j - 1] = '\0'; 1093 } 1094 } 1095 1096 ARRAY_FREE(&list); 1097 return (prefix); 1098} 1099