Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <fcall.h>
#include <plumb.h>
#include "dat.h"
#include "edit.h"
#include "fns.h"

static char     linex[]="\n";
static char     wordx[]=" \t\n";
struct cmdtab cmdtab[]={
/*      cmdc    text    regexp  addr    defcmd  defaddr count   token    fn     */
        '\n',   0,      0,      0,      0,      aDot,   0,      0,      nl_cmd,
        'a',    1,      0,      0,      0,      aDot,   0,      0,      a_cmd,
        'b',    0,      0,      0,      0,      aNo,    0,      linex,  b_cmd,
        'c',    1,      0,      0,      0,      aDot,   0,      0,      c_cmd,
        'd',    0,      0,      0,      0,      aDot,   0,      0,      d_cmd,
        'e',    0,      0,      0,      0,      aNo,    0,      wordx,  e_cmd,
        'f',    0,      0,      0,      0,      aNo,    0,      wordx,  f_cmd,
        'g',    0,      1,      0,      'p',    aDot,   0,      0,      g_cmd,
        'i',    1,      0,      0,      0,      aDot,   0,      0,      i_cmd,
        'm',    0,      0,      1,      0,      aDot,   0,      0,      m_cmd,
        'p',    0,      0,      0,      0,      aDot,   0,      0,      p_cmd,
        'r',    0,      0,      0,      0,      aDot,   0,      wordx,  e_cmd,
        's',    0,      1,      0,      0,      aDot,   1,      0,      s_cmd,
        't',    0,      0,      1,      0,      aDot,   0,      0,      m_cmd,
        'u',    0,      0,      0,      0,      aNo,    2,      0,      u_cmd,
        'v',    0,      1,      0,      'p',    aDot,   0,      0,      g_cmd,
        'w',    0,      0,      0,      0,      aAll,   0,      wordx,  w_cmd,
        'x',    0,      1,      0,      'p',    aDot,   0,      0,      x_cmd,
        'y',    0,      1,      0,      'p',    aDot,   0,      0,      x_cmd,
        '=',    0,      0,      0,      0,      aDot,   0,      linex,  eq_cmd,
        'B',    0,      0,      0,      0,      aNo,    0,      linex,  B_cmd,
        'D',    0,      0,      0,      0,      aNo,    0,      linex,  D_cmd,
        'X',    0,      1,      0,      'f',    aNo,    0,      0,      X_cmd,
        'Y',    0,      1,      0,      'f',    aNo,    0,      0,      X_cmd,
        '<',    0,      0,      0,      0,      aDot,   0,      linex,  pipe_cmd,
        '|',    0,      0,      0,      0,      aDot,   0,      linex,  pipe_cmd,
        '>',    0,      0,      0,      0,      aDot,   0,      linex,  pipe_cmd,
/* deliberately unimplemented:
        'k',    0,      0,      0,      0,      aDot,   0,      0,      k_cmd,
        'n',    0,      0,      0,      0,      aNo,    0,      0,      n_cmd,
        'q',    0,      0,      0,      0,      aNo,    0,      0,      q_cmd,
        '!',    0,      0,      0,      0,      aNo,    0,      linex,  plan9_cmd,
 */
        0,      0,      0,      0,      0,      0,      0,      0,
};

Cmd     *parsecmd(int);
Addr    *compoundaddr(void);
Addr    *simpleaddr(void);
void    freecmd(void);
void    okdelim(int);

Rune    *cmdstartp;
Rune    *cmdendp;
Rune    *cmdp;
Channel *editerrc;

String  *lastpat;
int     patset;

List    cmdlist;
List    addrlist;
List    stringlist;
Text    *curtext;
int     editing = Inactive;

String* newstring(int);

void
editthread(void*)
{
        Cmd *cmdp;

        threadsetname("editthread");
        while((cmdp=parsecmd(0)) != 0){
//              ocurfile = curfile;
//              loaded = curfile && !curfile->unread;
                if(cmdexec(curtext, cmdp) == 0)
                        break;
                freecmd();
        }
        sendp(editerrc, nil);
}

void
allelogterm(Window *w, void*)
{
        elogterm(w->body.file);
}

void
alleditinit(Window *w, void*)
{
        textcommit(&w->tag, TRUE);
        textcommit(&w->body, TRUE);
        w->body.file->editclean = FALSE;
}

