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 >
Wrap
C/C++ Source or Header
|
1996-09-28
|
17KB
|
824 lines
/* 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 ignored)
{
alarm(0);
longjmp(gCompleteJmp,1);
}
/* Get the filenames from the current directory */
static DirCache *
GetCompleteFiles(char *dir)
{
LineList fileList;
LinePtr f;
DirCache *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(char *text, int state)
{
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 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 off = i + j*lines;
if (off >= files.count)
continue;
start = FindStart(files.names[off]);
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;
f = FindCompleteFunc(text, start);
return completion_matches(text,f);
}
void
InitReadline(void)
{
rl_readline_name = "ncftp";
rl_attempted_completion_function = ncftp_completion;
}
#endif