status.c revision 1.150
1/* $OpenBSD: status.c,v 1.150 2016/09/12 15:40:58 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 32char *status_redraw_get_left(struct client *, time_t, struct grid_cell *, 33 size_t *); 34char *status_redraw_get_right(struct client *, time_t, struct grid_cell *, 35 size_t *); 36char *status_print(struct client *, struct winlink *, time_t, 37 struct grid_cell *); 38char *status_replace(struct client *, struct winlink *, const char *, time_t); 39void status_message_callback(int, short, void *); 40void status_timer_callback(int, short, void *); 41 42const char *status_prompt_up_history(u_int *); 43const char *status_prompt_down_history(u_int *); 44void status_prompt_add_history(const char *); 45 46const char **status_prompt_complete_list(u_int *, const char *); 47char *status_prompt_complete_prefix(const char **, u_int); 48char *status_prompt_complete(struct session *, const char *); 49 50char *status_prompt_find_history_file(void); 51 52/* Status prompt history. */ 53#define PROMPT_HISTORY 100 54char **status_prompt_hlist; 55u_int status_prompt_hsize; 56 57/* Find the history file to load/save from/to. */ 58char * 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. */ 147void 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_STATUS; 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/* Get screen line of status line. -1 means off. */ 196int 197status_at_line(struct client *c) 198{ 199 struct session *s = c->session; 200 201 if (!options_get_number(s->options, "status")) 202 return (-1); 203 204 if (options_get_number(s->options, "status-position") == 0) 205 return (0); 206 return (c->tty.sy - 1); 207} 208 209/* Retrieve options for left string. */ 210char * 211status_redraw_get_left(struct client *c, time_t t, struct grid_cell *gc, 212 size_t *size) 213{ 214 struct session *s = c->session; 215 const char *template; 216 char *left; 217 size_t leftlen; 218 219 style_apply_update(gc, s->options, "status-left-style"); 220 221 template = options_get_string(s->options, "status-left"); 222 left = status_replace(c, NULL, template, t); 223 224 *size = options_get_number(s->options, "status-left-length"); 225 leftlen = screen_write_cstrlen("%s", left); 226 if (leftlen < *size) 227 *size = leftlen; 228 return (left); 229} 230 231/* Retrieve options for right string. */ 232char * 233status_redraw_get_right(struct client *c, time_t t, struct grid_cell *gc, 234 size_t *size) 235{ 236 struct session *s = c->session; 237 const char *template; 238 char *right; 239 size_t rightlen; 240 241 style_apply_update(gc, s->options, "status-right-style"); 242 243 template = options_get_string(s->options, "status-right"); 244 right = status_replace(c, NULL, template, t); 245 246 *size = options_get_number(s->options, "status-right-length"); 247 rightlen = screen_write_cstrlen("%s", right); 248 if (rightlen < *size) 249 *size = rightlen; 250 return (right); 251} 252 253/* Get window at window list position. */ 254struct window * 255status_get_window_at(struct client *c, u_int x) 256{ 257 struct session *s = c->session; 258 struct winlink *wl; 259 struct options *oo; 260 const char *sep; 261 size_t seplen; 262 263 x += c->wlmouse; 264 RB_FOREACH(wl, winlinks, &s->windows) { 265 oo = wl->window->options; 266 267 sep = options_get_string(oo, "window-status-separator"); 268 seplen = screen_write_cstrlen("%s", sep); 269 270 if (x < wl->status_width) 271 return (wl->window); 272 x -= wl->status_width + seplen; 273 } 274 return (NULL); 275} 276 277/* Draw status for client on the last lines of given context. */ 278int 279status_redraw(struct client *c) 280{ 281 struct screen_write_ctx ctx; 282 struct session *s = c->session; 283 struct winlink *wl; 284 struct screen old_status, window_list; 285 struct grid_cell stdgc, lgc, rgc, gc; 286 struct options *oo; 287 time_t t; 288 char *left, *right, *sep; 289 u_int offset, needed; 290 u_int wlstart, wlwidth, wlavailable, wloffset, wlsize; 291 size_t llen, rlen, seplen; 292 int larrow, rarrow; 293 294 /* No status line? */ 295 if (c->tty.sy == 0 || !options_get_number(s->options, "status")) 296 return (1); 297 left = right = NULL; 298 larrow = rarrow = 0; 299 300 /* Store current time. */ 301 t = time(NULL); 302 303 /* Set up default colour. */ 304 style_apply(&stdgc, s->options, "status-style"); 305 306 /* Create the target screen. */ 307 memcpy(&old_status, &c->status, sizeof old_status); 308 screen_init(&c->status, c->tty.sx, 1, 0); 309 screen_write_start(&ctx, NULL, &c->status); 310 for (offset = 0; offset < c->tty.sx; offset++) 311 screen_write_putc(&ctx, &stdgc, ' '); 312 screen_write_stop(&ctx); 313 314 /* If the height is one line, blank status line. */ 315 if (c->tty.sy <= 1) 316 goto out; 317 318 /* Work out left and right strings. */ 319 memcpy(&lgc, &stdgc, sizeof lgc); 320 left = status_redraw_get_left(c, t, &lgc, &llen); 321 memcpy(&rgc, &stdgc, sizeof rgc); 322 right = status_redraw_get_right(c, t, &rgc, &rlen); 323 324 /* 325 * Figure out how much space we have for the window list. If there 326 * isn't enough space, just show a blank status line. 327 */ 328 needed = 0; 329 if (llen != 0) 330 needed += llen; 331 if (rlen != 0) 332 needed += rlen; 333 if (c->tty.sx == 0 || c->tty.sx <= needed) 334 goto out; 335 wlavailable = c->tty.sx - needed; 336 337 /* Calculate the total size needed for the window list. */ 338 wlstart = wloffset = wlwidth = 0; 339 RB_FOREACH(wl, winlinks, &s->windows) { 340 free(wl->status_text); 341 memcpy(&wl->status_cell, &stdgc, sizeof wl->status_cell); 342 wl->status_text = status_print(c, wl, t, &wl->status_cell); 343 wl->status_width = screen_write_cstrlen("%s", wl->status_text); 344 345 if (wl == s->curw) 346 wloffset = wlwidth; 347 348 oo = wl->window->options; 349 sep = options_get_string(oo, "window-status-separator"); 350 seplen = screen_write_cstrlen("%s", sep); 351 wlwidth += wl->status_width + seplen; 352 } 353 354 /* Create a new screen for the window list. */ 355 screen_init(&window_list, wlwidth, 1, 0); 356 357 /* And draw the window list into it. */ 358 screen_write_start(&ctx, NULL, &window_list); 359 RB_FOREACH(wl, winlinks, &s->windows) { 360 screen_write_cnputs(&ctx, -1, &wl->status_cell, "%s", 361 wl->status_text); 362 363 oo = wl->window->options; 364 sep = options_get_string(oo, "window-status-separator"); 365 screen_write_cnputs(&ctx, -1, &stdgc, "%s", sep); 366 } 367 screen_write_stop(&ctx); 368 369 /* If there is enough space for the total width, skip to draw now. */ 370 if (wlwidth <= wlavailable) 371 goto draw; 372 373 /* Find size of current window text. */ 374 wlsize = s->curw->status_width; 375 376 /* 377 * If the current window is already on screen, good to draw from the 378 * start and just leave off the end. 379 */ 380 if (wloffset + wlsize < wlavailable) { 381 if (wlavailable > 0) { 382 rarrow = 1; 383 wlavailable--; 384 } 385 wlwidth = wlavailable; 386 } else { 387 /* 388 * Work out how many characters we need to omit from the 389 * start. There are wlavailable characters to fill, and 390 * wloffset + wlsize must be the last. So, the start character 391 * is wloffset + wlsize - wlavailable. 392 */ 393 if (wlavailable > 0) { 394 larrow = 1; 395 wlavailable--; 396 } 397 398 wlstart = wloffset + wlsize - wlavailable; 399 if (wlavailable > 0 && wlwidth > wlstart + wlavailable + 1) { 400 rarrow = 1; 401 wlstart++; 402 wlavailable--; 403 } 404 wlwidth = wlavailable; 405 } 406 407 /* Bail if anything is now too small too. */ 408 if (wlwidth == 0 || wlavailable == 0) { 409 screen_free(&window_list); 410 goto out; 411 } 412 413 /* 414 * Now the start position is known, work out the state of the left and 415 * right arrows. 416 */ 417 offset = 0; 418 RB_FOREACH(wl, winlinks, &s->windows) { 419 if (wl->flags & WINLINK_ALERTFLAGS && 420 larrow == 1 && offset < wlstart) 421 larrow = -1; 422 423 offset += wl->status_width; 424 425 if (wl->flags & WINLINK_ALERTFLAGS && 426 rarrow == 1 && offset > wlstart + wlwidth) 427 rarrow = -1; 428 } 429 430draw: 431 /* Begin drawing. */ 432 screen_write_start(&ctx, NULL, &c->status); 433 434 /* Draw the left string and arrow. */ 435 screen_write_cursormove(&ctx, 0, 0); 436 if (llen != 0) 437 screen_write_cnputs(&ctx, llen, &lgc, "%s", left); 438 if (larrow != 0) { 439 memcpy(&gc, &stdgc, sizeof gc); 440 if (larrow == -1) 441 gc.attr ^= GRID_ATTR_REVERSE; 442 screen_write_putc(&ctx, &gc, '<'); 443 } 444 445 /* Draw the right string and arrow. */ 446 if (rarrow != 0) { 447 screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, 0); 448 memcpy(&gc, &stdgc, sizeof gc); 449 if (rarrow == -1) 450 gc.attr ^= GRID_ATTR_REVERSE; 451 screen_write_putc(&ctx, &gc, '>'); 452 } else 453 screen_write_cursormove(&ctx, c->tty.sx - rlen, 0); 454 if (rlen != 0) 455 screen_write_cnputs(&ctx, rlen, &rgc, "%s", right); 456 457 /* Figure out the offset for the window list. */ 458 if (llen != 0) 459 wloffset = llen; 460 else 461 wloffset = 0; 462 if (wlwidth < wlavailable) { 463 switch (options_get_number(s->options, "status-justify")) { 464 case 1: /* centred */ 465 wloffset += (wlavailable - wlwidth) / 2; 466 break; 467 case 2: /* right */ 468 wloffset += (wlavailable - wlwidth); 469 break; 470 } 471 } 472 if (larrow != 0) 473 wloffset++; 474 475 /* Copy the window list. */ 476 c->wlmouse = -wloffset + wlstart; 477 screen_write_cursormove(&ctx, wloffset, 0); 478 screen_write_copy(&ctx, &window_list, wlstart, 0, wlwidth, 1); 479 screen_free(&window_list); 480 481 screen_write_stop(&ctx); 482 483out: 484 free(left); 485 free(right); 486 487 if (grid_compare(c->status.grid, old_status.grid) == 0) { 488 screen_free(&old_status); 489 return (0); 490 } 491 screen_free(&old_status); 492 return (1); 493} 494 495/* Replace special sequences in fmt. */ 496char * 497status_replace(struct client *c, struct winlink *wl, const char *fmt, time_t t) 498{ 499 struct format_tree *ft; 500 char *expanded; 501 502 if (fmt == NULL) 503 return (xstrdup("")); 504 505 if (c->flags & CLIENT_STATUSFORCE) 506 ft = format_create(NULL, FORMAT_STATUS|FORMAT_FORCE); 507 else 508 ft = format_create(NULL, FORMAT_STATUS); 509 format_defaults(ft, c, NULL, wl, NULL); 510 511 expanded = format_expand_time(ft, fmt, t); 512 513 format_free(ft); 514 return (expanded); 515} 516 517/* Return winlink status line entry and adjust gc as necessary. */ 518char * 519status_print(struct client *c, struct winlink *wl, time_t t, 520 struct grid_cell *gc) 521{ 522 struct options *oo = wl->window->options; 523 struct session *s = c->session; 524 const char *fmt; 525 char *text; 526 527 style_apply_update(gc, oo, "window-status-style"); 528 fmt = options_get_string(oo, "window-status-format"); 529 if (wl == s->curw) { 530 style_apply_update(gc, oo, "window-status-current-style"); 531 fmt = options_get_string(oo, "window-status-current-format"); 532 } 533 if (wl == TAILQ_FIRST(&s->lastw)) 534 style_apply_update(gc, oo, "window-status-last-style"); 535 536 if (wl->flags & WINLINK_BELL) 537 style_apply_update(gc, oo, "window-status-bell-style"); 538 else if (wl->flags & (WINLINK_ACTIVITY|WINLINK_SILENCE)) 539 style_apply_update(gc, oo, "window-status-activity-style"); 540 541 text = status_replace(c, wl, fmt, t); 542 return (text); 543} 544 545/* Set a status line message. */ 546void 547status_message_set(struct client *c, const char *fmt, ...) 548{ 549 struct timeval tv; 550 struct message_entry *msg, *msg1; 551 va_list ap; 552 int delay; 553 u_int limit; 554 555 limit = options_get_number(global_options, "message-limit"); 556 557 status_prompt_clear(c); 558 status_message_clear(c); 559 560 va_start(ap, fmt); 561 xvasprintf(&c->message_string, fmt, ap); 562 va_end(ap); 563 564 msg = xcalloc(1, sizeof *msg); 565 msg->msg_time = time(NULL); 566 msg->msg_num = c->message_next++; 567 msg->msg = xstrdup(c->message_string); 568 TAILQ_INSERT_TAIL(&c->message_log, msg, entry); 569 570 TAILQ_FOREACH_SAFE(msg, &c->message_log, entry, msg1) { 571 if (msg->msg_num + limit >= c->message_next) 572 break; 573 free(msg->msg); 574 TAILQ_REMOVE(&c->message_log, msg, entry); 575 free(msg); 576 } 577 578 delay = options_get_number(c->session->options, "display-time"); 579 if (delay > 0) { 580 tv.tv_sec = delay / 1000; 581 tv.tv_usec = (delay % 1000) * 1000L; 582 583 if (event_initialized(&c->message_timer)) 584 evtimer_del(&c->message_timer); 585 evtimer_set(&c->message_timer, status_message_callback, c); 586 evtimer_add(&c->message_timer, &tv); 587 } 588 589 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); 590 c->flags |= CLIENT_STATUS; 591} 592 593/* Clear status line message. */ 594void 595status_message_clear(struct client *c) 596{ 597 if (c->message_string == NULL) 598 return; 599 600 free(c->message_string); 601 c->message_string = NULL; 602 603 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 604 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */ 605 606 screen_reinit(&c->status); 607} 608 609/* Clear status line message after timer expires. */ 610void 611status_message_callback(__unused int fd, __unused short event, void *data) 612{ 613 struct client *c = data; 614 615 status_message_clear(c); 616} 617 618/* Draw client message on status line of present else on last line. */ 619int 620status_message_redraw(struct client *c) 621{ 622 struct screen_write_ctx ctx; 623 struct session *s = c->session; 624 struct screen old_status; 625 size_t len; 626 struct grid_cell gc; 627 628 if (c->tty.sx == 0 || c->tty.sy == 0) 629 return (0); 630 memcpy(&old_status, &c->status, sizeof old_status); 631 screen_init(&c->status, c->tty.sx, 1, 0); 632 633 len = screen_write_strlen("%s", c->message_string); 634 if (len > c->tty.sx) 635 len = c->tty.sx; 636 637 style_apply(&gc, s->options, "message-style"); 638 639 screen_write_start(&ctx, NULL, &c->status); 640 641 screen_write_cursormove(&ctx, 0, 0); 642 screen_write_nputs(&ctx, len, &gc, "%s", c->message_string); 643 for (; len < c->tty.sx; len++) 644 screen_write_putc(&ctx, &gc, ' '); 645 646 screen_write_stop(&ctx); 647 648 if (grid_compare(c->status.grid, old_status.grid) == 0) { 649 screen_free(&old_status); 650 return (0); 651 } 652 screen_free(&old_status); 653 return (1); 654} 655 656/* Enable status line prompt. */ 657void 658status_prompt_set(struct client *c, const char *msg, const char *input, 659 int (*callbackfn)(void *, const char *), void (*freefn)(void *), 660 void *data, int flags) 661{ 662 struct format_tree *ft; 663 int keys; 664 time_t t; 665 666 ft = format_create(NULL, 0); 667 format_defaults(ft, c, NULL, NULL, NULL); 668 t = time(NULL); 669 670 status_message_clear(c); 671 status_prompt_clear(c); 672 673 c->prompt_string = format_expand_time(ft, msg, t); 674 675 c->prompt_buffer = format_expand_time(ft, input, t); 676 c->prompt_index = strlen(c->prompt_buffer); 677 678 c->prompt_callbackfn = callbackfn; 679 c->prompt_freefn = freefn; 680 c->prompt_data = data; 681 682 c->prompt_hindex = 0; 683 684 c->prompt_flags = flags; 685 686 keys = options_get_number(c->session->options, "status-keys"); 687 if (keys == MODEKEY_EMACS) 688 mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit); 689 else 690 mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit); 691 692 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); 693 c->flags |= CLIENT_STATUS; 694 695 format_free(ft); 696} 697 698/* Remove status line prompt. */ 699void 700status_prompt_clear(struct client *c) 701{ 702 if (c->prompt_string == NULL) 703 return; 704 705 if (c->prompt_freefn != NULL && c->prompt_data != NULL) 706 c->prompt_freefn(c->prompt_data); 707 708 free(c->prompt_string); 709 c->prompt_string = NULL; 710 711 free(c->prompt_buffer); 712 c->prompt_buffer = NULL; 713 714 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 715 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */ 716 717 screen_reinit(&c->status); 718} 719 720/* Update status line prompt with a new prompt string. */ 721void 722status_prompt_update(struct client *c, const char *msg, const char *input) 723{ 724 struct format_tree *ft; 725 time_t t; 726 727 ft = format_create(NULL, 0); 728 format_defaults(ft, c, NULL, NULL, NULL); 729 t = time(NULL); 730 731 free(c->prompt_string); 732 c->prompt_string = format_expand_time(ft, msg, t); 733 734 free(c->prompt_buffer); 735 c->prompt_buffer = format_expand_time(ft, input, t); 736 c->prompt_index = strlen(c->prompt_buffer); 737 738 c->prompt_hindex = 0; 739 740 c->flags |= CLIENT_STATUS; 741 742 format_free(ft); 743} 744 745/* Draw client prompt on status line of present else on last line. */ 746int 747status_prompt_redraw(struct client *c) 748{ 749 struct screen_write_ctx ctx; 750 struct session *s = c->session; 751 struct screen old_status; 752 size_t i, size, left, len, off; 753 struct grid_cell gc; 754 755 if (c->tty.sx == 0 || c->tty.sy == 0) 756 return (0); 757 memcpy(&old_status, &c->status, sizeof old_status); 758 screen_init(&c->status, c->tty.sx, 1, 0); 759 760 len = screen_write_strlen("%s", c->prompt_string); 761 if (len > c->tty.sx) 762 len = c->tty.sx; 763 off = 0; 764 765 /* Change colours for command mode. */ 766 if (c->prompt_mdata.mode == 1) 767 style_apply(&gc, s->options, "message-command-style"); 768 else 769 style_apply(&gc, s->options, "message-style"); 770 771 screen_write_start(&ctx, NULL, &c->status); 772 773 screen_write_cursormove(&ctx, 0, 0); 774 screen_write_nputs(&ctx, len, &gc, "%s", c->prompt_string); 775 776 left = c->tty.sx - len; 777 if (left != 0) { 778 size = screen_write_strlen("%s", c->prompt_buffer); 779 if (c->prompt_index >= left) { 780 off = c->prompt_index - left + 1; 781 if (c->prompt_index == size) 782 left--; 783 size = left; 784 } 785 screen_write_nputs(&ctx, left, &gc, "%s", c->prompt_buffer + 786 off); 787 788 for (i = len + size; i < c->tty.sx; i++) 789 screen_write_putc(&ctx, &gc, ' '); 790 } 791 792 screen_write_stop(&ctx); 793 794 /* Apply fake cursor. */ 795 off = len + c->prompt_index - off; 796 grid_view_get_cell(c->status.grid, off, 0, &gc); 797 gc.attr ^= GRID_ATTR_REVERSE; 798 grid_view_set_cell(c->status.grid, off, 0, &gc); 799 800 if (grid_compare(c->status.grid, old_status.grid) == 0) { 801 screen_free(&old_status); 802 return (0); 803 } 804 screen_free(&old_status); 805 return (1); 806} 807 808/* Handle keys in prompt. */ 809void 810status_prompt_key(struct client *c, key_code key) 811{ 812 struct session *sess = c->session; 813 struct options *oo = sess->options; 814 struct paste_buffer *pb; 815 char *s, *first, *last, word[64], swapc; 816 const char *histstr, *bufdata, *wsep = NULL; 817 u_char ch; 818 size_t size, n, off, idx, bufsize; 819 820 size = strlen(c->prompt_buffer); 821 switch (mode_key_lookup(&c->prompt_mdata, key, NULL, NULL)) { 822 case MODEKEYEDIT_CURSORLEFT: 823 if (c->prompt_index > 0) { 824 c->prompt_index--; 825 c->flags |= CLIENT_STATUS; 826 } 827 break; 828 case MODEKEYEDIT_SWITCHMODE: 829 c->flags |= CLIENT_STATUS; 830 break; 831 case MODEKEYEDIT_SWITCHMODEAPPEND: 832 c->flags |= CLIENT_STATUS; 833 /* FALLTHROUGH */ 834 case MODEKEYEDIT_CURSORRIGHT: 835 if (c->prompt_index < size) { 836 c->prompt_index++; 837 c->flags |= CLIENT_STATUS; 838 } 839 break; 840 case MODEKEYEDIT_SWITCHMODEBEGINLINE: 841 c->flags |= CLIENT_STATUS; 842 /* FALLTHROUGH */ 843 case MODEKEYEDIT_STARTOFLINE: 844 if (c->prompt_index != 0) { 845 c->prompt_index = 0; 846 c->flags |= CLIENT_STATUS; 847 } 848 break; 849 case MODEKEYEDIT_SWITCHMODEAPPENDLINE: 850 c->flags |= CLIENT_STATUS; 851 /* FALLTHROUGH */ 852 case MODEKEYEDIT_ENDOFLINE: 853 if (c->prompt_index != size) { 854 c->prompt_index = size; 855 c->flags |= CLIENT_STATUS; 856 } 857 break; 858 case MODEKEYEDIT_COMPLETE: 859 if (*c->prompt_buffer == '\0') 860 break; 861 862 idx = c->prompt_index; 863 if (idx != 0) 864 idx--; 865 866 /* Find the word we are in. */ 867 first = c->prompt_buffer + idx; 868 while (first > c->prompt_buffer && *first != ' ') 869 first--; 870 while (*first == ' ') 871 first++; 872 last = c->prompt_buffer + idx; 873 while (*last != '\0' && *last != ' ') 874 last++; 875 while (*last == ' ') 876 last--; 877 if (*last != '\0') 878 last++; 879 if (last <= first || 880 ((size_t) (last - first)) > (sizeof word) - 1) 881 break; 882 memcpy(word, first, last - first); 883 word[last - first] = '\0'; 884 885 /* And try to complete it. */ 886 if ((s = status_prompt_complete(sess, word)) == NULL) 887 break; 888 889 /* Trim out word. */ 890 n = size - (last - c->prompt_buffer) + 1; /* with \0 */ 891 memmove(first, last, n); 892 size -= last - first; 893 894 /* Insert the new word. */ 895 size += strlen(s); 896 off = first - c->prompt_buffer; 897 c->prompt_buffer = xrealloc(c->prompt_buffer, size + 1); 898 first = c->prompt_buffer + off; 899 memmove(first + strlen(s), first, n); 900 memcpy(first, s, strlen(s)); 901 902 c->prompt_index = (first - c->prompt_buffer) + strlen(s); 903 free(s); 904 905 c->flags |= CLIENT_STATUS; 906 break; 907 case MODEKEYEDIT_BACKSPACE: 908 if (c->prompt_index != 0) { 909 if (c->prompt_index == size) 910 c->prompt_buffer[--c->prompt_index] = '\0'; 911 else { 912 memmove(c->prompt_buffer + c->prompt_index - 1, 913 c->prompt_buffer + c->prompt_index, 914 size + 1 - c->prompt_index); 915 c->prompt_index--; 916 } 917 c->flags |= CLIENT_STATUS; 918 } 919 break; 920 case MODEKEYEDIT_DELETE: 921 case MODEKEYEDIT_SWITCHMODESUBSTITUTE: 922 if (c->prompt_index != size) { 923 memmove(c->prompt_buffer + c->prompt_index, 924 c->prompt_buffer + c->prompt_index + 1, 925 size + 1 - c->prompt_index); 926 c->flags |= CLIENT_STATUS; 927 } 928 break; 929 case MODEKEYEDIT_DELETELINE: 930 case MODEKEYEDIT_SWITCHMODESUBSTITUTELINE: 931 *c->prompt_buffer = '\0'; 932 c->prompt_index = 0; 933 c->flags |= CLIENT_STATUS; 934 break; 935 case MODEKEYEDIT_DELETETOENDOFLINE: 936 case MODEKEYEDIT_SWITCHMODECHANGELINE: 937 if (c->prompt_index < size) { 938 c->prompt_buffer[c->prompt_index] = '\0'; 939 c->flags |= CLIENT_STATUS; 940 } 941 break; 942 case MODEKEYEDIT_DELETEWORD: 943 wsep = options_get_string(oo, "word-separators"); 944 idx = c->prompt_index; 945 946 /* Find a non-separator. */ 947 while (idx != 0) { 948 idx--; 949 if (!strchr(wsep, c->prompt_buffer[idx])) 950 break; 951 } 952 953 /* Find the separator at the beginning of the word. */ 954 while (idx != 0) { 955 idx--; 956 if (strchr(wsep, c->prompt_buffer[idx])) { 957 /* Go back to the word. */ 958 idx++; 959 break; 960 } 961 } 962 963 memmove(c->prompt_buffer + idx, 964 c->prompt_buffer + c->prompt_index, 965 size + 1 - c->prompt_index); 966 memset(c->prompt_buffer + size - (c->prompt_index - idx), 967 '\0', c->prompt_index - idx); 968 c->prompt_index = idx; 969 c->flags |= CLIENT_STATUS; 970 break; 971 case MODEKEYEDIT_NEXTSPACE: 972 wsep = " "; 973 /* FALLTHROUGH */ 974 case MODEKEYEDIT_NEXTWORD: 975 if (wsep == NULL) 976 wsep = options_get_string(oo, "word-separators"); 977 978 /* Find a separator. */ 979 while (c->prompt_index != size) { 980 c->prompt_index++; 981 if (strchr(wsep, c->prompt_buffer[c->prompt_index])) 982 break; 983 } 984 985 /* Find the word right after the separation. */ 986 while (c->prompt_index != size) { 987 c->prompt_index++; 988 if (!strchr(wsep, c->prompt_buffer[c->prompt_index])) 989 break; 990 } 991 992 c->flags |= CLIENT_STATUS; 993 break; 994 case MODEKEYEDIT_NEXTSPACEEND: 995 wsep = " "; 996 /* FALLTHROUGH */ 997 case MODEKEYEDIT_NEXTWORDEND: 998 if (wsep == NULL) 999 wsep = options_get_string(oo, "word-separators"); 1000 1001 /* Find a word. */ 1002 while (c->prompt_index != size) { 1003 c->prompt_index++; 1004 if (!strchr(wsep, c->prompt_buffer[c->prompt_index])) 1005 break; 1006 } 1007 1008 /* Find the separator at the end of the word. */ 1009 while (c->prompt_index != size) { 1010 c->prompt_index++; 1011 if (strchr(wsep, c->prompt_buffer[c->prompt_index])) 1012 break; 1013 } 1014 1015 /* Back up to the end-of-word like vi. */ 1016 if (options_get_number(oo, "status-keys") == MODEKEY_VI && 1017 c->prompt_index != 0) 1018 c->prompt_index--; 1019 1020 c->flags |= CLIENT_STATUS; 1021 break; 1022 case MODEKEYEDIT_PREVIOUSSPACE: 1023 wsep = " "; 1024 /* FALLTHROUGH */ 1025 case MODEKEYEDIT_PREVIOUSWORD: 1026 if (wsep == NULL) 1027 wsep = options_get_string(oo, "word-separators"); 1028 1029 /* Find a non-separator. */ 1030 while (c->prompt_index != 0) { 1031 c->prompt_index--; 1032 if (!strchr(wsep, c->prompt_buffer[c->prompt_index])) 1033 break; 1034 } 1035 1036 /* Find the separator at the beginning of the word. */ 1037 while (c->prompt_index != 0) { 1038 c->prompt_index--; 1039 if (strchr(wsep, c->prompt_buffer[c->prompt_index])) { 1040 /* Go back to the word. */ 1041 c->prompt_index++; 1042 break; 1043 } 1044 } 1045 1046 c->flags |= CLIENT_STATUS; 1047 break; 1048 case MODEKEYEDIT_HISTORYUP: 1049 histstr = status_prompt_up_history(&c->prompt_hindex); 1050 if (histstr == NULL) 1051 break; 1052 free(c->prompt_buffer); 1053 c->prompt_buffer = xstrdup(histstr); 1054 c->prompt_index = strlen(c->prompt_buffer); 1055 c->flags |= CLIENT_STATUS; 1056 break; 1057 case MODEKEYEDIT_HISTORYDOWN: 1058 histstr = status_prompt_down_history(&c->prompt_hindex); 1059 if (histstr == NULL) 1060 break; 1061 free(c->prompt_buffer); 1062 c->prompt_buffer = xstrdup(histstr); 1063 c->prompt_index = strlen(c->prompt_buffer); 1064 c->flags |= CLIENT_STATUS; 1065 break; 1066 case MODEKEYEDIT_PASTE: 1067 if ((pb = paste_get_top(NULL)) == NULL) 1068 break; 1069 bufdata = paste_buffer_data(pb, &bufsize); 1070 for (n = 0; n < bufsize; n++) { 1071 ch = (u_char)bufdata[n]; 1072 if (ch < 32 || ch == 127) 1073 break; 1074 } 1075 1076 c->prompt_buffer = xrealloc(c->prompt_buffer, size + n + 1); 1077 if (c->prompt_index == size) { 1078 memcpy(c->prompt_buffer + c->prompt_index, bufdata, n); 1079 c->prompt_index += n; 1080 c->prompt_buffer[c->prompt_index] = '\0'; 1081 } else { 1082 memmove(c->prompt_buffer + c->prompt_index + n, 1083 c->prompt_buffer + c->prompt_index, 1084 size + 1 - c->prompt_index); 1085 memcpy(c->prompt_buffer + c->prompt_index, bufdata, n); 1086 c->prompt_index += n; 1087 } 1088 1089 c->flags |= CLIENT_STATUS; 1090 break; 1091 case MODEKEYEDIT_TRANSPOSECHARS: 1092 idx = c->prompt_index; 1093 if (idx < size) 1094 idx++; 1095 if (idx >= 2) { 1096 swapc = c->prompt_buffer[idx - 2]; 1097 c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1]; 1098 c->prompt_buffer[idx - 1] = swapc; 1099 c->prompt_index = idx; 1100 c->flags |= CLIENT_STATUS; 1101 } 1102 break; 1103 case MODEKEYEDIT_ENTER: 1104 if (*c->prompt_buffer != '\0') 1105 status_prompt_add_history(c->prompt_buffer); 1106 if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0) 1107 status_prompt_clear(c); 1108 break; 1109 case MODEKEYEDIT_CANCEL: 1110 if (c->prompt_callbackfn(c->prompt_data, NULL) == 0) 1111 status_prompt_clear(c); 1112 break; 1113 case MODEKEY_OTHER: 1114 if (key <= 0x1f || key >= 0x7f) 1115 break; 1116 c->prompt_buffer = xrealloc(c->prompt_buffer, size + 2); 1117 1118 if (c->prompt_index == size) { 1119 c->prompt_buffer[c->prompt_index++] = key; 1120 c->prompt_buffer[c->prompt_index] = '\0'; 1121 } else { 1122 memmove(c->prompt_buffer + c->prompt_index + 1, 1123 c->prompt_buffer + c->prompt_index, 1124 size + 1 - c->prompt_index); 1125 c->prompt_buffer[c->prompt_index++] = key; 1126 } 1127 1128 if (c->prompt_flags & PROMPT_SINGLE) { 1129 if (c->prompt_callbackfn(c->prompt_data, 1130 c->prompt_buffer) == 0) 1131 status_prompt_clear(c); 1132 } 1133 1134 c->flags |= CLIENT_STATUS; 1135 break; 1136 default: 1137 break; 1138 } 1139} 1140 1141/* Get previous line from the history. */ 1142const char * 1143status_prompt_up_history(u_int *idx) 1144{ 1145 /* 1146 * History runs from 0 to size - 1. Index is from 0 to size. Zero is 1147 * empty. 1148 */ 1149 1150 if (status_prompt_hsize == 0 || *idx == status_prompt_hsize) 1151 return (NULL); 1152 (*idx)++; 1153 return (status_prompt_hlist[status_prompt_hsize - *idx]); 1154} 1155 1156/* Get next line from the history. */ 1157const char * 1158status_prompt_down_history(u_int *idx) 1159{ 1160 if (status_prompt_hsize == 0 || *idx == 0) 1161 return (""); 1162 (*idx)--; 1163 if (*idx == 0) 1164 return (""); 1165 return (status_prompt_hlist[status_prompt_hsize - *idx]); 1166} 1167 1168/* Add line to the history. */ 1169void 1170status_prompt_add_history(const char *line) 1171{ 1172 size_t size; 1173 1174 if (status_prompt_hsize > 0 && 1175 strcmp(status_prompt_hlist[status_prompt_hsize - 1], line) == 0) 1176 return; 1177 1178 if (status_prompt_hsize == PROMPT_HISTORY) { 1179 free(status_prompt_hlist[0]); 1180 1181 size = (PROMPT_HISTORY - 1) * sizeof *status_prompt_hlist; 1182 memmove(&status_prompt_hlist[0], &status_prompt_hlist[1], size); 1183 1184 status_prompt_hlist[status_prompt_hsize - 1] = xstrdup(line); 1185 return; 1186 } 1187 1188 status_prompt_hlist = xreallocarray(status_prompt_hlist, 1189 status_prompt_hsize + 1, sizeof *status_prompt_hlist); 1190 status_prompt_hlist[status_prompt_hsize++] = xstrdup(line); 1191} 1192 1193/* Build completion list. */ 1194const char ** 1195status_prompt_complete_list(u_int *size, const char *s) 1196{ 1197 const char **list = NULL, **layout; 1198 const struct cmd_entry **cmdent; 1199 const struct options_table_entry *oe; 1200 const char *layouts[] = { 1201 "even-horizontal", "even-vertical", "main-horizontal", 1202 "main-vertical", "tiled", NULL 1203 }; 1204 1205 *size = 0; 1206 for (cmdent = cmd_table; *cmdent != NULL; cmdent++) { 1207 if (strncmp((*cmdent)->name, s, strlen(s)) == 0) { 1208 list = xreallocarray(list, (*size) + 1, sizeof *list); 1209 list[(*size)++] = (*cmdent)->name; 1210 } 1211 } 1212 for (oe = options_table; oe->name != NULL; oe++) { 1213 if (strncmp(oe->name, s, strlen(s)) == 0) { 1214 list = xreallocarray(list, (*size) + 1, sizeof *list); 1215 list[(*size)++] = oe->name; 1216 } 1217 } 1218 for (layout = layouts; *layout != NULL; layout++) { 1219 if (strncmp(*layout, s, strlen(s)) == 0) { 1220 list = xreallocarray(list, (*size) + 1, sizeof *list); 1221 list[(*size)++] = *layout; 1222 } 1223 } 1224 return (list); 1225} 1226 1227/* Find longest prefix. */ 1228char * 1229status_prompt_complete_prefix(const char **list, u_int size) 1230{ 1231 char *out; 1232 u_int i; 1233 size_t j; 1234 1235 out = xstrdup(list[0]); 1236 for (i = 1; i < size; i++) { 1237 j = strlen(list[i]); 1238 if (j > strlen(out)) 1239 j = strlen(out); 1240 for (; j > 0; j--) { 1241 if (out[j - 1] != list[i][j - 1]) 1242 out[j - 1] = '\0'; 1243 } 1244 } 1245 return (out); 1246} 1247 1248/* Complete word. */ 1249char * 1250status_prompt_complete(struct session *sess, const char *s) 1251{ 1252 const char **list = NULL, *colon; 1253 u_int size = 0, i; 1254 struct session *s_loop; 1255 struct winlink *wl; 1256 struct window *w; 1257 char *copy, *out, *tmp; 1258 1259 if (*s == '\0') 1260 return (NULL); 1261 out = NULL; 1262 1263 if (strncmp(s, "-t", 2) != 0 && strncmp(s, "-s", 2) != 0) { 1264 list = status_prompt_complete_list(&size, s); 1265 if (size == 0) 1266 out = NULL; 1267 else if (size == 1) 1268 xasprintf(&out, "%s ", list[0]); 1269 else 1270 out = status_prompt_complete_prefix(list, size); 1271 free(list); 1272 return (out); 1273 } 1274 copy = xstrdup(s); 1275 1276 colon = ":"; 1277 if (copy[strlen(copy) - 1] == ':') 1278 copy[strlen(copy) - 1] = '\0'; 1279 else 1280 colon = ""; 1281 s = copy + 2; 1282 1283 RB_FOREACH(s_loop, sessions, &sessions) { 1284 if (strncmp(s_loop->name, s, strlen(s)) == 0) { 1285 list = xreallocarray(list, size + 2, sizeof *list); 1286 list[size++] = s_loop->name; 1287 } 1288 } 1289 if (size == 1) { 1290 out = xstrdup(list[0]); 1291 if (session_find(list[0]) != NULL) 1292 colon = ":"; 1293 } else if (size != 0) 1294 out = status_prompt_complete_prefix(list, size); 1295 if (out != NULL) { 1296 xasprintf(&tmp, "-%c%s%s", copy[1], out, colon); 1297 out = tmp; 1298 goto found; 1299 } 1300 1301 colon = ""; 1302 if (*s == ':') { 1303 RB_FOREACH(wl, winlinks, &sess->windows) { 1304 xasprintf(&tmp, ":%s", wl->window->name); 1305 if (strncmp(tmp, s, strlen(s)) == 0){ 1306 list = xreallocarray(list, size + 1, 1307 sizeof *list); 1308 list[size++] = tmp; 1309 continue; 1310 } 1311 free(tmp); 1312 1313 xasprintf(&tmp, ":%d", wl->idx); 1314 if (strncmp(tmp, s, strlen(s)) == 0) { 1315 list = xreallocarray(list, size + 1, 1316 sizeof *list); 1317 list[size++] = tmp; 1318 continue; 1319 } 1320 free(tmp); 1321 } 1322 } else { 1323 RB_FOREACH(s_loop, sessions, &sessions) { 1324 RB_FOREACH(wl, winlinks, &s_loop->windows) { 1325 w = wl->window; 1326 1327 xasprintf(&tmp, "%s:%s", s_loop->name, w->name); 1328 if (strncmp(tmp, s, strlen(s)) == 0) { 1329 list = xreallocarray(list, size + 1, 1330 sizeof *list); 1331 list[size++] = tmp; 1332 continue; 1333 } 1334 free(tmp); 1335 1336 xasprintf(&tmp, "%s:%d", s_loop->name, wl->idx); 1337 if (strncmp(tmp, s, strlen(s)) == 0) { 1338 list = xreallocarray(list, size + 1, 1339 sizeof *list); 1340 list[size++] = tmp; 1341 continue; 1342 } 1343 free(tmp); 1344 } 1345 } 1346 } 1347 if (size == 1) { 1348 out = xstrdup(list[0]); 1349 colon = " "; 1350 } else if (size != 0) 1351 out = status_prompt_complete_prefix(list, size); 1352 if (out != NULL) { 1353 xasprintf(&tmp, "-%c%s%s", copy[1], out, colon); 1354 out = tmp; 1355 } 1356 1357 for (i = 0; i < size; i++) 1358 free((void *)list[i]); 1359 1360found: 1361 free(copy); 1362 free(list); 1363 return (out); 1364} 1365