home *** CD-ROM | disk | FTP | other *** search
/ Geek Gadgets 1 / ADE-1.bin / ade-dist / ncftp-2.3.0-src.tgz / tar.out / contrib / ncftp / Complete.c < prev    next >
C/C++ Source or Header  |  1996-09-28  |  17KB  |  824 lines

  1. /* Complete.c */
  2. /* Tim MacKenzie, t.mackenzie@trl.oz.au, April '95 */
  3.  
  4. #include "Sys.h"
  5. #include "LineList.h"
  6. #include "Cmdline.h"
  7. #include "Complete.h"
  8. #include "Prefs.h"
  9. #include "Bookmark.h"
  10. #include "Util.h"
  11. #include "List.h"
  12. #include <signal.h>
  13. #include <setjmp.h>
  14.  
  15. extern int gNumCommands;     /* Cmdlist.c */
  16. extern Command gCommands[];  /* Cmdlist.c */
  17. extern PrefOpt gPrefOpts[];      /*Prefs.c */
  18. extern int gNumEditablePrefOpts; /*Prefs.c */
  19. extern BookmarkPtr gFirstRsi; /* Bookmark.c */
  20. extern longstring gRemoteCWD;       /* Cmds.c */
  21. extern Bookmark gRmtInfo;   /* Bookmark.c */
  22. extern int gScreenWidth; /* Win.c */
  23.  
  24. /* We can't use a linked list because we need random access for the
  25.  * output routine...
  26.  */
  27. typedef struct {
  28.     char **names;
  29.     int count;
  30.     int alloc;
  31. } FileList;
  32.  
  33. struct _DirCache {
  34.     struct _DirCache *next;
  35.     FileList files;
  36.     char *name;
  37.     int flags; /* Combination of flags below */
  38. };
  39.  
  40. static DirCache *cacheHead;
  41. static DirCache *currentCache;
  42.  
  43. static void ForgetCurrent(void);
  44. /* Upon receipt of a signal we have to abort the completion */
  45. static jmp_buf gCompleteJmp;
  46.  
  47. #define LS_F 1              /* ls -F */
  48. #define LS_L 2                /* ls -l */
  49. #define LS_DIR 4            /* Have been supplied a directory to list */
  50. #define LS_R 8               /* ls -R */
  51.  
  52. typedef char * (*CompleteFunc)(char *, int);
  53.  
  54. #ifdef HAVE_LIBREADLINE
  55. #include <readline/readline.h>
  56.  
  57. #ifdef HAVE_FILENAME_COMPLETION_FUNCTION
  58. /* This should have been in your readline.h already, but some older
  59.  * versions of the library are still lurking out there.
  60.  */
  61. extern char *filename_completion_function ();    /* Yes, not a prototype... */
  62. #endif
  63. #endif
  64.  
  65.  
  66. static char *
  67. Strmcpy(char *dst, char *src, int howMany, size_t dstSize)
  68. {
  69.     size_t max;
  70.  
  71.     /* If you want to limit Strncpy to x characters, you
  72.      * must pass x + 1 because Strncpy subtracts one for the
  73.      * nul terminator.
  74.      */
  75.     max = howMany + 1;
  76.  
  77.     if (max > dstSize)
  78.         max = dstSize;
  79.     return (Strncpy(dst, src, max));
  80. }    /* Strmcpy */
  81.  
  82.  
  83. static char *
  84. StrnDup(char *s, int len)
  85. {
  86.     char *res;
  87.  
  88.     res = (char *) malloc(len+1);
  89.     strncpy(res,s,len);
  90.     res[len] = 0;
  91.     return res;
  92. }
  93.     
  94. static char *
  95. ConvertDir(char *dir)
  96. {
  97.     int baselen;
  98.     string res;
  99.     int i,j;
  100.  
  101.     baselen = (int) strlen(gRemoteCWD);
  102.     while (dir && *dir) {
  103.         if (*dir == '/') { /* It's an absolute pathname */
  104.             baselen = 0;
  105.             break;
  106.         }
  107.         if (*dir == '.' && dir[1] == '.') { /* ../ remove bit from base */
  108.             dir += 2;
  109.             while (*dir && *dir == '/') dir++;
  110.             while (baselen > 0 && gRemoteCWD[baselen-1] != '/')
  111.                 baselen--;
  112.             baselen--; /* Move back past the '/' */
  113.             if (baselen < 0) 
  114.                 baselen = 0;
  115.             continue;
  116.         }
  117.         if (*dir == '.' && (dir[1] == '/' || dir[1] == 0)) {
  118.             /* . or ./ just remove it from dirname */
  119.             dir += 1;
  120.             while (*dir && *dir == '/') dir++;
  121.             continue;
  122.         }
  123.         break;
  124.     }
  125.     if (!dir)
  126.         dir = "";
  127.     Strmcpy(res, gRemoteCWD, baselen, sizeof(res));
  128.     STRNCAT(res, "/");
  129.     STRNCAT(res, dir);
  130.     STRNCAT(res, "/");
  131.  
  132.     /* Remove //'s in the name */
  133.     for (i=j=1;res[i];i++) {
  134.         if (res[i] == '/' && res[j-1] == '/')
  135.             continue;
  136.         res[j++] = res[i];
  137.     }
  138.     res[j] = 0;
  139.     return StrDup(res);
  140. }
  141.  
  142. static DirCache *
  143. FindDirCache(char *dir)
  144. {
  145.     DirCache *l;
  146.     dir = ConvertDir(dir);
  147.     for (l = cacheHead;l;l=l->next)
  148.         if (!strcmp(dir, l->name))
  149.             break;
  150.     free(dir);
  151.     return l;
  152. }
  153.  
  154. static void
  155. SigComplete(int ignored)
  156. {
  157.     alarm(0);
  158.     longjmp(gCompleteJmp,1);
  159. }
  160.  
  161. /* Get the filenames from the current directory */
  162. static DirCache *
  163. GetCompleteFiles(char *dir)
  164. {
  165.     LineList fileList;
  166.     LinePtr f;
  167.     DirCache *l;
  168.     volatile Sig_t si, sp;
  169.  
  170.     l = FindDirCache(dir);
  171.     if (l)
  172.         return l;
  173.  
  174.     InitLineList(&fileList);
  175.     si = SIGNAL(SIGINT, SigComplete);
  176.     sp = SIGNAL(SIGPIPE, SigComplete);
  177.     SetBar(NULL, "GETTING COMPLETIONS", NULL, -1, 1);
  178.     if (setjmp(gCompleteJmp)) { /* Complete was interrupted */
  179.         ForgetCurrent();
  180.     } else {
  181.         GetFileList(&fileList, dir);
  182.         /* GetFileList returns a list of filenames with a preceding character
  183.          * denoting type
  184.          */
  185.         l = CompleteStart(dir);
  186.         /* GetFileList gets types, so might as well set -F */
  187.         CompleteSetFlags("-F"); 
  188.          
  189.         for (f = fileList.first; f != NULL; f = f->next) {
  190.             char *tmp = (char *)malloc(strlen(f->line)+1);
  191.             strcpy(tmp,f->line+1);
  192.             if (f->line[0] == 'd')
  193.                 strcat(tmp,"/");
  194.             CompleteParse(tmp);
  195.             free(tmp);
  196.         }
  197.     }
  198.     SetBar(NULL, NULL, NULL, -1, 1); /* Reset bar */
  199.     CompleteFinish();
  200.     DisposeLineListContents(&fileList);
  201.     SIGNAL(SIGINT, si);
  202.     SIGNAL(SIGPIPE, sp);
  203.     return l;
  204. }
  205.  
  206. static void
  207. InitFileList(FileList *f)
  208. {
  209.     f->names = 0;
  210.     f->count = 0;
  211.     f->alloc = 0;
  212. }
  213.  
  214. static void
  215. EmptyFileList(FileList *f)
  216. {
  217.     int i;
  218.     if (!f->names)
  219.         return;
  220.     for (i=0;i<f->count;i++)
  221.         free(f->names[i]);
  222.     free(f->names);
  223.     InitFileList(f);
  224. }
  225.  
  226. /* Add a filename to the list */
  227. static void
  228. FileListAdd(FileList *f, char *s)
  229. {
  230.     if (f->alloc <= f->count+1) {
  231.         f->alloc += 10;
  232.         if (f->names)
  233.             f->names = (char **) realloc(f->names, f->alloc * sizeof(char*));
  234.         else
  235.             f->names = (char **) malloc(f->alloc * sizeof(char*));
  236.     }
  237.     f->names[f->count++] = s;
  238.     f->names[f->count] = 0;
  239. }
  240.  
  241. static int
  242. CompareStrings(char **a, char **b)
  243. {
  244.     return strcmp(*a, *b);
  245. }
  246.  
  247. static void
  248. CompleteMatches(FileList *l, char *word, CompleteFunc f)
  249. {
  250.     char *s;
  251.     InitFileList(l);
  252.     for (s = (*f)(word, 0); s ; s = (*f)(word, 1))
  253.         FileListAdd(l,s);
  254.     if (l->count > 1)
  255.         QSORT(l->names, l->count, sizeof (char*), CompareStrings);
  256.     return;
  257. }
  258.  
  259. void
  260. ClearDirCache(void)
  261. {
  262.     DirCache *next, *curr;
  263.     for (curr = cacheHead ; curr ; curr = next) {
  264.         next = curr->next;
  265.         EmptyFileList(&curr->files);
  266.         if (curr->name)
  267.             free(curr->name);
  268.         free(curr);
  269.     }
  270.     cacheHead = 0;
  271.     currentCache = 0;
  272. }
  273.  
  274. /* Start a completion cycle - the initializes some memory to put the
  275.  * next listing into.
  276.  */
  277. DirCache *
  278. CompleteStart(char *dir)
  279. {
  280.     /* The flag determines whether this is a clear due to a cd or other
  281.      * cache invalidating command (flag=0) or due to the start of an ls
  282.      * command (flag=1)
  283.      */
  284.     DirCache *res;
  285.     
  286.     if (currentCache) {
  287.         Error(kDontPerror,
  288.             "Starting new completion without finishing last one\n");
  289.         CompleteFinish();
  290.     }
  291.     dir = ConvertDir(dir);
  292.     res = (DirCache *)malloc(sizeof *res);
  293.     res->name = dir;
  294.     res->flags = 0;
  295.     res->next = 0;
  296.     InitFileList(&res->files);
  297.     currentCache = res;
  298.  
  299.     return res;
  300. }
  301.  
  302. static int
  303. BetterCache(DirCache *a, DirCache *b)
  304. {
  305.     /* Returns true if a is better than b */
  306.     /* The only way b is better is if it has directory information and 
  307.      * a doesn't
  308.      */
  309.     if (b->flags & (LS_L|LS_F) && !(a->flags & (LS_L|LS_F)))
  310.         return 0;
  311.     return 1;
  312. }
  313.  
  314. void
  315. CompleteFinish(void)
  316. {
  317.     /* Finish off the current completion, adding it to the cache if it's
  318.      * better than the old one
  319.      */
  320.     DirCache *c;
  321.     if (!currentCache)
  322.         return;
  323.     
  324.     c = FindDirCache(currentCache->name);
  325.     if (!c) {
  326.         currentCache->next = cacheHead;
  327.         cacheHead = currentCache;
  328.     } else {
  329.         if (BetterCache(currentCache, c)) {
  330.             /* Replace old with new - we just nuke the old and copy the new
  331.              * over it
  332.              */
  333.             currentCache->next = c->next;
  334.             EmptyFileList(&c->files);
  335.             free(c->name);
  336.             *c = *currentCache;
  337.         } else {
  338.             /* Discard new */
  339.             EmptyFileList(¤tCache->files);
  340.             free(currentCache->name);
  341.         }
  342.         /* We free the memory allocated to the new since we copied over the
  343.          * old or just left the old
  344.          */
  345.         free(currentCache);
  346.     }
  347.     currentCache = 0;
  348. }
  349.     
  350. /* Generate dir/file names for the readline completion generator */
  351. static char *
  352. CompleteDirFileGenerator(char *text,int state, int dir)
  353. {
  354.     static int len,ind;
  355.     static DirCache *c;
  356.     static char *find;
  357.     static string base;
  358.     string res;
  359.     char *cmd;
  360.     char *s;
  361.  
  362.     if (!state) {
  363.         ind = 0;
  364.         s = strrchr(text,'/');
  365.         if (s) {
  366.             Strmcpy(base, text, s - text + 1, sizeof(base));
  367.             find = s+1;
  368.             c = GetCompleteFiles(base);
  369.         } else {
  370.             c = GetCompleteFiles(".");
  371.             find = text;
  372.             base[0] = '\0';
  373.         }
  374.         if (!c) {
  375.             return 0;
  376.         }
  377.         len = strlen(find);
  378.     }
  379.  
  380.     while (ind < c->files.count) {
  381.         cmd = c->files.names[ind++];
  382.         if (!strncmp(cmd,find,len)) {
  383.             if (dir && (c->flags & (LS_L|LS_F)) && 
  384.                     !(cmd[strlen(cmd)-1] == '/' || cmd[strlen(cmd)-1] == '@'))
  385.                 continue;
  386.             STRNCPY(res, base);
  387.             STRNCAT(res, cmd);
  388.             return (StrDup(res));
  389.         }
  390.     }
  391.     return 0;
  392. }
  393.  
  394. /* Generate file names for the readline completion generator */
  395. static char *
  396. CompleteFileGenerator(char *text,int state)
  397. {
  398.     return CompleteDirFileGenerator(text,state,0);
  399. }
  400.  
  401. /* Generate directories for the readline completion generator */
  402. static char *
  403. CompleteDirGenerator(char *text,int state)
  404. {
  405.     return CompleteDirFileGenerator(text,state,1);
  406. }
  407.  
  408. /* Generate commands for the readline completion routines */
  409. static char *
  410. CompleteCommandGenerator(char *text,int state)
  411. {
  412.     static int len,ind;
  413.     char *cmd;
  414.  
  415.     if (!state) {
  416.         len = strlen(text);
  417.         ind = 0;
  418.     }
  419.  
  420.     while (ind < gNumCommands) {
  421.         cmd = gCommands[ind].name;
  422.         ind ++;
  423.         if (!strncmp(cmd,text,len))
  424.             return StrDup(cmd);
  425.     }
  426.     return 0;
  427. }
  428.  
  429. /* Generate options for the readline completion routines */
  430. static char *
  431. CompleteOptionGenerator(char *text,int state)
  432. {
  433.     static int len,ind;
  434.     char *cmd;
  435.  
  436.     if (!state) {
  437.         len = strlen(text);
  438.         ind = 0;
  439.     }
  440.  
  441.     while (ind < gNumEditablePrefOpts) {
  442.         cmd = gPrefOpts[ind].name;
  443.         ind ++;
  444.         if (!strncmp(cmd,text,len))
  445.             return StrDup(cmd);
  446.     }
  447.     return 0;
  448. }
  449.  
  450. /* Generate options for the readline completion routines */
  451. static char *
  452. CompleteHostGenerator(char *text,int state)
  453. {
  454.     static int len;
  455.     static BookmarkPtr curr;
  456.     char *cmd;
  457.  
  458.     if (!state) {
  459.         len = strlen(text);
  460.         curr = gFirstRsi;
  461.     }
  462.  
  463.     while (curr) {
  464.         cmd = curr->bookmarkName;
  465.         curr = curr->next;
  466.         if (!strncmp(cmd,text,len))
  467.             return StrDup(cmd);
  468.     }
  469.     return 0;
  470. }
  471.  
  472. static char *
  473. CompleteNoneGenerator(char *text, int state)
  474. {
  475.     return 0;
  476. }
  477.  
  478. static CompleteFunc
  479. FindCompleteFunc(char *line, int start)
  480. {
  481.     int len= 0;
  482.     string cmd;
  483.     Command *c;
  484.  
  485.     if (start == 0)
  486.         return CompleteCommandGenerator;
  487.  
  488.     while (line[len] && isspace(line[len])) len++;
  489.     while (line[len] && !isspace(line[len])) len++;
  490.     Strmcpy(cmd, line, len, sizeof(cmd));
  491.     c = GetCommand(cmd, 0);
  492.     if (!c)
  493.         return CompleteFileGenerator;
  494.     switch (c->complete) {
  495.         case kCompleteDir:
  496.             if (gRmtInfo.isUnix)
  497.                 return CompleteDirGenerator;
  498.             /* Fall through */
  499.         case kCompleteFile:
  500.             return CompleteFileGenerator;
  501.         case kCompleteCmd:
  502.             return CompleteCommandGenerator;
  503.         case kCompleteOption:
  504.             return CompleteOptionGenerator;
  505.         case kCompleteHost:
  506.             return CompleteHostGenerator;
  507.         case kCompleteLocal:
  508. #ifdef HAVE_FILENAME_COMPLETION_FUNCTION
  509.             return filename_completion_function;
  510. #endif
  511.         default:
  512.             return CompleteNoneGenerator;
  513.     }
  514. }
  515.  
  516. static void
  517. ForgetCurrent(void)
  518. {
  519.     DirCache *c;
  520.     if (!currentCache)
  521.         return;
  522.     c = cacheHead;
  523.     if (c != currentCache)
  524.         return; /* Something really weird is happening */
  525.     EmptyFileList(&c->files);
  526.     cacheHead = c->next;
  527.     free(c);
  528.     currentCache = 0;
  529. }
  530.     
  531. /* Look through ls flags to determine if this is ls -l/ls -F, etc. */
  532. void
  533. CompleteSetFlags(char *s)
  534. {
  535.     if (!currentCache)
  536.         return;
  537.     if (*s == '-') {
  538.         if (strchr(s,'l'))
  539.             currentCache->flags |= LS_L;
  540.         if (strchr(s,'F'))
  541.             currentCache->flags |= LS_F;
  542.         if (strchr(s,'R'))
  543.             currentCache->flags |= LS_R;
  544.     } else if (*s) {
  545.         if (strchr(s,'*'))
  546.             ForgetCurrent();
  547.         if (strchr(s,'['))
  548.             ForgetCurrent();
  549.         if (currentCache->flags & LS_DIR)
  550.             ForgetCurrent();
  551.         if (currentCache) {
  552.             free(currentCache->name);
  553.             currentCache->name = ConvertDir(s);
  554.             currentCache->flags |= LS_DIR;
  555.         }
  556.     }
  557. }
  558.  
  559. /* Parse the output of ls for filenames */
  560. void
  561. CompleteParse(char *s)
  562. {
  563.     char *t;
  564.     int len=0;
  565.     string ss;
  566.     string tmp;
  567.  
  568.     if (!*s) return;
  569.     if (!currentCache) return;
  570.     if (currentCache->flags & LS_R) {
  571.         while (s[len] && s[len] != ':') len++;
  572.         if (s[len] == ':') {
  573.             int tmp_type = currentCache->flags; 
  574.             /* Save the type which is clobbered by CompleteStart */
  575.             Strmcpy(ss, s, len, sizeof(ss));
  576.             CompleteFinish();
  577.             CompleteStart(ss);
  578.             currentCache->flags = tmp_type;
  579.             return;
  580.         }
  581.     }
  582.     if (currentCache->flags & LS_L) { /* Only use the last word on the line */
  583.         /* If it's a "total <length>" line, ignore it. */
  584.         if (!strncmp(s,"total ",6))
  585.             return;
  586.         t = s+strlen(s) - 1;
  587.         while (!isspace(*t) && t>s) t--;
  588.         if (isspace(*t)) t++;
  589.         if (!(currentCache->flags & LS_F) && s[0] == 'd') {
  590.             /* We have a dir with no -F flag -
  591.              * get type from first character on line
  592.              */
  593.             STRNCPY(tmp, t);
  594.             STRNCAT(tmp, "/");
  595.             t = tmp;
  596.         } else if (s[0] == 'l') { 
  597.             /* It's a soft link - get the name of the link, not where it goes */
  598.             char *end = t-1;
  599.             /* Line should be lrwxrwxrwx .... file -> dest */
  600.             while (!(*end == '-' && end[1] == '>') && end > s) end--;
  601.             /* Sometimes the "-> dest" is missing */
  602.             if (end > s) {
  603.                 /* end points to the space before "->" */
  604.                 end --;
  605.                 t = end - 1;
  606.                 /* Find the start of the word */
  607.                 while (!isspace(*t) && t>s) t--;
  608.                 if (isspace(*t)) t++;
  609.                 Strmcpy(tmp, t, end-t+1, sizeof(tmp));
  610.                 strcpy(tmp + (end-t), "@");
  611.                 t = tmp;
  612.             }
  613.         }
  614.     } else { /* Use every word on the line */
  615.         t = s;
  616.     }
  617.     while ((*t != '\0') && (isspace(*t)))
  618.         t++;
  619.     while (*t != '\0') {
  620.         len = 0;
  621.         while (t[len] && !isspace(t[len])) len++;
  622.         /* Ignore last char if we gave -F to ls and it is one of @=* */
  623.         /* Note: We don't remove the '/' off directories */
  624.         if ((currentCache->flags & LS_F) && strchr("=*",t[len-1]))
  625.             FileListAdd(¤tCache->files,StrnDup(t,len-1));
  626.         else
  627.             FileListAdd(¤tCache->files,StrnDup(t,len));
  628.         t+=len;
  629.         while ((*t != '\0') && (isspace(*t)))
  630.             t++;
  631.     }
  632. }
  633.     
  634. /* How much of these 2 strings match? */
  635. static int
  636. MatchingLen(char *a, char *b)
  637. {
  638.     int i;
  639.     for (i=0;a[i] && b[i];i++)
  640.         if (a[i] != b[i])
  641.             break;
  642.     return i;
  643. }
  644.  
  645. /* Get the completion characters for the word in line that ends at
  646.  * position off
  647.  */
  648. char *
  649. CompleteGet(char *line, int off)
  650. {
  651.     int i;
  652.     int wstart;
  653.     int matchlen;
  654.     int cplen;
  655.     int alen;
  656.     char *cp;
  657.     string res;
  658.     CompleteFunc f;
  659.     string match;
  660.     FileList files;
  661.  
  662.     if (!line)
  663.         return 0;
  664.  
  665.     /* Find the start of the word */
  666.     for (wstart = off - 1; wstart >= 0 ; wstart --) {
  667.         if (strchr(" \t\n",line[wstart]))
  668.             break;
  669.     }
  670.     wstart++;
  671.  
  672.     Strmcpy(match, line+wstart, off-wstart, sizeof(match));
  673.     f = FindCompleteFunc(line,wstart);
  674.     CompleteMatches(&files, match, f);
  675.  
  676.     /* No matching files - give up */
  677.     if (!files.count)
  678.         return 0;
  679.  
  680.     /* If there was only one match, we complete the word and add a space
  681.      * as well (but only if it doesn't end in '/'
  682.      */
  683.     if (files.count == 1) {
  684.         matchlen = (int) strlen(files.names[0]);
  685.         cp = files.names[0]+off-wstart;
  686.         cplen = matchlen - (off-wstart) + 1;
  687.         Strmcpy(res, cp, cplen, sizeof(res));
  688.         alen = matchlen - 1;
  689.         if (files.names[0][alen] == '@') {
  690.             res[strlen(res)-1] = 0;
  691.         } else if (files.names[0][alen] != '/') {
  692.             STRNCAT(res, " ");
  693.         }
  694.         EmptyFileList(&files);
  695.         cp = StrDup(res);
  696.         return (cp);
  697.     }
  698.  
  699.     /* Otherwise, find the longest common prefix of all words that match */
  700.     matchlen = strlen(files.names[0]);
  701.     for (i=1;i<files.count;i++) {
  702.         int newmatch;
  703.         newmatch = MatchingLen(files.names[i], files.names[i-1]);
  704.         if (newmatch < matchlen)
  705.             matchlen = newmatch;
  706.     }
  707.  
  708.     /* Now, give the minimum number of characters which matched in all words */
  709.     if (matchlen > off-wstart) {
  710.         cp = StrnDup(files.names[0]+off-wstart, matchlen - (off-wstart));
  711.     } else {
  712.         cp = 0;
  713.     }
  714.     EmptyFileList(&files);
  715.     return (cp);
  716. }
  717.  
  718. /* Find the start of the last pathname component */
  719. static char *
  720. FindStart(char *s)
  721. {
  722.     char *tmp;
  723.     for (tmp = s;*tmp;tmp++)
  724.         if (*tmp == '/' && tmp[1])
  725.             s = tmp+1;
  726.     return s;
  727. }
  728.         
  729. /* Print out what options we have for completing this word */
  730. void
  731. CompleteOptions(char *line, int off)
  732. {
  733.     int wstart;
  734.     int maxlen,len,i,j;
  735.     CompleteFunc f;
  736.     string match;
  737.     FileList files;
  738.     int lines, columns;
  739.  
  740.     if (!line)
  741.         return;
  742.  
  743.     /* Find start of word */
  744.     for (wstart = off - 1; wstart >= 0 ; wstart --) {
  745.         if (strchr(" \t\n",line[wstart]))
  746.             break;
  747.     }
  748.     wstart++;
  749.  
  750.     Strmcpy(match, line+wstart, off-wstart, sizeof(match));
  751.     f = FindCompleteFunc(line,wstart);
  752.     CompleteMatches(&files, match, f);
  753.  
  754.     if (!files.count)
  755.         return;
  756.  
  757.     /* Find the maximum length filename that matches (for nice outputting) */
  758.     maxlen = strlen(FindStart(files.names[0]));
  759.     for (i=1;i<files.count;i++) {
  760.         len = strlen(FindStart(files.names[i]));
  761.         if (len>maxlen)
  762.             maxlen = len;
  763.     }
  764.  
  765.     /* Calculate how many lines to display on: we want to display like ls
  766.      * does: 1 3 5
  767.      *       2 4 6
  768.      * Use (gScreenWidth+1)/(maxlen+2) since we never print the last pair
  769.      * of spaces on a line.
  770.      */
  771.     columns = (gScreenWidth+1) / (maxlen+2);
  772.     if (columns < 1)
  773.         columns = 1;
  774.     lines = (files.count + columns -1) / columns;
  775.     /* A blank line so we can see where things start */
  776.     PrintF("\n");
  777.     for (i = 0; i<lines;i++) {
  778.         for (j=0;j<columns;j++) {
  779.             char *start;
  780.             int off = i + j*lines;
  781.             if (off >= files.count)
  782.                 continue;
  783.             start = FindStart(files.names[off]);
  784.                 
  785.             PrintF("%-*.*s",maxlen,maxlen,start);
  786.             if (j < columns-1)
  787.                 PrintF("  ");
  788.         }
  789.         PrintF("\n");
  790.     }
  791.     /* Ensure that we can see the things that have just arrived */
  792.     UpdateScreen(1); 
  793.  
  794.     EmptyFileList(&files);
  795. }
  796.  
  797. #ifndef HAVE_LIBREADLINE
  798. void
  799. InitReadline(void)
  800. {
  801. }
  802.  
  803. #else
  804.  
  805.  
  806. /* Completion function for readline */
  807. static char **
  808. ncftp_completion(char *text, int start, int end)
  809. {
  810.     CompleteFunc f;
  811.  
  812.     f = FindCompleteFunc(text, start);
  813.     return completion_matches(text,f);
  814. }
  815.  
  816. void
  817. InitReadline(void)
  818. {
  819.     rl_readline_name = "ncftp";
  820.     rl_attempted_completion_function = ncftp_completion;
  821. }
  822.  
  823. #endif
  824.