commit 58998bc84d7a091ffd9cb2d04f3d9f563cd60825
Author: thing1 <thing1@seacrossedlovers.xyz>
Date: Fri, 28 Nov 2025 21:00:13 +0000
init commit
Diffstat:
| A | .gitignore | | | 5 | +++++ |
| A | acme.c | | | 1171 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | addr.c | | | 297 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | buff.c | | | 325 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | cols.c | | | 593 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | dat.c | | | 62 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | dat.h | | | 582 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | disk.c | | | 133 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | ecmd.c | | | 1396 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | edit.c | | | 686 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | edit.h | | | 99 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | elog.c | | | 354 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | exec.c | | | 1817 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | file.c | | | 311 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | fns.h | | | 109 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | fsys.c | | | 749 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | logf.c | | | 199 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | look.c | | | 942 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | mail/dat.h | | | 180 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | mail/guide | | | 4 | ++++ |
| A | mail/html.c | | | 75 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | mail/mail.c | | | 643 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | mail/mesg.c | | | 1424 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | mail/mkbox | | | 11 | +++++++++++ |
| A | mail/mkfile | | | 15 | +++++++++++++++ |
| A | mail/readme | | | 57 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | mail/reply.c | | | 580 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | mail/util.c | | | 106 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | mail/win.c | | | 379 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | mkfile | | | 53 | +++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | regx.c | | | 843 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | rows.c | | | 830 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | scrl.c | | | 159 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | text.c | | | 1664 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | time.c | | | 124 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | util.c | | | 497 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | wind.c | | | 726 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | xfid.c | | | 1147 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
38 files changed, 19347 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,4 @@
+*.o
+o.acme
+mail/*.o
+mail/o.Mail
+\ No newline at end of file
diff --git a/acme.c b/acme.c
@@ -0,0 +1,1171 @@
+#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 <libsec.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*);
+int timefmt(Fmt*);
+
+Reffont **fontcache;
+int nfontcache;
+char wdir[512] = ".";
+Reffont *reffonts[2];
+int snarffd = -1;
+int mainpid;
+int swapscrollbuttons = FALSE;
+char *mtpt;
+
+enum{
+ NSnarf = 1000 /* less than 1024, I/O buffer size */
+};
+Rune snarfrune[NSnarf+1];
+
+char *fontnames[2] =
+{
+ "/lib/font/bit/lucsans/euro.8.font",
+ "/lib/font/bit/lucm/unicode.9.font"
+};
+
+Command *command;
+
+void shutdownthread(void*);
+void acmeerrorinit(void);
+void readfile(Column*, char*);
+static 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 = alloca(128);
+ Column *c;
+ int ncol;
+ Display *d;
+
+ rfork(RFENVG|RFNAMEG);
+
+ ncol = -1;
+
+ strcpy(loadfile, "acme.dump");
+ ARGBEGIN{
+ case 'D':
+ {extern int _threaddebuglevel;
+ _threaddebuglevel = ~0;
+ }
+ break;
+ case 'a':
+ globalautoindent = TRUE;
+ break;
+ 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;
+ case 'm':
+ mtpt = ARGF();
+ if(mtpt == nil)
+ goto Usage;
+ break;
+ case 'r':
+ swapscrollbuttons = TRUE;
+ break;
+ case 'W':
+ winsize = ARGF();
+ if(winsize == nil)
+ goto Usage;
+ break;
+ default:
+ Usage:
+ fprint(2, "usage: acme -a -c ncol -f fontname -F fixedwidthfontname -l loadfile -W winsize\n");
+ threadexitsall("usage");
+ }ARGEND
+
+ fontnames[0] = estrdup(fontnames[0]);
+ fontnames[1] = estrdup(fontnames[1]);
+
+ quotefmtinstall();
+ fmtinstall('t', timefmt);
+
+ cputype = getenv("cputype");
+ objtype = getenv("objtype");
+ home = getenv("HOME");
+ acmeshell = getenv("acmeshell");
+ if(acmeshell && *acmeshell == '\0')
+ acmeshell = nil;
+ 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");
+ threadexitsall("geninitdraw");
+ }
+*/
+ if(initdraw(derror, fontnames[0], "acme") < 0){
+ fprint(2, "acme: can't open display: %r\n");
+ threadexitsall("initdraw");
+ }
+
+ d = display;
+ font = d->defaultfont;
+/*assert(font); */
+
+ 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();
+
+ display->white = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x181818FF); /* set the default bg color */
+
+
+ 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);
+ cwarn = chancreate(sizeof(void*), 1);
+ if(cwait==nil || ccommand==nil || ckill==nil || cxfidalloc==nil || cxfidfree==nil || cerr==nil || cexit==nil || cwarn==nil){
+ fprint(2, "acme: can't create initial channels: %r\n");
+ threadexitsall("channels");
+ }
+ chansetname(ccommand, "ccommand");
+ chansetname(ckill, "ckill");
+ chansetname(cxfidalloc, "cxfidalloc");
+ chansetname(cxfidfree, "cxfidfree");
+ chansetname(cnewwindow, "cnewwindow");
+ chansetname(cerr, "cerr");
+ chansetname(cedit, "cedit");
+ chansetname(cexit, "cexit");
+ chansetname(cwarn, "cwarn");
+
+ mousectl = initmouse(nil, screen);
+ if(mousectl == nil){
+ fprint(2, "acme: can't initialize mouse: %r\n");
+ threadexitsall("mouse");
+ }
+ mouse = &mousectl->m;
+ keyboardctl = initkeyboard(nil);
+ if(keyboardctl == nil){
+ fprint(2, "acme: can't initialize keyboard: %r\n");
+ threadexitsall("keyboard");
+ }
+ mainpid = getpid();
+ startplumbing();
+/*
+ plumbeditfd = plumbopen("edit", OREAD|OCEXEC);
+ if(plumbeditfd < 0)
+ fprint(2, "acme: can't initialize plumber: %r\n");
+ else{
+ cplumb = chancreate(sizeof(Plumbmsg*), 0);
+ threadcreate(plumbproc, nil, STACK);
+ }
+ plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
+*/
+
+ fsysinit();
+
+ #define WPERCOL 8
+ disk = diskinit();
+ if(!loadfile || !rowload(&row, loadfile, TRUE)){
+ 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);
+/* threadcreate(shutdownthread, nil, STACK); */
+ threadnotify(shutdown, 1);
+ recvul(cexit);
+ killprocs();
+ threadexitsall(nil);
+}
+
+void
+readfile(Column *c, char *s)
+{
+ Window *w;
+ Rune rb[256];
+ int nr;
+ Runestr rs;
+
+ w = coladd(c, nil, nil, -1);
+ if(s[0] != '/')
+ runesnprint(rb, sizeof rb, "%s/%s", wdir, s);
+ else
+ runesnprint(rb, sizeof rb, "%s", s);
+ nr = runestrlen(rb);
+ 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);
+ winresize(w, w->r, FALSE, TRUE);
+ textscrdraw(&w->body);
+ textsetselect(&w->tag, w->tag.file->b.nc, w->tag.file->b.nc);
+ xfidlog(w, "new");
+}
+
+char *ignotes[] = {
+ "sys: write on closed pipe",
+ "sys: ttin",
+ "sys: ttou",
+ "sys: tstp",
+ nil
+};
+
+char *oknotes[] ={
+ "delete",
+ "hangup",
+ "kill",
+ "exit",
+ nil
+};
+
+int dumping;
+
+static int
+shutdown(void *v, char *msg)
+{
+ int i;
+
+ USED(v);
+
+ for(i=0; ignotes[i]; i++)
+ if(strncmp(ignotes[i], msg, strlen(ignotes[i])) == 0)
+ return 1;
+
+ 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);
+ return 0;
+}
+
+/*
+void
+shutdownthread(void *v)
+{
+ char *msg;
+ Channel *c;
+
+ USED(v);
+
+ threadsetname("shutdown");
+ c = threadnotechan();
+ while((msg = recvp(c)) != nil)
+ shutdown(nil, msg);
+}
+*/
+
+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));
+ }
+ free(buf);
+}
+
+void
+acmeerrorinit(void)
+{
+ int pfd[2];
+
+ 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 = threadplumbrecv(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, MWarnings, NMALT };
+ enum { Shift = 5 };
+ 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 = ±
+ alts[MPlumb].op = CHANRCV;
+ alts[MWarnings].c = cwarn;
+ alts[MWarnings].v = nil;
+ alts[MWarnings].op = CHANRCV;
+ if(cplumb == nil)
+ alts[MPlumb].op = CHANNOP;
+ alts[NMALT].op = CHANEND;
+
+ for(;;){
+ qlock(&row.lk);
+ flushwarnings();
+ qunlock(&row.lk);
+ flushimage(display, 1);
+ switch(alt(alts)){
+ case MResize:
+ if(getwindow(display, Refnone) < 0)
+ error("attach to window");
+ draw(screen, screen->r, display->white, nil, ZP);
+ iconinit();
+ scrlresize();
+ rowresize(&row, screen->clipr);
+ 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);
+ }
+ plumbfree(pm);
+ break;
+ case MWarnings:
+ 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 && t!=nil && t->w!=nil) &&
+ (mousetext==nil || mousetext->w==nil || t->w->id!=mousetext->w->id)) {
+ xfidlog(t->w, "focus");
+ }
+
+ 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){
+ if(swapscrollbuttons){
+ if(but == 1)
+ but = 3;
+ else if(but == 3)
+ but = 1;
+ }
+ winlock(w, 'M');
+ t->eq0 = ~0;
+ textscroll(t, but);
+ winunlock(w);
+ }
+ goto Continue;
+ }
+ /* scroll buttons, wheels, etc. */
+ if(w != nil && (m.buttons & (8|16))){
+ if(m.buttons & 8)
+ but = Kscrolloneup;
+ else
+ but = Kscrollonedown;
+ winlock(w, 'M');
+ t->eq0 = ~0;
+ texttype(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|(4<<Shift))){
+ if(textselect3(t, &q0, &q1))
+ look3(t, q0, q1, FALSE, (m.buttons&(4<<Shift))!=0);
+ }
+ if(w)
+ winunlock(w);
+ goto Continue;
+ }
+ Continue:
+ 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, FALSE)){
+ textdelete(t, t->q0, t->q1, TRUE);
+ textsetselect(t, 0, 0);
+ }
+ if(w->msg[0])
+ warning(c->md, "%.*S: exit %s\n", c->nname-1, 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);
+ chansetname(x->c, "xc%p", x->c);
+ 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);
+ xfidlog(w, "new");
+ 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 = erealloc(fontcache, (nfontcache+1)*sizeof(Reffont*));
+ fontcache[nfontcache++] = r;
+ }
+ Found:
+ if(save){
+ incref(&r->ref);
+ if(reffonts[fix])
+ rfclose(reffonts[fix]);
+ reffonts[fix] = r;
+ if(name != fontnames[fix]){
+ free(fontnames[fix]);
+ fontnames[fix] = estrdup(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}
+};
+
+Cursor2 boxcursor2 = {
+ {-15, -15},
+ {0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xC0, 0x03, 0xFF,
+ 0xFF, 0xC0, 0x03, 0xFF,
+ 0xFF, 0xC0, 0x03, 0xFF,
+ 0xFF, 0xC0, 0x03, 0xFF,
+ 0xFF, 0xC0, 0x03, 0xFF,
+ 0xFF, 0xC0, 0x03, 0xFF,
+ 0xFF, 0xC0, 0x03, 0xFF,
+ 0xFF, 0xC0, 0x03, 0xFF,
+ 0xFF, 0xC0, 0x03, 0xFF,
+ 0xFF, 0xC0, 0x03, 0xFF,
+ 0xFF, 0xC0, 0x03, 0xFF,
+ 0xFF, 0xC0, 0x03, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF},
+ {0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x3F, 0xFF, 0xFF, 0xFC,
+ 0x3F, 0xFF, 0xFF, 0xFC,
+ 0x3F, 0xFF, 0xFF, 0xFC,
+ 0x3F, 0xFF, 0xFF, 0xFC,
+ 0x3F, 0xFF, 0xFF, 0xFC,
+ 0x3F, 0xFF, 0xFF, 0xFC,
+ 0x3F, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0xFC,
+ 0x3F, 0x00, 0x00, 0xFC,
+ 0x3F, 0xFF, 0xFF, 0xFC,
+ 0x3F, 0xFF, 0xFF, 0xFC,
+ 0x3F, 0xFF, 0xFF, 0xFC,
+ 0x3F, 0xFF, 0xFF, 0xFC,
+ 0x3F, 0xFF, 0xFF, 0xFC,
+ 0x3F, 0xFF, 0xFF, 0xFC,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00}
+};
+
+void
+iconinit(void)
+{
+ Rectangle r;
+ Image *tmp;
+
+ if(tagcols[BACK] == nil) {
+ /* Blue */
+ tagcols[BACK] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x453D41FF);
+ tagcols[HIGH] = allocimagemix(display, 0x453D41FF, 0xFFFFFF33);
+ tagcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xD7AF00FF);
+ tagcols[TEXT] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xF4F4FFFF);
+ tagcols[HTEXT] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xF4F4FFFF);
+
+ /* Yellow */
+ textcols[BACK] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x181818FF);
+ textcols[HIGH] = allocimagemix(display, 0x181818FF, 0xFFFFFF33);
+ textcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xD7AF00FF);
+ textcols[TEXT] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xF4F4FFFF);
+ textcols[HTEXT] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xF4F4FFFF);
+ }
+
+ r = Rect(0, 0, Scrollwid, font->height+1);
+ if(button && eqrect(r, button->r))
+ return;
+
+ if(button){
+ freeimage(button);
+ freeimage(modbutton);
+ freeimage(colbutton);
+ }
+
+ button = allocimage(display, r, screen->chan, 0, DNofill);
+ draw(button, r, tagcols[BACK], nil, r.min);
+ border(button, r, ButtonBorder, tagcols[BORD], ZP);
+
+ r = button->r;
+ modbutton = allocimage(display, r, screen->chan, 0, DNofill);
+ draw(modbutton, r, tagcols[BACK], nil, r.min);
+ border(modbutton, r, ButtonBorder, tagcols[BORD], ZP);
+ r = insetrect(r, ButtonBorder);
+ 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 i, n;
+ Fmt f;
+ char *s;
+
+ if(snarfbuf.nc==0)
+ return;
+ if(snarfbuf.nc > MAXSNARF)
+ return;
+
+ fmtstrinit(&f);
+ for(i=0; i<snarfbuf.nc; i+=n){
+ n = snarfbuf.nc-i;
+ if(n >= NSnarf)
+ n = NSnarf;
+ bufread(&snarfbuf, i, snarfrune, n);
+ if(fmtprint(&f, "%.*S", n, snarfrune) < 0)
+ break;
+ }
+ s = fmtstrflush(&f);
+ if(s && s[0])
+ putsnarf(s);
+ free(s);
+}
+
+void
+acmegetsnarf(void)
+{
+ char *s;
+ int nb, nr, nulls, len;
+ Rune *r;
+
+ s = getsnarf();
+ if(s == nil || s[0]==0){
+ free(s);
+ return;
+ }
+
+ len = strlen(s);
+ r = runemalloc(len+1);
+ cvttorunes(s, len, r, &nb, &nr, &nulls);
+ bufreset(&snarfbuf);
+ bufinsert(&snarfbuf, 0, r, nr);
+ free(r);
+ free(s);
+}
+
+int
+ismtpt(char *file)
+{
+ int n;
+
+ if(mtpt == nil)
+ return 0;
+
+ /* This is not foolproof, but it will stop a lot of them. */
+ n = strlen(mtpt);
+ return strncmp(file, mtpt, n) == 0 && ((n > 0 && mtpt[n-1] == '/') || file[n] == '/' || file[n] == 0);
+}
+
+int
+timefmt(Fmt *f)
+{
+ Tm *tm;
+
+ tm = localtime(va_arg(f->args, ulong));
+ return fmtprint(f, "%04d/%02d/%02d %02d:%02d:%02d",
+ tm->year+1900, tm->mon+1, tm->mday, tm->hour, tm->min, tm->sec);
+}
diff --git a/addr.c b/addr.c
@@ -0,0 +1,297 @@
+#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 <libsec.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;
+}
+
+// nlcounttopos starts at q0 and advances nl lines,
+// being careful not to walk past the end of the text,
+// and then nr chars, being careful not to walk past
+// the end of the current line.
+// It returns the final position.
+long
+nlcounttopos(Text *t, long q0, long nl, long nr)
+{
+ while(nl > 0 && q0 < t->file->b.nc) {
+ if(textreadc(t, q0++) == '\n')
+ nl--;
+ }
+ if(nl > 0)
+ return q0;
+ while(nr > 0 && q0 < t->file->b.nc && textreadc(t, q0) != '\n') {
+ q0++;
+ nr--;
+ }
+ return q0;
+}
+
+Range
+number(uint showerr, 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==1 && q1==t->file->b.nc) // 6 goes to end of 5-line file
+ break;
+ if(line > 0)
+ goto Rescue;
+ break;
+ case Fore:
+ if(q1 > 0)
+ while(q1<t->file->b.nc && 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;
+ }
+ /* :1-1 is :0 = #0, but :1-2 is an error */
+ if(line > 1)
+ goto Rescue;
+ while(q0>0 && textreadc(t, q0-1)!='\n')
+ --q0;
+ }
+ *evalp = TRUE;
+ return range(q0, q1);
+
+ Rescue:
+ if(showerr)
+ warning(nil, "address out of range\n");
+ *evalp = FALSE;
+ return r;
+}
+
+
+Range
+regexp(uint showerr, Text *t, Range lim, Range r, Rune *pat, int dir, int *foundp)
+{
+ int found;
+ Rangeset sel;
+ int q;
+
+ if(pat[0] == '\0' && rxnull()){
+ if(showerr)
+ warning(nil, "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 && showerr)
+ warning(nil, "no match for regexp\n");
+ *foundp = found;
+ return sel.r[0];
+}
+
+Range
+address(uint showerr, Text *t, Range lim, Range ar, void *a, uint q0, uint q1, int (*getc)(void*, uint), int *evalp, uint *qp, int reverse)
+{
+ int dir, size, npat;
+ int prevc, c, nc, n;
+ uint q;
+ Rune *pat;
+ Range r, nr;
+
+ r = ar;
+ q = q0;
+ dir = None;
+ if(reverse)
+ dir = Back;
+ 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(showerr, t, lim, ar, a, q, q1, getc, evalp, &q, FALSE);
+ r.q1 = nr.q1;
+ }
+ *qp = q;
+ return r;
+ case '+':
+ case '-':
+ if(*evalp && (prevc=='+' || prevc=='-'))
+ if((nc=(*getc)(a, q))!='#' && nc!='/' && nc!='?')
+ r = number(showerr, 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){
+ nc = (*getc)(a, q++);
+ if(nc<'0' || '9'<nc){
+ q--;
+ break;
+ }
+ n = n*10+(nc-'0');
+ }
+ if(*evalp)
+ r = number(showerr, 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(showerr, t, lim, r, pat, dir, evalp);
+ free(pat);
+ dir = None;
+ size = Line;
+ break;
+ }
+ }
+ if(*evalp && dir != None)
+ r = number(showerr, t, r, 1, dir, Line, evalp); /* do previous one */
+ *qp = q;
+ return r;
+}
diff --git a/buff.c b/buff.c
@@ -0,0 +1,325 @@
+#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 <libsec.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, DigestState *h)
+{
+ 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;
+ }
+ if(h != nil)
+ sha1((uchar*)p+m, n, nil, h);
+ 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, DigestState *h)
+{
+ if(q0 > b->nc)
+ error("internal error: bufload");
+ return loadfile(fd, q0, nulls, bufloader, b, h);
+}
+
+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/cols.c b/cols.c
@@ -0,0 +1,593 @@
+#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 <libsec.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', ' ',
+ 'w', 'i', 'n', ' ',
+ 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);
+ textinsert(t, 0, Lheader, sizeof(Lheader) / sizeof(Rune), 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, j, minht, ymax, buggered;
+
+ 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;
+ }
+ buggered = 0;
+ if(c->nw > 0){
+ if(i < c->nw)
+ i++; /* new window will go after v */
+ /*
+ * if landing window (v) is too small, grow it first.
+ */
+ minht = v->tag.fr.font->height+Border+1;
+ j = 0;
+ while(!c->safe || v->body.fr.maxlines<=3 || Dy(v->body.all) <= minht){
+ if(++j > 10){
+ buggered = 1; /* too many windows in column */
+ break;
+ }
+ colgrow(c, v, 1);
+ }
+
+ /*
+ * figure out where to split v to make room for w
+ */
+
+ /* new window stops where next window begins */
+ if(i < c->nw)
+ ymax = c->w[i]->r.min.y-Border;
+ else
+ ymax = c->r.max.y;
+
+ /* new window must start after v's tag ends */
+ y = max(y, v->tagtop.max.y+Border);
+
+ /* new window must start early enough to end before ymax */
+ y = min(y, ymax - minht);
+
+ /* if y is too small, too many windows in column */
+ if(y < v->tagtop.max.y+Border)
+ buggered = 1;
+
+ /*
+ * resize & redraw v
+ */
+ r = v->r;
+ r.max.y = ymax;
+ draw(screen, r, textcols[BACK], nil, ZP);
+ r1 = r;
+ y = min(y, ymax-(v->tag.fr.font->height*v->taglines+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, FALSE);
+ r1.max.y = r1.min.y+Border;
+ draw(screen, r1, display->black, nil, ZP);
+
+ /*
+ * leave r with w's coordinates
+ */
+ 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, TRUE);
+ }
+ 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;
+ c->safe = TRUE;
+
+ /* if there were too many windows, redraw the whole column */
+ if(buggered)
+ colresize(c, c->r);
+
+ savemouse(w);
+ /* near the button, but in the body */
+ /* don't move the mouse to the new window if a mouse button is depressed */
+ if(!mousectl->m.buttons)
+ moveto(mousectl, addpt(w->tag.scrollr.max, Pt(3, 3)));
+
+ barttext = &w->body;
+ return w;
+}
+
+void
+colclose(Column *c, Window *w, int dofree)
+{
+ Rectangle r;
+ int i, didmouse, up;
+
+ /* 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;
+ didmouse = restoremouse(w);
+ if(dofree){
+ windelete(w);
+ winclose(w);
+ }
+ c->nw--;
+ memmove(c->w+i, c->w+i+1, (c->nw-i)*sizeof(Window*));
+ c->w = realloc(c->w, c->nw*sizeof(Window*));
+ if(c->nw == 0){
+ draw(screen, r, display->white, nil, ZP);
+ return;
+ }
+ up = 0;
+ 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 */
+ up = 1;
+ w = c->w[i];
+ r.max.y = w->r.max.y;
+ }
+ draw(screen, r, textcols[BACK], nil, ZP);
+ if(c->safe) {
+ if(!didmouse && up)
+ w->showdel = TRUE;
+ winresize(w, r, FALSE, TRUE);
+ if(!didmouse && up)
+ movetodel(w);
+ }
+}
+
+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, old, new;
+ Rectangle r1, r2;
+ Window *w;
+
+ clearmouse();
+ r1 = r;
+ r1.max.y = r1.min.y + c->tag.fr.font->height;
+ textresize(&c->tag, r1, TRUE);
+ 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;
+ new = Dy(r) - c->nw*(Border + font->height);
+ old = Dy(c->r) - c->nw*(Border + font->height);
+ 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;
+ if(new > 0 && old > 0 && Dy(w->r) > Border+font->height){
+ r1.max.y += (Dy(w->r)-Border-font->height)*new/old + Border + font->height;
+ }
+ }
+ r1.max.y = max(r1.max.y, r1.min.y + Border+font->height);
+ 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, i==c->nw-1);
+ }
+ 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, i==c->nw-1);
+ }
+ 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 - Border;
+ winresize(w, r, FALSE, TRUE);
+ 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, TRUE);
+ 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]->taglines-1 + 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->taglines-1+w->maxlines), onl/2), tot);
+ if(nnl < w->taglines-1+w->maxlines)
+ nnl = (w->taglines-1+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->tagtop);
+ if(nl[j])
+ r.max.y += 1 + nl[j]*v->body.fr.font->height;
+ r.min.y = winresize(v, r, c->safe, FALSE);
+ r.max.y = r.min.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->tagtop);
+ 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 = y2;
+ h = w->body.fr.font->height;
+ if(Dy(r) < Dy(w->tagtop)+1+h+Border)
+ r.max.y = r.min.y + Dy(w->tagtop)+1+h+Border;
+ /* draw window */
+ r.max.y = winresize(w, r, c->safe, TRUE);
+ 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->tagtop);
+ if(nl[j])
+ r.max.y += 1 + nl[j]*v->body.fr.font->height;
+ y1 = winresize(v, r, c->safe, j==c->nw-1);
+ if(j < c->nw-1){ /* no border on last window */
+ r.min.y = y1;
+ r.max.y += Border;
+ draw(screen, r, display->black, nil, ZP);
+ y1 = r.max.y;
+ }
+ }
+ 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();
+ setcursor2(mousectl, &boxcursor, &boxcursor2);
+ 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:
+ if(w->tagexpand) /* force recomputation of window tag size */
+ w->taglines = 1;
+ 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 = op.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->tagtop.max.y)
+ p.y = v->tagtop.max.y;
+ if(p.y > w->r.max.y-Dy(w->tagtop)-Border)
+ p.y = w->r.max.y-Dy(w->tagtop)-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++;
+ }
+ r.min.y = winresize(v, r, c->safe, FALSE);
+ 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;
+ winresize(w, r, c->safe, TRUE);
+ 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->tagtop) || ptinrect(p, w->tag.all))
+ return &w->tag;
+ /* exclude partial line at bottom */
+ if(p.x >= w->body.scrollr.max.x && p.y >= w->body.fr.r.max.y)
+ return nil;
+ 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/dat.c b/dat.c
@@ -0,0 +1,62 @@
+#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 <libsec.h>
+#include "dat.h"
+
+uint globalincref;
+uint seq;
+uint maxtab; /* size of a tab, in units of the '0' character */
+
+Mouse *mouse;
+Mousectl *mousectl;
+Keyboardctl *keyboardctl;
+Reffont reffont;
+Image *modbutton;
+Image *colbutton;
+Image *button;
+Image *but2col;
+Image *but3col;
+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;
+Rectangle nullrect;
+int fsyspid;
+char *cputype;
+char *objtype;
+char *acmeshell;
+//char *fontnames[2];
+extern char wdir[]; /* must use extern because no dimension given */
+int globalautoindent;
+int dodollarsigns;
+
+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) */
+Channel *cwarn; /* chan(void*)[1] (really chan(unit)[1]) */
+
+QLock editoutlk;
diff --git a/dat.h b/dat.h
@@ -0,0 +1,582 @@
+enum
+{
+ Qdir,
+ Qacme,
+ Qcons,
+ Qconsctl,
+ Qdraw,
+ Qeditout,
+ Qindex,
+ Qlabel,
+ Qlog,
+ Qnew,
+
+ QWaddr,
+ QWbody,
+ QWctl,
+ QWdata,
+ QWeditout,
+ QWerrors,
+ QWevent,
+ QWrdsel,
+ QWwrsel,
+ QWtag,
+ QWxdata,
+ QMAX
+};
+
+enum
+{
+ Blockincr = 256,
+ Maxblock = 32*1024,
+ NRange = 10,
+ Infinity = 0x7FFFFFFF /* huge value for regexp address */
+};
+
+#define Buffer AcmeBuffer
+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
+{
+ vlong 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;
+ vlong 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*, DigestState*);
+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 */
+ ulong mtime; /* of file when read */
+ int dev; /* of file when read */
+ uchar sha1[20]; /* 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*, DigestState*);
+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 iq1; /* last input position */
+ uint eq0; /* start of typing for ESC */
+ uint cq0; /* cache position */
+ int ncache; /* storage for insert */
+ int ncachealloc;
+ Rune *cache;
+ int nofill;
+ int needundo;
+};
+
+uint textbacknl(Text*, uint, uint);
+uint textbsinsert(Text*, uint, Rune*, uint, int, int*);
+int textbswidth(Text*, Rune);
+int textclickhtmlmatch(Text*, uint*, uint*);
+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);
+int textload(Text*, uint, char*, int);
+Rune textreadc(Text*, uint);
+void textredraw(Text*, Rectangle, Font*, Image*, int);
+void textreset(Text*);
+int textresize(Text*, Rectangle, int);
+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;
+ uchar autoindent;
+ uchar showdel;
+ int id;
+ Range addr;
+ Range limit;
+ uchar nopen[QMAX];
+ uchar nomark;
+ Range wrselrange;
+ int rdselfd;
+ 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;
+ int tagsafe; /* taglines is correct */
+ int tagexpand;
+ int taglines;
+ Rectangle tagtop;
+ QLock editoutlk;
+};
+
+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, 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*);
+char *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*);
+int 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];
+ vlong logoff; // for putlog
+};
+
+
+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);
+void xfidlogopen(Xfid*);
+void xfidlogread(Xfid*);
+void xfidlogflush(Xfid*);
+void xfidlog(Window*, char*);
+
+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;
+ int reverse;
+ 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,
+};
+
+#define Scrollwid scalesize(display, 12)
+#define Scrollgap scalesize(display, 4)
+#define Margin scalesize(display, 4)
+#define Border scalesize(display, 2)
+#define ButtonBorder scalesize(display, 2)
+
+#define QID(w,q) ((w<<8)|(q))
+#define WIN(q) ((((ulong)(q).path)>>8) & 0xFFFFFF)
+#define FILE(q) ((q).path & 0xFF)
+
+#undef FALSE
+#undef TRUE
+
+enum
+{
+ FALSE,
+ TRUE,
+ XXX
+};
+
+enum
+{
+ Empty = 0,
+ Null = '-',
+ Delete = 'd',
+ Insert = 'i',
+ Replace = 'r',
+ Filename = 'f'
+};
+
+enum /* editing */
+{
+ Inactive = 0,
+ Inserting,
+ Collecting
+};
+
+extern uint globalincref;
+extern uint seq;
+extern uint maxtab; /* size of a tab, in units of the '0' character */
+
+extern Display *display;
+extern Image *screen;
+extern Font *font;
+extern Mouse *mouse;
+extern Mousectl *mousectl;
+extern Keyboardctl *keyboardctl;
+extern Reffont reffont;
+extern Image *modbutton;
+extern Image *colbutton;
+extern Image *button;
+extern Image *but2col;
+extern Image *but3col;
+extern Cursor boxcursor;
+extern Cursor2 boxcursor2;
+extern Row row;
+extern int timerpid;
+extern Disk *disk;
+extern Text *seltext;
+extern Text *argtext;
+extern Text *mousetext; /* global because Text.close needs to clear it */
+extern Text *typetext; /* global because Text.close needs to clear it */
+extern Text *barttext; /* shared between mousetask and keyboardthread */
+extern int bartflag;
+extern int swapscrollbuttons;
+extern Window *activewin;
+extern Column *activecol;
+extern Buffer snarfbuf;
+extern Rectangle nullrect;
+extern int fsyspid;
+extern char *cputype;
+extern char *objtype;
+extern char *home;
+extern char *acmeshell;
+extern char *fontnames[2];
+extern Image *tagcols[NCOL];
+extern Image *textcols[NCOL];
+extern char wdir[]; /* must use extern because no dimension given */
+extern int editing;
+extern int erroutfd;
+extern int messagesize; /* negotiated in 9P version setup */
+extern int globalautoindent;
+extern int dodollarsigns;
+extern char* mtpt;
+
+enum
+{
+ Kscrolloneup = KF|0x20,
+ Kscrollonedown = KF|0x21
+};
+
+extern Channel *cplumb; /* chan(Plumbmsg*) */
+extern Channel *cwait; /* chan(Waitmsg) */
+extern Channel *ccommand; /* chan(Command*) */
+extern Channel *ckill; /* chan(Rune*) */
+extern Channel *cxfidalloc; /* chan(Xfid*) */
+extern Channel *cxfidfree; /* chan(Xfid*) */
+extern Channel *cnewwindow; /* chan(Channel*) */
+extern Channel *mouseexit0; /* chan(int) */
+extern Channel *mouseexit1; /* chan(int) */
+extern Channel *cexit; /* chan(int) */
+extern Channel *cerr; /* chan(char*) */
+extern Channel *cedit; /* chan(int) */
+extern Channel *cwarn; /* chan(void*)[1] (really chan(unit)[1]) */
+
+extern QLock editoutlk;
+
+#define STACK 65536
diff --git a/disk.c b/disk.c
@@ -0,0 +1,133 @@
+#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 <libsec.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;
+ if(d->addr+size < d->addr){
+ error("temp file overflow");
+ }
+ 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/ecmd.c b/ecmd.c
@@ -0,0 +1,1396 @@
+#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 <libsec.h>
+#include "dat.h"
+#include "edit.h"
+#include "fns.h"
+
+int Glooping;
+int nest;
+char Enoname[] = "no file name given";
+
+Address addr;
+File *menu;
+extern 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(Text*, 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:
+ 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.q1;
+ 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, nil);
+ 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 = ∅
+ }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 if(addr.r.q0==addr2.r.q0 && addr.r.q1==addr2.r.q1){
+ ; /* move to self; no-op */
+ }else
+ editerror("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] = '\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;
+ 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!=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(t, 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;
+ QLock *q;
+
+ r = skipbl(cr, ncr, &n);
+ if(n == 0)
+ editerror("no command specified for %c", cmd);
+ w = nil;
+ if(state == Inserting){
+ w = t->w;
+ t->q0 = addr.r.q0;
+ t->q1 = addr.r.q1;
+ 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);
+ /*
+ * The editoutlk exists only so that we can tell when
+ * the editout file has been closed. It can get closed *after*
+ * the process exits because, since the process cannot be
+ * connected directly to editout (no 9P kernel support),
+ * the process is actually connected to a pipe to another
+ * process (arranged via 9pserve) that reads from the pipe
+ * and then writes the data in the pipe to editout using
+ * 9P transactions. This process might still have a couple
+ * writes left to copy after the original process has exited.
+ */
+ if(w)
+ q = &w->editoutlk;
+ else
+ q = &editoutlk;
+ qlock(q); /* wait for file to close */
+ qunlock(q);
+ qlock(&row.lk);
+ editing = Inactive;
+ if(t!=nil && t->w!=nil)
+ winlock(t->w, 'M');
+}
+
+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 *pnr)
+{
+ long nl, start;
+ Rune *buf;
+ int i, nbuf;
+
+ buf = fbufalloc();
+ nbuf = 0;
+ i = nl = 0;
+ start = q0;
+ 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') {
+ start = q0+1;
+ nl++;
+ }
+ q0++;
+ }
+ fbuffree(buf);
+ if(pnr != nil)
+ *pnr = q0 - start;
+ return nl;
+}
+
+enum {
+ PosnLine = 0,
+ PosnChars = 1,
+ PosnLineChars = 2,
+};
+
+void
+printposn(Text *t, int mode)
+{
+ long l1, l2, r1, r2;
+
+ if (t != nil && t->file != nil && t->file->name != nil)
+ warning(nil, "%.*S:", t->file->nname, t->file->name);
+
+ switch(mode) {
+ case PosnChars:
+ warning(nil, "#%d", addr.r.q0);
+ if(addr.r.q1 != addr.r.q0)
+ warning(nil, ",#%d", addr.r.q1);
+ warning(nil, "\n");
+ return;
+
+ default:
+ case PosnLine:
+ l1 = 1+nlcount(t, 0, addr.r.q0, nil);
+ l2 = l1+nlcount(t, addr.r.q0, addr.r.q1, nil);
+ /* 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;
+
+ case PosnLineChars:
+ l1 = 1+nlcount(t, 0, addr.r.q0, &r1);
+ l2 = l1+nlcount(t, addr.r.q0, addr.r.q1, &r2);
+ if(l2 == l1)
+ r2 += r1;
+ warning(nil, "%lud+#%d", l1, r1);
+ if(l2 != l1)
+ warning(nil, ",%lud+#%d", l2, r2);
+ warning(nil, "\n");
+ return;
+ }
+}
+
+int
+eq_cmd(Text *t, Cmd *cp)
+{
+ int mode;
+
+ switch(cp->u.text->n){
+ case 0:
+ mode = PosnLine;
+ break;
+ case 1:
+ if(cp->u.text->r[0] == '#'){
+ mode = PosnChars;
+ break;
+ }
+ if(cp->u.text->r[0] == '+'){
+ mode = PosnLineChars;
+ break;
+ }
+ default:
+ SET(mode);
+ editerror("newline expected");
+ }
+ printposn(t, mode);
+ 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;
+ 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] = '\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(Text *t, Cmd *cp, int XY)
+{
+ int i;
+ Text *targ;
+
+ 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. note this with globalincref so that any
+ * newly created windows start with an extra reference.
+ */
+ allwindows(alllocker, (void*)1);
+ globalincref = 1;
+
+ /*
+ * Unlock the window running the X command.
+ * We'll need to lock and unlock each target window in turn.
+ */
+ if(t && t->w)
+ winunlock(t->w);
+
+ for(i=0; i<loopstruct.nw; i++) {
+ targ = &loopstruct.w[i]->body;
+ if(targ && targ->w)
+ winlock(targ->w, cp->cmdc);
+ cmdexec(targ, cp->u.cmd);
+ if(targ && targ->w)
+ winunlock(targ->w);
+ }
+
+ if(t && t->w)
+ winlock(t->w, cp->cmdc);
+
+ allwindows(alllocker, (void*)0);
+ globalincref = 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);
+ n = newname.nr;
+ r = runemalloc(n+1); /* NUL terminate */
+ runemove(r, newname.r, n);
+ free(newname.r);
+ }
+ 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/edit.c b/edit.c
@@ -0,0 +1,686 @@
+#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 <libsec.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){
+ 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-1] != '\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);
+ chansetname(editerrc, "editerrc");
+ 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(void*));
+ l->nused = 0;
+ }else if(l->nused == l->nalloc){
+ l->u.listptr = erealloc(l->u.listptr, (l->nalloc+INCR)*sizeof(void*));
+ memset(l->u.ptr+l->nalloc, 0, INCR*sizeof(void*));
+ l->nalloc += INCR;
+ }
+}
+
+/*
+ * Remove the ith element from the list
+ */
+void
+dellist(List *l, int i)
+{
+ memmove(&l->u.ptr[i], &l->u.ptr[i+1], (l->nused-(i+1))*sizeof(void*));
+ l->nused--;
+}
+
+/*
+ * Add a new element, whose position is i, to the list
+ */
+void
+inslist(List *l, int i, void *v)
+{
+ growlist(l);
+ memmove(&l->u.ptr[i+1], &l->u.ptr[i], (l->nused-i)*sizeof(void*));
+ l->u.ptr[i] = v;
+ 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, p);
+ return p;
+}
+
+String*
+newstring(int n)
+{
+ String *p;
+
+ p = allocstring(n);
+ inslist(&stringlist, stringlist.nused, p);
+ return p;
+}
+
+Addr*
+newaddr(void)
+{
+ Addr *p;
+
+ p = emalloc(sizeof(Addr));
+ inslist(&addrlist, addrlist.nused, p);
+ return p;
+}
+
+void
+freecmd(void)
+{
+ int i;
+
+ while(cmdlist.nused > 0)
+ free(cmdlist.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';
+ s->n -= 2;
+ }else{
+ okdelim(delim = getch());
+ getrhs(s, delim, 'a');
+ if(nextc()==delim)
+ getch();
+ atnl();
+ }
+ Return:
+ return s;
+}
+
+int
+cmdlookup(int c)
+{
+ int i;
+
+ for(i=0; cmdtab[i].cmdc; i++)
+ if(cmdtab[i].cmdc == c)
+ return i;
+ return -1;
+}
+
+Cmd*
+parsecmd(int nest)
+{
+ int i, c;
+ struct cmdtab *ct;
+ Cmd *cp, *ncp;
+ Cmd cmd;
+
+ cmd.next = cmd.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.num = 0;
+ 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=='"')
+ break;
+ /* fall through */
+ 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/edit.h b/edit.h
@@ -0,0 +1,99 @@
+/*#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;
+ void* *ptr;
+ uchar* *ucharptr;
+ String* *stringptr;
+ } 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/elog.c b/elog.c
@@ -0,0 +1,354 @@
+#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 <libsec.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 && f->elog.nr+nr<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 tq0, tq1;
+ Buffer *log;
+ Text *t;
+ int owner;
+
+ elogflush(f);
+ log = f->elogbuf;
+ t = f->curtext;
+
+ buf = fbufalloc();
+ mod = FALSE;
+
+ owner = 0;
+ if(t->w){
+ owner = t->w->owner;
+ if(owner == 0)
+ t->w->owner = 'E';
+ }
+
+ /*
+ * The edit commands have already updated the selection in t->q0, t->q1,
+ * but using coordinates relative to the unmodified buffer. As we apply the log,
+ * we have to update the coordinates to be relative to the modified buffer.
+ * Textinsert and textdelete will do this for us; our only work is to apply the
+ * convention that an insertion at t->q0==t->q1 is intended to select the
+ * inserted text.
+ */
+
+ /*
+ * 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 (%d %d)\n",
+ b.q0, b.q0+b.nd, t->q0, t->q1);
+ 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);
+ }
+ if(t->q0 == b.q0 && t->q1 == b.q0)
+ t->q1 += b.nr;
+ break;
+
+ case Delete:
+ if(tracelog)
+ warning(nil, "elog delete %d %d (%d %d)\n",
+ b.q0, b.q0+b.nd, t->q0, t->q1);
+ 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 (%d %d)\n",
+ b.q0, b.q0+b.nr, t->q0, t->q1);
+ 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);
+ }
+ if(t->q0 == b.q0 && t->q1 == b.q0)
+ t->q1 += b.nr;
+ 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);
+ elogterm(f);
+
+ /*
+ * Bad addresses will cause bufload to crash, so double check.
+ * If changes were out of order, we expect problems so don't complain further.
+ */
+ if(t->q0 > f->b.nc || t->q1 > f->b.nc || t->q0 > t->q1){
+ if(!warned)
+ warning(nil, "elogapply: can't happen %d %d %d\n", t->q0, t->q1, f->b.nc);
+ t->q1 = min(t->q1, f->b.nc);
+ t->q0 = min(t->q0, t->q1);
+ }
+
+ if(t->w)
+ t->w->owner = owner;
+}
diff --git a/exec.c b/exec.c
@@ -0,0 +1,1817 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.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 <libsec.h>
+#include <9pclient.h>
+#include "dat.h"
+#include "fns.h"
+
+Buffer snarfbuf;
+
+/*
+ * These functions get called as:
+ *
+ * fn(et, t, argt, flag1, flag1, flag2, s, n);
+ *
+ * Where the arguments are:
+ *
+ * et: the Text* in which the executing event (click) occurred
+ * t: the Text* containing the current selection (Edit, Cut, Snarf, Paste)
+ * argt: the Text* containing the argument for a 2-1 click.
+ * e->flag1: from Exectab entry
+ * e->flag2: from Exectab entry
+ * s: the command line remainder (e.g., "x" if executing "Dump x")
+ * n: length of s (s is *not* NUL-terminated)
+ */
+
+void doabort(Text*, Text*, Text*, int, int, Rune*, int);
+void del(Text*, Text*, Text*, int, int, Rune*, int);
+void delcol(Text*, Text*, Text*, int, int, Rune*, int);
+void dotfiles(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 indent(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 LAbort[] = { 'A', 'b', 'o', 'r', 't', 0 };
+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 LIndent[] = { 'I', 'n', 'd', 'e', 'n', 't', 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[] = {
+ { LAbort, doabort, FALSE, XXX, XXX, },
+ { 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 },
+ { LIndent, indent, 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, 0, 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(r);
+ 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);
+ 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);
+ }
+ 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;
+
+ memset(&e, 0, sizeof e);
+ *rp = nil;
+ *nrp = 0;
+ if(argt == nil)
+ return nil;
+ a = nil;
+ textcommit(argt, TRUE);
+ if(expand(argt, argt->q0, argt->q1, &e, FALSE)){
+ 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
+doabort(Text *__0, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
+{
+ static int n;
+
+ USED(__0);
+ USED(_0);
+ USED(_1);
+ USED(_2);
+ USED(_3);
+ USED(_4);
+ USED(_5);
+
+ if(n++ == 0)
+ warning(nil, "executing Abort again will call abort()\n");
+ else
+ abort();
+}
+
+void
+newcol(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
+{
+ Column *c;
+ Window *w;
+
+ USED(_0);
+ USED(_1);
+ USED(_2);
+ USED(_3);
+ USED(_4);
+ USED(_5);
+
+ c = rowadd(et->row, nil, -1);
+ if(c) {
+ w = coladd(c, nil, nil, -1);
+ winsettag(w);
+ xfidlog(w, "new");
+ }
+}
+
+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]+w->nopen[QWxdata] > 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(dir.nr==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);
+ if(dir.nr>0 && r[dir.nr]!='/' && n>0 && arg[0]!='/')
+ r[dir.nr++] = '/';
+ 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);
+ xfidlog(nw, "zerox");
+ }
+ if(locked)
+ winunlock(t->w);
+}
+
+typedef struct TextAddr TextAddr;
+struct TextAddr {
+ long lorigin; // line+rune for origin
+ long rorigin;
+ long lq0; // line+rune for q0
+ long rq0;
+ long lq1; // line+rune for q1
+ long rq1;
+};
+
+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;
+ TextAddr *addr, *a;
+ Window *w;
+ Text *u;
+ Dir *d;
+ long q0, q1;
+
+ 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;
+ }
+ }
+ addr = emalloc((t->file->ntext)*sizeof(TextAddr));
+ for(i=0; i<t->file->ntext; i++) {
+ a = &addr[i];
+ u = t->file->text[i];
+ a->lorigin = nlcount(u, 0, u->org, &a->rorigin);
+ a->lq0 = nlcount(u, 0, u->q0, &a->rq0);
+ a->lq1 = nlcount(u, u->q0, u->q1, &a->rq1);
+ }
+ 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);
+ if(samename) {
+ a = &addr[i];
+ // warning(nil, "%d %d %d %d %d %d\n", a->lorigin, a->rorigin, a->lq0, a->rq0, a->lq1, a->rq1);
+ q0 = nlcounttopos(u, 0, a->lq0, a->rq0);
+ q1 = nlcounttopos(u, q0, a->lq1, a->rq1);
+ textsetselect(u, q0, q1);
+ q0 = nlcounttopos(u, 0, a->lorigin, a->rorigin);
+ textsetorigin(u, q0, FALSE);
+ }
+ textscrdraw(u);
+ }
+ free(addr);
+ xfidlog(w, "get");
+}
+
+static void
+checksha1(char *name, File *f, Dir *d)
+{
+ int fd, n;
+ DigestState *h;
+ uchar out[20];
+ uchar *buf;
+
+ fd = open(name, OREAD);
+ if(fd < 0)
+ return;
+ h = sha1(nil, 0, nil, nil);
+ buf = emalloc(8192);
+ while((n = read(fd, buf, 8192)) > 0)
+ sha1(buf, n, nil, h);
+ free(buf);
+ close(fd);
+ sha1(nil, 0, out, h);
+ if(memcmp(out, f->sha1, sizeof out) == 0) {
+ f->dev = d->dev;
+ f->qidpath = d->qid.path;
+ f->mtime = d->mtime;
+ }
+}
+
+void
+putfile(File *f, int q0, int q1, Rune *namer, int nname)
+{
+ uint n, m;
+ Rune *r;
+ Biobuf *b;
+ char *s, *name;
+ int i, fd, q, ret, retc;
+ Dir *d, *d1;
+ Window *w;
+ int isapp;
+ DigestState *h;
+
+ w = f->curtext->w;
+ name = runetobyte(namer, nname);
+ d = dirstat(name);
+ if(d!=nil && runeeq(namer, nname, f->name, f->nname)){
+ if(f->dev!=d->dev || f->qidpath!=d->qid.path || f->mtime != d->mtime)
+ checksha1(name, f, d);
+ if(f->dev!=d->dev || f->qidpath!=d->qid.path || f->mtime != d->mtime) {
+ if(f->unread)
+ warning(nil, "%s not written; file already exists\n", name);
+ else
+ warning(nil, "%s modified%s%s since last read\n\twas %t; now %t\n", name, d->muid[0]?" by ":"", d->muid, f->mtime, d->mtime);
+ f->dev = d->dev;
+ f->qidpath = d->qid.path;
+ f->mtime = d->mtime;
+ goto Rescue1;
+ }
+ }
+
+ fd = create(name, OWRITE, 0666);
+ if(fd < 0){
+ warning(nil, "can't create file %s: %r\n", name);
+ goto Rescue1;
+ }
+ // Use bio in order to force the writes to be large and
+ // block-aligned (bio's default is 8K). This is not strictly
+ // necessary; it works around some buggy underlying
+ // file systems that mishandle unaligned writes.
+ // https://codereview.appspot.com/89550043/
+ b = emalloc(sizeof *b);
+ Binit(b, fd, OWRITE);
+ r = fbufalloc();
+ s = fbufalloc();
+ free(d);
+ d = dirfstat(fd);
+ h = sha1(nil, 0, nil, nil);
+ isapp = (d!=nil && d->length>0 && (d->qid.type&QTAPPEND));
+ if(isapp){
+ warning(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);
+ sha1((uchar*)s, m, nil, h);
+ if(Bwrite(b, s, m) != m){
+ warning(nil, "can't write file %s: %r\n", name);
+ goto Rescue2;
+ }
+ }
+ if(Bflush(b) < 0) {
+ warning(nil, "can't write file %s: %r\n", name);
+ goto Rescue2;
+ }
+ ret = Bterm(b);
+ retc = close(fd);
+ free(b);
+ b = nil;
+ if(ret < 0 || retc < 0) {
+ warning(nil, "can't write file %s: %r\n", name);
+ goto Rescue2; // flush or close failed
+ }
+ 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{
+ // In case the file is on NFS, reopen the fd
+ // before dirfstat to cause the attribute cache
+ // to be updated (otherwise the mtime in the
+ // dirfstat below will be stale and not match
+ // what NFS sees). The file is already written,
+ // so this should be a no-op when not on NFS.
+ // Opening for OWRITE (but no truncation)
+ // in case we don't have read permission.
+ // (The create above worked, so we probably
+ // still have write permission.)
+ fd = open(name, OWRITE);
+ d1 = dirfstat(fd);
+ close(fd);
+ if(d1 != nil){
+ free(d);
+ d = d1;
+ }
+ f->qidpath = d->qid.path;
+ f->dev = d->dev;
+ f->mtime = d->mtime;
+ sha1(nil, 0, f->sha1, h);
+ h = nil;
+ 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(h);
+ free(d);
+ free(namer);
+ free(name);
+ close(fd);
+ winsettag(w);
+ return;
+
+ Rescue2:
+ if(b != nil) {
+ Bterm(b);
+ free(b);
+ close(fd);
+ }
+ free(h);
+ fbuffree(s);
+ fbuffree(r);
+ /* fall through */
+
+ Rescue1:
+ free(d);
+ free(namer);
+ free(name);
+}
+
+static void
+trimspaces(Text *et)
+{
+ File *f;
+ Rune *r;
+ Text *t;
+ uint q0, n, delstart;
+ int c, i, marked;
+
+ t = &et->w->body;
+ f = t->file;
+ marked = 0;
+
+ if(t->w!=nil && et->w!=t->w){
+ /* can this happen when t == &et->w->body? */
+ c = 'M';
+ if(et->w)
+ c = et->w->owner;
+ winlock(t->w, c);
+ }
+
+ r = fbufalloc();
+ q0 = f->b.nc;
+ delstart = q0; /* end of current space run, or 0 if no active run; = q0 to delete spaces before EOF */
+ while(q0 > 0) {
+ n = RBUFSIZE;
+ if(n > q0)
+ n = q0;
+ q0 -= n;
+ bufread(&f->b, q0, r, n);
+ for(i=n; ; i--) {
+ if(i == 0 || (r[i-1] != ' ' && r[i-1] != '\t')) {
+ // Found non-space or start of buffer. Delete active space run.
+ if(q0+i < delstart) {
+ if(!marked) {
+ marked = 1;
+ seq++;
+ filemark(f);
+ }
+ textdelete(t, q0+i, delstart, TRUE);
+ }
+ if(i == 0) {
+ /* keep run active into tail of next buffer */
+ if(delstart > 0)
+ delstart = q0;
+ break;
+ }
+ delstart = 0;
+ if(r[i-1] == '\n')
+ delstart = q0+i-1; /* delete spaces before this newline */
+ }
+ }
+ }
+ fbuffree(r);
+
+ if(t->w!=nil && et->w!=t->w)
+ winunlock(t->w);
+}
+
+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){
+ warning(nil, "no file name\n");
+ return;
+ }
+ if(w->autoindent)
+ trimspaces(et);
+ namer = bytetorune(name, &nname);
+ putfile(f, 0, f->b.nc, namer, nname);
+ xfidlog(w, "put");
+ 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);
+
+ /*
+ * if not executing a mouse chord (et != t) and snarfing (dosnarf)
+ * and executed Cut or Snarf in window tag (et->w != nil),
+ * then use the window body selection or the tag selection
+ * or do nothing at all.
+ */
+ 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;
+ else
+ t = nil;
+ }
+ if(t == nil) /* no selection */
+ 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, FALSE);
+ 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, FALSE);
+ 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);
+ }
+ t->iq1 = t->q1;
+ textshow(t, t->q1, t->q1, 1);
+}
+
+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(_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");
+ }
+}
+
+static Rune LON[] = { 'O', 'N', 0 };
+static Rune LOFF[] = { 'O', 'F', 'F', 0 };
+static Rune Lon[] = { 'o', 'n', 0 };
+
+enum {
+ IGlobal = -2,
+ IError = -1,
+ Ion = 0,
+ Ioff = 1
+};
+
+static int
+indentval(Rune *s, int n)
+{
+ if(n < 2)
+ return IError;
+ if(runestrncmp(s, LON, n) == 0){
+ globalautoindent = TRUE;
+ warning(nil, "Indent ON\n");
+ return IGlobal;
+ }
+ if(runestrncmp(s, LOFF, n) == 0){
+ globalautoindent = FALSE;
+ warning(nil, "Indent OFF\n");
+ return IGlobal;
+ }
+ return runestrncmp(s, Lon, n) == 0;
+}
+
+static void
+fixindent(Window *w, void *arg)
+{
+ USED(arg);
+ w->autoindent = globalautoindent;
+}
+
+void
+indent(Text *et, Text *_0, Text *argt, int _1, int _2, Rune *arg, int narg)
+{
+ Rune *a, *r;
+ Window *w;
+ int na, len, autoindent;
+
+ USED(_0);
+ USED(_1);
+ USED(_2);
+
+ w = nil;
+ if(et!=nil && et->w!=nil)
+ w = et->w;
+ autoindent = IError;
+ getarg(argt, FALSE, TRUE, &r, &len);
+ if(r!=nil && len>0)
+ autoindent = indentval(r, len);
+ else{
+ a = findbl(arg, narg, &na);
+ if(a != arg)
+ autoindent = indentval(arg, narg-na);
+ }
+ if(autoindent == IGlobal)
+ allwindows(fixindent, nil);
+ else if(w != nil && autoindent >= 0)
+ w->autoindent = autoindent;
+}
+
+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, FALSE, TRUE);
+ }
+ }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];
+ int ret;
+ /*static void *parg[2]; */
+ char *rcarg[4];
+ void **argv;
+ CFsys *fs;
+ char *shell;
+
+ threadsetname("runproc");
+
+ argv = argvp;
+ win = argv[0];
+ s = argv[1];
+ rdir = argv[2];
+ ndir = (uintptr)argv[3];
+ newns = (uintptr)argv[4];
+ argaddr = argv[5];
+ arg = argv[6];
+ c = argv[7];
+ cpid = argv[8];
+ iseditcmd = (uintptr)argv[9];
+ free(argv);
+
+ unsetenv("acmeaddr");
+ unsetenv("winid");
+ unsetenv("%");
+ unsetenv("samfile");
+
+ 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)
+ memmove(name, e+1, strlen(e+1)+1); /* strcpy but overlaps */
+ 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(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);
+ putenv("samfile", 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(acmeshell != nil)
+ goto Hard;
+ 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;
+
+ dir = nil;
+ if(rdir != nil)
+ dir = runetobyte(rdir, ndir);
+ ret = threadspawnd(sfd, av[0], av, dir);
+ free(dir);
+ if(ret >= 0){
+ if(cpid)
+ sendul(cpid, ret);
+ threadexits("");
+ }
+/* 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;
+ }
+ }
+ dir = nil;
+ if(rdir != nil)
+ dir = runetobyte(rdir, ndir);
+ shell = acmeshell;
+ if(shell == nil)
+ shell = "rc";
+ rcarg[0] = shell;
+ rcarg[1] = "-c";
+ rcarg[2] = t;
+ rcarg[3] = nil;
+ ret = threadspawnd(sfd, rcarg[0], rcarg, dir);
+ unsetenv("acmeaddr");
+ unsetenv("winid");
+ unsetenv("%");
+ unsetenv("samfile");
+ free(dir);
+ if(ret >= 0){
+ if(cpid)
+ sendul(cpid, ret);
+ threadexits(nil);
+ }
+ warning(nil, "exec %s: %r\n", shell);
+
+ Fail:
+ /* threadexec 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);
+ chansetname(cpid, "cpid %s", s);
+ arg[0] = win;
+ arg[1] = s;
+ arg[2] = rdir;
+ arg[3] = (void*)(uintptr)ndir;
+ arg[4] = (void*)(uintptr)newns;
+ arg[5] = argaddr;
+ arg[6] = xarg;
+ arg[7] = c;
+ arg[8] = cpid;
+ arg[9] = (void*)(uintptr)iseditcmd;
+ threadcreate(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/file.c b/file.c
@@ -0,0 +1,311 @@
+#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 <libsec.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, DigestState *h)
+{
+ if(f->seq > 0)
+ error("undo in file.load unimplemented");
+ return bufload(&f->b, p0, fd, nulls, h);
+}
+
+/* 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/fns.h b/fns.h
@@ -0,0 +1,109 @@
+/*
+#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*);
+int restoremouse(Window*);
+void clearmouse(void);
+void allwindows(void(*)(Window*, void*), void*);
+uint seqof(Window*, int);
+
+uint loadfile(int, uint, int*, int(*)(void*, uint, Rune*, int), void*, DigestState*);
+void movetodel(Window*);
+
+Window* errorwin(Mntdir*, int);
+Window* errorwinforwin(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);
+#undef isalnum
+#define isalnum acmeisalnum
+int isalnum(Rune);
+void execute(Text*, uint, uint, int, Text*);
+int search(Text*, Rune*, uint, int);
+void look3(Text*, uint, uint, int, 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*);
+void fsysincid(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(uint, Text*, Range, Range, void*, uint, uint, int (*)(void*, uint), int*, uint*, int);
+int rxexecute(Text*, Rune*, uint, uint, Rangeset*);
+int rxbexecute(Text*, uint, Rangeset*);
+Window* makenewwindow(Text *t);
+int expand(Text*, uint, uint, Expand*, int);
+Rune* skipbl(Rune*, int, int*);
+Rune* findbl(Rune*, int, int*);
+char* edittext(Window*, int, Rune*, int);
+void flushwarnings(void);
+void startplumbing(void);
+long nlcount(Text*, long, long, long*);
+long nlcounttopos(Text*, long, long, long);
+Rune* parsetag(Window*, int, int*);
+
+Runestr runestr(Rune*, uint);
+Range range(int, 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))
+
+int ismtpt(char*);
diff --git a/fsys.c b/fsys.c
@@ -0,0 +1,749 @@
+#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 <libsec.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*);
+
+static void
+initfcall(void)
+{
+ fcall[Tflush] = fsysflush;
+ fcall[Tversion] = fsysversion;
+ fcall[Tauth] = fsysauth;
+ fcall[Tattach] = fsysattach;
+ fcall[Twalk] = fsyswalk;
+ fcall[Topen] = fsysopen;
+ fcall[Tcreate] = fsyscreate;
+ fcall[Tread] = fsysread;
+ fcall[Twrite] = fsyswrite;
+ fcall[Tclunk] = fsysclunk;
+ fcall[Tremove]= fsysremove;
+ fcall[Tstat] = fsysstat;
+ fcall[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 },
+ { "log", QTFILE, Qlog, 0400 },
+ { "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 },
+ { "errors", QTFILE, QWerrors, 0200 },
+ { "event", QTFILE, QWevent, 0600 },
+ { "rdsel", QTFILE, QWrdsel, 0400 },
+ { "wrsel", QTFILE, QWwrsel, 0200 },
+ { "tag", QTAPPEND, QWtag, 0600|DMAPPEND },
+ { "xdata", QTFILE, QWxdata, 0600 },
+ { 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];
+ char *u;
+
+ initfcall();
+ if(pipe(p) < 0)
+ error("can't create pipe");
+ if(post9pservice(p[0], "acme", mtpt) < 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;
+
+ threadsetname("fsysproc");
+
+ 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{
+ switch(x->fcall.type){
+ case Tversion:
+ case Tauth:
+ case Tflush:
+ f = nil;
+ break;
+ case Tattach:
+ f = newfid(x->fcall.fid);
+ break;
+ default:
+ f = newfid(x->fcall.fid);
+ if(!f->busy){
+ x->f = f;
+ x = respond(x, &t, "fid not in use");
+ continue;
+ }
+ break;
+ }
+ 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
+fsysincid(Mntdir *m)
+{
+ qlock(&mnt.lk);
+ m->ref++;
+ qunlock(&mnt.lk);
+}
+
+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;
+ /*
+ * apparently this is not kosher on openbsd.
+ * perhaps because fsysproc is reading from sfd right now,
+ * the close hangs indefinitely.
+ 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)
+{
+ Fcall t;
+
+ USED(f);
+ return respond(x, &t, "acme: authentication not required");
+}
+
+static
+Xfid*
+fsysflush(Xfid *x, Fid *f)
+{
+ USED(f);
+ sendp(x->c, (void*)xfidflush);
+ return nil;
+}
+
+static
+Xfid*
+fsysattach(Xfid *x, Fid *f)
+{
+ Fcall t;
+ int id;
+ Mntdir *m;
+ char buf[128];
+
+ 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 && x->fcall.aname[0]){
+ snprint(buf, sizeof buf, "unknown id '%s' in attach", x->fcall.aname);
+ sendp(cerr, estrdup(buf));
+ }
+ 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(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, (void*)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, (void*)xfidread);
+ return nil;
+}
+
+static
+Xfid*
+fsyswrite(Xfid *x, Fid *f)
+{
+ USED(f);
+ sendp(x->c, (void*)xfidwrite);
+ return nil;
+}
+
+static
+Xfid*
+fsysclunk(Xfid *x, Fid *f)
+{
+ fsysdelid(f->mntdir);
+ sendp(x->c, (void*)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)
+{
+ 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/logf.c b/logf.c
@@ -0,0 +1,199 @@
+#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 <libsec.h>
+#include "dat.h"
+#include "fns.h"
+
+// State for global log file.
+typedef struct Log Log;
+struct Log
+{
+ QLock lk;
+ Rendez r;
+
+ vlong start; // msg[0] corresponds to 'start' in the global sequence of events
+
+ // queued events (nev=entries in ev, mev=capacity of p)
+ char **ev;
+ int nev;
+ int mev;
+
+ // open acme/put files that need to read events
+ Fid **f;
+ int nf;
+ int mf;
+
+ // active (blocked) reads waiting for events
+ Xfid **read;
+ int nread;
+ int mread;
+};
+
+static Log eventlog;
+
+void
+xfidlogopen(Xfid *x)
+{
+ qlock(&eventlog.lk);
+ if(eventlog.nf >= eventlog.mf) {
+ eventlog.mf = eventlog.mf*2;
+ if(eventlog.mf == 0)
+ eventlog.mf = 8;
+ eventlog.f = erealloc(eventlog.f, eventlog.mf*sizeof eventlog.f[0]);
+ }
+ eventlog.f[eventlog.nf++] = x->f;
+ x->f->logoff = eventlog.start + eventlog.nev;
+
+ qunlock(&eventlog.lk);
+}
+
+void
+xfidlogclose(Xfid *x)
+{
+ int i;
+
+ qlock(&eventlog.lk);
+ for(i=0; i<eventlog.nf; i++) {
+ if(eventlog.f[i] == x->f) {
+ eventlog.f[i] = eventlog.f[--eventlog.nf];
+ break;
+ }
+ }
+ qunlock(&eventlog.lk);
+}
+
+void
+xfidlogread(Xfid *x)
+{
+ char *p;
+ int i;
+ Fcall fc;
+
+ qlock(&eventlog.lk);
+ if(eventlog.nread >= eventlog.mread) {
+ eventlog.mread = eventlog.mread*2;
+ if(eventlog.mread == 0)
+ eventlog.mread = 8;
+ eventlog.read = erealloc(eventlog.read, eventlog.mread*sizeof eventlog.read[0]);
+ }
+ eventlog.read[eventlog.nread++] = x;
+
+ if(eventlog.r.l == nil)
+ eventlog.r.l = &eventlog.lk;
+ x->flushed = FALSE;
+ while(x->f->logoff >= eventlog.start+eventlog.nev && !x->flushed)
+ rsleep(&eventlog.r);
+
+ for(i=0; i<eventlog.nread; i++) {
+ if(eventlog.read[i] == x) {
+ eventlog.read[i] = eventlog.read[--eventlog.nread];
+ break;
+ }
+ }
+
+ if(x->flushed) {
+ qunlock(&eventlog.lk);
+ return;
+ }
+
+ i = x->f->logoff - eventlog.start;
+ p = estrdup(eventlog.ev[i]);
+ x->f->logoff++;
+ qunlock(&eventlog.lk);
+
+ fc.data = p;
+ fc.count = strlen(p);
+ respond(x, &fc, nil);
+ free(p);
+}
+
+void
+xfidlogflush(Xfid *x)
+{
+ int i;
+ Xfid *rx;
+
+ qlock(&eventlog.lk);
+ for(i=0; i<eventlog.nread; i++) {
+ rx = eventlog.read[i];
+ if(rx->fcall.tag == x->fcall.oldtag) {
+ rx->flushed = TRUE;
+ rwakeupall(&eventlog.r);
+ }
+ }
+ qunlock(&eventlog.lk);
+}
+
+/*
+ * add a log entry for op on w.
+ * expected calls:
+ *
+ * op == "new" for each new window
+ * - caller of coladd or makenewwindow responsible for calling
+ * xfidlog after setting window name
+ * - exception: zerox
+ *
+ * op == "zerox" for new window created via zerox
+ * - called from zeroxx
+ *
+ * op == "get" for Get executed on window
+ * - called from get
+ *
+ * op == "put" for Put executed on window
+ * - called from put
+ *
+ * op == "del" for deleted window
+ * - called from winclose
+ */
+void
+xfidlog(Window *w, char *op)
+{
+ int i, n;
+ vlong min;
+ File *f;
+ char *name;
+
+ qlock(&eventlog.lk);
+ if(eventlog.nev >= eventlog.mev) {
+ // Remove and free any entries that all readers have read.
+ min = eventlog.start + eventlog.nev;
+ for(i=0; i<eventlog.nf; i++) {
+ if(min > eventlog.f[i]->logoff)
+ min = eventlog.f[i]->logoff;
+ }
+ if(min > eventlog.start) {
+ n = min - eventlog.start;
+ for(i=0; i<n; i++)
+ free(eventlog.ev[i]);
+ eventlog.nev -= n;
+ eventlog.start += n;
+ memmove(eventlog.ev, eventlog.ev+n, eventlog.nev*sizeof eventlog.ev[0]);
+ }
+
+ // Otherwise grow.
+ if(eventlog.nev >= eventlog.mev) {
+ eventlog.mev = eventlog.mev*2;
+ if(eventlog.mev == 0)
+ eventlog.mev = 8;
+ eventlog.ev = erealloc(eventlog.ev, eventlog.mev*sizeof eventlog.ev[0]);
+ }
+ }
+ f = w->body.file;
+ name = runetobyte(f->name, f->nname);
+ if(name == nil)
+ name = estrdup("");
+ eventlog.ev[eventlog.nev++] = smprint("%d %s %s\n", w->id, op, name);
+ free(name);
+ if(eventlog.r.l == nil)
+ eventlog.r.l = &eventlog.lk;
+ rwakeupall(&eventlog.r);
+ qunlock(&eventlog.lk);
+}
diff --git a/look.c b/look.c
@@ -0,0 +1,942 @@
+#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 <9pclient.h>
+#include <plumb.h>
+#include <libsec.h>
+#include "dat.h"
+#include "fns.h"
+
+CFid *plumbsendfid;
+CFid *plumbeditfid;
+
+Window* openfile(Text*, Expand*);
+
+int nuntitled;
+
+void
+plumbthread(void *v)
+{
+ CFid *fid;
+ Plumbmsg *m;
+ Timer *t;
+
+ USED(v);
+ threadsetname("plumbproc");
+
+ /*
+ * Loop so that if plumber is restarted, acme need not be.
+ */
+ for(;;){
+ /*
+ * Connect to plumber.
+ */
+ plumbunmount();
+ while((fid = plumbopenfid("edit", OREAD|OCEXEC)) == nil){
+ t = timerstart(2000);
+ recv(t->c, nil);
+ timerstop(t);
+ }
+ plumbeditfid = fid;
+ plumbsendfid = plumbopenfid("send", OWRITE|OCEXEC);
+
+ /*
+ * Relay messages.
+ */
+ for(;;){
+ m = plumbrecvfid(plumbeditfid);
+ if(m == nil)
+ break;
+ sendp(cplumb, m);
+ }
+
+ /*
+ * Lost connection.
+ */
+ fid = plumbsendfid;
+ plumbsendfid = nil;
+ fsclose(fid);
+
+ fid = plumbeditfid;
+ plumbeditfid = nil;
+ fsclose(fid);
+ }
+}
+
+void
+startplumbing(void)
+{
+ cplumb = chancreate(sizeof(Plumbmsg*), 0);
+ chansetname(cplumb, "cplumb");
+ threadcreate(plumbthread, nil, STACK);
+}
+
+
+void
+look3(Text *t, uint q0, uint q1, int external, int reverse)
+{
+ 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, reverse);
+ 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';
+ if(reverse)
+ c += 'R' - '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(plumbsendfid != nil){
+ /* 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 && plumbsendtofid(plumbsendfid, 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) {
+ uint q;
+ q = e.q1;
+ if(reverse)
+ q = e.q0;
+ textsetselect(ct, q, q);
+ }
+ n = e.q1 - e.q0;
+ r = runemalloc(n);
+ bufread(&t->file->b, e.q0, r, n);
+ if(search(ct, r, n, reverse) && 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>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;
+ }
+ memset(&e, 0, sizeof e);
+ 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;
+ }
+ drawtopwindow();
+ 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];
+
+ drawtopwindow();
+ 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);
+ xfidlog(w, "new");
+}
+
+int
+search(Text *ct, Rune *r, uint n, int reverse)
+{
+ uint nb, maxn;
+ int around;
+ Rune *s, *b;
+
+ 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;
+ if(reverse){
+ uint q1;
+ q1 = ct->q0; // q1 is (past) end of text being searched.
+ for(;;){
+ if(q1 <= 0){
+ q1 = ct->file->b.nc;
+ around = 1;
+ nb = 0;
+ b[nb] = 0;
+ }
+ if(nb > 0){
+ Rune *c;
+ for(c=b+nb; c>b; c--)
+ if(c[-1] == r[n-1])
+ break;
+ if(c == b) {
+ q1 -= nb;
+ nb = 0;
+ b[nb] = 0;
+ if(around && q1 <= 0)
+ break;
+ continue;
+ }
+ q1 -= nb - (c - b);
+ nb = c - b;
+ }
+ /* reload if buffer covers neither string nor beginning of file */
+ if(nb<n && nb!=q1){
+ nb = q1;
+ if(nb >= maxn)
+ nb = maxn-1;
+ bufread(&ct->file->b, q1-nb, s, nb);
+ b = s;
+ b[nb] = '\0';
+ }
+ if(runeeq(b+nb-n, n, r, n)==TRUE){
+ if(ct->w){
+ textshow(ct, q1-n, q1, 1);
+ winsettag(ct->w);
+ }else{
+ ct->q0 = q1-n;
+ ct->q1 = q1;
+ }
+ seltext = ct;
+ fbuffree(s);
+ return TRUE;
+ }
+ q1--;
+ nb--;
+ if(around && q1 <= 0)
+ break;
+ }
+ }else{
+ uint q;
+ q = ct->q1;
+ for(;;){
+ if(q >= ct->file->b.nc){
+ q = 0;
+ around = 1;
+ nb = 0;
+ b[nb] = 0;
+ }
+ if(nb > 0){
+ Rune *c;
+ 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;
+ }
+ --nb;
+ b++;
+ q++;
+ if(around && q>=ct->q1)
+ break;
+ }
+ }
+ 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 wrapper for cleanname */
+Runestr
+cleanrname(Runestr rs)
+{
+ char *s;
+ int nb, nulls;
+
+ s = runetobyte(rs.r, rs.nr);
+ cleanname(s);
+ cvttorunes(s, strlen(s), rs.r, &nb, &rs.nr, &nulls);
+ free(s);
+ return rs;
+}
+
+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 };
+ Rune Lusrinclude[] = { '/', 'u', 's', 'r', '/', 'i', 'n', 'c', 'l', 'u', 'd', 'e', 0 };
+ Rune Lusrlocalinclude[] = { '/', 'u', 's', 'r', '/', 'l', 'o', 'c', 'a', 'l',
+ '/', 'i', 'n', 'c', 'l', 'u', 'd', 'e', 0 };
+ Rune Lusrlocalplan9include[] = { '/', 'u', 's', 'r', '/', 'l', 'o', 'c', 'a', 'l',
+ '/', 'p', 'l', 'a', 'n', '9', '/', '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)
+ file = includefile(Lusrlocalplan9include, r, n);
+ if(file.r == nil)
+ file = includefile(Lusrlocalinclude, r, n);
+ if(file.r == nil)
+ file = includefile(Lusrinclude, 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;
+ uint nt;
+ int slash, i;
+ 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 = parsetag(t->w, n, &i);
+ slash = -1;
+ for(i--; i >= 0; i--){
+ if(b[i] == '/'){
+ slash = i;
+ 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;
+}
+
+static int
+texthas(Text *t, uint q0, Rune *r)
+{
+ int i;
+
+ if((int)q0 < 0)
+ return FALSE;
+ for(i=0; r[i]; i++)
+ if(q0+i >= t->file->b.nc || textreadc(t, q0+i) != r[i])
+ return FALSE;
+ return TRUE;
+}
+
+int
+expandfile(Text *t, uint q0, uint q1, Expand *e, int reverse)
+{
+ int i, n, nname, colon, eval;
+ uint amin, amax;
+ Rune *r, c;
+ Window *w;
+ Runestr rs;
+ Rune Lhttpcss[] = {'h', 't', 't', 'p', ':', '/', '/', 0};
+ Rune Lhttpscss[] = {'h', 't', 't', 'p', 's', ':', '/', '/', 0};
+
+ amax = q1;
+ if(q1 == q0){
+ colon = -1;
+ while(q1<t->file->b.nc && isfilec(c=textreadc(t, q1))){
+ if(c == ':' && !texthas(t, q1-4, Lhttpcss) && !texthas(t, q1-5, Lhttpscss)){
+ colon = q1;
+ break;
+ }
+ q1++;
+ }
+ while(q0>0 && (isfilec(c=textreadc(t, q0-1)) || isaddrc(c) || isregexc(c))){
+ q0--;
+ if(colon<0 && c==':' && !texthas(t, q0-4, Lhttpcss) && !texthas(t, q0-5, Lhttpscss))
+ 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 && 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;
+ if(colon != q0)
+ reverse = FALSE;
+ }else if(reverse){
+ if(textreadc(t, q0) != ':')
+ reverse = FALSE;
+ }
+ 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+1);
+ bufread(&t->file->b, q0, r, n);
+ r[n] = 0;
+ /* is it a URL? look for http:// and https:// prefix */
+ if(runestrncmp(r, Lhttpcss, 7) == 0 || runestrncmp(r, Lhttpscss, 8) == 0){
+ // Avoid capturing end-of-sentence punctuation.
+ if(r[n-1] == '.') {
+ e->q1--;
+ n--;
+ }
+ e->name = r;
+ e->nname = n;
+ e->u.at = t;
+ e->a0 = e->q1;
+ e->a1 = e->q1;
+ return TRUE;
+ }
+ /* 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]) && 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(ismtpt(e->bname) || 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;
+ e->reverse = reverse;
+ eval = FALSE;
+ // Note: address is repeated in openfile when
+ // expandfile returns to expand returns to look3.
+ address(TRUE, nil, range(-1,-1), range(0,0), t, e->a0, amax, tgetc, &eval, (uint*)&e->a1, e->reverse);
+ return TRUE;
+
+ Isntfile:
+ free(r);
+ return FALSE;
+}
+
+int
+expand(Text *t, uint q0, uint q1, Expand *e, int reverse)
+{
+ 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, reverse))
+ 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;
+ Runestr rs;
+ uint dummy;
+
+ r.q0 = 0;
+ r.q1 = 0;
+ if(e->nname == 0){
+ w = t->w;
+ if(w == nil)
+ return nil;
+ }else{
+ w = lookfile(e->name, e->nname);
+ if(w == nil && e->name[0] != '/'){
+ /*
+ * Unrooted path in new window.
+ * This can happen if we type a pwd-relative path
+ * in the topmost tag or the column tags.
+ * Most of the time plumber takes care of these,
+ * but plumber might not be running or might not
+ * be configured to accept plumbed directories.
+ * Make the name a full path, just like we would if
+ * opening via the plumber.
+ */
+ n = utflen(wdir)+1+e->nname+1;
+ rp = runemalloc(n);
+ runesnprint(rp, n, "%s/%.*S", wdir, e->nname, e->name);
+ rs = cleanrname(runestr(rp, n-1));
+ free(e->name);
+ e->name = rs.r;
+ e->nname = rs.nr;
+ 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);
+ if(textload(t, 0, e->bname, 1) >= 0)
+ t->file->unread = FALSE;
+ 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);
+ }
+ w->autoindent = ow->autoindent;
+ }else
+ w->autoindent = globalautoindent;
+ xfidlog(w, "new");
+ }
+ if(e->a1 == e->a0)
+ eval = FALSE;
+ else{
+ eval = TRUE;
+ r = address(TRUE, t, range(-1,-1), range(t->q0, t->q1), e->u.at, e->a0, e->a1, e->agetc, &eval, &dummy, e->reverse);
+ if(r.q0 > r.q1) {
+ eval = FALSE;
+ warning(nil, "addresses out of order\n");
+ }
+ 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;
+ Window *w;
+
+ 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) {
+ w = coladd(et->col, nil, nil, -1);
+ winsettag(w);
+ xfidlog(w, "new");
+ }
+ break;
+ }
+ nf = narg-na;
+ f = runemalloc(nf);
+ runemove(f, arg, nf);
+ rs = dirname(et, f, nf);
+ memset(&e, 0, sizeof e);
+ e.name = rs.r;
+ e.nname = rs.nr;
+ e.bname = runetobyte(rs.r, rs.nr);
+ e.jump = TRUE;
+ openfile(et, &e);
+ free(e.name);
+ free(e.bname);
+ arg = skipbl(a, na, &narg);
+ }
+}
diff --git a/mail/dat.h b/mail/dat.h
@@ -0,0 +1,180 @@
+typedef struct Event Event;
+typedef struct Exec Exec;
+typedef struct Message Message;
+typedef struct Window Window;
+
+enum
+{
+ STACK = 8192,
+ EVENTSIZE = 256,
+ NEVENT = 5
+};
+
+struct Event
+{
+ int c1;
+ int c2;
+ int q0;
+ int q1;
+ int flag;
+ int nb;
+ int nr;
+ char b[EVENTSIZE*UTFmax+1];
+ Rune r[EVENTSIZE+1];
+};
+
+struct Window
+{
+ /* coordinate wineventproc and window thread */
+ QLock lk;
+ int ref;
+
+ /* file descriptors */
+ CFid* ctl;
+ CFid* event;
+ CFid* addr;
+ CFid* data;
+ CFid* body;
+
+ /* event input */
+ char buf[512];
+ char *bufp;
+ int nbuf;
+ Event e[NEVENT];
+
+ int id;
+ int open;
+ Channel *cevent;
+};
+
+struct Message
+{
+ Window *w;
+ CFid* ctlfd;
+ char *name;
+ char *replyname;
+ uchar opened;
+ uchar dirty;
+ uchar isreply;
+ uchar deleted;
+ uchar writebackdel;
+ uchar tagposted;
+ uchar recursed;
+ uchar level;
+ uint replywinid;
+
+ /* header info */
+ char *from;
+ char *fromcolon;
+ char *to;
+ char *cc;
+ char *replyto;
+ char *sender;
+ char *date;
+ char *subject;
+ char *type;
+ char *disposition;
+ char *filename;
+ char *digest;
+
+ Message *next; /* next in this mailbox */
+ Message *prev; /* prev in this mailbox */
+ Message *head; /* first subpart */
+ Message *tail; /* last subpart */
+};
+
+enum
+{
+ NARGS = 100,
+ NARGCHAR = 8*1024,
+ EXECSTACK = STACK+(NARGS+1)*sizeof(char*)+NARGCHAR
+};
+
+struct Exec
+{
+ char *prog;
+ char **argv;
+ int p[2]; /* p[1] is write to program; p[0] set to prog fd 0*/
+ int q[2]; /* q[0] is read from program; q[1] set to prog fd 1 */
+ Channel *sync;
+};
+
+extern Window* newwindow(void);
+extern CFid* winopenfile(Window*, char*);
+extern void winopenbody(Window*, int);
+extern void winclosebody(Window*);
+extern void wintagwrite(Window*, char*, int);
+extern void winname(Window*, char*);
+extern void winwriteevent(Window*, Event*);
+extern void winread(Window*, uint, uint, char*);
+extern int windel(Window*, int);
+extern void wingetevent(Window*, Event*);
+extern void wineventproc(void*);
+extern void winwritebody(Window*, char*, int);
+extern void winclean(Window*);
+extern int winselect(Window*, char*, int);
+extern char* winselection(Window*);
+extern int winsetaddr(Window*, char*, int);
+extern char* winreadbody(Window*, int*);
+extern void windormant(Window*);
+extern void winsetdump(Window*, char*, char*);
+extern void winincref(Window*);
+extern void windecref(Window*);
+
+extern void readmbox(Message*, char*, char*);
+extern void rewritembox(Window*, Message*);
+
+extern void mkreply(Message*, char*, char*, Plumbattr*, char*);
+extern void delreply(Message*);
+
+extern int mesgadd(Message*, char*, Dir*, char*);
+extern void mesgmenu(Window*, Message*);
+extern void mesgmenunew(Window*, Message*);
+extern int mesgopen(Message*, char*, char*, Message*, int, char*);
+extern void mesgctl(void*);
+extern void mesgsend(Message*);
+extern void mesgdel(Message*, Message*);
+extern void mesgmenudel(Window*, Message*, Message*);
+extern void mesgmenumark(Window*, char*, char*);
+extern void mesgmenumarkdel(Window*, Message*, Message*, int);
+extern Message* mesglookup(Message*, char*, char*);
+extern Message* mesglookupfile(Message*, char*, char*);
+extern void mesgfreeparts(Message*);
+extern int mesgcommand(Message*, char*);
+
+extern char* info(Message*, int, int);
+extern char* readfile(char*, char*, int*);
+extern char* readbody(char*, char*, int*);
+extern void ctlprint(CFid*, char*, ...);
+extern void* emalloc(uint);
+extern void* erealloc(void*, uint);
+extern char* estrdup(char*);
+extern char* estrstrdup(char*, char*);
+extern char* egrow(char*, char*, char*);
+extern char* eappend(char*, char*, char*);
+extern void error(char*, ...);
+extern int tokenizec(char*, char**, int, char*);
+extern void execproc(void*);
+extern int fsprint(CFid*, char*, ...);
+
+#pragma varargck argpos error 1
+#pragma varargck argpos ctlprint 2
+
+extern Window *wbox;
+extern Message mbox;
+extern Message replies;
+extern char *fsname;
+extern CFid *plumbsendfd;
+extern CFid *plumbseemailfd;
+extern char *home;
+extern char *outgoing;
+extern char *mailboxdir;
+extern char *mboxname;
+extern char *user;
+extern char *srvname;
+extern char deleted[];
+extern int wctlfd;
+extern int shortmenu;
+
+extern CFsys *mailfs;
+extern CFsys *acmefs;
diff --git a/mail/guide b/mail/guide
@@ -0,0 +1,4 @@
+Mail stored
+plumb /mail/box/$user/names
+mail -'x' someaddress
+mkbox /mail/box/$user/new_box
diff --git a/mail/html.c b/mail/html.c
@@ -0,0 +1,75 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <ctype.h>
+#include <plumb.h>
+#include <9pclient.h>
+#include "dat.h"
+
+char*
+formathtml(char *body, int *np)
+{
+ int i, j, p[2], q[2];
+ Exec *e;
+ char buf[1024];
+ Channel *sync;
+
+ e = emalloc(sizeof(struct Exec));
+ if(pipe(p) < 0 || pipe(q) < 0)
+ error("can't create pipe: %r");
+
+ e->p[0] = p[0];
+ e->p[1] = p[1];
+ e->q[0] = q[0];
+ e->q[1] = q[1];
+ e->argv = emalloc(3*sizeof(char*));
+ e->argv[0] = estrdup("htmlfmt");
+ e->argv[1] = estrdup("-cutf-8");
+ e->argv[2] = nil;
+ e->prog = "htmlfmt";
+ sync = chancreate(sizeof(int), 0);
+ e->sync = sync;
+ proccreate(execproc, e, EXECSTACK);
+ recvul(sync);
+ close(p[0]);
+ close(q[1]);
+
+ if((i=write(p[1], body, *np)) != *np){
+ fprint(2, "Mail: warning: htmlfmt failed: wrote %d of %d: %r\n", i, *np);
+ close(p[1]);
+ close(q[0]);
+ return body;
+ }
+ close(p[1]);
+
+ free(body);
+ body = nil;
+ i = 0;
+ for(;;){
+ j = read(q[0], buf, sizeof buf);
+ if(j <= 0)
+ break;
+ body = realloc(body, i+j+1);
+ if(body == nil)
+ error("realloc failed: %r");
+ memmove(body+i, buf, j);
+ i += j;
+ body[i] = '\0';
+ }
+ close(q[0]);
+
+ *np = i;
+ return body;
+}
+
+char*
+readbody(char *type, char *dir, int *np)
+{
+ char *body;
+
+ body = readfile(dir, "body", np);
+ if(body != nil && strcmp(type, "text/html") == 0)
+ return formathtml(body, np);
+ return body;
+}
diff --git a/mail/mail.c b/mail/mail.c
@@ -0,0 +1,643 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <9pclient.h>
+#include <plumb.h>
+#include <ctype.h>
+#include "dat.h"
+
+char *maildir = "Mail/"; /* mountpoint of mail file system */
+char *mboxname = "mbox"; /* mailboxdir/mboxname is mail spool file */
+char *mailboxdir = nil; /* nil == /mail/box/$user */
+char *fsname; /* filesystem for mailboxdir/mboxname is at maildir/fsname */
+char *user;
+char *outgoing;
+char *srvname;
+
+Window *wbox;
+Message mbox;
+Message replies;
+extern char *home;
+CFid *plumbsendfd;
+CFid *plumbseemailfd;
+CFid *plumbshowmailfd;
+CFid *plumbsendmailfd;
+Channel *cplumb;
+Channel *cplumbshow;
+Channel *cplumbsend;
+int wctlfd;
+void mainctl(void*);
+void plumbproc(void*);
+void plumbshowproc(void*);
+void plumbsendproc(void*);
+void plumbthread(void);
+void plumbshowthread(void*);
+void plumbsendthread(void*);
+
+int shortmenu;
+
+CFsys *mailfs;
+CFsys *acmefs;
+
+void
+usage(void)
+{
+ fprint(2, "usage: Mail [-sS] [-n srvname] [-o outgoing] [mailboxname [directoryname]]\n");
+ threadexitsall("usage");
+}
+
+void
+removeupasfs(void)
+{
+ char buf[256];
+
+ if(strcmp(mboxname, "mbox") == 0)
+ return;
+ snprint(buf, sizeof buf, "close %s", mboxname);
+ fswrite(mbox.ctlfd, buf, strlen(buf));
+}
+
+int
+ismaildir(char *s)
+{
+ Dir *d;
+ int ret;
+
+ d = fsdirstat(mailfs, s);
+ if(d == nil)
+ return 0;
+ ret = d->qid.type & QTDIR;
+ free(d);
+ return ret;
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ char *s, *name;
+ char err[ERRMAX], *cmd;
+ int i, newdir;
+ Fmt fmt;
+
+ doquote = needsrcquote;
+ quotefmtinstall();
+
+ /* open these early so we won't miss notification of new mail messages while we read mbox */
+ if((plumbsendfd = plumbopenfid("send", OWRITE|OCEXEC)) == nil)
+ fprint(2, "warning: open plumb/send: %r\n");
+ if((plumbseemailfd = plumbopenfid("seemail", OREAD|OCEXEC)) == nil)
+ fprint(2, "warning: open plumb/seemail: %r\n");
+ if((plumbshowmailfd = plumbopenfid("showmail", OREAD|OCEXEC)) == nil)
+ fprint(2, "warning: open plumb/showmail: %r\n");
+
+ shortmenu = 0;
+ srvname = "mail";
+ ARGBEGIN{
+ case 's':
+ shortmenu = 1;
+ break;
+ case 'S':
+ shortmenu = 2;
+ break;
+ case 'o':
+ outgoing = EARGF(usage());
+ break;
+ case 'm':
+ smprint(maildir, "%s/", EARGF(usage()));
+ break;
+ case 'n':
+ srvname = EARGF(usage());
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ acmefs = nsmount("acme",nil);
+ if(acmefs == nil)
+ error("cannot mount acme: %r");
+ mailfs = nsmount(srvname, nil);
+ if(mailfs == nil)
+ error("cannot mount %s: %r", srvname);
+
+ name = "mbox";
+
+ newdir = 1;
+ if(argc > 0){
+ i = strlen(argv[0]);
+ if(argc>2 || i==0)
+ usage();
+ /* see if the name is that of an existing /mail/fs directory */
+ if(argc==1 && argv[0][0] != '/' && ismaildir(argv[0])){
+ name = argv[0];
+ mboxname = estrdup(name);
+ newdir = 0;
+ }else{
+ if(argv[0][i-1] == '/')
+ argv[0][i-1] = '\0';
+ s = strrchr(argv[0], '/');
+ if(s == nil)
+ mboxname = estrdup(argv[0]);
+ else{
+ *s++ = '\0';
+ if(*s == '\0')
+ usage();
+ mailboxdir = argv[0];
+ mboxname = estrdup(s);
+ }
+ if(argc > 1)
+ name = argv[1];
+ else
+ name = mboxname;
+ }
+ }
+
+ user = getenv("user");
+ if(user == nil)
+ user = "none";
+ home = getenv("home");
+ if(home == nil)
+ home = getenv("HOME");
+ if(home == nil)
+ error("can't find $home");
+ if(mailboxdir == nil)
+ mailboxdir = estrstrdup(home, "/mail");
+ if(outgoing == nil)
+ outgoing = estrstrdup(mailboxdir, "/outgoing");
+
+ mbox.ctlfd = fsopen(mailfs, estrstrdup(mboxname, "/ctl"), OWRITE);
+ if(mbox.ctlfd == nil)
+ error("can't open %s: %r", estrstrdup(mboxname, "/ctl"));
+
+ fsname = estrdup(name);
+ if(newdir && argc > 0){
+ s = emalloc(5+strlen(mailboxdir)+strlen(mboxname)+strlen(name)+10+1);
+ for(i=0; i<10; i++){
+ sprint(s, "open %s/%s %s", mailboxdir, mboxname, fsname);
+ if(fswrite(mbox.ctlfd, s, strlen(s)) >= 0)
+ break;
+ err[0] = '\0';
+ errstr(err, sizeof err);
+ if(strstr(err, "mbox name in use") == nil)
+ error("can't create directory %s for mail: %s", name, err);
+ free(fsname);
+ fsname = emalloc(strlen(name)+10);
+ sprint(fsname, "%s-%d", name, i);
+ }
+ if(i == 10)
+ error("can't open %s/%s: %r", mailboxdir, mboxname);
+ free(s);
+ }
+
+ s = estrstrdup(fsname, "/");
+ mbox.name = estrstrdup(maildir, s);
+ mbox.level= 0;
+ readmbox(&mbox, maildir, s);
+ home = getenv("home");
+ if(home == nil)
+ home = "/";
+
+ wbox = newwindow();
+ winname(wbox, mbox.name);
+ wintagwrite(wbox, "Put Mail Delmesg ", 3+1+4+1+7+1);
+ threadcreate(mainctl, wbox, STACK);
+
+ fmtstrinit(&fmt);
+ fmtprint(&fmt, "Mail");
+ if(shortmenu)
+ fmtprint(&fmt, " -%c", "sS"[shortmenu-1]);
+ if(outgoing)
+ fmtprint(&fmt, " -o %s", outgoing);
+ fmtprint(&fmt, " %s", name);
+ cmd = fmtstrflush(&fmt);
+ if(cmd == nil)
+ sysfatal("out of memory");
+ winsetdump(wbox, "/acme/mail", cmd);
+ mbox.w = wbox;
+
+ mesgmenu(wbox, &mbox);
+ winclean(wbox);
+
+/* wctlfd = open("/dev/wctl", OWRITE|OCEXEC); /* for acme window */
+ wctlfd = -1;
+ cplumb = chancreate(sizeof(Plumbmsg*), 0);
+ cplumbshow = chancreate(sizeof(Plumbmsg*), 0);
+ if(strcmp(name, "mbox") == 0){
+ /*
+ * Avoid creating multiple windows to send mail by only accepting
+ * sendmail plumb messages if we're reading the main mailbox.
+ */
+ plumbsendmailfd = plumbopenfid("sendmail", OREAD|OCEXEC);
+ cplumbsend = chancreate(sizeof(Plumbmsg*), 0);
+ proccreate(plumbsendproc, nil, STACK);
+ threadcreate(plumbsendthread, nil, STACK);
+ }
+ /* start plumb reader as separate proc ... */
+ proccreate(plumbproc, nil, STACK);
+ proccreate(plumbshowproc, nil, STACK);
+ threadcreate(plumbshowthread, nil, STACK);
+ fswrite(mbox.ctlfd, "refresh", 7);
+ /* ... and use this thread to read the messages */
+ plumbthread();
+}
+
+void
+plumbproc(void* v)
+{
+ Plumbmsg *m;
+
+ threadsetname("plumbproc");
+ for(;;){
+ m = plumbrecvfid(plumbseemailfd);
+ sendp(cplumb, m);
+ if(m == nil)
+ threadexits(nil);
+ }
+}
+
+void
+plumbshowproc(void* v)
+{
+ Plumbmsg *m;
+
+ threadsetname("plumbshowproc");
+ for(;;){
+ m = plumbrecvfid(plumbshowmailfd);
+ sendp(cplumbshow, m);
+ if(m == nil)
+ threadexits(nil);
+ }
+}
+
+void
+plumbsendproc(void* v)
+{
+ Plumbmsg *m;
+
+ threadsetname("plumbsendproc");
+ for(;;){
+ m = plumbrecvfid(plumbsendmailfd);
+ sendp(cplumbsend, m);
+ if(m == nil)
+ threadexits(nil);
+ }
+}
+
+void
+newmesg(char *name, char *digest)
+{
+ Dir *d;
+
+ if(strncmp(name, mbox.name, strlen(mbox.name)) != 0)
+ return; /* message is about another mailbox */
+ if(mesglookupfile(&mbox, name, digest) != nil)
+ return;
+ if(strncmp(name, "Mail/", 5) == 0)
+ name += 5;
+ d = fsdirstat(mailfs, name);
+ if(d == nil)
+ return;
+ if(mesgadd(&mbox, mbox.name, d, digest))
+ mesgmenunew(wbox, &mbox);
+ free(d);
+}
+
+void
+showmesg(char *name, char *digest)
+{
+ char *n;
+ char *mb;
+
+ mb = mbox.name;
+ if(strncmp(name, mb, strlen(mb)) != 0)
+ return; /* message is about another mailbox */
+ n = estrdup(name+strlen(mb));
+ if(n[strlen(n)-1] != '/')
+ n = egrow(n, "/", nil);
+ mesgopen(&mbox, mbox.name, name+strlen(mb), nil, 1, digest);
+ free(n);
+}
+
+void
+delmesg(char *name, char *digest, int dodel, char *save)
+{
+ Message *m;
+
+ m = mesglookupfile(&mbox, name, digest);
+ if(m != nil){
+ if(save)
+ mesgcommand(m, estrstrdup("Save ", save));
+ if(dodel)
+ mesgmenumarkdel(wbox, &mbox, m, 1);
+ else{
+ /* notification came from plumber - message is gone */
+ mesgmenudel(wbox, &mbox, m);
+ if(!m->opened)
+ mesgdel(&mbox, m);
+ }
+ }
+}
+
+void
+plumbthread(void)
+{
+ Plumbmsg *m;
+ Plumbattr *a;
+ char *type, *digest;
+
+ threadsetname("plumbthread");
+ while((m = recvp(cplumb)) != nil){
+ a = m->attr;
+ digest = plumblookup(a, "digest");
+ type = plumblookup(a, "mailtype");
+ if(type == nil)
+ fprint(2, "Mail: plumb message with no mailtype attribute\n");
+ else if(strcmp(type, "new") == 0)
+ newmesg(m->data, digest);
+ else if(strcmp(type, "delete") == 0)
+ delmesg(m->data, digest, 0, nil);
+ else
+ fprint(2, "Mail: unknown plumb attribute %s\n", type);
+ plumbfree(m);
+ }
+ threadexits(nil);
+}
+
+void
+plumbshowthread(void *v)
+{
+ Plumbmsg *m;
+
+ USED(v);
+ threadsetname("plumbshowthread");
+ while((m = recvp(cplumbshow)) != nil){
+ showmesg(m->data, plumblookup(m->attr, "digest"));
+ plumbfree(m);
+ }
+ threadexits(nil);
+}
+
+void
+plumbsendthread(void *v)
+{
+ Plumbmsg *m;
+
+ USED(v);
+ threadsetname("plumbsendthread");
+ while((m = recvp(cplumbsend)) != nil){
+ mkreply(nil, "Mail", m->data, m->attr, nil);
+ plumbfree(m);
+ }
+ threadexits(nil);
+}
+
+int
+mboxcommand(Window *w, char *s)
+{
+ char *args[10], **targs, *save;
+ Window *sbox;
+ Message *m, *next;
+ int ok, nargs, i, j;
+ CFid *searchfd;
+ char buf[128], *res;
+
+ nargs = tokenize(s, args, nelem(args));
+ if(nargs == 0)
+ return 0;
+ if(strcmp(args[0], "Mail") == 0){
+ if(nargs == 1)
+ mkreply(nil, "Mail", "", nil, nil);
+ else
+ mkreply(nil, "Mail", args[1], nil, nil);
+ return 1;
+ }
+ if(strcmp(s, "Del") == 0){
+ if(mbox.dirty){
+ mbox.dirty = 0;
+ fprint(2, "mail: mailbox not written\n");
+ return 1;
+ }
+ if(w != mbox.w){
+ windel(w, 1);
+ return 1;
+ }
+ ok = 1;
+ for(m=mbox.head; m!=nil; m=next){
+ next = m->next;
+ if(m->w){
+ if(windel(m->w, 0))
+ m->w = nil;
+ else
+ ok = 0;
+ }
+ }
+ for(m=replies.head; m!=nil; m=next){
+ next = m->next;
+ if(m->w){
+ if(windel(m->w, 0))
+ m->w = nil;
+ else
+ ok = 0;
+ }
+ }
+ if(ok){
+ windel(w, 1);
+ removeupasfs();
+ threadexitsall(nil);
+ }
+ return 1;
+ }
+ if(strcmp(s, "Put") == 0){
+ rewritembox(wbox, &mbox);
+ return 1;
+ }
+ if(strcmp(s, "Get") == 0){
+ fswrite(mbox.ctlfd, "refresh", 7);
+ return 1;
+ }
+ if(strcmp(s, "Delmesg") == 0){
+ save = nil;
+ if(nargs > 1)
+ save = args[1];
+ s = winselection(w);
+ if(s == nil)
+ return 1;
+ nargs = 1;
+ for(i=0; s[i]; i++)
+ if(s[i] == '\n')
+ nargs++;
+ targs = emalloc(nargs*sizeof(char*)); /* could be too many for a local array */
+ nargs = getfields(s, targs, nargs, 1, "\n");
+ for(i=0; i<nargs; i++){
+ if(!isdigit(targs[i][0]))
+ continue;
+ j = atoi(targs[i]); /* easy way to parse the number! */
+ if(j == 0)
+ continue;
+ snprint(buf, sizeof buf, "%s%d", mbox.name, j);
+ delmesg(buf, nil, 1, save);
+ }
+ free(s);
+ free(targs);
+ return 1;
+ }
+ if(strcmp(s, "Search") == 0){
+ if(nargs <= 1)
+ return 1;
+ s = estrstrdup(mboxname, "/search");
+ searchfd = fsopen(mailfs, s, ORDWR);
+ if(searchfd == nil)
+ return 1;
+ save = estrdup(args[1]);
+ for(i=2; i<nargs; i++)
+ save = eappend(save, " ", args[i]);
+ fswrite(searchfd, save, strlen(save));
+ fsseek(searchfd, 0, 0);
+ j = fsread(searchfd, buf, sizeof buf - 1);
+ if(j == 0){
+ fprint(2, "[%s] search %s: no results found\n", mboxname, save);
+ fsclose(searchfd);
+ free(save);
+ return 1;
+ }
+ free(save);
+ buf[j] = '\0';
+ res = estrdup(buf);
+ j = fsread(searchfd, buf, sizeof buf - 1);
+ for(; j != 0; j = fsread(searchfd, buf, sizeof buf - 1), buf[j] = '\0')
+ res = eappend(res, "", buf);
+ fsclose(searchfd);
+
+ sbox = newwindow();
+ winname(sbox, s);
+ free(s);
+ threadcreate(mainctl, sbox, STACK);
+ winopenbody(sbox, OWRITE);
+
+ /* show results in reverse order */
+ m = mbox.tail;
+ save = nil;
+ for(s=strrchr(res, ' '); s!=nil || save!=res; s=strrchr(res, ' ')){
+ if(s != nil){
+ save = s+1;
+ *s = '\0';
+ }
+ else save = res;
+ save = estrstrdup(save, "/");
+ for(; m && strcmp(save, m->name) != 0; m=m->prev);
+ free(save);
+ if(m == nil)
+ break;
+ fsprint(sbox->body, "%s%s\n", m->name, info(m, 0, 0));
+ m = m->prev;
+ }
+ free(res);
+ winclean(sbox);
+ winclosebody(sbox);
+ return 1;
+ }
+ return 0;
+}
+
+void
+mainctl(void *v)
+{
+ Window *w;
+ Event *e, *e2, *eq, *ea;
+ int na, nopen;
+ char *s, *t, *buf;
+
+ w = v;
+ winincref(w);
+ proccreate(wineventproc, w, STACK);
+
+ for(;;){
+ e = recvp(w->cevent);
+ switch(e->c1){
+ default:
+ Unknown:
+ print("unknown message %c%c\n", e->c1, e->c2);
+ break;
+
+ case 'E': /* write to body; can't affect us */
+ break;
+
+ case 'F': /* generated by our actions; ignore */
+ break;
+
+ case 'K': /* type away; we don't care */
+ break;
+
+ case 'M':
+ switch(e->c2){
+ case 'x':
+ case 'X':
+ ea = nil;
+ e2 = nil;
+ if(e->flag & 2)
+ e2 = recvp(w->cevent);
+ if(e->flag & 8){
+ ea = recvp(w->cevent);
+ na = ea->nb;
+ recvp(w->cevent);
+ }else
+ na = 0;
+ s = e->b;
+ /* if it's a known command, do it */
+ if((e->flag&2) && e->nb==0)
+ s = e2->b;
+ if(na){
+ t = emalloc(strlen(s)+1+na+1);
+ sprint(t, "%s %s", s, ea->b);
+ s = t;
+ }
+ /* if it's a long message, it can't be for us anyway */
+ if(!mboxcommand(w, s)) /* send it back */
+ winwriteevent(w, e);
+ if(na)
+ free(s);
+ break;
+
+ case 'l':
+ case 'L':
+ buf = nil;
+ eq = e;
+ if(e->flag & 2){
+ e2 = recvp(w->cevent);
+ eq = e2;
+ }
+ s = eq->b;
+ if(eq->q1>eq->q0 && eq->nb==0){
+ buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
+ winread(w, eq->q0, eq->q1, buf);
+ s = buf;
+ }
+ nopen = 0;
+ do{
+ /* skip 'deleted' string if present' */
+ if(strncmp(s, deleted, strlen(deleted)) == 0)
+ s += strlen(deleted);
+ /* skip mail box name if present */
+ if(strncmp(s, mbox.name, strlen(mbox.name)) == 0)
+ s += strlen(mbox.name);
+ nopen += mesgopen(&mbox, mbox.name, s, nil, 0, nil);
+ while(*s!='\0' && *s++!='\n')
+ ;
+ }while(*s);
+ if(nopen == 0) /* send it back */
+ winwriteevent(w, e);
+ free(buf);
+ break;
+
+ case 'I': /* modify away; we don't care */
+ case 'D':
+ case 'd':
+ case 'i':
+ break;
+
+ default:
+ goto Unknown;
+ }
+ }
+ }
+}
diff --git a/mail/mesg.c b/mail/mesg.c
@@ -0,0 +1,1424 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <ctype.h>
+#include <9pclient.h>
+#include <plumb.h>
+#include "dat.h"
+
+enum
+{
+ DIRCHUNK = 32*sizeof(Dir)
+};
+
+char regexchars[] = "\\/[].+?()*^$";
+char deleted[] = "(deleted)-";
+char deletedrx[] = "\\(deleted\\)-";
+char deletedrx01[] = "(\\(deleted\\)-)?";
+char deletedaddr[] = "-#0;/^\\(deleted\\)-/";
+
+struct{
+ char *type;
+ char *port;
+ char *suffix;
+} ports[] = {
+ "text/", "edit", ".txt", /* must be first for plumbport() */
+ "image/gif", "image", ".gif",
+ "image/jpeg", "image", ".jpg",
+ "image/jpeg", "image", ".jpeg",
+ "image/png", "image", ".png",
+ "application/postscript", "postscript", ".ps",
+ "application/pdf", "postscript", ".pdf",
+ "application/msword", "msword", ".doc",
+ "application/rtf", "msword", ".rtf",
+ nil, nil
+};
+
+char *goodtypes[] = {
+ "text",
+ "text/plain",
+ "message/rfc822",
+ "text/richtext",
+ "text/tab-separated-values",
+ "application/octet-stream",
+ nil
+};
+
+char *okheaders[] =
+{
+ "From:",
+ "Date:",
+ "To:",
+ "CC:",
+ "Subject:",
+ nil
+};
+
+char *extraheaders[] =
+{
+ "Resent-From:",
+ "Resent-To:",
+ "Sort:",
+ nil
+};
+
+char*
+line(char *data, char **pp)
+{
+ char *p, *q;
+
+ for(p=data; *p!='\0' && *p!='\n'; p++)
+ ;
+ if(*p == '\n')
+ *pp = p+1;
+ else
+ *pp = p;
+ q = emalloc(p-data + 1);
+ memmove(q, data, p-data);
+ return q;
+}
+
+static char*
+mkaddrs(char *t, char **colon)
+{
+ int i, nf, inquote;
+ char **f, *s;
+ Fmt fmt;
+
+ inquote = 0;
+ nf = 2;
+ for(s=t; *s; s++){
+ if(*s == '\'')
+ inquote = !inquote;
+ if(*s == ' ' && !inquote)
+ nf++;
+ }
+ f = emalloc(nf*sizeof f[0]);
+ nf = tokenize(t, f, nf);
+ if(colon){
+ fmtstrinit(&fmt);
+ for(i=0; i+1<nf; i+=2){
+ if(i > 0)
+ fmtprint(&fmt, ", ");
+ if(f[i][0] == 0 || strcmp(f[i], f[i+1]) == 0)
+ fmtprint(&fmt, "%s", f[i+1]);
+ else
+ fmtprint(&fmt, "%s <%s>", f[i], f[i+1]);
+ }
+ *colon = fmtstrflush(&fmt);
+ }
+ fmtstrinit(&fmt);
+ for(i=0; i+1<nf; i+=2){
+ if(i > 0)
+ fmtprint(&fmt, ", ");
+ fmtprint(&fmt, "%s", f[i+1]);
+ }
+ free(f);
+ return fmtstrflush(&fmt);
+}
+
+int
+loadinfo(Message *m, char *dir)
+{
+ int n;
+ char *data, *p, *s, *t;
+
+ data = readfile(dir, "info", &n);
+ if(data == nil)
+ return 0;
+
+ p = data;
+ while((s = line(p, &p)) != nil && *s != 0){
+ t = strchr(s, ' ');
+ if(t == nil)
+ continue;
+ *t++ = 0;
+ if(strcmp(s, "from") == 0){
+ free(m->from);
+ m->from = mkaddrs(t, &m->fromcolon);
+ }else if(strcmp(s, "sender") == 0){
+ free(m->sender);
+ m->sender = mkaddrs(t, nil);
+ }else if(strcmp(s, "to") == 0){
+ free(m->to);
+ m->to = mkaddrs(t, nil);
+ }else if(strcmp(s, "cc") == 0){
+ free(m->cc);
+ m->cc = mkaddrs(t, nil);
+ }else if(strcmp(s, "replyto") == 0){
+ free(m->replyto);
+ m->replyto = mkaddrs(t, nil);
+ }else if(strcmp(s, "subject") == 0){
+ free(m->subject);
+ m->subject = estrdup(t);
+ }else if(strcmp(s, "type") == 0){
+ free(m->type);
+ m->type = estrdup(t);
+ }else if(strcmp(s, "unixdate") == 0 && (t=strchr(t, ' ')) != nil){
+ free(m->date);
+ m->date = estrdup(t+1);
+ }else if(strcmp(s, "digest") == 0){
+ free(m->digest);
+ m->digest = estrdup(t);
+ }else if(strcmp(s, "filename") == 0){
+ free(m->filename);
+ m->filename = estrdup(t);
+ }
+ free(s);
+ }
+ free(s);
+ free(data);
+ if(m->replyto == nil){
+ if(m->sender)
+ m->replyto = estrdup(m->sender);
+ else if(m->from)
+ m->replyto = estrdup(m->from);
+ else
+ m->replyto = estrdup("");
+ }
+ if(m->from == nil)
+ m->from = estrdup("");
+ if(m->to == nil)
+ m->to = estrdup("");
+ if(m->cc == nil)
+ m->cc = estrdup("");
+ if(m->subject == nil)
+ m->subject = estrdup("");
+ if(m->type == nil)
+ m->type = estrdup("");
+ if(m->date == nil)
+ m->date = estrdup("");
+ if(m->disposition == nil)
+ m->disposition = estrdup("");
+ if(m->filename == nil)
+ m->filename = estrdup("");
+ if(m->digest == nil)
+ m->digest = estrdup("");
+ return 1;
+}
+
+int
+isnumeric(char *s)
+{
+ while(*s){
+ if(!isdigit(*s))
+ return 0;
+ s++;
+ }
+ return 1;
+}
+
+CFid*
+mailopen(char *name, int mode)
+{
+ if(strncmp(name, "Mail/", 5) != 0)
+ return nil;
+ return fsopen(mailfs, name+5, mode);
+}
+
+Dir*
+loaddir(char *name, int *np)
+{
+ CFid *fid;
+ Dir *dp;
+
+ fid = mailopen(name, OREAD);
+ if(fid == nil)
+ return nil;
+ *np = fsdirreadall(fid, &dp);
+ fsclose(fid);
+ return dp;
+}
+
+void
+readmbox(Message *mbox, char *dir, char *subdir)
+{
+ char *name;
+ Dir *d, *dirp;
+ int i, n;
+
+ name = estrstrdup(dir, subdir);
+ dirp = loaddir(name, &n);
+ mbox->recursed = 1;
+ if(dirp)
+ for(i=0; i<n; i++){
+ d = &dirp[i];
+ if(isnumeric(d->name))
+ mesgadd(mbox, name, d, nil);
+ }
+ free(dirp);
+ free(name);
+}
+
+/* add message to box, in increasing numerical order */
+int
+mesgadd(Message *mbox, char *dir, Dir *d, char *digest)
+{
+ Message *m;
+ char *name;
+ int loaded;
+
+ m = emalloc(sizeof(Message));
+ m->name = estrstrdup(d->name, "/");
+ m->next = nil;
+ m->prev = mbox->tail;
+ m->level= mbox->level+1;
+ m->recursed = 0;
+ name = estrstrdup(dir, m->name);
+ loaded = loadinfo(m, name);
+ free(name);
+ /* if two upas/fs are running, we can get misled, so check digest before accepting message */
+ if(loaded==0 || (digest!=nil && m->digest!=nil && strcmp(digest, m->digest)!=0)){
+ mesgfreeparts(m);
+ free(m);
+ return 0;
+ }
+ if(mbox->tail != nil)
+ mbox->tail->next = m;
+ mbox->tail = m;
+ if(mbox->head == nil)
+ mbox->head = m;
+
+ if (m->level != 1){
+ m->recursed = 1;
+ readmbox(m, dir, m->name);
+ }
+ return 1;
+}
+
+int
+thisyear(char *year)
+{
+ static char now[10];
+ char *s;
+
+ if(now[0] == '\0'){
+ s = ctime(time(nil));
+ strcpy(now, s+24);
+ }
+ return strncmp(year, now, 4) == 0;
+}
+
+char*
+stripdate(char *as)
+{
+ int n;
+ char *s, *fld[10];
+
+ as = estrdup(as);
+ s = estrdup(as);
+ n = tokenize(s, fld, 10);
+ if(n > 5){
+ sprint(as, "%.3s ", fld[0]); /* day */
+ /* some dates have 19 Apr, some Apr 19 */
+ if(strlen(fld[1])<4 && isnumeric(fld[1]))
+ sprint(as+strlen(as), "%.3s %.3s ", fld[1], fld[2]); /* date, month */
+ else
+ sprint(as+strlen(as), "%.3s %.3s ", fld[2], fld[1]); /* date, month */
+ /* do we use time or year? depends on whether year matches this one */
+ if(thisyear(fld[5])){
+ if(strchr(fld[3], ':') != nil)
+ sprint(as+strlen(as), "%.5s ", fld[3]); /* time */
+ else if(strchr(fld[4], ':') != nil)
+ sprint(as+strlen(as), "%.5s ", fld[4]); /* time */
+ }else
+ sprint(as+strlen(as), "%.4s ", fld[5]); /* year */
+ }
+ free(s);
+ return as;
+}
+
+char*
+readfile(char *dir, char *name, int *np)
+{
+ char *file, *data;
+ int len;
+ Dir *d;
+ CFid *fid;
+ char buf[1];
+
+ if(np != nil)
+ *np = 0;
+ file = estrstrdup(dir, name);
+ fid = mailopen(file, OREAD);
+ if(fid == nil)
+ return nil;
+ d = fsdirfstat(fid);
+ if(d && d->length == 0){
+ /* some files, e.g. body, are not loaded until we read them */
+ fsread(fid, buf, 1);
+ fsseek(fid, 0, 0);
+ free(d);
+ d = fsdirfstat(fid);
+ }
+ free(file);
+ len = 0;
+ if(d != nil)
+ len = d->length;
+ free(d);
+ data = emalloc(len+1);
+ len = fsreadn(fid, data, len);
+ if(len <= 0){
+ fsclose(fid);
+ free(data);
+ return nil;
+ }
+ fsclose(fid);
+ if(np != nil)
+ *np = len;
+ return data;
+}
+
+char*
+info(Message *m, int ind, int ogf)
+{
+ char *i;
+ int j, len, lens;
+ char *p;
+ char fmt[80], s[80];
+
+ if (ogf)
+ p=m->to;
+ else
+ p=m->fromcolon;
+
+ if(ind==0 && shortmenu){
+ len = 30;
+ lens = 30;
+ if(shortmenu > 1){
+ len = 10;
+ lens = 25;
+ }
+ if(ind==0 && m->subject[0]=='\0'){
+ snprint(fmt, sizeof fmt, " %%-%d.%ds", len, len);
+ snprint(s, sizeof s, fmt, p);
+ }else{
+ snprint(fmt, sizeof fmt, " %%-%d.%ds %%-%d.%ds", len, len, lens, lens);
+ snprint(s, sizeof s, fmt, p, m->subject);
+ }
+ i = estrdup(s);
+
+ return i;
+ }
+
+ i = estrdup("");
+ i = eappend(i, "\t", p);
+ i = egrow(i, "\t", stripdate(m->date));
+ if(ind == 0){
+ if(strcmp(m->type, "text")!=0 && strncmp(m->type, "text/", 5)!=0 &&
+ strncmp(m->type, "multipart/", 10)!=0)
+ i = egrow(i, "\t(", estrstrdup(m->type, ")"));
+ }else if(strncmp(m->type, "multipart/", 10) != 0)
+ i = egrow(i, "\t(", estrstrdup(m->type, ")"));
+ if(m->subject[0] != '\0'){
+ i = eappend(i, "\n", nil);
+ for(j=0; j<ind; j++)
+ i = eappend(i, "\t", nil);
+ i = eappend(i, "\t", m->subject);
+ }
+ return i;
+}
+
+void
+mesgmenu0(Window *w, Message *mbox, char *realdir, char *dir, int ind, CFid *fd, int onlyone, int dotail)
+{
+ int i;
+ Message *m;
+ char *name, *tmp;
+ int ogf=0;
+
+ if(strstr(realdir, "outgoing") != nil)
+ ogf=1;
+
+ /* show mail box in reverse order, pieces in forward order */
+ if(ind > 0)
+ m = mbox->head;
+ else
+ m = mbox->tail;
+ while(m != nil){
+ for(i=0; i<ind; i++)
+ fsprint(fd, "\t");
+ if(ind != 0)
+ fsprint(fd, " ");
+ name = estrstrdup(dir, m->name);
+ tmp = info(m, ind, ogf);
+ fsprint(fd, "%s%s\n", name, tmp);
+ free(tmp);
+ if(dotail && m->tail)
+ mesgmenu0(w, m, realdir, name, ind+1, fd, 0, dotail);
+ free(name);
+ if(ind)
+ m = m->next;
+ else
+ m = m->prev;
+ if(onlyone)
+ m = nil;
+ }
+}
+
+void
+mesgmenu(Window *w, Message *mbox)
+{
+ winopenbody(w, OWRITE);
+ mesgmenu0(w, mbox, mbox->name, "", 0, w->body, 0, !shortmenu);
+ winclosebody(w);
+}
+
+/* one new message has arrived, as mbox->tail */
+void
+mesgmenunew(Window *w, Message *mbox)
+{
+ Biobuf *b;
+
+ winselect(w, "0", 0);
+ w->data = winopenfile(w, "data");
+ b = emalloc(sizeof(Biobuf));
+ mesgmenu0(w, mbox, mbox->name, "", 0, w->data, 1, !shortmenu);
+ free(b);
+ if(!mbox->dirty)
+ winclean(w);
+ /* select tag line plus following indented lines, but not final newline (it's distinctive) */
+ winselect(w, "0/.*\\n((\t.*\\n)*\t.*)?/", 1);
+ fsclose(w->addr);
+ fsclose(w->data);
+ w->addr = nil;
+ w->data = nil;
+}
+
+char*
+name2regexp(char *prefix, char *s)
+{
+ char *buf, *p, *q;
+
+ buf = emalloc(strlen(prefix)+2*strlen(s)+50); /* leave room to append more */
+ p = buf;
+ *p++ = '0';
+ *p++ = '/';
+ *p++ = '^';
+ strcpy(p, prefix);
+ p += strlen(prefix);
+ for(q=s; *q!='\0'; q++){
+ if(strchr(regexchars, *q) != nil)
+ *p++ = '\\';
+ *p++ = *q;
+ }
+ *p++ = '/';
+ *p = '\0';
+ return buf;
+}
+
+void
+mesgmenumarkdel(Window *w, Message *mbox, Message *m, int writeback)
+{
+ char *buf;
+
+
+ if(m->deleted)
+ return;
+ m->writebackdel = writeback;
+ if(w->data == nil)
+ w->data = winopenfile(w, "data");
+ buf = name2regexp("", m->name);
+ strcat(buf, "-#0");
+ if(winselect(w, buf, 1))
+ fswrite(w->data, deleted, 10);
+ free(buf);
+ fsclose(w->data);
+ fsclose(w->addr);
+ w->addr = nil;
+ w->data = nil;
+ mbox->dirty = 1;
+ m->deleted = 1;
+}
+
+void
+mesgmenumarkundel(Window *w, Message *v, Message *m)
+{
+ char *buf;
+
+ USED(v);
+ if(m->deleted == 0)
+ return;
+ if(w->data == nil)
+ w->data = winopenfile(w, "data");
+ buf = name2regexp(deletedrx, m->name);
+ if(winselect(w, buf, 1))
+ if(winsetaddr(w, deletedaddr, 1))
+ fswrite(w->data, "", 0);
+ free(buf);
+ fsclose(w->data);
+ fsclose(w->addr);
+ w->addr = nil;
+ w->data = nil;
+ m->deleted = 0;
+}
+
+void
+mesgmenudel(Window *w, Message *mbox, Message *m)
+{
+ char *buf;
+
+ if(w->data ==nil)
+ w->data = winopenfile(w, "data");
+ buf = name2regexp(deletedrx01, m->name);
+ if(winsetaddr(w, buf, 1) && winsetaddr(w, ".,./.*\\n(\t.*\\n)*/", 1))
+ fswrite(w->data, "", 0);
+ free(buf);
+ fsclose(w->data);
+ fsclose(w->addr);
+ w->addr = nil;
+ w->data = nil;
+/* assume caller knows best mbox->dirty = 1; */
+ m->deleted = 1;
+}
+
+void
+mesgmenumark(Window *w, char *which, char *mark)
+{
+ char *buf;
+
+ if(w->data == nil)
+ w->data = winopenfile(w, "data");
+ buf = name2regexp(deletedrx01, which);
+ if(winsetaddr(w, buf, 1) && winsetaddr(w, "+0-#1", 1)) /* go to end of line */
+ fswrite(w->data, mark, strlen(mark));
+ free(buf);
+ fsclose(w->data);
+ fsclose(w->addr);
+ w->addr = nil;
+ w->data = nil;
+ if(!mbox.dirty)
+ winclean(w);
+}
+
+void
+mesgfreeparts(Message *m)
+{
+ free(m->name);
+ free(m->replyname);
+ free(m->from);
+ free(m->to);
+ free(m->cc);
+ free(m->replyto);
+ free(m->date);
+ free(m->subject);
+ free(m->type);
+ free(m->disposition);
+ free(m->filename);
+ free(m->digest);
+}
+
+void
+mesgdel(Message *mbox, Message *m)
+{
+ Message *n, *next;
+
+ if(m->opened)
+ error("internal error: deleted message still open in mesgdel\n");
+ /* delete subparts */
+ for(n=m->head; n!=nil; n=next){
+ next = n->next;
+ mesgdel(m, n);
+ }
+ /* remove this message from list */
+ if(m->next)
+ m->next->prev = m->prev;
+ else
+ mbox->tail = m->prev;
+ if(m->prev)
+ m->prev->next = m->next;
+ else
+ mbox->head = m->next;
+
+ mesgfreeparts(m);
+}
+
+int
+mesgsave(Message *m, char *s, int save)
+{
+ int ofd, n, k, ret;
+ char *t, *raw, *unixheader, *all;
+
+ if(save){
+ if(fsprint(mbox.ctlfd, "save %q %q", s, m->name) < 0){
+ fprint(2, "Mail: can't save %s to %s: %r\n", m->name, s);
+ return 0;
+ }
+ return 1;
+ }
+
+ t = estrstrdup(mbox.name, m->name);
+ raw = readfile(t, "raw", &n);
+ unixheader = readfile(t, "unixheader", &k);
+ if(raw==nil || unixheader==nil){
+ fprint(2, "Mail: can't read %s: %r\n", t);
+ free(t);
+ return 0;
+ }
+ free(t);
+
+ all = emalloc(n+k+1);
+ memmove(all, unixheader, k);
+ memmove(all+k, raw, n);
+ memmove(all+k+n, "\n", 1);
+ n = k+n+1;
+ free(unixheader);
+ free(raw);
+ ret = 1;
+ s = estrdup(s);
+ if(s[0] != '/')
+ s = egrow(estrdup(mailboxdir), "/", s);
+ ofd = open(s, OWRITE);
+ if(ofd < 0){
+ fprint(2, "Mail: can't open %s: %r\n", s);
+ ret = 0;
+ }else if(seek(ofd, 0LL, 2)<0 || write(ofd, all, n)!=n){
+ fprint(2, "Mail: save failed: can't write %s: %r\n", s);
+ ret = 0;
+ }
+ free(all);
+ close(ofd);
+ free(s);
+ return ret;
+}
+
+int
+mesgcommand(Message *m, char *cmd)
+{
+ char *s;
+ char *args[10];
+ int save, ok, ret, nargs;
+
+ s = cmd;
+ ret = 1;
+ nargs = tokenize(s, args, nelem(args));
+ if(nargs == 0)
+ return 0;
+ if(strcmp(args[0], "Post") == 0){
+ mesgsend(m);
+ goto Return;
+ }
+ if(strncmp(args[0], "Save", 4) == 0 || strncmp(args[0], "Write", 5) == 0){
+ if(m->isreply)
+ goto Return;
+ save = args[0][0]=='S';
+ if(save)
+ s = estrdup("\t[saved");
+ else
+ s = estrdup("\t[wrote");
+ if(nargs==1 || strcmp(args[1], "")==0){
+ ok = mesgsave(m, "stored", save);
+ }else{
+ ok = mesgsave(m, args[1], save);
+ s = eappend(s, " ", args[1]);
+ }
+ if(ok){
+ s = egrow(s, "]", nil);
+ mesgmenumark(mbox.w, m->name, s);
+ }
+ free(s);
+ goto Return;
+ }
+ if(strcmp(args[0], "Reply")==0){
+ if(nargs>=2 && strcmp(args[1], "all")==0)
+ mkreply(m, "Replyall", nil, nil, nil);
+ else
+ mkreply(m, "Reply", nil, nil, nil);
+ goto Return;
+ }
+ if(strcmp(args[0], "Q") == 0){
+ s = winselection(m->w); /* will be freed by mkreply */
+ if(nargs>=3 && strcmp(args[1], "Reply")==0 && strcmp(args[2], "all")==0)
+ mkreply(m, "QReplyall", nil, nil, s);
+ else
+ mkreply(m, "QReply", nil, nil, s);
+ goto Return;
+ }
+ if(strcmp(args[0], "Del") == 0){
+ if(windel(m->w, 0)){
+ windecref(m->w);
+ m->w = nil;
+ if(m->isreply)
+ delreply(m);
+ else{
+ m->opened = 0;
+ m->tagposted = 0;
+ }
+ free(cmd);
+ threadexits(nil);
+ }
+ goto Return;
+ }
+ if(strcmp(args[0], "Delmesg") == 0){
+ if(!m->isreply){
+ mesgmenumarkdel(wbox, &mbox, m, 1);
+ free(cmd); /* mesgcommand might not return */
+ mesgcommand(m, estrdup("Del"));
+ return 1;
+ }
+ goto Return;
+ }
+ if(strcmp(args[0], "UnDelmesg") == 0){
+ if(!m->isreply && m->deleted)
+ mesgmenumarkundel(wbox, &mbox, m);
+ goto Return;
+ }
+/* if(strcmp(args[0], "Headers") == 0){ */
+/* m->showheaders(); */
+/* return True; */
+/* } */
+
+ ret = 0;
+
+ Return:
+ free(cmd);
+ return ret;
+}
+
+void
+mesgtagpost(Message *m)
+{
+ if(m->tagposted)
+ return;
+ wintagwrite(m->w, " Post", 5);
+ m->tagposted = 1;
+}
+
+/* need to expand selection more than default word */
+#pragma varargck argpos eval 2
+
+long
+eval(Window *w, char *s, ...)
+{
+ char buf[64];
+ va_list arg;
+
+ va_start(arg, s);
+ vsnprint(buf, sizeof buf, s, arg);
+ va_end(arg);
+
+ if(winsetaddr(w, buf, 1)==0)
+ return -1;
+
+ if(fspread(w->addr, buf, 24, 0) != 24)
+ return -1;
+ return strtol(buf, 0, 10);
+}
+
+int
+isemail(char *s)
+{
+ int nat;
+
+ nat = 0;
+ for(; *s; s++)
+ if(*s == '@')
+ nat++;
+ else if(!isalpha(*s) && !isdigit(*s) && !strchr("_.-+/", *s))
+ return 0;
+ return nat==1;
+}
+
+char addrdelim[] = "/[ \t\\n<>()\\[\\]]/";
+char*
+expandaddr(Window *w, Event *e)
+{
+ char *s;
+ long q0, q1;
+
+ if(e->q0 != e->q1) /* cannot happen */
+ return nil;
+
+ q0 = eval(w, "#%d-%s", e->q0, addrdelim);
+ if(q0 == -1) /* bad char not found */
+ q0 = 0;
+ else /* increment past bad char */
+ q0++;
+
+ q1 = eval(w, "#%d+%s", e->q0, addrdelim);
+ if(q1 < 0){
+ q1 = eval(w, "$");
+ if(q1 < 0)
+ return nil;
+ }
+ if(q0 >= q1)
+ return nil;
+ s = emalloc((q1-q0)*UTFmax+1);
+ winread(w, q0, q1, s);
+ return s;
+}
+
+int
+replytoaddr(Window *w, Message *m, Event *e, char *s)
+{
+ int did;
+ char *buf;
+ Plumbmsg *pm;
+
+ buf = nil;
+ did = 0;
+ if(e->flag & 2){
+ /* autoexpanded; use our own bigger expansion */
+ buf = expandaddr(w, e);
+ if(buf == nil)
+ return 0;
+ s = buf;
+ }
+ if(isemail(s)){
+ did = 1;
+ pm = emalloc(sizeof(Plumbmsg));
+ pm->src = estrdup("Mail");
+ pm->dst = estrdup("sendmail");
+ pm->data = estrdup(s);
+ pm->ndata = -1;
+ if(m->subject && m->subject[0]){
+ pm->attr = emalloc(sizeof(Plumbattr));
+ pm->attr->name = estrdup("Subject");
+ if(tolower(m->subject[0]) != 'r' || tolower(m->subject[1]) != 'e' || m->subject[2] != ':')
+ pm->attr->value = estrstrdup("Re: ", m->subject);
+ else
+ pm->attr->value = estrdup(m->subject);
+ pm->attr->next = nil;
+ }
+ if(plumbsendtofid(plumbsendfd, pm) < 0)
+ fprint(2, "error writing plumb message: %r\n");
+ plumbfree(pm);
+ }
+ free(buf);
+ return did;
+}
+
+
+void
+mesgctl(void *v)
+{
+ Message *m;
+ Window *w;
+ Event *e, *eq, *e2, *ea;
+ int na, nopen, i, j;
+ char *os, *s, *t, *buf;
+
+ m = v;
+ w = m->w;
+ threadsetname("mesgctl");
+ winincref(w);
+ proccreate(wineventproc, w, STACK);
+ for(;;){
+ e = recvp(w->cevent);
+ switch(e->c1){
+ default:
+ Unk:
+ print("unknown message %c%c\n", e->c1, e->c2);
+ break;
+
+ case 'E': /* write to body; can't affect us */
+ break;
+
+ case 'F': /* generated by our actions; ignore */
+ break;
+
+ case 'K': /* type away; we don't care */
+ case 'M':
+ switch(e->c2){
+ case 'x': /* mouse only */
+ case 'X':
+ ea = nil;
+ eq = e;
+ if(e->flag & 2){
+ e2 = recvp(w->cevent);
+ eq = e2;
+ }
+ if(e->flag & 8){
+ ea = recvp(w->cevent);
+ recvp(w->cevent);
+ na = ea->nb;
+ }else
+ na = 0;
+ if(eq->q1>eq->q0 && eq->nb==0){
+ s = emalloc((eq->q1-eq->q0)*UTFmax+1);
+ winread(w, eq->q0, eq->q1, s);
+ }else
+ s = estrdup(eq->b);
+ if(na){
+ t = emalloc(strlen(s)+1+na+1);
+ sprint(t, "%s %s", s, ea->b);
+ free(s);
+ s = t;
+ }
+ if(!mesgcommand(m, s)) /* send it back */
+ winwriteevent(w, e);
+ break;
+
+ case 'l': /* mouse only */
+ case 'L':
+ buf = nil;
+ eq = e;
+ if(e->flag & 2){
+ e2 = recvp(w->cevent);
+ eq = e2;
+ }
+ s = eq->b;
+ if(eq->q1>eq->q0 && eq->nb==0){
+ buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
+ winread(w, eq->q0, eq->q1, buf);
+ s = buf;
+ }
+ os = s;
+ nopen = 0;
+ do{
+ /* skip mail box name if present */
+ if(strncmp(s, mbox.name, strlen(mbox.name)) == 0)
+ s += strlen(mbox.name);
+ if(strstr(s, "body") != nil){
+ /* strip any known extensions */
+ for(i=0; ports[i].suffix!=nil; i++){
+ j = strlen(ports[i].suffix);
+ if(strlen(s)>j && strcmp(s+strlen(s)-j, ports[i].suffix)==0){
+ s[strlen(s)-j] = '\0';
+ break;
+ }
+ }
+ if(strlen(s)>5 && strcmp(s+strlen(s)-5, "/body")==0)
+ s[strlen(s)-4] = '\0'; /* leave / in place */
+ }
+ nopen += mesgopen(&mbox, mbox.name, s, m, 0, nil);
+ while(*s!=0 && *s++!='\n')
+ ;
+ }while(*s);
+ if(nopen == 0 && e->c1 == 'L')
+ nopen += replytoaddr(w, m, e, os);
+ if(nopen == 0)
+ winwriteevent(w, e);
+ free(buf);
+ break;
+
+ case 'I': /* modify away; we don't care */
+ case 'D':
+ mesgtagpost(m);
+ /* fall through */
+ case 'd':
+ case 'i':
+ break;
+
+ default:
+ goto Unk;
+ }
+ }
+ }
+}
+
+void
+mesgline(Message *m, char *header, char *value)
+{
+ if(strlen(value) > 0)
+ fsprint(m->w->body, "%s: %s\n", header, value);
+}
+
+int
+isprintable(char *type)
+{
+ int i;
+
+ for(i=0; goodtypes[i]!=nil; i++)
+ if(strcmp(type, goodtypes[i])==0)
+ return 1;
+ return 0;
+}
+
+char*
+ext(char *type)
+{
+ int i;
+
+ for(i=0; ports[i].type!=nil; i++)
+ if(strcmp(type, ports[i].type)==0)
+ return ports[i].suffix;
+ return "";
+}
+
+void
+mimedisplay(Message *m, char *name, char *rootdir, Window *w, int fileonly)
+{
+ char *dest;
+
+ if(strcmp(m->disposition, "file")==0 || strlen(m->filename)!=0 || !fileonly){
+ if(strlen(m->filename) == 0)
+ dest = estrstrdup("a", ext(m->type));
+ else
+ dest = estrdup(m->filename);
+ if(m->filename[0] != '/')
+ dest = egrow(estrdup(home), "/", dest);
+ fsprint(w->body, "\t9p read %s/%s/%sbody > %s\n",
+ srvname, mboxname, name, dest);
+ free(dest);
+ }
+}
+
+void
+printheader(char *dir, CFid *fid, char **okheaders)
+{
+ char *s;
+ char *lines[100];
+ int i, j, n;
+
+ s = readfile(dir, "header", nil);
+ if(s == nil)
+ return;
+ n = getfields(s, lines, nelem(lines), 0, "\n");
+ for(i=0; i<n; i++)
+ for(j=0; okheaders[j]; j++)
+ if(cistrncmp(lines[i], okheaders[j], strlen(okheaders[j])) == 0)
+ fsprint(fid, "%s\n", lines[i]);
+ free(s);
+}
+
+void
+mesgload(Message *m, char *rootdir, char *file, Window *w)
+{
+ char *s, *subdir, *name, *dir;
+ Message *mp, *thisone;
+ int n;
+
+ dir = estrstrdup(rootdir, file);
+
+ if(strcmp(m->type, "message/rfc822") != 0){ /* suppress headers of envelopes */
+ if(strlen(m->from) > 0){
+ fsprint(w->body, "From: %s\n", m->from);
+ mesgline(m, "Date", m->date);
+ mesgline(m, "To", m->to);
+ mesgline(m, "CC", m->cc);
+ mesgline(m, "Subject", m->subject);
+ printheader(dir, w->body, extraheaders);
+ }else{
+ printheader(dir, w->body, okheaders);
+ printheader(dir, w->body, extraheaders);
+ }
+ fsprint(w->body, "\n");
+ }
+
+ if(m->level == 1 && m->recursed == 0){
+ m->recursed = 1;
+ readmbox(m, rootdir, m->name);
+ }
+ if(m->head == nil){ /* single part message */
+ if(strcmp(m->type, "text")==0 || strncmp(m->type, "text/", 5)==0){
+ mimedisplay(m, m->name, rootdir, w, 1);
+ s = readbody(m->type, dir, &n);
+ winwritebody(w, s, n);
+ free(s);
+ }else
+ mimedisplay(m, m->name, rootdir, w, 0);
+ }else{
+ /* multi-part message, either multipart/* or message/rfc822 */
+ thisone = nil;
+ if(strcmp(m->type, "multipart/alternative") == 0){
+ thisone = m->head; /* in case we can't find a good one */
+ for(mp=m->head; mp!=nil; mp=mp->next)
+ if(isprintable(mp->type)){
+ thisone = mp;
+ break;
+ }
+ }
+ for(mp=m->head; mp!=nil; mp=mp->next){
+ if(thisone!=nil && mp!=thisone)
+ continue;
+ subdir = estrstrdup(dir, mp->name);
+ name = estrstrdup(file, mp->name);
+ /* skip first element in name because it's already in window name */
+ if(mp != m->head)
+ fsprint(w->body, "\n===> %s (%s) [%s]\n", strchr(name, '/')+1, mp->type, mp->disposition);
+ if(strcmp(mp->type, "text")==0 || strncmp(mp->type, "text/", 5)==0){
+ mimedisplay(mp, name, rootdir, w, 1);
+ printheader(subdir, w->body, okheaders);
+ printheader(subdir, w->body, extraheaders);
+ winwritebody(w, "\n", 1);
+ s = readbody(mp->type, subdir, &n);
+ winwritebody(w, s, n);
+ free(s);
+ }else{
+ if(strncmp(mp->type, "multipart/", 10)==0 || strcmp(mp->type, "message/rfc822")==0){
+ mp->w = w;
+ mesgload(mp, rootdir, name, w);
+ mp->w = nil;
+ }else
+ mimedisplay(mp, name, rootdir, w, 0);
+ }
+ free(name);
+ free(subdir);
+ }
+ }
+ free(dir);
+}
+
+int
+tokenizec(char *str, char **args, int max, char *splitc)
+{
+ int i, na;
+ int intok = 0;
+ char *p;
+
+ if(max <= 0)
+ return 0;
+
+/* if(strchr(str, ',') || strchr(str, '"') || strchr(str, '<') || strchr(str, '(')) */
+/* splitc = ","; */
+ for(na=0; *str != '\0';str++){
+ if(strchr(splitc, *str) == nil){
+ if(intok)
+ continue;
+ args[na++] = str;
+ intok = 1;
+ }else{
+ /* it's a separator/skip character */
+ *str = '\0';
+ if(intok){
+ intok = 0;
+ if(na >= max)
+ break;
+ }
+ }
+ }
+ for(i=0; i<na; i++){
+ while(*args[i] && strchr(" \t\r\n", *args[i]))
+ args[i]++;
+ p = args[i]+strlen(args[i]);
+ while(p>args[i] && strchr(" \t\r\n", *(p-1)))
+ *--p = 0;
+ }
+ return na;
+}
+
+Message*
+mesglookup(Message *mbox, char *name, char *digest)
+{
+ int n;
+ Message *m;
+ char *t;
+
+ if(digest && digest[0]){
+ /* can find exactly */
+ for(m=mbox->head; m!=nil; m=m->next)
+ if(strcmp(digest, m->digest) == 0)
+ break;
+ return m;
+ }
+
+ n = strlen(name);
+ if(n == 0)
+ return nil;
+ if(name[n-1] == '/')
+ t = estrdup(name);
+ else
+ t = estrstrdup(name, "/");
+ for(m=mbox->head; m!=nil; m=m->next)
+ if(strcmp(t, m->name) == 0)
+ break;
+ free(t);
+ return m;
+}
+
+/*
+ * Find plumb port, knowing type is text, given file name (by extension)
+ */
+int
+plumbportbysuffix(char *file)
+{
+ char *suf;
+ int i, nsuf, nfile;
+
+ nfile = strlen(file);
+ for(i=0; ports[i].type!=nil; i++){
+ suf = ports[i].suffix;
+ nsuf = strlen(suf);
+ if(nfile > nsuf)
+ if(cistrncmp(file+nfile-nsuf, suf, nsuf) == 0)
+ return i;
+ }
+ return 0;
+}
+
+/*
+ * Find plumb port using type and file name (by extension)
+ */
+int
+plumbport(char *type, char *file)
+{
+ int i;
+
+ for(i=0; ports[i].type!=nil; i++)
+ if(strncmp(type, ports[i].type, strlen(ports[i].type)) == 0)
+ return i;
+ /* see if it's a text type */
+ for(i=0; goodtypes[i]!=nil; i++)
+ if(strncmp(type, goodtypes[i], strlen(goodtypes[i])) == 0)
+ return plumbportbysuffix(file);
+ return -1;
+}
+
+void
+plumb(Message *m, char *dir)
+{
+ int i;
+ char *port;
+ Plumbmsg *pm;
+
+ if(strlen(m->type) == 0)
+ return;
+ i = plumbport(m->type, m->filename);
+ if(i < 0)
+ fprint(2, "can't find destination for message subpart\n");
+ else{
+ port = ports[i].port;
+ pm = emalloc(sizeof(Plumbmsg));
+ pm->src = estrdup("Mail");
+ if(port)
+ pm->dst = estrdup(port);
+ else
+ pm->dst = nil;
+ pm->wdir = nil;
+ pm->type = estrdup("text");
+ pm->ndata = -1;
+ pm->data = estrstrdup(dir, "body");
+ pm->data = eappend(pm->data, "", ports[i].suffix);
+ if(plumbsendtofid(plumbsendfd, pm) < 0)
+ fprint(2, "error writing plumb message: %r\n");
+ plumbfree(pm);
+ }
+}
+
+int
+mesgopen(Message *mbox, char *dir, char *s, Message *mesg, int plumbed, char *digest)
+{
+ char *t, *u, *v;
+ Message *m;
+ char *direlem[10];
+ int i, ndirelem, reuse;
+
+ /* find white-space-delimited first word */
+ for(t=s; *t!='\0' && !isspace(*t); t++)
+ ;
+ u = emalloc(t-s+1);
+ memmove(u, s, t-s);
+ /* separate it on slashes */
+ ndirelem = tokenizec(u, direlem, nelem(direlem), "/");
+ if(ndirelem <= 0){
+ Error:
+ free(u);
+ return 0;
+ }
+ /*XXX
+ if(plumbed)
+ drawtopwindow();
+ */
+ /* open window for message */
+ m = mesglookup(mbox, direlem[0], digest);
+ if(m == nil)
+ goto Error;
+ if(mesg!=nil && m!=mesg) /* string looked like subpart but isn't part of this message */
+ goto Error;
+ if(m->opened == 0){
+ if(m->w == nil){
+ reuse = 0;
+ m->w = newwindow();
+ }else{
+ reuse = 1;
+ /* re-use existing window */
+ if(winsetaddr(m->w, "0,$", 1)){
+ if(m->w->data == nil)
+ m->w->data = winopenfile(m->w, "data");
+ fswrite(m->w->data, "", 0);
+ }
+ }
+ v = estrstrdup(mbox->name, m->name);
+ winname(m->w, v);
+ free(v);
+ if(!reuse){
+ if(m->deleted)
+ wintagwrite(m->w, "Q Reply all UnDelmesg Save ", 2+6+4+10+5);
+ else
+ wintagwrite(m->w, "Q Reply all Delmesg Save ", 2+6+4+8+5);
+ }
+ threadcreate(mesgctl, m, STACK);
+ winopenbody(m->w, OWRITE);
+ mesgload(m, dir, m->name, m->w);
+ winclosebody(m->w);
+ /* sleep(100); */
+ winclean(m->w);
+ m->opened = 1;
+ if(ndirelem == 1){
+ free(u);
+ return 1;
+ }
+ }
+ if(ndirelem == 1 && plumbport(m->type, m->filename) <= 0){
+ /* make sure dot is visible */
+ ctlprint(m->w->ctl, "show\n");
+ return 0;
+ }
+ /* walk to subpart */
+ dir = estrstrdup(dir, m->name);
+ for(i=1; i<ndirelem; i++){
+ m = mesglookup(m, direlem[i], digest);
+ if(m == nil)
+ break;
+ dir = egrow(dir, m->name, nil);
+ }
+ if(m != nil && plumbport(m->type, m->filename) > 0)
+ plumb(m, dir);
+ free(dir);
+ free(u);
+ return 1;
+}
+
+void
+rewritembox(Window *w, Message *mbox)
+{
+ Message *m, *next;
+ char *deletestr, *t;
+ int nopen;
+
+ deletestr = estrstrdup("delete ", fsname);
+
+ nopen = 0;
+ for(m=mbox->head; m!=nil; m=next){
+ next = m->next;
+ if(m->deleted == 0)
+ continue;
+ if(m->opened){
+ nopen++;
+ continue;
+ }
+ if(m->writebackdel){
+ /* messages deleted by plumb message are not removed again */
+ t = estrdup(m->name);
+ if(strlen(t) > 0)
+ t[strlen(t)-1] = '\0';
+ deletestr = egrow(deletestr, " ", t);
+ }
+ mesgmenudel(w, mbox, m);
+ mesgdel(mbox, m);
+ }
+ if(fswrite(mbox->ctlfd, deletestr, strlen(deletestr)) < 0)
+ fprint(2, "Mail: warning: error removing mail message files: %r\n");
+ free(deletestr);
+ winselect(w, "0", 0);
+ if(nopen == 0)
+ winclean(w);
+ mbox->dirty = 0;
+}
+
+/* name is a full file name, but it might not belong to us */
+Message*
+mesglookupfile(Message *mbox, char *name, char *digest)
+{
+ int k, n;
+
+ k = strlen(name);
+ n = strlen(mbox->name);
+ if(k==0 || strncmp(name, mbox->name, n) != 0){
+/* fprint(2, "Mail: message %s not in this mailbox\n", name); */
+ return nil;
+ }
+ return mesglookup(mbox, name+n, digest);
+}
diff --git a/mail/mkbox b/mail/mkbox
@@ -0,0 +1,11 @@
+#!/bin/rc
+
+for(i){
+ if(! test -f $i){
+ if(cp /dev/null $i){
+ chmod 600 $i
+ chmod +al $i
+ }
+ }
+ if not echo $i already exists
+}
diff --git a/mail/mkfile b/mail/mkfile
@@ -0,0 +1,15 @@
+<$PLAN9/src/mkhdr
+
+TARG=Mail
+OFILES=\
+ html.$O\
+ mail.$O\
+ mesg.$O\
+ reply.$O\
+ util.$O\
+ win.$O
+
+HFILES=dat.h
+
+<$PLAN9/src/mkone
+
diff --git a/mail/readme b/mail/readme
@@ -0,0 +1,57 @@
+The Acme Mail program uses upas/fs to parse the mail box, and then
+presents a file-browser-like user interface to reading and sending
+messages. The Mail window presents each numbered message like the
+contents of a directory presented one per line. If a message has a
+Subject: line, that is shown indented on the following line.
+Multipart MIME-encoded messages are presented in the obvious
+hierarchical format.
+
+Mail uses upas/fs to access the mail box. By default it reads "mbox",
+the standard user mail box. If Mail is given an argument, it is
+passed to upas/fs as the name of the mail box (or upas/fs directory)
+to open.
+
+Although Mail works if the plumber is not running, it's designed to be
+run with plumbing enabled and many of its features work best if it is.
+
+The mailbox window has a few commands: Put writes back the mailbox;
+Mail creates a new window in which to compose a message; and Delmesg
+deletes messages by number. The number may be given as argument or
+indicated by selecting the header line in the mailbox window.
+(Delmesg does not expand null selections, in the interest of safety.)
+
+Clicking the right button on a message number opens it; clicking on
+any of the subparts of a message opens that (and also opens the
+message itself). Each message window has a few commands in the tag
+with obvious names: Reply, Delmsg, etc. "Reply" replies to the single
+sender of the message, "Reply all" or "Replyall" replies to everyone
+in the From:, To:, and CC: lines.
+
+Message parts with recognized MIME types such as image/jpeg are sent
+to the plumber for further dispatch. Acme Mail also listens to
+messages on the seemail and showmail plumbing ports, to report the
+arrival of new messages (highlighting the entry; right-click on the
+entry to open the message) and open them if you right-click on the
+face in the faces window.
+
+When composing a mail message or replying to a message, the first line
+of the text is a list of recipients of the message. To:, and CC:, and BCC:
+lines are interpreted in the usual way. Two other header lines are
+special to Acme Mail:
+ Include: file places a copy of file in the message as an
+ inline MIME attachment.
+ Attach: file places a copy of file in the message as a regular
+ MIME attachment.
+
+Acme Mail uses these conventions when replying to messages,
+constructing headers for the default behavior. You may edit these to
+change behavior. Most important, when replying to a message Mail will
+always Include: the original message; delete that line if you don't
+want to include it.
+
+If the mailbox
+ /mail/box/$user/outgoing
+exists, Acme Mail will save your a copy of your outgoing messages
+there. Attachments are described in the copy but not included.
+
+The -m mntpoint flag specifies a different mount point for /upas/fs.
diff --git a/mail/reply.c b/mail/reply.c
@@ -0,0 +1,580 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <ctype.h>
+#include <plumb.h>
+#include <9pclient.h>
+#include "dat.h"
+
+static int replyid;
+
+int
+quote(Message *m, CFid *fid, char *dir, char *quotetext)
+{
+ char *body, *type;
+ int i, n, nlines;
+ char **lines;
+
+ if(quotetext){
+ body = quotetext;
+ n = strlen(body);
+ type = nil;
+ }else{
+ /* look for first textual component to quote */
+ type = readfile(dir, "type", &n);
+ if(type == nil){
+ print("no type in %s\n", dir);
+ return 0;
+ }
+ if(strncmp(type, "multipart/", 10)==0 || strncmp(type, "message/", 8)==0){
+ dir = estrstrdup(dir, "1/");
+ if(quote(m, fid, dir, nil)){
+ free(type);
+ free(dir);
+ return 1;
+ }
+ free(dir);
+ }
+ if(strncmp(type, "text", 4) != 0){
+ free(type);
+ return 0;
+ }
+ body = readbody(m->type, dir, &n);
+ if(body == nil)
+ return 0;
+ }
+ nlines = 0;
+ for(i=0; i<n; i++)
+ if(body[i] == '\n')
+ nlines++;
+ nlines++;
+ lines = emalloc(nlines*sizeof(char*));
+ nlines = getfields(body, lines, nlines, 0, "\n");
+ /* delete leading and trailing blank lines */
+ i = 0;
+ while(i<nlines && lines[i][0]=='\0')
+ i++;
+ while(i<nlines && lines[nlines-1][0]=='\0')
+ nlines--;
+ while(i < nlines){
+ fsprint(fid, ">%s%s\n", lines[i][0]=='>'? "" : " ", lines[i]);
+ i++;
+ }
+ free(lines);
+ free(body); /* will free quotetext if non-nil */
+ free(type);
+ return 1;
+}
+
+void
+mkreply(Message *m, char *label, char *to, Plumbattr *attr, char *quotetext)
+{
+ char buf[100];
+ CFid *fd;
+ Message *r;
+ char *dir, *t;
+ int quotereply;
+ Plumbattr *a;
+
+ quotereply = (label[0] == 'Q');
+
+ if(quotereply && m && m->replywinid > 0){
+ snprint(buf, sizeof buf, "%d/body", m->replywinid);
+ if((fd = fsopen(acmefs, buf, OWRITE)) != nil){
+ dir = estrstrdup(mbox.name, m->name);
+ quote(m, fd, dir, quotetext);
+ free(dir);
+ return;
+ }
+ }
+
+ r = emalloc(sizeof(Message));
+ r->isreply = 1;
+ if(m != nil)
+ r->replyname = estrdup(m->name);
+ r->next = replies.head;
+ r->prev = nil;
+ if(replies.head != nil)
+ replies.head->prev = r;
+ replies.head = r;
+ if(replies.tail == nil)
+ replies.tail = r;
+ r->name = emalloc(strlen(mbox.name)+strlen(label)+10);
+ sprint(r->name, "%s%s%d", mbox.name, label, ++replyid);
+ r->w = newwindow();
+ if(m)
+ m->replywinid = r->w->id;
+ winname(r->w, r->name);
+ ctlprint(r->w->ctl, "cleartag");
+ wintagwrite(r->w, "fmt Look Post Undo", 4+5+5+4);
+ r->tagposted = 1;
+ threadcreate(mesgctl, r, STACK);
+ winopenbody(r->w, OWRITE);
+ if(to!=nil && to[0]!='\0')
+ fsprint(r->w->body, "%s\n", to);
+ for(a=attr; a; a=a->next)
+ fsprint(r->w->body, "%s: %s\n", a->name, a->value);
+ dir = nil;
+ if(m != nil){
+ dir = estrstrdup(mbox.name, m->name);
+ if(to == nil && attr == nil){
+ /* Reply goes to replyto; Reply all goes to From and To and CC */
+ if(strstr(label, "all") == nil)
+ fsprint(r->w->body, "To: %s\n", m->replyto);
+ else{ /* Replyall */
+ if(strlen(m->from) > 0)
+ fsprint(r->w->body, "To: %s\n", m->from);
+ if(strlen(m->to) > 0)
+ fsprint(r->w->body, "To: %s\n", m->to);
+ if(strlen(m->cc) > 0)
+ fsprint(r->w->body, "CC: %s\n", m->cc);
+ }
+ }
+ if(strlen(m->subject) > 0){
+ t = "Subject: Re: ";
+ if(strlen(m->subject) >= 3)
+ if(tolower(m->subject[0])=='r' && tolower(m->subject[1])=='e' && m->subject[2]==':')
+ t = "Subject: ";
+ fsprint(r->w->body, "%s%s\n", t, m->subject);
+ }
+ if(!quotereply){
+ fsprint(r->w->body, "Include: %sraw\n", dir);
+ free(dir);
+ }
+ }
+ fsprint(r->w->body, "\n");
+ if(m == nil)
+ fsprint(r->w->body, "\n");
+ else if(quotereply){
+ quote(m, r->w->body, dir, quotetext);
+ free(dir);
+ }
+ winclosebody(r->w);
+ if(m==nil && (to==nil || to[0]=='\0'))
+ winselect(r->w, "0", 0);
+ else
+ winselect(r->w, "$", 0);
+ winclean(r->w);
+ windormant(r->w);
+}
+
+void
+delreply(Message *m)
+{
+ if(m->next == nil)
+ replies.tail = m->prev;
+ else
+ m->next->prev = m->prev;
+ if(m->prev == nil)
+ replies.head = m->next;
+ else
+ m->prev->next = m->next;
+ mesgfreeparts(m);
+ free(m);
+}
+
+/* copy argv to stack and free the incoming strings, so we don't leak argument vectors */
+void
+buildargv(char **inargv, char *argv[NARGS+1], char args[NARGCHAR])
+{
+ int i, n;
+ char *s, *a;
+
+ s = args;
+ for(i=0; i<NARGS; i++){
+ a = inargv[i];
+ if(a == nil)
+ break;
+ n = strlen(a)+1;
+ if((s-args)+n >= NARGCHAR) /* too many characters */
+ break;
+ argv[i] = s;
+ memmove(s, a, n);
+ s += n;
+ free(a);
+ }
+ argv[i] = nil;
+}
+
+void
+execproc(void *v)
+{
+ struct Exec *e;
+ int p[2], q[2];
+ char *prog;
+ char *argv[NARGS+1], args[NARGCHAR];
+ int fd[3];
+
+ e = v;
+ p[0] = e->p[0];
+ p[1] = e->p[1];
+ q[0] = e->q[0];
+ q[1] = e->q[1];
+ prog = e->prog; /* known not to be malloc'ed */
+
+ fd[0] = dup(p[0], -1);
+ if(q[0])
+ fd[1] = dup(q[1], -1);
+ else
+ fd[1] = dup(1, -1);
+ fd[2] = dup(2, -2);
+ sendul(e->sync, 1);
+ buildargv(e->argv, argv, args);
+ free(e->argv);
+ chanfree(e->sync);
+ free(e);
+
+ threadexec(nil, fd, prog, argv);
+ close(fd[0]);
+ close(fd[1]);
+ close(fd[2]);
+
+ fprint(2, "Mail: can't exec %s: %r\n", prog);
+ threadexits("can't exec");
+}
+
+enum{
+ ATTACH,
+ BCC,
+ CC,
+ FROM,
+ INCLUDE,
+ TO
+};
+
+char *headers[] = {
+ "attach:",
+ "bcc:",
+ "cc:",
+ "from:",
+ "include:",
+ "to:",
+ nil
+};
+
+int
+whichheader(char *h)
+{
+ int i;
+
+ for(i=0; headers[i]!=nil; i++)
+ if(cistrcmp(h, headers[i]) == 0)
+ return i;
+ return -1;
+}
+
+char *tolist[200];
+char *cclist[200];
+char *bcclist[200];
+int ncc, nbcc, nto;
+char *attlist[200];
+char included[200];
+
+int
+addressed(char *name)
+{
+ int i;
+
+ for(i=0; i<nto; i++)
+ if(strcmp(name, tolist[i]) == 0)
+ return 1;
+ for(i=0; i<ncc; i++)
+ if(strcmp(name, cclist[i]) == 0)
+ return 1;
+ for(i=0; i<nbcc; i++)
+ if(strcmp(name, bcclist[i]) == 0)
+ return 1;
+ return 0;
+}
+
+char*
+skipbl(char *s, char *e)
+{
+ while(s < e){
+ if(*s!=' ' && *s!='\t' && *s!=',')
+ break;
+ s++;
+ }
+ return s;
+}
+
+char*
+findbl(char *s, char *e)
+{
+ while(s < e){
+ if(*s==' ' || *s=='\t' || *s==',')
+ break;
+ s++;
+ }
+ return s;
+}
+
+/*
+ * comma-separate possibly blank-separated strings in line; e points before newline
+ */
+void
+commas(char *s, char *e)
+{
+ char *t;
+
+ /* may have initial blanks */
+ s = skipbl(s, e);
+ while(s < e){
+ s = findbl(s, e);
+ if(s == e)
+ break;
+ t = skipbl(s, e);
+ if(t == e) /* no more words */
+ break;
+ /* patch comma */
+ *s++ = ',';
+ while(s < t)
+ *s++ = ' ';
+ }
+}
+
+int
+print2(int fd, int ofd, char *fmt, ...)
+{
+ int m, n;
+ char *s;
+ va_list arg;
+
+ va_start(arg, fmt);
+ s = vsmprint(fmt, arg);
+ va_end(arg);
+ if(s == nil)
+ return -1;
+ m = strlen(s);
+ n = write(fd, s, m);
+ if(ofd > 0)
+ write(ofd, s, m);
+ return n;
+}
+
+void
+write2(int fd, int ofd, char *buf, int n, int nofrom)
+{
+ char *from, *p;
+ int m;
+
+ write(fd, buf, n);
+
+ if(ofd <= 0)
+ return;
+
+ if(nofrom == 0){
+ write(ofd, buf, n);
+ return;
+ }
+
+ /* need to escape leading From lines to avoid corrupting 'outgoing' mailbox */
+ for(p=buf; *p; p+=m){
+ from = cistrstr(p, "from");
+ if(from == nil)
+ m = n;
+ else
+ m = from - p;
+ if(m > 0)
+ write(ofd, p, m);
+ if(from){
+ if(p==buf || from[-1]=='\n')
+ write(ofd, " ", 1); /* escape with space if From is at start of line */
+ write(ofd, from, 4);
+ m += 4;
+ }
+ n -= m;
+ }
+}
+
+void
+mesgsend(Message *m)
+{
+ char *s, *body, *to;
+ int i, j, h, n, natt, p[2];
+ struct Exec *e;
+ Channel *sync;
+ int first, nfld, delit, ofd;
+ char *copy, *fld[100], *now;
+
+ body = winreadbody(m->w, &n);
+ /* assemble to: list from first line, to: line, and cc: line */
+ nto = 0;
+ natt = 0;
+ ncc = 0;
+ nbcc = 0;
+ first = 1;
+ to = body;
+ for(;;){
+ for(s=to; *s!='\n'; s++)
+ if(*s == '\0'){
+ free(body);
+ return;
+ }
+ if(s++ == to) /* blank line */
+ break;
+ /* make copy of line to tokenize */
+ copy = emalloc(s-to);
+ memmove(copy, to, s-to);
+ copy[s-to-1] = '\0';
+ nfld = tokenizec(copy, fld, nelem(fld), ", \t");
+ if(nfld == 0){
+ free(copy);
+ break;
+ }
+ n -= s-to;
+ switch(h = whichheader(fld[0])){
+ case TO:
+ case FROM:
+ delit = 1;
+ commas(to+strlen(fld[0]), s-1);
+ for(i=1; i<nfld && nto<nelem(tolist); i++)
+ if(!addressed(fld[i]))
+ tolist[nto++] = estrdup(fld[i]);
+ break;
+ case BCC:
+ delit = 1;
+ commas(to+strlen(fld[0]), s-1);
+ for(i=1; i<nfld && nbcc<nelem(bcclist); i++)
+ if(!addressed(fld[i]))
+ bcclist[nbcc++] = estrdup(fld[i]);
+ break;
+ case CC:
+ delit = 1;
+ commas(to+strlen(fld[0]), s-1);
+ for(i=1; i<nfld && ncc<nelem(cclist); i++)
+ if(!addressed(fld[i]))
+ cclist[ncc++] = estrdup(fld[i]);
+ break;
+ case ATTACH:
+ case INCLUDE:
+ delit = 1;
+ for(i=1; i<nfld && natt<nelem(attlist); i++){
+ attlist[natt] = estrdup(fld[i]);
+ included[natt++] = (h == INCLUDE);
+ }
+ break;
+ default:
+ if(first){
+ delit = 1;
+ for(i=0; i<nfld && nto<nelem(tolist); i++)
+ tolist[nto++] = estrdup(fld[i]);
+ }else /* ignore it */
+ delit = 0;
+ break;
+ }
+ if(delit){
+ /* delete line from body */
+ memmove(to, s, n+1);
+ }else
+ to = s;
+ free(copy);
+ first = 0;
+ }
+
+ ofd = open(outgoing, OWRITE|OCEXEC); /* no error check necessary */
+ if(ofd > 0){
+ /* From dhog Fri Aug 24 22:13:00 EDT 2001 */
+ now = ctime(time(0));
+ seek(ofd, 0, 2);
+ fprint(ofd, "From %s %s", user, now);
+ fprint(ofd, "From: %s\n", user);
+ fprint(ofd, "Date: %s", now);
+ for(i=0; i<natt; i++)
+ if(included[i])
+ fprint(ofd, "Include: %s\n", attlist[i]);
+ else
+ fprint(ofd, "Attach: %s\n", attlist[i]);
+ /* needed because mail is by default Latin-1 */
+ fprint(ofd, "Content-Type: text/plain; charset=\"UTF-8\"\n");
+ fprint(ofd, "Content-Transfer-Encoding: 8bit\n");
+ }
+
+ e = emalloc(sizeof(struct Exec));
+ if(pipe(p) < 0)
+ error("can't create pipe: %r");
+ e->p[0] = p[0];
+ e->p[1] = p[1];
+ e->prog = unsharp("#9/bin/upas/marshal");
+ e->argv = emalloc((1+1+2+4*natt+1)*sizeof(char*));
+ e->argv[0] = estrdup("marshal");
+ e->argv[1] = estrdup("-8");
+ j = 2;
+ if(m->replyname){
+ e->argv[j++] = estrdup("-R");
+ e->argv[j++] = estrstrdup(mbox.name, m->replyname);
+ }
+ for(i=0; i<natt; i++){
+ if(included[i])
+ e->argv[j++] = estrdup("-A");
+ else
+ e->argv[j++] = estrdup("-a");
+ e->argv[j++] = estrdup(attlist[i]);
+ }
+ sync = chancreate(sizeof(int), 0);
+ e->sync = sync;
+ proccreate(execproc, e, EXECSTACK);
+ recvul(sync);
+ /* close(p[0]); */
+
+ /* using marshal -8, so generate rfc822 headers */
+ if(nto > 0){
+ print2(p[1], ofd, "To: ");
+ for(i=0; i<nto-1; i++)
+ print2(p[1], ofd, "%s, ", tolist[i]);
+ print2(p[1], ofd, "%s\n", tolist[i]);
+ }
+ if(ncc > 0){
+ print2(p[1], ofd, "CC: ");
+ for(i=0; i<ncc-1; i++)
+ print2(p[1], ofd, "%s, ", cclist[i]);
+ print2(p[1], ofd, "%s\n", cclist[i]);
+ }
+ if(nbcc > 0){
+ print2(p[1], ofd, "BCC: ");
+ for(i=0; i<nbcc-1; i++)
+ print2(p[1], ofd, "%s, ", bcclist[i]);
+ print2(p[1], ofd, "%s\n", bcclist[i]);
+ }
+
+ i = strlen(body);
+ if(i > 0)
+ write2(p[1], ofd, body, i, 1);
+
+ /* guarantee a blank line, to ensure attachments are separated from body */
+ if(i==0 || body[i-1]!='\n')
+ write2(p[1], ofd, "\n\n", 2, 0);
+ else if(i>1 && body[i-2]!='\n')
+ write2(p[1], ofd, "\n", 1, 0);
+
+ /* these look like pseudo-attachments in the "outgoing" box */
+ if(ofd>0 && natt>0){
+ for(i=0; i<natt; i++)
+ if(included[i])
+ fprint(ofd, "=====> Include: %s\n", attlist[i]);
+ else
+ fprint(ofd, "=====> Attach: %s\n", attlist[i]);
+ }
+ if(ofd > 0)
+ write(ofd, "\n", 1);
+
+ for(i=0; i<natt; i++)
+ free(attlist[i]);
+ close(ofd);
+ close(p[1]);
+ free(body);
+
+ if(m->replyname != nil)
+ mesgmenumark(mbox.w, m->replyname, "\t[replied]");
+ if(m->name[0] == '/')
+ s = estrdup(m->name);
+ else
+ s = estrstrdup(mbox.name, m->name);
+ s = egrow(s, "-R", nil);
+ winname(m->w, s);
+ free(s);
+ winclean(m->w);
+ /* mark message unopened because it's no longer the original message */
+ m->opened = 0;
+}
diff --git a/mail/util.c b/mail/util.c
@@ -0,0 +1,106 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <plumb.h>
+#include <9pclient.h>
+#include "dat.h"
+
+void*
+emalloc(uint n)
+{
+ void *p;
+
+ p = malloc(n);
+ if(p == nil)
+ error("can't malloc: %r");
+ memset(p, 0, n);
+ setmalloctag(p, getcallerpc(&n));
+ return p;
+}
+
+void*
+erealloc(void *p, uint n)
+{
+ p = realloc(p, n);
+ if(p == nil)
+ error("can't realloc: %r");
+ setmalloctag(p, getcallerpc(&n));
+ return p;
+}
+
+char*
+estrdup(char *s)
+{
+ char *t;
+
+ t = emalloc(strlen(s)+1);
+ strcpy(t, s);
+ return t;
+}
+
+char*
+estrstrdup(char *s, char *t)
+{
+ char *u;
+
+ u = emalloc(strlen(s)+strlen(t)+1);
+ strcpy(u, s);
+ strcat(u, t);
+ return u;
+}
+
+char*
+eappend(char *s, char *sep, char *t)
+{
+ char *u;
+
+ if(t == nil)
+ u = estrstrdup(s, sep);
+ else{
+ u = emalloc(strlen(s)+strlen(sep)+strlen(t)+1);
+ strcpy(u, s);
+ strcat(u, sep);
+ strcat(u, t);
+ }
+ free(s);
+ return u;
+}
+
+char*
+egrow(char *s, char *sep, char *t)
+{
+ s = eappend(s, sep, t);
+ free(t);
+ return s;
+}
+
+void
+error(char *fmt, ...)
+{
+ Fmt f;
+ char buf[64];
+ va_list arg;
+
+ fmtfdinit(&f, 2, buf, sizeof buf);
+ fmtprint(&f, "Mail: ");
+ va_start(arg, fmt);
+ fmtvprint(&f, fmt, arg);
+ va_end(arg);
+ fmtprint(&f, "\n");
+ fmtfdflush(&f);
+ threadexitsall(fmt);
+}
+
+void
+ctlprint(CFid *fd, char *fmt, ...)
+{
+ int n;
+ va_list arg;
+
+ va_start(arg, fmt);
+ n = fsvprint(fd, fmt, arg);
+ va_end(arg);
+ if(n <= 0)
+ error("control file write error: %r");
+}
diff --git a/mail/win.c b/mail/win.c
@@ -0,0 +1,379 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <plumb.h>
+#include <9pclient.h>
+#include "dat.h"
+
+Window*
+newwindow(void)
+{
+ char buf[12];
+ Window *w;
+
+ w = emalloc(sizeof(Window));
+ w->ctl = fsopen(acmefs, "new/ctl", ORDWR|OCEXEC);
+ if(w->ctl == nil || fsread(w->ctl, buf, 12)!=12)
+ error("can't open window ctl file: %r");
+
+ w->id = atoi(buf);
+ w->event = winopenfile(w, "event");
+ w->addr = nil; /* will be opened when needed */
+ w->body = nil;
+ w->data = nil;
+ w->cevent = chancreate(sizeof(Event*), 0);
+ w->ref = 1;
+ return w;
+}
+
+void
+winincref(Window *w)
+{
+ qlock(&w->lk);
+ ++w->ref;
+ qunlock(&w->lk);
+}
+
+void
+windecref(Window *w)
+{
+ qlock(&w->lk);
+ if(--w->ref > 0){
+ qunlock(&w->lk);
+ return;
+ }
+ fsclose(w->event);
+ chanfree(w->cevent);
+ free(w);
+}
+
+void
+winsetdump(Window *w, char *dir, char *cmd)
+{
+ if(dir != nil)
+ ctlprint(w->ctl, "dumpdir %s\n", dir);
+ if(cmd != nil)
+ ctlprint(w->ctl, "dump %s\n", cmd);
+}
+
+void
+wineventproc(void *v)
+{
+ Window *w;
+ int i;
+
+ w = v;
+ for(i=0; ; i++){
+ if(i >= NEVENT)
+ i = 0;
+ wingetevent(w, &w->e[i]);
+ sendp(w->cevent, &w->e[i]);
+ }
+}
+
+static CFid*
+winopenfile1(Window *w, char *f, int m)
+{
+ char buf[64];
+ CFid* fd;
+
+ sprint(buf, "%d/%s", w->id, f);
+ fd = fsopen(acmefs, buf, m|OCEXEC);
+ if(fd == nil)
+ error("can't open window file %s: %r", f);
+ return fd;
+}
+
+CFid*
+winopenfile(Window *w, char *f)
+{
+ return winopenfile1(w, f, ORDWR);
+}
+
+void
+wintagwrite(Window *w, char *s, int n)
+{
+ CFid* fid;
+
+ fid = winopenfile(w, "tag");
+ if(fswrite(fid, s, n) != n)
+ error("tag write: %r");
+ fsclose(fid);
+}
+
+void
+winname(Window *w, char *s)
+{
+ int len;
+ char *ns, *sp;
+ Rune r = L'␣'; /* visible space */
+
+ len = 0;
+ ns = emalloc(strlen(s)*runelen(r) + 1);
+ for(sp = s; *sp != '\0'; sp++, len++){
+ if(isspace(*sp)){
+ len += runetochar(ns+len, &r)-1;
+ continue;
+ }
+ *(ns+len) = *sp;
+ }
+ ctlprint(w->ctl, "name %s\n", ns);
+ free(ns);
+ return;
+}
+
+void
+winopenbody(Window *w, int mode)
+{
+ char buf[256];
+ CFid* fid;
+
+ sprint(buf, "%d/body", w->id);
+ fid = fsopen(acmefs, buf, mode|OCEXEC);
+ w->body = fid;
+ if(w->body == nil)
+ error("can't open window body file: %r");
+}
+
+void
+winclosebody(Window *w)
+{
+ if(w->body != nil){
+ fsclose(w->body);
+ w->body = nil;
+ }
+}
+
+void
+winwritebody(Window *w, char *s, int n)
+{
+ if(w->body == nil)
+ winopenbody(w, OWRITE);
+ if(fswrite(w->body, s, n) != n)
+ error("write error to window: %r");
+}
+
+int
+wingetec(Window *w)
+{
+ if(w->nbuf == 0){
+ w->nbuf = fsread(w->event, w->buf, sizeof w->buf);
+ if(w->nbuf <= 0){
+ /* probably because window has exited, and only called by wineventproc, so just shut down */
+ windecref(w);
+ threadexits(nil);
+ }
+ w->bufp = w->buf;
+ }
+ w->nbuf--;
+ return *w->bufp++;
+}
+
+int
+wingeten(Window *w)
+{
+ int n, c;
+
+ n = 0;
+ while('0'<=(c=wingetec(w)) && c<='9')
+ n = n*10+(c-'0');
+ if(c != ' ')
+ error("event number syntax");
+ return n;
+}
+
+int
+wingeter(Window *w, char *buf, int *nb)
+{
+ Rune r;
+ int n;
+
+ r = wingetec(w);
+ buf[0] = r;
+ n = 1;
+ if(r >= Runeself) {
+ while(!fullrune(buf, n))
+ buf[n++] = wingetec(w);
+ chartorune(&r, buf);
+ }
+ *nb = n;
+ return r;
+}
+
+void
+wingetevent(Window *w, Event *e)
+{
+ int i, nb;
+
+ e->c1 = wingetec(w);
+ e->c2 = wingetec(w);
+ e->q0 = wingeten(w);
+ e->q1 = wingeten(w);
+ e->flag = wingeten(w);
+ e->nr = wingeten(w);
+ if(e->nr > EVENTSIZE)
+ error("event string too long");
+ e->nb = 0;
+ for(i=0; i<e->nr; i++){
+ e->r[i] = wingeter(w, e->b+e->nb, &nb);
+ e->nb += nb;
+ }
+ e->r[e->nr] = 0;
+ e->b[e->nb] = 0;
+ if(wingetec(w) != '\n')
+ error("event syntax error");
+}
+
+void
+winwriteevent(Window *w, Event *e)
+{
+ fsprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
+}
+
+void
+winread(Window *w, uint q0, uint q1, char *data)
+{
+ int m, n, nr;
+ char buf[256];
+
+ if(w->addr == nil)
+ w->addr = winopenfile(w, "addr");
+ if(w->data == nil)
+ w->data = winopenfile(w, "data");
+ m = q0;
+ while(m < q1){
+ n = sprint(buf, "#%d", m);
+ if(fswrite(w->addr, buf, n) != n)
+ error("error writing addr: %r");
+ n = fsread(w->data, buf, sizeof buf);
+ if(n <= 0)
+ error("reading data: %r");
+ nr = utfnlen(buf, n);
+ while(m+nr >q1){
+ do; while(n>0 && (buf[--n]&0xC0)==0x80);
+ --nr;
+ }
+ if(n == 0)
+ break;
+ memmove(data, buf, n);
+ data += n;
+ *data = 0;
+ m += nr;
+ }
+}
+
+void
+windormant(Window *w)
+{
+ if(w->addr != nil){
+ fsclose(w->addr);
+ w->addr = nil;
+ }
+ if(w->body != nil){
+ fsclose(w->body);
+ w->body = nil;
+ }
+ if(w->data != nil){
+ fsclose(w->data);
+ w->data = nil;
+ }
+}
+
+
+int
+windel(Window *w, int sure)
+{
+ if(sure)
+ fswrite(w->ctl, "delete\n", 7);
+ else if(fswrite(w->ctl, "del\n", 4) != 4)
+ return 0;
+ /* event proc will die due to read error from event file */
+ windormant(w);
+ fsclose(w->ctl);
+ w->ctl = nil;
+ return 1;
+}
+
+void
+winclean(Window *w)
+{
+ ctlprint(w->ctl, "clean\n");
+}
+
+int
+winsetaddr(Window *w, char *addr, int errok)
+{
+ if(w->addr == nil)
+ w->addr = winopenfile(w, "addr");
+ if(fswrite(w->addr, addr, strlen(addr)) < 0){
+ if(!errok)
+ error("error writing addr(%s): %r", addr);
+ return 0;
+ }
+ return 1;
+}
+
+int
+winselect(Window *w, char *addr, int errok)
+{
+ if(winsetaddr(w, addr, errok)){
+ ctlprint(w->ctl, "dot=addr\n");
+ return 1;
+ }
+ return 0;
+}
+
+char*
+winreadbody(Window *w, int *np) /* can't use readfile because acme doesn't report the length */
+{
+ char *s;
+ int m, na, n;
+
+ if(w->body != nil)
+ winclosebody(w);
+ winopenbody(w, OREAD);
+ s = nil;
+ na = 0;
+ n = 0;
+ for(;;){
+ if(na < n+512){
+ na += 1024;
+ s = realloc(s, na+1);
+ }
+ m = fsread(w->body, s+n, na-n);
+ if(m <= 0)
+ break;
+ n += m;
+ }
+ s[n] = 0;
+ winclosebody(w);
+ *np = n;
+ return s;
+}
+
+char*
+winselection(Window *w)
+{
+ int m, n;
+ char *buf;
+ char tmp[256];
+ CFid* fid;
+
+ fid = winopenfile1(w, "rdsel", OREAD);
+ if(fid == nil)
+ error("can't open rdsel: %r");
+ n = 0;
+ buf = nil;
+ for(;;){
+ m = fsread(fid, tmp, sizeof tmp);
+ if(m <= 0)
+ break;
+ buf = erealloc(buf, n+m+1);
+ memmove(buf+n, tmp, m);
+ n += m;
+ buf[n] = '\0';
+ }
+ fsclose(fid);
+ return buf;
+}
diff --git a/mkfile b/mkfile
@@ -0,0 +1,53 @@
+<$PLAN9/src/mkhdr
+
+TARG=acme
+DIRS=mail
+
+OFILES=\
+ acme.$O\
+ addr.$O\
+ buff.$O\
+ cols.$O\
+ disk.$O\
+ ecmd.$O\
+ edit.$O\
+ elog.$O\
+ exec.$O\
+ file.$O\
+ fsys.$O\
+ logf.$O\
+ look.$O\
+ regx.$O\
+ rows.$O\
+ scrl.$O\
+ text.$O\
+ time.$O\
+ util.$O\
+ wind.$O\
+ xfid.$O\
+ dat.$O\
+
+HFILES=dat.h\
+ edit.h\
+ fns.h\
+
+<$PLAN9/src/mkone
+<$PLAN9/src/mkdirs
+
+edit.$O ecmd.$O elog.$O: edit.h
+
+likeplan9:V:
+ mkdir -p likeplan9
+ rm -f likeplan9/*
+ for i in *.c
+ do
+ 9 sed 's/->(fcall|lk|b|fr|ref|m|u|u1)\./->/g;
+ s/\.(fcall|lk|b|fr|ref|m|u|u1)([^a-zA-Z0-9_])/\2/g
+ s/&(([a-zA-Z0-9_]|->|\.)*)->(fcall|lk|b|fr|ref|m|u|u1)([^a-zA-Z0-9_])/\1\4/g
+ s/range\(([^,()]+), ([^,()]+)\)/(Range){\1, \2}/g
+ ' $i >likeplan9/$i
+ done
+
+diffplan9:V:
+ mk likeplan9
+ 9 diff -n plan9 likeplan9 | sed 's;likeplan9/;;'
diff --git a/regx.c b/regx.c
@@ -0,0 +1,843 @@
+#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 <libsec.h>
+#include "dat.h"
+#include "fns.h"
+
+Rangeset sel;
+Rune *lastregexp;
+
+#undef class
+#define class regxclass /* some systems declare "class" in system headers */
+
+/*
+ * Machine Information
+ */
+typedef struct Inst Inst;
+struct Inst
+{
+ uint type; /* < OPERATOR ==> 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 127
+
+Ilist *tl, *nl; /* This list, next list */
+Ilist list[2][NLIST+1]; /* +1 for trailing null */
+static Rangeset sempty;
+
+/*
+ * Actions and Tokens
+ *
+ * 0x10000xx are operators, value == precedence
+ * 0x20000xx are tokens, i.e. operands for operators
+ */
+#define OPERATOR 0x1000000 /* Bit set in all operators */
+#define START (OPERATOR+0) /* Start, used for marker on stack */
+#define RBRA (OPERATOR+1) /* Right bracket, ) */
+#define LBRA (OPERATOR+2) /* Left bracket, ( */
+#define OR (OPERATOR+3) /* Alternation, | */
+#define CAT (OPERATOR+4) /* Concatentation, implicit operator */
+#define STAR (OPERATOR+5) /* Closure, * */
+#define PLUS (OPERATOR+6) /* a+ == aa* */
+#define QUEST (OPERATOR+7) /* a? == a|nothing, i.e. 0 or 1 a's */
+#define ANY 0x2000000 /* Any character but newline, . */
+#define NOP (ANY+1) /* No operation, internal use only */
+#define BOL (ANY+2) /* Beginning of line, ^ */
+#define EOL (ANY+3) /* End of line, $ */
+#define CCLASS (ANY+4) /* Character class, [] */
+#define NCCLASS (ANY+5) /* Negated character class, [^] */
+#define END (ANY+0x77) /* Terminate: match found */
+
+#define ISATOR OPERATOR
+#define ISAND ANY
+
+#define QUOTED 0x4000000 /* Bit set for \-ed lex characters */
+
+/*
+ * 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;
+
+int 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);
+ chansetname(rechan, "rechan");
+ 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++|QUOTED;
+ }
+ 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] = Runemax;
+ classp[n+1] = c1;
+ classp[n+2] = c2;
+ n += 3;
+ }else
+ classp[n++] = c1 & ~QUOTED;
+ }
+ 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 == Runemax){
+ 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.
+ */
+int
+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 0; /* It's already there */
+ }
+ }
+ p->inst = inst;
+ p->se= *sep;
+ (p+1)->inst = nil;
+ return 1;
+}
+
+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 */
+ sempty.r[0].q0 = p;
+ if(addinst(tl, startinst, &sempty))
+ if(++ntl >= NLIST){
+ Overflow:
+ warning(nil, "regexp list overflow\n");
+ sel.r[0].q0 = -1;
+ goto Return;
+ }
+ }
+ /* 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(addinst(nl, inst->u1.next, &tlp->se))
+ if(++nnl >= NLIST)
+ goto Overflow;
+ }
+ 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(addinst(tlp, inst->u.right, &tlp->se))
+ if(++ntl >= NLIST)
+ goto Overflow;
+ /* 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 */
+ /* the minus is so the optimizations in addinst work */
+ sempty.r[0].q0 = -p;
+ if(addinst(tl, bstartinst, &sempty))
+ if(++ntl >= NLIST){
+ Overflow:
+ warning(nil, "regexp list overflow\n");
+ sel.r[0].q0 = -1;
+ goto Return;
+ }
+ }
+ /* 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(addinst(nl, inst->u1.next, &tlp->se))
+ if(++nnl >= NLIST)
+ goto Overflow;
+ }
+ 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(addinst(tl, inst->u.right, &tlp->se))
+ if(++ntl >= NLIST)
+ goto Overflow;
+ /* 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/rows.c b/rows.c
@@ -0,0 +1,830 @@
+#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 <libsec.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-Border, 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, deltax;
+ Rectangle or, r1, r2;
+ Column *c;
+
+ or = row->r;
+ deltax = r.min.x - or.min.x;
+ row->r = r;
+ r1 = r;
+ r1.max.y = r1.min.y + font->height;
+ textresize(&row->tag, r1, TRUE);
+ 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;
+ /* the test should not be necessary, but guarantee we don't lose a pixel */
+ if(i == row->ncol-1)
+ r1.max.x = r.max.x;
+ else
+ r1.max.x = (c->r.max.x-or.min.x)*Dx(r)/Dx(or) + deltax;
+ 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();
+ setcursor2(mousectl, &boxcursor, &boxcursor2);
+ 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:
+ 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;
+ }
+ if(i == 0)
+ 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);
+ row->ncol--;
+ memmove(row->col+i, row->col+i+1, (row->ncol-i)*sizeof(Column*));
+ 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;
+
+ if(r == 0)
+ r = Runeerror;
+
+ 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);
+ /* Expand tag if necessary */
+ if(t->what == Tag){
+ t->w->tagsafe = FALSE;
+ if(r == '\n')
+ t->w->tagexpand = TRUE;
+ winresize(w, w->r, TRUE, TRUE);
+ }
+ 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, start, dumped;
+ uint q0, q1;
+ Biobuf *b;
+ char *buf, *a, *fontname, *fontfmt, *fontnamelo, *fontnamehi;
+ 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, "%11.7f", 100.0*(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;
+ }
+ m = min(RBUFSIZE, row->tag.file->b.nc);
+ bufread(&row->tag.file->b, 0, r, m);
+ n = 0;
+ while(n<m && r[n]!='\n')
+ n++;
+ Bprint(b, "w %.*S\n", n, r);
+ for(i=0; i<row->ncol; i++){
+ c = row->col[i];
+ m = min(RBUFSIZE, c->tag.file->b.nc);
+ bufread(&c->tag.file->b, 0, r, m);
+ n = 0;
+ while(n<m && r[n]!='\n')
+ n++;
+ Bprint(b, "c%11d %.*S\n", i, n, r);
+ }
+ 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;
+ }
+ fontfmt = "%s";
+ fontnamelo = "";
+ fontnamehi = nil;
+ if(t->reffont->f != font){
+ fontnamelo = t->reffont->f->lodpi->name;
+ if(t->reffont->f->hidpi != nil){
+ fontfmt = "%s,%s";
+ fontnamehi = t->reffont->f->hidpi->name;
+ }
+ }
+ fontname = smprint(fontfmt, fontnamelo, fontnamehi);
+ 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 %11.7f %s\n", i, t->file->dumpid,
+ w->body.q0, w->body.q1,
+ 100.0*(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 %11.7f %s\n", i, t->file->dumpid,
+ 0, 0,
+ 100.0*(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 %11.7f %s\n", i, w->id,
+ w->body.q0, w->body.q1,
+ 100.0*(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 %11.7f %11d %s\n", i, j,
+ w->body.q0, w->body.q1,
+ 100.0*(w->r.min.y-c->r.min.y)/Dy(c->r),
+ w->body.file->b.nc, fontname);
+ }
+ free(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) {
+ start = n;
+ while(n<m && r[n]!='\n')
+ n++;
+ Bprint(b, "%.*S", n-start, r+start);
+ if(n<m) {
+ Bputc(b, 0xff); // \n in tag becomes 0xff byte (invalid UTF)
+ n++;
+ }
+ }
+ Bprint(b, "\n");
+ 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){
+ free(fontnames[i]);
+ fontnames[i] = estrdup(l);
+ }
+ }
+ Return:
+ Bterm(b);
+}
+
+int
+rowload(Row *row, char *file, int initing)
+{
+ int i, j, line, y, nr, nfontr, n, ns, ndumped, dumpid, x, fd, done;
+ double percent;
+ Biobuf *b, *bout;
+ char *buf, *l, *t, *fontname;
+ Rune *r, *fontr;
+ int rune;
+ 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, 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 = atof(l+i*12);
+ if(percent<0 || percent>=100)
+ goto Rescue2;
+ x = row->r.min.x+percent*Dx(row->r)/100+0.5;
+ if(i < row->ncol){
+ if(i == 0)
+ continue;
+ c1 = row->col[i-1];
+ c2 = row->col[i];
+ r1 = c1->r;
+ r2 = c2->r;
+ if(x<Border)
+ x = Border;
+ r1.max.x = x-Border;
+ r2.min.x = x;
+ 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-Border;
+ r2.max.x = x;
+ draw(screen, r2, display->black, nil, ZP);
+ }
+ if(i >= row->ncol)
+ rowadd(row, nil, x);
+ }
+ done = 0;
+ while(!done){
+ l = rdline(b, &line);
+ if(l == nil)
+ break;
+ switch(l[0]){
+ case 'c':
+ l[Blinelen(b)-1] = 0;
+ i = atoi(l+1+0*12);
+ r = bytetorune(l+1*12, &nr);
+ ns = -1;
+ for(n=0; n<nr; n++){
+ if(r[n] == '/')
+ ns = n;
+ if(r[n] == ' ')
+ break;
+ }
+ textdelete(&row->col[i]->tag, 0, row->col[i]->tag.file->b.nc, TRUE);
+ textinsert(&row->col[i]->tag, 0, r+n+1, nr-(n+1), TRUE);
+ free(r);
+ break;
+ case 'w':
+ l[Blinelen(b)-1] = 0;
+ r = bytetorune(l+2, &nr);
+ ns = -1;
+ for(n=0; n<nr; n++){
+ if(r[n] == '/')
+ ns = n;
+ if(r[n] == ' ')
+ break;
+ }
+ textdelete(&row->tag, 0, row->tag.file->b.nc, TRUE);
+ textinsert(&row->tag, 0, r, nr, TRUE);
+ free(r);
+ break;
+ default:
+ done = 1;
+ break;
+ }
+ }
+ for(;;){
+ 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() */
+ goto Nextline;
+ 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 = atof(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+0.5;
+ 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)
+ goto Nextline;
+ w->dumpid = j;
+ l = rdline(b, &line);
+ if(l == nil)
+ goto Rescue2;
+ l[Blinelen(b)-1] = 0;
+ /* convert 0xff in multiline tag back to \n */
+ for(i = 0; l[i] != 0; i++)
+ if((uchar)l[i] == 0xff)
+ l[i] = '\n';
+ 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);
+ 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, 0600);
+ if(fd < 0){
+ free(r);
+ 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 == Beof){
+ free(r);
+ Bterm(bout);
+ free(bout);
+ close(fd);
+ remove(buf);
+ goto Rescue2;
+ }
+ Bputrune(bout, rune);
+ }
+ Bterm(bout);
+ free(bout);
+ textload(&w->body, 0, buf, 1);
+ remove(buf);
+ 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);
+ }
+ free(r);
+ 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));
+ xfidlog(w, "new");
+Nextline:
+ l = rdline(b, &line);
+ }
+ Bterm(b);
+ fbuffree(buf);
+ return TRUE;
+
+Rescue2:
+ warning(nil, "bad load file %s:%d\n", file, line);
+ Bterm(b);
+Rescue1:
+ fbuffree(buf);
+ return FALSE;
+}
+
+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/scrl.c b/scrl.c
@@ -0,0 +1,159 @@
+#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 <libsec.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);
+ 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;
+ p0 = (vlong)t->file->b.nc*(y-s.min.y)/h;
+ if(p0 >= t->q1)
+ p0 = textbacknl(t, p0, 2);
+ 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/text.c b/text.c
@@ -0,0 +1,1664 @@
+#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 <libsec.h>
+#include <complete.h>
+#include "dat.h"
+#include "fns.h"
+
+Image *tagcols[NCOL];
+Image *textcols[NCOL];
+static Rune Ldot[] = { '.', 0 };
+
+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+Scrollgap; /* back fill to scroll bar */
+ if(!t->fr.noredraw)
+ 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 keepextra)
+{
+ int odx;
+
+ if(Dy(r) <= 0)
+ r.max.y = r.min.y;
+ else if(!keepextra)
+ r.max.y -= Dy(r)%t->fr.font->height;
+ 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);
+ if(keepextra && t->fr.r.max.y < t->all.max.y && !t->fr.noredraw){
+ /* draw background in bottom fringe of window */
+ r.min.x -= Scrollgap;
+ r.min.y = t->fr.r.max.y;
+ r.max.y = t->all.max.y;
+ draw(screen, r, t->fr.cols[BACK], nil, ZP);
+ }
+ return t->all.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++;
+ }
+}
+
+int
+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;
+ DigestState *h;
+
+ if(t->ncache!=0 || t->file->b.nc || t->w==nil || t!=&t->w->body)
+ error("text.load");
+ if(t->w->isdir && t->file->nname==0){
+ warning(nil, "empty directory name");
+ return -1;
+ }
+ if(ismtpt(file)){
+ warning(nil, "will not open self mount point %s\n", file);
+ return -1;
+ }
+ fd = open(file, OREAD);
+ if(fd < 0){
+ warning(nil, "can't open %s: %r\n", file);
+ return -1;
+ }
+ d = dirfstat(fd);
+ if(d == nil){
+ warning(nil, "can't fstat %s: %r\n", file);
+ goto Rescue;
+ }
+ nulls = FALSE;
+ h = nil;
+ 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->nname > 0 && 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;
+ if(q0 == 0)
+ h = sha1(nil, 0, nil, nil);
+ q1 = q0 + fileload(t->file, q0, fd, &nulls, h);
+ }
+ if(setqid){
+ if(h != nil) {
+ sha1(nil, 0, t->file->sha1, h);
+ h = nil;
+ } else {
+ memset(t->file->sha1, 0, sizeof t->file->sha1);
+ }
+ 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, TRUE);
+ 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 -1;
+}
+
+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->iq1)
+ t->iq1 += n;
+ 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
+typecommit(Text *t)
+{
+ if(t->w != nil)
+ wincommit(t->w, t);
+ else
+ textcommit(t, TRUE);
+}
+
+void
+textfill(Text *t)
+{
+ Rune *rp;
+ int i, n, m, nl;
+
+ if(t->fr.lastlinefull || t->nofill)
+ return;
+ if(t->ncache > 0)
+ typecommit(t);
+ 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->iq1)
+ t->iq1 -= min(n, t->iq1-q0);
+ 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;
+}
+
+int
+textfilewidth(Text *t, uint q0, int oneelement)
+{
+ uint q;
+ Rune r;
+
+ q = q0;
+ while(q > 0){
+ r = textreadc(t, q-1);
+ if(r <= ' ')
+ break;
+ if(oneelement && r=='/')
+ break;
+ --q;
+ }
+ return q0-q;
+}
+
+Rune*
+textcomplete(Text *t)
+{
+ int i, nstr, npath;
+ uint q;
+ Rune tmp[200];
+ Rune *str, *path;
+ Rune *rp;
+ Completion *c;
+ char *s, *dirs;
+ Runestr dir;
+
+ /* control-f: filename completion; works back to white space or / */
+ if(t->q0<t->file->b.nc && textreadc(t, t->q0)>' ') /* must be at end of word */
+ return nil;
+ nstr = textfilewidth(t, t->q0, TRUE);
+ str = runemalloc(nstr);
+ npath = textfilewidth(t, t->q0-nstr, FALSE);
+ path = runemalloc(npath);
+
+ c = nil;
+ rp = nil;
+ dirs = nil;
+
+ q = t->q0-nstr;
+ for(i=0; i<nstr; i++)
+ str[i] = textreadc(t, q++);
+ q = t->q0-nstr-npath;
+ for(i=0; i<npath; i++)
+ path[i] = textreadc(t, q++);
+ /* is path rooted? if not, we need to make it relative to window path */
+ if(npath>0 && path[0]=='/')
+ dir = runestr(path, npath);
+ else{
+ dir = dirname(t, nil, 0);
+ if(dir.nr + 1 + npath > nelem(tmp)){
+ free(dir.r);
+ goto Return;
+ }
+ if(dir.nr == 0){
+ dir.nr = 1;
+ dir.r = runestrdup(Ldot);
+ }
+ runemove(tmp, dir.r, dir.nr);
+ tmp[dir.nr] = '/';
+ runemove(tmp+dir.nr+1, path, npath);
+ free(dir.r);
+ dir.r = tmp;
+ dir.nr += 1+npath;
+ dir = cleanrname(dir);
+ }
+
+ s = smprint("%.*S", nstr, str);
+ dirs = smprint("%.*S", dir.nr, dir.r);
+ c = complete(dirs, s);
+ free(s);
+ if(c == nil){
+ warning(nil, "error attempting completion: %r\n");
+ goto Return;
+ }
+
+ if(!c->advance){
+ warning(nil, "%.*S%s%.*S*%s\n",
+ dir.nr, dir.r,
+ dir.nr>0 && dir.r[dir.nr-1]!='/' ? "/" : "",
+ nstr, str,
+ c->nmatch ? "" : ": no matches in:");
+ for(i=0; i<c->nfile; i++)
+ warning(nil, " %s\n", c->filename[i]);
+ }
+
+ if(c->advance)
+ rp = runesmprint("%s", c->string);
+ else
+ rp = nil;
+ Return:
+ freecompletion(c);
+ free(dirs);
+ free(str);
+ free(path);
+ return rp;
+}
+
+void
+texttype(Text *t, Rune r)
+{
+ uint q0, q1;
+ int nnb, nb, n, i;
+ int nr;
+ Rune *rp;
+ Text *u;
+
+ if(t->what!=Body && t->what!=Tag && r=='\n')
+ return;
+ if(t->what == Tag)
+ t->w->tagsafe = FALSE;
+
+ nr = 1;
+ rp = &r;
+ switch(r){
+ case Kleft:
+ typecommit(t);
+ if(t->q0 > 0)
+ textshow(t, t->q0-1, t->q0-1, TRUE);
+ return;
+ case Kright:
+ typecommit(t);
+ if(t->q1 < t->file->b.nc)
+ textshow(t, t->q1+1, t->q1+1, TRUE);
+ return;
+ case Kdown:
+ if(t->what == Tag)
+ goto Tagdown;
+ n = t->fr.maxlines/3;
+ goto case_Down;
+ case Kscrollonedown:
+ if(t->what == Tag)
+ goto Tagdown;
+ n = mousescrollsize(t->fr.maxlines);
+ if(n <= 0)
+ n = 1;
+ goto case_Down;
+ case Kpgdown:
+ n = 2*t->fr.maxlines/3;
+ case_Down:
+ 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, TRUE);
+ return;
+ case Kup:
+ if(t->what == Tag)
+ goto Tagup;
+ n = t->fr.maxlines/3;
+ goto case_Up;
+ case Kscrolloneup:
+ if(t->what == Tag)
+ goto Tagup;
+ n = mousescrollsize(t->fr.maxlines);
+ goto case_Up;
+ case Kpgup:
+ n = 2*t->fr.maxlines/3;
+ case_Up:
+ q0 = textbacknl(t, t->org, n);
+ textsetorigin(t, q0, TRUE);
+ return;
+ case Khome:
+ typecommit(t);
+ if(t->org > t->iq1) {
+ q0 = textbacknl(t, t->iq1, 1);
+ textsetorigin(t, q0, TRUE);
+ } else
+ textshow(t, 0, 0, FALSE);
+ return;
+ case Kend:
+ typecommit(t);
+ if(t->iq1 > t->org+t->fr.nchars) {
+ if(t->iq1 > t->file->b.nc) {
+ // should not happen, but does. and it will crash textbacknl.
+ t->iq1 = t->file->b.nc;
+ }
+ q0 = textbacknl(t, t->iq1, 1);
+ textsetorigin(t, q0, TRUE);
+ } else
+ textshow(t, t->file->b.nc, t->file->b.nc, FALSE);
+ return;
+ case 0x01: /* ^A: beginning of line */
+ typecommit(t);
+ /* go to where ^U would erase, if not already at BOL */
+ nnb = 0;
+ if(t->q0>0 && textreadc(t, t->q0-1)!='\n')
+ nnb = textbswidth(t, 0x15);
+ textshow(t, t->q0-nnb, t->q0-nnb, TRUE);
+ return;
+ case 0x05: /* ^E: end of line */
+ typecommit(t);
+ q0 = t->q0;
+ while(q0<t->file->b.nc && textreadc(t, q0)!='\n')
+ q0++;
+ textshow(t, q0, q0, TRUE);
+ return;
+ case Kcmd+'c': /* %C: copy */
+ typecommit(t);
+ cut(t, t, nil, TRUE, FALSE, nil, 0);
+ return;
+ case Kcmd+'z': /* %Z: undo */
+ typecommit(t);
+ undo(t, nil, nil, TRUE, 0, nil, 0);
+ return;
+ case Kcmd+'Z': /* %-shift-Z: redo */
+ typecommit(t);
+ undo(t, nil, nil, FALSE, 0, nil, 0);
+ return;
+
+ Tagdown:
+ /* expand tag to show all text */
+ if(!t->w->tagexpand){
+ t->w->tagexpand = TRUE;
+ winresize(t->w, t->w->r, FALSE, TRUE);
+ }
+ return;
+
+ Tagup:
+ /* shrink tag to single line */
+ if(t->w->tagexpand){
+ t->w->tagexpand = FALSE;
+ t->w->taglines = 1;
+ winresize(t->w, t->w->r, FALSE, TRUE);
+ }
+ return;
+ }
+ if(t->what == Body){
+ seq++;
+ filemark(t->file);
+ }
+ /* cut/paste must be done after the seq++/filemark */
+ switch(r){
+ case Kcmd+'x': /* %X: cut */
+ typecommit(t);
+ if(t->what == Body){
+ seq++;
+ filemark(t->file);
+ }
+ cut(t, t, nil, TRUE, TRUE, nil, 0);
+ textshow(t, t->q0, t->q0, 1);
+ t->iq1 = t->q0;
+ return;
+ case Kcmd+'v': /* %V: paste */
+ typecommit(t);
+ if(t->what == Body){
+ seq++;
+ filemark(t->file);
+ }
+ paste(t, t, nil, TRUE, FALSE, nil, 0);
+ textshow(t, t->q0, t->q1, 1);
+ t->iq1 = t->q1;
+ return;
+ }
+ 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 0x06: /* ^F: complete */
+ case Kins:
+ typecommit(t);
+ rp = textcomplete(t);
+ if(rp == nil)
+ return;
+ nr = runestrlen(rp);
+ break; /* fall through to normal insertion case */
+ case 0x1B:
+ if(t->eq0 != ~0) {
+ if(t->eq0 <= t->q0)
+ textsetselect(t, t->eq0, t->q0);
+ else
+ textsetselect(t, t->q0, t->eq0);
+ }
+ if(t->ncache > 0)
+ typecommit(t);
+ t->iq1 = t->q0;
+ 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]);
+ t->iq1 = t->q0;
+ return;
+ case '\n':
+ if(t->w->autoindent){
+ /* find beginning of previous line using backspace code */
+ nnb = textbswidth(t, 0x15); /* ^U case */
+ rp = runemalloc(nnb + 1);
+ nr = 0;
+ rp[nr++] = r;
+ for(i=0; i<nnb; i++){
+ r = textreadc(t, t->q0-nnb+i);
+ if(r != ' ' && r != '\t')
+ break;
+ rp[nr++] = r;
+ }
+ }
+ break; /* fall through to normal code */
+ }
+ /* 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");
+ /*
+ * Change the tag before we add to ncache,
+ * so that if the window body is resized the
+ * commit will not find anything in ncache.
+ */
+ if(u->what==Body && u->ncache == 0){
+ u->needundo = TRUE;
+ winsettag(t->w);
+ u->needundo = FALSE;
+ }
+ textinsert(u, t->q0, rp, nr, FALSE);
+ if(u != t)
+ textsetselect(u, u->q0, u->q1);
+ if(u->ncache+nr > u->ncachealloc){
+ u->ncachealloc += 10 + nr;
+ u->cache = runerealloc(u->cache, u->ncachealloc);
+ }
+ runemove(u->cache+u->ncache, rp, nr);
+ u->ncache += nr;
+ }
+ if(rp != &r)
+ free(rp);
+ textsetselect(t, t->q0+nr, t->q0+nr);
+ if(r=='\n' && t->w!=nil)
+ wincommit(t->w, t);
+ t->iq1 = t->q0;
+}
+
+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;
+ enum { None, Cut, Paste };
+
+ 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 = None; /* what we've done; undo when possible */
+ while(mouse->buttons){
+ mouse->msec = 0;
+ b = mouse->buttons;
+ if((b&1) && (b&6)){
+ if(state==None && t->what==Body){
+ seq++;
+ filemark(t->w->body.file);
+ }
+ if(b & 2){
+ if(state==Paste && t->what==Body){
+ winundo(t->w, TRUE);
+ textsetselect(t, q0, t->q1);
+ state = None;
+ }else if(state != Cut){
+ cut(t, t, nil, TRUE, TRUE, nil, 0);
+ state = Cut;
+ }
+ }else{
+ if(state==Cut && t->what==Body){
+ winundo(t->w, TRUE);
+ textsetselect(t, q0, t->q1);
+ state = None;
+ }else if(state != Paste){
+ paste(t, t, nil, TRUE, FALSE, nil, 0);
+ state = Paste;
+ }
+ }
+ 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;
+ int tsd;
+ int nc;
+ uint q;
+
+ if(t->what != Body){
+ if(doselect)
+ textsetselect(t, q0, q1);
+ 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;
+ tsd = FALSE; /* do we call textscrdraw? */
+ nc = t->file->b.nc+t->ncache;
+ if(t->org <= q0){
+ if(nc==0 || q0<qe)
+ tsd = TRUE;
+ else if(q0==qe && qe==nc){
+ if(textreadc(t, nc-1) == '\n'){
+ if(t->fr.nlines<t->fr.maxlines)
+ tsd = TRUE;
+ }else
+ tsd = TRUE;
+ }
+ }
+ if(tsd)
+ 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, ticked;
+
+ /* 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;
+ ticked = 1;
+ if(p0 < 0){
+ ticked = 0;
+ p0 = 0;
+ }
+ if(p1 < 0)
+ p1 = 0;
+ if(p0 > t->fr.nchars)
+ p0 = t->fr.nchars;
+ if(p1 > t->fr.nchars){
+ ticked = 0;
+ p1 = t->fr.nchars;
+ }
+ if(p0==t->fr.p0 && p1==t->fr.p1){
+ if(p0 == p1 && ticked != t->fr.ticked)
+ frtick(&t->fr, frptofchar(&t->fr, p0), ticked);
+ return;
+ }
+ if(p0 > p1)
+ sysfatal("acme: textsetselect p0=%d p1=%d q0=%ud q1=%ud t->org=%d nchars=%d", p0, p1, q0, q1, (int)t->org, (int)t->fr.nchars);
+ /* 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);
+ if(p0 != p1 || ticked)
+ 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;
+
+ if(textclickhtmlmatch(t, q0, q1))
+ return;
+
+ 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;
+}
+
+// Is the text starting at location q an html tag?
+// Return 1 for <a>, -1 for </a>, 0 for no tag or <a />.
+// Set *q1, if non-nil, to the location after the tag.
+static int
+ishtmlstart(Text *t, uint q, uint *q1)
+{
+ int c, c1, c2;
+
+ if(q+2 > t->file->b.nc)
+ return 0;
+ if(textreadc(t, q++) != '<')
+ return 0;
+ c = textreadc(t, q++);
+ c1 = c;
+ c2 = c;
+ while(c != '>') {
+ if(q >= t->file->b.nc)
+ return 0;
+ c2 = c;
+ c = textreadc(t, q++);
+ }
+ if(q1)
+ *q1 = q;
+ if(c1 == '/') // closing tag
+ return -1;
+ if(c2 == '/' || c2 == '!') // open + close tag or comment
+ return 0;
+ return 1;
+}
+
+// Is the text ending at location q an html tag?
+// Return 1 for <a>, -1 for </a>, 0 for no tag or <a />.
+// Set *q0, if non-nil, to the start of the tag.
+static int
+ishtmlend(Text *t, uint q, uint *q0)
+{
+ int c, c1, c2;
+
+ if(q < 2)
+ return 0;
+ if(textreadc(t, --q) != '>')
+ return 0;
+ c = textreadc(t, --q);
+ c1 = c;
+ c2 = c;
+ while(c != '<') {
+ if(q == 0)
+ return 0;
+ c1 = c;
+ c = textreadc(t, --q);
+ }
+ if(q0)
+ *q0 = q;
+ if(c1 == '/') // closing tag
+ return -1;
+ if(c2 == '/' || c2 == '!') // open + close tag or comment
+ return 0;
+ return 1;
+}
+
+int
+textclickhtmlmatch(Text *t, uint *q0, uint *q1)
+{
+ int depth, n;
+ uint q, nq;
+
+ q = *q0;
+ // after opening tag? scan forward for closing tag
+ if(ishtmlend(t, q, nil) == 1) {
+ depth = 1;
+ while(q < t->file->b.nc) {
+ n = ishtmlstart(t, q, &nq);
+ if(n != 0) {
+ depth += n;
+ if(depth == 0) {
+ *q1 = q;
+ return 1;
+ }
+ q = nq;
+ continue;
+ }
+ q++;
+ }
+ }
+
+ // before closing tag? scan backward for opening tag
+ if(ishtmlstart(t, q, nil) == -1) {
+ depth = -1;
+ while(q > 0) {
+ n = ishtmlend(t, q, &nq);
+ if(n != 0) {
+ depth += n;
+ if(depth == 0) {
+ *q0 = q;
+ return 1;
+ }
+ q = nq;
+ continue;
+ }
+ q--;
+ }
+ }
+
+ return 0;
+}
+
+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 && textreadc(t, org-1) != '\n'){
+ /* 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/time.c b/time.c
@@ -0,0 +1,124 @@
+#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 <libsec.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(10); /* longer sleeps here delay recv on ctimer, but 10ms should not be noticeable */
+ 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);
+ chansetname(ctimer, "ctimer");
+ 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);
+ chansetname(t->c, "tc%p", t->c);
+ }
+ t->next = nil;
+ t->dt = dt;
+ t->cancel = FALSE;
+ sendp(ctimer, t);
+ return t;
+}
diff --git a/util.c b/util.c
@@ -0,0 +1,497 @@
+#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 <libsec.h>
+#include "dat.h"
+#include "fns.h"
+
+static Point prevmouse;
+static Window *mousew;
+
+Range
+range(int q0, int q1)
+{
+ Range r;
+
+ r.q0 = q0;
+ r.q1 = q1;
+ return r;
+}
+
+Runestr
+runestr(Rune *r, uint n)
+{
+ Runestr rs;
+
+ rs.r = r;
+ rs.nr = n;
+ return rs;
+}
+
+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);
+ threadexitsall(nil);
+}
+
+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+8);
+ if((n = ndir) != 0){
+ runemove(r, dir, ndir);
+ r[n++] = L'/';
+ }
+ 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);
+ xfidlog(w, "new");
+ }
+ free(r);
+ for(i=nincl; --i>=0; ){
+ n = runestrlen(incl[i]);
+ r = runemalloc(n);
+ runemove(r, incl[i], n);
+ winaddincl(w, r, n);
+ }
+ w->autoindent = globalautoindent;
+ return w;
+}
+
+/* make new window, if necessary; return with it locked */
+Window*
+errorwin(Mntdir *md, int owner)
+{
+ Window *w;
+
+ for(;;){
+ if(md == nil)
+ w = errorwin1(nil, 0, nil, 0);
+ else
+ w = errorwin1(md->dir, md->ndir, md->incl, md->nincl);
+ winlock(w, owner);
+ if(w->col != nil)
+ break;
+ /* window was deleted too fast */
+ winunlock(w);
+ }
+ return w;
+}
+
+/*
+ * Incoming window should be locked.
+ * It will be unlocked and returned window
+ * will be locked in its place.
+ */
+Window*
+errorwinforwin(Window *w)
+{
+ int i, n, nincl, owner;
+ Rune **incl;
+ Runestr dir;
+ Text *t;
+
+ t = &w->body;
+ dir = dirname(t, nil, 0);
+ if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */
+ free(dir.r);
+ dir.r = nil;
+ dir.nr = 0;
+ }
+ incl = nil;
+ nincl = w->nincl;
+ if(nincl > 0){
+ incl = emalloc(nincl*sizeof(Rune*));
+ for(i=0; i<nincl; i++){
+ n = runestrlen(w->incl[i]);
+ incl[i] = runemalloc(n+1);
+ runemove(incl[i], w->incl[i], n);
+ }
+ }
+ owner = w->owner;
+ winunlock(w);
+ for(;;){
+ w = errorwin1(dir.r, dir.nr, incl, nincl);
+ winlock(w, owner);
+ if(w->col != nil)
+ break;
+ /* window deleted too fast */
+ winunlock(w);
+ }
+ return w;
+}
+
+typedef struct Warning Warning;
+
+struct Warning{
+ Mntdir *md;
+ Buffer buf;
+ Warning *next;
+};
+
+static Warning *warnings;
+
+static
+void
+addwarningtext(Mntdir *md, Rune *r, int nr)
+{
+ Warning *warn;
+
+ for(warn = warnings; warn; warn=warn->next){
+ if(warn->md == md){
+ bufinsert(&warn->buf, warn->buf.nc, r, nr);
+ return;
+ }
+ }
+ warn = emalloc(sizeof(Warning));
+ warn->next = warnings;
+ warn->md = md;
+ if(md)
+ fsysincid(md);
+ warnings = warn;
+ bufinsert(&warn->buf, 0, r, nr);
+ nbsendp(cwarn, 0);
+}
+
+/* called while row is locked */
+void
+flushwarnings(void)
+{
+ Warning *warn, *next;
+ Window *w;
+ Text *t;
+ int owner, nr, q0, n;
+ Rune *r;
+
+ for(warn=warnings; warn; warn=next) {
+ w = errorwin(warn->md, 'E');
+ t = &w->body;
+ owner = w->owner;
+ if(owner == 0)
+ w->owner = 'E';
+ wincommit(w, t);
+ /*
+ * Most commands don't generate much output. For instance,
+ * Edit ,>cat goes through /dev/cons and is already in blocks
+ * because of the i/o system, but a few can. Edit ,p will
+ * put the entire result into a single hunk. So it's worth doing
+ * this in blocks (and putting the text in a buffer in the first
+ * place), to avoid a big memory footprint.
+ */
+ r = fbufalloc();
+ q0 = t->file->b.nc;
+ for(n = 0; n < warn->buf.nc; n += nr){
+ nr = warn->buf.nc - n;
+ if(nr > RBUFSIZE)
+ nr = RBUFSIZE;
+ bufread(&warn->buf, n, r, nr);
+ textbsinsert(t, t->file->b.nc, r, nr, TRUE, &nr);
+ }
+ textshow(t, q0, t->file->b.nc, 1);
+ free(r);
+ winsettag(t->w);
+ textscrdraw(t);
+ w->owner = owner;
+ w->dirty = FALSE;
+ winunlock(w);
+ bufclose(&warn->buf);
+ next = warn->next;
+ if(warn->md)
+ fsysdelid(warn->md);
+ free(warn);
+ }
+ warnings = nil;
+}
+
+void
+warning(Mntdir *md, char *s, ...)
+{
+ Rune *r;
+ va_list arg;
+
+ va_start(arg, s);
+ r = runevsmprint(s, arg);
+ va_end(arg);
+ if(r == nil)
+ error("runevsmprint failed");
+ addwarningtext(md, r, runestrlen(r));
+ free(r);
+}
+
+int
+runeeq(Rune *s1, uint n1, Rune *s2, uint n2)
+{
+ if(n1 != n2)
+ return FALSE;
+ if(n1 == 0)
+ return TRUE;
+ 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;
+}
+
+int
+restoremouse(Window *w)
+{
+ int did;
+
+ did = 0;
+ if(mousew!=nil && mousew==w) {
+ moveto(mousectl, prevmouse);
+ did = 1;
+ }
+ mousew = nil;
+ return did;
+}
+
+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)
+ 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)
+ 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/wind.c b/wind.c
@@ -0,0 +1,726 @@
+#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 <libsec.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->taglines = 1;
+ w->tagexpand = TRUE;
+ w->body.w = w;
+ w->id = ++winid;
+ incref(&w->ref);
+ if(globalincref)
+ incref(&w->ref);
+ w->ctlfid = ~0;
+ w->utflastqid = -1;
+ r1 = r;
+
+ w->tagtop = r;
+ w->tagtop.max.y = r.min.y + font->height;
+ r1.max.y = r1.min.y + w->taglines*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 += w->taglines*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;
+ 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;
+ w->autoindent = globalautoindent;
+ if(clone){
+ w->dirty = clone->dirty;
+ w->autoindent = clone->autoindent;
+ textsetselect(&w->body, clone->body.q0, clone->body.q1);
+ winsettag(w);
+ }
+}
+
+/*
+ * Draw the appropriate button.
+ */
+void
+windrawbutton(Window *w)
+{
+ Image *b;
+ Rectangle br;
+
+ 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);
+}
+
+int
+delrunepos(Window *w)
+{
+ Rune *r;
+ int i;
+
+ r = parsetag(w, 0, &i);
+ free(r);
+ i += 2;
+ if(i >= w->tag.file->b.nc)
+ return -1;
+ return i;
+}
+
+void
+movetodel(Window *w)
+{
+ int n;
+
+ n = delrunepos(w);
+ if(n < 0)
+ return;
+ moveto(mousectl, addpt(frptofchar(&w->tag.fr, n), Pt(4, w->tag.fr.font->height-4)));
+}
+
+/*
+ * Compute number of tag lines required
+ * to display entire tag text.
+ */
+int
+wintaglines(Window *w, Rectangle r)
+{
+ int n;
+ Rune rune;
+ Point p;
+
+ if(!w->tagexpand && !w->showdel)
+ return 1;
+ w->showdel = FALSE;
+ w->tag.fr.noredraw = 1;
+ textresize(&w->tag, r, TRUE);
+ w->tag.fr.noredraw = 0;
+ w->tagsafe = FALSE;
+
+ if(!w->tagexpand) {
+ /* use just as many lines as needed to show the Del */
+ n = delrunepos(w);
+ if(n < 0)
+ return 1;
+ p = subpt(frptofchar(&w->tag.fr, n), w->tag.fr.r.min);
+ return 1 + p.y / w->tag.fr.font->height;
+ }
+
+ /* can't use more than we have */
+ if(w->tag.fr.nlines >= w->tag.fr.maxlines)
+ return w->tag.fr.maxlines;
+
+ /* if tag ends with \n, include empty line at end for typing */
+ n = w->tag.fr.nlines;
+ if(w->tag.file->b.nc > 0){
+ bufread(&w->tag.file->b, w->tag.file->b.nc-1, &rune, 1);
+ if(rune == '\n')
+ n++;
+ }
+ if(n == 0)
+ n = 1;
+ return n;
+}
+
+int
+winresize(Window *w, Rectangle r, int safe, int keepextra)
+{
+ int oy, y, mouseintag, mouseinbody;
+ Point p;
+ Rectangle r1;
+
+ mouseintag = ptinrect(mouse->xy, w->tag.all);
+ mouseinbody = ptinrect(mouse->xy, w->body.all);
+
+ /* tagtop is first line of tag */
+ w->tagtop = r;
+ w->tagtop.max.y = r.min.y+font->height;
+
+ r1 = r;
+ r1.max.y = min(r.max.y, r1.min.y + w->taglines*font->height);
+
+ /* If needed, recompute number of lines in tag. */
+ if(!safe || !w->tagsafe || !eqrect(w->tag.all, r1)){
+ w->taglines = wintaglines(w, r);
+ r1.max.y = min(r.max.y, r1.min.y + w->taglines*font->height);
+ }
+
+ /* If needed, resize & redraw tag. */
+ y = r1.max.y;
+ if(!safe || !w->tagsafe || !eqrect(w->tag.all, r1)){
+ textresize(&w->tag, r1, TRUE);
+ y = w->tag.fr.r.max.y;
+ windrawbutton(w);
+ w->tagsafe = TRUE;
+
+ /* If mouse is in tag, pull up as tag closes. */
+ if(mouseintag && !ptinrect(mouse->xy, w->tag.all)){
+ p = mouse->xy;
+ p.y = w->tag.all.max.y-3;
+ moveto(mousectl, p);
+ }
+
+ /* If mouse is in body, push down as tag expands. */
+ if(mouseinbody && ptinrect(mouse->xy, w->tag.all)){
+ p = mouse->xy;
+ p.y = w->tag.all.max.y+3;
+ moveto(mousectl, p);
+ }
+ }
+
+ /* If needed, resize & redraw body. */
+ r1 = r;
+ r1.min.y = y;
+ if(!safe || !eqrect(w->body.all, r1)){
+ oy = y;
+ if(y+1+w->body.fr.font->height <= r.max.y){ /* room for one line */
+ r1.min.y = y;
+ r1.max.y = y+1;
+ draw(screen, r1, tagcols[BORD], nil, ZP);
+ y++;
+ r1.min.y = min(y, r.max.y);
+ r1.max.y = r.max.y;
+ }else{
+ draw(screen, r1, textcols[BACK], nil, ZP);
+ r1.min.y = y;
+ r1.max.y = y;
+ }
+ y = textresize(&w->body, r1, keepextra);
+ w->r = r;
+ w->r.max.y = y;
+ textscrdraw(&w->body);
+ w->body.all.min.y = oy;
+ }
+ 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;
+
+ /*
+ * subtle: loop runs backwards to avoid tripping over
+ * winclose indirectly editing f->text and freeing f
+ * on the last iteration of the loop.
+ */
+ f = w->body.file;
+ for(i=f->ntext-1; i>=0; i--){
+ w = f->text[i]->w;
+ w->owner = 0;
+ qunlock(&w->lk);
+ winclose(w);
+ }
+}
+
+void
+winmousebut(Window *w)
+{
+ moveto(mousectl, addpt(w->tag.scrollr.min,
+ divpt(Pt(Dx(w->tag.scrollr), font->height), 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){
+ xfidlog(w, "del");
+ 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 = parsetag(w, 0, &i);
+ 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);
+}
+
+Rune*
+parsetag(Window *w, int extra, int *len)
+{
+ static Rune Ldelsnarf[] = { ' ', 'D', 'e', 'l', ' ', 'S', 'n', 'a', 'r', 'f', 0 };
+ static Rune Lspacepipe[] = { ' ', '|', 0 };
+ static Rune Ltabpipe[] = { '\t', '|', 0 };
+ int i;
+ Rune *r, *p, *pipe;
+
+ r = runemalloc(w->tag.file->b.nc+extra+1);
+ bufread(&w->tag.file->b, 0, r, w->tag.file->b.nc);
+ r[w->tag.file->b.nc] = '\0';
+
+ /*
+ * " |" or "\t|" ends left half of tag
+ * If we find " Del Snarf" in the left half of the tag
+ * (before the pipe), that ends the file name.
+ */
+ pipe = runestrstr(r, Lspacepipe);
+ if((p = runestrstr(r, Ltabpipe)) != nil && (pipe == nil || p < pipe))
+ pipe = p;
+ if((p = runestrstr(r, Ldelsnarf)) != nil && (pipe == nil || p < pipe))
+ i = p - r;
+ else {
+ for(i=0; i<w->tag.file->b.nc; i++)
+ if(r[i]==' ' || r[i]=='\t')
+ break;
+ }
+ *len = i;
+ return r;
+}
+
+void
+winsettag1(Window *w)
+{
+ int i, j, k, n, bar, dirty, resize;
+ Rune *new, *old, *r;
+ uint q0, q1;
+ 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 = parsetag(w, 0, &i);
+ 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';
+ }
+
+ /* compute the text for the whole tag, replacing current only if it differs */
+ new = runemalloc(w->body.file->nname+100);
+ i = 0;
+ if(w->body.file->nname != 0)
+ runemove(new, 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.needundo || 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;
+
+ /* replace tag if the new one is different */
+ resize = 0;
+ if(runeeq(new, i, old, k) == FALSE){
+ resize = 1;
+ 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);
+ windrawbutton(w);
+ if(resize){
+ w->tagsafe = 0;
+ winresize(w, w->r, TRUE, TRUE);
+ }
+}
+
+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 = parsetag(w, 0, &i);
+ 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)
+{
+ 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;
+}
+
+char*
+winctlprint(Window *w, char *buf, int fonts)
+{
+ 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)
+ return smprint("%s%11d %q %11d %11d %11d ", buf, Dx(w->body.fr.r),
+ w->body.reffont->f->name, w->body.fr.maxtab, seqof(w, 1) != 0, seqof(w, 0) != 0);
+ return buf;
+}
+
+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 = erealloc(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/xfid.c b/xfid.c
@@ -0,0 +1,1147 @@
+#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 <libsec.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 = (void(*)(Xfid*))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;
+
+ xfidlogflush(x);
+
+ /* 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;
+ q = FILE(x->f->qid);
+ if(w){
+ winlock(w, 'E');
+ switch(q){
+ case QWaddr:
+ if(w->nopen[q]++ == 0){
+ w->addr = range(0, 0);
+ w->limit = range(-1,-1);
+ }
+ break;
+ case QWdata:
+ case QWxdata:
+ 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;
+ }
+ if(!canqlock(&w->editoutlk)){
+ winunlock(w);
+ respond(x, &fc, Einuse);
+ return;
+ }
+ w->wrselrange = range(t->q1, t->q1);
+ break;
+ }
+ winunlock(w);
+ }
+ else{
+ switch(q){
+ case Qlog:
+ xfidlogopen(x);
+ break;
+ case Qeditout:
+ if(!canqlock(&editoutlk)){
+ respond(x, &fc, Einuse);
+ return;
+ }
+ break;
+ }
+ }
+ 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;
+ x->f->w = nil;
+ if(x->f->open == FALSE){
+ if(w != nil)
+ winclose(w);
+ respond(x, &fc, nil);
+ return;
+ }
+
+ q = FILE(x->f->qid);
+ x->f->open = FALSE;
+ if(w){
+ winlock(w, 'E');
+ switch(q){
+ case QWctl:
+ if(w->ctlfid!=~0 && w->ctlfid==x->f->fid){
+ w->ctlfid = ~0;
+ qunlock(&w->ctllock);
+ }
+ break;
+ case QWdata:
+ case QWxdata:
+ 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 || q == QWxdata)
+ 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;
+ case QWeditout:
+ qunlock(&w->editoutlk);
+ break;
+ }
+ winunlock(w);
+ winclose(w);
+ }
+ else{
+ switch(q){
+ case Qeditout:
+ qunlock(&editoutlk);
+ break;
+ }
+ }
+ respond(x, &fc, nil);
+}
+
+void
+xfidread(Xfid *x)
+{
+ Fcall fc;
+ int n, q;
+ uint off;
+ char *b;
+ char buf[256];
+ 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;
+ case Qlog:
+ xfidlogread(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:
+ b = winctlprint(w, buf, 1);
+ goto Readb;
+
+ Readbuf:
+ b = buf;
+ Readb:
+ n = strlen(b);
+ if(off > n)
+ off = n;
+ if(off+x->fcall.count > n)
+ x->fcall.count = n-off;
+ fc.count = x->fcall.count;
+ fc.data = b+off;
+ respond(x, &fc, nil);
+ if(b != buf)
+ free(b);
+ 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 QWxdata:
+ /* 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->addr.q1);
+ 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);
+}
+
+static int
+shouldscroll(Text *t, uint q0, int qid)
+{
+ if(qid == Qcons)
+ return TRUE;
+ return t->org <= q0 && q0 <= t->org+t->fr.nchars;
+}
+
+static Rune*
+fullrunewrite(Xfid *x, int *inr)
+{
+ int q, cnt, c, nb, nr;
+ Rune *r;
+
+ 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;
+ }
+ *inr = nr;
+ return r;
+}
+
+void
+xfidwrite(Xfid *x)
+{
+ Fcall fc;
+ int c, qid, 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');
+ 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(FALSE, t, w->limit, w->addr, r, 0, nr, rgetc, &eval, (uint*)&nb, FALSE);
+ 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 = fullrunewrite(x, &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 QWerrors:
+ w = errorwinforwin(w);
+ t = &w->body;
+ goto BodyTag;
+
+ 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(shouldscroll(t, q0, qid))
+ textshow(t, q0+nr, 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:
+ r = fullrunewrite(x, &nr);
+ 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 && shouldscroll(t, q0, qid))
+ 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, "font ", 5) == 0){ /* execute font command */
+ 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 font string";
+ break;
+ }
+ fontx(&w->body, nil, nil, FALSE, XXX, 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, "nomenu", 6) == 0){ /* turn off automatic menu */
+ w->filemenu = FALSE;
+ settag = TRUE;
+ m = 6;
+ }else
+ if(strncmp(p, "menu", 4) == 0){ /* enable automatic menu */
+ w->filemenu = TRUE;
+ settag = TRUE;
+ m = 4;
+ }else
+ if(strncmp(p, "cleartag", 8) == 0){ /* wipe tag right of bar */
+ wincleartag(w);
+ settag = TRUE;
+ m = 8;
+ }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, FALSE);
+ break;
+ case 'r':
+ case 'R':
+ look3(t, q0, q1, TRUE, 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;
+ 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);
+ w->nevents -= n;
+ if(w->nevents){
+ memmove(w->events, w->events+n, w->nevents);
+ w->events = erealloc(w->events, w->nevents);
+ }else{
+ free(w->events);
+ w->events = nil;
+ }
+}
+
+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);
+}