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