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