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
--
⑨