1 /* $Header: /p/tcsh/cvsroot/tcsh/tw.parse.c,v 3.139 2015/10/16 14:59:56 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.139 2015/10/16 14:59:56 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 #if INVALID_BYTE != 0 622 /* add checking INVALID_BYTE for FIX UTF32 */ 623 if ((w & INVALID_BYTE) != INVALID_BYTE) /* w < INVALID_BYTE */ 624 #endif 625 w &= ~QUOTE; 626 627 if (cmap(w, _ESC | _QF)) 628 wq = QUOTE; /* quotes are always quoted */ 629 630 if (!wq && qu && tricky(w) && !(qu == '\"' && tricky_dq(w))) { 631 /* We have to unquote the character */ 632 in_sync = 0; 633 if (cmap(qu, _ESC)) 634 buffer.s[buffer.len - 1] = w; 635 else { 636 Strbuf_append1(&buffer, qu); 637 Strbuf_append1(&buffer, w); 638 if (wptr[1] == 0) 639 qu = 0; 640 else 641 Strbuf_append1(&buffer, qu); 642 } 643 } else if (qu && w == qu) { 644 in_sync = 0; 645 if (buffer.len != 0 && buffer.s[buffer.len - 1] == qu) { 646 /* User misunderstanding :) */ 647 buffer.s[buffer.len - 1] = '\\'; 648 Strbuf_append1(&buffer, w); 649 qu = 0; 650 } else { 651 Strbuf_append1(&buffer, qu); 652 Strbuf_append1(&buffer, '\\'); 653 Strbuf_append1(&buffer, w); 654 Strbuf_append1(&buffer, qu); 655 } 656 } 657 else if (wq && qu == '\"' && tricky_dq(w)) { 658 in_sync = 0; 659 Strbuf_append1(&buffer, qu); 660 Strbuf_append1(&buffer, '\\'); 661 Strbuf_append1(&buffer, w); 662 Strbuf_append1(&buffer, qu); 663 } else if (wq && 664 ((!qu && (tricky(w) || (w == HISTSUB && HISTSUB != '\0' 665 && buffer.len == 0))) || 666 (!cmap(qu, _ESC) && w == HIST && HIST != '\0'))) { 667 in_sync = 0; 668 Strbuf_append1(&buffer, '\\'); 669 Strbuf_append1(&buffer, w); 670 } else { 671 if (in_sync && *cp++ != w) 672 in_sync = 0; 673 Strbuf_append1(&buffer, w); 674 } 675 wptr++; 676 if (cmap(qu, _ESC)) 677 qu = 0; 678 } 679 if (closequotes && qu && !cmap(qu, _ESC)) 680 Strbuf_append1(&buffer, w); 681 bptr = Strbuf_finish(&buffer); 682 if (ndel) 683 DeleteBack(ndel); 684 res = InsertStr(bptr); 685 xfree(bptr); 686 return res; 687 } /* end insert_meta */ 688 689 690 691 /* is_prefix(): 692 * return true if check matches initial chars in template 693 * This differs from PWB imatch in that if check is null 694 * it matches anything 695 */ 696 static int 697 is_prefix(Char *check, Char *template) 698 { 699 for (; *check; check++, template++) 700 if ((*check & TRIM) != (*template & TRIM)) 701 return (FALSE); 702 return (TRUE); 703 } /* end is_prefix */ 704 705 706 /* is_prefixmatch(): 707 * return true if check matches initial chars in template 708 * This differs from PWB imatch in that if check is null 709 * it matches anything 710 * and matches on shortening of commands 711 */ 712 static int 713 is_prefixmatch(Char *check, Char *template, int enhanced) 714 { 715 Char MCH1, MCH2, LCH1, LCH2; 716 717 for (; *check; check++, template++) { 718 if ((*check & TRIM) != (*template & TRIM)) { 719 MCH1 = (*check & TRIM); 720 MCH2 = (*template & TRIM); 721 LCH1 = Isupper(MCH1) ? Tolower(MCH1) : 722 enhanced == 2 && MCH1 == '_' ? '-' : MCH1; 723 LCH2 = Isupper(MCH2) ? Tolower(MCH2) : 724 enhanced == 2 && MCH2 == '_' ? '-' : MCH2; 725 if (MCH1 != MCH2 && MCH1 != LCH2 && 726 (LCH1 != MCH2 || enhanced == 2)) { 727 if (enhanced && ((*check & TRIM) == '-' || 728 (*check & TRIM) == '.' || 729 (*check & TRIM) == '_')) { 730 MCH1 = MCH2 = (*check & TRIM); 731 if (MCH1 == '_' && enhanced != 2) { 732 MCH2 = '-'; 733 } else if (MCH1 == '-') { 734 MCH2 = '_'; 735 } 736 for (; *template && (*template & TRIM) != MCH1 && 737 (*template & TRIM) != MCH2; template++) 738 continue; 739 if (!*template) { 740 return (FALSE); 741 } 742 } else { 743 return (FALSE); 744 } 745 } 746 } 747 } 748 return (TRUE); 749 } /* end is_prefixmatch */ 750 751 752 /* is_suffix(): 753 * Return true if the chars in template appear at the 754 * end of check, I.e., are it's suffix. 755 */ 756 static int 757 is_suffix(Char *check, Char *template) 758 { 759 Char *t, *c; 760 761 t = Strend(template); 762 c = Strend(check); 763 for (;;) { 764 if (t == template) 765 return 1; 766 if (c == check || (*--t & TRIM) != (*--c & TRIM)) 767 return 0; 768 } 769 } /* end is_suffix */ 770 771 772 /* ignored(): 773 * Return true if this is an ignored item 774 */ 775 static int 776 ignored(Char *item) 777 { 778 struct varent *vp; 779 Char **cp; 780 781 if ((vp = adrof(STRfignore)) == NULL || (cp = vp->vec) == NULL) 782 return (FALSE); 783 for (; *cp != NULL; cp++) 784 if (is_suffix(item, *cp)) 785 return (TRUE); 786 return (FALSE); 787 } /* end ignored */ 788 789 790 791 /* starting_a_command(): 792 * return true if the command starting at wordstart is a command 793 */ 794 int 795 starting_a_command(Char *wordstart, Char *inputline) 796 { 797 Char *ptr, *ncmdstart; 798 int count, bsl; 799 static Char 800 cmdstart[] = {'`', ';', '&', '(', '|', '\0'}, 801 cmdalive[] = {' ', '\t', '\'', '"', '<', '>', '\0'}; 802 803 /* 804 * Find if the number of backquotes is odd or even. 805 */ 806 for (ptr = wordstart, count = 0; 807 ptr >= inputline; 808 count += (*ptr-- == '`')) 809 continue; 810 /* 811 * if the number of backquotes is even don't include the backquote char in 812 * the list of command starting delimiters [if it is zero, then it does not 813 * matter] 814 */ 815 ncmdstart = cmdstart + EVEN(count); 816 817 /* 818 * look for the characters previous to this word if we find a command 819 * starting delimiter we break. if we find whitespace and another previous 820 * word then we are not a command 821 * 822 * count is our state machine: 0 looking for anything 1 found white-space 823 * looking for non-ws 824 */ 825 for (count = 0; wordstart >= inputline; wordstart--) { 826 if (*wordstart == '\0') 827 continue; 828 if (Strchr(ncmdstart, *wordstart)) { 829 for (ptr = wordstart, bsl = 0; *(--ptr) == '\\'; bsl++); 830 if (bsl & 1) { 831 wordstart--; 832 continue; 833 } else 834 break; 835 } 836 /* 837 * found white space 838 */ 839 if ((ptr = Strchr(cmdalive, *wordstart)) != NULL) 840 count = 1; 841 if (count == 1 && !ptr) 842 return (FALSE); 843 } 844 845 if (wordstart > inputline) 846 switch (*wordstart) { 847 case '&': /* Look for >& */ 848 while (wordstart > inputline && 849 (*--wordstart == ' ' || *wordstart == '\t')) 850 continue; 851 if (*wordstart == '>') 852 return (FALSE); 853 break; 854 case '(': /* check for foreach, if etc. */ 855 while (wordstart > inputline && 856 (*--wordstart == ' ' || *wordstart == '\t')) 857 continue; 858 if (!iscmdmeta(*wordstart) && 859 (*wordstart != ' ' && *wordstart != '\t')) 860 return (FALSE); 861 break; 862 default: 863 break; 864 } 865 return (TRUE); 866 } /* end starting_a_command */ 867 868 869 /* recognize(): 870 * Object: extend what user typed up to an ambiguity. 871 * Algorithm: 872 * On first match, copy full item (assume it'll be the only match) 873 * On subsequent matches, shorten exp_name to the first 874 * character mismatch between exp_name and item. 875 * If we shorten it back to the prefix length, stop searching. 876 */ 877 static int 878 recognize(struct Strbuf *exp_name, const Char *item, size_t name_length, 879 int numitems, int enhanced, int igncase) 880 { 881 Char MCH1, MCH2, LCH1, LCH2; 882 Char *x; 883 const Char *ent; 884 size_t len = 0; 885 886 if (numitems == 1) { /* 1st match */ 887 exp_name->len = 0; 888 Strbuf_append(exp_name, item); 889 Strbuf_terminate(exp_name); 890 return (0); 891 } 892 if (!enhanced && !igncase) { 893 for (x = exp_name->s, ent = item; *x && (*x & TRIM) == (*ent & TRIM); 894 x++, ent++) 895 len++; 896 } else { 897 for (x = exp_name->s, ent = item; *x; x++, ent++) { 898 MCH1 = *x & TRIM; 899 MCH2 = *ent & TRIM; 900 LCH1 = Isupper(MCH1) ? Tolower(MCH1) : MCH1; 901 LCH2 = Isupper(MCH2) ? Tolower(MCH2) : MCH2; 902 if (MCH1 != MCH2) { 903 if (LCH1 == MCH2 || (MCH1 == '_' && MCH2 == '-')) 904 *x = *ent; 905 else if (LCH1 != LCH2) 906 break; 907 } 908 len++; 909 } 910 } 911 *x = '\0'; /* Shorten at 1st char diff */ 912 exp_name->len = x - exp_name->s; 913 if (!(match_unique_match || is_set(STRrecexact) || (enhanced && *ent)) && 914 len == name_length) /* Ambiguous to prefix? */ 915 return (-1); /* So stop now and save time */ 916 return (0); 917 } /* end recognize */ 918 919 920 /* tw_collect_items(): 921 * Collect items that match target. 922 * SPELL command: 923 * Returns the spelling distance of the closest match. 924 * else 925 * Returns the number of items found. 926 * If none found, but some ignored items were found, 927 * It returns the -number of ignored items. 928 */ 929 static int 930 tw_collect_items(COMMAND command, int looking, struct Strbuf *exp_dir, 931 struct Strbuf *exp_name, Char *target, const Char *pat, 932 int flags) 933 { 934 int done = FALSE; /* Search is done */ 935 int showdots; /* Style to show dot files */ 936 int nignored = 0; /* Number of fignored items */ 937 int numitems = 0; /* Number of matched items */ 938 size_t name_length = Strlen(target); /* Length of prefix (file name) */ 939 int exec_check = flags & TW_EXEC_CHK;/* need to check executability */ 940 int dir_check = flags & TW_DIR_CHK; /* Need to check for directories */ 941 int text_check = flags & TW_TEXT_CHK;/* Need to check for non-directories */ 942 int dir_ok = flags & TW_DIR_OK; /* Ignore directories? */ 943 int gpat = flags & TW_PAT_OK; /* Match against a pattern */ 944 int ignoring = flags & TW_IGN_OK; /* Use fignore? */ 945 int d = 4, nd; /* Spelling distance */ 946 Char **cp; 947 Char *ptr; 948 struct varent *vp; 949 struct Strbuf buf = Strbuf_INIT, item = Strbuf_INIT; 950 int enhanced = 0; 951 int cnt = 0; 952 int igncase = 0; 953 954 955 flags = 0; 956 957 showdots = DOT_NONE; 958 if ((ptr = varval(STRlistflags)) != STRNULL) 959 while (*ptr) 960 switch (*ptr++) { 961 case 'a': 962 showdots = DOT_ALL; 963 break; 964 case 'A': 965 showdots = DOT_NOT; 966 break; 967 default: 968 break; 969 } 970 971 if (looking == TW_COMMAND 972 && (vp = adrof(STRautorehash)) != NULL && vp->vec != NULL) 973 for (cp = vp->vec; *cp; cp++) 974 if (Strcmp(*cp, STRalways) == 0 975 || (Strcmp(*cp, STRcorrect) == 0 && command == SPELL) 976 || (Strcmp(*cp, STRcomplete) == 0 && command != SPELL)) { 977 tw_cmd_free(); 978 tw_cmd_start(NULL, NULL); 979 break; 980 } 981 982 cleanup_push(&item, Strbuf_cleanup); 983 cleanup_push(&buf, Strbuf_cleanup); 984 while (!done && 985 (item.len = 0, 986 tw_next_entry[looking](&item, exp_dir, &flags) != 0)) { 987 Strbuf_terminate(&item); 988 #ifdef TDEBUG 989 xprintf("item = %S\n", item.s); 990 #endif 991 switch (looking) { 992 case TW_FILE: 993 case TW_DIRECTORY: 994 case TW_TEXT: 995 /* 996 * Don't match . files on null prefix match 997 */ 998 if (showdots == DOT_NOT && (ISDOT(item.s) || ISDOTDOT(item.s))) 999 done = TRUE; 1000 if (name_length == 0 && item.s[0] == '.' && showdots == DOT_NONE) 1001 done = TRUE; 1002 break; 1003 1004 case TW_COMMAND: 1005 #if defined(_UWIN) || defined(__CYGWIN__) 1006 /* 1007 * Turn foo.{exe,com,bat,cmd} into foo since UWIN's readdir returns 1008 * the file with the .exe, .com, .bat, .cmd extension 1009 * 1010 * Same for Cygwin, but only for .exe and .com extension. 1011 */ 1012 { 1013 #ifdef __CYGWIN__ 1014 static const char *rext[] = { ".exe", ".com" }; 1015 #else 1016 static const char *rext[] = { ".exe", ".bat", ".com", ".cmd" }; 1017 #endif 1018 size_t exti = Strlen(item.s); 1019 1020 if (exti > 4) { 1021 char *ext = short2str(&item.s[exti -= 4]); 1022 size_t i; 1023 1024 for (i = 0; i < sizeof(rext) / sizeof(rext[0]); i++) 1025 if (strcasecmp(ext, rext[i]) == 0) { 1026 item.len = exti; 1027 Strbuf_terminate(&item); 1028 break; 1029 } 1030 } 1031 } 1032 #endif /* _UWIN || __CYGWIN__ */ 1033 exec_check = flags & TW_EXEC_CHK; 1034 dir_ok = flags & TW_DIR_OK; 1035 break; 1036 1037 default: 1038 break; 1039 } 1040 1041 if (done) { 1042 done = FALSE; 1043 continue; 1044 } 1045 1046 switch (command) { 1047 1048 case SPELL: /* correct the spelling of the last bit */ 1049 if (name_length == 0) {/* zero-length word can't be misspelled */ 1050 exp_name->len = 0; /* (not trying is important for ~) */ 1051 Strbuf_terminate(exp_name); 1052 d = 0; 1053 done = TRUE; 1054 break; 1055 } 1056 if (gpat && !Gmatch(item.s, pat)) 1057 break; 1058 /* 1059 * Swapped the order of the spdist() arguments as suggested 1060 * by eeide@asylum.cs.utah.edu (Eric Eide) 1061 */ 1062 nd = spdist(target, item.s); /* test the item against original */ 1063 if (nd <= d && nd != 4) { 1064 if (!(exec_check && !executable(exp_dir->s, item.s, dir_ok))) { 1065 exp_name->len = 0; 1066 Strbuf_append(exp_name, item.s); 1067 Strbuf_terminate(exp_name); 1068 d = nd; 1069 if (d == 0) /* if found it exactly */ 1070 done = TRUE; 1071 } 1072 } 1073 else if (nd == 4) { 1074 if (spdir(exp_name, exp_dir->s, item.s, target)) { 1075 if (exec_check && 1076 !executable(exp_dir->s, exp_name->s, dir_ok)) 1077 break; 1078 #ifdef notdef 1079 /* 1080 * We don't want to stop immediately, because 1081 * we might find an exact/better match later. 1082 */ 1083 d = 0; 1084 done = TRUE; 1085 #endif 1086 d = 3; 1087 } 1088 } 1089 break; 1090 1091 case LIST: 1092 case RECOGNIZE: 1093 case RECOGNIZE_ALL: 1094 case RECOGNIZE_SCROLL: 1095 1096 if ((vp = adrof(STRcomplete)) != NULL && vp->vec != NULL) 1097 for (cp = vp->vec; *cp; cp++) { 1098 if (Strcmp(*cp, STREnhance) == 0) 1099 enhanced = 2; 1100 else if (Strcmp(*cp, STRigncase) == 0) 1101 igncase = 1; 1102 else if (Strcmp(*cp, STRenhance) == 0) 1103 enhanced = 1; 1104 } 1105 1106 if (enhanced || igncase) { 1107 if (!is_prefixmatch(target, item.s, enhanced)) 1108 break; 1109 } else { 1110 if (!is_prefix(target, item.s)) 1111 break; 1112 } 1113 1114 if (exec_check && !executable(exp_dir->s, item.s, dir_ok)) 1115 break; 1116 1117 if (dir_check && !isadirectory(exp_dir->s, item.s)) 1118 break; 1119 1120 if (text_check && isadirectory(exp_dir->s, item.s)) 1121 break; 1122 1123 /* 1124 * Only pattern match directories if we're checking 1125 * for directories. 1126 */ 1127 if (gpat && !Gmatch(item.s, pat) && 1128 (dir_check || !isadirectory(exp_dir->s, item.s))) 1129 break; 1130 1131 /* 1132 * Remove duplicates in command listing and completion 1133 * AFEB added code for TW_LOGNAME and TW_USER cases 1134 */ 1135 if (looking == TW_COMMAND || looking == TW_LOGNAME 1136 || looking == TW_USER || command == LIST) { 1137 buf.len = 0; 1138 Strbuf_append(&buf, item.s); 1139 switch (looking) { 1140 case TW_COMMAND: 1141 if (!(dir_ok && exec_check)) 1142 break; 1143 if (filetype(exp_dir->s, item.s) == '/') 1144 Strbuf_append1(&buf, '/'); 1145 break; 1146 1147 case TW_FILE: 1148 case TW_DIRECTORY: 1149 Strbuf_append1(&buf, filetype(exp_dir->s, item.s)); 1150 break; 1151 1152 default: 1153 break; 1154 } 1155 Strbuf_terminate(&buf); 1156 if ((looking == TW_COMMAND || looking == TW_USER 1157 || looking == TW_LOGNAME) && tw_item_find(buf.s)) 1158 break; 1159 else { 1160 /* maximum length 1 (NULL) + 1 (~ or $) + 1 (filetype) */ 1161 tw_item_add(&buf); 1162 if (command == LIST) 1163 numitems++; 1164 } 1165 } 1166 1167 if (command == RECOGNIZE || command == RECOGNIZE_ALL || 1168 command == RECOGNIZE_SCROLL) { 1169 if (ignoring && ignored(item.s)) { 1170 nignored++; 1171 break; 1172 } 1173 else if (command == RECOGNIZE_SCROLL) { 1174 add_scroll_tab(item.s); 1175 cnt++; 1176 } 1177 1178 if (match_unique_match || is_set(STRrecexact)) { 1179 if (StrQcmp(target, item.s) == 0) { /* EXACT match */ 1180 exp_name->len = 0; 1181 Strbuf_append(exp_name, item.s); 1182 Strbuf_terminate(exp_name); 1183 numitems = 1; /* fake into expanding */ 1184 non_unique_match = TRUE; 1185 done = TRUE; 1186 break; 1187 } 1188 } 1189 if (recognize(exp_name, item.s, name_length, ++numitems, 1190 enhanced, igncase)) 1191 if (command != RECOGNIZE_SCROLL) 1192 done = TRUE; 1193 if (enhanced && exp_name->len < name_length) { 1194 exp_name->len = 0; 1195 Strbuf_append(exp_name, target); 1196 Strbuf_terminate(exp_name); 1197 } 1198 } 1199 break; 1200 1201 default: 1202 break; 1203 } 1204 #ifdef TDEBUG 1205 xprintf("done item = %S\n", item.s); 1206 #endif 1207 } 1208 cleanup_until(&item); 1209 1210 if (command == RECOGNIZE_SCROLL) { 1211 if ((cnt <= curchoice) || (curchoice == -1)) { 1212 curchoice = -1; 1213 nignored = 0; 1214 numitems = 0; 1215 } else if (numitems > 1) { 1216 if (curchoice < -1) 1217 curchoice = cnt - 1; 1218 choose_scroll_tab(exp_name, cnt); 1219 numitems = 1; 1220 } 1221 } 1222 free_scroll_tab(); 1223 1224 if (command == SPELL) 1225 return d; 1226 else { 1227 if (ignoring && numitems == 0 && nignored > 0) 1228 return -nignored; 1229 else 1230 return numitems; 1231 } 1232 } 1233 1234 1235 /* tw_suffix(): 1236 * Find and return the appropriate suffix character 1237 */ 1238 /*ARGSUSED*/ 1239 static Char 1240 tw_suffix(int looking, struct Strbuf *word, const Char *exp_dir, Char *exp_name) 1241 { 1242 Char *ptr; 1243 Char *dol; 1244 struct varent *vp; 1245 1246 (void) strip(exp_name); 1247 1248 switch (looking) { 1249 1250 case TW_LOGNAME: 1251 return '/'; 1252 1253 case TW_VARIABLE: 1254 /* 1255 * Don't consider array variables or empty variables 1256 */ 1257 if ((vp = adrof(exp_name)) != NULL && vp->vec != NULL) { 1258 if ((ptr = vp->vec[0]) == NULL || *ptr == '\0' || 1259 vp->vec[1] != NULL) 1260 return ' '; 1261 } 1262 else if ((ptr = tgetenv(exp_name)) == NULL || *ptr == '\0') 1263 return ' '; 1264 1265 if ((dol = Strrchr(word->s, '$')) != 0 && 1266 dol[1] == '{' && Strchr(dol, '}') == NULL) 1267 return '}'; 1268 1269 return isadirectory(exp_dir, ptr) ? '/' : ' '; 1270 1271 1272 case TW_DIRECTORY: 1273 return '/'; 1274 1275 case TW_COMMAND: 1276 case TW_FILE: 1277 return isadirectory(exp_dir, exp_name) ? '/' : ' '; 1278 1279 case TW_ALIAS: 1280 case TW_VARLIST: 1281 case TW_WORDLIST: 1282 case TW_SHELLVAR: 1283 case TW_ENVVAR: 1284 case TW_USER: 1285 case TW_BINDING: 1286 case TW_LIMIT: 1287 case TW_SIGNAL: 1288 case TW_JOB: 1289 case TW_COMPLETION: 1290 case TW_TEXT: 1291 case TW_GRPNAME: 1292 return ' '; 1293 1294 default: 1295 return '\0'; 1296 } 1297 } /* end tw_suffix */ 1298 1299 1300 /* tw_fixword(): 1301 * Repair a word after a spalling or a recognizwe 1302 */ 1303 static void 1304 tw_fixword(int looking, struct Strbuf *word, Char *dir, Char *exp_name) 1305 { 1306 Char *ptr; 1307 1308 switch (looking) { 1309 case TW_LOGNAME: 1310 word->len = 0; 1311 Strbuf_append1(word, '~'); 1312 break; 1313 1314 case TW_VARIABLE: 1315 if ((ptr = Strrchr(word->s, '$')) != NULL) { 1316 if (ptr[1] == '{') ptr++; 1317 word->len = ptr + 1 - word->s; /* Delete after the dollar */ 1318 } else 1319 word->len = 0; 1320 break; 1321 1322 case TW_DIRECTORY: 1323 case TW_FILE: 1324 case TW_TEXT: 1325 word->len = 0; 1326 Strbuf_append(word, dir); /* put back dir part */ 1327 break; 1328 1329 default: 1330 word->len = 0; 1331 break; 1332 } 1333 1334 (void) quote(exp_name); 1335 Strbuf_append(word, exp_name); /* add extended name */ 1336 Strbuf_terminate(word); 1337 } /* end tw_fixword */ 1338 1339 1340 /* tw_collect(): 1341 * Collect items. Return -1 in case we were interrupted or 1342 * the return value of tw_collect 1343 * This is really a wrapper for tw_collect_items, serving two 1344 * purposes: 1345 * 1. Handles interrupt cleanups. 1346 * 2. Retries if we had no matches, but there were ignored matches 1347 */ 1348 static int 1349 tw_collect(COMMAND command, int looking, struct Strbuf *exp_dir, 1350 struct Strbuf *exp_name, Char *target, Char *pat, int flags, 1351 DIR *dir_fd) 1352 { 1353 volatile int ni; 1354 jmp_buf_t osetexit; 1355 1356 #ifdef TDEBUG 1357 xprintf("target = %S\n", target); 1358 #endif 1359 ni = 0; 1360 getexit(osetexit); 1361 for (;;) { 1362 volatile size_t omark; 1363 1364 (*tw_start_entry[looking])(dir_fd, pat); 1365 InsideCompletion = 1; 1366 if (setexit()) { 1367 cleanup_pop_mark(omark); 1368 resexit(osetexit); 1369 /* interrupted, clean up */ 1370 haderr = 0; 1371 ni = -1; /* flag error */ 1372 break; 1373 } 1374 omark = cleanup_push_mark(); 1375 ni = tw_collect_items(command, looking, exp_dir, exp_name, target, pat, 1376 ni >= 0 ? flags : flags & ~TW_IGN_OK); 1377 cleanup_pop_mark(omark); 1378 resexit(osetexit); 1379 if (ni >= 0) 1380 break; 1381 } 1382 InsideCompletion = 0; 1383 #if defined(SOLARIS2) && defined(i386) && !defined(__GNUC__) 1384 /* Compiler bug? (from PWP) */ 1385 if ((looking == TW_LOGNAME) || (looking == TW_USER)) 1386 tw_logname_end(); 1387 else if (looking == TW_GRPNAME) 1388 tw_grpname_end(); 1389 else 1390 tw_dir_end(); 1391 #else /* !(SOLARIS2 && i386 && !__GNUC__) */ 1392 (*tw_end_entry[looking])(); 1393 #endif /* !(SOLARIS2 && i386 && !__GNUC__) */ 1394 return(ni); 1395 } /* end tw_collect */ 1396 1397 1398 /* tw_list_items(): 1399 * List the items that were found 1400 * 1401 * NOTE instead of looking at numerical vars listmax and listmaxrows 1402 * we can look at numerical var listmax, and have a string value 1403 * listmaxtype (or similar) than can have values 'items' and 'rows' 1404 * (by default interpreted as 'items', for backwards compatibility) 1405 */ 1406 static void 1407 tw_list_items(int looking, int numitems, int list_max) 1408 { 1409 Char *ptr; 1410 int max_items = 0; 1411 int max_rows = 0; 1412 1413 if (numitems == 0) 1414 return; 1415 1416 if ((ptr = varval(STRlistmax)) != STRNULL) { 1417 while (*ptr) { 1418 if (!Isdigit(*ptr)) { 1419 max_items = 0; 1420 break; 1421 } 1422 max_items = max_items * 10 + *ptr++ - '0'; 1423 } 1424 if ((max_items > 0) && (numitems > max_items) && list_max) 1425 max_items = numitems; 1426 else 1427 max_items = 0; 1428 } 1429 1430 if (max_items == 0 && (ptr = varval(STRlistmaxrows)) != STRNULL) { 1431 int rows; 1432 1433 while (*ptr) { 1434 if (!Isdigit(*ptr)) { 1435 max_rows = 0; 1436 break; 1437 } 1438 max_rows = max_rows * 10 + *ptr++ - '0'; 1439 } 1440 if (max_rows != 0 && looking != TW_JOB) 1441 rows = find_rows(tw_item_get(), numitems, TRUE); 1442 else 1443 rows = numitems; /* underestimate for lines wider than the termH */ 1444 if ((max_rows > 0) && (rows > max_rows) && list_max) 1445 max_rows = rows; 1446 else 1447 max_rows = 0; 1448 } 1449 1450 1451 if (max_items || max_rows) { 1452 char tc, *sname; 1453 const char *name; 1454 int maxs; 1455 1456 if (max_items) { 1457 name = CGETS(30, 5, "items"); 1458 maxs = max_items; 1459 } 1460 else { 1461 name = CGETS(30, 6, "rows"); 1462 maxs = max_rows; 1463 } 1464 1465 sname = strsave(name); 1466 cleanup_push(sname, xfree); 1467 xprintf(CGETS(30, 7, "There are %d %s, list them anyway? [n/y] "), 1468 maxs, sname); 1469 cleanup_until(sname); 1470 flush(); 1471 /* We should be in Rawmode here, so no \n to catch */ 1472 (void) xread(SHIN, &tc, 1); 1473 xprintf("%c\r\n", tc); /* echo the char, do a newline */ 1474 /* 1475 * Perhaps we should use the yesexpr from the 1476 * actual locale 1477 */ 1478 if (strchr(CGETS(30, 13, "Yy"), tc) == NULL) 1479 return; 1480 } 1481 1482 if (looking != TW_SIGNAL) 1483 qsort(tw_item_get(), numitems, sizeof(Char *), fcompare); 1484 if (looking != TW_JOB) 1485 print_by_column(STRNULL, tw_item_get(), numitems, TRUE); 1486 else { 1487 /* 1488 * print one item on every line because jobs can have spaces 1489 * and it is confusing. 1490 */ 1491 int i; 1492 Char **w = tw_item_get(); 1493 1494 for (i = 0; i < numitems; i++) { 1495 xprintf("%S", w[i]); 1496 if (Tty_raw_mode) 1497 xputchar('\r'); 1498 xputchar('\n'); 1499 } 1500 } 1501 } /* end tw_list_items */ 1502 1503 1504 /* t_search(): 1505 * Perform a RECOGNIZE, LIST or SPELL command on string "word". 1506 * 1507 * Return value: 1508 * >= 0: SPELL command: "distance" (see spdist()) 1509 * other: No. of items found 1510 * < 0: Error (message or beep is output) 1511 */ 1512 /*ARGSUSED*/ 1513 int 1514 t_search(struct Strbuf *word, COMMAND command, int looking, int list_max, 1515 Char *pat, eChar suf) 1516 { 1517 int numitems, /* Number of items matched */ 1518 flags = 0, /* search flags */ 1519 gpat = pat[0] != '\0', /* Glob pattern search */ 1520 res; /* Return value */ 1521 struct Strbuf exp_dir = Strbuf_INIT;/* dir after ~ expansion */ 1522 struct Strbuf dir = Strbuf_INIT; /* /x/y/z/ part in /x/y/z/f */ 1523 struct Strbuf exp_name = Strbuf_INIT;/* the recognized (extended) */ 1524 Char *name, /* f part in /d/d/d/f name */ 1525 *target; /* Target to expand/correct/list */ 1526 DIR *dir_fd = NULL; 1527 1528 /* 1529 * bugfix by Marty Grossman (grossman@CC5.BBN.COM): directory listing can 1530 * dump core when interrupted 1531 */ 1532 tw_item_free(); 1533 1534 non_unique_match = FALSE; /* See the recexact code below */ 1535 1536 extract_dir_and_name(word->s, &dir, &name); 1537 cleanup_push(&dir, Strbuf_cleanup); 1538 cleanup_push(&name, xfree_indirect); 1539 1540 /* 1541 * SPECIAL HARDCODED COMPLETIONS: 1542 * foo$variable -> TW_VARIABLE 1543 * ~user -> TW_LOGNAME 1544 * 1545 */ 1546 if ((*word->s == '~') && (Strchr(word->s, '/') == NULL)) { 1547 looking = TW_LOGNAME; 1548 target = name; 1549 gpat = 0; /* Override pattern mechanism */ 1550 } 1551 else if ((target = Strrchr(name, '$')) != 0 && 1552 (target[1] != '{' || Strchr(target, '}') == NULL) && 1553 (Strchr(name, '/') == NULL)) { 1554 target++; 1555 if (target[0] == '{') target++; 1556 looking = TW_VARIABLE; 1557 gpat = 0; /* Override pattern mechanism */ 1558 } 1559 else 1560 target = name; 1561 1562 /* 1563 * Try to figure out what we should be looking for 1564 */ 1565 if (looking & TW_PATH) { 1566 gpat = 0; /* pattern holds the pathname to be used */ 1567 Strbuf_append(&exp_dir, pat); 1568 if (exp_dir.len != 0 && exp_dir.s[exp_dir.len - 1] != '/') 1569 Strbuf_append1(&exp_dir, '/'); 1570 Strbuf_append(&exp_dir, dir.s); 1571 } 1572 Strbuf_terminate(&exp_dir); 1573 cleanup_push(&exp_dir, Strbuf_cleanup); 1574 1575 switch (looking & ~TW_PATH) { 1576 case TW_NONE: 1577 res = -1; 1578 goto err_dir; 1579 1580 case TW_ZERO: 1581 looking = TW_FILE; 1582 break; 1583 1584 case TW_COMMAND: 1585 if (Strchr(word->s, '/') || (looking & TW_PATH)) { 1586 looking = TW_FILE; 1587 flags |= TW_EXEC_CHK; 1588 flags |= TW_DIR_OK; 1589 } 1590 #ifdef notdef 1591 /* PWP: don't even bother when doing ALL of the commands */ 1592 if (looking == TW_COMMAND && word->len == 0) { 1593 res = -1; 1594 goto err_dir; 1595 } 1596 #endif 1597 break; 1598 1599 1600 case TW_VARLIST: 1601 case TW_WORDLIST: 1602 gpat = 0; /* pattern holds the name of the variable */ 1603 break; 1604 1605 case TW_EXPLAIN: 1606 if (command == LIST && pat != NULL) { 1607 xprintf("%S", pat); 1608 if (Tty_raw_mode) 1609 xputchar('\r'); 1610 xputchar('\n'); 1611 } 1612 res = 2; 1613 goto err_dir; 1614 1615 default: 1616 break; 1617 } 1618 1619 /* 1620 * let fignore work only when we are not using a pattern 1621 */ 1622 flags |= (gpat == 0) ? TW_IGN_OK : TW_PAT_OK; 1623 1624 #ifdef TDEBUG 1625 xprintf(CGETS(30, 8, "looking = %d\n"), looking); 1626 #endif 1627 1628 switch (looking) { 1629 Char *user_name; 1630 1631 case TW_ALIAS: 1632 case TW_SHELLVAR: 1633 case TW_ENVVAR: 1634 case TW_BINDING: 1635 case TW_LIMIT: 1636 case TW_SIGNAL: 1637 case TW_JOB: 1638 case TW_COMPLETION: 1639 case TW_GRPNAME: 1640 break; 1641 1642 1643 case TW_VARIABLE: 1644 if ((res = expand_dir(dir.s, &exp_dir, &dir_fd, command)) != 0) 1645 goto err_dir; 1646 break; 1647 1648 case TW_DIRECTORY: 1649 flags |= TW_DIR_CHK; 1650 1651 #ifdef notyet 1652 /* 1653 * This is supposed to expand the directory stack. 1654 * Problems: 1655 * 1. Slow 1656 * 2. directories with the same name 1657 */ 1658 flags |= TW_DIR_OK; 1659 #endif 1660 #ifdef notyet 1661 /* 1662 * Supposed to do delayed expansion, but it is inconsistent 1663 * from a user-interface point of view, since it does not 1664 * immediately obey addsuffix 1665 */ 1666 if ((res = expand_dir(dir.s, &exp_dir, &dir_fd, command)) != 0) 1667 goto err_dir; 1668 if (isadirectory(exp_dir.s, name)) { 1669 if (exp_dir.len != 0 || name[0] != '\0') { 1670 Strbuf_append(&dir, name); 1671 if (dir.s[dir.len - 1] != '/') 1672 Strbuf_append1(&dir, '/'); 1673 Strbuf_terminate(&dir); 1674 if ((res = expand_dir(dir.s, &exp_dir, &dir_fd, command)) != 0) 1675 goto err_dir; 1676 if (word->len != 0 && word->s[word->len - 1] != '/') { 1677 Strbuf_append1(word, '/'); 1678 Strbuf_terminate(word); 1679 } 1680 name[0] = '\0'; 1681 } 1682 } 1683 #endif 1684 if ((res = expand_dir(dir.s, &exp_dir, &dir_fd, command)) != 0) 1685 goto err_dir; 1686 break; 1687 1688 case TW_TEXT: 1689 flags |= TW_TEXT_CHK; 1690 /*FALLTHROUGH*/ 1691 case TW_FILE: 1692 if ((res = expand_dir(dir.s, &exp_dir, &dir_fd, command)) != 0) 1693 goto err_dir; 1694 break; 1695 1696 case TW_PATH | TW_TEXT: 1697 case TW_PATH | TW_FILE: 1698 case TW_PATH | TW_DIRECTORY: 1699 case TW_PATH | TW_COMMAND: 1700 if ((dir_fd = opendir(short2str(exp_dir.s))) == NULL) { 1701 if (command == RECOGNIZE) 1702 xprintf("\n"); 1703 xprintf("%S: %s", exp_dir.s, strerror(errno)); 1704 if (command != RECOGNIZE) 1705 xprintf("\n"); 1706 NeedsRedraw = 1; 1707 res = -1; 1708 goto err_dir; 1709 } 1710 if (exp_dir.len != 0 && exp_dir.s[exp_dir.len - 1] != '/') { 1711 Strbuf_append1(&exp_dir, '/'); 1712 Strbuf_terminate(&exp_dir); 1713 } 1714 1715 looking &= ~TW_PATH; 1716 1717 switch (looking) { 1718 case TW_TEXT: 1719 flags |= TW_TEXT_CHK; 1720 break; 1721 1722 case TW_FILE: 1723 break; 1724 1725 case TW_DIRECTORY: 1726 flags |= TW_DIR_CHK; 1727 break; 1728 1729 case TW_COMMAND: 1730 xfree(name); 1731 target = name = Strsave(word->s); /* so it can match things */ 1732 break; 1733 1734 default: 1735 abort(); /* Cannot happen */ 1736 break; 1737 } 1738 break; 1739 1740 case TW_LOGNAME: 1741 user_name = word->s + 1; 1742 goto do_user; 1743 1744 /*FALLTHROUGH*/ 1745 case TW_USER: 1746 user_name = word->s; 1747 do_user: 1748 /* 1749 * Check if the spelling was already correct 1750 * From: Rob McMahon <cudcv@cu.warwick.ac.uk> 1751 */ 1752 if (command == SPELL && xgetpwnam(short2str(user_name)) != NULL) { 1753 #ifdef YPBUGS 1754 fix_yp_bugs(); 1755 #endif /* YPBUGS */ 1756 res = 0; 1757 goto err_dir; 1758 } 1759 xfree(name); 1760 target = name = Strsave(user_name); 1761 break; 1762 1763 case TW_COMMAND: 1764 case TW_VARLIST: 1765 case TW_WORDLIST: 1766 target = name = Strsave(word->s); /* so it can match things */ 1767 break; 1768 1769 default: 1770 xprintf(CGETS(30, 9, 1771 "\n%s internal error: I don't know what I'm looking for!\n"), 1772 progname); 1773 NeedsRedraw = 1; 1774 res = -1; 1775 goto err_dir; 1776 } 1777 1778 cleanup_push(&exp_name, Strbuf_cleanup); 1779 numitems = tw_collect(command, looking, &exp_dir, &exp_name, target, pat, 1780 flags, dir_fd); 1781 if (numitems == -1) 1782 goto end; 1783 1784 switch (command) { 1785 case RECOGNIZE: 1786 case RECOGNIZE_ALL: 1787 case RECOGNIZE_SCROLL: 1788 if (numitems <= 0) 1789 break; 1790 1791 Strbuf_terminate(&exp_name); 1792 tw_fixword(looking, word, dir.s, exp_name.s); 1793 1794 if (!match_unique_match && is_set(STRaddsuffix) && numitems == 1) { 1795 switch (suf) { 1796 case 0: /* Automatic suffix */ 1797 Strbuf_append1(word, 1798 tw_suffix(looking, word, exp_dir.s, exp_name.s)); 1799 break; 1800 1801 case CHAR_ERR: /* No suffix */ 1802 break; 1803 1804 default: /* completion specified suffix */ 1805 Strbuf_append1(word, suf); 1806 break; 1807 } 1808 Strbuf_terminate(word); 1809 } 1810 break; 1811 1812 case LIST: 1813 tw_list_items(looking, numitems, list_max); 1814 tw_item_free(); 1815 break; 1816 1817 case SPELL: 1818 Strbuf_terminate(&exp_name); 1819 tw_fixword(looking, word, dir.s, exp_name.s); 1820 break; 1821 1822 default: 1823 xprintf("Bad tw_command\n"); 1824 numitems = 0; 1825 } 1826 end: 1827 res = numitems; 1828 err_dir: 1829 cleanup_until(&dir); 1830 return res; 1831 } /* end t_search */ 1832 1833 1834 /* extract_dir_and_name(): 1835 * parse full path in file into 2 parts: directory and file names 1836 * Should leave final slash (/) at end of dir. 1837 */ 1838 static void 1839 extract_dir_and_name(const Char *path, struct Strbuf *dir, Char **name) 1840 { 1841 Char *p; 1842 1843 p = Strrchr(path, '/'); 1844 #ifdef WINNT_NATIVE 1845 if (p == NULL) 1846 p = Strrchr(path, ':'); 1847 #endif /* WINNT_NATIVE */ 1848 if (p == NULL) 1849 *name = Strsave(path); 1850 else { 1851 p++; 1852 *name = Strsave(p); 1853 Strbuf_appendn(dir, path, p - path); 1854 } 1855 Strbuf_terminate(dir); 1856 } /* end extract_dir_and_name */ 1857 1858 1859 /* dollar(): 1860 * expand "/$old1/$old2/old3/" 1861 * to "/value_of_old1/value_of_old2/old3/" 1862 */ 1863 Char * 1864 dollar(const Char *old) 1865 { 1866 struct Strbuf buf = Strbuf_INIT; 1867 1868 while (*old) { 1869 if (*old != '$') 1870 Strbuf_append1(&buf, *old++); 1871 else { 1872 if (expdollar(&buf, &old, QUOTE) == 0) { 1873 xfree(buf.s); 1874 return NULL; 1875 } 1876 } 1877 } 1878 return Strbuf_finish(&buf); 1879 } /* end dollar */ 1880 1881 1882 /* tilde(): 1883 * expand ~person/foo to home_directory_of_person/foo 1884 * or =<stack-entry> to <dir in stack entry> 1885 */ 1886 static int 1887 tilde(struct Strbuf *new, Char *old) 1888 { 1889 Char *o, *p; 1890 1891 new->len = 0; 1892 switch (old[0]) { 1893 case '~': { 1894 Char *name, *home; 1895 1896 old++; 1897 for (o = old; *o && *o != '/'; o++) 1898 continue; 1899 name = Strnsave(old, o - old); 1900 home = gethdir(name); 1901 xfree(name); 1902 if (home == NULL) 1903 goto err; 1904 Strbuf_append(new, home); 1905 xfree(home); 1906 /* If the home directory expands to "/", we do 1907 * not want to create "//" by appending a slash from o. 1908 */ 1909 if (new->s[0] == '/' && new->len == 1 && *o == '/') 1910 ++o; 1911 Strbuf_append(new, o); 1912 break; 1913 } 1914 1915 case '=': 1916 if ((p = globequal(old)) == NULL) 1917 goto err; 1918 if (p != old) { 1919 Strbuf_append(new, p); 1920 xfree(p); 1921 break; 1922 } 1923 /*FALLTHROUGH*/ 1924 1925 default: 1926 Strbuf_append(new, old); 1927 break; 1928 } 1929 Strbuf_terminate(new); 1930 return 0; 1931 1932 err: 1933 Strbuf_terminate(new); 1934 return -1; 1935 } /* end tilde */ 1936 1937 1938 /* expand_dir(): 1939 * Open the directory given, expanding ~user and $var 1940 * Optionally normalize the path given 1941 */ 1942 static int 1943 expand_dir(const Char *dir, struct Strbuf *edir, DIR **dfd, COMMAND cmd) 1944 { 1945 Char *nd = NULL; 1946 Char *tdir; 1947 1948 tdir = dollar(dir); 1949 cleanup_push(tdir, xfree); 1950 if (tdir == NULL || 1951 (tilde(edir, tdir) != 0) || 1952 !(nd = dnormalize(edir->len ? edir->s : STRdot, 1953 symlinks == SYM_IGNORE || symlinks == SYM_EXPAND)) || 1954 ((*dfd = opendir(short2str(nd))) == NULL)) { 1955 xfree(nd); 1956 if (cmd == SPELL || SearchNoDirErr) { 1957 cleanup_until(tdir); 1958 return (-2); 1959 } 1960 /* 1961 * From: Amos Shapira <amoss@cs.huji.ac.il> 1962 * Print a better message when completion fails 1963 */ 1964 xprintf("\n%S %s\n", edir->len ? edir->s : (tdir ? tdir : dir), 1965 (errno == ENOTDIR ? CGETS(30, 10, "not a directory") : 1966 (errno == ENOENT ? CGETS(30, 11, "not found") : 1967 CGETS(30, 12, "unreadable")))); 1968 NeedsRedraw = 1; 1969 cleanup_until(tdir); 1970 return (-1); 1971 } 1972 cleanup_until(tdir); 1973 if (nd) { 1974 if (*dir != '\0') { 1975 int slash; 1976 1977 /* 1978 * Copy and append a / if there was one 1979 */ 1980 slash = edir->len != 0 && edir->s[edir->len - 1] == '/'; 1981 edir->len = 0; 1982 Strbuf_append(edir, nd); 1983 if (slash != 0 && edir->s[edir->len - 1] != '/') 1984 Strbuf_append1(edir, '/'); 1985 Strbuf_terminate(edir); 1986 } 1987 xfree(nd); 1988 } 1989 return 0; 1990 } /* end expand_dir */ 1991 1992 1993 /* nostat(): 1994 * Returns true if the directory should not be stat'd, 1995 * false otherwise. 1996 * This way, things won't grind to a halt when you complete in /afs 1997 * or very large directories. 1998 */ 1999 static int 2000 nostat(Char *dir) 2001 { 2002 struct varent *vp; 2003 Char **cp; 2004 2005 if ((vp = adrof(STRnostat)) == NULL || (cp = vp->vec) == NULL) 2006 return FALSE; 2007 for (; *cp != NULL; cp++) { 2008 if (Strcmp(*cp, STRstar) == 0) 2009 return TRUE; 2010 if (Gmatch(dir, *cp)) 2011 return TRUE; 2012 } 2013 return FALSE; 2014 } /* end nostat */ 2015 2016 2017 /* filetype(): 2018 * Return a character that signifies a filetype 2019 * symbology from 4.3 ls command. 2020 */ 2021 static Char 2022 filetype(Char *dir, Char *file) 2023 { 2024 if (dir) { 2025 Char *path; 2026 char *ptr; 2027 struct stat statb; 2028 2029 if (nostat(dir)) return(' '); 2030 2031 path = Strspl(dir, file); 2032 ptr = short2str(path); 2033 xfree(path); 2034 2035 if (lstat(ptr, &statb) != -1) { 2036 #ifdef S_ISLNK 2037 if (S_ISLNK(statb.st_mode)) { /* Symbolic link */ 2038 if (adrof(STRlistlinks)) { 2039 if (stat(ptr, &statb) == -1) 2040 return ('&'); 2041 else if (S_ISDIR(statb.st_mode)) 2042 return ('>'); 2043 else 2044 return ('@'); 2045 } 2046 else 2047 return ('@'); 2048 } 2049 #endif 2050 #ifdef S_ISSOCK 2051 if (S_ISSOCK(statb.st_mode)) /* Socket */ 2052 return ('='); 2053 #endif 2054 #ifdef S_ISFIFO 2055 if (S_ISFIFO(statb.st_mode)) /* Named Pipe */ 2056 return ('|'); 2057 #endif 2058 #ifdef S_ISHIDDEN 2059 if (S_ISHIDDEN(statb.st_mode)) /* Hidden Directory [aix] */ 2060 return ('+'); 2061 #endif 2062 #ifdef S_ISCDF 2063 { 2064 struct stat hpstatb; 2065 char *p2; 2066 2067 p2 = strspl(ptr, "+"); /* Must append a '+' and re-stat(). */ 2068 if ((stat(p2, &hpstatb) != -1) && S_ISCDF(hpstatb.st_mode)) { 2069 xfree(p2); 2070 return ('+'); /* Context Dependent Files [hpux] */ 2071 } 2072 xfree(p2); 2073 } 2074 #endif 2075 #ifdef S_ISNWK 2076 if (S_ISNWK(statb.st_mode)) /* Network Special [hpux] */ 2077 return (':'); 2078 #endif 2079 #ifdef S_ISCHR 2080 if (S_ISCHR(statb.st_mode)) /* char device */ 2081 return ('%'); 2082 #endif 2083 #ifdef S_ISBLK 2084 if (S_ISBLK(statb.st_mode)) /* block device */ 2085 return ('#'); 2086 #endif 2087 #ifdef S_ISDIR 2088 if (S_ISDIR(statb.st_mode)) /* normal Directory */ 2089 return ('/'); 2090 #endif 2091 if (statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) 2092 return ('*'); 2093 } 2094 } 2095 return (' '); 2096 } /* end filetype */ 2097 2098 2099 /* isadirectory(): 2100 * Return trus if the file is a directory 2101 */ 2102 static int 2103 isadirectory(const Char *dir, const Char *file) 2104 /* return 1 if dir/file is a directory */ 2105 /* uses stat rather than lstat to get dest. */ 2106 { 2107 if (dir) { 2108 Char *path; 2109 char *cpath; 2110 struct stat statb; 2111 2112 path = Strspl(dir, file); 2113 cpath = short2str(path); 2114 xfree(path); 2115 if (stat(cpath, &statb) >= 0) { /* resolve through symlink */ 2116 #ifdef S_ISSOCK 2117 if (S_ISSOCK(statb.st_mode)) /* Socket */ 2118 return 0; 2119 #endif 2120 #ifdef S_ISFIFO 2121 if (S_ISFIFO(statb.st_mode)) /* Named Pipe */ 2122 return 0; 2123 #endif 2124 if (S_ISDIR(statb.st_mode)) /* normal Directory */ 2125 return 1; 2126 } 2127 } 2128 return 0; 2129 } /* end isadirectory */ 2130 2131 2132 2133 /* find_rows(): 2134 * Return how many rows needed to print sorted down columns 2135 */ 2136 static int 2137 find_rows(Char *items[], int count, int no_file_suffix) 2138 { 2139 int i, columns, rows; 2140 unsigned int maxwidth = 0; 2141 2142 for (i = 0; i < count; i++) /* find widest string */ 2143 maxwidth = max(maxwidth, (unsigned int) Strlen(items[i])); 2144 2145 maxwidth += no_file_suffix ? 1 : 2; /* for the file tag and space */ 2146 columns = (TermH + 1) / maxwidth; /* PWP: terminal size change */ 2147 if (!columns) 2148 columns = 1; 2149 rows = (count + (columns - 1)) / columns; 2150 2151 return rows; 2152 } /* end rows_needed_by_print_by_column */ 2153 2154 2155 /* print_by_column(): 2156 * Print sorted down columns or across columns when the first 2157 * word of $listflags shell variable contains 'x'. 2158 * 2159 */ 2160 void 2161 print_by_column(Char *dir, Char *items[], int count, int no_file_suffix) 2162 { 2163 int i, r, c, columns, rows; 2164 size_t w; 2165 unsigned int wx, maxwidth = 0; 2166 Char *val; 2167 int across; 2168 2169 lbuffed = 0; /* turn off line buffering */ 2170 2171 2172 across = ((val = varval(STRlistflags)) != STRNULL) && 2173 (Strchr(val, 'x') != NULL); 2174 2175 for (i = 0; i < count; i++) { /* find widest string */ 2176 maxwidth = max(maxwidth, (unsigned int) NLSStringWidth(items[i])); 2177 } 2178 2179 maxwidth += no_file_suffix ? 1 : 2; /* for the file tag and space */ 2180 columns = TermH / maxwidth; /* PWP: terminal size change */ 2181 if (!columns || !isatty(didfds ? 1 : SHOUT)) 2182 columns = 1; 2183 rows = (count + (columns - 1)) / columns; 2184 2185 i = -1; 2186 for (r = 0; r < rows; r++) { 2187 for (c = 0; c < columns; c++) { 2188 i = across ? (i + 1) : (c * rows + r); 2189 2190 if (i < count) { 2191 wx = 0; 2192 w = Strlen(items[i]); 2193 2194 #ifdef COLOR_LS_F 2195 if (no_file_suffix) { 2196 /* Print the command name */ 2197 Char f = items[i][w - 1]; 2198 items[i][w - 1] = 0; 2199 print_with_color(items[i], w - 1, f); 2200 items[i][w - 1] = f; 2201 } 2202 else { 2203 /* Print filename followed by '/' or '*' or ' ' */ 2204 print_with_color(items[i], w, filetype(dir, items[i])); 2205 wx++; 2206 } 2207 #else /* ifndef COLOR_LS_F */ 2208 if (no_file_suffix) { 2209 /* Print the command name */ 2210 xprintf("%S", items[i]); 2211 } 2212 else { 2213 /* Print filename followed by '/' or '*' or ' ' */ 2214 xprintf("%-S%c", items[i], filetype(dir, items[i])); 2215 wx++; 2216 } 2217 #endif /* COLOR_LS_F */ 2218 2219 if (c < (columns - 1)) { /* Not last column? */ 2220 w = NLSStringWidth(items[i]) + wx; 2221 for (; w < maxwidth; w++) 2222 xputchar(' '); 2223 } 2224 } 2225 else if (across) 2226 break; 2227 } 2228 if (Tty_raw_mode) 2229 xputchar('\r'); 2230 xputchar('\n'); 2231 } 2232 2233 lbuffed = 1; /* turn back on line buffering */ 2234 flush(); 2235 } /* end print_by_column */ 2236 2237 2238 /* StrQcmp(): 2239 * Compare strings ignoring the quoting chars 2240 */ 2241 int 2242 StrQcmp(const Char *str1, const Char *str2) 2243 { 2244 for (; *str1 && samecase(*str1 & TRIM) == samecase(*str2 & TRIM); 2245 str1++, str2++) 2246 continue; 2247 /* 2248 * The following case analysis is necessary so that characters which look 2249 * negative collate low against normal characters but high against the 2250 * end-of-string NUL. 2251 */ 2252 if (*str1 == '\0' && *str2 == '\0') 2253 return (0); 2254 else if (*str1 == '\0') 2255 return (-1); 2256 else if (*str2 == '\0') 2257 return (1); 2258 else 2259 return ((*str1 & TRIM) - (*str2 & TRIM)); 2260 } /* end StrQcmp */ 2261 2262 2263 /* fcompare(): 2264 * Comparison routine for qsort, (Char **, Char **) 2265 */ 2266 int 2267 fcompare(const void *xfile1, const void *xfile2) 2268 { 2269 const Char *const *file1 = xfile1, *const *file2 = xfile2; 2270 2271 return collate(*file1, *file2); 2272 } /* end fcompare */ 2273 2274 2275 /* catn(): 2276 * Concatenate src onto tail of des. 2277 * Des is a string whose maximum length is count. 2278 * Always null terminate. 2279 */ 2280 void 2281 catn(Char *des, const Char *src, int count) 2282 { 2283 while (*des && --count > 0) 2284 des++; 2285 while (--count > 0) 2286 if ((*des++ = *src++) == 0) 2287 return; 2288 *des = '\0'; 2289 } /* end catn */ 2290 2291 2292 /* copyn(): 2293 * like strncpy but always leave room for trailing \0 2294 * and always null terminate. 2295 */ 2296 void 2297 copyn(Char *des, const Char *src, size_t count) 2298 { 2299 while (--count != 0) 2300 if ((*des++ = *src++) == 0) 2301 return; 2302 *des = '\0'; 2303 } /* end copyn */ 2304 2305 2306 /* tgetenv(): 2307 * like it's normal string counter-part 2308 */ 2309 Char * 2310 tgetenv(Char *str) 2311 { 2312 Char **var; 2313 size_t len; 2314 int res; 2315 2316 len = Strlen(str); 2317 /* Search the STR_environ for the entry matching str. */ 2318 for (var = STR_environ; var != NULL && *var != NULL; var++) 2319 if (Strlen(*var) >= len && (*var)[len] == '=') { 2320 /* Temporarily terminate the string so we can copy the variable 2321 name. */ 2322 (*var)[len] = '\0'; 2323 res = StrQcmp(*var, str); 2324 /* Restore the '=' and return a pointer to the value of the 2325 environment variable. */ 2326 (*var)[len] = '='; 2327 if (res == 0) 2328 return (&((*var)[len + 1])); 2329 } 2330 return (NULL); 2331 } /* end tgetenv */ 2332 2333 2334 struct scroll_tab_list *scroll_tab = 0; 2335 2336 static void 2337 add_scroll_tab(Char *item) 2338 { 2339 struct scroll_tab_list *new_scroll; 2340 2341 new_scroll = xmalloc(sizeof(struct scroll_tab_list)); 2342 new_scroll->element = Strsave(item); 2343 new_scroll->next = scroll_tab; 2344 scroll_tab = new_scroll; 2345 } 2346 2347 static void 2348 choose_scroll_tab(struct Strbuf *exp_name, int cnt) 2349 { 2350 struct scroll_tab_list *loop; 2351 int tmp = cnt; 2352 Char **ptr; 2353 2354 ptr = xmalloc(sizeof(Char *) * cnt); 2355 cleanup_push(ptr, xfree); 2356 2357 for(loop = scroll_tab; loop && (tmp >= 0); loop = loop->next) 2358 ptr[--tmp] = loop->element; 2359 2360 qsort(ptr, cnt, sizeof(Char *), fcompare); 2361 2362 exp_name->len = 0; 2363 Strbuf_append(exp_name, ptr[curchoice]); 2364 Strbuf_terminate(exp_name); 2365 cleanup_until(ptr); 2366 } 2367 2368 static void 2369 free_scroll_tab(void) 2370 { 2371 struct scroll_tab_list *loop; 2372 2373 while(scroll_tab) { 2374 loop = scroll_tab; 2375 scroll_tab = scroll_tab->next; 2376 xfree(loop->element); 2377 xfree(loop); 2378 } 2379 } 2380