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