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