tMore files related to user-level file servers. Also add acme! - plan9port - [fork] Plan 9 from user space
 (HTM) git clone git://src.adamsgaard.dk/plan9port
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit b3994ec5c78e6c18885079b58abb7fb997899c3f
 (DIR) parent 32f69c36e0eec1227934bbd34854bfebd88686f2
 (HTM) Author: rsc <devnull@localhost>
       Date:   Thu, 11 Dec 2003 17:50:28 +0000
       
       More files related to user-level file servers.
       Also add acme!
       
       Diffstat:
         A src/cmd/acme/acme.c                 |     949 +++++++++++++++++++++++++++++++
         A src/cmd/acme/addr.c                 |     269 +++++++++++++++++++++++++++++++
         A src/cmd/acme/buff.c                 |     322 +++++++++++++++++++++++++++++++
         A src/cmd/acme/cols.c                 |     556 ++++++++++++++++++++++++++++++
         A src/cmd/acme/dat.h                  |     546 +++++++++++++++++++++++++++++++
         A src/cmd/acme/disk.c                 |     129 +++++++++++++++++++++++++++++++
         A src/cmd/acme/ecmd.c                 |    1325 +++++++++++++++++++++++++++++++
         A src/cmd/acme/edit.c                 |     682 +++++++++++++++++++++++++++++++
         A src/cmd/acme/edit.h                 |     101 +++++++++++++++++++++++++++++++
         A src/cmd/acme/elog.c                 |     350 +++++++++++++++++++++++++++++++
         A src/cmd/acme/exec.c                 |    1491 +++++++++++++++++++++++++++++++
         A src/cmd/acme/file.c                 |     310 +++++++++++++++++++++++++++++++
         A src/cmd/acme/fns.h                  |      92 +++++++++++++++++++++++++++++++
         A src/cmd/acme/fsys.c                 |     717 +++++++++++++++++++++++++++++++
         A src/cmd/acme/look.c                 |     772 +++++++++++++++++++++++++++++++
         A src/cmd/acme/mkfile                 |      41 +++++++++++++++++++++++++++++++
         A src/cmd/acme/regx.c                 |     835 +++++++++++++++++++++++++++++++
         A src/cmd/acme/rows.c                 |     731 +++++++++++++++++++++++++++++++
         A src/cmd/acme/scrl.c                 |     165 +++++++++++++++++++++++++++++++
         A src/cmd/acme/text.c                 |    1221 ++++++++++++++++++++++++++++++
         A src/cmd/acme/time.c                 |     121 +++++++++++++++++++++++++++++++
         A src/cmd/acme/util.c                 |     395 ++++++++++++++++++++++++++++++
         A src/cmd/acme/wind.c                 |     576 +++++++++++++++++++++++++++++++
         A src/cmd/acme/xfid.c                 |    1046 +++++++++++++++++++++++++++++++
         A src/lib9/_p9translate.c             |      46 +++++++++++++++++++++++++++++++
         A src/lib9/access.c                   |      19 +++++++++++++++++++
         A src/lib9/getns.c                    |      74 +++++++++++++++++++++++++++++++
         A src/lib9/malloc.c                   |      11 +++++++++++
         A src/lib9/open.c                     |      38 +++++++++++++++++++++++++++++++
         A src/lib9/pipe.c                     |      10 ++++++++++
         A src/lib9/post9p.c                   |      40 +++++++++++++++++++++++++++++++
         A src/lib9/sendfd.c                   |      79 +++++++++++++++++++++++++++++++
         A src/libbio/_lib9.h                  |      12 ++++++++++++
         A src/libfs/ns.c                      |      36 +++++++++++++++++++++++++++++++
         A src/libfs/openfd.c                  |      26 ++++++++++++++++++++++++++
       
       35 files changed, 14133 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/src/cmd/acme/acme.c b/src/cmd/acme/acme.c
       t@@ -0,0 +1,949 @@
       +#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 "fns.h"
       +        /* for generating syms in mkfile only: */
       +        #include <bio.h>
       +        #include "edit.h"
       +
       +void        mousethread(void*);
       +void        keyboardthread(void*);
       +void        waitthread(void*);
       +void        xfidallocthread(void*);
       +void        newwindowthread(void*);
       +void plumbproc(void*);
       +
       +Reffont        **fontcache;
       +int                nfontcache;
       +char                wdir[512] = ".";
       +Reffont        *reffonts[2];
       +int                snarffd = -1;
       +int                mainpid;
       +int                plumbsendfd;
       +int                plumbeditfd;
       +
       +enum{
       +        NSnarf = 1000        /* less than 1024, I/O buffer size */
       +};
       +Rune        snarfrune[NSnarf+1];
       +
       +char                *fontnames[2] =
       +{
       +        "/lib/font/bit/lucidasans/euro.8.font",
       +        "/lib/font/bit/lucm/unicode.9.font",
       +};
       +
       +Command *command;
       +
       +void        acmeerrorinit(void);
       +void        readfile(Column*, char*);
       +int        shutdown(void*, char*);
       +
       +void
       +derror(Display *d, char *errorstr)
       +{
       +        USED(d);
       +        error(errorstr);
       +}
       +
       +void
       +threadmain(int argc, char *argv[])
       +{
       +        int i;
       +        char *p, *loadfile;
       +        char buf[256];
       +        Column *c;
       +        int ncol;
       +        Display *d;
       +
       +        rfork(RFENVG|RFNAMEG);
       +
       +        ncol = -1;
       +
       +        loadfile = nil;
       +        ARGBEGIN{
       +        case 'b':
       +                bartflag = TRUE;
       +                break;
       +        case 'c':
       +                p = ARGF();
       +                if(p == nil)
       +                        goto Usage;
       +                ncol = atoi(p);
       +                if(ncol <= 0)
       +                        goto Usage;
       +                break;
       +        case 'f':
       +                fontnames[0] = ARGF();
       +                if(fontnames[0] == nil)
       +                        goto Usage;
       +                break;
       +        case 'F':
       +                fontnames[1] = ARGF();
       +                if(fontnames[1] == nil)
       +                        goto Usage;
       +                break;
       +        case 'l':
       +                loadfile = ARGF();
       +                if(loadfile == nil)
       +                        goto Usage;
       +                break;
       +        default:
       +        Usage:
       +                fprint(2, "usage: acme -c ncol -f fontname -F fixedwidthfontname -l loadfile\n");
       +                exits("usage");
       +        }ARGEND
       +
       +        cputype = getenv("cputype");
       +        objtype = getenv("objtype");
       +        home = getenv("home");
       +        p = getenv("tabstop");
       +        if(p != nil){
       +                maxtab = strtoul(p, nil, 0);
       +                free(p);
       +        }
       +        if(maxtab == 0)
       +                maxtab = 4; 
       +        if(loadfile)
       +                rowloadfonts(loadfile);
       +        putenv("font", fontnames[0]);
       +        snarffd = open("/dev/snarf", OREAD|OCEXEC);
       +/*
       +        if(cputype){
       +                sprint(buf, "/acme/bin/%s", cputype);
       +                bind(buf, "/bin", MBEFORE);
       +        }
       +        bind("/acme/bin", "/bin", MBEFORE);
       +*/
       +        getwd(wdir, sizeof wdir);
       +
       +/*
       +        if(geninitdraw(nil, derror, fontnames[0], "acme", nil, Refnone) < 0){
       +                fprint(2, "acme: can't open display: %r\n");
       +                exits("geninitdraw");
       +        }
       +*/
       +        if(initdraw(derror, fontnames[0], "acme") < 0){
       +                fprint(2, "acme: can't open display: %r\n");
       +                exits("initdraw");
       +        }
       +
       +        d = display;
       +        font = d->defaultfont;
       +
       +        reffont.f = font;
       +        reffonts[0] = &reffont;
       +        incref(&reffont.ref);        /* one to hold up 'font' variable */
       +        incref(&reffont.ref);        /* one to hold up reffonts[0] */
       +        fontcache = emalloc(sizeof(Reffont*));
       +        nfontcache = 1;
       +        fontcache[0] = &reffont;
       +
       +        iconinit();
       +        timerinit();
       +        rxinit();
       +
       +        cwait = threadwaitchan();
       +        ccommand = chancreate(sizeof(Command**), 0);
       +        ckill = chancreate(sizeof(Rune*), 0);
       +        cxfidalloc = chancreate(sizeof(Xfid*), 0);
       +        cxfidfree = chancreate(sizeof(Xfid*), 0);
       +        cnewwindow = chancreate(sizeof(Channel*), 0);
       +        cerr = chancreate(sizeof(char*), 0);
       +        cedit = chancreate(sizeof(int), 0);
       +        cexit = chancreate(sizeof(int), 0);
       +        if(cwait==nil || ccommand==nil || ckill==nil || cxfidalloc==nil || cxfidfree==nil || cerr==nil || cexit==nil){
       +                fprint(2, "acme: can't create initial channels: %r\n");
       +                exits("channels");
       +        }
       +
       +        mousectl = initmouse(nil, screen);
       +        if(mousectl == nil){
       +                fprint(2, "acme: can't initialize mouse: %r\n");
       +                exits("mouse");
       +        }
       +        mouse = &mousectl->m;
       +        keyboardctl = initkeyboard(nil);
       +        if(keyboardctl == nil){
       +                fprint(2, "acme: can't initialize keyboard: %r\n");
       +                exits("keyboard");
       +        }
       +        mainpid = getpid();
       +        plumbeditfd = plumbopen("edit", OREAD|OCEXEC);
       +        if(plumbeditfd < 0)
       +                fprint(2, "acme: can't initialize plumber: %r\n");
       +        else{
       +                cplumb = chancreate(sizeof(Plumbmsg*), 0);
       +                proccreate(plumbproc, nil, STACK);
       +        }
       +        plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
       +
       +        fsysinit();
       +
       +        #define        WPERCOL        8
       +        disk = diskinit();
       +        if(loadfile)
       +                rowload(&row, loadfile, TRUE);
       +        else{
       +                rowinit(&row, screen->clipr);
       +                if(ncol < 0){
       +                        if(argc == 0)
       +                                ncol = 2;
       +                        else{
       +                                ncol = (argc+(WPERCOL-1))/WPERCOL;
       +                                if(ncol < 2)
       +                                        ncol = 2;
       +                        }
       +                }
       +                if(ncol == 0)
       +                        ncol = 2;
       +                for(i=0; i<ncol; i++){
       +                        c = rowadd(&row, nil, -1);
       +                        if(c==nil && i==0)
       +                                error("initializing columns");
       +                }
       +                c = row.col[row.ncol-1];
       +                if(argc == 0)
       +                        readfile(c, wdir);
       +                else
       +                        for(i=0; i<argc; i++){
       +                                p = utfrrune(argv[i], '/');
       +                                if((p!=nil && strcmp(p, "/guide")==0) || i/WPERCOL>=row.ncol)
       +                                        readfile(c, argv[i]);
       +                                else
       +                                        readfile(row.col[i/WPERCOL], argv[i]);
       +                        }
       +        }
       +        flushimage(display, 1);
       +
       +        acmeerrorinit();
       +        threadcreate(keyboardthread, nil, STACK);
       +        threadcreate(mousethread, nil, STACK);
       +        threadcreate(waitthread, nil, STACK);
       +        threadcreate(xfidallocthread, nil, STACK);
       +        threadcreate(newwindowthread, nil, STACK);
       +
       +        threadnotify(shutdown, 1);
       +        recvul(cexit);
       +        killprocs();
       +        threadexitsall(nil);
       +}
       +
       +void
       +readfile(Column *c, char *s)
       +{
       +        Window *w;
       +        Rune rb[256];
       +        int nb, nr;
       +        Runestr rs;
       +
       +        w = coladd(c, nil, nil, -1);
       +        cvttorunes(s, strlen(s), rb, &nb, &nr, nil);
       +        rs = cleanrname((Runestr){rb, nr});
       +        winsetname(w, rs.r, rs.nr);
       +        textload(&w->body, 0, s, 1);
       +        w->body.file->mod = FALSE;
       +        w->dirty = FALSE;
       +        winsettag(w);
       +        textscrdraw(&w->body);
       +        textsetselect(&w->tag, w->tag.file->b.nc, w->tag.file->b.nc);
       +}
       +
       +char *oknotes[] ={
       +        "delete",
       +        "hangup",
       +        "kill",
       +        "exit",
       +        nil
       +};
       +
       +int        dumping;
       +
       +int
       +shutdown(void *v, char *msg)
       +{
       +        int i;
       +
       +        if(strcmp(msg, "sys: write on closed pipe") == 0)
       +                return 1;
       +
       +        USED(v);
       +        killprocs();
       +        if(!dumping && strcmp(msg, "kill")!=0 && strcmp(msg, "exit")!=0 && getpid()==mainpid){
       +                dumping = TRUE;
       +                rowdump(&row, nil);
       +        }
       +        for(i=0; oknotes[i]; i++)
       +                if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0)
       +                        threadexitsall(msg);
       +        print("acme: %s\n", msg);
       +        abort();
       +        return 0;
       +}
       +
       +void
       +killprocs(void)
       +{
       +        Command *c;
       +
       +        fsysclose();
       +//        if(display)
       +//                flushimage(display, 1);
       +
       +        for(c=command; c; c=c->next)
       +                postnote(PNGROUP, c->pid, "hangup");
       +}
       +
       +static int errorfd;
       +int erroutfd;
       +
       +void
       +acmeerrorproc(void *v)
       +{
       +        char *buf;
       +        int n;
       +
       +        USED(v);
       +        threadsetname("acmeerrorproc");
       +        buf = emalloc(8192+1);
       +        while((n=read(errorfd, buf, 8192)) >= 0){
       +                buf[n] = '\0';
       +                sendp(cerr, estrdup(buf));
       +        }
       +}
       +
       +void
       +acmeerrorinit(void)
       +{
       +        int fd, pfd[2];
       +        char buf[64];
       +
       +        if(pipe(pfd) < 0)
       +                error("can't create pipe");
       +#if 0
       +        sprint(acmeerrorfile, "/srv/acme.%s.%d", getuser(), mainpid);
       +        fd = create(acmeerrorfile, OWRITE, 0666);
       +        if(fd < 0){
       +                remove(acmeerrorfile);
       +                  fd = create(acmeerrorfile, OWRITE, 0666);
       +                if(fd < 0)
       +                        error("can't create acmeerror file");
       +        }
       +        sprint(buf, "%d", pfd[0]);
       +        write(fd, buf, strlen(buf));
       +        close(fd);
       +        /* reopen pfd[1] close on exec */
       +        sprint(buf, "/fd/%d", pfd[1]);
       +        errorfd = open(buf, OREAD|OCEXEC);
       +#endif
       +        fcntl(pfd[0], F_SETFD, FD_CLOEXEC);
       +        fcntl(pfd[1], F_SETFD, FD_CLOEXEC);
       +        erroutfd = pfd[0];
       +        errorfd = pfd[1];
       +        if(errorfd < 0)
       +                error("can't re-open acmeerror file");
       +        proccreate(acmeerrorproc, nil, STACK);
       +}
       +
       +void
       +plumbproc(void *v)
       +{
       +        Plumbmsg *m;
       +
       +        USED(v);
       +        threadsetname("plumbproc");
       +        for(;;){
       +                m = plumbrecv(plumbeditfd);
       +                if(m == nil)
       +                        threadexits(nil);
       +                sendp(cplumb, m);
       +        }
       +}
       +
       +void
       +keyboardthread(void *v)
       +{
       +        Rune r;
       +        Timer *timer;
       +        Text *t;
       +        enum { KTimer, KKey, NKALT };
       +        static Alt alts[NKALT+1];
       +
       +        USED(v);
       +        alts[KTimer].c = nil;
       +        alts[KTimer].v = nil;
       +        alts[KTimer].op = CHANNOP;
       +        alts[KKey].c = keyboardctl->c;
       +        alts[KKey].v = &r;
       +        alts[KKey].op = CHANRCV;
       +        alts[NKALT].op = CHANEND;
       +
       +        timer = nil;
       +        typetext = nil;
       +        threadsetname("keyboardthread");
       +        for(;;){
       +                switch(alt(alts)){
       +                case KTimer:
       +                        timerstop(timer);
       +                        t = typetext;
       +                        if(t!=nil && t->what==Tag){
       +                                winlock(t->w, 'K');
       +                                wincommit(t->w, t);
       +                                winunlock(t->w);
       +                                flushimage(display, 1);
       +                        }
       +                        alts[KTimer].c = nil;
       +                        alts[KTimer].op = CHANNOP;
       +                        break;
       +                case KKey:
       +                casekeyboard:
       +                        typetext = rowtype(&row, r, mouse->xy);
       +                        t = typetext;
       +                        if(t!=nil && t->col!=nil && !(r==Kdown || r==Kleft || r==Kright))        /* scrolling doesn't change activecol */
       +                                activecol = t->col;
       +                        if(t!=nil && t->w!=nil)
       +                                t->w->body.file->curtext = &t->w->body;
       +                        if(timer != nil)
       +                                timercancel(timer);
       +                        if(t!=nil && t->what==Tag) {
       +                                timer = timerstart(500);
       +                                alts[KTimer].c = timer->c;
       +                                alts[KTimer].op = CHANRCV;
       +                        }else{
       +                                timer = nil;
       +                                alts[KTimer].c = nil;
       +                                alts[KTimer].op = CHANNOP;
       +                        }
       +                        if(nbrecv(keyboardctl->c, &r) > 0)
       +                                goto casekeyboard;
       +                        flushimage(display, 1);
       +                        break;
       +                }
       +        }
       +}
       +
       +void
       +mousethread(void *v)
       +{
       +        Text *t, *argt;
       +        int but;
       +        uint q0, q1;
       +        Window *w;
       +        Plumbmsg *pm;
       +        Mouse m;
       +        char *act;
       +        enum { MResize, MMouse, MPlumb, NMALT };
       +        static Alt alts[NMALT+1];
       +
       +        USED(v);
       +        threadsetname("mousethread");
       +        alts[MResize].c = mousectl->resizec;
       +        alts[MResize].v = nil;
       +        alts[MResize].op = CHANRCV;
       +        alts[MMouse].c = mousectl->c;
       +        alts[MMouse].v = &mousectl->m;
       +        alts[MMouse].op = CHANRCV;
       +        alts[MPlumb].c = cplumb;
       +        alts[MPlumb].v = &pm;
       +        alts[MPlumb].op = CHANRCV;
       +        if(cplumb == nil)
       +                alts[MPlumb].op = CHANNOP;
       +        alts[NMALT].op = CHANEND;
       +        
       +        for(;;){
       +                switch(alt(alts)){
       +                case MResize:
       +                        if(getwindow(display, Refnone) < 0)
       +                                error("attach to window");
       +                        draw(screen, screen->r, display->white, nil, ZP);
       +                        scrlresize();
       +                        rowresize(&row, screen->clipr);
       +                        flushimage(display, 1);
       +                        break;
       +                case MPlumb:
       +                        if(strcmp(pm->type, "text") == 0){
       +                                act = plumblookup(pm->attr, "action");
       +                                if(act==nil || strcmp(act, "showfile")==0)
       +                                        plumblook(pm);
       +                                else if(strcmp(act, "showdata")==0)
       +                                        plumbshow(pm);
       +                        }
       +                        flushimage(display, 1);
       +                        plumbfree(pm);
       +                        break;
       +                case MMouse:
       +                        /*
       +                         * Make a copy so decisions are consistent; mousectl changes
       +                         * underfoot.  Can't just receive into m because this introduces
       +                         * another race; see /sys/src/libdraw/mouse.c.
       +                         */
       +                        m = mousectl->m;
       +                        qlock(&row.lk);
       +                        t = rowwhich(&row, m.xy);
       +                        if(t!=mousetext && mousetext!=nil && mousetext->w!=nil){
       +                                winlock(mousetext->w, 'M');
       +                                mousetext->eq0 = ~0;
       +                                wincommit(mousetext->w, mousetext);
       +                                winunlock(mousetext->w);
       +                        }
       +                        mousetext = t;
       +                        if(t == nil)
       +                                goto Continue;
       +                        w = t->w;
       +                        if(t==nil || m.buttons==0)
       +                                goto Continue;
       +                        but = 0;
       +                        if(m.buttons == 1)
       +                                but = 1;
       +                        else if(m.buttons == 2)
       +                                but = 2;
       +                        else if(m.buttons == 4)
       +                                but = 3;
       +                        barttext = t;
       +                        if(t->what==Body && ptinrect(m.xy, t->scrollr)){
       +                                if(but){
       +                                        winlock(w, 'M');
       +                                        t->eq0 = ~0;
       +                                        textscroll(t, but);
       +                                        winunlock(w);
       +                                }
       +                                goto Continue;
       +                        }
       +                        if(ptinrect(m.xy, t->scrollr)){
       +                                if(but){
       +                                        if(t->what == Columntag)
       +                                                rowdragcol(&row, t->col, but);
       +                                        else if(t->what == Tag){
       +                                                coldragwin(t->col, t->w, but);
       +                                                if(t->w)
       +                                                        barttext = &t->w->body;
       +                                        }
       +                                        if(t->col)
       +                                                activecol = t->col;
       +                                }
       +                                goto Continue;
       +                        }
       +                        if(m.buttons){
       +                                if(w)
       +                                        winlock(w, 'M');
       +                                t->eq0 = ~0;
       +                                if(w)
       +                                        wincommit(w, t);
       +                                else
       +                                        textcommit(t, TRUE);
       +                                if(m.buttons & 1){
       +                                        textselect(t);
       +                                        if(w)
       +                                                winsettag(w);
       +                                        argtext = t;
       +                                        seltext = t;
       +                                        if(t->col)
       +                                                activecol = t->col;        /* button 1 only */
       +                                        if(t->w!=nil && t==&t->w->body)
       +                                                activewin = t->w;
       +                                }else if(m.buttons & 2){
       +                                        if(textselect2(t, &q0, &q1, &argt))
       +                                                execute(t, q0, q1, FALSE, argt);
       +                                }else if(m.buttons & 4){
       +                                        if(textselect3(t, &q0, &q1))
       +                                                look3(t, q0, q1, FALSE);
       +                                }
       +                                if(w)
       +                                        winunlock(w);
       +                                goto Continue;
       +                        }
       +    Continue:
       +                        flushimage(display, 1);
       +                        qunlock(&row.lk);
       +                        break;
       +                }
       +        }
       +}
       +
       +/*
       + * There is a race between process exiting and our finding out it was ever created.
       + * This structure keeps a list of processes that have exited we haven't heard of.
       + */
       +typedef struct Pid Pid;
       +struct Pid
       +{
       +        int        pid;
       +        char        msg[ERRMAX];
       +        Pid        *next;
       +};
       +
       +void
       +waitthread(void *v)
       +{
       +        Waitmsg *w;
       +        Command *c, *lc;
       +        uint pid;
       +        int found, ncmd;
       +        Rune *cmd;
       +        char *err;
       +        Text *t;
       +        Pid *pids, *p, *lastp;
       +        enum { WErr, WKill, WWait, WCmd, NWALT };
       +        Alt alts[NWALT+1];
       +
       +        USED(v);
       +        threadsetname("waitthread");
       +        pids = nil;
       +        alts[WErr].c = cerr;
       +        alts[WErr].v = &err;
       +        alts[WErr].op = CHANRCV;
       +        alts[WKill].c = ckill;
       +        alts[WKill].v = &cmd;
       +        alts[WKill].op = CHANRCV;
       +        alts[WWait].c = cwait;
       +        alts[WWait].v = &w;
       +        alts[WWait].op = CHANRCV;
       +        alts[WCmd].c = ccommand;
       +        alts[WCmd].v = &c;
       +        alts[WCmd].op = CHANRCV;
       +        alts[NWALT].op = CHANEND;
       +
       +        command = nil;
       +        for(;;){
       +                switch(alt(alts)){
       +                case WErr:
       +                        qlock(&row.lk);
       +                        warning(nil, "%s", err);
       +                        free(err);
       +                        flushimage(display, 1);
       +                        qunlock(&row.lk);
       +                        break;
       +                case WKill:
       +                        found = FALSE;
       +                        ncmd = runestrlen(cmd);
       +                        for(c=command; c; c=c->next){
       +                                /* -1 for blank */
       +                                if(runeeq(c->name, c->nname-1, cmd, ncmd) == TRUE){
       +                                        if(postnote(PNGROUP, c->pid, "kill") < 0)
       +                                                warning(nil, "kill %S: %r\n", cmd);
       +                                        found = TRUE;
       +                                }
       +                        }
       +                        if(!found)
       +                                warning(nil, "Kill: no process %S\n", cmd);
       +                        free(cmd);
       +                        break;
       +                case WWait:
       +                        pid = w->pid;
       +                        lc = nil;
       +                        for(c=command; c; c=c->next){
       +                                if(c->pid == pid){
       +                                        if(lc)
       +                                                lc->next = c->next;
       +                                        else
       +                                                command = c->next;
       +                                        break;
       +                                }
       +                                lc = c;
       +                        }
       +                        qlock(&row.lk);
       +                        t = &row.tag;
       +                        textcommit(t, TRUE);
       +                        if(c == nil){
       +                                /* helper processes use this exit status */
       +                                if(strncmp(w->msg, "libthread", 9) != 0){
       +                                        p = emalloc(sizeof(Pid));
       +                                        p->pid = pid;
       +                                        strncpy(p->msg, w->msg, sizeof(p->msg));
       +                                        p->next = pids;
       +                                        pids = p;
       +                                }
       +                        }else{
       +                                if(search(t, c->name, c->nname)){
       +                                        textdelete(t, t->q0, t->q1, TRUE);
       +                                        textsetselect(t, 0, 0);
       +                                }
       +                                if(w->msg[0])
       +                                        warning(c->md, "%s: %s\n", c->name, w->msg);
       +                                flushimage(display, 1);
       +                        }
       +                        qunlock(&row.lk);
       +                        free(w);
       +    Freecmd:
       +                        if(c){
       +                                if(c->iseditcmd)
       +                                        sendul(cedit, 0);
       +                                free(c->text);
       +                                free(c->name);
       +                                fsysdelid(c->md);
       +                                free(c);
       +                        }
       +                        break;
       +                case WCmd:
       +                        /* has this command already exited? */
       +                        lastp = nil;
       +                        for(p=pids; p!=nil; p=p->next){
       +                                if(p->pid == c->pid){
       +                                        if(p->msg[0])
       +                                                warning(c->md, "%s\n", p->msg);
       +                                        if(lastp == nil)
       +                                                pids = p->next;
       +                                        else
       +                                                lastp->next = p->next;
       +                                        free(p);
       +                                        goto Freecmd;
       +                                }
       +                                lastp = p;
       +                        }
       +                        c->next = command;
       +                        command = c;
       +                        qlock(&row.lk);
       +                        t = &row.tag;
       +                        textcommit(t, TRUE);
       +                        textinsert(t, 0, c->name, c->nname, TRUE);
       +                        textsetselect(t, 0, 0);
       +                        flushimage(display, 1);
       +                        qunlock(&row.lk);
       +                        break;
       +                }
       +        }
       +}
       +
       +void
       +xfidallocthread(void *v)
       +{
       +        Xfid *xfree, *x;
       +        enum { Alloc, Free, N };
       +        static Alt alts[N+1];
       +
       +        USED(v);
       +        threadsetname("xfidallocthread");
       +        alts[Alloc].c = cxfidalloc;
       +        alts[Alloc].v = nil;
       +        alts[Alloc].op = CHANRCV;
       +        alts[Free].c = cxfidfree;
       +        alts[Free].v = &x;
       +        alts[Free].op = CHANRCV;
       +        alts[N].op = CHANEND;
       +
       +        xfree = nil;
       +        for(;;){
       +                switch(alt(alts)){
       +                case Alloc:
       +                        x = xfree;
       +                        if(x)
       +                                xfree = x->next;
       +                        else{
       +                                x = emalloc(sizeof(Xfid));
       +                                x->c = chancreate(sizeof(void(*)(Xfid*)), 0);
       +                                x->arg = x;
       +                                threadcreate(xfidctl, x->arg, STACK);
       +                        }
       +                        sendp(cxfidalloc, x);
       +                        break;
       +                case Free:
       +                        x->next = xfree;
       +                        xfree = x;
       +                        break;
       +                }
       +        }
       +}
       +
       +/* this thread, in the main proc, allows fsysproc to get a window made without doing graphics */
       +void
       +newwindowthread(void *v)
       +{
       +        Window *w;
       +
       +        USED(v);
       +        threadsetname("newwindowthread");
       +
       +        for(;;){
       +                /* only fsysproc is talking to us, so synchronization is trivial */
       +                recvp(cnewwindow);
       +                w = makenewwindow(nil);
       +                winsettag(w);
       +                sendp(cnewwindow, w);
       +        }
       +}
       +
       +Reffont*
       +rfget(int fix, int save, int setfont, char *name)
       +{
       +        Reffont *r;
       +        Font *f;
       +        int i;
       +
       +        r = nil;
       +        if(name == nil){
       +                name = fontnames[fix];
       +                r = reffonts[fix];
       +        }
       +        if(r == nil){
       +                for(i=0; i<nfontcache; i++)
       +                        if(strcmp(name, fontcache[i]->f->name) == 0){
       +                                r = fontcache[i];
       +                                goto Found;
       +                        }
       +                f = openfont(display, name);
       +                if(f == nil){
       +                        warning(nil, "can't open font file %s: %r\n", name);
       +                        return nil;
       +                }
       +                r = emalloc(sizeof(Reffont));
       +                r->f = f;
       +                fontcache = realloc(fontcache, (nfontcache+1)*sizeof(Reffont*));
       +                fontcache[nfontcache++] = r;
       +        }
       +    Found:
       +        if(save){
       +                incref(&r->ref);
       +                if(reffonts[fix])
       +                        rfclose(reffonts[fix]);
       +                reffonts[fix] = r;
       +                free(fontnames[fix]);
       +                fontnames[fix] = name;
       +        }
       +        if(setfont){
       +                reffont.f = r->f;
       +                incref(&r->ref);
       +                rfclose(reffonts[0]);
       +                font = r->f;
       +                reffonts[0] = r;
       +                incref(&r->ref);
       +                iconinit();
       +        }
       +        incref(&r->ref);
       +        return r;
       +}
       +
       +void
       +rfclose(Reffont *r)
       +{
       +        int i;
       +
       +        if(decref(&r->ref) == 0){
       +                for(i=0; i<nfontcache; i++)
       +                        if(r == fontcache[i])
       +                                break;
       +                if(i >= nfontcache)
       +                        warning(nil, "internal error: can't find font in cache\n");
       +                else{
       +                        nfontcache--;
       +                        memmove(fontcache+i, fontcache+i+1, (nfontcache-i)*sizeof(Reffont*));
       +                }
       +                freefont(r->f);
       +                free(r);
       +        }
       +}
       +
       +Cursor boxcursor = {
       +        {-7, -7},
       +        {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
       +         0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F,
       +         0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF,
       +         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
       +        {0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE,
       +         0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
       +         0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
       +         0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00}
       +};
       +
       +void
       +iconinit(void)
       +{
       +        Rectangle r;
       +        Image *tmp;
       +
       +        /* Blue */
       +        tagcols[BACK] = allocimagemix(display, DPalebluegreen, DWhite);
       +        tagcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalegreygreen);
       +        tagcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPurpleblue);
       +        tagcols[TEXT] = display->black;
       +        tagcols[HTEXT] = display->black;
       +
       +        /* Yellow */
       +        textcols[BACK] = allocimagemix(display, DPaleyellow, DWhite);
       +        textcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkyellow);
       +        textcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DYellowgreen);
       +        textcols[TEXT] = display->black;
       +        textcols[HTEXT] = display->black;
       +
       +        if(button){
       +                freeimage(button);
       +                freeimage(modbutton);
       +                freeimage(colbutton);
       +        }
       +
       +        r = Rect(0, 0, Scrollwid+2, font->height+1);
       +        button = allocimage(display, r, screen->chan, 0, DNofill);
       +        draw(button, r, tagcols[BACK], nil, r.min);
       +        r.max.x -= 2;
       +        border(button, r, 2, tagcols[BORD], ZP);
       +
       +        r = button->r;
       +        modbutton = allocimage(display, r, screen->chan, 0, DNofill);
       +        draw(modbutton, r, tagcols[BACK], nil, r.min);
       +        r.max.x -= 2;
       +        border(modbutton, r, 2, tagcols[BORD], ZP);
       +        r = insetrect(r, 2);
       +        tmp = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedblue);
       +        draw(modbutton, r, tmp, nil, ZP);
       +        freeimage(tmp);
       +
       +        r = button->r;
       +        colbutton = allocimage(display, r, screen->chan, 0, DPurpleblue);
       +
       +        but2col = allocimage(display, r, screen->chan, 1, 0xAA0000FF);
       +        but3col = allocimage(display, r, screen->chan, 1, 0x006600FF);
       +}
       +
       +/*
       + * /dev/snarf updates when the file is closed, so we must open our own
       + * fd here rather than use snarffd
       + */
       +
       +/* rio truncates larges snarf buffers, so this avoids using the
       + * service if the string is huge */
       +
       +#define MAXSNARF 100*1024
       +
       +void
       +acmeputsnarf(void)
       +{
       +        int fd, i, n;
       +
       +        if(snarffd<0 || snarfbuf.nc==0)
       +                return;
       +        if(snarfbuf.nc > MAXSNARF)
       +                return;
       +        fd = open("/dev/snarf", OWRITE);
       +        if(fd < 0)
       +                return;
       +        for(i=0; i<snarfbuf.nc; i+=n){
       +                n = snarfbuf.nc-i;
       +                if(n >= NSnarf)
       +                        n = NSnarf;
       +                bufread(&snarfbuf, i, snarfrune, n);
       +                if(fprint(fd, "%.*S", n, snarfrune) < 0)
       +                        break;
       +        }
       +        close(fd);
       +}
       +
       +void
       +acmegetsnarf()
       +{
       +        int nulls;
       +
       +        if(snarfbuf.nc > MAXSNARF)
       +                return;
       +        if(snarffd < 0)
       +                return;
       +        seek(snarffd, 0, 0);
       +        bufreset(&snarfbuf);
       +        bufload(&snarfbuf, 0, snarffd, &nulls);
       +}
 (DIR) diff --git a/src/cmd/acme/addr.c b/src/cmd/acme/addr.c
       t@@ -0,0 +1,269 @@
       +#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 "fns.h"
       +
       +enum
       +{
       +        None = 0,
       +        Fore = '+',
       +        Back = '-',
       +};
       +
       +enum
       +{
       +        Char,
       +        Line,
       +};
       +
       +int
       +isaddrc(int r)
       +{
       +        if(r && utfrune("0123456789+-/$.#,;", r)!=nil)
       +                return TRUE;
       +        return FALSE;
       +}
       +
       +/*
       + * quite hard: could be almost anything but white space, but we are a little conservative,
       + * aiming for regular expressions of alphanumerics and no white space
       + */
       +int
       +isregexc(int r)
       +{
       +        if(r == 0)
       +                return FALSE;
       +        if(isalnum(r))
       +                return TRUE;
       +        if(utfrune("^+-.*?#,;[]()$", r)!=nil)
       +                return TRUE;
       +        return FALSE;
       +}
       +
       +Range
       +number(Mntdir *md, Text *t, Range r, int line, int dir, int size, int *evalp)
       +{
       +        uint q0, q1;
       +
       +        if(size == Char){
       +                if(dir == Fore)
       +                        line = r.q1+line;
       +                else if(dir == Back){
       +                        if(r.q0==0 && line>0)
       +                                r.q0 = t->file->b.nc;
       +                        line = r.q0 - line;
       +                }
       +                if(line<0 || line>t->file->b.nc)
       +                        goto Rescue;
       +                *evalp = TRUE;
       +                return (Range){line, line};
       +        }
       +        q0 = r.q0;
       +        q1 = r.q1;
       +        switch(dir){
       +        case None:
       +                q0 = 0;
       +                q1 = 0;
       +        Forward:
       +                while(line>0 && q1<t->file->b.nc)
       +                        if(textreadc(t, q1++) == '\n' || q1==t->file->b.nc)
       +                                if(--line > 0)
       +                                        q0 = q1;
       +                if(line > 0)
       +                        goto Rescue;
       +                break;
       +        case Fore:
       +                if(q1 > 0)
       +                        while(textreadc(t, q1-1) != '\n')
       +                                q1++;
       +                q0 = q1;
       +                goto Forward;
       +        case Back:
       +                if(q0 < t->file->b.nc)
       +                        while(q0>0 && textreadc(t, q0-1)!='\n')
       +                                q0--;
       +                q1 = q0;
       +                while(line>0 && q0>0){
       +                        if(textreadc(t, q0-1) == '\n'){
       +                                if(--line >= 0)
       +                                        q1 = q0;
       +                        }
       +                        --q0;
       +                }
       +                if(line > 0)
       +                        goto Rescue;
       +                while(q0>0 && textreadc(t, q0-1)!='\n')
       +                        --q0;
       +        }
       +        *evalp = TRUE;
       +        return (Range){q0, q1};
       +
       +    Rescue:
       +        if(md != nil)
       +                warning(nil, "address out of range\n");
       +        *evalp = FALSE;
       +        return r;
       +}
       +
       +
       +Range
       +regexp(Mntdir *md, Text *t, Range lim, Range r, Rune *pat, int dir, int *foundp)
       +{
       +        int found;
       +        Rangeset sel;
       +        int q;
       +
       +        if(pat[0] == '\0' && rxnull()){
       +                warning(md, "no previous regular expression\n");
       +                *foundp = FALSE;
       +                return r;
       +        }
       +        if(pat[0] && rxcompile(pat) == FALSE){
       +                *foundp = FALSE;
       +                return r;
       +        }
       +        if(dir == Back)
       +                found = rxbexecute(t, r.q0, &sel);
       +        else{
       +                if(lim.q0 < 0)
       +                        q = Infinity;
       +                else
       +                        q = lim.q1;
       +                found = rxexecute(t, nil, r.q1, q, &sel);
       +        }
       +        if(!found && md==nil)
       +                warning(nil, "no match for regexp\n");
       +        *foundp = found;
       +        return sel.r[0];
       +}
       +
       +Range
       +address(Mntdir *md, Text *t, Range lim, Range ar, void *a, uint q0, uint q1, int (*getc)(void*, uint),  int *evalp, uint *qp)
       +{
       +        int dir, size, npat;
       +        int prevc, c, nc, n;
       +        uint q;
       +        Rune *pat;
       +        Range r, nr;
       +
       +        r = ar;
       +        q = q0;
       +        dir = None;
       +        size = Line;
       +        c = 0;
       +        while(q < q1){
       +                prevc = c;
       +                c = (*getc)(a, q++);
       +                switch(c){
       +                default:
       +                        *qp = q-1;
       +                        return r;
       +                case ';':
       +                        ar = r;
       +                        /* fall through */
       +                case ',':
       +                        if(prevc == 0)        /* lhs defaults to 0 */
       +                                r.q0 = 0;
       +                        if(q>=q1 && t!=nil && t->file!=nil)        /* rhs defaults to $ */
       +                                r.q1 = t->file->b.nc;
       +                        else{
       +                                nr = address(md, t, lim, ar, a, q, q1, getc, evalp, &q);
       +                                r.q1 = nr.q1;
       +                        }
       +                        *qp = q;
       +                        return r;
       +                case '+':
       +                case '-':
       +                        if(*evalp && (prevc=='+' || prevc=='-'))
       +                                if((nc=(*getc)(a, q))!='#' && nc!='/' && nc!='?')
       +                                        r = number(md, t, r, 1, prevc, Line, evalp);        /* do previous one */
       +                        dir = c;
       +                        break;
       +                case '.':
       +                case '$':
       +                        if(q != q0+1){
       +                                *qp = q-1;
       +                                return r;
       +                        }
       +                        if(*evalp)
       +                                if(c == '.')
       +                                        r = ar;
       +                                else
       +                                        r = (Range){t->file->b.nc, t->file->b.nc};
       +                        if(q < q1)
       +                                dir = Fore;
       +                        else
       +                                dir = None;
       +                        break;
       +                case '#':
       +                        if(q==q1 || (c=(*getc)(a, q++))<'0' || '9'<c){
       +                                *qp = q-1;
       +                                return r;
       +                        }
       +                        size = Char;
       +                        /* fall through */
       +                case '0': case '1': case '2': case '3': case '4':
       +                case '5': case '6': case '7': case '8': case '9':
       +                        n = c -'0';
       +                        while(q<q1){
       +                                c = (*getc)(a, q++);
       +                                if(c<'0' || '9'<c){
       +                                        q--;
       +                                        break;
       +                                }
       +                                n = n*10+(c-'0');
       +                        }
       +                        if(*evalp)
       +                                r = number(md, t, r, n, dir, size, evalp);
       +                        dir = None;
       +                        size = Line;
       +                        break;
       +                case '?':
       +                        dir = Back;
       +                        /* fall through */
       +                case '/':
       +                        npat = 0;
       +                        pat = nil;
       +                        while(q<q1){
       +                                c = (*getc)(a, q++);
       +                                switch(c){
       +                                case '\n':
       +                                        --q;
       +                                        goto out;
       +                                case '\\':
       +                                        pat = runerealloc(pat, npat+1);
       +                                        pat[npat++] = c;
       +                                        if(q == q1)
       +                                                goto out;
       +                                        c = (*getc)(a, q++);
       +                                        break;
       +                                case '/':
       +                                        goto out;
       +                                }
       +                                pat = runerealloc(pat, npat+1);
       +                                pat[npat++] = c;
       +                        }
       +                    out:
       +                        pat = runerealloc(pat, npat+1);
       +                        pat[npat] = 0;
       +                        if(*evalp)
       +                                r = regexp(md, t, lim, r, pat, dir, evalp);
       +                        free(pat);
       +                        dir = None;
       +                        size = Line;
       +                        break;
       +                }
       +        }
       +        if(*evalp && dir != None)
       +                r = number(md, t, r, 1, dir, Line, evalp);        /* do previous one */
       +        *qp = q;
       +        return r;
       +}
 (DIR) diff --git a/src/cmd/acme/buff.c b/src/cmd/acme/buff.c
       t@@ -0,0 +1,322 @@
       +#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 "fns.h"
       +
       +enum
       +{
       +        Slop = 100,        /* room to grow with reallocation */
       +};
       +
       +static
       +void
       +sizecache(Buffer *b, uint n)
       +{
       +        if(n <= b->cmax)
       +                return;
       +        b->cmax = n+Slop;
       +        b->c = runerealloc(b->c, b->cmax);
       +}
       +
       +static
       +void
       +addblock(Buffer *b, uint i, uint n)
       +{
       +        if(i > b->nbl)
       +                error("internal error: addblock");
       +
       +        b->bl = realloc(b->bl, (b->nbl+1)*sizeof b->bl[0]);
       +        if(i < b->nbl)
       +                memmove(b->bl+i+1, b->bl+i, (b->nbl-i)*sizeof(Block*));
       +        b->bl[i] = disknewblock(disk, n);
       +        b->nbl++;
       +}
       +
       +static
       +void
       +delblock(Buffer *b, uint i)
       +{
       +        if(i >= b->nbl)
       +                error("internal error: delblock");
       +
       +        diskrelease(disk, b->bl[i]);
       +        b->nbl--;
       +        if(i < b->nbl)
       +                memmove(b->bl+i, b->bl+i+1, (b->nbl-i)*sizeof(Block*));
       +        b->bl = realloc(b->bl, b->nbl*sizeof b->bl[0]);
       +}
       +
       +/*
       + * Move cache so b->cq <= q0 < b->cq+b->cnc.
       + * If at very end, q0 will fall on end of cache block.
       + */
       +
       +static
       +void
       +flush(Buffer *b)
       +{
       +        if(b->cdirty || b->cnc==0){
       +                if(b->cnc == 0)
       +                        delblock(b, b->cbi);
       +                else
       +                        diskwrite(disk, &b->bl[b->cbi], b->c, b->cnc);
       +                b->cdirty = FALSE;
       +        }
       +}
       +
       +static
       +void
       +setcache(Buffer *b, uint q0)
       +{
       +        Block **blp, *bl;
       +        uint i, q;
       +
       +        if(q0 > b->nc)
       +                error("internal error: setcache");
       +        /*
       +         * flush and reload if q0 is not in cache.
       +         */
       +        if(b->nc == 0 || (b->cq<=q0 && q0<b->cq+b->cnc))
       +                return;
       +        /*
       +         * if q0 is at end of file and end of cache, continue to grow this block
       +         */
       +        if(q0==b->nc && q0==b->cq+b->cnc && b->cnc<Maxblock)
       +                return;
       +        flush(b);
       +        /* find block */
       +        if(q0 < b->cq){
       +                q = 0;
       +                i = 0;
       +        }else{
       +                q = b->cq;
       +                i = b->cbi;
       +        }
       +        blp = &b->bl[i];
       +        while(q+(*blp)->u.n <= q0 && q+(*blp)->u.n < b->nc){
       +                q += (*blp)->u.n;
       +                i++;
       +                blp++;
       +                if(i >= b->nbl)
       +                        error("block not found");
       +        }
       +        bl = *blp;
       +        /* remember position */
       +        b->cbi = i;
       +        b->cq = q;
       +        sizecache(b, bl->u.n);
       +        b->cnc = bl->u.n;
       +        /*read block*/
       +        diskread(disk, bl, b->c, b->cnc);
       +}
       +
       +void
       +bufinsert(Buffer *b, uint q0, Rune *s, uint n)
       +{
       +        uint i, m, t, off;
       +
       +        if(q0 > b->nc)
       +                error("internal error: bufinsert");
       +
       +        while(n > 0){
       +                setcache(b, q0);
       +                off = q0-b->cq;
       +                if(b->cnc+n <= Maxblock){
       +                        /* Everything fits in one block. */
       +                        t = b->cnc+n;
       +                        m = n;
       +                        if(b->bl == nil){        /* allocate */
       +                                if(b->cnc != 0)
       +                                        error("internal error: bufinsert1 cnc!=0");
       +                                addblock(b, 0, t);
       +                                b->cbi = 0;
       +                        }
       +                        sizecache(b, t);
       +                        runemove(b->c+off+m, b->c+off, b->cnc-off);
       +                        runemove(b->c+off, s, m);
       +                        b->cnc = t;
       +                        goto Tail;
       +                }
       +                /*
       +                 * We must make a new block.  If q0 is at
       +                 * the very beginning or end of this block,
       +                 * just make a new block and fill it.
       +                 */
       +                if(q0==b->cq || q0==b->cq+b->cnc){
       +                        if(b->cdirty)
       +                                flush(b);
       +                        m = min(n, Maxblock);
       +                        if(b->bl == nil){        /* allocate */
       +                                if(b->cnc != 0)
       +                                        error("internal error: bufinsert2 cnc!=0");
       +                                i = 0;
       +                        }else{
       +                                i = b->cbi;
       +                                if(q0 > b->cq)
       +                                        i++;
       +                        }
       +                        addblock(b, i, m);
       +                        sizecache(b, m);
       +                        runemove(b->c, s, m);
       +                        b->cq = q0;
       +                        b->cbi = i;
       +                        b->cnc = m;
       +                        goto Tail;
       +                }
       +                /*
       +                 * Split the block; cut off the right side and
       +                 * let go of it.
       +                 */
       +                m = b->cnc-off;
       +                if(m > 0){
       +                        i = b->cbi+1;
       +                        addblock(b, i, m);
       +                        diskwrite(disk, &b->bl[i], b->c+off, m);
       +                        b->cnc -= m;
       +                }
       +                /*
       +                 * Now at end of block.  Take as much input
       +                 * as possible and tack it on end of block.
       +                 */
       +                m = min(n, Maxblock-b->cnc);
       +                sizecache(b, b->cnc+m);
       +                runemove(b->c+b->cnc, s, m);
       +                b->cnc += m;
       +  Tail:
       +                b->nc += m;
       +                q0 += m;
       +                s += m;
       +                n -= m;
       +                b->cdirty = TRUE;
       +        }
       +}
       +
       +void
       +bufdelete(Buffer *b, uint q0, uint q1)
       +{
       +        uint m, n, off;
       +
       +        if(!(q0<=q1 && q0<=b->nc && q1<=b->nc))
       +                error("internal error: bufdelete");
       +        while(q1 > q0){
       +                setcache(b, q0);
       +                off = q0-b->cq;
       +                if(q1 > b->cq+b->cnc)
       +                        n = b->cnc - off;
       +                else
       +                        n = q1-q0;
       +                m = b->cnc - (off+n);
       +                if(m > 0)
       +                        runemove(b->c+off, b->c+off+n, m);
       +                b->cnc -= n;
       +                b->cdirty = TRUE;
       +                q1 -= n;
       +                b->nc -= n;
       +        }
       +}
       +
       +static int
       +bufloader(void *v, uint q0, Rune *r, int nr)
       +{
       +        bufinsert(v, q0, r, nr);
       +        return nr;
       +}
       +
       +uint
       +loadfile(int fd, uint q0, int *nulls, int(*f)(void*, uint, Rune*, int), void *arg)
       +{
       +        char *p;
       +        Rune *r;
       +        int l, m, n, nb, nr;
       +        uint q1;
       +
       +        p = emalloc((Maxblock+UTFmax+1)*sizeof p[0]);
       +        r = runemalloc(Maxblock);
       +        m = 0;
       +        n = 1;
       +        q1 = q0;
       +        /*
       +         * At top of loop, may have m bytes left over from
       +         * last pass, possibly representing a partial rune.
       +         */
       +        while(n > 0){
       +                n = read(fd, p+m, Maxblock);
       +                if(n < 0){
       +                        warning(nil, "read error in Buffer.load");
       +                        break;
       +                }
       +                m += n;
       +                p[m] = 0;
       +                l = m;
       +                if(n > 0)
       +                        l -= UTFmax;
       +                cvttorunes(p, l, r, &nb, &nr, nulls);
       +                memmove(p, p+nb, m-nb);
       +                m -= nb;
       +                q1 += (*f)(arg, q1, r, nr);
       +        }
       +        free(p);
       +        free(r);
       +        return q1-q0;
       +}
       +
       +uint
       +bufload(Buffer *b, uint q0, int fd, int *nulls)
       +{
       +        if(q0 > b->nc)
       +                error("internal error: bufload");
       +        return loadfile(fd, q0, nulls, bufloader, b);
       +}
       +
       +void
       +bufread(Buffer *b, uint q0, Rune *s, uint n)
       +{
       +        uint m;
       +
       +        if(!(q0<=b->nc && q0+n<=b->nc))
       +                error("bufread: internal error");
       +
       +        while(n > 0){
       +                setcache(b, q0);
       +                m = min(n, b->cnc-(q0-b->cq));
       +                runemove(s, b->c+(q0-b->cq), m);
       +                q0 += m;
       +                s += m;
       +                n -= m;
       +        }
       +}
       +
       +void
       +bufreset(Buffer *b)
       +{
       +        int i;
       +
       +        b->nc = 0;
       +        b->cnc = 0;
       +        b->cq = 0;
       +        b->cdirty = 0;
       +        b->cbi = 0;
       +        /* delete backwards to avoid n² behavior */
       +        for(i=b->nbl-1; --i>=0; )
       +                delblock(b, i);
       +}
       +
       +void
       +bufclose(Buffer *b)
       +{
       +        bufreset(b);
       +        free(b->c);
       +        b->c = nil;
       +        b->cnc = 0;
       +        free(b->bl);
       +        b->bl = nil;
       +        b->nbl = 0;
       +}
 (DIR) diff --git a/src/cmd/acme/cols.c b/src/cmd/acme/cols.c
       t@@ -0,0 +1,556 @@
       +#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 "fns.h"
       +
       +static Rune Lheader[] = {
       +        'N', 'e', 'w', ' ',
       +        'C', 'u', 't', ' ',
       +        'P', 'a', 's', 't', 'e', ' ',
       +        'S', 'n', 'a', 'r', 'f', ' ',
       +        'S', 'o', 'r', 't', ' ',
       +        'Z', 'e', 'r', 'o', 'x', ' ',
       +        'D', 'e', 'l', 'c', 'o', 'l', ' ',
       +        0
       +};
       +
       +void
       +colinit(Column *c, Rectangle r)
       +{
       +        Rectangle r1;
       +        Text *t;
       +
       +        draw(screen, r, display->white, nil, ZP);
       +        c->r = r;
       +        c->w = nil;
       +        c->nw = 0;
       +        t = &c->tag;
       +        t->w = nil;
       +        t->col = c;
       +        r1 = r;
       +        r1.max.y = r1.min.y + font->height;
       +        textinit(t, fileaddtext(nil, t), r1, &reffont, tagcols);
       +        t->what = Columntag;
       +        r1.min.y = r1.max.y;
       +        r1.max.y += Border;
       +        draw(screen, r1, display->black, nil, ZP);
       +        textinsert(t, 0, Lheader, 38, TRUE);
       +        textsetselect(t, t->file->b.nc, t->file->b.nc);
       +        draw(screen, t->scrollr, colbutton, nil, colbutton->r.min);
       +        c->safe = TRUE;
       +}
       +
       +Window*
       +coladd(Column *c, Window *w, Window *clone, int y)
       +{
       +        Rectangle r, r1;
       +        Window *v;
       +        int i, t;
       +
       +        v = nil;
       +        r = c->r;
       +        r.min.y = c->tag.fr.r.max.y+Border;
       +        if(y<r.min.y && c->nw>0){        /* steal half of last window by default */
       +                v = c->w[c->nw-1];
       +                y = v->body.fr.r.min.y+Dy(v->body.fr.r)/2;
       +        }
       +        /* look for window we'll land on */
       +        for(i=0; i<c->nw; i++){
       +                v = c->w[i];
       +                if(y < v->r.max.y)
       +                        break;
       +        }
       +        if(c->nw > 0){
       +                if(i < c->nw)
       +                        i++;        /* new window will go after v */
       +                /*
       +                 * if v's too small, grow it first.
       +                 */
       +                if(!c->safe || v->body.fr.maxlines<=3){
       +                        colgrow(c, v, 1);
       +                        y = v->body.fr.r.min.y+Dy(v->body.fr.r)/2;
       +                }
       +                r = v->r;
       +                if(i == c->nw)
       +                        t = c->r.max.y;
       +                else
       +                        t = c->w[i]->r.min.y-Border;
       +                r.max.y = t;
       +                draw(screen, r, textcols[BACK], nil, ZP);
       +                r1 = r;
       +                y = min(y, t-(v->tag.fr.font->height+v->body.fr.font->height+Border+1));
       +                r1.max.y = min(y, v->body.fr.r.min.y+v->body.fr.nlines*v->body.fr.font->height);
       +                r1.min.y = winresize(v, r1, FALSE);
       +                r1.max.y = r1.min.y+Border;
       +                draw(screen, r1, display->black, nil, ZP);
       +                r.min.y = r1.max.y;
       +        }
       +        if(w == nil){
       +                w = emalloc(sizeof(Window));
       +                w->col = c;
       +                draw(screen, r, textcols[BACK], nil, ZP);
       +                wininit(w, clone, r);
       +        }else{
       +                w->col = c;
       +                winresize(w, r, FALSE);
       +        }
       +        w->tag.col = c;
       +        w->tag.row = c->row;
       +        w->body.col = c;
       +        w->body.row = c->row;
       +        c->w = realloc(c->w, (c->nw+1)*sizeof(Window*));
       +        memmove(c->w+i+1, c->w+i, (c->nw-i)*sizeof(Window*));
       +        c->nw++;
       +        c->w[i] = w;
       +        savemouse(w);
       +        /* near but not on the button */
       +        moveto(mousectl, addpt(w->tag.scrollr.max, Pt(3, 3)));
       +        barttext = &w->body;
       +        c->safe = TRUE;
       +        return w;
       +}
       +
       +void
       +colclose(Column *c, Window *w, int dofree)
       +{
       +        Rectangle r;
       +        int i;
       +
       +        /* w is locked */
       +        if(!c->safe)
       +                colgrow(c, w, 1);
       +        for(i=0; i<c->nw; i++)
       +                if(c->w[i] == w)
       +                        goto Found;
       +        error("can't find window");
       +  Found:
       +        r = w->r;
       +        w->tag.col = nil;
       +        w->body.col = nil;
       +        w->col = nil;
       +        restoremouse(w);
       +        if(dofree){
       +                windelete(w);
       +                winclose(w);
       +        }
       +        memmove(c->w+i, c->w+i+1, (c->nw-i)*sizeof(Window*));
       +        c->nw--;
       +        c->w = realloc(c->w, c->nw*sizeof(Window*));
       +        if(c->nw == 0){
       +                draw(screen, r, display->white, nil, ZP);
       +                return;
       +        }
       +        if(i == c->nw){                /* extend last window down */
       +                w = c->w[i-1];
       +                r.min.y = w->r.min.y;
       +                r.max.y = c->r.max.y;
       +        }else{                        /* extend next window up */
       +                w = c->w[i];
       +                r.max.y = w->r.max.y;
       +        }
       +        draw(screen, r, textcols[BACK], nil, ZP);
       +        if(c->safe)
       +                winresize(w, r, FALSE);
       +}
       +
       +void
       +colcloseall(Column *c)
       +{
       +        int i;
       +        Window *w;
       +
       +        if(c == activecol)
       +                activecol = nil;
       +        textclose(&c->tag);
       +        for(i=0; i<c->nw; i++){
       +                w = c->w[i];
       +                winclose(w);
       +        }
       +        c->nw = 0;
       +        free(c->w);
       +        free(c);
       +        clearmouse();
       +}
       +
       +void
       +colmousebut(Column *c)
       +{
       +        moveto(mousectl, divpt(addpt(c->tag.scrollr.min, c->tag.scrollr.max), 2));
       +}
       +
       +void
       +colresize(Column *c, Rectangle r)
       +{
       +        int i;
       +        Rectangle r1, r2;
       +        Window *w;
       +
       +        clearmouse();
       +        r1 = r;
       +        r1.max.y = r1.min.y + c->tag.fr.font->height;
       +        textresize(&c->tag, r1);
       +        draw(screen, c->tag.scrollr, colbutton, nil, colbutton->r.min);
       +        r1.min.y = r1.max.y;
       +        r1.max.y += Border;
       +        draw(screen, r1, display->black, nil, ZP);
       +        r1.max.y = r.max.y;
       +        for(i=0; i<c->nw; i++){
       +                w = c->w[i];
       +                w->maxlines = 0;
       +                if(i == c->nw-1)
       +                        r1.max.y = r.max.y;
       +                else
       +                        r1.max.y = r1.min.y+(Dy(w->r)+Border)*Dy(r)/Dy(c->r);
       +                r2 = r1;
       +                r2.max.y = r2.min.y+Border;
       +                draw(screen, r2, display->black, nil, ZP);
       +                r1.min.y = r2.max.y;
       +                r1.min.y = winresize(w, r1, FALSE);
       +        }
       +        c->r = r;
       +}
       +
       +static
       +int
       +colcmp(const void *a, const void *b)
       +{
       +        Rune *r1, *r2;
       +        int i, nr1, nr2;
       +
       +        r1 = (*(Window**)a)->body.file->name;
       +        nr1 = (*(Window**)a)->body.file->nname;
       +        r2 = (*(Window**)b)->body.file->name;
       +        nr2 = (*(Window**)b)->body.file->nname;
       +        for(i=0; i<nr1 && i<nr2; i++){
       +                if(*r1 != *r2)
       +                        return *r1-*r2;
       +                r1++;
       +                r2++;
       +        }
       +        return nr1-nr2;
       +}
       +
       +void
       +colsort(Column *c)
       +{
       +        int i, y;
       +        Rectangle r, r1, *rp;
       +        Window **wp, *w;
       +
       +        if(c->nw == 0)
       +                return;
       +        clearmouse();
       +        rp = emalloc(c->nw*sizeof(Rectangle));
       +        wp = emalloc(c->nw*sizeof(Window*));
       +        memmove(wp, c->w, c->nw*sizeof(Window*));
       +        qsort(wp, c->nw, sizeof(Window*), colcmp);
       +        for(i=0; i<c->nw; i++)
       +                rp[i] = wp[i]->r;
       +        r = c->r;
       +        r.min.y = c->tag.fr.r.max.y;
       +        draw(screen, r, textcols[BACK], nil, ZP);
       +        y = r.min.y;
       +        for(i=0; i<c->nw; i++){
       +                w = wp[i];
       +                r.min.y = y;
       +                if(i == c->nw-1)
       +                        r.max.y = c->r.max.y;
       +                else
       +                        r.max.y = r.min.y+Dy(w->r)+Border;
       +                r1 = r;
       +                r1.max.y = r1.min.y+Border;
       +                draw(screen, r1, display->black, nil, ZP);
       +                r.min.y = r1.max.y;
       +                y = winresize(w, r, FALSE);
       +        }
       +        free(rp);
       +        free(c->w);
       +        c->w = wp;
       +}
       +
       +void
       +colgrow(Column *c, Window *w, int but)
       +{
       +        Rectangle r, cr;
       +        int i, j, k, l, y1, y2, *nl, *ny, tot, nnl, onl, dnl, h;
       +        Window *v;
       +
       +        for(i=0; i<c->nw; i++)
       +                if(c->w[i] == w)
       +                        goto Found;
       +        error("can't find window");
       +
       +  Found:
       +        cr = c->r;
       +        if(but < 0){        /* make sure window fills its own space properly */
       +                r = w->r;
       +                if(i==c->nw-1 || c->safe==FALSE)
       +                        r.max.y = cr.max.y;
       +                else
       +                        r.max.y = c->w[i+1]->r.min.y;
       +                winresize(w, r, FALSE);
       +                return;
       +        }
       +        cr.min.y = c->w[0]->r.min.y;
       +        if(but == 3){        /* full size */
       +                if(i != 0){
       +                        v = c->w[0];
       +                        c->w[0] = w;
       +                        c->w[i] = v;
       +                }
       +                draw(screen, cr, textcols[BACK], nil, ZP);
       +                winresize(w, cr, FALSE);
       +                for(i=1; i<c->nw; i++)
       +                        c->w[i]->body.fr.maxlines = 0;
       +                c->safe = FALSE;
       +                return;
       +        }
       +        /* store old #lines for each window */
       +        onl = w->body.fr.maxlines;
       +        nl = emalloc(c->nw * sizeof(int));
       +        ny = emalloc(c->nw * sizeof(int));
       +        tot = 0;
       +        for(j=0; j<c->nw; j++){
       +                l = c->w[j]->body.fr.maxlines;
       +                nl[j] = l;
       +                tot += l;
       +        }
       +        /* approximate new #lines for this window */
       +        if(but == 2){        /* as big as can be */
       +                memset(nl, 0, c->nw * sizeof(int));
       +                goto Pack;
       +        }
       +        nnl = min(onl + max(min(5, w->maxlines), onl/2), tot);
       +        if(nnl < w->maxlines)
       +                nnl = (w->maxlines+nnl)/2;
       +        if(nnl == 0)
       +                nnl = 2;
       +        dnl = nnl - onl;
       +        /* compute new #lines for each window */
       +        for(k=1; k<c->nw; k++){
       +                /* prune from later window */
       +                j = i+k;
       +                if(j<c->nw && nl[j]){
       +                        l = min(dnl, max(1, nl[j]/2));
       +                        nl[j] -= l;
       +                        nl[i] += l;
       +                        dnl -= l;
       +                }
       +                /* prune from earlier window */
       +                j = i-k;
       +                if(j>=0 && nl[j]){
       +                        l = min(dnl, max(1, nl[j]/2));
       +                        nl[j] -= l;
       +                        nl[i] += l;
       +                        dnl -= l;
       +                }
       +        }
       +    Pack:
       +        /* pack everyone above */
       +        y1 = cr.min.y;
       +        for(j=0; j<i; j++){
       +                v = c->w[j];
       +                r = v->r;
       +                r.min.y = y1;
       +                r.max.y = y1+Dy(v->tag.all);
       +                if(nl[j])
       +                        r.max.y += 1 + nl[j]*v->body.fr.font->height;
       +                if(!c->safe || !eqrect(v->r, r)){
       +                        draw(screen, r, textcols[BACK], nil, ZP);
       +                        winresize(v, r, c->safe);
       +                }
       +                r.min.y = v->r.max.y;
       +                r.max.y += Border;
       +                draw(screen, r, display->black, nil, ZP);
       +                y1 = r.max.y;
       +        }
       +        /* scan to see new size of everyone below */
       +        y2 = c->r.max.y;
       +        for(j=c->nw-1; j>i; j--){
       +                v = c->w[j];
       +                r = v->r;
       +                r.min.y = y2-Dy(v->tag.all);
       +                if(nl[j])
       +                        r.min.y -= 1 + nl[j]*v->body.fr.font->height;
       +                r.min.y -= Border;
       +                ny[j] = r.min.y;
       +                y2 = r.min.y;
       +        }
       +        /* compute new size of window */
       +        r = w->r;
       +        r.min.y = y1;
       +        r.max.y = r.min.y+Dy(w->tag.all);
       +        h = w->body.fr.font->height;
       +        if(y2-r.max.y >= 1+h+Border){
       +                r.max.y += 1;
       +                r.max.y += h*((y2-r.max.y)/h);
       +        }
       +        /* draw window */
       +        if(!c->safe || !eqrect(w->r, r)){
       +                draw(screen, r, textcols[BACK], nil, ZP);
       +                winresize(w, r, c->safe);
       +        }
       +        if(i < c->nw-1){
       +                r.min.y = r.max.y;
       +                r.max.y += Border;
       +                draw(screen, r, display->black, nil, ZP);
       +                for(j=i+1; j<c->nw; j++)
       +                        ny[j] -= (y2-r.max.y);
       +        }
       +        /* pack everyone below */
       +        y1 = r.max.y;
       +        for(j=i+1; j<c->nw; j++){
       +                v = c->w[j];
       +                r = v->r;
       +                r.min.y = y1;
       +                r.max.y = y1+Dy(v->tag.all);
       +                if(nl[j])
       +                        r.max.y += 1 + nl[j]*v->body.fr.font->height;
       +                if(!c->safe || !eqrect(v->r, r)){
       +                        draw(screen, r, textcols[BACK], nil, ZP);
       +                        winresize(v, r, c->safe);
       +                }
       +                if(j < c->nw-1){        /* no border on last window */
       +                        r.min.y = v->r.max.y;
       +                        r.max.y += Border;
       +                        draw(screen, r, display->black, nil, ZP);
       +                }
       +                y1 = r.max.y;
       +        }
       +        r = w->r;
       +        r.min.y = y1;
       +        r.max.y = c->r.max.y;
       +        draw(screen, r, textcols[BACK], nil, ZP);
       +        free(nl);
       +        free(ny);
       +        c->safe = TRUE;
       +        winmousebut(w);
       +}
       +
       +void
       +coldragwin(Column *c, Window *w, int but)
       +{
       +        Rectangle r;
       +        int i, b;
       +        Point p, op;
       +        Window *v;
       +        Column *nc;
       +
       +        clearmouse();
       +        setcursor(mousectl, &boxcursor);
       +        b = mouse->buttons;
       +        op = mouse->xy;
       +        while(mouse->buttons == b)
       +                readmouse(mousectl);
       +        setcursor(mousectl, nil);
       +        if(mouse->buttons){
       +                while(mouse->buttons)
       +                        readmouse(mousectl);
       +                return;
       +        }
       +
       +        for(i=0; i<c->nw; i++)
       +                if(c->w[i] == w)
       +                        goto Found;
       +        error("can't find window");
       +
       +  Found:
       +        p = mouse->xy;
       +        if(abs(p.x-op.x)<5 && abs(p.y-op.y)<5){
       +                colgrow(c, w, but);
       +                winmousebut(w);
       +                return;
       +        }
       +        /* is it a flick to the right? */
       +        if(abs(p.y-op.y)<10 && p.x>op.x+30 && rowwhichcol(c->row, p)==c)
       +                p.x += Dx(w->r);        /* yes: toss to next column */
       +        nc = rowwhichcol(c->row, p);
       +        if(nc!=nil && nc!=c){
       +                colclose(c, w, FALSE);
       +                coladd(nc, w, nil, p.y);
       +                winmousebut(w);
       +                return;
       +        }
       +        if(i==0 && c->nw==1)
       +                return;                        /* can't do it */
       +        if((i>0 && p.y<c->w[i-1]->r.min.y) || (i<c->nw-1 && p.y>w->r.max.y)
       +        || (i==0 && p.y>w->r.max.y)){
       +                /* shuffle */
       +                colclose(c, w, FALSE);
       +                coladd(c, w, nil, p.y);
       +                winmousebut(w);
       +                return;
       +        }
       +        if(i == 0)
       +                return;
       +        v = c->w[i-1];
       +        if(p.y < v->tag.all.max.y)
       +                p.y = v->tag.all.max.y;
       +        if(p.y > w->r.max.y-Dy(w->tag.all)-Border)
       +                p.y = w->r.max.y-Dy(w->tag.all)-Border;
       +        r = v->r;
       +        r.max.y = p.y;
       +        if(r.max.y > v->body.fr.r.min.y){
       +                r.max.y -= (r.max.y-v->body.fr.r.min.y)%v->body.fr.font->height;
       +                if(v->body.fr.r.min.y == v->body.fr.r.max.y)
       +                        r.max.y++;
       +        }
       +        if(!eqrect(v->r, r)){
       +                draw(screen, r, textcols[BACK], nil, ZP);
       +                winresize(v, r, c->safe);
       +        }
       +        r.min.y = v->r.max.y;
       +        r.max.y = r.min.y+Border;
       +        draw(screen, r, display->black, nil, ZP);
       +        r.min.y = r.max.y;
       +        if(i == c->nw-1)
       +                r.max.y = c->r.max.y;
       +        else
       +                r.max.y = c->w[i+1]->r.min.y-Border;
       +        if(!eqrect(w->r, r)){
       +                draw(screen, r, textcols[BACK], nil, ZP);
       +                winresize(w, r, c->safe);
       +        }
       +        c->safe = TRUE;
       +            winmousebut(w);
       +}
       +
       +Text*
       +colwhich(Column *c, Point p)
       +{
       +        int i;
       +        Window *w;
       +
       +        if(!ptinrect(p, c->r))
       +                return nil;
       +        if(ptinrect(p, c->tag.all))
       +                return &c->tag;
       +        for(i=0; i<c->nw; i++){
       +                w = c->w[i];
       +                if(ptinrect(p, w->r)){
       +                        if(ptinrect(p, w->tag.all))
       +                                return &w->tag;
       +                        return &w->body;
       +                }
       +        }
       +        return nil;
       +}
       +
       +int
       +colclean(Column *c)
       +{
       +        int i, clean;
       +
       +        clean = TRUE;
       +        for(i=0; i<c->nw; i++)
       +                clean &= winclean(c->w[i], TRUE);
       +        return clean;
       +}
 (DIR) diff --git a/src/cmd/acme/dat.h b/src/cmd/acme/dat.h
       t@@ -0,0 +1,546 @@
       +enum
       +{
       +        Qdir,
       +        Qacme,
       +        Qcons,
       +        Qconsctl,
       +        Qdraw,
       +        Qeditout,
       +        Qindex,
       +        Qlabel,
       +        Qnew,
       +
       +        QWaddr,
       +        QWbody,
       +        QWctl,
       +        QWdata,
       +        QWeditout,
       +        QWevent,
       +        QWrdsel,
       +        QWwrsel,
       +        QWtag,
       +        QMAX,
       +};
       +
       +enum
       +{
       +        Blockincr =        256,
       +        Maxblock =         8*1024,
       +        NRange =                10,
       +        Infinity =                 0x7FFFFFFF,        /* huge value for regexp address */
       +};
       +
       +typedef        struct        Block Block;
       +typedef        struct        Buffer Buffer;
       +typedef        struct        Command Command;
       +typedef        struct        Column Column;
       +typedef        struct        Dirlist Dirlist;
       +typedef        struct        Dirtab Dirtab;
       +typedef        struct        Disk Disk;
       +typedef        struct        Expand Expand;
       +typedef        struct        Fid Fid;
       +typedef        struct        File File;
       +typedef        struct        Elog Elog;
       +typedef        struct        Mntdir Mntdir;
       +typedef        struct        Range Range;
       +typedef        struct        Rangeset Rangeset;
       +typedef        struct        Reffont Reffont;
       +typedef        struct        Row Row;
       +typedef        struct        Runestr Runestr;
       +typedef        struct        Text Text;
       +typedef        struct        Timer Timer;
       +typedef        struct        Window Window;
       +typedef        struct        Xfid Xfid;
       +
       +struct Runestr
       +{
       +        Rune        *r;
       +        int        nr;
       +};
       +
       +struct Range
       +{
       +        int        q0;
       +        int        q1;
       +};
       +
       +struct Block
       +{
       +        uint                addr;        /* disk address in bytes */
       +        union
       +        {
       +                uint        n;                /* number of used runes in block */
       +                Block        *next;        /* pointer to next in free list */
       +        } u;
       +};
       +
       +struct Disk
       +{
       +        int                fd;
       +        uint                addr;        /* length of temp file */
       +        Block        *free[Maxblock/Blockincr+1];
       +};
       +
       +Disk*        diskinit(void);
       +Block*        disknewblock(Disk*, uint);
       +void                diskrelease(Disk*, Block*);
       +void                diskread(Disk*, Block*, Rune*, uint);
       +void                diskwrite(Disk*, Block**, Rune*, uint);
       +
       +struct Buffer
       +{
       +        uint        nc;
       +        Rune        *c;                        /* cache */
       +        uint        cnc;                        /* bytes in cache */
       +        uint        cmax;                /* size of allocated cache */
       +        uint        cq;                        /* position of cache */
       +        int                cdirty;        /* cache needs to be written */
       +        uint        cbi;                        /* index of cache Block */
       +        Block        **bl;                /* array of blocks */
       +        uint        nbl;                        /* number of blocks */
       +};
       +void                bufinsert(Buffer*, uint, Rune*, uint);
       +void                bufdelete(Buffer*, uint, uint);
       +uint                bufload(Buffer*, uint, int, int*);
       +void                bufread(Buffer*, uint, Rune*, uint);
       +void                bufclose(Buffer*);
       +void                bufreset(Buffer*);
       +
       +struct Elog
       +{
       +        short        type;                /* Delete, Insert, Filename */
       +        uint                q0;                /* location of change (unused in f) */
       +        uint                nd;                /* number of deleted characters */
       +        uint                nr;                /* # runes in string or file name */
       +        Rune                *r;
       +};
       +void        elogterm(File*);
       +void        elogclose(File*);
       +void        eloginsert(File*, int, Rune*, int);
       +void        elogdelete(File*, int, int);
       +void        elogreplace(File*, int, int, Rune*, int);
       +void        elogapply(File*);
       +
       +struct File
       +{
       +        Buffer        b;                        /* the data */
       +        Buffer        delta;        /* transcript of changes */
       +        Buffer        epsilon;        /* inversion of delta for redo */
       +        Buffer        *elogbuf;        /* log of pending editor changes */
       +        Elog                elog;                /* current pending change */
       +        Rune                *name;        /* name of associated file */
       +        int                nname;        /* size of name */
       +        uvlong        qidpath;        /* of file when read */
       +        uint                mtime;        /* of file when read */
       +        int                dev;                /* of file when read */
       +        int                unread;        /* file has not been read from disk */
       +        int                editclean;        /* mark clean after edit command */
       +
       +        int                seq;                /* if seq==0, File acts like Buffer */
       +        int                mod;
       +        Text                *curtext;        /* most recently used associated text */
       +        Text                **text;        /* list of associated texts */
       +        int                ntext;
       +        int                dumpid;        /* used in dumping zeroxed windows */
       +};
       +File*                fileaddtext(File*, Text*);
       +void                fileclose(File*);
       +void                filedelete(File*, uint, uint);
       +void                filedeltext(File*, Text*);
       +void                fileinsert(File*, uint, Rune*, uint);
       +uint                fileload(File*, uint, int, int*);
       +void                filemark(File*);
       +void                filereset(File*);
       +void                filesetname(File*, Rune*, int);
       +void                fileundelete(File*, Buffer*, uint, uint);
       +void                fileuninsert(File*, Buffer*, uint, uint);
       +void                fileunsetname(File*, Buffer*);
       +void                fileundo(File*, int, uint*, uint*);
       +uint                fileredoseq(File*);
       +
       +enum        /* Text.what */
       +{
       +        Columntag,
       +        Rowtag,
       +        Tag,
       +        Body,
       +};
       +
       +struct Text
       +{
       +        File                *file;
       +        Frame        fr;
       +        Reffont        *reffont;
       +        uint        org;
       +        uint        q0;
       +        uint        q1;
       +        int        what;
       +        int        tabstop;
       +        Window        *w;
       +        Rectangle scrollr;
       +        Rectangle lastsr;
       +        Rectangle all;
       +        Row                *row;
       +        Column        *col;
       +
       +        uint        eq0;        /* start of typing for ESC */
       +        uint        cq0;        /* cache position */
       +        int                ncache;        /* storage for insert */
       +        int                ncachealloc;
       +        Rune        *cache;
       +        int        nofill;
       +};
       +
       +uint                textbacknl(Text*, uint, uint);
       +uint                textbsinsert(Text*, uint, Rune*, uint, int, int*);
       +int                textbswidth(Text*, Rune);
       +int                textclickmatch(Text*, int, int, int, uint*);
       +void                textclose(Text*);
       +void                textcolumnate(Text*, Dirlist**, int);
       +void                textcommit(Text*, int);
       +void                textconstrain(Text*, uint, uint, uint*, uint*);
       +void                textdelete(Text*, uint, uint, int);
       +void                textdoubleclick(Text*, uint*, uint*);
       +void                textfill(Text*);
       +void                textframescroll(Text*, int);
       +void                textinit(Text*, File*, Rectangle, Reffont*, Image**);
       +void                textinsert(Text*, uint, Rune*, uint, int);
       +uint                textload(Text*, uint, char*, int);
       +Rune                textreadc(Text*, uint);
       +void                textredraw(Text*, Rectangle, Font*, Image*, int);
       +void                textreset(Text*);
       +int                textresize(Text*, Rectangle);
       +void                textscrdraw(Text*);
       +void                textscroll(Text*, int);
       +void                textselect(Text*);
       +int                textselect2(Text*, uint*, uint*, Text**);
       +int                textselect23(Text*, uint*, uint*, Image*, int);
       +int                textselect3(Text*, uint*, uint*);
       +void                textsetorigin(Text*, uint, int);
       +void                textsetselect(Text*, uint, uint);
       +void                textshow(Text*, uint, uint, int);
       +void                texttype(Text*, Rune);
       +
       +struct Window
       +{
       +        QLock        lk;
       +        Ref        ref;
       +        Text                tag;
       +        Text                body;
       +        Rectangle        r;
       +        uchar        isdir;
       +        uchar        isscratch;
       +        uchar        filemenu;
       +        uchar        dirty;
       +        int                id;
       +        Range        addr;
       +        Range        limit;
       +        uchar        nopen[QMAX];
       +        uchar        nomark;
       +        uchar        noscroll;
       +        Range        wrselrange;
       +        int                rdselfd;
       +        int                neditwrsel;
       +        Column        *col;
       +        Xfid                *eventx;
       +        char                *events;
       +        int                nevents;
       +        int                owner;
       +        int                maxlines;
       +        Dirlist        **dlp;
       +        int                ndl;
       +        int                putseq;
       +        int                nincl;
       +        Rune                **incl;
       +        Reffont        *reffont;
       +        QLock        ctllock;
       +        uint                ctlfid;
       +        char                *dumpstr;
       +        char                *dumpdir;
       +        int                dumpid;
       +        int                utflastqid;
       +        int                utflastboff;
       +        int                utflastq;
       +};
       +
       +void        wininit(Window*, Window*, Rectangle);
       +void        winlock(Window*, int);
       +void        winlock1(Window*, int);
       +void        winunlock(Window*);
       +void        wintype(Window*, Text*, Rune);
       +void        winundo(Window*, int);
       +void        winsetname(Window*, Rune*, int);
       +void        winsettag(Window*);
       +void        winsettag1(Window*);
       +void        wincommit(Window*, Text*);
       +int        winresize(Window*, Rectangle, int);
       +void        winclose(Window*);
       +void        windelete(Window*);
       +int        winclean(Window*, int);
       +void        windirfree(Window*);
       +void        winevent(Window*, char*, ...);
       +void        winmousebut(Window*);
       +void        winaddincl(Window*, Rune*, int);
       +void        wincleartag(Window*);
       +void        winctlprint(Window*, char*, int);
       +
       +struct Column
       +{
       +        Rectangle r;
       +        Text        tag;
       +        Row                *row;
       +        Window        **w;
       +        int                nw;
       +        int                safe;
       +};
       +
       +void                colinit(Column*, Rectangle);
       +Window*        coladd(Column*, Window*, Window*, int);
       +void                colclose(Column*, Window*, int);
       +void                colcloseall(Column*);
       +void                colresize(Column*, Rectangle);
       +Text*        colwhich(Column*, Point);
       +void                coldragwin(Column*, Window*, int);
       +void                colgrow(Column*, Window*, int);
       +int                colclean(Column*);
       +void                colsort(Column*);
       +void                colmousebut(Column*);
       +
       +struct Row
       +{
       +        QLock        lk;
       +        Rectangle r;
       +        Text        tag;
       +        Column        **col;
       +        int                ncol;
       +
       +};
       +
       +void                rowinit(Row*, Rectangle);
       +Column*        rowadd(Row*, Column *c, int);
       +void                rowclose(Row*, Column*, int);
       +Text*        rowwhich(Row*, Point);
       +Column*        rowwhichcol(Row*, Point);
       +void                rowresize(Row*, Rectangle);
       +Text*        rowtype(Row*, Rune, Point);
       +void                rowdragcol(Row*, Column*, int but);
       +int                rowclean(Row*);
       +void                rowdump(Row*, char*);
       +void                rowload(Row*, char*, int);
       +void                rowloadfonts(char*);
       +
       +struct Timer
       +{
       +        int                dt;
       +        int                cancel;
       +        Channel        *c;        /* chan(int) */
       +        Timer        *next;
       +};
       +
       +struct Command
       +{
       +        int                pid;
       +        Rune                *name;
       +        int                nname;
       +        char                *text;
       +        char                **av;
       +        int                iseditcmd;
       +        Mntdir        *md;
       +        Command        *next;
       +};
       +
       +struct Dirtab
       +{
       +        char        *name;
       +        uchar        type;
       +        uint        qid;
       +        uint        perm;
       +};
       +
       +struct Mntdir
       +{
       +        int                id;
       +        int                ref;
       +        Rune                *dir;
       +        int                ndir;
       +        Mntdir        *next;
       +        int                nincl;
       +        Rune                **incl;
       +};
       +
       +struct Fid
       +{
       +        int                fid;
       +        int                busy;
       +        int                open;
       +        Qid                qid;
       +        Window        *w;
       +        Dirtab        *dir;
       +        Fid                *next;
       +        Mntdir        *mntdir;
       +        int                nrpart;
       +        uchar        rpart[UTFmax];
       +};
       +
       +
       +struct Xfid
       +{
       +        void                *arg;        /* args to xfidinit */
       +        Fcall        fcall;
       +        Xfid        *next;
       +        Channel        *c;                /* chan(void(*)(Xfid*)) */
       +        Fid        *f;
       +        uchar        *buf;
       +        int        flushed;
       +
       +};
       +
       +void                xfidctl(void *);
       +void                xfidflush(Xfid*);
       +void                xfidopen(Xfid*);
       +void                xfidclose(Xfid*);
       +void                xfidread(Xfid*);
       +void                xfidwrite(Xfid*);
       +void                xfidctlwrite(Xfid*, Window*);
       +void                xfideventread(Xfid*, Window*);
       +void                xfideventwrite(Xfid*, Window*);
       +void                xfidindexread(Xfid*);
       +void                xfidutfread(Xfid*, Text*, uint, int);
       +int                xfidruneread(Xfid*, Text*, uint, uint);
       +
       +struct Reffont
       +{
       +        Ref        ref;
       +        Font        *f;
       +
       +};
       +Reffont        *rfget(int, int, int, char*);
       +void                rfclose(Reffont*);
       +
       +struct Rangeset
       +{
       +        Range        r[NRange];
       +};
       +
       +struct Dirlist
       +{
       +        Rune        *r;
       +        int                nr;
       +        int                wid;
       +};
       +
       +struct Expand
       +{
       +        uint        q0;
       +        uint        q1;
       +        Rune        *name;
       +        int        nname;
       +        char        *bname;
       +        int        jump;
       +        union{
       +                Text        *at;
       +                Rune        *ar;
       +        } u;
       +        int        (*agetc)(void*, uint);
       +        int        a0;
       +        int        a1;
       +};
       +
       +enum
       +{
       +        /* fbufalloc() guarantees room off end of BUFSIZE */
       +        BUFSIZE = Maxblock+IOHDRSZ,        /* size from fbufalloc() */
       +        RBUFSIZE = BUFSIZE/sizeof(Rune),
       +        EVENTSIZE = 256,
       +        Scrollwid = 12,        /* width of scroll bar */
       +        Scrollgap = 4,        /* gap right of scroll bar */
       +        Margin = 4,        /* margin around text */
       +        Border = 2,        /* line between rows, cols, windows */
       +};
       +
       +#define        QID(w,q)        ((w<<8)|(q))
       +#define        WIN(q)        ((((ulong)(q).path)>>8) & 0xFFFFFF)
       +#define        FILE(q)        ((q).path & 0xFF)
       +
       +enum
       +{
       +        FALSE,
       +        TRUE,
       +        XXX,
       +};
       +
       +enum
       +{
       +        Empty        = 0,
       +        Null                = '-',
       +        Delete        = 'd',
       +        Insert        = 'i',
       +        Replace        = 'r',
       +        Filename        = 'f',
       +};
       +
       +enum        /* editing */
       +{
       +        Inactive        = 0,
       +        Inserting,
       +        Collecting,
       +};
       +
       +uint                seq;
       +uint                maxtab;        /* size of a tab, in units of the '0' character */
       +
       +Display                *display;
       +Image                *screen;
       +Font                        *font;
       +Mouse                *mouse;
       +Mousectl                *mousectl;
       +Keyboardctl        *keyboardctl;
       +Reffont                reffont;
       +Image                *modbutton;
       +Image                *colbutton;
       +Image                *button;
       +Image                *but2col;
       +Image                *but3col;
       +Cursor                boxcursor;
       +Row                        row;
       +int                        timerpid;
       +Disk                        *disk;
       +Text                        *seltext;
       +Text                        *argtext;
       +Text                        *mousetext;        /* global because Text.close needs to clear it */
       +Text                        *typetext;                /* global because Text.close needs to clear it */
       +Text                        *barttext;                /* shared between mousetask and keyboardthread */
       +int                        bartflag;
       +Window                *activewin;
       +Column                *activecol;
       +Buffer                snarfbuf;
       +Rectangle                nullrect;
       +int                        fsyspid;
       +char                        *cputype;
       +char                        *objtype;
       +char                        *home;
       +char                        *fontnames[2];
       +Image                *tagcols[NCOL];
       +Image                *textcols[NCOL];
       +int                        plumbsendfd;
       +int                        plumbeditfd;
       +extern char                wdir[];
       +int                        editing;
       +int                        erroutfd;
       +int                        messagesize;                /* negotiated in 9P version setup */
       +
       +Channel        *ckeyboard;        /* chan(Rune)[10] */
       +Channel        *cplumb;                /* chan(Plumbmsg*) */
       +Channel        *cwait;                /* chan(Waitmsg) */
       +Channel        *ccommand;        /* chan(Command*) */
       +Channel        *ckill;                /* chan(Rune*) */
       +Channel        *cxfidalloc;        /* chan(Xfid*) */
       +Channel        *cxfidfree;        /* chan(Xfid*) */
       +Channel        *cnewwindow;        /* chan(Channel*) */
       +Channel        *mouseexit0;        /* chan(int) */
       +Channel        *mouseexit1;        /* chan(int) */
       +Channel        *cexit;                /* chan(int) */
       +Channel        *cerr;                /* chan(char*) */
       +Channel        *cedit;                /* chan(int) */
       +
       +#define        STACK        32768
 (DIR) diff --git a/src/cmd/acme/disk.c b/src/cmd/acme/disk.c
       t@@ -0,0 +1,129 @@
       +#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 "fns.h"
       +
       +static        Block        *blist;
       +
       +int
       +tempfile(void)
       +{
       +        char buf[128];
       +        int i, fd;
       +
       +        snprint(buf, sizeof buf, "/tmp/X%d.%.4sacme", getpid(), getuser());
       +        for(i='A'; i<='Z'; i++){
       +                buf[5] = i;
       +                if(access(buf, AEXIST) == 0)
       +                        continue;
       +                fd = create(buf, ORDWR|ORCLOSE|OCEXEC, 0600);
       +                if(fd >= 0)
       +                        return fd;
       +        }
       +        return -1;
       +}
       +
       +Disk*
       +diskinit()
       +{
       +        Disk *d;
       +
       +        d = emalloc(sizeof(Disk));
       +        d->fd = tempfile();
       +        if(d->fd < 0){
       +                fprint(2, "acme: can't create temp file: %r\n");
       +                threadexitsall("diskinit");
       +        }
       +        return d;
       +}
       +
       +static
       +uint
       +ntosize(uint n, uint *ip)
       +{
       +        uint size;
       +
       +        if(n > Maxblock)
       +                error("internal error: ntosize");
       +        size = n;
       +        if(size & (Blockincr-1))
       +                size += Blockincr - (size & (Blockincr-1));
       +        /* last bucket holds blocks of exactly Maxblock */
       +        if(ip)
       +                *ip = size/Blockincr;
       +        return size * sizeof(Rune);
       +}
       +
       +Block*
       +disknewblock(Disk *d, uint n)
       +{
       +        uint i, j, size;
       +        Block *b;
       +
       +        size = ntosize(n, &i);
       +        b = d->free[i];
       +        if(b)
       +                d->free[i] = b->u.next;
       +        else{
       +                /* allocate in chunks to reduce malloc overhead */
       +                if(blist == nil){
       +                        blist = emalloc(100*sizeof(Block));
       +                        for(j=0; j<100-1; j++)
       +                                blist[j].u.next = &blist[j+1];
       +                }
       +                b = blist;
       +                blist = b->u.next;
       +                b->addr = d->addr;
       +                d->addr += size;
       +        }
       +        b->u.n = n;
       +        return b;
       +}
       +
       +void
       +diskrelease(Disk *d, Block *b)
       +{
       +        uint i;
       +
       +        ntosize(b->u.n, &i);
       +        b->u.next = d->free[i];
       +        d->free[i] = b;
       +}
       +
       +void
       +diskwrite(Disk *d, Block **bp, Rune *r, uint n)
       +{
       +        int size, nsize;
       +        Block *b;
       +
       +        b = *bp;
       +        size = ntosize(b->u.n, nil);
       +        nsize = ntosize(n, nil);
       +        if(size != nsize){
       +                diskrelease(d, b);
       +                b = disknewblock(d, n);
       +                *bp = b;
       +        }
       +        if(pwrite(d->fd, r, n*sizeof(Rune), b->addr) != n*sizeof(Rune))
       +                error("write error to temp file");
       +        b->u.n = n;
       +}
       +
       +void
       +diskread(Disk *d, Block *b, Rune *r, uint n)
       +{
       +        if(n > b->u.n)
       +                error("internal error: diskread");
       +
       +        ntosize(b->u.n, nil);
       +        if(pread(d->fd, r, n*sizeof(Rune), b->addr) != n*sizeof(Rune))
       +                error("read error from temp file");
       +}
 (DIR) diff --git a/src/cmd/acme/ecmd.c b/src/cmd/acme/ecmd.c
       t@@ -0,0 +1,1325 @@
       +#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"
       +
       +int        Glooping;
       +int        nest;
       +char        Enoname[] = "no file name given";
       +
       +Address        addr;
       +File        *menu;
       +Rangeset        sel;
       +extern        Text*        curtext;
       +Rune        *collection;
       +int        ncollection;
       +
       +int        append(File*, Cmd*, long);
       +int        pdisplay(File*);
       +void        pfilename(File*);
       +void        looper(File*, Cmd*, int);
       +void        filelooper(Cmd*, int);
       +void        linelooper(File*, Cmd*);
       +Address        lineaddr(long, Address, int);
       +int        filematch(File*, String*);
       +File        *tofile(String*);
       +Rune*        cmdname(File *f, String *s, int);
       +void        runpipe(Text*, int, Rune*, int, int);
       +
       +void
       +clearcollection(void)
       +{
       +        free(collection);
       +        collection = nil;
       +        ncollection = 0;
       +}
       +
       +void
       +resetxec(void)
       +{
       +        Glooping = nest = 0;
       +        clearcollection();
       +}
       +
       +void
       +mkaddr(Address *a, File *f)
       +{
       +        a->r.q0 = f->curtext->q0;
       +        a->r.q1 = f->curtext->q1;
       +        a->f = f;
       +}
       +
       +int
       +cmdexec(Text *t, Cmd *cp)
       +{
       +        int i;
       +        Addr *ap;
       +        File *f;
       +        Window *w;
       +        Address dot;
       +
       +        if(t == nil)
       +                w = nil;
       +        else
       +                w = t->w;
       +        if(w==nil && (cp->addr==0 || cp->addr->type!='"') &&
       +            !utfrune("bBnqUXY!", cp->cmdc) &&
       +            !(cp->cmdc=='D' && cp->u.text))
       +                editerror("no current window");
       +        i = cmdlookup(cp->cmdc);        /* will be -1 for '{' */
       +        f = nil;
       +        if(t && t->w){
       +                t = &t->w->body;
       +                f = t->file;
       +                f->curtext = t;
       +        }
       +        if(i>=0 && cmdtab[i].defaddr != aNo){
       +                if((ap=cp->addr)==0 && cp->cmdc!='\n'){
       +                        cp->addr = ap = newaddr();
       +                        ap->type = '.';
       +                        if(cmdtab[i].defaddr == aAll)
       +                                ap->type = '*';
       +                }else if(ap && ap->type=='"' && ap->next==0 && cp->cmdc!='\n'){
       +                        ap->next = newaddr();
       +                        ap->next->type = '.';
       +                        if(cmdtab[i].defaddr == aAll)
       +                                ap->next->type = '*';
       +                }
       +                if(cp->addr){        /* may be false for '\n' (only) */
       +                        static Address none = {0,0,nil};
       +                        if(f){
       +                                mkaddr(&dot, f);
       +                                addr = cmdaddress(ap, dot, 0);
       +                        }else        /* a " */
       +                                addr = cmdaddress(ap, none, 0);
       +                        f = addr.f;
       +                        t = f->curtext;
       +                }
       +        }
       +        switch(cp->cmdc){
       +        case '{':
       +                mkaddr(&dot, f);
       +                if(cp->addr != nil)
       +                        dot = cmdaddress(cp->addr, dot, 0);
       +                for(cp = cp->u.cmd; cp; cp = cp->next){
       +                        if(dot.r.q1 > t->file->b.nc)
       +                                editerror("dot extends past end of buffer during { command");
       +                        t->q0 = dot.r.q0;
       +                        t->q1 = dot.r.q1;
       +                        cmdexec(t, cp);
       +                }
       +                break;
       +        default:
       +                if(i < 0)
       +                        editerror("unknown command %c in cmdexec", cp->cmdc);
       +                i = (*cmdtab[i].fn)(t, cp);
       +                return i;
       +        }
       +        return 1;
       +}
       +
       +char*
       +edittext(Window *w, int q, Rune *r, int nr)
       +{
       +        File *f;
       +
       +        f = w->body.file;
       +        switch(editing){
       +        case Inactive:
       +                return "permission denied";
       +        case Inserting:
       +                w->neditwrsel += nr;
       +                eloginsert(f, q, r, nr);
       +                return nil;
       +        case Collecting:
       +                collection = runerealloc(collection, ncollection+nr+1);
       +                runemove(collection+ncollection, r, nr);
       +                ncollection += nr;
       +                collection[ncollection] = '\0';
       +                return nil;
       +        default:
       +                return "unknown state in edittext";
       +        }
       +}
       +
       +/* string is known to be NUL-terminated */
       +Rune*
       +filelist(Text *t, Rune *r, int nr)
       +{
       +        if(nr == 0)
       +                return nil;
       +        r = skipbl(r, nr, &nr);
       +        if(r[0] != '<')
       +                return runestrdup(r);
       +        /* use < command to collect text */
       +        clearcollection();
       +        runpipe(t, '<', r+1, nr-1, Collecting);
       +        return collection;
       +}
       +
       +int
       +a_cmd(Text *t, Cmd *cp)
       +{
       +        return append(t->file, cp, addr.r.q1);
       +}
       +
       +int
       +b_cmd(Text *t, Cmd *cp)
       +{
       +        File *f;
       +
       +        USED(t);
       +        f = tofile(cp->u.text);
       +        if(nest == 0)
       +                pfilename(f);
       +        curtext = f->curtext;
       +        return TRUE;
       +}
       +
       +int
       +B_cmd(Text *t, Cmd *cp)
       +{
       +        Rune *list, *r, *s;
       +        int nr;
       +
       +        list = filelist(t, cp->u.text->r, cp->u.text->n);
       +        if(list == nil)
       +                editerror(Enoname);
       +        r = list;
       +        nr = runestrlen(r);
       +        r = skipbl(r, nr, &nr);
       +        if(nr == 0)
       +                new(t, t, nil, 0, 0, r, 0);
       +        else while(nr > 0){
       +                s = findbl(r, nr, &nr);
       +                *s = '\0';
       +                new(t, t, nil, 0, 0, r, runestrlen(r));
       +                if(nr > 0)
       +                        r = skipbl(s+1, nr-1, &nr);
       +        }
       +        clearcollection();
       +        return TRUE;
       +}
       +
       +int
       +c_cmd(Text *t, Cmd *cp)
       +{
       +        elogreplace(t->file, addr.r.q0, addr.r.q1, cp->u.text->r, cp->u.text->n);
       +        t->q0 = addr.r.q0;
       +        t->q1 = addr.r.q0+cp->u.text->n;
       +        return TRUE;
       +}
       +
       +int
       +d_cmd(Text *t, Cmd *cp)
       +{
       +        USED(cp);
       +        if(addr.r.q1 > addr.r.q0)
       +                elogdelete(t->file, addr.r.q0, addr.r.q1);
       +        t->q0 = addr.r.q0;
       +        t->q1 = addr.r.q0;
       +        return TRUE;
       +}
       +
       +void
       +D1(Text *t)
       +{
       +        if(t->w->body.file->ntext>1 || winclean(t->w, FALSE))
       +                colclose(t->col, t->w, TRUE);
       +}
       +
       +int
       +D_cmd(Text *t, Cmd *cp)
       +{
       +        Rune *list, *r, *s, *n;
       +        int nr, nn;
       +        Window *w;
       +        Runestr dir, rs;
       +        char buf[128];
       +
       +        list = filelist(t, cp->u.text->r, cp->u.text->n);
       +        if(list == nil){
       +                D1(t);
       +                return TRUE;
       +        }
       +        dir = dirname(t, nil, 0);
       +        r = list;
       +        nr = runestrlen(r);
       +        r = skipbl(r, nr, &nr);
       +        do{
       +                s = findbl(r, nr, &nr);
       +                *s = '\0';
       +                /* first time through, could be empty string, meaning delete file empty name */
       +                nn = runestrlen(r);
       +                if(r[0]=='/' || nn==0 || dir.nr==0){
       +                        rs.r = runestrdup(r);
       +                        rs.nr = nn;
       +                }else{
       +                        n = runemalloc(dir.nr+1+nn);
       +                        runemove(n, dir.r, dir.nr);
       +                        n[dir.nr] = '/';
       +                        runemove(n+dir.nr+1, r, nn);
       +                        rs = cleanrname((Runestr){n, dir.nr+1+nn});
       +                }
       +                w = lookfile(rs.r, rs.nr);
       +                if(w == nil){
       +                        snprint(buf, sizeof buf, "no such file %.*S", rs.nr, rs.r);
       +                        free(rs.r);
       +                        editerror(buf);
       +                }
       +                free(rs.r);
       +                D1(&w->body);
       +                if(nr > 0)
       +                        r = skipbl(s+1, nr-1, &nr);
       +        }while(nr > 0);
       +        clearcollection();
       +        free(dir.r);
       +        return TRUE;
       +}
       +
       +static int
       +readloader(void *v, uint q0, Rune *r, int nr)
       +{
       +        if(nr > 0)
       +                eloginsert(v, q0, r, nr);
       +        return 0;
       +}
       +
       +int
       +e_cmd(Text *t, Cmd *cp)
       +{
       +        Rune *name;
       +        File *f;
       +        int i, isdir, q0, q1, fd, nulls, samename, allreplaced;
       +        char *s, tmp[128];
       +        Dir *d;
       +
       +        f = t->file;
       +        q0 = addr.r.q0;
       +        q1 = addr.r.q1;
       +        if(cp->cmdc == 'e'){
       +                if(winclean(t->w, TRUE)==FALSE)
       +                        editerror("");        /* winclean generated message already */
       +                q0 = 0;
       +                q1 = f->b.nc;
       +        }
       +        allreplaced = (q0==0 && q1==f->b.nc);
       +        name = cmdname(f, cp->u.text, cp->cmdc=='e');
       +        if(name == nil)
       +                editerror(Enoname);
       +        i = runestrlen(name);
       +        samename = runeeq(name, i, t->file->name, t->file->nname);
       +        s = runetobyte(name, i);
       +        free(name);
       +        fd = open(s, OREAD);
       +        if(fd < 0){
       +                snprint(tmp, sizeof tmp, "can't open %s: %r", s);
       +                free(s);
       +                editerror(tmp);
       +        }
       +        d = dirfstat(fd);
       +        isdir = (d!=nil && (d->qid.type&QTDIR));
       +        free(d);
       +        if(isdir){
       +                close(fd);
       +                snprint(tmp, sizeof tmp, "%s is a directory", s);
       +                free(s);
       +                editerror(tmp);
       +        }
       +        elogdelete(f, q0, q1);
       +        nulls = 0;
       +        loadfile(fd, q1, &nulls, readloader, f);
       +        free(s);
       +        close(fd);
       +        if(nulls)
       +                warning(nil, "%s: NUL bytes elided\n", s);
       +        else if(allreplaced && samename)
       +                f->editclean = TRUE;
       +        return TRUE;
       +}
       +
       +static Rune Lempty[] = { 0 };
       +int
       +f_cmd(Text *t, Cmd *cp)
       +{
       +        Rune *name;
       +        String *str;
       +        String empty;
       +
       +        if(cp->u.text == nil){
       +                empty.n = 0;
       +                empty.r = Lempty;
       +                str = &empty;
       +        }else
       +                str = cp->u.text;
       +        name = cmdname(t->file, str, TRUE);
       +        free(name);
       +        pfilename(t->file);
       +        return TRUE;
       +}
       +
       +int
       +g_cmd(Text *t, Cmd *cp)
       +{
       +        if(t->file != addr.f){
       +                warning(nil, "internal error: g_cmd f!=addr.f\n");
       +                return FALSE;
       +        }
       +        if(rxcompile(cp->re->r) == FALSE)
       +                editerror("bad regexp in g command");
       +        if(rxexecute(t, nil, addr.r.q0, addr.r.q1, &sel) ^ cp->cmdc=='v'){
       +                t->q0 = addr.r.q0;
       +                t->q1 = addr.r.q1;
       +                return cmdexec(t, cp->u.cmd);
       +        }
       +        return TRUE;
       +}
       +
       +int
       +i_cmd(Text *t, Cmd *cp)
       +{
       +        return append(t->file, cp, addr.r.q0);
       +}
       +
       +void
       +copy(File *f, Address addr2)
       +{
       +        long p;
       +        int ni;
       +        Rune *buf;
       +
       +        buf = fbufalloc();
       +        for(p=addr.r.q0; p<addr.r.q1; p+=ni){
       +                ni = addr.r.q1-p;
       +                if(ni > RBUFSIZE)
       +                        ni = RBUFSIZE;
       +                bufread(&f->b, p, buf, ni);
       +                eloginsert(addr2.f, addr2.r.q1, buf, ni);
       +        }
       +        fbuffree(buf);
       +}
       +
       +void
       +move(File *f, Address addr2)
       +{
       +        if(addr.f!=addr2.f || addr.r.q1<=addr2.r.q0){
       +                elogdelete(f, addr.r.q0, addr.r.q1);
       +                copy(f, addr2);
       +        }else if(addr.r.q0 >= addr2.r.q1){
       +                copy(f, addr2);
       +                elogdelete(f, addr.r.q0, addr.r.q1);
       +        }else
       +                error("move overlaps itself");
       +}
       +
       +int
       +m_cmd(Text *t, Cmd *cp)
       +{
       +        Address dot, addr2;
       +
       +        mkaddr(&dot, t->file);
       +        addr2 = cmdaddress(cp->u.mtaddr, dot, 0);
       +        if(cp->cmdc == 'm')
       +                move(t->file, addr2);
       +        else
       +                copy(t->file, addr2);
       +        return TRUE;
       +}
       +
       +int
       +p_cmd(Text *t, Cmd *cp)
       +{
       +        USED(cp);
       +        return pdisplay(t->file);
       +}
       +
       +int
       +s_cmd(Text *t, Cmd *cp)
       +{
       +        int i, j, k, c, m, n, nrp, didsub;
       +        long p1, op, delta;
       +        String *buf;
       +        Rangeset *rp;
       +        char *err;
       +        Rune *rbuf;
       +
       +        n = cp->num;
       +        op= -1;
       +        if(rxcompile(cp->re->r) == FALSE)
       +                editerror("bad regexp in s command");
       +        nrp = 0;
       +        rp = nil;
       +        delta = 0;
       +        didsub = FALSE;
       +        for(p1 = addr.r.q0; p1<=addr.r.q1 && rxexecute(t, nil, p1, addr.r.q1, &sel); ){
       +                if(sel.r[0].q0 == sel.r[0].q1){        /* empty match? */
       +                        if(sel.r[0].q0 == op){
       +                                p1++;
       +                                continue;
       +                        }
       +                        p1 = sel.r[0].q1+1;
       +                }else
       +                        p1 = sel.r[0].q1;
       +                op = sel.r[0].q1;
       +                if(--n>0)
       +                        continue;
       +                nrp++;
       +                rp = erealloc(rp, nrp*sizeof(Rangeset));
       +                rp[nrp-1] = sel;
       +        }
       +        rbuf = fbufalloc();
       +        buf = allocstring(0);
       +        for(m=0; m<nrp; m++){
       +                buf->n = 0;
       +                buf->r[0] = L'\0';
       +                sel = rp[m];
       +                for(i = 0; i<cp->u.text->n; i++)
       +                        if((c = cp->u.text->r[i])=='\\' && i<cp->u.text->n-1){
       +                                c = cp->u.text->r[++i];
       +                                if('1'<=c && c<='9') {
       +                                        j = c-'0';
       +                                        if(sel.r[j].q1-sel.r[j].q0>RBUFSIZE){
       +                                                err = "replacement string too long";
       +                                                goto Err;
       +                                        }
       +                                        bufread(&t->file->b, sel.r[j].q0, rbuf, sel.r[j].q1-sel.r[j].q0);
       +                                        for(k=0; k<sel.r[j].q1-sel.r[j].q0; k++)
       +                                                Straddc(buf, rbuf[k]);
       +                                }else
       +                                         Straddc(buf, c);
       +                        }else if(c!='&')
       +                                Straddc(buf, c);
       +                        else{
       +                                if(sel.r[0].q1-sel.r[0].q0>RBUFSIZE){
       +                                        err = "right hand side too long in substitution";
       +                                        goto Err;
       +                                }
       +                                bufread(&t->file->b, sel.r[0].q0, rbuf, sel.r[0].q1-sel.r[0].q0);
       +                                for(k=0; k<sel.r[0].q1-sel.r[0].q0; k++)
       +                                        Straddc(buf, rbuf[k]);
       +                        }
       +                elogreplace(t->file, sel.r[0].q0, sel.r[0].q1,  buf->r, buf->n);
       +                delta -= sel.r[0].q1-sel.r[0].q0;
       +                delta += buf->n;
       +                didsub = 1;
       +                if(!cp->flag)
       +                        break;
       +        }
       +        free(rp);
       +        freestring(buf);
       +        fbuffree(rbuf);
       +        if(!didsub && nest==0)
       +                editerror("no substitution");
       +        t->q0 = addr.r.q0;
       +        t->q1 = addr.r.q1+delta;
       +        return TRUE;
       +
       +Err:
       +        free(rp);
       +        freestring(buf);
       +        fbuffree(rbuf);
       +        editerror(err);
       +        return FALSE;
       +}
       +
       +int
       +u_cmd(Text *t, Cmd *cp)
       +{
       +        int n, oseq, flag;
       +
       +        n = cp->num;
       +        flag = TRUE;
       +        if(n < 0){
       +                n = -n;
       +                flag = FALSE;
       +        }
       +        oseq = -1;
       +        while(n-->0 && t->file->seq!=0 && t->file->seq!=oseq){
       +                oseq = t->file->seq;
       +                undo(t, nil, nil, flag, 0, nil, 0);
       +        }
       +        return TRUE;
       +}
       +
       +int
       +w_cmd(Text *t, Cmd *cp)
       +{
       +        Rune *r;
       +        File *f;
       +
       +        f = t->file;
       +        if(f->seq == seq)
       +                editerror("can't write file with pending modifications");
       +        r = cmdname(f, cp->u.text, FALSE);
       +        if(r == nil)
       +                editerror("no name specified for 'w' command");
       +        putfile(f, addr.r.q0, addr.r.q1, r, runestrlen(r));
       +        /* r is freed by putfile */
       +        return TRUE;
       +}
       +
       +int
       +x_cmd(Text *t, Cmd *cp)
       +{
       +        if(cp->re)
       +                looper(t->file, cp, cp->cmdc=='x');
       +        else
       +                linelooper(t->file, cp);
       +        return TRUE;
       +}
       +
       +int
       +X_cmd(Text *t, Cmd *cp)
       +{
       +        USED(t);
       +
       +        filelooper(cp, cp->cmdc=='X');
       +        return TRUE;
       +}
       +
       +void
       +runpipe(Text *t, int cmd, Rune *cr, int ncr, int state)
       +{
       +        Rune *r, *s;
       +        int n;
       +        Runestr dir;
       +        Window *w;
       +
       +        r = skipbl(cr, ncr, &n);
       +        if(n == 0)
       +                editerror("no command specified for >");
       +        w = nil;
       +        if(state == Inserting){
       +                w = t->w;
       +                t->q0 = addr.r.q0;
       +                t->q1 = addr.r.q1;
       +                w->neditwrsel = 0;
       +                if(cmd == '<' || cmd=='|')
       +                        elogdelete(t->file, t->q0, t->q1);
       +        }
       +        s = runemalloc(n+2);
       +        s[0] = cmd;
       +        runemove(s+1, r, n);
       +        n++;
       +        dir.r = nil;
       +        dir.nr = 0;
       +        if(t != nil)
       +                dir = dirname(t, nil, 0);
       +        if(dir.nr==1 && dir.r[0]=='.'){        /* sigh */
       +                free(dir.r);
       +                dir.r = nil;
       +                dir.nr = 0;
       +        }
       +        editing = state;
       +        if(t!=nil && t->w!=nil)
       +                incref(&t->w->ref);        /* run will decref */
       +        run(w, runetobyte(s, n), dir.r, dir.nr, TRUE, nil, nil, TRUE);
       +        free(s);
       +        if(t!=nil && t->w!=nil)
       +                winunlock(t->w);
       +        qunlock(&row.lk);
       +        recvul(cedit);
       +        qlock(&row.lk);
       +        editing = Inactive;
       +        if(t!=nil && t->w!=nil)
       +                winlock(t->w, 'M');
       +        if(state == Inserting){
       +                t->q0 = addr.r.q0;
       +                t->q1 = addr.r.q0 + t->w->neditwrsel;
       +        }
       +}
       +
       +int
       +pipe_cmd(Text *t, Cmd *cp)
       +{
       +        runpipe(t, cp->cmdc, cp->u.text->r, cp->u.text->n, Inserting);
       +        return TRUE;
       +}
       +
       +long
       +nlcount(Text *t, long q0, long q1)
       +{
       +        long nl;
       +        Rune *buf;
       +        int i, nbuf;
       +
       +        buf = fbufalloc();
       +        nbuf = 0;
       +        i = nl = 0;
       +        while(q0 < q1){
       +                if(i == nbuf){
       +                        nbuf = q1-q0;
       +                        if(nbuf > RBUFSIZE)
       +                                nbuf = RBUFSIZE;
       +                        bufread(&t->file->b, q0, buf, nbuf);
       +                        i = 0;
       +                }
       +                if(buf[i++] == '\n')
       +                        nl++;
       +                q0++;
       +        }
       +        fbuffree(buf);
       +        return nl;
       +}
       +
       +void
       +printposn(Text *t, int charsonly)
       +{
       +        long l1, l2;
       +
       +        if (t != nil && t->file != nil && t->file->name != nil)
       +                warning(nil, "%.*S:", t->file->nname, t->file->name);
       +        if(!charsonly){
       +                l1 = 1+nlcount(t, 0, addr.r.q0);
       +                l2 = l1+nlcount(t, addr.r.q0, addr.r.q1);
       +                /* check if addr ends with '\n' */
       +                if(addr.r.q1>0 && addr.r.q1>addr.r.q0 && textreadc(t, addr.r.q1-1)=='\n')
       +                        --l2;
       +                warning(nil, "%lud", l1);
       +                if(l2 != l1)
       +                        warning(nil, ",%lud", l2);
       +                warning(nil, "\n");
       +                return;
       +        }
       +        warning(nil, "#%d", addr.r.q0);
       +        if(addr.r.q1 != addr.r.q0)
       +                warning(nil, ",#%d", addr.r.q1);
       +        warning(nil, "\n");
       +}
       +
       +int
       +eq_cmd(Text *t, Cmd *cp)
       +{
       +        int charsonly;
       +
       +        switch(cp->u.text->n){
       +        case 0:
       +                charsonly = FALSE;
       +                break;
       +        case 1:
       +                if(cp->u.text->r[0] == '#'){
       +                        charsonly = TRUE;
       +                        break;
       +                }
       +        default:
       +                SET(charsonly);
       +                editerror("newline expected");
       +        }
       +        printposn(t, charsonly);
       +        return TRUE;
       +}
       +
       +int
       +nl_cmd(Text *t, Cmd *cp)
       +{
       +        Address a;
       +        File *f;
       +
       +        f = t->file;
       +        if(cp->addr == 0){
       +                /* First put it on newline boundaries */
       +                mkaddr(&a, f);
       +                addr = lineaddr(0, a, -1);
       +                a = lineaddr(0, a, 1);
       +                addr.r.q1 = a.r.q1;
       +                if(addr.r.q0==t->q0 && addr.r.q1==t->q1){
       +                        mkaddr(&a, f);
       +                        addr = lineaddr(1, a, 1);
       +                }
       +        }
       +        textshow(t, addr.r.q0, addr.r.q1, 1);
       +        return TRUE;
       +}
       +
       +int
       +append(File *f, Cmd *cp, long p)
       +{
       +        if(cp->u.text->n > 0)
       +                eloginsert(f, p, cp->u.text->r, cp->u.text->n);
       +        f->curtext->q0 = p;
       +        f->curtext->q1 = p+cp->u.text->n;
       +        return TRUE;
       +}
       +
       +int
       +pdisplay(File *f)
       +{
       +        long p1, p2;
       +        int np;
       +        Rune *buf;
       +
       +        p1 = addr.r.q0;
       +        p2 = addr.r.q1;
       +        if(p2 > f->b.nc)
       +                p2 = f->b.nc;
       +        buf = fbufalloc();
       +        while(p1 < p2){
       +                np = p2-p1;
       +                if(np>RBUFSIZE-1)
       +                        np = RBUFSIZE-1;
       +                bufread(&f->b, p1, buf, np);
       +                buf[np] = L'\0';
       +                warning(nil, "%S", buf);
       +                p1 += np;
       +        }
       +        fbuffree(buf);
       +        f->curtext->q0 = addr.r.q0;
       +        f->curtext->q1 = addr.r.q1;
       +        return TRUE;
       +}
       +
       +void
       +pfilename(File *f)
       +{
       +        int dirty;
       +        Window *w;
       +
       +        w = f->curtext->w;
       +        /* same check for dirty as in settag, but we know ncache==0 */
       +        dirty = !w->isdir && !w->isscratch && f->mod;
       +        warning(nil, "%c%c%c %.*S\n", " '"[dirty],
       +                '+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name);
       +}
       +
       +void
       +loopcmd(File *f, Cmd *cp, Range *rp, long nrp)
       +{
       +        long i;
       +
       +        for(i=0; i<nrp; i++){
       +                f->curtext->q0 = rp[i].q0;
       +                f->curtext->q1 = rp[i].q1;
       +                cmdexec(f->curtext, cp);
       +        }
       +}
       +
       +void
       +looper(File *f, Cmd *cp, int xy)
       +{
       +        long p, op, nrp;
       +        Range r, tr;
       +        Range *rp;
       +
       +        r = addr.r;
       +        op= xy? -1 : r.q0;
       +        nest++;
       +        if(rxcompile(cp->re->r) == FALSE)
       +                editerror("bad regexp in %c command", cp->cmdc);
       +        nrp = 0;
       +        rp = nil;
       +        for(p = r.q0; p<=r.q1; ){
       +                if(!rxexecute(f->curtext, nil, p, r.q1, &sel)){ /* no match, but y should still run */
       +                        if(xy || op>r.q1)
       +                                break;
       +                        tr.q0 = op, tr.q1 = r.q1;
       +                        p = r.q1+1;        /* exit next loop */
       +                }else{
       +                        if(sel.r[0].q0==sel.r[0].q1){        /* empty match? */
       +                                if(sel.r[0].q0==op){
       +                                        p++;
       +                                        continue;
       +                                }
       +                                p = sel.r[0].q1+1;
       +                        }else
       +                                p = sel.r[0].q1;
       +                        if(xy)
       +                                tr = sel.r[0];
       +                        else
       +                                tr.q0 = op, tr.q1 = sel.r[0].q0;
       +                }
       +                op = sel.r[0].q1;
       +                nrp++;
       +                rp = erealloc(rp, nrp*sizeof(Range));
       +                rp[nrp-1] = tr;
       +        }
       +        loopcmd(f, cp->u.cmd, rp, nrp);
       +        free(rp);
       +        --nest;
       +}
       +
       +void
       +linelooper(File *f, Cmd *cp)
       +{
       +        long nrp, p;
       +        Range r, linesel;
       +        Address a, a3;
       +        Range *rp;
       +
       +        nest++;
       +        nrp = 0;
       +        rp = nil;
       +        r = addr.r;
       +        a3.f = f;
       +        a3.r.q0 = a3.r.q1 = r.q0;
       +        a = lineaddr(0, a3, 1);
       +        linesel = a.r;
       +        for(p = r.q0; p<r.q1; p = a3.r.q1){
       +                a3.r.q0 = a3.r.q1;
       +                if(p!=r.q0 || linesel.q1==p){
       +                        a = lineaddr(1, a3, 1);
       +                        linesel = a.r;
       +                }
       +                if(linesel.q0 >= r.q1)
       +                        break;
       +                if(linesel.q1 >= r.q1)
       +                        linesel.q1 = r.q1;
       +                if(linesel.q1 > linesel.q0)
       +                        if(linesel.q0>=a3.r.q1 && linesel.q1>a3.r.q1){
       +                                a3.r = linesel;
       +                                nrp++;
       +                                rp = erealloc(rp, nrp*sizeof(Range));
       +                                rp[nrp-1] = linesel;
       +                                continue;
       +                        }
       +                break;
       +        }
       +        loopcmd(f, cp->u.cmd, rp, nrp);
       +        free(rp);
       +        --nest;
       +}
       +
       +struct Looper
       +{
       +        Cmd *cp;
       +        int        XY;
       +        Window        **w;
       +        int        nw;
       +} loopstruct;        /* only one; X and Y can't nest */
       +
       +void
       +alllooper(Window *w, void *v)
       +{
       +        Text *t;
       +        struct Looper *lp;
       +        Cmd *cp;
       +
       +        lp = v;
       +        cp = lp->cp;
       +//        if(w->isscratch || w->isdir)
       +//                return;
       +        t = &w->body;
       +        /* only use this window if it's the current window for the file */
       +        if(t->file->curtext != t)
       +                return;
       +//        if(w->nopen[QWevent] > 0)
       +//                return;
       +        /* no auto-execute on files without names */
       +        if(cp->re==nil && t->file->nname==0)
       +                return;
       +        if(cp->re==nil || filematch(t->file, cp->re)==lp->XY){
       +                lp->w = erealloc(lp->w, (lp->nw+1)*sizeof(Window*));
       +                lp->w[lp->nw++] = w;
       +        }
       +}
       +
       +void
       +alllocker(Window *w, void *v)
       +{
       +        if(v)
       +                incref(&w->ref);
       +        else
       +                winclose(w);
       +}
       +
       +void
       +filelooper(Cmd *cp, int XY)
       +{
       +        int i;
       +
       +        if(Glooping++)
       +                editerror("can't nest %c command", "YX"[XY]);
       +        nest++;
       +
       +        loopstruct.cp = cp;
       +        loopstruct.XY = XY;
       +        if(loopstruct.w)        /* error'ed out last time */
       +                free(loopstruct.w);
       +        loopstruct.w = nil;
       +        loopstruct.nw = 0;
       +        allwindows(alllooper, &loopstruct);
       +        /*
       +         * add a ref to all windows to keep safe windows accessed by X
       +         * that would not otherwise have a ref to hold them up during
       +         * the shenanigans.
       +         */
       +        allwindows(alllocker, (void*)1);
       +        for(i=0; i<loopstruct.nw; i++)
       +                cmdexec(&loopstruct.w[i]->body, cp->u.cmd);
       +        allwindows(alllocker, (void*)0);
       +        free(loopstruct.w);
       +        loopstruct.w = nil;
       +
       +        --Glooping;
       +        --nest;
       +}
       +
       +void
       +nextmatch(File *f, String *r, long p, int sign)
       +{
       +        if(rxcompile(r->r) == FALSE)
       +                editerror("bad regexp in command address");
       +        if(sign >= 0){
       +                if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
       +                        editerror("no match for regexp");
       +                if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q0==p){
       +                        if(++p>f->b.nc)
       +                                p = 0;
       +                        if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
       +                                editerror("address");
       +                }
       +        }else{
       +                if(!rxbexecute(f->curtext, p, &sel))
       +                        editerror("no match for regexp");
       +                if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q1==p){
       +                        if(--p<0)
       +                                p = f->b.nc;
       +                        if(!rxbexecute(f->curtext, p, &sel))
       +                                editerror("address");
       +                }
       +        }
       +}
       +
       +File        *matchfile(String*);
       +Address        charaddr(long, Address, int);
       +Address        lineaddr(long, Address, int);
       +
       +Address
       +cmdaddress(Addr *ap, Address a, int sign)
       +{
       +        File *f = a.f;
       +        Address a1, a2;
       +
       +        do{
       +                switch(ap->type){
       +                case 'l':
       +                case '#':
       +                        a = (*(ap->type=='#'?charaddr:lineaddr))(ap->num, a, sign);
       +                        break;
       +
       +                case '.':
       +                        mkaddr(&a, f);
       +                        break;
       +
       +                case '$':
       +                        a.r.q0 = a.r.q1 = f->b.nc;
       +                        break;
       +
       +                case '\'':
       +editerror("can't handle '");
       +//                        a.r = f->mark;
       +                        break;
       +
       +                case '?':
       +                        sign = -sign;
       +                        if(sign == 0)
       +                                sign = -1;
       +                        /* fall through */
       +                case '/':
       +                        nextmatch(f, ap->u.re, sign>=0? a.r.q1 : a.r.q0, sign);
       +                        a.r = sel.r[0];
       +                        break;
       +
       +                case '"':
       +                        f = matchfile(ap->u.re);
       +                        mkaddr(&a, f);
       +                        break;
       +
       +                case '*':
       +                        a.r.q0 = 0, a.r.q1 = f->b.nc;
       +                        return a;
       +
       +                case ',':
       +                case ';':
       +                        if(ap->u.left)
       +                                a1 = cmdaddress(ap->u.left, a, 0);
       +                        else
       +                                a1.f = a.f, a1.r.q0 = a1.r.q1 = 0;
       +                        if(ap->type == ';'){
       +                                f = a1.f;
       +                                a = a1;
       +                                f->curtext->q0 = a1.r.q0;
       +                                f->curtext->q1 = a1.r.q1;
       +                        }
       +                        if(ap->next)
       +                                a2 = cmdaddress(ap->next, a, 0);
       +                        else
       +                                a2.f = a.f, a2.r.q0 = a2.r.q1 = f->b.nc;
       +                        if(a1.f != a2.f)
       +                                editerror("addresses in different files");
       +                        a.f = a1.f, a.r.q0 = a1.r.q0, a.r.q1 = a2.r.q1;
       +                        if(a.r.q1 < a.r.q0)
       +                                editerror("addresses out of order");
       +                        return a;
       +
       +                case '+':
       +                case '-':
       +                        sign = 1;
       +                        if(ap->type == '-')
       +                                sign = -1;
       +                        if(ap->next==0 || ap->next->type=='+' || ap->next->type=='-')
       +                                a = lineaddr(1L, a, sign);
       +                        break;
       +                default:
       +                        error("cmdaddress");
       +                        return a;
       +                }
       +        }while(ap = ap->next);        /* assign = */
       +        return a;
       +}
       +
       +struct Tofile{
       +        File                *f;
       +        String        *r;
       +};
       +
       +void
       +alltofile(Window *w, void *v)
       +{
       +        Text *t;
       +        struct Tofile *tp;
       +
       +        tp = v;
       +        if(tp->f != nil)
       +                return;
       +        if(w->isscratch || w->isdir)
       +                return;
       +        t = &w->body;
       +        /* only use this window if it's the current window for the file */
       +        if(t->file->curtext != t)
       +                return;
       +//        if(w->nopen[QWevent] > 0)
       +//                return;
       +        if(runeeq(tp->r->r, tp->r->n, t->file->name, t->file->nname))
       +                tp->f = t->file;
       +}
       +
       +File*
       +tofile(String *r)
       +{
       +        struct Tofile t;
       +        String rr;
       +
       +        rr.r = skipbl(r->r, r->n, &rr.n);
       +        t.f = nil;
       +        t.r = &rr;
       +        allwindows(alltofile, &t);
       +        if(t.f == nil)
       +                editerror("no such file\"%S\"", rr.r);
       +        return t.f;
       +}
       +
       +void
       +allmatchfile(Window *w, void *v)
       +{
       +        struct Tofile *tp;
       +        Text *t;
       +
       +        tp = v;
       +        if(w->isscratch || w->isdir)
       +                return;
       +        t = &w->body;
       +        /* only use this window if it's the current window for the file */
       +        if(t->file->curtext != t)
       +                return;
       +//        if(w->nopen[QWevent] > 0)
       +//                return;
       +        if(filematch(w->body.file, tp->r)){
       +                if(tp->f != nil)
       +                        editerror("too many files match \"%S\"", tp->r->r);
       +                tp->f = w->body.file;
       +        }
       +}
       +
       +File*
       +matchfile(String *r)
       +{
       +        struct Tofile tf;
       +
       +        tf.f = nil;
       +        tf.r = r;
       +        allwindows(allmatchfile, &tf);
       +
       +        if(tf.f == nil)
       +                editerror("no file matches \"%S\"", r->r);
       +        return tf.f;
       +}
       +
       +int
       +filematch(File *f, String *r)
       +{
       +        char *buf;
       +        Rune *rbuf;
       +        Window *w;
       +        int match, i, dirty;
       +        Rangeset s;
       +
       +        /* compile expr first so if we get an error, we haven't allocated anything */
       +        if(rxcompile(r->r) == FALSE)
       +                editerror("bad regexp in file match");
       +        buf = fbufalloc();
       +        w = f->curtext->w;
       +        /* same check for dirty as in settag, but we know ncache==0 */
       +        dirty = !w->isdir && !w->isscratch && f->mod;
       +        snprint(buf, BUFSIZE, "%c%c%c %.*S\n", " '"[dirty],
       +                '+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name);
       +        rbuf = bytetorune(buf, &i);
       +        fbuffree(buf);
       +        match = rxexecute(nil, rbuf, 0, i, &s);
       +        free(rbuf);
       +        return match;
       +}
       +
       +Address
       +charaddr(long l, Address addr, int sign)
       +{
       +        if(sign == 0)
       +                addr.r.q0 = addr.r.q1 = l;
       +        else if(sign < 0)
       +                addr.r.q1 = addr.r.q0 -= l;
       +        else if(sign > 0)
       +                addr.r.q0 = addr.r.q1 += l;
       +        if(addr.r.q0<0 || addr.r.q1>addr.f->b.nc)
       +                editerror("address out of range");
       +        return addr;
       +}
       +
       +Address
       +lineaddr(long l, Address addr, int sign)
       +{
       +        int n;
       +        int c;
       +        File *f = addr.f;
       +        Address a;
       +        long p;
       +
       +        a.f = f;
       +        if(sign >= 0){
       +                if(l == 0){
       +                        if(sign==0 || addr.r.q1==0){
       +                                a.r.q0 = a.r.q1 = 0;
       +                                return a;
       +                        }
       +                        a.r.q0 = addr.r.q1;
       +                        p = addr.r.q1-1;
       +                }else{
       +                        if(sign==0 || addr.r.q1==0){
       +                                p = 0;
       +                                n = 1;
       +                        }else{
       +                                p = addr.r.q1-1;
       +                                n = textreadc(f->curtext, p++)=='\n';
       +                        }
       +                        while(n < l){
       +                                if(p >= f->b.nc)
       +                                        editerror("address out of range");
       +                                if(textreadc(f->curtext, p++) == '\n')
       +                                        n++;
       +                        }
       +                        a.r.q0 = p;
       +                }
       +                while(p < f->b.nc && textreadc(f->curtext, p++)!='\n')
       +                        ;
       +                a.r.q1 = p;
       +        }else{
       +                p = addr.r.q0;
       +                if(l == 0)
       +                        a.r.q1 = addr.r.q0;
       +                else{
       +                        for(n = 0; n<l; ){        /* always runs once */
       +                                if(p == 0){
       +                                        if(++n != l)
       +                                                editerror("address out of range");
       +                                }else{
       +                                        c = textreadc(f->curtext, p-1);
       +                                        if(c != '\n' || ++n != l)
       +                                                p--;
       +                                }
       +                        }
       +                        a.r.q1 = p;
       +                        if(p > 0)
       +                                p--;
       +                }
       +                while(p > 0 && textreadc(f->curtext, p-1)!='\n')        /* lines start after a newline */
       +                        p--;
       +                a.r.q0 = p;
       +        }
       +        return a;
       +}
       +
       +struct Filecheck
       +{
       +        File        *f;
       +        Rune        *r;
       +        int nr;
       +};
       +
       +void
       +allfilecheck(Window *w, void *v)
       +{
       +        struct Filecheck *fp;
       +        File *f;
       +
       +        fp = v;
       +        f = w->body.file;
       +        if(w->body.file == fp->f)
       +                return;
       +        if(runeeq(fp->r, fp->nr, f->name, f->nname))
       +                warning(nil, "warning: duplicate file name \"%.*S\"\n", fp->nr, fp->r);
       +}
       +
       +Rune*
       +cmdname(File *f, String *str, int set)
       +{
       +        Rune *r, *s;
       +        int n;
       +        struct Filecheck fc;
       +        Runestr newname;
       +
       +        r = nil;
       +        n = str->n;
       +        s = str->r;
       +        if(n == 0){
       +                /* no name; use existing */
       +                if(f->nname == 0)
       +                        return nil;
       +                r = runemalloc(f->nname+1);
       +                runemove(r, f->name, f->nname);
       +                return r;
       +        }
       +        s = skipbl(s, n, &n);
       +        if(n == 0)
       +                goto Return;
       +
       +        if(s[0] == '/'){
       +                r = runemalloc(n+1);
       +                runemove(r, s, n);
       +        }else{
       +                newname = dirname(f->curtext, runestrdup(s), n);
       +                r = newname.r;
       +                n = newname.nr;
       +        }
       +        fc.f = f;
       +        fc.r = r;
       +        fc.nr = n;
       +        allwindows(allfilecheck, &fc);
       +        if(f->nname == 0)
       +                set = TRUE;
       +
       +    Return:
       +        if(set && !runeeq(r, n, f->name, f->nname)){
       +                filemark(f);
       +                f->mod = TRUE;
       +                f->curtext->w->dirty = TRUE;
       +                winsetname(f->curtext->w, r, n);
       +        }
       +        return r;
       +}
 (DIR) diff --git a/src/cmd/acme/edit.c b/src/cmd/acme/edit.c
       t@@ -0,0 +1,682 @@
       +#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 *v)
       +{
       +        Cmd *cmdp;
       +
       +        USED(v);
       +        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 *x)
       +{
       +        USED(x);
       +        elogterm(w->body.file);
       +}
       +
       +void
       +alleditinit(Window *w, void *x)
       +{
       +        USED(x);
       +        textcommit(&w->tag, TRUE);
       +        textcommit(&w->body, TRUE);
       +        w->body.file->editclean = FALSE;
       +}
       +
       +void
       +allupdate(Window *w, void *x)
       +{
       +        Text *t;
       +        int i;
       +        File *f;
       +
       +        USED(x);
       +        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->u.listptr==0 || l->nalloc==0){
       +                l->nalloc = INCR;
       +                l->u.listptr = emalloc(INCR*sizeof(long));
       +                l->nused = 0;
       +        }else if(l->nused == l->nalloc){
       +                l->u.listptr = erealloc(l->u.listptr, (l->nalloc+INCR)*sizeof(long));
       +                memset((void*)(l->u.longptr+l->nalloc), 0, INCR*sizeof(long));
       +                l->nalloc += INCR;
       +        }
       +}
       +
       +/*
       + * Remove the ith element from the list
       + */
       +void
       +dellist(List *l, int i)
       +{
       +        memmove(&l->u.longptr[i], &l->u.longptr[i+1], (l->nused-(i+1))*sizeof(long));
       +        l->nused--;
       +}
       +
       +/*
       + * Add a new element, whose position is i, to the list
       + */
       +void
       +inslist(List *l, int i, long val)
       +{
       +        growlist(l);
       +        memmove(&l->u.longptr[i+1], &l->u.longptr[i], (l->nused-i)*sizeof(long));
       +        l->u.longptr[i] = val;
       +        l->nused++;
       +}
       +
       +void
       +listfree(List *l)
       +{
       +        free(l->u.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, (long)p);
       +        return p;
       +}
       +
       +String*
       +newstring(int n)
       +{
       +        String *p;
       +
       +        p = allocstring(n);
       +        inslist(&stringlist, stringlist.nused, (long)p);
       +        return p;
       +}
       +
       +Addr*
       +newaddr(void)
       +{
       +        Addr *p;
       +
       +        p = emalloc(sizeof(Addr));
       +        inslist(&addrlist, addrlist.nused, (long)p);
       +        return p;
       +}
       +
       +void
       +freecmd(void)
       +{
       +        int i;
       +
       +        while(cmdlist.nused > 0)
       +                free(cmdlist.u.ucharptr[--cmdlist.nused]);
       +        while(addrlist.nused > 0)
       +                free(addrlist.u.ucharptr[--addrlist.nused]);
       +        while(stringlist.nused>0){
       +                i = --stringlist.nused;
       +                freestring(stringlist.u.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';
       +        }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.u.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.u.text = newstring(0);
       +                                        getrhs(cmd.u.text, c, 's');
       +                                        if(nextc() == c){
       +                                                getch();
       +                                                if(nextc() == 'g')
       +                                                        cmd.flag = getch();
       +                                        }
       +                        
       +                                }
       +                        }
       +                }
       +                if(ct->addr && (cmd.u.mtaddr=simpleaddr())==0)
       +                        editerror("bad address");
       +                if(ct->defcmd){
       +                        if(cmdskipbl() == '\n'){
       +                                getch();
       +                                cmd.u.cmd = newcmd();
       +                                cmd.u.cmd->cmdc = ct->defcmd;
       +                        }else if((cmd.u.cmd = parsecmd(nest))==0)
       +                                error("defcmd");
       +                }else if(ct->text)
       +                        cmd.u.text = collecttext();
       +                else if(ct->token)
       +                        cmd.u.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.u.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.u.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.u.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.u.left = simpleaddr();
       +        if((addr.type = cmdskipbl())!=',' && addr.type!=';')
       +                return addr.u.left;
       +        getch();
       +        next = addr.next = compoundaddr();
       +        if(next && (next->type==',' || next->type==';') && next->u.left==0)
       +                editerror("bad address syntax");
       +        ap = newaddr();
       +        *ap = addr;
       +        return ap;
       +}
 (DIR) diff --git a/src/cmd/acme/edit.h b/src/cmd/acme/edit.h
       t@@ -0,0 +1,101 @@
       +/*#pragma        varargck        argpos        editerror        1*/
       +
       +typedef struct Addr        Addr;
       +typedef struct Address        Address;
       +typedef struct Cmd        Cmd;
       +typedef struct List        List;
       +typedef struct String        String;
       +
       +struct String
       +{
       +        int        n;                /* excludes NUL */
       +        Rune        *r;                /* includes NUL */
       +        int        nalloc;
       +};
       +
       +struct Addr
       +{
       +        char        type;        /* # (char addr), l (line addr), / ? . $ + - , ; */
       +        union{
       +                String        *re;
       +                Addr        *left;                /* left side of , and ; */
       +        } u;
       +        ulong        num;
       +        Addr        *next;                        /* or right side of , and ; */
       +};
       +
       +struct Address
       +{
       +        Range        r;
       +        File        *f;
       +};
       +
       +struct Cmd
       +{
       +        Addr        *addr;                        /* address (range of text) */
       +        String        *re;                        /* regular expression for e.g. 'x' */
       +        union{
       +                Cmd        *cmd;                /* target of x, g, {, etc. */
       +                String        *text;                /* text of a, c, i; rhs of s */
       +                Addr        *mtaddr;                /* address for m, t */
       +        } u;
       +        Cmd        *next;                        /* pointer to next element in {} */
       +        short        num;
       +        ushort        flag;                        /* whatever */
       +        ushort        cmdc;                        /* command character; 'x' etc. */
       +};
       +
       +extern struct cmdtab{
       +        ushort        cmdc;                /* command character */
       +        uchar        text;                /* takes a textual argument? */
       +        uchar        regexp;                /* takes a regular expression? */
       +        uchar        addr;                /* takes an address (m or t)? */
       +        uchar        defcmd;                /* default command; 0==>none */
       +        uchar        defaddr;        /* default address */
       +        uchar        count;                /* takes a count e.g. s2/// */
       +        char        *token;                /* takes text terminated by one of these */
       +        int        (*fn)(Text*, Cmd*);        /* function to call with parse tree */
       +}cmdtab[];
       +
       +#define        INCR        25        /* delta when growing list */
       +
       +struct List        /* code depends on a long being able to hold a pointer */
       +{
       +        int        nalloc;
       +        int        nused;
       +        union{
       +                void        *listptr;
       +                Block        *blkptr;
       +                long        *longptr;
       +                uchar*        *ucharptr;
       +                String*        *stringptr;
       +                File*        *fileptr;
       +        } u;
       +};
       +
       +enum Defaddr{        /* default addresses */
       +        aNo,
       +        aDot,
       +        aAll,
       +};
       +
       +int        nl_cmd(Text*, Cmd*), a_cmd(Text*, Cmd*), b_cmd(Text*, Cmd*);
       +int        c_cmd(Text*, Cmd*), d_cmd(Text*, Cmd*);
       +int        B_cmd(Text*, Cmd*), D_cmd(Text*, Cmd*), e_cmd(Text*, Cmd*);
       +int        f_cmd(Text*, Cmd*), g_cmd(Text*, Cmd*), i_cmd(Text*, Cmd*);
       +int        k_cmd(Text*, Cmd*), m_cmd(Text*, Cmd*), n_cmd(Text*, Cmd*);
       +int        p_cmd(Text*, Cmd*);
       +int        s_cmd(Text*, Cmd*), u_cmd(Text*, Cmd*), w_cmd(Text*, Cmd*);
       +int        x_cmd(Text*, Cmd*), X_cmd(Text*, Cmd*), pipe_cmd(Text*, Cmd*);
       +int        eq_cmd(Text*, Cmd*);
       +
       +String        *allocstring(int);
       +void                freestring(String*);
       +String        *getregexp(int);
       +Addr        *newaddr(void);
       +Address        cmdaddress(Addr*, Address, int);
       +int        cmdexec(Text*, Cmd*);
       +void        editerror(char*, ...);
       +int        cmdlookup(int);
       +void        resetxec(void);
       +void        Straddc(String*, int);
 (DIR) diff --git a/src/cmd/acme/elog.c b/src/cmd/acme/elog.c
       t@@ -0,0 +1,350 @@
       +#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 "fns.h"
       +#include "edit.h"
       +
       +static char Wsequence[] = "warning: changes out of sequence\n";
       +static int        warned = FALSE;
       +
       +/*
       + * Log of changes made by editing commands.  Three reasons for this:
       + * 1) We want addresses in commands to apply to old file, not file-in-change.
       + * 2) It's difficult to track changes correctly as things move, e.g. ,x m$
       + * 3) This gives an opportunity to optimize by merging adjacent changes.
       + * It's a little bit like the Undo/Redo log in Files, but Point 3) argues for a
       + * separate implementation.  To do this well, we use Replace as well as
       + * Insert and Delete
       + */
       +
       +typedef struct Buflog Buflog;
       +struct Buflog
       +{
       +        short        type;                /* Replace, Filename */
       +        uint                q0;                /* location of change (unused in f) */
       +        uint                nd;                /* # runes to delete */
       +        uint                nr;                /* # runes in string or file name */
       +};
       +
       +enum
       +{
       +        Buflogsize = sizeof(Buflog)/sizeof(Rune),
       +};
       +
       +/*
       + * Minstring shouldn't be very big or we will do lots of I/O for small changes.
       + * Maxstring is RBUFSIZE so we can fbufalloc() once and not realloc elog.r.
       + */
       +enum
       +{
       +        Minstring = 16,                /* distance beneath which we merge changes */
       +        Maxstring = RBUFSIZE,        /* maximum length of change we will merge into one */
       +};
       +
       +void
       +eloginit(File *f)
       +{
       +        if(f->elog.type != Empty)
       +                return;
       +        f->elog.type = Null;
       +        if(f->elogbuf == nil)
       +                f->elogbuf = emalloc(sizeof(Buffer));
       +        if(f->elog.r == nil)
       +                f->elog.r = fbufalloc();
       +        bufreset(f->elogbuf);
       +}
       +
       +void
       +elogclose(File *f)
       +{
       +        if(f->elogbuf){
       +                bufclose(f->elogbuf);
       +                free(f->elogbuf);
       +                f->elogbuf = nil;
       +        }
       +}
       +
       +void
       +elogreset(File *f)
       +{
       +        f->elog.type = Null;
       +        f->elog.nd = 0;
       +        f->elog.nr = 0;
       +}
       +
       +void
       +elogterm(File *f)
       +{
       +        elogreset(f);
       +        if(f->elogbuf)
       +                bufreset(f->elogbuf);
       +        f->elog.type = Empty;
       +        fbuffree(f->elog.r);
       +        f->elog.r = nil;
       +        warned = FALSE;
       +}
       +
       +void
       +elogflush(File *f)
       +{
       +        Buflog b;
       +
       +        b.type = f->elog.type;
       +        b.q0 = f->elog.q0;
       +        b.nd = f->elog.nd;
       +        b.nr = f->elog.nr;
       +        switch(f->elog.type){
       +        default:
       +                warning(nil, "unknown elog type 0x%ux\n", f->elog.type);
       +                break;
       +        case Null:
       +                break;
       +        case Insert:
       +        case Replace:
       +                if(f->elog.nr > 0)
       +                        bufinsert(f->elogbuf, f->elogbuf->nc, f->elog.r, f->elog.nr);
       +                /* fall through */
       +        case Delete:
       +                bufinsert(f->elogbuf, f->elogbuf->nc, (Rune*)&b, Buflogsize);
       +                break;
       +        }
       +        elogreset(f);
       +}
       +
       +void
       +elogreplace(File *f, int q0, int q1, Rune *r, int nr)
       +{
       +        uint gap;
       +
       +        if(q0==q1 && nr==0)
       +                return;
       +        eloginit(f);
       +        if(f->elog.type!=Null && q0<f->elog.q0){
       +                if(warned++ == 0)
       +                        warning(nil, Wsequence);
       +                elogflush(f);
       +        }
       +        /* try to merge with previous */
       +        gap = q0 - (f->elog.q0+f->elog.nd);        /* gap between previous and this */
       +        if(f->elog.type==Replace && f->elog.nr+gap+nr<Maxstring){
       +                if(gap < Minstring){
       +                        if(gap > 0){
       +                                bufread(&f->b, f->elog.q0+f->elog.nd, f->elog.r+f->elog.nr, gap);
       +                                f->elog.nr += gap;
       +                        }
       +                        f->elog.nd += gap + q1-q0;
       +                        runemove(f->elog.r+f->elog.nr, r, nr);
       +                        f->elog.nr += nr;
       +                        return;
       +                }
       +        }
       +        elogflush(f);
       +        f->elog.type = Replace;
       +        f->elog.q0 = q0;
       +        f->elog.nd = q1-q0;
       +        f->elog.nr = nr;
       +        if(nr > RBUFSIZE)
       +                editerror("internal error: replacement string too large(%d)", nr);
       +        runemove(f->elog.r, r, nr);
       +}
       +
       +void
       +eloginsert(File *f, int q0, Rune *r, int nr)
       +{
       +        int n;
       +
       +        if(nr == 0)
       +                return;
       +        eloginit(f);
       +        if(f->elog.type!=Null && q0<f->elog.q0){
       +                if(warned++ == 0)
       +                        warning(nil, Wsequence);
       +                elogflush(f);
       +        }
       +        /* try to merge with previous */
       +        if(f->elog.type==Insert && q0==f->elog.q0 && (q0+nr)-f->elog.q0<Maxstring){
       +                runemove(f->elog.r+f->elog.nr, r, nr);
       +                f->elog.nr += nr;
       +                return;
       +        }
       +        while(nr > 0){
       +                elogflush(f);
       +                f->elog.type = Insert;
       +                f->elog.q0 = q0;
       +                n = nr;
       +                if(n > RBUFSIZE)
       +                        n = RBUFSIZE;
       +                f->elog.nr = n;
       +                runemove(f->elog.r, r, n);
       +                r += n;
       +                nr -= n;
       +        }
       +}
       +
       +void
       +elogdelete(File *f, int q0, int q1)
       +{
       +        if(q0 == q1)
       +                return;
       +        eloginit(f);
       +        if(f->elog.type!=Null && q0<f->elog.q0+f->elog.nd){
       +                if(warned++ == 0)
       +                        warning(nil, Wsequence);
       +                elogflush(f);
       +        }
       +        /* try to merge with previous */
       +        if(f->elog.type==Delete && f->elog.q0+f->elog.nd==q0){
       +                f->elog.nd += q1-q0;
       +                return;
       +        }
       +        elogflush(f);
       +        f->elog.type = Delete;
       +        f->elog.q0 = q0;
       +        f->elog.nd = q1-q0;
       +}
       +
       +#define tracelog 0
       +void
       +elogapply(File *f)
       +{
       +        Buflog b;
       +        Rune *buf;
       +        uint i, n, up, mod;
       +        uint q0, q1, tq0, tq1;
       +        Buffer *log;
       +        Text *t;
       +
       +        elogflush(f);
       +        log = f->elogbuf;
       +        t = f->curtext;
       +
       +        buf = fbufalloc();
       +        mod = FALSE;
       +
       +        /*
       +         * The edit commands have already updated the selection in t->q0, t->q1.
       +         * (At least, they are supposed to have updated them.
       +         * We still keep finding commands that don't do it right.)
       +         * The textinsert and textdelete calls below will update it again, so save the
       +         * current setting and restore it at the end.
       +         */
       +        q0 = t->q0;
       +        q1 = t->q1;
       +        /*
       +         * We constrain the addresses in here (with textconstrain()) because
       +         * overlapping changes will generate bogus addresses.   We will warn
       +         * about changes out of sequence but proceed anyway; here we must
       +         * keep things in range.
       +         */
       +
       +        while(log->nc > 0){
       +                up = log->nc-Buflogsize;
       +                bufread(log, up, (Rune*)&b, Buflogsize);
       +                switch(b.type){
       +                default:
       +                        fprint(2, "elogapply: 0x%ux\n", b.type);
       +                        abort();
       +                        break;
       +
       +                case Replace:
       +                        if(tracelog)
       +                                warning(nil, "elog replace %d %d\n",
       +                                        b.q0, b.q0+b.nd);
       +                        if(!mod){
       +                                mod = TRUE;
       +                                filemark(f);
       +                        }
       +                        textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1);
       +                        textdelete(t, tq0, tq1, TRUE);
       +                        up -= b.nr;
       +                        for(i=0; i<b.nr; i+=n){
       +                                n = b.nr - i;
       +                                if(n > RBUFSIZE)
       +                                        n = RBUFSIZE;
       +                                bufread(log, up+i, buf, n);
       +                                textinsert(t, tq0+i, buf, n, TRUE);
       +                        }
       +                        break;
       +
       +                case Delete:
       +                        if(tracelog)
       +                                warning(nil, "elog delete %d %d\n",
       +                                        b.q0, b.q0+b.nd);
       +                        if(!mod){
       +                                mod = TRUE;
       +                                filemark(f);
       +                        }
       +                        textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1);
       +                        textdelete(t, tq0, tq1, TRUE);
       +                        break;
       +
       +                case Insert:
       +                        if(tracelog)
       +                                warning(nil, "elog insert %d %d\n",
       +                                        b.q0, b.q0+b.nr);
       +                        if(!mod){
       +                                mod = TRUE;
       +                                filemark(f);
       +                        }
       +                        textconstrain(t, b.q0, b.q0, &tq0, &tq1);
       +                        up -= b.nr;
       +                        for(i=0; i<b.nr; i+=n){
       +                                n = b.nr - i;
       +                                if(n > RBUFSIZE)
       +                                        n = RBUFSIZE;
       +                                bufread(log, up+i, buf, n);
       +                                textinsert(t, tq0+i, buf, n, TRUE);
       +                        }
       +                        break;
       +
       +/*                case Filename:
       +                        f->seq = u.seq;
       +                        fileunsetname(f, epsilon);
       +                        f->mod = u.mod;
       +                        up -= u.n;
       +                        free(f->name);
       +                        if(u.n == 0)
       +                                f->name = nil;
       +                        else
       +                                f->name = runemalloc(u.n);
       +                        bufread(delta, up, f->name, u.n);
       +                        f->nname = u.n;
       +                        break;
       +*/
       +                }
       +                bufdelete(log, up, log->nc);
       +        }
       +        fbuffree(buf);
       +        if(warned){
       +                /*
       +                 * Changes were out of order, so the q0 and q1
       +                 * computed while generating those changes are not
       +                 * to be trusted.
       +                 */
       +                q1 = min(q1, f->b.nc);
       +                q0 = min(q0, q1);
       +        }
       +        elogterm(f);
       +
       +        /*
       +         * The q0 and q1 are supposed to be fine (see comment
       +         * above, where we saved them), but bad addresses
       +         * will cause bufload to crash, so double check.
       +         */
       +        if(q0 > f->b.nc || q1 > f->b.nc || q0 > q1){
       +                warning(nil, "elogapply: can't happen %d %d %d\n", q0, q1, f->b.nc);
       +                q1 = min(q1, f->b.nc);
       +                q0 = min(q0, q1);
       +        }
       +
       +        t->q0 = q0;
       +        t->q1 = q1;
       +}
 (DIR) diff --git a/src/cmd/acme/exec.c b/src/cmd/acme/exec.c
       t@@ -0,0 +1,1491 @@
       +#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>
       +#define Fid FsFid
       +#include <fs.h>
       +#undef Fid
       +#include "dat.h"
       +#include "fns.h"
       +
       +Buffer        snarfbuf;
       +
       +void        del(Text*, Text*, Text*, int, int, Rune*, int);
       +void        delcol(Text*, Text*, Text*, int, int, Rune*, int);
       +void        dump(Text*, Text*, Text*, int, int, Rune*, int);
       +void        edit(Text*, Text*, Text*, int, int, Rune*, int);
       +void        xexit(Text*, Text*, Text*, int, int, Rune*, int);
       +void        fontx(Text*, Text*, Text*, int, int, Rune*, int);
       +void        get(Text*, Text*, Text*, int, int, Rune*, int);
       +void        id(Text*, Text*, Text*, int, int, Rune*, int);
       +void        incl(Text*, Text*, Text*, int, int, Rune*, int);
       +void        xkill(Text*, Text*, Text*, int, int, Rune*, int);
       +void        local(Text*, Text*, Text*, int, int, Rune*, int);
       +void        look(Text*, Text*, Text*, int, int, Rune*, int);
       +void        newcol(Text*, Text*, Text*, int, int, Rune*, int);
       +void        paste(Text*, Text*, Text*, int, int, Rune*, int);
       +void        put(Text*, Text*, Text*, int, int, Rune*, int);
       +void        putall(Text*, Text*, Text*, int, int, Rune*, int);
       +void        sendx(Text*, Text*, Text*, int, int, Rune*, int);
       +void        sort(Text*, Text*, Text*, int, int, Rune*, int);
       +void        tab(Text*, Text*, Text*, int, int, Rune*, int);
       +void        zeroxx(Text*, Text*, Text*, int, int, Rune*, int);
       +
       +typedef struct Exectab Exectab;
       +struct Exectab
       +{
       +        Rune        *name;
       +        void        (*fn)(Text*, Text*, Text*, int, int, Rune*, int);
       +        int                mark;
       +        int                flag1;
       +        int                flag2;
       +};
       +
       +static Rune LCut[] = { 'C', 'u', 't', 0 };
       +static Rune LDel[] = { 'D', 'e', 'l', 0 };
       +static Rune LDelcol[] = { 'D', 'e', 'l', 'c', 'o', 'l', 0 };
       +static Rune LDelete[] = { 'D', 'e', 'l', 'e', 't', 'e', 0 };
       +static Rune LDump[] = { 'D', 'u', 'm', 'p', 0 };
       +static Rune LEdit[] = { 'E', 'd', 'i', 't', 0 };
       +static Rune LExit[] = { 'E', 'x', 'i', 't', 0 };
       +static Rune LFont[] = { 'F', 'o', 'n', 't', 0 };
       +static Rune LGet[] = { 'G', 'e', 't', 0 };
       +static Rune LID[] = { 'I', 'D', 0 };
       +static Rune LIncl[] = { 'I', 'n', 'c', 'l', 0 };
       +static Rune LKill[] = { 'K', 'i', 'l', 'l', 0 };
       +static Rune LLoad[] = { 'L', 'o', 'a', 'd', 0 };
       +static Rune LLocal[] = { 'L', 'o', 'c', 'a', 'l', 0 };
       +static Rune LLook[] = { 'L', 'o', 'o', 'k', 0 };
       +static Rune LNew[] = { 'N', 'e', 'w', 0 };
       +static Rune LNewcol[] = { 'N', 'e', 'w', 'c', 'o', 'l', 0 };
       +static Rune LPaste[] = { 'P', 'a', 's', 't', 'e', 0 };
       +static Rune LPut[] = { 'P', 'u', 't', 0 };
       +static Rune LPutall[] = { 'P', 'u', 't', 'a', 'l', 'l', 0 };
       +static Rune LRedo[] = { 'R', 'e', 'd', 'o', 0 };
       +static Rune LSend[] = { 'S', 'e', 'n', 'd', 0 };
       +static Rune LSnarf[] = { 'S', 'n', 'a', 'r', 'f', 0 };
       +static Rune LSort[] = { 'S', 'o', 'r', 't', 0 };
       +static Rune LTab[] = { 'T', 'a', 'b', 0 };
       +static Rune LUndo[] = { 'U', 'n', 'd', 'o', 0 };
       +static Rune LZerox[] = { 'Z', 'e', 'r', 'o', 'x', 0 };
       +
       +Exectab exectab[] = {
       +        { LCut,                cut,                TRUE,        TRUE,        TRUE        },
       +        { LDel,                del,                FALSE,        FALSE,        XXX                },
       +        { LDelcol,        delcol,        FALSE,        XXX,                XXX                },
       +        { LDelete,        del,                FALSE,        TRUE,        XXX                },
       +        { LDump,        dump,        FALSE,        TRUE,        XXX                },
       +        { LEdit,                edit,                FALSE,        XXX,                XXX                },
       +        { LExit,                xexit,                FALSE,        XXX,                XXX                },
       +        { LFont,                fontx,        FALSE,        XXX,                XXX                },
       +        { LGet,                get,                FALSE,        TRUE,        XXX                },
       +        { LID,                id,                FALSE,        XXX,                XXX                },
       +        { LIncl,                incl,                FALSE,        XXX,                XXX                },
       +        { LKill,                xkill,                FALSE,        XXX,                XXX                },
       +        { LLoad,                dump,        FALSE,        FALSE,        XXX                },
       +        { LLocal,                local,        FALSE,        XXX,                XXX                },
       +        { LLook,                look,                FALSE,        XXX,                XXX                },
       +        { LNew,                new,                FALSE,        XXX,                XXX                },
       +        { LNewcol,        newcol,        FALSE,        XXX,                XXX                },
       +        { LPaste,                paste,        TRUE,        TRUE,        XXX                },
       +        { LPut,                put,                FALSE,        XXX,                XXX                },
       +        { LPutall,                putall,        FALSE,        XXX,                XXX                },
       +        { LRedo,                undo,        FALSE,        FALSE,        XXX                },
       +        { LSend,                sendx,        TRUE,        XXX,                XXX                },
       +        { LSnarf,                cut,                FALSE,        TRUE,        FALSE        },
       +        { LSort,                sort,                FALSE,        XXX,                XXX                },
       +        { LTab,                tab,                FALSE,        XXX,                XXX                },
       +        { LUndo,                undo,        FALSE,        TRUE,        XXX                },
       +        { LZerox,        zeroxx,        FALSE,        XXX,                XXX                },
       +        { nil,                         nil,                0,                0,                0                },
       +};
       +
       +Exectab*
       +lookup(Rune *r, int n)
       +{
       +        Exectab *e;
       +        int nr;
       +
       +        r = skipbl(r, n, &n);
       +        if(n == 0)
       +                return nil;
       +        findbl(r, n, &nr);
       +        nr = n-nr;
       +        for(e=exectab; e->name; e++)
       +                if(runeeq(r, nr, e->name, runestrlen(e->name)) == TRUE)
       +                        return e;
       +        return nil;
       +}
       +
       +int
       +isexecc(int c)
       +{
       +        if(isfilec(c))
       +                return 1;
       +        return c=='<' || c=='|' || c=='>';
       +}
       +
       +void
       +execute(Text *t, uint aq0, uint aq1, int external, Text *argt)
       +{
       +        uint q0, q1;
       +        Rune *r, *s;
       +        char *b, *a, *aa;
       +        Exectab *e;
       +        int c, n, f;
       +        Runestr dir;
       +
       +        q0 = aq0;
       +        q1 = aq1;
       +        if(q1 == q0){        /* expand to find word (actually file name) */
       +                /* if in selection, choose selection */
       +                if(t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){
       +                        q0 = t->q0;
       +                        q1 = t->q1;
       +                }else{
       +                        while(q1<t->file->b.nc && isexecc(c=textreadc(t, q1)) && c!=':')
       +                                q1++;
       +                        while(q0>0 && isexecc(c=textreadc(t, q0-1)) && c!=':')
       +                                q0--;
       +                        if(q1 == q0)
       +                                return;
       +                }
       +        }
       +        r = runemalloc(q1-q0);
       +        bufread(&t->file->b, q0, r, q1-q0);
       +        e = lookup(r, q1-q0);
       +        if(!external && t->w!=nil && t->w->nopen[QWevent]>0){
       +                f = 0;
       +                if(e)
       +                        f |= 1;
       +                if(q0!=aq0 || q1!=aq1){
       +                        bufread(&t->file->b, aq0, r, aq1-aq0);
       +                        f |= 2;
       +                }
       +                aa = getbytearg(argt, TRUE, TRUE, &a);
       +                if(a){        
       +                        if(strlen(a) > EVENTSIZE){        /* too big; too bad */
       +                                free(aa);
       +                                free(a);
       +                                warning(nil, "`argument string too long\n");
       +                                return;
       +                        }
       +                        f |= 8;
       +                }
       +                c = 'x';
       +                if(t->what == Body)
       +                        c = 'X';
       +                n = aq1-aq0;
       +                if(n <= EVENTSIZE)
       +                        winevent(t->w, "%c%d %d %d %d %.*S\n", c, aq0, aq1, f, n, n, r);
       +                else
       +                        winevent(t->w, "%c%d %d %d 0 \n", c, aq0, aq1, f, n);
       +                if(q0!=aq0 || q1!=aq1){
       +                        n = q1-q0;
       +                        bufread(&t->file->b, q0, r, n);
       +                        if(n <= EVENTSIZE)
       +                                winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q1, n, n, r);
       +                        else
       +                                winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1, n);
       +                }
       +                if(a){
       +                        winevent(t->w, "%c0 0 0 %d %s\n", c, utflen(a), a);
       +                        if(aa)
       +                                winevent(t->w, "%c0 0 0 %d %s\n", c, utflen(aa), aa);
       +                        else
       +                                winevent(t->w, "%c0 0 0 0 \n", c);
       +                }
       +                free(r);
       +                free(aa);
       +                free(a);
       +                return;
       +        }
       +        if(e){
       +                if(e->mark && seltext!=nil)
       +                if(seltext->what == Body){
       +                        seq++;
       +                        filemark(seltext->w->body.file);
       +                }
       +                s = skipbl(r, q1-q0, &n);
       +                s = findbl(s, n, &n);
       +                s = skipbl(s, n, &n);
       +                (*e->fn)(t, seltext, argt, e->flag1, e->flag2, s, n);
       +                free(r);
       +                return;
       +        }
       +
       +        b = runetobyte(r, q1-q0);
       +        free(r);
       +        dir = dirname(t, nil, 0);
       +        if(dir.nr==1 && dir.r[0]=='.'){        /* sigh */
       +                free(dir.r);
       +                dir.r = nil;
       +                dir.nr = 0;
       +        }
       +        aa = getbytearg(argt, TRUE, TRUE, &a);
       +        if(t->w)
       +                incref(&t->w->ref);
       +        run(t->w, b, dir.r, dir.nr, TRUE, aa, a, FALSE);
       +}
       +
       +char*
       +printarg(Text *argt, uint q0, uint q1)
       +{
       +        char *buf;
       +
       +        if(argt->what!=Body || argt->file->name==nil)
       +                return nil;
       +        buf = emalloc(argt->file->nname+32);
       +        if(q0 == q1)
       +                sprint(buf, "%.*S:#%d", argt->file->nname, argt->file->name, q0);
       +        else
       +                sprint(buf, "%.*S:#%d,#%d", argt->file->nname, argt->file->name, q0, q1);
       +        return buf;
       +}
       +
       +char*
       +getarg(Text *argt, int doaddr, int dofile, Rune **rp, int *nrp)
       +{
       +        int n;
       +        Expand e;
       +        char *a;
       +
       +        *rp = nil;
       +        *nrp = 0;
       +        if(argt == nil)
       +                return nil;
       +        a = nil;
       +        textcommit(argt, TRUE);
       +        if(expand(argt, argt->q0, argt->q1, &e)){
       +                free(e.bname);
       +                if(e.nname && dofile){
       +                        e.name = runerealloc(e.name, e.nname+1);
       +                        if(doaddr)
       +                                a = printarg(argt, e.q0, e.q1);
       +                        *rp = e.name;
       +                        *nrp = e.nname;
       +                        return a;
       +                }
       +                free(e.name);
       +        }else{
       +                e.q0 = argt->q0;
       +                e.q1 = argt->q1;
       +        }
       +        n = e.q1 - e.q0;
       +        *rp = runemalloc(n+1);
       +        bufread(&argt->file->b, e.q0, *rp, n);
       +        if(doaddr)
       +                a = printarg(argt, e.q0, e.q1);
       +        *nrp = n;
       +        return a;
       +}
       +
       +char*
       +getbytearg(Text *argt, int doaddr, int dofile, char **bp)
       +{
       +        Rune *r;
       +        int n;
       +        char *aa;
       +
       +        *bp = nil;
       +        aa = getarg(argt, doaddr, dofile, &r, &n);
       +        if(r == nil)
       +                return nil;
       +        *bp = runetobyte(r, n);
       +        free(r);
       +        return aa;
       +}
       +
       +void
       +newcol(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
       +{
       +        Column *c;
       +
       +        USED(_0);
       +        USED(_1);
       +        USED(_2);
       +        USED(_3);
       +        USED(_4);
       +        USED(_5);
       +
       +        c = rowadd(et->row, nil, -1);
       +        if(c)
       +                winsettag(coladd(c, nil, nil, -1));
       +}
       +
       +void
       +delcol(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
       +{
       +        int i;
       +        Column *c;
       +        Window *w;
       +
       +        USED(_0);
       +        USED(_1);
       +        USED(_2);
       +        USED(_3);
       +        USED(_4);
       +        USED(_5);
       +
       +        c = et->col;
       +        if(c==nil || colclean(c)==0)
       +                return;
       +        for(i=0; i<c->nw; i++){
       +                w = c->w[i];
       +                if(w->nopen[QWevent]+w->nopen[QWaddr]+w->nopen[QWdata] > 0){
       +                        warning(nil, "can't delete column; %.*S is running an external command\n", w->body.file->nname, w->body.file->name);
       +                        return;
       +                }
       +        }
       +        rowclose(et->col->row, et->col, TRUE);
       +}
       +
       +void
       +del(Text *et, Text *_0, Text *_1, int flag1, int _2, Rune *_3, int _4)
       +{
       +        USED(_0);
       +        USED(_1);
       +        USED(_2);
       +        USED(_3);
       +        USED(_4);
       +
       +        if(et->col==nil || et->w == nil)
       +                return;
       +        if(flag1 || et->w->body.file->ntext>1 || winclean(et->w, FALSE))
       +                colclose(et->col, et->w, TRUE);
       +}
       +
       +void
       +sort(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
       +{
       +        USED(_0);
       +        USED(_1);
       +        USED(_2);
       +        USED(_3);
       +        USED(_4);
       +        USED(_5);
       +
       +        if(et->col)
       +                colsort(et->col);
       +}
       +
       +uint
       +seqof(Window *w, int isundo)
       +{
       +        /* if it's undo, see who changed with us */
       +        if(isundo)
       +                return w->body.file->seq;
       +        /* if it's redo, see who we'll be sync'ed up with */
       +        return fileredoseq(w->body.file);
       +}
       +
       +void
       +undo(Text *et, Text *_0, Text *_1, int flag1, int _2, Rune *_3, int _4)
       +{
       +        int i, j;
       +        Column *c;
       +        Window *w;
       +        uint seq;
       +
       +        USED(_0);
       +        USED(_1);
       +        USED(_2);
       +        USED(_3);
       +        USED(_4);
       +
       +        if(et==nil || et->w== nil)
       +                return;
       +        seq = seqof(et->w, flag1);
       +        if(seq == 0){
       +                /* nothing to undo */
       +                return;
       +        }
       +        /*
       +         * Undo the executing window first. Its display will update. other windows
       +         * in the same file will not call show() and jump to a different location in the file.
       +         * Simultaneous changes to other files will be chaotic, however.
       +         */
       +        winundo(et->w, flag1);
       +        for(i=0; i<row.ncol; i++){
       +                c = row.col[i];
       +                for(j=0; j<c->nw; j++){
       +                        w = c->w[j];
       +                        if(w == et->w)
       +                                continue;
       +                        if(seqof(w, flag1) == seq)
       +                                winundo(w, flag1);
       +                }
       +        }
       +}
       +
       +char*
       +getname(Text *t, Text *argt, Rune *arg, int narg, int isput)
       +{
       +        char *s;
       +        Rune *r;
       +        int i, n, promote;
       +        Runestr dir;
       +
       +        getarg(argt, FALSE, TRUE, &r, &n);
       +        promote = FALSE;
       +        if(r == nil)
       +                promote = TRUE;
       +        else if(isput){
       +                /* if are doing a Put, want to synthesize name even for non-existent file */
       +                /* best guess is that file name doesn't contain a slash */
       +                promote = TRUE;
       +                for(i=0; i<n; i++)
       +                        if(r[i] == '/'){
       +                                promote = FALSE;
       +                                break;
       +                        }
       +                if(promote){
       +                        t = argt;
       +                        arg = r;
       +                        narg = n;
       +                }
       +        }
       +        if(promote){
       +                n = narg;
       +                if(n <= 0){
       +                        s = runetobyte(t->file->name, t->file->nname);
       +                        return s;
       +                }
       +                /* prefix with directory name if necessary */
       +                dir.r = nil;
       +                dir.nr = 0;
       +                if(n>0 && arg[0]!='/'){
       +                        dir = dirname(t, nil, 0);
       +                        if(n==1 && dir.r[0]=='.'){        /* sigh */
       +                                free(dir.r);
       +                                dir.r = nil;
       +                                dir.nr = 0;
       +                        }
       +                }
       +                if(dir.r){
       +                        r = runemalloc(dir.nr+n+1);
       +                        runemove(r, dir.r, dir.nr);
       +                        free(dir.r);
       +                        runemove(r+dir.nr, arg, n);
       +                        n += dir.nr;
       +                }else{
       +                        r = runemalloc(n+1);
       +                        runemove(r, arg, n);
       +                }
       +        }
       +        s = runetobyte(r, n);
       +        free(r);
       +        if(strlen(s) == 0){
       +                free(s);
       +                s = nil;
       +        }
       +        return s;
       +}
       +
       +void
       +zeroxx(Text *et, Text *t, Text *_1, int _2, int _3, Rune *_4, int _5)
       +{
       +        Window *nw;
       +        int c, locked;
       +
       +        USED(_1);
       +        USED(_2);
       +        USED(_3);
       +        USED(_4);
       +        USED(_5);
       +
       +        locked = FALSE;
       +        if(t!=nil && t->w!=nil && t->w!=et->w){
       +                locked = TRUE;
       +                c = 'M';
       +                if(et->w)
       +                        c = et->w->owner;
       +                winlock(t->w, c);
       +        }
       +        if(t == nil)
       +                t = et;
       +        if(t==nil || t->w==nil)
       +                return;
       +        t = &t->w->body;
       +        if(t->w->isdir)
       +                warning(nil, "%.*S is a directory; Zerox illegal\n", t->file->nname, t->file->name);
       +        else{
       +                nw = coladd(t->w->col, nil, t->w, -1);
       +                /* ugly: fix locks so w->unlock works */
       +                winlock1(nw, t->w->owner);
       +        }
       +        if(locked)
       +                winunlock(t->w);
       +}
       +
       +void
       +get(Text *et, Text *t, Text *argt, int flag1, int _0, Rune *arg, int narg)
       +{
       +        char *name;
       +        Rune *r;
       +        int i, n, dirty, samename, isdir;
       +        Window *w;
       +        Text *u;
       +        Dir *d;
       +
       +        USED(_0);
       +
       +        if(flag1)
       +                if(et==nil || et->w==nil)
       +                        return;
       +        if(!et->w->isdir && (et->w->body.file->b.nc>0 && !winclean(et->w, TRUE)))
       +                return;
       +        w = et->w;
       +        t = &w->body;
       +        name = getname(t, argt, arg, narg, FALSE);
       +        if(name == nil){
       +                warning(nil, "no file name\n");
       +                return;
       +        }
       +        if(t->file->ntext>1){
       +                d = dirstat(name);
       +                isdir = (d!=nil && (d->qid.type & QTDIR));
       +                free(d);
       +                if(isdir)
       +                        warning(nil, "%s is a directory; can't read with multiple windows on it\n", name);
       +                return;
       +        }
       +        r = bytetorune(name, &n);
       +        for(i=0; i<t->file->ntext; i++){
       +                u = t->file->text[i];
       +                /* second and subsequent calls with zero an already empty buffer, but OK */
       +                textreset(u);
       +                windirfree(u->w);
       +        }
       +        samename = runeeq(r, n, t->file->name, t->file->nname);
       +        textload(t, 0, name, samename);
       +        if(samename){
       +                t->file->mod = FALSE;
       +                dirty = FALSE;
       +        }else{
       +                t->file->mod = TRUE;
       +                dirty = TRUE;
       +        }
       +        for(i=0; i<t->file->ntext; i++)
       +                t->file->text[i]->w->dirty = dirty;
       +        free(name);
       +        free(r);
       +        winsettag(w);
       +        t->file->unread = FALSE;
       +        for(i=0; i<t->file->ntext; i++){
       +                u = t->file->text[i];
       +                textsetselect(&u->w->tag, u->w->tag.file->b.nc, u->w->tag.file->b.nc);
       +                textscrdraw(u);
       +        }
       +}
       +
       +void
       +putfile(File *f, int q0, int q1, Rune *namer, int nname)
       +{
       +        uint n, m;
       +        Rune *r;
       +        char *s, *name;
       +        int i, fd, q;
       +        Dir *d, *d1;
       +        Window *w;
       +        int isapp;
       +
       +        w = f->curtext->w;
       +        name = runetobyte(namer, nname);
       +        d = dirstat(name);
       +        if(d!=nil && runeeq(namer, nname, f->name, f->nname)){
       +                /* f->mtime+1 because when talking over NFS it's often off by a second */
       +                if(f->dev!=d->dev || f->qidpath!=d->qid.path || f->mtime+1<d->mtime){
       +                        f->dev = d->dev;
       +                        f->qidpath = d->qid.path;
       +                        f->mtime = d->mtime;
       +                        if(f->unread)
       +                                warningew(w, nil, "%s not written; file already exists\n", name);
       +                        else
       +                                warningew(w, nil, "%s modified%s%s since last read\n", name, d->muid[0]?" by ":"", d->muid);
       +                        goto Rescue1;
       +                }
       +        }
       +        fd = create(name, OWRITE, 0666);
       +        if(fd < 0){
       +                warningew(w, nil, "can't create file %s: %r\n", name);
       +                goto Rescue1;
       +        }
       +        r = fbufalloc();
       +        s = fbufalloc();
       +        free(d);
       +        d = dirfstat(fd);
       +        isapp = (d!=nil && d->length>0 && (d->qid.type&QTAPPEND));
       +        if(isapp){
       +                warningew(w, nil, "%s not written; file is append only\n", name);
       +                goto Rescue2;
       +        }
       +
       +        for(q=q0; q<q1; q+=n){
       +                n = q1 - q;
       +                if(n > BUFSIZE/UTFmax)
       +                        n = BUFSIZE/UTFmax;
       +                bufread(&f->b, q, r, n);
       +                m = snprint(s, BUFSIZE+1, "%.*S", n, r);
       +                if(write(fd, s, m) != m){
       +                        warningew(w, nil, "can't write file %s: %r\n", name);
       +                        goto Rescue2;
       +                }
       +        }
       +        if(runeeq(namer, nname, f->name, f->nname)){
       +                if(q0!=0 || q1!=f->b.nc){
       +                        f->mod = TRUE;
       +                        w->dirty = TRUE;
       +                        f->unread = TRUE;
       +                }else{
       +                        d1 = dirfstat(fd);
       +                        if(d1 != nil){
       +                                free(d);
       +                                d = d1;
       +                        }
       +                        f->qidpath = d->qid.path;
       +                        f->dev = d->dev;
       +                        f->mtime = d->mtime;
       +                        f->mod = FALSE;
       +                        w->dirty = FALSE;
       +                        f->unread = FALSE;
       +                }
       +                for(i=0; i<f->ntext; i++){
       +                        f->text[i]->w->putseq = f->seq;
       +                        f->text[i]->w->dirty = w->dirty;
       +                }
       +        }
       +        fbuffree(s);
       +        fbuffree(r);
       +        free(d);
       +        free(namer);
       +        free(name);
       +        close(fd);
       +        winsettag(w);
       +        return;
       +
       +    Rescue2:
       +        fbuffree(s);
       +        fbuffree(r);
       +        close(fd);
       +        /* fall through */
       +
       +    Rescue1:
       +        free(d);
       +        free(namer);
       +        free(name);
       +}
       +
       +void
       +put(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg)
       +{
       +        int nname;
       +        Rune  *namer;
       +        Window *w;
       +        File *f;
       +        char *name;
       +
       +        USED(_0);
       +        USED(_1);
       +        USED(_2);
       +
       +        if(et==nil || et->w==nil || et->w->isdir)
       +                return;
       +        w = et->w;
       +        f = w->body.file;
       +        name = getname(&w->body, argt, arg, narg, TRUE);
       +        if(name == nil){
       +                warningew(w, nil, "no file name\n");
       +                return;
       +        }
       +        namer = bytetorune(name, &nname);
       +        putfile(f, 0, f->b.nc, namer, nname);
       +        free(name);
       +}
       +
       +void
       +dump(Text *_0, Text *_1, Text *argt, int isdump, int _2, Rune *arg, int narg)
       +{
       +        char *name;
       +
       +        USED(_0);
       +        USED(_1);
       +        USED(_2);
       +
       +        if(narg)
       +                name = runetobyte(arg, narg);
       +        else
       +                getbytearg(argt, FALSE, TRUE, &name);
       +        if(isdump)
       +                rowdump(&row, name);
       +        else
       +                rowload(&row, name, FALSE);
       +        free(name);
       +}
       +
       +void
       +cut(Text *et, Text *t, Text *_0, int dosnarf, int docut, Rune *_2, int _3)
       +{
       +        uint q0, q1, n, locked, c;
       +        Rune *r;
       +
       +        USED(_0);
       +        USED(_2);
       +        USED(_3);
       +
       +        /* use current window if snarfing and its selection is non-null */
       +        if(et!=t && dosnarf && et->w!=nil){
       +                if(et->w->body.q1>et->w->body.q0){
       +                        t = &et->w->body;
       +                        if(docut)
       +                                filemark(t->file);        /* seq has been incremented by execute */
       +                }else if(et->w->tag.q1>et->w->tag.q0)
       +                        t = &et->w->tag;
       +        }
       +        if(t == nil){
       +                /* can only happen if seltext == nil */
       +                return;
       +        }
       +        locked = FALSE;
       +        if(t->w!=nil && et->w!=t->w){
       +                locked = TRUE;
       +                c = 'M';
       +                if(et->w)
       +                        c = et->w->owner;
       +                winlock(t->w, c);
       +        }
       +        if(t->q0 == t->q1){
       +                if(locked)
       +                        winunlock(t->w);
       +                return;
       +        }
       +        if(dosnarf){
       +                q0 = t->q0;
       +                q1 = t->q1;
       +                bufdelete(&snarfbuf, 0, snarfbuf.nc);
       +                r = fbufalloc();
       +                while(q0 < q1){
       +                        n = q1 - q0;
       +                        if(n > RBUFSIZE)
       +                                n = RBUFSIZE;
       +                        bufread(&t->file->b, q0, r, n);
       +                        bufinsert(&snarfbuf, snarfbuf.nc, r, n);
       +                        q0 += n;
       +                }
       +                fbuffree(r);
       +                acmeputsnarf();
       +        }
       +        if(docut){
       +                textdelete(t, t->q0, t->q1, TRUE);
       +                textsetselect(t, t->q0, t->q0);
       +                if(t->w){
       +                        textscrdraw(t);
       +                        winsettag(t->w);
       +                }
       +        }else if(dosnarf)        /* Snarf command */
       +                argtext = t;
       +        if(locked)
       +                winunlock(t->w);
       +}
       +
       +void
       +paste(Text *et, Text *t, Text *_0, int selectall, int tobody, Rune *_1, int _2)
       +{
       +        int c;
       +        uint q, q0, q1, n;
       +        Rune *r;
       +
       +        USED(_0);
       +        USED(_1);
       +        USED(_2);
       +
       +        /* if(tobody), use body of executing window  (Paste or Send command) */
       +        if(tobody && et!=nil && et->w!=nil){
       +                t = &et->w->body;
       +                filemark(t->file);        /* seq has been incremented by execute */
       +        }
       +        if(t == nil)
       +                return;
       +
       +        acmegetsnarf();
       +        if(t==nil || snarfbuf.nc==0)
       +                return;
       +        if(t->w!=nil && et->w!=t->w){
       +                c = 'M';
       +                if(et->w)
       +                        c = et->w->owner;
       +                winlock(t->w, c);
       +        }
       +        cut(t, t, nil, FALSE, TRUE, nil, 0);
       +        q = 0;
       +        q0 = t->q0;
       +        q1 = t->q0+snarfbuf.nc;
       +        r = fbufalloc();
       +        while(q0 < q1){
       +                n = q1 - q0;
       +                if(n > RBUFSIZE)
       +                        n = RBUFSIZE;
       +                if(r == nil)
       +                        r = runemalloc(n);
       +                bufread(&snarfbuf, q, r, n);
       +                textinsert(t, q0, r, n, TRUE);
       +                q += n;
       +                q0 += n;
       +        }
       +        fbuffree(r);
       +        if(selectall)
       +                textsetselect(t, t->q0, q1);
       +        else
       +                textsetselect(t, q1, q1);
       +        if(t->w){
       +                textscrdraw(t);
       +                winsettag(t->w);
       +        }
       +        if(t->w!=nil && et->w!=t->w)
       +                winunlock(t->w);
       +}
       +
       +void
       +look(Text *et, Text *t, Text *argt, int _0, int _1, Rune *arg, int narg)
       +{
       +        Rune *r;
       +        int n;
       +
       +        USED(_0);
       +        USED(_1);
       +
       +        if(et && et->w){
       +                t = &et->w->body;
       +                if(narg > 0){
       +                        search(t, arg, narg);
       +                        return;
       +                }
       +                getarg(argt, FALSE, FALSE, &r, &n);
       +                if(r == nil){
       +                        n = t->q1-t->q0;
       +                        r = runemalloc(n);
       +                        bufread(&t->file->b, t->q0, r, n);
       +                }
       +                search(t, r, n);
       +                free(r);
       +        }
       +}
       +
       +static Rune Lnl[] = { '\n', 0 };
       +
       +void
       +sendx(Text *et, Text *t, Text *_0, int _1, int _2, Rune *_3, int _4)
       +{
       +        USED(_0);
       +        USED(_1);
       +        USED(_2);
       +        USED(_3);
       +        USED(_4);
       +
       +        if(et->w==nil)
       +                return;
       +        t = &et->w->body;
       +        if(t->q0 != t->q1)
       +                cut(t, t, nil, TRUE, FALSE, nil, 0);
       +        textsetselect(t, t->file->b.nc, t->file->b.nc);
       +        paste(t, t, nil, TRUE, TRUE, nil, 0);
       +        if(textreadc(t, t->file->b.nc-1) != '\n'){
       +                textinsert(t, t->file->b.nc, Lnl, 1, TRUE);
       +                textsetselect(t, t->file->b.nc, t->file->b.nc);
       +        }
       +}
       +
       +void
       +edit(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg)
       +{
       +        Rune *r;
       +        int len;
       +
       +        USED(_0);
       +        USED(_1);
       +        USED(_2);
       +
       +        if(et == nil)
       +                return;
       +        getarg(argt, FALSE, TRUE, &r, &len);
       +        seq++;
       +        if(r != nil){
       +                editcmd(et, r, len);
       +                free(r);
       +        }else
       +                editcmd(et, arg, narg);
       +}
       +
       +void
       +xexit(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
       +{
       +        USED(et);
       +        USED(_0);
       +        USED(_1);
       +        USED(_2);
       +        USED(_3);
       +        USED(_4);
       +        USED(_5);
       +
       +        if(rowclean(&row)){
       +                sendul(cexit, 0);
       +                threadexits(nil);
       +        }
       +}
       +
       +void
       +putall(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
       +{
       +        int i, j, e;
       +        Window *w;
       +        Column *c;
       +        char *a;
       +
       +        USED(et);
       +        USED(_0);
       +        USED(_1);
       +        USED(_2);
       +        USED(_3);
       +        USED(_4);
       +        USED(_5);
       +
       +        for(i=0; i<row.ncol; i++){
       +                c = row.col[i];
       +                for(j=0; j<c->nw; j++){
       +                        w = c->w[j];
       +                        if(w->isscratch || w->isdir || w->body.file->nname==0)
       +                                continue;
       +                        if(w->nopen[QWevent] > 0)
       +                                continue;
       +                        a = runetobyte(w->body.file->name, w->body.file->nname);
       +                        e = access(a, 0);
       +                        if(w->body.file->mod || w->body.ncache)
       +                                if(e < 0)
       +                                        warning(nil, "no auto-Put of %s: %r\n", a);
       +                                else{
       +                                        wincommit(w, &w->body);
       +                                        put(&w->body, nil, nil, XXX, XXX, nil, 0);
       +                                }
       +                        free(a);
       +                }
       +        }
       +}
       +
       +
       +void
       +id(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
       +{
       +        USED(et);
       +        USED(_0);
       +        USED(_1);
       +        USED(_2);
       +        USED(_3);
       +        USED(_4);
       +        USED(_5);
       +
       +        if(et && et->w)
       +                warning(nil, "/mnt/acme/%d/\n", et->w->id);
       +}
       +
       +void
       +local(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg)
       +{
       +        char *a, *aa;
       +        Runestr dir;
       +
       +        USED(_0);
       +        USED(_1);
       +        USED(_2);
       +
       +        aa = getbytearg(argt, TRUE, TRUE, &a);
       +
       +        dir = dirname(et, nil, 0);
       +        if(dir.nr==1 && dir.r[0]=='.'){        /* sigh */
       +                free(dir.r);
       +                dir.r = nil;
       +                dir.nr = 0;
       +        }
       +        run(nil, runetobyte(arg, narg), dir.r, dir.nr, FALSE, aa, a, FALSE);
       +}
       +
       +void
       +xkill(Text *_0, Text *_1, Text *argt, int _2, int _3, Rune *arg, int narg)
       +{
       +        Rune *a, *cmd, *r;
       +        int na;
       +
       +        USED(_0);
       +        USED(_1);
       +        USED(_2);
       +        USED(_3);
       +
       +        getarg(argt, FALSE, FALSE, &r, &na);
       +        if(r)
       +                xkill(nil, nil, nil, 0, 0, r, na);
       +        /* loop condition: *arg is not a blank */
       +        for(;;){
       +                a = findbl(arg, narg, &na);
       +                if(a == arg)
       +                        break;
       +                cmd = runemalloc(narg-na+1);
       +                runemove(cmd, arg, narg-na);
       +                sendp(ckill, cmd);
       +                arg = skipbl(a, na, &narg);
       +        }
       +}
       +
       +static Rune Lfix[] = { 'f', 'i', 'x', 0 };
       +static Rune Lvar[] = { 'v', 'a', 'r', 0 };
       +
       +void
       +fontx(Text *et, Text *t, Text *argt, int _0, int _1, Rune *arg, int narg)
       +{
       +        Rune *a, *r, *flag, *file;
       +        int na, nf;
       +        char *aa;
       +        Reffont *newfont;
       +        Dirlist *dp;
       +        int i, fix;
       +
       +        USED(_0);
       +        USED(_1);
       +
       +        if(et==nil || et->w==nil)
       +                return;
       +        t = &et->w->body;
       +        flag = nil;
       +        file = nil;
       +        /* loop condition: *arg is not a blank */
       +        nf = 0;
       +        for(;;){
       +                a = findbl(arg, narg, &na);
       +                if(a == arg)
       +                        break;
       +                r = runemalloc(narg-na+1);
       +                runemove(r, arg, narg-na);
       +                if(runeeq(r, narg-na, Lfix, 3) || runeeq(r, narg-na, Lvar, 3)){
       +                        free(flag);
       +                        flag = r;
       +                }else{
       +                        free(file);
       +                        file = r;
       +                        nf = narg-na;
       +                }
       +                arg = skipbl(a, na, &narg);
       +        }
       +        getarg(argt, FALSE, TRUE, &r, &na);
       +        if(r)
       +                if(runeeq(r, na, Lfix, 3) || runeeq(r, na, Lvar, 3)){
       +                        free(flag);
       +                        flag = r;
       +                }else{
       +                        free(file);
       +                        file = r;
       +                        nf = na;
       +                }
       +        fix = 1;
       +        if(flag)
       +                fix = runeeq(flag, runestrlen(flag), Lfix, 3);
       +        else if(file == nil){
       +                newfont = rfget(FALSE, FALSE, FALSE, nil);
       +                if(newfont)
       +                        fix = strcmp(newfont->f->name, t->fr.font->name)==0;
       +        }
       +        if(file){
       +                aa = runetobyte(file, nf);
       +                newfont = rfget(fix, flag!=nil, FALSE, aa);
       +                free(aa);
       +        }else
       +                newfont = rfget(fix, FALSE, FALSE, nil);
       +        if(newfont){
       +                draw(screen, t->w->r, textcols[BACK], nil, ZP);
       +                rfclose(t->reffont);
       +                t->reffont = newfont;
       +                t->fr.font = newfont->f;
       +                frinittick(&t->fr);
       +                if(t->w->isdir){
       +                        t->all.min.x++;        /* force recolumnation; disgusting! */
       +                        for(i=0; i<t->w->ndl; i++){
       +                                dp = t->w->dlp[i];
       +                                aa = runetobyte(dp->r, dp->nr);
       +                                dp->wid = stringwidth(newfont->f, aa);
       +                                free(aa);
       +                        }
       +                }
       +                /* avoid shrinking of window due to quantization */
       +                colgrow(t->w->col, t->w, -1);
       +        }
       +        free(file);
       +        free(flag);
       +}
       +
       +void
       +incl(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg)
       +{
       +        Rune *a, *r;
       +        Window *w;
       +        int na, n, len;
       +
       +        USED(_0);
       +        USED(_1);
       +        USED(_2);
       +
       +        if(et==nil || et->w==nil)
       +                return;
       +        w = et->w;
       +        n = 0;
       +        getarg(argt, FALSE, TRUE, &r, &len);
       +        if(r){
       +                n++;
       +                winaddincl(w, r, len);
       +        }
       +        /* loop condition: *arg is not a blank */
       +        for(;;){
       +                a = findbl(arg, narg, &na);
       +                if(a == arg)
       +                        break;
       +                r = runemalloc(narg-na+1);
       +                runemove(r, arg, narg-na);
       +                n++;
       +                winaddincl(w, r, narg-na);
       +                arg = skipbl(a, na, &narg);
       +        }
       +        if(n==0 && w->nincl){
       +                for(n=w->nincl; --n>=0; )
       +                        warning(nil, "%S ", w->incl[n]);
       +                warning(nil, "\n");
       +        }
       +}
       +
       +void
       +tab(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg)
       +{
       +        Rune *a, *r;
       +        Window *w;
       +        int na, len, tab;
       +        char *p;
       +
       +        USED(_0);
       +        USED(_1);
       +        USED(_2);
       +
       +        if(et==nil || et->w==nil)
       +                return;
       +        w = et->w;
       +        getarg(argt, FALSE, TRUE, &r, &len);
       +        tab = 0;
       +        if(r!=nil && len>0){
       +                p = runetobyte(r, len);
       +                if('0'<=p[0] && p[0]<='9')
       +                        tab = atoi(p);
       +                free(p);
       +        }else{
       +                a = findbl(arg, narg, &na);
       +                if(a != arg){
       +                        p = runetobyte(arg, narg-na);
       +                        if('0'<=p[0] && p[0]<='9')
       +                                tab = atoi(p);
       +                        free(p);
       +                }
       +        }
       +        if(tab > 0){
       +                if(w->body.tabstop != tab){
       +                        w->body.tabstop = tab;
       +                        winresize(w, w->r, 1);
       +                }
       +        }else
       +                warning(nil, "%.*S: Tab %d\n", w->body.file->nname, w->body.file->name, w->body.tabstop);
       +}
       +
       +void
       +runproc(void *argvp)
       +{
       +        /* args: */
       +                Window *win;
       +                char *s;
       +                Rune *rdir;
       +                int ndir;
       +                int newns;
       +                char *argaddr;
       +                char *arg;
       +                Command *c;
       +                Channel *cpid;
       +                int iseditcmd;
       +        /* end of args */
       +        char *e, *t, *name, *filename, *dir, **av, *news;
       +        Rune r, **incl;
       +        int ac, w, inarg, i, n, fd, nincl, winid;
       +        int sfd[3];
       +        int pipechar;
       +        char buf[512];
       +        //static void *parg[2];
       +        void **argv;
       +        Fsys *fs;
       +
       +        argv = argvp;
       +        win = argv[0];
       +        s = argv[1];
       +        rdir = argv[2];
       +        ndir = (int)argv[3];
       +        newns = (int)argv[4];
       +        argaddr = argv[5];
       +        arg = argv[6];
       +        c = argv[7];
       +        cpid = argv[8];
       +        iseditcmd = (int)argv[9];
       +        free(argv);
       +
       +        t = s;
       +        while(*t==' ' || *t=='\n' || *t=='\t')
       +                t++;
       +        for(e=t; *e; e++)
       +                if(*e==' ' || *e=='\n' || *e=='\t' )
       +                        break;
       +        name = emalloc((e-t)+2);
       +        memmove(name, t, e-t);
       +        name[e-t] = 0;
       +        e = utfrrune(name, '/');
       +        if(e)
       +                strcpy(name, e+1);
       +        strcat(name, " ");        /* add blank here for ease in waittask */
       +        c->name = bytetorune(name, &c->nname);
       +        free(name);
       +        pipechar = 0;
       +        if(*t=='<' || *t=='|' || *t=='>')
       +                pipechar = *t++;
       +        c->iseditcmd = iseditcmd;
       +        c->text = s;
       +        if(rdir != nil){
       +                dir = runetobyte(rdir, ndir);
       +                chdir(dir);        /* ignore error: probably app. window */
       +                free(dir);
       +        }
       +        if(newns){
       +                nincl = 0;
       +                incl = nil;
       +                if(win){
       +                        filename = smprint("%.*S", win->body.file->nname, win->body.file->name);
       +                        nincl = win->nincl;
       +                        if(nincl > 0){
       +                                incl = emalloc(nincl*sizeof(Rune*));
       +                                for(i=0; i<nincl; i++){
       +                                        n = runestrlen(win->incl[i]);
       +                                        incl[i] = runemalloc(n+1);
       +                                        runemove(incl[i], win->incl[i], n);
       +                                }
       +                        }
       +                        winid = win->id;
       +                }else{
       +                        filename = nil;
       +                        winid = 0;
       +                        if(activewin)
       +                                winid = activewin->id;
       +                }
       +                rfork(RFNAMEG|RFENVG|RFFDG|RFNOTEG);
       +                sprint(buf, "%d", winid);
       +                putenv("winid", buf);
       +
       +                if(filename){
       +                        putenv("%", filename);
       +                        free(filename);
       +                }
       +                c->md = fsysmount(rdir, ndir, incl, nincl);
       +                if(c->md == nil){
       +                        fprint(2, "child: can't allocate mntdir: %r\n");
       +                        threadexits("fsysmount");
       +                }
       +                sprint(buf, "%d", c->md->id);
       +                if((fs = nsmount("acme", buf)) == nil){
       +                        fprint(2, "child: can't mount acme: %r\n");
       +                        fsysdelid(c->md);
       +                        c->md = nil;
       +                        threadexits("nsmount");
       +                }
       +                if(winid>0 && (pipechar=='|' || pipechar=='>')){
       +                        sprint(buf, "%d/rdsel", winid);
       +                        sfd[0] = fsopenfd(fs, buf, OREAD);
       +                }else
       +                        sfd[0] = open("/dev/null", OREAD);
       +                if((winid>0 || iseditcmd) && (pipechar=='|' || pipechar=='<')){
       +                        if(iseditcmd){
       +                                if(winid > 0)
       +                                        sprint(buf, "%d/editout", winid);
       +                                else
       +                                        sprint(buf, "editout");
       +                        }else
       +                                sprint(buf, "%d/wrsel", winid);
       +                        sfd[1] = fsopenfd(fs, buf, OWRITE);
       +                        sfd[2] = fsopenfd(fs, "cons", OWRITE);
       +                }else{
       +                        sfd[1] = fsopenfd(fs, "cons", OWRITE);
       +                        sfd[2] = sfd[1];
       +                }
       +                fsunmount(fs);
       +        }else{
       +                rfork(RFFDG|RFNOTEG);
       +                fsysclose();
       +                sfd[0] = open("/dev/null", OREAD);
       +                sfd[1] = open("/dev/null", OWRITE);
       +                sfd[2] = dup(erroutfd, -1);
       +        }
       +        if(win)
       +                winclose(win);
       +
       +        if(argaddr)
       +                putenv("acmeaddr", argaddr);
       +        if(strlen(t) > sizeof buf-10)        /* may need to print into stack */
       +                goto Hard;
       +        inarg = FALSE;
       +        for(e=t; *e; e+=w){
       +                w = chartorune(&r, e);
       +                if(r==' ' || r=='\t')
       +                        continue;
       +                if(r < ' ')
       +                        goto Hard;
       +                if(utfrune("#;&|^$=`'{}()<>[]*?^~`", r))
       +                        goto Hard;
       +                inarg = TRUE;
       +        }
       +        if(!inarg)
       +                goto Fail;
       +
       +        ac = 0;
       +        av = nil;
       +        inarg = FALSE;
       +        for(e=t; *e; e+=w){
       +                w = chartorune(&r, e);
       +                if(r==' ' || r=='\t'){
       +                        inarg = FALSE;
       +                        *e = 0;
       +                        continue;
       +                }
       +                if(!inarg){
       +                        inarg = TRUE;
       +                        av = realloc(av, (ac+1)*sizeof(char**));
       +                        av[ac++] = e;
       +                }
       +        }
       +        av = realloc(av, (ac+2)*sizeof(char**));
       +        av[ac++] = arg;
       +        av[ac] = nil;
       +        c->av = av;
       +        procexec(cpid, sfd, av[0], av);
       +/* libthread uses execvp so no need to do this */
       +#if 0
       +        e = av[0];
       +        if(e[0]=='/' || (e[0]=='.' && e[1]=='/'))
       +                goto Fail;
       +        if(cputype){
       +                sprint(buf, "%s/%s", cputype, av[0]);
       +                procexec(cpid, sfd, buf, av);
       +        }
       +        sprint(buf, "/bin/%s", av[0]);
       +        procexec(cpid, sfd, buf, av);
       +#endif
       +        goto Fail;
       +
       +Hard:
       +
       +        /*
       +         * ugly: set path = (. $cputype /bin)
       +         * should honor $path if unusual.
       +         */
       +        if(cputype){
       +                n = 0;
       +                memmove(buf+n, ".", 2);
       +                n += 2;
       +                i = strlen(cputype)+1;
       +                memmove(buf+n, cputype, i);
       +                n += i;
       +                memmove(buf+n, "/bin", 5);
       +                n += 5;
       +                fd = create("/env/path", OWRITE, 0666);
       +                write(fd, buf, n);
       +                close(fd);
       +        }
       +
       +        if(arg){
       +                news = emalloc(strlen(t) + 1 + 1 + strlen(arg) + 1 + 1);
       +                if(news){
       +                        sprint(news, "%s '%s'", t, arg);        /* BUG: what if quote in arg? */
       +                        free(s);
       +                        t = news;
       +                        c->text = news;
       +                }
       +        }
       +        procexecl(cpid, sfd, "rc", "rc", "-c", t, nil);
       +
       +   Fail:
       +        /* procexec hasn't happened, so send a zero */
       +        close(sfd[0]);
       +        close(sfd[1]);
       +        if(sfd[2] != sfd[1])
       +                close(sfd[2]);
       +        sendul(cpid, 0);
       +        threadexits(nil);
       +}
       +
       +void
       +runwaittask(void *v)
       +{
       +        Command *c;
       +        Channel *cpid;
       +        void **a;
       +
       +        threadsetname("runwaittask");
       +        a = v;
       +        c = a[0];
       +        cpid = a[1];
       +        free(a);
       +        do
       +                c->pid = recvul(cpid);
       +        while(c->pid == ~0);
       +        free(c->av);
       +        if(c->pid != 0)        /* successful exec */
       +                sendp(ccommand, c);
       +        else{
       +                if(c->iseditcmd)
       +                        sendul(cedit, 0);
       +                free(c->name);
       +                free(c->text);
       +                free(c);
       +        }
       +        chanfree(cpid);
       +}
       +
       +void
       +run(Window *win, char *s, Rune *rdir, int ndir, int newns, char *argaddr, char *xarg, int iseditcmd)
       +{
       +        void **arg;
       +        Command *c;
       +        Channel *cpid;
       +
       +        if(s == nil)
       +                return;
       +
       +        arg = emalloc(10*sizeof(void*));
       +        c = emalloc(sizeof *c);
       +        cpid = chancreate(sizeof(ulong), 0);
       +        arg[0] = win;
       +        arg[1] = s;
       +        arg[2] = rdir;
       +        arg[3] = (void*)ndir;
       +        arg[4] = (void*)newns;
       +        arg[5] = argaddr;
       +        arg[6] = xarg;
       +        arg[7] = c;
       +        arg[8] = cpid;
       +        arg[9] = (void*)iseditcmd;
       +        proccreate(runproc, arg, STACK);
       +        /* mustn't block here because must be ready to answer mount() call in run() */
       +        arg = emalloc(2*sizeof(void*));
       +        arg[0] = c;
       +        arg[1] = cpid;
       +        threadcreate(runwaittask, arg, STACK);
       +}
 (DIR) diff --git a/src/cmd/acme/file.c b/src/cmd/acme/file.c
       t@@ -0,0 +1,310 @@
       +#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 "fns.h"
       +
       +/*
       + * Structure of Undo list:
       + *         The Undo structure follows any associated data, so the list
       + *        can be read backwards: read the structure, then read whatever
       + *        data is associated (insert string, file name) and precedes it.
       + *        The structure includes the previous value of the modify bit
       + *        and a sequence number; successive Undo structures with the
       + *        same sequence number represent simultaneous changes.
       + */
       +
       +typedef struct Undo Undo;
       +struct Undo
       +{
       +        short        type;                /* Delete, Insert, Filename */
       +        short        mod;        /* modify bit */
       +        uint                seq;                /* sequence number */
       +        uint                p0;                /* location of change (unused in f) */
       +        uint                n;                /* # runes in string or file name */
       +};
       +
       +enum
       +{
       +        Undosize = sizeof(Undo)/sizeof(Rune),
       +};
       +
       +File*
       +fileaddtext(File *f, Text *t)
       +{
       +        if(f == nil){
       +                f = emalloc(sizeof(File));
       +                f->unread = TRUE;
       +        }
       +        f->text = realloc(f->text, (f->ntext+1)*sizeof(Text*));
       +        f->text[f->ntext++] = t;
       +        f->curtext = t;
       +        return f;
       +}
       +
       +void
       +filedeltext(File *f, Text *t)
       +{
       +        int i;
       +
       +        for(i=0; i<f->ntext; i++)
       +                if(f->text[i] == t)
       +                        goto Found;
       +        error("can't find text in filedeltext");
       +
       +    Found:
       +        f->ntext--;
       +        if(f->ntext == 0){
       +                fileclose(f);
       +                return;
       +        }
       +        memmove(f->text+i, f->text+i+1, (f->ntext-i)*sizeof(Text*));
       +        if(f->curtext == t)
       +                f->curtext = f->text[0];
       +}
       +
       +void
       +fileinsert(File *f, uint p0, Rune *s, uint ns)
       +{
       +        if(p0 > f->b.nc)
       +                error("internal error: fileinsert");
       +        if(f->seq > 0)
       +                fileuninsert(f, &f->delta, p0, ns);
       +        bufinsert(&f->b, p0, s, ns);
       +        if(ns)
       +                f->mod = TRUE;
       +}
       +
       +void
       +fileuninsert(File *f, Buffer *delta, uint p0, uint ns)
       +{
       +        Undo u;
       +
       +        /* undo an insertion by deleting */
       +        u.type = Delete;
       +        u.mod = f->mod;
       +        u.seq = f->seq;
       +        u.p0 = p0;
       +        u.n = ns;
       +        bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
       +}
       +
       +void
       +filedelete(File *f, uint p0, uint p1)
       +{
       +        if(!(p0<=p1 && p0<=f->b.nc && p1<=f->b.nc))
       +                error("internal error: filedelete");
       +        if(f->seq > 0)
       +                fileundelete(f, &f->delta, p0, p1);
       +        bufdelete(&f->b, p0, p1);
       +        if(p1 > p0)
       +                f->mod = TRUE;
       +}
       +
       +void
       +fileundelete(File *f, Buffer *delta, uint p0, uint p1)
       +{
       +        Undo u;
       +        Rune *buf;
       +        uint i, n;
       +
       +        /* undo a deletion by inserting */
       +        u.type = Insert;
       +        u.mod = f->mod;
       +        u.seq = f->seq;
       +        u.p0 = p0;
       +        u.n = p1-p0;
       +        buf = fbufalloc();
       +        for(i=p0; i<p1; i+=n){
       +                n = p1 - i;
       +                if(n > RBUFSIZE)
       +                        n = RBUFSIZE;
       +                bufread(&f->b, i, buf, n);
       +                bufinsert(delta, delta->nc, buf, n);
       +        }
       +        fbuffree(buf);
       +        bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
       +
       +}
       +
       +void
       +filesetname(File *f, Rune *name, int n)
       +{
       +        if(f->seq > 0)
       +                fileunsetname(f, &f->delta);
       +        free(f->name);
       +        f->name = runemalloc(n);
       +        runemove(f->name, name, n);
       +        f->nname = n;
       +        f->unread = TRUE;
       +}
       +
       +void
       +fileunsetname(File *f, Buffer *delta)
       +{
       +        Undo u;
       +
       +        /* undo a file name change by restoring old name */
       +        u.type = Filename;
       +        u.mod = f->mod;
       +        u.seq = f->seq;
       +        u.p0 = 0;        /* unused */
       +        u.n = f->nname;
       +        if(f->nname)
       +                bufinsert(delta, delta->nc, f->name, f->nname);
       +        bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
       +}
       +
       +uint
       +fileload(File *f, uint p0, int fd, int *nulls)
       +{
       +        if(f->seq > 0)
       +                error("undo in file.load unimplemented");
       +        return bufload(&f->b, p0, fd, nulls);
       +}
       +
       +/* return sequence number of pending redo */
       +uint
       +fileredoseq(File *f)
       +{
       +        Undo u;
       +        Buffer *delta;
       +
       +        delta = &f->epsilon;
       +        if(delta->nc == 0)
       +                return 0;
       +        bufread(delta, delta->nc-Undosize, (Rune*)&u, Undosize);
       +        return u.seq;
       +}
       +
       +void
       +fileundo(File *f, int isundo, uint *q0p, uint *q1p)
       +{
       +        Undo u;
       +        Rune *buf;
       +        uint i, j, n, up;
       +        uint stop;
       +        Buffer *delta, *epsilon;
       +
       +        if(isundo){
       +                /* undo; reverse delta onto epsilon, seq decreases */
       +                delta = &f->delta;
       +                epsilon = &f->epsilon;
       +                stop = f->seq;
       +        }else{
       +                /* redo; reverse epsilon onto delta, seq increases */
       +                delta = &f->epsilon;
       +                epsilon = &f->delta;
       +                stop = 0;        /* don't know yet */
       +        }
       +
       +        buf = fbufalloc();
       +        while(delta->nc > 0){
       +                up = delta->nc-Undosize;
       +                bufread(delta, up, (Rune*)&u, Undosize);
       +                if(isundo){
       +                        if(u.seq < stop){
       +                                f->seq = u.seq;
       +                                goto Return;
       +                        }
       +                }else{
       +                        if(stop == 0)
       +                                stop = u.seq;
       +                        if(u.seq > stop)
       +                                goto Return;
       +                }
       +                switch(u.type){
       +                default:
       +                        fprint(2, "undo: 0x%ux\n", u.type);
       +                        abort();
       +                        break;
       +
       +                case Delete:
       +                        f->seq = u.seq;
       +                        fileundelete(f, epsilon, u.p0, u.p0+u.n);
       +                        f->mod = u.mod;
       +                        bufdelete(&f->b, u.p0, u.p0+u.n);
       +                        for(j=0; j<f->ntext; j++)
       +                                textdelete(f->text[j], u.p0, u.p0+u.n, FALSE);
       +                        *q0p = u.p0;
       +                        *q1p = u.p0;
       +                        break;
       +
       +                case Insert:
       +                        f->seq = u.seq;
       +                        fileuninsert(f, epsilon, u.p0, u.n);
       +                        f->mod = u.mod;
       +                        up -= u.n;
       +                        for(i=0; i<u.n; i+=n){
       +                                n = u.n - i;
       +                                if(n > RBUFSIZE)
       +                                        n = RBUFSIZE;
       +                                bufread(delta, up+i, buf, n);
       +                                bufinsert(&f->b, u.p0+i, buf, n);
       +                                for(j=0; j<f->ntext; j++)
       +                                        textinsert(f->text[j], u.p0+i, buf, n, FALSE);
       +                        }
       +                        *q0p = u.p0;
       +                        *q1p = u.p0+u.n;
       +                        break;
       +
       +                case Filename:
       +                        f->seq = u.seq;
       +                        fileunsetname(f, epsilon);
       +                        f->mod = u.mod;
       +                        up -= u.n;
       +                        free(f->name);
       +                        if(u.n == 0)
       +                                f->name = nil;
       +                        else
       +                                f->name = runemalloc(u.n);
       +                        bufread(delta, up, f->name, u.n);
       +                        f->nname = u.n;
       +                        break;
       +                }
       +                bufdelete(delta, up, delta->nc);
       +        }
       +        if(isundo)
       +                f->seq = 0;
       +    Return:
       +        fbuffree(buf);
       +}
       +
       +void
       +filereset(File *f)
       +{
       +        bufreset(&f->delta);
       +        bufreset(&f->epsilon);
       +        f->seq = 0;
       +}
       +
       +void
       +fileclose(File *f)
       +{
       +        free(f->name);
       +        f->nname = 0;
       +        f->name = nil;
       +        free(f->text);
       +        f->ntext = 0;
       +        f->text = nil;
       +        bufclose(&f->b);
       +        bufclose(&f->delta);
       +        bufclose(&f->epsilon);
       +        elogclose(f);
       +        free(f);
       +}
       +
       +void
       +filemark(File *f)
       +{
       +        if(f->epsilon.nc)
       +                bufdelete(&f->epsilon, 0, f->epsilon.nc);
       +        f->seq = seq;
       +}
 (DIR) diff --git a/src/cmd/acme/fns.h b/src/cmd/acme/fns.h
       t@@ -0,0 +1,92 @@
       +/*
       +#pragma        varargck        argpos        warning        2
       +#pragma        varargck        argpos        warningew        2
       +*/
       +
       +void        warning(Mntdir*, char*, ...);
       +void        warningew(Window*, Mntdir*, char*, ...);
       +
       +#define        fbufalloc()        emalloc(BUFSIZE)
       +#define        fbuffree(x)        free(x)
       +
       +void        plumblook(Plumbmsg*m);
       +void        plumbshow(Plumbmsg*m);
       +void        acmeputsnarf(void);
       +void        acmegetsnarf(void);
       +int        tempfile(void);
       +void        scrlresize(void);
       +Font*        getfont(int, int, char*);
       +char*        getarg(Text*, int, int, Rune**, int*);
       +char*        getbytearg(Text*, int, int, char**);
       +void        new(Text*, Text*, Text*, int, int, Rune*, int);
       +void        undo(Text*, Text*, Text*, int, int, Rune*, int);
       +void        scrsleep(uint);
       +void        savemouse(Window*);
       +void        restoremouse(Window*);
       +void        clearmouse(void);
       +void        allwindows(void(*)(Window*, void*), void*);
       +uint loadfile(int, uint, int*, int(*)(void*, uint, Rune*, int), void*);
       +
       +Window*        errorwin(Mntdir*, int, Window*);
       +Runestr cleanrname(Runestr);
       +void        run(Window*, char*, Rune*, int, int, char*, char*, int);
       +void fsysclose(void);
       +void        setcurtext(Text*, int);
       +int        isfilec(Rune);
       +void        rxinit(void);
       +int rxnull(void);
       +Runestr        dirname(Text*, Rune*, int);
       +void        error(char*);
       +void        cvttorunes(char*, int, Rune*, int*, int*, int*);
       +void*        tmalloc(uint);
       +void        tfree(void);
       +void        killprocs(void);
       +void        killtasks(void);
       +int        runeeq(Rune*, uint, Rune*, uint);
       +int        ALEF_tid(void);
       +void        iconinit(void);
       +Timer*        timerstart(int);
       +void        timerstop(Timer*);
       +void        timercancel(Timer*);
       +void        timerinit(void);
       +void        cut(Text*, Text*, Text*, int, int, Rune*, int);
       +void        paste(Text*, Text*, Text*, int, int, Rune*, int);
       +void        get(Text*, Text*, Text*, int, int, Rune*, int);
       +void        put(Text*, Text*, Text*, int, int, Rune*, int);
       +void        putfile(File*, int, int, Rune*, int);
       +void        fontx(Text*, Text*, Text*, int, int, Rune*, int);
       +int        isalnum(Rune);
       +void        execute(Text*, uint, uint, int, Text*);
       +int        search(Text*, Rune*, uint);
       +void        look3(Text*, uint, uint, int);
       +void        editcmd(Text*, Rune*, uint);
       +uint        min(uint, uint);
       +uint        max(uint, uint);
       +Window*        lookfile(Rune*, int);
       +Window*        lookid(int, int);
       +char*        runetobyte(Rune*, int);
       +Rune*        bytetorune(char*, int*);
       +void        fsysinit(void);
       +Mntdir*        fsysmount(Rune*, int, Rune**, int);
       +void                fsysdelid(Mntdir*);
       +Xfid*                respond(Xfid*, Fcall*, char*);
       +int                rxcompile(Rune*);
       +int                rgetc(void*, uint);
       +int                tgetc(void*, uint);
       +int                isaddrc(int);
       +int                isregexc(int);
       +void *emalloc(uint);
       +void *erealloc(void*, uint);
       +char        *estrdup(char*);
       +Range                address(Mntdir*, Text*, Range, Range, void*, uint, uint, int (*)(void*, uint),  int*, uint*);
       +int                rxexecute(Text*, Rune*, uint, uint, Rangeset*);
       +int                rxbexecute(Text*, uint, Rangeset*);
       +Window*        makenewwindow(Text *t);
       +int        expand(Text*, uint, uint, Expand*);
       +Rune*        skipbl(Rune*, int, int*);
       +Rune*        findbl(Rune*, int, int*);
       +char*        edittext(Window*, int, Rune*, int);        
       +
       +#define        runemalloc(a)                (Rune*)emalloc((a)*sizeof(Rune))
       +#define        runerealloc(a, b)        (Rune*)erealloc((a), (b)*sizeof(Rune))
       +#define        runemove(a, b, c)        memmove((a), (b), (c)*sizeof(Rune))
 (DIR) diff --git a/src/cmd/acme/fsys.c b/src/cmd/acme/fsys.c
       t@@ -0,0 +1,717 @@
       +#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 "fns.h"
       +
       +static        int        sfd;
       +
       +enum
       +{
       +        Nhash        = 16,
       +        DEBUG        = 0
       +};
       +
       +static        Fid        *fids[Nhash];
       +
       +Fid        *newfid(int);
       +
       +static        Xfid*        fsysflush(Xfid*, Fid*);
       +static        Xfid*        fsysauth(Xfid*, Fid*);
       +static        Xfid*        fsysversion(Xfid*, Fid*);
       +static        Xfid*        fsysattach(Xfid*, Fid*);
       +static        Xfid*        fsyswalk(Xfid*, Fid*);
       +static        Xfid*        fsysopen(Xfid*, Fid*);
       +static        Xfid*        fsyscreate(Xfid*, Fid*);
       +static        Xfid*        fsysread(Xfid*, Fid*);
       +static        Xfid*        fsyswrite(Xfid*, Fid*);
       +static        Xfid*        fsysclunk(Xfid*, Fid*);
       +static        Xfid*        fsysremove(Xfid*, Fid*);
       +static        Xfid*        fsysstat(Xfid*, Fid*);
       +static        Xfid*        fsyswstat(Xfid*, Fid*);
       +
       +Xfid*         (*fcall[Tmax])(Xfid*, Fid*) =
       +{
       +        [Tflush]        = fsysflush,
       +        [Tversion]        = fsysversion,
       +        [Tauth]        = fsysauth,
       +        [Tattach]        = fsysattach,
       +        [Twalk]        = fsyswalk,
       +        [Topen]        = fsysopen,
       +        [Tcreate]        = fsyscreate,
       +        [Tread]        = fsysread,
       +        [Twrite]        = fsyswrite,
       +        [Tclunk]        = fsysclunk,
       +        [Tremove]= fsysremove,
       +        [Tstat]        = fsysstat,
       +        [Twstat]        = fsyswstat,
       +};
       +
       +char Eperm[] = "permission denied";
       +char Eexist[] = "file does not exist";
       +char Enotdir[] = "not a directory";
       +
       +Dirtab dirtab[]=
       +{
       +        { ".",                        QTDIR,        Qdir,                0500|DMDIR },
       +        { "acme",                QTDIR,        Qacme,        0500|DMDIR },
       +        { "cons",                QTFILE,        Qcons,        0600 },
       +        { "consctl",        QTFILE,        Qconsctl,        0000 },
       +        { "draw",                QTDIR,        Qdraw,        0000|DMDIR },        /* to suppress graphics progs started in acme */
       +        { "editout",        QTFILE,        Qeditout,        0200 },
       +        { "index",                QTFILE,        Qindex,        0400 },
       +        { "label",                QTFILE,        Qlabel,        0600 },
       +        { "new",                QTDIR,        Qnew,        0500|DMDIR },
       +        { nil, }
       +};
       +
       +Dirtab dirtabw[]=
       +{
       +        { ".",                        QTDIR,                Qdir,                        0500|DMDIR },
       +        { "addr",                QTFILE,                QWaddr,                0600 },
       +        { "body",                QTAPPEND,        QWbody,                0600|DMAPPEND },
       +        { "ctl",                QTFILE,                QWctl,                0600 },
       +        { "data",                QTFILE,                QWdata,                0600 },
       +        { "editout",        QTFILE,                QWeditout,        0200 },
       +        { "event",                QTFILE,                QWevent,                0600 },
       +        { "rdsel",                QTFILE,                QWrdsel,                0400 },
       +        { "wrsel",                QTFILE,                QWwrsel,                0200 },
       +        { "tag",                QTAPPEND,        QWtag,                0600|DMAPPEND },
       +        { nil, }
       +};
       +
       +typedef struct Mnt Mnt;
       +struct Mnt
       +{
       +        QLock        lk;
       +        int                id;
       +        Mntdir        *md;
       +};
       +
       +Mnt        mnt;
       +
       +Xfid*        respond(Xfid*, Fcall*, char*);
       +int                dostat(int, Dirtab*, uchar*, int, uint);
       +uint        getclock(void);
       +
       +char        *user = "Wile E. Coyote";
       +static int closing = 0;
       +int        messagesize = Maxblock+IOHDRSZ;        /* good start */
       +
       +void        fsysproc(void *);
       +
       +void
       +fsysinit(void)
       +{
       +        int p[2];
       +        int n, fd;
       +        char buf[256], *u;
       +
       +        if(pipe(p) < 0)
       +                error("can't create pipe");
       +        if(post9pservice(p[0], "acme") < 0)
       +                error("can't post service");
       +        sfd = p[1];
       +        fmtinstall('F', fcallfmt);
       +        if((u = getuser()) != nil)
       +                user = estrdup(u);
       +        proccreate(fsysproc, nil, STACK);
       +}
       +
       +void
       +fsysproc(void *v)
       +{
       +        int n;
       +        Xfid *x;
       +        Fid *f;
       +        Fcall t;
       +        uchar *buf;
       +
       +        USED(v);
       +        x = nil;
       +        for(;;){
       +                buf = emalloc(messagesize+UTFmax);        /* overflow for appending partial rune in xfidwrite */
       +                n = read9pmsg(sfd, buf, messagesize);
       +                if(n <= 0){
       +                        if(closing)
       +                                break;
       +                        error("i/o error on server channel");
       +                }
       +                if(x == nil){
       +                        sendp(cxfidalloc, nil);
       +                        x = recvp(cxfidalloc);
       +                }
       +                x->buf = buf;
       +                if(convM2S(buf, n, &x->fcall) != n)
       +                        error("convert error in convM2S");
       +                if(DEBUG)
       +                        fprint(2, "%F\n", &x->fcall);
       +                if(fcall[x->fcall.type] == nil)
       +                        x = respond(x, &t, "bad fcall type");
       +                else{
       +                        if(x->fcall.type==Tversion || x->fcall.type==Tauth)
       +                                f = nil;
       +                        else
       +                                f = newfid(x->fcall.fid);
       +                        x->f = f;
       +                        x  = (*fcall[x->fcall.type])(x, f);
       +                }
       +        }
       +}
       +
       +Mntdir*
       +fsysaddid(Rune *dir, int ndir, Rune **incl, int nincl)
       +{
       +        Mntdir *m;
       +        int id;
       +
       +        qlock(&mnt.lk);
       +        id = ++mnt.id;
       +        m = emalloc(sizeof *m);
       +        m->id = id;
       +        m->dir =  dir;
       +        m->ref = 1;        /* one for Command, one will be incremented in attach */
       +        m->ndir = ndir;
       +        m->next = mnt.md;
       +        m->incl = incl;
       +        m->nincl = nincl;
       +        mnt.md = m;
       +        qunlock(&mnt.lk);
       +        return m;
       +}
       +
       +void
       +fsysdelid(Mntdir *idm)
       +{
       +        Mntdir *m, *prev;
       +        int i;
       +        char buf[64];
       +
       +        if(idm == nil)
       +                return;
       +        qlock(&mnt.lk);
       +        if(--idm->ref > 0){
       +                qunlock(&mnt.lk);
       +                return;
       +        }
       +        prev = nil;
       +        for(m=mnt.md; m; m=m->next){
       +                if(m == idm){
       +                        if(prev)
       +                                prev->next = m->next;
       +                        else
       +                                mnt.md = m->next;
       +                        for(i=0; i<m->nincl; i++)
       +                                free(m->incl[i]);
       +                        free(m->incl);
       +                        free(m->dir);
       +                        free(m);
       +                        qunlock(&mnt.lk);
       +                        return;
       +                }
       +                prev = m;
       +        }
       +        qunlock(&mnt.lk);
       +        sprint(buf, "fsysdelid: can't find id %d\n", idm->id);
       +        sendp(cerr, estrdup(buf));
       +}
       +
       +/*
       + * Called only in exec.c:/^run(), from a different FD group
       + */
       +Mntdir*
       +fsysmount(Rune *dir, int ndir, Rune **incl, int nincl)
       +{
       +        return fsysaddid(dir, ndir, incl, nincl);
       +}
       +
       +void
       +fsysclose(void)
       +{
       +        closing = 1;
       +        close(sfd);
       +}
       +
       +Xfid*
       +respond(Xfid *x, Fcall *t, char *err)
       +{
       +        int n;
       +
       +        if(err){
       +                t->type = Rerror;
       +                t->ename = err;
       +        }else
       +                t->type = x->fcall.type+1;
       +        t->fid = x->fcall.fid;
       +        t->tag = x->fcall.tag;
       +        if(x->buf == nil)
       +                x->buf = emalloc(messagesize);
       +        n = convS2M(t, x->buf, messagesize);
       +        if(n <= 0)
       +                error("convert error in convS2M");
       +        if(write(sfd, x->buf, n) != n)
       +                error("write error in respond");
       +        free(x->buf);
       +        x->buf = nil;
       +        if(DEBUG)
       +                fprint(2, "r: %F\n", t);
       +        return x;
       +}
       +
       +static
       +Xfid*
       +fsysversion(Xfid *x, Fid *f)
       +{
       +        Fcall t;
       +
       +        USED(f);
       +        if(x->fcall.msize < 256)
       +                return respond(x, &t, "version: message size too small");
       +        messagesize = x->fcall.msize;
       +        t.msize = messagesize;
       +        if(strncmp(x->fcall.version, "9P2000", 6) != 0)
       +                return respond(x, &t, "unrecognized 9P version");
       +        t.version = "9P2000";
       +        return respond(x, &t, nil);
       +}
       +
       +static
       +Xfid*
       +fsysauth(Xfid *x, Fid *f)
       +{
       +        USED(f);
       +        return respond(x, nil, "acme: authentication not required");
       +}
       +
       +static
       +Xfid*
       +fsysflush(Xfid *x, Fid *f)
       +{
       +        USED(f);
       +        sendp(x->c, xfidflush);
       +        return nil;
       +}
       +
       +static
       +Xfid*
       +fsysattach(Xfid *x, Fid *f)
       +{
       +        Fcall t;
       +        int id;
       +        Mntdir *m;
       +
       +        if(strcmp(x->fcall.uname, user) != 0)
       +                return respond(x, &t, Eperm);
       +        f->busy = TRUE;
       +        f->open = FALSE;
       +        f->qid.path = Qdir;
       +        f->qid.type = QTDIR;
       +        f->qid.vers = 0;
       +        f->dir = dirtab;
       +        f->nrpart = 0;
       +        f->w = nil;
       +        t.qid = f->qid;
       +        f->mntdir = nil;
       +        id = atoi(x->fcall.aname);
       +        qlock(&mnt.lk);
       +        for(m=mnt.md; m; m=m->next)
       +                if(m->id == id){
       +                        f->mntdir = m;
       +                        m->ref++;
       +                        break;
       +                }
       +        if(m == nil)
       +                sendp(cerr, estrdup("unknown id in attach"));
       +        qunlock(&mnt.lk);
       +        return respond(x, &t, nil);
       +}
       +
       +static
       +Xfid*
       +fsyswalk(Xfid *x, Fid *f)
       +{
       +        Fcall t;
       +        int c, i, j, id;
       +        Qid q;
       +        uchar type;
       +        ulong path;
       +        Fid *nf;
       +        Dirtab *d, *dir;
       +        Window *w;
       +        char *err;
       +
       +        nf = nil;
       +        w = nil;
       +        if(f->open)
       +                return respond(x, &t, "walk of open file");
       +        if(x->fcall.fid != x->fcall.newfid){
       +                nf = newfid(x->fcall.newfid);
       +                if(nf->busy)
       +                        return respond(x, &t, "newfid already in use");
       +                nf->busy = TRUE;
       +                nf->open = FALSE;
       +                nf->mntdir = f->mntdir;
       +                if(f->mntdir)
       +                        f->mntdir->ref++;
       +                nf->dir = f->dir;
       +                nf->qid = f->qid;
       +                nf->w = f->w;
       +                nf->nrpart = 0;        /* not open, so must be zero */
       +                if(nf->w)
       +                        incref(&nf->w->ref);
       +                f = nf;        /* walk f */
       +        }
       +
       +        t.nwqid = 0;
       +        err = nil;
       +        dir = nil;
       +        id = WIN(f->qid);
       +        q = f->qid;
       +
       +        if(x->fcall.nwname > 0){
       +                for(i=0; i<x->fcall.nwname; i++){
       +                        if((q.type & QTDIR) == 0){
       +                                err = Enotdir;
       +                                break;
       +                        }
       +
       +                        if(strcmp(x->fcall.wname[i], "..") == 0){
       +                                type = QTDIR;
       +                                path = Qdir;
       +                                id = 0;
       +                                if(w){
       +                                        winclose(w);
       +                                        w = nil;
       +                                }
       +    Accept:
       +                                if(i == MAXWELEM){
       +                                        err = "name too long";
       +                                        break;
       +                                }
       +                                q.type = type;
       +                                q.vers = 0;
       +                                q.path = QID(id, path);
       +                                t.wqid[t.nwqid++] = q;
       +                                continue;
       +                        }
       +
       +                        /* is it a numeric name? */
       +                        for(j=0; (c=x->fcall.wname[i][j]); j++)
       +                                if(c<'0' || '9'<c)
       +                                        goto Regular;
       +                        /* yes: it's a directory */
       +                        if(w)        /* name has form 27/23; get out before losing w */
       +                                break;
       +                        id = atoi(x->fcall.wname[i]);
       +                        qlock(&row.lk);
       +                        w = lookid(id, FALSE);
       +                        if(w == nil){
       +                                qunlock(&row.lk);
       +                                break;
       +                        }
       +                        incref(&w->ref);        /* we'll drop reference at end if there's an error */
       +                        path = Qdir;
       +                        type = QTDIR;
       +                        qunlock(&row.lk);
       +                        dir = dirtabw;
       +                        goto Accept;
       +        
       +    Regular:
       +//                        if(FILE(f->qid) == Qacme)        /* empty directory */
       +//                                break;
       +                        if(strcmp(x->fcall.wname[i], "new") == 0){
       +                                if(w)
       +                                        error("w set in walk to new");
       +                                sendp(cnewwindow, nil);        /* signal newwindowthread */
       +                                w = recvp(cnewwindow);        /* receive new window */
       +                                incref(&w->ref);
       +                                type = QTDIR;
       +                                path = QID(w->id, Qdir);
       +                                id = w->id;
       +                                dir = dirtabw;
       +                                goto Accept;
       +                        }
       +
       +                        if(id == 0)
       +                                d = dirtab;
       +                        else
       +                                d = dirtabw;
       +                        d++;        /* skip '.' */
       +                        for(; d->name; d++)
       +                                if(strcmp(x->fcall.wname[i], d->name) == 0){
       +                                        path = d->qid;
       +                                        type = d->type;
       +                                        dir = d;
       +                                        goto Accept;
       +                                }
       +
       +                        break;        /* file not found */
       +                }
       +
       +                if(i==0 && err == nil)
       +                        err = Eexist;
       +        }
       +
       +        if(err!=nil || t.nwqid<x->fcall.nwname){
       +                if(nf){
       +                        nf->busy = FALSE;
       +                        fsysdelid(nf->mntdir);
       +                }
       +        }else if(t.nwqid  == x->fcall.nwname){
       +                if(w){
       +                        f->w = w;
       +                        w = nil;        /* don't drop the reference */
       +                }
       +                if(dir)
       +                        f->dir = dir;
       +                f->qid = q;
       +        }
       +
       +        if(w != nil)
       +                winclose(w);
       +
       +        return respond(x, &t, err);
       +}
       +
       +static
       +Xfid*
       +fsysopen(Xfid *x, Fid *f)
       +{
       +        Fcall t;
       +        int m;
       +
       +        /* can't truncate anything, so just disregard */
       +        x->fcall.mode &= ~(OTRUNC|OCEXEC);
       +        /* can't execute or remove anything */
       +        if(x->fcall.mode==OEXEC || (x->fcall.mode&ORCLOSE))
       +                goto Deny;
       +        switch(x->fcall.mode){
       +        default:
       +                goto Deny;
       +        case OREAD:
       +                m = 0400;
       +                break;
       +        case OWRITE:
       +                m = 0200;
       +                break;
       +        case ORDWR:
       +                m = 0600;
       +                break;
       +        }
       +        if(((f->dir->perm&~(DMDIR|DMAPPEND))&m) != m)
       +                goto Deny;
       +
       +        sendp(x->c, xfidopen);
       +        return nil;
       +
       +    Deny:
       +        return respond(x, &t, Eperm);
       +}
       +
       +static
       +Xfid*
       +fsyscreate(Xfid *x, Fid *f)
       +{
       +        Fcall t;
       +
       +        USED(f);
       +        return respond(x, &t, Eperm);
       +}
       +
       +static
       +int
       +idcmp(const void *a, const void *b)
       +{
       +        return *(int*)a - *(int*)b;
       +}
       +
       +static
       +Xfid*
       +fsysread(Xfid *x, Fid *f)
       +{
       +        Fcall t;
       +        uchar *b;
       +        int i, id, n, o, e, j, k, *ids, nids;
       +        Dirtab *d, dt;
       +        Column *c;
       +        uint clock, len;
       +        char buf[16];
       +
       +        if(f->qid.type & QTDIR){
       +                if(FILE(f->qid) == Qacme){        /* empty dir */
       +                        t.data = nil;
       +                        t.count = 0;
       +                        respond(x, &t, nil);
       +                        return x;
       +                }
       +                o = x->fcall.offset;
       +                e = x->fcall.offset+x->fcall.count;
       +                clock = getclock();
       +                b = emalloc(messagesize);
       +                id = WIN(f->qid);
       +                n = 0;
       +                if(id > 0)
       +                        d = dirtabw;
       +                else
       +                        d = dirtab;
       +                d++;        /* first entry is '.' */
       +                for(i=0; d->name!=nil && i<e; i+=len){
       +                        len = dostat(WIN(x->f->qid), d, b+n, x->fcall.count-n, clock);
       +                        if(len <= BIT16SZ)
       +                                break;
       +                        if(i >= o)
       +                                n += len;
       +                        d++;
       +                }
       +                if(id == 0){
       +                        qlock(&row.lk);
       +                        nids = 0;
       +                        ids = nil;
       +                        for(j=0; j<row.ncol; j++){
       +                                c = row.col[j];
       +                                for(k=0; k<c->nw; k++){
       +                                        ids = realloc(ids, (nids+1)*sizeof(int));
       +                                        ids[nids++] = c->w[k]->id;
       +                                }
       +                        }
       +                        qunlock(&row.lk);
       +                        qsort(ids, nids, sizeof ids[0], idcmp);
       +                        j = 0;
       +                        dt.name = buf;
       +                        for(; j<nids && i<e; i+=len){
       +                                k = ids[j];
       +                                sprint(dt.name, "%d", k);
       +                                dt.qid = QID(k, Qdir);
       +                                dt.type = QTDIR;
       +                                dt.perm = DMDIR|0700;
       +                                len = dostat(k, &dt, b+n, x->fcall.count-n, clock);
       +                                if(len == 0)
       +                                        break;
       +                                if(i >= o)
       +                                        n += len;
       +                                j++;
       +                        }
       +                        free(ids);
       +                }
       +                t.data = (char*)b;
       +                t.count = n;
       +                respond(x, &t, nil);
       +                free(b);
       +                return x;
       +        }
       +        sendp(x->c, xfidread);
       +        return nil;
       +}
       +
       +static
       +Xfid*
       +fsyswrite(Xfid *x, Fid *f)
       +{
       +        USED(f);
       +        sendp(x->c, xfidwrite);
       +        return nil;
       +}
       +
       +static
       +Xfid*
       +fsysclunk(Xfid *x, Fid *f)
       +{
       +        fsysdelid(f->mntdir);
       +        sendp(x->c, xfidclose);
       +        return nil;
       +}
       +
       +static
       +Xfid*
       +fsysremove(Xfid *x, Fid *f)
       +{
       +        Fcall t;
       +
       +        USED(f);
       +        return respond(x, &t, Eperm);
       +}
       +
       +static
       +Xfid*
       +fsysstat(Xfid *x, Fid *f)
       +{
       +        Fcall t;
       +
       +        t.stat = emalloc(messagesize-IOHDRSZ);
       +        t.nstat = dostat(WIN(x->f->qid), f->dir, t.stat, messagesize-IOHDRSZ, getclock());
       +        x = respond(x, &t, nil);
       +        free(t.stat);
       +        return x;
       +}
       +
       +static
       +Xfid*
       +fsyswstat(Xfid *x, Fid *f)
       +{
       +        Fcall t;
       +
       +        USED(f);
       +        return respond(x, &t, Eperm);
       +}
       +
       +Fid*
       +newfid(int fid)
       +{
       +        Fid *f, *ff, **fh;
       +
       +        ff = nil;
       +        fh = &fids[fid&(Nhash-1)];
       +        for(f=*fh; f; f=f->next)
       +                if(f->fid == fid)
       +                        return f;
       +                else if(ff==nil && f->busy==FALSE)
       +                        ff = f;
       +        if(ff){
       +                ff->fid = fid;
       +                return ff;
       +        }
       +        f = emalloc(sizeof *f);
       +        f->fid = fid;
       +        f->next = *fh;
       +        *fh = f;
       +        return f;
       +}
       +
       +uint
       +getclock(void)
       +{
       +/*
       +        char buf[32];
       +
       +        buf[0] = '\0';
       +        pread(clockfd, buf, sizeof buf, 0);
       +        return atoi(buf);
       +*/        
       +        return time(0);
       +}
       +
       +int
       +dostat(int id, Dirtab *dir, uchar *buf, int nbuf, uint clock)
       +{
       +        Dir d;
       +
       +        d.qid.path = QID(id, dir->qid);
       +        d.qid.vers = 0;
       +        d.qid.type = dir->type;
       +        d.mode = dir->perm;
       +        d.length = 0;        /* would be nice to do better */
       +        d.name = dir->name;
       +        d.uid = user;
       +        d.gid = user;
       +        d.muid = user;
       +        d.atime = clock;
       +        d.mtime = clock;
       +        return convD2M(&d, buf, nbuf);
       +}
 (DIR) diff --git a/src/cmd/acme/look.c b/src/cmd/acme/look.c
       t@@ -0,0 +1,772 @@
       +#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 <regexp.h>
       +#include <plumb.h>
       +#include "dat.h"
       +#include "fns.h"
       +
       +Window*        openfile(Text*, Expand*);
       +
       +int        nuntitled;
       +
       +void
       +look3(Text *t, uint q0, uint q1, int external)
       +{
       +        int n, c, f, expanded;
       +        Text *ct;
       +        Expand e;
       +        Rune *r;
       +        uint p;
       +        Plumbmsg *m;
       +        Runestr dir;
       +        char buf[32];
       +
       +        ct = seltext;
       +        if(ct == nil)
       +                seltext = t;
       +        expanded = expand(t, q0, q1, &e);
       +        if(!external && t->w!=nil && t->w->nopen[QWevent]>0){
       +                /* send alphanumeric expansion to external client */
       +                if(expanded == FALSE)
       +                        return;
       +                f = 0;
       +                if((e.u.at!=nil && t->w!=nil) || (e.nname>0 && lookfile(e.name, e.nname)!=nil))
       +                        f = 1;                /* acme can do it without loading a file */
       +                if(q0!=e.q0 || q1!=e.q1)
       +                        f |= 2;        /* second (post-expand) message follows */
       +                if(e.nname)
       +                        f |= 4;        /* it's a file name */
       +                c = 'l';
       +                if(t->what == Body)
       +                        c = 'L';
       +                n = q1-q0;
       +                if(n <= EVENTSIZE){
       +                        r = runemalloc(n);
       +                        bufread(&t->file->b, q0, r, n);
       +                        winevent(t->w, "%c%d %d %d %d %.*S\n", c, q0, q1, f, n, n, r);
       +                        free(r);
       +                }else
       +                        winevent(t->w, "%c%d %d %d 0 \n", c, q0, q1, f, n);
       +                if(q0==e.q0 && q1==e.q1)
       +                        return;
       +                if(e.nname){
       +                        n = e.nname;
       +                        if(e.a1 > e.a0)
       +                                n += 1+(e.a1-e.a0);
       +                        r = runemalloc(n);
       +                        runemove(r, e.name, e.nname);
       +                        if(e.a1 > e.a0){
       +                                r[e.nname] = ':';
       +                                bufread(&e.u.at->file->b, e.a0, r+e.nname+1, e.a1-e.a0);
       +                        }
       +                }else{
       +                        n = e.q1 - e.q0;
       +                        r = runemalloc(n);
       +                        bufread(&t->file->b, e.q0, r, n);
       +                }
       +                f &= ~2;
       +                if(n <= EVENTSIZE)
       +                        winevent(t->w, "%c%d %d %d %d %.*S\n", c, e.q0, e.q1, f, n, n, r);
       +                else
       +                        winevent(t->w, "%c%d %d %d 0 \n", c, e.q0, e.q1, f, n);
       +                free(r);
       +                goto Return;
       +        }
       +        if(plumbsendfd >= 0){
       +                /* send whitespace-delimited word to plumber */
       +                m = emalloc(sizeof(Plumbmsg));
       +                m->src = estrdup("acme");
       +                m->dst = nil;
       +                dir = dirname(t, nil, 0);
       +                if(dir.nr==1 && dir.r[0]=='.'){        /* sigh */
       +                        free(dir.r);
       +                        dir.r = nil;
       +                        dir.nr = 0;
       +                }
       +                if(dir.nr == 0)
       +                        m->wdir = estrdup(wdir);
       +                else
       +                        m->wdir = runetobyte(dir.r, dir.nr);
       +                free(dir.r);
       +                m->type = estrdup("text");
       +                m->attr = nil;
       +                buf[0] = '\0';
       +                if(q1 == q0){
       +                        if(t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){
       +                                q0 = t->q0;
       +                                q1 = t->q1;
       +                        }else{
       +                                p = q0;
       +                                while(q0>0 && (c=tgetc(t, q0-1))!=' ' && c!='\t' && c!='\n')
       +                                        q0--;
       +                                while(q1<t->file->b.nc && (c=tgetc(t, q1))!=' ' && c!='\t' && c!='\n')
       +                                        q1++;
       +                                if(q1 == q0){
       +                                        plumbfree(m);
       +                                        goto Return;
       +                                }
       +                                sprint(buf, "click=%d", p-q0);
       +                                m->attr = plumbunpackattr(buf);
       +                        }
       +                }
       +                r = runemalloc(q1-q0);
       +                bufread(&t->file->b, q0, r, q1-q0);
       +                m->data = runetobyte(r, q1-q0);
       +                m->ndata = strlen(m->data);
       +                free(r);
       +                if(m->ndata<messagesize-1024 && plumbsend(plumbsendfd, m) >= 0){
       +                        plumbfree(m);
       +                        goto Return;
       +                }
       +                plumbfree(m);
       +                /* plumber failed to match; fall through */
       +        }
       +
       +        /* interpret alphanumeric string ourselves */
       +        if(expanded == FALSE)
       +                return;
       +        if(e.name || e.u.at)
       +                openfile(t, &e);
       +        else{
       +                if(t->w == nil)
       +                        return;
       +                ct = &t->w->body;
       +                if(t->w != ct->w)
       +                        winlock(ct->w, 'M');
       +                if(t == ct)
       +                        textsetselect(ct, e.q1, e.q1);
       +                n = e.q1 - e.q0;
       +                r = runemalloc(n);
       +                bufread(&t->file->b, e.q0, r, n);
       +                if(search(ct, r, n) && e.jump)
       +                        moveto(mousectl, addpt(frptofchar(&ct->fr, ct->fr.p0), Pt(4, ct->fr.font->height-4)));
       +                if(t->w != ct->w)
       +                        winunlock(ct->w);
       +                free(r);
       +        }
       +
       +   Return:
       +        free(e.name);
       +        free(e.bname);
       +}
       +
       +int
       +plumbgetc(void *a, uint n)
       +{
       +        Rune *r;
       +
       +        r = a;
       +        if(n<0 || n>runestrlen(r))
       +                return 0;
       +        return r[n];
       +}
       +
       +void
       +plumblook(Plumbmsg *m)
       +{
       +        Expand e;
       +        char *addr;
       +
       +        if(m->ndata >= BUFSIZE){
       +                warning(nil, "insanely long file name (%d bytes) in plumb message (%.32s...)\n", m->ndata, m->data);
       +                return;
       +        }
       +        e.q0 = 0;
       +        e.q1 = 0;
       +        if(m->data[0] == '\0')
       +                return;
       +        e.u.ar = nil;
       +        e.bname = m->data;
       +        e.name = bytetorune(e.bname, &e.nname);
       +        e.jump = TRUE;
       +        e.a0 = 0;
       +        e.a1 = 0;
       +        addr = plumblookup(m->attr, "addr");
       +        if(addr != nil){
       +                e.u.ar = bytetorune(addr, &e.a1);
       +                e.agetc = plumbgetc;
       +        }
       +        openfile(nil, &e);
       +        free(e.name);
       +        free(e.u.at);
       +}
       +
       +void
       +plumbshow(Plumbmsg *m)
       +{
       +        Window *w;
       +        Rune rb[256], *r;
       +        int nb, nr;
       +        Runestr rs;
       +        char *name, *p, namebuf[16];
       +
       +        w = makenewwindow(nil);
       +        name = plumblookup(m->attr, "filename");
       +        if(name == nil){
       +                name = namebuf;
       +                nuntitled++;
       +                snprint(namebuf, sizeof namebuf, "Untitled-%d", nuntitled);
       +        }
       +        p = nil;
       +        if(name[0]!='/' && m->wdir!=nil && m->wdir[0]!='\0'){
       +                nb = strlen(m->wdir) + 1 + strlen(name) + 1;
       +                p = emalloc(nb);
       +                snprint(p, nb, "%s/%s", m->wdir, name);
       +                name = p;
       +        }
       +        cvttorunes(name, strlen(name), rb, &nb, &nr, nil);
       +        free(p);
       +        rs = cleanrname((Runestr){rb, nr});
       +        winsetname(w, rs.r, rs.nr);
       +        r = runemalloc(m->ndata);
       +        cvttorunes(m->data, m->ndata, r, &nb, &nr, nil);
       +        textinsert(&w->body, 0, r, nr, TRUE);
       +        free(r);
       +        w->body.file->mod = FALSE;
       +        w->dirty = FALSE;
       +        winsettag(w);
       +        textscrdraw(&w->body);
       +        textsetselect(&w->tag, w->tag.file->b.nc, w->tag.file->b.nc);
       +}
       +
       +int
       +search(Text *ct, Rune *r, uint n)
       +{
       +        uint q, nb, maxn;
       +        int around;
       +        Rune *s, *b, *c;
       +
       +        if(n==0 || n>ct->file->b.nc)
       +                return FALSE;
       +        if(2*n > RBUFSIZE){
       +                warning(nil, "string too long\n");
       +                return FALSE;
       +        }
       +        maxn = max(2*n, RBUFSIZE);
       +        s = fbufalloc();
       +        b = s;
       +        nb = 0;
       +        b[nb] = 0;
       +        around = 0;
       +        q = ct->q1;
       +        for(;;){
       +                if(q >= ct->file->b.nc){
       +                        q = 0;
       +                        around = 1;
       +                        nb = 0;
       +                        b[nb] = 0;
       +                }
       +                if(nb > 0){
       +                        c = runestrchr(b, r[0]);
       +                        if(c == nil){
       +                                q += nb;
       +                                nb = 0;
       +                                b[nb] = 0;
       +                                if(around && q>=ct->q1)
       +                                        break;
       +                                continue;
       +                        }
       +                        q += (c-b);
       +                        nb -= (c-b);
       +                        b = c;
       +                }
       +                /* reload if buffer covers neither string nor rest of file */
       +                if(nb<n && nb!=ct->file->b.nc-q){
       +                        nb = ct->file->b.nc-q;
       +                        if(nb >= maxn)
       +                                nb = maxn-1;
       +                        bufread(&ct->file->b, q, s, nb);
       +                        b = s;
       +                        b[nb] = '\0';
       +                }
       +                /* this runeeq is fishy but the null at b[nb] makes it safe */
       +                if(runeeq(b, n, r, n)==TRUE){
       +                        if(ct->w){
       +                                textshow(ct, q, q+n, 1);
       +                                winsettag(ct->w);
       +                        }else{
       +                                ct->q0 = q;
       +                                ct->q1 = q+n;
       +                        }
       +                        seltext = ct;
       +                        fbuffree(s);
       +                        return TRUE;
       +                }
       +                if(around && q>=ct->q1)
       +                        break;
       +                --nb;
       +                b++;
       +                q++;
       +        }
       +        fbuffree(s);
       +        return FALSE;
       +}
       +
       +int
       +isfilec(Rune r)
       +{
       +        static Rune Lx[] = { '.', '-', '+', '/', ':', 0 };
       +        if(isalnum(r))
       +                return TRUE;
       +        if(runestrchr(Lx, r))
       +                return TRUE;
       +        return FALSE;
       +}
       +
       +Runestr
       +cleanrname(Runestr rs)
       +{
       +        int i, j, found;
       +        Rune *b;
       +        int n;
       +        static Rune Lslashdotdot[] = { '/', '.', '.', 0 };
       +
       +        b = rs.r;
       +        n = rs.nr;
       +
       +        /* compress multiple slashes */
       +        for(i=0; i<n-1; i++)
       +                if(b[i]=='/' && b[i+1]=='/'){
       +                        runemove(b+i, b+i+1, n-i-1);
       +                        --n;
       +                        --i;
       +                }
       +        /*  eliminate ./ */
       +        for(i=0; i<n-1; i++)
       +                if(b[i]=='.' && b[i+1]=='/' && (i==0 || b[i-1]=='/')){
       +                        runemove(b+i, b+i+2, n-i-2);
       +                        n -= 2;
       +                        --i;
       +                }
       +        /* eliminate trailing . */
       +        if(n>=2 && b[n-2]=='/' && b[n-1]=='.')
       +                --n;
       +        do{
       +                /* compress xx/.. */
       +                found = FALSE;
       +                for(i=1; i<=n-3; i++)
       +                        if(runeeq(b+i, 3, Lslashdotdot, 3)){
       +                                if(i==n-3 || b[i+3]=='/'){
       +                                        found = TRUE;
       +                                        break;
       +                                }
       +                        }
       +                if(found)
       +                        for(j=i-1; j>=0; --j)
       +                                if(j==0 || b[j-1]=='/'){
       +                                        i += 3;                /* character beyond .. */
       +                                        if(i<n && b[i]=='/')
       +                                                ++i;
       +                                        runemove(b+j, b+i, n-i);
       +                                        n -= (i-j);
       +                                        break;
       +                                }
       +        }while(found);
       +        if(n == 0){
       +                *b = '.';
       +                n = 1;
       +        }
       +        return (Runestr){b, n};
       +}
       +
       +Runestr
       +includefile(Rune *dir, Rune *file, int nfile)
       +{
       +        int m, n;
       +        char *a;
       +        Rune *r;
       +        static Rune Lslash[] = { '/', 0 };
       +
       +        m = runestrlen(dir);
       +        a = emalloc((m+1+nfile)*UTFmax+1);
       +        sprint(a, "%S/%.*S", dir, nfile, file);
       +        n = access(a, 0);
       +        free(a);
       +        if(n < 0)
       +                return (Runestr){nil, 0};
       +        r = runemalloc(m+1+nfile);
       +        runemove(r, dir, m);
       +        runemove(r+m, Lslash, 1);
       +        runemove(r+m+1, file, nfile);
       +        free(file);
       +        return cleanrname((Runestr){r, m+1+nfile});
       +}
       +
       +static        Rune        *objdir;
       +
       +Runestr
       +includename(Text *t, Rune *r, int n)
       +{
       +        Window *w;
       +        char buf[128];
       +        Rune Lsysinclude[] = { '/', 's', 'y', 's', '/', 'i', 'n', 'c', 'l', 'u', 'd', 'e', 0 };
       +        Runestr file;
       +        int i;
       +
       +        if(objdir==nil && objtype!=nil){
       +                sprint(buf, "/%s/include", objtype);
       +                objdir = bytetorune(buf, &i);
       +                objdir = runerealloc(objdir, i+1);
       +                objdir[i] = '\0';        
       +        }
       +
       +        w = t->w;
       +        if(n==0 || r[0]=='/' || w==nil)
       +                goto Rescue;
       +        if(n>2 && r[0]=='.' && r[1]=='/')
       +                goto Rescue;
       +        file.r = nil;
       +        file.nr = 0;
       +        for(i=0; i<w->nincl && file.r==nil; i++)
       +                file = includefile(w->incl[i], r, n);
       +
       +        if(file.r == nil)
       +                file = includefile(Lsysinclude, r, n);
       +        if(file.r==nil && objdir!=nil)
       +                file = includefile(objdir, r, n);
       +        if(file.r == nil)
       +                goto Rescue;
       +        return file;
       +
       +    Rescue:
       +        return (Runestr){r, n};
       +}
       +
       +Runestr
       +dirname(Text *t, Rune *r, int n)
       +{
       +        Rune *b, c;
       +        uint m, nt;
       +        int slash;
       +        Runestr tmp;
       +
       +        b = nil;
       +        if(t==nil || t->w==nil)
       +                goto Rescue;
       +        nt = t->w->tag.file->b.nc;
       +        if(nt == 0)
       +                goto Rescue;
       +        if(n>=1 &&  r[0]=='/')
       +                goto Rescue;
       +        b = runemalloc(nt+n+1);
       +        bufread(&t->w->tag.file->b, 0, b, nt);
       +        slash = -1;
       +        for(m=0; m<nt; m++){
       +                c = b[m];
       +                if(c == '/')
       +                        slash = m;
       +                if(c==' ' || c=='\t')
       +                        break;
       +        }
       +        if(slash < 0)
       +                goto Rescue;
       +        runemove(b+slash+1, r, n);
       +        free(r);
       +        return cleanrname((Runestr){b, slash+1+n});
       +
       +    Rescue:
       +        free(b);
       +        tmp = (Runestr){r, n};
       +        if(r)
       +                return cleanrname(tmp);
       +        return tmp;
       +}
       +
       +int
       +expandfile(Text *t, uint q0, uint q1, Expand *e)
       +{
       +        int i, n, nname, colon, eval;
       +        uint amin, amax;
       +        Rune *r, c;
       +        Window *w;
       +        Runestr rs;
       +
       +        amax = q1;
       +        if(q1 == q0){
       +                colon = -1;
       +                while(q1<t->file->b.nc && isfilec(c=textreadc(t, q1))){
       +                        if(c == ':'){
       +                                colon = q1;
       +                                break;
       +                        }
       +                        q1++;
       +                }
       +                while(q0>0 && (isfilec(c=textreadc(t, q0-1)) || isaddrc(c) || isregexc(c))){
       +                        q0--;
       +                        if(colon<0 && c==':')
       +                                colon = q0;
       +                }
       +                /*
       +                 * if it looks like it might begin file: , consume address chars after :
       +                 * otherwise terminate expansion at :
       +                 */
       +                if(colon >= 0){
       +                        q1 = colon;
       +                        if(colon<t->file->b.nc-1 && isaddrc(textreadc(t, colon+1))){
       +                                q1 = colon+1;
       +                                while(q1<t->file->b.nc-1 && isaddrc(textreadc(t, q1)))
       +                                        q1++;
       +                        }
       +                }
       +                if(q1 > q0)
       +                        if(colon >= 0){        /* stop at white space */
       +                                for(amax=colon+1; amax<t->file->b.nc; amax++)
       +                                        if((c=textreadc(t, amax))==' ' || c=='\t' || c=='\n')
       +                                                break;
       +                        }else
       +                                amax = t->file->b.nc;
       +        }
       +        amin = amax;
       +        e->q0 = q0;
       +        e->q1 = q1;
       +        n = q1-q0;
       +        if(n == 0)
       +                return FALSE;
       +        /* see if it's a file name */
       +        r = runemalloc(n);
       +        bufread(&t->file->b, q0, r, n);
       +        /* first, does it have bad chars? */
       +        nname = -1;
       +        for(i=0; i<n; i++){
       +                c = r[i];
       +                if(c==':' && nname<0){
       +                        if(q0+i+1<t->file->b.nc && (i==n-1 || isaddrc(textreadc(t, q0+i+1))))
       +                                amin = q0+i;
       +                        else
       +                                goto Isntfile;
       +                        nname = i;
       +                }
       +        }
       +        if(nname == -1)
       +                nname = n;
       +        for(i=0; i<nname; i++)
       +                if(!isfilec(r[i]))
       +                        goto Isntfile;
       +        /*
       +         * See if it's a file name in <>, and turn that into an include
       +         * file name if so.  Should probably do it for "" too, but that's not
       +         * restrictive enough syntax and checking for a #include earlier on the
       +         * line would be silly.
       +         */
       +        if(q0>0 && textreadc(t, q0-1)=='<' && q1<t->file->b.nc && textreadc(t, q1)=='>'){
       +                rs = includename(t, r, nname);
       +                r = rs.r;
       +                nname = rs.nr;
       +        }
       +        else if(amin == q0)
       +                goto Isfile;
       +        else{
       +                rs = dirname(t, r, nname);
       +                r = rs.r;
       +                nname = rs.nr;
       +        }
       +        e->bname = runetobyte(r, nname);
       +        /* if it's already a window name, it's a file */
       +        w = lookfile(r, nname);
       +        if(w != nil)
       +                goto Isfile;
       +        /* if it's the name of a file, it's a file */
       +        if(access(e->bname, 0) < 0){
       +                free(e->bname);
       +                e->bname = nil;
       +                goto Isntfile;
       +        }
       +
       +  Isfile:
       +        e->name = r;
       +        e->nname = nname;
       +        e->u.at = t;
       +        e->a0 = amin+1;
       +        eval = FALSE;
       +        address(nil, nil, (Range){-1,-1}, (Range){0, 0}, t, e->a0, amax, tgetc, &eval, (uint*)&e->a1);
       +        return TRUE;
       +
       +   Isntfile:
       +        free(r);
       +        return FALSE;
       +}
       +
       +int
       +expand(Text *t, uint q0, uint q1, Expand *e)
       +{
       +        memset(e, 0, sizeof *e);
       +        e->agetc = tgetc;
       +        /* if in selection, choose selection */
       +        e->jump = TRUE;
       +        if(q1==q0 && t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){
       +                q0 = t->q0;
       +                q1 = t->q1;
       +                if(t->what == Tag)
       +                        e->jump = FALSE;
       +        }
       +
       +        if(expandfile(t, q0, q1, e))
       +                return TRUE;
       +
       +        if(q0 == q1){
       +                while(q1<t->file->b.nc && isalnum(textreadc(t, q1)))
       +                        q1++;
       +                while(q0>0 && isalnum(textreadc(t, q0-1)))
       +                        q0--;
       +        }
       +        e->q0 = q0;
       +        e->q1 = q1;
       +        return q1 > q0;
       +}
       +
       +Window*
       +lookfile(Rune *s, int n)
       +{
       +        int i, j, k;
       +        Window *w;
       +        Column *c;
       +        Text *t;
       +
       +        /* avoid terminal slash on directories */
       +        if(n>1 && s[n-1] == '/')
       +                --n;
       +        for(j=0; j<row.ncol; j++){
       +                c = row.col[j];
       +                for(i=0; i<c->nw; i++){
       +                        w = c->w[i];
       +                        t = &w->body;
       +                        k = t->file->nname;
       +                        if(k>1 && t->file->name[k-1] == '/')
       +                                k--;
       +                        if(runeeq(t->file->name, k, s, n)){
       +                                w = w->body.file->curtext->w;
       +                                if(w->col != nil)        /* protect against race deleting w */
       +                                        return w;
       +                        }
       +                }
       +        }
       +        return nil;
       +}
       +
       +Window*
       +lookid(int id, int dump)
       +{
       +        int i, j;
       +        Window *w;
       +        Column *c;
       +
       +        for(j=0; j<row.ncol; j++){
       +                c = row.col[j];
       +                for(i=0; i<c->nw; i++){
       +                        w = c->w[i];
       +                        if(dump && w->dumpid == id)
       +                                return w;
       +                        if(!dump && w->id == id)
       +                                return w;
       +                }
       +        }
       +        return nil;
       +}
       +
       +
       +Window*
       +openfile(Text *t, Expand *e)
       +{
       +        Range r;
       +        Window *w, *ow;
       +        int eval, i, n;
       +        Rune *rp;
       +        uint dummy;
       +
       +        if(e->nname == 0){
       +                w = t->w;
       +                if(w == nil)
       +                        return nil;
       +        }else
       +                w = lookfile(e->name, e->nname);
       +        if(w){
       +                t = &w->body;
       +                if(!t->col->safe && t->fr.maxlines==0) /* window is obscured by full-column window */
       +                        colgrow(t->col, t->col->w[0], 1);
       +        }else{
       +                ow = nil;
       +                if(t)
       +                        ow = t->w;
       +                w = makenewwindow(t);
       +                t = &w->body;
       +                winsetname(w, e->name, e->nname);
       +                textload(t, 0, e->bname, 1);
       +                t->file->mod = FALSE;
       +                t->w->dirty = FALSE;
       +                winsettag(t->w);
       +                textsetselect(&t->w->tag, t->w->tag.file->b.nc, t->w->tag.file->b.nc);
       +                if(ow != nil)
       +                        for(i=ow->nincl; --i>=0; ){
       +                                n = runestrlen(ow->incl[i]);
       +                                rp = runemalloc(n);
       +                                runemove(rp, ow->incl[i], n);
       +                                winaddincl(w, rp, n);
       +                        }
       +        }
       +        if(e->a1 == e->a0)
       +                eval = FALSE;
       +        else{
       +                eval = TRUE;
       +                r = address(nil, t, (Range){-1, -1}, (Range){t->q0, t->q1}, e->u.at, e->a0, e->a1, e->agetc, &eval, &dummy);
       +                if(eval == FALSE)
       +                        e->jump = FALSE;        /* don't jump if invalid address */
       +        }
       +        if(eval == FALSE){
       +                r.q0 = t->q0;
       +                r.q1 = t->q1;
       +        }
       +        textshow(t, r.q0, r.q1, 1);
       +        winsettag(t->w);
       +        seltext = t;
       +        if(e->jump)
       +                moveto(mousectl, addpt(frptofchar(&t->fr, t->fr.p0), Pt(4, font->height-4)));
       +        return w;
       +}
       +
       +void
       +new(Text *et, Text *t, Text *argt, int flag1, int flag2, Rune *arg, int narg)
       +{
       +        int ndone;
       +        Rune *a, *f;
       +        int na, nf;
       +        Expand e;
       +        Runestr rs;
       +
       +        getarg(argt, FALSE, TRUE, &a, &na);
       +        if(a){
       +                new(et, t, nil, flag1, flag2, a, na);
       +                if(narg == 0)
       +                        return;
       +        }
       +        /* loop condition: *arg is not a blank */
       +        for(ndone=0; ; ndone++){
       +                a = findbl(arg, narg, &na);
       +                if(a == arg){
       +                        if(ndone==0 && et->col!=nil)
       +                                winsettag(coladd(et->col, nil, nil, -1));
       +                        break;
       +                }
       +                nf = narg-na;
       +                f = runemalloc(nf);
       +                runemove(f, arg, nf);
       +                rs = dirname(et, f, nf);
       +                f = rs.r;
       +                nf = rs.nr;
       +                memset(&e, 0, sizeof e);
       +                e.name = f;
       +                e.nname = nf;
       +                e.bname = runetobyte(f, nf);
       +                e.jump = TRUE;
       +                openfile(et, &e);
       +                free(f);
       +                free(e.bname);
       +                arg = skipbl(a, na, &narg);
       +        }
       +}
 (DIR) diff --git a/src/cmd/acme/mkfile b/src/cmd/acme/mkfile
       t@@ -0,0 +1,41 @@
       +PLAN9=../../..
       +<$PLAN9/src/mkhdr
       +
       +TARG=acme
       +
       +OFILES=\
       +        acme.$O\
       +        addr.$O\
       +        buff.$O\
       +        cols.$O\
       +        disk.$O\
       +        ecmd.$O\
       +        edit.$O\
       +        elog.$O\
       +        exec.$O\
       +        file.$O\
       +        fsys.$O\
       +        look.$O\
       +        regx.$O\
       +        rows.$O\
       +        scrl.$O\
       +        text.$O\
       +        time.$O\
       +        util.$O\
       +        wind.$O\
       +        xfid.$O\
       +
       +HFILES=dat.h\
       +        edit.h\
       +        fns.h\
       +
       +UPDATE=\
       +        mkfile\
       +        $HFILES\
       +        ${OFILES:%.$O=%.c}\
       +
       +<$PLAN9/src/mkone
       +
       +LDFLAGS=$LDFLAGS -lfs -lmux -lplumb -lthread -lframe -ldraw -lbio -l9 -lfmt -lutf -L$X11/lib -lX11
       +
       +edit.$O ecmd.$O elog.$O:        edit.h
 (DIR) diff --git a/src/cmd/acme/regx.c b/src/cmd/acme/regx.c
       t@@ -0,0 +1,835 @@
       +#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 "fns.h"
       +
       +Rangeset        sel;
       +Rune                *lastregexp;
       +
       +/*
       + * Machine Information
       + */
       +typedef struct Inst Inst;
       +struct Inst
       +{
       +        uint        type;        /* < 0x10000 ==> literal, otherwise action */
       +        union {
       +                int sid;
       +                int subid;
       +                int class;
       +                Inst *other;
       +                Inst *right;
       +        } u;
       +        union{
       +                Inst *left;
       +                Inst *next;
       +        } u1;
       +};
       +
       +#define        NPROG        1024
       +Inst        program[NPROG];
       +Inst        *progp;
       +Inst        *startinst;        /* First inst. of program; might not be program[0] */
       +Inst        *bstartinst;        /* same for backwards machine */
       +Channel        *rechan;        /* chan(Inst*) */
       +
       +typedef struct Ilist Ilist;
       +struct Ilist
       +{
       +        Inst        *inst;                /* Instruction of the thread */
       +        Rangeset se;
       +        uint        startp;                /* first char of match */
       +};
       +
       +#define        NLIST        128
       +
       +Ilist        *tl, *nl;        /* This list, next list */
       +Ilist        list[2][NLIST];
       +static        Rangeset sempty;
       +
       +/*
       + * Actions and Tokens
       + *
       + *        0x100xx are operators, value == precedence
       + *        0x200xx are tokens, i.e. operands for operators
       + */
       +#define        OPERATOR        0x10000        /* Bitmask of all operators */
       +#define        START                0x10000        /* Start, used for marker on stack */
       +#define        RBRA                0x10001        /* Right bracket, ) */
       +#define        LBRA                0x10002        /* Left bracket, ( */
       +#define        OR                0x10003        /* Alternation, | */
       +#define        CAT                0x10004        /* Concatentation, implicit operator */
       +#define        STAR                0x10005        /* Closure, * */
       +#define        PLUS                0x10006        /* a+ == aa* */
       +#define        QUEST                0x10007        /* a? == a|nothing, i.e. 0 or 1 a's */
       +#define        ANY                0x20000        /* Any character but newline, . */
       +#define        NOP                0x20001        /* No operation, internal use only */
       +#define        BOL                0x20002        /* Beginning of line, ^ */
       +#define        EOL                0x20003        /* End of line, $ */
       +#define        CCLASS                0x20004        /* Character class, [] */
       +#define        NCCLASS                0x20005        /* Negated character class, [^] */
       +#define        END                0x20077        /* Terminate: match found */
       +
       +#define        ISATOR                0x10000
       +#define        ISAND                0x20000
       +
       +/*
       + * Parser Information
       + */
       +typedef struct Node Node;
       +struct Node
       +{
       +        Inst        *first;
       +        Inst        *last;
       +};
       +
       +#define        NSTACK        20
       +Node        andstack[NSTACK];
       +Node        *andp;
       +int        atorstack[NSTACK];
       +int        *atorp;
       +int        lastwasand;        /* Last token was operand */
       +int        cursubid;
       +int        subidstack[NSTACK];
       +int        *subidp;
       +int        backwards;
       +int        nbra;
       +Rune        *exprp;                /* pointer to next character in source expression */
       +#define        DCLASS        10        /* allocation increment */
       +int        nclass;                /* number active */
       +int        Nclass;                /* high water mark */
       +Rune        **class;
       +int        negateclass;
       +
       +void        addinst(Ilist *l, Inst *inst, Rangeset *sep);
       +void        newmatch(Rangeset*);
       +void        bnewmatch(Rangeset*);
       +void        pushand(Inst*, Inst*);
       +void        pushator(int);
       +Node        *popand(int);
       +int        popator(void);
       +void        startlex(Rune*);
       +int        lex(void);
       +void        operator(int);
       +void        operand(int);
       +void        evaluntil(int);
       +void        optimize(Inst*);
       +void        bldcclass(void);
       +
       +void
       +rxinit(void)
       +{
       +        rechan = chancreate(sizeof(Inst*), 0);
       +        lastregexp = runemalloc(1);
       +}
       +
       +void
       +regerror(char *e)
       +{
       +        lastregexp[0] = 0;
       +        warning(nil, "regexp: %s\n", e);
       +        sendp(rechan, nil);
       +        threadexits(nil);
       +}
       +
       +Inst *
       +newinst(int t)
       +{
       +        if(progp >= &program[NPROG])
       +                regerror("expression too long");
       +        progp->type = t;
       +        progp->u1.left = nil;
       +        progp->u.right = nil;
       +        return progp++;
       +}
       +
       +void
       +realcompile(void *arg)
       +{
       +        int token;
       +        Rune *s;
       +
       +        threadsetname("regcomp");
       +        s = arg;
       +        startlex(s);
       +        atorp = atorstack;
       +        andp = andstack;
       +        subidp = subidstack;
       +        cursubid = 0;
       +        lastwasand = FALSE;
       +        /* Start with a low priority operator to prime parser */
       +        pushator(START-1);
       +        while((token=lex()) != END){
       +                if((token&ISATOR) == OPERATOR)
       +                        operator(token);
       +                else
       +                        operand(token);
       +        }
       +        /* Close with a low priority operator */
       +        evaluntil(START);
       +        /* Force END */
       +        operand(END);
       +        evaluntil(START);
       +        if(nbra)
       +                regerror("unmatched `('");
       +        --andp;        /* points to first and only operand */
       +        sendp(rechan, andp->first);
       +        threadexits(nil);
       +}
       +
       +/* r is null terminated */
       +int
       +rxcompile(Rune *r)
       +{
       +        int i, nr;
       +        Inst *oprogp;
       +
       +        nr = runestrlen(r)+1;
       +        if(runeeq(lastregexp, runestrlen(lastregexp)+1, r, nr)==TRUE)
       +                return TRUE;
       +        lastregexp[0] = 0;
       +        for(i=0; i<nclass; i++)
       +                free(class[i]);
       +        nclass = 0;
       +        progp = program;
       +        backwards = FALSE;
       +        bstartinst = nil;
       +        threadcreate(realcompile, r, STACK);
       +        startinst = recvp(rechan);
       +        if(startinst == nil)
       +                return FALSE;
       +        optimize(program);
       +        oprogp = progp;
       +        backwards = TRUE;
       +        threadcreate(realcompile, r, STACK);
       +        bstartinst = recvp(rechan);
       +        if(bstartinst == nil)
       +                return FALSE;
       +        optimize(oprogp);
       +        lastregexp = runerealloc(lastregexp, nr);
       +        runemove(lastregexp, r, nr);
       +        return TRUE;
       +}
       +
       +void
       +operand(int t)
       +{
       +        Inst *i;
       +        if(lastwasand)
       +                operator(CAT);        /* catenate is implicit */
       +        i = newinst(t);
       +        if(t == CCLASS){
       +                if(negateclass)
       +                        i->type = NCCLASS;        /* UGH */
       +                i->u.class = nclass-1;                /* UGH */
       +        }
       +        pushand(i, i);
       +        lastwasand = TRUE;
       +}
       +
       +void
       +operator(int t)
       +{
       +        if(t==RBRA && --nbra<0)
       +                regerror("unmatched `)'");
       +        if(t==LBRA){
       +                cursubid++;        /* silently ignored */
       +                nbra++;
       +                if(lastwasand)
       +                        operator(CAT);
       +        }else
       +                evaluntil(t);
       +        if(t!=RBRA)
       +                pushator(t);
       +        lastwasand = FALSE;
       +        if(t==STAR || t==QUEST || t==PLUS || t==RBRA)
       +                lastwasand = TRUE;        /* these look like operands */
       +}
       +
       +void
       +pushand(Inst *f, Inst *l)
       +{
       +        if(andp >= &andstack[NSTACK])
       +                error("operand stack overflow");
       +        andp->first = f;
       +        andp->last = l;
       +        andp++;
       +}
       +
       +void
       +pushator(int t)
       +{
       +        if(atorp >= &atorstack[NSTACK])
       +                error("operator stack overflow");
       +        *atorp++=t;
       +        if(cursubid >= NRange)
       +                *subidp++= -1;
       +        else
       +                *subidp++=cursubid;
       +}
       +
       +Node *
       +popand(int op)
       +{
       +        char buf[64];
       +
       +        if(andp <= &andstack[0])
       +                if(op){
       +                        sprint(buf, "missing operand for %c", op);
       +                        regerror(buf);
       +                }else
       +                        regerror("malformed regexp");
       +        return --andp;
       +}
       +
       +int
       +popator()
       +{
       +        if(atorp <= &atorstack[0])
       +                error("operator stack underflow");
       +        --subidp;
       +        return *--atorp;
       +}
       +
       +void
       +evaluntil(int pri)
       +{
       +        Node *op1, *op2, *t;
       +        Inst *inst1, *inst2;
       +
       +        while(pri==RBRA || atorp[-1]>=pri){
       +                switch(popator()){
       +                case LBRA:
       +                        op1 = popand('(');
       +                        inst2 = newinst(RBRA);
       +                        inst2->u.subid = *subidp;
       +                        op1->last->u1.next = inst2;
       +                        inst1 = newinst(LBRA);
       +                        inst1->u.subid = *subidp;
       +                        inst1->u1.next = op1->first;
       +                        pushand(inst1, inst2);
       +                        return;                /* must have been RBRA */
       +                default:
       +                        error("unknown regexp operator");
       +                        break;
       +                case OR:
       +                        op2 = popand('|');
       +                        op1 = popand('|');
       +                        inst2 = newinst(NOP);
       +                        op2->last->u1.next = inst2;
       +                        op1->last->u1.next = inst2;
       +                        inst1 = newinst(OR);
       +                        inst1->u.right = op1->first;
       +                        inst1->u1.left = op2->first;
       +                        pushand(inst1, inst2);
       +                        break;
       +                case CAT:
       +                        op2 = popand(0);
       +                        op1 = popand(0);
       +                        if(backwards && op2->first->type!=END){
       +                                t = op1;
       +                                op1 = op2;
       +                                op2 = t;
       +                        }
       +                        op1->last->u1.next = op2->first;
       +                        pushand(op1->first, op2->last);
       +                        break;
       +                case STAR:
       +                        op2 = popand('*');
       +                        inst1 = newinst(OR);
       +                        op2->last->u1.next = inst1;
       +                        inst1->u.right = op2->first;
       +                        pushand(inst1, inst1);
       +                        break;
       +                case PLUS:
       +                        op2 = popand('+');
       +                        inst1 = newinst(OR);
       +                        op2->last->u1.next = inst1;
       +                        inst1->u.right = op2->first;
       +                        pushand(op2->first, inst1);
       +                        break;
       +                case QUEST:
       +                        op2 = popand('?');
       +                        inst1 = newinst(OR);
       +                        inst2 = newinst(NOP);
       +                        inst1->u1.left = inst2;
       +                        inst1->u.right = op2->first;
       +                        op2->last->u1.next = inst2;
       +                        pushand(inst1, inst2);
       +                        break;
       +                }
       +        }
       +}
       +
       +
       +void
       +optimize(Inst *start)
       +{
       +        Inst *inst, *target;
       +
       +        for(inst=start; inst->type!=END; inst++){
       +                target = inst->u1.next;
       +                while(target->type == NOP)
       +                        target = target->u1.next;
       +                inst->u1.next = target;
       +        }
       +}
       +
       +void
       +startlex(Rune *s)
       +{
       +        exprp = s;
       +        nbra = 0;
       +}
       +
       +
       +int
       +lex(void){
       +        int c;
       +
       +        c = *exprp++;
       +        switch(c){
       +        case '\\':
       +                if(*exprp)
       +                        if((c= *exprp++)=='n')
       +                                c='\n';
       +                break;
       +        case 0:
       +                c = END;
       +                --exprp;        /* In case we come here again */
       +                break;
       +        case '*':
       +                c = STAR;
       +                break;
       +        case '?':
       +                c = QUEST;
       +                break;
       +        case '+':
       +                c = PLUS;
       +                break;
       +        case '|':
       +                c = OR;
       +                break;
       +        case '.':
       +                c = ANY;
       +                break;
       +        case '(':
       +                c = LBRA;
       +                break;
       +        case ')':
       +                c = RBRA;
       +                break;
       +        case '^':
       +                c = BOL;
       +                break;
       +        case '$':
       +                c = EOL;
       +                break;
       +        case '[':
       +                c = CCLASS;
       +                bldcclass();
       +                break;
       +        }
       +        return c;
       +}
       +
       +int
       +nextrec(void)
       +{
       +        if(exprp[0]==0 || (exprp[0]=='\\' && exprp[1]==0))
       +                regerror("malformed `[]'");
       +        if(exprp[0] == '\\'){
       +                exprp++;
       +                if(*exprp=='n'){
       +                        exprp++;
       +                        return '\n';
       +                }
       +                return *exprp++|0x10000;
       +        }
       +        return *exprp++;
       +}
       +
       +void
       +bldcclass(void)
       +{
       +        int c1, c2, n, na;
       +        Rune *classp;
       +
       +        classp = runemalloc(DCLASS);
       +        n = 0;
       +        na = DCLASS;
       +        /* we have already seen the '[' */
       +        if(*exprp == '^'){
       +                classp[n++] = '\n';        /* don't match newline in negate case */
       +                negateclass = TRUE;
       +                exprp++;
       +        }else
       +                negateclass = FALSE;
       +        while((c1 = nextrec()) != ']'){
       +                if(c1 == '-'){
       +    Error:
       +                        free(classp);
       +                        regerror("malformed `[]'");
       +                }
       +                if(n+4 >= na){                /* 3 runes plus NUL */
       +                        na += DCLASS;
       +                        classp = runerealloc(classp, na);
       +                }
       +                if(*exprp == '-'){
       +                        exprp++;        /* eat '-' */
       +                        if((c2 = nextrec()) == ']')
       +                                goto Error;
       +                        classp[n+0] = 0xFFFF;
       +                        classp[n+1] = c1;
       +                        classp[n+2] = c2;
       +                        n += 3;
       +                }else
       +                        classp[n++] = c1;
       +        }
       +        classp[n] = 0;
       +        if(nclass == Nclass){
       +                Nclass += DCLASS;
       +                class = realloc(class, Nclass*sizeof(Rune*));
       +        }
       +        class[nclass++] = classp;
       +}
       +
       +int
       +classmatch(int classno, int c, int negate)
       +{
       +        Rune *p;
       +
       +        p = class[classno];
       +        while(*p){
       +                if(*p == 0xFFFF){
       +                        if(p[1]<=c && c<=p[2])
       +                                return !negate;
       +                        p += 3;
       +                }else if(*p++ == c)
       +                        return !negate;
       +        }
       +        return negate;
       +}
       +
       +/*
       + * Note optimization in addinst:
       + *         *l must be pending when addinst called; if *l has been looked
       + *                at already, the optimization is a bug.
       + */
       +void
       +addinst(Ilist *l, Inst *inst, Rangeset *sep)
       +{
       +        Ilist *p;
       +
       +        for(p = l; p->inst; p++){
       +                if(p->inst==inst){
       +                        if((sep)->r[0].q0 < p->se.r[0].q0)
       +                                p->se= *sep;        /* this would be bug */
       +                        return;        /* It's already there */
       +                }
       +        }
       +        p->inst = inst;
       +        p->se= *sep;
       +        (p+1)->inst = nil;
       +}
       +
       +int
       +rxnull(void)
       +{
       +        return startinst==nil || bstartinst==nil;
       +}
       +
       +/* either t!=nil or r!=nil, and we match the string in the appropriate place */
       +int
       +rxexecute(Text *t, Rune *r, uint startp, uint eof, Rangeset *rp)
       +{
       +        int flag;
       +        Inst *inst;
       +        Ilist *tlp;
       +        uint p;
       +        int nnl, ntl;
       +        int nc, c;
       +        int wrapped;
       +        int startchar;
       +
       +        flag = 0;
       +        p = startp;
       +        startchar = 0;
       +        wrapped = 0;
       +        nnl = 0;
       +        if(startinst->type<OPERATOR)
       +                startchar = startinst->type;
       +        list[0][0].inst = list[1][0].inst = nil;
       +        sel.r[0].q0 = -1;
       +        if(t != nil)
       +                nc = t->file->b.nc;
       +        else
       +                nc = runestrlen(r);
       +        /* Execute machine once for each character */
       +        for(;;p++){
       +        doloop:
       +                if(p>=eof || p>=nc){
       +                        switch(wrapped++){
       +                        case 0:                /* let loop run one more click */
       +                        case 2:
       +                                break;
       +                        case 1:                /* expired; wrap to beginning */
       +                                if(sel.r[0].q0>=0 || eof!=Infinity)
       +                                        goto Return;
       +                                list[0][0].inst = list[1][0].inst = nil;
       +                                p = 0;
       +                                goto doloop;
       +                        default:
       +                                goto Return;
       +                        }
       +                        c = 0;
       +                }else{
       +                        if(((wrapped && p>=startp) || sel.r[0].q0>0) && nnl==0)
       +                                break;
       +                        if(t != nil)
       +                                c = textreadc(t, p);
       +                        else
       +                                c = r[p];
       +                }
       +                /* fast check for first char */
       +                if(startchar && nnl==0 && c!=startchar)
       +                        continue;
       +                tl = list[flag];
       +                nl = list[flag^=1];
       +                nl->inst = nil;
       +                ntl = nnl;
       +                nnl = 0;
       +                if(sel.r[0].q0<0 && (!wrapped || p<startp || startp==eof)){
       +                        /* Add first instruction to this list */
       +                        if(++ntl >= NLIST){
       +        Overflow:
       +                                warning(nil, "regexp list overflow\n");
       +                                sel.r[0].q0 = -1;
       +                                goto Return;
       +                        }
       +                        sempty.r[0].q0 = p;
       +                        addinst(tl, startinst, &sempty);
       +                }
       +                /* Execute machine until this list is empty */
       +                for(tlp = tl; inst = tlp->inst; tlp++){        /* assignment = */
       +        Switchstmt:
       +                        switch(inst->type){
       +                        default:        /* regular character */
       +                                if(inst->type==c){
       +        Addinst:
       +                                        if(++nnl >= NLIST)
       +                                                goto Overflow;
       +                                        addinst(nl, inst->u1.next, &tlp->se);
       +                                }
       +                                break;
       +                        case LBRA:
       +                                if(inst->u.subid>=0)
       +                                        tlp->se.r[inst->u.subid].q0 = p;
       +                                inst = inst->u1.next;
       +                                goto Switchstmt;
       +                        case RBRA:
       +                                if(inst->u.subid>=0)
       +                                        tlp->se.r[inst->u.subid].q1 = p;
       +                                inst = inst->u1.next;
       +                                goto Switchstmt;
       +                        case ANY:
       +                                if(c!='\n')
       +                                        goto Addinst;
       +                                break;
       +                        case BOL:
       +                                if(p==0 || (t!=nil && textreadc(t, p-1)=='\n') || (r!=nil && r[p-1]=='\n')){
       +        Step:
       +                                        inst = inst->u1.next;
       +                                        goto Switchstmt;
       +                                }
       +                                break;
       +                        case EOL:
       +                                if(c == '\n')
       +                                        goto Step;
       +                                break;
       +                        case CCLASS:
       +                                if(c>=0 && classmatch(inst->u.class, c, 0))
       +                                        goto Addinst;
       +                                break;
       +                        case NCCLASS:
       +                                if(c>=0 && classmatch(inst->u.class, c, 1))
       +                                        goto Addinst;
       +                                break;
       +                        case OR:
       +                                /* evaluate right choice later */
       +                                if(++ntl >= NLIST)
       +                                        goto Overflow;
       +                                addinst(tlp, inst->u.right, &tlp->se);
       +                                /* efficiency: advance and re-evaluate */
       +                                inst = inst->u1.left;
       +                                goto Switchstmt;
       +                        case END:        /* Match! */
       +                                tlp->se.r[0].q1 = p;
       +                                newmatch(&tlp->se);
       +                                break;
       +                        }
       +                }
       +        }
       +    Return:
       +        *rp = sel;
       +        return sel.r[0].q0 >= 0;
       +}
       +
       +void
       +newmatch(Rangeset *sp)
       +{
       +        if(sel.r[0].q0<0 || sp->r[0].q0<sel.r[0].q0 ||
       +           (sp->r[0].q0==sel.r[0].q0 && sp->r[0].q1>sel.r[0].q1))
       +                sel = *sp;
       +}
       +
       +int
       +rxbexecute(Text *t, uint startp, Rangeset *rp)
       +{
       +        int flag;
       +        Inst *inst;
       +        Ilist *tlp;
       +        int p;
       +        int nnl, ntl;
       +        int c;
       +        int wrapped;
       +        int startchar;
       +
       +        flag = 0;
       +        nnl = 0;
       +        wrapped = 0;
       +        p = startp;
       +        startchar = 0;
       +        if(bstartinst->type<OPERATOR)
       +                startchar = bstartinst->type;
       +        list[0][0].inst = list[1][0].inst = nil;
       +        sel.r[0].q0= -1;
       +        /* Execute machine once for each character, including terminal NUL */
       +        for(;;--p){
       +        doloop:
       +                if(p <= 0){
       +                        switch(wrapped++){
       +                        case 0:                /* let loop run one more click */
       +                        case 2:
       +                                break;
       +                        case 1:                /* expired; wrap to end */
       +                                if(sel.r[0].q0>=0)
       +                                        goto Return;
       +                                list[0][0].inst = list[1][0].inst = nil;
       +                                p = t->file->b.nc;
       +                                goto doloop;
       +                        case 3:
       +                        default:
       +                                goto Return;
       +                        }
       +                        c = 0;
       +                }else{
       +                        if(((wrapped && p<=startp) || sel.r[0].q0>0) && nnl==0)
       +                                break;
       +                        c = textreadc(t, p-1);
       +                }
       +                /* fast check for first char */
       +                if(startchar && nnl==0 && c!=startchar)
       +                        continue;
       +                tl = list[flag];
       +                nl = list[flag^=1];
       +                nl->inst = nil;
       +                ntl = nnl;
       +                nnl = 0;
       +                if(sel.r[0].q0<0 && (!wrapped || p>startp)){
       +                        /* Add first instruction to this list */
       +                        if(++ntl >= NLIST){
       +        Overflow:
       +                                warning(nil, "regexp list overflow\n");
       +                                sel.r[0].q0 = -1;
       +                                goto Return;
       +                        }
       +                        /* the minus is so the optimizations in addinst work */
       +                        sempty.r[0].q0 = -p;
       +                        addinst(tl, bstartinst, &sempty);
       +                }
       +                /* Execute machine until this list is empty */
       +                for(tlp = tl; inst = tlp->inst; tlp++){        /* assignment = */
       +        Switchstmt:
       +                        switch(inst->type){
       +                        default:        /* regular character */
       +                                if(inst->type == c){
       +        Addinst:
       +                                        if(++nnl >= NLIST)
       +                                                goto Overflow;
       +                                        addinst(nl, inst->u1.next, &tlp->se);
       +                                }
       +                                break;
       +                        case LBRA:
       +                                if(inst->u.subid>=0)
       +                                        tlp->se.r[inst->u.subid].q0 = p;
       +                                inst = inst->u1.next;
       +                                goto Switchstmt;
       +                        case RBRA:
       +                                if(inst->u.subid >= 0)
       +                                        tlp->se.r[inst->u.subid].q1 = p;
       +                                inst = inst->u1.next;
       +                                goto Switchstmt;
       +                        case ANY:
       +                                if(c != '\n')
       +                                        goto Addinst;
       +                                break;
       +                        case BOL:
       +                                if(c=='\n' || p==0){
       +        Step:
       +                                        inst = inst->u1.next;
       +                                        goto Switchstmt;
       +                                }
       +                                break;
       +                        case EOL:
       +                                if(p<t->file->b.nc && textreadc(t, p)=='\n')
       +                                        goto Step;
       +                                break;
       +                        case CCLASS:
       +                                if(c>0 && classmatch(inst->u.class, c, 0))
       +                                        goto Addinst;
       +                                break;
       +                        case NCCLASS:
       +                                if(c>0 && classmatch(inst->u.class, c, 1))
       +                                        goto Addinst;
       +                                break;
       +                        case OR:
       +                                /* evaluate right choice later */
       +                                if(++ntl >= NLIST)
       +                                        goto Overflow;
       +                                addinst(tlp, inst->u.right, &tlp->se);
       +                                /* efficiency: advance and re-evaluate */
       +                                inst = inst->u1.left;
       +                                goto Switchstmt;
       +                        case END:        /* Match! */
       +                                tlp->se.r[0].q0 = -tlp->se.r[0].q0; /* minus sign */
       +                                tlp->se.r[0].q1 = p;
       +                                bnewmatch(&tlp->se);
       +                                break;
       +                        }
       +                }
       +        }
       +    Return:
       +        *rp = sel;
       +        return sel.r[0].q0 >= 0;
       +}
       +
       +void
       +bnewmatch(Rangeset *sp)
       +{
       +        int  i;
       +
       +        if(sel.r[0].q0<0 || sp->r[0].q0>sel.r[0].q1 || (sp->r[0].q0==sel.r[0].q1 && sp->r[0].q1<sel.r[0].q0))
       +                for(i = 0; i<NRange; i++){       /* note the reversal; q0<=q1 */
       +                        sel.r[i].q0 = sp->r[i].q1;
       +                        sel.r[i].q1 = sp->r[i].q0;
       +                }
       +}
 (DIR) diff --git a/src/cmd/acme/rows.c b/src/cmd/acme/rows.c
       t@@ -0,0 +1,731 @@
       +#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 <bio.h>
       +#include <plumb.h>
       +#include "dat.h"
       +#include "fns.h"
       +
       +static Rune Lcolhdr[] = {
       +        'N', 'e', 'w', 'c', 'o', 'l', ' ',
       +        'K', 'i', 'l', 'l', ' ',
       +        'P', 'u', 't', 'a', 'l', 'l', ' ',
       +        'D', 'u', 'm', 'p', ' ',
       +        'E', 'x', 'i', 't', ' ',
       +        0
       +};
       +
       +void
       +rowinit(Row *row, Rectangle r)
       +{
       +        Rectangle r1;
       +        Text *t;
       +
       +        draw(screen, r, display->white, nil, ZP);
       +        row->r = r;
       +        row->col = nil;
       +        row->ncol = 0;
       +        r1 = r;
       +        r1.max.y = r1.min.y + font->height;
       +        t = &row->tag;
       +        textinit(t, fileaddtext(nil, t), r1, rfget(FALSE, FALSE, FALSE, nil), tagcols);
       +        t->what = Rowtag;
       +        t->row = row;
       +        t->w = nil;
       +        t->col = nil;
       +        r1.min.y = r1.max.y;
       +        r1.max.y += Border;
       +        draw(screen, r1, display->black, nil, ZP);
       +        textinsert(t, 0, Lcolhdr, 29, TRUE);
       +        textsetselect(t, t->file->b.nc, t->file->b.nc);
       +}
       +
       +Column*
       +rowadd(Row *row, Column *c, int x)
       +{
       +        Rectangle r, r1;
       +        Column *d;
       +        int i;
       +
       +        d = nil;
       +        r = row->r;
       +        r.min.y = row->tag.fr.r.max.y+Border;
       +        if(x<r.min.x && row->ncol>0){        /*steal 40% of last column by default */
       +                d = row->col[row->ncol-1];
       +                x = d->r.min.x + 3*Dx(d->r)/5;
       +        }
       +        /* look for column we'll land on */
       +        for(i=0; i<row->ncol; i++){
       +                d = row->col[i];
       +                if(x < d->r.max.x)
       +                        break;
       +        }
       +        if(row->ncol > 0){
       +                if(i < row->ncol)
       +                        i++;        /* new column will go after d */
       +                r = d->r;
       +                if(Dx(r) < 100)
       +                        return nil;
       +                draw(screen, r, display->white, nil, ZP);
       +                r1 = r;
       +                r1.max.x = min(x, r.max.x-50);
       +                if(Dx(r1) < 50)
       +                        r1.max.x = r1.min.x+50;
       +                colresize(d, r1);
       +                r1.min.x = r1.max.x;
       +                r1.max.x = r1.min.x+Border;
       +                draw(screen, r1, display->black, nil, ZP);
       +                r.min.x = r1.max.x;
       +        }
       +        if(c == nil){
       +                c = emalloc(sizeof(Column));
       +                colinit(c, r);
       +                incref(&reffont.ref);
       +        }else
       +                colresize(c, r);
       +        c->row = row;
       +        c->tag.row = row;
       +        row->col = realloc(row->col, (row->ncol+1)*sizeof(Column*));
       +        memmove(row->col+i+1, row->col+i, (row->ncol-i)*sizeof(Column*));
       +        row->col[i] = c;
       +        row->ncol++;
       +        clearmouse();
       +        return c;
       +}
       +
       +void
       +rowresize(Row *row, Rectangle r)
       +{
       +        int i, dx, odx;
       +        Rectangle r1, r2;
       +        Column *c;
       +
       +        dx = Dx(r);
       +        odx = Dx(row->r);
       +        row->r = r;
       +        r1 = r;
       +        r1.max.y = r1.min.y + font->height;
       +        textresize(&row->tag, r1);
       +        r1.min.y = r1.max.y;
       +        r1.max.y += Border;
       +        draw(screen, r1, display->black, nil, ZP);
       +        r.min.y = r1.max.y;
       +        r1 = r;
       +        r1.max.x = r1.min.x;
       +        for(i=0; i<row->ncol; i++){
       +                c = row->col[i];
       +                r1.min.x = r1.max.x;
       +                if(i == row->ncol-1)
       +                        r1.max.x = r.max.x;
       +                else
       +                        r1.max.x = r1.min.x+Dx(c->r)*dx/odx;
       +                if(i > 0){
       +                        r2 = r1;
       +                        r2.max.x = r2.min.x+Border;
       +                        draw(screen, r2, display->black, nil, ZP);
       +                        r1.min.x = r2.max.x;
       +                }
       +                colresize(c, r1);
       +        }
       +}
       +
       +void
       +rowdragcol(Row *row, Column *c, int _0)
       +{
       +        Rectangle r;
       +        int i, b, x;
       +        Point p, op;
       +        Column *d;
       +
       +        USED(_0);
       +
       +        clearmouse();
       +        setcursor(mousectl, &boxcursor);
       +        b = mouse->buttons;
       +        op = mouse->xy;
       +        while(mouse->buttons == b)
       +                readmouse(mousectl);
       +        setcursor(mousectl, nil);
       +        if(mouse->buttons){
       +                while(mouse->buttons)
       +                        readmouse(mousectl);
       +                return;
       +        }
       +
       +        for(i=0; i<row->ncol; i++)
       +                if(row->col[i] == c)
       +                        goto Found;
       +        error("can't find column");
       +
       +  Found:
       +        if(i == 0)
       +                return;
       +        p = mouse->xy;
       +        if((abs(p.x-op.x)<5 && abs(p.y-op.y)<5))
       +                return;
       +        if((i>0 && p.x<row->col[i-1]->r.min.x) || (i<row->ncol-1 && p.x>c->r.max.x)){
       +                /* shuffle */
       +                x = c->r.min.x;
       +                rowclose(row, c, FALSE);
       +                if(rowadd(row, c, p.x) == nil)        /* whoops! */
       +                if(rowadd(row, c, x) == nil)                /* WHOOPS! */
       +                if(rowadd(row, c, -1)==nil){                /* shit! */
       +                        rowclose(row, c, TRUE);
       +                        return;
       +                }
       +                colmousebut(c);
       +                return;
       +        }
       +        d = row->col[i-1];
       +        if(p.x < d->r.min.x+80+Scrollwid)
       +                p.x = d->r.min.x+80+Scrollwid;
       +        if(p.x > c->r.max.x-80-Scrollwid)
       +                p.x = c->r.max.x-80-Scrollwid;
       +        r = d->r;
       +        r.max.x = c->r.max.x;
       +        draw(screen, r, display->white, nil, ZP);
       +        r.max.x = p.x;
       +        colresize(d, r);
       +        r = c->r;
       +        r.min.x = p.x;
       +        r.max.x = r.min.x;
       +        r.max.x += Border;
       +        draw(screen, r, display->black, nil, ZP);
       +        r.min.x = r.max.x;
       +        r.max.x = c->r.max.x;
       +        colresize(c, r);
       +        colmousebut(c);
       +}
       +
       +void
       +rowclose(Row *row, Column *c, int dofree)
       +{
       +        Rectangle r;
       +        int i;
       +
       +        for(i=0; i<row->ncol; i++)
       +                if(row->col[i] == c)
       +                        goto Found;
       +        error("can't find column");
       +  Found:
       +        r = c->r;
       +        if(dofree)
       +                colcloseall(c);
       +        memmove(row->col+i, row->col+i+1, (row->ncol-i)*sizeof(Column*));
       +        row->ncol--;
       +        row->col = realloc(row->col, row->ncol*sizeof(Column*));
       +        if(row->ncol == 0){
       +                draw(screen, r, display->white, nil, ZP);
       +                return;
       +        }
       +        if(i == row->ncol){                /* extend last column right */
       +                c = row->col[i-1];
       +                r.min.x = c->r.min.x;
       +                r.max.x = row->r.max.x;
       +        }else{                        /* extend next window left */
       +                c = row->col[i];
       +                r.max.x = c->r.max.x;
       +        }
       +        draw(screen, r, display->white, nil, ZP);
       +        colresize(c, r);
       +}
       +
       +Column*
       +rowwhichcol(Row *row, Point p)
       +{
       +        int i;
       +        Column *c;
       +
       +        for(i=0; i<row->ncol; i++){
       +                c = row->col[i];
       +                if(ptinrect(p, c->r))
       +                        return c;
       +        }
       +        return nil;
       +}
       +
       +Text*
       +rowwhich(Row *row, Point p)
       +{
       +        Column *c;
       +
       +        if(ptinrect(p, row->tag.all))
       +                return &row->tag;
       +        c = rowwhichcol(row, p);
       +        if(c)
       +                return colwhich(c, p);
       +        return nil;
       +}
       +
       +Text*
       +rowtype(Row *row, Rune r, Point p)
       +{
       +        Window *w;
       +        Text *t;
       +
       +        clearmouse();
       +        qlock(&row->lk);
       +        if(bartflag)
       +                t = barttext;
       +        else
       +                t = rowwhich(row, p);
       +        if(t!=nil && !(t->what==Tag && ptinrect(p, t->scrollr))){
       +                w = t->w;
       +                if(w == nil)
       +                        texttype(t, r);
       +                else{
       +                        winlock(w, 'K');
       +                        wintype(w, t, r);
       +                        winunlock(w);
       +                }
       +        }
       +        qunlock(&row->lk);
       +        return t;
       +}
       +
       +int
       +rowclean(Row *row)
       +{
       +        int clean;
       +        int i;
       +
       +        clean = TRUE;
       +        for(i=0; i<row->ncol; i++)
       +                clean &= colclean(row->col[i]);
       +        return clean;
       +}
       +
       +void
       +rowdump(Row *row, char *file)
       +{
       +        int i, j, fd, m, n, dumped;
       +        uint q0, q1;
       +        Biobuf *b;
       +        char *buf, *a, *fontname;
       +        Rune *r;
       +        Column *c;
       +        Window *w, *w1;
       +        Text *t;
       +
       +        if(row->ncol == 0)
       +                return;
       +        buf = fbufalloc();
       +        if(file == nil){
       +                if(home == nil){
       +                        warning(nil, "can't find file for dump: $home not defined\n");
       +                        goto Rescue;
       +                }
       +                sprint(buf, "%s/acme.dump", home);
       +                file = buf;
       +        }
       +        fd = create(file, OWRITE, 0600);
       +        if(fd < 0){
       +                warning(nil, "can't open %s: %r\n", file);
       +                goto Rescue;
       +        }
       +        b = emalloc(sizeof(Biobuf));
       +        Binit(b, fd, OWRITE);
       +        r = fbufalloc();
       +        Bprint(b, "%s\n", wdir);
       +        Bprint(b, "%s\n", fontnames[0]);
       +        Bprint(b, "%s\n", fontnames[1]);
       +        for(i=0; i<row->ncol; i++){
       +                c = row->col[i];
       +                Bprint(b, "%11d", 100*(c->r.min.x-row->r.min.x)/Dx(row->r));
       +                if(i == row->ncol-1)
       +                        Bputc(b, '\n');
       +                else
       +                        Bputc(b, ' ');
       +        }
       +        for(i=0; i<row->ncol; i++){
       +                c = row->col[i];
       +                for(j=0; j<c->nw; j++)
       +                        c->w[j]->body.file->dumpid = 0;
       +        }
       +        for(i=0; i<row->ncol; i++){
       +                c = row->col[i];
       +                for(j=0; j<c->nw; j++){
       +                        w = c->w[j];
       +                        wincommit(w, &w->tag);
       +                        t = &w->body;
       +                        /* windows owned by others get special treatment */
       +                        if(w->nopen[QWevent] > 0)
       +                                if(w->dumpstr == nil)
       +                                        continue;
       +                        /* zeroxes of external windows are tossed */
       +                        if(t->file->ntext > 1)
       +                                for(n=0; n<t->file->ntext; n++){
       +                                        w1 = t->file->text[n]->w;
       +                                        if(w == w1)
       +                                                continue;
       +                                        if(w1->nopen[QWevent])
       +                                                goto Continue2;
       +                                }
       +                        fontname = "";
       +                        if(t->reffont->f != font)
       +                                fontname = t->reffont->f->name;
       +                        if(t->file->nname)
       +                                a = runetobyte(t->file->name, t->file->nname);
       +                        else
       +                                a = emalloc(1);
       +                        if(t->file->dumpid){
       +                                dumped = FALSE;
       +                                Bprint(b, "x%11d %11d %11d %11d %11d %s\n", i, t->file->dumpid,
       +                                        w->body.q0, w->body.q1,
       +                                        100*(w->r.min.y-c->r.min.y)/Dy(c->r),
       +                                        fontname);
       +                        }else if(w->dumpstr){
       +                                dumped = FALSE;
       +                                Bprint(b, "e%11d %11d %11d %11d %11d %s\n", i, t->file->dumpid,
       +                                        0, 0,
       +                                        100*(w->r.min.y-c->r.min.y)/Dy(c->r),
       +                                        fontname);
       +                        }else if((w->dirty==FALSE && access(a, 0)==0) || w->isdir){
       +                                dumped = FALSE;
       +                                t->file->dumpid = w->id;
       +                                Bprint(b, "f%11d %11d %11d %11d %11d %s\n", i, w->id,
       +                                        w->body.q0, w->body.q1,
       +                                        100*(w->r.min.y-c->r.min.y)/Dy(c->r),
       +                                        fontname);
       +                        }else{
       +                                dumped = TRUE;
       +                                t->file->dumpid = w->id;
       +                                Bprint(b, "F%11d %11d %11d %11d %11d %11d %s\n", i, j,
       +                                        w->body.q0, w->body.q1,
       +                                        100*(w->r.min.y-c->r.min.y)/Dy(c->r),
       +                                        w->body.file->b.nc, fontname);
       +                        }
       +                        free(a);
       +                        winctlprint(w, buf, 0);
       +                        Bwrite(b, buf, strlen(buf));
       +                        m = min(RBUFSIZE, w->tag.file->b.nc);
       +                        bufread(&w->tag.file->b, 0, r, m);
       +                        n = 0;
       +                        while(n<m && r[n]!='\n')
       +                                n++;
       +                        r[n++] = '\n';
       +                        Bprint(b, "%.*S", n, r);
       +                        if(dumped){
       +                                q0 = 0;
       +                                q1 = t->file->b.nc;
       +                                while(q0 < q1){
       +                                        n = q1 - q0;
       +                                        if(n > BUFSIZE/UTFmax)
       +                                                n = BUFSIZE/UTFmax;
       +                                        bufread(&t->file->b, q0, r, n);
       +                                        Bprint(b, "%.*S", n, r);
       +                                        q0 += n;
       +                                }
       +                        }
       +                        if(w->dumpstr){
       +                                if(w->dumpdir)
       +                                        Bprint(b, "%s\n%s\n", w->dumpdir, w->dumpstr);
       +                                else
       +                                        Bprint(b, "\n%s\n", w->dumpstr);
       +                        }
       +    Continue2:;
       +                }
       +        }
       +        Bterm(b);
       +        close(fd);
       +        free(b);
       +        fbuffree(r);
       +
       +   Rescue:
       +        fbuffree(buf);
       +}
       +
       +static
       +char*
       +rdline(Biobuf *b, int *linep)
       +{
       +        char *l;
       +
       +        l = Brdline(b, '\n');
       +        if(l)
       +                (*linep)++;
       +        return l;
       +}
       +
       +/*
       + * Get font names from load file so we don't load fonts we won't use
       + */
       +void
       +rowloadfonts(char *file)
       +{
       +        int i;
       +        Biobuf *b;
       +        char *l;
       +
       +        b = Bopen(file, OREAD);
       +        if(b == nil)
       +                return;
       +        /* current directory */
       +        l = Brdline(b, '\n');
       +        if(l == nil)
       +                goto Return;
       +        /* global fonts */
       +        for(i=0; i<2; i++){
       +                l = Brdline(b, '\n');
       +                if(l == nil)
       +                        goto Return;
       +                l[Blinelen(b)-1] = 0;
       +                if(*l && strcmp(l, fontnames[i])!=0)
       +                        fontnames[i] = estrdup(l);
       +        }
       +    Return:
       +        Bterm(b);
       +}
       +
       +void
       +rowload(Row *row, char *file, int initing)
       +{
       +        int i, j, line, percent, y, nr, nfontr, n, ns, ndumped, dumpid, x, fd;
       +        Biobuf *b, *bout;
       +        char *buf, *l, *t, *fontname;
       +        Rune *r, rune, *fontr;
       +        Column *c, *c1, *c2;
       +        uint q0, q1;
       +        Rectangle r1, r2;
       +        Window *w;
       +
       +        buf = fbufalloc();
       +        if(file == nil){
       +                if(home == nil){
       +                        warning(nil, "can't find file for load: $home not defined\n");
       +                        goto Rescue1;
       +                }
       +                sprint(buf, "%s/acme.dump", home);
       +                file = buf;
       +        }
       +        b = Bopen(file, OREAD);
       +        if(b == nil){
       +                warning(nil, "can't open load file %s: %r\n", file);
       +                goto Rescue1;
       +        }
       +        /* current directory */
       +        line = 0;
       +        l = rdline(b, &line);
       +        if(l == nil)
       +                goto Rescue2;
       +        l[Blinelen(b)-1] = 0;
       +        if(chdir(l) < 0){
       +                warning(nil, "can't chdir %s\n", l);
       +                goto Rescue2;
       +        }
       +        /* global fonts */
       +        for(i=0; i<2; i++){
       +                l = rdline(b, &line);
       +                if(l == nil)
       +                        goto Rescue2;
       +                l[Blinelen(b)-1] = 0;
       +                if(*l && strcmp(l, fontnames[i])!=0)
       +                        rfget(i, TRUE, i==0 && initing, estrdup(l));
       +        }
       +        if(initing && row->ncol==0)
       +                rowinit(row, screen->clipr);
       +        l = rdline(b, &line);
       +        if(l == nil)
       +                goto Rescue2;
       +        j = Blinelen(b)/12;
       +        if(j<=0 || j>10)
       +                goto Rescue2;
       +        for(i=0; i<j; i++){
       +                percent = atoi(l+i*12);
       +                if(percent<0 || percent>=100)
       +                        goto Rescue2;
       +                x = row->r.min.x+percent*Dx(row->r)/100;
       +                if(i < row->ncol){
       +                        if(i == 0)
       +                                continue;
       +                        c1 = row->col[i-1];
       +                        c2 = row->col[i];
       +                        r1 = c1->r;
       +                        r2 = c2->r;
       +                        r1.max.x = x;
       +                        r2.min.x = x+Border;
       +                        if(Dx(r1) < 50 || Dx(r2) < 50)
       +                                continue;
       +                        draw(screen, Rpt(r1.min, r2.max), display->white, nil, ZP);
       +                        colresize(c1, r1);
       +                        colresize(c2, r2);
       +                        r2.min.x = x;
       +                        r2.max.x = x+Border;
       +                        draw(screen, r2, display->black, nil, ZP);
       +                }
       +                if(i >= row->ncol)
       +                        rowadd(row, nil, x);
       +        }
       +        for(;;){
       +                l = rdline(b, &line);
       +                if(l == nil)
       +                        break;
       +                dumpid = 0;
       +                switch(l[0]){
       +                case 'e':
       +                        if(Blinelen(b) < 1+5*12+1)
       +                                goto Rescue2;
       +                        l = rdline(b, &line);        /* ctl line; ignored */
       +                        if(l == nil)
       +                                goto Rescue2;
       +                        l = rdline(b, &line);        /* directory */
       +                        if(l == nil)
       +                                goto Rescue2;
       +                        l[Blinelen(b)-1] = 0;
       +                        if(*l == '\0'){
       +                                if(home == nil)
       +                                        r = bytetorune("./", &nr);
       +                                else{
       +                                        t = emalloc(strlen(home)+1+1);
       +                                        sprint(t, "%s/", home);
       +                                        r = bytetorune(t, &nr);
       +                                        free(t);
       +                                }
       +                        }else
       +                                r = bytetorune(l, &nr);
       +                        l = rdline(b, &line);        /* command */
       +                        if(l == nil)
       +                                goto Rescue2;
       +                        t = emalloc(Blinelen(b)+1);
       +                        memmove(t, l, Blinelen(b));
       +                        run(nil, t, r, nr, TRUE, nil, nil, FALSE);
       +                        /* r is freed in run() */
       +                        continue;
       +                case 'f':
       +                        if(Blinelen(b) < 1+5*12+1)
       +                                goto Rescue2;
       +                        fontname = l+1+5*12;
       +                        ndumped = -1;
       +                        break;
       +                case 'F':
       +                        if(Blinelen(b) < 1+6*12+1)
       +                                goto Rescue2;
       +                        fontname = l+1+6*12;
       +                        ndumped = atoi(l+1+5*12+1);
       +                        break;
       +                case 'x':
       +                        if(Blinelen(b) < 1+5*12+1)
       +                                goto Rescue2;
       +                        fontname = l+1+5*12;
       +                        ndumped = -1;
       +                        dumpid = atoi(l+1+1*12);
       +                        break;
       +                default:
       +                        goto Rescue2;
       +                }
       +                l[Blinelen(b)-1] = 0;
       +                fontr = nil;
       +                nfontr = 0;
       +                if(*fontname)
       +                        fontr = bytetorune(fontname, &nfontr);
       +                i = atoi(l+1+0*12);
       +                j = atoi(l+1+1*12);
       +                q0 = atoi(l+1+2*12);
       +                q1 = atoi(l+1+3*12);
       +                percent = atoi(l+1+4*12);
       +                if(i<0 || i>10)
       +                        goto Rescue2;
       +                if(i > row->ncol)
       +                        i = row->ncol;
       +                c = row->col[i];
       +                y = c->r.min.y+(percent*Dy(c->r))/100;
       +                if(y<c->r.min.y || y>=c->r.max.y)
       +                        y = -1;
       +                if(dumpid == 0)
       +                        w = coladd(c, nil, nil, y);
       +                else
       +                        w = coladd(c, nil, lookid(dumpid, TRUE), y);
       +                if(w == nil)
       +                        continue;
       +                w->dumpid = j;
       +                l = rdline(b, &line);
       +                if(l == nil)
       +                        goto Rescue2;
       +                l[Blinelen(b)-1] = 0;
       +                r = bytetorune(l+5*12, &nr);
       +                ns = -1;
       +                for(n=0; n<nr; n++){
       +                        if(r[n] == '/')
       +                                ns = n;
       +                        if(r[n] == ' ')
       +                                break;
       +                }
       +                if(dumpid == 0)
       +                        winsetname(w, r, n);
       +                for(; n<nr; n++)
       +                        if(r[n] == '|')
       +                                break;
       +                wincleartag(w);
       +                textinsert(&w->tag, w->tag.file->b.nc, r+n+1, nr-(n+1), TRUE);
       +                free(r);
       +                if(ndumped >= 0){
       +                        /* simplest thing is to put it in a file and load that */
       +                        sprint(buf, "/tmp/d%d.%.4sacme", getpid(), getuser());
       +                        fd = create(buf, OWRITE|ORCLOSE, 0600);
       +                        if(fd < 0){
       +                                warning(nil, "can't create temp file: %r\n");
       +                                goto Rescue2;
       +                        }
       +                        bout = emalloc(sizeof(Biobuf));
       +                        Binit(bout, fd, OWRITE);
       +                        for(n=0; n<ndumped; n++){
       +                                rune = Bgetrune(b);
       +                                if(rune == '\n')
       +                                        line++;
       +                                if(rune == (Rune)Beof){
       +                                        Bterm(bout);
       +                                        free(bout);
       +                                        close(fd);
       +                                        goto Rescue2;
       +                                }
       +                                Bputrune(bout, rune);
       +                        }
       +                        Bterm(bout);
       +                        free(bout);
       +                        textload(&w->body, 0, buf, 1);
       +                        close(fd);
       +                        w->body.file->mod = TRUE;
       +                        for(n=0; n<w->body.file->ntext; n++)
       +                                w->body.file->text[n]->w->dirty = TRUE;
       +                        winsettag(w);
       +                }else if(dumpid==0 && r[ns+1]!='+' && r[ns+1]!='-')
       +                        get(&w->body, nil, nil, FALSE, XXX, nil, 0);
       +                if(fontr){
       +                        fontx(&w->body, nil, nil, 0, 0, fontr, nfontr);
       +                        free(fontr);
       +                }
       +                if(q0>w->body.file->b.nc || q1>w->body.file->b.nc || q0>q1)
       +                        q0 = q1 = 0;
       +                textshow(&w->body, q0, q1, 1);
       +                w->maxlines = min(w->body.fr.nlines, max(w->maxlines, w->body.fr.maxlines));
       +        }
       +        Bterm(b);
       +
       +Rescue1:
       +        fbuffree(buf);
       +        return;
       +
       +Rescue2:
       +        warning(nil, "bad load file %s:%d\n", file, line);
       +        Bterm(b);
       +        goto Rescue1;
       +}
       +
       +void
       +allwindows(void (*f)(Window*, void*), void *arg)
       +{
       +        int i, j;
       +        Column *c;
       +
       +        for(i=0; i<row.ncol; i++){
       +                c = row.col[i];
       +                for(j=0; j<c->nw; j++)
       +                        (*f)(c->w[j], arg);
       +        }
       +}
 (DIR) diff --git a/src/cmd/acme/scrl.c b/src/cmd/acme/scrl.c
       t@@ -0,0 +1,165 @@
       +#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 "fns.h"
       +
       +static Image *scrtmp;
       +
       +static
       +Rectangle
       +scrpos(Rectangle r, uint p0, uint p1, uint tot)
       +{
       +        Rectangle q;
       +        int h;
       +
       +        q = r;
       +        h = q.max.y-q.min.y;
       +        if(tot == 0)
       +                return q;
       +        if(tot > 1024*1024){
       +                tot>>=10;
       +                p0>>=10;
       +                p1>>=10;
       +        }
       +        if(p0 > 0)
       +                q.min.y += h*p0/tot;
       +        if(p1 < tot)
       +                q.max.y -= h*(tot-p1)/tot;
       +        if(q.max.y < q.min.y+2){
       +                if(q.min.y+2 <= r.max.y)
       +                        q.max.y = q.min.y+2;
       +                else
       +                        q.min.y = q.max.y-2;
       +        }
       +        return q;
       +}
       +
       +void
       +scrlresize(void)
       +{
       +        freeimage(scrtmp);
       +        scrtmp = allocimage(display, Rect(0, 0, 32, screen->r.max.y), screen->chan, 0, DNofill);
       +        if(scrtmp == nil)
       +                error("scroll alloc");
       +}
       +
       +void
       +textscrdraw(Text *t)
       +{
       +        Rectangle r, r1, r2;
       +        Image *b;
       +
       +        if(t->w==nil || t!=&t->w->body)
       +                return;
       +        if(scrtmp == nil)
       +                scrlresize();
       +        r = t->scrollr;
       +        b = scrtmp;
       +        r1 = r;
       +        r1.min.x = 0;
       +        r1.max.x = Dx(r);
       +        r2 = scrpos(r1, t->org, t->org+t->fr.nchars, t->file->b.nc);
       +        if(!eqrect(r2, t->lastsr)){
       +                t->lastsr = r2;
       +                draw(b, r1, t->fr.cols[BORD], nil, ZP);
       +                draw(b, r2, t->fr.cols[BACK], nil, ZP);
       +                r2.min.x = r2.max.x-1;
       +                draw(b, r2, t->fr.cols[BORD], nil, ZP);
       +                draw(t->fr.b, r, b, nil, Pt(0, r1.min.y));
       +/*flushimage(display, 1);*//*BUG?*/
       +        }
       +}
       +
       +void
       +scrsleep(uint dt)
       +{
       +        Timer        *timer;
       +        static Alt alts[3];
       +
       +        timer = timerstart(dt);
       +        alts[0].c = timer->c;
       +        alts[0].v = nil;
       +        alts[0].op = CHANRCV;
       +        alts[1].c = mousectl->c;
       +        alts[1].v = &mousectl->m;
       +        alts[1].op = CHANRCV;
       +        alts[2].op = CHANEND;
       +        for(;;)
       +                switch(alt(alts)){
       +                case 0:
       +                        timerstop(timer);
       +                        return;
       +                case 1:
       +                        timercancel(timer);
       +                        return;
       +                }
       +}
       +
       +void
       +textscroll(Text *t, int but)
       +{
       +        uint p0, oldp0;
       +        Rectangle s;
       +        int x, y, my, h, first;
       +
       +        s = insetrect(t->scrollr, 1);
       +        h = s.max.y-s.min.y;
       +        x = (s.min.x+s.max.x)/2;
       +        oldp0 = ~0;
       +        first = TRUE;
       +        do{
       +                flushimage(display, 1);
       +                if(mouse->xy.x<s.min.x || s.max.x<=mouse->xy.x){
       +                        readmouse(mousectl);
       +                }else{
       +                        my = mouse->xy.y;
       +                        if(my < s.min.y)
       +                                my = s.min.y;
       +                        if(my >= s.max.y)
       +                                my = s.max.y;
       +                        if(!eqpt(mouse->xy, Pt(x, my))){
       +                                moveto(mousectl, Pt(x, my));
       +                                readmouse(mousectl);                /* absorb event generated by moveto() */
       +                        }
       +                        if(but == 2){
       +                                y = my;
       +                                if(y > s.max.y-2)
       +                                        y = s.max.y-2;
       +                                if(t->file->b.nc > 1024*1024)
       +                                        p0 = ((t->file->b.nc>>10)*(y-s.min.y)/h)<<10;
       +                                else
       +                                        p0 = t->file->b.nc*(y-s.min.y)/h;
       +                                if(oldp0 != p0)
       +                                        textsetorigin(t, p0, FALSE);
       +                                oldp0 = p0;
       +                                readmouse(mousectl);
       +                                continue;
       +                        }
       +                        if(but == 1)
       +                                p0 = textbacknl(t, t->org, (my-s.min.y)/t->fr.font->height);
       +                        else
       +                                p0 = t->org+frcharofpt(&t->fr, Pt(s.max.x, my));
       +                        if(oldp0 != p0)
       +                                textsetorigin(t, p0, TRUE);
       +                        oldp0 = p0;
       +                        /* debounce */
       +                        if(first){
       +                                flushimage(display, 1);
       +                                sleep(200);
       +                                nbrecv(mousectl->c, &mousectl->m);
       +                                first = FALSE;
       +                        }
       +                        scrsleep(80);
       +                }
       +        }while(mouse->buttons & (1<<(but-1)));
       +        while(mouse->buttons)
       +                readmouse(mousectl);
       +}
 (DIR) diff --git a/src/cmd/acme/text.c b/src/cmd/acme/text.c
       t@@ -0,0 +1,1221 @@
       +#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 "fns.h"
       +
       +Image        *tagcols[NCOL];
       +Image        *textcols[NCOL];
       +
       +enum{
       +        TABDIR = 3        /* width of tabs in directory windows */
       +};
       +
       +void
       +textinit(Text *t, File *f, Rectangle r, Reffont *rf, Image *cols[NCOL])
       +{
       +        t->file = f;
       +        t->all = r;
       +        t->scrollr = r;
       +        t->scrollr.max.x = r.min.x+Scrollwid;
       +        t->lastsr = nullrect;
       +        r.min.x += Scrollwid+Scrollgap;
       +        t->eq0 = ~0;
       +        t->ncache = 0;
       +        t->reffont = rf;
       +        t->tabstop = maxtab;
       +        memmove(t->fr.cols, cols, sizeof t->fr.cols);
       +        textredraw(t, r, rf->f, screen, -1);
       +}
       +
       +void
       +textredraw(Text *t, Rectangle r, Font *f, Image *b, int odx)
       +{
       +        int maxt;
       +        Rectangle rr;
       +
       +        frinit(&t->fr, r, f, b, t->fr.cols);
       +        rr = t->fr.r;
       +        rr.min.x -= Scrollwid;        /* back fill to scroll bar */
       +        draw(t->fr.b, rr, t->fr.cols[BACK], nil, ZP);
       +        /* use no wider than 3-space tabs in a directory */
       +        maxt = maxtab;
       +        if(t->what == Body){
       +                if(t->w->isdir)
       +                        maxt = min(TABDIR, maxtab);
       +                else
       +                        maxt = t->tabstop;
       +        }
       +        t->fr.maxtab = maxt*stringwidth(f, "0");
       +        if(t->what==Body && t->w->isdir && odx!=Dx(t->all)){
       +                if(t->fr.maxlines > 0){
       +                        textreset(t);
       +                        textcolumnate(t, t->w->dlp,  t->w->ndl);
       +                        textshow(t, 0, 0, 1);
       +                }
       +        }else{
       +                textfill(t);
       +                textsetselect(t, t->q0, t->q1);
       +        }
       +}
       +
       +int
       +textresize(Text *t, Rectangle r)
       +{
       +        int odx;
       +
       +        if(Dy(r) > 0)
       +                r.max.y -= Dy(r)%t->fr.font->height;
       +        else
       +                r.max.y = r.min.y;
       +        odx = Dx(t->all);
       +        t->all = r;
       +        t->scrollr = r;
       +        t->scrollr.max.x = r.min.x+Scrollwid;
       +        t->lastsr = nullrect;
       +        r.min.x += Scrollwid+Scrollgap;
       +        frclear(&t->fr, 0);
       +        textredraw(t, r, t->fr.font, t->fr.b, odx);
       +        return r.max.y;
       +}
       +
       +void
       +textclose(Text *t)
       +{
       +        free(t->cache);
       +        frclear(&t->fr, 1);
       +        filedeltext(t->file, t);
       +        t->file = nil;
       +        rfclose(t->reffont);
       +        if(argtext == t)
       +                argtext = nil;
       +        if(typetext == t)
       +                typetext = nil;
       +        if(seltext == t)
       +                seltext = nil;
       +        if(mousetext == t)
       +                mousetext = nil;
       +        if(barttext == t)
       +                barttext = nil;
       +}
       +
       +int
       +dircmp(const void *a, const void *b)
       +{
       +        Dirlist *da, *db;
       +        int i, n;
       +
       +        da = *(Dirlist**)a;
       +        db = *(Dirlist**)b;
       +        n = min(da->nr, db->nr);
       +        i = memcmp(da->r, db->r, n*sizeof(Rune));
       +        if(i)
       +                return i;
       +        return da->nr - db->nr;
       +}
       +
       +void
       +textcolumnate(Text *t, Dirlist **dlp, int ndl)
       +{
       +        int i, j, w, colw, mint, maxt, ncol, nrow;
       +        Dirlist *dl;
       +        uint q1;
       +        static Rune Lnl[] = { '\n', 0 };
       +        static Rune Ltab[] = { '\t', 0 };
       +
       +        if(t->file->ntext > 1)
       +                return;
       +        mint = stringwidth(t->fr.font, "0");
       +        /* go for narrower tabs if set more than 3 wide */
       +        t->fr.maxtab = min(maxtab, TABDIR)*mint;
       +        maxt = t->fr.maxtab;
       +        colw = 0;
       +        for(i=0; i<ndl; i++){
       +                dl = dlp[i];
       +                w = dl->wid;
       +                if(maxt-w%maxt < mint || w%maxt==0)
       +                        w += mint;
       +                if(w % maxt)
       +                        w += maxt-(w%maxt);
       +                if(w > colw)
       +                        colw = w;
       +        }
       +        if(colw == 0)
       +                ncol = 1;
       +        else
       +                ncol = max(1, Dx(t->fr.r)/colw);
       +        nrow = (ndl+ncol-1)/ncol;
       +
       +        q1 = 0;
       +        for(i=0; i<nrow; i++){
       +                for(j=i; j<ndl; j+=nrow){
       +                        dl = dlp[j];
       +                        fileinsert(t->file, q1, dl->r, dl->nr);
       +                        q1 += dl->nr;
       +                        if(j+nrow >= ndl)
       +                                break;
       +                        w = dl->wid;
       +                        if(maxt-w%maxt < mint){
       +                                fileinsert(t->file, q1, Ltab, 1);
       +                                q1++;
       +                                w += mint;
       +                        }
       +                        do{
       +                                fileinsert(t->file, q1, Ltab, 1);
       +                                q1++;
       +                                w += maxt-(w%maxt);
       +                        }while(w < colw);
       +                }
       +                fileinsert(t->file, q1, Lnl, 1);
       +                q1++;
       +        }
       +}
       +
       +uint
       +textload(Text *t, uint q0, char *file, int setqid)
       +{
       +        Rune *rp;
       +        Dirlist *dl, **dlp;
       +        int fd, i, j, n, ndl, nulls;
       +        uint q, q1;
       +        Dir *d, *dbuf;
       +        char *tmp;
       +        Text *u;
       +
       +        if(t->ncache!=0 || t->file->b.nc || t->w==nil || t!=&t->w->body || (t->w->isdir && t->file->nname==0))
       +                error("text.load");
       +        fd = open(file, OREAD);
       +        if(fd < 0){
       +                warning(nil, "can't open %s: %r\n", file);
       +                return 0;
       +        }
       +        d = dirfstat(fd);
       +        if(d == nil){
       +                warning(nil, "can't fstat %s: %r\n", file);
       +                goto Rescue;
       +        }
       +        nulls = FALSE;
       +        if(d->qid.type & QTDIR){
       +                /* this is checked in get() but it's possible the file changed underfoot */
       +                if(t->file->ntext > 1){
       +                        warning(nil, "%s is a directory; can't read with multiple windows on it\n", file);
       +                        goto Rescue;
       +                }
       +                t->w->isdir = TRUE;
       +                t->w->filemenu = FALSE;
       +                if(t->file->name[t->file->nname-1] != '/'){
       +                        rp = runemalloc(t->file->nname+1);
       +                        runemove(rp, t->file->name, t->file->nname);
       +                        rp[t->file->nname] = '/';
       +                        winsetname(t->w, rp, t->file->nname+1);
       +                        free(rp);
       +                }
       +                dlp = nil;
       +                ndl = 0;
       +                dbuf = nil;
       +                while((n=dirread(fd, &dbuf)) > 0){
       +                        for(i=0; i<n; i++){
       +                                dl = emalloc(sizeof(Dirlist));
       +                                j = strlen(dbuf[i].name);
       +                                tmp = emalloc(j+1+1);
       +                                memmove(tmp, dbuf[i].name, j);
       +                                if(dbuf[i].qid.type & QTDIR)
       +                                        tmp[j++] = '/';
       +                                tmp[j] = '\0';
       +                                dl->r = bytetorune(tmp, &dl->nr);
       +                                dl->wid = stringwidth(t->fr.font, tmp);
       +                                free(tmp);
       +                                ndl++;
       +                                dlp = realloc(dlp, ndl*sizeof(Dirlist*));
       +                                dlp[ndl-1] = dl;
       +                        }
       +                        free(dbuf);
       +                }
       +                qsort(dlp, ndl, sizeof(Dirlist*), dircmp);
       +                t->w->dlp = dlp;
       +                t->w->ndl = ndl;
       +                textcolumnate(t, dlp, ndl);
       +                q1 = t->file->b.nc;
       +        }else{
       +                t->w->isdir = FALSE;
       +                t->w->filemenu = TRUE;
       +                q1 = q0 + fileload(t->file, q0, fd, &nulls);
       +        }
       +        if(setqid){
       +                t->file->dev = d->dev;
       +                t->file->mtime = d->mtime;
       +                t->file->qidpath = d->qid.path;
       +        }
       +        close(fd);
       +        rp = fbufalloc();
       +        for(q=q0; q<q1; q+=n){
       +                n = q1-q;
       +                if(n > RBUFSIZE)
       +                        n = RBUFSIZE;
       +                bufread(&t->file->b, q, rp, n);
       +                if(q < t->org)
       +                        t->org += n;
       +                else if(q <= t->org+t->fr.nchars)
       +                        frinsert(&t->fr, rp, rp+n, q-t->org);
       +                if(t->fr.lastlinefull)
       +                        break;
       +        }
       +        fbuffree(rp);
       +        for(i=0; i<t->file->ntext; i++){
       +                u = t->file->text[i];
       +                if(u != t){
       +                        if(u->org > u->file->b.nc)        /* will be 0 because of reset(), but safety first */
       +                                u->org = 0;
       +                        textresize(u, u->all);
       +                        textbacknl(u, u->org, 0);        /* go to beginning of line */
       +                }
       +                textsetselect(u, q0, q0);
       +        }
       +        if(nulls)
       +                warning(nil, "%s: NUL bytes elided\n", file);
       +        free(d);
       +        return q1-q0;
       +
       +    Rescue:
       +        close(fd);
       +        return 0;
       +}
       +
       +uint
       +textbsinsert(Text *t, uint q0, Rune *r, uint n, int tofile, int *nrp)
       +{
       +        Rune *bp, *tp, *up;
       +        int i, initial;
       +
       +        if(t->what == Tag){        /* can't happen but safety first: mustn't backspace over file name */
       +    Err:
       +                textinsert(t, q0, r, n, tofile);
       +                *nrp = n;
       +                return q0;
       +        }
       +        bp = r;
       +        for(i=0; i<n; i++)
       +                if(*bp++ == '\b'){
       +                        --bp;
       +                        initial = 0;
       +                        tp = runemalloc(n);
       +                        runemove(tp, r, i);
       +                        up = tp+i;
       +                        for(; i<n; i++){
       +                                *up = *bp++;
       +                                if(*up == '\b')
       +                                        if(up == tp)
       +                                                initial++;
       +                                        else
       +                                                --up;
       +                                else
       +                                        up++;
       +                        }
       +                        if(initial){
       +                                if(initial > q0)
       +                                        initial = q0;
       +                                q0 -= initial;
       +                                textdelete(t, q0, q0+initial, tofile);
       +                        }
       +                        n = up-tp;
       +                        textinsert(t, q0, tp, n, tofile);
       +                        free(tp);
       +                        *nrp = n;
       +                        return q0;
       +                }
       +        goto Err;
       +}
       +
       +void
       +textinsert(Text *t, uint q0, Rune *r, uint n, int tofile)
       +{
       +        int c, i;
       +        Text *u;
       +
       +        if(tofile && t->ncache != 0)
       +                error("text.insert");
       +        if(n == 0)
       +                return;
       +        if(tofile){
       +                fileinsert(t->file, q0, r, n);
       +                if(t->what == Body){
       +                        t->w->dirty = TRUE;
       +                        t->w->utflastqid = -1;
       +                }
       +                if(t->file->ntext > 1)
       +                        for(i=0; i<t->file->ntext; i++){
       +                                u = t->file->text[i];
       +                                if(u != t){
       +                                        u->w->dirty = TRUE;        /* always a body */
       +                                        textinsert(u, q0, r, n, FALSE);
       +                                        textsetselect(u, u->q0, u->q1);
       +                                        textscrdraw(u);
       +                                }
       +                        }
       +                                        
       +        }
       +        if(q0 < t->q1)
       +                t->q1 += n;
       +        if(q0 < t->q0)
       +                t->q0 += n;
       +        if(q0 < t->org)
       +                t->org += n;
       +        else if(q0 <= t->org+t->fr.nchars)
       +                frinsert(&t->fr, r, r+n, q0-t->org);
       +        if(t->w){
       +                c = 'i';
       +                if(t->what == Body)
       +                        c = 'I';
       +                if(n <= EVENTSIZE)
       +                        winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q0+n, n, n, r);
       +                else
       +                        winevent(t->w, "%c%d %d 0 0 \n", c, q0, q0+n, n);
       +        }
       +}
       +
       +
       +void
       +textfill(Text *t)
       +{
       +        Rune *rp;
       +        int i, n, m, nl;
       +
       +        if(t->fr.lastlinefull || t->nofill)
       +                return;
       +        if(t->ncache > 0){
       +                if(t->w != nil)
       +                        wincommit(t->w, t);
       +                else
       +                        textcommit(t, TRUE);
       +        }
       +        rp = fbufalloc();
       +        do{
       +                n = t->file->b.nc-(t->org+t->fr.nchars);
       +                if(n == 0)
       +                        break;
       +                if(n > 2000)        /* educated guess at reasonable amount */
       +                        n = 2000;
       +                bufread(&t->file->b, t->org+t->fr.nchars, rp, n);
       +                /*
       +                 * it's expensive to frinsert more than we need, so
       +                 * count newlines.
       +                 */
       +                nl = t->fr.maxlines-t->fr.nlines;
       +                m = 0;
       +                for(i=0; i<n; ){
       +                        if(rp[i++] == '\n'){
       +                                m++;
       +                                if(m >= nl)
       +                                        break;
       +                        }
       +                }
       +                frinsert(&t->fr, rp, rp+i, t->fr.nchars);
       +        }while(t->fr.lastlinefull == FALSE);
       +        fbuffree(rp);
       +}
       +
       +void
       +textdelete(Text *t, uint q0, uint q1, int tofile)
       +{
       +        uint n, p0, p1;
       +        int i, c;
       +        Text *u;
       +
       +        if(tofile && t->ncache != 0)
       +                error("text.delete");
       +        n = q1-q0;
       +        if(n == 0)
       +                return;
       +        if(tofile){
       +                filedelete(t->file, q0, q1);
       +                if(t->what == Body){
       +                        t->w->dirty = TRUE;
       +                        t->w->utflastqid = -1;
       +                }
       +                if(t->file->ntext > 1)
       +                        for(i=0; i<t->file->ntext; i++){
       +                                u = t->file->text[i];
       +                                if(u != t){
       +                                        u->w->dirty = TRUE;        /* always a body */
       +                                        textdelete(u, q0, q1, FALSE);
       +                                        textsetselect(u, u->q0, u->q1);
       +                                        textscrdraw(u);
       +                                }
       +                        }
       +        }
       +        if(q0 < t->q0)
       +                t->q0 -= min(n, t->q0-q0);
       +        if(q0 < t->q1)
       +                t->q1 -= min(n, t->q1-q0);
       +        if(q1 <= t->org)
       +                t->org -= n;
       +        else if(q0 < t->org+t->fr.nchars){
       +                p1 = q1 - t->org;
       +                if(p1 > t->fr.nchars)
       +                        p1 = t->fr.nchars;
       +                if(q0 < t->org){
       +                        t->org = q0;
       +                        p0 = 0;
       +                }else
       +                        p0 = q0 - t->org;
       +                frdelete(&t->fr, p0, p1);
       +                textfill(t);
       +        }
       +        if(t->w){
       +                c = 'd';
       +                if(t->what == Body)
       +                        c = 'D';
       +                winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1);
       +        }
       +}
       +
       +void
       +textconstrain(Text *t, uint q0, uint q1, uint *p0, uint *p1)
       +{
       +        *p0 = min(q0, t->file->b.nc);
       +        *p1 = min(q1, t->file->b.nc);
       +}
       +
       +Rune
       +textreadc(Text *t, uint q)
       +{
       +        Rune r;
       +
       +        if(t->cq0<=q && q<t->cq0+t->ncache)
       +                r = t->cache[q-t->cq0];
       +        else
       +                bufread(&t->file->b, q, &r, 1);
       +        return r;
       +}
       +
       +int
       +textbswidth(Text *t, Rune c)
       +{
       +        uint q, eq;
       +        Rune r;
       +        int skipping;
       +
       +        /* there is known to be at least one character to erase */
       +        if(c == 0x08)        /* ^H: erase character */
       +                return 1;
       +        q = t->q0;
       +        skipping = TRUE;
       +        while(q > 0){
       +                r = textreadc(t, q-1);
       +                if(r == '\n'){                /* eat at most one more character */
       +                        if(q == t->q0)        /* eat the newline */
       +                                --q;
       +                        break; 
       +                }
       +                if(c == 0x17){
       +                        eq = isalnum(r);
       +                        if(eq && skipping)        /* found one; stop skipping */
       +                                skipping = FALSE;
       +                        else if(!eq && !skipping)
       +                                break;
       +                }
       +                --q;
       +        }
       +        return t->q0-q;
       +}
       +
       +void
       +texttype(Text *t, Rune r)
       +{
       +        uint q0, q1;
       +        int nnb, nb, n, i;
       +        Text *u;
       +
       +        if(t->what!=Body && r=='\n')
       +                return;
       +        switch(r){
       +        case Kdown:
       +        case Kleft:
       +        case Kright:
       +                n = t->fr.maxlines/2;
       +                q0 = t->org+frcharofpt(&t->fr, Pt(t->fr.r.min.x, t->fr.r.min.y+n*t->fr.font->height));
       +                textsetorigin(t, q0, FALSE);
       +                return;
       +        case Kup:
       +                n = t->fr.maxlines/2;
       +                q0 = textbacknl(t, t->org, n);
       +                textsetorigin(t, q0, FALSE);
       +                return;
       +        }
       +        if(t->what == Body){
       +                seq++;
       +                filemark(t->file);
       +        }
       +        if(t->q1 > t->q0){
       +                if(t->ncache != 0)
       +                        error("text.type");
       +                cut(t, t, nil, TRUE, TRUE, nil, 0);
       +                t->eq0 = ~0;
       +        }
       +        textshow(t, t->q0, t->q0, 1);
       +        switch(r){
       +        case 0x1B:
       +                if(t->eq0 != ~0)
       +                        textsetselect(t, t->eq0, t->q0);
       +                if(t->ncache > 0){
       +                        if(t->w != nil)
       +                                wincommit(t->w, t);
       +                        else
       +                                textcommit(t, TRUE);
       +                }
       +                return;
       +        case 0x08:        /* ^H: erase character */
       +        case 0x15:        /* ^U: erase line */
       +        case 0x17:        /* ^W: erase word */
       +                if(t->q0 == 0)        /* nothing to erase */
       +                        return;
       +                nnb = textbswidth(t, r);
       +                q1 = t->q0;
       +                q0 = q1-nnb;
       +                /* if selection is at beginning of window, avoid deleting invisible text */
       +                if(q0 < t->org){
       +                        q0 = t->org;
       +                        nnb = q1-q0;
       +                }
       +                if(nnb <= 0)
       +                        return;
       +                for(i=0; i<t->file->ntext; i++){
       +                        u = t->file->text[i];
       +                        u->nofill = TRUE;
       +                        nb = nnb;
       +                        n = u->ncache;
       +                        if(n > 0){
       +                                if(q1 != u->cq0+n)
       +                                        error("text.type backspace");
       +                                if(n > nb)
       +                                        n = nb;
       +                                u->ncache -= n;
       +                                textdelete(u, q1-n, q1, FALSE);
       +                                nb -= n;
       +                        }
       +                        if(u->eq0==q1 || u->eq0==~0)
       +                                u->eq0 = q0;
       +                        if(nb && u==t)
       +                                textdelete(u, q0, q0+nb, TRUE);
       +                        if(u != t)
       +                                textsetselect(u, u->q0, u->q1);
       +                        else
       +                                textsetselect(t, q0, q0);
       +                        u->nofill = FALSE;
       +                }
       +                for(i=0; i<t->file->ntext; i++)
       +                        textfill(t->file->text[i]);
       +                return;
       +        }
       +        /* otherwise ordinary character; just insert, typically in caches of all texts */
       +        for(i=0; i<t->file->ntext; i++){
       +                u = t->file->text[i];
       +                if(u->eq0 == ~0)
       +                        u->eq0 = t->q0;
       +                if(u->ncache == 0)
       +                        u->cq0 = t->q0;
       +                else if(t->q0 != u->cq0+u->ncache)
       +                        error("text.type cq1");
       +                textinsert(u, t->q0, &r, 1, FALSE);
       +                if(u != t)
       +                        textsetselect(u, u->q0, u->q1);
       +                if(u->ncache == u->ncachealloc){
       +                        u->ncachealloc += 10;
       +                        u->cache = runerealloc(u->cache, u->ncachealloc);
       +                }
       +                u->cache[u->ncache++] = r;
       +        }
       +        textsetselect(t, t->q0+1, t->q0+1);
       +        if(r=='\n' && t->w!=nil)
       +                wincommit(t->w, t);
       +}
       +
       +void
       +textcommit(Text *t, int tofile)
       +{
       +        if(t->ncache == 0)
       +                return;
       +        if(tofile)
       +                fileinsert(t->file, t->cq0, t->cache, t->ncache);
       +        if(t->what == Body){
       +                t->w->dirty = TRUE;
       +                t->w->utflastqid = -1;
       +        }
       +        t->ncache = 0;
       +}
       +
       +static        Text        *clicktext;
       +static        uint        clickmsec;
       +static        Text        *selecttext;
       +static        uint        selectq;
       +
       +/*
       + * called from frame library
       + */
       +void
       +framescroll(Frame *f, int dl)
       +{
       +        if(f != &selecttext->fr)
       +                error("frameselect not right frame");
       +        textframescroll(selecttext, dl);
       +}
       +
       +void
       +textframescroll(Text *t, int dl)
       +{
       +        uint q0;
       +
       +        if(dl == 0){
       +                scrsleep(100);
       +                return;
       +        }
       +        if(dl < 0){
       +                q0 = textbacknl(t, t->org, -dl);
       +                if(selectq > t->org+t->fr.p0)
       +                        textsetselect(t, t->org+t->fr.p0, selectq);
       +                else
       +                        textsetselect(t, selectq, t->org+t->fr.p0);
       +        }else{
       +                if(t->org+t->fr.nchars == t->file->b.nc)
       +                        return;
       +                q0 = t->org+frcharofpt(&t->fr, Pt(t->fr.r.min.x, t->fr.r.min.y+dl*t->fr.font->height));
       +                if(selectq > t->org+t->fr.p1)
       +                        textsetselect(t, t->org+t->fr.p1, selectq);
       +                else
       +                        textsetselect(t, selectq, t->org+t->fr.p1);
       +        }
       +        textsetorigin(t, q0, TRUE);
       +}
       +
       +
       +void
       +textselect(Text *t)
       +{
       +        uint q0, q1;
       +        int b, x, y;
       +        int state;
       +
       +        selecttext = t;
       +        /*
       +         * To have double-clicking and chording, we double-click
       +         * immediately if it might make sense.
       +         */
       +        b = mouse->buttons;
       +        q0 = t->q0;
       +        q1 = t->q1;
       +        selectq = t->org+frcharofpt(&t->fr, mouse->xy);
       +        if(clicktext==t && mouse->msec-clickmsec<500)
       +        if(q0==q1 && selectq==q0){
       +                textdoubleclick(t, &q0, &q1);
       +                textsetselect(t, q0, q1);
       +                flushimage(display, 1);
       +                x = mouse->xy.x;
       +                y = mouse->xy.y;
       +                /* stay here until something interesting happens */
       +                do
       +                        readmouse(mousectl);
       +                while(mouse->buttons==b && abs(mouse->xy.x-x)<3 && abs(mouse->xy.y-y)<3);
       +                mouse->xy.x = x;        /* in case we're calling frselect */
       +                mouse->xy.y = y;
       +                q0 = t->q0;        /* may have changed */
       +                q1 = t->q1;
       +                selectq = q0;
       +        }
       +        if(mouse->buttons == b){
       +                t->fr.scroll = framescroll;
       +                frselect(&t->fr, mousectl);
       +                /* horrible botch: while asleep, may have lost selection altogether */
       +                if(selectq > t->file->b.nc)
       +                        selectq = t->org + t->fr.p0;
       +                t->fr.scroll = nil;
       +                if(selectq < t->org)
       +                        q0 = selectq;
       +                else
       +                        q0 = t->org + t->fr.p0;
       +                if(selectq > t->org+t->fr.nchars)
       +                        q1 = selectq;
       +                else
       +                        q1 = t->org+t->fr.p1;
       +        }
       +        if(q0 == q1){
       +                if(q0==t->q0 && clicktext==t && mouse->msec-clickmsec<500){
       +                        textdoubleclick(t, &q0, &q1);
       +                        clicktext = nil;
       +                }else{
       +                        clicktext = t;
       +                        clickmsec = mouse->msec;
       +                }
       +        }else
       +                clicktext = nil;
       +        textsetselect(t, q0, q1);
       +        flushimage(display, 1);
       +        state = 0;        /* undo when possible; +1 for cut, -1 for paste */
       +        while(mouse->buttons){
       +                mouse->msec = 0;
       +                b = mouse->buttons;
       +                if(b & 6){
       +                        if(state==0 && t->what==Body){
       +                                seq++;
       +                                filemark(t->w->body.file);
       +                        }
       +                        if(b & 2){
       +                                if(state==-1 && t->what==Body){
       +                                        winundo(t->w, TRUE);
       +                                        textsetselect(t, q0, t->q0);
       +                                        state = 0;
       +                                }else if(state != 1){
       +                                        cut(t, t, nil, TRUE, TRUE, nil, 0);
       +                                        state = 1;
       +                                }
       +                        }else{
       +                                if(state==1 && t->what==Body){
       +                                        winundo(t->w, TRUE);
       +                                        textsetselect(t, q0, t->q1);
       +                                        state = 0;
       +                                }else if(state != -1){
       +                                        paste(t, t, nil, TRUE, FALSE, nil, 0);
       +                                        state = -1;
       +                                }
       +                        }
       +                        textscrdraw(t);
       +                        clearmouse();
       +                }
       +                flushimage(display, 1);
       +                while(mouse->buttons == b)
       +                        readmouse(mousectl);
       +                clicktext = nil;
       +        }
       +}
       +
       +void
       +textshow(Text *t, uint q0, uint q1, int doselect)
       +{
       +        int qe;
       +        int nl;
       +        uint q;
       +
       +        if(t->what != Body)
       +                return;
       +        if(t->w!=nil && t->fr.maxlines==0)
       +                colgrow(t->col, t->w, 1);
       +        if(doselect)
       +                textsetselect(t, q0, q1);
       +        qe = t->org+t->fr.nchars;
       +        if(t->org<=q0 && (q0<qe || (q0==qe && qe==t->file->b.nc+t->ncache)))
       +                textscrdraw(t);
       +        else{
       +                if(t->w->nopen[QWevent] > 0)
       +                        nl = 3*t->fr.maxlines/4;
       +                else
       +                        nl = t->fr.maxlines/4;
       +                q = textbacknl(t, q0, nl);
       +                /* avoid going backwards if trying to go forwards - long lines! */
       +                if(!(q0>t->org && q<t->org))
       +                        textsetorigin(t, q, TRUE);
       +                while(q0 > t->org+t->fr.nchars)
       +                        textsetorigin(t, t->org+1, FALSE);
       +        }
       +}
       +
       +static
       +int
       +region(int a, int b)
       +{
       +        if(a < b)
       +                return -1;
       +        if(a == b)
       +                return 0;
       +        return 1;
       +}
       +
       +void
       +selrestore(Frame *f, Point pt0, uint p0, uint p1)
       +{
       +        if(p1<=f->p0 || p0>=f->p1){
       +                /* no overlap */
       +                frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]);
       +                return;
       +        }
       +        if(p0>=f->p0 && p1<=f->p1){
       +                /* entirely inside */
       +                frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
       +                return;
       +        }
       +
       +        /* they now are known to overlap */
       +
       +        /* before selection */
       +        if(p0 < f->p0){
       +                frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]);
       +                p0 = f->p0;
       +                pt0 = frptofchar(f, p0);
       +        }
       +        /* after selection */
       +        if(p1 > f->p1){
       +                frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]);
       +                p1 = f->p1;
       +        }
       +        /* inside selection */
       +        frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
       +}
       +
       +void
       +textsetselect(Text *t, uint q0, uint q1)
       +{
       +        int p0, p1;
       +
       +        /* t->fr.p0 and t->fr.p1 are always right; t->q0 and t->q1 may be off */
       +        t->q0 = q0;
       +        t->q1 = q1;
       +        /* compute desired p0,p1 from q0,q1 */
       +        p0 = q0-t->org;
       +        p1 = q1-t->org;
       +        if(p0 < 0)
       +                p0 = 0;
       +        if(p1 < 0)
       +                p1 = 0;
       +        if(p0 > t->fr.nchars)
       +                p0 = t->fr.nchars;
       +        if(p1 > t->fr.nchars)
       +                p1 = t->fr.nchars;
       +        if(p0==t->fr.p0 && p1==t->fr.p1)
       +                return;
       +        /* screen disagrees with desired selection */
       +        if(t->fr.p1<=p0 || p1<=t->fr.p0 || p0==p1 || t->fr.p1==t->fr.p0){
       +                /* no overlap or too easy to bother trying */
       +                frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p0), t->fr.p0, t->fr.p1, 0);
       +                frdrawsel(&t->fr, frptofchar(&t->fr, p0), p0, p1, 1);
       +                goto Return;
       +        }
       +        /* overlap; avoid unnecessary painting */
       +        if(p0 < t->fr.p0){
       +                /* extend selection backwards */
       +                frdrawsel(&t->fr, frptofchar(&t->fr, p0), p0, t->fr.p0, 1);
       +        }else if(p0 > t->fr.p0){
       +                /* trim first part of selection */
       +                frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p0), t->fr.p0, p0, 0);
       +        }
       +        if(p1 > t->fr.p1){
       +                /* extend selection forwards */
       +                frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p1), t->fr.p1, p1, 1);
       +        }else if(p1 < t->fr.p1){
       +                /* trim last part of selection */
       +                frdrawsel(&t->fr, frptofchar(&t->fr, p1), p1, t->fr.p1, 0);
       +        }
       +
       +    Return:
       +        t->fr.p0 = p0;
       +        t->fr.p1 = p1;
       +}
       +
       +/*
       + * Release the button in less than DELAY ms and it's considered a null selection
       + * if the mouse hardly moved, regardless of whether it crossed a char boundary.
       + */
       +enum {
       +        DELAY = 2,
       +        MINMOVE = 4,
       +};
       +
       +uint
       +xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p)        /* when called, button is down */
       +{
       +        uint p0, p1, q, tmp;
       +        ulong msec;
       +        Point mp, pt0, pt1, qt;
       +        int reg, b;
       +
       +        mp = mc->m.xy;
       +        b = mc->m.buttons;
       +        msec = mc->m.msec;
       +
       +        /* remove tick */
       +        if(f->p0 == f->p1)
       +                frtick(f, frptofchar(f, f->p0), 0);
       +        p0 = p1 = frcharofpt(f, mp);
       +        pt0 = frptofchar(f, p0);
       +        pt1 = frptofchar(f, p1);
       +        reg = 0;
       +        frtick(f, pt0, 1);
       +        do{
       +                q = frcharofpt(f, mc->m.xy);
       +                if(p1 != q){
       +                        if(p0 == p1)
       +                                frtick(f, pt0, 0);
       +                        if(reg != region(q, p0)){        /* crossed starting point; reset */
       +                                if(reg > 0)
       +                                        selrestore(f, pt0, p0, p1);
       +                                else if(reg < 0)
       +                                        selrestore(f, pt1, p1, p0);
       +                                p1 = p0;
       +                                pt1 = pt0;
       +                                reg = region(q, p0);
       +                                if(reg == 0)
       +                                        frdrawsel0(f, pt0, p0, p1, col, display->white);
       +                        }
       +                        qt = frptofchar(f, q);
       +                        if(reg > 0){
       +                                if(q > p1)
       +                                        frdrawsel0(f, pt1, p1, q, col, display->white);
       +
       +                                else if(q < p1)
       +                                        selrestore(f, qt, q, p1);
       +                        }else if(reg < 0){
       +                                if(q > p1)
       +                                        selrestore(f, pt1, p1, q);
       +                                else
       +                                        frdrawsel0(f, qt, q, p1, col, display->white);
       +                        }
       +                        p1 = q;
       +                        pt1 = qt;
       +                }
       +                if(p0 == p1)
       +                        frtick(f, pt0, 1);
       +                flushimage(f->display, 1);
       +                readmouse(mc);
       +        }while(mc->m.buttons == b);
       +        if(mc->m.msec-msec < DELAY && p0!=p1
       +        && abs(mp.x-mc->m.xy.x)<MINMOVE
       +        && abs(mp.y-mc->m.xy.y)<MINMOVE) {
       +                if(reg > 0)
       +                        selrestore(f, pt0, p0, p1);
       +                else if(reg < 0)
       +                        selrestore(f, pt1, p1, p0);
       +                p1 = p0;
       +        }
       +        if(p1 < p0){
       +                tmp = p0;
       +                p0 = p1;
       +                p1 = tmp;
       +        }
       +        pt0 = frptofchar(f, p0);
       +        if(p0 == p1)
       +                frtick(f, pt0, 0);
       +        selrestore(f, pt0, p0, p1);
       +        /* restore tick */
       +        if(f->p0 == f->p1)
       +                frtick(f, frptofchar(f, f->p0), 1);
       +        flushimage(f->display, 1);
       +        *p1p = p1;
       +        return p0;
       +}
       +
       +int
       +textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask)
       +{
       +        uint p0, p1;
       +        int buts;
       +        
       +        p0 = xselect(&t->fr, mousectl, high, &p1);
       +        buts = mousectl->m.buttons;
       +        if((buts & mask) == 0){
       +                *q0 = p0+t->org;
       +                *q1 = p1+t->org;
       +        }
       +
       +        while(mousectl->m.buttons)
       +                readmouse(mousectl);
       +        return buts;
       +}
       +
       +int
       +textselect2(Text *t, uint *q0, uint *q1, Text **tp)
       +{
       +        int buts;
       +
       +        *tp = nil;
       +        buts = textselect23(t, q0, q1, but2col, 4);
       +        if(buts & 4)
       +                return 0;
       +        if(buts & 1){        /* pick up argument */
       +                *tp = argtext;
       +                return 1;
       +        }
       +        return 1;
       +}
       +
       +int
       +textselect3(Text *t, uint *q0, uint *q1)
       +{
       +        int h;
       +
       +        h = (textselect23(t, q0, q1, but3col, 1|2) == 0);
       +        return h;
       +}
       +
       +static Rune left1[] =  { '{', '[', '(', '<', 0xab, 0 };
       +static Rune right1[] = { '}', ']', ')', '>', 0xbb, 0 };
       +static Rune left2[] =  { '\n', 0 };
       +static Rune left3[] =  { '\'', '"', '`', 0 };
       +
       +static
       +Rune *left[] = {
       +        left1,
       +        left2,
       +        left3,
       +        nil
       +};
       +static
       +Rune *right[] = {
       +        right1,
       +        left2,
       +        left3,
       +        nil
       +};
       +
       +void
       +textdoubleclick(Text *t, uint *q0, uint *q1)
       +{
       +        int c, i;
       +        Rune *r, *l, *p;
       +        uint q;
       +
       +        for(i=0; left[i]!=nil; i++){
       +                q = *q0;
       +                l = left[i];
       +                r = right[i];
       +                /* try matching character to left, looking right */
       +                if(q == 0)
       +                        c = '\n';
       +                else
       +                        c = textreadc(t, q-1);
       +                p = runestrchr(l, c);
       +                if(p != nil){
       +                        if(textclickmatch(t, c, r[p-l], 1, &q))
       +                                *q1 = q-(c!='\n');
       +                        return;
       +                }
       +                /* try matching character to right, looking left */
       +                if(q == t->file->b.nc)
       +                        c = '\n';
       +                else
       +                        c = textreadc(t, q);
       +                p = runestrchr(r, c);
       +                if(p != nil){
       +                        if(textclickmatch(t, c, l[p-r], -1, &q)){
       +                                *q1 = *q0+(*q0<t->file->b.nc && c=='\n');
       +                                *q0 = q;
       +                                if(c!='\n' || q!=0 || textreadc(t, 0)=='\n')
       +                                        (*q0)++;
       +                        }
       +                        return;
       +                }
       +        }
       +        /* try filling out word to right */
       +        while(*q1<t->file->b.nc && isalnum(textreadc(t, *q1)))
       +                (*q1)++;
       +        /* try filling out word to left */
       +        while(*q0>0 && isalnum(textreadc(t, *q0-1)))
       +                (*q0)--;
       +}
       +
       +int
       +textclickmatch(Text *t, int cl, int cr, int dir, uint *q)
       +{
       +        Rune c;
       +        int nest;
       +
       +        nest = 1;
       +        for(;;){
       +                if(dir > 0){
       +                        if(*q == t->file->b.nc)
       +                                break;
       +                        c = textreadc(t, *q);
       +                        (*q)++;
       +                }else{
       +                        if(*q == 0)
       +                                break;
       +                        (*q)--;
       +                        c = textreadc(t, *q);
       +                }
       +                if(c == cr){
       +                        if(--nest==0)
       +                                return 1;
       +                }else if(c == cl)
       +                        nest++;
       +        }
       +        return cl=='\n' && nest==1;
       +}
       +
       +uint
       +textbacknl(Text *t, uint p, uint n)
       +{
       +        int i, j;
       +
       +        /* look for start of this line if n==0 */
       +        if(n==0 && p>0 && textreadc(t, p-1)!='\n')
       +                n = 1;
       +        i = n;
       +        while(i-->0 && p>0){
       +                --p;        /* it's at a newline now; back over it */
       +                if(p == 0)
       +                        break;
       +                /* at 128 chars, call it a line anyway */
       +                for(j=128; --j>0 && p>0; p--)
       +                        if(textreadc(t, p-1)=='\n')
       +                                break;
       +        }
       +        return p;
       +}
       +
       +void
       +textsetorigin(Text *t, uint org, int exact)
       +{
       +        int i, a, fixup;
       +        Rune *r;
       +        uint n;
       +
       +        if(org>0 && !exact){
       +                /* org is an estimate of the char posn; find a newline */
       +                /* don't try harder than 256 chars */
       +                for(i=0; i<256 && org<t->file->b.nc; i++){
       +                        if(textreadc(t, org) == '\n'){
       +                                org++;
       +                                break;
       +                        }
       +                        org++;
       +                }
       +        }
       +        a = org-t->org;
       +        fixup = 0;
       +        if(a>=0 && a<t->fr.nchars){
       +                frdelete(&t->fr, 0, a);
       +                fixup = 1;        /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
       +        }
       +        else if(a<0 && -a<t->fr.nchars){
       +                n = t->org - org;
       +                r = runemalloc(n);
       +                bufread(&t->file->b, org, r, n);
       +                frinsert(&t->fr, r, r+n, 0);
       +                free(r);
       +        }else
       +                frdelete(&t->fr, 0, t->fr.nchars);
       +        t->org = org;
       +        textfill(t);
       +        textscrdraw(t);
       +        textsetselect(t, t->q0, t->q1);
       +        if(fixup && t->fr.p1 > t->fr.p0)
       +                frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p1-1), t->fr.p1-1, t->fr.p1, 1);
       +}
       +
       +void
       +textreset(Text *t)
       +{
       +        t->file->seq = 0;
       +        t->eq0 = ~0;
       +        /* do t->delete(0, t->nc, TRUE) without building backup stuff */
       +        textsetselect(t, t->org, t->org);
       +        frdelete(&t->fr, 0, t->fr.nchars);
       +        t->org = 0;
       +        t->q0 = 0;
       +        t->q1 = 0;
       +        filereset(t->file);
       +        bufreset(&t->file->b);
       +}
 (DIR) diff --git a/src/cmd/acme/time.c b/src/cmd/acme/time.c
       t@@ -0,0 +1,121 @@
       +#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 "fns.h"
       +
       +static Channel*        ctimer;        /* chan(Timer*)[100] */
       +static Timer *timer;
       +
       +static
       +uint
       +msec(void)
       +{
       +        return nsec()/1000000;
       +}
       +
       +void
       +timerstop(Timer *t)
       +{
       +        t->next = timer;
       +        timer = t;
       +}
       +
       +void
       +timercancel(Timer *t)
       +{
       +        t->cancel = TRUE;
       +}
       +
       +static
       +void
       +timerproc(void *v)
       +{
       +        int i, nt, na, dt, del;
       +        Timer **t, *x;
       +        uint old, new;
       +
       +        USED(v);
       +        threadsetname("timerproc");
       +        rfork(RFFDG);
       +        t = nil;
       +        na = 0;
       +        nt = 0;
       +        old = msec();
       +        for(;;){
       +                sleep(1);        /* will sleep minimum incr */
       +                new = msec();
       +                dt = new-old;
       +                old = new;
       +                if(dt < 0)        /* timer wrapped; go around, losing a tick */
       +                        continue;
       +                for(i=0; i<nt; i++){
       +                        x = t[i];
       +                        x->dt -= dt;
       +                        del = FALSE;
       +                        if(x->cancel){
       +                                timerstop(x);
       +                                del = TRUE;
       +                        }else if(x->dt <= 0){
       +                                /*
       +                                 * avoid possible deadlock if client is
       +                                 * now sending on ctimer
       +                                 */
       +                                if(nbsendul(x->c, 0) > 0)
       +                                        del = TRUE;
       +                        }
       +                        if(del){
       +                                memmove(&t[i], &t[i+1], (nt-i-1)*sizeof t[0]);
       +                                --nt;
       +                                --i;
       +                        }
       +                }
       +                if(nt == 0){
       +                        x = recvp(ctimer);
       +        gotit:
       +                        if(nt == na){
       +                                na += 10;
       +                                t = realloc(t, na*sizeof(Timer*));
       +                                if(t == nil)
       +                                        error("timer realloc failed");
       +                        }
       +                        t[nt++] = x;
       +                        old = msec();
       +                }
       +                if(nbrecv(ctimer, &x) > 0)
       +                        goto gotit;
       +        }
       +}
       +
       +void
       +timerinit(void)
       +{
       +        ctimer = chancreate(sizeof(Timer*), 100);
       +        proccreate(timerproc, nil, STACK);
       +}
       +
       +Timer*
       +timerstart(int dt)
       +{
       +        Timer *t;
       +
       +        t = timer;
       +        if(t)
       +                timer = timer->next;
       +        else{
       +                t = emalloc(sizeof(Timer));
       +                t->c = chancreate(sizeof(int), 0);
       +        }
       +        t->next = nil;
       +        t->dt = dt;
       +        t->cancel = FALSE;
       +        sendp(ctimer, t);
       +        return t;
       +}
 (DIR) diff --git a/src/cmd/acme/util.c b/src/cmd/acme/util.c
       t@@ -0,0 +1,395 @@
       +#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 "fns.h"
       +
       +static        Point                prevmouse;
       +static        Window        *mousew;
       +
       +void
       +cvttorunes(char *p, int n, Rune *r, int *nb, int *nr, int *nulls)
       +{
       +        uchar *q;
       +        Rune *s;
       +        int j, w;
       +
       +        /*
       +         * Always guaranteed that n bytes may be interpreted
       +         * without worrying about partial runes.  This may mean
       +         * reading up to UTFmax-1 more bytes than n; the caller
       +         * knows this.  If n is a firm limit, the caller should
       +         * set p[n] = 0.
       +         */
       +        q = (uchar*)p;
       +        s = r;
       +        for(j=0; j<n; j+=w){
       +                if(*q < Runeself){
       +                        w = 1;
       +                        *s = *q++;
       +                }else{
       +                        w = chartorune(s, (char*)q);
       +                        q += w;
       +                }
       +                if(*s)
       +                        s++;
       +                else if(nulls)
       +                        *nulls = TRUE;
       +        }
       +        *nb = (char*)q-p;
       +        *nr = s-r;
       +}
       +
       +void
       +error(char *s)
       +{
       +        fprint(2, "acme: %s: %r\n", s);
       +        abort();
       +}
       +
       +Window*
       +errorwin1(Rune *dir, int ndir, Rune **incl, int nincl)
       +{
       +        Window *w;
       +        Rune *r;
       +        int i, n;
       +        static Rune Lpluserrors[] = { '+', 'E', 'r', 'r', 'o', 'r', 's', 0 };
       +
       +        r = runemalloc(ndir+7);
       +        if(n = ndir)        /* assign = */
       +                runemove(r, dir, ndir);
       +        runemove(r+n, Lpluserrors, 7);
       +        n += 7;
       +        w = lookfile(r, n);
       +        if(w == nil){
       +                if(row.ncol == 0)
       +                        if(rowadd(&row, nil, -1) == nil)
       +                                error("can't create column to make error window");
       +                w = coladd(row.col[row.ncol-1], nil, nil, -1);
       +                w->filemenu = FALSE;
       +                winsetname(w, r, n);
       +        }
       +        free(r);
       +        for(i=nincl; --i>=0; ){
       +                n = runestrlen(incl[i]);
       +                r = runemalloc(n);
       +                runemove(r, incl[i], n);
       +                winaddincl(w, r, n);
       +        }
       +        return w;
       +}
       +
       +/* make new window, if necessary; return with it locked */
       +Window*
       +errorwin(Mntdir *md, int owner, Window *e)
       +{
       +        Window *w;
       +
       +        for(;;){
       +                if(md == nil)
       +                        w = errorwin1(nil, 0, nil, 0);
       +                else
       +                        w = errorwin1(md->dir, md->ndir, md->incl, md->nincl);
       +                if(w != e)
       +                        winlock(w, owner);
       +                if(w->col != nil)
       +                        break;
       +                /* window was deleted too fast */
       +                if(w != e)
       +                        winunlock(w);
       +        }
       +        return w;
       +}
       +
       +static void
       +printwarning(Window *ew, Mntdir *md, Rune *r)
       +{
       +        int nr, q0, owner;
       +        Window *w;
       +        Text *t;
       +
       +        if(r == nil)
       +                error("runevsmprint failed");
       +        nr = runestrlen(r);
       +
       +        if(row.ncol == 0){        /* really early error */
       +                rowinit(&row, screen->clipr);
       +                rowadd(&row, nil, -1);
       +                rowadd(&row, nil, -1);
       +                if(row.ncol == 0)
       +                        error("initializing columns in warning()");
       +        }
       +
       +        w = errorwin(md, 'E', ew);
       +        t = &w->body;
       +        owner = w->owner;
       +        if(owner == 0)
       +                w->owner = 'E';
       +        wincommit(w, t);
       +        q0 = textbsinsert(t, t->file->b.nc, r, nr, TRUE, &nr);
       +        textshow(t, q0, q0+nr, 1);
       +        winsettag(t->w);
       +        textscrdraw(t);
       +        w->owner = owner;
       +        w->dirty = FALSE;
       +        if(ew != w)
       +                winunlock(w);
       +        free(r);
       +}
       +
       +void
       +warning(Mntdir *md, char *s, ...)
       +{
       +        Rune *r;
       +        va_list arg;
       +
       +        va_start(arg, s);
       +        r = runevsmprint(s, arg);
       +        va_end(arg);
       +        printwarning(nil, md, r);
       +}
       +
       +/*
       + * Warningew is like warning but avoids locking the error window
       + * if it's already locked by checking that ew!=error window.
       + */
       +void
       +warningew(Window *ew, Mntdir *md, char *s, ...)
       +{
       +        Rune *r;
       +        va_list arg;
       +
       +        va_start(arg, s);
       +        r = runevsmprint(s, arg);
       +        va_end(arg);
       +        printwarning(ew, md, r);
       +}
       +
       +int
       +runeeq(Rune *s1, uint n1, Rune *s2, uint n2)
       +{
       +        if(n1 != n2)
       +                return FALSE;
       +        return memcmp(s1, s2, n1*sizeof(Rune)) == 0;
       +}
       +
       +uint
       +min(uint a, uint b)
       +{
       +        if(a < b)
       +                return a;
       +        return b;
       +}
       +
       +uint
       +max(uint a, uint b)
       +{
       +        if(a > b)
       +                return a;
       +        return b;
       +}
       +
       +char*
       +runetobyte(Rune *r, int n)
       +{
       +        char *s;
       +
       +        if(r == nil)
       +                return nil;
       +        s = emalloc(n*UTFmax+1);
       +        setmalloctag(s, getcallerpc(&r));
       +        snprint(s, n*UTFmax+1, "%.*S", n, r);
       +        return s;
       +}
       +
       +Rune*
       +bytetorune(char *s, int *ip)
       +{
       +        Rune *r;
       +        int nb, nr;
       +
       +        nb = strlen(s);
       +        r = runemalloc(nb+1);
       +        cvttorunes(s, nb, r, &nb, &nr, nil);
       +        r[nr] = '\0';
       +        *ip = nr;
       +        return r;
       +}
       +
       +int
       +isalnum(Rune c)
       +{
       +        /*
       +         * Hard to get absolutely right.  Use what we know about ASCII
       +         * and assume anything above the Latin control characters is
       +         * potentially an alphanumeric.
       +         */
       +        if(c <= ' ')
       +                return FALSE;
       +        if(0x7F<=c && c<=0xA0)
       +                return FALSE;
       +        if(utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c))
       +                return FALSE;
       +        return TRUE;
       +}
       +
       +int
       +rgetc(void *v, uint n)
       +{
       +        return ((Rune*)v)[n];
       +}
       +
       +int
       +tgetc(void *a, uint n)
       +{
       +        Text *t;
       +
       +        t = a;
       +        if(n >= t->file->b.nc)
       +                return 0;
       +        return textreadc(t, n);
       +}
       +
       +Rune*
       +skipbl(Rune *r, int n, int *np)
       +{
       +        while(n>0 && *r==' ' || *r=='\t' || *r=='\n'){
       +                --n;
       +                r++;
       +        }
       +        *np = n;
       +        return r;
       +}
       +
       +Rune*
       +findbl(Rune *r, int n, int *np)
       +{
       +        while(n>0 && *r!=' ' && *r!='\t' && *r!='\n'){
       +                --n;
       +                r++;
       +        }
       +        *np = n;
       +        return r;
       +}
       +
       +void
       +savemouse(Window *w)
       +{
       +        prevmouse = mouse->xy;
       +        mousew = w;
       +}
       +
       +void
       +restoremouse(Window *w)
       +{
       +        if(mousew!=nil && mousew==w)
       +                moveto(mousectl, prevmouse);
       +        mousew = nil;
       +}
       +
       +void
       +clearmouse()
       +{
       +        mousew = nil;
       +}
       +
       +char*
       +estrdup(char *s)
       +{
       +        char *t;
       +
       +        t = strdup(s);
       +        if(t == nil)
       +                error("strdup failed");
       +        setmalloctag(t, getcallerpc(&s));
       +        return t;
       +}
       +
       +void*
       +emalloc(uint n)
       +{
       +        void *p;
       +
       +        p = malloc(n);
       +        if(p == nil){
       +                fprint(2, "allocating %d from %lux: %r\n", n, getcallerpc(&n));
       +                *(int*)0=0;
       +                error("malloc failed");
       +        }
       +        setmalloctag(p, getcallerpc(&n));
       +        memset(p, 0, n);
       +        return p;
       +}
       +
       +void*
       +erealloc(void *p, uint n)
       +{
       +        p = realloc(p, n);
       +        if(p == nil){
       +                fprint(2, "reallocating %d: %r\n", n);
       +                error("realloc failed");
       +        }
       +        setmalloctag(p, getcallerpc(&n));
       +        return p;
       +}
       +
       +/*
       + * Heuristic city.
       + */
       +Window*
       +makenewwindow(Text *t)
       +{
       +        Column *c;
       +        Window *w, *bigw, *emptyw;
       +        Text *emptyb;
       +        int i, y, el;
       +
       +        if(activecol)
       +                c = activecol;
       +        else if(seltext && seltext->col)
       +                c = seltext->col;
       +        else if(t && t->col)
       +                c = t->col;
       +        else{
       +                if(row.ncol==0 && rowadd(&row, nil, -1)==nil)
       +                        error("can't make column");
       +                c = row.col[row.ncol-1];
       +        }
       +        activecol = c;
       +        if(t==nil || t->w==nil || c->nw==0)
       +                return coladd(c, nil, nil, -1);
       +
       +        /* find biggest window and biggest blank spot */
       +        emptyw = c->w[0];
       +        bigw = emptyw;
       +        for(i=1; i<c->nw; i++){
       +                w = c->w[i];
       +                /* use >= to choose one near bottom of screen */
       +                if(w->body.fr.maxlines >= bigw->body.fr.maxlines)
       +                        bigw = w;
       +                if(w->body.fr.maxlines-w->body.fr.nlines >= emptyw->body.fr.maxlines-emptyw->body.fr.nlines)
       +                        emptyw = w;
       +        }
       +        emptyb = &emptyw->body;
       +        el = emptyb->fr.maxlines-emptyb->fr.nlines;
       +        /* if empty space is big, use it */
       +        if(el>15 || (el>3 && el>(bigw->body.fr.maxlines-1)/2))
       +                y = emptyb->fr.r.min.y+emptyb->fr.nlines*font->height;
       +        else{
       +                /* if this window is in column and isn't much smaller, split it */
       +                if(t->col==c && Dy(t->w->r)>2*Dy(bigw->r)/3)
       +                        bigw = t->w;
       +                y = (bigw->r.min.y + bigw->r.max.y)/2;
       +        }
       +        w = coladd(c, nil, nil, y);
       +        if(w->body.fr.maxlines < 2)
       +                colgrow(w->col, w, 1);
       +        return w;
       +}
 (DIR) diff --git a/src/cmd/acme/wind.c b/src/cmd/acme/wind.c
       t@@ -0,0 +1,576 @@
       +#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 "fns.h"
       +
       +int        winid;
       +
       +void
       +wininit(Window *w, Window *clone, Rectangle r)
       +{
       +        Rectangle r1, br;
       +        File *f;
       +        Reffont *rf;
       +        Rune *rp;
       +        int nc;
       +
       +        w->tag.w = w;
       +        w->body.w = w;
       +        w->id = ++winid;
       +        incref(&w->ref);
       +        w->ctlfid = ~0;
       +        w->utflastqid = -1;
       +        r1 = r;
       +        r1.max.y = r1.min.y + font->height;
       +        incref(&reffont.ref);
       +        f = fileaddtext(nil, &w->tag);
       +        textinit(&w->tag, f, r1, &reffont, tagcols);
       +        w->tag.what = Tag;
       +        /* tag is a copy of the contents, not a tracked image */
       +        if(clone){
       +                textdelete(&w->tag, 0, w->tag.file->b.nc, TRUE);
       +                nc = clone->tag.file->b.nc;
       +                rp = runemalloc(nc);
       +                bufread(&clone->tag.file->b, 0, rp, nc);
       +                textinsert(&w->tag, 0, rp, nc, TRUE);
       +                free(rp);
       +                filereset(w->tag.file);
       +                textsetselect(&w->tag, nc, nc);
       +        }
       +        r1 = r;
       +        r1.min.y += font->height + 1;
       +        if(r1.max.y < r1.min.y)
       +                r1.max.y = r1.min.y;
       +        f = nil;
       +        if(clone){
       +                f = clone->body.file;
       +                w->body.org = clone->body.org;
       +                w->isscratch = clone->isscratch;
       +                rf = rfget(FALSE, FALSE, FALSE, clone->body.reffont->f->name);
       +        }else
       +                rf = rfget(FALSE, FALSE, FALSE, nil);
       +        f = fileaddtext(f, &w->body);
       +        w->body.what = Body;
       +        textinit(&w->body, f, r1, rf, textcols);
       +        r1.min.y -= 1;
       +        r1.max.y = r1.min.y+1;
       +        draw(screen, r1, tagcols[BORD], nil, ZP);
       +        textscrdraw(&w->body);
       +        w->r = r;
       +        w->r.max.y = w->body.fr.r.max.y;
       +        br.min = w->tag.scrollr.min;
       +        br.max.x = br.min.x + Dx(button->r);
       +        br.max.y = br.min.y + Dy(button->r);
       +        draw(screen, br, button, nil, button->r.min);
       +        w->filemenu = TRUE;
       +        w->maxlines = w->body.fr.maxlines;
       +        if(clone){
       +                w->dirty = clone->dirty;
       +                textsetselect(&w->body, clone->body.q0, clone->body.q1);
       +                winsettag(w);
       +        }
       +}
       +
       +int
       +winresize(Window *w, Rectangle r, int safe)
       +{
       +        Rectangle r1;
       +        int y;
       +        Image *b;
       +        Rectangle br;
       +
       +        r1 = r;
       +        r1.max.y = r1.min.y + font->height;
       +        y = r1.max.y;
       +        if(!safe || !eqrect(w->tag.fr.r, r1)){
       +                y = textresize(&w->tag, r1);
       +                b = button;
       +                if(w->body.file->mod && !w->isdir && !w->isscratch)
       +                        b = modbutton;
       +                br.min = w->tag.scrollr.min;
       +                br.max.x = br.min.x + Dx(b->r);
       +                br.max.y = br.min.y + Dy(b->r);
       +                draw(screen, br, b, nil, b->r.min);
       +        }
       +        if(!safe || !eqrect(w->body.fr.r, r1)){
       +                if(y+1+font->height > r.max.y){                /* no body */
       +                        r1.min.y = y;
       +                        r1.max.y = y;
       +                        textresize(&w->body, r1);
       +                        w->r = r;
       +                        w->r.max.y = y;
       +                        return y;
       +                }
       +                r1 = r;
       +                r1.min.y = y;
       +                r1.max.y = y + 1;
       +                draw(screen, r1, tagcols[BORD], nil, ZP);
       +                r1.min.y = y + 1;
       +                r1.max.y = r.max.y;
       +                y = textresize(&w->body, r1);
       +                w->r = r;
       +                w->r.max.y = y;
       +                textscrdraw(&w->body);
       +        }
       +        w->maxlines = min(w->body.fr.nlines, max(w->maxlines, w->body.fr.maxlines));
       +        return w->r.max.y;
       +}
       +
       +void
       +winlock1(Window *w, int owner)
       +{
       +        incref(&w->ref);
       +        qlock(&w->lk);
       +        w->owner = owner;
       +}
       +
       +void
       +winlock(Window *w, int owner)
       +{
       +        int i;
       +        File *f;
       +
       +        f = w->body.file;
       +        for(i=0; i<f->ntext; i++)
       +                winlock1(f->text[i]->w, owner);
       +}
       +
       +void
       +winunlock(Window *w)
       +{
       +        int i;
       +        File *f;
       +
       +        f = w->body.file;
       +        for(i=0; i<f->ntext; i++){
       +                w = f->text[i]->w;
       +                w->owner = 0;
       +                qunlock(&w->lk);
       +                winclose(w);
       +                /* winclose() can change up f->text; beware */
       +                if(f->ntext>0 && w != f->text[i]->w)
       +                        --i;        /* winclose() deleted window */
       +        }
       +}
       +
       +void
       +winmousebut(Window *w)
       +{
       +        moveto(mousectl, divpt(addpt(w->tag.scrollr.min, w->tag.scrollr.max), 2));
       +}
       +
       +void
       +windirfree(Window *w)
       +{
       +        int i;
       +        Dirlist *dl;
       +
       +        if(w->isdir){
       +                for(i=0; i<w->ndl; i++){
       +                        dl = w->dlp[i];
       +                        free(dl->r);
       +                        free(dl);
       +                }
       +                free(w->dlp);
       +        }
       +        w->dlp = nil;
       +        w->ndl = 0;
       +}
       +
       +void
       +winclose(Window *w)
       +{
       +        int i;
       +
       +        if(decref(&w->ref) == 0){
       +                windirfree(w);
       +                textclose(&w->tag);
       +                textclose(&w->body);
       +                if(activewin == w)
       +                        activewin = nil;
       +                for(i=0; i<w->nincl; i++)
       +                        free(w->incl[i]);
       +                free(w->incl);
       +                free(w->events);
       +                free(w);
       +        }
       +}
       +
       +void
       +windelete(Window *w)
       +{
       +        Xfid *x;
       +
       +        x = w->eventx;
       +        if(x){
       +                w->nevents = 0;
       +                free(w->events);
       +                w->events = nil;
       +                w->eventx = nil;
       +                sendp(x->c, nil);        /* wake him up */
       +        }
       +}
       +
       +void
       +winundo(Window *w, int isundo)
       +{
       +        Text *body;
       +        int i;
       +        File *f;
       +        Window *v;
       +
       +        w->utflastqid = -1;
       +        body = &w->body;
       +        fileundo(body->file, isundo, &body->q0, &body->q1);
       +        textshow(body, body->q0, body->q1, 1);
       +        f = body->file;
       +        for(i=0; i<f->ntext; i++){
       +                v = f->text[i]->w;
       +                v->dirty = (f->seq != v->putseq);
       +                if(v != w){
       +                        v->body.q0 = v->body.fr.p0+v->body.org;
       +                        v->body.q1 = v->body.fr.p1+v->body.org;
       +                }
       +        }
       +        winsettag(w);
       +}
       +
       +void
       +winsetname(Window *w, Rune *name, int n)
       +{
       +        Text *t;
       +        Window *v;
       +        int i;
       +        static Rune Lslashguide[] = { '/', 'g', 'u', 'i', 'd', 'e', 0 };
       +        static Rune Lpluserrors[] = { '+', 'E', 'r', 'r', 'o', 'r', 's', 0 };
       +        t = &w->body;
       +        if(runeeq(t->file->name, t->file->nname, name, n) == TRUE)
       +                return;
       +        w->isscratch = FALSE;
       +        if(n>=6 && runeeq(Lslashguide, 6, name+(n-6), 6))
       +                w->isscratch = TRUE;
       +        else if(n>=7 && runeeq(Lpluserrors, 7, name+(n-7), 7))
       +                w->isscratch = TRUE;
       +        filesetname(t->file, name, n);
       +        for(i=0; i<t->file->ntext; i++){
       +                v = t->file->text[i]->w;
       +                winsettag(v);
       +                v->isscratch = w->isscratch;
       +        }
       +}
       +
       +void
       +wintype(Window *w, Text *t, Rune r)
       +{
       +        int i;
       +
       +        texttype(t, r);
       +        if(t->what == Body)
       +                for(i=0; i<t->file->ntext; i++)
       +                        textscrdraw(t->file->text[i]);
       +        winsettag(w);
       +}
       +
       +void
       +wincleartag(Window *w)
       +{
       +        int i, n;
       +        Rune *r;
       +
       +        /* w must be committed */
       +        n = w->tag.file->b.nc;
       +        r = runemalloc(n);
       +        bufread(&w->tag.file->b, 0, r, n);
       +        for(i=0; i<n; i++)
       +                if(r[i]==' ' || r[i]=='\t')
       +                        break;
       +        for(; i<n; i++)
       +                if(r[i] == '|')
       +                        break;
       +        if(i == n)
       +                return;
       +        i++;
       +        textdelete(&w->tag, i, n, TRUE);
       +        free(r);
       +        w->tag.file->mod = FALSE;
       +        if(w->tag.q0 > i)
       +                w->tag.q0 = i;
       +        if(w->tag.q1 > i)
       +                w->tag.q1 = i;
       +        textsetselect(&w->tag, w->tag.q0, w->tag.q1);
       +}
       +
       +void
       +winsettag1(Window *w)
       +{
       +        int i, j, k, n, bar, dirty;
       +        Rune *new, *old, *r;
       +        Image *b;
       +        uint q0, q1;
       +        Rectangle br;
       +        static Rune Ldelsnarf[] = { ' ', 'D', 'e', 'l', ' ',
       +                'S', 'n', 'a', 'r', 'f', 0 };
       +        static Rune Lundo[] = { ' ', 'U', 'n', 'd', 'o', 0 };
       +        static Rune Lredo[] = { ' ', 'R', 'e', 'd', 'o', 0 };
       +        static Rune Lget[] = { ' ', 'G', 'e', 't', 0 };
       +        static Rune Lput[] = { ' ', 'P', 'u', 't', 0 };
       +        static Rune Llook[] = { ' ', 'L', 'o', 'o', 'k', ' ', 0 };
       +        static Rune Lpipe[] = { ' ', '|', 0 };
       +        /* there are races that get us here with stuff in the tag cache, so we take extra care to sync it */
       +        if(w->tag.ncache!=0 || w->tag.file->mod)
       +                wincommit(w, &w->tag);        /* check file name; also guarantees we can modify tag contents */
       +        old = runemalloc(w->tag.file->b.nc+1);
       +        bufread(&w->tag.file->b, 0, old, w->tag.file->b.nc);
       +        old[w->tag.file->b.nc] = '\0';
       +        for(i=0; i<w->tag.file->b.nc; i++)
       +                if(old[i]==' ' || old[i]=='\t')
       +                        break;
       +        if(runeeq(old, i, w->body.file->name, w->body.file->nname) == FALSE){
       +                textdelete(&w->tag, 0, i, TRUE);
       +                textinsert(&w->tag, 0, w->body.file->name, w->body.file->nname, TRUE);
       +                free(old);
       +                old = runemalloc(w->tag.file->b.nc+1);
       +                bufread(&w->tag.file->b, 0, old, w->tag.file->b.nc);
       +                old[w->tag.file->b.nc] = '\0';
       +        }
       +        new = runemalloc(w->body.file->nname+100);
       +        i = 0;
       +        runemove(new+i, w->body.file->name, w->body.file->nname);
       +        i += w->body.file->nname;
       +        runemove(new+i, Ldelsnarf, 10);
       +        i += 10;
       +        if(w->filemenu){
       +                if(w->body.file->delta.nc>0 || w->body.ncache){
       +                        runemove(new+i, Lundo, 5);
       +                        i += 5;
       +                }
       +                if(w->body.file->epsilon.nc > 0){
       +                        runemove(new+i, Lredo, 5);
       +                        i += 5;
       +                }
       +                dirty = w->body.file->nname && (w->body.ncache || w->body.file->seq!=w->putseq);
       +                if(!w->isdir && dirty){
       +                        runemove(new+i, Lput, 4);
       +                        i += 4;
       +                }
       +        }
       +        if(w->isdir){
       +                runemove(new+i, Lget, 4);
       +                i += 4;
       +        }
       +        runemove(new+i, Lpipe, 2);
       +        i += 2;
       +        r = runestrchr(old, '|');
       +        if(r)
       +                k = r-old+1;
       +        else{
       +                k = w->tag.file->b.nc;
       +                if(w->body.file->seq == 0){
       +                        runemove(new+i, Llook, 6);
       +                        i += 6;
       +                }
       +        }
       +        new[i] = 0;
       +        if(runestrlen(new) != i)
       +                fprint(2, "s '%S' len not %d\n", new, i);
       +        assert(i==runestrlen(new));
       +        if(runeeq(new, i, old, k) == FALSE){
       +                n = k;
       +                if(n > i)
       +                        n = i;
       +                for(j=0; j<n; j++)
       +                        if(old[j] != new[j])
       +                                break;
       +                q0 = w->tag.q0;
       +                q1 = w->tag.q1;
       +                textdelete(&w->tag, j, k, TRUE);
       +                textinsert(&w->tag, j, new+j, i-j, TRUE);
       +                /* try to preserve user selection */
       +                r = runestrchr(old, '|');
       +                if(r){
       +                        bar = r-old;
       +                        if(q0 > bar){
       +                                bar = (runestrchr(new, '|')-new)-bar;
       +                                w->tag.q0 = q0+bar;
       +                                w->tag.q1 = q1+bar;
       +                        }
       +                }
       +        }
       +        free(old);
       +        free(new);
       +        w->tag.file->mod = FALSE;
       +        n = w->tag.file->b.nc+w->tag.ncache;
       +        if(w->tag.q0 > n)
       +                w->tag.q0 = n;
       +        if(w->tag.q1 > n)
       +                w->tag.q1 = n;
       +        textsetselect(&w->tag, w->tag.q0, w->tag.q1);
       +        b = button;
       +        if(!w->isdir && !w->isscratch && (w->body.file->mod || w->body.ncache))
       +                b = modbutton;
       +        br.min = w->tag.scrollr.min;
       +        br.max.x = br.min.x + Dx(b->r);
       +        br.max.y = br.min.y + Dy(b->r);
       +        draw(screen, br, b, nil, b->r.min);
       +}
       +
       +void
       +winsettag(Window *w)
       +{
       +        int i;
       +        File *f;
       +        Window *v;
       +
       +        f = w->body.file;
       +        for(i=0; i<f->ntext; i++){
       +                v = f->text[i]->w;
       +                if(v->col->safe || v->body.fr.maxlines>0)
       +                        winsettag1(v);
       +        }
       +}
       +
       +void
       +wincommit(Window *w, Text *t)
       +{
       +        Rune *r;
       +        int i;
       +        File *f;
       +
       +        textcommit(t, TRUE);
       +        f = t->file;
       +        if(f->ntext > 1)
       +                for(i=0; i<f->ntext; i++)
       +                        textcommit(f->text[i], FALSE);        /* no-op for t */
       +        if(t->what == Body)
       +                return;
       +        r = runemalloc(w->tag.file->b.nc);
       +        bufread(&w->tag.file->b, 0, r, w->tag.file->b.nc);
       +        for(i=0; i<w->tag.file->b.nc; i++)
       +                if(r[i]==' ' || r[i]=='\t')
       +                        break;
       +        if(runeeq(r, i, w->body.file->name, w->body.file->nname) == FALSE){
       +                seq++;
       +                filemark(w->body.file);
       +                w->body.file->mod = TRUE;
       +                w->dirty = TRUE;
       +                winsetname(w, r, i);
       +                winsettag(w);
       +        }
       +        free(r);
       +}
       +
       +void
       +winaddincl(Window *w, Rune *r, int n)
       +{
       +        char *a;
       +        Dir *d;
       +        Runestr rs;
       +
       +        a = runetobyte(r, n);
       +        d = dirstat(a);
       +        if(d == nil){
       +                if(a[0] == '/')
       +                        goto Rescue;
       +                rs = dirname(&w->body, r, n);
       +                r = rs.r;
       +                n = rs.nr;
       +                free(a);
       +                a = runetobyte(r, n);
       +                d = dirstat(a);
       +                if(d == nil)
       +                        goto Rescue;
       +                r = runerealloc(r, n+1);
       +                r[n] = 0;
       +        }
       +        free(a);
       +        if((d->qid.type&QTDIR) == 0){
       +                free(d);
       +                warning(nil, "%s: not a directory\n", a);
       +                free(r);
       +                return;
       +        }
       +        free(d);
       +        w->nincl++;
       +        w->incl = realloc(w->incl, w->nincl*sizeof(Rune*));
       +        memmove(w->incl+1, w->incl, (w->nincl-1)*sizeof(Rune*));
       +        w->incl[0] = runemalloc(n+1);
       +        runemove(w->incl[0], r, n);
       +        free(r);
       +        return;
       +
       +Rescue:
       +        warning(nil, "%s: %r\n", a);
       +        free(r);
       +        free(a);
       +        return;
       +}
       +
       +int
       +winclean(Window *w, int conservative)        /* as it stands, conservative is always TRUE */
       +{
       +        if(w->isscratch || w->isdir)        /* don't whine if it's a guide file, error window, etc. */
       +                return TRUE;
       +        if(!conservative && w->nopen[QWevent]>0)
       +                return TRUE;
       +        if(w->dirty){
       +                if(w->body.file->nname)
       +                        warning(nil, "%.*S modified\n", w->body.file->nname, w->body.file->name);
       +                else{
       +                        if(w->body.file->b.nc < 100)        /* don't whine if it's too small */
       +                                return TRUE;
       +                        warning(nil, "unnamed file modified\n");
       +                }
       +                w->dirty = FALSE;
       +                return FALSE;
       +        }
       +        return TRUE;
       +}
       +
       +void
       +winctlprint(Window *w, char *buf, int fonts)
       +{
       +        int n;
       +
       +        n = sprint(buf, "%11d %11d %11d %11d %11d ", w->id, w->tag.file->b.nc,
       +                w->body.file->b.nc, w->isdir, w->dirty);
       +        if(fonts)
       +                sprint(buf+n, "%11d %s" , Dx(w->body.fr.r), w->body.reffont->f->name);
       +}
       +
       +void
       +winevent(Window *w, char *fmt, ...)
       +{
       +        int n;
       +        char *b;
       +        Xfid *x;
       +        va_list arg;
       +
       +        if(w->nopen[QWevent] == 0)
       +                return;
       +        if(w->owner == 0)
       +                error("no window owner");
       +        va_start(arg, fmt);
       +        b = vsmprint(fmt, arg);
       +        va_end(arg);
       +        if(b == nil)
       +                error("vsmprint failed");
       +        n = strlen(b);
       +        w->events = realloc(w->events, w->nevents+1+n);
       +        w->events[w->nevents++] = w->owner;
       +        memmove(w->events+w->nevents, b, n);
       +        free(b);
       +        w->nevents += n;
       +        x = w->eventx;
       +        if(x){
       +                w->eventx = nil;
       +                sendp(x->c, nil);
       +        }
       +}
 (DIR) diff --git a/src/cmd/acme/xfid.c b/src/cmd/acme/xfid.c
       t@@ -0,0 +1,1046 @@
       +#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 "fns.h"
       +
       +enum
       +{
       +        Ctlsize        = 5*12
       +};
       +
       +char        Edel[]                = "deleted window";
       +char        Ebadctl[]                = "ill-formed control message";
       +char        Ebadaddr[]        = "bad address syntax";
       +char        Eaddr[]                = "address out of range";
       +char        Einuse[]                = "already in use";
       +char        Ebadevent[]        = "bad event syntax";
       +extern char Eperm[];
       +
       +static
       +void
       +clampaddr(Window *w)
       +{
       +        if(w->addr.q0 < 0)
       +                w->addr.q0 = 0;
       +        if(w->addr.q1 < 0)
       +                w->addr.q1 = 0;
       +        if(w->addr.q0 > w->body.file->b.nc)
       +                w->addr.q0 = w->body.file->b.nc;
       +        if(w->addr.q1 > w->body.file->b.nc)
       +                w->addr.q1 = w->body.file->b.nc;
       +}
       +
       +void
       +xfidctl(void *arg)
       +{
       +        Xfid *x;
       +        void (*f)(Xfid*);
       +
       +        threadsetname("xfidctlthread");
       +        x = arg;
       +        for(;;){
       +                f = recvp(x->c);
       +                (*f)(x);
       +                flushimage(display, 1);
       +                sendp(cxfidfree, x);
       +        }
       +}
       +
       +void
       +xfidflush(Xfid *x)
       +{
       +        Fcall fc;
       +        int i, j;
       +        Window *w;
       +        Column *c;
       +        Xfid *wx;
       +
       +        /* search windows for matching tag */
       +        qlock(&row.lk);
       +        for(j=0; j<row.ncol; j++){
       +                c = row.col[j];
       +                for(i=0; i<c->nw; i++){
       +                        w = c->w[i];
       +                        winlock(w, 'E');
       +                        wx = w->eventx;
       +                        if(wx!=nil && wx->fcall.tag==x->fcall.oldtag){
       +                                w->eventx = nil;
       +                                wx->flushed = TRUE;
       +                                sendp(wx->c, nil);
       +                                winunlock(w);
       +                                goto out;
       +                        }
       +                        winunlock(w);
       +                }
       +        }
       +out:
       +        qunlock(&row.lk);
       +        respond(x, &fc, nil);
       +}
       +
       +void
       +xfidopen(Xfid *x)
       +{
       +        Fcall fc;
       +        Window *w;
       +        Text *t;
       +        char *s;
       +        Rune *r;
       +        int m, n, q, q0, q1;
       +
       +        w = x->f->w;
       +        t = &w->body;
       +        if(w){
       +                winlock(w, 'E');
       +                q = FILE(x->f->qid);
       +                switch(q){
       +                case QWaddr:
       +                        if(w->nopen[q]++ == 0){
       +                                w->addr = (Range){0,0};
       +                                w->limit = (Range){-1,-1};
       +                        }
       +                        break;
       +                case QWdata:
       +                        w->nopen[q]++;
       +                        break;
       +                case QWevent:
       +                        if(w->nopen[q]++ == 0){
       +                                if(!w->isdir && w->col!=nil){
       +                                        w->filemenu = FALSE;
       +                                        winsettag(w);
       +                                }
       +                        }
       +                        break;
       +                case QWrdsel:
       +                        /*
       +                         * Use a temporary file.
       +                         * A pipe would be the obvious, but we can't afford the
       +                         * broken pipe notification.  Using the code to read QWbody
       +                         * is n², which should probably also be fixed.  Even then,
       +                         * though, we'd need to squirrel away the data in case it's
       +                         * modified during the operation, e.g. by |sort
       +                         */
       +                        if(w->rdselfd > 0){
       +                                winunlock(w);
       +                                respond(x, &fc, Einuse);
       +                                return;
       +                        }
       +                        w->rdselfd = tempfile();
       +                        if(w->rdselfd < 0){
       +                                winunlock(w);
       +                                respond(x, &fc, "can't create temp file");
       +                                return;
       +                        }
       +                        w->nopen[q]++;
       +                        q0 = t->q0;
       +                        q1 = t->q1;
       +                        r = fbufalloc();
       +                        s = fbufalloc();
       +                        while(q0 < q1){
       +                                n = q1 - q0;
       +                                if(n > BUFSIZE/UTFmax)
       +                                        n = BUFSIZE/UTFmax;
       +                                bufread(&t->file->b, q0, r, n);
       +                                m = snprint(s, BUFSIZE+1, "%.*S", n, r);
       +                                if(write(w->rdselfd, s, m) != m){
       +                                        warning(nil, "can't write temp file for pipe command %r\n");
       +                                        break;
       +                                }
       +                                q0 += n;
       +                        }
       +                        fbuffree(s);
       +                        fbuffree(r);
       +                        break;
       +                case QWwrsel:
       +                        w->nopen[q]++;
       +                        seq++;
       +                        filemark(t->file);
       +                        cut(t, t, nil, FALSE, TRUE, nil, 0);
       +                        w->wrselrange = (Range){t->q1, t->q1};
       +                        w->nomark = TRUE;
       +                        break;
       +                case QWeditout:
       +                        if(editing == FALSE){
       +                                winunlock(w);
       +                                respond(x, &fc, Eperm);
       +                                return;
       +                        }
       +                        w->wrselrange = (Range){t->q1, t->q1};
       +                        break;
       +                }
       +                winunlock(w);
       +        }
       +        fc.qid = x->f->qid;
       +        fc.iounit = messagesize-IOHDRSZ;
       +        x->f->open = TRUE;
       +        respond(x, &fc, nil);
       +}
       +
       +void
       +xfidclose(Xfid *x)
       +{
       +        Fcall fc;
       +        Window *w;
       +        int q;
       +        Text *t;
       +
       +        w = x->f->w;
       +        x->f->busy = FALSE;
       +        if(x->f->open == FALSE){
       +                if(w != nil)
       +                        winclose(w);
       +                respond(x, &fc, nil);
       +                return;
       +        }
       +
       +        x->f->open = FALSE;
       +        if(w){
       +                winlock(w, 'E');
       +                q = FILE(x->f->qid);
       +                switch(q){
       +                case QWctl:
       +                        if(w->ctlfid!=~0 && w->ctlfid==x->f->fid){
       +                                w->ctlfid = ~0;
       +                                qunlock(&w->ctllock);
       +                        }
       +                        break;
       +                case QWdata:
       +                        w->nomark = FALSE;
       +                        /* fall through */
       +                case QWaddr:
       +                case QWevent:        /* BUG: do we need to shut down Xfid? */
       +                        if(--w->nopen[q] == 0){
       +                                if(q == QWdata)
       +                                        w->nomark = FALSE;
       +                                if(q==QWevent && !w->isdir && w->col!=nil){
       +                                        w->filemenu = TRUE;
       +                                        winsettag(w);
       +                                }
       +                                if(q == QWevent){
       +                                        free(w->dumpstr);
       +                                        free(w->dumpdir);
       +                                        w->dumpstr = nil;
       +                                        w->dumpdir = nil;
       +                                }
       +                        }
       +                        break;
       +                case QWrdsel:
       +                        close(w->rdselfd);
       +                        w->rdselfd = 0;
       +                        break;
       +                case QWwrsel:
       +                        w->nomark = FALSE;
       +                        t = &w->body;
       +                        /* before: only did this if !w->noscroll, but that didn't seem right in practice */
       +                        textshow(t, min(w->wrselrange.q0, t->file->b.nc),
       +                                min(w->wrselrange.q1, t->file->b.nc), 1);
       +                        textscrdraw(t);
       +                        break;
       +                }
       +                winunlock(w);
       +                winclose(w);
       +        }
       +        respond(x, &fc, nil);
       +}
       +
       +void
       +xfidread(Xfid *x)
       +{
       +        Fcall fc;
       +        int n, q;
       +        uint off;
       +        char *b;
       +        char buf[128];
       +        Window *w;
       +
       +        q = FILE(x->f->qid);
       +        w = x->f->w;
       +        if(w == nil){
       +                fc.count = 0;
       +                switch(q){
       +                case Qcons:
       +                case Qlabel:
       +                        break;
       +                case Qindex:
       +                        xfidindexread(x);
       +                        return;
       +                default:
       +                        warning(nil, "unknown qid %d\n", q);
       +                        break;
       +                }
       +                respond(x, &fc, nil);
       +                return;
       +        }
       +        winlock(w, 'F');
       +        if(w->col == nil){
       +                winunlock(w);
       +                respond(x, &fc, Edel);
       +                return;
       +        }
       +        off = x->fcall.offset;
       +        switch(q){
       +        case QWaddr:
       +                textcommit(&w->body, TRUE);
       +                clampaddr(w);
       +                sprint(buf, "%11d %11d ", w->addr.q0, w->addr.q1);
       +                goto Readbuf;
       +
       +        case QWbody:
       +                xfidutfread(x, &w->body, w->body.file->b.nc, QWbody);
       +                break;
       +
       +        case QWctl:
       +                winctlprint(w, buf, 1);
       +                goto Readbuf;
       +
       +        Readbuf:
       +                n = strlen(buf);
       +                if(off > n)
       +                        off = n;
       +                if(off+x->fcall.count > n)
       +                        x->fcall.count = n-off;
       +                fc.count = x->fcall.count;
       +                fc.data = buf+off;
       +                respond(x, &fc, nil);
       +                break;
       +
       +        case QWevent:
       +                xfideventread(x, w);
       +                break;
       +
       +        case QWdata:
       +                /* BUG: what should happen if q1 > q0? */
       +                if(w->addr.q0 > w->body.file->b.nc){
       +                        respond(x, &fc, Eaddr);
       +                        break;
       +                }
       +                w->addr.q0 += xfidruneread(x, &w->body, w->addr.q0, w->body.file->b.nc);
       +                w->addr.q1 = w->addr.q0;
       +                break;
       +
       +        case QWtag:
       +                xfidutfread(x, &w->tag, w->tag.file->b.nc, QWtag);
       +                break;
       +
       +        case QWrdsel:
       +                seek(w->rdselfd, off, 0);
       +                n = x->fcall.count;
       +                if(n > BUFSIZE)
       +                        n = BUFSIZE;
       +                b = fbufalloc();
       +                n = read(w->rdselfd, b, n);
       +                if(n < 0){
       +                        respond(x, &fc, "I/O error in temp file");
       +                        break;
       +                }
       +                fc.count = n;
       +                fc.data = b;
       +                respond(x, &fc, nil);
       +                fbuffree(b);
       +                break;
       +
       +        default:
       +                sprint(buf, "unknown qid %d in read", q);
       +                respond(x, &fc, nil);
       +        }
       +        winunlock(w);
       +}
       +
       +void
       +xfidwrite(Xfid *x)
       +{
       +        Fcall fc;
       +        int c, cnt, qid, q, nb, nr, eval;
       +        char buf[64], *err;
       +        Window *w;
       +        Rune *r;
       +        Range a;
       +        Text *t;
       +        uint q0, tq0, tq1;
       +
       +        qid = FILE(x->f->qid);
       +        w = x->f->w;
       +        if(w){
       +                c = 'F';
       +                if(qid==QWtag || qid==QWbody)
       +                        c = 'E';
       +                winlock(w, c);
       +                if(w->col == nil){
       +                        winunlock(w);
       +                        respond(x, &fc, Edel);
       +                        return;
       +                }
       +        }
       +        x->fcall.data[x->fcall.count] = 0;
       +        switch(qid){
       +        case Qcons:
       +                w = errorwin(x->f->mntdir, 'X', nil);
       +                t=&w->body;
       +                goto BodyTag;
       +
       +        case Qlabel:
       +                fc.count = x->fcall.count;
       +                respond(x, &fc, nil);
       +                break;
       +
       +        case QWaddr:
       +                x->fcall.data[x->fcall.count] = 0;
       +                r = bytetorune(x->fcall.data, &nr);
       +                t = &w->body;
       +                wincommit(w, t);
       +                eval = TRUE;
       +                a = address(x->f->mntdir, t, w->limit, w->addr, r, 0, nr, rgetc, &eval, (uint*)&nb);
       +                free(r);
       +                if(nb < nr){
       +                        respond(x, &fc, Ebadaddr);
       +                        break;
       +                }
       +                if(!eval){
       +                        respond(x, &fc, Eaddr);
       +                        break;
       +                }
       +                w->addr = a;
       +                fc.count = x->fcall.count;
       +                respond(x, &fc, nil);
       +                break;
       +
       +        case Qeditout:
       +        case QWeditout:
       +                r = bytetorune(x->fcall.data, &nr);
       +                if(w)
       +                        err = edittext(w, w->wrselrange.q1, r, nr);
       +                else
       +                        err = edittext(nil, 0, r, nr);
       +                free(r);
       +                if(err != nil){
       +                        respond(x, &fc, err);
       +                        break;
       +                }
       +                fc.count = x->fcall.count;
       +                respond(x, &fc, nil);
       +                break;
       +
       +        case QWbody:
       +        case QWwrsel:
       +                t = &w->body;
       +                goto BodyTag;
       +
       +        case QWctl:
       +                xfidctlwrite(x, w);
       +                break;
       +
       +        case QWdata:
       +                a = w->addr;
       +                t = &w->body;
       +                wincommit(w, t);
       +                if(a.q0>t->file->b.nc || a.q1>t->file->b.nc){
       +                        respond(x, &fc, Eaddr);
       +                        break;
       +                }
       +                r = runemalloc(x->fcall.count);
       +                cvttorunes(x->fcall.data, x->fcall.count, r, &nb, &nr, nil);
       +                if(w->nomark == FALSE){
       +                        seq++;
       +                        filemark(t->file);
       +                }
       +                q0 = a.q0;
       +                if(a.q1 > q0){
       +                        textdelete(t, q0, a.q1, TRUE);
       +                        w->addr.q1 = q0;
       +                }
       +                tq0 = t->q0;
       +                tq1 = t->q1;
       +                textinsert(t, q0, r, nr, TRUE);
       +                if(tq0 >= q0)
       +                        tq0 += nr;
       +                if(tq1 >= q0)
       +                        tq1 += nr;
       +                textsetselect(t, tq0, tq1);
       +                if(!t->w->noscroll)
       +                        textshow(t, q0, q0+nr, 0);
       +                textscrdraw(t);
       +                winsettag(w);
       +                free(r);
       +                w->addr.q0 += nr;
       +                w->addr.q1 = w->addr.q0;
       +                fc.count = x->fcall.count;
       +                respond(x, &fc, nil);
       +                break;
       +
       +        case QWevent:
       +                xfideventwrite(x, w);
       +                break;
       +
       +        case QWtag:
       +                t = &w->tag;
       +                goto BodyTag;
       +
       +        BodyTag:
       +                q = x->f->nrpart;
       +                cnt = x->fcall.count;
       +                if(q > 0){
       +                        memmove(x->fcall.data+q, x->fcall.data, cnt);        /* there's room; see fsysproc */
       +                        memmove(x->fcall.data, x->f->rpart, q);
       +                        cnt += q;
       +                        x->f->nrpart = 0;
       +                }
       +                r = runemalloc(cnt);
       +                cvttorunes(x->fcall.data, cnt-UTFmax, r, &nb, &nr, nil);
       +                /* approach end of buffer */
       +                while(fullrune(x->fcall.data+nb, cnt-nb)){
       +                        c = nb;
       +                        nb += chartorune(&r[nr], x->fcall.data+c);
       +                        if(r[nr])
       +                                nr++;
       +                }
       +                if(nb < cnt){
       +                        memmove(x->f->rpart, x->fcall.data+nb, cnt-nb);
       +                        x->f->nrpart = cnt-nb;
       +                }
       +                if(nr > 0){
       +                        wincommit(w, t);
       +                        if(qid == QWwrsel){
       +                                q0 = w->wrselrange.q1;
       +                                if(q0 > t->file->b.nc)
       +                                        q0 = t->file->b.nc;
       +                        }else
       +                                q0 = t->file->b.nc;
       +                        if(qid == QWtag)
       +                                textinsert(t, q0, r, nr, TRUE);
       +                        else{
       +                                if(w->nomark == FALSE){
       +                                        seq++;
       +                                        filemark(t->file);
       +                                }
       +                                q0 = textbsinsert(t, q0, r, nr, TRUE, &nr);
       +                                textsetselect(t, t->q0, t->q1);        /* insert could leave it somewhere else */
       +                                if(qid!=QWwrsel && !t->w->noscroll)
       +                                        textshow(t, q0+nr, q0+nr, 1);
       +                                textscrdraw(t);
       +                        }
       +                        winsettag(w);
       +                        if(qid == QWwrsel)
       +                                w->wrselrange.q1 += nr;
       +                        free(r);
       +                }
       +                fc.count = x->fcall.count;
       +                respond(x, &fc, nil);
       +                break;
       +
       +        default:
       +                sprint(buf, "unknown qid %d in write", qid);
       +                respond(x, &fc, buf);
       +                break;
       +        }
       +        if(w)
       +                winunlock(w);
       +}
       +
       +void
       +xfidctlwrite(Xfid *x, Window *w)
       +{
       +        Fcall fc;
       +        int i, m, n, nb, nr, nulls;
       +        Rune *r;
       +        char *err, *p, *pp, *q, *e;
       +        int isfbuf, scrdraw, settag;
       +        Text *t;
       +
       +        err = nil;
       +        e = x->fcall.data+x->fcall.count;
       +        scrdraw = FALSE;
       +        settag = FALSE;
       +        isfbuf = TRUE;
       +        if(x->fcall.count < RBUFSIZE)
       +                r = fbufalloc();
       +        else{
       +                isfbuf = FALSE;
       +                r = emalloc(x->fcall.count*UTFmax+1);
       +        }
       +        x->fcall.data[x->fcall.count] = 0;
       +        textcommit(&w->tag, TRUE);
       +        for(n=0; n<x->fcall.count; n+=m){
       +                p = x->fcall.data+n;
       +                if(strncmp(p, "lock", 4) == 0){        /* make window exclusive use */
       +                        qlock(&w->ctllock);
       +                        w->ctlfid = x->f->fid;
       +                        m = 4;
       +                }else
       +                if(strncmp(p, "unlock", 6) == 0){        /* release exclusive use */
       +                        w->ctlfid = ~0;
       +                        qunlock(&w->ctllock);
       +                        m = 6;
       +                }else
       +                if(strncmp(p, "clean", 5) == 0){        /* mark window 'clean', seq=0 */
       +                        t = &w->body;
       +                        t->eq0 = ~0;
       +                        filereset(t->file);
       +                        t->file->mod = FALSE;
       +                        w->dirty = FALSE;
       +                        settag = TRUE;
       +                        m = 5;
       +                }else
       +                if(strncmp(p, "dirty", 5) == 0){        /* mark window 'dirty' */
       +                        t = &w->body;
       +                        /* doesn't change sequence number, so "Put" won't appear.  it shouldn't. */
       +                        t->file->mod = TRUE;
       +                        w->dirty = TRUE;
       +                        settag = TRUE;
       +                        m = 5;
       +                }else
       +                if(strncmp(p, "show", 4) == 0){        /* show dot */
       +                        t = &w->body;
       +                        textshow(t, t->q0, t->q1, 1);
       +                        m = 4;
       +                }else
       +                if(strncmp(p, "name ", 5) == 0){        /* set file name */
       +                        pp = p+5;
       +                        m = 5;
       +                        q = memchr(pp, '\n', e-pp);
       +                        if(q==nil || q==pp){
       +                                err = Ebadctl;
       +                                break;
       +                        }
       +                        *q = 0;
       +                        nulls = FALSE;
       +                        cvttorunes(pp, q-pp, r, &nb, &nr, &nulls);
       +                        if(nulls){
       +                                err = "nulls in file name";
       +                                break;
       +                        }
       +                        for(i=0; i<nr; i++)
       +                                if(r[i] <= ' '){
       +                                        err = "bad character in file name";
       +                                        goto out;
       +                                }
       +out:
       +                        seq++;
       +                        filemark(w->body.file);
       +                        winsetname(w, r, nr);
       +                        m += (q+1) - pp;
       +                }else
       +                if(strncmp(p, "dump ", 5) == 0){        /* set dump string */
       +                        pp = p+5;
       +                        m = 5;
       +                        q = memchr(pp, '\n', e-pp);
       +                        if(q==nil || q==pp){
       +                                err = Ebadctl;
       +                                break;
       +                        }
       +                        *q = 0;
       +                        nulls = FALSE;
       +                        cvttorunes(pp, q-pp, r, &nb, &nr, &nulls);
       +                        if(nulls){
       +                                err = "nulls in dump string";
       +                                break;
       +                        }
       +                        w->dumpstr = runetobyte(r, nr);
       +                        m += (q+1) - pp;
       +                }else
       +                if(strncmp(p, "dumpdir ", 8) == 0){        /* set dump directory */
       +                        pp = p+8;
       +                        m = 8;
       +                        q = memchr(pp, '\n', e-pp);
       +                        if(q==nil || q==pp){
       +                                err = Ebadctl;
       +                                break;
       +                        }
       +                        *q = 0;
       +                        nulls = FALSE;
       +                        cvttorunes(pp, q-pp, r, &nb, &nr, &nulls);
       +                        if(nulls){
       +                                err = "nulls in dump directory string";
       +                                break;
       +                        }
       +                        w->dumpdir = runetobyte(r, nr);
       +                        m += (q+1) - pp;
       +                }else
       +                if(strncmp(p, "delete", 6) == 0){        /* delete for sure */
       +                        colclose(w->col, w, TRUE);
       +                        m = 6;
       +                }else
       +                if(strncmp(p, "del", 3) == 0){        /* delete, but check dirty */
       +                        if(!winclean(w, TRUE)){
       +                                err = "file dirty";
       +                                break;
       +                        }
       +                        colclose(w->col, w, TRUE);
       +                        m = 3;
       +                }else
       +                if(strncmp(p, "get", 3) == 0){        /* get file */
       +                        get(&w->body, nil, nil, FALSE, XXX, nil, 0);
       +                        m = 3;
       +                }else
       +                if(strncmp(p, "put", 3) == 0){        /* put file */
       +                        put(&w->body, nil, nil, XXX, XXX, nil, 0);
       +                        m = 3;
       +                }else
       +                if(strncmp(p, "dot=addr", 8) == 0){        /* set dot */
       +                        textcommit(&w->body, TRUE);
       +                        clampaddr(w);
       +                        w->body.q0 = w->addr.q0;
       +                        w->body.q1 = w->addr.q1;
       +                        textsetselect(&w->body, w->body.q0, w->body.q1);
       +                        settag = TRUE;
       +                        m = 8;
       +                }else
       +                if(strncmp(p, "addr=dot", 8) == 0){        /* set addr */
       +                        w->addr.q0 = w->body.q0;
       +                        w->addr.q1 = w->body.q1;
       +                        m = 8;
       +                }else
       +                if(strncmp(p, "limit=addr", 10) == 0){        /* set limit */
       +                        textcommit(&w->body, TRUE);
       +                        clampaddr(w);
       +                        w->limit.q0 = w->addr.q0;
       +                        w->limit.q1 = w->addr.q1;
       +                        m = 10;
       +                }else
       +                if(strncmp(p, "nomark", 6) == 0){        /* turn off automatic marking */
       +                        w->nomark = TRUE;
       +                        m = 6;
       +                }else
       +                if(strncmp(p, "mark", 4) == 0){        /* mark file */
       +                        seq++;
       +                        filemark(w->body.file);
       +                        settag = TRUE;
       +                        m = 4;
       +                }else
       +                if(strncmp(p, "noscroll", 8) == 0){        /* turn off automatic scrolling */
       +                        w->noscroll = TRUE;
       +                        m = 8;
       +                }else
       +                if(strncmp(p, "cleartag", 8) == 0){        /* wipe tag right of bar */
       +                        wincleartag(w);
       +                        settag = TRUE;
       +                        m = 8;
       +                }else
       +                if(strncmp(p, "scroll", 6) == 0){        /* turn on automatic scrolling (writes to body only) */
       +                        w->noscroll = FALSE;
       +                        m = 6;
       +                }else{
       +                        err = Ebadctl;
       +                        break;
       +                }
       +                while(p[m] == '\n')
       +                        m++;
       +        }
       +
       +        if(isfbuf)
       +                fbuffree(r);
       +        else
       +                free(r);
       +        if(err)
       +                n = 0;
       +        fc.count = n;
       +        respond(x, &fc, err);
       +        if(settag)
       +                winsettag(w);
       +        if(scrdraw)
       +                textscrdraw(&w->body);
       +}
       +
       +void
       +xfideventwrite(Xfid *x, Window *w)
       +{
       +        Fcall fc;
       +        int m, n;
       +        Rune *r;
       +        char *err, *p, *q;
       +        int isfbuf;
       +        Text *t;
       +        int c;
       +        uint q0, q1;
       +
       +        err = nil;
       +        isfbuf = TRUE;
       +        if(x->fcall.count < RBUFSIZE)
       +                r = fbufalloc();
       +        else{
       +                isfbuf = FALSE;
       +                r = emalloc(x->fcall.count*UTFmax+1);
       +        }
       +        for(n=0; n<x->fcall.count; n+=m){
       +                p = x->fcall.data+n;
       +                w->owner = *p++;        /* disgusting */
       +                c = *p++;
       +                while(*p == ' ')
       +                        p++;
       +                q0 = strtoul(p, &q, 10);
       +                if(q == p)
       +                        goto Rescue;
       +                p = q;
       +                while(*p == ' ')
       +                        p++;
       +                q1 = strtoul(p, &q, 10);
       +                if(q == p)
       +                        goto Rescue;
       +                p = q;
       +                while(*p == ' ')
       +                        p++;
       +                if(*p++ != '\n')
       +                        goto Rescue;
       +                m = p-(x->fcall.data+n);
       +                if('a'<=c && c<='z')
       +                        t = &w->tag;
       +                else if('A'<=c && c<='Z')
       +                        t = &w->body;
       +                else
       +                        goto Rescue;
       +                if(q0>t->file->b.nc || q1>t->file->b.nc || q0>q1)
       +                        goto Rescue;
       +
       +                qlock(&row.lk);        /* just like mousethread */
       +                switch(c){
       +                case 'x':
       +                case 'X':
       +                        execute(t, q0, q1, TRUE, nil);
       +                        break;
       +                case 'l':
       +                case 'L':
       +                        look3(t, q0, q1, TRUE);
       +                        break;
       +                default:
       +                        qunlock(&row.lk);
       +                        goto Rescue;
       +                }
       +                qunlock(&row.lk);
       +
       +        }
       +
       +    Out:
       +        if(isfbuf)
       +                fbuffree(r);
       +        else
       +                free(r);
       +        if(err)
       +                n = 0;
       +        fc.count = n;
       +        respond(x, &fc, err);
       +        return;
       +
       +    Rescue:
       +        err = Ebadevent;
       +        goto Out;
       +}
       +
       +void
       +xfidutfread(Xfid *x, Text *t, uint q1, int qid)
       +{
       +        Fcall fc;
       +        Window *w;
       +        Rune *r;
       +        char *b, *b1;
       +        uint q, off, boff;
       +        int m, n, nr, nb;
       +
       +        w = t->w;
       +        wincommit(w, t);
       +        off = x->fcall.offset;
       +        r = fbufalloc();
       +        b = fbufalloc();
       +        b1 = fbufalloc();
       +        n = 0;
       +        if(qid==w->utflastqid && off>=w->utflastboff && w->utflastq<=q1){
       +                boff = w->utflastboff;
       +                q = w->utflastq;
       +        }else{
       +                /* BUG: stupid code: scan from beginning */
       +                boff = 0;
       +                q = 0;
       +        }
       +        w->utflastqid = qid;
       +        while(q<q1 && n<x->fcall.count){
       +                /*
       +                 * Updating here avoids partial rune problem: we're always on a
       +                 * char boundary. The cost is we will usually do one more read
       +                 * than we really need, but that's better than being n^2.
       +                 */
       +                w->utflastboff = boff;
       +                w->utflastq = q;
       +                nr = q1-q;
       +                if(nr > BUFSIZE/UTFmax)
       +                        nr = BUFSIZE/UTFmax;
       +                bufread(&t->file->b, q, r, nr);
       +                nb = snprint(b, BUFSIZE+1, "%.*S", nr, r);
       +                if(boff >= off){
       +                        m = nb;
       +                        if(boff+m > off+x->fcall.count)
       +                                m = off+x->fcall.count - boff;
       +                        memmove(b1+n, b, m);
       +                        n += m;
       +                }else if(boff+nb > off){
       +                        if(n != 0)
       +                                error("bad count in utfrune");
       +                        m = nb - (off-boff);
       +                        if(m > x->fcall.count)
       +                                m = x->fcall.count;
       +                        memmove(b1, b+(off-boff), m);
       +                        n += m;
       +                }
       +                boff += nb;
       +                q += nr;
       +        }
       +        fbuffree(r);
       +        fbuffree(b);
       +        fc.count = n;
       +        fc.data = b1;
       +        respond(x, &fc, nil);
       +        fbuffree(b1);
       +}
       +
       +int
       +xfidruneread(Xfid *x, Text *t, uint q0, uint q1)
       +{
       +        Fcall fc;
       +        Window *w;
       +        Rune *r, junk;
       +        char *b, *b1;
       +        uint q, boff;
       +        int i, rw, m, n, nr, nb;
       +
       +        w = t->w;
       +        wincommit(w, t);
       +        r = fbufalloc();
       +        b = fbufalloc();
       +        b1 = fbufalloc();
       +        n = 0;
       +        q = q0;
       +        boff = 0;
       +        while(q<q1 && n<x->fcall.count){
       +                nr = q1-q;
       +                if(nr > BUFSIZE/UTFmax)
       +                        nr = BUFSIZE/UTFmax;
       +                bufread(&t->file->b, q, r, nr);
       +                nb = snprint(b, BUFSIZE+1, "%.*S", nr, r);
       +                m = nb;
       +                if(boff+m > x->fcall.count){
       +                        i = x->fcall.count - boff;
       +                        /* copy whole runes only */
       +                        m = 0;
       +                        nr = 0;
       +                        while(m < i){
       +                                rw = chartorune(&junk, b+m);
       +                                if(m+rw > i)
       +                                        break;
       +                                m += rw;
       +                                nr++;
       +                        }
       +                        if(m == 0)
       +                                break;
       +                }
       +                memmove(b1+n, b, m);
       +                n += m;
       +                boff += nb;
       +                q += nr;
       +        }
       +        fbuffree(r);
       +        fbuffree(b);
       +        fc.count = n;
       +        fc.data = b1;
       +        respond(x, &fc, nil);
       +        fbuffree(b1);
       +        return q-q0;
       +}
       +
       +void
       +xfideventread(Xfid *x, Window *w)
       +{
       +        Fcall fc;
       +        char *b;
       +        int i, n;
       +
       +        i = 0;
       +        x->flushed = FALSE;
       +        while(w->nevents == 0){
       +                if(i){
       +                        if(!x->flushed)
       +                                respond(x, &fc, "window shut down");
       +                        return;
       +                }
       +                w->eventx = x;
       +                winunlock(w);
       +                recvp(x->c);
       +                winlock(w, 'F');
       +                i++;
       +        }
       +
       +        n = w->nevents;
       +        if(n > x->fcall.count)
       +                n = x->fcall.count;
       +        fc.count = n;
       +        fc.data = w->events;
       +        respond(x, &fc, nil);
       +        b = w->events;
       +        w->events = estrdup(w->events+n);
       +        free(b);
       +        w->nevents -= n;
       +}
       +
       +void
       +xfidindexread(Xfid *x)
       +{
       +        Fcall fc;
       +        int i, j, m, n, nmax, isbuf, cnt, off;
       +        Window *w;
       +        char *b;
       +        Rune *r;
       +        Column *c;
       +
       +        qlock(&row.lk);
       +        nmax = 0;
       +        for(j=0; j<row.ncol; j++){
       +                c = row.col[j];
       +                for(i=0; i<c->nw; i++){
       +                        w = c->w[i];
       +                        nmax += Ctlsize + w->tag.file->b.nc*UTFmax + 1;
       +                }
       +        }
       +        nmax++;
       +        isbuf = (nmax<=RBUFSIZE);
       +        if(isbuf)
       +                b = (char*)x->buf;
       +        else
       +                b = emalloc(nmax);
       +        r = fbufalloc();
       +        n = 0;
       +        for(j=0; j<row.ncol; j++){
       +                c = row.col[j];
       +                for(i=0; i<c->nw; i++){
       +                        w = c->w[i];
       +                        /* only show the currently active window of a set */
       +                        if(w->body.file->curtext != &w->body)
       +                                continue;
       +                        winctlprint(w, b+n, 0);
       +                        n += Ctlsize;
       +                        m = min(RBUFSIZE, w->tag.file->b.nc);
       +                        bufread(&w->tag.file->b, 0, r, m);
       +                        m = n + snprint(b+n, nmax-n-1, "%.*S", m, r);
       +                        while(n<m && b[n]!='\n')
       +                                n++;
       +                        b[n++] = '\n';
       +                }
       +        }
       +        qunlock(&row.lk);
       +        off = x->fcall.offset;
       +        cnt = x->fcall.count;
       +        if(off > n)
       +                off = n;
       +        if(off+cnt > n)
       +                cnt = n-off;
       +        fc.count = cnt;
       +        memmove(r, b+off, cnt);
       +        fc.data = (char*)r;
       +        if(!isbuf)
       +                free(b);
       +        respond(x, &fc, nil);
       +        fbuffree(r);
       +}
 (DIR) diff --git a/src/lib9/_p9translate.c b/src/lib9/_p9translate.c
       t@@ -0,0 +1,46 @@
       +#include <u.h>
       +#include <libc.h>
       +
       +/*
       + * I don't want too many of these,
       + * but the ones we have are just too useful. 
       + */
       +static struct {
       +        char *old;
       +        char *new;
       +} replace[] = {
       +        "#9", nil,        /* must be first */
       +        "#d", "/dev/fd",
       +};
       +
       +char*
       +_p9translate(char *old)
       +{
       +        char *new;
       +        int i, olen, nlen, len;
       +
       +        if(replace[0].new == nil){
       +                replace[0].new = getenv("PLAN9");
       +                if(replace[0].new == nil)
       +                        replace[0].new = "/usr/local/plan9";
       +        }
       +
       +        for(i=0; i<nelem(replace); i++){
       +                if(!replace[i].new)
       +                        continue;
       +                olen = strlen(replace[i].old);
       +                if(strncmp(old, replace[i].old, olen) != 0
       +                || (old[olen] != '\0' && old[olen] != '/'))
       +                        continue;
       +                nlen = strlen(replace[i].new);
       +                len = strlen(old)+nlen-olen;
       +                new = malloc(len+1);
       +                if(new == nil)
       +                        return nil;
       +                strcpy(new, replace[i].new);
       +                strcpy(new+nlen, old+olen);
       +                assert(strlen(new) == len);
       +                return new;
       +        }
       +        return old;
       +}
 (DIR) diff --git a/src/lib9/access.c b/src/lib9/access.c
       t@@ -0,0 +1,19 @@
       +#include <u.h>
       +#define NOPLAN9DEFINES
       +#include <libc.h>
       +
       +char *_p9translate(char*);
       +
       +int
       +p9access(char *xname, int what)
       +{
       +        int ret;
       +        char *name;
       +
       +        if((name = _p9translate(xname)) == nil)
       +                return -1;
       +        ret = access(name, what);
       +        if(name != xname)
       +                free(name);
       +        return ret;
       +}
 (DIR) diff --git a/src/lib9/getns.c b/src/lib9/getns.c
       t@@ -0,0 +1,74 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <ctype.h>
       +
       +/*
       + * Absent other hints, it works reasonably well to use
       + * the X11 display name as the name space identifier.
       + * This is how sam's B has worked since the early days.
       + * Since most programs using name spaces are also using X,
       + * this still seems reasonable.  Terminal-only sessions
       + * can set $NAMESPACE.
       + */
       +static char*
       +nsfromdisplay(void)
       +{
       +        int fd;
       +        Dir *d;
       +        char *disp, *p;
       +
       +        if((disp = getenv("DISPLAY")) == nil){
       +                werrstr("$DISPLAY not set");
       +                return nil;
       +        }
       +
       +        /* canonicalize: xxx:0.0 => xxx:0 */
       +        p = strrchr(disp, ':');
       +        if(p){
       +                p++;
       +                while(isdigit((uchar)*p))
       +                        p++;
       +                if(strcmp(p, ".0") == 0)
       +                        *p = 0;
       +        }
       +
       +        p = smprint("/tmp/ns.%s.%s", getuser(), disp);
       +        free(disp);
       +        if(p == nil){
       +                werrstr("out of memory");
       +                return p;
       +        }
       +        if((fd=create(p, OREAD, DMDIR|0700)) >= 0){
       +                close(fd);
       +                return p;
       +        }
       +        if((d = dirstat(p)) == nil){
       +                free(d);
       +                werrstr("stat %s: %r", p);
       +                free(p);
       +                return nil;
       +        }
       +        if((d->mode&0777) != 0700 || strcmp(d->uid, getuser()) != 0){
       +                werrstr("bad name space dir %s", p);
       +                free(p);
       +                free(d);
       +                return nil;
       +        }
       +        free(d);
       +        return p;
       +}
       +
       +char*
       +getns(void)
       +{
       +        char *ns;
       +
       +        ns = getenv("NAMESPACE");
       +        if(ns == nil)
       +                ns = nsfromdisplay();
       +        if(ns == nil){
       +                werrstr("$NAMESPACE not set, %r");
       +                return nil;
       +        }
       +        return ns;
       +}
 (DIR) diff --git a/src/lib9/malloc.c b/src/lib9/malloc.c
       t@@ -0,0 +1,11 @@
       +#include <u.h>
       +#define NOPLAN9DEFINES
       +#include <libc.h>
       +
       +void*
       +p9malloc(ulong n)
       +{
       +        if(n == 0)
       +                n++;
       +        return malloc(n);
       +}
 (DIR) diff --git a/src/lib9/open.c b/src/lib9/open.c
       t@@ -0,0 +1,38 @@
       +#include <u.h>
       +#define NOPLAN9DEFINES
       +#include <libc.h>
       +
       +extern char* _p9translate(char*);
       +
       +int
       +p9open(char *xname, int mode)
       +{
       +        char *name;
       +        int cexec, rclose;
       +        int fd, umode;
       +
       +        umode = mode&3;
       +        cexec = mode&OCEXEC;
       +        rclose = mode&ORCLOSE;
       +        mode &= ~(3|OCEXEC|ORCLOSE);
       +        if(mode&OTRUNC){
       +                umode |= O_TRUNC;
       +                mode ^= OTRUNC;
       +        }
       +        if(mode){
       +                werrstr("mode not supported");
       +                return -1;
       +        }
       +        if((name = _p9translate(xname)) == nil)
       +                return -1;
       +        fd = open(name, umode);
       +        if(fd >= 0){
       +                if(cexec)
       +                        fcntl(fd, F_SETFL, FD_CLOEXEC);
       +                if(rclose)
       +                        remove(name);
       +        }
       +        if(name != xname)
       +                free(name);
       +        return fd;
       +}
 (DIR) diff --git a/src/lib9/pipe.c b/src/lib9/pipe.c
       t@@ -0,0 +1,10 @@
       +#include <u.h>
       +#define NOPLAN9DEFINES
       +#include <libc.h>
       +#include <sys/socket.h>
       +
       +int
       +p9pipe(int fd[2])
       +{
       +        return socketpair(AF_UNIX, SOCK_STREAM, 0, fd);
       +}
 (DIR) diff --git a/src/lib9/post9p.c b/src/lib9/post9p.c
       t@@ -0,0 +1,40 @@
       +#include <u.h>
       +#include <libc.h>
       +
       +int
       +post9pservice(int fd, char *name)
       +{
       +        int i;
       +        char *ns, *s;
       +        Waitmsg *w;
       +
       +        if((ns = getns()) == nil)
       +                return -1;
       +        s = smprint("unix!%s/%s", ns, name);
       +        free(ns);
       +        if(s == nil)
       +                return -1;
       +        switch(rfork(RFPROC|RFFDG)){
       +        case -1:
       +                return -1;
       +        case 0:
       +                dup(fd, 0);
       +                dup(fd, 1);
       +                for(i=3; i<20; i++)
       +                        close(i);
       +                execlp("9pserve", "9pserve", "-u", s, (char*)0);
       +                fprint(2, "exec 9pserve: %r\n");
       +                _exits("exec");
       +        default:
       +                w = wait();
       +                close(fd);
       +                free(s);
       +                if(w->msg && w->msg[0]){
       +                        free(w);
       +                        werrstr("9pserve failed");
       +                        return -1;
       +                }
       +                free(w);
       +                return 0;
       +        }
       +}
 (DIR) diff --git a/src/lib9/sendfd.c b/src/lib9/sendfd.c
       t@@ -0,0 +1,79 @@
       +#include <u.h>
       +#define NOPLAN9DEFINES
       +#include <libc.h>
       +#include <sys/socket.h>
       +#include <sys/uio.h>
       +#include <unistd.h>
       +#include <errno.h>
       +
       +typedef struct Sendfd Sendfd;
       +struct Sendfd {
       +        struct cmsghdr cmsg;
       +        int fd;
       +};
       +
       +int
       +sendfd(int s, int fd)
       +{
       +        char buf[1];
       +        struct iovec iov;
       +        struct msghdr msg;
       +        int n;
       +        Sendfd sfd;
       +
       +        buf[0] = 0;
       +        iov.iov_base = buf;
       +        iov.iov_len = 1;
       +
       +        memset(&msg, 0, sizeof msg);
       +        msg.msg_iov = &iov;
       +        msg.msg_iovlen = 1;
       +
       +        sfd.cmsg.cmsg_len = sizeof sfd;
       +        sfd.cmsg.cmsg_level = SOL_SOCKET;
       +        sfd.cmsg.cmsg_type = SCM_RIGHTS;
       +        sfd.fd = fd;
       +
       +        msg.msg_control = &sfd;
       +        msg.msg_controllen = sizeof sfd;
       +
       +        if((n=sendmsg(s, &msg, 0)) != iov.iov_len)
       +                return -1;
       +        return 0;
       +}
       +
       +int
       +recvfd(int s)
       +{
       +        int n;
       +        char buf[1];
       +        struct iovec iov;
       +        struct msghdr msg;
       +        Sendfd sfd;
       +
       +        iov.iov_base = buf;
       +        iov.iov_len = 1;
       +
       +        memset(&msg, 0, sizeof msg);
       +        msg.msg_name = 0;
       +        msg.msg_namelen = 0;
       +        msg.msg_iov = &iov;
       +        msg.msg_iovlen = 1;
       +
       +        memset(&sfd, 0, sizeof sfd);
       +        sfd.fd = -1;
       +        sfd.cmsg.cmsg_len = sizeof sfd;
       +        sfd.cmsg.cmsg_level = SOL_SOCKET;
       +        sfd.cmsg.cmsg_type = SCM_RIGHTS;
       +
       +        msg.msg_control = &sfd;
       +        msg.msg_controllen = sizeof sfd;
       +
       +        if((n=recvmsg(s, &msg, 0)) < 0)
       +                return -1;
       +        if(n==0 && sfd.fd==-1){
       +                werrstr("eof in recvfd");
       +                return -1;
       +        }
       +        return sfd.fd;
       +}
 (DIR) diff --git a/src/libbio/_lib9.h b/src/libbio/_lib9.h
       t@@ -0,0 +1,12 @@
       +#include <fmt.h>
       +#include <fcntl.h>
       +#include        <string.h>
       +#include        <unistd.h>
       +#include <stdlib.h>
       +
       +#define OREAD O_RDONLY
       +#define OWRITE O_WRONLY
       +
       +#include <utf.h>
       +
       +#define nil ((void*)0)
 (DIR) diff --git a/src/libfs/ns.c b/src/libfs/ns.c
       t@@ -0,0 +1,36 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <fcall.h>
       +#include <fs.h>
       +#include <ctype.h>
       +
       +Fsys*
       +nsmount(char *name, char *aname)
       +{
       +        char *addr, *ns;
       +        int fd;
       +        Fsys *fs;
       +
       +        ns = getns();
       +        if(ns == nil)
       +                return nil;
       +
       +        addr = smprint("unix!%s/%s", ns, name);
       +        free(ns);
       +        if(addr == nil)
       +                return nil;
       +
       +        fd = dial(addr, 0, 0, 0);
       +        if(fd < 0){
       +                werrstr("dial %s: %r", addr);
       +                return nil;
       +        }
       +
       +        fs = fsmount(fd, aname);
       +        if(fs == nil){
       +                close(fd);
       +                return nil;
       +        }
       +
       +        return fs;
       +}
 (DIR) diff --git a/src/libfs/openfd.c b/src/libfs/openfd.c
       t@@ -0,0 +1,26 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <fcall.h>
       +#include <fs.h>
       +#include "fsimpl.h"
       +
       +int
       +fsopenfd(Fsys *fs, char *name, int mode)
       +{
       +        Fid *fid;
       +        Fcall tx, rx;
       +
       +        if((fid = fswalk(fs->root, name)) == nil)
       +                return -1;
       +        tx.type = Topenfd;
       +        tx.fid = fid->fid;
       +        tx.mode = mode&~OCEXEC;
       +        if(fsrpc(fs, &tx, &rx, 0) < 0){
       +                fsclose(fid);
       +                return -1;
       +        }
       +        _fsputfid(fid);
       +        if(mode&OCEXEC && rx.unixfd>=0)
       +                fcntl(rx.unixfd, F_SETFL, FD_CLOEXEC);
       +        return rx.unixfd;
       +}