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

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