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