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