From 690197f35d10c46cf487c496e33f08228ffe0c32 Mon Sep 17 00:00:00 2001 From: Derek Stevens Date: Thu, 14 Nov 2019 18:15:48 -0500 Subject: [PATCH] first commit --- acme.c | 1172 ++++++++++++++++++++++++++++++++ addr.c | 295 +++++++++ buff.c | 325 +++++++++ cols.c | 586 ++++++++++++++++ dat.h | 581 ++++++++++++++++ disk.c | 133 ++++ ecmd.c | 1377 ++++++++++++++++++++++++++++++++++++++ edit.c | 684 +++++++++++++++++++ edit.h | 99 +++ elog.c | 354 ++++++++++ exec.c | 1803 ++++++++++++++++++++++++++++++++++++++++++++++++++ file.c | 311 +++++++++ fns.h | 106 +++ fsys.c | 749 +++++++++++++++++++++ logf.c | 199 ++++++ look.c | 872 ++++++++++++++++++++++++ mail/dat.h | 181 +++++ mail/guide | 4 + mail/html.c | 75 +++ mail/html.o | Bin 0 -> 17352 bytes mail/mail.c | 644 ++++++++++++++++++ mail/mail.o | Bin 0 -> 65248 bytes mail/mesg.c | 1424 +++++++++++++++++++++++++++++++++++++++ mail/mesg.o | Bin 0 -> 118240 bytes mail/mkbox | 11 + mail/mkfile | 15 + mail/o.Mail | Bin 0 -> 392472 bytes mail/readme | 57 ++ mail/reply.c | 580 ++++++++++++++++ mail/reply.o | Bin 0 -> 55328 bytes mail/util.c | 107 +++ mail/util.o | Bin 0 -> 20784 bytes mail/win.c | 379 +++++++++++ mail/win.o | Bin 0 -> 39264 bytes mkfile | 52 ++ nilix@zero. | Bin 0 -> 1074925 bytes regx.c | 840 +++++++++++++++++++++++ rows.c | 809 ++++++++++++++++++++++ scrl.c | 159 +++++ text.c | 1664 ++++++++++++++++++++++++++++++++++++++++++++++ time.c | 124 ++++ util.c | 495 ++++++++++++++ wind.c | 708 ++++++++++++++++++++ xfid.c | 1125 +++++++++++++++++++++++++++++++ 44 files changed, 19099 insertions(+) create mode 100644 acme.c create mode 100644 addr.c create mode 100644 buff.c create mode 100644 cols.c create mode 100644 dat.h create mode 100644 disk.c create mode 100644 ecmd.c create mode 100644 edit.c create mode 100644 edit.h create mode 100644 elog.c create mode 100644 exec.c create mode 100644 file.c create mode 100644 fns.h create mode 100644 fsys.c create mode 100644 logf.c create mode 100644 look.c create mode 100644 mail/dat.h create mode 100644 mail/guide create mode 100644 mail/html.c create mode 100644 mail/html.o create mode 100644 mail/mail.c create mode 100644 mail/mail.o create mode 100644 mail/mesg.c create mode 100644 mail/mesg.o create mode 100644 mail/mkbox create mode 100644 mail/mkfile create mode 100755 mail/o.Mail create mode 100644 mail/readme create mode 100644 mail/reply.c create mode 100644 mail/reply.o create mode 100644 mail/util.c create mode 100644 mail/util.o create mode 100644 mail/win.c create mode 100644 mail/win.o create mode 100644 mkfile create mode 100644 nilix@zero. create mode 100644 regx.c create mode 100644 rows.c create mode 100644 scrl.c create mode 100644 text.c create mode 100644 time.c create mode 100644 util.c create mode 100644 wind.c create mode 100644 xfid.c diff --git a/acme.c b/acme.c new file mode 100644 index 0000000..3f3d89e --- /dev/null +++ b/acme.c @@ -0,0 +1,1172 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dat.h" +#include "fns.h" + /* for generating syms in mkfile only: */ + #include + #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/typeunicode.7.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=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, *s; + int n; + + USED(v); + threadsetname("acmeerrorproc"); + buf = emalloc(8192+1); + while((n=read(errorfd, buf, 8192)) >= 0){ + buf[n] = '\0'; + s = estrdup(buf); + sendp(cerr, s); + free(s); + } + 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 = ± + 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; if->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) + 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) { + /* Black */ + tagcols[BACK] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DBlack); + tagcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x1F9B92FF); + tagcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x797979FF); + tagcols[TEXT] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x797979FF); + tagcols[HTEXT] = display->black; + + /* Blue */ + textcols[BACK] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x000F19FF); + textcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x1F9B92FF); + textcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x93A1A1FF); + textcols[TEXT] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x93A1A1FF); + 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, tagcols[BORD], ZP); + + r = button->r; + modbutton = allocimage(display, r, screen->chan, 0, DNofill); + draw(modbutton, r, tagcols[BACK], nil, r.min); + r.max.x -= ButtonBorder; + border(modbutton, r, ButtonBorder, tagcols[BORD], ZP); + r = insetrect(r, ButtonBorder); + tmp = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x09998DFF); + draw(modbutton, r, tmp, nil, ZP); + freeimage(tmp); + + r = button->r; + colbutton = allocimage(display, r, screen->chan, 0, 0x586E75FF); + + but2col = allocimage(display, r, screen->chan, 1, 0x797979FF); + but3col = allocimage(display, r, screen->chan, 1, 0x36D3C6FF); +} + +/* + * /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= NSnarf) + n = NSnarf; + bufread(&snarfbuf, i, snarfrune, n); + if(fmtprint(&f, "%.*S", n, snarfrune) < 0) + break; + } + s = fmtstrflush(&f); + if(s && s[0]) + putsnarf(s); + free(s); +} + +void +acmegetsnarf(void) +{ + char *s; + int nb, nr, nulls, len; + Rune *r; + + s = getsnarf(); + if(s == nil || s[0]==0){ + free(s); + return; + } + + len = strlen(s); + r = runemalloc(len+1); + cvttorunes(s, len, r, &nb, &nr, &nulls); + bufreset(&snarfbuf); + bufinsert(&snarfbuf, 0, r, nr); + free(r); + free(s); +} + +int +ismtpt(char *file) +{ + int n; + + if(mtpt == nil) + return 0; + + /* This is not foolproof, but it will stop a lot of them. */ + n = strlen(mtpt); + return strncmp(file, mtpt, n) == 0 && ((n > 0 && mtpt[n-1] == '/') || file[n] == '/' || file[n] == 0); +} + +int +timefmt(Fmt *f) +{ + Tm *tm; + + tm = localtime(va_arg(f->args, ulong)); + return fmtprint(f, "%04d/%02d/%02d %02d:%02d:%02d", + tm->year+1900, tm->mon+1, tm->mday, tm->hour, tm->min, tm->sec); +} + diff --git a/addr.c b/addr.c new file mode 100644 index 0000000..6aee899 --- /dev/null +++ b/addr.c @@ -0,0 +1,295 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 && q1file->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(q1file->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' +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 && q0cq+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->cnccq){ + q = 0; + i = 0; + }else{ + q = b->cq; + i = b->cbi; + } + blp = &b->bl[i]; + while(q+(*blp)->u.n <= q0 && q+(*blp)->u.n < b->nc){ + q += (*blp)->u.n; + i++; + blp++; + if(i >= b->nbl) + error("block not found"); + } + bl = *blp; + /* remember position */ + b->cbi = i; + b->cq = q; + sizecache(b, bl->u.n); + b->cnc = bl->u.n; + /*read block*/ + diskread(disk, bl, b->c, b->cnc); +} + +void +bufinsert(Buffer *b, uint q0, Rune *s, uint n) +{ + uint i, m, t, off; + + if(q0 > b->nc) + error("internal error: bufinsert"); + + while(n > 0){ + setcache(b, q0); + off = q0-b->cq; + if(b->cnc+n <= Maxblock){ + /* Everything fits in one block. */ + t = b->cnc+n; + m = n; + if(b->bl == nil){ /* allocate */ + if(b->cnc != 0) + error("internal error: bufinsert1 cnc!=0"); + addblock(b, 0, t); + b->cbi = 0; + } + sizecache(b, t); + runemove(b->c+off+m, b->c+off, b->cnc-off); + runemove(b->c+off, s, m); + b->cnc = t; + goto Tail; + } + /* + * We must make a new block. If q0 is at + * the very beginning or end of this block, + * just make a new block and fill it. + */ + if(q0==b->cq || q0==b->cq+b->cnc){ + if(b->cdirty) + flush(b); + m = min(n, Maxblock); + if(b->bl == nil){ /* allocate */ + if(b->cnc != 0) + error("internal error: bufinsert2 cnc!=0"); + i = 0; + }else{ + i = b->cbi; + if(q0 > b->cq) + i++; + } + addblock(b, i, m); + sizecache(b, m); + runemove(b->c, s, m); + b->cq = q0; + b->cbi = i; + b->cnc = m; + goto Tail; + } + /* + * Split the block; cut off the right side and + * let go of it. + */ + m = b->cnc-off; + if(m > 0){ + i = b->cbi+1; + addblock(b, i, m); + diskwrite(disk, &b->bl[i], b->c+off, m); + b->cnc -= m; + } + /* + * Now at end of block. Take as much input + * as possible and tack it on end of block. + */ + m = min(n, Maxblock-b->cnc); + sizecache(b, b->cnc+m); + runemove(b->c+b->cnc, s, m); + b->cnc += m; + Tail: + b->nc += m; + q0 += m; + s += m; + n -= m; + b->cdirty = TRUE; + } +} + +void +bufdelete(Buffer *b, uint q0, uint q1) +{ + uint m, n, off; + + if(!(q0<=q1 && q0<=b->nc && q1<=b->nc)) + error("internal error: bufdelete"); + while(q1 > q0){ + setcache(b, q0); + off = q0-b->cq; + if(q1 > b->cq+b->cnc) + n = b->cnc - off; + else + n = q1-q0; + m = b->cnc - (off+n); + if(m > 0) + runemove(b->c+off, b->c+off+n, m); + b->cnc -= n; + b->cdirty = TRUE; + q1 -= n; + b->nc -= n; + } +} + +static int +bufloader(void *v, uint q0, Rune *r, int nr) +{ + bufinsert(v, q0, r, nr); + return nr; +} + +uint +loadfile(int fd, uint q0, int *nulls, int(*f)(void*, uint, Rune*, int), void *arg, DigestState *h) +{ + char *p; + Rune *r; + int l, m, n, nb, nr; + uint q1; + + p = emalloc((Maxblock+UTFmax+1)*sizeof p[0]); + r = runemalloc(Maxblock); + m = 0; + n = 1; + q1 = q0; + /* + * At top of loop, may have m bytes left over from + * last pass, possibly representing a partial rune. + */ + while(n > 0){ + n = read(fd, p+m, Maxblock); + if(n < 0){ + warning(nil, "read error in Buffer.load"); + break; + } + if(h != nil) + sha1((uchar*)p+m, n, nil, h); + m += n; + p[m] = 0; + l = m; + if(n > 0) + l -= UTFmax; + cvttorunes(p, l, r, &nb, &nr, nulls); + memmove(p, p+nb, m-nb); + m -= nb; + q1 += (*f)(arg, q1, r, nr); + } + free(p); + free(r); + return q1-q0; +} + +uint +bufload(Buffer *b, uint q0, int fd, int *nulls, DigestState *h) +{ + if(q0 > b->nc) + error("internal error: bufload"); + return loadfile(fd, q0, nulls, bufloader, b, h); +} + +void +bufread(Buffer *b, uint q0, Rune *s, uint n) +{ + uint m; + + if(!(q0<=b->nc && q0+n<=b->nc)) + error("bufread: internal error"); + + while(n > 0){ + setcache(b, q0); + m = min(n, b->cnc-(q0-b->cq)); + runemove(s, b->c+(q0-b->cq), m); + q0 += m; + s += m; + n -= m; + } +} + +void +bufreset(Buffer *b) +{ + int i; + + b->nc = 0; + b->cnc = 0; + b->cq = 0; + b->cdirty = 0; + b->cbi = 0; + /* delete backwards to avoid n² behavior */ + for(i=b->nbl-1; --i>=0; ) + delblock(b, i); +} + +void +bufclose(Buffer *b) +{ + bufreset(b); + free(b->c); + b->c = nil; + b->cnc = 0; + free(b->bl); + b->bl = nil; + b->nbl = 0; +} diff --git a/cols.c b/cols.c new file mode 100644 index 0000000..0c7aaf0 --- /dev/null +++ b/cols.c @@ -0,0 +1,586 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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, allocimage(display,Rect(0,0,1,1), screen->chan, 1, 0x222222FF), 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, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), 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(ynw>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; inw; 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, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), 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; inw; 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, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), 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; inw; i++){ + w = c->w[i]; + winclose(w); + } + c->nw = 0; + free(c->w); + free(c); + clearmouse(); +} + +void +colmousebut(Column *c) +{ + moveto(mousectl, divpt(addpt(c->tag.scrollr.min, c->tag.scrollr.max), 2)); +} + +void +colresize(Column *c, Rectangle r) +{ + int i; + Rectangle r1, r2; + Window *w; + + clearmouse(); + r1 = r; + r1.max.y = r1.min.y + c->tag.fr.font->height; + textresize(&c->tag, r1, TRUE); + draw(screen, c->tag.scrollr, colbutton, nil, colbutton->r.min); + r1.min.y = r1.max.y; + r1.max.y += Border; + draw(screen, r1, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), nil, ZP); + r1.max.y = r.max.y; + for(i=0; inw; 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(Dy(c->r) != 0){ + r1.max.y += (Dy(w->r)+Border)*Dy(r)/Dy(c->r); + } + } + 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, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), 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; inw == 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; inw; 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; inw; 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, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), 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; inw; 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; inw; 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; jnw; 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; knw; k++){ + /* prune from later window */ + j = i+k; + if(jnw && 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; jw[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, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), 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, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), nil, ZP); + for(j=i+1; jnw; j++) + ny[j] -= (y2-r.max.y); + } + /* pack everyone below */ + y1 = r.max.y; + for(j=i+1; jnw; 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, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), 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; inw; 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.yw[i-1]->r.min.y) || (inw-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, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), 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; inw; 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; inw; i++) + clean &= winclean(c->w[i], TRUE); + return clean; +} diff --git a/dat.h b/dat.h new file mode 100644 index 0000000..8a81c97 --- /dev/null +++ b/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 diff --git a/disk.c b/disk.c new file mode 100644 index 0000000..c3ada9c --- /dev/null +++ b/disk.c @@ -0,0 +1,133 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dat.h" +#include "fns.h" + +static Block *blist; + +int +tempfile(void) +{ + char buf[128]; + int i, fd; + + snprint(buf, sizeof buf, "/tmp/X%d.%.4sacme", getpid(), getuser()); + for(i='A'; i<='Z'; i++){ + buf[5] = i; + if(access(buf, AEXIST) == 0) + continue; + fd = create(buf, ORDWR|ORCLOSE|OCEXEC, 0600); + if(fd >= 0) + return fd; + } + return -1; +} + +Disk* +diskinit() +{ + Disk *d; + + d = emalloc(sizeof(Disk)); + d->fd = tempfile(); + if(d->fd < 0){ + fprint(2, "acme: can't create temp file: %r\n"); + threadexitsall("diskinit"); + } + return d; +} + +static +uint +ntosize(uint n, uint *ip) +{ + uint size; + + if(n > Maxblock) + error("internal error: ntosize"); + size = n; + if(size & (Blockincr-1)) + size += Blockincr - (size & (Blockincr-1)); + /* last bucket holds blocks of exactly Maxblock */ + if(ip) + *ip = size/Blockincr; + return size * sizeof(Rune); +} + +Block* +disknewblock(Disk *d, uint n) +{ + uint i, j, size; + Block *b; + + size = ntosize(n, &i); + b = d->free[i]; + if(b) + d->free[i] = b->u.next; + else{ + /* allocate in chunks to reduce malloc overhead */ + if(blist == nil){ + blist = emalloc(100*sizeof(Block)); + for(j=0; j<100-1; j++) + blist[j].u.next = &blist[j+1]; + } + b = blist; + blist = b->u.next; + b->addr = d->addr; + if(d->addr+size < d->addr){ + error("temp file overflow"); + } + d->addr += size; + } + b->u.n = n; + return b; +} + +void +diskrelease(Disk *d, Block *b) +{ + uint i; + + ntosize(b->u.n, &i); + b->u.next = d->free[i]; + d->free[i] = b; +} + +void +diskwrite(Disk *d, Block **bp, Rune *r, uint n) +{ + int size, nsize; + Block *b; + + b = *bp; + size = ntosize(b->u.n, nil); + nsize = ntosize(n, nil); + if(size != nsize){ + diskrelease(d, b); + b = disknewblock(d, n); + *bp = b; + } + if(pwrite(d->fd, r, n*sizeof(Rune), b->addr) != n*sizeof(Rune)) + error("write error to temp file"); + b->u.n = n; +} + +void +diskread(Disk *d, Block *b, Rune *r, uint n) +{ + if(n > b->u.n) + error("internal error: diskread"); + + ntosize(b->u.n, nil); + if(pread(d->fd, r, n*sizeof(Rune), b->addr) != n*sizeof(Rune)) + error("read error from temp file"); +} diff --git a/ecmd.c b/ecmd.c new file mode 100644 index 0000000..ef92e33 --- /dev/null +++ b/ecmd.c @@ -0,0 +1,1377 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dat.h" +#include "edit.h" +#include "fns.h" + +int Glooping; +int nest; +char Enoname[] = "no file name given"; + +Address addr; +File *menu; +Rangeset sel; +extern Text* curtext; +Rune *collection; +int ncollection; + +int append(File*, Cmd*, long); +int pdisplay(File*); +void pfilename(File*); +void looper(File*, Cmd*, int); +void filelooper(Cmd*, int); +void linelooper(File*, Cmd*); +Address lineaddr(long, Address, int); +int filematch(File*, String*); +File *tofile(String*); +Rune* cmdname(File *f, String *s, int); +void runpipe(Text*, int, Rune*, int, int); + +void +clearcollection(void) +{ + free(collection); + collection = nil; + ncollection = 0; +} + +void +resetxec(void) +{ + Glooping = nest = 0; + clearcollection(); +} + +void +mkaddr(Address *a, File *f) +{ + a->r.q0 = f->curtext->q0; + a->r.q1 = f->curtext->q1; + a->f = f; +} + +int +cmdexec(Text *t, Cmd *cp) +{ + int i; + Addr *ap; + File *f; + Window *w; + Address dot; + + if(t == nil) + w = nil; + else + w = t->w; + if(w==nil && (cp->addr==0 || cp->addr->type!='"') && + !utfrune("bBnqUXY!", cp->cmdc) && + !(cp->cmdc=='D' && cp->u.text)) + editerror("no current window"); + i = cmdlookup(cp->cmdc); /* will be -1 for '{' */ + f = nil; + if(t && t->w){ + t = &t->w->body; + f = t->file; + f->curtext = t; + } + if(i>=0 && cmdtab[i].defaddr != aNo){ + if((ap=cp->addr)==0 && cp->cmdc!='\n'){ + cp->addr = ap = newaddr(); + ap->type = '.'; + if(cmdtab[i].defaddr == aAll) + ap->type = '*'; + }else if(ap && ap->type=='"' && ap->next==0 && cp->cmdc!='\n'){ + ap->next = newaddr(); + ap->next->type = '.'; + if(cmdtab[i].defaddr == aAll) + ap->next->type = '*'; + } + if(cp->addr){ /* may be false for '\n' (only) */ + static Address none = {0,0,nil}; + if(f){ + mkaddr(&dot, f); + addr = cmdaddress(ap, dot, 0); + }else /* a " */ + addr = cmdaddress(ap, none, 0); + f = addr.f; + t = f->curtext; + } + } + switch(cp->cmdc){ + case '{': + mkaddr(&dot, f); + if(cp->addr != nil) + dot = cmdaddress(cp->addr, dot, 0); + for(cp = cp->u.cmd; cp; cp = cp->next){ + if(dot.r.q1 > t->file->b.nc) + editerror("dot extends past end of buffer during { command"); + t->q0 = dot.r.q0; + t->q1 = dot.r.q1; + cmdexec(t, cp); + } + break; + default: + if(i < 0) + editerror("unknown command %c in cmdexec", cp->cmdc); + i = (*cmdtab[i].fn)(t, cp); + return i; + } + return 1; +} + +char* +edittext(Window *w, int q, Rune *r, int nr) +{ + File *f; + + f = w->body.file; + switch(editing){ + case Inactive: + return "permission denied"; + case Inserting: + eloginsert(f, q, r, nr); + return nil; + case Collecting: + collection = runerealloc(collection, ncollection+nr+1); + runemove(collection+ncollection, r, nr); + ncollection += nr; + collection[ncollection] = '\0'; + return nil; + default: + return "unknown state in edittext"; + } +} + +/* string is known to be NUL-terminated */ +Rune* +filelist(Text *t, Rune *r, int nr) +{ + if(nr == 0) + return nil; + r = skipbl(r, nr, &nr); + if(r[0] != '<') + return runestrdup(r); + /* use < command to collect text */ + clearcollection(); + runpipe(t, '<', r+1, nr-1, Collecting); + return collection; +} + +int +a_cmd(Text *t, Cmd *cp) +{ + return append(t->file, cp, addr.r.q1); +} + +int +b_cmd(Text *t, Cmd *cp) +{ + File *f; + + USED(t); + f = tofile(cp->u.text); + if(nest == 0) + pfilename(f); + curtext = f->curtext; + return TRUE; +} + +int +B_cmd(Text *t, Cmd *cp) +{ + Rune *list, *r, *s; + int nr; + + list = filelist(t, cp->u.text->r, cp->u.text->n); + if(list == nil) + editerror(Enoname); + r = list; + nr = runestrlen(r); + r = skipbl(r, nr, &nr); + if(nr == 0) + new(t, t, nil, 0, 0, r, 0); + else while(nr > 0){ + s = findbl(r, nr, &nr); + *s = '\0'; + new(t, t, nil, 0, 0, r, runestrlen(r)); + if(nr > 0) + r = skipbl(s+1, nr-1, &nr); + } + clearcollection(); + return TRUE; +} + +int +c_cmd(Text *t, Cmd *cp) +{ + elogreplace(t->file, addr.r.q0, addr.r.q1, cp->u.text->r, cp->u.text->n); + t->q0 = addr.r.q0; + t->q1 = addr.r.q1; + return TRUE; +} + +int +d_cmd(Text *t, Cmd *cp) +{ + USED(cp); + if(addr.r.q1 > addr.r.q0) + elogdelete(t->file, addr.r.q0, addr.r.q1); + t->q0 = addr.r.q0; + t->q1 = addr.r.q0; + return TRUE; +} + +void +D1(Text *t) +{ + if(t->w->body.file->ntext>1 || winclean(t->w, FALSE)) + colclose(t->col, t->w, TRUE); +} + +int +D_cmd(Text *t, Cmd *cp) +{ + Rune *list, *r, *s, *n; + int nr, nn; + Window *w; + Runestr dir, rs; + char buf[128]; + + list = filelist(t, cp->u.text->r, cp->u.text->n); + if(list == nil){ + D1(t); + return TRUE; + } + dir = dirname(t, nil, 0); + r = list; + nr = runestrlen(r); + r = skipbl(r, nr, &nr); + do{ + s = findbl(r, nr, &nr); + *s = '\0'; + /* first time through, could be empty string, meaning delete file empty name */ + nn = runestrlen(r); + if(r[0]=='/' || nn==0 || dir.nr==0){ + rs.r = runestrdup(r); + rs.nr = nn; + }else{ + n = runemalloc(dir.nr+1+nn); + runemove(n, dir.r, dir.nr); + n[dir.nr] = '/'; + runemove(n+dir.nr+1, r, nn); + rs = cleanrname(runestr(n, dir.nr+1+nn)); + } + w = lookfile(rs.r, rs.nr); + if(w == nil){ + snprint(buf, sizeof buf, "no such file %.*S", rs.nr, rs.r); + free(rs.r); + editerror(buf); + } + free(rs.r); + D1(&w->body); + if(nr > 0) + r = skipbl(s+1, nr-1, &nr); + }while(nr > 0); + clearcollection(); + free(dir.r); + return TRUE; +} + +static int +readloader(void *v, uint q0, Rune *r, int nr) +{ + if(nr > 0) + eloginsert(v, q0, r, nr); + return 0; +} + +int +e_cmd(Text *t, Cmd *cp) +{ + Rune *name; + File *f; + int i, isdir, q0, q1, fd, nulls, samename, allreplaced; + char *s, tmp[128]; + Dir *d; + + f = t->file; + q0 = addr.r.q0; + q1 = addr.r.q1; + if(cp->cmdc == 'e'){ + if(winclean(t->w, TRUE)==FALSE) + editerror(""); /* winclean generated message already */ + q0 = 0; + q1 = f->b.nc; + } + allreplaced = (q0==0 && q1==f->b.nc); + name = cmdname(f, cp->u.text, cp->cmdc=='e'); + if(name == nil) + editerror(Enoname); + i = runestrlen(name); + samename = runeeq(name, i, t->file->name, t->file->nname); + s = runetobyte(name, i); + free(name); + fd = open(s, OREAD); + if(fd < 0){ + snprint(tmp, sizeof tmp, "can't open %s: %r", s); + free(s); + editerror(tmp); + } + d = dirfstat(fd); + isdir = (d!=nil && (d->qid.type&QTDIR)); + free(d); + if(isdir){ + close(fd); + snprint(tmp, sizeof tmp, "%s is a directory", s); + free(s); + editerror(tmp); + } + elogdelete(f, q0, q1); + nulls = 0; + loadfile(fd, q1, &nulls, readloader, f, nil); + free(s); + close(fd); + if(nulls) + warning(nil, "%s: NUL bytes elided\n", s); + else if(allreplaced && samename) + f->editclean = TRUE; + return TRUE; +} + +static Rune Lempty[] = { 0 }; +int +f_cmd(Text *t, Cmd *cp) +{ + Rune *name; + String *str; + String empty; + + if(cp->u.text == nil){ + empty.n = 0; + empty.r = Lempty; + str = ∅ + }else + str = cp->u.text; + name = cmdname(t->file, str, TRUE); + free(name); + pfilename(t->file); + return TRUE; +} + +int +g_cmd(Text *t, Cmd *cp) +{ + if(t->file != addr.f){ + warning(nil, "internal error: g_cmd f!=addr.f\n"); + return FALSE; + } + if(rxcompile(cp->re->r) == FALSE) + editerror("bad regexp in g command"); + if(rxexecute(t, nil, addr.r.q0, addr.r.q1, &sel) ^ cp->cmdc=='v'){ + t->q0 = addr.r.q0; + t->q1 = addr.r.q1; + return cmdexec(t, cp->u.cmd); + } + return TRUE; +} + +int +i_cmd(Text *t, Cmd *cp) +{ + return append(t->file, cp, addr.r.q0); +} + +void +copy(File *f, Address addr2) +{ + long p; + int ni; + Rune *buf; + + buf = fbufalloc(); + for(p=addr.r.q0; p 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; mn = 0; + buf->r[0] = '\0'; + sel = rp[m]; + for(i = 0; iu.text->n; i++) + if((c = cp->u.text->r[i])=='\\' && iu.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; kRBUFSIZE){ + 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; kfile, 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(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; icurtext->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) + break; + if(linesel.q1 >= r.q1) + linesel.q1 = r.q1; + if(linesel.q1 > linesel.q0) + if(linesel.q0>=a3.r.q1 && linesel.q1>a3.r.q1){ + a3.r = linesel; + nrp++; + rp = erealloc(rp, nrp*sizeof(Range)); + rp[nrp-1] = linesel; + continue; + } + break; + } + loopcmd(f, cp->u.cmd, rp, nrp); + free(rp); + --nest; +} + +struct Looper +{ + Cmd *cp; + int XY; + Window **w; + int nw; +} loopstruct; /* only one; X and Y can't nest */ + +void +alllooper(Window *w, void *v) +{ + Text *t; + struct Looper *lp; + Cmd *cp; + + lp = v; + cp = lp->cp; +/* if(w->isscratch || w->isdir) */ +/* return; */ + t = &w->body; + /* only use this window if it's the current window for the file */ + if(t->file->curtext != t) + return; +/* if(w->nopen[QWevent] > 0) */ +/* return; */ + /* no auto-execute on files without names */ + if(cp->re==nil && t->file->nname==0) + return; + if(cp->re==nil || filematch(t->file, cp->re)==lp->XY){ + lp->w = erealloc(lp->w, (lp->nw+1)*sizeof(Window*)); + lp->w[lp->nw++] = w; + } +} + +void +alllocker(Window *w, void *v) +{ + if(v) + incref(&w->ref); + else + winclose(w); +} + +void +filelooper(Cmd *cp, int XY) +{ + int i; + + if(Glooping++) + editerror("can't nest %c command", "YX"[XY]); + nest++; + + loopstruct.cp = cp; + loopstruct.XY = XY; + if(loopstruct.w) /* error'ed out last time */ + free(loopstruct.w); + loopstruct.w = nil; + loopstruct.nw = 0; + allwindows(alllooper, &loopstruct); + /* + * add a ref to all windows to keep safe windows accessed by X + * that would not otherwise have a ref to hold them up during + * the shenanigans. note this with globalincref so that any + * newly created windows start with an extra reference. + */ + allwindows(alllocker, (void*)1); + globalincref = 1; + for(i=0; ibody, cp->u.cmd); + 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; ncurtext, p-1); + if(c != '\n' || ++n != l) + p--; + } + } + a.r.q1 = p; + if(p > 0) + p--; + } + while(p > 0 && textreadc(f->curtext, p-1)!='\n') /* lines start after a newline */ + p--; + a.r.q0 = p; + } + return a; +} + +struct Filecheck +{ + File *f; + Rune *r; + int nr; +}; + +void +allfilecheck(Window *w, void *v) +{ + struct Filecheck *fp; + File *f; + + fp = v; + f = w->body.file; + if(w->body.file == fp->f) + return; + if(runeeq(fp->r, fp->nr, f->name, f->nname)) + warning(nil, "warning: duplicate file name \"%.*S\"\n", fp->nr, fp->r); +} + +Rune* +cmdname(File *f, String *str, int set) +{ + Rune *r, *s; + int n; + struct Filecheck fc; + Runestr newname; + + r = nil; + n = str->n; + s = str->r; + if(n == 0){ + /* no name; use existing */ + if(f->nname == 0) + return nil; + r = runemalloc(f->nname+1); + runemove(r, f->name, f->nname); + return r; + } + s = skipbl(s, n, &n); + if(n == 0) + goto Return; + + if(s[0] == '/'){ + r = runemalloc(n+1); + runemove(r, s, n); + }else{ + newname = dirname(f->curtext, runestrdup(s), n); + n = newname.nr; + r = runemalloc(n+1); /* NUL terminate */ + runemove(r, newname.r, n); + free(newname.r); + } + fc.f = f; + fc.r = r; + fc.nr = n; + allwindows(allfilecheck, &fc); + if(f->nname == 0) + set = TRUE; + + Return: + if(set && !runeeq(r, n, f->name, f->nname)){ + filemark(f); + f->mod = TRUE; + f->curtext->w->dirty = TRUE; + winsetname(f->curtext->w, r, n); + } + return r; +} diff --git a/edit.c b/edit.c new file mode 100644 index 0000000..d3f8205 --- /dev/null +++ b/edit.c @@ -0,0 +1,684 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; intext; 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'= 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!='"') + case '"': + editerror("bad address syntax"); + break; + case 'l': + case '#': + if(addr.type=='"') + break; + /* fall through */ + case '/': + case '?': + if(addr.type!='+' && addr.type!='-'){ + /* insert the missing '+' */ + nap = newaddr(); + nap->type='+'; + nap->next = addr.next; + addr.next = nap; + } + break; + case '+': + case '-': + break; + default: + error("simpleaddr"); + } + ap = newaddr(); + *ap = addr; + return ap; +} + +Addr * +compoundaddr(void) +{ + Addr addr; + Addr *ap, *next; + + addr.u.left = simpleaddr(); + if((addr.type = cmdskipbl())!=',' && addr.type!=';') + return addr.u.left; + getch(); + next = addr.next = compoundaddr(); + if(next && (next->type==',' || next->type==';') && next->u.left==0) + editerror("bad address syntax"); + ap = newaddr(); + *ap = addr; + return ap; +} diff --git a/edit.h b/edit.h new file mode 100644 index 0000000..2320ee5 --- /dev/null +++ b/edit.h @@ -0,0 +1,99 @@ +/*#pragma varargck argpos editerror 1*/ + +typedef struct Addr Addr; +typedef struct Address Address; +typedef struct Cmd Cmd; +typedef struct List List; +typedef struct String String; + +struct String +{ + int n; /* excludes NUL */ + Rune *r; /* includes NUL */ + int nalloc; +}; + +struct Addr +{ + char type; /* # (char addr), l (line addr), / ? . $ + - , ; */ + union{ + String *re; + Addr *left; /* left side of , and ; */ + } u; + ulong num; + Addr *next; /* or right side of , and ; */ +}; + +struct Address +{ + Range r; + File *f; +}; + +struct Cmd +{ + Addr *addr; /* address (range of text) */ + String *re; /* regular expression for e.g. 'x' */ + union{ + Cmd *cmd; /* target of x, g, {, etc. */ + String *text; /* text of a, c, i; rhs of s */ + Addr *mtaddr; /* address for m, t */ + } u; + Cmd *next; /* pointer to next element in {} */ + short num; + ushort flag; /* whatever */ + ushort cmdc; /* command character; 'x' etc. */ +}; + +extern struct cmdtab{ + ushort cmdc; /* command character */ + uchar text; /* takes a textual argument? */ + uchar regexp; /* takes a regular expression? */ + uchar addr; /* takes an address (m or t)? */ + uchar defcmd; /* default command; 0==>none */ + uchar defaddr; /* default address */ + uchar count; /* takes a count e.g. s2/// */ + char *token; /* takes text terminated by one of these */ + int (*fn)(Text*, Cmd*); /* function to call with parse tree */ +}cmdtab[]; + +#define INCR 25 /* delta when growing list */ + +struct List /* code depends on a long being able to hold a pointer */ +{ + int nalloc; + int nused; + union{ + void *listptr; + void* *ptr; + uchar* *ucharptr; + String* *stringptr; + } u; +}; + +enum Defaddr{ /* default addresses */ + aNo, + aDot, + aAll +}; + +int nl_cmd(Text*, Cmd*), a_cmd(Text*, Cmd*), b_cmd(Text*, Cmd*); +int c_cmd(Text*, Cmd*), d_cmd(Text*, Cmd*); +int B_cmd(Text*, Cmd*), D_cmd(Text*, Cmd*), e_cmd(Text*, Cmd*); +int f_cmd(Text*, Cmd*), g_cmd(Text*, Cmd*), i_cmd(Text*, Cmd*); +int k_cmd(Text*, Cmd*), m_cmd(Text*, Cmd*), n_cmd(Text*, Cmd*); +int p_cmd(Text*, Cmd*); +int s_cmd(Text*, Cmd*), u_cmd(Text*, Cmd*), w_cmd(Text*, Cmd*); +int x_cmd(Text*, Cmd*), X_cmd(Text*, Cmd*), pipe_cmd(Text*, Cmd*); +int eq_cmd(Text*, Cmd*); + +String *allocstring(int); +void freestring(String*); +String *getregexp(int); +Addr *newaddr(void); +Address cmdaddress(Addr*, Address, int); +int cmdexec(Text*, Cmd*); +void editerror(char*, ...); +int cmdlookup(int); +void resetxec(void); +void Straddc(String*, int); diff --git a/elog.c b/elog.c new file mode 100644 index 0000000..c5650f0 --- /dev/null +++ b/elog.c @@ -0,0 +1,354 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 && q0elog.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 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 && q0elog.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+nrelog.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 && q0elog.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 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 RBUFSIZE) + n = RBUFSIZE; + bufread(log, up+i, buf, n); + textinsert(t, tq0+i, buf, n, TRUE); + } + if(t->q0 == b.q0 && t->q1 == b.q0) + t->q1 += b.nr; + break; + +/* case Filename: + f->seq = u.seq; + fileunsetname(f, epsilon); + f->mod = u.mod; + up -= u.n; + free(f->name); + if(u.n == 0) + f->name = nil; + else + f->name = runemalloc(u.n); + bufread(delta, up, f->name, u.n); + f->nname = u.n; + break; +*/ + } + bufdelete(log, up, log->nc); + } + fbuffree(buf); + elogterm(f); + + /* + * Bad addresses will cause bufload to crash, so double check. + * If changes were out of order, we expect problems so don't complain further. + */ + if(t->q0 > f->b.nc || t->q1 > f->b.nc || t->q0 > t->q1){ + if(!warned) + warning(nil, "elogapply: can't happen %d %d %d\n", t->q0, t->q1, f->b.nc); + t->q1 = min(t->q1, f->b.nc); + t->q0 = min(t->q0, t->q1); + } + + if(t->w) + t->w->owner = owner; +} diff --git a/exec.c b/exec.c new file mode 100644 index 0000000..ff49160 --- /dev/null +++ b/exec.c @@ -0,0 +1,1803 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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(q1file->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, n); + if(q0!=aq0 || q1!=aq1){ + n = q1-q0; + bufread(&t->file->b, q0, r, n); + if(n <= EVENTSIZE) + winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q1, n, n, r); + else + winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1, n); + } + if(a){ + winevent(t->w, "%c0 0 0 %d %s\n", c, utflen(a), a); + if(aa) + winevent(t->w, "%c0 0 0 %d %s\n", c, utflen(aa), aa); + else + winevent(t->w, "%c0 0 0 0 \n", c); + } + free(r); + free(aa); + free(a); + return; + } + if(e){ + if(e->mark && seltext!=nil) + if(seltext->what == Body){ + seq++; + filemark(seltext->w->body.file); + } + s = skipbl(r, q1-q0, &n); + s = findbl(s, n, &n); + s = skipbl(s, n, &n); + (*e->fn)(t, seltext, argt, e->flag1, e->flag2, s, n); + free(r); + return; + } + + b = runetobyte(r, q1-q0); + free(r); + dir = dirname(t, nil, 0); + if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */ + free(dir.r); + dir.r = nil; + dir.nr = 0; + } + aa = getbytearg(argt, TRUE, TRUE, &a); + if(t->w) + incref(&t->w->ref); + run(t->w, b, dir.r, dir.nr, TRUE, aa, a, FALSE); +} + +char* +printarg(Text *argt, uint q0, uint q1) +{ + char *buf; + + if(argt->what!=Body || argt->file->name==nil) + return nil; + buf = emalloc(argt->file->nname+32); + if(q0 == q1) + sprint(buf, "%.*S:#%d", argt->file->nname, argt->file->name, q0); + else + sprint(buf, "%.*S:#%d,#%d", argt->file->nname, argt->file->name, q0, q1); + return buf; +} + +char* +getarg(Text *argt, int doaddr, int dofile, Rune **rp, int *nrp) +{ + int n; + Expand e; + char *a; + + *rp = nil; + *nrp = 0; + if(argt == nil) + return nil; + a = nil; + textcommit(argt, TRUE); + if(expand(argt, argt->q0, argt->q1, &e)){ + free(e.bname); + if(e.nname && dofile){ + e.name = runerealloc(e.name, e.nname+1); + if(doaddr) + a = printarg(argt, e.q0, e.q1); + *rp = e.name; + *nrp = e.nname; + return a; + } + free(e.name); + }else{ + e.q0 = argt->q0; + e.q1 = argt->q1; + } + n = e.q1 - e.q0; + *rp = runemalloc(n+1); + bufread(&argt->file->b, e.q0, *rp, n); + if(doaddr) + a = printarg(argt, e.q0, e.q1); + *nrp = n; + return a; +} + +char* +getbytearg(Text *argt, int doaddr, int dofile, char **bp) +{ + Rune *r; + int n; + char *aa; + + *bp = nil; + aa = getarg(argt, doaddr, dofile, &r, &n); + if(r == nil) + return nil; + *bp = runetobyte(r, n); + free(r); + return aa; +} + +void +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; inw; 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; inw; 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; ifile->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; ifile->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; ifile->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; ifile->ntext; i++) + t->file->text[i]->w->dirty = dirty; + free(name); + free(r); + winsettag(w); + t->file->unread = FALSE; + for(i=0; ifile->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; + 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 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; + } + Bterm(b); + free(b); + b = nil; + 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.) + close(fd); + fd = open(name, OWRITE); + + d1 = dirfstat(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; intext; 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); + } + free(h); + fbuffree(s); + fbuffree(r); + close(fd); + /* 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; inw; 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; iw->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; iincl[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); +} diff --git a/file.c b/file.c new file mode 100644 index 0000000..e1eddc4 --- /dev/null +++ b/file.c @@ -0,0 +1,311 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; intext; 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 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; jntext; 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 RBUFSIZE) + n = RBUFSIZE; + bufread(delta, up+i, buf, n); + bufinsert(&f->b, u.p0+i, buf, n); + for(j=0; jntext; j++) + textinsert(f->text[j], u.p0+i, buf, n, FALSE); + } + *q0p = u.p0; + *q1p = u.p0+u.n; + break; + + case Filename: + f->seq = u.seq; + fileunsetname(f, epsilon); + f->mod = u.mod; + up -= u.n; + free(f->name); + if(u.n == 0) + f->name = nil; + else + f->name = runemalloc(u.n); + bufread(delta, up, f->name, u.n); + f->nname = u.n; + break; + } + bufdelete(delta, up, delta->nc); + } + if(isundo) + f->seq = 0; + Return: + fbuffree(buf); +} + +void +filereset(File *f) +{ + bufreset(&f->delta); + bufreset(&f->epsilon); + f->seq = 0; +} + +void +fileclose(File *f) +{ + free(f->name); + f->nname = 0; + f->name = nil; + free(f->text); + f->ntext = 0; + f->text = nil; + bufclose(&f->b); + bufclose(&f->delta); + bufclose(&f->epsilon); + elogclose(f); + free(f); +} + +void +filemark(File *f) +{ + if(f->epsilon.nc) + bufdelete(&f->epsilon, 0, f->epsilon.nc); + f->seq = seq; +} diff --git a/fns.h b/fns.h new file mode 100644 index 0000000..8dc0237 --- /dev/null +++ b/fns.h @@ -0,0 +1,106 @@ +/* +#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); + +Runestr runestr(Rune*, uint); +Range range(int, int); + +#define runemalloc(a) (Rune*)emalloc((a)*sizeof(Rune)) +#define runerealloc(a, b) (Rune*)erealloc((a), (b)*sizeof(Rune)) +#define runemove(a, b, c) memmove((a), (b), (c)*sizeof(Rune)) + +int ismtpt(char*); diff --git a/fsys.c b/fsys.c new file mode 100644 index 0000000..4c395eb --- /dev/null +++ b/fsys.c @@ -0,0 +1,749 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; inincl; 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; ifcall.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'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.nwqidfcall.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 && if->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; jnw; 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(; jfcall.count-n, clock); + if(len == 0) + break; + if(i >= o) + n += len; + j++; + } + free(ids); + } + t.data = (char*)b; + t.count = n; + respond(x, &t, nil); + free(b); + return x; + } + sendp(x->c, (void*)xfidread); + return nil; +} + +static +Xfid* +fsyswrite(Xfid *x, Fid *f) +{ + USED(f); + sendp(x->c, (void*)xfidwrite); + return nil; +} + +static +Xfid* +fsysclunk(Xfid *x, Fid *f) +{ + fsysdelid(f->mntdir); + sendp(x->c, (void*)xfidclose); + return nil; +} + +static +Xfid* +fsysremove(Xfid *x, Fid *f) +{ + Fcall t; + + USED(f); + return respond(x, &t, Eperm); +} + +static +Xfid* +fsysstat(Xfid *x, Fid *f) +{ + Fcall t; + + t.stat = emalloc(messagesize-IOHDRSZ); + t.nstat = dostat(WIN(x->f->qid), f->dir, t.stat, messagesize-IOHDRSZ, getclock()); + x = respond(x, &t, nil); + free(t.stat); + return x; +} + +static +Xfid* +fsyswstat(Xfid *x, Fid *f) +{ + Fcall t; + + USED(f); + return respond(x, &t, Eperm); +} + +Fid* +newfid(int fid) +{ + Fid *f, *ff, **fh; + + ff = nil; + fh = &fids[fid&(Nhash-1)]; + for(f=*fh; f; f=f->next) + if(f->fid == fid) + return f; + else if(ff==nil && f->busy==FALSE) + ff = f; + if(ff){ + ff->fid = fid; + return ff; + } + f = emalloc(sizeof *f); + f->fid = fid; + f->next = *fh; + *fh = f; + return f; +} + +uint +getclock(void) +{ + return time(0); +} + +int +dostat(int id, Dirtab *dir, uchar *buf, int nbuf, uint clock) +{ + Dir d; + + d.qid.path = QID(id, dir->qid); + d.qid.vers = 0; + d.qid.type = dir->type; + d.mode = dir->perm; + d.length = 0; /* would be nice to do better */ + d.name = dir->name; + d.uid = user; + d.gid = user; + d.muid = user; + d.atime = clock; + d.mtime = clock; + return convD2M(&d, buf, nbuf); +} diff --git a/logf.c b/logf.c new file mode 100644 index 0000000..567b838 --- /dev/null +++ b/logf.c @@ -0,0 +1,199 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; if) { + 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; iflushed) { + 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; ifcall.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.f[i]->logoff) + min = eventlog.f[i]->logoff; + } + if(min > eventlog.start) { + n = min - eventlog.start; + for(i=0; i= eventlog.mev) { + eventlog.mev = eventlog.mev*2; + if(eventlog.mev == 0) + eventlog.mev = 8; + eventlog.ev = erealloc(eventlog.ev, eventlog.mev*sizeof eventlog.ev[0]); + } + } + f = w->body.file; + name = runetobyte(f->name, f->nname); + if(name == nil) + name = estrdup(""); + eventlog.ev[eventlog.nev++] = smprint("%d %s %s\n", w->id, op, name); + free(name); + if(eventlog.r.l == nil) + eventlog.r.l = &eventlog.lk; + rwakeupall(&eventlog.r); + qunlock(&eventlog.lk); +} diff --git a/look.c b/look.c new file mode 100644 index 0000000..44e23cc --- /dev/null +++ b/look.c @@ -0,0 +1,872 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include <9pclient.h> +#include +#include +#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(q1file->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= 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(nbfile->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; inincl && 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, c; + uint m, nt; + int slash; + Runestr tmp; + + b = nil; + if(t==nil || t->w==nil) + goto Rescue; + nt = t->w->tag.file->b.nc; + if(nt == 0) + goto Rescue; + if(n>=1 && r[0]=='/') + goto Rescue; + b = runemalloc(nt+n+1); + bufread(&t->w->tag.file->b, 0, b, nt); + slash = -1; + for(m=0; m= 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(q1file->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(colonfile->b.nc-1 && isaddrc(textreadc(t, colon+1))){ + q1 = colon+1; + while(q1file->b.nc && isaddrc(textreadc(t, q1))) + q1++; + } + } + if(q1 > q0) + if(colon >= 0){ /* stop at white space */ + for(amax=colon+1; amaxfile->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; ifile->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, 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)=='<' && q1file->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(q1file->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; jnw; 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; jnw; 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); + } +} diff --git a/mail/dat.h b/mail/dat.h new file mode 100644 index 0000000..a7ac6a0 --- /dev/null +++ b/mail/dat.h @@ -0,0 +1,181 @@ +typedef struct Event Event; +typedef struct Exec Exec; +typedef struct Message Message; +typedef struct Window Window; + +enum +{ + STACK = 8192, + EVENTSIZE = 256, + NEVENT = 5 +}; + +struct Event +{ + int c1; + int c2; + int q0; + int q1; + int flag; + int nb; + int nr; + char b[EVENTSIZE*UTFmax+1]; + Rune r[EVENTSIZE+1]; +}; + +struct Window +{ + /* coordinate wineventproc and window thread */ + QLock lk; + int ref; + + /* file descriptors */ + CFid* ctl; + CFid* event; + CFid* addr; + CFid* data; + CFid* body; + + /* event input */ + char buf[512]; + char *bufp; + int nbuf; + Event e[NEVENT]; + + int id; + int open; + Channel *cevent; +}; + +struct Message +{ + Window *w; + CFid* ctlfd; + char *name; + char *replyname; + uchar opened; + uchar dirty; + uchar isreply; + uchar deleted; + uchar writebackdel; + uchar tagposted; + uchar recursed; + uchar level; + uint replywinid; + + /* header info */ + char *from; + char *fromcolon; + char *to; + char *cc; + char *replyto; + char *sender; + char *date; + char *subject; + char *type; + char *disposition; + char *filename; + char *digest; + + Message *next; /* next in this mailbox */ + Message *prev; /* prev in this mailbox */ + Message *head; /* first subpart */ + Message *tail; /* last subpart */ +}; + +enum +{ + NARGS = 100, + NARGCHAR = 8*1024, + EXECSTACK = STACK+(NARGS+1)*sizeof(char*)+NARGCHAR +}; + +struct Exec +{ + char *prog; + char **argv; + int p[2]; /* p[1] is write to program; p[0] set to prog fd 0*/ + int q[2]; /* q[0] is read from program; q[1] set to prog fd 1 */ + Channel *sync; +}; + +extern Window* newwindow(void); +extern CFid* winopenfile(Window*, char*); +extern void winopenbody(Window*, int); +extern void winclosebody(Window*); +extern void wintagwrite(Window*, char*, int); +extern void winname(Window*, char*); +extern void winwriteevent(Window*, Event*); +extern void winread(Window*, uint, uint, char*); +extern int windel(Window*, int); +extern void wingetevent(Window*, Event*); +extern void wineventproc(void*); +extern void winwritebody(Window*, char*, int); +extern void winclean(Window*); +extern int winselect(Window*, char*, int); +extern char* winselection(Window*); +extern int winsetaddr(Window*, char*, int); +extern char* winreadbody(Window*, int*); +extern void windormant(Window*); +extern void winsetdump(Window*, char*, char*); +extern void winincref(Window*); +extern void windecref(Window*); + +extern void readmbox(Message*, char*, char*); +extern void rewritembox(Window*, Message*); + +extern void mkreply(Message*, char*, char*, Plumbattr*, char*); +extern void delreply(Message*); + +extern int mesgadd(Message*, char*, Dir*, char*); +extern void mesgmenu(Window*, Message*); +extern void mesgmenunew(Window*, Message*); +extern int mesgopen(Message*, char*, char*, Message*, int, char*); +extern void mesgctl(void*); +extern void mesgsend(Message*); +extern void mesgdel(Message*, Message*); +extern void mesgmenudel(Window*, Message*, Message*); +extern void mesgmenumark(Window*, char*, char*); +extern void mesgmenumarkdel(Window*, Message*, Message*, int); +extern Message* mesglookup(Message*, char*, char*); +extern Message* mesglookupfile(Message*, char*, char*); +extern void mesgfreeparts(Message*); +extern int mesgcommand(Message*, char*); + +extern char* info(Message*, int, int); +extern char* readfile(char*, char*, int*); +extern char* readbody(char*, char*, int*); +extern void ctlprint(CFid*, char*, ...); +extern void* emalloc(uint); +extern void* erealloc(void*, uint); +extern char* estrdup(char*); +extern char* estrstrdup(char*, char*); +extern char* egrow(char*, char*, char*); +extern char* eappend(char*, char*, char*); +extern void error(char*, ...); +extern int tokenizec(char*, char**, int, char*); +extern void execproc(void*); +extern int fsprint(CFid*, char*, ...); + +#pragma varargck argpos error 1 +#pragma varargck argpos ctlprint 2 + +extern Window *wbox; +extern Message mbox; +extern Message replies; +extern char *fsname; +extern CFid *plumbsendfd; +extern CFid *plumbseemailfd; +extern char *home; +extern char *outgoing; +extern char *mailboxdir; +extern char *mboxname; +extern char *user; +extern char *srvname; +extern char deleted[]; +extern int wctlfd; +extern int shortmenu; + +extern CFsys *mailfs; +extern CFsys *acmefs; + diff --git a/mail/guide b/mail/guide new file mode 100644 index 0000000..8977ac7 --- /dev/null +++ b/mail/guide @@ -0,0 +1,4 @@ +Mail stored +plumb /mail/box/$user/names +mail -'x' someaddress +mkbox /mail/box/$user/new_box diff --git a/mail/html.c b/mail/html.c new file mode 100644 index 0000000..e3a956b --- /dev/null +++ b/mail/html.c @@ -0,0 +1,75 @@ +#include +#include +#include +#include +#include +#include +#include <9pclient.h> +#include "dat.h" + +char* +formathtml(char *body, int *np) +{ + int i, j, p[2], q[2]; + Exec *e; + char buf[1024]; + Channel *sync; + + e = emalloc(sizeof(struct Exec)); + if(pipe(p) < 0 || pipe(q) < 0) + error("can't create pipe: %r"); + + e->p[0] = p[0]; + e->p[1] = p[1]; + e->q[0] = q[0]; + e->q[1] = q[1]; + e->argv = emalloc(3*sizeof(char*)); + e->argv[0] = estrdup("htmlfmt"); + e->argv[1] = estrdup("-cutf-8"); + e->argv[2] = nil; + e->prog = "htmlfmt"; + sync = chancreate(sizeof(int), 0); + e->sync = sync; + proccreate(execproc, e, EXECSTACK); + recvul(sync); + close(p[0]); + close(q[1]); + + if((i=write(p[1], body, *np)) != *np){ + fprint(2, "Mail: warning: htmlfmt failed: wrote %d of %d: %r\n", i, *np); + close(p[1]); + close(q[0]); + return body; + } + close(p[1]); + + free(body); + body = nil; + i = 0; + for(;;){ + j = read(q[0], buf, sizeof buf); + if(j <= 0) + break; + body = realloc(body, i+j+1); + if(body == nil) + error("realloc failed: %r"); + memmove(body+i, buf, j); + i += j; + body[i] = '\0'; + } + close(q[0]); + + *np = i; + return body; +} + +char* +readbody(char *type, char *dir, int *np) +{ + char *body; + + body = readfile(dir, "body", np); + if(body != nil && strcmp(type, "text/html") == 0) + return formathtml(body, np); + return body; +} diff --git a/mail/html.o b/mail/html.o new file mode 100644 index 0000000000000000000000000000000000000000..b9af5f96d81af6002c6e090e953622ea740f5b7a GIT binary patch literal 17352 zcmbuGc{~=~|NlpqtQA7b#gnBFp8nls#)H66La$P%1)Hx9${@6tZP) zlW0XHC6Ur{Q9wf-T0jkZEs>9E}`|wph@9*3081M)r2({bSnf7)GXD{cC!5 z){C6NE<==%X{PO}4eyBBK}G{ih!H3U^~Yw?zt}Z8PL!IKx5gENJY~ zjJhdwdpG3QW0)1~En!#Og4RYav!XG)OQHKuM=5o+&}VW=9?|2Jx=MU&MH?qm1R0X~ zI*nmrv?g+z2GNpg!$#W2`Z@R!FhIz`;qU&FQ%u{v$m)#H@h0}+YV!VDL|mwt8yLOmak`%twARFieQG9Y(j+`0MrmQvY-Gwm zps0|3eLmS+(0c61v}rPJ5=4wqA+aG$ z?D{;yTSjlmw1F?@c#k*5J;M^Z!xrE{acu_u*@h1snbwArk*z;xXEBOLpj?f6eJ^L~X{GNL z6aHh-tF`zd)n~tBgL)=Bg=Tyw%+fXt8TGK6+zgy7Dan-;mXy1lx8uZxkv$tX6zavA zTC8aEp{f_<^V)cUJoLoY1^dL_S$Tvpo_zJ%Li-#sy7nGACL z;%cWNCyu8oQT$2Xi)oEE)#nC#*$RdC-Q9UD&MfS??E4Ka<7zf~2R69m(u!_2U2!6n zznI|DG~=6IH}b4xz$e>!E|>e+zoYzH@+NM?4i2bj*xxs(0#z4)>`+c#38@H$0 z?MZX=H$VSqvCCV}Y|qDOR@YdU>gk&G#QTj+lSJN=Z6ThH)mrc4 zxQ!jpQ%3DQgF?sRuaN1aQQg{N9p*VZ6dnASR%cqm?NGGga=%nt zQ+hc6ZKoV>m2$=M=kmud8!KxUPdnzTihMs7xkz1CC}obo7Pll2{#ztEti^$GNG3T&t4@x3<)Px6x(kH1NGbhh#l|)3$ZgxJc)UvWO@J(;-%@^d& zQnMcHPbNQi4sj}9wp4cQSoQ7wI)fF#nMX7RCD=}tWm)8p|LyzNfex%(@I!!4MS_#b zp6rcDIotgz7FqD6ee|kGtzMe;kzy-($;{HFBVco0ps#KBjlKvDv1NG&W$tr-3C+%5 z6f1b+OX!|ozO@TO&DrX?&&-wO+9g%}Y`bivWU<-pLP>v*BA%U&FEl+=+Ic28w#2S! z8LDV%Zqa_X(esRNm7Q|in|DV#gvntqH}#z~lU$W?N2+3;g^8EO(eEbu3B|N_QR(?f zF-t7eGY*SyB^S>>ESD$rG)<&ZaKxxvCW954!j=uZfS#6n!KANct*yJ+0k5 zV@&+Bs&q|KU$R$xdt1g*>Cbbe&6E5MLwPf94rPW6^g-C}^-Lg&n z}H~1yBj(dhG-!yZo4D!nA7@c_jmx|~!gVv$iwPWeZ62@mX?6$+haSz$&n;-OAdZ%Q3^Q~g% zbqZRwcYC8m;6W9s@DDnNOI&l4I9%4WM$#b;2ml*&15`?4@U zbJxpebDmH6i@q(?yZDik>wIHGYrkY)NabnN--?yx{3nf`G<>lMC<77d@vbg4Ks zadOPC)3?JXTRQ((<*Lh~iv!n9?|*3bVUO^9THYyVwWex0_r@opr%B_(m)PAyJd&UB zz0}dHKYKBYZ)W&N1RE$lINEN#^7>c5d^r`X zyY`Bj$tyP9O|489s@3Apw;S{jkdvdH`|&Q;(V(d3@cV+>_6uv*Jy>@>%;2ArO23sG z*G-oHIAdFHb?H%mYQNXe2nnCnUXkNz&*OP=R9&4?==SqtqNhMf)ccHt`%7wbUb;MN*?HwnL#a2< zi!9CDHerSRji##_&%2wc=nb}ch^lXouwUw~dP(odOOAKFega3`CLCH@WId;Eh^t1o zZ=(h`1Zdf;9c$5Ex=Q-VUD+txTlY>Fjmu`deX3EO9cw-)VJ&NOpqaKY_|~?7)zexA z+80kS-#0ju(EA3e?QPp8L%uN?#e$tpQpQPf@t;3)oUw^^@_p0VyK=+Nxd!p42G%<5 zOWt={U2M77^}GLk4YU4usXgV`>y@`lw4-gv!)wJKB-q-;V>@lUkbK--hoWI4rBM`d&_=l`p8I&}i|I)HvSmwpxNR?$Ej=rFp=XyMS%-Q97C;6(Em`TJpr#PR^i+6?cZd-}FU zkqG6;wp9^n?3LfH*gWv-N(Sb3g4f{vj;a9qiVrdr!+}?wlfR_Ebuo*FCR=4}@pg zo)%qK6Z_VG?g&2?d|`!}`p^wjlCOl%BecjlKC z3gysxBtAAhC52eU9TMen(zL;R9e0d?0*AhslET#|X-_yScQnqowdbua z4xba*bK7pWN2x{0+)qb?E<6}1JG^ktBl-IA>Gd2l0TUVF0i3~h#Zw0-jOs3Q?i0So z6MQiscXgm6?<{Z4UB70BCC_`W7*{q{%hUdTp5LRva7A|YX=q|5TSrc!|jZ4+ATQI0m%3FnR1ZEguSrrAZsXXy=cXg6&5 zw8SOnE+=`yWB63i+vzpxN8NTAYj3qRmj4-kmZoBA6*f@X8SVJi#-YU`QhJL;l}M&n zXwNcpo5+ya;NYnHrJomnA6pZte!_8nWUgcX>Bz~7!NVTW4!^F*S9lCeKR1`%aWa}e z-N;ho`1hzI_ug8bQ=`?41{;FuuE#|kU%r|o{Hy@`vxf-hb^Lw8QY zop}Z8b4>3SmNqX|tPO8i6!63~Zg0Hbi(;#oL|36oscW#5S zFufo@#uN?X2rNy{UyFig%Z6#0v9 z8#d^{o%3sAA4yH`?0x!7g!4;{i!m`_J}Q^G<$3yZO};$q3ErqYc2j4AA&&rm+k$}P zKwIe`cGGm}M?xiUS}Oh-)l)y(|AF&UqNL2dT(NIXBxAu33oK=N9Q;0p9UMDtBYaX! z@bt)G?;5PNEw$iu%o@2P*)cU4T&p!E=1`aObADLs>U6w;!%;kaNmu;g!sqSQ3r|#; z)PxkqRcY?;HGOEtrc&Xi=Dyptv(I6rWeWFbclD&@V}ZJzJ?}=lx?Dm?`sPjDZES8? zulgSwS8j_>D9S%@Zcjwau(I|?WxbVEVs$*zc_L$*#GKu+Pme#Q+WRb6wEOF#lh4vu zkBg)k`m6VK+A9RlGeQ8SN;|@{O4G*R$OsZzHtxvS588qfv z*(-iSAo)mF?%#(xgaS=V&goi5marX?IGu5=h)dayb>-C3I3^amZe)@7cS8Wa|0NO%l7VT^P_kY|LG*xIo`^B3a~z z+LT^%t(t9BWwTX-`W872n%c3{pYgB8j=H!77eAsF_McVaf zSzh8}ue52+LUM=dmLe(hFU_MK_LBM1MkigntZ#q+QNQ$d2Klge*V0WtKHLkE-nxoU zSNqiUmphM_axK&v7-74_=J0UGUmD8?AAHl`b8_3wai_6;%_;unHw~87$J818;>_2d zt<$;rGx|#Xo;{P^SC-AIGtv(WyCLWE&Q|VWZ1v3Nebc|r->oXL_7M}A`5ySaN+R&} z%g(Be(hnn22gxVh+WXaB$F#)wr#-&@;^@M8XK8794*XARS5gnR4^O;%m%ZL+&aMq# zd$JS`NDp>I&$?AUjMiw~A^w(+=i!CeH<;z4jm3qzJKe+|b(=bN@q{wuubJWz(u06&BOKP^BH<8pr_xkK)rD)$>acg1fe6(x5T$V#9~LRKkV4V z?4OEBzpsY>E+t{$;`ncjQ{r*#e-jA?EsM8f$016SchfN4I^=P1rJ@xMPshnSmx;`S`~HWr-1 zf(NqTkt}#T3!cV;XR_d@Snx9}_+KpeWfpt|enS2!Zr51o8(HvfoD<`<8Qdb%*9e?F zXQ3a$xdhYs3(w?fz+oQld2v4pJzQQOmj-+RUXMtS(;v+%Q!r(#^{T{1jo7FYo7Kc- z4YAQ6Hkz1sP-uWh82#zIo4J*>v5}RVwV9cnslA)Mk+GGj8%7PFKh?*Ab%+Q5782A*@%N+RhX0L&{lf!15f(i7Jp*lskF zp17t$4zHNR9RXaM@Iwr)M+7IXxoSWNuXK=K1-CYY4@83e2P2D#fISgD-~ycxeIXK| zhiwu0)x-u*(2fTYSUhPlcE5QsFb1#3P_GCs^`?k^1JGL`d- z?hbe=!o`6<83;E3`8jsR!3K*~Xz%R&$fj>(S4)5b+5Pk%-r-SfPkT*qm7s%Tp{1foU4dLs6KmG{U z06ZGu{9yhcM7S%6TPDIUfH)T*{3OuBe!%!#1@`L@J#kGV(rtu$0XvToZqATnuMsW- zI6MhqyC;F4!oVKn$si6(5zYtVDTVM8pgnnn4+HzA2>$~5YlHBcKo8>z{b>dM`yu*D z;7>ThCjsA&@NwX08p3Y^4#x+!3$IV-5IqcMBf?8SyFCaG0sR_A_-zn}uLy@f_1M5T zKz|_TMEEOEUyuctK=>%|Qw8B}Aa8+i7+)8J4}!S)vEY#ir+~o0=f$vJq98t*h<+uo z--z&O(BC11Ujuv+;md&?cF=FwF67G*&I|leL^vC;?~HJG-?{m9>i6I=$GkF%=7~x*P|IG;h0OGJ6;R@io0N(>af1H61t~Zdw zc$Fe{GC{r*;d4Q|{Rp1~`S%Ei*H>5$+pPvVb`U?v4FQMiJuz)m;K`2fUw z&j`SY>dH*k|q5c!1;b9E=|?!aIRKVl4PpgwF%zD3~*h9cY`>XBfOO124;_N{FORAc_19# zrv)M$-lxSN`~dJLi3P7kcoy)p1K|gOKaUWe0{Cl$gYaWN5v~H})qL=3fhZ=PC%|=8 z4dH8m-Vou+pkEaT7X|aA8sWyEU-bxw*Y7rjZvy>#i10;V-o8M1Gw^2=;fFxGlL+4j z`b7c{J764=0p~^d1K^Js!o@*<@*PaN6>qa2L;d8Ft z2%iM~I*4#Rz;h7Z4E!lZI5+U;GT=mYBE;}HeJi4e=kEh1PAmz)Jl2bF5H{=$!VN*e z2*TlcI)(5Ypr1i_D&TCyq{kB+cX+-mVB&I1z_dpkIve2EfGuhvV1| z;-$aeZc?y2p}`dbJ$1-t`r*e|$_y+Js99{m&HW{l~9 zE#hSyKs(NWs~|i9a5IF%^<*0pCtM_+-4lt*#EGeYkxnseHxnoH@cC*26DRcWxo`o( z;r(Yl!l!`!#|S5Z@f|`q58x9BC;p?2NOSoZdl(-Rz@?ZtSo-NgB_>YzZw>Ui2zLhD z9O1_RcVXgS$m#aInK)s;4Cr?uydLmWgbNUrhwU5#P)nK)rW57_BL zIQ)Kq4ZMJX>(58f2?2!Hf^}v&!jFS?S0X$CoL^c9F9U->Mz{j7Z;$ZDfV(5S1Na$$ z@D;#*2n!yKaM<6y2#4?05)cmkPeM2mKO!ALcr1v277Lz>@LRzD0v5ak;ZK46QWpFw z!a2aWTxY=>5q=uzTUhYB2#53b0So>V;Yy6KVf`%lTZGpDf8Mj;Ul1+>=E)=rK8tWM zV4uYJ0O7930$x}V@yo*q$fx?{xh0;UL^ z!}mle7pIppw9NX=2#5E{nDVaZfG`hFd>%pA==A@`P8_dG z#2AhbG-U`5;C(aXaJ*oi_@;yydP4}nzKbZp(`v9T67R{0cEIB!{C~(9NfvM*0~EvH z`=Z|f91u2`lOmoWY@+qwfysgT3#RFB6RP+TVN>llVI08vWQ4=^;ocB0|5JU~Z}@%$ znt}a?_3c6Z-+=)_U(lg7uma#n7PH3rpW;vd_ysQuCO(?M6A^#nyo2>&{M~Uz_XQT< zc_I~noA9&HQ3n29A7rsW{NPXZ$>4WH+W({eK3E?b1p8iaf4Lgrh&e18FaJ~jgV!=D z!umx2i7vrz!v3e>>`(r4gZsBN3^TvCus#F@_`#p*8-n`Vm=`Hri`FN;llqhY1;Bsk zC*eO45?CK{;{KOte>E(?kBJ1Y%Ol8%0c9|b*KjSpKE2leHZ-C^Jb~q$k-@jH9^5xD zvEL&6;1sCO%!p%H5At5XkT78dpnen-f<5#f;x7E)Pw}h%!}ts0T38>(kMIX=o3OnE y{NRWqjN^&O5_Qn}#BWk0#5lpQ98vlX(?mLqWa;0={i*&t;QtGTncrKq{{I0l=?&}v literal 0 HcmV?d00001 diff --git a/mail/mail.c b/mail/mail.c new file mode 100644 index 0000000..d79721f --- /dev/null +++ b/mail/mail.c @@ -0,0 +1,644 @@ +#include +#include +#include +#include +#include <9pclient.h> +#include +#include +#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; iname) != 0; m=m->prev); + free(save); + if(m == nil) + break; + fsprint(sbox->body, "%s%s\n", m->name, info(m, 0, 0)); + m = m->prev; + } + free(res); + winclean(sbox); + winclosebody(sbox); + return 1; + } + return 0; +} + +void +mainctl(void *v) +{ + Window *w; + Event *e, *e2, *eq, *ea; + int na, nopen; + char *s, *t, *buf; + + w = v; + winincref(w); + proccreate(wineventproc, w, STACK); + + for(;;){ + e = recvp(w->cevent); + switch(e->c1){ + default: + Unknown: + print("unknown message %c%c\n", e->c1, e->c2); + break; + + case 'E': /* write to body; can't affect us */ + break; + + case 'F': /* generated by our actions; ignore */ + break; + + case 'K': /* type away; we don't care */ + break; + + case 'M': + switch(e->c2){ + case 'x': + case 'X': + ea = nil; + e2 = nil; + if(e->flag & 2) + e2 = recvp(w->cevent); + if(e->flag & 8){ + ea = recvp(w->cevent); + na = ea->nb; + recvp(w->cevent); + }else + na = 0; + s = e->b; + /* if it's a known command, do it */ + if((e->flag&2) && e->nb==0) + s = e2->b; + if(na){ + t = emalloc(strlen(s)+1+na+1); + sprint(t, "%s %s", s, ea->b); + s = t; + } + /* if it's a long message, it can't be for us anyway */ + if(!mboxcommand(w, s)) /* send it back */ + winwriteevent(w, e); + if(na) + free(s); + break; + + case 'l': + case 'L': + buf = nil; + eq = e; + if(e->flag & 2){ + e2 = recvp(w->cevent); + eq = e2; + } + s = eq->b; + if(eq->q1>eq->q0 && eq->nb==0){ + buf = emalloc((eq->q1-eq->q0)*UTFmax+1); + winread(w, eq->q0, eq->q1, buf); + s = buf; + } + nopen = 0; + do{ + /* skip 'deleted' string if present' */ + if(strncmp(s, deleted, strlen(deleted)) == 0) + s += strlen(deleted); + /* skip mail box name if present */ + if(strncmp(s, mbox.name, strlen(mbox.name)) == 0) + s += strlen(mbox.name); + nopen += mesgopen(&mbox, mbox.name, s, nil, 0, nil); + while(*s!='\0' && *s++!='\n') + ; + }while(*s); + if(nopen == 0) /* send it back */ + winwriteevent(w, e); + free(buf); + break; + + case 'I': /* modify away; we don't care */ + case 'D': + case 'd': + case 'i': + break; + + default: + goto Unknown; + } + } + } +} + diff --git a/mail/mail.o b/mail/mail.o new file mode 100644 index 0000000000000000000000000000000000000000..331d8bdd1dd9cd1824f41bb8775761a4f091f366 GIT binary patch literal 65248 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