source: trunk/third/tcsh/tw.parse.c @ 12039

Revision 12039, 54.0 KB checked in by danw, 26 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r12038, which included commits to RCS files with non-trunk default branches.
Line 
1/* $Header: /afs/dev.mit.edu/source/repository/third/tcsh/tw.parse.c,v 1.1.1.2 1998-10-03 21:10:22 danw 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 1.1.1.2 1998-10-03 21:10:22 danw 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 - (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 = 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}
Note: See TracBrowser for help on using the repository browser.