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