aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorrsc <devnull@localhost>2003-12-11 17:50:28 +0000
committerrsc <devnull@localhost>2003-12-11 17:50:28 +0000
commitb3994ec5c78e6c18885079b58abb7fb997899c3f (patch)
treed4ead391f5ebd1554cc5ecfba69130e750de67bb /src
parent32f69c36e0eec1227934bbd34854bfebd88686f2 (diff)
downloadplan9port-b3994ec5c78e6c18885079b58abb7fb997899c3f.tar.gz
plan9port-b3994ec5c78e6c18885079b58abb7fb997899c3f.tar.bz2
plan9port-b3994ec5c78e6c18885079b58abb7fb997899c3f.zip
More files related to user-level file servers.
Also add acme!
Diffstat (limited to 'src')
-rw-r--r--src/cmd/acme/acme.c949
-rw-r--r--src/cmd/acme/addr.c269
-rw-r--r--src/cmd/acme/buff.c322
-rw-r--r--src/cmd/acme/cols.c556
-rw-r--r--src/cmd/acme/dat.h546
-rw-r--r--src/cmd/acme/disk.c129
-rw-r--r--src/cmd/acme/ecmd.c1325
-rw-r--r--src/cmd/acme/edit.c682
-rw-r--r--src/cmd/acme/edit.h101
-rw-r--r--src/cmd/acme/elog.c350
-rw-r--r--src/cmd/acme/exec.c1491
-rw-r--r--src/cmd/acme/file.c310
-rw-r--r--src/cmd/acme/fns.h92
-rw-r--r--src/cmd/acme/fsys.c717
-rw-r--r--src/cmd/acme/look.c772
-rw-r--r--src/cmd/acme/mkfile41
-rw-r--r--src/cmd/acme/regx.c835
-rw-r--r--src/cmd/acme/rows.c731
-rw-r--r--src/cmd/acme/scrl.c165
-rw-r--r--src/cmd/acme/text.c1221
-rw-r--r--src/cmd/acme/time.c121
-rw-r--r--src/cmd/acme/util.c395
-rw-r--r--src/cmd/acme/wind.c576
-rw-r--r--src/cmd/acme/xfid.c1046
-rw-r--r--src/lib9/_p9translate.c46
-rw-r--r--src/lib9/access.c19
-rw-r--r--src/lib9/getns.c74
-rw-r--r--src/lib9/malloc.c11
-rw-r--r--src/lib9/open.c38
-rw-r--r--src/lib9/pipe.c10
-rw-r--r--src/lib9/post9p.c40
-rw-r--r--src/lib9/sendfd.c79
-rw-r--r--src/libbio/_lib9.h12
-rw-r--r--src/libfs/ns.c36
-rw-r--r--src/libfs/openfd.c26
35 files changed, 14133 insertions, 0 deletions
diff --git a/src/cmd/acme/acme.c b/src/cmd/acme/acme.c
new file mode 100644
index 00000000..d9412c4e
--- /dev/null
+++ b/src/cmd/acme/acme.c
@@ -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);
+}
diff --git a/src/cmd/acme/addr.c b/src/cmd/acme/addr.c
new file mode 100644
index 00000000..d64db61b
--- /dev/null
+++ b/src/cmd/acme/addr.c
@@ -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;
+}
diff --git a/src/cmd/acme/buff.c b/src/cmd/acme/buff.c
new file mode 100644
index 00000000..39982f17
--- /dev/null
+++ b/src/cmd/acme/buff.c
@@ -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;
+}
diff --git a/src/cmd/acme/cols.c b/src/cmd/acme/cols.c
new file mode 100644
index 00000000..0e6ff409
--- /dev/null
+++ b/src/cmd/acme/cols.c
@@ -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;
+}
diff --git a/src/cmd/acme/dat.h b/src/cmd/acme/dat.h
new file mode 100644
index 00000000..b2a443da
--- /dev/null
+++ b/src/cmd/acme/dat.h
@@ -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
diff --git a/src/cmd/acme/disk.c b/src/cmd/acme/disk.c
new file mode 100644
index 00000000..857d9329
--- /dev/null
+++ b/src/cmd/acme/disk.c
@@ -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");
+}
diff --git a/src/cmd/acme/ecmd.c b/src/cmd/acme/ecmd.c
new file mode 100644
index 00000000..677bafb1
--- /dev/null
+++ b/src/cmd/acme/ecmd.c
@@ -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;
+}
diff --git a/src/cmd/acme/edit.c b/src/cmd/acme/edit.c
new file mode 100644
index 00000000..e052430e
--- /dev/null
+++ b/src/cmd/acme/edit.c
@@ -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;
+}
diff --git a/src/cmd/acme/edit.h b/src/cmd/acme/edit.h
new file mode 100644
index 00000000..efa0b02e
--- /dev/null
+++ b/src/cmd/acme/edit.h
@@ -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);
diff --git a/src/cmd/acme/elog.c b/src/cmd/acme/elog.c
new file mode 100644
index 00000000..e86af6ec
--- /dev/null
+++ b/src/cmd/acme/elog.c
@@ -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;
+}
diff --git a/src/cmd/acme/exec.c b/src/cmd/acme/exec.c
new file mode 100644
index 00000000..9c29dc1a
--- /dev/null
+++ b/src/cmd/acme/exec.c
@@ -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);
+}
diff --git a/src/cmd/acme/file.c b/src/cmd/acme/file.c
new file mode 100644
index 00000000..cca91b2e
--- /dev/null
+++ b/src/cmd/acme/file.c
@@ -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;
+}
diff --git a/src/cmd/acme/fns.h b/src/cmd/acme/fns.h
new file mode 100644
index 00000000..69bbbb3b
--- /dev/null
+++ b/src/cmd/acme/fns.h
@@ -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))
diff --git a/src/cmd/acme/fsys.c b/src/cmd/acme/fsys.c
new file mode 100644
index 00000000..cd333dc7
--- /dev/null
+++ b/src/cmd/acme/fsys.c
@@ -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);
+}
diff --git a/src/cmd/acme/look.c b/src/cmd/acme/look.c
new file mode 100644
index 00000000..9f54f9e4
--- /dev/null
+++ b/src/cmd/acme/look.c
@@ -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);
+ }
+}
diff --git a/src/cmd/acme/mkfile b/src/cmd/acme/mkfile
new file mode 100644
index 00000000..84149eb9
--- /dev/null
+++ b/src/cmd/acme/mkfile
@@ -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
diff --git a/src/cmd/acme/regx.c b/src/cmd/acme/regx.c
new file mode 100644
index 00000000..f934187b
--- /dev/null
+++ b/src/cmd/acme/regx.c
@@ -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;
+ }
+}
diff --git a/src/cmd/acme/rows.c b/src/cmd/acme/rows.c
new file mode 100644
index 00000000..11014c2c
--- /dev/null
+++ b/src/cmd/acme/rows.c
@@ -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);
+ }
+}
diff --git a/src/cmd/acme/scrl.c b/src/cmd/acme/scrl.c
new file mode 100644
index 00000000..77aa0f85
--- /dev/null
+++ b/src/cmd/acme/scrl.c
@@ -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);
+}
diff --git a/src/cmd/acme/text.c b/src/cmd/acme/text.c
new file mode 100644
index 00000000..c38773ea
--- /dev/null
+++ b/src/cmd/acme/text.c
@@ -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);
+}
diff --git a/src/cmd/acme/time.c b/src/cmd/acme/time.c
new file mode 100644
index 00000000..b281d68d
--- /dev/null
+++ b/src/cmd/acme/time.c
@@ -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;
+}
diff --git a/src/cmd/acme/util.c b/src/cmd/acme/util.c
new file mode 100644
index 00000000..8e3cfa29
--- /dev/null
+++ b/src/cmd/acme/util.c
@@ -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;
+}
diff --git a/src/cmd/acme/wind.c b/src/cmd/acme/wind.c
new file mode 100644
index 00000000..a9a1c4bf
--- /dev/null
+++ b/src/cmd/acme/wind.c
@@ -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);
+ }
+}
diff --git a/src/cmd/acme/xfid.c b/src/cmd/acme/xfid.c
new file mode 100644
index 00000000..f397623e
--- /dev/null
+++ b/src/cmd/acme/xfid.c
@@ -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);
+}
diff --git a/src/lib9/_p9translate.c b/src/lib9/_p9translate.c
new file mode 100644
index 00000000..4eb6eac9
--- /dev/null
+++ b/src/lib9/_p9translate.c
@@ -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;
+}
diff --git a/src/lib9/access.c b/src/lib9/access.c
new file mode 100644
index 00000000..20b00c32
--- /dev/null
+++ b/src/lib9/access.c
@@ -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;
+}
diff --git a/src/lib9/getns.c b/src/lib9/getns.c
new file mode 100644
index 00000000..29bc857c
--- /dev/null
+++ b/src/lib9/getns.c
@@ -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;
+}
diff --git a/src/lib9/malloc.c b/src/lib9/malloc.c
new file mode 100644
index 00000000..b75d2f07
--- /dev/null
+++ b/src/lib9/malloc.c
@@ -0,0 +1,11 @@
+#include <u.h>
+#define NOPLAN9DEFINES
+#include <libc.h>
+
+void*
+p9malloc(ulong n)
+{
+ if(n == 0)
+ n++;
+ return malloc(n);
+}
diff --git a/src/lib9/open.c b/src/lib9/open.c
new file mode 100644
index 00000000..bb597e8f
--- /dev/null
+++ b/src/lib9/open.c
@@ -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;
+}
diff --git a/src/lib9/pipe.c b/src/lib9/pipe.c
new file mode 100644
index 00000000..f9fe2420
--- /dev/null
+++ b/src/lib9/pipe.c
@@ -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);
+}
diff --git a/src/lib9/post9p.c b/src/lib9/post9p.c
new file mode 100644
index 00000000..35ba3167
--- /dev/null
+++ b/src/lib9/post9p.c
@@ -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;
+ }
+}
diff --git a/src/lib9/sendfd.c b/src/lib9/sendfd.c
new file mode 100644
index 00000000..b3a2448f
--- /dev/null
+++ b/src/lib9/sendfd.c
@@ -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;
+}
diff --git a/src/libbio/_lib9.h b/src/libbio/_lib9.h
new file mode 100644
index 00000000..843f7558
--- /dev/null
+++ b/src/libbio/_lib9.h
@@ -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)
diff --git a/src/libfs/ns.c b/src/libfs/ns.c
new file mode 100644
index 00000000..77a03a0f
--- /dev/null
+++ b/src/libfs/ns.c
@@ -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;
+}
diff --git a/src/libfs/openfd.c b/src/libfs/openfd.c
new file mode 100644
index 00000000..9e48c791
--- /dev/null
+++ b/src/libfs/openfd.c
@@ -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;
+}