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