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