all repos — acme @ 5220bc236bab8cda94171f65526976c1b3d24c78

fork of acme with my colors

Init acme
Anirudh Oppiliappan x@icyphox.sh
Tue, 12 Oct 2021 19:51:10 +0530
commit

5220bc236bab8cda94171f65526976c1b3d24c78

A .gitignore

@@ -0,0 +1,3 @@

+*.o +o.acme +o.Mail
A acme.c

@@ -0,0 +1,1169 @@

+#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; + Column *c; + int ncol; + Display *d; + + rfork(RFENVG|RFNAMEG); + + ncol = -1; + + loadfile = nil; + 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(); + + 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 }; + static Alt alts[NMALT+1]; + + USED(v); + threadsetname("mousethread"); + alts[MResize].c = mousectl->resizec; + alts[MResize].v = nil; + alts[MResize].op = CHANRCV; + alts[MMouse].c = mousectl->c; + alts[MMouse].v = &mousectl->m; + alts[MMouse].op = CHANRCV; + alts[MPlumb].c = cplumb; + alts[MPlumb].v = &pm; + alts[MPlumb].op = CHANRCV; + 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){ + if(textselect3(t, &q0, &q1)) + look3(t, q0, q1, FALSE); + } + 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)){ + 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) { + /* Tags */ + tagcols[BACK] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xCCCCCCFF); + tagcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x509C93FF); + tagcols[BORD] = display->black; + tagcols[TEXT] = display->black; + tagcols[HTEXT] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xF4F4F4FF); + + /* Body */ + textcols[BACK] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xF4F4F4FF); + textcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xCCCCCCFF); + textcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x676767FF); + textcols[TEXT] = display->black; + textcols[HTEXT] = display->black; + } + + r = Rect(0, 0, Scrollwid+ButtonBorder, 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); + r.max.x -= ButtonBorder; + border(button, r, ButtonBorder, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x676767FF), ZP); + + r = button->r; + modbutton = allocimage(display, r, screen ->chan, 0, DNofill); + draw(modbutton, r, tagcols[BACK], nil, r.min); + r.max.x -= ButtonBorder; + border(modbutton, r, ButtonBorder, tagcols[BORD], ZP); + r = insetrect(r, ButtonBorder); + tmp = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x676767FF); + draw(modbutton, r, tmp, nil, ZP); + freeimage(tmp); + + r = button->r; + colbutton = allocimage(display, r, screen->chan, 0, 0x509C93FF); + + but2col = allocimage(display, r, screen->chan, 1, 0xB870CEFF); + but3col = allocimage(display, r, screen->chan, 1, 0x7C9F4BFF); +} + +/* + * /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); +}
A addr.c

@@ -0,0 +1,295 @@

+#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 dir, size, npat; + int prevc, c, nc, n; + uint q; + Rune *pat; + Range r, nr; + + r = ar; + q = q0; + dir = None; + size = Line; + c = 0; + while(q < q1){ + prevc = c; + c = (*getc)(a, q++); + switch(c){ + default: + *qp = q-1; + return r; + case ';': + ar = r; + /* fall through */ + case ',': + if(prevc == 0) /* lhs defaults to 0 */ + r.q0 = 0; + if(q>=q1 && t!=nil && t->file!=nil) /* rhs defaults to $ */ + r.q1 = t->file->b.nc; + else{ + nr = address(showerr, t, lim, ar, a, q, q1, getc, evalp, &q); + r.q1 = nr.q1; + } + *qp = q; + return r; + case '+': + case '-': + if(*evalp && (prevc=='+' || prevc=='-')) + if((nc=(*getc)(a, q))!='#' && nc!='/' && nc!='?') + r = number(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; +}
A 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; +}
A cols.c

@@ -0,0 +1,588 @@

