1 /* $Header: /p/tcsh/cvsroot/tcsh/tw.parse.c,v 3.133 2011/04/14 14:33:05 christos Exp $ */ 2 /* 3 * tw.parse.c: Everyone has taken a shot in this futile effort to 4 * lexically analyze a csh line... Well we cannot good 5 * a job as good as sh.lex.c; but we try. Amazing that 6 * it works considering how many hands have touched this code 7 */ 8 /*- 9 * Copyright (c) 1980, 1991 The Regents of the University of California. 10 * All rights reserved. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions 14 * are met: 15 * 1. Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * 2. Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * 3. Neither the name of the University nor the names of its contributors 21 * may be used to endorse or promote products derived from this software 22 * without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 */ 36 #include "sh.h" 37 38 RCSID("$tcsh: tw.parse.c,v 3.133 2011/04/14 14:33:05 christos Exp $") 39 40 #include "tw.h" 41 #include "ed.h" 42 #include "tc.h" 43 44 #include <assert.h> 45 46 #ifdef WINNT_NATIVE 47 #include "nt.const.h" 48 #endif /* WINNT_NATIVE */ 49 #define EVEN(x) (((x) & 1) != 1) 50 51 #define DOT_NONE 0 /* Don't display dot files */ 52 #define DOT_NOT 1 /* Don't display dot or dot-dot */ 53 #define DOT_ALL 2 /* Display all dot files */ 54 55 /* TW_NONE, TW_COMMAND, TW_VARIABLE, TW_LOGNAME, */ 56 /* TW_FILE, TW_DIRECTORY, TW_VARLIST, TW_USER, */ 57 /* TW_COMPLETION, TW_ALIAS, TW_SHELLVAR, TW_ENVVAR, */ 58 /* TW_BINDING, TW_WORDLIST, TW_LIMIT, TW_SIGNAL */ 59 /* TW_JOB, TW_EXPLAIN, TW_TEXT, TW_GRPNAME */ 60 static void (*const tw_start_entry[]) (DIR *, const Char *) = { 61 tw_file_start, tw_cmd_start, tw_var_start, tw_logname_start, 62 tw_file_start, tw_file_start, tw_vl_start, tw_logname_start, 63 tw_complete_start, tw_alias_start, tw_var_start, tw_var_start, 64 tw_bind_start, tw_wl_start, tw_limit_start, tw_sig_start, 65 tw_job_start, tw_file_start, tw_file_start, tw_grpname_start 66 }; 67 68 static int (*const tw_next_entry[]) (struct Strbuf *, struct Strbuf *, 69 int *) = { 70 tw_file_next, tw_cmd_next, tw_var_next, tw_logname_next, 71 tw_file_next, tw_file_next, tw_var_next, tw_logname_next, 72 tw_var_next, tw_var_next, tw_shvar_next, tw_envvar_next, 73 tw_bind_next, tw_wl_next, tw_limit_next, tw_sig_next, 74 tw_job_next, tw_file_next, tw_file_next, tw_grpname_next 75 }; 76 77 static void (*const tw_end_entry[]) (void) = { 78 tw_dir_end, tw_dir_end, tw_dir_end, tw_logname_end, 79 tw_dir_end, tw_dir_end, tw_dir_end, tw_logname_end, 80 tw_dir_end, tw_dir_end, tw_dir_end, tw_dir_end, 81 tw_dir_end, tw_dir_end, tw_dir_end, tw_dir_end, 82 tw_dir_end, tw_dir_end, tw_dir_end, tw_grpname_end 83 }; 84 85 /* #define TDEBUG */ 86 87 /* Set to TRUE if recexact is set and an exact match is found 88 * along with other, longer, matches. 89 */ 90 91 int curchoice = -1; 92 93 int match_unique_match = FALSE; 94 int non_unique_match = FALSE; 95 static int SearchNoDirErr = 0; /* t_search returns -2 if dir is unreadable */ 96 97 /* state so if a completion is interrupted, the input line doesn't get 98 nuked */ 99 int InsideCompletion = 0; 100 101 /* do the expand or list on the command line -- SHOULD BE REPLACED */ 102 103 static void extract_dir_and_name (const Char *, struct Strbuf *, 104 Char **); 105 static int insert_meta (const Char *, const Char *, 106 const Char *, int); 107 static int tilde (struct Strbuf *, Char *); 108 static int expand_dir (const Char *, struct Strbuf *, DIR **, 109 COMMAND); 110 static int nostat (Char *); 111 static Char filetype (Char *, Char *); 112 static int t_glob (Char ***, int); 113 static int c_glob (Char ***); 114 static int is_prefix (Char *, Char *); 115 static int is_prefixmatch (Char *, Char *, int); 116 static int is_suffix (Char *, Char *); 117 static int recognize (struct Strbuf *, const Char *, size_t, 118 int, int, int); 119 static int ignored (Char *); 120 static int isadirectory (const Char *, const Char *); 121 static int tw_collect_items (COMMAND, int, struct Strbuf *, 122 struct Strbuf *, Char *, const Char *, 123 int); 124 static int tw_collect (COMMAND, int, struct Strbuf *, 125 struct Strbuf *, Char *, Char *, int, 126 DIR *); 127 static Char tw_suffix (int, struct Strbuf *,const Char *, 128 Char *); 129 static void tw_fixword (int, struct Strbuf *, Char *, Char *); 130 static void tw_list_items (int, int, int); 131 static void add_scroll_tab (Char *); 132 static void choose_scroll_tab (struct Strbuf *, int); 133 static void free_scroll_tab (void); 134 static int find_rows (Char *[], int, int); 135 136 #ifdef notdef 137 /* 138 * If we find a set command, then we break a=b to a= and word becomes 139 * b else, we don't break a=b. [don't use that; splits words badly and 140 * messes up tw_complete()] 141 */ 142 #define isaset(c, w) ((w)[-1] == '=' && \ 143 ((c)[0] == 's' && (c)[1] == 'e' && (c)[2] == 't' && \ 144 ((c[3] == ' ' || (c)[3] == '\t')))) 145 #endif 146 147 /* TRUE if character must be quoted */ 148 #define tricky(w) (cmap(w, _META | _DOL | _QF | _QB | _ESC | _GLOB) && w != '#') 149 /* TRUE if double quotes don't protect character */ 150 #define tricky_dq(w) (cmap(w, _DOL | _QB)) 151 152 /* tenematch(): 153 * Return: 154 * > 1: No. of items found 155 * = 1: Exactly one match / spelling corrected 156 * = 0: No match / spelling was correct 157 * < 0: Error (incl spelling correction impossible) 158 */ 159 int 160 tenematch(Char *inputline, int num_read, COMMAND command) 161 { 162 struct Strbuf qline = Strbuf_INIT; 163 Char qu = 0, *pat = STRNULL; 164 size_t wp, word, wordp, cmd_start, oword = 0, ocmd_start = 0; 165 Char *str_end, *cp; 166 Char *word_start; 167 Char *oword_start = NULL; 168 eChar suf = 0; 169 int looking; /* what we are looking for */ 170 int search_ret; /* what search returned for debugging */ 171 int backq = 0; 172 173 str_end = &inputline[num_read]; 174 cleanup_push(&qline, Strbuf_cleanup); 175 176 word_start = inputline; 177 word = cmd_start = 0; 178 for (cp = inputline; cp < str_end; cp++) { 179 if (!cmap(qu, _ESC)) { 180 if (cmap(*cp, _QF|_ESC)) { 181 if (qu == 0 || qu == *cp) { 182 qu ^= *cp; 183 continue; 184 } 185 } 186 if (qu != '\'' && cmap(*cp, _QB)) { 187 if ((backq ^= 1) != 0) { 188 ocmd_start = cmd_start; 189 oword_start = word_start; 190 oword = word; 191 word_start = cp + 1; 192 word = cmd_start = qline.len + 1; 193 } 194 else { 195 cmd_start = ocmd_start; 196 word_start = oword_start; 197 word = oword; 198 } 199 Strbuf_append1(&qline, *cp); 200 continue; 201 } 202 } 203 if (iscmdmeta(*cp)) 204 cmd_start = qline.len + 1; 205 206 /* Don't quote '/' to make the recognize stuff work easily */ 207 /* Don't quote '$' in double quotes */ 208 209 if (cmap(*cp, _ESC) && cp < str_end - 1 && cp[1] == HIST && 210 HIST != '\0') 211 Strbuf_append1(&qline, *++cp | QUOTE); 212 else if (qu && (tricky(*cp) || *cp == '~') && 213 !(qu == '\"' && tricky_dq(*cp))) 214 Strbuf_append1(&qline, *cp | QUOTE); 215 else 216 Strbuf_append1(&qline, *cp); 217 if (ismetahash(qline.s[qline.len - 1]) 218 /* || isaset(qline.s + cmd_start, qline.s + qline.len) */) 219 word = qline.len, word_start = cp + 1; 220 if (cmap(qu, _ESC)) 221 qu = 0; 222 } 223 Strbuf_terminate(&qline); 224 wp = qline.len; 225 226 /* 227 * SPECIAL HARDCODED COMPLETIONS: 228 * first word of command -> TW_COMMAND 229 * everything else -> TW_ZERO 230 * 231 */ 232 looking = starting_a_command(qline.s + word - 1, qline.s) ? 233 TW_COMMAND : TW_ZERO; 234 235 wordp = word; 236 237 #ifdef TDEBUG 238 { 239 const Char *p; 240 241 xprintf(CGETS(30, 1, "starting_a_command %d\n"), looking); 242 xprintf("\ncmd_start:%S:\n", qline.s + cmd_start); 243 xprintf("qline:%S:\n", qline.s); 244 xprintf("qline:"); 245 for (p = qline.s; *p; p++) 246 xprintf("%c", *p & QUOTE ? '-' : ' '); 247 xprintf(":\n"); 248 xprintf("word:%S:\n", qline.s + word); 249 xprintf("word:"); 250 for (p = qline.s + word; *p; p++) 251 xprintf("%c", *p & QUOTE ? '-' : ' '); 252 xprintf(":\n"); 253 } 254 #endif 255 256 if ((looking == TW_COMMAND || looking == TW_ZERO) && 257 (command == RECOGNIZE || command == LIST || command == SPELL || 258 command == RECOGNIZE_SCROLL)) { 259 Char *p; 260 261 #ifdef TDEBUG 262 xprintf(CGETS(30, 2, "complete %d "), looking); 263 #endif 264 p = qline.s + wordp; 265 looking = tw_complete(qline.s + cmd_start, &p, &pat, looking, &suf); 266 wordp = p - qline.s; 267 #ifdef TDEBUG 268 xprintf(CGETS(30, 3, "complete %d %S\n"), looking, pat); 269 #endif 270 } 271 272 switch (command) { 273 Char *bptr; 274 Char *items[2], **ptr; 275 int i, count; 276 277 case RECOGNIZE: 278 case RECOGNIZE_SCROLL: 279 case RECOGNIZE_ALL: { 280 struct Strbuf wordbuf = Strbuf_INIT; 281 Char *slshp; 282 283 if (adrof(STRautocorrect)) { 284 if ((slshp = Strrchr(qline.s + wordp, '/')) != NULL && 285 slshp[1] != '\0') { 286 SearchNoDirErr = 1; 287 for (bptr = qline.s + wordp; bptr < slshp; bptr++) { 288 /* 289 * do not try to correct spelling of words containing 290 * globbing characters 291 */ 292 if (isglob(*bptr)) { 293 SearchNoDirErr = 0; 294 break; 295 } 296 } 297 } 298 } 299 else 300 slshp = STRNULL; 301 Strbuf_append(&wordbuf, qline.s + wordp); 302 Strbuf_terminate(&wordbuf); 303 cleanup_push(&wordbuf, Strbuf_cleanup); 304 search_ret = t_search(&wordbuf, command, looking, 1, pat, suf); 305 qline.len = wordp; 306 Strbuf_append(&qline, wordbuf.s); 307 Strbuf_terminate(&qline); 308 cleanup_until(&wordbuf); 309 SearchNoDirErr = 0; 310 311 if (search_ret == -2) { 312 Char *rword; 313 314 rword = Strsave(slshp); 315 cleanup_push(rword, xfree); 316 if (slshp != STRNULL) 317 *slshp = '\0'; 318 wordbuf = Strbuf_init; 319 Strbuf_append(&wordbuf, qline.s + wordp); 320 Strbuf_terminate(&wordbuf); 321 cleanup_push(&wordbuf, Strbuf_cleanup); 322 search_ret = spell_me(&wordbuf, looking, pat, suf); 323 if (search_ret == 1) { 324 Strbuf_append(&wordbuf, rword); 325 Strbuf_terminate(&wordbuf); 326 wp = wordp + wordbuf.len; 327 search_ret = t_search(&wordbuf, command, looking, 1, pat, suf); 328 } 329 qline.len = wordp; 330 Strbuf_append(&qline, wordbuf.s); 331 Strbuf_terminate(&qline); 332 cleanup_until(rword); 333 } 334 if (qline.s[wp] != '\0' && 335 insert_meta(word_start, str_end, qline.s + word, !qu) < 0) 336 goto err; /* error inserting */ 337 break; 338 } 339 340 case SPELL: { 341 struct Strbuf wordbuf = Strbuf_INIT; 342 343 for (bptr = word_start; bptr < str_end; bptr++) { 344 /* 345 * do not try to correct spelling of words containing globbing 346 * characters 347 */ 348 if (isglob(*bptr)) { 349 search_ret = 0; 350 goto end; 351 } 352 } 353 Strbuf_append(&wordbuf, qline.s + wordp); 354 Strbuf_terminate(&wordbuf); 355 cleanup_push(&wordbuf, Strbuf_cleanup); 356 357 /* 358 * Don't try to spell things that we know they are correct. 359 * Trying to spell can hang when we have NFS mounted hung 360 * volumes. 361 */ 362 if ((looking == TW_COMMAND) && Strchr(wordbuf.s, '/') != NULL) { 363 if (executable(NULL, wordbuf.s, 0)) { 364 cleanup_until(&wordbuf); 365 search_ret = 0; 366 goto end; 367 } 368 } 369 370 search_ret = spell_me(&wordbuf, looking, pat, suf); 371 qline.len = wordp; 372 Strbuf_append(&qline, wordbuf.s); 373 Strbuf_terminate(&qline); 374 cleanup_until(&wordbuf); 375 if (search_ret == 1) { 376 if (insert_meta(word_start, str_end, qline.s + word, !qu) < 0) 377 goto err; /* error inserting */ 378 } 379 break; 380 } 381 382 case PRINT_HELP: 383 do_help(qline.s + cmd_start); 384 search_ret = 1; 385 break; 386 387 case GLOB: 388 case GLOB_EXPAND: 389 items[0] = Strsave(qline.s + wordp); 390 items[1] = NULL; 391 cleanup_push(items[0], xfree); 392 ptr = items; 393 count = (looking == TW_COMMAND && Strchr(qline.s + wordp, '/') == 0) ? 394 c_glob(&ptr) : 395 t_glob(&ptr, looking == TW_COMMAND); 396 cleanup_until(items[0]); 397 if (ptr != items) 398 cleanup_push(ptr, blk_cleanup); 399 if (count > 0) { 400 if (command == GLOB) 401 print_by_column(STRNULL, ptr, count, 0); 402 else { 403 DeleteBack(str_end - word_start);/* get rid of old word */ 404 for (i = 0; i < count; i++) 405 if (ptr[i] && *ptr[i]) { 406 (void) quote(ptr[i]); 407 if (insert_meta(0, 0, ptr[i], 0) < 0 || 408 InsertStr(STRspace) < 0) { 409 if (ptr != items) 410 cleanup_until(ptr); 411 goto err; /* error inserting */ 412 } 413 } 414 } 415 } 416 if (ptr != items) 417 cleanup_until(ptr); 418 search_ret = count; 419 break; 420 421 case VARS_EXPAND: 422 bptr = dollar(qline.s + word); 423 if (bptr != NULL) { 424 if (insert_meta(word_start, str_end, bptr, !qu) < 0) { 425 xfree(bptr); 426 goto err; /* error inserting */ 427 } 428 xfree(bptr); 429 search_ret = 1; 430 break; 431 } 432 search_ret = 0; 433 break; 434 435 case PATH_NORMALIZE: 436 if ((bptr = dnormalize(qline.s + wordp, symlinks == SYM_IGNORE || 437 symlinks == SYM_EXPAND)) != NULL) { 438 if (insert_meta(word_start, str_end, bptr, !qu) < 0) { 439 xfree(bptr); 440 goto err; /* error inserting */ 441 } 442 xfree(bptr); 443 search_ret = 1; 444 break; 445 } 446 search_ret = 0; 447 break; 448 449 case COMMAND_NORMALIZE: { 450 Char *p; 451 int found; 452 453 found = cmd_expand(qline.s + wordp, &p); 454 455 if (!found) { 456 xfree(p); 457 search_ret = 0; 458 break; 459 } 460 if (insert_meta(word_start, str_end, p, !qu) < 0) { 461 xfree(p); 462 goto err; /* error inserting */ 463 } 464 xfree(p); 465 search_ret = 1; 466 break; 467 } 468 469 case LIST: 470 case LIST_ALL: { 471 struct Strbuf wordbuf = Strbuf_INIT; 472 473 Strbuf_append(&wordbuf, qline.s + wordp); 474 Strbuf_terminate(&wordbuf); 475 cleanup_push(&wordbuf, Strbuf_cleanup); 476 search_ret = t_search(&wordbuf, LIST, looking, 1, pat, suf); 477 qline.len = wordp; 478 Strbuf_append(&qline, wordbuf.s); 479 Strbuf_terminate(&qline); 480 cleanup_until(&wordbuf); 481 break; 482 } 483 484 default: 485 xprintf(CGETS(30, 4, "%s: Internal match error.\n"), progname); 486 search_ret = 1; 487 } 488 end: 489 cleanup_until(&qline); 490 return search_ret; 491 492 err: 493 cleanup_until(&qline); 494 return -1; 495 } /* end tenematch */ 496 497 498 /* t_glob(): 499 * Return a list of files that match the pattern 500 */ 501 static int 502 t_glob(Char ***v, int cmd) 503 { 504 jmp_buf_t osetexit; 505 int gflag; 506 507 if (**v == 0) 508 return (0); 509 gflag = tglob(*v); 510 if (gflag) { 511 size_t omark; 512 513 getexit(osetexit); /* make sure to come back here */ 514 omark = cleanup_push_mark(); 515 if (setexit() == 0) 516 *v = globall(*v, gflag); 517 cleanup_pop_mark(omark); 518 resexit(osetexit); 519 if (haderr) { 520 haderr = 0; 521 NeedsRedraw = 1; 522 return (-1); 523 } 524 if (*v == 0) 525 return (0); 526 } 527 else 528 return (0); 529 530 if (cmd) { 531 Char **av = *v, *p; 532 int fwd, i; 533 534 for (i = 0, fwd = 0; av[i] != NULL; i++) 535 if (!executable(NULL, av[i], 0)) { 536 fwd++; 537 p = av[i]; 538 av[i] = NULL; 539 xfree(p); 540 } 541 else if (fwd) 542 av[i - fwd] = av[i]; 543 544 if (fwd) 545 av[i - fwd] = av[i]; 546 } 547 548 return blklen(*v); 549 } /* end t_glob */ 550 551 552 /* c_glob(): 553 * Return a list of commands that match the pattern 554 */ 555 static int 556 c_glob(Char ***v) 557 { 558 struct blk_buf av = BLK_BUF_INIT; 559 struct Strbuf cmd = Strbuf_INIT, dir = Strbuf_INIT; 560 Char *pat = **v; 561 int flag; 562 563 if (pat == NULL) 564 return (0); 565 566 cleanup_push(&av, bb_cleanup); 567 cleanup_push(&cmd, Strbuf_cleanup); 568 cleanup_push(&dir, Strbuf_cleanup); 569 570 tw_cmd_start(NULL, NULL); 571 while (cmd.len = 0, tw_cmd_next(&cmd, &dir, &flag) != 0) { 572 Strbuf_terminate(&cmd); 573 if (Gmatch(cmd.s, pat)) 574 bb_append(&av, Strsave(cmd.s)); 575 } 576 tw_dir_end(); 577 *v = bb_finish(&av); 578 cleanup_ignore(&av); 579 cleanup_until(&av); 580 581 return av.len; 582 } /* end c_glob */ 583 584 585 /* insert_meta(): 586 * change the word before the cursor. 587 * cp must point to the start of the unquoted word. 588 * cpend to the end of it. 589 * word is the text that has to be substituted. 590 * strategy: 591 * try to keep all the quote characters of the user's input. 592 * change quote type only if necessary. 593 */ 594 static int 595 insert_meta(const Char *cp, const Char *cpend, const Char *word, 596 int closequotes) 597 { 598 struct Strbuf buffer = Strbuf_INIT; 599 Char *bptr; 600 const Char *wptr; 601 int in_sync = (cp != NULL); 602 Char qu = 0; 603 int ndel = (int) (cp ? cpend - cp : 0); 604 Char w, wq; 605 int res; 606 607 for (wptr = word;;) { 608 if (cp >= cpend) 609 in_sync = 0; 610 if (in_sync && !cmap(qu, _ESC) && cmap(*cp, _QF|_ESC)) 611 if (qu == 0 || qu == *cp) { 612 qu ^= *cp; 613 Strbuf_append1(&buffer, *cp++); 614 continue; 615 } 616 w = *wptr; 617 if (w == 0) 618 break; 619 620 wq = w & QUOTE; 621 w &= ~QUOTE; 622 623 if (cmap(w, _ESC | _QF)) 624 wq = QUOTE; /* quotes are always quoted */ 625 626 if (!wq && qu && tricky(w) && !(qu == '\"' && tricky_dq(w))) { 627 /* We have to unquote the character */ 628 in_sync = 0; 629 if (cmap(qu, _ESC)) 630 buffer.s[buffer.len - 1] = w; 631 else { 632 Strbuf_append1(&buffer, qu); 633 Strbuf_append1(&buffer, w); 634 if (wptr[1] == 0) 635 qu = 0; 636 else 637 Strbuf_append1(&buffer, qu); 638 } 639 } else if (qu && w == qu) { 640 in_sync = 0; 641 if (buffer.len != 0 && buffer.s[buffer.len - 1] == qu) { 642 /* User misunderstanding :) */ 643 buffer.s[buffer.len - 1] = '\\'; 644 Strbuf_append1(&buffer, w); 645 qu = 0; 646 } else { 647 Strbuf_append1(&buffer, qu); 648 Strbuf_append1(&buffer, '\\'); 649 Strbuf_append1(&buffer, w); 650 Strbuf_append1(&buffer, qu); 651 } 652 } 653 else if (wq && qu == '\"' && tricky_dq(w)) { 654 in_sync = 0; 655 Strbuf_append1(&buffer, qu); 656 Strbuf_append1(&buffer, '\\'); 657 Strbuf_append1(&buffer, w); 658 Strbuf_append1(&buffer, qu); 659 } else if (wq && 660 ((!qu && (tricky(w) || (w == HISTSUB && HISTSUB != '\0' 661 && buffer.len == 0))) || 662 (!cmap(qu, _ESC) && w == HIST && HIST != '\0'))) { 663 in_sync = 0; 664 Strbuf_append1(&buffer, '\\'); 665 Strbuf_append1(&buffer, w); 666 } else { 667 if (in_sync && *cp++ != w) 668 in_sync = 0; 669 Strbuf_append1(&buffer, w); 670 } 671 wptr++; 672 if (cmap(qu, _ESC)) 673 qu = 0; 674 } 675 if (closequotes && qu && !cmap(qu, _ESC)) 676 Strbuf_append1(&buffer, w); 677 bptr = Strbuf_finish(&buffer); 678 if (ndel) 679 DeleteBack(ndel); 680 res = InsertStr(bptr); 681 xfree(bptr); 682 return res; 683 } /* end insert_meta */ 684 685 686 687 /* is_prefix(): 688 * return true if check matches initial chars in template 689 * This differs from PWB imatch in that if check is null 690 * it matches anything 691 */ 692 static int 693 is_prefix(Char *check, Char *template) 694 { 695 for (; *check; check++, template++) 696 if ((*check & TRIM) != (*template & TRIM)) 697 return (FALSE); 698 return (TRUE); 699 } /* end is_prefix */ 700 701 702 /* is_prefixmatch(): 703 * return true if check matches initial chars in template 704 * This differs from PWB imatch in that if check is null 705 * it matches anything 706 * and matches on shortening of commands 707 */ 708 static int 709 is_prefixmatch(Char *check, Char *template, int enhanced) 710 { 711 Char MCH1, MCH2, LCH1, LCH2; 712 713 for (; *check; check++, template++) { 714 if ((*check & TRIM) != (*template & TRIM)) { 715 MCH1 = (*check & TRIM); 716 MCH2 = (*template & TRIM); 717 LCH1 = Isupper(MCH1) ? Tolower(MCH1) : 718 enhanced == 2 && MCH1 == '_' ? '-' : MCH1; 719 LCH2 = Isupper(MCH2) ? Tolower(MCH2) : 720 enhanced == 2 && MCH2 == '_' ? '-' : MCH2; 721 if (MCH1 != MCH2 && MCH1 != LCH2 && 722 (LCH1 != MCH2 || enhanced == 2)) { 723 if (enhanced && ((*check & TRIM) == '-' || 724 (*check & TRIM) == '.' || 725 (*check & TRIM) == '_')) { 726 MCH1 = MCH2 = (*check & TRIM); 727 if (MCH1 == '_' && enhanced != 2) { 728 MCH2 = '-'; 729 } else if (MCH1 == '-') { 730 MCH2 = '_'; 731 } 732 for (; *template && (*template & TRIM) != MCH1 && 733 (*template & TRIM) != MCH2; template++) 734 continue; 735 if (!*template) { 736 return (FALSE); 737 } 738 } else { 739 return (FALSE); 740 } 741 } 742 } 743 } 744 return (TRUE); 745 } /* end is_prefixmatch */ 746 747 748 /* is_suffix(): 749 * Return true if the chars in template appear at the 750 * end of check, I.e., are it's suffix. 751 */ 752 static int 753 is_suffix(Char *check, Char *template) 754 { 755 Char *t, *c; 756 757 t = Strend(template); 758 c = Strend(check); 759 for (;;) { 760 if (t == template) 761 return 1; 762 if (c == check || (*--t & TRIM) != (*--c & TRIM)) 763 return 0; 764 } 765 } /* end is_suffix */ 766 767 768 /* ignored(): 769 * Return true if this is an ignored item 770 */ 771 static int 772 ignored(Char *item) 773 { 774 struct varent *vp; 775 Char **cp; 776 777 if ((vp = adrof(STRfignore)) == NULL || (cp = vp->vec) == NULL) 778 return (FALSE); 779 for (; *cp != NULL; cp++) 780 if (is_suffix(item, *cp)) 781 return (TRUE); 782 return (FALSE); 783 } /* end ignored */ 784 785 786 787 /* starting_a_command(): 788 * return true if the command starting at wordstart is a command 789 */ 790 int 791 starting_a_command(Char *wordstart, Char *inputline) 792 { 793 Char *ptr, *ncmdstart; 794 int count, bsl; 795 static Char 796 cmdstart[] = {'`', ';', '&', '(', '|', '\0'}, 797 cmdalive[] = {' ', '\t', '\'', '"', '<', '>', '\0'}; 798 799 /* 800 * Find if the number of backquotes is odd or even. 801 */ 802 for (ptr = wordstart, count = 0; 803 ptr >= inputline; 804 count += (*ptr-- == '`')) 805 continue; 806 /* 807 * if the number of backquotes is even don't include the backquote char in 808 * the list of command starting delimiters [if it is zero, then it does not 809 * matter] 810 */ 811 ncmdstart = cmdstart + EVEN(count); 812 813 /* 814 * look for the characters previous to this word if we find a command 815 * starting delimiter we break. if we find whitespace and another previous 816 * word then we are not a command 817 * 818 * count is our state machine: 0 looking for anything 1 found white-space 819 * looking for non-ws 820 */ 821 for (count = 0; wordstart >= inputline; wordstart--) { 822 if (*wordstart == '\0') 823 continue; 824 if (Strchr(ncmdstart, *wordstart)) { 825 for (ptr = wordstart, bsl = 0; *(--ptr) == '\\'; bsl++); 826 if (bsl & 1) { 827 wordstart--; 828 continue; 829 } else 830 break; 831 } 832 /* 833 * found white space 834 */ 835 if ((ptr = Strchr(cmdalive, *wordstart)) != NULL) 836 count = 1; 837 if (count == 1 && !ptr) 838 return (FALSE); 839 } 840 841 if (wordstart > inputline) 842 switch (*wordstart) { 843 case '&': /* Look for >& */ 844 while (wordstart > inputline && 845 (*--wordstart == ' ' || *wordstart == '\t')) 846 continue; 847 if (*wordstart == '>') 848 return (FALSE); 849 break; 850 case '(': /* check for foreach, if etc. */ 851 while (wordstart > inputline && 852 (*--wordstart == ' ' || *wordstart == '\t')) 853 continue; 854 if (!iscmdmeta(*wordstart) && 855 (*wordstart != ' ' && *wordstart != '\t')) 856 return (FALSE); 857 break; 858 default: 859 break; 860 } 861 return (TRUE); 862 } /* end starting_a_command */ 863 864 865 /* recognize(): 866 * Object: extend what user typed up to an ambiguity. 867 * Algorithm: 868 * On first match, copy full item (assume it'll be the only match) 869 * On subsequent matches, shorten exp_name to the first 870 * character mismatch between exp_name and item. 871 * If we shorten it back to the prefix length, stop searching. 872 */ 873 static int 874 recognize(struct Strbuf *exp_name, const Char *item, size_t name_length, 875 int numitems, int enhanced, int igncase) 876 { 877 Char MCH1, MCH2, LCH1, LCH2; 878 Char *x; 879 const Char *ent; 880 size_t len = 0; 881 882 if (numitems == 1) { /* 1st match */ 883 exp_name->len = 0; 884 Strbuf_append(exp_name, item); 885 Strbuf_terminate(exp_name); 886 return (0); 887 } 888 if (!enhanced && !igncase) { 889 for (x = exp_name->s, ent = item; *x && (*x & TRIM) == (*ent & TRIM); 890 x++, ent++) 891 len++; 892 } else { 893 for (x = exp_name->s, ent = item; *x; x++, ent++) { 894 MCH1 = *x & TRIM; 895 MCH2 = *ent & TRIM; 896 LCH1 = Isupper(MCH1) ? Tolower(MCH1) : MCH1; 897 LCH2 = Isupper(MCH2) ? Tolower(MCH2) : MCH2; 898 if (MCH1 != MCH2) { 899 if (LCH1 == MCH2 || (MCH1 == '_' && MCH2 == '-')) 900 *x = *ent; 901 else if (LCH1 != LCH2) 902 break; 903 } 904 len++; 905 } 906 } 907 *x = '\0'; /* Shorten at 1st char diff */ 908 exp_name->len = x - exp_name->s; 909 if (!(match_unique_match || is_set(STRrecexact) || (enhanced && *ent)) && 910 len == name_length) /* Ambiguous to prefix? */ 911 return (-1); /* So stop now and save time */ 912 return (0); 913 } /* end recognize */ 914 915 916 /* tw_collect_items(): 917 * Collect items that match target. 918 * SPELL command: 919 * Returns the spelling distance of the closest match. 920 * else 921 * Returns the number of items found. 922 * If none found, but some ignored items were found, 923 * It returns the -number of ignored items. 924 */ 925 static int 926 tw_collect_items(COMMAND command, int looking, struct Strbuf *exp_dir, 927 struct Strbuf *exp_name, Char *target, const Char *pat, 928 int flags) 929 { 930 int done = FALSE; /* Search is done */ 931 int showdots; /* Style to show dot files */ 932 int nignored = 0; /* Number of fignored items */ 933 int numitems = 0; /* Number of matched items */ 934 size_t name_length = Strlen(target); /* Length of prefix (file name) */ 935 int exec_check = flags & TW_EXEC_CHK;/* need to check executability */ 936 int dir_check = flags & TW_DIR_CHK; /* Need to check for directories */ 937 int text_check = flags & TW_TEXT_CHK;/* Need to check for non-directories */ 938 int dir_ok = flags & TW_DIR_OK; /* Ignore directories? */ 939 int gpat = flags & TW_PAT_OK; /* Match against a pattern */ 940 int ignoring = flags & TW_IGN_OK; /* Use fignore? */ 941 int d = 4, nd; /* Spelling distance */ 942 Char **cp; 943 Char *ptr; 944 struct varent *vp; 945 struct Strbuf buf = Strbuf_INIT, item = Strbuf_INIT; 946 int enhanced = 0; 947 int cnt = 0; 948 int igncase = 0; 949 950 951 flags = 0; 952 953 showdots = DOT_NONE; 954 if ((ptr = varval(STRlistflags)) != STRNULL) 955 while (*ptr) 956 switch (*ptr++) { 957 case 'a': 958 showdots = DOT_ALL; 959 break; 960 case 'A': 961 showdots = DOT_NOT; 962 break; 963 default: 964 break; 965 } 966 967 if (looking == TW_COMMAND 968 && (vp = adrof(STRautorehash)) != NULL && vp->vec != NULL) 969 for (cp = vp->vec; *cp; cp++) 970 if (Strcmp(*cp, STRalways) == 0 971 || (Strcmp(*cp, STRcorrect) == 0 && command == SPELL) 972 || (Strcmp(*cp, STRcomplete) == 0 && command != SPELL)) { 973 tw_cmd_free(); 974 tw_cmd_start(NULL, NULL); 975 break; 976 } 977 978 cleanup_push(&item, Strbuf_cleanup); 979 cleanup_push(&buf, Strbuf_cleanup); 980 while (!done && 981 (item.len = 0, 982 tw_next_entry[looking](&item, exp_dir, &flags) != 0)) { 983 Strbuf_terminate(&item); 984 #ifdef TDEBUG 985 xprintf("item = %S\n", item.s); 986 #endif 987 switch (looking) { 988 case TW_FILE: 989 case TW_DIRECTORY: 990 case TW_TEXT: 991 /* 992 * Don't match . files on null prefix match 993 */ 994 if (showdots == DOT_NOT && (ISDOT(item.s) || ISDOTDOT(item.s))) 995 done = TRUE; 996 if (name_length == 0 && item.s[0] == '.' && showdots == DOT_NONE) 997 done = TRUE; 998 break; 999 1000 case TW_COMMAND: 1001 #if defined(_UWIN) || defined(__CYGWIN__) 1002 /* 1003 * Turn foo.{exe,com,bat,cmd} into foo since UWIN's readdir returns 1004 * the file with the .exe, .com, .bat, .cmd extension 1005 * 1006 * Same for Cygwin, but only for .exe and .com extension. 1007 */ 1008 { 1009 #ifdef __CYGWIN__ 1010 static const char *rext[] = { ".exe", ".com" }; 1011 #else 1012 static const char *rext[] = { ".exe", ".bat", ".com", ".cmd" }; 1013 #endif 1014 size_t exti = Strlen(item.s); 1015 1016 if (exti > 4) { 1017 char *ext = short2str(&item.s[exti -= 4]); 1018 size_t i; 1019 1020 for (i = 0; i < sizeof(rext) / sizeof(rext[0]); i++) 1021 if (strcasecmp(ext, rext[i]) == 0) { 1022 item.len = exti; 1023 Strbuf_terminate(&item); 1024 break; 1025 } 1026 } 1027 } 1028 #endif /* _UWIN || __CYGWIN__ */ 1029 exec_check = flags & TW_EXEC_CHK; 1030 dir_ok = flags & TW_DIR_OK; 1031 break; 1032 1033 default: 1034 break; 1035 } 1036 1037 if (done) { 1038 done = FALSE; 1039 continue; 1040 } 1041 1042 switch (command) { 1043 1044 case SPELL: /* correct the spelling of the last bit */ 1045 if (name_length == 0) {/* zero-length word can't be misspelled */ 1046 exp_name->len = 0; /* (not trying is important for ~) */ 1047 Strbuf_terminate(exp_name); 1048 d = 0; 1049 done = TRUE; 1050 break; 1051 } 1052 if (gpat && !Gmatch(item.s, pat)) 1053 break; 1054 /* 1055 * Swapped the order of the spdist() arguments as suggested 1056 * by eeide@asylum.cs.utah.edu (Eric Eide) 1057 */ 1058 nd = spdist(target, item.s); /* test the item against original */ 1059 if (nd <= d && nd != 4) { 1060 if (!(exec_check && !executable(exp_dir->s, item.s, dir_ok))) { 1061 exp_name->len = 0; 1062 Strbuf_append(exp_name, item.s); 1063 Strbuf_terminate(exp_name); 1064 d = nd; 1065 if (d == 0) /* if found it exactly */ 1066 done = TRUE; 1067 } 1068 } 1069 else if (nd == 4) { 1070 if (spdir(exp_name, exp_dir->s, item.s, target)) { 1071 if (exec_check && 1072 !executable(exp_dir->s, exp_name->s, dir_ok)) 1073 break; 1074 #ifdef notdef 1075 /* 1076 * We don't want to stop immediately, because 1077 * we might find an exact/better match later. 1078 */ 1079 d = 0; 1080 done = TRUE; 1081 #endif 1082 d = 3; 1083 } 1084 } 1085 break; 1086 1087 case LIST: 1088 case RECOGNIZE: 1089 case RECOGNIZE_ALL: 1090 case RECOGNIZE_SCROLL: 1091 1092 if ((vp = adrof(STRcomplete)) != NULL && vp->vec != NULL) 1093 for (cp = vp->vec; *cp; cp++) { 1094 if (Strcmp(*cp, STREnhance) == 0) 1095 enhanced = 2; 1096 else if (Strcmp(*cp, STRigncase) == 0) 1097 igncase = 1; 1098 else if (Strcmp(*cp, STRenhance) == 0) 1099 enhanced = 1; 1100 } 1101 1102 if (enhanced || igncase) { 1103 if (!is_prefixmatch(target, item.s, enhanced)) 1104 break; 1105 } else { 1106 if (!is_prefix(target, item.s)) 1107 break; 1108 } 1109 1110 if (exec_check && !executable(exp_dir->s, item.s, dir_ok)) 1111 break; 1112 1113 if (dir_check && !isadirectory(exp_dir->s, item.s)) 1114 break; 1115 1116 if (text_check && isadirectory(exp_dir->s, item.s)) 1117 break; 1118 1119 /* 1120 * Only pattern match directories if we're checking 1121 * for directories. 1122 */ 1123 if (gpat && !Gmatch(item.s, pat) && 1124 (dir_check || !isadirectory(exp_dir->s, item.s))) 1125 break; 1126 1127 /* 1128 * Remove duplicates in command listing and completion 1129 * AFEB added code for TW_LOGNAME and TW_USER cases 1130 */ 1131 if (looking == TW_COMMAND || looking == TW_LOGNAME 1132 || looking == TW_USER || command == LIST) { 1133 buf.len = 0; 1134 Strbuf_append(&buf, item.s); 1135 switch (looking) { 1136 case TW_COMMAND: 1137 if (!(dir_ok && exec_check)) 1138 break; 1139 if (filetype(exp_dir->s, item.s) == '/') 1140 Strbuf_append1(&buf, '/'); 1141 break; 1142 1143 case TW_FILE: 1144 case TW_DIRECTORY: 1145 Strbuf_append1(&buf, filetype(exp_dir->s, item.s)); 1146 break; 1147 1148 default: 1149 break; 1150 } 1151 Strbuf_terminate(&buf); 1152 if ((looking == TW_COMMAND || looking == TW_USER 1153 || looking == TW_LOGNAME) && tw_item_find(buf.s)) 1154 break; 1155 else { 1156 /* maximum length 1 (NULL) + 1 (~ or $) + 1 (filetype) */ 1157 tw_item_add(&buf); 1158 if (command == LIST) 1159 numitems++; 1160 } 1161 } 1162 1163 if (command == RECOGNIZE || command == RECOGNIZE_ALL || 1164 command == RECOGNIZE_SCROLL) { 1165 if (ignoring && ignored(item.s)) { 1166 nignored++; 1167 break; 1168 } 1169 else if (command == RECOGNIZE_SCROLL) { 1170 add_scroll_tab(item.s); 1171 cnt++; 1172 } 1173 1174 if (match_unique_match || is_set(STRrecexact)) { 1175 if (StrQcmp(target, item.s) == 0) { /* EXACT match */ 1176 exp_name->len = 0; 1177 Strbuf_append(exp_name, item.s); 1178 Strbuf_terminate(exp_name); 1179 numitems = 1; /* fake into expanding */ 1180 non_unique_match = TRUE; 1181 done = TRUE; 1182 break; 1183 } 1184 } 1185 if (recognize(exp_name, item.s, name_length, ++numitems, 1186 enhanced, igncase)) 1187 if (command != RECOGNIZE_SCROLL) 1188 done = TRUE; 1189 if (enhanced && exp_name->len < name_length) { 1190 exp_name->len = 0; 1191 Strbuf_append(exp_name, target); 1192 Strbuf_terminate(exp_name); 1193 } 1194 } 1195 break; 1196 1197 default: 1198 break; 1199 } 1200 #ifdef TDEBUG 1201 xprintf("done item = %S\n", item.s); 1202 #endif 1203 } 1204 cleanup_until(&item); 1205 1206 if (command == RECOGNIZE_SCROLL) { 1207 if ((cnt <= curchoice) || (curchoice == -1)) { 1208 curchoice = -1; 1209 nignored = 0; 1210 numitems = 0; 1211 } else if (numitems > 1) { 1212 if (curchoice < -1) 1213 curchoice = cnt - 1; 1214 choose_scroll_tab(exp_name, cnt); 1215 numitems = 1; 1216 } 1217 } 1218 free_scroll_tab(); 1219 1220 if (command == SPELL) 1221 return d; 1222 else { 1223 if (ignoring && numitems == 0 && nignored > 0) 1224 return -nignored; 1225 else 1226 return numitems; 1227 } 1228 } 1229 1230 1231 /* tw_suffix(): 1232 * Find and return the appropriate suffix character 1233 */ 1234 /*ARGSUSED*/ 1235 static Char 1236 tw_suffix(int looking, struct Strbuf *word, const Char *exp_dir, Char *exp_name) 1237 { 1238 Char *ptr; 1239 Char *dollar; 1240 struct varent *vp; 1241 1242 (void) strip(exp_name); 1243 1244 switch (looking) { 1245 1246 case TW_LOGNAME: 1247 return '/'; 1248 1249 case TW_VARIABLE: 1250 /* 1251 * Don't consider array variables or empty variables 1252 */ 1253 if ((vp = adrof(exp_name)) != NULL && vp->vec != NULL) { 1254 if ((ptr = vp->vec[0]) == NULL || *ptr == '\0' || 1255 vp->vec[1] != NULL) 1256 return ' '; 1257 } 1258 else if ((ptr = tgetenv(exp_name)) == NULL || *ptr == '\0') 1259 return ' '; 1260 1261 if ((dollar = Strrchr(word->s, '$')) != 0 && 1262 dollar[1] == '{' && Strchr(dollar, '}') == NULL) 1263 return '}'; 1264 1265 return isadirectory(exp_dir, ptr) ? '/' : ' '; 1266 1267 1268 case TW_DIRECTORY: 1269 return '/'; 1270 1271 case TW_COMMAND: 1272 case TW_FILE: 1273 return isadirectory(exp_dir, exp_name) ? '/' : ' '; 1274 1275 case TW_ALIAS: 1276 case TW_VARLIST: 1277 case TW_WORDLIST: 1278 case TW_SHELLVAR: 1279 case TW_ENVVAR: 1280 case TW_USER: 1281 case TW_BINDING: 1282 case TW_LIMIT: 1283 case TW_SIGNAL: 1284 case TW_JOB: 1285 case TW_COMPLETION: 1286 case TW_TEXT: 1287 case TW_GRPNAME: 1288 return ' '; 1289 1290 default: 1291 return '\0'; 1292 } 1293 } /* end tw_suffix */ 1294 1295 1296 /* tw_fixword(): 1297 * Repair a word after a spalling or a recognizwe 1298 */ 1299 static void 1300 tw_fixword(int looking, struct Strbuf *word, Char *dir, Char *exp_name) 1301 { 1302 Char *ptr; 1303 1304 switch (looking) { 1305 case TW_LOGNAME: 1306 word->len = 0; 1307 Strbuf_append1(word, '~'); 1308 break; 1309 1310 case TW_VARIABLE: 1311 if ((ptr = Strrchr(word->s, '$')) != NULL) { 1312 if (ptr[1] == '{') ptr++; 1313 word->len = ptr + 1 - word->s; /* Delete after the dollar */ 1314 } else 1315 word->len = 0; 1316 break; 1317 1318 case TW_DIRECTORY: 1319 case TW_FILE: 1320 case TW_TEXT: 1321 word->len = 0; 1322 Strbuf_append(word, dir); /* put back dir part */ 1323 break; 1324 1325 default: 1326 word->len = 0; 1327 break; 1328 } 1329 1330 (void) quote(exp_name); 1331 Strbuf_append(word, exp_name); /* add extended name */ 1332 Strbuf_terminate(word); 1333 } /* end tw_fixword */ 1334 1335 1336 /* tw_collect(): 1337 * Collect items. Return -1 in case we were interrupted or 1338 * the return value of tw_collect 1339 * This is really a wrapper for tw_collect_items, serving two 1340 * purposes: 1341 * 1. Handles interrupt cleanups. 1342 * 2. Retries if we had no matches, but there were ignored matches 1343 */ 1344 static int 1345 tw_collect(COMMAND command, int looking, struct Strbuf *exp_dir, 1346 struct Strbuf *exp_name, Char *target, Char *pat, int flags, 1347 DIR *dir_fd) 1348 { 1349 volatile int ni; 1350 jmp_buf_t osetexit; 1351 1352 #ifdef TDEBUG 1353 xprintf("target = %S\n", target); 1354 #endif 1355 ni = 0; 1356 getexit(osetexit); 1357 for (;;) { 1358 volatile size_t omark; 1359 1360 (*tw_start_entry[looking])(dir_fd, pat); 1361 InsideCompletion = 1; 1362 if (setexit()) { 1363 cleanup_pop_mark(omark); 1364 resexit(osetexit); 1365 /* interrupted, clean up */ 1366 haderr = 0; 1367 ni = -1; /* flag error */ 1368 break; 1369 } 1370 omark = cleanup_push_mark(); 1371 ni = tw_collect_items(command, looking, exp_dir, exp_name, target, pat, 1372 ni >= 0 ? flags : flags & ~TW_IGN_OK); 1373 cleanup_pop_mark(omark); 1374 resexit(osetexit); 1375 if (ni >= 0) 1376 break; 1377 } 1378 InsideCompletion = 0; 1379 #if defined(SOLARIS2) && defined(i386) && !defined(__GNUC__) 1380 /* Compiler bug? (from PWP) */ 1381 if ((looking == TW_LOGNAME) || (looking == TW_USER)) 1382 tw_logname_end(); 1383 else if (looking == TW_GRPNAME) 1384 tw_grpname_end(); 1385 else 1386 tw_dir_end(); 1387 #else /* !(SOLARIS2 && i386 && !__GNUC__) */ 1388 (*tw_end_entry[looking])(); 1389 #endif /* !(SOLARIS2 && i386 && !__GNUC__) */ 1390 return(ni); 1391 } /* end tw_collect */ 1392 1393 1394 /* tw_list_items(): 1395 * List the items that were found 1396 * 1397 * NOTE instead of looking at numerical vars listmax and listmaxrows 1398 * we can look at numerical var listmax, and have a string value 1399 * listmaxtype (or similar) than can have values 'items' and 'rows' 1400 * (by default interpreted as 'items', for backwards compatibility) 1401 */ 1402 static void 1403 tw_list_items(int looking, int numitems, int list_max) 1404 { 1405 Char *ptr; 1406 int max_items = 0; 1407 int max_rows = 0; 1408 1409 if (numitems == 0) 1410 return; 1411 1412 if ((ptr = varval(STRlistmax)) != STRNULL) { 1413 while (*ptr) { 1414 if (!Isdigit(*ptr)) { 1415 max_items = 0; 1416 break; 1417 } 1418 max_items = max_items * 10 + *ptr++ - '0'; 1419 } 1420 if ((max_items > 0) && (numitems > max_items) && list_max) 1421 max_items = numitems; 1422 else 1423 max_items = 0; 1424 } 1425 1426 if (max_items == 0 && (ptr = varval(STRlistmaxrows)) != STRNULL) { 1427 int rows; 1428 1429 while (*ptr) { 1430 if (!Isdigit(*ptr)) { 1431 max_rows = 0; 1432 break; 1433 } 1434 max_rows = max_rows * 10 + *ptr++ - '0'; 1435 } 1436 if (max_rows != 0 && looking != TW_JOB) 1437 rows = find_rows(tw_item_get(), numitems, TRUE); 1438 else 1439 rows = numitems; /* underestimate for lines wider than the termH */ 1440 if ((max_rows > 0) && (rows > max_rows) && list_max) 1441 max_rows = rows; 1442 else 1443 max_rows = 0; 1444 } 1445 1446 1447 if (max_items || max_rows) { 1448 char tc, *sname; 1449 const char *name; 1450 int maxs; 1451 1452 if (max_items) { 1453 name = CGETS(30, 5, "items"); 1454 maxs = max_items; 1455 } 1456 else { 1457 name = CGETS(30, 6, "rows"); 1458 maxs = max_rows; 1459 } 1460 1461 sname = strsave(name); 1462 cleanup_push(sname, xfree); 1463 xprintf(CGETS(30, 7, "There are %d %s, list them anyway? [n/y] "), 1464 maxs, sname); 1465 cleanup_until(sname); 1466 flush(); 1467 /* We should be in Rawmode here, so no \n to catch */ 1468 (void) xread(SHIN, &tc, 1); 1469 xprintf("%c\r\n", tc); /* echo the char, do a newline */ 1470 /* 1471 * Perhaps we should use the yesexpr from the 1472 * actual locale 1473 */ 1474 if (strchr(CGETS(30, 13, "Yy"), tc) == NULL) 1475 return; 1476 } 1477 1478 if (looking != TW_SIGNAL) 1479 qsort(tw_item_get(), numitems, sizeof(Char *), fcompare); 1480 if (looking != TW_JOB) 1481 print_by_column(STRNULL, tw_item_get(), numitems, TRUE); 1482 else { 1483 /* 1484 * print one item on every line because jobs can have spaces 1485 * and it is confusing. 1486 */ 1487 int i; 1488 Char **w = tw_item_get(); 1489 1490 for (i = 0; i < numitems; i++) { 1491 xprintf("%S", w[i]); 1492 if (Tty_raw_mode) 1493 xputchar('\r'); 1494 xputchar('\n'); 1495 } 1496 } 1497 } /* end tw_list_items */ 1498 1499 1500 /* t_search(): 1501 * Perform a RECOGNIZE, LIST or SPELL command on string "word". 1502 * 1503 * Return value: 1504 * >= 0: SPELL command: "distance" (see spdist()) 1505 * other: No. of items found 1506 * < 0: Error (message or beep is output) 1507 */ 1508 /*ARGSUSED*/ 1509 int 1510 t_search(struct Strbuf *word, COMMAND command, int looking, int list_max, 1511 Char *pat, eChar suf) 1512 { 1513 int numitems, /* Number of items matched */ 1514 flags = 0, /* search flags */ 1515 gpat = pat[0] != '\0', /* Glob pattern search */ 1516 res; /* Return value */ 1517 struct Strbuf exp_dir = Strbuf_INIT;/* dir after ~ expansion */ 1518 struct Strbuf dir = Strbuf_INIT; /* /x/y/z/ part in /x/y/z/f */ 1519 struct Strbuf exp_name = Strbuf_INIT;/* the recognized (extended) */ 1520 Char *name, /* f part in /d/d/d/f name */ 1521 *target; /* Target to expand/correct/list */ 1522 DIR *dir_fd = NULL; 1523 1524 /* 1525 * bugfix by Marty Grossman (grossman@CC5.BBN.COM): directory listing can 1526 * dump core when interrupted 1527 */ 1528 tw_item_free(); 1529 1530 non_unique_match = FALSE; /* See the recexact code below */ 1531 1532 extract_dir_and_name(word->s, &dir, &name); 1533 cleanup_push(&dir, Strbuf_cleanup); 1534 cleanup_push(&name, xfree_indirect); 1535 1536 /* 1537 * SPECIAL HARDCODED COMPLETIONS: 1538 * foo$variable -> TW_VARIABLE 1539 * ~user -> TW_LOGNAME 1540 * 1541 */ 1542 if ((*word->s == '~') && (Strchr(word->s, '/') == NULL)) { 1543 looking = TW_LOGNAME; 1544 target = name; 1545 gpat = 0; /* Override pattern mechanism */ 1546 } 1547 else if ((target = Strrchr(name, '$')) != 0 && 1548 (target[1] != '{' || Strchr(target, '}') == NULL) && 1549 (Strchr(name, '/') == NULL)) { 1550 target++; 1551 if (target[0] == '{') target++; 1552 looking = TW_VARIABLE; 1553 gpat = 0; /* Override pattern mechanism */ 1554 } 1555 else 1556 target = name; 1557 1558 /* 1559 * Try to figure out what we should be looking for 1560 */ 1561 if (looking & TW_PATH) { 1562 gpat = 0; /* pattern holds the pathname to be used */ 1563 Strbuf_append(&exp_dir, pat); 1564 if (exp_dir.len != 0 && exp_dir.s[exp_dir.len - 1] != '/') 1565 Strbuf_append1(&exp_dir, '/'); 1566 Strbuf_append(&exp_dir, dir.s); 1567 } 1568 Strbuf_terminate(&exp_dir); 1569 cleanup_push(&exp_dir, Strbuf_cleanup); 1570 1571 switch (looking & ~TW_PATH) { 1572 case TW_NONE: 1573 res = -1; 1574 goto err_dir; 1575 1576 case TW_ZERO: 1577 looking = TW_FILE; 1578 break; 1579 1580 case TW_COMMAND: 1581 if (Strchr(word->s, '/') || (looking & TW_PATH)) { 1582 looking = TW_FILE; 1583 flags |= TW_EXEC_CHK; 1584 flags |= TW_DIR_OK; 1585 } 1586 #ifdef notdef 1587 /* PWP: don't even bother when doing ALL of the commands */ 1588 if (looking == TW_COMMAND && word->len == 0) { 1589 res = -1; 1590 goto err_dir; 1591 } 1592 #endif 1593 break; 1594 1595 1596 case TW_VARLIST: 1597 case TW_WORDLIST: 1598 gpat = 0; /* pattern holds the name of the variable */ 1599 break; 1600 1601 case TW_EXPLAIN: 1602 if (command == LIST && pat != NULL) { 1603 xprintf("%S", pat); 1604 if (Tty_raw_mode) 1605 xputchar('\r'); 1606 xputchar('\n'); 1607 } 1608 res = 2; 1609 goto err_dir; 1610 1611 default: 1612 break; 1613 } 1614 1615 /* 1616 * let fignore work only when we are not using a pattern 1617 */ 1618 flags |= (gpat == 0) ? TW_IGN_OK : TW_PAT_OK; 1619 1620 #ifdef TDEBUG 1621 xprintf(CGETS(30, 8, "looking = %d\n"), looking); 1622 #endif 1623 1624 switch (looking) { 1625 Char *user_name; 1626 1627 case TW_ALIAS: 1628 case TW_SHELLVAR: 1629 case TW_ENVVAR: 1630 case TW_BINDING: 1631 case TW_LIMIT: 1632 case TW_SIGNAL: 1633 case TW_JOB: 1634 case TW_COMPLETION: 1635 case TW_GRPNAME: 1636 break; 1637 1638 1639 case TW_VARIABLE: 1640 if ((res = expand_dir(dir.s, &exp_dir, &dir_fd, command)) != 0) 1641 goto err_dir; 1642 break; 1643 1644 case TW_DIRECTORY: 1645 flags |= TW_DIR_CHK; 1646 1647 #ifdef notyet 1648 /* 1649 * This is supposed to expand the directory stack. 1650 * Problems: 1651 * 1. Slow 1652 * 2. directories with the same name 1653 */ 1654 flags |= TW_DIR_OK; 1655 #endif 1656 #ifdef notyet 1657 /* 1658 * Supposed to do delayed expansion, but it is inconsistent 1659 * from a user-interface point of view, since it does not 1660 * immediately obey addsuffix 1661 */ 1662 if ((res = expand_dir(dir.s, &exp_dir, &dir_fd, command)) != 0) 1663 goto err_dir; 1664 if (isadirectory(exp_dir.s, name)) { 1665 if (exp_dir.len != 0 || name[0] != '\0') { 1666 Strbuf_append(&dir, name); 1667 if (dir.s[dir.len - 1] != '/') 1668 Strbuf_append1(&dir, '/'); 1669 Strbuf_terminate(&dir); 1670 if ((res = expand_dir(dir.s, &exp_dir, &dir_fd, command)) != 0) 1671 goto err_dir; 1672 if (word->len != 0 && word->s[word->len - 1] != '/') { 1673 Strbuf_append1(word, '/'); 1674 Strbuf_terminate(word); 1675 } 1676 name[0] = '\0'; 1677 } 1678 } 1679 #endif 1680 if ((res = expand_dir(dir.s, &exp_dir, &dir_fd, command)) != 0) 1681 goto err_dir; 1682 break; 1683 1684 case TW_TEXT: 1685 flags |= TW_TEXT_CHK; 1686 /*FALLTHROUGH*/ 1687 case TW_FILE: 1688 if ((res = expand_dir(dir.s, &exp_dir, &dir_fd, command)) != 0) 1689 goto err_dir; 1690 break; 1691 1692 case TW_PATH | TW_TEXT: 1693 case TW_PATH | TW_FILE: 1694 case TW_PATH | TW_DIRECTORY: 1695 case TW_PATH | TW_COMMAND: 1696 if ((dir_fd = opendir(short2str(exp_dir.s))) == NULL) { 1697 if (command == RECOGNIZE) 1698 xprintf("\n"); 1699 xprintf("%S: %s", exp_dir.s, strerror(errno)); 1700 if (command != RECOGNIZE) 1701 xprintf("\n"); 1702 NeedsRedraw = 1; 1703 res = -1; 1704 goto err_dir; 1705 } 1706 if (exp_dir.len != 0 && exp_dir.s[exp_dir.len - 1] != '/') { 1707 Strbuf_append1(&exp_dir, '/'); 1708 Strbuf_terminate(&exp_dir); 1709 } 1710 1711 looking &= ~TW_PATH; 1712 1713 switch (looking) { 1714 case TW_TEXT: 1715 flags |= TW_TEXT_CHK; 1716 break; 1717 1718 case TW_FILE: 1719 break; 1720 1721 case TW_DIRECTORY: 1722 flags |= TW_DIR_CHK; 1723 break; 1724 1725 case TW_COMMAND: 1726 xfree(name); 1727 target = name = Strsave(word->s); /* so it can match things */ 1728 break; 1729 1730 default: 1731 abort(); /* Cannot happen */ 1732 break; 1733 } 1734 break; 1735 1736 case TW_LOGNAME: 1737 user_name = word->s + 1; 1738 goto do_user; 1739 1740 /*FALLTHROUGH*/ 1741 case TW_USER: 1742 user_name = word->s; 1743 do_user: 1744 /* 1745 * Check if the spelling was already correct 1746 * From: Rob McMahon <cudcv@cu.warwick.ac.uk> 1747 */ 1748 if (command == SPELL && xgetpwnam(short2str(user_name)) != NULL) { 1749 #ifdef YPBUGS 1750 fix_yp_bugs(); 1751 #endif /* YPBUGS */ 1752 res = 0; 1753 goto err_dir; 1754 } 1755 xfree(name); 1756 target = name = Strsave(user_name); 1757 break; 1758 1759 case TW_COMMAND: 1760 case TW_VARLIST: 1761 case TW_WORDLIST: 1762 target = name = Strsave(word->s); /* so it can match things */ 1763 break; 1764 1765 default: 1766 xprintf(CGETS(30, 9, 1767 "\n%s internal error: I don't know what I'm looking for!\n"), 1768 progname); 1769 NeedsRedraw = 1; 1770 res = -1; 1771 goto err_dir; 1772 } 1773 1774 cleanup_push(&exp_name, Strbuf_cleanup); 1775 numitems = tw_collect(command, looking, &exp_dir, &exp_name, target, pat, 1776 flags, dir_fd); 1777 if (numitems == -1) 1778 goto end; 1779 1780 switch (command) { 1781 case RECOGNIZE: 1782 case RECOGNIZE_ALL: 1783 case RECOGNIZE_SCROLL: 1784 if (numitems <= 0) 1785 break; 1786 1787 Strbuf_terminate(&exp_name); 1788 tw_fixword(looking, word, dir.s, exp_name.s); 1789 1790 if (!match_unique_match && is_set(STRaddsuffix) && numitems == 1) { 1791 switch (suf) { 1792 case 0: /* Automatic suffix */ 1793 Strbuf_append1(word, 1794 tw_suffix(looking, word, exp_dir.s, exp_name.s)); 1795 break; 1796 1797 case CHAR_ERR: /* No suffix */ 1798 break; 1799 1800 default: /* completion specified suffix */ 1801 Strbuf_append1(word, suf); 1802 break; 1803 } 1804 Strbuf_terminate(word); 1805 } 1806 break; 1807 1808 case LIST: 1809 tw_list_items(looking, numitems, list_max); 1810 tw_item_free(); 1811 break; 1812 1813 case SPELL: 1814 Strbuf_terminate(&exp_name); 1815 tw_fixword(looking, word, dir.s, exp_name.s); 1816 break; 1817 1818 default: 1819 xprintf("Bad tw_command\n"); 1820 numitems = 0; 1821 } 1822 end: 1823 res = numitems; 1824 err_dir: 1825 cleanup_until(&dir); 1826 return res; 1827 } /* end t_search */ 1828 1829 1830 /* extract_dir_and_name(): 1831 * parse full path in file into 2 parts: directory and file names 1832 * Should leave final slash (/) at end of dir. 1833 */ 1834 static void 1835 extract_dir_and_name(const Char *path, struct Strbuf *dir, Char **name) 1836 { 1837 Char *p; 1838 1839 p = Strrchr(path, '/'); 1840 #ifdef WINNT_NATIVE 1841 if (p == NULL) 1842 p = Strrchr(path, ':'); 1843 #endif /* WINNT_NATIVE */ 1844 if (p == NULL) 1845 *name = Strsave(path); 1846 else { 1847 p++; 1848 *name = Strsave(p); 1849 Strbuf_appendn(dir, path, p - path); 1850 } 1851 Strbuf_terminate(dir); 1852 } /* end extract_dir_and_name */ 1853 1854 1855 /* dollar(): 1856 * expand "/$old1/$old2/old3/" 1857 * to "/value_of_old1/value_of_old2/old3/" 1858 */ 1859 Char * 1860 dollar(const Char *old) 1861 { 1862 struct Strbuf buf = Strbuf_INIT; 1863 1864 while (*old) { 1865 if (*old != '$') 1866 Strbuf_append1(&buf, *old++); 1867 else { 1868 if (expdollar(&buf, &old, QUOTE) == 0) { 1869 xfree(buf.s); 1870 return NULL; 1871 } 1872 } 1873 } 1874 return Strbuf_finish(&buf); 1875 } /* end dollar */ 1876 1877 1878 /* tilde(): 1879 * expand ~person/foo to home_directory_of_person/foo 1880 * or =<stack-entry> to <dir in stack entry> 1881 */ 1882 static int 1883 tilde(struct Strbuf *new, Char *old) 1884 { 1885 Char *o, *p; 1886 1887 new->len = 0; 1888 switch (old[0]) { 1889 case '~': { 1890 Char *name, *home; 1891 1892 old++; 1893 for (o = old; *o && *o != '/'; o++) 1894 continue; 1895 name = Strnsave(old, o - old); 1896 home = gethdir(name); 1897 xfree(name); 1898 if (home == NULL) 1899 goto err; 1900 Strbuf_append(new, home); 1901 xfree(home); 1902 /* If the home directory expands to "/", we do 1903 * not want to create "//" by appending a slash from o. 1904 */ 1905 if (new->s[0] == '/' && new->len == 1 && *o == '/') 1906 ++o; 1907 Strbuf_append(new, o); 1908 break; 1909 } 1910 1911 case '=': 1912 if ((p = globequal(old)) == NULL) 1913 goto err; 1914 if (p != old) { 1915 Strbuf_append(new, p); 1916 xfree(p); 1917 break; 1918 } 1919 /*FALLTHROUGH*/ 1920 1921 default: 1922 Strbuf_append(new, old); 1923 break; 1924 } 1925 Strbuf_terminate(new); 1926 return 0; 1927 1928 err: 1929 Strbuf_terminate(new); 1930 return -1; 1931 } /* end tilde */ 1932 1933 1934 /* expand_dir(): 1935 * Open the directory given, expanding ~user and $var 1936 * Optionally normalize the path given 1937 */ 1938 static int 1939 expand_dir(const Char *dir, struct Strbuf *edir, DIR **dfd, COMMAND cmd) 1940 { 1941 Char *nd = NULL; 1942 Char *tdir; 1943 1944 tdir = dollar(dir); 1945 cleanup_push(tdir, xfree); 1946 if (tdir == NULL || 1947 (tilde(edir, tdir) != 0) || 1948 !(nd = dnormalize(edir->len ? edir->s : STRdot, 1949 symlinks == SYM_IGNORE || symlinks == SYM_EXPAND)) || 1950 ((*dfd = opendir(short2str(nd))) == NULL)) { 1951 xfree(nd); 1952 if (cmd == SPELL || SearchNoDirErr) { 1953 cleanup_until(tdir); 1954 return (-2); 1955 } 1956 /* 1957 * From: Amos Shapira <amoss@cs.huji.ac.il> 1958 * Print a better message when completion fails 1959 */ 1960 xprintf("\n%S %s\n", edir->len ? edir->s : (tdir ? tdir : dir), 1961 (errno == ENOTDIR ? CGETS(30, 10, "not a directory") : 1962 (errno == ENOENT ? CGETS(30, 11, "not found") : 1963 CGETS(30, 12, "unreadable")))); 1964 NeedsRedraw = 1; 1965 cleanup_until(tdir); 1966 return (-1); 1967 } 1968 cleanup_until(tdir); 1969 if (nd) { 1970 if (*dir != '\0') { 1971 int slash; 1972 1973 /* 1974 * Copy and append a / if there was one 1975 */ 1976 slash = edir->len != 0 && edir->s[edir->len - 1] == '/'; 1977 edir->len = 0; 1978 Strbuf_append(edir, nd); 1979 if (slash != 0 && edir->s[edir->len - 1] != '/') 1980 Strbuf_append1(edir, '/'); 1981 Strbuf_terminate(edir); 1982 } 1983 xfree(nd); 1984 } 1985 return 0; 1986 } /* end expand_dir */ 1987 1988 1989 /* nostat(): 1990 * Returns true if the directory should not be stat'd, 1991 * false otherwise. 1992 * This way, things won't grind to a halt when you complete in /afs 1993 * or very large directories. 1994 */ 1995 static int 1996 nostat(Char *dir) 1997 { 1998 struct varent *vp; 1999 Char **cp; 2000 2001 if ((vp = adrof(STRnostat)) == NULL || (cp = vp->vec) == NULL) 2002 return FALSE; 2003 for (; *cp != NULL; cp++) { 2004 if (Strcmp(*cp, STRstar) == 0) 2005 return TRUE; 2006 if (Gmatch(dir, *cp)) 2007 return TRUE; 2008 } 2009 return FALSE; 2010 } /* end nostat */ 2011 2012 2013 /* filetype(): 2014 * Return a character that signifies a filetype 2015 * symbology from 4.3 ls command. 2016 */ 2017 static Char 2018 filetype(Char *dir, Char *file) 2019 { 2020 if (dir) { 2021 Char *path; 2022 char *ptr; 2023 struct stat statb; 2024 2025 if (nostat(dir)) return(' '); 2026 2027 path = Strspl(dir, file); 2028 ptr = short2str(path); 2029 xfree(path); 2030 2031 if (lstat(ptr, &statb) != -1) { 2032 #ifdef S_ISLNK 2033 if (S_ISLNK(statb.st_mode)) { /* Symbolic link */ 2034 if (adrof(STRlistlinks)) { 2035 if (stat(ptr, &statb) == -1) 2036 return ('&'); 2037 else if (S_ISDIR(statb.st_mode)) 2038 return ('>'); 2039 else 2040 return ('@'); 2041 } 2042 else 2043 return ('@'); 2044 } 2045 #endif 2046 #ifdef S_ISSOCK 2047 if (S_ISSOCK(statb.st_mode)) /* Socket */ 2048 return ('='); 2049 #endif 2050 #ifdef S_ISFIFO 2051 if (S_ISFIFO(statb.st_mode)) /* Named Pipe */ 2052 return ('|'); 2053 #endif 2054 #ifdef S_ISHIDDEN 2055 if (S_ISHIDDEN(statb.st_mode)) /* Hidden Directory [aix] */ 2056 return ('+'); 2057 #endif 2058 #ifdef S_ISCDF 2059 { 2060 struct stat hpstatb; 2061 char *p2; 2062 2063 p2 = strspl(ptr, "+"); /* Must append a '+' and re-stat(). */ 2064 if ((stat(p2, &hpstatb) != -1) && S_ISCDF(hpstatb.st_mode)) { 2065 xfree(p2); 2066 return ('+'); /* Context Dependent Files [hpux] */ 2067 } 2068 xfree(p2); 2069 } 2070 #endif 2071 #ifdef S_ISNWK 2072 if (S_ISNWK(statb.st_mode)) /* Network Special [hpux] */ 2073 return (':'); 2074 #endif 2075 #ifdef S_ISCHR 2076 if (S_ISCHR(statb.st_mode)) /* char device */ 2077 return ('%'); 2078 #endif 2079 #ifdef S_ISBLK 2080 if (S_ISBLK(statb.st_mode)) /* block device */ 2081 return ('#'); 2082 #endif 2083 #ifdef S_ISDIR 2084 if (S_ISDIR(statb.st_mode)) /* normal Directory */ 2085 return ('/'); 2086 #endif 2087 if (statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) 2088 return ('*'); 2089 } 2090 } 2091 return (' '); 2092 } /* end filetype */ 2093 2094 2095 /* isadirectory(): 2096 * Return trus if the file is a directory 2097 */ 2098 static int 2099 isadirectory(const Char *dir, const Char *file) 2100 /* return 1 if dir/file is a directory */ 2101 /* uses stat rather than lstat to get dest. */ 2102 { 2103 if (dir) { 2104 Char *path; 2105 char *cpath; 2106 struct stat statb; 2107 2108 path = Strspl(dir, file); 2109 cpath = short2str(path); 2110 xfree(path); 2111 if (stat(cpath, &statb) >= 0) { /* resolve through symlink */ 2112 #ifdef S_ISSOCK 2113 if (S_ISSOCK(statb.st_mode)) /* Socket */ 2114 return 0; 2115 #endif 2116 #ifdef S_ISFIFO 2117 if (S_ISFIFO(statb.st_mode)) /* Named Pipe */ 2118 return 0; 2119 #endif 2120 if (S_ISDIR(statb.st_mode)) /* normal Directory */ 2121 return 1; 2122 } 2123 } 2124 return 0; 2125 } /* end isadirectory */ 2126 2127 2128 2129 /* find_rows(): 2130 * Return how many rows needed to print sorted down columns 2131 */ 2132 static int 2133 find_rows(Char *items[], int count, int no_file_suffix) 2134 { 2135 int i, columns, rows; 2136 unsigned int maxwidth = 0; 2137 2138 for (i = 0; i < count; i++) /* find widest string */ 2139 maxwidth = max(maxwidth, (unsigned int) Strlen(items[i])); 2140 2141 maxwidth += no_file_suffix ? 1 : 2; /* for the file tag and space */ 2142 columns = (TermH + 1) / maxwidth; /* PWP: terminal size change */ 2143 if (!columns) 2144 columns = 1; 2145 rows = (count + (columns - 1)) / columns; 2146 2147 return rows; 2148 } /* end rows_needed_by_print_by_column */ 2149 2150 2151 /* print_by_column(): 2152 * Print sorted down columns or across columns when the first 2153 * word of $listflags shell variable contains 'x'. 2154 * 2155 */ 2156 void 2157 print_by_column(Char *dir, Char *items[], int count, int no_file_suffix) 2158 { 2159 int i, r, c, columns, rows; 2160 size_t w; 2161 unsigned int wx, maxwidth = 0; 2162 Char *val; 2163 int across; 2164 2165 lbuffed = 0; /* turn off line buffering */ 2166 2167 2168 across = ((val = varval(STRlistflags)) != STRNULL) && 2169 (Strchr(val, 'x') != NULL); 2170 2171 for (i = 0; i < count; i++) { /* find widest string */ 2172 maxwidth = max(maxwidth, (unsigned int) NLSStringWidth(items[i])); 2173 } 2174 2175 maxwidth += no_file_suffix ? 1 : 2; /* for the file tag and space */ 2176 columns = TermH / maxwidth; /* PWP: terminal size change */ 2177 if (!columns || !isatty(didfds ? 1 : SHOUT)) 2178 columns = 1; 2179 rows = (count + (columns - 1)) / columns; 2180 2181 i = -1; 2182 for (r = 0; r < rows; r++) { 2183 for (c = 0; c < columns; c++) { 2184 i = across ? (i + 1) : (c * rows + r); 2185 2186 if (i < count) { 2187 wx = 0; 2188 w = Strlen(items[i]); 2189 2190 #ifdef COLOR_LS_F 2191 if (no_file_suffix) { 2192 /* Print the command name */ 2193 Char f = items[i][w - 1]; 2194 items[i][w - 1] = 0; 2195 print_with_color(items[i], w - 1, f); 2196 items[i][w - 1] = f; 2197 } 2198 else { 2199 /* Print filename followed by '/' or '*' or ' ' */ 2200 print_with_color(items[i], w, filetype(dir, items[i])); 2201 wx++; 2202 } 2203 #else /* ifndef COLOR_LS_F */ 2204 if (no_file_suffix) { 2205 /* Print the command name */ 2206 xprintf("%S", items[i]); 2207 } 2208 else { 2209 /* Print filename followed by '/' or '*' or ' ' */ 2210 xprintf("%-S%c", items[i], filetype(dir, items[i])); 2211 wx++; 2212 } 2213 #endif /* COLOR_LS_F */ 2214 2215 if (c < (columns - 1)) { /* Not last column? */ 2216 w = NLSStringWidth(items[i]) + wx; 2217 for (; w < maxwidth; w++) 2218 xputchar(' '); 2219 } 2220 } 2221 else if (across) 2222 break; 2223 } 2224 if (Tty_raw_mode) 2225 xputchar('\r'); 2226 xputchar('\n'); 2227 } 2228 2229 lbuffed = 1; /* turn back on line buffering */ 2230 flush(); 2231 } /* end print_by_column */ 2232 2233 2234 /* StrQcmp(): 2235 * Compare strings ignoring the quoting chars 2236 */ 2237 int 2238 StrQcmp(const Char *str1, const Char *str2) 2239 { 2240 for (; *str1 && samecase(*str1 & TRIM) == samecase(*str2 & TRIM); 2241 str1++, str2++) 2242 continue; 2243 /* 2244 * The following case analysis is necessary so that characters which look 2245 * negative collate low against normal characters but high against the 2246 * end-of-string NUL. 2247 */ 2248 if (*str1 == '\0' && *str2 == '\0') 2249 return (0); 2250 else if (*str1 == '\0') 2251 return (-1); 2252 else if (*str2 == '\0') 2253 return (1); 2254 else 2255 return ((*str1 & TRIM) - (*str2 & TRIM)); 2256 } /* end StrQcmp */ 2257 2258 2259 /* fcompare(): 2260 * Comparison routine for qsort, (Char **, Char **) 2261 */ 2262 int 2263 fcompare(const void *xfile1, const void *xfile2) 2264 { 2265 const Char *const *file1 = xfile1, *const *file2 = xfile2; 2266 2267 return collate(*file1, *file2); 2268 } /* end fcompare */ 2269 2270 2271 /* catn(): 2272 * Concatenate src onto tail of des. 2273 * Des is a string whose maximum length is count. 2274 * Always null terminate. 2275 */ 2276 void 2277 catn(Char *des, const Char *src, int count) 2278 { 2279 while (*des && --count > 0) 2280 des++; 2281 while (--count > 0) 2282 if ((*des++ = *src++) == 0) 2283 return; 2284 *des = '\0'; 2285 } /* end catn */ 2286 2287 2288 /* copyn(): 2289 * like strncpy but always leave room for trailing \0 2290 * and always null terminate. 2291 */ 2292 void 2293 copyn(Char *des, const Char *src, size_t count) 2294 { 2295 while (--count != 0) 2296 if ((*des++ = *src++) == 0) 2297 return; 2298 *des = '\0'; 2299 } /* end copyn */ 2300 2301 2302 /* tgetenv(): 2303 * like it's normal string counter-part 2304 */ 2305 Char * 2306 tgetenv(Char *str) 2307 { 2308 Char **var; 2309 size_t len; 2310 int res; 2311 2312 len = Strlen(str); 2313 /* Search the STR_environ for the entry matching str. */ 2314 for (var = STR_environ; var != NULL && *var != NULL; var++) 2315 if (Strlen(*var) >= len && (*var)[len] == '=') { 2316 /* Temporarily terminate the string so we can copy the variable 2317 name. */ 2318 (*var)[len] = '\0'; 2319 res = StrQcmp(*var, str); 2320 /* Restore the '=' and return a pointer to the value of the 2321 environment variable. */ 2322 (*var)[len] = '='; 2323 if (res == 0) 2324 return (&((*var)[len + 1])); 2325 } 2326 return (NULL); 2327 } /* end tgetenv */ 2328 2329 2330 struct scroll_tab_list *scroll_tab = 0; 2331 2332 static void 2333 add_scroll_tab(Char *item) 2334 { 2335 struct scroll_tab_list *new_scroll; 2336 2337 new_scroll = xmalloc(sizeof(struct scroll_tab_list)); 2338 new_scroll->element = Strsave(item); 2339 new_scroll->next = scroll_tab; 2340 scroll_tab = new_scroll; 2341 } 2342 2343 static void 2344 choose_scroll_tab(struct Strbuf *exp_name, int cnt) 2345 { 2346 struct scroll_tab_list *loop; 2347 int tmp = cnt; 2348 Char **ptr; 2349 2350 ptr = xmalloc(sizeof(Char *) * cnt); 2351 cleanup_push(ptr, xfree); 2352 2353 for(loop = scroll_tab; loop && (tmp >= 0); loop = loop->next) 2354 ptr[--tmp] = loop->element; 2355 2356 qsort(ptr, cnt, sizeof(Char *), fcompare); 2357 2358 exp_name->len = 0; 2359 Strbuf_append(exp_name, ptr[curchoice]); 2360 Strbuf_terminate(exp_name); 2361 cleanup_until(ptr); 2362 } 2363 2364 static void 2365 free_scroll_tab(void) 2366 { 2367 struct scroll_tab_list *loop; 2368 2369 while(scroll_tab) { 2370 loop = scroll_tab; 2371 scroll_tab = scroll_tab->next; 2372 xfree(loop->element); 2373 xfree(loop); 2374 } 2375 } 2376