status.c revision 1.201
1/* $OpenBSD: status.c,v 1.201 2020/01/27 08:53:13 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 screen_resize(&sl->screen, width, lines, 0); 352 changed = force = 1; 353 } 354 screen_write_start(&ctx, NULL, &sl->screen); 355 356 /* Create format tree. */ 357 flags = FORMAT_STATUS; 358 if (c->flags & CLIENT_STATUSFORCE) 359 flags |= FORMAT_FORCE; 360 ft = format_create(c, NULL, FORMAT_NONE, flags); 361 format_defaults(ft, c, NULL, NULL, NULL); 362 363 /* Write the status lines. */ 364 o = options_get(s->options, "status-format"); 365 if (o == NULL) { 366 for (n = 0; n < width * lines; n++) 367 screen_write_putc(&ctx, &gc, ' '); 368 } else { 369 for (i = 0; i < lines; i++) { 370 screen_write_cursormove(&ctx, 0, i, 0); 371 372 ov = options_array_get(o, i); 373 if (ov == NULL) { 374 for (n = 0; n < width; n++) 375 screen_write_putc(&ctx, &gc, ' '); 376 continue; 377 } 378 sle = &sl->entries[i]; 379 380 expanded = format_expand_time(ft, ov->string); 381 if (!force && 382 sle->expanded != NULL && 383 strcmp(expanded, sle->expanded) == 0) { 384 free(expanded); 385 continue; 386 } 387 changed = 1; 388 389 for (n = 0; n < width; n++) 390 screen_write_putc(&ctx, &gc, ' '); 391 screen_write_cursormove(&ctx, 0, i, 0); 392 393 status_free_ranges(&sle->ranges); 394 format_draw(&ctx, &gc, width, expanded, &sle->ranges); 395 396 free(sle->expanded); 397 sle->expanded = expanded; 398 } 399 } 400 screen_write_stop(&ctx); 401 402 /* Free the format tree. */ 403 format_free(ft); 404 405 /* Return if the status line has changed. */ 406 log_debug("%s exit: force=%d, changed=%d", __func__, force, changed); 407 return (force || changed); 408} 409 410/* Set a status line message. */ 411void 412status_message_set(struct client *c, const char *fmt, ...) 413{ 414 struct timeval tv; 415 va_list ap; 416 int delay; 417 418 status_message_clear(c); 419 status_push_screen(c); 420 421 va_start(ap, fmt); 422 xvasprintf(&c->message_string, fmt, ap); 423 va_end(ap); 424 425 server_client_add_message(c, "%s", c->message_string); 426 427 delay = options_get_number(c->session->options, "display-time"); 428 if (delay > 0) { 429 tv.tv_sec = delay / 1000; 430 tv.tv_usec = (delay % 1000) * 1000L; 431 432 if (event_initialized(&c->message_timer)) 433 evtimer_del(&c->message_timer); 434 evtimer_set(&c->message_timer, status_message_callback, c); 435 evtimer_add(&c->message_timer, &tv); 436 } 437 438 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); 439 c->flags |= CLIENT_REDRAWSTATUS; 440} 441 442/* Clear status line message. */ 443void 444status_message_clear(struct client *c) 445{ 446 if (c->message_string == NULL) 447 return; 448 449 free(c->message_string); 450 c->message_string = NULL; 451 452 if (c->prompt_string == NULL) 453 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 454 c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */ 455 456 status_pop_screen(c); 457} 458 459/* Clear status line message after timer expires. */ 460static void 461status_message_callback(__unused int fd, __unused short event, void *data) 462{ 463 struct client *c = data; 464 465 status_message_clear(c); 466} 467 468/* Draw client message on status line of present else on last line. */ 469int 470status_message_redraw(struct client *c) 471{ 472 struct status_line *sl = &c->status; 473 struct screen_write_ctx ctx; 474 struct session *s = c->session; 475 struct screen old_screen; 476 size_t len; 477 u_int lines, offset; 478 struct grid_cell gc; 479 480 if (c->tty.sx == 0 || c->tty.sy == 0) 481 return (0); 482 memcpy(&old_screen, sl->active, sizeof old_screen); 483 484 lines = status_line_size(c); 485 if (lines <= 1) 486 lines = 1; 487 screen_init(sl->active, c->tty.sx, lines, 0); 488 489 len = screen_write_strlen("%s", c->message_string); 490 if (len > c->tty.sx) 491 len = c->tty.sx; 492 493 style_apply(&gc, s->options, "message-style"); 494 495 screen_write_start(&ctx, NULL, sl->active); 496 screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1); 497 screen_write_cursormove(&ctx, 0, lines - 1, 0); 498 for (offset = 0; offset < c->tty.sx; offset++) 499 screen_write_putc(&ctx, &gc, ' '); 500 screen_write_cursormove(&ctx, 0, lines - 1, 0); 501 screen_write_nputs(&ctx, len, &gc, "%s", c->message_string); 502 screen_write_stop(&ctx); 503 504 if (grid_compare(sl->active->grid, old_screen.grid) == 0) { 505 screen_free(&old_screen); 506 return (0); 507 } 508 screen_free(&old_screen); 509 return (1); 510} 511 512/* Enable status line prompt. */ 513void 514status_prompt_set(struct client *c, const char *msg, const char *input, 515 prompt_input_cb inputcb, prompt_free_cb freecb, void *data, int flags) 516{ 517 struct format_tree *ft; 518 char *tmp, *cp; 519 520 ft = format_create(c, NULL, FORMAT_NONE, 0); 521 format_defaults(ft, c, NULL, NULL, NULL); 522 523 if (input == NULL) 524 input = ""; 525 if (flags & PROMPT_NOFORMAT) 526 tmp = xstrdup(input); 527 else 528 tmp = format_expand_time(ft, input); 529 530 status_message_clear(c); 531 status_prompt_clear(c); 532 status_push_screen(c); 533 534 c->prompt_string = format_expand_time(ft, msg); 535 536 c->prompt_buffer = utf8_fromcstr(tmp); 537 c->prompt_index = utf8_strlen(c->prompt_buffer); 538 539 c->prompt_inputcb = inputcb; 540 c->prompt_freecb = freecb; 541 c->prompt_data = data; 542 543 c->prompt_hindex = 0; 544 545 c->prompt_flags = flags; 546 c->prompt_mode = PROMPT_ENTRY; 547 548 if (~flags & PROMPT_INCREMENTAL) 549 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); 550 c->flags |= CLIENT_REDRAWSTATUS; 551 552 if ((flags & PROMPT_INCREMENTAL) && *tmp != '\0') { 553 xasprintf(&cp, "=%s", tmp); 554 c->prompt_inputcb(c, c->prompt_data, cp, 0); 555 free(cp); 556 } 557 558 free(tmp); 559 format_free(ft); 560} 561 562/* Remove status line prompt. */ 563void 564status_prompt_clear(struct client *c) 565{ 566 if (c->prompt_string == NULL) 567 return; 568 569 if (c->prompt_freecb != NULL && c->prompt_data != NULL) 570 c->prompt_freecb(c->prompt_data); 571 572 free(c->prompt_string); 573 c->prompt_string = NULL; 574 575 free(c->prompt_buffer); 576 c->prompt_buffer = NULL; 577 578 free(c->prompt_saved); 579 c->prompt_saved = NULL; 580 581 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 582 c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */ 583 584 status_pop_screen(c); 585} 586 587/* Update status line prompt with a new prompt string. */ 588void 589status_prompt_update(struct client *c, const char *msg, const char *input) 590{ 591 struct format_tree *ft; 592 char *tmp; 593 594 ft = format_create(c, NULL, FORMAT_NONE, 0); 595 format_defaults(ft, c, NULL, NULL, NULL); 596 597 tmp = format_expand_time(ft, input); 598 599 free(c->prompt_string); 600 c->prompt_string = format_expand_time(ft, msg); 601 602 free(c->prompt_buffer); 603 c->prompt_buffer = utf8_fromcstr(tmp); 604 c->prompt_index = utf8_strlen(c->prompt_buffer); 605 606 c->prompt_hindex = 0; 607 608 c->flags |= CLIENT_REDRAWSTATUS; 609 610 free(tmp); 611 format_free(ft); 612} 613 614/* Draw client prompt on status line of present else on last line. */ 615int 616status_prompt_redraw(struct client *c) 617{ 618 struct status_line *sl = &c->status; 619 struct screen_write_ctx ctx; 620 struct session *s = c->session; 621 struct screen old_screen; 622 u_int i, lines, offset, left, start, width; 623 u_int pcursor, pwidth; 624 struct grid_cell gc, cursorgc; 625 626 if (c->tty.sx == 0 || c->tty.sy == 0) 627 return (0); 628 memcpy(&old_screen, sl->active, sizeof old_screen); 629 630 lines = status_line_size(c); 631 if (lines <= 1) 632 lines = 1; 633 screen_init(sl->active, c->tty.sx, lines, 0); 634 635 if (c->prompt_mode == PROMPT_COMMAND) 636 style_apply(&gc, s->options, "message-command-style"); 637 else 638 style_apply(&gc, s->options, "message-style"); 639 640 memcpy(&cursorgc, &gc, sizeof cursorgc); 641 cursorgc.attr ^= GRID_ATTR_REVERSE; 642 643 start = screen_write_strlen("%s", c->prompt_string); 644 if (start > c->tty.sx) 645 start = c->tty.sx; 646 647 screen_write_start(&ctx, NULL, sl->active); 648 screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1); 649 screen_write_cursormove(&ctx, 0, lines - 1, 0); 650 for (offset = 0; offset < c->tty.sx; offset++) 651 screen_write_putc(&ctx, &gc, ' '); 652 screen_write_cursormove(&ctx, 0, lines - 1, 0); 653 screen_write_nputs(&ctx, start, &gc, "%s", c->prompt_string); 654 screen_write_cursormove(&ctx, start, lines - 1, 0); 655 656 left = c->tty.sx - start; 657 if (left == 0) 658 goto finished; 659 660 pcursor = utf8_strwidth(c->prompt_buffer, c->prompt_index); 661 pwidth = utf8_strwidth(c->prompt_buffer, -1); 662 if (pcursor >= left) { 663 /* 664 * The cursor would be outside the screen so start drawing 665 * with it on the right. 666 */ 667 offset = (pcursor - left) + 1; 668 pwidth = left; 669 } else 670 offset = 0; 671 if (pwidth > left) 672 pwidth = left; 673 674 width = 0; 675 for (i = 0; c->prompt_buffer[i].size != 0; i++) { 676 if (width < offset) { 677 width += c->prompt_buffer[i].width; 678 continue; 679 } 680 if (width >= offset + pwidth) 681 break; 682 width += c->prompt_buffer[i].width; 683 if (width > offset + pwidth) 684 break; 685 686 if (i != c->prompt_index) { 687 utf8_copy(&gc.data, &c->prompt_buffer[i]); 688 screen_write_cell(&ctx, &gc); 689 } else { 690 utf8_copy(&cursorgc.data, &c->prompt_buffer[i]); 691 screen_write_cell(&ctx, &cursorgc); 692 } 693 } 694 if (sl->active->cx < screen_size_x(sl->active) && c->prompt_index >= i) 695 screen_write_putc(&ctx, &cursorgc, ' '); 696 697finished: 698 screen_write_stop(&ctx); 699 700 if (grid_compare(sl->active->grid, old_screen.grid) == 0) { 701 screen_free(&old_screen); 702 return (0); 703 } 704 screen_free(&old_screen); 705 return (1); 706} 707 708/* Is this a separator? */ 709static int 710status_prompt_in_list(const char *ws, const struct utf8_data *ud) 711{ 712 if (ud->size != 1 || ud->width != 1) 713 return (0); 714 return (strchr(ws, *ud->data) != NULL); 715} 716 717/* Is this a space? */ 718static int 719status_prompt_space(const struct utf8_data *ud) 720{ 721 if (ud->size != 1 || ud->width != 1) 722 return (0); 723 return (*ud->data == ' '); 724} 725 726/* 727 * Translate key from emacs to vi. Return 0 to drop key, 1 to process the key 728 * as an emacs key; return 2 to append to the buffer. 729 */ 730static int 731status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) 732{ 733 if (c->prompt_mode == PROMPT_ENTRY) { 734 switch (key) { 735 case '\003': /* C-c */ 736 case '\010': /* C-h */ 737 case '\011': /* Tab */ 738 case '\025': /* C-u */ 739 case '\027': /* C-w */ 740 case '\n': 741 case '\r': 742 case KEYC_BSPACE: 743 case KEYC_DC: 744 case KEYC_DOWN: 745 case KEYC_END: 746 case KEYC_HOME: 747 case KEYC_LEFT: 748 case KEYC_RIGHT: 749 case KEYC_UP: 750 *new_key = key; 751 return (1); 752 case '\033': /* Escape */ 753 c->prompt_mode = PROMPT_COMMAND; 754 c->flags |= CLIENT_REDRAWSTATUS; 755 return (0); 756 } 757 *new_key = key; 758 return (2); 759 } 760 761 switch (key) { 762 case 'A': 763 case 'I': 764 case 'C': 765 case 's': 766 case 'a': 767 c->prompt_mode = PROMPT_ENTRY; 768 c->flags |= CLIENT_REDRAWSTATUS; 769 break; /* switch mode and... */ 770 case 'S': 771 c->prompt_mode = PROMPT_ENTRY; 772 c->flags |= CLIENT_REDRAWSTATUS; 773 *new_key = '\025'; /* C-u */ 774 return (1); 775 case 'i': 776 case '\033': /* Escape */ 777 c->prompt_mode = PROMPT_ENTRY; 778 c->flags |= CLIENT_REDRAWSTATUS; 779 return (0); 780 } 781 782 switch (key) { 783 case 'A': 784 case '$': 785 *new_key = KEYC_END; 786 return (1); 787 case 'I': 788 case '0': 789 case '^': 790 *new_key = KEYC_HOME; 791 return (1); 792 case 'C': 793 case 'D': 794 *new_key = '\013'; /* C-k */ 795 return (1); 796 case KEYC_BSPACE: 797 case 'X': 798 *new_key = KEYC_BSPACE; 799 return (1); 800 case 'b': 801 case 'B': 802 *new_key = 'b'|KEYC_ESCAPE; 803 return (1); 804 case 'd': 805 *new_key = '\025'; 806 return (1); 807 case 'e': 808 case 'E': 809 case 'w': 810 case 'W': 811 *new_key = 'f'|KEYC_ESCAPE; 812 return (1); 813 case 'p': 814 *new_key = '\031'; /* C-y */ 815 return (1); 816 case 's': 817 case KEYC_DC: 818 case 'x': 819 *new_key = KEYC_DC; 820 return (1); 821 case KEYC_DOWN: 822 case 'j': 823 *new_key = KEYC_DOWN; 824 return (1); 825 case KEYC_LEFT: 826 case 'h': 827 *new_key = KEYC_LEFT; 828 return (1); 829 case 'a': 830 case KEYC_RIGHT: 831 case 'l': 832 *new_key = KEYC_RIGHT; 833 return (1); 834 case KEYC_UP: 835 case 'k': 836 *new_key = KEYC_UP; 837 return (1); 838 case '\010' /* C-h */: 839 case '\003' /* C-c */: 840 case '\n': 841 case '\r': 842 return (1); 843 } 844 return (0); 845} 846 847/* Paste into prompt. */ 848static int 849status_prompt_paste(struct client *c) 850{ 851 struct paste_buffer *pb; 852 const char *bufdata; 853 size_t size, n, bufsize; 854 u_int i; 855 struct utf8_data *ud, *udp; 856 enum utf8_state more; 857 858 size = utf8_strlen(c->prompt_buffer); 859 if (c->prompt_saved != NULL) { 860 ud = c->prompt_saved; 861 n = utf8_strlen(c->prompt_saved); 862 } else { 863 if ((pb = paste_get_top(NULL)) == NULL) 864 return (0); 865 bufdata = paste_buffer_data(pb, &bufsize); 866 ud = xreallocarray(NULL, bufsize + 1, sizeof *ud); 867 udp = ud; 868 for (i = 0; i != bufsize; /* nothing */) { 869 more = utf8_open(udp, bufdata[i]); 870 if (more == UTF8_MORE) { 871 while (++i != bufsize && more == UTF8_MORE) 872 more = utf8_append(udp, bufdata[i]); 873 if (more == UTF8_DONE) { 874 udp++; 875 continue; 876 } 877 i -= udp->have; 878 } 879 if (bufdata[i] <= 31 || bufdata[i] >= 127) 880 break; 881 utf8_set(udp, bufdata[i]); 882 udp++; 883 i++; 884 } 885 udp->size = 0; 886 n = udp - ud; 887 } 888 if (n == 0) 889 return (0); 890 891 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + n + 1, 892 sizeof *c->prompt_buffer); 893 if (c->prompt_index == size) { 894 memcpy(c->prompt_buffer + c->prompt_index, ud, 895 n * sizeof *c->prompt_buffer); 896 c->prompt_index += n; 897 c->prompt_buffer[c->prompt_index].size = 0; 898 } else { 899 memmove(c->prompt_buffer + c->prompt_index + n, 900 c->prompt_buffer + c->prompt_index, 901 (size + 1 - c->prompt_index) * sizeof *c->prompt_buffer); 902 memcpy(c->prompt_buffer + c->prompt_index, ud, 903 n * sizeof *c->prompt_buffer); 904 c->prompt_index += n; 905 } 906 907 if (ud != c->prompt_saved) 908 free(ud); 909 return (1); 910} 911 912/* Handle keys in prompt. */ 913int 914status_prompt_key(struct client *c, key_code key) 915{ 916 struct options *oo = c->session->options; 917 char *s, *cp, word[64], prefix = '='; 918 const char *histstr, *ws = NULL, *keystring; 919 size_t size, n, off, idx, used; 920 struct utf8_data tmp, *first, *last, *ud; 921 int keys; 922 923 if (c->prompt_flags & PROMPT_KEY) { 924 keystring = key_string_lookup_key(key); 925 c->prompt_inputcb(c, c->prompt_data, keystring, 1); 926 status_prompt_clear(c); 927 return (0); 928 } 929 size = utf8_strlen(c->prompt_buffer); 930 931 if (c->prompt_flags & PROMPT_NUMERIC) { 932 if (key >= '0' && key <= '9') 933 goto append_key; 934 s = utf8_tocstr(c->prompt_buffer); 935 c->prompt_inputcb(c, c->prompt_data, s, 1); 936 status_prompt_clear(c); 937 free(s); 938 return (1); 939 } 940 key &= ~KEYC_XTERM; 941 942 keys = options_get_number(c->session->options, "status-keys"); 943 if (keys == MODEKEY_VI) { 944 switch (status_prompt_translate_key(c, key, &key)) { 945 case 1: 946 goto process_key; 947 case 2: 948 goto append_key; 949 default: 950 return (0); 951 } 952 } 953 954process_key: 955 switch (key) { 956 case KEYC_LEFT: 957 case '\002': /* C-b */ 958 if (c->prompt_index > 0) { 959 c->prompt_index--; 960 break; 961 } 962 break; 963 case KEYC_RIGHT: 964 case '\006': /* C-f */ 965 if (c->prompt_index < size) { 966 c->prompt_index++; 967 break; 968 } 969 break; 970 case KEYC_HOME: 971 case '\001': /* C-a */ 972 if (c->prompt_index != 0) { 973 c->prompt_index = 0; 974 break; 975 } 976 break; 977 case KEYC_END: 978 case '\005': /* C-e */ 979 if (c->prompt_index != size) { 980 c->prompt_index = size; 981 break; 982 } 983 break; 984 case '\011': /* Tab */ 985 if (c->prompt_buffer[0].size == 0) 986 break; 987 988 idx = c->prompt_index; 989 if (idx != 0) 990 idx--; 991 992 /* Find the word we are in. */ 993 first = &c->prompt_buffer[idx]; 994 while (first > c->prompt_buffer && !status_prompt_space(first)) 995 first--; 996 while (first->size != 0 && status_prompt_space(first)) 997 first++; 998 last = &c->prompt_buffer[idx]; 999 while (last->size != 0 && !status_prompt_space(last)) 1000 last++; 1001 while (last > c->prompt_buffer && status_prompt_space(last)) 1002 last--; 1003 if (last->size != 0) 1004 last++; 1005 if (last <= first) 1006 break; 1007 1008 used = 0; 1009 for (ud = first; ud < last; ud++) { 1010 if (used + ud->size >= sizeof word) 1011 break; 1012 memcpy(word + used, ud->data, ud->size); 1013 used += ud->size; 1014 } 1015 if (ud != last) 1016 break; 1017 word[used] = '\0'; 1018 1019 /* And try to complete it. */ 1020 if ((s = status_prompt_complete(c->session, word)) == NULL) 1021 break; 1022 1023 /* Trim out word. */ 1024 n = size - (last - c->prompt_buffer) + 1; /* with \0 */ 1025 memmove(first, last, n * sizeof *c->prompt_buffer); 1026 size -= last - first; 1027 1028 /* Insert the new word. */ 1029 size += strlen(s); 1030 off = first - c->prompt_buffer; 1031 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 1, 1032 sizeof *c->prompt_buffer); 1033 first = c->prompt_buffer + off; 1034 memmove(first + strlen(s), first, n * sizeof *c->prompt_buffer); 1035 for (idx = 0; idx < strlen(s); idx++) 1036 utf8_set(&first[idx], s[idx]); 1037 1038 c->prompt_index = (first - c->prompt_buffer) + strlen(s); 1039 free(s); 1040 1041 goto changed; 1042 case KEYC_BSPACE: 1043 case '\010': /* C-h */ 1044 if (c->prompt_index != 0) { 1045 if (c->prompt_index == size) 1046 c->prompt_buffer[--c->prompt_index].size = 0; 1047 else { 1048 memmove(c->prompt_buffer + c->prompt_index - 1, 1049 c->prompt_buffer + c->prompt_index, 1050 (size + 1 - c->prompt_index) * 1051 sizeof *c->prompt_buffer); 1052 c->prompt_index--; 1053 } 1054 goto changed; 1055 } 1056 break; 1057 case KEYC_DC: 1058 case '\004': /* C-d */ 1059 if (c->prompt_index != size) { 1060 memmove(c->prompt_buffer + c->prompt_index, 1061 c->prompt_buffer + c->prompt_index + 1, 1062 (size + 1 - c->prompt_index) * 1063 sizeof *c->prompt_buffer); 1064 goto changed; 1065 } 1066 break; 1067 case '\025': /* C-u */ 1068 c->prompt_buffer[0].size = 0; 1069 c->prompt_index = 0; 1070 goto changed; 1071 case '\013': /* C-k */ 1072 if (c->prompt_index < size) { 1073 c->prompt_buffer[c->prompt_index].size = 0; 1074 goto changed; 1075 } 1076 break; 1077 case '\027': /* C-w */ 1078 ws = options_get_string(oo, "word-separators"); 1079 idx = c->prompt_index; 1080 1081 /* Find a non-separator. */ 1082 while (idx != 0) { 1083 idx--; 1084 if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1085 break; 1086 } 1087 1088 /* Find the separator at the beginning of the word. */ 1089 while (idx != 0) { 1090 idx--; 1091 if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) { 1092 /* Go back to the word. */ 1093 idx++; 1094 break; 1095 } 1096 } 1097 1098 free(c->prompt_saved); 1099 c->prompt_saved = xcalloc(sizeof *c->prompt_buffer, 1100 (c->prompt_index - idx) + 1); 1101 memcpy(c->prompt_saved, c->prompt_buffer + idx, 1102 (c->prompt_index - idx) * sizeof *c->prompt_buffer); 1103 1104 memmove(c->prompt_buffer + idx, 1105 c->prompt_buffer + c->prompt_index, 1106 (size + 1 - c->prompt_index) * 1107 sizeof *c->prompt_buffer); 1108 memset(c->prompt_buffer + size - (c->prompt_index - idx), 1109 '\0', (c->prompt_index - idx) * sizeof *c->prompt_buffer); 1110 c->prompt_index = idx; 1111 1112 goto changed; 1113 case 'f'|KEYC_ESCAPE: 1114 case KEYC_RIGHT|KEYC_CTRL: 1115 ws = options_get_string(oo, "word-separators"); 1116 1117 /* Find a word. */ 1118 while (c->prompt_index != size) { 1119 idx = ++c->prompt_index; 1120 if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1121 break; 1122 } 1123 1124 /* Find the separator at the end of the word. */ 1125 while (c->prompt_index != size) { 1126 idx = ++c->prompt_index; 1127 if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1128 break; 1129 } 1130 1131 /* Back up to the end-of-word like vi. */ 1132 if (options_get_number(oo, "status-keys") == MODEKEY_VI && 1133 c->prompt_index != 0) 1134 c->prompt_index--; 1135 1136 goto changed; 1137 case 'b'|KEYC_ESCAPE: 1138 case KEYC_LEFT|KEYC_CTRL: 1139 ws = options_get_string(oo, "word-separators"); 1140 1141 /* Find a non-separator. */ 1142 while (c->prompt_index != 0) { 1143 idx = --c->prompt_index; 1144 if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1145 break; 1146 } 1147 1148 /* Find the separator at the beginning of the word. */ 1149 while (c->prompt_index != 0) { 1150 idx = --c->prompt_index; 1151 if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) { 1152 /* Go back to the word. */ 1153 c->prompt_index++; 1154 break; 1155 } 1156 } 1157 goto changed; 1158 case KEYC_UP: 1159 case '\020': /* C-p */ 1160 histstr = status_prompt_up_history(&c->prompt_hindex); 1161 if (histstr == NULL) 1162 break; 1163 free(c->prompt_buffer); 1164 c->prompt_buffer = utf8_fromcstr(histstr); 1165 c->prompt_index = utf8_strlen(c->prompt_buffer); 1166 goto changed; 1167 case KEYC_DOWN: 1168 case '\016': /* C-n */ 1169 histstr = status_prompt_down_history(&c->prompt_hindex); 1170 if (histstr == NULL) 1171 break; 1172 free(c->prompt_buffer); 1173 c->prompt_buffer = utf8_fromcstr(histstr); 1174 c->prompt_index = utf8_strlen(c->prompt_buffer); 1175 goto changed; 1176 case '\031': /* C-y */ 1177 if (status_prompt_paste(c)) 1178 goto changed; 1179 break; 1180 case '\024': /* C-t */ 1181 idx = c->prompt_index; 1182 if (idx < size) 1183 idx++; 1184 if (idx >= 2) { 1185 utf8_copy(&tmp, &c->prompt_buffer[idx - 2]); 1186 utf8_copy(&c->prompt_buffer[idx - 2], 1187 &c->prompt_buffer[idx - 1]); 1188 utf8_copy(&c->prompt_buffer[idx - 1], &tmp); 1189 c->prompt_index = idx; 1190 goto changed; 1191 } 1192 break; 1193 case '\r': 1194 case '\n': 1195 s = utf8_tocstr(c->prompt_buffer); 1196 if (*s != '\0') 1197 status_prompt_add_history(s); 1198 if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) 1199 status_prompt_clear(c); 1200 free(s); 1201 break; 1202 case '\033': /* Escape */ 1203 case '\003': /* C-c */ 1204 case '\007': /* C-g */ 1205 if (c->prompt_inputcb(c, c->prompt_data, NULL, 1) == 0) 1206 status_prompt_clear(c); 1207 break; 1208 case '\022': /* C-r */ 1209 if (c->prompt_flags & PROMPT_INCREMENTAL) { 1210 prefix = '-'; 1211 goto changed; 1212 } 1213 break; 1214 case '\023': /* C-s */ 1215 if (c->prompt_flags & PROMPT_INCREMENTAL) { 1216 prefix = '+'; 1217 goto changed; 1218 } 1219 break; 1220 default: 1221 goto append_key; 1222 } 1223 1224 c->flags |= CLIENT_REDRAWSTATUS; 1225 return (0); 1226 1227append_key: 1228 if (key <= 0x1f || key >= KEYC_BASE) 1229 return (0); 1230 if (utf8_split(key, &tmp) != UTF8_DONE) 1231 return (0); 1232 1233 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 2, 1234 sizeof *c->prompt_buffer); 1235 1236 if (c->prompt_index == size) { 1237 utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp); 1238 c->prompt_index++; 1239 c->prompt_buffer[c->prompt_index].size = 0; 1240 } else { 1241 memmove(c->prompt_buffer + c->prompt_index + 1, 1242 c->prompt_buffer + c->prompt_index, 1243 (size + 1 - c->prompt_index) * 1244 sizeof *c->prompt_buffer); 1245 utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp); 1246 c->prompt_index++; 1247 } 1248 1249 if (c->prompt_flags & PROMPT_SINGLE) { 1250 s = utf8_tocstr(c->prompt_buffer); 1251 if (strlen(s) != 1) 1252 status_prompt_clear(c); 1253 else if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) 1254 status_prompt_clear(c); 1255 free(s); 1256 } 1257 1258changed: 1259 c->flags |= CLIENT_REDRAWSTATUS; 1260 if (c->prompt_flags & PROMPT_INCREMENTAL) { 1261 s = utf8_tocstr(c->prompt_buffer); 1262 xasprintf(&cp, "%c%s", prefix, s); 1263 c->prompt_inputcb(c, c->prompt_data, cp, 0); 1264 free(cp); 1265 free(s); 1266 } 1267 return (0); 1268} 1269 1270/* Get previous line from the history. */ 1271static const char * 1272status_prompt_up_history(u_int *idx) 1273{ 1274 /* 1275 * History runs from 0 to size - 1. Index is from 0 to size. Zero is 1276 * empty. 1277 */ 1278 1279 if (status_prompt_hsize == 0 || *idx == status_prompt_hsize) 1280 return (NULL); 1281 (*idx)++; 1282 return (status_prompt_hlist[status_prompt_hsize - *idx]); 1283} 1284 1285/* Get next line from the history. */ 1286static const char * 1287status_prompt_down_history(u_int *idx) 1288{ 1289 if (status_prompt_hsize == 0 || *idx == 0) 1290 return (""); 1291 (*idx)--; 1292 if (*idx == 0) 1293 return (""); 1294 return (status_prompt_hlist[status_prompt_hsize - *idx]); 1295} 1296 1297/* Add line to the history. */ 1298static void 1299status_prompt_add_history(const char *line) 1300{ 1301 size_t size; 1302 1303 if (status_prompt_hsize > 0 && 1304 strcmp(status_prompt_hlist[status_prompt_hsize - 1], line) == 0) 1305 return; 1306 1307 if (status_prompt_hsize == PROMPT_HISTORY) { 1308 free(status_prompt_hlist[0]); 1309 1310 size = (PROMPT_HISTORY - 1) * sizeof *status_prompt_hlist; 1311 memmove(&status_prompt_hlist[0], &status_prompt_hlist[1], size); 1312 1313 status_prompt_hlist[status_prompt_hsize - 1] = xstrdup(line); 1314 return; 1315 } 1316 1317 status_prompt_hlist = xreallocarray(status_prompt_hlist, 1318 status_prompt_hsize + 1, sizeof *status_prompt_hlist); 1319 status_prompt_hlist[status_prompt_hsize++] = xstrdup(line); 1320} 1321 1322/* Build completion list. */ 1323char ** 1324status_prompt_complete_list(u_int *size, const char *s) 1325{ 1326 char **list = NULL; 1327 const char **layout, *value, *cp; 1328 const struct cmd_entry **cmdent; 1329 const struct options_table_entry *oe; 1330 u_int idx; 1331 size_t slen = strlen(s), valuelen; 1332 struct options_entry *o; 1333 struct options_array_item *a; 1334 const char *layouts[] = { 1335 "even-horizontal", "even-vertical", "main-horizontal", 1336 "main-vertical", "tiled", NULL 1337 }; 1338 1339 *size = 0; 1340 for (cmdent = cmd_table; *cmdent != NULL; cmdent++) { 1341 if (strncmp((*cmdent)->name, s, slen) == 0) { 1342 list = xreallocarray(list, (*size) + 1, sizeof *list); 1343 list[(*size)++] = xstrdup((*cmdent)->name); 1344 } 1345 } 1346 for (oe = options_table; oe->name != NULL; oe++) { 1347 if (strncmp(oe->name, s, slen) == 0) { 1348 list = xreallocarray(list, (*size) + 1, sizeof *list); 1349 list[(*size)++] = xstrdup(oe->name); 1350 } 1351 } 1352 for (layout = layouts; *layout != NULL; layout++) { 1353 if (strncmp(*layout, s, slen) == 0) { 1354 list = xreallocarray(list, (*size) + 1, sizeof *list); 1355 list[(*size)++] = xstrdup(*layout); 1356 } 1357 } 1358 o = options_get_only(global_options, "command-alias"); 1359 if (o != NULL) { 1360 a = options_array_first(o); 1361 while (a != NULL) { 1362 value = options_array_item_value(a)->string; 1363 if ((cp = strchr(value, '=')) == NULL) 1364 goto next; 1365 valuelen = cp - value; 1366 if (slen > valuelen || strncmp(value, s, slen) != 0) 1367 goto next; 1368 1369 list = xreallocarray(list, (*size) + 1, sizeof *list); 1370 list[(*size)++] = xstrndup(value, valuelen); 1371 1372 next: 1373 a = options_array_next(a); 1374 } 1375 } 1376 for (idx = 0; idx < (*size); idx++) 1377 log_debug("complete %u: %s", idx, list[idx]); 1378 return (list); 1379} 1380 1381/* Find longest prefix. */ 1382static char * 1383status_prompt_complete_prefix(char **list, u_int size) 1384{ 1385 char *out; 1386 u_int i; 1387 size_t j; 1388 1389 out = xstrdup(list[0]); 1390 for (i = 1; i < size; i++) { 1391 j = strlen(list[i]); 1392 if (j > strlen(out)) 1393 j = strlen(out); 1394 for (; j > 0; j--) { 1395 if (out[j - 1] != list[i][j - 1]) 1396 out[j - 1] = '\0'; 1397 } 1398 } 1399 return (out); 1400} 1401 1402/* Complete word. */ 1403static char * 1404status_prompt_complete(struct session *session, const char *s) 1405{ 1406 char **list = NULL; 1407 const char *colon; 1408 u_int size = 0, i; 1409 struct session *s_loop; 1410 struct winlink *wl; 1411 struct window *w; 1412 char *copy, *out, *tmp; 1413 1414 if (*s == '\0') 1415 return (NULL); 1416 out = NULL; 1417 1418 if (strncmp(s, "-t", 2) != 0 && strncmp(s, "-s", 2) != 0) { 1419 list = status_prompt_complete_list(&size, s); 1420 if (size == 0) 1421 out = NULL; 1422 else if (size == 1) 1423 xasprintf(&out, "%s ", list[0]); 1424 else 1425 out = status_prompt_complete_prefix(list, size); 1426 for (i = 0; i < size; i++) 1427 free(list[i]); 1428 free(list); 1429 return (out); 1430 } 1431 copy = xstrdup(s); 1432 1433 colon = ":"; 1434 if (copy[strlen(copy) - 1] == ':') 1435 copy[strlen(copy) - 1] = '\0'; 1436 else 1437 colon = ""; 1438 s = copy + 2; 1439 1440 RB_FOREACH(s_loop, sessions, &sessions) { 1441 if (strncmp(s_loop->name, s, strlen(s)) == 0) { 1442 list = xreallocarray(list, size + 2, sizeof *list); 1443 list[size++] = s_loop->name; 1444 } 1445 } 1446 if (size == 1) { 1447 out = xstrdup(list[0]); 1448 if (session_find(list[0]) != NULL) 1449 colon = ":"; 1450 } else if (size != 0) 1451 out = status_prompt_complete_prefix(list, size); 1452 if (out != NULL) { 1453 xasprintf(&tmp, "-%c%s%s", copy[1], out, colon); 1454 free(out); 1455 out = tmp; 1456 goto found; 1457 } 1458 1459 colon = ""; 1460 if (*s == ':') { 1461 RB_FOREACH(wl, winlinks, &session->windows) { 1462 xasprintf(&tmp, ":%s", wl->window->name); 1463 if (strncmp(tmp, s, strlen(s)) == 0){ 1464 list = xreallocarray(list, size + 1, 1465 sizeof *list); 1466 list[size++] = tmp; 1467 continue; 1468 } 1469 free(tmp); 1470 1471 xasprintf(&tmp, ":%d", wl->idx); 1472 if (strncmp(tmp, s, strlen(s)) == 0) { 1473 list = xreallocarray(list, size + 1, 1474 sizeof *list); 1475 list[size++] = tmp; 1476 continue; 1477 } 1478 free(tmp); 1479 } 1480 } else { 1481 RB_FOREACH(s_loop, sessions, &sessions) { 1482 RB_FOREACH(wl, winlinks, &s_loop->windows) { 1483 w = wl->window; 1484 1485 xasprintf(&tmp, "%s:%s", s_loop->name, w->name); 1486 if (strncmp(tmp, s, strlen(s)) == 0) { 1487 list = xreallocarray(list, size + 1, 1488 sizeof *list); 1489 list[size++] = tmp; 1490 continue; 1491 } 1492 free(tmp); 1493 1494 xasprintf(&tmp, "%s:%d", s_loop->name, wl->idx); 1495 if (strncmp(tmp, s, strlen(s)) == 0) { 1496 list = xreallocarray(list, size + 1, 1497 sizeof *list); 1498 list[size++] = tmp; 1499 continue; 1500 } 1501 free(tmp); 1502 } 1503 } 1504 } 1505 if (size == 1) { 1506 out = xstrdup(list[0]); 1507 colon = " "; 1508 } else if (size != 0) 1509 out = status_prompt_complete_prefix(list, size); 1510 if (out != NULL) { 1511 xasprintf(&tmp, "-%c%s%s", copy[1], out, colon); 1512 out = tmp; 1513 } 1514 1515 for (i = 0; i < size; i++) 1516 free((void *)list[i]); 1517 1518found: 1519 free(copy); 1520 free(list); 1521 return (out); 1522} 1523