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