void
allupdate(Window *w, void*)
{
        Text *t;
        int i;
        File *f;

        t = &w->body;
        f = t->file;
        if(f->curtext != t)     /* do curtext only */
                return;
        if(f->elog.type == Null)
                elogterm(f);
        else if(f->elog.type != Empty){
                elogapply(f);
                if(f->editclean){
                        f->mod = FALSE;
                        for(i=0; i<f->ntext; i++)
                                f->text[i]->w->dirty = FALSE;
                }
        }
        textsetselect(t, t->q0, t->q1);
        textscrdraw(t);
        winsettag(w);
}

void
editerror(char *fmt, ...)
{
        va_list arg;
        char *s;

        va_start(arg, fmt);
        s = vsmprint(fmt, arg);
        va_end(arg);
        freecmd();
        allwindows(allelogterm, nil);   /* truncate the edit logs */
        sendp(editerrc, s);
        threadexits(nil);
}

void
editcmd(Text *ct, Rune *r, uint n)
{
        char *err;

        if(n == 0)
                return;
        if(2*n > RBUFSIZE){
                warning(nil, "string too long\n");
                return;
        }

        allwindows(alleditinit, nil);
        if(cmdstartp)
                free(cmdstartp);
        cmdstartp = runemalloc(n+2);
        runemove(cmdstartp, r, n);
        if(r[n] != '\n')
                cmdstartp[n++] = '\n';
        cmdstartp[n] = '\0';
        cmdendp = cmdstartp+n;
        cmdp = cmdstartp;
        if(ct->w == nil)
                curtext = nil;
        else
                curtext = &ct->w->body;
        resetxec();
        if(editerrc == nil){
                editerrc = chancreate(sizeof(char*), 0);
                lastpat = allocstring(0);
        }
        threadcreate(editthread, nil, STACK);
        err = recvp(editerrc);
        editing = Inactive;
        if(err != nil){
                if(err[0] != '\0')
                        warning(nil, "Edit: %s\n", err);
                free(err);
        }

        /* update everyone whose edit log has data */
        allwindows(allupdate, nil);
}

int
getch(void)
{
        if(*cmdp == *cmdendp)
                return -1;
        return *cmdp++;
}

int
nextc(void)
{
        if(*cmdp == *cmdendp)
                return -1;
        return *cmdp;
}

void
ungetch(void)
{
        if(--cmdp < cmdstartp)
                error("ungetch");
}

long
getnum(int signok)
{
        long n;
        int c, sign;

        n = 0;
        sign = 1;
        if(signok>1 && nextc()=='-'){
                sign = -1;
                getch();
        }
        if((c=nextc())<'0' || '9'<c)    /* no number defaults to 1 */
                return sign;
        while('0'<=(c=getch()) && c<='9')
                n = n*10 + (c-'0');
        ungetch();
        return sign*n;
}

int
cmdskipbl(void)
{
        int c;
        do
                c = getch();
        while(c==' ' || c=='\t');
        if(c >= 0)
                ungetch();
        return c;
}

/*
 * Check that list has room for one more element.
 */
void
growlist(List *l)
{
        if(l->listptr==0 || l->nalloc==0){
                l->nalloc = INCR;
                l->listptr = emalloc(INCR*sizeof(void*));
                l->nused = 0;
        }else if(l->nused == l->nalloc){
                l->listptr = erealloc(l->listptr, (l->nalloc+INCR)*sizeof(void*));
                memset(l->ptr+l->nalloc, 0, INCR*sizeof(void*));
                l->nalloc += INCR;
        }
}

/*
 * Remove the ith element from the list
 */
void
dellist(List *l, int i)
{
        memmove(&l->ptr[i], &l->ptr[i+1], (l->nused-(i+1))*sizeof(void*));
        l->nused--;
}

/*
 * Add a new element, whose position is i, to the list
 */
void
inslist(List *l, int i, void *v)
{
        growlist(l);
        memmove(&l->ptr[i+1], &l->ptr[i], (l->nused-i)*sizeof(void*));
        l->ptr[i] = v;
        l->nused++;
}

void
listfree(List *l)
{
        free(l->listptr);
        free(l);
}

String*
allocstring(int n)
{
        String *s;

        s = emalloc(sizeof(String));
        s->n = n;
        s->nalloc = n+10;
        s->r = emalloc(s->nalloc*sizeof(Rune));
        s->r[n] = '\0';
        return s;
}

void
freestring(String *s)
{
        free(s->r);
        free(s);
}

Cmd*
newcmd(void){
        Cmd *p;

        p = emalloc(sizeof(Cmd));
        inslist(&cmdlist, cmdlist.nused, p);
        return p;
}

