home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 5 Edit / 05-Edit.zip / vile-src.zip / vile-8.1 / filec.c < prev    next >
C/C++ Source or Header  |  1998-04-28  |  29KB  |  1,264 lines

  1. /*
  2.  *    filec.c
  3.  *
  4.  * Filename prompting and completion routines
  5.  * Written by T.E.Dickey for vile (march 1993).
  6.  *
  7.  *
  8.  * $Header: /usr/build/vile/vile/RCS/filec.c,v 1.81 1998/04/28 10:16:26 tom Exp $
  9.  *
  10.  */
  11.  
  12. #ifdef _WIN32
  13. # include <windows.h>
  14. #endif
  15.  
  16. #include "estruct.h"
  17. #include "edef.h"
  18.  
  19. #undef USE_QSORT
  20. #if SYS_OS2
  21. # define USE_QSORT 0
  22. # define INCL_DOSFILEMGR
  23. # define INCL_ERRORS
  24. # include <os2.h>
  25. # define FoundDirectory(path) ((fb.attrFile & FILE_DIRECTORY) != 0)
  26. #endif
  27.  
  28. #ifndef USE_QSORT
  29. #define USE_QSORT 1
  30. #endif
  31.  
  32. /* Systems that can have leafnames beginning with '.' (this doesn't include
  33.  * VMS, which does allow this as unlikely as that may seem, because the logic
  34.  * that masks dots simply won't work in conjunction with the other translations
  35.  * to/from hybrid form).
  36.  */
  37. #if (SYS_UNIX || SYS_WINNT) && ! OPT_VMS_PATH
  38. #define USE_DOTNAMES 1
  39. #else
  40. #define USE_DOTNAMES 0
  41. #endif
  42.  
  43. #define isDotname(leaf) (!strcmp(leaf, ".") || !strcmp(leaf, ".."))
  44.  
  45. #if (MISSING_EXTERN_ENVIRON || __DJGPP__ >= 2)
  46. extern char **environ;
  47. #endif
  48.  
  49. #define    SLASH (EOS+1) /* less than everything but EOS */
  50.  
  51. #if OPT_VMS_PATH
  52. #define    KBD_OPTIONS    KBD_NORMAL|KBD_UPPERC
  53. #endif
  54.  
  55. #ifndef    KBD_OPTIONS
  56. #define    KBD_OPTIONS    KBD_NORMAL
  57. #endif
  58.  
  59. #ifndef FoundDirectory
  60. # define FoundDirectory(path) is_directory(path)
  61. #endif
  62.  
  63. static    char    **MyGlob;    /* expanded list */
  64. static    int    in_glob;    /* index into MyGlob[] */
  65. static    int    only_dir;    /* can only match real directories */
  66.  
  67. static void
  68. free_expansion (void)
  69. {
  70.     MyGlob = glob_free(MyGlob);
  71.     in_glob = -1;
  72. }
  73.  
  74. #if COMPLETE_DIRS || COMPLETE_FILES
  75.  
  76. #include "dirstuff.h"
  77.  
  78. #if OPT_VMS_PATH
  79. static    void    vms2hybrid(char *path);
  80. #endif
  81.  
  82. /*--------------------------------------------------------------------------*/
  83.  
  84. /*
  85.  * Test if the path has a trailing slash-delimiter (i.e., can be syntactically
  86.  * distinguished from non-directory paths).
  87.  */
  88. static int
  89. trailing_slash(const char * path)
  90. {
  91.     register int    len = strlen(path);
  92.     if (len > 0) {
  93. #if OPT_VMS_PATH
  94.         if (is_vms_pathname(path, TRUE))
  95.             return TRUE;
  96. #endif
  97.         return (is_slashc(path[len-1]));
  98.     }
  99.     return FALSE;
  100. }
  101.  
  102. /*
  103.  * Force a trailing slash on the end of the path, returns the length of the
  104.  * resulting path.
  105.  */
  106. static SIZE_T
  107. force_slash(char * path)
  108. {
  109.     register SIZE_T    len = strlen(path);
  110.  
  111. #if OPT_VMS_PATH
  112.     if (!is_vms_pathname(path, -TRUE))    /* must be unix-style name */
  113. #endif
  114.     if (!trailing_slash(path)) {
  115.         path[len++] = SLASHC;
  116.         path[len] = EOS;
  117.     }
  118.     return len;
  119. }
  120.  
  121. /*
  122.  * Compare two paths lexically.
  123.  */
  124. static int
  125. pathcmp(LINE * lp, char * text)
  126. {
  127.     register char *l, *t;
  128.     register int lc, tc;
  129.  
  130.     if (llength(lp) <= 0)    /* (This happens on the first insertion) */
  131.     return -1;
  132.  
  133.     l = lp->l_text;
  134.     t = text;
  135.     for_ever {
  136.     lc = *l++;
  137.     tc = *t++;
  138. #if OPT_CASELESS
  139.     if (isUpper(lc))
  140.         lc = toLower(lc);
  141.     if (isUpper(tc))
  142.         tc = toLower(tc);
  143. #endif
  144.     if (lc == tc) {
  145.         if (tc == EOS)
  146.         return 0;
  147.     } else {
  148.         if (is_slashc(lc)) {
  149.         lc = (*l != EOS) ? SLASH : EOS;
  150.         }
  151.         if (is_slashc(tc)) {
  152.         tc = (*t != EOS) ? SLASH : EOS;
  153.         }
  154.         return lc - tc;
  155.     }
  156.     }
  157. }
  158.  
  159. /*
  160.  * Insert a pathname at the given line-pointer.
  161.  * Allocate up to three extra bytes for possible trailing slash, EOS, and
  162.  * directory scan indicator.  The latter is only used when there is a trailing
  163.  * slash.
  164.  */
  165. static LINE *
  166. makeString(BUFFER *bp, LINE * lp, char * text, SIZE_T len)
  167. {
  168.     register LINE    *np;
  169.     int extra = (len != 0 && is_slashc(text[len-1])) ? 2 : 3;
  170.  
  171.     if ((np = lalloc((int)len+extra, bp)) == NULL) {
  172.         lp = 0;
  173.     } else {
  174.         (void)strcpy(np->l_text, text);
  175.         np->l_text[len+extra-1] = 0;     /* clear scan indicator */
  176.         llength(np) -= extra;    /* hide the null and scan indicator */
  177.  
  178.  
  179.         set_lforw(lback(lp), np);
  180.         set_lback(np, lback(lp));
  181.         set_lback(lp, np);
  182.         set_lforw(np, lp);
  183.         lp = np;
  184.     }
  185.     return lp;
  186. }
  187.  
  188. /*
  189.  * Create a buffer to store null-terminated strings.
  190.  *
  191.  * The file (or directory) completion buffer is initialized at the beginning of
  192.  * each command.  Wildcard expansion causes entries to be read for a given path
  193.  * on demand.  Resetting the buffer in this fashion is a tradeoff between
  194.  * efficiency (allows reuse of a pattern in NAMEC/TESTC operations) and speed
  195.  * (directory scanning is slow).
  196.  *
  197.  * The tags buffer is initialized only once for a given tags-file.
  198.  */
  199. static BUFFER *
  200. bs_init(const char * name)
  201. {
  202.     register BUFFER *bp;
  203.  
  204.     if ((bp = bfind(name, BFINVS)) != 0) {
  205.         b_clr_scratch(bp);    /* make it nonvolatile */
  206.         (void)bclear(bp);
  207.         bp->b_active = TRUE;
  208.     }
  209.     return bp;
  210. }
  211.  
  212. /*
  213.  * Look for or insert a pathname string into the given buffer.  Start looking
  214.  * at the given line if non-null.  The pathname is expected to be in
  215.  * canonical form.
  216.  */
  217. static int
  218. bs_find(
  219. char *    fname,    /* pathname to find */
  220. SIZE_T    len,    /* ...its length */
  221. BUFFER *bp,    /* buffer to search */
  222. LINEPTR *lpp)    /* in/out line pointer, for iteration */
  223. {
  224.     register LINE    *lp;
  225.     int    doit    = FALSE;
  226. #if OPT_VMS_PATH
  227.     char    temp[NFILEN];
  228.     if (!is_slashc(*fname))
  229.         vms2hybrid(fname = strcpy(temp, fname));
  230. #endif
  231.  
  232.     if (lpp == NULL || (lp = *lpp) == NULL)
  233.         lp = buf_head(bp);
  234.     lp = lforw(lp);
  235.  
  236.     for_ever {
  237.         register int r = pathcmp(lp, fname);
  238.  
  239.         if (r == 0) {
  240.             if (trailing_slash(fname)
  241.              && !trailing_slash(lp->l_text)) {
  242.                 /* reinsert so it is sorted properly! */
  243.                 lremove(bp, lp);
  244.                 return bs_find(fname, len,  bp, lpp);
  245.             }
  246.             break;
  247.         } else if (r > 0) {
  248.             doit = TRUE;
  249.             break;
  250.         }
  251.  
  252.         lp = lforw(lp);
  253.         if (lp == buf_head(bp)) {
  254.             doit = TRUE;
  255.             break;
  256.         }
  257.     }
  258.  
  259.     if (doit) {
  260.         lp = makeString(bp, lp, fname, len);
  261.         b_clr_counted(bp);
  262.     }
  263.  
  264.     if (lpp)
  265.         *lpp = lp;
  266.     return TRUE;
  267. }
  268. #endif /* COMPLETE_DIRS || COMPLETE_FILES */
  269.  
  270. #if COMPLETE_DIRS || COMPLETE_FILES
  271.  
  272. static    BUFFER    *MyBuff;    /* the buffer containing pathnames */
  273. static    const char *MyName;    /* name of buffer for name-completion */
  274. static    char    **MyList;    /* list, for name-completion code */
  275. static    ALLOC_T MySize;        /* length of list, for (re)allocation */
  276.  
  277. /*
  278.  * Returns true if the string looks like an environment variable (i.e.,
  279.  * a '$' followed by an optional name.
  280.  */
  281. static int
  282. is_environ(const char *s)
  283. {
  284.     if (*s++ == '$') {
  285.         while (*s != EOS) {
  286.             if (!isAlnum(*s) && (*s != '_'))
  287.                 return FALSE;
  288.             s++;
  289.         }
  290.         return TRUE;
  291.     }
  292.     return FALSE;
  293. }
  294.  
  295. /*
  296.  * Tests if the given path has been scanned during this prompt/reply operation
  297.  *
  298.  * If there is anything else in the list that we can do completion with, return
  299.  * true.  This allows the case in which we scan a directory (for directory
  300.  * completion and then look at the subdirectories.  It should not permit
  301.  * directories which have already been scanned to be rescanned.
  302.  */
  303. static int
  304. already_scanned(char * path)
  305. {
  306.     register LINE    *lp;
  307.     register SIZE_T    len;
  308.     char    fname[NFILEN];
  309.     LINEPTR slp;
  310.  
  311.     len = force_slash(strcpy(fname, path));
  312.  
  313.     for_each_line(lp,MyBuff) {
  314. #if OPT_CASELESS
  315.         if (stricmp(fname, lp->l_text) == 0)
  316. #else
  317.         if (strcmp(fname, lp->l_text) == 0)
  318. #endif
  319.         {
  320.             if (lp->l_text[llength(lp)+1])
  321.             return TRUE;
  322.             else
  323.             break;     /* name should not occur more than once */
  324.         }
  325.     }
  326.  
  327.     /* force the name in with a trailing slash */
  328.     slp = buf_head(MyBuff);
  329.     (void)bs_find(fname, len, MyBuff, &slp);
  330.  
  331.     /*
  332.      * mark name as scanned (since that is what we're about to do after
  333.      * returning)
  334.      */
  335.     lp = slp;
  336.     lp->l_text[llength(lp)+1] = 1;
  337.     return FALSE;
  338. }
  339.  
  340. #if USE_DOTNAMES
  341. /*
  342.  * Before canonicalizing a pathname, check for a leaf consisting of trailing
  343.  * dots.  Convert this to another form so that it won't be combined with
  344.  * other leaves.  We do this because we don't want to prevent the user from
  345.  * doing filename completion with leaves that begin with dots.
  346.  */
  347. static void mask_dots(char *path, SIZE_T *dots)
  348. {
  349.     char *leaf = pathleaf(path);
  350.     if (isDotname(leaf)) {
  351.         *dots = strlen(leaf);
  352.         memset(leaf, '?', *dots);
  353.     } else
  354.         *dots = 0;
  355. }
  356.  
  357. /*
  358.  * Restore the leaf to its original form.
  359.  */
  360. static void add_dots(char *path, SIZE_T dots)
  361. {
  362.     if (dots != 0) {
  363.         memset(pathleaf(path), '.', dots);
  364.     }
  365. }
  366.  
  367. static void strip_dots(char *path, SIZE_T *dots)
  368. {
  369.     char *leaf = pathleaf(path);
  370.     if (isDotname(leaf)) {
  371.         *dots = strlen(leaf);
  372.         *leaf = EOS;
  373.     }
  374. }
  375. #define if_dots(path, dots) if (!strncmp(path, "..", dots))
  376. #else
  377. #define mask_dots(path, dots) /* nothing */
  378. #define add_dots(path, dots) /* nothing */
  379. #define strip_dots(path, dots) /* nothing */
  380. #define if_dots(path, dots) /* nothing */
  381. #endif /* USE_DOTNAMES */
  382.  
  383. #if OPT_VMS_PATH
  384. /*
  385.  * Convert a canonical VMS pathname to a hybrid form, in which the leaf (e.g.,
  386.  * "name.type;ver") is left untouched, but the directory portion is in UNIX
  387.  * form.  This alleviates a sticky problem with VMS's pathname delimiters by
  388.  * making them all '/' characters.
  389.  */
  390. static void
  391. vms2hybrid(char *path)
  392. {
  393.     char    leaf[NFILEN];
  394.     char    head[NFILEN];
  395.     char    *s = strcpy(head, path);
  396.     char    *t;
  397.  
  398.     TRACE(("vms2hybrid '%s'\n", path))
  399.     (void)strcpy(leaf, s = pathleaf(head));
  400.     if ((t = is_vms_dirtype(leaf)) != 0)
  401.         (void)strcpy(t, "/");
  402.     *s = EOS;
  403.     if (s == path)    /* a non-canonical name got here somehow */
  404.         (void) strcpy(head, current_directory(FALSE));
  405.     pathcat(path, mkupper(vms2unix_path(head, head)), leaf);
  406.     TRACE((" -> '%s' (vms2hybrid)\n", path))
  407. }
  408.  
  409. static void
  410. hybrid2vms(char *path)
  411. {
  412.     char    leaf[NFILEN];
  413.     char    head[NFILEN];
  414.     char    *s = strcpy(head, path);
  415.  
  416.     TRACE(("hybrid2vms '%s'\n", path))
  417.     (void)strcpy(leaf, s = pathleaf(head));
  418.     *s = EOS;
  419.     if (s == head)    /* a non-canonical name got here somehow */
  420.         (void) vms2unix_path(head, current_directory(FALSE));
  421.     (void) pathcat(path, head, leaf);
  422.     if (isDotname(leaf)) {
  423.         force_slash(path);    /* ...so we'll interpret as directory */
  424.         lengthen_path(path);    /* ...makes VMS-style absolute-path */
  425.     } else {
  426.         unix2vms_path(path, path);
  427.     }
  428.     TRACE((" -> '%s' hybrid2vms\n", path))
  429. }
  430.  
  431. static void
  432. hybrid2unix(char *path)
  433. {
  434.     char    leaf[NFILEN];
  435.     char    head[NFILEN];
  436.     char    *s = strcpy(head, path);
  437.  
  438.     TRACE(("hybrid2unix '%s'\n", path))
  439.     (void)strcpy(leaf, s = pathleaf(head));
  440.     *s = EOS;
  441.     if (s == path)    /* a non-canonical name got here somehow */
  442.         (void) vms2unix_path(head, current_directory(FALSE));
  443.     pathcat(path, head, leaf);
  444.  
  445.     /* The semicolon that indicates the version is a little tricky.  When
  446.      * simulating via fakevms.c on UNIX, we've got to trim the version
  447.      * marker at this point.  Otherwise, it'll be changed to a dollar sign
  448.      * the next time the string is converted from UNIX to VMS form.  A
  449.      * side-effect is that the name-completion echos (for UNIX only!) the
  450.      * version, but doesn't store it in the buffer returned to the caller.
  451.      */
  452.     if ((s = strrchr(path, ';')) != 0) {
  453. #if SYS_UNIX
  454.         *s = EOS;    /* ...we're only simulating */
  455. #else
  456.         *s = '.';    /* this'll be interpreted as version-mark */
  457. #endif
  458.     }
  459.     TRACE((" -> '%s' hybrid2unix\n", path))
  460. }
  461. #endif /* OPT_VMS_PATH */
  462.  
  463. #if USE_QSORT
  464. /*
  465.  * Compare two lines (containing paths) for quicksort by calling pathcmp().
  466.  * If the paths compare equal force the one with the trailing slash to be
  467.  * less than.  This'll make deleting the ones without slashes easier in
  468.  * sortMyBuff().
  469.  */
  470. static int
  471. qs_pathcmp(const void *lpp1, const void *lpp2)
  472. {
  473.     int r = pathcmp(*(LINE *const*)lpp1, (* (LINE *const*) lpp2)->l_text);
  474.  
  475.     if (r == 0) {
  476.     const LINE *lp1 = *(LINE *const*)lpp1;
  477.  
  478.     if (llength(lp1) > 0 && is_slashc(lgetc(lp1, llength(lp1)-1)))
  479.         return -1;
  480.     else        /* Don't care if the other one has slash or not... */
  481.         return 1;    /* ...returning 1 for two equal elements won't do  */
  482.                 /* any harm. */
  483.     }
  484.     else
  485.     return r;
  486. }
  487.  
  488. static void
  489. sortMyBuff(void)
  490. {
  491.     L_NUM n;
  492.     LINE **sortvec;
  493.     register LINE *lp, *plp;
  494.     register LINE **slp;
  495.  
  496.     b_clr_counted(MyBuff);
  497.     n = line_count(MyBuff);
  498.     if (n <= 0)
  499.     return;            /* Nothing to sort */
  500.  
  501.     sortvec = typecallocn(LINE *, (ALLOC_T) n);
  502.     if (sortvec == NULL)
  503.     return;            /* Can't sort it .. have to get by unsorted */
  504.  
  505.     slp = sortvec;
  506.     for_each_line(lp, MyBuff) {
  507.     *slp++ = lp;
  508.     }
  509.     qsort((char *) sortvec, (SIZE_T)n, sizeof(LINE *), qs_pathcmp);
  510.  
  511.     plp = buf_head(MyBuff);
  512.     slp = sortvec;
  513.     while (n-- > 0) {
  514.     lp = *slp++;
  515.     if (pathcmp(plp, lp->l_text) == 0) {
  516.         lfree(lp, MyBuff);
  517.     }
  518.     else {
  519.         set_lforw(plp, lp);
  520.         set_lback(lp, plp);
  521.         plp = lp;
  522.     }
  523.     }
  524.     lp = buf_head(MyBuff);
  525.     set_lforw(plp, lp);
  526.     set_lback(lp, plp);
  527.     b_clr_counted(MyBuff);
  528.  
  529.     free((char *)sortvec);
  530. }
  531. #endif    /* USE_QSORT */
  532.  
  533. /*
  534.  * If the given path is not in the completion-buffer, expand it, and add the
  535.  * expanded paths to the buffer.  Because the user may be trying to get an
  536.  * intermediate directory-name, we must 'stat()' each name, so that we can
  537.  * provide the trailing slash in the completion.  This is slow.
  538.  */
  539. static int
  540. fillMyBuff(char * name)
  541. {
  542.     int count = 0;
  543. #if !OPT_VMS_PATH
  544.     SIZE_T dots = 0;
  545. #endif
  546.     register char    *s;
  547. #if SYS_OS2
  548.     FILEFINDBUF3 fb;
  549.     ULONG entries;
  550.     HDIR hdir;
  551.     APIRET rc;
  552.     int case_preserving = is_case_preserving(name);
  553.  
  554.     char    path[NFILEN + 2];
  555. #else    /* UNIX, VMS or MSDOS */
  556.     char    *leaf;
  557.  
  558.     DIR    *dp;
  559.     DIRENT    *de;
  560.  
  561.     char    path[NFILEN];
  562. #if OPT_VMS_PATH
  563.     char    temp[NFILEN];
  564. #endif
  565. #endif
  566.  
  567.     TRACE(("fillMyBuff '%s'\n", name))
  568.  
  569.     /**********************************************************************/
  570.     if (is_environ(name)) {
  571.         LINEPTR lp;
  572.         int n;
  573.  
  574.         /*
  575.          * The presence of any environment variable in the list is
  576.          * sufficient indication that we've copied the environment.
  577.          */
  578.         for_each_line(lp,MyBuff)
  579.             if (is_environ(lp->l_text))
  580.                 return 0;
  581.  
  582.         /*
  583.          * Copy all of the environment-variable names, prefixed with
  584.          * the '$' that indicates what they are.
  585.          */
  586.         for (n = 0; environ[n] != 0; n++) {
  587.             char *d = path;
  588.  
  589.             s = environ[n];
  590.             *d++ = name[0];
  591.             while (((*d = *s++) != '=') && (*d != EOS)) {
  592.                 if ((size_t)(d++ - path) > sizeof(path)-2)
  593.                     break;
  594.             }
  595.             *d = EOS;
  596. #if SYS_MSDOS || SYS_OS2
  597.             mklower(path);    /* glob.c will uppercase for getenv */
  598. #endif
  599. #if USE_QSORT
  600.             (void)makeString(MyBuff,
  601.                              buf_head(MyBuff),
  602.                      path,
  603.                      (SIZE_T) strlen(path));
  604. #else    /* !USE_QSORT */
  605.             (void)bs_find(path, (SIZE_T)strlen(path),
  606.                     MyBuff, (LINEPTR*)0);
  607. #endif    /* USE_QSORT/!USE_QSORT */
  608.             TRACE(("> '%s'\n", path))
  609.         }
  610. #if USE_QSORT
  611.         sortMyBuff();
  612. #endif
  613.         return 0;
  614.     }
  615.  
  616.     (void)strcpy(path, name);
  617. #if OPT_MSDOS_PATH
  618.     bsl_to_sl_inplace(path);
  619. #endif
  620. #if OPT_VMS_PATH
  621.     (void)strcpy(temp, name);
  622.     hybrid2vms(path);        /* convert to canonical VMS name */
  623. #else
  624.     strip_dots(path, &dots);    /* ignore trailing /. or /.. */
  625. #endif
  626.     if (!is_environ(path) && !is_directory(path)) {
  627.         *pathleaf(path) = EOS;
  628.         if (!is_directory(path))
  629.             return 1;
  630. #if OPT_VMS_PATH
  631.         *pathleaf(temp) = EOS;
  632. #endif
  633.     }
  634.  
  635. #if OPT_VMS_PATH
  636.     if (already_scanned(temp))    /* we match the hybrid name */
  637.         return 1;
  638. #else
  639.     if (already_scanned(path)) {
  640. #if USE_DOTNAMES
  641.         /*
  642.          * Handle the special cases where we're continuing a completion
  643.          * with ".." on the end of the path.  We have to distinguish
  644.          * the return value so that we can drive a second scan for the
  645.          * case where there's no dot-names found.
  646.          */
  647.         if (dots) {
  648.             char    temp[NFILEN];
  649.             LINE    *lp;
  650.             SIZE_T    need, want;
  651.             int    found = 0;
  652.  
  653.             while (dots--)
  654.                 strcat(path, ".");
  655.             (void)lengthen_path(strcpy(temp, path));
  656.             need = strlen(temp);
  657.             want = strlen(path);
  658.             for_each_line(lp,MyBuff) {
  659.                 SIZE_T have = llength(lp);
  660.                 if (have == need
  661.                  && !memcmp(lp->l_text, temp, need))
  662.                      found = -1;
  663.                 else if (have >= want
  664.                  && !memcmp(lp->l_text, path, want)) {
  665.                     found = 1;
  666.                     break;
  667.                 }
  668.             }
  669.             return found;
  670.         }
  671. #endif
  672.         return 0;
  673.     }
  674. #endif
  675.  
  676.     /**********************************************************************/
  677.  
  678. #if SYS_OS2
  679.     s = path + force_slash(path);
  680.     (void)strcat(path, "*.*");
  681.  
  682.     hdir = HDIR_CREATE;
  683.     entries = 1;
  684.     rc = DosFindFirst(SL_TO_BSL(path), &hdir,
  685.             FILE_DIRECTORY | FILE_READONLY,
  686.             &fb, sizeof(fb), &entries, FIL_STANDARD);
  687.     if (rc == NO_ERROR)
  688.     {
  689.         do
  690.         {
  691.             (void) strcpy(s, fb.achName);
  692.             if (!case_preserving)
  693.                 (void) mklower(s);
  694.  
  695.             if (isDotname(s))
  696.                  continue;
  697.  
  698.             if (only_dir) {
  699.                 if (!FoundDirectory(path))
  700.                     continue;
  701.                 (void) force_slash(path);
  702.             }
  703. #if COMPLETE_DIRS
  704.             else {
  705.                 if (global_g_val(GMDDIRC) && FoundDirectory(path))
  706.                     (void) force_slash(path);
  707.             }
  708. #endif
  709.             TRACE(("> '%s'\n", path))
  710.             if_dots(s,dots) count++;
  711.             (void)bs_find(path, strlen(path), MyBuff, (LINEPTR*)0);
  712.  
  713.         } while (entries = 1,
  714.                  DosFindNext(hdir, &fb, sizeof(fb), &entries) == NO_ERROR
  715.                  && entries == 1);
  716.  
  717.         DosFindClose(hdir);
  718.     }
  719. #else /* UNIX, VMS or MSDOS */
  720.     /* For MS-DOS pathnames, force the use of '\' instead of '/' in the
  721.      * open-directory operation to allow for runtime libraries that
  722.      * don't allow using UNIX-style '/' pathnames.
  723.      */
  724.     if ((dp = opendir(SL_TO_BSL(path))) != 0) {
  725.         s = path;
  726. #if !OPT_VMS_PATH
  727.         s += force_slash(path);
  728. #endif
  729.  
  730.         leaf = s;
  731.         while ((de = readdir(dp)) != 0) {
  732. #if SYS_UNIX || SYS_VMS || SYS_WINNT
  733. # if USE_D_NAMLEN
  734.             (void)strncpy(leaf, de->d_name, (SIZE_T)(de->d_namlen));
  735.             leaf[de->d_namlen] = EOS;
  736. # else
  737.             (void)strcpy(leaf, de->d_name);
  738. # endif
  739. #else
  740. # if SYS_MSDOS
  741.             (void)mklower(strcpy(leaf, de->d_name));
  742. # else
  743.             huh??
  744. # endif
  745. #endif
  746. #if OPT_VMS_PATH
  747.             vms_dir2path(path);
  748. #else
  749. # if SYS_UNIX || SYS_MSDOS || SYS_OS2 || SYS_WINNT
  750.             if (isDotname(leaf))
  751.                  continue;
  752. # endif
  753. #endif
  754.             if (only_dir) {
  755.                 if (!FoundDirectory(path))
  756.                     continue;
  757.                 (void) force_slash(path);
  758.             }
  759. #if COMPLETE_DIRS
  760.             else {
  761.                 if (global_g_val(GMDDIRC) && FoundDirectory(path))
  762.                     (void) force_slash(path);
  763.             }
  764. #endif
  765.             TRACE(("> '%s'\n", path))
  766.             if_dots(leaf,dots) count++;
  767. #if USE_QSORT
  768. #if OPT_VMS_PATH
  769.             vms2hybrid(s = strcpy(temp, path));
  770. #else
  771.             s = path;
  772. #endif
  773.             (void)makeString(MyBuff,
  774.                              buf_head(MyBuff),
  775.                      s,
  776.                      (SIZE_T) strlen(s));
  777. #else    /* !USE_QSORT */
  778.             (void)bs_find(path, (SIZE_T)strlen(path), MyBuff,
  779.                     (LINEPTR*)0);
  780. #endif    /* USE_QSORT/!USE_QSORT */
  781.         }
  782.         (void)closedir(dp);
  783. #if USE_QSORT
  784.         sortMyBuff();
  785. #endif
  786.     }
  787. #endif    /* SYS_OS2/!SYS_OS2 */
  788.     TRACE(("...fillMyBuff returns %d\n", count))
  789.     return count;
  790. }
  791.  
  792. /*
  793.  * Make the list of names needed for name-completion
  794.  */
  795. static void
  796. makeMyList(char *name)
  797. {
  798.     register ALLOC_T need;
  799.     register int    n;
  800.     register LINE *    lp;
  801.     char *slashocc;
  802.     int len = strlen(name);
  803.  
  804.     if (is_slashc(name[len-1]))
  805.         len++;
  806.  
  807.     (void)bsizes(MyBuff);
  808.     need = MyBuff->b_linecount + 2;
  809.     if (MySize < need) {
  810.         MySize = need * 2;
  811.         if (MyList == 0)
  812.             MyList = typeallocn(char *, MySize);
  813.         else
  814.             MyList = typereallocn(char *, MyList, MySize);
  815.     }
  816.  
  817.     n = 0;
  818.     for_each_line(lp,MyBuff)
  819.         /* exclude listings of subdirectories below
  820.            current directory */
  821.         if (llength(lp) >= len
  822.          && ((slashocc = strchr(lp->l_text+len, SLASHC)) == NULL
  823.            || slashocc[1] == EOS))
  824.             MyList[n++] = lp->l_text;
  825.     MyList[n] = 0;
  826. }
  827.  
  828. #if NO_LEAKS
  829. static void
  830. freeMyList(void)
  831. {
  832.     FreeAndNull(MyList);
  833.     MySize = 0;
  834. }
  835. #else
  836. #define    freeMyList()
  837. #endif
  838.  
  839. static void
  840. force_output(int c, char *buf, unsigned *pos)
  841. {
  842.     kbd_putc(c);
  843.     TTflush();
  844.     buf[*pos] = (char)c;
  845.     *pos += 1;
  846.     buf[*pos] = EOS;
  847. }
  848.  
  849. /*
  850.  * Initialize the file-completion module.  We'll only do either file- or
  851.  * directory-completion during any given command, and they use different
  852.  * buffers (and slightly different parsing).
  853.  */
  854. void
  855. init_filec(const char *buffer_name)
  856. {
  857.     MyBuff = 0;
  858.     MyName = buffer_name;
  859. }
  860.  
  861. /*
  862.  * Perform the name-completion/display.  Note that we must convert a copy of
  863.  * the pathname to absolute form so that we can match against the strings that
  864.  * are stored in the completion table.  However, the characters that might be
  865.  * added are always applicable to the original buffer.
  866.  *
  867.  * We only do name-completion if asked; if we did it when the user typed a
  868.  * return it would be too slow.
  869.  */
  870. int
  871. path_completion(int c, char *buf, unsigned *pos)
  872. {
  873.     int    code    = FALSE,
  874.         action    = (c == NAMEC || c == TESTC),
  875.         ignore    = (*buf != EOS && isInternalName(buf));
  876. #if USE_DOTNAMES
  877.     SIZE_T    dots = 0;
  878.     int    count;
  879. #endif
  880.  
  881.     TRACE(("path_completion('%c' %d:\"%.*s\"\n", c, *pos, (int)*pos, buf))
  882. #if OPT_VMS_PATH
  883.     if (ignore && action) {        /* resolve scratch-name conflict */
  884.         if (is_vms_pathname(buf, -TRUE))
  885.             ignore = FALSE;
  886.     }
  887. #endif
  888.     if (ignore) {
  889.         if (action) {        /* completion-chars have no meaning */
  890.             force_output(c, buf, pos);
  891.         }
  892.     } else if (action) {
  893.         char    *s;
  894.         char    path[NFILEN];
  895.         unsigned oldlen,
  896.             newlen;
  897.  
  898.         /* initialize only on demand */
  899.         if (MyBuff == 0) {
  900.             if (MyName == 0
  901.              || (MyBuff = bs_init(MyName)) == 0)
  902.                  return FALSE;
  903.         }
  904.  
  905.         /*
  906.          * Copy 'buf' into 'path', making it canonical-form.
  907.          */
  908. #if OPT_VMS_PATH
  909.         if (*strcpy(path, buf) == EOS) {
  910.             (void)strcpy(path, current_directory(FALSE));
  911.         } else if (!is_environ(path)) {
  912.             char    frac[NFILEN];
  913.  
  914.             if (is_vms_pathname(path, -TRUE)) {
  915.                 s = vms_pathleaf(path);
  916.                 (void)strcpy(frac, s);
  917.                 *s = EOS;
  918.             } else {
  919.                 s = pathleaf(path);
  920.                 if (strcmp(s, ".")
  921.                  && is_vms_pathname(s, -TRUE)) {
  922.                     (void)strcpy(frac, s);
  923.                     *s = EOS;
  924.                 } else {    /* e.g., path=".." */
  925.                     *frac = EOS;
  926.                 }
  927.             }
  928.             if (*path == EOS)
  929.                 (void)strcpy(path, current_directory(FALSE));
  930.             else {
  931.                 if (!is_vms_pathname(path, -TRUE)) {
  932.                     unix2vms_path(path, path);
  933.                     if (*path == EOS)
  934.                         (void)strcpy(path, current_directory(FALSE));
  935.                 }
  936.                 (void)lengthen_path(path);
  937.             }
  938.             (void)strcat(path, frac);
  939.         }
  940.         if (is_vms_pathname(path, -TRUE)) {
  941.             int pad = is_vms_pathname(path, TRUE);
  942.  
  943.             vms2hybrid(path);
  944.             /*
  945.              * FIXME: This compensates for the hack in canonpath
  946.              */
  947.             if (!strcmp(buf, "/")) {
  948.                 while ((size_t)*pos < strlen(path))
  949.                     force_output(path[*pos], buf, pos);
  950.             } else if (pad && *buf != EOS && !trailing_slash(buf)) {
  951.                 force_output(SLASHC, buf, pos);
  952.             }
  953.         }
  954. #else
  955.         if (is_environ(buf)) {
  956.             (void)strcpy(path, buf);
  957.         } else {
  958. # if SYS_UNIX || OPT_MSDOS_PATH
  959.             char    **expand;
  960.  
  961.             /*
  962.              * Expand _unique_ wildcards and environment variables.
  963.              * Like 'doglob()', but without the prompt.
  964.              */
  965.             expand = glob_string(strcpy(path, buf));
  966.             switch (glob_length(expand)) {
  967.             default:
  968.                 (void)glob_free(expand);
  969.                 kbd_alarm();
  970.                 return FALSE;
  971.             case 1:
  972.                 (void)strcpy(path, expand[0]);
  973.                 /*FALLTHRU*/
  974.             case 0:
  975.                 (void)glob_free(expand);
  976.                 break;
  977.             }
  978.             mask_dots(path, &dots);
  979.             (void)lengthen_path(path);
  980.             add_dots(path, dots);
  981. #  if OPT_MSDOS_PATH
  982.             /*
  983.              * Pick up slash (special case) when we've just expanded a
  984.              * device such as "c:" to "c:/".
  985.              */
  986.             if ((newlen = strlen(path)) == 3
  987.              && (oldlen = strlen(buf)) == 2
  988.              && is_slashc(path[newlen-1])
  989.              && path[newlen-2] == ':') {
  990.                 force_output(SLASHC, buf, pos);
  991.             }
  992. #  endif
  993. # endif
  994.         }
  995. #endif
  996.  
  997.         if ((s = is_appendname(buf)) == 0)
  998.             s = buf;
  999.         if ((*s == EOS) || trailing_slash(s)) {
  1000.             if (*path == EOS)
  1001.                 strcpy(path, ".");
  1002.             (void)force_slash(path);
  1003.         }
  1004.  
  1005.         if ((s = is_appendname(path)) != NULL) {
  1006.             register char *d;
  1007.             for (d = path; (*d++ = *s++) != EOS; )
  1008.                 ;
  1009.         }
  1010. #if OPT_MSDOS_PATH
  1011.         /* if the user typed a back-slash, we need to
  1012.          * convert it, since it's stored as '/' in the file
  1013.          * completion buffer to avoid conflicts with the use of
  1014.          * backslash for escaping special characters.
  1015.          */
  1016.         bsl_to_sl_inplace(path);
  1017. #endif
  1018.  
  1019.         newlen =
  1020.         oldlen = strlen(path);
  1021.  
  1022. #if USE_DOTNAMES
  1023.         /*
  1024.          * If we're on a filesystem that allows names beginning with
  1025.          * ".", try to handle the ambiguity between .name and ./ by
  1026.          * preferring matches on the former.  If we get a zero return
  1027.          * from the first scan, it means that we've only the latter
  1028.          * case to consider.
  1029.          */
  1030.         count = fillMyBuff(path);
  1031.         if (dots == 2) {
  1032.             if (count == 0) {
  1033.                 force_slash(path);
  1034.                 lengthen_path(path);
  1035.                 newlen =
  1036.                 oldlen = strlen(path);
  1037. #if 0
  1038.                 if ((MyBuff = bs_init(MyName)) == 0)
  1039.                     return FALSE;
  1040. #endif
  1041.                 (void) fillMyBuff(path);
  1042.             } else if (count < 0) {
  1043.                 lengthen_path(path);
  1044.                 newlen =
  1045.                 oldlen = strlen(path);
  1046.             }
  1047.         } else if (dots == 1) {
  1048.             if (count == 0) {
  1049.                 lengthen_path(path);
  1050.                 newlen =
  1051.                 oldlen = strlen(path);
  1052.             }
  1053.         }
  1054. #else
  1055.         (void) fillMyBuff(path);
  1056. #endif
  1057.         makeMyList(path);
  1058.  
  1059.         /* patch: should also force-dot to the matched line, as in history.c */
  1060.         /* patch: how can I force buffer-update to show? */
  1061.  
  1062. #if OPT_CASELESS
  1063.         code = kbd_complete(TRUE, c, path, &newlen, (char *)&MyList[0], sizeof(MyList[0]));
  1064. #if 0 /* case insensitive reply correction doesn't work reliably yet */
  1065.         (void)strcpy(buf, path);
  1066. #else
  1067.         (void)strcat(buf, path+oldlen);
  1068. #endif
  1069. #else
  1070.         code = kbd_complete(FALSE, c, path, &newlen, (char *)&MyList[0], sizeof(MyList[0]));
  1071.         (void)strcat(buf, path+oldlen);
  1072. #endif
  1073. #if OPT_VMS_PATH
  1074.         if (*buf != EOS
  1075.          && !is_vms_pathname(buf, -TRUE))
  1076.             hybrid2unix(buf);
  1077. #endif
  1078.         *pos = strlen(buf);
  1079.  
  1080.         /* avoid accidentally picking up directory names for files */
  1081.         if ((code == TRUE)
  1082.          && !only_dir
  1083.          && !trailing_slash(path)
  1084.          && is_directory(path)) {
  1085.             force_output(SLASHC, buf, pos);
  1086.             code = FALSE;
  1087.         }
  1088.     }
  1089.     TRACE((" -> '%s' path_completion\n", buf))
  1090.     return code;
  1091. }
  1092. #else    /* no filename-completion */
  1093. #define    freeMyList()
  1094. #endif    /* filename-completion */
  1095.  
  1096. /******************************************************************************/
  1097. int
  1098. mlreply_file(
  1099. const char * prompt,
  1100. TBUFF **buffer,
  1101. int    flag,        /* +1 to read, -1 to write, 0 don't care */
  1102. char *    result)
  1103. {
  1104.     register int    s = FALSE;
  1105.     static    TBUFF    *last;
  1106.     char    Reply[NFILEN];
  1107.     int    (*complete) (DONE_ARGS) = no_completion;
  1108.     int    had_fname = (curbp != 0
  1109.               && curbp->b_fname != 0
  1110.               && curbp->b_fname[0] != EOS);
  1111.     int    do_prompt = (clexec || isnamedcmd || (flag & FILEC_PROMPT));
  1112.     int    ok_expand = (flag & FILEC_EXPAND);
  1113.  
  1114.     flag &= ~ (FILEC_PROMPT | FILEC_EXPAND);
  1115.  
  1116. #if COMPLETE_FILES
  1117.     if (do_prompt && !clexec) {
  1118.         complete = shell_complete;
  1119.         init_filec(FILECOMPLETION_BufName);
  1120.     }
  1121. #endif
  1122.  
  1123.     /* use the current filename if none given */
  1124.     if (buffer == 0) {
  1125.         (void)tb_scopy(
  1126.             buffer = &last,
  1127.             had_fname && is_pathname(curbp->b_fname)
  1128.                 ? shorten_path(strcpy(Reply, curbp->b_fname),
  1129.                 FALSE)
  1130.                 : "");
  1131.     }
  1132.  
  1133.     if (do_prompt) {
  1134.         char    *t1 = tb_values(*buffer),
  1135.             *t2 = is_appendname(t1);
  1136.  
  1137.         if (t1 != 0)
  1138.             (void)strcpy(Reply, (t2 != 0) ? t2 : t1);
  1139.         else
  1140.             *Reply = EOS;
  1141.  
  1142.             s = kbd_string(prompt, Reply, sizeof(Reply),
  1143.             '\n', KBD_OPTIONS|KBD_MAYBEC, complete);
  1144.         freeMyList();
  1145.  
  1146.         if (s == ABORT)
  1147.             return s;
  1148.         if (s != TRUE) {
  1149.             if ((flag == FILEC_REREAD)
  1150.              && had_fname
  1151.              && (!global_g_val(GMDWARNREREAD)
  1152.               || ((s = mlyesno("Reread current buffer")) == TRUE)))
  1153.                 (void)strcpy(Reply, curbp->b_fname);
  1154.             else
  1155.                         return s;
  1156.         } else if (kbd_is_pushed_back() && isShellOrPipe(Reply)) {
  1157.             /*
  1158.              * The first call on 'kbd_string()' split the text off
  1159.              * the shell command.  This is needed for the logic of
  1160.              * colon-commands, but is inappropriate for filename
  1161.              * prompting.  Read the rest of the text into Reply.
  1162.              */
  1163.                 s = kbd_string(prompt, Reply+1, sizeof(Reply)-1,
  1164.                 '\n', KBD_OPTIONS|KBD_MAYBEC, complete);
  1165.         }
  1166.         } else if (!screen_to_bname(Reply)) {
  1167.         return FALSE;
  1168.         }
  1169.     if (flag >= FILEC_UNKNOWN && is_appendname(Reply) != 0) {
  1170.         mlforce("[file is not a legal input]");
  1171.         return FALSE;
  1172.     }
  1173.  
  1174.     free_expansion();
  1175.     if (ok_expand) {
  1176.         if ((MyGlob = glob_string(Reply)) == 0
  1177.          || (s = glob_length(MyGlob)) == 0) {
  1178.             mlforce("[No files found] %s", Reply);
  1179.             return FALSE;
  1180.         }
  1181.         if (s > 1) {
  1182.             char    tmp[80];
  1183.             (void)lsprintf(tmp, "Will create %d buffers. Okay", s);
  1184.             s = mlyesno(tmp);
  1185.             mlerase();
  1186.             if (s != TRUE)
  1187.                 return s;
  1188.         }
  1189.     } else if (doglob(Reply) != TRUE) {
  1190.         return FALSE;
  1191.     }
  1192.  
  1193.     (void)strcpy (result, Reply);
  1194.     if (flag <= FILEC_WRITE) {    /* we want to write a file */
  1195.         if (!isInternalName(Reply)
  1196.          && !same_fname(Reply, curbp, TRUE)
  1197.          && flook(Reply, FL_HERE|FL_READABLE)) {
  1198.             if (mlyesno("File exists, okay to overwrite") != TRUE) {
  1199.                 mlwrite("File not written");
  1200.                 return FALSE;
  1201.             }
  1202.         }
  1203.     }
  1204.  
  1205.     (void)tb_scopy(buffer, Reply);
  1206.     return TRUE;
  1207. }
  1208.  
  1209. /******************************************************************************/
  1210. int
  1211. mlreply_dir(
  1212. const char * prompt,
  1213. TBUFF **buffer,
  1214. char *    result)
  1215. {
  1216.     register int    s;
  1217.     char    Reply[NFILEN];
  1218.     int    (*complete) (DONE_ARGS) = no_completion;
  1219.  
  1220. #if COMPLETE_DIRS
  1221.     if (isnamedcmd && !clexec) {
  1222.         complete = path_completion;
  1223.         init_filec(DIRCOMPLETION_BufName);
  1224.     }
  1225. #endif
  1226.     if (clexec || isnamedcmd) {
  1227.         if (tb_values(*buffer) != 0)
  1228.             (void)strcpy(Reply, tb_values(*buffer));
  1229.         else
  1230.             *Reply = EOS;
  1231.  
  1232.         only_dir = TRUE;
  1233.         s = kbd_string(prompt, Reply, sizeof(Reply), '\n',
  1234.             KBD_OPTIONS|KBD_MAYBEC, complete);
  1235.         freeMyList();
  1236.         only_dir = FALSE;
  1237.         if (s != TRUE)
  1238.             return s;
  1239.  
  1240.         } else if (!screen_to_bname(Reply)) {
  1241.         return FALSE;
  1242.         }
  1243.  
  1244.     (void)tb_scopy(buffer, strcpy(result, Reply));
  1245.     return TRUE;
  1246. }
  1247.  
  1248. /******************************************************************************/
  1249.  
  1250. /*
  1251.  * This is called after 'mlreply_file()' to iterate over the list of files
  1252.  * that are matched by a glob-expansion.
  1253.  */
  1254. char *
  1255. filec_expand(void)
  1256. {
  1257.     if (MyGlob != 0) {
  1258.         if (MyGlob[++in_glob] != 0)
  1259.             return MyGlob[in_glob];
  1260.         free_expansion();
  1261.     }
  1262.     return 0;
  1263. }
  1264.