ref: b7ce059874852bf1aad83c269f0ae3bc5fd61152
dir: /complete.c/
#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); }