home *** CD-ROM | disk | FTP | other *** search
- /* Complete.c */
- /* Tim MacKenzie, t.mackenzie@trl.oz.au, April '95 */
-
- #include "Sys.h"
- #include "LineList.h"
- #include "Cmdline.h"
- #include "Complete.h"
- #include "Prefs.h"
- #include "Bookmark.h"
- #include "Util.h"
- #include "List.h"
- #include <signal.h>
- #include <setjmp.h>
-
- extern int gNumCommands; /* Cmdlist.c */
- extern Command gCommands[]; /* Cmdlist.c */
- extern PrefOpt gPrefOpts[]; /*Prefs.c */
- extern int gNumEditablePrefOpts; /*Prefs.c */
- extern BookmarkPtr gFirstRsi; /* Bookmark.c */
- extern longstring gRemoteCWD; /* Cmds.c */
- extern Bookmark gRmtInfo; /* Bookmark.c */
- extern int gScreenWidth; /* Win.c */
-
- /* We can't use a linked list because we need random access for the
- * output routine...
- */
- typedef struct {
- char **names;
- int count;
- int alloc;
- } FileList;
-
- struct _DirCache {
- struct _DirCache *next;
- FileList files;
- char *name;
- int flags; /* Combination of flags below */
- };
-
- static DirCache *cacheHead;
- static DirCache *currentCache;
-
- static void ForgetCurrent(void);
- /* Upon receipt of a signal we have to abort the completion */
- static jmp_buf gCompleteJmp;
-
- #define LS_F 1 /* ls -F */
- #define LS_L 2 /* ls -l */
- #define LS_DIR 4 /* Have been supplied a directory to list */
- #define LS_R 8 /* ls -R */
-
- typedef char * (*CompleteFunc)(char *, int);
-
- #ifdef HAVE_LIBREADLINE
- #include <readline/readline.h>
-
- #ifdef HAVE_FILENAME_COMPLETION_FUNCTION
- /* This should have been in your readline.h already, but some older
- * versions of the library are still lurking out there.
- */
- extern char *filename_completion_function (); /* Yes, not a prototype... */
- #endif
- #endif
-
-
- static char *
- Strmcpy(char *dst, char *src, int howMany, size_t dstSize)
- {
- size_t max;
-
- /* If you want to limit Strncpy to x characters, you
- * must pass x + 1 because Strncpy subtracts one for the
- * nul terminator.
- */
- max = howMany + 1;
-
- if (max > dstSize)
- max = dstSize;
- return (Strncpy(dst, src, max));
- } /* Strmcpy */
-
-
- static char *
- StrnDup(char *s, int len)
- {
- char *res;
-
- res = (char *) malloc(len+1);
- strncpy(res,s,len);
- res[len] = 0;
- return res;
- }
-
- static char *
- ConvertDir(char *dir)
- {
- int baselen;
- string res;
- int i,j;
-
- baselen = (int) strlen(gRemoteCWD);
- while (dir && *dir) {
- if (*dir == '/') { /* It's an absolute pathname */
- baselen = 0;
- break;
- }
- if (*dir == '.' && dir[1] == '.') { /* ../ remove bit from base */
- dir += 2;
- while (*dir && *dir == '/') dir++;
- while (baselen > 0 && gRemoteCWD[baselen-1] != '/')
- baselen--;
- baselen--; /* Move back past the '/' */
- if (baselen < 0)
- baselen = 0;
- continue;
- }
- if (*dir == '.' && (dir[1] == '/' || dir[1] == 0)) {
- /* . or ./ just remove it from dirname */
- dir += 1;
- while (*dir && *dir == '/') dir++;
- continue;
- }
- break;
- }
- if (!dir)
- dir = "";
- Strmcpy(res, gRemoteCWD, baselen, sizeof(res));
- STRNCAT(res, "/");
- STRNCAT(res, dir);
- STRNCAT(res, "/");
-
- /* Remove //'s in the name */
- for (i=j=1;res[i];i++) {
- if (res[i] == '/' && res[j-1] == '/')
- continue;
- res[j++] = res[i];
- }
- res[j] = 0;
- return StrDup(res);
- }
-
- static DirCache *
- FindDirCache(char *dir)
- {
- DirCache *l;
- dir = ConvertDir(dir);
- for (l = cacheHead;l;l=l->next)
- if (!strcmp(dir, l->name))
- break;
- free(dir);
- return l;
- }
-
- static void
- SigComplete(/* int sigNumUNUSED */ void)
- {
- alarm(0);
- longjmp(gCompleteJmp,1);
- }
-
- /* Get the filenames from the current directory */
- static DirCache *
- GetCompleteFiles(char *dir)
- {
- LineList fileList;
- LinePtr f;
- DirCache *volatile l;
- volatile Sig_t si, sp;
-
- l = FindDirCache(dir);
- if (l)
- return l;
-
- InitLineList(&fileList);
- si = SIGNAL(SIGINT, SigComplete);
- sp = SIGNAL(SIGPIPE, SigComplete);
- SetBar(NULL, "GETTING COMPLETIONS", NULL, -1, 1);
- if (setjmp(gCompleteJmp)) { /* Complete was interrupted */
- ForgetCurrent();
- } else {
- GetFileList(&fileList, dir);
- /* GetFileList returns a list of filenames with a preceding character
- * denoting type
- */
- l = CompleteStart(dir);
- /* GetFileList gets types, so might as well set -F */
- CompleteSetFlags("-F");
-
- for (f = fileList.first; f != NULL; f = f->next) {
- char *tmp = (char *)malloc(strlen(f->line)+1);
- strcpy(tmp,f->line+1);
- if (f->line[0] == 'd')
- strcat(tmp,"/");
- CompleteParse(tmp);
- free(tmp);
- }
- }
- SetBar(NULL, NULL, NULL, -1, 1); /* Reset bar */
- CompleteFinish();
- DisposeLineListContents(&fileList);
- SIGNAL(SIGINT, si);
- SIGNAL(SIGPIPE, sp);
- return l;
- }
-
- static void
- InitFileList(FileList *f)
- {
- f->names = 0;
- f->count = 0;
- f->alloc = 0;
- }
-
- static void
- EmptyFileList(FileList *f)
- {
- int i;
- if (!f->names)
- return;
- for (i=0;i<f->count;i++)
- free(f->names[i]);
- free(f->names);
- InitFileList(f);
- }
-
- /* Add a filename to the list */
- static void
- FileListAdd(FileList *f, char *s)
- {
- if (f->alloc <= f->count+1) {
- f->alloc += 10;
- if (f->names)
- f->names = (char **) realloc(f->names, f->alloc * sizeof(char*));
- else
- f->names = (char **) malloc(f->alloc * sizeof(char*));
- }
- f->names[f->count++] = s;
- f->names[f->count] = 0;
- }
-
- static int
- CompareStrings(char **a, char **b)
- {
- return strcmp(*a, *b);
- }
-
- static void
- CompleteMatches(FileList *l, char *word, CompleteFunc f)
- {
- char *s;
- InitFileList(l);
- for (s = (*f)(word, 0); s ; s = (*f)(word, 1))
- FileListAdd(l,s);
- if (l->count > 1)
- QSORT(l->names, l->count, sizeof (char*), CompareStrings);
- return;
- }
-
- void
- ClearDirCache(void)
- {
- DirCache *next, *curr;
- for (curr = cacheHead ; curr ; curr = next) {
- next = curr->next;
- EmptyFileList(&curr->files);
- if (curr->name)
- free(curr->name);
- free(curr);
- }
- cacheHead = 0;
- currentCache = 0;
- }
-
- /* Start a completion cycle - the initializes some memory to put the
- * next listing into.
- */
- DirCache *
- CompleteStart(char *dir)
- {
- /* The flag determines whether this is a clear due to a cd or other
- * cache invalidating command (flag=0) or due to the start of an ls
- * command (flag=1)
- */
- DirCache *res;
-
- if (currentCache) {
- Error(kDontPerror,
- "Starting new completion without finishing last one\n");
- CompleteFinish();
- }
- dir = ConvertDir(dir);
- res = (DirCache *)malloc(sizeof *res);
- res->name = dir;
- res->flags = 0;
- res->next = 0;
- InitFileList(&res->files);
- currentCache = res;
-
- return res;
- }
-
- static int
- BetterCache(DirCache *a, DirCache *b)
- {
- /* Returns true if a is better than b */
- /* The only way b is better is if it has directory information and
- * a doesn't
- */
- if (b->flags & (LS_L|LS_F) && !(a->flags & (LS_L|LS_F)))
- return 0;
- return 1;
- }
-
- void
- CompleteFinish(void)
- {
- /* Finish off the current completion, adding it to the cache if it's
- * better than the old one
- */
- DirCache *c;
- if (!currentCache)
- return;
-
- c = FindDirCache(currentCache->name);
- if (!c) {
- currentCache->next = cacheHead;
- cacheHead = currentCache;
- } else {
- if (BetterCache(currentCache, c)) {
- /* Replace old with new - we just nuke the old and copy the new
- * over it
- */
- currentCache->next = c->next;
- EmptyFileList(&c->files);
- free(c->name);
- *c = *currentCache;
- } else {
- /* Discard new */
- EmptyFileList(¤tCache->files);
- free(currentCache->name);
- }
- /* We free the memory allocated to the new since we copied over the
- * old or just left the old
- */
- free(currentCache);
- }
- currentCache = 0;
- }
-
- /* Generate dir/file names for the readline completion generator */
- static char *
- CompleteDirFileGenerator(char *text,int state, int dir)
- {
- static int len,ind;
- static DirCache *c;
- static char *find;
- static string base;
- string res;
- char *cmd;
- char *s;
-
- if (!state) {
- ind = 0;
- s = strrchr(text,'/');
- if (s) {
- Strmcpy(base, text, s - text + 1, sizeof(base));
- find = s+1;
- c = GetCompleteFiles(base);
- } else {
- c = GetCompleteFiles(".");
- find = text;
- base[0] = '\0';
- }
- if (!c) {
- return 0;
- }
- len = strlen(find);
- }
-
- while (ind < c->files.count) {
- cmd = c->files.names[ind++];
- if (!strncmp(cmd,find,len)) {
- if (dir && (c->flags & (LS_L|LS_F)) &&
- !(cmd[strlen(cmd)-1] == '/' || cmd[strlen(cmd)-1] == '@'))
- continue;
- STRNCPY(res, base);
- STRNCAT(res, cmd);
- return (StrDup(res));
- }
- }
- return 0;
- }
-
- /* Generate file names for the readline completion generator */
- static char *
- CompleteFileGenerator(char *text,int state)
- {
- return CompleteDirFileGenerator(text,state,0);
- }
-
- /* Generate directories for the readline completion generator */
- static char *
- CompleteDirGenerator(char *text,int state)
- {
- return CompleteDirFileGenerator(text,state,1);
- }
-
- /* Generate commands for the readline completion routines */
- static char *
- CompleteCommandGenerator(char *text,int state)
- {
- static int len,ind;
- char *cmd;
-
- if (!state) {
- len = strlen(text);
- ind = 0;
- }
-
- while (ind < gNumCommands) {
- cmd = gCommands[ind].name;
- ind ++;
- if (!strncmp(cmd,text,len))
- return StrDup(cmd);
- }
- return 0;
- }
-
- /* Generate options for the readline completion routines */
- static char *
- CompleteOptionGenerator(char *text,int state)
- {
- static int len,ind;
- char *cmd;
-
- if (!state) {
- len = strlen(text);
- ind = 0;
- }
-
- while (ind < gNumEditablePrefOpts) {
- cmd = gPrefOpts[ind].name;
- ind ++;
- if (!strncmp(cmd,text,len))
- return StrDup(cmd);
- }
- return 0;
- }
-
- /* Generate options for the readline completion routines */
- static char *
- CompleteHostGenerator(char *text,int state)
- {
- static int len;
- static BookmarkPtr curr;
- char *cmd;
-
- if (!state) {
- len = strlen(text);
- curr = gFirstRsi;
- }
-
- while (curr) {
- cmd = curr->bookmarkName;
- curr = curr->next;
- if (!strncmp(cmd,text,len))
- return StrDup(cmd);
- }
- return 0;
- }
-
- static char *
- CompleteNoneGenerator(void)
- {
- return 0;
- }
-
- static CompleteFunc
- FindCompleteFunc(char *line, int start)
- {
- int len= 0;
- string cmd;
- Command *c;
-
- if (start == 0)
- return CompleteCommandGenerator;
-
- while (line[len] && isspace(line[len])) len++;
- while (line[len] && !isspace(line[len])) len++;
- Strmcpy(cmd, line, len, sizeof(cmd));
- c = GetCommand(cmd, 0);
- if (!c)
- return CompleteFileGenerator;
- switch (c->complete) {
- case kCompleteDir:
- if (gRmtInfo.isUnix)
- return CompleteDirGenerator;
- /* Fall through */
- case kCompleteFile:
- return CompleteFileGenerator;
- case kCompleteCmd:
- return CompleteCommandGenerator;
- case kCompleteOption:
- return CompleteOptionGenerator;
- case kCompleteHost:
- return CompleteHostGenerator;
- case kCompleteLocal:
- #ifdef HAVE_FILENAME_COMPLETION_FUNCTION
- return filename_completion_function;
- #endif
- default:
- return (CompleteFunc) CompleteNoneGenerator;
- }
- }
-
- static void
- ForgetCurrent(void)
- {
- DirCache *c;
- if (!currentCache)
- return;
- c = cacheHead;
- if (c != currentCache)
- return; /* Something really weird is happening */
- EmptyFileList(&c->files);
- cacheHead = c->next;
- free(c);
- currentCache = 0;
- }
-
- /* Look through ls flags to determine if this is ls -l/ls -F, etc. */
- void
- CompleteSetFlags(char *s)
- {
- if (!currentCache)
- return;
- if (*s == '-') {
- if (strchr(s,'l'))
- currentCache->flags |= LS_L;
- if (strchr(s,'F'))
- currentCache->flags |= LS_F;
- if (strchr(s,'R'))
- currentCache->flags |= LS_R;
- } else if (*s) {
- if (strchr(s,'*'))
- ForgetCurrent();
- if (strchr(s,'['))
- ForgetCurrent();
- if (currentCache->flags & LS_DIR)
- ForgetCurrent();
- if (currentCache) {
- free(currentCache->name);
- currentCache->name = ConvertDir(s);
- currentCache->flags |= LS_DIR;
- }
- }
- }
-
- /* Parse the output of ls for filenames */
- void
- CompleteParse(char *s)
- {
- char *t;
- int len=0;
- string ss;
- string tmp;
-
- if (!*s) return;
- if (!currentCache) return;
- if (currentCache->flags & LS_R) {
- while (s[len] && s[len] != ':') len++;
- if (s[len] == ':') {
- int tmp_type = currentCache->flags;
- /* Save the type which is clobbered by CompleteStart */
- Strmcpy(ss, s, len, sizeof(ss));
- CompleteFinish();
- CompleteStart(ss);
- currentCache->flags = tmp_type;
- return;
- }
- }
- if (currentCache->flags & LS_L) { /* Only use the last word on the line */
- /* If it's a "total <length>" line, ignore it. */
- if (!strncmp(s,"total ",6))
- return;
- t = s+strlen(s) - 1;
- while (!isspace(*t) && t>s) t--;
- if (isspace(*t)) t++;
- if (!(currentCache->flags & LS_F) && s[0] == 'd') {
- /* We have a dir with no -F flag -
- * get type from first character on line
- */
- STRNCPY(tmp, t);
- STRNCAT(tmp, "/");
- t = tmp;
- } else if (s[0] == 'l') {
- /* It's a soft link - get the name of the link, not where it goes */
- char *end = t-1;
- /* Line should be lrwxrwxrwx .... file -> dest */
- while (!(*end == '-' && end[1] == '>') && end > s) end--;
- /* Sometimes the "-> dest" is missing */
- if (end > s) {
- /* end points to the space before "->" */
- end --;
- t = end - 1;
- /* Find the start of the word */
- while (!isspace(*t) && t>s) t--;
- if (isspace(*t)) t++;
- Strmcpy(tmp, t, end-t+1, sizeof(tmp));
- strcpy(tmp + (end-t), "@");
- t = tmp;
- }
- }
- } else { /* Use every word on the line */
- t = s;
- }
- while ((*t != '\0') && (isspace(*t)))
- t++;
- while (*t != '\0') {
- len = 0;
- while (t[len] && !isspace(t[len])) len++;
- /* Ignore last char if we gave -F to ls and it is one of @=* */
- /* Note: We don't remove the '/' off directories */
- if ((currentCache->flags & LS_F) && strchr("=*",t[len-1]))
- FileListAdd(¤tCache->files,StrnDup(t,len-1));
- else
- FileListAdd(¤tCache->files,StrnDup(t,len));
- t+=len;
- while ((*t != '\0') && (isspace(*t)))
- t++;
- }
- }
-
- /* How much of these 2 strings match? */
- static int
- MatchingLen(char *a, char *b)
- {
- int i;
- for (i=0;a[i] && b[i];i++)
- if (a[i] != b[i])
- break;
- return i;
- }
-
- /* Get the completion characters for the word in line that ends at
- * position off
- */
- char *
- CompleteGet(char *line, int off)
- {
- int i;
- int wstart;
- int matchlen;
- int cplen;
- int alen;
- char *cp;
- string res;
- CompleteFunc f;
- string match;
- FileList files;
-
- if (!line)
- return 0;
-
- /* Find the start of the word */
- for (wstart = off - 1; wstart >= 0 ; wstart --) {
- if (strchr(" \t\n",line[wstart]))
- break;
- }
- wstart++;
-
- Strmcpy(match, line+wstart, off-wstart, sizeof(match));
- f = FindCompleteFunc(line,wstart);
- CompleteMatches(&files, match, f);
-
- /* No matching files - give up */
- if (!files.count)
- return 0;
-
- /* If there was only one match, we complete the word and add a space
- * as well (but only if it doesn't end in '/'
- */
- if (files.count == 1) {
- matchlen = (int) strlen(files.names[0]);
- cp = files.names[0]+off-wstart;
- cplen = matchlen - (off-wstart) + 1;
- Strmcpy(res, cp, cplen, sizeof(res));
- alen = matchlen - 1;
- if (files.names[0][alen] == '@') {
- res[strlen(res)-1] = 0;
- } else if (files.names[0][alen] != '/') {
- STRNCAT(res, " ");
- }
- EmptyFileList(&files);
- cp = StrDup(res);
- return (cp);
- }
-
- /* Otherwise, find the longest common prefix of all words that match */
- matchlen = strlen(files.names[0]);
- for (i=1;i<files.count;i++) {
- int newmatch;
- newmatch = MatchingLen(files.names[i], files.names[i-1]);
- if (newmatch < matchlen)
- matchlen = newmatch;
- }
-
- /* Now, give the minimum number of characters which matched in all words */
- if (matchlen > off-wstart) {
- cp = StrnDup(files.names[0]+off-wstart, matchlen - (off-wstart));
- } else {
- cp = 0;
- }
- EmptyFileList(&files);
- return (cp);
- }
-
- /* Find the start of the last pathname component */
- static char *
- FindStart(char *s)
- {
- char *tmp;
- for (tmp = s;*tmp;tmp++)
- if (*tmp == '/' && tmp[1])
- s = tmp+1;
- return s;
- }
-
- /* Print out what options we have for completing this word */
- void
- CompleteOptions(char *line, int off)
- {
- int wstart;
- int maxlen,len,i,j;
- CompleteFunc f;
- string match;
- FileList files;
- int lines, columns;
-
- if (!line)
- return;
-
- /* Find start of word */
- for (wstart = off - 1; wstart >= 0 ; wstart --) {
- if (strchr(" \t\n",line[wstart]))
- break;
- }
- wstart++;
-
- Strmcpy(match, line+wstart, off-wstart, sizeof(match));
- f = FindCompleteFunc(line,wstart);
- CompleteMatches(&files, match, f);
-
- if (!files.count)
- return;
-
- /* Find the maximum length filename that matches (for nice outputting) */
- maxlen = strlen(FindStart(files.names[0]));
- for (i=1;i<files.count;i++) {
- len = strlen(FindStart(files.names[i]));
- if (len>maxlen)
- maxlen = len;
- }
-
- /* Calculate how many lines to display on: we want to display like ls
- * does: 1 3 5
- * 2 4 6
- * Use (gScreenWidth+1)/(maxlen+2) since we never print the last pair
- * of spaces on a line.
- */
- columns = (gScreenWidth+1) / (maxlen+2);
- if (columns < 1)
- columns = 1;
- lines = (files.count + columns -1) / columns;
- /* A blank line so we can see where things start */
- PrintF("\n");
- for (i = 0; i<lines;i++) {
- for (j=0;j<columns;j++) {
- char *start;
- int off2 = i + j*lines;
- if (off2 >= files.count)
- continue;
- start = FindStart(files.names[off2]);
-
- PrintF("%-*.*s",maxlen,maxlen,start);
- if (j < columns-1)
- PrintF(" ");
- }
- PrintF("\n");
- }
- /* Ensure that we can see the things that have just arrived */
- UpdateScreen(1);
-
- EmptyFileList(&files);
- }
-
- #ifndef HAVE_LIBREADLINE
- void
- InitReadline(void)
- {
- }
-
- #else
-
-
- /* Completion function for readline */
- static char **
- ncftp_completion(char *text, int start, int end)
- {
- CompleteFunc f;
-
- if (end < start)
- return NULL;
- f = FindCompleteFunc(text, start);
- return completion_matches(text,f);
- }
-
- void
- InitReadline(void)
- {
- rl_readline_name = "ncftp";
- rl_attempted_completion_function = ncftp_completion;
- }
-
- #endif
-