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