+#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', ' ', + 0 +}; + +void +colinit(Column *c, Rectangle r) +{ + Rectangle r1; + Text *t; + + draw(screen, r, display->white, nil, ZP); + c->r = r; + c->w = nil; + c->nw = 0; + t = &c->tag; + t->w = nil; + t->col = c; + r1 = r; + r1.max.y = r1.min.y + font->height; + textinit(t, fileaddtext(nil, t), r1, &reffont, tagcols); + t->what = Columntag; + r1.min.y = r1.max.y; + r1.max.y += Border; + draw(screen, r1, display->black, nil, ZP); + textinsert(t, 0, Lheader, 38, TRUE); + textsetselect(t, t->file->b.nc, t->file->b.nc); + draw(screen, t->scrollr, colbutton, nil, colbutton->r.min); + c->safe = TRUE; +} + +Window* +coladd(Column *c, Window *w, Window *clone, int y) +{ + Rectangle r, r1; + Window *v; + int i, 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 */ + 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 += 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; +}
A dat.h

@@ -0,0 +1,581 @@

+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 = 8*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; + 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 +}; + +uint globalincref; +uint seq; +uint maxtab; /* size of a tab, in units of the '0' character */ + +Display *display; +Image *screen; +Font *font; +Mouse *mouse; +Mousectl *mousectl; +Keyboardctl *keyboardctl; +Reffont reffont; +Image *modbutton; +Image *colbutton; +Image *button; +Image *but2col; +Image *but3col; +Cursor boxcursor; +Cursor2 boxcursor2; +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; +int swapscrollbuttons; +Window *activewin; +Column *activecol; +Buffer snarfbuf; +Rectangle nullrect; +int fsyspid; +char *cputype; +char *objtype; +char *home; +char *acmeshell; +char *fontnames[2]; +Image *tagcols[NCOL]; +Image *textcols[NCOL]; +extern char wdir[]; /* must use extern because no dimension given */ +int editing; +int erroutfd; +int messagesize; /* negotiated in 9P version setup */ +int globalautoindent; +int dodollarsigns; +char* mtpt; + +enum +{ + Kscrolloneup = KF|0x20, + Kscrollonedown = KF|0x21 +}; + +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; + +#define STACK 65536
A 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"); +}
A 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; +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 = &empty; + }else + str = cp->u.text; + name = cmdname(t->file, str, TRUE); + free(name); + pfilename(t->file); + return TRUE; +} + +int +g_cmd(Text *t, Cmd *cp) +{ + if(t->file != addr.f){ + warning(nil, "internal error: g_cmd f!=addr.f\n"); + return FALSE; + } + if(rxcompile(cp->re->r) == FALSE) + editerror("bad regexp in g command"); + if(rxexecute(t, nil, addr.r.q0, addr.r.q1, &sel) ^ cp->cmdc=='v'){ + t->q0 = addr.r.q0; + t->q1 = addr.r.q1; + return cmdexec(t, cp->u.cmd); + } + return TRUE; +} + +int +i_cmd(Text *t, Cmd *cp) +{ + return append(t->file, cp, addr.r.q0); +} + +void +copy(File *f, Address addr2) +{ + long p; + int ni; + Rune *buf; + + buf = fbufalloc(); + for(p=addr.r.q0; p<addr.r.q1; p+=ni){ + ni = addr.r.q1-p; + if(ni > RBUFSIZE) + ni = RBUFSIZE; + bufread(&f->b, p, buf, ni); + eloginsert(addr2.f, addr2.r.q1, buf, ni); + } + fbuffree(buf); +} + +void +move(File *f, Address addr2) +{ + if(addr.f!=addr2.f || addr.r.q1<=addr2.r.q0){ + elogdelete(f, addr.r.q0, addr.r.q1); + copy(f, addr2); + }else if(addr.r.q0 >= addr2.r.q1){ + copy(f, addr2); + elogdelete(f, addr.r.q0, addr.r.q1); + }else 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; +}
A 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; +}
A 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);
A 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; +}
A exec.c

