shithub: plumb-complete

ref: b7ce059874852bf1aad83c269f0ae3bc5fd61152
dir: /complete.c/

View raw version
#include <u.h>
#include <libc.h>
#include <plumb.h>
#include <ctype.h>
#include <complete.h>

typedef struct EQueue EQueue;

struct EQueue {
	int		id;
	char		*buf;
	int		nbuf;
	EQueue	*next;
};

typedef enum CompletionType {
	DIRECTORY,
	FILE_CONTENT
} CompletionType;

static int
longestprefixlength(char *a, char *b, int n)
{
	int i, w;
	Rune ra, rb;

	for(i=0; i<n; i+=w){
		w = chartorune(&ra, a);
		chartorune(&rb, b);
		if(ra != rb)
			break;
		a += w;
		b += w;
	}
	return i;
}

static int
strpcmp(const void *va, const void *vb)
{
	char *a, *b;

	a = *(char**)va;
	b = *(char**)vb;
	return strcmp(a, b);
}

Completion*
complete(char *dir, char *s)
{
	long i, l, n, nfile, len, nbytes;
	int fd, minlen;
	Dir *dirp;
	char **name, *p;
	ulong* mode;
	Completion *c;

	if(strchr(s, '/') != nil){
		werrstr("slash character in name argument to complete()");
		return nil;
	}

	fd = open(dir, OREAD|OCEXEC);
	if(fd < 0)
		return nil;

	n = dirreadall(fd, &dirp);
	if(n <= 0){
		close(fd);
		return nil;
	}

	len = 0;
	for(i=0; i<n; i++){
		l = strlen(dirp[i].name) + 1 + 1;
		if(l > len)
			len = l;
	}

	name = malloc(n*sizeof(char*));
	mode = malloc(n*sizeof(ulong));
	c = malloc(sizeof(Completion) + len);
	if(name == nil || mode == nil || c == nil)
		goto Return;
	memset(c, 0, sizeof(Completion));

	len = strlen(s);
	nfile = 0;
	minlen = 1000000;
	for(i=0; i<n; i++)
		if(strncmp(s, dirp[i].name, len) == 0){
			name[nfile] = dirp[i].name;
			mode[nfile] = dirp[i].mode;
			if(minlen > strlen(dirp[i].name))
				minlen = strlen(dirp[i].name);
			nfile++;
		}

	if(nfile > 0) {
		for(i=1; i<nfile; i++)
			minlen = longestprefixlength(name[0], name[i], minlen);

		c->complete = (nfile == 1);
		c->advance = c->complete || (minlen > len);
		c->string = (char*)(c+1);
		memmove(c->string, name[0]+len, minlen-len);
		if(c->complete)
			c->string[minlen++ - len] = (mode[0]&DMDIR)? '/' : ' ';
		c->string[minlen - len] = '\0';
		c->nmatch = nfile;
	} else {
		for(i=0; i<n; i++){
			name[i] = dirp[i].name;
			mode[i] = dirp[i].mode;
		}
		nfile = n;
		c->nmatch = 0;
	}

	nbytes = nfile * sizeof(char*);
	for(i=0; i<nfile; i++)
		nbytes += strlen(name[i]) + 1 + 1;
	c->filename = malloc(nbytes);
	if(c->filename == nil)
		goto Return;
	p = (char*)(c->filename + nfile);
	for(i=0; i<nfile; i++){
		c->filename[i] = p;
		strcpy(p, name[i]);
		p += strlen(p);
		if(mode[i] & DMDIR)
			*p++ = '/';
		*p++ = '\0';
	}
	c->nfile = nfile;
	qsort(c->filename, c->nfile, sizeof(c->filename[0]), strpcmp);

Return:
	free(name);
	free(mode);
	free(dirp);
	close(fd);
	return c;
}

void
freecompletion(Completion *c)
{
	if(c){
		free(c->filename);
		free(c);
	}
}

