status.c revision 1.191
1/* $OpenBSD: status.c,v 1.191 2019/03/18 11:58:40 nicm Exp $ */ 2 3/* 4 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> 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 32static char *status_redraw_get_left(struct client *, struct grid_cell *, 33 size_t *); 34static char *status_redraw_get_right(struct client *, struct grid_cell *, 35 size_t *); 36static char *status_print(struct client *, struct winlink *, 37 struct grid_cell *); 38static char *status_replace(struct client *, struct winlink *, 39 const char *); 40static void status_message_callback(int, short, void *); 41static void status_timer_callback(int, short, void *); 42 43static char *status_prompt_find_history_file(void); 44static const char *status_prompt_up_history(u_int *); 45static const char *status_prompt_down_history(u_int *); 46static void status_prompt_add_history(const char *); 47 48static char **status_prompt_complete_list(u_int *, const char *); 49static char *status_prompt_complete_prefix(char **, u_int); 50static char *status_prompt_complete(struct session *, const char *); 51 52/* Status prompt history. */ 53#define PROMPT_HISTORY 100 54static char **status_prompt_hlist; 55static u_int status_prompt_hsize; 56 57/* Find the history file to load/save from/to. */ 58static char * 59status_prompt_find_history_file(void) 60{ 61 const char *home, *history_file; 62 char *path; 63 64 history_file = options_get_string(global_options, "history-file"); 65 if (*history_file == '\0') 66 return (NULL); 67 if (*history_file == '/') 68 return (xstrdup(history_file)); 69 70 if (history_file[0] != '~' || history_file[1] != '/') 71 return (NULL); 72 if ((home = find_home()) == NULL) 73 return (NULL); 74 xasprintf(&path, "%s%s", home, history_file + 1); 75 return (path); 76} 77 78/* Load status prompt history from file. */ 79void 80status_prompt_load_history(void) 81{ 82 FILE *f; 83 char *history_file, *line, *tmp; 84 size_t length; 85 86 if ((history_file = status_prompt_find_history_file()) == NULL) 87 return; 88 log_debug("loading history from %s", history_file); 89 90 f = fopen(history_file, "r"); 91 if (f == NULL) { 92 log_debug("%s: %s", history_file, strerror(errno)); 93 free(history_file); 94 return; 95 } 96 free(history_file); 97 98 for (;;) { 99 if ((line = fgetln(f, &length)) == NULL) 100 break; 101 102 if (length > 0) { 103 if (line[length - 1] == '\n') { 104 line[length - 1] = '\0'; 105 status_prompt_add_history(line); 106 } else { 107 tmp = xmalloc(length + 1); 108 memcpy(tmp, line, length); 109 tmp[length] = '\0'; 110 status_prompt_add_history(tmp); 111 free(tmp); 112 } 113 } 114 } 115 fclose(f); 116} 117 118/* Save status prompt history to file. */ 119void 120status_prompt_save_history(void) 121{ 122 FILE *f; 123 u_int i; 124 char *history_file; 125 126 if ((history_file = status_prompt_find_history_file()) == NULL) 127 return; 128 log_debug("saving history to %s", history_file); 129 130 f = fopen(history_file, "w"); 131 if (f == NULL) { 132 log_debug("%s: %s", history_file, strerror(errno)); 133 free(history_file); 134 return; 135 } 136 free(history_file); 137 138 for (i = 0; i < status_prompt_hsize; i++) { 139 fputs(status_prompt_hlist[i], f); 140 fputc('\n', f); 141 } 142 fclose(f); 143 144} 145 146/* Status timer callback. */ 147static void 148status_timer_callback(__unused int fd, __unused short events, void *arg) 149{ 150 struct client *c = arg; 151 struct session *s = c->session; 152 struct timeval tv; 153 154 evtimer_del(&c->status.timer); 155 156 if (s == NULL) 157 return; 158 159 if (c->message_string == NULL && c->prompt_string == NULL) 160 c->flags |= CLIENT_REDRAWSTATUS; 161 162 timerclear(&tv); 163 tv.tv_sec = options_get_number(s->options, "status-interval"); 164 165 if (tv.tv_sec != 0) 166 evtimer_add(&c->status.timer, &tv); 167 log_debug("client %p, status interval %d", c, (int)tv.tv_sec); 168} 169 170/* Start status timer for client. */ 171void 172status_timer_start(struct client *c) 173{ 174 struct session *s = c->session; 175 176 if (event_initialized(&c->status.timer)) 177 evtimer_del(&c->status.timer); 178 else 179 evtimer_set(&c->status.timer, status_timer_callback, c); 180 181 if (s != NULL && options_get_number(s->options, "status")) 182 status_timer_callback(-1, 0, c); 183} 184 185/* Start status timer for all clients. */ 186void 187status_timer_start_all(void) 188{ 189 struct client *c; 190 191 TAILQ_FOREACH(c, &clients, entry) 192 status_timer_start(c); 193} 194 195/* Update status cache. */ 196void 197status_update_cache(struct session *s) 198{ 199 if (!options_get_number(s->options, "status")) 200 s->statusat = -1; 201 else if (options_get_number(s->options, "status-position") == 0) 202 s->statusat = 0; 203 else 204 s->statusat = 1; 205} 206 207/* Get screen line of status line. -1 means off. */ 208int 209status_at_line(struct client *c) 210{ 211 struct session *s = c->session; 212 213 if (c->flags & CLIENT_STATUSOFF) 214 return (-1); 215 if (s->statusat != 1) 216 return (s->statusat); 217 return (c->tty.sy - status_line_size(c)); 218} 219 220/* Get size of status line for client's session. 0 means off. */ 221u_int 222status_line_size(struct client *c) 223{ 224 struct session *s = c->session; 225 226 if (c->flags & CLIENT_STATUSOFF) 227 return (0); 228 if (s->statusat == -1) 229 return (0); 230 return (1); 231} 232 233/* Retrieve options for left string. */ 234static char * 235status_redraw_get_left(struct client *c, struct grid_cell *gc, size_t *size) 236{ 237 struct session *s = c->session; 238 const char *template; 239 char *left; 240 size_t leftlen; 241 242 style_apply_update(gc, s->options, "status-left-style"); 243 244 template = options_get_string(s->options, "status-left"); 245 left = status_replace(c, NULL, template); 246 247 *size = options_get_number(s->options, "status-left-length"); 248 leftlen = screen_write_cstrlen("%s", left); 249 if (leftlen < *size) 250 *size = leftlen; 251 return (left); 252} 253 254/* Retrieve options for right string. */ 255static char * 256status_redraw_get_right(struct client *c, struct grid_cell *gc, size_t *size) 257{ 258 struct session *s = c->session; 259 const char *template; 260 char *right; 261 size_t rightlen; 262 263 style_apply_update(gc, s->options, "status-right-style"); 264 265 template = options_get_string(s->options, "status-right"); 266 right = status_replace(c, NULL, template); 267 268 *size = options_get_number(s->options, "status-right-length"); 269 rightlen = screen_write_cstrlen("%s", right); 270 if (rightlen < *size) 271 *size = rightlen; 272 return (right); 273} 274 275/* Get window at window list position. */ 276struct window * 277status_get_window_at(struct client *c, u_int x) 278{ 279 struct session *s = c->session; 280 struct winlink *wl; 281 struct options *oo; 282 const char *sep; 283 size_t seplen; 284 285 x += c->status.window_list_offset; 286 RB_FOREACH(wl, winlinks, &s->windows) { 287 oo = wl->window->options; 288 289 sep = options_get_string(oo, "window-status-separator"); 290 seplen = screen_write_cstrlen("%s", sep); 291 292 if (x < wl->status_width) 293 return (wl->window); 294 x -= wl->status_width + seplen; 295 } 296 return (NULL); 297} 298 299/* Save old status line. */ 300static void 301status_push_screen(struct client *c) 302{ 303 struct status_line *sl = &c->status; 304 305 if (sl->active == &sl->screen) { 306 sl->active = xmalloc(sizeof *sl->active); 307 screen_init(sl->active, c->tty.sx, status_line_size(c), 0); 308 } 309 sl->references++; 310} 311 312/* Restore old status line. */ 313static void 314status_pop_screen(struct client *c) 315{ 316 struct status_line *sl = &c->status; 317 318 if (--sl->references == 0) { 319 screen_free(sl->active); 320 free(sl->active); 321 sl->active = &sl->screen; 322 } 323} 324 325/* Initialize status line. */ 326void 327status_init(struct client *c) 328{ 329 struct status_line *sl = &c->status; 330 331 screen_init(&sl->screen, c->tty.sx, 1, 0); 332 sl->active = &sl->screen; 333} 334 335/* Free status line. */ 336void 337status_free(struct client *c) 338{ 339 struct status_line *sl = &c->status; 340 341 if (event_initialized(&sl->timer)) 342 evtimer_del(&sl->timer); 343 344 if (sl->active != &sl->screen) { 345 screen_free(sl->active); 346 free(sl->active); 347 } 348 screen_free(&sl->screen); 349} 350 351/* Draw status line for client. */ 352int 353status_redraw(struct client *c) 354{ 355 struct status_line *sl = &c->status; 356 struct screen_write_ctx ctx; 357 struct session *s = c->session; 358 struct winlink *wl; 359 struct screen old_screen, window_list; 360 struct grid_cell stdgc, lgc, rgc, gc; 361 struct options *oo; 362 char *left, *right; 363 const char *sep; 364 u_int offset, needed, lines; 365 u_int wlstart, wlwidth, wlavailable, wloffset, wlsize; 366 size_t llen, rlen, seplen; 367 int larrow, rarrow; 368 369 /* Shouldn't get here if not the active screen. */ 370 if (sl->active != &sl->screen) 371 fatalx("not the active screen"); 372 373 /* No status line? */ 374 lines = status_line_size(c); 375 if (c->tty.sy == 0 || lines == 0) 376 return (1); 377 left = right = NULL; 378 larrow = rarrow = 0; 379 380 /* Set up default colour. */ 381 style_apply(&stdgc, s->options, "status-style"); 382 383 /* Create the target screen. */ 384 memcpy(&old_screen, sl->active, sizeof old_screen); 385 screen_init(sl->active, c->tty.sx, lines, 0); 386 screen_write_start(&ctx, NULL, sl->active); 387 for (offset = 0; offset < lines * c->tty.sx; offset++) 388 screen_write_putc(&ctx, &stdgc, ' '); 389 screen_write_stop(&ctx); 390 391 /* If the height is too small, blank status line. */ 392 if (c->tty.sy < lines) 393 goto out; 394 395 /* Work out left and right strings. */ 396 memcpy(&lgc, &stdgc, sizeof lgc); 397 left = status_redraw_get_left(c, &lgc, &llen); 398 memcpy(&rgc, &stdgc, sizeof rgc); 399 right = status_redraw_get_right(c, &rgc, &rlen); 400 401 /* 402 * Figure out how much space we have for the window list. If there 403 * isn't enough space, just show a blank status line. 404 */ 405 needed = 0; 406 if (llen != 0) 407 needed += llen; 408 if (rlen != 0) 409 needed += rlen; 410 if (c->tty.sx == 0 || c->tty.sx <= needed) 411 goto out; 412 wlavailable = c->tty.sx - needed; 413 414 /* Calculate the total size needed for the window list. */ 415 wlstart = wloffset = wlwidth = 0; 416 RB_FOREACH(wl, winlinks, &s->windows) { 417 free(wl->status_text); 418 memcpy(&wl->status_cell, &stdgc, sizeof wl->status_cell); 419 wl->status_text = status_print(c, wl, &wl->status_cell); 420 wl->status_width = screen_write_cstrlen("%s", wl->status_text); 421 422 if (wl == s->curw) 423 wloffset = wlwidth; 424 425 oo = wl->window->options; 426 sep = options_get_string(oo, "window-status-separator"); 427 seplen = screen_write_cstrlen("%s", sep); 428 wlwidth += wl->status_width + seplen; 429 } 430 431 /* Create a new screen for the window list. */ 432 screen_init(&window_list, wlwidth, 1, 0); 433 434 /* And draw the window list into it. */ 435 screen_write_start(&ctx, NULL, &window_list); 436 RB_FOREACH(wl, winlinks, &s->windows) { 437 screen_write_cnputs(&ctx, -1, &wl->status_cell, "%s", 438 wl->status_text); 439 440 oo = wl->window->options; 441 sep = options_get_string(oo, "window-status-separator"); 442 screen_write_cnputs(&ctx, -1, &stdgc, "%s", sep); 443 } 444 screen_write_stop(&ctx); 445 446 /* If there is enough space for the total width, skip to draw now. */ 447 if (wlwidth <= wlavailable) 448 goto draw; 449 450 /* Find size of current window text. */ 451 wlsize = s->curw->status_width; 452 453 /* 454 * If the current window is already on screen, good to draw from the 455 * start and just leave off the end. 456 */ 457 if (wloffset + wlsize < wlavailable) { 458 if (wlavailable > 0) { 459 rarrow = 1; 460 wlavailable--; 461 } 462 wlwidth = wlavailable; 463 } else { 464 /* 465 * Work out how many characters we need to omit from the 466 * start. There are wlavailable characters to fill, and 467 * wloffset + wlsize must be the last. So, the start character 468 * is wloffset + wlsize - wlavailable. 469 */ 470 if (wlavailable > 0) { 471 larrow = 1; 472 wlavailable--; 473 } 474 475 wlstart = wloffset + wlsize - wlavailable; 476 if (wlavailable > 0 && wlwidth > wlstart + wlavailable + 1) { 477 rarrow = 1; 478 wlstart++; 479 wlavailable--; 480 } 481 wlwidth = wlavailable; 482 } 483 484 /* Bail if anything is now too small too. */ 485 if (wlwidth == 0 || wlavailable == 0) { 486 screen_free(&window_list); 487 goto out; 488 } 489 490 /* 491 * Now the start position is known, work out the state of the left and 492 * right arrows. 493 */ 494 offset = 0; 495 RB_FOREACH(wl, winlinks, &s->windows) { 496 if (wl->flags & WINLINK_ALERTFLAGS && 497 larrow == 1 && offset < wlstart) 498 larrow = -1; 499 500 offset += wl->status_width; 501 502 if (wl->flags & WINLINK_ALERTFLAGS && 503 rarrow == 1 && offset > wlstart + wlwidth) 504 rarrow = -1; 505 } 506 507draw: 508 /* Begin drawing. */ 509 screen_write_start(&ctx, NULL, sl->active); 510 511 /* Draw the left string and arrow. */ 512 screen_write_cursormove(&ctx, 0, 0, 0); 513 if (llen != 0) 514 screen_write_cnputs(&ctx, llen, &lgc, "%s", left); 515 if (larrow != 0) { 516 memcpy(&gc, &stdgc, sizeof gc); 517 if (larrow == -1) 518 gc.attr ^= GRID_ATTR_REVERSE; 519 screen_write_putc(&ctx, &gc, '<'); 520 } 521 522 /* Draw the right string and arrow. */ 523 if (rarrow != 0) { 524 screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, 0, 0); 525 memcpy(&gc, &stdgc, sizeof gc); 526 if (rarrow == -1) 527 gc.attr ^= GRID_ATTR_REVERSE; 528 screen_write_putc(&ctx, &gc, '>'); 529 } else 530 screen_write_cursormove(&ctx, c->tty.sx - rlen, 0, 0); 531 if (rlen != 0) 532 screen_write_cnputs(&ctx, rlen, &rgc, "%s", right); 533 534 /* Figure out the offset for the window list. */ 535 if (llen != 0) 536 wloffset = llen; 537 else 538 wloffset = 0; 539 if (wlwidth < wlavailable) { 540 switch (options_get_number(s->options, "status-justify")) { 541 case 1: /* centred */ 542 wloffset += (wlavailable - wlwidth) / 2; 543 break; 544 case 2: /* right */ 545 wloffset += (wlavailable - wlwidth); 546 break; 547 } 548 } 549 if (larrow != 0) 550 wloffset++; 551 552 /* Copy the window list. */ 553 sl->window_list_offset = -wloffset + wlstart; 554 screen_write_cursormove(&ctx, wloffset, 0, 0); 555 screen_write_fast_copy(&ctx, &window_list, wlstart, 0, wlwidth, 1); 556 screen_free(&window_list); 557 558 /* Save left and right size. */ 559 sl->left_size = llen; 560 sl->right_size = rlen; 561 562 screen_write_stop(&ctx); 563 564out: 565 free(left); 566 free(right); 567 568 if (grid_compare(sl->active->grid, old_screen.grid) == 0) { 569 screen_free(&old_screen); 570 return (0); 571 } 572 screen_free(&old_screen); 573 return (1); 574} 575 576/* Replace special sequences in fmt. */ 577static char * 578status_replace(struct client *c, struct winlink *wl, const char *fmt) 579{ 580 struct format_tree *ft; 581 char *expanded; 582 u_int tag; 583 584 if (fmt == NULL) 585 return (xstrdup("")); 586 587 if (wl != NULL) 588 tag = FORMAT_WINDOW|wl->window->id; 589 else 590 tag = FORMAT_NONE; 591 if (c->flags & CLIENT_STATUSFORCE) 592 ft = format_create(c, NULL, tag, FORMAT_STATUS|FORMAT_FORCE); 593 else 594 ft = format_create(c, NULL, tag, FORMAT_STATUS); 595 format_defaults(ft, c, NULL, wl, NULL); 596 597 expanded = format_expand_time(ft, fmt); 598 599 format_free(ft); 600 return (expanded); 601} 602 603/* Return winlink status line entry and adjust gc as necessary. */ 604static char * 605status_print(struct client *c, struct winlink *wl, struct grid_cell *gc) 606{ 607 struct options *oo = wl->window->options; 608 struct session *s = c->session; 609 const char *fmt; 610 char *text; 611 612 style_apply_update(gc, oo, "window-status-style"); 613 fmt = options_get_string(oo, "window-status-format"); 614 if (wl == s->curw) { 615 style_apply_update(gc, oo, "window-status-current-style"); 616 fmt = options_get_string(oo, "window-status-current-format"); 617 } 618 if (wl == TAILQ_FIRST(&s->lastw)) 619 style_apply_update(gc, oo, "window-status-last-style"); 620 621 if (wl->flags & WINLINK_BELL) 622 style_apply_update(gc, oo, "window-status-bell-style"); 623 else if (wl->flags & (WINLINK_ACTIVITY|WINLINK_SILENCE)) 624 style_apply_update(gc, oo, "window-status-activity-style"); 625 626 text = status_replace(c, wl, fmt); 627 return (text); 628} 629 630/* Set a status line message. */ 631void 632status_message_set(struct client *c, const char *fmt, ...) 633{ 634 struct timeval tv; 635 va_list ap; 636 int delay; 637 638 status_message_clear(c); 639 status_push_screen(c); 640 641 va_start(ap, fmt); 642 xvasprintf(&c->message_string, fmt, ap); 643 va_end(ap); 644 645 server_client_add_message(c, "%s", c->message_string); 646 647 delay = options_get_number(c->session->options, "display-time"); 648 if (delay > 0) { 649 tv.tv_sec = delay / 1000; 650 tv.tv_usec = (delay % 1000) * 1000L; 651 652 if (event_initialized(&c->message_timer)) 653 evtimer_del(&c->message_timer); 654 evtimer_set(&c->message_timer, status_message_callback, c); 655 evtimer_add(&c->message_timer, &tv); 656 } 657 658 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); 659 c->flags |= CLIENT_REDRAWSTATUS; 660} 661 662/* Clear status line message. */ 663void 664status_message_clear(struct client *c) 665{ 666 if (c->message_string == NULL) 667 return; 668 669 free(c->message_string); 670 c->message_string = NULL; 671 672 if (c->prompt_string == NULL) 673 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 674 c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */ 675 676 status_pop_screen(c); 677} 678 679/* Clear status line message after timer expires. */ 680static void 681status_message_callback(__unused int fd, __unused short event, void *data) 682{ 683 struct client *c = data; 684 685 status_message_clear(c); 686} 687 688/* Draw client message on status line of present else on last line. */ 689int 690status_message_redraw(struct client *c) 691{ 692 struct status_line *sl = &c->status; 693 struct screen_write_ctx ctx; 694 struct session *s = c->session; 695 struct screen old_screen; 696 size_t len; 697 u_int lines, offset; 698 struct grid_cell gc; 699 700 if (c->tty.sx == 0 || c->tty.sy == 0) 701 return (0); 702 memcpy(&old_screen, sl->active, sizeof old_screen); 703 704 lines = status_line_size(c); 705 if (lines <= 1) 706 lines = 1; 707 screen_init(sl->active, c->tty.sx, lines, 0); 708 709 len = screen_write_strlen("%s", c->message_string); 710 if (len > c->tty.sx) 711 len = c->tty.sx; 712 713 style_apply(&gc, s->options, "message-style"); 714 715 screen_write_start(&ctx, NULL, sl->active); 716 screen_write_cursormove(&ctx, 0, 0, 0); 717 for (offset = 0; offset < lines * c->tty.sx; offset++) 718 screen_write_putc(&ctx, &gc, ' '); 719 screen_write_cursormove(&ctx, 0, lines - 1, 0); 720 screen_write_nputs(&ctx, len, &gc, "%s", c->message_string); 721 screen_write_stop(&ctx); 722 723 if (grid_compare(sl->active->grid, old_screen.grid) == 0) { 724 screen_free(&old_screen); 725 return (0); 726 } 727 screen_free(&old_screen); 728 return (1); 729} 730 731/* Enable status line prompt. */ 732void 733status_prompt_set(struct client *c, const char *msg, const char *input, 734 prompt_input_cb inputcb, prompt_free_cb freecb, void *data, int flags) 735{ 736 struct format_tree *ft; 737 char *tmp, *cp; 738 739 ft = format_create(c, NULL, FORMAT_NONE, 0); 740 format_defaults(ft, c, NULL, NULL, NULL); 741 742 if (input == NULL) 743 input = ""; 744 if (flags & PROMPT_NOFORMAT) 745 tmp = xstrdup(input); 746 else 747 tmp = format_expand_time(ft, input); 748 749 status_message_clear(c); 750 status_prompt_clear(c); 751 status_push_screen(c); 752 753 c->prompt_string = format_expand_time(ft, msg); 754 755 c->prompt_buffer = utf8_fromcstr(tmp); 756 c->prompt_index = utf8_strlen(c->prompt_buffer); 757 758 c->prompt_inputcb = inputcb; 759 c->prompt_freecb = freecb; 760 c->prompt_data = data; 761 762 c->prompt_hindex = 0; 763 764 c->prompt_flags = flags; 765 c->prompt_mode = PROMPT_ENTRY; 766 767 if (~flags & PROMPT_INCREMENTAL) 768 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); 769 c->flags |= CLIENT_REDRAWSTATUS; 770 771 if ((flags & PROMPT_INCREMENTAL) && *tmp != '\0') { 772 xasprintf(&cp, "=%s", tmp); 773 c->prompt_inputcb(c, c->prompt_data, cp, 0); 774 free(cp); 775 } 776 777 free(tmp); 778 format_free(ft); 779} 780 781/* Remove status line prompt. */ 782void 783status_prompt_clear(struct client *c) 784{ 785 if (c->prompt_string == NULL) 786 return; 787 788 if (c->prompt_freecb != NULL && c->prompt_data != NULL) 789 c->prompt_freecb(c->prompt_data); 790 791 free(c->prompt_string); 792 c->prompt_string = NULL; 793 794 free(c->prompt_buffer); 795 c->prompt_buffer = NULL; 796 797 free(c->prompt_saved); 798 c->prompt_saved = NULL; 799 800 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 801 c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */ 802 803 status_pop_screen(c); 804} 805 806/* Update status line prompt with a new prompt string. */ 807void 808status_prompt_update(struct client *c, const char *msg, const char *input) 809{ 810 struct format_tree *ft; 811 char *tmp; 812 813 ft = format_create(c, NULL, FORMAT_NONE, 0); 814 format_defaults(ft, c, NULL, NULL, NULL); 815 816 tmp = format_expand_time(ft, input); 817 818 free(c->prompt_string); 819 c->prompt_string = format_expand_time(ft, msg); 820 821 free(c->prompt_buffer); 822 c->prompt_buffer = utf8_fromcstr(tmp); 823 c->prompt_index = utf8_strlen(c->prompt_buffer); 824 825 c->prompt_hindex = 0; 826 827 c->flags |= CLIENT_REDRAWSTATUS; 828 829 free(tmp); 830 format_free(ft); 831} 832 833/* Draw client prompt on status line of present else on last line. */ 834int 835status_prompt_redraw(struct client *c) 836{ 837 struct status_line *sl = &c->status; 838 struct screen_write_ctx ctx; 839 struct session *s = c->session; 840 struct screen old_screen; 841 u_int i, lines, offset, left, start, width; 842 u_int pcursor, pwidth; 843 struct grid_cell gc, cursorgc; 844 845 if (c->tty.sx == 0 || c->tty.sy == 0) 846 return (0); 847 memcpy(&old_screen, sl->active, sizeof old_screen); 848 849 lines = status_line_size(c); 850 if (lines <= 1) 851 lines = 1; 852 screen_init(sl->active, c->tty.sx, lines, 0); 853 854 if (c->prompt_mode == PROMPT_COMMAND) 855 style_apply(&gc, s->options, "message-command-style"); 856 else 857 style_apply(&gc, s->options, "message-style"); 858 859 memcpy(&cursorgc, &gc, sizeof cursorgc); 860 cursorgc.attr ^= GRID_ATTR_REVERSE; 861 862 start = screen_write_strlen("%s", c->prompt_string); 863 if (start > c->tty.sx) 864 start = c->tty.sx; 865 866 screen_write_start(&ctx, NULL, sl->active); 867 screen_write_cursormove(&ctx, 0, 0, 0); 868 for (offset = 0; offset < lines * c->tty.sx; offset++) 869 screen_write_putc(&ctx, &gc, ' '); 870 screen_write_cursormove(&ctx, 0, 0, 0); 871 screen_write_nputs(&ctx, start, &gc, "%s", c->prompt_string); 872 screen_write_cursormove(&ctx, start, 0, 0); 873 874 left = c->tty.sx - start; 875 if (left == 0) 876 goto finished; 877 878 pcursor = utf8_strwidth(c->prompt_buffer, c->prompt_index); 879 pwidth = utf8_strwidth(c->prompt_buffer, -1); 880 if (pcursor >= left) { 881 /* 882 * The cursor would be outside the screen so start drawing 883 * with it on the right. 884 */ 885 offset = (pcursor - left) + 1; 886 pwidth = left; 887 } else 888 offset = 0; 889 if (pwidth > left) 890 pwidth = left; 891 892 width = 0; 893 for (i = 0; c->prompt_buffer[i].size != 0; i++) { 894 if (width < offset) { 895 width += c->prompt_buffer[i].width; 896 continue; 897 } 898 if (width >= offset + pwidth) 899 break; 900 width += c->prompt_buffer[i].width; 901 if (width > offset + pwidth) 902 break; 903 904 if (i != c->prompt_index) { 905 utf8_copy(&gc.data, &c->prompt_buffer[i]); 906 screen_write_cell(&ctx, &gc); 907 } else { 908 utf8_copy(&cursorgc.data, &c->prompt_buffer[i]); 909 screen_write_cell(&ctx, &cursorgc); 910 } 911 } 912 if (sl->active->cx < screen_size_x(sl->active) && c->prompt_index >= i) 913 screen_write_putc(&ctx, &cursorgc, ' '); 914 915finished: 916 screen_write_stop(&ctx); 917 918 if (grid_compare(sl->active->grid, old_screen.grid) == 0) { 919 screen_free(&old_screen); 920 return (0); 921 } 922 screen_free(&old_screen); 923 return (1); 924} 925 926/* Is this a separator? */ 927static int 928status_prompt_in_list(const char *ws, const struct utf8_data *ud) 929{ 930 if (ud->size != 1 || ud->width != 1) 931 return (0); 932 return (strchr(ws, *ud->data) != NULL); 933} 934 935/* Is this a space? */ 936static int 937status_prompt_space(const struct utf8_data *ud) 938{ 939 if (ud->size != 1 || ud->width != 1) 940 return (0); 941 return (*ud->data == ' '); 942} 943 944/* 945 * Translate key from emacs to vi. Return 0 to drop key, 1 to process the key 946 * as an emacs key; return 2 to append to the buffer. 947 */ 948static int 949status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) 950{ 951 if (c->prompt_mode == PROMPT_ENTRY) { 952 switch (key) { 953 case '\003': /* C-c */ 954 case '\010': /* C-h */ 955 case '\011': /* Tab */ 956 case '\025': /* C-u */ 957 case '\027': /* C-w */ 958 case '\n': 959 case '\r': 960 case KEYC_BSPACE: 961 case KEYC_DC: 962 case KEYC_DOWN: 963 case KEYC_END: 964 case KEYC_HOME: 965 case KEYC_LEFT: 966 case KEYC_RIGHT: 967 case KEYC_UP: 968 *new_key = key; 969 return (1); 970 case '\033': /* Escape */ 971 c->prompt_mode = PROMPT_COMMAND; 972 c->flags |= CLIENT_REDRAWSTATUS; 973 return (0); 974 } 975 *new_key = key; 976 return (2); 977 } 978 979 switch (key) { 980 case 'A': 981 case 'I': 982 case 'C': 983 case 's': 984 case 'a': 985 c->prompt_mode = PROMPT_ENTRY; 986 c->flags |= CLIENT_REDRAWSTATUS; 987 break; /* switch mode and... */ 988 case 'S': 989 c->prompt_mode = PROMPT_ENTRY; 990 c->flags |= CLIENT_REDRAWSTATUS; 991 *new_key = '\025'; /* C-u */ 992 return (1); 993 case 'i': 994 case '\033': /* Escape */ 995 c->prompt_mode = PROMPT_ENTRY; 996 c->flags |= CLIENT_REDRAWSTATUS; 997 return (0); 998 } 999 1000 switch (key) { 1001 case 'A': 1002 case '$': 1003 *new_key = KEYC_END; 1004 return (1); 1005 case 'I': 1006 case '0': 1007 case '^': 1008 *new_key = KEYC_HOME; 1009 return (1); 1010 case 'C': 1011 case 'D': 1012 *new_key = '\013'; /* C-k */ 1013 return (1); 1014 case KEYC_BSPACE: 1015 case 'X': 1016 *new_key = KEYC_BSPACE; 1017 return (1); 1018 case 'b': 1019 case 'B': 1020 *new_key = 'b'|KEYC_ESCAPE; 1021 return (1); 1022 case 'd': 1023 *new_key = '\025'; 1024 return (1); 1025 case 'e': 1026 case 'E': 1027 case 'w': 1028 case 'W': 1029 *new_key = 'f'|KEYC_ESCAPE; 1030 return (1); 1031 case 'p': 1032 *new_key = '\031'; /* C-y */ 1033 return (1); 1034 case 's': 1035 case KEYC_DC: 1036 case 'x': 1037 *new_key = KEYC_DC; 1038 return (1); 1039 case KEYC_DOWN: 1040 case 'j': 1041 *new_key = KEYC_DOWN; 1042 return (1); 1043 case KEYC_LEFT: 1044 case 'h': 1045 *new_key = KEYC_LEFT; 1046 return (1); 1047 case 'a': 1048 case KEYC_RIGHT: 1049 case 'l': 1050 *new_key = KEYC_RIGHT; 1051 return (1); 1052 case KEYC_UP: 1053 case 'k': 1054 *new_key = KEYC_UP; 1055 return (1); 1056 case '\010' /* C-h */: 1057 case '\003' /* C-c */: 1058 case '\n': 1059 case '\r': 1060 return (1); 1061 } 1062 return (0); 1063} 1064 1065/* Handle keys in prompt. */ 1066int 1067status_prompt_key(struct client *c, key_code key) 1068{ 1069 struct options *oo = c->session->options; 1070 struct paste_buffer *pb; 1071 char *s, *cp, word[64], prefix = '='; 1072 const char *histstr, *bufdata, *ws = NULL; 1073 u_char ch; 1074 size_t size, n, off, idx, bufsize, used; 1075 struct utf8_data tmp, *first, *last, *ud; 1076 int keys; 1077 1078 size = utf8_strlen(c->prompt_buffer); 1079 1080 if (c->prompt_flags & PROMPT_NUMERIC) { 1081 if (key >= '0' && key <= '9') 1082 goto append_key; 1083 s = utf8_tocstr(c->prompt_buffer); 1084 c->prompt_inputcb(c, c->prompt_data, s, 1); 1085 status_prompt_clear(c); 1086 free(s); 1087 return (1); 1088 } 1089 key &= ~KEYC_XTERM; 1090 1091 keys = options_get_number(c->session->options, "status-keys"); 1092 if (keys == MODEKEY_VI) { 1093 switch (status_prompt_translate_key(c, key, &key)) { 1094 case 1: 1095 goto process_key; 1096 case 2: 1097 goto append_key; 1098 default: 1099 return (0); 1100 } 1101 } 1102 1103process_key: 1104 switch (key) { 1105 case KEYC_LEFT: 1106 case '\002': /* C-b */ 1107 if (c->prompt_index > 0) { 1108 c->prompt_index--; 1109 break; 1110 } 1111 break; 1112 case KEYC_RIGHT: 1113 case '\006': /* C-f */ 1114 if (c->prompt_index < size) { 1115 c->prompt_index++; 1116 break; 1117 } 1118 break; 1119 case KEYC_HOME: 1120 case '\001': /* C-a */ 1121 if (c->prompt_index != 0) { 1122 c->prompt_index = 0; 1123 break; 1124 } 1125 break; 1126 case KEYC_END: 1127 case '\005': /* C-e */ 1128 if (c->prompt_index != size) { 1129 c->prompt_index = size; 1130 break; 1131 } 1132 break; 1133 case '\011': /* Tab */ 1134 if (c->prompt_buffer[0].size == 0) 1135 break; 1136 1137 idx = c->prompt_index; 1138 if (idx != 0) 1139 idx--; 1140 1141 /* Find the word we are in. */ 1142 first = &c->prompt_buffer[idx]; 1143 while (first > c->prompt_buffer && !status_prompt_space(first)) 1144 first--; 1145 while (first->size != 0 && status_prompt_space(first)) 1146 first++; 1147 last = &c->prompt_buffer[idx]; 1148 while (last->size != 0 && !status_prompt_space(last)) 1149 last++; 1150 while (last > c->prompt_buffer && status_prompt_space(last)) 1151 last--; 1152 if (last->size != 0) 1153 last++; 1154 if (last <= first) 1155 break; 1156 1157 used = 0; 1158 for (ud = first; ud < last; ud++) { 1159 if (used + ud->size >= sizeof word) 1160 break; 1161 memcpy(word + used, ud->data, ud->size); 1162 used += ud->size; 1163 } 1164 if (ud != last) 1165 break; 1166 word[used] = '\0'; 1167 1168 /* And try to complete it. */ 1169 if ((s = status_prompt_complete(c->session, word)) == NULL) 1170 break; 1171 1172 /* Trim out word. */ 1173 n = size - (last - c->prompt_buffer) + 1; /* with \0 */ 1174 memmove(first, last, n * sizeof *c->prompt_buffer); 1175 size -= last - first; 1176 1177 /* Insert the new word. */ 1178 size += strlen(s); 1179 off = first - c->prompt_buffer; 1180 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 1, 1181 sizeof *c->prompt_buffer); 1182 first = c->prompt_buffer + off; 1183 memmove(first + strlen(s), first, n * sizeof *c->prompt_buffer); 1184 for (idx = 0; idx < strlen(s); idx++) 1185 utf8_set(&first[idx], s[idx]); 1186 1187 c->prompt_index = (first - c->prompt_buffer) + strlen(s); 1188 free(s); 1189 1190 goto changed; 1191 case KEYC_BSPACE: 1192 case '\010': /* C-h */ 1193 if (c->prompt_index != 0) { 1194 if (c->prompt_index == size) 1195 c->prompt_buffer[--c->prompt_index].size = 0; 1196 else { 1197 memmove(c->prompt_buffer + c->prompt_index - 1, 1198 c->prompt_buffer + c->prompt_index, 1199 (size + 1 - c->prompt_index) * 1200 sizeof *c->prompt_buffer); 1201 c->prompt_index--; 1202 } 1203 goto changed; 1204 } 1205 break; 1206 case KEYC_DC: 1207 case '\004': /* C-d */ 1208 if (c->prompt_index != size) { 1209 memmove(c->prompt_buffer + c->prompt_index, 1210 c->prompt_buffer + c->prompt_index + 1, 1211 (size + 1 - c->prompt_index) * 1212 sizeof *c->prompt_buffer); 1213 goto changed; 1214 } 1215 break; 1216 case '\025': /* C-u */ 1217 c->prompt_buffer[0].size = 0; 1218 c->prompt_index = 0; 1219 goto changed; 1220 case '\013': /* C-k */ 1221 if (c->prompt_index < size) { 1222 c->prompt_buffer[c->prompt_index].size = 0; 1223 goto changed; 1224 } 1225 break; 1226 case '\027': /* C-w */ 1227 ws = options_get_string(oo, "word-separators"); 1228 idx = c->prompt_index; 1229 1230 /* Find a non-separator. */ 1231 while (idx != 0) { 1232 idx--; 1233 if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1234 break; 1235 } 1236 1237 /* Find the separator at the beginning of the word. */ 1238 while (idx != 0) { 1239 idx--; 1240 if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) { 1241 /* Go back to the word. */ 1242 idx++; 1243 break; 1244 } 1245 } 1246 1247 free(c->prompt_saved); 1248 c->prompt_saved = xcalloc(sizeof *c->prompt_buffer, 1249 (c->prompt_index - idx) + 1); 1250 memcpy(c->prompt_saved, c->prompt_buffer + idx, 1251 (c->prompt_index - idx) * sizeof *c->prompt_buffer); 1252 1253 memmove(c->prompt_buffer + idx, 1254 c->prompt_buffer + c->prompt_index, 1255 (size + 1 - c->prompt_index) * 1256 sizeof *c->prompt_buffer); 1257 memset(c->prompt_buffer + size - (c->prompt_index - idx), 1258 '\0', (c->prompt_index - idx) * sizeof *c->prompt_buffer); 1259 c->prompt_index = idx; 1260 1261 goto changed; 1262 case 'f'|KEYC_ESCAPE: 1263 case KEYC_RIGHT|KEYC_CTRL: 1264 ws = options_get_string(oo, "word-separators"); 1265 1266 /* Find a word. */ 1267 while (c->prompt_index != size) { 1268 idx = ++c->prompt_index; 1269 if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1270 break; 1271 } 1272 1273 /* Find the separator at the end of the word. */ 1274 while (c->prompt_index != size) { 1275 idx = ++c->prompt_index; 1276 if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1277 break; 1278 } 1279 1280 /* Back up to the end-of-word like vi. */ 1281 if (options_get_number(oo, "status-keys") == MODEKEY_VI && 1282 c->prompt_index != 0) 1283 c->prompt_index--; 1284 1285 goto changed; 1286 case 'b'|KEYC_ESCAPE: 1287 case KEYC_LEFT|KEYC_CTRL: 1288 ws = options_get_string(oo, "word-separators"); 1289 1290 /* Find a non-separator. */ 1291 while (c->prompt_index != 0) { 1292 idx = --c->prompt_index; 1293 if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1294 break; 1295 } 1296 1297 /* Find the separator at the beginning of the word. */ 1298 while (c->prompt_index != 0) { 1299 idx = --c->prompt_index; 1300 if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) { 1301 /* Go back to the word. */ 1302 c->prompt_index++; 1303 break; 1304 } 1305 } 1306 goto changed; 1307 case KEYC_UP: 1308 case '\020': /* C-p */ 1309 histstr = status_prompt_up_history(&c->prompt_hindex); 1310 if (histstr == NULL) 1311 break; 1312 free(c->prompt_buffer); 1313 c->prompt_buffer = utf8_fromcstr(histstr); 1314 c->prompt_index = utf8_strlen(c->prompt_buffer); 1315 goto changed; 1316 case KEYC_DOWN: 1317 case '\016': /* C-n */ 1318 histstr = status_prompt_down_history(&c->prompt_hindex); 1319 if (histstr == NULL) 1320 break; 1321 free(c->prompt_buffer); 1322 c->prompt_buffer = utf8_fromcstr(histstr); 1323 c->prompt_index = utf8_strlen(c->prompt_buffer); 1324 goto changed; 1325 case '\031': /* C-y */ 1326 if (c->prompt_saved != NULL) { 1327 ud = c->prompt_saved; 1328 n = utf8_strlen(c->prompt_saved); 1329 } else { 1330 if ((pb = paste_get_top(NULL)) == NULL) 1331 break; 1332 bufdata = paste_buffer_data(pb, &bufsize); 1333 for (n = 0; n < bufsize; n++) { 1334 ch = (u_char)bufdata[n]; 1335 if (ch < 32 || ch >= 127) 1336 break; 1337 } 1338 ud = xreallocarray(NULL, n, sizeof *ud); 1339 for (idx = 0; idx < n; idx++) 1340 utf8_set(&ud[idx], bufdata[idx]); 1341 } 1342 1343 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + n + 1, 1344 sizeof *c->prompt_buffer); 1345 if (c->prompt_index == size) { 1346 memcpy(c->prompt_buffer + c->prompt_index, ud, 1347 n * sizeof *c->prompt_buffer); 1348 c->prompt_index += n; 1349 c->prompt_buffer[c->prompt_index].size = 0; 1350 } else { 1351 memmove(c->prompt_buffer + c->prompt_index + n, 1352 c->prompt_buffer + c->prompt_index, 1353 (size + 1 - c->prompt_index) * 1354 sizeof *c->prompt_buffer); 1355 memcpy(c->prompt_buffer + c->prompt_index, ud, 1356 n * sizeof *c->prompt_buffer); 1357 c->prompt_index += n; 1358 } 1359 1360 if (ud != c->prompt_saved) 1361 free(ud); 1362 goto changed; 1363 case '\024': /* C-t */ 1364 idx = c->prompt_index; 1365 if (idx < size) 1366 idx++; 1367 if (idx >= 2) { 1368 utf8_copy(&tmp, &c->prompt_buffer[idx - 2]); 1369 utf8_copy(&c->prompt_buffer[idx - 2], 1370 &c->prompt_buffer[idx - 1]); 1371 utf8_copy(&c->prompt_buffer[idx - 1], &tmp); 1372 c->prompt_index = idx; 1373 goto changed; 1374 } 1375 break; 1376 case '\r': 1377 case '\n': 1378 s = utf8_tocstr(c->prompt_buffer); 1379 if (*s != '\0') 1380 status_prompt_add_history(s); 1381 if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) 1382 status_prompt_clear(c); 1383 free(s); 1384 break; 1385 case '\033': /* Escape */ 1386 case '\003': /* C-c */ 1387 case '\007': /* C-g */ 1388 if (c->prompt_inputcb(c, c->prompt_data, NULL, 1) == 0) 1389 status_prompt_clear(c); 1390 break; 1391 case '\022': /* C-r */ 1392 if (c->prompt_flags & PROMPT_INCREMENTAL) { 1393 prefix = '-'; 1394 goto changed; 1395 } 1396 break; 1397 case '\023': /* C-s */ 1398 if (c->prompt_flags & PROMPT_INCREMENTAL) { 1399 prefix = '+'; 1400 goto changed; 1401 } 1402 break; 1403 default: 1404 goto append_key; 1405 } 1406 1407 c->flags |= CLIENT_REDRAWSTATUS; 1408 return (0); 1409 1410append_key: 1411 if (key <= 0x1f || key >= KEYC_BASE) 1412 return (0); 1413 if (utf8_split(key, &tmp) != UTF8_DONE) 1414 return (0); 1415 1416 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 2, 1417 sizeof *c->prompt_buffer); 1418 1419 if (c->prompt_index == size) { 1420 utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp); 1421 c->prompt_index++; 1422 c->prompt_buffer[c->prompt_index].size = 0; 1423 } else { 1424 memmove(c->prompt_buffer + c->prompt_index + 1, 1425 c->prompt_buffer + c->prompt_index, 1426 (size + 1 - c->prompt_index) * 1427 sizeof *c->prompt_buffer); 1428 utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp); 1429 c->prompt_index++; 1430 } 1431 1432 if (c->prompt_flags & PROMPT_SINGLE) { 1433 s = utf8_tocstr(c->prompt_buffer); 1434 if (strlen(s) != 1) 1435 status_prompt_clear(c); 1436 else if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) 1437 status_prompt_clear(c); 1438 free(s); 1439 } 1440 1441changed: 1442 c->flags |= CLIENT_REDRAWSTATUS; 1443 if (c->prompt_flags & PROMPT_INCREMENTAL) { 1444 s = utf8_tocstr(c->prompt_buffer); 1445 xasprintf(&cp, "%c%s", prefix, s); 1446 c->prompt_inputcb(c, c->prompt_data, cp, 0); 1447 free(cp); 1448 free(s); 1449 } 1450 return (0); 1451} 1452 1453/* Get previous line from the history. */ 1454static const char * 1455status_prompt_up_history(u_int *idx) 1456{ 1457 /* 1458 * History runs from 0 to size - 1. Index is from 0 to size. Zero is 1459 * empty. 1460 */ 1461 1462 if (status_prompt_hsize == 0 || *idx == status_prompt_hsize) 1463 return (NULL); 1464 (*idx)++; 1465 return (status_prompt_hlist[status_prompt_hsize - *idx]); 1466} 1467 1468/* Get next line from the history. */ 1469static const char * 1470status_prompt_down_history(u_int *idx) 1471{ 1472 if (status_prompt_hsize == 0 || *idx == 0) 1473 return (""); 1474 (*idx)--; 1475 if (*idx == 0) 1476 return (""); 1477 return (status_prompt_hlist[status_prompt_hsize - *idx]); 1478} 1479 1480/* Add line to the history. */ 1481static void 1482status_prompt_add_history(const char *line) 1483{ 1484 size_t size; 1485 1486 if (status_prompt_hsize > 0 && 1487 strcmp(status_prompt_hlist[status_prompt_hsize - 1], line) == 0) 1488 return; 1489 1490 if (status_prompt_hsize == PROMPT_HISTORY) { 1491 free(status_prompt_hlist[0]); 1492 1493 size = (PROMPT_HISTORY - 1) * sizeof *status_prompt_hlist; 1494 memmove(&status_prompt_hlist[0], &status_prompt_hlist[1], size); 1495 1496 status_prompt_hlist[status_prompt_hsize - 1] = xstrdup(line); 1497 return; 1498 } 1499 1500 status_prompt_hlist = xreallocarray(status_prompt_hlist, 1501 status_prompt_hsize + 1, sizeof *status_prompt_hlist); 1502 status_prompt_hlist[status_prompt_hsize++] = xstrdup(line); 1503} 1504 1505/* Build completion list. */ 1506char ** 1507status_prompt_complete_list(u_int *size, const char *s) 1508{ 1509 char **list = NULL; 1510 const char **layout, *value, *cp; 1511 const struct cmd_entry **cmdent; 1512 const struct options_table_entry *oe; 1513 u_int idx; 1514 size_t slen = strlen(s), valuelen; 1515 struct options_entry *o; 1516 struct options_array_item *a; 1517 const char *layouts[] = { 1518 "even-horizontal", "even-vertical", "main-horizontal", 1519 "main-vertical", "tiled", NULL 1520 }; 1521 1522 *size = 0; 1523 for (cmdent = cmd_table; *cmdent != NULL; cmdent++) { 1524 if (strncmp((*cmdent)->name, s, slen) == 0) { 1525 list = xreallocarray(list, (*size) + 1, sizeof *list); 1526 list[(*size)++] = xstrdup((*cmdent)->name); 1527 } 1528 } 1529 for (oe = options_table; oe->name != NULL; oe++) { 1530 if (strncmp(oe->name, s, slen) == 0) { 1531 list = xreallocarray(list, (*size) + 1, sizeof *list); 1532 list[(*size)++] = xstrdup(oe->name); 1533 } 1534 } 1535 for (layout = layouts; *layout != NULL; layout++) { 1536 if (strncmp(*layout, s, slen) == 0) { 1537 list = xreallocarray(list, (*size) + 1, sizeof *list); 1538 list[(*size)++] = xstrdup(*layout); 1539 } 1540 } 1541 o = options_get_only(global_options, "command-alias"); 1542 if (o != NULL) { 1543 a = options_array_first(o); 1544 while (a != NULL) { 1545 value = options_array_item_value(a);; 1546 if (value == NULL || (cp = strchr(value, '=')) == NULL) 1547 goto next; 1548 1549 valuelen = cp - value; 1550 if (slen > valuelen || strncmp(value, s, slen) != 0) 1551 goto next; 1552 1553 list = xreallocarray(list, (*size) + 1, sizeof *list); 1554 list[(*size)++] = xstrndup(value, valuelen); 1555 1556 next: 1557 a = options_array_next(a); 1558 } 1559 } 1560 for (idx = 0; idx < (*size); idx++) 1561 log_debug("complete %u: %s", idx, list[idx]); 1562 return (list); 1563} 1564 1565/* Find longest prefix. */ 1566static char * 1567status_prompt_complete_prefix(char **list, u_int size) 1568{ 1569 char *out; 1570 u_int i; 1571 size_t j; 1572 1573 out = xstrdup(list[0]); 1574 for (i = 1; i < size; i++) { 1575 j = strlen(list[i]); 1576 if (j > strlen(out)) 1577 j = strlen(out); 1578 for (; j > 0; j--) { 1579 if (out[j - 1] != list[i][j - 1]) 1580 out[j - 1] = '\0'; 1581 } 1582 } 1583 return (out); 1584} 1585 1586/* Complete word. */ 1587static char * 1588status_prompt_complete(struct session *session, const char *s) 1589{ 1590 char **list = NULL; 1591 const char *colon; 1592 u_int size = 0, i; 1593 struct session *s_loop; 1594 struct winlink *wl; 1595 struct window *w; 1596 char *copy, *out, *tmp; 1597 1598 if (*s == '\0') 1599 return (NULL); 1600 out = NULL; 1601 1602 if (strncmp(s, "-t", 2) != 0 && strncmp(s, "-s", 2) != 0) { 1603 list = status_prompt_complete_list(&size, s); 1604 if (size == 0) 1605 out = NULL; 1606 else if (size == 1) 1607 xasprintf(&out, "%s ", list[0]); 1608 else 1609 out = status_prompt_complete_prefix(list, size); 1610 for (i = 0; i < size; i++) 1611 free(list[i]); 1612 free(list); 1613 return (out); 1614 } 1615 copy = xstrdup(s); 1616 1617 colon = ":"; 1618 if (copy[strlen(copy) - 1] == ':') 1619 copy[strlen(copy) - 1] = '\0'; 1620 else 1621 colon = ""; 1622 s = copy + 2; 1623 1624 RB_FOREACH(s_loop, sessions, &sessions) { 1625 if (strncmp(s_loop->name, s, strlen(s)) == 0) { 1626 list = xreallocarray(list, size + 2, sizeof *list); 1627 list[size++] = s_loop->name; 1628 } 1629 } 1630 if (size == 1) { 1631 out = xstrdup(list[0]); 1632 if (session_find(list[0]) != NULL) 1633 colon = ":"; 1634 } else if (size != 0) 1635 out = status_prompt_complete_prefix(list, size); 1636 if (out != NULL) { 1637 xasprintf(&tmp, "-%c%s%s", copy[1], out, colon); 1638 free(out); 1639 out = tmp; 1640 goto found; 1641 } 1642 1643 colon = ""; 1644 if (*s == ':') { 1645 RB_FOREACH(wl, winlinks, &session->windows) { 1646 xasprintf(&tmp, ":%s", wl->window->name); 1647 if (strncmp(tmp, s, strlen(s)) == 0){ 1648 list = xreallocarray(list, size + 1, 1649 sizeof *list); 1650 list[size++] = tmp; 1651 continue; 1652 } 1653 free(tmp); 1654 1655 xasprintf(&tmp, ":%d", wl->idx); 1656 if (strncmp(tmp, s, strlen(s)) == 0) { 1657 list = xreallocarray(list, size + 1, 1658 sizeof *list); 1659 list[size++] = tmp; 1660 continue; 1661 } 1662 free(tmp); 1663 } 1664 } else { 1665 RB_FOREACH(s_loop, sessions, &sessions) { 1666 RB_FOREACH(wl, winlinks, &s_loop->windows) { 1667 w = wl->window; 1668 1669 xasprintf(&tmp, "%s:%s", s_loop->name, w->name); 1670 if (strncmp(tmp, s, strlen(s)) == 0) { 1671 list = xreallocarray(list, size + 1, 1672 sizeof *list); 1673 list[size++] = tmp; 1674 continue; 1675 } 1676 free(tmp); 1677 1678 xasprintf(&tmp, "%s:%d", s_loop->name, wl->idx); 1679 if (strncmp(tmp, s, strlen(s)) == 0) { 1680 list = xreallocarray(list, size + 1, 1681 sizeof *list); 1682 list[size++] = tmp; 1683 continue; 1684 } 1685 free(tmp); 1686 } 1687 } 1688 } 1689 if (size == 1) { 1690 out = xstrdup(list[0]); 1691 colon = " "; 1692 } else if (size != 0) 1693 out = status_prompt_complete_prefix(list, size); 1694 if (out != NULL) { 1695 xasprintf(&tmp, "-%c%s%s", copy[1], out, colon); 1696 out = tmp; 1697 } 1698 1699 for (i = 0; i < size; i++) 1700 free((void *)list[i]); 1701 1702found: 1703 free(copy); 1704 free(list); 1705 return (out); 1706} 1707