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