status.c revision 1.105
1/* $OpenBSD: status.c,v 1.105 2013/07/05 14:38:22 nicm Exp $ */ 2 3/* 4 * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19#include <sys/types.h> 20#include <sys/time.h> 21 22#include <errno.h> 23#include <limits.h> 24#include <stdarg.h> 25#include <stdlib.h> 26#include <string.h> 27#include <time.h> 28#include <unistd.h> 29 30#include "tmux.h" 31 32char *status_redraw_get_left( 33 struct client *, time_t, int, struct grid_cell *, size_t *); 34char *status_redraw_get_right( 35 struct client *, time_t, int, struct grid_cell *, size_t *); 36char *status_find_job(struct client *, char **); 37void status_job_free(void *); 38void status_job_callback(struct job *); 39char *status_print( 40 struct client *, struct winlink *, time_t, struct grid_cell *); 41void status_replace1(struct client *, char **, char **, char *, size_t, int); 42void status_message_callback(int, short, void *); 43 44const char *status_prompt_up_history(u_int *); 45const char *status_prompt_down_history(u_int *); 46void status_prompt_add_history(const char *); 47char *status_prompt_complete(const char *); 48 49/* Status prompt history. */ 50ARRAY_DECL(, char *) status_prompt_history = ARRAY_INITIALIZER; 51 52/* Status output tree. */ 53RB_GENERATE(status_out_tree, status_out, entry, status_out_cmp); 54 55/* Output tree comparison function. */ 56int 57status_out_cmp(struct status_out *so1, struct status_out *so2) 58{ 59 return (strcmp(so1->cmd, so2->cmd)); 60} 61 62/* Get screen line of status line. -1 means off. */ 63int 64status_at_line(struct client *c) 65{ 66 struct session *s = c->session; 67 68 if (!options_get_number(&s->options, "status")) 69 return (-1); 70 71 if (options_get_number(&s->options, "status-position") == 0) 72 return (0); 73 return (c->tty.sy - 1); 74} 75 76/* Retrieve options for left string. */ 77char * 78status_redraw_get_left(struct client *c, 79 time_t t, int utf8flag, struct grid_cell *gc, size_t *size) 80{ 81 struct session *s = c->session; 82 char *left; 83 int fg, bg, attr; 84 size_t leftlen; 85 86 fg = options_get_number(&s->options, "status-left-fg"); 87 if (fg != 8) 88 colour_set_fg(gc, fg); 89 bg = options_get_number(&s->options, "status-left-bg"); 90 if (bg != 8) 91 colour_set_bg(gc, bg); 92 attr = options_get_number(&s->options, "status-left-attr"); 93 if (attr != 0) 94 gc->attr = attr; 95 96 left = status_replace(c, NULL, 97 NULL, NULL, options_get_string(&s->options, "status-left"), t, 1); 98 99 *size = options_get_number(&s->options, "status-left-length"); 100 leftlen = screen_write_cstrlen(utf8flag, "%s", left); 101 if (leftlen < *size) 102 *size = leftlen; 103 return (left); 104} 105 106/* Retrieve options for right string. */ 107char * 108status_redraw_get_right(struct client *c, 109 time_t t, int utf8flag, struct grid_cell *gc, size_t *size) 110{ 111 struct session *s = c->session; 112 char *right; 113 int fg, bg, attr; 114 size_t rightlen; 115 116 fg = options_get_number(&s->options, "status-right-fg"); 117 if (fg != 8) 118 colour_set_fg(gc, fg); 119 bg = options_get_number(&s->options, "status-right-bg"); 120 if (bg != 8) 121 colour_set_bg(gc, bg); 122 attr = options_get_number(&s->options, "status-right-attr"); 123 if (attr != 0) 124 gc->attr = attr; 125 126 right = status_replace(c, NULL, 127 NULL, NULL, options_get_string(&s->options, "status-right"), t, 1); 128 129 *size = options_get_number(&s->options, "status-right-length"); 130 rightlen = screen_write_cstrlen(utf8flag, "%s", right); 131 if (rightlen < *size) 132 *size = rightlen; 133 return (right); 134} 135 136/* Set window at window list position. */ 137void 138status_set_window_at(struct client *c, u_int x) 139{ 140 struct session *s = c->session; 141 struct winlink *wl; 142 143 x += c->wlmouse; 144 RB_FOREACH(wl, winlinks, &s->windows) { 145 if (x < wl->status_width && 146 session_select(s, wl->idx) == 0) { 147 server_redraw_session(s); 148 } 149 x -= wl->status_width + 1; 150 } 151} 152 153/* Draw status for client on the last lines of given context. */ 154int 155status_redraw(struct client *c) 156{ 157 struct screen_write_ctx ctx; 158 struct session *s = c->session; 159 struct winlink *wl; 160 struct screen old_status, window_list; 161 struct grid_cell stdgc, lgc, rgc, gc; 162 struct options *oo; 163 time_t t; 164 char *left, *right, *sep; 165 u_int offset, needed; 166 u_int wlstart, wlwidth, wlavailable, wloffset, wlsize; 167 size_t llen, rlen, seplen; 168 int larrow, rarrow, utf8flag; 169 170 /* No status line? */ 171 if (c->tty.sy == 0 || !options_get_number(&s->options, "status")) 172 return (1); 173 left = right = NULL; 174 larrow = rarrow = 0; 175 176 /* Update status timer. */ 177 if (gettimeofday(&c->status_timer, NULL) != 0) 178 fatal("gettimeofday failed"); 179 t = c->status_timer.tv_sec; 180 181 /* Set up default colour. */ 182 memcpy(&stdgc, &grid_default_cell, sizeof gc); 183 colour_set_fg(&stdgc, options_get_number(&s->options, "status-fg")); 184 colour_set_bg(&stdgc, options_get_number(&s->options, "status-bg")); 185 stdgc.attr |= options_get_number(&s->options, "status-attr"); 186 187 /* Create the target screen. */ 188 memcpy(&old_status, &c->status, sizeof old_status); 189 screen_init(&c->status, c->tty.sx, 1, 0); 190 screen_write_start(&ctx, NULL, &c->status); 191 for (offset = 0; offset < c->tty.sx; offset++) 192 screen_write_putc(&ctx, &stdgc, ' '); 193 screen_write_stop(&ctx); 194 195 /* If the height is one line, blank status line. */ 196 if (c->tty.sy <= 1) 197 goto out; 198 199 /* Get UTF-8 flag. */ 200 utf8flag = options_get_number(&s->options, "status-utf8"); 201 202 /* Work out left and right strings. */ 203 memcpy(&lgc, &stdgc, sizeof lgc); 204 left = status_redraw_get_left(c, t, utf8flag, &lgc, &llen); 205 memcpy(&rgc, &stdgc, sizeof rgc); 206 right = status_redraw_get_right(c, t, utf8flag, &rgc, &rlen); 207 208 /* 209 * Figure out how much space we have for the window list. If there 210 * isn't enough space, just show a blank status line. 211 */ 212 needed = 0; 213 if (llen != 0) 214 needed += llen + 1; 215 if (rlen != 0) 216 needed += rlen + 1; 217 if (c->tty.sx == 0 || c->tty.sx <= needed) 218 goto out; 219 wlavailable = c->tty.sx - needed; 220 221 /* Calculate the total size needed for the window list. */ 222 wlstart = wloffset = wlwidth = 0; 223 RB_FOREACH(wl, winlinks, &s->windows) { 224 free(wl->status_text); 225 memcpy(&wl->status_cell, &stdgc, sizeof wl->status_cell); 226 wl->status_text = status_print(c, wl, t, &wl->status_cell); 227 wl->status_width = 228 screen_write_cstrlen(utf8flag, "%s", wl->status_text); 229 230 if (wl == s->curw) 231 wloffset = wlwidth; 232 233 oo = &wl->window->options; 234 sep = options_get_string(oo, "window-status-separator"); 235 seplen = screen_write_strlen(utf8flag, "%s", sep); 236 wlwidth += wl->status_width + seplen; 237 } 238 239 /* Create a new screen for the window list. */ 240 screen_init(&window_list, wlwidth, 1, 0); 241 242 /* And draw the window list into it. */ 243 screen_write_start(&ctx, NULL, &window_list); 244 RB_FOREACH(wl, winlinks, &s->windows) { 245 screen_write_cnputs(&ctx, 246 -1, &wl->status_cell, utf8flag, "%s", wl->status_text); 247 248 oo = &wl->window->options; 249 sep = options_get_string(oo, "window-status-separator"); 250 screen_write_nputs(&ctx, -1, &stdgc, utf8flag, "%s", sep); 251 } 252 screen_write_stop(&ctx); 253 254 /* If there is enough space for the total width, skip to draw now. */ 255 if (wlwidth <= wlavailable) 256 goto draw; 257 258 /* Find size of current window text. */ 259 wlsize = s->curw->status_width; 260 261 /* 262 * If the current window is already on screen, good to draw from the 263 * start and just leave off the end. 264 */ 265 if (wloffset + wlsize < wlavailable) { 266 if (wlavailable > 0) { 267 rarrow = 1; 268 wlavailable--; 269 } 270 wlwidth = wlavailable; 271 } else { 272 /* 273 * Work out how many characters we need to omit from the 274 * start. There are wlavailable characters to fill, and 275 * wloffset + wlsize must be the last. So, the start character 276 * is wloffset + wlsize - wlavailable. 277 */ 278 if (wlavailable > 0) { 279 larrow = 1; 280 wlavailable--; 281 } 282 283 wlstart = wloffset + wlsize - wlavailable; 284 if (wlavailable > 0 && wlwidth > wlstart + wlavailable + 1) { 285 rarrow = 1; 286 wlstart++; 287 wlavailable--; 288 } 289 wlwidth = wlavailable; 290 } 291 292 /* Bail if anything is now too small too. */ 293 if (wlwidth == 0 || wlavailable == 0) { 294 screen_free(&window_list); 295 goto out; 296 } 297 298 /* 299 * Now the start position is known, work out the state of the left and 300 * right arrows. 301 */ 302 offset = 0; 303 RB_FOREACH(wl, winlinks, &s->windows) { 304 if (wl->flags & WINLINK_ALERTFLAGS && 305 larrow == 1 && offset < wlstart) 306 larrow = -1; 307 308 offset += wl->status_width; 309 310 if (wl->flags & WINLINK_ALERTFLAGS && 311 rarrow == 1 && offset > wlstart + wlwidth) 312 rarrow = -1; 313 } 314 315draw: 316 /* Begin drawing. */ 317 screen_write_start(&ctx, NULL, &c->status); 318 319 /* Draw the left string and arrow. */ 320 screen_write_cursormove(&ctx, 0, 0); 321 if (llen != 0) { 322 screen_write_cnputs(&ctx, llen, &lgc, utf8flag, "%s", left); 323 screen_write_putc(&ctx, &stdgc, ' '); 324 } 325 if (larrow != 0) { 326 memcpy(&gc, &stdgc, sizeof gc); 327 if (larrow == -1) 328 gc.attr ^= GRID_ATTR_REVERSE; 329 screen_write_putc(&ctx, &gc, '<'); 330 } 331 332 /* Draw the right string and arrow. */ 333 if (rarrow != 0) { 334 screen_write_cursormove(&ctx, c->tty.sx - rlen - 2, 0); 335 memcpy(&gc, &stdgc, sizeof gc); 336 if (rarrow == -1) 337 gc.attr ^= GRID_ATTR_REVERSE; 338 screen_write_putc(&ctx, &gc, '>'); 339 } else 340 screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, 0); 341 if (rlen != 0) { 342 screen_write_putc(&ctx, &stdgc, ' '); 343 screen_write_cnputs(&ctx, rlen, &rgc, utf8flag, "%s", right); 344 } 345 346 /* Figure out the offset for the window list. */ 347 if (llen != 0) 348 wloffset = llen + 1; 349 else 350 wloffset = 0; 351 if (wlwidth < wlavailable) { 352 switch (options_get_number(&s->options, "status-justify")) { 353 case 1: /* centered */ 354 wloffset += (wlavailable - wlwidth) / 2; 355 break; 356 case 2: /* right */ 357 wloffset += (wlavailable - wlwidth); 358 break; 359 } 360 } 361 if (larrow != 0) 362 wloffset++; 363 364 /* Copy the window list. */ 365 c->wlmouse = -wloffset + wlstart; 366 screen_write_cursormove(&ctx, wloffset, 0); 367 screen_write_copy(&ctx, &window_list, wlstart, 0, wlwidth, 1); 368 screen_free(&window_list); 369 370 screen_write_stop(&ctx); 371 372out: 373 free(left); 374 free(right); 375 376 if (grid_compare(c->status.grid, old_status.grid) == 0) { 377 screen_free(&old_status); 378 return (0); 379 } 380 screen_free(&old_status); 381 return (1); 382} 383 384/* Replace a single special sequence (prefixed by #). */ 385void 386status_replace1(struct client *c, char **iptr, char **optr, char *out, 387 size_t outsize, int jobsflag) 388{ 389 char ch, tmp[256], *ptr, *endptr; 390 size_t ptrlen; 391 long limit; 392 393 errno = 0; 394 limit = strtol(*iptr, &endptr, 10); 395 if ((limit == 0 && errno != EINVAL) || 396 (limit == LONG_MIN && errno != ERANGE) || 397 (limit == LONG_MAX && errno != ERANGE) || 398 limit != 0) 399 *iptr = endptr; 400 if (limit <= 0) 401 limit = LONG_MAX; 402 403 switch (*(*iptr)++) { 404 case '(': 405 if (!jobsflag) { 406 ch = ')'; 407 goto skip_to; 408 } 409 if ((ptr = status_find_job(c, iptr)) == NULL) 410 return; 411 goto do_replace; 412 case '[': 413 /* 414 * Embedded style, handled at display time. Leave present and 415 * skip input until ]. 416 */ 417 ch = ']'; 418 goto skip_to; 419 case '{': 420 ptr = (char *) "#{"; 421 goto do_replace; 422 case '#': 423 *(*optr)++ = '#'; 424 break; 425 default: 426 xsnprintf(tmp, sizeof tmp, "#%c", *(*iptr - 1)); 427 ptr = tmp; 428 goto do_replace; 429 } 430 431 return; 432 433do_replace: 434 ptrlen = strlen(ptr); 435 if ((size_t) limit < ptrlen) 436 ptrlen = limit; 437 438 if (*optr + ptrlen >= out + outsize - 1) 439 return; 440 while (ptrlen > 0 && *ptr != '\0') { 441 *(*optr)++ = *ptr++; 442 ptrlen--; 443 } 444 445 return; 446 447skip_to: 448 *(*optr)++ = '#'; 449 450 (*iptr)--; /* include ch */ 451 while (**iptr != ch && **iptr != '\0') { 452 if (*optr >= out + outsize - 1) 453 break; 454 *(*optr)++ = *(*iptr)++; 455 } 456} 457 458/* Replace special sequences in fmt. */ 459char * 460status_replace(struct client *c, struct session *s, struct winlink *wl, 461 struct window_pane *wp, const char *fmt, time_t t, int jobsflag) 462{ 463 static char out[BUFSIZ]; 464 char in[BUFSIZ], ch, *iptr, *optr, *expanded; 465 size_t len; 466 struct format_tree *ft; 467 468 if (fmt == NULL) 469 return (xstrdup("")); 470 471 if (s == NULL) 472 s = c->session; 473 if (wl == NULL) 474 wl = s->curw; 475 if (wp == NULL) 476 wp = wl->window->active; 477 478 len = strftime(in, sizeof in, fmt, localtime(&t)); 479 in[len] = '\0'; 480 481 iptr = in; 482 optr = out; 483 484 while (*iptr != '\0') { 485 if (optr >= out + (sizeof out) - 1) 486 break; 487 ch = *iptr++; 488 489 if (ch != '#' || *iptr == '\0') { 490 *optr++ = ch; 491 continue; 492 } 493 status_replace1(c, &iptr, &optr, out, sizeof out, jobsflag); 494 } 495 *optr = '\0'; 496 497 ft = format_create(); 498 format_client(ft, c); 499 format_session(ft, s); 500 format_winlink(ft, s, wl); 501 format_window_pane(ft, wp); 502 expanded = format_expand(ft, out); 503 format_free(ft); 504 return (expanded); 505} 506 507/* Figure out job name and get its result, starting it off if necessary. */ 508char * 509status_find_job(struct client *c, char **iptr) 510{ 511 struct status_out *so, so_find; 512 char *cmd; 513 int lastesc; 514 size_t len; 515 516 if (**iptr == '\0') 517 return (NULL); 518 if (**iptr == ')') { /* no command given */ 519 (*iptr)++; 520 return (NULL); 521 } 522 523 cmd = xmalloc(strlen(*iptr) + 1); 524 len = 0; 525 526 lastesc = 0; 527 for (; **iptr != '\0'; (*iptr)++) { 528 if (!lastesc && **iptr == ')') 529 break; /* unescaped ) is the end */ 530 if (!lastesc && **iptr == '\\') { 531 lastesc = 1; 532 continue; /* skip \ if not escaped */ 533 } 534 lastesc = 0; 535 cmd[len++] = **iptr; 536 } 537 if (**iptr == '\0') /* no terminating ) */ { 538 free(cmd); 539 return (NULL); 540 } 541 (*iptr)++; /* skip final ) */ 542 cmd[len] = '\0'; 543 544 /* First try in the new tree. */ 545 so_find.cmd = cmd; 546 so = RB_FIND(status_out_tree, &c->status_new, &so_find); 547 if (so != NULL && so->out != NULL) { 548 free(cmd); 549 return (so->out); 550 } 551 552 /* If not found at all, start the job and add to the tree. */ 553 if (so == NULL) { 554 job_run(cmd, NULL, status_job_callback, status_job_free, c); 555 c->references++; 556 557 so = xmalloc(sizeof *so); 558 so->cmd = xstrdup(cmd); 559 so->out = NULL; 560 RB_INSERT(status_out_tree, &c->status_new, so); 561 } 562 563 /* Lookup in the old tree. */ 564 so_find.cmd = cmd; 565 so = RB_FIND(status_out_tree, &c->status_old, &so_find); 566 free(cmd); 567 if (so != NULL) 568 return (so->out); 569 return (NULL); 570} 571 572/* Free job tree. */ 573void 574status_free_jobs(struct status_out_tree *sotree) 575{ 576 struct status_out *so, *so_next; 577 578 so_next = RB_MIN(status_out_tree, sotree); 579 while (so_next != NULL) { 580 so = so_next; 581 so_next = RB_NEXT(status_out_tree, sotree, so); 582 583 RB_REMOVE(status_out_tree, sotree, so); 584 free(so->out); 585 free(so->cmd); 586 free(so); 587 } 588} 589 590/* Update jobs on status interval. */ 591void 592status_update_jobs(struct client *c) 593{ 594 /* Free the old tree. */ 595 status_free_jobs(&c->status_old); 596 597 /* Move the new to old. */ 598 memcpy(&c->status_old, &c->status_new, sizeof c->status_old); 599 RB_INIT(&c->status_new); 600} 601 602/* Free status job. */ 603void 604status_job_free(void *data) 605{ 606 struct client *c = data; 607 608 c->references--; 609} 610 611/* Job has finished: save its result. */ 612void 613status_job_callback(struct job *job) 614{ 615 struct client *c = job->data; 616 struct status_out *so, so_find; 617 char *line, *buf; 618 size_t len; 619 620 if (c->flags & CLIENT_DEAD) 621 return; 622 623 so_find.cmd = job->cmd; 624 so = RB_FIND(status_out_tree, &c->status_new, &so_find); 625 if (so == NULL || so->out != NULL) 626 return; 627 628 buf = NULL; 629 if ((line = evbuffer_readline(job->event->input)) == NULL) { 630 len = EVBUFFER_LENGTH(job->event->input); 631 buf = xmalloc(len + 1); 632 if (len != 0) 633 memcpy(buf, EVBUFFER_DATA(job->event->input), len); 634 buf[len] = '\0'; 635 } else 636 buf = line; 637 638 so->out = buf; 639 server_status_client(c); 640} 641 642/* Return winlink status line entry and adjust gc as necessary. */ 643char * 644status_print( 645 struct client *c, struct winlink *wl, time_t t, struct grid_cell *gc) 646{ 647 struct options *oo = &wl->window->options; 648 struct session *s = c->session; 649 const char *fmt; 650 char *text; 651 int fg, bg, attr; 652 653 fg = options_get_number(oo, "window-status-fg"); 654 if (fg != 8) 655 colour_set_fg(gc, fg); 656 bg = options_get_number(oo, "window-status-bg"); 657 if (bg != 8) 658 colour_set_bg(gc, bg); 659 attr = options_get_number(oo, "window-status-attr"); 660 if (attr != 0) 661 gc->attr = attr; 662 fmt = options_get_string(oo, "window-status-format"); 663 if (wl == s->curw) { 664 fg = options_get_number(oo, "window-status-current-fg"); 665 if (fg != 8) 666 colour_set_fg(gc, fg); 667 bg = options_get_number(oo, "window-status-current-bg"); 668 if (bg != 8) 669 colour_set_bg(gc, bg); 670 attr = options_get_number(oo, "window-status-current-attr"); 671 if (attr != 0) 672 gc->attr = attr; 673 fmt = options_get_string(oo, "window-status-current-format"); 674 } 675 if (wl == TAILQ_FIRST(&s->lastw)) { 676 fg = options_get_number(oo, "window-status-last-fg"); 677 if (fg != 8) 678 colour_set_fg(gc, fg); 679 bg = options_get_number(oo, "window-status-last-bg"); 680 if (bg != 8) 681 colour_set_bg(gc, bg); 682 attr = options_get_number(oo, "window-status-last-attr"); 683 if (attr != 0) 684 gc->attr = attr; 685 } 686 687 if (wl->flags & WINLINK_BELL) { 688 fg = options_get_number(oo, "window-status-bell-fg"); 689 if (fg != 8) 690 colour_set_fg(gc, fg); 691 bg = options_get_number(oo, "window-status-bell-bg"); 692 if (bg != 8) 693 colour_set_bg(gc, bg); 694 attr = options_get_number(oo, "window-status-bell-attr"); 695 if (attr != 0) 696 gc->attr = attr; 697 } else if (wl->flags & WINLINK_CONTENT) { 698 fg = options_get_number(oo, "window-status-content-fg"); 699 if (fg != 8) 700 colour_set_fg(gc, fg); 701 bg = options_get_number(oo, "window-status-content-bg"); 702 if (bg != 8) 703 colour_set_bg(gc, bg); 704 attr = options_get_number(oo, "window-status-content-attr"); 705 if (attr != 0) 706 gc->attr = attr; 707 } else if (wl->flags & (WINLINK_ACTIVITY|WINLINK_SILENCE)) { 708 fg = options_get_number(oo, "window-status-activity-fg"); 709 if (fg != 8) 710 colour_set_fg(gc, fg); 711 bg = options_get_number(oo, "window-status-activity-bg"); 712 if (bg != 8) 713 colour_set_bg(gc, bg); 714 attr = options_get_number(oo, "window-status-activity-attr"); 715 if (attr != 0) 716 gc->attr = attr; 717 } 718 719 text = status_replace(c, NULL, wl, NULL, fmt, t, 1); 720 return (text); 721} 722 723/* Set a status line message. */ 724void printflike2 725status_message_set(struct client *c, const char *fmt, ...) 726{ 727 struct timeval tv; 728 struct session *s = c->session; 729 struct message_entry *msg; 730 va_list ap; 731 int delay; 732 u_int i, limit; 733 734 status_prompt_clear(c); 735 status_message_clear(c); 736 737 va_start(ap, fmt); 738 xvasprintf(&c->message_string, fmt, ap); 739 va_end(ap); 740 741 ARRAY_EXPAND(&c->message_log, 1); 742 msg = &ARRAY_LAST(&c->message_log); 743 msg->msg_time = time(NULL); 744 msg->msg = xstrdup(c->message_string); 745 746 if (s == NULL) 747 limit = 0; 748 else 749 limit = options_get_number(&s->options, "message-limit"); 750 if (ARRAY_LENGTH(&c->message_log) > limit) { 751 limit = ARRAY_LENGTH(&c->message_log) - limit; 752 for (i = 0; i < limit; i++) { 753 msg = &ARRAY_FIRST(&c->message_log); 754 free(msg->msg); 755 ARRAY_REMOVE(&c->message_log, 0); 756 } 757 } 758 759 delay = options_get_number(&c->session->options, "display-time"); 760 tv.tv_sec = delay / 1000; 761 tv.tv_usec = (delay % 1000) * 1000L; 762 763 if (event_initialized (&c->message_timer)) 764 evtimer_del(&c->message_timer); 765 evtimer_set(&c->message_timer, status_message_callback, c); 766 evtimer_add(&c->message_timer, &tv); 767 768 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); 769 c->flags |= CLIENT_STATUS; 770} 771 772/* Clear status line message. */ 773void 774status_message_clear(struct client *c) 775{ 776 if (c->message_string == NULL) 777 return; 778 779 free(c->message_string); 780 c->message_string = NULL; 781 782 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 783 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */ 784 785 screen_reinit(&c->status); 786} 787 788/* Clear status line message after timer expires. */ 789void 790status_message_callback(unused int fd, unused short event, void *data) 791{ 792 struct client *c = data; 793 794 status_message_clear(c); 795} 796 797/* Draw client message on status line of present else on last line. */ 798int 799status_message_redraw(struct client *c) 800{ 801 struct screen_write_ctx ctx; 802 struct session *s = c->session; 803 struct screen old_status; 804 size_t len; 805 struct grid_cell gc; 806 int utf8flag; 807 808 if (c->tty.sx == 0 || c->tty.sy == 0) 809 return (0); 810 memcpy(&old_status, &c->status, sizeof old_status); 811 screen_init(&c->status, c->tty.sx, 1, 0); 812 813 utf8flag = options_get_number(&s->options, "status-utf8"); 814 815 len = screen_write_strlen(utf8flag, "%s", c->message_string); 816 if (len > c->tty.sx) 817 len = c->tty.sx; 818 819 memcpy(&gc, &grid_default_cell, sizeof gc); 820 colour_set_fg(&gc, options_get_number(&s->options, "message-fg")); 821 colour_set_bg(&gc, options_get_number(&s->options, "message-bg")); 822 gc.attr |= options_get_number(&s->options, "message-attr"); 823 824 screen_write_start(&ctx, NULL, &c->status); 825 826 screen_write_cursormove(&ctx, 0, 0); 827 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->message_string); 828 for (; len < c->tty.sx; len++) 829 screen_write_putc(&ctx, &gc, ' '); 830 831 screen_write_stop(&ctx); 832 833 if (grid_compare(c->status.grid, old_status.grid) == 0) { 834 screen_free(&old_status); 835 return (0); 836 } 837 screen_free(&old_status); 838 return (1); 839} 840 841/* Enable status line prompt. */ 842void 843status_prompt_set(struct client *c, const char *msg, const char *input, 844 int (*callbackfn)(void *, const char *), void (*freefn)(void *), 845 void *data, int flags) 846{ 847 int keys; 848 849 status_message_clear(c); 850 status_prompt_clear(c); 851 852 c->prompt_string = status_replace(c, NULL, NULL, NULL, msg, 853 time(NULL), 0); 854 855 c->prompt_buffer = status_replace(c, NULL, NULL, NULL, input, 856 time(NULL), 0); 857 c->prompt_index = strlen(c->prompt_buffer); 858 859 c->prompt_callbackfn = callbackfn; 860 c->prompt_freefn = freefn; 861 c->prompt_data = data; 862 863 c->prompt_hindex = 0; 864 865 c->prompt_flags = flags; 866 867 keys = options_get_number(&c->session->options, "status-keys"); 868 if (keys == MODEKEY_EMACS) 869 mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit); 870 else 871 mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit); 872 873 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); 874 c->flags |= CLIENT_STATUS; 875} 876 877/* Remove status line prompt. */ 878void 879status_prompt_clear(struct client *c) 880{ 881 if (c->prompt_string == NULL) 882 return; 883 884 if (c->prompt_freefn != NULL && c->prompt_data != NULL) 885 c->prompt_freefn(c->prompt_data); 886 887 free(c->prompt_string); 888 c->prompt_string = NULL; 889 890 free(c->prompt_buffer); 891 c->prompt_buffer = NULL; 892 893 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 894 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */ 895 896 screen_reinit(&c->status); 897} 898 899/* Update status line prompt with a new prompt string. */ 900void 901status_prompt_update(struct client *c, const char *msg, const char *input) 902{ 903 free(c->prompt_string); 904 c->prompt_string = status_replace(c, NULL, NULL, NULL, msg, 905 time(NULL), 0); 906 907 free(c->prompt_buffer); 908 c->prompt_buffer = status_replace(c, NULL, NULL, NULL, input, 909 time(NULL), 0); 910 c->prompt_index = strlen(c->prompt_buffer); 911 912 c->prompt_hindex = 0; 913 914 c->flags |= CLIENT_STATUS; 915} 916 917/* Draw client prompt on status line of present else on last line. */ 918int 919status_prompt_redraw(struct client *c) 920{ 921 struct screen_write_ctx ctx; 922 struct session *s = c->session; 923 struct screen old_status; 924 size_t i, size, left, len, off; 925 struct grid_cell gc, *gcp; 926 int utf8flag; 927 928 if (c->tty.sx == 0 || c->tty.sy == 0) 929 return (0); 930 memcpy(&old_status, &c->status, sizeof old_status); 931 screen_init(&c->status, c->tty.sx, 1, 0); 932 933 utf8flag = options_get_number(&s->options, "status-utf8"); 934 935 len = screen_write_strlen(utf8flag, "%s", c->prompt_string); 936 if (len > c->tty.sx) 937 len = c->tty.sx; 938 off = 0; 939 940 memcpy(&gc, &grid_default_cell, sizeof gc); 941 /* Change colours for command mode. */ 942 if (c->prompt_mdata.mode == 1) { 943 colour_set_fg(&gc, options_get_number(&s->options, "message-command-fg")); 944 colour_set_bg(&gc, options_get_number(&s->options, "message-command-bg")); 945 gc.attr |= options_get_number(&s->options, "message-command-attr"); 946 } else { 947 colour_set_fg(&gc, options_get_number(&s->options, "message-fg")); 948 colour_set_bg(&gc, options_get_number(&s->options, "message-bg")); 949 gc.attr |= options_get_number(&s->options, "message-attr"); 950 } 951 952 screen_write_start(&ctx, NULL, &c->status); 953 954 screen_write_cursormove(&ctx, 0, 0); 955 screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->prompt_string); 956 957 left = c->tty.sx - len; 958 if (left != 0) { 959 size = screen_write_strlen(utf8flag, "%s", c->prompt_buffer); 960 if (c->prompt_index >= left) { 961 off = c->prompt_index - left + 1; 962 if (c->prompt_index == size) 963 left--; 964 size = left; 965 } 966 screen_write_nputs( 967 &ctx, left, &gc, utf8flag, "%s", c->prompt_buffer + off); 968 969 for (i = len + size; i < c->tty.sx; i++) 970 screen_write_putc(&ctx, &gc, ' '); 971 } 972 973 screen_write_stop(&ctx); 974 975 /* Apply fake cursor. */ 976 off = len + c->prompt_index - off; 977 gcp = grid_view_get_cell(c->status.grid, off, 0); 978 gcp->attr ^= GRID_ATTR_REVERSE; 979 980 if (grid_compare(c->status.grid, old_status.grid) == 0) { 981 screen_free(&old_status); 982 return (0); 983 } 984 screen_free(&old_status); 985 return (1); 986} 987 988/* Handle keys in prompt. */ 989void 990status_prompt_key(struct client *c, int key) 991{ 992 struct session *sess = c->session; 993 struct options *oo = &sess->options; 994 struct paste_buffer *pb; 995 char *s, *first, *last, word[64], swapc; 996 const char *histstr; 997 const char *wsep = NULL; 998 u_char ch; 999 size_t size, n, off, idx; 1000 1001 size = strlen(c->prompt_buffer); 1002 switch (mode_key_lookup(&c->prompt_mdata, key, NULL)) { 1003 case MODEKEYEDIT_CURSORLEFT: 1004 if (c->prompt_index > 0) { 1005 c->prompt_index--; 1006 c->flags |= CLIENT_STATUS; 1007 } 1008 break; 1009 case MODEKEYEDIT_SWITCHMODE: 1010 c->flags |= CLIENT_STATUS; 1011 break; 1012 case MODEKEYEDIT_SWITCHMODEAPPEND: 1013 c->flags |= CLIENT_STATUS; 1014 /* FALLTHROUGH */ 1015 case MODEKEYEDIT_CURSORRIGHT: 1016 if (c->prompt_index < size) { 1017 c->prompt_index++; 1018 c->flags |= CLIENT_STATUS; 1019 } 1020 break; 1021 case MODEKEYEDIT_SWITCHMODEBEGINLINE: 1022 c->flags |= CLIENT_STATUS; 1023 /* FALLTHROUGH */ 1024 case MODEKEYEDIT_STARTOFLINE: 1025 if (c->prompt_index != 0) { 1026 c->prompt_index = 0; 1027 c->flags |= CLIENT_STATUS; 1028 } 1029 break; 1030 case MODEKEYEDIT_SWITCHMODEAPPENDLINE: 1031 c->flags |= CLIENT_STATUS; 1032 /* FALLTHROUGH */ 1033 case MODEKEYEDIT_ENDOFLINE: 1034 if (c->prompt_index != size) { 1035 c->prompt_index = size; 1036 c->flags |= CLIENT_STATUS; 1037 } 1038 break; 1039 case MODEKEYEDIT_COMPLETE: 1040 if (*c->prompt_buffer == '\0') 1041 break; 1042 1043 idx = c->prompt_index; 1044 if (idx != 0) 1045 idx--; 1046 1047 /* Find the word we are in. */ 1048 first = c->prompt_buffer + idx; 1049 while (first > c->prompt_buffer && *first != ' ') 1050 first--; 1051 while (*first == ' ') 1052 first++; 1053 last = c->prompt_buffer + idx; 1054 while (*last != '\0' && *last != ' ') 1055 last++; 1056 while (*last == ' ') 1057 last--; 1058 if (*last != '\0') 1059 last++; 1060 if (last <= first || 1061 ((size_t) (last - first)) > (sizeof word) - 1) 1062 break; 1063 memcpy(word, first, last - first); 1064 word[last - first] = '\0'; 1065 1066 /* And try to complete it. */ 1067 if ((s = status_prompt_complete(word)) == NULL) 1068 break; 1069 1070 /* Trim out word. */ 1071 n = size - (last - c->prompt_buffer) + 1; /* with \0 */ 1072 memmove(first, last, n); 1073 size -= last - first; 1074 1075 /* Insert the new word. */ 1076 size += strlen(s); 1077 off = first - c->prompt_buffer; 1078 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1); 1079 first = c->prompt_buffer + off; 1080 memmove(first + strlen(s), first, n); 1081 memcpy(first, s, strlen(s)); 1082 1083 c->prompt_index = (first - c->prompt_buffer) + strlen(s); 1084 free(s); 1085 1086 c->flags |= CLIENT_STATUS; 1087 break; 1088 case MODEKEYEDIT_BACKSPACE: 1089 if (c->prompt_index != 0) { 1090 if (c->prompt_index == size) 1091 c->prompt_buffer[--c->prompt_index] = '\0'; 1092 else { 1093 memmove(c->prompt_buffer + c->prompt_index - 1, 1094 c->prompt_buffer + c->prompt_index, 1095 size + 1 - c->prompt_index); 1096 c->prompt_index--; 1097 } 1098 c->flags |= CLIENT_STATUS; 1099 } 1100 break; 1101 case MODEKEYEDIT_DELETE: 1102 case MODEKEYEDIT_SWITCHMODESUBSTITUTE: 1103 if (c->prompt_index != size) { 1104 memmove(c->prompt_buffer + c->prompt_index, 1105 c->prompt_buffer + c->prompt_index + 1, 1106 size + 1 - c->prompt_index); 1107 c->flags |= CLIENT_STATUS; 1108 } 1109 break; 1110 case MODEKEYEDIT_DELETELINE: 1111 case MODEKEYEDIT_SWITCHMODESUBSTITUTELINE: 1112 *c->prompt_buffer = '\0'; 1113 c->prompt_index = 0; 1114 c->flags |= CLIENT_STATUS; 1115 break; 1116 case MODEKEYEDIT_DELETETOENDOFLINE: 1117 case MODEKEYEDIT_SWITCHMODECHANGELINE: 1118 if (c->prompt_index < size) { 1119 c->prompt_buffer[c->prompt_index] = '\0'; 1120 c->flags |= CLIENT_STATUS; 1121 } 1122 break; 1123 case MODEKEYEDIT_DELETEWORD: 1124 wsep = options_get_string(oo, "word-separators"); 1125 idx = c->prompt_index; 1126 1127 /* Find a non-separator. */ 1128 while (idx != 0) { 1129 idx--; 1130 if (!strchr(wsep, c->prompt_buffer[idx])) 1131 break; 1132 } 1133 1134 /* Find the separator at the beginning of the word. */ 1135 while (idx != 0) { 1136 idx--; 1137 if (strchr(wsep, c->prompt_buffer[idx])) { 1138 /* Go back to the word. */ 1139 idx++; 1140 break; 1141 } 1142 } 1143 1144 memmove(c->prompt_buffer + idx, 1145 c->prompt_buffer + c->prompt_index, 1146 size + 1 - c->prompt_index); 1147 memset(c->prompt_buffer + size - (c->prompt_index - idx), 1148 '\0', c->prompt_index - idx); 1149 c->prompt_index = idx; 1150 c->flags |= CLIENT_STATUS; 1151 break; 1152 case MODEKEYEDIT_NEXTSPACE: 1153 wsep = " "; 1154 /* FALLTHROUGH */ 1155 case MODEKEYEDIT_NEXTWORD: 1156 if (wsep == NULL) 1157 wsep = options_get_string(oo, "word-separators"); 1158 1159 /* Find a separator. */ 1160 while (c->prompt_index != size) { 1161 c->prompt_index++; 1162 if (strchr(wsep, c->prompt_buffer[c->prompt_index])) 1163 break; 1164 } 1165 1166 /* Find the word right after the separation. */ 1167 while (c->prompt_index != size) { 1168 c->prompt_index++; 1169 if (!strchr(wsep, c->prompt_buffer[c->prompt_index])) 1170 break; 1171 } 1172 1173 c->flags |= CLIENT_STATUS; 1174 break; 1175 case MODEKEYEDIT_NEXTSPACEEND: 1176 wsep = " "; 1177 /* FALLTHROUGH */ 1178 case MODEKEYEDIT_NEXTWORDEND: 1179 if (wsep == NULL) 1180 wsep = options_get_string(oo, "word-separators"); 1181 1182 /* Find a word. */ 1183 while (c->prompt_index != size) { 1184 c->prompt_index++; 1185 if (!strchr(wsep, c->prompt_buffer[c->prompt_index])) 1186 break; 1187 } 1188 1189 /* Find the separator at the end of the word. */ 1190 while (c->prompt_index != size) { 1191 c->prompt_index++; 1192 if (strchr(wsep, c->prompt_buffer[c->prompt_index])) 1193 break; 1194 } 1195 1196 c->flags |= CLIENT_STATUS; 1197 break; 1198 case MODEKEYEDIT_PREVIOUSSPACE: 1199 wsep = " "; 1200 /* FALLTHROUGH */ 1201 case MODEKEYEDIT_PREVIOUSWORD: 1202 if (wsep == NULL) 1203 wsep = options_get_string(oo, "word-separators"); 1204 1205 /* Find a non-separator. */ 1206 while (c->prompt_index != 0) { 1207 c->prompt_index--; 1208 if (!strchr(wsep, c->prompt_buffer[c->prompt_index])) 1209 break; 1210 } 1211 1212 /* Find the separator at the beginning of the word. */ 1213 while (c->prompt_index != 0) { 1214 c->prompt_index--; 1215 if (strchr(wsep, c->prompt_buffer[c->prompt_index])) { 1216 /* Go back to the word. */ 1217 c->prompt_index++; 1218 break; 1219 } 1220 } 1221 1222 c->flags |= CLIENT_STATUS; 1223 break; 1224 case MODEKEYEDIT_HISTORYUP: 1225 histstr = status_prompt_up_history(&c->prompt_hindex); 1226 if (histstr == NULL) 1227 break; 1228 free(c->prompt_buffer); 1229 c->prompt_buffer = xstrdup(histstr); 1230 c->prompt_index = strlen(c->prompt_buffer); 1231 c->flags |= CLIENT_STATUS; 1232 break; 1233 case MODEKEYEDIT_HISTORYDOWN: 1234 histstr = status_prompt_down_history(&c->prompt_hindex); 1235 if (histstr == NULL) 1236 break; 1237 free(c->prompt_buffer); 1238 c->prompt_buffer = xstrdup(histstr); 1239 c->prompt_index = strlen(c->prompt_buffer); 1240 c->flags |= CLIENT_STATUS; 1241 break; 1242 case MODEKEYEDIT_PASTE: 1243 if ((pb = paste_get_top(&global_buffers)) == NULL) 1244 break; 1245 for (n = 0; n < pb->size; n++) { 1246 ch = (u_char) pb->data[n]; 1247 if (ch < 32 || ch == 127) 1248 break; 1249 } 1250 1251 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1); 1252 if (c->prompt_index == size) { 1253 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n); 1254 c->prompt_index += n; 1255 c->prompt_buffer[c->prompt_index] = '\0'; 1256 } else { 1257 memmove(c->prompt_buffer + c->prompt_index + n, 1258 c->prompt_buffer + c->prompt_index, 1259 size + 1 - c->prompt_index); 1260 memcpy(c->prompt_buffer + c->prompt_index, pb->data, n); 1261 c->prompt_index += n; 1262 } 1263 1264 c->flags |= CLIENT_STATUS; 1265 break; 1266 case MODEKEYEDIT_TRANSPOSECHARS: 1267 idx = c->prompt_index; 1268 if (idx < size) 1269 idx++; 1270 if (idx >= 2) { 1271 swapc = c->prompt_buffer[idx - 2]; 1272 c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1]; 1273 c->prompt_buffer[idx - 1] = swapc; 1274 c->prompt_index = idx; 1275 c->flags |= CLIENT_STATUS; 1276 } 1277 break; 1278 case MODEKEYEDIT_ENTER: 1279 if (*c->prompt_buffer != '\0') 1280 status_prompt_add_history(c->prompt_buffer); 1281 if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0) 1282 status_prompt_clear(c); 1283 break; 1284 case MODEKEYEDIT_CANCEL: 1285 if (c->prompt_callbackfn(c->prompt_data, NULL) == 0) 1286 status_prompt_clear(c); 1287 break; 1288 case MODEKEY_OTHER: 1289 if ((key & 0xff00) != 0 || key < 32 || key == 127) 1290 break; 1291 c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2); 1292 1293 if (c->prompt_index == size) { 1294 c->prompt_buffer[c->prompt_index++] = key; 1295 c->prompt_buffer[c->prompt_index] = '\0'; 1296 } else { 1297 memmove(c->prompt_buffer + c->prompt_index + 1, 1298 c->prompt_buffer + c->prompt_index, 1299 size + 1 - c->prompt_index); 1300 c->prompt_buffer[c->prompt_index++] = key; 1301 } 1302 1303 if (c->prompt_flags & PROMPT_SINGLE) { 1304 if (c->prompt_callbackfn( 1305 c->prompt_data, c->prompt_buffer) == 0) 1306 status_prompt_clear(c); 1307 } 1308 1309 c->flags |= CLIENT_STATUS; 1310 break; 1311 default: 1312 break; 1313 } 1314} 1315 1316/* Get previous line from the history. */ 1317const char * 1318status_prompt_up_history(u_int *idx) 1319{ 1320 u_int size; 1321 1322 /* 1323 * History runs from 0 to size - 1. 1324 * 1325 * Index is from 0 to size. Zero is empty. 1326 */ 1327 1328 size = ARRAY_LENGTH(&status_prompt_history); 1329 if (size == 0 || *idx == size) 1330 return (NULL); 1331 (*idx)++; 1332 return (ARRAY_ITEM(&status_prompt_history, size - *idx)); 1333} 1334 1335/* Get next line from the history. */ 1336const char * 1337status_prompt_down_history(u_int *idx) 1338{ 1339 u_int size; 1340 1341 size = ARRAY_LENGTH(&status_prompt_history); 1342 if (size == 0 || *idx == 0) 1343 return (""); 1344 (*idx)--; 1345 if (*idx == 0) 1346 return (""); 1347 return (ARRAY_ITEM(&status_prompt_history, size - *idx)); 1348} 1349 1350/* Add line to the history. */ 1351void 1352status_prompt_add_history(const char *line) 1353{ 1354 u_int size; 1355 1356 size = ARRAY_LENGTH(&status_prompt_history); 1357 if (size > 0 && strcmp(ARRAY_LAST(&status_prompt_history), line) == 0) 1358 return; 1359 1360 if (size == PROMPT_HISTORY) { 1361 free(ARRAY_FIRST(&status_prompt_history)); 1362 ARRAY_REMOVE(&status_prompt_history, 0); 1363 } 1364 1365 ARRAY_ADD(&status_prompt_history, xstrdup(line)); 1366} 1367 1368/* Complete word. */ 1369char * 1370status_prompt_complete(const char *s) 1371{ 1372 const struct cmd_entry **cmdent; 1373 const struct options_table_entry *oe; 1374 ARRAY_DECL(, const char *) list; 1375 char *prefix, *s2; 1376 u_int i; 1377 size_t j; 1378 1379 if (*s == '\0') 1380 return (NULL); 1381 1382 /* First, build a list of all the possible matches. */ 1383 ARRAY_INIT(&list); 1384 for (cmdent = cmd_table; *cmdent != NULL; cmdent++) { 1385 if (strncmp((*cmdent)->name, s, strlen(s)) == 0) 1386 ARRAY_ADD(&list, (*cmdent)->name); 1387 } 1388 for (oe = server_options_table; oe->name != NULL; oe++) { 1389 if (strncmp(oe->name, s, strlen(s)) == 0) 1390 ARRAY_ADD(&list, oe->name); 1391 } 1392 for (oe = session_options_table; oe->name != NULL; oe++) { 1393 if (strncmp(oe->name, s, strlen(s)) == 0) 1394 ARRAY_ADD(&list, oe->name); 1395 } 1396 for (oe = window_options_table; oe->name != NULL; oe++) { 1397 if (strncmp(oe->name, s, strlen(s)) == 0) 1398 ARRAY_ADD(&list, oe->name); 1399 } 1400 1401 /* If none, bail now. */ 1402 if (ARRAY_LENGTH(&list) == 0) { 1403 ARRAY_FREE(&list); 1404 return (NULL); 1405 } 1406 1407 /* If an exact match, return it, with a trailing space. */ 1408 if (ARRAY_LENGTH(&list) == 1) { 1409 xasprintf(&s2, "%s ", ARRAY_FIRST(&list)); 1410 ARRAY_FREE(&list); 1411 return (s2); 1412 } 1413 1414 /* Now loop through the list and find the longest common prefix. */ 1415 prefix = xstrdup(ARRAY_FIRST(&list)); 1416 for (i = 1; i < ARRAY_LENGTH(&list); i++) { 1417 s = ARRAY_ITEM(&list, i); 1418 1419 j = strlen(s); 1420 if (j > strlen(prefix)) 1421 j = strlen(prefix); 1422 for (; j > 0; j--) { 1423 if (prefix[j - 1] != s[j - 1]) 1424 prefix[j - 1] = '\0'; 1425 } 1426 } 1427 1428 ARRAY_FREE(&list); 1429 return (prefix); 1430} 1431