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