Subversion Repositories planix.SVN

Rev

Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

#include <u.h>
#include <libc.h>
#include "complete.h"

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;
}

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

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);
        if(fd < 0)
                return nil;

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

        /* find longest string, for allocation */
        len = 0;
        for(i=0; i<n; i++){
                l = strlen(dirp[i].name) + 1 + 1; /* +1 for /   +1 for \0 */
                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));

        /* find the matches */
        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) {
                /* report interesting results */
                /* trim length back to longest common initial string */
                for(i=1; i<nfile; i++)
                        minlen = longestprefixlength(name[0], name[i], minlen);

                /* build the answer */
                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 {
                /* no match, so return all possible strings */
                for(i=0; i<n; i++){
                        name[i] = dirp[i].name;
                        mode[i] = dirp[i].mode;
                }
                nfile = n;
                c->nmatch = 0;
        }

        /* attach list of names */
        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;
}