status.c revision 1.183
1/* $OpenBSD: status.c,v 1.183 2019/02/09 18:18:36 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 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_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(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, 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(c); 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); 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 free(c->prompt_saved); 770 c->prompt_saved = NULL; 771 772 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 773 c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */ 774 775 screen_reinit(&c->status.status); 776} 777 778/* Update status line prompt with a new prompt string. */ 779void 780status_prompt_update(struct client *c, const char *msg, const char *input) 781{ 782 struct format_tree *ft; 783 time_t t; 784 char *tmp; 785 786 ft = format_create(c, NULL, FORMAT_NONE, 0); 787 format_defaults(ft, c, NULL, NULL, NULL); 788 789 t = time(NULL); 790 tmp = format_expand_time(ft, input, t); 791 792 free(c->prompt_string); 793 c->prompt_string = format_expand_time(ft, msg, t); 794 795 free(c->prompt_buffer); 796 c->prompt_buffer = utf8_fromcstr(tmp); 797 c->prompt_index = utf8_strlen(c->prompt_buffer); 798 799 c->prompt_hindex = 0; 800 801 c->flags |= CLIENT_REDRAWSTATUS; 802 803 free(tmp); 804 format_free(ft); 805} 806 807/* Draw client prompt on status line of present else on last line. */ 808int 809status_prompt_redraw(struct client *c) 810{ 811 struct screen_write_ctx ctx; 812 struct session *s = c->session; 813 struct screen old_status; 814 u_int i, offset, left, start, pcursor, pwidth, width; 815 u_int lines; 816 struct grid_cell gc, cursorgc; 817 818 if (c->tty.sx == 0 || c->tty.sy == 0) 819 return (0); 820 memcpy(&old_status, &c->status.status, sizeof old_status); 821 822 lines = status_line_size(c); 823 if (lines <= 1) { 824 lines = 1; 825 screen_init(&c->status.status, c->tty.sx, 1, 0); 826 } else 827 screen_init(&c->status.status, c->tty.sx, lines, 0); 828 829 if (c->prompt_mode == PROMPT_COMMAND) 830 style_apply(&gc, s->options, "message-command-style"); 831 else 832 style_apply(&gc, s->options, "message-style"); 833 834 memcpy(&cursorgc, &gc, sizeof cursorgc); 835 cursorgc.attr ^= GRID_ATTR_REVERSE; 836 837 start = screen_write_strlen("%s", c->prompt_string); 838 if (start > c->tty.sx) 839 start = c->tty.sx; 840 841 screen_write_start(&ctx, NULL, &c->status.status); 842 screen_write_cursormove(&ctx, 0, 0); 843 for (offset = 0; offset < lines * c->tty.sx; offset++) 844 screen_write_putc(&ctx, &gc, ' '); 845 screen_write_cursormove(&ctx, 0, 0); 846 screen_write_nputs(&ctx, start, &gc, "%s", c->prompt_string); 847 screen_write_cursormove(&ctx, start, 0); 848 849 left = c->tty.sx - start; 850 if (left == 0) 851 goto finished; 852 853 pcursor = utf8_strwidth(c->prompt_buffer, c->prompt_index); 854 pwidth = utf8_strwidth(c->prompt_buffer, -1); 855 if (pcursor >= left) { 856 /* 857 * The cursor would be outside the screen so start drawing 858 * with it on the right. 859 */ 860 offset = (pcursor - left) + 1; 861 pwidth = left; 862 } else 863 offset = 0; 864 if (pwidth > left) 865 pwidth = left; 866 867 width = 0; 868 for (i = 0; c->prompt_buffer[i].size != 0; i++) { 869 if (width < offset) { 870 width += c->prompt_buffer[i].width; 871 continue; 872 } 873 if (width >= offset + pwidth) 874 break; 875 width += c->prompt_buffer[i].width; 876 if (width > offset + pwidth) 877 break; 878 879 if (i != c->prompt_index) { 880 utf8_copy(&gc.data, &c->prompt_buffer[i]); 881 screen_write_cell(&ctx, &gc); 882 } else { 883 utf8_copy(&cursorgc.data, &c->prompt_buffer[i]); 884 screen_write_cell(&ctx, &cursorgc); 885 } 886 } 887 if (c->status.status.cx < screen_size_x(&c->status.status) && 888 c->prompt_index >= i) 889 screen_write_putc(&ctx, &cursorgc, ' '); 890 891finished: 892 screen_write_stop(&ctx); 893 894 if (grid_compare(c->status.status.grid, old_status.grid) == 0) { 895 screen_free(&old_status); 896 return (0); 897 } 898 screen_free(&old_status); 899 return (1); 900} 901 902/* Is this a separator? */ 903static int 904status_prompt_in_list(const char *ws, const struct utf8_data *ud) 905{ 906 if (ud->size != 1 || ud->width != 1) 907 return (0); 908 return (strchr(ws, *ud->data) != NULL); 909} 910 911/* Is this a space? */ 912static int 913status_prompt_space(const struct utf8_data *ud) 914{ 915 if (ud->size != 1 || ud->width != 1) 916 return (0); 917 return (*ud->data == ' '); 918} 919 920/* 921 * Translate key from emacs to vi. Return 0 to drop key, 1 to process the key 922 * as an emacs key; return 2 to append to the buffer. 923 */ 924static int 925status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) 926{ 927 if (c->prompt_mode == PROMPT_ENTRY) { 928 switch (key) { 929 case '\003': /* C-c */ 930 case '\010': /* C-h */ 931 case '\011': /* Tab */ 932 case '\025': /* C-u */ 933 case '\027': /* C-w */ 934 case '\n': 935 case '\r': 936 case KEYC_BSPACE: 937 case KEYC_DC: 938 case KEYC_DOWN: 939 case KEYC_END: 940 case KEYC_HOME: 941 case KEYC_LEFT: 942 case KEYC_RIGHT: 943 case KEYC_UP: 944 *new_key = key; 945 return (1); 946 case '\033': /* Escape */ 947 c->prompt_mode = PROMPT_COMMAND; 948 c->flags |= CLIENT_REDRAWSTATUS; 949 return (0); 950 } 951 *new_key = key; 952 return (2); 953 } 954 955 switch (key) { 956 case 'A': 957 case 'I': 958 case 'C': 959 case 's': 960 case 'a': 961 c->prompt_mode = PROMPT_ENTRY; 962 c->flags |= CLIENT_REDRAWSTATUS; 963 break; /* switch mode and... */ 964 case 'S': 965 c->prompt_mode = PROMPT_ENTRY; 966 c->flags |= CLIENT_REDRAWSTATUS; 967 *new_key = '\025'; /* C-u */ 968 return (1); 969 case 'i': 970 case '\033': /* Escape */ 971 c->prompt_mode = PROMPT_ENTRY; 972 c->flags |= CLIENT_REDRAWSTATUS; 973 return (0); 974 } 975 976 switch (key) { 977 case 'A': 978 case '$': 979 *new_key = KEYC_END; 980 return (1); 981 case 'I': 982 case '0': 983 case '^': 984 *new_key = KEYC_HOME; 985 return (1); 986 case 'C': 987 case 'D': 988 *new_key = '\013'; /* C-k */ 989 return (1); 990 case KEYC_BSPACE: 991 case 'X': 992 *new_key = KEYC_BSPACE; 993 return (1); 994 case 'b': 995 case 'B': 996 *new_key = 'b'|KEYC_ESCAPE; 997 return (1); 998 case 'd': 999 *new_key = '\025'; 1000 return (1); 1001 case 'e': 1002 case 'E': 1003 case 'w': 1004 case 'W': 1005 *new_key = 'f'|KEYC_ESCAPE; 1006 return (1); 1007 case 'p': 1008 *new_key = '\031'; /* C-y */ 1009 return (1); 1010 case 's': 1011 case KEYC_DC: 1012 case 'x': 1013 *new_key = KEYC_DC; 1014 return (1); 1015 case KEYC_DOWN: 1016 case 'j': 1017 *new_key = KEYC_DOWN; 1018 return (1); 1019 case KEYC_LEFT: 1020 case 'h': 1021 *new_key = KEYC_LEFT; 1022 return (1); 1023 case 'a': 1024 case KEYC_RIGHT: 1025 case 'l': 1026 *new_key = KEYC_RIGHT; 1027 return (1); 1028 case KEYC_UP: 1029 case 'k': 1030 *new_key = KEYC_UP; 1031 return (1); 1032 case '\010' /* C-h */: 1033 case '\003' /* C-c */: 1034 case '\n': 1035 case '\r': 1036 return (1); 1037 } 1038 return (0); 1039} 1040 1041/* Handle keys in prompt. */ 1042int 1043status_prompt_key(struct client *c, key_code key) 1044{ 1045 struct options *oo = c->session->options; 1046 struct paste_buffer *pb; 1047 char *s, *cp, word[64], prefix = '='; 1048 const char *histstr, *bufdata, *ws = NULL; 1049 u_char ch; 1050 size_t size, n, off, idx, bufsize, used; 1051 struct utf8_data tmp, *first, *last, *ud; 1052 int keys; 1053 1054 size = utf8_strlen(c->prompt_buffer); 1055 1056 if (c->prompt_flags & PROMPT_NUMERIC) { 1057 if (key >= '0' && key <= '9') 1058 goto append_key; 1059 s = utf8_tocstr(c->prompt_buffer); 1060 c->prompt_inputcb(c, c->prompt_data, s, 1); 1061 status_prompt_clear(c); 1062 free(s); 1063 return (1); 1064 } 1065 key &= ~KEYC_XTERM; 1066 1067 keys = options_get_number(c->session->options, "status-keys"); 1068 if (keys == MODEKEY_VI) { 1069 switch (status_prompt_translate_key(c, key, &key)) { 1070 case 1: 1071 goto process_key; 1072 case 2: 1073 goto append_key; 1074 default: 1075 return (0); 1076 } 1077 } 1078 1079process_key: 1080 switch (key) { 1081 case KEYC_LEFT: 1082 case '\002': /* C-b */ 1083 if (c->prompt_index > 0) { 1084 c->prompt_index--; 1085 break; 1086 } 1087 break; 1088 case KEYC_RIGHT: 1089 case '\006': /* C-f */ 1090 if (c->prompt_index < size) { 1091 c->prompt_index++; 1092 break; 1093 } 1094 break; 1095 case KEYC_HOME: 1096 case '\001': /* C-a */ 1097 if (c->prompt_index != 0) { 1098 c->prompt_index = 0; 1099 break; 1100 } 1101 break; 1102 case KEYC_END: 1103 case '\005': /* C-e */ 1104 if (c->prompt_index != size) { 1105 c->prompt_index = size; 1106 break; 1107 } 1108 break; 1109 case '\011': /* Tab */ 1110 if (c->prompt_buffer[0].size == 0) 1111 break; 1112 1113 idx = c->prompt_index; 1114 if (idx != 0) 1115 idx--; 1116 1117 /* Find the word we are in. */ 1118 first = &c->prompt_buffer[idx]; 1119 while (first > c->prompt_buffer && !status_prompt_space(first)) 1120 first--; 1121 while (first->size != 0 && status_prompt_space(first)) 1122 first++; 1123 last = &c->prompt_buffer[idx]; 1124 while (last->size != 0 && !status_prompt_space(last)) 1125 last++; 1126 while (last > c->prompt_buffer && status_prompt_space(last)) 1127 last--; 1128 if (last->size != 0) 1129 last++; 1130 if (last <= first) 1131 break; 1132 1133 used = 0; 1134 for (ud = first; ud < last; ud++) { 1135 if (used + ud->size >= sizeof word) 1136 break; 1137 memcpy(word + used, ud->data, ud->size); 1138 used += ud->size; 1139 } 1140 if (ud != last) 1141 break; 1142 word[used] = '\0'; 1143 1144 /* And try to complete it. */ 1145 if ((s = status_prompt_complete(c->session, word)) == NULL) 1146 break; 1147 1148 /* Trim out word. */ 1149 n = size - (last - c->prompt_buffer) + 1; /* with \0 */ 1150 memmove(first, last, n * sizeof *c->prompt_buffer); 1151 size -= last - first; 1152 1153 /* Insert the new word. */ 1154 size += strlen(s); 1155 off = first - c->prompt_buffer; 1156 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 1, 1157 sizeof *c->prompt_buffer); 1158 first = c->prompt_buffer + off; 1159 memmove(first + strlen(s), first, n * sizeof *c->prompt_buffer); 1160 for (idx = 0; idx < strlen(s); idx++) 1161 utf8_set(&first[idx], s[idx]); 1162 1163 c->prompt_index = (first - c->prompt_buffer) + strlen(s); 1164 free(s); 1165 1166 goto changed; 1167 case KEYC_BSPACE: 1168 case '\010': /* C-h */ 1169 if (c->prompt_index != 0) { 1170 if (c->prompt_index == size) 1171 c->prompt_buffer[--c->prompt_index].size = 0; 1172 else { 1173 memmove(c->prompt_buffer + c->prompt_index - 1, 1174 c->prompt_buffer + c->prompt_index, 1175 (size + 1 - c->prompt_index) * 1176 sizeof *c->prompt_buffer); 1177 c->prompt_index--; 1178 } 1179 goto changed; 1180 } 1181 break; 1182 case KEYC_DC: 1183 case '\004': /* C-d */ 1184 if (c->prompt_index != size) { 1185 memmove(c->prompt_buffer + c->prompt_index, 1186 c->prompt_buffer + c->prompt_index + 1, 1187 (size + 1 - c->prompt_index) * 1188 sizeof *c->prompt_buffer); 1189 goto changed; 1190 } 1191 break; 1192 case '\025': /* C-u */ 1193 c->prompt_buffer[0].size = 0; 1194 c->prompt_index = 0; 1195 goto changed; 1196 case '\013': /* C-k */ 1197 if (c->prompt_index < size) { 1198 c->prompt_buffer[c->prompt_index].size = 0; 1199 goto changed; 1200 } 1201 break; 1202 case '\027': /* C-w */ 1203 ws = options_get_string(oo, "word-separators"); 1204 idx = c->prompt_index; 1205 1206 /* Find a non-separator. */ 1207 while (idx != 0) { 1208 idx--; 1209 if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1210 break; 1211 } 1212 1213 /* Find the separator at the beginning of the word. */ 1214 while (idx != 0) { 1215 idx--; 1216 if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) { 1217 /* Go back to the word. */ 1218 idx++; 1219 break; 1220 } 1221 } 1222 1223 free(c->prompt_saved); 1224 c->prompt_saved = xcalloc(sizeof *c->prompt_buffer, 1225 (c->prompt_index - idx) + 1); 1226 memcpy(c->prompt_saved, c->prompt_buffer + idx, 1227 (c->prompt_index - idx) * sizeof *c->prompt_buffer); 1228 1229 memmove(c->prompt_buffer + idx, 1230 c->prompt_buffer + c->prompt_index, 1231 (size + 1 - c->prompt_index) * 1232 sizeof *c->prompt_buffer); 1233 memset(c->prompt_buffer + size - (c->prompt_index - idx), 1234 '\0', (c->prompt_index - idx) * sizeof *c->prompt_buffer); 1235 c->prompt_index = idx; 1236 1237 goto changed; 1238 case 'f'|KEYC_ESCAPE: 1239 case KEYC_RIGHT|KEYC_CTRL: 1240 ws = options_get_string(oo, "word-separators"); 1241 1242 /* Find a word. */ 1243 while (c->prompt_index != size) { 1244 idx = ++c->prompt_index; 1245 if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1246 break; 1247 } 1248 1249 /* Find the separator at the end of the word. */ 1250 while (c->prompt_index != size) { 1251 idx = ++c->prompt_index; 1252 if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1253 break; 1254 } 1255 1256 /* Back up to the end-of-word like vi. */ 1257 if (options_get_number(oo, "status-keys") == MODEKEY_VI && 1258 c->prompt_index != 0) 1259 c->prompt_index--; 1260 1261 goto changed; 1262 case 'b'|KEYC_ESCAPE: 1263 case KEYC_LEFT|KEYC_CTRL: 1264 ws = options_get_string(oo, "word-separators"); 1265 1266 /* Find a non-separator. */ 1267 while (c->prompt_index != 0) { 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 beginning of the word. */ 1274 while (c->prompt_index != 0) { 1275 idx = --c->prompt_index; 1276 if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) { 1277 /* Go back to the word. */ 1278 c->prompt_index++; 1279 break; 1280 } 1281 } 1282 goto changed; 1283 case KEYC_UP: 1284 case '\020': /* C-p */ 1285 histstr = status_prompt_up_history(&c->prompt_hindex); 1286 if (histstr == NULL) 1287 break; 1288 free(c->prompt_buffer); 1289 c->prompt_buffer = utf8_fromcstr(histstr); 1290 c->prompt_index = utf8_strlen(c->prompt_buffer); 1291 goto changed; 1292 case KEYC_DOWN: 1293 case '\016': /* C-n */ 1294 histstr = status_prompt_down_history(&c->prompt_hindex); 1295 if (histstr == NULL) 1296 break; 1297 free(c->prompt_buffer); 1298 c->prompt_buffer = utf8_fromcstr(histstr); 1299 c->prompt_index = utf8_strlen(c->prompt_buffer); 1300 goto changed; 1301 case '\031': /* C-y */ 1302 if (c->prompt_saved != NULL) { 1303 ud = c->prompt_saved; 1304 n = utf8_strlen(c->prompt_saved); 1305 } else { 1306 if ((pb = paste_get_top(NULL)) == NULL) 1307 break; 1308 bufdata = paste_buffer_data(pb, &bufsize); 1309 for (n = 0; n < bufsize; n++) { 1310 ch = (u_char)bufdata[n]; 1311 if (ch < 32 || ch >= 127) 1312 break; 1313 } 1314 ud = xreallocarray(NULL, n, sizeof *ud); 1315 for (idx = 0; idx < n; idx++) 1316 utf8_set(&ud[idx], bufdata[idx]); 1317 } 1318 1319 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + n + 1, 1320 sizeof *c->prompt_buffer); 1321 if (c->prompt_index == size) { 1322 memcpy(c->prompt_buffer + c->prompt_index, ud, 1323 n * sizeof *c->prompt_buffer); 1324 c->prompt_index += n; 1325 c->prompt_buffer[c->prompt_index].size = 0; 1326 } else { 1327 memmove(c->prompt_buffer + c->prompt_index + n, 1328 c->prompt_buffer + c->prompt_index, 1329 (size + 1 - c->prompt_index) * 1330 sizeof *c->prompt_buffer); 1331 memcpy(c->prompt_buffer + c->prompt_index, ud, 1332 n * sizeof *c->prompt_buffer); 1333 c->prompt_index += n; 1334 } 1335 1336 if (ud != c->prompt_saved) 1337 free(ud); 1338 goto changed; 1339 case '\024': /* C-t */ 1340 idx = c->prompt_index; 1341 if (idx < size) 1342 idx++; 1343 if (idx >= 2) { 1344 utf8_copy(&tmp, &c->prompt_buffer[idx - 2]); 1345 utf8_copy(&c->prompt_buffer[idx - 2], 1346 &c->prompt_buffer[idx - 1]); 1347 utf8_copy(&c->prompt_buffer[idx - 1], &tmp); 1348 c->prompt_index = idx; 1349 goto changed; 1350 } 1351 break; 1352 case '\r': 1353 case '\n': 1354 s = utf8_tocstr(c->prompt_buffer); 1355 if (*s != '\0') 1356 status_prompt_add_history(s); 1357 if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) 1358 status_prompt_clear(c); 1359 free(s); 1360 break; 1361 case '\033': /* Escape */ 1362 case '\003': /* C-c */ 1363 case '\007': /* C-g */ 1364 if (c->prompt_inputcb(c, c->prompt_data, NULL, 1) == 0) 1365 status_prompt_clear(c); 1366 break; 1367 case '\022': /* C-r */ 1368 if (c->prompt_flags & PROMPT_INCREMENTAL) { 1369 prefix = '-'; 1370 goto changed; 1371 } 1372 break; 1373 case '\023': /* C-s */ 1374 if (c->prompt_flags & PROMPT_INCREMENTAL) { 1375 prefix = '+'; 1376 goto changed; 1377 } 1378 break; 1379 default: 1380 goto append_key; 1381 } 1382 1383 c->flags |= CLIENT_REDRAWSTATUS; 1384 return (0); 1385 1386append_key: 1387 if (key <= 0x1f || key >= KEYC_BASE) 1388 return (0); 1389 if (utf8_split(key, &tmp) != UTF8_DONE) 1390 return (0); 1391 1392 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 2, 1393 sizeof *c->prompt_buffer); 1394 1395 if (c->prompt_index == size) { 1396 utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp); 1397 c->prompt_index++; 1398 c->prompt_buffer[c->prompt_index].size = 0; 1399 } else { 1400 memmove(c->prompt_buffer + c->prompt_index + 1, 1401 c->prompt_buffer + c->prompt_index, 1402 (size + 1 - c->prompt_index) * 1403 sizeof *c->prompt_buffer); 1404 utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp); 1405 c->prompt_index++; 1406 } 1407 1408 if (c->prompt_flags & PROMPT_SINGLE) { 1409 s = utf8_tocstr(c->prompt_buffer); 1410 if (strlen(s) != 1) 1411 status_prompt_clear(c); 1412 else if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) 1413 status_prompt_clear(c); 1414 free(s); 1415 } 1416 1417changed: 1418 c->flags |= CLIENT_REDRAWSTATUS; 1419 if (c->prompt_flags & PROMPT_INCREMENTAL) { 1420 s = utf8_tocstr(c->prompt_buffer); 1421 xasprintf(&cp, "%c%s", prefix, s); 1422 c->prompt_inputcb(c, c->prompt_data, cp, 0); 1423 free(cp); 1424 free(s); 1425 } 1426 return (0); 1427} 1428 1429/* Get previous line from the history. */ 1430static const char * 1431status_prompt_up_history(u_int *idx) 1432{ 1433 /* 1434 * History runs from 0 to size - 1. Index is from 0 to size. Zero is 1435 * empty. 1436 */ 1437 1438 if (status_prompt_hsize == 0 || *idx == status_prompt_hsize) 1439 return (NULL); 1440 (*idx)++; 1441 return (status_prompt_hlist[status_prompt_hsize - *idx]); 1442} 1443 1444/* Get next line from the history. */ 1445static const char * 1446status_prompt_down_history(u_int *idx) 1447{ 1448 if (status_prompt_hsize == 0 || *idx == 0) 1449 return (""); 1450 (*idx)--; 1451 if (*idx == 0) 1452 return (""); 1453 return (status_prompt_hlist[status_prompt_hsize - *idx]); 1454} 1455 1456/* Add line to the history. */ 1457static void 1458status_prompt_add_history(const char *line) 1459{ 1460 size_t size; 1461 1462 if (status_prompt_hsize > 0 && 1463 strcmp(status_prompt_hlist[status_prompt_hsize - 1], line) == 0) 1464 return; 1465 1466 if (status_prompt_hsize == PROMPT_HISTORY) { 1467 free(status_prompt_hlist[0]); 1468 1469 size = (PROMPT_HISTORY - 1) * sizeof *status_prompt_hlist; 1470 memmove(&status_prompt_hlist[0], &status_prompt_hlist[1], size); 1471 1472 status_prompt_hlist[status_prompt_hsize - 1] = xstrdup(line); 1473 return; 1474 } 1475 1476 status_prompt_hlist = xreallocarray(status_prompt_hlist, 1477 status_prompt_hsize + 1, sizeof *status_prompt_hlist); 1478 status_prompt_hlist[status_prompt_hsize++] = xstrdup(line); 1479} 1480 1481/* Build completion list. */ 1482char ** 1483status_prompt_complete_list(u_int *size, const char *s) 1484{ 1485 char **list = NULL; 1486 const char **layout, *value, *cp; 1487 const struct cmd_entry **cmdent; 1488 const struct options_table_entry *oe; 1489 u_int items, idx; 1490 size_t slen = strlen(s), valuelen; 1491 struct options_entry *o; 1492 const char *layouts[] = { 1493 "even-horizontal", "even-vertical", "main-horizontal", 1494 "main-vertical", "tiled", NULL 1495 }; 1496 1497 *size = 0; 1498 for (cmdent = cmd_table; *cmdent != NULL; cmdent++) { 1499 if (strncmp((*cmdent)->name, s, slen) == 0) { 1500 list = xreallocarray(list, (*size) + 1, sizeof *list); 1501 list[(*size)++] = xstrdup((*cmdent)->name); 1502 } 1503 } 1504 for (oe = options_table; oe->name != NULL; oe++) { 1505 if (strncmp(oe->name, s, slen) == 0) { 1506 list = xreallocarray(list, (*size) + 1, sizeof *list); 1507 list[(*size)++] = xstrdup(oe->name); 1508 } 1509 } 1510 for (layout = layouts; *layout != NULL; layout++) { 1511 if (strncmp(*layout, s, slen) == 0) { 1512 list = xreallocarray(list, (*size) + 1, sizeof *list); 1513 list[(*size)++] = xstrdup(*layout); 1514 } 1515 } 1516 o = options_get_only(global_options, "command-alias"); 1517 if (o != NULL && options_array_size(o, &items) != -1) { 1518 for (idx = 0; idx < items; idx++) { 1519 value = options_array_get(o, idx); 1520 if (value == NULL || (cp = strchr(value, '=')) == NULL) 1521 continue; 1522 valuelen = cp - value; 1523 if (slen > valuelen || strncmp(value, s, slen) != 0) 1524 continue; 1525 list = xreallocarray(list, (*size) + 1, sizeof *list); 1526 list[(*size)++] = xstrndup(value, valuelen); 1527 } 1528 } 1529 for (idx = 0; idx < (*size); idx++) 1530 log_debug("complete %u: %s", idx, list[idx]); 1531 return (list); 1532} 1533 1534/* Find longest prefix. */ 1535static char * 1536status_prompt_complete_prefix(char **list, u_int size) 1537{ 1538 char *out; 1539 u_int i; 1540 size_t j; 1541 1542 out = xstrdup(list[0]); 1543 for (i = 1; i < size; i++) { 1544 j = strlen(list[i]); 1545 if (j > strlen(out)) 1546 j = strlen(out); 1547 for (; j > 0; j--) { 1548 if (out[j - 1] != list[i][j - 1]) 1549 out[j - 1] = '\0'; 1550 } 1551 } 1552 return (out); 1553} 1554 1555/* Complete word. */ 1556static char * 1557status_prompt_complete(struct session *session, const char *s) 1558{ 1559 char **list = NULL; 1560 const char *colon; 1561 u_int size = 0, i; 1562 struct session *s_loop; 1563 struct winlink *wl; 1564 struct window *w; 1565 char *copy, *out, *tmp; 1566 1567 if (*s == '\0') 1568 return (NULL); 1569 out = NULL; 1570 1571 if (strncmp(s, "-t", 2) != 0 && strncmp(s, "-s", 2) != 0) { 1572 list = status_prompt_complete_list(&size, s); 1573 if (size == 0) 1574 out = NULL; 1575 else if (size == 1) 1576 xasprintf(&out, "%s ", list[0]); 1577 else 1578 out = status_prompt_complete_prefix(list, size); 1579 for (i = 0; i < size; i++) 1580 free(list[i]); 1581 free(list); 1582 return (out); 1583 } 1584 copy = xstrdup(s); 1585 1586 colon = ":"; 1587 if (copy[strlen(copy) - 1] == ':') 1588 copy[strlen(copy) - 1] = '\0'; 1589 else 1590 colon = ""; 1591 s = copy + 2; 1592 1593 RB_FOREACH(s_loop, sessions, &sessions) { 1594 if (strncmp(s_loop->name, s, strlen(s)) == 0) { 1595 list = xreallocarray(list, size + 2, sizeof *list); 1596 list[size++] = s_loop->name; 1597 } 1598 } 1599 if (size == 1) { 1600 out = xstrdup(list[0]); 1601 if (session_find(list[0]) != NULL) 1602 colon = ":"; 1603 } else if (size != 0) 1604 out = status_prompt_complete_prefix(list, size); 1605 if (out != NULL) { 1606 xasprintf(&tmp, "-%c%s%s", copy[1], out, colon); 1607 free(out); 1608 out = tmp; 1609 goto found; 1610 } 1611 1612 colon = ""; 1613 if (*s == ':') { 1614 RB_FOREACH(wl, winlinks, &session->windows) { 1615 xasprintf(&tmp, ":%s", wl->window->name); 1616 if (strncmp(tmp, s, strlen(s)) == 0){ 1617 list = xreallocarray(list, size + 1, 1618 sizeof *list); 1619 list[size++] = tmp; 1620 continue; 1621 } 1622 free(tmp); 1623 1624 xasprintf(&tmp, ":%d", wl->idx); 1625 if (strncmp(tmp, s, strlen(s)) == 0) { 1626 list = xreallocarray(list, size + 1, 1627 sizeof *list); 1628 list[size++] = tmp; 1629 continue; 1630 } 1631 free(tmp); 1632 } 1633 } else { 1634 RB_FOREACH(s_loop, sessions, &sessions) { 1635 RB_FOREACH(wl, winlinks, &s_loop->windows) { 1636 w = wl->window; 1637 1638 xasprintf(&tmp, "%s:%s", s_loop->name, w->name); 1639 if (strncmp(tmp, s, strlen(s)) == 0) { 1640 list = xreallocarray(list, size + 1, 1641 sizeof *list); 1642 list[size++] = tmp; 1643 continue; 1644 } 1645 free(tmp); 1646 1647 xasprintf(&tmp, "%s:%d", s_loop->name, wl->idx); 1648 if (strncmp(tmp, s, strlen(s)) == 0) { 1649 list = xreallocarray(list, size + 1, 1650 sizeof *list); 1651 list[size++] = tmp; 1652 continue; 1653 } 1654 free(tmp); 1655 } 1656 } 1657 } 1658 if (size == 1) { 1659 out = xstrdup(list[0]); 1660 colon = " "; 1661 } else if (size != 0) 1662 out = status_prompt_complete_prefix(list, size); 1663 if (out != NULL) { 1664 xasprintf(&tmp, "-%c%s%s", copy[1], out, colon); 1665 out = tmp; 1666 } 1667 1668 for (i = 0; i < size; i++) 1669 free((void *)list[i]); 1670 1671found: 1672 free(copy); 1673 free(list); 1674 return (out); 1675} 1676