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