shithub: plumb-complete

Download patch

ref: c00282de3cdec23843a7398ebefc0f9593ebf3be
author: glenda <glenda@cirno>
date: Fri Apr 4 12:15:08 EDT 2025

prototype plumbing completion system

--- /dev/null
+++ b/complete.c
@@ -1,0 +1,274 @@
+#include <u.h>
+#include <libc.h>
+#include <plumb.h>
+#include <complete.h>
+
+typedef struct EQueue EQueue;
+
+struct EQueue {
+	int		id;
+	char		*buf;
+	int		nbuf;
+	EQueue	*next;
+};
+
+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);
+	}
+}
+
+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 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);
+}
+
+void
+main(int argc, char *argv[])
+{
+	int fd, n;
+	Plumbmsg *msg;
+	Completion *c;
+	char *prefix;
+	
+	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;
+		}
+		
+		c = complete(msg->wdir, prefix);
+		if(c == nil){
+			plumbfree(msg);
+			continue;
+		}
+		
+		sendcompletionresponse(msg, c);
+		freecompletion(c);
+		plumbfree(msg);
+	}
+	
+	exits(nil);
+}
\ No newline at end of file
--