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