Completion*
completefile(char *content, char *s)
{
	char *p, *token, *next, *newtok, **tokens;
	int ntokens, maxtokens, i, j, len, minlen, tokenlen, duplicate;
	Completion *c;
	
	if(s == nil || content == nil)
		return nil;
		
	maxtokens = 1024;
	tokens = malloc(maxtokens * sizeof(char*));
	if(tokens == nil)
		return nil;
		
	ntokens = 0;
	len = strlen(s);
	
	p = content;
	while(*p != '\0' && ntokens < maxtokens) {
		while(*p != '\0' && !isalnum(*p) && *p != '_')
			p++;
			
		if(*p == '\0')
			break;
			
		token = p;
		while(isalnum(*p) || *p == '_')
			p++;
			
		next = p;
		
		if(next > token) {
			tokenlen = next - token;
			duplicate = 0;
			
			if(tokenlen < len)
				continue;
				
			if(strncmp(token, s, len) != 0)
				continue;
				
			newtok = malloc(tokenlen + 1);
			if(newtok == nil)
				continue;
				
			strncpy(newtok, token, tokenlen);
			newtok[tokenlen] = '\0';
			
			for(i = 0; i < ntokens; i++) {
				if(strcmp(tokens[i], newtok) == 0) {
					duplicate = 1;
					break;
				}
			}
			
			if(!duplicate) {
				tokens[ntokens++] = newtok;
			} else {
				free(newtok);
			}
		}
	}
	
	if(ntokens == 0) {
		free(tokens);
		return nil;
	}
	
	qsort(tokens, ntokens, sizeof(char*), strpcmp);
	
	c = malloc(sizeof(Completion) + 256);
	if(c == nil) {
		for(i = 0; i < ntokens; i++)
			free(tokens[i]);
		free(tokens);
		return nil;
	}
	
	memset(c, 0, sizeof(Completion));
	
	minlen = 10000;
	for(i = 0; i < ntokens; i++)
		if(minlen > strlen(tokens[i]))
			minlen = strlen(tokens[i]);
			
	for(i = 1; i < ntokens; i++)
		minlen = longestprefixlength(tokens[0], tokens[i], minlen);
		
	c->complete = (ntokens == 1);
	c->advance = c->complete || (minlen > len);
	c->string = (char*)(c+1);
	memmove(c->string, tokens[0]+len, minlen-len);
	if(c->complete)
		c->string[minlen++ - len] = ' ';
	c->string[minlen - len] = '\0';
	c->nmatch = ntokens;
	
	c->filename = malloc(ntokens * sizeof(char*));
	if(c->filename == nil) {
		for(i = 0; i < ntokens; i++)
			free(tokens[i]);
		free(tokens);
		free(c);
		return nil;
	}
	
	for(i = 0; i < ntokens; i++)
		c->filename[i] = tokens[i];
		
	c->nfile = ntokens;
	
	free(tokens);
	return c;
}

char*
packcompletion(Completion *c, int *outlen)
{
	char *buf, *p;
	int i, len;
	
	len = 64; /* space for header info */
	len += strlen(c->string) + 1;
	for(i = 0; i < c->nfile; i++)
		len += strlen(c->filename[i]) + 1;
	
	buf = malloc(len);
	if(buf == nil)
		return nil;
	
	p = buf;
	p += sprint(p, "%d\n", c->advance);
	p += sprint(p, "%d\n", c->complete);
	p += sprint(p, "%s\n", c->string);
	p += sprint(p, "%d\n", c->nmatch);
	p += sprint(p, "%d\n", c->nfile);
	
	for(i = 0; i < c->nfile; i++){
		strcpy(p, c->filename[i]);
		p += strlen(p) + 1;
	}
	
	*outlen = p - buf;
	return buf;
}

static int
getprefix(Plumbmsg *msg)
{
	Plumbattr *attr;
	
	for(attr = msg->attr; attr != nil; attr = attr->next)
		if(strcmp(attr->name, "prefix") == 0)
			return 1;
	return 0;
}

static char*
getprefixvalue(Plumbmsg *msg)
{
	char *prefix;
	
	prefix = plumblookup(msg->attr, "prefix");
	if(prefix == nil)
		return msg->data;
	return prefix;
}

static CompletionType
getcompletiontype(Plumbmsg *msg)
{
	char *type;
	
	type = plumblookup(msg->attr, "type");
	if(type != nil && strcmp(type, "file") == 0)
		return FILE_CONTENT;
	return DIRECTORY;
}

static void
sendcompletionresponse(Plumbmsg *msg, Completion *c)
{
	Plumbmsg response;
	char abuf[256], *databuf;
	int datalen;
	
	memset(&response, 0, sizeof(response));
	response.src = "complete";
	response.dst = "completion-response";
	response.wdir = msg->wdir;
	response.type = "completion";
	
	snprint(abuf, sizeof abuf, "advance=%d complete=%d nmatch=%d nfile=%d", 
		c->advance, c->complete, c->nmatch, c->nfile);
	response.attr = plumbunpackattr(abuf);
	
	databuf = packcompletion(c, &datalen);
	if(databuf == nil){
		plumbfree(msg);
		return;
	}
	
	response.data = databuf;
	response.ndata = datalen;
	
	plumbsend(plumbopen("send", OWRITE), &response);
	
	free(databuf);
	free(response.attr);
}

void
main(int argc, char *argv[])
{
	int fd, n;
	Plumbmsg *msg;
	Completion *c;
	char *prefix;
	CompletionType ctype;
	
	ARGBEGIN{
	default:
		sysfatal("usage: complete");
	}ARGEND

	fd = plumbopen("completion", OREAD);
	if(fd < 0)
		sysfatal("can't open completion port: %r");
	
	while((msg = plumbrecv(fd)) != nil){
		if(!getprefix(msg)){
			plumbfree(msg);
			continue;
		}
		
		prefix = getprefixvalue(msg);
		if(prefix == nil || prefix[0] == '\0'){
			plumbfree(msg);
			continue;
		}
		
		ctype = getcompletiontype(msg);
		
		if(ctype == FILE_CONTENT) {
			if(msg->ndata <= 0) {
				plumbfree(msg);
				continue;
			}
			
			c = completefile(msg->data, prefix);
		} else {
			c = complete(msg->wdir, prefix);
		}
		
		if(c == nil){
			plumbfree(msg);
			continue;
		}
		
		sendcompletionresponse(msg, c);
		freecompletion(c);
		plumbfree(msg);
	}
	
	exits(nil);
}