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