String*
newstring(int n)
{
        String *p;

        p = allocstring(n);
        inslist(&stringlist, stringlist.nused, p);
        return p;
}

Addr*
newaddr(void)
{
        Addr *p;

        p = emalloc(sizeof(Addr));
        inslist(&addrlist, addrlist.nused, p);
        return p;
}

void
freecmd(void)
{
        int i;

        while(cmdlist.nused > 0)
                free(cmdlist.ucharptr[--cmdlist.nused]);
        while(addrlist.nused > 0)
                free(addrlist.ucharptr[--addrlist.nused]);
        while(stringlist.nused>0){
                i = --stringlist.nused;
                freestring(stringlist.stringptr[i]);
        }
}

void
okdelim(int c)
{
        if(c=='\\' || ('a'<=c && c<='z')
        || ('A'<=c && c<='Z') || ('0'<=c && c<='9'))
                editerror("bad delimiter %c\n", c);
}

void
atnl(void)
{
        int c;

        cmdskipbl();
        c = getch();
        if(c != '\n')
                editerror("newline expected (saw %C)", c);
}

void
Straddc(String *s, int c)
{
        if(s->n+1 >= s->nalloc){
                s->nalloc += 10;
                s->r = erealloc(s->r, s->nalloc*sizeof(Rune));
        }
        s->r[s->n++] = c;
        s->r[s->n] = '\0';
}

void
getrhs(String *s, int delim, int cmd)
{
        int c;

        while((c = getch())>0 && c!=delim && c!='\n'){
                if(c == '\\'){
                        if((c=getch()) <= 0)
                                error("bad right hand side");
                        if(c == '\n'){
                                ungetch();
                                c='\\';
                        }else if(c == 'n')
                                c='\n';
                        else if(c!=delim && (cmd=='s' || c!='\\'))      /* s does its own */
                                Straddc(s, '\\');
                }
                Straddc(s, c);
        }
        ungetch();      /* let client read whether delimiter, '\n' or whatever */
}

String *
collecttoken(char *end)
{
        String *s = newstring(0);
        int c;

        while((c=nextc())==' ' || c=='\t')
                Straddc(s, getch()); /* blanks significant for getname() */
        while((c=getch())>0 && utfrune(end, c)==0)
                Straddc(s, c);
        if(c != '\n')
                atnl();
        return s;
}

String *
collecttext(void)
{
        String *s;
        int begline, i, c, delim;

        s = newstring(0);
        if(cmdskipbl()=='\n'){
                getch();
                i = 0;
                do{
                        begline = i;
                        while((c = getch())>0 && c!='\n')
                                i++, Straddc(s, c);
                        i++, Straddc(s, '\n');
                        if(c < 0)
                                goto Return;
                }while(s->r[begline]!='.' || s->r[begline+1]!='\n');
                s->r[s->n-2] = '\0';
                s->n -= 2;
        }else{
                okdelim(delim = getch());
                getrhs(s, delim, 'a');
                if(nextc()==delim)
                        getch();
                atnl();
        }
    Return:
        return s;
}

int
cmdlookup(int c)
{
        int i;

        for(i=0; cmdtab[i].cmdc; i++)
                if(cmdtab[i].cmdc == c)
                        return i;
        return -1;
}

