ref: 1b13f47873c44d49bbf4d72bca49fe0f82a197b9
dir: /complete.c/
#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <plumb.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, **tokens;
int ntokens, maxtokens, i, j, len, minlen, nmatches;
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) {
int tokenlen = next - token;
char *newtok;
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) {
free(newtok);
break;
}
if(i == ntokens)
tokens[ntokens++] = newtok;
else
free(newtok);
}
}
if(ntokens == 0) {
free(tokens);
return nil;
}
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);
}
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);
}