status.c revision 1.136
1258945Sroberto/* $OpenBSD: status.c,v 1.136 2015/10/20 21:12:09 nicm Exp $ */ 2280849Scy 3258945Sroberto/* 4258945Sroberto * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net> 5258945Sroberto * 6258945Sroberto * Permission to use, copy, modify, and distribute this software for any 7258945Sroberto * purpose with or without fee is hereby granted, provided that the above 8258945Sroberto * copyright notice and this permission notice appear in all copies. 9258945Sroberto * 10258945Sroberto * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11258945Sroberto * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12258945Sroberto * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13258945Sroberto * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14258945Sroberto * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15258945Sroberto * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16258945Sroberto * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17258945Sroberto */ 18280849Scy 19258945Sroberto#include <sys/types.h> 20258945Sroberto#include <sys/time.h> 21258945Sroberto 22258945Sroberto#include <errno.h> 23258945Sroberto#include <limits.h> 24258945Sroberto#include <stdarg.h> 25258945Sroberto#include <stdlib.h> 26258945Sroberto#include <string.h> 27280849Scy#include <time.h> 28280849Scy#include <unistd.h> 29280849Scy 30280849Scy#include "tmux.h" 31258945Sroberto 32280849Scychar *status_redraw_get_left(struct client *, time_t, int, struct grid_cell *, 33280849Scy size_t *); 34280849Scychar *status_redraw_get_right(struct client *, time_t, int, 35280849Scy struct grid_cell *, size_t *); 36258945Srobertochar *status_print(struct client *, struct winlink *, time_t, 37258945Sroberto struct grid_cell *); 38258945Srobertochar *status_replace(struct client *, struct winlink *, const char *, time_t); 39258945Srobertovoid status_message_callback(int, short, void *); 40258945Srobertovoid status_timer_callback(int, short, void *); 41258945Sroberto 42258945Srobertoconst char *status_prompt_up_history(u_int *); 43258945Srobertoconst char *status_prompt_down_history(u_int *); 44258945Srobertovoid status_prompt_add_history(const char *); 45258945Sroberto 46258945Srobertoconst char **status_prompt_complete_list(u_int *, const char *); 47258945Srobertochar *status_prompt_complete_prefix(const char **, u_int); 48258945Srobertochar *status_prompt_complete(struct session *, const char *); 49258945Sroberto 50258945Srobertochar *status_prompt_find_history_file(void); 51258945Sroberto 52258945Sroberto/* Status prompt history. */ 53258945Sroberto#define PROMPT_HISTORY 100 54258945Srobertochar **status_prompt_hlist; 55258945Srobertou_int status_prompt_hsize; 56258945Sroberto 57258945Sroberto/* Find the history file to load/save from/to. */ 58258945Srobertochar * 59258945Srobertostatus_prompt_find_history_file(void) 60258945Sroberto{ 61258945Sroberto const char *home, *history_file; 62258945Sroberto char *path; 63258945Sroberto 64258945Sroberto history_file = options_get_string(&global_options, "history-file"); 65258945Sroberto if (*history_file == '\0') 66258945Sroberto return (NULL); 67258945Sroberto if (*history_file == '/') 68258945Sroberto return (xstrdup(history_file)); 69258945Sroberto 70258945Sroberto if (history_file[0] != '~' || history_file[1] != '/') 71258945Sroberto return (NULL); 72258945Sroberto if ((home = find_home()) == NULL) 73258945Sroberto return (NULL); 74258945Sroberto xasprintf(&path, "%s%s", home, history_file + 1); 75258945Sroberto return (path); 76258945Sroberto} 77258945Sroberto 78258945Sroberto/* Load status prompt history from file. */ 79258945Srobertovoid 80258945Srobertostatus_prompt_load_history(void) 81258945Sroberto{ 82258945Sroberto FILE *f; 83258945Sroberto char *history_file, *line, *tmp; 84258945Sroberto size_t length; 85280849Scy 86280849Scy if ((history_file = status_prompt_find_history_file()) == NULL) 87280849Scy return; 88280849Scy log_debug("loading history from %s", history_file); 89280849Scy 90280849Scy f = fopen(history_file, "r"); 91280849Scy if (f == NULL) { 92280849Scy log_debug("%s: %s", history_file, strerror(errno)); 93280849Scy free(history_file); 94280849Scy return; 95280849Scy } 96280849Scy free(history_file); 97280849Scy 98280849Scy for (;;) { 99280849Scy if ((line = fgetln(f, &length)) == NULL) 100280849Scy break; 101280849Scy 102280849Scy if (length > 0) { 103280849Scy 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, int utf8flag, 212 struct grid_cell *gc, 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(utf8flag, "%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, int utf8flag, 234 struct grid_cell *gc, 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(utf8flag, "%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, utf8flag; 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 /* Get UTF-8 flag. */ 316 utf8flag = options_get_number(&s->options, "status-utf8"); 317 318 /* Work out left and right strings. */ 319 memcpy(&lgc, &stdgc, sizeof lgc); 320 left = status_redraw_get_left(c, t, utf8flag, &lgc, &llen); 321 memcpy(&rgc, &stdgc, sizeof rgc); 322 right = status_redraw_get_right(c, t, utf8flag, &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 = 344 screen_write_cstrlen(utf8flag, "%s", wl->status_text); 345 346 if (wl == s->curw) 347 wloffset = wlwidth; 348 349 oo = &wl->window->options; 350 sep = options_get_string(oo, "window-status-separator"); 351 seplen = screen_write_strlen(utf8flag, "%s", sep); 352 wlwidth += wl->status_width + seplen; 353 } 354 355 /* Create a new screen for the window list. */ 356 screen_init(&window_list, wlwidth, 1, 0); 357 358 /* And draw the window list into it. */ 359 screen_write_start(&ctx, NULL, &window_list); 360 RB_FOREACH(wl, winlinks, &s->windows) { 361 screen_write_cnputs(&ctx, 362 -1, &wl->status_cell, utf8flag, "%s", wl->status_text); 363 364 oo = &wl->window->options; 365 sep = options_get_string(oo, "window-status-separator"); 366 screen_write_nputs(&ctx, -1, &stdgc, utf8flag, "%s", sep); 367 } 368 screen_write_stop(&ctx); 369 370 /* If there is enough space for the total width, skip to draw now. */ 371 if (wlwidth <= wlavailable) 372 goto draw; 373 374 /* Find size of current window text. */ 375 wlsize = s->curw->status_width; 376 377 /* 378 * If the current window is already on screen, good to draw from the 379 * start and just leave off the end. 380 */ 381 if (wloffset + wlsize < wlavailable) { 382 if (wlavailable > 0) { 383 rarrow = 1; 384 wlavailable--; 385 } 386 wlwidth = wlavailable; 387 } else { 388 /* 389 * Work out how many characters we need to omit from the 390 * start. There are wlavailable characters to fill, and 391 * wloffset + wlsize must be the last. So, the start character 392 * is wloffset + wlsize - wlavailable. 393 */ 394 if (wlavailable > 0) { 395 larrow = 1; 396 wlavailable--; 397 } 398 399 wlstart = wloffset + wlsize - wlavailable; 400 if (wlavailable > 0 && wlwidth > wlstart + wlavailable + 1) { 401 rarrow = 1; 402 wlstart++; 403 wlavailable--; 404 } 405 wlwidth = wlavailable; 406 } 407 408 /* Bail if anything is now too small too. */ 409 if (wlwidth == 0 || wlavailable == 0) { 410 screen_free(&window_list); 411 goto out; 412 } 413 414 /* 415 * Now the start position is known, work out the state of the left and 416 * right arrows. 417 */ 418 offset = 0; 419 RB_FOREACH(wl, winlinks, &s->windows) { 420 if (wl->flags & WINLINK_ALERTFLAGS && 421 larrow == 1 && offset < wlstart) 422 larrow = -1; 423 424 offset += wl->status_width; 425 426 if (wl->flags & WINLINK_ALERTFLAGS && 427 rarrow == 1 && offset > wlstart + wlwidth) 428 rarrow = -1; 429 } 430 431draw: 432 /* Begin drawing. */ 433 screen_write_start(&ctx, NULL, &c->status); 434 435 /* Draw the left string and arrow. */ 436 screen_write_cursormove(&ctx, 0, 0); 437 if (llen != 0) 438 screen_write_cnputs(&ctx, llen, &lgc, utf8flag, "%s", left); 439 if (larrow != 0) { 440 memcpy(&gc, &stdgc, sizeof gc); 441 if (larrow == -1) 442 gc.attr ^= GRID_ATTR_REVERSE; 443 screen_write_putc(&ctx, &gc, '<'); 444 } 445 446 /* Draw the right string and arrow. */ 447 if (rarrow != 0) { 448 screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, 0); 449 memcpy(&gc, &stdgc, sizeof gc); 450 if (rarrow == -1) 451 gc.attr ^= GRID_ATTR_REVERSE; 452 screen_write_putc(&ctx, &gc, '>'); 453 } else 454 screen_write_cursormove(&ctx, c->tty.sx - rlen, 0); 455 if (rlen != 0) 456 screen_write_cnputs(&ctx, rlen, &rgc, utf8flag, "%s", right); 457 458 /* Figure out the offset for the window list. */ 459 if (llen != 0) 460 wloffset = llen; 461 else 462 wloffset = 0; 463 if (wlwidth < wlavailable) { 464 switch (options_get_number(&s->options, "status-justify")) { 465 case 1: /* centred */ 466 wloffset += (wlavailable - wlwidth) / 2; 467 break; 468 case 2: /* right */ 469 wloffset += (wlavailable - wlwidth); 470 break; 471 } 472 } 473 if (larrow != 0) 474 wloffset++; 475 476 /* Copy the window list. */ 477 c->wlmouse = -wloffset + wlstart; 478 screen_write_cursormove(&ctx, wloffset, 0); 479 screen_write_copy(&ctx, &window_list, wlstart, 0, wlwidth, 1); 480 screen_free(&window_list); 481 482 screen_write_stop(&ctx); 483 484out: 485 free(left); 486 free(right); 487 488 if (grid_compare(c->status.grid, old_status.grid) == 0) { 489 screen_free(&old_status); 490 return (0); 491 } 492 screen_free(&old_status); 493 return (1); 494} 495 496/* Replace special sequences in fmt. */ 497char * 498status_replace(struct client *c, struct winlink *wl, const char *fmt, time_t t) 499{ 500 struct format_tree *ft; 501 char *expanded; 502 503 if (fmt == NULL) 504 return (xstrdup("")); 505 506 if (c->flags & CLIENT_STATUSFORCE) 507 ft = format_create_flags(FORMAT_STATUS|FORMAT_FORCE); 508 else 509 ft = format_create_flags(FORMAT_STATUS); 510 format_defaults(ft, c, NULL, wl, NULL); 511 512 expanded = format_expand_time(ft, fmt, t); 513 514 format_free(ft); 515 return (expanded); 516} 517 518/* Return winlink status line entry and adjust gc as necessary. */ 519char * 520status_print(struct client *c, struct winlink *wl, time_t t, 521 struct grid_cell *gc) 522{ 523 struct options *oo = &wl->window->options; 524 struct session *s = c->session; 525 const char *fmt; 526 char *text; 527 528 style_apply_update(gc, oo, "window-status-style"); 529 fmt = options_get_string(oo, "window-status-format"); 530 if (wl == s->curw) { 531 style_apply_update(gc, oo, "window-status-current-style"); 532 fmt = options_get_string(oo, "window-status-current-format"); 533 } 534 if (wl == TAILQ_FIRST(&s->lastw)) 535 style_apply_update(gc, oo, "window-status-last-style"); 536 537 if (wl->flags & WINLINK_BELL) 538 style_apply_update(gc, oo, "window-status-bell-style"); 539 else if (wl->flags & (WINLINK_ACTIVITY|WINLINK_SILENCE)) 540 style_apply_update(gc, oo, "window-status-activity-style"); 541 542 text = status_replace(c, wl, fmt, t); 543 return (text); 544} 545 546/* Set a status line message. */ 547void 548status_message_set(struct client *c, const char *fmt, ...) 549{ 550 struct timeval tv; 551 struct message_entry *msg, *msg1; 552 va_list ap; 553 int delay; 554 u_int first, limit; 555 556 limit = options_get_number(&global_options, "message-limit"); 557 558 status_prompt_clear(c); 559 status_message_clear(c); 560 561 va_start(ap, fmt); 562 xvasprintf(&c->message_string, fmt, ap); 563 va_end(ap); 564 565 msg = xcalloc(1, sizeof *msg); 566 msg->msg_time = time(NULL); 567 msg->msg_num = c->message_next++; 568 msg->msg = xstrdup(c->message_string); 569 TAILQ_INSERT_TAIL(&c->message_log, msg, entry); 570 571 first = c->message_next - limit; 572 TAILQ_FOREACH_SAFE(msg, &c->message_log, entry, msg1) { 573 if (msg->msg_num >= first) 574 continue; 575 free(msg->msg); 576 TAILQ_REMOVE(&c->message_log, msg, entry); 577 free(msg); 578 } 579 580 delay = options_get_number(&c->session->options, "display-time"); 581 tv.tv_sec = delay / 1000; 582 tv.tv_usec = (delay % 1000) * 1000L; 583 584 if (event_initialized(&c->message_timer)) 585 evtimer_del(&c->message_timer); 586 evtimer_set(&c->message_timer, status_message_callback, c); 587 evtimer_add(&c->message_timer, &tv); 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 int utf8flag; 628 629 if (c->tty.sx == 0 || c->tty.sy == 0) 630 return (0); 631 memcpy(&old_status, &c->status, sizeof old_status); 632 screen_init(&c->status, c->tty.sx, 1, 0); 633 634 utf8flag = options_get_number(&s->options, "status-utf8"); 635 636 len = screen_write_strlen(utf8flag, "%s", c->message_string); 637 if (len > c->tty.sx) 638 len = c->tty.sx; 639 640 style_apply(&gc, &s->options, "message-style"); 641 642 screen_write_start(&ctx, NULL, &c->status); 643 644 screen_write_cursormove(&ctx, 0, 0); 645 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->message_string); 646 for (; len < c->tty.sx; len++) 647 screen_write_putc(&ctx, &gc, ' '); 648 649 screen_write_stop(&ctx); 650 651 if (grid_compare(c->status.grid, old_status.grid) == 0) { 652 screen_free(&old_status); 653 return (0); 654 } 655 screen_free(&old_status); 656 return (1); 657} 658 659/* Enable status line prompt. */ 660void 661status_prompt_set(struct client *c, const char *msg, const char *input, 662 int (*callbackfn)(void *, const char *), void (*freefn)(void *), 663 void *data, int flags) 664{ 665 struct format_tree *ft; 666 int keys; 667 time_t t; 668 669 ft = format_create(); 670 format_defaults(ft, c, NULL, NULL, NULL); 671 t = time(NULL); 672 673 status_message_clear(c); 674 status_prompt_clear(c); 675 676 c->prompt_string = format_expand_time(ft, msg, t); 677 678 c->prompt_buffer = format_expand_time(ft, input, t); 679 c->prompt_index = strlen(c->prompt_buffer); 680 681 c->prompt_callbackfn = callbackfn; 682 c->prompt_freefn = freefn; 683 c->prompt_data = data; 684 685 c->prompt_hindex = 0; 686 687 c->prompt_flags = flags; 688 689 keys = options_get_number(&c->session->options, "status-keys"); 690 if (keys == MODEKEY_EMACS) 691 mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit); 692 else 693 mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit); 694 695 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); 696 c->flags |= CLIENT_STATUS; 697 698 format_free(ft); 699} 700 701/* Remove status line prompt. */ 702void 703status_prompt_clear(struct client *c) 704{ 705 if (c->prompt_string == NULL) 706 return; 707 708 if (c->prompt_freefn != NULL && c->prompt_data != NULL) 709 c->prompt_freefn(c->prompt_data); 710 711 free(c->prompt_string); 712 c->prompt_string = NULL; 713 714 free(c->prompt_buffer); 715 c->prompt_buffer = NULL; 716 717 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 718 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */ 719 720 screen_reinit(&c->status); 721} 722 723/* Update status line prompt with a new prompt string. */ 724void 725status_prompt_update(struct client *c, const char *msg, const char *input) 726{ 727 struct format_tree *ft; 728 time_t t; 729 730 ft = format_create(); 731 format_defaults(ft, c, NULL, NULL, NULL); 732 t = time(NULL); 733 734 free(c->prompt_string); 735 c->prompt_string = format_expand_time(ft, msg, t); 736 737 free(c->prompt_buffer); 738 c->prompt_buffer = format_expand_time(ft, input, t); 739 c->prompt_index = strlen(c->prompt_buffer); 740 741 c->prompt_hindex = 0; 742 743 c->flags |= CLIENT_STATUS; 744 745 format_free(ft); 746} 747 748/* Draw client prompt on status line of present else on last line. */ 749int 750status_prompt_redraw(struct client *c) 751{ 752 struct screen_write_ctx ctx; 753 struct session *s = c->session; 754 struct screen old_status; 755 size_t i, size, left, len, off; 756 struct grid_cell gc, *gcp; 757 int utf8flag; 758 759 if (c->tty.sx == 0 || c->tty.sy == 0) 760 return (0); 761 memcpy(&old_status, &c->status, sizeof old_status); 762 screen_init(&c->status, c->tty.sx, 1, 0); 763 764 utf8flag = options_get_number(&s->options, "status-utf8"); 765 766 len = screen_write_strlen(utf8flag, "%s", c->prompt_string); 767 if (len > c->tty.sx) 768 len = c->tty.sx; 769 off = 0; 770 771 /* Change colours for command mode. */ 772 if (c->prompt_mdata.mode == 1) 773 style_apply(&gc, &s->options, "message-command-style"); 774 else 775 style_apply(&gc, &s->options, "message-style"); 776 777 screen_write_start(&ctx, NULL, &c->status); 778 779 screen_write_cursormove(&ctx, 0, 0); 780 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->prompt_string); 781 782 left = c->tty.sx - len; 783 if (left != 0) { 784 size = screen_write_strlen(utf8flag, "%s", c->prompt_buffer); 785 if (c->prompt_index >= left) { 786 off = c->prompt_index - left + 1; 787 if (c->prompt_index == size) 788 left--; 789 size = left; 790 } 791 screen_write_nputs( 792 &ctx, left, &gc, utf8flag, "%s", c->prompt_buffer + off); 793 794 for (i = len + size; i < c->tty.sx; i++) 795 screen_write_putc(&ctx, &gc, ' '); 796 } 797 798 screen_write_stop(&ctx); 799 800 /* Apply fake cursor. */ 801 off = len + c->prompt_index - off; 802 gcp = grid_view_get_cell(c->status.grid, off, 0); 803 gcp->attr ^= GRID_ATTR_REVERSE; 804 805 if (grid_compare(c->status.grid, old_status.grid) == 0) { 806 screen_free(&old_status); 807 return (0); 808 } 809 screen_free(&old_status); 810 return (1); 811} 812 813/* Handle keys in prompt. */ 814void 815status_prompt_key(struct client *c, int key) 816{ 817 struct session *sess = c->session; 818 struct options *oo = &sess->options; 819 struct paste_buffer *pb; 820 char *s, *first, *last, word[64], swapc; 821 const char *histstr, *bufdata, *wsep = NULL; 822 u_char ch; 823 size_t size, n, off, idx, bufsize; 824 825 size = strlen(c->prompt_buffer); 826 switch (mode_key_lookup(&c->prompt_mdata, key, NULL)) { 827 case MODEKEYEDIT_CURSORLEFT: 828 if (c->prompt_index > 0) { 829 c->prompt_index--; 830 c->flags |= CLIENT_STATUS; 831 } 832 break; 833 case MODEKEYEDIT_SWITCHMODE: 834 c->flags |= CLIENT_STATUS; 835 break; 836 case MODEKEYEDIT_SWITCHMODEAPPEND: 837 c->flags |= CLIENT_STATUS; 838 /* FALLTHROUGH */ 839 case MODEKEYEDIT_CURSORRIGHT: 840 if (c->prompt_index < size) { 841 c->prompt_index++; 842 c->flags |= CLIENT_STATUS; 843 } 844 break; 845 case MODEKEYEDIT_SWITCHMODEBEGINLINE: 846 c->flags |= CLIENT_STATUS; 847 /* FALLTHROUGH */ 848 case MODEKEYEDIT_STARTOFLINE: 849 if (c->prompt_index != 0) { 850 c->prompt_index = 0; 851 c->flags |= CLIENT_STATUS; 852 } 853 break; 854 case MODEKEYEDIT_SWITCHMODEAPPENDLINE: 855 c->flags |= CLIENT_STATUS; 856 /* FALLTHROUGH */ 857 case MODEKEYEDIT_ENDOFLINE: 858 if (c->prompt_index != size) { 859 c->prompt_index = size; 860 c->flags |= CLIENT_STATUS; 861 } 862 break; 863 case MODEKEYEDIT_COMPLETE: 864 if (*c->prompt_buffer == '\0') 865 break; 866 867 idx = c->prompt_index; 868 if (idx != 0) 869 idx--; 870 871 /* Find the word we are in. */ 872 first = c->prompt_buffer + idx; 873 while (first > c->prompt_buffer && *first != ' ') 874 first--; 875 while (*first == ' ') 876 first++; 877 last = c->prompt_buffer + idx; 878 while (*last != '\0' && *last != ' ') 879 last++; 880 while (*last == ' ') 881 last--; 882 if (*last != '\0') 883 last++; 884 if (last <= first || 885 ((size_t) (last - first)) > (sizeof word) - 1) 886 break; 887 memcpy(word, first, last - first); 888 word[last - first] = '\0'; 889 890 /* And try to complete it. */ 891 if ((s = status_prompt_complete(sess, word)) == NULL) 892 break; 893 894 /* Trim out word. */ 895 n = size - (last - c->prompt_buffer) + 1; /* with \0 */ 896 memmove(first, last, n); 897 size -= last - first; 898 899 /* Insert the new word. */ 900 size += strlen(s); 901 off = first - c->prompt_buffer; 902 c->prompt_buffer = xrealloc(c->prompt_buffer, size + 1); 903 first = c->prompt_buffer + off; 904 memmove(first + strlen(s), first, n); 905 memcpy(first, s, strlen(s)); 906 907 c->prompt_index = (first - c->prompt_buffer) + strlen(s); 908 free(s); 909 910 c->flags |= CLIENT_STATUS; 911 break; 912 case MODEKEYEDIT_BACKSPACE: 913 if (c->prompt_index != 0) { 914 if (c->prompt_index == size) 915 c->prompt_buffer[--c->prompt_index] = '\0'; 916 else { 917 memmove(c->prompt_buffer + c->prompt_index - 1, 918 c->prompt_buffer + c->prompt_index, 919 size + 1 - c->prompt_index); 920 c->prompt_index--; 921 } 922 c->flags |= CLIENT_STATUS; 923 } 924 break; 925 case MODEKEYEDIT_DELETE: 926 case MODEKEYEDIT_SWITCHMODESUBSTITUTE: 927 if (c->prompt_index != size) { 928 memmove(c->prompt_buffer + c->prompt_index, 929 c->prompt_buffer + c->prompt_index + 1, 930 size + 1 - c->prompt_index); 931 c->flags |= CLIENT_STATUS; 932 } 933 break; 934 case MODEKEYEDIT_DELETELINE: 935 case MODEKEYEDIT_SWITCHMODESUBSTITUTELINE: 936 *c->prompt_buffer = '\0'; 937 c->prompt_index = 0; 938 c->flags |= CLIENT_STATUS; 939 break; 940 case MODEKEYEDIT_DELETETOENDOFLINE: 941 case MODEKEYEDIT_SWITCHMODECHANGELINE: 942 if (c->prompt_index < size) { 943 c->prompt_buffer[c->prompt_index] = '\0'; 944 c->flags |= CLIENT_STATUS; 945 } 946 break; 947 case MODEKEYEDIT_DELETEWORD: 948 wsep = options_get_string(oo, "word-separators"); 949 idx = c->prompt_index; 950 951 /* Find a non-separator. */ 952 while (idx != 0) { 953 idx--; 954 if (!strchr(wsep, c->prompt_buffer[idx])) 955 break; 956 } 957 958 /* Find the separator at the beginning of the word. */ 959 while (idx != 0) { 960 idx--; 961 if (strchr(wsep, c->prompt_buffer[idx])) { 962 /* Go back to the word. */ 963 idx++; 964 break; 965 } 966 } 967 968 memmove(c->prompt_buffer + idx, 969 c->prompt_buffer + c->prompt_index, 970 size + 1 - c->prompt_index); 971 memset(c->prompt_buffer + size - (c->prompt_index - idx), 972 '\0', c->prompt_index - idx); 973 c->prompt_index = idx; 974 c->flags |= CLIENT_STATUS; 975 break; 976 case MODEKEYEDIT_NEXTSPACE: 977 wsep = " "; 978 /* FALLTHROUGH */ 979 case MODEKEYEDIT_NEXTWORD: 980 if (wsep == NULL) 981 wsep = options_get_string(oo, "word-separators"); 982 983 /* Find a separator. */ 984 while (c->prompt_index != size) { 985 c->prompt_index++; 986 if (strchr(wsep, c->prompt_buffer[c->prompt_index])) 987 break; 988 } 989 990 /* Find the word right after the separation. */ 991 while (c->prompt_index != size) { 992 c->prompt_index++; 993 if (!strchr(wsep, c->prompt_buffer[c->prompt_index])) 994 break; 995 } 996 997 c->flags |= CLIENT_STATUS; 998 break; 999 case MODEKEYEDIT_NEXTSPACEEND: 1000 wsep = " "; 1001 /* FALLTHROUGH */ 1002 case MODEKEYEDIT_NEXTWORDEND: 1003 if (wsep == NULL) 1004 wsep = options_get_string(oo, "word-separators"); 1005 1006 /* Find a word. */ 1007 while (c->prompt_index != size) { 1008 c->prompt_index++; 1009 if (!strchr(wsep, c->prompt_buffer[c->prompt_index])) 1010 break; 1011 } 1012 1013 /* Find the separator at the end of the word. */ 1014 while (c->prompt_index != size) { 1015 c->prompt_index++; 1016 if (strchr(wsep, c->prompt_buffer[c->prompt_index])) 1017 break; 1018 } 1019 1020 /* Back up to the end-of-word like vi. */ 1021 if (options_get_number(oo, "status-keys") == MODEKEY_VI && 1022 c->prompt_index != 0) 1023 c->prompt_index--; 1024 1025 c->flags |= CLIENT_STATUS; 1026 break; 1027 case MODEKEYEDIT_PREVIOUSSPACE: 1028 wsep = " "; 1029 /* FALLTHROUGH */ 1030 case MODEKEYEDIT_PREVIOUSWORD: 1031 if (wsep == NULL) 1032 wsep = options_get_string(oo, "word-separators"); 1033 1034 /* Find a non-separator. */ 1035 while (c->prompt_index != 0) { 1036 c->prompt_index--; 1037 if (!strchr(wsep, c->prompt_buffer[c->prompt_index])) 1038 break; 1039 } 1040 1041 /* Find the separator at the beginning of the word. */ 1042 while (c->prompt_index != 0) { 1043 c->prompt_index--; 1044 if (strchr(wsep, c->prompt_buffer[c->prompt_index])) { 1045 /* Go back to the word. */ 1046 c->prompt_index++; 1047 break; 1048 } 1049 } 1050 1051 c->flags |= CLIENT_STATUS; 1052 break; 1053 case MODEKEYEDIT_HISTORYUP: 1054 histstr = status_prompt_up_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_HISTORYDOWN: 1063 histstr = status_prompt_down_history(&c->prompt_hindex); 1064 if (histstr == NULL) 1065 break; 1066 free(c->prompt_buffer); 1067 c->prompt_buffer = xstrdup(histstr); 1068 c->prompt_index = strlen(c->prompt_buffer); 1069 c->flags |= CLIENT_STATUS; 1070 break; 1071 case MODEKEYEDIT_PASTE: 1072 if ((pb = paste_get_top(NULL)) == NULL) 1073 break; 1074 bufdata = paste_buffer_data(pb, &bufsize); 1075 for (n = 0; n < bufsize; n++) { 1076 ch = (u_char)bufdata[n]; 1077 if (ch < 32 || ch == 127) 1078 break; 1079 } 1080 1081 c->prompt_buffer = xrealloc(c->prompt_buffer, size + n + 1); 1082 if (c->prompt_index == size) { 1083 memcpy(c->prompt_buffer + c->prompt_index, bufdata, n); 1084 c->prompt_index += n; 1085 c->prompt_buffer[c->prompt_index] = '\0'; 1086 } else { 1087 memmove(c->prompt_buffer + c->prompt_index + n, 1088 c->prompt_buffer + c->prompt_index, 1089 size + 1 - c->prompt_index); 1090 memcpy(c->prompt_buffer + c->prompt_index, bufdata, n); 1091 c->prompt_index += n; 1092 } 1093 1094 c->flags |= CLIENT_STATUS; 1095 break; 1096 case MODEKEYEDIT_TRANSPOSECHARS: 1097 idx = c->prompt_index; 1098 if (idx < size) 1099 idx++; 1100 if (idx >= 2) { 1101 swapc = c->prompt_buffer[idx - 2]; 1102 c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1]; 1103 c->prompt_buffer[idx - 1] = swapc; 1104 c->prompt_index = idx; 1105 c->flags |= CLIENT_STATUS; 1106 } 1107 break; 1108 case MODEKEYEDIT_ENTER: 1109 if (*c->prompt_buffer != '\0') 1110 status_prompt_add_history(c->prompt_buffer); 1111 if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0) 1112 status_prompt_clear(c); 1113 break; 1114 case MODEKEYEDIT_CANCEL: 1115 if (c->prompt_callbackfn(c->prompt_data, NULL) == 0) 1116 status_prompt_clear(c); 1117 break; 1118 case MODEKEY_OTHER: 1119 if ((key & 0xff00) != 0 || key < 32 || key == 127) 1120 break; 1121 c->prompt_buffer = xrealloc(c->prompt_buffer, size + 2); 1122 1123 if (c->prompt_index == size) { 1124 c->prompt_buffer[c->prompt_index++] = key; 1125 c->prompt_buffer[c->prompt_index] = '\0'; 1126 } else { 1127 memmove(c->prompt_buffer + c->prompt_index + 1, 1128 c->prompt_buffer + c->prompt_index, 1129 size + 1 - c->prompt_index); 1130 c->prompt_buffer[c->prompt_index++] = key; 1131 } 1132 1133 if (c->prompt_flags & PROMPT_SINGLE) { 1134 if (c->prompt_callbackfn( 1135 c->prompt_data, c->prompt_buffer) == 0) 1136 status_prompt_clear(c); 1137 } 1138 1139 c->flags |= CLIENT_STATUS; 1140 break; 1141 default: 1142 break; 1143 } 1144} 1145 1146/* Get previous line from the history. */ 1147const char * 1148status_prompt_up_history(u_int *idx) 1149{ 1150 /* 1151 * History runs from 0 to size - 1. Index is from 0 to size. Zero is 1152 * empty. 1153 */ 1154 1155 if (status_prompt_hsize == 0 || *idx == status_prompt_hsize) 1156 return (NULL); 1157 (*idx)++; 1158 return (status_prompt_hlist[status_prompt_hsize - *idx]); 1159} 1160 1161/* Get next line from the history. */ 1162const char * 1163status_prompt_down_history(u_int *idx) 1164{ 1165 if (status_prompt_hsize == 0 || *idx == 0) 1166 return (""); 1167 (*idx)--; 1168 if (*idx == 0) 1169 return (""); 1170 return (status_prompt_hlist[status_prompt_hsize - *idx]); 1171} 1172 1173/* Add line to the history. */ 1174void 1175status_prompt_add_history(const char *line) 1176{ 1177 size_t size; 1178 1179 if (status_prompt_hsize > 0 && 1180 strcmp(status_prompt_hlist[status_prompt_hsize - 1], line) == 0) 1181 return; 1182 1183 if (status_prompt_hsize == PROMPT_HISTORY) { 1184 free(status_prompt_hlist[0]); 1185 1186 size = (PROMPT_HISTORY - 1) * sizeof *status_prompt_hlist; 1187 memmove(&status_prompt_hlist[0], &status_prompt_hlist[1], size); 1188 1189 status_prompt_hlist[status_prompt_hsize - 1] = xstrdup(line); 1190 return; 1191 } 1192 1193 status_prompt_hlist = xreallocarray(status_prompt_hlist, 1194 status_prompt_hsize + 1, sizeof *status_prompt_hlist); 1195 status_prompt_hlist[status_prompt_hsize++] = xstrdup(line); 1196} 1197 1198/* Build completion list. */ 1199const char ** 1200status_prompt_complete_list(u_int *size, const char *s) 1201{ 1202 const char **list = NULL, **layout; 1203 const struct cmd_entry **cmdent; 1204 const struct options_table_entry *oe; 1205 const char *layouts[] = { 1206 "even-horizontal", "even-vertical", "main-horizontal", 1207 "main-vertical", "tiled", NULL 1208 }; 1209 1210 *size = 0; 1211 for (cmdent = cmd_table; *cmdent != NULL; cmdent++) { 1212 if (strncmp((*cmdent)->name, s, strlen(s)) == 0) { 1213 list = xreallocarray(list, (*size) + 1, sizeof *list); 1214 list[(*size)++] = (*cmdent)->name; 1215 } 1216 } 1217 for (oe = server_options_table; oe->name != NULL; oe++) { 1218 if (strncmp(oe->name, s, strlen(s)) == 0) { 1219 list = xreallocarray(list, (*size) + 1, sizeof *list); 1220 list[(*size)++] = oe->name; 1221 } 1222 } 1223 for (oe = session_options_table; oe->name != NULL; oe++) { 1224 if (strncmp(oe->name, s, strlen(s)) == 0) { 1225 list = xreallocarray(list, (*size) + 1, sizeof *list); 1226 list[(*size)++] = oe->name; 1227 } 1228 } 1229 for (oe = window_options_table; oe->name != NULL; oe++) { 1230 if (strncmp(oe->name, s, strlen(s)) == 0) { 1231 list = xreallocarray(list, (*size) + 1, sizeof *list); 1232 list[(*size)++] = oe->name; 1233 } 1234 } 1235 for (layout = layouts; *layout != NULL; layout++) { 1236 if (strncmp(*layout, s, strlen(s)) == 0) { 1237 list = xreallocarray(list, (*size) + 1, sizeof *list); 1238 list[(*size)++] = *layout; 1239 } 1240 } 1241 return (list); 1242} 1243 1244/* Find longest prefix. */ 1245char * 1246status_prompt_complete_prefix(const char **list, u_int size) 1247{ 1248 char *out; 1249 u_int i; 1250 size_t j; 1251 1252 out = xstrdup(list[0]); 1253 for (i = 1; i < size; i++) { 1254 j = strlen(list[i]); 1255 if (j > strlen(out)) 1256 j = strlen(out); 1257 for (; j > 0; j--) { 1258 if (out[j - 1] != list[i][j - 1]) 1259 out[j - 1] = '\0'; 1260 } 1261 } 1262 return (out); 1263} 1264 1265/* Complete word. */ 1266char * 1267status_prompt_complete(struct session *sess, const char *s) 1268{ 1269 const char **list = NULL, *colon; 1270 u_int size = 0, i; 1271 struct session *s_loop; 1272 struct winlink *wl; 1273 struct window *w; 1274 char *copy, *out, *tmp; 1275 1276 if (*s == '\0') 1277 return (NULL); 1278 out = NULL; 1279 1280 if (strncmp(s, "-t", 2) != 0 && strncmp(s, "-s", 2) != 0) { 1281 list = status_prompt_complete_list(&size, s); 1282 if (size == 0) 1283 out = NULL; 1284 else if (size == 1) 1285 xasprintf(&out, "%s ", list[0]); 1286 else 1287 out = status_prompt_complete_prefix(list, size); 1288 free(list); 1289 return (out); 1290 } 1291 copy = xstrdup(s); 1292 1293 colon = ":"; 1294 if (copy[strlen(copy) - 1] == ':') 1295 copy[strlen(copy) - 1] = '\0'; 1296 else 1297 colon = ""; 1298 s = copy + 2; 1299 1300 RB_FOREACH(s_loop, sessions, &sessions) { 1301 if (strncmp(s_loop->name, s, strlen(s)) == 0) { 1302 list = xreallocarray(list, size + 2, sizeof *list); 1303 list[size++] = s_loop->name; 1304 } 1305 } 1306 if (size == 1) { 1307 out = xstrdup(list[0]); 1308 if (session_find(list[0]) != NULL) 1309 colon = ":"; 1310 } else if (size != 0) 1311 out = status_prompt_complete_prefix(list, size); 1312 if (out != NULL) { 1313 xasprintf(&tmp, "-%c%s%s", copy[1], out, colon); 1314 out = tmp; 1315 goto found; 1316 } 1317 1318 colon = ""; 1319 if (*s == ':') { 1320 RB_FOREACH(wl, winlinks, &sess->windows) { 1321 xasprintf(&tmp, ":%s", wl->window->name); 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 xasprintf(&tmp, ":%d", wl->idx); 1331 if (strncmp(tmp, s, strlen(s)) == 0) { 1332 list = xreallocarray(list, size + 1, 1333 sizeof *list); 1334 list[size++] = tmp; 1335 continue; 1336 } 1337 free(tmp); 1338 } 1339 } else { 1340 RB_FOREACH(s_loop, sessions, &sessions) { 1341 RB_FOREACH(wl, winlinks, &s_loop->windows) { 1342 w = wl->window; 1343 1344 xasprintf(&tmp, "%s:%s", s_loop->name, w->name); 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 xasprintf(&tmp, "%s:%d", s_loop->name, wl->idx); 1354 if (strncmp(tmp, s, strlen(s)) == 0) { 1355 list = xreallocarray(list, size + 1, 1356 sizeof *list); 1357 list[size++] = tmp; 1358 continue; 1359 } 1360 free(tmp); 1361 } 1362 } 1363 } 1364 if (size == 1) { 1365 out = xstrdup(list[0]); 1366 colon = " "; 1367 } else if (size != 0) 1368 out = status_prompt_complete_prefix(list, size); 1369 if (out != NULL) { 1370 xasprintf(&tmp, "-%c%s%s", copy[1], out, colon); 1371 out = tmp; 1372 } 1373 1374 for (i = 0; i < size; i++) 1375 free((void *)list[i]); 1376 1377found: 1378 free(copy); 1379 free(list); 1380 return (out); 1381} 1382