@@ -0,0 +1,1807 @@

+#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; + + *rp = nil; + *nrp = 0; + if(argt == nil) + return nil; + a = nil; + textcommit(argt, TRUE); + if(expand(argt, argt->q0, argt->q1, &e)){ + free(e.bname); + if(e.nname && dofile){ + e.name = runerealloc(e.name, e.nname+1); + if(doaddr) + a = printarg(argt, e.q0, e.q1); + *rp = e.name; + *nrp = e.nname; + return a; + } + free(e.name); + }else{ + e.q0 = argt->q0; + e.q1 = argt->q1; + } + n = e.q1 - e.q0; + *rp = runemalloc(n+1); + bufread(&argt->file->b, e.q0, *rp, n); + if(doaddr) + a = printarg(argt, e.q0, e.q1); + *nrp = n; + return a; +} + +char* +getbytearg(Text *argt, int doaddr, int dofile, char **bp) +{ + Rune *r; + int n; + char *aa; + + *bp = nil; + aa = getarg(argt, doaddr, dofile, &r, &n); + if(r == nil) + return nil; + *bp = runetobyte(r, n); + free(r); + return aa; +} + +void +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); + return; + } + getarg(argt, FALSE, FALSE, &r, &n); + if(r == nil){ + n = t->q1-t->q0; + r = runemalloc(n); + bufread(&t->file->b, t->q0, r, n); + } + search(t, r, n); + free(r); + } +} + +static Rune Lnl[] = { '\n', 0 }; + +void +sendx(Text *et, Text *t, Text *_0, int _1, int _2, Rune *_3, int _4) +{ + USED(_0); + USED(_1); + USED(_2); + USED(_3); + USED(_4); + + if(et->w==nil) + return; + t = &et->w->body; + if(t->q0 != t->q1) + cut(t, t, nil, TRUE, FALSE, nil, 0); + textsetselect(t, t->file->b.nc, t->file->b.nc); + paste(t, t, nil, TRUE, TRUE, nil, 0); + if(textreadc(t, t->file->b.nc-1) != '\n'){ + textinsert(t, t->file->b.nc, Lnl, 1, TRUE); + textsetselect(t, t->file->b.nc, t->file->b.nc); + } + 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); + + 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); + 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); +}
A 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; +}
A fns.h

@@ -0,0 +1,107 @@

