status.c revision 1.199
1/* $OpenBSD: status.c,v 1.199 2019/05/23 12:47:52 nicm Exp $ */ 2 3/* 4 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19#include <sys/types.h> 20#include <sys/time.h> 21 22#include <errno.h> 23#include <limits.h> 24#include <stdarg.h> 25#include <stdlib.h> 26#include <string.h> 27#include <time.h> 28#include <unistd.h> 29 30#include "tmux.h" 31 32static void status_message_callback(int, short, void *); 33static void status_timer_callback(int, short, void *); 34 35static char *status_prompt_find_history_file(void); 36static const char *status_prompt_up_history(u_int *); 37static const char *status_prompt_down_history(u_int *); 38static void status_prompt_add_history(const char *); 39 40static char **status_prompt_complete_list(u_int *, const char *); 41static char *status_prompt_complete_prefix(char **, u_int); 42static char *status_prompt_complete(struct session *, const char *); 43 44/* Status prompt history. */ 45#define PROMPT_HISTORY 100 46static char **status_prompt_hlist; 47static u_int status_prompt_hsize; 48 49/* Find the history file to load/save from/to. */ 50static char * 51status_prompt_find_history_file(void) 52{ 53 const char *home, *history_file; 54 char *path; 55 56 history_file = options_get_string(global_options, "history-file"); 57 if (*history_file == '\0') 58 return (NULL); 59 if (*history_file == '/') 60 return (xstrdup(history_file)); 61 62 if (history_file[0] != '~' || history_file[1] != '/') 63 return (NULL); 64 if ((home = find_home()) == NULL) 65 return (NULL); 66 xasprintf(&path, "%s%s", home, history_file + 1); 67 return (path); 68} 69 70/* Load status prompt history from file. */ 71void 72status_prompt_load_history(void) 73{ 74 FILE *f; 75 char *history_file, *line, *tmp; 76 size_t length; 77 78 if ((history_file = status_prompt_find_history_file()) == NULL) 79 return; 80 log_debug("loading history from %s", history_file); 81 82 f = fopen(history_file, "r"); 83 if (f == NULL) { 84 log_debug("%s: %s", history_file, strerror(errno)); 85 free(history_file); 86 return; 87 } 88 free(history_file); 89 90 for (;;) { 91 if ((line = fgetln(f, &length)) == NULL) 92 break; 93 94 if (length > 0) { 95 if (line[length - 1] == '\n') { 96 line[length - 1] = '\0'; 97 status_prompt_add_history(line); 98 } else { 99 tmp = xmalloc(length + 1); 100 memcpy(tmp, line, length); 101 tmp[length] = '\0'; 102 status_prompt_add_history(tmp); 103 free(tmp); 104 } 105 } 106 } 107 fclose(f); 108} 109 110/* Save status prompt history to file. */ 111void 112status_prompt_save_history(void) 113{ 114 FILE *f; 115 u_int i; 116 char *history_file; 117 118 if ((history_file = status_prompt_find_history_file()) == NULL) 119 return; 120 log_debug("saving history to %s", history_file); 121 122 f = fopen(history_file, "w"); 123 if (f == NULL) { 124 log_debug("%s: %s", history_file, strerror(errno)); 125 free(history_file); 126 return; 127 } 128 free(history_file); 129 130 for (i = 0; i < status_prompt_hsize; i++) { 131 fputs(status_prompt_hlist[i], f); 132 fputc('\n', f); 133 } 134 fclose(f); 135 136} 137 138/* Status timer callback. */ 139static void 140status_timer_callback(__unused int fd, __unused short events, void *arg) 141{ 142 struct client *c = arg; 143 struct session *s = c->session; 144 struct timeval tv; 145 146 evtimer_del(&c->status.timer); 147 148 if (s == NULL) 149 return; 150 151 if (c->message_string == NULL && c->prompt_string == NULL) 152 c->flags |= CLIENT_REDRAWSTATUS; 153 154 timerclear(&tv); 155 tv.tv_sec = options_get_number(s->options, "status-interval"); 156 157 if (tv.tv_sec != 0) 158 evtimer_add(&c->status.timer, &tv); 159 log_debug("client %p, status interval %d", c, (int)tv.tv_sec); 160} 161 162/* Start status timer for client. */ 163void 164status_timer_start(struct client *c) 165{ 166 struct session *s = c->session; 167 168 if (event_initialized(&c->status.timer)) 169 evtimer_del(&c->status.timer); 170 else 171 evtimer_set(&c->status.timer, status_timer_callback, c); 172 173 if (s != NULL && options_get_number(s->options, "status")) 174 status_timer_callback(-1, 0, c); 175} 176 177/* Start status timer for all clients. */ 178void 179status_timer_start_all(void) 180{ 181 struct client *c; 182 183 TAILQ_FOREACH(c, &clients, entry) 184 status_timer_start(c); 185} 186 187/* Update status cache. */ 188void 189status_update_cache(struct session *s) 190{ 191 s->statuslines = options_get_number(s->options, "status"); 192 if (s->statuslines == 0) 193 s->statusat = -1; 194 else if (options_get_number(s->options, "status-position") == 0) 195 s->statusat = 0; 196 else 197 s->statusat = 1; 198} 199 200/* Get screen line of status line. -1 means off. */ 201int 202status_at_line(struct client *c) 203{ 204 struct session *s = c->session; 205 206 if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL)) 207 return (-1); 208 if (s->statusat != 1) 209 return (s->statusat); 210 return (c->tty.sy - status_line_size(c)); 211} 212 213/* Get size of status line for client's session. 0 means off. */ 214u_int 215status_line_size(struct client *c) 216{ 217 struct session *s = c->session; 218 219 if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL)) 220 return (0); 221 return (s->statuslines); 222} 223 224/* Get window at window list position. */ 225struct style_range * 226status_get_range(struct client *c, u_int x, u_int y) 227{ 228 struct status_line *sl = &c->status; 229 struct style_range *sr; 230 231 if (y >= nitems(sl->entries)) 232 return (NULL); 233 TAILQ_FOREACH(sr, &sl->entries[y].ranges, entry) { 234 if (x >= sr->start && x < sr->end) 235 return (sr); 236 } 237 return (NULL); 238} 239 240/* Free all ranges. */ 241static void 242status_free_ranges(struct style_ranges *srs) 243{ 244 struct style_range *sr, *sr1; 245 246 TAILQ_FOREACH_SAFE(sr, srs, entry, sr1) { 247 TAILQ_REMOVE(srs, sr, entry); 248 free(sr); 249 } 250} 251 252/* Save old status line. */ 253static void 254status_push_screen(struct client *c) 255{ 256 struct status_line *sl = &c->status; 257 258 if (sl->active == &sl->screen) { 259 sl->active = xmalloc(sizeof *sl->active); 260 screen_init(sl->active, c->tty.sx, status_line_size(c), 0); 261 } 262 sl->references++; 263} 264 265/* Restore old status line. */ 266static void 267status_pop_screen(struct client *c) 268{ 269 struct status_line *sl = &c->status; 270 271 if (--sl->references == 0) { 272 screen_free(sl->active); 273 free(sl->active); 274 sl->active = &sl->screen; 275 } 276} 277 278/* Initialize status line. */ 279void 280status_init(struct client *c) 281{ 282 struct status_line *sl = &c->status; 283 u_int i; 284 285 for (i = 0; i < nitems(sl->entries); i++) 286 TAILQ_INIT(&sl->entries[i].ranges); 287 288 screen_init(&sl->screen, c->tty.sx, 1, 0); 289 sl->active = &sl->screen; 290} 291 292/* Free status line. */ 293void 294status_free(struct client *c) 295{ 296 struct status_line *sl = &c->status; 297 u_int i; 298 299 for (i = 0; i < nitems(sl->entries); i++) { 300 status_free_ranges(&sl->entries[i].ranges); 301 free((void *)sl->entries[i].expanded); 302 } 303 304 if (event_initialized(&sl->timer)) 305 evtimer_del(&sl->timer); 306 307 if (sl->active != &sl->screen) { 308 screen_free(sl->active); 309 free(sl->active); 310 } 311 screen_free(&sl->screen); 312} 313 314/* Draw status line for client. */ 315int 316status_redraw(struct client *c) 317{ 318 struct status_line *sl = &c->status; 319 struct status_line_entry *sle; 320 struct session *s = c->session; 321 struct screen_write_ctx ctx; 322 struct grid_cell gc; 323 u_int lines, i, n, width = c->tty.sx; 324 int flags, force = 0, changed = 0; 325 struct options_entry *o; 326 union options_value *ov; 327 struct format_tree *ft; 328 char *expanded; 329 330 log_debug("%s enter", __func__); 331 332 /* Shouldn't get here if not the active screen. */ 333 if (sl->active != &sl->screen) 334 fatalx("not the active screen"); 335 336 /* No status line? */ 337 lines = status_line_size(c); 338 if (c->tty.sy == 0 || lines == 0) 339 return (1); 340 341 /* Set up default colour. */ 342 style_apply(&gc, s->options, "status-style"); 343 if (!grid_cells_equal(&gc, &sl->style)) { 344 force = 1; 345 memcpy(&sl->style, &gc, sizeof sl->style); 346 } 347 348 /* Resize the target screen. */ 349 if (screen_size_x(&sl->screen) != width || 350 screen_size_y(&sl->screen) != lines) { 351 if (screen_size_x(&sl->screen) != width) 352 force = 1; 353 screen_resize(&sl->screen, width, lines, 0); 354 changed = 1; 355 } 356 screen_write_start(&ctx, NULL, &sl->screen); 357 358 /* Create format tree. */ 359 flags = FORMAT_STATUS; 360 if (c->flags & CLIENT_STATUSFORCE) 361 flags |= FORMAT_FORCE; 362 ft = format_create(c, NULL, FORMAT_NONE, flags); 363 format_defaults(ft, c, NULL, NULL, NULL); 364 365 /* Write the status lines. */ 366 o = options_get(s->options, "status-format"); 367 if (o == NULL) { 368 for (n = 0; n < width * lines; n++) 369 screen_write_putc(&ctx, &gc, ' '); 370 } else { 371 for (i = 0; i < lines; i++) { 372 screen_write_cursormove(&ctx, 0, i, 0); 373 374 ov = options_array_get(o, i); 375 if (ov == NULL) { 376 for (n = 0; n < width; n++) 377 screen_write_putc(&ctx, &gc, ' '); 378 continue; 379 } 380 sle = &sl->entries[i]; 381 382 expanded = format_expand_time(ft, ov->string); 383 if (!force && 384 sle->expanded != NULL && 385 strcmp(expanded, sle->expanded) == 0) { 386 free(expanded); 387 continue; 388 } 389 changed = 1; 390 391 for (n = 0; n < width; n++) 392 screen_write_putc(&ctx, &gc, ' '); 393 screen_write_cursormove(&ctx, 0, i, 0); 394 395 status_free_ranges(&sle->ranges); 396 format_draw(&ctx, &gc, width, expanded, &sle->ranges); 397 398 free(sle->expanded); 399 sle->expanded = expanded; 400 } 401 } 402 screen_write_stop(&ctx); 403 404 /* Free the format tree. */ 405 format_free(ft); 406 407 /* Return if the status line has changed. */ 408 log_debug("%s exit: force=%d, changed=%d", __func__, force, changed); 409 return (force || changed); 410} 411 412/* Set a status line message. */ 413void 414status_message_set(struct client *c, const char *fmt, ...) 415{ 416 struct timeval tv; 417 va_list ap; 418 int delay; 419 420 status_message_clear(c); 421 status_push_screen(c); 422 423 va_start(ap, fmt); 424 xvasprintf(&c->message_string, fmt, ap); 425 va_end(ap); 426 427 server_client_add_message(c, "%s", c->message_string); 428 429 delay = options_get_number(c->session->options, "display-time"); 430 if (delay > 0) { 431 tv.tv_sec = delay / 1000; 432 tv.tv_usec = (delay % 1000) * 1000L; 433 434 if (event_initialized(&c->message_timer)) 435 evtimer_del(&c->message_timer); 436 evtimer_set(&c->message_timer, status_message_callback, c); 437 evtimer_add(&c->message_timer, &tv); 438 } 439 440 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); 441 c->flags |= CLIENT_REDRAWSTATUS; 442} 443 444/* Clear status line message. */ 445void 446status_message_clear(struct client *c) 447{ 448 if (c->message_string == NULL) 449 return; 450 451 free(c->message_string); 452 c->message_string = NULL; 453 454 if (c->prompt_string == NULL) 455 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 456 c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */ 457 458 status_pop_screen(c); 459} 460 461/* Clear status line message after timer expires. */ 462static void 463status_message_callback(__unused int fd, __unused short event, void *data) 464{ 465 struct client *c = data; 466 467 status_message_clear(c); 468} 469 470/* Draw client message on status line of present else on last line. */ 471int 472status_message_redraw(struct client *c) 473{ 474 struct status_line *sl = &c->status; 475 struct screen_write_ctx ctx; 476 struct session *s = c->session; 477 struct screen old_screen; 478 size_t len; 479 u_int lines, offset; 480 struct grid_cell gc; 481 482 if (c->tty.sx == 0 || c->tty.sy == 0) 483 return (0); 484 memcpy(&old_screen, sl->active, sizeof old_screen); 485 486 lines = status_line_size(c); 487 if (lines <= 1) 488 lines = 1; 489 screen_init(sl->active, c->tty.sx, lines, 0); 490 491 len = screen_write_strlen("%s", c->message_string); 492 if (len > c->tty.sx) 493 len = c->tty.sx; 494 495 style_apply(&gc, s->options, "message-style"); 496 497 screen_write_start(&ctx, NULL, sl->active); 498 screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1); 499 screen_write_cursormove(&ctx, 0, lines - 1, 0); 500 for (offset = 0; offset < c->tty.sx; offset++) 501 screen_write_putc(&ctx, &gc, ' '); 502 screen_write_cursormove(&ctx, 0, lines - 1, 0); 503 screen_write_nputs(&ctx, len, &gc, "%s", c->message_string); 504 screen_write_stop(&ctx); 505 506 if (grid_compare(sl->active->grid, old_screen.grid) == 0) { 507 screen_free(&old_screen); 508 return (0); 509 } 510 screen_free(&old_screen); 511 return (1); 512} 513 514/* Enable status line prompt. */ 515void 516status_prompt_set(struct client *c, const char *msg, const char *input, 517 prompt_input_cb inputcb, prompt_free_cb freecb, void *data, int flags) 518{ 519 struct format_tree *ft; 520 char *tmp, *cp; 521 522 ft = format_create(c, NULL, FORMAT_NONE, 0); 523 format_defaults(ft, c, NULL, NULL, NULL); 524 525 if (input == NULL) 526 input = ""; 527 if (flags & PROMPT_NOFORMAT) 528 tmp = xstrdup(input); 529 else 530 tmp = format_expand_time(ft, input); 531 532 status_message_clear(c); 533 status_prompt_clear(c); 534 status_push_screen(c); 535 536 c->prompt_string = format_expand_time(ft, msg); 537 538 c->prompt_buffer = utf8_fromcstr(tmp); 539 c->prompt_index = utf8_strlen(c->prompt_buffer); 540 541 c->prompt_inputcb = inputcb; 542 c->prompt_freecb = freecb; 543 c->prompt_data = data; 544 545 c->prompt_hindex = 0; 546 547 c->prompt_flags = flags; 548 c->prompt_mode = PROMPT_ENTRY; 549 550 if (~flags & PROMPT_INCREMENTAL) 551 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); 552 c->flags |= CLIENT_REDRAWSTATUS; 553 554 if ((flags & PROMPT_INCREMENTAL) && *tmp != '\0') { 555 xasprintf(&cp, "=%s", tmp); 556 c->prompt_inputcb(c, c->prompt_data, cp, 0); 557 free(cp); 558 } 559 560 free(tmp); 561 format_free(ft); 562} 563 564/* Remove status line prompt. */ 565void 566status_prompt_clear(struct client *c) 567{ 568 if (c->prompt_string == NULL) 569 return; 570 571 if (c->prompt_freecb != NULL && c->prompt_data != NULL) 572 c->prompt_freecb(c->prompt_data); 573 574 free(c->prompt_string); 575 c->prompt_string = NULL; 576 577 free(c->prompt_buffer); 578 c->prompt_buffer = NULL; 579 580 free(c->prompt_saved); 581 c->prompt_saved = NULL; 582 583 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 584 c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */ 585 586 status_pop_screen(c); 587} 588 589/* Update status line prompt with a new prompt string. */ 590void 591status_prompt_update(struct client *c, const char *msg, const char *input) 592{ 593 struct format_tree *ft; 594 char *tmp; 595 596 ft = format_create(c, NULL, FORMAT_NONE, 0); 597 format_defaults(ft, c, NULL, NULL, NULL); 598 599 tmp = format_expand_time(ft, input); 600 601 free(c->prompt_string); 602 c->prompt_string = format_expand_time(ft, msg); 603 604 free(c->prompt_buffer); 605 c->prompt_buffer = utf8_fromcstr(tmp); 606 c->prompt_index = utf8_strlen(c->prompt_buffer); 607 608 c->prompt_hindex = 0; 609 610 c->flags |= CLIENT_REDRAWSTATUS; 611 612 free(tmp); 613 format_free(ft); 614} 615 616/* Draw client prompt on status line of present else on last line. */ 617int 618status_prompt_redraw(struct client *c) 619{ 620 struct status_line *sl = &c->status; 621 struct screen_write_ctx ctx; 622 struct session *s = c->session; 623 struct screen old_screen; 624 u_int i, lines, offset, left, start, width; 625 u_int pcursor, pwidth; 626 struct grid_cell gc, cursorgc; 627 628 if (c->tty.sx == 0 || c->tty.sy == 0) 629 return (0); 630 memcpy(&old_screen, sl->active, sizeof old_screen); 631 632 lines = status_line_size(c); 633 if (lines <= 1) 634 lines = 1; 635 screen_init(sl->active, c->tty.sx, lines, 0); 636 637 if (c->prompt_mode == PROMPT_COMMAND) 638 style_apply(&gc, s->options, "message-command-style"); 639 else 640 style_apply(&gc, s->options, "message-style"); 641 642 memcpy(&cursorgc, &gc, sizeof cursorgc); 643 cursorgc.attr ^= GRID_ATTR_REVERSE; 644 645 start = screen_write_strlen("%s", c->prompt_string); 646 if (start > c->tty.sx) 647 start = c->tty.sx; 648 649 screen_write_start(&ctx, NULL, sl->active); 650 screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1); 651 screen_write_cursormove(&ctx, 0, lines - 1, 0); 652 for (offset = 0; offset < c->tty.sx; offset++) 653 screen_write_putc(&ctx, &gc, ' '); 654 screen_write_cursormove(&ctx, 0, lines - 1, 0); 655 screen_write_nputs(&ctx, start, &gc, "%s", c->prompt_string); 656 screen_write_cursormove(&ctx, start, lines - 1, 0); 657 658 left = c->tty.sx - start; 659 if (left == 0) 660 goto finished; 661 662 pcursor = utf8_strwidth(c->prompt_buffer, c->prompt_index); 663 pwidth = utf8_strwidth(c->prompt_buffer, -1); 664 if (pcursor >= left) { 665 /* 666 * The cursor would be outside the screen so start drawing 667 * with it on the right. 668 */ 669 offset = (pcursor - left) + 1; 670 pwidth = left; 671 } else 672 offset = 0; 673 if (pwidth > left) 674 pwidth = left; 675 676 width = 0; 677 for (i = 0; c->prompt_buffer[i].size != 0; i++) { 678 if (width < offset) { 679 width += c->prompt_buffer[i].width; 680 continue; 681 } 682 if (width >= offset + pwidth) 683 break; 684 width += c->prompt_buffer[i].width; 685 if (width > offset + pwidth) 686 break; 687 688 if (i != c->prompt_index) { 689 utf8_copy(&gc.data, &c->prompt_buffer[i]); 690 screen_write_cell(&ctx, &gc); 691 } else { 692 utf8_copy(&cursorgc.data, &c->prompt_buffer[i]); 693 screen_write_cell(&ctx, &cursorgc); 694 } 695 } 696 if (sl->active->cx < screen_size_x(sl->active) && c->prompt_index >= i) 697 screen_write_putc(&ctx, &cursorgc, ' '); 698 699finished: 700 screen_write_stop(&ctx); 701 702 if (grid_compare(sl->active->grid, old_screen.grid) == 0) { 703 screen_free(&old_screen); 704 return (0); 705 } 706 screen_free(&old_screen); 707 return (1); 708} 709 710/* Is this a separator? */ 711static int 712status_prompt_in_list(const char *ws, const struct utf8_data *ud) 713{ 714 if (ud->size != 1 || ud->width != 1) 715 return (0); 716 return (strchr(ws, *ud->data) != NULL); 717} 718 719/* Is this a space? */ 720static int 721status_prompt_space(const struct utf8_data *ud) 722{ 723 if (ud->size != 1 || ud->width != 1) 724 return (0); 725 return (*ud->data == ' '); 726} 727 728/* 729 * Translate key from emacs to vi. Return 0 to drop key, 1 to process the key 730 * as an emacs key; return 2 to append to the buffer. 731 */ 732static int 733status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) 734{ 735 if (c->prompt_mode == PROMPT_ENTRY) { 736 switch (key) { 737 case '\003': /* C-c */ 738 case '\010': /* C-h */ 739 case '\011': /* Tab */ 740 case '\025': /* C-u */ 741 case '\027': /* C-w */ 742 case '\n': 743 case '\r': 744 case KEYC_BSPACE: 745 case KEYC_DC: 746 case KEYC_DOWN: 747 case KEYC_END: 748 case KEYC_HOME: 749 case KEYC_LEFT: 750 case KEYC_RIGHT: 751 case KEYC_UP: 752 *new_key = key; 753 return (1); 754 case '\033': /* Escape */ 755 c->prompt_mode = PROMPT_COMMAND; 756 c->flags |= CLIENT_REDRAWSTATUS; 757 return (0); 758 } 759 *new_key = key; 760 return (2); 761 } 762 763 switch (key) { 764 case 'A': 765 case 'I': 766 case 'C': 767 case 's': 768 case 'a': 769 c->prompt_mode = PROMPT_ENTRY; 770 c->flags |= CLIENT_REDRAWSTATUS; 771 break; /* switch mode and... */ 772 case 'S': 773 c->prompt_mode = PROMPT_ENTRY; 774 c->flags |= CLIENT_REDRAWSTATUS; 775 *new_key = '\025'; /* C-u */ 776 return (1); 777 case 'i': 778 case '\033': /* Escape */ 779 c->prompt_mode = PROMPT_ENTRY; 780 c->flags |= CLIENT_REDRAWSTATUS; 781 return (0); 782 } 783 784 switch (key) { 785 case 'A': 786 case '$': 787 *new_key = KEYC_END; 788 return (1); 789 case 'I': 790 case '0': 791 case '^': 792 *new_key = KEYC_HOME; 793 return (1); 794 case 'C': 795 case 'D': 796 *new_key = '\013'; /* C-k */ 797 return (1); 798 case KEYC_BSPACE: 799 case 'X': 800 *new_key = KEYC_BSPACE; 801 return (1); 802 case 'b': 803 case 'B': 804 *new_key = 'b'|KEYC_ESCAPE; 805 return (1); 806 case 'd': 807 *new_key = '\025'; 808 return (1); 809 case 'e': 810 case 'E': 811 case 'w': 812 case 'W': 813 *new_key = 'f'|KEYC_ESCAPE; 814 return (1); 815 case 'p': 816 *new_key = '\031'; /* C-y */ 817 return (1); 818 case 's': 819 case KEYC_DC: 820 case 'x': 821 *new_key = KEYC_DC; 822 return (1); 823 case KEYC_DOWN: 824 case 'j': 825 *new_key = KEYC_DOWN; 826 return (1); 827 case KEYC_LEFT: 828 case 'h': 829 *new_key = KEYC_LEFT; 830 return (1); 831 case 'a': 832 case KEYC_RIGHT: 833 case 'l': 834 *new_key = KEYC_RIGHT; 835 return (1); 836 case KEYC_UP: 837 case 'k': 838 *new_key = KEYC_UP; 839 return (1); 840 case '\010' /* C-h */: 841 case '\003' /* C-c */: 842 case '\n': 843 case '\r': 844 return (1); 845 } 846 return (0); 847} 848 849/* Paste into prompt. */ 850static int 851status_prompt_paste(struct client *c) 852{ 853 struct paste_buffer *pb; 854 const char *bufdata; 855 size_t size, n, bufsize; 856 u_int i; 857 struct utf8_data *ud, *udp; 858 enum utf8_state more; 859 860 size = utf8_strlen(c->prompt_buffer); 861 if (c->prompt_saved != NULL) { 862 ud = c->prompt_saved; 863 n = utf8_strlen(c->prompt_saved); 864 } else { 865 if ((pb = paste_get_top(NULL)) == NULL) 866 return (0); 867 bufdata = paste_buffer_data(pb, &bufsize); 868 ud = xreallocarray(NULL, bufsize, sizeof *ud); 869 udp = ud; 870 for (i = 0; i != bufsize; /* nothing */) { 871 more = utf8_open(udp, bufdata[i]); 872 if (more == UTF8_MORE) { 873 while (++i != bufsize && more == UTF8_MORE) 874 more = utf8_append(udp, bufdata[i]); 875 if (more == UTF8_DONE) { 876 udp++; 877 continue; 878 } 879 i -= udp->have; 880 } 881 if (bufdata[i] <= 31 || bufdata[i] >= 127) 882 break; 883 utf8_set(udp, bufdata[i]); 884 udp++; 885 i++; 886 } 887 udp->size = 0; 888 n = udp - ud; 889 } 890 if (n == 0) 891 return (0); 892 893 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + n + 1, 894 sizeof *c->prompt_buffer); 895 if (c->prompt_index == size) { 896 memcpy(c->prompt_buffer + c->prompt_index, ud, 897 n * sizeof *c->prompt_buffer); 898 c->prompt_index += n; 899 c->prompt_buffer[c->prompt_index].size = 0; 900 } else { 901 memmove(c->prompt_buffer + c->prompt_index + n, 902 c->prompt_buffer + c->prompt_index, 903 (size + 1 - c->prompt_index) * sizeof *c->prompt_buffer); 904 memcpy(c->prompt_buffer + c->prompt_index, ud, 905 n * sizeof *c->prompt_buffer); 906 c->prompt_index += n; 907 } 908 909 if (ud != c->prompt_saved) 910 free(ud); 911 return (1); 912} 913 914/* Handle keys in prompt. */ 915int 916status_prompt_key(struct client *c, key_code key) 917{ 918 struct options *oo = c->session->options; 919 char *s, *cp, word[64], prefix = '='; 920 const char *histstr, *ws = NULL; 921 size_t size, n, off, idx, used; 922 struct utf8_data tmp, *first, *last, *ud; 923 int keys; 924 925 size = utf8_strlen(c->prompt_buffer); 926 927 if (c->prompt_flags & PROMPT_NUMERIC) { 928 if (key >= '0' && key <= '9') 929 goto append_key; 930 s = utf8_tocstr(c->prompt_buffer); 931 c->prompt_inputcb(c, c->prompt_data, s, 1); 932 status_prompt_clear(c); 933 free(s); 934 return (1); 935 } 936 key &= ~KEYC_XTERM; 937 938 keys = options_get_number(c->session->options, "status-keys"); 939 if (keys == MODEKEY_VI) { 940 switch (status_prompt_translate_key(c, key, &key)) { 941 case 1: 942 goto process_key; 943 case 2: 944 goto append_key; 945 default: 946 return (0); 947 } 948 } 949 950process_key: 951 switch (key) { 952 case KEYC_LEFT: 953 case '\002': /* C-b */ 954 if (c->prompt_index > 0) { 955 c->prompt_index--; 956 break; 957 } 958 break; 959 case KEYC_RIGHT: 960 case '\006': /* C-f */ 961 if (c->prompt_index < size) { 962 c->prompt_index++; 963 break; 964 } 965 break; 966 case KEYC_HOME: 967 case '\001': /* C-a */ 968 if (c->prompt_index != 0) { 969 c->prompt_index = 0; 970 break; 971 } 972 break; 973 case KEYC_END: 974 case '\005': /* C-e */ 975 if (c->prompt_index != size) { 976 c->prompt_index = size; 977 break; 978 } 979 break; 980 case '\011': /* Tab */ 981 if (c->prompt_buffer[0].size == 0) 982 break; 983 984 idx = c->prompt_index; 985 if (idx != 0) 986 idx--; 987 988 /* Find the word we are in. */ 989 first = &c->prompt_buffer[idx]; 990 while (first > c->prompt_buffer && !status_prompt_space(first)) 991 first--; 992 while (first->size != 0 && status_prompt_space(first)) 993 first++; 994 last = &c->prompt_buffer[idx]; 995 while (last->size != 0 && !status_prompt_space(last)) 996 last++; 997 while (last > c->prompt_buffer && status_prompt_space(last)) 998 last--; 999 if (last->size != 0) 1000 last++; 1001 if (last <= first) 1002 break; 1003 1004 used = 0; 1005 for (ud = first; ud < last; ud++) { 1006 if (used + ud->size >= sizeof word) 1007 break; 1008 memcpy(word + used, ud->data, ud->size); 1009 used += ud->size; 1010 } 1011 if (ud != last) 1012 break; 1013 word[used] = '\0'; 1014 1015 /* And try to complete it. */ 1016 if ((s = status_prompt_complete(c->session, word)) == NULL) 1017 break; 1018 1019 /* Trim out word. */ 1020 n = size - (last - c->prompt_buffer) + 1; /* with \0 */ 1021 memmove(first, last, n * sizeof *c->prompt_buffer); 1022 size -= last - first; 1023 1024 /* Insert the new word. */ 1025 size += strlen(s); 1026 off = first - c->prompt_buffer; 1027 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 1, 1028 sizeof *c->prompt_buffer); 1029 first = c->prompt_buffer + off; 1030 memmove(first + strlen(s), first, n * sizeof *c->prompt_buffer); 1031 for (idx = 0; idx < strlen(s); idx++) 1032 utf8_set(&first[idx], s[idx]); 1033 1034 c->prompt_index = (first - c->prompt_buffer) + strlen(s); 1035 free(s); 1036 1037 goto changed; 1038 case KEYC_BSPACE: 1039 case '\010': /* C-h */ 1040 if (c->prompt_index != 0) { 1041 if (c->prompt_index == size) 1042 c->prompt_buffer[--c->prompt_index].size = 0; 1043 else { 1044 memmove(c->prompt_buffer + c->prompt_index - 1, 1045 c->prompt_buffer + c->prompt_index, 1046 (size + 1 - c->prompt_index) * 1047 sizeof *c->prompt_buffer); 1048 c->prompt_index--; 1049 } 1050 goto changed; 1051 } 1052 break; 1053 case KEYC_DC: 1054 case '\004': /* C-d */ 1055 if (c->prompt_index != size) { 1056 memmove(c->prompt_buffer + c->prompt_index, 1057 c->prompt_buffer + c->prompt_index + 1, 1058 (size + 1 - c->prompt_index) * 1059 sizeof *c->prompt_buffer); 1060 goto changed; 1061 } 1062 break; 1063 case '\025': /* C-u */ 1064 c->prompt_buffer[0].size = 0; 1065 c->prompt_index = 0; 1066 goto changed; 1067 case '\013': /* C-k */ 1068 if (c->prompt_index < size) { 1069 c->prompt_buffer[c->prompt_index].size = 0; 1070 goto changed; 1071 } 1072 break; 1073 case '\027': /* C-w */ 1074 ws = options_get_string(oo, "word-separators"); 1075 idx = c->prompt_index; 1076 1077 /* Find a non-separator. */ 1078 while (idx != 0) { 1079 idx--; 1080 if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1081 break; 1082 } 1083 1084 /* Find the separator at the beginning of the word. */ 1085 while (idx != 0) { 1086 idx--; 1087 if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) { 1088 /* Go back to the word. */ 1089 idx++; 1090 break; 1091 } 1092 } 1093 1094 free(c->prompt_saved); 1095 c->prompt_saved = xcalloc(sizeof *c->prompt_buffer, 1096 (c->prompt_index - idx) + 1); 1097 memcpy(c->prompt_saved, c->prompt_buffer + idx, 1098 (c->prompt_index - idx) * sizeof *c->prompt_buffer); 1099 1100 memmove(c->prompt_buffer + idx, 1101 c->prompt_buffer + c->prompt_index, 1102 (size + 1 - c->prompt_index) * 1103 sizeof *c->prompt_buffer); 1104 memset(c->prompt_buffer + size - (c->prompt_index - idx), 1105 '\0', (c->prompt_index - idx) * sizeof *c->prompt_buffer); 1106 c->prompt_index = idx; 1107 1108 goto changed; 1109 case 'f'|KEYC_ESCAPE: 1110 case KEYC_RIGHT|KEYC_CTRL: 1111 ws = options_get_string(oo, "word-separators"); 1112 1113 /* Find a word. */ 1114 while (c->prompt_index != size) { 1115 idx = ++c->prompt_index; 1116 if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1117 break; 1118 } 1119 1120 /* Find the separator at the end of the word. */ 1121 while (c->prompt_index != size) { 1122 idx = ++c->prompt_index; 1123 if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1124 break; 1125 } 1126 1127 /* Back up to the end-of-word like vi. */ 1128 if (options_get_number(oo, "status-keys") == MODEKEY_VI && 1129 c->prompt_index != 0) 1130 c->prompt_index--; 1131 1132 goto changed; 1133 case 'b'|KEYC_ESCAPE: 1134 case KEYC_LEFT|KEYC_CTRL: 1135 ws = options_get_string(oo, "word-separators"); 1136 1137 /* Find a non-separator. */ 1138 while (c->prompt_index != 0) { 1139 idx = --c->prompt_index; 1140 if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1141 break; 1142 } 1143 1144 /* Find the separator at the beginning of the word. */ 1145 while (c->prompt_index != 0) { 1146 idx = --c->prompt_index; 1147 if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) { 1148 /* Go back to the word. */ 1149 c->prompt_index++; 1150 break; 1151 } 1152 } 1153 goto changed; 1154 case KEYC_UP: 1155 case '\020': /* C-p */ 1156 histstr = status_prompt_up_history(&c->prompt_hindex); 1157 if (histstr == NULL) 1158 break; 1159 free(c->prompt_buffer); 1160 c->prompt_buffer = utf8_fromcstr(histstr); 1161 c->prompt_index = utf8_strlen(c->prompt_buffer); 1162 goto changed; 1163 case KEYC_DOWN: 1164 case '\016': /* C-n */ 1165 histstr = status_prompt_down_history(&c->prompt_hindex); 1166 if (histstr == NULL) 1167 break; 1168 free(c->prompt_buffer); 1169 c->prompt_buffer = utf8_fromcstr(histstr); 1170 c->prompt_index = utf8_strlen(c->prompt_buffer); 1171 goto changed; 1172 case '\031': /* C-y */ 1173 if (status_prompt_paste(c)) 1174 goto changed; 1175 break; 1176 case '\024': /* C-t */ 1177 idx = c->prompt_index; 1178 if (idx < size) 1179 idx++; 1180 if (idx >= 2) { 1181 utf8_copy(&tmp, &c->prompt_buffer[idx - 2]); 1182 utf8_copy(&c->prompt_buffer[idx - 2], 1183 &c->prompt_buffer[idx - 1]); 1184 utf8_copy(&c->prompt_buffer[idx - 1], &tmp); 1185 c->prompt_index = idx; 1186 goto changed; 1187 } 1188 break; 1189 case '\r': 1190 case '\n': 1191 s = utf8_tocstr(c->prompt_buffer); 1192 if (*s != '\0') 1193 status_prompt_add_history(s); 1194 if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) 1195 status_prompt_clear(c); 1196 free(s); 1197 break; 1198 case '\033': /* Escape */ 1199 case '\003': /* C-c */ 1200 case '\007': /* C-g */ 1201 if (c->prompt_inputcb(c, c->prompt_data, NULL, 1) == 0) 1202 status_prompt_clear(c); 1203 break; 1204 case '\022': /* C-r */ 1205 if (c->prompt_flags & PROMPT_INCREMENTAL) { 1206 prefix = '-'; 1207 goto changed; 1208 } 1209 break; 1210 case '\023': /* C-s */ 1211 if (c->prompt_flags & PROMPT_INCREMENTAL) { 1212 prefix = '+'; 1213 goto changed; 1214 } 1215 break; 1216 default: 1217 goto append_key; 1218 } 1219 1220 c->flags |= CLIENT_REDRAWSTATUS; 1221 return (0); 1222 1223append_key: 1224 if (key <= 0x1f || key >= KEYC_BASE) 1225 return (0); 1226 if (utf8_split(key, &tmp) != UTF8_DONE) 1227 return (0); 1228 1229 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 2, 1230 sizeof *c->prompt_buffer); 1231 1232 if (c->prompt_index == size) { 1233 utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp); 1234 c->prompt_index++; 1235 c->prompt_buffer[c->prompt_index].size = 0; 1236 } else { 1237 memmove(c->prompt_buffer + c->prompt_index + 1, 1238 c->prompt_buffer + c->prompt_index, 1239 (size + 1 - c->prompt_index) * 1240 sizeof *c->prompt_buffer); 1241 utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp); 1242 c->prompt_index++; 1243 } 1244 1245 if (c->prompt_flags & PROMPT_SINGLE) { 1246 s = utf8_tocstr(c->prompt_buffer); 1247 if (strlen(s) != 1) 1248 status_prompt_clear(c); 1249 else if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) 1250 status_prompt_clear(c); 1251 free(s); 1252 } 1253 1254changed: 1255 c->flags |= CLIENT_REDRAWSTATUS; 1256 if (c->prompt_flags & PROMPT_INCREMENTAL) { 1257 s = utf8_tocstr(c->prompt_buffer); 1258 xasprintf(&cp, "%c%s", prefix, s); 1259 c->prompt_inputcb(c, c->prompt_data, cp, 0); 1260 free(cp); 1261 free(s); 1262 } 1263 return (0); 1264} 1265 1266/* Get previous line from the history. */ 1267static const char * 1268status_prompt_up_history(u_int *idx) 1269{ 1270 /* 1271 * History runs from 0 to size - 1. Index is from 0 to size. Zero is 1272 * empty. 1273 */ 1274 1275 if (status_prompt_hsize == 0 || *idx == status_prompt_hsize) 1276 return (NULL); 1277 (*idx)++; 1278 return (status_prompt_hlist[status_prompt_hsize - *idx]); 1279} 1280 1281/* Get next line from the history. */ 1282static const char * 1283status_prompt_down_history(u_int *idx) 1284{ 1285 if (status_prompt_hsize == 0 || *idx == 0) 1286 return (""); 1287 (*idx)--; 1288 if (*idx == 0) 1289 return (""); 1290 return (status_prompt_hlist[status_prompt_hsize - *idx]); 1291} 1292 1293/* Add line to the history. */ 1294static void 1295status_prompt_add_history(const char *line) 1296{ 1297 size_t size; 1298 1299 if (status_prompt_hsize > 0 && 1300 strcmp(status_prompt_hlist[status_prompt_hsize - 1], line) == 0) 1301 return; 1302 1303 if (status_prompt_hsize == PROMPT_HISTORY) { 1304 free(status_prompt_hlist[0]); 1305 1306 size = (PROMPT_HISTORY - 1) * sizeof *status_prompt_hlist; 1307 memmove(&status_prompt_hlist[0], &status_prompt_hlist[1], size); 1308 1309 status_prompt_hlist[status_prompt_hsize - 1] = xstrdup(line); 1310 return; 1311 } 1312 1313 status_prompt_hlist = xreallocarray(status_prompt_hlist, 1314 status_prompt_hsize + 1, sizeof *status_prompt_hlist); 1315 status_prompt_hlist[status_prompt_hsize++] = xstrdup(line); 1316} 1317 1318/* Build completion list. */ 1319char ** 1320status_prompt_complete_list(u_int *size, const char *s) 1321{ 1322 char **list = NULL; 1323 const char **layout, *value, *cp; 1324 const struct cmd_entry **cmdent; 1325 const struct options_table_entry *oe; 1326 u_int idx; 1327 size_t slen = strlen(s), valuelen; 1328 struct options_entry *o; 1329 struct options_array_item *a; 1330 const char *layouts[] = { 1331 "even-horizontal", "even-vertical", "main-horizontal", 1332 "main-vertical", "tiled", NULL 1333 }; 1334 1335 *size = 0; 1336 for (cmdent = cmd_table; *cmdent != NULL; cmdent++) { 1337 if (strncmp((*cmdent)->name, s, slen) == 0) { 1338 list = xreallocarray(list, (*size) + 1, sizeof *list); 1339 list[(*size)++] = xstrdup((*cmdent)->name); 1340 } 1341 } 1342 for (oe = options_table; oe->name != NULL; oe++) { 1343 if (strncmp(oe->name, s, slen) == 0) { 1344 list = xreallocarray(list, (*size) + 1, sizeof *list); 1345 list[(*size)++] = xstrdup(oe->name); 1346 } 1347 } 1348 for (layout = layouts; *layout != NULL; layout++) { 1349 if (strncmp(*layout, s, slen) == 0) { 1350 list = xreallocarray(list, (*size) + 1, sizeof *list); 1351 list[(*size)++] = xstrdup(*layout); 1352 } 1353 } 1354 o = options_get_only(global_options, "command-alias"); 1355 if (o != NULL) { 1356 a = options_array_first(o); 1357 while (a != NULL) { 1358 value = options_array_item_value(a)->string; 1359 if ((cp = strchr(value, '=')) == NULL) 1360 goto next; 1361 valuelen = cp - value; 1362 if (slen > valuelen || strncmp(value, s, slen) != 0) 1363 goto next; 1364 1365 list = xreallocarray(list, (*size) + 1, sizeof *list); 1366 list[(*size)++] = xstrndup(value, valuelen); 1367 1368 next: 1369 a = options_array_next(a); 1370 } 1371 } 1372 for (idx = 0; idx < (*size); idx++) 1373 log_debug("complete %u: %s", idx, list[idx]); 1374 return (list); 1375} 1376 1377/* Find longest prefix. */ 1378static char * 1379status_prompt_complete_prefix(char **list, u_int size) 1380{ 1381 char *out; 1382 u_int i; 1383 size_t j; 1384 1385 out = xstrdup(list[0]); 1386 for (i = 1; i < size; i++) { 1387 j = strlen(list[i]); 1388 if (j > strlen(out)) 1389 j = strlen(out); 1390 for (; j > 0; j--) { 1391 if (out[j - 1] != list[i][j - 1]) 1392 out[j - 1] = '\0'; 1393 } 1394 } 1395 return (out); 1396} 1397 1398/* Complete word. */ 1399static char * 1400status_prompt_complete(struct session *session, const char *s) 1401{ 1402 char **list = NULL; 1403 const char *colon; 1404 u_int size = 0, i; 1405 struct session *s_loop; 1406 struct winlink *wl; 1407 struct window *w; 1408 char *copy, *out, *tmp; 1409 1410 if (*s == '\0') 1411 return (NULL); 1412 out = NULL; 1413 1414 if (strncmp(s, "-t", 2) != 0 && strncmp(s, "-s", 2) != 0) { 1415 list = status_prompt_complete_list(&size, s); 1416 if (size == 0) 1417 out = NULL; 1418 else if (size == 1) 1419 xasprintf(&out, "%s ", list[0]); 1420 else 1421 out = status_prompt_complete_prefix(list, size); 1422 for (i = 0; i < size; i++) 1423 free(list[i]); 1424 free(list); 1425 return (out); 1426 } 1427 copy = xstrdup(s); 1428 1429 colon = ":"; 1430 if (copy[strlen(copy) - 1] == ':') 1431 copy[strlen(copy) - 1] = '\0'; 1432 else 1433 colon = ""; 1434 s = copy + 2; 1435 1436 RB_FOREACH(s_loop, sessions, &sessions) { 1437 if (strncmp(s_loop->name, s, strlen(s)) == 0) { 1438 list = xreallocarray(list, size + 2, sizeof *list); 1439 list[size++] = s_loop->name; 1440 } 1441 } 1442 if (size == 1) { 1443 out = xstrdup(list[0]); 1444 if (session_find(list[0]) != NULL) 1445 colon = ":"; 1446 } else if (size != 0) 1447 out = status_prompt_complete_prefix(list, size); 1448 if (out != NULL) { 1449 xasprintf(&tmp, "-%c%s%s", copy[1], out, colon); 1450 free(out); 1451 out = tmp; 1452 goto found; 1453 } 1454 1455 colon = ""; 1456 if (*s == ':') { 1457 RB_FOREACH(wl, winlinks, &session->windows) { 1458 xasprintf(&tmp, ":%s", wl->window->name); 1459 if (strncmp(tmp, s, strlen(s)) == 0){ 1460 list = xreallocarray(list, size + 1, 1461 sizeof *list); 1462 list[size++] = tmp; 1463 continue; 1464 } 1465 free(tmp); 1466 1467 xasprintf(&tmp, ":%d", wl->idx); 1468 if (strncmp(tmp, s, strlen(s)) == 0) { 1469 list = xreallocarray(list, size + 1, 1470 sizeof *list); 1471 list[size++] = tmp; 1472 continue; 1473 } 1474 free(tmp); 1475 } 1476 } else { 1477 RB_FOREACH(s_loop, sessions, &sessions) { 1478 RB_FOREACH(wl, winlinks, &s_loop->windows) { 1479 w = wl->window; 1480 1481 xasprintf(&tmp, "%s:%s", s_loop->name, w->name); 1482 if (strncmp(tmp, s, strlen(s)) == 0) { 1483 list = xreallocarray(list, size + 1, 1484 sizeof *list); 1485 list[size++] = tmp; 1486 continue; 1487 } 1488 free(tmp); 1489 1490 xasprintf(&tmp, "%s:%d", s_loop->name, wl->idx); 1491 if (strncmp(tmp, s, strlen(s)) == 0) { 1492 list = xreallocarray(list, size + 1, 1493 sizeof *list); 1494 list[size++] = tmp; 1495 continue; 1496 } 1497 free(tmp); 1498 } 1499 } 1500 } 1501 if (size == 1) { 1502 out = xstrdup(list[0]); 1503 colon = " "; 1504 } else if (size != 0) 1505 out = status_prompt_complete_prefix(list, size); 1506 if (out != NULL) { 1507 xasprintf(&tmp, "-%c%s%s", copy[1], out, colon); 1508 out = tmp; 1509 } 1510 1511 for (i = 0; i < size; i++) 1512 free((void *)list[i]); 1513 1514found: 1515 free(copy); 1516 free(list); 1517 return (out); 1518} 1519