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