+/* +#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 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); +void look3(Text*, uint, uint, int); +void editcmd(Text*, Rune*, uint); +uint min(uint, uint); +uint max(uint, uint); +Window* lookfile(Rune*, int); +Window* lookid(int, int); +char* runetobyte(Rune*, int); +Rune* bytetorune(char*, int*); +void fsysinit(void); +Mntdir* fsysmount(Rune*, int, Rune**, int); +void fsysdelid(Mntdir*); +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 rxexecute(Text*, Rune*, uint, uint, Rangeset*); +int rxbexecute(Text*, uint, Rangeset*); +Window* makenewwindow(Text *t); +int expand(Text*, uint, uint, Expand*); +Rune* skipbl(Rune*, int, int*); +Rune* findbl(Rune*, int, int*); +char* edittext(Window*, int, Rune*, int); +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*);
A 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); +}
A 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); +}
A look.c

@@ -0,0 +1,870 @@

+#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 n, c, f, expanded; + Text *ct; + Expand e; + Rune *r; + uint p; + Plumbmsg *m; + Runestr dir; + char buf[32]; + + ct = seltext; + if(ct == nil) + seltext = t; + expanded = expand(t, q0, q1, &e); + if(!external && t->w!=nil && t->w->nopen[QWevent]>0){ + /* send alphanumeric expansion to external client */ + if(expanded == FALSE) + return; + f = 0; + if((e.u.at!=nil && t->w!=nil) || (e.nname>0 && lookfile(e.name, e.nname)!=nil)) + f = 1; /* acme can do it without loading a file */ + if(q0!=e.q0 || q1!=e.q1) + f |= 2; /* second (post-expand) message follows */ + if(e.nname) + f |= 4; /* it's a file name */ + c = 'l'; + if(t->what == Body) + c = 'L'; + n = q1-q0; + if(n <= EVENTSIZE){ + r = runemalloc(n); + bufread(&t->file->b, q0, r, n); + winevent(t->w, "%c%d %d %d %d %.*S\n", c, q0, q1, f, n, n, r); + free(r); + }else + winevent(t->w, "%c%d %d %d 0 \n", c, q0, q1, f, n); + if(q0==e.q0 && q1==e.q1) + return; + if(e.nname){ + n = e.nname; + if(e.a1 > e.a0) + n += 1+(e.a1-e.a0); + r = runemalloc(n); + runemove(r, e.name, e.nname); + if(e.a1 > e.a0){ + r[e.nname] = ':'; + bufread(&e.u.at->file->b, e.a0, r+e.nname+1, e.a1-e.a0); + } + }else{ + n = e.q1 - e.q0; + r = runemalloc(n); + bufread(&t->file->b, e.q0, r, n); + } + f &= ~2; + if(n <= EVENTSIZE) + winevent(t->w, "%c%d %d %d %d %.*S\n", c, e.q0, e.q1, f, n, n, r); + else + winevent(t->w, "%c%d %d %d 0 \n", c, e.q0, e.q1, f, n); + free(r); + goto Return; + } + if(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) + textsetselect(ct, e.q1, e.q1); + n = e.q1 - e.q0; + r = runemalloc(n); + bufread(&t->file->b, e.q0, r, n); + if(search(ct, r, n) && e.jump) + moveto(mousectl, addpt(frptofchar(&ct->fr, ct->fr.p0), Pt(4, ct->fr.font->height-4))); + if(t->w != ct->w) + winunlock(ct->w); + free(r); + } + + Return: + free(e.name); + free(e.bname); +} + +int +plumbgetc(void *a, uint n) +{ + Rune *r; + + r = a; + if(n>runestrlen(r)) + return 0; + return r[n]; +} + +void +plumblook(Plumbmsg *m) +{ + Expand e; + char *addr; + + if(m->ndata >= BUFSIZE){ + warning(nil, "insanely long file name (%d bytes) in plumb message (%.32s...)\n", m->ndata, m->data); + return; + } + e.q0 = 0; + e.q1 = 0; + if(m->data[0] == '\0') + return; + e.u.ar = nil; + e.bname = m->data; + e.name = bytetorune(e.bname, &e.nname); + e.jump = TRUE; + e.a0 = 0; + e.a1 = 0; + addr = plumblookup(m->attr, "addr"); + if(addr != nil){ + e.u.ar = bytetorune(addr, &e.a1); + e.agetc = plumbgetc; + } + 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) +{ + uint q, nb, maxn; + int around; + Rune *s, *b, *c; + + if(n==0 || n>ct->file->b.nc) + return FALSE; + if(2*n > RBUFSIZE){ + warning(nil, "string too long\n"); + return FALSE; + } + maxn = max(2*n, RBUFSIZE); + s = fbufalloc(); + b = s; + nb = 0; + b[nb] = 0; + around = 0; + q = ct->q1; + for(;;){ + if(q >= ct->file->b.nc){ + q = 0; + around = 1; + nb = 0; + b[nb] = 0; + } + if(nb > 0){ + c = runestrchr(b, r[0]); + if(c == nil){ + q += nb; + nb = 0; + b[nb] = 0; + if(around && q>=ct->q1) + break; + continue; + } + q += (c-b); + nb -= (c-b); + b = c; + } + /* reload if buffer covers neither string nor rest of file */ + if(nb<n && nb!=ct->file->b.nc-q){ + nb = ct->file->b.nc-q; + if(nb >= maxn) + nb = maxn-1; + bufread(&ct->file->b, q, s, nb); + b = s; + b[nb] = '\0'; + } + /* this runeeq is fishy but the null at b[nb] makes it safe */ + if(runeeq(b, n, r, n)==TRUE){ + if(ct->w){ + textshow(ct, q, q+n, 1); + winsettag(ct->w); + }else{ + ct->q0 = q; + ct->q1 = q+n; + } + seltext = ct; + fbuffree(s); + return TRUE; + } + --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 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; + } + 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; + eval = FALSE; + address(TRUE, nil, range(-1,-1), range(0,0), t, e->a0, amax, tgetc, &eval, (uint*)&e->a1); + return TRUE; + + Isntfile: + free(r); + return FALSE; +} + +int +expand(Text *t, uint q0, uint q1, Expand *e) +{ + memset(e, 0, sizeof *e); + e->agetc = tgetc; + /* if in selection, choose selection */ + e->jump = TRUE; + if(q1==q0 && t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){ + q0 = t->q0; + q1 = t->q1; + if(t->what == Tag) + e->jump = FALSE; + } + + if(expandfile(t, q0, q1, e)) + return TRUE; + + if(q0 == q1){ + while(q1<t->file->b.nc && isalnum(textreadc(t, q1))) + q1++; + while(q0>0 && isalnum(textreadc(t, q0-1))) + q0--; + } + e->q0 = q0; + e->q1 = q1; + return q1 > q0; +} + +Window* +lookfile(Rune *s, int n) +{ + int i, j, k; + Window *w; + Column *c; + Text *t; + + /* avoid terminal slash on directories */ + if(n>1 && s[n-1] == '/') + --n; + for(j=0; j<row.ncol; j++){ + c = row.col[j]; + for(i=0; i<c->nw; i++){ + w = c->w[i]; + t = &w->body; + k = t->file->nname; + if(k>1 && t->file->name[k-1] == '/') + k--; + if(runeeq(t->file->name, k, s, n)){ + w = w->body.file->curtext->w; + if(w->col != nil) /* protect against race deleting w */ + return w; + } + } + } + return nil; +} + +Window* +lookid(int id, int dump) +{ + int i, j; + Window *w; + Column *c; + + for(j=0; j<row.ncol; j++){ + c = row.col[j]; + for(i=0; i<c->nw; i++){ + w = c->w[i]; + if(dump && w->dumpid == id) + return w; + if(!dump && w->id == id) + return w; + } + } + return nil; +} + + +Window* +openfile(Text *t, Expand *e) +{ + Range r; + Window *w, *ow; + int eval, i, n; + Rune *rp; + 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); + 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); + } +}
A 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;
A mail/guide

@@ -0,0 +1,4 @@

+Mail stored +plumb /mail/box/$user/names +mail -'x' someaddress +mkbox /mail/box/$user/new_box
A 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; +}
A 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; +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; + } + } + } +}
A 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); +}
A 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 +}
A 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 +
A 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.
A 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; +}
A 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"); +}
A 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; +}
A mkfile

@@ -0,0 +1,52 @@

+<$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\ + +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/;;'
A 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; + } +}
A rows.c

@@ -0,0 +1,821 @@

+#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; + 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; + } + fontname = ""; + if(t->reffont->f != font) + fontname = t->reffont->f->name; + if(t->file->nname) + a = runetobyte(t->file->name, t->file->nname); + else + a = emalloc(1); + if(t->file->dumpid){ + dumped = FALSE; + Bprint(b, "x%11d %11d %11d %11d %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(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); + } +}
A 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); +}
A 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); +}
A 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; +}
A 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; +}
A wind.c

@@ -0,0 +1,725 @@

+#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{ + 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 ", buf, Dx(w->body.fr.r), + w->body.reffont->f->name, w->body.fr.maxtab); + 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); + } +}
A xfid.c

@@ -0,0 +1,1143 @@

+#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); + 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); + 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); +}