Cmd*
parsecmd(int nest)
{
        int i, c;
        struct cmdtab *ct;
        Cmd *cp, *ncp;
        Cmd cmd;

        cmd.next = cmd.cmd = 0;
        cmd.re = 0;
        cmd.flag = cmd.num = 0;
        cmd.addr = compoundaddr();
        if(cmdskipbl() == -1)
                return 0;
        if((c=getch())==-1)
                return 0;
        cmd.cmdc = c;
        if(cmd.cmdc=='c' && nextc()=='d'){      /* sleazy two-character case */
                getch();                /* the 'd' */
                cmd.cmdc='c'|0x100;
        }
        i = cmdlookup(cmd.cmdc);
        if(i >= 0){
                if(cmd.cmdc == '\n')
                        goto Return;    /* let nl_cmd work it all out */
                ct = &cmdtab[i];
                if(ct->defaddr==aNo && cmd.addr)
                        editerror("command takes no address");
                if(ct->count)
                        cmd.num = getnum(ct->count);
                if(ct->regexp){
                        /* x without pattern -> .*\n, indicated by cmd.re==0 */
                        /* X without pattern is all files */
                        if((ct->cmdc!='x' && ct->cmdc!='X') ||
                           ((c = nextc())!=' ' && c!='\t' && c!='\n')){
                                cmdskipbl();
                                if((c = getch())=='\n' || c<0)
                                        editerror("no address");
                                okdelim(c);
                                cmd.re = getregexp(c);
                                if(ct->cmdc == 's'){
                                        cmd.text = newstring(0);
                                        getrhs(cmd.text, c, 's');
                                        if(nextc() == c){
                                                getch();
                                                if(nextc() == 'g')
                                                        cmd.flag = getch();
                                        }
                        
                                }
                        }
                }
                if(ct->addr && (cmd.mtaddr=simpleaddr())==0)
                        editerror("bad address");
                if(ct->defcmd){
                        if(cmdskipbl() == '\n'){
                                getch();
                                cmd.cmd = newcmd();
                                cmd.cmd->cmdc = ct->defcmd;
                        }else if((cmd.cmd = parsecmd(nest))==0)
                                error("defcmd");
                }else if(ct->text)
                        cmd.text = collecttext();
                else if(ct->token)
                        cmd.text = collecttoken(ct->token);
                else
                        atnl();
        }else
                switch(cmd.cmdc){
                case '{':
                        cp = 0;
                        do{
                                if(cmdskipbl()=='\n')
                                        getch();
                                ncp = parsecmd(nest+1);
                                if(cp)
                                        cp->next = ncp;
                                else
                                        cmd.cmd = ncp;
                        }while(cp = ncp);
                        break;
                case '}':
                        atnl();
                        if(nest==0)
                                editerror("right brace with no left brace");
                        return 0;
                default:
                        editerror("unknown command %c", cmd.cmdc);
                }
    Return:
        cp = newcmd();
        *cp = cmd;
        return cp;
}

String*
getregexp(int delim)
{
        String *buf, *r;
        int i, c;

        buf = allocstring(0);
        for(i=0; ; i++){
                if((c = getch())=='\\'){
                        if(nextc()==delim)
                                c = getch();
                        else if(nextc()=='\\'){
                                Straddc(buf, c);
                                c = getch();
                        }
                }else if(c==delim || c=='\n')
                        break;
                if(i >= RBUFSIZE)
                        editerror("regular expression too long");
                Straddc(buf, c);
        }
        if(c!=delim && c)
                ungetch();
        if(buf->n > 0){
                patset = TRUE;
                freestring(lastpat);
                lastpat = buf;
        }else
                freestring(buf);
        if(lastpat->n == 0)
                editerror("no regular expression defined");
        r = newstring(lastpat->n);
        runemove(r->r, lastpat->r, lastpat->n); /* newstring put \0 at end */
        return r;
}

Addr *
simpleaddr(void)
{
        Addr addr;
        Addr *ap, *nap;

        addr.next = 0;
        addr.left = 0;
        switch(cmdskipbl()){
        case '#':
                addr.type = getch();
                addr.num = getnum(1);
                break;
        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9': 
                addr.num = getnum(1);
                addr.type='l';
                break;
        case '/': case '?': case '"':
                addr.re = getregexp(addr.type = getch());
                break;
        case '.':
        case '$':
        case '+':
        case '-':
        case '\'':
                addr.type = getch();
                break;
        default:
                return 0;
        }
        if(addr.next = simpleaddr())
                switch(addr.next->type){
                case '.':
                case '$':
                case '\'':
                        if(addr.type!='"')
                case '"':
                                editerror("bad address syntax");
                        break;
                case 'l':
                case '#':
                        if(addr.type=='"')
                                break;
                        /* fall through */
                case '/':
                case '?':
                        if(addr.type!='+' && addr.type!='-'){
                                /* insert the missing '+' */
                                nap = newaddr();
                                nap->type='+';
                                nap->next = addr.next;
                                addr.next = nap;
                        }
                        break;
                case '+':
                case '-':
                        break;
                default:
                        error("simpleaddr");
                }
        ap = newaddr();
        *ap = addr;
        return ap;
}

Addr *
compoundaddr(void)
{
        Addr addr;
        Addr *ap, *next;

        addr.left = simpleaddr();
        if((addr.type = cmdskipbl())!=',' && addr.type!=';')
                return addr.left;
        getch();
        next = addr.next = compoundaddr();
        if(next && (next->type==',' || next->type==';') && next->left==0)
                editerror("bad address syntax");
        ap = newaddr();
        *ap = addr;
        return ap;
}